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,54 +156,67 @@ impl Recipe {
|
||||
.map(|(item_def, amount, is_mod_comp)| (item_def, *amount, *is_mod_comp))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// 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)>> {
|
||||
// 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();
|
||||
// Important to be a vec and to remain separate from slot_claims as it must
|
||||
// remain ordered, unlike the hashmap
|
||||
let mut slots = Vec::<(u32, InvSlotId)>::new();
|
||||
// The inputs to a recipe that have missing items, and the amount missing
|
||||
let mut missing = Vec::<(&RecipeInput, u32)>::new();
|
||||
inventory_contains_ingredients(
|
||||
self.inputs()
|
||||
.map(|(input, amount, _is_modular)| (input, amount)),
|
||||
inv,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (i, (input, amount, _)) in self.inputs().enumerate() {
|
||||
let mut needed = amount;
|
||||
let mut contains_any = false;
|
||||
// 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))
|
||||
{
|
||||
let claim = slot_claims.entry(inv_slot_id).or_insert(0);
|
||||
slots.push((i as u32, inv_slot_id));
|
||||
let can_claim = (item.amount().saturating_sub(*claim)).min(needed);
|
||||
*claim += can_claim;
|
||||
needed -= can_claim;
|
||||
contains_any = true;
|
||||
}
|
||||
}
|
||||
/// 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.
|
||||
#[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();
|
||||
// Important to be a vec and to remain separate from slot_claims as it must
|
||||
// remain ordered, unlike the hashmap
|
||||
let mut slots = Vec::<(u32, InvSlotId)>::new();
|
||||
// The inputs to a recipe that have missing items, and the amount missing
|
||||
let mut missing = Vec::<(&RecipeInput, u32)>::new();
|
||||
|
||||
if needed > 0 || !contains_any {
|
||||
missing.push((input, needed));
|
||||
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
|
||||
// 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))
|
||||
{
|
||||
let claim = slot_claims.entry(inv_slot_id).or_insert(0);
|
||||
slots.push((i as u32, inv_slot_id));
|
||||
let can_claim = (item.amount().saturating_sub(*claim)).min(needed);
|
||||
*claim += can_claim;
|
||||
needed -= can_claim;
|
||||
contains_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
if missing.is_empty() {
|
||||
Ok(slots)
|
||||
} else {
|
||||
Err(missing)
|
||||
if needed > 0 || !contains_any {
|
||||
missing.push((input, needed));
|
||||
}
|
||||
}
|
||||
|
||||
if missing.is_empty() {
|
||||
Ok(slots)
|
||||
} else {
|
||||
Err(missing)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SalvageError {
|
||||
@ -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()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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