feat: bedrock crafting
This commit is contained in:
parent
0eb7cb1a79
commit
2f653f6336
193
pumpkin-protocol/src/bedrock/client/crafting_data.rs
Normal file
193
pumpkin-protocol/src/bedrock/client/crafting_data.rs
Normal file
@ -0,0 +1,193 @@
|
||||
use crate::{
|
||||
bedrock::network_item::NetworkItemDescriptor,
|
||||
codec::{var_int::VarInt, var_uint::VarUInt},
|
||||
serial::PacketWrite,
|
||||
};
|
||||
use pumpkin_macros::packet;
|
||||
use std::io::{Error, Write};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ItemDescriptorCount {
|
||||
pub network_id: i16,
|
||||
pub metadata_value: i16,
|
||||
pub count: i32,
|
||||
}
|
||||
|
||||
impl PacketWrite for ItemDescriptorCount {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
if self.network_id == 0 {
|
||||
// Invalid item descriptor (type 0)
|
||||
0u8.write(writer)?;
|
||||
} else {
|
||||
// Default item descriptor (type 1)
|
||||
1u8.write(writer)?;
|
||||
self.network_id.write(writer)?;
|
||||
self.metadata_value.write(writer)?;
|
||||
}
|
||||
VarInt(self.count).write(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RecipeUnlockRequirement {
|
||||
pub context: u8,
|
||||
}
|
||||
|
||||
impl PacketWrite for RecipeUnlockRequirement {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
self.context.write(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BedrockShapelessRecipe {
|
||||
pub recipe_id: String,
|
||||
pub input: Vec<ItemDescriptorCount>,
|
||||
pub output: Vec<NetworkItemDescriptor>,
|
||||
pub uuid: [u8; 16],
|
||||
pub block: String,
|
||||
pub priority: VarInt,
|
||||
pub unlock_requirement: RecipeUnlockRequirement,
|
||||
pub recipe_network_id: VarUInt,
|
||||
}
|
||||
|
||||
impl PacketWrite for BedrockShapelessRecipe {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
self.recipe_id.write(writer)?;
|
||||
|
||||
// input slice with VarUInt length prefix
|
||||
VarUInt(self.input.len() as u32).write(writer)?;
|
||||
for item in &self.input {
|
||||
item.write(writer)?;
|
||||
}
|
||||
|
||||
// output slice with VarUInt length prefix
|
||||
VarUInt(self.output.len() as u32).write(writer)?;
|
||||
for item in &self.output {
|
||||
item.write(writer)?;
|
||||
}
|
||||
|
||||
// uuid
|
||||
writer.write_all(&self.uuid)?;
|
||||
|
||||
// block
|
||||
self.block.write(writer)?;
|
||||
|
||||
// priority
|
||||
self.priority.write(writer)?;
|
||||
|
||||
// unlock_requirement
|
||||
self.unlock_requirement.write(writer)?;
|
||||
|
||||
// recipe_network_id
|
||||
self.recipe_network_id.write(writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BedrockShapedRecipe {
|
||||
pub recipe_id: String,
|
||||
pub width: VarInt,
|
||||
pub height: VarInt,
|
||||
pub input: Vec<ItemDescriptorCount>,
|
||||
pub output: Vec<NetworkItemDescriptor>,
|
||||
pub uuid: [u8; 16],
|
||||
pub block: String,
|
||||
pub priority: VarInt,
|
||||
pub assume_symmetry: bool,
|
||||
pub unlock_requirement: RecipeUnlockRequirement,
|
||||
pub recipe_network_id: VarUInt,
|
||||
}
|
||||
|
||||
impl PacketWrite for BedrockShapedRecipe {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
self.recipe_id.write(writer)?;
|
||||
self.width.write(writer)?;
|
||||
self.height.write(writer)?;
|
||||
|
||||
// input slice: width * height elements sequentially (no count prefix)
|
||||
for item in &self.input {
|
||||
item.write(writer)?;
|
||||
}
|
||||
|
||||
// output slice with VarUInt length prefix
|
||||
VarUInt(self.output.len() as u32).write(writer)?;
|
||||
for item in &self.output {
|
||||
item.write(writer)?;
|
||||
}
|
||||
|
||||
// uuid
|
||||
writer.write_all(&self.uuid)?;
|
||||
|
||||
// block
|
||||
self.block.write(writer)?;
|
||||
|
||||
// priority
|
||||
self.priority.write(writer)?;
|
||||
|
||||
// assume_symmetry
|
||||
self.assume_symmetry.write(writer)?;
|
||||
|
||||
// unlock_requirement
|
||||
self.unlock_requirement.write(writer)?;
|
||||
|
||||
// recipe_network_id
|
||||
self.recipe_network_id.write(writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BedrockRecipe {
|
||||
Shapeless(BedrockShapelessRecipe),
|
||||
Shaped(BedrockShapedRecipe),
|
||||
}
|
||||
|
||||
impl PacketWrite for BedrockRecipe {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
match self {
|
||||
Self::Shapeless(recipe) => {
|
||||
VarInt(0).write(writer)?; // type 0: Shapeless
|
||||
recipe.write(writer)?;
|
||||
}
|
||||
Self::Shaped(recipe) => {
|
||||
VarInt(1).write(writer)?; // type 1: Shaped
|
||||
recipe.write(writer)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[packet(52)]
|
||||
pub struct CCraftingData {
|
||||
pub recipes: Vec<BedrockRecipe>,
|
||||
pub clean_recipes: bool,
|
||||
}
|
||||
|
||||
impl PacketWrite for CCraftingData {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
// recipes slice with VarUInt prefix
|
||||
VarUInt(self.recipes.len() as u32).write(writer)?;
|
||||
for recipe in &self.recipes {
|
||||
recipe.write(writer)?;
|
||||
}
|
||||
|
||||
// potion recipes count prefix (0)
|
||||
VarUInt(0).write(writer)?;
|
||||
// potion container change recipes count prefix (0)
|
||||
VarUInt(0).write(writer)?;
|
||||
// material reducers count prefix (0)
|
||||
VarUInt(0).write(writer)?;
|
||||
|
||||
// clean_recipes
|
||||
self.clean_recipes.write(writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ pub mod chunk_radius_update;
|
||||
pub mod common;
|
||||
pub mod container_open;
|
||||
pub mod correct_player_move;
|
||||
pub mod crafting_data;
|
||||
pub mod creative_content;
|
||||
pub mod disconnect_player;
|
||||
pub mod gamerules_changed;
|
||||
@ -53,6 +54,7 @@ pub use chunk_radius_update::*;
|
||||
pub use common::*;
|
||||
pub use container_open::*;
|
||||
pub use correct_player_move::*;
|
||||
pub use crafting_data::*;
|
||||
pub use creative_content::*;
|
||||
pub use disconnect_player::*;
|
||||
pub use gamerules_changed::*;
|
||||
|
||||
@ -16,6 +16,7 @@ pub mod modal_form_response;
|
||||
pub mod player_action;
|
||||
pub mod player_auth_input;
|
||||
pub mod raknet;
|
||||
pub mod request_ability;
|
||||
pub mod request_chunk_radius;
|
||||
pub mod request_network_settings;
|
||||
pub mod resource_pack_response;
|
||||
@ -41,6 +42,7 @@ pub use modal_form_response::*;
|
||||
pub use player_action::{Action as PlayerActionType, SPlayerAction};
|
||||
pub use player_auth_input::*;
|
||||
pub use raknet::*;
|
||||
pub use request_ability::*;
|
||||
pub use request_chunk_radius::*;
|
||||
pub use request_network_settings::*;
|
||||
pub use resource_pack_response::*;
|
||||
|
||||
39
pumpkin-protocol/src/bedrock/server/request_ability.rs
Normal file
39
pumpkin-protocol/src/bedrock/server/request_ability.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use crate::{codec::var_int::VarInt, serial::PacketRead};
|
||||
use pumpkin_macros::packet;
|
||||
use std::io::{Error, Read};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AbilityValue {
|
||||
Bool(bool),
|
||||
Float(f32),
|
||||
}
|
||||
|
||||
impl PacketRead for AbilityValue {
|
||||
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
|
||||
let val_type = u8::read(buf)?;
|
||||
let bool_val = bool::read(buf)?;
|
||||
let float_val = f32::read(buf)?;
|
||||
match val_type {
|
||||
1 => Ok(Self::Bool(bool_val)),
|
||||
2 => Ok(Self::Float(float_val)),
|
||||
_ => Err(Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!("Invalid ability value type: {val_type}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[packet(102)]
|
||||
pub struct SRequestAbility {
|
||||
pub ability: VarInt,
|
||||
pub value: AbilityValue,
|
||||
}
|
||||
|
||||
impl PacketRead for SRequestAbility {
|
||||
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
|
||||
let ability = VarInt::read(buf)?;
|
||||
let value = AbilityValue::read(buf)?;
|
||||
Ok(Self { ability, value })
|
||||
}
|
||||
}
|
||||
@ -49,6 +49,7 @@ use pumpkin_protocol::{
|
||||
open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2},
|
||||
unconnected_ping::SUnconnectedPing,
|
||||
},
|
||||
request_ability::SRequestAbility,
|
||||
request_chunk_radius::SRequestChunkRadius,
|
||||
request_network_settings::SRequestNetworkSettings,
|
||||
resource_pack_response::SResourcePackResponse,
|
||||
@ -1121,6 +1122,10 @@ impl BedrockClient {
|
||||
self.handle_block_pick_request(player, SBlockPickRequest::read(reader)?)
|
||||
.await;
|
||||
}
|
||||
SRequestAbility::PACKET_ID => {
|
||||
self.handle_request_ability(player, SRequestAbility::read(reader)?)
|
||||
.await;
|
||||
}
|
||||
SMobEquipment::PACKET_ID => {
|
||||
self.handle_mob_equipment(player, SMobEquipment::read(reader)?)
|
||||
.await;
|
||||
|
||||
@ -595,8 +595,8 @@ impl BedrockClient {
|
||||
let target_runtime_id = data.target_entity_runtime_id.0 as i32;
|
||||
// TODO: replace with consts, i'm too lazy
|
||||
match data.action_type.0 {
|
||||
// Interact
|
||||
0 => {
|
||||
// Interact / Item Interact
|
||||
0 | 2 => {
|
||||
let world = player.world();
|
||||
if let Some(target) = world.get_entity_by_id(target_runtime_id) {
|
||||
let held = player.inventory.held_item();
|
||||
@ -1188,6 +1188,22 @@ impl BedrockClient {
|
||||
..
|
||||
} => {
|
||||
if repetitions > 0 {
|
||||
screen_handler.update_to_client().await;
|
||||
|
||||
let is_player = screen_handler.window_type().is_none();
|
||||
let grid_size = if is_player { 4 } else { 9 };
|
||||
for i in 0..grid_size {
|
||||
let grid_slot_index = 1 + i;
|
||||
let grid_slot =
|
||||
screen_handler.get_behaviour().slots[grid_slot_index].clone();
|
||||
let grid_stack = grid_slot.get_cloned_stack().await;
|
||||
tracing::info!(
|
||||
"Crafting Grid slot {i} (slot index {grid_slot_index}): Item ID: {}, Count: {}",
|
||||
grid_stack.item.id,
|
||||
grid_stack.item_count
|
||||
);
|
||||
}
|
||||
|
||||
let output_slot = screen_handler.get_behaviour().slots[0].clone();
|
||||
let output_stack = output_slot.get_cloned_stack().await;
|
||||
|
||||
@ -1467,6 +1483,36 @@ impl BedrockClient {
|
||||
let equipment = &[(EquipmentSlot::MAIN_HAND, stack)];
|
||||
player.living_entity.send_equipment_changes(equipment);
|
||||
}
|
||||
|
||||
pub async fn handle_request_ability(
|
||||
&self,
|
||||
player: &Arc<Player>,
|
||||
packet: pumpkin_protocol::bedrock::server::request_ability::SRequestAbility,
|
||||
) {
|
||||
player.update_last_action_time();
|
||||
let ability_id = packet.ability.0;
|
||||
match ability_id {
|
||||
9 => {
|
||||
// Flying
|
||||
if let pumpkin_protocol::bedrock::server::request_ability::AbilityValue::Bool(
|
||||
requested_flying,
|
||||
) = packet.value
|
||||
{
|
||||
let mut abilities = player.abilities.lock().await;
|
||||
if abilities.allow_flying {
|
||||
abilities.flying = requested_flying;
|
||||
} else {
|
||||
abilities.flying = false;
|
||||
}
|
||||
drop(abilities);
|
||||
player.send_abilities_update().await;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug!("Received RequestAbility packet for unhandled ability {ability_id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
|
||||
@ -1791,6 +1791,11 @@ impl World {
|
||||
) {
|
||||
static CREATIVE_CONTENT: std::sync::OnceLock<(Vec<Group>, Vec<Entry>)> =
|
||||
std::sync::OnceLock::new();
|
||||
|
||||
static BEDROCK_CRAFTING_DATA: std::sync::OnceLock<
|
||||
Vec<pumpkin_protocol::bedrock::client::BedrockRecipe>,
|
||||
> = std::sync::OnceLock::new();
|
||||
|
||||
let level_info = server.level_info.load();
|
||||
let weather = self.weather.lock().await;
|
||||
let runtime_id = player.entity_id() as u64;
|
||||
@ -2009,6 +2014,185 @@ impl World {
|
||||
.send_game_packet(&CCreativeContent { groups, entries })
|
||||
.await;
|
||||
|
||||
let bedrock_recipes = BEDROCK_CRAFTING_DATA.get_or_init(|| {
|
||||
use pumpkin_data::item::{Item, JavaToBedrockItemMapping};
|
||||
use pumpkin_data::recipes::{CraftingRecipeTypes, RecipeIngredientTypes};
|
||||
use pumpkin_protocol::bedrock::client::{
|
||||
BedrockRecipe, BedrockShapedRecipe, BedrockShapelessRecipe, ItemDescriptorCount,
|
||||
RecipeUnlockRequirement,
|
||||
};
|
||||
use pumpkin_protocol::bedrock::network_item::NetworkItemDescriptor;
|
||||
use pumpkin_protocol::codec::{var_int::VarInt, var_uint::VarUInt};
|
||||
|
||||
let mut mapped_recipes = Vec::new();
|
||||
let mut network_id_counter = 1u32;
|
||||
|
||||
for recipe in pumpkin_data::recipes::RECIPES_CRAFTING {
|
||||
let map_ingredient = |ing: &RecipeIngredientTypes| -> ItemDescriptorCount {
|
||||
let item_key = match ing {
|
||||
RecipeIngredientTypes::Simple(name) => Some(*name),
|
||||
RecipeIngredientTypes::Tagged(tag) => {
|
||||
let tag_name = tag.strip_prefix('#').unwrap_or(tag);
|
||||
pumpkin_data::tag::get_tag_ids(
|
||||
pumpkin_data::tag::RegistryKey::Item,
|
||||
tag_name,
|
||||
)
|
||||
.and_then(|ids| {
|
||||
ids.first().and_then(|&first_id| {
|
||||
Item::from_id(first_id).map(|item| item.registry_key)
|
||||
})
|
||||
})
|
||||
}
|
||||
RecipeIngredientTypes::OneOf(names) => names.first().copied(),
|
||||
};
|
||||
|
||||
if let Some(key) = item_key {
|
||||
let registry_key = key.strip_prefix("minecraft:").unwrap_or(key);
|
||||
if let Some(item) = Item::from_registry_key(registry_key)
|
||||
&& let Some(mapping) =
|
||||
JavaToBedrockItemMapping::from_java_item_id(item.id)
|
||||
{
|
||||
return ItemDescriptorCount {
|
||||
network_id: mapping.bedrock_item.id,
|
||||
metadata_value: mapping.bedrock_data as i16,
|
||||
count: 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ItemDescriptorCount {
|
||||
network_id: 0,
|
||||
metadata_value: 0,
|
||||
count: 0,
|
||||
}
|
||||
};
|
||||
|
||||
match recipe {
|
||||
CraftingRecipeTypes::CraftingShaped {
|
||||
category: _,
|
||||
group: _,
|
||||
show_notification: _,
|
||||
key,
|
||||
pattern,
|
||||
result,
|
||||
} => {
|
||||
let height = pattern.len() as i32;
|
||||
let width = pattern.iter().map(|s| s.len()).max().unwrap_or(0) as i32;
|
||||
|
||||
let mut input = Vec::new();
|
||||
for r in 0..height {
|
||||
let pattern_row = pattern[r as usize];
|
||||
for c in 0..width {
|
||||
let ch = pattern_row.chars().nth(c as usize).unwrap_or(' ');
|
||||
if ch == ' ' {
|
||||
input.push(ItemDescriptorCount {
|
||||
network_id: 0,
|
||||
metadata_value: 0,
|
||||
count: 0,
|
||||
});
|
||||
} else {
|
||||
let mut ingredient = None;
|
||||
for &(key_ch, ref ing) in *key {
|
||||
if key_ch == ch {
|
||||
ingredient = Some(ing);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(ing) = ingredient {
|
||||
input.push(map_ingredient(ing));
|
||||
} else {
|
||||
input.push(ItemDescriptorCount {
|
||||
network_id: 0,
|
||||
metadata_value: 0,
|
||||
count: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let output_item = Item::from_registry_key(result.id);
|
||||
if let Some(item) = output_item
|
||||
&& let Some(mapping) =
|
||||
JavaToBedrockItemMapping::from_java_item_id(item.id)
|
||||
{
|
||||
let output_descriptor = NetworkItemDescriptor {
|
||||
id: VarInt::from(mapping.bedrock_item.id),
|
||||
stack_size: result.count as u16,
|
||||
aux_value: VarUInt(mapping.bedrock_data),
|
||||
block_runtime_id: VarInt::from(mapping.bedrock_block_state),
|
||||
nbt_data: pumpkin_nbt::Nbt::default(),
|
||||
place_on_blocks: Vec::new(),
|
||||
destroy_blocks: Vec::new(),
|
||||
shield_blocking_tick: 0,
|
||||
};
|
||||
|
||||
mapped_recipes.push(BedrockRecipe::Shaped(BedrockShapedRecipe {
|
||||
recipe_id: format!("pumpkin:recipe_{network_id_counter}"),
|
||||
width: VarInt(width),
|
||||
height: VarInt(height),
|
||||
input,
|
||||
output: vec![output_descriptor],
|
||||
uuid: [0; 16],
|
||||
block: "crafting_table".to_string(),
|
||||
priority: VarInt(1),
|
||||
assume_symmetry: true,
|
||||
unlock_requirement: RecipeUnlockRequirement { context: 1 },
|
||||
recipe_network_id: VarUInt(network_id_counter),
|
||||
}));
|
||||
network_id_counter += 1;
|
||||
}
|
||||
}
|
||||
CraftingRecipeTypes::CraftingShapeless {
|
||||
category: _,
|
||||
group: _,
|
||||
ingredients,
|
||||
result,
|
||||
} => {
|
||||
let input = ingredients.iter().map(map_ingredient).collect::<Vec<_>>();
|
||||
|
||||
let output_item = Item::from_registry_key(result.id);
|
||||
if let Some(item) = output_item
|
||||
&& let Some(mapping) =
|
||||
JavaToBedrockItemMapping::from_java_item_id(item.id)
|
||||
{
|
||||
let output_descriptor = NetworkItemDescriptor {
|
||||
id: VarInt::from(mapping.bedrock_item.id),
|
||||
stack_size: result.count as u16,
|
||||
aux_value: VarUInt(mapping.bedrock_data),
|
||||
block_runtime_id: VarInt::from(mapping.bedrock_block_state),
|
||||
nbt_data: pumpkin_nbt::Nbt::default(),
|
||||
place_on_blocks: Vec::new(),
|
||||
destroy_blocks: Vec::new(),
|
||||
shield_blocking_tick: 0,
|
||||
};
|
||||
|
||||
mapped_recipes.push(BedrockRecipe::Shapeless(BedrockShapelessRecipe {
|
||||
recipe_id: format!("pumpkin:recipe_{network_id_counter}"),
|
||||
input,
|
||||
output: vec![output_descriptor],
|
||||
uuid: [0; 16],
|
||||
block: "crafting_table".to_string(),
|
||||
priority: VarInt(1),
|
||||
unlock_requirement: RecipeUnlockRequirement { context: 1 },
|
||||
recipe_network_id: VarUInt(network_id_counter),
|
||||
}));
|
||||
network_id_counter += 1;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
mapped_recipes
|
||||
});
|
||||
|
||||
client
|
||||
.send_game_packet(&pumpkin_protocol::bedrock::client::CCraftingData {
|
||||
recipes: bedrock_recipes.clone(),
|
||||
clean_recipes: false,
|
||||
})
|
||||
.await;
|
||||
|
||||
client
|
||||
.send_game_packet(&CInventoryContent {
|
||||
container_id: VarUInt(0), // player inventory,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user