// check if a monster's movement would overlap with another monster.
 private boolean checkOverlap(Monster mon, int x, int y, ArrayList<Coord> futureOccupied) {
   if (monsters.containsPosition(Coord.get(x, y)) && !mon.equals(monsters.get(Coord.get(x, y))))
     return true;
   for (Coord p : futureOccupied) {
     if (x == p.x && y == p.y) return true;
   }
   return false;
 }
Пример #2
0
  protected OrderedSet<Coord> removeAdjacent(OrderedSet<Coord> coll, Coord pt) {
    for (Coord temp :
        new Coord[] {
          Coord.get(pt.x + 1, pt.y),
          Coord.get(pt.x - 1, pt.y),
          Coord.get(pt.x, pt.y + 1),
          Coord.get(pt.x, pt.y - 1)
        }) {
      if (coll.contains(temp) && !(temp.x == pt.x && temp.y == pt.y)) coll.remove(temp);
    }

    return coll;
  }
Пример #3
0
 /**
  * Finds and returns the closest point containing a city to the given point. Does not include
  * provided point as a possible city location.
  *
  * <p>If there are no cities, null is returned.
  *
  * @param point
  * @return
  */
 public Coord closestCity(Coord point) {
   double dist = 999999999, newdist;
   Coord closest = null;
   for (Coord c : cities) {
     if (c.equals(point)) {
       continue; // skip the one being tested for
     }
     newdist = Math.pow(point.x - c.x, 2) + Math.pow(point.y - c.y, 2);
     if (newdist < dist) {
       dist = newdist;
       closest = c;
     }
   }
   return closest;
 }
  /**
   * Move the player or open closed doors, remove any monsters the player bumped, then update the
   * DijkstraMap and have the monsters that can see the player try to approach. In a fully-fledged
   * game, this would not be organized like this, but this is a one-file demo.
   *
   * @param xmod
   * @param ymod
   */
  private void move(int xmod, int ymod) {
    clearHelp();

    if (health <= 0) return;

    int newX = player.gridX + xmod, newY = player.gridY + ymod;
    if (newX >= 0 && newY >= 0 && newX < width && newY < height && bareDungeon[newX][newY] != '#') {
      // '+' is a door.
      if (decoDungeon[newX][newY] == '+') {
        decoDungeon[newX][newY] = '/';
        lineDungeon[newX * 2][newY] = '/';
        // changes to the map mean the resistances for FOV need to be regenerated.
        res = DungeonUtility.generateResistances(decoDungeon);
        // recalculate FOV, store it in fovmap for the render to use.
        fovmap = fov.calculateFOV(res, player.gridX, player.gridY, 8, Radius.SQUARE);
      } else {
        // recalculate FOV, store it in fovmap for the render to use.
        fovmap = fov.calculateFOV(res, newX, newY, 8, Radius.SQUARE);
        display.slide(player, newX, newY);
        monsters.remove(Coord.get(newX, newY));
      }
      if (newX == dungeonGen.stairsDown.x && newY == dungeonGen.stairsDown.y) {
        messages.appendMessage("WINNER WINNER CHICKEN DINNER!");
      }

      phase = Phase.PLAYER_ANIM;
    }
  }
Пример #5
0
  public double[][] makeWeightedMap() {
    // Weighted map for road
    double weightedMap[][] = new double[width][height];
    double SEALEVEL = 0;
    double BEACHLEVEL = 0.05;
    double PLAINSLEVEL = 0.3;
    for (int i = 0; i < width / 4; i++) {
      for (int j = 0; j < height / 4; j++) {
        weightedMap[i][j] = 0;
        if (map[i * 4][j * 4] > BEACHLEVEL) {
          weightedMap[i][j] = 2 + (map[i * 4][j * 4] - PLAINSLEVEL) * 8;
        }
        if (map[i][j] <= BEACHLEVEL && map[i * 4][j * 4] >= SEALEVEL) {
          weightedMap[i][j] = 2 - map[i * 4][j * 4] * 2;
        }
      }
    }

    CITIES:
    for (int i = 0; i < CITYAMOUNT; i++) {
      int px = rng.between(0, width), py = rng.between(0, height), frustration = 0;
      while (map[px][py] < SEALEVEL || map[px][py] > BEACHLEVEL) {
        px = rng.between(0, width);
        py = rng.between(0, height);
        if (frustration++ > 20) continue CITIES;
      }
      cities.add(Coord.get(4 * (px >> 2), 4 * (py >> 2)));
    }
    return weightedMap;
  }
 public void putMap() {
   display.erase();
   boolean overlapping;
   for (int i = 0; i < width; i++) {
     for (int j = 0; j < height; j++) {
       overlapping =
           (!monsters.isEmpty() && monsters.containsPosition(Coord.get(i, j)))
               || (!monsters.isEmpty() && player.gridX == i && player.gridY == j);
       // if we see it now, we remember the cell and show a lit cell based on the fovmap value
       // (between 0.0
       // and 1.0), with 1.0 being almost pure white at +215 lightness and 0.0 being rather dark at
       // -105.
       if (fovmap[i][j] > 0.0) {
         seen[i][j] = true;
         display.put(
             i * 2,
             j,
             (overlapping) ? ' ' : lineDungeon[i * 2][j],
             fgCenter.filter(colors[i * 2][j]),
             bgCenter.filter(bgColors[i * 2][j]),
             lights[i][j] + (int) (-105 + 320 * fovmap[i][j]));
         display.put(
             i * 2 + 1,
             j,
             (overlapping) ? ' ' : lineDungeon[i * 2 + 1][j],
             fgCenter.filter(colors[i * 2][j]),
             bgCenter.filter(bgColors[i * 2][j]),
             lights[i][j] + (int) (-105 + 320 * fovmap[i][j]));
         // if we don't see it now, but did earlier, use a very dark background, but lighter than
         // black.
       } else if (seen[i][j]) {
         display.put(
             i * 2,
             j,
             lineDungeon[i * 2][j],
             fgCenter.filter(colors[i * 2][j]),
             bgCenter.filter(bgColors[i * 2][j]),
             -140);
         display.put(
             i * 2 + 1,
             j,
             lineDungeon[i * 2 + 1][j],
             fgCenter.filter(colors[i * 2][j]),
             bgCenter.filter(bgColors[i * 2][j]),
             -140);
       }
     }
   }
   for (Coord pt : toCursor) {
     // use a brighter light to trace the path to the cursor, from 170 max lightness to 0 min.
     display.highlight(pt.x * 2, pt.y, lights[pt.x][pt.y] + (int) (170 * fovmap[pt.x][pt.y]));
     display.highlight(pt.x * 2 + 1, pt.y, lights[pt.x][pt.y] + (int) (170 * fovmap[pt.x][pt.y]));
   }
 }
Пример #7
0
 private boolean bresenhamReachable(Radius radiusStrategy) {
   Queue<Coord> path = Bresenham.line2D(startx, starty, targetx, targety);
   lastPath = new LinkedList<>();
   lastPath.add(Coord.get(startx, starty));
   double force = 1;
   double decay = 1 / radiusStrategy.radius(startx, starty, targetx, targety);
   double currentForce = force;
   for (Coord p : path) {
     lastPath.offer(p);
     if (p.x == targetx && p.y == targety) {
       return true; // reached the end
     }
     if (p.x != startx
         || p.y != starty) { // don't discount the start location even if on resistant cell
       currentForce -= resistanceMap[p.x][p.y];
     }
     double r = radiusStrategy.radius(startx, starty, p.x, p.y);
     if (currentForce - (r * decay) <= 0) {
       return false; // too much resistance
     }
   }
   return false; // never got to the target point
 }
  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));
        }
      }
    }
  }
  @Override
  public void create() {
    // gotta have a random number generator. Here the RNG is unseeded, which means a different
    // dungeon every time,
    // among many other changes.
    rng = new RNG();

    // seeds can be given easily to RNG's constructor. If you want to request the current state from
    // a random number
    // generator or change the state in the middle of usage, you need to use a StatefulRNG (or
    // EditRNG) instead of a
    // normal RNG; StatefulRNG and EditRNG can also be constructed with seeds like RNG can.
    // rng = new RNG("seeeeeeeed");
    // rng = new RNG(0xBADACE);

    // for demo purposes, we allow changing the SquidColorCenter and the filter effect associated
    // with it.
    // next, we populate the colorCenters array with the SquidColorCenters that will modify any
    // colors we request
    // of them using the filter we specify. Only one SquidColorCenter will be used at any time for
    // foreground, and
    // sometimes another will be used for background.
    colorCenters = new SquidColorCenter[18];
    // MultiLerpFilter here is given two colors to tint everything toward one of; this is meant to
    // reproduce the
    // "Hollywood action movie poster" style of using primarily light orange (explosions) and
    // gray-blue (metal).

    colorCenters[0] =
        new SquidColorCenter(
            new Filters.MultiLerpFilter(
                new Color[] {SColor.GAMBOGE_DYE, SColor.COLUMBIA_BLUE}, new float[] {0.25f, 0.2f}));
    colorCenters[1] = colorCenters[0];

    // MultiLerpFilter here is given three colors to tint everything toward one of; this is meant to
    // look bolder.

    colorCenters[2] =
        new SquidColorCenter(
            new Filters.MultiLerpFilter(
                new Color[] {SColor.RED_PIGMENT, SColor.MEDIUM_BLUE, SColor.LIME_GREEN},
                new float[] {0.2f, 0.25f, 0.25f}));
    colorCenters[3] = colorCenters[2];

    // ColorizeFilter here is given a slightly-grayish dark brown to imitate a sepia tone.

    colorCenters[4] =
        new SquidColorCenter(new Filters.ColorizeFilter(SColor.CLOVE_BROWN, 0.7f, -0.05f));
    colorCenters[5] =
        new SquidColorCenter(new Filters.ColorizeFilter(SColor.CLOVE_BROWN, 0.65f, 0.07f));

    // HallucinateFilter makes all the colors very saturated and move even when you aren't doing
    // anything.

    colorCenters[6] = new SquidColorCenter(new Filters.HallucinateFilter());
    colorCenters[7] = colorCenters[6];

    // SaturationFilter here is used to over-saturate the colors slightly. Background is less
    // saturated.

    colorCenters[8] = new SquidColorCenter(new Filters.SaturationFilter(1.35f));
    colorCenters[9] = new SquidColorCenter(new Filters.SaturationFilter(1.15f));

    // SaturationFilter here is used to de-saturate the colors slightly. Background is less
    // saturated.

    colorCenters[10] = new SquidColorCenter(new Filters.SaturationFilter(0.7f));
    colorCenters[11] = new SquidColorCenter(new Filters.SaturationFilter(0.5f));

    // WiggleFilter here is used to randomize the colors slightly.

    colorCenters[12] = new SquidColorCenter(new Filters.WiggleFilter());
    colorCenters[13] = colorCenters[12];

    // SaturationFilter here is used to de-saturate the colors slightly. Background is less
    // saturated.

    colorCenters[14] = new SquidColorCenter(new Filters.PaletteFilter(SColor.BLUE_GREEN_SERIES));
    colorCenters[15] = new SquidColorCenter(new Filters.PaletteFilter(SColor.ACHROMATIC_SERIES));

    colorCenters[16] = DefaultResources.getSCC();
    colorCenters[17] = colorCenters[16];

    fgCenter = colorCenters[16];
    bgCenter = colorCenters[17];
    currentCenter = 8;
    batch = new SpriteBatch();
    width = 60;
    height = 30;
    // NOTE: cellWidth and cellHeight are assigned values that are significantly larger than the
    // corresponding sizes
    // in the EverythingDemoLauncher's main method. Because they are scaled up by an integer here,
    // they can be scaled
    // down when rendered, allowing certain small details to appear sharper. This _only_ works with
    // distance field,
    // a.k.a. stretchable, fonts! INTERNAL_ZOOM is a tradeoff between rendering more pixels to
    // increase quality (when
    // values are high) or rendering fewer pixels for speed (when values are low). Using 2 seems to
    // work well.
    cellWidth = 13 * INTERNAL_ZOOM;
    cellHeight = 26 * INTERNAL_ZOOM;
    // getStretchableFont loads an embedded font, Inconsolata-LGC-Custom, that is a distance field
    // font as mentioned
    // earlier. We set the smoothing multiplier on it only because we are using internal zoom to
    // increase sharpness
    // on small details, but if the smoothing is incorrect some sizes look blurry or over-sharpened.
    // This can be set
    // manually if you use a constant internal zoom; here we use 1f for internal zoom 1, about 2/3f
    // for zoom 2, and
    // about 1/2f for zoom 3. If you have more zooms as options for some reason, this formula should
    // hold for many
    // cases but probably not all.
    textFactory =
        DefaultResources.getStretchableFont()
            .setSmoothingMultiplier(2f / (INTERNAL_ZOOM + 1f))
            .width(cellWidth)
            .height(cellHeight)
            .initBySize();
    // Creates a layered series of text grids in a SquidLayers object, using the previously set-up
    // textFactory and
    // SquidColorCenters.
    display =
        new SquidLayers(
            width * 2, height, cellWidth, cellHeight, textFactory.copy(), bgCenter, fgCenter);
    // subCell is a SquidPanel, the same class that SquidLayers has for each of its layers, but we
    // want to render
    // certain effects on top of all other panels, which can't be done in the all-in-one-pass
    // rendering of the grids
    // in SquidLayers, though it could be done with a slight hassle if the effects are made into
    // AnimatedEntity
    // objects or Actors, then rendered separately like the monsters are (see render() below). It is
    // called subCell
    // because its text will be made smaller than a full cell, and appears in the upper left corner
    // for things like
    // the current health of the player and an '!' for alerted monsters.
    subCell = new SquidPanel(width * 2, height, textFactory.copy(), fgCenter);

    display.setAnimationDuration(0.1f);
    messages = new SquidMessageBox(width * 2, 4, textFactory.copy());
    // a bit of a hack to increase the text height slightly without changing the size of the cells
    // they're in.
    // this causes a tiny bit of overlap between cells, which gets rid of an annoying gap between
    // vertical lines.
    // if you use '#' for walls instead of box drawing chars, you don't need this.
    messages.setTextSize(cellWidth, cellHeight + INTERNAL_ZOOM * 2);
    display.setTextSize(cellWidth, cellHeight + INTERNAL_ZOOM * 2);
    // The subCell SquidPanel uses a smaller size here; the numbers 8 and 16 should change if
    // cellWidth or cellHeight
    // change, and the INTERNAL_ZOOM multiplier keeps things sharp, the same as it does all over
    // here.
    subCell.setTextSize(8 * INTERNAL_ZOOM, 16 * INTERNAL_ZOOM);
    viewport = new StretchViewport(width * 2 * cellWidth, (height + 4) * cellHeight);
    stage = new Stage(viewport, batch);

    // These need to have their positions set before adding any entities if there is an offset
    // involved.
    messages.setBounds(0, 0, cellWidth * width * 2, cellHeight * 4);
    display.setPosition(0, messages.getHeight());
    subCell.setPosition(0, messages.getHeight());
    messages.appendWrappingMessage(
        "Use numpad or vi-keys (hjklyubn) to move. Use ? for help, f to change colors, q to quit."
            + " Click the top or bottom border of this box to scroll.");
    counter = 0;

    dungeonGen = new SectionDungeonGenerator(width, height, rng);
    dungeonGen.addWater(SectionDungeonGenerator.ALL, 8, 6);
    dungeonGen.addGrass(SectionDungeonGenerator.CAVE, 5);
    dungeonGen.addBoulders(SectionDungeonGenerator.CAVE, 10);
    dungeonGen.addDoors(18, false);
    dungeonGen.addMaze(8);
    SerpentMapGenerator serpent = new SerpentMapGenerator(width, height, rng);
    serpent.putCaveCarvers(2);
    serpent.putWalledBoxRoomCarvers(2);
    serpent.putWalledRoundRoomCarvers(1);
    char[][] mg = serpent.generate();
    decoDungeon = dungeonGen.generate(mg, serpent.getEnvironment());
    Coord pl = dungeonGen.stairsUp, tgt = dungeonGen.stairsDown;
    decoDungeon[pl.x][pl.y] = '<';
    decoDungeon[tgt.x][tgt.y] = '>';
    // DefaultResources has not only default fonts but now also default icons.
    // These need the actual assets to be downloaded as part of the zip or tar.gz
    // archive of assets, or separately fetched from GitHub in the assets/ folder.
    atlas = DefaultResources.getIconAtlas();
    region = atlas.findRegion("haunting");

    // change the TilesetType to lots of different choices to see what dungeon works best.
    // bareDungeon = dungeonGen.generate(TilesetType.DEFAULT_DUNGEON);
    bareDungeon = dungeonGen.getBareDungeon();
    lineDungeon = DungeonUtility.doubleWidth(DungeonUtility.hashesToLines(decoDungeon, true));
    // it's more efficient to get random floors from a packed set containing only (compressed) floor
    // positions.
    short[] placement = CoordPacker.pack(bareDungeon, '.');
    // Coord pl = dungeonGen.utility.randomCell(placement);
    placement = CoordPacker.removeSeveralPacked(placement, pl, tgt);
    int numMonsters = 60;
    monsters = new SpatialMap<Integer, Monster>(numMonsters);
    for (int i = 0; i < numMonsters; i++) {
      Coord monPos = dungeonGen.utility.randomCell(placement);
      placement = CoordPacker.removePacked(placement, monPos.x, monPos.y);
      monsters.put(
          monPos,
          i,
          new Monster(
              display.animateActor(
                  monPos.x, monPos.y, region, fgCenter.filter(display.getPalette().get(11)), true),
              0));
      // monsters.put(monPos, i, new Monster(display.animateActor(monPos.x, monPos.y, 'Я',
      //        fgCenter.filter(display.getPalette().get(11))), 0));
    }
    // your choice of FOV matters here.
    fov = new FOV(FOV.RIPPLE_TIGHT);
    res = DungeonUtility.generateResistances(decoDungeon);
    fovmap = fov.calculateFOV(res, pl.x, pl.y, 8, Radius.SQUARE);
    getToPlayer = new DijkstraMap(decoDungeon, DijkstraMap.Measurement.CHEBYSHEV);
    getToPlayer.rng = rng;
    getToPlayer.setGoal(pl);
    pathMap = getToPlayer.scan(null);

    player =
        display.animateActor(
            pl.x,
            pl.y,
            '@',
            fgCenter.loopingGradient(SColor.CAPE_JASMINE, SColor.HAN_PURPLE, 45),
            1.5f,
            true);
    //                fgCenter.filter(display.getPalette().get(30)));
    cursor = Coord.get(-1, -1);
    toCursor = new ArrayList<Coord>(10);
    awaitedMoves = new ArrayList<Coord>(10);
    playerToCursor = new DijkstraMap(decoDungeon, DijkstraMap.Measurement.EUCLIDEAN);
    final int[][] initialColors = DungeonUtility.generatePaletteIndices(lineDungeon),
        initialBGColors = DungeonUtility.generateBGPaletteIndices(lineDungeon);
    colors = new Color[width * 2][height];
    bgColors = new Color[width * 2][height];
    ArrayList<Color> palette = display.getPalette();
    bgColor = SColor.DARK_SLATE_GRAY;
    for (int i = 0; i < width * 2; i++) {
      for (int j = 0; j < height; j++) {
        colors[i][j] = palette.get(initialColors[i][j]);
        bgColors[i][j] = palette.get(initialBGColors[i][j]);
      }
    }
    lights = DungeonUtility.generateLightnessModifiers(decoDungeon, counter);
    seen = new boolean[width][height];
    lang =
        FakeLanguageGen.RUSSIAN_AUTHENTIC.sentence(
            rng, 4, 6, new String[] {",", ",", ",", " -"}, new String[] {"..."}, 0.25);
    // this is a big one.
    // SquidInput can be constructed with a KeyHandler (which just processes specific keypresses), a
    // SquidMouse
    // (which is given an InputProcessor implementation and can handle multiple kinds of mouse
    // move), or both.
    // keyHandler is meant to be able to handle complex, modified key input, typically for games
    // that distinguish
    // between, say, 'q' and 'Q' for 'quaff' and 'Quip' or whatever obtuse combination you choose.
    // The
    // implementation here handles hjklyubn keys for 8-way movement, numpad for 8-way movement,
    // arrow keys for
    // 4-way movement, and wasd for 4-way movement. Shifted letter keys produce capitalized chars
    // when passed to
    // KeyHandler.handle(), but we don't care about that so we just use two case statements with the
    // same body,
    // one for the lower case letter and one for the upper case letter.
    // You can also set up a series of future moves by clicking within FOV range, using mouseMoved
    // to determine the
    // path to the mouse position with a DijkstraMap (called playerToCursor), and using touchUp to
    // actually trigger
    // the event when someone clicks.
    input =
        new VisualInput(
            new SquidInput.KeyHandler() {
              @Override
              public void handle(char key, boolean alt, boolean ctrl, boolean shift) {
                switch (key) {
                  case SquidInput.UP_ARROW:
                  case 'k':
                  case 'w':
                  case 'K':
                  case 'W':
                    {
                      move(0, -1);
                      break;
                    }
                  case SquidInput.DOWN_ARROW:
                  case 'j':
                  case 's':
                  case 'J':
                  case 'S':
                    {
                      move(0, 1);
                      break;
                    }
                  case SquidInput.LEFT_ARROW:
                  case 'h':
                  case 'a':
                  case 'H':
                  case 'A':
                    {
                      move(-1, 0);
                      break;
                    }
                  case SquidInput.RIGHT_ARROW:
                  case 'l':
                  case 'd':
                  case 'L':
                  case 'D':
                    {
                      move(1, 0);
                      break;
                    }

                  case SquidInput.UP_LEFT_ARROW:
                  case 'y':
                  case 'Y':
                    {
                      move(-1, -1);
                      break;
                    }
                  case SquidInput.UP_RIGHT_ARROW:
                  case 'u':
                  case 'U':
                    {
                      move(1, -1);
                      break;
                    }
                  case SquidInput.DOWN_RIGHT_ARROW:
                  case 'n':
                  case 'N':
                    {
                      move(1, 1);
                      break;
                    }
                  case SquidInput.DOWN_LEFT_ARROW:
                  case 'b':
                  case 'B':
                    {
                      move(-1, 1);
                      break;
                    }
                  case '?':
                    {
                      toggleHelp();
                      break;
                    }
                  case 'Q':
                  case 'q':
                  case SquidInput.ESCAPE:
                    {
                      Gdx.app.exit();
                      break;
                    }
                  case 'f':
                  case 'F':
                    {
                      currentCenter = (currentCenter + 1) % 9;
                      // idx is 3 when we use the HallucinateFilter, which needs special work
                      changingColors = currentCenter == 3;
                      fgCenter = colorCenters[currentCenter * 2];
                      bgCenter = colorCenters[currentCenter * 2 + 1];
                      display.setFGColorCenter(fgCenter);
                      display.setBGColorCenter(bgCenter);
                      break;
                    }
                }
              }
            },
            new SquidMouse(
                cellWidth,
                cellHeight,
                width * 2,
                height,
                0,
                0,
                new InputAdapter() {

                  // if the user clicks within FOV range and there are no awaitedMoves queued up,
                  // generate toCursor if it
                  // hasn't been generated already by mouseMoved, then copy it over to awaitedMoves.
                  @Override
                  public boolean touchUp(int screenX, int screenY, int pointer, int button) {
                    if (fovmap[(screenX) / 2][screenY] > 0.0 && awaitedMoves.isEmpty()) {
                      if (toCursor.isEmpty()) {
                        cursor = Coord.get((screenX) / 2, screenY);
                        // Uses DijkstraMap to get a path. from the player's position to the cursor
                        toCursor =
                            playerToCursor.findPath(
                                30, null, null, Coord.get(player.gridX, player.gridY), cursor);
                      }
                      awaitedMoves = new ArrayList<Coord>(toCursor);
                    }
                    return false;
                  }

                  @Override
                  public boolean touchDragged(int screenX, int screenY, int pointer) {
                    return mouseMoved(screenX, screenY);
                  }

                  // causes the path to the mouse position to become highlighted (toCursor contains
                  // a list of points that
                  // receive highlighting). Uses DijkstraMap.findPath() to find the path, which is
                  // surprisingly fast.
                  @Override
                  public boolean mouseMoved(int screenX, int screenY) {
                    if (!awaitedMoves.isEmpty()) return false;
                    if (cursor.x == screenX && cursor.y == screenY) {
                      return false;
                    }
                    if (fovmap[(screenX) / 2][screenY] > 0.0) {
                      cursor = Coord.get((screenX) / 2, screenY);
                      // Uses DijkstraMap to get a path. from the player's position to the cursor
                      toCursor =
                          playerToCursor.findPath(
                              30, null, null, Coord.get(player.gridX, player.gridY), cursor);
                    }
                    return false;
                  }
                }));
    // set this to true to test visual input on desktop
    input.forceButtons = false;
    // actions to give names to in the visual input menu
    input.init("filter", "??? help?", "quit");
    // ABSOLUTELY NEEDED TO HANDLE INPUT
    Gdx.input.setInputProcessor(new InputMultiplexer(input, stage));
    subCell.setOffsetY(messages.getGridHeight() * cellHeight);
    // and then add display and messages, our two visual components, to the list of things that act
    // in Stage.
    stage.addActor(display);
    // stage.addActor(subCell); // this is not added since it is manually drawn after other steps
    stage.addActor(messages);
    viewport = input.resizeInnerStage(stage);
  }
Пример #10
0
 /**
  * @param i
  * @return {@code (x*i,y*j)}.
  */
 public Coord scale(int i, int j) {
   return Coord.get(x * i, y * j);
 }
Пример #11
0
 /**
  * @param i
  * @return {@code (x*i,y*i)}.
  */
 public Coord scale(int i) {
   return Coord.get(x * i, y * i);
 }
Пример #12
0
 /**
  * @param d A non-{@code null} direction.
  * @return The coordinate obtained by applying {@code d} on {@code this}.
  */
 public Coord translate(Direction d) {
   return Coord.get(x + d.deltaX, y + d.deltaY);
 }
Пример #13
0
  private char[][] innerGenerate(char[][] map) {
    OrderedSet<Coord> doorways;
    OrderedSet<Coord> hazards = new OrderedSet<>();
    Coord temp;
    boolean doubleDoors = false;
    int floorCount = DungeonUtility.countCells(map, '.'),
        doorFill = 0,
        waterFill = 0,
        grassFill = 0,
        trapFill = 0,
        boulderFill = 0,
        islandSpacing = 0;
    if (fx.containsKey(FillEffect.DOORS)) {
      doorFill = fx.get(FillEffect.DOORS);
      if (doorFill < 0) {
        doubleDoors = true;
        doorFill *= -1;
      }
    }
    if (fx.containsKey(FillEffect.GRASS)) {
      grassFill = fx.get(FillEffect.GRASS);
    }
    if (fx.containsKey(FillEffect.WATER)) {
      waterFill = fx.get(FillEffect.WATER);
    }
    if (fx.containsKey(FillEffect.BOULDERS)) {
      boulderFill = fx.get(FillEffect.BOULDERS) * floorCount / 100;
    }
    if (fx.containsKey(FillEffect.ISLANDS)) {
      islandSpacing = fx.get(FillEffect.ISLANDS);
    }
    if (fx.containsKey(FillEffect.TRAPS)) {
      trapFill = fx.get(FillEffect.TRAPS);
    }

    doorways = viableDoorways(doubleDoors, map);

    OrderedSet<Coord> obstacles = new OrderedSet<>(doorways.size() * doorFill / 100);
    if (doorFill > 0) {
      int total = doorways.size() * doorFill / 100;

      BigLoop:
      for (int i = 0; i < total; i++) {
        Coord entry = rng.getRandomElement(doorways);
        if (map[entry.x][entry.y] == '<' || map[entry.x][entry.y] == '>') continue;
        if (map[entry.x - 1][entry.y] != '#' && map[entry.x + 1][entry.y] != '#') {
          map[entry.x][entry.y] = '+';
        } else {
          map[entry.x][entry.y] = '/';
        }
        obstacles.add(entry);
        Coord[] adj =
            new Coord[] {
              Coord.get(entry.x + 1, entry.y),
              Coord.get(entry.x - 1, entry.y),
              Coord.get(entry.x, entry.y + 1),
              Coord.get(entry.x, entry.y - 1)
            };
        for (Coord near : adj) {
          if (doorways.contains(near)) {
            map[near.x][near.y] = '#';
            obstacles.add(near);
            doorways.remove(near);
            i++;
            doorways.remove(entry);
            continue BigLoop;
          }
        }
        doorways.remove(entry);
      }
    }
    if (boulderFill > 0.0) {
      /*
      short[] floor = pack(map, '.');
      short[] viable = retract(floor, 1, width, height, true);
      ArrayList<Coord> boulders = randomPortion(viable, boulderFill, rng);
      for (Coord boulder : boulders) {
          map[boulder.x][boulder.y] = '#';
      }
      */
      Coord[] boulders = new GreasedRegion(map, '.').retract8way(1).randomPortion(rng, boulderFill);
      Coord t;
      for (int i = 0; i < boulders.length; i++) {
        t = boulders[i];
        map[t.x][t.y] = '#';
      }
    }

    if (trapFill > 0) {
      for (int x = 1; x < map.length - 1; x++) {
        for (int y = 1; y < map[x].length - 1; y++) {
          temp = Coord.get(x, y);
          if (map[x][y] == '.' && !obstacles.contains(temp)) {
            int ctr = 0;
            if (map[x + 1][y] != '#') ++ctr;
            if (map[x - 1][y] != '#') ++ctr;
            if (map[x][y + 1] != '#') ++ctr;
            if (map[x][y - 1] != '#') ++ctr;
            if (map[x + 1][y + 1] != '#') ++ctr;
            if (map[x - 1][y + 1] != '#') ++ctr;
            if (map[x + 1][y - 1] != '#') ++ctr;
            if (map[x - 1][y - 1] != '#') ++ctr;
            if (ctr >= 5) hazards.add(Coord.get(x, y));
          }
        }
      }
    }
    short[] floors = pack(map, '.'), working;
    floorCount = count(floors);
    float waterRate = waterFill / 100.0f, grassRate = grassFill / 100.0f;
    if (waterRate + grassRate > 1.0f) {
      waterRate /= (waterFill + grassFill) / 100.0f;
      grassRate /= (waterFill + grassFill) / 100.0f;
    }
    int targetWater = Math.round(floorCount * waterRate),
        targetGrass = Math.round(floorCount * grassRate),
        sizeWaterPools = targetWater / rng.between(3, 6),
        sizeGrassPools = targetGrass / rng.between(2, 5);

    Coord[] scatter;
    int remainingWater = targetWater, remainingGrass = targetGrass;
    if (targetWater > 0) {
      scatter = fractionPacked(floors, 7);
      scatter = rng.shuffle(scatter, new Coord[scatter.length]);
      ArrayList<Coord> allWater = new ArrayList<>(targetWater);
      for (int i = 0; i < scatter.length; i++) {
        if (remainingWater > 5) // remainingWater >= targetWater * 0.02 &&
        {
          if (!queryPacked(floors, scatter[i].x, scatter[i].y)) continue;
          working =
              spill(
                  floors,
                  packOne(scatter[i]),
                  rng.between(4, Math.min(remainingWater, sizeWaterPools)),
                  rng);

          floors = differencePacked(floors, working);
          Coord[] pts = allPacked(working);
          remainingWater -= pts.length;
          Collections.addAll(allWater, pts);
        } else break;
      }

      for (Coord pt : allWater) {
        hazards.remove(pt);
        // obstacles.add(pt);
        if (map[pt.x][pt.y] != '<' && map[pt.x][pt.y] != '>') map[pt.x][pt.y] = '~';
      }
      for (Coord pt : allWater) {
        if (map[pt.x][pt.y] != '<'
            && map[pt.x][pt.y] != '>'
            && (map[pt.x - 1][pt.y] == '.'
                || map[pt.x + 1][pt.y] == '.'
                || map[pt.x][pt.y - 1] == '.'
                || map[pt.x][pt.y + 1] == '.')) map[pt.x][pt.y] = ',';
      }
    }
    if (targetGrass > 0) {
      scatter = fractionPacked(floors, 7);
      scatter = rng.shuffle(scatter, new Coord[scatter.length]);
      Coord p;
      for (int i = 0; i < scatter.length; i++) {
        if (remainingGrass > 5) // remainingGrass >= targetGrass * 0.02 &&
        {
          working =
              spill(
                  floors,
                  packOne(scatter[i]),
                  rng.between(4, Math.min(remainingGrass, sizeGrassPools)),
                  rng);
          if (working.length == 0) continue;
          floors = differencePacked(floors, working);
          Coord[] pts = allPacked(working);
          remainingGrass -= pts.length;
          for (int c = 0; c < pts.length; c++) {
            p = pts[c];
            map[p.x][p.y] = '"';
          }
        } else break;
      }
    }

    if (islandSpacing > 1 && targetWater > 0) {
      ArrayList<Coord> islands =
          PoissonDisk.sampleMap(
              map, 1f * islandSpacing, rng, '#', '.', '"', '+', '/', '^', '<', '>');
      for (Coord c : islands) {
        map[c.x][c.y] = '.';
        if (map[c.x - 1][c.y] != '#' && map[c.x - 1][c.y] != '<' && map[c.x - 1][c.y] != '>')
          map[c.x - 1][c.y] = ',';
        if (map[c.x + 1][c.y] != '#' && map[c.x + 1][c.y] != '<' && map[c.x + 1][c.y] != '>')
          map[c.x + 1][c.y] = ',';
        if (map[c.x][c.y - 1] != '#' && map[c.x][c.y - 1] != '<' && map[c.x][c.y - 1] != '>')
          map[c.x][c.y - 1] = ',';
        if (map[c.x][c.y + 1] != '#' && map[c.x][c.y + 1] != '<' && map[c.x][c.y + 1] != '>')
          map[c.x][c.y + 1] = ',';
      }
    }

    if (trapFill > 0) {
      int total = hazards.size() * trapFill / 100;

      for (int i = 0; i < total; i++) {
        Coord entry = rng.getRandomElement(hazards);
        if (map[entry.x][entry.y] == '<' || map[entry.x][entry.y] == '<') continue;
        map[entry.x][entry.y] = '^';
        hazards.remove(entry);
      }
    }

    dungeon = map;
    return map;
  }
Пример #14
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;
  }
Пример #15
0
  /**
   * Finds an A* path to the target from the start. If no path is possible, returns null.
   *
   * @param startx the x coordinate of the start location
   * @param starty the y coordinate of the start location
   * @param targetx the x coordinate of the target location
   * @param targety the y coordinate of the target location
   * @return the shortest path, or null
   */
  public Queue<Coord> path(int startx, int starty, int targetx, int targety) {
    start = Coord.get(startx, starty);
    target = Coord.get(targetx, targety);
    open.clear();
    finished = new boolean[width][height];
    parent = new Coord[width][height];

    Direction[] dirs;
    switch (type) {
      case MANHATTAN:
        dirs = Direction.CARDINALS;
        break;
      case CHEBYSHEV:
      case EUCLIDEAN:
      case DIJKSTRA:
      default:
        dirs = Direction.OUTWARDS;
        break;
    }

    Coord p = start;
    open.add(p);

    while (!p.equals(target)) {
      finished[p.x][p.y] = true;
      open.remove(p);
      for (Direction dir : dirs) {

        int x = p.x + dir.deltaX;
        if (x < 0 || x >= width) {
          continue; // out of bounds so skip ahead
        }

        int y = p.y + dir.deltaY;
        if (y < 0 || y >= height) {
          continue; // out of bounds so skip ahead
        }

        if (!finished[x][y]) {
          Coord test = Coord.get(x, y);
          if (open.contains(test)) {
            double parentG = g(parent[x][y].x, parent[x][y].y);
            if (parentG < 0) {
              continue; // not a valid point so skip ahead
            }
            double g = g(p.x, p.y);
            if (g < 0) {
              continue; // not a valid point so skip ahead
            }
            if (parent[x][y] == null || parentG > g) {
              parent[x][y] = p;
            }
          } else {
            open.add(test);
            parent[x][y] = p;
          }
        }
      }
      p = smallestF();
      if (p == null) {
        return null; // no path possible
      }
    }

    Deque<Coord> deq = new ArrayDeque<>();
    while (!p.equals(start)) {
      deq.offerFirst(p);
      p = parent[p.x][p.y];
    }
    return deq;
  }
Пример #16
0
  private boolean rayReachable(Radius radiusStrategy) {
    lastPath = new LinkedList<>(); // save path for later retreival
    lastPath.add(Coord.get(startx, starty));
    if (startx == targetx && starty == targety) { // already there!
      return true;
    }

    int width = resistanceMap.length;
    int height = resistanceMap[0].length;

    CoordDouble start = new CoordDouble(startx, starty);
    CoordDouble end = new CoordDouble(targetx, targety);
    // find out which direction to step, on each axis
    int stepX = (int) Math.signum(end.x - start.x);
    int stepY = (int) Math.signum(end.y - start.y);

    double deltaY = end.x - start.x;
    double deltaX = end.y - start.y;

    deltaX = Math.abs(deltaX);
    deltaY = Math.abs(deltaY);

    int testX = (int) start.x;
    int testY = (int) start.y;

    double maxX = (float) (start.x % 1);
    double maxY = (float) (start.y % 1);

    int endTileX = (int) end.x;
    int endTileY = (int) end.y;

    CoordDouble collisionPoint = new CoordDouble();
    while (testX >= 0
        && testX < width
        && testY >= 0
        && testY < height
        && (testX != endTileX || testY != endTileY)) {
      lastPath.add(Coord.get(testX, testY));
      if (maxX < maxY) {
        maxX += deltaX;
        testX += stepX;
        if (resistanceMap[testX][testY] >= 1f) {
          collisionPoint.y = testY;
          collisionPoint.x = testX;
          end = collisionPoint;
          break;
        }
      } else if (maxY < maxX) {
        maxY += deltaY;
        testY += stepY;
        if (resistanceMap[testX][testY] >= 1f) {
          collisionPoint.y = testY;
          collisionPoint.x = testX;
          end = collisionPoint;
          break;
        }
      } else { // directly on diagonal, move both full step
        maxY += deltaY;
        testY += stepY;
        maxX += deltaX;
        testX += stepX;
        if (resistanceMap[testX][testY] >= 1f) {
          collisionPoint.y = testY;
          collisionPoint.x = testX;
          end = collisionPoint;
          break;
        }
      }
      if (radiusStrategy.radius(testX, testY, start.x, start.y)
          > radiusStrategy.radius(startx, starty, targetx, targety)) { // went too far
        break;
      }
    }

    if (end.x >= 0 && end.x < width && end.y >= 0 && end.y < height) {
      lastPath.add(Coord.get((int) end.x, (int) end.y));
    }

    return (int) end.x == targetx && (int) end.y == targety;
  }