diff --git a/README.md b/README.md index 865963d0..fcbece2d 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ server_context = "global" ### General Commands | Command | Alias | Description | | --- | --- |--- | -| `/rtps [rtps]` | None | Set the **redstone** ticks per second in the plot to `[rtps]`. (There are two game ticks in a redstone tick) | +| `/rtps [rtps\|unlimited]` | None | Set the **redstone** ticks per second in the plot to `[rtps]`. (There are two game ticks in a redstone tick) | | `/radvance [ticks]` | `/radv` | Advances the plot by `[ticks]` redstone ticks. | | `/teleport [player]` | `/tp` | Teleports you to `[player]`. | | `/teleport [x] [y] [z]` | `/tp` | Teleports you to `[x] [y] [z]`. Supports relative coordinates. Floats can be expressed as described [here](https://doc.rust-lang.org/std/primitive.f64.html#grammar). | @@ -122,7 +122,7 @@ These are the commands that are currently implemented: | `//replace` | None | Replace all blocks in a selection with another | | `//copy` | `//c` | Copy the selection to the clipboard | | `//cut` | `//x` | Cut the selection to the clipboard | -| `//paste` | `//v` | Paste the clipboard's contents | +| `//paste` | `//v` | Paste the clipboard's contents (`-a` to ignore air, `-u` to also update) | | `//undo` | None | Undoes the last action (from history) | | `//redo` | None | Redoes the last action (from history) | | `//rstack` | `//rs` | Stack with more options, Refer to [RedstoneTools](https://github.com/paulikauro/RedstoneTools) | @@ -136,7 +136,7 @@ These are the commands that are currently implemented: | `//shift` | None | Shift the selection area | | `//flip` | `//f` | Flip the contents of the clipboard across the origin | | `//rotate` | `//r` | Rotate the contents of the clipboard | -| `//update` | None | Updates all blocks in the selection | +| `//update` | None | Updates all blocks in the selection (`-p` to update the entire plot) | | `//help` | None | Displays help for WorldEdit commands | ## Acknowledgments diff --git a/crates/core/src/plot/commands.rs b/crates/core/src/plot/commands.rs index 4c169cdf..44bfedb8 100644 --- a/crates/core/src/plot/commands.rs +++ b/crates/core/src/plot/commands.rs @@ -301,7 +301,7 @@ impl Plot { return false; } Tps::Limited(tps) - } else if args[0] == "unlimited" { + } else if !args[0].is_empty() && "unlimited".starts_with(args[0]) { Tps::Unlimited } else { self.players[player].send_error_message("Unable to parse rtps!"); diff --git a/crates/core/src/plot/mod.rs b/crates/core/src/plot/mod.rs index b76c6bca..b9b5e4ea 100644 --- a/crates/core/src/plot/mod.rs +++ b/crates/core/src/plot/mod.rs @@ -162,7 +162,6 @@ impl World for PlotWorld { ) } - /// Returns the block state id of the block at `pos` fn get_block_raw(&self, pos: BlockPos) -> u32 { let chunk_index = match self.get_chunk_index_for_block(pos.x, pos.z) { @@ -173,7 +172,6 @@ impl World for PlotWorld { chunk.get_block((pos.x & 0xF) as u32, pos.y as u32, (pos.z & 0xF) as u32) } - fn delete_block_entity(&mut self, pos: BlockPos) { let chunk_index = match self.get_chunk_index_for_block(pos.x, pos.z) { Some(idx) => idx, diff --git a/crates/core/src/plot/worldedit/execute.rs b/crates/core/src/plot/worldedit/execute.rs index b9206a4c..4eaa28d1 100644 --- a/crates/core/src/plot/worldedit/execute.rs +++ b/crates/core/src/plot/worldedit/execute.rs @@ -3,7 +3,6 @@ use crate::chat::{ChatComponentBuilder, ColorCode}; use crate::config::CONFIG; use crate::player::PacketSender; use crate::plot::PLOT_BLOCK_HEIGHT; -use crate::redstone; use crate::utils::HyphenatedUUID; use mchprs_blocks::block_entities::InventoryEntry; use mchprs_blocks::blocks::{Block, FlipDirection, RotateAmt}; @@ -235,17 +234,17 @@ pub(super) fn execute_paste(ctx: CommandExecuteContext<'_>) { let offset_x = pos.x - cb.offset_x; let offset_y = pos.y - cb.offset_y; let offset_z = pos.z - cb.offset_z; - capture_undo( - ctx.plot, - ctx.player, - BlockPos::new(offset_x, offset_y, offset_z), - BlockPos::new( - offset_x + cb.size_x as i32, - offset_y + cb.size_y as i32, - offset_z + cb.size_z as i32, - ), + let first_pos = BlockPos::new(offset_x, offset_y, offset_z); + let second_pos = BlockPos::new( + offset_x + cb.size_x as i32, + offset_y + cb.size_y as i32, + offset_z + cb.size_z as i32, ); + capture_undo(ctx.plot, ctx.player, first_pos, second_pos); paste_clipboard(ctx.plot, cb, pos, ctx.has_flag('a')); + if ctx.has_flag('u') { + update(ctx.plot, first_pos, second_pos); + } ctx.player.send_worldedit_message(&format!( "Your clipboard was pasted. ({:?})", start_time.elapsed() @@ -993,16 +992,21 @@ pub(super) fn execute_rstack(ctx: CommandExecuteContext<'_>) { pub(super) fn execute_update(ctx: CommandExecuteContext<'_>) { let start_time = Instant::now(); - let operation = worldedit_start_operation(ctx.player); - for x in operation.x_range() { - for y in operation.y_range() { - for z in operation.z_range() { - let block_pos = BlockPos::new(x, y, z); - let block = ctx.plot.get_block(block_pos); - redstone::update(block, ctx.plot, block_pos); - } + let (first_pos, second_pos) = if ctx.has_flag('p') { + ctx.plot.get_corners() + } else { + if let (Some(first_pos), Some(second_pos)) = + (ctx.player.first_position, ctx.player.second_position) + { + (first_pos, second_pos) + } else { + ctx.player + .send_error_message("Your selection is incomplete."); + return; } - } + }; + + update(ctx.plot, first_pos, second_pos); ctx.player.send_worldedit_message(&format!( "Your selection was updated sucessfully. ({:?})", diff --git a/crates/core/src/plot/worldedit/mod.rs b/crates/core/src/plot/worldedit/mod.rs index 055f7250..68a9c127 100644 --- a/crates/core/src/plot/worldedit/mod.rs +++ b/crates/core/src/plot/worldedit/mod.rs @@ -5,8 +5,9 @@ mod schematic; use super::{Plot, PlotWorld}; use crate::player::{PacketSender, Player, PlayerPos}; +use crate::redstone; use crate::world::storage::PalettedBitBuffer; -use crate::world::World; +use crate::world::{for_each_block_mut_optimized, World}; use execute::*; use mchprs_blocks::block_entities::{BlockEntity, ContainerType}; use mchprs_blocks::blocks::Block; @@ -540,7 +541,8 @@ static COMMANDS: Lazy> = Lazy::new(|| { execute_fn: execute_paste, description: "Paste the clipboard's contents", flags: &[ - flag!('a', None, "Skip air blocks") + flag!('a', None, "Skip air blocks"), + flag!('u', None, "Also update all affected blocks"), ], permission_node: "worldedit.clipboard.paste", ..Default::default() @@ -694,7 +696,10 @@ static COMMANDS: Lazy> = Lazy::new(|| { execute_fn: execute_update, description: "Updates all blocks in the selection", permission_node: "mchprs.we.update", - requires_positions: true, + requires_positions: false, + flags: &[ + flag!('p', None, "Update the entire plot"), + ], ..Default::default() }, "/help" => WorldeditCommand { @@ -1136,3 +1141,10 @@ fn expand_selection(player: &mut Player, amount: BlockPos, contract: bool) { player.worldedit_set_second_position(p2); } } + +fn update(plot: &mut PlotWorld, first_pos: BlockPos, second_pos: BlockPos) { + for_each_block_mut_optimized(plot, first_pos, second_pos, |plot, pos| { + let block = plot.get_block(pos); + redstone::update(block, plot, pos); + }); +} diff --git a/crates/core/src/redpiler/mod.rs b/crates/core/src/redpiler/mod.rs index 8dfccb45..999d8cd5 100644 --- a/crates/core/src/redpiler/mod.rs +++ b/crates/core/src/redpiler/mod.rs @@ -5,7 +5,7 @@ mod passes; use crate::redpiler::passes::make_default_pass_manager; use crate::redstone; -use crate::world::World; +use crate::world::{for_each_block_mut_optimized, World}; use backend::JITBackend; use mchprs_blocks::blocks::Block; use mchprs_blocks::BlockPos; @@ -130,19 +130,12 @@ impl Compiler { if self.options.optimize { let (first_pos, second_pos) = bounds; - let start_pos = first_pos.min(second_pos); - let end_pos = first_pos.max(second_pos); - for y in start_pos.y..=end_pos.y { - for z in start_pos.z..=end_pos.z { - for x in start_pos.x..=end_pos.x { - let pos = BlockPos::new(x, y, z); - let block = world.get_block(pos); - if matches!(block, Block::RedstoneWire { .. }) { - redstone::update(block, world, pos); - } - } + for_each_block_mut_optimized(world, first_pos, second_pos, |world, pos| { + let block = world.get_block(pos); + if matches!(block, Block::RedstoneWire { .. }) { + redstone::update(block, world, pos); } - } + }); } self.options = Default::default(); } diff --git a/crates/core/src/redpiler/passes/identify_nodes.rs b/crates/core/src/redpiler/passes/identify_nodes.rs index 8de57b51..2d0251d7 100644 --- a/crates/core/src/redpiler/passes/identify_nodes.rs +++ b/crates/core/src/redpiler/passes/identify_nodes.rs @@ -11,7 +11,7 @@ use super::Pass; use crate::redpiler::compile_graph::{CompileGraph, CompileNode, NodeState, NodeType}; use crate::redpiler::{CompilerInput, CompilerOptions}; use crate::redstone; -use crate::world::World; +use crate::world::{for_each_block_optimized, World}; use mchprs_blocks::block_entities::BlockEntity; use mchprs_blocks::blocks::{Block, RedstoneComparator, RedstoneRepeater}; use mchprs_blocks::BlockPos; @@ -30,16 +30,9 @@ impl Pass for IdentifyNodes { let (first_pos, second_pos) = input.bounds; - let start_pos = first_pos.min(second_pos); - let end_pos = first_pos.max(second_pos); - for y in start_pos.y..=end_pos.y { - for z in start_pos.z..=end_pos.z { - for x in start_pos.x..=end_pos.x { - let pos = BlockPos::new(x, y, z); - for_pos(ignore_wires, plot, graph, pos); - } - } - } + for_each_block_optimized(plot, first_pos, second_pos, |pos| { + for_pos(ignore_wires, plot, graph, pos) + }); } fn should_run(&self, _: &CompilerOptions) -> bool { diff --git a/crates/core/src/world/mod.rs b/crates/core/src/world/mod.rs index 6b69381d..afcd93a0 100644 --- a/crates/core/src/world/mod.rs +++ b/crates/core/src/world/mod.rs @@ -54,3 +54,102 @@ pub trait World { false } } + +// TODO: I have no idea how to deduplicate this in a sane way + +/// Executes the given function for each block excluding most air blocks +pub fn for_each_block_optimized( + world: &W, + first_pos: BlockPos, + second_pos: BlockPos, + mut f: F, +) where + F: FnMut(BlockPos), +{ + let start_x = i32::min(first_pos.x, second_pos.x); + let end_x = i32::max(first_pos.x, second_pos.x); + + let start_y = i32::min(first_pos.y, second_pos.y); + let end_y = i32::max(first_pos.y, second_pos.y); + + let start_z = i32::min(first_pos.z, second_pos.z); + let end_z = i32::max(first_pos.z, second_pos.z); + + // Iterate over chunks + for chunk_start_x in (start_x..=end_x).step_by(16) { + for chunk_start_z in (start_z..=end_z).step_by(16) { + let chunk = world + .get_chunk(chunk_start_x.div_euclid(16), chunk_start_z.div_euclid(16)) + .unwrap(); + for chunk_start_y in (start_y..=end_y).step_by(16) { + // Check if the chunk even has non air blocks + if chunk.sections[chunk_start_y as usize / 16].block_count() > 0 { + // Calculate the end position of the current chunk + let chunk_end_x = i32::min(chunk_start_x + 16 - 1, end_x); + let chunk_end_y = i32::min(chunk_start_y + 16 - 1, end_y); + let chunk_end_z = i32::min(chunk_start_z + 16 - 1, end_z); + + // Iterate over each position within the current chunk + for y in chunk_start_y..=chunk_end_y { + for z in chunk_start_z..=chunk_end_z { + for x in chunk_start_x..=chunk_end_x { + let pos = BlockPos::new(x, y, z); + f(pos); + } + } + } + } + } + } + } +} + +/// Executes the given function for each block excluding most air blocks +pub fn for_each_block_mut_optimized( + world: &mut W, + first_pos: BlockPos, + second_pos: BlockPos, + mut f: F, +) where + F: FnMut(&mut W, BlockPos), +{ + let start_x = i32::min(first_pos.x, second_pos.x); + let end_x = i32::max(first_pos.x, second_pos.x); + + let start_y = i32::min(first_pos.y, second_pos.y); + let end_y = i32::max(first_pos.y, second_pos.y); + + let start_z = i32::min(first_pos.z, second_pos.z); + let end_z = i32::max(first_pos.z, second_pos.z); + + // Iterate over chunks + for chunk_start_x in (start_x..=end_x).step_by(16) { + for chunk_start_z in (start_z..=end_z).step_by(16) { + for chunk_start_y in (start_y..=end_y).step_by(16) { + // Check if the chunk even has non air blocks + if world + .get_chunk(chunk_start_x.div_euclid(16), chunk_start_z.div_euclid(16)) + .unwrap() + .sections[chunk_start_y as usize / 16] + .block_count() + > 0 + { + // Calculate the end position of the current chunk + let chunk_end_x = i32::min(chunk_start_x + 16 - 1, end_x); + let chunk_end_y = i32::min(chunk_start_y + 16 - 1, end_y); + let chunk_end_z = i32::min(chunk_start_z + 16 - 1, end_z); + + // Iterate over each position within the current chunk + for y in chunk_start_y..=chunk_end_y { + for z in chunk_start_z..=chunk_end_z { + for x in chunk_start_x..=chunk_end_x { + let pos = BlockPos::new(x, y, z); + f(world, pos); + } + } + } + } + } + } + } +} diff --git a/crates/core/src/world/storage.rs b/crates/core/src/world/storage.rs index d4d6b06a..72179bbd 100644 --- a/crates/core/src/world/storage.rs +++ b/crates/core/src/world/storage.rs @@ -386,6 +386,10 @@ impl ChunkSection { } &self.multi_block } + + pub fn block_count(&self) -> u32 { + self.block_count + } } impl Default for ChunkSection { @@ -416,7 +420,8 @@ impl Chunk { pub fn encode_packet(&self) -> PacketEncoder { // Integer arithmetic trick: ceil(log2(x)) can be calculated with 32 - (x - 1).leading_zeros(). // See also: https://wiki.vg/Protocol#Chunk_Data_and_Update_Light - const HEIGHTMAP_BITS: u8 = (32 - ((PLOT_BLOCK_HEIGHT as u32 + 1) - 1).leading_zeros()) as u8; + const HEIGHTMAP_BITS: u8 = + (32 - ((PLOT_BLOCK_HEIGHT as u32 + 1) - 1).leading_zeros()) as u8; let mut heightmap_buffer = BitBuffer::create(HEIGHTMAP_BITS, 16 * 16); for x in 0..16 { for z in 0..16 {