mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 14:31:30 +11:00
Update to Bevy 0.10 (#265)
## Description Fix #264 Bevy version is pinned to a recent commit on the main branch until 0.10 is released. ## Test Plan Run examples and tests. Behavior should be the same. scheduling is a bit tricky so I may have made some subtle mistakes.
This commit is contained in:
parent
cc2571d7e6
commit
62f882eec7
|
@ -9,7 +9,7 @@ const SPAWN_Y: i32 = 64;
|
|||
|
||||
pub fn build_app(app: &mut App) {
|
||||
app.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system(default_event_handler.in_schedule(EventLoopSchedule))
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_system(despawn_disconnected_clients);
|
||||
|
|
|
@ -16,8 +16,8 @@ anyhow = "1.0.65"
|
|||
arrayvec = "0.7.2"
|
||||
async-trait = "0.1.60"
|
||||
base64 = "0.21.0"
|
||||
bevy_app = "0.9.1"
|
||||
bevy_ecs = "0.9.1"
|
||||
bevy_app = { git = "https://github.com/bevyengine/bevy/", rev = "2ea00610188dce1eba1172a3ded8244570189230" }
|
||||
bevy_ecs = { git = "https://github.com/bevyengine/bevy/", rev = "2ea00610188dce1eba1172a3ded8244570189230" }
|
||||
bitfield-struct = "0.3.1"
|
||||
bytes = "1.2.1"
|
||||
flume = "0.10.14"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use bevy_app::{App, CoreStage};
|
||||
use valence::client::despawn_disconnected_clients;
|
||||
use valence::client::event::default_event_handler;
|
||||
use valence::instance::{Chunk, Instance};
|
||||
|
@ -22,12 +21,14 @@ fn main() {
|
|||
.with_max_connections(50_000),
|
||||
)
|
||||
.add_startup_system(setup)
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_to_stage(CoreStage::First, record_tick_start_time)
|
||||
.add_system_to_stage(CoreStage::Last, print_tick_time)
|
||||
.add_system(init_clients)
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_systems((
|
||||
default_event_handler.in_schedule(EventLoopSchedule),
|
||||
record_tick_start_time.in_base_set(CoreSet::First),
|
||||
print_tick_time.in_base_set(CoreSet::Last),
|
||||
init_clients,
|
||||
despawn_disconnected_clients,
|
||||
))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -32,11 +32,13 @@ pub fn main() {
|
|||
.collect::<Vec<_>>(),
|
||||
),
|
||||
)
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_systems((
|
||||
default_event_handler.in_schedule(EventLoopSchedule),
|
||||
init_clients,
|
||||
despawn_disconnected_clients,
|
||||
))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,12 +13,14 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_to_stage(EventLoop, event_handler)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_systems((
|
||||
default_event_handler.in_schedule(EventLoopSchedule),
|
||||
event_handler.in_schedule(EventLoopSchedule),
|
||||
init_clients,
|
||||
despawn_disconnected_clients,
|
||||
))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -12,15 +12,20 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_to_stage(EventLoop, toggle_gamemode_on_sneak)
|
||||
.add_system_to_stage(EventLoop, digging_creative_mode)
|
||||
.add_system_to_stage(EventLoop, digging_survival_mode)
|
||||
.add_system_to_stage(EventLoop, place_blocks)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_systems(
|
||||
(
|
||||
default_event_handler,
|
||||
toggle_gamemode_on_sneak,
|
||||
digging_creative_mode,
|
||||
digging_survival_mode,
|
||||
place_blocks,
|
||||
)
|
||||
.in_schedule(EventLoopSchedule),
|
||||
)
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -12,12 +12,17 @@ pub fn main() {
|
|||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()).with_connection_mode(ConnectionMode::Offline))
|
||||
.add_startup_system(setup)
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_to_stage(EventLoop, handle_message_events)
|
||||
.add_system_to_stage(EventLoop, handle_command_events)
|
||||
.add_system(init_clients)
|
||||
.add_systems(
|
||||
(
|
||||
default_event_handler,
|
||||
handle_message_events,
|
||||
handle_command_events,
|
||||
)
|
||||
.in_schedule(EventLoopSchedule),
|
||||
)
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.run();
|
||||
}
|
||||
|
||||
|
@ -65,9 +70,10 @@ fn handle_message_events(mut clients: Query<&mut Client>, mut messages: EventRea
|
|||
.color(Color::YELLOW)
|
||||
+ message.into_text().not_bold().color(Color::WHITE);
|
||||
|
||||
clients.par_for_each_mut(16, |mut client| {
|
||||
// TODO: write message to instance buffer.
|
||||
for mut client in &mut clients {
|
||||
client.send_message(formatted.clone());
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,13 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_to_stage(EventLoop, toggle_gamemode_on_sneak)
|
||||
.add_system_to_stage(EventLoop, open_chest)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_systems(
|
||||
(default_event_handler, toggle_gamemode_on_sneak, open_chest)
|
||||
.in_schedule(EventLoopSchedule),
|
||||
)
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.run();
|
||||
}
|
||||
|
|
|
@ -22,11 +22,10 @@ pub fn main() {
|
|||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_startup_system(setup)
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_to_stage(EventLoop, handle_combat_events)
|
||||
.add_system(init_clients)
|
||||
.add_systems((default_event_handler, handle_combat_events).in_schedule(EventLoopSchedule))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_system(teleport_oob_clients)
|
||||
.run();
|
||||
}
|
||||
|
|
|
@ -27,15 +27,16 @@ pub fn main() {
|
|||
grass_color: Some(0x00ff00),
|
||||
..Default::default()
|
||||
}]))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_system_to_stage(EventLoop, toggle_cell_on_dig)
|
||||
.add_system(update_board)
|
||||
.add_system(pause_on_crouch)
|
||||
.add_system(reset_oob_clients)
|
||||
.add_systems((default_event_handler, toggle_cell_on_dig).in_schedule(EventLoopSchedule))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_systems((
|
||||
despawn_disconnected_clients,
|
||||
update_board,
|
||||
pause_on_crouch,
|
||||
reset_oob_clients,
|
||||
))
|
||||
.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -24,12 +24,12 @@ fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_system(default_event_handler.in_schedule(EventLoopSchedule))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_system(update_sphere)
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,12 +10,12 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_to_stage(EventLoop, squat_and_die)
|
||||
.add_system_to_stage(EventLoop, necromancy)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_systems(
|
||||
(default_event_handler, squat_and_die, necromancy).in_schedule(EventLoopSchedule),
|
||||
)
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.run();
|
||||
}
|
||||
|
|
|
@ -9,11 +9,10 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_to_stage(EventLoop, interpret_command)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_systems((default_event_handler, interpret_command).in_schedule(EventLoopSchedule))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.run();
|
||||
}
|
||||
|
|
|
@ -28,13 +28,16 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_system(init_clients)
|
||||
.add_system(default_event_handler.in_schedule(EventLoopSchedule))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_systems((
|
||||
reset_clients.after(init_clients),
|
||||
manage_chunks.after(reset_clients).before(manage_blocks),
|
||||
manage_blocks,
|
||||
despawn_disconnected_clients,
|
||||
))
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_system(reset_clients.after(init_clients))
|
||||
.add_system(manage_chunks.after(reset_clients).before(manage_blocks))
|
||||
.add_system(manage_blocks)
|
||||
.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,10 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_system(default_event_handler.in_schedule(EventLoopSchedule))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_system(manage_particles)
|
||||
.run();
|
||||
|
|
|
@ -14,11 +14,14 @@ fn main() {
|
|||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_startup_system(setup)
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system(init_clients)
|
||||
.add_system(update_player_list)
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_system(remove_disconnected_clients_from_player_list)
|
||||
.add_system(default_event_handler.in_schedule(EventLoopSchedule))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_systems((
|
||||
init_clients,
|
||||
update_player_list,
|
||||
remove_disconnected_clients_from_player_list,
|
||||
despawn_disconnected_clients,
|
||||
))
|
||||
.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -12,12 +12,17 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_to_stage(EventLoop, prompt_on_punch)
|
||||
.add_system_to_stage(EventLoop, on_resource_pack_status)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_systems(
|
||||
(
|
||||
default_event_handler,
|
||||
prompt_on_punch,
|
||||
on_resource_pack_status,
|
||||
)
|
||||
.in_schedule(EventLoopSchedule),
|
||||
)
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.run();
|
||||
}
|
||||
|
@ -33,7 +38,7 @@ fn setup(mut commands: Commands, server: Res<Server>) {
|
|||
|
||||
for z in -25..25 {
|
||||
for x in -25..25 {
|
||||
instance.set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
||||
instance.set_block([x, SPAWN_Y, z], BlockState::BEDROCK);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +46,8 @@ fn setup(mut commands: Commands, server: Res<Server>) {
|
|||
|
||||
let mut sheep = McEntity::new(EntityKind::Sheep, instance_ent);
|
||||
sheep.set_position([0.0, SPAWN_Y as f64 + 1.0, 2.0]);
|
||||
sheep.set_yaw(180.0);
|
||||
sheep.set_head_yaw(180.0);
|
||||
commands.spawn(sheep);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,14 +43,19 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_system(remove_unviewed_chunks.after(init_clients))
|
||||
.add_system(update_client_views.after(remove_unviewed_chunks))
|
||||
.add_system(send_recv_chunks.after(update_client_views))
|
||||
.add_system(default_event_handler.in_schedule(EventLoopSchedule))
|
||||
.add_systems(
|
||||
(
|
||||
init_clients,
|
||||
remove_unviewed_chunks,
|
||||
update_client_views,
|
||||
send_recv_chunks,
|
||||
)
|
||||
.chain(),
|
||||
)
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,10 +10,10 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_system(default_event_handler.in_schedule(EventLoopSchedule))
|
||||
.add_systems(PlayerList::default_systems())
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.run();
|
||||
}
|
||||
|
|
|
@ -633,32 +633,33 @@ pub(crate) fn update_clients(
|
|||
instances: Query<&Instance>,
|
||||
entities: Query<&McEntity>,
|
||||
) {
|
||||
// TODO: what batch size to use?
|
||||
clients.par_for_each_mut(16, |(entity_id, mut client, self_entity)| {
|
||||
if !client.is_disconnected() {
|
||||
if let Err(e) = update_one_client(
|
||||
&mut client,
|
||||
self_entity,
|
||||
entity_id,
|
||||
&instances,
|
||||
&entities,
|
||||
&server,
|
||||
) {
|
||||
client.write_packet(&DisconnectS2c {
|
||||
reason: Text::from("").into(),
|
||||
});
|
||||
client.is_disconnected = true;
|
||||
warn!(
|
||||
username = %client.username,
|
||||
uuid = %client.uuid,
|
||||
ip = %client.ip,
|
||||
"error updating client: {e:#}"
|
||||
);
|
||||
clients
|
||||
.par_iter_mut()
|
||||
.for_each_mut(|(entity_id, mut client, self_entity)| {
|
||||
if !client.is_disconnected() {
|
||||
if let Err(e) = update_one_client(
|
||||
&mut client,
|
||||
self_entity,
|
||||
entity_id,
|
||||
&instances,
|
||||
&entities,
|
||||
&server,
|
||||
) {
|
||||
client.write_packet(&DisconnectS2c {
|
||||
reason: Text::from("").into(),
|
||||
});
|
||||
client.is_disconnected = true;
|
||||
warn!(
|
||||
username = %client.username,
|
||||
uuid = %client.uuid,
|
||||
ip = %client.ip,
|
||||
"error updating client: {e:#}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.is_new = false;
|
||||
});
|
||||
client.is_new = false;
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -2,8 +2,7 @@ use std::cmp;
|
|||
|
||||
use anyhow::bail;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::schedule::ShouldRun;
|
||||
use bevy_ecs::system::SystemParam;
|
||||
use bevy_ecs::system::{SystemParam, SystemState};
|
||||
use glam::{DVec3, Vec3};
|
||||
use paste::paste;
|
||||
use tracing::warn;
|
||||
|
@ -34,6 +33,7 @@ use valence_protocol::types::{Difficulty, Direction, Hand};
|
|||
use crate::client::Client;
|
||||
use crate::entity::{EntityAnimation, EntityKind, McEntity, TrackedData};
|
||||
use crate::inventory::Inventory;
|
||||
use crate::server::EventLoopSchedule;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct QueryBlockNbt {
|
||||
|
@ -635,57 +635,62 @@ events! {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn event_loop_run_criteria(
|
||||
mut clients: Query<(Entity, &mut Client, &mut Inventory)>,
|
||||
/// An exclusive system for running the event loop schedule.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn run_event_loop(
|
||||
world: &mut World,
|
||||
state: &mut SystemState<(Query<(Entity, &mut Client, &mut Inventory)>, ClientEvents)>,
|
||||
mut clients_to_check: Local<Vec<Entity>>,
|
||||
mut events: ClientEvents,
|
||||
) -> ShouldRun {
|
||||
if clients_to_check.is_empty() {
|
||||
// First run of the criteria. Prepare packets.
|
||||
) {
|
||||
let (mut clients, mut events) = state.get_mut(world);
|
||||
|
||||
update_all_event_buffers(&mut events);
|
||||
update_all_event_buffers(&mut events);
|
||||
|
||||
for (entity, client, inventory) in &mut clients {
|
||||
let client = client.into_inner();
|
||||
let inventory = inventory.into_inner();
|
||||
for (entity, client, inventory) in &mut clients {
|
||||
let client = client.into_inner();
|
||||
let inventory = inventory.into_inner();
|
||||
|
||||
let Ok(bytes) = client.conn.try_recv() else {
|
||||
// Client is disconnected.
|
||||
client.is_disconnected = true;
|
||||
continue;
|
||||
};
|
||||
let Ok(bytes) = client.conn.try_recv() else {
|
||||
// Client is disconnected.
|
||||
client.is_disconnected = true;
|
||||
continue;
|
||||
};
|
||||
|
||||
if bytes.is_empty() {
|
||||
// No data was received.
|
||||
continue;
|
||||
if bytes.is_empty() {
|
||||
// No data was received.
|
||||
continue;
|
||||
}
|
||||
|
||||
client.dec.queue_bytes(bytes);
|
||||
|
||||
match handle_one_packet(client, inventory, entity, &mut events) {
|
||||
Ok(had_packet) => {
|
||||
if had_packet {
|
||||
// We decoded one packet, but there might be more.
|
||||
clients_to_check.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
client.dec.queue_bytes(bytes);
|
||||
|
||||
match handle_one_packet(client, inventory, entity, &mut events) {
|
||||
Ok(had_packet) => {
|
||||
if had_packet {
|
||||
// We decoded one packet, but there might be more.
|
||||
clients_to_check.push(entity);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
username = %client.username,
|
||||
uuid = %client.uuid,
|
||||
ip = %client.ip,
|
||||
"failed to dispatch events: {e:#}"
|
||||
);
|
||||
client.is_disconnected = true;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
username = %client.username,
|
||||
uuid = %client.uuid,
|
||||
ip = %client.ip,
|
||||
"failed to dispatch events: {e:#}"
|
||||
);
|
||||
client.is_disconnected = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Continue to filter the list of clients we need to check until there are none
|
||||
// left.
|
||||
}
|
||||
|
||||
// Keep looping until all serverbound packets are decoded.
|
||||
while !clients_to_check.is_empty() {
|
||||
world.run_schedule(EventLoopSchedule);
|
||||
|
||||
let (mut clients, mut events) = state.get_mut(world);
|
||||
|
||||
clients_to_check.retain(|&entity| {
|
||||
let Ok((_, mut client, mut inventory)) = clients.get_mut(entity) else {
|
||||
// Client was deleted during the last run of the stage.
|
||||
// Client must have been deleted during the last run of the schedule.
|
||||
return false;
|
||||
};
|
||||
|
||||
|
@ -705,12 +710,6 @@ pub(crate) fn event_loop_run_criteria(
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
if clients_to_check.is_empty() {
|
||||
ShouldRun::No
|
||||
} else {
|
||||
ShouldRun::YesAndCheckAgain
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_one_packet(
|
||||
|
@ -1353,8 +1352,8 @@ fn handle_one_packet(
|
|||
/// is subject to change.
|
||||
///
|
||||
/// This system must be scheduled to run in the
|
||||
/// [`EventLoop`](crate::server::EventLoop) stage. Otherwise, it may not
|
||||
/// function correctly.
|
||||
/// [`EventLoopSchedule`](crate::server::EventLoopSchedule). Otherwise, it may
|
||||
/// not function correctly.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn default_event_handler(
|
||||
mut clients: Query<(&mut Client, Option<&mut McEntity>)>,
|
||||
|
|
|
@ -93,16 +93,6 @@ pub(crate) fn update_entities(mut entities: Query<&mut McEntity, Changed<McEntit
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_entity_invariants(removed: RemovedComponents<McEntity>) {
|
||||
for entity in &removed {
|
||||
warn!(
|
||||
entity = ?entity,
|
||||
"A `McEntity` component was removed from the world directly. You must use the \
|
||||
`Despawned` marker component instead."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A component for Minecraft entities. For Valence to recognize a
|
||||
/// Minecraft entity, it must have this component attached.
|
||||
///
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::borrow::Cow;
|
|||
use std::iter::FusedIterator;
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::schedule::SystemConfigs;
|
||||
use tracing::{debug, warn};
|
||||
use valence_protocol::item::ItemStack;
|
||||
use valence_protocol::packet::s2c::play::{
|
||||
|
@ -16,6 +17,24 @@ use crate::client::event::{
|
|||
};
|
||||
use crate::client::Client;
|
||||
|
||||
/// The systems needed for updating the inventories.
|
||||
pub(crate) fn update_inventories() -> SystemConfigs {
|
||||
(
|
||||
handle_set_held_item,
|
||||
update_open_inventories,
|
||||
handle_close_container,
|
||||
update_client_on_close_inventory.after(update_open_inventories),
|
||||
update_player_inventories,
|
||||
handle_click_container
|
||||
.before(update_open_inventories)
|
||||
.before(update_player_inventories),
|
||||
handle_set_slot_creative
|
||||
.before(update_open_inventories)
|
||||
.before(update_player_inventories),
|
||||
)
|
||||
.into_configs()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Component)]
|
||||
pub struct Inventory {
|
||||
title: Text,
|
||||
|
@ -115,7 +134,7 @@ impl Inventory {
|
|||
}
|
||||
|
||||
/// Send updates for each client's player inventory.
|
||||
pub(crate) fn update_player_inventories(
|
||||
fn update_player_inventories(
|
||||
mut query: Query<(&mut Inventory, &mut Client), Without<OpenInventory>>,
|
||||
) {
|
||||
for (mut inventory, mut client) in query.iter_mut() {
|
||||
|
@ -180,6 +199,292 @@ pub(crate) fn update_player_inventories(
|
|||
}
|
||||
}
|
||||
|
||||
/// Used to indicate that the client with this component is currently viewing
|
||||
/// an inventory.
|
||||
#[derive(Debug, Clone, Component)]
|
||||
pub struct OpenInventory {
|
||||
/// The Entity with the `Inventory` component that the client is currently
|
||||
/// viewing.
|
||||
pub(crate) entity: Entity,
|
||||
client_modified: u64,
|
||||
}
|
||||
|
||||
impl OpenInventory {
|
||||
pub fn new(entity: Entity) -> Self {
|
||||
OpenInventory {
|
||||
entity,
|
||||
client_modified: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entity(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the `OpenInventory` component being added to a client, which
|
||||
/// indicates that the client is now viewing an inventory, and sends inventory
|
||||
/// updates to the client when the inventory is modified.
|
||||
fn update_open_inventories(
|
||||
mut commands: Commands,
|
||||
mut clients: Query<(Entity, &mut Client, &mut OpenInventory)>,
|
||||
mut inventories: Query<&mut Inventory>,
|
||||
) {
|
||||
// These operations need to happen in this order.
|
||||
|
||||
// send the inventory contents to all clients that are viewing an inventory
|
||||
for (client_entity, mut client, mut open_inventory) in clients.iter_mut() {
|
||||
// validate that the inventory exists
|
||||
let Ok(inventory) = inventories.get_component::<Inventory>(open_inventory.entity) else {
|
||||
// the inventory no longer exists, so close the inventory
|
||||
commands.entity(client_entity).remove::<OpenInventory>();
|
||||
let window_id = client.window_id;
|
||||
client.write_packet(&CloseScreenS2c {
|
||||
window_id,
|
||||
});
|
||||
continue;
|
||||
};
|
||||
|
||||
if open_inventory.is_added() {
|
||||
// send the inventory to the client if the client just opened the inventory
|
||||
client.window_id = client.window_id % 100 + 1;
|
||||
open_inventory.client_modified = 0;
|
||||
|
||||
let packet = OpenScreenS2c {
|
||||
window_id: VarInt(client.window_id.into()),
|
||||
window_type: WindowType::from(inventory.kind),
|
||||
window_title: (&inventory.title).into(),
|
||||
};
|
||||
client.write_packet(&packet);
|
||||
|
||||
let packet = InventoryS2c {
|
||||
window_id: client.window_id,
|
||||
state_id: VarInt(client.inventory_state_id.0),
|
||||
slots: Cow::Borrowed(inventory.slot_slice()),
|
||||
// TODO: eliminate clone?
|
||||
carried_item: Cow::Owned(client.cursor_item.clone()),
|
||||
};
|
||||
client.write_packet(&packet);
|
||||
} else {
|
||||
// the client is already viewing the inventory
|
||||
if inventory.modified == u64::MAX {
|
||||
// send the entire inventory
|
||||
client.inventory_state_id += 1;
|
||||
let packet = InventoryS2c {
|
||||
window_id: client.window_id,
|
||||
state_id: VarInt(client.inventory_state_id.0),
|
||||
slots: Cow::Borrowed(inventory.slot_slice()),
|
||||
// TODO: eliminate clone?
|
||||
carried_item: Cow::Owned(client.cursor_item.clone()),
|
||||
};
|
||||
client.write_packet(&packet);
|
||||
} else {
|
||||
// send the modified slots
|
||||
let window_id = client.window_id as i8;
|
||||
// The slots that were NOT modified by this client, and they need to be sent
|
||||
let modified_filtered = inventory.modified & !open_inventory.client_modified;
|
||||
if modified_filtered != 0 {
|
||||
client.inventory_state_id += 1;
|
||||
let state_id = client.inventory_state_id.0;
|
||||
for (i, slot) in inventory.slots.iter().enumerate() {
|
||||
if (modified_filtered >> i) & 1 == 1 {
|
||||
client.write_packet(&ScreenHandlerSlotUpdateS2c {
|
||||
window_id,
|
||||
state_id: VarInt(state_id),
|
||||
slot_idx: i as i16,
|
||||
slot_data: Cow::Borrowed(slot),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open_inventory.client_modified = 0;
|
||||
client.inventory_slots_modified = 0;
|
||||
}
|
||||
|
||||
// reset the modified flag
|
||||
for (_, _, open_inventory) in clients.iter_mut() {
|
||||
// validate that the inventory exists
|
||||
if let Ok(mut inventory) = inventories.get_component_mut::<Inventory>(open_inventory.entity)
|
||||
{
|
||||
inventory.modified = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles clients telling the server that they are closing an inventory.
|
||||
fn handle_close_container(mut commands: Commands, mut events: EventReader<CloseHandledScreen>) {
|
||||
for event in events.iter() {
|
||||
commands.entity(event.client).remove::<OpenInventory>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects when a client's `OpenInventory` component is removed, which
|
||||
/// indicates that the client is no longer viewing an inventory.
|
||||
fn update_client_on_close_inventory(
|
||||
mut removals: RemovedComponents<OpenInventory>,
|
||||
mut clients: Query<&mut Client>,
|
||||
) {
|
||||
for entity in &mut removals {
|
||||
if let Ok(mut client) = clients.get_component_mut::<Client>(entity) {
|
||||
let window_id = client.window_id;
|
||||
client.write_packet(&CloseScreenS2c { window_id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_click_container(
|
||||
mut clients: Query<(&mut Client, &mut Inventory, Option<&mut OpenInventory>)>,
|
||||
mut inventories: Query<&mut Inventory, Without<Client>>,
|
||||
mut events: EventReader<ClickSlot>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
let Ok((mut client, mut client_inventory, mut open_inventory)) =
|
||||
clients.get_mut(event.client) else {
|
||||
// the client does not exist, ignore
|
||||
continue;
|
||||
};
|
||||
|
||||
// validate the window id
|
||||
if (event.window_id == 0) != open_inventory.is_none() {
|
||||
warn!(
|
||||
"Client sent a click with an invalid window id for current state: window_id = {}, \
|
||||
open_inventory present = {}",
|
||||
event.window_id,
|
||||
open_inventory.is_some()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(open_inventory) = open_inventory.as_mut() {
|
||||
// the player is interacting with an inventory that is open
|
||||
let Ok(mut target_inventory) = inventories.get_component_mut::<Inventory>(open_inventory.entity) else {
|
||||
// the inventory does not exist, ignore
|
||||
continue;
|
||||
};
|
||||
if client.inventory_state_id.0 != event.state_id {
|
||||
// client is out of sync, resync, ignore click
|
||||
debug!("Client state id mismatch, resyncing");
|
||||
client.inventory_state_id += 1;
|
||||
let packet = InventoryS2c {
|
||||
window_id: client.window_id,
|
||||
state_id: VarInt(client.inventory_state_id.0),
|
||||
slots: Cow::Borrowed(target_inventory.slot_slice()),
|
||||
// TODO: eliminate clone?
|
||||
carried_item: Cow::Owned(client.cursor_item.clone()),
|
||||
};
|
||||
client.write_packet(&packet);
|
||||
continue;
|
||||
}
|
||||
|
||||
client.cursor_item = event.carried_item.clone();
|
||||
|
||||
for slot in event.slot_changes.clone() {
|
||||
if (0i16..target_inventory.slot_count() as i16).contains(&slot.idx) {
|
||||
// the client is interacting with a slot in the target inventory
|
||||
target_inventory.replace_slot(slot.idx as u16, slot.item);
|
||||
open_inventory.client_modified |= 1 << slot.idx;
|
||||
} else {
|
||||
// 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_inventory.replace_slot(slot_id, slot.item);
|
||||
client.inventory_slots_modified |= 1 << slot_id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// the client is interacting with their own inventory
|
||||
|
||||
if client.inventory_state_id.0 != event.state_id {
|
||||
// client is out of sync, resync, and ignore the click
|
||||
debug!("Client state id mismatch, resyncing");
|
||||
client.inventory_state_id += 1;
|
||||
let packet = InventoryS2c {
|
||||
window_id: client.window_id,
|
||||
state_id: VarInt(client.inventory_state_id.0),
|
||||
slots: Cow::Borrowed(client_inventory.slot_slice()),
|
||||
// TODO: eliminate clone?
|
||||
carried_item: Cow::Owned(client.cursor_item.clone()),
|
||||
};
|
||||
client.write_packet(&packet);
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: do more validation on the click
|
||||
client.cursor_item = event.carried_item.clone();
|
||||
for slot in event.slot_changes.clone() {
|
||||
if (0i16..client_inventory.slot_count() as i16).contains(&slot.idx) {
|
||||
client_inventory.replace_slot(slot.idx as u16, slot.item);
|
||||
client.inventory_slots_modified |= 1 << slot.idx;
|
||||
} else {
|
||||
// the client is trying to interact with a slot that does not exist,
|
||||
// ignore
|
||||
warn!(
|
||||
"Client attempted to interact with slot {} which does not exist",
|
||||
slot.idx
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_set_slot_creative(
|
||||
mut clients: Query<(&mut Client, &mut Inventory)>,
|
||||
mut events: EventReader<CreativeInventoryAction>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
if let Ok((mut client, mut inventory)) = clients.get_mut(event.client) {
|
||||
if client.game_mode() != GameMode::Creative {
|
||||
// the client is not in creative mode, ignore
|
||||
continue;
|
||||
}
|
||||
if event.slot < 0 || event.slot >= inventory.slot_count() as i16 {
|
||||
// the client is trying to interact with a slot that does not exist, ignore
|
||||
continue;
|
||||
}
|
||||
inventory.replace_slot(event.slot as u16, event.clicked_item.clone());
|
||||
inventory.modified &= !(1 << event.slot); // clear the modified bit, since we are about to send the update
|
||||
client.inventory_state_id += 1;
|
||||
let state_id = client.inventory_state_id.0;
|
||||
// HACK: notchian clients rely on the server to send the slot update when in
|
||||
// creative mode Simply marking the slot as modified is not enough. This was
|
||||
// discovered because shift-clicking the destroy item slot in creative mode does
|
||||
// not work without this hack.
|
||||
client.write_packet(&ScreenHandlerSlotUpdateS2c {
|
||||
window_id: 0,
|
||||
state_id: VarInt(state_id),
|
||||
slot_idx: event.slot,
|
||||
slot_data: Cow::Borrowed(&event.clicked_item),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_set_held_item(
|
||||
mut clients: Query<&mut Client>,
|
||||
mut events: EventReader<UpdateSelectedSlot>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
if let Ok(mut client) = clients.get_mut(event.client) {
|
||||
client.held_item_slot = convert_hotbar_slot_id(event.slot as u16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a slot that is outside a target inventory's range to a slot that is
|
||||
/// inside the player's inventory.
|
||||
fn convert_to_player_slot_id(target_kind: InventoryKind, slot_id: u16) -> u16 {
|
||||
// the first slot in the player's general inventory
|
||||
let offset = target_kind.slot_count() as u16;
|
||||
slot_id - offset + 9
|
||||
}
|
||||
|
||||
fn convert_hotbar_slot_id(slot_id: u16) -> u16 {
|
||||
slot_id + 36
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum InventoryKind {
|
||||
Generic9x1,
|
||||
|
@ -308,295 +613,6 @@ impl From<WindowType> for InventoryKind {
|
|||
}
|
||||
}
|
||||
|
||||
/// Used to indicate that the client with this component is currently viewing
|
||||
/// an inventory.
|
||||
#[derive(Debug, Clone, Component)]
|
||||
pub struct OpenInventory {
|
||||
/// The Entity with the `Inventory` component that the client is currently
|
||||
/// viewing.
|
||||
pub(crate) entity: Entity,
|
||||
client_modified: u64,
|
||||
}
|
||||
|
||||
impl OpenInventory {
|
||||
pub fn new(entity: Entity) -> Self {
|
||||
OpenInventory {
|
||||
entity,
|
||||
client_modified: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entity(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the `OpenInventory` component being added to a client, which
|
||||
/// indicates that the client is now viewing an inventory, and sends inventory
|
||||
/// updates to the client when the inventory is modified.
|
||||
pub(crate) fn update_open_inventories(
|
||||
mut commands: Commands,
|
||||
mut clients: Query<(Entity, &mut Client, &mut OpenInventory)>,
|
||||
mut inventories: Query<&mut Inventory>,
|
||||
) {
|
||||
// These operations need to happen in this order.
|
||||
|
||||
// send the inventory contents to all clients that are viewing an inventory
|
||||
for (client_entity, mut client, mut open_inventory) in clients.iter_mut() {
|
||||
// validate that the inventory exists
|
||||
let Ok(inventory) = inventories.get_component::<Inventory>(open_inventory.entity) else {
|
||||
// the inventory no longer exists, so close the inventory
|
||||
commands.entity(client_entity).remove::<OpenInventory>();
|
||||
let window_id = client.window_id;
|
||||
client.write_packet(&CloseScreenS2c {
|
||||
window_id,
|
||||
});
|
||||
continue;
|
||||
};
|
||||
|
||||
if open_inventory.is_added() {
|
||||
// send the inventory to the client if the client just opened the inventory
|
||||
client.window_id = client.window_id % 100 + 1;
|
||||
open_inventory.client_modified = 0;
|
||||
|
||||
let packet = OpenScreenS2c {
|
||||
window_id: VarInt(client.window_id.into()),
|
||||
window_type: WindowType::from(inventory.kind),
|
||||
window_title: (&inventory.title).into(),
|
||||
};
|
||||
client.write_packet(&packet);
|
||||
|
||||
let packet = InventoryS2c {
|
||||
window_id: client.window_id,
|
||||
state_id: VarInt(client.inventory_state_id.0),
|
||||
slots: Cow::Borrowed(inventory.slot_slice()),
|
||||
// TODO: eliminate clone?
|
||||
carried_item: Cow::Owned(client.cursor_item.clone()),
|
||||
};
|
||||
client.write_packet(&packet);
|
||||
} else {
|
||||
// the client is already viewing the inventory
|
||||
if inventory.modified == u64::MAX {
|
||||
// send the entire inventory
|
||||
client.inventory_state_id += 1;
|
||||
let packet = InventoryS2c {
|
||||
window_id: client.window_id,
|
||||
state_id: VarInt(client.inventory_state_id.0),
|
||||
slots: Cow::Borrowed(inventory.slot_slice()),
|
||||
// TODO: eliminate clone?
|
||||
carried_item: Cow::Owned(client.cursor_item.clone()),
|
||||
};
|
||||
client.write_packet(&packet);
|
||||
} else {
|
||||
// send the modified slots
|
||||
let window_id = client.window_id as i8;
|
||||
// The slots that were NOT modified by this client, and they need to be sent
|
||||
let modified_filtered = inventory.modified & !open_inventory.client_modified;
|
||||
if modified_filtered != 0 {
|
||||
client.inventory_state_id += 1;
|
||||
let state_id = client.inventory_state_id.0;
|
||||
for (i, slot) in inventory.slots.iter().enumerate() {
|
||||
if (modified_filtered >> i) & 1 == 1 {
|
||||
client.write_packet(&ScreenHandlerSlotUpdateS2c {
|
||||
window_id,
|
||||
state_id: VarInt(state_id),
|
||||
slot_idx: i as i16,
|
||||
slot_data: Cow::Borrowed(slot),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open_inventory.client_modified = 0;
|
||||
client.inventory_slots_modified = 0;
|
||||
}
|
||||
|
||||
// reset the modified flag
|
||||
for (_, _, open_inventory) in clients.iter_mut() {
|
||||
// validate that the inventory exists
|
||||
if let Ok(mut inventory) = inventories.get_component_mut::<Inventory>(open_inventory.entity)
|
||||
{
|
||||
inventory.modified = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles clients telling the server that they are closing an inventory.
|
||||
pub(crate) fn handle_close_container(
|
||||
mut commands: Commands,
|
||||
mut events: EventReader<CloseHandledScreen>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
commands.entity(event.client).remove::<OpenInventory>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects when a client's `OpenInventory` component is removed, which
|
||||
/// indicates that the client is no longer viewing an inventory.
|
||||
pub(crate) fn update_client_on_close_inventory(
|
||||
removals: RemovedComponents<OpenInventory>,
|
||||
mut clients: Query<&mut Client>,
|
||||
) {
|
||||
for entity in removals.iter() {
|
||||
if let Ok(mut client) = clients.get_component_mut::<Client>(entity) {
|
||||
let window_id = client.window_id;
|
||||
client.write_packet(&CloseScreenS2c { window_id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_click_container(
|
||||
mut clients: Query<(&mut Client, &mut Inventory, Option<&mut OpenInventory>)>,
|
||||
mut inventories: Query<&mut Inventory, Without<Client>>,
|
||||
mut events: EventReader<ClickSlot>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
let Ok((mut client, mut client_inventory, mut open_inventory)) =
|
||||
clients.get_mut(event.client) else {
|
||||
// the client does not exist, ignore
|
||||
continue;
|
||||
};
|
||||
|
||||
// validate the window id
|
||||
if (event.window_id == 0) != open_inventory.is_none() {
|
||||
warn!(
|
||||
"Client sent a click with an invalid window id for current state: window_id = {}, \
|
||||
open_inventory present = {}",
|
||||
event.window_id,
|
||||
open_inventory.is_some()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(open_inventory) = open_inventory.as_mut() {
|
||||
// the player is interacting with an inventory that is open
|
||||
let Ok(mut target_inventory) = inventories.get_component_mut::<Inventory>(open_inventory.entity) else {
|
||||
// the inventory does not exist, ignore
|
||||
continue;
|
||||
};
|
||||
if client.inventory_state_id.0 != event.state_id {
|
||||
// client is out of sync, resync, ignore click
|
||||
debug!("Client state id mismatch, resyncing");
|
||||
client.inventory_state_id += 1;
|
||||
let packet = InventoryS2c {
|
||||
window_id: client.window_id,
|
||||
state_id: VarInt(client.inventory_state_id.0),
|
||||
slots: Cow::Borrowed(target_inventory.slot_slice()),
|
||||
// TODO: eliminate clone?
|
||||
carried_item: Cow::Owned(client.cursor_item.clone()),
|
||||
};
|
||||
client.write_packet(&packet);
|
||||
continue;
|
||||
}
|
||||
|
||||
client.cursor_item = event.carried_item.clone();
|
||||
|
||||
for slot in event.slot_changes.clone() {
|
||||
if (0i16..target_inventory.slot_count() as i16).contains(&slot.idx) {
|
||||
// the client is interacting with a slot in the target inventory
|
||||
target_inventory.replace_slot(slot.idx as u16, slot.item);
|
||||
open_inventory.client_modified |= 1 << slot.idx;
|
||||
} else {
|
||||
// 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_inventory.replace_slot(slot_id, slot.item);
|
||||
client.inventory_slots_modified |= 1 << slot_id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// the client is interacting with their own inventory
|
||||
|
||||
if client.inventory_state_id.0 != event.state_id {
|
||||
// client is out of sync, resync, and ignore the click
|
||||
debug!("Client state id mismatch, resyncing");
|
||||
client.inventory_state_id += 1;
|
||||
let packet = InventoryS2c {
|
||||
window_id: client.window_id,
|
||||
state_id: VarInt(client.inventory_state_id.0),
|
||||
slots: Cow::Borrowed(client_inventory.slot_slice()),
|
||||
// TODO: eliminate clone?
|
||||
carried_item: Cow::Owned(client.cursor_item.clone()),
|
||||
};
|
||||
client.write_packet(&packet);
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: do more validation on the click
|
||||
client.cursor_item = event.carried_item.clone();
|
||||
for slot in event.slot_changes.clone() {
|
||||
if (0i16..client_inventory.slot_count() as i16).contains(&slot.idx) {
|
||||
client_inventory.replace_slot(slot.idx as u16, slot.item);
|
||||
client.inventory_slots_modified |= 1 << slot.idx;
|
||||
} else {
|
||||
// the client is trying to interact with a slot that does not exist,
|
||||
// ignore
|
||||
warn!(
|
||||
"Client attempted to interact with slot {} which does not exist",
|
||||
slot.idx
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_set_slot_creative(
|
||||
mut clients: Query<(&mut Client, &mut Inventory)>,
|
||||
mut events: EventReader<CreativeInventoryAction>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
if let Ok((mut client, mut inventory)) = clients.get_mut(event.client) {
|
||||
if client.game_mode() != GameMode::Creative {
|
||||
// the client is not in creative mode, ignore
|
||||
continue;
|
||||
}
|
||||
if event.slot < 0 || event.slot >= inventory.slot_count() as i16 {
|
||||
// the client is trying to interact with a slot that does not exist, ignore
|
||||
continue;
|
||||
}
|
||||
inventory.replace_slot(event.slot as u16, event.clicked_item.clone());
|
||||
inventory.modified &= !(1 << event.slot); // clear the modified bit, since we are about to send the update
|
||||
client.inventory_state_id += 1;
|
||||
let state_id = client.inventory_state_id.0;
|
||||
// HACK: notchian clients rely on the server to send the slot update when in
|
||||
// creative mode Simply marking the slot as modified is not enough. This was
|
||||
// discovered because shift-clicking the destroy item slot in creative mode does
|
||||
// not work without this hack.
|
||||
client.write_packet(&ScreenHandlerSlotUpdateS2c {
|
||||
window_id: 0,
|
||||
state_id: VarInt(state_id),
|
||||
slot_idx: event.slot,
|
||||
slot_data: Cow::Borrowed(&event.clicked_item),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_set_held_item(
|
||||
mut clients: Query<&mut Client>,
|
||||
mut events: EventReader<UpdateSelectedSlot>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
if let Ok(mut client) = clients.get_mut(event.client) {
|
||||
client.held_item_slot = convert_hotbar_slot_id(event.slot as u16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a slot that is outside a target inventory's range to a slot that is
|
||||
/// inside the player's inventory.
|
||||
fn convert_to_player_slot_id(target_kind: InventoryKind, slot_id: u16) -> u16 {
|
||||
// the first slot in the player's general inventory
|
||||
let offset = target_kind.slot_count() as u16;
|
||||
slot_id - offset + 9
|
||||
}
|
||||
|
||||
fn convert_hotbar_slot_id(slot_id: u16) -> u16 {
|
||||
slot_id + 36
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use bevy_app::App;
|
||||
|
|
|
@ -49,7 +49,7 @@ pub mod view;
|
|||
|
||||
pub mod prelude {
|
||||
pub use async_trait::async_trait;
|
||||
pub use bevy_app::App;
|
||||
pub use bevy_app::prelude::*;
|
||||
pub use bevy_ecs::prelude::*;
|
||||
pub use biome::{Biome, BiomeId};
|
||||
pub use client::Client;
|
||||
|
@ -70,7 +70,7 @@ pub mod prelude {
|
|||
pub use protocol::text::{Color, Text, TextFormat};
|
||||
pub use protocol::types::GameMode;
|
||||
pub use protocol::username::Username;
|
||||
pub use server::{EventLoop, NewClientInfo, Server, SharedServer};
|
||||
pub use server::{EventLoopSchedule, EventLoopSet, NewClientInfo, Server, SharedServer};
|
||||
pub use uuid::Uuid;
|
||||
pub use valence_nbt::Compound;
|
||||
pub use valence_protocol::block::BlockKind;
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::iter::FusedIterator;
|
|||
use std::mem;
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::schedule::SystemConfigs;
|
||||
use tracing::warn;
|
||||
use uuid::Uuid;
|
||||
use valence_protocol::packet::s2c::play::player_list::{
|
||||
|
@ -52,7 +53,7 @@ impl PlayerList {
|
|||
/// Returns a set of systems for maintaining the player list in a reasonable
|
||||
/// default way. When clients connect, they are added to the player list.
|
||||
/// When clients disconnect, they are removed from the player list.
|
||||
pub fn default_system_set() -> SystemSet {
|
||||
pub fn default_systems() -> SystemConfigs {
|
||||
fn add_new_clients_to_player_list(
|
||||
clients: Query<&Client, Added<Client>>,
|
||||
mut player_list: ResMut<PlayerList>,
|
||||
|
@ -79,9 +80,11 @@ impl PlayerList {
|
|||
}
|
||||
}
|
||||
|
||||
SystemSet::new()
|
||||
.with_system(add_new_clients_to_player_list)
|
||||
.with_system(remove_disconnected_clients_from_player_list)
|
||||
(
|
||||
add_new_clients_to_player_list,
|
||||
remove_disconnected_clients_from_player_list,
|
||||
)
|
||||
.into_configs()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,14 +2,13 @@ use std::iter::FusedIterator;
|
|||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::ensure;
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_app::AppExit;
|
||||
use bevy_ecs::event::ManualEventReader;
|
||||
use bevy_app::{ScheduleRunnerPlugin, ScheduleRunnerSettings};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::schedule::ScheduleLabel;
|
||||
use flume::{Receiver, Sender};
|
||||
use rand::rngs::OsRng;
|
||||
use rsa::{PublicKeyParts, RsaPrivateKey};
|
||||
|
@ -22,23 +21,17 @@ use valence_protocol::types::Property;
|
|||
use valence_protocol::username::Username;
|
||||
|
||||
use crate::biome::{validate_biomes, Biome, BiomeId};
|
||||
use crate::client::event::{event_loop_run_criteria, register_client_events};
|
||||
use crate::client::event::{register_client_events, run_event_loop};
|
||||
use crate::client::{update_clients, Client};
|
||||
use crate::config::{AsyncCallbacks, ConnectionMode, ServerPlugin};
|
||||
use crate::dimension::{validate_dimensions, Dimension, DimensionId};
|
||||
use crate::entity::{
|
||||
check_entity_invariants, deinit_despawned_entities, init_entities, update_entities,
|
||||
McEntityManager,
|
||||
};
|
||||
use crate::entity::{deinit_despawned_entities, init_entities, update_entities, McEntityManager};
|
||||
use crate::instance::{
|
||||
check_instance_invariants, update_instances_post_client, update_instances_pre_client, Instance,
|
||||
};
|
||||
use crate::inventory::{
|
||||
handle_click_container, handle_close_container, handle_set_held_item, handle_set_slot_creative,
|
||||
update_client_on_close_inventory, update_open_inventories, update_player_inventories,
|
||||
Inventory, InventoryKind,
|
||||
};
|
||||
use crate::inventory::update_inventories;
|
||||
use crate::player_list::{update_player_list, PlayerList};
|
||||
use crate::prelude::{Inventory, InventoryKind};
|
||||
use crate::server::connect::do_accept_loop;
|
||||
use crate::Despawned;
|
||||
|
||||
|
@ -98,8 +91,6 @@ struct SharedServerInner {
|
|||
/// Contains info about dimensions, biomes, and chats.
|
||||
/// Sent to all clients when joining.
|
||||
registry_codec: Compound,
|
||||
/// The instant the server was started.
|
||||
start_instant: Instant,
|
||||
/// Sender for new clients past the login stage.
|
||||
new_clients_send: Sender<Client>,
|
||||
/// Receiver for new clients past the login stage.
|
||||
|
@ -205,11 +196,6 @@ impl SharedServer {
|
|||
pub(crate) fn registry_codec(&self) -> &Compound {
|
||||
&self.0.registry_codec
|
||||
}
|
||||
|
||||
/// Returns the instant the server was started.
|
||||
pub fn start_instant(&self) -> Instant {
|
||||
self.0.start_instant
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains information about a new client joining the server.
|
||||
|
@ -280,7 +266,6 @@ pub fn build_plugin(
|
|||
dimensions: plugin.dimensions.clone(),
|
||||
biomes: plugin.biomes.clone(),
|
||||
registry_codec,
|
||||
start_instant: Instant::now(),
|
||||
new_clients_send,
|
||||
new_clients_recv,
|
||||
connection_sema: Arc::new(Semaphore::new(plugin.max_connections)),
|
||||
|
@ -306,7 +291,7 @@ pub fn build_plugin(
|
|||
|
||||
let shared = server.shared.clone();
|
||||
|
||||
// Exclusive system to spawn new clients. Should run before everything else.
|
||||
// System to spawn new clients.
|
||||
let spawn_new_clients = move |world: &mut World| {
|
||||
for _ in 0..shared.0.new_clients_recv.len() {
|
||||
let Ok(client) = shared.0.new_clients_recv.try_recv() else {
|
||||
|
@ -319,96 +304,72 @@ pub fn build_plugin(
|
|||
|
||||
let shared = server.shared.clone();
|
||||
|
||||
// Start accepting connections in PostStartup to allow user startup code to run
|
||||
// first.
|
||||
app.add_startup_system_to_stage(StartupStage::PostStartup, start_accept_loop);
|
||||
|
||||
// Insert resources.
|
||||
app.insert_resource(server)
|
||||
.insert_resource(McEntityManager::new())
|
||||
.insert_resource(PlayerList::new());
|
||||
register_client_events(&mut app.world);
|
||||
|
||||
// Add core systems and stages. User code is expected to run in
|
||||
// `CoreStage::Update` and `EventLoop`.
|
||||
app.add_system_to_stage(CoreStage::PreUpdate, spawn_new_clients)
|
||||
.add_stage_before(
|
||||
CoreStage::Update,
|
||||
EventLoop,
|
||||
SystemStage::parallel().with_run_criteria(event_loop_run_criteria),
|
||||
// Add the event loop schedule.
|
||||
let mut event_loop = Schedule::new();
|
||||
event_loop.configure_set(EventLoopSet);
|
||||
event_loop.set_default_base_set(EventLoopSet);
|
||||
|
||||
app.add_schedule(EventLoopSchedule, event_loop);
|
||||
|
||||
// Make the app loop forever at the configured TPS.
|
||||
{
|
||||
let tick_period = Duration::from_secs_f64((shared.tps() as f64).recip());
|
||||
|
||||
app.insert_resource(ScheduleRunnerSettings::run_loop(tick_period))
|
||||
.add_plugin(ScheduleRunnerPlugin);
|
||||
}
|
||||
|
||||
// Start accepting connections in `PostStartup` to allow user startup code to
|
||||
// run first.
|
||||
app.add_system(
|
||||
start_accept_loop
|
||||
.in_schedule(CoreSchedule::Startup)
|
||||
.in_base_set(StartupSet::PostStartup),
|
||||
);
|
||||
|
||||
// Add `CoreSet:::PreUpdate` systems.
|
||||
app.add_systems(
|
||||
(spawn_new_clients.before(run_event_loop), run_event_loop).in_base_set(CoreSet::PreUpdate),
|
||||
);
|
||||
|
||||
// Add internal valence systems that run after `CoreSet::Update`.
|
||||
app.add_systems(
|
||||
(
|
||||
init_entities,
|
||||
check_instance_invariants,
|
||||
update_player_list.before(update_instances_pre_client),
|
||||
update_instances_pre_client.after(init_entities),
|
||||
update_clients.after(update_instances_pre_client),
|
||||
update_instances_post_client.after(update_clients),
|
||||
deinit_despawned_entities.after(update_instances_post_client),
|
||||
despawn_marked_entities.after(deinit_despawned_entities),
|
||||
update_entities.after(despawn_marked_entities),
|
||||
)
|
||||
.add_system_set_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
SystemSet::new()
|
||||
.label("valence_core")
|
||||
.with_system(init_entities)
|
||||
.with_system(check_entity_invariants)
|
||||
.with_system(check_instance_invariants.after(check_entity_invariants))
|
||||
.with_system(update_player_list.before(update_instances_pre_client))
|
||||
.with_system(update_instances_pre_client.after(init_entities))
|
||||
.with_system(update_clients.after(update_instances_pre_client))
|
||||
.with_system(update_instances_post_client.after(update_clients))
|
||||
.with_system(deinit_despawned_entities.after(update_instances_post_client))
|
||||
.with_system(despawn_marked_entities.after(deinit_despawned_entities))
|
||||
.with_system(update_entities.after(despawn_marked_entities)),
|
||||
)
|
||||
.add_system_set_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
SystemSet::new()
|
||||
.label("inventory")
|
||||
.before("valence_core")
|
||||
.with_system(handle_set_held_item)
|
||||
.with_system(update_open_inventories)
|
||||
.with_system(handle_close_container)
|
||||
.with_system(update_client_on_close_inventory.after(update_open_inventories))
|
||||
.with_system(update_player_inventories)
|
||||
.with_system(
|
||||
handle_click_container
|
||||
.before(update_open_inventories)
|
||||
.before(update_player_inventories),
|
||||
)
|
||||
.with_system(
|
||||
handle_set_slot_creative
|
||||
.before(update_open_inventories)
|
||||
.before(update_player_inventories),
|
||||
),
|
||||
)
|
||||
.add_system_to_stage(CoreStage::Last, inc_current_tick);
|
||||
|
||||
let tick_duration = Duration::from_secs_f64((shared.tps() as f64).recip());
|
||||
|
||||
// Overwrite the app's runner.
|
||||
app.set_runner(move |mut app: App| {
|
||||
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||
|
||||
loop {
|
||||
let tick_start = Instant::now();
|
||||
|
||||
// Stop the server if there was an AppExit event.
|
||||
if let Some(app_exit_events) = app.world.get_resource_mut::<Events<AppExit>>() {
|
||||
if app_exit_event_reader
|
||||
.iter(&app_exit_events)
|
||||
.last()
|
||||
.is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Run the scheduled stages.
|
||||
app.update();
|
||||
|
||||
// Sleep until the next tick.
|
||||
thread::sleep(tick_duration.saturating_sub(tick_start.elapsed()));
|
||||
}
|
||||
});
|
||||
.in_base_set(CoreSet::PostUpdate),
|
||||
)
|
||||
.add_systems(
|
||||
update_inventories()
|
||||
.in_base_set(CoreSet::PostUpdate)
|
||||
.before(init_entities),
|
||||
)
|
||||
.add_system(increment_tick_counter.in_base_set(CoreSet::Last));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The stage label for the special "event loop" stage.
|
||||
#[derive(StageLabel)]
|
||||
pub struct EventLoop;
|
||||
/// The [`ScheduleLabel`] for the event loop [`Schedule`].
|
||||
#[derive(ScheduleLabel, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub struct EventLoopSchedule;
|
||||
|
||||
/// The default base set for the event loop [`Schedule`].
|
||||
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub struct EventLoopSet;
|
||||
|
||||
/// Despawns all the entities marked as despawned with the [`Despawned`]
|
||||
/// component.
|
||||
|
@ -418,7 +379,7 @@ fn despawn_marked_entities(mut commands: Commands, entities: Query<Entity, With<
|
|||
}
|
||||
}
|
||||
|
||||
fn inc_current_tick(mut server: ResMut<Server>) {
|
||||
fn increment_tick_counter(mut server: ResMut<Server>) {
|
||||
server.current_tick += 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use bevy_app::App;
|
||||
use bevy_app::{App, CoreSchedule};
|
||||
use bevy_ecs::prelude::Entity;
|
||||
use bevy_ecs::schedule::{LogLevel, ScheduleBuildSettings};
|
||||
use bytes::BytesMut;
|
||||
use valence_protocol::codec::{PacketDecoder, PacketEncoder};
|
||||
use valence_protocol::packet::S2cPlayPacket;
|
||||
|
@ -164,17 +165,28 @@ pub fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) {
|
|||
.with_compression_threshold(None)
|
||||
.with_connection_mode(ConnectionMode::Offline),
|
||||
);
|
||||
|
||||
let server = app.world.resource::<Server>();
|
||||
let instance = server.new_instance(DimensionId::default());
|
||||
let instance_ent = app.world.spawn(instance).id();
|
||||
let info = gen_client_info("test");
|
||||
let (mut client, client_helper) = create_mock_client(info);
|
||||
|
||||
// HACK: needed so client does not get disconnected on first update
|
||||
client.set_instance(instance_ent);
|
||||
let client_ent = app
|
||||
.world
|
||||
.spawn((client, Inventory::new(InventoryKind::Player)))
|
||||
.id();
|
||||
|
||||
// Print warnings if there are ambiguities in the schedule.
|
||||
app.edit_schedule(CoreSchedule::Main, |schedule| {
|
||||
schedule.set_build_settings(ScheduleBuildSettings {
|
||||
ambiguity_detection: LogLevel::Warn,
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
|
||||
(client_ent, client_helper)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ valence_nbt = { version = "0.5.0", path = "../valence_nbt" }
|
|||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.68"
|
||||
bevy_ecs = "0.9.1"
|
||||
bevy_ecs = { git = "https://github.com/bevyengine/bevy/", rev = "2ea00610188dce1eba1172a3ded8244570189230" }
|
||||
clap = "4.1.4"
|
||||
criterion = "0.4.0"
|
||||
flume = "0.10.14"
|
||||
|
|
|
@ -40,13 +40,17 @@ pub fn main() {
|
|||
|
||||
App::new()
|
||||
.add_plugin(ServerPlugin::new(()))
|
||||
.add_system_to_stage(EventLoop, default_event_handler)
|
||||
.add_system_set(PlayerList::default_system_set())
|
||||
.add_startup_system(setup)
|
||||
.add_system(init_clients)
|
||||
.add_system(remove_unviewed_chunks.after(init_clients))
|
||||
.add_system(update_client_views.after(remove_unviewed_chunks))
|
||||
.add_system(send_recv_chunks.after(update_client_views))
|
||||
.add_system(default_event_handler.in_schedule(EventLoopSchedule))
|
||||
.add_systems(
|
||||
(
|
||||
init_clients,
|
||||
remove_unviewed_chunks,
|
||||
update_client_views,
|
||||
send_recv_chunks,
|
||||
)
|
||||
.chain(),
|
||||
)
|
||||
.add_system(despawn_disconnected_clients)
|
||||
.run();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue