mirror of
https://github.com/italicsjenga/valence.git
synced 2025-01-10 23:01:31 +11:00
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:
parent
8d9c0a7553
commit
7574fa33c5
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
432
crates/valence_protocol/src/packets/s2c/commands.rs
Normal file
432
crates/valence_protocol/src/packets/s2c/commands.rs
Normal 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}"),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue