Backend stuff for salvaging.

This commit is contained in:
Sam 2021-10-05 17:27:11 -04:00
parent 05c2bb5e35
commit ed5cf8ebf9
7 changed files with 232 additions and 80 deletions

View File

@ -1927,15 +1927,15 @@
craft_sprite: Some(Anvil),
is_recycling: false,
),
"linen": (
output: ("common.items.crafting_ing.cloth.linen", 1),
inputs: [
(Tag(Material(Linen)), 1),
(Item("common.items.crafting_tools.sewing_set"), 0),
],
craft_sprite: None,
is_recycling: true,
),
// "linen": (
// output: ("common.items.crafting_ing.cloth.linen", 1),
// inputs: [
// (Tag(Material(Linen)), 1),
// (Item("common.items.crafting_tools.sewing_set"), 0),
// ],
// craft_sprite: None,
// is_recycling: true,
// ),
"wool": (
output: ("common.items.crafting_ing.cloth.wool", 1),
inputs: [

View File

@ -23,6 +23,7 @@ use common::{
comp::{
self,
chat::{KillSource, KillType},
controller::CraftEvent,
group,
invite::{InviteKind, InviteResponse},
skills::Skill,
@ -1004,7 +1005,7 @@ impl Client {
if can_craft && has_sprite {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
InventoryEvent::CraftRecipe {
recipe: recipe.to_string(),
craft_event: CraftEvent::Simple(recipe.to_string()),
craft_sprite: craft_sprite.map(|(pos, _)| pos),
},
)));

View File

@ -23,7 +23,7 @@ pub enum InventoryEvent {
SplitDrop(InvSlotId),
Sort,
CraftRecipe {
recipe: String,
craft_event: CraftEvent,
craft_sprite: Option<Vec3<i32>>,
},
}
@ -48,7 +48,7 @@ pub enum InventoryManip {
SplitDrop(Slot),
Sort,
CraftRecipe {
recipe: String,
craft_event: CraftEvent,
craft_sprite: Option<Vec3<i32>>,
},
SwapEquippedWeapons,
@ -80,16 +80,22 @@ impl From<InventoryEvent> for InventoryManip {
InventoryEvent::SplitDrop(inv) => Self::SplitDrop(Slot::Inventory(inv)),
InventoryEvent::Sort => Self::Sort,
InventoryEvent::CraftRecipe {
recipe,
craft_event,
craft_sprite,
} => Self::CraftRecipe {
recipe,
craft_event,
craft_sprite,
},
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum CraftEvent {
Simple(String),
Salvage(InvSlotId),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupManip {
Leave,

View File

@ -170,6 +170,41 @@ impl Material {
| Material::Dragonscale => MaterialKind::Hide,
}
}
pub fn asset_identifier(&self) -> &'static str {
match self {
Material::Bronze => "common.items.mineral.ingot.bronze",
Material::Iron => "common.items.mineral.ingot.iron",
Material::Steel => "common.items.mineral.ingot.steel",
Material::Cobalt => "common.items.mineral.ingot.cobalt",
Material::Bloodsteel => "common.items.mineral.ingot.bloodsteel",
Material::Orichalcum => "common.items.mineral.ingot.orichalcum",
Material::Wood
| Material::Bamboo
| Material::Hardwood
| Material::Ironwood
| Material::Frostwood
| Material::Eldwood => unreachable!(),
Material::Rock
| Material::Granite
| Material::Bone
| Material::Basalt
| Material::Obsidian
| Material::Velorite => unreachable!(),
Material::Linen => "common.items.crafting_ing.cloth.linen",
Material::Wool => "common.items.crafting_ing.cloth.wool",
Material::Silk => "common.items.crafting_ing.cloth.silk",
Material::Lifecloth => "common.items.crafting_ing.cloth.lifecloth",
Material::Moonweave => "common.items.crafting_ing.cloth.moonweave",
Material::Sunsilk => "common.items.crafting_ing.cloth.sunsilk",
Material::Rawhide => "common.items.crafting_ing.leather.simple_leather",
Material::Leather => "common.items.crafting_ing.leather.thick_leather",
Material::Scale => "common.items.crafting_ing.leather.scales",
Material::Carapace => "common.items.crafting_ing.leather.carapace",
Material::Plate => "common.items.crafting_ing.leather.plate",
Material::Dragonscale => "common.items.crafting_ing.leather.dragon_scale",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -202,6 +237,7 @@ pub enum ItemTag {
CraftingTool, // Pickaxe, Craftsman-Hammer, Sewing-Set
Utility,
Bag,
SalvageInto(Material),
}
impl TagExampleInfo for ItemTag {
@ -219,6 +255,7 @@ impl TagExampleInfo for ItemTag {
ItemTag::CraftingTool => "tool",
ItemTag::Utility => "utility",
ItemTag::Bag => "bag",
ItemTag::SalvageInto(_) => "salvage",
}
}
@ -237,6 +274,7 @@ impl TagExampleInfo for ItemTag {
ItemTag::CraftingTool => "common.items.tag_examples.placeholder",
ItemTag::Utility => "common.items.tag_examples.placeholder",
ItemTag::Bag => "common.items.tag_examples.placeholder",
ItemTag::SalvageInto(_) => "common.items.tag_examples.placeholder",
}
}
}
@ -759,6 +797,43 @@ impl Item {
}
}
pub fn is_salvageable(&self) -> bool {
self.item_def
.tags
.iter()
.any(|tag| matches!(tag, ItemTag::SalvageInto(_)))
}
// Attempts to salvage an item, returning the salvaged items if salvageable,
// else the original item is not Theoretically supports returning multiple
// items, only returns one per tag in the item for now
pub fn try_salvage(self) -> Result<Vec<Item>, Item> {
if !self.is_salvageable() {
return Err(self);
}
let salvaged_items: Vec<_> = self
.item_def
.tags
.iter()
.filter_map(|tag| {
if let ItemTag::SalvageInto(material) = tag {
Some(material)
} else {
None
}
})
.map(|material| material.asset_identifier())
.map(|asset| Item::new_from_asset_expect(asset))
.collect();
if salvaged_items.is_empty() {
Err(self)
} else {
Ok(salvaged_items)
}
}
pub fn name(&self) -> &str { &self.item_def.name }
pub fn description(&self) -> &str { &self.item_def.description }

View File

@ -13,7 +13,7 @@ pub mod character_state;
#[cfg(not(target_arch = "wasm32"))] pub mod combo;
pub mod compass;
#[cfg(not(target_arch = "wasm32"))]
mod controller;
pub mod controller;
#[cfg(not(target_arch = "wasm32"))]
pub mod dialogue;
#[cfg(not(target_arch = "wasm32"))] mod energy;

View File

@ -1,6 +1,7 @@
use crate::{
assets::{self, AssetExt, AssetHandle},
comp::{
inventory::slot::InvSlotId,
item::{modular, tool::AbilityMap, ItemDef, ItemTag, MaterialStatManifest},
Inventory, Item,
},
@ -32,7 +33,7 @@ impl Recipe {
inv: &mut Inventory,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Result<Option<(Item, u32)>, Vec<(&RecipeInput, u32)>> {
) -> Result<(Item, u32), Vec<(&RecipeInput, u32)>> {
// Get ingredient cells from inventory,
let mut components = Vec::new();
@ -47,15 +48,18 @@ impl Recipe {
})
});
for i in 0..self.output.1 {
let crafted_item =
Item::new_from_item_def(Arc::clone(&self.output.0), &components, ability_map, msm);
if let Err(item) = inv.push(crafted_item) {
return Ok(Some((item, self.output.1 - i)));
}
}
let crafted_item =
Item::new_from_item_def(Arc::clone(&self.output.0), &components, ability_map, msm);
Ok(None)
Ok((crafted_item, self.output.1))
// for i in 0..self.output.1 {
// if let Err(item) = inv.push(crafted_item) {
// return Ok(Some((item, self.output.1 - i)));
// }
// }
// Ok(None)
}
pub fn inputs(&self) -> impl ExactSizeIterator<Item = (&RecipeInput, u32)> {
@ -65,6 +69,33 @@ impl Recipe {
}
}
pub enum SalvageError {
NotSalvageable,
}
pub fn try_salvage(
inv: &mut Inventory,
slot: InvSlotId,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Result<Vec<Item>, SalvageError> {
if inv.get(slot).map_or(false, |item| item.is_salvageable()) {
let salvage_item = inv
.take(slot, ability_map, msm)
.expect("Expected item to exist in inventory");
match salvage_item.try_salvage() {
Ok(items) => Ok(items),
Err(item) => {
inv.push(item)
.expect("Item taken from inventory just before");
Err(SalvageError::NotSalvageable)
},
}
} else {
Err(SalvageError::NotSalvageable)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RecipeBook {
recipes: HashMap<String, Recipe>,

View File

@ -11,7 +11,7 @@ use common::{
slot::{self, Slot},
},
consts::MAX_PICKUP_RANGE,
recipe::default_recipe_book,
recipe::{self, default_recipe_book},
trade::Trades,
uid::Uid,
util::find_dist::{self, FindDist},
@ -563,53 +563,91 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
drop(inventories);
},
comp::InventoryManip::CraftRecipe {
recipe,
craft_event,
craft_sprite,
} => {
use comp::controller::CraftEvent;
let recipe_book = default_recipe_book().read();
let craft_result = recipe_book
.get(&recipe)
.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
let ability_map = &state.ecs().read_resource::<AbilityMap>();
let msm = state.ecs().read_resource::<MaterialStatManifest>();
let crafted_items = match craft_event {
CraftEvent::Simple(recipe) => recipe_book
.get(&recipe)
.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.perform(
&mut inventory,
&state.ecs().read_resource::<AbilityMap>(),
&state.ecs().read_resource::<item::MaterialStatManifest>(),
)
.ok()
})
.map(|(crafted_item, amount)| {
let mut crafted_items = Vec::with_capacity(amount as usize);
for _ in 0..amount {
crafted_items.push(crafted_item.duplicate(ability_map, &msm));
}
crafted_items
}),
CraftEvent::Salvage(slot) => {
recipe::try_salvage(&mut inventory, slot, ability_map, &msm).ok()
},
};
// Attempt to insert items into inventory, dropping them if there is not enough
// space
let items_were_crafted = if let Some(crafted_items) = crafted_items {
for item in crafted_items {
if let Err(item) = inventory.push(item) {
dropped_items.push((
state
.read_component_copied::<comp::Pos>(entity)
.unwrap_or_default(),
state
.read_component_copied::<comp::Ori>(entity)
.unwrap_or_default(),
item.duplicate(ability_map, &msm),
));
}
})
.and_then(|r| {
r.perform(
&mut inventory,
&state.ecs().read_resource::<AbilityMap>(),
&state.ecs().read_resource::<item::MaterialStatManifest>(),
)
.ok()
});
}
true
} else {
false
};
drop(inventories);
// FIXME: We should really require the drop and write to be atomic!
if craft_result.is_some() {
if items_were_crafted {
let _ = state.ecs().write_storage().insert(
entity,
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Craft),
@ -617,21 +655,22 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
}
// Drop the item if there wasn't enough space
if let Some(Some((item, amount))) = craft_result {
let ability_map = &state.ecs().read_resource::<AbilityMap>();
let msm = state.ecs().read_resource::<MaterialStatManifest>();
for _ in 0..amount {
dropped_items.push((
state
.read_component_copied::<comp::Pos>(entity)
.unwrap_or_default(),
state
.read_component_copied::<comp::Ori>(entity)
.unwrap_or_default(),
item.duplicate(ability_map, &msm),
));
}
}
// if let Some(Some((item, amount))) = craft_result {
// let ability_map = &state.ecs().read_resource::<AbilityMap>();
// let msm =
// state.ecs().read_resource::<MaterialStatManifest>();
// for _ in 0..amount {
// dropped_items.push((
// state
// .read_component_copied::<comp::Pos>(entity)
// .unwrap_or_default(),
// state
// .read_component_copied::<comp::Ori>(entity)
// .unwrap_or_default(),
// item.duplicate(ability_map, &msm),
// ));
// }
// }
},
comp::InventoryManip::Sort => {
inventory.sort();