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 {
/// The property is a simple boolean (`true`/`false`).
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`.
Enum { name: String },
}
@ -211,6 +213,7 @@ impl ToTokens for BlockPropertyStruct {
let key = Ident::new_raw(&entry.original_name, Span::call_site());
match &entry.property_type {
PropertyType::Bool => quote! { pub #key: bool },
PropertyType::Int { .. } => quote! { pub #key: u8 },
PropertyType::Enum { name } => {
let value = Ident::new(name, Span::call_site());
quote! { pub #key: #value }
@ -229,6 +232,10 @@ impl ToTokens for BlockPropertyStruct {
let field = Ident::new_raw(&entry.original_name, Span::call_site());
match &entry.property_type {
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 } => {
let ty = Ident::new(name, Span::call_site());
quote! { (self.#field.to_index(), #ty::variant_count()) }
@ -251,6 +258,16 @@ impl ToTokens for BlockPropertyStruct {
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 } => {
let enum_ident = Ident::new(name, Span::call_site());
quote! {
@ -272,6 +289,19 @@ impl ToTokens for BlockPropertyStruct {
PropertyType::Bool => quote! {
(#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! {
(#key_str, self.#field.to_value())
},
@ -287,6 +317,21 @@ impl ToTokens for BlockPropertyStruct {
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 } => {
let enum_ident = Ident::new(name, Span::call_site());
quote! {
@ -596,6 +641,8 @@ pub struct Block {
pub states: Vec<BlockState>,
/// Experience points dropped when the block is mined, if any.
pub experience: Option<Experience>,
/// Calculated property descriptors for the block.
pub property_descriptors: Vec<TokenStream>,
}
impl ToTokens for Block {
@ -638,6 +685,7 @@ impl ToTokens for Block {
} else {
quote! { None }
};
let property_descriptors = &self.property_descriptors;
tokens.extend(quote! {
Block {
id: #id,
@ -650,6 +698,7 @@ impl ToTokens for Block {
item_id: #item_id,
default_state: &#default_state,
states: &[#(#states),*],
properties: &[#(#property_descriptors),*],
flammable: #flammable,
loot_table: #loot_table,
experience: #experience,
@ -800,15 +849,14 @@ pub fn build() -> TokenStream {
let property = generated_property.to_property();
let renamed_property = property.enum_name.to_upper_camel_case();
let property_type = if property.values.len() == 2
&& property.values.contains(&"true".to_string())
&& property.values.contains(&"false".to_string())
{
PropertyType::Bool
} else {
PropertyType::Enum {
name: renamed_property.clone(),
let property_type = match &generated_property.property_type {
GeneratedPropertyType::Boolean => PropertyType::Bool,
GeneratedPropertyType::Int { min, max } => {
PropertyType::Int { min: *min, max: *max }
}
GeneratedPropertyType::Enum { .. } => PropertyType::Enum {
name: renamed_property.clone(),
},
};
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 name_str = &block.name;
let id_lit = LitInt::new(&block.id.to_string(), Span::call_site());
let item_id = block.item_id;
let mut block_with_descriptors = block.clone();
block_with_descriptors.property_descriptors = property_descriptors;
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 }));
@ -887,6 +957,15 @@ pub fn build() -> TokenStream {
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 } => {
let enum_info = property_enums.get(name).unwrap();
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",
}
}
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;
for _ in 0..patch_len {
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()?;
if is_present {

View File

@ -1,7 +1,7 @@
use std::pin::Pin;
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_util::math::position::BlockPos;
@ -120,7 +120,7 @@ impl DaylightDetectorBlockEntity {
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 {
props.power = power;
let state = props.to_state_id(block);

View File

@ -22,24 +22,29 @@ use crate::{
use super::{ChunkSerializer, FileIO, LoadedData};
/// A simple implementation of the `ChunkSerializer` trait
/// that load and save the data from a file in the disk
/// using parallelism and a cache for the files with ongoing IO operations.
/// A simple implementation of the `ChunkSerializer` trait that loads and saves data
/// to disk using parallelism and a lazy-loading cache keyed by file path.
///
/// It also avoid IO operations that could produce dataraces thanks to the
/// custom *`DashMap`*-like implementation.
/// ### Concurrency model
///
/// * `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>> {
// 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>>>>,
watchers: RwLock<BTreeMap<PathBuf, usize>>,
chunk_config: S::ChunkConfig,
}
@ -49,6 +54,7 @@ pub(crate) trait PathFromLevelFolder {
struct ChunkSerializerLazyLoader<S: ChunkSerializer<WriteBackend = PathBuf>> {
path: PathBuf,
/// Initialised at most once; subsequent calls reuse the same Arc.
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
/// IMPORTANT: This must be called within the write lock of the parent map
/// Returns `true` only when no outside caller still holds a clone of this
/// 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 {
// The map itself holds 1 strong count; anything above that means an
// active caller still has a handle.
if Arc::strong_count(loader) > 1 {
return false;
}
match loader.internal.get() {
Some(arc) => {
// A strong count of 1 means it's only in the map
Arc::strong_count(arc) == 1
}
// The loader holds 1 strong count; anything above that means
// a read/write guard is still live somewhere.
Some(arc) => Arc::strong_count(arc) == 1,
None => true,
}
}
/// Returns the serializer, initialising it from disk on the first call.
async fn get(&self) -> Result<Arc<RwLock<S>>, ChunkReadingError> {
self.internal
.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> {
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| {
if err.kind() == std::io::ErrorKind::NotFound {
ChunkReadingError::ChunkNotExist
} else {
ChunkReadingError::IoError(err.kind())
}
});
match file_bytes {
match tokio::fs::read(&self.path).await {
Ok(bytes) => {
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)
}
Err(ChunkReadingError::ChunkNotExist) => {
trace!(
"Chunk not found, using default for: {}",
self.path.display()
);
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
trace!("File not found, using default for: {}", self.path.display());
Ok(S::default())
}
Err(err) => Err(err),
Err(err) => Err(ChunkReadingError::IoError(err.kind())),
}
}
}
@ -125,23 +127,63 @@ 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> {
// 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
// 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 loader = {
let mut locks = self.file_locks.write().await;
let loader = locks
locks
.entry(path.into())
.or_insert_with(|| Arc::new(ChunkSerializerLazyLoader::new(path.into())))
.clone();
drop(locks);
loader.get().await
.clone()
// Write-lock dropped here — `loader.get()` may block on I/O and
// must not hold the map lock.
};
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,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> {
let paths = chunks
.iter()
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)));
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;
for path in paths {
watchers
.entry(path)
.and_modify(|count| *count += 1)
.or_insert(1);
*watchers.entry(path).or_insert(0) += 1;
}
})
}
@ -178,21 +219,19 @@ where
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> {
let paths = chunks
.iter()
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)));
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;
for path in paths {
if let std::collections::btree_map::Entry::Occupied(mut occupied) =
watchers.entry(path)
{
let count = occupied.get_mut();
if let std::collections::btree_map::Entry::Occupied(mut e) = watchers.entry(path) {
let count = e.get_mut();
*count = count.saturating_sub(1);
if *count == 0 {
occupied.remove();
e.remove();
}
}
}
@ -209,64 +248,63 @@ where
&'a self,
folder: &'a LevelFolder,
chunk_coords: &'a [Vector2<i32>],
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
stream: mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) -> BoxFuture<'a, ()> {
Box::pin(async move {
// Group requested chunk coords by their region file.
let mut regions_chunks: BTreeMap<String, Vec<Vector2<i32>>> = BTreeMap::new();
for at in chunk_coords {
let key = S::get_chunk_key(at);
regions_chunks
.entry(key)
.and_modify(|chunks| chunks.push(*at))
.or_insert(vec![*at]);
.entry(S::get_chunk_key(at))
.or_default()
.push(*at);
}
// we use a Sync Closure with an Async Block to execute the tasks concurrently
// Also improves File Cache utilizations.
let region_read_tasks = regions_chunks.into_iter().map(move |(file_name, chunks)| {
let region_tasks = regions_chunks.into_iter().map(|(file_name, chunks)| {
let task_stream = stream.clone();
async move {
let path = P::file_path(folder, &file_name);
let chunk_serializer = match self.get_serializer(&path).await {
Ok(chunk_serializer) => chunk_serializer,
Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => {
unreachable!("Default Serializer must be created")
unreachable!("get_serializer always returns a default serializer")
}
Err(err) => {
// Best-effort: report the error for the first coord in the batch.
let _ = task_stream.send(LoadedData::Error((chunks[0], err))).await;
return;
}
};
// A bounded channel of 1 keeps backpressure between the
// serializer and the caller without unbounded buffering.
let (send, mut recv) =
mpsc::channel::<LoadedData<S::Data, ChunkReadingError>>(1);
let intermediary = async move {
// Note: 'task_stream' is captured and moved here
// Forward received chunks, wrapping them in `Arc`.
// Captured move is intentional — `task_stream` is consumed here.
let forward = async move {
while let Some(data) = recv.recv().await {
let wrapped_data = data.map_loaded(|data| Arc::new(data));
if task_stream.send(wrapped_data).await.is_err() {
// Stream is closed, so stop unneeded computation and io
let wrapped = data.map_loaded(Arc::new);
if task_stream.send(wrapped).await.is_err() {
// Receiver dropped; abort early to avoid wasted work.
return;
}
}
};
// We need to hold the read lock only for the duration of get_chunks
// This minimizes the time we block other operations
let reader = async move {
// Hold the read lock only for the duration of `get_chunks`.
let read = async move {
let serializer = chunk_serializer.read().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)>,
) -> BoxFuture<'a, Result<(), ChunkWritingError>> {
Box::pin(async move {
// Group chunks by region file.
let mut regions_chunks: BTreeMap<String, Vec<Self::Data>> = BTreeMap::new();
for (at, chunk) in chunks_data {
let key = S::get_chunk_key(&at);
match regions_chunks.entry(key) {
std::collections::btree_map::Entry::Occupied(mut occupied) => {
occupied.get_mut().push(chunk);
}
std::collections::btree_map::Entry::Vacant(vacant) => {
vacant.insert(vec![chunk]);
}
}
regions_chunks
.entry(S::get_chunk_key(&at))
.or_default()
.push(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
.into_iter()
// The closure now needs `move` to capture `self` and `folder` by value
.map(move |(file_name, chunk_locks)| async move {
.map(|(file_name, chunk_locks)| async move {
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 {
Ok(file) => Ok(file),
Ok(s) => s,
Err(ChunkReadingError::ChunkNotExist) => {
unreachable!("Must be managed by the cache")
unreachable!("get_serializer always returns a default serializer")
}
Err(ChunkReadingError::IoError(err)) => {
error!("Error reading the data before write: {err}");
Err(ChunkWritingError::IoError(err))
error!("I/O error reading region before write: {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 chunk_serializer = chunk_serializer.clone();
async move {
let chunk = chunk_lock;
let chunk_is_dirty = chunk.is_dirty();
// Edge case: this chunk is loaded while we were saving, mark it as cleaned since we are
// updating what we will write here
{
let mut writer = chunk_serializer.write().await;
for chunk in &chunk_locks {
// Atomically snapshot and clear the dirty flag before we
// write so that any mutation that races in *during* this
// serialisation round will mark dirty again correctly.
let was_dirty = chunk.is_dirty();
chunk.mark_dirty(false);
// We only need to update the chunk if it is dirty
if chunk_is_dirty {
let mut writer = chunk_serializer.write().await;
writer.update_chunk(&*chunk, &self.chunk_config).await?;
if was_dirty {
writer.update_chunk(&**chunk, &self.chunk_config).await?;
}
Ok::<(), ChunkWritingError>(())
}
});
// Run all update tasks concurrently and propagate any error
futures::future::try_join_all(update_tasks).await?;
trace!("Updated data for file {}", path.display());
// Write-lock released here — flush can proceed under a read-lock.
}
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 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 {
// With the modification done, we can drop the write lock but keep the read lock
// to avoid other threads to write/modify the data, but allow other threads to read it
let serializer = chunk_serializer.read().await;
// A read-lock suffices for `write()` since we have already
// applied all mutations above.
{
let serializer = chunk_serializer.read().await;
debug!("Flushing {} to disk", path.display());
serializer
.write(&path)
.await
.map_err(|e| ChunkWritingError::IoError(e.kind()))?;
// Read-lock released here.
}
debug!("Writing file for {}", path.display());
serializer
.write(&path)
.await
.map_err(|err| ChunkWritingError::IoError(err.kind()))?;
// Remove lock
drop(serializer);
// Decrement strong count
// Drop our handle so `can_remove` may succeed.
drop(chunk_serializer);
let should_remove = {
let watchers = self.watchers.read().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());
}
}
// Evict the cache entry when no longer needed.
self.maybe_evict(&path).await;
}
Ok(())
});
//TODO: we need to handle the errors and return the result
// files to save
let _test: Vec<Result<(), ChunkWritingError>> = join_all(tasks).await;
Ok(())
// Collect all region results; surface the first error encountered.
let results: Vec<Result<(), ChunkWritingError>> = join_all(tasks).await;
results.into_iter().find(Result::is_err).unwrap_or(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<'_, ()> {
Box::pin(async move {
//we need to block any other operation
let serializer_cache = self.file_locks.read().await;
// Snapshot the current set of loaders under a read-lock so we do
// 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
let _tasks = serializer_cache
.values()
.map(ChunkSerializerLazyLoader::can_remove);
// For each loader that has been initialised, acquire a write-lock
// and release it immediately. This guarantees that any concurrent
// read or write operation that was in progress has finished.
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
// so there is no **operation** ongoing
// TODO
// let _ = join_all(tasks);
join_all(drain_tasks).await;
})
}
}

View File

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

View File

@ -1,7 +1,7 @@
use pumpkin_data::{
Block, BlockDirection,
block_properties::{
BlockProperties, BrownMushroomBlockLikeProperties, ChorusFlowerLikeProperties, Integer0To5,
BlockProperties, BrownMushroomBlockLikeProperties, ChorusFlowerLikeProperties,
},
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`.
fn place_dead_flower<T: GenerationCache>(chunk: &mut T, pos: &BlockPos) {
let props = ChorusFlowerLikeProperties {
age: Integer0To5::L5,
};
let props = ChorusFlowerLikeProperties { age: 5 };
let state_id = props.to_state_id(&Block::CHORUS_FLOWER);
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 pumpkin_data::{
Block, BlockDirection, BlockState,
block_properties::{BlockProperties, EnumVariants, Integer1To4, SeaPickleLikeProperties},
block_properties::{BlockProperties, EnumVariants, SeaPickleLikeProperties},
tag,
};
use pumpkin_util::{
@ -38,7 +38,7 @@ impl CoralFeature {
);
} else if random.next_f32() < 0.05 {
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(
&pos.0,
BlockState::from_id(props.to_state_id(&Block::SEA_PICKLE)),

View File

@ -1,7 +1,7 @@
use crate::generation::proto_chunk::GenerationCache;
use pumpkin_data::{
Block, BlockState,
block_properties::{BlockProperties, EnumVariants, Integer1To4, SeaPickleLikeProperties},
block_properties::{BlockProperties, SeaPickleLikeProperties},
};
use pumpkin_util::{
math::{int_provider::IntProvider, position::BlockPos},
@ -32,7 +32,7 @@ impl SeaPickleFeature {
continue;
}
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);
chunk.set_block_state(
&pos.0,

View File

@ -14,7 +14,6 @@ pub mod generation;
pub mod inventory;
pub mod level;
pub mod lighting;
pub mod lock;
pub mod poi;
pub mod tick;
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::{
Block,
block_properties::{BlockProperties, CakeLikeProperties, EnumVariants, Integer0To6},
block_properties::{BlockProperties, CakeLikeProperties},
sound::{Sound, SoundCategory},
};
use pumpkin_macros::pumpkin_block;
@ -52,9 +52,9 @@ impl CakeBlock {
}
let mut properties = CakeLikeProperties::from_state_id(state_id, block);
match properties.bites.to_index() {
match properties.bites {
0..=5 => {
properties.bites = Integer0To6::from_index(properties.bites.to_index() + 1);
properties.bites += 1;
world
.set_block_state(
location,
@ -107,7 +107,7 @@ impl BlockBehaviour for CakeBlock {
drop(item_lock);
match item.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(
args.world,
args.player,

View File

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

View File

@ -10,7 +10,7 @@ use crate::{
};
use pumpkin_data::{
Block,
block_properties::{BlockProperties, ComposterLikeProperties, EnumVariants, Integer0To8},
block_properties::{BlockProperties, ComposterLikeProperties},
composter_increase_chance::get_composter_increase_chance_from_item_id,
entity::EntityType,
item::Item,
@ -31,7 +31,7 @@ impl BlockBehaviour for ComposterBlock {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position).await;
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)
.await;
}
@ -47,7 +47,7 @@ impl BlockBehaviour for ComposterBlock {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position).await;
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
if level == 8 {
@ -93,7 +93,7 @@ impl BlockBehaviour for ComposterBlock {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position).await;
let props = ComposterLikeProperties::from_state_id(state_id, args.block);
let level = props.get_level();
let level = props.level;
if level == 7 {
self.update_level_composter(
args.world,
@ -113,7 +113,7 @@ impl BlockBehaviour for ComposterBlock {
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
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,
) {
let mut props = ComposterLikeProperties::from_state_id(state_id, block);
props.set_level(level);
props.level = level;
world
.set_block_state(location, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
@ -167,17 +167,3 @@ impl ComposterBlock {
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::BlockDirection;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::block_properties::EnumVariants;
use pumpkin_data::block_properties::FarmlandLikeProperties;
use pumpkin_data::block_properties::Integer0To7;
use pumpkin_data::tag;
use pumpkin_data::tag::Taggable;
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.
if is_water_nearby(args.world, args.position).await {
let mut props = FarmlandProperties::default(args.block);
props.moisture = Integer0To7::L7;
props.moisture = 7;
args.world
.set_block_state(
args.position,
@ -88,7 +86,7 @@ impl BlockBehaviour for FarmlandBlock {
} else {
let state_id = args.world.get_block_state_id(args.position).await;
let mut props = FarmlandProperties::from_state_id(state_id, args.block);
if props.moisture == Integer0To7::L0 {
if props.moisture == 0 {
if !args
.world
.get_block(&args.position.up())
@ -105,7 +103,7 @@ impl BlockBehaviour for FarmlandBlock {
.await;
}
} else {
props.moisture = Integer0To7::from_index(props.moisture.to_index() - 1);
props.moisture -= 1;
args.world
.set_block_state(
args.position,

View File

@ -1,5 +1,5 @@
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::fluid::Fluid;
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 odds = Self::get_burn_odds(block);
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
&& !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
.get_state_for_position(world.as_ref(), &Block::FIRE, pos)
.await;
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);
world
.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)
.await;
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);
}
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 age = fire_props.age.to_index();
let age = fire_props.age;
// Check if rain should extinguish the fire
if !infiniburn && Self::is_near_rain(world.as_ref(), pos) {
@ -341,10 +341,10 @@ impl BlockBehaviour for FireBlock {
}
// 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);
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);
world
.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
&& !Self::is_near_rain(world.as_ref(), &offset_pos)
{
let spread_age =
(new_age + rand::rng().random_range(0..5) / 4).min(15);
let spread_age = (new_age + rand::rng().random_range(0..5) / 4)
.min(15)
as u8;
let fire_state_id = self
.get_state_for_position(world.as_ref(), block, &offset_pos)
.await;
let mut new_fire_props =
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
.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::{
Block,
block_properties::{BlockProperties, EnumVariants, Integer0To24, NoteBlockLikeProperties},
block_properties::{BlockProperties, NoteBlockLikeProperties},
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
@ -85,13 +85,7 @@ impl BlockBehaviour for NoteBlock {
Box::pin(async move {
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 next_index = note_props.note.to_index() + 1;
// Increment and check if max
note_props.note = if next_index >= Integer0To24::variant_count() {
Integer0To24::from_index(0)
} else {
Integer0To24::from_index(next_index)
};
note_props.note = (note_props.note + 1) % 25;
args.world
.set_block_state(
args.position,
@ -125,7 +119,7 @@ impl BlockBehaviour for NoteBlock {
let instrument = note_props.instrument;
let pitch = if is_base_block(instrument) {
// checks if can be pitched
Self::get_note_pitch(note_props.note.to_index())
Self::get_note_pitch(u16::from(note_props.note))
} else {
1.0 // default pitch
};

View File

@ -1,8 +1,6 @@
use std::sync::Arc;
use pumpkin_data::block_properties::{
BambooLeaves, BambooLikeProperties, BlockProperties, EnumVariants, Integer0To1,
};
use pumpkin_data::block_properties::{BambooLeaves, BambooLikeProperties, BlockProperties};
use pumpkin_data::item::Item;
use pumpkin_data::tag::Block::MINECRAFT_SUPPORTS_BAMBOO;
use pumpkin_data::tag::Taggable;
@ -44,8 +42,8 @@ impl BlockBehaviour for BambooBlock {
} else if block_below == &Block::BAMBOO {
let props_below =
BambooLikeProperties::from_state_id(state_id_below, block_below);
if props_below.age.to_index() > 0 {
props.age = Integer0To1::L1;
if props_below.age > 0 {
props.age = 1;
}
} else {
let (block_above, state_id_above) =
@ -119,10 +117,10 @@ impl BlockBehaviour for BambooBlock {
let neighbor_props =
BambooLikeProperties::from_state_id(args.neighbor_state_id, neighbor_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 {
Integer0To1::L0 => Integer0To1::L1,
Integer0To1::L1 => Integer0To1::L0,
0 => 1,
_ => 0,
};
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);
if props.stage != Integer0To1::L0 {
if props.stage != 0 {
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 {
Integer0To1::L0
} else {
Integer0To1::L1
};
props.age = u8::from(!(props.age != 1 && block_two_below == &Block::BAMBOO));
props.stage =
if (bamboo_count < 11 || rand::rng().random::<f32>() >= 0.25) && bamboo_count != 15 {
Integer0To1::L0
} else {
Integer0To1::L1
};
props.stage = u8::from(
!((bamboo_count < 11 || rand::rng().random::<f32>() >= 0.25) && bamboo_count != 15),
);
world
.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);
if next_props.stage == Integer0To1::L1 {
if next_props.stage == 1 {
return;
}

View File

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

View File

@ -1,7 +1,5 @@
use pumpkin_data::Block;
use pumpkin_data::block_properties::{
BlockProperties, EnumVariants, Integer0To3, NetherWartLikeProperties,
};
use pumpkin_data::block_properties::{BlockProperties, NetherWartLikeProperties};
use pumpkin_macros::pumpkin_block;
use pumpkin_world::BlockStateId;
use rand::RngExt;
@ -62,12 +60,12 @@ impl CropBlockBase for BeetrootBlock {
fn get_age(&self, state: u16, block: &Block) -> i32 {
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 {
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)
}
}

View File

@ -1,8 +1,6 @@
use pumpkin_data::{
Block,
block_properties::{
BlockProperties, EnumVariants, Integer0To7, WallTorchLikeProperties, WheatLikeProperties,
},
block_properties::{BlockProperties, WallTorchLikeProperties, WheatLikeProperties},
tag::{self, Taggable},
};
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
{
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));
}
<Self as PlantBlockBase>::get_state_for_neighbor_update(

View File

@ -5,9 +5,7 @@ use crate::block::{
};
use pumpkin_data::{
Block, BlockDirection,
block_properties::{
BlockProperties, EnumVariants, Integer0To7, WallTorchLikeProperties, WheatLikeProperties,
},
block_properties::{BlockProperties, WallTorchLikeProperties, WheatLikeProperties},
tag,
tag::Taggable,
};
@ -35,7 +33,7 @@ impl BlockMetadata for StemBlock {
impl StemBlock {
fn state_with_age(block: &Block, state: u16, age: i32) -> BlockStateId {
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)
}
@ -88,7 +86,7 @@ impl BlockBehaviour for StemBlock {
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 props = StemProperties::from_state_id(state, block);
let age = i32::from(props.age.to_index());
let age = i32::from(props.age);
if age < 7 {
args.world
.set_block_state(

View File

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

View File

@ -2,7 +2,7 @@ use std::sync::Arc;
use pumpkin_data::{
Block,
block_properties::{BlockProperties, EnumVariants, Integer0To3, NetherWartLikeProperties},
block_properties::{BlockProperties, NetherWartLikeProperties},
tag::{self, Taggable},
};
use pumpkin_macros::pumpkin_block;
@ -75,12 +75,12 @@ impl CropBlockBase for NetherWartBlock {
fn get_age(&self, state: u16, block: &Block) -> i32 {
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 {
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)
}

View File

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

View File

@ -1,7 +1,5 @@
use pumpkin_data::Block;
use pumpkin_data::block_properties::{
BlockProperties, EnumVariants, Integer0To1, TorchflowerCropLikeProperties,
};
use pumpkin_data::block_properties::{BlockProperties, TorchflowerCropLikeProperties};
use pumpkin_macros::pumpkin_block;
use pumpkin_world::BlockStateId;
use rand::RngExt;
@ -62,13 +60,13 @@ impl CropBlockBase for TorchFlowerBlock {
fn get_age(&self, state: u16, block: &Block) -> i32 {
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 {
if age == 1 {
let mut properties = TorchFlowerProperties::from_state_id(state, block);
properties.age = Integer0To1::L1;
properties.age = 1;
properties.to_state_id(block)
} else {
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);
//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;
}
} 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_util::math::position::BlockPos;
use pumpkin_world::BlockStateId;
@ -20,8 +20,8 @@ impl SaplingBlock {
async fn generate(&self, world: &Arc<World>, pos: &BlockPos) {
let (block, state) = world.get_block_and_state_id(pos).await;
let mut props = SaplingProperties::from_state_id(state, block);
if props.stage == Integer0To1::L0 {
props.stage = Integer0To1::L1;
if props.stage == 0 {
props.stage = 1;
world
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;

View File

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

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 crate::block::{BlockBehaviour, BlockFuture, CanUpdateAtArgs};
use crate::block::{BlockIsReplacing, OnPlaceArgs};
pub trait SegmentProperties {
fn get_segment_amount(&self) -> Integer1To4;
fn set_segment_amount(&mut self, amount: Integer1To4);
fn get_segment_amount(&self) -> u8;
fn set_segment_amount(&mut self, amount: u8);
fn get_facing(&self) -> HorizontalFacing;
fn set_facing(&mut self, facing: HorizontalFacing);
}
@ -14,11 +14,11 @@ pub trait SegmentProperties {
macro_rules! impl_segment_properties {
($type:ty, $amount_field:ident) => {
impl SegmentProperties for $type {
fn get_segment_amount(&self) -> Integer1To4 {
fn get_segment_amount(&self) -> u8 {
self.$amount_field
}
fn set_segment_amount(&mut self, amount: Integer1To4) {
fn set_segment_amount(&mut self, amount: u8) {
self.$amount_field = amount;
}
@ -46,28 +46,23 @@ pub trait Segmented: BlockBehaviour {
type Properties: BlockProperties + SegmentProperties;
fn can_add_segment(&self, props: &Self::Properties) -> bool {
let amount = props.get_segment_amount();
matches!(amount, Integer1To4::L1 | Integer1To4::L2 | Integer1To4::L3)
props.get_segment_amount() < 4
}
fn get_next_segment_amount(&self, current: Integer1To4) -> Integer1To4 {
match current {
Integer1To4::L1 => Integer1To4::L2,
Integer1To4::L2 => Integer1To4::L3,
Integer1To4::L3 | Integer1To4::L4 => Integer1To4::L4,
}
fn get_next_segment_amount(&self, current: u8) -> u8 {
(current + 1).min(4)
}
fn get_facing_for_segment(
&self,
player_facing: HorizontalFacing,
segment_amount: Integer1To4,
segment_amount: u8,
) -> HorizontalFacing {
let base_facing = match segment_amount {
Integer1To4::L1 => HorizontalFacing::South,
Integer1To4::L2 => HorizontalFacing::East,
Integer1To4::L3 => HorizontalFacing::North,
Integer1To4::L4 => HorizontalFacing::West,
1 => HorizontalFacing::South,
2 => HorizontalFacing::East,
3 => HorizontalFacing::North,
_ => HorizontalFacing::West,
};
match player_facing {
@ -102,8 +97,8 @@ pub trait Segmented: BlockBehaviour {
// Set first segment orientation based on player direction
let player_facing = args.player.living_entity.entity.get_horizontal_facing();
let mut props = Self::Properties::default(args.block);
props.set_segment_amount(Integer1To4::L1);
props.set_facing(self.get_facing_for_segment(player_facing, Integer1To4::L1));
props.set_segment_amount(1);
props.set_facing(self.get_facing_for_segment(player_facing, 1));
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::{
Block,
block_properties::{BlockProperties, CactusLikeProperties, EnumVariants, Integer0To15},
block_properties::{BlockProperties, CactusLikeProperties},
tag,
};
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 age = CactusLikeProperties::from_state_id(state_id, args.block).age;
if age == Integer0To15::L15 {
if age == 15 {
args.world
.set_block_state(&args.position.up(), state_id, BlockFlags::empty())
.await;
let props = CactusLikeProperties {
age: Integer0To15::L0,
};
let props = CactusLikeProperties { age: 0 };
args.world
.set_block_state(
args.position,
@ -58,9 +56,7 @@ impl BlockBehaviour for SugarCaneBlock {
)
.await;
} else {
let props = CactusLikeProperties {
age: Integer0To15::from_index(age.to_index() + 1),
};
let props = CactusLikeProperties { age: age + 1 };
args.world
.set_block_state(
args.position,

View File

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

View File

@ -1,9 +1,6 @@
use std::sync::Arc;
use pumpkin_data::{
Block,
block_properties::{BlockProperties, EnumVariants},
};
use pumpkin_data::{Block, block_properties::BlockProperties};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::entities::daylight_detector::DaylightDetectorBlockEntity;
@ -61,7 +58,7 @@ impl BlockBehaviour for DaylightDetectorBlock {
Box::pin(async move {
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::{
Block, BlockDirection, BlockState,
block_properties::{BlockProperties, EnumVariants, Integer0To15},
};
use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::BlockProperties};
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos};
use pumpkin_world::{BlockStateId, world::BlockFlags};
@ -98,7 +95,7 @@ impl BlockBehaviour for WeightedPressurePlateBlock {
impl PressurePlate for WeightedPressurePlateBlock {
fn get_redstone_output(&self, block: &Block, state: BlockStateId) -> u8 {
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 {
@ -129,7 +126,7 @@ impl PressurePlate for WeightedPressurePlateBlock {
output: u8,
) -> pumpkin_world::BlockStateId {
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)
}

View File

@ -1,9 +1,8 @@
use std::sync::Arc;
use pumpkin_data::block_properties::{
BlockProperties, EastRedstone, EnumVariants, Integer0To15, NorthRedstone,
ObserverLikeProperties, RedstoneWireLikeProperties, RepeaterLikeProperties, SouthRedstone,
WestRedstone,
BlockProperties, EastRedstone, NorthRedstone, ObserverLikeProperties,
RedstoneWireLikeProperties, RepeaterLikeProperties, SouthRedstone, WestRedstone,
};
use pumpkin_data::{Block, BlockDirection, BlockState, HorizontalFacingExt};
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> {
Box::pin(async move {
let mut wire = RedstoneWireProperties::default(args.block);
wire.power =
Integer0To15::from_index(calculate_power(args.world, args.position).await.into());
wire.power = calculate_power(args.world, args.position).await;
wire = get_regulated_sides(wire, args.world, args.position).await;
if is_dot(wire) {
wire = make_cross(wire.power);
@ -89,8 +87,7 @@ impl BlockBehaviour for RedstoneWireBlock {
}
wire = get_regulated_sides(wire, args.world, args.position).await;
wire.power =
Integer0To15::from_index(calculate_power(args.world, args.position).await.into());
wire.power = calculate_power(args.world, args.position).await;
if is_cross(old_state) && new_side.is_none() {
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 mut wire = RedstoneWireProperties::from_state_id(state.id, args.block);
let new_power = calculate_power(args.world, args.position).await;
if wire.power.to_index() as u8 != new_power {
wire.power = Integer0To15::from_index(new_power.into());
if wire.power != new_power {
wire.power = new_power;
args.world
.set_block_state(
args.position,
@ -206,7 +203,7 @@ impl BlockBehaviour for RedstoneWireBlock {
if args.direction == BlockDirection::Up
|| wire.is_side_connected(args.direction.opposite())
{
wire.power.to_index() as u8
wire.power
} else {
0
}
@ -222,7 +219,7 @@ impl BlockBehaviour for RedstoneWireBlock {
if args.direction == BlockDirection::Up
|| wire.is_side_connected(args.direction.opposite())
{
wire.power.to_index() as u8
wire.power
} else {
0
}
@ -273,7 +270,7 @@ async fn on_use(wire: RedstoneWireProperties, world: &Arc<World>, block_pos: &Bl
}
#[must_use]
pub const fn make_cross(power: Integer0To15) -> RedstoneWireProperties {
pub const fn make_cross(power: u8) -> RedstoneWireProperties {
RedstoneWireProperties {
north: NorthRedstone::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;
if block == &Block::REDSTONE_WIRE {
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 {
wire_power
}

View File

@ -2,7 +2,7 @@ use std::sync::Arc;
use pumpkin_data::{
Block, BlockDirection, BlockState, HorizontalFacingExt,
block_properties::{BlockProperties, EnumVariants, HorizontalFacing, Integer1To4},
block_properties::{BlockProperties, HorizontalFacing},
};
use pumpkin_macros::pumpkin_block;
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 {
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,
) {
let mut props = props;
props.delay = match props.delay {
Integer1To4::L1 => Integer1To4::L2,
Integer1To4::L2 => Integer1To4::L3,
Integer1To4::L3 => Integer1To4::L4,
Integer1To4::L4 => Integer1To4::L1,
};
props.delay = if props.delay == 4 { 1 } else { props.delay + 1 };
let state = props.to_state_id(block);
world
.set_block_state(&block_pos, state, BlockFlags::empty())

View File

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

View File

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

View File

@ -1,9 +1,5 @@
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::{
Block, BlockDirection,
block_properties::{Integer1To8, SnowLikeProperties},
item::Item,
};
use pumpkin_data::{Block, BlockDirection, block_properties::SnowLikeProperties, item::Item};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{
@ -27,7 +23,7 @@ impl BlockBehaviour for LayeredSnowBlock {
return Block::AIR.default_state.id;
}
let mut props = SnowLikeProperties::default(args.block);
props.layers = Integer1To8::L1;
props.layers = 1;
props.to_state_id(&Block::SNOW)
})
}
@ -58,25 +54,17 @@ impl BlockBehaviour for LayeredSnowBlock {
}
let mut props = SnowLikeProperties::from_state_id(state_id, &Block::SNOW);
props.layers = match props.layers {
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
.set_block_state(
pos,
Block::SNOW_BLOCK.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
return BlockActionResult::Success;
}
};
if props.layers >= 8 {
args.world
.set_block_state(
pos,
Block::SNOW_BLOCK.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
return BlockActionResult::Success;
}
props.layers += 1;
let state_id = props.to_state_id(&Block::SNOW);
args.world

View File

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

View File

@ -15,7 +15,7 @@ use living::LivingEntity;
use player::Player;
use pumpkin_data::BlockState;
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::dimension::Dimension;
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 index = (adjusted_yaw / 22.5).round() as u16 % 16;
Integer0To15::from_index(index)
((adjusted_yaw / 22.5).round() as u8) % 16
}
pub fn get_flipped_rotation_16(&self) -> Integer0To15 {
match self.get_rotation_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_flipped_rotation_16(&self) -> u8 {
(self.get_rotation_16() + 8) % 16
}
pub fn get_facing(&self) -> Facing {

View File

@ -29,7 +29,7 @@ use tracing::{debug, warn};
use uuid::Uuid;
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::data_component_impl::{AttributeModifiersImpl, Operation};
use pumpkin_data::data_component_impl::{EquipmentSlot, EquippableImpl, ToolImpl, WeaponImpl};
@ -1106,10 +1106,8 @@ impl Player {
// Handle respawn anchor (Nether)
if block == &Block::RESPAWN_ANCHOR {
use pumpkin_data::block_properties::Integer0To4;
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
if charges == 0 {
@ -1121,7 +1119,7 @@ impl Player {
// Decrement charges after successful respawn position found
let new_charges = charges - 1;
let mut new_props = anchor_props;
new_props.charges = Integer0To4::from_index(new_charges);
new_props.charges = new_charges;
world
.set_block_state(
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_util::Difficulty;
use pumpkin_util::text::TextComponent;
use pumpkin_world::lock::LevelLocker;
use pumpkin_world::lock::anvil::AnvilLevelLocker;
use pumpkin_world::world_info::anvil::{
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
pub level_info: Arc<ArcSwap<LevelData>>,
world_info_writer: Arc<dyn WorldInfoWriter>,
// Gets unlocked when dropped
// TODO: Make this a trait
_locker: Arc<Option<AnvilLevelLocker>>,
}
impl Server {
@ -167,16 +162,6 @@ impl Server {
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| {
warn!("Failed to get level_info, using default instead: {err}");
let default_data = LevelData::default(basic_config.seed);
@ -254,7 +239,6 @@ impl Server {
mojang_public_keys: ArcSwap::from_pointee(Vec::new()),
world_info_writer: Arc::new(AnvilLevelInfo),
level_info,
_locker: Arc::new(locker),
};
let server = Arc::new(server);