/** * Generate a char[][] dungeon with extra features given a baseDungeon that has already been * generated, and that already has staircases represented by greater than and less than signs. * Typically, you want to call generate with a TilesetType or no argument for the easiest * generation; this method is meant for adding features like water and doors to existing simple * maps. This uses '#' for walls, '.' for floors, '~' for deep water, ',' for shallow water, '^' * for traps, '+' for doors that provide horizontal passage, and '/' for doors that provide * vertical passage. Use the addDoors, addWater, addGrass, and addTraps methods of this class to * request these in the generated map. Also sets the fields stairsUp and stairsDown to null, and * expects stairs to be already handled. * * @param baseDungeon a pre-made dungeon consisting of '#' for walls and '.' for floors, with * stairs already in; may be modified in-place * @return a char[][] dungeon */ public char[][] generateRespectingStairs(char[][] baseDungeon) { if (!seedFixed) { rebuildSeed = rng.getState(); } seedFixed = false; char[][] map = DungeonUtility.wallWrap(baseDungeon); DijkstraMap dijkstra = new DijkstraMap(map); stairsUp = null; stairsDown = null; dijkstra.clearGoals(); ArrayList<Coord> stairs = DungeonUtility.allMatching(map, '<', '>'); for (int j = 0; j < stairs.size(); j++) { dijkstra.setGoal(stairs.get(j)); } dijkstra.scan(null); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if (dijkstra.gradientMap[i][j] >= DijkstraMap.FLOOR) { map[i][j] = '#'; } } } return innerGenerate(map); }
/** * Generate a char[][] dungeon with extra features given a baseDungeon that has already been * generated. Typically, you want to call generate with a TilesetType or no argument for the * easiest generation; this method is meant for adding features like water and doors to existing * simple maps. This uses '#' for walls, '.' for floors, '~' for deep water, ',' for shallow * water, '^' for traps, '+' for doors that provide horizontal passage, and '/' for doors that * provide vertical passage. Use the addDoors, addWater, addGrass, and addTraps methods of this * class to request these in the generated map. Also sets the fields stairsUp and stairsDown to * two randomly chosen, distant, connected, walkable cells. <br> * Special behavior here: If tab characters are present in the 2D char array, they will be * replaced with '.' in the final dungeon, but will also be tried first as valid staircase * locations (with a high distance possible to travel away from the starting staircase). If no tab * characters are present this will search for '.' floors to place stairs on, as normal. This * tab-first behavior is useful in conjunction with some methods that establish a good path in an * existing dungeon; an example is {@code DungeonUtility.ensurePath(dungeon, rng, '\t', '#');} * then passing dungeon (which that code modifies) in as baseDungeon to this method. * * @param baseDungeon a pre-made dungeon consisting of '#' for walls and '.' for floors; may be * modified in-place * @return a char[][] dungeon */ public char[][] generate(char[][] baseDungeon) { if (!seedFixed) { rebuildSeed = rng.getState(); } seedFixed = false; char[][] map = DungeonUtility.wallWrap(baseDungeon); width = map.length; height = map[0].length; DijkstraMap dijkstra = new DijkstraMap(map); int frustrated = 0; do { dijkstra.clearGoals(); stairsUp = utility.randomMatchingTile(map, '\t'); if (stairsUp == null) { stairsUp = utility.randomFloor(map); if (stairsUp == null) { frustrated++; continue; } } dijkstra.setGoal(stairsUp); dijkstra.scan(null); frustrated++; } while (dijkstra.getMappedCount() < width + height && frustrated < 8); if (frustrated >= 8) { return generate(); } double maxDijkstra = 0.0; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if (dijkstra.gradientMap[i][j] >= DijkstraMap.FLOOR) { map[i][j] = '#'; } else if (dijkstra.gradientMap[i][j] > maxDijkstra) { maxDijkstra = dijkstra.gradientMap[i][j]; } if (map[i][j] == '\t') { map[i][j] = '.'; } } } stairsDown = new GreasedRegion(dijkstra.gradientMap, maxDijkstra * 0.7, DijkstraMap.FLOOR) .singleRandom(rng); return innerGenerate(map); }
private void postMove() { phase = Phase.MONSTER_ANIM; // The next two lines are important to avoid monsters treating cells the player WAS in as goals. getToPlayer.clearGoals(); getToPlayer.resetMap(); // now that goals are cleared, we can mark the current player position as a goal. getToPlayer.setGoal(player.gridX, player.gridY); // this is an important piece of DijkstraMap usage; the argument is a Set of Points for squares // that // temporarily cannot be moved through (not walls, which are automatically known because the map // char[][] // was passed to the DijkstraMap constructor, but things like moving creatures and objects). LinkedHashSet<Coord> monplaces = monsters.positions(); pathMap = getToPlayer.scan(monplaces); // recalculate FOV, store it in fovmap for the render to use. fovmap = fov.calculateFOV(res, player.gridX, player.gridY, 8, Radius.SQUARE); // handle monster turns ArrayList<Coord> nextMovePositions = new ArrayList<Coord>(25); for (Coord pos : monsters.positions()) { Monster mon = monsters.get(pos); // monster values are used to store their aggression, 1 for actively stalking the player, 0 // for not. if (mon.state > 0 || fovmap[pos.x][pos.y] > 0.1) { if (mon.state == 0) { messages.appendMessage( "The PHANTOM cackles at you, \"" + FakeLanguageGen.RUSSIAN_AUTHENTIC.sentence( rng, 1, 3, new String[] {",", ",", ",", " -"}, new String[] {"!"}, 0.25) + "\""); } // this block is used to ensure that the monster picks the best path, or a random choice if // there // is more than one equally good best option. Direction choice = null; double best = 9999.0; Direction[] ds = new Direction[8]; rng.shuffle(Direction.OUTWARDS, ds); for (Direction d : ds) { Coord tmp = pos.translate(d); if (pathMap[tmp.x][tmp.y] < best && !checkOverlap(mon, tmp.x, tmp.y, nextMovePositions)) { // pathMap is a 2D array of doubles where 0 is the goal (the player). // we use best to store which option is closest to the goal. best = pathMap[tmp.x][tmp.y]; choice = d; } } if (choice != null) { Coord tmp = pos.translate(choice); // if we would move into the player, instead damage the player and give newMons the // current // position of this monster. if (tmp.x == player.gridX && tmp.y == player.gridY) { display.tint(player.gridX * 2, player.gridY, SColor.PURE_CRIMSON, 0, 0.415f); display.tint(player.gridX * 2 + 1, player.gridY, SColor.PURE_CRIMSON, 0, 0.415f); health--; // player.setText("" + health); monsters.positionalModify(pos, mon.change(1)); } // otherwise store the new position in newMons. else { /*if (fovmap[mon.getKey().x][mon.getKey().y] > 0.0) { display.put(mon.getKey().x, mon.getKey().y, 'M', 11); }*/ nextMovePositions.add(tmp); monsters.positionalModify(pos, mon.change(1)); monsters.move(pos, tmp); display.slide(mon.entity, tmp.x, tmp.y); } } else { monsters.positionalModify(pos, mon.change(1)); } } } }
protected OrderedSet<Coord> viableDoorways(boolean doubleDoors, char[][] map) { OrderedSet<Coord> doors = new OrderedSet<>(); OrderedSet<Coord> blocked = new OrderedSet<>(4); DijkstraMap dm = new DijkstraMap(map, DijkstraMap.Measurement.EUCLIDEAN); for (int x = 1; x < map.length - 1; x++) { for (int y = 1; y < map[x].length - 1; y++) { if (map[x][y] == '#') continue; if (doubleDoors) { if (x >= map.length - 2 || y >= map[x].length - 2) continue; else { if (map[x + 1][y] != '#' && map[x + 2][y] == '#' && map[x - 1][y] == '#' && map[x][y + 1] != '#' && map[x][y - 1] != '#' && map[x + 1][y + 1] != '#' && map[x + 1][y - 1] != '#') { if (map[x + 2][y + 1] != '#' || map[x - 1][y + 1] != '#' || map[x + 2][y - 1] != '#' || map[x - 1][y - 1] != '#') { dm.resetMap(); dm.clearGoals(); dm.setGoal(x, y + 1); blocked.clear(); blocked.add(Coord.get(x, y)); blocked.add(Coord.get(x + 1, y)); if (dm.partialScan(16, blocked)[x][y - 1] < DijkstraMap.FLOOR) continue; doors.add(Coord.get(x, y)); doors.add(Coord.get(x + 1, y)); doors = removeAdjacent(doors, Coord.get(x, y), Coord.get(x + 1, y)); continue; } } else if (map[x][y + 1] != '#' && map[x][y + 2] == '#' && map[x][y - 1] == '#' && map[x + 1][y] != '#' && map[x - 1][y] != '#' && map[x + 1][y + 1] != '#' && map[x - 1][y + 1] != '#') { if (map[x + 1][y + 2] != '#' || map[x + 1][y - 1] != '#' || map[x - 1][y + 2] != '#' || map[x - 1][y - 1] != '#') { dm.resetMap(); dm.clearGoals(); dm.setGoal(x + 1, y); blocked.clear(); blocked.add(Coord.get(x, y)); blocked.add(Coord.get(x, y + 1)); if (dm.partialScan(16, blocked)[x - 1][y] < DijkstraMap.FLOOR) continue; doors.add(Coord.get(x, y)); doors.add(Coord.get(x, y + 1)); doors = removeAdjacent(doors, Coord.get(x, y), Coord.get(x, y + 1)); continue; } } } } if (map[x + 1][y] == '#' && map[x - 1][y] == '#' && map[x][y + 1] != '#' && map[x][y - 1] != '#') { if (map[x + 1][y + 1] != '#' || map[x - 1][y + 1] != '#' || map[x + 1][y - 1] != '#' || map[x - 1][y - 1] != '#') { dm.resetMap(); dm.clearGoals(); dm.setGoal(x, y + 1); blocked.clear(); blocked.add(Coord.get(x, y)); if (dm.partialScan(16, blocked)[x][y - 1] < DijkstraMap.FLOOR) continue; doors.add(Coord.get(x, y)); doors = removeAdjacent(doors, Coord.get(x, y)); } } else if (map[x][y + 1] == '#' && map[x][y - 1] == '#' && map[x + 1][y] != '#' && map[x - 1][y] != '#') { if (map[x + 1][y + 1] != '#' || map[x + 1][y - 1] != '#' || map[x - 1][y + 1] != '#' || map[x - 1][y - 1] != '#') { dm.resetMap(); dm.clearGoals(); dm.setGoal(x + 1, y); blocked.clear(); blocked.add(Coord.get(x, y)); if (dm.partialScan(16, blocked)[x - 1][y] < DijkstraMap.FLOOR) continue; doors.add(Coord.get(x, y)); doors = removeAdjacent(doors, Coord.get(x, y)); } } } } return doors; }