mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Changed crafting to only consume items after checking that the crafting would be successful instead of consuming items first and reinserting on failure.
This commit is contained in:
parent
4b9e9c506b
commit
fbd742abdb
@ -998,7 +998,7 @@ impl Client {
|
|||||||
pub fn craft_recipe(
|
pub fn craft_recipe(
|
||||||
&mut self,
|
&mut self,
|
||||||
recipe: &str,
|
recipe: &str,
|
||||||
slots: Vec<InvSlotId>,
|
slots: Vec<(u32, InvSlotId)>,
|
||||||
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let (can_craft, required_sprite) = self.can_craft_recipe(recipe);
|
let (can_craft, required_sprite) = self.can_craft_recipe(recipe);
|
||||||
@ -1019,6 +1019,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the item in the given slot can be salvaged.
|
||||||
pub fn can_salvage_item(&self, slot: InvSlotId) -> bool {
|
pub fn can_salvage_item(&self, slot: InvSlotId) -> bool {
|
||||||
self.inventories()
|
self.inventories()
|
||||||
.get(self.entity())
|
.get(self.entity())
|
||||||
@ -1026,6 +1027,8 @@ impl Client {
|
|||||||
.map_or(false, |item| item.is_salvageable())
|
.map_or(false, |item| item.is_salvageable())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Salvage the item in the given inventory slot. `salvage_pos` should be
|
||||||
|
/// the location of a relevant crafting station within range of the player.
|
||||||
pub fn salvage_item(&mut self, slot: InvSlotId, salvage_pos: Vec3<i32>) -> bool {
|
pub fn salvage_item(&mut self, slot: InvSlotId, salvage_pos: Vec3<i32>) -> bool {
|
||||||
let is_salvageable = self.can_salvage_item(slot);
|
let is_salvageable = self.can_salvage_item(slot);
|
||||||
if is_salvageable {
|
if is_salvageable {
|
||||||
|
@ -94,7 +94,7 @@ impl From<InventoryEvent> for InventoryManip {
|
|||||||
pub enum CraftEvent {
|
pub enum CraftEvent {
|
||||||
Simple {
|
Simple {
|
||||||
recipe: String,
|
recipe: String,
|
||||||
slots: Vec<InvSlotId>,
|
slots: Vec<(u32, InvSlotId)>,
|
||||||
},
|
},
|
||||||
Salvage(InvSlotId),
|
Salvage(InvSlotId),
|
||||||
}
|
}
|
||||||
|
@ -171,38 +171,38 @@ impl Material {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn asset_identifier(&self) -> &'static str {
|
pub fn asset_identifier(&self) -> Option<&'static str> {
|
||||||
match self {
|
match self {
|
||||||
Material::Bronze => "common.items.mineral.ingot.bronze",
|
Material::Bronze => Some("common.items.mineral.ingot.bronze"),
|
||||||
Material::Iron => "common.items.mineral.ingot.iron",
|
Material::Iron => Some("common.items.mineral.ingot.iron"),
|
||||||
Material::Steel => "common.items.mineral.ingot.steel",
|
Material::Steel => Some("common.items.mineral.ingot.steel"),
|
||||||
Material::Cobalt => "common.items.mineral.ingot.cobalt",
|
Material::Cobalt => Some("common.items.mineral.ingot.cobalt"),
|
||||||
Material::Bloodsteel => "common.items.mineral.ingot.bloodsteel",
|
Material::Bloodsteel => Some("common.items.mineral.ingot.bloodsteel"),
|
||||||
Material::Orichalcum => "common.items.mineral.ingot.orichalcum",
|
Material::Orichalcum => Some("common.items.mineral.ingot.orichalcum"),
|
||||||
Material::Wood
|
Material::Wood
|
||||||
| Material::Bamboo
|
| Material::Bamboo
|
||||||
| Material::Hardwood
|
| Material::Hardwood
|
||||||
| Material::Ironwood
|
| Material::Ironwood
|
||||||
| Material::Frostwood
|
| Material::Frostwood
|
||||||
| Material::Eldwood => unimplemented!(),
|
| Material::Eldwood => None,
|
||||||
Material::Rock
|
Material::Rock
|
||||||
| Material::Granite
|
| Material::Granite
|
||||||
| Material::Bone
|
| Material::Bone
|
||||||
| Material::Basalt
|
| Material::Basalt
|
||||||
| Material::Obsidian
|
| Material::Obsidian
|
||||||
| Material::Velorite => unimplemented!(),
|
| Material::Velorite => None,
|
||||||
Material::Linen => "common.items.crafting_ing.cloth.linen",
|
Material::Linen => Some("common.items.crafting_ing.cloth.linen"),
|
||||||
Material::Wool => "common.items.crafting_ing.cloth.wool",
|
Material::Wool => Some("common.items.crafting_ing.cloth.wool"),
|
||||||
Material::Silk => "common.items.crafting_ing.cloth.silk",
|
Material::Silk => Some("common.items.crafting_ing.cloth.silk"),
|
||||||
Material::Lifecloth => "common.items.crafting_ing.cloth.lifecloth",
|
Material::Lifecloth => Some("common.items.crafting_ing.cloth.lifecloth"),
|
||||||
Material::Moonweave => "common.items.crafting_ing.cloth.moonweave",
|
Material::Moonweave => Some("common.items.crafting_ing.cloth.moonweave"),
|
||||||
Material::Sunsilk => "common.items.crafting_ing.cloth.sunsilk",
|
Material::Sunsilk => Some("common.items.crafting_ing.cloth.sunsilk"),
|
||||||
Material::Rawhide => "common.items.crafting_ing.leather.simple_leather",
|
Material::Rawhide => Some("common.items.crafting_ing.leather.simple_leather"),
|
||||||
Material::Leather => "common.items.crafting_ing.leather.thick_leather",
|
Material::Leather => Some("common.items.crafting_ing.leather.thick_leather"),
|
||||||
Material::Scale => "common.items.crafting_ing.hide.scales",
|
Material::Scale => Some("common.items.crafting_ing.hide.scales"),
|
||||||
Material::Carapace => "common.items.crafting_ing.hide.carapace",
|
Material::Carapace => Some("common.items.crafting_ing.hide.carapace"),
|
||||||
Material::Plate => "common.items.crafting_ing.hide.plate",
|
Material::Plate => Some("common.items.crafting_ing.hide.plate"),
|
||||||
Material::Dragonscale => "common.items.crafting_ing.hide.dragon_scale",
|
Material::Dragonscale => Some("common.items.crafting_ing.hide.dragon_scale"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -815,27 +815,7 @@ impl Item {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|material| material.asset_identifier())
|
.filter_map(|material| material.asset_identifier())
|
||||||
}
|
|
||||||
|
|
||||||
// Attempts to salvage an item by consuming it, returns the salvaged items if
|
|
||||||
// salvageable, else the original item
|
|
||||||
pub fn try_salvage(self) -> Result<Vec<Item>, Item> {
|
|
||||||
if !self.is_salvageable() {
|
|
||||||
return Err(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates one item for every salvage tag in the target item
|
|
||||||
let salvaged_items: Vec<_> = self
|
|
||||||
.salvage_output()
|
|
||||||
.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 name(&self) -> &str { &self.item_def.name }
|
||||||
|
@ -30,50 +30,65 @@ impl Recipe {
|
|||||||
pub fn craft_simple(
|
pub fn craft_simple(
|
||||||
&self,
|
&self,
|
||||||
inv: &mut Inventory,
|
inv: &mut Inventory,
|
||||||
slots: Vec<InvSlotId>,
|
// Vec tying an input to a slot
|
||||||
|
slots: Vec<(u32, InvSlotId)>,
|
||||||
ability_map: &AbilityMap,
|
ability_map: &AbilityMap,
|
||||||
msm: &MaterialStatManifest,
|
msm: &MaterialStatManifest,
|
||||||
) -> Result<Vec<Item>, Vec<(&RecipeInput, u32)>> {
|
) -> Result<Vec<Item>, Vec<(&RecipeInput, u32)>> {
|
||||||
let mut recipe_inputs = Vec::new();
|
let mut slot_claims = HashMap::new();
|
||||||
let mut unsatisfied_requirements = Vec::new();
|
let mut unsatisfied_requirements = Vec::new();
|
||||||
|
|
||||||
// Checks each input against a slot in the inventory. If the slot contains an
|
// Checks each input against slots in the inventory. If the slots contain an
|
||||||
// item that fulfills the need of the input, takes from the inventory up to the
|
// item that fulfills the need of the input, marks some of the item as claimed
|
||||||
// quantity needed for the crafting input. If the item either cannot be used, or
|
// up to quantity needed for the crafting input. If the item either
|
||||||
// there is insufficient quantity, adds input and number of materials needed to
|
// cannot be used, or there is insufficient quantity, adds input and
|
||||||
// unsatisfied requirements.
|
// number of materials needed to unsatisfied requirements.
|
||||||
self.inputs
|
self.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.for_each(|(i, (input, amount))| {
|
.for_each(|(i, (input, mut required))| {
|
||||||
let valid_input = if let Some(item) = slots.get(i).and_then(|slot| inv.get(*slot)) {
|
// Check used for recipes that have an input that is not consumed, e.g.
|
||||||
item.matches_recipe_input(input)
|
// craftsman hammer
|
||||||
} else {
|
let mut contains_any = false;
|
||||||
false
|
// Gets all slots provided for this input by the frontend
|
||||||
};
|
let input_slots = slots
|
||||||
|
.iter()
|
||||||
if let Some(slot) = slots.get(i) {
|
.filter_map(|(j, slot)| if i as u32 == *j { Some(slot) } else { None });
|
||||||
if !valid_input {
|
// Goes through each slot and marks some amount from each slot as claimed
|
||||||
unsatisfied_requirements.push((input, *amount));
|
for slot in input_slots {
|
||||||
} else {
|
// Checks that the item in the slot can be used for the input
|
||||||
for taken in 0..*amount {
|
if let Some(item) = inv
|
||||||
if let Some(item) = inv.take(*slot, ability_map, msm) {
|
.get(*slot)
|
||||||
recipe_inputs.push(item);
|
.filter(|item| item.matches_recipe_input(input))
|
||||||
} else {
|
{
|
||||||
unsatisfied_requirements.push((input, *amount - taken));
|
// Gets the number of items claimed from the slot, or sets to 0 if slot has
|
||||||
break;
|
// 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;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
unsatisfied_requirements.push((input, *amount));
|
// 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));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// If there are no unsatisfied requirements, create the items produced by the
|
// If there are no unsatisfied requirements, create the items produced by the
|
||||||
// recipe in the necessary quantity, else insert the ingredients back into the
|
// recipe in the necessary quantity and remove the items that the recipe
|
||||||
// inventory
|
// consumes
|
||||||
if unsatisfied_requirements.is_empty() {
|
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 (item_def, quantity) = &self.output;
|
let (item_def, quantity) = &self.output;
|
||||||
let crafted_item = Item::new_from_item_def(Arc::clone(item_def), &[], ability_map, msm);
|
let crafted_item = Item::new_from_item_def(Arc::clone(item_def), &[], ability_map, msm);
|
||||||
let mut crafted_items = Vec::with_capacity(*quantity as usize);
|
let mut crafted_items = Vec::with_capacity(*quantity as usize);
|
||||||
@ -82,10 +97,6 @@ impl Recipe {
|
|||||||
}
|
}
|
||||||
Ok(crafted_items)
|
Ok(crafted_items)
|
||||||
} else {
|
} else {
|
||||||
for item in recipe_inputs {
|
|
||||||
inv.push(item)
|
|
||||||
.expect("Item was in inventory before craft attempt");
|
|
||||||
}
|
|
||||||
Err(unsatisfied_requirements)
|
Err(unsatisfied_requirements)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,26 +115,28 @@ impl Recipe {
|
|||||||
pub fn inventory_contains_ingredients<'a>(
|
pub fn inventory_contains_ingredients<'a>(
|
||||||
&self,
|
&self,
|
||||||
inv: &'a Inventory,
|
inv: &'a Inventory,
|
||||||
) -> Result<Vec<InvSlotId>, Vec<(&RecipeInput, u32)>> {
|
) -> 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();
|
let mut slot_claims = HashMap::<InvSlotId, u32>::new();
|
||||||
// Important to be a vec and to remain separate from slot_claims as it must
|
// Important to be a vec and to remain separate from slot_claims as it must
|
||||||
// remain ordered, unlike the hashmap
|
// remain ordered, unlike the hashmap
|
||||||
let mut slots = Vec::<InvSlotId>::new();
|
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();
|
let mut missing = Vec::<(&RecipeInput, u32)>::new();
|
||||||
|
|
||||||
for (input, mut needed) in self.inputs() {
|
for (i, (input, mut needed)) in self.inputs().enumerate() {
|
||||||
let mut contains_any = false;
|
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() {
|
for (inv_slot_id, slot) in inv.slots_with_id() {
|
||||||
if let Some(item) = slot
|
if let Some(item) = slot
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|item| item.matches_recipe_input(&*input))
|
.filter(|item| item.matches_recipe_input(&*input))
|
||||||
{
|
{
|
||||||
let claim = slot_claims.entry(inv_slot_id).or_insert(0);
|
let claim = slot_claims.entry(inv_slot_id).or_insert(0);
|
||||||
slots.push(inv_slot_id);
|
slots.push((i as u32, inv_slot_id));
|
||||||
// FIXME: Fishy, looks like it can underflow before min which can trigger an
|
let can_claim = (item.amount().saturating_sub(*claim)).min(needed);
|
||||||
// overflow check.
|
|
||||||
let can_claim = (item.amount() - *claim).min(needed);
|
|
||||||
*claim += can_claim;
|
*claim += can_claim;
|
||||||
needed -= can_claim;
|
needed -= can_claim;
|
||||||
contains_any = true;
|
contains_any = true;
|
||||||
@ -154,16 +167,24 @@ pub fn try_salvage(
|
|||||||
msm: &MaterialStatManifest,
|
msm: &MaterialStatManifest,
|
||||||
) -> Result<Vec<Item>, SalvageError> {
|
) -> Result<Vec<Item>, SalvageError> {
|
||||||
if inv.get(slot).map_or(false, |item| item.is_salvageable()) {
|
if inv.get(slot).map_or(false, |item| item.is_salvageable()) {
|
||||||
let salvage_item = inv
|
let salvage_item = inv.get(slot).expect("Expected item to exist in inventory");
|
||||||
.take(slot, ability_map, msm)
|
let salvage_output: Vec<_> = salvage_item
|
||||||
.expect("Expected item to exist in inventory");
|
.salvage_output()
|
||||||
match salvage_item.try_salvage() {
|
.map(|asset| Item::new_from_asset_expect(asset))
|
||||||
Ok(items) => Ok(items),
|
.collect();
|
||||||
Err(item) => {
|
if salvage_output.is_empty() {
|
||||||
inv.push(item)
|
// If no output items, assume salvaging was a failure
|
||||||
.expect("Item taken from inventory just before");
|
// TODO: If we ever change salvaging to have a percent chance, remove the check
|
||||||
Err(SalvageError::NotSalvageable)
|
// of outputs being empty (requires assets to exist for rock and wood materials
|
||||||
},
|
// so that salvaging doesn't silently fail)
|
||||||
|
Err(SalvageError::NotSalvageable)
|
||||||
|
} else {
|
||||||
|
// Remove item that is being salvaged
|
||||||
|
let _ = inv
|
||||||
|
.take(slot, ability_map, msm)
|
||||||
|
.expect("Expected item to exist in inventory");
|
||||||
|
// Return the salvaging output
|
||||||
|
Ok(salvage_output)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(SalvageError::NotSalvageable)
|
Err(SalvageError::NotSalvageable)
|
||||||
|
Loading…
Reference in New Issue
Block a user