diff --git a/Cargo.lock b/Cargo.lock index 0b4381092..1ab5f5f15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -833,6 +833,7 @@ dependencies = [ "libcraft-core", "libcraft-inventory", "libcraft-items", + "libcraft-text", "log", "parking_lot", "quill-common", @@ -953,6 +954,7 @@ dependencies = [ "hematite-nbt", "libcraft-core", "libcraft-items", + "libcraft-text", "log", "md-5", "num-bigint", diff --git a/feather/common/Cargo.toml b/feather/common/Cargo.toml index a371549d5..2789ed83f 100644 --- a/feather/common/Cargo.toml +++ b/feather/common/Cargo.toml @@ -21,6 +21,7 @@ uuid = { version = "0.8", features = [ "v4" ] } libcraft-core = { path = "../../libcraft/core" } libcraft-inventory = { path = "../../libcraft/inventory" } libcraft-items = { path = "../../libcraft/items" } +libcraft-text = { path = "../../libcraft/text" } rayon = "1.5" worldgen = { path = "../worldgen", package = "feather-worldgen" } rand = "0.8" \ No newline at end of file diff --git a/feather/server/Cargo.toml b/feather/server/Cargo.toml index 970560311..9d4a4365c 100644 --- a/feather/server/Cargo.toml +++ b/feather/server/Cargo.toml @@ -55,6 +55,7 @@ uuid = "0.8" slab = "0.4" libcraft-core = { path = "../../libcraft/core" } libcraft-items = { path = "../../libcraft/items" } +libcraft-text = { path = "../../libcraft/text" } worldgen = { path = "../worldgen", package = "feather-worldgen" } [features] diff --git a/feather/server/config.toml b/feather/server/config.toml index 5e9efff7a..0dc45c588 100644 --- a/feather/server/config.toml +++ b/feather/server/config.toml @@ -13,6 +13,8 @@ motd = "A Feather server" max_players = 16 default_gamemode = "creative" view_distance = 12 +tablist_header = "A Feather Server" +tablist_footer = "" [log] # If you prefer less verbose logs, switch this to "info". diff --git a/feather/server/src/client.rs b/feather/server/src/client.rs index f18af09a1..b2e4eeb58 100644 --- a/feather/server/src/client.rs +++ b/feather/server/src/client.rs @@ -19,7 +19,10 @@ use common::{ Window, }; use libcraft_items::InventorySlot; -use packets::server::{Particle, SetSlot, SpawnLivingEntity, UpdateLight, WindowConfirmation}; +use packets::server::{ + Particle, PlayerListHeaderAndFooter, SetSlot, SpawnLivingEntity, UpdateLight, + WindowConfirmation, +}; use protocol::packets::server::{ ChangeGameState, EntityPosition, EntityPositionAndRotation, EntityTeleport, GameStateChange, HeldItemChange, PlayerAbilities, @@ -332,6 +335,14 @@ impl Client { self.send_packet(PlayerInfo::RemovePlayers(vec![uuid])); } + pub fn send_tablist_header_footer(&self, header: &str, footer: &str) { + log::trace!("Sending PlayerListHeaderAndFooter ({},{})", header, footer); + self.send_packet(PlayerListHeaderAndFooter { + header: header.to_string(), + footer: footer.to_string(), + }) + } + pub fn change_player_tablist_gamemode(&self, uuid: Uuid, gamemode: Gamemode) { self.send_packet(PlayerInfo::UpdateGamemodes(vec![(uuid, gamemode)])); } diff --git a/feather/server/src/config.rs b/feather/server/src/config.rs index 9111a037f..c874c0353 100644 --- a/feather/server/src/config.rs +++ b/feather/server/src/config.rs @@ -3,7 +3,7 @@ use std::{fs, net::IpAddr, path::Path, str::FromStr}; use anyhow::Context; -use base::Gamemode; +use base::{Gamemode, Text}; use serde::{Deserialize, Deserializer}; use crate::{favicon::Favicon, Options}; @@ -90,6 +90,8 @@ pub struct ServerConfig { pub max_players: u32, pub default_gamemode: Gamemode, pub view_distance: u32, + pub tablist_header: Text, + pub tablist_footer: Text, } #[derive(Debug, Deserialize)] diff --git a/feather/server/src/main.rs b/feather/server/src/main.rs index c3bf7ea16..9d90f4d06 100644 --- a/feather/server/src/main.rs +++ b/feather/server/src/main.rs @@ -29,24 +29,26 @@ async fn main() -> anyhow::Result<()> { let options = config.to_options(); let server = Server::bind(options).await?; - let game = init_game(server, &config)?; + let game = init_game(server, config)?; run(game); Ok(()) } -fn init_game(server: Server, config: &Config) -> anyhow::Result { +fn init_game(server: Server, config: Config) -> anyhow::Result { let mut game = Game::new(); - init_systems(&mut game, server); - init_world_source(&mut game, config); + init_world_source(&mut game, &config); + init_systems(&mut game, server, config); init_plugin_manager(&mut game)?; Ok(game) } -fn init_systems(game: &mut Game, server: Server) { +fn init_systems(game: &mut Game, server: Server, config: Config) { let mut systems = SystemExecutor::new(); + game.insert_resource(config); + // Register common before server code, so // that packet broadcasting happens after // gameplay actions. diff --git a/feather/server/src/systems.rs b/feather/server/src/systems.rs index e364dc591..69075cee0 100644 --- a/feather/server/src/systems.rs +++ b/feather/server/src/systems.rs @@ -31,7 +31,7 @@ pub fn register(server: Server, game: &mut Game, systems: &mut SystemExecutor) { +pub fn register(game: &mut Game, systems: &mut SystemExecutor) { + let (header, footer) = { + let server_config = &game.resources.get::().unwrap().server; + ( + server_config.tablist_header.clone(), + server_config.tablist_footer.clone(), + ) + }; + + game.insert_resource(TablistHeaderFooter { header, footer }); + systems .group::() .add_system(remove_tablist_players) .add_system(add_tablist_players) + .add_system(update_tablist_header_footer) + .add_system(send_tablist_header_footer_on_join) .add_system(change_tablist_player_gamemode); } @@ -63,6 +79,36 @@ fn add_tablist_players(game: &mut Game, server: &mut Server) -> SysResult { Ok(()) } +/// Updates tablist header and footer when [TablistExtrasUpdateEvent] is raised +fn update_tablist_header_footer(game: &mut Game, server: &mut Server) -> SysResult { + for _ in game.ecs.query::<&TablistExtrasUpdateEvent>().iter() { + let header_footer = game.resources.get::()?; + server.broadcast_with(|client| { + client.send_tablist_header_footer( + &header_footer.header.to_string(), + &header_footer.footer.to_string(), + ) + }); + } + Ok(()) +} + +/// Sends tablist header and footer to players who join +fn send_tablist_header_footer_on_join(game: &mut Game, server: &mut Server) -> SysResult { + for (_, (_, &client_id)) in game.ecs.query::<(&PlayerJoinEvent, &ClientId)>().iter() { + let header_footer = game.resources.get::()?; + server + .clients + .get(client_id) + .unwrap() + .send_tablist_header_footer( + &header_footer.header.to_string(), + &header_footer.footer.to_string(), + ); + } + Ok(()) +} + fn change_tablist_player_gamemode(game: &mut Game, server: &mut Server) -> SysResult { for (_, (event, &uuid)) in game.ecs.query::<(&GamemodeEvent, &Uuid)>().iter() { // Change this player's gamemode in players' tablists diff --git a/quill/common/src/component.rs b/quill/common/src/component.rs index 11e4c4651..f8073e85e 100644 --- a/quill/common/src/component.rs +++ b/quill/common/src/component.rs @@ -206,6 +206,7 @@ host_component_enum! { FlyingAbilityEvent = 1028, BuildingAbilityEvent = 1029, InvulnerabilityEvent = 1030, + TablistExtrasUpdateEvent = 1031, } } @@ -377,3 +378,4 @@ bincode_component_impl!(InstabreakEvent); bincode_component_impl!(FlyingAbilityEvent); bincode_component_impl!(BuildingAbilityEvent); bincode_component_impl!(InvulnerabilityEvent); +bincode_component_impl!(TablistExtrasUpdateEvent); diff --git a/quill/common/src/events.rs b/quill/common/src/events.rs index e253da176..f0190a56c 100644 --- a/quill/common/src/events.rs +++ b/quill/common/src/events.rs @@ -1,7 +1,7 @@ pub use block_interact::{BlockInteractEvent, BlockPlacementEvent}; pub use change::{ BuildingAbilityEvent, CreativeFlyingEvent, FlyingAbilityEvent, GamemodeEvent, InstabreakEvent, - InvulnerabilityEvent, SneakEvent, SprintEvent, + InvulnerabilityEvent, SneakEvent, SprintEvent, TablistExtrasUpdateEvent, }; pub use entity::{EntityCreateEvent, EntityRemoveEvent, PlayerJoinEvent}; pub use interact_entity::InteractEntityEvent; diff --git a/quill/common/src/events/change.rs b/quill/common/src/events/change.rs index 266846160..d7e4ccf87 100644 --- a/quill/common/src/events/change.rs +++ b/quill/common/src/events/change.rs @@ -64,3 +64,7 @@ pub struct BuildingAbilityEvent(pub bool); /// This event is called when player's invulnerability property changes. #[derive(Debug, Serialize, Deserialize, Clone, Deref)] pub struct InvulnerabilityEvent(pub bool); + +/// Raise this event to resend tablist extras to all players +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct TablistExtrasUpdateEvent; diff --git a/quill/common/src/lib.rs b/quill/common/src/lib.rs index 53d8a262b..bf64a5d04 100644 --- a/quill/common/src/lib.rs +++ b/quill/common/src/lib.rs @@ -8,6 +8,7 @@ pub mod entities; pub mod entity; pub mod entity_init; pub mod events; +pub mod tablist; use std::marker::PhantomData; diff --git a/quill/common/src/tablist.rs b/quill/common/src/tablist.rs new file mode 100644 index 000000000..68deeb48e --- /dev/null +++ b/quill/common/src/tablist.rs @@ -0,0 +1,7 @@ +use libcraft_text::Text; + +#[derive(Debug, Clone)] +pub struct TablistHeaderFooter { + pub header: Text, + pub footer: Text, +}