/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.pepsoft.worldpainter; import java.awt.Point; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.PrintStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.pepsoft.util.MathUtils; import org.pepsoft.util.undo.BufferKey; import org.pepsoft.util.undo.UndoListener; import org.pepsoft.util.undo.UndoManager; import static org.pepsoft.util.ObjectUtils.copyObject; import static org.pepsoft.worldpainter.Constants.TILE_SIZE; import org.pepsoft.worldpainter.gardenofeden.Seed; import org.pepsoft.worldpainter.layers.Layer; import org.pepsoft.worldpainter.layers.Layer.DataSize; import static org.pepsoft.worldpainter.Constants.TILE_SIZE_BITS; import static org.pepsoft.worldpainter.Constants.TILE_SIZE_MASK; import org.pepsoft.worldpainter.layers.Biome; import org.pepsoft.worldpainter.layers.FloodWithLava; import static org.pepsoft.worldpainter.Tile.TileBuffer.*; import static org.pepsoft.worldpainter.layers.Layer.DataSize.*; /** * * @author pepijn */ public class Tile extends InstanceKeeper implements Serializable, UndoListener, Cloneable { public Tile(int x, int y, int maxHeight) { this(x, y, maxHeight, true); } protected Tile(int x, int y, int maxHeight, boolean init) { this.x = x; this.y = y; this.maxHeight = maxHeight; if (maxHeight > 256) { tall = true; } if (init) { terrain = new byte[TILE_SIZE * TILE_SIZE]; if (maxHeight > 256) { tallHeightMap = new int[TILE_SIZE * TILE_SIZE]; tallWaterLevel = new short[TILE_SIZE * TILE_SIZE]; } else { heightMap = new short[TILE_SIZE * TILE_SIZE]; waterLevel = new byte[TILE_SIZE * TILE_SIZE]; } layerData = new HashMap<>(); bitLayerData = new HashMap<>(); init(); } } public int getX() { return x; } public int getY() { return y; } public synchronized int getMaxHeight() { return maxHeight; } public synchronized void setMaxHeight(int maxHeight, HeightTransform heightTransform) { if (maxHeight != this.maxHeight) { this.maxHeight = maxHeight; maxY = maxHeight - 1; boolean newTall = maxHeight > 256; if (newTall == tall) { // Tallness is not changing if (! heightTransform.isIdentity()) { for (int x = 0; x < TILE_SIZE; x++) { for (int y = 0; y < TILE_SIZE; y++) { setHeight(x, y, clamp(heightTransform.transformHeight(getHeight(x, y)))); setWaterLevel(x, y, clamp(heightTransform.transformHeight(getWaterLevel(x, y)))); } } } else { for (int x = 0; x < TILE_SIZE; x++) { for (int y = 0; y < TILE_SIZE; y++) { setHeight(x, y, clamp(getHeight(x, y))); setWaterLevel(x, y, clamp(getWaterLevel(x, y))); } } } } else if (tall) { // Going from tall to not tall heightMap = new short[TILE_SIZE * TILE_SIZE]; waterLevel = new byte[TILE_SIZE * TILE_SIZE]; if (undoManager != null) { undoManager.addBuffer(HEIGHTMAP_BUFFER_KEY, heightMap, this); undoManager.addBuffer(WATERLEVEL_BUFFER_KEY, waterLevel, this); readableBuffers.add(HEIGHTMAP); readableBuffers.add(WATERLEVEL); writeableBuffers.add(HEIGHTMAP); writeableBuffers.add(WATERLEVEL); } tall = false; for (int x = 0; x < TILE_SIZE; x++) { for (int y = 0; y < TILE_SIZE; y++) { setHeight(x, y, clamp(heightTransform.transformHeight(tallHeightMap[x | (y << TILE_SIZE_BITS)] / 256f))); setWaterLevel(x, y, clamp(heightTransform.transformHeight(tallWaterLevel[x | (y << TILE_SIZE_BITS)]))); } } if (undoManager != null) { undoManager.removeBuffer(TALL_HEIGHTMAP_BUFFER_KEY); undoManager.removeBuffer(TALL_WATERLEVEL_BUFFER_KEY); readableBuffers.remove(TALL_HEIGHTMAP); readableBuffers.remove(TALL_WATERLEVEL); writeableBuffers.remove(TALL_HEIGHTMAP); writeableBuffers.remove(TALL_WATERLEVEL); } tallHeightMap = null; tallWaterLevel = null; } else { // Going from not tall to tall tallHeightMap = new int[TILE_SIZE * TILE_SIZE]; tallWaterLevel = new short[TILE_SIZE * TILE_SIZE]; if (undoManager != null) { undoManager.addBuffer(TALL_HEIGHTMAP_BUFFER_KEY, tallHeightMap, this); undoManager.addBuffer(TALL_WATERLEVEL_BUFFER_KEY, tallWaterLevel, this); readableBuffers.add(TALL_HEIGHTMAP); readableBuffers.add(TALL_WATERLEVEL); writeableBuffers.add(TALL_HEIGHTMAP); writeableBuffers.add(TALL_WATERLEVEL); } tall = true; for (int x = 0; x < TILE_SIZE; x++) { for (int y = 0; y < TILE_SIZE; y++) { setHeight(x, y, clamp(heightTransform.transformHeight((heightMap[x | (y << TILE_SIZE_BITS)] & 0xFFFF) / 256f))); setWaterLevel(x, y, clamp(heightTransform.transformHeight(waterLevel[x | (y << TILE_SIZE_BITS)] & 0xFF))); } } if (undoManager != null) { undoManager.removeBuffer(HEIGHTMAP_BUFFER_KEY); undoManager.removeBuffer(WATERLEVEL_BUFFER_KEY); readableBuffers.remove(HEIGHTMAP); readableBuffers.remove(WATERLEVEL); writeableBuffers.remove(HEIGHTMAP); writeableBuffers.remove(WATERLEVEL); } heightMap = null; waterLevel = null; } heightMapChanged(); waterLevelChanged(); } else if (! heightTransform.isIdentity()) { for (int x = 0; x < TILE_SIZE; x++) { for (int y = 0; y < TILE_SIZE; y++) { setHeight(x, y, clamp(heightTransform.transformHeight(getHeight(x, y)))); setWaterLevel(x, y, clamp(heightTransform.transformHeight(getWaterLevel(x, y)))); } } heightMapChanged(); waterLevelChanged(); } } public int getIntHeight(int x, int y) { return (int) (getHeight(x, y) + 0.5f); } public synchronized float getHeight(int x, int y) { if (tall) { ensureReadable(TALL_HEIGHTMAP); return tallHeightMap[x | (y << TILE_SIZE_BITS)] / 256f; } else { ensureReadable(HEIGHTMAP); return (heightMap[x | (y << TILE_SIZE_BITS)] & 0xFFFF) / 256f; } } public synchronized void setHeight(int x, int y, float height) { if (tall) { ensureWriteable(TALL_HEIGHTMAP); tallHeightMap[x | (y << TILE_SIZE_BITS)] = (int) (height * 256); } else { ensureWriteable(HEIGHTMAP); heightMap[x | (y << TILE_SIZE_BITS)] = (short) (height * 256); } heightMapChanged(); } public synchronized int getRawHeight(int x, int y) { if (tall) { ensureReadable(TALL_HEIGHTMAP); return tallHeightMap[x | (y << TILE_SIZE_BITS)]; } else { ensureReadable(HEIGHTMAP); return heightMap[x | (y << TILE_SIZE_BITS)] & 0xFFFF; } } public synchronized void setRawHeight(int x, int y, int rawHeight) { if (tall) { ensureWriteable(TALL_HEIGHTMAP); tallHeightMap[x | (y << TILE_SIZE_BITS)] = rawHeight; } else { ensureWriteable(HEIGHTMAP); heightMap[x | (y << TILE_SIZE_BITS)] = (short) rawHeight; } heightMapChanged(); } public synchronized float getSlope(int x, int y) { return Math.max(Math.max(Math.abs(getHeight(x + 1, y) - getHeight(x - 1, y)) / 2, Math.abs(getHeight(x + 1, y + 1) - getHeight(x - 1, y - 1)) / SQRT_OF_EIGHT), Math.max(Math.abs(getHeight(x, y + 1) - getHeight(x, y - 1)) / 2, Math.abs(getHeight(x - 1, y + 1) - getHeight(x + 1, y - 1)) / SQRT_OF_EIGHT)); } public synchronized Terrain getTerrain(int x, int y) { ensureReadable(TERRAIN); return TERRAIN_VALUES[terrain[x | (y << TILE_SIZE_BITS)] & 0xFF]; } public synchronized void setTerrain(int x, int y, Terrain terrain) { ensureWriteable(TERRAIN); this.terrain[x | (y << TILE_SIZE_BITS)] = (byte) terrain.ordinal(); terrainChanged(); } public synchronized int getWaterLevel(int x, int y) { if (tall) { ensureReadable(TALL_WATERLEVEL); return tallWaterLevel[x | (y << TILE_SIZE_BITS)]; } else { ensureReadable(WATERLEVEL); return waterLevel[x | (y << TILE_SIZE_BITS)] & 0xFF; } } public synchronized void setWaterLevel(int x, int y, int waterLevel) { if (tall) { ensureWriteable(TALL_WATERLEVEL); this.tallWaterLevel[x | (y << TILE_SIZE_BITS)] = (short) waterLevel; } else { ensureWriteable(WATERLEVEL); this.waterLevel[x | (y << TILE_SIZE_BITS)] = (byte) waterLevel; } waterLevelChanged(); } public synchronized List<Layer> getLayers() { if (cachedLayers == null) { ensureReadable(LAYER_DATA); ensureReadable(BIT_LAYER_DATA); List<Layer> layers = new ArrayList<>(); layers.addAll(layerData.keySet()); layers.addAll(bitLayerData.keySet()); Collections.sort(layers); cachedLayers = Collections.unmodifiableList(layers); } return cachedLayers; } public synchronized boolean hasLayer(Layer layer) { DataSize dataSize = layer.getDataSize(); if ((dataSize == DataSize.BIT) || (dataSize == DataSize.BIT_PER_CHUNK)) { ensureReadable(BIT_LAYER_DATA); return bitLayerData.containsKey(layer); } else { ensureReadable(LAYER_DATA); return layerData.containsKey(layer); } } public List<Layer> getActiveLayers(int x, int y) { ensureReadable(BIT_LAYER_DATA); ensureReadable(LAYER_DATA); List<Layer> activeLayers = new ArrayList<>(bitLayerData.size() + layerData.size()); for (Map.Entry<Layer, BitSet> entry: bitLayerData.entrySet()) { final Layer layer = entry.getKey(); final DataSize dataSize = layer.getDataSize(); if (((dataSize == DataSize.BIT) && getBitPerBlockLayerValue(entry.getValue(), x, y)) || ((dataSize == DataSize.BIT_PER_CHUNK) && getBitPerChunkLayerValue(entry.getValue(), x, y))) { activeLayers.add(layer); } } for (Map.Entry<Layer, byte[]> entry: layerData.entrySet()) { final Layer layer = entry.getKey(); final DataSize dataSize = layer.getDataSize(); final int defaultValue = layer.getDefaultValue(); if (dataSize == DataSize.NIBBLE) { final int byteOffset = x | (y << TILE_SIZE_BITS); final byte _byte = entry.getValue()[byteOffset / 2]; if ((byteOffset % 2 == 0) ? ((_byte & 0x0F) != defaultValue) : (((_byte & 0xF0) >> 4) != defaultValue)) { activeLayers.add(layer); } } else if ((entry.getValue()[x | (y << TILE_SIZE_BITS)] & 0xFF) != defaultValue) { activeLayers.add(layer); } } return activeLayers; } /** * Get a list of all layers in use in the tile, as well as the set of * additional layers provided, the total sorted by layer priority. * * @param additionalLayers The additional layers to include in the list. * @return The list of all layers provided or in use on the tile, sorted by * layer priority. */ public List<Layer> getLayers(Set<Layer> additionalLayers) { SortedSet<Layer> layers = new TreeSet<>(additionalLayers); layers.addAll(getLayers()); return new ArrayList<>(layers); } public synchronized boolean getBitLayerValue(Layer layer, int x, int y) { if ((layer.getDataSize() != Layer.DataSize.BIT) && (layer.getDataSize() != Layer.DataSize.BIT_PER_CHUNK)) { throw new IllegalArgumentException("Layer is not bit sized"); } ensureReadable(BIT_LAYER_DATA); BitSet bitSet = bitLayerData.get(layer); if (bitSet == null) { return false; } else { if (layer.getDataSize() == Layer.DataSize.BIT) { return getBitPerBlockLayerValue(bitSet, x, y); } else { return getBitPerChunkLayerValue(bitSet, x, y); } } } /** * Count the number of blocks where the specified bit layer is set in a * square around a particular location * * @param layer The bit layer to count. * @param x The X coordinate (local to the tile) of the location around * which to count the layer. * @param y The Y coordinate (local to the tile) of the location around * which to count the layer. * @param r The radius of the square. * @return The number of blocks in the specified square where the specified * bit layer is set. */ public synchronized int getBitLayerCount(Layer layer, int x, int y, int r) { if ((layer.getDataSize() != Layer.DataSize.BIT) && (layer.getDataSize() != Layer.DataSize.BIT_PER_CHUNK)) { throw new IllegalArgumentException("Layer is not bit sized"); } if (((x - r) < 0) || ((x + r) >= TILE_SIZE) || ((y - r) < 0) || ((y + r) >= TILE_SIZE)) { throw new IllegalArgumentException("Requested area not contained entirely on tile"); } ensureReadable(BIT_LAYER_DATA); BitSet bitSet = bitLayerData.get(layer); if (bitSet == null) { return 0; } else { boolean bitPerChunk = layer.getDataSize() == Layer.DataSize.BIT_PER_CHUNK; int count = 0, bitOffset; for (int dx = -r; dx <= r; dx++) { for (int dy = -r; dy <= r; dy++) { if (bitPerChunk) { bitOffset = ((x + dx) / 16) + ((y + dy) / 16) * (TILE_SIZE / 16); } else { bitOffset = x + dx + (y + dy) * TILE_SIZE; } if (bitSet.get(bitOffset)) { count++; } } } return count; } } /** * Gets all layers that are set at the specified location, along with their * intensities. For bit valued layers the intensity is zero for off, one for * on. * * @param x The X location for which to retrieve all layers. * @param y The Y location for which to retrieve all layers. * @return A map with all layers set at the specified location, mapped to * their intensities at that location. May either be <code>null</code> * or an empty map if no layers are present. */ public Map<Layer, Integer> getLayersAt(int x, int y) { Map<Layer, Integer> layers = null; ensureReadable(LAYER_DATA); for (Map.Entry<Layer, byte[]> entry: layerData.entrySet()) { Layer layer = entry.getKey(); byte[] layerValues = entry.getValue(); int value; if (layer.getDataSize() == DataSize.NIBBLE) { int byteOffset = x | (y << TILE_SIZE_BITS); byte _byte = layerValues[byteOffset / 2]; if (byteOffset % 2 == 0) { value = _byte & 0x0F; } else { value = (_byte & 0xF0) >> 4; } } else { value = layerValues[x | (y << TILE_SIZE_BITS)] & 0xFF; } if (value != layer.getDefaultValue()) { if (layers == null) { layers = new HashMap<>(); } layers.put(layer, value); } } ensureReadable(BIT_LAYER_DATA); for (Map.Entry<Layer, BitSet> entry: bitLayerData.entrySet()) { Layer layer = entry.getKey(); BitSet layerValues = entry.getValue(); int value; if (layer.getDataSize() == Layer.DataSize.BIT) { value = layerValues.get(x | (y << TILE_SIZE_BITS)) ? 1 : 0; } else { value = layerValues.get((x >> 4) + (y >> 4) * (TILE_SIZE >> 4)) ? 1 : 0; } if (value != layer.getDefaultValue()) { if (layers == null) { layers = new HashMap<>(); } layers.put(layer, value); } } return layers; } /** * Count the number of blocks that are flooded in a square around a * particular location * * @param x The X coordinate (local to the tile) of the location around * which to count flooded blocks. * @param y The Y coordinate (local to the tile) of the location around * which to count flooded blocks. * @param r The radius of the square. * @param lava Whether to check for lava (when <code>true</code>) or water * (when <code>false</code>). * @return The number of blocks in the specified square that are flooded. */ public synchronized int getFloodedCount(final int x, final int y, final int r, final boolean lava) { if (((x - r) < 0) || ((x + r) >= TILE_SIZE) || ((y - r) < 0) || ((y + r) >= TILE_SIZE)) { throw new IllegalArgumentException("Requested area not contained entirely on tile"); } if (tall) { ensureReadable(TALL_HEIGHTMAP); ensureReadable(TALL_WATERLEVEL); ensureReadable(BIT_LAYER_DATA); final BitSet floodWithLava = bitLayerData.get(FloodWithLava.INSTANCE); int count = 0; for (int dx = -r; dx <= r; dx++) { for (int dy = -r; dy <= r; dy++) { final int xx = x + dx, yy = y + dy; if (((tallWaterLevel[xx + yy * TILE_SIZE]) > ((int) (tallHeightMap[xx + yy * TILE_SIZE] / 256f + 0.5f))) && (lava ? ((floodWithLava != null) && getBitPerBlockLayerValue(floodWithLava, xx, yy)) : ((floodWithLava == null) || (! getBitPerBlockLayerValue(floodWithLava, xx, yy))))) { count++; } } } return count; } else { ensureReadable(HEIGHTMAP); ensureReadable(WATERLEVEL); ensureReadable(BIT_LAYER_DATA); final BitSet floodWithLava = bitLayerData.get(FloodWithLava.INSTANCE); int count = 0; for (int dx = -r; dx <= r; dx++) { for (int dy = -r; dy <= r; dy++) { final int xx = x + dx, yy = y + dy; if (((waterLevel[xx + yy * TILE_SIZE] & 0xFF) > ((int) ((heightMap[xx + yy * TILE_SIZE] & 0xFFFF) / 256f + 0.5f))) && (lava ? ((floodWithLava != null) && getBitPerBlockLayerValue(floodWithLava, xx, yy)) : ((floodWithLava == null) || (! getBitPerBlockLayerValue(floodWithLava, xx, yy))))) { count++; } } } return count; } } public synchronized float getDistanceToEdge(final Layer layer, final int x, final int y, final float maxDistance) { if ((layer.getDataSize() != Layer.DataSize.BIT) && (layer.getDataSize() != Layer.DataSize.BIT_PER_CHUNK)) { throw new IllegalArgumentException("Layer is not bit sized"); } int r = (int) Math.ceil(maxDistance); if (((x - r) < 0) || ((x + r) >= TILE_SIZE) || ((y - r) < 0) || ((y + r) >= TILE_SIZE)) { throw new IllegalArgumentException("Requested area not contained entirely on tile"); } ensureReadable(BIT_LAYER_DATA); BitSet bitSet = bitLayerData.get(layer); if (bitSet == null) { return 0; } else { float distance = maxDistance; if (layer.getDataSize() == DataSize.BIT) { if (! getBitPerBlockLayerValue(bitSet, x, y)) { return 0; } for (int i = 1; i <= r; i++) { if (((! getBitPerBlockLayerValue(bitSet, x - i, y)) || (! getBitPerBlockLayerValue(bitSet, x + i, y)) || (! getBitPerBlockLayerValue(bitSet, x, y - i)) || (! getBitPerBlockLayerValue(bitSet, x, y + i))) && (i < distance)) { // If we get here there's no possible way a shorter // distance could be found later, so return immediately return i; } for (int d = 1; d <= i; d++) { if ((! getBitPerBlockLayerValue(bitSet, x - i, y - d)) || (! getBitPerBlockLayerValue(bitSet, x + d, y - i)) || (! getBitPerBlockLayerValue(bitSet, x + i, y + d)) || (! getBitPerBlockLayerValue(bitSet, x - d, y + i)) || ((d < i) && ((! getBitPerBlockLayerValue(bitSet, x - i, y + d)) || (! getBitPerBlockLayerValue(bitSet, x - d, y - i)) || (! getBitPerBlockLayerValue(bitSet, x + i, y - d)) || (! getBitPerBlockLayerValue(bitSet, x + d, y + i))))) { float tDistance = MathUtils.getDistance(i, d); if (tDistance < distance) { distance = tDistance; } // We won't find a shorter distance this round, so // skip to the next round break; } } } } else { if (! getBitPerChunkLayerValue(bitSet, x, y)) { return 0; } for (int i = 1; i <= r; i++) { if (((! getBitPerChunkLayerValue(bitSet, x - i, y)) || (! getBitPerChunkLayerValue(bitSet, x + i, y)) || (! getBitPerChunkLayerValue(bitSet, x, y - i)) || (! getBitPerChunkLayerValue(bitSet, x, y + i))) && (i < distance)) { // If we get here there's no possible way a shorter // distance could be found later, so return immediately return i; } for (int d = 1; d <= i; d++) { if ((! getBitPerChunkLayerValue(bitSet, x - i, y - d)) || (! getBitPerChunkLayerValue(bitSet, x + d, y - i)) || (! getBitPerChunkLayerValue(bitSet, x + i, y + d)) || (! getBitPerChunkLayerValue(bitSet, x - d, y + i)) || ((d < i) && ((! getBitPerChunkLayerValue(bitSet, x - i, y + d)) || (! getBitPerChunkLayerValue(bitSet, x - d, y - i)) || (! getBitPerChunkLayerValue(bitSet, x + i, y - d)) || (! getBitPerChunkLayerValue(bitSet, x + d, y + i))))) { float tDistance = MathUtils.getDistance(i, d); if (tDistance < distance) { distance = tDistance; } // We won't find a shorter distance this round, so // skip to the next round break; } } } } return distance; } } public synchronized void setBitLayerValue(Layer layer, int x, int y, boolean value) { if ((layer.getDataSize() != Layer.DataSize.BIT) && (layer.getDataSize() != Layer.DataSize.BIT_PER_CHUNK)) { throw new IllegalArgumentException("Layer is not bit sized"); } ensureWriteable(BIT_LAYER_DATA); BitSet bitSet = bitLayerData.get(layer); if (bitSet == null) { if (value) { cachedLayers = null; if (layer.getDataSize() == Layer.DataSize.BIT) { bitSet = new BitSet(TILE_SIZE * TILE_SIZE); } else { bitSet = new BitSet(TILE_SIZE * TILE_SIZE / 256); } bitLayerData.put(layer, bitSet); } else { // If there is no bitset the default value is false, so if we're // setting to false anyway there's no point in creating the // bitset return; } } int bitOffset; if (layer.getDataSize() == Layer.DataSize.BIT) { bitOffset = x | (y << TILE_SIZE_BITS); } else { bitOffset = (x / 16) + (y / 16) * (TILE_SIZE / 16); } bitSet.set(bitOffset, value); layerDataChanged(layer); } public synchronized int getLayerValue(Layer layer, int x, int y) { ensureReadable(LAYER_DATA); byte[] layerValues = layerData.get(layer); if (layerValues == null) { return layer.getDefaultValue(); } else { switch (layer.getDataSize()) { case BIT: case BIT_PER_CHUNK: throw new IllegalArgumentException("Can't get bits using this method"); case NIBBLE: int byteOffset = x | (y << TILE_SIZE_BITS); byte _byte = layerValues[byteOffset / 2]; if (byteOffset % 2 == 0) { return _byte & 0x0F; } else { return (_byte & 0xF0) >> 4; } case BYTE: byteOffset = x | (y << TILE_SIZE_BITS); return layerValues[byteOffset] & 0xFF; default: throw new InternalError(); } } } public synchronized void setLayerValue(Layer layer, int x, int y, int value) { ensureWriteable(LAYER_DATA); byte[] layerValues = layerData.get(layer); if (layerValues == null) { if (value == layer.getDefaultValue()) { // There is no data buffer and we're setting the value to the // default, so we don't need to create it return; } cachedLayers = null; switch (layer.getDataSize()) { case BIT: case BIT_PER_CHUNK: throw new IllegalArgumentException("Can't set bits using this method"); case NIBBLE: layerValues = new byte[TILE_SIZE * TILE_SIZE / 2]; if (layer.getDefaultValue() != 0) { byte defaultValue = (byte) (layer.getDefaultValue() << 4 | layer.getDefaultValue()); Arrays.fill(layerValues, defaultValue); } break; case BYTE: layerValues = new byte[TILE_SIZE * TILE_SIZE]; if (layer.getDefaultValue() != 0) { byte defaultValue = (byte) layer.getDefaultValue(); Arrays.fill(layerValues, defaultValue); } break; default: throw new InternalError(); } layerData.put(layer, layerValues); } switch (layer.getDataSize()) { case BIT: case BIT_PER_CHUNK: throw new IllegalArgumentException("Can't set bits using this method"); case NIBBLE: if ((value < 0) || (value > 15)) { throw new IllegalArgumentException("Illegal value for nibble sized layer: " + value); } int byteOffset = x | (y << TILE_SIZE_BITS); byte _byte = layerValues[byteOffset / 2]; if (byteOffset % 2 == 0) { _byte &= 0xF0; _byte |= value; } else { _byte &= 0x0F; _byte |= (value << 4); } layerValues[byteOffset / 2] = _byte; break; case BYTE: if ((value < 0) || (value > 255)) { throw new IllegalArgumentException("Illegal value for byte sized layer: " + value); } byteOffset = x | (y << TILE_SIZE_BITS); layerValues[byteOffset] = (byte) value; break; default: throw new InternalError(); } layerDataChanged(layer); } public synchronized void clearLayerData(Layer layer) { if ((layer.getDataSize() == Layer.DataSize.BIT) || (layer.getDataSize() == Layer.DataSize.BIT_PER_CHUNK)) { ensureReadable(BIT_LAYER_DATA); if (bitLayerData.containsKey(layer)) { ensureWriteable(BIT_LAYER_DATA); bitLayerData.remove(layer); layerDataChanged(layer); cachedLayers = null; } } else { ensureReadable(LAYER_DATA); if (layerData.containsKey(layer)) { ensureWriteable(LAYER_DATA); layerData.remove(layer); layerDataChanged(layer); cachedLayers = null; } } } /** * Clear all layer data at a particular location (by resetting to the * layer's default value), possibly with the exception of certain layers. * * @param x The X coordinate of the location to clear of layer data. * @param y The Y coordinate of the location to clear of layer data. * @param excludedLayers The layers to exclude, if any. May be * <code>null</code>. */ public void clearLayerData(int x, int y, Set<Layer> excludedLayers) { ensureWriteable(BIT_LAYER_DATA); for (Map.Entry<Layer, BitSet> entry: bitLayerData.entrySet()) { Layer layer = entry.getKey(); if ((excludedLayers != null) && excludedLayers.contains(layer)) { continue; } int bitOffset; if (layer.getDataSize() == Layer.DataSize.BIT) { bitOffset = x | (y << TILE_SIZE_BITS); } else { bitOffset = (x / 16) + (y / 16) * (TILE_SIZE / 16); } entry.getValue().set(bitOffset, layer.getDefaultValue() != 0); layerDataChanged(layer); } ensureWriteable(LAYER_DATA); for (Map.Entry<Layer, byte[]> entry: layerData.entrySet()) { Layer layer = entry.getKey(); if ((excludedLayers != null) && excludedLayers.contains(layer)) { continue; } byte[] layerValues = entry.getValue(); switch (layer.getDataSize()) { case NIBBLE: int byteOffset = x | (y << TILE_SIZE_BITS); byte _byte = layerValues[byteOffset / 2]; if (byteOffset % 2 == 0) { _byte &= 0xF0; _byte |= layer.getDefaultValue(); } else { _byte &= 0x0F; _byte |= (layer.getDefaultValue() << 4); } layerValues[byteOffset / 2] = _byte; break; case BYTE: byteOffset = x | (y << TILE_SIZE_BITS); layerValues[byteOffset] = (byte) layer.getDefaultValue(); break; default: throw new InternalError(); } layerDataChanged(layer); } } public synchronized HashSet<Seed> getSeeds() { if (seeds != null) { ensureReadable(SEEDS); return seeds; } else { return null; } } public synchronized boolean plantSeed(Seed seed) { if (seeds == null) { seeds = new HashSet<>(); if (undoManager != null) { undoManager.addBuffer(SEEDS_BUFFER_KEY, seeds, this); readableBuffers.add(SEEDS); writeableBuffers.add(SEEDS); } } else { ensureWriteable(SEEDS); } seeds.add(seed); seedsChanged(); return true; } public synchronized void removeSeed(Seed seed) { if (seeds == null) { seeds = new HashSet<>(); if (undoManager != null) { undoManager.addBuffer(SEEDS_BUFFER_KEY, seeds, this); readableBuffers.add(SEEDS); writeableBuffers.add(SEEDS); } } else { ensureWriteable(SEEDS); } seeds.remove(seed); seedsChanged(); } public synchronized void addListener(Listener listener) { listeners.add(listener); } public synchronized void removeListener(Listener listener) { listeners.remove(listener); } public synchronized boolean isEventsInhibited() { return eventInhibitionCounter != 0; } /** * Stop firing events when the tile is modified, until {@link #releaseEvents()} is invoked. Make sure that * <code>releaseEvents()</code> is always invoked, even if an exception is thrown, by using a try-finally statement: * * <p><code>tile.inhibitEvents();<br> * try {<br> *     // modify the tile<br> * } finally {<br> *     tile.releaseEvents();<br> * }</code> * * <p><strong>Note</strong> that calls to these methods may be nested, and if so, events will only be released after * the final invocation of <code>releaseEvents()</code>. */ public synchronized void inhibitEvents() { eventInhibitionCounter++; } /** * Release an inhibition on firing events. Will fire all appropriate events at this time, if the tile was modified * since the first invocation of {@link #inhibitEvents()}, but only if this is the last invocation of * <code>releaseEvents()</code> in a nested set. */ public synchronized void releaseEvents() { if (eventInhibitionCounter > 0) { eventInhibitionCounter--; if (eventInhibitionCounter == 0) { if (heightMapDirty) { heightMapChanged(); heightMapDirty = false; } if (terrainDirty) { terrainChanged(); terrainDirty = false; } if (waterLevelDirty) { waterLevelChanged(); waterLevelDirty = false; } if (bitLayersDirty) { allBitLayerDataChanged(); bitLayersDirty = false; for (Iterator<Layer> i = dirtyLayers.iterator(); i.hasNext(); ) { DataSize dataSize = i.next().getDataSize(); if ((dataSize == DataSize.BIT) || (dataSize == DataSize.BIT_PER_CHUNK)) { i.remove(); } } } if (nonBitLayersDirty) { allNonBitLayerDataChanged(); nonBitLayersDirty = false; for (Iterator<Layer> i = dirtyLayers.iterator(); i.hasNext(); ) { DataSize dataSize = i.next().getDataSize(); if ((dataSize != DataSize.BIT) && (dataSize != DataSize.BIT_PER_CHUNK)) { i.remove(); } } } if (! dirtyLayers.isEmpty()) { Set<Layer> changedLayers = Collections.unmodifiableSet(dirtyLayers); for (Listener listener: listeners) { listener.layerDataChanged(this, changedLayers); } dirtyLayers.clear(); } if (seedsDirty) { seedsChanged(); seedsDirty = false; } } } else { throw new IllegalStateException("Events not inhibited"); } } public synchronized void register(UndoManager undoManager) { this.undoManager = undoManager; registerUndoBuffers(); undoManager.addListener(this); } public synchronized void unregister() { if (undoManager != null) { undoManager.removeListener(this); unregisterUndoBuffers(); undoManager = null; } } /** * Create a new tile based on this one but horizontally transformed according to some transformation. * * @param transform The transform to apply. * @return A new tile with the same contents, except transformed according to the specified transform (including the * X and Y coordinates). */ public synchronized Tile transform(CoordinateTransform transform) { Point transformedCoords = transform.transform(x << TILE_SIZE_BITS, y << TILE_SIZE_BITS); Tile transformedTile; boolean transformContents = ((transformedCoords.x & TILE_SIZE_MASK) != 0) || ((transformedCoords.y & TILE_SIZE_MASK) != 0); if (transformContents) { transformedTile = new Tile(transformedCoords.x >> TILE_SIZE_BITS, transformedCoords.y >> TILE_SIZE_BITS, maxHeight); for (int x = 0; x < TILE_SIZE; x++) { for (int y = 0; y < TILE_SIZE; y++) { transformedCoords.x = x; transformedCoords.y = y; transform.transformInPlace(transformedCoords); transformedCoords.x &= TILE_SIZE_MASK; transformedCoords.y &= TILE_SIZE_MASK; transformedTile.setTerrain(transformedCoords.x, transformedCoords.y, getTerrain(x, y)); transformedTile.setRawHeight(transformedCoords.x, transformedCoords.y, getRawHeight(x, y)); transformedTile.setWaterLevel(transformedCoords.x, transformedCoords.y, getWaterLevel(x, y)); } } for (Layer layer: getLayers()) { if ((layer.getDataSize() == Layer.DataSize.BIT) || (layer.getDataSize() == Layer.DataSize.BIT_PER_CHUNK)) { for (int x = 0; x < TILE_SIZE; x++) { for (int y = 0; y < TILE_SIZE; y++) { if (getBitLayerValue(layer, x, y)) { transformedCoords.x = x; transformedCoords.y = y; transform.transformInPlace(transformedCoords); transformedCoords.x &= TILE_SIZE_MASK; transformedCoords.y &= TILE_SIZE_MASK; transformedTile.setBitLayerValue(layer, transformedCoords.x, transformedCoords.y, true); } } } } else if (layer.getDataSize() != Layer.DataSize.NONE) { for (int x = 0; x < TILE_SIZE; x++) { for (int y = 0; y < TILE_SIZE; y++) { int value = getLayerValue(layer, x, y); if (value > 0) { transformedCoords.x = x; transformedCoords.y = y; transform.transformInPlace(transformedCoords); transformedCoords.x &= TILE_SIZE_MASK; transformedCoords.y &= TILE_SIZE_MASK; transformedTile.setLayerValue(layer, transformedCoords.x, transformedCoords.y, value); } } } } } } else { // The transformation does not affect intra-tile coordinates, so just copy the buffers without transforming them transformedTile = new Tile(transformedCoords.x >> TILE_SIZE_BITS, transformedCoords.y >> TILE_SIZE_BITS, maxHeight, false); transformedTile.heightMap = copyObject(heightMap); transformedTile.tallHeightMap = copyObject(tallHeightMap); transformedTile.terrain = terrain.clone(); transformedTile.waterLevel = copyObject(waterLevel); transformedTile.tallWaterLevel = copyObject(tallWaterLevel); transformedTile.layerData = copyObject(layerData); transformedTile.bitLayerData = copyObject(bitLayerData); transformedTile.init(); } if (seeds != null) { transformedTile.seeds = new HashSet<>(); transformedTile.seeds.addAll(seeds); for (Seed seed: transformedTile.seeds) { seed.transform(transform); } } return transformedTile; } public boolean repair(int maxHeight, PrintStream out) { // Repair as much as possible if the tile was not read in completely this.maxHeight = maxHeight; maxY = maxHeight - 1; if (maxHeight > 256) { tall = true; if (tallHeightMap == null) { out.println("Height map for tile " + x + "," + y + " lost"); tallHeightMap = new int[TILE_SIZE * TILE_SIZE]; } if (tallWaterLevel == null) { out.println("Water level map for tile " + x + "," + y + " lost"); tallWaterLevel = new short[TILE_SIZE * TILE_SIZE]; } heightMap = null; waterLevel = null; } else { tall = false; if (heightMap == null) { out.println("Height map for tile " + x + "," + y + " lost"); heightMap = new short[TILE_SIZE * TILE_SIZE]; } if (waterLevel == null) { out.println("Water level map for tile " + x + "," + y + " lost"); waterLevel = new byte[TILE_SIZE * TILE_SIZE]; } tallHeightMap = null; tallWaterLevel = null; } if (terrain == null) { out.println("Terrain type map for tile " + x + "," + y + " lost"); terrain = new byte[TILE_SIZE * TILE_SIZE]; } if (layerData == null) { out.println("Non-bit valued layer data for tile " + x + "," + y + " lost"); layerData = new HashMap<>(); } if (bitLayerData == null) { out.println("Bit valued layer data for tile " + x + "," + y + " lost"); bitLayerData = new HashMap<>(); } init(); return true; } public boolean containsOneOf(Layer... layers) { boolean bitLayersAvailable = false, nonBitLayersAvailable = false; for (Layer layer: layers) { switch (layer.getDataSize()) { case BIT: case BIT_PER_CHUNK: if (! bitLayersAvailable) { ensureReadable(BIT_LAYER_DATA); bitLayersAvailable = true; } if (bitLayerData.containsKey(layer)) { return true; } break; case BYTE: case NIBBLE: if (! nonBitLayersAvailable) { ensureReadable(LAYER_DATA); nonBitLayersAvailable = true; } if (layerData.containsKey(layer)) { return true; } break; default: throw new IllegalArgumentException("Data size " + layer.getDataSize() + " not supported"); } } return false; } // UndoListener @Override public synchronized void savePointArmed() { if (logger.isTraceEnabled()) { logger.trace("Save point armed; clearing writable buffers"); } writeableBuffers.clear(); } @Override public synchronized void savePointCreated() { if (logger.isTraceEnabled()) { logger.trace("Save point created; clearing writable buffers"); } writeableBuffers.clear(); } @Override public void undoPerformed() { // Do nothing } @Override public void redoPerformed() { // Do nothing } @Override public synchronized void bufferChanged(BufferKey<?> key) { TileUndoBufferKey<?> tileKey = (TileUndoBufferKey<?>) key; if (logger.isTraceEnabled()) { logger.trace("Buffer " + key + " changed; clearing buffer cache for type " + tileKey.buffer + " and notifying listeners"); } switch (tileKey.buffer) { case BIT_LAYER_DATA: readableBuffers.remove(BIT_LAYER_DATA); writeableBuffers.remove(BIT_LAYER_DATA); allBitLayerDataChanged(); cachedLayers = null; break; case HEIGHTMAP: readableBuffers.remove(HEIGHTMAP); writeableBuffers.remove(HEIGHTMAP); heightMapChanged(); break; case TALL_HEIGHTMAP: readableBuffers.remove(TALL_HEIGHTMAP); writeableBuffers.remove(TALL_HEIGHTMAP); heightMapChanged(); break; case LAYER_DATA: readableBuffers.remove(LAYER_DATA); writeableBuffers.remove(LAYER_DATA); allNonBitLayerDataChanged(); cachedLayers = null; break; case TERRAIN: readableBuffers.remove(TERRAIN); writeableBuffers.remove(TERRAIN); terrainChanged(); break; case WATERLEVEL: readableBuffers.remove(WATERLEVEL); writeableBuffers.remove(WATERLEVEL); waterLevelChanged(); break; case TALL_WATERLEVEL: readableBuffers.remove(TALL_WATERLEVEL); writeableBuffers.remove(TALL_WATERLEVEL); waterLevelChanged(); break; case SEEDS: readableBuffers.remove(SEEDS); writeableBuffers.remove(SEEDS); seedsChanged(); break; } } // Object @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Tile other = (Tile) obj; if (this.x != other.x) { return false; } if (this.y != other.y) { return false; } return true; } @Override public int hashCode() { int hash = 7; hash = 17 * hash + this.x; hash = 17 * hash + this.y; return hash; } @Override public String toString() { return "Tile[x=" + x + ",y=" + y + "]"; } synchronized void ensureAllReadable() { if (tall) { ensureReadable(TALL_HEIGHTMAP); ensureReadable(TALL_WATERLEVEL); } else { ensureReadable(HEIGHTMAP); ensureReadable(WATERLEVEL); } ensureReadable(TERRAIN); ensureReadable(LAYER_DATA); ensureReadable(BIT_LAYER_DATA); ensureReadable(SEEDS); } void convertBiomeData() { byte[] biomeData = layerData.remove(Biome.INSTANCE); byte[] newBiomeData = new byte[biomeData.length * 2]; for (int i = 0; i < biomeData.length; i++) { newBiomeData[i * 2] = (byte) (biomeData[i] & 0x0f); newBiomeData[i * 2 + 1] = (byte) ((biomeData[i] & 0xf0) >> 4); } layerData.put(Biome.INSTANCE, newBiomeData); } private boolean getBitPerBlockLayerValue(BitSet bitSet, int x, int y) { return bitSet.get(x | (y << TILE_SIZE_BITS)); } private boolean getBitPerChunkLayerValue(BitSet bitSet, int x, int y) { return bitSet.get((x >> 4) + (y >> 4) * (TILE_SIZE >> 4)); } private void registerUndoBuffers() { if (tall) { undoManager.addBuffer(TALL_HEIGHTMAP_BUFFER_KEY, tallHeightMap, this); undoManager.addBuffer(TALL_WATERLEVEL_BUFFER_KEY, tallWaterLevel, this); readableBuffers = EnumSet.of(TALL_HEIGHTMAP, TALL_WATERLEVEL, TERRAIN, LAYER_DATA, BIT_LAYER_DATA); writeableBuffers = EnumSet.of(TALL_HEIGHTMAP, TALL_WATERLEVEL, TERRAIN, LAYER_DATA, BIT_LAYER_DATA); } else { undoManager.addBuffer(HEIGHTMAP_BUFFER_KEY, heightMap, this); undoManager.addBuffer(WATERLEVEL_BUFFER_KEY, waterLevel, this); readableBuffers = EnumSet.of(HEIGHTMAP, WATERLEVEL, TERRAIN, LAYER_DATA, BIT_LAYER_DATA); writeableBuffers = EnumSet.of(HEIGHTMAP, WATERLEVEL, TERRAIN, LAYER_DATA, BIT_LAYER_DATA); } undoManager.addBuffer(TERRAIN_BUFFER_KEY, terrain, this); undoManager.addBuffer(LAYER_DATA_BUFFER_KEY, layerData, this); undoManager.addBuffer(BIT_LAYER_DATA_BUFFER_KEY, bitLayerData, this); if (seeds != null) { undoManager.addBuffer(SEEDS_BUFFER_KEY, seeds, this); readableBuffers.add(SEEDS); writeableBuffers.add(SEEDS); } } private void unregisterUndoBuffers() { // Also make sure that we have all the data from the current undo level // references, because after this we can't get at it any more if (tall) { ensureReadable(TALL_HEIGHTMAP); undoManager.removeBuffer(TALL_HEIGHTMAP_BUFFER_KEY); ensureReadable(TALL_WATERLEVEL); undoManager.removeBuffer(TALL_WATERLEVEL_BUFFER_KEY); } else { ensureReadable(HEIGHTMAP); undoManager.removeBuffer(HEIGHTMAP_BUFFER_KEY); ensureReadable(WATERLEVEL); undoManager.removeBuffer(WATERLEVEL_BUFFER_KEY); } ensureReadable(TERRAIN); undoManager.removeBuffer(TERRAIN_BUFFER_KEY); ensureReadable(LAYER_DATA); undoManager.removeBuffer(LAYER_DATA_BUFFER_KEY); ensureReadable(BIT_LAYER_DATA); undoManager.removeBuffer(BIT_LAYER_DATA_BUFFER_KEY); if (seeds != null) { ensureReadable(SEEDS); undoManager.removeBuffer(SEEDS_BUFFER_KEY); } readableBuffers = writeableBuffers = null; } protected synchronized void ensureReadable(TileBuffer buffer) { if ((undoManager != null) && (! readableBuffers.contains(buffer))) { switch (buffer) { case HEIGHTMAP: heightMap = undoManager.getBuffer(HEIGHTMAP_BUFFER_KEY); break; case TALL_HEIGHTMAP: tallHeightMap = undoManager.getBuffer(TALL_HEIGHTMAP_BUFFER_KEY); break; case TERRAIN: terrain = undoManager.getBuffer(TERRAIN_BUFFER_KEY); break; case WATERLEVEL: waterLevel = undoManager.getBuffer(WATERLEVEL_BUFFER_KEY); break; case TALL_WATERLEVEL: tallWaterLevel = undoManager.getBuffer(TALL_WATERLEVEL_BUFFER_KEY); break; case LAYER_DATA: layerData = undoManager.getBuffer(LAYER_DATA_BUFFER_KEY); break; case BIT_LAYER_DATA: bitLayerData = undoManager.getBuffer(BIT_LAYER_DATA_BUFFER_KEY); break; case SEEDS: seeds = undoManager.getBuffer(SEEDS_BUFFER_KEY); break; } readableBuffers.add(buffer); } } private void ensureWriteable(TileBuffer buffer) { if ((undoManager != null) && (! writeableBuffers.contains(buffer))) { switch (buffer) { case HEIGHTMAP: heightMap = undoManager.getBufferForEditing(HEIGHTMAP_BUFFER_KEY); break; case TALL_HEIGHTMAP: tallHeightMap = undoManager.getBufferForEditing(TALL_HEIGHTMAP_BUFFER_KEY); break; case TERRAIN: terrain = undoManager.getBufferForEditing(TERRAIN_BUFFER_KEY); break; case WATERLEVEL: waterLevel = undoManager.getBufferForEditing(WATERLEVEL_BUFFER_KEY); break; case TALL_WATERLEVEL: tallWaterLevel = undoManager.getBufferForEditing(TALL_WATERLEVEL_BUFFER_KEY); break; case LAYER_DATA: layerData = undoManager.getBufferForEditing(LAYER_DATA_BUFFER_KEY); break; case BIT_LAYER_DATA: bitLayerData = undoManager.getBufferForEditing(BIT_LAYER_DATA_BUFFER_KEY); break; case SEEDS: seeds = undoManager.getBufferForEditing(SEEDS_BUFFER_KEY); break; } readableBuffers.add(buffer); writeableBuffers.add(buffer); } } private void heightMapChanged() { if (eventInhibitionCounter != 0) { heightMapDirty = true; } else { for (Listener listener: listeners) { listener.heightMapChanged(this); } } } private void terrainChanged() { if (eventInhibitionCounter != 0) { terrainDirty = true; } else { for (Listener listener: listeners) { listener.terrainChanged(this); } } } private void waterLevelChanged() { if (eventInhibitionCounter != 0) { waterLevelDirty = true; } else { for (Listener listener: listeners) { listener.waterLevelChanged(this); } } } private void layerDataChanged(Layer layer) { if (eventInhibitionCounter != 0) { dirtyLayers.add(layer); } else { Set<Layer> changedLayers = Collections.singleton(layer); for (Listener listener: listeners) { listener.layerDataChanged(this, changedLayers); } } } private void allBitLayerDataChanged() { if (eventInhibitionCounter != 0) { bitLayersDirty = true; } else { for (Listener listener: listeners) { listener.allBitLayerDataChanged(this); } } } private void allNonBitLayerDataChanged() { if (eventInhibitionCounter != 0) { nonBitLayersDirty = true; } else { for (Listener listener: listeners) { listener.allNonBitlayerDataChanged(this); } } } private void seedsChanged() { if (eventInhibitionCounter != 0) { seedsDirty = true; } else { for (Listener listener: listeners) { listener.seedsChanged(this); } } } private float clamp(float level) { if (level < 0.0f) { return 0.0f; } else if (level > maxY) { return maxY; } else { return level; } } private int clamp(int level) { if (level < 0) { return 0; } else if (level > maxY) { return maxY; } else { return level; } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); init(); } private synchronized void writeObject(ObjectOutputStream out) throws IOException { // Make sure all buffers are current, otherwise we may save out of date // data to disk ensureAllReadable(); // Take the opportunity to save memory and disk space by throwing away "empty" layer buffers. Since this is // functionally a null operation there is no need to notify listeners, make the buffer writable or otherwise // notify the undo manager for (Iterator<Map.Entry<Layer, BitSet>> i = bitLayerData.entrySet().iterator(); i.hasNext(); ) { final Map.Entry<Layer, BitSet> entry = i.next(); if (entry.getValue().isEmpty()) { i.remove(); cachedLayers = null; } } layerLoop: for (Iterator<Map.Entry<Layer, byte[]>> i = layerData.entrySet().iterator(); i.hasNext(); ) { Map.Entry<Layer, byte[]> entry = i.next(); final Layer layer = entry.getKey(); final byte[] buffer = entry.getValue(); if (layer.getDataSize() == NIBBLE) { final byte defaultByte = (byte) (layer.getDefaultValue() << 4 | layer.getDefaultValue()); for (byte bufferByte: buffer) { if (bufferByte != defaultByte) { continue layerLoop; } } // If we reach here all bytes were default bytes i.remove(); cachedLayers = null; } else if (layer.getDataSize() == BYTE) { final byte defaultByte = (byte) layer.getDefaultValue(); for (byte bufferByte: buffer) { if (bufferByte != defaultByte) { continue layerLoop; } } // If we reach here all bytes were default bytes i.remove(); cachedLayers = null; } } out.defaultWriteObject(); } private void init() { listeners = new ArrayList<>(); HEIGHTMAP_BUFFER_KEY = new TileUndoBufferKey<>(this, HEIGHTMAP); TALL_HEIGHTMAP_BUFFER_KEY = new TileUndoBufferKey<>(this, TALL_HEIGHTMAP); TERRAIN_BUFFER_KEY = new TileUndoBufferKey<>(this, TERRAIN); WATERLEVEL_BUFFER_KEY = new TileUndoBufferKey<>(this, WATERLEVEL); TALL_WATERLEVEL_BUFFER_KEY = new TileUndoBufferKey<>(this, TALL_WATERLEVEL); LAYER_DATA_BUFFER_KEY = new TileUndoBufferKey<>(this, LAYER_DATA); BIT_LAYER_DATA_BUFFER_KEY = new TileUndoBufferKey<>(this, BIT_LAYER_DATA); SEEDS_BUFFER_KEY = new TileUndoBufferKey<>(this, SEEDS); dirtyLayers = new HashSet<>(); maxY = maxHeight - 1; // Legacy map support if (maxHeight == 0) { maxHeight = 128; tall = false; } if ((seeds != null) && seeds.isEmpty()) { seeds = null; } } private final int x, y; private int maxHeight; private boolean tall; protected short[] heightMap; protected int[] tallHeightMap; protected byte[] terrain; protected byte[] waterLevel; protected short[] tallWaterLevel; protected Map<Layer, byte[]> layerData; protected Map<Layer, BitSet> bitLayerData; private HashSet<Seed> seeds; private transient List<Listener> listeners; private transient boolean heightMapDirty, terrainDirty, waterLevelDirty, seedsDirty, bitLayersDirty, nonBitLayersDirty; private transient Set<TileBuffer> readableBuffers; private transient Set<TileBuffer> writeableBuffers; private transient UndoManager undoManager; private transient List<Layer> cachedLayers; private transient Set<Layer> dirtyLayers; private transient int maxY, eventInhibitionCounter; private transient BufferKey<short[]> HEIGHTMAP_BUFFER_KEY; private transient BufferKey<int[]> TALL_HEIGHTMAP_BUFFER_KEY; private transient BufferKey<byte[]> TERRAIN_BUFFER_KEY; private transient BufferKey<byte[]> WATERLEVEL_BUFFER_KEY; private transient BufferKey<short[]> TALL_WATERLEVEL_BUFFER_KEY; private transient BufferKey<Map<Layer, byte[]>> LAYER_DATA_BUFFER_KEY; private transient BufferKey<Map<Layer, BitSet>> BIT_LAYER_DATA_BUFFER_KEY; private transient BufferKey<HashSet<Seed>> SEEDS_BUFFER_KEY; private static final Terrain[] TERRAIN_VALUES = Terrain.values(); private static final float SQRT_OF_EIGHT = (float) Math.sqrt(8.0); private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Tile.class); private static final long serialVersionUID = 2011040101L; public interface Listener { void heightMapChanged(Tile tile); void terrainChanged(Tile tile); void waterLevelChanged(Tile tile); void layerDataChanged(Tile tile, Set<Layer> changedLayers); void allBitLayerDataChanged(Tile tile); void allNonBitlayerDataChanged(Tile tile); void seedsChanged(Tile tile); } static class TileUndoBufferKey<T> implements BufferKey<T> { public TileUndoBufferKey(Tile tile, TileBuffer buffer) { this.tile = tile; this.buffer = buffer; } @Override public boolean equals(Object obj) { return (obj instanceof TileUndoBufferKey) && (tile == ((TileUndoBufferKey) obj).tile) && (buffer == ((TileUndoBufferKey) obj).buffer); } @Override public int hashCode() { return (31 + System.identityHashCode(tile)) * 31 + buffer.hashCode(); } @Override public String toString() { return "[" + tile.x + ", " + tile.y + ", " + buffer + "]"; } final Tile tile; final TileBuffer buffer; } public enum TileBuffer { HEIGHTMAP, TERRAIN, WATERLEVEL, LAYER_DATA, BIT_LAYER_DATA, TALL_HEIGHTMAP, TALL_WATERLEVEL, SEEDS } }