This commit is contained in:
Alexander Medvedev 2026-08-23 12:42:37 +02:00
parent 1abbc7bfec
commit edb848d608
14 changed files with 1236 additions and 899 deletions

@ -1 +1 @@
Subproject commit b44607b19fa796edb28562382a1f4f7f788a4504
Subproject commit 2337e6f389293a6371667d495feace8e35570318

View File

@ -546,6 +546,10 @@ impl<S: SingleChunkDataSerializer + 'static> ChunkSerializer for AnvilChunkFile<
fn read(r: Bytes) -> Result<Self, ChunkReadingError> {
let mut raw_file_bytes = r;
if raw_file_bytes.is_empty() {
return Ok(Self::default());
}
if raw_file_bytes.len() < SECTOR_BYTES * 2 {
return Err(ChunkReadingError::InvalidHeader);
}

View File

@ -94,15 +94,26 @@ fn extract_u16_array(tag: &pumpkin_nbt::tag::NbtTag) -> Option<Box<[BlockStateId
pumpkin_nbt::tag::NbtTag::List(list) => {
let ids: Box<[BlockStateId]> = list
.iter()
.map(|t| {
let val = match t {
pumpkin_nbt::tag::NbtTag::Int(x) => *x as u16,
pumpkin_nbt::tag::NbtTag::Short(x) => *x as u16,
pumpkin_nbt::tag::NbtTag::Byte(x) => *x as u16,
pumpkin_nbt::tag::NbtTag::Long(x) => *x as u16,
_ => 0,
};
BlockStateId::new_or_air(val)
.map(|t| match t {
pumpkin_nbt::tag::NbtTag::Int(x) => BlockStateId::new_or_air(*x as u16),
pumpkin_nbt::tag::NbtTag::Short(x) => BlockStateId::new_or_air(*x as u16),
pumpkin_nbt::tag::NbtTag::Byte(x) => BlockStateId::new_or_air(*x as u16),
pumpkin_nbt::tag::NbtTag::Long(x) => BlockStateId::new_or_air(*x as u16),
pumpkin_nbt::tag::NbtTag::Compound(compound) => {
if let Ok(entry) =
crate::generation::structure::template::PaletteEntry::from_nbt_compound(
compound,
)
&& let Some(state) =
crate::generation::structure::template::BlockStateResolver::resolve_simple(
&entry,
)
{
return state.id;
}
BlockStateId::AIR
}
_ => BlockStateId::AIR,
})
.collect();
Some(ids)
@ -122,6 +133,10 @@ fn extract_u8_array(tag: &pumpkin_nbt::tag::NbtTag) -> Option<Box<[u8]>> {
pumpkin_nbt::tag::NbtTag::Byte(x) => *x as u8,
pumpkin_nbt::tag::NbtTag::Int(x) => *x as u8,
pumpkin_nbt::tag::NbtTag::Short(x) => *x as u8,
pumpkin_nbt::tag::NbtTag::String(s) => {
let name = s.strip_prefix("minecraft:").unwrap_or(s);
pumpkin_data::biome::Biome::from_name(name).map_or(0, |b| b.id)
}
_ => 0,
})
.collect();
@ -490,7 +505,27 @@ impl ChunkData {
let palette_tags: Vec<NbtTag> = block_states_nbt
.palette
.iter()
.map(|id| NbtTag::Int(BlockStateId::as_u16(*id) as i32))
.map(|&id| {
let block = Block::from_state_id(id);
let mut comp = NbtCompound::new();
let name = if block.name.starts_with("minecraft:") {
block.name.to_string()
} else {
format!("minecraft:{}", block.name)
};
comp.put_string("Name", name);
if let Some(props) = block.properties(id) {
let prop_vec = props.to_props();
if !prop_vec.is_empty() {
let mut props_comp = NbtCompound::new();
for (k, v) in prop_vec {
props_comp.put_string(k, v.to_string());
}
comp.put_compound("Properties", props_comp);
}
}
NbtTag::Compound(comp)
})
.collect();
bs_comp.put_list("palette", palette_tags);
section_comp.put_compound("block_states", bs_comp);
@ -504,7 +539,16 @@ impl ChunkData {
let biome_palette_tags: Vec<NbtTag> = biomes_nbt
.palette
.iter()
.map(|&val| NbtTag::Byte(val as i8))
.map(|&val| {
let name = pumpkin_data::biome::Biome::from_id(val)
.map_or("plains", |b| b.registry_id);
let full_name = if name.starts_with("minecraft:") {
name.to_string()
} else {
format!("minecraft:{name}")
};
NbtTag::String(full_name.into())
})
.collect();
b_comp.put_list("palette", biome_palette_tags);
section_comp.put_compound("biomes", b_comp);
@ -849,3 +893,63 @@ impl Default for LightContainer {
Self::new_empty(15)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pumpkin_data::Block;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::tag::NbtTag;
#[test]
fn extract_u16_array_from_vanilla_compound_palette() {
let mut entry1 = NbtCompound::new();
entry1.put_string("Name", "minecraft:stone".to_string());
let mut entry2 = NbtCompound::new();
entry2.put_string("Name", "minecraft:repeater".to_string());
let mut props = NbtCompound::new();
props.put_string("facing", "north".to_string());
props.put_string("delay", "2".to_string());
props.put_string("locked", "false".to_string());
props.put_string("powered", "false".to_string());
entry2.put_compound("Properties", props);
let list_tag = NbtTag::List(vec![NbtTag::Compound(entry1), NbtTag::Compound(entry2)]);
let result = extract_u16_array(&list_tag).expect("should extract palette");
assert_eq!(result.len(), 2);
assert_eq!(result[0], Block::STONE.default_state.id);
let repeater_state = Block::REPEATER
.from_properties(&[
("facing", "north"),
("delay", "2"),
("locked", "false"),
("powered", "false"),
])
.to_state_id(&Block::REPEATER);
assert_eq!(result[1], repeater_state);
}
#[test]
fn extract_u8_array_from_vanilla_string_palette() {
let list_tag = NbtTag::List(vec![
NbtTag::String("minecraft:plains".to_string().into()),
NbtTag::String("minecraft:the_void".to_string().into()),
]);
let result = extract_u8_array(&list_tag).expect("should extract biome palette");
assert_eq!(result.len(), 2);
assert_eq!(
result[0],
pumpkin_data::biome::Biome::from_name("plains").unwrap().id
);
assert_eq!(
result[1],
pumpkin_data::biome::Biome::from_name("the_void")
.unwrap()
.id
);
}
}

View File

@ -101,6 +101,13 @@ impl<S: ChunkSerializer<WriteBackend = PathBuf> + 'static> ChunkSerializerLazyLo
match tokio::fs::read(&self.path).await {
Ok(bytes) => {
if bytes.is_empty() {
trace!(
"File is empty (0 bytes), using default for: {}",
self.path.display()
);
return Ok(S::default());
}
let value = tokio::task::spawn_blocking(move || S::read(bytes.into()))
.await
.map_err(|e| ChunkReadingError::IoError(std::io::Error::other(e)))??;

View File

@ -444,9 +444,12 @@ impl Cache {
.get_proto_chunk_mut()
.set_structure_starts(noise_gen);
}
generator::WorldGenerator::Flat(_) => {}
generator::WorldGenerator::Flat(_) => {
self.chunks[mid].get_proto_chunk_mut().stage = StagedChunkEnum::StructureStart;
}
generator::WorldGenerator::Custom(custom_gen) => {
custom_gen.set_structure_starts(self.chunks[mid].get_proto_chunk_mut());
self.chunks[mid].get_proto_chunk_mut().stage = StagedChunkEnum::StructureStart;
}
},
StagedChunkEnum::StructureReferences => match generator {
@ -455,9 +458,14 @@ impl Cache {
.get_proto_chunk_mut()
.set_structure_references(noise_gen);
}
generator::WorldGenerator::Flat(_) => {}
generator::WorldGenerator::Flat(_) => {
self.chunks[mid].get_proto_chunk_mut().stage =
StagedChunkEnum::StructureReferences;
}
generator::WorldGenerator::Custom(custom_gen) => {
custom_gen.set_structure_references(self.chunks[mid].get_proto_chunk_mut());
self.chunks[mid].get_proto_chunk_mut().stage =
StagedChunkEnum::StructureReferences;
}
},
StagedChunkEnum::Biomes => match generator {

View File

@ -268,9 +268,10 @@ impl GenerationSchedule {
let mut ready = Vec::new();
for (key, node) in &mut graph.nodes {
node.in_queue = false;
if node.stage == StagedChunkEnum::None
|| node.in_degree != 0
|| node.in_queue
|| node.in_flight
|| waiting_for_chunks.contains(&key)
{
continue;
@ -1242,8 +1243,8 @@ impl GenerationSchedule {
}
if let Some(node) = self.graph.nodes.get_mut(task.1) {
if node.in_degree != 0 {
node.in_queue = false;
if node.in_degree != 0 {
continue;
}
node.in_flight = true;
@ -1492,7 +1493,9 @@ impl GenerationSchedule {
&self.waiting_for_chunks,
);
if restored > 0 {
warn!("Restored {restored} stranded ready chunk tasks to generation queue");
debug!(
"Restored {restored} stranded ready chunk tasks to generation queue"
);
continue;
}
debug_assert!(self.debug_check());

View File

@ -9,15 +9,9 @@ use crate::level::Level;
pub fn into_level(
dimension: Dimension,
level_config: &LevelConfig,
mut base_directory: PathBuf,
base_directory: PathBuf,
seed: i64,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) -> Arc<Level> {
if dimension.minecraft_name == Dimension::OVERWORLD.minecraft_name {
} else if dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name {
base_directory.push("DIM-1");
} else if dimension.minecraft_name == Dimension::THE_END.minecraft_name {
base_directory.push("DIM1");
}
Level::from_root_folder(level_config, base_directory, seed, dimension, gen_pool)
}

View File

@ -148,17 +148,31 @@ impl Level {
dimension: Dimension,
gen_pool: Option<Arc<rayon::ThreadPool>>,
) -> Arc<Self> {
let dim_folder = if dimension.minecraft_name == Dimension::OVERWORLD.minecraft_name
|| dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name
|| dimension.minecraft_name == Dimension::THE_END.minecraft_name
{
root_folder.clone()
} else {
let (namespace, name) = match dimension.minecraft_name.split_once(':') {
Some((ns, n)) => (ns, n),
None => ("minecraft", dimension.minecraft_name),
};
root_folder.join("dimensions").join(namespace).join(name)
// 26.2 canonical layout: root_folder/dimensions/<namespace>/<name>
let canonical_dim_folder = root_folder.join("dimensions").join(namespace).join(name);
// Check if canonical 26.2 folder exists, or fall back to pre-26.2 legacy folders
let dim_folder = if canonical_dim_folder.exists() {
canonical_dim_folder
} else if dimension.minecraft_name == Dimension::OVERWORLD.minecraft_name
&& root_folder.join("region").exists()
{
root_folder.clone()
} else if dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name
&& root_folder.join("DIM-1").join("region").exists()
{
root_folder.join("DIM-1")
} else if dimension.minecraft_name == Dimension::THE_END.minecraft_name
&& root_folder.join("DIM1").join("region").exists()
{
root_folder.join("DIM1")
} else {
canonical_dim_folder
};
let region_folder = dim_folder.join("region");
@ -177,21 +191,13 @@ impl Level {
poi_folder,
});
let main_folder = if dimension.minecraft_name == Dimension::OVERWORLD.minecraft_name {
level_folder.root_folder.clone()
} else {
level_folder
.root_folder
.parent()
.unwrap_or(&level_folder.root_folder)
.to_path_buf()
};
let main_folder = &level_folder.root_folder;
let mut is_flat = false;
let mut flat_layers = Vec::new();
let mut flat_biome = "minecraft:plains".to_string();
if let Some(wgs) = crate::world_info::data_files::read_world_gen_settings(&main_folder)
if let Some(wgs) = crate::world_info::data_files::read_world_gen_settings(main_folder)
&& let Some(dim_settings) = wgs.dimensions.get(dimension.minecraft_name)
&& dim_settings.generator.generator_type == "minecraft:flat"
{
@ -1005,3 +1011,67 @@ impl Level {
.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pumpkin_config::world::LevelConfig;
use tempfile::TempDir;
#[tokio::test]
async fn dimension_paths_26_2() {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path().to_path_buf();
let config = LevelConfig::default();
let overworld_level =
Level::from_root_folder(&config, root.clone(), 0, Dimension::OVERWORLD, None);
assert_eq!(
overworld_level.level_folder.dim_folder,
root.join("dimensions").join("minecraft").join("overworld")
);
assert_eq!(
overworld_level.level_folder.region_folder,
root.join("dimensions")
.join("minecraft")
.join("overworld")
.join("region")
);
let nether_level =
Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER, None);
assert_eq!(
nether_level.level_folder.dim_folder,
root.join("dimensions").join("minecraft").join("the_nether")
);
let end_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_END, None);
assert_eq!(
end_level.level_folder.dim_folder,
root.join("dimensions").join("minecraft").join("the_end")
);
}
#[tokio::test]
async fn legacy_dimension_fallback() {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path().to_path_buf();
let config = LevelConfig::default();
// Create legacy directories
std::fs::create_dir_all(root.join("region")).unwrap();
std::fs::create_dir_all(root.join("DIM-1").join("region")).unwrap();
std::fs::create_dir_all(root.join("DIM1").join("region")).unwrap();
let overworld_level =
Level::from_root_folder(&config, root.clone(), 0, Dimension::OVERWORLD, None);
assert_eq!(overworld_level.level_folder.dim_folder, root);
let nether_level =
Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_NETHER, None);
assert_eq!(nether_level.level_folder.dim_folder, root.join("DIM-1"));
let end_level = Level::from_root_folder(&config, root.clone(), 0, Dimension::THE_END, None);
assert_eq!(end_level.level_folder.dim_folder, root.join("DIM1"));
}
}

View File

@ -20,7 +20,8 @@ use crate::world_info::{
data_files::{
minecraft_data_dir, read_game_rules, read_wandering_trader, read_weather,
read_world_clocks, read_world_gen_settings, write_custom_boss_events_stub,
write_game_rules, write_scheduled_events_stub, write_wandering_trader, write_weather,
write_game_rules, write_random_sequences_stub, write_scheduled_events_stub,
write_scoreboard_stub, write_stopwatches_stub, write_wandering_trader, write_weather,
write_world_clocks, write_world_gen_settings,
},
default_data_packs,
@ -189,6 +190,24 @@ fn update_world_border_from_nbt(level_data: &mut LevelData, data: &NbtCompound)
}
fn update_spawn_from_nbt(level_data: &mut LevelData, data: &NbtCompound) {
if let Some(spawn_comp) = data.get_compound("spawn") {
if let Some(pos) = spawn_comp.get_int_array("pos")
&& pos.len() >= 3
{
level_data.spawn_x = pos[0];
level_data.spawn_y = pos[1];
level_data.spawn_z = pos[2];
}
if let Some(spawn_yaw) = spawn_comp
.get_float("yaw")
.or_else(|| spawn_comp.get_float("SpawnAngle"))
{
level_data.spawn_yaw = spawn_yaw;
}
if let Some(spawn_pitch) = spawn_comp.get_float("pitch") {
level_data.spawn_pitch = spawn_pitch;
}
}
if let Some(spawn_x) = data.get_int("SpawnX") {
level_data.spawn_x = spawn_x;
}
@ -224,12 +243,27 @@ fn level_data_from_nbt(data: &NbtCompound, seed: i64) -> LevelData {
if let Some(data_version) = data.get_int("DataVersion") {
level_data.data_version = data_version;
}
if let Some(diff_comp) = data.get_compound("difficulty_settings") {
if let Some(diff_str) = diff_comp.get_string("difficulty") {
match diff_str {
"peaceful" => level_data.difficulty = Difficulty::Peaceful,
"easy" => level_data.difficulty = Difficulty::Easy,
"normal" => level_data.difficulty = Difficulty::Normal,
"hard" => level_data.difficulty = Difficulty::Hard,
_ => {}
}
}
if let Some(difficulty_locked) = diff_comp.get_bool("locked") {
level_data.difficulty_locked = difficulty_locked;
}
} else {
if let Some(difficulty) = data.get_byte("Difficulty").and_then(difficulty_from_id) {
level_data.difficulty = difficulty;
}
if let Some(difficulty_locked) = data.get_bool("DifficultyLocked") {
level_data.difficulty_locked = difficulty_locked;
}
}
if let Some(last_played) = data.get_long("LastPlayed") {
level_data.last_played = last_played;
}
@ -268,10 +302,36 @@ fn level_data_to_nbt(info: &LevelData, data: &mut NbtCompound) {
data.put_double("BorderWarningTime", info.border_warning_time);
data.put_compound("DataPacks", data_packs_to_nbt(&info.data_packs));
data.put_int("DataVersion", info.data_version);
// 26.2 difficulty_settings
let mut diff_comp = NbtCompound::new();
let diff_name = match info.difficulty {
Difficulty::Peaceful => "peaceful",
Difficulty::Easy => "easy",
Difficulty::Normal => "normal",
Difficulty::Hard => "hard",
};
diff_comp.put_string("difficulty", diff_name.to_string());
diff_comp.put_bool("hardcore", false);
diff_comp.put_bool("locked", info.difficulty_locked);
data.put_compound("difficulty_settings", diff_comp);
data.put_byte("Difficulty", info.difficulty as i8);
data.put_bool("DifficultyLocked", info.difficulty_locked);
data.put_long("LastPlayed", info.last_played);
data.put_string("LevelName", info.level_name.clone());
// 26.2 spawn
let mut spawn_comp = NbtCompound::new();
spawn_comp.put_string("dimension", "minecraft:overworld".to_string());
spawn_comp.put(
"pos",
NbtTag::IntArray(vec![info.spawn_x, info.spawn_y, info.spawn_z]),
);
spawn_comp.put_float("pitch", info.spawn_pitch);
spawn_comp.put_float("yaw", info.spawn_yaw);
data.put_compound("spawn", spawn_comp);
data.put_int("SpawnX", info.spawn_x);
data.put_int("SpawnY", info.spawn_y);
data.put_int("SpawnZ", info.spawn_z);
@ -318,6 +378,10 @@ impl WorldInfoReader for AnvilLevelInfo {
let mut level_data = level_data_from_nbt(data, seed);
if let Some(wgs) = read_world_gen_settings(level_folder) {
level_data.world_gen_settings = wgs;
}
// game_rules.dat – prefer the new file; fall back to level.dat values
if minecraft_data_dir(level_folder)
.join("game_rules.dat")
@ -363,6 +427,8 @@ impl WorldInfoWriter for AnvilLevelInfo {
// ── Write level.dat ───────────────────────────────────────────────────
let path = level_folder.join(LEVEL_DAT_FILE_NAME);
let path_new = level_folder.join("level.dat_new");
let path_old = level_folder.join(LEVEL_DAT_BACKUP_FILE_NAME);
let mut root = existing_level_dat_root(&path)?;
let mut data_comp = root
@ -372,9 +438,14 @@ impl WorldInfoWriter for AnvilLevelInfo {
level_data_to_nbt(&level_data, &mut data_comp);
root.put_compound(LEVEL_DATA_TAG, data_comp);
write_gzip_compound_tag(root, File::create(path)?)
write_gzip_compound_tag(root, File::create(&path_new)?)
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))?;
if path.exists() {
let _ = std::fs::copy(&path, &path_old);
}
let _ = std::fs::rename(&path_new, &path);
let data_version = level_data.data_version;
// ── Write data/minecraft/*.dat files ─────────────────────────────────
@ -431,6 +502,21 @@ impl WorldInfoWriter for AnvilLevelInfo {
error!("Failed to write scheduled_events.dat: {e}");
}
// random_sequences.dat
if let Err(e) = write_random_sequences_stub(level_folder, data_version) {
error!("Failed to write random_sequences.dat: {e}");
}
// scoreboard.dat
if let Err(e) = write_scoreboard_stub(level_folder, data_version) {
error!("Failed to write scoreboard.dat: {e}");
}
// stopwatches.dat
if let Err(e) = write_stopwatches_stub(level_folder, data_version) {
error!("Failed to write stopwatches.dat: {e}");
}
Ok(())
}
}
@ -465,7 +551,7 @@ mod test {
DataPacks, LevelData, MAXIMUM_SUPPORTED_LEVEL_VERSION,
MAXIMUM_SUPPORTED_WORLD_DATA_VERSION, MINIMUM_SUPPORTED_LEVEL_VERSION,
MINIMUM_SUPPORTED_WORLD_DATA_VERSION, WorldGenSettings, WorldInfoError, WorldVersion,
data_files::minecraft_data_dir,
data_files::{minecraft_data_dir, read_world_gen_settings, write_world_gen_settings},
},
};
@ -813,4 +899,139 @@ mod test {
let bytes = pumpkin_nbt::Nbt::from(root).write();
assert!(!bytes.is_empty());
}
#[test]
fn spawn_and_difficulty_format_26_2() {
let temp_dir = TempDir::new().unwrap();
let mut data_comp = NbtCompound::new();
data_comp.put_int("DataVersion", 4903);
data_comp.put_int("version", 19133);
data_comp.put_string("LevelName", "26.2 Test".to_string());
data_comp.put_long("LastPlayed", 123456789);
data_comp.put_long("DayTime", 1000);
// 26.2 difficulty_settings compound
let mut diff_comp = NbtCompound::new();
diff_comp.put_string("difficulty", "hard".to_string());
diff_comp.put_bool("locked", true);
diff_comp.put_bool("hardcore", false);
data_comp.put_compound("difficulty_settings", diff_comp);
// 26.2 spawn compound
let mut spawn_comp = NbtCompound::new();
spawn_comp.put_string("dimension", "minecraft:overworld".to_string());
spawn_comp.put("pos", NbtTag::IntArray(vec![42, -60, 99]));
spawn_comp.put_float("pitch", 15.0);
spawn_comp.put_float("yaw", 180.0);
data_comp.put_compound("spawn", spawn_comp);
let mut root = NbtCompound::new();
root.put_compound("Data", data_comp);
write_level_dat(temp_dir.path(), root);
// Write world_gen_settings.dat
let mut wgs = WorldGenSettings::new(Seed(777));
wgs.dimensions.insert(
"minecraft:overworld".to_string(),
crate::world_info::Dimension {
generator: crate::world_info::Generator {
settings: Some(crate::world_info::GeneratorSettings::Compound(
serde_json::json!({
"biome": "minecraft:the_void",
"layers": [
{ "block": "minecraft:air", "height": 1 }
]
}),
)),
biome_source: None,
generator_type: "minecraft:flat".to_string(),
},
dimension_type: "minecraft:overworld".to_string(),
},
);
write_world_gen_settings(temp_dir.path(), &wgs, 4903).unwrap();
let loaded = AnvilLevelInfo.read_world_info(temp_dir.path()).unwrap();
assert_eq!(loaded.spawn_x, 42);
assert_eq!(loaded.spawn_y, -60);
assert_eq!(loaded.spawn_z, 99);
assert_eq!(loaded.spawn_pitch, 15.0);
assert_eq!(loaded.spawn_yaw, 180.0);
assert_eq!(loaded.difficulty, Difficulty::Hard);
assert!(loaded.difficulty_locked);
let read_wgs = read_world_gen_settings(temp_dir.path()).unwrap();
assert_eq!(read_wgs.seed, 777);
let overworld_dim = read_wgs.dimensions.get("minecraft:overworld").unwrap();
assert_eq!(overworld_dim.generator.generator_type, "minecraft:flat");
if let Some(crate::world_info::GeneratorSettings::Compound(val)) =
&overworld_dim.generator.settings
{
assert_eq!(val["biome"], "minecraft:the_void");
} else {
panic!("Expected Compound generator settings");
}
}
#[test]
fn all_26_2_minecraft_data_files_written_and_read() {
let temp_dir = TempDir::new().unwrap();
let mut level_data = LEVEL_DAT.data.clone();
level_data.data_version = 4903;
level_data.day_time = 24000;
level_data.clear_weather_time = 100;
level_data.world_gen_settings = WorldGenSettings::new(Seed(9999));
AnvilLevelInfo
.write_world_info(&level_data, temp_dir.path())
.unwrap();
let data_dir = temp_dir.path().join("data").join("minecraft");
let expected_files = [
"game_rules.dat",
"random_sequences.dat",
"scoreboard.dat",
"stopwatches.dat",
"wandering_trader.dat",
"world_clocks.dat",
"world_gen_settings.dat",
"scheduled_events.dat",
"custom_boss_events.dat",
"weather.dat",
];
for file_name in &expected_files {
let file_path = data_dir.join(file_name);
assert!(
file_path.exists(),
"Expected file {file_name} to exist in data/minecraft/"
);
}
// Verify world_gen_settings.dat read
let loaded_wgs =
crate::world_info::data_files::read_world_gen_settings(temp_dir.path()).unwrap();
assert_eq!(loaded_wgs.seed, 9999);
// Verify weather.dat read
let loaded_weather = crate::world_info::data_files::read_weather(temp_dir.path());
assert_eq!(loaded_weather.clear_weather_time, 100);
// Verify world_clocks.dat read
let loaded_clocks = crate::world_info::data_files::read_world_clocks(temp_dir.path());
assert_eq!(
loaded_clocks
.clocks
.get("minecraft:overworld")
.unwrap()
.total_ticks,
24000
);
// Verify wandering_trader.dat read
let loaded_wt = crate::world_info::data_files::read_wandering_trader(temp_dir.path());
assert_eq!(loaded_wt.spawn_delay, 24000);
assert_eq!(loaded_wt.spawn_chance, 25);
}
}

View File

@ -166,11 +166,72 @@ pub fn write_weather(level_folder: &Path, data: &WeatherData) -> Result<(), Worl
data_comp.put_bool("raining", data.raining);
data_comp.put_bool("thundering", data.thundering);
let mut root = NbtCompound::new();
root.put_int("DataVersion", data.data_version);
root.put_compound("data", data_comp);
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, BufWriter::new(file))
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))
}
#[must_use]
pub fn json_to_nbt_tag(val: &serde_json::Value) -> NbtTag {
match val {
serde_json::Value::Null => NbtTag::End,
serde_json::Value::Bool(b) => NbtTag::Byte(i8::from(*b)),
serde_json::Value::Number(n) => n.as_i64().map_or_else(
|| n.as_f64().map_or(NbtTag::End, NbtTag::Double),
|i| i32::try_from(i).map_or(NbtTag::Long(i), NbtTag::Int),
),
serde_json::Value::String(s) => NbtTag::String(s.clone().into()),
serde_json::Value::Array(arr) => NbtTag::List(arr.iter().map(json_to_nbt_tag).collect()),
serde_json::Value::Object(map) => {
let mut compound = NbtCompound::new();
for (k, v) in map {
compound.put(k, json_to_nbt_tag(v));
}
NbtTag::Compound(compound)
}
}
}
#[must_use]
pub fn nbt_tag_to_json(tag: &NbtTag) -> serde_json::Value {
match tag {
NbtTag::Byte(b) => serde_json::Value::Number((*b).into()),
NbtTag::Short(s) => serde_json::Value::Number((*s).into()),
NbtTag::Int(i) => serde_json::Value::Number((*i).into()),
NbtTag::Long(l) => serde_json::Value::Number((*l).into()),
NbtTag::Float(f) => serde_json::Number::from_f64(*f as f64)
.map_or(serde_json::Value::Null, serde_json::Value::Number),
NbtTag::Double(d) => serde_json::Number::from_f64(*d)
.map_or(serde_json::Value::Null, serde_json::Value::Number),
NbtTag::String(s) => serde_json::Value::String(s.to_string()),
NbtTag::List(list) => serde_json::Value::Array(list.iter().map(nbt_tag_to_json).collect()),
NbtTag::Compound(comp) => {
let mut map = serde_json::Map::new();
for (k, v) in &comp.child_tags {
map.insert(k.to_string(), nbt_tag_to_json(v));
}
serde_json::Value::Object(map)
}
NbtTag::ByteArray(arr) => serde_json::Value::Array(
arr.iter()
.map(|b| serde_json::Value::Number((*b).into()))
.collect(),
),
NbtTag::IntArray(arr) => serde_json::Value::Array(
arr.iter()
.map(|i| serde_json::Value::Number((*i).into()))
.collect(),
),
NbtTag::LongArray(arr) => serde_json::Value::Array(
arr.iter()
.map(|l| serde_json::Value::Number((*l).into()))
.collect(),
),
NbtTag::End => serde_json::Value::Null,
}
}
pub fn read_world_gen_settings(level_folder: &Path) -> Option<WorldGenSettings> {
let path = minecraft_data_dir(level_folder).join("world_gen_settings.dat");
if !path.exists() {
@ -179,16 +240,71 @@ pub fn read_world_gen_settings(level_folder: &Path) -> Option<WorldGenSettings>
match File::open(&path) {
Ok(f) => match read_gzip_compound_tag(f) {
Ok(compound) => {
let seed = compound
.get_compound("data")
.and_then(|c| c.get_long("seed"));
let data_compound = compound.get_compound("data");
let c = data_compound.as_ref().map_or(&compound, |v| v);
let seed = c.get_long("seed");
if seed.is_none() {
warn!("world_gen_settings.dat has no seed");
}
seed.map(|seed| WorldGenSettings {
seed,
dimensions: std::collections::HashMap::new(),
let seed = seed?;
let mut dimensions = std::collections::HashMap::new();
if let Some(dims_comp) = c.get_compound("dimensions") {
for (dim_name, dim_tag) in &dims_comp.child_tags {
if let NbtTag::Compound(dim_c) = dim_tag {
let dim_type = dim_c.get_string("type").unwrap_or(dim_name).to_string();
if let Some(gen_c) = dim_c.get_compound("generator") {
let generator_type = gen_c
.get_string("type")
.unwrap_or("minecraft:noise")
.to_string();
let settings = gen_c
.get_string("settings")
.map(|s| {
crate::world_info::GeneratorSettings::Reference(
s.to_string(),
)
})
.or_else(|| {
gen_c.get_compound("settings").map(|settings_c| {
let json_val = nbt_tag_to_json(&NbtTag::Compound(
settings_c.clone(),
));
crate::world_info::GeneratorSettings::Compound(json_val)
})
});
let biome_source = gen_c.get_compound("biome_source").map(|bs_c| {
let biome_type = bs_c
.get_string("type")
.unwrap_or("minecraft:multi_noise")
.to_string();
match bs_c.get_string("preset") {
Some(preset) => {
crate::world_info::BiomeSource::WithPreset {
preset: preset.to_string(),
biome_type,
}
}
None => {
crate::world_info::BiomeSource::Simple { biome_type }
}
}
});
dimensions.insert(
dim_name.to_string(),
crate::world_info::Dimension {
generator: crate::world_info::Generator {
settings,
biome_source,
generator_type,
},
dimension_type: dim_type,
},
);
}
}
}
}
Some(WorldGenSettings { seed, dimensions })
}
Err(e) => {
warn!("Failed to deserialize world_gen_settings.dat: {e}");
@ -213,6 +329,43 @@ pub fn write_world_gen_settings(
let mut inner = NbtCompound::new();
inner.put_int("DataVersion", data_version);
inner.put_long("seed", settings.seed);
inner.put_bool("generate_structures", true);
inner.put_bool("bonus_chest", false);
let mut dims_comp = NbtCompound::new();
for (dim_name, dim) in &settings.dimensions {
let mut dim_comp = NbtCompound::new();
dim_comp.put_string("type", dim.dimension_type.clone());
let mut gen_comp = NbtCompound::new();
gen_comp.put_string("type", dim.generator.generator_type.clone());
if let Some(s) = &dim.generator.settings {
match s {
crate::world_info::GeneratorSettings::Reference(r) => {
gen_comp.put_string("settings", r.clone());
}
crate::world_info::GeneratorSettings::Compound(json_val) => {
gen_comp.put("settings", json_to_nbt_tag(json_val));
}
}
}
if let Some(bs) = &dim.generator.biome_source {
let mut bs_comp = NbtCompound::new();
match bs {
crate::world_info::BiomeSource::WithPreset { preset, biome_type } => {
bs_comp.put_string("preset", preset.clone());
bs_comp.put_string("type", biome_type.clone());
}
crate::world_info::BiomeSource::Simple { biome_type } => {
bs_comp.put_string("type", biome_type.clone());
}
}
gen_comp.put_compound("biome_source", bs_comp);
}
dim_comp.put_compound("generator", gen_comp);
dims_comp.put_compound(dim_name, dim_comp);
}
inner.put_compound("dimensions", dims_comp);
let mut root = NbtCompound::new();
root.put_compound("data", inner);
@ -379,10 +532,20 @@ pub fn read_wandering_trader(level_folder: &Path) -> WanderingTraderData {
Ok(compound) => {
let data_compound = compound.get_compound("data");
let c = data_compound.as_ref().map_or(&compound, |v| v);
let data_version = compound
.get_int("DataVersion")
.or_else(|| c.get_int("DataVersion"))
.unwrap_or(0);
WanderingTraderData {
spawn_delay: c.get_int("WanderingTraderSpawnDelay").unwrap_or(24_000),
spawn_chance: c.get_int("WanderingTraderSpawnChance").unwrap_or(25),
data_version: c.get_int("DataVersion").unwrap_or(0),
spawn_delay: c
.get_int("spawn_delay")
.or_else(|| c.get_int("WanderingTraderSpawnDelay"))
.unwrap_or(24_000),
spawn_chance: c
.get_int("spawn_chance")
.or_else(|| c.get_int("WanderingTraderSpawnChance"))
.unwrap_or(25),
data_version,
}
}
Err(e) => {
@ -391,7 +554,7 @@ pub fn read_wandering_trader(level_folder: &Path) -> WanderingTraderData {
}
},
Err(e) => {
warn!("Failed to open wandering_trader.dat: {e}");
warn!("Failed to open wandering_trader.dat, using defaults: {e}");
WanderingTraderData::default()
}
}
@ -405,9 +568,10 @@ pub fn write_wandering_trader(
let path = dir.join("wandering_trader.dat");
let file = File::create(&path)?;
let mut data_comp = NbtCompound::new();
data_comp.put_int("WanderingTraderSpawnDelay", data.spawn_delay);
data_comp.put_int("WanderingTraderSpawnChance", data.spawn_chance);
data_comp.put_int("spawn_delay", data.spawn_delay);
data_comp.put_int("spawn_chance", data.spawn_chance);
let mut root = NbtCompound::new();
root.put_int("DataVersion", data.data_version);
root.put_compound("data", data_comp);
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, BufWriter::new(file))
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))
@ -419,18 +583,15 @@ pub fn write_custom_boss_events_stub(
) -> Result<(), WorldInfoError> {
let dir = ensure_minecraft_data_dir(level_folder)?;
let path = dir.join("custom_boss_events.dat");
// Only create if absent; actual boss-bar persistence lives elsewhere.
if path.exists() {
return Ok(());
}
let mut inner = NbtCompound::new();
inner.put_int("DataVersion", data_version);
let mut root = NbtCompound::new();
root.put_compound("data", inner);
root.put_int("DataVersion", data_version);
root.put_compound("data", NbtCompound::new());
let file = File::create(&path)?;
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, file)
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))
}
@ -447,12 +608,70 @@ pub fn write_scheduled_events_stub(
let mut inner = NbtCompound::new();
inner.put("events", NbtTag::List(vec![]));
inner.put_int("DataVersion", data_version);
let mut root = NbtCompound::new();
root.put_int("DataVersion", data_version);
root.put_compound("data", inner);
let file = File::create(&path)?;
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, file)
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))
}
pub fn write_random_sequences_stub(
level_folder: &Path,
data_version: i32,
) -> Result<(), WorldInfoError> {
let dir = ensure_minecraft_data_dir(level_folder)?;
let path = dir.join("random_sequences.dat");
if path.exists() {
return Ok(());
}
let mut inner = NbtCompound::new();
inner.put_int("salt", 0);
inner.put_compound("sequences", NbtCompound::new());
let mut root = NbtCompound::new();
root.put_int("DataVersion", data_version);
root.put_compound("data", inner);
let file = File::create(&path)?;
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, file)
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))
}
pub fn write_scoreboard_stub(level_folder: &Path, data_version: i32) -> Result<(), WorldInfoError> {
let dir = ensure_minecraft_data_dir(level_folder)?;
let path = dir.join("scoreboard.dat");
if path.exists() {
return Ok(());
}
let mut root = NbtCompound::new();
root.put_int("DataVersion", data_version);
root.put_compound("data", NbtCompound::new());
let file = File::create(&path)?;
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, file)
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))
}
pub fn write_stopwatches_stub(
level_folder: &Path,
data_version: i32,
) -> Result<(), WorldInfoError> {
let dir = ensure_minecraft_data_dir(level_folder)?;
let path = dir.join("stopwatches.dat");
if path.exists() {
return Ok(());
}
let mut inner = NbtCompound::new();
inner.put_compound("stopwatches", NbtCompound::new());
let mut root = NbtCompound::new();
root.put_int("DataVersion", data_version);
root.put_compound("data", inner);
let file = File::create(&path)?;
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, file)
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))
}

View File

@ -1,5 +1,3 @@
use std::sync::Arc;
/**
* This implementation is heavily based on <https://github.com/MCHPR/MCHPRS>
* Updated to fit pumpkin by 4lve
@ -31,32 +29,11 @@ pub mod sculk_sensor;
pub mod target_block;
pub mod tripwire;
pub mod tripwire_hook;
pub mod turbo;
// abstract
pub mod abstract_redstone_gate;
pub mod dispenser;
pub async fn update_wire_neighbors(world: &Arc<World>, pos: &BlockPos) {
for direction in BlockDirection::all() {
let neighbor_pos = pos.offset(direction.to_offset());
let block = world.get_block(&neighbor_pos);
world
.block_registry
.on_neighbor_update(world, block, &neighbor_pos, block, true)
.await;
for n_direction in BlockDirection::all() {
let n_neighbor_pos = neighbor_pos.offset(n_direction.to_offset());
let block = world.get_block(&n_neighbor_pos);
world
.block_registry
.on_neighbor_update(world, block, &n_neighbor_pos, block, true)
.await;
}
}
}
pub async fn is_emitting_redstone_power(
block: &Block,
state: &BlockState,

View File

@ -5,6 +5,8 @@ use pumpkin_data::block_properties::{
BlockProperties, EastRedstone, HorizontalFacing, NorthRedstone, ObserverLikeProperties,
RedstoneWireLikeProperties, RepeaterLikeProperties, SouthRedstone, WestRedstone,
};
use pumpkin_data::block_rotation::{Mirror, Rotation};
use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, BlockDirection, BlockState, HorizontalFacingExt};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
@ -12,16 +14,13 @@ use pumpkin_world::world::{BlockAccessor, BlockFlags};
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockFuture, BrokenArgs, CanPlaceAtArgs, GetRedstonePowerArgs, GetStateForNeighborUpdateArgs,
OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs, PrepareArgs,
};
use crate::{
block::{BlockBehaviour, NormalUseArgs},
world::World,
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetRedstonePowerArgs,
GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs,
PrepareArgs,
};
use crate::world::World;
use super::turbo::RedstoneWireTurbo;
use super::{get_redstone_power_no_dust, update_wire_neighbors};
use super::get_redstone_power_no_dust;
type RedstoneWireProperties = RedstoneWireLikeProperties;
@ -30,19 +29,38 @@ pub struct RedstoneWireBlock;
impl BlockBehaviour for RedstoneWireBlock {
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
can_place_at(args.block_accessor, args.position)
can_survive(args.block_accessor, args.position)
}
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 = 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);
let initial_state = make_cross(0);
let wire = get_connection_state(args.world, initial_state, args.position).await;
wire.to_state_id(args.block)
})
}
wire.to_state_id(args.block)
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
update_power_strength(args.world, args.position).await;
for direction in [BlockDirection::Up, BlockDirection::Down] {
let neighbor_pos = args.position.offset(direction.to_offset());
update_neighbors_at(args.world, &neighbor_pos, &Block::REDSTONE_WIRE).await;
}
update_neighbors_of_neighboring_wires(args.world, args.position).await;
})
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
for direction in BlockDirection::all() {
let neighbor_pos = args.position.offset(direction.to_offset());
update_neighbors_at(args.world, &neighbor_pos, &Block::REDSTONE_WIRE).await;
}
update_neighbors_of_neighboring_wires(args.world, args.position).await;
})
}
@ -51,82 +69,74 @@ impl BlockBehaviour for RedstoneWireBlock {
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut wire = RedstoneWireProperties::from_state_id(args.state_id, args.block);
let old_state = wire;
let new_side: WireConnection = match args.direction {
BlockDirection::Up => {
if args.direction == BlockDirection::Down {
let (below_block, below_state) =
args.world.get_block_and_state(args.neighbor_position);
if !can_survive_on(below_block, below_state) {
return Block::AIR.default_state.id;
}
return args.state_id;
}
BlockDirection::Down => {
return get_regulated_sides(wire, args.world, args.position)
.await
.to_state_id(args.block);
}
BlockDirection::North => {
let side = get_side(args.world, args.position, BlockDirection::North).await;
wire.north = side.to_north();
side
}
BlockDirection::South => {
let side = get_side(args.world, args.position, BlockDirection::South).await;
wire.south = side.to_south();
side
}
BlockDirection::East => {
let side = get_side(args.world, args.position, BlockDirection::East).await;
wire.east = side.to_east();
side
}
BlockDirection::West => {
let side = get_side(args.world, args.position, BlockDirection::West).await;
wire.west = side.to_west();
side
let wire = RedstoneWireProperties::from_state_id(args.state_id, args.block);
if args.direction == BlockDirection::Up {
let new_wire = get_connection_state(args.world, wire, args.position).await;
return new_wire.to_state_id(args.block);
}
let can_connect_up = !args
.world
.get_block_state(&args.position.up())
.is_solid_block();
let side_connection =
get_connecting_side(args.world, args.position, args.direction, can_connect_up)
.await;
let Some(horizontal) = args.direction.to_horizontal_facing() else {
return args.state_id;
};
wire = get_regulated_sides(wire, args.world, args.position).await;
wire.power = calculate_power(args.world, args.position).await;
let current_side = get_side_connection(wire, horizontal);
let is_connected_same = side_connection.is_connected() == current_side.is_connected();
if is_cross(old_state) && new_side.is_none() {
return wire.to_state_id(args.block);
if is_connected_same && !is_cross(wire) {
let mut new_wire = wire;
set_side_connection(&mut new_wire, horizontal, side_connection);
new_wire.to_state_id(args.block)
} else {
let mut cross = make_cross(wire.power);
set_side_connection(&mut cross, horizontal, side_connection);
let new_wire = get_connection_state(args.world, cross, args.position).await;
new_wire.to_state_id(args.block)
}
if !is_dot(old_state) && is_dot(wire) {
let power = wire.power;
wire = make_cross(power);
}
wire.to_state_id(args.block)
})
}
fn prepare<'a>(&'a self, args: PrepareArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let wire_props =
RedstoneWireLikeProperties::from_state_id(args.state_id, &Block::REDSTONE_WIRE);
let wire = RedstoneWireProperties::from_state_id(args.state_id, args.block);
for direction in BlockDirection::horizontal() {
let other_block_pos = args.position.offset(direction.to_offset());
let other_block = args.world.get_block(&other_block_pos);
if wire_props.is_side_connected(direction) && other_block != &Block::REDSTONE_WIRE {
let up_block_pos = other_block_pos.up();
let up_block = args.world.get_block(&up_block_pos);
if up_block == &Block::REDSTONE_WIRE {
if is_side_connected_prop(wire, direction) {
let dir_block_pos = args.position.offset(direction.to_offset());
if args.world.get_block(&dir_block_pos) != &Block::REDSTONE_WIRE {
let down_pos = dir_block_pos.down();
if args.world.get_block(&down_pos) == &Block::REDSTONE_WIRE {
args.world
.replace_with_state_for_neighbor_update(
&up_block_pos,
&down_pos,
direction.opposite().to_block_direction(),
args.flags,
)
.await;
}
let down_block_pos = other_block_pos.down();
let down_block = args.world.get_block(&down_block_pos);
if down_block == &Block::REDSTONE_WIRE {
let up_pos = dir_block_pos.up();
if args.world.get_block(&up_pos) == &Block::REDSTONE_WIRE {
args.world
.replace_with_state_for_neighbor_update(
&down_block_pos,
&up_pos,
direction.opposite().to_block_direction(),
args.flags,
)
@ -134,23 +144,6 @@ impl BlockBehaviour for RedstoneWireBlock {
}
}
}
for side_dir in BlockDirection::all() {
let side_block_pos = args.position.offset(side_dir.to_offset());
for dir in BlockDirection::all() {
if dir.opposite() == side_dir {
continue;
}
let side_neighbor_pos = side_block_pos.offset(dir.to_offset());
let side_neighbor_block = args.world.get_block(&side_neighbor_pos);
if side_neighbor_block == &Block::REDSTONE_WALL_TORCH
|| side_neighbor_block == &Block::PISTON
|| side_neighbor_block == &Block::STICKY_PISTON
{
args.world
.update_neighbor(&side_neighbor_pos, &Block::REDSTONE_WIRE)
.await;
}
}
}
})
}
@ -159,32 +152,54 @@ impl BlockBehaviour for RedstoneWireBlock {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
let wire = RedstoneWireProperties::from_state_id(state.id, args.block);
if on_use(wire, args.world, args.position).await {
BlockActionResult::Success
if is_cross(wire) || is_dot(wire) {
let mut new_wire = if is_cross(wire) {
RedstoneWireProperties::default(&Block::REDSTONE_WIRE)
} else {
BlockActionResult::Pass
make_cross(wire.power)
};
new_wire.power = wire.power;
new_wire = get_connection_state(args.world, new_wire, args.position).await;
if wire != new_wire {
args.world
.set_block_state(
args.position,
new_wire.to_state_id(&Block::REDSTONE_WIRE),
BlockFlags::NOTIFY_ALL,
)
.await;
for direction in BlockDirection::horizontal() {
let relative_pos = args.position.offset(direction.to_offset());
let old_connected = is_side_connected_prop(wire, direction);
let new_connected = is_side_connected_prop(new_wire, direction);
if old_connected != new_connected
&& args.world.get_block_state(&relative_pos).is_solid_block()
{
args.world
.update_neighbors(
&relative_pos,
Some(direction.opposite().to_block_direction()),
)
.await;
}
}
return BlockActionResult::Success;
}
}
BlockActionResult::Pass
})
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if can_place_at(args.world.as_ref(), args.position) {
let state = args.world.get_block_state(args.position);
let mut wire = RedstoneWireProperties::from_state_id(state.id, args.block);
let new_power = calculate_power(args.world, args.position).await;
if wire.power != new_power {
wire.power = new_power;
args.world
.set_block_state(
args.position,
wire.to_state_id(&Block::REDSTONE_WIRE),
BlockFlags::empty(),
)
.await;
RedstoneWireTurbo::update_surrounding_neighbors(args.world, *args.position)
.await;
}
if can_survive(args.world.as_ref(), args.position) {
update_power_strength(args.world, args.position).await;
} else {
args.world
.break_block(args.position, None, BlockFlags::NOTIFY_ALL)
@ -199,16 +214,18 @@ impl BlockBehaviour for RedstoneWireBlock {
) -> BlockFuture<'a, u8> {
Box::pin(async move {
let wire = RedstoneWireProperties::from_state_id(args.state.id, args.block);
let is_connected = args
.direction
.opposite()
.to_horizontal_facing()
.is_some_and(|f| wire.is_side_connected(f));
if args.direction == BlockDirection::Up || is_connected {
wire.power
} else {
0
if wire.power == 0 || args.direction == BlockDirection::Down {
return 0;
}
if args.direction == BlockDirection::Up {
return wire.power;
}
if let Some(horizontal) = args.direction.opposite().to_horizontal_facing()
&& is_side_connected_prop(wire, horizontal)
{
return wire.power;
}
0
})
}
@ -218,61 +235,359 @@ impl BlockBehaviour for RedstoneWireBlock {
) -> BlockFuture<'a, u8> {
Box::pin(async move {
let wire = RedstoneWireProperties::from_state_id(args.state.id, args.block);
let is_connected = args
.direction
.opposite()
.to_horizontal_facing()
.is_some_and(|f| wire.is_side_connected(f));
if args.direction == BlockDirection::Up || is_connected {
wire.power
} else {
if wire.power == 0 || args.direction == BlockDirection::Down {
return 0;
}
if args.direction == BlockDirection::Up {
return wire.power;
}
if let Some(horizontal) = args.direction.opposite().to_horizontal_facing()
&& is_side_connected_prop(wire, horizontal)
{
return wire.power;
}
0
}
})
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
update_wire_neighbors(args.world, args.position).await;
})
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
update_wire_neighbors(args.world, args.position).await;
})
}
}
fn can_place_at(world: &dyn BlockAccessor, block_pos: &BlockPos) -> bool {
let floor = world.get_block_state(&block_pos.down());
floor.is_side_solid(BlockDirection::Up)
}
async fn on_use(wire: RedstoneWireProperties, world: &Arc<World>, block_pos: &BlockPos) -> bool {
if is_cross(wire) || is_dot(wire) {
let mut new_wire = if is_cross(wire) {
RedstoneWireProperties::default(&Block::REDSTONE_WIRE)
} else {
make_cross(wire.power)
fn rotate(
&self,
block: &Block,
state_id: BlockStateId,
rotation: Rotation,
) -> &'static BlockState {
let wire = RedstoneWireProperties::from_state_id(state_id, block);
let new_wire = match rotation {
Rotation::Rotate180 => RedstoneWireProperties {
north: wire.south.to_wire_connection().to_north(),
east: wire.west.to_wire_connection().to_east(),
south: wire.north.to_wire_connection().to_south(),
west: wire.east.to_wire_connection().to_west(),
power: wire.power,
},
Rotation::CounterClockwise90 => RedstoneWireProperties {
north: wire.east.to_wire_connection().to_north(),
east: wire.south.to_wire_connection().to_east(),
south: wire.west.to_wire_connection().to_south(),
west: wire.north.to_wire_connection().to_west(),
power: wire.power,
},
Rotation::Clockwise90 => RedstoneWireProperties {
north: wire.west.to_wire_connection().to_north(),
east: wire.north.to_wire_connection().to_east(),
south: wire.east.to_wire_connection().to_south(),
west: wire.south.to_wire_connection().to_west(),
power: wire.power,
},
Rotation::None => wire,
};
new_wire.power = wire.power;
BlockState::from_id(new_wire.to_state_id(block))
}
fn mirror(&self, block: &Block, state_id: BlockStateId, mirror: Mirror) -> &'static BlockState {
let wire = RedstoneWireProperties::from_state_id(state_id, block);
let new_wire = match mirror {
Mirror::LeftRight => RedstoneWireProperties {
north: wire.south.to_wire_connection().to_north(),
south: wire.north.to_wire_connection().to_south(),
east: wire.east,
west: wire.west,
power: wire.power,
},
Mirror::FrontBack => RedstoneWireProperties {
north: wire.north,
south: wire.south,
east: wire.west.to_wire_connection().to_east(),
west: wire.east.to_wire_connection().to_west(),
power: wire.power,
},
Mirror::None => wire,
};
BlockState::from_id(new_wire.to_state_id(block))
}
}
// ---------------------------------------------------------------------------
// Evaluator Logic (matching DefaultRedstoneWireEvaluator)
// ---------------------------------------------------------------------------
pub async fn update_power_strength(world: &Arc<World>, pos: &BlockPos) {
let (block, state) = world.get_block_and_state(pos);
if block != &Block::REDSTONE_WIRE {
return;
}
let mut wire = RedstoneWireProperties::from_state_id(state.id, block);
let target_strength = calculate_target_strength(world, pos).await;
if wire.power != target_strength {
wire.power = target_strength;
let new_state_id = wire.to_state_id(&Block::REDSTONE_WIRE);
new_wire = get_regulated_sides(new_wire, world, block_pos).await;
if wire != new_wire {
world
.set_block_state(
block_pos,
new_wire.to_state_id(&Block::REDSTONE_WIRE),
BlockFlags::empty(),
)
.set_block_state(pos, new_state_id, BlockFlags::empty())
.await;
update_wire_neighbors(world, block_pos).await;
let mut to_update = Vec::with_capacity(7);
to_update.push(*pos);
for direction in BlockDirection::all() {
to_update.push(pos.offset(direction.to_offset()));
}
for block_pos in to_update {
update_neighbors_at(world, &block_pos, &Block::REDSTONE_WIRE).await;
}
}
}
async fn calculate_target_strength(world: &World, pos: &BlockPos) -> u8 {
let block_signal = get_block_signal(world, pos).await;
if block_signal == 15 {
return 15;
}
let wire_signal = get_incoming_wire_signal(world, pos);
block_signal.max(wire_signal)
}
async fn get_block_signal(world: &World, pos: &BlockPos) -> u8 {
let mut max_signal = 0;
for side in BlockDirection::all() {
let neighbor_pos = pos.offset(side.to_offset());
let (neighbor_block, neighbor_state) = world.get_block_and_state(&neighbor_pos);
let signal =
get_redstone_power_no_dust(neighbor_block, neighbor_state, world, neighbor_pos, side)
.await;
if signal == 15 {
return 15;
}
max_signal = max_signal.max(signal);
}
max_signal
}
fn get_incoming_wire_signal(world: &World, pos: &BlockPos) -> u8 {
let mut max_wire_signal = 0;
let up_pos = pos.up();
let is_up_conductor = world.get_block_state(&up_pos).is_solid_block();
for direction in BlockDirection::horizontal() {
let neighbor_pos = pos.offset(direction.to_offset());
let (neighbor_block, neighbor_state) = world.get_block_and_state(&neighbor_pos);
// Same level
if neighbor_block == &Block::REDSTONE_WIRE {
let wire = RedstoneWireProperties::from_state_id(neighbor_state.id, neighbor_block);
max_wire_signal = max_wire_signal.max(wire.power);
}
// Wire UP: if pos.up() is not a solid conductor, and neighbor is a solid conductor
if !is_up_conductor && neighbor_state.is_solid_block() {
let neighbor_up_pos = neighbor_pos.up();
let (up_block, up_state) = world.get_block_and_state(&neighbor_up_pos);
if up_block == &Block::REDSTONE_WIRE {
let wire = RedstoneWireProperties::from_state_id(up_state.id, up_block);
max_wire_signal = max_wire_signal.max(wire.power);
}
} else if !neighbor_state.is_solid_block() {
// Wire DOWN: if neighbor is not a solid conductor
let neighbor_down_pos = neighbor_pos.down();
let (down_block, down_state) = world.get_block_and_state(&neighbor_down_pos);
if down_block == &Block::REDSTONE_WIRE {
let wire = RedstoneWireProperties::from_state_id(down_state.id, down_block);
max_wire_signal = max_wire_signal.max(wire.power);
}
}
}
max_wire_signal.saturating_sub(1)
}
pub async fn update_neighbors_at(world: &Arc<World>, pos: &BlockPos, source_block: &Block) {
for direction in BlockDirection::update_order() {
let neighbor_pos = pos.offset(direction.to_offset());
world.update_neighbor(&neighbor_pos, source_block).await;
}
}
async fn check_corner_change_at(world: &Arc<World>, pos: &BlockPos) {
if world.get_block(pos) == &Block::REDSTONE_WIRE {
update_neighbors_at(world, pos, &Block::REDSTONE_WIRE).await;
for direction in BlockDirection::all() {
let neighbor_pos = pos.offset(direction.to_offset());
update_neighbors_at(world, &neighbor_pos, &Block::REDSTONE_WIRE).await;
}
}
}
async fn update_neighbors_of_neighboring_wires(world: &Arc<World>, pos: &BlockPos) {
for direction in BlockDirection::horizontal() {
let neighbor_pos = pos.offset(direction.to_offset());
check_corner_change_at(world, &neighbor_pos).await;
}
for direction in BlockDirection::horizontal() {
let target = pos.offset(direction.to_offset());
if world.get_block_state(&target).is_solid_block() {
check_corner_change_at(world, &target.up()).await;
} else {
check_corner_change_at(world, &target.down()).await;
}
}
}
// ---------------------------------------------------------------------------
// Connection & Shape Helper Functions
// ---------------------------------------------------------------------------
pub async fn get_connection_state(
world: &World,
state: RedstoneWireProperties,
pos: &BlockPos,
) -> RedstoneWireProperties {
let was_dot = is_dot(state);
let mut default_state = RedstoneWireProperties::default(&Block::REDSTONE_WIRE);
default_state.power = state.power;
let mut new_state = get_missing_connections(world, default_state, pos).await;
if was_dot && is_dot(new_state) {
return new_state;
}
let north = new_state.north.to_wire_connection().is_connected();
let south = new_state.south.to_wire_connection().is_connected();
let east = new_state.east.to_wire_connection().is_connected();
let west = new_state.west.to_wire_connection().is_connected();
let north_south_empty = !north && !south;
let east_west_empty = !east && !west;
if !west && north_south_empty {
new_state.west = WestRedstone::Side;
}
if !east && north_south_empty {
new_state.east = EastRedstone::Side;
}
if !north && east_west_empty {
new_state.north = NorthRedstone::Side;
}
if !south && east_west_empty {
new_state.south = SouthRedstone::Side;
}
new_state
}
async fn get_missing_connections(
world: &World,
mut state: RedstoneWireProperties,
pos: &BlockPos,
) -> RedstoneWireProperties {
let can_connect_up = !world.get_block_state(&pos.up()).is_solid_block();
for direction in BlockDirection::horizontal() {
if !is_side_connected_prop(state, direction) {
let side_connection =
get_connecting_side(world, pos, direction.to_block_direction(), can_connect_up)
.await;
set_side_connection(&mut state, direction, side_connection);
}
}
state
}
async fn get_connecting_side(
world: &World,
pos: &BlockPos,
direction: BlockDirection,
can_connect_up: bool,
) -> WireConnection {
let relative_pos = pos.offset(direction.to_offset());
let (relative_block, relative_state) = world.get_block_and_state(&relative_pos);
if can_connect_up {
let is_placeable_above =
is_trapdoor(relative_block) || can_survive_on(relative_block, relative_state);
let (above_block, above_state) = world.get_block_and_state(&relative_pos.up());
if is_placeable_above && should_connect_to(world, above_block, above_state, None).await {
if relative_state.is_side_solid(direction.opposite()) {
return WireConnection::Up;
}
return WireConnection::Side;
}
}
let connects_to_relative =
should_connect_to(world, relative_block, relative_state, Some(direction)).await;
let (below_block, below_state) = world.get_block_and_state(&relative_pos.down());
let connects_to_below = should_connect_to(world, below_block, below_state, None).await;
if !connects_to_relative && (relative_state.is_solid_block() || !connects_to_below) {
WireConnection::None
} else {
WireConnection::Side
}
}
async fn should_connect_to(
world: &World,
block: &Block,
state: &BlockState,
direction: Option<BlockDirection>,
) -> bool {
if block == &Block::REDSTONE_WIRE {
return true;
}
if block == &Block::REPEATER {
let repeater_props = RepeaterLikeProperties::from_state_id(state.id, block);
let repeater_facing = repeater_props.facing.to_block_direction();
return direction
.is_some_and(|dir| repeater_facing == dir || repeater_facing == dir.opposite());
}
if block == &Block::OBSERVER {
let observer_props = ObserverLikeProperties::from_state_id(state.id, block);
let observer_facing = observer_props.facing;
return direction.is_some_and(|dir| dir.to_facing() == observer_facing);
}
if let Some(dir) = direction {
world
.block_registry
.emits_redstone_power(block, state, dir)
.await
} else {
false
}
}
fn is_trapdoor(block: &Block) -> bool {
block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_TRAPDOORS)
}
fn can_survive(world: &dyn BlockAccessor, pos: &BlockPos) -> bool {
let below = pos.down();
let (below_block, below_state) = world.get_block_and_state(&below);
can_survive_on(below_block, below_state)
}
fn can_survive_on(relative_block: &Block, relative_state: &BlockState) -> bool {
relative_state.is_side_solid(BlockDirection::Up) || relative_block == &Block::HOPPER
}
#[must_use]
pub fn is_dot(wire: RedstoneWireProperties) -> bool {
!wire.north.to_wire_connection().is_connected()
&& !wire.south.to_wire_connection().is_connected()
&& !wire.east.to_wire_connection().is_connected()
&& !wire.west.to_wire_connection().is_connected()
}
#[must_use]
pub fn is_cross(wire: RedstoneWireProperties) -> bool {
wire.north.to_wire_connection().is_connected()
&& wire.south.to_wire_connection().is_connected()
&& wire.east.to_wire_connection().is_connected()
&& wire.west.to_wire_connection().is_connected()
}
#[must_use]
pub const fn make_cross(power: u8) -> RedstoneWireProperties {
@ -285,149 +600,33 @@ pub const fn make_cross(power: u8) -> RedstoneWireProperties {
}
}
async fn can_connect_to(
world: &World,
block: &Block,
side: BlockDirection,
state: &BlockState,
) -> bool {
if world
.block_registry
.emits_redstone_power(block, state, side)
.await
{
return true;
}
if block == &Block::REPEATER {
let repeater_props = RepeaterLikeProperties::from_state_id(state.id, block);
return repeater_props.facing.to_block_direction() == side
|| repeater_props.facing.to_block_direction() == side.opposite();
} else if block == &Block::OBSERVER {
let observer_props = ObserverLikeProperties::from_state_id(state.id, block);
return observer_props.facing == side.to_facing();
} else if block == &Block::REDSTONE_WIRE {
return true;
}
false
}
fn can_connect_diagonal_to(block: &Block) -> bool {
block == &Block::REDSTONE_WIRE
}
pub async fn get_side(world: &World, pos: &BlockPos, side: BlockDirection) -> WireConnection {
let neighbor_pos: BlockPos = pos.offset(side.to_offset());
let (neighbor, state) = world.get_block_and_state(&neighbor_pos);
if can_connect_to(world, neighbor, side, state).await {
return WireConnection::Side;
}
let up_pos = pos.offset(BlockDirection::Up.to_offset());
let up_state = world.get_block_state(&up_pos);
if !up_state.is_solid_block()
&& state.is_side_solid(side.opposite())
&& can_connect_diagonal_to(
world.get_block(&neighbor_pos.offset(BlockDirection::Up.to_offset())),
)
{
WireConnection::Up
} else if !state.is_solid_block()
&& can_connect_diagonal_to(
world.get_block(&neighbor_pos.offset(BlockDirection::Down.to_offset())),
)
{
WireConnection::Side
} else {
WireConnection::None
}
}
async fn get_all_sides(
mut wire: RedstoneWireProperties,
world: &World,
pos: &BlockPos,
) -> RedstoneWireProperties {
wire.north = get_side(world, pos, BlockDirection::North).await.to_north();
wire.south = get_side(world, pos, BlockDirection::South).await.to_south();
wire.east = get_side(world, pos, BlockDirection::East).await.to_east();
wire.west = get_side(world, pos, BlockDirection::West).await.to_west();
wire
}
#[must_use]
pub fn is_dot(wire: RedstoneWireProperties) -> bool {
wire.north == NorthRedstone::None
&& wire.south == SouthRedstone::None
&& wire.east == EastRedstone::None
&& wire.west == WestRedstone::None
}
#[must_use]
pub fn is_cross(wire: RedstoneWireProperties) -> bool {
wire.north == NorthRedstone::Side
&& wire.south == SouthRedstone::Side
&& wire.east == EastRedstone::Side
&& wire.west == WestRedstone::Side
}
pub async fn get_regulated_sides(
fn get_side_connection(
wire: RedstoneWireProperties,
world: &World,
pos: &BlockPos,
) -> RedstoneWireProperties {
let mut state = get_all_sides(wire, world, pos).await;
if is_dot(wire) && is_dot(state) {
return state;
}
let north_none = state.north == NorthRedstone::None;
let south_none = state.south == SouthRedstone::None;
let east_none = state.east == EastRedstone::None;
let west_none = state.west == WestRedstone::None;
let north_south_none = north_none && south_none;
let east_west_none = east_none && west_none;
if north_none && east_west_none {
state.north = NorthRedstone::Side;
}
if south_none && east_west_none {
state.south = SouthRedstone::Side;
}
if east_none && north_south_none {
state.east = EastRedstone::Side;
}
if west_none && north_south_none {
state.west = WestRedstone::Side;
}
state
}
trait RedstoneWireLikePropertiesExt {
fn is_side_connected(&self, direction: HorizontalFacing) -> bool;
//fn get_connection_type(&self, direction: BlockDirection) -> WireConnection;
}
impl RedstoneWireLikePropertiesExt for RedstoneWireLikeProperties {
fn is_side_connected(&self, direction: HorizontalFacing) -> bool {
direction: HorizontalFacing,
) -> WireConnection {
match direction {
HorizontalFacing::North => self.north.to_wire_connection().is_connected(),
HorizontalFacing::South => self.south.to_wire_connection().is_connected(),
HorizontalFacing::East => self.east.to_wire_connection().is_connected(),
HorizontalFacing::West => self.west.to_wire_connection().is_connected(),
HorizontalFacing::North => wire.north.to_wire_connection(),
HorizontalFacing::South => wire.south.to_wire_connection(),
HorizontalFacing::East => wire.east.to_wire_connection(),
HorizontalFacing::West => wire.west.to_wire_connection(),
}
}
/*
fn get_connection_type(&self, direction: BlockDirection) -> WireConnection {
const fn set_side_connection(
wire: &mut RedstoneWireProperties,
direction: HorizontalFacing,
connection: WireConnection,
) {
match direction {
BlockDirection::North => self.north.to_wire_connection(),
BlockDirection::South => self.south.to_wire_connection(),
BlockDirection::East => self.east.to_wire_connection(),
BlockDirection::West => self.west.to_wire_connection(),
_ => WireConnection::None,
HorizontalFacing::North => wire.north = connection.to_north(),
HorizontalFacing::South => wire.south = connection.to_south(),
HorizontalFacing::East => wire.east = connection.to_east(),
HorizontalFacing::West => wire.west = connection.to_west(),
}
}
*/
fn is_side_connected_prop(wire: RedstoneWireProperties, direction: HorizontalFacing) -> bool {
get_side_connection(wire, direction).is_connected()
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@ -438,15 +637,13 @@ pub enum WireConnection {
}
impl WireConnection {
fn is_connected(self) -> bool {
self != Self::None
#[must_use]
pub const fn is_connected(self) -> bool {
!matches!(self, Self::None)
}
fn is_none(self) -> bool {
self == Self::None
}
const fn to_north(self) -> NorthRedstone {
#[must_use]
pub const fn to_north(self) -> NorthRedstone {
match self {
Self::Up => NorthRedstone::Up,
Self::Side => NorthRedstone::Side,
@ -454,7 +651,8 @@ impl WireConnection {
}
}
const fn to_south(self) -> SouthRedstone {
#[must_use]
pub const fn to_south(self) -> SouthRedstone {
match self {
Self::Up => SouthRedstone::Up,
Self::Side => SouthRedstone::Side,
@ -462,7 +660,8 @@ impl WireConnection {
}
}
const fn to_east(self) -> EastRedstone {
#[must_use]
pub const fn to_east(self) -> EastRedstone {
match self {
Self::Up => EastRedstone::Up,
Self::Side => EastRedstone::Side,
@ -470,7 +669,8 @@ impl WireConnection {
}
}
const fn to_west(self) -> WestRedstone {
#[must_use]
pub const fn to_west(self) -> WestRedstone {
match self {
Self::Up => WestRedstone::Up,
Self::Side => WestRedstone::Side,
@ -478,7 +678,8 @@ impl WireConnection {
}
}
}
trait CardinalWireConnectionExt {
pub trait CardinalWireConnectionExt {
fn to_wire_connection(&self) -> WireConnection;
}
@ -521,49 +722,3 @@ impl CardinalWireConnectionExt for WestRedstone {
}
}
}
fn max_wire_power(wire_power: u8, world: &World, pos: BlockPos) -> u8 {
let (block, block_state) = world.get_block_and_state(&pos);
if block == &Block::REDSTONE_WIRE {
let wire = RedstoneWireProperties::from_state_id(block_state.id, block);
wire_power.max(wire.power)
} else {
wire_power
}
}
async fn calculate_power(world: &World, pos: &BlockPos) -> u8 {
let mut block_power: u8 = 0;
let mut wire_power: u8 = 0;
let up_pos = pos.offset(BlockDirection::Up.to_offset());
let up_state = world.get_block_state(&up_pos);
for side in BlockDirection::all() {
let neighbor_pos = pos.offset(side.to_offset());
wire_power = max_wire_power(wire_power, world, neighbor_pos);
let (neighbor, neighbor_state) = world.get_block_and_state(&neighbor_pos);
block_power = block_power.max(
get_redstone_power_no_dust(neighbor, neighbor_state, world, neighbor_pos, side).await,
);
if side.is_horizontal() {
if !up_state.is_solid_block() && neighbor_state.is_solid_block() {
wire_power = max_wire_power(
wire_power,
world,
neighbor_pos.offset(BlockDirection::Up.to_offset()),
);
}
if !neighbor_state.is_solid_block() {
wire_power = max_wire_power(
wire_power,
world,
neighbor_pos.offset(BlockDirection::Down.to_offset()),
);
}
}
}
block_power.max(wire_power.saturating_sub(1))
}

View File

@ -1,435 +0,0 @@
//! The implementation of "Redstone Wire Turbo" was largely based on
//! the accelerator created by theosib. For more information, see:
//! <https://bugs.mojang.com/browse/MC-81098>.
use pumpkin_data::{
Block, BlockDirection, BlockState,
block_properties::{BlockProperties, RedstoneWireLikeProperties},
};
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::world::BlockFlags;
use rustc_hash::FxHashMap;
use std::sync::Arc;
use crate::world::World;
use super::get_redstone_power_no_dust;
type RedstoneWireProps = RedstoneWireLikeProperties;
fn unwrap_wire(state: &BlockState) -> RedstoneWireProps {
RedstoneWireProps::from_state_id(state.id, &Block::REDSTONE_WIRE)
}
#[derive(Clone, Copy)]
struct NodeId {
index: usize,
}
struct UpdateNode {
pos: BlockPos,
/// The cached state of the block
state: &'static BlockState,
/// This will only be `Some` when all the neighbors are identified.
neighbors: Option<Vec<NodeId>>,
visited: bool,
xbias: i32,
zbias: i32,
layer: u32,
}
impl UpdateNode {
fn new(world: &World, pos: BlockPos) -> Self {
Self {
pos,
state: world.get_block_state(&pos),
visited: false,
neighbors: None,
xbias: 0,
zbias: 0,
layer: 0,
}
}
}
pub(super) struct RedstoneWireTurbo {
nodes: Vec<UpdateNode>,
node_cache: FxHashMap<BlockPos, NodeId>,
update_queue: Vec<Vec<NodeId>>,
current_walk_layer: u32,
}
impl RedstoneWireTurbo {
// Internal numbering for cardinal directions
const NORTH: usize = 0;
const EAST: usize = 1;
const SOUTH: usize = 2;
const WEST: usize = 3;
pub fn new() -> Self {
Self {
nodes: Vec::new(),
node_cache: FxHashMap::default(),
update_queue: vec![vec![], vec![], vec![]],
current_walk_layer: 0,
}
}
fn get_node(&self, node_id: NodeId) -> &UpdateNode {
&self.nodes[node_id.index]
}
const fn compute_all_neighbors(pos: BlockPos) -> [BlockPos; 24] {
let Vector3 { x, y, z } = pos.0;
[
BlockPos::new(x - 1, y, z),
BlockPos::new(x + 1, y, z),
BlockPos::new(x, y - 1, z),
BlockPos::new(x, y + 1, z),
BlockPos::new(x, y, z - 1),
BlockPos::new(x, y, z + 1),
// Neighbors of neighbors, in the same order,
// except that duplicates are not included
BlockPos::new(x - 2, y, z),
BlockPos::new(x - 1, y - 1, z),
BlockPos::new(x - 1, y + 1, z),
BlockPos::new(x - 1, y, z - 1),
BlockPos::new(x - 1, y, z + 1),
BlockPos::new(x + 2, y, z),
BlockPos::new(x + 1, y - 1, z),
BlockPos::new(x + 1, y + 1, z),
BlockPos::new(x + 1, y, z - 1),
BlockPos::new(x + 1, y, z + 1),
BlockPos::new(x, y - 2, z),
BlockPos::new(x, y - 1, z - 1),
BlockPos::new(x, y - 1, z + 1),
BlockPos::new(x, y + 2, z),
BlockPos::new(x, y + 1, z - 1),
BlockPos::new(x, y + 1, z + 1),
BlockPos::new(x, y, z - 2),
BlockPos::new(x, y, z + 2),
]
}
fn compute_heading(rx: i32, rz: i32) -> usize {
let code = (rx + 1) + 3 * (rz + 1);
match code {
0 | 1 => Self::NORTH,
2 | 5 => Self::EAST,
3 | 4 => Self::WEST,
6..=8 => Self::SOUTH,
_ => {
tracing::error!("Invalid turbo heading code: {}", code);
Self::NORTH
}
}
}
// const UPDATE_REDSTONE: [bool; 24] = [
// true, true, false, false, true, true, // 0 to 5
// false, true, true, false, false, false, // 6 to 11
// true, true, false, false, false, true, // 12 to 17
// true, false, true, true, false, false // 18 to 23
// ];
fn identify_neighbors(&mut self, world: &World, upd1: NodeId) {
let pos = self.nodes[upd1.index].pos;
let neighbors = Self::compute_all_neighbors(pos);
let mut neighbors_visited = Vec::with_capacity(24);
let mut neighbor_nodes = Vec::with_capacity(24);
for neighbor_pos in &neighbors[0..24] {
let neighbor = if self.node_cache.contains_key(neighbor_pos) {
self.node_cache[neighbor_pos]
} else {
let node_id = NodeId {
index: self.nodes.len(),
};
self.node_cache.insert(*neighbor_pos, node_id);
self.nodes.push(UpdateNode::new(world, *neighbor_pos));
node_id
};
let node = &self.nodes[neighbor.index];
// if let Block::RedstoneWire { .. } = node.state {
// if RedstoneWireTurbo::UPDATE_REDSTONE[i] {
neighbor_nodes.push(neighbor);
neighbors_visited.push(node.visited);
// continue;
// }
// }
// neighbor_nodes.push(None);
// neighbors_visited.push(false);
}
let from_west = neighbors_visited[0] || neighbors_visited[7] || neighbors_visited[8];
let from_east = neighbors_visited[1] || neighbors_visited[12] || neighbors_visited[13];
let from_north = neighbors_visited[4] || neighbors_visited[17] || neighbors_visited[20];
let from_south = neighbors_visited[5] || neighbors_visited[18] || neighbors_visited[21];
let mut cx = 0;
let mut cz = 0;
if from_west {
cx += 1;
}
if from_east {
cx -= 1;
}
if from_north {
cz += 1;
}
if from_south {
cz -= 1;
}
let UpdateNode { xbias, zbias, .. } = &self.nodes[upd1.index];
let xbias = *xbias;
let zbias = *zbias;
let heading;
if cx == 0 && cz == 0 {
heading = Self::compute_heading(xbias, zbias);
for node_id in &neighbor_nodes {
// if let Some(node_id) = node_id {
let nn = &mut self.nodes[node_id.index];
nn.xbias = xbias;
nn.zbias = zbias;
// }
}
} else {
if cx != 0 && cz != 0 {
if xbias != 0 {
cz = 0;
}
if zbias != 0 {
cx = 0;
}
}
heading = Self::compute_heading(cx, cz);
for node_id in &neighbor_nodes {
// if let Some(node_id) = node_id {
let nn = &mut self.nodes[node_id.index];
nn.xbias = cx;
nn.zbias = cz;
// }
}
}
self.orient_neighbors(&neighbor_nodes, upd1, heading);
}
const REORDERING: [[usize; 24]; 4] = [
[
2, 3, 16, 19, 0, 4, 1, 5, 7, 8, 17, 20, 12, 13, 18, 21, 6, 9, 22, 14, 11, 10, 23, 15,
],
[
2, 3, 16, 19, 4, 1, 5, 0, 17, 20, 12, 13, 18, 21, 7, 8, 22, 14, 11, 15, 23, 9, 6, 10,
],
[
2, 3, 16, 19, 1, 5, 0, 4, 12, 13, 18, 21, 7, 8, 17, 20, 11, 15, 23, 10, 6, 14, 22, 9,
],
[
2, 3, 16, 19, 5, 0, 4, 1, 18, 21, 7, 8, 17, 20, 12, 13, 23, 10, 6, 9, 22, 15, 11, 14,
],
];
fn orient_neighbors(&mut self, src: &[NodeId], dst_id: NodeId, heading: usize) {
let dst = &mut self.nodes[dst_id.index];
let mut neighbors = Vec::with_capacity(24);
let re = Self::REORDERING[heading];
for i in &re {
neighbors.push(src[*i]);
}
dst.neighbors = Some(neighbors);
}
/// This is the start of a great adventure
pub async fn update_surrounding_neighbors(world: &Arc<World>, pos: BlockPos) {
let mut turbo = Self::new();
let mut root_node = UpdateNode::new(world, pos);
root_node.visited = true;
let node_id = NodeId { index: 0 };
turbo.node_cache.insert(pos, node_id);
turbo.nodes.push(root_node);
turbo.propagate_changes(world, node_id, 0);
turbo.breadth_first_walk(world).await;
}
fn propagate_changes(&mut self, world: &World, upd1: NodeId, layer: u32) {
if self.nodes[upd1.index].neighbors.is_none() {
self.identify_neighbors(world, upd1);
}
let Some(neighbors_ref) = self.nodes[upd1.index].neighbors.as_ref() else {
return;
};
let Ok(neighbors): Result<[NodeId; 24], _> = neighbors_ref[0..24].try_into() else {
return;
};
let layer1 = layer + 1;
for neighbor_id in neighbors {
let neighbor = &mut self.nodes[neighbor_id.index];
if layer1 > neighbor.layer {
neighbor.layer = layer1;
self.update_queue[1].push(neighbor_id);
}
}
let layer2 = layer + 2;
for neighbor_id in &neighbors[0..4] {
let neighbor = &mut self.nodes[neighbor_id.index];
if layer2 > neighbor.layer {
neighbor.layer = layer2;
self.update_queue[2].push(*neighbor_id);
}
}
}
async fn breadth_first_walk(&mut self, world: &Arc<World>) {
self.shift_queue();
self.current_walk_layer = 1;
while !self.update_queue[0].is_empty() || !self.update_queue[1].is_empty() {
let mut current_queue = self.update_queue.remove(0);
self.update_queue.push(Vec::new());
for node_id in &current_queue {
let block = Block::from_state_id(self.nodes[node_id.index].state.id);
if block == &Block::REDSTONE_WIRE {
self.update_node(world, *node_id, self.current_walk_layer)
.await;
} else {
// This only works because updating any other block than a wire will
// never change the state of the block. If that changes in the future,
// the cached state will need to be updated
world
.update_neighbor(&self.nodes[node_id.index].pos, block)
.await;
}
}
current_queue.clear();
let last_idx = self.update_queue.len() - 1;
self.update_queue[last_idx] = current_queue;
self.current_walk_layer += 1;
}
self.current_walk_layer = 0;
}
fn shift_queue(&mut self) {
let mut t = self.update_queue.remove(0);
t.clear();
self.update_queue.push(t);
}
async fn update_node(&mut self, world: &Arc<World>, upd1: NodeId, layer: u32) {
let old_wire = {
let node = &mut self.nodes[upd1.index];
node.visited = true;
unwrap_wire(node.state)
};
let new_wire = self.calculate_current_changes(world, upd1).await;
if old_wire.power != new_wire.power {
let node = &mut self.nodes[upd1.index];
let mut wire = unwrap_wire(node.state);
wire.power = new_wire.power;
node.state = BlockState::from_id(wire.to_state_id(&Block::REDSTONE_WIRE));
self.propagate_changes(world, upd1, layer);
}
}
const RS_NEIGHBORS: [usize; 4] = [4, 5, 6, 7];
const RS_NEIGHBORS_UP: [usize; 4] = [9, 11, 13, 15];
const RS_NEIGHBORS_DN: [usize; 4] = [8, 10, 12, 14];
async fn calculate_current_changes(
&mut self,
world: &Arc<World>,
upd: NodeId,
) -> RedstoneWireProps {
let mut wire = unwrap_wire(self.nodes[upd.index].state);
let i = wire.power;
let mut block_power = 0;
if self.nodes[upd.index].neighbors.is_none() {
self.identify_neighbors(world, upd);
}
let pos = self.nodes[upd.index].pos;
let mut wire_power = 0;
for side in BlockDirection::all() {
let neighbor_pos = pos.offset(side.to_offset());
let neighbor = &self.nodes[self.node_cache[&neighbor_pos].index].state;
wire_power = wire_power.max(
get_redstone_power_no_dust(
Block::from_state_id(neighbor.id),
neighbor,
world,
neighbor_pos,
side,
)
.await,
);
}
if wire_power < 15 {
let Some(neighbors) = self.nodes[upd.index].neighbors.as_ref() else {
return wire;
};
let center_up = self.nodes[neighbors[1].index].state;
for m in 0..4 {
let n = Self::RS_NEIGHBORS[m];
let neighbor_id = neighbors[n];
let neighbor = self.get_node(neighbor_id).state;
block_power = self.get_max_current_strength(neighbor_id, block_power);
if !neighbor.is_solid_block() {
let neighbor_down = neighbors[Self::RS_NEIGHBORS_DN[m]];
block_power = self.get_max_current_strength(neighbor_down, block_power);
} else if !center_up.is_solid_block() && neighbor.is_solid_block() {
let neighbor_up = neighbors[Self::RS_NEIGHBORS_UP[m]];
block_power = self.get_max_current_strength(neighbor_up, block_power);
}
}
}
let mut j = block_power.saturating_sub(1);
if wire_power > j {
j = wire_power;
}
if i != j {
wire.power = j;
world
.set_block_state(
&pos,
wire.to_state_id(&Block::REDSTONE_WIRE),
BlockFlags::empty(),
)
.await;
}
wire
}
fn get_max_current_strength(&self, upd: NodeId, strength: u8) -> u8 {
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.max(strength)
} else {
strength
}
}
}

View File

@ -2301,7 +2301,12 @@ impl World {
let spawn_position = Vector2::new(level_info.spawn_x, level_info.spawn_z);
let chunk_pos = Vector2::new(level_info.spawn_x >> 4, level_info.spawn_z >> 4);
self.level.get_or_fetch_chunk(chunk_pos, |_| ()).await;
let pos_y = self.get_top_block(spawn_position) + 1; // +1 to spawn on top of the block
let top = self.get_top_block(spawn_position);
let pos_y = if top > self.dimension.min_y {
top + 1
} else {
level_info.spawn_y
};
let position = Vector3::new(
f64::from(level_info.spawn_x) + 0.5,
@ -3146,7 +3151,12 @@ impl World {
let spawn_position = Vector2::new(info.spawn_x, info.spawn_z);
let chunk_pos = Vector2::new(info.spawn_x >> 4, info.spawn_z >> 4);
self.level.get_or_fetch_chunk(chunk_pos, |_| ()).await;
let pos_y = self.get_top_block(spawn_position) + 1; // +1 to spawn on top of the block
let top = self.get_top_block(spawn_position);
let pos_y = if top > self.dimension.min_y {
top + 1
} else {
info.spawn_y
};
let position = Vector3::new(
f64::from(info.spawn_x) + 0.5,