/** * Copyright (C) 2013 Gundog Studios LLC. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.godsandtowers.core.grid; import gnu.trove.map.hash.TObjectIntHashMap; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.TreeSet; import com.godsandtowers.sprites.BaseTower; import com.godsandtowers.sprites.BuildingSphere; import com.godsandtowers.sprites.Creature; import com.godsandtowers.sprites.DelayedProjectile; import com.godsandtowers.sprites.MovingProjectile; import com.godsandtowers.sprites.Projectile; import com.godsandtowers.sprites.Race; import com.godsandtowers.sprites.Special; import com.godsandtowers.sprites.Tower; import com.gundogstudios.gl.Sprite; import com.gundogstudios.modules.Modules; import com.gundogstudios.util.FastMath; public class Grid implements Externalizable { private static final int TIME_TO_DIE = 1000; private static final String TAG = "Grid"; private GridSquareComparator comparator; private GridSquare[][] grid; private HashSet<Creature> creatures; private HashSet<Tower> towers; private HashSet<Tower> buildingTowers; private HashMap<Tower, BuildingSphere> buildingSpheres; private HashSet<DelayedProjectile> delayedProjectiles; private HashSet<MovingProjectile> movingProjectiles; private HashSet<Special> specials; private TObjectIntHashMap<Sprite> dyingSprites; private ArrayList<GridSquare> startSquares; private ArrayList<GridSquare> allGridSquares; private HashSet<GridSquare> finishSquares; private int numRows; private int numColumns; private float xOffset; private float yOffset; public Grid() { } public Grid(Board board) { this.numRows = board.getRows(); this.numColumns = board.getColumns(); creatures = new HashSet<Creature>(); towers = new HashSet<Tower>(); buildingTowers = new HashSet<Tower>(); buildingSpheres = new HashMap<Tower, BuildingSphere>(); delayedProjectiles = new HashSet<DelayedProjectile>(); movingProjectiles = new HashSet<MovingProjectile>(); specials = new HashSet<Special>(); grid = new GridSquare[numRows][numColumns]; startSquares = new ArrayList<GridSquare>(); finishSquares = new HashSet<GridSquare>(); allGridSquares = new ArrayList<GridSquare>(); for (int row = 0; row < numRows; row++) { for (int col = 0; col < numColumns; col++) { Square square = board.getSquare(row, col); grid[row][col] = new GridSquare(square); if (square.isFinish()) { finishSquares.add(grid[row][col]); } else if (square.isStart()) { startSquares.add(grid[row][col]); } allGridSquares.add(grid[row][col]); } } init(); } private void init() { dyingSprites = new TObjectIntHashMap<Sprite>(); this.xOffset = ((float) numRows) / 2f;// * TILE_SIZE; this.yOffset = ((float) numColumns) / 2f;// * TILE_SIZE; comparator = new GridSquareComparator(numColumns); updateXY(); updateAirPaths(); updatePaths(); } /** * Used for debugging purposes. Checks to make sure there is consistency in the board's structures */ public boolean checkState() { boolean error = false; int creatureCount = 0; int towerCount = 0; HashSet<Creature> gridCreatures = new HashSet<Creature>(); HashSet<Tower> gridTowers = new HashSet<Tower>(); for (int row = 0; row < numRows; row++) { for (int col = 0; col < numColumns; col++) { creatureCount += grid[row][col].getCreatures().size(); gridCreatures.addAll(grid[row][col].getCreatures()); Tower tower = grid[row][col].getTower(); if (tower != null) { towerCount++; gridTowers.add(tower); } } } if (creatureCount != gridCreatures.size()) { Modules.LOG.error(TAG, "Creature is in more than one gridsquare at once " + creatureCount + " " + gridCreatures.size()); error = true; } if (gridCreatures.size() != creatures.size()) { Modules.LOG.error(TAG, "Grid squares contain different amount of creatures than the board " + gridCreatures.size() + " " + creatures.size()); error = true; } gridCreatures.removeAll(creatures); if (gridCreatures.size() != 0) { for (Creature creature : gridCreatures) { Modules.LOG.error(TAG, "Extra creature " + creature); } error = true; } if (towerCount != gridTowers.size()) { Modules.LOG.error(TAG, "Tower is in more than one gridsquares at once " + towerCount + " " + gridTowers.size()); error = true; } if (gridTowers.size() != towers.size()) { Modules.LOG.error(TAG, "Grid squares contain different amount of towers than the board " + gridTowers.size() + " " + towers.size()); error = true; } gridTowers.removeAll(towers); if (gridTowers.size() != 0) { for (Tower tower : gridTowers) { Modules.LOG.error(TAG, "Extra tower " + tower); } error = true; } boolean canPlaceTower = false; for (int row = 0; row < numRows; row++) { for (int col = 0; col < numColumns; col++) { if (grid[row][col].canPlaceTower()) canPlaceTower = true; } } if (!canPlaceTower) Modules.LOG.error(TAG, "CANNOT PLACE ANYMORE TOWERS"); if (error) printGridCosts(); return error; } public GridSquare getGridSquare(int row, int column) { return grid[row][column]; } public int convertToRow(float y) { return FastMath.floor(xOffset + y); } public int convertToColumn(float x) { return FastMath.floor(yOffset + x); } public float convertToY(int row) { return row + .5f - xOffset; } public float convertToX(int column) { return column + .5f - yOffset; } public void addSpecial(Special special) { dyingSprites.put(special, special.getTimeToLive()); } public ArrayList<Sprite> updateDyingSprites(int timePassed) { ArrayList<Sprite> remove = new ArrayList<Sprite>(); for (Sprite dying : dyingSprites.keySet()) { int timeLeft = dyingSprites.get(dying) - timePassed; if (timeLeft <= 0) { remove.add(dying); continue; } if (dying instanceof Creature) { float z = dying.getZ(); if (z > 0f) { z -= .125f; z = (z > 0) ? z : 0; } ((Creature) dying).setZ(z); } dyingSprites.put(dying, timeLeft); } for (Sprite dying : remove) dyingSprites.remove(dying); return remove; } public boolean removeTower(Tower tower) { int row = convertToRow(tower.getY()); int column = convertToColumn(tower.getX()); GridSquare square = grid[row][column]; if (square.containsTower()) { square.removeTower(); towers.remove(tower); updatePaths(); return true; } return false; } private void applySquareBonus(GridSquare square, Tower tower) { BaseTower baseTower = tower.getBaseTower(); if (square.getModel() == Square.ATTACK_RATE_BONUS) { baseTower = new BaseTower(baseTower); baseTower.setAttackRate(baseTower.getAttackRate() * .75f); } else if (square.getModel() == Square.DAMAGE_BONUS) { baseTower = new BaseTower(baseTower); baseTower.setDamage(baseTower.getDamage() * 2f); } else if (square.getModel() == Square.RANGE_BONUS) { baseTower = new BaseTower(baseTower); baseTower.setAttackRange(baseTower.getAttackRange() + 1f); } tower.replace(baseTower); } public Tower replaceTower(Tower oldTower, BaseTower baseTower) { int row = convertToRow(oldTower.getY()); int column = convertToColumn(oldTower.getX()); Tower newTower = new Tower(baseTower, oldTower.getRace().getBaseRace(), oldTower.getX(), oldTower.getY()); GridSquare square = grid[row][column]; if (square.containsTower()) { square.removeTower(); towers.remove(oldTower); applySquareBonus(square, newTower); square.place(newTower); towers.add(newTower); return newTower; } else { return null; } } public BuildingSphere addBuildingTower(Tower tower) { int row = convertToRow(tower.getY()); int column = convertToColumn(tower.getX()); if (invalid(row, column)) { return null; } GridSquare square = grid[row][column]; if (!square.isValidForTowers() || square.containsTower()) { return null; } applySquareBonus(square, tower); buildingTowers.add(tower); BuildingSphere sphere = new BuildingSphere(tower); buildingSpheres.put(tower, sphere); return sphere; } public Tower removeBuildingTower(int row, int column) { float x = convertToX(column); float y = convertToY(row); Tower found = null; for (Tower other : buildingTowers) { if (other.getX() == x && other.getY() == y) { found = other; break; } } if (found != null) { buildingTowers.remove(found); return found; } return null; } public BuildingSphere removeBuildingSphere(Tower tower) { return buildingSpheres.remove(tower); } public BuildingSphere addBuildingSphere(Tower tower) { BuildingSphere buildingSphere = new BuildingSphere(tower); buildingSpheres.put(tower, buildingSphere); return buildingSphere; } public void cancelBuild() { buildingTowers.clear(); buildingSpheres.clear(); } public ArrayList<Tower> buildAll() { ArrayList<Tower> failed = new ArrayList<Tower>(); for (Tower tower : buildingTowers) { int row = convertToRow(tower.getY()); int column = convertToColumn(tower.getX()); GridSquare square = grid[row][column]; if (!square.canPlaceTower()) { failed.add(tower); continue; } square.place(tower); updatePaths(); if (!checkSquares()) { square.removeTower(); updatePaths(); failed.add(tower); continue; } else { towers.add(tower); } } buildingTowers.clear(); buildingSpheres.clear(); return failed; } public boolean addTower(Tower tower) { int row = convertToRow(tower.getY()); int column = convertToColumn(tower.getX()); if (invalid(row, column)) { return false; } GridSquare square = grid[row][column]; if (!square.canPlaceTower()) { return false; } applySquareBonus(square, tower); square.place(tower); updatePaths(); if (!checkSquares()) { square.removeTower(); updatePaths(); return false; } else { towers.add(tower); } return true; } public void addCreature(Creature creature) { int loc = FastMath.floor(startSquares.size() * FastMath.random()); GridSquare square = startSquares.get(loc); creature.setX(square.getX()); creature.setY(square.getY()); if (creature.isAir()) creature.setZ(3f); else creature.setZ(0); square.place(creature); creature.setGridSquare(square); creatures.add(creature); } private void resetCosts() { for (int row = 0; row < numRows; row++) { for (int col = 0; col < numColumns; col++) { grid[row][col].setCost(Integer.MAX_VALUE); } } for (GridSquare finish : finishSquares) finish.setCost(0); } private void updateXY() { for (int row = 0; row < numRows; row++) { for (int column = 0; column < numColumns; column++) { GridSquare square = grid[row][column]; float x = convertToX(square.getColumn()); square.setX(x); float y = convertToY(square.getRow()); square.setY(y); } } } public boolean checkSquares() { for (GridSquare start : startSquares) { if (start.getCost() == Integer.MAX_VALUE) { return false; } } for (int row = 0; row < numRows; row++) { for (int col = 0; col < numColumns; col++) { if (grid[row][col].containsWalker() && grid[row][col].getCost() == Integer.MAX_VALUE) { return false; } } } return true; } /** * Tries to build on the squares with the "lowest movement cost" to the finish. Skips squares that cause an invalid * path */ public ArrayList<GridSquare> calculateBuildSquares() { ArrayList<GridSquare> buildOrder = new ArrayList<GridSquare>(); TreeSet<GridSquare> squares = new TreeSet<GridSquare>(); ArrayList<GridSquare> invalid = new ArrayList<GridSquare>(); Tower fakeTower = new Tower(); for (int row = 0; row < numRows; row++) { for (int col = 0; col < numColumns; col++) { if (grid[row][col].canPlaceTower()) squares.add(grid[row][col]); } } boolean keepGoing; do { keepGoing = false; // Find the next valid location to place a tower for (GridSquare gridSquare : squares) { if (!canBuildTower(gridSquare, fakeTower)) { invalid.add(gridSquare); continue; } keepGoing = true; buildOrder.add(gridSquare); invalid.add(gridSquare); break; } // Remove all squares that cannot be used, and sort the set based on new costs TreeSet<GridSquare> newSquares = new TreeSet<GridSquare>(); for (GridSquare gridSquare : squares) { newSquares.add(gridSquare); } newSquares.removeAll(invalid); invalid.clear(); squares = newSquares; // printGridCosts(); } while (keepGoing); // Put the grid back in its original state for (int row = 0; row < numRows; row++) { for (int col = 0; col < numColumns; col++) { grid[row][col].removeTower(); } } updatePaths(); // for (GridSquare gridSquare : buildOrder) // System.out.println(gridSquare.getCost() + " " + gridSquare.getRow() + " " + gridSquare.getColumn()); return buildOrder; } private boolean canBuildTower(GridSquare square, Tower tower) { if (!square.canPlaceTower()) { return false; } square.place(tower); updatePaths(); if (!checkSquares()) { square.removeTower(); updatePaths(); return false; } return true; } private void printGridCosts() { for (int row = 0; row < numRows; row++) { for (int col = 0; col < numColumns; col++) { System.out.print(grid[row][col].getCost() + ","); } System.out.println(); } System.out.println(); } /** * Uses backwards propagation to determine the best route from and square to the finish */ private void updatePaths() { TreeSet<GridSquare> unprocessed = new TreeSet<GridSquare>(comparator); resetCosts(); for (GridSquare finish : finishSquares) unprocessed.add(finish); while (!unprocessed.isEmpty()) { GridSquare current = unprocessed.first(); unprocessed.remove(current); boolean UP = tryMove(current.getRow() - 1, current.getColumn(), 2, unprocessed, current); boolean DOWN = tryMove(current.getRow() + 1, current.getColumn(), 2, unprocessed, current); boolean LEFT = tryMove(current.getRow(), current.getColumn() - 1, 2, unprocessed, current); boolean RIGHT = tryMove(current.getRow(), current.getColumn() + 1, 2, unprocessed, current); if (UP && RIGHT) tryMove(current.getRow() - 1, current.getColumn() + 1, 3, unprocessed, current); if (UP && LEFT) tryMove(current.getRow() - 1, current.getColumn() - 1, 3, unprocessed, current); if (DOWN && RIGHT) tryMove(current.getRow() + 1, current.getColumn() + 1, 3, unprocessed, current); if (DOWN && LEFT) tryMove(current.getRow() + 1, current.getColumn() - 1, 3, unprocessed, current); } // For the case where a creature reaches the finish while Board is drawing and thinks it hasn't made it yet // not sure if this is needed anymore for (GridSquare finish : finishSquares) finish.setNextSquare(finish); } public boolean invalid(int row, int column) { return (row >= numRows || column >= numColumns || row < 0 || column < 0); } private boolean tryMove(int row, int col, int cost, TreeSet<GridSquare> unprocessed, GridSquare current) { if (invalid(row, col)) return false; GridSquare previous = grid[row][col]; if (!previous.canPlaceCreature()) return false; int newCost = current.getCost() + cost; if (newCost < previous.getCost()) { unprocessed.remove(previous); previous.setCost(newCost); previous.setNextSquare(current); unprocessed.add(previous); } return true; } private void updateAirPaths() { TreeSet<GridSquare> unprocessed = new TreeSet<GridSquare>(comparator); resetCosts(); for (GridSquare finish : finishSquares) unprocessed.add(finish); while (!unprocessed.isEmpty()) { GridSquare current = unprocessed.first(); unprocessed.remove(current); boolean UP = tryAirMove(current.getRow() - 1, current.getColumn(), 2, unprocessed, current); boolean DOWN = tryAirMove(current.getRow() + 1, current.getColumn(), 2, unprocessed, current); boolean LEFT = tryAirMove(current.getRow(), current.getColumn() - 1, 2, unprocessed, current); boolean RIGHT = tryAirMove(current.getRow(), current.getColumn() + 1, 2, unprocessed, current); if (UP && RIGHT) tryAirMove(current.getRow() - 1, current.getColumn() + 1, 3, unprocessed, current); if (UP && LEFT) tryAirMove(current.getRow() - 1, current.getColumn() - 1, 3, unprocessed, current); if (DOWN && RIGHT) tryAirMove(current.getRow() + 1, current.getColumn() + 1, 3, unprocessed, current); if (DOWN && LEFT) tryAirMove(current.getRow() + 1, current.getColumn() - 1, 3, unprocessed, current); } for (GridSquare finish : finishSquares) finish.setNextAirSquare(finish); } private boolean tryAirMove(int row, int col, int cost, TreeSet<GridSquare> unprocessed, GridSquare current) { if (invalid(row, col)) return false; GridSquare previous = grid[row][col]; int newCost = current.getCost() + cost; if (newCost < previous.getCost()) { unprocessed.remove(previous); previous.setCost(newCost); previous.setNextAirSquare(current); unprocessed.add(previous); } return true; } public ArrayList<Creature> moveProjectiles(int timePassed, ArrayList<MovingProjectile> uselessProjectiles) { ArrayList<Creature> creaturesKilled = new ArrayList<Creature>(); moveMovingProjectiles(creaturesKilled, timePassed, uselessProjectiles); moveDelayedProjectiles(creaturesKilled, timePassed); return creaturesKilled; } private void moveMovingProjectiles(ArrayList<Creature> creaturesKilled, int timePassed, ArrayList<MovingProjectile> uselessProjectiles) { ArrayList<MovingProjectile> hit = new ArrayList<MovingProjectile>(); for (MovingProjectile projectile : movingProjectiles) { if (!projectile.getTarget().isAlive()) { hit.add(projectile); uselessProjectiles.add(projectile); continue; } boolean hitTarget = projectile.move(timePassed); if (hitTarget) { processHit(projectile, creaturesKilled); hit.add(projectile); dyingSprites.put(projectile, TIME_TO_DIE); } } movingProjectiles.removeAll(hit); removeKilledCreatures(creaturesKilled); } private void moveDelayedProjectiles(ArrayList<Creature> creaturesKilled, int timePassed) { ArrayList<DelayedProjectile> hit = new ArrayList<DelayedProjectile>(); for (DelayedProjectile projectile : delayedProjectiles) { if (!projectile.getTarget().isAlive()) { hit.add(projectile); continue; } boolean hitTarget = projectile.move(timePassed); if (hitTarget) { processHit(projectile, creaturesKilled); hit.add(projectile); } } delayedProjectiles.removeAll(hit); removeKilledCreatures(creaturesKilled); } private void removeKilledCreatures(ArrayList<Creature> creaturesKilled) { for (Creature target : creaturesKilled) { removeKilledCreature(target); } } public void removeKilledCreature(Creature creature) { creature.getCurrentGridSquare().remove(creature); creatures.remove(creature); dyingSprites.put(creature, TIME_TO_DIE); } private void processHit(Projectile projectile, ArrayList<Creature> creaturesKilled) { Creature target = projectile.getTarget(); float row = target.getX(); float col = target.getY(); Race race = projectile.getRace(); if (race.isFire() && !projectile.attacksAll()) { float radius = race.getSplashRadius(); float splashDamage = projectile.getDamage() * race.getSplashStrengthFade(); splashNeighbors(creaturesKilled, row, col, splashDamage, radius, target.isAir()); } if (target.getRace().getCreatureEvadePercentage() > FastMath.random()) { // Creature dodged the attack } else if (target.subtractHealth(projectile.getDamage()) && !attemptResurrect(creaturesKilled, target)) { // Creature was killed and was not resurrected } else { target.setSlow(race.getSlowDuration(), race.getSlowFactor()); target.setStun(race.getStunDuration()); target.setDrain(race.getDrainDuration(), race.getDrainPercentage()); if (race.getKillPercentage() > FastMath.random()) { target.kill(); attemptResurrect(creaturesKilled, target); } } } private boolean attemptResurrect(ArrayList<Creature> creaturesKilled, Creature dead) { if (dead.getRace().getResurrectPercentage() > FastMath.random()) { dead.resurrect(); return true; } else { creaturesKilled.add(dead); return false; } } private void splashNeighbors(ArrayList<Creature> creaturesKilled, float startX, float startY, float damage, float radius, boolean air) { int range = FastMath.ceil(radius); int towerRow = convertToRow(startY); int towerColumn = convertToColumn(startX); for (int i = -range; i < range; i++) { for (int j = -range; j < range; j++) { int row = towerRow + j; int column = towerColumn + i; if (invalid(row, column)) { continue; } GridSquare square = grid[row][column]; for (Creature creature : square.getCreatures()) { if (creature.isAir() && !air) continue; if (!creature.isAir() && air) continue; float x = creature.getX(); float y = creature.getY(); float targetDistance = FastMath.sqrt(x * x + y * y); if (targetDistance > radius) continue; if (creature.subtractHealth(damage)) { attemptResurrect(creaturesKilled, creature); } } } } } public ArrayList<Creature> moveCreatures(int timePassed) { ArrayList<Creature> remove = new ArrayList<Creature>(); for (Creature creature : creatures) { creature.nextTick(timePassed); if (creature.isAttacking()) continue; GridSquare square = creature.getCurrentGridSquare(); float movePercentLeft = creature.move(timePassed); while (movePercentLeft > 0) { square.remove(creature); if (finishSquares.contains(square)) { remove.add(creature); creature.remove(); break; } if (creature.isAir()) { square = square.getNextAir(); } else { square = square.getNext(); } square.place(creature); creature.setGridSquare(square); movePercentLeft = creature.moveDistance(movePercentLeft); } } creatures.removeAll(remove); return remove; } public ArrayList<Tower> attackTowers(int timePassed) { ArrayList<Tower> destroyed = new ArrayList<Tower>(); for (Creature creature : creatures) { if (creature.getAttackingTarget() != null && !creature.getAttackingTarget().isAlive()) { creature.setTarget(null); } if (creature.canAttack(timePassed)) { Tower tower = attackTower(creature); if (tower != null) { destroyed.add(tower); } } } return destroyed; } private Tower attackTower(Creature creature) { Tower target = findTarget(creature); if (target != null) { creature.setTarget(target); creature.attacked(); if (target.subtractHealth(creature.getDamage())) { removeTower(target); dyingSprites.put(target, TIME_TO_DIE); return target; } } else { creature.setTarget(null); } return null; } private Tower findTarget(Creature creature) { Tower target = creature.getAttackingTarget(); if (target != null) { if (!target.isAlive()) { creature.setTarget(null); target = null; } else { float y = target.getY(); float x = target.getX(); float targetDistance = FastMath.sqrt(y * y + x * x); if (targetDistance > creature.getAttackRange()) { target = null; creature.setTarget(null); } else { return target; } } } int range = FastMath.ceil(creature.getAttackRange()); int creatureRow = convertToRow(creature.getY()); int creatureColumn = convertToColumn(creature.getX()); for (int i = -range; i < range; i++) { for (int j = -range; j < range; j++) { int row = creatureRow + j; int column = creatureColumn + i; if (invalid(row, column)) { continue; } GridSquare square = grid[row][column]; Tower tower = square.getTower(); if (tower != null) { float x = tower.getX() - creature.getX(); float y = tower.getY() - creature.getY(); float targetDistance = FastMath.sqrt(x * x + y * y); if (targetDistance > tower.getAttackRange()) continue; return tower; } } } return null; } public ArrayList<Creature> attackCreatures(int timePassed, ArrayList<Projectile> newProjectiles) { ArrayList<Creature> creaturesKilled = new ArrayList<Creature>(); for (Tower tower : towers) { if (tower.getTarget() != null && !tower.getTarget().isAlive()) { tower.setTarget(null); } if (tower.canFire(timePassed)) { if (tower.attacksAllInRange()) { attackAllCreatures(tower, creaturesKilled, newProjectiles); } else { attackOneCreature(tower, creaturesKilled, newProjectiles); } } } removeKilledCreatures(creaturesKilled); return creaturesKilled; } private void attackAllCreatures(Tower tower, ArrayList<Creature> creaturesKilled, ArrayList<Projectile> newProjectiles) { int range = FastMath.ceil(tower.getAttackRange()); int towerRow = convertToRow(tower.getY()); int towerColumn = convertToColumn(tower.getX()); for (int i = -range; i < range; i++) { for (int j = -range; j < range; j++) { int row = towerRow + j; int column = towerColumn + i; if (invalid(row, column)) { continue; } GridSquare square = grid[row][column]; for (Creature creature : square.getCreatures()) { if (creature.isAir() && tower.attacksOnlyGround()) continue; if (!creature.isAir() && tower.attacksOnlyAir()) continue; float x = tower.getX() - creature.getX(); float y = tower.getY() - creature.getY(); float targetDistance = FastMath.sqrt(x * x + y * y); if (targetDistance > tower.getAttackRange()) continue; if (tower.instantAttack()) { DelayedProjectile projectile = new DelayedProjectile(tower, creature); delayedProjectiles.add(projectile); } else { MovingProjectile projectile = new MovingProjectile(tower, creature); movingProjectiles.add(projectile); newProjectiles.add(projectile); } tower.shot(); tower.setTarget(creature); } } } } private void attackOneCreature(Tower tower, ArrayList<Creature> creaturesKilled, ArrayList<Projectile> newProjectiles) { Creature target = findTarget(tower); if (target != null) { tower.setTarget(target); if (tower.instantAttack()) { DelayedProjectile projectile = new DelayedProjectile(tower, target); delayedProjectiles.add(projectile); } else { MovingProjectile projectile = new MovingProjectile(tower, target); movingProjectiles.add(projectile); newProjectiles.add(projectile); } tower.shot(); } } private Creature findTarget(Tower tower) { Creature target = tower.getTarget(); if (target != null) { if (!target.isAlive()) { tower.setTarget(null); target = null; } else { float x = tower.getX() - target.getX(); float y = tower.getY() - target.getY(); float targetDistance = FastMath.sqrt(x * x + y * y); if (targetDistance > tower.getAttackRange()) { tower.setTarget(null); target = null; } else { return target; } } } int range = FastMath.ceil(tower.getAttackRange()); int towerRow = convertToRow(tower.getY()); int towerColumn = convertToColumn(tower.getX()); for (int i = -range; i < range; i++) { for (int j = -range; j < range; j++) { int row = towerRow + j; int column = towerColumn + i; if (invalid(row, column)) { continue; } GridSquare square = grid[row][column]; for (Creature creature : square.getCreatures()) { if (creature.isAir() && tower.attacksOnlyGround()) continue; if (!creature.isAir() && tower.attacksOnlyAir()) continue; float x = tower.getX() - creature.getX(); float y = tower.getY() - creature.getY(); float targetDistance = FastMath.sqrt(x * x + y * y); if (targetDistance > tower.getAttackRange()) continue; return creature; } } } return null; } public Collection<Creature> getCreatures() { return creatures; } public Collection<Tower> getTowers() { return towers; } public Collection<MovingProjectile> getMovingProjectiles() { return movingProjectiles; } public Collection<GridSquare> getAllGridSquares() { return allGridSquares; } public Collection<Tower> getBuildingTowers() { return buildingTowers; } public Collection<BuildingSphere> getBuildingSpheres() { return buildingSpheres.values(); } public Collection<Sprite> getDyingSprites() { return dyingSprites.keySet(); } public Collection<Special> getSpecials() { return specials; } public int getNumColumns() { return numColumns; } public int getNumRows() { return numRows; } public boolean containsTower(int row, int col) { return grid[row][col].containsTower(); } public Tower getTower(int row, int col) { return grid[row][col].getTower(); } public Tower getTower(float x, float y) { return grid[convertToRow(y)][convertToColumn(x)].getTower(); } private static class GridSquareComparator implements Comparator<GridSquare> { private int columns; public GridSquareComparator(int numColumns) { this.columns = numColumns; } @Override public int compare(GridSquare o1, GridSquare o2) { if (o1 == o2) return 0; int diff = o1.getCost() - o2.getCost(); if (diff == 0) return (o1.getRow() * columns + o1.getColumn()) - (o2.getRow() * columns + o2.getColumn()); else return diff; } } @SuppressWarnings("unchecked") @Override public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException { numRows = input.readInt(); numColumns = input.readInt(); grid = (GridSquare[][]) input.readObject(); startSquares = (ArrayList<GridSquare>) input.readObject(); finishSquares = (HashSet<GridSquare>) input.readObject(); allGridSquares = (ArrayList<GridSquare>) input.readObject(); creatures = (HashSet<Creature>) input.readObject(); towers = (HashSet<Tower>) input.readObject(); buildingTowers = (HashSet<Tower>) input.readObject(); buildingSpheres = (HashMap<Tower, BuildingSphere>) input.readObject(); movingProjectiles = (HashSet<MovingProjectile>) input.readObject(); delayedProjectiles = (HashSet<DelayedProjectile>) input.readObject(); specials = (HashSet<Special>) input.readObject(); init(); } @Override public void writeExternal(ObjectOutput output) throws IOException { output.writeInt(numRows); output.writeInt(numColumns); output.writeObject(grid); output.writeObject(startSquares); output.writeObject(finishSquares); output.writeObject(allGridSquares); output.writeObject(creatures); output.writeObject(towers); output.writeObject(buildingTowers); output.writeObject(buildingSpheres); output.writeObject(movingProjectiles); output.writeObject(delayedProjectiles); output.writeObject(specials); } }