Avoid duplicate searches in the inventory for required items when

interacting with sprites and rustfmt decides to format a bunch of
stuff...

* Add PartialEq impls between ItemDefinitionId<'_> and
  ItemDefinitionIdOwned.
* Remove unused Serialize and Deserialize derives from
  ItemDefinitionId<'_>
* Add Inventory::get_slot_of_item_by_def_id which acts like
  Inventory::get_slot_of_item but accepts a ItemDefinitionIdOwned
  reference instead of an Item reference.
* Add some TODOs for some potential optimizations
* Rustfmt decided now was the time to format some random stuff I didn't
  touch. Maybe I fixed something it was getting stuck on???? But some
  files I didn't make any changes (although might have inadvertantly saved
  them when viewing in editor (with fmt on save)).
* InvSlotId passed to SpriteInteract character state instead of
  refinding the item in the inventory (if it moved we simply give up on
  the state as if the requirements weren't met). (overall in this change
  3 searches for the item in the inventory are reduced to a single one)
This commit is contained in:
Imbris 2023-03-10 01:33:20 -05:00
parent 93eab4791d
commit 0d8aa16d89
5 changed files with 93 additions and 46 deletions

View File

@ -462,7 +462,10 @@ impl ItemBase {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
// TODO: could this theorectically hold a ref to the actual components and
// lazily get their IDs for hash/partialeq/debug/to_owned/etc? (i.e. eliminating
// `Vec`s)
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ItemDefinitionId<'a> {
Simple(&'a str),
Modular {
@ -1291,6 +1294,38 @@ pub fn try_all_item_defs() -> Result<Vec<String>, Error> {
Ok(defs.ids().map(|id| id.to_string()).collect())
}
impl PartialEq<ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
fn eq(&self, other: &ItemDefinitionId<'_>) -> bool {
use ItemDefinitionId as DefId;
match self {
Self::Simple(simple) => {
matches!(other, DefId::Simple(other_simple) if simple == other_simple)
},
Self::Modular {
pseudo_base,
components,
} => matches!(
other,
DefId::Modular { pseudo_base: other_base, components: other_comps }
if pseudo_base == other_base && components == other_comps
),
Self::Compound {
simple_base,
components,
} => matches!(
other,
DefId::Compound { simple_base: other_base, components: other_comps }
if simple_base == other_base && components == other_comps
),
}
}
}
impl PartialEq<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
#[inline]
fn eq(&self, other: &ItemDefinitionIdOwned) -> bool { other == self }
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -408,6 +408,21 @@ impl Inventory {
.map(|(slot, _)| slot)
}
pub fn get_slot_of_item_by_def_id(
&self,
item_def_id: &item::ItemDefinitionIdOwned,
) -> Option<InvSlotId> {
self.slots_with_id()
.find(|&(_, it)| {
if let Some(it) = it {
it.item_definition_id() == *item_def_id
} else {
false
}
})
.map(|(slot, _)| slot)
}
/// Get content of a slot
pub fn get(&self, inv_slot_id: InvSlotId) -> Option<&Item> {
self.slot(inv_slot_id).and_then(Option::as_ref)

View File

@ -456,6 +456,7 @@ struct EqualitySet {
impl EqualitySet {
fn canonical<'a>(&'a self, item_name: &'a ItemDefinitionIdOwned) -> &'a ItemDefinitionIdOwned {
// TODO: use hashbrown Equivalent trait to avoid needing owned item def here
let canonical_itemname = self
.equivalence_class
.get(item_name)
@ -996,7 +997,7 @@ impl TradePricing {
result
}
fn get_materials_impl(&self, item: &ItemDefinitionId) -> Option<MaterialUse> {
fn get_materials_impl(&self, item: &ItemDefinitionId<'_>) -> Option<MaterialUse> {
self.price_lookup(&item.to_owned()).cloned()
}
@ -1012,7 +1013,7 @@ impl TradePricing {
}
#[must_use]
pub fn get_materials(item: &ItemDefinitionId) -> Option<MaterialUse> {
pub fn get_materials(item: &ItemDefinitionId<'_>) -> Option<MaterialUse> {
TRADE_PRICING.get_materials_impl(item)
}

View File

@ -1,8 +1,7 @@
use super::utils::*;
use crate::{
comp::{
character_state::OutputEvents,
item::{Item, ItemDefinitionIdOwned},
character_state::OutputEvents, inventory::slot::InvSlotId, item::ItemDefinitionIdOwned,
CharacterState, InventoryManip, StateUpdate,
},
event::{LocalEvent, ServerEvent},
@ -33,8 +32,13 @@ pub struct StaticData {
/// Was sneaking
pub was_sneak: bool,
/// The item required to interact with the sprite, if one was required
// If second field is true, item should be consumed on collection
pub required_item: Option<(ItemDefinitionIdOwned, bool)>,
///
/// The second field is the slot that the required item was in when this
/// state was created. If it isn't in this slot anymore the interaction will
/// fail.
///
/// If third field is true, item should be consumed on collection
pub required_item: Option<(ItemDefinitionIdOwned, InvSlotId, bool)>,
/// Miscellaneous information about the ability
pub ability_info: AbilityInfo,
}
@ -97,32 +101,20 @@ impl CharacterBehavior for Data {
}
} else {
// Create inventory manipulation event
let required_item =
self.static_data
.required_item
.as_ref()
.and_then(|(i, consume)| {
Some((
Item::new_from_item_definition_id(
i.as_ref(),
data.ability_map,
data.msm,
)
.ok()?,
*consume,
))
});
let has_required_item =
required_item.as_ref().map_or(true, |(item, _consume)| {
data.inventory.map_or(false, |inv| inv.contains(item))
let (has_required_item, inv_slot) = self
.static_data
.required_item
.as_ref()
.map_or((true, None), |&(ref item_def_id, slot, consume)| {
// Check that required item is still in expected slot
let has_item = data
.inventory
.and_then(|inv| inv.get(slot))
.map_or(false, |item| item.item_definition_id() == *item_def_id);
(has_item, has_item.then_some((slot, consume)))
});
if has_required_item {
let inv_slot = required_item.and_then(|(item, consume)| {
Some((
data.inventory.and_then(|inv| inv.get_slot_of_item(&item))?,
consume,
))
});
let inv_manip = InventoryManip::Collect {
sprite_pos: self.static_data.sprite_pos,
required_item: inv_slot,

View File

@ -7,7 +7,7 @@ use crate::{
character_state::OutputEvents,
controller::InventoryManip,
inventory::slot::{ArmorSlot, EquipSlot, Slot},
item::{armor::Friction, tool::AbilityContext, Hands, Item, ItemKind, ToolKind},
item::{armor::Friction, tool::AbilityContext, Hands, ItemKind, ToolKind},
quadruped_low, quadruped_medium, quadruped_small,
skills::{Skill, SwimSkill, SKILL_MODIFIERS},
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
@ -918,24 +918,28 @@ pub fn handle_manipulate_loadout(
UnlockKind::Requires(item) => Some((item, false)),
UnlockKind::Consumes(item) => Some((item, true)),
});
let has_required_items = required_item
.as_ref()
.and_then(|(i, _consume)| {
Item::new_from_item_definition_id(
i.as_ref(),
data.ability_map,
data.msm,
)
.ok()
})
.map_or(true, |i| {
data.inventory.map_or(false, |inv| inv.contains(&i))
});
// None: An required items exist but no available
// Some(None): No required items
// Some(Some(_)): Required items satisfied, contains info about them
let has_required_items = match required_item {
Some((item_id, consume)) => {
if let Some(slot) = data
.inventory
.and_then(|inv| inv.get_slot_of_item_by_def_id(&item_id))
{
Some(Some((item_id, slot, consume)))
} else {
None
}
},
None => Some(None),
};
// If path can be found between entity interacting with sprite and entity, start
// interaction with sprite
if not_blocked_by_terrain {
if has_required_items {
if let Some(required_item) = has_required_items {
// If the sprite is collectible, enter the sprite interaction character
// state TODO: Handle cases for sprite being
// interactible, but not collectible (none currently