/* * Copyright (c) 2003-onwards Shaven Puppy Ltd * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'Shaven Puppy' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package worm.generator; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import net.puppygames.applet.Game; import net.puppygames.applet.GameInputStream; import net.puppygames.applet.GameOutputStream; import net.puppygames.applet.RoamingFile; import org.lwjgl.util.Point; import org.lwjgl.util.ReadablePoint; import worm.GameMap; import worm.IntGrid; import worm.Res; import worm.Tile; import worm.Worm; import worm.WormGameState; import worm.features.LevelFeature; import worm.path.AStar; import worm.path.Topology; import worm.tiles.Crystal; import worm.tiles.Exclude; import worm.tiles.Ruin; import worm.tiles.TotalExclude; import com.shavenpuppy.jglib.interpolators.LinearInterpolator; import com.shavenpuppy.jglib.util.IntList; import com.shavenpuppy.jglib.util.Util; /** * Base class for {@link MapGenerator}s */ abstract class AbstractMapGenerator implements MapGenerator, SimpleTiles { protected static final float ROADS_COMPLETED_PROGRESS = 0.5f; /* chaz hack - amount of random tile 2 */ private static final double RANDOM_ROCKY_THRESHOLD = 0.05; /** Bucket of common properties for generator classes */ protected final MapGeneratorParams mapGeneratorParams; /** The template; this provides tile sets for various features */ protected final MapTemplate template; /** The scenery */ protected final Scenery scenery; /** The level which we're going to make maps for */ protected final int level; /** The level-in-world which we're going to make maps for */ protected final int levelInWorld; /** The levelfeature itself */ protected final LevelFeature levelFeature; /** The spawnpoints */ private final List<Point> spawnPoints = new ArrayList<Point>(); /** The bases */ private final List<Point> bases = new ArrayList<Point>(); /** Ruins (map of Points to Ruins) */ private final Map<Point, Ruin> ruins = new HashMap<Point, Ruin>(); /** Ruins (map of Points to Crystals) */ private final Map<Point, Crystal> crystals = new HashMap<Point, Crystal>(); /** A much simplified version of the map */ private IntGrid map; /** Roads overlay */ protected IntGrid roadsOverlay; /** Topology used for pathfinding */ private IntGridTopology topology; private boolean test; /** Progress */ private float progress; /** Abort */ private boolean abort; /** Formation */ protected char[] formation; /** Duff attempts so far */ private int duff; /** * C'tor * @param template * @param level * @param levelInWorld * @param levelFeature */ public AbstractMapGenerator(MapTemplate template, MapGeneratorParams mapGeneratorParams) { this.mapGeneratorParams = mapGeneratorParams; this.template = template; this.level = mapGeneratorParams.getLevel(); this.levelInWorld = mapGeneratorParams.getLevelInWorld(); this.levelFeature = mapGeneratorParams.getLevelFeature(); this.scenery = levelFeature.getScenery(); if (mapGeneratorParams.getGameMode() != WormGameState.GAME_MODE_SURVIVAL && mapGeneratorParams.getGameMode() != WormGameState.GAME_MODE_XMAS) { Util.setSeed(getSeed()); } } @Override public void finish() { abort = true; } /** * @return the map */ protected final IntGrid getMap() { return map; } /** * @param test the test to set */ public void setTest(boolean test) { this.test = test; } /** * @return the level */ public final int getLevel() { return level; } /** * @return the levelInWorld */ public final int getLevelInWorld() { return levelInWorld; } /** * @return the template */ public final MapTemplate getTemplate() { return template; } /** * @return the map width */ protected int getWidth() { return levelFeature.getWidth(); } /** * @return the map height */ protected int getHeight() { return levelFeature.getHeight(); } /** * @return the roads overlay */ public final IntGrid getRoadsOverlay() { return roadsOverlay; } /** * Update the progress * @param newProgress * @throws GenerationAbortedException if the generator has been aborted */ protected final void setProgress(float newProgress) throws GenerationAbortedException { this.progress = newProgress; if (abort) { throw new GenerationAbortedException(); } Thread.yield(); } protected final long getSeed() { String levelName = levelFeature.getTitle(); int gameMode = mapGeneratorParams.getGameMode(); duff = Worm.getExtraLevelData(Game.getPlayerSlot(), level, gameMode, "duff_" + levelName, 0); long unique = Game.getPlayerSlot().getPreferences().getLong("unique_"+gameMode, 0L); if (unique == 0L) { unique = new Random().nextLong(); Game.getPlayerSlot().getPreferences().putLong("unique_"+gameMode, unique); Game.flushPrefs(); } long seed = ((Game.getPlayerSlot().getName().hashCode() + duff + WormGameState.getDifficultyAdjust(level, gameMode))) | (((long) Float.floatToRawIntBits(mapGeneratorParams.getBasicDifficulty())) << 32L); seed ^= mapGeneratorParams.getResearchHash(); seed ^= unique; return seed; } private String getFileName() { String levelName = levelFeature.getTitle(); int gameMode = mapGeneratorParams.getGameMode(); long seed = getSeed(); return Game.getPlayerDirectoryPrefix()+levelName.replace(' ', '_')+"_"+gameMode+"_"+Long.toHexString(seed)+".dat"; } private boolean exists() { return level != -1 && new RoamingFile(getFileName()).exists(); } /** * Load the map based on seed * @throws IOException */ private GameMap load() throws IOException { if (!exists()) { throw new IOException(getFileName()+" does not exist"); } GameInputStream gis = null; ObjectInputStream ois = null; GameMap ret = null; try { gis = new GameInputStream(getFileName()); ois = new ObjectInputStream(gis); ret = (GameMap) ois.readObject(); return ret; } catch (Exception e) { throw new IOException("Couldn't load map due to "+e); } finally { try { if (gis != null) { gis.close(); } } catch (IOException e) { } } } private void save(GameMap gameMap) throws IOException { GameOutputStream gos = null; ObjectOutputStream oos = null; try { gos = new GameOutputStream(getFileName()); oos = new ObjectOutputStream(gos); oos.writeObject(gameMap); oos.flush(); } finally { try { if (gos != null) { gos.close(); } } catch (IOException e) { } } } @Override public GameMap generate() { try { if (exists()) { try { return load(); } catch (IOException e) { e.printStackTrace(System.err); } } boolean valid = false; while (!valid) { map = null; map = new IntGrid(getWidth(), getHeight(), SimpleTiles.EMPTY); roadsOverlay = new IntGrid(getWidth(), getHeight(), 0); spawnPoints.clear(); bases.clear(); ruins.clear(); crystals.clear(); topology = new IntGridTopology(map); // Alien formations. Let's randomize the order formation = levelFeature.getFormation().toCharArray(); for (int i = 0; i < formation.length; i ++) { int idx = Util.random(0, formation.length - 1); char c = formation[i]; formation[i] = formation[idx]; formation[idx] = c; } setProgress(0.0f); generateBases(); setProgress(0.05f); generateAreas(); setProgress(0.1f); clean(); setProgress(0.2f); generateRuins(); setProgress(0.35f); generateSpawnPoints(); setProgress(0.45f); checkRuins(); setProgress(0.47f); generateRoads(); setProgress(ROADS_COMPLETED_PROGRESS); generateObstacles(); setProgress(0.6f); valid = isValid(); if (!isValid()) { duff ++; Worm.setExtraLevelData(level, Worm.getGameState().getGameMode(), "duff_"+levelFeature.getTitle(), duff); } } if (test) { prebuild(new GameMap(map.getWidth(), map.getHeight(), template.getFill())); dump(); return null; } else { return build(); } } catch (GenerationAbortedException e) { return null; } } /** * @return the progress of the map g eneration */ @Override public float getProgress() { return progress; } /** * Access the map * @param x * @param y * @return */ protected final int getValue(int x, int y) { return map.getValue(x, y); } /** * Access the map * @param x * @param y * @param newValue */ protected final void setValue(int x, int y, int newValue) { map.setValue(x, y, newValue); } /** * @return the bases */ public List<Point> getBases() { return Collections.unmodifiableList(bases); } /** * @return the spawn points */ public List<Point> getSpawnPoints() { return Collections.unmodifiableList(spawnPoints); } /** * Calculates the distance to the closest base from the specified coordinates * @param x * @param y * @return distance, ≥ 0.0f */ protected final float getDistanceToClosestBase(int x, int y) { ReadablePoint p = getClosestBase(x, y); float dx = x - p.getX(); float dy = y - p.getY(); return (float) Math.sqrt(dx * dx + dy * dy); } protected ReadablePoint getClosestBase(int x, int y) { Point nearestPoint = null; float nearest = Float.MAX_VALUE; for (int i = 0; i < bases.size(); i ++) { Point p = bases.get(i); float dx = x - p.getX(); float dy = y - p.getY(); float dist = (float) Math.sqrt(dx * dx + dy * dy); if (dist < nearest) { nearest = dist; nearestPoint = p; } } return new Point(nearestPoint); } /** * Specifies the largely passable or impassable terrain areas, either by carving holes in solid maps, or adding * walls in empty maps. After this method is called, no map tile should be EMPTY. */ protected abstract void generateAreas(); /** * Places a number of bases at random locations around the map. Use {@link #addBase(int, int)} */ protected abstract void generateBases(); /** * Places a number of alien spawn points at random locations around the map. Use {@link #addSpawnPoint(int, int)} */ protected abstract void generateSpawnPoints(); /** * Generates a random road network, and joins bases together with roads. */ protected abstract void generateRoads(); /** * Place a few random obstacles around on the map */ protected abstract void generateObstacles(); /** * Place some ruins on the map */ protected abstract void generateRuins(); /** * Place some crystals on the map */ protected abstract void generateCrystals(); /** * Adds a base at the specified location * @param x * @param y */ protected final void addBase(int x, int y) { assert x >= 0 && y >= 0 && x < getWidth() - 2 && y < getHeight() - 2 : "Attempt to add base at "+x+","+y+" (w x h ="+getWidth()+"x"+getHeight()+")"; for (int yy = 0; yy < 3; yy ++) { for (int xx = 0; xx < 4; xx ++) { map.setValue(x + xx, y + yy, BASE); } } //System.out.println("Base added at "+x+","+y); bases.add(new Point(x, y)); } /** * Add a ruin * @param x * @param y * @param w * @param h */ protected final void addRuin(int x, int y, int w, int h) { // Pick a ruin from the template at this point Ruin ruin = scenery.getRuin(w, h); if (ruin == null) { return; } // chaz hack - added bThruMap stuff... //System.out.println("Ruin:"+ruin.getName()+" "+w+"x"+h+" added at "+x+","+y); String bThruMap = ruin.getBThruMap(); //if (bThruMap!=null) System.out.println(" bThruMap:"+bThruMap); for (int yy = 0; yy < h; yy ++) { for (int xx = 0; xx < w; xx ++) { //assert map.getValue(x + xx, y + yy) == FLOOR : "Argh! Map is "+map.getValue(x + xx, y + yy)+" not FLOOR!"; int val; if (bThruMap != null) { val = bThruMap.charAt(xx + yy * w) == '0' ? (ruin.getRoads() ? IMPASSABLE : SPECIAL_IMPASSABLE) : TOTAL_IMPASSABLE; } else { val = ruin.isBulletThrough() ? (ruin.getRoads() ? IMPASSABLE : SPECIAL_IMPASSABLE) : TOTAL_IMPASSABLE; } map.setValue(x + xx, y + yy, val); //System.out.println(" add impassable: "+(x + xx)+", "+(y + yy)+": "+val); } } map.setValue(x, y, ruin.getRoads() || bThruMap != null && bThruMap.charAt(0) == '0' || ruin.isBulletThrough() ? RUIN : RUIN_IMPASSABLE); ruins.put(new Point(x, y), ruin); } /** * Checks each ruin is still "intact" on the map. If not, the ruin is removed. */ private void checkRuins() { for (Iterator<Map.Entry<Point, Ruin>> i = ruins.entrySet().iterator(); i.hasNext(); ) { Map.Entry<Point, Ruin> entry = i.next(); ReadablePoint p = entry.getKey(); Ruin r = entry.getValue(); boolean ok = true; outer: for (int x = 0; x < r.getWidth(); x ++) { for (int y = 0; y < r.getHeight(); y ++) { int f = map.getValue(x + p.getX(), y + p.getY()); if (f == FLOOR) { // Ruin's been damaged. Remove it completely ok = false; break outer; } } } if (!ok) { for (int x = 0; x < r.getWidth(); x ++) { for (int y = 0; y < r.getHeight(); y ++) { map.setValue(x + p.getX(), y + p.getY(), FLOOR); } } i.remove(); } } } /** * Add a crystal * @param x * @param y * @param w * @param h */ protected final boolean addCrystal(int x, int y, int size) { // Pick a crystal from the template at this point Crystal crystal = scenery.getCrystal(size); if (crystal == null) { return false; } int w = crystal.getWidth(); int h = crystal.getHeight(); for (int yy = 0; yy < h; yy ++) { for (int xx = 0; xx < w; xx ++) { assert map.getValue(x + xx, y + yy) == FLOOR; map.setValue(x + xx, y + yy, TOTAL_IMPASSABLE); } } map.setValue(x, y, CRYSTAL); crystals.put(new Point(x, y), crystal); if (Game.DEBUG) { System.out.println("Crystal:"+size+" added at "+x+","+y); } return true; } /** * @return the ruins: an unmodifiable map of Points to Dimensions */ public final Map<Point, Ruin> getRuins() { return Collections.unmodifiableMap(ruins); } /** * @return the crystals: an unmodifiable map of Points to Dimensions */ public final Map<Point, Crystal> getCrystals() { return Collections.unmodifiableMap(crystals); } /** * Adds a spawn point at the specified location * @param x * @param y */ protected final void addSpawnPoint(int x, int y) { if (levelFeature.useFixedSpawnPoints()) { assert map.getValue(x, y) == FLOOR : "Can't put spawnpoint down at "+x+","+y+": "+map.getValue(x, y); map.setValue(x, y, SPAWN); spawnPoints.add(new Point(x, y)); } } /** * Ensure that each base is accessible by at least one spawnpoint * @return true if the map is valid, false if not */ private boolean isValid() { if (spawnPoints.size() == 0 && levelFeature.useFixedSpawnPoints()) { System.out.println("NO SPAWNPOINTS!"); return false; } IntList path = new IntList(true, getWidth() + getHeight()); int[] steps = new int[1]; SolidCheck check = new SolidCheck() { @Override public boolean isSolid(int x, int y, int v) { return x < 0 || y < 0 || x >= getWidth() || y >= getHeight() || v == EMPTY || v == WALL || v == WATER || v == IMPASSABLE || v == RUIN_IMPASSABLE || v == TOTAL_IMPASSABLE || v == SPECIAL_IMPASSABLE || v == CRYSTAL || v == RUIN; } }; outer: for (Iterator<Point> i = spawnPoints.iterator(); i.hasNext(); ) { Point spawnP = i.next(); if (getValue(spawnP.getX(), spawnP.getY()) != SPAWN) { continue; } for (Iterator<Point> j = bases.iterator(); j.hasNext(); ) { Point baseP = j.next(); if (getValue(baseP.getX(), baseP.getY()) != BASE) { continue; } assert !spawnP.equals(baseP); if (findPath(spawnP.getX(), spawnP.getY(), baseP.getX(), baseP.getY(), path, check, steps)) { // // Stash in route cache // Gidrah.addCachedRoute(spawnP.getX(), spawnP.getY(), baseP.getX(), baseP.getY(), path); continue outer; } } // We couldn't find a route between this spawn point and any bases. if (Game.DEBUG) { System.out.println("No route between "+spawnP+" and any base. Here's the duff map:"); dump(); } // Gidrah.clearCachedRoutes(null); return false; } return true; } protected void dump() { for (int y = getHeight(); --y >= 0;) { if (y < 10) { System.out.print(" "+y+" "); } else if (y < 100) { System.out.print(" "+y+" "); } else { System.out.print(y+" "); } for (int x = 0; x < getWidth(); x ++) { switch (getValue(x, y)) { case FLOOR: System.out.print('.'); break; case WALL: System.out.print('#'); break; case INTERNAL: System.out.print('$'); break; case SPAWN: System.out.print('+'); break; case OBSTACLE: System.out.print('%'); break; case CRYSTAL: System.out.print('^'); break; case IMPASSABLE: System.out.print('*'); break; case BASE: System.out.print('@'); break; case SPECIAL: case SPECIAL_IMPASSABLE: System.out.print('%'); break; case TOTAL_IMPASSABLE: System.out.print('$'); break; case EMPTY: System.out.print(' '); break; case WALLEDGE: System.out.print('X'); break; case WATER: System.out.print('~'); break; case RUIN: case RUIN_IMPASSABLE: System.out.print('H'); break; case DEEP: System.out.print(' '); break; default: assert false : getValue(x, y); break; } } System.out.println(); } System.out.print(" "); for (int x = 0; x < getWidth(); x ++) { System.out.print(x % 10); } System.out.println(); // for (Iterator i = spawnPoints.iterator(); i.hasNext(); ) { // System.out.println("Spawnpoint "+i.next()); // } } /** * Can we plot an unobstructed path between two points? */ protected final boolean findPath(int sx, int sy, int tx, int ty, IntList path, Topology topology, int[] steps) { AStar astar = new AStar(topology); path.clear(); astar.findPath(IntGridTopology.pack(sx, sy), IntGridTopology.pack(tx, ty), path); int result;//, count = 0; while ((result = astar.nextStep()) == AStar.SEARCH_STATE_SEARCHING) { // Do nothing... // System.out.println("Search steps..."+(++count)); } // if (result == AStar.SEARCH_STATE_FAILED) { // System.out.println("Result for "+sx+", "+sy+"->"+tx+", "+ty+":"+result); // dump(); // } // System.out.println("Path found in "+astar.getNumSteps()); steps[0] = astar.getNumSteps(); // Clean up nodes astar.cancel(); return result == AStar.SEARCH_STATE_SUCCEEDED; } /** * Can we plot an unobstructed path between two points? */ protected final boolean findPath(int sx, int sy, int tx, int ty, IntList path, SolidCheck check, int[] steps) { topology.setCheck(check); return findPath(sx, sy, tx, ty, path, topology, steps); } /** * Perform any cleanup after bases and spawnpoints and areas have been generated */ protected void clean() { } private void prebuild(GameMap ret) { for (int y = 0; y < map.getHeight(); y ++) { for (int x = 0; x < map.getWidth(); x ++) { int currValue = map.getValue(x, y); if (currValue == WALL) { ret.setFade(x, y, 1); } } } for (int depth = 1; depth < 10; depth ++) { for (int y = 0; y < map.getHeight(); y ++) { for (int x = 0; x < map.getWidth(); x ++) { int currValue = ret.getFade(x, y); if (currValue == depth) { int neighbours = (ret.getFade(x, y + 1) >= depth || y == map.getHeight() - 1 ? 1 : 0) + (ret.getFade(x, y - 1) >= depth || y == 0 ? 1 : 0) + (ret.getFade(x + 1, y) >= depth || x == map.getWidth() - 1 ? 1 : 0) + (ret.getFade(x - 1, y) >= depth || x == 0 ? 1 : 0) + (ret.getFade(x + 1, y + 1) >= depth || x == map.getWidth() - 1 || y == map.getHeight() - 1 ? 1 : 0) + (ret.getFade(x + 1, y - 1) >= depth || x == map.getWidth() - 1 || y == 0 ? 1 : 0) + (ret.getFade(x - 1, y + 1) >= depth || x == 0 || y == map.getHeight() - 1 ? 1 : 0) + (ret.getFade(x - 1, y - 1) >= depth || x == 0 || y == 0 ? 1 : 0) ; if (neighbours == 8) { ret.setFade(x, y, depth + 1); } } } } } // First process the wall edges for (int y = 0; y < map.getHeight(); y ++) { for (int x = 0; x < map.getWidth(); x ++) { int currValue = map.getValue(x, y); if (currValue == WALL) { // If we've got any non-WALL neighbours we'll become WALLEDGE. int neighbours = (map.getValue(x, y + 1) == WALL || map.getValue(x, y + 1) == EMPTY || map.getValue(x, y + 1) == WALLEDGE ? 1 : 0) + (map.getValue(x, y - 1) == WALL || map.getValue(x, y - 1) == EMPTY || map.getValue(x, y - 1) == WALLEDGE ? 1 : 0) + (map.getValue(x + 1, y) == WALL || map.getValue(x + 1, y) == EMPTY || map.getValue(x + 1, y) == WALLEDGE ? 1 : 0) + (map.getValue(x - 1, y) == WALL || map.getValue(x - 1, y) == EMPTY || map.getValue(x - 1, y) == WALLEDGE ? 1 : 0) + (map.getValue(x + 1, y + 1) == WALL || map.getValue(x + 1, y + 1) == EMPTY || map.getValue(x + 1, y + 1) == WALLEDGE ? 1 : 0) + (map.getValue(x + 1, y - 1) == WALL || map.getValue(x + 1, y - 1) == EMPTY || map.getValue(x + 1, y - 1) == WALLEDGE ? 1 : 0) + (map.getValue(x - 1, y + 1) == WALL || map.getValue(x - 1, y + 1) == EMPTY || map.getValue(x - 1, y + 1) == WALLEDGE ? 1 : 0) + (map.getValue(x - 1, y - 1) == WALL || map.getValue(x - 1, y - 1) == EMPTY || map.getValue(x - 1, y - 1) == WALLEDGE ? 1 : 0) ; if (neighbours != 8) { map.setValue(x, y, WALLEDGE); } } } Thread.yield(); } setProgress(0.65f); // Second process the internal walls. Any wall which has 9 WALL or INTERNAL entries itself becomes INTERNAL. for (int y = 0; y < map.getHeight(); y ++) { for (int x = 0; x < map.getWidth(); x ++) { int currValue = map.getValue(x, y); if (currValue == WALL) { int neighbours = (map.getValue(x, y + 1) == WALL || map.getValue(x, y + 1) == EMPTY || map.getValue(x, y + 1) == INTERNAL ? 1 : 0) + (map.getValue(x, y - 1) == WALL || map.getValue(x, y - 1) == EMPTY || map.getValue(x, y - 1) == INTERNAL ? 1 : 0) + (map.getValue(x + 1, y) == WALL || map.getValue(x + 1, y) == EMPTY || map.getValue(x + 1, y) == INTERNAL ? 1 : 0) + (map.getValue(x - 1, y) == WALL || map.getValue(x - 1, y) == EMPTY || map.getValue(x - 1, y) == INTERNAL ? 1 : 0) + (map.getValue(x + 1, y + 1) == WALL || map.getValue(x + 1, y + 1) == EMPTY || map.getValue(x + 1, y + 1) == INTERNAL ? 1 : 0) + (map.getValue(x + 1, y - 1) == WALL || map.getValue(x + 1, y - 1) == EMPTY || map.getValue(x + 1, y - 1) == INTERNAL ? 1 : 0) + (map.getValue(x - 1, y + 1) == WALL || map.getValue(x - 1, y + 1) == EMPTY || map.getValue(x - 1, y + 1) == INTERNAL ? 1 : 0) + (map.getValue(x - 1, y - 1) == WALL || map.getValue(x - 1, y - 1) == EMPTY || map.getValue(x - 1, y - 1) == INTERNAL ? 1 : 0) ; if (neighbours == 8) { map.setValue(x, y, INTERNAL); } } } Thread.yield(); } setProgress(0.7f); // Now any WALLEDGE that doesn't have at least one WALL or EMPTY neighbour gets turned to FLOOR. for (int y = 0; y < map.getHeight(); y ++) { for (int x = 0; x < map.getWidth(); x ++) { int currValue = map.getValue(x, y); if (currValue == WALLEDGE) { int neighbours = (map.getValue(x, y + 1) == WALL ? 1 : 0) + (map.getValue(x, y - 1) == WALL ? 1 : 0) + (map.getValue(x + 1, y) == WALL ? 1 : 0) + (map.getValue(x - 1, y) == WALL ? 1 : 0) + (map.getValue(x + 1, y + 1) == WALL ? 1 : 0) + (map.getValue(x + 1, y - 1) == WALL ? 1 : 0) + (map.getValue(x - 1, y + 1) == WALL ? 1 : 0) + (map.getValue(x - 1, y - 1) == WALL ? 1 : 0) ; if (neighbours == 0) { map.setValue(x, y, FLOOR); } } } Thread.yield(); } // Any road at the edge of the map that doesn't have a neighbour just inside the edge gets removed // North & south edges for (int x = 0; x < map.getWidth(); x ++) { if (roadsOverlay.getValue(x, 0) == 1 && roadsOverlay.getValue(x, 1) == 0) { roadsOverlay.setValue(x, 0, 0); } if (roadsOverlay.getValue(x, getHeight() - 1) == 1 && roadsOverlay.getValue(x, getHeight() - 2) == 0) { roadsOverlay.setValue(x, getHeight() - 1, 0); } } // East & west edges for (int y = 0; y < map.getHeight(); y ++) { if (roadsOverlay.getValue(0, y) == 1 && roadsOverlay.getValue(1, y) == 0) { roadsOverlay.setValue(0, y, 0); } if (roadsOverlay.getValue(getWidth() - 1, y) == 1 && roadsOverlay.getValue(getWidth() - 2, y) == 0) { roadsOverlay.setValue(getWidth() - 1, y, 0); } } setProgress(0.75f); } /** * Take the generated map and draw tiles using the template, returning a GameMap. * @return a {@link GameMap} */ protected GameMap build() { GameMap ret = new GameMap(map.getWidth(), map.getHeight(), template.getFill()); if (Game.DEBUG) { System.out.println("Width:"+map.getWidth()+" x Height:"+map.getHeight()); } prebuild(ret); int currentFormation = 0; // Noise to do the 2 kinds of floor terrain with IntGrid floorGrid = new IntGrid(map.getWidth(), map.getHeight(), 0); PerlinNoise noise = new PerlinNoise(Util.random(0, Integer.MAX_VALUE - 1), 6, 3, 0.5f); float threshold = LinearInterpolator.instance.interpolate(0.9f, 0.5f, levelInWorld / 10.0f); for (int y = 0; y < map.getHeight(); y ++) { for (int x = 0; x < map.getWidth(); x ++) { floorGrid.setValue(x, y, noise.perlinNoise(x, y) > threshold ? 1 : 0); } Thread.yield(); } setProgress(0.8f); // Don't use floor1 near ruins or wall edges or roads... or inside internal rock stuff! for (int y = 0; y < map.getHeight(); y ++) { for (int x = 0; x < map.getWidth(); x ++) { int currValue = map.getValue(x, y); switch (currValue) { case WALLEDGE: case WALL: case INTERNAL: case RUIN: case CRYSTAL: case IMPASSABLE: case TOTAL_IMPASSABLE: case RUIN_IMPASSABLE: case SPECIAL_IMPASSABLE: case BASE: for (int yy = y - 2; yy <= y + 2; yy ++) { for (int xx = x - 2; xx <= x + 2; xx ++) { int dx = xx - x; int dy = yy - y; if (Math.sqrt(dx * dx + dy * dy) <= 2.0) { floorGrid.setValue(xx, yy, 0); } } } break; default: break; } if (roadsOverlay.getValue(x, y) != 0) { for (int yy = y - 2; yy <= y + 2; yy ++) { for (int xx = x - 2; xx <= x + 2; xx ++) { int dx = xx - x; int dy = yy - y; if (Math.sqrt(dx * dx + dy * dy) <= 2.0) { floorGrid.setValue(xx, yy, 0); } } } } } Thread.yield(); } setProgress(0.85f); generateCrystals(); dump(); double randomRockyThreshold = scenery.getRandomRockyThreshold(); for (int y = 0; y < map.getHeight(); y ++) { for (int x = 0; x < map.getWidth(); x ++) { int currValue = map.getValue(x, y); // warning - chaz hack! - to make walledges use a diff tile int floorTile = 0; switch (currValue) { case EMPTY: Tile.getEmptyTile().toMap(ret, x, y, false); break; case DEEP: Tile.getTile(1).toMap(ret, x, y, false); break; case FLOOR: if (Util.random() < randomRockyThreshold) { floorTile = 2; } writeFloor(ret, x, y, floorGrid, floorTile); break; case WALLEDGE: floorTile=2; writeFloor(ret, x, y, floorGrid, floorTile); break; case IMPASSABLE: floorTile=2; writeFloor(ret, x, y, floorGrid, floorTile); Exclude.getInstance().toMap(ret, x, y, true); break; case WALL: int neighbours = (map.getValue(x, y + 1) == WALL || map.getValue(x, y + 1) == EMPTY ? 1 : 0) + (map.getValue(x, y - 1) == WALL || map.getValue(x, y - 1) == EMPTY ? 1 : 0) + (map.getValue(x + 1, y) == WALL || map.getValue(x + 1, y) == EMPTY ? 1 : 0) + (map.getValue(x - 1, y) == WALL || map.getValue(x - 1, y) == EMPTY ? 1 : 0) + (map.getValue(x + 1, y + 1) == WALL || map.getValue(x + 1, y + 1) == EMPTY ? 1 : 0) + (map.getValue(x + 1, y - 1) == WALL || map.getValue(x + 1, y - 1) == EMPTY ? 1 : 0) + (map.getValue(x - 1, y + 1) == WALL || map.getValue(x - 1, y + 1) == EMPTY ? 1 : 0) + (map.getValue(x - 1, y - 1) == WALL || map.getValue(x - 1, y - 1) == EMPTY ? 1 : 0) ; writeFloor(ret, x, y, floorGrid, floorTile); template.getWall(neighbours).toMap(ret, x, y, true); break; case INTERNAL: floorTile=3; int neighbours2 = (map.getValue(x, y + 1) == INTERNAL || map.getValue(x, y + 1) == EMPTY ? 1 : 0) + (map.getValue(x, y - 1) == INTERNAL || map.getValue(x, y - 1) == EMPTY ? 1 : 0) + (map.getValue(x + 1, y) == INTERNAL || map.getValue(x + 1, y) == EMPTY ? 1 : 0) + (map.getValue(x - 1, y) == INTERNAL || map.getValue(x - 1, y) == EMPTY ? 1 : 0) + (map.getValue(x + 1, y + 1) == INTERNAL || map.getValue(x + 1, y + 1) == EMPTY ? 1 : 0) + (map.getValue(x + 1, y - 1) == INTERNAL || map.getValue(x + 1, y - 1) == EMPTY ? 1 : 0) + (map.getValue(x - 1, y + 1) == INTERNAL || map.getValue(x - 1, y + 1) == EMPTY ? 1 : 0) + (map.getValue(x - 1, y - 1) == INTERNAL || map.getValue(x - 1, y - 1) == EMPTY ? 1 : 0) ; // chaz hack! gradient edges? if (neighbours2 < 8) { Res.getFloorEdgeTransition( (map.getValue(x, y+1) == WALL), (map.getValue(x+1, y+1) == WALL), (map.getValue(x+1, y) == WALL), (map.getValue(x+1, y-1) == WALL), (map.getValue(x, y-1) == WALL), (map.getValue(x-1, y-1) == WALL), (map.getValue(x-1, y) == WALL), (map.getValue(x-1, y+1) == WALL) ).toMap(ret, x, y, false); } else { writeFloor(ret, x, y, floorGrid, floorTile); } // Hackery: if 8 neighbours, every other tile is blank // but only if getBigInternalWall == true if (template.getBigInternalWalls()) { if (neighbours2 < 8 || neighbours2 == 8 && (x & 1 ^ y & 1) == 0) { template.getInternalWall(neighbours2).toMap(ret, x, y, true); } } else { template.getInternalWall(neighbours2).toMap(ret, x, y, true); } break; case BASE: // Draw some floor under the base writeFloor(ret, x, y, floorGrid, floorTile); // Then add the base template.getBase().toMap(ret, x, y, true); // Erase base squares for (int yy = 0; yy < 3; yy ++) { for (int xx = 0; xx < 4; xx ++) { map.setValue(x + xx, y + yy, SPECIAL); } } break; case SPAWN: // Draw some floor under the spawn point. writeFloor(ret, x, y, floorGrid, floorTile); if (x == 0) { char type = formation[currentFormation ++]; Res.getWestSpawnPoint(type - '1').toMap(ret, x, y, true); } else if (x == map.getWidth() - 1) { char type = formation[currentFormation ++]; Res.getEastSpawnPoint(type - '1').toMap(ret, x, y, true); } else if (y == 0) { char type = formation[currentFormation ++]; Res.getSouthSpawnPoint(type - '1').toMap(ret, x, y, true); } else if (y == map.getHeight() - 1) { char type = mapGeneratorParams.getGameMode() == WormGameState.GAME_MODE_XMAS ? '1' : formation[currentFormation ++]; // Xmas Hax! Res.getNorthSpawnPoint(type - '1').toMap(ret, x, y, true); } else { template.getMidSpawn().toMap(ret, x, y, true); } break; case OBSTACLE: floorTile=2; //floorTile=3; writeFloor(ret, x, y, floorGrid, floorTile); if (roadsOverlay.getValue(x, y) == 1) { scenery.getRoadObstacle().toMap(ret, x, y, true); } else { scenery.getObstacle().toMap(ret, x, y, true); } break; case RUIN_IMPASSABLE: case RUIN: // Draw a ruin. First the floor floorTile=2; writeFloor(ret, x, y, floorGrid, floorTile); // Get ruin from ruins map Ruin ruin = ruins.get(new Point(x, y)); if (ruin != null) { ruin.toMap(ret, x, y, true); } break; case CRYSTAL: // Draw a crystal. First the floor floorTile=2; writeFloor(ret, x, y, floorGrid, floorTile); // and an exclude Exclude.getInstance().toMap(ret, x, y, false); // Get crystal from crystals map Crystal crystal = crystals.get(new Point(x, y)); if (crystal != null) { crystal.toMap(ret, x, y, true); } break; case SPECIAL: // Put floor down writeFloor(ret, x, y, floorGrid, floorTile); break; case SPECIAL_IMPASSABLE: // Put floor down and an exclude on top writeFloor(ret, x, y, floorGrid, floorTile); Exclude.getInstance().toMap(ret, x, y, false); break; case TOTAL_IMPASSABLE: writeFloor(ret, x, y, floorGrid, floorTile); TotalExclude.getInstance().toMap(ret, x, y, true); break; default: assert false; } // Now plonk road overlay down if (roadsOverlay.getValue(x, y) == 1) { // Draw road depending on the NSEW neighbours. boolean nRoad = roadsOverlay.getValue(x, y + 1) == 1 || y == getHeight() - 1; boolean sRoad = roadsOverlay.getValue(x, y - 1) == 1 || y == 0; boolean eRoad = roadsOverlay.getValue(x + 1, y) == 1 || x == getWidth() - 1; boolean wRoad = roadsOverlay.getValue(x - 1, y) == 1 || x == 0; scenery.getRoad(nRoad, eRoad, sRoad, wRoad).toMap(ret, x, y, true); ret.setCost(x, y, Topology.ROAD_COST); } else if (isBog(x, y, floorGrid)) { ret.setCost(x, y, Topology.BOG_COST); } else { ret.setCost(x, y, Topology.NORMAL_COST); } } Thread.yield(); } if (mapGeneratorParams.getGameMode() == WormGameState.GAME_MODE_CAMPAIGN || mapGeneratorParams.getGameMode() == WormGameState.GAME_MODE_ENDLESS) { try { save(ret); } catch (IOException e) { e.printStackTrace(System.err); } } setProgress(1.0f); return ret; } private boolean isBog(int x, int y, IntGrid floorGrid) { return floorGrid.getValue(x, y) == 1; } private void writeFloor(GameMap map, int x, int y, IntGrid floorGrid, int floorTile) { if (isBog(x, y, floorGrid)) { // Count the neighbours which are also above the threshold... int neighbours = floorGrid.getValue(x, y + 1) + floorGrid.getValue(x, y - 1) + floorGrid.getValue(x + 1, y) + floorGrid.getValue(x - 1, y) + floorGrid.getValue(x + 1, y + 1) + floorGrid.getValue(x + 1, y - 1) + floorGrid.getValue(x - 1, y + 1) + floorGrid.getValue(x - 1, y - 1) ; if (neighbours == 8) { template.getFloor1().toMap(map, x, y, true); } else { template.getFloorTransitions(neighbours).toMap(map, x, y, true); } } else { // chaz hack! - how you do eval(string) in java? //eval("template.getFloor"+floorTile+"().toMap(map, x, y, true)"); // spose getFloor should use an array but hey it works ok switch (floorTile) { case 1: template.getFloor1().toMap(map, x, y, true); break; case 2: template.getFloor2().toMap(map, x, y, true); break; case 3: template.getFloor3().toMap(map, x, y, true); break; default: template.getFloor0().toMap(map, x, y, true); } } } }