Repairing UI

This commit is contained in:
Sam 2022-08-02 22:29:52 -04:00
parent 536d88a2c7
commit 658de93c59
5 changed files with 292 additions and 207 deletions

View File

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

View File

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

View File

@ -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<String>),
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<u32, InvSlotId>,
pub recipe_inputs: HashMap<u32, Slot>,
}
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<Item = _>
} 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

View File

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

View File

@ -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<AbilitiesSource<'a>, img_ids::Imgs> for AbilitySlot {
#[derive(Clone, Copy)]
pub struct CraftSlot {
pub index: u32,
pub invslot: Option<InvSlotId>,
pub slot: Option<Slot>,
pub requirement: fn(&Item, &ComponentRecipeBook, Option<CraftSlotInfo>) -> bool,
pub info: Option<CraftSlotInfo>,
}
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<Inventory, ItemImgs> for CraftSlot {
type ImageKey = ItemKey;
fn image_key(&self, source: &Inventory) -> Option<(Self::ImageKey, Option<Color>)> {
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<u32> {
self.invslot
.and_then(|invslot| source.get(invslot))
self.item(source)
.map(|item| item.amount())
.filter(|amount| *amount > 1)
}