aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorregenerativep <regeneralp@gmail.com>2021-06-29 15:25:22 -0400
committerregenerativep <regeneralp@gmail.com>2021-06-29 15:25:22 -0400
commit5e58c8153235a4bd93486c1196b2883280c2424e (patch)
tree53795b1edc3757e3199de12035547e4a64ae950f /src
parent57a8c1b7c5e9bcc3d92c75732c29ba7300f40753 (diff)
downloadmcproto-rs-5e58c8153235a4bd93486c1196b2883280c2424e.tar.gz
mcproto-rs-5e58c8153235a4bd93486c1196b2883280c2424e.tar.bz2
mcproto-rs-5e58c8153235a4bd93486c1196b2883280c2424e.zip
possible chat fix, jigsaw/creative swap
Diffstat (limited to 'src')
-rw-r--r--src/chat.rs385
-rw-r--r--src/v1_16_3.rs4
2 files changed, 248 insertions, 141 deletions
diff --git a/src/chat.rs b/src/chat.rs
index 59b9097..fbce103 100644
--- a/src/chat.rs
+++ b/src/chat.rs
@@ -1,9 +1,16 @@
-use alloc::{vec::Vec, string::{String, ToString}, collections::{BTreeMap}, boxed::Box, borrow::ToOwned, fmt, format};
-use serde::{Serialize, Deserialize, Deserializer, de, Serializer};
-use serde::de::{Visitor, Error, IntoDeserializer, MapAccess};
+use crate::{DeserializeResult, SerializeResult};
+use alloc::{
+ borrow::ToOwned,
+ boxed::Box,
+ collections::BTreeMap,
+ fmt, format,
+ string::{String, ToString},
+ vec::Vec,
+};
+use serde::de::{Error, IntoDeserializer, MapAccess, Visitor};
use serde::ser::SerializeMap;
+use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
-use crate::{SerializeResult, DeserializeResult};
pub type BoxedChat = Box<Chat>;
@@ -51,7 +58,7 @@ impl Chat {
match self {
Text(body) => Some(body.to_traditional()),
- _ => None
+ _ => None,
}
}
}
@@ -75,7 +82,6 @@ struct TraditionalParser {
}
impl TraditionalParser {
-
fn new(source: &str, translate_colorcodes: bool) -> Self {
Self {
source: source.chars().collect(),
@@ -101,7 +107,7 @@ impl TraditionalParser {
} else if let Some(next) = self.consume_char() {
self.push_next(next)
} else {
- return self.finalize()
+ return self.finalize();
}
}
}
@@ -145,8 +151,8 @@ impl TraditionalParser {
hover_event: None,
click_event: None,
insertion: None,
- extra: Vec::default()
- }
+ extra: Vec::default(),
+ },
};
self.text.clear();
self.done.push(current);
@@ -165,14 +171,17 @@ impl TraditionalParser {
}
fn has_text(&self) -> bool {
- return !self.text.is_empty()
+ return !self.text.is_empty();
}
fn is_on_formatter(&self) -> bool {
- self.source.get(self.at).map(move |c| {
- let c = *c;
- c == SECTION_SYMBOL || (self.translate_colorcodes && c == '&')
- }).unwrap_or(false)
+ self.source
+ .get(self.at)
+ .map(move |c| {
+ let c = *c;
+ c == SECTION_SYMBOL || (self.translate_colorcodes && c == '&')
+ })
+ .unwrap_or(false)
}
fn consume_char(&mut self) -> Option<char> {
@@ -214,8 +223,10 @@ impl TraditionalParser {
if n_components > 0 {
top_level.base.extra.extend(
- self.done.into_iter()
- .map(move |component| Chat::Text(component).boxed()));
+ self.done
+ .into_iter()
+ .map(move |component| Chat::Text(component).boxed()),
+ );
}
Chat::Text(top_level)
@@ -276,14 +287,13 @@ fn should_skip_flag_field(flag: &bool) -> bool {
}
impl BaseComponent {
-
fn has_same_style_as(&self, other: &Self) -> bool {
- other.bold == self.bold &&
- other.italic == self.italic &&
- other.underlined == self.underlined &&
- other.strikethrough == self.strikethrough &&
- other.obfuscated == self.obfuscated &&
- other.color.eq(&self.color)
+ other.bold == self.bold
+ && other.italic == self.italic
+ && other.underlined == self.underlined
+ && other.strikethrough == self.strikethrough
+ && other.obfuscated == self.obfuscated
+ && other.color.eq(&self.color)
}
}
@@ -299,7 +309,11 @@ impl Into<BaseComponent> for JsonComponentBase {
insertion: self.insertion,
click_event: self.click_event,
hover_event: self.hover_event,
- extra: self.extra.into_iter().map(move |elem| elem.boxed()).collect(),
+ extra: self
+ .extra
+ .into_iter()
+ .map(move |elem| elem.boxed())
+ .collect(),
}
}
}
@@ -321,7 +335,7 @@ struct JsonComponentBase {
pub extra: Vec<Chat>,
#[serde(flatten)]
- _additional: BTreeMap<String, serde_json::Value>
+ _additional: BTreeMap<String, serde_json::Value>,
}
impl Default for BaseComponent {
@@ -372,7 +386,7 @@ impl TextComponent {
Some(child_fmts) => {
last_had_formatters = true;
buf.extend(child_fmts.chars())
- },
+ }
None => {
last_had_formatters = false;
}
@@ -438,7 +452,7 @@ pub struct KeybindComponent {
#[serde(flatten)]
#[serde(skip_deserializing)]
- pub base: BaseComponent
+ pub base: BaseComponent,
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
pub struct ScoreComponent {
@@ -446,7 +460,7 @@ pub struct ScoreComponent {
#[serde(flatten)]
#[serde(skip_deserializing)]
- pub base: BaseComponent
+ pub base: BaseComponent,
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
@@ -463,23 +477,27 @@ pub enum ChatClickEvent {
OpenUrl(String),
RunCommand(String),
SuggestCommand(String),
- ChangePage(i32)
+ ChangePage(i32),
}
impl Serialize for ChatClickEvent {
- fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
- S: Serializer
+ fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
+ where
+ S: Serializer,
{
let mut m = serializer.serialize_map(Some(2))?;
use ChatClickEvent::*;
- m.serialize_entry("action", match self {
- OpenUrl(_) => "open_url",
- RunCommand(_) => "run_command",
- SuggestCommand(_) => "suggest_command",
- ChangePage(_) => "change_page",
- })?;
+ m.serialize_entry(
+ "action",
+ match self {
+ OpenUrl(_) => "open_url",
+ RunCommand(_) => "run_command",
+ SuggestCommand(_) => "suggest_command",
+ ChangePage(_) => "change_page",
+ },
+ )?;
m.serialize_key("value")?;
@@ -495,8 +513,9 @@ impl Serialize for ChatClickEvent {
}
impl<'de> Deserialize<'de> for ChatClickEvent {
- fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where
- D: Deserializer<'de>
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
+ where
+ D: Deserializer<'de>,
{
struct V;
@@ -507,8 +526,9 @@ impl<'de> Deserialize<'de> for ChatClickEvent {
write!(f, "an event object for ChatClickEvent")
}
- fn visit_map<A>(self, mut map: A) -> Result<Self::Value, <A as MapAccess<'de>>::Error> where
- A: MapAccess<'de>
+ fn visit_map<A>(self, mut map: A) -> Result<Self::Value, <A as MapAccess<'de>>::Error>
+ where
+ A: MapAccess<'de>,
{
let (action, value) = read_event(&mut map)?;
@@ -516,21 +536,36 @@ impl<'de> Deserialize<'de> for ChatClickEvent {
match action {
"open_url" => match value.as_str() {
Some(url) => Ok(OpenUrl(url.to_owned())),
- None => Err(A::Error::custom(format!("open_url requires string body, got {}", value)))
+ None => Err(A::Error::custom(format!(
+ "open_url requires string body, got {}",
+ value
+ ))),
},
"run_command" => match value.as_str() {
Some(cmd) => Ok(RunCommand(cmd.to_owned())),
- None => Err(A::Error::custom(format!("run_command requires string body, got {}", value)))
+ None => Err(A::Error::custom(format!(
+ "run_command requires string body, got {}",
+ value
+ ))),
},
"suggest_command" => match value.as_str() {
Some(cmd) => Ok(SuggestCommand(cmd.to_owned())),
- None => Err(A::Error::custom(format!("suggest_command requires string body, got {}", value)))
+ None => Err(A::Error::custom(format!(
+ "suggest_command requires string body, got {}",
+ value
+ ))),
},
"change_page" => match value.as_i64() {
Some(v) => Ok(ChangePage(v as i32)),
- None => Err(A::Error::custom(format!("change_page requires integer body, got {}", value)))
+ None => Err(A::Error::custom(format!(
+ "change_page requires integer body, got {}",
+ value
+ ))),
},
- other => Err(A::Error::custom(format!("invalid click action kind {}", other)))
+ other => Err(A::Error::custom(format!(
+ "invalid click action kind {}",
+ other
+ ))),
}
}
}
@@ -543,22 +578,26 @@ impl<'de> Deserialize<'de> for ChatClickEvent {
pub enum ChatHoverEvent {
ShowText(BoxedChat),
ShowItem(Value),
- ShowEntity(Value)
+ ShowEntity(Value),
}
impl Serialize for ChatHoverEvent {
- fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
- S: Serializer
+ fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
+ where
+ S: Serializer,
{
let mut m = serializer.serialize_map(Some(2))?;
use ChatHoverEvent::*;
- m.serialize_entry("action", match self {
- ShowText(_) => "show_text",
- ShowItem(_) => "show_item",
- ShowEntity(_) => "show_entity",
- })?;
+ m.serialize_entry(
+ "action",
+ match self {
+ ShowText(_) => "show_text",
+ ShowItem(_) => "show_item",
+ ShowEntity(_) => "show_entity",
+ },
+ )?;
m.serialize_key("value")?;
@@ -573,8 +612,9 @@ impl Serialize for ChatHoverEvent {
}
impl<'de> Deserialize<'de> for ChatHoverEvent {
- fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where
- D: Deserializer<'de>
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
+ where
+ D: Deserializer<'de>,
{
struct V;
@@ -586,8 +626,9 @@ impl<'de> Deserialize<'de> for ChatHoverEvent {
}
//noinspection ALL
- fn visit_map<A>(self, mut map: A) -> Result<Self::Value, <A as MapAccess<'de>>::Error> where
- A: MapAccess<'de>
+ fn visit_map<A>(self, mut map: A) -> Result<Self::Value, <A as MapAccess<'de>>::Error>
+ where
+ A: MapAccess<'de>,
{
let (action, value) = read_event(&mut map)?;
@@ -595,12 +636,20 @@ impl<'de> Deserialize<'de> for ChatHoverEvent {
match action {
"show_text" => Ok(ShowText(
Chat::deserialize(value.into_deserializer())
- .map_err(move |err| A::Error::custom(
- format!("error deserializing text to show {:?}", err)))?
- .boxed())),
+ .map_err(move |err| {
+ A::Error::custom(format!(
+ "error deserializing text to show {:?}",
+ err
+ ))
+ })?
+ .boxed(),
+ )),
"show_item" => Ok(ShowItem(value)),
"show_entity" => Ok(ShowEntity(value)),
- other => Err(A::Error::custom(format!("invalid hover action kind {}", other)))
+ other => Err(A::Error::custom(format!(
+ "invalid hover action kind {}",
+ other
+ ))),
}
}
}
@@ -726,15 +775,18 @@ impl fmt::Display for ColorCode {
}
impl Serialize for ColorCode {
- fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
- S: Serializer {
+ fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
+ where
+ S: Serializer,
+ {
serializer.serialize_str(self.name())
}
}
impl<'de> Deserialize<'de> for ColorCode {
- fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where
- D: Deserializer<'de>
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
+ where
+ D: Deserializer<'de>,
{
struct V;
@@ -745,7 +797,8 @@ impl<'de> Deserialize<'de> for ColorCode {
write!(fmt, "a string representing a color code")
}
- fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
+ fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+ where
E: de::Error,
{
if let Some(code) = ColorCode::from_name(v) {
@@ -827,10 +880,10 @@ impl fmt::Display for Formatter {
}
}
-
impl<'de> Deserialize<'de> for Chat {
- fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where
- D: Deserializer<'de>
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
+ where
+ D: Deserializer<'de>,
{
struct V;
@@ -838,30 +891,49 @@ impl<'de> Deserialize<'de> for Chat {
type Value = Chat;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(formatter, "any primitive or a JSON object specifying the component")
+ write!(
+ formatter,
+ "any primitive or a JSON object specifying the component"
+ )
}
- fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> where E: de::Error {
+ fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
self.visit_string(value.to_string())
}
- fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> where E: de::Error {
+ fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
self.visit_string(value.to_string())
}
- fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> where E: de::Error {
+ fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
self.visit_string(value.to_string())
}
- fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: de::Error {
+ fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
Ok(Chat::Text(TextComponent {
base: BaseComponent::default(),
text: value.to_owned(),
}))
}
- fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error> where M: de::MapAccess<'de> {
- let mut base: JsonComponentBase = de::Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
+ fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
+ where
+ M: de::MapAccess<'de>,
+ {
+ let mut base: JsonComponentBase =
+ de::Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
let additional = &mut base._additional;
// string component
@@ -873,7 +945,10 @@ impl<'de> Deserialize<'de> for Chat {
base: base.into(),
}))
} else {
- Err(M::Error::custom(format!("have text but it's not a string - {:?}", raw_text)))
+ Err(M::Error::custom(format!(
+ "have text but it's not a string - {:?}",
+ raw_text
+ )))
};
}
@@ -890,36 +965,45 @@ impl<'de> Deserialize<'de> for Chat {
format!("unable to parse one of the translation with entries :: {}", err)))?
.boxed());
}
- Ok(Chat::Translation(TranslationComponent{
+ Ok(Chat::Translation(TranslationComponent {
base: base.into(),
translate: translate.to_owned(),
with: withs_out,
}))
} else {
- Err(M::Error::custom(format!("have with but it's not an array - {:?}", raw_with)))
+ Err(M::Error::custom(format!(
+ "have with but it's not an array - {:?}",
+ raw_with
+ )))
}
} else {
- Ok(Chat::Translation(TranslationComponent{
+ Ok(Chat::Translation(TranslationComponent {
base: base.into(),
translate: translate.to_owned(),
with: Vec::default(),
}))
}
} else {
- Err(M::Error::custom(format!("have translate but it's not a string - {:?}", raw_translate)))
- }
+ Err(M::Error::custom(format!(
+ "have translate but it's not a string - {:?}",
+ raw_translate
+ )))
+ };
}
// keybind
if let Some(raw_keybind) = additional.remove("keybind") {
return if let Some(keybind) = raw_keybind.as_str() {
- Ok(Chat::Keybind(KeybindComponent{
+ Ok(Chat::Keybind(KeybindComponent {
keybind: keybind.to_owned(),
- base: base.into()
+ base: base.into(),
}))
} else {
- Err(M::Error::custom(format!("have keybind but it's not a string! {:?}", raw_keybind)))
- }
+ Err(M::Error::custom(format!(
+ "have keybind but it's not a string! {:?}",
+ raw_keybind
+ )))
+ };
}
// score
@@ -928,7 +1012,7 @@ impl<'de> Deserialize<'de> for Chat {
.map_err(move |err| M::Error::custom(
format!("failed to deserialize scoreboard objective for score chat component :: {:?}", err)))?;
- return Ok(Chat::Score(ScoreComponent{
+ return Ok(Chat::Score(ScoreComponent {
score,
base: base.into(),
}));
@@ -936,7 +1020,9 @@ impl<'de> Deserialize<'de> for Chat {
// selector (SKIP)
- Err(M::Error::custom("not able to parse chat component, not a valid chat component kind"))
+ Err(M::Error::custom(
+ "not able to parse chat component, not a valid chat component kind",
+ ))
}
}
@@ -945,8 +1031,9 @@ impl<'de> Deserialize<'de> for Chat {
}
impl Serialize for Chat {
- fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
- S: Serializer
+ fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
+ where
+ S: Serializer,
{
use Chat::*;
@@ -954,7 +1041,7 @@ impl Serialize for Chat {
Text(body) => body.serialize(serializer),
Translation(body) => body.serialize(serializer),
Keybind(body) => body.serialize(serializer),
- Score(body) => body.serialize(serializer)
+ Score(body) => body.serialize(serializer),
}
}
}
@@ -962,8 +1049,12 @@ impl Serialize for Chat {
impl super::Serialize for Chat {
fn mc_serialize<S: super::Serializer>(&self, to: &mut S) -> SerializeResult {
serde_json::to_string(self)
- .map_err(move |err| super::SerializeErr::FailedJsonEncode(
- format!("error while encoding chat :: {:?} -> {:?}", self, err)))?
+ .map_err(move |err| {
+ super::SerializeErr::FailedJsonEncode(format!(
+ "error while encoding chat :: {:?} -> {:?}",
+ self, err
+ ))
+ })?
.mc_serialize(to)
}
}
@@ -971,10 +1062,12 @@ impl super::Serialize for Chat {
impl super::Deserialize for Chat {
fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
String::mc_deserialize(data)?.try_map(move |raw| {
- serde_json::from_str(raw.as_str()).map_err(move |err|
+ serde_json::from_str(raw.as_str()).map_err(move |err| {
super::DeserializeErr::FailedJsonDeserialize(format!(
- "failed to deserialize chat from JSON '{}' :: {:?}", raw, err
- )))
+ "failed to deserialize chat from JSON '{}' :: {:?}",
+ raw, err
+ ))
+ })
})
}
}
@@ -990,10 +1083,9 @@ impl TestRandom for Chat {
}
}
-fn read_event<'de, A>(
- access: &mut A,
-) -> Result<(&'de str, Value), <A as MapAccess<'de>>::Error>
- where A: MapAccess<'de>
+fn read_event<'de, A>(access: &mut A) -> Result<(&'de str, Value), <A as MapAccess<'de>>::Error>
+where
+ A: MapAccess<'de>,
{
let mut action: Option<&str> = None;
let mut value: Option<Value> = None;
@@ -1005,15 +1097,18 @@ fn read_event<'de, A>(
if action.is_none() {
return Err(A::Error::custom("none for value key=action"));
}
- },
- "value" => {
+ }
+ "value" | "content" => {
value = access.next_value()?;
if value.is_none() {
return Err(A::Error::custom("none for value key=value"));
}
- },
+ }
other => {
- return Err(A::Error::custom(format!("unexpected key in event {}", other)));
+ return Err(A::Error::custom(format!(
+ "unexpected key in event {}",
+ other
+ )));
}
}
} else {
@@ -1032,40 +1127,52 @@ pub mod tests {
#[test]
fn test_from_traditional_simple() {
let out = Chat::from_traditional("&cthis &cis red, and &rthis is &e&lyellow", true);
- assert_eq!(out, Chat::Text(TextComponent{
- text: String::default(),
- base: {
- let mut b = BaseComponent::default();
- b.extra = alloc::vec!(
- Chat::Text(TextComponent{
- text: "this is red, and ".to_owned(),
- base: {
- let mut b = BaseComponent::default();
- b.color = Some(ColorCode::Red);
- b
- },
- }).boxed(),
- Chat::Text(TextComponent{
- text: "this is ".to_owned(),
- base: BaseComponent::default(),
- }).boxed(),
- Chat::Text(TextComponent{
- text: "yellow".to_owned(),
- base: {
- let mut b = BaseComponent::default();
- b.color = Some(ColorCode::Yellow);
- b.bold = true;
- b
- }
- }).boxed()
- );
- b
- }
- }));
+ assert_eq!(
+ out,
+ Chat::Text(TextComponent {
+ text: String::default(),
+ base: {
+ let mut b = BaseComponent::default();
+ b.extra = alloc::vec!(
+ Chat::Text(TextComponent {
+ text: "this is red, and ".to_owned(),
+ base: {
+ let mut b = BaseComponent::default();
+ b.color = Some(ColorCode::Red);
+ b
+ },
+ })
+ .boxed(),
+ Chat::Text(TextComponent {
+ text: "this is ".to_owned(),
+ base: BaseComponent::default(),
+ })
+ .boxed(),
+ Chat::Text(TextComponent {
+ text: "yellow".to_owned(),
+ base: {
+ let mut b = BaseComponent::default();
+ b.color = Some(ColorCode::Yellow);
+ b.bold = true;
+ b
+ }
+ })
+ .boxed()
+ );
+ b
+ }
+ })
+ );
let traditional = out.to_traditional().expect("is text");
- assert_eq!(traditional.as_str(), "§cthis is red, and §rthis is §e§lyellow");
- #[cfg(feature="std")]
- println!("{}", serde_json::to_string_pretty(&out).expect("should serialize fine"));
+ assert_eq!(
+ traditional.as_str(),
+ "§cthis is red, and §rthis is §e§lyellow"
+ );
+ #[cfg(feature = "std")]
+ println!(
+ "{}",
+ serde_json::to_string_pretty(&out).expect("should serialize fine")
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/v1_16_3.rs b/src/v1_16_3.rs
index b9743d4..4c7bce5 100644
--- a/src/v1_16_3.rs
+++ b/src/v1_16_3.rs
@@ -673,13 +673,13 @@ define_protocol!(753, Packet753, RawPacket753, RawPacket753Body, Packet753Kind =
command: String,
track_output: bool
},
- PlayUpdateJigsawBlock, 0x28, Play, ServerBound => PlayUpdateJigsawBlockSpec {
+ PlayUpdateJigsawBlock, 0x29, Play, ServerBound => PlayUpdateJigsawBlockSpec {
location: IntPosition,
attachment_type: String,
target_pool: String,
final_state: String
},
- PlayCreativeInventoryAction, 0x29, Play, ServerBound => PlayCreativeInventoryActionSpec {
+ PlayCreativeInventoryAction, 0x28, Play, ServerBound => PlayCreativeInventoryActionSpec {
slot: i16,
clicked_item: Slot
},