Initial UI including ingredients in crafting modular components

This commit is contained in:
Sam 2022-03-25 11:09:42 -04:00
parent 085b18c07a
commit a5a517916e
2 changed files with 624 additions and 563 deletions

View File

@ -666,6 +666,17 @@ impl ComponentRecipe {
},
}
}
pub fn inputs(&self) -> impl ExactSizeIterator<Item = (&RecipeInput, u32)> {
let material = core::iter::once(&self.material);
let modifier = self.modifier.iter();
let additional_inputs = self.additional_inputs.iter();
material.chain(modifier.chain(additional_inputs))
.map(|(item_def, amount)| (item_def, *amount))
// Hack, not sure how to get exact size iterator from multiple chains.
.collect::<Vec<_>>()
.into_iter()
}
}
#[derive(Clone, Deserialize)]

View File

@ -806,7 +806,9 @@ impl<'a> Widget for Crafting<'a> {
},
None => None,
} {
let title = if let Some((_recipe, modular_name)) = modular_entries.get(recipe_name) {
// TODO: Avoid reallocating string and appease borrow checker
let recipe_name = String::from(recipe_name);
let title = if let Some((_recipe, modular_name)) = modular_entries.get(&recipe_name) {
*modular_name
} else {
&recipe.output.0.name
@ -820,36 +822,41 @@ impl<'a> Widget for Crafting<'a> {
.parent(state.ids.window)
.set(state.ids.title_ing, ui);
if modular_entries.get(recipe_name).is_some() {
#[derive(Clone, Copy, Debug)]
enum RecipeKind {
ModularWeapon,
Component(ToolKind),
Simple,
}
let recipe_kind = match recipe_name {
"veloren.core.pseudo_recipe.modular_weapon" => Some(RecipeKind::ModularWeapon),
let recipe_kind = match recipe_name.as_str() {
"veloren.core.pseudo_recipe.modular_weapon" => RecipeKind::ModularWeapon,
"veloren.core.pseudo_recipe.modular_weapon_component.sword" => {
Some(RecipeKind::Component(ToolKind::Sword))
RecipeKind::Component(ToolKind::Sword)
},
"veloren.core.pseudo_recipe.modular_weapon_component.axe" => {
Some(RecipeKind::Component(ToolKind::Axe))
RecipeKind::Component(ToolKind::Axe)
},
"veloren.core.pseudo_recipe.modular_weapon_component.hammer" => {
Some(RecipeKind::Component(ToolKind::Hammer))
RecipeKind::Component(ToolKind::Hammer)
},
"veloren.core.pseudo_recipe.modular_weapon_component.bow" => {
Some(RecipeKind::Component(ToolKind::Bow))
RecipeKind::Component(ToolKind::Bow)
},
"veloren.core.pseudo_recipe.modular_weapon_component.staff" => {
Some(RecipeKind::Component(ToolKind::Staff))
RecipeKind::Component(ToolKind::Staff)
},
"veloren.core.pseudo_recipe.modular_weapon_component.sceptre" => {
Some(RecipeKind::Component(ToolKind::Sceptre))
RecipeKind::Component(ToolKind::Sceptre)
},
_ => None,
_ => RecipeKind::Simple,
};
let mut modular_slot_check_hack = (None, None);
// Output slot, tags, and modular input slots
match recipe_kind {
RecipeKind::ModularWeapon | RecipeKind::Component(_) => {
let mut slot_maker = SlotMaker {
empty_slot: self.imgs.inv_slot,
filled_slot: self.imgs.inv_slot,
@ -887,7 +894,7 @@ impl<'a> Widget for Crafting<'a> {
index: 0,
invslot: self.show.crafting_fields.recipe_inputs.get(&0).copied(),
requirement: match recipe_kind {
Some(RecipeKind::ModularWeapon) => |inv, slot| {
RecipeKind::ModularWeapon => |inv, slot| {
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
matches!(
&*item.kind(),
@ -902,12 +909,12 @@ impl<'a> Widget for Crafting<'a> {
// Some(RecipeKind::Component(toolkind)) => |inv, slot| {
// inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
// self.client.component_recipe_book().iter().filter(|(key,
// _recipe)| key.toolkind == toolkind).any(|(key, _recipe)| key.material ==
// item.item_definition_id()) })
// _recipe)| key.toolkind == toolkind).any(|(key, _recipe)| key.material
// == item.item_definition_id()) })
// },
Some(RecipeKind::Component(
RecipeKind::Component(
ToolKind::Sword | ToolKind::Axe | ToolKind::Hammer,
)) => |inv, slot| {
) => |inv, slot| {
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
matches!(&*item.kind(), ItemKind::Ingredient { .. })
&& item
@ -919,9 +926,9 @@ impl<'a> Widget for Crafting<'a> {
.any(|tag| matches!(tag, ItemTag::Material(_)))
})
},
Some(RecipeKind::Component(
RecipeKind::Component(
ToolKind::Bow | ToolKind::Staff | ToolKind::Sceptre,
)) => |inv, slot| {
) => |inv, slot| {
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
matches!(&*item.kind(), ItemKind::Ingredient { .. })
&& item
@ -933,7 +940,7 @@ impl<'a> Widget for Crafting<'a> {
.any(|tag| matches!(tag, ItemTag::Material(_)))
})
},
Some(RecipeKind::Component(_)) | None => |_, _| false,
RecipeKind::Component(_) | RecipeKind::Simple => |_, _| false,
},
};
@ -978,7 +985,7 @@ impl<'a> Widget for Crafting<'a> {
index: 1,
invslot: self.show.crafting_fields.recipe_inputs.get(&1).copied(),
requirement: match recipe_kind {
Some(RecipeKind::ModularWeapon) => |inv, slot| {
RecipeKind::ModularWeapon => |inv, slot| {
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
matches!(
&*item.kind(),
@ -993,16 +1000,16 @@ impl<'a> Widget for Crafting<'a> {
// Some(RecipeKind::Component(toolkind)) => |inv, slot| {
// inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
// self.client.component_recipe_book().iter().filter(|(key,
// _recipe)| key.toolkind == toolkind).any(|(key, _recipe)| key.modifier ==
// Some(item.item_definition_id())) })
// },
Some(RecipeKind::Component(_)) => |inv, slot| {
// _recipe)| key.toolkind == toolkind).any(|(key, _recipe)| key.modifier
// == Some(item.item_definition_id()))
// }) },
RecipeKind::Component(_) => |inv, slot| {
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
item.item_definition_id()
.starts_with("common.items.crafting_ing.animal_misc")
})
},
None => |_, _| false,
RecipeKind::Simple => |_, _| false,
},
};
@ -1042,65 +1049,9 @@ impl<'a> Widget for Crafting<'a> {
// .set(state.ids.modular_inputs[1], ui);
// }
let can_perform = match recipe_kind {
Some(RecipeKind::ModularWeapon) => {
primary_slot.invslot.is_some() && secondary_slot.invslot.is_some()
},
Some(RecipeKind::Component(_)) => primary_slot.invslot.is_some(),
None => false,
};
let prim_item_placed = primary_slot.invslot.is_some();
let sec_item_placed = secondary_slot.invslot.is_some();
// Craft button
if Button::image(self.imgs.button)
.w_h(105.0, 25.0)
.hover_image(
can_perform
.then_some(self.imgs.button_hover)
.unwrap_or(self.imgs.button),
)
.press_image(
can_perform
.then_some(self.imgs.button_press)
.unwrap_or(self.imgs.button),
)
.label(self.localized_strings.get("hud.crafting.craft"))
.label_y(conrod_core::position::Relative::Scalar(1.0))
.label_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR))
.label_font_size(self.fonts.cyri.scale(12))
.label_font_id(self.fonts.cyri.conrod_id)
.image_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR))
.mid_bottom_with_margin_on(state.ids.align_ing, -31.0)
.parent(state.ids.window_frame)
.set(state.ids.btn_craft, ui)
.was_clicked()
{
match recipe_kind {
Some(RecipeKind::ModularWeapon) => {
if let (Some(primary_slot), Some(secondary_slot)) =
(primary_slot.invslot, secondary_slot.invslot)
{
events.push(Event::CraftModularWeapon {
primary_slot,
secondary_slot,
});
}
},
Some(RecipeKind::Component(toolkind)) => {
if let Some(primary_slot) = primary_slot.invslot {
events.push(Event::CraftModularWeaponComponent {
toolkind,
material: primary_slot,
modifier: secondary_slot.invslot,
});
}
},
None => {},
}
}
// Output Image
Image::new(self.imgs.inv_slot)
.w_h(80.0, 80.0)
@ -1129,7 +1080,7 @@ impl<'a> Widget for Crafting<'a> {
let msm = &MaterialStatManifest::load().read();
if let Some(output_item) = match recipe_kind {
Some(RecipeKind::ModularWeapon) => {
RecipeKind::ModularWeapon => {
if let Some((primary_comp, toolkind)) = primary_slot
.invslot
.and_then(|slot| self.inventory.get(slot))
@ -1170,7 +1121,7 @@ impl<'a> Widget for Crafting<'a> {
None
}
},
Some(RecipeKind::Component(toolkind)) => {
RecipeKind::Component(toolkind) => {
if let Some(material) = primary_slot
.invslot
.and_then(|slot| self.inventory.get(slot))
@ -1185,13 +1136,15 @@ impl<'a> Widget for Crafting<'a> {
.map(|item| String::from(item.item_definition_id())),
};
self.client.component_recipe_book().get(&component_key).map(
|component_recipe| component_recipe.item_output(ability_map, msm),
|component_recipe| {
component_recipe.item_output(ability_map, msm)
},
)
} else {
None
}
},
None => None,
RecipeKind::Simple => None,
} {
Button::image(animate_by_pulse(
&self
@ -1228,44 +1181,10 @@ impl<'a> Widget for Crafting<'a> {
.graphics_for(state.ids.output_img)
.set(state.ids.modular_wep_empty_bg, ui);
}
} else {
let can_perform =
self.client
.available_recipes()
.get(recipe_name)
.map_or(false, |cs| {
cs.map_or(true, |cs| {
Some(cs) == self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
})
});
// Craft button
if Button::image(self.imgs.button)
.w_h(105.0, 25.0)
.hover_image(
can_perform
.then_some(self.imgs.button_hover)
.unwrap_or(self.imgs.button),
)
.press_image(
can_perform
.then_some(self.imgs.button_press)
.unwrap_or(self.imgs.button),
)
.label(self.localized_strings.get("hud.crafting.craft"))
.label_y(conrod_core::position::Relative::Scalar(1.0))
.label_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR))
.label_font_size(self.fonts.cyri.scale(12))
.label_font_id(self.fonts.cyri.conrod_id)
.image_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR))
.mid_bottom_with_margin_on(state.ids.align_ing, -31.0)
.parent(state.ids.window_frame)
.set(state.ids.btn_craft, ui)
.was_clicked()
{
events.push(Event::CraftRecipe(String::from(recipe_name)));
}
modular_slot_check_hack = (primary_slot.invslot, secondary_slot.invslot);
},
RecipeKind::Simple => {
// Output Image Frame
let quality_col_img = match recipe.output.0.quality() {
Quality::Low => self.imgs.inv_slot_grey,
@ -1322,7 +1241,9 @@ impl<'a> Widget for Crafting<'a> {
CraftingTab::All => false,
_ => crafting_tab.satisfies(recipe),
})
.filter(|crafting_tab| crafting_tab != &self.show.crafting_fields.crafting_tab)
.filter(|crafting_tab| {
crafting_tab != &self.show.crafting_fields.crafting_tab
})
.collect::<Vec<_>>()
.chunks(3)
.enumerate()
@ -1351,17 +1272,95 @@ impl<'a> Widget for Crafting<'a> {
.set(state.ids.tags_ing[i], ui);
}
}
},
}
let can_perform = match recipe_kind {
RecipeKind::ModularWeapon => {
modular_slot_check_hack.0.is_some() && modular_slot_check_hack.1.is_some()
},
RecipeKind::Component(_) => modular_slot_check_hack.0.is_some(),
RecipeKind::Simple => {
self.client
.available_recipes()
.get(&recipe_name)
.map_or(false, |cs| {
cs.map_or(true, |cs| {
Some(cs) == self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
})
})
},
};
// Craft button
if Button::image(self.imgs.button)
.w_h(105.0, 25.0)
.hover_image(
can_perform
.then_some(self.imgs.button_hover)
.unwrap_or(self.imgs.button),
)
.press_image(
can_perform
.then_some(self.imgs.button_press)
.unwrap_or(self.imgs.button),
)
.label(self.localized_strings.get("hud.crafting.craft"))
.label_y(conrod_core::position::Relative::Scalar(1.0))
.label_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR))
.label_font_size(self.fonts.cyri.scale(12))
.label_font_id(self.fonts.cyri.conrod_id)
.image_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR))
.mid_bottom_with_margin_on(state.ids.align_ing, -31.0)
.parent(state.ids.window_frame)
.set(state.ids.btn_craft, ui)
.was_clicked()
{
match recipe_kind {
RecipeKind::ModularWeapon => {
if let (Some(primary_slot), Some(secondary_slot)) = modular_slot_check_hack
{
events.push(Event::CraftModularWeapon {
primary_slot,
secondary_slot,
});
}
},
RecipeKind::Component(toolkind) => {
if let Some(primary_slot) = modular_slot_check_hack.0 {
events.push(Event::CraftModularWeaponComponent {
toolkind,
material: primary_slot,
modifier: modular_slot_check_hack.1,
});
}
},
RecipeKind::Simple => {
events.push(Event::CraftRecipe(recipe_name))
},
}
}
// Crafting Station Info
if recipe.craft_sprite.is_some() {
Text::new(
let mut sprite_text = 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.req_station_title, ui);
.color(TEXT_COLOR);
match recipe_kind {
RecipeKind::Simple => {
sprite_text =
sprite_text.top_left_with_margins_on(state.ids.align_ing, 10.0, 5.0);
},
RecipeKind::ModularWeapon | RecipeKind::Component(_) => {
sprite_text =
sprite_text.top_left_with_margins_on(state.ids.align_ing, 325.0, 5.0);
},
}
sprite_text.set(state.ids.req_station_title, ui);
let station_img = match recipe.craft_sprite {
Some(SpriteKind::Anvil) => "Anvil",
Some(SpriteKind::Cauldron) => "Cauldron",
@ -1414,6 +1413,52 @@ impl<'a> Widget for Crafting<'a> {
.set(state.ids.req_station_txt, ui);
}
// 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 ingredients = match recipe_kind {
RecipeKind::Simple => {
iter_a = recipe
.inputs
.iter()
.map(|(recipe, amount, _)| (recipe, *amount));
&mut iter_a as &mut dyn ExactSizeIterator<Item = (&RecipeInput, u32)>
},
RecipeKind::ModularWeapon => {
iter_b = core::iter::empty();
&mut iter_b
},
RecipeKind::Component(toolkind) => {
if let Some(material) = modular_slot_check_hack
.0
.and_then(|slot| self.inventory.get(slot))
.map(|item| String::from(item.item_definition_id()))
{
let component_key = ComponentKey {
toolkind,
material,
modifier: modular_slot_check_hack
.1
.and_then(|slot| self.inventory.get(slot))
.map(|item| String::from(item.item_definition_id())),
};
if let Some(comp_recipe) =
self.client.component_recipe_book().get(&component_key)
{
iter_c = comp_recipe.inputs();
&mut iter_c 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();
if num_ingredients > 0 {
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))
@ -1426,49 +1471,49 @@ impl<'a> Widget for Crafting<'a> {
ing_txt.set(state.ids.ingredients_txt, ui);
// Ingredient images with tooltip
if state.ids.ingredient_frame.len() < recipe.inputs().len() {
if state.ids.ingredient_frame.len() < num_ingredients {
state.update(|state| {
state
.ids
.ingredient_frame
.resize(recipe.inputs().len(), &mut ui.widget_id_generator())
.resize(num_ingredients, &mut ui.widget_id_generator())
});
};
if state.ids.ingredients.len() < recipe.inputs().len() {
if state.ids.ingredients.len() < num_ingredients {
state.update(|state| {
state
.ids
.ingredients
.resize(recipe.inputs().len(), &mut ui.widget_id_generator())
.resize(num_ingredients, &mut ui.widget_id_generator())
});
};
if state.ids.ingredient_btn.len() < recipe.inputs().len() {
if state.ids.ingredient_btn.len() < num_ingredients {
state.update(|state| {
state
.ids
.ingredient_btn
.resize(recipe.inputs().len(), &mut ui.widget_id_generator())
.resize(num_ingredients, &mut ui.widget_id_generator())
});
};
if state.ids.ingredient_img.len() < recipe.inputs().len() {
if state.ids.ingredient_img.len() < num_ingredients {
state.update(|state| {
state
.ids
.ingredient_img
.resize(recipe.inputs().len(), &mut ui.widget_id_generator())
.resize(num_ingredients, &mut ui.widget_id_generator())
});
};
if state.ids.req_text.len() < recipe.inputs().len() {
if state.ids.req_text.len() < num_ingredients {
state.update(|state| {
state
.ids
.req_text
.resize(recipe.inputs().len(), &mut ui.widget_id_generator())
.resize(num_ingredients, &mut ui.widget_id_generator())
});
};
// Widget generation for every ingredient
for (i, (recipe_input, amount, _)) in recipe.inputs.iter().enumerate() {
for (i, (recipe_input, amount)) in ingredients.enumerate() {
let item_def = match recipe_input {
RecipeInput::Item(item_def) => Arc::clone(item_def),
RecipeInput::Tag(tag) | RecipeInput::TagSameItem(tag) => {
@ -1477,7 +1522,7 @@ impl<'a> Widget for Crafting<'a> {
.slots()
.find_map(|slot| {
slot.as_ref().and_then(|item| {
if item.matches_recipe_input(recipe_input, *amount) {
if item.matches_recipe_input(recipe_input, amount) {
Some(item.item_definition_id())
} else {
None
@ -1492,7 +1537,7 @@ impl<'a> Widget for Crafting<'a> {
.slots()
.find_map(|slot| {
slot.as_ref().and_then(|item| {
if item.matches_recipe_input(recipe_input, *amount) {
if item.matches_recipe_input(recipe_input, amount) {
Some(item.item_definition_id())
} else {
None
@ -1511,7 +1556,7 @@ impl<'a> Widget for Crafting<'a> {
// Grey color for images and text if their amount is too low to craft the
// item
let item_count_in_inventory = self.inventory.item_count(&*item_def);
let col = if item_count_in_inventory >= u64::from(*amount.max(&1)) {
let col = if item_count_in_inventory >= u64::from(amount.max(1)) {
TEXT_COLOR
} else {
TEXT_DULL_RED_COLOR
@ -1526,7 +1571,7 @@ impl<'a> Widget for Crafting<'a> {
// for Catalysts/Tools"
let frame_offset = if i == 0 {
10.0
} else if *amount == 0 {
} else if amount == 0 {
5.0
} else {
0.0
@ -1542,7 +1587,7 @@ impl<'a> Widget for Crafting<'a> {
_ => self.imgs.inv_slot_red,
};
let frame = Image::new(quality_col_img).w_h(25.0, 25.0);
let frame = if *amount == 0 {
let frame = if amount == 0 {
frame.down_from(state.ids.req_text[i], 10.0 + frame_offset)
} else {
frame.down_from(frame_pos, 10.0 + frame_offset)
@ -1587,10 +1632,15 @@ impl<'a> Widget for Crafting<'a> {
let in_inv: &str = &item_count_in_inventory.to_string();
// Show Ingredients
// Align "Required" Text below last ingredient
if *amount == 0 {
if amount == 0 {
// Catalysts/Tools
let ref_widget = if i == 0 {
state.ids.ingredients_txt
} else {
state.ids.ingredient_frame[i - 1]
};
Text::new(self.localized_strings.get("hud.crafting.tool_cata"))
.down_from(state.ids.ingredient_frame[i - 1], 20.0)
.down_from(ref_widget, 20.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.color(TEXT_COLOR)