mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
rebase block_id
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2065,6 +2065,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"parking_lot",
|
||||
"pumpkin-core",
|
||||
"pumpkin-macros",
|
||||
"rand",
|
||||
"rayon",
|
||||
"serde",
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::{collections::HashMap, sync::LazyLock};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
use proc_macro::TokenStream;
|
||||
@@ -50,7 +53,108 @@ static BLOCKS: LazyLock<HashMap<String, RegistryBlockType>> = LazyLock::new(|| {
|
||||
.expect("Could not parse block.json registry.")
|
||||
});
|
||||
|
||||
pub fn block_id_impl(item: TokenStream) -> TokenStream {
|
||||
fn pascal_case(original: &str) -> String {
|
||||
let mut pascal = String::new();
|
||||
let mut capitalize = true;
|
||||
for ch in original.chars() {
|
||||
if ch == '_' {
|
||||
capitalize = true;
|
||||
} else if capitalize {
|
||||
pascal.push(ch.to_ascii_uppercase());
|
||||
capitalize = false;
|
||||
} else {
|
||||
pascal.push(ch);
|
||||
}
|
||||
}
|
||||
pascal
|
||||
}
|
||||
|
||||
pub fn block_type_enum_impl() -> TokenStream {
|
||||
let categories: &HashSet<&str> = &BLOCKS
|
||||
.values()
|
||||
.map(|val| val.definition.category.as_str())
|
||||
.collect();
|
||||
|
||||
let original_and_converted_stream = categories.iter().map(|key| {
|
||||
(
|
||||
key,
|
||||
pascal_case(key.split_once(':').expect("Bad minecraft id").1),
|
||||
)
|
||||
});
|
||||
let new_names: proc_macro2::TokenStream = original_and_converted_stream
|
||||
.clone()
|
||||
.map(|(_, x)| x)
|
||||
.join(",\n")
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let from_string: proc_macro2::TokenStream = original_and_converted_stream
|
||||
.clone()
|
||||
.map(|(original, converted)| format!("\"{}\" => BlockCategory::{},", original, converted))
|
||||
.join("\n")
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
// I;ve never used macros before so call me out on this lol
|
||||
quote! {
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum BlockCategory {
|
||||
#new_names
|
||||
}
|
||||
|
||||
impl BlockCategory {
|
||||
pub fn from_registry_id(id: &str) -> BlockCategory {
|
||||
match id {
|
||||
#from_string
|
||||
_ => panic!("Not a valid block type id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn block_enum_impl() -> TokenStream {
|
||||
let original_and_converted_stream = &BLOCKS.keys().map(|key| {
|
||||
(
|
||||
key,
|
||||
pascal_case(key.split_once(':').expect("Bad minecraft id").1),
|
||||
)
|
||||
});
|
||||
let new_names: proc_macro2::TokenStream = original_and_converted_stream
|
||||
.clone()
|
||||
.map(|(_, x)| x)
|
||||
.join(",\n")
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let from_string: proc_macro2::TokenStream = original_and_converted_stream
|
||||
.clone()
|
||||
.map(|(original, converted)| format!("\"{}\" => Block::{},", original, converted))
|
||||
.join("\n")
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
// I;ve never used macros before so call me out on this lol
|
||||
quote! {
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum Block {
|
||||
#new_names
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn from_registry_id(id: &str) -> Block {
|
||||
match id {
|
||||
#from_string
|
||||
_ => panic!("Not a valid block id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn block_state_impl(item: TokenStream) -> TokenStream {
|
||||
let data = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated
|
||||
.parse(item)
|
||||
.unwrap();
|
||||
@@ -61,9 +165,12 @@ pub fn block_id_impl(item: TokenStream) -> TokenStream {
|
||||
let block_name = match block_name {
|
||||
syn::Expr::Lit(lit) => match &lit.lit {
|
||||
syn::Lit::Str(name) => name.value(),
|
||||
_ => panic!("The first argument should be a string"),
|
||||
_ => panic!("The first argument should be a string, have: {:?}", lit),
|
||||
},
|
||||
_ => panic!("The first argument should be a string"),
|
||||
_ => panic!(
|
||||
"The first argument should be a string, have: {:?}",
|
||||
block_name
|
||||
),
|
||||
};
|
||||
|
||||
let mut properties = HashMap::new();
|
||||
@@ -104,7 +211,7 @@ pub fn block_id_impl(item: TokenStream) -> TokenStream {
|
||||
.get(&block_name)
|
||||
.expect("Block with that name does not exist");
|
||||
|
||||
let id = if properties.is_empty() {
|
||||
let state = if properties.is_empty() {
|
||||
block_info
|
||||
.states
|
||||
.iter()
|
||||
@@ -112,14 +219,13 @@ pub fn block_id_impl(item: TokenStream) -> TokenStream {
|
||||
.expect(
|
||||
"Error inside blocks.json file: Every Block should have at least 1 default state",
|
||||
)
|
||||
.id
|
||||
} else {
|
||||
match block_info
|
||||
.states
|
||||
.iter()
|
||||
.find(|state| state.properties == properties)
|
||||
{
|
||||
Some(state) => state.id,
|
||||
Some(state) => state,
|
||||
None => panic!(
|
||||
"Could not find block with these properties, the following are valid properties: \n{}",
|
||||
block_info
|
||||
@@ -131,13 +237,24 @@ pub fn block_id_impl(item: TokenStream) -> TokenStream {
|
||||
}
|
||||
};
|
||||
|
||||
let id = state.id;
|
||||
let category_name = block_info.definition.category.clone();
|
||||
|
||||
if std::env::var("CARGO_PKG_NAME").unwrap() == "pumpkin-world" {
|
||||
quote! {
|
||||
crate::block::block_id::BlockId::from_id(#id as u16)
|
||||
crate::block::block_state::BlockState::new_unchecked(
|
||||
#id as u16,
|
||||
crate::block::Block::from_registry_id(#block_name),
|
||||
crate::block::BlockCategory::from_registry_id(#category_name),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
pumpkin_world::block::block_id::BlockId::from_id(#id as u16)
|
||||
pumpkin_world::block::block_id::BlockStateId::new_unchecked(
|
||||
#id as u16,
|
||||
pumpkin_world::block::Block::from_registry_id(#block_name),
|
||||
pumpkin_world::block::BlockCategory::from_registry_id(#category_name),
|
||||
)
|
||||
}
|
||||
}
|
||||
.into()
|
||||
@@ -23,8 +23,20 @@ pub fn packet(input: TokenStream, item: TokenStream) -> TokenStream {
|
||||
gen.into()
|
||||
}
|
||||
|
||||
mod block_id;
|
||||
mod block_state;
|
||||
#[proc_macro]
|
||||
pub fn block_id(item: TokenStream) -> TokenStream {
|
||||
block_id::block_id_impl(item)
|
||||
pub fn block(item: TokenStream) -> TokenStream {
|
||||
block_state::block_state_impl(item)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
/// Creates an enum for all block types. Should only be used once
|
||||
pub fn blocks_enum(_item: TokenStream) -> TokenStream {
|
||||
block_state::block_enum_impl()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
/// Creates an enum for all block categories. Should only be used once
|
||||
pub fn block_categories_enum(_item: TokenStream) -> TokenStream {
|
||||
block_state::block_type_enum_impl()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pumpkin-core = { path = "../pumpkin-core" }
|
||||
pumpkin-macros = { path = "../pumpkin-macros" }
|
||||
|
||||
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
|
||||
tokio.workspace = true
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::block_registry::BLOCKS;
|
||||
use crate::level::WorldError;
|
||||
|
||||
// 0 is air -> reasonable default
|
||||
#[derive(Default, Deserialize, Debug, Hash, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(transparent)]
|
||||
pub struct BlockId {
|
||||
data: u16,
|
||||
}
|
||||
|
||||
impl BlockId {
|
||||
pub const AIR: Self = Self::from_id(0);
|
||||
|
||||
pub fn new(
|
||||
text_id: &str,
|
||||
properties: Option<&HashMap<String, String>>,
|
||||
) -> Result<Self, WorldError> {
|
||||
let mut block_states = BLOCKS
|
||||
.get(text_id)
|
||||
.ok_or(WorldError::BlockIdentifierNotFound)?
|
||||
.states
|
||||
.iter();
|
||||
|
||||
let block_state = match properties {
|
||||
Some(properties) => block_states
|
||||
.find(|state| &state.properties == properties)
|
||||
.ok_or_else(|| WorldError::BlockStateIdNotFound)?,
|
||||
None => block_states
|
||||
.find(|state| state.is_default)
|
||||
.expect("Every Block should have at least 1 default state"),
|
||||
};
|
||||
|
||||
Ok(block_state.id)
|
||||
}
|
||||
|
||||
pub const fn from_id(id: u16) -> Self {
|
||||
// TODO: add check if the id is actually valid
|
||||
Self { data: id }
|
||||
}
|
||||
|
||||
pub fn is_air(&self) -> bool {
|
||||
self.data == 0 || self.data == 12959 || self.data == 12958
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> u16 {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// An i32 is the way mojang internally represents their Blocks
|
||||
pub fn get_id_mojang_repr(&self) -> i32 {
|
||||
self.data as i32
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,16 @@ use std::{collections::HashMap, sync::LazyLock};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::block_id::BlockId;
|
||||
use super::BlockState;
|
||||
|
||||
pub static BLOCKS: LazyLock<HashMap<String, RegistryBlockType>> = LazyLock::new(|| {
|
||||
serde_json::from_str(include_str!("../../../assets/blocks.json"))
|
||||
.expect("Could not parse block.json registry.")
|
||||
});
|
||||
|
||||
pumpkin_macros::blocks_enum!();
|
||||
pumpkin_macros::block_categories_enum!();
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RegistryBlockDefinition {
|
||||
/// e.g. minecraft:door or minecraft:button
|
||||
@@ -48,3 +51,31 @@ pub struct RegistryBlockType {
|
||||
#[serde(default, rename = "properties")]
|
||||
valid_properties: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[serde(transparent)]
|
||||
pub struct BlockId {
|
||||
pub data: u16,
|
||||
}
|
||||
|
||||
impl BlockId {
|
||||
pub fn is_air(&self) -> bool {
|
||||
self.data == 0 || self.data == 12959 || self.data == 12958
|
||||
}
|
||||
|
||||
pub fn get_id_mojang_repr(&self) -> i32 {
|
||||
self.data as i32
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> u16 {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockState> for BlockId {
|
||||
fn from(value: BlockState) -> Self {
|
||||
Self {
|
||||
data: value.get_id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
73
pumpkin-world/src/block/block_state.rs
Normal file
73
pumpkin-world/src/block/block_state.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::level::WorldError;
|
||||
|
||||
use super::block_registry::{Block, BlockCategory, BLOCKS};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlockState {
|
||||
state_id: u16,
|
||||
block: Block,
|
||||
category: BlockCategory,
|
||||
}
|
||||
|
||||
impl BlockState {
|
||||
pub const AIR: BlockState = BlockState {
|
||||
state_id: 0,
|
||||
block: Block::Air,
|
||||
category: BlockCategory::Air,
|
||||
};
|
||||
|
||||
pub fn new(
|
||||
registry_id: &str,
|
||||
properties: Option<&HashMap<String, String>>,
|
||||
) -> Result<Self, WorldError> {
|
||||
let block_registry = BLOCKS
|
||||
.get(registry_id)
|
||||
.ok_or(WorldError::BlockIdentifierNotFound)?;
|
||||
let mut block_states = block_registry.states.iter();
|
||||
|
||||
let block_state = match properties {
|
||||
Some(properties) => block_states
|
||||
.find(|state| &state.properties == properties)
|
||||
.ok_or_else(|| WorldError::BlockStateIdNotFound)?,
|
||||
None => block_states
|
||||
.find(|state| state.is_default)
|
||||
.expect("Every Block should have at least 1 default state"),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
state_id: block_state.id.data,
|
||||
block: Block::from_registry_id(registry_id),
|
||||
category: BlockCategory::from_registry_id(&block_registry.definition.category),
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn new_unchecked(state_id: u16, block: Block, category: BlockCategory) -> Self {
|
||||
Self {
|
||||
state_id,
|
||||
block,
|
||||
category,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_air(&self) -> bool {
|
||||
self.category == BlockCategory::Air
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> u16 {
|
||||
self.state_id
|
||||
}
|
||||
|
||||
pub fn get_id_mojang_repr(&self) -> i32 {
|
||||
self.state_id as i32
|
||||
}
|
||||
|
||||
pub fn of_block(&self, block: Block) -> bool {
|
||||
self.block == block
|
||||
}
|
||||
|
||||
pub fn of_category(&self, category: BlockCategory) -> bool {
|
||||
self.category == category
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
use num_derive::FromPrimitive;
|
||||
|
||||
pub mod block_id;
|
||||
mod block_registry;
|
||||
pub mod block_state;
|
||||
|
||||
pub use block_id::BlockId;
|
||||
use pumpkin_core::math::vector3::Vector3;
|
||||
|
||||
pub use block_registry::{Block, BlockCategory, BlockId};
|
||||
pub use block_state::BlockState;
|
||||
|
||||
#[derive(FromPrimitive)]
|
||||
pub enum BlockFace {
|
||||
Bottom = 0,
|
||||
|
||||
@@ -7,7 +7,7 @@ use pumpkin_core::math::vector2::Vector2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
block::BlockId,
|
||||
block::{BlockId, BlockState},
|
||||
coordinates::{ChunkRelativeBlockCoordinates, Height},
|
||||
level::{ChunkNotGeneratedError, WorldError},
|
||||
WORLD_HEIGHT,
|
||||
@@ -215,7 +215,12 @@ impl ChunkData {
|
||||
let palette = block_states
|
||||
.palette
|
||||
.iter()
|
||||
.map(|entry| BlockId::new(&entry.name, entry.properties.as_ref()))
|
||||
.map(
|
||||
|entry| match BlockState::new(&entry.name, entry.properties.as_ref()) {
|
||||
Err(e) => Err(e),
|
||||
Ok(state) => Ok(state.into()),
|
||||
},
|
||||
)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let block_data = match block_states.data {
|
||||
|
||||
@@ -3,7 +3,7 @@ use pumpkin_core::math::vector2::Vector2;
|
||||
use static_assertions::assert_obj_safe;
|
||||
|
||||
use crate::biome::Biome;
|
||||
use crate::block::BlockId;
|
||||
use crate::block::block_state::BlockState;
|
||||
use crate::chunk::ChunkData;
|
||||
use crate::coordinates::{BlockCoordinates, XZBlockCoordinates};
|
||||
use crate::world_gen::Seed;
|
||||
@@ -26,12 +26,12 @@ pub(crate) trait TerrainGenerator: Sync + Send {
|
||||
fn prepare_chunk(&self, at: &Vector2<i32>);
|
||||
|
||||
/// Is static
|
||||
fn generate_block(&self, at: BlockCoordinates, biome: Biome) -> BlockId;
|
||||
fn generate_block(&self, at: BlockCoordinates, biome: Biome) -> BlockState;
|
||||
}
|
||||
|
||||
pub(crate) trait PerlinTerrainGenerator: Sync + Send {
|
||||
fn prepare_chunk(&self, at: &Vector2<i32>, perlin: &Perlin);
|
||||
|
||||
/// Dependens on the perlin noise height
|
||||
fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, biome: Biome) -> BlockId;
|
||||
fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, biome: Biome) -> BlockState;
|
||||
}
|
||||
|
||||
@@ -62,11 +62,13 @@ impl<B: BiomeGenerator, T: PerlinTerrainGenerator> WorldGenerator for GenericGen
|
||||
|
||||
blocks.set_block(
|
||||
coordinates,
|
||||
self.terrain_generator.generate_block(
|
||||
coordinates.with_chunk_coordinates(at),
|
||||
chunk_height as i16,
|
||||
biome,
|
||||
),
|
||||
self.terrain_generator
|
||||
.generate_block(
|
||||
coordinates.with_chunk_coordinates(at),
|
||||
chunk_height as i16,
|
||||
biome,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use pumpkin_core::math::vector2::Vector2;
|
||||
|
||||
use crate::{
|
||||
biome::Biome,
|
||||
block::BlockId,
|
||||
block::block_state::BlockState,
|
||||
coordinates::{BlockCoordinates, XZBlockCoordinates},
|
||||
world_gen::{
|
||||
generator::{BiomeGenerator, GeneratorInit, PerlinTerrainGenerator},
|
||||
@@ -40,21 +40,21 @@ impl GeneratorInit for PlainsTerrainGenerator {
|
||||
impl PerlinTerrainGenerator for PlainsTerrainGenerator {
|
||||
fn prepare_chunk(&self, _at: &Vector2<i32>, _perlin: &Perlin) {}
|
||||
// TODO allow specifying which blocks should be at which height in the config.
|
||||
fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, _: Biome) -> BlockId {
|
||||
fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, _: Biome) -> BlockState {
|
||||
let begin_stone_height = chunk_height - 5;
|
||||
let begin_dirt_height = chunk_height - 1;
|
||||
|
||||
let y = *at.y;
|
||||
if y == -64 {
|
||||
BlockId::from_id(79) // BEDROCK
|
||||
pumpkin_macros::block!("minecraft:bedrock")
|
||||
} else if y >= -63 && y <= begin_stone_height {
|
||||
return BlockId::from_id(1); // STONE
|
||||
pumpkin_macros::block!("minecraft:stone")
|
||||
} else if y >= begin_stone_height && y < begin_dirt_height {
|
||||
return BlockId::from_id(10); // DIRT;
|
||||
pumpkin_macros::block!("minecraft:dirt")
|
||||
} else if y == chunk_height - 1 {
|
||||
return BlockId::from_id(9); // GRASS BLOCK
|
||||
pumpkin_macros::block!("minecraft:grass_block")
|
||||
} else {
|
||||
BlockId::AIR
|
||||
BlockState::AIR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use pumpkin_core::math::vector2::Vector2;
|
||||
use pumpkin_macros::block;
|
||||
|
||||
use crate::{
|
||||
biome::Biome,
|
||||
block::BlockId,
|
||||
block::block_state::BlockState,
|
||||
coordinates::{BlockCoordinates, XZBlockCoordinates},
|
||||
world_gen::{
|
||||
generator::{BiomeGenerator, GeneratorInit, TerrainGenerator},
|
||||
@@ -40,12 +41,12 @@ impl GeneratorInit for SuperflatTerrainGenerator {
|
||||
impl TerrainGenerator for SuperflatTerrainGenerator {
|
||||
fn prepare_chunk(&self, _at: &Vector2<i32>) {}
|
||||
// TODO allow specifying which blocks should be at which height in the config.
|
||||
fn generate_block(&self, at: BlockCoordinates, _: Biome) -> BlockId {
|
||||
fn generate_block(&self, at: BlockCoordinates, _: Biome) -> BlockState {
|
||||
match *at.y {
|
||||
-64 => BlockId::from_id(79), // Bedrock
|
||||
-63..=-62 => BlockId::from_id(10), // Dirt
|
||||
-61 => BlockId::from_id(9), // Grass
|
||||
_ => BlockId::AIR,
|
||||
-64 => block!("minecraft:bedrock"),
|
||||
-63..=-62 => block!("minecraft:dirt"),
|
||||
-61 => block!("minecraft:grass_block"),
|
||||
_ => BlockState::AIR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ use pumpkin_protocol::{
|
||||
SUseItemOn, Status,
|
||||
},
|
||||
};
|
||||
use pumpkin_world::block::{BlockFace, BlockId};
|
||||
use pumpkin_world::block::{BlockFace, BlockState};
|
||||
use pumpkin_world::global_registry;
|
||||
|
||||
use super::PlayerConfig;
|
||||
@@ -517,7 +517,7 @@ impl Player {
|
||||
item.item_id,
|
||||
)
|
||||
.expect("All item ids are in the global registry");
|
||||
if let Ok(block_state_id) = BlockId::new(minecraft_id, None) {
|
||||
if let Ok(block_state_id) = BlockState::new(minecraft_id, None) {
|
||||
let entity = &self.entity;
|
||||
let world = &entity.world;
|
||||
world.broadcast_packet_all(&CBlockUpdate::new(
|
||||
|
||||
Reference in New Issue
Block a user