diff --git a/Cargo.toml b/Cargo.toml index b30fc7bcd..0cf2beb68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ valence_advancement = { workspace = true, optional = true } valence_anvil = { workspace = true, optional = true } valence_boss_bar = { workspace = true, optional = true } valence_server.workspace = true +valence_event.workspace = true valence_inventory = { workspace = true, optional = true } valence_network = { workspace = true, optional = true } valence_player_list = { workspace = true, optional = true } @@ -167,6 +168,7 @@ valence_advancement.path = "crates/valence_advancement" valence_anvil.path = "crates/valence_anvil" valence_build_utils.path = "crates/valence_build_utils" valence_server.path = "crates/valence_server" +valence_event.path = "crates/valence_event" valence_packet_macros.path = "crates/valence_packet_macros" valence_server_core.path = "crates/valence_server_core" valence_entity.path = "crates/valence_entity" diff --git a/crates/valence_event/Cargo.toml b/crates/valence_event/Cargo.toml new file mode 100644 index 000000000..1030fb5b3 --- /dev/null +++ b/crates/valence_event/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "valence_event" +version.workspace = true +edition.workspace = true + +[dependencies] +valence_server_core.workspace = true +valence_protocol.workspace = true +valence_server.workspace = true +bevy_ecs.workspace = true +bevy_app.workspace = true +bitfield-struct.workspace = true +tracing.workspace = true + + diff --git a/crates/valence_event/README.md b/crates/valence_event/README.md new file mode 100644 index 000000000..7564491a0 --- /dev/null +++ b/crates/valence_event/README.md @@ -0,0 +1,3 @@ +# valence_event + +This crate provides a cancellable event system for Bevy, and implements all valence event types. diff --git a/crates/valence_event/src/events.rs b/crates/valence_event/src/events.rs new file mode 100644 index 000000000..ae219e17d --- /dev/null +++ b/crates/valence_event/src/events.rs @@ -0,0 +1,60 @@ +use bevy_app::{App, First, Plugin, PluginGroup, PluginGroupBuilder}; +use bevy_ecs::prelude::Event; +use bevy_ecs::schedule::{IntoSystemSetConfigs, SystemSet}; +use valence_server::EventLoopPreUpdate; + +use self::inventory::InventoryEventPlugin; +use crate::state_event::{EventsWithState, State, States}; + +pub mod inventory; + +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +pub enum EventDispacherSets { + MainEvents, + Checks, + UserEvents, +} + +pub struct EventDispacherPlugin; + +impl Plugin for EventDispacherPlugin { + fn build(&self, app: &mut App) { + app.configure_sets( + EventLoopPreUpdate, + ( + EventDispacherSets::MainEvents, + EventDispacherSets::Checks, + EventDispacherSets::UserEvents, + ) + .chain(), + ) + + .init_resource::(); + + } +} + +pub struct EventPlugins; + +impl PluginGroup for EventPlugins { + fn build(self) -> PluginGroupBuilder { + PluginGroupBuilder::start::().add(InventoryEventPlugin) + } +} + +trait AddEventWithStateExt { + fn add_event_with_state(&mut self) -> &mut Self; +} + +impl AddEventWithStateExt for App { + fn add_event_with_state(&mut self) -> &mut Self { + if !self.world.contains_resource::>() { + self.init_resource::>() + .add_systems(First, EventsWithState::::update_system); + } + // if !self.world.contains_resource::() { + // self.init_resource::(); + // } + self + } +} diff --git a/crates/valence_event/src/events/inventory.rs b/crates/valence_event/src/events/inventory.rs new file mode 100644 index 000000000..09a1f18c7 --- /dev/null +++ b/crates/valence_event/src/events/inventory.rs @@ -0,0 +1,79 @@ +use bevy_ecs::prelude::{Entity, Event, EventReader}; +use bevy_ecs::schedule::IntoSystemConfigs; +use valence_protocol::packets::play::click_slot_c2s::{ClickMode, SlotChange}; +use valence_protocol::packets::play::ClickSlotC2s; +use valence_protocol::ItemStack; +use valence_server::event_loop::PacketEvent; + +use self::click::{handle_inventory_click, InventoryClickEvent}; +use super::*; +use crate::state_event::EventWithStateWriter; + +pub mod click; + +pub struct InventoryEventPlugin; + +impl Plugin for InventoryEventPlugin { + fn build(&self, app: &mut App) { + app.add_event_with_state::() + .add_event_with_state::() + .add_systems( + EventLoopPreUpdate, + handle_click_slot.in_set(EventDispacherSets::MainEvents), + ) + .add_systems( + EventLoopPreUpdate, + handle_inventory_click.in_set(EventDispacherSets::UserEvents), + ); + } +} + +#[derive(Clone, Debug)] +struct Canceled(bool); + +impl State for Canceled { + fn get(&self) -> bool { + self.0 + } + + fn set(&mut self, value: bool) { + self.0 = value; + } +} + +#[derive(Clone, Debug, Event)] +pub struct ClickSlotEvent { + pub client: Entity, + pub window_id: u8, + pub state_id: i32, + pub slot_idx: i16, + pub button: i8, + pub mode: ClickMode, + pub slot_changes: Vec, + pub carried_item: Option, +} + +fn handle_click_slot( + mut packet_events: EventReader, + mut click_slot_events: EventWithStateWriter, +) { + for packet in packet_events.iter() { + let Some(pkt) = packet.decode::() else { + continue; + }; + + click_slot_events.send( + ClickSlotEvent { + client: packet.client, + window_id: pkt.window_id, + state_id: pkt.state_id.0, + slot_idx: pkt.slot_idx, + button: pkt.button, + mode: pkt.mode, + slot_changes: pkt.slot_changes, + carried_item: pkt.carried_item, + }, + Canceled(false), + ); + } +} diff --git a/crates/valence_event/src/events/inventory/click.rs b/crates/valence_event/src/events/inventory/click.rs new file mode 100644 index 000000000..f8ad356e9 --- /dev/null +++ b/crates/valence_event/src/events/inventory/click.rs @@ -0,0 +1,64 @@ +use crate::state_event::{EventWithStateReader, EventReader}; + +use super::*; + +#[derive(Debug, Event, Clone)] +pub struct InventoryClickEvent { + pub client: Entity, + pub window_id: u8, + pub state_id: i32, + pub slot_idx: i16, + pub click_type: ClickType, + pub clicked_item: Option, + pub cursor_item: Option, +} + +#[derive(Debug, Clone)] +pub enum ClickType { + Left, + Right, + ShiftLeft, + ShiftRight, + DoubleLeft, + DoubleRight, +} + +pub(super) fn handle_inventory_click( + mut click_slot_events: EventReader, + mut inventory_click_events: EventWithStateWriter, +) { + for (event, state_id, _) in click_slot_events.iter_with_ids() { + let click_type = match event.mode { + ClickMode::Click => match event.button { + 0 => ClickType::Left, + 1 => ClickType::Right, + _ => panic!("Invalid button for click mode"), + }, + ClickMode::ShiftClick => match event.button { + 0 => ClickType::ShiftLeft, + 1 => ClickType::ShiftRight, + _ => panic!("Invalid button for shift click mode"), + }, + ClickMode::DoubleClick => match event.button { + 0 => ClickType::DoubleLeft, + 1 => ClickType::DoubleRight, + _ => panic!("Invalid button for double click mode"), + }, + _ => { + continue; + } + }; + + let inventory_click_event = InventoryClickEvent { + client: event.client, + window_id: event.window_id, + state_id: event.state_id, + slot_idx: event.slot_idx, + click_type, + clicked_item: event.slot_changes.get(0).cloned(), + cursor_item: event.carried_item.clone(), + }; + + inventory_click_events.send_with_state(inventory_click_event, state_id) + } +} diff --git a/crates/valence_event/src/lib.rs b/crates/valence_event/src/lib.rs new file mode 100644 index 000000000..881029752 --- /dev/null +++ b/crates/valence_event/src/lib.rs @@ -0,0 +1,2 @@ +pub mod events; +pub mod state_event; diff --git a/crates/valence_event/src/state_event.rs b/crates/valence_event/src/state_event.rs new file mode 100644 index 000000000..38c9342a9 --- /dev/null +++ b/crates/valence_event/src/state_event.rs @@ -0,0 +1,591 @@ +use std::cmp::Ordering; +use std::collections::HashMap; +use std::fmt; +use std::iter::Chain; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::slice::Iter; + +use bevy_ecs::prelude::Event; +use bevy_ecs::system::{Local, Res, ResMut, Resource, SystemParam}; + +pub struct EventId { + /// Uniquely identifies the event associated with this ID. + // This value corresponds to the order in which each event was added to the world. + pub id: usize, + _marker: PhantomData, +} + +impl Copy for EventId {} +impl Clone for EventId { + fn clone(&self) -> Self { + *self + } +} + +impl fmt::Display for EventId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(self, f) + } +} + +impl fmt::Debug for EventId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "event<{}>#{}", + std::any::type_name::().split("::").last().unwrap(), + self.id, + ) + } +} + +impl Eq for EventId {} +impl PartialEq for EventId { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl PartialOrd for EventId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for EventId { + fn cmp(&self, other: &Self) -> Ordering { + self.id.cmp(&other.id) + } +} + +/// A unique identifier for a state usable by multiple events types. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct StateId(usize); + +/// Defines a struct that can be used as a state for an event. +pub trait State: Send + Sync + 'static { + fn get(&self) -> bool; + + fn set(&mut self, value: bool); +} + +/// Resource that holds all states for all events. +#[derive(Resource)] +pub struct States { + state_count: usize, + states: HashMap>, +} + +impl Default for States { + fn default() -> Self { + Self { + state_count: 0, + states: Default::default(), + } + } +} + +impl States { + /// Adds a new state to the state registry. + pub fn add(&mut self, state: impl State) -> StateId { + let id = StateId(self.state_count); + self.state_count += 1; + self.states.insert(id, Box::new(state)); + id + } + + /// Gets a state by id. + pub fn get(&self, id: StateId) -> &dyn State { + self.states.get(&id).unwrap().as_ref() + } + + /// Gets a mutable state by id. + pub fn get_mut(&mut self, id: StateId) -> &mut dyn State { + self.states.get_mut(&id).unwrap().as_mut() + } +} + +/// An event linked to a specific state. +#[derive(Debug)] +struct EventWithStateInstance { + pub event_id: EventId, + pub event: E, + pub state_id: StateId, +} + +/// +#[derive(Debug)] +struct EventWithStateSequence { + events: Vec>, + start_event_count: usize, +} + +// Derived Default impl would incorrectly require E: Default +impl Default for EventWithStateSequence { + fn default() -> Self { + Self { + events: Default::default(), + start_event_count: Default::default(), + } + } +} + +impl Deref for EventWithStateSequence { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.events + } +} + +impl DerefMut for EventWithStateSequence { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.events + } +} + +/// A resource that holds all events of a specific type in a double buffer. +#[derive(Debug, Resource)] +pub struct EventsWithState { + /// Holds the oldest still active events. + /// Note that a.start_event_count + a.len() should always === + /// events_b.start_event_count. + events_a: EventWithStateSequence, + /// Holds the newer events. + events_b: EventWithStateSequence, + event_count: usize, +} + +// Derived Default impl would incorrectly require E: Default +impl Default for EventsWithState { + fn default() -> Self { + Self { + events_a: Default::default(), + events_b: Default::default(), + event_count: Default::default(), + } + } +} + +impl EventsWithState { + /// Returns the index of the oldest event stored in the event buffer. + pub fn oldest_event_count(&self) -> usize { + self.events_a + .start_event_count + .min(self.events_b.start_event_count) + } + + pub fn send(&mut self, event: E, state_id: StateId) { + let event_id = EventId { + id: self.event_count, + _marker: PhantomData, + }; + // detailed_trace!("Events::send() -> id: {}", event_id); + + let event_instance = EventWithStateInstance { + event_id, + event, + state_id, + }; + + self.events_b.push(event_instance); + self.event_count += 1; + } + + /// Gets a new [`ManualStateEventReader`]. This will include all events + /// already in the event buffers. + pub fn get_reader(&self) -> ManualEventWithStateReader { + ManualEventWithStateReader::default() + } + + /// Gets a new [`ManualStateEventReader`]. This will ignore all events + /// already in the event buffers. It will read all future events. + pub fn get_reader_current(&self) -> ManualEventWithStateReader { + ManualEventWithStateReader { + last_event_count: self.event_count, + ..Default::default() + } + } + + /// Swaps the event buffers and clears the oldest event buffer. In general, + /// this should be called once per frame/update. + pub fn update(&mut self) { + std::mem::swap(&mut self.events_a, &mut self.events_b); + self.events_b.clear(); + self.events_b.start_event_count = self.event_count; + debug_assert_eq!( + self.events_a.start_event_count + self.events_a.len(), + self.events_b.start_event_count + ); + } + + /// A system that calls [`Events::update`] once per frame. + pub fn update_system(mut events: ResMut) { + events.update(); + } + + #[inline] + fn reset_start_event_count(&mut self) { + self.events_a.start_event_count = self.event_count; + self.events_b.start_event_count = self.event_count; + } + + /// Removes all events. + #[inline] + pub fn clear(&mut self) { + self.reset_start_event_count(); + self.events_a.clear(); + self.events_b.clear(); + } + + /// Returns the number of events currently stored in the event buffer. + #[inline] + pub fn len(&self) -> usize { + self.events_a.len() + self.events_b.len() + } + + /// Returns true if there are no events currently stored in the event + /// buffer. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Creates a draining iterator that removes all events. + pub fn drain(&mut self) -> impl Iterator + '_ { + self.reset_start_event_count(); + + // Drain the oldest events first, then the newest + self.events_a + .drain(..) + .chain(self.events_b.drain(..)) + .map(|i| i.event) + } + + /// Iterates over events that happened since the last "update" call. + /// WARNING: You probably don't want to use this call. In most cases you + /// should use an [`EventWithStateReader`]. You should only use this if + /// you know you only need to consume events between the last `update()` + /// call and your call to `iter_current_update_events`. If events happen + /// outside that window, they will not be handled. For example, any events + /// that happen after this call and before the next `update()` call will + /// be dropped. + pub fn iter_current_update_events(&self) -> impl ExactSizeIterator { + self.events_b.iter().map(|i| &i.event) + } + + /// Get a specific event by id if it still exists in the events buffer. + pub fn get_event(&self, id: usize) -> Option<(&E, EventId)> { + if id < self.oldest_id() { + return None; + } + + let sequence = self.sequence(id); + let index = id.saturating_sub(sequence.start_event_count); + + sequence + .get(index) + .map(|instance| (&instance.event, instance.event_id)) + } + + /// Oldest id still in the events buffer. + pub fn oldest_id(&self) -> usize { + self.events_a.start_event_count + } + + /// Which event buffer is this event id a part of. + fn sequence(&self, id: usize) -> &EventWithStateSequence { + if id < self.events_b.start_event_count { + &self.events_a + } else { + &self.events_b + } + } +} + +#[derive(SystemParam)] +pub struct EventWithStateWriter<'w, E: Event> { + events: ResMut<'w, EventsWithState>, + states: ResMut<'w, States>, +} + +impl<'w, E: Event> EventWithStateWriter<'w, E> { + pub fn send(&mut self, event: E, state: impl State) { + let state_id = self.states.add(state); + self.events.send(event, state_id); + } + + pub fn send_with_state(&mut self, event: E, state_id: StateId) { + self.events.send(event, state_id); + } +} + +/// +#[derive(SystemParam)] +pub struct EventWithStateReader<'s, 'w, E: Event> { + reader: Local<'s, ManualEventWithStateReader>, + events: Res<'w, EventsWithState>, + states: Res<'w, States>, +} + +impl<'s, 'w, E: Event> EventWithStateReader<'s, 'w, E> { + pub fn iter(&mut self) -> ManualEventWithStateIterator<'_, E> { + self.reader.iter(&self.events, &self.states) + } +} + +impl<'a, E: Event> IntoIterator for &'a mut EventWithStateReader<'_, '_, E> { + type Item = (&'a E, &'a dyn State); + type IntoIter = ManualEventWithStateIterator<'a, E>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[derive(Debug)] +pub struct ManualEventWithStateReader { + last_event_count: usize, + _marker: PhantomData, +} + +impl Default for ManualEventWithStateReader { + fn default() -> Self { + ManualEventWithStateReader { + last_event_count: 0, + _marker: Default::default(), + } + } +} + +impl ManualEventWithStateReader { + pub fn iter<'a>( + &'a mut self, + events: &'a EventsWithState, + states: &'a States, + ) -> ManualEventWithStateIterator<'a, E> { + ManualEventWithStateIterator::new(self, events, states) + } +} + +#[derive()] +pub struct ManualEventWithStateIterator<'a, E: Event> { + reader: &'a mut ManualEventWithStateReader, + chain: Chain>, Iter<'a, EventWithStateInstance>>, + states: &'a States, + unread: usize, +} + +impl<'a, E: Event> ManualEventWithStateIterator<'a, E> { + pub fn new( + reader: &'a mut ManualEventWithStateReader, + events: &'a EventsWithState, + states: &'a States, + ) -> ManualEventWithStateIterator<'a, E> { + let a_index = (reader.last_event_count).saturating_sub(events.events_a.start_event_count); + let b_index = (reader.last_event_count).saturating_sub(events.events_b.start_event_count); + let a = events.events_a.get(a_index..).unwrap_or_default(); + let b = events.events_b.get(b_index..).unwrap_or_default(); + + let unread_count = a.len() + b.len(); + // Ensure `len` is implemented correctly + // debug_assert_eq!(unread_count, reader.len(events)); + reader.last_event_count = events.event_count - unread_count; + // Iterate the oldest first, then the newer events + let chain = a.iter().chain(b.iter()); + + ManualEventWithStateIterator { + reader, + chain, + states, + unread: unread_count, + } + } + +} + +impl<'a, E: Event> Iterator for ManualEventWithStateIterator<'a, E> { + type Item = (&'a E, &'a dyn State); + + fn next(&mut self) -> Option { + match self + .chain + .next() + .map(|instance| (&instance.event, instance.state_id, instance.event_id)) + { + Some(item) => { + // detailed_trace!("EventWithStateReader::iter() -> {}", item.1); + self.reader.last_event_count += 1; + self.unread -= 1; + Some((item.0, self.states.get(item.1))) + } + None => None, + } + } +} + +#[derive(SystemParam)] +pub struct EventWithStateMutReader<'s, 'w, E: Event> { + reader: Local<'s, ManualEventWithStateReader>, + events: Res<'w, EventsWithState>, + states: ResMut<'w, States>, +} + +impl<'s, 'w, E: Event> EventWithStateMutReader<'s, 'w, E> { + pub fn iter_mut(&mut self) -> ManualEventWithStateMutIterator<'_, E> { + self.reader.iter_mut(&self.events, &mut self.states) + } +} + +// impl<'a, E: Event> IntoIterator for &'a mut EventWithStateMutReader<'_, '_, E> { +// type Item = (&'a E, &'a mut dyn State); +// type IntoIter = ManualEventWithStateMutIterator<'a, E>; + +// fn into_iter(self) -> Self::IntoIter { +// self.iter_mut() +// } +// } + +#[derive()] +pub struct ManualEventWithStateMutIterator<'a, E: Event> { + reader: &'a mut ManualEventWithStateReader, + chain: Chain>, Iter<'a, EventWithStateInstance>>, + states: &'a mut States, + unread: usize, +} + +impl ManualEventWithStateReader { + pub fn iter_mut<'a>( + &'a mut self, + events: &'a EventsWithState, + states: &'a mut States, + ) -> ManualEventWithStateMutIterator<'a, E> { + ManualEventWithStateMutIterator::new(self, events, states) + } +} + +impl<'a, E: Event> ManualEventWithStateMutIterator<'a, E> { + pub fn new( + reader: &'a mut ManualEventWithStateReader, + events: &'a EventsWithState, + states: &'a mut States, + ) -> ManualEventWithStateMutIterator<'a, E> { + let a_index = (reader.last_event_count).saturating_sub(events.events_a.start_event_count); + let b_index = (reader.last_event_count).saturating_sub(events.events_b.start_event_count); + let a = events.events_a.get(a_index..).unwrap_or_default(); + let b = events.events_b.get(b_index..).unwrap_or_default(); + + let unread_count = a.len() + b.len(); + // Ensure `len` is implemented correctly + // debug_assert_eq!(unread_count, reader.len(events)); + reader.last_event_count = events.event_count - unread_count; + // Iterate the oldest first, then the newer events + let chain = a.iter().chain(b.iter()); + + ManualEventWithStateMutIterator { + reader, + chain, + states, + unread: unread_count, + } + } +} + +pub trait LendingIterator { + type Item<'a> where Self: 'a; + + fn next(&mut self) -> Option>; +} + +impl LendingIterator for ManualEventWithStateMutIterator<'_, E> { + type Item<'a> = (&'a E, &'a mut dyn State) where Self: 'a; + + fn next(&mut self) -> Option> { + match self + .chain + .next() + .map(|instance| (&instance.event, instance.state_id)) + { + Some(item) => { + self.reader.last_event_count += 1; + self.unread -= 1; + Some((item.0, self.states.get_mut(item.1))) + } + None => None, + } + } +} + + +#[derive(SystemParam)] +pub struct EventReader<'s, 'w, A: Event> { + reader: Local<'s, ManualEventWithStateReader>, + events_a: Res<'w, EventsWithState>, +} + +impl<'s, 'w, A: Event> EventReader<'s, 'w, A> { + pub fn iter_with_ids(&mut self) -> ManualEventWithIdsIterator<'_, A> { + self.reader.iter_with_ids(&self.events_a) + } +} + +#[derive()] +pub struct ManualEventWithIdsIterator<'a, E: Event> { + reader: &'a mut ManualEventWithStateReader, + chain: Chain>, Iter<'a, EventWithStateInstance>>, + unread: usize, +} + +impl ManualEventWithStateReader { + pub fn iter_with_ids<'a>( + &'a mut self, + events: &'a EventsWithState, + ) -> ManualEventWithIdsIterator<'a, E> { + ManualEventWithIdsIterator::new(self, events) + } +} + +impl<'a, E: Event> ManualEventWithIdsIterator<'a, E> { + pub fn new( + reader: &'a mut ManualEventWithStateReader, + events: &'a EventsWithState, + ) -> ManualEventWithIdsIterator<'a, E> { + let a_index = (reader.last_event_count).saturating_sub(events.events_a.start_event_count); + let b_index = (reader.last_event_count).saturating_sub(events.events_b.start_event_count); + let a = events.events_a.get(a_index..).unwrap_or_default(); + let b = events.events_b.get(b_index..).unwrap_or_default(); + + let unread_count = a.len() + b.len(); + // Ensure `len` is implemented correctly + // debug_assert_eq!(unread_count, reader.len(events)); + reader.last_event_count = events.event_count - unread_count; + // Iterate the oldest first, then the newer events + let chain = a.iter().chain(b.iter()); + + ManualEventWithIdsIterator { + reader, + chain, + unread: unread_count, + } + } +} + +impl<'a, E: Event> Iterator for ManualEventWithIdsIterator<'a, E> { + type Item = (&'a E, StateId, EventId); + + fn next(&mut self) -> Option { + match self + .chain + .next() + .map(|instance| (&instance.event, instance.state_id, instance.event_id)) + { + Some(item) => { + self.reader.last_event_count += 1; + self.unread -= 1; + Some(item) + } + None => None, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index a94058194..639eb8d6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,6 @@ pub use valence_anvil as anvil; pub use valence_boss_bar as boss_bar; #[cfg(feature = "inventory")] pub use valence_inventory as inventory; -pub use valence_lang as lang; #[cfg(feature = "network")] pub use valence_network as network; #[cfg(feature = "player_list")] @@ -75,6 +74,7 @@ pub use valence_server::*; pub use valence_weather as weather; #[cfg(feature = "world_border")] pub use valence_world_border as world_border; +pub use {valence_event as event, valence_lang as lang}; /// Contains the most frequently used items in Valence projects. ///