From d368886e24c5571da503a294f200c0eaea0b0210 Mon Sep 17 00:00:00 2001 From: Bafbi Date: Thu, 3 Aug 2023 19:22:01 +0200 Subject: [PATCH 1/4] make bossbar use `EntityLayer` for visibility --- crates/valence_boss_bar/Cargo.toml | 1 + crates/valence_boss_bar/src/components.rs | 53 + crates/valence_boss_bar/src/lib.rs | 240 +-- .../valence_core/src/protocol/packet.old.rs | 1316 ----------------- crates/valence_inventory/src/lib.rs | 4 +- .../src/packets/play/boss_bar_s2c.rs | 25 +- .../packets/play/update_selected_slot_c2s.rs | 2 +- examples/boss_bar.rs | 58 +- src/tests/boss_bar.rs | 153 +- 9 files changed, 215 insertions(+), 1637 deletions(-) delete mode 100644 crates/valence_core/src/protocol/packet.old.rs diff --git a/crates/valence_boss_bar/Cargo.toml b/crates/valence_boss_bar/Cargo.toml index a2d669157..7ff01f964 100644 --- a/crates/valence_boss_bar/Cargo.toml +++ b/crates/valence_boss_bar/Cargo.toml @@ -13,6 +13,7 @@ valence_network.workspace = true valence_entity.workspace = true valence_client.workspace = true valence_packet.workspace = true +valence_layer.workspace = true uuid.workspace = true bitfield-struct.workspace = true bevy_app.workspace = true diff --git a/crates/valence_boss_bar/src/components.rs b/crates/valence_boss_bar/src/components.rs index e69de29bb..ff8b1f794 100644 --- a/crates/valence_boss_bar/src/components.rs +++ b/crates/valence_boss_bar/src/components.rs @@ -0,0 +1,53 @@ +use bevy_ecs::prelude::{Bundle, Component}; +use valence_core::text::Text; +use valence_core::uuid::UniqueId; +use valence_entity::EntityLayerId; +use valence_packet::packets::play::boss_bar_s2c::{BossBarAction, ToPacketAction}; +pub use valence_packet::packets::play::boss_bar_s2c::{ + BossBarColor, BossBarDivision, BossBarFlags, +}; + +/// The bundle of components that make up a boss bar. +#[derive(Bundle, Default)] +pub struct BossBarBundle { + pub id: UniqueId, + pub title: BossBarTitle, + pub health: BossBarHealth, + pub style: BossBarStyle, + pub flags: BossBarFlags, + pub entity_layer_id: EntityLayerId, +} + +/// The title of a boss bar. +#[derive(Component, Clone, Default)] +pub struct BossBarTitle(pub Text); + +impl ToPacketAction for BossBarTitle { + fn to_packet_action(&self) -> BossBarAction { + BossBarAction::UpdateTitle(self.0.to_owned()) + } +} + +/// The health of a boss bar. +#[derive(Component, Default)] +pub struct BossBarHealth(pub f32); + +impl ToPacketAction for BossBarHealth { + fn to_packet_action(&self) -> BossBarAction { + BossBarAction::UpdateHealth(self.0) + } +} + +/// The style of a boss bar. This includes the color and division of the boss +/// bar. +#[derive(Component, Default)] +pub struct BossBarStyle { + pub color: BossBarColor, + pub division: BossBarDivision, +} + +impl ToPacketAction for BossBarStyle { + fn to_packet_action(&self) -> BossBarAction { + BossBarAction::UpdateStyle(self.color, self.division) + } +} diff --git a/crates/valence_boss_bar/src/lib.rs b/crates/valence_boss_bar/src/lib.rs index e93c02fe7..55290ee89 100644 --- a/crates/valence_boss_bar/src/lib.rs +++ b/crates/valence_boss_bar/src/lib.rs @@ -18,76 +18,19 @@ clippy::dbg_macro )] -use std::borrow::Cow; -use std::collections::BTreeSet; - use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use valence_client::{Client, FlushPacketsSet}; +use valence_client::{Client, FlushPacketsSet, OldVisibleEntityLayers, VisibleEntityLayers}; use valence_core::despawn::Despawned; -use valence_core::text::Text; use valence_core::uuid::UniqueId; -pub use valence_packet::packets::play::boss_bar_s2c::{ - BossBarAction, BossBarColor, BossBarDivision, BossBarFlags, -}; +use valence_packet::packets::play::boss_bar_s2c::{BossBarAction, ToPacketAction}; use valence_packet::packets::play::BossBarS2c; use valence_packet::protocol::encode::WritePacket; -/// The bundle of components that make up a boss bar. -#[derive(Bundle, Debug, Default)] -pub struct BossBarBundle { - pub id: UniqueId, - pub title: BossBarTitle, - pub health: BossBarHealth, - pub style: BossBarStyle, - pub flags: BossBarFlags, - pub viewers: BossBarViewers, -} - -impl BossBarBundle { - pub fn new( - title: Text, - color: BossBarColor, - division: BossBarDivision, - flags: BossBarFlags, - ) -> BossBarBundle { - BossBarBundle { - id: UniqueId::default(), - title: BossBarTitle(title), - health: BossBarHealth(1.0), - style: BossBarStyle { color, division }, - flags, - viewers: BossBarViewers::default(), - } - } -} - -/// The title of a boss bar. -#[derive(Component, Clone, Debug, Default)] -pub struct BossBarTitle(pub Text); - -/// The health of a boss bar. -#[derive(Component, Debug, Default)] -pub struct BossBarHealth(pub f32); - -/// The style of a boss bar. This includes the color and division of the boss -/// bar. -#[derive(Component, Debug, Default)] -pub struct BossBarStyle { - pub color: BossBarColor, - pub division: BossBarDivision, -} - -/// The viewers of a boss bar. -#[derive(Component, Default, Debug)] -pub struct BossBarViewers { - /// The current viewers of the boss bar. It is the list that should be - /// updated. - pub viewers: BTreeSet, - /// The viewers of the last tick in order to determine which viewers have - /// been added and removed. - pub(crate) old_viewers: BTreeSet, -} +mod components; +pub use components::*; +use valence_entity::{EntityLayerId, Position}; +use valence_layer::{EntityLayer, Layer}; pub struct BossBarPlugin; @@ -96,148 +39,96 @@ impl Plugin for BossBarPlugin { app.add_systems( PostUpdate, ( - boss_bar_title_update, - boss_bar_health_update, - boss_bar_style_update, - boss_bar_flags_update, - boss_bar_viewers_update, + update_boss_bar::, + update_boss_bar::, + update_boss_bar::, + update_boss_bar::, + update_boss_bar_view, boss_bar_despawn, - client_disconnection.before(boss_bar_viewers_update), ) .before(FlushPacketsSet), ); } } -/// System that sends a bossbar update title packet to all viewers of a boss bar -/// that has had its title updated. -fn boss_bar_title_update( - boss_bars: Query<(&UniqueId, &BossBarTitle, &BossBarViewers), Changed>, - mut clients: Query<&mut Client>, -) { - for (id, title, boss_bar_viewers) in boss_bars.iter() { - for viewer in boss_bar_viewers.viewers.iter() { - if let Ok(mut client) = clients.get_mut(*viewer) { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::UpdateTitle(Cow::Borrowed(&title.0)), - }); - } - } - } -} - -/// System that sends a bossbar update health packet to all viewers of a boss -/// bar that has had its health updated. -fn boss_bar_health_update( - boss_bars: Query<(&UniqueId, &BossBarHealth, &BossBarViewers), Changed>, - mut clients: Query<&mut Client>, -) { - for (id, health, boss_bar_viewers) in boss_bars.iter() { - for viewer in boss_bar_viewers.viewers.iter() { - if let Ok(mut client) = clients.get_mut(*viewer) { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::UpdateHealth(health.0), - }); - } - } - } -} - -/// System that sends a bossbar update style packet to all viewers of a boss bar -/// that has had its style updated. -fn boss_bar_style_update( - boss_bars: Query<(&UniqueId, &BossBarStyle, &BossBarViewers), Changed>, - mut clients: Query<&mut Client>, +fn update_boss_bar( + boss_bars_query: Query<(&UniqueId, &T, &EntityLayerId, Option<&Position>), Changed>, + mut entity_layers_query: Query<&mut EntityLayer>, ) { - for (id, style, boss_bar_viewers) in boss_bars.iter() { - for viewer in boss_bar_viewers.viewers.iter() { - if let Ok(mut client) = clients.get_mut(*viewer) { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::UpdateStyle(style.color, style.division), - }); + for (id, part, entity_layer_id, pos) in boss_bars_query.iter() { + if let Ok(mut entity_layer) = entity_layers_query.get_mut(entity_layer_id.0) { + let packet = BossBarS2c { + id: id.0, + action: part.to_packet_action(), + }; + if let Some(pos) = pos { + entity_layer + .view_writer(pos.to_chunk_pos()) + .write_packet(&packet); + } else { + entity_layer.write_packet(&packet); } } } } -/// System that sends a bossbar update flags packet to all viewers of a boss bar -/// that has had its flags updated. -fn boss_bar_flags_update( - boss_bars: Query<(&UniqueId, &BossBarFlags, &BossBarViewers), Changed>, - mut clients: Query<&mut Client>, -) { - for (id, flags, boss_bar_viewers) in boss_bars.iter() { - for viewer in boss_bar_viewers.viewers.iter() { - if let Ok(mut client) = clients.get_mut(*viewer) { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::UpdateFlags(*flags), - }); - } - } - } -} - -/// System that sends a bossbar add/remove packet to all viewers of a boss bar -/// that just have been added/removed. -fn boss_bar_viewers_update( - mut boss_bars: Query< - ( - &UniqueId, - &BossBarTitle, - &BossBarHealth, - &BossBarStyle, - &BossBarFlags, - &mut BossBarViewers, - ), - Changed, +fn update_boss_bar_view( + mut clients_query: Query< + (&mut Client, &VisibleEntityLayers, &OldVisibleEntityLayers), + Changed, >, - mut clients: Query<&mut Client>, + boss_bars_query: Query<( + &UniqueId, + &BossBarTitle, + &BossBarHealth, + &BossBarStyle, + &BossBarFlags, + &EntityLayerId, + )>, ) { - for (id, title, health, style, flags, mut boss_bar_viewers) in boss_bars.iter_mut() { - let old_viewers = &boss_bar_viewers.old_viewers; - let current_viewers = &boss_bar_viewers.viewers; - - for &added_viewer in current_viewers.difference(old_viewers) { - if let Ok(mut client) = clients.get_mut(added_viewer) { + for (mut client, visible_entity_layers, old_visible_entity_layers) in clients_query.iter_mut() { + let old_layers = old_visible_entity_layers.get(); + let current_layers = &visible_entity_layers.0; + + for &added_layer in current_layers.difference(old_layers) { + if let Some((id, title, health, style, flags, _)) = boss_bars_query + .iter() + .find(|(_, _, _, _, _, layer_id)| layer_id.0 == added_layer) + { client.write_packet(&BossBarS2c { id: id.0, action: BossBarAction::Add { - title: Cow::Borrowed(&title.0), + title: title.0.to_owned(), health: health.0, color: style.color, division: style.division, flags: *flags, }, }); - } + }; } - for &removed_viewer in old_viewers.difference(current_viewers) { - if let Ok(mut client) = clients.get_mut(removed_viewer) { + for &removed_layer in old_layers.difference(current_layers) { + if let Some((id, _, _, _, _, _)) = boss_bars_query + .iter() + .find(|(_, _, _, _, _, layer_id)| layer_id.0 == removed_layer) + { client.write_packet(&BossBarS2c { id: id.0, action: BossBarAction::Remove, }); } } - - boss_bar_viewers.old_viewers = boss_bar_viewers.viewers.clone(); } } -/// System that sends a bossbar remove packet to all viewers of a boss bar that -/// has been despawned. fn boss_bar_despawn( - mut boss_bars: Query<(&UniqueId, &BossBarViewers), Added>, - mut clients: Query<&mut Client>, + boss_bars_query: Query<(&UniqueId, &EntityLayerId), Added>, + mut clients_query: Query<(&mut Client, &VisibleEntityLayers)>, ) { - for (id, viewers) in boss_bars.iter_mut() { - for viewer in viewers.viewers.iter() { - if let Ok(mut client) = clients.get_mut(*viewer) { + for (id, entity_layer_id) in boss_bars_query.iter() { + for (mut client, visible_layer_id) in clients_query.iter_mut() { + if visible_layer_id.0.contains(&entity_layer_id.0) { client.write_packet(&BossBarS2c { id: id.0, action: BossBarAction::Remove, @@ -246,16 +137,3 @@ fn boss_bar_despawn( } } } - -/// System that removes a client from the viewers of its boss bars when it -/// disconnects. -fn client_disconnection( - disconnected_clients: Query, Added)>, - mut boss_bars_viewers: Query<&mut BossBarViewers>, -) { - for entity in disconnected_clients.iter() { - for mut boss_bar_viewers in boss_bars_viewers.iter_mut() { - boss_bar_viewers.viewers.remove(&entity); - } - } -} diff --git a/crates/valence_core/src/protocol/packet.old.rs b/crates/valence_core/src/protocol/packet.old.rs deleted file mode 100644 index d11b6ded1..000000000 --- a/crates/valence_core/src/protocol/packet.old.rs +++ /dev/null @@ -1,1316 +0,0 @@ -//! A temporary module for packets that do not yet have a home outside of -//! valence_core. -//! -//! All packets should be moved out of this module eventually. - -use std::borrow::Cow; -use std::io::Write; - -use anyhow::bail; -use bitfield_struct::bitfield; -use byteorder::WriteBytesExt; -use glam::IVec3; -use uuid::Uuid; - -use crate::ident; -use crate::ident::Ident; -use crate::protocol::var_int::VarInt; -use crate::protocol::{packet_id, Decode, Encode, Packet}; -use crate::text::Text; - -// TODO: move module contents to valence_chat. -pub mod chat { - pub use super::*; - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::GAME_MESSAGE_S2C)] - pub struct GameMessageS2c<'a> { - pub chat: Cow<'a, Text>, - /// Whether the message is in the actionbar or the chat. - pub overlay: bool, - } - - #[derive(Copy, Clone, PartialEq, Debug)] - pub struct MessageSignature<'a> { - pub message_id: i32, - pub signature: Option<&'a [u8; 256]>, - } - - impl<'a> Encode for MessageSignature<'a> { - fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { - VarInt(self.message_id + 1).encode(&mut w)?; - - match self.signature { - None => {} - Some(signature) => signature.encode(&mut w)?, - } - - Ok(()) - } - } - - impl<'a> Decode<'a> for MessageSignature<'a> { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - let message_id = VarInt::decode(r)?.0 - 1; // TODO: this can underflow. - - let signature = if message_id == -1 { - Some(<&[u8; 256]>::decode(r)?) - } else { - None - }; - - Ok(Self { - message_id, - signature, - }) - } - } - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::CHAT_MESSAGE_C2S)] - pub struct ChatMessageC2s<'a> { - pub message: &'a str, - pub timestamp: u64, - pub salt: u64, - pub signature: Option<&'a [u8; 256]>, - pub message_count: VarInt, - // This is a bitset of 20; each bit represents one - // of the last 20 messages received and whether or not - // the message was acknowledged by the client - pub acknowledgement: [u8; 3], - } - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::COMMAND_EXECUTION_C2S)] - pub struct CommandExecutionC2s<'a> { - pub command: &'a str, - pub timestamp: u64, - pub salt: u64, - pub argument_signatures: Vec>, - pub message_count: VarInt, - //// This is a bitset of 20; each bit represents one - //// of the last 20 messages received and whether or not - //// the message was acknowledged by the client - pub acknowledgement: [u8; 3], - } - - #[derive(Copy, Clone, Debug, Encode, Decode)] - pub struct CommandArgumentSignature<'a> { - pub argument_name: &'a str, - pub signature: &'a [u8; 256], - } - - #[derive(Copy, Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::MESSAGE_ACKNOWLEDGMENT_C2S)] - - pub struct MessageAcknowledgmentC2s { - pub message_count: VarInt, - } - - #[derive(Copy, Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::PLAYER_SESSION_C2S)] - pub struct PlayerSessionC2s<'a> { - pub session_id: Uuid, - // Public key - pub expires_at: i64, - pub public_key_data: &'a [u8], - pub key_signature: &'a [u8], - } - - #[derive(Copy, Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::REQUEST_COMMAND_COMPLETIONS_C2S)] - pub struct RequestCommandCompletionsC2s<'a> { - pub transaction_id: VarInt, - pub text: &'a str, - } - - #[derive(Clone, PartialEq, Debug, Packet)] - #[packet(id = packet_id::CHAT_MESSAGE_S2C)] - pub struct ChatMessageS2c<'a> { - pub sender: Uuid, - pub index: VarInt, - pub message_signature: Option<&'a [u8; 256]>, - pub message: &'a str, - pub time_stamp: u64, - pub salt: u64, - pub previous_messages: Vec>, - pub unsigned_content: Option>, - pub filter_type: MessageFilterType, - pub filter_type_bits: Option, - pub chat_type: VarInt, - pub network_name: Cow<'a, Text>, - pub network_target_name: Option>, - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] - pub enum MessageFilterType { - PassThrough, - FullyFiltered, - PartiallyFiltered, - } - - impl<'a> Encode for ChatMessageS2c<'a> { - fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { - self.sender.encode(&mut w)?; - self.index.encode(&mut w)?; - self.message_signature.encode(&mut w)?; - self.message.encode(&mut w)?; - self.time_stamp.encode(&mut w)?; - self.salt.encode(&mut w)?; - self.previous_messages.encode(&mut w)?; - self.unsigned_content.encode(&mut w)?; - self.filter_type.encode(&mut w)?; - - if self.filter_type == MessageFilterType::PartiallyFiltered { - match self.filter_type_bits { - // Filler data - None => 0u8.encode(&mut w)?, - Some(bits) => bits.encode(&mut w)?, - } - } - - self.chat_type.encode(&mut w)?; - self.network_name.encode(&mut w)?; - self.network_target_name.encode(&mut w)?; - - Ok(()) - } - } - - impl<'a> Decode<'a> for ChatMessageS2c<'a> { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - let sender = Uuid::decode(r)?; - let index = VarInt::decode(r)?; - let message_signature = Option::<&'a [u8; 256]>::decode(r)?; - let message = <&str>::decode(r)?; - let time_stamp = u64::decode(r)?; - let salt = u64::decode(r)?; - let previous_messages = Vec::::decode(r)?; - let unsigned_content = Option::>::decode(r)?; - let filter_type = MessageFilterType::decode(r)?; - - let filter_type_bits = match filter_type { - MessageFilterType::PartiallyFiltered => Some(u8::decode(r)?), - _ => None, - }; - - let chat_type = VarInt::decode(r)?; - let network_name = >::decode(r)?; - let network_target_name = Option::>::decode(r)?; - - Ok(Self { - sender, - index, - message_signature, - message, - time_stamp, - salt, - previous_messages, - unsigned_content, - filter_type, - filter_type_bits, - chat_type, - network_name, - network_target_name, - }) - } - } - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::CHAT_SUGGESTIONS_S2C)] - pub struct ChatSuggestionsS2c<'a> { - pub action: ChatSuggestionsAction, - pub entries: Cow<'a, [&'a str]>, - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] - pub enum ChatSuggestionsAction { - Add, - Remove, - Set, - } - - #[derive(Copy, Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::REMOVE_MESSAGE_S2C)] - pub struct RemoveMessageS2c<'a> { - pub signature: MessageSignature<'a>, - } - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::COMMAND_SUGGESTIONS_S2C)] - pub struct CommandSuggestionsS2c<'a> { - pub id: VarInt, - pub start: VarInt, - pub length: VarInt, - pub matches: Vec>, - } - - #[derive(Clone, PartialEq, Debug, Encode, Decode)] - pub struct CommandSuggestionsMatch<'a> { - pub suggested_match: &'a str, - pub tooltip: Option>, - } - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::PROFILELESS_CHAT_MESSAGE_S2C)] - pub struct ProfilelessChatMessageS2c<'a> { - pub message: Cow<'a, Text>, - pub chat_type: VarInt, - pub chat_type_name: Cow<'a, Text>, - pub target_name: Option>, - } -} - -// TODO: move to valence_scoreboard? -pub mod scoreboard { - - use super::*; - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::TEAM_S2C)] - pub struct TeamS2c<'a> { - pub team_name: &'a str, - pub mode: Mode<'a>, - } - - #[derive(Clone, PartialEq, Debug)] - pub enum Mode<'a> { - CreateTeam { - team_display_name: Cow<'a, Text>, - friendly_flags: TeamFlags, - name_tag_visibility: NameTagVisibility, - collision_rule: CollisionRule, - team_color: TeamColor, - team_prefix: Cow<'a, Text>, - team_suffix: Cow<'a, Text>, - entities: Vec<&'a str>, - }, - RemoveTeam, - UpdateTeamInfo { - team_display_name: Cow<'a, Text>, - friendly_flags: TeamFlags, - name_tag_visibility: NameTagVisibility, - collision_rule: CollisionRule, - team_color: TeamColor, - team_prefix: Cow<'a, Text>, - team_suffix: Cow<'a, Text>, - }, - AddEntities { - entities: Vec<&'a str>, - }, - RemoveEntities { - entities: Vec<&'a str>, - }, - } - - #[bitfield(u8)] - #[derive(PartialEq, Eq, Encode, Decode)] - pub struct TeamFlags { - pub friendly_fire: bool, - pub see_invisible_teammates: bool, - #[bits(6)] - _pad: u8, - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug)] - pub enum NameTagVisibility { - Always, - Never, - HideForOtherTeams, - HideForOwnTeam, - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug)] - pub enum CollisionRule { - Always, - Never, - PushOtherTeams, - PushOwnTeam, - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] - pub enum TeamColor { - Black, - DarkBlue, - DarkGreen, - DarkCyan, - DarkRed, - Purple, - Gold, - Gray, - DarkGray, - Blue, - BrightGreen, - Cyan, - Red, - Pink, - Yellow, - White, - Obfuscated, - Bold, - Strikethrough, - Underlined, - Italic, - Reset, - } - - impl Encode for Mode<'_> { - fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { - match self { - Mode::CreateTeam { - team_display_name, - friendly_flags, - name_tag_visibility, - collision_rule, - team_color, - team_prefix, - team_suffix, - entities, - } => { - 0i8.encode(&mut w)?; - team_display_name.encode(&mut w)?; - friendly_flags.encode(&mut w)?; - match name_tag_visibility { - NameTagVisibility::Always => "always", - NameTagVisibility::Never => "never", - NameTagVisibility::HideForOtherTeams => "hideForOtherTeams", - NameTagVisibility::HideForOwnTeam => "hideForOwnTeam", - } - .encode(&mut w)?; - match collision_rule { - CollisionRule::Always => "always", - CollisionRule::Never => "never", - CollisionRule::PushOtherTeams => "pushOtherTeams", - CollisionRule::PushOwnTeam => "pushOwnTeam", - } - .encode(&mut w)?; - team_color.encode(&mut w)?; - team_prefix.encode(&mut w)?; - team_suffix.encode(&mut w)?; - entities.encode(&mut w)?; - } - Mode::RemoveTeam => 1i8.encode(&mut w)?, - Mode::UpdateTeamInfo { - team_display_name, - friendly_flags, - name_tag_visibility, - collision_rule, - team_color, - team_prefix, - team_suffix, - } => { - 2i8.encode(&mut w)?; - team_display_name.encode(&mut w)?; - friendly_flags.encode(&mut w)?; - match name_tag_visibility { - NameTagVisibility::Always => "always", - NameTagVisibility::Never => "never", - NameTagVisibility::HideForOtherTeams => "hideForOtherTeams", - NameTagVisibility::HideForOwnTeam => "hideForOwnTeam", - } - .encode(&mut w)?; - match collision_rule { - CollisionRule::Always => "always", - CollisionRule::Never => "never", - CollisionRule::PushOtherTeams => "pushOtherTeams", - CollisionRule::PushOwnTeam => "pushOwnTeam", - } - .encode(&mut w)?; - team_color.encode(&mut w)?; - team_prefix.encode(&mut w)?; - team_suffix.encode(&mut w)?; - } - Mode::AddEntities { entities } => { - 3i8.encode(&mut w)?; - entities.encode(&mut w)?; - } - Mode::RemoveEntities { entities } => { - 4i8.encode(&mut w)?; - entities.encode(&mut w)?; - } - } - Ok(()) - } - } - - impl<'a> Decode<'a> for Mode<'a> { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - Ok(match i8::decode(r)? { - 0 => Self::CreateTeam { - team_display_name: Decode::decode(r)?, - friendly_flags: Decode::decode(r)?, - name_tag_visibility: match <&str>::decode(r)? { - "always" => NameTagVisibility::Always, - "never" => NameTagVisibility::Never, - "hideForOtherTeams" => NameTagVisibility::HideForOtherTeams, - "hideForOwnTeam" => NameTagVisibility::HideForOwnTeam, - other => bail!("unknown name tag visibility type \"{other}\""), - }, - collision_rule: match <&str>::decode(r)? { - "always" => CollisionRule::Always, - "never" => CollisionRule::Never, - "pushOtherTeams" => CollisionRule::PushOtherTeams, - "pushOwnTeam" => CollisionRule::PushOwnTeam, - other => bail!("unknown collision rule type \"{other}\""), - }, - team_color: Decode::decode(r)?, - team_prefix: Decode::decode(r)?, - team_suffix: Decode::decode(r)?, - entities: Decode::decode(r)?, - }, - 1 => Self::RemoveTeam, - 2 => Self::UpdateTeamInfo { - team_display_name: Decode::decode(r)?, - friendly_flags: Decode::decode(r)?, - name_tag_visibility: match <&str>::decode(r)? { - "always" => NameTagVisibility::Always, - "never" => NameTagVisibility::Never, - "hideForOtherTeams" => NameTagVisibility::HideForOtherTeams, - "hideForOwnTeam" => NameTagVisibility::HideForOwnTeam, - other => bail!("unknown name tag visibility type \"{other}\""), - }, - collision_rule: match <&str>::decode(r)? { - "always" => CollisionRule::Always, - "never" => CollisionRule::Never, - "pushOtherTeams" => CollisionRule::PushOtherTeams, - "pushOwnTeam" => CollisionRule::PushOwnTeam, - other => bail!("unknown collision rule type \"{other}\""), - }, - team_color: Decode::decode(r)?, - team_prefix: Decode::decode(r)?, - team_suffix: Decode::decode(r)?, - }, - 3 => Self::AddEntities { - entities: Decode::decode(r)?, - }, - 4 => Self::RemoveEntities { - entities: Decode::decode(r)?, - }, - n => bail!("unknown update teams action of {n}"), - }) - } - } - - #[derive(Copy, Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::SCOREBOARD_DISPLAY_S2C)] - pub struct ScoreboardDisplayS2c<'a> { - pub position: ScoreboardPosition, - pub score_name: &'a str, - } - - #[derive(Copy, Clone, PartialEq, Debug)] - pub enum ScoreboardPosition { - List, - Sidebar, - BelowName, - SidebarTeam(TeamColor), - } - - impl Encode for ScoreboardPosition { - fn encode(&self, w: impl std::io::Write) -> anyhow::Result<()> { - match self { - ScoreboardPosition::List => 0u8.encode(w), - ScoreboardPosition::Sidebar => 1u8.encode(w), - ScoreboardPosition::BelowName => 2u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::Black) => 3u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::DarkBlue) => 4u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::DarkGreen) => 5u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::DarkCyan) => 6u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::DarkRed) => 7u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::Purple) => 8u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::Gold) => 9u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::Gray) => 10u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::DarkGray) => 11u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::Blue) => 12u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::BrightGreen) => 13u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::Cyan) => 14u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::Red) => 15u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::Pink) => 16u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::Yellow) => 17u8.encode(w), - ScoreboardPosition::SidebarTeam(TeamColor::White) => 18u8.encode(w), - ScoreboardPosition::SidebarTeam(_) => { - Err(anyhow::anyhow!("Invalid scoreboard display position")) - } - } - } - } - - impl<'a> Decode<'a> for ScoreboardPosition { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - let value = u8::decode(r)?; - match value { - 0 => Ok(ScoreboardPosition::List), - 1 => Ok(ScoreboardPosition::Sidebar), - 2 => Ok(ScoreboardPosition::BelowName), - 3 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::Black)), - 4 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::DarkBlue)), - 5 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::DarkGreen)), - 6 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::DarkCyan)), - 7 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::DarkRed)), - 8 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::Purple)), - 9 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::Gold)), - 10 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::Gray)), - 11 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::DarkGray)), - 12 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::Blue)), - 13 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::BrightGreen)), - 14 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::Cyan)), - 15 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::Red)), - 16 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::Pink)), - 17 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::Yellow)), - 18 => Ok(ScoreboardPosition::SidebarTeam(TeamColor::White)), - _ => Err(anyhow::anyhow!("Invalid scoreboard display position")), - } - } - } - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::SCOREBOARD_OBJECTIVE_UPDATE_S2C)] - pub struct ScoreboardObjectiveUpdateS2c<'a> { - pub objective_name: &'a str, - pub mode: ObjectiveMode, - } - - #[derive(Clone, PartialEq, Debug, Encode, Decode)] - pub enum ObjectiveMode { - Create { - objective_display_name: Text, - render_type: ObjectiveRenderType, - }, - Remove, - Update { - objective_display_name: Text, - render_type: ObjectiveRenderType, - }, - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] - pub enum ObjectiveRenderType { - Integer, - Hearts, - } - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::SCOREBOARD_PLAYER_UPDATE_S2C)] - pub struct ScoreboardPlayerUpdateS2c<'a> { - pub entity_name: &'a str, - pub action: ScoreboardPlayerUpdateAction<'a>, - } - - #[derive(Clone, PartialEq, Debug, Encode, Decode)] - pub enum ScoreboardPlayerUpdateAction<'a> { - Update { - objective_name: &'a str, - objective_score: VarInt, - }, - Remove { - objective_name: &'a str, - }, - } -} - -// TODO: move to valence_sound? -pub mod sound { - use super::*; - - include!(concat!(env!("OUT_DIR"), "/sound.rs")); - - impl Sound { - pub fn to_id(self) -> SoundId<'static> { - SoundId::Direct { - id: self.to_ident().into(), - range: None, - } - } - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] - pub enum SoundCategory { - Master, - Music, - Record, - Weather, - Block, - Hostile, - Neutral, - Player, - Ambient, - Voice, - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn sound_to_soundid() { - assert_eq!( - Sound::BlockBellUse.to_id(), - SoundId::Direct { - id: ident!("block.bell.use").into(), - range: None - }, - ); - } - } - - #[derive(Copy, Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::PLAY_SOUND_FROM_ENTITY_S2C)] - pub struct PlaySoundFromEntityS2c { - pub id: VarInt, - pub category: SoundCategory, - pub entity_id: VarInt, - pub volume: f32, - pub pitch: f32, - pub seed: i64, - } - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::PLAY_SOUND_S2C)] - pub struct PlaySoundS2c<'a> { - pub id: SoundId<'a>, - pub category: SoundCategory, - pub position: IVec3, - pub volume: f32, - pub pitch: f32, - pub seed: i64, - } - - #[derive(Clone, PartialEq, Debug)] - pub enum SoundId<'a> { - Direct { - id: Ident>, - range: Option, - }, - Reference { - id: VarInt, - }, - } - - impl Encode for SoundId<'_> { - fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { - match self { - SoundId::Direct { id, range } => { - VarInt(0).encode(&mut w)?; - id.encode(&mut w)?; - range.encode(&mut w)?; - } - SoundId::Reference { id } => VarInt(id.0 + 1).encode(&mut w)?, - } - - Ok(()) - } - } - - impl<'a> Decode<'a> for SoundId<'a> { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - let i = VarInt::decode(r)?.0; - - if i == 0 { - Ok(SoundId::Direct { - id: Ident::decode(r)?, - range: >::decode(r)?, - }) - } else { - Ok(SoundId::Reference { id: VarInt(i - 1) }) - } - } - } - - #[derive(Clone, PartialEq, Debug, Packet)] - #[packet(id = packet_id::STOP_SOUND_S2C)] - pub struct StopSoundS2c<'a> { - pub source: Option, - pub sound: Option>>, - } - - impl Encode for StopSoundS2c<'_> { - fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { - match (self.source, self.sound.as_ref()) { - (Some(source), Some(sound)) => { - 3i8.encode(&mut w)?; - source.encode(&mut w)?; - sound.encode(&mut w)?; - } - (None, Some(sound)) => { - 2i8.encode(&mut w)?; - sound.encode(&mut w)?; - } - (Some(source), None) => { - 1i8.encode(&mut w)?; - source.encode(&mut w)?; - } - _ => 0i8.encode(&mut w)?, - } - - Ok(()) - } - } - - impl<'a> Decode<'a> for StopSoundS2c<'a> { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - let (source, sound) = match i8::decode(r)? { - 3 => ( - Some(SoundCategory::decode(r)?), - Some(>>::decode(r)?), - ), - 2 => (None, Some(>>::decode(r)?)), - 1 => (Some(SoundCategory::decode(r)?), None), - _ => (None, None), - }; - - Ok(Self { source, sound }) - } - } -} - -// TODO: move to valence_command -pub mod command { - - use super::*; - - #[derive(Clone, Debug, Encode, Decode, Packet)] - #[packet(id = packet_id::COMMAND_TREE_S2C)] - pub struct CommandTreeS2c<'a> { - pub commands: Vec>, - pub root_index: VarInt, - } - - #[derive(Clone, Debug)] - pub struct Node<'a> { - pub children: Vec, - pub data: NodeData<'a>, - pub executable: bool, - pub redirect_node: Option, - } - - #[derive(Clone, Debug)] - pub enum NodeData<'a> { - Root, - Literal { - name: &'a str, - }, - Argument { - name: &'a str, - parser: Parser<'a>, - suggestion: Option, - }, - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug)] - pub enum Suggestion { - AskServer, - AllRecipes, - AvailableSounds, - AvailableBiomes, - SummonableEntities, - } - - #[derive(Clone, Debug)] - pub enum Parser<'a> { - Bool, - Float { min: Option, max: Option }, - Double { min: Option, max: Option }, - Integer { min: Option, max: Option }, - Long { min: Option, max: Option }, - String(StringArg), - Entity { single: bool, only_players: bool }, - GameProfile, - BlockPos, - ColumnPos, - Vec3, - Vec2, - BlockState, - BlockPredicate, - ItemStack, - ItemPredicate, - Color, - Component, - Message, - NbtCompoundTag, - NbtTag, - NbtPath, - Objective, - ObjectiveCriteria, - Operation, - Particle, - Angle, - Rotation, - ScoreboardSlot, - ScoreHolder { allow_multiple: bool }, - Swizzle, - Team, - ItemSlot, - ResourceLocation, - Function, - EntityAnchor, - IntRange, - FloatRange, - Dimension, - GameMode, - Time, - ResourceOrTag { registry: Ident> }, - ResourceOrTagKey { registry: Ident> }, - Resource { registry: Ident> }, - ResourceKey { registry: Ident> }, - TemplateMirror, - TemplateRotation, - Uuid, - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] - pub enum StringArg { - SingleWord, - QuotablePhrase, - GreedyPhrase, - } - - impl Encode for Node<'_> { - fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { - let node_type = match &self.data { - NodeData::Root => 0, - NodeData::Literal { .. } => 1, - NodeData::Argument { .. } => 2, - }; - - let has_suggestion = matches!( - &self.data, - NodeData::Argument { - suggestion: Some(_), - .. - } - ); - - let flags: u8 = node_type - | (self.executable as u8 * 0x04) - | (self.redirect_node.is_some() as u8 * 0x08) - | (has_suggestion as u8 * 0x10); - - w.write_u8(flags)?; - - self.children.encode(&mut w)?; - - if let Some(redirect_node) = self.redirect_node { - redirect_node.encode(&mut w)?; - } - - match &self.data { - NodeData::Root => {} - NodeData::Literal { name } => { - name.encode(&mut w)?; - } - NodeData::Argument { - name, - parser, - suggestion, - } => { - name.encode(&mut w)?; - parser.encode(&mut w)?; - - if let Some(suggestion) = suggestion { - match suggestion { - Suggestion::AskServer => "ask_server", - Suggestion::AllRecipes => "all_recipes", - Suggestion::AvailableSounds => "available_sounds", - Suggestion::AvailableBiomes => "available_biomes", - Suggestion::SummonableEntities => "summonable_entities", - } - .encode(&mut w)?; - } - } - } - - Ok(()) - } - } - - impl<'a> Decode<'a> for Node<'a> { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - let flags = u8::decode(r)?; - - let children = Vec::decode(r)?; - - let redirect_node = if flags & 0x08 != 0 { - Some(VarInt::decode(r)?) - } else { - None - }; - - let node_data = match flags & 0x3 { - 0 => NodeData::Root, - 1 => NodeData::Literal { - name: <&str>::decode(r)?, - }, - 2 => NodeData::Argument { - name: <&str>::decode(r)?, - parser: Parser::decode(r)?, - suggestion: if flags & 0x10 != 0 { - Some(match Ident::>::decode(r)?.as_str() { - "minecraft:ask_server" => Suggestion::AskServer, - "minecraft:all_recipes" => Suggestion::AllRecipes, - "minecraft:available_sounds" => Suggestion::AvailableSounds, - "minecraft:available_biomes" => Suggestion::AvailableBiomes, - "minecraft:summonable_entities" => Suggestion::SummonableEntities, - other => bail!("unknown command suggestion type of \"{other}\""), - }) - } else { - None - }, - }, - n => bail!("invalid node type of {n}"), - }; - - Ok(Self { - children, - data: node_data, - executable: flags & 0x04 != 0, - redirect_node, - }) - } - } - - impl Encode for Parser<'_> { - fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { - match self { - Parser::Bool => 0u8.encode(&mut w)?, - Parser::Float { min, max } => { - 1u8.encode(&mut w)?; - - (min.is_some() as u8 | (max.is_some() as u8 * 0x2)).encode(&mut w)?; - - if let Some(min) = min { - min.encode(&mut w)?; - } - - if let Some(max) = max { - max.encode(&mut w)?; - } - } - Parser::Double { min, max } => { - 2u8.encode(&mut w)?; - - (min.is_some() as u8 | (max.is_some() as u8 * 0x2)).encode(&mut w)?; - - if let Some(min) = min { - min.encode(&mut w)?; - } - - if let Some(max) = max { - max.encode(&mut w)?; - } - } - Parser::Integer { min, max } => { - 3u8.encode(&mut w)?; - - (min.is_some() as u8 | (max.is_some() as u8 * 0x2)).encode(&mut w)?; - - if let Some(min) = min { - min.encode(&mut w)?; - } - - if let Some(max) = max { - max.encode(&mut w)?; - } - } - Parser::Long { min, max } => { - 4u8.encode(&mut w)?; - - (min.is_some() as u8 | (max.is_some() as u8 * 0x2)).encode(&mut w)?; - - if let Some(min) = min { - min.encode(&mut w)?; - } - - if let Some(max) = max { - max.encode(&mut w)?; - } - } - Parser::String(arg) => { - 5u8.encode(&mut w)?; - arg.encode(&mut w)?; - } - Parser::Entity { - single, - only_players, - } => { - 6u8.encode(&mut w)?; - (*single as u8 | (*only_players as u8 * 0x2)).encode(&mut w)?; - } - Parser::GameProfile => 7u8.encode(&mut w)?, - Parser::BlockPos => 8u8.encode(&mut w)?, - Parser::ColumnPos => 9u8.encode(&mut w)?, - Parser::Vec3 => 10u8.encode(&mut w)?, - Parser::Vec2 => 11u8.encode(&mut w)?, - Parser::BlockState => 12u8.encode(&mut w)?, - Parser::BlockPredicate => 13u8.encode(&mut w)?, - Parser::ItemStack => 14u8.encode(&mut w)?, - Parser::ItemPredicate => 15u8.encode(&mut w)?, - Parser::Color => 16u8.encode(&mut w)?, - Parser::Component => 17u8.encode(&mut w)?, - Parser::Message => 18u8.encode(&mut w)?, - Parser::NbtCompoundTag => 19u8.encode(&mut w)?, - Parser::NbtTag => 20u8.encode(&mut w)?, - Parser::NbtPath => 21u8.encode(&mut w)?, - Parser::Objective => 22u8.encode(&mut w)?, - Parser::ObjectiveCriteria => 23u8.encode(&mut w)?, - Parser::Operation => 24u8.encode(&mut w)?, - Parser::Particle => 25u8.encode(&mut w)?, - Parser::Angle => 26u8.encode(&mut w)?, - Parser::Rotation => 27u8.encode(&mut w)?, - Parser::ScoreboardSlot => 28u8.encode(&mut w)?, - Parser::ScoreHolder { allow_multiple } => { - 29u8.encode(&mut w)?; - allow_multiple.encode(&mut w)?; - } - Parser::Swizzle => 30u8.encode(&mut w)?, - Parser::Team => 31u8.encode(&mut w)?, - Parser::ItemSlot => 32u8.encode(&mut w)?, - Parser::ResourceLocation => 33u8.encode(&mut w)?, - Parser::Function => 34u8.encode(&mut w)?, - Parser::EntityAnchor => 35u8.encode(&mut w)?, - Parser::IntRange => 36u8.encode(&mut w)?, - Parser::FloatRange => 37u8.encode(&mut w)?, - Parser::Dimension => 38u8.encode(&mut w)?, - Parser::GameMode => 39u8.encode(&mut w)?, - Parser::Time => 40u8.encode(&mut w)?, - Parser::ResourceOrTag { registry } => { - 41u8.encode(&mut w)?; - registry.encode(&mut w)?; - } - Parser::ResourceOrTagKey { registry } => { - 42u8.encode(&mut w)?; - registry.encode(&mut w)?; - } - Parser::Resource { registry } => { - 43u8.encode(&mut w)?; - registry.encode(&mut w)?; - } - Parser::ResourceKey { registry } => { - 44u8.encode(&mut w)?; - registry.encode(&mut w)?; - } - Parser::TemplateMirror => 45u8.encode(&mut w)?, - Parser::TemplateRotation => 46u8.encode(&mut w)?, - Parser::Uuid => 47u8.encode(&mut w)?, - } - - Ok(()) - } - } - - impl<'a> Decode<'a> for Parser<'a> { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - fn decode_min_max<'a, T: Decode<'a>>( - r: &mut &'a [u8], - ) -> anyhow::Result<(Option, Option)> { - let flags = u8::decode(r)?; - - let min = if flags & 0x1 != 0 { - Some(T::decode(r)?) - } else { - None - }; - - let max = if flags & 0x2 != 0 { - Some(T::decode(r)?) - } else { - None - }; - - Ok((min, max)) - } - - Ok(match u8::decode(r)? { - 0 => Self::Bool, - 1 => { - let (min, max) = decode_min_max(r)?; - Self::Float { min, max } - } - 2 => { - let (min, max) = decode_min_max(r)?; - Self::Double { min, max } - } - 3 => { - let (min, max) = decode_min_max(r)?; - Self::Integer { min, max } - } - 4 => { - let (min, max) = decode_min_max(r)?; - Self::Long { min, max } - } - 5 => Self::String(StringArg::decode(r)?), - 6 => { - let flags = u8::decode(r)?; - Self::Entity { - single: flags & 0x1 != 0, - only_players: flags & 0x2 != 0, - } - } - 7 => Self::GameProfile, - 8 => Self::BlockPos, - 9 => Self::ColumnPos, - 10 => Self::Vec3, - 11 => Self::Vec2, - 12 => Self::BlockState, - 13 => Self::BlockPredicate, - 14 => Self::ItemStack, - 15 => Self::ItemPredicate, - 16 => Self::Color, - 17 => Self::Component, - 18 => Self::Message, - 19 => Self::NbtCompoundTag, - 20 => Self::NbtTag, - 21 => Self::NbtPath, - 22 => Self::Objective, - 23 => Self::ObjectiveCriteria, - 24 => Self::Operation, - 25 => Self::Particle, - 26 => Self::Angle, - 27 => Self::Rotation, - 28 => Self::ScoreboardSlot, - 29 => Self::ScoreHolder { - allow_multiple: bool::decode(r)?, - }, - 30 => Self::Swizzle, - 31 => Self::Team, - 32 => Self::ItemSlot, - 33 => Self::ResourceLocation, - 34 => Self::Function, - 35 => Self::EntityAnchor, - 36 => Self::IntRange, - 37 => Self::FloatRange, - 38 => Self::Dimension, - 39 => Self::GameMode, - 40 => Self::Time, - 41 => Self::ResourceOrTag { - registry: Ident::decode(r)?, - }, - 42 => Self::ResourceOrTagKey { - registry: Ident::decode(r)?, - }, - 43 => Self::Resource { - registry: Ident::decode(r)?, - }, - 44 => Self::ResourceKey { - registry: Ident::decode(r)?, - }, - 45 => Self::TemplateMirror, - 46 => Self::TemplateRotation, - 47 => Self::Uuid, - n => bail!("unknown command parser ID of {n}"), - }) - } - } -} - -/// Move to valence_map? -pub mod map { - use super::*; - - #[derive(Clone, PartialEq, Debug, Packet)] - #[packet(id = packet_id::MAP_UPDATE_S2C)] - pub struct MapUpdateS2c<'a> { - pub map_id: VarInt, - pub scale: i8, - pub locked: bool, - pub icons: Option>>, - pub data: Option>, - } - - #[derive(Clone, PartialEq, Debug, Encode, Decode)] - pub struct Icon<'a> { - pub icon_type: IconType, - /// In map coordinates; -128 for furthest left, +127 for furthest right - pub position: [i8; 2], - /// 0 is a vertical icon and increments by 22.5° - pub direction: i8, - pub display_name: Option>, - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] - pub enum IconType { - WhiteArrow, - GreenArrow, - RedArrow, - BlueArrow, - WhiteCross, - RedPointer, - WhiteCircle, - SmallWhiteCircle, - Mansion, - Temple, - WhiteBanner, - OrangeBanner, - MagentaBanner, - LightBlueBanner, - YellowBanner, - LimeBanner, - PinkBanner, - GrayBanner, - LightGrayBanner, - CyanBanner, - PurpleBanner, - BlueBanner, - BrownBanner, - GreenBanner, - RedBanner, - BlackBanner, - TreasureMarker, - } - - #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode)] - pub struct Data<'a> { - pub columns: u8, - pub rows: u8, - pub position: [i8; 2], - pub data: &'a [u8], - } - - impl Encode for MapUpdateS2c<'_> { - fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { - self.map_id.encode(&mut w)?; - self.scale.encode(&mut w)?; - self.locked.encode(&mut w)?; - self.icons.encode(&mut w)?; - - match self.data { - None => 0u8.encode(&mut w)?, - Some(data) => data.encode(&mut w)?, - } - - Ok(()) - } - } - - impl<'a> Decode<'a> for MapUpdateS2c<'a> { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - let map_id = VarInt::decode(r)?; - let scale = i8::decode(r)?; - let locked = bool::decode(r)?; - let icons = >>>::decode(r)?; - let columns = u8::decode(r)?; - - let data = if columns > 0 { - let rows = u8::decode(r)?; - let position = <[i8; 2]>::decode(r)?; - let data = <&'a [u8]>::decode(r)?; - - Some(Data { - columns, - rows, - position, - data, - }) - } else { - None - }; - - Ok(Self { - map_id, - scale, - locked, - icons, - data, - }) - } - } -} diff --git a/crates/valence_inventory/src/lib.rs b/crates/valence_inventory/src/lib.rs index ec969eec7..c0ee740a8 100644 --- a/crates/valence_inventory/src/lib.rs +++ b/crates/valence_inventory/src/lib.rs @@ -1237,11 +1237,11 @@ fn handle_update_selected_slot( // The client is trying to interact with a slot that does not exist, ignore. continue; } - held.held_item_slot = convert_hotbar_slot_id(pkt.slot as u16); + held.held_item_slot = convert_hotbar_slot_id(pkt.slot); events.send(UpdateSelectedSlotEvent { client: packet.client, - slot: pkt.slot, + slot: pkt.slot as u8, }); } } diff --git a/crates/valence_packet/src/packets/play/boss_bar_s2c.rs b/crates/valence_packet/src/packets/play/boss_bar_s2c.rs index 50012e1fe..b58c9c558 100644 --- a/crates/valence_packet/src/packets/play/boss_bar_s2c.rs +++ b/crates/valence_packet/src/packets/play/boss_bar_s2c.rs @@ -4,15 +4,15 @@ use super::*; #[derive(Clone, Debug, Encode, Decode, Packet)] #[packet(id = packet_id::BOSS_BAR_S2C)] -pub struct BossBarS2c<'a> { +pub struct BossBarS2c { pub id: Uuid, - pub action: BossBarAction<'a>, + pub action: BossBarAction, } #[derive(Clone, PartialEq, Debug, Encode, Decode)] -pub enum BossBarAction<'a> { +pub enum BossBarAction { Add { - title: Cow<'a, Text>, + title: Text, health: f32, color: BossBarColor, division: BossBarDivision, @@ -20,13 +20,13 @@ pub enum BossBarAction<'a> { }, Remove, UpdateHealth(f32), - UpdateTitle(Cow<'a, Text>), + UpdateTitle(Text), UpdateStyle(BossBarColor, BossBarDivision), UpdateFlags(BossBarFlags), } /// The color of a boss bar. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Component, Default)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Default)] pub enum BossBarColor { #[default] Pink, @@ -39,7 +39,7 @@ pub enum BossBarColor { } /// The division of a boss bar. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Component, Default)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Default)] pub enum BossBarDivision { #[default] NoDivision, @@ -59,3 +59,14 @@ pub struct BossBarFlags { #[bits(5)] _pad: u8, } + +impl ToPacketAction for BossBarFlags { + fn to_packet_action(&self) -> BossBarAction { + BossBarAction::UpdateFlags(*self) + } +} + +/// Trait for converting a component to a boss bar action. +pub trait ToPacketAction { + fn to_packet_action(&self) -> BossBarAction; +} diff --git a/crates/valence_packet/src/packets/play/update_selected_slot_c2s.rs b/crates/valence_packet/src/packets/play/update_selected_slot_c2s.rs index 445238593..855b01bc0 100644 --- a/crates/valence_packet/src/packets/play/update_selected_slot_c2s.rs +++ b/crates/valence_packet/src/packets/play/update_selected_slot_c2s.rs @@ -3,5 +3,5 @@ use super::*; #[derive(Copy, Clone, Debug, Encode, Decode, Packet)] #[packet(id = packet_id::UPDATE_SELECTED_SLOT_C2S)] pub struct UpdateSelectedSlotC2s { - pub slot: u8, + pub slot: u16, } diff --git a/examples/boss_bar.rs b/examples/boss_bar.rs index f6858dfa1..8f8ca343c 100644 --- a/examples/boss_bar.rs +++ b/examples/boss_bar.rs @@ -2,11 +2,9 @@ use rand::seq::SliceRandom; use valence::prelude::*; -use valence_boss_bar::{ - BossBarBundle, BossBarColor, BossBarDivision, BossBarFlags, BossBarHealth, BossBarStyle, - BossBarTitle, BossBarViewers, -}; +use valence_boss_bar::{BossBarBundle, BossBarFlags, BossBarHealth, BossBarStyle, BossBarTitle}; use valence_client::message::{ChatMessageEvent, SendMessage}; +use valence_packet::packets::play::boss_bar_s2c::{BossBarColor, BossBarDivision}; const SPAWN_Y: i32 = 64; @@ -43,23 +41,19 @@ fn setup( } } - commands.spawn(layer); + let layer_id = commands.spawn(layer).id(); - commands.spawn(BossBarBundle { + commands.spawn((BossBarBundle { title: BossBarTitle("Boss Bar".into_text()), - health: BossBarHealth(1.0), - style: BossBarStyle { - color: BossBarColor::Blue, - division: BossBarDivision::TenNotches, - }, + health: BossBarHealth(0.5), + entity_layer_id: EntityLayerId(layer_id), ..Default::default() - }); + },)); } fn init_clients( - mut clients: Query< + mut clients_query: Query< ( - Entity, &mut Client, &mut EntityLayerId, &mut VisibleChunkLayer, @@ -69,23 +63,19 @@ fn init_clients( ), Added, >, - mut boss_bar_viewers: Query<&mut BossBarViewers>, - layers: Query>, + layers_query: Query, With)>, ) { - let mut boss_bar_viewers = boss_bar_viewers.single_mut(); + let layer = layers_query.single(); for ( - entity, mut client, mut layer_id, mut visible_chunk_layer, mut visible_entity_layers, mut pos, mut game_mode, - ) in &mut clients + ) in &mut clients_query { - let layer = layers.single(); - layer_id.0 = layer; visible_chunk_layer.0 = layer; visible_entity_layers.0.insert(layer); @@ -118,40 +108,40 @@ fn init_clients( client.send_chat_message( "Type any number between 0 and 1 to set the health".on_click_suggest_command("health"), ); - - boss_bar_viewers.viewers.insert(entity); } } fn listen_messages( mut message_events: EventReader, - mut boss_bar: Query<( - &mut BossBarViewers, + mut boss_bars_query: Query<( &mut BossBarStyle, &mut BossBarFlags, &mut BossBarHealth, &mut BossBarTitle, + &EntityLayerId, )>, + mut clients_query: Query<&mut VisibleEntityLayers, With>, ) { let ( - mut boss_bar_viewers, mut boss_bar_style, mut boss_bar_flags, mut boss_bar_health, mut boss_bar_title, - ) = boss_bar.single_mut(); + entity_layer_id, + ) = boss_bars_query.single_mut(); - let events: Vec = message_events.iter().cloned().collect(); for ChatMessageEvent { client, message, .. - } in events.iter() + } in message_events.iter() { match message.as_ref() { "view" => { - if boss_bar_viewers.viewers.contains(client) { - boss_bar_viewers.viewers.remove(client); - } else { - boss_bar_viewers.viewers.insert(*client); + if let Ok(mut visible_entity_layers) = clients_query.get_mut(*client) { + if visible_entity_layers.0.contains(&entity_layer_id.0) { + visible_entity_layers.0.remove(&entity_layer_id.0); + } else { + visible_entity_layers.0.insert(entity_layer_id.0); + } } } "color" => { @@ -197,7 +187,7 @@ fn listen_messages( boss_bar_health.0 = health; } } else { - boss_bar_title.0 = message.to_string().into(); + boss_bar_title.0 = message.to_string().into_text(); } } }; diff --git a/src/tests/boss_bar.rs b/src/tests/boss_bar.rs index 244ca6e2e..b79ff1c16 100644 --- a/src/tests/boss_bar.rs +++ b/src/tests/boss_bar.rs @@ -1,31 +1,35 @@ use valence_boss_bar::{ BossBarBundle, BossBarColor, BossBarDivision, BossBarFlags, BossBarHealth, BossBarStyle, - BossBarTitle, BossBarViewers, + BossBarTitle, }; +use valence_client::VisibleEntityLayers; use valence_core::despawn::Despawned; -use valence_core::text::Text; +use valence_core::text::{IntoText, Text}; +use valence_entity::EntityLayerId; use valence_packet::packets::play::BossBarS2c; use crate::testing::ScenarioSingleClient; #[test] fn test_intialize_on_join() { - let ScenarioSingleClient { - mut app, - client, - mut helper, - layer, - } = prepare(); + let mut scenario = ScenarioSingleClient::new(); - // Fetch the boss bar component - let mut boss_bar = app.world.get_mut::(layer).unwrap(); - // Add our mock client to the viewers list - assert!(boss_bar.viewers.insert(client)); - - app.update(); - - // Check if a boss bar packet was sent - let frames = helper.collect_received(); + // Insert a boss bar into the world + scenario + .app + .world + .entity_mut(scenario.layer) + .insert(BossBarBundle { + title: BossBarTitle("Boss Bar".into_text()), + health: BossBarHealth(0.5), + entity_layer_id: EntityLayerId(scenario.layer), + ..Default::default() + }); + + scenario.app.update(); + + // We should receive a boss bar packet with the ADD action + let frames = scenario.helper.collect_received(); frames.assert_count::(1); } @@ -33,45 +37,30 @@ fn test_intialize_on_join() { fn test_despawn() { let ScenarioSingleClient { mut app, - client, mut helper, layer, + .. } = prepare(); - // Fetch the boss bar component - let mut boss_bar = app.world.get_mut::(layer).unwrap(); - // Add our mock client to the viewers list - assert!(boss_bar.viewers.insert(client)); - - app.update(); - // Despawn the boss bar app.world.entity_mut(layer).insert(Despawned); app.update(); - // Check if a boss bar packet was sent in addition to the ADD packet, which - // should be a Remove packet + // We should receive a boss bar packet with the REMOVE action let frames = helper.collect_received(); - frames.assert_count::(2); + frames.assert_count::(1); } #[test] fn test_title_update() { let ScenarioSingleClient { mut app, - client, mut helper, layer, + .. } = prepare(); - // Fetch the boss bar component - let mut boss_bar = app.world.get_mut::(layer).unwrap(); - // Add our mock client to the viewers list - assert!(boss_bar.viewers.insert(client)); - - app.update(); - // Update the title app.world .entity_mut(layer) @@ -79,55 +68,39 @@ fn test_title_update() { app.update(); - // Check if a boss bar packet was sent in addition to the ADD packet, which - // should be an UpdateTitle packet + // We should receive a boss bar packet with the UPDATE_TITLE action let frames = helper.collect_received(); - frames.assert_count::(2); + frames.assert_count::(1); } #[test] fn test_health_update() { let ScenarioSingleClient { mut app, - client, mut helper, layer, + .. } = prepare(); - // Fetch the boss bar component - let mut boss_bar = app.world.get_mut::(layer).unwrap(); - // Add our mock client to the viewers list - assert!(boss_bar.viewers.insert(client)); - - app.update(); - // Update the health app.world.entity_mut(layer).insert(BossBarHealth(0.5)); app.update(); - // Check if a boss bar packet was sent in addition to the ADD packet, which - // should be an UpdateHealth packet + // We should receive a boss bar packet with the UPDATE_HEALTH action let frames = helper.collect_received(); - frames.assert_count::(2); + frames.assert_count::(1); } #[test] fn test_style_update() { let ScenarioSingleClient { mut app, - client, mut helper, layer, + .. } = prepare(); - // Fetch the boss bar component - let mut boss_bar = app.world.get_mut::(layer).unwrap(); - // Add our mock client to the viewers list - assert!(boss_bar.viewers.insert(client)); - - app.update(); - // Update the style app.world.entity_mut(layer).insert(BossBarStyle { color: BossBarColor::Red, @@ -136,28 +109,20 @@ fn test_style_update() { app.update(); - // Check if a boss bar packet was sent in addition to the ADD packet, which - // should be an UpdateStyle packet + // We should receive a boss bar packet with the UPDATE_STYLE action let frames = helper.collect_received(); - frames.assert_count::(2); + frames.assert_count::(1); } #[test] fn test_flags_update() { let ScenarioSingleClient { mut app, - client, mut helper, layer, + .. } = prepare(); - // Fetch the boss bar component - let mut boss_bar = app.world.get_mut::(layer).unwrap(); - // Add our mock client to the viewers list - assert!(boss_bar.viewers.insert(client)); - - app.update(); - // Update the flags let mut new_flags = BossBarFlags::new(); new_flags.set_create_fog(true); @@ -165,44 +130,43 @@ fn test_flags_update() { app.update(); - // Check if a boss bar packet was sent in addition to the ADD packet, which - // should be an UpdateFlags packet + // We should receive a boss bar packet with the UPDATE_FLAGS action let frames = helper.collect_received(); - frames.assert_count::(2); + frames.assert_count::(1); } #[test] -fn test_client_disconnection() { +fn test_client_layer_change() { let ScenarioSingleClient { mut app, - client, mut helper, layer, + client, } = prepare(); - // Fetch the boss bar component - let mut boss_bar = app.world.get_mut::(layer).unwrap(); - // Add our mock client to the viewers list - assert!(boss_bar.viewers.insert(client)); + // Remove the layer from the client + { + let mut visible_entity_layers = app.world.get_mut::(client).unwrap(); + visible_entity_layers.0.clear(); + } app.update(); - // Remove the client from the world - app.world.entity_mut(client).insert(Despawned); + // We should receive a boss bar packet with the REMOVE action + let frames = helper.collect_received(); + frames.assert_count::(1); - app.update(); + // Add the layer back to the client + { + let mut visible_entity_layers = app.world.get_mut::(client).unwrap(); + visible_entity_layers.0.insert(layer); + } - assert!(app - .world - .get_mut::(layer) - .unwrap() - .viewers - .is_empty()); + app.update(); - // Check if a boss bar packet was sent in addition to the ADD packet, which - // should be a Remove packet + // We should receive a boss bar packet with the ADD action let frames = helper.collect_received(); - frames.assert_count::(2); + frames.assert_count::(1); } fn prepare() -> ScenarioSingleClient { @@ -216,12 +180,9 @@ fn prepare() -> ScenarioSingleClient { // Attach the new boss bar to the layer for convenience. s.app.world.entity_mut(s.layer).insert(BossBarBundle { - title: BossBarTitle(Text::text("Test")), - style: BossBarStyle { - color: BossBarColor::Blue, - division: BossBarDivision::SixNotches, - }, - flags: BossBarFlags::new(), + title: BossBarTitle("Boss Bar".into_text()), + health: BossBarHealth(0.5), + entity_layer_id: EntityLayerId(s.layer), ..Default::default() }); From ba0a930d66ae576c32fd4171f57b734e89ea3892 Mon Sep 17 00:00:00 2001 From: Bafbi Date: Fri, 4 Aug 2023 16:37:03 +0200 Subject: [PATCH 2/4] Change `BossBarAction` `Text` to `Cow<'a, Text>` --- crates/valence_boss_bar/src/components.rs | 4 +++- crates/valence_boss_bar/src/lib.rs | 12 +++++++----- .../valence_packet/src/packets/play/boss_bar_s2c.rs | 10 +++++----- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/valence_boss_bar/src/components.rs b/crates/valence_boss_bar/src/components.rs index ff8b1f794..2ffb6fb49 100644 --- a/crates/valence_boss_bar/src/components.rs +++ b/crates/valence_boss_bar/src/components.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use bevy_ecs::prelude::{Bundle, Component}; use valence_core::text::Text; use valence_core::uuid::UniqueId; @@ -24,7 +26,7 @@ pub struct BossBarTitle(pub Text); impl ToPacketAction for BossBarTitle { fn to_packet_action(&self) -> BossBarAction { - BossBarAction::UpdateTitle(self.0.to_owned()) + BossBarAction::UpdateTitle(Cow::Borrowed(&self.0)) } } diff --git a/crates/valence_boss_bar/src/lib.rs b/crates/valence_boss_bar/src/lib.rs index 55290ee89..1ec889b9d 100644 --- a/crates/valence_boss_bar/src/lib.rs +++ b/crates/valence_boss_bar/src/lib.rs @@ -18,9 +18,11 @@ clippy::dbg_macro )] +use std::borrow::Cow; + use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use valence_client::{Client, FlushPacketsSet, OldVisibleEntityLayers, VisibleEntityLayers}; +use valence_client::{Client, OldVisibleEntityLayers, VisibleEntityLayers}; use valence_core::despawn::Despawned; use valence_core::uuid::UniqueId; use valence_packet::packets::play::boss_bar_s2c::{BossBarAction, ToPacketAction}; @@ -30,7 +32,7 @@ use valence_packet::protocol::encode::WritePacket; mod components; pub use components::*; use valence_entity::{EntityLayerId, Position}; -use valence_layer::{EntityLayer, Layer}; +use valence_layer::{EntityLayer, Layer, UpdateLayersPreClientSet}; pub struct BossBarPlugin; @@ -46,7 +48,7 @@ impl Plugin for BossBarPlugin { update_boss_bar_view, boss_bar_despawn, ) - .before(FlushPacketsSet), + .before(UpdateLayersPreClientSet), ); } } @@ -98,7 +100,7 @@ fn update_boss_bar_view( client.write_packet(&BossBarS2c { id: id.0, action: BossBarAction::Add { - title: title.0.to_owned(), + title: Cow::Borrowed(&title.0), health: health.0, color: style.color, division: style.division, @@ -123,7 +125,7 @@ fn update_boss_bar_view( } fn boss_bar_despawn( - boss_bars_query: Query<(&UniqueId, &EntityLayerId), Added>, + boss_bars_query: Query<(&UniqueId, &EntityLayerId), With>, mut clients_query: Query<(&mut Client, &VisibleEntityLayers)>, ) { for (id, entity_layer_id) in boss_bars_query.iter() { diff --git a/crates/valence_packet/src/packets/play/boss_bar_s2c.rs b/crates/valence_packet/src/packets/play/boss_bar_s2c.rs index b58c9c558..efc55a502 100644 --- a/crates/valence_packet/src/packets/play/boss_bar_s2c.rs +++ b/crates/valence_packet/src/packets/play/boss_bar_s2c.rs @@ -4,15 +4,15 @@ use super::*; #[derive(Clone, Debug, Encode, Decode, Packet)] #[packet(id = packet_id::BOSS_BAR_S2C)] -pub struct BossBarS2c { +pub struct BossBarS2c<'a> { pub id: Uuid, - pub action: BossBarAction, + pub action: BossBarAction<'a>, } #[derive(Clone, PartialEq, Debug, Encode, Decode)] -pub enum BossBarAction { +pub enum BossBarAction<'a> { Add { - title: Text, + title: Cow<'a, Text>, health: f32, color: BossBarColor, division: BossBarDivision, @@ -20,7 +20,7 @@ pub enum BossBarAction { }, Remove, UpdateHealth(f32), - UpdateTitle(Text), + UpdateTitle(Cow<'a, Text>), UpdateStyle(BossBarColor, BossBarDivision), UpdateFlags(BossBarFlags), } From c3c276053514377e5c0bb3da6e402ed5d29fb6d1 Mon Sep 17 00:00:00 2001 From: Bafbi Date: Fri, 4 Aug 2023 18:59:39 +0200 Subject: [PATCH 3/4] Handle BossBar link to a `Position` When a bossbar as a `Position` componant, the bossbar will only be shown when in view. --- crates/valence_boss_bar/src/components.rs | 2 +- crates/valence_boss_bar/src/lib.rs | 191 ++++++++++++++++++---- examples/boss_bar.rs | 52 ++++-- src/tests/boss_bar.rs | 4 +- 4 files changed, 199 insertions(+), 50 deletions(-) diff --git a/crates/valence_boss_bar/src/components.rs b/crates/valence_boss_bar/src/components.rs index 2ffb6fb49..eedb59f9f 100644 --- a/crates/valence_boss_bar/src/components.rs +++ b/crates/valence_boss_bar/src/components.rs @@ -17,7 +17,7 @@ pub struct BossBarBundle { pub health: BossBarHealth, pub style: BossBarStyle, pub flags: BossBarFlags, - pub entity_layer_id: EntityLayerId, + pub layer: EntityLayerId, } /// The title of a boss bar. diff --git a/crates/valence_boss_bar/src/lib.rs b/crates/valence_boss_bar/src/lib.rs index 1ec889b9d..bc1f1ea4a 100644 --- a/crates/valence_boss_bar/src/lib.rs +++ b/crates/valence_boss_bar/src/lib.rs @@ -22,7 +22,10 @@ use std::borrow::Cow; use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use valence_client::{Client, OldVisibleEntityLayers, VisibleEntityLayers}; +use valence_client::{ + Client, OldViewDistance, OldVisibleEntityLayers, ViewDistance, VisibleEntityLayers, +}; +use valence_core::chunk_pos::{ChunkPos, ChunkView}; use valence_core::despawn::Despawned; use valence_core::uuid::UniqueId; use valence_packet::packets::play::boss_bar_s2c::{BossBarAction, ToPacketAction}; @@ -31,7 +34,7 @@ use valence_packet::protocol::encode::WritePacket; mod components; pub use components::*; -use valence_entity::{EntityLayerId, Position}; +use valence_entity::{EntityLayerId, OldPosition, Position}; use valence_layer::{EntityLayer, Layer, UpdateLayersPreClientSet}; pub struct BossBarPlugin; @@ -45,7 +48,8 @@ impl Plugin for BossBarPlugin { update_boss_bar::, update_boss_bar::, update_boss_bar::, - update_boss_bar_view, + update_boss_bar_layer_view, + update_boss_bar_chunk_view, boss_bar_despawn, ) .before(UpdateLayersPreClientSet), @@ -74,9 +78,17 @@ fn update_boss_bar( } } -fn update_boss_bar_view( +fn update_boss_bar_layer_view( mut clients_query: Query< - (&mut Client, &VisibleEntityLayers, &OldVisibleEntityLayers), + ( + &mut Client, + &VisibleEntityLayers, + &OldVisibleEntityLayers, + &Position, + &OldPosition, + &ViewDistance, + &OldViewDistance, + ), Changed, >, boss_bars_query: Query<( @@ -86,55 +98,166 @@ fn update_boss_bar_view( &BossBarStyle, &BossBarFlags, &EntityLayerId, + Option<&Position>, )>, ) { - for (mut client, visible_entity_layers, old_visible_entity_layers) in clients_query.iter_mut() { + for ( + mut client, + visible_entity_layers, + old_visible_entity_layers, + position, + _old_position, + view_distance, + _old_view_distance, + ) in clients_query.iter_mut() + { + let view = ChunkView::new(ChunkPos::from_pos(position.0), view_distance.get()); + let old_layers = old_visible_entity_layers.get(); let current_layers = &visible_entity_layers.0; for &added_layer in current_layers.difference(old_layers) { - if let Some((id, title, health, style, flags, _)) = boss_bars_query + for (id, title, health, style, flags, _, boss_bar_position) in boss_bars_query .iter() - .find(|(_, _, _, _, _, layer_id)| layer_id.0 == added_layer) + .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == added_layer) { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::Add { - title: Cow::Borrowed(&title.0), - health: health.0, - color: style.color, - division: style.division, - flags: *flags, - }, - }); - }; + if let Some(position) = boss_bar_position { + if view.contains(position.to_chunk_pos()) { + client.write_packet(&BossBarS2c { + id: id.0, + action: BossBarAction::Add { + title: Cow::Borrowed(&title.0), + health: health.0, + color: style.color, + division: style.division, + flags: *flags, + }, + }); + } + } else { + client.write_packet(&BossBarS2c { + id: id.0, + action: BossBarAction::Add { + title: Cow::Borrowed(&title.0), + health: health.0, + color: style.color, + division: style.division, + flags: *flags, + }, + }); + } + } } for &removed_layer in old_layers.difference(current_layers) { - if let Some((id, _, _, _, _, _)) = boss_bars_query + for (id, _, _, _, _, _, boss_bar_position) in boss_bars_query .iter() - .find(|(_, _, _, _, _, layer_id)| layer_id.0 == removed_layer) + .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == removed_layer) { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::Remove, - }); + if let Some(position) = boss_bar_position { + if view.contains(position.to_chunk_pos()) { + client.write_packet(&BossBarS2c { + id: id.0, + action: BossBarAction::Remove, + }); + } + } else { + client.write_packet(&BossBarS2c { + id: id.0, + action: BossBarAction::Remove, + }); + } + } + } + } +} + +fn update_boss_bar_chunk_view( + mut clients_query: Query< + ( + &mut Client, + &VisibleEntityLayers, + &OldVisibleEntityLayers, + &Position, + &OldPosition, + &ViewDistance, + &OldViewDistance, + ), + Changed, + >, + boss_bars_query: Query<( + &UniqueId, + &BossBarTitle, + &BossBarHealth, + &BossBarStyle, + &BossBarFlags, + &EntityLayerId, + &Position, + )>, +) { + for ( + mut client, + visible_entity_layers, + _old_visible_entity_layers, + position, + old_position, + view_distance, + old_view_distance, + ) in clients_query.iter_mut() + { + let view = ChunkView::new(ChunkPos::from_pos(position.0), view_distance.get()); + let old_view = ChunkView::new( + ChunkPos::from_pos(old_position.get()), + old_view_distance.get(), + ); + + for layer in visible_entity_layers.0.iter() { + for (id, title, health, style, flags, _, boss_bar_position) in boss_bars_query + .iter() + .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == *layer) + { + if view.contains(boss_bar_position.to_chunk_pos()) + && !old_view.contains(boss_bar_position.to_chunk_pos()) + { + client.write_packet(&BossBarS2c { + id: id.0, + action: BossBarAction::Add { + title: Cow::Borrowed(&title.0), + health: health.0, + color: style.color, + division: style.division, + flags: *flags, + }, + }); + } else if !view.contains(boss_bar_position.to_chunk_pos()) + && old_view.contains(boss_bar_position.to_chunk_pos()) + { + client.write_packet(&BossBarS2c { + id: id.0, + action: BossBarAction::Remove, + }); + } } } } } fn boss_bar_despawn( - boss_bars_query: Query<(&UniqueId, &EntityLayerId), With>, - mut clients_query: Query<(&mut Client, &VisibleEntityLayers)>, + boss_bars_query: Query<(&UniqueId, &EntityLayerId, Option<&Position>), With>, + mut entity_layer_query: Query<&mut EntityLayer>, ) { - for (id, entity_layer_id) in boss_bars_query.iter() { - for (mut client, visible_layer_id) in clients_query.iter_mut() { - if visible_layer_id.0.contains(&entity_layer_id.0) { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::Remove, - }); + for (id, entity_layer_id, position) in boss_bars_query.iter() { + if let Ok(mut entity_layer) = entity_layer_query.get_mut(entity_layer_id.0) { + let packet = BossBarS2c { + id: id.0, + action: BossBarAction::Remove, + }; + if let Some(pos) = position { + entity_layer + .view_writer(pos.to_chunk_pos()) + .write_packet(&packet); + } else { + entity_layer.write_packet(&packet); } } } diff --git a/examples/boss_bar.rs b/examples/boss_bar.rs index 8f8ca343c..c9a888e8d 100644 --- a/examples/boss_bar.rs +++ b/examples/boss_bar.rs @@ -4,10 +4,15 @@ use rand::seq::SliceRandom; use valence::prelude::*; use valence_boss_bar::{BossBarBundle, BossBarFlags, BossBarHealth, BossBarStyle, BossBarTitle}; use valence_client::message::{ChatMessageEvent, SendMessage}; +use valence_core::text::color::NamedColor; +use valence_entity::cow::CowEntityBundle; use valence_packet::packets::play::boss_bar_s2c::{BossBarColor, BossBarDivision}; const SPAWN_Y: i32 = 64; +#[derive(Component)] +struct CustomBossBar; + pub fn main() { App::new() .add_plugins(DefaultPlugins) @@ -43,12 +48,30 @@ fn setup( let layer_id = commands.spawn(layer).id(); - commands.spawn((BossBarBundle { - title: BossBarTitle("Boss Bar".into_text()), - health: BossBarHealth(0.5), - entity_layer_id: EntityLayerId(layer_id), - ..Default::default() - },)); + commands.spawn(( + BossBarBundle { + title: BossBarTitle("Boss Bar".into_text()), + health: BossBarHealth(0.5), + layer: EntityLayerId(layer_id), + ..Default::default() + }, + CustomBossBar, + )); + + commands.spawn(( + CowEntityBundle { + position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]), + layer: EntityLayerId(layer_id), + ..Default::default() + }, + BossBarTitle("BigCow".color(NamedColor::Red)), + BossBarHealth(0.5), + BossBarStyle { + color: BossBarColor::Red, + division: BossBarDivision::default(), + }, + BossBarFlags::default(), + )); } fn init_clients( @@ -113,13 +136,16 @@ fn init_clients( fn listen_messages( mut message_events: EventReader, - mut boss_bars_query: Query<( - &mut BossBarStyle, - &mut BossBarFlags, - &mut BossBarHealth, - &mut BossBarTitle, - &EntityLayerId, - )>, + mut boss_bars_query: Query< + ( + &mut BossBarStyle, + &mut BossBarFlags, + &mut BossBarHealth, + &mut BossBarTitle, + &EntityLayerId, + ), + With, + >, mut clients_query: Query<&mut VisibleEntityLayers, With>, ) { let ( diff --git a/src/tests/boss_bar.rs b/src/tests/boss_bar.rs index b79ff1c16..dabb9c8d8 100644 --- a/src/tests/boss_bar.rs +++ b/src/tests/boss_bar.rs @@ -22,7 +22,7 @@ fn test_intialize_on_join() { .insert(BossBarBundle { title: BossBarTitle("Boss Bar".into_text()), health: BossBarHealth(0.5), - entity_layer_id: EntityLayerId(scenario.layer), + layer: EntityLayerId(scenario.layer), ..Default::default() }); @@ -182,7 +182,7 @@ fn prepare() -> ScenarioSingleClient { s.app.world.entity_mut(s.layer).insert(BossBarBundle { title: BossBarTitle("Boss Bar".into_text()), health: BossBarHealth(0.5), - entity_layer_id: EntityLayerId(s.layer), + layer: EntityLayerId(s.layer), ..Default::default() }); From c87ad8d2ddee28af1012ac6332fce02dd7670b3a Mon Sep 17 00:00:00 2001 From: Bafbi Date: Thu, 10 Aug 2023 21:31:17 +0200 Subject: [PATCH 4/4] Reverse unrelated fix + nit --- crates/valence_inventory/src/lib.rs | 4 ++-- .../src/packets/play/update_selected_slot_c2s.rs | 2 +- examples/boss_bar.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/valence_inventory/src/lib.rs b/crates/valence_inventory/src/lib.rs index a0d12c6f1..35edb227a 100644 --- a/crates/valence_inventory/src/lib.rs +++ b/crates/valence_inventory/src/lib.rs @@ -1235,11 +1235,11 @@ fn handle_update_selected_slot( // The client is trying to interact with a slot that does not exist, ignore. continue; } - held.held_item_slot = convert_hotbar_slot_id(pkt.slot); + held.held_item_slot = convert_hotbar_slot_id(pkt.slot as u16); events.send(UpdateSelectedSlotEvent { client: packet.client, - slot: pkt.slot as u8, + slot: pkt.slot, }); } } diff --git a/crates/valence_protocol/src/packets/play/update_selected_slot_c2s.rs b/crates/valence_protocol/src/packets/play/update_selected_slot_c2s.rs index 855b01bc0..445238593 100644 --- a/crates/valence_protocol/src/packets/play/update_selected_slot_c2s.rs +++ b/crates/valence_protocol/src/packets/play/update_selected_slot_c2s.rs @@ -3,5 +3,5 @@ use super::*; #[derive(Copy, Clone, Debug, Encode, Decode, Packet)] #[packet(id = packet_id::UPDATE_SELECTED_SLOT_C2S)] pub struct UpdateSelectedSlotC2s { - pub slot: u16, + pub slot: u8, } diff --git a/examples/boss_bar.rs b/examples/boss_bar.rs index ce872af0f..a8aba6968 100644 --- a/examples/boss_bar.rs +++ b/examples/boss_bar.rs @@ -66,7 +66,7 @@ fn setup( layer: EntityLayerId(layer_id), ..Default::default() }, - BossBarTitle("BigCow".color(NamedColor::Red)), + BossBarTitle("Louis XVI".color(NamedColor::Red)), BossBarHealth(0.5), BossBarStyle { color: BossBarColor::Red,