use std::io::{self, Cursor, Write}; use azalea_buf::{AzBuf, AzBufVar, BufReadError}; use azalea_core::bitset::FixedBitSet; use azalea_protocol_macros::ClientboundGamePacket; use azalea_registry::identifier::Identifier; use tracing::warn; #[derive(AzBuf, ClientboundGamePacket, Clone, Debug, PartialEq)] pub struct ClientboundCommands { pub entries: Vec, #[var] pub root_index: u32, } #[derive(Clone, Debug, PartialEq)] pub struct BrigadierNodeStub { pub is_executable: bool, pub children: Vec, pub redirect_node: Option, pub node_type: NodeType, pub is_restricted: bool, } #[derive(Clone, Debug, Eq)] pub struct BrigadierNumber { pub min: Option, pub max: Option, } impl BrigadierNumber { pub fn new(min: Option, max: Option) -> BrigadierNumber { BrigadierNumber { min, max } } } impl PartialEq for BrigadierNumber { fn eq(&self, other: &Self) -> bool { match (&self.min, &self.max, &other.min, &other.max) { (Some(f_min), None, Some(s_min), None) => f_min == s_min, (None, Some(f_max), None, Some(s_max)) => f_max == s_max, (Some(f_min), Some(f_max), Some(s_min), Some(s_max)) => { f_min == s_min && f_max == s_max } (None, None, None, None) => true, _ => false, } } } impl AzBuf for BrigadierNumber { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { let flags = FixedBitSet::<2>::azalea_read(buf)?; let min = if flags.index(0) { Some(T::azalea_read(buf)?) } else { None }; let max = if flags.index(1) { Some(T::azalea_read(buf)?) } else { None }; Ok(BrigadierNumber { min, max }) } fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { let mut flags = FixedBitSet::<2>::new(); if self.min.is_some() { flags.set(0); } if self.max.is_some() { flags.set(1); } flags.azalea_write(buf)?; if let Some(min) = &self.min { min.azalea_write(buf)?; } if let Some(max) = &self.max { max.azalea_write(buf)?; } Ok(()) } } #[derive(AzBuf, Clone, Copy, Debug, Eq, PartialEq)] pub enum BrigadierString { /// Reads a single word SingleWord = 0, // If it starts with a ", keeps reading until another " (allowing escaping with \). Otherwise // behaves the same as SINGLE_WORD QuotablePhrase = 1, // Reads the rest of the content after the cursor. Quotes will not be removed. GreedyPhrase = 2, } // see ArgumentTypeInfos.java #[derive(AzBuf, Clone, Debug, PartialEq)] pub enum BrigadierParser { Bool, Float(BrigadierNumber), Double(BrigadierNumber), Integer(BrigadierNumber), Long(BrigadierNumber), String(BrigadierString), Entity(EntityParser), GameProfile, BlockPos, ColumnPos, Vec3, Vec2, BlockState, BlockPredicate, ItemStack, ItemPredicate, Color, HexColor, FormattedText, Style, Message, NbtCompoundTag, NbtTag, NbtPath, Objective, ObjectiveCriteria, Operation, Particle, Angle, Rotation, ScoreboardSlot, ScoreHolder { allows_multiple: bool }, Swizzle, Team, ItemSlot, ItemSlots, Identifier, Function, EntityAnchor, IntRange, FloatRange, Dimension, GameMode, Time { min: i32 }, ResourceOrTag { registry_key: Identifier }, ResourceOrTagKey { registry_key: Identifier }, Resource { registry_key: Identifier }, ResourceKey { registry_key: Identifier }, ResourceSelector { registry_key: Identifier }, TemplateMirror, TemplateRotation, Heightmap, LootTable, LootPredicate, LootModifier, Dialog, Uuid, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct EntityParser { pub single: bool, pub players_only: bool, } impl AzBuf for EntityParser { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { let flags = FixedBitSet::<2>::azalea_read(buf)?; Ok(EntityParser { single: flags.index(0), players_only: flags.index(1), }) } fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { let mut flags = FixedBitSet::<2>::new(); if self.single { flags.set(0); } if self.players_only { flags.set(1); } flags.azalea_write(buf)?; Ok(()) } } // TODO: BrigadierNodeStub should have more stuff impl AzBuf for BrigadierNodeStub { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { let flags = FixedBitSet::<8>::azalea_read(buf)?; if flags.index(6) || flags.index(7) { warn!( "The flags from a Brigadier node are over 63. This is a bug, BrigadierParser probably needs updating.", ); } let node_type = u8::from(flags.index(0)) + (u8::from(flags.index(1)) * 2); let is_executable = flags.index(2); let has_redirect = flags.index(3); let has_suggestions_type = flags.index(4); let is_restricted = flags.index(5); let children = Vec::::azalea_read_var(buf)?; let redirect_node = if has_redirect { Some(u32::azalea_read_var(buf)?) } else { None }; // argument node if node_type == 2 { let name = String::azalea_read(buf)?; let parser = BrigadierParser::azalea_read(buf)?; let suggestions_type = if has_suggestions_type { Some(Identifier::azalea_read(buf)?) } else { None }; Ok(BrigadierNodeStub { is_executable, children, redirect_node, node_type: NodeType::Argument { name, parser, suggestions_type, }, is_restricted, }) } // literal node else if node_type == 1 { let name = String::azalea_read(buf)?; Ok(BrigadierNodeStub { is_executable, children, redirect_node, node_type: NodeType::Literal { name }, is_restricted, }) } else { Ok(BrigadierNodeStub { is_executable, children, redirect_node, node_type: NodeType::Root, is_restricted, }) } } fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { let mut flags = FixedBitSet::<4>::new(); if self.is_executable { flags.set(2); } if self.redirect_node.is_some() { flags.set(3); } match &self.node_type { NodeType::Root => { flags.azalea_write(buf)?; self.children.azalea_write_var(buf)?; if let Some(redirect) = self.redirect_node { redirect.azalea_write_var(buf)?; } } NodeType::Literal { name } => { flags.set(0); flags.azalea_write(buf)?; self.children.azalea_write_var(buf)?; if let Some(redirect) = self.redirect_node { redirect.azalea_write_var(buf)?; } name.azalea_write(buf)?; } NodeType::Argument { name, parser, suggestions_type, } => { flags.set(1); if suggestions_type.is_some() { flags.set(4); } flags.azalea_write(buf)?; self.children.azalea_write_var(buf)?; if let Some(redirect) = self.redirect_node { redirect.azalea_write_var(buf)?; } name.azalea_write(buf)?; parser.azalea_write(buf)?; if let Some(suggestion) = suggestions_type { suggestion.azalea_write(buf)?; } } } Ok(()) } } #[derive(Clone, Debug, PartialEq)] pub enum NodeType { Root, Literal { name: String, }, Argument { name: String, parser: BrigadierParser, suggestions_type: Option, }, } impl BrigadierNodeStub { #[must_use] pub fn name(&self) -> Option<&str> { match &self.node_type { NodeType::Root => None, NodeType::Literal { name } | NodeType::Argument { name, .. } => Some(name), } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_brigadier_node_stub_root() { let data = BrigadierNodeStub { is_executable: false, children: vec![1, 2], redirect_node: None, node_type: NodeType::Root, is_restricted: false, }; let mut buf = Vec::new(); data.azalea_write(&mut buf).unwrap(); let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf); let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap(); assert_eq!(data, read_data); } #[test] fn test_brigadier_node_stub_literal() { let data = BrigadierNodeStub { is_executable: true, children: vec![], redirect_node: None, node_type: NodeType::Literal { name: "String".to_owned(), }, is_restricted: false, }; let mut buf = Vec::new(); data.azalea_write(&mut buf).unwrap(); let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf); let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap(); assert_eq!(data, read_data); } #[test] fn test_brigadier_node_stub_argument() { let data = BrigadierNodeStub { is_executable: false, children: vec![6, 9], redirect_node: Some(5), node_type: NodeType::Argument { name: "position".to_owned(), parser: BrigadierParser::Vec3, suggestions_type: Some(Identifier::new("minecraft:test_suggestion")), }, is_restricted: false, }; let mut buf = Vec::new(); data.azalea_write(&mut buf).unwrap(); let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf); let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap(); assert_eq!(data, read_data); } }