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
- 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 Craft All button.
### Changed
- Use fluent for translations

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,8 @@
hud-crafting = Criação
hud-crafting-recipes = Receitas
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-req_crafting_station = Requer:
hud-crafting-anvil = Bigorna

View File

@ -1074,13 +1074,13 @@ impl Client {
/// 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<SpriteKind>) {
pub fn can_craft_recipe(&self, recipe: &str, amount: u32) -> (bool, Option<SpriteKind>) {
self.recipe_book
.get(recipe)
.zip(self.inventories().get(self.entity()))
.map(|(recipe, inv)| {
(
recipe.inventory_contains_ingredients(inv).is_ok(),
recipe.inventory_contains_ingredients(inv, amount).is_ok(),
recipe.craft_sprite,
)
})
@ -1092,8 +1092,9 @@ impl Client {
recipe: &str,
slots: Vec<(u32, InvSlotId)>,
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
amount: u32,
) -> 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));
if can_craft && has_sprite {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
@ -1101,6 +1102,7 @@ impl Client {
craft_event: CraftEvent::Simple {
recipe: recipe.to_string(),
slots,
amount,
},
craft_sprite: craft_sprite.map(|(pos, _)| pos),
},
@ -1213,7 +1215,7 @@ impl Client {
.iter()
.map(|(name, _)| name.clone())
.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 {
Some((name, required_sprite))
} else {

View File

@ -98,6 +98,7 @@ pub enum CraftEvent {
Simple {
recipe: String,
slots: Vec<(u32, InvSlotId)>,
amount: u32,
},
Salvage(InvSlotId),
// 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(
&self,
inv: &Inventory,
recipe_amount: u32,
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> {
inventory_contains_ingredients(
self.inputs()
.map(|(input, amount, _is_modular)| (input, amount)),
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.
@ -183,6 +222,7 @@ impl Recipe {
fn inventory_contains_ingredients<'a, I: Iterator<Item = (&'a RecipeInput, u32)>>(
ingredients: I,
inv: &Inventory,
recipe_amount: u32,
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&'a RecipeInput, u32)>> {
// 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)
@ -194,7 +234,7 @@ fn inventory_contains_ingredients<'a, I: Iterator<Item = (&'a RecipeInput, u32)>
let mut missing = Vec::<(&RecipeInput, u32)>::new();
for (i, (input, amount)) in ingredients.enumerate() {
let mut needed = amount;
let mut needed = amount * recipe_amount;
let mut contains_any = false;
// Checks through every slot, filtering to only those that contain items that
// can satisfy the input
@ -358,7 +398,7 @@ impl RecipeBook {
pub fn get_available(&self, inv: &Inventory) -> Vec<(String, Recipe)> {
self.recipes
.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()))
.collect()
}
@ -654,6 +694,7 @@ impl ComponentRecipe {
.iter()
.map(|(input, amount)| (input, *amount)),
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 {
CraftEvent::Simple { recipe, slots } => recipe_book
CraftEvent::Simple {
recipe,
slots,
amount,
} => recipe_book
.get(&recipe)
.filter(|r| {
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| {
let items = (0..amount)
.into_iter()
.filter_map(|_| {
r.craft_simple(
&mut inventory,
slots,
slots.clone(),
&state.ecs().read_resource::<AbilityMap>(),
&state.ecs().read_resource::<MaterialStatManifest>(),
)
.ok()
})
.flatten()
.collect::<Vec<_>>();
if items.is_empty() { None } else { Some(items) }
}),
CraftEvent::Salvage(slot) => {
let sprite = get_craft_sprite(state, craft_sprite);

View File

@ -38,7 +38,7 @@ use hashbrown::HashMap;
use i18n::Localization;
use std::{borrow::Cow, collections::BTreeMap, sync::Arc};
use strum::{EnumIter, IntoEnumIterator};
use tracing::warn;
use tracing::{error, warn};
use vek::*;
widget_ids! {
@ -61,6 +61,7 @@ widget_ids! {
align_ing,
scrollbar_ing,
btn_craft,
btn_craft_all,
recipe_list_btns[],
recipe_list_labels[],
recipe_list_quality_indicators[],
@ -96,7 +97,10 @@ widget_ids! {
}
pub enum Event {
CraftRecipe(String),
CraftRecipe {
recipe_name: String,
amount: u32,
},
CraftModularWeapon {
primary_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_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)
.bottom_left_with_margins_on(state.ids.align_ing, -31.0, 15.0)
.parent(state.ids.window_frame)
.set(state.ids.btn_craft, ui)
.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
if recipe.craft_sprite.is_some() {
Text::new(

View File

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

View File

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