rebase block_id

This commit is contained in:
kralverde
2024-09-28 17:39:11 -04:00
parent fff7357756
commit b7b975a064
14 changed files with 285 additions and 97 deletions

1
Cargo.lock generated
View File

@@ -2065,6 +2065,7 @@ dependencies = [
"num-traits",
"parking_lot",
"pumpkin-core",
"pumpkin-macros",
"rand",
"rayon",
"serde",

View File

@@ -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()

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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(),
}
}
}

View 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
}
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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(),
);
}
}

View File

@@ -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
}
}
}

View File

@@ -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,
}
}
}

View File

@@ -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(