package squidpony.squidgrid.mapping; import squidpony.squidgrid.FOV; import squidpony.squidgrid.Radius; import squidpony.squidmath.Coord; import squidpony.squidmath.GreasedRegion; import squidpony.squidmath.OrderedSet; /** * Utility class for finding areas where game-specific terrain features might be suitable to place. * Example placement for alongStraightWalls, using all regions where there's an extended straight wall in a room to * place a rack of bows (as curly braces): https://gist.github.com/tommyettinger/2b69a265bd93304f091b * Created by Tommy Ettinger on 3/13/2016. */ public class Placement { /** * The RoomFinder this uses internally to find placement areas only where they are appropriate. */ public RoomFinder finder; private GreasedRegion allRooms, allCorridors, allCaves, allFloors, nonRoom; private OrderedSet<OrderedSet<Coord>> alongStraightWalls = null, corners = null, centers = null; private OrderedSet<Coord> hidingPlaces = null; private Placement() { } /** * Constructs a Placement using the given RoomFinder, which will have collections of rooms, corridors, and caves. * A common use case for this class involves the Placement field that is constructed in a SectionDungeonGenerator * when generate() or generateRespectingStairs() in that class is called; if you use SectionDungeonGenerator, there * isn't much need for this constructor, since you can normally use the one created as a field in that class. * @param finder a RoomFinder that must not be null. */ public Placement(RoomFinder finder) { if(finder == null) throw new UnsupportedOperationException("RoomFinder passed to Placement constructor cannot be null"); this.finder = finder; /* allRooms = new GreasedRegion(finder.width, finder.height); allCorridors = new GreasedRegion(finder.width, finder.height); allCaves = new GreasedRegion(finder.width, finder.height); allFloors = new GreasedRegion(finder.width, finder.height); for(GreasedRegion region : finder.rooms.keySet()) { allRooms.or(region); } for(GreasedRegion region : finder.corridors.keySet()) { allCorridors.or(region); } for(GreasedRegion region : finder.caves.keySet()) { allCaves.or(region); } */ allCorridors = finder.allCorridors; allRooms = finder.allRooms; allCaves = finder.allCaves; allFloors = allRooms.copy().or(allCorridors).or(allCaves); nonRoom = allCorridors.copy().or(allCaves).expand(2); } /** * Gets an OrderedSet of OrderedSet of Coord, where each inner OrderedSet of Coord refers to a placement * region along a straight wall with length 3 or more, not including corners. Each Coord refers to a single cell * along the straight wall. This could be useful for placing weapon racks in armories, chalkboards in schoolrooms * (tutorial missions, perhaps?), or even large paintings/murals in palaces. * @return a set of sets of Coord where each set of Coord is a wall's viable placement for long things along it */ public OrderedSet<OrderedSet<Coord>> getAlongStraightWalls() { if(alongStraightWalls == null) { alongStraightWalls = new OrderedSet<>(32); GreasedRegion working = new GreasedRegion(finder.width, finder.height); for(GreasedRegion region : finder.rooms.keySet()) { working.remake(region).retract().fringe().andNot(nonRoom); for (GreasedRegion sp : working.split()) { if (sp.size() >= 3) alongStraightWalls.add(arrayToSet(sp.asCoords())); } } } return alongStraightWalls; } /** * Gets an OrderedSet of OrderedSet of Coord, where each inner OrderedSet of Coord refers to a room's * corners, and each Coord is one of those corners. There are more uses for corner placement than I can list. This * doesn't always identify all corners, since it only finds ones in rooms, and a cave too close to a corner can * cause that corner to be ignored. * @return a set of sets of Coord where each set of Coord is a room's corners */ public OrderedSet<OrderedSet<Coord>> getCorners() { if(corners == null) { corners = new OrderedSet<>(32); GreasedRegion working = new GreasedRegion(finder.width, finder.height); for(GreasedRegion region : finder.rooms.keySet()) { working.remake(region).expand().retract8way().xor(region).andNot(nonRoom); OrderedSet<Coord> os = new OrderedSet<>(working.asCoords()); corners.add(os); } } return corners; } /** * Gets an OrderedSet of OrderedSet of Coord, where each inner OrderedSet of Coord refers to a room's cells * that are furthest from the walls, and each Coord is one of those central positions. There are many uses for this, * like finding a position to place a throne or shrine in a large room where it should be visible from all around. * This doesn't always identify all centers, since it only finds ones in rooms, and it can also find multiple * central points if they are all the same distance from a wall (common in something like a 3x7 room, where it will * find a 1x5 column as the centers of that room). * @return a set of sets of Coord where each set of Coord contains a room's cells that are furthest from the walls. */ public OrderedSet<OrderedSet<Coord>> getCenters() { if(centers == null) { centers = new OrderedSet<>(32); GreasedRegion working, working2; for(GreasedRegion region : finder.rooms.keySet()) { working = null; working2 = region.copy().retract(); for (int i = 2; i < 7; i++) { if(working2.isEmpty()) break; working = working2.copy(); working2.retract(); } if(working == null) continue; //working = // differencePacked( // working, // nonRoom); centers.add(arrayToSet(working.asCoords())); } } return centers; } /** * Gets an OrderedSet of Coord, where each Coord is hidden (using the given radiusStrategy and range for FOV * calculations) from any doorways or similar narrow choke-points where a character might be easily ambushed. If * multiple choke-points can see a cell (using shadow-casting FOV, which is asymmetrical), then the cell is very * unlikely to be included in the returned Coords, but if a cell is visible from one or no choke-points and is far * enough away, then it is more likely to be included. * @param radiusStrategy a Radius object that will be used to determine visibility. * @param range the minimum distance things are expected to hide at; often related to player FOV range * @return a Set of Coord where each Coord is either far away from or is concealed from a door-like area */ public OrderedSet<Coord> getHidingPlaces(Radius radiusStrategy, int range) { if(hidingPlaces == null) { double[][] composite = new double[finder.width][finder.height], resMap = DungeonUtility.generateResistances(finder.map), temp; FOV fov = new FOV(FOV.SHADOW); Coord pt; for (int d = 0; d < finder.connections.length; d++) { pt = finder.connections[d]; temp = fov.calculateFOV(resMap, pt.x, pt.y, range, radiusStrategy); for (int x = 0; x < finder.width; x++) { for (int y = 0; y < finder.height; y++) { composite[x][y] += temp[x][y] * temp[x][y]; } } } hidingPlaces = arrayToSet(new GreasedRegion(composite, 0.25).and(allFloors).asCoords()); } return hidingPlaces; } private static OrderedSet<Coord> arrayToSet(Coord[] arr) { return new OrderedSet<> (arr); } }