chore: Remove Integer0ToX

This commit is contained in:
Alexander Medvedev 2026-04-29 14:57:11 +02:00
parent 6f2c3cd5e3
commit fdf8ff5fd8
45 changed files with 2074 additions and 1972 deletions

View File

@ -67,6 +67,8 @@ fn property_group_name_from_derived_name(name: &str) -> String {
enum PropertyType { enum PropertyType {
/// The property is a simple boolean (`true`/`false`). /// The property is a simple boolean (`true`/`false`).
Bool, Bool,
/// The property is an integer with an inclusive range.
Int { min: u8, max: u8 },
/// The property is an enum with a generated Rust type identified by `name`. /// The property is an enum with a generated Rust type identified by `name`.
Enum { name: String }, Enum { name: String },
} }
@ -211,6 +213,7 @@ impl ToTokens for BlockPropertyStruct {
let key = Ident::new_raw(&entry.original_name, Span::call_site()); let key = Ident::new_raw(&entry.original_name, Span::call_site());
match &entry.property_type { match &entry.property_type {
PropertyType::Bool => quote! { pub #key: bool }, PropertyType::Bool => quote! { pub #key: bool },
PropertyType::Int { .. } => quote! { pub #key: u8 },
PropertyType::Enum { name } => { PropertyType::Enum { name } => {
let value = Ident::new(name, Span::call_site()); let value = Ident::new(name, Span::call_site());
quote! { pub #key: #value } quote! { pub #key: #value }
@ -229,6 +232,10 @@ impl ToTokens for BlockPropertyStruct {
let field = Ident::new_raw(&entry.original_name, Span::call_site()); let field = Ident::new_raw(&entry.original_name, Span::call_site());
match &entry.property_type { match &entry.property_type {
PropertyType::Bool => quote! { (!self.#field as u16, 2) }, PropertyType::Bool => quote! { (!self.#field as u16, 2) },
PropertyType::Int { min, max } => {
let count = (max - min + 1) as u16;
quote! { ((self.#field - #min) as u16, #count) }
}
PropertyType::Enum { name } => { PropertyType::Enum { name } => {
let ty = Ident::new(name, Span::call_site()); let ty = Ident::new(name, Span::call_site());
quote! { (self.#field.to_index(), #ty::variant_count()) } quote! { (self.#field.to_index(), #ty::variant_count()) }
@ -251,6 +258,16 @@ impl ToTokens for BlockPropertyStruct {
value == 0 value == 0
} }
}, },
PropertyType::Int { min, max } => {
let count = (max - min + 1) as u16;
quote! {
#field_name: {
let value = (index % #count) as u8;
index /= #count;
value + #min
}
}
}
PropertyType::Enum { name } => { PropertyType::Enum { name } => {
let enum_ident = Ident::new(name, Span::call_site()); let enum_ident = Ident::new(name, Span::call_site());
quote! { quote! {
@ -272,6 +289,19 @@ impl ToTokens for BlockPropertyStruct {
PropertyType::Bool => quote! { PropertyType::Bool => quote! {
(#key_str, if self.#field { "true" } else { "false" }) (#key_str, if self.#field { "true" } else { "false" })
}, },
PropertyType::Int { min, max } => {
let mut arms = Vec::new();
for i in *min..=*max {
let i_str = i.to_string();
arms.push(quote! { #i => #i_str });
}
quote! {
(#key_str, match self.#field {
#(#arms,)*
_ => unreachable!()
})
}
}
PropertyType::Enum { .. } => quote! { PropertyType::Enum { .. } => quote! {
(#key_str, self.#field.to_value()) (#key_str, self.#field.to_value())
}, },
@ -287,6 +317,21 @@ impl ToTokens for BlockPropertyStruct {
block_props.#field_name = matches!(*value, "true") block_props.#field_name = matches!(*value, "true")
} }
}, },
PropertyType::Int { min, max } => {
let mut arms = Vec::new();
for i in *min..=*max {
let i_str = i.to_string();
arms.push(quote! { #i_str => #i });
}
quote! {
#key => {
block_props.#field_name = match *value {
#(#arms,)*
_ => #min,
}
}
}
}
PropertyType::Enum { name } => { PropertyType::Enum { name } => {
let enum_ident = Ident::new(name, Span::call_site()); let enum_ident = Ident::new(name, Span::call_site());
quote! { quote! {
@ -596,6 +641,8 @@ pub struct Block {
pub states: Vec<BlockState>, pub states: Vec<BlockState>,
/// Experience points dropped when the block is mined, if any. /// Experience points dropped when the block is mined, if any.
pub experience: Option<Experience>, pub experience: Option<Experience>,
/// Calculated property descriptors for the block.
pub property_descriptors: Vec<TokenStream>,
} }
impl ToTokens for Block { impl ToTokens for Block {
@ -638,6 +685,7 @@ impl ToTokens for Block {
} else { } else {
quote! { None } quote! { None }
}; };
let property_descriptors = &self.property_descriptors;
tokens.extend(quote! { tokens.extend(quote! {
Block { Block {
id: #id, id: #id,
@ -650,6 +698,7 @@ impl ToTokens for Block {
item_id: #item_id, item_id: #item_id,
default_state: &#default_state, default_state: &#default_state,
states: &[#(#states),*], states: &[#(#states),*],
properties: &[#(#property_descriptors),*],
flammable: #flammable, flammable: #flammable,
loot_table: #loot_table, loot_table: #loot_table,
experience: #experience, experience: #experience,
@ -800,15 +849,14 @@ pub fn build() -> TokenStream {
let property = generated_property.to_property(); let property = generated_property.to_property();
let renamed_property = property.enum_name.to_upper_camel_case(); let renamed_property = property.enum_name.to_upper_camel_case();
let property_type = if property.values.len() == 2 let property_type = match &generated_property.property_type {
&& property.values.contains(&"true".to_string()) GeneratedPropertyType::Boolean => PropertyType::Bool,
&& property.values.contains(&"false".to_string()) GeneratedPropertyType::Int { min, max } => {
{ PropertyType::Int { min: *min, max: *max }
PropertyType::Bool
} else {
PropertyType::Enum {
name: renamed_property.clone(),
} }
GeneratedPropertyType::Enum { .. } => PropertyType::Enum {
name: renamed_property.clone(),
},
}; };
if let PropertyType::Enum { name } = &property_type { if let PropertyType::Enum { name } = &property_type {
@ -826,13 +874,35 @@ pub fn build() -> TokenStream {
}); });
} }
let mut multiplier = 1;
let mut property_descriptors = Vec::new();
for hash in block.properties.iter().rev() {
let gen_prop = generated_prop_map.get(hash).unwrap();
let variant_count = match &gen_prop.property_type {
GeneratedPropertyType::Boolean => 2,
GeneratedPropertyType::Int { min, max } => (max - min + 1) as u16,
GeneratedPropertyType::Enum { values } => values.len() as u16,
};
property_descriptors.push(quote! {
PropertyDescriptor {
hash_key: #hash,
multiplier: #multiplier,
variant_count: #variant_count,
}
});
multiplier *= variant_count;
}
let const_ident = format_ident!("{}", const_block_name_from_block_name(&block.name)); let const_ident = format_ident!("{}", const_block_name_from_block_name(&block.name));
let name_str = &block.name; let name_str = &block.name;
let id_lit = LitInt::new(&block.id.to_string(), Span::call_site()); let id_lit = LitInt::new(&block.id.to_string(), Span::call_site());
let item_id = block.item_id; let item_id = block.item_id;
let mut block_with_descriptors = block.clone();
block_with_descriptors.property_descriptors = property_descriptors;
constants_list.push(quote! { constants_list.push(quote! {
pub const #const_ident: Block = #block; pub const #const_ident: Block = #block_with_descriptors;
}); });
type_from_raw_id_array.push((block.id, quote! { &Self::#const_ident })); type_from_raw_id_array.push((block.id, quote! { &Self::#const_ident }));
@ -887,6 +957,15 @@ pub fn build() -> TokenStream {
if val == 0 { "true" } else { "false" }.to_string(), if val == 0 { "true" } else { "false" }.to_string(),
); );
} }
PropertyType::Int { min, max } => {
let count = (*max - *min + 1) as u16;
let val = (temp_index % count) as u8;
temp_index /= count;
java_props_for_this_state.insert(
mapping.original_name.clone(),
(val + *min).to_string(),
);
}
PropertyType::Enum { name } => { PropertyType::Enum { name } => {
let enum_info = property_enums.get(name).unwrap(); let enum_info = property_enums.get(name).unwrap();
let count = enum_info.values.len() as u16; let count = enum_info.values.len() as u16;

File diff suppressed because it is too large Load Diff

View File

@ -464,11 +464,4 @@ impl DataComponent {
Self::ShulkerColor => "minecraft:shulker/color", Self::ShulkerColor => "minecraft:shulker/color",
} }
} }
pub fn try_from_u8(v: u8) -> Option<Self> {
if v <= 106 {
Some(unsafe { std::mem::transmute::<u8, Self>(v) })
} else {
None
}
}
} }

View File

@ -549,7 +549,7 @@ impl ItemStack {
let patch_len = nbt.get_u32().ok()? as usize; let patch_len = nbt.get_u32().ok()? as usize;
for _ in 0..patch_len { for _ in 0..patch_len {
let component_id_raw = nbt.get_u8().ok()?; let component_id_raw = nbt.get_u8().ok()?;
let id = DataComponent::try_from_u8(component_id_raw)?; let id = DataComponent::try_from_id(component_id_raw)?;
let is_present = nbt.get_bool().ok()?; let is_present = nbt.get_bool().ok()?;
if is_present { if is_present {

View File

@ -1,7 +1,7 @@
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::block_properties::{BlockProperties, EnumVariants, Integer0To15}; use pumpkin_data::block_properties::BlockProperties;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
@ -120,7 +120,7 @@ impl DaylightDetectorBlockEntity {
power = (power as f32 * sun_angle_radians.cos()).round() as u8; power = (power as f32 * sun_angle_radians.cos()).round() as u8;
} }
let power = Integer0To15::from_index(power.clamp(0, 15).into()); let power = power.clamp(0, 15);
if power != props.power { if power != props.power {
props.power = power; props.power = power;
let state = props.to_state_id(block); let state = props.to_state_id(block);

View File

@ -22,24 +22,29 @@ use crate::{
use super::{ChunkSerializer, FileIO, LoadedData}; use super::{ChunkSerializer, FileIO, LoadedData};
/// A simple implementation of the `ChunkSerializer` trait /// A simple implementation of the `ChunkSerializer` trait that loads and saves data
/// that load and save the data from a file in the disk /// to disk using parallelism and a lazy-loading cache keyed by file path.
/// using parallelism and a cache for the files with ongoing IO operations.
/// ///
/// It also avoid IO operations that could produce dataraces thanks to the /// ### Concurrency model
/// custom *`DashMap`*-like implementation. ///
/// * `file_locks` — one `Arc<RwLock<S>>` per on-disk file, created lazily.
/// All readers/writers for the same region file share this lock, so there
/// are never two concurrent writers for the same file.
/// * `watchers` — a ref-count per path. While a path has active watchers the
/// serializer is **not** evicted from the cache and the file is **not**
/// flushed to disk (the caller owns the flush lifecycle).
///
/// ### Lock ordering (must never be violated to avoid deadlocks)
///
/// 1. `file_locks` (outer)
/// 2. individual `RwLock<S>` inside each loader (inner)
/// 3. `watchers` (independent — never held at the same time as either above)
///
/// `watchers` is always acquired in its own critical section, after all
/// serializer locks are released, which keeps it strictly independent.
pub struct ChunkFileManager<S: ChunkSerializer<WriteBackend = PathBuf>> { pub struct ChunkFileManager<S: ChunkSerializer<WriteBackend = PathBuf>> {
// Dashmap has rw-locks on shards, but we want per-serializer.
//
// Lock lookups need to be:
// - Relatively quick insertion / lookup as to not extraneously block other concurrent hashmap
// ops
// - Guarantee that there is only one serializer per file at a time
// - Lazily load files as to support point 1
// - Allow for ease of usage (able to return the serializer from a function)
file_locks: RwLock<BTreeMap<PathBuf, Arc<ChunkSerializerLazyLoader<S>>>>, file_locks: RwLock<BTreeMap<PathBuf, Arc<ChunkSerializerLazyLoader<S>>>>,
watchers: RwLock<BTreeMap<PathBuf, usize>>, watchers: RwLock<BTreeMap<PathBuf, usize>>,
chunk_config: S::ChunkConfig, chunk_config: S::ChunkConfig,
} }
@ -49,6 +54,7 @@ pub(crate) trait PathFromLevelFolder {
struct ChunkSerializerLazyLoader<S: ChunkSerializer<WriteBackend = PathBuf>> { struct ChunkSerializerLazyLoader<S: ChunkSerializer<WriteBackend = PathBuf>> {
path: PathBuf, path: PathBuf,
/// Initialised at most once; subsequent calls reuse the same Arc.
internal: OnceCell<Arc<RwLock<S>>>, internal: OnceCell<Arc<RwLock<S>>>,
} }
@ -60,21 +66,28 @@ impl<S: ChunkSerializer<WriteBackend = PathBuf>> ChunkSerializerLazyLoader<S> {
} }
} }
/// We can only remove this entry from the map if we are the only ones with a reference to it /// Returns `true` only when no outside caller still holds a clone of this
/// IMPORTANT: This must be called within the write lock of the parent map /// loader *or* the inner serializer.
///
/// # Safety requirement
/// **Must be called while the write-lock on the parent `file_locks` map is
/// held.** That guarantees no new `Arc` clones can be issued while we
/// inspect the strong counts.
fn can_remove(loader: &Arc<Self>) -> bool { fn can_remove(loader: &Arc<Self>) -> bool {
// The map itself holds 1 strong count; anything above that means an
// active caller still has a handle.
if Arc::strong_count(loader) > 1 { if Arc::strong_count(loader) > 1 {
return false; return false;
} }
match loader.internal.get() { match loader.internal.get() {
Some(arc) => { // The loader holds 1 strong count; anything above that means
// A strong count of 1 means it's only in the map // a read/write guard is still live somewhere.
Arc::strong_count(arc) == 1 Some(arc) => Arc::strong_count(arc) == 1,
}
None => true, None => true,
} }
} }
/// Returns the serializer, initialising it from disk on the first call.
async fn get(&self) -> Result<Arc<RwLock<S>>, ChunkReadingError> { async fn get(&self) -> Result<Arc<RwLock<S>>, ChunkReadingError> {
self.internal self.internal
.get_or_try_init(|| async { .get_or_try_init(|| async {
@ -86,30 +99,19 @@ impl<S: ChunkSerializer<WriteBackend = PathBuf>> ChunkSerializerLazyLoader<S> {
} }
async fn read_from_disk(&self) -> Result<S, ChunkReadingError> { async fn read_from_disk(&self) -> Result<S, ChunkReadingError> {
trace!("Opening file from Disk: {}", self.path.display()); trace!("Opening file from disk: {}", self.path.display());
let file_bytes = tokio::fs::read(&self.path).await.map_err(|err| { match tokio::fs::read(&self.path).await {
if err.kind() == std::io::ErrorKind::NotFound {
ChunkReadingError::ChunkNotExist
} else {
ChunkReadingError::IoError(err.kind())
}
});
match file_bytes {
Ok(bytes) => { Ok(bytes) => {
let value = S::read(bytes.into())?; let value = S::read(bytes.into())?;
trace!("Successfully read file from Disk: {}", self.path.display()); trace!("Successfully read file from disk: {}", self.path.display());
Ok(value) Ok(value)
} }
Err(ChunkReadingError::ChunkNotExist) => { Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
trace!( trace!("File not found, using default for: {}", self.path.display());
"Chunk not found, using default for: {}",
self.path.display()
);
Ok(S::default()) Ok(S::default())
} }
Err(err) => Err(err), Err(err) => Err(ChunkReadingError::IoError(err.kind())),
} }
} }
} }
@ -125,24 +127,64 @@ impl<S: ChunkSerializer<WriteBackend = PathBuf>> ChunkFileManager<S> {
} }
impl<S: ChunkSerializer<WriteBackend = PathBuf>> ChunkFileManager<S> { impl<S: ChunkSerializer<WriteBackend = PathBuf>> ChunkFileManager<S> {
/// Returns the serializer for `path`, inserting a lazy-loader if absent.
///
/// Uses an optimistic read-first pattern: in the common case (cache hit)
/// we never need a write-lock on the map.
async fn get_serializer(&self, path: &Path) -> Result<Arc<RwLock<S>>, ChunkReadingError> { async fn get_serializer(&self, path: &Path) -> Result<Arc<RwLock<S>>, ChunkReadingError> {
// We get the entry from the DashMap and try to insert a new lock if it doesn't exist {
// using dead-lock safe methods like `or_try_insert_with` let locks = self.file_locks.read().await;
if let Some(loader) = locks.get(path) {
// Clone the Arc *before* releasing the lock so it stays alive.
let loader = loader.clone();
drop(locks);
return loader.get().await;
}
}
// We use a lazy loader here to quickly make an insertion into the map without holding the let loader = {
// lock for too long starving other threads
let loader = self.file_locks.read().await.get(path).cloned();
if let Some(serializer_loader) = loader {
serializer_loader.get().await
} else {
let mut locks = self.file_locks.write().await; let mut locks = self.file_locks.write().await;
let loader = locks locks
.entry(path.into()) .entry(path.into())
.or_insert_with(|| Arc::new(ChunkSerializerLazyLoader::new(path.into()))) .or_insert_with(|| Arc::new(ChunkSerializerLazyLoader::new(path.into())))
.clone(); .clone()
drop(locks); // Write-lock dropped here — `loader.get()` may block on I/O and
// must not hold the map lock.
};
loader.get().await loader.get().await
} }
/// Attempt to evict the cached serializer for `path`.
///
/// The entry is only removed when *both* conditions hold:
/// 1. No watcher still references the path.
/// 2. No other `Arc` clone is live (ensured via `can_remove`).
async fn maybe_evict(&self, path: &PathBuf) {
// Check watchers independently of file_locks to honour lock ordering.
let still_watched = {
let watchers = self.watchers.read().await;
watchers.get(path).is_some_and(|&c| c > 0)
};
if still_watched {
return;
}
let mut locks = self.file_locks.write().await;
let removable = locks
.get(path)
.is_some_and(ChunkSerializerLazyLoader::can_remove);
if removable {
locks.remove(path);
trace!("Evicted serializer cache for {}", path.display());
} else {
trace!(
"Skipping eviction for {} — references still live",
path.display()
);
}
} }
} }
@ -159,16 +201,15 @@ where
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>], chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> { ) -> BoxFuture<'a, ()> {
let paths = chunks
.iter()
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)));
Box::pin(async move { Box::pin(async move {
let paths: Vec<_> = chunks
.iter()
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
.collect();
let mut watchers = self.watchers.write().await; let mut watchers = self.watchers.write().await;
for path in paths { for path in paths {
watchers *watchers.entry(path).or_insert(0) += 1;
.entry(path)
.and_modify(|count| *count += 1)
.or_insert(1);
} }
}) })
} }
@ -178,21 +219,19 @@ where
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>], chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> { ) -> BoxFuture<'a, ()> {
let paths = chunks
.iter()
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)));
Box::pin(async move { Box::pin(async move {
let paths: Vec<_> = chunks
.iter()
.map(|c| P::file_path(folder, &S::get_chunk_key(c)))
.collect();
let mut watchers = self.watchers.write().await; let mut watchers = self.watchers.write().await;
for path in paths { for path in paths {
if let std::collections::btree_map::Entry::Occupied(mut occupied) = if let std::collections::btree_map::Entry::Occupied(mut e) = watchers.entry(path) {
watchers.entry(path) let count = e.get_mut();
{
let count = occupied.get_mut();
*count = count.saturating_sub(1); *count = count.saturating_sub(1);
if *count == 0 { if *count == 0 {
occupied.remove(); e.remove();
} }
} }
} }
@ -209,64 +248,63 @@ where
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunk_coords: &'a [Vector2<i32>], chunk_coords: &'a [Vector2<i32>],
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>, stream: mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) -> BoxFuture<'a, ()> { ) -> BoxFuture<'a, ()> {
Box::pin(async move { Box::pin(async move {
// Group requested chunk coords by their region file.
let mut regions_chunks: BTreeMap<String, Vec<Vector2<i32>>> = BTreeMap::new(); let mut regions_chunks: BTreeMap<String, Vec<Vector2<i32>>> = BTreeMap::new();
for at in chunk_coords { for at in chunk_coords {
let key = S::get_chunk_key(at);
regions_chunks regions_chunks
.entry(key) .entry(S::get_chunk_key(at))
.and_modify(|chunks| chunks.push(*at)) .or_default()
.or_insert(vec![*at]); .push(*at);
} }
// we use a Sync Closure with an Async Block to execute the tasks concurrently let region_tasks = regions_chunks.into_iter().map(|(file_name, chunks)| {
// Also improves File Cache utilizations.
let region_read_tasks = regions_chunks.into_iter().map(move |(file_name, chunks)| {
let task_stream = stream.clone(); let task_stream = stream.clone();
async move { async move {
let path = P::file_path(folder, &file_name); let path = P::file_path(folder, &file_name);
let chunk_serializer = match self.get_serializer(&path).await { let chunk_serializer = match self.get_serializer(&path).await {
Ok(chunk_serializer) => chunk_serializer, Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => { Err(ChunkReadingError::ChunkNotExist) => {
unreachable!("Default Serializer must be created") unreachable!("get_serializer always returns a default serializer")
} }
Err(err) => { Err(err) => {
// Best-effort: report the error for the first coord in the batch.
let _ = task_stream.send(LoadedData::Error((chunks[0], err))).await; let _ = task_stream.send(LoadedData::Error((chunks[0], err))).await;
return; return;
} }
}; };
// A bounded channel of 1 keeps backpressure between the
// serializer and the caller without unbounded buffering.
let (send, mut recv) = let (send, mut recv) =
mpsc::channel::<LoadedData<S::Data, ChunkReadingError>>(1); mpsc::channel::<LoadedData<S::Data, ChunkReadingError>>(1);
let intermediary = async move { // Forward received chunks, wrapping them in `Arc`.
// Note: 'task_stream' is captured and moved here // Captured move is intentional — `task_stream` is consumed here.
let forward = async move {
while let Some(data) = recv.recv().await { while let Some(data) = recv.recv().await {
let wrapped_data = data.map_loaded(|data| Arc::new(data)); let wrapped = data.map_loaded(Arc::new);
if task_stream.send(wrapped_data).await.is_err() { if task_stream.send(wrapped).await.is_err() {
// Stream is closed, so stop unneeded computation and io // Receiver dropped; abort early to avoid wasted work.
return; return;
} }
} }
}; };
// We need to hold the read lock only for the duration of get_chunks // Hold the read lock only for the duration of `get_chunks`.
// This minimizes the time we block other operations let read = async move {
let reader = async move {
let serializer = chunk_serializer.read().await; let serializer = chunk_serializer.read().await;
serializer.get_chunks(chunks, send).await; serializer.get_chunks(chunks, send).await;
}; };
join!(intermediary, reader); join!(forward, read);
} }
}); });
let _ = join_all(region_read_tasks).await; join_all(region_tasks).await;
}) })
} }
@ -276,133 +314,115 @@ where
chunks_data: Vec<(Vector2<i32>, Self::Data)>, chunks_data: Vec<(Vector2<i32>, Self::Data)>,
) -> BoxFuture<'a, Result<(), ChunkWritingError>> { ) -> BoxFuture<'a, Result<(), ChunkWritingError>> {
Box::pin(async move { Box::pin(async move {
// Group chunks by region file.
let mut regions_chunks: BTreeMap<String, Vec<Self::Data>> = BTreeMap::new(); let mut regions_chunks: BTreeMap<String, Vec<Self::Data>> = BTreeMap::new();
for (at, chunk) in chunks_data { for (at, chunk) in chunks_data {
let key = S::get_chunk_key(&at); regions_chunks
.entry(S::get_chunk_key(&at))
match regions_chunks.entry(key) { .or_default()
std::collections::btree_map::Entry::Occupied(mut occupied) => { .push(chunk);
occupied.get_mut().push(chunk);
}
std::collections::btree_map::Entry::Vacant(vacant) => {
vacant.insert(vec![chunk]);
}
}
} }
// we use a Sync Closure with an Async Block to execute the tasks in parallel
// with out waiting the future. Also it improve we File Cache utilizations.
let tasks = regions_chunks let tasks = regions_chunks
.into_iter() .into_iter()
// The closure now needs `move` to capture `self` and `folder` by value .map(|(file_name, chunk_locks)| async move {
.map(move |(file_name, chunk_locks)| async move {
let path = P::file_path(folder, &file_name); let path = P::file_path(folder, &file_name);
trace!("Updating data for file {}", path.display()); trace!("Saving chunks into {}", path.display());
let chunk_serializer = match self.get_serializer(&path).await { let chunk_serializer = match self.get_serializer(&path).await {
Ok(file) => Ok(file), Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => { Err(ChunkReadingError::ChunkNotExist) => {
unreachable!("Must be managed by the cache") unreachable!("get_serializer always returns a default serializer")
} }
Err(ChunkReadingError::IoError(err)) => { Err(ChunkReadingError::IoError(err)) => {
error!("Error reading the data before write: {err}"); error!("I/O error reading region before write: {err}");
Err(ChunkWritingError::IoError(err)) return Err(ChunkWritingError::IoError(err));
} }
Err(_) => Err(ChunkWritingError::IoError(std::io::ErrorKind::Other)), Err(_) => {
}?; return Err(ChunkWritingError::IoError(std::io::ErrorKind::Other));
}
};
// Create a task for each chunk_lock {
let update_tasks = chunk_locks.into_iter().map(|chunk_lock| { let mut writer = chunk_serializer.write().await;
let chunk_serializer = chunk_serializer.clone(); for chunk in &chunk_locks {
async move { // Atomically snapshot and clear the dirty flag before we
let chunk = chunk_lock; // write so that any mutation that races in *during* this
let chunk_is_dirty = chunk.is_dirty(); // serialisation round will mark dirty again correctly.
// Edge case: this chunk is loaded while we were saving, mark it as cleaned since we are let was_dirty = chunk.is_dirty();
// updating what we will write here
chunk.mark_dirty(false); chunk.mark_dirty(false);
// We only need to update the chunk if it is dirty if was_dirty {
if chunk_is_dirty { writer.update_chunk(&**chunk, &self.chunk_config).await?;
let mut writer = chunk_serializer.write().await;
writer.update_chunk(&*chunk, &self.chunk_config).await?;
} }
Ok::<(), ChunkWritingError>(())
} }
}); // Write-lock released here — flush can proceed under a read-lock.
// Run all update tasks concurrently and propagate any error }
futures::future::try_join_all(update_tasks).await?;
trace!("Updated data for file {}", path.display());
trace!("Chunk data updated for {}", path.display());
// We check watchers *after* releasing the write-lock to honour
// lock ordering (serializer lock → watchers, never the reverse).
let is_watched = { let is_watched = {
let watchers = self.watchers.read().await; let watchers = self.watchers.read().await;
watchers.get(&path).is_some_and(|count| count != &0) watchers.get(&path).is_some_and(|&c| c > 0)
}; };
if !is_watched { if !is_watched {
// With the modification done, we can drop the write lock but keep the read lock // A read-lock suffices for `write()` since we have already
// to avoid other threads to write/modify the data, but allow other threads to read it // applied all mutations above.
{
let serializer = chunk_serializer.read().await; let serializer = chunk_serializer.read().await;
debug!("Flushing {} to disk", path.display());
debug!("Writing file for {}", path.display());
serializer serializer
.write(&path) .write(&path)
.await .await
.map_err(|err| ChunkWritingError::IoError(err.kind()))?; .map_err(|e| ChunkWritingError::IoError(e.kind()))?;
// Read-lock released here.
}
// Remove lock // Drop our handle so `can_remove` may succeed.
drop(serializer);
// Decrement strong count
drop(chunk_serializer); drop(chunk_serializer);
let should_remove = { // Evict the cache entry when no longer needed.
let watchers = self.watchers.read().await; self.maybe_evict(&path).await;
watchers.get(&path).is_none_or(|count| count == &0)
};
if should_remove {
let mut locks = self.file_locks.write().await;
let can_remove = if let Some(loader) = locks.get(&path) {
ChunkSerializerLazyLoader::can_remove(loader)
} else {
true
};
if can_remove {
locks.remove(&path);
trace!("Removed lockfile cache {}", path.display());
} else {
trace!("Wanted to remove lockfile cache {} but someone still holds a reference to it!", path.display());
}
}
} }
Ok(()) Ok(())
}); });
//TODO: we need to handle the errors and return the result // Collect all region results; surface the first error encountered.
// files to save let results: Vec<Result<(), ChunkWritingError>> = join_all(tasks).await;
let _test: Vec<Result<(), ChunkWritingError>> = join_all(tasks).await; results.into_iter().find(Result::is_err).unwrap_or(Ok(()))
Ok(())
}) })
} }
/// Blocks until all in-flight serialiser operations have completed by
/// acquiring (and immediately releasing) a write-lock on every cached
/// serialiser.
///
/// This is a linearisation point: after this future resolves no mutation
/// started before the call is still running.
fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> { fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
//we need to block any other operation // Snapshot the current set of loaders under a read-lock so we do
let serializer_cache = self.file_locks.read().await; // not block new insertions longer than necessary.
let loaders: Vec<Arc<ChunkSerializerLazyLoader<S>>> =
{ self.file_locks.read().await.values().cloned().collect() };
// Acquire a write lock on all entries to verify they are complete // For each loader that has been initialised, acquire a write-lock
let _tasks = serializer_cache // and release it immediately. This guarantees that any concurrent
.values() // read or write operation that was in progress has finished.
.map(ChunkSerializerLazyLoader::can_remove); let drain_tasks = loaders.into_iter().map(|loader| async move {
if let Some(serializer_arc) = loader.internal.get() {
// Acquiring + immediately dropping the write-lock acts as a
// barrier: it can only succeed once all current lock holders
// have released their guards.
let _guard = serializer_arc.write().await;
}
});
// We need to wait to ensure that all the locks are acquired join_all(drain_tasks).await;
// so there is no **operation** ongoing
// TODO
// let _ = join_all(tasks);
}) })
} }
} }

View File

@ -1,6 +1,6 @@
use pumpkin_data::{ use pumpkin_data::{
Block, BlockState, Block, BlockState,
block_properties::{BambooLeaves, BambooLikeProperties, BlockProperties, Integer0To1}, block_properties::{BambooLeaves, BambooLikeProperties, BlockProperties},
tag, tag,
}; };
use pumpkin_util::{ use pumpkin_util::{
@ -60,13 +60,13 @@ impl BambooFeature {
if bpos.0.y - pos.0.y >= 3 { if bpos.0.y - pos.0.y >= 3 {
let mut props = BambooLikeProperties::default(&Block::BAMBOO); let mut props = BambooLikeProperties::default(&Block::BAMBOO);
props.leaves = BambooLeaves::Large; props.leaves = BambooLeaves::Large;
props.stage = Integer0To1::L1; props.stage = 1;
chunk.set_block_state( chunk.set_block_state(
&bpos.0, &bpos.0,
BlockState::from_id(props.to_state_id(&Block::BAMBOO)), BlockState::from_id(props.to_state_id(&Block::BAMBOO)),
); );
props.stage = Integer0To1::L0; props.stage = 0;
chunk.set_block_state( chunk.set_block_state(
&bpos.down().0, &bpos.down().0,

View File

@ -1,7 +1,7 @@
use pumpkin_data::{ use pumpkin_data::{
Block, BlockDirection, Block, BlockDirection,
block_properties::{ block_properties::{
BlockProperties, BrownMushroomBlockLikeProperties, ChorusFlowerLikeProperties, Integer0To5, BlockProperties, BrownMushroomBlockLikeProperties, ChorusFlowerLikeProperties,
}, },
tag, tag,
}; };
@ -100,9 +100,7 @@ fn set_chorus_plant<T: GenerationCache>(chunk: &mut T, pos: &BlockPos) {
/// Places a dead chorus flower (age 5) at `pos`. /// Places a dead chorus flower (age 5) at `pos`.
fn place_dead_flower<T: GenerationCache>(chunk: &mut T, pos: &BlockPos) { fn place_dead_flower<T: GenerationCache>(chunk: &mut T, pos: &BlockPos) {
let props = ChorusFlowerLikeProperties { let props = ChorusFlowerLikeProperties { age: 5 };
age: Integer0To5::L5,
};
let state_id = props.to_state_id(&Block::CHORUS_FLOWER); let state_id = props.to_state_id(&Block::CHORUS_FLOWER);
chunk.set_block_state(&pos.0, pumpkin_data::BlockState::from_id(state_id)); chunk.set_block_state(&pos.0, pumpkin_data::BlockState::from_id(state_id));
} }

View File

@ -1,7 +1,7 @@
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use pumpkin_data::{ use pumpkin_data::{
Block, BlockDirection, BlockState, Block, BlockDirection, BlockState,
block_properties::{BlockProperties, EnumVariants, Integer1To4, SeaPickleLikeProperties}, block_properties::{BlockProperties, EnumVariants, SeaPickleLikeProperties},
tag, tag,
}; };
use pumpkin_util::{ use pumpkin_util::{
@ -38,7 +38,7 @@ impl CoralFeature {
); );
} else if random.next_f32() < 0.05 { } else if random.next_f32() < 0.05 {
let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE); let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE);
props.pickles = Integer1To4::from_index(random.next_bounded_i32(4) as u16); // TODO: vanilla adds + 1, but this can crash props.pickles = (random.next_bounded_i32(4) as u8) + 1;
chunk.set_block_state( chunk.set_block_state(
&pos.0, &pos.0,
BlockState::from_id(props.to_state_id(&Block::SEA_PICKLE)), BlockState::from_id(props.to_state_id(&Block::SEA_PICKLE)),

View File

@ -1,7 +1,7 @@
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use pumpkin_data::{ use pumpkin_data::{
Block, BlockState, Block, BlockState,
block_properties::{BlockProperties, EnumVariants, Integer1To4, SeaPickleLikeProperties}, block_properties::{BlockProperties, SeaPickleLikeProperties},
}; };
use pumpkin_util::{ use pumpkin_util::{
math::{int_provider::IntProvider, position::BlockPos}, math::{int_provider::IntProvider, position::BlockPos},
@ -32,7 +32,7 @@ impl SeaPickleFeature {
continue; continue;
} }
let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE); let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE);
props.pickles = Integer1To4::from_index(random.next_bounded_i32(4) as u16); // TODO: vanilla adds + 1, but this can crash props.pickles = (random.next_bounded_i32(4) as u8) + 1;
let pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z); let pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z);
chunk.set_block_state( chunk.set_block_state(
&pos.0, &pos.0,

View File

@ -14,7 +14,6 @@ pub mod generation;
pub mod inventory; pub mod inventory;
pub mod level; pub mod level;
pub mod lighting; pub mod lighting;
pub mod lock;
pub mod poi; pub mod poi;
pub mod tick; pub mod tick;
pub mod world; pub mod world;

View File

@ -1,38 +0,0 @@
use super::{LevelLocker, LockError};
use std::{
fs::{File, TryLockError},
io::Write,
path::Path,
};
pub struct AnvilLevelLocker {
_locked_file: File,
}
const SESSION_LOCK_FILE_NAME: &str = "session.lock";
const SNOWMAN: &[u8] = "☃".as_bytes();
impl LevelLocker<Self> for AnvilLevelLocker {
fn lock(folder: &Path) -> Result<Self, LockError> {
std::fs::create_dir_all(folder).map_err(|_| LockError::FailedWrite)?;
let file_path = folder.join(SESSION_LOCK_FILE_NAME);
let mut file = File::options()
.create(true)
.truncate(false)
.write(true)
.open(file_path)
.unwrap();
// im not joking, mojang writes a snowman into the lock file
file.write_all(SNOWMAN)
.map_err(|_| LockError::FailedWrite)?;
file.try_lock().map_err(|e| match e {
TryLockError::WouldBlock => {
LockError::AlreadyLocked(SESSION_LOCK_FILE_NAME.to_string())
}
TryLockError::Error(io_err) => LockError::Error(io_err),
})?;
Ok(Self { _locked_file: file })
}
}

View File

@ -1,20 +0,0 @@
use std::path::Path;
use thiserror::Error;
pub mod anvil;
// Gets unlocked when dropped
pub trait LevelLocker<T>: Send + Sync {
fn lock(folder: &Path) -> Result<T, LockError>;
}
#[derive(Error, Debug)]
pub enum LockError {
#[error("Oh no, Level is already locked by {0}")]
AlreadyLocked(String),
#[error("{0}")]
Error(std::io::Error),
#[error("Failed to write into lock file")]
FailedWrite,
}

View File

@ -12,7 +12,7 @@ use crate::{
use pumpkin_data::item::Item; use pumpkin_data::item::Item;
use pumpkin_data::{ use pumpkin_data::{
Block, Block,
block_properties::{BlockProperties, CakeLikeProperties, EnumVariants, Integer0To6}, block_properties::{BlockProperties, CakeLikeProperties},
sound::{Sound, SoundCategory}, sound::{Sound, SoundCategory},
}; };
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
@ -52,9 +52,9 @@ impl CakeBlock {
} }
let mut properties = CakeLikeProperties::from_state_id(state_id, block); let mut properties = CakeLikeProperties::from_state_id(state_id, block);
match properties.bites.to_index() { match properties.bites {
0..=5 => { 0..=5 => {
properties.bites = Integer0To6::from_index(properties.bites.to_index() + 1); properties.bites += 1;
world world
.set_block_state( .set_block_state(
location, location,
@ -107,7 +107,7 @@ impl BlockBehaviour for CakeBlock {
drop(item_lock); drop(item_lock);
match item.id { match item.id {
id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => { id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => {
if properties.bites.to_index() != 0 { if properties.bites != 0 {
return Self::consume_if_hungry( return Self::consume_if_hungry(
args.world, args.world,
args.player, args.player,

View File

@ -1,7 +1,7 @@
use pumpkin_data::item::Item; use pumpkin_data::item::Item;
use pumpkin_data::{ use pumpkin_data::{
BlockDirection, BlockDirection,
block_properties::{BlockProperties, CandleLikeProperties, EnumVariants, Integer1To4}, block_properties::{BlockProperties, CandleLikeProperties},
entity::EntityPose, entity::EntityPose,
}; };
use pumpkin_macros::pumpkin_block_from_tag; use pumpkin_macros::pumpkin_block_from_tag;
@ -33,8 +33,8 @@ impl BlockBehaviour for CandleBlock {
&& let BlockIsReplacing::Itself(state_id) = args.replacing && let BlockIsReplacing::Itself(state_id) = args.replacing
{ {
let mut properties = CandleLikeProperties::from_state_id(state_id, args.block); let mut properties = CandleLikeProperties::from_state_id(state_id, args.block);
if properties.candles.to_index() < 3 { if properties.candles < 4 {
properties.candles = Integer1To4::from_index(properties.candles.to_index() + 1); properties.candles += 1;
} }
return properties.to_state_id(args.block); return properties.to_state_id(args.block);
} }
@ -63,9 +63,8 @@ impl BlockBehaviour for CandleBlock {
{ {
let was_lit = properties.lit; let was_lit = properties.lit;
if properties.candles.to_index() < 3 { if properties.candles < 4 {
properties.candles = properties.candles += 1;
Integer1To4::from_index(properties.candles.to_index() + 1);
} }
properties.lit = was_lit; properties.lit = was_lit;
@ -130,8 +129,7 @@ impl BlockBehaviour for CandleBlock {
Box::pin(async move { Box::pin(async move {
let b = args.world.get_block(args.position).await; let b = args.world.get_block(args.position).await;
args.player.get_entity().pose.load() != EntityPose::Crouching args.player.get_entity().pose.load() != EntityPose::Crouching
&& CandleLikeProperties::from_state_id(args.state_id, args.block).candles && CandleLikeProperties::from_state_id(args.state_id, args.block).candles != 4
!= Integer1To4::L4
&& args.block.id == b.id && args.block.id == b.id
}) })
} }

View File

@ -10,7 +10,7 @@ use crate::{
}; };
use pumpkin_data::{ use pumpkin_data::{
Block, Block,
block_properties::{BlockProperties, ComposterLikeProperties, EnumVariants, Integer0To8}, block_properties::{BlockProperties, ComposterLikeProperties},
composter_increase_chance::get_composter_increase_chance_from_item_id, composter_increase_chance::get_composter_increase_chance_from_item_id,
entity::EntityType, entity::EntityType,
item::Item, item::Item,
@ -31,7 +31,7 @@ impl BlockBehaviour for ComposterBlock {
Box::pin(async move { Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position).await; let state_id = args.world.get_block_state_id(args.position).await;
let props = ComposterLikeProperties::from_state_id(state_id, args.block); let props = ComposterLikeProperties::from_state_id(state_id, args.block);
if props.get_level() == 8 { if props.level == 8 {
self.clear_composter(args.world, args.position, state_id, args.block) self.clear_composter(args.world, args.position, state_id, args.block)
.await; .await;
} }
@ -47,7 +47,7 @@ impl BlockBehaviour for ComposterBlock {
Box::pin(async move { Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position).await; let state_id = args.world.get_block_state_id(args.position).await;
let props = ComposterLikeProperties::from_state_id(state_id, args.block); let props = ComposterLikeProperties::from_state_id(state_id, args.block);
let level = props.get_level(); let level = props.level;
// Check if the composter is full // Check if the composter is full
if level == 8 { if level == 8 {
@ -93,7 +93,7 @@ impl BlockBehaviour for ComposterBlock {
Box::pin(async move { Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position).await; let state_id = args.world.get_block_state_id(args.position).await;
let props = ComposterLikeProperties::from_state_id(state_id, args.block); let props = ComposterLikeProperties::from_state_id(state_id, args.block);
let level = props.get_level(); let level = props.level;
if level == 7 { if level == 7 {
self.update_level_composter( self.update_level_composter(
args.world, args.world,
@ -113,7 +113,7 @@ impl BlockBehaviour for ComposterBlock {
) -> BlockFuture<'a, Option<u8>> { ) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move { Box::pin(async move {
let props = ComposterLikeProperties::from_state_id(args.state.id, args.block); let props = ComposterLikeProperties::from_state_id(args.state.id, args.block);
Some(props.get_level()) Some(props.level)
}) })
} }
} }
@ -128,7 +128,7 @@ impl ComposterBlock {
level: u8, level: u8,
) { ) {
let mut props = ComposterLikeProperties::from_state_id(state_id, block); let mut props = ComposterLikeProperties::from_state_id(state_id, block);
props.set_level(level); props.level = level;
world world
.set_block_state(location, props.to_state_id(block), BlockFlags::NOTIFY_ALL) .set_block_state(location, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await; .await;
@ -167,17 +167,3 @@ impl ComposterBlock {
world.spawn_entity(Arc::new(item_entity)).await; world.spawn_entity(Arc::new(item_entity)).await;
} }
} }
pub trait ComposterPropertiesEx {
fn get_level(&self) -> u8;
fn set_level(&mut self, level: u8);
}
impl ComposterPropertiesEx for ComposterLikeProperties {
fn get_level(&self) -> u8 {
self.level.to_index() as u8
}
fn set_level(&mut self, level: u8) {
self.level = Integer0To8::from_index(u16::from(level));
}
}

View File

@ -11,9 +11,7 @@ use crate::world::World;
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_data::BlockDirection; use pumpkin_data::BlockDirection;
use pumpkin_data::block_properties::BlockProperties; use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::block_properties::EnumVariants;
use pumpkin_data::block_properties::FarmlandLikeProperties; use pumpkin_data::block_properties::FarmlandLikeProperties;
use pumpkin_data::block_properties::Integer0To7;
use pumpkin_data::tag; use pumpkin_data::tag;
use pumpkin_data::tag::Taggable; use pumpkin_data::tag::Taggable;
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
@ -77,7 +75,7 @@ impl BlockBehaviour for FarmlandBlock {
// TODO: add rain check. Remember to check which one is most optimized. // TODO: add rain check. Remember to check which one is most optimized.
if is_water_nearby(args.world, args.position).await { if is_water_nearby(args.world, args.position).await {
let mut props = FarmlandProperties::default(args.block); let mut props = FarmlandProperties::default(args.block);
props.moisture = Integer0To7::L7; props.moisture = 7;
args.world args.world
.set_block_state( .set_block_state(
args.position, args.position,
@ -88,7 +86,7 @@ impl BlockBehaviour for FarmlandBlock {
} else { } else {
let state_id = args.world.get_block_state_id(args.position).await; let state_id = args.world.get_block_state_id(args.position).await;
let mut props = FarmlandProperties::from_state_id(state_id, args.block); let mut props = FarmlandProperties::from_state_id(state_id, args.block);
if props.moisture == Integer0To7::L0 { if props.moisture == 0 {
if !args if !args
.world .world
.get_block(&args.position.up()) .get_block(&args.position.up())
@ -105,7 +103,7 @@ impl BlockBehaviour for FarmlandBlock {
.await; .await;
} }
} else { } else {
props.moisture = Integer0To7::from_index(props.moisture.to_index() - 1); props.moisture -= 1;
args.world args.world
.set_block_state( .set_block_state(
args.position, args.position,

View File

@ -1,5 +1,5 @@
use pumpkin_data::biome::Biome; use pumpkin_data::biome::Biome;
use pumpkin_data::block_properties::{BlockProperties, EnumVariants, HorizontalAxis}; use pumpkin_data::block_properties::{BlockProperties, HorizontalAxis};
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use pumpkin_data::fluid::Fluid; use pumpkin_data::fluid::Fluid;
use pumpkin_data::tag::{self, Taggable}; use pumpkin_data::tag::{self, Taggable};
@ -152,7 +152,7 @@ impl FireBlock {
) )
} }
async fn try_spreading_fire(&self, world: &Arc<World>, pos: &BlockPos, chance: i32, age: u16) { async fn try_spreading_fire(&self, world: &Arc<World>, pos: &BlockPos, chance: i32, age: u8) {
let block = world.get_block(pos).await; let block = world.get_block(pos).await;
let odds = Self::get_burn_odds(block); let odds = Self::get_burn_odds(block);
if rand::rng().random_range(0..chance) < odds { if rand::rng().random_range(0..chance) < odds {
@ -160,12 +160,12 @@ impl FireBlock {
if rand::rng().random_range(0..(age + 10) as i32) < 5 if rand::rng().random_range(0..(age + 10) as i32) < 5
&& !Self::is_near_rain(world.as_ref(), pos) && !Self::is_near_rain(world.as_ref(), pos)
{ {
let new_age = (age + (rand::rng().random_range(0..5) / 4)).min(15); let new_age = (age + (rand::rng().random_range(0..5) / 4)).min(15) as u8;
let state_id = self let state_id = self
.get_state_for_position(world.as_ref(), &Block::FIRE, pos) .get_state_for_position(world.as_ref(), &Block::FIRE, pos)
.await; .await;
let mut fire_props = FireProperties::from_state_id(state_id, &Block::FIRE); let mut fire_props = FireProperties::from_state_id(state_id, &Block::FIRE);
fire_props.age = EnumVariants::from_index(new_age); fire_props.age = new_age;
let new_state_id = fire_props.to_state_id(&Block::FIRE); let new_state_id = fire_props.to_state_id(&Block::FIRE);
world world
.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS) .set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS)
@ -244,7 +244,7 @@ impl BlockBehaviour for FireBlock {
.get_state_for_position(args.world, &Block::FIRE, args.position) .get_state_for_position(args.world, &Block::FIRE, args.position)
.await; .await;
let mut fire_props = FireProperties::from_state_id(fire_state_id, &Block::FIRE); let mut fire_props = FireProperties::from_state_id(fire_state_id, &Block::FIRE);
fire_props.age = EnumVariants::from_index(old_fire_props.age.to_index()); fire_props.age = old_fire_props.age;
return fire_props.to_state_id(&Block::FIRE); return fire_props.to_state_id(&Block::FIRE);
} }
Block::AIR.default_state.id Block::AIR.default_state.id
@ -323,7 +323,7 @@ impl BlockBehaviour for FireBlock {
}; };
let mut fire_props = FireProperties::from_state_id(block_state.id, &Block::FIRE); let mut fire_props = FireProperties::from_state_id(block_state.id, &Block::FIRE);
let age = fire_props.age.to_index(); let age = fire_props.age;
// Check if rain should extinguish the fire // Check if rain should extinguish the fire
if !infiniburn && Self::is_near_rain(world.as_ref(), pos) { if !infiniburn && Self::is_near_rain(world.as_ref(), pos) {
@ -341,10 +341,10 @@ impl BlockBehaviour for FireBlock {
} }
// Increment age // Increment age
let random = rand::rng().random_range(0..3) / 2; let random = (rand::rng().random_range(0..3) / 2) as u8;
let new_age = (age + random).min(15); let new_age = (age + random).min(15);
if new_age != age { if new_age != age {
fire_props.age = EnumVariants::from_index(new_age); fire_props.age = new_age;
let new_state_id = fire_props.to_state_id(&Block::FIRE); let new_state_id = fire_props.to_state_id(&Block::FIRE);
world world
.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS) .set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS)
@ -481,14 +481,15 @@ impl BlockBehaviour for FireBlock {
&& rand::rng().random_range(0..rate) <= odds && rand::rng().random_range(0..rate) <= odds
&& !Self::is_near_rain(world.as_ref(), &offset_pos) && !Self::is_near_rain(world.as_ref(), &offset_pos)
{ {
let spread_age = let spread_age = (new_age + rand::rng().random_range(0..5) / 4)
(new_age + rand::rng().random_range(0..5) / 4).min(15); .min(15)
as u8;
let fire_state_id = self let fire_state_id = self
.get_state_for_position(world.as_ref(), block, &offset_pos) .get_state_for_position(world.as_ref(), block, &offset_pos)
.await; .await;
let mut new_fire_props = let mut new_fire_props =
FireProperties::from_state_id(fire_state_id, &Block::FIRE); FireProperties::from_state_id(fire_state_id, &Block::FIRE);
new_fire_props.age = EnumVariants::from_index(spread_age); new_fire_props.age = spread_age;
world world
.set_block_state( .set_block_state(

View File

@ -7,7 +7,7 @@ use pumpkin_data::block_properties::{Axis, NoteblockInstrument};
use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::{ use pumpkin_data::{
Block, Block,
block_properties::{BlockProperties, EnumVariants, Integer0To24, NoteBlockLikeProperties}, block_properties::{BlockProperties, NoteBlockLikeProperties},
}; };
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
@ -85,13 +85,7 @@ impl BlockBehaviour for NoteBlock {
Box::pin(async move { Box::pin(async move {
let block_state = args.world.get_block_state(args.position).await; let block_state = args.world.get_block_state(args.position).await;
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block); let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
let next_index = note_props.note.to_index() + 1; note_props.note = (note_props.note + 1) % 25;
// Increment and check if max
note_props.note = if next_index >= Integer0To24::variant_count() {
Integer0To24::from_index(0)
} else {
Integer0To24::from_index(next_index)
};
args.world args.world
.set_block_state( .set_block_state(
args.position, args.position,
@ -125,7 +119,7 @@ impl BlockBehaviour for NoteBlock {
let instrument = note_props.instrument; let instrument = note_props.instrument;
let pitch = if is_base_block(instrument) { let pitch = if is_base_block(instrument) {
// checks if can be pitched // checks if can be pitched
Self::get_note_pitch(note_props.note.to_index()) Self::get_note_pitch(u16::from(note_props.note))
} else { } else {
1.0 // default pitch 1.0 // default pitch
}; };

View File

@ -1,8 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::block_properties::{ use pumpkin_data::block_properties::{BambooLeaves, BambooLikeProperties, BlockProperties};
BambooLeaves, BambooLikeProperties, BlockProperties, EnumVariants, Integer0To1,
};
use pumpkin_data::item::Item; use pumpkin_data::item::Item;
use pumpkin_data::tag::Block::MINECRAFT_SUPPORTS_BAMBOO; use pumpkin_data::tag::Block::MINECRAFT_SUPPORTS_BAMBOO;
use pumpkin_data::tag::Taggable; use pumpkin_data::tag::Taggable;
@ -44,8 +42,8 @@ impl BlockBehaviour for BambooBlock {
} else if block_below == &Block::BAMBOO { } else if block_below == &Block::BAMBOO {
let props_below = let props_below =
BambooLikeProperties::from_state_id(state_id_below, block_below); BambooLikeProperties::from_state_id(state_id_below, block_below);
if props_below.age.to_index() > 0 { if props_below.age > 0 {
props.age = Integer0To1::L1; props.age = 1;
} }
} else { } else {
let (block_above, state_id_above) = let (block_above, state_id_above) =
@ -119,10 +117,10 @@ impl BlockBehaviour for BambooBlock {
let neighbor_props = let neighbor_props =
BambooLikeProperties::from_state_id(args.neighbor_state_id, neighbor_block); BambooLikeProperties::from_state_id(args.neighbor_state_id, neighbor_block);
let mut props = BambooLikeProperties::from_state_id(args.state_id, args.block); let mut props = BambooLikeProperties::from_state_id(args.state_id, args.block);
if neighbor_props.age.to_index() > props.age.to_index() { if neighbor_props.age > props.age {
props.age = match props.age { props.age = match props.age {
Integer0To1::L0 => Integer0To1::L1, 0 => 1,
Integer0To1::L1 => Integer0To1::L0, _ => 0,
}; };
return props.to_state_id(args.block); return props.to_state_id(args.block);
} }
@ -153,7 +151,7 @@ async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
} }
let mut props = BambooLikeProperties::from_state_id(state_id, block); let mut props = BambooLikeProperties::from_state_id(state_id, block);
if props.stage != Integer0To1::L0 { if props.stage != 0 {
return; return;
} }
@ -200,18 +198,11 @@ async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
} }
} }
props.age = if props.age != Integer0To1::L1 && block_two_below == &Block::BAMBOO { props.age = u8::from(!(props.age != 1 && block_two_below == &Block::BAMBOO));
Integer0To1::L0
} else {
Integer0To1::L1
};
props.stage = props.stage = u8::from(
if (bamboo_count < 11 || rand::rng().random::<f32>() >= 0.25) && bamboo_count != 15 { !((bamboo_count < 11 || rand::rng().random::<f32>() >= 0.25) && bamboo_count != 15),
Integer0To1::L0 );
} else {
Integer0To1::L1
};
world world
.set_block_state(&above_pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL) .set_block_state(&above_pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
@ -265,7 +256,7 @@ async fn bone_meal(world: Arc<World>, position: &BlockPos) {
} }
let next_props = BambooLikeProperties::from_state_id(next_state.id, &Block::BAMBOO); let next_props = BambooLikeProperties::from_state_id(next_state.id, &Block::BAMBOO);
if next_props.stage == Integer0To1::L1 { if next_props.stage == 1 {
return; return;
} }

View File

@ -1,6 +1,4 @@
use pumpkin_data::block_properties::{ use pumpkin_data::block_properties::{BlockProperties, CactusLikeProperties};
BlockProperties, CactusLikeProperties, EnumVariants, Integer0To15,
};
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
use pumpkin_data::tag::Taggable; use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, BlockDirection, tag}; use pumpkin_data::{Block, BlockDirection, tag};
@ -40,12 +38,12 @@ impl BlockBehaviour for CactusBlock {
let mut i = 1; let mut i = 1;
while args.world.get_block(&args.position.down_height(i)).await == &Block::CACTUS { while args.world.get_block(&args.position.down_height(i)).await == &Block::CACTUS {
i += 1; i += 1;
if 1 == 3 && age == Integer0To15::L15 { if 1 == 3 && age == 15 {
return; return;
} }
} }
if age == Integer0To15::L8 && can_place_at(args.world.as_ref(), &block_up).await { if age == 8 && can_place_at(args.world.as_ref(), &block_up).await {
let d = if i >= 3 { 0.25 } else { 0.1 }; let d = if i >= 3 { 0.25 } else { 0.1 };
if rand::rng().random_range(0.0..1.0) <= d { if rand::rng().random_range(0.0..1.0) <= d {
args.world args.world
@ -56,7 +54,7 @@ impl BlockBehaviour for CactusBlock {
) )
.await; .await;
} }
} else if age == Integer0To15::L15 && i < 3 { } else if age == 15 && i < 3 {
args.world args.world
.set_block_state( .set_block_state(
&block_up, &block_up,
@ -65,7 +63,7 @@ impl BlockBehaviour for CactusBlock {
) )
.await; .await;
let mut new_props = CactusLikeProperties::default(&Block::CACTUS); let mut new_props = CactusLikeProperties::default(&Block::CACTUS);
new_props.age = Integer0To15::L0; new_props.age = 0;
args.world args.world
.set_block_state( .set_block_state(
args.position, args.position,
@ -77,8 +75,8 @@ impl BlockBehaviour for CactusBlock {
.update_neighbor(args.position, &Block::CACTUS) .update_neighbor(args.position, &Block::CACTUS)
.await; .await;
} }
if age.to_index() < 15 { if age < 15 {
props.age = Integer0To15::from_index(age.to_index() + 1); props.age = age + 1;
args.world args.world
.set_block_state( .set_block_state(
args.position, args.position,

View File

@ -1,7 +1,5 @@
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_data::block_properties::{ use pumpkin_data::block_properties::{BlockProperties, NetherWartLikeProperties};
BlockProperties, EnumVariants, Integer0To3, NetherWartLikeProperties,
};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_world::BlockStateId; use pumpkin_world::BlockStateId;
use rand::RngExt; use rand::RngExt;
@ -62,12 +60,12 @@ impl CropBlockBase for BeetrootBlock {
fn get_age(&self, state: u16, block: &Block) -> i32 { fn get_age(&self, state: u16, block: &Block) -> i32 {
let props = BeetrootProperties::from_state_id(state, block); let props = BeetrootProperties::from_state_id(state, block);
i32::from(props.age.to_index()) i32::from(props.age)
} }
fn state_with_age(&self, block: &Block, state: u16, age: i32) -> BlockStateId { fn state_with_age(&self, block: &Block, state: u16, age: i32) -> BlockStateId {
let mut props = BeetrootProperties::from_state_id(state, block); let mut props = BeetrootProperties::from_state_id(state, block);
props.age = Integer0To3::from_index(age as u16); props.age = age as u8;
props.to_state_id(block) props.to_state_id(block)
} }
} }

View File

@ -1,8 +1,6 @@
use pumpkin_data::{ use pumpkin_data::{
Block, Block,
block_properties::{ block_properties::{BlockProperties, WallTorchLikeProperties, WheatLikeProperties},
BlockProperties, EnumVariants, Integer0To7, WallTorchLikeProperties, WheatLikeProperties,
},
tag::{self, Taggable}, tag::{self, Taggable},
}; };
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
@ -63,7 +61,7 @@ impl BlockBehaviour for AttachedStemBlock {
&& args.neighbor_state_id != Self::get_gourd(args.block).default_state.id && args.neighbor_state_id != Self::get_gourd(args.block).default_state.id
{ {
let mut props = StemProperties::default(Self::get_stem(args.block)); let mut props = StemProperties::default(Self::get_stem(args.block));
props.age = Integer0To7::from_index(7); props.age = 7;
return props.to_state_id(Self::get_stem(args.block)); return props.to_state_id(Self::get_stem(args.block));
} }
<Self as PlantBlockBase>::get_state_for_neighbor_update( <Self as PlantBlockBase>::get_state_for_neighbor_update(

View File

@ -5,9 +5,7 @@ use crate::block::{
}; };
use pumpkin_data::{ use pumpkin_data::{
Block, BlockDirection, Block, BlockDirection,
block_properties::{ block_properties::{BlockProperties, WallTorchLikeProperties, WheatLikeProperties},
BlockProperties, EnumVariants, Integer0To7, WallTorchLikeProperties, WheatLikeProperties,
},
tag, tag,
tag::Taggable, tag::Taggable,
}; };
@ -35,7 +33,7 @@ impl BlockMetadata for StemBlock {
impl StemBlock { impl StemBlock {
fn state_with_age(block: &Block, state: u16, age: i32) -> BlockStateId { fn state_with_age(block: &Block, state: u16, age: i32) -> BlockStateId {
let mut props = StemProperties::from_state_id(state, block); let mut props = StemProperties::from_state_id(state, block);
props.age = Integer0To7::from_index(age as u16); props.age = age as u8;
props.to_state_id(block) props.to_state_id(block)
} }
@ -88,7 +86,7 @@ impl BlockBehaviour for StemBlock {
if rand::rng().random_range(0..=(25.0 / f).floor() as i32) == 0 { if rand::rng().random_range(0..=(25.0 / f).floor() as i32) == 0 {
let (block, state) = args.world.get_block_and_state_id(args.position).await; let (block, state) = args.world.get_block_and_state_id(args.position).await;
let props = StemProperties::from_state_id(state, block); let props = StemProperties::from_state_id(state, block);
let age = i32::from(props.age.to_index()); let age = i32::from(props.age);
if age < 7 { if age < 7 {
args.world args.world
.set_block_state( .set_block_state(

View File

@ -3,9 +3,7 @@ use std::sync::Arc;
use pumpkin_data::{ use pumpkin_data::{
Block, Block,
BlockDirection::{East, North, South, West}, BlockDirection::{East, North, South, West},
block_properties::{ block_properties::{BlockProperties, FarmlandLikeProperties, WheatLikeProperties},
BlockProperties, EnumVariants, FarmlandLikeProperties, Integer0To7, WheatLikeProperties,
},
}; };
use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::{ use pumpkin_world::{
@ -43,12 +41,12 @@ trait CropBlockBase: PlantBlockBase {
fn get_age(&self, state: u16, block: &Block) -> i32 { fn get_age(&self, state: u16, block: &Block) -> i32 {
let props = CropProperties::from_state_id(state, block); let props = CropProperties::from_state_id(state, block);
i32::from(props.age.to_index()) i32::from(props.age)
} }
fn state_with_age(&self, block: &Block, state: u16, age: i32) -> BlockStateId { fn state_with_age(&self, block: &Block, state: u16, age: i32) -> BlockStateId {
let mut props = CropProperties::from_state_id(state, block); let mut props = CropProperties::from_state_id(state, block);
props.age = Integer0To7::from_index(age as u16); props.age = age as u8;
props.to_state_id(block) props.to_state_id(block)
} }
@ -98,7 +96,7 @@ pub async fn get_available_moisture(world: &Arc<World>, pos: &BlockPos, block: &
if block == &Block::FARMLAND { if block == &Block::FARMLAND {
local_moisture = 1.0; local_moisture = 1.0;
let props = FarmlandProperties::from_state_id(block_state, block); let props = FarmlandProperties::from_state_id(block_state, block);
if props.moisture != Integer0To7::L0 { if props.moisture != 0 {
local_moisture = 3.0; local_moisture = 3.0;
} }
} }

View File

@ -2,7 +2,7 @@ use std::sync::Arc;
use pumpkin_data::{ use pumpkin_data::{
Block, Block,
block_properties::{BlockProperties, EnumVariants, Integer0To3, NetherWartLikeProperties}, block_properties::{BlockProperties, NetherWartLikeProperties},
tag::{self, Taggable}, tag::{self, Taggable},
}; };
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
@ -75,12 +75,12 @@ impl CropBlockBase for NetherWartBlock {
fn get_age(&self, state: u16, block: &Block) -> i32 { fn get_age(&self, state: u16, block: &Block) -> i32 {
let props = NetherWartLikeProperties::from_state_id(state, block); let props = NetherWartLikeProperties::from_state_id(state, block);
i32::from(props.age.to_index()) i32::from(props.age)
} }
fn state_with_age(&self, block: &Block, state: u16, age: i32) -> BlockStateId { fn state_with_age(&self, block: &Block, state: u16, age: i32) -> BlockStateId {
let mut props = NetherWartLikeProperties::from_state_id(state, block); let mut props = NetherWartLikeProperties::from_state_id(state, block);
props.age = Integer0To3::from_index(age as u16); props.age = age as u8;
props.to_state_id(block) props.to_state_id(block)
} }

View File

@ -11,7 +11,7 @@ use crate::{
}; };
use pumpkin_data::{ use pumpkin_data::{
Block, Block,
block_properties::{BlockProperties, EnumVariants, Integer0To3, NetherWartLikeProperties}, block_properties::{BlockProperties, NetherWartLikeProperties},
damage::DamageType, damage::DamageType,
entity::EntityType, entity::EntityType,
item::Item, item::Item,
@ -35,9 +35,9 @@ impl BlockBehaviour for SweetBerryBushBlock {
let state_id = args.world.get_block_state_id(args.position).await; let state_id = args.world.get_block_state_id(args.position).await;
let mut props = NetherWartLikeProperties::from_state_id(state_id, args.block); let mut props = NetherWartLikeProperties::from_state_id(state_id, args.block);
match props.age { match props.age {
Integer0To3::L2 | Integer0To3::L3 => { 2 | 3 => {
let index = props.age.to_index() as u8; let index = props.age;
props.age = Integer0To3::L1; props.age = 1;
let count: u8 = rand::rng().random_range((index - 1)..=(index)); let count: u8 = rand::rng().random_range((index - 1)..=(index));
for _ in 0..count { for _ in 0..count {
args.world args.world
@ -68,9 +68,7 @@ impl BlockBehaviour for SweetBerryBushBlock {
Box::pin(async move { Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position).await; let state_id = args.world.get_block_state_id(args.position).await;
let props = NetherWartLikeProperties::from_state_id(state_id, &Block::SWEET_BERRY_BUSH); let props = NetherWartLikeProperties::from_state_id(state_id, &Block::SWEET_BERRY_BUSH);
if props.age != Integer0To3::L3 if props.age != 3 && args.item_stack.lock().await.get_item() == &Item::BONE_MEAL {
&& args.item_stack.lock().await.get_item() == &Item::BONE_MEAL
{
BlockActionResult::Pass BlockActionResult::Pass
} else { } else {
BlockActionResult::PassToDefaultBlockAction BlockActionResult::PassToDefaultBlockAction
@ -123,7 +121,7 @@ impl BlockBehaviour for SweetBerryBushBlock {
let state_id = args.world.get_block_state_id(args.position).await; let state_id = args.world.get_block_state_id(args.position).await;
let props = NetherWartLikeProperties::from_state_id(state_id, args.block); let props = NetherWartLikeProperties::from_state_id(state_id, args.block);
if props.age == Integer0To3::L0 { if props.age == 0 {
return; return;
} }
@ -177,12 +175,12 @@ impl CropBlockBase for SweetBerryBushBlock {
fn get_age(&self, state: u16, block: &Block) -> i32 { fn get_age(&self, state: u16, block: &Block) -> i32 {
let props = NetherWartLikeProperties::from_state_id(state, block); let props = NetherWartLikeProperties::from_state_id(state, block);
i32::from(props.age.to_index()) i32::from(props.age)
} }
fn state_with_age(&self, block: &Block, state: u16, age: i32) -> BlockStateId { fn state_with_age(&self, block: &Block, state: u16, age: i32) -> BlockStateId {
let mut props = NetherWartLikeProperties::from_state_id(state, block); let mut props = NetherWartLikeProperties::from_state_id(state, block);
props.age = Integer0To3::from_index(age as u16); props.age = age as u8;
props.to_state_id(block) props.to_state_id(block)
} }

View File

@ -1,7 +1,5 @@
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_data::block_properties::{ use pumpkin_data::block_properties::{BlockProperties, TorchflowerCropLikeProperties};
BlockProperties, EnumVariants, Integer0To1, TorchflowerCropLikeProperties,
};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_world::BlockStateId; use pumpkin_world::BlockStateId;
use rand::RngExt; use rand::RngExt;
@ -62,13 +60,13 @@ impl CropBlockBase for TorchFlowerBlock {
fn get_age(&self, state: u16, block: &Block) -> i32 { fn get_age(&self, state: u16, block: &Block) -> i32 {
let props = TorchFlowerProperties::from_state_id(state, block); let props = TorchFlowerProperties::from_state_id(state, block);
i32::from(props.age.to_index()) i32::from(props.age)
} }
fn state_with_age(&self, block: &Block, state: u16, age: i32) -> BlockStateId { fn state_with_age(&self, block: &Block, state: u16, age: i32) -> BlockStateId {
if age == 1 { if age == 1 {
let mut properties = TorchFlowerProperties::from_state_id(state, block); let mut properties = TorchFlowerProperties::from_state_id(state, block);
properties.age = Integer0To1::L1; properties.age = 1;
properties.to_state_id(block) properties.to_state_id(block)
} else { } else {
Block::TORCHFLOWER.default_state.id Block::TORCHFLOWER.default_state.id

View File

@ -93,7 +93,7 @@ impl PlantBlockBase for KelpBlock {
WaterLikeProperties::from_state_id(replacing_block_state.id, replacing_block); WaterLikeProperties::from_state_id(replacing_block_state.id, replacing_block);
//Only allow placing kelp on either full water or downward flowing water //Only allow placing kelp on either full water or downward flowing water
if water_props.level as u8 != 0 && water_props.level as u8 != 8 { if water_props.level != 0 && water_props.level != 8 {
return false; return false;
} }
} else { } else {

View File

@ -1,4 +1,4 @@
use pumpkin_data::block_properties::{BlockProperties, Integer0To1}; use pumpkin_data::block_properties::BlockProperties;
use pumpkin_macros::pumpkin_block_from_tag; use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId; use pumpkin_world::BlockStateId;
@ -20,8 +20,8 @@ impl SaplingBlock {
async fn generate(&self, world: &Arc<World>, pos: &BlockPos) { async fn generate(&self, world: &Arc<World>, pos: &BlockPos) {
let (block, state) = world.get_block_and_state_id(pos).await; let (block, state) = world.get_block_and_state_id(pos).await;
let mut props = SaplingProperties::from_state_id(state, block); let mut props = SaplingProperties::from_state_id(state, block);
if props.stage == Integer0To1::L0 { if props.stage == 0 {
props.stage = Integer0To1::L1; props.stage = 1;
world world
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL) .set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await; .await;

View File

@ -6,7 +6,7 @@ use crate::block::{
}; };
use crate::block::{BlockFuture, BlockIsReplacing}; use crate::block::{BlockFuture, BlockIsReplacing};
use crate::entity::EntityBase; use crate::entity::EntityBase;
use pumpkin_data::block_properties::{BlockProperties, Integer1To4}; use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::entity::EntityPose; use pumpkin_data::entity::EntityPose;
use pumpkin_data::item::Item; use pumpkin_data::item::Item;
use pumpkin_data::tag::Taggable; use pumpkin_data::tag::Taggable;
@ -73,12 +73,7 @@ impl BlockBehaviour for SeaPickleBlock {
} }
let mut sea_pickle_prop = SeaPickleProperties::default(args.block); let mut sea_pickle_prop = SeaPickleProperties::default(args.block);
sea_pickle_prop.pickles = match rand::rng().random_range(0..4) + 1 { sea_pickle_prop.pickles = rand::rng().random_range(1..=4);
1 => Integer1To4::L1,
2 => Integer1To4::L2,
3 => Integer1To4::L3,
_ => Integer1To4::L4,
};
args.world args.world
.set_block_state( .set_block_state(
&lv, &lv,
@ -98,7 +93,7 @@ impl BlockBehaviour for SeaPickleBlock {
count += 1; count += 1;
} }
let mut sea_pickle_prop = SeaPickleProperties::default(args.block); let mut sea_pickle_prop = SeaPickleProperties::default(args.block);
sea_pickle_prop.pickles = Integer1To4::L4; sea_pickle_prop.pickles = 4;
args.world args.world
.set_block_state( .set_block_state(
args.position, args.position,
@ -117,12 +112,8 @@ impl BlockBehaviour for SeaPickleBlock {
&& let BlockIsReplacing::Itself(state_id) = args.replacing && let BlockIsReplacing::Itself(state_id) = args.replacing
{ {
let mut sea_pickle_prop = SeaPickleProperties::from_state_id(state_id, args.block); let mut sea_pickle_prop = SeaPickleProperties::from_state_id(state_id, args.block);
if sea_pickle_prop.pickles != Integer1To4::L4 { if sea_pickle_prop.pickles < 4 {
sea_pickle_prop.pickles = match sea_pickle_prop.pickles { sea_pickle_prop.pickles += 1;
Integer1To4::L1 => Integer1To4::L2,
Integer1To4::L2 => Integer1To4::L3,
_ => Integer1To4::L4,
};
} }
return sea_pickle_prop.to_state_id(args.block); return sea_pickle_prop.to_state_id(args.block);
} }
@ -146,8 +137,7 @@ impl BlockBehaviour for SeaPickleBlock {
fn can_update_at<'a>(&'a self, args: CanUpdateAtArgs<'a>) -> BlockFuture<'a, bool> { fn can_update_at<'a>(&'a self, args: CanUpdateAtArgs<'a>) -> BlockFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {
args.player.get_entity().pose.load() != EntityPose::Crouching args.player.get_entity().pose.load() != EntityPose::Crouching
&& SeaPickleProperties::from_state_id(args.state_id, args.block).pickles && SeaPickleProperties::from_state_id(args.state_id, args.block).pickles < 4
!= Integer1To4::L4
}) })
} }

View File

@ -1,12 +1,12 @@
use pumpkin_data::block_properties::{BlockProperties, HorizontalFacing, Integer1To4}; use pumpkin_data::block_properties::{BlockProperties, HorizontalFacing};
use pumpkin_world::BlockStateId; use pumpkin_world::BlockStateId;
use crate::block::{BlockBehaviour, BlockFuture, CanUpdateAtArgs}; use crate::block::{BlockBehaviour, BlockFuture, CanUpdateAtArgs};
use crate::block::{BlockIsReplacing, OnPlaceArgs}; use crate::block::{BlockIsReplacing, OnPlaceArgs};
pub trait SegmentProperties { pub trait SegmentProperties {
fn get_segment_amount(&self) -> Integer1To4; fn get_segment_amount(&self) -> u8;
fn set_segment_amount(&mut self, amount: Integer1To4); fn set_segment_amount(&mut self, amount: u8);
fn get_facing(&self) -> HorizontalFacing; fn get_facing(&self) -> HorizontalFacing;
fn set_facing(&mut self, facing: HorizontalFacing); fn set_facing(&mut self, facing: HorizontalFacing);
} }
@ -14,11 +14,11 @@ pub trait SegmentProperties {
macro_rules! impl_segment_properties { macro_rules! impl_segment_properties {
($type:ty, $amount_field:ident) => { ($type:ty, $amount_field:ident) => {
impl SegmentProperties for $type { impl SegmentProperties for $type {
fn get_segment_amount(&self) -> Integer1To4 { fn get_segment_amount(&self) -> u8 {
self.$amount_field self.$amount_field
} }
fn set_segment_amount(&mut self, amount: Integer1To4) { fn set_segment_amount(&mut self, amount: u8) {
self.$amount_field = amount; self.$amount_field = amount;
} }
@ -46,28 +46,23 @@ pub trait Segmented: BlockBehaviour {
type Properties: BlockProperties + SegmentProperties; type Properties: BlockProperties + SegmentProperties;
fn can_add_segment(&self, props: &Self::Properties) -> bool { fn can_add_segment(&self, props: &Self::Properties) -> bool {
let amount = props.get_segment_amount(); props.get_segment_amount() < 4
matches!(amount, Integer1To4::L1 | Integer1To4::L2 | Integer1To4::L3)
} }
fn get_next_segment_amount(&self, current: Integer1To4) -> Integer1To4 { fn get_next_segment_amount(&self, current: u8) -> u8 {
match current { (current + 1).min(4)
Integer1To4::L1 => Integer1To4::L2,
Integer1To4::L2 => Integer1To4::L3,
Integer1To4::L3 | Integer1To4::L4 => Integer1To4::L4,
}
} }
fn get_facing_for_segment( fn get_facing_for_segment(
&self, &self,
player_facing: HorizontalFacing, player_facing: HorizontalFacing,
segment_amount: Integer1To4, segment_amount: u8,
) -> HorizontalFacing { ) -> HorizontalFacing {
let base_facing = match segment_amount { let base_facing = match segment_amount {
Integer1To4::L1 => HorizontalFacing::South, 1 => HorizontalFacing::South,
Integer1To4::L2 => HorizontalFacing::East, 2 => HorizontalFacing::East,
Integer1To4::L3 => HorizontalFacing::North, 3 => HorizontalFacing::North,
Integer1To4::L4 => HorizontalFacing::West, _ => HorizontalFacing::West,
}; };
match player_facing { match player_facing {
@ -102,8 +97,8 @@ pub trait Segmented: BlockBehaviour {
// Set first segment orientation based on player direction // Set first segment orientation based on player direction
let player_facing = args.player.living_entity.entity.get_horizontal_facing(); let player_facing = args.player.living_entity.entity.get_horizontal_facing();
let mut props = Self::Properties::default(args.block); let mut props = Self::Properties::default(args.block);
props.set_segment_amount(Integer1To4::L1); props.set_segment_amount(1);
props.set_facing(self.get_facing_for_segment(player_facing, Integer1To4::L1)); props.set_facing(self.get_facing_for_segment(player_facing, 1));
props.to_state_id(args.block) props.to_state_id(args.block)
} }
}) })

View File

@ -2,7 +2,7 @@ use pumpkin_data::block_properties::HorizontalFacing;
use pumpkin_data::tag::Taggable; use pumpkin_data::tag::Taggable;
use pumpkin_data::{ use pumpkin_data::{
Block, Block,
block_properties::{BlockProperties, CactusLikeProperties, EnumVariants, Integer0To15}, block_properties::{BlockProperties, CactusLikeProperties},
tag, tag,
}; };
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
@ -43,13 +43,11 @@ impl BlockBehaviour for SugarCaneBlock {
{ {
let state_id = args.world.get_block_state(args.position).await.id; let state_id = args.world.get_block_state(args.position).await.id;
let age = CactusLikeProperties::from_state_id(state_id, args.block).age; let age = CactusLikeProperties::from_state_id(state_id, args.block).age;
if age == Integer0To15::L15 { if age == 15 {
args.world args.world
.set_block_state(&args.position.up(), state_id, BlockFlags::empty()) .set_block_state(&args.position.up(), state_id, BlockFlags::empty())
.await; .await;
let props = CactusLikeProperties { let props = CactusLikeProperties { age: 0 };
age: Integer0To15::L0,
};
args.world args.world
.set_block_state( .set_block_state(
args.position, args.position,
@ -58,9 +56,7 @@ impl BlockBehaviour for SugarCaneBlock {
) )
.await; .await;
} else { } else {
let props = CactusLikeProperties { let props = CactusLikeProperties { age: age + 1 };
age: Integer0To15::from_index(age.to_index() + 1),
};
args.world args.world
.set_block_state( .set_block_state(
args.position, args.position,

View File

@ -3,8 +3,8 @@ use std::sync::Arc;
use pumpkin_data::{ use pumpkin_data::{
Block, BlockDirection, BlockState, HorizontalFacingExt, Block, BlockDirection, BlockState, HorizontalFacingExt,
block_properties::{ block_properties::{
BlockProperties, ComparatorLikeProperties, EnumVariants, HorizontalFacing, BlockProperties, ComparatorLikeProperties, HorizontalFacing, RedstoneWireLikeProperties,
RedstoneWireLikeProperties, RepeaterLikeProperties, RepeaterLikeProperties,
}, },
}; };
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
@ -300,7 +300,7 @@ pub async fn get_power<T: BlockProperties + RedstoneGateBlockProperties + Send>(
} else { } else {
source_level.max(if source_block == &Block::REDSTONE_WIRE { source_level.max(if source_block == &Block::REDSTONE_WIRE {
let props = RedstoneWireLikeProperties::from_state_id(source_state.id, source_block); let props = RedstoneWireLikeProperties::from_state_id(source_state.id, source_block);
props.power.to_index() as u8 props.power
} else { } else {
0 0
}) })

View File

@ -1,9 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::{ use pumpkin_data::{Block, block_properties::BlockProperties};
Block,
block_properties::{BlockProperties, EnumVariants},
};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::entities::daylight_detector::DaylightDetectorBlockEntity; use pumpkin_world::block::entities::daylight_detector::DaylightDetectorBlockEntity;
@ -61,7 +58,7 @@ impl BlockBehaviour for DaylightDetectorBlock {
Box::pin(async move { Box::pin(async move {
let props = DaylightDetectorProperties::from_state_id(args.state.id, args.block); let props = DaylightDetectorProperties::from_state_id(args.state.id, args.block);
props.power.to_index() as u8 props.power
}) })
} }

View File

@ -1,7 +1,4 @@
use pumpkin_data::{ use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::BlockProperties};
Block, BlockDirection, BlockState,
block_properties::{BlockProperties, EnumVariants, Integer0To15},
};
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos}; use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos};
use pumpkin_world::{BlockStateId, world::BlockFlags}; use pumpkin_world::{BlockStateId, world::BlockFlags};
@ -98,7 +95,7 @@ impl BlockBehaviour for WeightedPressurePlateBlock {
impl PressurePlate for WeightedPressurePlateBlock { impl PressurePlate for WeightedPressurePlateBlock {
fn get_redstone_output(&self, block: &Block, state: BlockStateId) -> u8 { fn get_redstone_output(&self, block: &Block, state: BlockStateId) -> u8 {
let props = PressurePlateProps::from_state_id(state, block); let props = PressurePlateProps::from_state_id(state, block);
props.power.to_index() as u8 props.power
} }
async fn calculate_redstone_output(&self, world: &World, block: &Block, pos: &BlockPos) -> u8 { async fn calculate_redstone_output(&self, world: &World, block: &Block, pos: &BlockPos) -> u8 {
@ -129,7 +126,7 @@ impl PressurePlate for WeightedPressurePlateBlock {
output: u8, output: u8,
) -> pumpkin_world::BlockStateId { ) -> pumpkin_world::BlockStateId {
let mut props = PressurePlateProps::from_state_id(state.id, block); let mut props = PressurePlateProps::from_state_id(state.id, block);
props.power = Integer0To15::from_index(u16::from(output)); props.power = output;
props.to_state_id(block) props.to_state_id(block)
} }

View File

@ -1,9 +1,8 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::block_properties::{ use pumpkin_data::block_properties::{
BlockProperties, EastRedstone, EnumVariants, Integer0To15, NorthRedstone, BlockProperties, EastRedstone, NorthRedstone, ObserverLikeProperties,
ObserverLikeProperties, RedstoneWireLikeProperties, RepeaterLikeProperties, SouthRedstone, RedstoneWireLikeProperties, RepeaterLikeProperties, SouthRedstone, WestRedstone,
WestRedstone,
}; };
use pumpkin_data::{Block, BlockDirection, BlockState, HorizontalFacingExt}; use pumpkin_data::{Block, BlockDirection, BlockState, HorizontalFacingExt};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
@ -37,8 +36,7 @@ impl BlockBehaviour for RedstoneWireBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { Box::pin(async move {
let mut wire = RedstoneWireProperties::default(args.block); let mut wire = RedstoneWireProperties::default(args.block);
wire.power = wire.power = calculate_power(args.world, args.position).await;
Integer0To15::from_index(calculate_power(args.world, args.position).await.into());
wire = get_regulated_sides(wire, args.world, args.position).await; wire = get_regulated_sides(wire, args.world, args.position).await;
if is_dot(wire) { if is_dot(wire) {
wire = make_cross(wire.power); wire = make_cross(wire.power);
@ -89,8 +87,7 @@ impl BlockBehaviour for RedstoneWireBlock {
} }
wire = get_regulated_sides(wire, args.world, args.position).await; wire = get_regulated_sides(wire, args.world, args.position).await;
wire.power = wire.power = calculate_power(args.world, args.position).await;
Integer0To15::from_index(calculate_power(args.world, args.position).await.into());
if is_cross(old_state) && new_side.is_none() { if is_cross(old_state) && new_side.is_none() {
return wire.to_state_id(args.block); return wire.to_state_id(args.block);
@ -177,8 +174,8 @@ impl BlockBehaviour for RedstoneWireBlock {
let state = args.world.get_block_state(args.position).await; let state = args.world.get_block_state(args.position).await;
let mut wire = RedstoneWireProperties::from_state_id(state.id, args.block); let mut wire = RedstoneWireProperties::from_state_id(state.id, args.block);
let new_power = calculate_power(args.world, args.position).await; let new_power = calculate_power(args.world, args.position).await;
if wire.power.to_index() as u8 != new_power { if wire.power != new_power {
wire.power = Integer0To15::from_index(new_power.into()); wire.power = new_power;
args.world args.world
.set_block_state( .set_block_state(
args.position, args.position,
@ -206,7 +203,7 @@ impl BlockBehaviour for RedstoneWireBlock {
if args.direction == BlockDirection::Up if args.direction == BlockDirection::Up
|| wire.is_side_connected(args.direction.opposite()) || wire.is_side_connected(args.direction.opposite())
{ {
wire.power.to_index() as u8 wire.power
} else { } else {
0 0
} }
@ -222,7 +219,7 @@ impl BlockBehaviour for RedstoneWireBlock {
if args.direction == BlockDirection::Up if args.direction == BlockDirection::Up
|| wire.is_side_connected(args.direction.opposite()) || wire.is_side_connected(args.direction.opposite())
{ {
wire.power.to_index() as u8 wire.power
} else { } else {
0 0
} }
@ -273,7 +270,7 @@ async fn on_use(wire: RedstoneWireProperties, world: &Arc<World>, block_pos: &Bl
} }
#[must_use] #[must_use]
pub const fn make_cross(power: Integer0To15) -> RedstoneWireProperties { pub const fn make_cross(power: u8) -> RedstoneWireProperties {
RedstoneWireProperties { RedstoneWireProperties {
north: NorthRedstone::Side, north: NorthRedstone::Side,
south: SouthRedstone::Side, south: SouthRedstone::Side,
@ -529,7 +526,7 @@ async fn max_wire_power(wire_power: u8, world: &World, pos: BlockPos) -> u8 {
let (block, block_state) = world.get_block_and_state(&pos).await; let (block, block_state) = world.get_block_and_state(&pos).await;
if block == &Block::REDSTONE_WIRE { if block == &Block::REDSTONE_WIRE {
let wire = RedstoneWireProperties::from_state_id(block_state.id, block); let wire = RedstoneWireProperties::from_state_id(block_state.id, block);
wire_power.max(wire.power.to_index() as u8) wire_power.max(wire.power)
} else { } else {
wire_power wire_power
} }

View File

@ -2,7 +2,7 @@ use std::sync::Arc;
use pumpkin_data::{ use pumpkin_data::{
Block, BlockDirection, BlockState, HorizontalFacingExt, Block, BlockDirection, BlockState, HorizontalFacingExt,
block_properties::{BlockProperties, EnumVariants, HorizontalFacing, Integer1To4}, block_properties::{BlockProperties, HorizontalFacing},
}; };
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
@ -275,7 +275,7 @@ impl RedstoneGateBlock<RepeaterProperties> for RepeaterBlock {
fn get_update_delay_internal(&self, state_id: BlockStateId, block: &Block) -> u8 { fn get_update_delay_internal(&self, state_id: BlockStateId, block: &Block) -> u8 {
let props = RepeaterProperties::from_state_id(state_id, block); let props = RepeaterProperties::from_state_id(state_id, block);
(props.delay.to_index() as u8 + 1) * 2 props.delay * 2
} }
} }
@ -288,12 +288,7 @@ impl RepeaterBlock {
block: &Block, block: &Block,
) { ) {
let mut props = props; let mut props = props;
props.delay = match props.delay { props.delay = if props.delay == 4 { 1 } else { props.delay + 1 };
Integer1To4::L1 => Integer1To4::L2,
Integer1To4::L2 => Integer1To4::L3,
Integer1To4::L3 => Integer1To4::L4,
Integer1To4::L4 => Integer1To4::L1,
};
let state = props.to_state_id(block); let state = props.to_state_id(block);
world world
.set_block_state(&block_pos, state, BlockFlags::empty()) .set_block_state(&block_pos, state, BlockFlags::empty())

View File

@ -4,7 +4,7 @@
use pumpkin_data::{ use pumpkin_data::{
Block, BlockDirection, BlockState, Block, BlockDirection, BlockState,
block_properties::{BlockProperties, EnumVariants, Integer0To15, RedstoneWireLikeProperties}, block_properties::{BlockProperties, RedstoneWireLikeProperties},
}; };
use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::world::BlockFlags; use pumpkin_world::world::BlockFlags;
@ -402,8 +402,8 @@ impl RedstoneWireTurbo {
if wire_power > j { if wire_power > j {
j = wire_power; j = wire_power;
} }
if i.to_index() as u8 != j { if i != j {
wire.power = Integer0To15::from_index(j.into()); wire.power = j;
world world
.set_block_state( .set_block_state(
&pos, &pos,
@ -419,7 +419,7 @@ impl RedstoneWireTurbo {
let node = &self.nodes[upd.index]; let node = &self.nodes[upd.index];
let block = Block::from_state_id(node.state.id); let block = Block::from_state_id(node.state.id);
if block == &Block::REDSTONE_WIRE { if block == &Block::REDSTONE_WIRE {
(unwrap_wire(node.state).power.to_index() as u8).max(strength) unwrap_wire(node.state).power.max(strength)
} else { } else {
strength strength
} }

View File

@ -4,7 +4,6 @@ use std::sync::atomic::Ordering;
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_data::BlockDirection; use pumpkin_data::BlockDirection;
use pumpkin_data::block_properties::EnumVariants; use pumpkin_data::block_properties::EnumVariants;
use pumpkin_data::block_properties::Integer0To15;
use pumpkin_data::tag::Taggable; use pumpkin_data::tag::Taggable;
use pumpkin_inventory::screen_handler::InventoryPlayer; use pumpkin_inventory::screen_handler::InventoryPlayer;
use pumpkin_macros::pumpkin_block_from_tag; use pumpkin_macros::pumpkin_block_from_tag;
@ -47,7 +46,7 @@ struct SupportInfo {
struct SignPlacement { struct SignPlacement {
block_id: u16, block_id: u16,
facing: Option<String>, facing: Option<String>,
rotation: Option<Integer0To15>, rotation: Option<u8>,
attached: bool, attached: bool,
} }
@ -113,30 +112,21 @@ impl SignBlock {
/// Calculates rotation for wall-hanging signs. /// Calculates rotation for wall-hanging signs.
fn calculate_wall_hanging_rotation( fn calculate_wall_hanging_rotation(
wall_dir: BlockDirection, wall_dir: BlockDirection,
player_rot: Integer0To15, player_rot: u8,
is_sneaking: bool, is_sneaking: bool,
) -> Integer0To15 { ) -> u8 {
if is_sneaking { if is_sneaking {
return player_rot; return player_rot;
} }
let idx = player_rot.to_index();
match wall_dir { match wall_dir {
BlockDirection::North | BlockDirection::South => { BlockDirection::North | BlockDirection::South => {
// Snap to North-South axis (0 or 8) // Snap to North-South axis (0 or 8)
if (4..12).contains(&idx) { if (4..12).contains(&player_rot) { 8 } else { 0 }
Integer0To15::from_index(8)
} else {
Integer0To15::from_index(0)
}
} }
BlockDirection::East | BlockDirection::West => { BlockDirection::East | BlockDirection::West => {
// Snap to East-West axis (4 or 12) // Snap to East-West axis (4 or 12)
if (2..10).contains(&idx) { if (2..10).contains(&player_rot) { 4 } else { 12 }
Integer0To15::from_index(4)
} else {
Integer0To15::from_index(12)
}
} }
_ => player_rot, _ => player_rot,
} }
@ -205,7 +195,7 @@ impl SignBlock {
args: &OnPlaceArgs, args: &OnPlaceArgs,
support: &SupportInfo, support: &SupportInfo,
is_sneaking: bool, is_sneaking: bool,
) -> (Option<String>, Option<Integer0To15>, bool) { ) -> (Option<String>, Option<u8>, bool) {
let wall_dir = if args.direction.is_horizontal() { let wall_dir = if args.direction.is_horizontal() {
args.direction args.direction
} else { } else {
@ -218,7 +208,7 @@ impl SignBlock {
let player_rot = args.player.get_entity().get_flipped_rotation_16(); let player_rot = args.player.get_entity().get_flipped_rotation_16();
let rotation = Self::calculate_wall_hanging_rotation(wall_dir, player_rot, is_sneaking); let rotation = Self::calculate_wall_hanging_rotation(wall_dir, player_rot, is_sneaking);
let is_angled = rotation.to_index() % 4 != 0; let is_angled = rotation % 4 != 0;
let attached = is_angled || is_sneaking; let attached = is_angled || is_sneaking;
(Some(facing.to_string()), Some(rotation), attached) (Some(facing.to_string()), Some(rotation), attached)
@ -228,37 +218,29 @@ impl SignBlock {
fn calculate_ceiling_orientation( fn calculate_ceiling_orientation(
args: &OnPlaceArgs, args: &OnPlaceArgs,
is_sneaking: bool, is_sneaking: bool,
) -> (Option<String>, Option<Integer0To15>, bool) { ) -> (Option<String>, Option<u8>, bool) {
let rotation = if is_sneaking { let rotation = if is_sneaking {
args.player.get_entity().get_flipped_rotation_16() args.player.get_entity().get_flipped_rotation_16()
} else { } else {
// Snap to nearest cardinal // Snap to nearest cardinal
let index = args let index = args.player.get_entity().get_flipped_rotation_16();
.player ((index + 2) / 4 * 4) % 16
.get_entity()
.get_flipped_rotation_16()
.to_index();
Integer0To15::from_index(((index + 2) / 4 * 4) % 16)
}; };
let is_angled = rotation.to_index() % 4 != 0; let is_angled = rotation % 4 != 0;
let attached = is_angled || is_sneaking; let attached = is_angled || is_sneaking;
(None, Some(rotation), attached) (None, Some(rotation), attached)
} }
/// Calculates orientation for wall signs. /// Calculates orientation for wall signs.
fn calculate_wall_orientation( fn calculate_wall_orientation(args: &OnPlaceArgs) -> (Option<String>, Option<u8>, bool) {
args: &OnPlaceArgs,
) -> (Option<String>, Option<Integer0To15>, bool) {
let facing = args.direction.opposite().to_cardinal_direction().to_value(); let facing = args.direction.opposite().to_cardinal_direction().to_value();
(Some(facing.to_string()), None, false) (Some(facing.to_string()), None, false)
} }
/// Calculates orientation for standing signs. /// Calculates orientation for standing signs.
fn calculate_standing_orientation( fn calculate_standing_orientation(args: &OnPlaceArgs) -> (Option<String>, Option<u8>, bool) {
args: &OnPlaceArgs,
) -> (Option<String>, Option<Integer0To15>, bool) {
let rotation = args.player.get_entity().get_flipped_rotation_16(); let rotation = args.player.get_entity().get_flipped_rotation_16();
(None, Some(rotation), false) (None, Some(rotation), false)
} }
@ -279,7 +261,24 @@ impl SignBlock {
if let Some(rotation) = placement.rotation if let Some(rotation) = placement.rotation
&& let Some(prop) = props.iter_mut().find(|(k, _)| *k == "rotation") && let Some(prop) = props.iter_mut().find(|(k, _)| *k == "rotation")
{ {
prop.1 = rotation.to_value(); prop.1 = match rotation {
1 => "1",
2 => "2",
3 => "3",
4 => "4",
5 => "5",
6 => "6",
7 => "7",
8 => "8",
9 => "9",
10 => "10",
11 => "11",
12 => "12",
13 => "13",
14 => "14",
15 => "15",
_ => "0",
};
} }
if let Some(prop) = props.iter_mut().find(|(k, _)| *k == "attached") { if let Some(prop) = props.iter_mut().find(|(k, _)| *k == "attached") {
@ -596,7 +595,7 @@ async fn is_facing_front_text(
if let Some(props) = block.properties(state_id) { if let Some(props) = block.properties(state_id) {
let prop_map = props.to_props(); let prop_map = props.to_props();
if let Some((_, val)) = prop_map.iter().find(|(k, _)| k == &"rotation") { if let Some((_, val)) = prop_map.iter().find(|(k, _)| k == &"rotation") {
let r = Integer0To15::from_value(val); let r = val.parse().unwrap_or(0);
rotation = get_yaw_from_rotation_16(r); rotation = get_yaw_from_rotation_16(r);
} else if let Some((_, val)) = prop_map.iter().find(|(k, _)| k == &"facing") { } else if let Some((_, val)) = prop_map.iter().find(|(k, _)| k == &"facing") {
rotation = match &val[..] { rotation = match &val[..] {
@ -618,10 +617,8 @@ async fn is_facing_front_text(
diff.abs() <= 90.0 diff.abs() <= 90.0
} }
fn get_yaw_from_rotation_16(rotation: Integer0To15) -> f32 { fn get_yaw_from_rotation_16(rotation: u8) -> f32 {
let index = rotation.to_index(); f32::from(rotation) * 22.5
f32::from(index) * 22.5
} }
fn try_claim_sign( fn try_claim_sign(

View File

@ -1,9 +1,5 @@
use pumpkin_data::block_properties::BlockProperties; use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::{ use pumpkin_data::{Block, BlockDirection, block_properties::SnowLikeProperties, item::Item};
Block, BlockDirection,
block_properties::{Integer1To8, SnowLikeProperties},
item::Item,
};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{ use pumpkin_world::{
@ -27,7 +23,7 @@ impl BlockBehaviour for LayeredSnowBlock {
return Block::AIR.default_state.id; return Block::AIR.default_state.id;
} }
let mut props = SnowLikeProperties::default(args.block); let mut props = SnowLikeProperties::default(args.block);
props.layers = Integer1To8::L1; props.layers = 1;
props.to_state_id(&Block::SNOW) props.to_state_id(&Block::SNOW)
}) })
} }
@ -58,15 +54,7 @@ impl BlockBehaviour for LayeredSnowBlock {
} }
let mut props = SnowLikeProperties::from_state_id(state_id, &Block::SNOW); let mut props = SnowLikeProperties::from_state_id(state_id, &Block::SNOW);
props.layers = match props.layers { if props.layers >= 8 {
Integer1To8::L1 => Integer1To8::L2,
Integer1To8::L2 => Integer1To8::L3,
Integer1To8::L3 => Integer1To8::L4,
Integer1To8::L4 => Integer1To8::L5,
Integer1To8::L5 => Integer1To8::L6,
Integer1To8::L6 => Integer1To8::L7,
Integer1To8::L7 => Integer1To8::L8,
Integer1To8::L8 => {
args.world args.world
.set_block_state( .set_block_state(
pos, pos,
@ -76,7 +64,7 @@ impl BlockBehaviour for LayeredSnowBlock {
.await; .await;
return BlockActionResult::Success; return BlockActionResult::Success;
} }
}; props.layers += 1;
let state_id = props.to_state_id(&Block::SNOW); let state_id = props.to_state_id(&Block::SNOW);
args.world args.world

View File

@ -1,4 +1,3 @@
use pumpkin_data::block_properties::Integer0To15;
use pumpkin_data::{Block, BlockState}; use pumpkin_data::{Block, BlockState};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
@ -465,7 +464,7 @@ pub async fn calc_block_breaking(
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
pub enum BlockIsReplacing { pub enum BlockIsReplacing {
Itself(BlockStateId), Itself(BlockStateId),
Water(Integer0To15), Water(u8),
Other, Other,
None, None,
} }
@ -473,10 +472,10 @@ pub enum BlockIsReplacing {
impl BlockIsReplacing { impl BlockIsReplacing {
#[must_use] #[must_use]
/// Returns true if the block was a water source block. /// Returns true if the block was a water source block.
pub fn water_source(&self) -> bool { pub const fn water_source(&self) -> bool {
match self { match self {
// Level 0 means the water is a source block // Level 0 means the water is a source block
Self::Water(level) => *level == Integer0To15::L0, Self::Water(level) => *level == 0,
_ => false, _ => false,
} }
} }

View File

@ -15,7 +15,7 @@ use living::LivingEntity;
use player::Player; use player::Player;
use pumpkin_data::BlockState; use pumpkin_data::BlockState;
use pumpkin_data::biome::Biome; use pumpkin_data::biome::Biome;
use pumpkin_data::block_properties::{EnumVariants, Integer0To15, blocks_movement}; use pumpkin_data::block_properties::blocks_movement;
use pumpkin_data::data_component_impl::EquipmentSlot; use pumpkin_data::data_component_impl::EquipmentSlot;
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use pumpkin_data::entity::EntityStatus; use pumpkin_data::entity::EntityStatus;
@ -2082,33 +2082,14 @@ impl Entity {
} }
} }
pub fn get_rotation_16(&self) -> Integer0To15 { pub fn get_rotation_16(&self) -> u8 {
let adjusted_yaw = self.yaw.load().rem_euclid(360.0); let adjusted_yaw = self.yaw.load().rem_euclid(360.0);
let index = (adjusted_yaw / 22.5).round() as u16 % 16; ((adjusted_yaw / 22.5).round() as u8) % 16
Integer0To15::from_index(index)
} }
pub fn get_flipped_rotation_16(&self) -> Integer0To15 { pub fn get_flipped_rotation_16(&self) -> u8 {
match self.get_rotation_16() { (self.get_rotation_16() + 8) % 16
Integer0To15::L0 => Integer0To15::L8,
Integer0To15::L1 => Integer0To15::L9,
Integer0To15::L2 => Integer0To15::L10,
Integer0To15::L3 => Integer0To15::L11,
Integer0To15::L4 => Integer0To15::L12,
Integer0To15::L5 => Integer0To15::L13,
Integer0To15::L6 => Integer0To15::L14,
Integer0To15::L7 => Integer0To15::L15,
Integer0To15::L8 => Integer0To15::L0,
Integer0To15::L9 => Integer0To15::L1,
Integer0To15::L10 => Integer0To15::L2,
Integer0To15::L11 => Integer0To15::L3,
Integer0To15::L12 => Integer0To15::L4,
Integer0To15::L13 => Integer0To15::L5,
Integer0To15::L14 => Integer0To15::L6,
Integer0To15::L15 => Integer0To15::L7,
}
} }
pub fn get_facing(&self) -> Facing { pub fn get_facing(&self) -> Facing {

View File

@ -29,7 +29,7 @@ use tracing::{debug, warn};
use uuid::Uuid; use uuid::Uuid;
use pumpkin_data::attributes::Attributes; use pumpkin_data::attributes::Attributes;
use pumpkin_data::block_properties::{BlockProperties, EnumVariants, HorizontalFacing}; use pumpkin_data::block_properties::{BlockProperties, HorizontalFacing};
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
use pumpkin_data::data_component_impl::{AttributeModifiersImpl, Operation}; use pumpkin_data::data_component_impl::{AttributeModifiersImpl, Operation};
use pumpkin_data::data_component_impl::{EquipmentSlot, EquippableImpl, ToolImpl, WeaponImpl}; use pumpkin_data::data_component_impl::{EquipmentSlot, EquippableImpl, ToolImpl, WeaponImpl};
@ -1106,10 +1106,8 @@ impl Player {
// Handle respawn anchor (Nether) // Handle respawn anchor (Nether)
if block == &Block::RESPAWN_ANCHOR { if block == &Block::RESPAWN_ANCHOR {
use pumpkin_data::block_properties::Integer0To4;
let anchor_props = AnchorProperties::from_state_id(state_id, block); let anchor_props = AnchorProperties::from_state_id(state_id, block);
let charges = anchor_props.charges.to_index(); let charges = anchor_props.charges;
// Anchor needs at least 1 charge to work // Anchor needs at least 1 charge to work
if charges == 0 { if charges == 0 {
@ -1121,7 +1119,7 @@ impl Player {
// Decrement charges after successful respawn position found // Decrement charges after successful respawn position found
let new_charges = charges - 1; let new_charges = charges - 1;
let mut new_props = anchor_props; let mut new_props = anchor_props;
new_props.charges = Integer0To4::from_index(new_charges); new_props.charges = new_charges;
world world
.set_block_state( .set_block_state(
pos, pos,

View File

@ -31,8 +31,6 @@ use pumpkin_protocol::java::client::play::{CChangeDifficulty, CTabList};
use pumpkin_protocol::{ClientPacket, java::client::config::CPluginMessage}; use pumpkin_protocol::{ClientPacket, java::client::config::CPluginMessage};
use pumpkin_util::Difficulty; use pumpkin_util::Difficulty;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_world::lock::LevelLocker;
use pumpkin_world::lock::anvil::AnvilLevelLocker;
use pumpkin_world::world_info::anvil::{ use pumpkin_world::world_info::anvil::{
AnvilLevelInfo, LEVEL_DAT_BACKUP_FILE_NAME, LEVEL_DAT_FILE_NAME, AnvilLevelInfo, LEVEL_DAT_BACKUP_FILE_NAME, LEVEL_DAT_FILE_NAME,
}; };
@ -124,9 +122,6 @@ pub struct Server {
// world stuff which maybe should be put into a struct // world stuff which maybe should be put into a struct
pub level_info: Arc<ArcSwap<LevelData>>, pub level_info: Arc<ArcSwap<LevelData>>,
world_info_writer: Arc<dyn WorldInfoWriter>, world_info_writer: Arc<dyn WorldInfoWriter>,
// Gets unlocked when dropped
// TODO: Make this a trait
_locker: Arc<Option<AnvilLevelLocker>>,
} }
impl Server { impl Server {
@ -167,16 +162,6 @@ impl Server {
fs::copy(dat_path, backup_path).unwrap(); fs::copy(dat_path, backup_path).unwrap();
} }
} }
let locker = match AnvilLevelLocker::lock(&world_path) {
Ok(l) => Some(l),
Err(err) => {
warn!(
"Could not lock the level file. Data corruption is possible if the world is accessed by multiple processes simultaneously. Error: {err}"
);
None
}
};
let level_info = level_info.unwrap_or_else(|err| { let level_info = level_info.unwrap_or_else(|err| {
warn!("Failed to get level_info, using default instead: {err}"); warn!("Failed to get level_info, using default instead: {err}");
let default_data = LevelData::default(basic_config.seed); let default_data = LevelData::default(basic_config.seed);
@ -254,7 +239,6 @@ impl Server {
mojang_public_keys: ArcSwap::from_pointee(Vec::new()), mojang_public_keys: ArcSwap::from_pointee(Vec::new()),
world_info_writer: Arc::new(AnvilLevelInfo), world_info_writer: Arc::new(AnvilLevelInfo),
level_info, level_info,
_locker: Arc::new(locker),
}; };
let server = Arc::new(server); let server = Arc::new(server);