Initial crafting UI for modular weapons.

This commit is contained in:
Sam 2021-12-11 15:28:37 -05:00
parent bf348b7f43
commit 8fc0138e84
6 changed files with 756 additions and 462 deletions

View File

@ -1081,7 +1081,7 @@ impl Client {
&mut self,
slot_a: InvSlotId,
slot_b: InvSlotId,
sprite_pos: Vec3<i32>,
sprite_pos: Option<Vec3<i32>>,
) -> bool {
let inventories = self.inventories();
let inventory = inventories.get(self.entity());
@ -1122,7 +1122,7 @@ impl Client {
primary_component,
secondary_component,
},
craft_sprite: Some(sprite_pos),
craft_sprite: sprite_pos,
},
)));
true

View File

@ -760,7 +760,7 @@ impl<'a> Widget for Bag<'a> {
true,
inventory,
&state.bg_ids,
self.show.salvage,
self.show.crafting_fields.salvage,
)
.set(state.ids.inventory_scroller, ui);

View File

@ -2,20 +2,24 @@ use super::{
get_quality_col,
img_ids::{Imgs, ImgsRot},
item_imgs::{animate_by_pulse, ItemImgs},
slots::{CraftSlot, SlotManager},
Show, TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
};
use crate::ui::{
fonts::Fonts, ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip,
TooltipManager, Tooltipable,
fonts::Fonts,
slot::{ContentSize, SlotMaker},
ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip, TooltipManager,
Tooltipable,
};
use client::{self, Client};
use common::{
assets::AssetExt,
comp::{
comp::inventory::{
item::{
item_key::ItemKey, ItemDef, ItemDesc, ItemKind, ItemTag, MaterialStatManifest, Quality,
TagExampleInfo,
item_key::ItemKey, modular::ModularComponent, ItemDef, ItemDesc, ItemKind, ItemTag,
MaterialStatManifest, Quality, TagExampleInfo,
},
slot::InvSlotId,
Inventory,
},
recipe::{Recipe, RecipeInput},
@ -27,10 +31,12 @@ use conrod_core::{
widget::{self, Button, Image, Rectangle, Scrollbar, Text, TextEdit},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
};
use hashbrown::HashMap;
use i18n::Localization;
use std::sync::Arc;
use std::{borrow::Cow, sync::Arc};
use strum::{EnumIter, IntoEnumIterator};
use vek::*;
widget_ids! {
pub struct Ids {
@ -77,17 +83,43 @@ widget_ids! {
dismantle_img,
dismantle_txt,
dismantle_highlight_txt,
modular_inputs[],
}
}
pub enum Event {
CraftRecipe(String),
CraftModularWeapon {
primary_slot: InvSlotId,
secondary_slot: InvSlotId,
},
ChangeCraftingTab(CraftingTab),
Close,
Focus(widget::Id),
SearchRecipe(Option<String>),
}
pub struct CraftingShow {
pub crafting_tab: CraftingTab,
pub crafting_search_key: Option<String>,
pub craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
pub salvage: bool,
// TODO: Maybe try to do something that doesn't need to allocate?
pub recipe_inputs: HashMap<u32, InvSlotId>,
}
impl Default for CraftingShow {
fn default() -> Self {
Self {
crafting_tab: CraftingTab::All,
crafting_search_key: None,
craft_sprite: None,
salvage: false,
recipe_inputs: HashMap::new(),
}
}
}
#[derive(WidgetCommon)]
pub struct Crafting<'a> {
client: &'a Client,
@ -97,6 +129,7 @@ pub struct Crafting<'a> {
pulse: f32,
rot_imgs: &'a ImgsRot,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut SlotManager,
item_imgs: &'a ItemImgs,
inventory: &'a Inventory,
msm: &'a MaterialStatManifest,
@ -115,6 +148,7 @@ impl<'a> Crafting<'a> {
pulse: f32,
rot_imgs: &'a ImgsRot,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut SlotManager,
item_imgs: &'a ItemImgs,
inventory: &'a Inventory,
msm: &'a MaterialStatManifest,
@ -129,6 +163,7 @@ impl<'a> Crafting<'a> {
pulse,
rot_imgs,
item_tooltip_manager,
slot_manager,
tooltip_manager,
item_imgs,
inventory,
@ -372,7 +407,7 @@ impl<'a> Widget for Crafting<'a> {
)
})
};
let sel_crafting_tab = &self.show.crafting_tab;
let sel_crafting_tab = &self.show.crafting_fields.crafting_tab;
for (i, crafting_tab) in CraftingTab::iter().enumerate() {
if crafting_tab != CraftingTab::Dismantle {
let tab_img = crafting_tab.img_id(self.imgs);
@ -429,7 +464,7 @@ impl<'a> Widget for Crafting<'a> {
// more filters gets added
let mut _lower_case_search = String::new();
let (search_filter, search_keys) = {
if let Some(key) = &self.show.crafting_search_key {
if let Some(key) = &self.show.crafting_fields.crafting_search_key {
_lower_case_search = key.as_str().to_lowercase();
_lower_case_search
.split_once(':')
@ -448,6 +483,21 @@ impl<'a> Widget for Crafting<'a> {
}
};
let (modular_entry_name, modular_entry_recipe) = {
let recipe = Recipe {
output: (
Arc::<ItemDef>::load_expect_cloned("common.items.weapons.empty.empty"),
0,
),
inputs: Vec::new(),
craft_sprite: Some(SpriteKind::CraftingBench),
};
(
String::from("veloren.core.pseudo_recipe.modular_weapon"),
recipe,
)
};
// First available recipes, then ones with available materials,
// then unavailable ones, each sorted by quality and then alphabetically
// In the tuple, "name" is the recipe book key, and "recipe.output.0.name()"
@ -490,11 +540,22 @@ impl<'a> Widget for Crafting<'a> {
.get(name.as_str())
.map_or(false, |cs| {
cs.map_or(true, |cs| {
Some(cs) == self.show.craft_sprite.map(|(_, s)| s)
Some(cs) == self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
})
});
(name, recipe, is_craftable, has_materials)
})
.chain(
matches!(sel_crafting_tab, CraftingTab::Weapon | CraftingTab::All)
.then_some((
&modular_entry_name,
&modular_entry_recipe,
self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
== modular_entry_recipe.craft_sprite,
true,
))
.into_iter(),
)
.collect();
ordered_recipes.sort_by_key(|(_, recipe, is_craftable, has_materials)| {
(
@ -506,42 +567,44 @@ impl<'a> Widget for Crafting<'a> {
});
// Recipe list
if state.ids.recipe_list_btns.len() < self.client.recipe_book().iter().len() {
// 1 added for modular weapon fake recipe entry
// TODO: Some better automated way of doing this
let recipe_list_length = self.client.recipe_book().iter().len() + 1;
if state.ids.recipe_list_btns.len() < recipe_list_length {
state.update(|state| {
state.ids.recipe_list_btns.resize(
self.client.recipe_book().iter().len(),
&mut ui.widget_id_generator(),
)
state
.ids
.recipe_list_btns
.resize(recipe_list_length, &mut ui.widget_id_generator())
});
}
if state.ids.recipe_list_labels.len() < self.client.recipe_book().iter().len() {
if state.ids.recipe_list_labels.len() < recipe_list_length {
state.update(|state| {
state.ids.recipe_list_labels.resize(
self.client.recipe_book().iter().len(),
&mut ui.widget_id_generator(),
)
state
.ids
.recipe_list_labels
.resize(recipe_list_length, &mut ui.widget_id_generator())
});
}
if state.ids.recipe_list_quality_indicators.len() < self.client.recipe_book().iter().len() {
if state.ids.recipe_list_quality_indicators.len() < recipe_list_length {
state.update(|state| {
state.ids.recipe_list_quality_indicators.resize(
self.client.recipe_book().iter().len(),
&mut ui.widget_id_generator(),
)
state
.ids
.recipe_list_quality_indicators
.resize(recipe_list_length, &mut ui.widget_id_generator())
});
}
if state.ids.recipe_list_materials_indicators.len() < self.client.recipe_book().iter().len()
{
if state.ids.recipe_list_materials_indicators.len() < recipe_list_length {
state.update(|state| {
state.ids.recipe_list_materials_indicators.resize(
self.client.recipe_book().iter().len(),
&mut ui.widget_id_generator(),
)
state
.ids
.recipe_list_materials_indicators
.resize(recipe_list_length, &mut ui.widget_id_generator())
});
}
for (i, (name, recipe, is_craftable, has_materials)) in ordered_recipes
.into_iter()
.filter(|(_, recipe, _, _)| self.show.crafting_tab.satisfies(recipe))
.filter(|(_, recipe, _, _)| self.show.crafting_fields.crafting_tab.satisfies(recipe))
.enumerate()
{
let button = Button::image(if state.selected_recipe.as_ref() == Some(name) {
@ -561,7 +624,10 @@ 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 = recipe.output.0.name();
let recipe_name = match name.as_str() {
"veloren.core.pseudo_recipe.modular_weapon" => Cow::Borrowed("Modular Weapon"),
_ => recipe.output.0.name(),
};
let text = Text::new(&recipe_name)
.color(if is_craftable {
@ -590,7 +656,10 @@ impl<'a> Widget for Crafting<'a> {
if state.selected_recipe.as_ref() == Some(name) {
state.update(|s| s.selected_recipe = None);
} else {
if matches!(self.show.crafting_tab, CraftingTab::Dismantle) {
if matches!(
self.show.crafting_fields.crafting_tab,
CraftingTab::Dismantle
) {
// If current tab is dismantle, and recipe is selected, change to general
// tab, as in dismantle tab recipe gets deselected
events.push(Event::ChangeCraftingTab(CraftingTab::All));
@ -655,31 +724,153 @@ impl<'a> Widget for Crafting<'a> {
// Deselect recipe if current tab is dismantle, elsewhere if recipe selected
// while dismantling, tab is changed to general
if matches!(self.show.crafting_tab, CraftingTab::Dismantle) {
if matches!(
self.show.crafting_fields.crafting_tab,
CraftingTab::Dismantle
) {
state.update(|s| s.selected_recipe = None);
}
// Selected Recipe
if let Some((recipe_name, recipe)) = state
.selected_recipe
.as_ref()
.and_then(|rn| self.client.recipe_book().get(rn.as_str()).map(|r| (rn, r)))
{
if let Some((recipe_name, recipe)) = match state.selected_recipe.as_deref() {
Some("veloren.core.pseudo_recipe.modular_weapon") => {
Some((modular_entry_name.as_str(), &modular_entry_recipe))
},
sel_recipe => {
sel_recipe.and_then(|rn| self.client.recipe_book().get(rn).map(|r| (rn, r)))
},
} {
let title = match recipe_name {
"veloren.core.pseudo_recipe.modular_weapon" => Cow::Borrowed("Modular Weapon"),
_ => recipe.output.0.name(),
};
// Title
Text::new(&recipe.output.0.name())
Text::new(&title)
.mid_top_with_margin_on(state.ids.align_ing, -22.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.title_ing, ui);
let can_perform = self
.client
match recipe_name {
"veloren.core.pseudo_recipe.modular_weapon" => {
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 {
state.update(|s| {
s.ids
.modular_inputs
.resize(2, &mut ui.widget_id_generator());
});
}
let primary_slot = CraftSlot {
index: 0,
invslot: self.show.crafting_fields.recipe_inputs.get(&0).copied(),
requirement: |inv, slot| {
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
matches!(
&*item.kind(),
ItemKind::ModularComponent(
ModularComponent::ToolPrimaryComponent { .. }
)
)
})
},
required_amount: 1,
};
slot_maker
.fabricate(primary_slot, [40.0; 2])
.top_left_with_margins_on(state.ids.align_ing, 20.0, 20.0)
.set(state.ids.modular_inputs[0], ui);
let secondary_slot = CraftSlot {
index: 1,
invslot: self.show.crafting_fields.recipe_inputs.get(&1).copied(),
requirement: |inv, slot| {
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
matches!(
&*item.kind(),
ItemKind::ModularComponent(
ModularComponent::ToolSecondaryComponent { .. }
)
)
})
},
required_amount: 1,
};
slot_maker
.fabricate(secondary_slot, [40.0; 2])
.top_left_with_margins_on(state.ids.align_ing, 80.0, 20.0)
.set(state.ids.modular_inputs[1], ui);
let can_perform =
primary_slot.invslot.is_some() && 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()
{
if let (Some(primary_slot), Some(secondary_slot)) =
(primary_slot.invslot, secondary_slot.invslot)
{
events.push(Event::CraftModularWeapon {
primary_slot,
secondary_slot,
});
}
}
},
_ => {
let can_perform =
self.client
.available_recipes()
.get(recipe_name.as_str())
.get(recipe_name)
.map_or(false, |cs| {
cs.map_or(true, |cs| {
Some(cs) == self.show.craft_sprite.map(|(_, s)| s)
Some(cs)
== self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
})
});
@ -707,13 +898,13 @@ impl<'a> Widget for Crafting<'a> {
.set(state.ids.btn_craft, ui)
.was_clicked()
{
events.push(Event::CraftRecipe(recipe_name.clone()));
events.push(Event::CraftRecipe(String::from(recipe_name)));
}
// Output Image Frame
let quality_col_img = match recipe.output.0.quality() {
Quality::Low => self.imgs.inv_slot_grey,
Quality::Common => self.imgs.inv_slot_common,
Quality::Common => self.imgs.inv_slot,
Quality::Moderate => self.imgs.inv_slot_green,
Quality::High => self.imgs.inv_slot_blue,
Quality::Epic => self.imgs.inv_slot_purple,
@ -766,7 +957,9 @@ impl<'a> Widget for Crafting<'a> {
CraftingTab::All => false,
_ => crafting_tab.satisfies(recipe),
})
.filter(|crafting_tab| crafting_tab != &self.show.crafting_tab)
.filter(|crafting_tab| {
crafting_tab != &self.show.crafting_fields.crafting_tab
})
.collect::<Vec<_>>()
.chunks(3)
.enumerate()
@ -847,7 +1040,9 @@ impl<'a> Widget for Crafting<'a> {
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.color(
if self.show.craft_sprite.map(|(_, s)| s) == recipe.craft_sprite {
if self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
== recipe.craft_sprite
{
TEXT_COLOR
} else {
TEXT_DULL_RED_COLOR
@ -856,7 +1051,8 @@ impl<'a> Widget for Crafting<'a> {
.set(state.ids.req_station_txt, ui);
}
// Ingredients Text
let mut ing_txt = Text::new(self.localized_strings.get("hud.crafting.ingredients"))
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))
.color(TEXT_COLOR);
@ -919,7 +1115,8 @@ 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
@ -929,12 +1126,14 @@ impl<'a> Widget for Crafting<'a> {
.unwrap_or_else(|| tag.exemplar_identifier()),
)
},
RecipeInput::ListSameItem(item_defs) => Arc::<ItemDef>::load_expect_cloned(
RecipeInput::ListSameItem(item_defs) => {
Arc::<ItemDef>::load_expect_cloned(
self.inventory
.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
@ -947,10 +1146,12 @@ impl<'a> Widget for Crafting<'a> {
.map(|i| i.item_definition_id())
.unwrap_or("common.items.weapons.empty.empty")
}),
),
)
},
};
// Grey color for images and text if their amount is too low to craft the item
// 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)) {
TEXT_COLOR
@ -963,8 +1164,8 @@ impl<'a> Widget for Crafting<'a> {
} else {
state.ids.ingredient_frame[i - 1]
};
// add a larger offset for the the first ingredient and the "Required Text for
// Catalysts/Tools"
// add a larger offset for the the first ingredient and the "Required Text
// for Catalysts/Tools"
let frame_offset = if i == 0 {
10.0
} else if *amount == 0 {
@ -974,7 +1175,7 @@ impl<'a> Widget for Crafting<'a> {
};
let quality_col_img = match &item_def.quality() {
Quality::Low => self.imgs.inv_slot_grey,
Quality::Common => self.imgs.inv_slot_common,
Quality::Common => self.imgs.inv_slot,
Quality::Moderate => self.imgs.inv_slot_green,
Quality::High => self.imgs.inv_slot_blue,
Quality::Epic => self.imgs.inv_slot_purple,
@ -1077,6 +1278,8 @@ impl<'a> Widget for Crafting<'a> {
.set(state.ids.ingredients[i], ui);
}
}
},
}
} else if *sel_crafting_tab == CraftingTab::Dismantle {
// Title
Text::new(self.localized_strings.get("hud.crafting.dismantle_title"))
@ -1115,7 +1318,7 @@ impl<'a> Widget for Crafting<'a> {
}
// Search / Title Recipes
if let Some(key) = &self.show.crafting_search_key {
if let Some(key) = &self.show.crafting_fields.crafting_search_key {
if Button::image(self.imgs.close_btn)
.top_left_with_margins_on(state.ids.align_rec, -20.0, 5.0)
.w_h(14.0, 14.0)

View File

@ -533,6 +533,11 @@ pub enum Event {
slot: InvSlotId,
salvage_pos: Vec3<i32>,
},
CraftModularWeapon {
primary_slot: InvSlotId,
secondary_slot: InvSlotId,
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
},
InviteMember(Uid),
AcceptInvite,
DeclineInvite,
@ -692,9 +697,7 @@ pub struct Show {
chat_tab_settings_index: Option<usize>,
settings_tab: SettingsTab,
diary_fields: diary::DiaryShow,
crafting_tab: CraftingTab,
crafting_search_key: Option<String>,
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
crafting_fields: crafting::CraftingShow,
social_search_key: Option<String>,
want_grab: bool,
stats: bool,
@ -703,7 +706,6 @@ pub struct Show {
camera_clamp: bool,
prompt_dialog: Option<PromptDialogSettings>,
location_markers: MapMarkers,
salvage: bool,
trade_amount_input_key: Option<TradeAmountInput>,
}
impl Show {
@ -712,7 +714,7 @@ impl Show {
self.bag = open;
self.map = false;
self.want_grab = !open;
self.salvage = false;
self.crafting_fields.salvage = false;
if !open {
self.crafting = false;
@ -734,7 +736,7 @@ impl Show {
self.map = open;
self.bag = false;
self.crafting = false;
self.salvage = false;
self.crafting_fields.salvage = false;
self.social = false;
self.diary = false;
self.want_grab = !open;
@ -760,7 +762,7 @@ impl Show {
self.search_crafting_recipe(None);
}
self.crafting = open;
self.salvage = false;
self.crafting_fields.salvage = false;
self.bag = open;
self.map = false;
self.want_grab = !open;
@ -774,16 +776,18 @@ impl Show {
) {
self.selected_crafting_tab(tab);
self.crafting(true);
self.craft_sprite = self.craft_sprite.or(craft_sprite);
self.salvage = matches!(self.craft_sprite, Some((_, SpriteKind::DismantlingBench)))
&& matches!(tab, CraftingTab::Dismantle);
self.crafting_fields.craft_sprite = self.crafting_fields.craft_sprite.or(craft_sprite);
self.crafting_fields.salvage = matches!(
self.crafting_fields.craft_sprite,
Some((_, SpriteKind::DismantlingBench))
) && matches!(tab, CraftingTab::Dismantle);
}
fn diary(&mut self, open: bool) {
if !self.esc_menu {
self.social = false;
self.crafting = false;
self.salvage = false;
self.crafting_fields.salvage = false;
self.bag = false;
self.map = false;
self.diary_fields = diary::DiaryShow::default();
@ -802,7 +806,7 @@ impl Show {
self.bag = false;
self.social = false;
self.crafting = false;
self.salvage = false;
self.crafting_fields.salvage = false;
self.diary = false;
self.want_grab = !open;
}
@ -894,10 +898,12 @@ impl Show {
self.social = false;
}
fn selected_crafting_tab(&mut self, sel_cat: CraftingTab) { self.crafting_tab = sel_cat; }
fn selected_crafting_tab(&mut self, sel_cat: CraftingTab) {
self.crafting_fields.crafting_tab = sel_cat;
}
fn search_crafting_recipe(&mut self, search_key: Option<String>) {
self.crafting_search_key = search_key;
self.crafting_fields.crafting_search_key = search_key;
}
fn search_social_players(&mut self, search_key: Option<String>) {
@ -1106,9 +1112,7 @@ impl Hud {
chat_tab_settings_index: None,
settings_tab: SettingsTab::Interface,
diary_fields: diary::DiaryShow::default(),
crafting_tab: CraftingTab::All,
crafting_search_key: None,
craft_sprite: None,
crafting_fields: crafting::CraftingShow::default(),
social_search_key: None,
want_grab: true,
ingame: true,
@ -1118,7 +1122,6 @@ impl Hud {
camera_clamp: false,
prompt_dialog: None,
location_markers: MapMarkers::default(),
salvage: false,
trade_amount_input_key: None,
},
to_focus: None,
@ -2930,6 +2933,7 @@ impl Hud {
self.pulse,
&self.rot_imgs,
item_tooltip_manager,
&mut self.slot_manager,
&self.item_imgs,
inventory,
&msm,
@ -2942,7 +2946,17 @@ impl Hud {
crafting::Event::CraftRecipe(recipe) => {
events.push(Event::CraftRecipe {
recipe,
craft_sprite: self.show.craft_sprite,
craft_sprite: self.show.crafting_fields.craft_sprite,
});
},
crafting::Event::CraftModularWeapon {
primary_slot,
secondary_slot,
} => {
events.push(Event::CraftModularWeapon {
primary_slot,
secondary_slot,
craft_sprite: self.show.crafting_fields.craft_sprite,
});
},
crafting::Event::Close => {
@ -3352,6 +3366,7 @@ impl Hud {
Hotbar(_) => None,
Trade(_) => None,
Ability(_) => None,
Crafting(_) => None,
};
match event {
slot::Event::Dragged(a, b) => {
@ -3428,6 +3443,17 @@ impl Hud {
},
(AbilitySlot::Ability(_), AbilitySlot::Ability(_)) => {},
}
} else if let (Inventory(i), Crafting(c)) = (a, b) {
// Add item to crafting input
if (c.requirement)(inventories.get(client.entity()), i.slot) {
self.show
.crafting_fields
.recipe_inputs
.insert(c.index, i.slot);
}
} else if let (Crafting(c), Inventory(_)) = (a, b) {
// Remove item from crafting input
self.show.crafting_fields.recipe_inputs.remove(&c.index);
}
},
slot::Event::Dropped(from) => {
@ -3535,11 +3561,14 @@ impl Hud {
slot::Event::Used(from) => {
// Item used (selected and then clicked again)
if let Some(from) = to_slot(from) {
if self.show.salvage
&& matches!(self.show.crafting_tab, CraftingTab::Dismantle)
if self.show.crafting_fields.salvage
&& matches!(
self.show.crafting_fields.crafting_tab,
CraftingTab::Dismantle
)
{
if let (Slot::Inventory(slot), Some((salvage_pos, _sprite_kind))) =
(from, self.show.craft_sprite)
(from, self.show.crafting_fields.craft_sprite)
{
events.push(Event::SalvageItem { slot, salvage_pos })
}
@ -3575,6 +3604,9 @@ impl Hud {
});
} else if let Ability(AbilitySlot::Slot(index)) = from {
events.push(Event::ChangeAbility(index, AuxiliaryAbility::Empty));
} else if let Crafting(c) = from {
// Remove item from crafting input
self.show.crafting_fields.recipe_inputs.remove(&c.index);
}
},
slot::Event::Request {
@ -4232,7 +4264,8 @@ impl Hud {
}
// Stop selecting a sprite to perform crafting with when out of range
self.show.craft_sprite = self.show.craft_sprite.filter(|(pos, _)| {
self.show.crafting_fields.craft_sprite =
self.show.crafting_fields.craft_sprite.filter(|(pos, _)| {
self.show.crafting
&& if let Some(player_pos) = client.position() {
pos.map(|e| e as f32 + 0.5).distance(player_pos) < MAX_PICKUP_RANGE

View File

@ -12,6 +12,7 @@ use common::comp::{
};
use conrod_core::{image, Color};
use specs::Entity as EcsEntity;
use std::fmt::{Debug, Formatter};
pub use common::comp::slot::{ArmorSlot, EquipSlot};
@ -22,6 +23,7 @@ pub enum SlotKind {
Hotbar(HotbarSlot),
Trade(TradeSlot),
Ability(AbilitySlot),
Crafting(CraftSlot),
/* Spellbook(SpellbookSlot), TODO */
}
@ -233,6 +235,46 @@ impl<'a> SlotKey<AbilitiesSource<'a>, img_ids::Imgs> for AbilitySlot {
}
}
#[derive(Clone, Copy)]
pub struct CraftSlot {
pub index: u32,
pub invslot: Option<InvSlotId>,
pub requirement: fn(Option<&Inventory>, InvSlotId) -> bool,
pub required_amount: u32,
}
impl PartialEq for CraftSlot {
fn eq(&self, other: &Self) -> bool {
(self.index, self.invslot, self.required_amount)
== (other.index, other.invslot, other.required_amount)
}
}
impl Debug for CraftSlot {
fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { todo!() }
}
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))
}
fn amount(&self, source: &Inventory) -> Option<u32> {
self.invslot
.and_then(|invslot| source.get(invslot))
.map(|item| self.required_amount.min(item.amount()))
.filter(|amount| *amount > 1)
}
fn image_ids(key: &Self::ImageKey, source: &ItemImgs) -> Vec<image::Id> {
source.img_ids_or_not_found_img(key.clone())
}
}
impl From<InventorySlot> for SlotKind {
fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) }
}
@ -244,6 +286,7 @@ impl From<EquipSlot> for SlotKind {
impl From<HotbarSlot> for SlotKind {
fn from(hotbar: HotbarSlot) -> Self { Self::Hotbar(hotbar) }
}
impl From<TradeSlot> for SlotKind {
fn from(trade: TradeSlot) -> Self { Self::Trade(trade) }
}
@ -252,6 +295,10 @@ impl From<AbilitySlot> for SlotKind {
fn from(ability: AbilitySlot) -> Self { Self::Ability(ability) }
}
impl From<CraftSlot> for SlotKind {
fn from(craft: CraftSlot) -> Self { Self::Crafting(craft) }
}
impl SumSlot for SlotKind {
fn drag_size(&self) -> Option<[f64; 2]> {
Some(match self {

View File

@ -1420,6 +1420,17 @@ impl PlayState for SessionState {
.craft_recipe(&recipe, slots, craft_sprite);
}
},
HudEvent::CraftModularWeapon {
primary_slot,
secondary_slot,
craft_sprite,
} => {
self.client.borrow_mut().craft_modular_weapon(
primary_slot,
secondary_slot,
craft_sprite.map(|(pos, _sprite)| pos),
);
},
HudEvent::SalvageItem { slot, salvage_pos } => {
self.client.borrow_mut().salvage_item(slot, salvage_pos);
},