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},
|
chat::{KillSource, KillType},
|
||||||
controller::CraftEvent,
|
controller::CraftEvent,
|
||||||
group,
|
group,
|
||||||
inventory::item::{modular, ItemKind},
|
inventory::item::{modular, tool, ItemKind},
|
||||||
invite::{InviteKind, InviteResponse},
|
invite::{InviteKind, InviteResponse},
|
||||||
skills::Skill,
|
skills::Skill,
|
||||||
slot::{EquipSlot, InvSlotId, Slot},
|
slot::{EquipSlot, InvSlotId, Slot},
|
||||||
@ -39,7 +39,7 @@ use common::{
|
|||||||
lod,
|
lod,
|
||||||
mounting::Rider,
|
mounting::Rider,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
recipe::RecipeBook,
|
recipe::{ComponentRecipeBook, RecipeBook},
|
||||||
resources::{PlayerEntity, TimeOfDay},
|
resources::{PlayerEntity, TimeOfDay},
|
||||||
spiral::Spiral2d,
|
spiral::Spiral2d,
|
||||||
terrain::{
|
terrain::{
|
||||||
@ -173,6 +173,7 @@ pub struct Client {
|
|||||||
pois: Vec<PoiInfo>,
|
pois: Vec<PoiInfo>,
|
||||||
pub chat_mode: ChatMode,
|
pub chat_mode: ChatMode,
|
||||||
recipe_book: RecipeBook,
|
recipe_book: RecipeBook,
|
||||||
|
component_recipe_book: ComponentRecipeBook,
|
||||||
available_recipes: HashMap<String, Option<SpriteKind>>,
|
available_recipes: HashMap<String, Option<SpriteKind>>,
|
||||||
lod_zones: HashMap<Vec2<i32>, lod::Zone>,
|
lod_zones: HashMap<Vec2<i32>, lod::Zone>,
|
||||||
lod_last_requested: Option<Instant>,
|
lod_last_requested: Option<Instant>,
|
||||||
@ -291,6 +292,7 @@ impl Client {
|
|||||||
sites,
|
sites,
|
||||||
pois,
|
pois,
|
||||||
recipe_book,
|
recipe_book,
|
||||||
|
component_recipe_book,
|
||||||
max_group_size,
|
max_group_size,
|
||||||
client_timeout,
|
client_timeout,
|
||||||
) = match loop {
|
) = match loop {
|
||||||
@ -306,6 +308,7 @@ impl Client {
|
|||||||
client_timeout,
|
client_timeout,
|
||||||
world_map,
|
world_map,
|
||||||
recipe_book,
|
recipe_book,
|
||||||
|
component_recipe_book,
|
||||||
material_stats,
|
material_stats,
|
||||||
ability_map,
|
ability_map,
|
||||||
} => {
|
} => {
|
||||||
@ -593,6 +596,7 @@ impl Client {
|
|||||||
world_map.sites,
|
world_map.sites,
|
||||||
world_map.pois,
|
world_map.pois,
|
||||||
recipe_book,
|
recipe_book,
|
||||||
|
component_recipe_book,
|
||||||
max_group_size,
|
max_group_size,
|
||||||
client_timeout,
|
client_timeout,
|
||||||
))
|
))
|
||||||
@ -627,6 +631,7 @@ impl Client {
|
|||||||
.collect(),
|
.collect(),
|
||||||
pois,
|
pois,
|
||||||
recipe_book,
|
recipe_book,
|
||||||
|
component_recipe_book,
|
||||||
available_recipes: HashMap::default(),
|
available_recipes: HashMap::default(),
|
||||||
chat_mode: ChatMode::default(),
|
chat_mode: ChatMode::default(),
|
||||||
|
|
||||||
@ -1006,6 +1011,8 @@ impl Client {
|
|||||||
|
|
||||||
pub fn recipe_book(&self) -> &RecipeBook { &self.recipe_book }
|
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>> {
|
pub fn available_recipes(&self) -> &HashMap<String, Option<SpriteKind>> {
|
||||||
&self.available_recipes
|
&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) {
|
fn update_available_recipes(&mut self) {
|
||||||
self.available_recipes = self
|
self.available_recipes = self
|
||||||
.recipe_book
|
.recipe_book
|
||||||
|
@ -9,7 +9,7 @@ use common::{
|
|||||||
comp::{self, invite::InviteKind, item::MaterialStatManifest},
|
comp::{self, invite::InviteKind, item::MaterialStatManifest},
|
||||||
lod,
|
lod,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
recipe::RecipeBook,
|
recipe::{ComponentRecipeBook, RecipeBook},
|
||||||
resources::TimeOfDay,
|
resources::TimeOfDay,
|
||||||
terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||||
trade::{PendingTrade, SitePrices, TradeId, TradeResult},
|
trade::{PendingTrade, SitePrices, TradeId, TradeResult},
|
||||||
@ -61,6 +61,7 @@ pub enum ServerInit {
|
|||||||
client_timeout: Duration,
|
client_timeout: Duration,
|
||||||
world_map: crate::msg::world_msg::WorldMapMsg,
|
world_map: crate::msg::world_msg::WorldMapMsg,
|
||||||
recipe_book: RecipeBook,
|
recipe_book: RecipeBook,
|
||||||
|
component_recipe_book: ComponentRecipeBook,
|
||||||
material_stats: MaterialStatManifest,
|
material_stats: MaterialStatManifest,
|
||||||
ability_map: comp::item::tool::AbilityMap,
|
ability_map: comp::item::tool::AbilityMap,
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comp::{
|
comp::{
|
||||||
ability,
|
ability,
|
||||||
inventory::slot::{EquipSlot, InvSlotId, Slot},
|
inventory::{
|
||||||
|
item::tool::ToolKind,
|
||||||
|
slot::{EquipSlot, InvSlotId, Slot},
|
||||||
|
},
|
||||||
invite::{InviteKind, InviteResponse},
|
invite::{InviteKind, InviteResponse},
|
||||||
BuffKind,
|
BuffKind,
|
||||||
},
|
},
|
||||||
@ -103,6 +106,13 @@ pub enum CraftEvent {
|
|||||||
primary_component: InvSlotId,
|
primary_component: InvSlotId,
|
||||||
secondary_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)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
@ -3,7 +3,9 @@ use crate::{
|
|||||||
comp::{
|
comp::{
|
||||||
inventory::slot::InvSlotId,
|
inventory::slot::InvSlotId,
|
||||||
item::{
|
item::{
|
||||||
modular, tool::AbilityMap, ItemBase, ItemDef, ItemKind, ItemTag, MaterialStatManifest,
|
modular,
|
||||||
|
tool::{AbilityMap, ToolKind},
|
||||||
|
ItemBase, ItemDef, ItemKind, ItemTag, MaterialStatManifest,
|
||||||
},
|
},
|
||||||
Inventory, Item,
|
Inventory, Item,
|
||||||
},
|
},
|
||||||
@ -154,54 +156,67 @@ impl Recipe {
|
|||||||
.map(|(item_def, amount, is_mod_comp)| (item_def, *amount, *is_mod_comp))
|
.map(|(item_def, amount, is_mod_comp)| (item_def, *amount, *is_mod_comp))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine whether the inventory contains the ingredients for a recipe.
|
/// Determines if the inventory contains the ingredients for a given 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(
|
pub fn inventory_contains_ingredients(
|
||||||
&self,
|
&self,
|
||||||
inv: &Inventory,
|
inv: &Inventory,
|
||||||
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> {
|
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> {
|
||||||
// Hashmap tracking the quantity that needs to be removed from each slot (so
|
inventory_contains_ingredients(
|
||||||
// that it doesn't think a slot can provide more items than it contains)
|
self.inputs()
|
||||||
let mut slot_claims = HashMap::<InvSlotId, u32>::new();
|
.map(|(input, amount, _is_modular)| (input, amount)),
|
||||||
// Important to be a vec and to remain separate from slot_claims as it must
|
inv,
|
||||||
// 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();
|
|
||||||
|
|
||||||
for (i, (input, amount, _)) in self.inputs().enumerate() {
|
/// Determine whether the inventory contains the ingredients for a recipe.
|
||||||
let mut needed = amount;
|
/// If it does, return a vec of inventory slots that contain the
|
||||||
let mut contains_any = false;
|
/// ingredients needed, whose positions correspond to particular recipe
|
||||||
// Checks through every slot, filtering to only those that contain items that
|
/// inputs. If items are missing, return the missing items, and how many
|
||||||
// can satisfy the input
|
/// are missing.
|
||||||
for (inv_slot_id, slot) in inv.slots_with_id() {
|
#[allow(clippy::type_complexity)]
|
||||||
if let Some(item) = slot
|
fn inventory_contains_ingredients<'a, 'b, I: Iterator<Item = (&'a RecipeInput, u32)>>(
|
||||||
.as_ref()
|
ingredients: I,
|
||||||
.filter(|item| item.matches_recipe_input(&*input, amount))
|
inv: &'b Inventory,
|
||||||
{
|
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&'a RecipeInput, u32)>> {
|
||||||
let claim = slot_claims.entry(inv_slot_id).or_insert(0);
|
// Hashmap tracking the quantity that needs to be removed from each slot (so
|
||||||
slots.push((i as u32, inv_slot_id));
|
// that it doesn't think a slot can provide more items than it contains)
|
||||||
let can_claim = (item.amount().saturating_sub(*claim)).min(needed);
|
let mut slot_claims = HashMap::<InvSlotId, u32>::new();
|
||||||
*claim += can_claim;
|
// Important to be a vec and to remain separate from slot_claims as it must
|
||||||
needed -= can_claim;
|
// remain ordered, unlike the hashmap
|
||||||
contains_any = true;
|
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 {
|
for (i, (input, amount)) in ingredients.enumerate() {
|
||||||
missing.push((input, needed));
|
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() {
|
if needed > 0 || !contains_any {
|
||||||
Ok(slots)
|
missing.push((input, needed));
|
||||||
} else {
|
|
||||||
Err(missing)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if missing.is_empty() {
|
||||||
|
Ok(slots)
|
||||||
|
} else {
|
||||||
|
Err(missing)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SalvageError {
|
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> {
|
pub fn default_recipe_book() -> AssetHandle<RecipeBook> {
|
||||||
RecipeBook::load_expect("common.recipe_book")
|
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},
|
slot::{self, Slot},
|
||||||
},
|
},
|
||||||
consts::MAX_PICKUP_RANGE,
|
consts::MAX_PICKUP_RANGE,
|
||||||
recipe::{self, default_recipe_book},
|
recipe::{self, default_component_recipe_book, default_recipe_book},
|
||||||
terrain::SpriteKind,
|
terrain::SpriteKind,
|
||||||
trade::Trades,
|
trade::Trades,
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
@ -593,7 +593,9 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
craft_sprite,
|
craft_sprite,
|
||||||
} => {
|
} => {
|
||||||
use comp::controller::CraftEvent;
|
use comp::controller::CraftEvent;
|
||||||
|
use recipe::ComponentKey;
|
||||||
let recipe_book = default_recipe_book().read();
|
let recipe_book = default_recipe_book().read();
|
||||||
|
let component_recipes = default_component_recipe_book().read();
|
||||||
let ability_map = &state.ecs().read_resource::<AbilityMap>();
|
let ability_map = &state.ecs().read_resource::<AbilityMap>();
|
||||||
let msm = state.ecs().read_resource::<MaterialStatManifest>();
|
let msm = state.ecs().read_resource::<MaterialStatManifest>();
|
||||||
|
|
||||||
@ -707,6 +709,64 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
None
|
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
|
// Attempt to insert items into inventory, dropping them if there is not enough
|
||||||
|
@ -75,7 +75,7 @@ use common::{
|
|||||||
cmd::ChatCommand,
|
cmd::ChatCommand,
|
||||||
comp,
|
comp,
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
recipe::default_recipe_book,
|
recipe::{default_component_recipe_book, default_recipe_book},
|
||||||
resources::{BattleMode, Time, TimeOfDay},
|
resources::{BattleMode, Time, TimeOfDay},
|
||||||
rtsim::RtSimEntity,
|
rtsim::RtSimEntity,
|
||||||
slowjob::SlowJobPool,
|
slowjob::SlowJobPool,
|
||||||
@ -1087,6 +1087,7 @@ impl Server {
|
|||||||
client_timeout: self.settings().client_timeout,
|
client_timeout: self.settings().client_timeout,
|
||||||
world_map: self.map.clone(),
|
world_map: self.map.clone(),
|
||||||
recipe_book: default_recipe_book().cloned(),
|
recipe_book: default_recipe_book().cloned(),
|
||||||
|
component_recipe_book: default_component_recipe_book().cloned(),
|
||||||
material_stats: (&*self
|
material_stats: (&*self
|
||||||
.state
|
.state
|
||||||
.ecs()
|
.ecs()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -538,6 +538,12 @@ pub enum Event {
|
|||||||
secondary_slot: InvSlotId,
|
secondary_slot: InvSlotId,
|
||||||
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||||
},
|
},
|
||||||
|
CraftModularWeaponComponent {
|
||||||
|
toolkind: ToolKind,
|
||||||
|
material: InvSlotId,
|
||||||
|
modifier: Option<InvSlotId>,
|
||||||
|
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||||
|
},
|
||||||
InviteMember(Uid),
|
InviteMember(Uid),
|
||||||
AcceptInvite,
|
AcceptInvite,
|
||||||
DeclineInvite,
|
DeclineInvite,
|
||||||
@ -2959,6 +2965,18 @@ impl Hud {
|
|||||||
craft_sprite: self.show.crafting_fields.craft_sprite,
|
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 => {
|
crafting::Event::Close => {
|
||||||
self.show.stats = false;
|
self.show.stats = false;
|
||||||
self.show.crafting(false);
|
self.show.crafting(false);
|
||||||
@ -2978,6 +2996,9 @@ impl Hud {
|
|||||||
crafting::Event::SearchRecipe(search_key) => {
|
crafting::Event::SearchRecipe(search_key) => {
|
||||||
self.show.search_crafting_recipe(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 index: u32,
|
||||||
pub invslot: Option<InvSlotId>,
|
pub invslot: Option<InvSlotId>,
|
||||||
pub requirement: fn(Option<&Inventory>, InvSlotId) -> bool,
|
pub requirement: fn(Option<&Inventory>, InvSlotId) -> bool,
|
||||||
pub required_amount: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for CraftSlot {
|
impl PartialEq for CraftSlot {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
(self.index, self.invslot, self.required_amount)
|
(self.index, self.invslot) == (other.index, other.invslot)
|
||||||
== (other.index, other.invslot, other.required_amount)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +264,7 @@ impl SlotKey<Inventory, ItemImgs> for CraftSlot {
|
|||||||
fn amount(&self, source: &Inventory) -> Option<u32> {
|
fn amount(&self, source: &Inventory) -> Option<u32> {
|
||||||
self.invslot
|
self.invslot
|
||||||
.and_then(|invslot| source.get(invslot))
|
.and_then(|invslot| source.get(invslot))
|
||||||
.map(|item| self.required_amount.min(item.amount()))
|
.map(|item| item.amount())
|
||||||
.filter(|amount| *amount > 1)
|
.filter(|amount| *amount > 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ use common::{
|
|||||||
link::Is,
|
link::Is,
|
||||||
mounting::Mount,
|
mounting::Mount,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
|
recipe,
|
||||||
terrain::{Block, BlockKind},
|
terrain::{Block, BlockKind},
|
||||||
trade::TradeResult,
|
trade::TradeResult,
|
||||||
util::{Dir, Plane},
|
util::{Dir, Plane},
|
||||||
@ -1431,6 +1432,48 @@ impl PlayState for SessionState {
|
|||||||
craft_sprite.map(|(pos, _sprite)| pos),
|
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 } => {
|
HudEvent::SalvageItem { slot, salvage_pos } => {
|
||||||
self.client.borrow_mut().salvage_item(slot, salvage_pos);
|
self.client.borrow_mut().salvage_item(slot, salvage_pos);
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user