Skip to content
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
21 changes: 19 additions & 2 deletions pomme-client/src/app/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,10 +503,27 @@ impl AppCore {
game.player.armor = armor;
}
}
NetworkEvent::InventoryContent { items } => {
NetworkEvent::InventoryContent {
items,
carried,
state_id,
} => {
game.player.inventory.set_contents(items);
game.cursor_item = carried;
game.container_state_id = state_id;
}
NetworkEvent::CursorItem { item } => {
game.cursor_item = item;
}
NetworkEvent::InventorySlot { index, item } => {
NetworkEvent::Registries(registries) => {
game.registries = registries;
}
NetworkEvent::InventorySlot {
index,
item,
state_id,
} => {
game.container_state_id = state_id;
game.player.inventory.set_slot(index as usize, item);
}
NetworkEvent::ChatMessage { spans } => {
Expand Down
8 changes: 4 additions & 4 deletions pomme-client/src/app/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,10 @@ impl InputState {
self.left_click.just_pressed
}

pub fn right_just_pressed(&self) -> bool {
self.right_click.just_pressed
}

pub fn left_held(&self) -> bool {
self.left_click.held
}
Expand All @@ -654,10 +658,6 @@ impl InputState {
self.middle_click.just_pressed
}

pub fn right_just_pressed(&self) -> bool {
self.right_click.just_pressed
}

pub fn on_cursor_moved(&mut self, x: f32, y: f32) {
self.cursor_pos = (x, y);
self.cursor_moved = true;
Expand Down
127 changes: 123 additions & 4 deletions pomme-client/src/app/phases/in_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ pub struct GameState {
pub inventory_open: bool,
pub creative_inventory_open: bool,
pub creative_state: crate::ui::creative_inventory::CreativeState,
/// Latest container state id from the server, echoed in container clicks.
pub container_state_id: u32,
/// Carried (cursor) stack for the survival inventory, driven by the server.
pub cursor_item: azalea_inventory::ItemStack,
/// Whether the survival inventory was open last frame, to detect the close
/// transition and send a container-close packet.
pub inventory_was_open: bool,
/// Active survival click-drag (button + slots covered), if any.
pub inv_drag: Option<(azalea_inventory::operations::QuickCraftKind, Vec<u16>)>,
/// Last survival left click (slot, time) for double-click detection.
pub inv_last_click: Option<(u16, Instant)>,
/// Server registries, for hashing predicted container clicks.
pub registries: Arc<azalea_core::registry_holder::RegistryHolder>,
pub chat: ChatState,
pub command_tree: Option<Arc<crate::net::commands::CommandTree>>,
pub tab_list: TabList,
Expand Down Expand Up @@ -176,6 +189,12 @@ impl GameState {
inventory_open: false,
creative_inventory_open: false,
creative_state: crate::ui::creative_inventory::CreativeState::new(),
container_state_id: 0,
cursor_item: azalea_inventory::ItemStack::Empty,
inventory_was_open: false,
inv_drag: None,
inv_last_click: None,
registries: Arc::new(azalea_core::registry_holder::RegistryHolder::default()),
chat: ChatState::new(),
command_tree: None,
tab_list: TabList::new(),
Expand Down Expand Up @@ -593,6 +612,82 @@ fn apply_render_distance(
game.sync_render_distance(connection, rd);
}

/// Predict each survival container click locally (instant UI + drag preview),
/// then send the predicted diff as `HashedStack`es so the server suppresses
/// corrections when the prediction is right (vanilla lockstep).
fn send_container_clicks(
game: &mut GameState,
connection: &ConnectionHandle,
ops: Vec<azalea_inventory::operations::ClickOperation>,
) {
use azalea_inventory::ItemStack;
use azalea_inventory::operations::{
ClickOperation, QuickCraftClick, QuickCraftKind, QuickCraftStatus,
};
use azalea_protocol::packets::game::s_container_click::{
HashedStack, ServerboundContainerClick,
};

use crate::player::menu_click;

let mut drag_kind = QuickCraftKind::Left;
let mut drag_slots: Vec<u16> = Vec::new();
for op in &ops {
let (changed, carried): (Vec<(u16, ItemStack)>, ItemStack) = match op {
ClickOperation::QuickCraft(QuickCraftClick { kind, status }) => match status {
QuickCraftStatus::Start => {
drag_kind = kind.clone();
drag_slots.clear();
(Vec::new(), game.cursor_item.clone())
}
QuickCraftStatus::Add { slot } => {
drag_slots.push(*slot);
(Vec::new(), game.cursor_item.clone())
}
QuickCraftStatus::End => {
let (changed, remainder) = menu_click::drag_distribution(
&game.player.inventory,
&game.cursor_item,
&drag_kind,
&drag_slots,
);
for (s, item) in &changed {
game.player.inventory.set_slot(*s as usize, item.clone());
}
game.cursor_item = remainder.clone();
(changed, remainder)
}
},
other => {
let changed = menu_click::apply_click(
&mut game.player.inventory,
&mut game.cursor_item,
other,
);
(changed, game.cursor_item.clone())
}
};

let mut click = ServerboundContainerClick {
container_id: 0,
state_id: game.container_state_id,
slot_num: op.slot_num().map(|s| s as i16).unwrap_or(-999),
button_num: op.button_num(),
click_type: op.click_type(),
changed_slots: Default::default(),
carried_item: HashedStack::from_item_stack(&carried, &game.registries),
};
for (s, item) in &changed {
click
.changed_slots
.insert(*s, HashedStack::from_item_stack(item, &game.registries));
}
connection
.packet_tx
.send(ServerboundGamePacket::ContainerClick(click));
}
}

pub fn update_game(
core: &mut AppCore,
dt: f32,
Expand Down Expand Up @@ -1107,18 +1202,27 @@ pub fn update_game(

let mut player_preview = None;
if game.inventory_open {
let cursor = core.input.cursor_pos();
let clicked = core.input.left_just_pressed();
let input = crate::ui::inventory::InventoryInput {
left_pressed: core.input.left_just_pressed(),
right_pressed: core.input.right_just_pressed(),
left_held: core.input.left_held(),
right_held: core.input.right_held(),
shift: core.input.shift_held(),
};
let result = crate::ui::inventory::build_inventory(
&mut elements,
sw,
sh,
cursor,
clicked,
core.input.cursor_pos(),
&input,
&game.player.inventory,
&game.cursor_item,
&mut game.inv_drag,
&mut game.inv_last_click,
gs,
);
close_inventory = result.clicked_outside;
send_container_clicks(game, connection, result.ops);
player_preview = Some(result.player_preview);
core.input.clear_just_pressed_actions();
}
Expand Down Expand Up @@ -1408,6 +1512,21 @@ pub fn update_game(
core.apply_cursor_grab(&gfx.window, Some(game));
}

// Tell the server when the survival inventory closes so it returns/drops the
// cursor stack, and forget any in-flight gesture so a stale drag can't
// commit on reopen.
if game.inventory_was_open && !game.inventory_open {
use azalea_protocol::packets::game::s_container_close::ServerboundContainerClose;
connection
.packet_tx
.send(ServerboundGamePacket::ContainerClose(
ServerboundContainerClose { container_id: 0 },
));
game.inv_drag = None;
game.inv_last_click = None;
}
game.inventory_was_open = game.inventory_open;

match death_action {
DeathAction::Respawn => {
game.death_confirm = false;
Expand Down
5 changes: 5 additions & 0 deletions pomme-client/src/net/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,11 @@ async fn game_loop(
}
});

// Share the registries with the game loop for hashing predicted container
// clicks.
let registry_holder = std::sync::Arc::new(registry_holder);
let _ = event_tx.try_send(NetworkEvent::Registries(registry_holder.clone()));

loop {
match reader.read().await {
Ok(packet) => {
Expand Down
8 changes: 8 additions & 0 deletions pomme-client/src/net/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ pub fn handle_game_packet(
ClientboundGamePacket::ContainerSetContent(p) if p.container_id == 0 => {
let _ = event_tx.try_send(NetworkEvent::InventoryContent {
items: p.items.clone(),
carried: p.carried_item.clone(),
state_id: p.state_id,
});
}
ClientboundGamePacket::SetCursorItem(p) => {
let _ = event_tx.try_send(NetworkEvent::CursorItem {
item: p.contents.clone(),
});
}
ClientboundGamePacket::ContainerSetSlot(p)
Expand All @@ -153,6 +160,7 @@ pub fn handle_game_packet(
let _ = event_tx.try_send(NetworkEvent::InventorySlot {
index: p.slot,
item: p.item_stack.clone(),
state_id: p.state_id,
});
}
ClientboundGamePacket::SetHealth(p) => {
Expand Down
7 changes: 7 additions & 0 deletions pomme-client/src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::entity::components::Position;

pub enum NetworkEvent {
Connected,
Registries(Arc<azalea_core::registry_holder::RegistryHolder>),
BiomeColors {
colors: std::collections::HashMap<u32, crate::renderer::chunk::mesher::BiomeClimate>,
},
Expand Down Expand Up @@ -59,10 +60,16 @@ pub enum NetworkEvent {
},
InventoryContent {
items: Vec<ItemStack>,
carried: ItemStack,
state_id: u32,
},
InventorySlot {
index: u16,
item: ItemStack,
state_id: u32,
},
CursorItem {
item: ItemStack,
},
ChatMessage {
spans: Vec<crate::ui::text::TextSpan>,
Expand Down
14 changes: 7 additions & 7 deletions pomme-client/src/player/inventory.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use azalea_inventory::ItemStack;
use azalea_registry::builtin::ItemKind;

const PLAYER_SLOTS: usize = 46;
const HOTBAR_START: usize = 36;
pub const PLAYER_SLOTS: usize = 46;
pub const HOTBAR_START: usize = 36;
const HOTBAR_END: usize = 45;
const MAIN_START: usize = 9;
pub const MAIN_START: usize = 9;
const MAIN_END: usize = 36;
const ARMOR_START: usize = 5;
pub const ARMOR_START: usize = 5;
const ARMOR_END: usize = 9;
const CRAFT_INPUT_START: usize = 1;
pub const CRAFT_INPUT_START: usize = 1;
const CRAFT_INPUT_END: usize = 5;
const CRAFT_OUTPUT: usize = 0;
const OFFHAND: usize = 45;
pub const CRAFT_OUTPUT: usize = 0;
pub const OFFHAND: usize = 45;

pub struct Inventory {
slots: Vec<ItemStack>,
Expand Down
Loading
Loading