mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/inventory-overflow' into 'master'
Inventory Overflow Slots for Persistence See merge request veloren/veloren!4021
This commit is contained in:
commit
aef9d27f64
@ -1119,6 +1119,13 @@ impl Client {
|
||||
|
||||
pub fn swap_slots(&mut self, a: Slot, b: Slot) {
|
||||
match (a, b) {
|
||||
(Slot::Overflow(o), Slot::Inventory(inv))
|
||||
| (Slot::Inventory(inv), Slot::Overflow(o)) => {
|
||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
|
||||
InventoryEvent::OverflowMove(o, inv),
|
||||
)));
|
||||
},
|
||||
(Slot::Overflow(_), _) | (_, Slot::Overflow(_)) => {},
|
||||
(Slot::Equip(equip), slot) | (slot, Slot::Equip(equip)) => self.control_action(
|
||||
ControlAction::InventoryAction(InventoryAction::Swap(equip, slot)),
|
||||
),
|
||||
@ -1138,6 +1145,9 @@ impl Client {
|
||||
Slot::Inventory(inv) => self.send_msg(ClientGeneral::ControlEvent(
|
||||
ControlEvent::InventoryEvent(InventoryEvent::Drop(inv)),
|
||||
)),
|
||||
Slot::Overflow(o) => self.send_msg(ClientGeneral::ControlEvent(
|
||||
ControlEvent::InventoryEvent(InventoryEvent::OverflowDrop(o)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1165,6 +1175,7 @@ impl Client {
|
||||
|
||||
pub fn split_swap_slots(&mut self, a: Slot, b: Slot) {
|
||||
match (a, b) {
|
||||
(Slot::Overflow(_), _) | (_, Slot::Overflow(_)) => {},
|
||||
(Slot::Equip(equip), slot) | (slot, Slot::Equip(equip)) => self.control_action(
|
||||
ControlAction::InventoryAction(InventoryAction::Swap(equip, slot)),
|
||||
),
|
||||
@ -1184,6 +1195,9 @@ impl Client {
|
||||
Slot::Inventory(inv) => self.send_msg(ClientGeneral::ControlEvent(
|
||||
ControlEvent::InventoryEvent(InventoryEvent::SplitDrop(inv)),
|
||||
)),
|
||||
Slot::Overflow(o) => self.send_msg(ClientGeneral::ControlEvent(
|
||||
ControlEvent::InventoryEvent(InventoryEvent::OverflowSplitDrop(o)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1393,6 +1407,7 @@ impl Client {
|
||||
if let Some(item) = match item {
|
||||
Slot::Equip(equip_slot) => inv.equipped(equip_slot),
|
||||
Slot::Inventory(invslot) => inv.get(invslot),
|
||||
Slot::Overflow(_) => None,
|
||||
} {
|
||||
item.has_durability()
|
||||
} else {
|
||||
|
@ -31,6 +31,9 @@ pub enum InventoryEvent {
|
||||
craft_event: CraftEvent,
|
||||
craft_sprite: Option<VolumePos>,
|
||||
},
|
||||
OverflowMove(usize, InvSlotId),
|
||||
OverflowDrop(usize),
|
||||
OverflowSplitDrop(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@ -86,6 +89,11 @@ impl From<InventoryEvent> for InventoryManip {
|
||||
craft_event,
|
||||
craft_sprite,
|
||||
},
|
||||
InventoryEvent::OverflowMove(o, inv) => {
|
||||
Self::Swap(Slot::Overflow(o), Slot::Inventory(inv))
|
||||
},
|
||||
InventoryEvent::OverflowDrop(o) => Self::Drop(Slot::Overflow(o)),
|
||||
InventoryEvent::OverflowSplitDrop(o) => Self::SplitDrop(Slot::Overflow(o)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1439,6 +1439,30 @@ impl Item {
|
||||
self.update_item_state(ability_map, msm);
|
||||
}
|
||||
|
||||
/// If an item is stackable and has an amount greater than 1, creates a new
|
||||
/// item with half the amount (rounded down), and decreases the amount of
|
||||
/// the original item by the same quantity.
|
||||
#[must_use = "Returned items will be lost if not used"]
|
||||
pub fn take_half(
|
||||
&mut self,
|
||||
ability_map: &AbilityMap,
|
||||
msm: &MaterialStatManifest,
|
||||
) -> Option<Item> {
|
||||
if self.is_stackable() && self.amount() > 1 {
|
||||
let mut return_item = self.duplicate(ability_map, msm);
|
||||
let returning_amount = self.amount() / 2;
|
||||
self.decrease_amount(returning_amount).ok()?;
|
||||
return_item.set_amount(returning_amount).expect(
|
||||
"return_item.amount() = self.amount() / 2 < self.amount() (since self.amount() ≥ \
|
||||
1) ≤ self.max_amount() = return_item.max_amount(), since return_item is a \
|
||||
duplicate of item",
|
||||
);
|
||||
Some(return_item)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn create_test_item_from_kind(kind: ItemKind) -> Self {
|
||||
let ability_map = &AbilityMap::load().read();
|
||||
|
@ -45,6 +45,11 @@ pub struct Inventory {
|
||||
/// The "built-in" slots belonging to the inventory itself, all other slots
|
||||
/// are provided by equipped items
|
||||
slots: Vec<InvSlot>,
|
||||
/// For when slot amounts are rebalanced or the inventory otherwise does not
|
||||
/// have enough space to hold all the items after loading from database.
|
||||
/// These slots are "remove-only" meaning that during normal gameplay items
|
||||
/// can only be removed from these slots and never entered.
|
||||
overflow_items: Vec<Item>,
|
||||
}
|
||||
|
||||
/// Errors which the methods on `Inventory` produce
|
||||
@ -55,6 +60,14 @@ pub enum Error {
|
||||
Full(Vec<Item>),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn returned_items(self) -> impl Iterator<Item = Item> {
|
||||
match self {
|
||||
Error::Full(items) => items.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum InventorySortOrder {
|
||||
Name,
|
||||
@ -115,6 +128,7 @@ impl Inventory {
|
||||
next_sort_order: InventorySortOrder::Name,
|
||||
loadout,
|
||||
slots: vec![None; DEFAULT_INVENTORY_SLOTS],
|
||||
overflow_items: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,10 +137,11 @@ impl Inventory {
|
||||
next_sort_order: InventorySortOrder::Name,
|
||||
loadout,
|
||||
slots: vec![None; 1],
|
||||
overflow_items: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Total number of slots in in the inventory.
|
||||
/// Total number of slots in the inventory.
|
||||
pub fn capacity(&self) -> usize { self.slots().count() }
|
||||
|
||||
/// An iterator of all inventory slots
|
||||
@ -136,6 +151,9 @@ impl Inventory {
|
||||
.chain(self.loadout.inv_slots_with_id().map(|(_, slot)| slot))
|
||||
}
|
||||
|
||||
/// An iterator of all overflow slots in the inventory
|
||||
pub fn overflow_items(&self) -> impl Iterator<Item = &Item> { self.overflow_items.iter() }
|
||||
|
||||
/// A mutable iterator of all inventory slots
|
||||
fn slots_mut(&mut self) -> impl Iterator<Item = &mut InvSlot> {
|
||||
self.slots.iter_mut().chain(self.loadout.inv_slots_mut())
|
||||
@ -451,6 +469,20 @@ impl Inventory {
|
||||
self.slot(inv_slot_id).and_then(Option::as_ref)
|
||||
}
|
||||
|
||||
/// Get content of an overflow slot
|
||||
pub fn get_overflow(&self, overflow: usize) -> Option<&Item> {
|
||||
self.overflow_items.get(overflow)
|
||||
}
|
||||
|
||||
/// Get content of any kind of slot
|
||||
pub fn get_slot(&self, slot: Slot) -> Option<&Item> {
|
||||
match slot {
|
||||
Slot::Inventory(inv_slot) => self.get(inv_slot),
|
||||
Slot::Equip(equip) => self.equipped(equip),
|
||||
Slot::Overflow(overflow) => self.get_overflow(overflow),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get item from inventory
|
||||
pub fn get_by_hash(&self, item_hash: u64) -> Option<&Item> {
|
||||
self.slots().flatten().find(|i| i.item_hash() == item_hash)
|
||||
@ -506,11 +538,39 @@ impl Inventory {
|
||||
*self.slot_mut(b).unwrap() = slot_a;
|
||||
}
|
||||
|
||||
/// Moves an item from an overflow slot to an inventory slot
|
||||
pub fn move_overflow_item(&mut self, overflow: usize, inv_slot: InvSlotId) {
|
||||
match self.slot(inv_slot) {
|
||||
Some(Some(_)) => {
|
||||
warn!("Attempted to move from overflow slot to a filled inventory slot");
|
||||
return;
|
||||
},
|
||||
None => {
|
||||
warn!("Attempted to move from overflow slot to a non-existent inventory slot");
|
||||
return;
|
||||
},
|
||||
Some(None) => {},
|
||||
};
|
||||
|
||||
let item = self.overflow_items.remove(overflow);
|
||||
*self.slot_mut(inv_slot).unwrap() = Some(item);
|
||||
}
|
||||
|
||||
/// Remove an item from the slot
|
||||
pub fn remove(&mut self, inv_slot_id: InvSlotId) -> Option<Item> {
|
||||
self.slot_mut(inv_slot_id).and_then(|item| item.take())
|
||||
}
|
||||
|
||||
/// Remove an item from an overflow slot
|
||||
#[must_use = "Returned items will be lost if not used"]
|
||||
pub fn overflow_remove(&mut self, overflow_slot: usize) -> Option<Item> {
|
||||
if overflow_slot < self.overflow_items.len() {
|
||||
Some(self.overflow_items.remove(overflow_slot))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove just one item from the slot
|
||||
pub fn take(
|
||||
&mut self,
|
||||
@ -536,6 +596,7 @@ impl Inventory {
|
||||
}
|
||||
|
||||
/// Takes half of the items from a slot in the inventory
|
||||
#[must_use = "Returned items will be lost if not used"]
|
||||
pub fn take_half(
|
||||
&mut self,
|
||||
inv_slot_id: InvSlotId,
|
||||
@ -543,19 +604,24 @@ impl Inventory {
|
||||
msm: &MaterialStatManifest,
|
||||
) -> Option<Item> {
|
||||
if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
|
||||
if item.is_stackable() && item.amount() > 1 {
|
||||
let mut return_item = item.duplicate(ability_map, msm);
|
||||
let returning_amount = item.amount() / 2;
|
||||
item.decrease_amount(returning_amount).ok()?;
|
||||
return_item.set_amount(returning_amount).expect(
|
||||
"return_item.amount() = item.amount() / 2 < item.amount() (since \
|
||||
item.amount() ≥ 1) ≤ item.max_amount() = return_item.max_amount(), since \
|
||||
return_item is a duplicate of item",
|
||||
);
|
||||
Some(return_item)
|
||||
} else {
|
||||
self.remove(inv_slot_id)
|
||||
}
|
||||
item.take_half(ability_map, msm)
|
||||
.or_else(|| self.remove(inv_slot_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes half of the items from an overflow slot
|
||||
#[must_use = "Returned items will be lost if not used"]
|
||||
pub fn overflow_take_half(
|
||||
&mut self,
|
||||
overflow_slot: usize,
|
||||
ability_map: &AbilityMap,
|
||||
msm: &MaterialStatManifest,
|
||||
) -> Option<Item> {
|
||||
if let Some(item) = self.overflow_items.get_mut(overflow_slot) {
|
||||
item.take_half(ability_map, msm)
|
||||
.or_else(|| self.overflow_remove(overflow_slot))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -763,6 +829,15 @@ impl Inventory {
|
||||
self.loadout.swap_slots(slot_a, slot_b, time);
|
||||
Vec::new()
|
||||
},
|
||||
(Slot::Overflow(overflow_slot), Slot::Inventory(inv_slot))
|
||||
| (Slot::Inventory(inv_slot), Slot::Overflow(overflow_slot)) => {
|
||||
self.move_overflow_item(overflow_slot, inv_slot);
|
||||
Vec::new()
|
||||
},
|
||||
// Items from overflow slots cannot be equipped until moved into a real inventory slot
|
||||
(Slot::Overflow(_), Slot::Equip(_)) | (Slot::Equip(_), Slot::Overflow(_)) => Vec::new(),
|
||||
// Items cannot be moved between overflow slots
|
||||
(Slot::Overflow(_), Slot::Overflow(_)) => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -910,6 +985,9 @@ impl Inventory {
|
||||
item.update_item_state(ability_map, msm);
|
||||
}
|
||||
});
|
||||
self.overflow_items
|
||||
.iter_mut()
|
||||
.for_each(|item| item.update_item_state(ability_map, msm));
|
||||
}
|
||||
|
||||
/// Increments durability lost for all valid items equipped in loadout and
|
||||
@ -955,8 +1033,17 @@ impl Inventory {
|
||||
self.loadout
|
||||
.repair_item_at_slot(equip_slot, ability_map, msm);
|
||||
},
|
||||
// Items in overflow slots cannot be repaired until they are moved to a real slot
|
||||
Slot::Overflow(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// When loading a character from the persistence system, pushes any items
|
||||
/// to overflow_items that were not able to be loaded into or pushed to the
|
||||
/// inventory
|
||||
pub fn persistence_push_overflow_items<I: Iterator<Item = Item>>(&mut self, overflow_items: I) {
|
||||
self.overflow_items.extend(overflow_items);
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Inventory {
|
||||
|
@ -15,6 +15,7 @@ pub enum SlotError {
|
||||
pub enum Slot {
|
||||
Inventory(InvSlotId),
|
||||
Equip(EquipSlot),
|
||||
Overflow(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@ -106,15 +107,6 @@ pub enum ArmorSlot {
|
||||
Bag4,
|
||||
}
|
||||
|
||||
impl Slot {
|
||||
pub fn can_hold(self, item_kind: &ItemKind) -> bool {
|
||||
match (self, item_kind) {
|
||||
(Self::Inventory(_), _) => true,
|
||||
(Self::Equip(slot), item_kind) => slot.can_hold(item_kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EquipSlot {
|
||||
pub fn can_hold(self, item_kind: &ItemKind) -> bool {
|
||||
match (self, item_kind) {
|
||||
|
@ -23,6 +23,7 @@ fn push_full() {
|
||||
.map(|a| Some(a.duplicate(ability_map, msm)))
|
||||
.collect(),
|
||||
loadout: LoadoutBuilder::empty().build(),
|
||||
overflow_items: vec![],
|
||||
};
|
||||
assert_eq!(
|
||||
inv.push(TEST_ITEMS[0].duplicate(ability_map, msm))
|
||||
@ -43,6 +44,7 @@ fn push_all_full() {
|
||||
.map(|a| Some(a.duplicate(ability_map, msm)))
|
||||
.collect(),
|
||||
loadout: LoadoutBuilder::empty().build(),
|
||||
overflow_items: vec![],
|
||||
};
|
||||
let Error::Full(leftovers) = inv
|
||||
.push_all(
|
||||
@ -73,6 +75,7 @@ fn push_unique_all_full() {
|
||||
.map(|a| Some(a.duplicate(ability_map, msm)))
|
||||
.collect(),
|
||||
loadout: LoadoutBuilder::empty().build(),
|
||||
overflow_items: vec![],
|
||||
};
|
||||
inv.push_all_unique(
|
||||
TEST_ITEMS
|
||||
@ -92,6 +95,7 @@ fn push_all_empty() {
|
||||
next_sort_order: InventorySortOrder::Name,
|
||||
slots: vec![None, None],
|
||||
loadout: LoadoutBuilder::empty().build(),
|
||||
overflow_items: vec![],
|
||||
};
|
||||
inv.push_all(
|
||||
TEST_ITEMS
|
||||
@ -111,6 +115,7 @@ fn push_all_unique_empty() {
|
||||
next_sort_order: InventorySortOrder::Name,
|
||||
slots: vec![None, None],
|
||||
loadout: LoadoutBuilder::empty().build(),
|
||||
overflow_items: vec![],
|
||||
};
|
||||
inv.push_all_unique(
|
||||
TEST_ITEMS
|
||||
|
@ -1030,6 +1030,8 @@ impl RepairRecipeBook {
|
||||
if let Some(item) = match item {
|
||||
Slot::Equip(slot) => inv.equipped(slot),
|
||||
Slot::Inventory(slot) => inv.get(slot),
|
||||
// Items in overflow slots cannot be repaired until item is moved to a real slot
|
||||
Slot::Overflow(_) => None,
|
||||
} {
|
||||
if let Some(repair_recipe) = self.repair_recipe(item) {
|
||||
repair_recipe
|
||||
|
@ -1141,6 +1141,9 @@ pub fn handle_manipulate_loadout(
|
||||
let inv_manip = InventoryManip::Use(slot);
|
||||
output_events.emit_server(ServerEvent::InventoryManip(data.entity, inv_manip));
|
||||
},
|
||||
InventoryAction::Use(Slot::Overflow(_)) => {
|
||||
// Items in overflow slots cannot be used until moved to a real slot
|
||||
},
|
||||
InventoryAction::ToggleSpriteLight(pos, enable) => {
|
||||
if matches!(pos.kind, Volume::Terrain) {
|
||||
let sprite_interact = sprite_interact::SpriteInteractKind::ToggleLight(enable);
|
||||
|
@ -559,6 +559,8 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
}
|
||||
Some(InventoryUpdateEvent::Used)
|
||||
},
|
||||
// Items in overflow slots cannot be used
|
||||
Slot::Overflow(_) => None,
|
||||
};
|
||||
|
||||
drop(inventories);
|
||||
@ -663,6 +665,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
let item = match slot {
|
||||
Slot::Inventory(slot) => inventory.take_half(slot, &ability_map, &msm),
|
||||
Slot::Equip(_) => None,
|
||||
Slot::Overflow(_) => None,
|
||||
};
|
||||
|
||||
if let Some(item) = item {
|
||||
@ -686,6 +689,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
let item = match slot {
|
||||
Slot::Inventory(slot) => inventory.remove(slot),
|
||||
Slot::Equip(slot) => inventory.replace_loadout_item(slot, None, time),
|
||||
Slot::Overflow(slot) => inventory.overflow_remove(slot),
|
||||
};
|
||||
|
||||
// FIXME: We should really require the drop and write to be atomic!
|
||||
@ -717,6 +721,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
let item = match slot {
|
||||
Slot::Inventory(slot) => inventory.take_half(slot, ability_map, &msm),
|
||||
Slot::Equip(_) => None,
|
||||
Slot::Overflow(o) => inventory.overflow_take_half(o, ability_map, &msm),
|
||||
};
|
||||
|
||||
// FIXME: We should really require the drop and write to be atomic!
|
||||
|
32
server/src/migrations/V54__overflow_slots.sql
Normal file
32
server/src/migrations/V54__overflow_slots.sql
Normal file
@ -0,0 +1,32 @@
|
||||
CREATE TEMP TABLE _temp_character_overflow_items_pairings
|
||||
(
|
||||
temp_overflow_items_container_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
character_id INT NOT NULL,
|
||||
overflow_items_container_id INT
|
||||
);
|
||||
|
||||
INSERT
|
||||
INTO _temp_character_overflow_items_pairings
|
||||
SELECT NULL,
|
||||
i.item_id,
|
||||
NULL
|
||||
FROM item i
|
||||
WHERE i.item_definition_id = 'veloren.core.pseudo_containers.character';
|
||||
|
||||
UPDATE _temp_character_overflow_items_pairings
|
||||
SET overflow_items_container_id = ((SELECT MAX(entity_id) FROM entity) + temp_overflow_items_container_id);
|
||||
|
||||
INSERT
|
||||
INTO entity
|
||||
SELECT t.overflow_items_container_id
|
||||
FROM _temp_character_overflow_items_pairings t;
|
||||
|
||||
INSERT
|
||||
INTO item
|
||||
SELECT t.overflow_items_container_id,
|
||||
t.character_id,
|
||||
'veloren.core.pseudo_containers.overflow_items',
|
||||
1,
|
||||
'overflow_items',
|
||||
''
|
||||
FROM _temp_character_overflow_items_pairings t;
|
@ -46,14 +46,18 @@ pub(crate) use conversions::convert_waypoint_from_database_json as parse_waypoin
|
||||
const CHARACTER_PSEUDO_CONTAINER_DEF_ID: &str = "veloren.core.pseudo_containers.character";
|
||||
const INVENTORY_PSEUDO_CONTAINER_DEF_ID: &str = "veloren.core.pseudo_containers.inventory";
|
||||
const LOADOUT_PSEUDO_CONTAINER_DEF_ID: &str = "veloren.core.pseudo_containers.loadout";
|
||||
const OVERFLOW_ITEMS_PSEUDO_CONTAINER_DEF_ID: &str =
|
||||
"veloren.core.pseudo_containers.overflow_items";
|
||||
const INVENTORY_PSEUDO_CONTAINER_POSITION: &str = "inventory";
|
||||
const LOADOUT_PSEUDO_CONTAINER_POSITION: &str = "loadout";
|
||||
const OVERFLOW_ITEMS_PSEUDO_CONTAINER_POSITION: &str = "overflow_items";
|
||||
const WORLD_PSEUDO_CONTAINER_ID: EntityId = 1;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct CharacterContainers {
|
||||
inventory_container_id: EntityId,
|
||||
loadout_container_id: EntityId,
|
||||
overflow_items_container_id: EntityId,
|
||||
}
|
||||
|
||||
/// Load the inventory/loadout
|
||||
@ -126,6 +130,8 @@ pub fn load_character_data(
|
||||
let character_containers = get_pseudo_containers(connection, char_id)?;
|
||||
let inventory_items = load_items(connection, character_containers.inventory_container_id)?;
|
||||
let loadout_items = load_items(connection, character_containers.loadout_container_id)?;
|
||||
let overflow_items_items =
|
||||
load_items(connection, character_containers.overflow_items_container_id)?;
|
||||
|
||||
let mut stmt = connection.prepare_cached(
|
||||
"
|
||||
@ -276,6 +282,8 @@ pub fn load_character_data(
|
||||
&inventory_items,
|
||||
character_containers.loadout_container_id,
|
||||
&loadout_items,
|
||||
character_containers.overflow_items_container_id,
|
||||
&overflow_items_items,
|
||||
)?,
|
||||
waypoint: char_waypoint,
|
||||
pets,
|
||||
@ -383,13 +391,14 @@ pub fn create_character(
|
||||
map_marker,
|
||||
} = persisted_components;
|
||||
|
||||
// Fetch new entity IDs for character, inventory and loadout
|
||||
let mut new_entity_ids = get_new_entity_ids(transaction, |next_id| next_id + 3)?;
|
||||
// Fetch new entity IDs for character, inventory, loadout, and overflow items
|
||||
let mut new_entity_ids = get_new_entity_ids(transaction, |next_id| next_id + 4)?;
|
||||
|
||||
// Create pseudo-container items for character
|
||||
let character_id = new_entity_ids.next().unwrap();
|
||||
let inventory_container_id = new_entity_ids.next().unwrap();
|
||||
let loadout_container_id = new_entity_ids.next().unwrap();
|
||||
let overflow_items_container_id = new_entity_ids.next().unwrap();
|
||||
|
||||
let pseudo_containers = vec![
|
||||
Item {
|
||||
@ -416,6 +425,14 @@ pub fn create_character(
|
||||
position: LOADOUT_PSEUDO_CONTAINER_POSITION.to_owned(),
|
||||
properties: String::new(),
|
||||
},
|
||||
Item {
|
||||
stack_size: 1,
|
||||
item_id: overflow_items_container_id,
|
||||
parent_container_item_id: character_id,
|
||||
item_definition_id: OVERFLOW_ITEMS_PSEUDO_CONTAINER_DEF_ID.to_owned(),
|
||||
position: OVERFLOW_ITEMS_PSEUDO_CONTAINER_POSITION.to_owned(),
|
||||
properties: String::new(),
|
||||
},
|
||||
];
|
||||
|
||||
let mut stmt = transaction.prepare_cached(
|
||||
@ -524,6 +541,7 @@ pub fn create_character(
|
||||
loadout_container_id,
|
||||
&inventory,
|
||||
inventory_container_id,
|
||||
overflow_items_container_id,
|
||||
&mut next_id,
|
||||
);
|
||||
inserts = inserts_;
|
||||
@ -807,6 +825,11 @@ fn get_pseudo_containers(
|
||||
character_id,
|
||||
INVENTORY_PSEUDO_CONTAINER_POSITION,
|
||||
)?,
|
||||
overflow_items_container_id: get_pseudo_container_id(
|
||||
connection,
|
||||
character_id,
|
||||
OVERFLOW_ITEMS_PSEUDO_CONTAINER_POSITION,
|
||||
)?,
|
||||
};
|
||||
|
||||
Ok(character_containers)
|
||||
@ -1001,6 +1024,7 @@ pub fn update(
|
||||
pseudo_containers.loadout_container_id,
|
||||
&inventory,
|
||||
pseudo_containers.inventory_container_id,
|
||||
pseudo_containers.overflow_items_container_id,
|
||||
&mut next_id,
|
||||
);
|
||||
upserts = upserts_;
|
||||
@ -1012,6 +1036,7 @@ pub fn update(
|
||||
let mut existing_item_ids: Vec<_> = vec![
|
||||
Value::from(pseudo_containers.inventory_container_id),
|
||||
Value::from(pseudo_containers.loadout_container_id),
|
||||
Value::from(pseudo_containers.overflow_items_container_id),
|
||||
];
|
||||
for it in load_items(transaction, pseudo_containers.inventory_container_id)? {
|
||||
existing_item_ids.push(Value::from(it.item_id));
|
||||
@ -1019,6 +1044,9 @@ pub fn update(
|
||||
for it in load_items(transaction, pseudo_containers.loadout_container_id)? {
|
||||
existing_item_ids.push(Value::from(it.item_id));
|
||||
}
|
||||
for it in load_items(transaction, pseudo_containers.overflow_items_container_id)? {
|
||||
existing_item_ids.push(Value::from(it.item_id));
|
||||
}
|
||||
|
||||
let non_upserted_items = upserts
|
||||
.iter()
|
||||
|
@ -56,16 +56,25 @@ pub fn convert_items_to_database_items(
|
||||
loadout_container_id: EntityId,
|
||||
inventory: &Inventory,
|
||||
inventory_container_id: EntityId,
|
||||
overflow_items_container_id: EntityId,
|
||||
next_id: &mut i64,
|
||||
) -> Vec<ItemModelPair> {
|
||||
let loadout = inventory
|
||||
.loadout_items_with_persistence_key()
|
||||
.map(|(slot, item)| (slot.to_string(), item, loadout_container_id));
|
||||
|
||||
let overflow_items = inventory.overflow_items().enumerate().map(|(i, item)| {
|
||||
(
|
||||
serde_json::to_string(&i).expect("failed to serialize index of overflow item"),
|
||||
Some(item),
|
||||
overflow_items_container_id,
|
||||
)
|
||||
});
|
||||
|
||||
// Inventory slots.
|
||||
let inventory = inventory.slots_with_id().map(|(pos, item)| {
|
||||
(
|
||||
serde_json::to_string(&pos).expect("failed to serialize InventorySlotPos"),
|
||||
serde_json::to_string(&pos).expect("failed to serialize InvSlotId"),
|
||||
item.as_ref(),
|
||||
inventory_container_id,
|
||||
)
|
||||
@ -73,11 +82,12 @@ pub fn convert_items_to_database_items(
|
||||
|
||||
// Use Breadth-first search to recurse into containers/modular weapons to store
|
||||
// their parts
|
||||
let mut bfs_queue: VecDeque<_> = inventory.chain(loadout).collect();
|
||||
let mut bfs_queue: VecDeque<_> = inventory.chain(loadout).chain(overflow_items).collect();
|
||||
let mut upserts = Vec::new();
|
||||
let mut depth = HashMap::new();
|
||||
depth.insert(inventory_container_id, 0);
|
||||
depth.insert(loadout_container_id, 0);
|
||||
depth.insert(overflow_items_container_id, 0);
|
||||
while let Some((position, item, parent_container_item_id)) = bfs_queue.pop_front() {
|
||||
// Construct new items.
|
||||
if let Some(item) = item {
|
||||
@ -352,6 +362,8 @@ pub fn convert_inventory_from_database_items(
|
||||
inventory_items: &[Item],
|
||||
loadout_container_id: i64,
|
||||
loadout_items: &[Item],
|
||||
overflow_items_container_id: i64,
|
||||
overflow_items: &[Item],
|
||||
) -> Result<Inventory, PersistenceError> {
|
||||
// Loadout items must be loaded before inventory items since loadout items
|
||||
// provide inventory slots. Since items stored inside loadout items actually
|
||||
@ -360,9 +372,13 @@ pub fn convert_inventory_from_database_items(
|
||||
// inventory at the correct position.
|
||||
//
|
||||
let loadout = convert_loadout_from_database_items(loadout_container_id, loadout_items)?;
|
||||
let overflow_items =
|
||||
convert_overflow_items_from_database_items(overflow_items_container_id, overflow_items)?;
|
||||
let mut inventory = Inventory::with_loadout_humanoid(loadout);
|
||||
let mut item_indices = HashMap::new();
|
||||
|
||||
let mut failed_inserts = HashMap::new();
|
||||
|
||||
// In order to items with components to properly load, it is important that this
|
||||
// item iteration occurs in order so that any modular items are loaded before
|
||||
// its components.
|
||||
@ -412,36 +428,50 @@ pub fn convert_inventory_from_database_items(
|
||||
};
|
||||
|
||||
if db_item.parent_container_item_id == inventory_container_id {
|
||||
let slot = slot(&db_item.position)?;
|
||||
let insert_res = inventory.insert_at(slot, item).map_err(|_| {
|
||||
// If this happens there were too many items in the database for the current
|
||||
// inventory size
|
||||
//
|
||||
// FIXME: On failure, collect the set of items that don't fit and return them
|
||||
// (to be dropped next to the player) as this could be the
|
||||
// result of a change in the slot capacity for an equipped bag
|
||||
// (or a change in the inventory size).
|
||||
PersistenceError::ConversionError(format!(
|
||||
"Error inserting item into inventory, position: {:?}",
|
||||
slot
|
||||
))
|
||||
})?;
|
||||
match slot(&db_item.position) {
|
||||
Ok(slot) => {
|
||||
let insert_res = inventory.insert_at(slot, item);
|
||||
|
||||
if insert_res.is_some() {
|
||||
// If inventory.insert returns an item, it means it was swapped for an item that
|
||||
// already occupied the slot. Multiple items being stored in the database for
|
||||
// the same slot is an error.
|
||||
return Err(PersistenceError::ConversionError(
|
||||
"Inserted an item into the same slot twice".to_string(),
|
||||
));
|
||||
match insert_res {
|
||||
Ok(None) => {
|
||||
// Insert successful
|
||||
},
|
||||
Ok(Some(_item)) => {
|
||||
// If inventory.insert returns an item, it means it was swapped for
|
||||
// an item that already occupied the
|
||||
// slot. Multiple items being stored
|
||||
// in the database for the same slot is
|
||||
// an error.
|
||||
return Err(PersistenceError::ConversionError(
|
||||
"Inserted an item into the same slot twice".to_string(),
|
||||
));
|
||||
},
|
||||
Err(item) => {
|
||||
// If this happens there were too many items in the database for the
|
||||
// current inventory size
|
||||
failed_inserts.insert(db_item.position.clone(), item);
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
},
|
||||
}
|
||||
} else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) {
|
||||
get_mutable_item(
|
||||
j,
|
||||
inventory_items,
|
||||
&item_indices,
|
||||
&mut inventory,
|
||||
&|inv, s| inv.slot_mut(slot(s).ok()?).and_then(|a| a.as_mut()),
|
||||
&mut (&mut inventory, &mut failed_inserts),
|
||||
&|(inv, f_i): &mut (&mut Inventory, &mut HashMap<String, VelorenItem>), s| {
|
||||
// Attempts first to access inventory if that slot exists there. If it does not
|
||||
// it instead attempts to access failed inserts list.
|
||||
slot(s)
|
||||
.ok()
|
||||
.and_then(|slot| inv.slot_mut(slot))
|
||||
.and_then(|a| a.as_mut())
|
||||
.or_else(|| f_i.get_mut(s))
|
||||
},
|
||||
)?
|
||||
.persistence_access_add_component(item);
|
||||
} else {
|
||||
@ -452,6 +482,16 @@ pub fn convert_inventory_from_database_items(
|
||||
}
|
||||
}
|
||||
|
||||
// For overflow items and failed inserts, attempt to push to inventory. If push
|
||||
// fails, move to overflow slots.
|
||||
if let Err(inv_error) = inventory.push_all(
|
||||
overflow_items
|
||||
.into_iter()
|
||||
.chain(failed_inserts.into_values()),
|
||||
) {
|
||||
inventory.persistence_push_overflow_items(inv_error.returned_items());
|
||||
}
|
||||
|
||||
// Some items may have had components added, so update the item config of each
|
||||
// item to ensure that it correctly accounts for components that were added
|
||||
inventory.persistence_update_all_item_states(&ABILITY_MAP, &MATERIAL_STATS_MANIFEST);
|
||||
@ -519,6 +559,86 @@ pub fn convert_loadout_from_database_items(
|
||||
Ok(loadout)
|
||||
}
|
||||
|
||||
pub fn convert_overflow_items_from_database_items(
|
||||
overflow_items_container_id: i64,
|
||||
database_items: &[Item],
|
||||
) -> Result<Vec<VelorenItem>, PersistenceError> {
|
||||
let mut overflow_items_with_database_position = HashMap::new();
|
||||
let mut item_indices = HashMap::new();
|
||||
|
||||
// In order to items with components to properly load, it is important that this
|
||||
// item iteration occurs in order so that any modular items are loaded before
|
||||
// its components.
|
||||
for (i, db_item) in database_items.iter().enumerate() {
|
||||
item_indices.insert(db_item.item_id, i);
|
||||
|
||||
let mut item = get_item_from_asset(db_item.item_definition_id.as_str())?;
|
||||
let item_properties =
|
||||
serde_json::de::from_str::<DatabaseItemProperties>(&db_item.properties)?;
|
||||
json_models::apply_db_item_properties(&mut item, &item_properties);
|
||||
|
||||
// NOTE: item id is currently *unique*, so we can store the ID safely.
|
||||
let comp = item.get_item_id_for_database();
|
||||
|
||||
// Item ID
|
||||
comp.store(Some(NonZeroU64::try_from(db_item.item_id as u64).map_err(
|
||||
|_| PersistenceError::ConversionError("Item with zero item_id".to_owned()),
|
||||
)?));
|
||||
|
||||
// Stack Size
|
||||
if db_item.stack_size == 1 || item.is_stackable() {
|
||||
// FIXME: On failure, collect the set of items that don't fit and return them
|
||||
// (to be dropped next to the player) as this could be the result of
|
||||
// a change in the max amount for that item.
|
||||
item.set_amount(u32::try_from(db_item.stack_size).map_err(|_| {
|
||||
PersistenceError::ConversionError(format!(
|
||||
"Invalid item stack size for stackable={}: {}",
|
||||
item.is_stackable(),
|
||||
&db_item.stack_size
|
||||
))
|
||||
})?)
|
||||
.map_err(|_| {
|
||||
PersistenceError::ConversionError("Error setting amount for item".to_owned())
|
||||
})?;
|
||||
}
|
||||
|
||||
if db_item.parent_container_item_id == overflow_items_container_id {
|
||||
match overflow_items_with_database_position.insert(db_item.position.clone(), item) {
|
||||
None => {
|
||||
// Insert successful
|
||||
},
|
||||
Some(_item) => {
|
||||
// If insert returns a value, database had two items stored with the same
|
||||
// position which is an error.
|
||||
return Err(PersistenceError::ConversionError(
|
||||
"Inserted an item into the same overflow slot twice".to_string(),
|
||||
));
|
||||
},
|
||||
}
|
||||
} else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) {
|
||||
get_mutable_item(
|
||||
j,
|
||||
database_items,
|
||||
&item_indices,
|
||||
&mut overflow_items_with_database_position,
|
||||
&|o_i, s| o_i.get_mut(s),
|
||||
)?
|
||||
.persistence_access_add_component(item);
|
||||
} else {
|
||||
return Err(PersistenceError::ConversionError(format!(
|
||||
"Couldn't find parent item {} before item {} in overflow items",
|
||||
db_item.parent_container_item_id, db_item.item_id
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let overflow_items = overflow_items_with_database_position
|
||||
.into_values()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(overflow_items)
|
||||
}
|
||||
|
||||
fn get_item_from_asset(item_definition_id: &str) -> Result<common::comp::Item, PersistenceError> {
|
||||
common::comp::Item::new_from_asset(item_definition_id).map_err(|err| {
|
||||
PersistenceError::AssetError(format!(
|
||||
|
@ -20,7 +20,7 @@ use common::{
|
||||
assets::AssetExt,
|
||||
combat::{combat_rating, perception_dist_multiplier_from_stealth, Damage},
|
||||
comp::{
|
||||
inventory::InventorySortOrder,
|
||||
inventory::{slot::Slot, InventorySortOrder},
|
||||
item::{ItemDef, ItemDesc, ItemI18n, MaterialStatManifest, Quality},
|
||||
Body, Energy, Health, Inventory, Poise, SkillSet, Stats,
|
||||
},
|
||||
@ -308,9 +308,10 @@ impl<'a> InventoryScroller<'a> {
|
||||
// Create available inventory slot widgets
|
||||
if state.ids.inv_slots.len() < self.inventory.capacity() {
|
||||
state.update(|s| {
|
||||
s.ids
|
||||
.inv_slots
|
||||
.resize(self.inventory.capacity(), &mut ui.widget_id_generator());
|
||||
s.ids.inv_slots.resize(
|
||||
self.inventory.capacity() + self.inventory.overflow_items().count(),
|
||||
&mut ui.widget_id_generator(),
|
||||
);
|
||||
});
|
||||
}
|
||||
if state.ids.inv_slot_names.len() < self.inventory.capacity() {
|
||||
@ -363,7 +364,17 @@ impl<'a> InventoryScroller<'a> {
|
||||
};
|
||||
|
||||
let mut i = 0;
|
||||
let mut items = self.inventory.slots_with_id().collect::<Vec<_>>();
|
||||
let mut items = self
|
||||
.inventory
|
||||
.slots_with_id()
|
||||
.map(|(slot, item)| (Slot::Inventory(slot), item.as_ref()))
|
||||
.chain(
|
||||
self.inventory
|
||||
.overflow_items()
|
||||
.enumerate()
|
||||
.map(|(i, item)| (Slot::Overflow(i), Some(item))),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
if self.details_mode && !self.is_us {
|
||||
items.sort_by_cached_key(|(_, item)| {
|
||||
(
|
||||
@ -419,6 +430,11 @@ impl<'a> InventoryScroller<'a> {
|
||||
slot_widget = slot_widget.with_background_color(Color::Rgba(1.0, 1.0, 1.0, 1.0));
|
||||
}
|
||||
|
||||
// Highlight in red slots that are overflow
|
||||
if matches!(pos, Slot::Overflow(_)) {
|
||||
slot_widget = slot_widget.with_background_color(Color::Rgba(1.0, 0.0, 0.0, 1.0));
|
||||
}
|
||||
|
||||
if let Some(item) = item {
|
||||
let quality_col_img = match item.quality() {
|
||||
Quality::Low => self.imgs.inv_slot_grey,
|
||||
|
@ -1576,6 +1576,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
modifier: craft_slot_2.and_then(|slot| match slot {
|
||||
Slot::Inventory(slot) => Some(slot),
|
||||
Slot::Equip(_) => None,
|
||||
Slot::Overflow(_) => None,
|
||||
}),
|
||||
});
|
||||
}
|
||||
@ -1736,6 +1737,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.and_then(|slot| match slot {
|
||||
Slot::Inventory(slot) => self.inventory.get(slot),
|
||||
Slot::Equip(_) => None,
|
||||
Slot::Overflow(_) => None,
|
||||
})
|
||||
.and_then(|item| item.item_definition_id().itemdef_id().map(String::from))
|
||||
{
|
||||
@ -1746,6 +1748,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.and_then(|slot| match slot {
|
||||
Slot::Inventory(slot) => self.inventory.get(slot),
|
||||
Slot::Equip(_) => None,
|
||||
Slot::Overflow(_) => None,
|
||||
})
|
||||
.and_then(|item| {
|
||||
item.item_definition_id().itemdef_id().map(String::from)
|
||||
@ -1769,6 +1772,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
if let Some(item) = match craft_slot_1 {
|
||||
Some(Slot::Inventory(slot)) => self.inventory.get(slot),
|
||||
Some(Slot::Equip(slot)) => self.inventory.equipped(slot),
|
||||
Some(Slot::Overflow(_)) => None,
|
||||
None => None,
|
||||
} {
|
||||
if let Some(recipe) = self.client.repair_recipe_book().repair_recipe(item) {
|
||||
|
@ -3887,9 +3887,18 @@ impl Hud {
|
||||
'slot_events: for event in self.slot_manager.maintain(ui_widgets) {
|
||||
use slots::{AbilitySlot, InventorySlot, SlotKind::*};
|
||||
let to_slot = |slot_kind| match slot_kind {
|
||||
Inventory(
|
||||
i @ InventorySlot {
|
||||
slot: Slot::Inventory(_) | Slot::Overflow(_),
|
||||
ours: true,
|
||||
..
|
||||
},
|
||||
) => Some(i.slot),
|
||||
Inventory(InventorySlot {
|
||||
slot, ours: true, ..
|
||||
}) => Some(Slot::Inventory(slot)),
|
||||
slot: Slot::Equip(_),
|
||||
ours: true,
|
||||
..
|
||||
}) => None,
|
||||
Inventory(InventorySlot { ours: false, .. }) => None,
|
||||
Equip(e) => Some(Slot::Equip(e)),
|
||||
Hotbar(_) => None,
|
||||
@ -3913,21 +3922,27 @@ impl Hud {
|
||||
Hotbar(h),
|
||||
) = (a, b)
|
||||
{
|
||||
if let Some(item) = inventories
|
||||
.get(info.viewpoint_entity)
|
||||
.and_then(|inv| inv.get(slot))
|
||||
{
|
||||
self.hotbar.add_inventory_link(h, item);
|
||||
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
|
||||
if let Slot::Inventory(slot) = slot {
|
||||
if let Some(item) = inventories
|
||||
.get(info.viewpoint_entity)
|
||||
.and_then(|inv| inv.get(slot))
|
||||
{
|
||||
self.hotbar.add_inventory_link(h, item);
|
||||
events.push(Event::ChangeHotbarState(Box::new(
|
||||
self.hotbar.to_owned(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
} else if let (Hotbar(a), Hotbar(b)) = (a, b) {
|
||||
self.hotbar.swap(a, b);
|
||||
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
|
||||
} else if let (Inventory(i), Trade(t)) = (a, b) {
|
||||
if i.ours == t.ours {
|
||||
if let Some(inventory) = inventories.get(t.entity) {
|
||||
if let (Some(inventory), Slot::Inventory(slot)) =
|
||||
(inventories.get(t.entity), i.slot)
|
||||
{
|
||||
events.push(Event::TradeAction(TradeAction::AddItem {
|
||||
item: i.slot,
|
||||
item: slot,
|
||||
quantity: i.amount(inventory).unwrap_or(1),
|
||||
ours: i.ours,
|
||||
}));
|
||||
@ -3973,18 +3988,20 @@ impl Hud {
|
||||
(AbilitySlot::Ability(_), AbilitySlot::Ability(_)) => {},
|
||||
}
|
||||
} else if let (Inventory(i), Crafting(c)) = (a, b) {
|
||||
// Add item to crafting input
|
||||
if inventories
|
||||
.get(info.viewpoint_entity)
|
||||
.and_then(|inv| inv.get(i.slot))
|
||||
.map_or(false, |item| {
|
||||
(c.requirement)(item, client.component_recipe_book(), c.info)
|
||||
})
|
||||
{
|
||||
self.show
|
||||
.crafting_fields
|
||||
.recipe_inputs
|
||||
.insert(c.index, Slot::Inventory(i.slot));
|
||||
if let Slot::Inventory(slot) = i.slot {
|
||||
// Add item to crafting input
|
||||
if inventories
|
||||
.get(info.viewpoint_entity)
|
||||
.and_then(|inv| inv.get(slot))
|
||||
.map_or(false, |item| {
|
||||
(c.requirement)(item, client.component_recipe_book(), c.info)
|
||||
})
|
||||
{
|
||||
self.show
|
||||
.crafting_fields
|
||||
.recipe_inputs
|
||||
.insert(c.index, i.slot);
|
||||
}
|
||||
}
|
||||
} else if let (Equip(e), Crafting(c)) = (a, b) {
|
||||
// Add item to crafting input
|
||||
@ -4055,21 +4072,27 @@ impl Hud {
|
||||
bypass_dialog: false,
|
||||
});
|
||||
} else if let (Inventory(i), Hotbar(h)) = (a, b) {
|
||||
if let Some(item) = inventories
|
||||
.get(info.viewpoint_entity)
|
||||
.and_then(|inv| inv.get(i.slot))
|
||||
{
|
||||
self.hotbar.add_inventory_link(h, item);
|
||||
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
|
||||
if let Slot::Inventory(slot) = i.slot {
|
||||
if let Some(item) = inventories
|
||||
.get(info.viewpoint_entity)
|
||||
.and_then(|inv| inv.get(slot))
|
||||
{
|
||||
self.hotbar.add_inventory_link(h, item);
|
||||
events.push(Event::ChangeHotbarState(Box::new(
|
||||
self.hotbar.to_owned(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
} else if let (Hotbar(a), Hotbar(b)) = (a, b) {
|
||||
self.hotbar.swap(a, b);
|
||||
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
|
||||
} else if let (Inventory(i), Trade(t)) = (a, b) {
|
||||
if i.ours == t.ours {
|
||||
if let Some(inventory) = inventories.get(t.entity) {
|
||||
if let (Some(inventory), Slot::Inventory(slot)) =
|
||||
(inventories.get(t.entity), i.slot)
|
||||
{
|
||||
events.push(Event::TradeAction(TradeAction::AddItem {
|
||||
item: i.slot,
|
||||
item: slot,
|
||||
quantity: i.amount(inventory).unwrap_or(1) / 2,
|
||||
ours: i.ours,
|
||||
}));
|
||||
@ -4253,24 +4276,26 @@ impl Hud {
|
||||
match slot {
|
||||
Inventory(i) => {
|
||||
if let Some(inventory) = inventories.get(i.entity) {
|
||||
let mut quantity = 1;
|
||||
if auto_quantity {
|
||||
do_auto_quantity(
|
||||
inventory,
|
||||
i.slot,
|
||||
i.ours,
|
||||
false,
|
||||
&mut quantity,
|
||||
);
|
||||
let inv_quantity = i.amount(inventory).unwrap_or(1);
|
||||
quantity = quantity.min(inv_quantity);
|
||||
}
|
||||
if let Slot::Inventory(slot) = i.slot {
|
||||
let mut quantity = 1;
|
||||
if auto_quantity {
|
||||
do_auto_quantity(
|
||||
inventory,
|
||||
slot,
|
||||
i.ours,
|
||||
false,
|
||||
&mut quantity,
|
||||
);
|
||||
let inv_quantity = i.amount(inventory).unwrap_or(1);
|
||||
quantity = quantity.min(inv_quantity);
|
||||
}
|
||||
|
||||
events.push(Event::TradeAction(TradeAction::AddItem {
|
||||
item: i.slot,
|
||||
quantity,
|
||||
ours: i.ours,
|
||||
}));
|
||||
events.push(Event::TradeAction(TradeAction::AddItem {
|
||||
item: slot,
|
||||
quantity,
|
||||
ours: i.ours,
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
Trade(t) => {
|
||||
@ -4530,7 +4555,7 @@ impl Hud {
|
||||
) {
|
||||
use slots::InventorySlot;
|
||||
if let Some(slots::SlotKind::Inventory(InventorySlot {
|
||||
slot: i,
|
||||
slot: Slot::Inventory(i),
|
||||
ours: true,
|
||||
..
|
||||
})) = slot_manager.selected()
|
||||
|
@ -36,7 +36,7 @@ pub type SlotManager = slot::SlotManager<SlotKind>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct InventorySlot {
|
||||
pub slot: InvSlotId,
|
||||
pub slot: Slot,
|
||||
pub entity: EcsEntity,
|
||||
pub ours: bool,
|
||||
}
|
||||
@ -45,12 +45,12 @@ impl SlotKey<Inventory, ItemImgs> for InventorySlot {
|
||||
type ImageKey = ItemKey;
|
||||
|
||||
fn image_key(&self, source: &Inventory) -> Option<(Self::ImageKey, Option<Color>)> {
|
||||
source.get(self.slot).map(|i| (i.into(), None))
|
||||
source.get_slot(self.slot).map(|i| (i.into(), None))
|
||||
}
|
||||
|
||||
fn amount(&self, source: &Inventory) -> Option<u32> {
|
||||
source
|
||||
.get(self.slot)
|
||||
.get_slot(self.slot)
|
||||
.map(|item| item.amount())
|
||||
.filter(|amount| *amount > 1)
|
||||
}
|
||||
@ -90,7 +90,7 @@ impl SlotKey<Inventory, ItemImgs> for TradeSlot {
|
||||
fn image_key(&self, source: &Inventory) -> Option<(Self::ImageKey, Option<Color>)> {
|
||||
self.invslot.and_then(|inv_id| {
|
||||
InventorySlot {
|
||||
slot: inv_id,
|
||||
slot: Slot::Inventory(inv_id),
|
||||
ours: self.ours,
|
||||
entity: self.entity,
|
||||
}
|
||||
@ -102,7 +102,7 @@ impl SlotKey<Inventory, ItemImgs> for TradeSlot {
|
||||
self.invslot
|
||||
.and_then(|inv_id| {
|
||||
InventorySlot {
|
||||
slot: inv_id,
|
||||
slot: Slot::Inventory(inv_id),
|
||||
ours: self.ours,
|
||||
entity: self.entity,
|
||||
}
|
||||
@ -286,6 +286,7 @@ impl CraftSlot {
|
||||
match self.slot {
|
||||
Some(Slot::Inventory(slot)) => inv.get(slot),
|
||||
Some(Slot::Equip(slot)) => inv.equipped(slot),
|
||||
Some(Slot::Overflow(_)) => None,
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -1753,6 +1753,7 @@ impl PlayState for SessionState {
|
||||
move_allowed = false;
|
||||
}
|
||||
},
|
||||
Slot::Overflow(_) => {},
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -2021,6 +2022,7 @@ impl PlayState for SessionState {
|
||||
let item = match item {
|
||||
Slot::Equip(slot) => inventory.equipped(slot),
|
||||
Slot::Inventory(slot) => inventory.get(slot),
|
||||
Slot::Overflow(_) => None,
|
||||
}?;
|
||||
let repair_recipe =
|
||||
client.repair_recipe_book().repair_recipe(item)?;
|
||||
|
Loading…
Reference in New Issue
Block a user