diff --git a/assets/voxygen/i18n/en/hud/crafting.ftl b/assets/voxygen/i18n/en/hud/crafting.ftl index c65852f348..7c356bf048 100644 --- a/assets/voxygen/i18n/en/hud/crafting.ftl +++ b/assets/voxygen/i18n/en/hud/crafting.ftl @@ -14,6 +14,7 @@ hud-crafting-loom = Loom hud-crafting-spinning_wheel = Spinning Wheel hud-crafting-tanning_rack = Tanning Rack hud-crafting-salvaging_station = Salvaging Bench +hud-crafting-repair_bench = Repair Bench hud-crafting-campfire = Campfire hud-crafting-tabs-all = All hud-crafting-tabs-armor = Armor @@ -34,11 +35,8 @@ hud-crafting-dismantle_explanation = Double-Click them to start dismantling. hud-crafting-repair_title = Repair Items -hud-crafting-repair_explanation = - Repair your damaged and - broken equipment here. - - Double-Click the item to repair it. +hud-crafting-repair_slot_title = Damaged Item +hud-crafting-repair_slot_desc = Place a damaged item here to repair it. hud-crafting-modular_desc = Drag Item-Parts here to craft a weapon hud-crafting-mod_weap_prim_slot_title = Primary Weapon Component hud-crafting-mod_weap_prim_slot_desc = Place a primary weapon component here (e.g. a sword blade, axe head, or bow limbs). diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 2a4039a30e..df1d68f37c 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -39,6 +39,10 @@ "voxel.sprite.salvaging_station.salvaging_station-0", (0.0, 0.0, 0.0), (-50.0, 40.0, 30.0), 0.9, ), + Simple("RepairBench"): VoxTrans( + "voxel.sprite.repair_bench.repair_bench-0", + (0.0, 0.0, 0.0), (-50.0, 40.0, 30.0), 0.9, + ), // Weapons // Diary Example Images Simple("example_utility"): VoxTrans( diff --git a/voxygen/src/hud/crafting.rs b/voxygen/src/hud/crafting.rs index c344e4514b..15eb730e56 100644 --- a/voxygen/src/hud/crafting.rs +++ b/voxygen/src/hud/crafting.rs @@ -22,7 +22,7 @@ use common::{ Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemTag, MaterialStatManifest, Quality, TagExampleInfo, }, - slot::InvSlotId, + slot::{InvSlotId, Slot}, Inventory, }, recipe::{ComponentKey, Recipe, RecipeInput}, @@ -86,10 +86,7 @@ widget_ids! { dismantle_title, dismantle_img, dismantle_txt, - repair_title, - repair_img, - repair_txt, - modular_inputs[], + craft_slots[], modular_art, modular_desc_txt, modular_wep_empty_bg, @@ -117,6 +114,9 @@ pub enum Event { Focus(widget::Id), SearchRecipe(Option), ClearRecipeInputs, + RepairItem { + slot: Slot, + }, } pub struct CraftingShow { @@ -126,7 +126,7 @@ pub struct CraftingShow { pub salvage: bool, pub repair: bool, // TODO: Maybe try to do something that doesn't need to allocate? - pub recipe_inputs: HashMap, + pub recipe_inputs: HashMap, } impl Default for CraftingShow { @@ -282,7 +282,7 @@ impl CraftingTab { // Tells UI whether tab is an adhoc tab that should only sometimes be present // depending on what station is accessed - fn is_adhoc(self) -> bool { matches!(self, CraftingTab::Dismantle | CraftingTab::Repair) } + fn is_adhoc(self) -> bool { matches!(self, CraftingTab::Dismantle) } } pub struct State { @@ -534,40 +534,45 @@ impl<'a> Widget for Crafting<'a> { let weapon_recipe = make_pseudo_recipe(SpriteKind::CraftingBench); let metal_comp_recipe = make_pseudo_recipe(SpriteKind::Anvil); let wood_comp_recipe = make_pseudo_recipe(SpriteKind::CraftingBench); - let modular_entries = { + let repair_recipe = make_pseudo_recipe(SpriteKind::RepairBench); + let pseudo_entries = { // A BTreeMap is used over a HashMap as when a HashMap is used, the UI shuffles // the positions of these every tick, so a BTreeMap is necessary to keep it // ordered. - let mut modular_entries = BTreeMap::new(); - modular_entries.insert( + let mut pseudo_entries = BTreeMap::new(); + pseudo_entries.insert( String::from("veloren.core.pseudo_recipe.modular_weapon"), - (&weapon_recipe, "Modular Weapon"), + (&weapon_recipe, "Modular Weapon", CraftingTab::Weapon), ); - modular_entries.insert( + pseudo_entries.insert( String::from("veloren.core.pseudo_recipe.modular_weapon_component.sword"), - (&metal_comp_recipe, "Sword Blade"), + (&metal_comp_recipe, "Sword Blade", CraftingTab::Weapon), ); - modular_entries.insert( + pseudo_entries.insert( String::from("veloren.core.pseudo_recipe.modular_weapon_component.axe"), - (&metal_comp_recipe, "Axe Head"), + (&metal_comp_recipe, "Axe Head", CraftingTab::Weapon), ); - modular_entries.insert( + pseudo_entries.insert( String::from("veloren.core.pseudo_recipe.modular_weapon_component.hammer"), - (&metal_comp_recipe, "Hammer Head"), + (&metal_comp_recipe, "Hammer Head", CraftingTab::Weapon), ); - modular_entries.insert( + pseudo_entries.insert( String::from("veloren.core.pseudo_recipe.modular_weapon_component.bow"), - (&wood_comp_recipe, "Bow Limbs"), + (&wood_comp_recipe, "Bow Limbs", CraftingTab::Weapon), ); - modular_entries.insert( + pseudo_entries.insert( String::from("veloren.core.pseudo_recipe.modular_weapon_component.staff"), - (&wood_comp_recipe, "Staff Shaft"), + (&wood_comp_recipe, "Staff Shaft", CraftingTab::Weapon), ); - modular_entries.insert( + pseudo_entries.insert( String::from("veloren.core.pseudo_recipe.modular_weapon_component.sceptre"), - (&wood_comp_recipe, "Sceptre Shaft"), + (&wood_comp_recipe, "Sceptre Shaft", CraftingTab::Weapon), ); - modular_entries + pseudo_entries.insert( + String::from("veloren.core.pseudo_recipe.repair"), + (&repair_recipe, "Repair Equipment", CraftingTab::Repair), + ); + pseudo_entries }; // First available recipes, then ones with available materials, @@ -618,36 +623,34 @@ impl<'a> Widget for Crafting<'a> { (name, recipe, is_craftable, has_materials) }) .chain( - matches!(sel_crafting_tab, CraftingTab::Weapon | CraftingTab::All) - .then_some( - modular_entries - .iter() - .filter(|(_, (_, output_name))| { - match search_filter { - SearchFilter::None => { - let output_name = output_name.to_lowercase(); - search_keys - .iter() - .all(|&substring| output_name.contains(substring)) - }, - // TODO: Get input filtering to work here, probably requires - // checking component recipe book? - SearchFilter::Input => false, - SearchFilter::Nonexistent => false, - } - }) - .map(|(recipe_name, (recipe, _))| { - ( - recipe_name, - *recipe, - self.show.crafting_fields.craft_sprite.map(|(_, s)| s) - == recipe.craft_sprite, - true, - ) - }), - ) - .into_iter() - .flatten(), + pseudo_entries + .iter() + // Filter by selected tab + .filter(|(_, (_, _, tab))| *sel_crafting_tab == CraftingTab::All || sel_crafting_tab == tab) + // Filter by search filter + .filter(|(_, (_, output_name, _))| { + match search_filter { + SearchFilter::None => { + let output_name = output_name.to_lowercase(); + search_keys + .iter() + .all(|&substring| output_name.contains(substring)) + }, + // TODO: Get input filtering to work here, probably requires + // checking component recipe book? + SearchFilter::Input => false, + SearchFilter::Nonexistent => false, + } + }) + .map(|(recipe_name, (recipe, _, _))| { + ( + recipe_name, + *recipe, + self.show.crafting_fields.craft_sprite.map(|(_, s)| s) + == recipe.craft_sprite, + true, + ) + }), ) .collect(); ordered_recipes.sort_by_key(|(_, recipe, is_craftable, has_materials)| { @@ -660,7 +663,7 @@ impl<'a> Widget for Crafting<'a> { }); // Recipe list - let recipe_list_length = self.client.recipe_book().iter().len() + modular_entries.len(); + let recipe_list_length = self.client.recipe_book().iter().len() + pseudo_entries.len(); if state.ids.recipe_list_btns.len() < recipe_list_length { state.update(|state| { state @@ -715,11 +718,12 @@ impl<'a> Widget for Crafting<'a> { .press_image(self.imgs.selection_press) .image_color(color::rgba(1.0, 0.82, 0.27, 1.0)); - let recipe_name = if let Some((_recipe, modular_name)) = modular_entries.get(name) { - *modular_name - } else { - &recipe.output.0.name - }; + let recipe_name = + if let Some((_recipe, pseudo_name, _filter_tab)) = pseudo_entries.get(name) { + *pseudo_name + } else { + &recipe.output.0.name + }; let text = Text::new(recipe_name) .color(if is_craftable { @@ -821,7 +825,8 @@ impl<'a> Widget for Crafting<'a> { // Selected Recipe if let Some((recipe_name, recipe)) = match state.selected_recipe.as_deref() { Some(selected_recipe) => { - if let Some((modular_recipe, _modular_name)) = modular_entries.get(selected_recipe) + if let Some((modular_recipe, _pseudo_name, _filter_tab)) = + pseudo_entries.get(selected_recipe) { Some((selected_recipe, *modular_recipe)) } else { @@ -834,8 +839,10 @@ impl<'a> Widget for Crafting<'a> { None => None, } { let recipe_name = String::from(recipe_name); - let title = if let Some((_recipe, modular_name)) = modular_entries.get(&recipe_name) { - *modular_name + let title = if let Some((_recipe, pseudo_name, _filter_tab)) = + pseudo_entries.get(&recipe_name) + { + *pseudo_name } else { &recipe.output.0.name }; @@ -853,6 +860,7 @@ impl<'a> Widget for Crafting<'a> { ModularWeapon, Component(ToolKind), Simple, + Repair, } let recipe_kind = match recipe_name.as_str() { @@ -875,37 +883,36 @@ impl<'a> Widget for Crafting<'a> { "veloren.core.pseudo_recipe.modular_weapon_component.sceptre" => { RecipeKind::Component(ToolKind::Sceptre) }, + "veloren.core.pseudo_recipe.repair" => RecipeKind::Repair, _ => RecipeKind::Simple, }; - // Output slot, tags, and modular input slots - let (modular_primary_slot, modular_secondary_slot, can_perform) = match recipe_kind { - RecipeKind::ModularWeapon | RecipeKind::Component(_) => { - let mut slot_maker = SlotMaker { - empty_slot: self.imgs.inv_slot, - filled_slot: self.imgs.inv_slot, - selected_slot: self.imgs.inv_slot_sel, - background_color: Some(UI_MAIN), - content_size: ContentSize { - width_height_ratio: 1.0, - max_fraction: 0.75, - }, - selected_content_scale: 1.067, - amount_font: self.fonts.cyri.conrod_id, - amount_margins: Vec2::new(-4.0, 0.0), - amount_font_size: self.fonts.cyri.scale(12), - amount_text_color: TEXT_COLOR, - content_source: self.inventory, - image_source: self.item_imgs, - slot_manager: Some(self.slot_manager), - pulse: self.pulse, - }; + let mut slot_maker = SlotMaker { + empty_slot: self.imgs.inv_slot, + filled_slot: self.imgs.inv_slot, + selected_slot: self.imgs.inv_slot_sel, + background_color: Some(UI_MAIN), + content_size: ContentSize { + width_height_ratio: 1.0, + max_fraction: 0.75, + }, + selected_content_scale: 1.067, + amount_font: self.fonts.cyri.conrod_id, + amount_margins: Vec2::new(-4.0, 0.0), + amount_font_size: self.fonts.cyri.scale(12), + amount_text_color: TEXT_COLOR, + content_source: self.inventory, + image_source: self.item_imgs, + slot_manager: Some(self.slot_manager), + pulse: self.pulse, + }; - if state.ids.modular_inputs.len() < 2 { + // Output slot, tags, and modular input slots + let (craft_slot_1, craft_slot_2, can_perform) = match recipe_kind { + RecipeKind::ModularWeapon | RecipeKind::Component(_) => { + if state.ids.craft_slots.len() < 2 { state.update(|s| { - s.ids - .modular_inputs - .resize(2, &mut ui.widget_id_generator()); + s.ids.craft_slots.resize(2, &mut ui.widget_id_generator()); }); } // Modular Weapon Crafting BG-Art @@ -916,7 +923,7 @@ impl<'a> Widget for Crafting<'a> { let primary_slot = CraftSlot { index: 0, - invslot: self.show.crafting_fields.recipe_inputs.get(&0).copied(), + slot: self.show.crafting_fields.recipe_inputs.get(&0).copied(), requirement: match recipe_kind { RecipeKind::ModularWeapon => |item, _, _| { matches!( @@ -939,11 +946,13 @@ impl<'a> Widget for Crafting<'a> { false } }, - RecipeKind::Simple => |_, _, _| unreachable!(), + RecipeKind::Simple | RecipeKind::Repair => |_, _, _| unreachable!(), }, info: match recipe_kind { RecipeKind::Component(toolkind) => Some(CraftSlotInfo::Tool(toolkind)), - RecipeKind::ModularWeapon | RecipeKind::Simple => None, + RecipeKind::ModularWeapon | RecipeKind::Simple | RecipeKind::Repair => { + None + }, }, }; @@ -952,10 +961,7 @@ impl<'a> Widget for Crafting<'a> { .top_left_with_margins_on(state.ids.modular_art, 4.0, 4.0) .parent(state.ids.align_ing); - if let Some(item) = primary_slot - .invslot - .and_then(|slot| self.inventory.get(slot)) - { + if let Some(item) = primary_slot.item(self.inventory) { primary_slot_widget .with_item_tooltip( self.item_tooltip_manager, @@ -963,7 +969,7 @@ impl<'a> Widget for Crafting<'a> { &None, &item_tooltip, ) - .set(state.ids.modular_inputs[0], ui); + .set(state.ids.craft_slots[0], ui); } else { let (tooltip_title, tooltip_desc) = match recipe_kind { RecipeKind::ModularWeapon => ( @@ -988,7 +994,7 @@ impl<'a> Widget for Crafting<'a> { self.localized_strings .get_msg("hud-crafting-mod_comp_wood_prim_slot_desc"), ), - RecipeKind::Component(_) | RecipeKind::Simple => { + RecipeKind::Component(_) | RecipeKind::Simple | RecipeKind::Repair => { (Cow::Borrowed(""), Cow::Borrowed("")) }, }; @@ -1000,12 +1006,12 @@ impl<'a> Widget for Crafting<'a> { &tabs_tooltip, TEXT_COLOR, ) - .set(state.ids.modular_inputs[0], ui); + .set(state.ids.craft_slots[0], ui); } let secondary_slot = CraftSlot { index: 1, - invslot: self.show.crafting_fields.recipe_inputs.get(&1).copied(), + slot: self.show.crafting_fields.recipe_inputs.get(&1).copied(), requirement: match recipe_kind { RecipeKind::ModularWeapon => |item, _, _| { matches!( @@ -1028,11 +1034,13 @@ impl<'a> Widget for Crafting<'a> { false } }, - RecipeKind::Simple => |_, _, _| unreachable!(), + RecipeKind::Simple | RecipeKind::Repair => |_, _, _| unreachable!(), }, info: match recipe_kind { RecipeKind::Component(toolkind) => Some(CraftSlotInfo::Tool(toolkind)), - RecipeKind::ModularWeapon | RecipeKind::Simple => None, + RecipeKind::ModularWeapon | RecipeKind::Simple | RecipeKind::Repair => { + None + }, }, }; @@ -1041,10 +1049,7 @@ impl<'a> Widget for Crafting<'a> { .top_right_with_margins_on(state.ids.modular_art, 4.0, 4.0) .parent(state.ids.align_ing); - if let Some(item) = secondary_slot - .invslot - .and_then(|slot| self.inventory.get(slot)) - { + if let Some(item) = secondary_slot.item(self.inventory) { secondary_slot_widget .with_item_tooltip( self.item_tooltip_manager, @@ -1052,7 +1057,7 @@ impl<'a> Widget for Crafting<'a> { &None, &item_tooltip, ) - .set(state.ids.modular_inputs[1], ui); + .set(state.ids.craft_slots[1], ui); } else { let (tooltip_title, tooltip_desc) = match recipe_kind { RecipeKind::ModularWeapon => ( @@ -1067,7 +1072,9 @@ impl<'a> Widget for Crafting<'a> { self.localized_strings .get_msg("hud-crafting-mod_comp_sec_slot_desc"), ), - RecipeKind::Simple => (Cow::Borrowed(""), Cow::Borrowed("")), + RecipeKind::Simple | RecipeKind::Repair => { + (Cow::Borrowed(""), Cow::Borrowed("")) + }, }; secondary_slot_widget .with_tooltip( @@ -1077,11 +1084,11 @@ impl<'a> Widget for Crafting<'a> { &tabs_tooltip, TEXT_COLOR, ) - .set(state.ids.modular_inputs[1], ui); + .set(state.ids.craft_slots[1], ui); } - let prim_item_placed = primary_slot.invslot.is_some(); - let sec_item_placed = secondary_slot.invslot.is_some(); + let prim_item_placed = primary_slot.slot.is_some(); + let sec_item_placed = secondary_slot.slot.is_some(); let prim_icon = match recipe_kind { RecipeKind::ModularWeapon => self.imgs.icon_primary_comp, @@ -1109,18 +1116,18 @@ impl<'a> Widget for Crafting<'a> { let bg_col = Color::Rgba(1.0, 1.0, 1.0, 0.4); if !prim_item_placed { Image::new(prim_icon) - .middle_of(state.ids.modular_inputs[0]) + .middle_of(state.ids.craft_slots[0]) .color(Some(bg_col)) .w_h(34.0, 34.0) - .graphics_for(state.ids.modular_inputs[0]) + .graphics_for(state.ids.craft_slots[0]) .set(state.ids.modular_wep_ing_1_bg, ui); } if !sec_item_placed { Image::new(sec_icon) - .middle_of(state.ids.modular_inputs[1]) + .middle_of(state.ids.craft_slots[1]) .color(Some(bg_col)) .w_h(50.0, 50.0) - .graphics_for(state.ids.modular_inputs[1]) + .graphics_for(state.ids.craft_slots[1]) .set(state.ids.modular_wep_ing_2_bg, ui); } @@ -1129,10 +1136,8 @@ impl<'a> Widget for Crafting<'a> { let output_item = match recipe_kind { RecipeKind::ModularWeapon => { - if let Some((primary_comp, toolkind, hand_restriction)) = primary_slot - .invslot - .and_then(|slot| self.inventory.get(slot)) - .and_then(|item| { + if let Some((primary_comp, toolkind, hand_restriction)) = + primary_slot.item(self.inventory).and_then(|item| { if let ItemKind::ModularComponent( ModularComponent::ToolPrimaryComponent { toolkind, @@ -1148,8 +1153,7 @@ impl<'a> Widget for Crafting<'a> { }) { secondary_slot - .invslot - .and_then(|slot| self.inventory.get(slot)) + .item(self.inventory) .filter(|item| { matches!( &*item.kind(), @@ -1174,22 +1178,19 @@ impl<'a> Widget for Crafting<'a> { } }, RecipeKind::Component(toolkind) => { - if let Some(material) = primary_slot - .invslot - .and_then(|slot| self.inventory.get(slot)) - .and_then(|item| { + if let Some(material) = + primary_slot.item(self.inventory).and_then(|item| { item.item_definition_id().itemdef_id().map(String::from) }) { let component_key = ComponentKey { toolkind, material, - modifier: secondary_slot - .invslot - .and_then(|slot| self.inventory.get(slot)) - .and_then(|item| { + modifier: secondary_slot.item(self.inventory).and_then( + |item| { item.item_definition_id().itemdef_id().map(String::from) - }), + }, + ), }; self.client.component_recipe_book().get(&component_key).map( |component_recipe| { @@ -1200,7 +1201,7 @@ impl<'a> Widget for Crafting<'a> { None } }, - RecipeKind::Simple => None, + RecipeKind::Simple | RecipeKind::Repair => None, }; if let Some(output_item) = output_item { @@ -1226,8 +1227,8 @@ impl<'a> Widget for Crafting<'a> { ) .set(state.ids.output_img, ui); ( - primary_slot.invslot, - secondary_slot.invslot, + primary_slot.slot, + secondary_slot.slot, self.show.crafting_fields.craft_sprite.map(|(_, s)| s) == recipe.craft_sprite, ) @@ -1244,7 +1245,7 @@ impl<'a> Widget for Crafting<'a> { .w_h(70.0, 70.0) .graphics_for(state.ids.output_img) .set(state.ids.modular_wep_empty_bg, ui); - (primary_slot.invslot, secondary_slot.invslot, false) + (primary_slot.slot, secondary_slot.slot, false) } }, RecipeKind::Simple => { @@ -1349,6 +1350,64 @@ impl<'a> Widget for Crafting<'a> { }), ) }, + RecipeKind::Repair => { + if state.ids.craft_slots.len() < 1 { + state.update(|s| { + s.ids.craft_slots.resize(1, &mut ui.widget_id_generator()); + }); + } + + // Slot for item to be repaired + let repair_slot = CraftSlot { + index: 0, + slot: self.show.crafting_fields.recipe_inputs.get(&0).copied(), + requirement: |item, _, _| item.durability().map_or(false, |d| d > 0), + info: None, + }; + + let repair_slot_widget = slot_maker + .fabricate(repair_slot, [40.0; 2]) + .mid_top_with_margin_on(state.ids.align_ing, 20.0) + .parent(state.ids.align_ing); + + if let Some(item) = repair_slot.item(self.inventory) { + repair_slot_widget + .with_item_tooltip( + self.item_tooltip_manager, + core::iter::once(item as &dyn ItemDesc), + &None, + &item_tooltip, + ) + .set(state.ids.craft_slots[0], ui); + } else { + repair_slot_widget + .with_tooltip( + self.tooltip_manager, + &self + .localized_strings + .get_msg("hud-crafting-repair_slot_title"), + &self + .localized_strings + .get_msg("hud-crafting-repair_slot_desc"), + &tabs_tooltip, + TEXT_COLOR, + ) + .set(state.ids.craft_slots[0], ui); + } + + let can_perform = repair_slot.item(self.inventory).map_or(false, |item| { + self.client.repair_recipe_book().repair_recipe(item).map_or( + false, + |recipe| { + recipe + .inventory_contains_ingredients(item, self.inventory) + .is_ok() + }, + ) + }); + + (repair_slot.slot, None, can_perform) + }, }; // Craft button @@ -1386,8 +1445,10 @@ impl<'a> Widget for Crafting<'a> { { match recipe_kind { RecipeKind::ModularWeapon => { - if let (Some(primary_slot), Some(secondary_slot)) = - (modular_primary_slot, modular_secondary_slot) + if let ( + Some(Slot::Inventory(primary_slot)), + Some(Slot::Inventory(secondary_slot)), + ) = (craft_slot_1, craft_slot_2) { events.push(Event::CraftModularWeapon { primary_slot, @@ -1396,11 +1457,14 @@ impl<'a> Widget for Crafting<'a> { } }, RecipeKind::Component(toolkind) => { - if let Some(primary_slot) = modular_primary_slot { + if let Some(Slot::Inventory(primary_slot)) = craft_slot_1 { events.push(Event::CraftModularWeaponComponent { toolkind, material: primary_slot, - modifier: modular_secondary_slot, + modifier: craft_slot_2.and_then(|slot| match slot { + Slot::Inventory(slot) => Some(slot), + Slot::Equip(_) => None, + }), }); } }, @@ -1408,6 +1472,11 @@ impl<'a> Widget for Crafting<'a> { recipe_name, amount: 1, }), + RecipeKind::Repair => { + if let Some(slot) = craft_slot_1 { + events.push(Event::RepairItem { slot }); + } + }, } } @@ -1477,6 +1546,9 @@ impl<'a> Widget for Crafting<'a> { RecipeKind::ModularWeapon | RecipeKind::Component(_) => { t.top_left_with_margins_on(state.ids.align_ing, 325.0, 5.0) }, + RecipeKind::Repair => { + t.top_left_with_margins_on(state.ids.align_ing, 80.0, 5.0) + }, }) .set(state.ids.req_station_title, ui); let station_img = match recipe.craft_sprite { @@ -1489,6 +1561,7 @@ impl<'a> Widget for Crafting<'a> { Some(SpriteKind::SpinningWheel) => "SpinningWheel", Some(SpriteKind::TanningRack) => "TanningRack", Some(SpriteKind::DismantlingBench) => "DismantlingBench", + Some(SpriteKind::RepairBench) => "RepairBench", None => "CraftsmanHammer", _ => "CraftsmanHammer", }; @@ -1513,6 +1586,7 @@ impl<'a> Widget for Crafting<'a> { Some(SpriteKind::SpinningWheel) => "hud-crafting-spinning_wheel", Some(SpriteKind::TanningRack) => "hud-crafting-tanning_rack", Some(SpriteKind::DismantlingBench) => "hud-crafting-salvaging_station", + Some(SpriteKind::RepairBench) => "hud-crafting-repair_bench", _ => "", }; Text::new(&self.localized_strings.get_msg(station_name)) @@ -1532,7 +1606,7 @@ impl<'a> Widget for Crafting<'a> { } // Ingredients Text // Hack from Sharp to account for iterators not having the same type - let (mut iter_a, mut iter_b, mut iter_c); + let (mut iter_a, mut iter_b, mut iter_c, mut iter_d); let ingredients = match recipe_kind { RecipeKind::Simple => { iter_a = recipe @@ -1546,15 +1620,21 @@ impl<'a> Widget for Crafting<'a> { &mut iter_b }, RecipeKind::Component(toolkind) => { - if let Some(material) = modular_primary_slot - .and_then(|slot| self.inventory.get(slot)) + if let Some(material) = craft_slot_1 + .and_then(|slot| match slot { + Slot::Inventory(slot) => self.inventory.get(slot), + Slot::Equip(_) => None, + }) .and_then(|item| item.item_definition_id().itemdef_id().map(String::from)) { let component_key = ComponentKey { toolkind, material, - modifier: modular_secondary_slot - .and_then(|slot| self.inventory.get(slot)) + modifier: craft_slot_2 + .and_then(|slot| match slot { + Slot::Inventory(slot) => self.inventory.get(slot), + Slot::Equip(_) => None, + }) .and_then(|item| { item.item_definition_id().itemdef_id().map(String::from) }), @@ -1573,6 +1653,24 @@ impl<'a> Widget for Crafting<'a> { &mut iter_b } }, + RecipeKind::Repair => { + if let Some(item) = match craft_slot_1 { + Some(Slot::Inventory(slot)) => self.inventory.get(slot), + Some(Slot::Equip(slot)) => self.inventory.equipped(slot), + None => None, + } { + if let Some(recipe) = self.client.repair_recipe_book().repair_recipe(item) { + iter_d = recipe.inputs(item); + &mut iter_d as &mut dyn ExactSizeIterator + } else { + iter_b = core::iter::empty(); + &mut iter_b + } + } else { + iter_b = core::iter::empty(); + &mut iter_b + } + }, }; let num_ingredients = ingredients.len(); @@ -1861,42 +1959,6 @@ impl<'a> Widget for Crafting<'a> { .color(TEXT_COLOR) .parent(state.ids.window) .set(state.ids.dismantle_txt, ui); - } else if *sel_crafting_tab == CraftingTab::Repair { - // Title - Text::new(&self.localized_strings.get_msg("hud-crafting-repair_title")) - .mid_top_with_margin_on(state.ids.align_ing, 0.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(24)) - .color(TEXT_COLOR) - .parent(state.ids.window) - .set(state.ids.repair_title, ui); - - // Bench Icon - let size = 140.0; - Image::new(animate_by_pulse( - &self - .item_imgs - .img_ids_or_not_found_img(ItemKey::Simple("RepairBench".to_string())), - self.pulse, - )) - .wh([size; 2]) - .mid_top_with_margin_on(state.ids.align_ing, 50.0) - .parent(state.ids.align_ing) - .set(state.ids.repair_img, ui); - - // Explanation - - Text::new( - &self - .localized_strings - .get_msg("hud-crafting-repair_explanation"), - ) - .mid_bottom_with_margin_on(state.ids.repair_img, -60.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(14)) - .color(TEXT_COLOR) - .parent(state.ids.window) - .set(state.ids.repair_txt, ui); } // Search / Title Recipes diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 03d0129229..b9462d8580 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -3271,6 +3271,19 @@ impl Hud { crafting::Event::ClearRecipeInputs => { self.show.crafting_fields.recipe_inputs.clear(); }, + crafting::Event::RepairItem { slot } => { + if let Some(sprite_pos) = self + .show + .crafting_fields + .craft_sprite + .map(|(pos, _sprite)| pos) + { + events.push(Event::RepairItem { + item: slot, + sprite_pos, + }); + } + }, } } } @@ -3805,7 +3818,21 @@ impl Hud { self.show .crafting_fields .recipe_inputs - .insert(c.index, i.slot); + .insert(c.index, Slot::Inventory(i.slot)); + } + } else if let (Equip(e), Crafting(c)) = (a, b) { + // Add item to crafting input + if inventories + .get(client.entity()) + .and_then(|inv| inv.equipped(e)) + .map_or(false, |item| { + (c.requirement)(item, client.component_recipe_book(), c.info) + }) + { + self.show + .crafting_fields + .recipe_inputs + .insert(c.index, Slot::Equip(e)); } } else if let (Crafting(c), Inventory(_)) = (a, b) { // Remove item from crafting input @@ -3937,17 +3964,6 @@ impl Hud { { events.push(Event::SalvageItem { slot, salvage_pos }) } - } else if self.show.crafting_fields.repair - && matches!(self.show.crafting_fields.crafting_tab, CraftingTab::Repair) - { - if let Some((sprite_pos, _sprite_kind)) = - self.show.crafting_fields.craft_sprite - { - events.push(Event::RepairItem { - item: from, - sprite_pos, - }) - } } else { events.push(Event::UseSlot { slot: from, diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index 04e52acc6e..32770ba423 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -9,7 +9,7 @@ use common::{ comp::{ ability::{Ability, AbilityInput, AuxiliaryAbility}, item::tool::{AbilityContext, ToolKind}, - slot::InvSlotId, + slot::{InvSlotId, Slot}, ActiveAbilities, Body, CharacterState, Combo, Energy, Inventory, Item, ItemKey, SkillSet, Stance, }, @@ -276,27 +276,35 @@ impl<'a> SlotKey, img_ids::Imgs> for AbilitySlot { #[derive(Clone, Copy)] pub struct CraftSlot { pub index: u32, - pub invslot: Option, + pub slot: Option, pub requirement: fn(&Item, &ComponentRecipeBook, Option) -> bool, pub info: Option, } +impl CraftSlot { + pub fn item<'a>(&'a self, inv: &'a Inventory) -> Option<&'a Item> { + match self.slot { + Some(Slot::Inventory(slot)) => inv.get(slot), + Some(Slot::Equip(slot)) => inv.equipped(slot), + None => None, + } + } +} + #[derive(Clone, Copy, Debug)] pub enum CraftSlotInfo { Tool(ToolKind), } impl PartialEq for CraftSlot { - fn eq(&self, other: &Self) -> bool { - (self.index, self.invslot) == (other.index, other.invslot) - } + fn eq(&self, other: &Self) -> bool { (self.index, self.slot) == (other.index, other.slot) } } impl Debug for CraftSlot { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { f.debug_struct("CraftSlot") .field("index", &self.index) - .field("invslot", &self.invslot) + .field("slot", &self.slot) .field("requirement", &"fn ptr") .finish() } @@ -306,14 +314,11 @@ impl SlotKey for CraftSlot { type ImageKey = ItemKey; fn image_key(&self, source: &Inventory) -> Option<(Self::ImageKey, Option)> { - self.invslot - .and_then(|invslot| source.get(invslot)) - .map(|i| (i.into(), None)) + self.item(source).map(|i| (i.into(), None)) } fn amount(&self, source: &Inventory) -> Option { - self.invslot - .and_then(|invslot| source.get(invslot)) + self.item(source) .map(|item| item.amount()) .filter(|amount| *amount > 1) }