From 5654ebc1675b7068fcdb23292e1a886e958be5c2 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:22:56 +0200 Subject: [PATCH 01/52] Add SculkVibrationSignal --- src/packets/play_clientbound.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 743d3d91..abe79de0 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -88,6 +88,15 @@ pub enum ClientboundPacket<'a> { pitch: Angle, }, + /// Shows a permanent particle. + SculkVibrationSignal { + /// Source position for the vibration + source_position: Position, + /// Identifier of the destination codec type + destination_identifier: Identifier<'a>, + rest: RawBytes<'a>, + }, + /// Sent whenever an entity should change animation EntityAnimation { id: VarInt, From 2cfa6149209df90ac456e67542a70fac3ce6bc32 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:26:11 +0200 Subject: [PATCH 02/52] Add ClearTitles --- src/packets/play_clientbound.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index abe79de0..2ea8f584 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -195,6 +195,11 @@ pub enum ClientboundPacket<'a> { sender: UUID, }, + /// Clears the client's current title information, with the option to also reset it. + ClearTitles { + reset: bool, + }, + /// The server responds with a list of auto-completions of the last word sent to it. /// In the case of regular chat, this is a player username. /// Command names and parameters are also supported. From 1068b6ea25ac78000dd1ba5a27e61247b38ab420 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:28:20 +0200 Subject: [PATCH 03/52] Remove WindowConfirmation --- src/packets/play_clientbound.rs | 13 ------------- src/packets/play_serverbound.rs | 14 -------------- 2 files changed, 27 deletions(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 2ea8f584..0402a64f 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -225,19 +225,6 @@ pub enum ClientboundPacket<'a> { data: RawBytes<'a>, }, - /// A packet from the server indicating whether a request from the client was accepted, or whether there was a conflict (due to lag). - /// If the packet was not accepted, the client must respond with a serverbound window confirmation packet. - /// - /// *Request for [ServerboundPacket::WindowConfirmation]* - WindowConfirmation { - /// The ID of the window that the action occurred in. - window_id: i8, - /// Every action that is to be accepted has a unique ID. This number is an incrementing integer (starting at 0) with separate counts for each window ID. - action_id: i16, - /// Whether the action was accepted. - accepted: bool, - }, - /// This packet is sent from the server to the client when a window is forcibly closed, such as when a chest is destroyed while it's open. CloseWindow { /// This is the ID of the window that was closed. 0 for inventory. diff --git a/src/packets/play_serverbound.rs b/src/packets/play_serverbound.rs index 592f511d..72f20205 100644 --- a/src/packets/play_serverbound.rs +++ b/src/packets/play_serverbound.rs @@ -62,20 +62,6 @@ pub enum ServerboundPacket<'a> { text: &'a str, }, - /// The server may reject client actions by sending [ClientboundPacket::WindowConfirmation] with the `accepted` field set to `false`. - /// When this happens, the client must send this packet to apologize (as with movement), otherwise the server ignores any successive confirmations. - /// - /// *Response to [ClientboundPacket::WindowConfirmation]* - WindowConfirmation { - /// The ID of the window that the action occurred in - window_id: i8, - /// Every action that is to be accepted has a unique id. - /// This id is an incrementing integer (starting at 1) with separate counts for each window ID. - action_id: i16, - /// Whether the action was accepted - accepted: bool, - }, - /// Used when clicking on window buttons ClickWindowButton { /// The ID of the window sent by [ClientboundPacket::OpenWindow]. From a744f00a29b6b1d35a2b3c8f195e7f1dfa0229e2 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:33:46 +0200 Subject: [PATCH 04/52] Add IntitializeWorldBorder --- src/packets/play_clientbound.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 0402a64f..8d4df593 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -375,6 +375,27 @@ pub enum ClientboundPacket<'a> { entity_id: i32, }, + /// The Notchian client determines how solid to display the warning by comparing to whichever is higher, the warning distance or whichever is lower, the distance from the current diameter to the target diameter or the place the border will be after warningTime seconds. + IntitializeWorldBorder { + x: f64, + y: f64, + /// Current length of a single side of the world border, in meters. + old_diameter: f64, + /// Target length of a single side of the world border, in meters. + new_diameter: f64, + /// Number of real-time milliseconds until New Diameter is reached. + /// It appears that Notchian server does not sync world border speed to game ticks, so it gets out of sync with server lag. + /// If the world border is not moving, this is set to 0. + speed: VarLong, + /// Resulting coordinates from a portal teleport are limited to ±value. + /// Usually 29999984. + portal_teleport_boundary: VarInt, + /// In meters + warning_blocks: VarInt, + /// In seconds as set by `/worldborder` warning time. + warning_time: VarInt, + }, + /// The server will frequently send out a keep-alive, each containing a random ID. /// The client must respond with the same payload (see [serverbound Keep Alive](https://wiki.vg/Protocol#Keep_Alive_.28serverbound.29)). /// If the client does not respond to them for over 30 seconds, the server kicks the client. From 3ff3e04a9cd49d5b804e699c646402db841c6ec7 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:36:15 +0200 Subject: [PATCH 05/52] Remove EntityMovement --- src/packets/play_clientbound.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 8d4df593..1723f301 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -555,14 +555,6 @@ pub enum ClientboundPacket<'a> { on_ground: bool, }, - /// This packet may be used to initialize an entity. - /// - /// For player entities, either this packet or any move/look packet is sent every game tick. - /// So the meaning of this packet is basically that the entity did not move/look since the last such packet. - EntityMovement { - entity_id: VarInt, - }, - /// Note that all fields use absolute positioning and do not allow for relative positioning. VehicleMove { /// Absolute position (X coordinate) From a7a706e4aaf7c94e9466d35bc749bcc93875dd98 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:38:05 +0200 Subject: [PATCH 06/52] Add UselessPacket --- src/packets/play_clientbound.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 1723f301..a87011a8 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -597,6 +597,12 @@ pub enum ClientboundPacket<'a> { location: Position, }, + /// Unknown what this packet does just yet, not used by the Notchian server or client. + /// Most likely added as a replacement to the removed window confirmation packet. + UselessPacket { + id: i32, + }, + // Todo make add doc links /// Response to the serverbound packet (Craft Recipe Request), with the same recipe ID. /// Appears to be used to notify the UI. From 41aacb4f8d64be7a8c116c265cd4688633c6e78a Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:41:16 +0200 Subject: [PATCH 07/52] Remove CombatEvent --- src/components/combat.rs | 20 -------------------- src/components/mod.rs | 1 - src/packets/play_clientbound.rs | 6 ------ 3 files changed, 27 deletions(-) delete mode 100644 src/components/combat.rs diff --git a/src/components/combat.rs b/src/components/combat.rs deleted file mode 100644 index 6ace7dcf..00000000 --- a/src/components/combat.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::*; - -#[derive(Debug, MinecraftPacketPart)] -#[discriminant(VarInt)] -pub enum CombatEvent<'a> { - EnterCombat, - EndCombat { - /// Length of the combat in ticks - duration: VarInt, - /// ID of the primary opponent of the ended combat, or -1 if there is no obvious primary opponent - opponent_entity_id: i32, - }, - EntityDead { - /// Entity ID of the player that died (should match the client's entity ID) - dead_entity_id: VarInt, - /// The killing entity's ID, or -1 if there is no obvious killer - killer_entity_id: i32, - death_message: Chat<'a>, - }, -} diff --git a/src/components/mod.rs b/src/components/mod.rs index fd393158..e69ed01d 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -5,7 +5,6 @@ pub mod blocks; pub mod boss_bar; pub mod chat; pub mod chunk; -pub mod combat; pub mod command_block; pub mod difficulty; pub mod effect; diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index a87011a8..535cf67c 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -622,12 +622,6 @@ pub enum ClientboundPacket<'a> { field_of_view_modifier: f32, }, - /// Originally used for metadata for twitch streaming circa 1.8. - /// Now only used to display the game over screen (with enter combat and end combat completely ignored by the Notchain client) - CombatEvent { - event: combat::CombatEvent<'a>, - }, - /// Sent by the server to update the user list ( in the client). PlayerInfo { value: players::PlayerInfoAction<'a>, From 50fc966a23bbc24bf0a9094efeebe21e54255c59 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:41:32 +0200 Subject: [PATCH 08/52] Add EndCombatEvent --- src/packets/play_clientbound.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 535cf67c..f1b546e4 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -622,6 +622,15 @@ pub enum ClientboundPacket<'a> { field_of_view_modifier: f32, }, + /// Unused by the Notchain client. + /// This data was once used for twitch.tv metadata circa 1.8.f + EndCombatEvent { + /// Length of the combat in ticks. + duration: VarInt, + /// ID of the primary opponent of the ended combat, or -1 if there is no obvious primary opponent. + entity_id: i32, + }, + /// Sent by the server to update the user list ( in the client). PlayerInfo { value: players::PlayerInfoAction<'a>, From e8989f9f5621a93a0b44105eff9b7069d7b59d68 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:42:15 +0200 Subject: [PATCH 09/52] Add EnterCombatEvent --- src/packets/play_clientbound.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index f1b546e4..b4fbaf8f 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -623,7 +623,7 @@ pub enum ClientboundPacket<'a> { }, /// Unused by the Notchain client. - /// This data was once used for twitch.tv metadata circa 1.8.f + /// This data was once used for twitch.tv metadata circa 1.8. EndCombatEvent { /// Length of the combat in ticks. duration: VarInt, @@ -631,6 +631,10 @@ pub enum ClientboundPacket<'a> { entity_id: i32, }, + /// Unused by the Notchain client. + /// This data was once used for twitch.tv metadata circa 1.8. + EnterCombatEvent, + /// Sent by the server to update the user list ( in the client). PlayerInfo { value: players::PlayerInfoAction<'a>, From 825f4011076506bb68942fa2f51b3129a0ce735d Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:43:25 +0200 Subject: [PATCH 10/52] Add DeathCombatEvent --- src/packets/play_clientbound.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index b4fbaf8f..61c99684 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -635,6 +635,16 @@ pub enum ClientboundPacket<'a> { /// This data was once used for twitch.tv metadata circa 1.8. EnterCombatEvent, + /// Used to send a respawn screen. + DeathCombatEvent { + /// Entity ID of the player that died (should match the client's entity ID) + player_id: VarInt, + /// The killing entity's ID, or -1 if there is no obvious killer + entity_id: i32, + /// The death message + message: Chat<'a>, + }, + /// Sent by the server to update the user list ( in the client). PlayerInfo { value: players::PlayerInfoAction<'a>, From c250df8b30df4c93854a05e39571a7546bac6388 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:44:13 +0200 Subject: [PATCH 11/52] Fix typo --- src/packets/play_clientbound.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 61c99684..c12162f4 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -696,7 +696,7 @@ pub enum ClientboundPacket<'a> { }, /// Sent by the server when a list of entities is to be destroyed on the client - DestoryEntities { + DestroyEntities { entity_ids: Array<'a, VarInt, VarInt>, }, From 011883a1e2dc1af48bdf33c78efe0936ca60bc0c Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:47:03 +0200 Subject: [PATCH 12/52] Add ActionBar --- src/packets/play_clientbound.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index c12162f4..6ab71f4c 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -770,6 +770,11 @@ pub enum ClientboundPacket<'a> { identifier: Option>, }, + /// Displays a message above the hotbar (the same as position 2 in Chat Message (clientbound). + ActionBar { + action_bar_text: Chat<'a>, + }, + WorldBorder { action: chunk::WorldBorderAction, }, From b4deb0c34eb6ce883d79a2e3793f8d0117d6d1ea Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:47:49 +0200 Subject: [PATCH 13/52] Remove WorldBorder --- src/components/chunk.rs | 49 --------------------------------- src/packets/play_clientbound.rs | 4 --- 2 files changed, 53 deletions(-) diff --git a/src/components/chunk.rs b/src/components/chunk.rs index cc35590f..f7c3c942 100644 --- a/src/components/chunk.rs +++ b/src/components/chunk.rs @@ -216,52 +216,3 @@ impl<'a> ChunkData<'a> { Ok(chunk_sections) } } - -#[derive(Debug, MinecraftPacketPart)] -#[discriminant(VarInt)] -pub enum WorldBorderAction { - SetSize { - /// Length of a single side of the world border, in meters - diameter: f64, - }, - LerpSize { - /// Current length of a single side of the world border, in meters - old_diameter: f64, - /// Target length of a single side of the world border, in meters - new_diameter: f64, - /// Number of real-time milliseconds until New Diameter is reached. - /// It appears that Notchian server does not sync world border speed to game ticks, so it gets out of sync with server lag. - /// If the world border is not moving, this is set to 0. - speed: VarLong, - }, - SetCenter { - x: f64, - z: f64, - }, - Initialize { - x: f64, - z: f64, - /// Current length of a single side of the world border, in meters - old_diameter: f64, - /// Target length of a single side of the world border, in meters - new_diameter: f64, - /// Number of real-time milliseconds until New Diameter is reached. - /// It appears that Notchian server does not sync world border speed to game ticks, so it gets out of sync with server lag. - /// If the world border is not moving, this is set to 0. - speed: VarLong, - /// Resulting coordinates from a portal teleport are limited to ±value. Usually 29999984. - portal_teleport_value: VarInt, - /// In meters - warning_blocks: VarInt, - /// In seconds as set by `/worldborder warning time` - warning_time: VarInt, - }, - SetWarningTime { - /// In seconds as set by `/worldborder warning time` - warning_time: VarInt, - }, - SetWarningBlocks { - /// In meters - warning_blocks: VarInt, - }, -} diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 6ab71f4c..da1309dc 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -775,10 +775,6 @@ pub enum ClientboundPacket<'a> { action_bar_text: Chat<'a>, }, - WorldBorder { - action: chunk::WorldBorderAction, - }, - /// Sets the entity that the player renders from. /// This is normally used when the player left-clicks an entity while in spectator mode. /// From 63f70d3bb6051d40dbef1f645130ea1cc969f172 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:48:21 +0200 Subject: [PATCH 14/52] Add WorldBorderCenter --- src/packets/play_clientbound.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index da1309dc..561e880c 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -775,6 +775,11 @@ pub enum ClientboundPacket<'a> { action_bar_text: Chat<'a>, }, + WorldBorderCenter { + x: f64, + y: f64, + }, + /// Sets the entity that the player renders from. /// This is normally used when the player left-clicks an entity while in spectator mode. /// From b1991daec34069e284903c0f047dc92b9a503527 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:49:31 +0200 Subject: [PATCH 15/52] Add WorldBorderLerpSize --- src/packets/play_clientbound.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 561e880c..49477ab3 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -780,6 +780,17 @@ pub enum ClientboundPacket<'a> { y: f64, }, + WorldBorderLerpSize { + /// Current length of a single side of the world border, in meters. + old_diameter: f64, + /// Target length of a single side of the world border, in meters. + new_diameter: f64, + /// Number of real-time milliseconds until New Diameter is reached. + /// It appears that Notchian server does not sync world border speed to game ticks, so it gets out of sync with server lag. + /// If the world border is not moving, this is set to 0. + speed: VarLong, + }, + /// Sets the entity that the player renders from. /// This is normally used when the player left-clicks an entity while in spectator mode. /// From 90dd1fe5f64362874ad88c4a9088a7dac7f48ad0 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:50:05 +0200 Subject: [PATCH 16/52] Add WorldBorderSize --- src/packets/play_clientbound.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 49477ab3..2ad506fb 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -781,9 +781,9 @@ pub enum ClientboundPacket<'a> { }, WorldBorderLerpSize { - /// Current length of a single side of the world border, in meters. + /// Current length of a single side of the world border, in meters old_diameter: f64, - /// Target length of a single side of the world border, in meters. + /// Target length of a single side of the world border, in meters new_diameter: f64, /// Number of real-time milliseconds until New Diameter is reached. /// It appears that Notchian server does not sync world border speed to game ticks, so it gets out of sync with server lag. @@ -791,6 +791,11 @@ pub enum ClientboundPacket<'a> { speed: VarLong, }, + WorldBorderSize { + /// Length of a single side of the world border, in meters + diameter: f64, + }, + /// Sets the entity that the player renders from. /// This is normally used when the player left-clicks an entity while in spectator mode. /// From c244e8b08f4273fbf1f2ce276a3e2478acfd83cc Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:51:09 +0200 Subject: [PATCH 17/52] Add WorldBorderWarningDelay --- src/packets/play_clientbound.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 2ad506fb..0c394184 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -392,7 +392,7 @@ pub enum ClientboundPacket<'a> { portal_teleport_boundary: VarInt, /// In meters warning_blocks: VarInt, - /// In seconds as set by `/worldborder` warning time. + /// In seconds as set by `/worldborder warning time` warning_time: VarInt, }, @@ -796,6 +796,11 @@ pub enum ClientboundPacket<'a> { diameter: f64, }, + WorldBorderWarningDelay { + /// In seconds as set by `/worldborder warning time` + warning_time: VarInt, + }, + /// Sets the entity that the player renders from. /// This is normally used when the player left-clicks an entity while in spectator mode. /// From fde309af647f9abae58d65592f5410aedef7752d Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:51:47 +0200 Subject: [PATCH 18/52] Add WorldBorderWarningReach --- src/packets/play_clientbound.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 0c394184..b27682f6 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -801,6 +801,11 @@ pub enum ClientboundPacket<'a> { warning_time: VarInt, }, + WorldBorderWarningReach { + /// In meters + warning_blocks: VarInt, + }, + /// Sets the entity that the player renders from. /// This is normally used when the player left-clicks an entity while in spectator mode. /// From 89ae51b5bc4588c9280b0013a4024f8fa2030913 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:53:15 +0200 Subject: [PATCH 19/52] Add SetTitleSubTitle --- src/packets/play_clientbound.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index b27682f6..b7155dc3 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -942,6 +942,10 @@ pub enum ClientboundPacket<'a> { score_action: teams::ScoreboardScoreAction<'a>, }, + SetTitleSubTitle { + subtitle_text: Chat<'a>, + }, + /// Time is based on ticks, where 20 ticks happen every second. /// There are 24000 ticks in a day, making Minecraft days exactly 20 minutes long. /// The time of day is based on the timestamp modulo 24000. 0 is sunrise, 6000 is noon, 12000 is sunset, and 18000 is midnight. From 08b0755d2d68927b99969fc412668cc78c5cbcee Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:54:26 +0200 Subject: [PATCH 20/52] Remove Title --- src/components/chat.rs | 27 --------------------------- src/packets/play_clientbound.rs | 6 +----- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/src/components/chat.rs b/src/components/chat.rs index dad351e8..36f43efa 100644 --- a/src/components/chat.rs +++ b/src/components/chat.rs @@ -20,30 +20,3 @@ pub enum ChatMode { CommandsOnly, Hidden, } - -#[derive(Debug, MinecraftPacketPart)] -pub enum TitleAction<'a> { - SetTitle { - title: Chat<'a>, - }, - SetSubtitle { - subtitle: Chat<'a>, - }, - SetActionBar { - /// Displays a message above the hotbar (the same as [Position::GameInfo] in [ClientboundPacket::ChatMessage], except that it correctly renders formatted chat; see MC-119145 for more information). - action_bar_text: Chat<'a>, - }, - SetTimes { - /// Ticks to spend fading in - fade_int: i32, - /// Ticks to keep the title displayed - stay: i32, - /// Ticks to spend out, not when to start fading out - fade_out: i32, - }, - /// Sending [TitleAction::Hide] once makes the text disappear. - /// Sending another time will make the text reappear. - Hide, - /// Erases the text - Reset, -} diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index b7155dc3..314e8eb1 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -957,11 +957,7 @@ pub enum ClientboundPacket<'a> { /// If negative the sun will stop moving. time_of_day: i64, }, - - Title { - action: chat::TitleAction<'a>, - }, - + /// Plays a sound effect from an entity EntitySoundEffect { /// ID of hardcoded sound event ([events](https://pokechu22.github.io/Burger/1.16.5.html#sounds) as of 1.16.5). From c368eca402dfb6c70cd80fbcafa3ba04c89aecd0 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:54:49 +0200 Subject: [PATCH 21/52] Add SetTitleText --- src/packets/play_clientbound.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 314e8eb1..e762b793 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -957,7 +957,11 @@ pub enum ClientboundPacket<'a> { /// If negative the sun will stop moving. time_of_day: i64, }, - + + SetTitleText { + title_text: Chat<'a>, + }, + /// Plays a sound effect from an entity EntitySoundEffect { /// ID of hardcoded sound event ([events](https://pokechu22.github.io/Burger/1.16.5.html#sounds) as of 1.16.5). From 04c4d5e5a1956a4d2a00d18dee448db5f200bac6 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:55:41 +0200 Subject: [PATCH 22/52] Add SetTitleTimes --- src/packets/play_clientbound.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index e762b793..5eee6d74 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -962,6 +962,15 @@ pub enum ClientboundPacket<'a> { title_text: Chat<'a>, }, + SetTitleTimes { + /// Ticks to spend fading in + fade_in: i32, + /// Ticks to keep the title displayed + stay: i32, + /// Ticks to spend out, not when to start fading out + fade_out: i32, + }, + /// Plays a sound effect from an entity EntitySoundEffect { /// ID of hardcoded sound event ([events](https://pokechu22.github.io/Burger/1.16.5.html#sounds) as of 1.16.5). From 192dfff2513dd1f4589d0a5400de06131238effd Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 17:56:40 +0200 Subject: [PATCH 23/52] Rename PlayerListHeaderAndFooter --- src/packets/play_clientbound.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 5eee6d74..c2ba165e 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -1011,7 +1011,7 @@ pub enum ClientboundPacket<'a> { /// This packet may be used by custom servers to display additional information above/below the player list. /// It is never sent by the Notchian server. - PlayerListSetHeaderAndFooter { + PlayerListHeaderAndFooter { /// To remove the header, send a empty text component: `{"text":""}` header: Chat<'a>, /// To remove the footer, send a empty text component: `{"text":""}` From 7e1d7104bc1bcbda8b58f20f21d912808e9808b5 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 18:00:45 +0200 Subject: [PATCH 24/52] Add UselessPacket --- src/packets/play_serverbound.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/packets/play_serverbound.rs b/src/packets/play_serverbound.rs index 72f20205..4aee2532 100644 --- a/src/packets/play_serverbound.rs +++ b/src/packets/play_serverbound.rs @@ -294,6 +294,13 @@ pub enum ServerboundPacket<'a> { flags: u8, }, + /// A response to the ping packet sync to the main thread. + /// Unknown what this is used for, this is ignored by the Notchian client and server. + /// Most likely added as a replacement to the removed window confirmation packet. + UselessPacket { + id: i32, + }, + /// Replaces Recipe Book Data, type 1. SetRecipeBookState { book: recipes::RecipeBook, From e52a422a1caad49a1d912479f3aaabe99dc2b5a2 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 18:02:44 +0200 Subject: [PATCH 25/52] Fix warning --- build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 5b782ff6..d00b55cc 100644 --- a/build.rs +++ b/build.rs @@ -562,7 +562,7 @@ mod entities { unknown_category => panic!("Unknown entity category {}", unknown_category), }; categories.push_str("EntityCategory::"); - categories.push_str(&variant_name); + categories.push_str(variant_name); categories.push_str(", "); } categories.push(']'); From e1f449e80275b35a1d809260899498f561771674 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 18:29:01 +0200 Subject: [PATCH 26/52] Update build.rs --- build.rs | 64 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/build.rs b/build.rs index d00b55cc..814c8930 100644 --- a/build.rs +++ b/build.rs @@ -3,8 +3,7 @@ use serde::{Deserialize, Serialize}; use std::io::{ErrorKind, Read, Write}; use std::{collections::HashMap, fs::File}; -/// Changing this is not enough, please also change static urls in main() since -const VERSION: &str = "1.16.5"; +const VERSION: &str = "1.17.1"; fn get_data(url: &str, cache: &str) -> serde_json::Value { match File::open(cache) { @@ -114,14 +113,18 @@ mod blocks { .map(|(k, _v)| k) .collect(), ); - raw_materials.push( - block - .material - .clone() - .unwrap_or_else(|| "unknown_material".to_string()) - .from_case(Case::Snake) - .to_case(Case::UpperCamel), - ); + let mut material = block + .material + .clone() + .unwrap_or_else(|| "unknown_material".to_string()) + .split(';') + .next() + .unwrap() + .to_string(); + if material.starts_with("mineable") { + material = "unknown_material".to_string(); + } + raw_materials.push(material.from_case(Case::Snake).to_case(Case::UpperCamel)); } // Generate the MaterialBlock enum and array @@ -420,7 +423,7 @@ mod items { items.sort_by_key(|item| item.id); // Look for missing items in the array - let mut expected = 0; + let mut expected = 1; for item in &items { if item.id != expected { panic!("The item with id {} is missing.", expected) @@ -504,7 +507,7 @@ const DISPLAY_NAMES: [&str; {max_value}] = {display_names:?}; const TEXT_IDS: [&str; {max_value}] = {text_ids:?}; "#, variants = variants, - max_value = expected, + max_value = expected - 1, max_stack_sizes = items.iter().map(|i| i.stack_size).collect::>(), durabilities = items.iter().map(|i| i.durability).collect::>(), display_names = items.iter().map(|i| &i.display_name).collect::>(), @@ -530,11 +533,12 @@ mod entities { display_name: String, width: f32, height: f32, + #[serde(rename = "type")] category: String, } pub fn generate_entity_enum(data: serde_json::Value) { - let mut entities: Vec = serde_json::from_value(data).expect("Invalid block data"); + let mut entities: Vec = serde_json::from_value(data).expect("Invalid entity data"); entities.sort_by_key(|entity| entity.id); // Look for missing items in the array @@ -551,14 +555,16 @@ mod entities { categories.push('['); for entity in &entities { let variant_name = match entity.category.as_str() { - "Passive mobs" => "Passive", - "Hostile mobs" => "Hostile", - "Vehicles" => "Vehicle", - "Immobile" => "Immobile", - "Projectiles" => "Projectile", - "Drops" => "Drop", - "Blocks" => "Block", - "UNKNOWN" => "Unknown", + "other" => "Other", + "living" => "Living", + "projectile" => "Projectile", + "animal" => "Animal", + "ambient" => "Ambient", + "hostile" => "Hostile", + "water_creature" => "WaterCreature", + "mob" => "Mob", + "passive" => "Passive", + "player" => "Player", unknown_category => panic!("Unknown entity category {}", unknown_category), }; categories.push_str("EntityCategory::"); @@ -590,14 +596,16 @@ pub enum Entity {{ #[derive(Debug, Clone, Copy, PartialEq)] pub enum EntityCategory {{ - Passive, - Hostile, + Other, + Living, Projectile, - Immobile, - Vehicle, - Drop, - Block, - Unknown + Animal, + Ambient, + Hostile, + WaterCreature, + Mob, + Passive, + Player, }} impl Entity {{ From 9df84240a3d2e08ded1936316d8d83ee10bbc1e9 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Mon, 13 Sep 2021 18:29:27 +0200 Subject: [PATCH 27/52] Cargo fmt --- src/components/entity.rs | 8 ++++++-- src/packets/play_clientbound.rs | 9 ++++----- src/packets/play_serverbound.rs | 10 +++------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/components/entity.rs b/src/components/entity.rs index ba015140..38e3d6ba 100644 --- a/src/components/entity.rs +++ b/src/components/entity.rs @@ -180,8 +180,12 @@ mod tests { #[test] fn test_entity_metadata() { - let t1 = [/*68, 160, 129, 2, */0, 0, 0, 2, 5, 0, 6, 18, 0, 4, 7, 0, 15, 13, 10, 14, 0, 0, 12, 1, 0, 13, 10, 0, 8, 2, 66, 32, 0, 0, 9, 1, 0, 11, 1, 0, 10, 7, 0, 1, 1, 172, 2, 3, 7, 0, 7, 0, 0, 5, 7, 0, 16, 7, 0, 17, 7, 0, 255]; - let t2 = [/*68, 219, 242, 1, */15, 13, 68, 255]; + let t1 = [ + /*68, 160, 129, 2, */ 0, 0, 0, 2, 5, 0, 6, 18, 0, 4, 7, 0, 15, 13, 10, 14, 0, 0, + 12, 1, 0, 13, 10, 0, 8, 2, 66, 32, 0, 0, 9, 1, 0, 11, 1, 0, 10, 7, 0, 1, 1, 172, 2, 3, + 7, 0, 7, 0, 0, 5, 7, 0, 16, 7, 0, 17, 7, 0, 255, + ]; + let t2 = [/*68, 219, 242, 1, */ 15, 13, 68, 255]; EntityMetadata::deserialize_uncompressed_minecraft_packet(&t1).unwrap(); EntityMetadata::deserialize_uncompressed_minecraft_packet(&t2).unwrap(); diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index c2ba165e..83280532 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -1,10 +1,10 @@ #[allow(unused_imports)] use super::play_serverbound::ServerboundPacket; -use crate::nbt::NbtTag; +use super::*; use crate::components::*; -use crate::ids::*; use crate::ids::blocks; -use super::*; +use crate::ids::*; +use crate::nbt::NbtTag; #[derive(Debug, MinecraftPacketPart)] #[discriminant(VarInt)] @@ -1064,8 +1064,7 @@ pub enum ClientboundPacket<'a> { advancement_mapping: Map<'a, Identifier<'a>, advancements::Advancement<'a>, VarInt>, /// The identifiers of the advancements that should be removed advancements_to_remove: Array<'a, Identifier<'a>, VarInt>, - progress_mapping: - Map<'a, Identifier<'a>, advancements::AdvancementProgress<'a>, VarInt>, + progress_mapping: Map<'a, Identifier<'a>, advancements::AdvancementProgress<'a>, VarInt>, }, /// Sets [attributes](https://minecraft.fandom.com/wiki/Attribute) on the given entity diff --git a/src/packets/play_serverbound.rs b/src/packets/play_serverbound.rs index 4aee2532..12779f20 100644 --- a/src/packets/play_serverbound.rs +++ b/src/packets/play_serverbound.rs @@ -1,7 +1,7 @@ #[allow(unused_imports)] use super::play_clientbound::ClientboundPacket; -use crate::components::*; use super::*; +use crate::components::*; #[derive(Debug, MinecraftPacketPart)] #[discriminant(VarInt)] @@ -37,9 +37,7 @@ pub enum ServerboundPacket<'a> { }, /// *Request for [ClientboundPacket::Statistics]* - ClientStatus { - action: game_state::ClientStatus, - }, + ClientStatus { action: game_state::ClientStatus }, /// Sent when the player connects, or when settings are changed ClientSettings { @@ -297,9 +295,7 @@ pub enum ServerboundPacket<'a> { /// A response to the ping packet sync to the main thread. /// Unknown what this is used for, this is ignored by the Notchian client and server. /// Most likely added as a replacement to the removed window confirmation packet. - UselessPacket { - id: i32, - }, + UselessPacket { id: i32 }, /// Replaces Recipe Book Data, type 1. SetRecipeBookState { From b88da33509762985095167f9199ade3cc2ad3935 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 16 Sep 2021 18:59:45 +0200 Subject: [PATCH 28/52] Fix ChunkData --- src/components/chunk.rs | 45 ++++++++++++++++++++------------------ test_data/chunk.mc_packet | Bin 0 -> 12031 bytes 2 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 test_data/chunk.mc_packet diff --git a/src/components/chunk.rs b/src/components/chunk.rs index f7c3c942..e2ef963d 100644 --- a/src/components/chunk.rs +++ b/src/components/chunk.rs @@ -9,15 +9,16 @@ pub struct ChunkData<'a> { pub chunk_x: i32, /// Chunk coordinate (block coordinate divided by 16, rounded down). pub chunk_z: i32, - /// Bitmask with bits set to 1 for every 16×16×16 chunk section whose data is included in Data. - /// The least significant bit represents the chunk section at the bottom of the chunk column (from y=0 to y=15). - pub primary_bit_mask: VarInt, - /// Compound containing one long array named `MOTION_BLOCKING`, which is a heightmap for the highest solid block at each position in the chunk (as a compacted long array with 256 entries at 9 bits per entry totaling 36 longs). The Notchian server also adds a `WORLD_SURFACE` long array, the purpose of which is unknown, but it's not required for the chunk to be accepted. + /// BitSet with bits (world height in blocks / 16) set to 1 for every 16×16×16 chunk section whose data is included in Data. + /// The least significant bit represents the chunk section at the bottom of the chunk column (from the lowest y to 15 blocks above). + pub primary_bit_mask: Array<'a, u64, VarInt>, + /// Compound containing one long array named `MOTION_BLOCKING`, which is a heightmap for the highest solid block at each position in the chunk (as a compacted long array with 256 entries at 9 bits per entry totaling 36 longs). + /// The Notchian server also adds a `WORLD_SURFACE` long array, the purpose of which is unknown, but it's not required for the chunk to be accepted. pub heightmaps: NbtTag, /// 1024 biome IDs, ordered by x then z then y, in 4×4×4 blocks. /// Biomes cannot be changed unless a chunk is re-sent. /// The structure is an array of 1024 integers, each representing a [Biome ID](http://minecraft.gamepedia.com/Biome/ID) (it is recommended that "Void" is used if there is no set biome - its default id is 127). The array is ordered by x then z then y, in 4×4×4 blocks. The array is indexed by `((y >> 2) & 63) << 4 | ((z >> 2) & 3) << 2 | ((x >> 2) & 3)`. - pub biomes: Option>, + pub biomes: Array<'a, VarInt, VarInt>, /// The data section of the packet contains most of the useful data for the chunk. /// The number of elements in the array is equal to the number of bits set in [ChunkData::primary_bit_mask]. /// Sections are sent bottom-to-top, i.e. the first section, if sent, extends from Y=0 to Y=15. @@ -34,15 +35,10 @@ impl<'a> MinecraftPacketPart<'a> for ChunkData<'a> { fn serialize_minecraft_packet_part(self, output: &mut Vec) -> Result<(), &'static str> { self.chunk_x.serialize_minecraft_packet_part(output)?; self.chunk_z.serialize_minecraft_packet_part(output)?; - self.biomes - .is_some() - .serialize_minecraft_packet_part(output)?; self.primary_bit_mask .serialize_minecraft_packet_part(output)?; self.heightmaps.serialize_minecraft_packet_part(output)?; - if let Some(biomes) = self.biomes { - biomes.serialize_minecraft_packet_part(output)?; - } + self.biomes.serialize_minecraft_packet_part(output)?; VarInt(self.data.len() as i32).serialize_minecraft_packet_part(output)?; output.extend_from_slice(self.data); self.entities.serialize_minecraft_packet_part(output)?; @@ -54,18 +50,11 @@ impl<'a> MinecraftPacketPart<'a> for ChunkData<'a> { ) -> Result<(Self, &'a [u8]), &'static str> { let (chunk_x, input) = MinecraftPacketPart::deserialize_minecraft_packet_part(input)?; let (chunk_z, input) = MinecraftPacketPart::deserialize_minecraft_packet_part(input)?; - let (full_chunk, input) = MinecraftPacketPart::deserialize_minecraft_packet_part(input)?; let (primary_bit_mask, input) = MinecraftPacketPart::deserialize_minecraft_packet_part(input)?; let (heightmaps, input) = MinecraftPacketPart::deserialize_minecraft_packet_part(input)?; - let (biomes, input) = match full_chunk { - false => (None, input), - true => { - let (biomes, input) = + let (biomes, input) = >::deserialize_minecraft_packet_part(input)?; - (Some(biomes), input) - } - }; let (data_len, input) = VarInt::deserialize_minecraft_packet_part(input)?; let data_len = std::cmp::max(data_len.0, 0) as usize; let (data, input) = input.split_at(data_len); @@ -109,9 +98,12 @@ impl<'a> ChunkData<'a> { &mut self, ) -> Result<[Option; 16], &'static str> { let mut input = self.data; - let primary_bit_mask: u32 = unsafe { + if self.primary_bit_mask.items.len() != 1 { + return Err("ChunkData::deserialize_chunk_sections: primary_bit_mask.items.len() != 1"); + } + let primary_bit_mask: u64 = unsafe { // We don't care the type since we only want to check the bits - std::mem::transmute(self.primary_bit_mask.0) + std::mem::transmute(self.primary_bit_mask.items.get_unchecked(0)) }; let mut chunk_sections: [Option; 16] = [ @@ -216,3 +208,14 @@ impl<'a> ChunkData<'a> { Ok(chunk_sections) } } + +#[cfg(test)] +#[test] +fn test() { + let chunk_data = &include_bytes!("../../test_data/chunk.mc_packet")[1..]; + + let mut chunk_data_deserialized = ChunkData::deserialize_uncompressed_minecraft_packet(chunk_data).unwrap(); + let _blocks = chunk_data_deserialized.deserialize_chunk_sections().unwrap(); + + //println!("{:?}", chunk_data_deserialized); +} diff --git a/test_data/chunk.mc_packet b/test_data/chunk.mc_packet new file mode 100644 index 0000000000000000000000000000000000000000..9956f91f56e7ca342bea1015e5118720298a767a GIT binary patch literal 12031 zcmeHNPi!1l8DASyQWF}#-JM;hNTf4yhfn4;G%8hvCS9 zcmDbDC!hY}$JY|pWei^7+t%muT`(r zb-iXIYj?j!YkSbzy}Q*l`+#aUXY1XmcD3vKCidE11@j(962atOb>!A^aziTm;gQuZ)Io!BKXXohwS0SoZe?Wa?ApE(7Ol|&j&#m zAvK@<0SnALhV_UhWIpT$iy;Pageh=Cx)}o2qX-hMCOm?8J^;I?KKmim7-#+#QG4zX z8EhDHN>FAr1KI=wnwEMj7Nf@57lfhk87?Q)rg$?Gpqv)4eUh>N?ZSbt+mn07_{KtW-VkogpOlXYv zqVir;K0|+34TRulo((jbzTp{{6rs7G{L8CkKkobM{uwDsxm-W4(C?&9;c)N6 zQ`orPU%DqeK;bnsgI9+UL08X>Q0|~25^QKhKjnYLDE#-7x6H%%4F}@pBArtlux&5M zBFklCb+&97RQT0(({%tTEga;`f9{3t8Mn7y((sow4_q0YU^qPA7Hya-zd}wmIeYw^ zqTfY!JO#6J3pq?ZAlth07Y=+f@%7r+!u;6}((AVra#)of~Vzg5>6Wr#;92X#u^(D|0fpc6|K{xro=?wTpkWe$+I zk^C-^IEi8<>L{Y3B@XSPD}yVQ2{dd<9O4B(0L09XJgTh;JsJ`OPE($Wq@M13wsIP` zi))I-HIa*geXbNT$Q4@^emDrfAp3(+Pzbp|2Yas)f7sde1#ozM&c*A zUG(|8WG8+a5Itoh*?xb#|aTiMkSgZ{3$X7PY?4J6bYbrgoxA1DT~DNXy$w!TzUoN zXeMboUtGnd!(SVN1gnGw9a2GRn8`I`1bEvX9=O7RY&l1OGClA2o*-Mb4RAUKHd)Xi20rIMxpQ;eg;}+yqi#ODQrIu zE0`dUE`Cj=$qKaf5QzJROPA~YyUQdt@#_3iuUjndih+2km1y@Z_*vJ|JVe4a7f*ZY z^ikr-QxI+P#Xs zwgR^jMU>~UjyHqPSQljS3W6W8oDI~BI7cNrV5bw4Z`2W9LQ;HtMR0|TyJ!-(&(BjU z{9RIjpAkc)bBB#rF@Ik8^A)MXa2^T88PVv?4W9s62yv1cvL zb(%^CBq8_}8F<~2g7mb0w49$+t(C#eNt=W2(>!nCQ%a~9(dJx%LcS=`w;N%Crwf%I zk6HVVMt3g2ZzUUv4ldUuh9=-G#rU{7vN-R9?(PGky#pk&ck#R8V|PkM)v6D*%SB zfp~L}ab;cFCB!=gejs#OKTeCe5U=6QbapOcGk!b}He;$kH$&kz9$=fyKz(GliyJb5 z`)YoT7`!0t#Az~LfHzK4OcKNkN(Xn*Y})8_>N9B!jB&tGdO*ZNdW;wjBJ)GLncp7z zas#;=IIDQUv-Ea{Ja7-84hRe+{yHJ}qnXq(M#9fUgd3xar++D{0?JbB;rGFhcWc{y z^je1|_u2Ffx{+ZAf1Z#Y_l!6Yt#dK)iS(2ZRhtiIN%oiWnL~Jx3c_YIpP~se}uwy z(Df2;S9WFja|d})+e&V$yiceXC(!;Qd*LWvyBFv7ix zv<8ytpWNC7mK24=LKlr5M1mccq3X;p7{k1Eqde#dt0=sY@PhzfBk)g)OQ;+0i0}d< zx-J2pEr$`Ao^gkfi15!Q8FY=#Y~q?2pbq#NL?2)j7}uOT2pPcGdpu%wFCr(jAzaTy z-CGn~KM!2PzuzcEglP508H5Yb53;D;9~ob}eVg(2XoPutyUa)bbBYHnC>&;Sc(T4C zdQ!tdfcjnWzpi;0zNPYOf8qSV^VWaOe^=TD!{2WU-hQKs@87+>!7}{=ANlQo@2lPL zjUkyt#9{LW{x7K7Kd9Tfee}_Q?U6?Y#9D1w9x~T#y5XSxyA%6rZyxH6y)w2w_WIcG z$9_9@o&LwSo!Zn?-^2V5t7=x!WV82Y)M_@J@t9gpK2~+vc3kCOR8Qj@<~Msa;Dhp4 zV-1fdRPmKz!zTHfXvUW>^WfxTs@UYXE5**d7VfePzN)mxo>hg}zi9l)7fm?CcX>$O Q6Za~xSAo3>eAz1SAJ!#rPyhe` literal 0 HcmV?d00001 From d0fb9ad35edc5fdb40e85080d316cda219c49e60 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 16 Sep 2021 19:08:34 +0200 Subject: [PATCH 29/52] Fix Tags --- src/components/mod.rs | 1 + src/components/tags.rs | 11 +++++++++++ src/packets/play_clientbound.rs | 11 +++-------- 3 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 src/components/tags.rs diff --git a/src/components/mod.rs b/src/components/mod.rs index e69ed01d..f682d6aa 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -18,5 +18,6 @@ pub mod recipes; pub mod resource_pack; pub mod slots; pub mod sound; +pub mod tags; pub mod teams; pub mod trades; diff --git a/src/components/tags.rs b/src/components/tags.rs new file mode 100644 index 00000000..fef324bb --- /dev/null +++ b/src/components/tags.rs @@ -0,0 +1,11 @@ +use crate::*; + +/// A tag +/// +/// More information on tags is available at: https://minecraft.gamepedia.com/Tag +/// And a list of all tags is here: https://minecraft.gamepedia.com/Tag#List_of_tags +#[derive(Debug, MinecraftPacketPart)] +pub struct Tag<'a> { + pub tag_name: Identifier<'a>, + pub data: Array<'a, VarInt, VarInt>, +} diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 83280532..bd7f266d 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -1096,13 +1096,8 @@ pub enum ClientboundPacket<'a> { }, Tags { - /// A map linking block tags to an array of corresponding IDs. - block_tags: Map<'a, Identifier<'a>, Array<'a, VarInt, VarInt>, VarInt>, - /// A map linking item tags to an array of corresponding IDs. - item_tags: Map<'a, Identifier<'a>, Array<'a, VarInt, VarInt>, VarInt>, - /// A map linking fluid tags to an array of corresponding IDs. - fluid_tags: Map<'a, Identifier<'a>, Array<'a, VarInt, VarInt>, VarInt>, - /// A map linking entity tags to an array of corresponding IDs. - entity_tags: Map<'a, Identifier<'a>, Array<'a, VarInt, VarInt>, VarInt>, + /// More information on tags is available at: https://minecraft.gamepedia.com/Tag + /// And a list of all tags is here: https://minecraft.gamepedia.com/Tag#List_of_tags + tags: Map<'a, Identifier<'a>, Array<'a, tags::Tag<'a>, VarInt>, VarInt>, }, } From e73c08597d99727b01f94d5f70031348c4973e42 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 16 Sep 2021 19:14:20 +0200 Subject: [PATCH 30/52] Update PlayerPositionAndLook --- src/packets/play_clientbound.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index bd7f266d..53fc3172 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -689,6 +689,8 @@ pub enum ClientboundPacket<'a> { pitch: f32, flags: u8, teleport_id: VarInt, + /// True if the player should dismount their vehicle + dismount_vehicle: bool, }, UnlockRecipes { From e2c8ee55e3fb93f1351688ea4211641da9fdc694 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 16 Sep 2021 19:24:24 +0200 Subject: [PATCH 31/52] Update EntityAttributes --- src/components/entity.rs | 1 + src/network.rs | 2 +- src/packets/play_clientbound.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/entity.rs b/src/components/entity.rs index 38e3d6ba..f80cbe2e 100644 --- a/src/components/entity.rs +++ b/src/components/entity.rs @@ -28,6 +28,7 @@ pub enum EntityAttributeModifierOperation { /// `value = base_value * modifier` Multiply, } + #[derive(Debug, MinecraftPacketPart)] #[discriminant(VarInt)] pub enum EntityInteraction { diff --git a/src/network.rs b/src/network.rs index e1f9aa58..e8ea02e9 100644 --- a/src/network.rs +++ b/src/network.rs @@ -97,7 +97,7 @@ mod tests { send_packet( &mut stream, crate::packets::handshake::ServerboundPacket::Hello { - protocol_version: 754.into(), + protocol_version: 756.into(), server_address: "127.0.0.1", server_port: 25565, next_state: crate::packets::ConnectionState::Login, diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 53fc3172..62ca0c12 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -1076,7 +1076,7 @@ pub enum ClientboundPacket<'a> { /// [Attributes](entity::EntityAttribute) also have [Attributes](entity::EntityAttributeModifier) that adjust the strength of their effect. /// /// [More information](https://minecraft.fandom.com/wiki/Attribute) - attributes: Map<'a, Identifier<'a>, entity::EntityAttribute<'a>, i32>, + attributes: Map<'a, Identifier<'a>, entity::EntityAttribute<'a>, VarInt>, }, EntityEffect { From 859733202e044125bab56038d4b076db051a190c Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 16 Sep 2021 19:27:16 +0200 Subject: [PATCH 32/52] Update ClientSettings --- src/packets/play_serverbound.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/packets/play_serverbound.rs b/src/packets/play_serverbound.rs index 12779f20..171022f4 100644 --- a/src/packets/play_serverbound.rs +++ b/src/packets/play_serverbound.rs @@ -51,6 +51,9 @@ pub enum ServerboundPacket<'a> { /// Bit mask, see [the wiki](https://wiki.vg/Protocol#Client_Settings) displayed_skin_parts: u8, main_hand: slots::MainHand, + /// Disables filtering of text on signs and written book titles. + /// Currently always true (i.e. the filtering is disabled) + disable_text_filtering: bool, }, /// *Request for [ClientboundPacket::TabComplete]* From 4d0f4a73326a32ee11d4008a76827881d0600e72 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 16 Sep 2021 19:28:26 +0200 Subject: [PATCH 33/52] Update SpawnPosition --- src/packets/play_clientbound.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 62ca0c12..3312c97d 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -851,6 +851,8 @@ pub enum ClientboundPacket<'a> { /// It can be sent at any time to update the point compasses point at. SpawnPosition { location: Position, + /// The angle at which to respawn at + angle: f32, }, /// This is sent to the client when it should display a scoreboard From b95b2e2f825cf6c12eaef4a57b2085bcaa0af983 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 16 Sep 2021 19:32:35 +0200 Subject: [PATCH 34/52] Update WindowItems --- src/packets/play_clientbound.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 3312c97d..dbce01ae 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -236,9 +236,13 @@ pub enum ClientboundPacket<'a> { WindowItems { /// The ID of window which items are being sent for. 0 for player inventory. window_id: i8, + /// The last recieved State ID from either a [ClientboundPacket::SetSlot] or a [ClientboundPacket::WindowItems] packet + state_id: VarInt, /// The [slots::Slot]s in this window. /// See [inventory windows](https://wiki.vg/Inventory#Windows) for further information about how slots are indexed. - slots: Array<'a, slots::Slot, i16>, + slots: Array<'a, slots::Slot, VarInt>, + /// Item held by player + carried_item: slots::Slot, }, /// This packet is used to inform the client that part of a GUI window should be updated. From d9bcae1920a3cd58acf4b182bc30ca56e9b3ab5f Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 16 Sep 2021 21:08:48 +0200 Subject: [PATCH 35/52] Fix chunk data --- src/components/chunk.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/chunk.rs b/src/components/chunk.rs index e2ef963d..1ef9c68f 100644 --- a/src/components/chunk.rs +++ b/src/components/chunk.rs @@ -113,6 +113,12 @@ impl<'a> ChunkData<'a> { let mut mask = 0b1; for y in 0..16 { chunk_sections[y] = if primary_bit_mask & mask != 0 { + if input.is_empty() { + // No idea why this is necessary + //println!("nothing left {:b} {:b}", primary_bit_mask, primary_bit_mask & mask); + return Ok(chunk_sections); + } + let (block_count, new_input) = i16::deserialize_minecraft_packet_part(input)?; let (mut bits_per_block, new_input) = u8::deserialize_minecraft_packet_part(new_input)?; From fd2728db0fc3e89a9ae02f8ddae4f8f0b6cfefe9 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Thu, 16 Sep 2021 21:33:13 +0200 Subject: [PATCH 36/52] Add pose variant --- src/components/entity.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/entity.rs b/src/components/entity.rs index f80cbe2e..3ce11d44 100644 --- a/src/components/entity.rs +++ b/src/components/entity.rs @@ -70,6 +70,7 @@ pub enum Pose { SpinAttack, Sneaking, Dying, + LongJumping, } #[derive(Debug, Clone)] From a908617f89cb9f5937b7a21239eb19756f7537fb Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Sat, 18 Sep 2021 12:04:13 +0200 Subject: [PATCH 37/52] Fix bug --- src/components/chunk.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/chunk.rs b/src/components/chunk.rs index 1ef9c68f..79d9400e 100644 --- a/src/components/chunk.rs +++ b/src/components/chunk.rs @@ -103,7 +103,7 @@ impl<'a> ChunkData<'a> { } let primary_bit_mask: u64 = unsafe { // We don't care the type since we only want to check the bits - std::mem::transmute(self.primary_bit_mask.items.get_unchecked(0)) + *self.primary_bit_mask.items.get_unchecked(0) }; let mut chunk_sections: [Option; 16] = [ @@ -112,13 +112,7 @@ impl<'a> ChunkData<'a> { ]; let mut mask = 0b1; for y in 0..16 { - chunk_sections[y] = if primary_bit_mask & mask != 0 { - if input.is_empty() { - // No idea why this is necessary - //println!("nothing left {:b} {:b}", primary_bit_mask, primary_bit_mask & mask); - return Ok(chunk_sections); - } - + chunk_sections[y] = if primary_bit_mask & mask != 0 { let (block_count, new_input) = i16::deserialize_minecraft_packet_part(input)?; let (mut bits_per_block, new_input) = u8::deserialize_minecraft_packet_part(new_input)?; From da8c27260427381a1f5dc1337b114ccb79defc5f Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Sat, 18 Sep 2021 12:41:06 +0200 Subject: [PATCH 38/52] Update SetSlot --- src/packets/play_clientbound.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index dbce01ae..6cca49ca 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -236,7 +236,7 @@ pub enum ClientboundPacket<'a> { WindowItems { /// The ID of window which items are being sent for. 0 for player inventory. window_id: i8, - /// The last recieved State ID from either a [ClientboundPacket::SetSlot] or a [ClientboundPacket::WindowItems] packet + /// The last received State ID from either a [ClientboundPacket::SetSlot] or a [ClientboundPacket::WindowItems] packet state_id: VarInt, /// The [slots::Slot]s in this window. /// See [inventory windows](https://wiki.vg/Inventory#Windows) for further information about how slots are indexed. @@ -269,6 +269,8 @@ pub enum ClientboundPacket<'a> { /// This packet will only be sent for the currently opened window while the player is performing actions, even if it affects the player inventory. /// After the window is closed, a number of these packets are sent to update the player's inventory window (0). window_id: i8, + /// The last received State ID from either a [ClientboundPacket::SetSlot] or a [ClientboundPacket::WindowItems] packet + state_id: VarInt, /// The slot that should be updated. slot_index: i16, slot_value: slots::Slot, From 6cb98434f9aa3a643fc15e9847fb8e0a4f0986bf Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Sat, 18 Sep 2021 13:52:02 +0200 Subject: [PATCH 39/52] Add WindowType --- src/components/slots.rs | 29 +++++++++++++++++++++++++++++ src/packets/play_clientbound.rs | 3 +-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/components/slots.rs b/src/components/slots.rs index feb89727..1d47f798 100644 --- a/src/components/slots.rs +++ b/src/components/slots.rs @@ -104,6 +104,35 @@ impl<'a> MinecraftPacketPart<'a> for EquipmentSlotArray { } } +#[minecraft_enum(VarInt)] +#[derive(Debug)] +pub enum WindowType { + OneRow, + TwoRows, + ThreeRows, + FourRows, + FiveRows, + SixRows, + ThreeByThree, + Anvil, + Beacon, + BlastFurnace, + BrewingStand, + Crafting, + Enchantment, + Furnace, + GrindStone, + Hopper, + Lectern, + Loom, + Merchant, + ShulkerBox, + Smithing, + Smoker, + Cartography, + StoneCutter, +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 6cca49ca..fae76ffa 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -589,8 +589,7 @@ pub enum ClientboundPacket<'a> { /// Notchian server implementation is a counter, starting at 1. window_id: VarInt, /// The window type to use for display. - /// TODO: replace by an enum - window_type: VarInt, + window_type: slots::WindowType, /// The title of the window window_title: Chat<'a>, }, From be9fcd633d37012d2e777aebb13cbfcea77771ea Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Sat, 18 Sep 2021 14:06:38 +0200 Subject: [PATCH 40/52] Update ClickWindowSlot --- src/packets/play_clientbound.rs | 4 ++-- src/packets/play_serverbound.rs | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index fae76ffa..a61014be 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -236,7 +236,7 @@ pub enum ClientboundPacket<'a> { WindowItems { /// The ID of window which items are being sent for. 0 for player inventory. window_id: i8, - /// The last received State ID from either a [ClientboundPacket::SetSlot] or a [ClientboundPacket::WindowItems] packet + /// A state id required for future [ServerboundPacket::ClickWindowSlot] state_id: VarInt, /// The [slots::Slot]s in this window. /// See [inventory windows](https://wiki.vg/Inventory#Windows) for further information about how slots are indexed. @@ -269,7 +269,7 @@ pub enum ClientboundPacket<'a> { /// This packet will only be sent for the currently opened window while the player is performing actions, even if it affects the player inventory. /// After the window is closed, a number of these packets are sent to update the player's inventory window (0). window_id: i8, - /// The last received State ID from either a [ClientboundPacket::SetSlot] or a [ClientboundPacket::WindowItems] packet + /// A state id required for future [ServerboundPacket::ClickWindowSlot] state_id: VarInt, /// The slot that should be updated. slot_index: i16, diff --git a/src/packets/play_serverbound.rs b/src/packets/play_serverbound.rs index 171022f4..927c3a29 100644 --- a/src/packets/play_serverbound.rs +++ b/src/packets/play_serverbound.rs @@ -77,15 +77,19 @@ pub enum ServerboundPacket<'a> { ClickWindowSlot { /// The ID of the window which was clicked. 0 for player inventory. window_id: i8, + /// The last received State ID from either a [ClientboundPacket::SetSlot] or a [ClientboundPacket::WindowItems] packet + state_id: VarInt, /// The clicked slot number, see [the wiki](https://wiki.vg/Protocol#Click_Window) slot: i16, /// The button used in the click, see [the wiki](https://wiki.vg/Protocol#Click_Window) button: u8, - /// A unique number for the action, implemented by Notchian as a counter, starting at 1 (different counter for every window ID). Used by the server to send back a [ClientboundPacket::WindowConfirmation]. - action_id: i16, /// Inventory operation mode, see [the wiki](https://wiki.vg/Protocol#Click_Window) mode: VarInt, - /// The clicked slot. Has to be empty (item ID = -1) for drop mode. (TODO: check this) + /// New values for affected slots + new_slot_values: Map<'a, i16, slots::Slot, VarInt>, + /// The clicked slot + /// Has to be empty (item ID = -1) for drop mode. (TODO: check this) + /// Is always empty for mode 2 and mode 5 packets. clicked_item: slots::Slot, }, From 3a82be262bbd69fc86611ccbf88cd57564c1dba4 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Sat, 18 Sep 2021 14:37:46 +0200 Subject: [PATCH 41/52] Add From for Array and Map --- src/packets/mod.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/packets/mod.rs b/src/packets/mod.rs index cfd70204..56e2828f 100644 --- a/src/packets/mod.rs +++ b/src/packets/mod.rs @@ -84,6 +84,17 @@ pub struct Array<'a, T: MinecraftPacketPart<'a> + std::fmt::Debug, U: MinecraftP pub items: Vec, } +impl<'a, T: std::fmt::Debug + MinecraftPacketPart<'a>, U: MinecraftPacketPart<'a>> From> + for Array<'a, T, U> +{ + fn from(value: Vec) -> Self { + Array { + _len_prefix: std::marker::PhantomData, + items: value, + } + } +} + #[derive(Debug)] pub struct Map< 'a, @@ -95,6 +106,21 @@ pub struct Map< pub items: std::collections::BTreeMap, } +impl< + 'a, + K: std::fmt::Debug + MinecraftPacketPart<'a>, + V: std::fmt::Debug + MinecraftPacketPart<'a>, + U: MinecraftPacketPart<'a>, + > From> for Map<'a, K, V, U> +{ + fn from(value: std::collections::BTreeMap) -> Self { + Map { + _len_prefix: std::marker::PhantomData, + items: value, + } + } +} + /// The possible packets are different for each state. #[minecraft_enum(VarInt)] #[derive(Debug)] From efc0ee7e0e6bd48e67327312f0cd3b22d977c77c Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Sun, 19 Sep 2021 01:37:39 +0200 Subject: [PATCH 42/52] Add Block state id support --- build.rs | 384 ++++++++++++++++++++++++++++++-- src/ids/.gitignore | 1 + src/ids/mod.rs | 1 + src/packets/play_clientbound.rs | 8 +- 4 files changed, 374 insertions(+), 20 deletions(-) diff --git a/build.rs b/build.rs index 814c8930..e91b2af3 100644 --- a/build.rs +++ b/build.rs @@ -65,6 +65,106 @@ fn get_data(url: &str, cache: &str) -> serde_json::Value { mod blocks { use super::*; + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct BlockState { + name: String, + #[serde(rename = "type")] + ty: String, + num_values: usize, + values: Option>, + } + + impl BlockState { + fn ty(&self, block_name: &str, competing_definitions: bool) -> String { + match self.ty.as_str() { + "int" => { + let values: Vec = self + .values + .as_ref() + .expect("No values for int block state") + .iter() + .map(|v| v.parse().expect("Invalid block state value: expected int")) + .collect(); + let mut min_value: i128 = *values.first().unwrap_or(&0); + let mut max_value: i128 = *values.first().unwrap_or(&0); + + for value in values { + if value < min_value { + min_value = value; + } + if value > max_value { + max_value = value; + } + } + + if min_value >= u8::MIN as i128 && max_value <= u8::MAX as i128 { + return String::from("u8"); + } + if min_value >= i8::MIN as i128 && max_value <= i8::MAX as i128 { + return String::from("i8"); + } + if min_value >= u16::MIN as i128 && max_value <= u16::MAX as i128 { + return String::from("u16"); + } + if min_value >= i16::MIN as i128 && max_value <= i16::MAX as i128 { + return String::from("i16"); + } + if min_value >= u32::MIN as i128 && max_value <= u32::MAX as i128 { + return String::from("u32"); + } + if min_value >= i32::MIN as i128 && max_value <= i32::MAX as i128 { + return String::from("i32"); + } + if min_value >= u64::MIN as i128 && max_value <= u64::MAX as i128 { + return String::from("u64"); + } + if min_value >= i64::MIN as i128 && max_value <= i64::MAX as i128 { + return String::from("i64"); + } + String::from("i128") + } + "enum" => match competing_definitions { + true => format!("{}_{}", block_name, self.name), + false => self.name.to_string(), + } + .from_case(Case::Snake) + .to_case(Case::UpperCamel), + "bool" => String::from("bool"), + _ => unimplemented!(), + } + } + + fn define_enum(&self, block_name: &str, competing_definitions: bool) -> String { + if self.ty.as_str() != "enum" { + panic!("Called defined enum on non-enum"); + } + + let mut variants = String::new(); + for (i, value) in self + .values + .as_ref() + .expect("Expecting values in enum (state id)") + .iter() + .enumerate() + { + variants.push_str(&format!( + "\n\t{} = {},", + value.from_case(Case::Snake).to_case(Case::UpperCamel), + i + )); + } + + format!( + r#"#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum {} {{{} +}}"#, + self.ty(block_name, competing_definitions), + variants + ) + } + } + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct Block { @@ -85,6 +185,7 @@ mod blocks { material: Option, #[serde(default)] harvest_tools: HashMap, + states: Vec, } #[allow(clippy::explicit_counter_loop)] @@ -249,49 +350,49 @@ impl Block {{ /// Get the textual identifier of this block. #[inline] - pub fn get_text_id(self) -> &'static str {{ + pub fn text_id(self) -> &'static str {{ unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_default_state_id(self) -> u32 {{ + pub fn default_state_id(self) -> u32 {{ unsafe {{*DEFAULT_STATE_IDS.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_id(self) -> u32 {{ + pub fn id(self) -> u32 {{ self as u32 }} /// This returns the item that will be dropped if you break the block. /// If the item is Air, there is actually no drop. #[inline] - pub fn get_associated_item_id(self) -> u32 {{ + pub fn associated_item_id(self) -> u32 {{ unsafe {{*ITEM_IDS.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_resistance(self) -> f32 {{ + pub fn resistance(self) -> f32 {{ unsafe {{*RESISTANCES.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_hardness(self) -> f32 {{ + pub fn hardness(self) -> f32 {{ unsafe {{*HARDNESSES.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_material(self) -> Option {{ + pub fn material(self) -> Option {{ unsafe {{*MATERIALS.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_display_name(self) -> &'static str {{ + pub fn display_name(self) -> &'static str {{ unsafe {{*DISPLAY_NAMES.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_state_id_range(self) -> std::ops::Range {{ + pub fn state_id_range(self) -> std::ops::Range {{ unsafe {{STATE_ID_RANGES.get_unchecked((self as u32) as usize).clone()}} }} @@ -306,17 +407,17 @@ impl Block {{ }} #[inline] - pub fn get_compatible_harvest_tools(self) -> &'static [u32] {{ + pub fn compatible_harvest_tools(self) -> &'static [u32] {{ unsafe {{*HARVEST_TOOLS.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_light_emissions(self) -> u8 {{ + pub fn light_emissions(self) -> u8 {{ unsafe {{*LIGHT_EMISSIONS.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_light_absorption(self) -> u8 {{ + pub fn light_absorption(self) -> u8 {{ unsafe {{*LIGHT_ABSORPTION.get_unchecked((self as u32) as usize)}} }} @@ -340,6 +441,13 @@ impl Block {{ }} }} +impl From for Block {{ + #[inline] + fn from(block_with_state: super::block_states::BlockWithState) -> Block {{ + unsafe {{std::mem::transmute(block_with_state.block_id())}} + }} +}} + impl<'a> MinecraftPacketPart<'a> for Block {{ #[inline] fn serialize_minecraft_packet_part(self, output: &mut Vec) -> Result<(), &'static str> {{ @@ -401,6 +509,250 @@ const AIR_BLOCKS: [bool; {max_value}] = {air_blocks:?}; .write_all(code.as_bytes()) .unwrap() } + + #[allow(clippy::explicit_counter_loop)] + pub fn generate_block_with_state_enum(data: serde_json::Value) { + let mut blocks: Vec = serde_json::from_value(data).expect("Invalid block data"); + blocks.sort_by_key(|block| block.id); + + // Look for missing blocks in the array + let mut expected = 0; + for block in &blocks { + if block.id != expected { + panic!("The block with id {} is missing.", expected) + } + expected += 1; + } + + // Generate the enum definitions + let mut enum_definitions = Vec::new(); + let mut enum_definitions_string = String::new(); + let mut already_defined_enums = Vec::new(); + for block in &blocks { + for state in &block.states { + if state.ty.as_str() == "enum" { + enum_definitions.push((&block.text_id, state)); + } + } + } + for (block_name, enum_definition) in &enum_definitions { + let mut competing_definitions = false; + for (_, enum_definition2) in &enum_definitions { + if enum_definition.name == enum_definition2.name + && enum_definition.values != enum_definition2.values + { + competing_definitions = true; + break; + } + } + if !already_defined_enums + .contains(&enum_definition.ty(block_name, competing_definitions)) + { + enum_definitions_string + .push_str(&enum_definition.define_enum(block_name, competing_definitions)); + enum_definitions_string.push('\n'); + enum_definitions_string.push('\n'); + + already_defined_enums.push(enum_definition.ty(block_name, competing_definitions)); + } + } + + // Generate the variants of the Block enum + let mut variants = String::new(); + for block in &blocks { + let name = block + .text_id + .from_case(Case::Snake) + .to_case(Case::UpperCamel); + let mut fields = String::new(); + for state in &block.states { + let name = match state.name.as_str() == "type" { + true => "ty", + false => state.name.as_str(), + }; + let competing_definitions = + already_defined_enums.contains(&state.ty(&block.text_id, true)); + fields.push_str(&format!( + "{}: {}, ", + name, + state.ty(&block.text_id, competing_definitions) + )); + } + if fields.is_empty() { + variants.push_str(&format!("\t{},\n", name)); + } else { + variants.push_str(&format!("\t{}{{ {}}},\n", name, fields)); + } + } + + // Generate the `match` of state ids + let mut state_id_match_arms = String::new(); + for block in &blocks { + let name = block + .text_id + .from_case(Case::Snake) + .to_case(Case::UpperCamel); + let start = block.min_state_id; + let stop = block.max_state_id; + + if block.states.is_empty() { + state_id_match_arms.push_str(&format!( + "\n\t\t\t{} => Some(BlockWithState::{}),", + start, name + )); + continue; + } + + let mut state_calculations = String::new(); + let mut fields = String::new(); + for (i, state) in block.states.iter().enumerate().rev() { + let competing_definitions = + already_defined_enums.contains(&state.ty(&block.text_id, true)); + let ty = state.ty(&block.text_id, competing_definitions); + let name = match state.name.as_str() { + "type" => "ty", + _ => &state.name, + }; + fields.push_str(&format!("{}, ", name)); + + if i == 0 { + state_calculations.push_str("\n\t\t\t\tlet field_value = state_id;"); + } else { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet field_value = state_id.rem_euclid({});\ + \n\t\t\t\tstate_id -= field_value;\ + \n\t\t\t\tstate_id /= {};", + state.num_values, state.num_values + )); + } + + match state.ty.as_str() { + "enum" => { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet {}: {} = unsafe{{std::mem::transmute(field_value as u8)}};\n", + name, ty + )); + } + "int" => { + let values: Vec = state + .values + .as_ref() + .expect("No values for int block state") + .iter() + .map(|v| v.parse().expect("Invalid block state value: expected int")) + .collect(); + + let mut expected = values[0]; + let mut standard = true; + for value in &values { + if value != &expected { + standard = false; + break; + } + expected += 1; + } + + if standard && values[0] == 0 { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet {}: {} = field_value as {};\n", + name, ty, ty + )); + } else if standard { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet {}: {} = {} + field_value as {};\n", + name, ty, values[0], ty + )); + } else { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet {}: {} = {:?}[field_value as usize];\n", + name, ty, values + )); + } + } + "bool" => { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet {}: bool = field_value == 0;\n", + name + )); + } + other => panic!("Unknown {} type", other), + } + } + + state_id_match_arms.push_str(&format!( + " + {}..={} => {{ + state_id -= {}; + {} + Some(BlockWithState::{}{{ {}}}) + }},", + start, stop, start, state_calculations, name, fields + )); + } + + // Generate the code + let code = format!( + r#"//! Contains the [BlockWithState] enum to help with block state IDs. + +use crate::*; + +{enum_definitions} + +/// Can be converted for free to [super::blocks::Block] which implements [useful methods](super::blocks::Block#implementations). +#[derive(Debug, Clone)] +#[repr(u32)] +pub enum BlockWithState {{ +{variants} +}} + +impl BlockWithState {{ + #[inline] + pub fn from_state_id(mut state_id: u32) -> Option {{ + match state_id {{ +{state_id_match_arms} + _ => None, + }} + }} + + /// Returns the block id, **not the block state id**. + #[inline] + pub fn block_id(&self) -> u32 {{ + unsafe {{std::mem::transmute(std::mem::discriminant(self))}} + }} +}} + +impl From for BlockWithState {{ + #[inline] + fn from(block: super::blocks::Block) -> BlockWithState {{ + BlockWithState::from_state_id(block.default_state_id()).unwrap() // TODO: unwrap unchecked + }} +}} + +impl<'a> MinecraftPacketPart<'a> for BlockWithState {{ + #[inline] + fn serialize_minecraft_packet_part(self, output: &mut Vec) -> Result<(), &'static str> {{ + unimplemented!("Cannot serialize BlockWithState yet"); + }} + + #[inline] + fn deserialize_minecraft_packet_part(input: &'a[u8]) -> Result<(Self, &'a[u8]), &'static str> {{ + let (id, input) = VarInt::deserialize_minecraft_packet_part(input)?; + let id = std::cmp::max(id.0, 0) as u32; + let block_with_state = BlockWithState::from_state_id(id).ok_or("No block corresponding to the specified block state ID.")?; + Ok((block_with_state, input)) + }} +}} +"#, + enum_definitions = enum_definitions_string, + state_id_match_arms = state_id_match_arms, + variants = variants, + ); + + File::create("src/ids/block_states.rs") + .unwrap() + .write_all(code.as_bytes()) + .unwrap() + } } mod items { @@ -686,10 +1038,11 @@ const CATEGORIES: [EntityCategory; {max_value}] = {categories}; } fn main() { - //println!("cargo:rerun-if-changed=target/burger-cache-{}.json", VERSION); + println!("cargo:rerun-if-changed=target/cache-file-location-{}.json", VERSION); + let mut file_locations = get_data( "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/dataPaths.json", - "target/cache-file-location.json", + &format!("target/cache-file-location-{}.json", VERSION), ); let file_locations = file_locations.get_mut("pc").unwrap().take(); let file_locations: HashMap> = @@ -706,7 +1059,8 @@ fn main() { &blocks_url, &format!("target/cache-blocks-{}.json", VERSION), ); - blocks::generate_block_enum(block_data); + blocks::generate_block_enum(block_data.clone()); + blocks::generate_block_with_state_enum(block_data); let items_url = format!( "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/items.json", diff --git a/src/ids/.gitignore b/src/ids/.gitignore index d137a50b..29f126f1 100644 --- a/src/ids/.gitignore +++ b/src/ids/.gitignore @@ -1,3 +1,4 @@ blocks.rs +block_states.rs items.rs entities.rs diff --git a/src/ids/mod.rs b/src/ids/mod.rs index 55e45f7b..802306a7 100644 --- a/src/ids/mod.rs +++ b/src/ids/mod.rs @@ -4,3 +4,4 @@ pub mod blocks; pub mod entities; pub mod items; +pub mod block_states; diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index a61014be..311c235a 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -114,8 +114,7 @@ pub enum ClientboundPacket<'a> { /// Position where the digging was happening location: Position, /// Block state ID of the block that should be at that position now. - /// Use [Block::from_state_id](blocks::Block::from_state_id) to get the corresponding [Block](blocks::Block). - block: VarInt, + block: block_states::BlockWithState, status: crate::components::blocks::PartialDiggingState, /// True if the digging succeeded; false if the client should undo any changes it made locally. successful: bool, @@ -165,9 +164,8 @@ pub enum ClientboundPacket<'a> { BlockChange { /// Block Coordinates location: Position, - /// The new block state ID for the block as given in the [global palette](http://minecraft.gamepedia.com/Data_values%23Block_IDs). See that section for more information. - /// Use [Block::from_state_id](blocks::Block::from_state_id) to get the corresponding [Block](blocks::Block). - block_state: VarInt, + /// The new block state ID for the block + block_state: block_states::BlockWithState, }, BossBar { From b0291cf41023bbeaa3cc1b71b0330a1f77da6f46 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Sun, 19 Sep 2021 18:40:48 +0200 Subject: [PATCH 43/52] Add recipes --- build.rs | 413 +++++++++++++++++++++++++++++++- src/ids/.gitignore | 2 + src/ids/mod.rs | 1 + src/packets/play_clientbound.rs | 2 +- 4 files changed, 405 insertions(+), 13 deletions(-) diff --git a/build.rs b/build.rs index e91b2af3..28d0fff7 100644 --- a/build.rs +++ b/build.rs @@ -730,7 +730,7 @@ impl From for BlockWithState {{ impl<'a> MinecraftPacketPart<'a> for BlockWithState {{ #[inline] - fn serialize_minecraft_packet_part(self, output: &mut Vec) -> Result<(), &'static str> {{ + fn serialize_minecraft_packet_part(self, _output: &mut Vec) -> Result<(), &'static str> {{ unimplemented!("Cannot serialize BlockWithState yet"); }} @@ -760,22 +760,36 @@ mod items { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] - struct Item { - id: u32, + pub struct Item { + pub id: u32, display_name: String, #[serde(rename = "name")] - text_id: String, + pub text_id: String, stack_size: u8, - durability: Option, + max_durability: Option, } #[allow(clippy::explicit_counter_loop)] - pub fn generate_item_enum(data: serde_json::Value) { + pub fn generate_item_enum(data: serde_json::Value) -> Vec { let mut items: Vec = serde_json::from_value(data).expect("Invalid block data"); items.sort_by_key(|item| item.id); + // Patch the missing Air + if items.first().map(|i| i.id) != Some(0) { + items.insert( + 0, + Item { + id: 0, + display_name: String::from("Air"), + text_id: String::from("air"), + stack_size: 64, + max_durability: None, + }, + ); + } + // Look for missing items in the array - let mut expected = 1; + let mut expected = 0; for item in &items { if item.id != expected { panic!("The item with id {} is missing.", expected) @@ -833,6 +847,11 @@ impl Item {{ pub fn get_durability(self) -> Option {{ unsafe {{*DURABILITIES.get_unchecked((self as u32) as usize)}} }} + + #[inline] + pub fn crafting_recipes(&self) -> &'static [crate::ids::recipes::Recipe] {{ + crate::ids::recipes::Recipe::get_recipes_for_item(*self) + }} }} impl<'a> MinecraftPacketPart<'a> for Item {{ @@ -859,9 +878,9 @@ const DISPLAY_NAMES: [&str; {max_value}] = {display_names:?}; const TEXT_IDS: [&str; {max_value}] = {text_ids:?}; "#, variants = variants, - max_value = expected - 1, + max_value = expected, max_stack_sizes = items.iter().map(|i| i.stack_size).collect::>(), - durabilities = items.iter().map(|i| i.durability).collect::>(), + durabilities = items.iter().map(|i| i.max_durability).collect::>(), display_names = items.iter().map(|i| &i.display_name).collect::>(), text_ids = items.iter().map(|i| &i.text_id).collect::>(), ); @@ -869,7 +888,9 @@ const TEXT_IDS: [&str; {max_value}] = {text_ids:?}; File::create("src/ids/items.rs") .unwrap() .write_all(code.as_bytes()) - .unwrap() + .unwrap(); + + items } } @@ -1037,8 +1058,366 @@ const CATEGORIES: [EntityCategory; {max_value}] = {categories}; } } +mod recipes { + use convert_case::{Case, Casing}; + use serde::{Deserialize, Serialize}; + use std::collections::HashMap; + use std::fs::File; + use std::io::prelude::*; + + #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + #[serde(untagged)] + enum RecipeItem { + IDAndMetadataAndCount { id: u32, metadata: u32, count: u8 }, + IDAndMetadata { id: u32, metadata: u32 }, + IDAndCount { id: u32, count: u8 }, + ID(u32), + } + + impl RecipeItem { + fn to_id_and_count(&self) -> (u32, u8) { + match self { + RecipeItem::IDAndMetadataAndCount { .. } => panic!("Metadata not handled"), + RecipeItem::IDAndMetadata { .. } => panic!("Metadata not handled"), + RecipeItem::IDAndCount { id, count } => (*id, *count), + RecipeItem::ID(id) => (*id, 1), + } + } + + fn format(&self, items: &[super::items::Item]) -> String { + let (id, count) = self.to_id_and_count(); + let item_ident = item_id_to_item(id, items); + format!( + "RecipeItem {{item: Item::{}, count: {}}}", + item_ident, count + ) + } + } + + fn format_option_item(item: &Option, items: &[super::items::Item]) -> String { + match item { + Some(item) => format!("Some({})", item.format(items)), + None => "None".to_string(), + } + } + + #[derive(Serialize, Deserialize)] + #[serde(untagged)] + enum Shape { + ThreeByThree([[Option; 3]; 3]), + ThreeByTwo([[Option; 3]; 2]), + ThreeByOne([[Option; 3]; 1]), + TwoByThree([[Option; 2]; 3]), + TwoByTwo([[Option; 2]; 2]), + TwoByOne([[Option; 2]; 1]), + OneByThree([[Option; 1]; 3]), + OneByTwo([[Option; 1]; 2]), + OneByOne([[Option; 1]; 1]), + } + + impl Shape { + fn format(&self, i: &[super::items::Item]) -> String { + match self { + Shape::ThreeByThree([[v1, v2, v3], [v4, v5, v6], [v7, v8, v9]]) => { + format!( + "Shape::ThreeByThree([[{}, {}, {}], [{}, {}, {}], [{}, {}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i), + format_option_item(v4, i), + format_option_item(v5, i), + format_option_item(v6, i), + format_option_item(v7, i), + format_option_item(v8, i), + format_option_item(v9, i) + ) + } + Shape::ThreeByTwo([[v1, v2, v3], [v4, v5, v6]]) => { + format!( + "Shape::ThreeByTwo([[{}, {}, {}], [{}, {}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i), + format_option_item(v4, i), + format_option_item(v5, i), + format_option_item(v6, i) + ) + } + Shape::ThreeByOne([[v1, v2, v3]]) => { + format!( + "Shape::ThreeByOne([[{}, {}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i) + ) + } + Shape::TwoByThree([[v1, v2], [v3, v4], [v5, v6]]) => { + format!( + "Shape::TwoByThree([[{}, {}], [{}, {}], [{}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i), + format_option_item(v4, i), + format_option_item(v5, i), + format_option_item(v6, i) + ) + } + Shape::TwoByTwo([[v1, v2], [v3, v4]]) => { + format!( + "Shape::TwoByTwo([[{}, {}], [{}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i), + format_option_item(v4, i) + ) + } + Shape::TwoByOne([[v1, v2]]) => { + format!( + "Shape::TwoByOne([[{}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i) + ) + } + Shape::OneByThree([[v1], [v2], [v3]]) => { + format!( + "Shape::OneByThree([[{}], [{}], [{}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i) + ) + } + Shape::OneByTwo([[v1], [v2]]) => { + format!( + "Shape::OneByTwo([[{}], [{}]])", + format_option_item(v1, i), + format_option_item(v2, i) + ) + } + Shape::OneByOne([[v1]]) => { + format!("Shape::OneByOne([[{}]])", format_option_item(v1, i)) + } + } + } + } + + #[derive(Serialize, Deserialize)] + #[serde(untagged)] + enum Recipe { + #[serde(rename_all = "camelCase")] + DoubleShaped { + result: RecipeItem, + in_shape: Shape, + out_shape: Shape, + }, + #[serde(rename_all = "camelCase")] + Shaped { in_shape: Shape, result: RecipeItem }, + #[serde(rename_all = "camelCase")] + ShapeLess { + result: RecipeItem, + ingredients: Vec, + }, + } + + fn item_id_to_item(id: u32, items: &[super::items::Item]) -> String { + for item in items { + if item.id == id { + return item + .text_id + .from_case(Case::Snake) + .to_case(Case::UpperCamel); + } + } + + panic!("Item ID from recipe not found") + } + + pub fn generate_recipes(data: serde_json::Value, items: Vec) { + let item_recipes: HashMap> = + serde_json::from_value(data).expect("Invalid recipes"); + + // Count recipes + let mut recipes_count = 0; + for recipes in item_recipes.values() { + recipes_count += recipes.len(); + } + + // Generate recipes + let mut recipes_data = String::new(); + for recipes in item_recipes.values() { + for recipe in recipes { + match recipe { + Recipe::ShapeLess { + result, + ingredients, + } => { + let mut ingredients_string = String::new(); + for ingredient in ingredients { + ingredients_string.push_str(&ingredient.format(&items)); + ingredients_string.push_str(", "); + } + + recipes_data.push_str(&format!( + "\tRecipe::ShapeLess {{ result: {}, ingredients: &[{}] }},\n", + result.format(&items), + ingredients_string, + )); + } + Recipe::Shaped { result, in_shape } => { + recipes_data.push_str(&format!( + "\tRecipe::Shaped {{ result: {}, in_shape: {} }},\n", + result.format(&items), + in_shape.format(&items), + )); + } + Recipe::DoubleShaped { + result, + in_shape, + out_shape, + } => { + recipes_data.push_str(&format!( + "\tRecipe::DoubleShaped {{ result: {}, in_shape: {}, out_shape: {} }},\n", + result.format(&items), + in_shape.format(&items), + out_shape.format(&items), + )); + } + } + } + } + + // Generate shortcuts + let mut idx_in_array = 0; + let mut shortcuts = Vec::new(); + for item_id in 0..items.len() { + let vec_default = Vec::new(); + let recipes = item_recipes.get(&(item_id as u32)).unwrap_or(&vec_default); + shortcuts.push((idx_in_array, idx_in_array + recipes.len())); + idx_in_array += recipes.len(); + } + + #[allow(clippy::useless_format)] + let code = format!( + r#"//! All crafting recipes + +use crate::ids::items::Item; + +/// An [Item](crate::ids::items::Item) associated with a count of this item +#[derive(Debug, Clone)] +pub struct RecipeItem {{ + pub item: Item, + pub count: u8, +}} + +#[derive(Debug, Clone)] +pub enum Shape {{ + ThreeByThree([[Option; 3]; 3]), + ThreeByTwo([[Option; 3]; 2]), + ThreeByOne([[Option; 3]; 1]), + TwoByThree([[Option; 2]; 3]), + TwoByTwo([[Option; 2]; 2]), + TwoByOne([[Option; 2]; 1]), + OneByThree([[Option; 1]; 3]), + OneByTwo([[Option; 1]; 2]), + OneByOne([[Option; 1]; 1]), +}} + +impl Shape {{ + /// Returns the size of the shape. + /// (width, height) + pub const fn size(&self) -> (u8, u8) {{ + match self {{ + Shape::ThreeByThree(_) => (3, 3), + Shape::ThreeByTwo(_) => (3, 2), + Shape::ThreeByOne(_) => (3, 1), + Shape::TwoByThree(_) => (2, 3), + Shape::TwoByTwo(_) => (2, 2), + Shape::TwoByOne(_) => (2, 1), + Shape::OneByThree(_) => (1, 3), + Shape::OneByTwo(_) => (1, 2), + Shape::OneByOne(_) => (1, 1), + }} + }} +}} + +#[derive(Debug, Clone)] +pub enum Recipe {{ + DoubleShaped {{ in_shape: Shape, out_shape: Shape, result: RecipeItem }}, + Shaped {{ in_shape: Shape, result: RecipeItem }}, + ShapeLess {{ ingredients: &'static [RecipeItem], result: RecipeItem }}, +}} + +impl Recipe {{ + /// Returns all the recipes for an item + #[inline] + pub fn get_recipes_for_item(item: Item) -> &'static [Recipe] {{ + unsafe {{ + let (start, end) = SHORTCUTS.get_unchecked((item as u32) as usize); + RECIPES.get_unchecked(*start..*end) + }} + }} + + #[inline] + pub const fn result(&self) -> &RecipeItem {{ + match self {{ + Recipe::DoubleShaped {{ result, .. }} => result, + Recipe::Shaped {{ result, .. }} => result, + Recipe::ShapeLess {{ result, .. }} => result, + }} + }} + + #[inline] + pub const fn in_shape(&self) -> Option<&Shape> {{ + match self {{ + Recipe::DoubleShaped {{ in_shape, .. }} => Some(in_shape), + Recipe::Shaped {{ in_shape, .. }} => Some(in_shape), + Recipe::ShapeLess {{ .. }} => None, + }} + }} + + #[inline] + pub const fn out_shape(&self) -> Option<&Shape> {{ + match self {{ + Recipe::DoubleShaped {{ out_shape, .. }} => Some(out_shape), + Recipe::Shaped {{ .. }} => None, + Recipe::ShapeLess {{ .. }} => None, + }} + }} + + #[inline] + pub const fn ingredients(&self) -> Option<&'static [RecipeItem]> {{ + match self {{ + Recipe::DoubleShaped {{ .. }} => None, + Recipe::Shaped {{ .. }} => None, + Recipe::ShapeLess {{ ingredients, .. }} => Some(ingredients), + }} + }} +}} + +const RECIPES: [Recipe; {recipes_count}] = [ +{recipes_data} +]; + +const SHORTCUTS: [(usize, usize); {item_count}] = {shortcuts:?}; +"#, + recipes_count = recipes_count, + recipes_data = recipes_data, + item_count = items.len(), + shortcuts = shortcuts, + ); + + File::create("src/ids/recipes.rs") + .unwrap() + .write_all(code.as_bytes()) + .unwrap() + } +} + fn main() { - println!("cargo:rerun-if-changed=target/cache-file-location-{}.json", VERSION); + println!( + "cargo:rerun-if-changed=target/cache-file-location-{}.json", + VERSION + ); let mut file_locations = get_data( "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/dataPaths.json", @@ -1067,7 +1446,7 @@ fn main() { file_locations.get("items").unwrap() ); let items_data = get_data(&items_url, &format!("target/cache-items-{}.json", VERSION)); - items::generate_item_enum(items_data); + let items = items::generate_item_enum(items_data); let entities_url = format!( "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/entities.json", @@ -1078,4 +1457,14 @@ fn main() { &format!("target/cache-entities-{}.json", VERSION), ); entities::generate_entity_enum(entities_data); + + let recipes_url = format!( + "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/recipes.json", + file_locations.get("recipes").unwrap() + ); + let recipes_data = get_data( + &recipes_url, + &format!("target/cache-recipes-{}.json", VERSION), + ); + recipes::generate_recipes(recipes_data, items); } diff --git a/src/ids/.gitignore b/src/ids/.gitignore index 29f126f1..aff77a29 100644 --- a/src/ids/.gitignore +++ b/src/ids/.gitignore @@ -2,3 +2,5 @@ blocks.rs block_states.rs items.rs entities.rs +recipes.rs + diff --git a/src/ids/mod.rs b/src/ids/mod.rs index 802306a7..8acfb997 100644 --- a/src/ids/mod.rs +++ b/src/ids/mod.rs @@ -5,3 +5,4 @@ pub mod blocks; pub mod entities; pub mod items; pub mod block_states; +pub mod recipes; diff --git a/src/packets/play_clientbound.rs b/src/packets/play_clientbound.rs index 311c235a..6d6fb437 100644 --- a/src/packets/play_clientbound.rs +++ b/src/packets/play_clientbound.rs @@ -697,7 +697,7 @@ pub enum ClientboundPacket<'a> { }, UnlockRecipes { - action: recipes::UnlockRecipesAction<'a>, + action: crate::components::recipes::UnlockRecipesAction<'a>, }, /// Sent by the server when a list of entities is to be destroyed on the client From e2a13ffd50294449b46cf7efb4a5d495ebad1b38 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Sun, 19 Sep 2021 18:41:57 +0200 Subject: [PATCH 44/52] Comply with conventions for getters --- build.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/build.rs b/build.rs index 28d0fff7..58579359 100644 --- a/build.rs +++ b/build.rs @@ -829,22 +829,22 @@ impl Item {{ }} #[inline] - pub fn get_text_id(self) -> &'static str {{ + pub fn text_id(self) -> &'static str {{ unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_display_name(self) -> &'static str {{ + pub fn display_name(self) -> &'static str {{ unsafe {{*DISPLAY_NAMES.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_max_stack_size(self) -> u8 {{ + pub fn max_stack_size(self) -> u8 {{ unsafe {{*STACK_SIZES.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_durability(self) -> Option {{ + pub fn durability(self) -> Option {{ unsafe {{*DURABILITIES.get_unchecked((self as u32) as usize)}} }} @@ -992,27 +992,27 @@ impl Entity {{ }} #[inline] - pub fn get_text_id(self) -> &'static str {{ + pub fn text_id(self) -> &'static str {{ unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_display_name(self) -> &'static str {{ + pub fn display_name(self) -> &'static str {{ unsafe {{*DISPLAY_NAMES.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_category(self) -> EntityCategory {{ + pub fn category(self) -> EntityCategory {{ unsafe {{*CATEGORIES.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_height(self) -> f32 {{ + pub fn height(self) -> f32 {{ unsafe {{*HEIGHTS.get_unchecked((self as u32) as usize)}} }} #[inline] - pub fn get_width(self) -> f32 {{ + pub fn width(self) -> f32 {{ unsafe {{*WIDTHS.get_unchecked((self as u32) as usize)}} }} }} From df60feae963f382b115e1b47930860bb1717ed7b Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Tue, 21 Sep 2021 20:07:49 +0200 Subject: [PATCH 45/52] Add state_id getter --- build.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 58579359..588849fa 100644 --- a/build.rs +++ b/build.rs @@ -513,7 +513,7 @@ const AIR_BLOCKS: [bool; {max_value}] = {air_blocks:?}; #[allow(clippy::explicit_counter_loop)] pub fn generate_block_with_state_enum(data: serde_json::Value) { let mut blocks: Vec = serde_json::from_value(data).expect("Invalid block data"); - blocks.sort_by_key(|block| block.id); + blocks.sort_by_key(|block| block.min_state_id); // Look for missing blocks in the array let mut expected = 0; @@ -587,6 +587,7 @@ const AIR_BLOCKS: [bool; {max_value}] = {air_blocks:?}; // Generate the `match` of state ids let mut state_id_match_arms = String::new(); + let mut state_id_rebuild_arms = String::new(); for block in &blocks { let name = block .text_id @@ -600,6 +601,8 @@ const AIR_BLOCKS: [bool; {max_value}] = {air_blocks:?}; "\n\t\t\t{} => Some(BlockWithState::{}),", start, name )); + state_id_rebuild_arms + .push_str(&format!("\n\t\t\tBlockWithState::{} => Some({}),", name, start)); continue; } @@ -679,6 +682,79 @@ const AIR_BLOCKS: [bool; {max_value}] = {air_blocks:?}; } } + let mut state_reformation = String::new(); + for (i, state) in block.states.iter().enumerate() { + let name = match state.name.as_str() { + "type" => "ty", + _ => &state.name, + }; + + match state.ty.as_str() { + "enum" => { + state_reformation.push_str(&format!( + "\n\t\t\t\tlet field_value = (*{} as u8) as u32;", + name + )); + } + "int" => { + let values: Vec = state + .values + .as_ref() + .expect("No values for int block state") + .iter() + .map(|v| v.parse().expect("Invalid block state value: expected int")) + .collect(); + + let mut expected = values[0]; + let mut standard = true; + for value in &values { + if value != &expected { + standard = false; + break; + } + expected += 1; + } + + if standard && values[0] == 0 { + state_reformation.push_str(&format!( + "\n\t\t\t\tif *{name} > {max} {{ return None }}\ + \n\t\t\t\tlet field_value = *{name} as u32;", + name=name, max=values.last().unwrap() + )); + } else if standard { + state_reformation.push_str(&format!( + "\n\t\t\t\tif *{name} < {min} || *{name} > {max} {{ return None }}\ + \n\t\t\t\tlet field_value = ({name} - {min}) as u32;", + name=name, min=values[0], max=values.last().unwrap() + )); + } else { + state_reformation.push_str(&format!( + "\n\t\t\t\tlet field_value = {:?}.find({})?;", + values, name + )); + } + } + "bool" => { + state_reformation.push_str(&format!( + "\n\t\t\t\tlet field_value = if *{} {{0}} else {{1}};", + name + )); + } + other => panic!("Unknown {} type", other), + } + + if i == 0 { + state_reformation.push_str("\n\t\t\t\tlet mut state_id = field_value;\n"); + } else { + state_reformation.push_str(&format!( + "\n\t\t\t\tstate_id *= {};\ + \n\t\t\t\tstate_id += field_value;\n", + state.num_values + )); + } + + } + state_id_match_arms.push_str(&format!( " {}..={} => {{ @@ -688,6 +764,15 @@ const AIR_BLOCKS: [bool; {max_value}] = {air_blocks:?}; }},", start, stop, start, state_calculations, name, fields )); + state_id_rebuild_arms.push_str(&format!( + " + BlockWithState::{}{{ {}}} => {{ + {} + state_id += {}; + Some(state_id) + }},", + name, fields, state_reformation, start + )); } // Generate the code @@ -719,6 +804,15 @@ impl BlockWithState {{ pub fn block_id(&self) -> u32 {{ unsafe {{std::mem::transmute(std::mem::discriminant(self))}} }} + + /// Returns the block state id. + /// Returns None in case of error (invalid field value). + #[inline] + pub fn block_state_id(&self) -> Option {{ + match self {{ +{state_id_rebuild_arms} + }} + }} }} impl From for BlockWithState {{ @@ -742,10 +836,26 @@ impl<'a> MinecraftPacketPart<'a> for BlockWithState {{ Ok((block_with_state, input)) }} }} + +#[cfg(test)] +mod tests {{ + use super::*; + + #[test] + fn test_block_states() {{ + for id in 0..={max_block_state_id} {{ + let block = BlockWithState::from_state_id(id).unwrap(); + let id_from_block = block.block_state_id().unwrap(); + assert_eq!(id, id_from_block); + }} + }} +}} "#, enum_definitions = enum_definitions_string, state_id_match_arms = state_id_match_arms, + state_id_rebuild_arms = state_id_rebuild_arms, variants = variants, + max_block_state_id = blocks.last().unwrap().max_state_id ); File::create("src/ids/block_states.rs") From f1110ccbbaa8b3b0f77a40cf11417d1668f2688d Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Tue, 21 Sep 2021 20:17:45 +0200 Subject: [PATCH 46/52] Split build script into multiple files --- Cargo.toml | 1 + build.rs | 1580 --------------------------------------------- build/blocks.rs | 801 +++++++++++++++++++++++ build/build.rs | 127 ++++ build/entities.rs | 161 +++++ build/items.rs | 136 ++++ build/recipes.rs | 352 ++++++++++ 7 files changed, 1578 insertions(+), 1580 deletions(-) delete mode 100644 build.rs create mode 100644 build/blocks.rs create mode 100644 build/build.rs create mode 100644 build/entities.rs create mode 100644 build/items.rs create mode 100644 build/recipes.rs diff --git a/Cargo.toml b/Cargo.toml index 545bff2d..7e767d0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "minecraft-protocol" version = "0.1.0" authors = ["Mubelotix "] edition = "2018" +build = "build/build.rs" [dependencies] minecraft-packet-derive = {path="../minecraft-packet-derive"} diff --git a/build.rs b/build.rs deleted file mode 100644 index 588849fa..00000000 --- a/build.rs +++ /dev/null @@ -1,1580 +0,0 @@ -use convert_case::{Case, Casing}; -use serde::{Deserialize, Serialize}; -use std::io::{ErrorKind, Read, Write}; -use std::{collections::HashMap, fs::File}; - -const VERSION: &str = "1.17.1"; - -fn get_data(url: &str, cache: &str) -> serde_json::Value { - match File::open(cache) { - // The cache file is ready - Ok(mut file) => { - let mut data = Vec::new(); - if let Err(e) = file.read_to_end(&mut data) { - panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, this file cannot be read. Error: {}", cache, e) - } - - let json_text = match String::from_utf8(data) { - Ok(json_text) => json_text, - Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, this file appears to contain invalid text data. Error: {}\nNote: Deleting the file will allow the library to download it again.", cache, e), - }; - - let json = match serde_json::from_str(&json_text) { - Ok(json) => json, - Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, this file appears to contain invalid json data. Error: {}\nNote: Deleting the file will allow the library to download it again.", cache, e), - }; - - json - } - // The cache file needs to be downloaded - Err(e) if e.kind() == ErrorKind::NotFound => { - let response = match minreq::get(url).send() { - Ok(response) => response, - Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded from `{}`. Unfortunately, we can't access this URL. Error: {}", url, e) - }; - - let json_text = match response.as_str() { - Ok(json_text) => json_text, - Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded from `{}`. Unfortunately, this file appears to contain invalid data. Error: {}", url, e), - }; - - let mut file = match File::create(cache) { - Ok(file) => file, - Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, we can't access this path. Error: {}", cache, e), - }; - - if let Err(e) = file.write_all(json_text.as_bytes()) { - panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, we can't write to this path. Error: {}", cache, e) - }; - - let json = match serde_json::from_str(json_text) { - Ok(json) => json, - Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, this file appears to contain invalid json data. Error: {}\nNote: Deleting the file will allow the library to download it again.", cache, e), - }; - - json - } - - // The cache file cannot be accessed - Err(e) => { - panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, we can't access this path. Error: {}", cache, e); - } - } -} - -mod blocks { - use super::*; - - #[derive(Debug, Serialize, Deserialize, PartialEq)] - struct BlockState { - name: String, - #[serde(rename = "type")] - ty: String, - num_values: usize, - values: Option>, - } - - impl BlockState { - fn ty(&self, block_name: &str, competing_definitions: bool) -> String { - match self.ty.as_str() { - "int" => { - let values: Vec = self - .values - .as_ref() - .expect("No values for int block state") - .iter() - .map(|v| v.parse().expect("Invalid block state value: expected int")) - .collect(); - let mut min_value: i128 = *values.first().unwrap_or(&0); - let mut max_value: i128 = *values.first().unwrap_or(&0); - - for value in values { - if value < min_value { - min_value = value; - } - if value > max_value { - max_value = value; - } - } - - if min_value >= u8::MIN as i128 && max_value <= u8::MAX as i128 { - return String::from("u8"); - } - if min_value >= i8::MIN as i128 && max_value <= i8::MAX as i128 { - return String::from("i8"); - } - if min_value >= u16::MIN as i128 && max_value <= u16::MAX as i128 { - return String::from("u16"); - } - if min_value >= i16::MIN as i128 && max_value <= i16::MAX as i128 { - return String::from("i16"); - } - if min_value >= u32::MIN as i128 && max_value <= u32::MAX as i128 { - return String::from("u32"); - } - if min_value >= i32::MIN as i128 && max_value <= i32::MAX as i128 { - return String::from("i32"); - } - if min_value >= u64::MIN as i128 && max_value <= u64::MAX as i128 { - return String::from("u64"); - } - if min_value >= i64::MIN as i128 && max_value <= i64::MAX as i128 { - return String::from("i64"); - } - String::from("i128") - } - "enum" => match competing_definitions { - true => format!("{}_{}", block_name, self.name), - false => self.name.to_string(), - } - .from_case(Case::Snake) - .to_case(Case::UpperCamel), - "bool" => String::from("bool"), - _ => unimplemented!(), - } - } - - fn define_enum(&self, block_name: &str, competing_definitions: bool) -> String { - if self.ty.as_str() != "enum" { - panic!("Called defined enum on non-enum"); - } - - let mut variants = String::new(); - for (i, value) in self - .values - .as_ref() - .expect("Expecting values in enum (state id)") - .iter() - .enumerate() - { - variants.push_str(&format!( - "\n\t{} = {},", - value.from_case(Case::Snake).to_case(Case::UpperCamel), - i - )); - } - - format!( - r#"#[derive(Debug, Clone, Copy)] -#[repr(u8)] -pub enum {} {{{} -}}"#, - self.ty(block_name, competing_definitions), - variants - ) - } - } - - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - struct Block { - id: u32, - #[serde(rename = "name")] - text_id: String, - display_name: String, - hardness: f32, - resistance: f32, - diggable: bool, - transparent: bool, - filter_light: u8, - emit_light: u8, - default_state: u32, - min_state_id: u32, - max_state_id: u32, - drops: Vec, - material: Option, - #[serde(default)] - harvest_tools: HashMap, - states: Vec, - } - - #[allow(clippy::explicit_counter_loop)] - pub fn generate_block_enum(data: serde_json::Value) { - let mut blocks: Vec = serde_json::from_value(data).expect("Invalid block data"); - blocks.sort_by_key(|block| block.id); - - // Look for missing blocks in the array - let mut expected = 0; - for block in &blocks { - if block.id != expected { - panic!("The block with id {} is missing.", expected) - } - expected += 1; - } - - // Process a few fields - let mut raw_harvest_tools: Vec> = Vec::new(); - let mut raw_materials: Vec = Vec::new(); - for block in &blocks { - raw_harvest_tools.push( - block - .harvest_tools - .clone() - .into_iter() - .map(|(k, _v)| k) - .collect(), - ); - let mut material = block - .material - .clone() - .unwrap_or_else(|| "unknown_material".to_string()) - .split(';') - .next() - .unwrap() - .to_string(); - if material.starts_with("mineable") { - material = "unknown_material".to_string(); - } - raw_materials.push(material.from_case(Case::Snake).to_case(Case::UpperCamel)); - } - - // Generate the MaterialBlock enum and array - let mut different_materials = raw_materials.clone(); - different_materials.sort(); - different_materials.dedup(); - let mut material_variants = String::new(); - for material in different_materials { - material_variants.push_str(&format!("\t{},\n", material)); - } - let mut materials = String::new(); - materials.push('['); - for material in raw_materials { - materials.push_str("Some(BlockMaterial::"); - materials.push_str(&material); - materials.push_str("), "); - } - materials.push(']'); - - // Generate the HARVEST_TOOLS array - let mut harvest_tools = String::new(); - harvest_tools.push('['); - for block_harvest_tools in raw_harvest_tools { - harvest_tools.push_str("&["); - for harvest_tool in block_harvest_tools { - harvest_tools.push_str(&harvest_tool.to_string()); - harvest_tools.push_str(", "); - } - harvest_tools.push_str("], "); - } - harvest_tools.push(']'); - - // Enumerate the air blocks - let mut air_blocks = vec![false; expected as usize]; - for air_block in &[ - "air", - "cave_air", - "grass", - "torch", - "wall_torch", - "wheat", - "soul_torch", - "soul_wall_torch", - "carrots", - "potatoes", - ] { - let mut success = false; - for block in &blocks { - if &block.text_id.as_str() == air_block { - air_blocks[block.id as usize] = true; - success = true; - break; - } - } - if !success { - panic!("Could not find block {} in the block array", air_block); - } - } - - // Generate the variants of the Block enum - let mut variants = String::new(); - for block in &blocks { - let name = block - .text_id - .from_case(Case::Snake) - .to_case(Case::UpperCamel); - variants.push_str(&format!("\t{} = {},\n", name, block.id)); - } - - // Generate the `match` of state ids - let mut state_id_match_arms = String::new(); - for block in &blocks { - let name = block - .text_id - .from_case(Case::Snake) - .to_case(Case::UpperCamel); - let start = block.min_state_id; - let stop = block.max_state_id; - if start != stop { - state_id_match_arms.push_str(&format!( - "\t\t\t{}..={} => Some(Block::{}),\n", - start, stop, name - )); - } else { - state_id_match_arms - .push_str(&format!("\t\t\t{} => Some(Block::{}),\n", start, name)); - } - } - - // Generate the code - let code = format!( - r#"use crate::*; - -/// See [implementations](#implementations) for useful methods. -#[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Block {{ -{variants} -}} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum BlockMaterial {{ -{material_variants} -}} - -impl Block {{ - #[inline] - pub fn from_id(id: u32) -> Option {{ - if id < {max_value} {{ - Some(unsafe{{std::mem::transmute(id)}}) - }} else {{ - None - }} - }} - - pub fn from_state_id(state_id: u32) -> Option {{ - match state_id {{ -{state_id_match_arms} - _ => None, - }} - }} - - /// Get the textual identifier of this block. - #[inline] - pub fn text_id(self) -> &'static str {{ - unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn default_state_id(self) -> u32 {{ - unsafe {{*DEFAULT_STATE_IDS.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn id(self) -> u32 {{ - self as u32 - }} - - /// This returns the item that will be dropped if you break the block. - /// If the item is Air, there is actually no drop. - #[inline] - pub fn associated_item_id(self) -> u32 {{ - unsafe {{*ITEM_IDS.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn resistance(self) -> f32 {{ - unsafe {{*RESISTANCES.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn hardness(self) -> f32 {{ - unsafe {{*HARDNESSES.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn material(self) -> Option {{ - unsafe {{*MATERIALS.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn display_name(self) -> &'static str {{ - unsafe {{*DISPLAY_NAMES.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn state_id_range(self) -> std::ops::Range {{ - unsafe {{STATE_ID_RANGES.get_unchecked((self as u32) as usize).clone()}} - }} - - #[inline] - pub fn is_diggable(self) -> bool {{ - unsafe {{*DIGGABLE.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn is_transparent(self) -> bool {{ - unsafe {{*TRANSPARENT.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn compatible_harvest_tools(self) -> &'static [u32] {{ - unsafe {{*HARVEST_TOOLS.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn light_emissions(self) -> u8 {{ - unsafe {{*LIGHT_EMISSIONS.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn light_absorption(self) -> u8 {{ - unsafe {{*LIGHT_ABSORPTION.get_unchecked((self as u32) as usize)}} - }} - - /// A "air block" is a block on which a player cannot stand, like air, wheat, torch... - /// Fire is excluded since you may not want your clients to walk trought fire by default. - /// The list of air blocks is maintained by hand. - /// It could not be exhaustive. - /// See also [Block::is_blocking]. - #[inline] - pub fn is_air_block(self) -> bool {{ - unsafe {{*AIR_BLOCKS.get_unchecked((self as u32) as usize)}} - }} - - /// The opposite of [Block::is_air_block]. - /// Fire is included since you may not want your clients to walk trought fire by default. - /// The list of blocking blocks is maintained by hand. - /// It could not be exhaustive. - #[inline] - pub fn is_blocking(self) -> bool {{ - unsafe {{!(*AIR_BLOCKS.get_unchecked((self as u32) as usize))}} - }} -}} - -impl From for Block {{ - #[inline] - fn from(block_with_state: super::block_states::BlockWithState) -> Block {{ - unsafe {{std::mem::transmute(block_with_state.block_id())}} - }} -}} - -impl<'a> MinecraftPacketPart<'a> for Block {{ - #[inline] - fn serialize_minecraft_packet_part(self, output: &mut Vec) -> Result<(), &'static str> {{ - VarInt((self as u32) as i32).serialize_minecraft_packet_part(output) - }} - - #[inline] - fn deserialize_minecraft_packet_part(input: &'a[u8]) -> Result<(Self, &'a[u8]), &'static str> {{ - let (id, input) = VarInt::deserialize_minecraft_packet_part(input)?; - let id = std::cmp::max(id.0, 0) as u32; - let block = Block::from_id(id).ok_or("No block corresponding to the specified numeric ID.")?; - Ok((block, input)) - }} -}} - -const TEXT_IDS: [&str; {max_value}] = {text_ids:?}; -const DISPLAY_NAMES: [&str; {max_value}] = {display_names:?}; -const STATE_ID_RANGES: [std::ops::Range; {max_value}] = {state_id_ranges:?}; -const DEFAULT_STATE_IDS: [u32; {max_value}] = {default_state_ids:?}; -const ITEM_IDS: [u32; {max_value}] = {item_ids:?}; -const RESISTANCES: [f32; {max_value}] = {resistances:?}; -const MATERIALS: [Option; {max_value}] = {materials}; -const HARVEST_TOOLS: [&[u32]; {max_value}] = {harvest_tools}; -const HARDNESSES: [f32; {max_value}] = {hardnesses:?}; -const LIGHT_EMISSIONS: [u8; {max_value}] = {light_emissions:?}; -const LIGHT_ABSORPTION: [u8; {max_value}] = {light_absorption:?}; -const DIGGABLE: [bool; {max_value}] = {diggable:?}; -const TRANSPARENT: [bool; {max_value}] = {transparent:?}; -const AIR_BLOCKS: [bool; {max_value}] = {air_blocks:?}; -"#, - variants = variants, - material_variants = material_variants, - max_value = expected, - state_id_match_arms = state_id_match_arms, - text_ids = blocks.iter().map(|b| &b.text_id).collect::>(), - display_names = blocks.iter().map(|b| &b.display_name).collect::>(), - state_id_ranges = blocks - .iter() - .map(|b| b.min_state_id..b.max_state_id + 1) - .collect::>(), - default_state_ids = blocks.iter().map(|b| b.default_state).collect::>(), - item_ids = blocks - .iter() - .map(|b| b.drops.get(0).copied().unwrap_or(0)) - .collect::>(), - materials = materials, - resistances = blocks.iter().map(|b| b.resistance).collect::>(), - harvest_tools = harvest_tools, - hardnesses = blocks.iter().map(|b| b.hardness).collect::>(), - light_emissions = blocks.iter().map(|b| b.emit_light).collect::>(), - light_absorption = blocks.iter().map(|b| b.filter_light).collect::>(), - diggable = blocks.iter().map(|b| b.diggable).collect::>(), - transparent = blocks.iter().map(|b| b.transparent).collect::>(), - air_blocks = air_blocks, - ); - - File::create("src/ids/blocks.rs") - .unwrap() - .write_all(code.as_bytes()) - .unwrap() - } - - #[allow(clippy::explicit_counter_loop)] - pub fn generate_block_with_state_enum(data: serde_json::Value) { - let mut blocks: Vec = serde_json::from_value(data).expect("Invalid block data"); - blocks.sort_by_key(|block| block.min_state_id); - - // Look for missing blocks in the array - let mut expected = 0; - for block in &blocks { - if block.id != expected { - panic!("The block with id {} is missing.", expected) - } - expected += 1; - } - - // Generate the enum definitions - let mut enum_definitions = Vec::new(); - let mut enum_definitions_string = String::new(); - let mut already_defined_enums = Vec::new(); - for block in &blocks { - for state in &block.states { - if state.ty.as_str() == "enum" { - enum_definitions.push((&block.text_id, state)); - } - } - } - for (block_name, enum_definition) in &enum_definitions { - let mut competing_definitions = false; - for (_, enum_definition2) in &enum_definitions { - if enum_definition.name == enum_definition2.name - && enum_definition.values != enum_definition2.values - { - competing_definitions = true; - break; - } - } - if !already_defined_enums - .contains(&enum_definition.ty(block_name, competing_definitions)) - { - enum_definitions_string - .push_str(&enum_definition.define_enum(block_name, competing_definitions)); - enum_definitions_string.push('\n'); - enum_definitions_string.push('\n'); - - already_defined_enums.push(enum_definition.ty(block_name, competing_definitions)); - } - } - - // Generate the variants of the Block enum - let mut variants = String::new(); - for block in &blocks { - let name = block - .text_id - .from_case(Case::Snake) - .to_case(Case::UpperCamel); - let mut fields = String::new(); - for state in &block.states { - let name = match state.name.as_str() == "type" { - true => "ty", - false => state.name.as_str(), - }; - let competing_definitions = - already_defined_enums.contains(&state.ty(&block.text_id, true)); - fields.push_str(&format!( - "{}: {}, ", - name, - state.ty(&block.text_id, competing_definitions) - )); - } - if fields.is_empty() { - variants.push_str(&format!("\t{},\n", name)); - } else { - variants.push_str(&format!("\t{}{{ {}}},\n", name, fields)); - } - } - - // Generate the `match` of state ids - let mut state_id_match_arms = String::new(); - let mut state_id_rebuild_arms = String::new(); - for block in &blocks { - let name = block - .text_id - .from_case(Case::Snake) - .to_case(Case::UpperCamel); - let start = block.min_state_id; - let stop = block.max_state_id; - - if block.states.is_empty() { - state_id_match_arms.push_str(&format!( - "\n\t\t\t{} => Some(BlockWithState::{}),", - start, name - )); - state_id_rebuild_arms - .push_str(&format!("\n\t\t\tBlockWithState::{} => Some({}),", name, start)); - continue; - } - - let mut state_calculations = String::new(); - let mut fields = String::new(); - for (i, state) in block.states.iter().enumerate().rev() { - let competing_definitions = - already_defined_enums.contains(&state.ty(&block.text_id, true)); - let ty = state.ty(&block.text_id, competing_definitions); - let name = match state.name.as_str() { - "type" => "ty", - _ => &state.name, - }; - fields.push_str(&format!("{}, ", name)); - - if i == 0 { - state_calculations.push_str("\n\t\t\t\tlet field_value = state_id;"); - } else { - state_calculations.push_str(&format!( - "\n\t\t\t\tlet field_value = state_id.rem_euclid({});\ - \n\t\t\t\tstate_id -= field_value;\ - \n\t\t\t\tstate_id /= {};", - state.num_values, state.num_values - )); - } - - match state.ty.as_str() { - "enum" => { - state_calculations.push_str(&format!( - "\n\t\t\t\tlet {}: {} = unsafe{{std::mem::transmute(field_value as u8)}};\n", - name, ty - )); - } - "int" => { - let values: Vec = state - .values - .as_ref() - .expect("No values for int block state") - .iter() - .map(|v| v.parse().expect("Invalid block state value: expected int")) - .collect(); - - let mut expected = values[0]; - let mut standard = true; - for value in &values { - if value != &expected { - standard = false; - break; - } - expected += 1; - } - - if standard && values[0] == 0 { - state_calculations.push_str(&format!( - "\n\t\t\t\tlet {}: {} = field_value as {};\n", - name, ty, ty - )); - } else if standard { - state_calculations.push_str(&format!( - "\n\t\t\t\tlet {}: {} = {} + field_value as {};\n", - name, ty, values[0], ty - )); - } else { - state_calculations.push_str(&format!( - "\n\t\t\t\tlet {}: {} = {:?}[field_value as usize];\n", - name, ty, values - )); - } - } - "bool" => { - state_calculations.push_str(&format!( - "\n\t\t\t\tlet {}: bool = field_value == 0;\n", - name - )); - } - other => panic!("Unknown {} type", other), - } - } - - let mut state_reformation = String::new(); - for (i, state) in block.states.iter().enumerate() { - let name = match state.name.as_str() { - "type" => "ty", - _ => &state.name, - }; - - match state.ty.as_str() { - "enum" => { - state_reformation.push_str(&format!( - "\n\t\t\t\tlet field_value = (*{} as u8) as u32;", - name - )); - } - "int" => { - let values: Vec = state - .values - .as_ref() - .expect("No values for int block state") - .iter() - .map(|v| v.parse().expect("Invalid block state value: expected int")) - .collect(); - - let mut expected = values[0]; - let mut standard = true; - for value in &values { - if value != &expected { - standard = false; - break; - } - expected += 1; - } - - if standard && values[0] == 0 { - state_reformation.push_str(&format!( - "\n\t\t\t\tif *{name} > {max} {{ return None }}\ - \n\t\t\t\tlet field_value = *{name} as u32;", - name=name, max=values.last().unwrap() - )); - } else if standard { - state_reformation.push_str(&format!( - "\n\t\t\t\tif *{name} < {min} || *{name} > {max} {{ return None }}\ - \n\t\t\t\tlet field_value = ({name} - {min}) as u32;", - name=name, min=values[0], max=values.last().unwrap() - )); - } else { - state_reformation.push_str(&format!( - "\n\t\t\t\tlet field_value = {:?}.find({})?;", - values, name - )); - } - } - "bool" => { - state_reformation.push_str(&format!( - "\n\t\t\t\tlet field_value = if *{} {{0}} else {{1}};", - name - )); - } - other => panic!("Unknown {} type", other), - } - - if i == 0 { - state_reformation.push_str("\n\t\t\t\tlet mut state_id = field_value;\n"); - } else { - state_reformation.push_str(&format!( - "\n\t\t\t\tstate_id *= {};\ - \n\t\t\t\tstate_id += field_value;\n", - state.num_values - )); - } - - } - - state_id_match_arms.push_str(&format!( - " - {}..={} => {{ - state_id -= {}; - {} - Some(BlockWithState::{}{{ {}}}) - }},", - start, stop, start, state_calculations, name, fields - )); - state_id_rebuild_arms.push_str(&format!( - " - BlockWithState::{}{{ {}}} => {{ - {} - state_id += {}; - Some(state_id) - }},", - name, fields, state_reformation, start - )); - } - - // Generate the code - let code = format!( - r#"//! Contains the [BlockWithState] enum to help with block state IDs. - -use crate::*; - -{enum_definitions} - -/// Can be converted for free to [super::blocks::Block] which implements [useful methods](super::blocks::Block#implementations). -#[derive(Debug, Clone)] -#[repr(u32)] -pub enum BlockWithState {{ -{variants} -}} - -impl BlockWithState {{ - #[inline] - pub fn from_state_id(mut state_id: u32) -> Option {{ - match state_id {{ -{state_id_match_arms} - _ => None, - }} - }} - - /// Returns the block id, **not the block state id**. - #[inline] - pub fn block_id(&self) -> u32 {{ - unsafe {{std::mem::transmute(std::mem::discriminant(self))}} - }} - - /// Returns the block state id. - /// Returns None in case of error (invalid field value). - #[inline] - pub fn block_state_id(&self) -> Option {{ - match self {{ -{state_id_rebuild_arms} - }} - }} -}} - -impl From for BlockWithState {{ - #[inline] - fn from(block: super::blocks::Block) -> BlockWithState {{ - BlockWithState::from_state_id(block.default_state_id()).unwrap() // TODO: unwrap unchecked - }} -}} - -impl<'a> MinecraftPacketPart<'a> for BlockWithState {{ - #[inline] - fn serialize_minecraft_packet_part(self, _output: &mut Vec) -> Result<(), &'static str> {{ - unimplemented!("Cannot serialize BlockWithState yet"); - }} - - #[inline] - fn deserialize_minecraft_packet_part(input: &'a[u8]) -> Result<(Self, &'a[u8]), &'static str> {{ - let (id, input) = VarInt::deserialize_minecraft_packet_part(input)?; - let id = std::cmp::max(id.0, 0) as u32; - let block_with_state = BlockWithState::from_state_id(id).ok_or("No block corresponding to the specified block state ID.")?; - Ok((block_with_state, input)) - }} -}} - -#[cfg(test)] -mod tests {{ - use super::*; - - #[test] - fn test_block_states() {{ - for id in 0..={max_block_state_id} {{ - let block = BlockWithState::from_state_id(id).unwrap(); - let id_from_block = block.block_state_id().unwrap(); - assert_eq!(id, id_from_block); - }} - }} -}} -"#, - enum_definitions = enum_definitions_string, - state_id_match_arms = state_id_match_arms, - state_id_rebuild_arms = state_id_rebuild_arms, - variants = variants, - max_block_state_id = blocks.last().unwrap().max_state_id - ); - - File::create("src/ids/block_states.rs") - .unwrap() - .write_all(code.as_bytes()) - .unwrap() - } -} - -mod items { - use super::*; - - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Item { - pub id: u32, - display_name: String, - #[serde(rename = "name")] - pub text_id: String, - stack_size: u8, - max_durability: Option, - } - - #[allow(clippy::explicit_counter_loop)] - pub fn generate_item_enum(data: serde_json::Value) -> Vec { - let mut items: Vec = serde_json::from_value(data).expect("Invalid block data"); - items.sort_by_key(|item| item.id); - - // Patch the missing Air - if items.first().map(|i| i.id) != Some(0) { - items.insert( - 0, - Item { - id: 0, - display_name: String::from("Air"), - text_id: String::from("air"), - stack_size: 64, - max_durability: None, - }, - ); - } - - // Look for missing items in the array - let mut expected = 0; - for item in &items { - if item.id != expected { - panic!("The item with id {} is missing.", expected) - } - expected += 1; - } - - // Generate the variants of the Item enum - let mut variants = String::new(); - for item in &items { - let name = item - .text_id - .from_case(Case::Snake) - .to_case(Case::UpperCamel); - variants.push_str(&format!("\t{} = {},\n", name, item.id)); - } - - // Generate the code - let code = format!( - r#"use crate::*; - -/// See [implementations](#implementations) for useful methods. -#[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Item {{ -{variants} -}} - -impl Item {{ - #[inline] - pub fn from_id(id: u32) -> Option {{ - if id < {max_value} {{ - Some(unsafe{{std::mem::transmute(id)}}) - }} else {{ - None - }} - }} - - #[inline] - pub fn text_id(self) -> &'static str {{ - unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn display_name(self) -> &'static str {{ - unsafe {{*DISPLAY_NAMES.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn max_stack_size(self) -> u8 {{ - unsafe {{*STACK_SIZES.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn durability(self) -> Option {{ - unsafe {{*DURABILITIES.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn crafting_recipes(&self) -> &'static [crate::ids::recipes::Recipe] {{ - crate::ids::recipes::Recipe::get_recipes_for_item(*self) - }} -}} - -impl<'a> MinecraftPacketPart<'a> for Item {{ - #[inline] - fn serialize_minecraft_packet_part(self, output: &mut Vec) -> Result<(), &'static str> {{ - VarInt((self as u32) as i32).serialize_minecraft_packet_part(output) - }} - - #[inline] - fn deserialize_minecraft_packet_part(input: &'a[u8]) -> Result<(Self, &'a[u8]), &'static str> {{ - let (id, input) = VarInt::deserialize_minecraft_packet_part(input)?; - let id = std::cmp::max(id.0, 0) as u32; - let item = Item::from_id(id).ok_or("No item corresponding to the specified numeric ID.")?; - Ok((item, input)) - }} -}} - -const STACK_SIZES: [u8; {max_value}] = {max_stack_sizes:?}; - -const DURABILITIES: [Option; {max_value}] = {durabilities:?}; - -const DISPLAY_NAMES: [&str; {max_value}] = {display_names:?}; - -const TEXT_IDS: [&str; {max_value}] = {text_ids:?}; -"#, - variants = variants, - max_value = expected, - max_stack_sizes = items.iter().map(|i| i.stack_size).collect::>(), - durabilities = items.iter().map(|i| i.max_durability).collect::>(), - display_names = items.iter().map(|i| &i.display_name).collect::>(), - text_ids = items.iter().map(|i| &i.text_id).collect::>(), - ); - - File::create("src/ids/items.rs") - .unwrap() - .write_all(code.as_bytes()) - .unwrap(); - - items - } -} - -mod entities { - use super::*; - - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - struct Entity { - id: u32, - #[serde(rename = "name")] - text_id: String, - display_name: String, - width: f32, - height: f32, - #[serde(rename = "type")] - category: String, - } - - pub fn generate_entity_enum(data: serde_json::Value) { - let mut entities: Vec = serde_json::from_value(data).expect("Invalid entity data"); - entities.sort_by_key(|entity| entity.id); - - // Look for missing items in the array - let mut expected = 0; - for entity in &entities { - if entity.id != expected { - panic!("The entity with id {} is missing.", expected) - } - expected += 1; - } - - // Generate the categories array - let mut categories = String::new(); - categories.push('['); - for entity in &entities { - let variant_name = match entity.category.as_str() { - "other" => "Other", - "living" => "Living", - "projectile" => "Projectile", - "animal" => "Animal", - "ambient" => "Ambient", - "hostile" => "Hostile", - "water_creature" => "WaterCreature", - "mob" => "Mob", - "passive" => "Passive", - "player" => "Player", - unknown_category => panic!("Unknown entity category {}", unknown_category), - }; - categories.push_str("EntityCategory::"); - categories.push_str(variant_name); - categories.push_str(", "); - } - categories.push(']'); - - // Generate the variants of the Item enum - let mut variants = String::new(); - for entity in &entities { - let name = entity - .text_id - .from_case(Case::Snake) - .to_case(Case::UpperCamel); - variants.push_str(&format!("\t{} = {},\n", name, entity.id)); - } - - // Generate the code - let code = format!( - r#"use crate::*; - -/// See [implementations](#implementations) for useful methods. -#[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Entity {{ -{variants} -}} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum EntityCategory {{ - Other, - Living, - Projectile, - Animal, - Ambient, - Hostile, - WaterCreature, - Mob, - Passive, - Player, -}} - -impl Entity {{ - #[inline] - pub fn from_id(id: u32) -> Option {{ - if id < {max_value} {{ - Some(unsafe{{std::mem::transmute(id)}}) - }} else {{ - None - }} - }} - - #[inline] - pub fn text_id(self) -> &'static str {{ - unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn display_name(self) -> &'static str {{ - unsafe {{*DISPLAY_NAMES.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn category(self) -> EntityCategory {{ - unsafe {{*CATEGORIES.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn height(self) -> f32 {{ - unsafe {{*HEIGHTS.get_unchecked((self as u32) as usize)}} - }} - - #[inline] - pub fn width(self) -> f32 {{ - unsafe {{*WIDTHS.get_unchecked((self as u32) as usize)}} - }} -}} - -impl<'a> MinecraftPacketPart<'a> for Entity {{ - #[inline] - fn serialize_minecraft_packet_part(self, output: &mut Vec) -> Result<(), &'static str> {{ - VarInt((self as u32) as i32).serialize_minecraft_packet_part(output) - }} - - #[inline] - fn deserialize_minecraft_packet_part(input: &'a[u8]) -> Result<(Self, &'a[u8]), &'static str> {{ - let (id, input) = VarInt::deserialize_minecraft_packet_part(input)?; - let id = std::cmp::max(id.0, 0) as u32; - let entity = Entity::from_id(id).ok_or("No entity corresponding to the specified numeric ID.")?; - Ok((entity, input)) - }} -}} - -const HEIGHTS: [f32; {max_value}] = {heights:?}; - -const WIDTHS: [f32; {max_value}] = {widths:?}; - -const DISPLAY_NAMES: [&str; {max_value}] = {display_names:?}; - -const TEXT_IDS: [&str; {max_value}] = {text_ids:?}; - -const CATEGORIES: [EntityCategory; {max_value}] = {categories}; -"#, - variants = variants, - max_value = expected, - heights = entities.iter().map(|e| e.height).collect::>(), - widths = entities.iter().map(|e| e.width).collect::>(), - display_names = entities.iter().map(|e| &e.display_name).collect::>(), - text_ids = entities.iter().map(|e| &e.text_id).collect::>(), - categories = categories, - ); - - File::create("src/ids/entities.rs") - .unwrap() - .write_all(code.as_bytes()) - .unwrap() - } -} - -mod recipes { - use convert_case::{Case, Casing}; - use serde::{Deserialize, Serialize}; - use std::collections::HashMap; - use std::fs::File; - use std::io::prelude::*; - - #[derive(Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - #[serde(untagged)] - enum RecipeItem { - IDAndMetadataAndCount { id: u32, metadata: u32, count: u8 }, - IDAndMetadata { id: u32, metadata: u32 }, - IDAndCount { id: u32, count: u8 }, - ID(u32), - } - - impl RecipeItem { - fn to_id_and_count(&self) -> (u32, u8) { - match self { - RecipeItem::IDAndMetadataAndCount { .. } => panic!("Metadata not handled"), - RecipeItem::IDAndMetadata { .. } => panic!("Metadata not handled"), - RecipeItem::IDAndCount { id, count } => (*id, *count), - RecipeItem::ID(id) => (*id, 1), - } - } - - fn format(&self, items: &[super::items::Item]) -> String { - let (id, count) = self.to_id_and_count(); - let item_ident = item_id_to_item(id, items); - format!( - "RecipeItem {{item: Item::{}, count: {}}}", - item_ident, count - ) - } - } - - fn format_option_item(item: &Option, items: &[super::items::Item]) -> String { - match item { - Some(item) => format!("Some({})", item.format(items)), - None => "None".to_string(), - } - } - - #[derive(Serialize, Deserialize)] - #[serde(untagged)] - enum Shape { - ThreeByThree([[Option; 3]; 3]), - ThreeByTwo([[Option; 3]; 2]), - ThreeByOne([[Option; 3]; 1]), - TwoByThree([[Option; 2]; 3]), - TwoByTwo([[Option; 2]; 2]), - TwoByOne([[Option; 2]; 1]), - OneByThree([[Option; 1]; 3]), - OneByTwo([[Option; 1]; 2]), - OneByOne([[Option; 1]; 1]), - } - - impl Shape { - fn format(&self, i: &[super::items::Item]) -> String { - match self { - Shape::ThreeByThree([[v1, v2, v3], [v4, v5, v6], [v7, v8, v9]]) => { - format!( - "Shape::ThreeByThree([[{}, {}, {}], [{}, {}, {}], [{}, {}, {}]])", - format_option_item(v1, i), - format_option_item(v2, i), - format_option_item(v3, i), - format_option_item(v4, i), - format_option_item(v5, i), - format_option_item(v6, i), - format_option_item(v7, i), - format_option_item(v8, i), - format_option_item(v9, i) - ) - } - Shape::ThreeByTwo([[v1, v2, v3], [v4, v5, v6]]) => { - format!( - "Shape::ThreeByTwo([[{}, {}, {}], [{}, {}, {}]])", - format_option_item(v1, i), - format_option_item(v2, i), - format_option_item(v3, i), - format_option_item(v4, i), - format_option_item(v5, i), - format_option_item(v6, i) - ) - } - Shape::ThreeByOne([[v1, v2, v3]]) => { - format!( - "Shape::ThreeByOne([[{}, {}, {}]])", - format_option_item(v1, i), - format_option_item(v2, i), - format_option_item(v3, i) - ) - } - Shape::TwoByThree([[v1, v2], [v3, v4], [v5, v6]]) => { - format!( - "Shape::TwoByThree([[{}, {}], [{}, {}], [{}, {}]])", - format_option_item(v1, i), - format_option_item(v2, i), - format_option_item(v3, i), - format_option_item(v4, i), - format_option_item(v5, i), - format_option_item(v6, i) - ) - } - Shape::TwoByTwo([[v1, v2], [v3, v4]]) => { - format!( - "Shape::TwoByTwo([[{}, {}], [{}, {}]])", - format_option_item(v1, i), - format_option_item(v2, i), - format_option_item(v3, i), - format_option_item(v4, i) - ) - } - Shape::TwoByOne([[v1, v2]]) => { - format!( - "Shape::TwoByOne([[{}, {}]])", - format_option_item(v1, i), - format_option_item(v2, i) - ) - } - Shape::OneByThree([[v1], [v2], [v3]]) => { - format!( - "Shape::OneByThree([[{}], [{}], [{}]])", - format_option_item(v1, i), - format_option_item(v2, i), - format_option_item(v3, i) - ) - } - Shape::OneByTwo([[v1], [v2]]) => { - format!( - "Shape::OneByTwo([[{}], [{}]])", - format_option_item(v1, i), - format_option_item(v2, i) - ) - } - Shape::OneByOne([[v1]]) => { - format!("Shape::OneByOne([[{}]])", format_option_item(v1, i)) - } - } - } - } - - #[derive(Serialize, Deserialize)] - #[serde(untagged)] - enum Recipe { - #[serde(rename_all = "camelCase")] - DoubleShaped { - result: RecipeItem, - in_shape: Shape, - out_shape: Shape, - }, - #[serde(rename_all = "camelCase")] - Shaped { in_shape: Shape, result: RecipeItem }, - #[serde(rename_all = "camelCase")] - ShapeLess { - result: RecipeItem, - ingredients: Vec, - }, - } - - fn item_id_to_item(id: u32, items: &[super::items::Item]) -> String { - for item in items { - if item.id == id { - return item - .text_id - .from_case(Case::Snake) - .to_case(Case::UpperCamel); - } - } - - panic!("Item ID from recipe not found") - } - - pub fn generate_recipes(data: serde_json::Value, items: Vec) { - let item_recipes: HashMap> = - serde_json::from_value(data).expect("Invalid recipes"); - - // Count recipes - let mut recipes_count = 0; - for recipes in item_recipes.values() { - recipes_count += recipes.len(); - } - - // Generate recipes - let mut recipes_data = String::new(); - for recipes in item_recipes.values() { - for recipe in recipes { - match recipe { - Recipe::ShapeLess { - result, - ingredients, - } => { - let mut ingredients_string = String::new(); - for ingredient in ingredients { - ingredients_string.push_str(&ingredient.format(&items)); - ingredients_string.push_str(", "); - } - - recipes_data.push_str(&format!( - "\tRecipe::ShapeLess {{ result: {}, ingredients: &[{}] }},\n", - result.format(&items), - ingredients_string, - )); - } - Recipe::Shaped { result, in_shape } => { - recipes_data.push_str(&format!( - "\tRecipe::Shaped {{ result: {}, in_shape: {} }},\n", - result.format(&items), - in_shape.format(&items), - )); - } - Recipe::DoubleShaped { - result, - in_shape, - out_shape, - } => { - recipes_data.push_str(&format!( - "\tRecipe::DoubleShaped {{ result: {}, in_shape: {}, out_shape: {} }},\n", - result.format(&items), - in_shape.format(&items), - out_shape.format(&items), - )); - } - } - } - } - - // Generate shortcuts - let mut idx_in_array = 0; - let mut shortcuts = Vec::new(); - for item_id in 0..items.len() { - let vec_default = Vec::new(); - let recipes = item_recipes.get(&(item_id as u32)).unwrap_or(&vec_default); - shortcuts.push((idx_in_array, idx_in_array + recipes.len())); - idx_in_array += recipes.len(); - } - - #[allow(clippy::useless_format)] - let code = format!( - r#"//! All crafting recipes - -use crate::ids::items::Item; - -/// An [Item](crate::ids::items::Item) associated with a count of this item -#[derive(Debug, Clone)] -pub struct RecipeItem {{ - pub item: Item, - pub count: u8, -}} - -#[derive(Debug, Clone)] -pub enum Shape {{ - ThreeByThree([[Option; 3]; 3]), - ThreeByTwo([[Option; 3]; 2]), - ThreeByOne([[Option; 3]; 1]), - TwoByThree([[Option; 2]; 3]), - TwoByTwo([[Option; 2]; 2]), - TwoByOne([[Option; 2]; 1]), - OneByThree([[Option; 1]; 3]), - OneByTwo([[Option; 1]; 2]), - OneByOne([[Option; 1]; 1]), -}} - -impl Shape {{ - /// Returns the size of the shape. - /// (width, height) - pub const fn size(&self) -> (u8, u8) {{ - match self {{ - Shape::ThreeByThree(_) => (3, 3), - Shape::ThreeByTwo(_) => (3, 2), - Shape::ThreeByOne(_) => (3, 1), - Shape::TwoByThree(_) => (2, 3), - Shape::TwoByTwo(_) => (2, 2), - Shape::TwoByOne(_) => (2, 1), - Shape::OneByThree(_) => (1, 3), - Shape::OneByTwo(_) => (1, 2), - Shape::OneByOne(_) => (1, 1), - }} - }} -}} - -#[derive(Debug, Clone)] -pub enum Recipe {{ - DoubleShaped {{ in_shape: Shape, out_shape: Shape, result: RecipeItem }}, - Shaped {{ in_shape: Shape, result: RecipeItem }}, - ShapeLess {{ ingredients: &'static [RecipeItem], result: RecipeItem }}, -}} - -impl Recipe {{ - /// Returns all the recipes for an item - #[inline] - pub fn get_recipes_for_item(item: Item) -> &'static [Recipe] {{ - unsafe {{ - let (start, end) = SHORTCUTS.get_unchecked((item as u32) as usize); - RECIPES.get_unchecked(*start..*end) - }} - }} - - #[inline] - pub const fn result(&self) -> &RecipeItem {{ - match self {{ - Recipe::DoubleShaped {{ result, .. }} => result, - Recipe::Shaped {{ result, .. }} => result, - Recipe::ShapeLess {{ result, .. }} => result, - }} - }} - - #[inline] - pub const fn in_shape(&self) -> Option<&Shape> {{ - match self {{ - Recipe::DoubleShaped {{ in_shape, .. }} => Some(in_shape), - Recipe::Shaped {{ in_shape, .. }} => Some(in_shape), - Recipe::ShapeLess {{ .. }} => None, - }} - }} - - #[inline] - pub const fn out_shape(&self) -> Option<&Shape> {{ - match self {{ - Recipe::DoubleShaped {{ out_shape, .. }} => Some(out_shape), - Recipe::Shaped {{ .. }} => None, - Recipe::ShapeLess {{ .. }} => None, - }} - }} - - #[inline] - pub const fn ingredients(&self) -> Option<&'static [RecipeItem]> {{ - match self {{ - Recipe::DoubleShaped {{ .. }} => None, - Recipe::Shaped {{ .. }} => None, - Recipe::ShapeLess {{ ingredients, .. }} => Some(ingredients), - }} - }} -}} - -const RECIPES: [Recipe; {recipes_count}] = [ -{recipes_data} -]; - -const SHORTCUTS: [(usize, usize); {item_count}] = {shortcuts:?}; -"#, - recipes_count = recipes_count, - recipes_data = recipes_data, - item_count = items.len(), - shortcuts = shortcuts, - ); - - File::create("src/ids/recipes.rs") - .unwrap() - .write_all(code.as_bytes()) - .unwrap() - } -} - -fn main() { - println!( - "cargo:rerun-if-changed=target/cache-file-location-{}.json", - VERSION - ); - - let mut file_locations = get_data( - "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/dataPaths.json", - &format!("target/cache-file-location-{}.json", VERSION), - ); - let file_locations = file_locations.get_mut("pc").unwrap().take(); - let file_locations: HashMap> = - serde_json::from_value(file_locations).unwrap(); - let file_locations = file_locations - .get(VERSION) - .expect("There is no generated data for this minecraft version yet"); - - let blocks_url = format!( - "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/blocks.json", - file_locations.get("blocks").unwrap() - ); - let block_data = get_data( - &blocks_url, - &format!("target/cache-blocks-{}.json", VERSION), - ); - blocks::generate_block_enum(block_data.clone()); - blocks::generate_block_with_state_enum(block_data); - - let items_url = format!( - "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/items.json", - file_locations.get("items").unwrap() - ); - let items_data = get_data(&items_url, &format!("target/cache-items-{}.json", VERSION)); - let items = items::generate_item_enum(items_data); - - let entities_url = format!( - "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/entities.json", - file_locations.get("entities").unwrap() - ); - let entities_data = get_data( - &entities_url, - &format!("target/cache-entities-{}.json", VERSION), - ); - entities::generate_entity_enum(entities_data); - - let recipes_url = format!( - "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/recipes.json", - file_locations.get("recipes").unwrap() - ); - let recipes_data = get_data( - &recipes_url, - &format!("target/cache-recipes-{}.json", VERSION), - ); - recipes::generate_recipes(recipes_data, items); -} diff --git a/build/blocks.rs b/build/blocks.rs new file mode 100644 index 00000000..ee412562 --- /dev/null +++ b/build/blocks.rs @@ -0,0 +1,801 @@ +use super::*; + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +struct BlockState { + name: String, + #[serde(rename = "type")] + ty: String, + num_values: usize, + values: Option>, +} + +impl BlockState { + fn ty(&self, block_name: &str, competing_definitions: bool) -> String { + match self.ty.as_str() { + "int" => { + let values: Vec = self + .values + .as_ref() + .expect("No values for int block state") + .iter() + .map(|v| v.parse().expect("Invalid block state value: expected int")) + .collect(); + let mut min_value: i128 = *values.first().unwrap_or(&0); + let mut max_value: i128 = *values.first().unwrap_or(&0); + + for value in values { + if value < min_value { + min_value = value; + } + if value > max_value { + max_value = value; + } + } + + if min_value >= u8::MIN as i128 && max_value <= u8::MAX as i128 { + return String::from("u8"); + } + if min_value >= i8::MIN as i128 && max_value <= i8::MAX as i128 { + return String::from("i8"); + } + if min_value >= u16::MIN as i128 && max_value <= u16::MAX as i128 { + return String::from("u16"); + } + if min_value >= i16::MIN as i128 && max_value <= i16::MAX as i128 { + return String::from("i16"); + } + if min_value >= u32::MIN as i128 && max_value <= u32::MAX as i128 { + return String::from("u32"); + } + if min_value >= i32::MIN as i128 && max_value <= i32::MAX as i128 { + return String::from("i32"); + } + if min_value >= u64::MIN as i128 && max_value <= u64::MAX as i128 { + return String::from("u64"); + } + if min_value >= i64::MIN as i128 && max_value <= i64::MAX as i128 { + return String::from("i64"); + } + String::from("i128") + } + "enum" => match competing_definitions { + true => format!("{}_{}", block_name, self.name), + false => self.name.to_string(), + } + .from_case(Case::Snake) + .to_case(Case::UpperCamel), + "bool" => String::from("bool"), + _ => unimplemented!(), + } + } + + fn define_enum(&self, block_name: &str, competing_definitions: bool) -> String { + if self.ty.as_str() != "enum" { + panic!("Called defined enum on non-enum"); + } + + let mut variants = String::new(); + for (i, value) in self + .values + .as_ref() + .expect("Expecting values in enum (state id)") + .iter() + .enumerate() + { + variants.push_str(&format!( + "\n\t{} = {},", + value.from_case(Case::Snake).to_case(Case::UpperCamel), + i + )); + } + + format!( + r#"#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum {} {{{} +}}"#, + self.ty(block_name, competing_definitions), + variants + ) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Block { + id: u32, + #[serde(rename = "name")] + text_id: String, + display_name: String, + hardness: f32, + resistance: f32, + diggable: bool, + transparent: bool, + filter_light: u8, + emit_light: u8, + default_state: u32, + min_state_id: u32, + max_state_id: u32, + drops: Vec, + material: Option, + #[serde(default)] + harvest_tools: HashMap, + states: Vec, +} + +#[allow(clippy::explicit_counter_loop)] +pub fn generate_block_enum(data: serde_json::Value) { + let mut blocks: Vec = serde_json::from_value(data).expect("Invalid block data"); + blocks.sort_by_key(|block| block.id); + + // Look for missing blocks in the array + let mut expected = 0; + for block in &blocks { + if block.id != expected { + panic!("The block with id {} is missing.", expected) + } + expected += 1; + } + + // Process a few fields + let mut raw_harvest_tools: Vec> = Vec::new(); + let mut raw_materials: Vec = Vec::new(); + for block in &blocks { + raw_harvest_tools.push( + block + .harvest_tools + .clone() + .into_iter() + .map(|(k, _v)| k) + .collect(), + ); + let mut material = block + .material + .clone() + .unwrap_or_else(|| "unknown_material".to_string()) + .split(';') + .next() + .unwrap() + .to_string(); + if material.starts_with("mineable") { + material = "unknown_material".to_string(); + } + raw_materials.push(material.from_case(Case::Snake).to_case(Case::UpperCamel)); + } + + // Generate the MaterialBlock enum and array + let mut different_materials = raw_materials.clone(); + different_materials.sort(); + different_materials.dedup(); + let mut material_variants = String::new(); + for material in different_materials { + material_variants.push_str(&format!("\t{},\n", material)); + } + let mut materials = String::new(); + materials.push('['); + for material in raw_materials { + materials.push_str("Some(BlockMaterial::"); + materials.push_str(&material); + materials.push_str("), "); + } + materials.push(']'); + + // Generate the HARVEST_TOOLS array + let mut harvest_tools = String::new(); + harvest_tools.push('['); + for block_harvest_tools in raw_harvest_tools { + harvest_tools.push_str("&["); + for harvest_tool in block_harvest_tools { + harvest_tools.push_str(&harvest_tool.to_string()); + harvest_tools.push_str(", "); + } + harvest_tools.push_str("], "); + } + harvest_tools.push(']'); + + // Enumerate the air blocks + let mut air_blocks = vec![false; expected as usize]; + for air_block in &[ + "air", + "cave_air", + "grass", + "torch", + "wall_torch", + "wheat", + "soul_torch", + "soul_wall_torch", + "carrots", + "potatoes", + ] { + let mut success = false; + for block in &blocks { + if &block.text_id.as_str() == air_block { + air_blocks[block.id as usize] = true; + success = true; + break; + } + } + if !success { + panic!("Could not find block {} in the block array", air_block); + } + } + + // Generate the variants of the Block enum + let mut variants = String::new(); + for block in &blocks { + let name = block + .text_id + .from_case(Case::Snake) + .to_case(Case::UpperCamel); + variants.push_str(&format!("\t{} = {},\n", name, block.id)); + } + + // Generate the `match` of state ids + let mut state_id_match_arms = String::new(); + for block in &blocks { + let name = block + .text_id + .from_case(Case::Snake) + .to_case(Case::UpperCamel); + let start = block.min_state_id; + let stop = block.max_state_id; + if start != stop { + state_id_match_arms.push_str(&format!( + "\t\t\t{}..={} => Some(Block::{}),\n", + start, stop, name + )); + } else { + state_id_match_arms.push_str(&format!("\t\t\t{} => Some(Block::{}),\n", start, name)); + } + } + + // Generate the code + let code = format!( + r#"use crate::*; + +/// See [implementations](#implementations) for useful methods. +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Block {{ +{variants} +}} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BlockMaterial {{ +{material_variants} +}} + +impl Block {{ + #[inline] + pub fn from_id(id: u32) -> Option {{ + if id < {max_value} {{ + Some(unsafe{{std::mem::transmute(id)}}) + }} else {{ + None + }} + }} + + pub fn from_state_id(state_id: u32) -> Option {{ + match state_id {{ +{state_id_match_arms} + _ => None, + }} + }} + + /// Get the textual identifier of this block. + #[inline] + pub fn text_id(self) -> &'static str {{ + unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn default_state_id(self) -> u32 {{ + unsafe {{*DEFAULT_STATE_IDS.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn id(self) -> u32 {{ + self as u32 + }} + + /// This returns the item that will be dropped if you break the block. + /// If the item is Air, there is actually no drop. + #[inline] + pub fn associated_item_id(self) -> u32 {{ + unsafe {{*ITEM_IDS.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn resistance(self) -> f32 {{ + unsafe {{*RESISTANCES.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn hardness(self) -> f32 {{ + unsafe {{*HARDNESSES.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn material(self) -> Option {{ + unsafe {{*MATERIALS.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn display_name(self) -> &'static str {{ + unsafe {{*DISPLAY_NAMES.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn state_id_range(self) -> std::ops::Range {{ + unsafe {{STATE_ID_RANGES.get_unchecked((self as u32) as usize).clone()}} + }} + + #[inline] + pub fn is_diggable(self) -> bool {{ + unsafe {{*DIGGABLE.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn is_transparent(self) -> bool {{ + unsafe {{*TRANSPARENT.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn compatible_harvest_tools(self) -> &'static [u32] {{ + unsafe {{*HARVEST_TOOLS.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn light_emissions(self) -> u8 {{ + unsafe {{*LIGHT_EMISSIONS.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn light_absorption(self) -> u8 {{ + unsafe {{*LIGHT_ABSORPTION.get_unchecked((self as u32) as usize)}} + }} + + /// A "air block" is a block on which a player cannot stand, like air, wheat, torch... + /// Fire is excluded since you may not want your clients to walk trought fire by default. + /// The list of air blocks is maintained by hand. + /// It could not be exhaustive. + /// See also [Block::is_blocking]. + #[inline] + pub fn is_air_block(self) -> bool {{ + unsafe {{*AIR_BLOCKS.get_unchecked((self as u32) as usize)}} + }} + + /// The opposite of [Block::is_air_block]. + /// Fire is included since you may not want your clients to walk trought fire by default. + /// The list of blocking blocks is maintained by hand. + /// It could not be exhaustive. + #[inline] + pub fn is_blocking(self) -> bool {{ + unsafe {{!(*AIR_BLOCKS.get_unchecked((self as u32) as usize))}} + }} +}} + +impl From for Block {{ + #[inline] + fn from(block_with_state: super::block_states::BlockWithState) -> Block {{ + unsafe {{std::mem::transmute(block_with_state.block_id())}} + }} +}} + +impl<'a> MinecraftPacketPart<'a> for Block {{ + #[inline] + fn serialize_minecraft_packet_part(self, output: &mut Vec) -> Result<(), &'static str> {{ + VarInt((self as u32) as i32).serialize_minecraft_packet_part(output) + }} + + #[inline] + fn deserialize_minecraft_packet_part(input: &'a[u8]) -> Result<(Self, &'a[u8]), &'static str> {{ + let (id, input) = VarInt::deserialize_minecraft_packet_part(input)?; + let id = std::cmp::max(id.0, 0) as u32; + let block = Block::from_id(id).ok_or("No block corresponding to the specified numeric ID.")?; + Ok((block, input)) + }} +}} + +const TEXT_IDS: [&str; {max_value}] = {text_ids:?}; +const DISPLAY_NAMES: [&str; {max_value}] = {display_names:?}; +const STATE_ID_RANGES: [std::ops::Range; {max_value}] = {state_id_ranges:?}; +const DEFAULT_STATE_IDS: [u32; {max_value}] = {default_state_ids:?}; +const ITEM_IDS: [u32; {max_value}] = {item_ids:?}; +const RESISTANCES: [f32; {max_value}] = {resistances:?}; +const MATERIALS: [Option; {max_value}] = {materials}; +const HARVEST_TOOLS: [&[u32]; {max_value}] = {harvest_tools}; +const HARDNESSES: [f32; {max_value}] = {hardnesses:?}; +const LIGHT_EMISSIONS: [u8; {max_value}] = {light_emissions:?}; +const LIGHT_ABSORPTION: [u8; {max_value}] = {light_absorption:?}; +const DIGGABLE: [bool; {max_value}] = {diggable:?}; +const TRANSPARENT: [bool; {max_value}] = {transparent:?}; +const AIR_BLOCKS: [bool; {max_value}] = {air_blocks:?}; +"#, + variants = variants, + material_variants = material_variants, + max_value = expected, + state_id_match_arms = state_id_match_arms, + text_ids = blocks.iter().map(|b| &b.text_id).collect::>(), + display_names = blocks.iter().map(|b| &b.display_name).collect::>(), + state_id_ranges = blocks + .iter() + .map(|b| b.min_state_id..b.max_state_id + 1) + .collect::>(), + default_state_ids = blocks.iter().map(|b| b.default_state).collect::>(), + item_ids = blocks + .iter() + .map(|b| b.drops.get(0).copied().unwrap_or(0)) + .collect::>(), + materials = materials, + resistances = blocks.iter().map(|b| b.resistance).collect::>(), + harvest_tools = harvest_tools, + hardnesses = blocks.iter().map(|b| b.hardness).collect::>(), + light_emissions = blocks.iter().map(|b| b.emit_light).collect::>(), + light_absorption = blocks.iter().map(|b| b.filter_light).collect::>(), + diggable = blocks.iter().map(|b| b.diggable).collect::>(), + transparent = blocks.iter().map(|b| b.transparent).collect::>(), + air_blocks = air_blocks, + ); + + File::create("src/ids/blocks.rs") + .unwrap() + .write_all(code.as_bytes()) + .unwrap() +} + +#[allow(clippy::explicit_counter_loop)] +pub fn generate_block_with_state_enum(data: serde_json::Value) { + let mut blocks: Vec = serde_json::from_value(data).expect("Invalid block data"); + blocks.sort_by_key(|block| block.min_state_id); + + // Look for missing blocks in the array + let mut expected = 0; + for block in &blocks { + if block.id != expected { + panic!("The block with id {} is missing.", expected) + } + expected += 1; + } + + // Generate the enum definitions + let mut enum_definitions = Vec::new(); + let mut enum_definitions_string = String::new(); + let mut already_defined_enums = Vec::new(); + for block in &blocks { + for state in &block.states { + if state.ty.as_str() == "enum" { + enum_definitions.push((&block.text_id, state)); + } + } + } + for (block_name, enum_definition) in &enum_definitions { + let mut competing_definitions = false; + for (_, enum_definition2) in &enum_definitions { + if enum_definition.name == enum_definition2.name + && enum_definition.values != enum_definition2.values + { + competing_definitions = true; + break; + } + } + if !already_defined_enums.contains(&enum_definition.ty(block_name, competing_definitions)) { + enum_definitions_string + .push_str(&enum_definition.define_enum(block_name, competing_definitions)); + enum_definitions_string.push('\n'); + enum_definitions_string.push('\n'); + + already_defined_enums.push(enum_definition.ty(block_name, competing_definitions)); + } + } + + // Generate the variants of the Block enum + let mut variants = String::new(); + for block in &blocks { + let name = block + .text_id + .from_case(Case::Snake) + .to_case(Case::UpperCamel); + let mut fields = String::new(); + for state in &block.states { + let name = match state.name.as_str() == "type" { + true => "ty", + false => state.name.as_str(), + }; + let competing_definitions = + already_defined_enums.contains(&state.ty(&block.text_id, true)); + fields.push_str(&format!( + "{}: {}, ", + name, + state.ty(&block.text_id, competing_definitions) + )); + } + if fields.is_empty() { + variants.push_str(&format!("\t{},\n", name)); + } else { + variants.push_str(&format!("\t{}{{ {}}},\n", name, fields)); + } + } + + // Generate the `match` of state ids + let mut state_id_match_arms = String::new(); + let mut state_id_rebuild_arms = String::new(); + for block in &blocks { + let name = block + .text_id + .from_case(Case::Snake) + .to_case(Case::UpperCamel); + let start = block.min_state_id; + let stop = block.max_state_id; + + if block.states.is_empty() { + state_id_match_arms.push_str(&format!( + "\n\t\t\t{} => Some(BlockWithState::{}),", + start, name + )); + state_id_rebuild_arms.push_str(&format!( + "\n\t\t\tBlockWithState::{} => Some({}),", + name, start + )); + continue; + } + + let mut state_calculations = String::new(); + let mut fields = String::new(); + for (i, state) in block.states.iter().enumerate().rev() { + let competing_definitions = + already_defined_enums.contains(&state.ty(&block.text_id, true)); + let ty = state.ty(&block.text_id, competing_definitions); + let name = match state.name.as_str() { + "type" => "ty", + _ => &state.name, + }; + fields.push_str(&format!("{}, ", name)); + + if i == 0 { + state_calculations.push_str("\n\t\t\t\tlet field_value = state_id;"); + } else { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet field_value = state_id.rem_euclid({});\ + \n\t\t\t\tstate_id -= field_value;\ + \n\t\t\t\tstate_id /= {};", + state.num_values, state.num_values + )); + } + + match state.ty.as_str() { + "enum" => { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet {}: {} = unsafe{{std::mem::transmute(field_value as u8)}};\n", + name, ty + )); + } + "int" => { + let values: Vec = state + .values + .as_ref() + .expect("No values for int block state") + .iter() + .map(|v| v.parse().expect("Invalid block state value: expected int")) + .collect(); + + let mut expected = values[0]; + let mut standard = true; + for value in &values { + if value != &expected { + standard = false; + break; + } + expected += 1; + } + + if standard && values[0] == 0 { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet {}: {} = field_value as {};\n", + name, ty, ty + )); + } else if standard { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet {}: {} = {} + field_value as {};\n", + name, ty, values[0], ty + )); + } else { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet {}: {} = {:?}[field_value as usize];\n", + name, ty, values + )); + } + } + "bool" => { + state_calculations.push_str(&format!( + "\n\t\t\t\tlet {}: bool = field_value == 0;\n", + name + )); + } + other => panic!("Unknown {} type", other), + } + } + + let mut state_reformation = String::new(); + for (i, state) in block.states.iter().enumerate() { + let name = match state.name.as_str() { + "type" => "ty", + _ => &state.name, + }; + + match state.ty.as_str() { + "enum" => { + state_reformation.push_str(&format!( + "\n\t\t\t\tlet field_value = (*{} as u8) as u32;", + name + )); + } + "int" => { + let values: Vec = state + .values + .as_ref() + .expect("No values for int block state") + .iter() + .map(|v| v.parse().expect("Invalid block state value: expected int")) + .collect(); + + let mut expected = values[0]; + let mut standard = true; + for value in &values { + if value != &expected { + standard = false; + break; + } + expected += 1; + } + + if standard && values[0] == 0 { + state_reformation.push_str(&format!( + "\n\t\t\t\tif *{name} > {max} {{ return None }}\ + \n\t\t\t\tlet field_value = *{name} as u32;", + name = name, + max = values.last().unwrap() + )); + } else if standard { + state_reformation.push_str(&format!( + "\n\t\t\t\tif *{name} < {min} || *{name} > {max} {{ return None }}\ + \n\t\t\t\tlet field_value = ({name} - {min}) as u32;", + name = name, + min = values[0], + max = values.last().unwrap() + )); + } else { + state_reformation.push_str(&format!( + "\n\t\t\t\tlet field_value = {:?}.find({})?;", + values, name + )); + } + } + "bool" => { + state_reformation.push_str(&format!( + "\n\t\t\t\tlet field_value = if *{} {{0}} else {{1}};", + name + )); + } + other => panic!("Unknown {} type", other), + } + + if i == 0 { + state_reformation.push_str("\n\t\t\t\tlet mut state_id = field_value;\n"); + } else { + state_reformation.push_str(&format!( + "\n\t\t\t\tstate_id *= {};\ + \n\t\t\t\tstate_id += field_value;\n", + state.num_values + )); + } + } + + state_id_match_arms.push_str(&format!( + " + {}..={} => {{ + state_id -= {}; + {} + Some(BlockWithState::{}{{ {}}}) + }},", + start, stop, start, state_calculations, name, fields + )); + state_id_rebuild_arms.push_str(&format!( + " + BlockWithState::{}{{ {}}} => {{ + {} + state_id += {}; + Some(state_id) + }},", + name, fields, state_reformation, start + )); + } + + // Generate the code + let code = format!( + r#"//! Contains the [BlockWithState] enum to help with block state IDs. + +use crate::*; + +{enum_definitions} + +/// Can be converted for free to [super::blocks::Block] which implements [useful methods](super::blocks::Block#implementations). +#[derive(Debug, Clone)] +#[repr(u32)] +pub enum BlockWithState {{ +{variants} +}} + +impl BlockWithState {{ + #[inline] + pub fn from_state_id(mut state_id: u32) -> Option {{ + match state_id {{ +{state_id_match_arms} + _ => None, + }} + }} + + /// Returns the block id, **not the block state id**. + #[inline] + pub fn block_id(&self) -> u32 {{ + unsafe {{std::mem::transmute(std::mem::discriminant(self))}} + }} + + /// Returns the block state id. + /// Returns None in case of error (invalid field value). + #[inline] + pub fn block_state_id(&self) -> Option {{ + match self {{ +{state_id_rebuild_arms} + }} + }} +}} + +impl From for BlockWithState {{ + #[inline] + fn from(block: super::blocks::Block) -> BlockWithState {{ + BlockWithState::from_state_id(block.default_state_id()).unwrap() // TODO: unwrap unchecked + }} +}} + +impl<'a> MinecraftPacketPart<'a> for BlockWithState {{ + #[inline] + fn serialize_minecraft_packet_part(self, _output: &mut Vec) -> Result<(), &'static str> {{ + unimplemented!("Cannot serialize BlockWithState yet"); + }} + + #[inline] + fn deserialize_minecraft_packet_part(input: &'a[u8]) -> Result<(Self, &'a[u8]), &'static str> {{ + let (id, input) = VarInt::deserialize_minecraft_packet_part(input)?; + let id = std::cmp::max(id.0, 0) as u32; + let block_with_state = BlockWithState::from_state_id(id).ok_or("No block corresponding to the specified block state ID.")?; + Ok((block_with_state, input)) + }} +}} + +#[cfg(test)] +mod tests {{ + use super::*; + + #[test] + fn test_block_states() {{ + for id in 0..={max_block_state_id} {{ + let block = BlockWithState::from_state_id(id).unwrap(); + let id_from_block = block.block_state_id().unwrap(); + assert_eq!(id, id_from_block); + }} + }} +}} +"#, + enum_definitions = enum_definitions_string, + state_id_match_arms = state_id_match_arms, + state_id_rebuild_arms = state_id_rebuild_arms, + variants = variants, + max_block_state_id = blocks.last().unwrap().max_state_id + ); + + File::create("src/ids/block_states.rs") + .unwrap() + .write_all(code.as_bytes()) + .unwrap() +} diff --git a/build/build.rs b/build/build.rs new file mode 100644 index 00000000..ab3b38c0 --- /dev/null +++ b/build/build.rs @@ -0,0 +1,127 @@ +mod blocks; +mod entities; +mod items; +mod recipes; + +use convert_case::{Case, Casing}; +use serde::{Deserialize, Serialize}; +use std::io::{ErrorKind, Read, Write}; +use std::{collections::HashMap, fs::File}; + +const VERSION: &str = "1.17.1"; + +fn get_data(url: &str, cache: &str) -> serde_json::Value { + match File::open(cache) { + // The cache file is ready + Ok(mut file) => { + let mut data = Vec::new(); + if let Err(e) = file.read_to_end(&mut data) { + panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, this file cannot be read. Error: {}", cache, e) + } + + let json_text = match String::from_utf8(data) { + Ok(json_text) => json_text, + Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, this file appears to contain invalid text data. Error: {}\nNote: Deleting the file will allow the library to download it again.", cache, e), + }; + + let json = match serde_json::from_str(&json_text) { + Ok(json) => json, + Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, this file appears to contain invalid json data. Error: {}\nNote: Deleting the file will allow the library to download it again.", cache, e), + }; + + json + } + // The cache file needs to be downloaded + Err(e) if e.kind() == ErrorKind::NotFound => { + let response = match minreq::get(url).send() { + Ok(response) => response, + Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded from `{}`. Unfortunately, we can't access this URL. Error: {}", url, e) + }; + + let json_text = match response.as_str() { + Ok(json_text) => json_text, + Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded from `{}`. Unfortunately, this file appears to contain invalid data. Error: {}", url, e), + }; + + let mut file = match File::create(cache) { + Ok(file) => file, + Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, we can't access this path. Error: {}", cache, e), + }; + + if let Err(e) = file.write_all(json_text.as_bytes()) { + panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, we can't write to this path. Error: {}", cache, e) + }; + + let json = match serde_json::from_str(json_text) { + Ok(json) => json, + Err(e) => panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, this file appears to contain invalid json data. Error: {}\nNote: Deleting the file will allow the library to download it again.", cache, e), + }; + + json + } + + // The cache file cannot be accessed + Err(e) => { + panic!("The minecraft-format library uses a build script to generate data structures from extracted data. The extracted data is downloaded and cached to `{}`. Unfortunately, we can't access this path. Error: {}", cache, e); + } + } +} + +fn main() { + println!( + "cargo:rerun-if-changed=target/cache-file-location-{}.json", + VERSION + ); + println!( + "cargo:rerun-if-changed=build" + ); + + let mut file_locations = get_data( + "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/dataPaths.json", + &format!("target/cache-file-location-{}.json", VERSION), + ); + let file_locations = file_locations.get_mut("pc").unwrap().take(); + let file_locations: HashMap> = + serde_json::from_value(file_locations).unwrap(); + let file_locations = file_locations + .get(VERSION) + .expect("There is no generated data for this minecraft version yet"); + + let blocks_url = format!( + "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/blocks.json", + file_locations.get("blocks").unwrap() + ); + let block_data = get_data( + &blocks_url, + &format!("target/cache-blocks-{}.json", VERSION), + ); + blocks::generate_block_enum(block_data.clone()); + blocks::generate_block_with_state_enum(block_data); + + let items_url = format!( + "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/items.json", + file_locations.get("items").unwrap() + ); + let items_data = get_data(&items_url, &format!("target/cache-items-{}.json", VERSION)); + let items = items::generate_item_enum(items_data); + + let entities_url = format!( + "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/entities.json", + file_locations.get("entities").unwrap() + ); + let entities_data = get_data( + &entities_url, + &format!("target/cache-entities-{}.json", VERSION), + ); + entities::generate_entity_enum(entities_data); + + let recipes_url = format!( + "https://github.com/PrismarineJS/minecraft-data/raw/master/data/{}/recipes.json", + file_locations.get("recipes").unwrap() + ); + let recipes_data = get_data( + &recipes_url, + &format!("target/cache-recipes-{}.json", VERSION), + ); + recipes::generate_recipes(recipes_data, items); +} diff --git a/build/entities.rs b/build/entities.rs new file mode 100644 index 00000000..57707ebb --- /dev/null +++ b/build/entities.rs @@ -0,0 +1,161 @@ +use super::*; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Entity { + id: u32, + #[serde(rename = "name")] + text_id: String, + display_name: String, + width: f32, + height: f32, + #[serde(rename = "type")] + category: String, +} + +pub fn generate_entity_enum(data: serde_json::Value) { + let mut entities: Vec = serde_json::from_value(data).expect("Invalid entity data"); + entities.sort_by_key(|entity| entity.id); + + // Look for missing items in the array + let mut expected = 0; + for entity in &entities { + if entity.id != expected { + panic!("The entity with id {} is missing.", expected) + } + expected += 1; + } + + // Generate the categories array + let mut categories = String::new(); + categories.push('['); + for entity in &entities { + let variant_name = match entity.category.as_str() { + "other" => "Other", + "living" => "Living", + "projectile" => "Projectile", + "animal" => "Animal", + "ambient" => "Ambient", + "hostile" => "Hostile", + "water_creature" => "WaterCreature", + "mob" => "Mob", + "passive" => "Passive", + "player" => "Player", + unknown_category => panic!("Unknown entity category {}", unknown_category), + }; + categories.push_str("EntityCategory::"); + categories.push_str(variant_name); + categories.push_str(", "); + } + categories.push(']'); + + // Generate the variants of the Item enum + let mut variants = String::new(); + for entity in &entities { + let name = entity + .text_id + .from_case(Case::Snake) + .to_case(Case::UpperCamel); + variants.push_str(&format!("\t{} = {},\n", name, entity.id)); + } + + // Generate the code + let code = format!( + r#"use crate::*; + +/// See [implementations](#implementations) for useful methods. +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Entity {{ +{variants} +}} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum EntityCategory {{ + Other, + Living, + Projectile, + Animal, + Ambient, + Hostile, + WaterCreature, + Mob, + Passive, + Player, +}} + +impl Entity {{ + #[inline] + pub fn from_id(id: u32) -> Option {{ + if id < {max_value} {{ + Some(unsafe{{std::mem::transmute(id)}}) + }} else {{ + None + }} + }} + + #[inline] + pub fn text_id(self) -> &'static str {{ + unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn display_name(self) -> &'static str {{ + unsafe {{*DISPLAY_NAMES.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn category(self) -> EntityCategory {{ + unsafe {{*CATEGORIES.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn height(self) -> f32 {{ + unsafe {{*HEIGHTS.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn width(self) -> f32 {{ + unsafe {{*WIDTHS.get_unchecked((self as u32) as usize)}} + }} +}} + +impl<'a> MinecraftPacketPart<'a> for Entity {{ + #[inline] + fn serialize_minecraft_packet_part(self, output: &mut Vec) -> Result<(), &'static str> {{ + VarInt((self as u32) as i32).serialize_minecraft_packet_part(output) + }} + + #[inline] + fn deserialize_minecraft_packet_part(input: &'a[u8]) -> Result<(Self, &'a[u8]), &'static str> {{ + let (id, input) = VarInt::deserialize_minecraft_packet_part(input)?; + let id = std::cmp::max(id.0, 0) as u32; + let entity = Entity::from_id(id).ok_or("No entity corresponding to the specified numeric ID.")?; + Ok((entity, input)) + }} +}} + +const HEIGHTS: [f32; {max_value}] = {heights:?}; + +const WIDTHS: [f32; {max_value}] = {widths:?}; + +const DISPLAY_NAMES: [&str; {max_value}] = {display_names:?}; + +const TEXT_IDS: [&str; {max_value}] = {text_ids:?}; + +const CATEGORIES: [EntityCategory; {max_value}] = {categories}; +"#, + variants = variants, + max_value = expected, + heights = entities.iter().map(|e| e.height).collect::>(), + widths = entities.iter().map(|e| e.width).collect::>(), + display_names = entities.iter().map(|e| &e.display_name).collect::>(), + text_ids = entities.iter().map(|e| &e.text_id).collect::>(), + categories = categories, + ); + + File::create("src/ids/entities.rs") + .unwrap() + .write_all(code.as_bytes()) + .unwrap() +} diff --git a/build/items.rs b/build/items.rs new file mode 100644 index 00000000..fb47cabe --- /dev/null +++ b/build/items.rs @@ -0,0 +1,136 @@ +use super::*; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Item { + pub id: u32, + display_name: String, + #[serde(rename = "name")] + pub text_id: String, + stack_size: u8, + max_durability: Option, +} + +#[allow(clippy::explicit_counter_loop)] +pub fn generate_item_enum(data: serde_json::Value) -> Vec { + let mut items: Vec = serde_json::from_value(data).expect("Invalid block data"); + items.sort_by_key(|item| item.id); + + // Patch the missing Air + if items.first().map(|i| i.id) != Some(0) { + items.insert( + 0, + Item { + id: 0, + display_name: String::from("Air"), + text_id: String::from("air"), + stack_size: 64, + max_durability: None, + }, + ); + } + + // Look for missing items in the array + let mut expected = 0; + for item in &items { + if item.id != expected { + panic!("The item with id {} is missing.", expected) + } + expected += 1; + } + + // Generate the variants of the Item enum + let mut variants = String::new(); + for item in &items { + let name = item + .text_id + .from_case(Case::Snake) + .to_case(Case::UpperCamel); + variants.push_str(&format!("\t{} = {},\n", name, item.id)); + } + + // Generate the code + let code = format!( + r#"use crate::*; + +/// See [implementations](#implementations) for useful methods. +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Item {{ +{variants} +}} + +impl Item {{ + #[inline] + pub fn from_id(id: u32) -> Option {{ + if id < {max_value} {{ + Some(unsafe{{std::mem::transmute(id)}}) + }} else {{ + None + }} + }} + + #[inline] + pub fn text_id(self) -> &'static str {{ + unsafe {{*TEXT_IDS.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn display_name(self) -> &'static str {{ + unsafe {{*DISPLAY_NAMES.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn max_stack_size(self) -> u8 {{ + unsafe {{*STACK_SIZES.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn durability(self) -> Option {{ + unsafe {{*DURABILITIES.get_unchecked((self as u32) as usize)}} + }} + + #[inline] + pub fn crafting_recipes(&self) -> &'static [crate::ids::recipes::Recipe] {{ + crate::ids::recipes::Recipe::get_recipes_for_item(*self) + }} +}} + +impl<'a> MinecraftPacketPart<'a> for Item {{ + #[inline] + fn serialize_minecraft_packet_part(self, output: &mut Vec) -> Result<(), &'static str> {{ + VarInt((self as u32) as i32).serialize_minecraft_packet_part(output) + }} + + #[inline] + fn deserialize_minecraft_packet_part(input: &'a[u8]) -> Result<(Self, &'a[u8]), &'static str> {{ + let (id, input) = VarInt::deserialize_minecraft_packet_part(input)?; + let id = std::cmp::max(id.0, 0) as u32; + let item = Item::from_id(id).ok_or("No item corresponding to the specified numeric ID.")?; + Ok((item, input)) + }} +}} + +const STACK_SIZES: [u8; {max_value}] = {max_stack_sizes:?}; + +const DURABILITIES: [Option; {max_value}] = {durabilities:?}; + +const DISPLAY_NAMES: [&str; {max_value}] = {display_names:?}; + +const TEXT_IDS: [&str; {max_value}] = {text_ids:?}; +"#, + variants = variants, + max_value = expected, + max_stack_sizes = items.iter().map(|i| i.stack_size).collect::>(), + durabilities = items.iter().map(|i| i.max_durability).collect::>(), + display_names = items.iter().map(|i| &i.display_name).collect::>(), + text_ids = items.iter().map(|i| &i.text_id).collect::>(), + ); + + File::create("src/ids/items.rs") + .unwrap() + .write_all(code.as_bytes()) + .unwrap(); + + items +} diff --git a/build/recipes.rs b/build/recipes.rs new file mode 100644 index 00000000..81718296 --- /dev/null +++ b/build/recipes.rs @@ -0,0 +1,352 @@ +use convert_case::{Case, Casing}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +enum RecipeItem { + IDAndMetadataAndCount { id: u32, metadata: u32, count: u8 }, + IDAndMetadata { id: u32, metadata: u32 }, + IDAndCount { id: u32, count: u8 }, + ID(u32), +} + +impl RecipeItem { + fn to_id_and_count(&self) -> (u32, u8) { + match self { + RecipeItem::IDAndMetadataAndCount { .. } => panic!("Metadata not handled"), + RecipeItem::IDAndMetadata { .. } => panic!("Metadata not handled"), + RecipeItem::IDAndCount { id, count } => (*id, *count), + RecipeItem::ID(id) => (*id, 1), + } + } + + fn format(&self, items: &[super::items::Item]) -> String { + let (id, count) = self.to_id_and_count(); + let item_ident = item_id_to_item(id, items); + format!( + "RecipeItem {{item: Item::{}, count: {}}}", + item_ident, count + ) + } +} + +fn format_option_item(item: &Option, items: &[super::items::Item]) -> String { + match item { + Some(item) => format!("Some({})", item.format(items)), + None => "None".to_string(), + } +} + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +enum Shape { + ThreeByThree([[Option; 3]; 3]), + ThreeByTwo([[Option; 3]; 2]), + ThreeByOne([[Option; 3]; 1]), + TwoByThree([[Option; 2]; 3]), + TwoByTwo([[Option; 2]; 2]), + TwoByOne([[Option; 2]; 1]), + OneByThree([[Option; 1]; 3]), + OneByTwo([[Option; 1]; 2]), + OneByOne([[Option; 1]; 1]), +} + +impl Shape { + fn format(&self, i: &[super::items::Item]) -> String { + match self { + Shape::ThreeByThree([[v1, v2, v3], [v4, v5, v6], [v7, v8, v9]]) => { + format!( + "Shape::ThreeByThree([[{}, {}, {}], [{}, {}, {}], [{}, {}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i), + format_option_item(v4, i), + format_option_item(v5, i), + format_option_item(v6, i), + format_option_item(v7, i), + format_option_item(v8, i), + format_option_item(v9, i) + ) + } + Shape::ThreeByTwo([[v1, v2, v3], [v4, v5, v6]]) => { + format!( + "Shape::ThreeByTwo([[{}, {}, {}], [{}, {}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i), + format_option_item(v4, i), + format_option_item(v5, i), + format_option_item(v6, i) + ) + } + Shape::ThreeByOne([[v1, v2, v3]]) => { + format!( + "Shape::ThreeByOne([[{}, {}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i) + ) + } + Shape::TwoByThree([[v1, v2], [v3, v4], [v5, v6]]) => { + format!( + "Shape::TwoByThree([[{}, {}], [{}, {}], [{}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i), + format_option_item(v4, i), + format_option_item(v5, i), + format_option_item(v6, i) + ) + } + Shape::TwoByTwo([[v1, v2], [v3, v4]]) => { + format!( + "Shape::TwoByTwo([[{}, {}], [{}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i), + format_option_item(v4, i) + ) + } + Shape::TwoByOne([[v1, v2]]) => { + format!( + "Shape::TwoByOne([[{}, {}]])", + format_option_item(v1, i), + format_option_item(v2, i) + ) + } + Shape::OneByThree([[v1], [v2], [v3]]) => { + format!( + "Shape::OneByThree([[{}], [{}], [{}]])", + format_option_item(v1, i), + format_option_item(v2, i), + format_option_item(v3, i) + ) + } + Shape::OneByTwo([[v1], [v2]]) => { + format!( + "Shape::OneByTwo([[{}], [{}]])", + format_option_item(v1, i), + format_option_item(v2, i) + ) + } + Shape::OneByOne([[v1]]) => { + format!("Shape::OneByOne([[{}]])", format_option_item(v1, i)) + } + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +enum Recipe { + #[serde(rename_all = "camelCase")] + DoubleShaped { + result: RecipeItem, + in_shape: Shape, + out_shape: Shape, + }, + #[serde(rename_all = "camelCase")] + Shaped { in_shape: Shape, result: RecipeItem }, + #[serde(rename_all = "camelCase")] + ShapeLess { + result: RecipeItem, + ingredients: Vec, + }, +} + +fn item_id_to_item(id: u32, items: &[super::items::Item]) -> String { + for item in items { + if item.id == id { + return item + .text_id + .from_case(Case::Snake) + .to_case(Case::UpperCamel); + } + } + + panic!("Item ID from recipe not found") +} + +pub fn generate_recipes(data: serde_json::Value, items: Vec) { + let item_recipes: HashMap> = + serde_json::from_value(data).expect("Invalid recipes"); + + // Count recipes + let mut recipes_count = 0; + for recipes in item_recipes.values() { + recipes_count += recipes.len(); + } + + // Generate recipes + let mut recipes_data = String::new(); + for recipes in item_recipes.values() { + for recipe in recipes { + match recipe { + Recipe::ShapeLess { + result, + ingredients, + } => { + let mut ingredients_string = String::new(); + for ingredient in ingredients { + ingredients_string.push_str(&ingredient.format(&items)); + ingredients_string.push_str(", "); + } + + recipes_data.push_str(&format!( + "\tRecipe::ShapeLess {{ result: {}, ingredients: &[{}] }},\n", + result.format(&items), + ingredients_string, + )); + } + Recipe::Shaped { result, in_shape } => { + recipes_data.push_str(&format!( + "\tRecipe::Shaped {{ result: {}, in_shape: {} }},\n", + result.format(&items), + in_shape.format(&items), + )); + } + Recipe::DoubleShaped { + result, + in_shape, + out_shape, + } => { + recipes_data.push_str(&format!( + "\tRecipe::DoubleShaped {{ result: {}, in_shape: {}, out_shape: {} }},\n", + result.format(&items), + in_shape.format(&items), + out_shape.format(&items), + )); + } + } + } + } + + // Generate shortcuts + let mut idx_in_array = 0; + let mut shortcuts = Vec::new(); + for item_id in 0..items.len() { + let vec_default = Vec::new(); + let recipes = item_recipes.get(&(item_id as u32)).unwrap_or(&vec_default); + shortcuts.push((idx_in_array, idx_in_array + recipes.len())); + idx_in_array += recipes.len(); + } + + #[allow(clippy::useless_format)] + let code = format!( + r#"//! All crafting recipes + +use crate::ids::items::Item; + +/// An [Item](crate::ids::items::Item) associated with a count of this item +#[derive(Debug, Clone)] +pub struct RecipeItem {{ + pub item: Item, + pub count: u8, +}} + +#[derive(Debug, Clone)] +pub enum Shape {{ + ThreeByThree([[Option; 3]; 3]), + ThreeByTwo([[Option; 3]; 2]), + ThreeByOne([[Option; 3]; 1]), + TwoByThree([[Option; 2]; 3]), + TwoByTwo([[Option; 2]; 2]), + TwoByOne([[Option; 2]; 1]), + OneByThree([[Option; 1]; 3]), + OneByTwo([[Option; 1]; 2]), + OneByOne([[Option; 1]; 1]), +}} + +impl Shape {{ + /// Returns the size of the shape. + /// (width, height) + pub const fn size(&self) -> (u8, u8) {{ + match self {{ + Shape::ThreeByThree(_) => (3, 3), + Shape::ThreeByTwo(_) => (3, 2), + Shape::ThreeByOne(_) => (3, 1), + Shape::TwoByThree(_) => (2, 3), + Shape::TwoByTwo(_) => (2, 2), + Shape::TwoByOne(_) => (2, 1), + Shape::OneByThree(_) => (1, 3), + Shape::OneByTwo(_) => (1, 2), + Shape::OneByOne(_) => (1, 1), + }} + }} +}} + +#[derive(Debug, Clone)] +pub enum Recipe {{ + DoubleShaped {{ in_shape: Shape, out_shape: Shape, result: RecipeItem }}, + Shaped {{ in_shape: Shape, result: RecipeItem }}, + ShapeLess {{ ingredients: &'static [RecipeItem], result: RecipeItem }}, +}} + +impl Recipe {{ + /// Returns all the recipes for an item + #[inline] + pub fn get_recipes_for_item(item: Item) -> &'static [Recipe] {{ + unsafe {{ + let (start, end) = SHORTCUTS.get_unchecked((item as u32) as usize); + RECIPES.get_unchecked(*start..*end) + }} + }} + + #[inline] + pub const fn result(&self) -> &RecipeItem {{ + match self {{ + Recipe::DoubleShaped {{ result, .. }} => result, + Recipe::Shaped {{ result, .. }} => result, + Recipe::ShapeLess {{ result, .. }} => result, + }} + }} + + #[inline] + pub const fn in_shape(&self) -> Option<&Shape> {{ + match self {{ + Recipe::DoubleShaped {{ in_shape, .. }} => Some(in_shape), + Recipe::Shaped {{ in_shape, .. }} => Some(in_shape), + Recipe::ShapeLess {{ .. }} => None, + }} + }} + + #[inline] + pub const fn out_shape(&self) -> Option<&Shape> {{ + match self {{ + Recipe::DoubleShaped {{ out_shape, .. }} => Some(out_shape), + Recipe::Shaped {{ .. }} => None, + Recipe::ShapeLess {{ .. }} => None, + }} + }} + + #[inline] + pub const fn ingredients(&self) -> Option<&'static [RecipeItem]> {{ + match self {{ + Recipe::DoubleShaped {{ .. }} => None, + Recipe::Shaped {{ .. }} => None, + Recipe::ShapeLess {{ ingredients, .. }} => Some(ingredients), + }} + }} +}} + +const RECIPES: [Recipe; {recipes_count}] = [ +{recipes_data} +]; + +const SHORTCUTS: [(usize, usize); {item_count}] = {shortcuts:?}; +"#, + recipes_count = recipes_count, + recipes_data = recipes_data, + item_count = items.len(), + shortcuts = shortcuts, + ); + + File::create("src/ids/recipes.rs") + .unwrap() + .write_all(code.as_bytes()) + .unwrap() +} From dbd9d14b03eef0834f0925ffc72b771aa0e1d6d1 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Tue, 21 Sep 2021 20:26:19 +0200 Subject: [PATCH 47/52] Add doc for BlockWithState fields --- build/blocks.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/build/blocks.rs b/build/blocks.rs index ee412562..12d09205 100644 --- a/build/blocks.rs +++ b/build/blocks.rs @@ -504,8 +504,35 @@ pub fn generate_block_with_state_enum(data: serde_json::Value) { }; let competing_definitions = already_defined_enums.contains(&state.ty(&block.text_id, true)); + let doc = if state.ty == "int" { + let values: Vec = state + .values + .as_ref() + .expect("No values for int block state") + .iter() + .map(|v| v.parse().expect("Invalid block state value: expected int")) + .collect(); + + let mut expected = values[0]; + let mut standard = true; + for value in &values { + if value != &expected { + standard = false; + break; + } + expected += 1; + } + + match standard { + true => format!("\t\t/// Valid values are all values between {} and {}.\n", values[0], values.last().unwrap()), + false => format!("\t\t/// Valid values: {:?}.\n", values), + } + } else { + String::new() + }; fields.push_str(&format!( - "{}: {}, ", + "{}\t\t{}: {},\n", + doc, name, state.ty(&block.text_id, competing_definitions) )); @@ -513,7 +540,7 @@ pub fn generate_block_with_state_enum(data: serde_json::Value) { if fields.is_empty() { variants.push_str(&format!("\t{},\n", name)); } else { - variants.push_str(&format!("\t{}{{ {}}},\n", name, fields)); + variants.push_str(&format!("\t{} {{\n{}\t}},\n", name, fields)); } } From bcc28c55b5a02cffeef399f69780baae56f8b15e Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Tue, 21 Sep 2021 20:29:27 +0200 Subject: [PATCH 48/52] Change doc --- build/blocks.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/blocks.rs b/build/blocks.rs index 12d09205..a858847b 100644 --- a/build/blocks.rs +++ b/build/blocks.rs @@ -524,8 +524,8 @@ pub fn generate_block_with_state_enum(data: serde_json::Value) { } match standard { - true => format!("\t\t/// Valid values are all values between {} and {}.\n", values[0], values.last().unwrap()), - false => format!("\t\t/// Valid values: {:?}.\n", values), + true => format!("\t\t/// Valid if {} <= {} <= {}\n", values[0], name, values.last().unwrap()), + false => format!("\t\t/// Valid if {} ∈ {:?}\n", name, values), } } else { String::new() From 0bcbd095fa1d9adc236f82bd635321ee71e84ea9 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Tue, 21 Sep 2021 20:58:50 +0200 Subject: [PATCH 49/52] Optimize for size Size of Shape went from 76 to 40 and size for Recipe went from 168 to 56 --- build/recipes.rs | 150 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 122 insertions(+), 28 deletions(-) diff --git a/build/recipes.rs b/build/recipes.rs index 81718296..89db5bee 100644 --- a/build/recipes.rs +++ b/build/recipes.rs @@ -32,8 +32,19 @@ impl RecipeItem { item_ident, count ) } + + fn format_count1(&self, items: &[super::items::Item]) -> String { + let (id, count) = self.to_id_and_count(); + assert!(count == 1); + let item_ident = item_id_to_item(id, items); + format!( + "Item::{}", + item_ident + ) + } } +#[allow(dead_code)] fn format_option_item(item: &Option, items: &[super::items::Item]) -> String { match item { Some(item) => format!("Some({})", item.format(items)), @@ -41,6 +52,13 @@ fn format_option_item(item: &Option, items: &[super::items::Item]) - } } +fn format_option_item_count1(item: &Option, items: &[super::items::Item]) -> String { + match item { + Some(item) => format!("Some({})", item.format_count1(items)), + None => "None".to_string(), + } +} + #[derive(Serialize, Deserialize)] #[serde(untagged)] enum Shape { @@ -56,6 +74,7 @@ enum Shape { } impl Shape { + #[allow(dead_code)] fn format(&self, i: &[super::items::Item]) -> String { match self { Shape::ThreeByThree([[v1, v2, v3], [v4, v5, v6], [v7, v8, v9]]) => { @@ -138,6 +157,89 @@ impl Shape { } } } + + fn format_count1(&self, i: &[super::items::Item]) -> String { + match self { + Shape::ThreeByThree([[v1, v2, v3], [v4, v5, v6], [v7, v8, v9]]) => { + format!( + "Shape::ThreeByThree([[{}, {}, {}], [{}, {}, {}], [{}, {}, {}]])", + format_option_item_count1(v1, i), + format_option_item_count1(v2, i), + format_option_item_count1(v3, i), + format_option_item_count1(v4, i), + format_option_item_count1(v5, i), + format_option_item_count1(v6, i), + format_option_item_count1(v7, i), + format_option_item_count1(v8, i), + format_option_item_count1(v9, i) + ) + } + Shape::ThreeByTwo([[v1, v2, v3], [v4, v5, v6]]) => { + format!( + "Shape::ThreeByTwo([[{}, {}, {}], [{}, {}, {}]])", + format_option_item_count1(v1, i), + format_option_item_count1(v2, i), + format_option_item_count1(v3, i), + format_option_item_count1(v4, i), + format_option_item_count1(v5, i), + format_option_item_count1(v6, i) + ) + } + Shape::ThreeByOne([[v1, v2, v3]]) => { + format!( + "Shape::ThreeByOne([[{}, {}, {}]])", + format_option_item_count1(v1, i), + format_option_item_count1(v2, i), + format_option_item_count1(v3, i) + ) + } + Shape::TwoByThree([[v1, v2], [v3, v4], [v5, v6]]) => { + format!( + "Shape::TwoByThree([[{}, {}], [{}, {}], [{}, {}]])", + format_option_item_count1(v1, i), + format_option_item_count1(v2, i), + format_option_item_count1(v3, i), + format_option_item_count1(v4, i), + format_option_item_count1(v5, i), + format_option_item_count1(v6, i) + ) + } + Shape::TwoByTwo([[v1, v2], [v3, v4]]) => { + format!( + "Shape::TwoByTwo([[{}, {}], [{}, {}]])", + format_option_item_count1(v1, i), + format_option_item_count1(v2, i), + format_option_item_count1(v3, i), + format_option_item_count1(v4, i) + ) + } + Shape::TwoByOne([[v1, v2]]) => { + format!( + "Shape::TwoByOne([[{}, {}]])", + format_option_item_count1(v1, i), + format_option_item_count1(v2, i) + ) + } + Shape::OneByThree([[v1], [v2], [v3]]) => { + format!( + "Shape::OneByThree([[{}], [{}], [{}]])", + format_option_item_count1(v1, i), + format_option_item_count1(v2, i), + format_option_item_count1(v3, i) + ) + } + Shape::OneByTwo([[v1], [v2]]) => { + format!( + "Shape::OneByTwo([[{}], [{}]])", + format_option_item_count1(v1, i), + format_option_item_count1(v2, i) + ) + } + Shape::OneByOne([[v1]]) => { + format!("Shape::OneByOne([[{}]])", format_option_item_count1(v1, i)) + } + } + } } #[derive(Serialize, Deserialize)] @@ -179,6 +281,11 @@ pub fn generate_recipes(data: serde_json::Value, items: Vec) let mut recipes_count = 0; for recipes in item_recipes.values() { recipes_count += recipes.len(); + for recipe in recipes { + if matches!(recipe, Recipe::DoubleShaped{..}) { + panic!("Contains a double shaped recipe, which support has been removed as an optimization. It needs to be enabled again if required by future minecraft updates.") + } + } } // Generate recipes @@ -192,7 +299,7 @@ pub fn generate_recipes(data: serde_json::Value, items: Vec) } => { let mut ingredients_string = String::new(); for ingredient in ingredients { - ingredients_string.push_str(&ingredient.format(&items)); + ingredients_string.push_str(&ingredient.format_count1(&items)); ingredients_string.push_str(", "); } @@ -206,7 +313,7 @@ pub fn generate_recipes(data: serde_json::Value, items: Vec) recipes_data.push_str(&format!( "\tRecipe::Shaped {{ result: {}, in_shape: {} }},\n", result.format(&items), - in_shape.format(&items), + in_shape.format_count1(&items), )); } Recipe::DoubleShaped { @@ -217,8 +324,8 @@ pub fn generate_recipes(data: serde_json::Value, items: Vec) recipes_data.push_str(&format!( "\tRecipe::DoubleShaped {{ result: {}, in_shape: {}, out_shape: {} }},\n", result.format(&items), - in_shape.format(&items), - out_shape.format(&items), + in_shape.format_count1(&items), + out_shape.format_count1(&items), )); } } @@ -250,15 +357,15 @@ pub struct RecipeItem {{ #[derive(Debug, Clone)] pub enum Shape {{ - ThreeByThree([[Option; 3]; 3]), - ThreeByTwo([[Option; 3]; 2]), - ThreeByOne([[Option; 3]; 1]), - TwoByThree([[Option; 2]; 3]), - TwoByTwo([[Option; 2]; 2]), - TwoByOne([[Option; 2]; 1]), - OneByThree([[Option; 1]; 3]), - OneByTwo([[Option; 1]; 2]), - OneByOne([[Option; 1]; 1]), + ThreeByThree([[Option; 3]; 3]), + ThreeByTwo([[Option; 3]; 2]), + ThreeByOne([[Option; 3]; 1]), + TwoByThree([[Option; 2]; 3]), + TwoByTwo([[Option; 2]; 2]), + TwoByOne([[Option; 2]; 1]), + OneByThree([[Option; 1]; 3]), + OneByTwo([[Option; 1]; 2]), + OneByOne([[Option; 1]; 1]), }} impl Shape {{ @@ -281,9 +388,8 @@ impl Shape {{ #[derive(Debug, Clone)] pub enum Recipe {{ - DoubleShaped {{ in_shape: Shape, out_shape: Shape, result: RecipeItem }}, Shaped {{ in_shape: Shape, result: RecipeItem }}, - ShapeLess {{ ingredients: &'static [RecipeItem], result: RecipeItem }}, + ShapeLess {{ ingredients: &'static [Item], result: RecipeItem }}, }} impl Recipe {{ @@ -299,7 +405,6 @@ impl Recipe {{ #[inline] pub const fn result(&self) -> &RecipeItem {{ match self {{ - Recipe::DoubleShaped {{ result, .. }} => result, Recipe::Shaped {{ result, .. }} => result, Recipe::ShapeLess {{ result, .. }} => result, }} @@ -308,25 +413,14 @@ impl Recipe {{ #[inline] pub const fn in_shape(&self) -> Option<&Shape> {{ match self {{ - Recipe::DoubleShaped {{ in_shape, .. }} => Some(in_shape), Recipe::Shaped {{ in_shape, .. }} => Some(in_shape), Recipe::ShapeLess {{ .. }} => None, }} }} #[inline] - pub const fn out_shape(&self) -> Option<&Shape> {{ - match self {{ - Recipe::DoubleShaped {{ out_shape, .. }} => Some(out_shape), - Recipe::Shaped {{ .. }} => None, - Recipe::ShapeLess {{ .. }} => None, - }} - }} - - #[inline] - pub const fn ingredients(&self) -> Option<&'static [RecipeItem]> {{ + pub const fn ingredients(&self) -> Option<&'static [Item]> {{ match self {{ - Recipe::DoubleShaped {{ .. }} => None, Recipe::Shaped {{ .. }} => None, Recipe::ShapeLess {{ ingredients, .. }} => Some(ingredients), }} From dbfbf2fcd9b71e96e5eab2e6c99b013d59ee7497 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Tue, 21 Sep 2021 21:00:23 +0200 Subject: [PATCH 50/52] Rename RecipeItem to CountedItem --- build/recipes.rs | 52 ++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/build/recipes.rs b/build/recipes.rs index 89db5bee..d7310ca4 100644 --- a/build/recipes.rs +++ b/build/recipes.rs @@ -7,20 +7,20 @@ use std::io::prelude::*; #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(untagged)] -enum RecipeItem { +enum CountedItem { IDAndMetadataAndCount { id: u32, metadata: u32, count: u8 }, IDAndMetadata { id: u32, metadata: u32 }, IDAndCount { id: u32, count: u8 }, ID(u32), } -impl RecipeItem { +impl CountedItem { fn to_id_and_count(&self) -> (u32, u8) { match self { - RecipeItem::IDAndMetadataAndCount { .. } => panic!("Metadata not handled"), - RecipeItem::IDAndMetadata { .. } => panic!("Metadata not handled"), - RecipeItem::IDAndCount { id, count } => (*id, *count), - RecipeItem::ID(id) => (*id, 1), + CountedItem::IDAndMetadataAndCount { .. } => panic!("Metadata not handled"), + CountedItem::IDAndMetadata { .. } => panic!("Metadata not handled"), + CountedItem::IDAndCount { id, count } => (*id, *count), + CountedItem::ID(id) => (*id, 1), } } @@ -28,7 +28,7 @@ impl RecipeItem { let (id, count) = self.to_id_and_count(); let item_ident = item_id_to_item(id, items); format!( - "RecipeItem {{item: Item::{}, count: {}}}", + "CountedItem {{item: Item::{}, count: {}}}", item_ident, count ) } @@ -45,14 +45,14 @@ impl RecipeItem { } #[allow(dead_code)] -fn format_option_item(item: &Option, items: &[super::items::Item]) -> String { +fn format_option_item(item: &Option, items: &[super::items::Item]) -> String { match item { Some(item) => format!("Some({})", item.format(items)), None => "None".to_string(), } } -fn format_option_item_count1(item: &Option, items: &[super::items::Item]) -> String { +fn format_option_item_count1(item: &Option, items: &[super::items::Item]) -> String { match item { Some(item) => format!("Some({})", item.format_count1(items)), None => "None".to_string(), @@ -62,15 +62,15 @@ fn format_option_item_count1(item: &Option, items: &[super::items::I #[derive(Serialize, Deserialize)] #[serde(untagged)] enum Shape { - ThreeByThree([[Option; 3]; 3]), - ThreeByTwo([[Option; 3]; 2]), - ThreeByOne([[Option; 3]; 1]), - TwoByThree([[Option; 2]; 3]), - TwoByTwo([[Option; 2]; 2]), - TwoByOne([[Option; 2]; 1]), - OneByThree([[Option; 1]; 3]), - OneByTwo([[Option; 1]; 2]), - OneByOne([[Option; 1]; 1]), + ThreeByThree([[Option; 3]; 3]), + ThreeByTwo([[Option; 3]; 2]), + ThreeByOne([[Option; 3]; 1]), + TwoByThree([[Option; 2]; 3]), + TwoByTwo([[Option; 2]; 2]), + TwoByOne([[Option; 2]; 1]), + OneByThree([[Option; 1]; 3]), + OneByTwo([[Option; 1]; 2]), + OneByOne([[Option; 1]; 1]), } impl Shape { @@ -247,16 +247,16 @@ impl Shape { enum Recipe { #[serde(rename_all = "camelCase")] DoubleShaped { - result: RecipeItem, + result: CountedItem, in_shape: Shape, out_shape: Shape, }, #[serde(rename_all = "camelCase")] - Shaped { in_shape: Shape, result: RecipeItem }, + Shaped { in_shape: Shape, result: CountedItem }, #[serde(rename_all = "camelCase")] ShapeLess { - result: RecipeItem, - ingredients: Vec, + result: CountedItem, + ingredients: Vec, }, } @@ -350,7 +350,7 @@ use crate::ids::items::Item; /// An [Item](crate::ids::items::Item) associated with a count of this item #[derive(Debug, Clone)] -pub struct RecipeItem {{ +pub struct CountedItem {{ pub item: Item, pub count: u8, }} @@ -388,8 +388,8 @@ impl Shape {{ #[derive(Debug, Clone)] pub enum Recipe {{ - Shaped {{ in_shape: Shape, result: RecipeItem }}, - ShapeLess {{ ingredients: &'static [Item], result: RecipeItem }}, + Shaped {{ in_shape: Shape, result: CountedItem }}, + ShapeLess {{ ingredients: &'static [Item], result: CountedItem }}, }} impl Recipe {{ @@ -403,7 +403,7 @@ impl Recipe {{ }} #[inline] - pub const fn result(&self) -> &RecipeItem {{ + pub const fn result(&self) -> &CountedItem {{ match self {{ Recipe::Shaped {{ result, .. }} => result, Recipe::ShapeLess {{ result, .. }} => result, From b2d8eec7108bd0495d9342395b496be9aa048038 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Sat, 25 Sep 2021 12:21:01 +0200 Subject: [PATCH 51/52] Remove typo --- src/components/slots.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/slots.rs b/src/components/slots.rs index 1d47f798..205d0e3b 100644 --- a/src/components/slots.rs +++ b/src/components/slots.rs @@ -121,7 +121,7 @@ pub enum WindowType { Crafting, Enchantment, Furnace, - GrindStone, + Grindstone, Hopper, Lectern, Loom, From 92c0ed5af3fcd8e434a95e4d6ee9f7110f4a50d9 Mon Sep 17 00:00:00 2001 From: Mubelotix Date: Sat, 25 Sep 2021 12:23:26 +0200 Subject: [PATCH 52/52] Remove another typo --- src/components/slots.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/slots.rs b/src/components/slots.rs index 205d0e3b..399b6207 100644 --- a/src/components/slots.rs +++ b/src/components/slots.rs @@ -130,7 +130,7 @@ pub enum WindowType { Smithing, Smoker, Cartography, - StoneCutter, + Stonecutter, } #[cfg(test)]