// 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; }
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; }
/** * 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; } }
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])); } }
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); }
/** * @param i * @return {@code (x*i,y*j)}. */ public Coord scale(int i, int j) { return Coord.get(x * i, y * j); }
/** * @param i * @return {@code (x*i,y*i)}. */ public Coord scale(int i) { return Coord.get(x * i, y * i); }
/** * @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); }
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; }
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; }
/** * 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; }
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; }