/*******************************************************************************
* Copyright (c) 2015 - 2017
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*******************************************************************************/
package jsettlers.logic.map.grid.landscape;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import jsettlers.algorithms.partitions.IBlockingProvider;
import jsettlers.algorithms.previewimage.IPreviewImageDataSupplier;
import jsettlers.common.landscape.ELandscapeType;
import jsettlers.common.landscape.EResourceType;
import jsettlers.common.map.IGraphicsBackgroundListener;
import jsettlers.common.map.shapes.HexGridArea;
import jsettlers.common.position.RelativePoint;
import jsettlers.common.position.ShortPoint2D;
import jsettlers.logic.constants.Constants;
import jsettlers.logic.constants.MatchConstants;
import jsettlers.logic.map.grid.flags.IProtectedProvider;
import jsettlers.logic.map.grid.flags.IProtectedProvider.IProtectedChangedListener;
/**
* This grid stores the height and the {@link ELandscapeType} of every position.
*
* @author Andreas Eberle
*/
public final class LandscapeGrid implements Serializable, IWalkableGround, IFlattenedResettable, IDebugColorSetable, IProtectedChangedListener, IBlockingProvider {
private static final long serialVersionUID = -751261669662036483L;
/**
* This class is used as null object to get rid of a lot of null checks
*
* @author Andreas Eberle
*/
private static final class NullBackgroundListener implements IGraphicsBackgroundListener, Serializable {
private static final long serialVersionUID = -332117701485179252L;
@Override
public final void backgroundChangedAt(int x, int y) {
}
}
private final byte[] heightGrid;
private final byte[] landscapeGrid;
private final byte[] resourceAmount;
private final byte[] temporaryFlatened;
private final byte[] resourceType;
private final short[] blockedPartitions;
private final short width;
private final short height;
private final IProtectedProvider protectedProvider;
private final FlattenedResetter flattenedResetter;
public transient int[] debugColors;
private transient IGraphicsBackgroundListener backgroundListener;
public LandscapeGrid(short width, short height, IProtectedProvider protectedProvider) {
this.width = width;
this.height = height;
this.protectedProvider = protectedProvider;
final int tiles = width * height;
this.heightGrid = new byte[tiles];
this.landscapeGrid = new byte[tiles];
this.resourceAmount = new byte[tiles];
this.resourceType = new byte[tiles];
this.temporaryFlatened = new byte[tiles];
this.blockedPartitions = new short[tiles];
initDebugColors();
this.flattenedResetter = new FlattenedResetter(this);
setBackgroundListener(null);
protectedProvider.setProtectedChangedListener(this);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
setBackgroundListener(null);
initDebugColors();
}
private final void initDebugColors() {
if (MatchConstants.ENABLE_DEBUG_COLORS) {
this.debugColors = new int[width * height];
} else {
this.debugColors = null;
}
}
public final byte getHeightAt(int x, int y) {
return heightGrid[x + y * width];
}
public final ELandscapeType getLandscapeTypeAt(int x, int y) {
return ELandscapeType.VALUES[landscapeGrid[x + y * width]];
}
public boolean isLandscapeOf(int x, int y, ELandscapeType... landscapeTypes) {
ELandscapeType landscapeType = getLandscapeTypeAt(x, y);
for (ELandscapeType curr : landscapeTypes) {
if (landscapeType == curr) {
return true;
}
}
return false;
}
public boolean isHexAreaOfType(int x, int y, int minRadius, int maxRadius, ELandscapeType... landscapeTypes) {
boolean isOfType = HexGridArea
.stream(x, y, minRadius, maxRadius)
.filter((currX, currY) -> !isLandscapeOf(currX, currY, landscapeTypes))
.isEmpty();
return isOfType;
}
@Override
public final void setDebugColor(int x, int y, int argb) {
if (MatchConstants.ENABLE_DEBUG_COLORS) {
debugColors[x + y * width] = argb;
}
}
public final int getDebugColor(int x, int y) {
if (MatchConstants.ENABLE_DEBUG_COLORS) {
return debugColors[x + y * width];
} else {
return 0;
}
}
public final void resetDebugColors() {
if (MatchConstants.ENABLE_DEBUG_COLORS) {
for (int i = 0; i < debugColors.length; i++) {
debugColors[i] = 0;
}
}
}
public final void setLandscapeTypeAt(int x, int y, ELandscapeType landscapeType) {
if (landscapeType == ELandscapeType.FLATTENED && this.landscapeGrid[x + y * width] != ELandscapeType.FLATTENED.ordinal) {
flattenedResetter.addPosition(x, y);
}
this.landscapeGrid[x + y * width] = landscapeType.ordinal;
backgroundListener.backgroundChangedAt(x, y);
}
public final void setHeightAt(short x, short y, byte height) {
this.heightGrid[x + y * width] = height;
backgroundListener.backgroundChangedAt(x, y);
}
public void flattenAndChangeHeightTowards(int x, int y, byte targetHeight) {
final int index = x + y * width;
this.heightGrid[index] += Math.signum(targetHeight - this.heightGrid[index]);
this.landscapeGrid[index] = ELandscapeType.FLATTENED.ordinal;
this.temporaryFlatened[index] = Byte.MAX_VALUE; // cancel the flattening
backgroundListener.backgroundChangedAt(x, y);
}
public final void setBackgroundListener(IGraphicsBackgroundListener backgroundListener) {
if (backgroundListener != null) {
this.backgroundListener = backgroundListener;
} else {
this.backgroundListener = new NullBackgroundListener();
}
}
public final void setResourceAt(short x, short y, EResourceType resourceType, byte amount) {
this.resourceType[x + y * width] = resourceType.ordinal;
this.resourceAmount[x + y * width] = (byte) Math.min(amount, Constants.MAX_RESOURCE_AMOUNT_PER_POSITION);
}
/**
* gets the resource amount at the given position
*
* @param x
* @param y
* @return The amount of resources, where 0 is no resources and @link Byte.MAX_VALUE means full resources.
*/
public final byte getResourceAmountAt(int x, int y) {
return resourceAmount[x + y * width];
}
public final EResourceType getResourceTypeAt(int x, int y) {
return EResourceType.VALUES[resourceType[x + y * width]];
}
public int getAmountOfResource(EResourceType resource, Iterable<ShortPoint2D> positions) {
int amount = 0;
for (ShortPoint2D position : positions) {
int index = position.x + position.y * width;
if (resourceType[index] == resource.ordinal) {
amount += resourceAmount[index];
}
}
return amount;
}
public boolean tryTakingResource(ShortPoint2D position, EResourceType resource) {
int idx = position.x + position.y * width;
if (resourceType[idx] == resource.ordinal && resourceAmount[idx] > 0) {
resourceAmount[idx]--;
return true;
} else {
return false;
}
}
@Override
public final void walkOn(int x, int y) {
int i = x + y * width;
if (temporaryFlatened[i] < 100) {
temporaryFlatened[i] += 3;
if (temporaryFlatened[i] > 20) {
flatten(x, y);
}
}
}
/**
* Sets the landscape to flattened after a settler walked on it.
*
* @param x
* @param y
*/
private void flatten(int x, int y) {
if (isHexAreaOfType(x, y, 0, 1, ELandscapeType.GRASS, ELandscapeType.FLATTENED)) {
setLandscapeTypeAt((short) x, (short) y, ELandscapeType.FLATTENED);
}
}
@Override
public boolean countFlattenedDown(short x, short y) {
if (protectedProvider.isProtected(x, y)) {
return true; // remove the position from the unflattener
}
final int index = x + y * width;
byte flattenedValue = temporaryFlatened[index];
if (flattenedValue == Byte.MAX_VALUE) { // the unflattening has been canceled.
return true; // tell the flattened resetter that it does not need to work on this pos again.
}
// count down the value
flattenedValue--;
temporaryFlatened[index] = flattenedValue;
if (flattenedValue <= -30) { // if the value is smaller than the hysteresis, set it to zero
temporaryFlatened[index] = 0;
setLandscapeTypeAt(x, y, ELandscapeType.GRASS);
return true; // tell the flattened resetter that it does not need to work on this pos again.
} else {
return false;
}
}
public void setBlockedPartition(short x, short y, short blockedPartition) {
this.blockedPartitions[x + y * width] = blockedPartition;
}
public short getBlockedPartitionAt(int x, int y) {
return this.blockedPartitions[x + y * width];
}
@Override
public boolean isBlocked(int x, int y) {
return getBlockedPartitionAt(x, y) == 0;
}
public IPreviewImageDataSupplier getPreviewImageDataSupplier() {
return new IPreviewImageDataSupplier() {
@Override
public byte getLandscapeHeight(short x, short y) {
return getHeightAt(x, y);
}
@Override
public ELandscapeType getLandscape(short x, short y) {
return getLandscapeTypeAt(x, y);
}
};
}
/**
* This method activates the unflattening process. This causes a flattened position to be turned into grass after a while.
*
* @param x
* X coordinate of the position.
* @param y
* Y coordinate of the position.
*/
private void activateUnflattening(int x, int y) {
ELandscapeType landscapeType = getLandscapeTypeAt(x, y);
if (landscapeType != ELandscapeType.FLATTENED && landscapeType != ELandscapeType.FLATTENED_DESERT) {
return; // do not unflatten mountain or desert.
}
this.temporaryFlatened[x + y * width] = (byte) (40 + MatchConstants.random().nextFloat() * 80);
this.flattenedResetter.addPosition(x, y);
}
public boolean isAreaFlattenedAtHeight(ShortPoint2D position, RelativePoint[] positions, byte expectedHeight) {
for (RelativePoint currPos : positions) {
int index = currPos.calculateX(position.x) + currPos.calculateY(position.y) * width;
if (heightGrid[index] != expectedHeight || landscapeGrid[index] != ELandscapeType.FLATTENED.ordinal) {
return false;
}
}
return true;
}
@Override
public void protectedChanged(int x, int y, boolean newProtectedState) {
if (!newProtectedState) {
activateUnflattening(x, y);
}
}
}