-
-
Notifications
You must be signed in to change notification settings - Fork 145
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
Readonly inventory #655
Conversation
Hey nice work, just chiming in with a couple of thoughts, I read what RJ wrote in the draft for inventory menus in the draft pr #355 it is possible to implement readonly behavior in user space. So I think exploring implementing this as a separate crate we maintain is worthwhile. The alternative approach is to not bloat up the inventory crate with the readonly stuff, it should be implementable as its own separate crate with the approach to listen to the With this approach it should be possible i think to have another create One footgun with the described approach (the separate crate one) is that for example a user implementing a feature might be listening to the I don't really have any strong argument for one approach or the other myself currently. I think its worthwile to bring up this approach too :D |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely opposed to having a readonly
flag on Inventory
. Should make inventory menus easier.
There needs to be some tests though.
im currently making some tests and i made this test here: #[test]
fn test_prevent_hotbar_item_click_container_readonly_inventory() {
let ScenarioSingleClient {
mut app,
client,
mut helper,
..
} = ScenarioSingleClient::new();
// Process a tick to get past the "on join" logic.
app.update();
helper.clear_received();
// player inventory is not read-only
let mut player_inventory = app
.world_mut()
.get_mut::<Inventory>(client)
.expect("could not find inventory for client");
// 36 is the first hotbar slot
player_inventory.set_slot(36, ItemStack::new(ItemKind::Diamond, 1, None));
let open_inv_ent = set_up_open_inventory(&mut app, client);
let mut open_inventory = app
.world_mut()
.get_mut::<Inventory>(open_inv_ent)
.expect("could not find inventory for client");
// Open inventory is read-only
// open_inventory.readonly = true;
open_inventory.set_slot(0, ItemStack::new(ItemKind::IronIngot, 10, None));
let inv_state = app.world_mut().get::<ClientInventoryState>(client).unwrap();
let state_id = inv_state.state_id();
let window_id = inv_state.window_id();
// The player hovers over the iron ingots in the open inventory, and tries
// to move them to their own (via pressing 1), which should swap the iron
// for the diamonds. However the opened inventory is read-only, so nothing
// should happen.
helper.send(&ClickSlotC2s {
window_id,
state_id: VarInt(state_id.0),
slot_idx: 0,
button: 0, // hotbar slot starting at 0
mode: ClickMode::Hotbar,
slot_changes: vec![
// First SlotChange is the item is the slot in the player's hotbar.
// target slot.
SlotChange {
idx: 0,
stack: ItemStack::new(ItemKind::Diamond, 1, None),
},
SlotChange {
// 54 is the players hotbar slot 1, when the 9x3 inventory is opnened.
idx: 54,
stack: ItemStack::new(ItemKind::IronIngot, 10, None),
},
// The second one is the slot in the open inventory, after the ClickSlot action
// source slot.
]
.into(),
carried_item: ItemStack::EMPTY,
});
app.update();
let sent_packets = helper.collect_received();
// 1 resync for each inventory
sent_packets.assert_count::<InventoryS2c>(2);
// Make assertions
let player_inventory = app
.world_mut()
.get::<Inventory>(client)
.expect("could not find client");
// Opened inventory is read-only, the items are not swapped.
assert_eq!(player_inventory.slot(36), &ItemStack::new(ItemKind::Diamond, 1, None));
let open_inventory = app
.world_mut()
.get::<Inventory>(open_inv_ent)
.expect("could not find inventory");
// Opened inventory is read-only, the items are not swapped.
assert_eq!(open_inventory.slot(0), &ItemStack::new(ItemKind::IronIngot, 10, None));
} this test here will pass if the items were not swapped (as they usually should) for some reason, this always passes (even when the inventory is not readonly, and this also happens on the latest master branch) The event being sent is the exact same that is also sent when i test it with a game client and replicate this (and that works fine, no desync) Any ideas why the inventory slots are not updating in the inventory component? Edit: |
Co-authored-by: Carson McManus <[email protected]>
ive added all the tests ive made so far, as i said in the message above some of them do not work (because of the strange behavior where the inventory content does not update). Ive commented them out for now. |
IIRC, We have some basic anti cheat validation on |
After a lot of trial and error i finally figured this out This test below is an implementation that can properly swap two items (NON-read only) should be easy enough to make it pass in the read only case. there was an #[test]
fn test_hotbar_item_swap_container() {
let ScenarioSingleClient {
mut app,
client,
mut helper,
..
} = ScenarioSingleClient::new();
// Process a tick to get past the "on join" logic.
app.update();
let mut player_inventory = app
.world_mut()
.get_mut::<Inventory>(client)
.expect("could not find inventory for client");
// 36 is the first hotbar slot
player_inventory.set_slot(36, ItemStack::new(ItemKind::Diamond, 1, None));
let open_inv_ent = set_up_open_inventory(&mut app, client);
let mut open_inventory = app
.world_mut()
.get_mut::<Inventory>(open_inv_ent)
.expect("could not find inventory for client");
open_inventory.set_slot(0, ItemStack::new(ItemKind::IronIngot, 10, None));
// This update makes sure we have the items in the inventory by the time the
// client wants to update these
app.update();
helper.clear_received();
let inv_state = app.world_mut().get::<ClientInventoryState>(client).unwrap();
let state_id = inv_state.state_id();
let window_id = inv_state.window_id();
// The player hovers over the iron ingots in the open inventory, and tries
// to move them to their own (via pressing 1), which should swap the iron
// for the diamonds.
helper.send(&ClickSlotC2s {
window_id,
state_id: VarInt(state_id.0),
slot_idx: 0,
button: 0, // hotbar slot starting at 0
mode: ClickMode::Hotbar,
slot_changes: vec![
// First SlotChange is the item is the slot in the player's hotbar.
// target slot.
SlotChange {
idx: 0,
stack: ItemStack::new(ItemKind::Diamond, 1, None),
},
SlotChange {
// 54 is the players hotbar slot 1, when the 9x3 inventory is opnened.
idx: 54,
stack: ItemStack::new(ItemKind::IronIngot, 10, None),
},
// The second one is the slot in the open inventory, after the ClickSlot action
// source slot.
]
.into(),
carried_item: ItemStack::EMPTY,
});
app.update();
let sent_packets = helper.collect_received();
// No resyncs beacuse the client was in sync and sent us the updates
sent_packets.assert_count::<InventoryS2c>(0);
// Make assertions
let player_inventory = app
.world_mut()
.get::<Inventory>(client)
.expect("could not find client");
// Swapped items sucessfully
assert_eq!(
player_inventory.slot(36),
&ItemStack::new(ItemKind::IronIngot, 10, None)
);
let open_inventory = app
.world_mut()
.get::<Inventory>(open_inv_ent)
.expect("could not find inventory");
assert_eq!(
open_inventory.slot(0),
&ItemStack::new(ItemKind::Diamond, 1, None)
);
} what finnaly helped me figure everything out was setting the output gave a lot away, the resyncs where also a bit suspicious, as when i looked at what happens with a client and server (with the packet inspector) in this instance no re-syncs where observed |
should be ready now! |
Objective
Solution
readonly: bool
field to theInventory
component, that will make any interactions with this item impossible (includes: moving, shift moving, hotbar moving, dropping) if a player inventory is readonly, then the player will also not be able to drop items (even when not in the inventory), so the drop event will not be emitted (this could be changed if requested)