summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/town_cmd.cpp135
1 files changed, 125 insertions, 10 deletions
diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp
index a9420b459..9b46343db 100644
--- a/src/town_cmd.cpp
+++ b/src/town_cmd.cpp
@@ -1604,21 +1604,132 @@ CommandCost CmdBuildTown(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
return CommandCost();
}
+/**
+ * Towns must all be placed on the same grid or when they eventually
+ * interpenetrate their road networks will not mesh nicely; this
+ * function adjusts a tile so that it aligns properly.
+ *
+ * @param tile the tile to start at
+ * @param layout which town layout algo is in effect
+ * @return the adjusted tile
+ */
+static TileIndex AlignTileToGrid(TileIndex tile, TownLayout layout)
+{
+ switch (layout) {
+ case TL_2X2_GRID: return TileXY(TileX(tile) - TileX(tile) % 3, TileY(tile) - TileY(tile) % 3);
+ case TL_3X3_GRID: return TileXY(TileX(tile) & ~3, TileY(tile) & ~3);
+ default: return tile;
+ }
+}
+
+/**
+ * Towns must all be placed on the same grid or when they eventually
+ * interpenetrate their road networks will not mesh nicely; this
+ * function tells you if a tile is properly aligned.
+ *
+ * @param tile the tile to start at
+ * @param layout which town layout algo is in effect
+ * @return true if the tile is in the correct location
+ */
+static bool IsTileAlignedToGrid(TileIndex tile, TownLayout layout)
+{
+ switch (layout) {
+ case TL_2X2_GRID: return TileX(tile) % 3 == 0 && TileY(tile) % 3 == 0;
+ case TL_3X3_GRID: return TileX(tile) % 4 == 0 && TileY(tile) % 4 == 0;
+ default: return true;
+ }
+}
+
+/**
+ * Used as the user_data for FindFurthestFromWater
+ */
+struct SpotData {
+ TileIndex tile; ///< holds the tile that was found
+ uint max_dist; ///< holds the distance that tile is from the water
+ TownLayout layout; ///< tells us what kind of town we're building
+};
+
+/**
+ * CircularTileSearch callback; finds the tile furthest from any
+ * water. slightly bit tricky, since it has to do a search of it's own
+ * in order to find the distance to the water from each square in the
+ * radius.
+ *
+ * Also, this never returns true, because it needs to take into
+ * account all locations being searched before it knows which is the
+ * furthest.
+ *
+ * @param tile Start looking from this tile
+ * @param user_data Storage area for data that must last across calls;
+ * must be a pointer to struct SpotData
+ *
+ * @return always false
+ */
+static bool FindFurthestFromWater(TileIndex tile, void *user_data)
+{
+ SpotData *sp = (SpotData*)user_data;
+ uint dist = GetClosestWaterDistance(tile, true);
+
+ if (IsTileType(tile, MP_CLEAR) &&
+ GetTileSlope(tile, NULL) == SLOPE_FLAT &&
+ IsTileAlignedToGrid(tile, sp->layout) &&
+ dist > sp->max_dist) {
+ sp->tile = tile;
+ sp->max_dist = dist;
+ }
+
+ return false;
+}
+
+/**
+ * CircularTileSearch callback; finds the nearest land tile
+ *
+ * @param tile Start looking from this tile
+ * @param user_data not used
+ */
+static bool FindNearestEmptyLand(TileIndex tile, void *user_data)
+{
+ return IsTileType(tile, MP_CLEAR);
+}
+
+/**
+ * Given a spot on the map (presumed to be a water tile), find a good
+ * coastal spot to build a city. We don't want to build too close to
+ * the edge if we can help it (since that retards city growth) hence
+ * the search within a search within a search. O(n*m^2), where n is
+ * how far to search for land, and m is how far inland to look for a
+ * flat spot.
+ *
+ * @param tile Start looking from this spot.
+ * @return tile that was found
+ */
+static TileIndex FindNearestGoodCoastalTownSpot(TileIndex tile, TownLayout layout)
+{
+ SpotData sp = { INVALID_TILE, 0, layout };
+
+ TileIndex coast = tile;
+ if (CircularTileSearch(&coast, 40, FindNearestEmptyLand, NULL)) {
+ CircularTileSearch(&coast, 10, FindFurthestFromWater, &sp);
+ return sp.tile;
+ }
+
+ /* if we get here just give up */
+ return INVALID_TILE;
+}
+
Town *CreateRandomTown(uint attempts, TownSize size, bool city, TownLayout layout)
{
if (!Town::CanAllocateItem()) return NULL;
do {
/* Generate a tile index not too close from the edge */
- TileIndex tile = RandomTile();
- switch (layout) {
- case TL_2X2_GRID:
- tile = TileXY(TileX(tile) - TileX(tile) % 3, TileY(tile) - TileY(tile) % 3);
- break;
- case TL_3X3_GRID:
- tile = TileXY(TileX(tile) & ~3, TileY(tile) & ~3);
- break;
- default: break;
+ TileIndex tile = AlignTileToGrid(RandomTile(), layout);
+
+ /* if we tried to place the town on water, slide it over onto
+ * the nearest likely-looking spot */
+ if (IsTileType(tile, MP_WATER)) {
+ tile = FindNearestGoodCoastalTownSpot(tile, layout);
+ if (tile == INVALID_TILE) continue;
}
/* Make sure town can be placed here */
@@ -1632,7 +1743,11 @@ Town *CreateRandomTown(uint attempts, TownSize size, bool city, TownLayout layou
Town *t = new Town(tile);
DoCreateTown(t, tile, townnameparts, size, city, layout);
- return t;
+
+ /* if the population is still 0 at the point, then the
+ * placement is so bad it couldn't grow at all */
+ if (t->population > 0) return t;
+ delete t;
} while (--attempts != 0);
return NULL;