diff --git a/src/client.rs b/src/client.rs index 2411f1b..d950728 100644 --- a/src/client.rs +++ b/src/client.rs @@ -19,16 +19,17 @@ use crate::player_textures::SignedPlayerTextures; use crate::protocol::packets::play::c2s::{ C2sPlayPacket, DiggingStatus, InteractKind, PlayerCommandId, }; +pub use crate::protocol::packets::play::s2c::SetTitleAnimationTimes as TitleAnimationTimes; use crate::protocol::packets::play::s2c::{ Animate, Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic, BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, BlockChangeAck, ChatType, ChatTypeChat, ChatTypeNarration, ChatTypeRegistry, ChatTypeRegistryEntry, - DimensionType, DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, EntityEvent, - ForgetLevelChunk, GameEvent, GameEventReason, KeepAlive, Login, MoveEntityPosition, - MoveEntityPositionAndRotation, MoveEntityRotation, PlayerPosition, PlayerPositionFlags, - RegistryCodec, RemoveEntities, Respawn, RotateHead, S2cPlayPacket, SetChunkCacheCenter, - SetChunkCacheRadius, SetEntityMetadata, SetEntityMotion, SpawnPosition, SystemChat, - TeleportEntity, ENTITY_EVENT_MAX_BOUND, + ClearTitles, DimensionType, DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, + EntityEvent, ForgetLevelChunk, GameEvent, GameEventReason, KeepAlive, Login, + MoveEntityPosition, MoveEntityPositionAndRotation, MoveEntityRotation, PlayerPosition, + PlayerPositionFlags, RegistryCodec, RemoveEntities, Respawn, RotateHead, S2cPlayPacket, + SetChunkCacheCenter, SetChunkCacheRadius, SetEntityMetadata, SetEntityMotion, SetSubtitleText, + SetTitleText, SpawnPosition, SystemChat, TeleportEntity, ENTITY_EVENT_MAX_BOUND, }; use crate::protocol::{BoundedInt, ByteAngle, Nbt, RawBytes, VarInt}; use crate::server::C2sPacketChannels; @@ -310,6 +311,32 @@ impl Client { self.new_game_mode = new_game_mode; } + pub fn set_title( + &mut self, + title: impl Into, + subtitle: impl Into, + animation: impl Into>, + ) { + let title = title.into(); + let subtitle = subtitle.into(); + + self.send_packet(SetTitleText { text: title }); + + if !subtitle.is_empty() { + self.send_packet(SetSubtitleText { + subtitle_text: subtitle, + }); + } + + if let Some(anim) = animation.into() { + self.send_packet(anim); + } + } + + pub fn clear_title(&mut self) { + self.send_packet(ClearTitles { reset: true }); + } + pub fn on_ground(&self) -> bool { self.flags.on_ground() } diff --git a/src/protocol/packets.rs b/src/protocol/packets.rs index 1fad2e7..742293d 100644 --- a/src/protocol/packets.rs +++ b/src/protocol/packets.rs @@ -724,6 +724,12 @@ pub mod play { } } + def_struct! { + ClearTitles 0x0d { + reset: bool, + } + } + def_struct! { Disconnect 0x17 { reason: Text, @@ -1168,6 +1174,12 @@ pub mod play { } } + def_struct! { + SetSubtitleText 0x58 { + subtitle_text: Text, + } + } + def_struct! { SetTime 0x59 { /// The age of the world in 1/20ths of a second. @@ -1179,6 +1191,24 @@ pub mod play { } } + def_struct! { + SetTitleText 0x5a { + text: Text, + } + } + + def_struct! { + #[derive(Copy, PartialEq, Eq)] + SetTitleAnimationTimes 0x5b { + /// Ticks to spend fading in. + fade_in: u32, + /// Ticks to keep the title displayed. + stay: u32, + /// Ticks to spend fading out. + fade_out: u32, + } + } + def_struct! { SystemChat 0x5f { chat: Text, @@ -1216,6 +1246,7 @@ pub mod play { BlockEvent, BlockUpdate, BossEvent, + ClearTitles, Disconnect, EntityEvent, ForgetLevelChunk, @@ -1239,7 +1270,10 @@ pub mod play { SpawnPosition, SetEntityMetadata, SetEntityMotion, + SetSubtitleText, SetTime, + SetTitleText, + SetTitleAnimationTimes, SystemChat, TabList, TeleportEntity, diff --git a/src/text.rs b/src/text.rs index d6ef017..3ff49b4 100644 --- a/src/text.rs +++ b/src/text.rs @@ -81,6 +81,21 @@ pub struct Text { extra: Vec, } +impl Text { + pub fn is_empty(&self) -> bool { + for extra in &self.extra { + if !extra.is_empty() { + return false; + } + } + + match &self.content { + TextContent::Text { text } => text.is_empty(), + TextContent::Translate { translate } => translate.is_empty(), + } + } +} + /// Provides the methods necessary for working with [`Text`] objects. /// /// This trait exists to allow using `Into` types without having to first @@ -539,4 +554,13 @@ mod tests { assert_eq!(color_from_str("#000000"), Some(Color::BLACK)); assert_eq!(color_from_str("#"), None); } + + #[test] + fn empty() { + assert!("".into_text().is_empty()); + + let txt = "".into_text() + Text::translate("") + ("".italic().color(Color::RED) + ""); + assert!(txt.is_empty()); + assert!(txt.to_plain().is_empty()); + } }