feat(plugin-api): allow to set tab header/footer
This commit is contained in:
parent
53aa958d90
commit
cddb7e3f6a
@ -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;
|
||||
|
||||
@ -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.
|
||||
@ -51,6 +61,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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -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<InitChat>),
|
||||
UpdateGameMode(VarInt),
|
||||
UpdateListed(bool),
|
||||
UpdateLatency(u8),
|
||||
UpdateDisplayName(u8),
|
||||
UpdateListOrder,
|
||||
UpdateLatency(VarInt),
|
||||
UpdateDisplayName(Option<&'a TextComponent>),
|
||||
UpdateListOrder(VarInt),
|
||||
}
|
||||
|
||||
pub struct InitChat {
|
||||
|
||||
@ -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)?,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
pumpkin-protocol/src/java/client/play/tab_list.rs
Normal file
21
pumpkin-protocol/src/java/client/play/tab_list.rs
Normal file
@ -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 }
|
||||
}
|
||||
}
|
||||
@ -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<G: Serialize>(&mut self, data: &G) -> Result<(), WritingError>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn write_nbt(&mut self, data: NbtTag) -> Result<(), WritingError>;
|
||||
}
|
||||
|
||||
@ -398,6 +401,7 @@ impl<W: Write> 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<W: Write> NetworkWriteExt for W {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_serialize<G: 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)
|
||||
|
||||
@ -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(()) => {}
|
||||
|
||||
@ -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<dyn ScreenHandlerListener>,
|
||||
pub screen_handler_sync_handler: Arc<SyncHandler>,
|
||||
pub tab_list_header: Mutex<TextComponent>,
|
||||
pub tab_list_footer: Mutex<TextComponent>,
|
||||
pub display_name: Mutex<Option<TextComponent>>,
|
||||
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<TextComponent>) {
|
||||
*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 {
|
||||
|
||||
@ -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()),
|
||||
|
||||
@ -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<BossBar>) -> 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<BossBar>) -> wasmtime::Result<WitColor> {
|
||||
todo!()
|
||||
async fn get_color(&mut self, res: Resource<BossBar>) -> wasmtime::Result<WitColor> {
|
||||
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<BossBar>,
|
||||
_color: WitColor,
|
||||
) -> wasmtime::Result<()> {
|
||||
todo!()
|
||||
|
||||
async fn set_color(&mut self, res: Resource<BossBar>, 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<BossBar>) -> wasmtime::Result<WitDivision> {
|
||||
todo!()
|
||||
|
||||
async fn get_division(&mut self, res: Resource<BossBar>) -> wasmtime::Result<WitDivision> {
|
||||
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<BossBar>,
|
||||
_division: WitDivision,
|
||||
res: Resource<BossBar>,
|
||||
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<BossBar>) -> wasmtime::Result<WitMetadata> {
|
||||
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<BossBar>,
|
||||
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<BossBar>,
|
||||
res: Resource<BossBar>,
|
||||
) -> 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(
|
||||
|
||||
@ -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::plugin::text::TextComponent>,
|
||||
) -> 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<Player>,
|
||||
display_name: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
|
||||
) -> 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<Player>,
|
||||
@ -260,6 +271,49 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_tab_list_header_footer(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
header: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
|
||||
footer: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
|
||||
) -> 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<Player>,
|
||||
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<Player>,
|
||||
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<Player>,
|
||||
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<Player>,
|
||||
|
||||
@ -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<Server>,
|
||||
header: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
|
||||
footer: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
|
||||
) -> 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<Server>) -> wasmtime::Result<u32> {
|
||||
let server = self
|
||||
.server
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -603,7 +603,7 @@ impl DragonFight {
|
||||
health: 1.0,
|
||||
color: BossbarColor::Pink,
|
||||
division: BossbarDivisions::NoDivision,
|
||||
flags: BossbarFlags::DragonBar,
|
||||
flags: BossbarFlags::DRAGON_BAR,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user