001package squidpony.squidgrid.mapping; 002 003import squidpony.squidgrid.FOV; 004import squidpony.squidgrid.Radius; 005import squidpony.squidmath.Coord; 006 007import java.util.Collections; 008import java.util.LinkedHashSet; 009 010import static squidpony.squidmath.CoordPacker.*; 011 012/** 013 * Utility class for finding areas where game-specific terrain features might be suitable to place. 014 * Example placement for alongStraightWalls, using all regions where there's an extended straight wall in a room to 015 * place a rack of bows (as curly braces): https://gist.github.com/tommyettinger/2b69a265bd93304f091b 016 * Created by Tommy Ettinger on 3/13/2016. 017 */ 018public class Placement { 019 020 /** 021 * The RoomFinder this uses internally to find placement areas only where they are appropriate. 022 */ 023 public RoomFinder finder; 024 025 private short[] allRooms = ALL_WALL, allCaves = ALL_WALL, allCorridors = ALL_WALL, allFloors = ALL_WALL, 026 nonRoom; 027 private LinkedHashSet<LinkedHashSet<Coord>> alongStraightWalls = null, 028 corners = null, centers = null; 029 private LinkedHashSet<Coord> hidingPlaces = null; 030 private Placement() 031 { 032 033 } 034 035 /** 036 * Constructs a Placement using the given RoomFinder, which will have collections of rooms, corridors, and caves. 037 * A common use case for this class involves the Placement field that is constructed in a SectionDungeonGenerator 038 * when generate() or generateRespectingStairs() in that class is called; if you use SectionDungeonGenerator, there 039 * isn't much need for this constructor, since you can normally use the one created as a field in that class. 040 * @param finder a RoomFinder that must not be null. 041 */ 042 public Placement(RoomFinder finder) 043 { 044 if(finder == null) 045 throw new UnsupportedOperationException("RoomFinder passed to Placement constructor cannot be null"); 046 047 this.finder = finder; 048 049 for(short[] region : finder.rooms.keys()) 050 { 051 allRooms = unionPacked(allRooms, region); 052 } 053 for(short[] region : finder.caves.keys()) 054 { 055 allCaves = unionPacked(allCaves, region); 056 } 057 for(short[] region : finder.corridors.keys()) 058 { 059 allCorridors = unionPacked(allCorridors, region); 060 } 061 allFloors = unionPacked(unionPacked(allRooms, allCorridors), allCaves); 062 nonRoom = expand(unionPacked(allCorridors, allCaves), 2, finder.width, finder.height, false); 063 } 064 065 /** 066 * Gets a LinkedHashSet of LinkedHashSet of Coord, where each inner LinkedHashSet of Coord refers to a placement 067 * region along a straight wall with length 3 or more, not including corners. Each Coord refers to a single cell 068 * along the straight wall. This could be useful for placing weapon racks in armories, chalkboards in schoolrooms 069 * (tutorial missions, perhaps?), or even large paintings/murals in palaces. 070 * @return a set of sets of Coord where each set of Coord is a wall's viable placement for long things along it 071 */ 072 public LinkedHashSet<LinkedHashSet<Coord>> getAlongStraightWalls() { 073 if(alongStraightWalls == null) 074 { 075 alongStraightWalls = new LinkedHashSet<>(32); 076 short[] working; 077 for(short[] region : finder.rooms.keys()) { 078 working = 079 differencePacked( 080 fringe( 081 retract(region, 1, finder.width, finder.height, false), 082 1, finder.width, finder.height, false), 083 nonRoom); 084 for (short[] sp : split(working)) { 085 if (count(sp) >= 3) 086 alongStraightWalls.add(arrayToSet(allPacked(sp))); 087 } 088 } 089 090 } 091 return alongStraightWalls; 092 } 093 094 /** 095 * Gets a LinkedHashSet of LinkedHashSet of Coord, where each inner LinkedHashSet of Coord refers to a room's 096 * corners, and each Coord is one of those corners. There are more uses for corner placement than I can list. This 097 * doesn't always identify all corners, since it only finds ones in rooms, and a cave too close to a corner can 098 * cause that corner to be ignored. 099 * @return a set of sets of Coord where each set of Coord is a room's corners 100 */ 101 public LinkedHashSet<LinkedHashSet<Coord>> getCorners() { 102 if(corners == null) 103 { 104 corners = new LinkedHashSet<>(32); 105 short[] working; 106 for(short[] region : finder.rooms.keys()) { 107 working = 108 differencePacked( 109 differencePacked(region, 110 retract( 111 expand(region, 1, finder.width, finder.height, false), 112 1, finder.width, finder.height, true) 113 ), 114 nonRoom); 115 for(Coord c : allPacked(working)) 116 { 117 LinkedHashSet<Coord> lhs = new LinkedHashSet<Coord>(); 118 lhs.add(c); 119 corners.add(lhs); 120 } 121 122 } 123 } 124 return corners; 125 } 126 /** 127 * Gets a LinkedHashSet of LinkedHashSet of Coord, where each inner LinkedHashSet of Coord refers to a room's cells 128 * that are furthest from the walls, and each Coord is one of those central positions. There are many uses for this, 129 * like finding a position to place a throne or shrine in a large room where it should be visible from all around. 130 * This doesn't always identify all centers, since it only finds ones in rooms, and it can also find multiple 131 * central points if they are all the same distance from a wall (common in something like a 3x7 room, where it will 132 * find a 1x5 column as the centers of that room). 133 * @return a set of sets of Coord where each set of Coord contains a room's cells that are furthest from the walls. 134 */ 135 public LinkedHashSet<LinkedHashSet<Coord>> getCenters() { 136 if(centers == null) 137 { 138 centers = new LinkedHashSet<>(32); 139 short[] working, working2; 140 for(short[] region : finder.rooms.keys()) { 141 142 working = null; 143 working2 = retract(region, 1, finder.width, finder.height, false); 144 for (int i = 2; i < 7; i++) { 145 if(count(working2) <= 0) 146 break; 147 working = working2; 148 working2 = retract(region, i, finder.width, finder.height, false); 149 } 150 if(working == null) 151 continue; 152 153 //working = 154 // differencePacked( 155 // working, 156 // nonRoom); 157 centers.add(arrayToSet(allPacked(working))); 158 159 } 160 } 161 return centers; 162 } 163 164 /** 165 * Gets a LinkedHashSet of Coord, where each Coord is hidden (using the given radiusStrategy and range for FOV 166 * calculations) from any doorways or similar narrow choke-points where a character might be easily ambushed. If 167 * multiple choke-points can see a cell (using shadow-casting FOV, which is asymmetrical), then the cell is very 168 * unlikely to be included in the returned Coords, but if a cell is visible from one or no choke-points and is far 169 * enough away, then it is more likely to be included. 170 * @param radiusStrategy a Radius object that will be used to determine visibility. 171 * @param range the minimum distance things are expected to hide at; often related to player FOV range 172 * @return a set of Coord where each Coord is either far away from or is concealed from a door-like area 173 */ 174 public LinkedHashSet<Coord> getHidingPlaces(Radius radiusStrategy, int range) { 175 if(hidingPlaces == null) 176 { 177 double[][] composite = new double[finder.width][finder.height], 178 resMap = DungeonUtility.generateResistances(finder.map), 179 temp; 180 FOV fov = new FOV(FOV.SHADOW); 181 Coord pt; 182 for (int d = 0; d < finder.connections.length; d++) { 183 pt = finder.connections[d]; 184 temp = fov.calculateFOV(resMap, pt.x, pt.y, range, radiusStrategy); 185 for (int x = 0; x < finder.width; x++) { 186 for (int y = 0; y < finder.height; y++) { 187 composite[x][y] += temp[x][y] * temp[x][y]; 188 } 189 } 190 } 191 hidingPlaces = arrayToSet(allPacked(intersectPacked(allFloors, pack(composite, 0.25)))); 192 } 193 return hidingPlaces; 194 } 195 196 private static LinkedHashSet<Coord> arrayToSet(Coord[] arr) 197 { 198 LinkedHashSet<Coord> lhs = new LinkedHashSet<>(arr.length); 199 Collections.addAll(lhs, arr); 200 return lhs; 201 } 202}