Plex-FAWE/worldedit-core/src/main/java/com/boydti/fawe/object/brush/visualization/cfi/HeightMapMCAGenerator.java

2137 lines
79 KiB
Java

package com.boydti.fawe.object.brush.visualization.cfi;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.beta.IBlocks;
import com.boydti.fawe.beta.IChunkGet;
import com.boydti.fawe.beta.IChunkSet;
import com.boydti.fawe.beta.implementation.blocks.FallbackChunkGet;
import com.boydti.fawe.beta.implementation.filter.block.ArrayFilterBlock;
import com.boydti.fawe.beta.implementation.packet.ChunkPacket;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.object.FaweInputStream;
import com.boydti.fawe.object.FaweOutputStream;
import com.boydti.fawe.object.Metadatable;
import com.boydti.fawe.object.brush.visualization.VirtualWorld;
import com.boydti.fawe.object.change.StreamChange;
import com.boydti.fawe.object.changeset.CFIChangeSet;
import com.boydti.fawe.object.collection.DifferentialArray;
import com.boydti.fawe.object.collection.DifferentialBlockBuffer;
import com.boydti.fawe.object.collection.LocalBlockVector2DSet;
import com.boydti.fawe.object.collection.SummedAreaTable;
import com.boydti.fawe.object.exception.FaweChunkLoadException;
import com.boydti.fawe.util.CachedTextureUtil;
import com.boydti.fawe.util.RandomTextureUtil;
import com.boydti.fawe.util.ReflectionUtils;
import com.boydti.fawe.util.TextureUtil;
import com.boydti.fawe.util.image.Drawable;
import com.boydti.fawe.util.image.ImageViewer;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.MutableBlockVector3;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.Identifiable;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.TreeGenerator;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockID;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import org.apache.logging.log4j.Logger;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Supplier;
import javax.annotation.Nullable;
public class HeightMapMCAGenerator extends MCAWriter implements StreamChange, Drawable, VirtualWorld {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private final MutableBlockVector3 mutable = new MutableBlockVector3();
private final DifferentialBlockBuffer blocks;
protected final DifferentialArray<byte[]> heights;
protected final DifferentialArray<byte[]> biomes;
protected final DifferentialArray<char[]> floor;
protected final DifferentialArray<char[]> main;
protected DifferentialArray<char[]> overlay;
protected Metadatable metaData = new Metadatable();
protected TextureUtil textureUtil;
protected final CFIPrimitives primitives = new CFIPrimitives();
private CFIPrimitives oldPrimitives = new CFIPrimitives();
@Override
public void flush() {}
public final class CFIPrimitives implements Cloneable {
int waterHeight;
int floorThickness;
int worldThickness;
boolean randomVariation = true;
int biomePriority;
char waterOrdinal = BlockTypes.WATER.getDefaultState().getOrdinalChar();
char bedrockOrdinal = BlockTypes.BEDROCK.getDefaultState().getOrdinalChar();
boolean modifiedMain;
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof CFIPrimitives)) {
return false;
}
try {
for (Field field : CFIPrimitives.class.getDeclaredFields()) {
if (field.get(this) != field.get(obj)) {
return false;
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return true;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Override
public void flushChanges(FaweOutputStream out) throws IOException {
heights.flushChanges(out);
biomes.flushChanges(out);
floor.flushChanges(out);
main.flushChanges(out);
out.writeBoolean(overlay != null);
if (overlay != null) {
overlay.flushChanges(out);
}
try {
for (Field field : ReflectionUtils
.sortFields(CFIPrimitives.class.getDeclaredFields())) {
Object now = field.get(primitives);
Object old = field.get(oldPrimitives);
boolean diff = old != now;
out.writeBoolean(diff);
if (diff) {
out.writePrimitive(old);
out.writePrimitive(now);
}
}
resetPrimitives();
} catch (Throwable neverHappens) {
neverHappens.printStackTrace();
}
blocks.flushChanges(out);
}
public boolean isModified() {
return blocks.isModified() ||
heights.isModified() ||
biomes.isModified() ||
overlay != null && overlay.isModified() ||
!primitives.equals(oldPrimitives);
}
private void resetPrimitives() throws CloneNotSupportedException {
oldPrimitives = (CFIPrimitives) primitives.clone();
}
@Override
public void undoChanges(FaweInputStream in) throws IOException {
heights.undoChanges(in);
biomes.undoChanges(in);
floor.undoChanges(in);
main.undoChanges(in);
if (in.readBoolean()) {
overlay.undoChanges(in);
}
try {
for (Field field : ReflectionUtils
.sortFields(CFIPrimitives.class.getDeclaredFields())) {
if (in.readBoolean()) {
field.set(primitives, in.readPrimitive(field.getType())); // old
in.readPrimitive(field.getType()); // new
}
}
resetPrimitives();
} catch (Throwable neverHappens) {
neverHappens.printStackTrace();
}
blocks.undoChanges(in);
}
@Override
public void redoChanges(FaweInputStream in) throws IOException {
heights.redoChanges(in);
biomes.redoChanges(in);
floor.redoChanges(in);
main.redoChanges(in);
if (in.readBoolean()) {
overlay.redoChanges(in);
}
try {
for (Field field : ReflectionUtils
.sortFields(CFIPrimitives.class.getDeclaredFields())) {
if (in.readBoolean()) {
in.readPrimitive(field.getType()); // old
field.set(primitives, in.readPrimitive(field.getType())); // new
}
}
resetPrimitives();
} catch (Throwable neverHappens) {
neverHappens.printStackTrace();
}
blocks.clearChanges(); // blocks.redoChanges(in); Unsupported
}
// @Override TODO NOT IMPLEMENTED
public void addEditSession(EditSession session) {
session.setFastMode(true);
this.editSession = session;
}
// Used for visualizing the world on a map
private ImageViewer viewer;
// Used for visualizing the world by sending chunk packets
// These three variables should be set together
// private IQueueExtent packetQueue;
private Player player;
private BlockVector2 chunkOffset = BlockVector2.ZERO;
private EditSession editSession;
// end
public HeightMapMCAGenerator(BufferedImage img, File regionFolder) {
this(img.getWidth(), img.getHeight(), regionFolder);
setHeight(img);
}
public HeightMapMCAGenerator(int width, int length, File regionFolder) {
super(width, length, regionFolder);
blocks = new DifferentialBlockBuffer(width, length);
heights = new DifferentialArray<>(new byte[getArea()]);
biomes = new DifferentialArray<>(new byte[getArea()]);
floor = new DifferentialArray<>(new char[getArea()]);
main = new DifferentialArray<>(new char[getArea()]);
char stone = BlockTypes.STONE.getDefaultState().getOrdinalChar();
char grass = BlockTypes.GRASS_BLOCK.getDefaultState().getOrdinalChar();
Arrays.fill(main.getCharArray(), stone);
Arrays.fill(floor.getCharArray(), grass);
}
public Metadatable getMetaData() {
return metaData;
}
@Override
public Vector3 getOrigin() {
return Vector3.at(chunkOffset.getBlockX() << 4, 0, chunkOffset.getBlockZ() << 4);
}
public boolean hasPacketViewer() {
return player != null;
}
public void setPacketViewer(Player player) {
this.player = player;
if (player != null) {
Location pos = player.getLocation();
this.chunkOffset = BlockVector2
.at(1 + (pos.getBlockX() >> 4), 1 + (pos.getBlockZ() >> 4));
}
}
public Player getOwner() {
return player;
}
private char[][][] getChunkArray(int x, int z) {
char[][][][][] blocksData = blocks.get();
if (blocksData == null) {
return null;
}
char[][][][] arr = blocksData[z];
return arr != null ? arr[x] : null;
}
public void setImageViewer(ImageViewer viewer) {
this.viewer = viewer;
}
public ImageViewer getImageViewer() {
return viewer;
}
@Override
public void update() {
if (viewer != null) {
viewer.view(this);
}
if (chunkOffset != null && player != null) {
World world = player.getWorld();
int lenCX = (getWidth() + 15) >> 4;
int lenCZ = (getLength() + 15) >> 4;
Location position = player.getLocation();
int pcx = (position.getBlockX() >> 4) - chunkOffset.getBlockX();
int pcz = (position.getBlockZ() >> 4) - chunkOffset.getBlockZ();
int scx = Math.max(0, pcx - 15);
int scz = Math.max(0, pcz - 15);
int ecx = Math.min(lenCX - 1, pcx + 15);
int ecz = Math.min(lenCZ - 1, pcz + 15);
for (int chunkZ = scz; chunkZ <= ecz; chunkZ++) {
for (int chunkX = scx; chunkX <= ecx; chunkX++) {
refreshChunk(world, chunkX, chunkZ);
}
}
}
}
public void refreshChunk(World world, int chunkX, int chunkZ) {
Supplier<IBlocks> blocksSupplier = () -> getChunk(chunkX, chunkZ);
int realChunkX = chunkX + chunkOffset.getBlockX();
int realChunkZ = chunkZ + chunkOffset.getBlockZ();
ChunkPacket packet = new ChunkPacket(realChunkX, realChunkZ, blocksSupplier, true);
world.sendFakeChunk(player, packet);
}
@Override
public void sendFakeChunk(@Nullable Player player, ChunkPacket packet) {
if (this.player != null) {
player.getWorld().sendFakeChunk(player, packet);
}
}
@Override
public void refreshChunk(int chunkX, int chunkZ) {
if (chunkOffset != null && player != null) {
refreshChunk(player.getWorld(), chunkX, chunkZ);
}
}
public IChunkSet getChunk(int chunkX, int chunkZ) {
// TODO don't generate new Writeable MCA chunk
MCAChunk tmp = new MCAChunk();
int bx = chunkX << 4;
int bz = chunkZ << 4;
write(tmp, bx, bx + 15, bz, bz + 15);
return tmp;
}
public TextureUtil getRawTextureUtil() {
if (textureUtil == null) {
textureUtil = Fawe.get().getTextureUtil();
}
return this.textureUtil;
}
public TextureUtil getTextureUtil() {
if (textureUtil == null) {
textureUtil = Fawe.get().getTextureUtil();
}
try {
if (primitives.randomVariation) {
return new RandomTextureUtil(textureUtil);
} else if (textureUtil instanceof CachedTextureUtil) {
return textureUtil;
} else {
return new CachedTextureUtil(textureUtil);
}
} catch (FileNotFoundException neverHappens) {
neverHappens.printStackTrace();
return null;
}
}
public void setBedrock(BlockState bedrock) {
this.primitives.bedrockOrdinal = bedrock.getOrdinalChar();
}
public void setFloorThickness(int floorThickness) {
this.primitives.floorThickness = floorThickness;
}
public void setWorldThickness(int height) {
this.primitives.worldThickness = height;
}
public void setWaterHeight(int waterHeight) {
this.primitives.waterHeight = waterHeight;
}
public void setWater(BlockState water) {
this.primitives.waterOrdinal = water.getOrdinalChar();
}
public void setTextureRandomVariation(boolean randomVariation) {
this.primitives.randomVariation = randomVariation;
}
public boolean getTextureRandomVariation() {
return this.primitives.randomVariation;
}
public void setTextureUtil(TextureUtil textureUtil) {
this.textureUtil = textureUtil;
}
public void smooth(BufferedImage img, boolean white, int radius, int iterations) {
smooth(img, null, white, radius, iterations);
}
public void smooth(Mask mask, int radius, int iterations) {
smooth(null, mask, false, radius, iterations);
}
public void smooth(BlockVector2 min, BlockVector2 max, int radius, int iterations) {
int snowLayer = BlockTypes.SNOW.getDefaultState().getOrdinalChar();
int snowBlock = BlockTypes.SNOW_BLOCK.getDefaultState().getOrdinalChar();
char[] floor = this.floor.get();
byte[] heights = this.heights.get();
int width = getWidth();
int length = getLength();
int minX = min.getBlockX();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxZ = max.getBlockZ();
int tableWidth = maxX - minX + 1;
int tableLength = maxZ - minZ + 1;
int smoothArea = tableWidth * tableLength;
long[] copy = new long[smoothArea];
char[] layers = new char[smoothArea];
SummedAreaTable table = new SummedAreaTable(copy, layers, tableWidth, radius);
for (int j = 0; j < iterations; j++) {
{ // Copy to table
int localIndex = 0;
int zIndex = minZ * getWidth();
for (int z = minZ; z <= maxZ; z++, zIndex += getWidth()) {
int index = zIndex + minX;
for (int x = minX; x <= maxX; x++, index++, localIndex++) {
int combined = floor[index];
if (BlockTypes.getFromStateOrdinal(combined) == BlockTypes.SNOW) {
layers[localIndex] = (char) (
((heights[index] & 0xFF) << 3) + (floor[index]
>> BlockTypesCache.BIT_OFFSET) - 7);
} else {
layers[localIndex] = (char) ((heights[index] & 0xFF) << 3);
}
}
}
}
// Process table
table.processSummedAreaTable();
// Copy from table
int localIndex = 0;
int zIndex = minZ * getWidth();
for (int z = minZ, localZ = 0; z <= maxZ; z++, localZ++, zIndex += getWidth()) {
int index = zIndex + minX;
for (int x = minX, localX = 0; x <= maxX; x++, localX++, index++, localIndex++) {
int y = heights[index] & 0xFF;
int newHeight = table.average(localX, localZ, localIndex);
setLayerHeight(index, newHeight);
}
}
}
}
private final void setLayerHeight(int index, int height) {
int blockHeight = height >> 3;
int layerHeight = height & 0x7;
setLayerHeight(index, blockHeight, layerHeight);
}
private final void setLayerHeight(int index, int blockHeight, int layerHeight) {
int floorState = floor.get()[index];
switch (floorState) {
case BlockID.SNOW:
case BlockID.SNOW_BLOCK:
if (layerHeight != 0) {
this.heights.setByte(index, (byte) (blockHeight + 1));
this.floor.setInt(index,
BlockTypes.SNOW.getDefaultState().getOrdinalChar() + layerHeight);
} else {
this.heights.setByte(index, (byte) blockHeight);
this.floor
.setInt(index, BlockTypes.SNOW_BLOCK.getDefaultState().getOrdinalChar());
}
break;
default:
this.heights.setByte(index, (byte) blockHeight);
break;
}
}
private final void setLayerHeightRaw(int index, int height) {
int blockHeight = height >> 3;
int layerHeight = height & 0x7;
setLayerHeightRaw(index, blockHeight, layerHeight);
}
private final void setLayerHeightRaw(int index, int blockHeight, int layerHeight) {
int floorState = floor.get()[index];
switch (floorState) {
case BlockID.SNOW:
case BlockID.SNOW_BLOCK:
if (layerHeight != 0) {
this.heights.getByteArray()[index] = (byte) (blockHeight + 1);
this.overlay.getCharArray()[index] = (char) (
BlockTypes.SNOW.getDefaultState().getOrdinalChar() + layerHeight);
} else {
this.heights.getByteArray()[index] = (byte) blockHeight;
this.overlay.getCharArray()[index] = BlockTypes.SNOW_BLOCK.getDefaultState()
.getOrdinalChar();
}
break;
default:
this.heights.getByteArray()[index] = (byte) blockHeight;
break;
}
}
private void smooth(BufferedImage img, Mask mask, boolean white, int radius, int iterations) {
char[] floor = this.floor.get();
byte[] heights = this.heights.get();
long[] copy = new long[heights.length];
char[] layers = new char[heights.length];
this.floor.record(() -> HeightMapMCAGenerator.this.heights.record(() -> {
int width = getWidth();
int length = getLength();
SummedAreaTable table = new SummedAreaTable(copy, layers, width, radius);
for (int j = 0; j < iterations; j++) {
for (int i = 0; i < heights.length; i++) {
int combined = floor[i];
if (BlockTypes.getFromStateOrdinal(combined) == BlockTypes.SNOW) {
layers[i] = (char) (
((heights[i] & 0xFF) << 3) + (floor[i] >> BlockTypesCache.BIT_OFFSET)
- 7);
} else {
layers[i] = (char) ((heights[i] & 0xFF) << 3);
}
}
int index = 0;
table.processSummedAreaTable();
if (img != null) {
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = img.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && !white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
int newHeight = table.average(x, z, index);
setLayerHeightRaw(index, newHeight);
}
}
}
} else if (mask != null) {
for (int z = 0; z < getLength(); z++) {
mutable.mutZ(z);
for (int x = 0; x < getWidth(); x++, index++) {
int y = heights[index] & 0xFF;
mutable.mutX(x);
mutable.mutY(y);
if (mask.test(mutable)) {
int newHeight = table.average(x, z, index);
setLayerHeightRaw(index, newHeight);
}
}
}
} else {
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int newHeight = table.average(x, z, index);
setLayerHeightRaw(index, newHeight);
}
}
}
}
}));
}
public void setHeight(BufferedImage img) {
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
heights.setByte(index, (byte) (img.getRGB(x, z) >> 8));
}
}
}
public void addCaves() throws WorldEditException {
CuboidRegion region = new CuboidRegion(BlockVector3.at(0, 0, 0),
BlockVector3.at(getWidth() - 1, 255, getLength() - 1));
addCaves(region);
}
@Deprecated
public void addSchems(Mask mask, List<ClipboardHolder> clipboards, int rarity, boolean rotate)
throws WorldEditException {
CuboidRegion region = new CuboidRegion(BlockVector3.at(0, 0, 0),
BlockVector3.at(getWidth() - 1, 255, getLength() - 1));
addSchems(region, mask, clipboards, rarity, rotate);
}
public void addSchems(BufferedImage img, Mask mask, List<ClipboardHolder> clipboards,
int rarity, int distance, boolean randomRotate) throws WorldEditException {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
double doubleRarity = rarity / 100d;
int index = 0;
AffineTransform identity = new AffineTransform();
LocalBlockVector2DSet placed = new LocalBlockVector2DSet();
for (int z = 0; z < getLength(); z++) {
mutable.mutZ(z);
for (int x = 0; x < getWidth(); x++, index++) {
int y = heights.getByte(index) & 0xFF;
int height = img.getRGB(x, z) & 0xFF;
if (height == 0
|| ThreadLocalRandom.current().nextInt(256) > height * doubleRarity) {
continue;
}
mutable.mutX(x);
mutable.mutY(y);
if (!mask.test(mutable)) {
continue;
}
if (placed.containsRadius(x, z, distance)) {
continue;
}
placed.add(x, z);
ClipboardHolder holder = clipboards
.get(ThreadLocalRandom.current().nextInt(clipboards.size()));
if (randomRotate) {
int rotate = ThreadLocalRandom.current().nextInt(4) * 90;
if (rotate != 0) {
holder.setTransform(new AffineTransform()
.rotateY(ThreadLocalRandom.current().nextInt(4) * 90));
} else {
holder.setTransform(identity);
}
}
Clipboard clipboard = holder.getClipboard();
Transform transform = holder.getTransform();
if (transform.isIdentity()) {
clipboard.paste(this, mutable, false);
} else {
clipboard.paste(this, mutable, false, transform);
}
if (x + distance < getWidth()) {
x += distance;
index += distance;
} else {
break;
}
}
}
}
public void addSchems(Mask mask, List<ClipboardHolder> clipboards, int rarity, int distance,
boolean randomRotate) throws WorldEditException {
int scaledRarity = 256 * rarity / 100;
int index = 0;
AffineTransform identity = new AffineTransform();
LocalBlockVector2DSet placed = new LocalBlockVector2DSet();
for (int z = 0; z < getLength(); z++) {
mutable.mutZ(z);
for (int x = 0; x < getWidth(); x++, index++) {
int y = heights.getByte(index) & 0xFF;
if (ThreadLocalRandom.current().nextInt(256) > scaledRarity) {
continue;
}
mutable.mutX(x);
mutable.mutY(y);
if (!mask.test(mutable)) {
continue;
}
if (placed.containsRadius(x, z, distance)) {
continue;
}
mutable.mutY(y + 1);
placed.add(x, z);
ClipboardHolder holder = clipboards
.get(ThreadLocalRandom.current().nextInt(clipboards.size()));
if (randomRotate) {
int rotate = ThreadLocalRandom.current().nextInt(4) * 90;
if (rotate != 0) {
holder.setTransform(new AffineTransform()
.rotateY(ThreadLocalRandom.current().nextInt(4) * 90));
} else {
holder.setTransform(identity);
}
}
Clipboard clipboard = holder.getClipboard();
Transform transform = holder.getTransform();
if (transform.isIdentity()) {
clipboard.paste(this, mutable, false);
} else {
clipboard.paste(this, mutable, false, transform);
}
if (x + distance < getWidth()) {
x += distance;
index += distance;
} else {
break;
}
}
}
}
public void addOre(Mask mask, Pattern material, int size, int frequency, int rarity, int minY,
int maxY) throws WorldEditException {
CuboidRegion region = new CuboidRegion(BlockVector3.at(0, 0, 0),
BlockVector3.at(getWidth() - 1, 255, getLength() - 1));
addOre(region, mask, material, size, frequency, rarity, minY, maxY);
}
public void addDefaultOres(Mask mask) throws WorldEditException {
addOres(new CuboidRegion(BlockVector3.at(0, 0, 0),
BlockVector3.at(getWidth() - 1, 255, getLength() - 1)), mask);
}
@Override
public BlockVector3 getMinimumPoint() {
return BlockVector3.at(0, 0, 0);
}
@Override
public Player getPlayer() {
return player;
}
@Override
public BlockVector3 getMaximumPoint() {
return BlockVector3.at(getWidth() - 1, 255, getLength() - 1);
}
@Override
public Set<SideEffect> applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet)
throws WorldEditException{
return SideEffectSet.none().getSideEffectsToApply();
}
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block)
throws WorldEditException {
return setBlock(position.getBlockX(), position.getBlockY(), position.getBlockZ(), block);
}
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects)
throws WorldEditException {
return setBlock(position.getBlockX(), position.getBlockY(), position.getBlockZ(), block);
}
private boolean setBlock(int x, int y, int z, char combined) {
int index = z * getWidth() + x;
if (index < 0 || index >= getArea()) {
return false;
}
int height = heights.getByte(index) & 0xFF;
switch (y - height) {
case 0:
floor.setInt(index, combined);
return true;
case 1:
char mainId = overlay.getChar(index);
char floorId = overlay.getChar(index);
floor.setInt(index, combined);
byte currentHeight = heights.getByte(index);
currentHeight++;
heights.setByte(index, currentHeight);
if (mainId == floorId) {
return true;
}
y--;
combined = floorId;
default:
try {
blocks.set(x, y, z, combined);
return true;
} catch (IndexOutOfBoundsException ignored) {
return false;
}
}
}
@Override
public boolean setBiome(int x, int y, int z, BiomeType biome) {
int index = z * getWidth() + x;
if (index < 0 || index >= getArea()) {
return false;
}
biomes.setByte(index, (byte) biome.getInternalId());
return true;
}
// @Override TODO NOT IMPLEMENTED
// public FaweChunk getFaweChunk(int chunkX, int chunkZ) {
// return new SimpleIntFaweChunk(this, chunkX, chunkZ);
// }
//
// @Override
// public FaweChunk getSnapshot(int chunkX, int chunkZ) {
// return getSnapshot(null, chunkX, chunkZ);
// }
//
// private FaweChunk getSnapshot(final MCAChunk chunk, int chunkX, int chunkZ) {
// return new LazyFaweChunk<MCAChunk>(this, chunkX, chunkZ) {
// @Override
// public MCAChunk getChunk() {
// MCAChunk tmp = chunk;
// if (tmp == null) {
// tmp = new MCAChunk();
// }
// tmp.setLoc(HeightMapMCAGenerator.this, chunkX, chunkZ);
// int cbx = chunkX << 4;
// int cbz = chunkZ << 4;
// int csx = Math.max(0, cbx);
// int csz = Math.max(0, cbz);
// int cex = Math.min(getWidth(), cbx + 15);
// int cez = Math.min(getLength(), cbz + 15);
// write(tmp, csx, cex, csz, cez);
// tmp.setLoc(HeightMapMCAGenerator.this, getX(), getZ());
// return tmp;
// }
//
// @Override
// public void addToQueue() {
// MCAChunk cached = getCachedChunk();
// if (cached != null) setChunk(cached);
// }
// };
// }
//
// @Override
// public Collection<FaweChunk> getFaweChunks() {
// return Collections.emptyList();
// }
//
// @Override
// public void setChunk(FaweChunk chunk) {
// int[][] src = chunk.getCombinedIdArrays();
// for (int i = 0; i < src.length; i++) {
// if (src[i] != null) {
// int bx = chunk.getX() << 4;
// int bz = chunk.getZ() << 4;
// int by = i << 4;
// for (int layer = i; layer < src.length; layer++) {
// int[] srcLayer = src[layer];
// if (srcLayer != null) {
// int index = 0;
// for (int y = 0; y < 16; y++) {
// int yy = by + y;
// for (int z = 0; z < 16; z++) {
// int zz = bz + z;
// for (int x = 0; x < 16; x++, index++) {
// int combined = srcLayer[index];
// if (combined != 0) {
// setBlock(bx + x, yy, zz, combined);
// }
// }
// }
// }
// }
// }
// break;
// }
// }
// }
@Nullable
@Override
public Path getStoragePath() {
return getFolder().toPath();
}
@Override
public int getMinY() {
return 0;
}
@Override
public boolean regenerateChunk(int x, int z, @Nullable BiomeType biome, @Nullable Long seed) {
// Unsupported
return false;
}
@Nullable
@Override
public Operation commit() {
EditSession curES = editSession;
if (curES != null && isModified()) {
try {
update();
Player esPlayer = curES.getPlayer();
UUID uuid = esPlayer != null ? esPlayer.getUniqueId() : Identifiable.CONSOLE;
try {
curES.setRawChangeSet(new CFIChangeSet(this, uuid));
} catch (IOException e) {
e.printStackTrace();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
return null;
}
@Override
public void close(boolean update) {
if (chunkOffset != null && player != null && update) {
World world = player.getWorld();
int lenCX = getWidth() + 15 >> 4;
int lenCZ = getLength() + 15 >> 4;
int OX = chunkOffset.getBlockX();
int OZ = chunkOffset.getBlockZ();
Location position = player.getLocation();
int pcx = (position.getBlockX() >> 4) - OX;
int pcz = (position.getBlockZ() >> 4) - OZ;
int scx = Math.max(0, pcx - 10);
int scz = Math.max(0, pcz - 10);
int ecx = Math.min(lenCX - 1, pcx + 10);
int ecz = Math.min(lenCZ - 1, pcz + 10);
for (int cz = scz; cz <= ecz; cz++) {
for (int cx = scx; cx <= ecx; cx++) {
world.refreshChunk(cx + OX, cz + OZ);
}
}
}
if (player != null) {
player.deleteMeta("CFISettings");
LocalSession session = player.getSession();
session.clearHistory();
}
player = null;
chunkOffset = null;
}
@Override
public BiomeType getBiomeType(int x, int y, int z) throws FaweChunkLoadException {
int index = z * getWidth() + x;
if (index < 0 || index >= getArea()) {
index = Math.floorMod(index, getArea());
}
return BiomeTypes.get(biomes.getByte(index));
}
public int getOrdinal(int x, int y, int z) throws FaweChunkLoadException {
int index = z * getWidth() + x;
if (y < 0) {
return 0;
}
if (index < 0 || index >= getArea() || x < 0 || x >= getWidth()) {
return 0;
}
int height = heights.getByte(index) & 0xFF;
if (y > height) {
if (y == height + 1) {
return overlay != null ? overlay.getChar(index) : 0;
}
if (blocks != null) {
short chunkX = (short) (x >> 4);
short chunkZ = (short) (z >> 4);
char[][][] map = getChunkArray(chunkX, chunkZ);
if (map != null) {
int combined = get(map, x, y, z);
if (combined != 0) {
return combined;
}
}
}
if (y <= primitives.waterHeight) {
return primitives.waterOrdinal;
}
return 0;
} else if (y == height) {
return overlay.getChar(index);
} else {
if (blocks != null) {
short chunkX = (short) (x >> 4);
short chunkZ = (short) (z >> 4);
char[][][] map = getChunkArray(chunkX, chunkZ);
if (map != null) {
int combined = get(map, x, y, z);
if (combined != 0) {
return combined;
}
}
}
return overlay.getChar(index);
}
}
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block)
throws WorldEditException {
return this.setBlock(x, y, z, block.getOrdinalChar());
}
@Override
public BiomeType getBiome(BlockVector3 position) {
return getBiomeType(position.getBlockX(), position.getBlockY(), position.getBlockZ());
}
@Override
public BlockState getBlock(BlockVector3 position) {
return getBlock(position.getX(), position.getY(), position.getZ());
}
public BlockState getFloor(int x, int z) {
int index = z * getWidth() + x;
return BlockState.getFromOrdinal(overlay.getChar(index));
}
public int getHeight(int x, int z) {
int index = z * getWidth() + x;
return heights.getByte(index) & 0xFF;
}
public int getHeight(int index) {
return heights.getByte(index) & 0xFF;
}
public <B extends BlockStateHolder<B>> void setFloor(int x, int z, B block) {
int index = z * getWidth() + x;
floor.setInt(index, block.getOrdinalChar());
}
@Override
public BlockState getBlock(int x, int y, int z) {
return BlockState.getFromOrdinal(getOrdinal(x, y, z));
}
@Override
public int getNearestSurfaceLayer(int x, int z, int y, int minY, int maxY) {
int index = z * getWidth() + x;
if (index < 0 || index >= getArea()) {
index = Math.floorMod(index, getArea());
}
return ((heights.getByte(index) & 0xFF) << 3) + (overlay.getChar(index) & 0xFF) + 1;
}
@Override
public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY) {
int index = z * getWidth() + x;
if (index < 0 || index >= getArea()) {
index = Math.floorMod(index, getArea());
}
return heights.getByte(index) & 0xFF;
}
@Override
public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY, int failedMin,
int failedMax) {
int index = z * getWidth() + x;
if (index < 0 || index >= getArea()) {
index = Math.floorMod(index, getArea());
}
return heights.getByte(index) & 0xFF;
}
public void setBiome(BufferedImage img, BiomeType biome, boolean white) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
byte biomeByte = (byte) biome.getInternalId();
biomes.record(new Runnable() {
@Override
public void run() {
byte[] biomeArr = biomes.get();
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = img.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && !white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
biomeArr[index] = biomeByte;
}
}
}
}
});
}
@Override
public BufferedImage draw() {
return new CFIDrawer(this).draw();
}
public void setBiomePriority(int value) {
this.primitives.biomePriority = value * 65536 / 100 - 32768;
}
public int getBiomePriority() {
return (primitives.biomePriority + 32768) * 100 / 65536;
}
public void setBlockAndBiomeColor(BufferedImage img, Mask mask, BufferedImage imgMask,
boolean whiteOnly) {
if (mask == null && imgMask == null) {
setBlockAndBiomeColor(img);
return;
}
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
TextureUtil textureUtil = getTextureUtil();
int widthIndex = img.getWidth() - 1;
int heightIndex = img.getHeight() - 1;
int maxIndex = getArea() - 1;
biomes.record(() -> floor.record(() -> main.record(() -> {
char[] mainArr = main.get();
char[] floorArr = floor.get();
byte[] biomesArr = biomes.get();
int index = 0;
char[] buffer = new char[2];
for (int z = 0; z < img.getHeight(); z++) {
mutable.mutZ(z);
for (int x = 0; x < img.getWidth(); x++, index++) {
if (mask != null) {
mutable.mutX(z);
mutable.mutY(heights.getByte(index) & 0xFF);
if (!mask.test(mutable)) {
continue;
}
}
if (imgMask != null) {
int height = imgMask.getRGB(x, z) & 0xFF;
if (height != 255 && (height <= 0 || !whiteOnly || ThreadLocalRandom
.current().nextInt(256) > height)) {
continue;
}
}
int color = img.getRGB(x, z);
if (textureUtil
.getIsBlockCloserThanBiome(buffer, color, primitives.biomePriority)) {
char combined = buffer[0];
mainArr[index] = combined;
floorArr[index] = combined;
}
biomesArr[index] = (byte) buffer[1];
}
}
})));
}
public void setBlockAndBiomeColor(BufferedImage img) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
TextureUtil textureUtil = getTextureUtil();
int heightIndex = img.getHeight() - 1;
biomes.record(() -> floor.record(() -> main.record(() -> {
char[] mainArr = main.get();
char[] floorArr = floor.get();
byte[] biomesArr = biomes.get();
char[] buffer = new char[2];
int index = 0;
for (int y = 0; y < img.getHeight(); y++) {
for (int x = 0; x < img.getWidth(); x++, index++) {
int color = img.getRGB(x, y);
if (textureUtil
.getIsBlockCloserThanBiome(buffer, color, primitives.biomePriority)) {
char combined = buffer[0];
mainArr[index] = combined;
floorArr[index] = combined;
}
biomesArr[index] = (byte) buffer[1];
}
}
})));
}
public void setBiomeColor(BufferedImage img) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
TextureUtil textureUtil = getTextureUtil();
biomes.record(() -> {
byte[] biomesArr = biomes.get();
int index = 0;
for (int y = 0; y < img.getHeight(); y++) {
for (int x = 0; x < img.getWidth(); x++) {
int color = img.getRGB(x, y);
TextureUtil.BiomeColor biome = textureUtil.getNearestBiome(color);
if (biome != null) {
biomesArr[index] = (byte) biome.id;
}
index++;
}
}
});
}
public void setColor(BufferedImage img, BufferedImage mask, boolean white) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
if (mask.getWidth() != getWidth() || mask.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
primitives.modifiedMain = true;
TextureUtil textureUtil = getTextureUtil();
floor.record(() -> main.record(() -> {
char[] mainArr = main.get();
char[] floorArr = floor.get();
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = mask.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && !white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
int color = img.getRGB(x, z);
BlockType block = textureUtil.getNearestBlock(color);
if (block != null) {
char combined = block.getDefaultState().getOrdinalChar();
mainArr[index] = combined;
floorArr[index] = combined;
}
}
}
}
}));
}
public void setColor(BufferedImage img, Mask mask) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
primitives.modifiedMain = true;
TextureUtil textureUtil = getTextureUtil();
floor.record(() -> main.record(() -> {
char[] mainArr = main.get();
char[] floorArr = floor.get();
int index = 0;
for (int z = 0; z < getLength(); z++) {
mutable.mutZ(z);
for (int x = 0; x < getWidth(); x++, index++) {
mutable.mutX(x);
mutable.mutY(heights.getByte(index) & 0xFF);
if (mask.test(mutable)) {
int color = img.getRGB(x, z);
BlockType block = textureUtil.getNearestBlock(color);
if (block != null) {
char combined = block.getDefaultState().getOrdinalChar();
mainArr[index] = combined;
floorArr[index] = combined;
}
}
}
}
}));
}
public void setColor(BufferedImage img) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
primitives.modifiedMain = true;
TextureUtil textureUtil = getTextureUtil();
floor.record(() -> main.record(() -> {
char[] mainArr = main.get();
char[] floorArr = floor.get();
int index = 0;
for (int z = 0; z < img.getHeight(); z++) {
for (int x = 0; x < img.getWidth(); x++) {
int color = img.getRGB(x, z);
BlockType block = textureUtil.getNearestBlock(color);
if (block != null) {
char combined = block.getDefaultState().getOrdinalChar();
mainArr[index] = combined;
floorArr[index] = combined;
}
index++;
}
}
}));
}
public void setColorWithGlass(BufferedImage img) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
TextureUtil textureUtil = getTextureUtil();
floor.record(() -> main.record(() -> {
char[] mainArr = main.get();
char[] floorArr = floor.get();
int index = 0;
for (int y = 0; y < img.getHeight(); y++) {
for (int x = 0; x < img.getWidth(); x++) {
int color = img.getRGB(x, y);
BlockType[] layer = textureUtil.getNearestLayer(color);
if (layer != null) {
floorArr[index] = layer[0].getDefaultState().getOrdinalChar();
mainArr[index] = layer[1].getDefaultState().getOrdinalChar();
}
index++;
}
}
}));
}
public void setBiome(Mask mask, BiomeType biome) {
int index = 0;
byte biomeByte = (byte) biome.getInternalId();
for (int z = 0; z < getLength(); z++) {
mutable.mutZ(z);
for (int x = 0; x < getWidth(); x++, index++) {
int y = heights.getByte(index) & 0xFF;
mutable.mutX(x);
mutable.mutY(y);
if (mask.test(mutable)) {
biomes.setByte(index, biomeByte);
}
}
}
}
public void setOverlay(BufferedImage img, Pattern pattern, boolean white) {
if (pattern instanceof BlockStateHolder) {
setOverlay(img, ((BlockStateHolder) pattern).getOrdinalChar(), white);
} else if (pattern instanceof BlockType) {
setOverlay(img, ((BlockType) pattern).getDefaultState().getOrdinalChar(), white);
} else {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
if (overlay == null) {
overlay = new DifferentialArray<>(new char[getArea()]);
}
overlay.record(() -> {
ArrayFilterBlock filter = new ArrayFilterBlock(this, overlay.get(), heights.get(),
getWidth(), getLength(), 1);
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = img.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && !white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
filter.init(x, z, index);
pattern.apply(this, filter, filter);
}
}
}
});
}
}
public void setMain(BufferedImage img, Pattern pattern, boolean white) {
if (pattern instanceof BlockStateHolder) {
setMain(img, ((BlockStateHolder) pattern).getOrdinalChar(), white);
} else {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
primitives.modifiedMain = true;
main.record(() -> {
ArrayFilterBlock filter = new ArrayFilterBlock(this, main.get(), heights.get(),
getWidth(), getLength(), -1);
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = img.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && !white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
filter.init(x, z, index);
pattern.apply(this, filter, filter);
}
}
}
});
}
}
public void setFloor(BufferedImage img, Pattern pattern, boolean white) {
if (pattern instanceof BlockStateHolder) {
setFloor(img, ((BlockStateHolder) pattern).getOrdinalChar(), white);
} else {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
floor.record(() -> {
ArrayFilterBlock filter = new ArrayFilterBlock(this, floor.get(), heights.get(),
getWidth(), getLength(), 1);
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = img.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && !white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
filter.init(x, z, index);
pattern.apply(this, filter, filter);
}
}
}
});
}
}
public void setColumn(BufferedImage img, Pattern pattern, boolean white) {
if (pattern instanceof BlockStateHolder) {
setColumn(img, ((BlockStateHolder) pattern).getOrdinalChar(), white);
} else {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
primitives.modifiedMain = true;
main.record(() -> floor.record(() -> {
ArrayFilterBlock filterFloor = new ArrayFilterBlock(this, floor.get(),
heights.get(), getWidth(), getLength(), 0);
ArrayFilterBlock filterMain = new ArrayFilterBlock(this, main.get(), heights.get(),
getWidth(), getLength(), -1);
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = img.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && !white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
filterFloor.init(x, z, index);
filterMain.init(x, z, index);
pattern.apply(this, filterFloor, filterFloor);
pattern.apply(this, filterMain, filterMain);
}
}
}
}));
}
}
public void setOverlay(Mask mask, Pattern pattern) {
if (pattern instanceof BlockStateHolder) {
setOverlay(mask, ((BlockStateHolder) pattern).getOrdinalChar());
} else {
if (overlay == null) {
overlay = new DifferentialArray<>(new char[getArea()]);
}
overlay.record(() -> {
ArrayFilterBlock filter = new ArrayFilterBlock(this, overlay.get(), heights.get(),
getWidth(), getLength(), 1);
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
filter.init(x, z, index);
if (mask.test(filter)) {
pattern.apply(this, filter, filter);
}
}
}
});
}
}
public void setFloor(Mask mask, Pattern pattern) {
if (pattern instanceof BlockStateHolder) {
setFloor(mask, ((BlockStateHolder) pattern).getOrdinalChar());
} else {
floor.record(() -> {
ArrayFilterBlock filter = new ArrayFilterBlock(this, floor.get(), heights.get(),
getWidth(), getLength(), 0);
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
filter.init(x, z, index);
if (mask.test(filter)) {
pattern.apply(this, filter, filter);
}
}
}
});
}
}
public void setMain(Mask mask, Pattern pattern) {
if (pattern instanceof BlockStateHolder) {
setMain(mask, ((BlockStateHolder) pattern).getOrdinalChar());
} else {
main.record(() -> {
ArrayFilterBlock filter = new ArrayFilterBlock(this, main.get(), heights.get(),
getWidth(), getLength(), -1);
primitives.modifiedMain = true;
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
if (mask.test(filter)) {
pattern.apply(this, filter, filter);
}
}
}
});
}
}
public void setColumn(Mask mask, Pattern pattern) {
if (pattern instanceof BlockStateHolder) {
setColumn(mask, ((BlockStateHolder) pattern).getOrdinalChar());
} else {
floor.record(() -> main.record(() -> {
ArrayFilterBlock floorFilter = new ArrayFilterBlock(this, floor.get(),
heights.get(), getWidth(), getLength(), 0);
ArrayFilterBlock mainFilter = new ArrayFilterBlock(this, main.get(), heights.get(),
getWidth(), getLength(), -1);
primitives.modifiedMain = true;
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
floorFilter.init(x, z, index);
mainFilter.init(x, z, index);
if (mask.test(mainFilter)) {
pattern.apply(this, mainFilter, mainFilter);
}
if (mask.test(floorFilter)) {
pattern.apply(this, floorFilter, floorFilter);
}
}
}
}));
}
}
public void setBiome(BiomeType biome) {
biomes.record(() -> Arrays.fill(biomes.get(), (byte) biome.getInternalId()));
}
public void setFloor(Pattern value) {
if (value instanceof BlockStateHolder) {
setFloor(((BlockStateHolder) value).getOrdinalChar());
} else {
floor.record(() -> {
ArrayFilterBlock filter = new ArrayFilterBlock(this, floor.get(), heights.get(),
getWidth(), getLength(), 0);
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
filter.init(x, z, index);
value.apply(this, filter, filter);
}
}
});
}
}
public void setColumn(Pattern value) {
if (value instanceof BlockStateHolder) {
setColumn(((BlockStateHolder) value).getOrdinalChar());
} else {
main.record(() -> floor.record(() -> {
ArrayFilterBlock floorFilter = new ArrayFilterBlock(this, floor.get(),
heights.get(), getWidth(), getLength(), 0);
ArrayFilterBlock mainFilter = new ArrayFilterBlock(this, main.get(), heights.get(),
getWidth(), getLength(), -1);
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
floorFilter.init(x, z, index);
mainFilter.init(x, z, index);
value.apply(this, floorFilter, floorFilter);
value.apply(this, mainFilter, mainFilter);
}
}
}));
}
}
public void setMain(Pattern value) {
if (value instanceof BlockStateHolder) {
setMain(((BlockStateHolder) value).getOrdinalChar());
} else {
main.record(() -> {
ArrayFilterBlock filter = new ArrayFilterBlock(this, main.get(), heights.get(),
getWidth(), getLength(), -1);
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
filter.init(x, z, index);
value.apply(this, filter, filter);
}
}
});
}
}
public void setOverlay(Pattern value) {
if (overlay == null) {
overlay = new DifferentialArray<>(new char[getArea()]);
}
if (value instanceof BlockStateHolder) {
setOverlay(((BlockStateHolder) value).getOrdinalChar());
} else {
overlay.record(() -> {
ArrayFilterBlock filter = new ArrayFilterBlock(this, overlay.get(), heights.get(),
getWidth(), getLength(), 1);
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
filter.init(x, z, index);
value.apply(this, filter, filter);
}
}
});
}
}
public void setHeight(int x, int z, int height) {
int index = z * getWidth() + x;
if (index < 0 || index >= getArea()) {
return;
}
heights.setByte(index, (byte) height);
}
public void setHeight(int index, int height) {
heights.setByte(index, (byte) height);
}
public void setHeights(int value) {
heights.record(() -> Arrays.fill(heights.get(), (byte) value));
}
@Override
public boolean shouldWrite(int chunkX, int chunkZ) {
return true;
}
@Override
public MCAChunk write(MCAChunk chunk, int csx, int cex, int csz, int cez) {
byte[] heights = this.heights.get();
byte[] biomes = this.biomes.get();
char[] main = this.main.get();
char[] floor = this.floor.get();
char[] overlay = this.overlay != null ? this.overlay.get() : null;
try {
int[] indexes = FaweCache.IMP.INDEX_STORE.get();
int index;
int maxY = 0;
int minY = Integer.MAX_VALUE;
int[] heightMap = FaweCache.IMP.HEIGHT_STORE.get();
int globalIndex;
for (int z = csz; z <= cez; z++) {
globalIndex = z * getWidth() + csx;
index = (z & 15) << 4;
for (int x = csx; x <= cex; x++, index++, globalIndex++) {
indexes[index] = globalIndex;
int height = heights[globalIndex] & 0xFF;
heightMap[index] = height;
maxY = Math.max(maxY, height);
minY = Math.min(minY, height);
}
}
boolean hasOverlay = this.overlay != null;
if (hasOverlay) {
maxY++;
}
int maxLayer = maxY >> 4;
for (int layer = 0; layer <= maxLayer; layer++) {
chunk.hasSections[layer] = true;
}
if (primitives.waterHeight != 0) {
int maxIndex = primitives.waterHeight << 8;
Arrays.fill(chunk.blocks, 0, maxIndex, primitives.waterOrdinal);
}
if (primitives.modifiedMain) { // If the main block is modified, we can't short circuit this
for (int z = csz; z <= cez; z++) {
index = (z & 15) << 4;
for (int x = csx; x <= cex; x++, index++) {
globalIndex = indexes[index];
char mainCombined = main[globalIndex];
for (int y = 0; y < minY; y++) {
chunk.blocks[index + (y << 8)] = mainCombined;
}
}
}
} else {
int maxIndex = minY << 8;
Arrays.fill(chunk.blocks, 0, maxIndex, (char) BlockID.STONE);
}
final boolean hasFloorThickness =
primitives.floorThickness != 0 || primitives.worldThickness != 0;
if (primitives.worldThickness != 0) {
int endLayer = minY - primitives.worldThickness + 1 >> 4;
for (int layer = 0; layer < endLayer; layer++) {
chunk.hasSections[layer] = false;
}
}
for (int z = csz; z <= cez; z++) {
index = (z & 15) << 4;
for (int x = csx; x <= cex; x++, index++) {
globalIndex = indexes[index];
int height = heightMap[index];
int maxMainY = height;
int minMainY = minY;
char mainCombined = main[globalIndex];
char floorCombined = floor[globalIndex];
if (hasFloorThickness) {
if (x > 0) {
maxMainY = Math.min(heights[globalIndex - 1] & 0xFF, maxMainY);
}
if (x < getWidth() - 1) {
maxMainY = Math.min(heights[globalIndex + 1] & 0xFF, maxMainY);
}
if (z > 0) {
maxMainY = Math.min(heights[globalIndex - getWidth()] & 0xFF, maxMainY);
}
if (z < getLength() - 1) {
maxMainY = Math.min(heights[globalIndex + getWidth()] & 0xFF, maxMainY);
}
int min = maxMainY;
if (primitives.floorThickness != 0) {
maxMainY = Math.max(0, maxMainY - (primitives.floorThickness - 1));
for (int y = maxMainY; y <= height; y++) {
chunk.blocks[index + (y << 8)] = floorCombined;
}
} else {
chunk.blocks[index + (height << 8)] = floorCombined;
}
if (primitives.worldThickness != 0) {
minMainY = Math.max(minY, min - primitives.worldThickness + 1);
for (int y = minY; y < minMainY; y++) {
chunk.blocks[index + (y << 8)] = BlockID.AIR;
}
}
} else {
chunk.blocks[index + (height << 8)] = floorCombined;
}
for (int y = minMainY; y < maxMainY; y++) {
chunk.blocks[index + (y << 8)] = mainCombined;
}
if (hasOverlay) {
char overlayCombined = overlay[globalIndex];
int overlayIndex = index + (height + 1 << 8);
chunk.blocks[overlayIndex] = overlayCombined;
}
if (primitives.bedrockOrdinal != 0) {
chunk.blocks[index] = primitives.bedrockOrdinal;
}
}
}
char[][][] localBlocks = getChunkArray(chunk.getX(), chunk.getZ());
if (localBlocks != null) {
index = 0;
for (int layer = 0; layer < 16; layer++) {
int by = layer << 4;
int ty = by + 15;
for (int y = by; y <= ty; y++, index += 256) {
char[][] yBlocks = localBlocks[y];
if (yBlocks != null) {
chunk.hasSections[layer] = true;
for (int z = 0; z < yBlocks.length; z++) {
char[] zBlocks = yBlocks[z];
if (zBlocks != null) {
int zIndex = index + (z << 4);
for (int x = 0; x < zBlocks.length; x++, zIndex++) {
char combined = zBlocks[x];
if (combined == 0) {
continue;
}
chunk.blocks[zIndex] = combined;
}
}
}
}
}
}
}
for (int i = 0; i < 256; i++) {
byte biomeId = biomes[indexes[i]];
if (biomeId != 0) {
chunk.biomes[i] = BiomeTypes.get(biomeId);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
return chunk;
}
private void setUnsafe(char[][][] map, char combined, int x, int y, int z) {
char[][] yMap = map[y];
if (yMap == null) {
map[y] = yMap = new char[16][];
}
char[] zMap = yMap[z];
if (zMap == null) {
yMap[z] = zMap = new char[16];
}
zMap[x] = combined;
}
private int get(char[][][] map, int x, int y, int z) {
char[][] yMap = map[y];
if (yMap == null) {
return 0;
}
char[] zMap = yMap[z & 15];
if (zMap == null) {
return 0;
}
return zMap[x & 15];
}
private void setOverlay(Mask mask, int combined) {
int index = 0;
if (overlay == null) {
overlay = new DifferentialArray<>(new char[getArea()]);
}
for (int z = 0; z < getLength(); z++) {
mutable.mutZ(z);
for (int x = 0; x < getWidth(); x++, index++) {
int y = heights.getByte(index) & 0xFF;
mutable.mutX(x);
mutable.mutY(y);
if (mask.test(mutable)) {
overlay.setInt(index, combined);
}
}
}
}
private void setFloor(Mask mask, int combined) {
int index = 0;
for (int z = 0; z < getLength(); z++) {
mutable.mutZ(z);
for (int x = 0; x < getWidth(); x++, index++) {
int y = heights.getByte(index) & 0xFF;
mutable.mutX(x);
mutable.mutY(y);
if (mask.test(mutable)) {
floor.setInt(index, combined);
}
}
}
}
private void setMain(Mask mask, int combined) {
primitives.modifiedMain = true;
int index = 0;
for (int z = 0; z < getLength(); z++) {
mutable.mutZ(z);
for (int x = 0; x < getWidth(); x++, index++) {
int y = heights.getByte(index) & 0xFF;
mutable.mutX(x);
mutable.mutY(y);
if (mask.test(mutable)) {
main.setInt(index, combined);
}
}
}
}
private void setColumn(Mask mask, int combined) {
primitives.modifiedMain = true;
int index = 0;
for (int z = 0; z < getLength(); z++) {
mutable.mutZ(z);
for (int x = 0; x < getWidth(); x++, index++) {
int y = heights.getByte(index) & 0xFF;
mutable.mutX(x);
mutable.mutY(y);
if (mask.test(mutable)) {
floor.setInt(index, combined);
main.setInt(index, combined);
}
}
}
}
private void setFloor(char value) {
floor.record(() -> Arrays.fill(floor.get(), value));
}
private void setColumn(char value) {
setFloor(value);
setMain(value);
}
private void setMain(char value) {
primitives.modifiedMain = true;
main.record(() -> Arrays.fill(main.get(), value));
}
private void setOverlay(char value) {
if (overlay == null) {
overlay = new DifferentialArray<>(new char[getArea()]);
}
overlay.record(() -> Arrays.fill(overlay.get(), value));
}
private void setOverlay(BufferedImage img, char combined, boolean white) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
if (overlay == null) {
overlay = new DifferentialArray<>(new char[getArea()]);
}
overlay.record(() -> {
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = img.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
overlay.get()[index] = combined;
}
}
}
});
}
private void setMain(BufferedImage img, char combined, boolean white) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
primitives.modifiedMain = true;
main.record(() -> {
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = img.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && !white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
main.get()[index] = combined;
}
}
}
});
}
private void setFloor(BufferedImage img, char combined, boolean white) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
floor.record(() -> {
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = img.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && !white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
floor.get()[index] = combined;
}
}
}
});
}
private void setColumn(BufferedImage img, char combined, boolean white) {
if (img.getWidth() != getWidth() || img.getHeight() != getLength()) {
throw new IllegalArgumentException(
"Input image dimensions do not match the current height map!");
}
primitives.modifiedMain = true;
main.record(() -> floor.record(() -> {
int index = 0;
for (int z = 0; z < getLength(); z++) {
for (int x = 0; x < getWidth(); x++, index++) {
int height = img.getRGB(x, z) & 0xFF;
if (height == 255 || height > 0 && !white && ThreadLocalRandom.current()
.nextInt(256) <= height) {
main.get()[index] = combined;
floor.get()[index] = combined;
}
}
}
}));
}
@Override
public int getMaxY() {
return 255;
}
@Override
public String getName() {
File folder = getFolder();
if (folder != null) {
String name = folder.getName();
if (name.equalsIgnoreCase("region")) {
return folder.getParentFile().getName();
}
return name;
}
return Integer.toString(hashCode());
}
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block,
boolean notifyAndLight) throws WorldEditException {
return setBlock(position, block);
}
// These aren't implemented yet...
@Override
public boolean setTile(int x, int y, int z, CompoundTag tile) throws WorldEditException {
return false;
}
@Override
public int getBlockLightLevel(BlockVector3 position) {
return 0;
}
@Override
public boolean clearContainerBlockContents(BlockVector3 position) {
return false;
}
@Override
public boolean regenerate(Region region, EditSession editSession) {
return false;
}
@Override
public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession,
BlockVector3 position) throws MaxChangedBlocksException {
return false;
}
@Override
public void dropItem(Vector3 position, BaseItemStack item) {
// TODO Auto-generated method stub
}
@Override
public boolean playEffect(Vector3 position, int type, int data) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean notifyAndLightBlock(BlockVector3 position, BlockState previousType)
throws WorldEditException {
// TODO Auto-generated method stub
return false;
}
@Override
public BlockVector3 getSpawnPosition() {
// TODO Auto-generated method stub
return null;
}
@Override
public IChunkGet get(int x, int z) {
LOGGER.debug("Should not be using buffering with HMMG");
return new FallbackChunkGet(this, x, z);
}
}