diff --git a/CHANGELOG.md b/CHANGELOG.md index 5da070ed06..5f7336fbb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Players can now opt-in to server-authoritiative physics in gameplay settings. - Added `/server_physics` admin command. - Sort inventory button -- Option to change the master volume when window is unfocused +- Option to change the master volume when window is unfocused +- Crafting stations in towns ### Changed diff --git a/assets/common/items/crafting_tools/craftsman_hammer.ron b/assets/common/items/crafting_tools/craftsman_hammer.ron deleted file mode 100644 index f8d7f84ae3..0000000000 --- a/assets/common/items/crafting_tools/craftsman_hammer.ron +++ /dev/null @@ -1,9 +0,0 @@ -ItemDef( - name: "Craftsman Hammer", - description: "Used to craft various items.", - kind: Ingredient( - kind: "CraftsmanHammer", - ), - quality: Moderate, - tags: [CraftingTool], -) diff --git a/assets/common/items/tool/craftsman_hammer.ron b/assets/common/items/tool/craftsman_hammer.ron new file mode 100644 index 0000000000..599a308d0d --- /dev/null +++ b/assets/common/items/tool/craftsman_hammer.ron @@ -0,0 +1,18 @@ +ItemDef( + name: "Craftsman Hammer", + description: "Used to craft various items.", + kind: Tool(( + kind: Hammer, + hands: One, + stats: Direct(( + equip_time_secs: 0.25, + power: 0.2, + poise_strength: 0.25, + speed: 1.5, + crit_chance: 0.0, + crit_mult: 0.0, + )), + )), + quality: Common, + tags: [CraftingTool], +) \ No newline at end of file diff --git a/assets/common/recipe_book.ron b/assets/common/recipe_book.ron index ef20666454..5b1b11f81b 100644 --- a/assets/common/recipe_book.ron +++ b/assets/common/recipe_book.ron @@ -1,344 +1,375 @@ { "crafting_hammer": ( - ("common.items.crafting_tools.craftsman_hammer", 1), - [ + output: ("common.items.tool.craftsman_hammer", 1), + inputs: [ (Item("common.items.crafting_ing.twigs"), 6), (Item("common.items.crafting_ing.stones"), 6), ], + craft_sprite: Some(Anvil), ), "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), + (Item("common.items.tool.craftsman_hammer"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), ], + craft_sprite: Some(CraftingBench), ), "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), + (Item("common.items.tool.craftsman_hammer"), 0), ], + craft_sprite: Some(Anvil), ), "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), ], + craft_sprite: Some(Cauldron), ), "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), ], + craft_sprite: Some(Cauldron), ), "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), (Item("common.items.crafting_tools.mortar_pestle"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.ore.veloritefrag"), 1), (Item("common.items.crafting_tools.mortar_pestle"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.ore.veloritefrag"), 1), (Item("common.items.crafting_tools.mortar_pestle"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.ore.veloritefrag"), 1), (Item("common.items.crafting_tools.mortar_pestle"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.ore.veloritefrag"), 1), (Item("common.items.crafting_tools.mortar_pestle"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.ore.veloritefrag"), 1), (Item("common.items.crafting_tools.mortar_pestle"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.ore.veloritefrag"), 1), (Item("common.items.crafting_tools.mortar_pestle"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.crafting_tools.mortar_pestle"), 0), ], + craft_sprite: Some(CookingPot), ), "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), - ], + ], ), "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), (Item("common.items.crafting_ing.honey"), 1), ], + craft_sprite: Some(Cauldron), ), "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), - (Item("common.items.crafting_tools.craftsman_hammer"), 0), + (Item("common.items.tool.craftsman_hammer"), 0), (Item("common.items.crafting_tools.sewing_set"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), - (Item("common.items.crafting_tools.craftsman_hammer"), 0), + (Item("common.items.tool.craftsman_hammer"), 0), (Item("common.items.crafting_tools.sewing_set"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.crafting_ing.emerald"), 1), - (Item("common.items.crafting_tools.craftsman_hammer"), 0), + (Item("common.items.tool.craftsman_hammer"), 0), (Item("common.items.crafting_tools.sewing_set"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.crafting_ing.cloth_scraps"), 5), (Item("common.items.crafting_ing.ruby"), 1), - (Item("common.items.crafting_tools.craftsman_hammer"), 0), + (Item("common.items.tool.craftsman_hammer"), 0), (Item("common.items.crafting_tools.sewing_set"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.crafting_ing.cloth_scraps"), 5), (Item("common.items.crafting_ing.icy_fang"), 1), (Item("common.items.crafting_ing.ruby"), 1), - (Item("common.items.crafting_tools.craftsman_hammer"), 0), + (Item("common.items.tool.craftsman_hammer"), 0), (Item("common.items.crafting_tools.sewing_set"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.crafting_ing.cloth_scraps"), 5), (Item("common.items.crafting_ing.ruby"), 1), - (Item("common.items.crafting_tools.craftsman_hammer"), 0), + (Item("common.items.tool.craftsman_hammer"), 0), (Item("common.items.crafting_tools.sewing_set"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), - (Item("common.items.crafting_tools.craftsman_hammer"), 0), + (Item("common.items.tool.craftsman_hammer"), 0), ], ), "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), ], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.crafting_ing.stones"), 0), ], + craft_sprite: Some(Anvil), ), "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)], + craft_sprite: Some(CraftingBench), ), "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)], + craft_sprite: Some(CraftingBench), ), "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)], + craft_sprite: Some(CraftingBench), ), "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)], + craft_sprite: Some(CraftingBench), ), "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)], + craft_sprite: Some(CraftingBench), ), "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)], + craft_sprite: Some(CraftingBench), ), "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)], + craft_sprite: Some(CraftingBench), ), "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), (Item("common.items.crafting_tools.sewing_set"), 0), ], + craft_sprite: Some(CraftingBench), ), "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), @@ -346,10 +377,11 @@ (Item("common.items.armor.misc.bag.tiny_leather_pouch"), 2), (Item("common.items.crafting_tools.sewing_set"), 0), ], + craft_sprite: Some(CraftingBench), ), "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 +389,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 +398,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,33 +408,34 @@ ], ), "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), - (Item("common.items.crafting_tools.craftsman_hammer"), 0), + (Item("common.items.tool.craftsman_hammer"), 0), ], + craft_sprite: Some(Anvil), ), "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), + // (Item("common.items.tool.craftsman_hammer"), 0), // ], //), } diff --git a/assets/voxygen/i18n/en/hud/crafting.ron b/assets/voxygen/i18n/en/hud/crafting.ron index 41d5cf4a49..985f4c3bd4 100644 --- a/assets/voxygen/i18n/en/hud/crafting.ron +++ b/assets/voxygen/i18n/en/hud/crafting.ron @@ -8,7 +8,12 @@ "hud.crafting.ingredients": "Ingredients:", "hud.crafting.craft": "Craft", "hud.crafting.tool_cata": "Requires:", - + // Crafting Stations + "hud.crafting.req_crafting_station": "Requires:", + "hud.crafting.anvil": "Anvil", + "hud.crafting.cauldron": "Cauldron", + "hud.crafting.cooking_pot": "Cooking Pot", + "hud.crafting.crafting_bench": "Crafting Bench", // Tabs "hud.crafting.tabs.all": "All", "hud.crafting.tabs.armor": "Armor", diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index c863e6abbc..94c348a731 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -2,6 +2,23 @@ // Vox(specier), // VoxTrans(specifier, offset, (x_rot, y_rot, z_rot), zoom) ({ + // Crafting Stations + Tool("Anvil"): VoxTrans( + "voxel.sprite.anvil.anvil-0", + (0.5, 0.5, 0.0), (0.0, 60.0, 90.0), 1.0, + ), + Tool("Cauldron"): VoxTrans( + "voxel.sprite.cauldron.cauldron-0", + (0.0, 0.0, 0.0), (-50.0, 40.0, 30.0), 1.0, + ), + Tool("CookingPot"): VoxTrans( + "voxel.sprite.cooking_pot.pot-0", + (0.0, 0.0, 0.0), (0.0, 90.0, 90.0), 1.2, + ), + Tool("CraftingBench"): VoxTrans( + "voxel.sprite.crafting_bench.crafting_bench-0", + (0.0, 0.0, 0.0), (-50.0, 40.0, 20.0), 1.0, + ), // Weapons // Diary Example Images Tool("example_utility"): VoxTrans( @@ -1920,11 +1937,11 @@ "voxel.object.training_dummy", (0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8, ), - // Ingredients - Ingredient("CraftsmanHammer"): VoxTrans( //TODO This should be a 1h hammer! + // Ingredients + Tool("common.items.tool.craftsman_hammer"): VoxTrans( "voxel.weapon.hammer.craftsman", - (1.0, 1.0, 0.0), (-135.0, 90.0, 0.0), 1.0, - ), + (1.0, -1.0, 0.0), (-135.0, 90.0, 0.0), 0.9, + ), Ingredient("SewingSet"): Png( "element.items.sewing_set", ), diff --git a/assets/voxygen/shaders/sprite-frag.glsl b/assets/voxygen/shaders/sprite-frag.glsl index 317f385417..3d6ff65d74 100644 --- a/assets/voxygen/shaders/sprite-frag.glsl +++ b/assets/voxygen/shaders/sprite-frag.glsl @@ -18,7 +18,7 @@ in vec3 f_pos; flat in vec3 f_norm; -flat in float f_light; +flat in float f_select; // flat in vec3 f_pos_norm; in vec2 f_uv_pos; in vec2 f_inst_light; @@ -181,9 +181,11 @@ void main() { emitted_light *= ao; reflected_light *= ao; - surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light) * f_light; + surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light); // vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light); + surf_color += f_select * (surf_color + 0.1) * vec3(0.15, 0.15, 0.15); + // tgt_color = vec4(color, 1.0); tgt_color = vec4(surf_color, 1.0 - clamp((distance(focus_pos.xy, f_pos.xy) - (sprite_render_distance - FADE_DIST)) / FADE_DIST, 0, 1)); } diff --git a/assets/voxygen/shaders/sprite-vert.glsl b/assets/voxygen/shaders/sprite-vert.glsl index d0118972f7..e22235a1d8 100644 --- a/assets/voxygen/shaders/sprite-vert.glsl +++ b/assets/voxygen/shaders/sprite-vert.glsl @@ -72,7 +72,7 @@ uniform u_terrain_locals { out vec3 f_pos; flat out vec3 f_norm; -flat out float f_light; +flat out float f_select; // flat out vec3 f_pos_norm; // out vec3 f_col; // out float f_ao; @@ -228,7 +228,7 @@ void main() { // f_light = 1.0; // if (select_pos.w > 0) */{ vec3 sprite_pos = /*round*/floor(((inst_mat * vec4(-offs.xyz, 1)).xyz) * SCALE/* - vec3(0.5, 0.5, 0.0)*/) + inst_offs; - f_light = (select_pos.w > 0 && select_pos.xyz == sprite_pos/* - vec3(0.5, 0.5, 0.0) * SCALE*/) ? 10.0 : 1.0; + f_select = (select_pos.w > 0 && select_pos.xyz == sprite_pos/* - vec3(0.5, 0.5, 0.0) * SCALE*/) ? 1.0 : 0.0; // } gl_Position = diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 63b668d592..12b4fb15af 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -268,9 +268,6 @@ void main() { vec3 glow = glow_light(f_pos) * (pow(f_glow, 6) * 5 + pow(f_glow, 1.5) * 2); reflected_light += glow; - float f_select = (select_pos.w > 0 && select_pos.xyz == floor(f_pos - f_norm * 0.5)) ? 0.2 / PERSISTENT_AMBIANCE : 0.0; - reflected_light += f_select; - max_light += lights_at(f_pos, f_norm, view_dir, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light); // float f_ao = 1.0; @@ -371,5 +368,8 @@ void main() { // vec3 col = /*srgb_to_linear*/(f_col + hash(vec4(floor(f_pos * 3.0 - f_norm * 0.5), 0)) * 0.01); // Small-scale noise vec3 surf_color = illuminate(max_light, view_dir, col * emitted_light, col * reflected_light); + float f_select = (select_pos.w > 0 && select_pos.xyz == floor(f_pos - f_norm * 0.5)) ? 1.0 : 0.0; + surf_color += f_select * (surf_color + 0.1) * vec3(0.5, 0.5, 0.5); + tgt_color = vec4(surf_color, 1.0); } diff --git a/assets/voxygen/voxel/armor/bonerattler/chest.vox b/assets/voxygen/voxel/armor/bonerattler/chest.vox index d516e45bd8..5ace1bdfbe 100644 --- a/assets/voxygen/voxel/armor/bonerattler/chest.vox +++ b/assets/voxygen/voxel/armor/bonerattler/chest.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:340ef35d35982e51f1d11bd0990bbf3e512aad52332026ccc138a81205faef54 +oid sha256:f8568714be1928adb9dd82a822e7d4830fe350bad3ac756249b3811d58ce9849 size 2624 diff --git a/assets/voxygen/voxel/biped_weapon_manifest.ron b/assets/voxygen/voxel/biped_weapon_manifest.ron index 1a69cb2f35..27f5b7c0ca 100644 --- a/assets/voxygen/voxel/biped_weapon_manifest.ron +++ b/assets/voxygen/voxel/biped_weapon_manifest.ron @@ -462,7 +462,11 @@ vox_spec: ("weapon.axe_1h.wood-1", (-1.5, -5.0, -3.0)), color: None ), - // Hammers + // Hammers + "common.items.tool.craftsman_hammer": ( + vox_spec: ("weapon.hammer.craftsman", (-2.5, -5.5, -4.5)), + color: None + ), "common.items.weapons.hammer.hammer_1": ( vox_spec: ("weapon.hammer.2hhammer_rusty", (-2.5, -5.5, -4.5)), color: None diff --git a/assets/voxygen/voxel/glider/glider_cultists.vox b/assets/voxygen/voxel/glider/glider_cultists.vox index f8d13a6080..e21bb0c6b0 100644 --- a/assets/voxygen/voxel/glider/glider_cultists.vox +++ b/assets/voxygen/voxel/glider/glider_cultists.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88540f687e06db70e617d19e969e4fd8903aaec801752201f7abb4bce393cdd5 +oid sha256:895d804a940f30dd14d1ba5676180e2e68510810d4f3ef70a5c0653ec44edb16 size 14960 diff --git a/assets/voxygen/voxel/sprite/anvil/anvil-0.vox b/assets/voxygen/voxel/sprite/anvil/anvil-0.vox new file mode 100644 index 0000000000..5b117d035c --- /dev/null +++ b/assets/voxygen/voxel/sprite/anvil/anvil-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:673fa4daf3a0a2835ad064c62578103329b52c85ffe23e2f587b9ff4337c0d3b +size 4432 diff --git a/assets/voxygen/voxel/sprite/cauldron/cauldron-0.vox b/assets/voxygen/voxel/sprite/cauldron/cauldron-0.vox new file mode 100644 index 0000000000..d8276da2e6 --- /dev/null +++ b/assets/voxygen/voxel/sprite/cauldron/cauldron-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fafc69bbd90506232891146a6f626e5b219ac2932b2e579a22ee51534974df07 +size 10696 diff --git a/assets/voxygen/voxel/sprite/cooking_pot/pot-0.vox b/assets/voxygen/voxel/sprite/cooking_pot/pot-0.vox new file mode 100644 index 0000000000..14e9a3c62f --- /dev/null +++ b/assets/voxygen/voxel/sprite/cooking_pot/pot-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e616ae51d56fd59d500371e7b1341dbb46352b7e367168dd0e4988ce539e3847 +size 8340 diff --git a/assets/voxygen/voxel/sprite/crafting_bench/crafting_bench-0.vox b/assets/voxygen/voxel/sprite/crafting_bench/crafting_bench-0.vox new file mode 100644 index 0000000000..6df008ffbe --- /dev/null +++ b/assets/voxygen/voxel/sprite/crafting_bench/crafting_bench-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:603b2aaeaae6872caf431e394eb97f92a9b85895238cbde80857c9550f826077 +size 3456 diff --git a/assets/voxygen/voxel/sprite/forge/forge-0.vox b/assets/voxygen/voxel/sprite/forge/forge-0.vox new file mode 100644 index 0000000000..3e8d319c49 --- /dev/null +++ b/assets/voxygen/voxel/sprite/forge/forge-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a45de7fed286c5b0011ca741e7c51765ae2d3c3260bb2beeae51b6ea3daa8fa5 +size 44800 diff --git a/assets/voxygen/voxel/sprite_manifest.ron b/assets/voxygen/voxel/sprite_manifest.ron index 1afc97ac66..4e2b11a7cd 100644 --- a/assets/voxygen/voxel/sprite_manifest.ron +++ b/assets/voxygen/voxel/sprite_manifest.ron @@ -2852,4 +2852,59 @@ Lantern: Some(( ], wind_sway: 0.0, )), +// Anvil +Anvil: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.anvil.anvil-0", + offset: (-5.5, -7.0, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +// Cauldron +Cauldron: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.cauldron.cauldron-0", + offset: (-10.0, -10.0, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +// Forge +Forge: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.forge.forge-0", + offset: (-20.0, -20.0, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +// Crafting Bench +CraftingBench: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.crafting_bench.crafting_bench-0", + offset: (-9.5, -7.0, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), +// Cooking Pot +CookingPot: Some(( + variations: [ + ( + model: "voxygen.voxel.sprite.cooking_pot.pot-0", + offset: (-9.0, -10.0, 0.0), + lod_axes: (0.0, 0.0, 0.0), + ), + ], + wind_sway: 0.0, +)), ) diff --git a/assets/voxygen/voxel/weapon/hammer/craftsman.vox b/assets/voxygen/voxel/weapon/hammer/craftsman.vox index 7501e83d4f..a2adc3bd18 100644 --- a/assets/voxygen/voxel/weapon/hammer/craftsman.vox +++ b/assets/voxygen/voxel/weapon/hammer/craftsman.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4dccf2543585a5d0a18dea671d9b25347715a89ccab44e793faf735a0d52e62 +oid sha256:ff5902aca1ec5ee83d24798920391cff003ad292f1a0fb0d815bc085532cc7e1 size 2240 diff --git a/client/src/lib.rs b/client/src/lib.rs index 777292495d..5f225758bc 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -35,7 +35,7 @@ use common::{ recipe::RecipeBook, resources::{DeltaTime, PlayerEntity, TimeOfDay}, terrain::{ - block::Block, map::MapConfig, neighbors, BiomeKind, SitesKind, TerrainChunk, + block::Block, map::MapConfig, neighbors, BiomeKind, SitesKind, SpriteKind, TerrainChunk, TerrainChunkSize, }, trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult}, @@ -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,38 @@ 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 { + /// Returns whether the specified recipe can be crafted and the sprite, if + /// any, that is required to do so. + pub fn can_craft_recipe(&self, recipe: &str) -> (bool, 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)| { + ( + inv.contains_ingredients(&*recipe).is_ok(), + recipe.craft_sprite, + ) + }) + .unwrap_or((false, 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 { + let (can_craft, required_sprite) = self.can_craft_recipe(recipe); + let has_sprite = required_sprite.map_or(true, |s| Some(s) == craft_sprite.map(|(_, s)| s)); + if can_craft && has_sprite { 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 +1014,14 @@ impl Client { .recipe_book .iter() .map(|(name, _)| name.clone()) - .filter(|name| self.can_craft_recipe(name)) + .filter_map(|name| { + let (can_craft, required_sprite) = self.can_craft_recipe(&name); + if can_craft { + Some((name, required_sprite)) + } else { + None + } + }) .collect(); } diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 8437d9a269..62d7f0befd 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -22,8 +22,11 @@ pub enum InventoryEvent { SplitSwap(InvSlotId, InvSlotId), Drop(InvSlotId), SplitDrop(InvSlotId), - CraftRecipe(String), Sort, + CraftRecipe { + recipe: String, + craft_sprite: Option>, + }, } #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] @@ -43,8 +46,11 @@ pub enum InventoryManip { SplitSwap(Slot, Slot), Drop(Slot), SplitDrop(Slot), - CraftRecipe(String), Sort, + CraftRecipe { + recipe: String, + craft_sprite: Option>, + }, } impl From for InventoryManip { @@ -71,8 +77,14 @@ impl From for InventoryManip { }, InventoryEvent::Drop(inv) => Self::Drop(Slot::Inventory(inv)), InventoryEvent::SplitDrop(inv) => Self::SplitDrop(Slot::Inventory(inv)), - InventoryEvent::CraftRecipe(recipe) => Self::CraftRecipe(recipe), 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..d1f604c8f1 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::{RawRecipe, RawRecipeBook, RawRecipeInput}; use hashbrown::HashMap; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; @@ -234,11 +234,8 @@ fn make_weapon_def(toolkind: ToolKind) -> (String, RawItemDef) { (identifier, item) } -fn make_recipe_def( - identifier: String, - toolkind: ToolKind, -) -> ((String, u32), Vec<(RawRecipeInput, u32)>) { - let outputs = (identifier, 1); +fn make_recipe_def(identifier: String, toolkind: ToolKind) -> RawRecipe { + let output = (identifier, 1); let mut inputs = Vec::new(); for &modkind in &MODKINDS { let input = RawRecipeInput::Tag(ItemTag::ModularComponent(ModularComponentTag { @@ -247,7 +244,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..1e0a3f3987 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,12 +88,17 @@ 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) struct RawRecipeBook(pub(crate) HashMap); impl assets::Asset for RawRecipeBook { type Loader = assets::RonLoader; @@ -134,14 +141,27 @@ impl assets::Compound for RecipeBook { let recipes = raw .0 .iter() - .map(|(name, (output, inputs))| { - let inputs = inputs - .iter() - .map(load_recipe_input) - .collect::>()?; - let output = load_item_def(output)?; - Ok((name.clone(), Recipe { output, inputs })) - }) + .map( + |( + name, + RawRecipe { + output, + inputs, + craft_sprite, + }, + )| { + let inputs = inputs + .iter() + .map(load_recipe_input) + .collect::, _>>()?; + let output = load_item_def(output)?; + Ok((name.clone(), Recipe { + output, + inputs, + craft_sprite: *craft_sprite, + })) + }, + ) .collect::>()?; Ok(RecipeBook { recipes }) diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 8068a3a7de..6c6d370c3f 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -171,12 +171,12 @@ impl Block { match self.get_sprite()? { SpriteKind::StreetLamp | SpriteKind::StreetLampTall => Some(24), SpriteKind::Ember => Some(20), - SpriteKind::WallLamp => Some(16), - SpriteKind::WallLampSmall => Some(16), - SpriteKind::WallSconce => Some(16), - SpriteKind::FireBowlGround => Some(16), + SpriteKind::WallLamp + | SpriteKind::WallLampSmall + | SpriteKind::WallSconce + | SpriteKind::FireBowlGround => Some(16), SpriteKind::Velorite | SpriteKind::VeloriteFrag => Some(6), - SpriteKind::CaveMushroom => Some(12), + SpriteKind::CaveMushroom | SpriteKind::CookingPot => Some(12), SpriteKind::Amethyst | SpriteKind::Ruby | SpriteKind::Sapphire diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index 4d3fdd77f7..e967185ba6 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -143,6 +143,11 @@ make_case_elim!( RedAlgae = 0x74, UnderwaterVent = 0x75, Lantern = 0x76, + CraftingBench = 0x77, + Forge = 0x78, + Cauldron = 0x79, + Anvil = 0x7A, + CookingPot = 0x7B, } ); @@ -184,6 +189,11 @@ impl SpriteKind { SpriteKind::Mud => 0.36, SpriteKind::ChestBuried => 0.91, SpriteKind::StonyCoral => 1.4, + SpriteKind::CraftingBench => 1.18, + SpriteKind::Forge => 2.7, + SpriteKind::Cauldron => 1.27, + SpriteKind::Anvil => 1.1, + SpriteKind::CookingPot => 1.36, // TODO: Find suitable heights. SpriteKind::BarrelCactus | SpriteKind::RoundCactus @@ -309,6 +319,11 @@ impl SpriteKind { | SpriteKind::VialEmpty | SpriteKind::FireBowlGround | SpriteKind::Lantern + | SpriteKind::CraftingBench + | SpriteKind::Forge + | SpriteKind::Cauldron + | SpriteKind::Anvil + | SpriteKind::CookingPot ) } } diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 6e687077b6..fa9e41b01d 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -576,15 +576,49 @@ 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| { + 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()); + Some(needed_sprite) == sprite + } else { + true + } + }) + .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/server/src/migrations/V35__rename_craftsman_hammer.sql b/server/src/migrations/V35__rename_craftsman_hammer.sql new file mode 100644 index 0000000000..773436b601 --- /dev/null +++ b/server/src/migrations/V35__rename_craftsman_hammer.sql @@ -0,0 +1,3 @@ +-- Replace old crafting hammer ingredient with functional 1h-hammer variant + +UPDATE item SET item_definition_id = 'common.items.tool.craftsman_hammer' WHERE item_definition_id = 'common.items.crafting_tools.craftsman_hammer'; \ No newline at end of file diff --git a/voxygen/src/hud/crafting.rs b/voxygen/src/hud/crafting.rs index 2867bb36dd..ee616e822d 100644 --- a/voxygen/src/hud/crafting.rs +++ b/voxygen/src/hud/crafting.rs @@ -1,6 +1,6 @@ use super::{ img_ids::{Imgs, ImgsRot}, - item_imgs::{animate_by_pulse, ItemImgs}, + item_imgs::{animate_by_pulse, ItemImgs, ItemKey::Tool}, Show, TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN, }; use crate::{ @@ -20,6 +20,7 @@ use common::{ Inventory, }, recipe::RecipeInput, + terrain::SpriteKind, }; use conrod_core::{ color, image, @@ -59,6 +60,9 @@ widget_ids! { ingredient_img[], req_text[], ingredients_txt, + req_station_title, + req_station_img, + req_station_txt, output_img_frame, output_img, output_amount, @@ -127,7 +131,7 @@ impl<'a> Crafting<'a> { } } -#[derive(Debug, EnumIter, PartialEq)] +#[derive(Copy, Clone, Debug, EnumIter, PartialEq)] pub enum CraftingTab { All, Armor, @@ -434,7 +438,16 @@ impl<'a> Widget for Crafting<'a> { .unwrap_or(false) }) }); - let state = if self.client.available_recipes().contains(name.as_str()) { + let show_craft_sprite = + 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) + }) + }); + let state = if show_craft_sprite { RecipeIngredientQuantity::All } else if at_least_some_ingredients { RecipeIngredientQuantity::Some @@ -528,7 +541,12 @@ 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) @@ -642,14 +660,68 @@ impl<'a> Widget for Crafting<'a> { .set(state.ids.tags_ing[i], ui); } } - - // Ingredients Text - Text::new(&self.localized_strings.get("hud.crafting.ingredients")) + // Crafting Station Info + if recipe.craft_sprite.is_some() { + Text::new( + &self + .localized_strings + .get("hud.crafting.req_crafting_station"), + ) .top_left_with_margins_on(state.ids.align_ing, 10.0, 5.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(18)) .color(TEXT_COLOR) - .set(state.ids.ingredients_txt, ui); + .set(state.ids.req_station_title, ui); + let station_img = match recipe.craft_sprite { + Some(SpriteKind::Anvil) => "Anvil", + Some(SpriteKind::Cauldron) => "Cauldron", + Some(SpriteKind::CookingPot) => "CookingPot", + Some(SpriteKind::CraftingBench) => "CraftingBench", + None => "CraftsmanHammer", + _ => "CraftsmanHammer", + }; + Image::new(animate_by_pulse( + &self + .item_imgs + .img_ids_or_not_found_img(Tool(station_img.to_string())), + self.pulse, + )) + .w_h(25.0, 25.0) + .down_from(state.ids.req_station_title, 10.0) + .parent(state.ids.align_ing) + .set(state.ids.req_station_img, ui); + + let station_name = match recipe.craft_sprite { + Some(SpriteKind::Anvil) => "hud.crafting.anvil", + Some(SpriteKind::Cauldron) => "hud.crafting.cauldron", + Some(SpriteKind::CookingPot) => "hud.crafting.cooking_pot", + Some(SpriteKind::CraftingBench) => "hud.crafting.crafting_bench", + _ => "", + }; + Text::new(&self.localized_strings.get(station_name)) + .right_from(state.ids.req_station_img, 10.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .color( + if self.show.craft_sprite.map(|(_, s)| s) == recipe.craft_sprite { + TEXT_COLOR + } else { + TEXT_DULL_RED_COLOR + }, + ) + .set(state.ids.req_station_txt, ui); + } + // Ingredients Text + let mut ing_txt = Text::new(&self.localized_strings.get("hud.crafting.ingredients")) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(18)) + .color(TEXT_COLOR); + if recipe.craft_sprite.is_some() { + ing_txt = ing_txt.down_from(state.ids.req_station_img, 10.0); + } else { + ing_txt = ing_txt.top_left_with_margins_on(state.ids.align_ing, 10.0, 5.0); + }; + ing_txt.set(state.ids.ingredients_txt, ui); // Ingredient images with tooltip if state.ids.ingredient_frame.len() < recipe.inputs().len() { @@ -684,6 +756,7 @@ impl<'a> Widget for Crafting<'a> { .resize(recipe.inputs().len(), &mut ui.widget_id_generator()) }); }; + // Widget generation for every ingredient for (i, (recipe_input, amount)) in recipe.inputs.iter().enumerate() { let item_def = match recipe_input { diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 4f2524cf74..293f2208c2 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -22,6 +22,7 @@ mod social; mod trade; pub mod util; +pub use crafting::CraftingTab; pub use hotbar::{SlotContents as HotbarSlotContents, State as HotbarState}; pub use item_imgs::animate_by_pulse; pub use settings_window::ScaleChange; @@ -31,7 +32,7 @@ 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; @@ -73,8 +74,9 @@ use common::{ skills::{Skill, SkillGroupKind}, BuffKind, Item, }, + consts::MAX_PICKUP_RANGE, outcome::Outcome, - terrain::TerrainChunk, + terrain::{SpriteKind, TerrainChunk}, trade::{ReducedInventory, TradeAction}, uid::Uid, util::srgba_to_linear, @@ -380,7 +382,10 @@ pub enum Event { Logout, Quit, - CraftRecipe(String), + CraftRecipe { + recipe: String, + craft_sprite: Option<(Vec3, SpriteKind)>, + }, InviteMember(Uid), AcceptInvite, DeclineInvite, @@ -492,6 +497,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 +564,16 @@ 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 +750,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 +856,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 +1380,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 +1412,20 @@ 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 +2451,11 @@ 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 +2468,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)); @@ -3328,6 +3362,16 @@ impl Hud { .handle_event(conrod_core::event::Input::Text("\t".to_string())); } + // Stop selecting a sprite to perform crafting with when out of range + self.show.craft_sprite = self.show.craft_sprite.filter(|(pos, _)| { + self.show.crafting + && if let Some(player_pos) = client.position() { + pos.map(|e| e as f32 + 0.5).distance(player_pos) < MAX_PICKUP_RANGE + } else { + false + } + }); + // Optimization: skip maintaining UI when it's off. if !self.show.ui { return std::mem::take(&mut self.events); @@ -3443,11 +3487,11 @@ fn try_hotbar_slot_from_input(input: GameInput) -> Option { } pub fn cr_color(combat_rating: f32) -> Color { - let common = 4.3; - let moderate = 6.0; - let high = 8.0; - let epic = 10.0; - let legendary = 79.0; + let common = 2.0; + let moderate = 3.5; + let high = 6.5; + let epic = 8.5; + let legendary = 10.4; let artifact = 122.0; let debug = 200.0; 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..3b32b2f6a3 100644 --- a/voxygen/src/scene/terrain/watcher.rs +++ b/voxygen/src/scene/terrain/watcher.rs @@ -1,3 +1,4 @@ +use crate::hud::CraftingTab; use common::{ terrain::{BlockKind, SpriteKind, TerrainChunk}, vol::{IntoVolIterator, RectRasterableVol}, @@ -6,6 +7,12 @@ use common_base::span; use rand::prelude::*; use vek::*; +#[derive(Copy, Clone)] +pub enum Interaction { + Collect, + Craft(CraftingTab), +} + #[derive(Default)] pub struct BlocksOfInterest { pub leaves: Vec>, @@ -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,26 @@ impl BlocksOfInterest { Some(SpriteKind::WhiteFlower) => flowers.push(pos), Some(SpriteKind::YellowFlower) => flowers.push(pos), Some(SpriteKind::Sunflower) => flowers.push(pos), + Some(SpriteKind::CraftingBench) => { + interactables.push((pos, Interaction::Craft(CraftingTab::All))) + }, + Some(SpriteKind::Forge) => { + interactables.push((pos, Interaction::Craft(CraftingTab::Dismantle))) + }, + Some(SpriteKind::Cauldron) => { + interactables.push((pos, Interaction::Craft(CraftingTab::Potion))) + }, + Some(SpriteKind::Anvil) => { + interactables.push((pos, Interaction::Craft(CraftingTab::Weapon))) + }, + Some(SpriteKind::CookingPot) => { + 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..102cfd3201 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, terrain::Interaction, CameraMode, Scene, SceneData}, 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,9 +591,19 @@ 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) => { @@ -618,7 +628,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 +1191,11 @@ 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 +1453,7 @@ fn under_cursor( #[derive(Clone, Copy)] pub enum Interactable { - Block(Block, Vec3), + Block(Block, Vec3, Interaction), Entity(specs::Entity), } @@ -1448,7 +1461,7 @@ impl Interactable { pub fn entity(self) -> Option { match self { Self::Entity(e) => Some(e), - Self::Block(_, _) => None, + Self::Block(_, _, _) => None, } } } @@ -1476,7 +1489,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 +1553,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..7aecd69def 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,13 +295,12 @@ 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, - }) - .unwrap(), + Block::air(attr.window).with_ori(end_ori).unwrap(), structural_layer, ); let fire = BlockMask::new(Block::air(SpriteKind::Ember), foundation_layer); @@ -519,6 +518,32 @@ 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, + )) % 8 + { + 0..=1 => SpriteKind::Crate, + 2 => SpriteKind::Bench, + 3 => SpriteKind::Anvil, + 4 => SpriteKind::CookingPot, + 5 => SpriteKind::CraftingBench, + 6 => SpriteKind::FireBowlGround, + 7 => SpriteKind::Cauldron, + //8 => SpriteKind::Forge, + _ => 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 +617,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, };