mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Consolidated crafting UI for the primnary component of modular weapons.
This commit is contained in:
parent
74a3f4a7dc
commit
d436362a8d
4544
assets/common/component_recipe_book.ron
Normal file
4544
assets/common/component_recipe_book.ron
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,7 @@ use common::{
|
||||
chat::{KillSource, KillType},
|
||||
controller::CraftEvent,
|
||||
group,
|
||||
inventory::item::{modular, ItemKind},
|
||||
inventory::item::{modular, tool, ItemKind},
|
||||
invite::{InviteKind, InviteResponse},
|
||||
skills::Skill,
|
||||
slot::{EquipSlot, InvSlotId, Slot},
|
||||
@ -39,7 +39,7 @@ use common::{
|
||||
lod,
|
||||
mounting::Rider,
|
||||
outcome::Outcome,
|
||||
recipe::RecipeBook,
|
||||
recipe::{ComponentRecipeBook, RecipeBook},
|
||||
resources::{PlayerEntity, TimeOfDay},
|
||||
spiral::Spiral2d,
|
||||
terrain::{
|
||||
@ -173,6 +173,7 @@ pub struct Client {
|
||||
pois: Vec<PoiInfo>,
|
||||
pub chat_mode: ChatMode,
|
||||
recipe_book: RecipeBook,
|
||||
component_recipe_book: ComponentRecipeBook,
|
||||
available_recipes: HashMap<String, Option<SpriteKind>>,
|
||||
lod_zones: HashMap<Vec2<i32>, lod::Zone>,
|
||||
lod_last_requested: Option<Instant>,
|
||||
@ -291,6 +292,7 @@ impl Client {
|
||||
sites,
|
||||
pois,
|
||||
recipe_book,
|
||||
component_recipe_book,
|
||||
max_group_size,
|
||||
client_timeout,
|
||||
) = match loop {
|
||||
@ -306,6 +308,7 @@ impl Client {
|
||||
client_timeout,
|
||||
world_map,
|
||||
recipe_book,
|
||||
component_recipe_book,
|
||||
material_stats,
|
||||
ability_map,
|
||||
} => {
|
||||
@ -593,6 +596,7 @@ impl Client {
|
||||
world_map.sites,
|
||||
world_map.pois,
|
||||
recipe_book,
|
||||
component_recipe_book,
|
||||
max_group_size,
|
||||
client_timeout,
|
||||
))
|
||||
@ -627,6 +631,7 @@ impl Client {
|
||||
.collect(),
|
||||
pois,
|
||||
recipe_book,
|
||||
component_recipe_book,
|
||||
available_recipes: HashMap::default(),
|
||||
chat_mode: ChatMode::default(),
|
||||
|
||||
@ -1006,6 +1011,8 @@ impl Client {
|
||||
|
||||
pub fn recipe_book(&self) -> &RecipeBook { &self.recipe_book }
|
||||
|
||||
pub fn component_recipe_book(&self) -> &ComponentRecipeBook { &self.component_recipe_book }
|
||||
|
||||
pub fn available_recipes(&self) -> &HashMap<String, Option<SpriteKind>> {
|
||||
&self.available_recipes
|
||||
}
|
||||
@ -1131,6 +1138,27 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn craft_modular_weapon_component(
|
||||
&mut self,
|
||||
toolkind: tool::ToolKind,
|
||||
material: InvSlotId,
|
||||
modifier: Option<InvSlotId>,
|
||||
slots: Vec<(u32, InvSlotId)>,
|
||||
sprite_pos: Option<Vec3<i32>>,
|
||||
) {
|
||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
|
||||
InventoryEvent::CraftRecipe {
|
||||
craft_event: CraftEvent::ModularWeaponPrimaryComponent {
|
||||
toolkind,
|
||||
material,
|
||||
modifier,
|
||||
slots,
|
||||
},
|
||||
craft_sprite: sprite_pos,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
fn update_available_recipes(&mut self) {
|
||||
self.available_recipes = self
|
||||
.recipe_book
|
||||
|
@ -9,7 +9,7 @@ use common::{
|
||||
comp::{self, invite::InviteKind, item::MaterialStatManifest},
|
||||
lod,
|
||||
outcome::Outcome,
|
||||
recipe::RecipeBook,
|
||||
recipe::{ComponentRecipeBook, RecipeBook},
|
||||
resources::TimeOfDay,
|
||||
terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||
trade::{PendingTrade, SitePrices, TradeId, TradeResult},
|
||||
@ -61,6 +61,7 @@ pub enum ServerInit {
|
||||
client_timeout: Duration,
|
||||
world_map: crate::msg::world_msg::WorldMapMsg,
|
||||
recipe_book: RecipeBook,
|
||||
component_recipe_book: ComponentRecipeBook,
|
||||
material_stats: MaterialStatManifest,
|
||||
ability_map: comp::item::tool::AbilityMap,
|
||||
},
|
||||
|
@ -1,7 +1,10 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
ability,
|
||||
inventory::slot::{EquipSlot, InvSlotId, Slot},
|
||||
inventory::{
|
||||
item::tool::ToolKind,
|
||||
slot::{EquipSlot, InvSlotId, Slot},
|
||||
},
|
||||
invite::{InviteKind, InviteResponse},
|
||||
BuffKind,
|
||||
},
|
||||
@ -103,6 +106,13 @@ pub enum CraftEvent {
|
||||
primary_component: InvSlotId,
|
||||
secondary_component: InvSlotId,
|
||||
},
|
||||
// TODO: Maybe try to consolidate into another? Otherwise eventually make more general.
|
||||
ModularWeaponPrimaryComponent {
|
||||
toolkind: ToolKind,
|
||||
material: InvSlotId,
|
||||
modifier: Option<InvSlotId>,
|
||||
slots: Vec<(u32, InvSlotId)>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -3,7 +3,9 @@ use crate::{
|
||||
comp::{
|
||||
inventory::slot::InvSlotId,
|
||||
item::{
|
||||
modular, tool::AbilityMap, ItemBase, ItemDef, ItemKind, ItemTag, MaterialStatManifest,
|
||||
modular,
|
||||
tool::{AbilityMap, ToolKind},
|
||||
ItemBase, ItemDef, ItemKind, ItemTag, MaterialStatManifest,
|
||||
},
|
||||
Inventory, Item,
|
||||
},
|
||||
@ -154,15 +156,29 @@ impl Recipe {
|
||||
.map(|(item_def, amount, is_mod_comp)| (item_def, *amount, *is_mod_comp))
|
||||
}
|
||||
|
||||
/// Determines if the inventory contains the ingredients for a given recipe
|
||||
pub fn inventory_contains_ingredients(
|
||||
&self,
|
||||
inv: &Inventory,
|
||||
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> {
|
||||
inventory_contains_ingredients(
|
||||
self.inputs()
|
||||
.map(|(input, amount, _is_modular)| (input, amount)),
|
||||
inv,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine whether the inventory contains the ingredients for a recipe.
|
||||
/// If it does, return a vec of inventory slots that contain the
|
||||
/// ingredients needed, whose positions correspond to particular recipe
|
||||
/// inputs. If items are missing, return the missing items, and how many
|
||||
/// are missing.
|
||||
pub fn inventory_contains_ingredients(
|
||||
&self,
|
||||
inv: &Inventory,
|
||||
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn inventory_contains_ingredients<'a, 'b, I: Iterator<Item = (&'a RecipeInput, u32)>>(
|
||||
ingredients: I,
|
||||
inv: &'b Inventory,
|
||||
) -> 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)
|
||||
let mut slot_claims = HashMap::<InvSlotId, u32>::new();
|
||||
@ -172,7 +188,7 @@ impl Recipe {
|
||||
// The inputs to a recipe that have missing items, and the amount missing
|
||||
let mut missing = Vec::<(&RecipeInput, u32)>::new();
|
||||
|
||||
for (i, (input, amount, _)) in self.inputs().enumerate() {
|
||||
for (i, (input, amount)) in ingredients.enumerate() {
|
||||
let mut needed = amount;
|
||||
let mut contains_any = false;
|
||||
// Checks through every slot, filtering to only those that contain items that
|
||||
@ -202,7 +218,6 @@ impl Recipe {
|
||||
Err(missing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SalvageError {
|
||||
NotSalvageable,
|
||||
@ -443,6 +458,331 @@ impl assets::Compound for RecipeBook {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ComponentRecipeBook {
|
||||
recipes: HashMap<ComponentKey, ComponentRecipe>,
|
||||
}
|
||||
|
||||
impl ComponentRecipeBook {
|
||||
pub fn get(&self, key: &ComponentKey) -> Option<&ComponentRecipe> { self.recipes.get(key) }
|
||||
|
||||
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&ComponentKey, &ComponentRecipe)> {
|
||||
self.recipes.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
struct RawComponentRecipeBook(HashMap<ComponentKey, RawComponentRecipe>);
|
||||
|
||||
impl assets::Asset for RawComponentRecipeBook {
|
||||
type Loader = assets::RonLoader;
|
||||
|
||||
const EXTENSION: &'static str = "ron";
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
|
||||
pub struct ComponentKey {
|
||||
// Can't use ItemDef here because hash needed, item definition id used instead
|
||||
// TODO: Figure out how to get back to ItemDef maybe?
|
||||
// Keeping under ComponentRecipe may be sufficient?
|
||||
// TODO: Make more general for other things that have component inputs that should be tracked
|
||||
// after item creation
|
||||
pub toolkind: ToolKind,
|
||||
pub material: String,
|
||||
pub modifier: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ComponentRecipe {
|
||||
output: ComponentOutput,
|
||||
material: (RecipeInput, u32),
|
||||
modifier: Option<(RecipeInput, u32)>,
|
||||
additional_inputs: Vec<(RecipeInput, u32)>,
|
||||
pub craft_sprite: Option<SpriteKind>,
|
||||
}
|
||||
|
||||
impl ComponentRecipe {
|
||||
/// Craft an itme that has components, returning a list of missing items on
|
||||
/// failure
|
||||
pub fn craft_component(
|
||||
&self,
|
||||
inv: &mut Inventory,
|
||||
material_slot: InvSlotId,
|
||||
modifier_slot: Option<InvSlotId>,
|
||||
// Vec tying an input to a slot
|
||||
slots: Vec<(u32, InvSlotId)>,
|
||||
ability_map: &AbilityMap,
|
||||
msm: &MaterialStatManifest,
|
||||
) -> Result<Vec<Item>, Vec<(&RecipeInput, u32)>> {
|
||||
let mut slot_claims = HashMap::new();
|
||||
let mut unsatisfied_requirements = Vec::new();
|
||||
|
||||
fn handle_requirement<'a, 'b, I: Iterator<Item = InvSlotId>>(
|
||||
slot_claims: &mut HashMap<InvSlotId, u32>,
|
||||
unsatisfied_requirements: &mut Vec<(&'a RecipeInput, u32)>,
|
||||
inv: &'b Inventory,
|
||||
input: &'a RecipeInput,
|
||||
amount: u32,
|
||||
input_slots: I,
|
||||
) {
|
||||
let mut required = amount;
|
||||
// Check used for recipes that have an input that is not consumed, e.g.
|
||||
// craftsman hammer
|
||||
let mut contains_any = false;
|
||||
// Goes through each slot and marks some amount from each slot as claimed
|
||||
for slot in input_slots {
|
||||
// Checks that the item in the slot can be used for the input
|
||||
if let Some(item) = inv
|
||||
.get(slot)
|
||||
.filter(|item| item.matches_recipe_input(input, amount))
|
||||
{
|
||||
// Gets the number of items claimed from the slot, or sets to 0 if slot has
|
||||
// not been claimed by another input yet
|
||||
let claimed = slot_claims.entry(slot).or_insert(0);
|
||||
let available = item.amount().saturating_sub(*claimed);
|
||||
let provided = available.min(required);
|
||||
required -= provided;
|
||||
*claimed += provided;
|
||||
contains_any = true;
|
||||
}
|
||||
}
|
||||
// If there were not sufficient items to cover requirement between all provided
|
||||
// slots, or if non-consumed item was not present, mark input as not satisfied
|
||||
if required > 0 || !contains_any {
|
||||
unsatisfied_requirements.push((input, required));
|
||||
}
|
||||
}
|
||||
|
||||
// Checks each input against slots in the inventory. If the slots contain an
|
||||
// item that fulfills the need of the input, marks some of the item as claimed
|
||||
// up to quantity needed for the crafting input. If the item either
|
||||
// cannot be used, or there is insufficient quantity, adds input and
|
||||
// number of materials needed to unsatisfied requirements.
|
||||
handle_requirement(
|
||||
&mut slot_claims,
|
||||
&mut unsatisfied_requirements,
|
||||
inv,
|
||||
&self.material.0,
|
||||
self.material.1,
|
||||
core::iter::once(material_slot),
|
||||
);
|
||||
if let Some((modifier_input, modifier_amount)) = &self.modifier {
|
||||
// TODO: Better way to get slot to use that ensures this requirement fails if no
|
||||
// slot provided?
|
||||
handle_requirement(
|
||||
&mut slot_claims,
|
||||
&mut unsatisfied_requirements,
|
||||
inv,
|
||||
modifier_input,
|
||||
*modifier_amount,
|
||||
core::iter::once(modifier_slot.unwrap_or(InvSlotId::new(0, 0))),
|
||||
);
|
||||
}
|
||||
self.additional_inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, (input, amount))| {
|
||||
// Gets all slots provided for this input by the frontend
|
||||
let input_slots = slots
|
||||
.iter()
|
||||
.filter_map(|(j, slot)| if i as u32 == *j { Some(slot) } else { None })
|
||||
.copied();
|
||||
// Checks if requirement is met, and if not marks it as unsatisfied
|
||||
handle_requirement(
|
||||
&mut slot_claims,
|
||||
&mut unsatisfied_requirements,
|
||||
inv,
|
||||
input,
|
||||
*amount,
|
||||
input_slots,
|
||||
);
|
||||
});
|
||||
|
||||
// If there are no unsatisfied requirements, create the items produced by the
|
||||
// recipe in the necessary quantity and remove the items that the recipe
|
||||
// consumes
|
||||
if unsatisfied_requirements.is_empty() {
|
||||
for (slot, to_remove) in slot_claims.iter() {
|
||||
for _ in 0..*to_remove {
|
||||
let _ = inv
|
||||
.take(*slot, ability_map, msm)
|
||||
.expect("Expected item to exist in the inventory");
|
||||
}
|
||||
}
|
||||
|
||||
let crafted_item = self.item_output(ability_map, msm);
|
||||
|
||||
Ok(vec![crafted_item])
|
||||
} else {
|
||||
Err(unsatisfied_requirements)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
/// Determines if the inventory contains the ignredients for a given recipe
|
||||
pub fn inventory_contains_additional_ingredients<'a>(
|
||||
&self,
|
||||
inv: &'a Inventory,
|
||||
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> {
|
||||
inventory_contains_ingredients(
|
||||
self.additional_inputs
|
||||
.iter()
|
||||
.map(|(input, amount)| (input, *amount)),
|
||||
inv,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn item_output(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Item {
|
||||
match &self.output {
|
||||
ComponentOutput::Item(item_def) => Item::new_from_item_base(
|
||||
ItemBase::Raw(Arc::clone(item_def)),
|
||||
Vec::new(),
|
||||
ability_map,
|
||||
msm,
|
||||
),
|
||||
ComponentOutput::ItemComponents {
|
||||
item: item_def,
|
||||
components,
|
||||
} => {
|
||||
let components = components
|
||||
.iter()
|
||||
.map(|item_def| {
|
||||
Item::new_from_item_base(
|
||||
ItemBase::Raw(Arc::clone(item_def)),
|
||||
Vec::new(),
|
||||
ability_map,
|
||||
msm,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Item::new_from_item_base(
|
||||
ItemBase::Raw(Arc::clone(item_def)),
|
||||
components,
|
||||
ability_map,
|
||||
msm,
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
struct RawComponentRecipe {
|
||||
output: RawComponentOutput,
|
||||
material: (RawRecipeInput, u32),
|
||||
modifier: Option<(RawRecipeInput, u32)>,
|
||||
additional_inputs: Vec<(RawRecipeInput, u32)>,
|
||||
craft_sprite: Option<SpriteKind>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
enum ComponentOutput {
|
||||
Item(Arc<ItemDef>),
|
||||
ItemComponents {
|
||||
item: Arc<ItemDef>,
|
||||
components: Vec<Arc<ItemDef>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
enum RawComponentOutput {
|
||||
Item(String),
|
||||
ItemComponents {
|
||||
item: String,
|
||||
components: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl assets::Compound for ComponentRecipeBook {
|
||||
fn load<S: assets::source::Source + ?Sized>(
|
||||
cache: &assets::AssetCache<S>,
|
||||
specifier: &str,
|
||||
) -> Result<Self, assets::BoxedError> {
|
||||
#[inline]
|
||||
fn load_recipe_input(
|
||||
(input, amount): &(RawRecipeInput, u32),
|
||||
) -> Result<(RecipeInput, u32), assets::Error> {
|
||||
let def = match &input {
|
||||
RawRecipeInput::Item(name) => RecipeInput::Item(Arc::<ItemDef>::load_cloned(name)?),
|
||||
RawRecipeInput::Tag(tag) => RecipeInput::Tag(*tag),
|
||||
RawRecipeInput::TagSameItem(tag) => RecipeInput::TagSameItem(*tag),
|
||||
RawRecipeInput::ListSameItem(list) => {
|
||||
let assets = &ItemList::load_expect(list).read().0;
|
||||
let items = assets
|
||||
.iter()
|
||||
.map(|asset| Arc::<ItemDef>::load_expect_cloned(asset))
|
||||
.collect();
|
||||
RecipeInput::ListSameItem(items)
|
||||
},
|
||||
};
|
||||
Ok((def, *amount))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn load_recipe_output(
|
||||
output: &RawComponentOutput,
|
||||
) -> Result<ComponentOutput, assets::Error> {
|
||||
let def = match &output {
|
||||
RawComponentOutput::Item(def) => {
|
||||
ComponentOutput::Item(Arc::<ItemDef>::load_cloned(def)?)
|
||||
},
|
||||
RawComponentOutput::ItemComponents {
|
||||
item: def,
|
||||
components: defs,
|
||||
} => ComponentOutput::ItemComponents {
|
||||
item: Arc::<ItemDef>::load_cloned(def)?,
|
||||
components: defs
|
||||
.iter()
|
||||
.map(|def| Arc::<ItemDef>::load_cloned(def))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
},
|
||||
};
|
||||
Ok(def)
|
||||
}
|
||||
|
||||
let raw = cache.load::<RawComponentRecipeBook>(specifier)?.cloned();
|
||||
|
||||
let recipes = raw
|
||||
.0
|
||||
.iter()
|
||||
.map(
|
||||
|(
|
||||
key,
|
||||
RawComponentRecipe {
|
||||
output,
|
||||
material,
|
||||
modifier,
|
||||
additional_inputs,
|
||||
craft_sprite,
|
||||
},
|
||||
)| {
|
||||
let additional_inputs = additional_inputs
|
||||
.iter()
|
||||
.map(load_recipe_input)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let material = load_recipe_input(material)?;
|
||||
let modifier = modifier.as_ref().map(load_recipe_input).transpose()?;
|
||||
let output = load_recipe_output(output)?;
|
||||
Ok((key.clone(), ComponentRecipe {
|
||||
output,
|
||||
material,
|
||||
modifier,
|
||||
additional_inputs,
|
||||
craft_sprite: *craft_sprite,
|
||||
}))
|
||||
},
|
||||
)
|
||||
.collect::<Result<_, assets::Error>>()?;
|
||||
|
||||
Ok(ComponentRecipeBook { recipes })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_recipe_book() -> AssetHandle<RecipeBook> {
|
||||
RecipeBook::load_expect("common.recipe_book")
|
||||
}
|
||||
|
||||
pub fn default_component_recipe_book() -> AssetHandle<ComponentRecipeBook> {
|
||||
ComponentRecipeBook::load_expect("common.component_recipe_book")
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use common::{
|
||||
slot::{self, Slot},
|
||||
},
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
recipe::{self, default_recipe_book},
|
||||
recipe::{self, default_component_recipe_book, default_recipe_book},
|
||||
terrain::SpriteKind,
|
||||
trade::Trades,
|
||||
uid::Uid,
|
||||
@ -593,7 +593,9 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
craft_sprite,
|
||||
} => {
|
||||
use comp::controller::CraftEvent;
|
||||
use recipe::ComponentKey;
|
||||
let recipe_book = default_recipe_book().read();
|
||||
let component_recipes = default_component_recipe_book().read();
|
||||
let ability_map = &state.ecs().read_resource::<AbilityMap>();
|
||||
let msm = state.ecs().read_resource::<MaterialStatManifest>();
|
||||
|
||||
@ -707,6 +709,64 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
None
|
||||
}
|
||||
},
|
||||
CraftEvent::ModularWeaponPrimaryComponent {
|
||||
toolkind,
|
||||
material,
|
||||
modifier,
|
||||
slots,
|
||||
} => {
|
||||
let item_id = |slot| inventory.get(slot).map(|item| item.item_definition_id());
|
||||
if let Some(material_item_id) = item_id(material) {
|
||||
component_recipes
|
||||
.get(&ComponentKey {
|
||||
toolkind,
|
||||
material: String::from(material_item_id),
|
||||
modifier: modifier.and_then(item_id).map(String::from),
|
||||
})
|
||||
.filter(|r| {
|
||||
if let Some(needed_sprite) = r.craft_sprite {
|
||||
let sprite = craft_sprite
|
||||
.filter(|pos| {
|
||||
let entity_cylinder = get_cylinder(state, entity);
|
||||
if !within_pickup_range(entity_cylinder, || {
|
||||
Some(find_dist::Cube {
|
||||
min: pos.as_(),
|
||||
side_length: 1.0,
|
||||
})
|
||||
}) {
|
||||
debug!(
|
||||
?entity_cylinder,
|
||||
"Failed to craft recipe as not within range \
|
||||
of required sprite, sprite pos: {}",
|
||||
pos
|
||||
);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.and_then(|pos| state.terrain().get(pos).ok().copied())
|
||||
.and_then(|block| block.get_sprite());
|
||||
Some(needed_sprite) == sprite
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.and_then(|r| {
|
||||
r.craft_component(
|
||||
&mut inventory,
|
||||
material,
|
||||
modifier,
|
||||
slots,
|
||||
&state.ecs().read_resource::<AbilityMap>(),
|
||||
&state.ecs().read_resource::<item::MaterialStatManifest>(),
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Attempt to insert items into inventory, dropping them if there is not enough
|
||||
|
@ -75,7 +75,7 @@ use common::{
|
||||
cmd::ChatCommand,
|
||||
comp,
|
||||
event::{EventBus, ServerEvent},
|
||||
recipe::default_recipe_book,
|
||||
recipe::{default_component_recipe_book, default_recipe_book},
|
||||
resources::{BattleMode, Time, TimeOfDay},
|
||||
rtsim::RtSimEntity,
|
||||
slowjob::SlowJobPool,
|
||||
@ -1087,6 +1087,7 @@ impl Server {
|
||||
client_timeout: self.settings().client_timeout,
|
||||
world_map: self.map.clone(),
|
||||
recipe_book: default_recipe_book().cloned(),
|
||||
component_recipe_book: default_component_recipe_book().cloned(),
|
||||
material_stats: (&*self
|
||||
.state
|
||||
.ecs()
|
||||
|
@ -16,14 +16,17 @@ use common::{
|
||||
assets::AssetExt,
|
||||
comp::inventory::{
|
||||
item::{
|
||||
item_key::ItemKey, modular, modular::ModularComponent, tool::AbilityMap, Item,
|
||||
ItemBase, ItemDef, ItemDesc, ItemKind, ItemTag, MaterialStatManifest, Quality,
|
||||
TagExampleInfo,
|
||||
item_key::ItemKey,
|
||||
modular,
|
||||
modular::ModularComponent,
|
||||
tool::{AbilityMap, ToolKind},
|
||||
Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemTag, MaterialKind,
|
||||
MaterialStatManifest, Quality, TagExampleInfo,
|
||||
},
|
||||
slot::InvSlotId,
|
||||
Inventory,
|
||||
},
|
||||
recipe::{Recipe, RecipeInput},
|
||||
recipe::{ComponentKey, Recipe, RecipeInput},
|
||||
terrain::SpriteKind,
|
||||
};
|
||||
use conrod_core::{
|
||||
@ -34,7 +37,7 @@ use conrod_core::{
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use i18n::Localization;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
use vek::*;
|
||||
@ -99,10 +102,16 @@ pub enum Event {
|
||||
primary_slot: InvSlotId,
|
||||
secondary_slot: InvSlotId,
|
||||
},
|
||||
CraftModularWeaponComponent {
|
||||
toolkind: ToolKind,
|
||||
material: InvSlotId,
|
||||
modifier: Option<InvSlotId>,
|
||||
},
|
||||
ChangeCraftingTab(CraftingTab),
|
||||
Close,
|
||||
Focus(widget::Id),
|
||||
SearchRecipe(Option<String>),
|
||||
ClearRecipeInputs,
|
||||
}
|
||||
|
||||
pub struct CraftingShow {
|
||||
@ -489,8 +498,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
let (modular_entry_name, modular_entry_recipe) = {
|
||||
let recipe = Recipe {
|
||||
let weapon_recipe = Recipe {
|
||||
output: (
|
||||
Arc::<ItemDef>::load_expect_cloned("common.items.weapons.empty.empty"),
|
||||
0,
|
||||
@ -498,10 +506,53 @@ impl<'a> Widget for Crafting<'a> {
|
||||
inputs: Vec::new(),
|
||||
craft_sprite: Some(SpriteKind::CraftingBench),
|
||||
};
|
||||
(
|
||||
let metal_comp_recipe = Recipe {
|
||||
output: (
|
||||
Arc::<ItemDef>::load_expect_cloned("common.items.weapons.empty.empty"),
|
||||
0,
|
||||
),
|
||||
inputs: Vec::new(),
|
||||
craft_sprite: Some(SpriteKind::Anvil),
|
||||
};
|
||||
let wood_comp_recipe = Recipe {
|
||||
output: (
|
||||
Arc::<ItemDef>::load_expect_cloned("common.items.weapons.empty.empty"),
|
||||
0,
|
||||
),
|
||||
inputs: Vec::new(),
|
||||
craft_sprite: Some(SpriteKind::CraftingBench),
|
||||
};
|
||||
let modular_entries = {
|
||||
let mut modular_entries = BTreeMap::new();
|
||||
modular_entries.insert(
|
||||
String::from("veloren.core.pseudo_recipe.modular_weapon"),
|
||||
recipe,
|
||||
)
|
||||
(&weapon_recipe, "Modular Weapon"),
|
||||
);
|
||||
modular_entries.insert(
|
||||
String::from("veloren.core.pseudo_recipe.modular_weapon_component.sword"),
|
||||
(&metal_comp_recipe, "Sword Blade"),
|
||||
);
|
||||
modular_entries.insert(
|
||||
String::from("veloren.core.pseudo_recipe.modular_weapon_component.axe"),
|
||||
(&metal_comp_recipe, "Axe Head"),
|
||||
);
|
||||
modular_entries.insert(
|
||||
String::from("veloren.core.pseudo_recipe.modular_weapon_component.hammer"),
|
||||
(&metal_comp_recipe, "Hammer Head"),
|
||||
);
|
||||
modular_entries.insert(
|
||||
String::from("veloren.core.pseudo_recipe.modular_weapon_component.bow"),
|
||||
(&wood_comp_recipe, "Bow Limbs"),
|
||||
);
|
||||
modular_entries.insert(
|
||||
String::from("veloren.core.pseudo_recipe.modular_weapon_component.staff"),
|
||||
(&wood_comp_recipe, "Staff Shaft"),
|
||||
);
|
||||
modular_entries.insert(
|
||||
String::from("veloren.core.pseudo_recipe.modular_weapon_component.sceptre"),
|
||||
(&wood_comp_recipe, "Sceptre Shaft"),
|
||||
);
|
||||
modular_entries
|
||||
};
|
||||
|
||||
// First available recipes, then ones with available materials,
|
||||
@ -553,14 +604,17 @@ impl<'a> Widget for Crafting<'a> {
|
||||
})
|
||||
.chain(
|
||||
matches!(sel_crafting_tab, CraftingTab::Weapon | CraftingTab::All)
|
||||
.then_some((
|
||||
&modular_entry_name,
|
||||
&modular_entry_recipe,
|
||||
.then_some(modular_entries.iter().map(|(recipe_name, (recipe, _))| {
|
||||
(
|
||||
recipe_name,
|
||||
*recipe,
|
||||
self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
|
||||
== modular_entry_recipe.craft_sprite,
|
||||
== recipe.craft_sprite,
|
||||
true,
|
||||
))
|
||||
.into_iter(),
|
||||
)
|
||||
}))
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.collect();
|
||||
ordered_recipes.sort_by_key(|(_, recipe, is_craftable, has_materials)| {
|
||||
@ -573,9 +627,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
});
|
||||
|
||||
// Recipe list
|
||||
// 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;
|
||||
let recipe_list_length = self.client.recipe_book().iter().len() + modular_entries.len();
|
||||
if state.ids.recipe_list_btns.len() < recipe_list_length {
|
||||
state.update(|state| {
|
||||
state
|
||||
@ -630,12 +682,13 @@ 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 = match name.as_str() {
|
||||
"veloren.core.pseudo_recipe.modular_weapon" => Cow::Borrowed("Modular Weapon"),
|
||||
_ => recipe.output.0.name(),
|
||||
let recipe_name = if let Some((_recipe, modular_name)) = modular_entries.get(name) {
|
||||
*modular_name
|
||||
} else {
|
||||
&recipe.output.0.name
|
||||
};
|
||||
|
||||
let text = Text::new(&recipe_name)
|
||||
let text = Text::new(recipe_name)
|
||||
.color(if is_craftable {
|
||||
TEXT_COLOR
|
||||
} else {
|
||||
@ -672,6 +725,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
}
|
||||
state.update(|s| s.selected_recipe = Some(name.clone()));
|
||||
}
|
||||
events.push(Event::ClearRecipeInputs);
|
||||
}
|
||||
// set the text here so that the correct position of the button is retrieved
|
||||
text.set(state.ids.recipe_list_labels[i], ui);
|
||||
@ -739,19 +793,26 @@ impl<'a> Widget for Crafting<'a> {
|
||||
|
||||
// Selected Recipe
|
||||
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)))
|
||||
Some(selected_recipe) => {
|
||||
if let Some((modular_recipe, _modular_name)) = modular_entries.get(selected_recipe)
|
||||
{
|
||||
Some((selected_recipe, *modular_recipe))
|
||||
} else {
|
||||
self.client
|
||||
.recipe_book()
|
||||
.get(selected_recipe)
|
||||
.map(|r| (selected_recipe, r))
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
} {
|
||||
let title = match recipe_name {
|
||||
"veloren.core.pseudo_recipe.modular_weapon" => Cow::Borrowed("Modular Weapon"),
|
||||
_ => recipe.output.0.name(),
|
||||
let title = if let Some((_recipe, modular_name)) = modular_entries.get(recipe_name) {
|
||||
*modular_name
|
||||
} else {
|
||||
&recipe.output.0.name
|
||||
};
|
||||
// Title
|
||||
Text::new(&title)
|
||||
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))
|
||||
@ -759,8 +820,36 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.parent(state.ids.window)
|
||||
.set(state.ids.title_ing, ui);
|
||||
|
||||
match recipe_name {
|
||||
"veloren.core.pseudo_recipe.modular_weapon" => {
|
||||
if modular_entries.get(recipe_name).is_some() {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum RecipeKind {
|
||||
ModularWeapon,
|
||||
Component(ToolKind),
|
||||
}
|
||||
|
||||
let recipe_kind = match recipe_name {
|
||||
"veloren.core.pseudo_recipe.modular_weapon" => Some(RecipeKind::ModularWeapon),
|
||||
"veloren.core.pseudo_recipe.modular_weapon_component.sword" => {
|
||||
Some(RecipeKind::Component(ToolKind::Sword))
|
||||
},
|
||||
"veloren.core.pseudo_recipe.modular_weapon_component.axe" => {
|
||||
Some(RecipeKind::Component(ToolKind::Axe))
|
||||
},
|
||||
"veloren.core.pseudo_recipe.modular_weapon_component.hammer" => {
|
||||
Some(RecipeKind::Component(ToolKind::Hammer))
|
||||
},
|
||||
"veloren.core.pseudo_recipe.modular_weapon_component.bow" => {
|
||||
Some(RecipeKind::Component(ToolKind::Bow))
|
||||
},
|
||||
"veloren.core.pseudo_recipe.modular_weapon_component.staff" => {
|
||||
Some(RecipeKind::Component(ToolKind::Staff))
|
||||
},
|
||||
"veloren.core.pseudo_recipe.modular_weapon_component.sceptre" => {
|
||||
Some(RecipeKind::Component(ToolKind::Sceptre))
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut slot_maker = SlotMaker {
|
||||
empty_slot: self.imgs.inv_slot,
|
||||
filled_slot: self.imgs.inv_slot,
|
||||
@ -793,10 +882,12 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.mid_top_with_margin_on(state.ids.align_ing, 55.0)
|
||||
.w_h(168.0, 250.0)
|
||||
.set(state.ids.modular_art, ui);
|
||||
|
||||
let primary_slot = CraftSlot {
|
||||
index: 0,
|
||||
invslot: self.show.crafting_fields.recipe_inputs.get(&0).copied(),
|
||||
requirement: |inv, slot| {
|
||||
requirement: match recipe_kind {
|
||||
Some(RecipeKind::ModularWeapon) => |inv, slot| {
|
||||
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
|
||||
matches!(
|
||||
&*item.kind(),
|
||||
@ -806,7 +897,44 @@ impl<'a> Widget for Crafting<'a> {
|
||||
)
|
||||
})
|
||||
},
|
||||
required_amount: 1,
|
||||
// TODO: Maybe try to figure out way to get this to work?
|
||||
// Captures self and toolkind which prevents it from becoming a function
|
||||
// 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()) })
|
||||
// },
|
||||
Some(RecipeKind::Component(
|
||||
ToolKind::Sword | ToolKind::Axe | ToolKind::Hammer,
|
||||
)) => |inv, slot| {
|
||||
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
|
||||
matches!(&*item.kind(), ItemKind::Ingredient { .. })
|
||||
&& item
|
||||
.tags()
|
||||
.contains(&ItemTag::MaterialKind(MaterialKind::Metal))
|
||||
&& item
|
||||
.tags()
|
||||
.iter()
|
||||
.any(|tag| matches!(tag, ItemTag::Material(_)))
|
||||
})
|
||||
},
|
||||
Some(RecipeKind::Component(
|
||||
ToolKind::Bow | ToolKind::Staff | ToolKind::Sceptre,
|
||||
)) => |inv, slot| {
|
||||
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
|
||||
matches!(&*item.kind(), ItemKind::Ingredient { .. })
|
||||
&& item
|
||||
.tags()
|
||||
.contains(&ItemTag::MaterialKind(MaterialKind::Wood))
|
||||
&& item
|
||||
.tags()
|
||||
.iter()
|
||||
.any(|tag| matches!(tag, ItemTag::Material(_)))
|
||||
})
|
||||
},
|
||||
Some(RecipeKind::Component(_)) | None => |_, _| false,
|
||||
},
|
||||
};
|
||||
|
||||
let primary_slot_widget = slot_maker
|
||||
@ -849,7 +977,8 @@ impl<'a> Widget for Crafting<'a> {
|
||||
let secondary_slot = CraftSlot {
|
||||
index: 1,
|
||||
invslot: self.show.crafting_fields.recipe_inputs.get(&1).copied(),
|
||||
requirement: |inv, slot| {
|
||||
requirement: match recipe_kind {
|
||||
Some(RecipeKind::ModularWeapon) => |inv, slot| {
|
||||
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
|
||||
matches!(
|
||||
&*item.kind(),
|
||||
@ -859,7 +988,22 @@ impl<'a> Widget for Crafting<'a> {
|
||||
)
|
||||
})
|
||||
},
|
||||
required_amount: 1,
|
||||
// TODO: Maybe try to figure out way to get this to work?
|
||||
// Captures self and toolkind which prevents it from becoming a function
|
||||
// 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| {
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
let secondary_slot_widget = slot_maker
|
||||
@ -898,8 +1042,13 @@ impl<'a> Widget for Crafting<'a> {
|
||||
// .set(state.ids.modular_inputs[1], ui);
|
||||
// }
|
||||
|
||||
let can_perform =
|
||||
primary_slot.invslot.is_some() && secondary_slot.invslot.is_some();
|
||||
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();
|
||||
@ -928,6 +1077,8 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.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)
|
||||
{
|
||||
@ -936,6 +1087,18 @@ impl<'a> Widget for Crafting<'a> {
|
||||
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
|
||||
@ -962,18 +1125,25 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.set(state.ids.modular_wep_ing_2_bg, ui);
|
||||
}
|
||||
|
||||
if let (Some(primary_comp), Some(secondary_comp)) = (
|
||||
primary_slot
|
||||
let ability_map = &AbilityMap::load().read();
|
||||
let msm = &MaterialStatManifest::load().read();
|
||||
|
||||
if let Some(output_item) = match recipe_kind {
|
||||
Some(RecipeKind::ModularWeapon) => {
|
||||
if let Some((primary_comp, toolkind)) = primary_slot
|
||||
.invslot
|
||||
.and_then(|slot| self.inventory.get(slot))
|
||||
.filter(|item| {
|
||||
matches!(
|
||||
&*item.kind(),
|
||||
ItemKind::ModularComponent(
|
||||
ModularComponent::ToolPrimaryComponent { .. }
|
||||
)
|
||||
)
|
||||
}),
|
||||
.and_then(|item| {
|
||||
if let ItemKind::ModularComponent(
|
||||
ModularComponent::ToolPrimaryComponent { toolkind, .. },
|
||||
) = &*item.kind()
|
||||
{
|
||||
Some((item, *toolkind))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
{
|
||||
secondary_slot
|
||||
.invslot
|
||||
.and_then(|slot| self.inventory.get(slot))
|
||||
@ -981,14 +1151,12 @@ impl<'a> Widget for Crafting<'a> {
|
||||
matches!(
|
||||
&*item.kind(),
|
||||
ItemKind::ModularComponent(
|
||||
ModularComponent::ToolSecondaryComponent { .. }
|
||||
ModularComponent::ToolSecondaryComponent { toolkind: toolkind_b, .. }
|
||||
) if toolkind == *toolkind_b
|
||||
)
|
||||
)
|
||||
}),
|
||||
) {
|
||||
let ability_map = &AbilityMap::load().read();
|
||||
let msm = &MaterialStatManifest::load().read();
|
||||
let output_item = Item::new_from_item_base(
|
||||
})
|
||||
.map(|secondary_comp| {
|
||||
Item::new_from_item_base(
|
||||
ItemBase::Modular(modular::ModularBase::Tool),
|
||||
vec![
|
||||
primary_comp.duplicate(ability_map, msm),
|
||||
@ -996,14 +1164,39 @@ impl<'a> Widget for Crafting<'a> {
|
||||
],
|
||||
ability_map,
|
||||
msm,
|
||||
);
|
||||
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
Some(RecipeKind::Component(toolkind)) => {
|
||||
if let Some(material) = primary_slot
|
||||
.invslot
|
||||
.and_then(|slot| self.inventory.get(slot))
|
||||
.map(|item| String::from(item.item_definition_id()))
|
||||
{
|
||||
let component_key = ComponentKey {
|
||||
toolkind,
|
||||
material,
|
||||
modifier: secondary_slot
|
||||
.invslot
|
||||
.and_then(|slot| self.inventory.get(slot))
|
||||
.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),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
} {
|
||||
Button::image(animate_by_pulse(
|
||||
&self
|
||||
.item_imgs
|
||||
.img_ids_or_not_found_img(ItemKey::ModularWeapon(
|
||||
modular::weapon_to_key(&output_item),
|
||||
)),
|
||||
.img_ids_or_not_found_img(ItemKey::from(&output_item)),
|
||||
self.pulse,
|
||||
))
|
||||
.w_h(55.0, 55.0)
|
||||
@ -1035,16 +1228,14 @@ 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)
|
||||
Some(cs) == self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
|
||||
})
|
||||
});
|
||||
|
||||
@ -1131,9 +1322,7 @@ 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()
|
||||
@ -1225,8 +1414,7 @@ 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);
|
||||
@ -1289,8 +1477,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
|
||||
@ -1300,14 +1487,12 @@ 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
|
||||
@ -1320,8 +1505,7 @@ 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
|
||||
@ -1452,7 +1636,6 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.set(state.ids.ingredients[i], ui);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
} else if *sel_crafting_tab == CraftingTab::Dismantle {
|
||||
// Title
|
||||
|
@ -538,6 +538,12 @@ pub enum Event {
|
||||
secondary_slot: InvSlotId,
|
||||
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||
},
|
||||
CraftModularWeaponComponent {
|
||||
toolkind: ToolKind,
|
||||
material: InvSlotId,
|
||||
modifier: Option<InvSlotId>,
|
||||
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||
},
|
||||
InviteMember(Uid),
|
||||
AcceptInvite,
|
||||
DeclineInvite,
|
||||
@ -2959,6 +2965,18 @@ impl Hud {
|
||||
craft_sprite: self.show.crafting_fields.craft_sprite,
|
||||
});
|
||||
},
|
||||
crafting::Event::CraftModularWeaponComponent {
|
||||
toolkind,
|
||||
material,
|
||||
modifier,
|
||||
} => {
|
||||
events.push(Event::CraftModularWeaponComponent {
|
||||
toolkind,
|
||||
material,
|
||||
modifier,
|
||||
craft_sprite: self.show.crafting_fields.craft_sprite,
|
||||
});
|
||||
},
|
||||
crafting::Event::Close => {
|
||||
self.show.stats = false;
|
||||
self.show.crafting(false);
|
||||
@ -2978,6 +2996,9 @@ impl Hud {
|
||||
crafting::Event::SearchRecipe(search_key) => {
|
||||
self.show.search_crafting_recipe(search_key);
|
||||
},
|
||||
crafting::Event::ClearRecipeInputs => {
|
||||
self.show.crafting_fields.recipe_inputs.clear();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -240,13 +240,11 @@ 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)
|
||||
(self.index, self.invslot) == (other.index, other.invslot)
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,7 +264,7 @@ impl SlotKey<Inventory, ItemImgs> for CraftSlot {
|
||||
fn amount(&self, source: &Inventory) -> Option<u32> {
|
||||
self.invslot
|
||||
.and_then(|invslot| source.get(invslot))
|
||||
.map(|item| self.required_amount.min(item.amount()))
|
||||
.map(|item| item.amount())
|
||||
.filter(|amount| *amount > 1)
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ use common::{
|
||||
link::Is,
|
||||
mounting::Mount,
|
||||
outcome::Outcome,
|
||||
recipe,
|
||||
terrain::{Block, BlockKind},
|
||||
trade::TradeResult,
|
||||
util::{Dir, Plane},
|
||||
@ -1431,6 +1432,48 @@ impl PlayState for SessionState {
|
||||
craft_sprite.map(|(pos, _sprite)| pos),
|
||||
);
|
||||
},
|
||||
HudEvent::CraftModularWeaponComponent {
|
||||
toolkind,
|
||||
material,
|
||||
modifier,
|
||||
craft_sprite,
|
||||
} => {
|
||||
let additional_slots = {
|
||||
let client = self.client.borrow();
|
||||
let item_id = |slot| {
|
||||
client
|
||||
.inventories()
|
||||
.get(client.entity())
|
||||
.and_then(|inv| inv.get(slot))
|
||||
.map(|item| String::from(item.item_definition_id()))
|
||||
};
|
||||
if let Some(material_id) = item_id(material) {
|
||||
let key = recipe::ComponentKey {
|
||||
toolkind,
|
||||
material: material_id,
|
||||
modifier: modifier.and_then(item_id),
|
||||
};
|
||||
if let Some(recipe) = client.component_recipe_book().get(&key) {
|
||||
client.inventories().get(client.entity()).and_then(|inv| {
|
||||
recipe.inventory_contains_additional_ingredients(inv).ok()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(additional_slots) = additional_slots {
|
||||
self.client.borrow_mut().craft_modular_weapon_component(
|
||||
toolkind,
|
||||
material,
|
||||
modifier,
|
||||
additional_slots,
|
||||
craft_sprite.map(|(pos, _sprite)| pos),
|
||||
);
|
||||
}
|
||||
},
|
||||
HudEvent::SalvageItem { slot, salvage_pos } => {
|
||||
self.client.borrow_mut().salvage_item(slot, salvage_pos);
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user