mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41:30 +11:00
Add recipe and tags packets (#182)
Also improve generated `Encode`, `Decode` error messages and fix unnecessary build script reruns.
This commit is contained in:
parent
623e88908d
commit
8d9c0a7553
|
@ -9,7 +9,7 @@ mod entity;
|
||||||
mod entity_event;
|
mod entity_event;
|
||||||
|
|
||||||
pub fn main() -> anyhow::Result<()> {
|
pub fn main() -> anyhow::Result<()> {
|
||||||
println!("cargo:rerun-if-changed=extracted/");
|
println!("cargo:rerun-if-changed=../../extracted/");
|
||||||
|
|
||||||
let generators = [
|
let generators = [
|
||||||
(entity::build as fn() -> _, "entity.rs"),
|
(entity::build as fn() -> _, "entity.rs"),
|
||||||
|
|
|
@ -24,7 +24,7 @@ use valence_protocol::packets::s2c::play::{
|
||||||
};
|
};
|
||||||
use valence_protocol::particle::{Particle, ParticleS2c};
|
use valence_protocol::particle::{Particle, ParticleS2c};
|
||||||
use valence_protocol::types::{
|
use valence_protocol::types::{
|
||||||
AttributeProperty, DisplayedSkinParts, GameMode, GameStateChangeReason, SyncPlayerPosLookFlags,
|
AttributeProperty, DisplayedSkinParts, GameEventKind, GameMode, SyncPlayerPosLookFlags,
|
||||||
};
|
};
|
||||||
use valence_protocol::{
|
use valence_protocol::{
|
||||||
BlockPos, EncodePacket, Ident, ItemStack, RawBytes, Text, Username, VarInt,
|
BlockPos, EncodePacket, Ident, ItemStack, RawBytes, Text, Username, VarInt,
|
||||||
|
@ -531,7 +531,7 @@ impl<C: Config> Client<C> {
|
||||||
|
|
||||||
if !self.created_this_tick() {
|
if !self.created_this_tick() {
|
||||||
self.queue_packet(&GameEvent {
|
self.queue_packet(&GameEvent {
|
||||||
reason: GameStateChangeReason::ChangeGameMode,
|
kind: GameEventKind::ChangeGameMode,
|
||||||
value: game_mode as i32 as f32,
|
value: game_mode as i32 as f32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -541,10 +541,10 @@ impl<C: Config> Client<C> {
|
||||||
/// Sets whether or not the client sees rain.
|
/// Sets whether or not the client sees rain.
|
||||||
pub fn set_raining(&mut self, raining: bool) {
|
pub fn set_raining(&mut self, raining: bool) {
|
||||||
self.queue_packet(&GameEvent {
|
self.queue_packet(&GameEvent {
|
||||||
reason: if raining {
|
kind: if raining {
|
||||||
GameStateChangeReason::BeginRaining
|
GameEventKind::BeginRaining
|
||||||
} else {
|
} else {
|
||||||
GameStateChangeReason::EndRaining
|
GameEventKind::EndRaining
|
||||||
},
|
},
|
||||||
value: 0.0,
|
value: 0.0,
|
||||||
});
|
});
|
||||||
|
@ -556,7 +556,7 @@ impl<C: Config> Client<C> {
|
||||||
/// The rain level is clamped between `0.0.` and `1.0`.
|
/// The rain level is clamped between `0.0.` and `1.0`.
|
||||||
pub fn set_rain_level(&mut self, rain_level: f32) {
|
pub fn set_rain_level(&mut self, rain_level: f32) {
|
||||||
self.queue_packet(&GameEvent {
|
self.queue_packet(&GameEvent {
|
||||||
reason: GameStateChangeReason::RainLevelChange,
|
kind: GameEventKind::RainLevelChange,
|
||||||
value: rain_level.clamp(0.0, 1.0),
|
value: rain_level.clamp(0.0, 1.0),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -571,7 +571,7 @@ impl<C: Config> Client<C> {
|
||||||
/// The thunder level is clamped between `0.0` and `1.0`.
|
/// The thunder level is clamped between `0.0` and `1.0`.
|
||||||
pub fn set_thunder_level(&mut self, thunder_level: f32) {
|
pub fn set_thunder_level(&mut self, thunder_level: f32) {
|
||||||
self.queue_packet(&GameEvent {
|
self.queue_packet(&GameEvent {
|
||||||
reason: GameStateChangeReason::ThunderLevelChange,
|
kind: GameEventKind::ThunderLevelChange,
|
||||||
value: thunder_level.clamp(0.0, 1.0),
|
value: thunder_level.clamp(0.0, 1.0),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -699,7 +699,7 @@ impl<C: Config> Client<C> {
|
||||||
/// Respawns client. Optionally can roll the credits before respawning.
|
/// Respawns client. Optionally can roll the credits before respawning.
|
||||||
pub fn win_game(&mut self, show_credits: bool) {
|
pub fn win_game(&mut self, show_credits: bool) {
|
||||||
self.queue_packet(&GameEvent {
|
self.queue_packet(&GameEvent {
|
||||||
reason: GameStateChangeReason::WinGame,
|
kind: GameEventKind::WinGame,
|
||||||
value: if show_credits { 1.0 } else { 0.0 },
|
value: if show_credits { 1.0 } else { 0.0 },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -715,7 +715,7 @@ impl<C: Config> Client<C> {
|
||||||
|
|
||||||
if !self.created_this_tick() {
|
if !self.created_this_tick() {
|
||||||
self.queue_packet(&GameEvent {
|
self.queue_packet(&GameEvent {
|
||||||
reason: GameStateChangeReason::EnableRespawnScreen,
|
kind: GameEventKind::EnableRespawnScreen,
|
||||||
value: if enable { 0.0 } else { 1.0 },
|
value: if enable { 0.0 } else { 1.0 },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
pub fn derive_decode(item: TokenStream) -> Result<TokenStream> {
|
pub fn derive_decode(item: TokenStream) -> Result<TokenStream> {
|
||||||
let mut input = parse2::<DeriveInput>(item)?;
|
let mut input = parse2::<DeriveInput>(item)?;
|
||||||
|
|
||||||
let name = input.ident;
|
let input_name = input.ident;
|
||||||
|
|
||||||
if input.generics.lifetimes().count() > 1 {
|
if input.generics.lifetimes().count() > 1 {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
|
@ -34,7 +34,7 @@ pub fn derive_decode(item: TokenStream) -> Result<TokenStream> {
|
||||||
Fields::Named(fields) => {
|
Fields::Named(fields) => {
|
||||||
let init = fields.named.iter().map(|f| {
|
let init = fields.named.iter().map(|f| {
|
||||||
let name = f.ident.as_ref().unwrap();
|
let name = f.ident.as_ref().unwrap();
|
||||||
let ctx = format!("failed to decode field `{name}`");
|
let ctx = format!("failed to decode field `{name}` in `{input_name}`");
|
||||||
quote! {
|
quote! {
|
||||||
#name: Decode::decode(_r).context(#ctx)?,
|
#name: Decode::decode(_r).context(#ctx)?,
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ pub fn derive_decode(item: TokenStream) -> Result<TokenStream> {
|
||||||
Fields::Unnamed(fields) => {
|
Fields::Unnamed(fields) => {
|
||||||
let init = (0..fields.unnamed.len())
|
let init = (0..fields.unnamed.len())
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let ctx = format!("failed to decode field `{i}`");
|
let ctx = format!("failed to decode field `{i}` in `{input_name}`");
|
||||||
quote! {
|
quote! {
|
||||||
Decode::decode(_r).context(#ctx)?,
|
Decode::decode(_r).context(#ctx)?,
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ pub fn derive_decode(item: TokenStream) -> Result<TokenStream> {
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
impl #impl_generics ::valence_protocol::__private::Decode<#lifetime> for #name #ty_generics
|
impl #impl_generics ::valence_protocol::__private::Decode<#lifetime> for #input_name #ty_generics
|
||||||
#where_clause
|
#where_clause
|
||||||
{
|
{
|
||||||
fn decode(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result<Self> {
|
fn decode(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result<Self> {
|
||||||
|
@ -100,7 +100,8 @@ pub fn derive_decode(item: TokenStream) -> Result<TokenStream> {
|
||||||
.map(|f| {
|
.map(|f| {
|
||||||
let field = f.ident.as_ref().unwrap();
|
let field = f.ident.as_ref().unwrap();
|
||||||
let ctx = format!(
|
let ctx = format!(
|
||||||
"failed to decode field `{field}` in variant `{name}`",
|
"failed to decode field `{field}` in variant `{name}` in \
|
||||||
|
`{input_name}`",
|
||||||
);
|
);
|
||||||
quote! {
|
quote! {
|
||||||
#field: Decode::decode(_r).context(#ctx)?,
|
#field: Decode::decode(_r).context(#ctx)?,
|
||||||
|
@ -116,7 +117,8 @@ pub fn derive_decode(item: TokenStream) -> Result<TokenStream> {
|
||||||
let init = (0..fields.unnamed.len())
|
let init = (0..fields.unnamed.len())
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let ctx = format!(
|
let ctx = format!(
|
||||||
"failed to decode field `{i}` in variant `{name}`",
|
"failed to decode field `{i}` in variant `{name}` in \
|
||||||
|
`{input_name}`",
|
||||||
);
|
);
|
||||||
quote! {
|
quote! {
|
||||||
Decode::decode(_r).context(#ctx)?,
|
Decode::decode(_r).context(#ctx)?,
|
||||||
|
@ -143,16 +145,17 @@ pub fn derive_decode(item: TokenStream) -> Result<TokenStream> {
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
impl #impl_generics ::valence_protocol::__private::Decode<#lifetime> for #name #ty_generics
|
impl #impl_generics ::valence_protocol::__private::Decode<#lifetime> for #input_name #ty_generics
|
||||||
#where_clause
|
#where_clause
|
||||||
{
|
{
|
||||||
fn decode(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result<Self> {
|
fn decode(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result<Self> {
|
||||||
use ::valence_protocol::__private::{Decode, Context, VarInt, bail};
|
use ::valence_protocol::__private::{Decode, Context, VarInt, bail};
|
||||||
|
|
||||||
let disc = VarInt::decode(_r).context("failed to decode enum discriminant")?.0;
|
let ctx = concat!("failed to decode enum discriminant in `", stringify!(#input_name), "`");
|
||||||
|
let disc = VarInt::decode(_r).context(ctx)?.0;
|
||||||
match disc {
|
match disc {
|
||||||
#decode_arms
|
#decode_arms
|
||||||
n => bail!("unexpected enum discriminant {}", disc),
|
n => bail!("unexpected enum discriminant {} in `{}`", disc, stringify!(#input_name)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::{add_trait_bounds, find_packet_id_attr, pair_variants_with_discrimina
|
||||||
pub fn derive_encode(item: TokenStream) -> Result<TokenStream> {
|
pub fn derive_encode(item: TokenStream) -> Result<TokenStream> {
|
||||||
let mut input = parse2::<DeriveInput>(item)?;
|
let mut input = parse2::<DeriveInput>(item)?;
|
||||||
|
|
||||||
let name = input.ident;
|
let input_name = input.ident;
|
||||||
|
|
||||||
add_trait_bounds(
|
add_trait_bounds(
|
||||||
&mut input.generics,
|
&mut input.generics,
|
||||||
|
@ -25,7 +25,7 @@ pub fn derive_encode(item: TokenStream) -> Result<TokenStream> {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| {
|
.map(|f| {
|
||||||
let name = &f.ident.as_ref().unwrap();
|
let name = &f.ident.as_ref().unwrap();
|
||||||
let ctx = format!("failed to encode field `{name}`");
|
let ctx = format!("failed to encode field `{name}` in `{input_name}`");
|
||||||
quote! {
|
quote! {
|
||||||
self.#name.encode(&mut _w).context(#ctx)?;
|
self.#name.encode(&mut _w).context(#ctx)?;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ pub fn derive_encode(item: TokenStream) -> Result<TokenStream> {
|
||||||
Fields::Unnamed(fields) => (0..fields.unnamed.len())
|
Fields::Unnamed(fields) => (0..fields.unnamed.len())
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let lit = LitInt::new(&i.to_string(), Span::call_site());
|
let lit = LitInt::new(&i.to_string(), Span::call_site());
|
||||||
let ctx = format!("failed to encode field `{lit}`");
|
let ctx = format!("failed to encode field `{lit}` in `{input_name}`");
|
||||||
quote! {
|
quote! {
|
||||||
self.#lit.encode(&mut _w).context(#ctx)?;
|
self.#lit.encode(&mut _w).context(#ctx)?;
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ pub fn derive_encode(item: TokenStream) -> Result<TokenStream> {
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
impl #impl_generics ::valence_protocol::__private::Encode for #name #ty_generics
|
impl #impl_generics ::valence_protocol::__private::Encode for #input_name #ty_generics
|
||||||
#where_clause
|
#where_clause
|
||||||
{
|
{
|
||||||
fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> {
|
fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> {
|
||||||
|
@ -67,7 +67,8 @@ pub fn derive_encode(item: TokenStream) -> Result<TokenStream> {
|
||||||
let variant_name = &variant.ident;
|
let variant_name = &variant.ident;
|
||||||
|
|
||||||
let disc_ctx = format!(
|
let disc_ctx = format!(
|
||||||
"failed to encode enum discriminant {disc} for variant `{variant_name}`",
|
"failed to encode enum discriminant {disc} for variant `{variant_name}` \
|
||||||
|
in `{input_name}`",
|
||||||
);
|
);
|
||||||
|
|
||||||
match &variant.fields {
|
match &variant.fields {
|
||||||
|
@ -83,7 +84,7 @@ pub fn derive_encode(item: TokenStream) -> Result<TokenStream> {
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
let ctx = format!(
|
let ctx = format!(
|
||||||
"failed to encode field `{name}` in variant \
|
"failed to encode field `{name}` in variant \
|
||||||
`{variant_name}`",
|
`{variant_name}` in `{input_name}`",
|
||||||
);
|
);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -111,7 +112,7 @@ pub fn derive_encode(item: TokenStream) -> Result<TokenStream> {
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
let ctx = format!(
|
let ctx = format!(
|
||||||
"failed to encode field `{name}` in variant \
|
"failed to encode field `{name}` in variant \
|
||||||
`{variant_name}`"
|
`{variant_name}` in `{input_name}`"
|
||||||
);
|
);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -142,7 +143,7 @@ pub fn derive_encode(item: TokenStream) -> Result<TokenStream> {
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[allow(unused_imports, unreachable_code)]
|
#[allow(unused_imports, unreachable_code)]
|
||||||
impl #impl_generics ::valence_protocol::Encode for #name #ty_generics
|
impl #impl_generics ::valence_protocol::Encode for #input_name #ty_generics
|
||||||
#where_clause
|
#where_clause
|
||||||
{
|
{
|
||||||
fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> {
|
fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> {
|
||||||
|
|
|
@ -15,7 +15,6 @@ struct TopLevel {
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
struct Block {
|
struct Block {
|
||||||
#[allow(unused)]
|
|
||||||
id: u16,
|
id: u16,
|
||||||
item_id: u16,
|
item_id: u16,
|
||||||
translation_key: String,
|
translation_key: String,
|
||||||
|
@ -362,6 +361,18 @@ pub fn build() -> anyhow::Result<TokenStream> {
|
||||||
})
|
})
|
||||||
.collect::<TokenStream>();
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let block_kind_from_raw_arms = blocks
|
||||||
|
.iter()
|
||||||
|
.map(|block| {
|
||||||
|
let name = ident(block.name.to_pascal_case());
|
||||||
|
let id = block.id;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#id => Some(BlockKind::#name),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
let block_kind_count = blocks.len();
|
let block_kind_count = blocks.len();
|
||||||
|
|
||||||
let prop_names = blocks
|
let prop_names = blocks
|
||||||
|
@ -642,6 +653,23 @@ pub fn build() -> anyhow::Result<TokenStream> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a block kind from a raw block kind ID.
|
||||||
|
///
|
||||||
|
/// If the given ID is invalid, `None` is returned.
|
||||||
|
pub const fn from_raw(id: u16) -> Option<Self> {
|
||||||
|
match id {
|
||||||
|
#block_kind_from_raw_arms
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this block kind to its underlying raw block state ID.
|
||||||
|
///
|
||||||
|
/// The original block kind can be recovered with [`BlockKind::from_raw`].
|
||||||
|
pub const fn to_raw(self) -> u16 {
|
||||||
|
self as u16
|
||||||
|
}
|
||||||
|
|
||||||
/// An array of all block kinds.
|
/// An array of all block kinds.
|
||||||
pub const ALL: [Self; #block_kind_count] = [#(Self::#block_kind_variants,)*];
|
pub const ALL: [Self; #block_kind_count] = [#(Self::#block_kind_variants,)*];
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ mod item;
|
||||||
mod translation_key;
|
mod translation_key;
|
||||||
|
|
||||||
pub fn main() -> anyhow::Result<()> {
|
pub fn main() -> anyhow::Result<()> {
|
||||||
println!("cargo:rerun-if-changed=../extracted/");
|
println!("cargo:rerun-if-changed=../../extracted/");
|
||||||
|
|
||||||
let generators = [
|
let generators = [
|
||||||
(block::build as fn() -> _, "block.rs"),
|
(block::build as fn() -> _, "block.rs"),
|
||||||
|
|
|
@ -51,7 +51,7 @@ fn fmt_block_state(bs: BlockState, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
|
||||||
impl Encode for BlockState {
|
impl Encode for BlockState {
|
||||||
fn encode(&self, w: impl Write) -> Result<()> {
|
fn encode(&self, w: impl Write) -> Result<()> {
|
||||||
VarInt(self.0 as i32).encode(w)
|
VarInt(self.to_raw() as i32).encode(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,21 @@ impl Decode<'_> for BlockState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Encode for BlockKind {
|
||||||
|
fn encode(&self, w: impl Write) -> Result<()> {
|
||||||
|
VarInt(self.to_raw() as i32).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode<'_> for BlockKind {
|
||||||
|
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||||
|
let id = VarInt::decode(r)?.0;
|
||||||
|
let errmsg = "invalid block kind ID";
|
||||||
|
|
||||||
|
BlockKind::from_raw(id.try_into().context(errmsg)?).context(errmsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||||
pub enum BlockFace {
|
pub enum BlockFace {
|
||||||
/// -Y
|
/// -Y
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::{BTreeSet, HashSet};
|
||||||
|
use std::hash::{BuildHasher, Hash};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@ -429,6 +431,92 @@ impl<'a, T: Decode<'a>> Decode<'a> for Box<[T]> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Encode, S> Encode for HashSet<T, S> {
|
||||||
|
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||||
|
let len = self.len();
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
len <= i32::MAX as usize,
|
||||||
|
"length of hash set ({len}) exceeds i32::MAX"
|
||||||
|
);
|
||||||
|
|
||||||
|
VarInt(len as i32).encode(&mut w)?;
|
||||||
|
|
||||||
|
for val in self {
|
||||||
|
val.encode(&mut w)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, S> Decode<'a> for HashSet<T, S>
|
||||||
|
where
|
||||||
|
T: Eq + Hash + Decode<'a>,
|
||||||
|
S: BuildHasher + Default,
|
||||||
|
{
|
||||||
|
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||||
|
let len = VarInt::decode(r)?.0;
|
||||||
|
ensure!(len >= 0, "attempt to decode hash set with negative length");
|
||||||
|
let len = len as usize;
|
||||||
|
|
||||||
|
// Don't allocate more memory than what would roughly fit in a single packet in
|
||||||
|
// case we get a malicious array length.
|
||||||
|
let cap = (MAX_PACKET_SIZE as usize / mem::size_of::<T>().max(1)).min(len);
|
||||||
|
let mut set = HashSet::with_capacity_and_hasher(cap, S::default());
|
||||||
|
|
||||||
|
for _ in 0..len {
|
||||||
|
ensure!(
|
||||||
|
set.insert(T::decode(r)?),
|
||||||
|
"encountered duplicate item while decoding hash set"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Encode> Encode for BTreeSet<T> {
|
||||||
|
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||||
|
let len = self.len();
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
len <= i32::MAX as usize,
|
||||||
|
"length of b-tree set ({len}) exceeds i32::MAX"
|
||||||
|
);
|
||||||
|
|
||||||
|
VarInt(len as i32).encode(&mut w)?;
|
||||||
|
|
||||||
|
for val in self {
|
||||||
|
val.encode(&mut w)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Ord + Decode<'a>> Decode<'a> for BTreeSet<T> {
|
||||||
|
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||||
|
let len = VarInt::decode(r)?.0;
|
||||||
|
ensure!(
|
||||||
|
len >= 0,
|
||||||
|
"attempt to decode b-tree set with negative length"
|
||||||
|
);
|
||||||
|
let len = len as usize;
|
||||||
|
|
||||||
|
let mut set = BTreeSet::new();
|
||||||
|
|
||||||
|
for _ in 0..len {
|
||||||
|
ensure!(
|
||||||
|
set.insert(T::decode(r)?),
|
||||||
|
"encountered duplicate item while decoding b-tree set"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==== String ==== //
|
// ==== String ==== //
|
||||||
|
|
||||||
impl Encode for str {
|
impl Encode for str {
|
||||||
|
|
|
@ -112,6 +112,7 @@ pub mod packets;
|
||||||
pub mod particle;
|
pub mod particle;
|
||||||
pub mod player_list;
|
pub mod player_list;
|
||||||
mod raw_bytes;
|
mod raw_bytes;
|
||||||
|
pub mod recipe;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod translation_key;
|
pub mod translation_key;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
|
@ -7,11 +7,12 @@ use crate::byte_angle::ByteAngle;
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
use crate::item::ItemStack;
|
use crate::item::ItemStack;
|
||||||
use crate::raw_bytes::RawBytes;
|
use crate::raw_bytes::RawBytes;
|
||||||
|
use crate::recipe::DeclaredRecipe;
|
||||||
use crate::text::Text;
|
use crate::text::Text;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
AttributeProperty, BossBarAction, ChunkDataBlockEntity, Difficulty, GameMode,
|
AttributeProperty, BossBarAction, ChunkDataBlockEntity, Difficulty, GameEventKind, GameMode,
|
||||||
GameStateChangeReason, GlobalPos, PlayerAbilitiesFlags, SignedProperty, SoundCategory,
|
GlobalPos, PlayerAbilitiesFlags, SignedProperty, SoundCategory, SyncPlayerPosLookFlags,
|
||||||
SyncPlayerPosLookFlags,
|
TagGroup,
|
||||||
};
|
};
|
||||||
use crate::username::Username;
|
use crate::username::Username;
|
||||||
use crate::var_int::VarInt;
|
use crate::var_int::VarInt;
|
||||||
|
@ -276,7 +277,7 @@ pub mod play {
|
||||||
#[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
|
#[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
|
||||||
#[packet_id = 0x1c]
|
#[packet_id = 0x1c]
|
||||||
pub struct GameEvent {
|
pub struct GameEvent {
|
||||||
pub reason: GameStateChangeReason,
|
pub kind: GameEventKind,
|
||||||
pub value: f32,
|
pub value: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -673,6 +674,16 @@ pub mod play {
|
||||||
pub features: Vec<Ident<&'a str>>,
|
pub features: Vec<Ident<&'a str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Encode, Decode, EncodePacket, DecodePacket)]
|
||||||
|
#[packet_id = 0x69]
|
||||||
|
pub struct DeclareRecipes<'a> {
|
||||||
|
pub recipes: Vec<DeclaredRecipe<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Encode, Decode, EncodePacket, DecodePacket)]
|
||||||
|
#[packet_id = 0x6a]
|
||||||
|
pub struct UpdateTags<'a>(pub Vec<TagGroup<'a>>);
|
||||||
|
|
||||||
packet_enum! {
|
packet_enum! {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
S2cPlayPacket<'a> {
|
S2cPlayPacket<'a> {
|
||||||
|
@ -737,6 +748,8 @@ pub mod play {
|
||||||
TeleportEntity,
|
TeleportEntity,
|
||||||
UpdateAttributes<'a>,
|
UpdateAttributes<'a>,
|
||||||
FeatureFlags<'a>,
|
FeatureFlags<'a>,
|
||||||
|
DeclareRecipes<'a>,
|
||||||
|
UpdateTags<'a>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
392
crates/valence_protocol/src/recipe.rs
Normal file
392
crates/valence_protocol/src/recipe.rs
Normal file
|
@ -0,0 +1,392 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use anyhow::{bail, ensure};
|
||||||
|
|
||||||
|
use crate::{Decode, Encode, Ident, ItemStack, VarInt};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum DeclaredRecipe<'a> {
|
||||||
|
CraftingShapeless {
|
||||||
|
recipe_id: Ident<&'a str>,
|
||||||
|
group: &'a str,
|
||||||
|
category: CraftingCategory,
|
||||||
|
ingredients: Vec<Ingredient>,
|
||||||
|
result: Option<ItemStack>,
|
||||||
|
},
|
||||||
|
CraftingShaped {
|
||||||
|
recipe_id: Ident<&'a str>,
|
||||||
|
width: VarInt,
|
||||||
|
height: VarInt,
|
||||||
|
group: &'a str,
|
||||||
|
category: CraftingCategory,
|
||||||
|
ingredients: Vec<Ingredient>,
|
||||||
|
result: Option<ItemStack>,
|
||||||
|
},
|
||||||
|
CraftingSpecial {
|
||||||
|
kind: SpecialCraftingKind,
|
||||||
|
recipe_id: Ident<&'a str>,
|
||||||
|
category: CraftingCategory,
|
||||||
|
},
|
||||||
|
Smelting {
|
||||||
|
recipe_id: Ident<&'a str>,
|
||||||
|
group: &'a str,
|
||||||
|
category: SmeltCategory,
|
||||||
|
ingredient: Ingredient,
|
||||||
|
result: Option<ItemStack>,
|
||||||
|
experience: f32,
|
||||||
|
cooking_time: VarInt,
|
||||||
|
},
|
||||||
|
Blasting {
|
||||||
|
recipe_id: Ident<&'a str>,
|
||||||
|
group: &'a str,
|
||||||
|
category: SmeltCategory,
|
||||||
|
ingredient: Ingredient,
|
||||||
|
result: Option<ItemStack>,
|
||||||
|
experience: f32,
|
||||||
|
cooking_time: VarInt,
|
||||||
|
},
|
||||||
|
Smoking {
|
||||||
|
recipe_id: Ident<&'a str>,
|
||||||
|
group: &'a str,
|
||||||
|
category: SmeltCategory,
|
||||||
|
ingredient: Ingredient,
|
||||||
|
result: Option<ItemStack>,
|
||||||
|
experience: f32,
|
||||||
|
cooking_time: VarInt,
|
||||||
|
},
|
||||||
|
CampfireCooking {
|
||||||
|
recipe_id: Ident<&'a str>,
|
||||||
|
group: &'a str,
|
||||||
|
category: SmeltCategory,
|
||||||
|
ingredient: Ingredient,
|
||||||
|
result: Option<ItemStack>,
|
||||||
|
experience: f32,
|
||||||
|
cooking_time: VarInt,
|
||||||
|
},
|
||||||
|
Stonecutting {
|
||||||
|
recipe_id: Ident<&'a str>,
|
||||||
|
group: &'a str,
|
||||||
|
ingredient: Ingredient,
|
||||||
|
result: Option<ItemStack>,
|
||||||
|
},
|
||||||
|
Smithing {
|
||||||
|
recipe_id: Ident<&'a str>,
|
||||||
|
base: Ingredient,
|
||||||
|
addition: Ingredient,
|
||||||
|
result: Option<ItemStack>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum SpecialCraftingKind {
|
||||||
|
ArmorDye,
|
||||||
|
BookCloning,
|
||||||
|
MapCloning,
|
||||||
|
MapExtending,
|
||||||
|
FireworkRocket,
|
||||||
|
FireworkStar,
|
||||||
|
FireworkStarFade,
|
||||||
|
RepairItem,
|
||||||
|
TippedArrow,
|
||||||
|
BannerDuplicate,
|
||||||
|
BannerAddPattern,
|
||||||
|
ShieldDecoration,
|
||||||
|
ShulkerBoxColoring,
|
||||||
|
SuspiciousStew,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Any item in the Vec may be used for the recipe.
|
||||||
|
pub type Ingredient = Vec<Option<ItemStack>>;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||||
|
pub enum CraftingCategory {
|
||||||
|
Building,
|
||||||
|
Redstone,
|
||||||
|
Equipment,
|
||||||
|
Misc,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||||
|
pub enum SmeltCategory {
|
||||||
|
Food,
|
||||||
|
Blocks,
|
||||||
|
Misc,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Encode for DeclaredRecipe<'a> {
|
||||||
|
fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
|
||||||
|
match self {
|
||||||
|
DeclaredRecipe::CraftingShapeless {
|
||||||
|
recipe_id,
|
||||||
|
group,
|
||||||
|
category,
|
||||||
|
ingredients,
|
||||||
|
result,
|
||||||
|
} => {
|
||||||
|
"crafting_shapeless".encode(&mut w)?;
|
||||||
|
recipe_id.encode(&mut w)?;
|
||||||
|
group.encode(&mut w)?;
|
||||||
|
category.encode(&mut w)?;
|
||||||
|
ingredients.encode(&mut w)?;
|
||||||
|
result.encode(w)
|
||||||
|
}
|
||||||
|
DeclaredRecipe::CraftingShaped {
|
||||||
|
recipe_id,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
group,
|
||||||
|
category,
|
||||||
|
ingredients,
|
||||||
|
result,
|
||||||
|
} => {
|
||||||
|
"crafting_shaped".encode(&mut w)?;
|
||||||
|
recipe_id.encode(&mut w)?;
|
||||||
|
width.encode(&mut w)?;
|
||||||
|
height.encode(&mut w)?;
|
||||||
|
group.encode(&mut w)?;
|
||||||
|
category.encode(&mut w)?;
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
(width.0 as usize).saturating_mul(height.0 as usize) == ingredients.len(),
|
||||||
|
"width * height must be equal to the number of ingredients"
|
||||||
|
);
|
||||||
|
|
||||||
|
for ing in ingredients {
|
||||||
|
ing.encode(&mut w)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.encode(w)
|
||||||
|
}
|
||||||
|
DeclaredRecipe::CraftingSpecial {
|
||||||
|
kind,
|
||||||
|
recipe_id,
|
||||||
|
category,
|
||||||
|
} => {
|
||||||
|
match kind {
|
||||||
|
SpecialCraftingKind::ArmorDye => "crafting_special_armordye",
|
||||||
|
SpecialCraftingKind::BookCloning => "crafting_special_bookcloning",
|
||||||
|
SpecialCraftingKind::MapCloning => "crafting_special_mapcloning",
|
||||||
|
SpecialCraftingKind::MapExtending => "crafting_special_mapextending",
|
||||||
|
SpecialCraftingKind::FireworkRocket => "crafting_special_firework_rocket",
|
||||||
|
SpecialCraftingKind::FireworkStar => "crafting_special_firework_star",
|
||||||
|
SpecialCraftingKind::FireworkStarFade => "crafting_special_firework_star_fade",
|
||||||
|
SpecialCraftingKind::RepairItem => "crafting_special_repairitem",
|
||||||
|
SpecialCraftingKind::TippedArrow => "crafting_special_tippedarrow",
|
||||||
|
SpecialCraftingKind::BannerDuplicate => "crafting_special_bannerduplicate",
|
||||||
|
SpecialCraftingKind::BannerAddPattern => "crafting_special_banneraddpattern",
|
||||||
|
SpecialCraftingKind::ShieldDecoration => "crafting_special_shielddecoration",
|
||||||
|
SpecialCraftingKind::ShulkerBoxColoring => {
|
||||||
|
"crafting_special_shulkerboxcoloring"
|
||||||
|
}
|
||||||
|
SpecialCraftingKind::SuspiciousStew => "crafting_special_suspiciousstew",
|
||||||
|
}
|
||||||
|
.encode(&mut w)?;
|
||||||
|
recipe_id.encode(&mut w)?;
|
||||||
|
category.encode(w)
|
||||||
|
}
|
||||||
|
DeclaredRecipe::Smelting {
|
||||||
|
recipe_id,
|
||||||
|
group,
|
||||||
|
category,
|
||||||
|
ingredient,
|
||||||
|
result,
|
||||||
|
experience,
|
||||||
|
cooking_time,
|
||||||
|
} => {
|
||||||
|
"smelting".encode(&mut w)?;
|
||||||
|
recipe_id.encode(&mut w)?;
|
||||||
|
group.encode(&mut w)?;
|
||||||
|
category.encode(&mut w)?;
|
||||||
|
ingredient.encode(&mut w)?;
|
||||||
|
result.encode(&mut w)?;
|
||||||
|
experience.encode(&mut w)?;
|
||||||
|
cooking_time.encode(w)
|
||||||
|
}
|
||||||
|
DeclaredRecipe::Blasting {
|
||||||
|
recipe_id,
|
||||||
|
group,
|
||||||
|
category,
|
||||||
|
ingredient,
|
||||||
|
result,
|
||||||
|
experience,
|
||||||
|
cooking_time,
|
||||||
|
} => {
|
||||||
|
"blasting".encode(&mut w)?;
|
||||||
|
recipe_id.encode(&mut w)?;
|
||||||
|
group.encode(&mut w)?;
|
||||||
|
category.encode(&mut w)?;
|
||||||
|
ingredient.encode(&mut w)?;
|
||||||
|
result.encode(&mut w)?;
|
||||||
|
experience.encode(&mut w)?;
|
||||||
|
cooking_time.encode(w)
|
||||||
|
}
|
||||||
|
DeclaredRecipe::Smoking {
|
||||||
|
recipe_id,
|
||||||
|
group,
|
||||||
|
category,
|
||||||
|
ingredient,
|
||||||
|
result,
|
||||||
|
experience,
|
||||||
|
cooking_time,
|
||||||
|
} => {
|
||||||
|
"smoking".encode(&mut w)?;
|
||||||
|
recipe_id.encode(&mut w)?;
|
||||||
|
group.encode(&mut w)?;
|
||||||
|
category.encode(&mut w)?;
|
||||||
|
ingredient.encode(&mut w)?;
|
||||||
|
result.encode(&mut w)?;
|
||||||
|
experience.encode(&mut w)?;
|
||||||
|
cooking_time.encode(w)
|
||||||
|
}
|
||||||
|
DeclaredRecipe::CampfireCooking {
|
||||||
|
recipe_id,
|
||||||
|
group,
|
||||||
|
category,
|
||||||
|
ingredient,
|
||||||
|
result,
|
||||||
|
experience,
|
||||||
|
cooking_time,
|
||||||
|
} => {
|
||||||
|
"campfire_cooking".encode(&mut w)?;
|
||||||
|
recipe_id.encode(&mut w)?;
|
||||||
|
group.encode(&mut w)?;
|
||||||
|
category.encode(&mut w)?;
|
||||||
|
ingredient.encode(&mut w)?;
|
||||||
|
result.encode(&mut w)?;
|
||||||
|
experience.encode(&mut w)?;
|
||||||
|
cooking_time.encode(w)
|
||||||
|
}
|
||||||
|
DeclaredRecipe::Stonecutting {
|
||||||
|
recipe_id,
|
||||||
|
group,
|
||||||
|
ingredient,
|
||||||
|
result,
|
||||||
|
} => {
|
||||||
|
"stonecutting".encode(&mut w)?;
|
||||||
|
recipe_id.encode(&mut w)?;
|
||||||
|
group.encode(&mut w)?;
|
||||||
|
ingredient.encode(&mut w)?;
|
||||||
|
result.encode(w)
|
||||||
|
}
|
||||||
|
DeclaredRecipe::Smithing {
|
||||||
|
recipe_id,
|
||||||
|
base,
|
||||||
|
addition,
|
||||||
|
result,
|
||||||
|
} => {
|
||||||
|
"smithing".encode(&mut w)?;
|
||||||
|
recipe_id.encode(&mut w)?;
|
||||||
|
base.encode(&mut w)?;
|
||||||
|
addition.encode(&mut w)?;
|
||||||
|
result.encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Decode<'a> for DeclaredRecipe<'a> {
|
||||||
|
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
|
||||||
|
Ok(match Ident::<&str>::decode(r)?.path() {
|
||||||
|
"crafting_shapeless" => Self::CraftingShapeless {
|
||||||
|
recipe_id: Decode::decode(r)?,
|
||||||
|
group: Decode::decode(r)?,
|
||||||
|
category: Decode::decode(r)?,
|
||||||
|
ingredients: Decode::decode(r)?,
|
||||||
|
result: Decode::decode(r)?,
|
||||||
|
},
|
||||||
|
"crafting_shaped" => {
|
||||||
|
let recipe_id = Ident::<&str>::decode(r)?;
|
||||||
|
let width = VarInt::decode(r)?.0;
|
||||||
|
let height = VarInt::decode(r)?.0;
|
||||||
|
let group = <&str>::decode(r)?;
|
||||||
|
let category = CraftingCategory::decode(r)?;
|
||||||
|
|
||||||
|
let mut ingredients = Vec::new();
|
||||||
|
for _ in 0..width.saturating_mul(height) {
|
||||||
|
ingredients.push(Ingredient::decode(r)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::CraftingShaped {
|
||||||
|
recipe_id,
|
||||||
|
width: VarInt(width),
|
||||||
|
height: VarInt(height),
|
||||||
|
group,
|
||||||
|
category,
|
||||||
|
ingredients,
|
||||||
|
result: Decode::decode(r)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"smelting" => Self::Smelting {
|
||||||
|
recipe_id: Decode::decode(r)?,
|
||||||
|
group: Decode::decode(r)?,
|
||||||
|
category: Decode::decode(r)?,
|
||||||
|
ingredient: Decode::decode(r)?,
|
||||||
|
result: Decode::decode(r)?,
|
||||||
|
experience: Decode::decode(r)?,
|
||||||
|
cooking_time: Decode::decode(r)?,
|
||||||
|
},
|
||||||
|
"blasting" => Self::Blasting {
|
||||||
|
recipe_id: Decode::decode(r)?,
|
||||||
|
group: Decode::decode(r)?,
|
||||||
|
category: Decode::decode(r)?,
|
||||||
|
ingredient: Decode::decode(r)?,
|
||||||
|
result: Decode::decode(r)?,
|
||||||
|
experience: Decode::decode(r)?,
|
||||||
|
cooking_time: Decode::decode(r)?,
|
||||||
|
},
|
||||||
|
"smoking" => Self::Smoking {
|
||||||
|
recipe_id: Decode::decode(r)?,
|
||||||
|
group: Decode::decode(r)?,
|
||||||
|
category: Decode::decode(r)?,
|
||||||
|
ingredient: Decode::decode(r)?,
|
||||||
|
result: Decode::decode(r)?,
|
||||||
|
experience: Decode::decode(r)?,
|
||||||
|
cooking_time: Decode::decode(r)?,
|
||||||
|
},
|
||||||
|
"campfire_cooking" => Self::CampfireCooking {
|
||||||
|
recipe_id: Decode::decode(r)?,
|
||||||
|
group: Decode::decode(r)?,
|
||||||
|
category: Decode::decode(r)?,
|
||||||
|
ingredient: Decode::decode(r)?,
|
||||||
|
result: Decode::decode(r)?,
|
||||||
|
experience: Decode::decode(r)?,
|
||||||
|
cooking_time: Decode::decode(r)?,
|
||||||
|
},
|
||||||
|
"stonecutting" => Self::Stonecutting {
|
||||||
|
recipe_id: Decode::decode(r)?,
|
||||||
|
group: Decode::decode(r)?,
|
||||||
|
ingredient: Decode::decode(r)?,
|
||||||
|
result: Decode::decode(r)?,
|
||||||
|
},
|
||||||
|
"smithing" => Self::Smithing {
|
||||||
|
recipe_id: Decode::decode(r)?,
|
||||||
|
base: Decode::decode(r)?,
|
||||||
|
addition: Decode::decode(r)?,
|
||||||
|
result: Decode::decode(r)?,
|
||||||
|
},
|
||||||
|
other => Self::CraftingSpecial {
|
||||||
|
kind: match other {
|
||||||
|
"crafting_special_armordye" => SpecialCraftingKind::ArmorDye,
|
||||||
|
"crafting_special_bookcloning" => SpecialCraftingKind::BookCloning,
|
||||||
|
"crafting_special_mapcloning" => SpecialCraftingKind::MapCloning,
|
||||||
|
"crafting_special_mapextending" => SpecialCraftingKind::MapExtending,
|
||||||
|
"crafting_special_firework_rocket" => SpecialCraftingKind::FireworkRocket,
|
||||||
|
"crafting_special_firework_star" => SpecialCraftingKind::FireworkStar,
|
||||||
|
"crafting_special_firework_star_fade" => SpecialCraftingKind::FireworkStarFade,
|
||||||
|
"crafting_special_repairitem" => SpecialCraftingKind::RepairItem,
|
||||||
|
"crafting_special_tippedarrow" => SpecialCraftingKind::TippedArrow,
|
||||||
|
"crafting_special_bannerduplicate" => SpecialCraftingKind::BannerDuplicate,
|
||||||
|
"crafting_special_banneraddpattern" => SpecialCraftingKind::BannerAddPattern,
|
||||||
|
"crafting_special_shielddecoration" => SpecialCraftingKind::ShieldDecoration,
|
||||||
|
"crafting_special_shulkerboxcoloring" => {
|
||||||
|
SpecialCraftingKind::ShulkerBoxColoring
|
||||||
|
}
|
||||||
|
"crafting_special_suspiciousstew" => SpecialCraftingKind::SuspiciousStew,
|
||||||
|
_ => bail!("unknown recipe type \"{other}\""),
|
||||||
|
},
|
||||||
|
recipe_id: Decode::decode(r)?,
|
||||||
|
category: CraftingCategory::decode(r)?,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -241,7 +241,7 @@ pub enum SoundCategory {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||||
pub enum GameStateChangeReason {
|
pub enum GameEventKind {
|
||||||
NoRespawnBlockAvailable,
|
NoRespawnBlockAvailable,
|
||||||
EndRaining,
|
EndRaining,
|
||||||
BeginRaining,
|
BeginRaining,
|
||||||
|
@ -358,3 +358,15 @@ pub struct PlayerAbilitiesFlags {
|
||||||
#[bits(4)]
|
#[bits(4)]
|
||||||
_pad: u8,
|
_pad: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||||
|
pub struct TagGroup<'a> {
|
||||||
|
pub kind: Ident<&'a str>,
|
||||||
|
pub tags: Vec<Tag<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||||
|
pub struct Tag<'a> {
|
||||||
|
pub name: Ident<&'a str>,
|
||||||
|
pub entries: Vec<VarInt>,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue