Commands and recipe book packets (#183)

Implement the commands and recipe book packets. Also reorganizes some
modules in valence_protocol.
This commit is contained in:
Ryan Johnson 2022-12-31 22:59:22 -08:00 committed by GitHub
parent 8d9c0a7553
commit 7574fa33c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 563 additions and 10 deletions

View file

@ -13,6 +13,7 @@ use rayon::iter::ParallelIterator;
use tokio::sync::OwnedSemaphorePermit;
use tracing::{info, warn};
use uuid::Uuid;
use valence_protocol::packets::s2c::particle::{Particle, ParticleS2c};
use valence_protocol::packets::s2c::play::{
AcknowledgeBlockChange, ClearTitles, CloseContainerS2c, CombatDeath, DisconnectPlay,
EntityAnimationS2c, EntityEvent, GameEvent, KeepAliveS2c, LoginPlayOwned, OpenScreen,
@ -22,7 +23,6 @@ use valence_protocol::packets::s2c::play::{
SetSubtitleText, SetTitleAnimationTimes, SetTitleText, SynchronizePlayerPosition,
SystemChatMessage, UnloadChunk, UpdateAttributes, UpdateTime,
};
use valence_protocol::particle::{Particle, ParticleS2c};
use valence_protocol::types::{
AttributeProperty, DisplayedSkinParts, GameEventKind, GameMode, SyncPlayerPosLookFlags,
};

View file

@ -135,8 +135,8 @@ pub mod prelude {
pub use valence_protocol::block::{PropName, PropValue};
pub use valence_protocol::entity_meta::Pose;
pub use valence_protocol::ident::IdentError;
pub use valence_protocol::packets::s2c::particle::Particle;
pub use valence_protocol::packets::s2c::play::SetTitleAnimationTimes;
pub use valence_protocol::particle::Particle;
pub use valence_protocol::text::Color;
pub use valence_protocol::types::{GameMode, Hand, SoundCategory};
pub use valence_protocol::{

View file

@ -6,7 +6,9 @@ use std::ops::{Deref, DerefMut, Index, IndexMut};
use uuid::Uuid;
use valence_protocol::packets::s2c::play::{PlayerInfoRemove, SetTabListHeaderAndFooter};
use valence_protocol::player_list::{Actions, Entry as PacketEntry, PlayerInfoUpdate};
use valence_protocol::packets::s2c::player_info_update::{
Actions, Entry as PacketEntry, PlayerInfoUpdate,
};
use valence_protocol::types::{GameMode, SignedProperty};
use valence_protocol::Text;

View file

@ -109,10 +109,7 @@ mod impls;
mod inventory;
mod item;
pub mod packets;
pub mod particle;
pub mod player_list;
mod raw_bytes;
pub mod recipe;
pub mod text;
pub mod translation_key;
pub mod types;

View file

@ -7,7 +7,6 @@ use crate::byte_angle::ByteAngle;
use crate::ident::Ident;
use crate::item::ItemStack;
use crate::raw_bytes::RawBytes;
use crate::recipe::DeclaredRecipe;
use crate::text::Text;
use crate::types::{
AttributeProperty, BossBarAction, ChunkDataBlockEntity, Difficulty, GameEventKind, GameMode,
@ -19,6 +18,12 @@ use crate::var_int::VarInt;
use crate::var_long::VarLong;
use crate::LengthPrefixedArray;
pub mod commands;
pub mod declare_recipes;
pub mod particle;
pub mod player_info_update;
pub mod update_recipe_book;
pub mod status {
use super::*;
@ -95,9 +100,13 @@ pub mod login {
}
pub mod play {
use commands::Node;
pub use particle::ParticleS2c;
pub use player_info_update::PlayerInfoUpdate;
pub use update_recipe_book::UpdateRecipeBook;
use super::*;
pub use crate::particle::ParticleS2c;
pub use crate::player_list::PlayerInfoUpdate;
use crate::packets::s2c::declare_recipes::DeclaredRecipe;
#[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x00]
@ -189,6 +198,13 @@ pub mod play {
pub reset: bool,
}
#[derive(Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x0e]
pub struct Commands<'a> {
pub commands: Vec<Node<'a>>,
pub root_index: VarInt,
}
#[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x0f]
pub struct CloseContainerS2c {
@ -527,6 +543,14 @@ pub mod play {
pub blocks: &'a [VarLong],
}
#[derive(Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x41]
pub struct ServerData<'a> {
pub motd: Option<Text>,
pub icon: Option<&'a str>,
pub enforce_secure_chat: bool,
}
#[derive(Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
#[packet_id = 0x42]
pub struct SetActionBarText(pub Text);
@ -698,6 +722,7 @@ pub mod play {
BossBar,
SetDifficulty,
ClearTitles,
Commands<'a>,
CloseContainerS2c,
SetContainerContent,
SetContainerProperty,
@ -723,11 +748,13 @@ pub mod play {
PlayerInfoRemove,
PlayerInfoUpdate<'a>,
SynchronizePlayerPosition,
UpdateRecipeBook<'a>,
RemoveEntities,
ResourcePackS2c<'a>,
Respawn<'a>,
SetHeadRotation,
UpdateSectionBlocks,
ServerData<'a>,
SetActionBarText,
SetHeldItemS2c,
SetCenterChunk,

View file

@ -0,0 +1,432 @@
use std::io::Write;
use anyhow::bail;
use byteorder::WriteBytesExt;
use crate::{Decode, Encode, Ident, VarInt};
#[derive(Clone, Debug)]
pub struct Node<'a> {
pub children: Vec<VarInt>,
pub data: NodeData<'a>,
pub executable: bool,
pub redirect_node: Option<VarInt>,
}
#[derive(Clone, Debug)]
pub enum NodeData<'a> {
Root,
Literal {
name: &'a str,
},
Argument {
name: &'a str,
parser: Parser<'a>,
suggestion: Option<Suggestion>,
},
}
#[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<f32>, max: Option<f32> },
Double { min: Option<f64>, max: Option<f64> },
Integer { min: Option<i32>, max: Option<i32> },
Long { min: Option<i64>, max: Option<i64> },
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<&'a str> },
ResourceOrTagKey { registry: Ident<&'a str> },
Resource { registry: Ident<&'a str> },
ResourceKey { registry: Ident<&'a str> },
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<Self> {
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::<&str>::decode(r)?.path() {
"ask_server" => Suggestion::AskServer,
"all_recipes" => Suggestion::AllRecipes,
"available_sounds" => Suggestion::AvailableSounds,
"available_biomes" => Suggestion::AvailableBiomes,
"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<Self> {
fn decode_min_max<'a, T: Decode<'a>>(
r: &mut &'a [u8],
) -> anyhow::Result<(Option<T>, Option<T>)> {
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}"),
})
}
}

View file

@ -0,0 +1,90 @@
use std::io::Write;
use anyhow::bail;
use crate::{Decode, DecodePacket, Encode, EncodePacket, Ident, VarInt};
#[derive(Clone, PartialEq, Eq, Debug, EncodePacket, DecodePacket)]
#[packet_id = 0x39]
pub struct UpdateRecipeBook<'a> {
pub action: UpdateRecipeBookAction<'a>,
pub crafting_recipe_book_open: bool,
pub crafting_recipe_book_filter_active: bool,
pub smelting_recipe_book_open: bool,
pub smelting_recipe_book_filter_active: bool,
pub blast_furnace_recipe_book_open: bool,
pub blast_furnace_recipe_book_filter_active: bool,
pub smoker_recipe_book_open: bool,
pub smoker_recipe_book_filter_active: bool,
pub recipe_ids: Vec<Ident<&'a str>>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum UpdateRecipeBookAction<'a> {
Init { recipe_ids: Vec<Ident<&'a str>> },
Add,
Remove,
}
impl Encode for UpdateRecipeBook<'_> {
fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
VarInt(match &self.action {
UpdateRecipeBookAction::Init { .. } => 0,
UpdateRecipeBookAction::Add => 1,
UpdateRecipeBookAction::Remove => 2,
})
.encode(&mut w)?;
self.crafting_recipe_book_open.encode(&mut w)?;
self.crafting_recipe_book_filter_active.encode(&mut w)?;
self.smelting_recipe_book_open.encode(&mut w)?;
self.smelting_recipe_book_filter_active.encode(&mut w)?;
self.blast_furnace_recipe_book_open.encode(&mut w)?;
self.blast_furnace_recipe_book_filter_active
.encode(&mut w)?;
self.smoker_recipe_book_open.encode(&mut w)?;
self.smoker_recipe_book_filter_active.encode(&mut w)?;
self.recipe_ids.encode(&mut w)?;
if let UpdateRecipeBookAction::Init { recipe_ids } = &self.action {
recipe_ids.encode(&mut w)?;
}
Ok(())
}
}
impl<'a> Decode<'a> for UpdateRecipeBook<'a> {
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
let action_id = VarInt::decode(r)?.0;
let crafting_recipe_book_open = bool::decode(r)?;
let crafting_recipe_book_filter_active = bool::decode(r)?;
let smelting_recipe_book_open = bool::decode(r)?;
let smelting_recipe_book_filter_active = bool::decode(r)?;
let blast_furnace_recipe_book_open = bool::decode(r)?;
let blast_furnace_recipe_book_filter_active = bool::decode(r)?;
let smoker_recipe_book_open = bool::decode(r)?;
let smoker_recipe_book_filter_active = bool::decode(r)?;
let recipe_ids = Vec::decode(r)?;
Ok(Self {
action: match action_id {
0 => UpdateRecipeBookAction::Init {
recipe_ids: Vec::decode(r)?,
},
1 => UpdateRecipeBookAction::Add,
2 => UpdateRecipeBookAction::Remove,
n => bail!("unknown recipe book action of {n}"),
},
crafting_recipe_book_open,
crafting_recipe_book_filter_active,
smelting_recipe_book_open,
smelting_recipe_book_filter_active,
blast_furnace_recipe_book_open,
blast_furnace_recipe_book_filter_active,
smoker_recipe_book_open,
smoker_recipe_book_filter_active,
recipe_ids,
})
}
}

View file

@ -4,6 +4,7 @@ use std::borrow::Cow;
use std::io::Write;
use std::{fmt, ops};
use anyhow::Context;
use serde::de::Visitor;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
@ -740,7 +741,11 @@ impl Encode for Text {
impl Decode<'_> for Text {
fn decode(r: &mut &[u8]) -> Result<Self> {
let string = <&str>::decode(r)?;
Ok(serde_json::from_str(string)?)
if string.is_empty() {
Ok(Self::default())
} else {
serde_json::from_str(string).context("decoding text JSON")
}
}
}