diff --git a/pumpkin-plugin-api/src/lib.rs b/pumpkin-plugin-api/src/lib.rs index 399a7bc50..5fe5e07c1 100644 --- a/pumpkin-plugin-api/src/lib.rs +++ b/pumpkin-plugin-api/src/lib.rs @@ -45,7 +45,7 @@ pub mod command { pub use wit::pumpkin::plugin::{ block_entity, command as command_wit, common, context::{Context, Server}, - entity, gui, permission, scoreboard, server, text, world, + entity, gui, permission, player, scoreboard, server, text, world, }; pub mod logging; diff --git a/pumpkin-plugin-wit/v0.1/boss-bar.wit b/pumpkin-plugin-wit/v0.1/boss-bar.wit index 7c3638176..646f6e13e 100644 --- a/pumpkin-plugin-wit/v0.1/boss-bar.wit +++ b/pumpkin-plugin-wit/v0.1/boss-bar.wit @@ -13,7 +13,7 @@ interface boss-bar { white, } - /// Represents the division (notches) of a boss bar. + /// Represents the notches/divisions of a boss bar. enum boss-bar-division { no-division, notches-6, @@ -22,6 +22,16 @@ interface boss-bar { notches-20, } + /// Metadata flags that can be toggled on a boss bar. + record boss-bar-metadata { + /// Darkens the sky for players who see the boss bar. + darken-sky: bool, + /// If true, the bar is considered a "dragon bar" (used for Ender Dragon). + dragon-bar: bool, + /// If true, fog is created around the players who see the boss bar. + create-fog: bool, + } + /// Represents a boss bar that can be shown to players. resource boss-bar { /// Creates a new boss bar. @@ -50,6 +60,12 @@ interface boss-bar { /// Sets the division of this boss bar. set-division: func(division: boss-bar-division); + + /// Returns the currently active metadata on this boss bar. + get-metadata: func() -> boss-bar-metadata; + + /// Updates the metadata on this boss bar. + set-metadata: func(metadata: boss-bar-metadata); /// Adds a player to this boss bar, making it visible to them. add-player: func(player: player); diff --git a/pumpkin-plugin-wit/v0.1/player.wit b/pumpkin-plugin-wit/v0.1/player.wit index dda89dd1e..f6355e9f6 100644 --- a/pumpkin-plugin-wit/v0.1/player.wit +++ b/pumpkin-plugin-wit/v0.1/player.wit @@ -63,12 +63,25 @@ interface player { has-permission: func (node: string) -> bool; /// Returns the player's display name, which may include formatting. get-display-name: func () -> text-component; + /// Sets the player's display name. + set-display-name: func (display-name: text-component); // Messaging & titles /// Sends a system message to the player's chat. send-system-message: func (text: text-component, overlay: bool); + /// Sets the header and footer of the player's Tab list. + set-tab-list-header-footer: func (header: text-component, footer: text-component); + + /// Sets the sorting order of the player in the Tab list. + set-tab-list-order: func (order: s32); + /// Sets the latency (ping) shown for the player in the Tab list. + set-tab-list-latency: func (latency: s32); + /// Sets whether the player is visible in the Tab list. + set-tab-list-listed: func (listed: bool); + /// Shows a large title on the player's screen. + show-title: func (text: text-component); /// Shows a smaller subtitle on the player's screen. show-subtitle: func (text: text-component); diff --git a/pumpkin-plugin-wit/v0.1/server.wit b/pumpkin-plugin-wit/v0.1/server.wit index fff99a75a..b72de2e0c 100644 --- a/pumpkin-plugin-wit/v0.1/server.wit +++ b/pumpkin-plugin-wit/v0.1/server.wit @@ -2,6 +2,7 @@ interface server { use player.{player}; use common.{game-mode}; use %world.{%world}; + use text.{text-component}; /// Represents the difficulty setting of a Minecraft server. enum difficulty { @@ -49,6 +50,9 @@ interface server { /// Broadcasts a system chat message to all players. broadcast: func(message: string); + /// Broadcasts a tab list header and footer to all players. + broadcast-tab-list-header-footer: func(header: text-component, footer: text-component); + /// Returns the maximum number of players allowed on the server. get-max-players: func() -> u32; diff --git a/pumpkin-protocol/src/java/client/play/mod.rs b/pumpkin-protocol/src/java/client/play/mod.rs index 2957d190d..d3f831065 100644 --- a/pumpkin-protocol/src/java/client/play/mod.rs +++ b/pumpkin-protocol/src/java/client/play/mod.rs @@ -81,6 +81,7 @@ mod stop_sound; mod store_cookie; mod subtitle; mod system_chat_message; +mod tab_list; mod take_item; mod teleport_entity; mod ticking_state; @@ -179,6 +180,7 @@ pub use stop_sound::*; pub use store_cookie::*; pub use subtitle::*; pub use system_chat_message::*; +pub use tab_list::*; pub use take_item::*; pub use teleport_entity::*; pub use ticking_state::*; diff --git a/pumpkin-protocol/src/java/client/play/player_action.rs b/pumpkin-protocol/src/java/client/play/player_action.rs index bf3630277..fbe07780e 100644 --- a/pumpkin-protocol/src/java/client/play/player_action.rs +++ b/pumpkin-protocol/src/java/client/play/player_action.rs @@ -1,3 +1,5 @@ +use pumpkin_util::text::TextComponent; + use crate::{Property, VarInt}; pub enum PlayerAction<'a> { @@ -8,9 +10,9 @@ pub enum PlayerAction<'a> { InitializeChat(Option), UpdateGameMode(VarInt), UpdateListed(bool), - UpdateLatency(u8), - UpdateDisplayName(u8), - UpdateListOrder, + UpdateLatency(VarInt), + UpdateDisplayName(Option<&'a TextComponent>), + UpdateListOrder(VarInt), } pub struct InitChat { diff --git a/pumpkin-protocol/src/java/client/play/player_info_update.rs b/pumpkin-protocol/src/java/client/play/player_info_update.rs index 90f40e931..c92c746c8 100644 --- a/pumpkin-protocol/src/java/client/play/player_info_update.rs +++ b/pumpkin-protocol/src/java/client/play/player_info_update.rs @@ -105,9 +105,11 @@ impl ClientPacket for CPlayerInfoUpdate<'_> { } PlayerAction::UpdateGameMode(gamemode) => p.write_var_int(gamemode)?, PlayerAction::UpdateListed(listed) => p.write_bool(*listed)?, - PlayerAction::UpdateLatency(_) => todo!(), - PlayerAction::UpdateDisplayName(_) => todo!(), - PlayerAction::UpdateListOrder => todo!(), + PlayerAction::UpdateLatency(latency) => p.write_var_int(latency)?, + PlayerAction::UpdateDisplayName(display_name) => { + p.write_option(display_name, crate::ser::NetworkWriteExt::write_serialize)?; + } + PlayerAction::UpdateListOrder(order) => p.write_var_int(order)?, } } diff --git a/pumpkin-protocol/src/java/client/play/tab_list.rs b/pumpkin-protocol/src/java/client/play/tab_list.rs new file mode 100644 index 000000000..7f134a067 --- /dev/null +++ b/pumpkin-protocol/src/java/client/play/tab_list.rs @@ -0,0 +1,21 @@ +use pumpkin_data::packet::clientbound::PLAY_TAB_LIST; +use pumpkin_macros::java_packet; +use pumpkin_util::text::TextComponent; +use serde::Serialize; + +/// Updates the header and footer of the player list (Tab List). +#[derive(Serialize)] +#[java_packet(PLAY_TAB_LIST)] +pub struct CTabList<'a> { + /// The text to be displayed at the top of the player list. + pub header: &'a TextComponent, + /// The text to be displayed at the bottom of the player list. + pub footer: &'a TextComponent, +} + +impl<'a> CTabList<'a> { + #[must_use] + pub const fn new(header: &'a TextComponent, footer: &'a TextComponent) -> Self { + Self { header, footer } + } +} diff --git a/pumpkin-protocol/src/ser/mod.rs b/pumpkin-protocol/src/ser/mod.rs index 077c2e2be..775c991f0 100644 --- a/pumpkin-protocol/src/ser/mod.rs +++ b/pumpkin-protocol/src/ser/mod.rs @@ -1,4 +1,5 @@ use core::str; +use serde::Serialize; use std::io::{Read, Write}; use crate::{ @@ -283,9 +284,7 @@ pub trait NetworkWriteExt { list: &[G], writer: impl Fn(&mut Self, &G) -> Result<(), WritingError>, ) -> Result<(), WritingError> { - self.write_var_int(&list.len().try_into().map_err(|_| { - WritingError::Message(format!("{} isn't representable as a VarInt", list.len())) - })?)?; + self.write_var_int(&(list.len() as i32).into())?; for data in list { writer(self, data)?; @@ -294,6 +293,10 @@ pub trait NetworkWriteExt { Ok(()) } + fn write_serialize(&mut self, data: &G) -> Result<(), WritingError> + where + Self: Sized; + fn write_nbt(&mut self, data: NbtTag) -> Result<(), WritingError>; } @@ -398,6 +401,7 @@ impl NetworkWriteExt for W { writer: impl Fn(&mut Self, &G) -> Result<(), WritingError>, ) -> Result<(), WritingError> { self.write_var_int(&(list.len() as i32).into())?; + for data in list { writer(self, data)?; } @@ -405,6 +409,11 @@ impl NetworkWriteExt for W { Ok(()) } + fn write_serialize(&mut self, data: &G) -> Result<(), WritingError> { + let mut serializer = serializer::Serializer::new(self); + data.serialize(&mut serializer) + } + fn write_nbt(&mut self, data: NbtTag) -> Result<(), WritingError> { let mut write_adaptor = WriteAdaptor::new(self); data.serialize(&mut write_adaptor) diff --git a/pumpkin/src/command/commands/bossbar.rs b/pumpkin/src/command/commands/bossbar.rs index a1f1d4b43..0e2e042f6 100644 --- a/pumpkin/src/command/commands/bossbar.rs +++ b/pumpkin/src/command/commands/bossbar.rs @@ -296,7 +296,7 @@ impl CommandExecutor for SetExecutor { .bossbars .lock() .await - .update_color(server, namespace.to_string(), color.clone()) + .update_color(server, namespace.to_string(), *color) .await { Ok(()) => {} @@ -450,7 +450,7 @@ impl CommandExecutor for SetExecutor { .bossbars .lock() .await - .update_division(server, namespace.to_string(), style.clone()) + .update_division(server, namespace.to_string(), *style) .await { Ok(()) => {} diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index cb892f124..c44ee5f0c 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -59,9 +59,9 @@ use pumpkin_protocol::java::client::play::{ COpenScreen, CParticle, CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, CPlayerSpawnPosition, CRespawn, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem, CSetEquipment, CSetExperience, CSetHealth, CSetPlayerInventory, - CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle, CSystemChatMessage, CTitleAnimation, - CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent, Metadata, PlayerAction, - PlayerInfoFlags, PreviousMessage, + CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle, CSystemChatMessage, CTabList, + CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent, Metadata, + PlayerAction, PlayerInfoFlags, PreviousMessage, }; use pumpkin_protocol::java::server::play::SClickSlot; use pumpkin_util::math::{ @@ -480,9 +480,16 @@ pub struct Player { pub screen_handler_sync_id: AtomicU8, pub screen_handler_listener: Arc, pub screen_handler_sync_handler: Arc, + pub tab_list_header: Mutex, + pub tab_list_footer: Mutex, + pub display_name: Mutex>, + pub tab_list_order: AtomicI32, + pub tab_list_latency: AtomicI32, + pub tab_list_listed: AtomicBool, } impl Player { + #[expect(clippy::too_many_lines)] pub async fn new( client: ClientPlatform, gameprofile: GameProfile, @@ -603,11 +610,82 @@ impl Player { player_screen_handler: player_screen_handler.clone(), current_screen_handler: Mutex::new(player_screen_handler), screen_handler_sync_id: AtomicU8::new(0), - screen_handler_listener: Arc::new(ScreenListener {}), + screen_handler_listener: Arc::new(ScreenListener), screen_handler_sync_handler: Arc::new(SyncHandler::new()), + tab_list_header: Mutex::new(TextComponent::text("")), + tab_list_footer: Mutex::new(TextComponent::text("")), + display_name: Mutex::new(None), + tab_list_order: AtomicI32::new(0), + tab_list_latency: AtomicI32::new(0), + tab_list_listed: AtomicBool::new(true), } } + pub async fn set_tab_list_header_footer(&self, header: TextComponent, footer: TextComponent) { + *self.tab_list_header.lock().await = header.clone(); + *self.tab_list_footer.lock().await = footer.clone(); + self.client + .enqueue_packet(&CTabList::new(&header, &footer)) + .await; + } + + pub async fn set_display_name(&self, display_name: Option) { + *self.display_name.lock().await = display_name.clone(); + // Update the tab list for everyone + let world = self.world(); + world + .broadcast_packet_all(&CPlayerInfoUpdate::new( + PlayerInfoFlags::UPDATE_DISPLAY_NAME.bits(), + &[pumpkin_protocol::java::client::play::Player { + uuid: self.gameprofile.id, + actions: &[PlayerAction::UpdateDisplayName(display_name.as_ref())], + }], + )) + .await; + } + + pub async fn set_tab_list_order(&self, order: i32) { + self.tab_list_order.store(order, Ordering::Relaxed); + let world = self.world(); + world + .broadcast_packet_all(&CPlayerInfoUpdate::new( + PlayerInfoFlags::UPDATE_LIST_PRIORITY.bits(), + &[pumpkin_protocol::java::client::play::Player { + uuid: self.gameprofile.id, + actions: &[PlayerAction::UpdateListOrder(VarInt(order))], + }], + )) + .await; + } + + pub async fn set_tab_list_latency(&self, latency: i32) { + self.tab_list_latency.store(latency, Ordering::Relaxed); + let world = self.world(); + world + .broadcast_packet_all(&CPlayerInfoUpdate::new( + PlayerInfoFlags::UPDATE_LATENCY.bits(), + &[pumpkin_protocol::java::client::play::Player { + uuid: self.gameprofile.id, + actions: &[PlayerAction::UpdateLatency(VarInt(latency))], + }], + )) + .await; + } + + pub async fn set_tab_list_listed(&self, listed: bool) { + self.tab_list_listed.store(listed, Ordering::Relaxed); + let world = self.world(); + world + .broadcast_packet_all(&CPlayerInfoUpdate::new( + PlayerInfoFlags::UPDATE_LISTED.bits(), + &[pumpkin_protocol::java::client::play::Player { + uuid: self.gameprofile.id, + actions: &[PlayerAction::UpdateListed(listed)], + }], + )) + .await; + } + /// Spawns a task associated with this player-client. All tasks spawned with this method are awaited /// when the client. This means tasks should complete in a reasonable amount of time or select /// on `Self::await_close_interrupt` to cancel the task when the client is closed @@ -3533,17 +3611,22 @@ impl EntityBase for Player { } fn get_display_name(&self) -> EntityBaseFuture<'_, TextComponent> { - let name = self.get_name(); - let name_clone = name.clone(); - let mut name = name.click_event(ClickEvent::SuggestCommand { - command: format!("/tell {} ", self.gameprofile.name.clone()).into(), - }); - name = name.hover_event(HoverEvent::show_entity( - self.living_entity.entity.entity_uuid.to_string(), - self.living_entity.entity.entity_type.resource_name.into(), - Some(name_clone), - )); - Box::pin(async move { name.insertion(self.gameprofile.name.clone()) }) + Box::pin(async move { + if let Some(display_name) = self.display_name.lock().await.as_ref() { + return display_name.clone(); + } + let name = self.get_name(); + let name_clone = name.clone(); + let mut name = name.click_event(ClickEvent::SuggestCommand { + command: format!("/tell {} ", self.gameprofile.name.clone()).into(), + }); + name = name.hover_event(HoverEvent::show_entity( + self.living_entity.entity.entity_uuid.to_string(), + self.living_entity.entity.entity_type.resource_name.into(), + Some(name_clone), + )); + name.insertion(self.gameprofile.name.clone()) + }) } fn cast_any(&self) -> &dyn std::any::Any { diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/args.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/args.rs index 2985ab61a..a713d3ffd 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/args.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/args.rs @@ -64,8 +64,8 @@ impl OwnedArg { Arg::ResourceLocation(s) => Self::ResourceLocation(s.to_string()), Arg::Block(s) => Self::Block(s.to_string()), Arg::BlockPredicate(s) => Self::BlockPredicate(s.to_string()), - Arg::BossbarColor(c) => Self::BossbarColor(c.clone()), - Arg::BossbarStyle(s) => Self::BossbarStyle(s.clone()), + Arg::BossbarColor(c) => Self::BossbarColor(*c), + Arg::BossbarStyle(s) => Self::BossbarStyle(*s), Arg::Particle(p) => Self::Particle(*p), Arg::Msg(m) => Self::Msg(m.clone()), Arg::TextComponent(t) => Self::TextComponent(t.clone()), diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs index 66d0a2e10..7133ab9f3 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs @@ -2,10 +2,11 @@ use crate::plugin::loader::wasm::wasm_host::{ state::{BossBarResource, PlayerResource, PluginHostState}, wit::v0_1::pumpkin::plugin::boss_bar::{ self, BossBar, BossBarColor as WitColor, BossBarDivision as WitDivision, + BossBarMetadata as WitMetadata, }, }; use crate::server::Server; -use crate::world::bossbar::{Bossbar, BossbarColor, BossbarDivisions}; +use crate::world::bossbar::{Bossbar, BossbarColor, BossbarDivisions, BossbarFlags}; use std::sync::{Arc, Weak}; use tokio::sync::Mutex; use uuid::Uuid; @@ -41,6 +42,72 @@ fn player_from_resource( .map(|resource| resource.provider.clone()) } +const fn to_wit_color(color: BossbarColor) -> WitColor { + match color { + BossbarColor::Pink => WitColor::Pink, + BossbarColor::Blue => WitColor::Blue, + BossbarColor::Red => WitColor::Red, + BossbarColor::Green => WitColor::Green, + BossbarColor::Yellow => WitColor::Yellow, + BossbarColor::Purple => WitColor::Purple, + BossbarColor::White => WitColor::White, + } +} + +const fn from_wit_color(color: WitColor) -> BossbarColor { + match color { + WitColor::Pink => BossbarColor::Pink, + WitColor::Blue => BossbarColor::Blue, + WitColor::Red => BossbarColor::Red, + WitColor::Green => BossbarColor::Green, + WitColor::Yellow => BossbarColor::Yellow, + WitColor::Purple => BossbarColor::Purple, + WitColor::White => BossbarColor::White, + } +} + +const fn to_wit_division(division: BossbarDivisions) -> WitDivision { + match division { + BossbarDivisions::NoDivision => WitDivision::NoDivision, + BossbarDivisions::Notches6 => WitDivision::Notches6, + BossbarDivisions::Notches10 => WitDivision::Notches10, + BossbarDivisions::Notches12 => WitDivision::Notches12, + BossbarDivisions::Notches20 => WitDivision::Notches20, + } +} + +const fn from_wit_division(division: WitDivision) -> BossbarDivisions { + match division { + WitDivision::NoDivision => BossbarDivisions::NoDivision, + WitDivision::Notches6 => BossbarDivisions::Notches6, + WitDivision::Notches10 => BossbarDivisions::Notches10, + WitDivision::Notches12 => BossbarDivisions::Notches12, + WitDivision::Notches20 => BossbarDivisions::Notches20, + } +} + +const fn to_wit_metadata(flags: BossbarFlags) -> WitMetadata { + WitMetadata { + darken_sky: flags.contains(BossbarFlags::DARKEN_SKY), + dragon_bar: flags.contains(BossbarFlags::DRAGON_BAR), + create_fog: flags.contains(BossbarFlags::CREATE_FOG), + } +} + +fn from_wit_metadata(metadata: WitMetadata) -> BossbarFlags { + let mut f = BossbarFlags::empty(); + if metadata.darken_sky { + f |= BossbarFlags::DARKEN_SKY; + } + if metadata.dragon_bar { + f |= BossbarFlags::DRAGON_BAR; + } + if metadata.create_fog { + f |= BossbarFlags::CREATE_FOG; + } + f +} + impl PluginHostState { fn get_bossbar_res(&self, res: &Resource) -> wasmtime::Result<&BossBarResource> { self.resource_table @@ -63,23 +130,8 @@ impl boss_bar::HostBossBar for PluginHostState { let title = self.get_text_provider(&title)?; let mut bossbar = Bossbar::new(title); - bossbar.color = match color { - WitColor::Pink => BossbarColor::Pink, - WitColor::Blue => BossbarColor::Blue, - WitColor::Red => BossbarColor::Red, - WitColor::Green => BossbarColor::Green, - WitColor::Yellow => BossbarColor::Yellow, - WitColor::Purple => BossbarColor::Purple, - WitColor::White => BossbarColor::White, - }; - - bossbar.division = match division { - WitDivision::NoDivision => BossbarDivisions::NoDivision, - WitDivision::Notches6 => BossbarDivisions::Notches6, - WitDivision::Notches10 => BossbarDivisions::Notches10, - WitDivision::Notches12 => BossbarDivisions::Notches12, - WitDivision::Notches20 => BossbarDivisions::Notches20, - }; + bossbar.color = from_wit_color(color); + bossbar.division = from_wit_division(division); let server = self.server.as_ref().expect("server not available").clone(); let plugin_bossbar = Arc::new(Mutex::new(PluginBossBar::new( @@ -147,29 +199,85 @@ impl boss_bar::HostBossBar for PluginHostState { Ok(()) } - async fn get_color(&mut self, _res: Resource) -> wasmtime::Result { - todo!() + async fn get_color(&mut self, res: Resource) -> wasmtime::Result { + let pbb = self.get_bossbar_res(&res)?.provider.lock().await; + Ok(to_wit_color(pbb.bossbar.color)) } - async fn set_color( - &mut self, - _res: Resource, - _color: WitColor, - ) -> wasmtime::Result<()> { - todo!() + + async fn set_color(&mut self, res: Resource, color: WitColor) -> wasmtime::Result<()> { + let mut pbb = self.get_bossbar_res(&res)?.provider.lock().await; + pbb.bossbar.color = from_wit_color(color); + if let Some(server) = pbb.server.upgrade() { + for uuid in &pbb.players { + if let Some(player) = server.get_player_by_uuid(*uuid) { + player + .update_bossbar_style( + &pbb.bossbar.uuid, + pbb.bossbar.color, + pbb.bossbar.division, + ) + .await; + } + } + } + Ok(()) } - async fn get_division(&mut self, _res: Resource) -> wasmtime::Result { - todo!() + + async fn get_division(&mut self, res: Resource) -> wasmtime::Result { + let pbb = self.get_bossbar_res(&res)?.provider.lock().await; + Ok(to_wit_division(pbb.bossbar.division)) } + async fn set_division( &mut self, - _res: Resource, - _division: WitDivision, + res: Resource, + division: WitDivision, ) -> wasmtime::Result<()> { - todo!() + let mut pbb = self.get_bossbar_res(&res)?.provider.lock().await; + pbb.bossbar.division = from_wit_division(division); + if let Some(server) = pbb.server.upgrade() { + for uuid in &pbb.players { + if let Some(player) = server.get_player_by_uuid(*uuid) { + player + .update_bossbar_style( + &pbb.bossbar.uuid, + pbb.bossbar.color, + pbb.bossbar.division, + ) + .await; + } + } + } + Ok(()) } + + async fn get_metadata(&mut self, res: Resource) -> wasmtime::Result { + let pbb = self.get_bossbar_res(&res)?.provider.lock().await; + Ok(to_wit_metadata(pbb.bossbar.flags)) + } + + async fn set_metadata( + &mut self, + res: Resource, + metadata: WitMetadata, + ) -> wasmtime::Result<()> { + let mut pbb = self.get_bossbar_res(&res)?.provider.lock().await; + pbb.bossbar.flags = from_wit_metadata(metadata); + if let Some(server) = pbb.server.upgrade() { + for uuid in &pbb.players { + if let Some(player) = server.get_player_by_uuid(*uuid) { + player + .update_bossbar_flags(&pbb.bossbar.uuid, pbb.bossbar.flags) + .await; + } + } + } + Ok(()) + } + async fn get_players( &mut self, - _res: Resource, + res: Resource, ) -> wasmtime::Result< Vec< Resource< @@ -177,7 +285,23 @@ impl boss_bar::HostBossBar for PluginHostState { >, >, > { - todo!() + let players = { + let pbb = self.get_bossbar_res(&res)?.provider.lock().await; + pbb.players.clone() + }; + + let server = self + .server + .clone() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + let mut wit_players = Vec::new(); + for uuid in players { + if let Some(player) = server.get_player_by_uuid(uuid) { + wit_players.push(self.add_player(player)?); + } + } + Ok(wit_players) } async fn add_player( diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs index 17b5661c6..89a22df8b 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs @@ -34,7 +34,7 @@ fn player_from_resource( .map(|resource| resource.provider.clone()) } -fn text_component_from_resource( +pub(crate) fn text_component_from_resource( state: &PluginHostState, text: &Resource, ) -> pumpkin_util::text::TextComponent { @@ -248,6 +248,17 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { .map_err(|_| wasmtime::Error::msg("failed to add text-component resource")) } + async fn set_display_name( + &mut self, + player: Resource, + display_name: wasmtime::component::Resource, + ) -> wasmtime::Result<()> { + let display_name = text_component_from_resource(self, &display_name); + let player = player_from_resource(self, &player)?; + player.set_display_name(Some(display_name)).await; + Ok(()) + } + async fn send_system_message( &mut self, player: Resource, @@ -260,6 +271,49 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { Ok(()) } + async fn set_tab_list_header_footer( + &mut self, + player: Resource, + header: wasmtime::component::Resource, + footer: wasmtime::component::Resource, + ) -> wasmtime::Result<()> { + let header = text_component_from_resource(self, &header); + let footer = text_component_from_resource(self, &footer); + let player = player_from_resource(self, &player)?; + player.set_tab_list_header_footer(header, footer).await; + Ok(()) + } + + async fn set_tab_list_order( + &mut self, + player: Resource, + order: i32, + ) -> wasmtime::Result<()> { + let player = player_from_resource(self, &player)?; + player.set_tab_list_order(order).await; + Ok(()) + } + + async fn set_tab_list_latency( + &mut self, + player: Resource, + latency: i32, + ) -> wasmtime::Result<()> { + let player = player_from_resource(self, &player)?; + player.set_tab_list_latency(latency).await; + Ok(()) + } + + async fn set_tab_list_listed( + &mut self, + player: Resource, + listed: bool, + ) -> wasmtime::Result<()> { + let player = player_from_resource(self, &player)?; + player.set_tab_list_listed(listed).await; + Ok(()) + } + async fn show_title( &mut self, player: Resource, diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs index a08f70833..942466c53 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs @@ -1,6 +1,8 @@ +use pumpkin_util::text::TextComponent; use uuid::Uuid; use wasmtime::component::Resource; +use super::player::text_component_from_resource; use crate::plugin::loader::wasm::wasm_host::{ state::{PluginHostState, ServerResource}, wit::v0_1::pumpkin::{ @@ -162,8 +164,8 @@ impl pumpkin::plugin::server::HostServer for PluginHostState { server .broadcast_message( - &pumpkin_util::text::TextComponent::text(message), - &pumpkin_util::text::TextComponent::text("Server"), + &TextComponent::text(message), + &TextComponent::text("Server"), 0, None, ) @@ -172,6 +174,24 @@ impl pumpkin::plugin::server::HostServer for PluginHostState { Ok(()) } + async fn broadcast_tab_list_header_footer( + &mut self, + _rep: Resource, + header: wasmtime::component::Resource, + footer: wasmtime::component::Resource, + ) -> wasmtime::Result<()> { + let header = text_component_from_resource(self, &header); + let footer = text_component_from_resource(self, &footer); + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + server + .broadcast_tab_list_header_footer(&header, &footer) + .await; + Ok(()) + } + async fn get_max_players(&mut self, _rep: Resource) -> wasmtime::Result { let server = self .server diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index daed9a3ba..2e29f6e54 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -27,7 +27,7 @@ use tracing::{debug, error, info, warn}; use crate::command::CommandSender; use pumpkin_macros::send_cancellable; use pumpkin_protocol::java::client::login::CEncryptionRequest; -use pumpkin_protocol::java::client::play::CChangeDifficulty; +use pumpkin_protocol::java::client::play::{CChangeDifficulty, CTabList}; use pumpkin_protocol::{ClientPacket, java::client::config::CPluginMessage}; use pumpkin_util::Difficulty; use pumpkin_util::text::TextComponent; @@ -506,6 +506,21 @@ impl Server { } } + pub async fn broadcast_tab_list_header_footer( + &self, + header: &TextComponent, + footer: &TextComponent, + ) { + let packet = CTabList::new(header, footer); + for world in self.worlds.load().iter() { + for player in world.players.load().iter() { + *player.tab_list_header.lock().await = header.clone(); + *player.tab_list_footer.lock().await = footer.clone(); + player.client.enqueue_packet(&packet).await; + } + } + } + pub async fn broadcast_message( &self, message: &TextComponent, diff --git a/pumpkin/src/world/bossbar.rs b/pumpkin/src/world/bossbar.rs index 29b94461c..442a44fa6 100644 --- a/pumpkin/src/world/bossbar.rs +++ b/pumpkin/src/world/bossbar.rs @@ -1,9 +1,10 @@ use crate::entity::player::Player; +use bitflags::bitflags; use pumpkin_protocol::java::client::play::{BosseventAction, CBossEvent}; use pumpkin_util::text::TextComponent; use uuid::Uuid; -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum BossbarColor { Pink, Blue, @@ -14,7 +15,7 @@ pub enum BossbarColor { White, } -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum BossbarDivisions { NoDivision, Notches6, @@ -23,12 +24,13 @@ pub enum BossbarDivisions { Notches20, } -#[derive(Clone)] -pub enum BossbarFlags { - NoFlags, - DarkenSky = 0x01, - DragonBar = 0x02, - CreateFog = 0x04, +bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct BossbarFlags: u8 { + const DARKEN_SKY = 0x01; + const DRAGON_BAR = 0x02; + const CREATE_FOG = 0x04; + } } #[derive(Clone)] @@ -52,7 +54,7 @@ impl Bossbar { health: 0.0, color: BossbarColor::White, division: BossbarDivisions::NoDivision, - flags: BossbarFlags::NoFlags, + flags: BossbarFlags::empty(), } } } @@ -60,19 +62,18 @@ impl Bossbar { /// Extra methods for [`Player`] to send and manage the bossbar. impl Player { pub async fn send_bossbar(&self, bossbar: &Bossbar) { - // Maybe this section could be implemented. Feel free to change it. - let bossbar = bossbar.clone(); let boss_action = BosseventAction::Add { - title: bossbar.title, + title: bossbar.title.clone(), health: bossbar.health, color: (bossbar.color as u8).into(), division: (bossbar.division as u8).into(), - flags: bossbar.flags as u8, + flags: bossbar.flags.bits(), }; let packet = CBossEvent::new(&bossbar.uuid, boss_action); self.client.enqueue_packet(&packet).await; } + pub async fn remove_bossbar(&self, uuid: Uuid) { let boss_action = BosseventAction::Remove; @@ -110,7 +111,7 @@ impl Player { } pub async fn update_bossbar_flags(&self, uuid: &Uuid, flags: BossbarFlags) { - let boss_action = BosseventAction::UpdateFlags(flags as u8); + let boss_action = BosseventAction::UpdateFlags(flags.bits()); let packet = CBossEvent::new(uuid, boss_action); self.client.enqueue_packet(&packet).await; diff --git a/pumpkin/src/world/custom_bossbar.rs b/pumpkin/src/world/custom_bossbar.rs index c5a1a0358..2394088c1 100644 --- a/pumpkin/src/world/custom_bossbar.rs +++ b/pumpkin/src/world/custom_bossbar.rs @@ -292,8 +292,8 @@ impl CustomBossbars { player .update_bossbar_style( &bossbar.bossbar_data.uuid, - bossbar.bossbar_data.color.clone(), - bossbar.bossbar_data.division.clone(), + bossbar.bossbar_data.color, + bossbar.bossbar_data.division, ) .await; } @@ -331,8 +331,8 @@ impl CustomBossbars { player .update_bossbar_style( &bossbar.bossbar_data.uuid, - bossbar.bossbar_data.color.clone(), - bossbar.bossbar_data.division.clone(), + bossbar.bossbar_data.color, + bossbar.bossbar_data.division, ) .await; } diff --git a/pumpkin/src/world/dragon_fight.rs b/pumpkin/src/world/dragon_fight.rs index 9bd5f3ca9..82031bbba 100644 --- a/pumpkin/src/world/dragon_fight.rs +++ b/pumpkin/src/world/dragon_fight.rs @@ -603,7 +603,7 @@ impl DragonFight { health: 1.0, color: BossbarColor::Pink, division: BossbarDivisions::NoDivision, - flags: BossbarFlags::DragonBar, + flags: BossbarFlags::DRAGON_BAR, } } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 5f1e38e9b..da3417388 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1837,7 +1837,9 @@ impl World { self.broadcast_packet_all(&CPlayerInfoUpdate::new( (PlayerInfoFlags::ADD_PLAYER | PlayerInfoFlags::UPDATE_GAME_MODE - | PlayerInfoFlags::UPDATE_LISTED) + | PlayerInfoFlags::UPDATE_LISTED + | PlayerInfoFlags::UPDATE_LATENCY + | PlayerInfoFlags::UPDATE_LIST_PRIORITY) .bits(), &[pumpkin_protocol::java::client::play::Player { uuid: gameprofile.id, @@ -1848,6 +1850,8 @@ impl World { }, PlayerAction::UpdateGameMode(VarInt(gamemode as i32)), PlayerAction::UpdateListed(true), + PlayerAction::UpdateLatency(VarInt(0)), + PlayerAction::UpdateListOrder(VarInt(0)), ], }], )) @@ -1867,7 +1871,13 @@ impl World { name: &player.gameprofile.name, properties: &player.gameprofile.properties, }, - PlayerAction::UpdateListed(true), + PlayerAction::UpdateListed(player.tab_list_listed.load(Ordering::Relaxed)), + PlayerAction::UpdateLatency(VarInt( + player.tab_list_latency.load(Ordering::Relaxed), + )), + PlayerAction::UpdateListOrder(VarInt( + player.tab_list_order.load(Ordering::Relaxed), + )), ]; if base_config.allow_chat_reports { @@ -1883,7 +1893,10 @@ impl World { current_player_data.push((&player.gameprofile.id, player_actions)); } - let mut action_flags = PlayerInfoFlags::ADD_PLAYER | PlayerInfoFlags::UPDATE_LISTED; + let mut action_flags = PlayerInfoFlags::ADD_PLAYER + | PlayerInfoFlags::UPDATE_LISTED + | PlayerInfoFlags::UPDATE_LATENCY + | PlayerInfoFlags::UPDATE_LIST_PRIORITY; if base_config.allow_chat_reports { action_flags |= PlayerInfoFlags::INITIALIZE_CHAT; }