Remove hardcoding of world limits (#1199)

* Remove hardcoding of world limits
 - seems to be working fine without the datapack for world height changing
 - particular attention should be given to LocalBlockVectorSet and MathMan changes

* update adapters

* Override getMinY in various classes and ensure selections have a world attached to them

* no message

* Address comments
 - Fix for lighting mode 1

* A few more changes

* Fix LocalBlockVectorSet

* Fix range statement

* Various fixes/comment-addressing
- There's not much point in having a different file name now for history. We've broken it before...
- Fix history read/write
- Fix range on for loops in CharBlocks

* undo bad CharBlocks change

* Fix history y level

* Fix biome history

* Fix lighting

* Fix /up

* Make regen fail not because of these changes

* Fixes for y < 0

* Fix isEmpty where only the uppermost chunksection is edited

* Fix javadocs/FAWE annotations

* Better explain why BiomeMath is removed

* If history task throws an error, it should only be caught and printed if not completing now.

* Min|max world heights for new patterns

* Load biomes from NMS instead of bukkit (#1200)

* Update adapters

* Update adapters

* Don't initialise BlockTypes when biomes aren't set up yet so all BiomeTypes.BIOME are no longer null thanks.

* Address some comments.

* rename layer -> sectionIndex to imply inclusivity

* Javadoctored.

Co-authored-by: NotMyFault <mc.cache@web.de>
Co-authored-by: Hannes Greule <SirYwell@users.noreply.github.com>
This commit is contained in:
dordsor21 2021-08-17 22:13:51 +01:00 committed by GitHub
parent 5b2bd45d86
commit 1d9b1a3d5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
110 changed files with 1489 additions and 677 deletions

View File

@ -266,8 +266,8 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
}); });
} }
source = new SingleThreadQueueExtent(); source = new SingleThreadQueueExtent(originalBukkitWorld.getMinHeight(), originalBukkitWorld.getMaxHeight());
source.init(null, initSourceQueueCache(), null); source.init(target, initSourceQueueCache(), null);
return true; return true;
} }

View File

@ -11,6 +11,7 @@ import javax.annotation.Nonnull;
public class MinecraftVersion implements Comparable<MinecraftVersion> { public class MinecraftVersion implements Comparable<MinecraftVersion> {
public static final MinecraftVersion NETHER = new MinecraftVersion(1, 16); public static final MinecraftVersion NETHER = new MinecraftVersion(1, 16);
public static final MinecraftVersion CAVES_17 = new MinecraftVersion(1, 17);
private final int major; private final int major;
private final int minor; private final int minor;

View File

@ -49,6 +49,7 @@ import com.sk89q.worldedit.util.lifecycle.Lifecycled;
import com.sk89q.worldedit.util.lifecycle.SimpleLifecycled; import com.sk89q.worldedit.util.lifecycle.SimpleLifecycled;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockCategory; import com.sk89q.worldedit.world.block.BlockCategory;
import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.entity.EntityType;
import com.sk89q.worldedit.world.gamemode.GameModes; import com.sk89q.worldedit.world.gamemode.GameModes;
@ -59,6 +60,7 @@ import org.apache.logging.log4j.Logger;
import org.bstats.bukkit.Metrics; import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag; import org.bukkit.Tag;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.command.BlockCommandSender; import org.bukkit.command.BlockCommandSender;
@ -234,17 +236,18 @@ public class WorldEditPlugin extends JavaPlugin {
// datapacks aren't loaded until just before the world is, and bukkit has no event for this // datapacks aren't loaded until just before the world is, and bukkit has no event for this
// so the earliest we can do this is in WorldInit // so the earliest we can do this is in WorldInit
setupTags(); setupTags();
setupBiomes(); // FAWE - load biomes later
WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform)); WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform));
} }
@SuppressWarnings({"deprecation", "unchecked"}) @SuppressWarnings({"deprecation", "unchecked"})
private void initializeRegistries() { private void initializeRegistries() {
// Biome /* // FAWE start - move Biomes to their own method
for (Biome biome : Biome.values()) { for (Biome biome : Biome.values()) {
String lowerCaseBiomeName = biome.name().toLowerCase(Locale.ROOT); String lowerCaseBiomeName = biome.name().toLowerCase(Locale.ROOT);
BiomeType.REGISTRY.register("minecraft:" + lowerCaseBiomeName, new BiomeType("minecraft:" + lowerCaseBiomeName)); BiomeType.REGISTRY.register("minecraft:" + lowerCaseBiomeName, new BiomeType("minecraft:" + lowerCaseBiomeName));
} }
/* // FAWE end
// Block & Item // Block & Item
for (Material material : Material.values()) { for (Material material : Material.values()) {
@ -304,6 +307,30 @@ public class WorldEditPlugin extends JavaPlugin {
"The version of Spigot/Paper you are using doesn't support Tags. The usage of tags with WorldEdit will not work until you update."); "The version of Spigot/Paper you are using doesn't support Tags. The usage of tags with WorldEdit will not work until you update.");
} }
} }
// FAWE start
private void setupBiomes() {
if (this.adapter.value().isPresent()) {
// We don't know which world is the one with the data packs
// so we just loop over them. Doesn't hurt
for (org.bukkit.World world : Bukkit.getWorlds()) {
// cast is needed, thanks to raw types <3
for (final NamespacedKey biome : ((BukkitImplAdapter<?>) adapter.value().get()).getRegisteredBiomes(world)) {
if (BiomeType.REGISTRY.get(biome.toString()) == null) { // only register once
BiomeType.REGISTRY.register(biome.toString(), new BiomeType(biome.toString()));
}
}
}
} else {
LOGGER.warn("Failed to load biomes via adapter (not present). Will load via bukkit");
for (Biome biome : Biome.values()) {
if (BiomeType.REGISTRY.get(biome.toString()) == null) { // only register once
BiomeType.REGISTRY.register(biome.getKey().toString(), new BiomeType(biome.getKey().toString()));
}
}
}
}
// FAWE end
private void loadAdapter() { private void loadAdapter() {
WorldEdit worldEdit = WorldEdit.getInstance(); WorldEdit worldEdit = WorldEdit.getInstance();

View File

@ -50,7 +50,9 @@ import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.item.ItemType;
import com.sk89q.worldedit.world.registry.BlockMaterial; import com.sk89q.worldedit.world.registry.BlockMaterial;
import org.bukkit.Keyed;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.WorldCreator; import org.bukkit.WorldCreator;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
@ -60,9 +62,11 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.OptionalInt; import java.util.OptionalInt;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
/** /**
* An interface for adapters of various Bukkit implementations. * An interface for adapters of various Bukkit implementations.
@ -304,6 +308,18 @@ public interface BukkitImplAdapter<T> extends IBukkitAdapter {
return Biome.BADLANDS.ordinal(); return Biome.BADLANDS.ordinal();
} }
/**
* Returns an iterable of all biomes known to the server.
*
* @param world the world to load the registered biomes from.
* @return all biomes known to the server.
*/
default Iterable<NamespacedKey> getRegisteredBiomes(World world) {
return Arrays.stream(Biome.values())
.map(Keyed::getKey)
.collect(Collectors.toList());
}
default RelighterFactory getRelighterFactory() { default RelighterFactory getRelighterFactory() {
return new NMSRelighterFactory(); // TODO implement in adapters instead return new NMSRelighterFactory(); // TODO implement in adapters instead
} }

View File

@ -270,17 +270,22 @@ public class FaweAPI {
RegionWrapper bounds = new RegionWrapper( RegionWrapper bounds = new RegionWrapper(
origin.getBlockX() - radius, origin.getBlockX() - radius,
origin.getBlockX() + radius, origin.getBlockX() + radius,
extent.getMinY(),
extent.getMaxY(),
origin.getBlockZ() - radius, origin.getBlockZ() - radius,
origin.getBlockZ() + radius origin.getBlockZ() + radius
); );
RegionWrapper boundsPlus = new RegionWrapper(bounds.minX - 64, bounds.maxX + 512, bounds.minZ - 64, bounds.maxZ + 512); RegionWrapper boundsPlus = new RegionWrapper(bounds.minX - 64, bounds.maxX + 512, bounds.minY, bounds.maxY,
bounds.minZ - 64,
bounds.maxZ + 512);
HashSet<RegionWrapper> regionSet = Sets.<RegionWrapper>newHashSet(bounds); HashSet<RegionWrapper> regionSet = Sets.<RegionWrapper>newHashSet(bounds);
ArrayList<DiskStorageHistory> result = new ArrayList<>(); ArrayList<DiskStorageHistory> result = new ArrayList<>();
for (File file : files) { for (File file : files) {
UUID uuid = UUID.fromString(file.getParentFile().getName()); UUID uuid = UUID.fromString(file.getParentFile().getName());
DiskStorageHistory dsh = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0])); DiskStorageHistory dsh = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0]));
SimpleChangeSetSummary summary = dsh.summarize(boundsPlus, shallow); SimpleChangeSetSummary summary = dsh.summarize(boundsPlus, shallow);
RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, summary.minZ, summary.maxZ); RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, extent.getMinY(), extent.getMaxY(), summary.minZ,
summary.maxZ);
boolean encompassed = false; boolean encompassed = false;
boolean isIn = false; boolean isIn = false;
for (RegionWrapper allowed : regionSet) { for (RegionWrapper allowed : regionSet) {

View File

@ -64,9 +64,6 @@ public enum FaweCache implements Trimable {
private static final Logger LOGGER = LogManagerCompat.getLogger(); private static final Logger LOGGER = LogManagerCompat.getLogger();
public final int BLOCKS_PER_LAYER = 4096; public final int BLOCKS_PER_LAYER = 4096;
public final int CHUNK_LAYERS = 16;
public final int WORLD_HEIGHT = CHUNK_LAYERS << 4;
public final int WORLD_MAX_Y = WORLD_HEIGHT - 1;
public final char[] EMPTY_CHAR_4096 = new char[4096]; public final char[] EMPTY_CHAR_4096 = new char[4096];

View File

@ -24,7 +24,8 @@ public class BlendBall implements Brush {
int[] frequency = new int[BlockTypes.size()]; int[] frequency = new int[BlockTypes.size()];
int maxY = editSession.getMaximumPoint().getBlockY(); int maxY = editSession.getMaxY();
int minY = editSession.getMinY();
for (int x = -outsetSize; x <= outsetSize; x++) { for (int x = -outsetSize; x <= outsetSize; x++) {
int x0 = x + tx; int x0 = x + tx;
@ -43,7 +44,7 @@ public class BlendBall implements Brush {
for (int ox = -1; ox <= 1; ox++) { for (int ox = -1; ox <= 1; ox++) {
for (int oz = -1; oz <= 1; oz++) { for (int oz = -1; oz <= 1; oz++) {
for (int oy = -1; oy <= 1; oy++) { for (int oy = -1; oy <= 1; oy++) {
if (oy + y0 < 0 || oy + y0 > maxY) { if (oy + y0 < minY || oy + y0 > maxY) {
continue; continue;
} }
BlockState state = editSession.getBlock(x0 + ox, y0 + oy, z0 + oz); BlockState state = editSession.getBlock(x0 + ox, y0 + oy, z0 + oz);

View File

@ -41,7 +41,8 @@ public class CommandBrush implements Brush {
.replace("{size}", Integer.toString(radius)); .replace("{size}", Integer.toString(radius));
Player player = editSession.getPlayer(); Player player = editSession.getPlayer();
Location face = player.getBlockTraceFace(256, true); //Use max world height to allow full coverage of the world height
Location face = player.getBlockTraceFace(editSession.getWorld().getMaxY(), true);
if (face == null) { if (face == null) {
position = position.add(0, 1, 1); position = position.add(0, 1, 1);
} else { } else {

View File

@ -78,7 +78,8 @@ public class CopyPastaBrush implements Brush, ResettableTool {
}; };
// Add origin // Add origin
mask.test(position); mask.test(position);
RecursiveVisitor visitor = new RecursiveVisitor(mask, new NullRegionFunction(), (int) size); RecursiveVisitor visitor = new RecursiveVisitor(mask, new NullRegionFunction(), (int) size, editSession.getMinY(),
editSession.getMaxY());
visitor.visit(position); visitor.visit(position);
Operations.completeBlindly(visitor); Operations.completeBlindly(visitor);
// Build the clipboard // Build the clipboard

View File

@ -16,6 +16,7 @@ public class FallingSphere implements Brush {
int py = position.getBlockY(); int py = position.getBlockY();
int pz = position.getBlockZ(); int pz = position.getBlockZ();
int maxY = editSession.getMaxY(); int maxY = editSession.getMaxY();
int minY = editSession.getMinY();
int radius = (int) Math.round(size); int radius = (int) Math.round(size);
int radiusSqr = (int) Math.round(size * size); int radiusSqr = (int) Math.round(size * size);
@ -37,10 +38,10 @@ public class FallingSphere implements Brush {
} }
int yRadius = MathMan.usqrt(remainingY); int yRadius = MathMan.usqrt(remainingY);
int startY = Math.max(0, py - yRadius); int startY = Math.max(minY, py - yRadius);
int endY = Math.min(maxY, py + yRadius); int endY = Math.min(maxY, py + yRadius);
int heightY = editSession.getHighestTerrainBlock(ax, az, 0, endY); int heightY = editSession.getHighestTerrainBlock(ax, az, startY, endY);
if (heightY < startY) { if (heightY < startY) {
int diff = startY - heightY; int diff = startY - heightY;
startY -= diff; startY -= diff;

View File

@ -21,9 +21,11 @@ public class FlattenBrush extends HeightBrush {
boolean layers, boolean layers,
boolean smooth, boolean smooth,
Clipboard clipboard, Clipboard clipboard,
ScalableHeightMap.Shape shape ScalableHeightMap.Shape shape,
int minY,
int maxY
) { ) {
super(stream, rotation, yscale, layers, smooth, clipboard, shape); super(stream, rotation, yscale, layers, smooth, clipboard, shape, minY, maxY);
} }
@Override @Override

View File

@ -27,8 +27,9 @@ public class HeightBrush implements Brush {
public final boolean layers; public final boolean layers;
public final boolean smooth; public final boolean smooth;
public HeightBrush(InputStream stream, int rotation, double yscale, boolean layers, boolean smooth, Clipboard clipboard) { public HeightBrush(InputStream stream, int rotation, double yscale, boolean layers, boolean smooth, Clipboard clipboard,
this(stream, rotation, yscale, layers, smooth, clipboard, ScalableHeightMap.Shape.CONE); int minY, int maxY) {
this(stream, rotation, yscale, layers, smooth, clipboard, ScalableHeightMap.Shape.CONE, minY, maxY);
} }
public HeightBrush( public HeightBrush(
@ -38,7 +39,9 @@ public class HeightBrush implements Brush {
boolean layers, boolean layers,
boolean smooth, boolean smooth,
Clipboard clipboard, Clipboard clipboard,
ScalableHeightMap.Shape shape ScalableHeightMap.Shape shape,
int minY,
int maxY
) { ) {
this.rotation = (rotation / 90) % 4; this.rotation = (rotation / 90) % 4;
this.yscale = yscale; this.yscale = yscale;
@ -46,14 +49,14 @@ public class HeightBrush implements Brush {
this.smooth = smooth; this.smooth = smooth;
if (stream != null) { if (stream != null) {
try { try {
heightMap = ScalableHeightMap.fromPNG(stream); heightMap = ScalableHeightMap.fromPNG(stream, minY, maxY);
} catch (IOException e) { } catch (IOException e) {
throw new FaweException(Caption.of("fawe.worldedit.brush.brush.height.invalid")); throw new FaweException(Caption.of("fawe.worldedit.brush.brush.height.invalid"));
} }
} else if (clipboard != null) { } else if (clipboard != null) {
heightMap = ScalableHeightMap.fromClipboard(clipboard); heightMap = ScalableHeightMap.fromClipboard(clipboard, minY, maxY);
} else { } else {
heightMap = ScalableHeightMap.fromShape(shape); heightMap = ScalableHeightMap.fromShape(shape, minY, maxY);
} }
} }

View File

@ -102,7 +102,7 @@ public class ImageBrush implements Brush {
colorFunction, colorFunction,
editSession, editSession,
session.getTextureUtil() session.getTextureUtil()
), vector -> true, Integer.MAX_VALUE); ), vector -> true, Integer.MAX_VALUE, editSession.getMinY(), editSession.getMaxY());
visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS)); visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS));
visitor.visit(center); visitor.visit(center);
Operations.completeBlindly(visitor); Operations.completeBlindly(visitor);

View File

@ -36,10 +36,12 @@ public class LayerBrush implements Brush {
BlockTypes.AIR, BlockTypes.AIR,
BlockTypes.CAVE_AIR, BlockTypes.CAVE_AIR,
BlockTypes.VOID_AIR BlockTypes.VOID_AIR
)); ), editSession.getMinY(), editSession.getMaxY());
final SolidBlockMask solid = new SolidBlockMask(editSession); final SolidBlockMask solid = new SolidBlockMask(editSession);
final RadiusMask radius = new RadiusMask(0, (int) size); final RadiusMask radius = new RadiusMask(0, (int) size);
visitor = new RecursiveVisitor(new MaskIntersection(adjacent, solid, radius), function -> true); visitor = new RecursiveVisitor(new MaskIntersection(adjacent, solid, radius), funcion -> true, Integer.MAX_VALUE,
editSession.getMinY(),
editSession.getMaxY());
visitor.visit(position); visitor.visit(position);
visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS)); visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS));
Operations.completeBlindly(visitor); Operations.completeBlindly(visitor);
@ -48,7 +50,7 @@ public class LayerBrush implements Brush {
int depth = visitor.getDepth(); int depth = visitor.getDepth();
Pattern currentPattern = layers[depth]; Pattern currentPattern = layers[depth];
return currentPattern.apply(editSession, pos, pos); return currentPattern.apply(editSession, pos, pos);
}, layers.length - 1); }, layers.length - 1, editSession.getMinY(), editSession.getMaxY());
for (BlockVector3 pos : visited) { for (BlockVector3 pos : visited) {
visitor.visit(pos); visitor.visit(pos);
} }

View File

@ -48,7 +48,7 @@ public class RecurseBrush implements Brush {
visitor.visit(position); visitor.visit(position);
Operations.completeBlindly(visitor); Operations.completeBlindly(visitor);
} else { } else {
RecursiveVisitor visitor = new RecursiveVisitor(mask, replace, radius) { RecursiveVisitor visitor = new RecursiveVisitor(mask, replace, radius, editSession.getMinY(), editSession.getMaxY()) {
@Override @Override
public boolean isVisitable(BlockVector3 from, BlockVector3 to) { public boolean isVisitable(BlockVector3 from, BlockVector3 to) {
int y = to.getBlockY(); int y = to.getBlockY();

View File

@ -52,7 +52,8 @@ public class ScatterBrush implements Brush {
final int distance = Math.min((int) size, this.distance); final int distance = Math.min((int) size, this.distance);
RecursiveVisitor visitor = new RecursiveVisitor(new MaskIntersection(radius, surface), function -> true); RecursiveVisitor visitor = new RecursiveVisitor(new MaskIntersection(radius, surface), function -> true,
Integer.MAX_VALUE, editSession.getMinY(), editSession.getMaxY());
visitor.visit(position); visitor.visit(position);
visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS)); visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS));
Operations.completeBlindly(visitor); Operations.completeBlindly(visitor);

View File

@ -42,7 +42,7 @@ public class SplatterBrush extends ScatterBrush {
SurfaceMask surface = new SurfaceMask(editSession); SurfaceMask surface = new SurfaceMask(editSession);
RecursiveVisitor visitor = new RecursiveVisitor(new SplatterBrushMask(editSession, position, size2, surface, placed), RecursiveVisitor visitor = new RecursiveVisitor(new SplatterBrushMask(editSession, position, size2, surface, placed),
vector -> editSession.setBlock(vector, finalPattern), recursion vector -> editSession.setBlock(vector, finalPattern), recursion, editSession.getMinY(), editSession.getMaxY()
); );
visitor.setMaxBranch(2); visitor.setMaxBranch(2);
visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS)); visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS));

View File

@ -21,8 +21,9 @@ public class StencilBrush extends HeightBrush {
private final boolean onlyWhite; private final boolean onlyWhite;
public StencilBrush(InputStream stream, int rotation, double yscale, boolean onlyWhite, Clipboard clipboard) { public StencilBrush(InputStream stream, int rotation, double yscale, boolean onlyWhite, Clipboard clipboard, int minY,
super(stream, rotation, yscale, false, true, clipboard); int maxY) {
super(stream, rotation, yscale, false, true, clipboard, minY, maxY);
this.onlyWhite = onlyWhite; this.onlyWhite = onlyWhite;
} }
@ -32,15 +33,16 @@ public class StencilBrush extends HeightBrush {
int size = (int) sizeDouble; int size = (int) sizeDouble;
int size2 = (int) (sizeDouble * sizeDouble); int size2 = (int) (sizeDouble * sizeDouble);
int maxY = editSession.getMaxY(); int maxY = editSession.getMaxY();
int minY = editSession.getMinY();
int add; int add;
if (yscale < 0) { if (yscale < 0) {
add = maxY; add = maxY - minY;
} else { } else {
add = 0; add = 0;
} }
final HeightMap map = getHeightMap(); final HeightMap map = getHeightMap();
map.setSize(size); map.setSize(size);
int cutoff = onlyWhite ? maxY : 0; int cutoff = onlyWhite ? maxY - minY : 0;
final SolidBlockMask solid = new SolidBlockMask(editSession); final SolidBlockMask solid = new SolidBlockMask(editSession);
Location loc = editSession.getPlayer().getLocation(); Location loc = editSession.getPlayer().getLocation();
@ -48,7 +50,7 @@ public class StencilBrush extends HeightBrush {
float pitch = loc.getPitch(); float pitch = loc.getPitch();
AffineTransform transform = new AffineTransform().rotateY((-yaw) % 360).rotateX(pitch - 90).inverse(); AffineTransform transform = new AffineTransform().rotateY((-yaw) % 360).rotateX(pitch - 90).inverse();
double scale = (yscale / sizeDouble) * (maxY + 1); double scale = (yscale / sizeDouble) * (maxY - minY + 1);
RecursiveVisitor visitor = RecursiveVisitor visitor =
new RecursiveVisitor(new StencilBrushMask( new RecursiveVisitor(new StencilBrushMask(
editSession, editSession,
@ -63,7 +65,7 @@ public class StencilBrush extends HeightBrush {
maxY, maxY,
pattern pattern
), ),
vector -> true, Integer.MAX_VALUE vector -> true, Integer.MAX_VALUE, editSession.getMinY(), editSession.getMaxY()
); );
visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS)); visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS));
visitor.visit(center); visitor.visit(center);

View File

@ -25,7 +25,7 @@ public class SurfaceSphereBrush implements Brush {
final RadiusMask radius = new RadiusMask(0, (int) size); final RadiusMask radius = new RadiusMask(0, (int) size);
RecursiveVisitor visitor = new RecursiveVisitor( RecursiveVisitor visitor = new RecursiveVisitor(
new MaskIntersection(surface, radius), new MaskIntersection(surface, radius),
vector -> editSession.setBlock(vector, pattern) vector -> editSession.setBlock(vector, pattern), Integer.MAX_VALUE, editSession.getMinY(), editSession.getMaxY()
); );
visitor.visit(position); visitor.visit(position);
visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS)); visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS));

View File

@ -37,13 +37,14 @@ public class SurfaceSpline implements Brush {
public void build(EditSession editSession, BlockVector3 pos, Pattern pattern, double radius) throws public void build(EditSession editSession, BlockVector3 pos, Pattern pattern, double radius) throws
MaxChangedBlocksException { MaxChangedBlocksException {
int maxY = editSession.getMaxY(); int maxY = editSession.getMaxY();
int minY = editSession.getMinY();
if (path.isEmpty() || !pos.equals(path.get(path.size() - 1))) { if (path.isEmpty() || !pos.equals(path.get(path.size() - 1))) {
int max = editSession.getNearestSurfaceTerrainBlock( int max = editSession.getNearestSurfaceTerrainBlock(
pos.getBlockX(), pos.getBlockX(),
pos.getBlockZ(), pos.getBlockZ(),
pos.getBlockY(), pos.getBlockY(),
0, minY,
editSession.getMaxY() maxY
); );
if (max == -1) { if (max == -1) {
return; return;
@ -71,7 +72,7 @@ public class SurfaceSpline implements Brush {
final int tipx = MathMan.roundInt(tipv.getX()); final int tipx = MathMan.roundInt(tipv.getX());
final int tipz = (int) tipv.getZ(); final int tipz = (int) tipv.getZ();
int tipy = MathMan.roundInt(tipv.getY()); int tipy = MathMan.roundInt(tipv.getY());
tipy = editSession.getNearestSurfaceTerrainBlock(tipx, tipz, tipy, 0, maxY); tipy = editSession.getNearestSurfaceTerrainBlock(tipx, tipz, tipy, minY, maxY);
if (tipy == -1) { if (tipy == -1) {
continue; continue;
} }

View File

@ -5,9 +5,13 @@ import com.fastasyncworldedit.core.function.mask.AdjacentAnyMask;
import com.fastasyncworldedit.core.function.mask.AdjacentMask; import com.fastasyncworldedit.core.function.mask.AdjacentMask;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.util.SuggestionHelper; import com.sk89q.worldedit.command.util.SuggestionHelper;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extension.platform.Locatable;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -43,7 +47,7 @@ public class AdjacentMaskParser extends RichParser<Mask> {
max = min; max = min;
} }
if (max >= 8 && min == 1) { if (max >= 8 && min == 1) {
return new AdjacentAnyMask(subMask); return new AdjacentAnyMask(subMask, context.getMinY(), context.getMaxY());
} }
return new AdjacentMask(subMask, min, max); return new AdjacentMask(subMask, min, max);
} }

View File

@ -43,7 +43,7 @@ public class RichOffsetMaskParser extends RichParser<Mask> {
int y = Integer.parseInt(arguments[1]); int y = Integer.parseInt(arguments[1]);
int z = Integer.parseInt(arguments[2]); int z = Integer.parseInt(arguments[2]);
Mask submask = worldEdit.getMaskFactory().parseFromInput(arguments[3], context); Mask submask = worldEdit.getMaskFactory().parseFromInput(arguments[3], context);
return new OffsetMask(submask, BlockVector3.at(x, y, z)); return new OffsetMask(submask, BlockVector3.at(x, y, z), context.getMinY(), context.getMaxY());
} }
} }

View File

@ -58,10 +58,7 @@ public class OffsetPatternParser extends RichParser<Pattern> {
} else { } else {
x = y = z = Integer.parseInt(arguments[1]); x = y = z = Integer.parseInt(arguments[1]);
} }
Extent extent = context.requireExtent(); return new OffsetPattern(inner, x, y, z, context.getMinY(), context.getMaxY());
int minY = extent.getMinY();
int maxY = extent.getMaxY();
return new OffsetPattern(inner, x, y, z, minY, maxY);
} }
} }

View File

@ -58,8 +58,7 @@ public class RandomOffsetPatternParser extends RichParser<Pattern> {
} else { } else {
x = y = z = Integer.parseInt(arguments[1]); x = y = z = Integer.parseInt(arguments[1]);
} }
Extent extent = context.requireExtent(); return new RandomOffsetPattern(inner, x, y, z, context.getMinY(), context.getMaxY());
return new RandomOffsetPattern(inner, x, y, z, extent.getMinY(), extent.getMaxY());
} }
} }

View File

@ -41,8 +41,7 @@ public class RelativePatternParser extends RichParser<Pattern> {
)); ));
} }
Pattern inner = this.worldEdit.getPatternFactory().parseFromInput(input[0], context); Pattern inner = this.worldEdit.getPatternFactory().parseFromInput(input[0], context);
Extent extent = context.requireExtent(); return new RelativePattern(inner, context.getMinY(), context.getMaxY());
return new RelativePattern(inner, extent.getMinY(), extent.getMaxY());
} }
} }

View File

@ -58,8 +58,7 @@ public class SolidRandomOffsetPatternParser extends RichParser<Pattern> {
} else { } else {
x = y = z = Integer.parseInt(arguments[1]); x = y = z = Integer.parseInt(arguments[1]);
} }
Extent extent = context.requireExtent(); return new SolidRandomOffsetPattern(inner, x, y, z, context.getMinY(), context.getMaxY());
return new SolidRandomOffsetPattern(inner, x, y, z, extent.getMinY(), extent.getMaxY());
} }
} }

View File

@ -47,8 +47,7 @@ public class SurfaceRandomOffsetPatternParser extends RichParser<Pattern> {
} }
Pattern inner = this.worldEdit.getPatternFactory().parseFromInput(arguments[0], context); Pattern inner = this.worldEdit.getPatternFactory().parseFromInput(arguments[0], context);
int distance = Integer.parseInt(arguments[1]); int distance = Integer.parseInt(arguments[1]);
Extent extent = context.requireExtent(); return new SurfaceRandomOffsetPattern(inner, distance, context.getMinY(), context.getMaxY());
return new SurfaceRandomOffsetPattern(inner, distance, extent.getMinY(), extent.getMaxY());
} }
} }

View File

@ -44,7 +44,7 @@ public class ExtentHeightCacher extends PassthroughExtent {
index = rx + (rz << 8); index = rx + (rz << 8);
} }
int result = cacheHeights[index] & 0xFF; int result = cacheHeights[index] & 0xFF;
if (result == 0) { if (result == minY) {
cacheHeights[index] = (byte) (result = lastY = super cacheHeights[index] = (byte) (result = lastY = super
.getNearestSurfaceTerrainBlock(x, z, lastY, minY, maxY)); .getNearestSurfaceTerrainBlock(x, z, lastY, minY, maxY));
} }

View File

@ -217,6 +217,11 @@ public class NullExtent extends FaweRegionExtent implements IBatchProcessor {
throw reason; throw reason;
} }
@Override
public int getMinY() {
throw reason;
}
@Override @Override
public BlockArrayClipboard lazyCopy(Region region) { public BlockArrayClipboard lazyCopy(Region region) {
throw reason; throw reason;
@ -273,21 +278,6 @@ public class NullExtent extends FaweRegionExtent implements IBatchProcessor {
throw reason; throw reason;
} }
@Override
public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, boolean ignoreAir) {
throw reason;
}
@Override
public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY) {
throw reason;
}
@Override
public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax) {
throw reason;
}
@Override @Override
public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax, Mask mask) { public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax, Mask mask) {
throw reason; throw reason;

View File

@ -71,7 +71,7 @@ public abstract class ChunkFilterBlock extends AbstractExtentFilterBlock {
*/ */
public final IChunkSet filter(IChunk chunk, IChunkGet get, IChunkSet set, Filter filter) { public final IChunkSet filter(IChunk chunk, IChunkGet get, IChunkSet set, Filter filter) {
initChunk(chunk.getX(), chunk.getZ()); initChunk(chunk.getX(), chunk.getZ());
for (int layer = 0; layer < 16; layer++) { for (int layer = get.getMinSectionIndex(); layer <= get.getMaxSectionIndex(); layer++) {
if (set.hasSection(layer)) { if (set.hasSection(layer)) {
initLayer(get, set, layer); initLayer(get, set, layer);
filter(filter); filter(filter);
@ -87,7 +87,7 @@ public abstract class ChunkFilterBlock extends AbstractExtentFilterBlock {
if (region != null) { if (region != null) {
region.filter(chunk, filter, this, get, set, full); region.filter(chunk, filter, this, get, set, full);
} else { } else {
for (int layer = 0; layer < 16; layer++) { for (int layer = get.getMinSectionIndex(); layer <= get.getMaxSectionIndex(); layer++) {
if ((!full && !get.hasSection(layer)) || !filter.appliesLayer(chunk, layer)) { if ((!full && !get.hasSection(layer)) || !filter.appliesLayer(chunk, layer)) {
continue; continue;
} }

View File

@ -119,92 +119,92 @@ public class LimitExtent extends AbstractDelegateExtent {
@Override @Override
public int getHighestTerrainBlock(int x, int z, int minY, int maxY) { public int getHighestTerrainBlock(int x, int z, int minY, int maxY) {
limit.THROW_MAX_CHECKS(FaweCache.IMP.WORLD_HEIGHT); limit.THROW_MAX_CHECKS(maxY - minY + 1);
try { try {
return super.getHighestTerrainBlock(x, z, minY, maxY); return super.getHighestTerrainBlock(x, z, minY, maxY);
} catch (FaweException e) { } catch (FaweException e) {
if (!limit.MAX_FAILS()) { if (!limit.MAX_FAILS()) {
throw e; throw e;
} }
return 0; return minY;
} }
} }
@Override @Override
public int getHighestTerrainBlock(int x, int z, int minY, int maxY, Mask filter) { public int getHighestTerrainBlock(int x, int z, int minY, int maxY, Mask filter) {
limit.THROW_MAX_CHECKS(FaweCache.IMP.WORLD_HEIGHT); limit.THROW_MAX_CHECKS(maxY - minY + 1);
try { try {
return super.getHighestTerrainBlock(x, z, minY, maxY, filter); return super.getHighestTerrainBlock(x, z, minY, maxY, filter);
} catch (FaweException e) { } catch (FaweException e) {
if (!limit.MAX_FAILS()) { if (!limit.MAX_FAILS()) {
throw e; throw e;
} }
return 0; return minY;
} }
} }
@Override @Override
public int getNearestSurfaceLayer(int x, int z, int y, int minY, int maxY) { public int getNearestSurfaceLayer(int x, int z, int y, int minY, int maxY) {
limit.THROW_MAX_CHECKS(FaweCache.IMP.WORLD_HEIGHT); limit.THROW_MAX_CHECKS(maxY - minY + 1);
try { try {
return super.getNearestSurfaceLayer(x, z, y, minY, maxY); return super.getNearestSurfaceLayer(x, z, y, minY, maxY);
} catch (FaweException e) { } catch (FaweException e) {
if (!limit.MAX_FAILS()) { if (!limit.MAX_FAILS()) {
throw e; throw e;
} }
return 0; return minY;
} }
} }
@Override @Override
public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, boolean ignoreAir) { public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, boolean ignoreAir) {
limit.THROW_MAX_CHECKS(FaweCache.IMP.WORLD_HEIGHT); limit.THROW_MAX_CHECKS(maxY - minY + 1);
try { try {
return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, ignoreAir); return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, ignoreAir);
} catch (FaweException e) { } catch (FaweException e) {
if (!limit.MAX_FAILS()) { if (!limit.MAX_FAILS()) {
throw e; throw e;
} }
return 0; return minY;
} }
} }
@Override @Override
public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY) { public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY) {
limit.THROW_MAX_CHECKS(FaweCache.IMP.WORLD_HEIGHT); limit.THROW_MAX_CHECKS(maxY - minY + 1);
try { try {
return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY); return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY);
} catch (FaweException e) { } catch (FaweException e) {
if (!limit.MAX_FAILS()) { if (!limit.MAX_FAILS()) {
throw e; throw e;
} }
return 0; return minY;
} }
} }
@Override @Override
public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax) { public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax) {
limit.THROW_MAX_CHECKS(FaweCache.IMP.WORLD_HEIGHT); limit.THROW_MAX_CHECKS(maxY - minY + 1);
try { try {
return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax); return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax);
} catch (FaweException e) { } catch (FaweException e) {
if (!limit.MAX_FAILS()) { if (!limit.MAX_FAILS()) {
throw e; throw e;
} }
return 0; return minY;
} }
} }
@Override @Override
public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax, Mask mask) { public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax, Mask mask) {
limit.THROW_MAX_CHECKS(FaweCache.IMP.WORLD_HEIGHT); limit.THROW_MAX_CHECKS(maxY - minY + 1);
try { try {
return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax, mask); return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax, mask);
} catch (FaweException e) { } catch (FaweException e) {
if (!limit.MAX_FAILS()) { if (!limit.MAX_FAILS()) {
throw e; throw e;
} }
return 0; return minY;
} }
} }
@ -219,14 +219,14 @@ public class LimitExtent extends AbstractDelegateExtent {
int failedMax, int failedMax,
boolean ignoreAir boolean ignoreAir
) { ) {
limit.THROW_MAX_CHECKS(FaweCache.IMP.WORLD_HEIGHT); limit.THROW_MAX_CHECKS(maxY - minY + 1);
try { try {
return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax, ignoreAir); return super.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax, ignoreAir);
} catch (FaweException e) { } catch (FaweException e) {
if (!limit.MAX_FAILS()) { if (!limit.MAX_FAILS()) {
throw e; throw e;
} }
return 0; return minY;
} }
} }

View File

@ -11,7 +11,15 @@ public class ArrayHeightMap extends ScalableHeightMap {
private double rx; private double rx;
private double rz; private double rz;
public ArrayHeightMap(byte[][] height) { /**
* New height map represented by byte array[][] of values x*z to be scaled given a set size
*
* @param height array of height values
* @param minY min y value allowed to be set. Inclusive.
* @param maxY max y value allowed to be set. Inclusive.
*/
public ArrayHeightMap(byte[][] height, int minY, int maxY) {
super(minY, maxY);
setSize(5); setSize(5);
this.height = height; this.height = height;
this.width = height.length; this.width = height.length;

View File

@ -2,8 +2,15 @@ package com.fastasyncworldedit.core.extent.processor.heightmap;
public class FlatScalableHeightMap extends ScalableHeightMap { public class FlatScalableHeightMap extends ScalableHeightMap {
public FlatScalableHeightMap() { /**
super(); * New height map where the returned height is the minmum height value if outside the size, otherwise returns height equal
* to size.
*
* @param minY min y value allowed to be set. Inclusive.
* @param maxY max y value allowed to be set. Inclusive.
*/
public FlatScalableHeightMap(int minY, int maxY) {
super(minY, maxY);
} }
@Override @Override
@ -12,7 +19,7 @@ public class FlatScalableHeightMap extends ScalableHeightMap {
int dz = Math.abs(z); int dz = Math.abs(z);
int d2 = dx * dx + dz * dz; int d2 = dx * dx + dz * dz;
if (d2 > size2) { if (d2 > size2) {
return 0; return minY;
} }
return size; return size;
} }

View File

@ -18,7 +18,6 @@ public interface HeightMap {
void setSize(int size); void setSize(int size);
default void perform( default void perform(
EditSession session, EditSession session,
Mask mask, Mask mask,
@ -83,8 +82,8 @@ public interface HeightMap {
boolean towards, boolean towards,
final boolean layers final boolean layers
) { ) {
BlockVector3 top = session.getMaximumPoint(); int maxY = session.getMaxY();
int maxY = top.getBlockY(); int minY = session.getMinY();
int diameter = 2 * size + 1; int diameter = 2 * size + 1;
int centerX = pos.getBlockX(); int centerX = pos.getBlockX();
int centerZ = pos.getBlockZ(); int centerZ = pos.getBlockZ();
@ -121,15 +120,15 @@ public interface HeightMap {
} }
int height; int height;
if (layers) { if (layers) {
height = tmpY = session.getNearestSurfaceLayer(xx, zz, tmpY, 0, maxY); height = tmpY = session.getNearestSurfaceLayer(xx, zz, tmpY, minY, maxY);
} else { } else {
height = tmpY = session.getNearestSurfaceTerrainBlock(xx, zz, tmpY, 0, maxY); height = tmpY = session.getNearestSurfaceTerrainBlock(xx, zz, tmpY, minY, maxY);
if (height == -1) { if (height == -1) {
continue; continue;
} }
} }
oldData[index] = height; oldData[index] = height;
if (height == 0) { if (height == minY) {
newData[index] = centerY; newData[index] = centerY;
continue; continue;
} }
@ -137,8 +136,9 @@ public interface HeightMap {
int diff = targetY - height; int diff = targetY - height;
double raiseScaled = diff * (raisePow * sizePowInv); double raiseScaled = diff * (raisePow * sizePowInv);
double raiseScaledAbs = Math.abs(raiseScaled); double raiseScaledAbs = Math.abs(raiseScaled);
int random = ThreadLocalRandom.current().nextInt(256) < (int) ((Math.ceil(raiseScaledAbs) - Math.floor( int random =
raiseScaledAbs)) * 256) ? (diff > 0 ? 1 : -1) : 0; ThreadLocalRandom.current().nextInt(maxY + 1 - minY) - minY < (int) ((Math.ceil(raiseScaledAbs) - Math.floor(
raiseScaledAbs)) * (maxY + 1 - minY)) ? (diff > 0 ? 1 : -1) : 0;
int raiseScaledInt = (int) raiseScaled + random; int raiseScaledInt = (int) raiseScaled + random;
newData[index] = height + raiseScaledInt; newData[index] = height + raiseScaledInt;
} }
@ -166,20 +166,22 @@ public interface HeightMap {
break; break;
} }
if (layers) { if (layers) {
height = session.getNearestSurfaceLayer(xx, zz, height, 0, maxY); height = session.getNearestSurfaceLayer(xx, zz, height, minY, maxY);
} else { } else {
height = session.getNearestSurfaceTerrainBlock(xx, zz, height, 0, maxY); height = session.getNearestSurfaceTerrainBlock(xx, zz, height, minY, maxY);
if (height == -1) { if (height == minY - 1) {
continue; continue;
} }
} }
oldData[index] = height; oldData[index] = height;
if (height == 0) { if (height == minY) {
newData[index] = centerY; newData[index] = centerY;
continue; continue;
} }
raise = (yscale * raise); raise = (yscale * raise);
int random = ThreadLocalRandom.current().nextInt(256) < (int) ((raise - (int) raise) * (256)) ? 1 : 0; int random =
ThreadLocalRandom.current().nextInt(maxY + 1 - minY) - minY < (int) ((raise - (int) raise) * (maxY - minY + 1))
? 1 : 0;
int newHeight = height + (int) raise + random; int newHeight = height + (int) raise + random;
newData[index] = newHeight; newData[index] = newHeight;
} }

View File

@ -18,20 +18,26 @@ public class ScalableHeightMap implements HeightMap {
public int size2; public int size2;
public int size; public int size;
protected int minY;
protected int maxY;
public enum Shape { public enum Shape {
CONE, CONE,
CYLINDER, CYLINDER,
} }
public ScalableHeightMap() { /**
* New height map.
*
* @param minY min y value allowed to be set. Inclusive.
* @param maxY max y value allowed to be set. Inclusive.
*/
public ScalableHeightMap(final int minY, final int maxY) {
this.minY = minY;
this.maxY = maxY;
setSize(5); setSize(5);
} }
public ScalableHeightMap(int size) {
setSize(size);
}
@Override @Override
public void setSize(int size) { public void setSize(int size) {
this.size = size; this.size = size;
@ -44,29 +50,29 @@ public class ScalableHeightMap implements HeightMap {
int dz = Math.abs(z); int dz = Math.abs(z);
int d2 = dx * dx + dz * dz; int d2 = dx * dx + dz * dz;
if (d2 > size2) { if (d2 > size2) {
return 0; return minY;
} }
return Math.max(0, size - MathMan.sqrtApprox(d2)); return Math.max(minY, size - MathMan.sqrtApprox(d2));
} }
public static ScalableHeightMap fromShape(Shape shape) { public static ScalableHeightMap fromShape(Shape shape, int minY, int maxY) {
switch (shape) { switch (shape) {
default: default:
case CONE: case CONE:
return new ScalableHeightMap(); return new ScalableHeightMap(minY, maxY);
case CYLINDER: case CYLINDER:
return new FlatScalableHeightMap(); return new FlatScalableHeightMap(minY, maxY);
} }
} }
public static ScalableHeightMap fromClipboard(Clipboard clipboard) { public static ScalableHeightMap fromClipboard(Clipboard clipboard, int minY, int maxY) {
BlockVector3 dim = clipboard.getDimensions(); BlockVector3 dim = clipboard.getDimensions();
byte[][] heightArray = new byte[dim.getBlockX()][dim.getBlockZ()]; byte[][] heightArray = new byte[dim.getBlockX()][dim.getBlockZ()];
int minX = clipboard.getMinimumPoint().getBlockX(); int clipMinX = clipboard.getMinimumPoint().getBlockX();
int minZ = clipboard.getMinimumPoint().getBlockZ(); int clipMinZ = clipboard.getMinimumPoint().getBlockZ();
int minY = clipboard.getMinimumPoint().getBlockY(); int clipMinY = clipboard.getMinimumPoint().getBlockY();
int maxY = clipboard.getMaximumPoint().getBlockY(); int clipMaxY = clipboard.getMaximumPoint().getBlockY();
int clipHeight = maxY - minY + 1; int clipHeight = clipMaxY - clipMinY + 1;
HashSet<IntPair> visited = new HashSet<>(); HashSet<IntPair> visited = new HashSet<>();
MutableBlockVector3 bv = new MutableBlockVector3(); MutableBlockVector3 bv = new MutableBlockVector3();
for (BlockVector3 pos : clipboard.getRegion()) { for (BlockVector3 pos : clipboard.getRegion()) {
@ -77,24 +83,24 @@ public class ScalableHeightMap implements HeightMap {
visited.add(pair); visited.add(pair);
int xx = pos.getBlockX(); int xx = pos.getBlockX();
int zz = pos.getBlockZ(); int zz = pos.getBlockZ();
int highestY = minY; int highestY = clipMinY;
bv.setComponents(pos); bv.setComponents(pos);
for (int y = minY; y <= maxY; y++) { for (int y = clipMinY; y <= clipMaxY; y++) {
bv.mutY(y); bv.mutY(y);
BlockState block = clipboard.getBlock(bv); BlockState block = clipboard.getBlock(bv);
if (!block.getBlockType().getMaterial().isAir()) { if (!block.getBlockType().getMaterial().isAir()) {
highestY = y + 1; highestY = y + 1;
} }
} }
int pointHeight = Math.min(255, (256 * (highestY - minY)) / clipHeight); int pointHeight = Math.min(clipMaxY, ((maxY - minY + 1 ) * (highestY - clipMinY)) / clipHeight);
int x = xx - minX; int x = xx - clipMinX;
int z = zz - minZ; int z = zz - clipMinZ;
heightArray[x][z] = (byte) pointHeight; heightArray[x][z] = (byte) pointHeight;
} }
return new ArrayHeightMap(heightArray); return new ArrayHeightMap(heightArray, minY, maxY);
} }
public static ScalableHeightMap fromPNG(InputStream stream) throws IOException { public static ScalableHeightMap fromPNG(InputStream stream, int minY, int maxY) throws IOException {
BufferedImage heightFile = MainUtil.readImage(stream); BufferedImage heightFile = MainUtil.readImage(stream);
int width = heightFile.getWidth(); int width = heightFile.getWidth();
int length = heightFile.getHeight(); int length = heightFile.getHeight();
@ -113,7 +119,7 @@ public class ScalableHeightMap implements HeightMap {
array[x][z] = (byte) intensity; array[x][z] = (byte) intensity;
} }
} }
return new ArrayHeightMap(array); return new ArrayHeightMap(array, minY, maxY);
} }
} }

View File

@ -60,11 +60,12 @@ public class NMSRelighter implements Relighter {
private final ConcurrentHashMap<Long, long[][][]> concurrentLightQueue; private final ConcurrentHashMap<Long, long[][][]> concurrentLightQueue;
private final RelightMode relightMode; private final RelightMode relightMode;
private final int maxY; private final int maxY;
private final int minY;
private final ReentrantLock lightingLock; private final ReentrantLock lightingLock;
private final AtomicBoolean finished = new AtomicBoolean(false); private final AtomicBoolean finished = new AtomicBoolean(false);
private boolean removeFirst; private boolean removeFirst;
public NMSRelighter(IQueueExtent<IQueueChunk> queue, boolean calculateHeightMaps) { public NMSRelighter(IQueueExtent<IQueueChunk> queue) {
this(queue, null); this(queue, null);
} }
@ -75,6 +76,7 @@ public class NMSRelighter implements Relighter {
this.chunksToSend = new Long2ObjectOpenHashMap<>(12); this.chunksToSend = new Long2ObjectOpenHashMap<>(12);
this.concurrentLightQueue = new ConcurrentHashMap<>(12); this.concurrentLightQueue = new ConcurrentHashMap<>(12);
this.maxY = queue.getMaxY(); this.maxY = queue.getMaxY();
this.minY = queue.getMinY();
this.relightMode = relightMode != null ? relightMode : RelightMode.valueOf(Settings.IMP.LIGHTING.MODE); this.relightMode = relightMode != null ? relightMode : RelightMode.valueOf(Settings.IMP.LIGHTING.MODE);
this.lightingLock = new ReentrantLock(); this.lightingLock = new ReentrantLock();
} }
@ -118,6 +120,8 @@ public class NMSRelighter implements Relighter {
if (m2 == null) { if (m2 == null) {
m2 = m1[x] = new long[4]; m2 = m1[x] = new long[4];
} }
// Account for negative y values by "adding" minY
y -= minY;
m2[y >> 6] |= 1L << y; m2[y >> 6] |= 1L << y;
} }
@ -132,6 +136,8 @@ public class NMSRelighter implements Relighter {
this.lightQueue.put(index, currentMap); this.lightQueue.put(index, currentMap);
} }
set(x & 15, y, z & 15, currentMap); set(x & 15, y, z & 15, currentMap);
this.lightQueue.putAll(concurrentLightQueue);
concurrentLightQueue.clear();
} finally { } finally {
lightLock.set(false); lightLock.set(false);
} }
@ -154,7 +160,7 @@ public class NMSRelighter implements Relighter {
} }
public boolean addChunk(int cx, int cz, byte[] fix, int bitmask) { public boolean addChunk(int cx, int cz, byte[] fix, int bitmask) {
RelightSkyEntry toPut = new RelightSkyEntry(cx, cz, fix, bitmask); RelightSkyEntry toPut = new RelightSkyEntry(cx, cz, fix, bitmask, minY, maxY);
extendSkyToRelight.add(toPut); extendSkyToRelight.add(toPut);
return true; return true;
} }
@ -188,7 +194,7 @@ public class NMSRelighter implements Relighter {
if (!iChunk.isInit()) { if (!iChunk.isInit()) {
iChunk.init(queue, chunk.x, chunk.z); iChunk.init(queue, chunk.x, chunk.z);
} }
for (int i = 0; i < 16; i++) { for (int i = minY >> 4; i <= maxY >> 4; i++) {
iChunk.removeSectionLighting(i, true); iChunk.removeSectionLighting(i, true);
} }
iter.remove(); iter.remove();
@ -238,7 +244,7 @@ public class NMSRelighter implements Relighter {
for (int j = 0; j < 64; j++) { for (int j = 0; j < 64; j++) {
if (((value >> j) & 1) == 1) { if (((value >> j) & 1) == 1) {
int x = lx + bx; int x = lx + bx;
int y = yStart + j; int y = yStart + j + minY;
int z = lz + bz; int z = lz + bz;
int oldLevel = iChunk.getEmittedLight(lx, y, lz); int oldLevel = iChunk.getEmittedLight(lx, y, lz);
int newLevel = iChunk.getBrightness(lx, y, lz); int newLevel = iChunk.getBrightness(lx, y, lz);
@ -287,7 +293,7 @@ public class NMSRelighter implements Relighter {
removalVisited, removalVisited,
visited visited
); );
if (node.getY() > 0) { if (node.getY() > minY) {
this.computeRemoveBlockLight( this.computeRemoveBlockLight(
node.getX(), node.getX(),
node.getY() - 1, node.getY() - 1,
@ -299,7 +305,7 @@ public class NMSRelighter implements Relighter {
visited visited
); );
} }
if (node.getY() < 255) { if (node.getY() < maxY) {
this.computeRemoveBlockLight( this.computeRemoveBlockLight(
node.getX(), node.getX(),
node.getY() + 1, node.getY() + 1,
@ -650,7 +656,7 @@ public class NMSRelighter implements Relighter {
this.computeSpreadBlockLight(x, y - 1, z, currentLight, queue, visited); this.computeSpreadBlockLight(x, y - 1, z, currentLight, queue, visited);
} }
state = this.queue.getBlock(x, y + 1, z); state = this.queue.getBlock(x, y + 1, z);
if (y < 255 && !top && isSlabOrTrueValue(state, "top") && isStairOrTrueTop(state, true)) { if (y < maxY && !top && isSlabOrTrueValue(state, "top") && isStairOrTrueTop(state, true)) {
this.computeSpreadBlockLight(x, y + 1, z, currentLight, queue, visited); this.computeSpreadBlockLight(x, y + 1, z, currentLight, queue, visited);
} }
} }
@ -696,7 +702,7 @@ public class NMSRelighter implements Relighter {
this.computeSpreadBlockLight(x, y - 1, z, currentLight, queue, visited); this.computeSpreadBlockLight(x, y - 1, z, currentLight, queue, visited);
} }
state = this.queue.getBlock(x, y + 1, z); state = this.queue.getBlock(x, y + 1, z);
if (y < 255 && isSlabOrTrueValue(state, "top") && isStairOrTrueTop(state, false)) { if (y < maxY && isSlabOrTrueValue(state, "top") && isStairOrTrueTop(state, false)) {
this.computeSpreadBlockLight(x, y + 1, z, currentLight, queue, visited); this.computeSpreadBlockLight(x, y + 1, z, currentLight, queue, visited);
} }
} }
@ -944,7 +950,7 @@ public class NMSRelighter implements Relighter {
int z = MathMan.unpairIntY(pair); int z = MathMan.unpairIntY(pair);
ChunkHolder<?> chunk = (ChunkHolder<?>) queue.getOrCreateChunk(x, z); ChunkHolder<?> chunk = (ChunkHolder<?>) queue.getOrCreateChunk(x, z);
chunk.setBitMask(bitMask); chunk.setBitMask(bitMask);
chunk.flushLightToGet(true); chunk.flushLightToGet();
Fawe.imp().getPlatformAdapter().sendChunk(chunk.getOrCreateGet(), bitMask, true); Fawe.imp().getPlatformAdapter().sendChunk(chunk.getOrCreateGet(), bitMask, true);
iter.remove(); iter.remove();
} }
@ -984,7 +990,7 @@ public class NMSRelighter implements Relighter {
} }
} }
public void fill(byte[] mask, int chunkX, int y, int chunkZ, byte reason) { public void fill(byte[] mask, ChunkHolder<?> iChunk, int y, byte reason) {
if (y >= 16) { if (y >= 16) {
Arrays.fill(mask, (byte) 15); Arrays.fill(mask, (byte) 15);
return; return;
@ -995,12 +1001,10 @@ public class NMSRelighter implements Relighter {
return; return;
} }
case SkipReason.AIR: { case SkipReason.AIR: {
int bx = chunkX << 4;
int bz = chunkZ << 4;
int index = 0; int index = 0;
for (int z = 0; z < 16; z++) { for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) { for (int x = 0; x < 16; x++) {
mask[index++] = (byte) queue.getSkyLight(bx + x, y, bz + z); mask[index++] = (byte) iChunk.getSkyLight(x, y, z);
} }
} }
} }
@ -1026,19 +1030,19 @@ public class NMSRelighter implements Relighter {
} }
} }
} }
for (int y = 255; y > 0; y--) { for (int y = maxY; y > minY; y--) {
for (RelightSkyEntry chunk : chunks) { // Propagate skylight for (RelightSkyEntry chunk : chunks) { // Propagate skylight
int layer = y >> 4; int layer = (y - minY) >> 4;
byte[] mask = chunk.mask; byte[] mask = chunk.mask;
if (chunk.fix[layer] != SkipReason.NONE) {
if ((y & 15) == 0 && layer != 0 && chunk.fix[layer - 1] == SkipReason.NONE) {
fill(mask, chunk.x, y, chunk.z, chunk.fix[layer]);
}
continue;
}
int bx = chunk.x << 4; int bx = chunk.x << 4;
int bz = chunk.z << 4; int bz = chunk.z << 4;
ChunkHolder<?> iChunk = (ChunkHolder<?>) queue.getOrCreateChunk(chunk.x, chunk.z); ChunkHolder<?> iChunk = (ChunkHolder<?>) queue.getOrCreateChunk(chunk.x, chunk.z);
if (chunk.fix[layer] != SkipReason.NONE) {
if ((y & 15) == 0 && layer != 0 && chunk.fix[layer - 1] == SkipReason.NONE) {
fill(mask, iChunk, y, chunk.fix[layer]);
}
continue;
}
if (!iChunk.isInit()) { if (!iChunk.isInit()) {
iChunk.init(queue, chunk.x, chunk.z); iChunk.init(queue, chunk.x, chunk.z);
} }
@ -1157,29 +1161,29 @@ public class NMSRelighter implements Relighter {
} }
byte value = mask[j]; byte value = mask[j];
if (x != 0 && z != 0) { if (x != 0 && z != 0) {
if ((value = (byte) Math.max(iChunk.getSkyLight(x - 1, y, z) - 1, value)) >= 14) { if ((value = (byte) Math.max(iChunk.getSkyLight(x - 1, y, z) - 1, value)) < 14) {
} else if ((value = (byte) Math.max(iChunk.getSkyLight(x, y, z - 1) - 1, value)) >= 14) { value = (byte) Math.max(iChunk.getSkyLight(x, y, z - 1) - 1, value);
} }
if (value > mask[j]) { if (value > mask[j]) {
iChunk.setSkyLight(x, y, z, mask[j] = value); iChunk.setSkyLight(x, y, z, mask[j] = value);
} }
} else if (x == 0 && z == 0) { } else if (x == 0 && z == 0) {
if ((value = (byte) Math.max(iChunkx.getSkyLight(15, y, z) - 1, value)) >= 14) { if ((value = (byte) Math.max(iChunkx.getSkyLight(15, y, z) - 1, value)) < 14) {
} else if ((value = (byte) Math.max(iChunkz.getSkyLight(x, y, 15) - 1, value)) >= 14) { value = (byte) Math.max(iChunkz.getSkyLight(x, y, 15) - 1, value);
} }
if (value > mask[j]) { if (value > mask[j]) {
iChunk.setSkyLight(x, y, z, mask[j] = value); iChunk.setSkyLight(x, y, z, mask[j] = value);
} }
} else if (x == 0) { } else if (x == 0) {
if ((value = (byte) Math.max(iChunkx.getSkyLight(15, y, z) - 1, value)) >= 14) { if ((value = (byte) Math.max(iChunkx.getSkyLight(15, y, z) - 1, value)) < 14) {
} else if ((value = (byte) Math.max(iChunk.getSkyLight(x, y, z - 1) - 1, value)) >= 14) { value = (byte) Math.max(iChunk.getSkyLight(x, y, z - 1) - 1, value);
} }
if (value > mask[j]) { if (value > mask[j]) {
iChunk.setSkyLight(x, y, z, mask[j] = value); iChunk.setSkyLight(x, y, z, mask[j] = value);
} }
} else { } else {
if ((value = (byte) Math.max(iChunk.getSkyLight(x - 1, y, z) - 1, value)) >= 14) { if ((value = (byte) Math.max(iChunk.getSkyLight(x - 1, y, z) - 1, value)) < 14) {
} else if ((value = (byte) Math.max(iChunkz.getSkyLight(x, y, 15) - 1, value)) >= 14) { value = (byte) Math.max(iChunkz.getSkyLight(x, y, 15) - 1, value);
} }
if (value > mask[j]) { if (value > mask[j]) {
iChunk.setSkyLight(x, y, z, mask[j] = value); iChunk.setSkyLight(x, y, z, mask[j] = value);
@ -1203,29 +1207,29 @@ public class NMSRelighter implements Relighter {
} }
byte value = mask[j]; byte value = mask[j];
if (x != 15 && z != 15) { if (x != 15 && z != 15) {
if ((value = (byte) Math.max(iChunk.getSkyLight(x + 1, y, z) - 1, value)) >= 14) { if ((value = (byte) Math.max(iChunk.getSkyLight(x + 1, y, z) - 1, value)) < 14) {
} else if ((value = (byte) Math.max(iChunk.getSkyLight(x, y, z + 1) - 1, value)) >= 14) { value = (byte) Math.max(iChunk.getSkyLight(x, y, z + 1) - 1, value);
} }
if (value > mask[j]) { if (value > mask[j]) {
iChunk.setSkyLight(x, y, z, mask[j] = value); iChunk.setSkyLight(x, y, z, mask[j] = value);
} }
} else if (x == 15 && z == 15) { } else if (x == 15 && z == 15) {
if ((value = (byte) Math.max(iChunkx.getSkyLight(0, y, z) - 1, value)) >= 14) { if ((value = (byte) Math.max(iChunkx.getSkyLight(0, y, z) - 1, value)) < 14) {
} else if ((value = (byte) Math.max(iChunkz.getSkyLight(x, y, 0) - 1, value)) >= 14) { value = (byte) Math.max(iChunkz.getSkyLight(x, y, 0) - 1, value);
} }
if (value > mask[j]) { if (value > mask[j]) {
iChunk.setSkyLight(x, y, z, mask[j] = value); iChunk.setSkyLight(x, y, z, mask[j] = value);
} }
} else if (x == 15) { } else if (x == 15) {
if ((value = (byte) Math.max(iChunkx.getSkyLight(0, y, z) - 1, value)) >= 14) { if ((value = (byte) Math.max(iChunkx.getSkyLight(0, y, z) - 1, value)) < 14) {
} else if ((value = (byte) Math.max(iChunk.getSkyLight(x, y, z + 1) - 1, value)) >= 14) { value = (byte) Math.max(iChunk.getSkyLight(x, y, z + 1) - 1, value);
} }
if (value > mask[j]) { if (value > mask[j]) {
iChunk.setSkyLight(x, y, z, mask[j] = value); iChunk.setSkyLight(x, y, z, mask[j] = value);
} }
} else { } else {
if ((value = (byte) Math.max(iChunk.getSkyLight(x + 1, y, z) - 1, value)) >= 14) { if ((value = (byte) Math.max(iChunk.getSkyLight(x + 1, y, z) - 1, value)) < 14) {
} else if ((value = (byte) Math.max(iChunkz.getSkyLight(x, y, 0) - 1, value)) >= 14) { value = (byte) Math.max(iChunkz.getSkyLight(x, y, 0) - 1, value);
} }
if (value > mask[j]) { if (value > mask[j]) {
iChunk.setSkyLight(x, y, z, mask[j] = value); iChunk.setSkyLight(x, y, z, mask[j] = value);
@ -1235,7 +1239,7 @@ public class NMSRelighter implements Relighter {
} }
} }
private class RelightSkyEntry implements Comparable<RelightSkyEntry> { private static class RelightSkyEntry implements Comparable<RelightSkyEntry> {
public final int x; public final int x;
public final int z; public final int z;
@ -1244,7 +1248,7 @@ public class NMSRelighter implements Relighter {
public int bitmask; public int bitmask;
public boolean smooth; public boolean smooth;
public RelightSkyEntry(int x, int z, byte[] fix, int bitmask) { private RelightSkyEntry(int x, int z, byte[] fix, int bitmask, int minY, int maxY) {
this.x = x; this.x = x;
this.z = z; this.z = z;
byte[] array = new byte[256]; byte[] array = new byte[256];
@ -1252,13 +1256,14 @@ public class NMSRelighter implements Relighter {
this.mask = array; this.mask = array;
this.bitmask = bitmask; this.bitmask = bitmask;
if (fix == null) { if (fix == null) {
this.fix = new byte[(maxY + 1) >> 4]; this.fix = new byte[(maxY - minY + 1) >> 4];
Arrays.fill(this.fix, SkipReason.NONE); Arrays.fill(this.fix, SkipReason.NONE);
} else { } else {
this.fix = fix; this.fix = fix;
} }
} }
//Following are public because they are public in Object. NONE of this nested class is API.
@Override @Override
public String toString() { public String toString() {
return x + "," + z; return x + "," + z;

View File

@ -28,11 +28,12 @@ public class RelightProcessor implements IBatchProcessor {
if (Settings.IMP.LIGHTING.MODE == 2) { if (Settings.IMP.LIGHTING.MODE == 2) {
relighter.addChunk(chunk.getX(), chunk.getZ(), null, chunk.getBitMask()); relighter.addChunk(chunk.getX(), chunk.getZ(), null, chunk.getBitMask());
} else if (Settings.IMP.LIGHTING.MODE == 1) { } else if (Settings.IMP.LIGHTING.MODE == 1) {
byte[] fix = new byte[16]; byte[] fix = new byte[get.getSectionCount()];
boolean relight = false; boolean relight = false;
for (int i = 15; i >= 0; i--) { for (int i = get.getMaxSectionIndex(); i >= get.getMinSectionIndex(); i--) {
if (!set.hasSection(i)) { if (!set.hasSection(i)) {
fix[i] = Relighter.SkipReason.AIR; // Array index cannot be < 0 so "add" the min
fix[i - get.getMinSectionIndex()] = Relighter.SkipReason.AIR;
continue; continue;
} }
relight = true; relight = true;

View File

@ -189,11 +189,11 @@ public class CavesGen extends GenBase {
n = 16; n = 16;
} }
if (i1 < 1) { if (i1 < chunk.getMinY() + 1) {
i1 = 1; i1 = chunk.getMinY();
} }
if (i2 > 256 - 8) { if (i2 >= chunk.getMaxY() - 8) {
i2 = 256 - 8; i2 = chunk.getMaxY() - 8;
} }
if (i3 < 0) { if (i3 < 0) {
i3 = 0; i3 = 0;
@ -207,7 +207,7 @@ public class CavesGen extends GenBase {
for (int local_x = m; !waterFound && local_x < n; local_x++) { for (int local_x = m; !waterFound && local_x < n; local_x++) {
for (int local_z = i3; !waterFound && local_z < i4; local_z++) { for (int local_z = i3; !waterFound && local_z < i4; local_z++) {
for (int local_y = i2 + 1; !waterFound && local_y >= i1 - 1; local_y--) { for (int local_y = i2 + 1; !waterFound && local_y >= i1 - 1; local_y--) {
if (local_y < 255) { if (local_y < chunk.getMaxY()) {
BlockState material = chunk.getBlock(bx + local_x, local_y, bz + local_z); BlockState material = chunk.getBlock(bx + local_x, local_y, bz + local_z);
if (material.getBlockType() == BlockTypes.WATER) { if (material.getBlockType() == BlockTypes.WATER) {
waterFound = true; waterFound = true;

View File

@ -78,11 +78,11 @@ public class OreGen implements Resource {
double d12o2 = d12 * ONE_2; double d12o2 = d12 * ONE_2;
int minX = MathMan.floorZero(d7 - d11o2); int minX = MathMan.floorZero(d7 - d11o2);
int minY = Math.max(1, MathMan.floorZero(d8 - d12o2)); int minY = Math.max(this.minY + 1, MathMan.floorZero(d8 - d12o2));
int minZ = MathMan.floorZero(d9 - d11o2); int minZ = MathMan.floorZero(d9 - d11o2);
int maxX = MathMan.floorZero(d7 + d11o2); int maxX = MathMan.floorZero(d7 + d11o2);
int maxY = Math.min(255, MathMan.floorZero(d8 + d12o2)); int maxY = Math.min(this.maxY, MathMan.floorZero(d8 + d12o2));
int maxZ = MathMan.floorZero(d9 + d11o2); int maxZ = MathMan.floorZero(d9 + d11o2);
double id11o2 = 1.0 / (d11o2); double id11o2 = 1.0 / (d11o2);

View File

@ -33,7 +33,7 @@ public class SchemGen implements Resource {
public boolean spawn(Random random, int x, int z) throws WorldEditException { public boolean spawn(Random random, int x, int z) throws WorldEditException {
mutable.mutX(x); mutable.mutX(x);
mutable.mutZ(z); mutable.mutZ(z);
int y = extent.getNearestSurfaceTerrainBlock(x, z, mutable.getBlockY(), 0, 255); int y = extent.getNearestSurfaceTerrainBlock(x, z, mutable.getBlockY(), this.extent.getMinY(), this.extent.getMaxY());
if (y == -1) { if (y == -1) {
return false; return false;
} }

View File

@ -12,10 +12,14 @@ public class AdjacentAnyMask extends AbstractMask implements ResettableMask {
private final CachedMask mask; private final CachedMask mask;
private final MutableBlockVector3 mutable; private final MutableBlockVector3 mutable;
private final int minY;
private final int maxY;
public AdjacentAnyMask(Mask mask) { public AdjacentAnyMask(Mask mask, int minY, int maxY) {
this.mask = CachedMask.cache(mask); this.mask = CachedMask.cache(mask);
mutable = new MutableBlockVector3(); mutable = new MutableBlockVector3();
this.minY = minY;
this.maxY = maxY;
} }
@Override @Override
@ -44,9 +48,9 @@ public class AdjacentAnyMask extends AbstractMask implements ResettableMask {
return mutable.setComponents(0, 0, 1); return mutable.setComponents(0, 0, 1);
} else if (mask.test(x, y, z - 1)) { } else if (mask.test(x, y, z - 1)) {
return mutable.setComponents(0, 0, -1); return mutable.setComponents(0, 0, -1);
} else if (y < 256 && mask.test(x, y + 1, z)) { } else if (y < maxY && mask.test(x, y + 1, z)) {
return mutable.setComponents(0, 1, 0); return mutable.setComponents(0, 1, 0);
} else if (y > 0 && mask.test(x, y - 1, z)) { } else if (y > minY && mask.test(x, y - 1, z)) {
return mutable.setComponents(0, -1, 0); return mutable.setComponents(0, -1, 0);
} else { } else {
return null; return null;
@ -55,7 +59,7 @@ public class AdjacentAnyMask extends AbstractMask implements ResettableMask {
@Override @Override
public Mask copy() { public Mask copy() {
return new AdjacentAnyMask(mask.copy()); return new AdjacentAnyMask(mask.copy(), minY, maxY);
} }
} }

View File

@ -18,6 +18,7 @@ public class AngleMask extends SolidBlockMask implements ResettableMask {
protected final boolean overlay; protected final boolean overlay;
protected final boolean checkFirst; protected final boolean checkFirst;
protected final int maxY; protected final int maxY;
protected final int minY;
protected final int distance; protected final int distance;
public AngleMask(Extent extent, double min, double max, boolean overlay, int distance) { public AngleMask(Extent extent, double min, double max, boolean overlay, int distance) {
@ -26,7 +27,8 @@ public class AngleMask extends SolidBlockMask implements ResettableMask {
this.min = min; this.min = min;
this.max = max; this.max = max;
this.checkFirst = max >= (Math.tan(90 * (Math.PI / 180))); this.checkFirst = max >= (Math.tan(90 * (Math.PI / 180)));
this.maxY = extent.getMaximumPoint().getBlockY(); this.maxY = extent.getMaxY();
this.minY = extent.getMinY();
this.overlay = overlay; this.overlay = overlay;
this.distance = distance; this.distance = distance;
} }
@ -77,7 +79,7 @@ public class AngleMask extends SolidBlockMask implements ResettableMask {
} }
int result = cacheHeights[index] & 0xFF; int result = cacheHeights[index] & 0xFF;
if (y > result) { if (y > result) {
cacheHeights[index] = (byte) (result = lastY = extent.getNearestSurfaceTerrainBlock(x, z, lastY, 0, maxY)); cacheHeights[index] = (byte) (result = lastY = extent.getNearestSurfaceTerrainBlock(x, z, lastY, minY, maxY));
} }
return result; return result;
} catch (Throwable e) { } catch (Throwable e) {
@ -141,10 +143,10 @@ public class AngleMask extends SolidBlockMask implements ResettableMask {
if (!mask.test(x, y, z - 1)) { if (!mask.test(x, y, z - 1)) {
return true; return true;
} }
if (y < 255 && !mask.test(x, y + 1, z)) { if (y < maxY && !mask.test(x, y + 1, z)) {
return true; return true;
} }
return y > 0 && !mask.test(x, y - 1, z); return y > minY && !mask.test(x, y - 1, z);
} }
@Override @Override
@ -164,7 +166,7 @@ public class AngleMask extends SolidBlockMask implements ResettableMask {
return false; return false;
} }
if (overlay) { if (overlay) {
if (y < 255 && !adjacentAir(vector)) { if (y < maxY && !adjacentAir(vector)) {
return lastValue = false; return lastValue = false;
} }
} }

View File

@ -57,9 +57,10 @@ public class CachedMask extends AbstractDelegateMask implements ResettableMask {
return result; return result;
} catch (UnsupportedOperationException ignored) { } catch (UnsupportedOperationException ignored) {
boolean result = getMask().test(mutable.setComponents(x, y, z)); boolean result = getMask().test(mutable.setComponents(x, y, z));
if (y < 0 || y > 255) { // Assume that the mask won't be given y outside the world range
return result; // if (y < 0 || y > 255) {
} // return result;
//}
resetCache(); resetCache();
cache_checked.setOffset(x, z); cache_checked.setOffset(x, z);
cache_results.setOffset(x, z); cache_results.setOffset(x, z);

View File

@ -25,10 +25,10 @@ public class MaskedTargetBlock extends TargetBlock {
if (!mask.test(current.toBlockPoint())) { if (!mask.test(current.toBlockPoint())) {
if (searchForLastBlock) { if (searchForLastBlock) {
lastBlock = current; lastBlock = current;
if (lastBlock.getBlockY() <= 0 || lastBlock.getBlockY() >= world.getMaxY()) { if (lastBlock.getBlockY() <= world.getMinY() || lastBlock.getBlockY() >= world.getMaxY()) {
searchForLastBlock = false; searchForLastBlock = false;
} }
} else if (current.getBlockY() <= 0) { } else if (current.getBlockY() <= world.getMinY()) {
break; break;
} }
} else { } else {

View File

@ -9,7 +9,7 @@ import com.sk89q.worldedit.world.block.BlockTypes;
public class SurfaceMask extends AdjacentAnyMask { public class SurfaceMask extends AdjacentAnyMask {
public SurfaceMask(Extent extent) { public SurfaceMask(Extent extent) {
super(getMask(extent)); super(getMask(extent), extent.getMinY(), extent.getMaxY());
} }
public static AbstractExtentMask getMask(Extent extent) { public static AbstractExtentMask getMask(Extent extent) {

View File

@ -45,8 +45,8 @@ public class AngleColorPattern extends AnglePattern {
int x = vector.getBlockX(); int x = vector.getBlockX();
int y = vector.getBlockY(); int y = vector.getBlockY();
int z = vector.getBlockZ(); int z = vector.getBlockZ();
int height = extent.getNearestSurfaceTerrainBlock(x, z, y, 0, maxY); int height = extent.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY);
if (height > 0) { if (height > minY) {
BlockState below = extent.getBlock(x, height - 1, z); BlockState below = extent.getBlock(x, height - 1, z);
if (!below.getBlockType().getMaterial().isMovementBlocker()) { if (!below.getBlockType().getMaterial().isMovementBlocker()) {
return Integer.MAX_VALUE; return Integer.MAX_VALUE;

View File

@ -13,6 +13,7 @@ public abstract class AnglePattern extends AbstractPattern {
public final double factor; public final double factor;
public final Extent extent; public final Extent extent;
public final int maxY; public final int maxY;
public final int minY;
public final int distance; public final int distance;
/** /**
@ -23,9 +24,10 @@ public abstract class AnglePattern extends AbstractPattern {
*/ */
public AnglePattern(Extent extent, int distance) { public AnglePattern(Extent extent, int distance) {
this.extent = new ExtentHeightCacher(extent); this.extent = new ExtentHeightCacher(extent);
this.maxY = extent.getMaximumPoint().getBlockY(); this.maxY = extent.getMaxY();
this.minY = extent.getMinY();
this.distance = distance; this.distance = distance;
this.factor = (1D / distance) * (1D / 255); this.factor = (1D / distance) * (1D / maxY);
} }
public <T extends BlockStateHolder<T>> int getSlope(T block, BlockVector3 vector, Extent extent) { public <T extends BlockStateHolder<T>> int getSlope(T block, BlockVector3 vector, Extent extent) {
@ -36,29 +38,29 @@ public abstract class AnglePattern extends AbstractPattern {
return -1; return -1;
} }
int slope = Math.abs( int slope = Math.abs(
extent.getNearestSurfaceTerrainBlock(x + distance, z, y, 0, maxY) - extent extent.getNearestSurfaceTerrainBlock(x + distance, z, y, minY, maxY) - extent
.getNearestSurfaceTerrainBlock(x - distance, z, y, 0, maxY)) * 7; .getNearestSurfaceTerrainBlock(x - distance, z, y, minY, maxY)) * 7;
slope += Math.abs(extent.getNearestSurfaceTerrainBlock( slope += Math.abs(extent.getNearestSurfaceTerrainBlock(
x, x,
z + distance, z + distance,
y, y,
0, minY,
maxY maxY
) - extent.getNearestSurfaceTerrainBlock(x, z - distance, y, 0, maxY)) * 7; ) - extent.getNearestSurfaceTerrainBlock(x, z - distance, y, minY, maxY)) * 7;
slope += Math.abs(extent.getNearestSurfaceTerrainBlock( slope += Math.abs(extent.getNearestSurfaceTerrainBlock(
x + distance, x + distance,
z + distance, z + distance,
y, y,
0, minY,
maxY maxY
) - extent.getNearestSurfaceTerrainBlock(x - distance, z - distance, y, 0, maxY)) * 5; ) - extent.getNearestSurfaceTerrainBlock(x - distance, z - distance, y, minY, maxY)) * 5;
slope += Math.abs(extent.getNearestSurfaceTerrainBlock( slope += Math.abs(extent.getNearestSurfaceTerrainBlock(
x - distance, x - distance,
z + distance, z + distance,
y, y,
0, minY,
maxY maxY
) - extent.getNearestSurfaceTerrainBlock(x + distance, z - distance, y, 0, maxY)) * 5; ) - extent.getNearestSurfaceTerrainBlock(x + distance, z - distance, y, minY, maxY)) * 5;
return slope; return slope;
} }

View File

@ -7,6 +7,7 @@ import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockTypes;
public class OffsetPattern extends AbstractPattern { public class OffsetPattern extends AbstractPattern {
@ -42,6 +43,9 @@ public class OffsetPattern extends AbstractPattern {
mutable.mutX(position.getX() + dx); mutable.mutX(position.getX() + dx);
mutable.mutY(position.getY() + dy); mutable.mutY(position.getY() + dy);
mutable.mutZ(position.getZ() + dz); mutable.mutZ(position.getZ() + dz);
if (mutable.getY() < minY || mutable.getY() > maxY) {
return BlockTypes.AIR.getDefaultState().toBaseBlock();
}
return pattern.applyBlock(mutable); return pattern.applyBlock(mutable);
} }
@ -49,10 +53,10 @@ public class OffsetPattern extends AbstractPattern {
public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException {
mutable.mutX(get.getX() + dx); mutable.mutX(get.getX() + dx);
mutable.mutY(get.getY() + dy); mutable.mutY(get.getY() + dy);
if (mutable.getY() < minY || mutable.getY() > maxY) { mutable.mutZ(get.getZ() + dz);
if (mutable.getY() < extent.getMinY() || mutable.getY() > extent.getMaxY()) {
return false; return false;
} }
mutable.mutZ(get.getZ() + dz);
return pattern.apply(extent, get, mutable); return pattern.apply(extent, get, mutable);
} }

View File

@ -7,6 +7,7 @@ import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockTypes;
import java.util.SplittableRandom; import java.util.SplittableRandom;
@ -32,8 +33,8 @@ public class RandomOffsetPattern extends AbstractPattern {
* @param dx offset x * @param dx offset x
* @param dy offset y * @param dy offset y
* @param dz offset z * @param dz offset z
* @param minY min applicable y (inclusive * @param minY min applicable y (inclusive)
* @param maxY max applicable y (inclusive * @param maxY max applicable y (inclusive)
*/ */
public RandomOffsetPattern(Pattern pattern, int dx, int dy, int dz, int minY, int maxY) { public RandomOffsetPattern(Pattern pattern, int dx, int dy, int dz, int minY, int maxY) {
this.pattern = pattern; this.pattern = pattern;
@ -54,6 +55,9 @@ public class RandomOffsetPattern extends AbstractPattern {
mutable.mutX((position.getX() + r.nextInt(dx2) - dx)); mutable.mutX((position.getX() + r.nextInt(dx2) - dx));
mutable.mutY((position.getY() + r.nextInt(dy2) - dy)); mutable.mutY((position.getY() + r.nextInt(dy2) - dy));
mutable.mutZ((position.getZ() + r.nextInt(dz2) - dz)); mutable.mutZ((position.getZ() + r.nextInt(dz2) - dz));
if (mutable.getY() < minY || mutable.getY() > maxY) {
return BlockTypes.AIR.getDefaultState().toBaseBlock();
}
return pattern.applyBlock(mutable); return pattern.applyBlock(mutable);
} }
@ -62,7 +66,7 @@ public class RandomOffsetPattern extends AbstractPattern {
mutable.mutX((set.getX() + r.nextInt(dx2) - dx)); mutable.mutX((set.getX() + r.nextInt(dx2) - dx));
mutable.mutY((set.getY() + r.nextInt(dy2) - dy)); mutable.mutY((set.getY() + r.nextInt(dy2) - dy));
mutable.mutZ((set.getZ() + r.nextInt(dz2) - dz)); mutable.mutZ((set.getZ() + r.nextInt(dz2) - dz));
if (mutable.getY() < minY || mutable.getY() > maxY) { if (mutable.getY() < extent.getMinY() || mutable.getY() > extent.getMaxY()) {
return false; return false;
} }
return pattern.apply(extent, get, mutable); return pattern.apply(extent, get, mutable);

View File

@ -7,6 +7,7 @@ import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockTypes;
public class RelativePattern extends AbstractPattern implements ResettablePattern { public class RelativePattern extends AbstractPattern implements ResettablePattern {
@ -37,6 +38,9 @@ public class RelativePattern extends AbstractPattern implements ResettablePatter
mutable.mutX(pos.getX() - origin.getX()); mutable.mutX(pos.getX() - origin.getX());
mutable.mutY(pos.getY() - origin.getY()); mutable.mutY(pos.getY() - origin.getY());
mutable.mutZ(pos.getZ() - origin.getZ()); mutable.mutZ(pos.getZ() - origin.getZ());
if (mutable.getY() < minY || mutable.getY() > maxY) {
return BlockTypes.AIR.getDefaultState().toBaseBlock();
}
return pattern.applyBlock(mutable); return pattern.applyBlock(mutable);
} }
@ -47,10 +51,10 @@ public class RelativePattern extends AbstractPattern implements ResettablePatter
} }
mutable.mutX(set.getX() - origin.getX()); mutable.mutX(set.getX() - origin.getX());
mutable.mutY(set.getY() - origin.getY()); mutable.mutY(set.getY() - origin.getY());
if (mutable.getY() < minY || mutable.getY() > maxY) { mutable.mutZ(set.getZ() - origin.getZ());
if (mutable.getY() < extent.getMinY() || mutable.getY() > extent.getMaxY()) {
return false; return false;
} }
mutable.mutZ(set.getZ() - origin.getZ());
return pattern.apply(extent, get, mutable); return pattern.apply(extent, get, mutable);
} }

View File

@ -66,6 +66,12 @@ public class SolidRandomOffsetPattern extends AbstractPattern {
mutable.mutX(position.getX() + r.nextInt(dx2) - dx); mutable.mutX(position.getX() + r.nextInt(dx2) - dx);
mutable.mutY(position.getY() + r.nextInt(dy2) - dy); mutable.mutY(position.getY() + r.nextInt(dy2) - dy);
mutable.mutZ(position.getZ() + r.nextInt(dz2) - dz); mutable.mutZ(position.getZ() + r.nextInt(dz2) - dz);
if (mutable.getY() < minY || mutable.getY() > maxY) {
return BlockTypes.AIR.getDefaultState().toBaseBlock();
}
if (mutable.getY() < minY || mutable.getY() > maxY) {
return BlockTypes.AIR.getDefaultState().toBaseBlock();
}
BaseBlock block = pattern.applyBlock(mutable); BaseBlock block = pattern.applyBlock(mutable);
if (block.getMaterial().isSolid()) { if (block.getMaterial().isSolid()) {
return block; return block;
@ -77,10 +83,10 @@ public class SolidRandomOffsetPattern extends AbstractPattern {
public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException {
mutable.mutX(set.getX() + r.nextInt(dx2) - dx); mutable.mutX(set.getX() + r.nextInt(dx2) - dx);
mutable.mutY(set.getY() + r.nextInt(dy2) - dy); mutable.mutY(set.getY() + r.nextInt(dy2) - dy);
if (mutable.getY() < minY || mutable.getY() > maxY) { mutable.mutZ(set.getZ() + r.nextInt(dz2) - dz);
if (mutable.getY() < extent.getMinY() || mutable.getY() > extent.getMaxY()) {
return false; return false;
} }
mutable.mutZ(set.getZ() + r.nextInt(dz2) - dz);
BaseBlock block = pattern.applyBlock(mutable); BaseBlock block = pattern.applyBlock(mutable);
if (block.getMaterial().isSolid()) { if (block.getMaterial().isSolid()) {
return pattern.apply(extent, get, mutable); return pattern.apply(extent, get, mutable);

View File

@ -1,6 +1,8 @@
package com.fastasyncworldedit.core.function.pattern; package com.fastasyncworldedit.core.function.pattern;
import com.fastasyncworldedit.core.math.MutableBlockVector3; import com.fastasyncworldedit.core.math.MutableBlockVector3;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.pattern.AbstractPattern; import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.visitor.BreadthFirstSearch; import com.sk89q.worldedit.function.visitor.BreadthFirstSearch;
@ -41,6 +43,11 @@ public class SurfaceRandomOffsetPattern extends AbstractPattern {
allowed = new MutableBlockVector3[buffer.length]; allowed = new MutableBlockVector3[buffer.length];
} }
@Override
public boolean apply(final Extent extent, final BlockVector3 get, final BlockVector3 set) throws WorldEditException {
return super.apply(extent, get, set);
}
@Override @Override
public BaseBlock applyBlock(BlockVector3 position) { public BaseBlock applyBlock(BlockVector3 position) {
return pattern.applyBlock(travel(position)); return pattern.applyBlock(travel(position));

View File

@ -22,13 +22,12 @@ public class AboveVisitor extends RecursiveVisitor {
* @param mask the mask * @param mask the mask
* @param function the function * @param function the function
* @param baseY the base Y * @param baseY the base Y
* @param depth maximum number of iterations
* @param minY min visitable y value. Inclusive.
* @param maxY max visitable y value. Inclusive.
*/ */
public AboveVisitor(Mask mask, RegionFunction function, int baseY) { public AboveVisitor(Mask mask, RegionFunction function, int baseY, int depth, int minY, int maxY) {
this(mask, function, baseY, Integer.MAX_VALUE); super(mask, function, depth, minY, maxY);
}
public AboveVisitor(Mask mask, RegionFunction function, int baseY, int depth) {
super(mask, function, depth);
checkNotNull(mask); checkNotNull(mask);
this.baseY = baseY; this.baseY = baseY;

View File

@ -19,12 +19,22 @@ public class DirectionalVisitor extends RecursiveVisitor {
private final BlockVector3 origin; private final BlockVector3 origin;
private final BlockVector3 dirVec; private final BlockVector3 dirVec;
public DirectionalVisitor(Mask mask, RegionFunction function, BlockVector3 origin, BlockVector3 direction) { /**
this(mask, function, origin, direction, Integer.MAX_VALUE); * New visitor. Only visits in the given direction
} *
* @param mask block mask
public DirectionalVisitor(Mask mask, RegionFunction function, BlockVector3 origin, BlockVector3 direction, int distance) { * @param function function to apply
super(mask, function, distance); * @param origin start position
* @param direction allowable direction to visit between
* @param distance max number of iterations
* @param minY min visitable y value. Inclusive.
* @param maxY max visitable y value. Inclusive.
*/
public DirectionalVisitor(
Mask mask, RegionFunction function, BlockVector3 origin, BlockVector3 direction, int distance,
int minY, int maxY
) {
super(mask, function, distance, minY, maxY);
checkNotNull(mask); checkNotNull(mask);
this.origin = origin; this.origin = origin;
this.dirVec = direction; this.dirVec = direction;

View File

@ -102,6 +102,7 @@ public class DiskStorageHistory extends FaweStreamChangeSet {
nbttFile = new File(folder, index + ".nbtt"); nbttFile = new File(folder, index + ".nbtt");
entfFile = new File(folder, index + ".entf"); entfFile = new File(folder, index + ".entf");
enttFile = new File(folder, index + ".entt"); enttFile = new File(folder, index + ".entt");
//Switch file ending due to new (sort-of) format. (Added e for Extended height)
bdFile = new File(folder, index + ".bd"); bdFile = new File(folder, index + ".bd");
bioFile = new File(folder, index + ".bio"); bioFile = new File(folder, index + ".bio");
} }
@ -431,6 +432,8 @@ public class DiskStorageHistory extends FaweStreamChangeSet {
final FaweInputStream gis = MainUtil.getCompressedIS(fis); final FaweInputStream gis = MainUtil.getCompressedIS(fis);
// skip mode // skip mode
gis.skipFully(1); gis.skipFully(1);
// skip version
gis.skipFully(1);
// origin // origin
ox = ((gis.read() << 24) + (gis.read() << 16) + (gis.read() << 8) + gis.read()); ox = ((gis.read() << 24) + (gis.read() << 16) + (gis.read() << 8) + gis.read());
oz = ((gis.read() << 24) + (gis.read() << 16) + (gis.read() << 8) + gis.read()); oz = ((gis.read() << 24) + (gis.read() << 16) + (gis.read() << 8) + gis.read());

View File

@ -157,7 +157,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
addEntityCreate(tag); addEntityCreate(tag);
} }
} }
for (int layer = 0; layer < 16; layer++) { for (int layer = get.getMinSectionIndex(); layer <= get.getMaxSectionIndex(); layer++) {
if (!set.hasSection(layer)) { if (!set.hasSection(layer)) {
continue; continue;
} }
@ -172,6 +172,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
char[] blocksSet; char[] blocksSet;
System.arraycopy(set.load(layer), 0, (blocksSet = new char[4096]), 0, 4096); System.arraycopy(set.load(layer), 0, (blocksSet = new char[4096]), 0, 4096);
// Account for negative layers
int by = layer << 4; int by = layer << 4;
for (int y = 0, index = 0; y < 16; y++) { for (int y = 0, index = 0; y < 16; y++) {
int yy = y + by; int yy = y + by;
@ -195,14 +196,21 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
BiomeType[] biomes = set.getBiomes(); BiomeType[] biomes = set.getBiomes();
if (biomes != null) { if (biomes != null) {
for (int y = 0, index = 0; y < 64; y++) { int index = 0;
for (int z = 0; z < 4; z++) { for (int layer = get.getMinSectionIndex(); layer <= get.getMaxSectionIndex(); layer++) {
for (int x = 0; x < 4; x++, index++) { if (!set.hasBiomes(layer)) {
BiomeType newBiome = biomes[index]; continue;
if (newBiome != null) { }
BiomeType oldBiome = get.getBiomeType(x, y, z); int yy = layer << 4;
if (oldBiome != newBiome) { for (int y = 0; y < 4; y++) {
addBiomeChange(bx + (x << 2), y << 2, bz + (z << 2), oldBiome, newBiome); for (int z = 0; z < 4; z++) {
for (int x = 0; x < 4; x++, index++) {
BiomeType newBiome = biomes[index];
if (newBiome != null) {
BiomeType oldBiome = get.getBiomeType(x, y, z);
if (oldBiome != newBiome) {
addBiomeChange(bx + (x << 2), yy + (y << 2), bz + (z << 2), oldBiome, newBiome);
}
} }
} }
} }
@ -341,11 +349,17 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
return addWriteTask(writeTask, Fawe.isMainThread()); return addWriteTask(writeTask, Fawe.isMainThread());
} }
public Future<?> addWriteTask(Runnable writeTask, boolean completeNow) { public Future<?> addWriteTask(final Runnable writeTask, final boolean completeNow) {
AbstractChangeSet.this.waitingCombined.incrementAndGet(); AbstractChangeSet.this.waitingCombined.incrementAndGet();
Runnable wrappedTask = () -> { Runnable wrappedTask = () -> {
try { try {
writeTask.run(); writeTask.run();
} catch (Throwable t) {
if (completeNow) {
throw t;
} else {
t.printStackTrace();
}
} finally { } finally {
if (AbstractChangeSet.this.waitingCombined.decrementAndGet() <= 0) { if (AbstractChangeSet.this.waitingCombined.decrementAndGet() <= 0) {
synchronized (AbstractChangeSet.this.waitingAsync) { synchronized (AbstractChangeSet.this.waitingAsync) {

View File

@ -28,11 +28,16 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
/**
* FAWE stream ChangeSet offering support for extended-height worlds
*/
public abstract class FaweStreamChangeSet extends AbstractChangeSet { public abstract class FaweStreamChangeSet extends AbstractChangeSet {
public static final int HEADER_SIZE = 9; public static final int HEADER_SIZE = 9;
private static final int version = 1;
private int mode; private int mode;
private final int compression; private final int compression;
private final int minY;
protected FaweStreamIdDelegate idDel; protected FaweStreamIdDelegate idDel;
protected FaweStreamPositionDelegate posDel; protected FaweStreamPositionDelegate posDel;
@ -44,6 +49,7 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
public FaweStreamChangeSet(World world, int compression, boolean storeRedo, boolean smallLoc) { public FaweStreamChangeSet(World world, int compression, boolean storeRedo, boolean smallLoc) {
super(world); super(world);
this.compression = compression; this.compression = compression;
this.minY = world.getMinY();
init(storeRedo, smallLoc); init(storeRedo, smallLoc);
} }
@ -139,6 +145,10 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
@Override @Override
public void write(OutputStream out, int x, int y, int z) throws IOException { public void write(OutputStream out, int x, int y, int z) throws IOException {
if (y < 0 || y > 255) {
throw new UnsupportedOperationException("y cannot be outside range 0-255 for " +
"small-edits=true");
}
int rx = -lx + (lx = x); int rx = -lx + (lx = x);
int ry = -ly + (ly = y); int ry = -ly + (ly = y);
int rz = -lz + (lz = z); int rz = -lz + (lz = z);
@ -174,7 +184,7 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
}; };
} else { } else {
posDel = new FaweStreamPositionDelegate() { posDel = new FaweStreamPositionDelegate() {
final byte[] buffer = new byte[5]; final byte[] buffer = new byte[6];
int lx; int lx;
int ly; int ly;
int lz; int lz;
@ -188,7 +198,8 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
stream.write(((rx) >> 8) & 0xff); stream.write(((rx) >> 8) & 0xff);
stream.write((rz) & 0xff); stream.write((rz) & 0xff);
stream.write(((rz) >> 8) & 0xff); stream.write(((rz) >> 8) & 0xff);
stream.write((byte) ry); stream.write((ry) & 0xff);
stream.write(((ry) >> 8) & 0xff);
} }
@Override @Override
@ -199,7 +210,7 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
@Override @Override
public int readY(FaweInputStream is) throws IOException { public int readY(FaweInputStream is) throws IOException {
return (ly = (ly + (buffer[4]))) & 0xFF; return ly = (ly + (buffer[4] & 0xFF) + (buffer[5] << 8));
} }
@Override @Override
@ -212,6 +223,8 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
public void writeHeader(OutputStream os, int x, int y, int z) throws IOException { public void writeHeader(OutputStream os, int x, int y, int z) throws IOException {
os.write(mode); os.write(mode);
// Allows for version detection of history in case of changes to format.
os.write(version);
setOrigin(x, z); setOrigin(x, z);
os.write((byte) (x >> 24)); os.write((byte) (x >> 24));
os.write((byte) (x >> 16)); os.write((byte) (x >> 16));
@ -227,6 +240,10 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
public void readHeader(InputStream is) throws IOException { public void readHeader(InputStream is) throws IOException {
// skip mode // skip mode
int mode = is.read(); int mode = is.read();
int version = is.read();
if (version != FaweStreamChangeSet.version) {
throw new UnsupportedOperationException(String.format("Version %s history not supported!", version));
}
// origin // origin
int x = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read()); int x = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read());
int z = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read()); int z = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read());
@ -290,10 +307,6 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
public abstract NBTInputStream getTileRemoveIS() throws IOException; public abstract NBTInputStream getTileRemoveIS() throws IOException;
protected int blockSize; protected int blockSize;
public int entityCreateSize;
public int entityRemoveSize;
public int tileCreateSize;
public int tileRemoveSize;
private int originX; private int originX;
private int originZ; private int originZ;
@ -325,9 +338,12 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
} }
@Override @Override
public void addBiomeChange(int x, int y, int z, BiomeType from, BiomeType to) { public void addBiomeChange(int bx, int by, int bz, BiomeType from, BiomeType to) {
blockSize++; blockSize++;
try { try {
int x = bx >> 2;
int y = by >> 2;
int z = bz >> 2;
FaweOutputStream os = getBiomeOS(); FaweOutputStream os = getBiomeOS();
os.write((byte) (x >> 24)); os.write((byte) (x >> 24));
os.write((byte) (x >> 16)); os.write((byte) (x >> 16));
@ -337,7 +353,9 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
os.write((byte) (z >> 16)); os.write((byte) (z >> 16));
os.write((byte) (z >> 8)); os.write((byte) (z >> 8));
os.write((byte) (z)); os.write((byte) (z));
os.write((byte) (y)); // only need to store biomes in the 4x4x4 chunks so only need one byte for y still (signed byte -128 -> 127)
// means -512 -> 508. Add 128 to avoid negative value casting.
os.write((byte) (y + 128));
os.writeVarInt(from.getInternalId()); os.writeVarInt(from.getInternalId());
os.writeVarInt(to.getInternalId()); os.writeVarInt(to.getInternalId());
} catch (Throwable e) { } catch (Throwable e) {
@ -465,9 +483,9 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
try { try {
int int1 = is.read(); int int1 = is.read();
if (int1 != -1) { if (int1 != -1) {
int x = ((int1 << 24) + (is.read() << 16) + (is.read() << 8) + is.read()); int x = ((int1 << 24) + (is.read() << 16) + (is.read() << 8) + is.read()) << 2;
int z = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read()); int z = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read()) << 2;
int y = is.read(); int y = (is.read() - 128) << 2;
int from = is.readVarInt(); int from = is.readVarInt();
int to = is.readVarInt(); int to = is.readVarInt();
change.setBiome(x, y, z, from, to); change.setBiome(x, y, z, from, to);

View File

@ -3,26 +3,26 @@ package com.fastasyncworldedit.core.math;
import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.collection.IAdaptedMap; import com.fastasyncworldedit.core.util.collection.IAdaptedMap;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import java.util.Map; import java.util.Map;
public class BlockVector3ChunkMap<T> implements IAdaptedMap<BlockVector3, T, Short, T> { public class BlockVector3ChunkMap<T> implements IAdaptedMap<BlockVector3, T, Integer, T> {
private final Short2ObjectArrayMap<T> map = new Short2ObjectArrayMap<>(); private final Int2ObjectArrayMap<T> map = new Int2ObjectArrayMap<>();
@Override @Override
public Map<Short, T> getParent() { public Map<Integer, T> getParent() {
return map; return map;
} }
@Override @Override
public Short adaptKey(BlockVector3 key) { public Integer adaptKey(BlockVector3 key) {
return MathMan.tripleBlockCoord(key.getX(), key.getY(), key.getZ()); return MathMan.tripleBlockCoord(key.getX(), key.getY(), key.getZ());
} }
@Override @Override
public BlockVector3 adaptKey2(Short key) { public BlockVector3 adaptKey2(Integer key) {
int x = MathMan.untripleBlockCoordX(key); int x = MathMan.untripleBlockCoordX(key);
int y = MathMan.untripleBlockCoordY(key); int y = MathMan.untripleBlockCoordY(key);
int z = MathMan.untripleBlockCoordZ(key); int z = MathMan.untripleBlockCoordZ(key);
@ -40,23 +40,23 @@ public class BlockVector3ChunkMap<T> implements IAdaptedMap<BlockVector3, T, Sho
} }
public T put(int x, int y, int z, T value) { public T put(int x, int y, int z, T value) {
short key = MathMan.tripleBlockCoord(x, y, z); int key = MathMan.tripleBlockCoord(x, y, z);
return map.put(key, value); return map.put(key, value);
} }
public T get(int x, int y, int z) { public T get(int x, int y, int z) {
short key = MathMan.tripleBlockCoord(x, y, z); int key = MathMan.tripleBlockCoord(x, y, z);
return map.get(key); return map.get(key);
} }
public T remove(int x, int y, int z) { public T remove(int x, int y, int z) {
short key = MathMan.tripleBlockCoord(x, y, z); int key = MathMan.tripleBlockCoord(x, y, z);
return map.remove(key); return map.remove(key);
} }
public boolean contains(int x, int y, int z) { public boolean contains(int x, int y, int z) {
short key = MathMan.tripleBlockCoord(x, y, z); int key = MathMan.tripleBlockCoord(x, y, z);
return map.containsKey(key); return map.containsKey(key);
} }

View File

@ -44,14 +44,14 @@ public class BlockVectorSet extends AbstractCollection<BlockVector3> implements
int newSize = count + size; int newSize = count + size;
if (newSize > index) { if (newSize > index) {
int localIndex = index - count; int localIndex = index - count;
BlockVector3 pos = set.getIndex(localIndex); MutableBlockVector3 pos = set.getIndex(localIndex);
if (pos != null) { if (pos != null) {
int pair = entry.getIntKey(); int pair = entry.getIntKey();
int cx = MathMan.unpairX(pair); int cx = MathMan.unpairX(pair);
int cz = MathMan.unpairY(pair); int cz = MathMan.unpairY(pair);
pos = pos.mutX((cx << 11) + pos.getBlockX()); pos.mutX((cx << 11) + pos.getBlockX());
pos = pos.mutZ((cz << 11) + pos.getBlockZ()); pos.mutZ((cz << 11) + pos.getBlockZ());
return pos; return pos.toImmutable();
} }
} }
count += newSize; count += newSize;
@ -91,7 +91,7 @@ public class BlockVectorSet extends AbstractCollection<BlockVector3> implements
if (!entries.hasNext()) { if (!entries.hasNext()) {
return Collections.emptyIterator(); return Collections.emptyIterator();
} }
return new Iterator<BlockVector3>() { return new Iterator<>() {
Int2ObjectMap.Entry<LocalBlockVectorSet> entry = entries.next(); Int2ObjectMap.Entry<LocalBlockVectorSet> entry = entries.next();
Iterator<BlockVector3> entryIter = entry.getValue().iterator(); Iterator<BlockVector3> entryIter = entry.getValue().iterator();
final MutableBlockVector3 mutable = new MutableBlockVector3(); final MutableBlockVector3 mutable = new MutableBlockVector3();

View File

@ -11,14 +11,14 @@ import java.util.Set;
/** /**
* The LocalBlockVectorSet is a Memory and CPU optimized Set for storing BlockVectors which are all in a local region * The LocalBlockVectorSet is a Memory and CPU optimized Set for storing BlockVectors which are all in a local region
* - All vectors must be in a 2048 * 2048 area centered around the first entry * - All vectors must be in a 2048 * 512 * 2048 area centered around the first entry
* - This will use 8 bytes for every 64 BlockVectors (about 800x less than a HashSet) * - This will use 8 bytes for every 64 BlockVectors (about 800x less than a HashSet)
*/ */
public class LocalBlockVectorSet implements Set<BlockVector3> { public class LocalBlockVectorSet implements Set<BlockVector3> {
private final SparseBitSet set;
private int offsetX; private int offsetX;
private int offsetZ; private int offsetZ;
private final SparseBitSet set;
public LocalBlockVectorSet() { public LocalBlockVectorSet() {
offsetX = offsetZ = Integer.MAX_VALUE; offsetX = offsetZ = Integer.MAX_VALUE;
@ -42,7 +42,8 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
} }
public boolean contains(int x, int y, int z) { public boolean contains(int x, int y, int z) {
return set.get(MathMan.tripleSearchCoords(x - offsetX, y, z - offsetZ)); // take 128 to fit -256<y<255
return set.get(MathMan.tripleSearchCoords(x - offsetX, y - 128, z - offsetZ));
} }
@Override @Override
@ -67,12 +68,15 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
if (size() < length * length * length) { if (size() < length * length * length) {
int index = -1; int index = -1;
while ((index = set.nextSetBit(index + 1)) != -1) { while ((index = set.nextSetBit(index + 1)) != -1) {
int b1 = (byte) (index >> 0) & 0xFF; int b1 = (index & 0xFF);
int b2 = (byte) (index >> 8) & 0x7F; int b2 = (index >> 8) & 0xff;
int b3 = (byte) (index >> 15) & 0xFF; int b3 = (index >> 15) & 0xFF;
int b4 = (byte) (index >> 23) & 0xFF; int b4 = (index >> 23) & 0xFF;
if (Math.abs((offsetX + (((b3 + ((MathMan.unpair8x(b2)) << 8)) << 21) >> 21)) - x) <= radius && Math.abs((offsetZ + (((b4 + ((MathMan int ix = (offsetX + (b3 + (((b2 & 0x7)) << 8)) << 21) >> 21;
.unpair8y(b2)) << 8)) << 21) >> 21)) - z) <= radius && Math.abs((b1) - y) <= radius) { // Add 128 as we shift y by 128 to fit -256<y<255
int iy = 128 + b1 * (((b2 >> 6) & 0x1) == 0 ? 1 : -1);
int iz = (offsetZ + (b4 + (((b2 >> 3) & 0x7) << 8)) << 21) >> 21;
if (Math.abs(ix - x) <= radius && Math.abs(iz - z) <= radius && Math.abs(iy - y) <= radius) {
return true; return true;
} }
} }
@ -100,7 +104,7 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
this.offsetZ = z; this.offsetZ = z;
} }
protected BlockVector3 getIndex(int getIndex) { protected MutableBlockVector3 getIndex(int getIndex) {
int size = size(); int size = size();
if (getIndex > size) { if (getIndex > size) {
return null; return null;
@ -110,13 +114,15 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
index = set.nextSetBit(index + 1); index = set.nextSetBit(index + 1);
} }
if (index != -1) { if (index != -1) {
int b1 = (byte) (index >> 0) & 0xFF; int b1 = (index & 0xFF);
int b2 = (byte) (index >> 8) & 0x7F; int b2 = (index >> 8) & 0xff;
int b3 = (byte) (index >> 15) & 0xFF; int b3 = (index >> 15) & 0xFF;
int b4 = (byte) (index >> 23) & 0xFF; int b4 = (index >> 23) & 0xFF;
int x = offsetX + (((b3 + (MathMan.unpair8x(b2) << 8)) << 21) >> 21); int x = (offsetX + (b3 + (((b2 & 0x7)) << 8)) << 21) >> 21;
int z = offsetZ + (((b4 + (MathMan.unpair8y(b2) << 8)) << 21) >> 21); // Add 128 as we shift y by 128 to fit -256<y<255
return MutableBlockVector3.get(x, b1, z); int y = 128 + b1 * (((b2 >> 6) & 0x1) == 0 ? 1 : -1);
int z = (offsetZ + (b4 + (((b2 >> 3) & 0x7) << 8)) << 21) >> 21;
return MutableBlockVector3.get(x, y, z);
} }
return null; return null;
} }
@ -125,9 +131,9 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
@Override @Override
public Iterator<BlockVector3> iterator() { public Iterator<BlockVector3> iterator() {
return new Iterator<BlockVector3>() { return new Iterator<BlockVector3>() {
final MutableBlockVector3 mutable = new MutableBlockVector3(0, 0, 0);
int index = set.nextSetBit(0); int index = set.nextSetBit(0);
int previous = -1; int previous = -1;
final MutableBlockVector3 mutable = new MutableBlockVector3(0, 0, 0);
@Override @Override
public void remove() { public void remove() {
@ -143,12 +149,16 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
public BlockVector3 next() { public BlockVector3 next() {
if (index != -1) { if (index != -1) {
int b1 = (index & 0xFF); int b1 = (index & 0xFF);
int b2 = ((byte) (index >> 8)) & 0x7F; int b2 = (index >> 8) & 0xff;
int b3 = ((byte) (index >> 15)) & 0xFF; int b3 = (index >> 15) & 0xFF;
int b4 = ((byte) (index >> 23)) & 0xFF; int b4 = (index >> 23) & 0xFF;
mutable.mutX(offsetX + (((b3 + (MathMan.unpair8x(b2) << 8)) << 21) >> 21)); int x = (offsetX + (b3 + (((b2 & 0x7)) << 8)) << 21) >> 21;
mutable.mutY(b1); // Add 128 as we shift y by 128 to fit -256<y<255
mutable.mutZ(offsetZ + (((b4 + (MathMan.unpair8y(b2) << 8)) << 21) >> 21)); int y = 128 + b1 * (((b2 >> 6) & 0x1) == 0 ? 1 : -1);
int z = (offsetZ + (b4 + (((b2 >> 3) & 0x7) << 8)) << 21) >> 21;
mutable.mutX(x);
mutable.mutY(y);
mutable.mutZ(z);
previous = index; previous = index;
index = set.nextSetBit(index + 1); index = set.nextSetBit(index + 1);
return mutable; return mutable;
@ -175,12 +185,14 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
index = set.nextSetBit(index); index = set.nextSetBit(index);
int b1 = (index & 0xFF); int b1 = (index & 0xFF);
int b2 = ((byte) (index >> 8)) & 0x7F; int b2 = (index >> 8) & 0xff;
int b3 = ((byte) (index >> 15)) & 0xFF; int b3 = (index >> 15) & 0xFF;
int b4 = ((byte) (index >> 23)) & 0xFF; int b4 = (index >> 23) & 0xFF;
int x = offsetX + (((b3 + ((MathMan.unpair8x(b2)) << 8)) << 21) >> 21); int x = (offsetX + (b3 + (((b2 & 0x7)) << 8)) << 21) >> 21;
int z = offsetZ + (((b4 + ((MathMan.unpair8y(b2)) << 8)) << 21) >> 21); // Add 128 as we shift y by 128 to fit -256<y<255
array[i] = (T) BlockVector3.at(x, b1, z); int y = 128 + b1 * (((b2 >> 6) & 0x1) == 0 ? 1 : -1);
int z = (offsetZ + (b4 + (((b2 >> 3) & 0x7) << 8)) << 21) >> 21;
array[i] = (T) BlockVector3.at(x, y, z);
index++; index++;
} }
return array; return array;
@ -195,7 +207,7 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
if (relX > 1023 || relX < -1024 || relZ > 1023 || relZ < -1024) { if (relX > 1023 || relX < -1024 || relZ > 1023 || relZ < -1024) {
return false; return false;
} }
return y >= 0 && y <= 256; return y >= -128 && y <= 383;
} }
public boolean add(int x, int y, int z) { public boolean add(int x, int y, int z) {
@ -209,8 +221,8 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"LocalVectorSet can only contain vectors within 1024 blocks (cuboid) of the first entry. "); "LocalVectorSet can only contain vectors within 1024 blocks (cuboid) of the first entry. ");
} }
if (y < 0 || y > 255) { if (y < -128 || y > 383) {
throw new UnsupportedOperationException("LocalVectorSet can only contain vectors from y elem:[0,255]"); throw new UnsupportedOperationException("LocalVectorSet can only contain vectors from y elem:[-128,383]");
} }
int index = getIndex(x, y, z); int index = getIndex(x, y, z);
if (set.get(index)) { if (set.get(index)) {
@ -227,11 +239,17 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
} }
private int getIndex(BlockVector3 vector) { private int getIndex(BlockVector3 vector) {
return MathMan.tripleSearchCoords(vector.getBlockX() - offsetX, vector.getBlockY(), vector.getBlockZ() - offsetZ); // take 128 to fit -256<y<255
return MathMan.tripleSearchCoords(
vector.getBlockX() - offsetX,
vector.getBlockY() - 128,
vector.getBlockZ() - offsetZ
);
} }
private int getIndex(int x, int y, int z) { private int getIndex(int x, int y, int z) {
return MathMan.tripleSearchCoords(x - offsetX, y, z - offsetZ); // take 128 to fit -256<y<255
return MathMan.tripleSearchCoords(x - offsetX, y - 128, z - offsetZ);
} }
public boolean remove(int x, int y, int z) { public boolean remove(int x, int y, int z) {
@ -240,7 +258,8 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
if (relX > 1023 || relX < -1024 || relZ > 1023 || relZ < -1024) { if (relX > 1023 || relX < -1024 || relZ > 1023 || relZ < -1024) {
return false; return false;
} }
int index = MathMan.tripleSearchCoords(relX, y, relZ); // take 128 to fit -256<y<255
int index = MathMan.tripleSearchCoords(relX, y - 128, relZ);
boolean value = set.get(index); boolean value = set.get(index);
set.clear(index); set.clear(index);
return value; return value;
@ -283,12 +302,16 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
index = set.nextSetBit(index + 1); index = set.nextSetBit(index + 1);
int b1 = (index & 0xFF); int b1 = (index & 0xFF);
int b2 = ((byte) (index >> 8)) & 0x7F; int b2 = (index >> 8) & 0xff;
int b3 = ((byte) (index >> 15)) & 0xFF; int b3 = (index >> 15) & 0xFF;
int b4 = ((byte) (index >> 23)) & 0xFF; int b4 = (index >> 23) & 0xFF;
mVec.mutX(offsetX + (((b3 + ((MathMan.unpair8x(b2)) << 8)) << 21) >> 21)); int x = (offsetX + (b3 + (((b2 & 0x7)) << 8)) << 21) >> 21;
mVec.mutY(b1); // Add 128 as we shift y by 128 to fit -256<y<255
mVec.mutZ(offsetZ + (((b4 + ((MathMan.unpair8y(b2)) << 8)) << 21) >> 21)); int y = 128 + b1 * (((b2 >> 6) & 0x1) == 0 ? 1 : -1);
int z = (offsetZ + (b4 + (((b2 >> 3) & 0x7) << 8)) << 21) >> 21;
mVec.mutX(x);
mVec.mutY(y);
mVec.mutZ(z);
if (!c.contains(mVec)) { if (!c.contains(mVec)) {
result = true; result = true;
set.clear(index); set.clear(index);
@ -312,21 +335,17 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
index = set.nextSetBit(index + 1); index = set.nextSetBit(index + 1);
int b1 = (index & 0xFF); int b1 = (index & 0xFF);
int b2 = ((byte) (index >> 8)) & 0x7F; int b2 = (index >> 8) & 0xff;
int b3 = ((byte) (index >> 15)) & 0xFF; int b3 = (index >> 15) & 0xFF;
int b4 = ((byte) (index >> 23)) & 0xFF; int b4 = (index >> 23) & 0xFF;
int x = offsetX + (((b3 + ((MathMan.unpair8x(b2)) << 8)) << 21) >> 21); int x = (offsetX + (b3 + (((b2 & 0x7)) << 8)) << 21) >> 21;
int z = offsetZ + (((b4 + ((MathMan.unpair8y(b2)) << 8)) << 21) >> 21); // Add 128 as we shift y by 128 to fit -256<y<255
visitor.run(x, b1, z, index); int y = 128 + b1 * (((b2 >> 6) & 0x1) == 0 ? 1 : -1);
int z = (offsetZ + (b4 + (((b2 >> 3) & 0x7) << 8)) << 21) >> 21;
visitor.run(x, y, z, index);
} }
} }
public interface BlockVectorSetVisitor {
void run(int x, int y, int z, int index);
}
@Override @Override
public void clear() { public void clear() {
offsetZ = Integer.MAX_VALUE; offsetZ = Integer.MAX_VALUE;
@ -334,4 +353,10 @@ public class LocalBlockVectorSet implements Set<BlockVector3> {
set.clear(); set.clear();
} }
public interface BlockVectorSetVisitor {
void run(int x, int y, int z, int index);
}
} }

View File

@ -1,6 +1,5 @@
package com.fastasyncworldedit.core.queue; package com.fastasyncworldedit.core.queue;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.extent.processor.EmptyBatchProcessor; import com.fastasyncworldedit.core.extent.processor.EmptyBatchProcessor;
import com.fastasyncworldedit.core.extent.processor.MultiBatchProcessor; import com.fastasyncworldedit.core.extent.processor.MultiBatchProcessor;
import com.fastasyncworldedit.core.extent.processor.ProcessorScope; import com.fastasyncworldedit.core.extent.processor.ProcessorScope;
@ -42,7 +41,7 @@ public interface IBatchProcessor {
*/ */
default boolean trimY(IChunkSet set, int minY, int maxY) { default boolean trimY(IChunkSet set, int minY, int maxY) {
int minLayer = (minY - 1) >> 4; int minLayer = (minY - 1) >> 4;
for (int layer = 0; layer <= minLayer; layer++) { for (int layer = set.getMinSectionIndex(); layer <= minLayer; layer++) {
if (set.hasSection(layer)) { if (set.hasSection(layer)) {
if (layer == minLayer) { if (layer == minLayer) {
char[] arr = set.load(layer); char[] arr = set.load(layer);
@ -57,7 +56,7 @@ public interface IBatchProcessor {
} }
} }
int maxLayer = (maxY + 1) >> 4; int maxLayer = (maxY + 1) >> 4;
for (int layer = maxLayer; layer < FaweCache.IMP.CHUNK_LAYERS; layer++) { for (int layer = maxLayer; layer < set.getMaxSectionIndex(); layer++) {
if (set.hasSection(layer)) { if (set.hasSection(layer)) {
if (layer == minLayer) { if (layer == minLayer) {
char[] arr = set.load(layer); char[] arr = set.load(layer);
@ -74,10 +73,8 @@ public interface IBatchProcessor {
try { try {
int layer = (minY - 15) >> 4; int layer = (minY - 15) >> 4;
while (layer < (maxY + 15) >> 4) { while (layer < (maxY + 15) >> 4) {
if (layer > -1) { if (set.hasSection(layer)) {
if (set.hasSection(layer)) { return true;
return true;
}
} }
layer++; layer++;
} }

View File

@ -11,7 +11,6 @@ import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.registry.BlockRegistry; import com.sk89q.worldedit.world.registry.BlockRegistry;
import org.jetbrains.annotations.Range;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
@ -23,7 +22,14 @@ import java.util.stream.IntStream;
*/ */
public interface IBlocks extends Trimable { public interface IBlocks extends Trimable {
boolean hasSection(@Range(from = 0, to = 15) int layer); /**
* Returns if the chunk has a BLOCKS section at the given layer. May not be indicative of presence
* of entities, tile entites, biomes, etc.
*
* @param layer chunk section layer
* @return if blocks/a block section is present
*/
boolean hasSection(int layer);
char[] load(int layer); char[] load(int layer);
@ -38,7 +44,7 @@ public interface IBlocks extends Trimable {
BiomeType getBiomeType(int x, int y, int z); BiomeType getBiomeType(int x, int y, int z);
default int getBitMask() { default int getBitMask() {
return IntStream.range(0, FaweCache.IMP.CHUNK_LAYERS).filter(this::hasSection) return IntStream.range(getMinSectionIndex(), getMaxSectionIndex() + 1).filter(this::hasSection)
.map(layer -> (1 << layer)).sum(); .map(layer -> (1 << layer)).sum();
} }
@ -48,6 +54,21 @@ public interface IBlocks extends Trimable {
IBlocks reset(); IBlocks reset();
/**
* Get the number of stores sections
*/
int getSectionCount();
/**
* Max ChunkSection array index
*/
int getMaxSectionIndex();
/**
* Min ChunkSection array index
*/
int getMinSectionIndex();
default byte[] toByteArray(boolean full, boolean stretched) { default byte[] toByteArray(boolean full, boolean stretched) {
return toByteArray(null, getBitMask(), full, stretched); return toByteArray(null, getBitMask(), full, stretched);
} }
@ -61,7 +82,7 @@ public interface IBlocks extends Trimable {
.queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry(); .queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry();
FastByteArrayOutputStream sectionByteArray = new FastByteArrayOutputStream(buffer); FastByteArrayOutputStream sectionByteArray = new FastByteArrayOutputStream(buffer);
try (FaweOutputStream sectionWriter = new FaweOutputStream(sectionByteArray)) { try (FaweOutputStream sectionWriter = new FaweOutputStream(sectionByteArray)) {
for (int layer = 0; layer < FaweCache.IMP.CHUNK_LAYERS; layer++) { for (int layer = 0; layer < this.getSectionCount(); layer++) {
if (!this.hasSection(layer) || (bitMask & (1 << layer)) == 0) { if (!this.hasSection(layer) || (bitMask & (1 << layer)) == 0) {
continue; continue;
} }

View File

@ -17,7 +17,6 @@ import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockStateHolder;
import org.jetbrains.annotations.Range;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -37,7 +36,7 @@ public interface IChunkExtent<T extends IChunk> extends Extent {
T getOrCreateChunk(int chunkX, int chunkZ); T getOrCreateChunk(int chunkX, int chunkZ);
@Override @Override
default <B extends BlockStateHolder<B>> boolean setBlock(int x, @Range(from = 0, to = 255) int y, int z, B state) { default <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B state) {
final IChunk chunk = getOrCreateChunk(x >> 4, z >> 4); final IChunk chunk = getOrCreateChunk(x >> 4, z >> 4);
return chunk.setBlock(x & 15, y, z & 15, state); return chunk.setBlock(x & 15, y, z & 15, state);
} }

View File

@ -48,19 +48,43 @@ public interface IChunkGet extends IBlocks, Trimable, InputExtent, ITileInput {
CompoundTag getEntity(UUID uuid); CompoundTag getEntity(UUID uuid);
void setCreateCopy(boolean createCopy);
boolean isCreateCopy(); boolean isCreateCopy();
void setCreateCopy(boolean createCopy);
@Nullable @Nullable
default IChunkGet getCopy() { default IChunkGet getCopy() {
return null; return null;
} }
void setLightingToGet(char[][] lighting); /**
* Flush the block lighting array (section*blocks) to the chunk GET between the given section indices. Negative allowed.
*
* @param lighting lighting array
* @param startSectionIndex lowest section index
* @param endSectionIndex highest section index
*/
void setLightingToGet(char[][] lighting, int startSectionIndex, int endSectionIndex);
void setSkyLightingToGet(char[][] lighting); /**
* Flush the sky lighting array (section*blocks) to the chunk GET between the given section indices. Negative allowed.
*
* @param lighting sky lighting array
* @param startSectionIndex lowest section index
* @param endSectionIndex highest section index
*/
void setSkyLightingToGet(char[][] lighting, int startSectionIndex, int endSectionIndex);
void setHeightmapToGet(HeightMapType type, int[] data); void setHeightmapToGet(HeightMapType type, int[] data);
/**
* Max y value for the chunk's world (inclusive)
*/
int getMaxY();
/**
* Min y value for the chunk's world (inclusive)
*/
int getMinY();
} }

View File

@ -107,4 +107,12 @@ public interface IChunkSet extends IBlocks, OutputExtent {
return null; return null;
} }
/**
* If the given layer has biomes stored to be set to the world. Can be negative
*
* @param layer layer to check
* @return if the layer has biomes stored to be set to the world
*/
boolean hasBiomes(int layer);
} }

View File

@ -25,11 +25,17 @@ public class Flood {
private int chunkYLayer; private int chunkYLayer;
private int chunkZ; private int chunkZ;
private final ConcurrentLinkedQueue<int[]> queuePool = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue<int[]> queuePool = new ConcurrentLinkedQueue<>();
private final int minSectionIndex;
private final int maxSectionIndex;
private final int sectionCount;
public Flood(int maxBranch, int maxDepth, Direction[] directions) { public Flood(int maxBranch, int maxDepth, Direction[] directions, int minSectionIndex, int maxSectionIndex) {
this.maxBranch = maxBranch; this.maxBranch = maxBranch;
this.maxDepth = maxDepth; this.maxDepth = maxDepth;
this.directions = directions; this.directions = directions;
this.minSectionIndex = minSectionIndex;
this.maxSectionIndex = maxSectionIndex;
this.sectionCount = maxSectionIndex - minSectionIndex + 1;
this.queues = new int[27][]; this.queues = new int[27][];
this.visits = new long[27][]; this.visits = new long[27][];
@ -64,7 +70,7 @@ public class Flood {
int chunkX = x >> 4; int chunkX = x >> 4;
int chunkZ = z >> 4; int chunkZ = z >> 4;
long pair = MathMan.pairInt(chunkX, chunkZ); long pair = MathMan.pairInt(chunkX, chunkZ);
int layer = y >> 4; int layer = (y >> 4) - minSectionIndex;
int[] section = getOrCreateQueue(pair, layer); int[] section = getOrCreateQueue(pair, layer);
int val = (x & 15) + ((z & 15) << 4) + ((y & 15) << 8) + (depth << 12); int val = (x & 15) + ((z & 15) << 4) + ((y & 15) << 8) + (depth << 12);
push(section, val); push(section, val);
@ -73,7 +79,7 @@ public class Flood {
private int[] getOrCreateQueue(long pair, int layer) { private int[] getOrCreateQueue(long pair, int layer) {
int[][] arrs = chunkQueues.get(pair); int[][] arrs = chunkQueues.get(pair);
if (arrs == null) { if (arrs == null) {
chunkQueues.put(pair, arrs = new int[16][]); chunkQueues.put(pair, arrs = new int[sectionCount][]);
} }
int[] section = arrs[layer]; int[] section = arrs[layer];
if (section == null) { if (section == null) {
@ -154,7 +160,7 @@ public class Flood {
if (visit == null || queue == null) { if (visit == null || queue == null) {
long pair = MathMan.pairInt(this.chunkX + nextX, this.chunkZ + nextZ); long pair = MathMan.pairInt(this.chunkX + nextX, this.chunkZ + nextZ);
int layer = this.chunkYLayer + nextY; int layer = this.chunkYLayer + nextY;
if (layer < 0 || layer > 15) { if (layer < minSectionIndex || layer > maxSectionIndex) {
continue; continue;
} }
queues[sectionIndex] = queue = getOrCreateQueue(pair, layer); queues[sectionIndex] = queue = getOrCreateQueue(pair, layer);

View File

@ -52,6 +52,10 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
// Chunks currently being queued / worked on // Chunks currently being queued / worked on
private final Long2ObjectLinkedOpenHashMap<IQueueChunk> chunks = new Long2ObjectLinkedOpenHashMap<>(); private final Long2ObjectLinkedOpenHashMap<IQueueChunk> chunks = new Long2ObjectLinkedOpenHashMap<>();
private World world = null;
private int minY = 0;
private int maxY = 255;
private IChunkCache<IChunkGet> cacheGet; private IChunkCache<IChunkGet> cacheGet;
private IChunkCache<IChunkSet> cacheSet; private IChunkCache<IChunkSet> cacheSet;
private boolean initialized; private boolean initialized;
@ -68,7 +72,15 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
private final ReentrantLock getChunkLock = new ReentrantLock(); private final ReentrantLock getChunkLock = new ReentrantLock();
private World world = null; public SingleThreadQueueExtent() {}
/**
* New instance given inclusive world height bounds.
*/
public SingleThreadQueueExtent(int minY, int maxY) {
this.minY = minY;
this.maxY = maxY;
}
/** /**
* Safety check to ensure that the thread being used matches the one being initialized on. - Can * Safety check to ensure that the thread being used matches the one being initialized on. - Can
@ -111,6 +123,16 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
return fastmode; return fastmode;
} }
@Override
public int getMinY() {
return minY;
}
@Override
public int getMaxY() {
return maxY;
}
/** /**
* Resets the queue. * Resets the queue.
*/ */
@ -142,6 +164,8 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
@Override @Override
public synchronized void init(Extent extent, IChunkCache<IChunkGet> get, IChunkCache<IChunkSet> set) { public synchronized void init(Extent extent, IChunkCache<IChunkGet> get, IChunkCache<IChunkSet> set) {
reset(); reset();
this.minY = extent.getMinY();
this.maxY = extent.getMaxY();
currentThread = Thread.currentThread(); currentThread = Thread.currentThread();
if (get == null) { if (get == null) {
get = (x, z) -> { get = (x, z) -> {

View File

@ -20,14 +20,21 @@ public class BitSetBlocks implements IChunkSet {
private final MemBlockSet.RowZ row; private final MemBlockSet.RowZ row;
private final BlockState blockState; private final BlockState blockState;
private final int minSectionIndex;
private final int maxSectionIndex;
private final int layers;
public BitSetBlocks(BlockState blockState) { public BitSetBlocks(BlockState blockState, int minSectionIndex, int maxSectionIndex) {
this.row = new MemBlockSet.RowZ(); this.row = new MemBlockSet.RowZ(minSectionIndex, maxSectionIndex);
this.blockState = blockState; this.blockState = blockState;
this.minSectionIndex = minSectionIndex;
this.maxSectionIndex = maxSectionIndex;
this.layers = maxSectionIndex - minSectionIndex + 1;
} }
@Override @Override
public boolean hasSection(int layer) { public boolean hasSection(int layer) {
layer -= minSectionIndex;
return row.rows[layer] != MemBlockSet.NULL_ROW_Y; return row.rows[layer] != MemBlockSet.NULL_ROW_Y;
} }
@ -39,19 +46,21 @@ public class BitSetBlocks implements IChunkSet {
@Override @Override
public <T extends BlockStateHolder<T>> boolean setBlock(int x, int y, int z, T holder) { public <T extends BlockStateHolder<T>> boolean setBlock(int x, int y, int z, T holder) {
row.set(null, x, y, z); y -= minSectionIndex << 4;
row.set(null, x, y, z, minSectionIndex, maxSectionIndex);
return true; return true;
} }
@Override @Override
public void setBlocks(int layer, char[] data) { public void setBlocks(int layer, char[] data) {
layer -= minSectionIndex;
row.reset(layer); row.reset(layer);
int by = layer << 4; int by = layer << 4;
for (int y = 0, index = 0; y < 16; y++) { for (int y = 0, index = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) { for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++, index++) { for (int x = 0; x < 16; x++, index++) {
if (data[index] != 0) { if (data[index] != 0) {
row.set(null, x, by + y, z); row.set(null, x, by + y, z, minSectionIndex, maxSectionIndex);
} }
} }
} }
@ -114,6 +123,7 @@ public class BitSetBlocks implements IChunkSet {
@Override @Override
public char[] load(int layer) { public char[] load(int layer) {
layer -= minSectionIndex;
char[] arr = FaweCache.IMP.SECTION_BITS_TO_CHAR.get(); char[] arr = FaweCache.IMP.SECTION_BITS_TO_CHAR.get();
MemBlockSet.IRow nullRowY = row.getRow(layer); MemBlockSet.IRow nullRowY = row.getRow(layer);
if (nullRowY instanceof MemBlockSet.RowY) { if (nullRowY instanceof MemBlockSet.RowY) {
@ -189,6 +199,26 @@ public class BitSetBlocks implements IChunkSet {
return this; return this;
} }
@Override
public boolean hasBiomes(final int layer) {
return false;
}
@Override
public int getSectionCount() {
return layers;
}
@Override
public int getMaxSectionIndex() {
return minSectionIndex;
}
@Override
public int getMinSectionIndex() {
return maxSectionIndex;
}
@Override @Override
public boolean trim(boolean aggressive) { public boolean trim(boolean aggressive) {
return false; return false;

View File

@ -7,7 +7,6 @@ import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.block.BlockTypesCache;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Range;
public abstract class CharBlocks implements IBlocks { public abstract class CharBlocks implements IBlocks {
@ -15,30 +14,30 @@ public abstract class CharBlocks implements IBlocks {
protected static final Section FULL = new Section() { protected static final Section FULL = new Section() {
@Override @Override
public final char[] get(CharBlocks blocks, int layer) { public char[] get(CharBlocks blocks, int layer) {
return blocks.blocks[layer]; return blocks.blocks[layer];
} }
// Ignore aggressive switch here. // Ignore aggressive switch here.
@Override @Override
public char[] get(CharBlocks blocks, @Range(from = 0, to = 15) int layer, boolean aggressive) { public char[] get(CharBlocks blocks, int layer, boolean aggressive) {
return blocks.blocks[layer]; return blocks.blocks[layer];
} }
@Override @Override
public final boolean isFull() { public boolean isFull() {
return true; return true;
} }
}; };
protected final Section empty = new Section() { protected final Section empty = new Section() {
@Override @Override
public final synchronized char[] get(CharBlocks blocks, int layer) { public synchronized char[] get(CharBlocks blocks, int layer) {
// Defaults to aggressive as it should only be avoided where we know we've reset a chunk during an edit // Defaults to aggressive as it should only be avoided where we know we've reset a chunk during an edit
return get(blocks, layer, true); return get(blocks, layer, true);
} }
@Override @Override
public synchronized char[] get(CharBlocks blocks, @Range(from = 0, to = 15) int layer, boolean aggressive) { public synchronized char[] get(CharBlocks blocks, int layer, boolean aggressive) {
char[] arr = blocks.blocks[layer]; char[] arr = blocks.blocks[layer];
if (arr == null) { if (arr == null) {
arr = blocks.blocks[layer] = blocks.update(layer, null, aggressive); arr = blocks.blocks[layer] = blocks.update(layer, null, aggressive);
@ -58,17 +57,26 @@ public abstract class CharBlocks implements IBlocks {
} }
@Override @Override
public final boolean isFull() { public boolean isFull() {
return false; return false;
} }
}; };
public final char[][] blocks; public char[][] blocks;
public final Section[] sections; public Section[] sections;
protected int minSectionIndex;
protected int maxSectionIndex;
protected int sectionCount;
public CharBlocks() { /**
blocks = new char[16][]; * New instance given initial min/max section indices. Can be negative.
sections = new Section[16]; */
for (int i = 0; i < 16; i++) { public CharBlocks(int minSectionIndex, int maxSectionIndex) {
this.minSectionIndex = minSectionIndex;
this.maxSectionIndex = maxSectionIndex;
this.sectionCount = maxSectionIndex - minSectionIndex + 1;
blocks = new char[sectionCount][];
sections = new Section[sectionCount];
for (int i = 0; i < sectionCount; i++) {
sections[i] = empty; sections[i] = empty;
} }
} }
@ -76,7 +84,7 @@ public abstract class CharBlocks implements IBlocks {
@Override @Override
public synchronized boolean trim(boolean aggressive) { public synchronized boolean trim(boolean aggressive) {
boolean result = true; boolean result = true;
for (int i = 0; i < 16; i++) { for (int i = 0; i < sectionCount; i++) {
if (!sections[i].isFull() && blocks[i] != null) { if (!sections[i].isFull() && blocks[i] != null) {
blocks[i] = null; blocks[i] = null;
} else { } else {
@ -99,13 +107,14 @@ public abstract class CharBlocks implements IBlocks {
@Override @Override
public synchronized IChunkSet reset() { public synchronized IChunkSet reset() {
for (int i = 0; i < 16; i++) { for (int i = 0; i < sectionCount; i++) {
sections[i] = empty; sections[i] = empty;
} }
return null; return null;
} }
public synchronized void reset(@Range(from = 0, to = 15) int layer) { public synchronized void reset(int layer) {
layer -= minSectionIndex;
sections[layer] = empty; sections[layer] = empty;
} }
@ -121,12 +130,14 @@ public abstract class CharBlocks implements IBlocks {
// Not synchronized as any subsequent methods called from this class will be, or the section shouldn't appear as loaded anyway. // Not synchronized as any subsequent methods called from this class will be, or the section shouldn't appear as loaded anyway.
@Override @Override
public boolean hasSection(@Range(from = 0, to = 15) int layer) { public boolean hasSection(int layer) {
return sections[layer].isFull(); layer -= minSectionIndex;
return layer >= 0 && layer < sections.length && sections[layer].isFull();
} }
@Override @Override
public char[] load(@Range(from = 0, to = 15) int layer) { public char[] load(int layer) {
layer -= minSectionIndex;
synchronized (sections[layer]) { synchronized (sections[layer]) {
return sections[layer].get(this, layer); return sections[layer].get(this, layer);
} }
@ -137,17 +148,17 @@ public abstract class CharBlocks implements IBlocks {
return BlockTypesCache.states[get(x, y, z)]; return BlockTypesCache.states[get(x, y, z)];
} }
public char get(int x, @Range(from = 0, to = 255) int y, int z) { public char get(int x, int y, int z) {
final int layer = y >> 4; int layer = y >> 4;
final int index = (y & 15) << 8 | z << 4 | x; final int index = (y & 15) << 8 | z << 4 | x;
if (layer >= sections.length || layer < 0) { if (layer > maxSectionIndex || layer < minSectionIndex) {
return 0; return 0;
} }
return sections[layer].get(this, layer, index); return get(layer, index);
} }
// Not synchronized as it refers to a synchronized method and includes nothing that requires synchronization // Not synchronized as it refers to a synchronized method and includes nothing that requires synchronization
public void set(int x, @Range(from = 0, to = 255) int y, int z, char value) { public void set(int x, int y, int z, char value) {
final int layer = y >> 4; final int layer = y >> 4;
final int index = (y & 15) << 8 | z << 4 | x; final int index = (y & 15) << 8 | z << 4 | x;
try { try {
@ -163,24 +174,26 @@ public abstract class CharBlocks implements IBlocks {
Section Section
*/ */
public final char get(@Range(from = 0, to = 15) int layer, int index) { public final char get(int layer, int index) {
layer -= minSectionIndex;
return sections[layer].get(this, layer, index); return sections[layer].get(this, layer, index);
} }
public synchronized final void set(@Range(from = 0, to = 15) int layer, int index, char value) throws public synchronized final void set(int layer, int index, char value) throws
ArrayIndexOutOfBoundsException { ArrayIndexOutOfBoundsException {
layer -= minSectionIndex;
sections[layer].set(this, layer, index, value); sections[layer].set(this, layer, index, value);
} }
public abstract static class Section { public abstract static class Section {
public abstract char[] get(CharBlocks blocks, @Range(from = 0, to = 15) int layer); public abstract char[] get(CharBlocks blocks, int layer);
public abstract char[] get(CharBlocks blocks, @Range(from = 0, to = 15) int layer, boolean aggressive); public abstract char[] get(CharBlocks blocks, int layer, boolean aggressive);
public abstract boolean isFull(); public abstract boolean isFull();
public final char get(CharBlocks blocks, @Range(from = 0, to = 15) int layer, int index) { public final char get(CharBlocks blocks, int layer, int index) {
char[] section = get(blocks, layer); char[] section = get(blocks, layer);
if (section == null) { if (section == null) {
blocks.reset(layer); blocks.reset(layer);
@ -189,7 +202,7 @@ public abstract class CharBlocks implements IBlocks {
return section[index]; return section[index];
} }
public final void set(CharBlocks blocks, @Range(from = 0, to = 15) int layer, int index, char value) { public final void set(CharBlocks blocks, int layer, int index, char value) {
get(blocks, layer)[index] = value; get(blocks, layer)[index] = value;
} }

View File

@ -10,6 +10,13 @@ import java.util.Arrays;
public abstract class CharGetBlocks extends CharBlocks implements IChunkGet { public abstract class CharGetBlocks extends CharBlocks implements IChunkGet {
/**
* New instance given the min/max section indices
*/
public CharGetBlocks(final int minSectionIndex, final int maxSectionIndex) {
super(minSectionIndex, maxSectionIndex);
}
@Override @Override
public BaseBlock getFullBlock(int x, int y, int z) { public BaseBlock getFullBlock(int x, int y, int z) {
BlockState state = BlockTypesCache.states[get(x, y, z)]; BlockState state = BlockTypesCache.states[get(x, y, z)];
@ -18,7 +25,7 @@ public abstract class CharGetBlocks extends CharBlocks implements IChunkGet {
@Override @Override
public boolean trim(boolean aggressive) { public boolean trim(boolean aggressive) {
for (int i = 0; i < 16; i++) { for (int i = 0; i < sectionCount; i++) {
sections[i] = empty; sections[i] = empty;
blocks[i] = null; blocks[i] = null;
} }
@ -36,6 +43,7 @@ public abstract class CharGetBlocks extends CharBlocks implements IChunkGet {
@Override @Override
public synchronized boolean trim(boolean aggressive, int layer) { public synchronized boolean trim(boolean aggressive, int layer) {
layer -= minSectionIndex;
sections[layer] = empty; sections[layer] = empty;
blocks[layer] = null; blocks[layer] = null;
return true; return true;
@ -47,4 +55,9 @@ public abstract class CharGetBlocks extends CharBlocks implements IChunkGet {
return null; return null;
} }
@Override
public int getSectionCount() {
return sectionCount;
}
} }

View File

@ -11,7 +11,6 @@ import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockStateHolder;
import org.jetbrains.annotations.Range;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -45,6 +44,8 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
private int bitMask = -1; private int bitMask = -1;
private CharSetBlocks() { private CharSetBlocks() {
// Expand as we go
super(0, 15);
} }
@Override @Override
@ -59,9 +60,10 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
@Override @Override
public BiomeType getBiomeType(int x, int y, int z) { public BiomeType getBiomeType(int x, int y, int z) {
if (biomes == null) { if (biomes == null || (y >> 4) < minSectionIndex || (y >> 4) > maxSectionIndex) {
return null; return null;
} }
y -= minSectionIndex << 4;
return biomes[(y >> 2) << 4 | (z >> 2) << 2 | x >> 2]; return biomes[(y >> 2) << 4 | (z >> 2) << 2 | x >> 2];
} }
@ -92,15 +94,18 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
@Override @Override
public boolean setBiome(int x, int y, int z, BiomeType biome) { public boolean setBiome(int x, int y, int z, BiomeType biome) {
updateSectionIndexRange(y >> 4);
y -= minSectionIndex << 4;
if (biomes == null) { if (biomes == null) {
biomes = new BiomeType[1024]; biomes = new BiomeType[64 * sectionCount];
} }
biomes[(y >> 2) << 4 | (z >> 2) << 2 | x >> 2] = biome; biomes[(y >> 2) << 4 | (z >> 2) << 2 | x >> 2] = biome;
return true; return true;
} }
@Override @Override
public <T extends BlockStateHolder<T>> boolean setBlock(int x, @Range(from = 0, to = 255) int y, int z, T holder) { public <T extends BlockStateHolder<T>> boolean setBlock(int x, int y, int z, T holder) {
updateSectionIndexRange(y >> 4);
set(x, y, z, holder.getOrdinalChar()); set(x, y, z, holder.getOrdinalChar());
holder.applyTileEntity(this, x, y, z); holder.applyTileEntity(this, x, y, z);
return true; return true;
@ -108,6 +113,8 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
@Override @Override
public void setBlocks(int layer, char[] data) { public void setBlocks(int layer, char[] data) {
updateSectionIndexRange(layer);
layer -= minSectionIndex;
this.blocks[layer] = data; this.blocks[layer] = data;
this.sections[layer] = data == null ? empty : FULL; this.sections[layer] = data == null ? empty : FULL;
} }
@ -123,38 +130,41 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
if (tiles == null) { if (tiles == null) {
tiles = new BlockVector3ChunkMap<>(); tiles = new BlockVector3ChunkMap<>();
} }
updateSectionIndexRange(y >> 4);
tiles.put(x, y, z, tile); tiles.put(x, y, z, tile);
return true; return true;
} }
@Override @Override
public void setBlockLight(int x, int y, int z, int value) { public void setBlockLight(int x, int y, int z, int value) {
updateSectionIndexRange(y >> 4);
if (light == null) { if (light == null) {
light = new char[16][]; light = new char[sectionCount][];
} }
final int layer = y >> 4; final int layer = (y >> 4) - minSectionIndex;
if (light[layer] == null) { if (light[layer] == null) {
char[] c = new char[4096]; char[] c = new char[4096];
Arrays.fill(c, (char) 16); Arrays.fill(c, (char) 16);
light[layer] = c; light[layer] = c;
} }
final int index = (y & 15) << 8 | (z & 15) << 4 | (x & 15); final int index = (y & 15) << 8 | (z & 15) << 4 | (x & 15);
light[y >> 4][index] = (char) value; light[layer][index] = (char) value;
} }
@Override @Override
public void setSkyLight(int x, int y, int z, int value) { public void setSkyLight(int x, int y, int z, int value) {
updateSectionIndexRange(y >> 4);
if (skyLight == null) { if (skyLight == null) {
skyLight = new char[16][]; skyLight = new char[sectionCount][];
} }
final int layer = y >> 4; final int layer = (y >> 4) - minSectionIndex;
if (skyLight[layer] == null) { if (skyLight[layer] == null) {
char[] c = new char[4096]; char[] c = new char[4096];
Arrays.fill(c, (char) 16); Arrays.fill(c, (char) 16);
skyLight[layer] = c; skyLight[layer] = c;
} }
final int index = (y & 15) << 8 | (z & 15) << 4 | (x & 15); final int index = (y & 15) << 8 | (z & 15) << 4 | (x & 15);
skyLight[y >> 4][index] = (char) value; skyLight[layer][index] = (char) value;
} }
@Override @Override
@ -167,17 +177,21 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
@Override @Override
public void setLightLayer(int layer, char[] toSet) { public void setLightLayer(int layer, char[] toSet) {
updateSectionIndexRange(layer);
if (light == null) { if (light == null) {
light = new char[16][]; light = new char[sectionCount][];
} }
layer -= minSectionIndex;
light[layer] = toSet; light[layer] = toSet;
} }
@Override @Override
public void setSkyLightLayer(int layer, char[] toSet) { public void setSkyLightLayer(int layer, char[] toSet) {
updateSectionIndexRange(layer);
if (skyLight == null) { if (skyLight == null) {
skyLight = new char[16][]; skyLight = new char[sectionCount][];
} }
layer -= minSectionIndex;
skyLight[layer] = toSet; skyLight[layer] = toSet;
} }
@ -193,8 +207,10 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
@Override @Override
public void removeSectionLighting(int layer, boolean sky) { public void removeSectionLighting(int layer, boolean sky) {
updateSectionIndexRange(layer);
layer -= minSectionIndex;
if (light == null) { if (light == null) {
light = new char[16][]; light = new char[sectionCount][];
} }
if (light[layer] == null) { if (light[layer] == null) {
light[layer] = new char[4096]; light[layer] = new char[4096];
@ -202,7 +218,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
Arrays.fill(light[layer], (char) 0); Arrays.fill(light[layer], (char) 0);
if (sky) { if (sky) {
if (skyLight == null) { if (skyLight == null) {
skyLight = new char[16][]; skyLight = new char[sectionCount][];
} }
if (skyLight[layer] == null) { if (skyLight[layer] == null) {
skyLight[layer] = new char[4096]; skyLight[layer] = new char[4096];
@ -213,14 +229,16 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
@Override @Override
public void setFullBright(int layer) { public void setFullBright(int layer) {
updateSectionIndexRange(layer);
layer -= minSectionIndex;
if (light == null) { if (light == null) {
light = new char[16][]; light = new char[sectionCount][];
} }
if (light[layer] == null) { if (light[layer] == null) {
light[layer] = new char[4096]; light[layer] = new char[4096];
} }
if (skyLight == null) { if (skyLight == null) {
skyLight = new char[16][]; skyLight = new char[sectionCount][];
} }
if (skyLight[layer] == null) { if (skyLight[layer] == null) {
skyLight[layer] = new char[4096]; skyLight[layer] = new char[4096];
@ -275,7 +293,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
if (biomes != null || light != null || skyLight != null) { if (biomes != null || light != null || skyLight != null) {
return false; return false;
} }
return IntStream.range(0, 16).noneMatch(this::hasSection); return IntStream.range(minSectionIndex, maxSectionIndex + 1).noneMatch(this::hasSection);
} }
@Override @Override
@ -288,4 +306,98 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet {
return null; return null;
} }
@Override
public boolean hasBiomes(int layer) {
layer -= minSectionIndex;
if (layer < 0 || layer >= sections.length) {
return false;
}
return biomes != null;
}
@Override
public char[] load(final int layer) {
updateSectionIndexRange(layer);
return super.load(layer);
}
@Override
public int getSectionCount() {
return sectionCount;
}
@Override
public int getMaxSectionIndex() {
return maxSectionIndex;
}
@Override
public int getMinSectionIndex() {
return minSectionIndex;
}
// Checks and updates the various section arrays against the new layer index
private void updateSectionIndexRange(int layer) {
if (layer >= minSectionIndex && layer <= maxSectionIndex) {
return;
}
if (layer < minSectionIndex) {
int diff = minSectionIndex - layer;
sectionCount += diff;
char[][] tmpBlocks = new char[sectionCount][];
Section[] tmpSections = new Section[sectionCount];
System.arraycopy(blocks, 0, tmpBlocks, diff, blocks.length);
System.arraycopy(sections, 0, tmpSections, diff, sections.length);
for (int i = 0; i < diff; i++) {
tmpSections[i] = empty;
}
blocks = tmpBlocks;
sections = tmpSections;
minSectionIndex = layer;
if (biomes != null) {
BiomeType[] tmpBiomes = new BiomeType[sectionCount * 64];
System.arraycopy(biomes, 0, tmpBiomes, 64*diff, biomes.length);
biomes = tmpBiomes;
}
if (light != null) {
char[][] tmplight = new char[sectionCount][];
System.arraycopy(light, 0, tmplight, diff, light.length);
light = tmplight;
}
if (skyLight != null) {
char[][] tmplight = new char[sectionCount][];
System.arraycopy(skyLight, 0, tmplight, diff, skyLight.length);
skyLight = tmplight;
}
} else {
int diff = layer - maxSectionIndex;
sectionCount += diff;
char[][] tmpBlocks = new char[sectionCount][];
Section[] tmpSections = new Section[sectionCount];
System.arraycopy(blocks, 0, tmpBlocks, 0, blocks.length);
System.arraycopy(sections, 0, tmpSections, 0, sections.length);
for (int i = sectionCount - diff; i < sectionCount; i++) {
tmpSections[i] = empty;
}
blocks = tmpBlocks;
sections = tmpSections;
maxSectionIndex = layer;
if (biomes != null) {
BiomeType[] tmpBiomes = new BiomeType[sectionCount * 64];
System.arraycopy(biomes, 0, tmpBiomes, 0, biomes.length);
biomes = tmpBiomes;
}
if (light != null) {
char[][] tmplight = new char[sectionCount][];
System.arraycopy(light, 0, tmplight, 0, light.length);
light = tmplight;
}
if (skyLight != null) {
char[][] tmplight = new char[sectionCount][];
System.arraycopy(skyLight, 0, tmplight, 0, skyLight.length);
skyLight = tmplight;
}
}
}
} }

View File

@ -78,17 +78,37 @@ public final class NullChunkGet implements IChunkGet {
} }
@Override @Override
public void setLightingToGet(char[][] lighting) { public void setLightingToGet(char[][] lighting, int startSectionIndex, int endSectionIndex) {
} }
@Override @Override
public void setSkyLightingToGet(char[][] lighting) { public void setSkyLightingToGet(char[][] lighting, int minSectionIndex, int maxSectionIndex) {
} }
@Override @Override
public void setHeightmapToGet(HeightMapType type, int[] data) { public void setHeightmapToGet(HeightMapType type, int[] data) {
} }
@Override
public int getMaxY() {
return 0;
}
@Override
public int getMinY() {
return 0;
}
@Override
public int getMaxSectionIndex() {
return 0;
}
@Override
public int getMinSectionIndex() {
return 0;
}
public boolean trim(boolean aggressive) { public boolean trim(boolean aggressive) {
return true; return true;
} }
@ -129,6 +149,11 @@ public final class NullChunkGet implements IChunkGet {
return null; return null;
} }
@Override
public int getSectionCount() {
return 0;
}
private NullChunkGet() { private NullChunkGet() {
} }

View File

@ -19,7 +19,6 @@ import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockStateHolder;
import org.jetbrains.annotations.Range;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Map; import java.util.Map;
@ -140,6 +139,12 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
return bitMask; return bitMask;
} }
@Override
public boolean hasBiomes(final int layer) {
// No need to go through delegate. hasBiomes is SET only.
return getOrCreateSet().hasBiomes(layer);
}
public boolean isInit() { public boolean isInit() {
return isInit; return isInit;
} }
@ -160,12 +165,12 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
} }
@Override @Override
public void setLightingToGet(char[][] lighting) { public void setLightingToGet(char[][] lighting, int minSectionIndex, int maxSectionIndex) {
delegate.setLightingToGet(this, lighting); delegate.setLightingToGet(this, lighting);
} }
@Override @Override
public void setSkyLightingToGet(char[][] lighting) { public void setSkyLightingToGet(char[][] lighting, int minSectionIndex, int maxSectionIndex) {
delegate.setSkyLightingToGet(this, lighting); delegate.setSkyLightingToGet(this, lighting);
} }
@ -174,8 +179,28 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
delegate.setHeightmapToGet(this, type, data); delegate.setHeightmapToGet(this, type, data);
} }
public void flushLightToGet(boolean heightmaps) { @Override
delegate.flushLightToGet(this, heightmaps); public int getMaxY() {
return getOrCreateGet().getMaxY();
}
@Override
public int getMinY() {
return getOrCreateGet().getMinY();
}
@Override
public int getMaxSectionIndex() {
return getOrCreateGet().getMaxSectionIndex();
}
@Override
public int getMinSectionIndex() {
return getOrCreateGet().getMinSectionIndex();
}
public void flushLightToGet() {
delegate.flushLightToGet(this);
} }
private static final IBlockDelegate BOTH = new IBlockDelegate() { private static final IBlockDelegate BOTH = new IBlockDelegate() {
@ -260,6 +285,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
public int getSkyLight(ChunkHolder chunk, int x, int y, int z) { public int getSkyLight(ChunkHolder chunk, int x, int y, int z) {
if (chunk.chunkSet.getSkyLight() != null) { if (chunk.chunkSet.getSkyLight() != null) {
int layer = y >> 4; int layer = y >> 4;
layer -= chunk.chunkSet.getMinSectionIndex();
if (chunk.chunkSet.getSkyLight()[layer] != null) { if (chunk.chunkSet.getSkyLight()[layer] != null) {
int setLightValue = chunk.chunkSet.getSkyLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)]; int setLightValue = chunk.chunkSet.getSkyLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)];
if (setLightValue < 16) { if (setLightValue < 16) {
@ -274,6 +300,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
public int getEmittedLight(ChunkHolder chunk, int x, int y, int z) { public int getEmittedLight(ChunkHolder chunk, int x, int y, int z) {
if (chunk.chunkSet.getLight() != null) { if (chunk.chunkSet.getLight() != null) {
int layer = y >> 4; int layer = y >> 4;
layer -= chunk.chunkSet.getMinSectionIndex();
if (chunk.chunkSet.getLight()[layer] != null) { if (chunk.chunkSet.getLight()[layer] != null) {
int setLightValue = chunk.chunkSet.getLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)]; int setLightValue = chunk.chunkSet.getLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)];
if (setLightValue < 16) { if (setLightValue < 16) {
@ -300,19 +327,21 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
} }
@Override @Override
public void flushLightToGet(ChunkHolder chunk, boolean heightmaps) { public void flushLightToGet(ChunkHolder chunk) {
chunk.chunkExisting.setLightingToGet(chunk.chunkSet.getLight()); chunk.chunkExisting.setLightingToGet(chunk.chunkSet.getLight(), chunk.chunkSet.getMinSectionIndex(),
chunk.chunkExisting.setSkyLightingToGet(chunk.chunkSet.getSkyLight()); chunk.chunkSet.getMaxSectionIndex());
chunk.chunkExisting.setSkyLightingToGet(chunk.chunkSet.getSkyLight(), chunk.chunkSet.getMinSectionIndex(),
chunk.chunkSet.getMaxSectionIndex());
} }
@Override @Override
public void setLightingToGet(ChunkHolder chunk, char[][] lighting) { public void setLightingToGet(ChunkHolder chunk, char[][] lighting) {
chunk.chunkExisting.setLightingToGet(lighting); chunk.chunkExisting.setLightingToGet(lighting, chunk.chunkSet.getMinSectionIndex(), chunk.chunkSet.getMaxSectionIndex());
} }
@Override @Override
public void setSkyLightingToGet(ChunkHolder chunk, char[][] lighting) { public void setSkyLightingToGet(ChunkHolder chunk, char[][] lighting) {
chunk.chunkExisting.setSkyLightingToGet(lighting); chunk.chunkExisting.setSkyLightingToGet(lighting, chunk.chunkSet.getMinSectionIndex(), chunk.chunkSet.getMaxSectionIndex());
} }
@Override @Override
@ -447,18 +476,18 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
} }
@Override @Override
public void flushLightToGet(ChunkHolder chunk, boolean heightmaps) { public void flushLightToGet(ChunkHolder chunk) {
// Do nothing as no lighting to flush to GET // Do nothing as no lighting to flush to GET
} }
@Override @Override
public void setLightingToGet(ChunkHolder chunk, char[][] lighting) { public void setLightingToGet(ChunkHolder chunk, char[][] lighting) {
chunk.chunkExisting.setLightingToGet(lighting); chunk.chunkExisting.setLightingToGet(lighting, chunk.chunkSet.getMinSectionIndex(), chunk.chunkSet.getMaxSectionIndex());
} }
@Override @Override
public void setSkyLightingToGet(ChunkHolder chunk, char[][] lighting) { public void setSkyLightingToGet(ChunkHolder chunk, char[][] lighting) {
chunk.chunkExisting.setSkyLightingToGet(lighting); chunk.chunkExisting.setSkyLightingToGet(lighting, chunk.chunkSet.getMinSectionIndex(), chunk.chunkSet.getMaxSectionIndex());
} }
@Override @Override
@ -493,7 +522,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
public <B extends BlockStateHolder<B>> boolean setBlock( public <B extends BlockStateHolder<B>> boolean setBlock(
ChunkHolder chunk, ChunkHolder chunk,
int x, int x,
@Range(from = 0, to = 255) int y, int y,
int z, int z,
B block B block
) { ) {
@ -568,6 +597,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
public int getSkyLight(ChunkHolder chunk, int x, int y, int z) { public int getSkyLight(ChunkHolder chunk, int x, int y, int z) {
if (chunk.chunkSet.getSkyLight() != null) { if (chunk.chunkSet.getSkyLight() != null) {
int layer = y >> 4; int layer = y >> 4;
layer -= chunk.chunkSet.getMinSectionIndex();
if (chunk.chunkSet.getSkyLight()[layer] != null) { if (chunk.chunkSet.getSkyLight()[layer] != null) {
int setLightValue = chunk.chunkSet.getSkyLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)]; int setLightValue = chunk.chunkSet.getSkyLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)];
if (setLightValue < 16) { if (setLightValue < 16) {
@ -585,6 +615,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
public int getEmittedLight(ChunkHolder chunk, int x, int y, int z) { public int getEmittedLight(ChunkHolder chunk, int x, int y, int z) {
if (chunk.chunkSet.getLight() != null) { if (chunk.chunkSet.getLight() != null) {
int layer = y >> 4; int layer = y >> 4;
layer -= chunk.chunkSet.getMinSectionIndex();
if (chunk.chunkSet.getLight()[layer] != null) { if (chunk.chunkSet.getLight()[layer] != null) {
int setLightValue = chunk.chunkSet.getLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)]; int setLightValue = chunk.chunkSet.getLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)];
if (setLightValue < 16) { if (setLightValue < 16) {
@ -623,12 +654,11 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
} }
@Override @Override
public void flushLightToGet(ChunkHolder chunk, boolean heightmaps) { public void flushLightToGet(ChunkHolder chunk) {
chunk.getOrCreateGet(); chunk.getOrCreateGet();
chunk.delegate = BOTH; chunk.delegate = BOTH;
chunk.chunkExisting.trim(false); chunk.chunkExisting.trim(false);
chunk.chunkExisting.setLightingToGet(chunk.chunkSet.getLight()); chunk.flushLightToGet();
chunk.chunkExisting.setSkyLightingToGet(chunk.chunkSet.getSkyLight());
} }
@Override @Override
@ -636,7 +666,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
chunk.getOrCreateGet(); chunk.getOrCreateGet();
chunk.delegate = BOTH; chunk.delegate = BOTH;
chunk.chunkExisting.trim(false); chunk.chunkExisting.trim(false);
chunk.chunkExisting.setLightingToGet(lighting); chunk.setLightingToGet(lighting, chunk.chunkSet.getMinSectionIndex(), chunk.chunkSet.getMaxSectionIndex());
} }
@Override @Override
@ -644,7 +674,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
chunk.getOrCreateGet(); chunk.getOrCreateGet();
chunk.delegate = BOTH; chunk.delegate = BOTH;
chunk.chunkExisting.trim(false); chunk.chunkExisting.trim(false);
chunk.chunkExisting.setSkyLightingToGet(lighting); chunk.setSkyLightingToGet(lighting, chunk.chunkSet.getMinSectionIndex(), chunk.chunkSet.getMaxSectionIndex());
} }
@Override @Override
@ -652,7 +682,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
chunk.getOrCreateGet(); chunk.getOrCreateGet();
chunk.delegate = BOTH; chunk.delegate = BOTH;
chunk.chunkExisting.trim(false); chunk.chunkExisting.trim(false);
chunk.chunkExisting.setHeightmapToGet(type, data); chunk.setHeightmapToGet(type, data);
} }
}; };
@ -804,7 +834,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
} }
@Override @Override
public void flushLightToGet(ChunkHolder chunk, boolean heightmaps) { public void flushLightToGet(ChunkHolder chunk) {
// Do nothing as no light to flush // Do nothing as no light to flush
} }
@ -813,7 +843,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
chunk.getOrCreateGet(); chunk.getOrCreateGet();
chunk.delegate = GET; chunk.delegate = GET;
chunk.chunkExisting.trim(false); chunk.chunkExisting.trim(false);
chunk.setLightingToGet(lighting); chunk.setLightingToGet(lighting, chunk.chunkSet.getMinSectionIndex(), chunk.chunkSet.getMaxSectionIndex());
} }
@Override @Override
@ -821,7 +851,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
chunk.getOrCreateGet(); chunk.getOrCreateGet();
chunk.delegate = GET; chunk.delegate = GET;
chunk.chunkExisting.trim(false); chunk.chunkExisting.trim(false);
chunk.setSkyLightingToGet(lighting); chunk.setSkyLightingToGet(lighting, chunk.chunkSet.getMinSectionIndex(), chunk.chunkSet.getMaxSectionIndex());
} }
@Override @Override
@ -889,6 +919,11 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
return this.trim(aggressive); return this.trim(aggressive);
} }
@Override
public int getSectionCount() {
return getOrCreateGet().getSectionCount();
}
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return chunkSet == null || chunkSet.isEmpty(); return chunkSet == null || chunkSet.isEmpty();
@ -1117,7 +1152,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
int[] getHeightMap(ChunkHolder chunk, HeightMapType type); int[] getHeightMap(ChunkHolder chunk, HeightMapType type);
void flushLightToGet(ChunkHolder chunk, boolean heightmaps); void flushLightToGet(ChunkHolder chunk);
void setLightingToGet(ChunkHolder chunk, char[][] lighting); void setLightingToGet(ChunkHolder chunk, char[][] lighting);

View File

@ -82,6 +82,11 @@ public final class NullChunk implements IQueueChunk {
return new char[0][]; return new char[0][];
} }
@Override
public boolean hasBiomes(final int layer) {
return false;
}
@Nonnull @Nonnull
public int[] getHeightMap(@Nullable HeightMapType type) { public int[] getHeightMap(@Nullable HeightMapType type) {
return new int[256]; return new int[256];
@ -182,17 +187,37 @@ public final class NullChunk implements IQueueChunk {
} }
@Override @Override
public void setLightingToGet(char[][] lighting) { public void setLightingToGet(char[][] lighting, int minSectionIndex, int maxSectionIndex) {
} }
@Override @Override
public void setSkyLightingToGet(char[][] lighting) { public void setSkyLightingToGet(char[][] lighting, int minSectionIndex, int maxSectionIndex) {
} }
@Override @Override
public void setHeightmapToGet(HeightMapType type, int[] data) { public void setHeightmapToGet(HeightMapType type, int[] data) {
} }
@Override
public int getMaxY() {
return 0;
}
@Override
public int getMinY() {
return 0;
}
@Override
public int getMaxSectionIndex() {
return 0;
}
@Override
public int getMinSectionIndex() {
return 0;
}
@Nullable @Nullable
public <T extends Future<T>> T call(@Nullable IChunkSet set, @Nullable Runnable finalize) { public <T extends Future<T>> T call(@Nullable IChunkSet set, @Nullable Runnable finalize) {
return null; return null;
@ -206,6 +231,11 @@ public final class NullChunk implements IQueueChunk {
return true; return true;
} }
@Override
public int getSectionCount() {
return 0;
}
private NullChunk() { private NullChunk() {
} }

View File

@ -51,7 +51,7 @@ public class FuzzyRegion extends AbstractRegion {
RecursiveVisitor search = new RecursiveVisitor(mask, p -> { RecursiveVisitor search = new RecursiveVisitor(mask, p -> {
setMinMax(p.getBlockX(), p.getBlockY(), p.getBlockZ()); setMinMax(p.getBlockX(), p.getBlockY(), p.getBlockZ());
return true; return true;
}, 256); }, 256, extent.getMinY(), extent.getMaxY());
search.setVisited(set); search.setVisited(set);
search.visit(BlockVector3.at(x, y, z)); search.visit(BlockVector3.at(x, y, z));
Operations.completeBlindly(search); Operations.completeBlindly(search);

View File

@ -7,6 +7,8 @@ import com.sk89q.worldedit.regions.CuboidRegion;
public class RegionWrapper extends CuboidRegion { public class RegionWrapper extends CuboidRegion {
private static final RegionWrapper GLOBAL = new RegionWrapper( private static final RegionWrapper GLOBAL = new RegionWrapper(
Integer.MIN_VALUE,
Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MIN_VALUE,
Integer.MAX_VALUE, Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MIN_VALUE,
@ -20,16 +22,16 @@ public class RegionWrapper extends CuboidRegion {
public int minZ; public int minZ;
public int maxZ; public int maxZ;
public static RegionWrapper GLOBAL() { /**
return GLOBAL; * @deprecated use {@link RegionWrapper#RegionWrapper(int, int, int, int, int, int)}
} */
@Deprecated
public RegionWrapper(final int minX, final int maxX, final int minZ, final int maxZ) { public RegionWrapper(final int minX, final int maxX, final int minZ, final int maxZ) {
this(minX, maxX, 0, 255, minZ, maxZ); this(minX, maxX, 0, 255, minZ, maxZ);
} }
public RegionWrapper(final int minX, final int maxX, final int minY, final int maxY, final int minZ, final int maxZ) { public RegionWrapper(final int minX, final int maxX, final int minY, final int maxY, final int minZ, final int maxZ) {
this(BlockVector3.at(minX, 0, minZ), BlockVector3.at(maxX, 255, maxZ)); this(BlockVector3.at(minX, minY, minZ), BlockVector3.at(maxX, maxY, maxZ));
} }
public RegionWrapper(final BlockVector3 pos1, final BlockVector3 pos2) { public RegionWrapper(final BlockVector3 pos1, final BlockVector3 pos2) {
@ -42,6 +44,10 @@ public class RegionWrapper extends CuboidRegion {
this.maxY = Math.max(pos1.getBlockY(), pos2.getBlockY()); this.maxY = Math.max(pos1.getBlockY(), pos2.getBlockY());
} }
public static RegionWrapper GLOBAL() {
return GLOBAL;
}
@Override @Override
protected void recalculate() { protected void recalculate() {
super.recalculate(); super.recalculate();
@ -134,7 +140,8 @@ public class RegionWrapper extends CuboidRegion {
@Override @Override
public boolean isGlobal() { public boolean isGlobal() {
return minX == Integer.MIN_VALUE && minZ == Integer.MIN_VALUE && maxX == Integer.MAX_VALUE && maxZ == Integer.MAX_VALUE && minY <= 0 && maxY >= 255; return minX == Integer.MIN_VALUE && minY == Integer.MIN_VALUE && minZ == Integer.MIN_VALUE
&& maxX == Integer.MAX_VALUE && maxY == Integer.MAX_VALUE && maxZ == Integer.MAX_VALUE;
} }
public boolean contains(RegionWrapper current) { public boolean contains(RegionWrapper current) {

View File

@ -152,52 +152,65 @@ public class MathMan {
} }
public static long tripleWorldCoord(int x, int y, int z) { public static long tripleWorldCoord(int x, int y, int z) {
return y + (((long) x & 0x3FFFFFF) << 8) + (((long) z & 0x3FFFFFF) << 34); return ((y + 256) & 0xffff) + (((long) x & 0xffffff) << 16) + (((long) z & 0xffffff) << 40);
} }
public static long untripleWorldCoordX(long triple) { public static long untripleWorldCoordX(long triple) {
return (((triple >> 8) & 0x3FFFFFF) << 38) >> 38; return (((triple >> 16) & 0xffffff) << 38) >> 38;
} }
public static long untripleWorldCoordY(long triple) { public static long untripleWorldCoordY(long triple) {
return triple & 0xFF; return (triple & 0xffff) - 256;
} }
public static long untripleWorldCoordZ(long triple) { public static long untripleWorldCoordZ(long triple) {
return (((triple >> 34) & 0x3FFFFFF) << 38) >> 38; return (((triple >> 40) & 0xffffff) << 38) >> 38;
} }
public static short tripleBlockCoord(int x, int y, int z) { public static int tripleBlockCoord(int x, int y, int z) {
return (short) ((x & 15) << 12 | (z & 15) << 8 | y); // account for the fact y can be negative now. Assume it won't be less than -256
y += 256;
return ((x & 15) << 16 | (z & 15) << 12 | y);
} }
public static char tripleBlockCoordChar(int x, int y, int z) { public static char tripleBlockCoordChar(int x, int y, int z) {
return (char) ((x & 15) << 12 | (z & 15) << 8 | y); return (char) ((x & 15) << 16 | (z & 15) << 12 | y);
} }
public static int untripleBlockCoordX(int triple) { public static int untripleBlockCoordX(int triple) {
return (triple >> 12) & 0xF; return (triple >> 16) & 0xF;
} }
public static int untripleBlockCoordY(int triple) { public static int untripleBlockCoordY(int triple) {
return (triple & 0xFF); return (triple & 0x1ff) - 256;
} }
public static int untripleBlockCoordZ(int triple) { public static int untripleBlockCoordZ(int triple) {
return (triple >> 8) & 0xF; return (triple >> 12) & 0xF;
} }
/**
* Obtain an integer representation of 3 ints, x, y and z. y is represented by the right-most 9 bits and can
* be within the range -256 to 255 (inclusive). x and z are represented by 11 bits each and can be within the range
* -1024 to 1023 (inclusive).
*
* @param x x value
* @param y y value
* @param z z value
* @return integer representation of x y z
*/
public static int tripleSearchCoords(int x, int y, int z) { public static int tripleSearchCoords(int x, int y, int z) {
byte b1 = (byte) y; if (x > 1023 || x < -1024 || y > 255 || y < -256 || z > 1023 || z < -1024) {
byte b3 = (byte) (x); throw new IndexOutOfBoundsException(String.format("Check range on x=%s, y=%s and z=%s!", x, y, z));
byte b4 = (byte) (z); }
int x16 = (x >> 8) & 0x7; int b1 = Math.abs(y) & 0xff;
int z16 = (z >> 8) & 0x7; int b3 = x & 0xff;
byte b2 = MathMan.pair8(x16, z16); int b4 = z & 0xff;
return ((b1 & 0xFF) int x16 = (((x >> 8) & 0x3) | (x < 0 ? 0x4 : 0x00));
+ ((b2 & 0x7F) << 8) int z16 = (((z >> 8) & 0x3) | (z < 0 ? 0x4 : 0x00));
+ ((b3 & 0xFF) << 15) int y16 = (y < 0 ? 0x1 : 0x00);
+ ((b4 & 0xFF) << 23)); int b2 = ((x16 + (z16 << 3) + (y16 << 6)));
return (((b1) | (b2 << 8)) | (b3 << 15)) | (b4 << 23);
} }
public static int pairSearchCoords(int x, int y) { public static int pairSearchCoords(int x, int y) {

View File

@ -28,17 +28,17 @@ public final class MemBlockSet extends BlockSet {
public static final IRow NULL_ROW_Y = new NullRowY(); public static final IRow NULL_ROW_Y = new NullRowY();
public final IRow[] rows; public final IRow[] rows;
public final MutableBlockVector3 mutable; public final MutableBlockVector3 mutable;
private final int minSectionIndex;
private final int maxSectionIndex;
public MemBlockSet() { public MemBlockSet(int size, int offsetX, int offsetZ, int minSectionIndex, int maxSectionIndex) {
this(16, 0, 0);
}
public MemBlockSet(int size, int offsetX, int offsetZ) {
super(offsetX, offsetZ); super(offsetX, offsetZ);
this.rows = new IRow[size]; this.rows = new IRow[size];
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
rows[i] = NULL_ROW_X; rows[i] = NULL_ROW_X;
} }
this.minSectionIndex = minSectionIndex;
this.maxSectionIndex = maxSectionIndex;
this.mutable = new MutableBlockVector3(); this.mutable = new MutableBlockVector3();
} }
@ -53,14 +53,14 @@ public final class MemBlockSet extends BlockSet {
public boolean add(int x, int y, int z) { public boolean add(int x, int y, int z) {
x -= getBlockOffsetX(); x -= getBlockOffsetX();
z -= getBlockOffsetZ(); z -= getBlockOffsetZ();
return rows[x >> 4].add(this.rows, x, y, z - getBlockOffsetZ()); return rows[x >> 4].add(this.rows, x, y, z - getBlockOffsetZ(), minSectionIndex, maxSectionIndex);
} }
@Override @Override
public void set(int x, int y, int z) { public void set(int x, int y, int z) {
x -= getBlockOffsetX(); x -= getBlockOffsetX();
z -= getBlockOffsetZ(); z -= getBlockOffsetZ();
rows[x >> 4].set(this.rows, x, y, z - getBlockOffsetZ()); rows[x >> 4].set(this.rows, x, y, z - getBlockOffsetZ(), minSectionIndex, maxSectionIndex);
} }
@Override @Override
@ -88,11 +88,11 @@ public final class MemBlockSet extends BlockSet {
@Override @Override
public Set<BlockVector2> getChunks() { public Set<BlockVector2> getChunks() {
return new AbstractSet<BlockVector2>() { return new AbstractSet<>() {
@Nonnull @Nonnull
@Override @Override
public Iterator<BlockVector2> iterator() { public Iterator<BlockVector2> iterator() {
return new Iterator<BlockVector2>() { return new Iterator<>() {
private final MutableBlockVector2 mutable = new MutableBlockVector2(); private final MutableBlockVector2 mutable = new MutableBlockVector2();
private boolean hasNext; private boolean hasNext;
private int X; private int X;
@ -181,10 +181,10 @@ public final class MemBlockSet extends BlockSet {
} }
public Set<BlockVector3> getChunkCubes() { public Set<BlockVector3> getChunkCubes() {
return new AbstractSet<BlockVector3>() { return new AbstractSet<>() {
@Override @Override
public Iterator<BlockVector3> iterator() { public Iterator<BlockVector3> iterator() {
return new Iterator<BlockVector3>() { return new Iterator<>() {
private final MutableBlockVector3 mutable = new MutableBlockVector3(); private final MutableBlockVector3 mutable = new MutableBlockVector3();
private boolean hasNext; private boolean hasNext;
private int X; private int X;
@ -234,7 +234,7 @@ public final class MemBlockSet extends BlockSet {
@Override @Override
public BlockVector3 next() { public BlockVector3 next() {
mutable.setComponents( mutable.setComponents(
setX + getBlockOffsetX(), setY, setZ + getBlockOffsetX()); setX + getBlockOffsetX(), setY - (minSectionIndex << 4), setZ + getBlockOffsetX());
init(); init();
return mutable; return mutable;
} }
@ -282,7 +282,7 @@ public final class MemBlockSet extends BlockSet {
if (rowx instanceof RowX) { if (rowx instanceof RowX) {
IRow rowz = ((RowX) rowx).rows[other.getZ()]; IRow rowz = ((RowX) rowx).rows[other.getZ()];
if (rowz instanceof RowZ) { if (rowz instanceof RowZ) {
return ((RowZ) rowz).rows[other.getY() - getChunkOffsetZ()] instanceof RowY; return ((RowZ) rowz).rows[other.getY() - (minSectionIndex << 4) - getChunkOffsetZ()] instanceof RowY;
} }
} }
} }
@ -293,7 +293,7 @@ public final class MemBlockSet extends BlockSet {
@Override @Override
public int getMinimumY() { public int getMinimumY() {
int maxY = 15; int maxY = maxSectionIndex;
int maxy = 16; int maxy = 16;
int by = Integer.MAX_VALUE; int by = Integer.MAX_VALUE;
for (IRow nullRowX : rows) { for (IRow nullRowX : rows) {
@ -357,7 +357,7 @@ public final class MemBlockSet extends BlockSet {
} }
RowZ rowz = (RowZ) nullRowZ; RowZ rowz = (RowZ) nullRowZ;
outer: outer:
for (int Y = 15; Y >= maxY; Y--) { for (int Y = maxSectionIndex; Y >= maxY; Y--) {
IRow nullRowY = rowz.rows[Y]; IRow nullRowY = rowz.rows[Y];
if (!(nullRowY instanceof RowY)) { if (!(nullRowY instanceof RowY)) {
continue; continue;
@ -376,8 +376,8 @@ public final class MemBlockSet extends BlockSet {
maxy = y + 1; maxy = y + 1;
} }
by = (Y << 4) + y; by = (Y << 4) + y;
if (by == FaweCache.IMP.WORLD_MAX_Y) { if (by == (maxSectionIndex << 4) + 15) {
return FaweCache.IMP.WORLD_MAX_Y; return (maxSectionIndex << 4) + 15;
} }
break outer; break outer;
} }
@ -582,7 +582,7 @@ public final class MemBlockSet extends BlockSet {
if (!(nullRowY instanceof RowY)) { if (!(nullRowY instanceof RowY)) {
continue; continue;
} }
int by = Y << 4; int by = ((Y - minSectionIndex) << 4);
RowY rowY = (RowY) nullRowY; RowY rowY = (RowY) nullRowY;
for (int y = 0, i = 0; y < 16; y++) { for (int y = 0, i = 0; y < 16; y++) {
for (int z = 0; z < 16; z += 4, i++) { for (int z = 0; z < 16; z += 4, i++) {
@ -615,7 +615,7 @@ public final class MemBlockSet extends BlockSet {
@Override @Override
public Iterator<BlockVector3> iterator() { public Iterator<BlockVector3> iterator() {
return new Iterator<BlockVector3>() { return new Iterator<>() {
private int bx; private int bx;
private int by; private int by;
private int bz; private int bz;
@ -817,10 +817,10 @@ public final class MemBlockSet extends BlockSet {
return false; return false;
} }
void set(IRow[] rows, int x, int y, int z); void set(IRow[] rows, int x, int y, int z, int minSectionIndex, int maxSectionIndex);
default boolean add(IRow[] rows, int x, int y, int z) { default boolean add(IRow[] rows, int x, int y, int z, int minSectionIndex, int maxSectionIndex) {
set(rows, x, y, z); set(rows, x, y, z, minSectionIndex, maxSectionIndex);
return true; return true;
} }
@ -837,10 +837,10 @@ public final class MemBlockSet extends BlockSet {
public static final class NullRowX implements IRow { public static final class NullRowX implements IRow {
@Override @Override
public void set(IRow[] parent, int x, int y, int z) { public void set(IRow[] parent, int x, int y, int z, int minSectionIndex, int maxSectionIndex) {
IRow row = new RowX(parent.length); IRow row = new RowX(parent.length);
parent[x >> 4] = row; parent[x >> 4] = row;
row.set(parent, x, y, z); row.set(parent, x, y, z, minSectionIndex, maxSectionIndex);
} }
} }
@ -848,10 +848,10 @@ public final class MemBlockSet extends BlockSet {
public static final class NullRowZ implements IRow { public static final class NullRowZ implements IRow {
@Override @Override
public void set(IRow[] parent, int x, int y, int z) { public void set(IRow[] parent, int x, int y, int z, int minSectionIndex, int maxSectionIndex) {
IRow row = new RowZ(); IRow row = new RowZ(minSectionIndex, maxSectionIndex);
parent[z >> 4] = row; parent[z >> 4] = row;
row.set(parent, x, y, z); row.set(parent, x, y, z, minSectionIndex, maxSectionIndex);
} }
} }
@ -859,10 +859,10 @@ public final class MemBlockSet extends BlockSet {
public static final class NullRowY implements IRow { public static final class NullRowY implements IRow {
@Override @Override
public void set(IRow[] parent, int x, int y, int z) { public void set(IRow[] parent, int x, int y, int z, int minSectionIndex, int maxSectionIndex) {
IRow row = new RowY(); IRow row = new RowY();
parent[y >> 4] = row; parent[y >> 4] = row;
row.set(parent, x, y, z); row.set(parent, x, y, z, minSectionIndex, maxSectionIndex);
} }
} }
@ -884,13 +884,13 @@ public final class MemBlockSet extends BlockSet {
} }
@Override @Override
public void set(IRow[] parent, int x, int y, int z) { public void set(IRow[] parent, int x, int y, int z, int minSectionIndex, int maxSectionIndex) {
this.rows[z >> 4].set(this.rows, x, y, z); this.rows[z >> 4].set(this.rows, x, y, z, minSectionIndex, maxSectionIndex);
} }
@Override @Override
public boolean add(IRow[] parent, int x, int y, int z) { public boolean add(IRow[] parent, int x, int y, int z, int minSectionIndex, int maxSectionIndex) {
return this.rows[z >> 4].add(this.rows, x, y, z); return this.rows[z >> 4].add(this.rows, x, y, z, minSectionIndex, maxSectionIndex);
} }
@Override @Override
@ -909,8 +909,8 @@ public final class MemBlockSet extends BlockSet {
public final IRow[] rows; public final IRow[] rows;
public RowZ() { public RowZ(int minSectionIndex, int maxSectionIndex) {
this.rows = new IRow[FaweCache.IMP.CHUNK_LAYERS]; this.rows = new IRow[maxSectionIndex - minSectionIndex + 1];
reset(); reset();
} }
@ -924,18 +924,19 @@ public final class MemBlockSet extends BlockSet {
} }
@Override @Override
public void set(IRow[] parent, int x, int y, int z) { public void set(IRow[] parent, int x, int y, int z, int minSectionIndex, int maxSectionIndex) {
this.rows[y >> 4].set(this.rows, x, y, z); this.rows[y >> 4].set(this.rows, x, y, z, minSectionIndex, maxSectionIndex);
} }
@Override @Override
public boolean add(IRow[] parent, int x, int y, int z) { public boolean add(IRow[] parent, int x, int y, int z, int minSectionIndex, int maxSectionIndex) {
return this.rows[y >> 4].add(this.rows, x, y, z); return this.rows[y >> 4].add(this.rows, x, y, z, minSectionIndex, maxSectionIndex);
} }
@Override @Override
public void clear(IRow[] parent, int x, int y, int z) { public void clear(IRow[] parent, int x, int y, int z) {
this.rows[y >> 4].set(this.rows, x, y, z); // min/amx layer does not matter here.
this.rows[y >> 4].set(this.rows, x, y, z, 0, 0);
} }
@Override @Override
@ -957,9 +958,7 @@ public final class MemBlockSet extends BlockSet {
} }
public void reset() { public void reset() {
for (int i = 0; i < FaweCache.IMP.CHUNK_LAYERS; i++) { Arrays.fill(rows, NULL_ROW_Y);
rows[i] = NULL_ROW_Y;
}
} }
} }
@ -983,13 +982,13 @@ public final class MemBlockSet extends BlockSet {
} }
@Override @Override
public void set(IRow[] parent, int x, int y, int z) { public void set(IRow[] parent, int x, int y, int z, int minSectionIndex, int maxSectionIndex) {
int i = ((y & 15) << 8) | ((z & 15) << 4) | (x & 15); int i = ((y & 15) << 8) | ((z & 15) << 4) | (x & 15);
bits[i >> 6] |= (1L << (i & 0x3F)); bits[i >> 6] |= (1L << (i & 0x3F));
} }
@Override @Override
public boolean add(IRow[] parent, int x, int y, int z) { public boolean add(IRow[] parent, int x, int y, int z, int minSectionIndex, int maxSectionIndex) {
int i = ((y & 15) << 8) | ((z & 15) << 4) | (x & 15); int i = ((y & 15) << 8) | ((z & 15) << 4) | (x & 15);
int offset = i >> 6; int offset = i >> 6;
long value = bits[offset]; long value = bits[offset];

View File

@ -61,6 +61,11 @@ public interface SimpleWorld extends World {
return getMaximumPoint().getBlockY(); return getMaximumPoint().getBlockY();
} }
@Override
default int getMinY() {
return getMinimumPoint().getBlockY();
}
@Override @Override
default Mask createLiquidMask() { default Mask createLiquidMask() {
return new BlockMask(this).add(BlockTypes.LAVA, BlockTypes.WATER); return new BlockMask(this).add(BlockTypes.LAVA, BlockTypes.WATER);

View File

@ -85,8 +85,8 @@ public class AsyncPlayer extends PlayerProxy {
public boolean ascendToCeiling(int clearance, boolean alwaysGlass) { public boolean ascendToCeiling(int clearance, boolean alwaysGlass) {
Location pos = getBlockLocation(); Location pos = getBlockLocation();
int x = pos.getBlockX(); int x = pos.getBlockX();
int initialY = Math.max(0, pos.getBlockY()); int initialY = Math.max(getWorld().getMinY(), pos.getBlockY());
int y = Math.max(0, pos.getBlockY() + 2); int y = Math.max(getWorld().getMinY(), pos.getBlockY() + 2);
int z = pos.getBlockZ(); int z = pos.getBlockZ();
Extent world = getLocation().getExtent(); Extent world = getLocation().getExtent();
@ -121,15 +121,15 @@ public class AsyncPlayer extends PlayerProxy {
public boolean ascendUpwards(int distance, boolean alwaysGlass) { public boolean ascendUpwards(int distance, boolean alwaysGlass) {
final Location pos = getBlockLocation(); final Location pos = getBlockLocation();
final int x = pos.getBlockX(); final int x = pos.getBlockX();
final int initialY = Math.max(0, pos.getBlockY()); final int initialY = Math.max(getWorld().getMinY(), pos.getBlockY());
int y = Math.max(0, pos.getBlockY() + 1); int y = Math.max(getWorld().getMinY(), pos.getBlockY() + 1);
final int z = pos.getBlockZ(); final int z = pos.getBlockZ();
final int maxY = Math.min(getWorld().getMaxY() + 1, initialY + distance); final int maxY = Math.min(getWorld().getMaxY() + 1, initialY + distance);
final Extent world = getLocation().getExtent(); final Extent world = getLocation().getExtent();
MutableBlockVector3 mutable = new MutableBlockVector3(x, y, z); MutableBlockVector3 mutable = new MutableBlockVector3(x, y, z);
while (y <= world.getMaximumPoint().getY() + 2) { while (y <= world.getMaxY() + 2) {
if (world.getBlock(mutable.mutY(y)).getBlockType().getMaterial() if (world.getBlock(mutable.mutY(y)).getBlockType().getMaterial()
.isMovementBlocker()) { .isMovementBlocker()) {
break; // Hit something break; // Hit something

View File

@ -96,6 +96,11 @@ public class WorldWrapper extends AbstractWorld {
return parent.getMaxY(); return parent.getMaxY();
} }
@Override
public int getMinY() {
return parent.getMinY();
}
@Override @Override
public Mask createLiquidMask() { public Mask createLiquidMask() {
return parent.createLiquidMask(); return parent.createLiquidMask();

View File

@ -1184,6 +1184,16 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
return getBlockChangeCount(); return getBlockChangeCount();
} }
@Override
public BlockVector3 getMinimumPoint() {
return getWorld().getMinimumPoint();
}
@Override
public BlockVector3 getMaximumPoint() {
return getWorld().getMaximumPoint();
}
//FAWE start //FAWE start
public void setSize(int size) { public void setSize(int size) {
this.changes = size; this.changes = size;
@ -1276,7 +1286,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
public <B extends BlockStateHolder<B>> int fall(final Region region, boolean fullHeight, final B replace) { public <B extends BlockStateHolder<B>> int fall(final Region region, boolean fullHeight, final B replace) {
FlatRegion flat = asFlatRegion(region); FlatRegion flat = asFlatRegion(region);
final int startPerformY = region.getMinimumPoint().getBlockY(); final int startPerformY = region.getMinimumPoint().getBlockY();
final int startCheckY = fullHeight ? 0 : startPerformY; final int startCheckY = fullHeight ? getMinY() : startPerformY;
final int endY = region.getMaximumPoint().getBlockY(); final int endY = region.getMaximumPoint().getBlockY();
RegionVisitor visitor = new RegionVisitor(flat, pos -> { RegionVisitor visitor = new RegionVisitor(flat, pos -> {
int x = pos.getX(); int x = pos.getX();
@ -1353,7 +1363,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
final BlockReplace replace = new BlockReplace(EditSession.this, pattern); final BlockReplace replace = new BlockReplace(EditSession.this, pattern);
// Pick how we're going to visit blocks // Pick how we're going to visit blocks
RecursiveVisitor visitor = new DirectionalVisitor(mask, replace, origin, direction, (int) (radius * 2 + 1)); RecursiveVisitor visitor = new DirectionalVisitor(mask, replace, origin, direction, (int) (radius * 2 + 1), minY, maxY);
// Start at the origin // Start at the origin
visitor.visit(origin); visitor.visit(origin);
@ -1406,8 +1416,8 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
Mask mask = new MaskIntersection( Mask mask = new MaskIntersection(
new RegionMask(new EllipsoidRegion(null, origin, Vector3.at(radius, radius, radius))), new RegionMask(new EllipsoidRegion(null, origin, Vector3.at(radius, radius, radius))),
new BoundedHeightMask( new BoundedHeightMask(
Math.max(origin.getBlockY() - depth + 1, getMinimumPoint().getBlockY()), Math.max(origin.getBlockY() - depth + 1, minY),
Math.min(getMaxY(), origin.getBlockY()) Math.min(maxY, origin.getBlockY())
), ),
Masks.negate(new ExistingBlockMask(this)) Masks.negate(new ExistingBlockMask(this))
); );
@ -1417,11 +1427,11 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
// Pick how we're going to visit blocks // Pick how we're going to visit blocks
RecursiveVisitor visitor; RecursiveVisitor visitor;
//FAWE start - provide extent for preloading //FAWE start - provide extent for preloading, min/max y
if (recursive) { if (recursive) {
visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1), this); visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1), minY, maxY, this);
} else { } else {
visitor = new DownwardVisitor(mask, replace, origin.getBlockY(), (int) (radius * 2 + 1), this); visitor = new DownwardVisitor(mask, replace, origin.getBlockY(), (int) (radius * 2 + 1), minY, maxY, this);
} }
//FAWE end //FAWE end
@ -1938,7 +1948,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
//FAWE end //FAWE end
} }
Mask mask = new MaskIntersection( Mask mask = new MaskIntersection(
new BoundedHeightMask(getWorld().getMinY(), getWorld().getMaxY()), new BoundedHeightMask(minY, maxY),
new RegionMask(new EllipsoidRegion(null, origin, Vector3.at(radius, radius, radius))), new RegionMask(new EllipsoidRegion(null, origin, Vector3.at(radius, radius, radius))),
//FAWE start //FAWE start
liquidMask liquidMask
@ -1950,8 +1960,8 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
} else { } else {
replace = new BlockReplace(this, BlockTypes.AIR.getDefaultState()); replace = new BlockReplace(this, BlockTypes.AIR.getDefaultState());
} }
//FAWE start - provide extent for preloading //FAWE start - provide extent for preloading, min/max y
RecursiveVisitor visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1), this); RecursiveVisitor visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1), minY, maxY, this);
//FAWE end //FAWE end
// Around the origin in a 3x3 block // Around the origin in a 3x3 block
@ -1989,14 +1999,14 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
// There are boundaries that the routine needs to stay in // There are boundaries that the routine needs to stay in
Mask mask = new MaskIntersection( Mask mask = new MaskIntersection(
new BoundedHeightMask(getWorld().getMinY(), Math.min(origin.getBlockY(), getWorld().getMaxY())), new BoundedHeightMask(minY, Math.min(origin.getBlockY(), maxY)),
new RegionMask(new EllipsoidRegion(null, origin, Vector3.at(radius, radius, radius))), new RegionMask(new EllipsoidRegion(null, origin, Vector3.at(radius, radius, radius))),
blockMask blockMask
); );
BlockReplace replace = new BlockReplace(this, fluid.getDefaultState()); BlockReplace replace = new BlockReplace(this, fluid.getDefaultState());
//FAWE start - provide extent for preloading //FAWE start - provide extent for preloading, world min/maxY
NonRisingVisitor visitor = new NonRisingVisitor(mask, replace, Integer.MAX_VALUE, this); NonRisingVisitor visitor = new NonRisingVisitor(mask, replace, Integer.MAX_VALUE, minY, maxY, this);
//FAWE end //FAWE end
// Around the origin in a 3x3 block // Around the origin in a 3x3 block
@ -2411,7 +2421,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
} }
} }
} }
if (y != 0 && (yy = py - y) >= 0) { if (y != 0 && (yy = py - y) >= minY) {
this.setBlock(px + x, yy, pz + z, block); this.setBlock(px + x, yy, pz + z, block);
if (x != 0) { if (x != 0) {
this.setBlock(px - x, yy, pz + z, block); this.setBlock(px - x, yy, pz + z, block);
@ -2506,9 +2516,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
BlockState air = BlockTypes.AIR.getDefaultState(); BlockState air = BlockTypes.AIR.getDefaultState();
BlockState water = BlockTypes.WATER.getDefaultState(); BlockState water = BlockTypes.WATER.getDefaultState();
int centerY = Math.max(getWorld().getMinY(), Math.min(getWorld().getMaxY(), oy)); int centerY = Math.max(minY, Math.min(maxY, oy));
int minY = Math.max(getWorld().getMinY(), centerY - height); int minY = Math.max(this.minY, centerY - height);
int maxY = Math.min(getWorld().getMaxY(), centerY + height); int maxY = Math.min(this.maxY, centerY + height);
//FAWE start - mutable //FAWE start - mutable
MutableBlockVector3 mutable = new MutableBlockVector3(); MutableBlockVector3 mutable = new MutableBlockVector3();
@ -2535,8 +2545,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
++affected; ++affected;
} }
} else if (id == BlockTypes.SNOW) { } else if (id == BlockTypes.SNOW) {
//FAWE start
if (setBlock(mutable, air)) { if (setBlock(mutable, air)) {
if (y > 0) { if (y > getMinY()) {
BlockState block = getBlock(mutable2); BlockState block = getBlock(mutable2);
if (block.getStates().containsKey(snowy)) { if (block.getStates().containsKey(snowy)) {
if (setBlock(mutable2, block.with(snowy, false))) { if (setBlock(mutable2, block.with(snowy, false))) {
@ -2649,9 +2660,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
final BlockState grass = BlockTypes.GRASS_BLOCK.getDefaultState(); final BlockState grass = BlockTypes.GRASS_BLOCK.getDefaultState();
final int centerY = Math.max(getWorld().getMinY(), Math.min(getWorld().getMaxY(), oy)); final int centerY = Math.max(minY, Math.min(maxY, oy));
final int minY = Math.max(getWorld().getMinY(), centerY - height); final int minY = Math.max(this.minY, centerY - height);
final int maxY = Math.min(getWorld().getMaxY(), centerY + height); final int maxY = Math.min(this.maxY, centerY + height);
//FAWE start - mutable //FAWE start - mutable
MutableBlockVector3 mutable = new MutableBlockVector3(); MutableBlockVector3 mutable = new MutableBlockVector3();
@ -2952,7 +2963,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
int zv = (int) (z.getValue() * unit.getZ() + zero2.getZ()); int zv = (int) (z.getValue() * unit.getZ() + zero2.getZ());
BlockState get; BlockState get;
if (yv >= 0 && yv < 256) { if (yv >= minY && yv <= maxY) {
get = getBlock(xv, yv, zv); get = getBlock(xv, yv, zv);
} else { } else {
get = BlockTypes.AIR.getDefaultState(); get = BlockTypes.AIR.getDefaultState();
@ -3538,7 +3549,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
int xx = x + bx; int xx = x + bx;
for (int z = 0; z < 16; z++) { for (int z = 0; z < 16; z++) {
int zz = z + bz; int zz = z + bz;
for (int y = 0; y < maxY + 1; y++) { for (int y = minY; y < maxY + 1; y++) {
BaseBlock block = getFullBlock(mutable.setComponents(xx, y, zz)); BaseBlock block = getFullBlock(mutable.setComponents(xx, y, zz));
fcs.add(mutable, block, BlockTypes.AIR.getDefaultState().toBaseBlock()); fcs.add(mutable, block, BlockTypes.AIR.getDefaultState().toBaseBlock());
} }
@ -3561,7 +3572,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
for (int z = 0; z < 16; z++) { for (int z = 0; z < 16; z++) {
int zz = z + bz; int zz = z + bz;
mutable.mutZ(zz); mutable.mutZ(zz);
for (int y = 0; y < maxY + 1; y++) { for (int y = minY; y < maxY + 1; y++) {
mutable.mutY(y); mutable.mutY(y);
boolean contains = (fe == null || fe.contains(xx, y, zz)) && region.contains(mutable); boolean contains = (fe == null || fe.contains(xx, y, zz)) && region.contains(mutable);
if (contains) { if (contains) {

View File

@ -211,6 +211,13 @@ public class LocalSession implements TextureHolder {
if (defaultSelector != null) { if (defaultSelector != null) {
this.selector = defaultSelector.createSelector(); this.selector = defaultSelector.createSelector();
} }
//FAWE start
if (worldOverride != null) {
this.selector.setWorld(worldOverride);
} else {
this.selector.setWorld(currentWorld);
}
//FAWE end
} }
//FAWE start //FAWE start

View File

@ -457,6 +457,7 @@ public class BrushCommands {
) )
@CommandPermissions("worldedit.brush.stencil") @CommandPermissions("worldedit.brush.stencil")
public void stencilBrush( public void stencilBrush(
Player player,
LocalSession session, InjectedValueAccess context, LocalSession session, InjectedValueAccess context,
@Arg(desc = "Pattern") @Arg(desc = "Pattern")
Pattern fill, Pattern fill,
@ -478,13 +479,15 @@ public class BrushCommands {
worldEdit.checkMaxBrushRadius(radius); worldEdit.checkMaxBrushRadius(radius);
InputStream stream = getHeightmapStream(image); InputStream stream = getHeightmapStream(image);
HeightBrush brush; HeightBrush brush;
int minY = player.getWorld().getMinY();
int maxY = player.getWorld().getMaxY();
try { try {
brush = new StencilBrush(stream, rotation, yscale, onlyWhite, brush = new StencilBrush(stream, rotation, yscale, onlyWhite,
"#clipboard".equalsIgnoreCase(image) "#clipboard".equalsIgnoreCase(image)
? session.getClipboard().getClipboard() : null ? session.getClipboard().getClipboard() : null, minY, maxY
); );
} catch (EmptyClipboardException ignored) { } catch (EmptyClipboardException ignored) {
brush = new StencilBrush(stream, rotation, yscale, onlyWhite, null); brush = new StencilBrush(stream, rotation, yscale, onlyWhite, null, minY, maxY);
} }
if (randomRotate) { if (randomRotate) {
brush.setRandomRotate(true); brush.setRandomRotate(true);
@ -710,6 +713,7 @@ public class BrushCommands {
) )
@CommandPermissions("worldedit.brush.height") @CommandPermissions("worldedit.brush.height")
public void heightBrush( public void heightBrush(
Player player,
LocalSession session, LocalSession session,
@Arg(desc = "Expression", def = "5") @Arg(desc = "Expression", def = "5")
Expression radius, Expression radius,
@ -728,7 +732,7 @@ public class BrushCommands {
boolean dontSmooth, InjectedValueAccess context boolean dontSmooth, InjectedValueAccess context
) )
throws WorldEditException, FileNotFoundException { throws WorldEditException, FileNotFoundException {
terrainBrush(session, radius, image, rotation, yscale, false, randomRotate, layers, terrainBrush(player, session, radius, image, rotation, yscale, false, randomRotate, layers,
!dontSmooth, ScalableHeightMap.Shape.CONE, context !dontSmooth, ScalableHeightMap.Shape.CONE, context
); );
} }
@ -741,6 +745,7 @@ public class BrushCommands {
) )
@CommandPermissions("worldedit.brush.height") @CommandPermissions("worldedit.brush.height")
public void cliffBrush( public void cliffBrush(
Player player,
LocalSession session, LocalSession session,
@Arg(desc = "Expression", def = "5") @Arg(desc = "Expression", def = "5")
Expression radius, Expression radius,
@ -760,7 +765,7 @@ public class BrushCommands {
boolean dontSmooth, InjectedValueAccess context boolean dontSmooth, InjectedValueAccess context
) )
throws WorldEditException, FileNotFoundException { throws WorldEditException, FileNotFoundException {
terrainBrush(session, radius, image, rotation, yscale, true, randomRotate, layers, terrainBrush(player, session, radius, image, rotation, yscale, true, randomRotate, layers,
!dontSmooth, ScalableHeightMap.Shape.CYLINDER, context !dontSmooth, ScalableHeightMap.Shape.CYLINDER, context
); );
} }
@ -775,6 +780,7 @@ public class BrushCommands {
) )
@CommandPermissions("worldedit.brush.height") @CommandPermissions("worldedit.brush.height")
public void flattenBrush( public void flattenBrush(
Player player,
LocalSession session, LocalSession session,
@Arg(desc = "Expression", def = "5") @Arg(desc = "Expression", def = "5")
Expression radius, Expression radius,
@ -794,12 +800,13 @@ public class BrushCommands {
boolean dontSmooth, InjectedValueAccess context boolean dontSmooth, InjectedValueAccess context
) )
throws WorldEditException, FileNotFoundException { throws WorldEditException, FileNotFoundException {
terrainBrush(session, radius, image, rotation, yscale, true, randomRotate, layers, terrainBrush(player, session, radius, image, rotation, yscale, true, randomRotate, layers,
!dontSmooth, ScalableHeightMap.Shape.CONE, context !dontSmooth, ScalableHeightMap.Shape.CONE, context
); );
} }
private void terrainBrush( private void terrainBrush(
Player player,
LocalSession session, LocalSession session,
Expression radius, Expression radius,
String image, String image,
@ -816,23 +823,25 @@ public class BrushCommands {
worldEdit.checkMaxBrushRadius(radius); worldEdit.checkMaxBrushRadius(radius);
InputStream stream = getHeightmapStream(image); InputStream stream = getHeightmapStream(image);
HeightBrush brush; HeightBrush brush;
int minY = player.getWorld().getMinY();
int maxY = player.getWorld().getMaxY();
if (flat) { if (flat) {
try { try {
brush = new FlattenBrush(stream, rotation, yscale, layers, smooth, brush = new FlattenBrush(stream, rotation, yscale, layers, smooth,
"#clipboard".equalsIgnoreCase(image) "#clipboard".equalsIgnoreCase(image)
? session.getClipboard().getClipboard() : null, shape ? session.getClipboard().getClipboard() : null, shape, minY, maxY
); );
} catch (EmptyClipboardException ignored) { } catch (EmptyClipboardException ignored) {
brush = new FlattenBrush(stream, rotation, yscale, layers, smooth, null, shape); brush = new FlattenBrush(stream, rotation, yscale, layers, smooth, null, shape, minY, maxY);
} }
} else { } else {
try { try {
brush = new HeightBrush(stream, rotation, yscale, layers, smooth, brush = new HeightBrush(stream, rotation, yscale, layers, smooth,
"#clipboard".equalsIgnoreCase(image) "#clipboard".equalsIgnoreCase(image)
? session.getClipboard().getClipboard() : null ? session.getClipboard().getClipboard() : null, minY, maxY
); );
} catch (EmptyClipboardException ignored) { } catch (EmptyClipboardException ignored) {
brush = new HeightBrush(stream, rotation, yscale, layers, smooth, null); brush = new HeightBrush(stream, rotation, yscale, layers, smooth, null, minY, maxY);
} }
} }
if (randomRotate) { if (randomRotate) {

View File

@ -635,12 +635,18 @@ public class GenerationCommands {
@Arg(desc = "Ore vein size") @Range(from = 0, to = Integer.MAX_VALUE) int size, @Arg(desc = "Ore vein size") @Range(from = 0, to = Integer.MAX_VALUE) int size,
@Arg(desc = "Ore vein frequency (number of times to attempt to place ore)", def = "10") @Range(from = 0, to = Integer.MAX_VALUE) int freq, @Arg(desc = "Ore vein frequency (number of times to attempt to place ore)", def = "10") @Range(from = 0, to = Integer.MAX_VALUE) int freq,
@Arg(desc = "Ore vein rarity (% chance each attempt is placed)", def = "100") @Range(from = 0, to = 100) int rarity, @Arg(desc = "Ore vein rarity (% chance each attempt is placed)", def = "100") @Range(from = 0, to = 100) int rarity,
@Arg(desc = "Ore vein min y", def = "0") @Range(from = 0, to = 255) int minY, @Arg(desc = "Ore vein min y", def = "0") int minY,
@Arg(desc = "Ore vein max y", def = "63") @Range(from = 0, to = 255) int maxY @Arg(desc = "Ore vein max y", def = "63") int maxY
) throws WorldEditException { ) throws WorldEditException {
if (mask instanceof AbstractExtentMask) { if (mask instanceof AbstractExtentMask) {
((AbstractExtentMask) mask).setExtent(editSession); ((AbstractExtentMask) mask).setExtent(editSession);
} }
checkCommandArgument(minY >= editSession.getMinY(), Caption.of("fawe.error.outside-range-lower", "miny",
editSession.getMinY()));
checkCommandArgument(maxY <= editSession.getMaxY(), Caption.of("fawe.error.outside-range-upper", "maxy",
editSession.getMaxY()));
checkCommandArgument(minY < maxY, Caption.of("fawe.error.argument-size-mismatch", "miny",
"maxy"));
editSession.addOre(region, mask, material, size, freq, rarity, minY, maxY); editSession.addOre(region, mask, material, size, freq, rarity, minY, maxY);
actor.print(Caption.of("fawe.worldedit.visitor.visitor.block", editSession.getBlockChangeCount())); actor.print(Caption.of("fawe.worldedit.visitor.visitor.block", editSession.getBlockChangeCount()));
} }

View File

@ -133,8 +133,8 @@ public class HistorySubCommands {
Location origin = player.getLocation(); Location origin = player.getLocation();
BlockVector3 bot = origin.toBlockPoint().subtract(radius, radius, radius); BlockVector3 bot = origin.toBlockPoint().subtract(radius, radius, radius);
BlockVector3 top = origin.toBlockPoint().add(radius, radius, radius); BlockVector3 top = origin.toBlockPoint().add(radius, radius, radius);
bot = bot.clampY(0, world.getMaxY()); bot = bot.clampY(world.getMinY(), world.getMaxY());
top = top.clampY(0, world.getMaxY()); top = top.clampY(world.getMinY(), world.getMaxY());
// TODO mask the regions bot / top to the bottom and top coord in the allowedRegions // TODO mask the regions bot / top to the bottom and top coord in the allowedRegions
// TODO: then mask the edit to the bot / top // TODO: then mask the edit to the bot / top
// if (allowedRegions.length != 1 || !allowedRegions[0].isGlobal()) { // if (allowedRegions.length != 1 || !allowedRegions[0].isGlobal()) {
@ -196,9 +196,9 @@ public class HistorySubCommands {
.summarize(RegionWrapper.GLOBAL(), false); .summarize(RegionWrapper.GLOBAL(), false);
if (summary != null) { if (summary != null) {
rollback.setDimensions( rollback.setDimensions(
BlockVector3.at(summary.minX, 0, summary.minZ), BlockVector3.at(summary.minX, world.getMinY(), summary.minZ),
BlockVector3 BlockVector3
.at(summary.maxX, 255, summary.maxZ) .at(summary.maxX, world.getMaxY(), summary.maxZ)
); );
rollback.setTime(historyFile.lastModified()); rollback.setTime(historyFile.lastModified());
RollbackDatabase db = DBHandler.IMP RollbackDatabase db = DBHandler.IMP
@ -410,8 +410,8 @@ public class HistorySubCommands {
BlockVector3 bot = origin.toBlockPoint().subtract(radius, radius, radius); BlockVector3 bot = origin.toBlockPoint().subtract(radius, radius, radius);
BlockVector3 top = origin.toBlockPoint().add(radius, radius, radius); BlockVector3 top = origin.toBlockPoint().add(radius, radius, radius);
bot = bot.clampY(0, world.getMaxY()); bot = bot.clampY(world.getMinY(), world.getMaxY());
top = top.clampY(0, world.getMaxY()); top = top.clampY(world.getMinY(), world.getMaxY());
long minTime = System.currentTimeMillis() - timeDiff; long minTime = System.currentTimeMillis() - timeDiff;
Iterable<Supplier<RollbackOptimizedHistory>> edits = database.getEdits(other, minTime, bot, top, false, false); Iterable<Supplier<RollbackOptimizedHistory>> edits = database.getEdits(other, minTime, bot, top, false, false);

View File

@ -363,17 +363,23 @@ public class RegionCommands {
@Selection Region region, @Selection Region region,
@Arg(name = "pattern", desc = "The pattern of blocks to lay") Pattern patternArg @Arg(name = "pattern", desc = "The pattern of blocks to lay") Pattern patternArg
) throws WorldEditException { ) throws WorldEditException {
BlockVector3 max = region.getMaximumPoint(); //FAWE start - world min/maxY
int maxY = max.getBlockY(); int maxY = region.getMaximumY();
int minY = region.getMinimumY();
//FAWE end
Iterable<BlockVector2> flat = Regions.asFlatRegion(region).asFlatRegion(); Iterable<BlockVector2> flat = Regions.asFlatRegion(region).asFlatRegion();
Iterator<BlockVector2> iter = flat.iterator(); Iterator<BlockVector2> iter = flat.iterator();
int y = 0; //FAWE start - world min/maxY
int y = minY;
//FAWE end
int affected = 0; int affected = 0;
while (iter.hasNext()) { while (iter.hasNext()) {
BlockVector2 pos = iter.next(); BlockVector2 pos = iter.next();
int x = pos.getBlockX(); int x = pos.getBlockX();
int z = pos.getBlockZ(); int z = pos.getBlockZ();
y = editSession.getNearestSurfaceTerrainBlock(x, z, y, 0, maxY); //FAWE start - world min/maxY
y = editSession.getNearestSurfaceTerrainBlock(x, z, y, minY, maxY);
//FAWE end
editSession.setBlock(x, y, z, patternArg); editSession.setBlock(x, y, z, patternArg);
affected++; affected++;
} }
@ -856,6 +862,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.flora") @CommandPermissions("worldedit.region.flora")
@Logging(REGION) @Logging(REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public int flora( public int flora(
Actor actor, EditSession editSession, @Selection Region region, Actor actor, EditSession editSession, @Selection Region region,
@ -867,7 +874,7 @@ public class RegionCommands {
FloraGenerator generator = new FloraGenerator(editSession); FloraGenerator generator = new FloraGenerator(editSession);
GroundFunction ground = new GroundFunction(new ExistingBlockMask(editSession), generator); GroundFunction ground = new GroundFunction(new ExistingBlockMask(editSession), generator);
//FAWE start - provide extent for preloading //FAWE start - provide extent for preloading
LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground, editSession); LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground);
//FAWE end //FAWE end
visitor.setMask(new NoiseFilter2D(new RandomNoise(), density)); visitor.setMask(new NoiseFilter2D(new RandomNoise(), density));
Operations.completeLegacy(visitor); Operations.completeLegacy(visitor);

View File

@ -124,9 +124,9 @@ public class SelectionCommands {
Location pos; Location pos;
//FAWE start - clamp //FAWE start - clamp
if (coordinates != null) { if (coordinates != null) {
pos = new Location(world, coordinates.toVector3().clampY(0, world.getMaxY())); pos = new Location(world, coordinates.toVector3().clampY(world.getMinY(), world.getMaxY()));
} else if (actor instanceof Locatable) { } else if (actor instanceof Locatable) {
pos = ((Locatable) actor).getBlockLocation().clampY(0, world.getMaxY()); pos = ((Locatable) actor).getBlockLocation().clampY(world.getMinY(), world.getMaxY());
//FAWE end //FAWE end
} else { } else {
actor.print(Caption.of("worldedit.pos.console-require-coords")); actor.print(Caption.of("worldedit.pos.console-require-coords"));
@ -157,9 +157,9 @@ public class SelectionCommands {
Location pos; Location pos;
if (coordinates != null) { if (coordinates != null) {
//FAWE start - clamp //FAWE start - clamp
pos = new Location(world, coordinates.toVector3().clampY(0, world.getMaxY())); pos = new Location(world, coordinates.toVector3().clampY(world.getMinY(), world.getMaxY()));
} else if (actor instanceof Locatable) { } else if (actor instanceof Locatable) {
pos = ((Locatable) actor).getBlockLocation().clampY(0, world.getMaxY()); pos = ((Locatable) actor).getBlockLocation().clampY(world.getMinY(), world.getMaxY());
//Fawe end //Fawe end
} else { } else {
actor.print(Caption.of("worldedit.pos.console-require-coords")); actor.print(Caption.of("worldedit.pos.console-require-coords"));
@ -258,7 +258,7 @@ public class SelectionCommands {
.clampY(minChunkY, maxChunkY); .clampY(minChunkY, maxChunkY);
min = minChunk.shl(CHUNK_SHIFTS, CHUNK_SHIFTS_Y, CHUNK_SHIFTS); min = minChunk.shl(CHUNK_SHIFTS, CHUNK_SHIFTS_Y, CHUNK_SHIFTS);
max = maxChunk.shl(CHUNK_SHIFTS, CHUNK_SHIFTS_Y, CHUNK_SHIFTS).add(15, world.getMaxY(), 15); max = maxChunk.shl(CHUNK_SHIFTS, CHUNK_SHIFTS_Y, CHUNK_SHIFTS).add(15, 255, 15);
actor.print(Caption.of( actor.print(Caption.of(
"worldedit.chunk.selected-multiple", "worldedit.chunk.selected-multiple",
@ -286,7 +286,7 @@ public class SelectionCommands {
} }
min = minChunk.shl(CHUNK_SHIFTS, CHUNK_SHIFTS_Y, CHUNK_SHIFTS); min = minChunk.shl(CHUNK_SHIFTS, CHUNK_SHIFTS_Y, CHUNK_SHIFTS);
max = min.add(15, world.getMaxY(), 15); max = min.add(15, 255, 15);
actor.print(Caption.of( actor.print(Caption.of(
"worldedit.chunk.selected", "worldedit.chunk.selected",

View File

@ -413,8 +413,8 @@ public class UtilityCommands {
) throws WorldEditException { ) throws WorldEditException {
size = Math.max(1, size); size = Math.max(1, size);
we.checkMaxRadius(size); we.checkMaxRadius(size);
height = height != null ? Math.min((world.getMaxY() + 1), height + 1) : (world.getMaxY() + 1);
height = height != null ? Math.min((world.getMaxY() - world.getMinY() + 1), height + 1) : (world.getMaxY() - world.getMinY() + 1);
int affected = editSession.removeAbove(session.getPlacementPosition(actor), size, height); int affected = editSession.removeAbove(session.getPlacementPosition(actor), size, height);
actor.print(Caption.of("worldedit.removeabove.removed", TextComponent.of(affected))); actor.print(Caption.of("worldedit.removeabove.removed", TextComponent.of(affected)));
return affected; return affected;
@ -436,8 +436,8 @@ public class UtilityCommands {
) throws WorldEditException { ) throws WorldEditException {
size = Math.max(1, size); size = Math.max(1, size);
we.checkMaxRadius(size); we.checkMaxRadius(size);
height = height != null ? Math.min((world.getMaxY() + 1), height + 1) : (world.getMaxY() + 1);
height = height != null ? Math.min((world.getMaxY() - world.getMinY() + 1), height + 1) : (world.getMaxY() - world.getMinY() + 1);
int affected = editSession.removeBelow(session.getPlacementPosition(actor), size, height); int affected = editSession.removeBelow(session.getPlacementPosition(actor), size, height);
actor.print(Caption.of("worldedit.removebelow.removed", TextComponent.of(affected))); actor.print(Caption.of("worldedit.removebelow.removed", TextComponent.of(affected)));
return affected; return affected;

View File

@ -388,7 +388,7 @@ public class BrushTool
final int x = loc.getBlockX(); final int x = loc.getBlockX();
final int z = loc.getBlockZ(); final int z = loc.getBlockZ();
int y; int y;
for (y = height; y > 0; y--) { for (y = height; y > editSession.getMinY(); y--) {
BlockType block = editSession.getBlockType(x, y, z); BlockType block = editSession.getBlockType(x, y, z);
if (block.getMaterial().isMovementBlocker()) { if (block.getMaterial().isMovementBlocker()) {
break; break;

View File

@ -87,7 +87,8 @@ public class FloodFillTool implements BlockTool {
//FAWE start - Respect masks //FAWE start - Respect masks
Mask mask = initialType.toMask(editSession); Mask mask = initialType.toMask(editSession);
BlockReplace function = new BlockReplace(editSession, pattern); BlockReplace function = new BlockReplace(editSession, pattern);
RecursiveVisitor visitor = new RecursiveVisitor(mask, function, range, editSession); RecursiveVisitor visitor = new RecursiveVisitor(mask, function, range, editSession.getMinY(),
editSession.getMaxY(), editSession);
visitor.visit(origin); visitor.visit(origin);
Operations.completeLegacy(visitor); Operations.completeLegacy(visitor);
//FAWE end //FAWE end

View File

@ -86,7 +86,14 @@ public class RecursivePickaxe implements BlockTool {
final int radius = (int) range; final int radius = (int) range;
final BlockReplace replace = new BlockReplace(editSession, (BlockTypes.AIR.getDefaultState())); final BlockReplace replace = new BlockReplace(editSession, (BlockTypes.AIR.getDefaultState()));
editSession.setMask(null); editSession.setMask(null);
RecursiveVisitor visitor = new RecursiveVisitor(new IdMask(editSession), replace, radius, editSession); RecursiveVisitor visitor = new RecursiveVisitor(
new IdMask(editSession),
replace,
radius,
editSession.getMinY(),
editSession.getMaxY(),
editSession
);
//TODO: Fix below //TODO: Fix below
//visitor.visit(pos); //visitor.visit(pos);
//Operations.completeBlindly(visitor); //Operations.completeBlindly(visitor);

View File

@ -39,8 +39,8 @@ public class GravityBrush implements Brush {
MaxChangedBlocksException { MaxChangedBlocksException {
//FAWE start - Ours operates differently to upstream, but does the same //FAWE start - Ours operates differently to upstream, but does the same
double endY = position.getY() + size; double endY = position.getY() + size;
double startPerformY = Math.max(0, position.getY() - size); double startPerformY = Math.max(editSession.getMinY(), position.getY() - size);
double startCheckY = fullHeight ? 0 : startPerformY; double startCheckY = fullHeight ? editSession.getMinY() : startPerformY;
for (double x = position.getX() + size; x > position.getX() - size; --x) { for (double x = position.getX() + size; x > position.getX() - size; --x) {
for (double z = position.getZ() + size; z > position.getZ() - size; --z) { for (double z = position.getZ() + size; z > position.getZ() - size; --z) {
double freeSpot = startCheckY; double freeSpot = startCheckY;

View File

@ -69,7 +69,7 @@ public class OffsetMaskParser extends InputParser<Mask> implements AliasedParser
submask = new ExistingBlockMask(context.requireExtent()); submask = new ExistingBlockMask(context.requireExtent());
} }
//FAWE start - OffsetMask > OffsetsMask //FAWE start - OffsetMask > OffsetsMask
return new OffsetMask(submask, BlockVector3.at(0, firstChar == '>' ? -1 : 1, 0)); return new OffsetMask(submask, BlockVector3.at(0, firstChar == '>' ? -1 : 1, 0), context.getMinY(), context.getMaxY());
//FAWE end //FAWE end
} }

View File

@ -23,6 +23,7 @@ import com.fastasyncworldedit.core.configuration.Caption;
import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.extension.factory.MaskFactory; import com.sk89q.worldedit.extension.factory.MaskFactory;
import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extension.platform.Locatable;
import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import org.enginehub.piston.inject.InjectedValueAccess; import org.enginehub.piston.inject.InjectedValueAccess;
@ -50,6 +51,8 @@ public class ParserContext {
private boolean preferringWildcard; private boolean preferringWildcard;
//Fawe start //Fawe start
private InjectedValueAccess injected; private InjectedValueAccess injected;
private int minY = Integer.MIN_VALUE;
private int maxY = Integer.MAX_VALUE;
//FAWE end //FAWE end
/** /**
@ -270,5 +273,69 @@ public class ParserContext {
public InjectedValueAccess getInjected() { public InjectedValueAccess getInjected() {
return injected; return injected;
} }
/**
* Attempts to resolve the minimum Y value associated with this context or returns 0.
* Caches both min and max y values.
*
* @return Minimum y value (inclusive) or 0
*/
public int getMinY() {
if (minY != Integer.MIN_VALUE) {
return minY;
}
Extent extent = null;
if (actor instanceof Locatable) {
extent = ((Locatable) actor).getExtent();
} else if (world != null) {
extent = world;
} else if (this.extent != null) {
extent = this.extent;
}
if (extent != null) {
minY = extent.getMinY();
maxY = extent.getMaxY();
} else {
minY = 0;
maxY = 255;
}
return minY;
}
/**
* Attempts to resolve the maximum Y value associated with this context or returns 255.
* Caches both min and max y values.
*
* @return Maximum y value (inclusive) or 255
*/
public int getMaxY() {
if (maxY != Integer.MAX_VALUE) {
return maxY;
}
Extent extent = null;
if (actor instanceof Locatable) {
extent = ((Locatable) actor).getExtent();
} else if (world != null) {
extent = world;
} else if (this.extent != null) {
extent = this.extent;
}
if (extent != null) {
minY = extent.getMinY();
maxY = extent.getMaxY();
} else {
minY = 0;
maxY = 255;
}
return maxY;
}
//FAWE end //FAWE end
} }

View File

@ -202,6 +202,11 @@ public class AbstractDelegateExtent implements Extent {
return extent.getMaxY(); return extent.getMaxY();
} }
@Override
public int getMinY() {
return extent.getMinY();
}
@Override @Override
public boolean relight(int x, int y, int z) { public boolean relight(int x, int y, int z) {
return extent.relight(x, y, z); return extent.relight(x, y, z);
@ -317,7 +322,7 @@ public class AbstractDelegateExtent implements Extent {
@Override @Override
public <T extends BlockStateHolder<T>> boolean setBlock( public <T extends BlockStateHolder<T>> boolean setBlock(
int x, @Range(from = 0, to = 255) int y, int x, int y,
int z, T block int z, T block
) throws WorldEditException { ) throws WorldEditException {
return extent.setBlock(x, y, z, block); return extent.setBlock(x, y, z, block);

View File

@ -200,9 +200,16 @@ public interface Extent extends InputExtent, OutputExtent {
- TODO: actually optimize these - TODO: actually optimize these
*/ */
/**
* Returns the highest solid 'terrain' block.
*
* @param x the X coordinate
* @param z the Z coordinate
* @param minY minimal height
* @param maxY maximal height
* @return height of highest block found or 'minY'
*/
default int getHighestTerrainBlock(final int x, final int z, int minY, int maxY) { default int getHighestTerrainBlock(final int x, final int z, int minY, int maxY) {
maxY = Math.min(maxY, Math.max(0, maxY));
minY = Math.max(0, minY);
for (int y = maxY; y >= minY; --y) { for (int y = maxY; y >= minY; --y) {
BlockState block = getBlock(x, y, z); BlockState block = getBlock(x, y, z);
if (block.getBlockType().getMaterial().isMovementBlocker()) { if (block.getBlockType().getMaterial().isMovementBlocker()) {
@ -212,9 +219,19 @@ public interface Extent extends InputExtent, OutputExtent {
return minY; return minY;
} }
/**
* Returns the highest solid 'terrain' block.
*
* @param x the X coordinate
* @param z the Z coordinate
* @param minY minimal height
* @param maxY maximal height
* @param filter a mask of blocks to consider, or null to consider any solid (movement-blocking) block
* @return height of highest block found or 'minY'
*/
default int getHighestTerrainBlock(final int x, final int z, int minY, int maxY, Mask filter) { default int getHighestTerrainBlock(final int x, final int z, int minY, int maxY, Mask filter) {
maxY = Math.min(maxY, Math.max(0, maxY)); maxY = Math.min(maxY, getMaxY());
minY = Math.max(0, minY); minY = Math.max(getMinY(), minY);
MutableBlockVector3 mutable = new MutableBlockVector3(); MutableBlockVector3 mutable = new MutableBlockVector3();
@ -226,6 +243,18 @@ public interface Extent extends InputExtent, OutputExtent {
return minY; return minY;
} }
/**
* Returns the nearest surface layer (up/down from start)
* <p>
* TODO: Someone understand this..?
*
* @param x x to search from
* @param z y to search from
* @param y z to search from
* @param minY min y to search (inclusive)
* @param maxY max y to search (inclusive)
* @return nearest surface layer
*/
default int getNearestSurfaceLayer(int x, int z, int y, int minY, int maxY) { default int getNearestSurfaceLayer(int x, int z, int y, int minY, int maxY) {
int clearanceAbove = maxY - y; int clearanceAbove = maxY - y;
int clearanceBelow = y - minY; int clearanceBelow = y - minY;
@ -255,7 +284,7 @@ public interface Extent extends InputExtent, OutputExtent {
for (int layer = y - clearance - 1; layer >= minY; layer--) { for (int layer = y - clearance - 1; layer >= minY; layer--) {
block = getBlock(x, layer, z); block = getBlock(x, layer, z);
if (block.getBlockType().getMaterial().isMovementBlocker() == state) { if (block.getBlockType().getMaterial().isMovementBlocker() == state) {
return ((layer + offset) << 4) + 0; return (layer + offset) << 4;
} }
data1 = PropertyGroup.LEVEL.get(block); data1 = PropertyGroup.LEVEL.get(block);
} }
@ -272,18 +301,20 @@ public interface Extent extends InputExtent, OutputExtent {
return (state ? minY : maxY) << 4; return (state ? minY : maxY) << 4;
} }
default int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, boolean ignoreAir) { /**
return getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, minY, maxY, ignoreAir); * Gets y value for the nearest block that is considered the surface of the terrain (cave roof/floor, mountain surface,
} * etc) where the block conforms to a given mask. Searches in the x,z column given.
*
default int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY) { * @param x column x
return getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, minY, maxY); * @param z column z
} * @param y start y
* @param minY minimum y height to consider. Inclusive.
default int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax) { * @param maxY maximum y height to consider. Inclusive.
return getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax, true); * @param failedMin if nothing found, the minimum y value to return if returning min
} * @param failedMax if nothing found, the maximum y value to return if returning max
* @param mask mask to test blocks against
* @return The y value of the nearest terrain block
*/
default int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax, Mask mask) { default int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax, Mask mask) {
y = Math.max(minY, Math.min(maxY, y)); y = Math.max(minY, Math.min(maxY, y));
int clearanceAbove = maxY - y; int clearanceAbove = maxY - y;
@ -320,6 +351,68 @@ public interface Extent extends InputExtent, OutputExtent {
return state ? failedMin : failedMax; return state ? failedMin : failedMax;
} }
/**
* Gets y value for the nearest block that is considered the surface of the terrain (cave roof/floor, mountain surface,
* etc). Searches in the x,z column given.
*
* @param x column x
* @param z column z
* @param y start y
* @param minY minimum y height to consider. Inclusive.
* @param maxY maximum y height to consider. Inclusive.
* @param ignoreAir if air at the final value if no block found should be considered for return, else return -1
* @return The y value of the nearest terrain block
*/
default int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, boolean ignoreAir) {
return getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, minY, maxY, ignoreAir);
}
/**
* Gets y value for the nearest block that is considered the surface of the terrain (cave roof/floor, mountain surface,
* etc). Searches in the x,z column given.
*
* @param x column x
* @param z column z
* @param y start y
* @param minY minimum y height to consider. Inclusive.
* @param maxY maximum y height to consider. Inclusive.
* @return The y value of the nearest terrain block
*/
default int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY) {
return getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, minY, maxY);
}
/**
* Gets y value for the nearest block that is considered the surface of the terrain (cave roof/floor, mountain surface,
* etc). Searches in the x,z column given.
*
* @param x column x
* @param z column z
* @param y start y
* @param minY minimum y height to consider. Inclusive.
* @param maxY maximum y height to consider. Inclusive.
* @param failedMin if nothing found, the minimum y value to return if returning min
* @param failedMax if nothing found, the maximum y value to return if returning max
* @return The y value of the nearest terrain block
*/
default int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin, int failedMax) {
return getNearestSurfaceTerrainBlock(x, z, y, minY, maxY, failedMin, failedMax, true);
}
/**
* Gets y value for the nearest block that is considered the surface of the terrain (cave roof/floor, mountain surface,
* etc). Searches in the x,z column given.
*
* @param x column x
* @param z column z
* @param y start y
* @param minY minimum y height to consider. Inclusive.
* @param maxY maximum y height to consider. Inclusive.
* @param failedMin if nothing found, the minimum y value to return if returning min
* @param failedMax if nothing found, the maximum y value to return if returning max
* @param ignoreAir if air at the final value if no block found should be considered for return, else return -1
* @return The y value of the nearest terrain block
*/
default int getNearestSurfaceTerrainBlock( default int getNearestSurfaceTerrainBlock(
int x, int x,
int z, int z,
@ -367,7 +460,7 @@ public interface Extent extends InputExtent, OutputExtent {
} }
} }
int result = state ? failedMin : failedMax; int result = state ? failedMin : failedMax;
if (result > 0 && !ignoreAir) { if (result > minY && !ignoreAir) {
block = getBlock(x, result, z); block = getBlock(x, result, z);
return block.getBlockType().getMaterial().isAir() ? -1 : result; return block.getBlockType().getMaterial().isAir() ? -1 : result;
} }
@ -444,12 +537,13 @@ public interface Extent extends InputExtent, OutputExtent {
spawnResource(region, new OreGen(this, mask, material, size, minY, maxY), rarity, frequency); spawnResource(region, new OreGen(this, mask, material, size, minY, maxY), rarity, frequency);
} }
//TODO: probably update these for 1.18 etc.
default void addOres(Region region, Mask mask) throws WorldEditException { default void addOres(Region region, Mask mask) throws WorldEditException {
addOre(region, mask, BlockTypes.DIRT.getDefaultState(), 33, 10, 100, 0, 255); addOre(region, mask, BlockTypes.DIRT.getDefaultState(), 33, 10, 100, getMinY(), getMaxY());
addOre(region, mask, BlockTypes.GRAVEL.getDefaultState(), 33, 8, 100, 0, 255); addOre(region, mask, BlockTypes.GRAVEL.getDefaultState(), 33, 8, 100, getMinY(), getMaxY());
addOre(region, mask, BlockTypes.ANDESITE.getDefaultState(), 33, 10, 100, 0, 79); addOre(region, mask, BlockTypes.ANDESITE.getDefaultState(), 33, 10, 100, getMinY(), 79);
addOre(region, mask, BlockTypes.DIORITE.getDefaultState(), 33, 10, 100, 0, 79); addOre(region, mask, BlockTypes.DIORITE.getDefaultState(), 33, 10, 100, getMinY(), 79);
addOre(region, mask, BlockTypes.GRANITE.getDefaultState(), 33, 10, 100, 0, 79); addOre(region, mask, BlockTypes.GRANITE.getDefaultState(), 33, 10, 100, getMinY(), 79);
addOre(region, mask, BlockTypes.COAL_ORE.getDefaultState(), 17, 20, 100, 0, 127); addOre(region, mask, BlockTypes.COAL_ORE.getDefaultState(), 17, 20, 100, 0, 127);
addOre(region, mask, BlockTypes.IRON_ORE.getDefaultState(), 9, 20, 100, 0, 63); addOre(region, mask, BlockTypes.IRON_ORE.getDefaultState(), 9, 20, 100, 0, 63);
addOre(region, mask, BlockTypes.GOLD_ORE.getDefaultState(), 9, 2, 100, 0, 31); addOre(region, mask, BlockTypes.GOLD_ORE.getDefaultState(), 9, 2, 100, 0, 31);
@ -556,11 +650,11 @@ public interface Extent extends InputExtent, OutputExtent {
} }
default int getMinY() { default int getMinY() {
return 0; return getMinimumPoint().getY();
} }
default int getMaxY() { default int getMaxY() {
return 255; return getMaximumPoint().getY();
} }
/** /**

View File

@ -347,7 +347,7 @@ public interface Clipboard extends Extent, Iterable<BlockVector3>, Closeable {
if (!pasteAir && block.getBlockType().getMaterial().isAir()) { if (!pasteAir && block.getBlockType().getMaterial().isAir()) {
continue; continue;
} }
if (pos.getY() < 0) { if (pos.getY() < extent.getMinY()) {
throw new RuntimeException("Y-Position cannot be less than 0!"); throw new RuntimeException("Y-Position cannot be less than 0!");
} }
extent.setBlock(xx, yy, zz, block); extent.setBlock(xx, yy, zz, block);

View File

@ -31,6 +31,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/ */
public class OffsetMask extends AbstractMask { public class OffsetMask extends AbstractMask {
//FAWE start - ignore resultant position outside world height range
private final int minY;
private final int maxY;
//FAWE end
private Mask mask; private Mask mask;
private BlockVector3 offset; private BlockVector3 offset;
@ -39,12 +43,30 @@ public class OffsetMask extends AbstractMask {
* *
* @param mask the mask * @param mask the mask
* @param offset the offset * @param offset the offset
* @deprecated use {@link OffsetMask#OffsetMask(Mask, BlockVector3, int, int)}
*/ */
@Deprecated
public OffsetMask(Mask mask, BlockVector3 offset) { public OffsetMask(Mask mask, BlockVector3 offset) {
this(mask, offset, 0, 255);
}
/**
* Create a new instance.
*
* @param mask the mask
* @param offset the offset
* @param minY minimum allowable y value to be set. Inclusive.
* @param maxY maximum allowable y value to be set. Inclusive.
*/
//FAWE start - ignore resultant position outside world height range
public OffsetMask(Mask mask, BlockVector3 offset, int minY, int maxY) {
checkNotNull(mask); checkNotNull(mask);
checkNotNull(offset); checkNotNull(offset);
this.mask = mask; this.mask = mask;
this.offset = offset; this.offset = offset;
this.minY = minY;
this.maxY = maxY;
//FAWE end
} }
/** /**
@ -87,11 +109,13 @@ public class OffsetMask extends AbstractMask {
@Override @Override
public boolean test(BlockVector3 vector) { public boolean test(BlockVector3 vector) {
//FAWE start - ignore resultant position outside world height range
BlockVector3 testPos = vector.add(offset); BlockVector3 testPos = vector.add(offset);
if (testPos.getBlockY() < 0 || testPos.getBlockY() > 255) { if (testPos.getBlockY() < minY || testPos.getBlockY() > maxY) {
return false; return false;
} }
return getMask().test(vector.add(offset)); return getMask().test(testPos);
//FAWE end
} }
@Nullable @Nullable
@ -108,7 +132,7 @@ public class OffsetMask extends AbstractMask {
//FAWE start //FAWE start
@Override @Override
public Mask copy() { public Mask copy() {
return new OffsetMask(mask.copy(), offset.toImmutable()); return new OffsetMask(mask.copy(), offset.toImmutable(), minY, maxY);
} }
//FAWE end //FAWE end

Some files were not shown because too many files have changed in this diff Show More