From 5598d0794441e7d452dd8b2aead7739c22af2807 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 17 Apr 2021 15:58:43 +0100 Subject: [PATCH] Added crafting stations --- assets/common/recipe_book.ron | 201 +++++++++--------- client/src/lib.rs | 32 ++- common/src/comp/controller.rs | 18 +- common/src/comp/inventory/item/modular.rs | 12 +- common/src/recipe.rs | 18 +- server/src/events/inventory_manip.rs | 24 ++- voxygen/src/hud/crafting.rs | 10 +- voxygen/src/hud/mod.rs | 38 +++- voxygen/src/scene/terrain.rs | 2 +- voxygen/src/scene/terrain/watcher.rs | 12 +- voxygen/src/session/mod.rs | 47 ++-- .../settlement/building/archetype/house.rs | 44 +++- 12 files changed, 283 insertions(+), 175 deletions(-) diff --git a/assets/common/recipe_book.ron b/assets/common/recipe_book.ron index ef20666454..a193e72204 100644 --- a/assets/common/recipe_book.ron +++ b/assets/common/recipe_book.ron @@ -1,59 +1,59 @@ { "crafting_hammer": ( - ("common.items.crafting_tools.craftsman_hammer", 1), - [ + output: ("common.items.crafting_tools.craftsman_hammer", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 6), (Item("common.items.crafting_ing.stones"), 6), ], ), "mortar_pestle": ( - ("common.items.crafting_tools.mortar_pestle", 1), - [ + output: ("common.items.crafting_tools.mortar_pestle", 1), + inputs: [ (Item("common.items.crafting_ing.stones"), 6), (Item("common.items.food.coconut"), 1), (Item("common.items.crafting_tools.craftsman_hammer"), 0), ], ), "sewing_set": ( - ("common.items.crafting_tools.sewing_set", 1), - [ + output: ("common.items.crafting_tools.sewing_set", 1), + inputs: [ (Item("common.items.crafting_ing.leather_scraps"), 2), (Item("common.items.crafting_ing.twigs"), 4), (Item("common.items.crafting_ing.stones"), 2), ], ), "velorite_frag": ( - ("common.items.ore.veloritefrag", 2), - [ + output: ("common.items.ore.veloritefrag", 2), + inputs: [ (Item("common.items.ore.velorite"), 1), (Item("common.items.crafting_tools.craftsman_hammer"), 0), ], ), "potion_s": ( - ("common.items.consumable.potion_minor", 1), - [ + output: ("common.items.consumable.potion_minor", 1), + inputs: [ (Item("common.items.crafting_ing.empty_vial"), 1), (Item("common.items.food.apple"), 4), (Item("common.items.crafting_ing.honey"), 1), ], ), "potion_m": ( - ("common.items.consumable.potion_med", 1), - [ + output: ("common.items.consumable.potion_med", 1), + inputs: [ (Item("common.items.consumable.potion_minor"), 2), (Item("common.items.ore.veloritefrag"), 4), ], ), "collar_basic": ( - ("common.items.utility.collar", 1), - [ + output: ("common.items.utility.collar", 1), + inputs: [ (Item("common.items.crafting_ing.leather_scraps"), 5), (Item("common.items.crafting_ing.ruby"), 1), ], ), "bomb_coconut": ( - ("common.items.utility.bomb", 1), - [ + output: ("common.items.utility.bomb", 1), + inputs: [ (Item("common.items.crafting_ing.stones"), 10), (Item("common.items.food.coconut"), 2), (Item("common.items.ore.veloritefrag"), 2), @@ -61,8 +61,8 @@ ], ), "firework_blue": ( - ("common.items.utility.firework_blue", 1), - [ + output: ("common.items.utility.firework_blue", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 1), (Item("common.items.crafting_ing.stones"), 1), (Item("common.items.food.coconut"), 1), @@ -71,8 +71,8 @@ ], ), "firework_green": ( - ("common.items.utility.firework_green", 1), - [ + output: ("common.items.utility.firework_green", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 1), (Item("common.items.crafting_ing.stones"), 1), (Item("common.items.food.coconut"), 1), @@ -81,8 +81,8 @@ ], ), "firework_purple": ( - ("common.items.utility.firework_purple", 1), - [ + output: ("common.items.utility.firework_purple", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 1), (Item("common.items.crafting_ing.stones"), 1), (Item("common.items.food.coconut"), 1), @@ -91,8 +91,8 @@ ], ), "firework_red": ( - ("common.items.utility.firework_red", 1), - [ + output: ("common.items.utility.firework_red", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 1), (Item("common.items.crafting_ing.stones"), 1), (Item("common.items.food.coconut"), 1), @@ -101,8 +101,8 @@ ], ), "firework_white": ( - ("common.items.utility.firework_white", 1), - [ + output: ("common.items.utility.firework_white", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 1), (Item("common.items.crafting_ing.stones"), 1), (Item("common.items.food.coconut"), 1), @@ -111,8 +111,8 @@ ], ), "firework_yellow": ( - ("common.items.utility.firework_yellow", 1), - [ + output: ("common.items.utility.firework_yellow", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 1), (Item("common.items.crafting_ing.stones"), 1), (Item("common.items.food.coconut"), 1), @@ -121,8 +121,8 @@ ], ), "apple_shroom_curry": ( - ("common.items.food.apple_mushroom_curry", 1), - [ + output: ("common.items.food.apple_mushroom_curry", 1), + inputs: [ (Item("common.items.food.mushroom"), 8), (Item("common.items.food.coconut"), 1), (Item("common.items.food.apple"), 4), @@ -130,37 +130,38 @@ ], ), "salad_plain": ( - ("common.items.food.plainsalad", 1), - [ + output: ("common.items.food.plainsalad", 1), + inputs: [ (Item("common.items.food.lettuce"), 1), (Item("common.items.crafting_ing.bowl"), 1), ], + craft_sprite: Some(Pot), ), "salad_tomato": ( - ("common.items.food.tomatosalad", 1), - [ + output: ("common.items.food.tomatosalad", 1), + inputs: [ (Item("common.items.food.lettuce"), 1), (Item("common.items.food.tomato"), 2), (Item("common.items.crafting_ing.bowl"), 1), ], ), "apples_stick": ( - ("common.items.food.apple_stick", 1), - [ + output: ("common.items.food.apple_stick", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 2), (Item("common.items.food.apple"), 2) ], ), "mushroom_stick": ( - ("common.items.food.mushroom_stick", 1), - [ + output: ("common.items.food.mushroom_stick", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 2), (Item("common.items.food.mushroom"), 3), ], ), "sunflower_icetea": ( - ("common.items.food.sunflower_icetea", 4), - [ + output: ("common.items.food.sunflower_icetea", 4), + inputs: [ (Item("common.items.crafting_ing.empty_vial"), 1), (Item("common.items.crafting_ing.icy_fang"), 1), (Item("common.items.flowers.sunflower"), 4), @@ -168,8 +169,8 @@ ], ), "Plain Cloth Glider": ( - ("common.items.glider.glider_basic_white", 1), - [ + output: ("common.items.glider.glider_basic_white", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 5), (Item("common.items.crafting_ing.leather_scraps"), 5), (Item("common.items.crafting_ing.cloth_scraps"), 10), @@ -178,8 +179,8 @@ ], ), "Red Cloth Glider": ( - ("common.items.glider.glider_basic_red", 1), - [ + output: ("common.items.glider.glider_basic_red", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 5), (Item("common.items.crafting_ing.cloth_scraps_red"), 10), (Item("common.items.crafting_ing.leather_scraps"), 5), @@ -188,8 +189,8 @@ ], ), "Leaves Glider": ( - ("common.items.glider.glider_leaves", 1), - [ + output: ("common.items.glider.glider_leaves", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 5), (Item("common.items.crafting_ing.leather_scraps"), 5), (Item("common.items.crafting_ing.cloth_scraps"), 5), @@ -199,8 +200,8 @@ ], ), "Sand Raptor Wings": ( - ("common.items.glider.glider_sandraptor", 1), - [ + output: ("common.items.glider.glider_sandraptor", 1), + inputs: [ (Item("common.items.crafting_ing.raptor_feather"), 6), (Item("common.items.crafting_ing.twigs"), 5), (Item("common.items.crafting_ing.leather_scraps"), 5), @@ -211,8 +212,8 @@ ], ), "Snow Raptor Wings": ( - ("common.items.glider.glider_snowraptor", 1), - [ + output: ("common.items.glider.glider_snowraptor", 1), + inputs: [ (Item("common.items.crafting_ing.raptor_feather"), 6), (Item("common.items.crafting_ing.twigs"), 5), (Item("common.items.crafting_ing.leather_scraps"), 5), @@ -224,8 +225,8 @@ ], ), "Wood Raptor Wings": ( - ("common.items.glider.glider_woodraptor", 1), - [ + output: ("common.items.glider.glider_woodraptor", 1), + inputs: [ (Item("common.items.crafting_ing.raptor_feather"), 6), (Item("common.items.crafting_ing.twigs"), 15), (Item("common.items.crafting_ing.leather_scraps"), 5), @@ -236,8 +237,8 @@ ], ), "Soothing Loop": ( - ("common.items.weapons.sceptre.loops0", 1), - [ + output: ("common.items.weapons.sceptre.loops0", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 20), (Item("common.items.ore.veloritefrag"), 8), (Item("common.items.crafting_ing.ruby"), 4), @@ -245,16 +246,16 @@ ], ), "Hunting Bow": ( - ("common.items.weapons.bow.wood-2", 1), - [ + output: ("common.items.weapons.bow.wood-2", 1), + inputs: [ (Item("common.items.crafting_ing.leather_scraps"), 8), (Item("common.items.crafting_ing.twigs"), 6), (Item("common.items.crafting_ing.stones"), 0), ], ), "Forest Spirit": ( - ("common.items.weapons.sword.wood-2", 1), - [ + output: ("common.items.weapons.sword.wood-2", 1), + inputs: [ (Item("common.items.crafting_ing.leather_scraps"), 4), (Item("common.items.crafting_ing.twigs"), 10), (Item("common.items.ore.veloritefrag"), 1), @@ -262,36 +263,36 @@ ], ), "adventure back": ( - ("common.items.armor.agile.back", 1), - [(Item("common.items.crafting_ing.leather_scraps"), 4)], + output: ("common.items.armor.agile.back", 1), + inputs: [(Item("common.items.crafting_ing.leather_scraps"), 4)], ), "adventure belt": ( - ("common.items.armor.agile.belt", 1), - [(Item("common.items.crafting_ing.leather_scraps"), 2)], + output: ("common.items.armor.agile.belt", 1), + inputs: [(Item("common.items.crafting_ing.leather_scraps"), 2)], ), "adventure chest": ( - ("common.items.armor.agile.chest", 1), - [(Item("common.items.crafting_ing.leather_scraps"), 12)], + output: ("common.items.armor.agile.chest", 1), + inputs: [(Item("common.items.crafting_ing.leather_scraps"), 12)], ), "adventure feet": ( - ("common.items.armor.agile.foot", 1), - [(Item("common.items.crafting_ing.leather_scraps"), 3)], + output: ("common.items.armor.agile.foot", 1), + inputs: [(Item("common.items.crafting_ing.leather_scraps"), 3)], ), "adventure hands": ( - ("common.items.armor.agile.hand", 1), - [(Item("common.items.crafting_ing.leather_scraps"), 4)], + output: ("common.items.armor.agile.hand", 1), + inputs: [(Item("common.items.crafting_ing.leather_scraps"), 4)], ), "adventure pants": ( - ("common.items.armor.agile.pants", 1), - [(Item("common.items.crafting_ing.leather_scraps"), 8)], + output: ("common.items.armor.agile.pants", 1), + inputs: [(Item("common.items.crafting_ing.leather_scraps"), 8)], ), "adventure shoulder": ( - ("common.items.armor.agile.shoulder", 1), - [(Item("common.items.crafting_ing.leather_scraps"), 12)], + output: ("common.items.armor.agile.shoulder", 1), + inputs: [(Item("common.items.crafting_ing.leather_scraps"), 12)], ), "Seashell Necklace": ( - ("common.items.armor.misc.neck.shell", 1), - [ + output: ("common.items.armor.misc.neck.shell", 1), + inputs: [ (Item("common.items.crafting_ing.cloth_scraps"), 2), (Item("common.items.crafting_ing.sapphire"), 1), (Item("common.items.crafting_ing.seashells"), 3), @@ -299,46 +300,46 @@ ], ), "red cloth": ( - ("common.items.crafting_ing.cloth_scraps_red", 1), - [ + output: ("common.items.crafting_ing.cloth_scraps_red", 1), + inputs: [ (Item("common.items.crafting_ing.cloth_scraps"), 1), (Item("common.items.flowers.red"), 1), (Item("common.items.crafting_tools.mortar_pestle"), 0), ], ), "tiny red pouch": ( - ("common.items.armor.misc.bag.tiny_red_pouch", 1), - [ + output: ("common.items.armor.misc.bag.tiny_red_pouch", 1), + inputs: [ (Item("common.items.crafting_ing.cloth_scraps_red"), 3), (Item("common.items.crafting_tools.sewing_set"), 0), ], ), "tiny leather pouch": ( - ("common.items.armor.misc.bag.tiny_leather_pouch", 1), - [ + output: ("common.items.armor.misc.bag.tiny_leather_pouch", 1), + inputs: [ (Item("common.items.crafting_ing.leather_scraps"), 6), (Item("common.items.crafting_tools.sewing_set"), 0), ], ), "knitted red pouch": ( - ("common.items.armor.misc.bag.knitted_red_pouch", 1), - [ + output: ("common.items.armor.misc.bag.knitted_red_pouch", 1), + inputs: [ (Item("common.items.crafting_ing.cloth_scraps_red"), 3), (Item("common.items.armor.misc.bag.tiny_red_pouch"), 2), (Item("common.items.crafting_tools.sewing_set"), 0), ], ), "woven red bag": ( - ("common.items.armor.misc.bag.woven_red_bag", 1), - [ + output: ("common.items.armor.misc.bag.woven_red_bag", 1), + inputs: [ (Item("common.items.crafting_ing.cloth_scraps_red"), 6), (Item("common.items.armor.misc.bag.knitted_red_pouch"), 1), (Item("common.items.crafting_tools.sewing_set"), 0), ], ), "traveler backpack": ( - ("common.items.armor.misc.back.backpack", 1), - [ + output: ("common.items.armor.misc.back.backpack", 1), + inputs: [ (Item("common.items.crafting_ing.diamond"), 2), (Item("common.items.crafting_ing.twigs"), 2), (Item("common.items.crafting_ing.cloth_scraps"), 3), @@ -348,8 +349,8 @@ ], ), "sturdy red backpack": ( - ("common.items.armor.misc.bag.sturdy_red_backpack", 1), - [ + output: ("common.items.armor.misc.bag.sturdy_red_backpack", 1), + inputs: [ (Item("common.items.crafting_ing.diamond"), 2), (Item("common.items.crafting_ing.cloth_scraps_red"), 3), (Item("common.items.armor.misc.bag.woven_red_bag"), 1), @@ -357,8 +358,8 @@ ], ), "troll hide pack": ( - ("common.items.armor.misc.bag.troll_hide_pack", 1), - [ + output: ("common.items.armor.misc.bag.troll_hide_pack", 1), + inputs: [ (Item("common.items.crafting_ing.leather_troll"), 10), (Item("common.items.crafting_ing.leather_scraps"), 10), (Item("common.items.crafting_ing.diamond"), 1), @@ -366,8 +367,8 @@ ], ), "Mindflayer Spellbag": ( - ("common.items.armor.misc.bag.mindflayer_spellbag", 1), - [ + output: ("common.items.armor.misc.bag.mindflayer_spellbag", 1), + inputs: [ (Item("common.items.crafting_ing.mindflayer_bag_damaged"), 1), (Item("common.items.crafting_ing.leather_scraps"), 10), (Item("common.items.crafting_ing.diamond"), 4), @@ -376,8 +377,8 @@ ], ), "pickaxe": ( - ("common.items.tool.pick", 1), - [ + output: ("common.items.tool.pick", 1), + inputs: [ (Item("common.items.crafting_ing.cloth_scraps"), 1), // TODO: Replace with plant fiber when obtainable (Item("common.items.crafting_ing.stones"), 5), // TODO: Replace with iron ingots when obtainable (Item("common.items.crafting_ing.twigs"), 4), @@ -385,22 +386,22 @@ ], ), "cloth_scraps": ( - ("common.items.crafting_ing.cloth_scraps", 1), - [ + output: ("common.items.crafting_ing.cloth_scraps", 1), + inputs: [ (Tag(ClothItem), 1), (Item("common.items.crafting_tools.sewing_set"), 0), ], ), "leather_scraps": ( - ("common.items.crafting_ing.leather_scraps", 1), - [ + output: ("common.items.crafting_ing.leather_scraps", 1), + inputs: [ (Tag(LeatherItem), 1), (Item("common.items.crafting_tools.sewing_set"), 0), ], ), //"metal_blade": ( - // ("common.items.crafting_ing.modular.damage.sword.metal_blade", 1), - // [ + // output: ("common.items.crafting_ing.modular.damage.sword.metal_blade", 1), + // inputs: [ // (Tag(MetalIngot), 5), // (Item("common.items.crafting_tools.craftsman_hammer"), 0), // ], diff --git a/client/src/lib.rs b/client/src/lib.rs index 777292495d..89a0b26021 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -36,7 +36,7 @@ use common::{ resources::{DeltaTime, PlayerEntity, TimeOfDay}, terrain::{ block::Block, map::MapConfig, neighbors, BiomeKind, SitesKind, TerrainChunk, - TerrainChunkSize, + TerrainChunkSize, SpriteKind, }, trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult}, uid::{Uid, UidAllocator}, @@ -152,7 +152,7 @@ pub struct Client { sites: HashMap, pub chat_mode: ChatMode, recipe_book: RecipeBook, - available_recipes: HashSet, + available_recipes: HashMap>, max_group_size: u32, // Client has received an invite (inviter uid, time out instant) @@ -655,7 +655,7 @@ impl Client { }) .collect(), recipe_book, - available_recipes: HashSet::default(), + available_recipes: HashMap::default(), chat_mode: ChatMode::default(), max_group_size, @@ -970,20 +970,27 @@ impl Client { pub fn recipe_book(&self) -> &RecipeBook { &self.recipe_book } - pub fn available_recipes(&self) -> &HashSet { &self.available_recipes } + pub fn available_recipes(&self) -> &HashMap> { &self.available_recipes } - pub fn can_craft_recipe(&self, recipe: &str) -> bool { + pub fn can_craft_recipe(&self, recipe: &str) -> Option> { self.recipe_book .get(recipe) .zip(self.inventories().get(self.entity())) - .map(|(recipe, inv)| inv.contains_ingredients(&*recipe).is_ok()) - .unwrap_or(false) + .map(|(recipe, inv)| if inv.contains_ingredients(&*recipe).is_ok() { + Some(recipe.craft_sprite) + } else { + None + }) + .unwrap_or(None) } - pub fn craft_recipe(&mut self, recipe: &str) -> bool { - if self.can_craft_recipe(recipe) { + pub fn craft_recipe(&mut self, recipe: &str, craft_sprite: Option<(Vec3, SpriteKind)>) -> bool { + if self.can_craft_recipe(recipe).map_or(false, |cs| cs.map_or(true, |cs| Some(cs) == craft_sprite.map(|(_, s)| s))) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( - InventoryEvent::CraftRecipe(recipe.to_string()), + InventoryEvent::CraftRecipe { + recipe: recipe.to_string(), + craft_sprite: craft_sprite.map(|(pos, _)| pos), + } ))); true } else { @@ -996,7 +1003,10 @@ impl Client { .recipe_book .iter() .map(|(name, _)| name.clone()) - .filter(|name| self.can_craft_recipe(name)) + .filter_map(|name| { + let required_sprite = self.can_craft_recipe(&name)?; + Some((name, required_sprite)) + }) .collect(); } diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 8437d9a269..19dc1eb138 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -21,9 +21,12 @@ pub enum InventoryEvent { Swap(InvSlotId, InvSlotId), SplitSwap(InvSlotId, InvSlotId), Drop(InvSlotId), - SplitDrop(InvSlotId), - CraftRecipe(String), + SplitDrop(InvSlotId), Sort, + CraftRecipe { + recipe: String, + craft_sprite: Option>, + }, } #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] @@ -42,9 +45,12 @@ pub enum InventoryManip { Swap(Slot, Slot), SplitSwap(Slot, Slot), Drop(Slot), - SplitDrop(Slot), - CraftRecipe(String), + SplitDrop(Slot), Sort, + CraftRecipe { + recipe: String, + craft_sprite: Option>, + }, } impl From for InventoryManip { @@ -70,9 +76,9 @@ impl From for InventoryManip { Self::SplitSwap(Slot::Inventory(inv1), Slot::Inventory(inv2)) }, InventoryEvent::Drop(inv) => Self::Drop(Slot::Inventory(inv)), - InventoryEvent::SplitDrop(inv) => Self::SplitDrop(Slot::Inventory(inv)), - InventoryEvent::CraftRecipe(recipe) => Self::CraftRecipe(recipe), + InventoryEvent::SplitDrop(inv) => Self::SplitDrop(Slot::Inventory(inv)), InventoryEvent::Sort => Self::Sort, + InventoryEvent::CraftRecipe { recipe, craft_sprite } => Self::CraftRecipe { recipe, craft_sprite }, } } } diff --git a/common/src/comp/inventory/item/modular.rs b/common/src/comp/inventory/item/modular.rs index c30a8ca24c..416ff688dd 100644 --- a/common/src/comp/inventory/item/modular.rs +++ b/common/src/comp/inventory/item/modular.rs @@ -1,5 +1,5 @@ use super::{tool, ItemKind, ItemTag, Quality, RawItemDef, TagExampleInfo, ToolKind}; -use crate::recipe::{RawRecipeBook, RawRecipeInput}; +use crate::recipe::{RawRecipeBook, RawRecipeInput, RawRecipe}; use hashbrown::HashMap; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; @@ -237,8 +237,8 @@ fn make_weapon_def(toolkind: ToolKind) -> (String, RawItemDef) { fn make_recipe_def( identifier: String, toolkind: ToolKind, -) -> ((String, u32), Vec<(RawRecipeInput, u32)>) { - let outputs = (identifier, 1); +) -> RawRecipe { + let output = (identifier, 1); let mut inputs = Vec::new(); for &modkind in &MODKINDS { let input = RawRecipeInput::Tag(ItemTag::ModularComponent(ModularComponentTag { @@ -247,7 +247,11 @@ fn make_recipe_def( })); inputs.push((input, 1)); } - (outputs, inputs) + RawRecipe { + output, + inputs, + craft_sprite: None, + } } fn make_tagexample_def( diff --git a/common/src/recipe.rs b/common/src/recipe.rs index 0e67509059..86be1a3637 100644 --- a/common/src/recipe.rs +++ b/common/src/recipe.rs @@ -4,6 +4,7 @@ use crate::{ item::{modular, ItemDef, ItemTag, MaterialStatManifest}, Inventory, Item, }, + terrain::SpriteKind, }; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; @@ -19,6 +20,7 @@ pub enum RecipeInput { pub struct Recipe { pub output: (Arc, u32), pub inputs: Vec<(RecipeInput, u32)>, + pub craft_sprite: Option, } #[allow(clippy::type_complexity)] @@ -86,11 +88,19 @@ pub enum RawRecipeInput { Tag(ItemTag), } +#[derive(Clone, Deserialize)] +pub(crate) struct RawRecipe { + pub(crate) output: (String, u32), + pub(crate) inputs: Vec<(RawRecipeInput, u32)>, + #[serde(default)] + pub(crate) craft_sprite: Option, +} + #[derive(Clone, Deserialize)] #[serde(transparent)] #[allow(clippy::type_complexity)] pub(crate) struct RawRecipeBook( - pub(crate) HashMap)>, + pub(crate) HashMap, ); impl assets::Asset for RawRecipeBook { @@ -134,13 +144,13 @@ impl assets::Compound for RecipeBook { let recipes = raw .0 .iter() - .map(|(name, (output, inputs))| { + .map(|(name, RawRecipe { output, inputs, craft_sprite })| { let inputs = inputs .iter() .map(load_recipe_input) - .collect::>()?; + .collect::, _>>()?; let output = load_item_def(output)?; - Ok((name.clone(), Recipe { output, inputs })) + Ok((name.clone(), Recipe { output, inputs, craft_sprite: *craft_sprite })) }) .collect::>()?; diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 6e687077b6..dfdae58433 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -576,15 +576,23 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .expect("We know entity exists since we got its inventory."); drop(inventories); }, - comp::InventoryManip::CraftRecipe(recipe) => { + comp::InventoryManip::CraftRecipe { recipe, craft_sprite } => { let recipe_book = default_recipe_book().read(); - let craft_result = recipe_book.get(&recipe).and_then(|r| { - r.perform( - &mut inventory, - &state.ecs().read_resource::(), - ) - .ok() - }); + let craft_result = recipe_book + .get(&recipe) + .filter(|r| { + let sprite = craft_sprite + .and_then(|pos| state.terrain().get(pos).ok().copied()) + .and_then(|block| block.get_sprite()); + r.craft_sprite.map_or(true, |cs| Some(cs) == sprite) + }) + .and_then(|r| { + r.perform( + &mut inventory, + &state.ecs().read_resource::(), + ) + .ok() + }); drop(inventories); // FIXME: We should really require the drop and write to be atomic! diff --git a/voxygen/src/hud/crafting.rs b/voxygen/src/hud/crafting.rs index 2867bb36dd..630bc3441c 100644 --- a/voxygen/src/hud/crafting.rs +++ b/voxygen/src/hud/crafting.rs @@ -127,7 +127,7 @@ impl<'a> Crafting<'a> { } } -#[derive(Debug, EnumIter, PartialEq)] +#[derive(Copy, Clone, Debug, EnumIter, PartialEq)] pub enum CraftingTab { All, Armor, @@ -434,7 +434,10 @@ impl<'a> Widget for Crafting<'a> { .unwrap_or(false) }) }); - let state = if self.client.available_recipes().contains(name.as_str()) { + let state = if self.client.available_recipes() + .get(name.as_str()) + .map_or(false, |cs| cs.map_or(true, |cs| Some(cs) == self.show.craft_sprite.map(|(_, s)| s))) + { RecipeIngredientQuantity::All } else if at_least_some_ingredients { RecipeIngredientQuantity::Some @@ -528,7 +531,8 @@ impl<'a> Widget for Crafting<'a> { let can_perform = self .client .available_recipes() - .contains(recipe_name.as_str()); + .get(recipe_name.as_str()) + .map_or(false, |cs| cs.map_or(true, |cs| Some(cs) == self.show.craft_sprite.map(|(_, s)| s))); // Craft button if Button::image(self.imgs.button) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 4f2524cf74..238474b5e3 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -25,13 +25,14 @@ pub mod util; pub use hotbar::{SlotContents as HotbarSlotContents, State as HotbarState}; pub use item_imgs::animate_by_pulse; pub use settings_window::ScaleChange; +pub use crafting::CraftingTab; use bag::Bag; use buffs::BuffsBar; use buttons::Buttons; use chat::Chat; use chrono::NaiveTime; -use crafting::{Crafting, CraftingTab}; +use crafting::Crafting; use diary::{Diary, SelectedSkillTree}; use esc_menu::EscMenu; use group::Group; @@ -74,7 +75,7 @@ use common::{ BuffKind, Item, }, outcome::Outcome, - terrain::TerrainChunk, + terrain::{TerrainChunk, SpriteKind}, trade::{ReducedInventory, TradeAction}, uid::Uid, util::srgba_to_linear, @@ -380,7 +381,7 @@ pub enum Event { Logout, Quit, - CraftRecipe(String), + CraftRecipe { recipe: String, craft_sprite: Option<(Vec3, SpriteKind)> }, InviteMember(Uid), AcceptInvite, DeclineInvite, @@ -492,6 +493,7 @@ pub struct Show { skilltreetab: SelectedSkillTree, crafting_tab: CraftingTab, crafting_search_key: Option, + craft_sprite: Option<(Vec3, SpriteKind)>, social_search_key: Option, want_grab: bool, stats: bool, @@ -558,6 +560,12 @@ impl Show { } } + pub fn open_crafting_tab(&mut self, tab: CraftingTab, craft_sprite: Option<(Vec3, SpriteKind)>) { + self.selected_crafting_tab(tab); + self.crafting(true); + self.craft_sprite = craft_sprite; + } + fn diary(&mut self, open: bool) { if !self.esc_menu { self.social = false; @@ -734,7 +742,7 @@ pub struct Hud { new_messages: VecDeque, new_notifications: VecDeque, speech_bubbles: HashMap, - show: Show, + pub show: Show, //never_show: bool, //intro: bool, //intro_2: bool, @@ -840,6 +848,7 @@ impl Hud { skilltreetab: SelectedSkillTree::General, crafting_tab: CraftingTab::All, crafting_search_key: None, + craft_sprite: None, social_search_key: None, want_grab: true, ingame: true, @@ -1363,7 +1372,7 @@ impl Hud { } // Render overtime for an interactable block - if let Some(Interactable::Block(block, pos)) = interactable { + if let Some(Interactable::Block(block, pos, _)) = interactable { let overitem_id = overitem_walker.next( &mut self.ids.overitems, &mut ui_widgets.widget_id_generator(), @@ -1395,6 +1404,19 @@ impl Hud { &self.fonts, ) .set(overitem_id, ui_widgets); + } else if let Some(sprite) = block.get_sprite() { + overitem::Overitem::new( + format!("{:?}", sprite).into(), // TODO: A better way to generate text for this + overitem::TEXT_COLOR, + pos.distance_squared(player_pos), + &self.fonts, + &global_state.settings.controls, + true, + &global_state.window.key_layout, + ) + .x_y(0.0, 100.0) + .position_ingame(over_pos) + .set(overitem_id, ui_widgets); } } @@ -2420,8 +2442,8 @@ impl Hud { .set(self.ids.crafting_window, ui_widgets) { match event { - crafting::Event::CraftRecipe(r) => { - events.push(Event::CraftRecipe(r)); + crafting::Event::CraftRecipe(recipe) => { + events.push(Event::CraftRecipe { recipe, craft_sprite: self.show.craft_sprite }); }, crafting::Event::Close => { self.show.stats = false; @@ -2434,7 +2456,7 @@ impl Hud { }; }, crafting::Event::ChangeCraftingTab(sel_cat) => { - self.show.selected_crafting_tab(sel_cat); + self.show.open_crafting_tab(sel_cat, None); }, crafting::Event::Focus(widget_id) => { self.to_focus = Some(Some(widget_id)); diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 3d67e4c060..13121fe75f 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -1,5 +1,5 @@ mod watcher; -pub use self::watcher::BlocksOfInterest; +pub use self::watcher::{BlocksOfInterest, Interaction}; use crate::{ mesh::{greedy::GreedyMesh, terrain::SUNLIGHT, Meshable}, diff --git a/voxygen/src/scene/terrain/watcher.rs b/voxygen/src/scene/terrain/watcher.rs index 74d2fda778..6b5a6eb14f 100644 --- a/voxygen/src/scene/terrain/watcher.rs +++ b/voxygen/src/scene/terrain/watcher.rs @@ -5,6 +5,13 @@ use common::{ use common_base::span; use rand::prelude::*; use vek::*; +use crate::hud::CraftingTab; + +#[derive(Copy, Clone)] +pub enum Interaction { + Collect, + Craft(CraftingTab), +} #[derive(Default)] pub struct BlocksOfInterest { @@ -26,7 +33,7 @@ pub struct BlocksOfInterest { pub frogs: Vec>, // Note: these are only needed for chunks within the iteraction range so this is a potential // area for optimization - pub interactables: Vec>, + pub interactables: Vec<(Vec3, Interaction)>, pub lights: Vec<(Vec3, u8)>, } @@ -108,11 +115,12 @@ impl BlocksOfInterest { Some(SpriteKind::WhiteFlower) => flowers.push(pos), Some(SpriteKind::YellowFlower) => flowers.push(pos), Some(SpriteKind::Sunflower) => flowers.push(pos), + Some(SpriteKind::Pot) => interactables.push((pos, Interaction::Craft(CraftingTab::Food))), _ => {}, }, } if block.is_collectible() { - interactables.push(pos); + interactables.push((pos, Interaction::Collect)); } if let Some(glow) = block.get_glow() { lights.push((pos, glow)); diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 4fd1dbe383..976347af71 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -38,7 +38,7 @@ use crate::{ key_state::KeyState, menu::char_selection::CharSelectionState, render::Renderer, - scene::{camera, CameraMode, Scene, SceneData}, + scene::{camera, CameraMode, Scene, SceneData, terrain::Interaction}, settings::Settings, window::{AnalogGameInput, Event, GameInput}, Direction, Error, GlobalState, PlayState, PlayStateResult, @@ -359,7 +359,7 @@ impl PlayState for SessionState { .map(|sp| sp.map(|e| e.floor() as i32)) .filter(|_| can_build || is_mining) .or_else(|| match self.interactable { - Some(Interactable::Block(_, block_pos)) => Some(block_pos), + Some(Interactable::Block(_, block_pos, _)) => Some(block_pos), _ => None, }), ); @@ -591,10 +591,16 @@ impl PlayState for SessionState { if let Some(interactable) = self.interactable { let mut client = self.client.borrow_mut(); match interactable { - Interactable::Block(block, pos) => { - if block.is_collectible() { - client.collect_block(pos); - } + Interactable::Block(block, pos, interaction) => match interaction { + Interaction::Collect => { + if block.is_collectible() { + client.collect_block(pos); + } + }, + Interaction::Craft(tab) => self.hud.show.open_crafting_tab( + tab, + block.get_sprite().map(|s| (pos, s)), + ), }, Interactable::Entity(entity) => { if client @@ -618,7 +624,7 @@ impl PlayState for SessionState { if let Some(interactable) = self.interactable { let mut client = self.client.borrow_mut(); match interactable { - Interactable::Block(_, _) => {}, + Interactable::Block(_, _, _) => {}, Interactable::Entity(entity) => { if let Some(uid) = client.state().ecs().uid_from_entity(entity) @@ -1181,8 +1187,8 @@ impl PlayState for SessionState { client.request_site_economy(id); }, - HudEvent::CraftRecipe(r) => { - self.client.borrow_mut().craft_recipe(&r); + HudEvent::CraftRecipe { recipe, craft_sprite } => { + self.client.borrow_mut().craft_recipe(&recipe, craft_sprite); }, HudEvent::InviteMember(uid) => { self.client.borrow_mut().send_invite(uid, InviteKind::Group); @@ -1440,7 +1446,7 @@ fn under_cursor( #[derive(Clone, Copy)] pub enum Interactable { - Block(Block, Vec3), + Block(Block, Vec3, Interaction), Entity(specs::Entity), } @@ -1448,7 +1454,7 @@ impl Interactable { pub fn entity(self) -> Option { match self { Self::Entity(e) => Some(e), - Self::Block(_, _) => None, + Self::Block(_, _, _) => None, } } } @@ -1476,7 +1482,7 @@ fn select_interactable( .or_else(|| selected_pos.and_then(|sp| client.state().terrain().get(sp).ok().copied() .filter(|b| hit(*b)) - .map(|b| Interactable::Block(b, sp)) + .map(|b| Interactable::Block(b, sp, Interaction::Collect)) )) .or_else(|| { let ecs = client.state().ecs(); @@ -1540,22 +1546,23 @@ fn select_interactable( .blocks_of_interest .interactables .iter() - .map(move |block_offset| chunk_pos + block_offset) + .map(move |(block_offset, interaction)| (chunk_pos + block_offset, interaction)) }) - .map(|block_pos| ( + .map(|(block_pos, interaction)| ( block_pos, block_pos.map(|e| e as f32 + 0.5) - .distance_squared(player_pos) + .distance_squared(player_pos), + interaction, )) - .min_by_key(|(_, dist_sqr)| OrderedFloat(*dist_sqr)) - .map(|(block_pos, _)| block_pos); + .min_by_key(|(_, dist_sqr, _)| OrderedFloat(*dist_sqr)) + .map(|(block_pos, _, interaction)| (block_pos, interaction)); // Pick closer one if they exist closest_interactable_block_pos - .filter(|block_pos| player_cylinder.min_distance(Cube { min: block_pos.as_(), side_length: 1.0}) < search_dist) - .and_then(|block_pos| + .filter(|(block_pos, _)| player_cylinder.min_distance(Cube { min: block_pos.as_(), side_length: 1.0}) < search_dist) + .and_then(|(block_pos, interaction)| client.state().terrain().get(block_pos).ok().copied() - .map(|b| Interactable::Block(b, block_pos)) + .map(|b| Interactable::Block(b, block_pos, *interaction)) ) .or_else(|| closest_interactable_entity.map(|(e, _)| Interactable::Entity(e))) }) diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index efb0157d11..5a124bfb5f 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -151,9 +151,9 @@ impl Attr { pub fn generate(rng: &mut R, _locus: i32) -> Self { Self { central_supports: rng.gen(), - storey_fill: match rng.gen_range(0..2) { - //0 => StoreyFill::None, - 0 => StoreyFill::Upper, + storey_fill: match rng.gen_range(0..3) { + 0 => StoreyFill::None, + 1 => StoreyFill::Upper, _ => StoreyFill::All, }, roof_style: match rng.gen_range(0..3) { @@ -295,12 +295,13 @@ impl Archetype for House { const EMPTY: BlockMask = BlockMask::nothing(); // TODO: Take environment into account. let internal = BlockMask::new(Block::air(SpriteKind::Empty), internal_layer); + let end_ori = match ori { + Ori::East => 2, + Ori::North => 4, + }; let end_window = BlockMask::new( Block::air(attr.window) - .with_ori(match ori { - Ori::East => 2, - Ori::North => 0, - }) + .with_ori(end_ori) .unwrap(), structural_layer, ); @@ -519,6 +520,31 @@ impl Archetype for House { // Ceiling return floor; } + } else if !attr.storey_fill.has_lower() + && center_offset.sum() % 2 == 0 + && profile.y == 1 + && center_offset.map(|e| e % 3 == 0).reduce_and() + && self + .noise + .chance(Vec3::new(center_offset.x, center_offset.y, z), 0.2) + { + let furniture = match self.noise.get(Vec3::new( + center_offset.x, + center_offset.y, + z + 100, + )) % 5 + { + 0..=1 => SpriteKind::Crate, + 2 => SpriteKind::Bench, + 3 => SpriteKind::ChairSingle, + 4 => SpriteKind::FireBowlGround, + _ => unreachable!(), + }; + + return BlockMask::new( + Block::air(furniture).with_ori(end_ori).unwrap(), + 1, + ); } else if (!attr.storey_fill.has_lower() && profile.y < ceil_height) || (!attr.storey_fill.has_upper() && profile.y >= ceil_height) { @@ -592,10 +618,12 @@ impl Archetype for House { match self .noise .get(Vec3::new(center_offset.x, center_offset.y, z + 100)) - % 4 + % 6 { 0 => SpriteKind::HangingSign, 1 | 2 | 3 => SpriteKind::HangingBasket, + 4 => SpriteKind::WallSconce, + 5 => SpriteKind::WallLampSmall, _ => SpriteKind::DungeonWallDecor, };