valence-rs / valence

A Rust framework for building Minecraft servers.
http://valence.rs/
MIT License
2.77k stars 142 forks source link

Player Inventory not synced if OpenInventory is attached (and changes are made to player inventory) #666

Open maxomatic458 opened 3 days ago

maxomatic458 commented 3 days ago

Valence Version

f526a17

What You Did

Im trying to add items to a player while the player is in a inventory, i believe that is perfectly doable by adding the items to the OpenInventory in those slots that are actually the players inventory slots.

Playground

use rand::Rng;
use valence::entity::armor_stand::ArmorStandEntityBundle;
use valence::entity::player::PlayerEntity;
use valence::entity::zombie::ZombieEntityBundle;
use valence::prelude::*;

const SPAWN_Y: i32 = 64;

pub fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, (despawn_disconnected_clients,))
        .add_systems(
            Update,
            (
                init_clients,
                despawn_disconnected_clients,
                give_items,
                delayed_open_inventory,
            ),
        )
        .run();
}

fn setup(
    mut commands: Commands,
    server: Res<Server>,
    dimensions: Res<DimensionTypeRegistry>,
    biomes: Res<BiomeRegistry>,
) {
    let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server);

    for z in -5..5 {
        for x in -5..5 {
            layer.chunk.insert_chunk([x, z], UnloadedChunk::new());
        }
    }

    for z in -25..25 {
        for x in -25..25 {
            layer
                .chunk
                .set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
        }
    }
}

fn init_clients(
    mut commands: Commands,
    mut clients: Query<
        (
            Entity,
            &mut Position,
            &mut EntityLayerId,
            &mut VisibleChunkLayer,
            &mut VisibleEntityLayers,
            &mut GameMode,
        ),
        Added<Client>,
    >,
    layers: Query<Entity, (With<ChunkLayer>, With<EntityLayer>)>,
) {
    for (
        player,
        mut pos,
        mut layer_id,
        mut visible_chunk_layer,
        mut visible_entity_layers,
        mut game_mode,
    ) in &mut clients
    {
        let layer = layers.single();

        pos.0 = [0.0, f64::from(SPAWN_Y) + 1.0, 0.0].into();
        layer_id.0 = layer;
        visible_chunk_layer.0 = layer;
        visible_entity_layers.0.insert(layer);
        *game_mode = GameMode::Survival;

        let inventory = Inventory::new(InventoryKind::Generic9x3);

    }
}

fn delayed_open_inventory(
    mut commands: Commands,
    mut clients: Query<Entity, With<PlayerEntity>>,
    server: Res<Server>,
) {
    let ticks = server.current_tick() as u32;

    if ticks  != 200 {
        return;
    }

    for player in &mut clients {

        let inventory = Inventory::new(InventoryKind::Generic9x3);

        let inv_ent = commands.spawn(inventory).id();

        let open_inv = OpenInventory::new(inv_ent);

        tracing::info!("Opening inventory for player.");

        commands.entity(player).insert(open_inv);
    }

}

fn give_items(mut query: Query<&mut Inventory, With<PlayerEntity>>, server: Res<Server>) {
    let ticks = server.current_tick() as u32;

    if ticks % server.tick_rate() != 0 {
        return;
    }

    for mut inventory in &mut query {
        // set slot 36 to wool + 1

        let hb_one = inventory.slot(36).clone();

        if hb_one.is_empty() {
            inventory.set_slot(36, ItemStack::new(ItemKind::WhiteWool, 1, None));
        } else {
            inventory.set_slot(36, ItemStack::new(ItemKind::WhiteWool, hb_one.count + 1, None));
        }

        tracing::info!("Gave wool to player.");
    }
}

What Went Wrong

However i noticed that when i insert items directly into the player's inventory (while the OpenInventory is still attached) the changes where not properly synced, and i do believe that this is unintended behavior.

## Additional Information - i believe modifying the players inventory while looking at an open inventory is just not being handled Video clip (1 wool is added every second to the players inventory) https://github.com/user-attachments/assets/334bc9f5-aefa-4ca5-8996-756d419face5
lukashermansson commented 2 days ago

While going over the update to 1.21.1 i noticied this note on the packet

When a container window is open, the server never sends updates targeting Window ID 0—all of the window types include slots for the player inventory. The client must automatically apply changes targeting the inventory portion of a container window to the main inventory; the server does not resend them for ID 0 when the window is closed. However, since the armor and offhand slots are only present on ID 0, updates to those slots occurring while a window is open must be deferred by the server until the window's closure.

https://wiki.vg/Protocol#Set_Container_Slot its also present on the version we currently use but with less information https://wiki.vg/index.php?title=Protocol&oldid=18375#Set_Container_Slot

this should be considered when researching this or possibly fixing it by derfering the updates (if we don't do this already)

dyc3 commented 2 days ago

Yes, this was originally intentional behavior. If an inventory is open, you should operate on the open inventory, not the player inventory. But I do see your point, this should be possible. Consider the following vanilla behavior: