From cf671ad7ff74fc932417f20fa27515e908029dd0 Mon Sep 17 00:00:00 2001 From: NotMyFault Date: Sun, 22 Aug 2021 11:56:39 +0200 Subject: [PATCH] Update Upstream ed28089 Don't crash if fields are null in ChunkDeleter (1874) f049d56 Revert "Use a Guava Cache instead of a ThreadLocal (1859)" c5a4450 Internally use a negated mask class to prevent russian doll wrapping (1877) 1397ec7 Add Snow Smooth Tools (1580) Fixes #955 Fixes #858 --- .../worldedit/command/BrushCommands.java | 50 +++- .../worldedit/command/RegionCommands.java | 32 ++- .../command/tool/brush/SnowSmoothBrush.java | 69 ++++++ .../sk89q/worldedit/function/mask/Mask.java | 2 + .../sk89q/worldedit/function/mask/Masks.java | 70 +++++- .../internal/anvil/ChunkDeleter.java | 2 +- .../worldedit/math/convolution/HeightMap.java | 4 +- .../math/convolution/HeightMapFilter.java | 123 +++++++--- .../math/convolution/SnowHeightMap.java | 218 ++++++++++++++++++ .../worldedit/session/request/Request.java | 27 ++- .../src/main/resources/lang/strings.json | 6 + 11 files changed, 540 insertions(+), 63 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/SnowSmoothBrush.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/SnowHeightMap.java diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index 6d0b7ee8b..47440343b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -79,6 +79,7 @@ import com.sk89q.worldedit.command.tool.brush.HollowCylinderBrush; import com.sk89q.worldedit.command.tool.brush.HollowSphereBrush; import com.sk89q.worldedit.command.tool.brush.OperationFactoryBrush; import com.sk89q.worldedit.command.tool.brush.SmoothBrush; +import com.sk89q.worldedit.command.tool.brush.SnowSmoothBrush; import com.sk89q.worldedit.command.tool.brush.SphereBrush; import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; @@ -1292,20 +1293,65 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.smooth") public void smoothBrush( Player player, LocalSession session, + //FAWE start - Expression > double @Arg(desc = "The radius to sample for softening", def = "2") Expression radius, + //FAWE end @Arg(desc = "The number of iterations to perform", def = "4") int iterations, @Arg(desc = "The mask of blocks to use for the heightmap", def = "") - Mask maskOpt, + Mask mask, InjectedValueAccess context ) throws WorldEditException { worldEdit.checkMaxBrushRadius(radius); + //FAWE start FaweLimit limit = Settings.IMP.getLimit(player); iterations = Math.min(limit.MAX_ITERATIONS, iterations); + //FAWE end - set(context, new SmoothBrush(iterations, maskOpt)).setSize(radius); + set(context, new SmoothBrush(iterations, mask)).setSize(radius); + player.print(Caption.of( + "worldedit.brush.smooth.equip", + radius, + iterations, + Caption.of("worldedit.brush.smooth." + (mask == null ? "no" : "") + "filter") + )); + } + + @Command( + name = "snowsmooth", + desc = "Choose the snow terrain softener brush", + descFooter = "Example: '/brush snowsmooth 5 1 -l 3'" + ) + @CommandPermissions("worldedit.brush.snowsmooth") + public void snowSmoothBrush( + Player player, LocalSession session, + //FAWE start - Expression > double + @Arg(desc = "The radius to sample for softening", def = "2") + Expression radius, + //FAWE end + @Arg(desc = "The number of iterations to perform", def = "4") + int iterations, + @ArgFlag(name = 'l', desc = "The number of snow blocks under snow", def = "1") + int snowBlockCount, + @ArgFlag(name = 'm', desc = "The mask of blocks to use for the heightmap") + Mask mask, InjectedValueAccess context + ) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + + //FAWE start + FaweLimit limit = Settings.IMP.getLimit(player); + iterations = Math.min(limit.MAX_ITERATIONS, iterations); + //FAWE end + + set(context, new SnowSmoothBrush(iterations, mask)).setSize(radius); + player.print(Caption.of( + "worldedit.brush.smooth.equip", + radius, + iterations, + Caption.of("worldedit.brush.smooth." + (mask == null ? "no" : "") + "filter") + )); } @Command( diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index b51e82113..1a72af85b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -55,6 +55,7 @@ import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.math.convolution.GaussianKernel; import com.sk89q.worldedit.math.convolution.HeightMap; import com.sk89q.worldedit.math.convolution.HeightMapFilter; +import com.sk89q.worldedit.math.convolution.SnowHeightMap; import com.sk89q.worldedit.math.noise.RandomNoise; import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; import com.sk89q.worldedit.regions.CuboidRegion; @@ -466,9 +467,7 @@ public class RegionCommands { @Arg(desc = "# of iterations to perform", def = "1") int iterations, @Arg(desc = "The mask of blocks to use as the height map", def = "") - Mask mask, - @Switch(name = 's', desc = "The flag makes it only consider snow") - boolean snow + Mask mask ) throws WorldEditException { //FAWE start > the mask will have been initialised with a WorldWrapper extent (very bad/slow) if (mask instanceof AbstractExtentMask) { @@ -485,7 +484,7 @@ public class RegionCommands { } int affected; try { - HeightMap heightMap = new HeightMap(editSession, region, mask, snow); + HeightMap heightMap = new HeightMap(editSession, region, mask); HeightMapFilter filter = new HeightMapFilter(new GaussianKernel(5, 1.0)); affected = heightMap.applyFilter(filter, iterations); actor.print(Caption.of("worldedit.smooth.changed", TextComponent.of(affected))); @@ -527,6 +526,31 @@ public class RegionCommands { } } + @Command( + name = "/snowsmooth", + desc = "Smooth the elevation in the selection with snow layers", + descFooter = "Example: '//snowsmooth 1 -m snow_block,snow' would only smooth snow terrain." + ) + @CommandPermissions("worldedit.region.snowsmooth") + @Logging(REGION) + @Preload(Preload.PreloadCheck.PRELOAD) + @Confirm(Confirm.Processor.REGION) + public int snowSmooth( + Actor actor, EditSession editSession, @Selection Region region, + @Arg(desc = "# of iterations to perform", def = "1") + int iterations, + @ArgFlag(name = 'l', desc = "Set the amount of snow blocks under the snow", def = "1") + int snowBlockCount, + @ArgFlag(name = 'm', desc = "The mask of blocks to use as the height map") + Mask mask + ) throws WorldEditException { + SnowHeightMap heightMap = new SnowHeightMap(editSession, region, mask); + HeightMapFilter filter = new HeightMapFilter(new GaussianKernel(5, 1.0)); + float[] changed = heightMap.applyFilter(filter, iterations); + int affected = heightMap.applyChanges(changed, snowBlockCount); + actor.printInfo(Caption.of("worldedit.snowsmooth.changed", TextComponent.of(affected))); + return affected; + } @Command( name = "/move", diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/SnowSmoothBrush.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/SnowSmoothBrush.java new file mode 100644 index 000000000..5a256afe6 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/SnowSmoothBrush.java @@ -0,0 +1,69 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.command.tool.brush; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.math.convolution.GaussianKernel; +import com.sk89q.worldedit.math.convolution.HeightMapFilter; +import com.sk89q.worldedit.math.convolution.SnowHeightMap; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; + +import javax.annotation.Nullable; + +public class SnowSmoothBrush implements Brush { + + private final Mask mask; + private final int iterations; + private final int snowBlockLayer; + + public SnowSmoothBrush(int iterations) { + this(iterations, null); + } + + public SnowSmoothBrush(int iterations, @Nullable Mask mask) { + this(iterations, 1, mask); + } + + public SnowSmoothBrush(int iterations, int snowBlockLayer, @Nullable Mask mask) { + this.iterations = iterations; + this.mask = mask; + this.snowBlockLayer = snowBlockLayer; + } + + @Override + public void build(EditSession editSession, BlockVector3 position, Pattern pattern, double size) throws + MaxChangedBlocksException { + Vector3 posDouble = position.toVector3(); + BlockVector3 min = posDouble.subtract(size, size, size).toBlockPoint(); + BlockVector3 max = posDouble.add(size, size + 10, size).toBlockPoint(); + Region region = new CuboidRegion(editSession.getWorld(), min, max); + SnowHeightMap heightMap = new SnowHeightMap(editSession, region, mask); + HeightMapFilter filter = new HeightMapFilter(new GaussianKernel(10, 1.0)); + float[] data = heightMap.applyFilter(filter, iterations); + heightMap.applyChanges(data, snowBlockLayer); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/Mask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/Mask.java index 86d76cf5d..3c4cb1378 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/Mask.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/Mask.java @@ -86,6 +86,8 @@ public interface Mask { return Masks.ALWAYS_FALSE; } else if (this instanceof Masks.AlwaysFalse) { return Masks.ALWAYS_TRUE; + } else if (this instanceof Masks.NegatedMask) { + return ((Masks.NegatedMask) this).mask; } return new InverseMask(this); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/Masks.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/Masks.java index cf1ee8321..1c5fcb9fc 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/Masks.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/Masks.java @@ -61,6 +61,7 @@ public final class Masks { return ALWAYS_TRUE; } + //FAWE start /** * Negate the given mask. * @@ -70,6 +71,7 @@ public final class Masks { public static Mask negate(final Mask mask) { return mask.inverse(); } + //FAWE end /** * Negate the given mask. @@ -82,20 +84,12 @@ public final class Masks { return ALWAYS_FALSE; } else if (mask instanceof AlwaysFalse) { return ALWAYS_TRUE; + } else if (mask instanceof NegatedMask2D) { + return ((NegatedMask2D) mask).mask; } checkNotNull(mask); - return new AbstractMask2D() { - @Override - public boolean test(BlockVector2 vector) { - return !mask.test(vector); - } - - @Override - public Mask2D copy2D() { - return Masks.negate(mask.copy2D()); - } - }; + return new NegatedMask2D(mask); } /** @@ -216,4 +210,58 @@ public final class Masks { } + //FAWE start - protected > private + protected static class NegatedMask implements Mask { + //FAWE end + protected final Mask mask; + + private NegatedMask(Mask mask) { + this.mask = mask; + } + + @Override + public boolean test(BlockVector3 vector) { + return !mask.test(vector); + } + + @Nullable + @Override + public Mask2D toMask2D() { + Mask2D mask2D = mask.toMask2D(); + if (mask2D == null) { + return null; + } + return negate(mask2D); + } + + //FAWE start + @Override + public Mask copy() { + return Masks.negate(mask.copy()); + } + //FAWE end + + } + + private static class NegatedMask2D implements Mask2D { + private final Mask2D mask; + + private NegatedMask2D(Mask2D mask) { + this.mask = mask; + } + + @Override + public boolean test(BlockVector2 vector) { + return !mask.test(vector); + } + + //FAWE start + @Override + public Mask2D copy2D() { + return Masks.negate(mask.copy2D()); + } + //FAWE end + + } + } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/anvil/ChunkDeleter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/anvil/ChunkDeleter.java index ddf12e521..d2cdf50c0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/anvil/ChunkDeleter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/anvil/ChunkDeleter.java @@ -58,7 +58,7 @@ public final class ChunkDeleter { ); private static final Gson chunkDeleterGson = new GsonBuilder() - .registerTypeAdapter(BlockVector2.class, new BlockVector2Adapter()) + .registerTypeAdapter(BlockVector2.class, new BlockVector2Adapter().nullSafe()) .setPrettyPrinting() .create(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java index fc8ada868..d77716954 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java @@ -180,7 +180,7 @@ public class HeightMap { System.arraycopy(data, 0, newData, 0, data.length); for (int i = 0; i < iterations; ++i) { - newData = filter.filter(newData, width, height); + newData = filter.filter(newData, width, height, 0.5F); } //FAWE start - check layers @@ -229,7 +229,7 @@ public class HeightMap { BlockStateHolder existing = session.getBlock(xr, curBlock, zr); // Skip water/lava - if (existing.getBlockType().getMaterial().isMovementBlocker()) { + if (!existing.getBlockType().getMaterial().isLiquid()) { // Grow -- start from 1 below top replacing airblocks for (int setY = newBlock - 1, getY = curBlock; setY >= curBlock; --setY, getY--) { BlockStateHolder get = session.getBlock(xr, getY, zr); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMapFilter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMapFilter.java index 1e693facd..9015a17ac 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMapFilter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMapFilter.java @@ -79,50 +79,107 @@ public class HeightMapFilter { * @return the modified height map */ public int[] filter(int[] inData, int width, int height) { + return filter(inData, width, height, 0.5F); + } + + /** + * Filter with a 2D kernel. + * + * @param inData the data + * @param width the width + * @param height the height + * @param offset the offset added to the height + * @return the modified height map + */ + public int[] filter(int[] inData, int width, int height, float offset) { checkNotNull(inData); + float[] inDataFloat = new float[inData.length]; + for (int i = 0; i < inData.length; i++) { + inDataFloat[i] = inData[i]; + } + int index = 0; float[] matrix = kernel.getKernelData(null); int[] outData = new int[inData.length]; - int kh = kernel.getHeight(); - int kw = kernel.getWidth(); - int kox = kernel.getXOrigin(); - int koy = kernel.getYOrigin(); - for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { - float z = 0; - - for (int ky = 0; ky < kh; ++ky) { - int offsetY = y + ky - koy; - // Clamp coordinates inside data - if (offsetY < 0 || offsetY >= height) { - offsetY = y; - } - - offsetY *= width; - - int matrixOffset = ky * kw; - for (int kx = 0; kx < kw; ++kx) { - float f = matrix[matrixOffset + kx]; - if (f == 0) { - continue; - } - - int offsetX = x + kx - kox; - // Clamp coordinates inside data - if (offsetX < 0 || offsetX >= width) { - offsetX = x; - } - - z += f * inData[offsetY + offsetX]; - } - } - outData[index++] = (int) (z + 0.5); + outData[index++] = (int) calculateHeight(inDataFloat, width, height, offset, matrix, x, y); } } return outData; } + /** + * Filter with a 2D kernel for float values. + * + * @param inData the data + * @param width the width + * @param height the height + * @return the modified height map + */ + public float[] filter(float[] inData, int width, int height, float offset) { + checkNotNull(inData); + + int index = 0; + float[] matrix = kernel.getKernelData(null); + float[] outData = new float[inData.length]; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + outData[index++] = calculateHeight(inData, width, height, offset, matrix, x, y); + } + } + return outData; + } + + /** + * Calculate the height based on the existing data and the kernel data. + * + * @param inData the existing height data + * @param width the width + * @param height the height + * @param offset the offset to add to the height + * @param matrix the matrix with the kernel's data + * @param x the current x coordinate + * @param y the current y coordinate + * @return the calculated height for that block + */ + private float calculateHeight(float[] inData, int width, int height, float offset, float[] matrix, int x, int y) { + int kh = kernel.getHeight(); + int kw = kernel.getWidth(); + int kox = kernel.getXOrigin(); + int koy = kernel.getYOrigin(); + + float z = 0; + + for (int ky = 0; ky < kh; ++ky) { + int offsetY = y + ky - koy; + // Clamp coordinates inside data + if (offsetY < 0 || offsetY >= height) { + offsetY = y; + } + + offsetY *= width; + + int matrixOffset = ky * kw; + for (int kx = 0; kx < kw; ++kx) { + float f = matrix[matrixOffset + kx]; + if (f == 0) { + continue; + } + + int offsetX = x + kx - kox; + // Clamp coordinates inside data + if (offsetX < 0 || offsetX >= width) { + offsetX = x; + } + + z += f * inData[offsetY + offsetX]; + } + } + return z + offset; + } + } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/SnowHeightMap.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/SnowHeightMap.java new file mode 100644 index 000000000..d31101d10 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/convolution/SnowHeightMap.java @@ -0,0 +1,218 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.math.convolution; + +import com.google.common.collect.ImmutableMap; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Allows applications of Kernels onto the region's height map for snow layers + * + *

Currently only used for snow layer smoothing (with a GaussianKernel)

. + */ +public class SnowHeightMap { + + private final float[] data; + private final int width; + private final int height; + + private final Region region; + private final EditSession session; + + private final Property layers; + + /** + * Constructs the SnowHeightMap. + * + * @param session an edit session + * @param region the region + * @param mask optional mask for the height map + */ + public SnowHeightMap(EditSession session, Region region, @Nullable Mask mask) { + checkNotNull(session); + checkNotNull(region); + + this.session = session; + this.region = region; + + this.width = region.getWidth(); + this.height = region.getLength(); + + this.layers = BlockTypes.SNOW.getProperty("layers"); + + int minX = region.getMinimumPoint().getBlockX(); + int minY = region.getMinimumPoint().getBlockY(); + int minZ = region.getMinimumPoint().getBlockZ(); + int maxY = region.getMaximumPoint().getBlockY(); + + // Store current heightmap data + data = new float[width * height]; + for (int z = 0; z < height; ++z) { + for (int x = 0; x < width; ++x) { + int highestBlockY = session.getHighestTerrainBlock(x + minX, z + minZ, minY, maxY, mask); + BlockState upper = session.getBlock(BlockVector3.at(x + minX, highestBlockY + 1, z + minZ)); + if (upper.getBlockType() == BlockTypes.SNOW) { + Integer amountLayers = upper.getState(layers); + data[z * width + x] = (highestBlockY + 1 + (((float) amountLayers - 1) / 8)); + } else { + BlockState block = session.getBlock(BlockVector3.at(x + minX, highestBlockY, z + minZ)); + if (block.getBlockType().getMaterial().isAir()) { + data[z * width + x] = highestBlockY; + } else { + data[z * width + x] = highestBlockY + 1; + } + } + } + } + } + + /** + * Compute the new heightmap with the filter 'iterations' amount times. + * + * @param filter the filter + * @param iterations the number of iterations + * @return new generated heightmap of the terrain + */ + public float[] applyFilter(HeightMapFilter filter, int iterations) { + checkNotNull(filter); + + float[] newData = data.clone(); + + for (int i = 0; i < iterations; ++i) { + // add an offset from 0.0625F to the values (snowlayer half) + newData = filter.filter(newData, width, height, 0.0625F); + } + return newData; + } + + /** + * Apply a raw heightmap to a region. Use snow layers. + * + * @param data the data + * @param layerBlocks amount of blocks with type SNOW_BLOCK + * @return number of blocks affected + * @throws MaxChangedBlocksException if the maximum block change limit is exceeded + */ + public int applyChanges(float[] data, int layerBlocks) throws MaxChangedBlocksException { + checkNotNull(data); + + BlockVector3 minY = region.getMinimumPoint(); + int originX = minY.getBlockX(); + int originY = minY.getBlockY(); + int originZ = minY.getBlockZ(); + + int maxY = region.getMaximumPoint().getBlockY(); + + BlockState fillerAir = BlockTypes.AIR.getDefaultState(); + BlockState fillerSnow = BlockTypes.SNOW_BLOCK.getDefaultState(); + + int blocksChanged = 0; + + // Apply heightmap + for (int z = 0; z < height; ++z) { + for (int x = 0; x < width; ++x) { + int index = z * width + x; + float curHeight = this.data[index]; + + if (curHeight == originY) { + continue; + } + + // Clamp newHeight within the selection area + float newHeight = Math.min(maxY, data[index]); + + // Offset x,z to be 'real' coordinates + int xr = x + originX; + int zr = z + originZ; + + // We are keeping the topmost blocks so take that in account for the scale + double scale = (double) (curHeight - originY) / (double) (newHeight - originY); + + // Depending on growing or shrinking we need to start at the bottom or top + if (newHeight >= curHeight) { + // Set the top block of the column to be the same type (this might go wrong with rounding) + BlockState existing = session.getBlock(BlockVector3.at(xr, curHeight, zr)); + + // Skip water/lava + if (!existing.getBlockType().getMaterial().isLiquid()) { + setSnowLayer(xr, zr, newHeight); + ++blocksChanged; + + // Grow -- start from 1 below top replacing airblocks + for (int y = (int) newHeight - 1 - originY; y >= 0; --y) { + if (y >= newHeight - 1 - originY - layerBlocks) { + session.setBlock(BlockVector3.at(xr, originY + y, zr), fillerSnow); + } else { + int copyFrom = (int) (y * scale); + BlockState block = session.getBlock(BlockVector3.at(xr, originY + copyFrom, zr)); + session.setBlock(BlockVector3.at(xr, originY + y, zr), block); + } + ++blocksChanged; + } + } + } else { + // Shrink -- start from bottom + for (int y = 0; y < (int) newHeight - originY; ++y) { + if (y >= (int) newHeight - originY - layerBlocks) { + session.setBlock(BlockVector3.at(xr, originY + y, zr), fillerSnow); + } else { + int copyFrom = (int) (y * scale); + BlockState block = session.getBlock(BlockVector3.at(xr, originY + copyFrom, zr)); + session.setBlock(BlockVector3.at(xr, originY + y, zr), block); + } + ++blocksChanged; + } + + setSnowLayer(xr, zr, newHeight); + ++blocksChanged; + + // Fill rest with air + for (int y = (int) newHeight + 1; y <= curHeight; ++y) { + session.setBlock(BlockVector3.at(xr, y, zr), fillerAir); + ++blocksChanged; + } + } + } + } + + // Drop trees to the floor -- TODO + return blocksChanged; + } + + private void setSnowLayer(int x, int z, float newHeight) throws MaxChangedBlocksException { + int numOfLayers = (int) ((newHeight % 1) * 8) + 1; + session.setBlock( + BlockVector3.at(x, (int) newHeight, z), + BlockTypes.SNOW.getState(ImmutableMap.of(this.layers, numOfLayers)) + ); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/session/request/Request.java b/worldedit-core/src/main/java/com/sk89q/worldedit/session/request/Request.java index c933b2e95..489caf5fc 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/session/request/Request.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/session/request/Request.java @@ -19,9 +19,6 @@ package com.sk89q.worldedit.session.request; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.extension.platform.Actor; @@ -30,15 +27,19 @@ import com.sk89q.worldedit.world.World; import javax.annotation.Nullable; import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** - * Describes the current request + * Describes the current request using a {@link ThreadLocal}. */ public final class Request { - private static final LoadingCache THREAD_TO_REQUEST = CacheBuilder.newBuilder() - .weakKeys() - .build(CacheLoader.from(Request::new)); + private static final ThreadLocal threadLocal = ThreadLocal.withInitial(Request::new); + //FAWE start + // TODO any better way to deal with this? + private static final Map requests = new ConcurrentHashMap<>(); + //FAWE end @Nullable private World world; @@ -55,11 +56,14 @@ public final class Request { //FAWE end private Request() { + //FAWE start + requests.put(Thread.currentThread(), this); + //FAWE end } //FAWE start public static Collection getAll() { - return THREAD_TO_REQUEST.asMap().values(); + return requests.values(); } //FAWE end @@ -158,7 +162,7 @@ public final class Request { * @return the current request */ public static Request request() { - return THREAD_TO_REQUEST.getUnchecked(Thread.currentThread()); + return threadLocal.get(); } /** @@ -166,7 +170,10 @@ public final class Request { */ public static void reset() { request().invalidate(); - THREAD_TO_REQUEST.invalidate(Thread.currentThread()); + threadLocal.remove(); + //FAWE start + requests.remove(Thread.currentThread()); + //FAWE end } /** diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index 303b6124b..ebe6b1b8d 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -283,6 +283,11 @@ "worldedit.brush.cylinder.equip": "Cylinder brush shape equipped ({0} by {1}).", "worldedit.brush.clipboard.equip": "Clipboard brush shape equipped.", "worldedit.brush.smooth.equip": "Smooth brush equipped ({0} x {1}x using {2}).", + "worldedit.brush.smooth.nofilter": "any block", + "worldedit.brush.smooth.filter": "filter", + "worldedit.brush.snowsmooth.equip": "SnowSmooth brush equipped ({0} x {1}x using {2}), {3} snow blocks.", + "worldedit.brush.snowsmooth.nofilter": "any block", + "worldedit.brush.snowsmooth.filter": "filter", "worldedit.brush.extinguish.equip": "Extinguisher equipped ({0}).", "worldedit.brush.gravity.equip": "Gravity brush equipped ({0}).", "worldedit.brush.butcher.equip": "Butcher brush equipped ({0}).", @@ -482,6 +487,7 @@ "worldedit.naturalize.naturalized": "{0} block(s) have been made to look more natural.", "worldedit.center.changed": "Center set. ({0} blocks changed)", "worldedit.smooth.changed": "Terrain's height map smoothed. {0} blocks changed.", + "worldedit.snowsmooth.changed": "Snow's height map smoothed. {0} blocks changed.", "worldedit.move.moved": "{0} blocks moved.", "worldedit.deform.deformed": "{0} blocks have been deformed.", "worldedit.hollow.changed": "{0} blocks have been changed.",