예제 #1
0
  /**
   * 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);
  }
예제 #2
0
  /**
   * 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));
        }
      }
    }
  }
예제 #4
0
  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;
  }