Craft all

This commit is contained in:
Julio Cezar Silva 2022-08-12 00:47:48 +00:00 committed by Samuel Keiffer
parent 2d2d6b5c64
commit 9315482fc4
13 changed files with 158 additions and 30 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Currently playing music track and artist now shows in the debug menu. - Currently playing music track and artist now shows in the debug menu.
- Added a setting to influence the gap between music track plays. - Added a setting to influence the gap between music track plays.
- Added a Craft All button.
### Changed ### Changed
- Use fluent for translations - Use fluent for translations

View File

@ -2,6 +2,7 @@ hud-crafting = Elaborar
hud-crafting-recipes = Receptes hud-crafting-recipes = Receptes
hud-crafting-ingredients = Ingredients: hud-crafting-ingredients = Ingredients:
hud-crafting-craft = Elaborar hud-crafting-craft = Elaborar
hud-crafting-craft_all = Elaborar Tot
hud-crafting-tool_cata = Requereix: hud-crafting-tool_cata = Requereix:
hud-crafting-req_crafting_station = Requereix: hud-crafting-req_crafting_station = Requereix:
hud-crafting-anvil = Enclusa hud-crafting-anvil = Enclusa

View File

@ -2,6 +2,7 @@ hud-crafting = Crafting
hud-crafting-recipes = Recipes hud-crafting-recipes = Recipes
hud-crafting-ingredients = Ingredients: hud-crafting-ingredients = Ingredients:
hud-crafting-craft = Craft hud-crafting-craft = Craft
hud-crafting-craft_all = Craft All
hud-crafting-tool_cata = Requires: hud-crafting-tool_cata = Requires:
hud-crafting-req_crafting_station = Requires: hud-crafting-req_crafting_station = Requires:
hud-crafting-anvil = Anvil hud-crafting-anvil = Anvil

View File

@ -2,6 +2,7 @@ hud-crafting = Fabricación
hud-crafting-recipes = Recetas hud-crafting-recipes = Recetas
hud-crafting-ingredients = Ingredientes: hud-crafting-ingredients = Ingredientes:
hud-crafting-craft = Fabricar hud-crafting-craft = Fabricar
hud-crafting-craft_all = Fabricar Todo
hud-crafting-tool_cata = Requisitos: hud-crafting-tool_cata = Requisitos:
hud-crafting-req_crafting_station = Requisitos: hud-crafting-req_crafting_station = Requisitos:
hud-crafting-anvil = Yunque hud-crafting-anvil = Yunque

View File

@ -2,6 +2,7 @@ hud-crafting = Fabrication
hud-crafting-recipes = Recettes hud-crafting-recipes = Recettes
hud-crafting-ingredients = Ingrédients : hud-crafting-ingredients = Ingrédients :
hud-crafting-craft = Fabriquer hud-crafting-craft = Fabriquer
hud-crafting-craft_all = Tout Fabriquer
hud-crafting-tool_cata = Nécessite : hud-crafting-tool_cata = Nécessite :
hud-crafting-req_crafting_station = Nécessite: hud-crafting-req_crafting_station = Nécessite:
hud-crafting-anvil = Enclume hud-crafting-anvil = Enclume

View File

@ -1,7 +1,8 @@
hud-crafting = Criação hud-crafting = Criação
hud-crafting-recipes = Receitas hud-crafting-recipes = Receitas
hud-crafting-ingredients = Ingredientes: hud-crafting-ingredients = Ingredientes:
hud-crafting-craft = Criar hud-crafting-craft = Forjar
hud-crafting-craft_all = Forjar Tudo
hud-crafting-tool_cata = Requer: hud-crafting-tool_cata = Requer:
hud-crafting-req_crafting_station = Requer: hud-crafting-req_crafting_station = Requer:
hud-crafting-anvil = Bigorna hud-crafting-anvil = Bigorna

View File

@ -1074,13 +1074,13 @@ impl Client {
/// Returns whether the specified recipe can be crafted and the sprite, if /// Returns whether the specified recipe can be crafted and the sprite, if
/// any, that is required to do so. /// any, that is required to do so.
pub fn can_craft_recipe(&self, recipe: &str) -> (bool, Option<SpriteKind>) { pub fn can_craft_recipe(&self, recipe: &str, amount: u32) -> (bool, Option<SpriteKind>) {
self.recipe_book self.recipe_book
.get(recipe) .get(recipe)
.zip(self.inventories().get(self.entity())) .zip(self.inventories().get(self.entity()))
.map(|(recipe, inv)| { .map(|(recipe, inv)| {
( (
recipe.inventory_contains_ingredients(inv).is_ok(), recipe.inventory_contains_ingredients(inv, amount).is_ok(),
recipe.craft_sprite, recipe.craft_sprite,
) )
}) })
@ -1092,8 +1092,9 @@ impl Client {
recipe: &str, recipe: &str,
slots: Vec<(u32, InvSlotId)>, slots: Vec<(u32, InvSlotId)>,
craft_sprite: Option<(Vec3<i32>, SpriteKind)>, craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
amount: u32,
) -> bool { ) -> bool {
let (can_craft, required_sprite) = self.can_craft_recipe(recipe); let (can_craft, required_sprite) = self.can_craft_recipe(recipe, amount);
let has_sprite = required_sprite.map_or(true, |s| Some(s) == craft_sprite.map(|(_, s)| s)); let has_sprite = required_sprite.map_or(true, |s| Some(s) == craft_sprite.map(|(_, s)| s));
if can_craft && has_sprite { if can_craft && has_sprite {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
@ -1101,6 +1102,7 @@ impl Client {
craft_event: CraftEvent::Simple { craft_event: CraftEvent::Simple {
recipe: recipe.to_string(), recipe: recipe.to_string(),
slots, slots,
amount,
}, },
craft_sprite: craft_sprite.map(|(pos, _)| pos), craft_sprite: craft_sprite.map(|(pos, _)| pos),
}, },
@ -1213,7 +1215,7 @@ impl Client {
.iter() .iter()
.map(|(name, _)| name.clone()) .map(|(name, _)| name.clone())
.filter_map(|name| { .filter_map(|name| {
let (can_craft, required_sprite) = self.can_craft_recipe(&name); let (can_craft, required_sprite) = self.can_craft_recipe(&name, 1);
if can_craft { if can_craft {
Some((name, required_sprite)) Some((name, required_sprite))
} else { } else {

View File

@ -98,6 +98,7 @@ pub enum CraftEvent {
Simple { Simple {
recipe: String, recipe: String,
slots: Vec<(u32, InvSlotId)>, slots: Vec<(u32, InvSlotId)>,
amount: u32,
}, },
Salvage(InvSlotId), Salvage(InvSlotId),
// TODO: Maybe look at making this more general when there are more modular recipes? // TODO: Maybe look at making this more general when there are more modular recipes?

View File

@ -164,13 +164,52 @@ impl Recipe {
pub fn inventory_contains_ingredients( pub fn inventory_contains_ingredients(
&self, &self,
inv: &Inventory, inv: &Inventory,
recipe_amount: u32,
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> { ) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> {
inventory_contains_ingredients( inventory_contains_ingredients(
self.inputs() self.inputs()
.map(|(input, amount, _is_modular)| (input, amount)), .map(|(input, amount, _is_modular)| (input, amount)),
inv, inv,
recipe_amount,
) )
} }
/// Calculates the maximum number of items craftable given the current
/// inventory state.
pub fn max_from_ingredients(&self, inv: &Inventory) -> u32 {
let mut max_recipes = None;
for (input, amount) in self
.inputs()
.map(|(input, amount, _is_modular)| (input, amount))
{
let needed = amount as f32;
let mut input_max = HashMap::<InvSlotId, u32>::new();
// Checks through every slot, filtering to only those that contain items that
// can satisfy the input.
for (inv_slot_id, slot) in inv.slots_with_id() {
if let Some(item) = slot
.as_ref()
.filter(|item| item.matches_recipe_input(&*input, amount))
{
*input_max.entry(inv_slot_id).or_insert(0) += item.amount();
}
}
// Updates maximum craftable amount based on least recipe-proportional
// availability.
let max_item_proportion =
((input_max.values().sum::<u32>() as f32) / needed).floor() as u32;
max_recipes = Some(match max_recipes {
None => max_item_proportion,
Some(max_recipes) if (max_item_proportion < max_recipes) => max_item_proportion,
Some(n) => n,
});
}
max_recipes.unwrap_or(0)
}
} }
/// Determine whether the inventory contains the ingredients for a recipe. /// Determine whether the inventory contains the ingredients for a recipe.
@ -183,6 +222,7 @@ impl Recipe {
fn inventory_contains_ingredients<'a, I: Iterator<Item = (&'a RecipeInput, u32)>>( fn inventory_contains_ingredients<'a, I: Iterator<Item = (&'a RecipeInput, u32)>>(
ingredients: I, ingredients: I,
inv: &Inventory, inv: &Inventory,
recipe_amount: u32,
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&'a RecipeInput, u32)>> { ) -> Result<Vec<(u32, InvSlotId)>, Vec<(&'a RecipeInput, u32)>> {
// Hashmap tracking the quantity that needs to be removed from each slot (so // Hashmap tracking the quantity that needs to be removed from each slot (so
// that it doesn't think a slot can provide more items than it contains) // that it doesn't think a slot can provide more items than it contains)
@ -194,7 +234,7 @@ fn inventory_contains_ingredients<'a, I: Iterator<Item = (&'a RecipeInput, u32)>
let mut missing = Vec::<(&RecipeInput, u32)>::new(); let mut missing = Vec::<(&RecipeInput, u32)>::new();
for (i, (input, amount)) in ingredients.enumerate() { for (i, (input, amount)) in ingredients.enumerate() {
let mut needed = amount; let mut needed = amount * recipe_amount;
let mut contains_any = false; let mut contains_any = false;
// Checks through every slot, filtering to only those that contain items that // Checks through every slot, filtering to only those that contain items that
// can satisfy the input // can satisfy the input
@ -358,7 +398,7 @@ impl RecipeBook {
pub fn get_available(&self, inv: &Inventory) -> Vec<(String, Recipe)> { pub fn get_available(&self, inv: &Inventory) -> Vec<(String, Recipe)> {
self.recipes self.recipes
.iter() .iter()
.filter(|(_, recipe)| recipe.inventory_contains_ingredients(inv).is_ok()) .filter(|(_, recipe)| recipe.inventory_contains_ingredients(inv, 1).is_ok())
.map(|(name, recipe)| (name.clone(), recipe.clone())) .map(|(name, recipe)| (name.clone(), recipe.clone()))
.collect() .collect()
} }
@ -654,6 +694,7 @@ impl ComponentRecipe {
.iter() .iter()
.map(|(input, amount)| (input, *amount)), .map(|(input, amount)| (input, *amount)),
inv, inv,
1,
) )
} }

View File

@ -672,7 +672,11 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
}; };
let crafted_items = match craft_event { let crafted_items = match craft_event {
CraftEvent::Simple { recipe, slots } => recipe_book CraftEvent::Simple {
recipe,
slots,
amount,
} => recipe_book
.get(&recipe) .get(&recipe)
.filter(|r| { .filter(|r| {
if let Some(needed_sprite) = r.craft_sprite { if let Some(needed_sprite) = r.craft_sprite {
@ -683,13 +687,21 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
} }
}) })
.and_then(|r| { .and_then(|r| {
let items = (0..amount)
.into_iter()
.filter_map(|_| {
r.craft_simple( r.craft_simple(
&mut inventory, &mut inventory,
slots, slots.clone(),
&state.ecs().read_resource::<AbilityMap>(), &state.ecs().read_resource::<AbilityMap>(),
&state.ecs().read_resource::<MaterialStatManifest>(), &state.ecs().read_resource::<MaterialStatManifest>(),
) )
.ok() .ok()
})
.flatten()
.collect::<Vec<_>>();
if items.is_empty() { None } else { Some(items) }
}), }),
CraftEvent::Salvage(slot) => { CraftEvent::Salvage(slot) => {
let sprite = get_craft_sprite(state, craft_sprite); let sprite = get_craft_sprite(state, craft_sprite);

View File

@ -38,7 +38,7 @@ use hashbrown::HashMap;
use i18n::Localization; use i18n::Localization;
use std::{borrow::Cow, collections::BTreeMap, sync::Arc}; use std::{borrow::Cow, collections::BTreeMap, sync::Arc};
use strum::{EnumIter, IntoEnumIterator}; use strum::{EnumIter, IntoEnumIterator};
use tracing::warn; use tracing::{error, warn};
use vek::*; use vek::*;
widget_ids! { widget_ids! {
@ -61,6 +61,7 @@ widget_ids! {
align_ing, align_ing,
scrollbar_ing, scrollbar_ing,
btn_craft, btn_craft,
btn_craft_all,
recipe_list_btns[], recipe_list_btns[],
recipe_list_labels[], recipe_list_labels[],
recipe_list_quality_indicators[], recipe_list_quality_indicators[],
@ -96,7 +97,10 @@ widget_ids! {
} }
pub enum Event { pub enum Event {
CraftRecipe(String), CraftRecipe {
recipe_name: String,
amount: u32,
},
CraftModularWeapon { CraftModularWeapon {
primary_slot: InvSlotId, primary_slot: InvSlotId,
secondary_slot: InvSlotId, secondary_slot: InvSlotId,
@ -1355,7 +1359,7 @@ impl<'a> Widget for Crafting<'a> {
.label_font_size(self.fonts.cyri.scale(12)) .label_font_size(self.fonts.cyri.scale(12))
.label_font_id(self.fonts.cyri.conrod_id) .label_font_id(self.fonts.cyri.conrod_id)
.image_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR)) .image_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR))
.mid_bottom_with_margin_on(state.ids.align_ing, -31.0) .bottom_left_with_margins_on(state.ids.align_ing, -31.0, 15.0)
.parent(state.ids.window_frame) .parent(state.ids.window_frame)
.set(state.ids.btn_craft, ui) .set(state.ids.btn_craft, ui)
.was_clicked() .was_clicked()
@ -1381,10 +1385,62 @@ impl<'a> Widget for Crafting<'a> {
}); });
} }
}, },
RecipeKind::Simple => events.push(Event::CraftRecipe(recipe_name)), RecipeKind::Simple => events.push(Event::CraftRecipe {
recipe_name,
amount: 1,
}),
} }
} }
// Craft All button
let can_perform_all = can_perform && matches!(recipe_kind, RecipeKind::Simple);
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_all"))
.label_y(conrod_core::position::Relative::Scalar(1.0))
.label_color(
can_perform_all
.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_all
.then_some(TEXT_COLOR)
.unwrap_or(TEXT_GRAY_COLOR),
)
.bottom_right_with_margins_on(state.ids.align_ing, -31.0, 15.0)
.parent(state.ids.window_frame)
.set(state.ids.btn_craft_all, ui)
.was_clicked()
&& can_perform_all
{
if let (RecipeKind::Simple, Some(selected_recipe)) =
(recipe_kind, &state.selected_recipe)
{
let amount = recipe.max_from_ingredients(self.inventory);
if amount > 0 {
events.push(Event::CraftRecipe {
recipe_name: selected_recipe.to_string(),
amount,
});
}
} else {
error!("State shows no selected recipe when trying to craft multiple.");
}
};
// Crafting Station Info // Crafting Station Info
if recipe.craft_sprite.is_some() { if recipe.craft_sprite.is_some() {
Text::new( Text::new(

View File

@ -538,8 +538,9 @@ pub enum Event {
Quit, Quit,
CraftRecipe { CraftRecipe {
recipe: String, recipe_name: String,
craft_sprite: Option<(Vec3<i32>, SpriteKind)>, craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
amount: u32,
}, },
SalvageItem { SalvageItem {
slot: InvSlotId, slot: InvSlotId,
@ -2912,10 +2913,14 @@ impl Hud {
.set(self.ids.crafting_window, ui_widgets) .set(self.ids.crafting_window, ui_widgets)
{ {
match event { match event {
crafting::Event::CraftRecipe(recipe) => { crafting::Event::CraftRecipe {
recipe_name,
amount,
} => {
events.push(Event::CraftRecipe { events.push(Event::CraftRecipe {
recipe, recipe_name,
craft_sprite: self.show.crafting_fields.craft_sprite, craft_sprite: self.show.crafting_fields.craft_sprite,
amount,
}); });
}, },
crafting::Event::CraftModularWeapon { crafting::Event::CraftModularWeapon {

View File

@ -1541,26 +1541,30 @@ impl PlayState for SessionState {
}, },
HudEvent::CraftRecipe { HudEvent::CraftRecipe {
recipe, recipe_name: recipe,
craft_sprite, craft_sprite,
amount,
} => { } => {
let slots = { let slots = {
let client = self.client.borrow(); let client = self.client.borrow();
if let Some(recipe) = client.recipe_book().get(&recipe) { if let Some(recipe) = client.recipe_book().get(&recipe) {
client client.inventories().get(client.entity()).and_then(|inv| {
.inventories() recipe.inventory_contains_ingredients(inv, 1).ok()
.get(client.entity()) })
.and_then(|inv| recipe.inventory_contains_ingredients(inv).ok())
} else { } else {
None None
} }
}; };
if let Some(slots) = slots { if let Some(slots) = slots {
self.client self.client.borrow_mut().craft_recipe(
.borrow_mut() &recipe,
.craft_recipe(&recipe, slots, craft_sprite); slots,
craft_sprite,
amount,
);
} }
}, },
HudEvent::CraftModularWeapon { HudEvent::CraftModularWeapon {
primary_slot, primary_slot,
secondary_slot, secondary_slot,
@ -1572,6 +1576,7 @@ impl PlayState for SessionState {
craft_sprite, craft_sprite,
); );
}, },
HudEvent::CraftModularWeaponComponent { HudEvent::CraftModularWeaponComponent {
toolkind, toolkind,
material, material,