Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Readonly inventory #655

Merged
merged 9 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 150 additions & 10 deletions crates/valence_inventory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ pub struct Inventory {
/// Contains a set bit for each modified slot in `slots`.
#[doc(hidden)]
pub changed: u64,
/// Makes an inventory read-only for clients. This will prevent adding
/// or removing items. If this is a player inventory
/// This will also make it impossible to drop items while not
/// in the inventory (e.g. by pressing Q)
pub readonly: bool,
}

impl Inventory {
Expand All @@ -86,6 +91,7 @@ impl Inventory {
kind,
slots: vec![ItemStack::EMPTY; kind.slot_count()].into(),
changed: 0,
readonly: false,
}
}

Expand Down Expand Up @@ -915,6 +921,17 @@ fn handle_click_slot(
if (0_i16..target_inventory.slot_count() as i16).contains(&pkt.slot_idx) {
// The player is dropping an item from another inventory.

if target_inventory.readonly {
// resync target inventory
client.write_packet(&InventoryS2c {
window_id: inv_state.window_id,
state_id: VarInt(inv_state.state_id.0),
slots: Cow::Borrowed(target_inventory.slot_slice()),
carried_item: Cow::Borrowed(&cursor_item.0),
});
continue;
}

let stack = target_inventory.slot(pkt.slot_idx as u16);

if !stack.is_empty() {
Expand All @@ -938,6 +955,18 @@ fn handle_click_slot(
}
} else {
// The player is dropping an item from their inventory.

if client_inv.readonly {
// resync the client inventory
client.write_packet(&InventoryS2c {
window_id: 0,
state_id: VarInt(inv_state.state_id.0),
slots: Cow::Borrowed(client_inv.slot_slice()),
carried_item: Cow::Borrowed(&cursor_item.0),
});
continue;
}

let slot_id =
convert_to_player_slot_id(target_inventory.kind, pkt.slot_idx as u16);

Expand Down Expand Up @@ -966,6 +995,17 @@ fn handle_click_slot(
// The player has no inventory open and is dropping an item from their
// inventory.

if client_inv.readonly {
// resync the client inventory
client.write_packet(&InventoryS2c {
window_id: 0,
state_id: VarInt(inv_state.state_id.0),
slots: Cow::Borrowed(client_inv.slot_slice()),
carried_item: Cow::Borrowed(&cursor_item.0),
});
continue;
}

let stack = client_inv.slot(pkt.slot_idx as u16);

if !stack.is_empty() {
Expand Down Expand Up @@ -1002,7 +1042,8 @@ fn handle_click_slot(
}

if let Some(mut open_inventory) = open_inventory {
// The player is interacting with an inventory that is open.
// The player is interacting with an inventory that is
// open or has an inventory open while interacting with their own inventory.

let Ok(mut target_inventory) = inventories.get_mut(open_inventory.entity) else {
// The inventory does not exist, ignore.
Expand All @@ -1026,22 +1067,60 @@ fn handle_click_slot(
continue;
}

cursor_item.set_if_neq(CursorItem(pkt.carried_item.clone()));
inv_state.client_updated_cursor_item = Some(pkt.carried_item.clone());
let mut new_cursor = pkt.carried_item.clone();

for slot in pkt.slot_changes.iter() {
let transferred_between_inventories =
((0_i16..target_inventory.slot_count() as i16).contains(&pkt.slot_idx)
&& pkt.mode == ClickMode::Hotbar)
|| pkt.mode == ClickMode::ShiftClick;

if (0_i16..target_inventory.slot_count() as i16).contains(&slot.idx) {
// The client is interacting with a slot in the target inventory.
if (client_inv.readonly && transferred_between_inventories)
|| target_inventory.readonly
{
new_cursor = cursor_item.0.clone();
continue;
}

target_inventory.set_slot(slot.idx as u16, slot.stack.clone());
open_inventory.client_changed |= 1 << slot.idx;
} else {
if (target_inventory.readonly && transferred_between_inventories)
|| client_inv.readonly
{
new_cursor = cursor_item.0.clone();
continue;
}

// The client is interacting with a slot in their own inventory.
let slot_id =
convert_to_player_slot_id(target_inventory.kind, slot.idx as u16);
client_inv.set_slot(slot_id, slot.stack.clone());
inv_state.slots_changed |= 1 << slot_id;
}
}

cursor_item.set_if_neq(CursorItem(new_cursor.clone()));
inv_state.client_updated_cursor_item = Some(new_cursor);

if target_inventory.readonly || client_inv.readonly {
// resync the target inventory
client.write_packet(&InventoryS2c {
window_id: inv_state.window_id,
state_id: VarInt(inv_state.state_id.0),
slots: Cow::Borrowed(target_inventory.slot_slice()),
carried_item: Cow::Borrowed(&cursor_item.0),
});

// resync the client inventory
client.write_packet(&InventoryS2c {
window_id: 0,
state_id: VarInt(inv_state.state_id.0),
slots: Cow::Borrowed(client_inv.slot_slice()),
carried_item: Cow::Borrowed(&cursor_item.0),
});
}
} else {
// The client is interacting with their own inventory.

Expand All @@ -1062,11 +1141,14 @@ fn handle_click_slot(
continue;
}

cursor_item.set_if_neq(CursorItem(pkt.carried_item.clone()));
inv_state.client_updated_cursor_item = Some(pkt.carried_item.clone());
let mut new_cursor = pkt.carried_item.clone();

for slot in pkt.slot_changes.iter() {
if (0_i16..client_inv.slot_count() as i16).contains(&slot.idx) {
if client_inv.readonly {
new_cursor = cursor_item.0.clone();
continue;
}
client_inv.set_slot(slot.idx as u16, slot.stack.clone());
inv_state.slots_changed |= 1 << slot.idx;
} else {
Expand All @@ -1078,6 +1160,19 @@ fn handle_click_slot(
);
}
}

cursor_item.set_if_neq(CursorItem(new_cursor.clone()));
inv_state.client_updated_cursor_item = Some(new_cursor);

if client_inv.readonly {
// resync the client inventory
client.write_packet(&InventoryS2c {
window_id: 0,
state_id: VarInt(inv_state.state_id.0),
slots: Cow::Borrowed(client_inv.slot_slice()),
carried_item: Cow::Borrowed(&cursor_item.0),
});
}
}

click_slot_events.send(ClickSlotEvent {
Expand All @@ -1096,14 +1191,32 @@ fn handle_click_slot(

fn handle_player_actions(
mut packets: EventReader<PacketEvent>,
mut clients: Query<(&mut Inventory, &mut ClientInventoryState, &HeldItem)>,
mut clients: Query<(
&mut Inventory,
&mut ClientInventoryState,
&HeldItem,
&mut Client,
)>,
mut drop_item_stack_events: EventWriter<DropItemStackEvent>,
) {
for packet in packets.read() {
if let Some(pkt) = packet.decode::<PlayerActionC2s>() {
match pkt.action {
PlayerAction::DropAllItems => {
if let Ok((mut inv, mut inv_state, &held)) = clients.get_mut(packet.client) {
if let Ok((mut inv, mut inv_state, &held, mut client)) =
clients.get_mut(packet.client)
{
if inv.readonly {
// resync the client inventory
client.write_packet(&InventoryS2c {
window_id: 0,
state_id: VarInt(inv_state.state_id.0),
slots: Cow::Borrowed(inv.slot_slice()),
carried_item: Cow::Borrowed(&ItemStack::EMPTY),
});
continue;
}

let stack = inv.replace_slot(held.slot(), ItemStack::EMPTY);

if !stack.is_empty() {
Expand All @@ -1118,7 +1231,20 @@ fn handle_player_actions(
}
}
PlayerAction::DropItem => {
if let Ok((mut inv, mut inv_state, held)) = clients.get_mut(packet.client) {
if let Ok((mut inv, mut inv_state, held, mut client)) =
clients.get_mut(packet.client)
{
if inv.readonly {
// resync the client inventory
client.write_packet(&InventoryS2c {
window_id: 0,
state_id: VarInt(inv_state.state_id.0),
slots: Cow::Borrowed(inv.slot_slice()),
carried_item: Cow::Borrowed(&ItemStack::EMPTY),
});
continue;
}

let mut stack = inv.replace_slot(held.slot(), ItemStack::EMPTY);

if !stack.is_empty() {
Expand All @@ -1142,7 +1268,21 @@ fn handle_player_actions(
}
}
PlayerAction::SwapItemWithOffhand => {
if let Ok((mut inv, _, held)) = clients.get_mut(packet.client) {
if let Ok((mut inv, inv_state, held, mut client)) =
clients.get_mut(packet.client)
{
// this check here might not actually be necessary
if inv.readonly {
// resync the client inventory
client.write_packet(&InventoryS2c {
window_id: 0,
state_id: VarInt(inv_state.state_id.0),
slots: Cow::Borrowed(inv.slot_slice()),
carried_item: Cow::Borrowed(&ItemStack::EMPTY),
});
continue;
}

inv.swap_slot(held.slot(), PlayerInventory::SLOT_OFFHAND);
}
}
Expand Down
Loading
Loading