/******************************************************************************* * Copyright (c) 2015 - 2017 * * 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: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * 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; import java8.util.Optional; import jsettlers.algorithms.borders.BordersThread; import jsettlers.algorithms.borders.IBordersThreadGrid; import jsettlers.algorithms.construction.AbstractConstructionMarkableMap; import jsettlers.algorithms.fogofwar.FogOfWar; import jsettlers.algorithms.fogofwar.IFogOfWarGrid; import jsettlers.algorithms.fogofwar.IViewDistancable; import jsettlers.algorithms.landmarks.EnclosedBlockedAreaFinderAlgorithm; import jsettlers.algorithms.landmarks.IEnclosedBlockedAreaFinderGrid; import jsettlers.algorithms.path.IPathCalculatable; import jsettlers.algorithms.path.Path; import jsettlers.algorithms.path.area.IInAreaFinderMap; import jsettlers.algorithms.path.area.InAreaFinder; import jsettlers.algorithms.path.astar.AbstractAStar; import jsettlers.algorithms.path.astar.BucketQueueAStar; import jsettlers.algorithms.path.astar.IAStarPathMap; import jsettlers.algorithms.path.dijkstra.DijkstraAlgorithm; import jsettlers.algorithms.path.dijkstra.IDijkstraPathMap; import jsettlers.algorithms.previewimage.PreviewImageCreator; import jsettlers.common.Color; import jsettlers.common.buildings.BuildingAreaBitSet; import jsettlers.common.buildings.EBuildingType; import jsettlers.common.buildings.IBuilding; import jsettlers.common.landscape.ELandscapeType; import jsettlers.common.landscape.EResourceType; import jsettlers.common.map.EDebugColorModes; import jsettlers.common.map.IGraphicsBackgroundListener; import jsettlers.common.map.IGraphicsGrid; import jsettlers.common.map.IMapData; import jsettlers.common.map.object.BuildingObject; import jsettlers.common.map.object.MapObject; import jsettlers.common.map.object.MapStoneObject; import jsettlers.common.map.object.MapTreeObject; import jsettlers.common.map.object.MovableObject; import jsettlers.common.map.object.StackObject; import jsettlers.common.map.partition.IPartitionData; import jsettlers.common.map.shapes.FreeMapArea; import jsettlers.common.map.shapes.HexGridArea; import jsettlers.common.map.shapes.MapCircle; import jsettlers.common.map.shapes.MapLine; import jsettlers.common.map.shapes.MapNeighboursArea; import jsettlers.common.mapobject.EMapObjectType; import jsettlers.common.mapobject.IMapObject; import jsettlers.common.material.EMaterialType; import jsettlers.common.material.ESearchType; import jsettlers.common.movable.EDirection; import jsettlers.common.movable.EMovableType; import jsettlers.common.movable.IMovable; import jsettlers.common.player.IPlayerable; import jsettlers.common.position.MutablePoint2D; import jsettlers.common.position.RelativePoint; import jsettlers.common.position.ShortPoint2D; import jsettlers.common.utils.collections.IPredicate; import jsettlers.common.utils.coordinates.CoordinateStream; import jsettlers.input.IGuiInputGrid; import jsettlers.input.PlayerState; import jsettlers.logic.buildings.Building; import jsettlers.logic.buildings.IBuildingsGrid; import jsettlers.logic.buildings.MaterialProductionSettings; import jsettlers.logic.buildings.military.IOccupyableBuilding; import jsettlers.logic.buildings.stack.IRequestsStackGrid; import jsettlers.logic.buildings.stack.multi.StockSettings; import jsettlers.logic.buildings.workers.WorkerBuilding; import jsettlers.logic.constants.Constants; import jsettlers.logic.constants.MatchConstants; import jsettlers.logic.map.grid.flags.FlagsGrid; import jsettlers.logic.map.grid.landscape.LandscapeGrid; import jsettlers.logic.map.grid.movable.MovableGrid; import jsettlers.logic.map.grid.objects.AbstractHexMapObject; import jsettlers.logic.map.grid.objects.IMapObjectsManagerGrid; import jsettlers.logic.map.grid.objects.MapObjectsManager; import jsettlers.logic.map.grid.objects.ObjectsGrid; import jsettlers.logic.map.grid.partition.IPlayerChangedListener; import jsettlers.logic.map.grid.partition.PartitionsGrid; import jsettlers.logic.map.grid.partition.manager.PartitionManager; import jsettlers.logic.map.grid.partition.manager.manageables.IManageableBearer; import jsettlers.logic.map.grid.partition.manager.manageables.IManageableBricklayer; import jsettlers.logic.map.grid.partition.manager.manageables.IManageableDigger; import jsettlers.logic.map.grid.partition.manager.manageables.IManageableWorker; import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IBarrack; import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IDiggerRequester; import jsettlers.logic.map.grid.partition.manager.materials.interfaces.IOfferEmptiedListener; import jsettlers.logic.map.grid.partition.manager.materials.offers.EOfferPriority; import jsettlers.logic.map.grid.partition.manager.materials.requests.MaterialRequestObject; import jsettlers.logic.map.loading.list.MapList; import jsettlers.logic.map.loading.newmap.MapFileHeader; import jsettlers.logic.map.loading.newmap.MapFileHeader.MapType; import jsettlers.logic.movable.Movable; import jsettlers.logic.movable.interfaces.ILogicMovable; import jsettlers.logic.movable.interfaces.AbstractMovableGrid; import jsettlers.logic.movable.interfaces.IAttackable; import jsettlers.logic.objects.arrow.ArrowObject; import jsettlers.logic.objects.stack.StackMapObject; import jsettlers.logic.player.Player; import jsettlers.logic.player.PlayerSetting; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.BitSet; import java.util.Date; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; /** * This is the main grid offering an interface for interacting with the grid. * * @author Andreas Eberle */ public final class MainGrid implements Serializable { private static final long serialVersionUID = 3824511313693431423L; final String mapId; final String mapName; final short width; final short height; final LandscapeGrid landscapeGrid; final ObjectsGrid objectsGrid; final PartitionsGrid partitionsGrid; final MovableGrid movableGrid; final FlagsGrid flagsGrid; final MovablePathfinderGrid movablePathfinderGrid; final MapObjectsManager mapObjectsManager; final BuildingsGrid buildingsGrid; transient FogOfWar fogOfWar; transient GraphicsGrid graphicsGrid; transient ConstructionMarksGrid constructionMarksGrid; transient BordersThread bordersThread; transient IGuiInputGrid guiInputGrid; private transient IEnclosedBlockedAreaFinderGrid enclosedBlockedAreaFinderGrid; public MainGrid(String mapId, String mapName, short width, short height, PlayerSetting[] playerSettings) { this.mapId = mapId; this.mapName = mapName; this.width = width; this.height = height; this.flagsGrid = new FlagsGrid(width, height); this.movablePathfinderGrid = new MovablePathfinderGrid(); this.mapObjectsManager = new MapObjectsManager(new MapObjectsManagerGrid()); this.objectsGrid = new ObjectsGrid(width, height); this.landscapeGrid = new LandscapeGrid(width, height, flagsGrid); this.movableGrid = new MovableGrid(width, height, landscapeGrid); this.partitionsGrid = new PartitionsGrid(width, height, playerSettings, landscapeGrid); this.buildingsGrid = new BuildingsGrid(); initAdditional(); } public boolean isWinePlantable(ShortPoint2D point) { return movablePathfinderGrid.pathfinderGrid.isWinePlantable(point.x, point.y); } public boolean isCornPlantable(ShortPoint2D point) { return movablePathfinderGrid.pathfinderGrid.isCornPlantable(point.x, point.y); } private void initAdditional() { this.graphicsGrid = new GraphicsGrid(); this.constructionMarksGrid = new ConstructionMarksGrid(); this.bordersThread = new BordersThread(new BordersThreadGrid()); this.guiInputGrid = new GuiInputGrid(); this.partitionsGrid.setPlayerChangedListener(new PlayerChangedListener()); this.enclosedBlockedAreaFinderGrid = new EnclosedBlockedAreaFinderGrid(); } public final short getHeight() { return height; } public final short getWidth() { return width; } public void initForPlayer(byte playerId, FogOfWar fogOfWar) { if (fogOfWar != null) { this.fogOfWar = fogOfWar; } else { this.fogOfWar = new FogOfWar(width, height, playerId); } } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); initAdditional(); this.bordersThread.checkArea(0, 0, width, height); } public void startThreads() { bordersThread.start(); if (fogOfWar != null) { fogOfWar.start(new FogOfWarGrid()); } } public void stopThreads() { bordersThread.cancel(); if (fogOfWar != null) { fogOfWar.cancel(); } } public MainGrid(String mapId, String mapName, IMapData mapGrid, PlayerSetting[] playerSettings) { this(mapId, mapName, (short) mapGrid.getWidth(), (short) mapGrid.getHeight(), playerSettings); for (short y = 0; y < height; y++) { for (short x = 0; x < width; x++) { ELandscapeType landscape = mapGrid.getLandscape(x, y); setLandscapeTypeAt(x, y, landscape); landscapeGrid.setHeightAt(x, y, mapGrid.getLandscapeHeight(x, y)); landscapeGrid.setResourceAt(x, y, mapGrid.getResourceType(x, y), mapGrid.getResourceAmount(x, y)); landscapeGrid.setBlockedPartition(x, y, mapGrid.getBlockedPartition(x, y)); } } // two phases, we might need the base grid tiles to add blocking, status for (short y = 0; y < height; y++) { for (short x = 0; x < width; x++) { MapObject object = mapGrid.getMapObject(x, y); if (object != null && isOccupyableBuilding(object) && isActivePlayer(object, playerSettings)) { addMapObject(x, y, object); } if ((x + y / 2) % 4 == 0 && y % 4 == 0 && isInsideWater(x, y)) { mapObjectsManager.addWaves(x, y); if (landscapeGrid.getResourceAmountAt(x, y) > 50) { mapObjectsManager.addFish(x, y); } } } } for (short y = 0; y < height; y++) { for (short x = 0; x < width; x++) { MapObject object = mapGrid.getMapObject(x, y); if (object != null && !isOccupyableBuilding(object) && isActivePlayer(object, playerSettings)) { try { addMapObject(x, y, object); } catch (Throwable t) { t.printStackTrace(); } } } } System.out.println("grid filled"); } private boolean isActivePlayer(MapObject object, PlayerSetting[] playerSettings) { return !(object instanceof IPlayerable) || playerSettings[((IPlayerable) object).getPlayerId()].isAvailable(); } private static boolean isOccupyableBuilding(MapObject object) { return object instanceof BuildingObject && ((BuildingObject) object).getType().isMilitaryBuilding(); } private boolean isInsideWater(short x, short y) { return isWaterSafe(x - 1, y) && isWaterSafe(x, y) && isWaterSafe(x + 1, y) && isWaterSafe(x - 1, y + 1) && isWaterSafe(x, y + 1) && isWaterSafe(x + 1, y + 1) && isWaterSafe(x, y + 2) && isWaterSafe(x + 1, y + 2) && isWaterSafe(x + 2, y + 2); } private boolean isWaterSafe(int x, int y) { return isInBounds((short) x, (short) y) && landscapeGrid.getLandscapeTypeAt((short) x, (short) y).isWater(); } private void addMapObject(short x, short y, MapObject object) { ShortPoint2D pos = new ShortPoint2D(x, y); if (object instanceof MapTreeObject) { if (isInBounds(x, y) && movablePathfinderGrid.pathfinderGrid.isTreePlantable(x, y)) { mapObjectsManager.plantAdultTree(pos); } } else if (object instanceof MapStoneObject) { mapObjectsManager.addStone(pos, ((MapStoneObject) object).getCapacity()); } else if (object instanceof StackObject) { placeStack(pos, ((StackObject) object).getType(), ((StackObject) object).getCount()); } else if (object instanceof BuildingObject) { BuildingObject buildingObject = (BuildingObject) object; Building building = constructBuildingAt(pos, buildingObject.getType(), partitionsGrid.getPlayer(buildingObject.getPlayerId()), true); if (building instanceof IOccupyableBuilding) { IOccupyableBuilding occupyableBuilding = (IOccupyableBuilding) building; ILogicMovable soldier = createNewMovableAt(building.getDoor(), EMovableType.SWORDSMAN_L1, building.getPlayer()); occupyableBuilding.requestSoldier(soldier); } } else if (object instanceof MovableObject) { MovableObject movableObject = (MovableObject) object; createNewMovableAt(pos, movableObject.getType(), partitionsGrid.getPlayer(movableObject.getPlayerId())); } } public MapFileHeader generateSaveHeader() { // TODO: description PreviewImageCreator previewImageCreator = new PreviewImageCreator(width, height, MapFileHeader.PREVIEW_IMAGE_SIZE, landscapeGrid.getPreviewImageDataSupplier()); short[] bgImage = previewImageCreator.getPreviewImage(); Player[] players = partitionsGrid.getPlayers(); PlayerSetting[] playerConfigurations = new PlayerSetting[players.length]; for (int i = 0; i < players.length; i++) { Player player = players[i]; if (player != null) { playerConfigurations[i] = new PlayerSetting(player.getPlayerType(), player.getCivilisation(), player.getTeamId()); } else { playerConfigurations[i] = new PlayerSetting(); } } return new MapFileHeader( MapType.SAVED_SINGLE, mapName, mapId, "TODO: description", width, height, (short) 1, playerConfigurations, new Date(), bgImage); } private void placeStack(ShortPoint2D pos, EMaterialType materialType, int count) { for (int i = 0; i < count; i++) { movablePathfinderGrid.dropMaterial(pos, materialType, true, false); } } public ConstructionMarksGrid getConstructionMarksGrid() { return constructionMarksGrid; } public LandscapeGrid getLandscapeGrid() { return landscapeGrid; } public ObjectsGrid getObjectsGrid() { return objectsGrid; } public PartitionsGrid getPartitionsGrid() { return partitionsGrid; } public IGraphicsGrid getGraphicsGrid() { return graphicsGrid; } public IGuiInputGrid getGuiInputGrid() { return guiInputGrid; } public MovableGrid getMovableGrid() { return movableGrid; } public final boolean isInBounds(int x, int y) { return x >= 0 && x < width && y >= 0 && y < height; } final ILogicMovable createNewMovableAt(ShortPoint2D pos, EMovableType type, Player player) { return new Movable(movablePathfinderGrid, type, pos, player); } /** * Creates a new building at the given position. * * @param position * The position to place the building. * @param type * The {@link EBuildingType} of the building. * @param player * The player owning the building. * @param fullyConstructed * If true, the building will be placed as fully constructed building.<br> * If false, it will only be placed as a construction site. * @return The newly created building. */ final Building constructBuildingAt(ShortPoint2D position, EBuildingType type, Player player, boolean fullyConstructed) { Building building = Building.createBuilding(type, player, position, buildingsGrid); building.construct(fullyConstructed); if (fullyConstructed) { byte buildingHeight = landscapeGrid.getHeightAt(position.x, position.y); for (RelativePoint curr : building.getFlattenTiles()) { landscapeGrid.flattenAndChangeHeightTowards(curr.getDx() + position.x, curr.getDy() + position.y, buildingHeight); } } return building; } final void setLandscapeTypeAt(int x, int y, ELandscapeType newType) { if (newType.isBlocking) { flagsGrid.setBlockedAndProtected(x, y, true); } else { if (landscapeGrid.getLandscapeTypeAt(x, y).isBlocking) { flagsGrid.setBlockedAndProtected(x, y, false); } } landscapeGrid.setLandscapeTypeAt(x, y, newType); } final void checkPositionThatChangedPlayer(int x, int y) { if (!isInBounds(x, y)) { return; } EnclosedBlockedAreaFinderAlgorithm.checkLandmark(enclosedBlockedAreaFinderGrid, x, y); ILogicMovable movable = movableGrid.getMovableAt(x, y); if (movable != null) { movable.checkPlayerOfPosition(partitionsGrid.getPlayerAt(x, y)); } } final boolean isValidPosition(IPathCalculatable pathCalculatable, int x, int y) { return isInBounds(x, y) && !flagsGrid.isBlocked(x, y) && (!pathCalculatable.needsPlayersGround() || pathCalculatable.getPlayerId() == partitionsGrid.getPlayerIdAt(x, y)); } public FlagsGrid getFlagsGrid() { return flagsGrid; } public void initWithPlayerSettings(PlayerSetting[] playerSettings) { partitionsGrid.initWithPlayerSettings(playerSettings); } final class PathfinderGrid implements IAStarPathMap, IDijkstraPathMap, IInAreaFinderMap, Serializable { private static final long serialVersionUID = -2775530442375843213L; @Override public boolean isBlocked(IPathCalculatable requester, int x, int y) { return flagsGrid.isBlocked(x, y) || (requester.needsPlayersGround() && requester.getPlayerId() != partitionsGrid.getPlayerIdAt(x, y)); } @Override public final float getCost(int sx, int sy, int tx, int ty) { // return Constants.TILE_PATHFINDER_COST * (flagsGrid.isProtected(sx, sy) ? 3.5f : 1); return 1; } @Override public final void markAsOpen(int x, int y) { landscapeGrid.setDebugColor(x, y, Color.BLUE.getARGB()); } @Override public final void markAsClosed(int x, int y) { landscapeGrid.setDebugColor(x, y, Color.RED.getARGB()); } @Override public final void setDijkstraSearched(int x, int y) { markAsOpen(x, y); } @Override public final boolean fitsSearchType(int x, int y, ESearchType searchType, IPathCalculatable pathCalculable) { switch (searchType) { case UNENFORCED_FOREIGN_GROUND: return !flagsGrid.isProtected(x, y) && !hasSamePlayer(x, y, pathCalculable) && !partitionsGrid.isEnforcedByTower(x, y); case VALID_FREE_POSITION: return isValidPosition(pathCalculable, x, y) && movableGrid.hasNoMovableAt(x, y); case PLANTABLE_TREE: return y < height - 1 && isTreePlantable(x, y + 1) && !hasProtectedNeighbor(x, y + 1) && hasSamePlayer(x, y + 1, pathCalculable) && !isMarked(x, y); case CUTTABLE_TREE: return isInBounds(x - 1, y - 1) && isMapObjectCuttable(x - 1, y - 1, EMapObjectType.TREE_ADULT) && hasSamePlayer(x - 1, y - 1, pathCalculable) && !isMarked(x, y); case PLANTABLE_CORN: return !isMarked(x, y) && hasSamePlayer(x, y, pathCalculable) && isCornPlantable(x, y); case CUTTABLE_CORN: return isMapObjectCuttable(x, y, EMapObjectType.CORN_ADULT) && hasSamePlayer(x, y, pathCalculable) && !isMarked(x, y); case PLANTABLE_WINE: return !isMarked(x, y) && hasSamePlayer(x, y, pathCalculable) && isWinePlantable(x, y); case HARVESTABLE_WINE: return isMapObjectCuttable(x, y, EMapObjectType.WINE_HARVESTABLE) && hasSamePlayer(x, y, pathCalculable) && !isMarked(x, y); case CUTTABLE_STONE: return y + 1 < height && x - 1 > 0 && isMapObjectCuttable(x - 1, y + 1, EMapObjectType.STONE) && hasSamePlayer(x, y, pathCalculable) && !isMarked(x, y); case ENEMY: { IMovable movable = movableGrid.getMovableAt(x, y); return movable != null && movable.getPlayerId() != pathCalculable.getPlayerId(); } case RIVER: return isRiver(x, y) && hasSamePlayer(x, y, pathCalculable) && !isMarked(x, y); case FISHABLE: return hasSamePlayer(x, y, pathCalculable) && hasNeighbourLandscape(x, y, ELandscapeType.WATER1); case NON_BLOCKED_OR_PROTECTED: return !(flagsGrid.isProtected(x, y) || flagsGrid.isBlocked(x, y)) && (!pathCalculable.needsPlayersGround() || hasSamePlayer(x, y, pathCalculable)) && movableGrid.getMovableAt(x, y) == null; case SOLDIER_BOWMAN: case SOLDIER_SWORDSMAN: case SOLDIER_PIKEMAN: case SOLDIER_INFANTRY: return isSoldierAt(x, y, searchType, pathCalculable.getPlayerId()); case RESOURCE_SIGNABLE: return isInBounds(x, y) && !flagsGrid.isProtected(x, y) && !flagsGrid.isMarked(x, y) && canAddRessourceSign(x, y); case FOREIGN_MATERIAL: return isInBounds(x, y) && !hasSamePlayer(x, y, pathCalculable) && mapObjectsManager.hasStealableMaterial(x, y); default: System.err.println("ERROR: Can't handle search type in fitsSearchType(): " + searchType); return false; } } @Override public boolean fitsSearchType(int x, int y, Set<ESearchType> types, IPathCalculatable requester) { for (ESearchType searchType : types) { if (fitsSearchType(x, y, searchType, requester)) { return true; } } return false; } final boolean canAddRessourceSign(int x, int y) { return x % 2 == 0 && y % 2 == 0 && landscapeGrid.getLandscapeTypeAt(x, y) == ELandscapeType.MOUNTAIN && !objectsGrid.hasMapObjectType(x, y, EMapObjectType.FOUND_COAL, EMapObjectType.FOUND_IRON, EMapObjectType.FOUND_GOLD, EMapObjectType.FOUND_NOTHING, EMapObjectType.FOUND_GEMSTONE, EMapObjectType.FOUND_BRIMSTONE); } private boolean isSoldierAt(int x, int y, ESearchType searchType, byte player) { ILogicMovable movable = movableGrid.getMovableAt(x, y); if (movable == null) { return false; } else { if (movable.getPlayerId() == player && movable.canOccupyBuilding()) { EMovableType movableType = movable.getMovableType(); switch (searchType) { case SOLDIER_BOWMAN: return movableType.isBowman(); case SOLDIER_SWORDSMAN: return movableType.isSwordsman(); case SOLDIER_PIKEMAN: return movableType.isPikeman(); case SOLDIER_INFANTRY: return movableType.isInfantry(); default: return false; } } else { return false; } } } private boolean isMarked(int x, int y) { return flagsGrid.isMarked(x, y); } private boolean hasProtectedNeighbor(int x, int y) { for (EDirection currDir : EDirection.VALUES) { if (flagsGrid.isProtected(currDir.getNextTileX(x), currDir.getNextTileY(y))) return true; } return false; } private boolean hasNeighbourLandscape(int x, int y, ELandscapeType landscape) { for (ShortPoint2D pos : new MapNeighboursArea(new ShortPoint2D(x, y))) { if (isInBounds(pos.x, pos.y) && landscapeGrid.getLandscapeTypeAt(pos.x, pos.y) == landscape) { return true; } } return false; } private boolean hasSamePlayer(int x, int y, IPathCalculatable requester) { return partitionsGrid.getPlayerIdAt(x, y) == requester.getPlayerId(); } private boolean isRiver(int x, int y) { ELandscapeType type = landscapeGrid.getLandscapeTypeAt(x, y); return type == ELandscapeType.RIVER1 || type == ELandscapeType.RIVER2 || type == ELandscapeType.RIVER3 || type == ELandscapeType.RIVER4; } final boolean isTreePlantable(int x, int y) { return landscapeGrid.getLandscapeTypeAt(x, y).isGrass() && !flagsGrid.isProtected(x, y) && !hasBlockedNeighbor((short) x, (short) y); } private boolean hasBlockedNeighbor(short x, short y) { return !MapNeighboursArea.stream(x, y).iterate((currX, currY) -> isInBounds(currX, currY) && !flagsGrid.isBlocked(currX, currY)); } private boolean isCornPlantable(int x, int y) { return !flagsGrid.isProtected(x, y) && !hasProtectedNeighbor(x, y) && !objectsGrid.hasMapObjectType(x, y, EMapObjectType.CORN_GROWING, EMapObjectType.CORN_ADULT) && !objectsGrid.hasNeighborObjectType(x, y, EMapObjectType.CORN_ADULT, EMapObjectType.CORN_GROWING) && landscapeGrid.isHexAreaOfType(x, y, 0, 2, ELandscapeType.GRASS, ELandscapeType.EARTH); } private boolean isMapObjectCuttable(int x, int y, EMapObjectType type) { return objectsGrid.hasCuttableObject(x, y, type); } private boolean isWinePlantable(int x, int y) { if (!flagsGrid.isProtected(x, y) && !objectsGrid.hasMapObjectType(x, y, EMapObjectType.WINE_GROWING, EMapObjectType.WINE_HARVESTABLE, EMapObjectType.WINE_DEAD) && landscapeGrid.isHexAreaOfType(x, y, 0, 1, ELandscapeType.GRASS, ELandscapeType.EARTH)) { EDirection direction = getDirectionOfMaximumHeightDifference(x, y, 2); if (direction != null) { // if minimum height difference has been found ShortPoint2D inDirPos = direction.getNextHexPoint(x, y); ShortPoint2D invDirPos = direction.getInverseDirection().getNextHexPoint(x, y); return !objectsGrid.hasMapObjectType(inDirPos.x, inDirPos.y, EMapObjectType.WINE_GROWING, EMapObjectType.WINE_HARVESTABLE, EMapObjectType.WINE_DEAD) && !objectsGrid.hasMapObjectType(invDirPos.x, invDirPos.y, EMapObjectType.WINE_GROWING, EMapObjectType.WINE_HARVESTABLE, EMapObjectType.WINE_DEAD); } } return false; } private EDirection getDirectionOfMaximumHeightDifference(int x, int y, int minimumHeightDifference) { byte height = landscapeGrid.getHeightAt(x, y); for (ShortPoint2D pos : new MapNeighboursArea((short) x, (short) y)) { if (Math.abs(height - landscapeGrid.getHeightAt(pos.x, pos.y)) >= minimumHeightDifference) { return EDirection.getDirection((short) x, (short) y, pos.x, pos.y); } } return null; } @Override public void setDebugColor(int x, int y, Color color) { landscapeGrid.setDebugColor(x, y, color.getARGB()); } @Override public short getBlockedPartition(int x, int y) { return landscapeGrid.getBlockedPartitionAt(x, y); } } final class GraphicsGrid implements IGraphicsGrid { private transient BitSet bordersGrid = new BitSet(width * height); @Override public final short getHeight() { return height; } @Override public final short getWidth() { return width; } @Override public final IMovable getMovableAt(int x, int y) { return movableGrid.getMovableAt(x, y); } @Override public final IMapObject getMapObjectsAt(int x, int y) { return objectsGrid.getObjectsAt(x, y); } @Override public final byte getHeightAt(int x, int y) { return landscapeGrid.getHeightAt(x, y); } @Override public final ELandscapeType getLandscapeTypeAt(int x, int y) { return landscapeGrid.getLandscapeTypeAt(x, y); } @Override public final int getDebugColorAt(int x, int y, EDebugColorModes debugColorMode) { if (!MatchConstants.ENABLE_DEBUG_COLORS) { return 0; } switch (debugColorMode) { case BLOCKED_PARTITIONS: return getScaledColor(landscapeGrid.getBlockedPartitionAt(x, y) + 1); case PARTITION_ID: return getScaledColor(partitionsGrid.getPartitionIdAt(x, y)); case REAL_PARTITION_ID: return getScaledColor(partitionsGrid.getRealPartitionIdAt(x, y)); case PLAYER_ID: return getScaledColor(partitionsGrid.getPlayerIdAt(x, y) + 1); case TOWER_COUNT: return getScaledColor(partitionsGrid.getTowerCountAt(x, y) + 1); case DEBUG_COLOR: return landscapeGrid.getDebugColor(x, y); case MARKS_AND_OBJECTS: return flagsGrid.isMarked(x, y) ? Color.ORANGE.getARGB() : (objectsGrid.getMapObjectAt(x, y, EMapObjectType.INFORMABLE_MAP_OBJECT) != null ? Color.GREEN.getARGB() : (objectsGrid .getMapObjectAt(x, y, EMapObjectType.ATTACKABLE_TOWER) != null ? Color.RED.getARGB() : (flagsGrid.isBlocked(x, y) ? Color.BLACK.getARGB() : (flagsGrid.isProtected(x, y) ? Color.BLUE.getARGB() : 0)))); case RESOURCE_AMOUNTS: float resource = ((float) landscapeGrid.getResourceAmountAt(x, y)) / Byte.MAX_VALUE; return Color.getARGB(1, .6f, 0, resource); case NONE: default: return 0; } } private int getScaledColor(int value) { final int SCALE = 4; return Color.getABGR(((float) (value % SCALE)) / SCALE, ((float) ((value / SCALE) % SCALE)) / SCALE, ((float) ((value / SCALE / SCALE) % SCALE)) / SCALE, 1); } @Override public final boolean isBorder(int x, int y) { return bordersGrid.get(x + y * width); } @Override public final byte getPlayerIdAt(int x, int y) { return partitionsGrid.getPlayerIdAt(x, y); } @Override public final byte getVisibleStatus(int x, int y) { return fogOfWar.getVisibleStatus(x, y); } @Override public final void setBackgroundListener(IGraphicsBackgroundListener backgroundListener) { landscapeGrid.setBackgroundListener(backgroundListener); } @Override public int nextDrawableX(int x, int y, int maxX) { return x + 1; } @Override public IPartitionData getPartitionData(int x, int y) { return partitionsGrid.getPartitionDataForManagerAt(x, y); } @Override public boolean isBuilding(int x, int y) { return flagsGrid.isBlocked(x, y) && objectsGrid.isBuildingAt(x, y); } } final class MapObjectsManagerGrid implements IMapObjectsManagerGrid { private static final long serialVersionUID = 6223899915568781576L; @Override public final void setLandscape(int x, int y, ELandscapeType landscapeType) { setLandscapeTypeAt(x, y, landscapeType); } @Override public final boolean isBlocked(int x, int y) { return flagsGrid.isBlocked(x, y); } @Override public final void setBlocked(int x, int y, boolean blocked) { flagsGrid.setBlockedAndProtected(x, y, blocked); } @Override public final boolean isProtected(int x, int y) { return flagsGrid.isProtected(x, y); } @Override public final void setProtected(int x, int y, boolean protect) { flagsGrid.setProtected(x, y, protect); } @Override public final boolean removeMapObject(int x, int y, AbstractHexMapObject mapObject) { return objectsGrid.removeMapObject(x, y, mapObject); } @Override public final AbstractHexMapObject getMapObject(int x, int y, EMapObjectType mapObjectType) { return objectsGrid.getMapObjectAt(x, y, mapObjectType); } @Override public final void addMapObject(int x, int y, AbstractHexMapObject mapObject) { objectsGrid.addMapObjectAt(x, y, mapObject); } @Override public final short getWidth() { return width; } @Override public final short getHeight() { return height; } @Override public final boolean isInBounds(int x, int y) { return MainGrid.this.isInBounds(x, y); } @Override public EResourceType getRessourceTypeAt(int x, int y) { return landscapeGrid.getResourceTypeAt(x, y); } @Override public byte getRessourceAmountAt(int x, int y) { return landscapeGrid.getResourceAmountAt(x, y); } @Override public void hitWithArrowAt(ArrowObject arrow) { short x = arrow.getTargetX(); short y = arrow.getTargetY(); ILogicMovable movable = movableGrid.getMovableAt(x, y); if (movable != null) { movable.receiveHit(arrow.getHitStrength(), arrow.getSourcePos(), arrow.getShooterPlayerId()); mapObjectsManager.removeMapObject(x, y, arrow); } } @Override public void spawnDonkey(ShortPoint2D position, byte playerId) { Player player = partitionsGrid.getPlayer(playerId); ILogicMovable donkey = new Movable(movablePathfinderGrid, EMovableType.DONKEY, position, player); donkey.leavePosition(); } @Override public boolean isBuildingAreaAt(short x, short y) { return objectsGrid.isBuildingAt(x, y); } @Override public boolean hasMapObjectType(int x, int y, EMapObjectType... mapObjectTypes) { return objectsGrid.hasMapObjectType(x, y, mapObjectTypes); } } final class EnclosedBlockedAreaFinderGrid implements IEnclosedBlockedAreaFinderGrid { @Override public final boolean isPioneerBlockedAndWithoutTowerProtection(int x, int y) { return MainGrid.this.isInBounds(x, y) && flagsGrid.isPioneerBlocked(x, y) && landscapeGrid.getBlockedPartitionAt(x, y) > 0 && !partitionsGrid.isEnforcedByTower(x, y); } @Override public final boolean isInBounds(int x, int y) { return MainGrid.this.isInBounds(x, y); } @Override public final short getPartitionAt(int x, int y) { return partitionsGrid.getPartitionIdAt(x, y); } @Override public final void setPartitionAt(int x, int y, short partition) { partitionsGrid.setPartitionAt(x, y, partition); } @Override public short getHeight() { return height; } @Override public short getWidth() { return width; } } final class ConstructionMarksGrid extends AbstractConstructionMarkableMap { @Override public final void setConstructMarking(int x, int y, boolean set, boolean binaryConstructionMarkValues, RelativePoint[] flattenPositions) { if (isInBounds(x, y)) { if (set) { byte newValue = binaryConstructionMarkValues ? 0 : calculateConstructionMarkValue(x, y, flattenPositions); mapObjectsManager.setConstructionMarking(x, y, newValue); } else { mapObjectsManager.setConstructionMarking(x, y, (byte) -1); } } } @Override public final short getWidth() { return width; } @Override public final short getHeight() { return height; } @Override public boolean canConstructAt(short x, short y, EBuildingType buildingType, byte playerId) { RelativePoint[] buildingArea = buildingType.getBuildingArea(); BuildingAreaBitSet areaBitSet = buildingType.getBuildingAreaBitSet(); if (!isInBounds(areaBitSet.minX + x, areaBitSet.minY + y) || !isInBounds(areaBitSet.maxX + x, areaBitSet.maxY + y)) { return false; } short partitionId = getPartitionIdAt(areaBitSet.aPosition.calculateX(x), areaBitSet.aPosition.calculateY(y)); if (!canPlayerConstructOnPartition(playerId, partitionId)) { return false; } for (RelativePoint curr : buildingArea) { int currX = curr.calculateX(x); int currY = curr.calculateY(y); if (!canUsePositionForConstruction(currX, currY, buildingType.getRequiredGroundTypeAt(currX, currY), partitionId)) { return false; } } return !buildingType.needsFlattenedGround() || calculateConstructionMarkValue(x, y, buildingArea) >= 0; } @Override public boolean canUsePositionForConstruction(int x, int y, Set<ELandscapeType> allowedGroundTypes, short partitionId) { return isInBounds(x, y) && !flagsGrid.isProtected(x, y) && partitionsGrid.getPartitionIdAt(x, y) == partitionId && allowedGroundTypes.contains(landscapeGrid.getLandscapeTypeAt(x, y)); } @Override public byte calculateConstructionMarkValue(int mapX, int mapY, final RelativePoint[] flattenPositions) { int sum = 0; for (RelativePoint currPos : flattenPositions) { sum += landscapeGrid.getHeightAt(currPos.calculateX(mapX), currPos.calculateY(mapY)); } float avg = ((float) sum) / flattenPositions.length; float diff = 0; for (RelativePoint currPos : flattenPositions) { float currDiff = Math.abs(landscapeGrid.getHeightAt(currPos.calculateX(mapX), currPos.calculateY(mapY)) - avg); diff += currDiff; } int result = (int) (Constants.CONSTRUCTION_MARK_SCALE_FACTOR * Math.pow(diff, Constants.CONSTRUCTION_MARK_POW_FACTOR) / flattenPositions.length); if (result <= Byte.MAX_VALUE) { return (byte) result; } else { return -1; } } @Override public short getPartitionIdAt(int x, int y) { return partitionsGrid.getPartitionIdAt(x, y); } @Override public boolean canPlayerConstructOnPartition(byte playerId, short partitionId) { return (playerId == 0 && MatchConstants.ENABLE_ALL_PLAYER_SELECTION && !partitionsGrid.isDefaultPartition(partitionId)) || partitionsGrid.ownsPlayerPartition(partitionId, playerId); } @Override public boolean isInBounds(int x, int y) { return MainGrid.this.isInBounds(x, y); } } final class MovablePathfinderGrid extends AbstractMovableGrid { private static final long serialVersionUID = 4006228724969442801L; private transient PathfinderGrid pathfinderGrid; private transient AbstractAStar aStar; transient DijkstraAlgorithm dijkstra; // not private, because it's used by BuildingsGrid private transient InAreaFinder inAreaFinder; public MovablePathfinderGrid() { initPathfinders(); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); initPathfinders(); } private void initPathfinders() { pathfinderGrid = new PathfinderGrid(); aStar = new BucketQueueAStar(pathfinderGrid, width, height); dijkstra = new DijkstraAlgorithm(pathfinderGrid, aStar, width, height); inAreaFinder = new InAreaFinder(pathfinderGrid, width, height); } @Override public final boolean isBlocked(int x, int y) { return flagsGrid.isBlocked(x, y); } @Override public final boolean isProtected(int x, int y) { return flagsGrid.isProtected(x, y); } @Override public final boolean isBlockedOrProtected(int x, int y) { return isBlocked(x, y) || isProtected(x, y); } @Override public final boolean canTakeMaterial(ShortPoint2D position, EMaterialType material) { return mapObjectsManager.canPop(position.x, position.y, material); } @Override public final byte getHeightAt(ShortPoint2D position) { return landscapeGrid.getHeightAt(position.x, position.y); } @Override public final void setMarked(ShortPoint2D position, boolean marked) { flagsGrid.setMarked(position.x, position.y, marked); } @Override public final boolean isMarked(ShortPoint2D position) { return flagsGrid.isMarked(position.x, position.y); } @Override public final void placeSmoke(ShortPoint2D pos, boolean place) { if (place) { mapObjectsManager.addSimpleMapObject(pos, EMapObjectType.SMOKE, false, null); } else { mapObjectsManager.removeMapObjectType(pos.x, pos.y, EMapObjectType.SMOKE); } } @Override public void changePlayerAt(ShortPoint2D position, Player player) { partitionsGrid.changePlayerAt(position, player.playerId); bordersThread.checkPosition(position); checkPositionThatChangedPlayer(position.x, position.y); } @Override public void addJobless(IManageableBearer bearer) { partitionsGrid.getPartitionAt(bearer).addJobless(bearer); } @Override public void removeJobless(IManageableBearer bearer) { partitionsGrid.getPartitionAt(bearer).removeJobless(bearer); } @Override public void addJobless(IManageableWorker worker) { partitionsGrid.getPartitionAt(worker).addJobless(worker); } @Override public void removeJobless(IManageableWorker worker) { partitionsGrid.getPartitionAt(worker).removeJobless(worker); } @Override public void addJobless(IManageableDigger digger) { partitionsGrid.getPartitionAt(digger).addJobless(digger); } @Override public void removeJobless(IManageableDigger digger) { partitionsGrid.getPartitionAt(digger).removeJobless(digger); } @Override public void addJobless(IManageableBricklayer bricklayer) { partitionsGrid.getPartitionAt(bricklayer).addJobless(bricklayer); } @Override public void removeJobless(IManageableBricklayer bricklayer) { partitionsGrid.getPartitionAt(bricklayer).removeJobless(bricklayer); } @Override public boolean takeMaterial(ShortPoint2D position, EMaterialType materialType) { return mapObjectsManager.popMaterial(position.x, position.y, materialType); } @Override public boolean dropMaterial(ShortPoint2D position, EMaterialType materialType, boolean offer, boolean forced) { boolean successful; if (forced) { position = mapObjectsManager.pushMaterialForced(position.x, position.y, materialType); successful = position != null; } else { successful = mapObjectsManager.pushMaterial(position.x, position.y, materialType); } if (successful && offer) { partitionsGrid.getPartitionAt(position.x, position.y).addOffer(position, materialType, EOfferPriority.OFFER_TO_ALL); } return successful; } @Override public EDirection getDirectionOfSearched(ShortPoint2D position, ESearchType searchType) { IPredicate<ELandscapeType> predicate; if (searchType == ESearchType.FISHABLE) { predicate = ELandscapeType::isWater; } else if (searchType == ESearchType.RIVER) { predicate = ELandscapeType::isRiver; } else { return null; } for (EDirection direction : EDirection.VALUES) { int x = direction.getNextTileX(position.x); int y = direction.getNextTileY(position.y); if (isInBounds(x, y) && predicate.evaluate(landscapeGrid.getLandscapeTypeAt(x, y))) { return direction; } } return null; } @Override public EMaterialType popToolProductionRequest(ShortPoint2D pos) { return partitionsGrid.getPartitionAt(pos.x, pos.y).popToolProduction(pos); } @Override public final boolean isPigAdult(ShortPoint2D pos) { return mapObjectsManager.isPigAdult(pos); } @Override public void placePigAt(ShortPoint2D pos, boolean place) { mapObjectsManager.placePig(pos, place); } @Override public boolean hasPigAt(ShortPoint2D position) { return mapObjectsManager.isPigThere(position); } @Override public boolean feedDonkeyAt(ShortPoint2D position) { byte playerId = partitionsGrid.getPartitionAt(position.x, position.y).getPlayerId(); return mapObjectsManager.feedDonkeyAt(position, playerId); } @Override public boolean canPushMaterial(ShortPoint2D position) { return mapObjectsManager.canPush(position); } @Override public void changeHeightTowards(int x, int y, byte targetHeight) { landscapeGrid.flattenAndChangeHeightTowards(x, y, targetHeight); objectsGrid.removeMapObjectTypes(x, y, EMapObjectType.TO_BE_REMOVED_WHEN_FLATTENED); } @Override public boolean hasNoMovableAt(int x, int y) { return movableGrid.hasNoMovableAt(x, y); } @Override public boolean isFreePosition(ShortPoint2D position) { short x = position.x; short y = position.y; return isInBounds(x, y) && !flagsGrid.isBlocked(x, y) && movableGrid.hasNoMovableAt(x, y); } @Override public void leavePosition(ShortPoint2D position, ILogicMovable movable) { movableGrid.movableLeft(position, movable); } @Override public void enterPosition(ShortPoint2D position, ILogicMovable movable, boolean informFullArea) { movableGrid.movableEntered(position, movable); notifyAttackers(position, movable, informFullArea); } public void notifyAttackers(ShortPoint2D position, ILogicMovable movable, boolean informFullArea) { if (movable.isAttackable()) { movableGrid.informMovables(movable, position.x, position.y, informFullArea); objectsGrid.informObjectsAboutAttackable(position, movable, informFullArea, !movable.getMovableType().isBowman()); } } @Override public Path calculatePathTo(IPathCalculatable pathRequester, ShortPoint2D targetPos) { return aStar.findPath(pathRequester, targetPos); } @Override public Path searchDijkstra(IPathCalculatable pathCalculateable, short centerX, short centerY, short radius, ESearchType searchType) { return dijkstra.find(pathCalculateable, centerX, centerY, (short) 0, radius, searchType); } @Override public Path searchInArea(IPathCalculatable pathCalculateable, short centerX, short centerY, short radius, ESearchType searchType) { ShortPoint2D target = inAreaFinder.find(pathCalculateable, centerX, centerY, radius, searchType); if (target != null) { return calculatePathTo(pathCalculateable, target); } else { return null; } } @Override public ILogicMovable getMovableAt(int x, int y) { return movableGrid.getMovableAt(x, y); } @Override public void addSelfDeletingMapObject(ShortPoint2D position, EMapObjectType mapObjectType, float duration, Player player) { mapObjectsManager.addSelfDeletingMapObject(position, mapObjectType, duration, player); } @Override public boolean isInBounds(int x, int y) { return MainGrid.this.isInBounds(x, y); } @Override public boolean fitsSearchType(IPathCalculatable pathCalculable, int x, int y, ESearchType searchType) { return pathfinderGrid.fitsSearchType(x, y, searchType, pathCalculable); } @Override public final boolean executeSearchType(IPathCalculatable pathCalculable, ShortPoint2D position, ESearchType searchType) { if (fitsSearchType(pathCalculable, position.x, position.y, searchType)) { return mapObjectsManager.executeSearchType(position, searchType); } else { return false; } } @Override public ELandscapeType getLandscapeTypeAt(int x, int y) { return landscapeGrid.getLandscapeTypeAt(x, y); } @Override public IAttackable getEnemyInSearchArea(final ShortPoint2D position, final IAttackable searchingAttackable, final short minSearchRadius, final short maxSearchRadius, final boolean includeTowers) { boolean isBowman = searchingAttackable.getMovableType().isBowman(); IAttackable enemy = getEnemyInSearchArea(searchingAttackable.getPlayerId(), new HexGridArea(position.x, position.y, minSearchRadius, maxSearchRadius), isBowman, includeTowers); if (includeTowers && !isBowman && enemy == null) { enemy = getEnemyInSearchArea(searchingAttackable.getPlayerId(), new HexGridArea(position.x, position.y, maxSearchRadius, Constants.TOWER_SEARCH_RADIUS), false, true); } return enemy; } private IAttackable getEnemyInSearchArea(byte searchingPlayer, HexGridArea area, boolean isBowman, boolean includeTowers) { return area.stream().filterBounds(width, height).iterateForResult((x, y) -> { IAttackable currAttackable = movableGrid.getMovableAt(x, y); if (includeTowers && !isBowman && currAttackable == null) { currAttackable = (IAttackable) objectsGrid.getMapObjectAt(x, y, EMapObjectType.ATTACKABLE_TOWER); } if (currAttackable != null && MovableGrid.isEnemy(searchingPlayer, currAttackable)) { return Optional.of(currAttackable); } else { return Optional.empty(); } }).orElse(null); } @Override public void addArrowObject(ShortPoint2D attackedPos, ShortPoint2D shooterPos, byte shooterPlayerId, float hitStrength) { mapObjectsManager.addArrowObject(attackedPos, shooterPos, shooterPlayerId, hitStrength); } @Override public final ShortPoint2D calcDecentralizeVector(short x, short y) { MutablePoint2D vector = new MutablePoint2D(); HexGridArea.stream(x, y, 1, Constants.MOVABLE_FLOCK_TO_DECENTRALIZE_MAX_RADIUS).forEach((currX, currY) -> { int radius = ShortPoint2D.getOnGridDist(currX - x, currY - y); int factor; if (!MainGrid.this.isInBounds(currX, currY)) { factor = radius == 1 ? 6 : 2; } else if (!movableGrid.hasNoMovableAt(currX, currY)) { factor = Constants.MOVABLE_FLOCK_TO_DECENTRALIZE_MAX_RADIUS - radius + 1; } else { return; } vector.x += (x - currX) * factor; vector.y += (y - currY) * factor; }); return vector.toShortPoint2D(); } @Override public Player getPlayerAt(ShortPoint2D position) { return partitionsGrid.getPlayerAt(position.x, position.y); } @Override public boolean isValidPosition(IPathCalculatable pathCalculatable, int x, int y) { return MainGrid.this.isValidPosition(pathCalculatable, x, y); } @Override public boolean isValidNextPathPosition(IPathCalculatable pathCalculatable, ShortPoint2D nextPos, ShortPoint2D targetPos) { return isValidPosition(pathCalculatable, nextPos.x, nextPos.y) && (!pathCalculatable.needsPlayersGround() || partitionsGrid.getPartitionAt(pathCalculatable) == partitionsGrid.getPartitionAt(targetPos.x, targetPos.y)); } @Override public boolean tryTakingRecource(ShortPoint2D position, EResourceType resource) { return landscapeGrid.tryTakingResource(position, resource); } } final class BordersThreadGrid implements IBordersThreadGrid { @Override public final byte getPlayerIdAt(int x, int y) { return partitionsGrid.getPlayerIdAt(x, y); } @Override public final void setBorderAt(int x, int y, boolean isBorder) { graphicsGrid.bordersGrid.set(x + y * width, isBorder); } @Override public final boolean isInBounds(int x, int y) { return MainGrid.this.isInBounds(x, y); } @Override public final short getBlockedPartition(int x, int y) { return landscapeGrid.getBlockedPartitionAt(x, y); } } final class BuildingsGrid implements IBuildingsGrid, Serializable { private static final long serialVersionUID = -5567034251907577276L; private final RequestStackGrid requestStackGrid = new RequestStackGrid(); @Override public final byte getHeightAt(ShortPoint2D position) { return landscapeGrid.getHeightAt(position.x, position.y); } @Override public final void pushMaterialsTo(ShortPoint2D position, EMaterialType type, byte numberOf) { for (int i = 0; i < numberOf; i++) { movablePathfinderGrid.dropMaterial(position, type, true, true); } } @Override public final boolean setBuilding(ShortPoint2D position, Building newBuilding) { if (MainGrid.this.isInBounds(position.x, position.y)) { FreeMapArea protectedArea = new FreeMapArea(position, newBuilding.getBuildingType().getProtectedTiles()); if (canConstructAt(protectedArea)) { setProtectedState(protectedArea, true); mapObjectsManager.addBuildingTo(position, newBuilding); objectsGrid.setBuildingArea(protectedArea, newBuilding); return true; } else { return false; } } else { return false; } } private void setProtectedState(FreeMapArea area, boolean setProtected) { area.stream().forEach((x, y) -> flagsGrid.setProtected(x, y, setProtected)); } private boolean canConstructAt(FreeMapArea area) { for (ShortPoint2D curr : area) { short x = curr.x; short y = curr.y; if (!isInBounds(x, y) || flagsGrid.isProtected(x, y) || flagsGrid.isBlocked(x, y)) { return false; } } return true; } @Override public final void removeBuildingAt(ShortPoint2D pos) { IBuilding building = (IBuilding) objectsGrid.getMapObjectAt(pos.x, pos.y, EMapObjectType.BUILDING); mapObjectsManager.removeMapObjectType(pos.x, pos.y, EMapObjectType.BUILDING); FreeMapArea area = new FreeMapArea(pos, building.getBuildingType().getProtectedTiles()); objectsGrid.setBuildingArea(area, null); area.stream().filterBounds(width, height).forEach((x, y) -> { StackMapObject stack = (StackMapObject) objectsGrid.getMapObjectAt(x, y, EMapObjectType.STACK_OBJECT); flagsGrid.setBlockedAndProtected(x, y, false, stack != null); // if there is a stack, the position must stay protected }); } @Override public final void setBlocked(FreeMapArea area, boolean blocked) { area.stream().filterBounds(width, height).forEach((x, y) -> flagsGrid.setBlockedAndProtected(x, y, blocked)); } @Override public final short getWidth() { return width; } @Override public final short getHeight() { return height; } @Override public final ILogicMovable getMovable(ShortPoint2D position) { return movableGrid.getMovableAt(position.x, position.y); } @Override public final MapObjectsManager getMapObjectsManager() { return mapObjectsManager; } @Override public final AbstractMovableGrid getMovableGrid() { return movablePathfinderGrid; } @Override public final void requestDiggers(IDiggerRequester requester, byte amount) { partitionsGrid.getPartitionAt(requester).requestDiggers(requester, amount); } @Override public final void requestBricklayer(Building building, ShortPoint2D bricklayerTargetPos, EDirection direction) { partitionsGrid.getPartitionAt(building).requestBricklayer(building, bricklayerTargetPos, direction); } @Override public final IRequestsStackGrid getRequestStackGrid() { return requestStackGrid; } @Override public final void requestBuildingWorker(EMovableType workerType, WorkerBuilding workerBuilding) { partitionsGrid.getPartitionAt(workerBuilding).requestBuildingWorker(workerType, workerBuilding); } @Override public final void requestSoldierable(IBarrack barrack) { partitionsGrid.getPartitionAt(barrack).requestSoldierable(barrack); } @Override public final DijkstraAlgorithm getDijkstra() { return movablePathfinderGrid.dijkstra; } private class RequestStackGrid implements IRequestsStackGrid, Serializable { private static final long serialVersionUID = 1278397366408051067L; @Override public final void request(EMaterialType materialType, MaterialRequestObject requestObject) { partitionsGrid.getPartitionAt(requestObject).request(materialType, requestObject); } @Override public final boolean hasMaterial(ShortPoint2D position, EMaterialType materialType) { return mapObjectsManager.canPop(position.x, position.y, materialType); } @Override public final boolean popMaterial(ShortPoint2D position, EMaterialType materialType) { return mapObjectsManager.popMaterial(position.x, position.y, materialType); } @Override public StockSettings getPartitionStockSettings(ShortPoint2D position) { return partitionsGrid.getPartitionSettings(position).getStockSettings(); } @Override public final byte getStackSize(ShortPoint2D position, EMaterialType materialType) { return mapObjectsManager.getStackSize(position.x, position.y, materialType); } @Override public final void createOffersForAvailableMaterials(ShortPoint2D position, EMaterialType materialType) { byte stackSize = mapObjectsManager.getStackSize(position.x, position.y, materialType); PartitionManager partition = partitionsGrid.getPartitionAt(position.x, position.y); for (byte i = 0; i < stackSize; i++) { partition.addOffer(position, materialType, EOfferPriority.OFFER_TO_ALL); } } @Override public void offer(ShortPoint2D position, EMaterialType materialType, EOfferPriority priority, IOfferEmptiedListener offerListener) { partitionsGrid.getPartitionAt(position.x, position.y).addOffer(position, materialType, priority, offerListener); } @Override public void updateOfferPriorities(ShortPoint2D position, EMaterialType materialType, EOfferPriority newPriority) { partitionsGrid.getPartitionAt(position.x, position.y).updateOfferPriority(position, materialType, newPriority); } } @Override public void occupyAreaByTower(Player player, MapCircle influencingArea, FreeMapArea groundArea) { partitionsGrid.addTowerAndOccupyArea(player.playerId, influencingArea, groundArea); checkAllPositionsForEnclosedBlockedAreas(influencingArea.stream()); // TODO @Andreas Eberle only test the borders of changed areas!! } @Override public void freeAreaOccupiedByTower(ShortPoint2D towerPosition) { CoordinateStream positions = partitionsGrid.removeTowerAndFreeOccupiedArea(towerPosition); checkAllPositionsForEnclosedBlockedAreas(positions); } @Override public void changePlayerOfTower(ShortPoint2D towerPosition, Player newPlayer, FreeMapArea groundArea) { CoordinateStream positions = partitionsGrid.changePlayerOfTower(towerPosition, newPlayer.playerId); checkAllPositionsForEnclosedBlockedAreas(positions); } private void checkAllPositionsForEnclosedBlockedAreas(CoordinateStream area) { area.forEach(MainGrid.this::checkPositionThatChangedPlayer); } @Override public boolean isAreaFlattenedAtHeight(ShortPoint2D position, RelativePoint[] positions, byte expectedHeight) { return landscapeGrid.isAreaFlattenedAtHeight(position, positions, expectedHeight); } @Override public void drawWorkAreaCircle(ShortPoint2D buildingPosition, ShortPoint2D workAreaCenter, short radius, boolean draw) { short buildingPartition = partitionsGrid.getPartitionIdAt(buildingPosition.x, buildingPosition.y); final int numCircles = 4; for (int circle = 1; circle <= 4; circle++) { float circleRadius = radius * circle / (float) numCircles; float mapObjectProgress = (circle - 1) / (float) (numCircles - 1); MapCircle.streamBorder(workAreaCenter.x, workAreaCenter.y, circleRadius).forEach( (x, y) -> addOrRemoveMarkObject(buildingPartition, draw, new ShortPoint2D(x, y), mapObjectProgress)); } } @Override public void drawTradingPathLine(ShortPoint2D start, ShortPoint2D[] waypoints, boolean draw) { ShortPoint2D lastWaypoint = start; float progress = 0; for (ShortPoint2D currentWaypoint : waypoints) { if (currentWaypoint == null) { continue; } float fixedProgress = progress; MapLine.stream(lastWaypoint, currentWaypoint) .filterBounds(width, height) .forEach((x, y) -> { if (draw) { mapObjectsManager.addBuildingWorkAreaObject(x, y, fixedProgress); } else { mapObjectsManager.removeMapObjectType(x, y, EMapObjectType.WORKAREA_MARK); } }); lastWaypoint = currentWaypoint; progress += 1f / (waypoints.length - 1); } } private void addOrRemoveMarkObject(short buildingPartition, boolean draw, ShortPoint2D pos, float progress) { if (draw) { // Only place an object if the position is the same as the one of the building. if (partitionsGrid.getPartitionIdAt(pos.x, pos.y) == buildingPartition) { mapObjectsManager.addBuildingWorkAreaObject(pos.x, pos.y, progress); } } else { mapObjectsManager.removeMapObjectType(pos.x, pos.y, EMapObjectType.WORKAREA_MARK); } } @Override public short getPartitionIdAt(ShortPoint2D pos) { return partitionsGrid.getPartitionIdAt(pos.x, pos.y); } @Override public boolean tryTakingResource(ShortPoint2D position, EResourceType resource) { return landscapeGrid.tryTakingResource(position, resource); } @Override public int getAmountOfResource(EResourceType resource, Iterable<ShortPoint2D> positions) { return landscapeGrid.getAmountOfResource(resource, positions); } @Override public MaterialProductionSettings getMaterialProductionAt(int x, int y) { return partitionsGrid.getMaterialProductionAt(x, y); } @Override public ShortPoint2D getClosestReachablePosition(final ShortPoint2D start, ShortPoint2D target, final boolean needsPlayersGround, final byte playerId, short targetRadius) { Path path = movablePathfinderGrid.searchDijkstra(new IPathCalculatable() { private static final long serialVersionUID = 1L; @Override public ShortPoint2D getPos() { return start; } @Override public byte getPlayerId() { return playerId; } @Override public boolean needsPlayersGround() { return needsPlayersGround; } }, target.x, target.y, targetRadius, ESearchType.VALID_FREE_POSITION); return path != null ? path.getTargetPos() : null; } } final class GuiInputGrid implements IGuiInputGrid { @Override public final ILogicMovable getMovable(int x, int y) { return movableGrid.getMovableAt(x, y); } @Override public final short getWidth() { return width; } @Override public final short getHeight() { return height; } @Override public final IBuilding getBuildingAt(int x, int y) { return objectsGrid.getBuildingAt(x, y); } @Override public final boolean isInBounds(ShortPoint2D position) { return MainGrid.this.isInBounds(position.x, position.y); } @Override public final void resetDebugColors() { landscapeGrid.resetDebugColors(); } @Override public final ShortPoint2D getConstructablePosition(ShortPoint2D pos, EBuildingType type, byte playerId, boolean useNeighbors) { if (constructionMarksGrid.canConstructAt(pos.x, pos.y, type, playerId)) { return pos; } else if (useNeighbors) { for (ShortPoint2D neighbour : new MapNeighboursArea(pos)) { if (constructionMarksGrid.canConstructAt(neighbour.x, neighbour.y, type, playerId)) { return neighbour; } } return null; } else { return null; } } @Override public final void save(PlayerState[] playerStates) throws IOException, InterruptedException { boolean savedPausingState = MatchConstants.clock().isPausing(); MatchConstants.clock().setPausing(true); try { Thread.sleep(300); // FIXME @Andreas serializer should wait until threads did their work! } catch (InterruptedException e) { e.printStackTrace(); } MapList list = MapList.getDefaultList(); list.saveMap(playerStates, MainGrid.this); MatchConstants.clock().setPausing(savedPausingState); } @Override public final void toggleFogOfWar() { fogOfWar.toggleEnabled(); } @Override public AbstractConstructionMarkableMap getConstructionMarksGrid() { return constructionMarksGrid; } @Override public void constructBuildingAt(ShortPoint2D position, EBuildingType type, byte playerId) { if (constructionMarksGrid.canConstructAt(position.x, position.y, type, playerId)) { MainGrid.this.constructBuildingAt(position, type, partitionsGrid.getPlayerAt(position.x, position.y), false); } else { System.out.println("WARNING: TRIED TO CONSTRUCT BUILDING WHERE IT WASN'T POSSIBLE! Type: " + type + " pos: " + position + " playerId: " + playerId); } } @Override public void positionClicked(int x, int y) { System.out.println("clicked pos (" + x + "|" + y + "): player: " + partitionsGrid.getPlayerIdAt(x, y) + " partition: " + partitionsGrid.getPartitionIdAt(x, y) + " real partition: " + partitionsGrid.getRealPartitionIdAt(x, y) + " towerCount: " + partitionsGrid.getTowerCountAt(x, y) + " blocked partition: " + landscapeGrid.getBlockedPartitionAt(x, y) + " landscapeType: " + landscapeGrid.getLandscapeTypeAt(x, y)); } @Override public void setMaterialDistributionSettings(ShortPoint2D managerPosition, EMaterialType materialType, float[] probabilities) { if (isInBounds(managerPosition)) partitionsGrid.getPartitionSettings(managerPosition).setMaterialDistributionSettings(materialType, probabilities); } @Override public void setMaterialPrioritiesSettings(ShortPoint2D managerPosition, EMaterialType[] materialTypeForPriority) { if (isInBounds(managerPosition)) partitionsGrid.getPartitionSettings(managerPosition).setMaterialPriorities(materialTypeForPriority); } @Override public short getBlockedPartition(int x, int y) { return landscapeGrid.getBlockedPartitionAt(x, y); } @Override public boolean isBlocked(int x, int y) { return flagsGrid.isBlocked(x, y); } @Override public Player getPlayer(byte playerId) { return partitionsGrid.getPlayer(playerId); } @Override public byte getNumberOfPlayers() { return partitionsGrid.getNumberOfPlayers(); } @Override public FogOfWar getFogOfWar() { return fogOfWar; } @Override public MaterialProductionSettings getMaterialProductionAt(ShortPoint2D position) { return getPartitionsGrid().getMaterialProductionAt(position.x, position.y); } @Override public void setAcceptedStockMaterial(ShortPoint2D position, EMaterialType materialType, boolean accepted) { partitionsGrid.getPartitionSettings(position).setAcceptedStockMaterial(materialType, accepted); } } /** * This class implements the {@link IPlayerChangedListener} interface and executes all work that needs to be done when a position of the grid * changes it's player. * * @author Andreas Eberle */ final class PlayerChangedListener implements IPlayerChangedListener { @Override public void playerChangedAt(int x, int y, byte newPlayerId) { final ShortPoint2D position = new ShortPoint2D(x, y); bordersThread.checkPosition(position); Building building = objectsGrid.getBuildingAt(x, y); if (building != null && building.getPlayerId() != newPlayerId) { building.kill(); } } } final class FogOfWarGrid implements IFogOfWarGrid { @Override public final IMovable getMovableAt(short x, short y) { return movableGrid.getMovableAt(x, y); } @Override public final IMapObject getMapObjectsAt(short x, short y) { return objectsGrid.getObjectsAt(x, y); } @Override public final ConcurrentLinkedQueue<? extends IViewDistancable> getMovableViewDistancables() { return Movable.getAllMovables(); } @Override public final ConcurrentLinkedQueue<? extends IViewDistancable> getBuildingViewDistancables() { return Building.getAllBuildings(); } } }