Index: src/town_cmd.cpp =================================================================== --- src/town_cmd.cpp (revision 15495) +++ src/town_cmd.cpp (working copy) @@ -1556,6 +1556,75 @@ return CommandCost(); } +CommandCost CmdPlaceSign(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text); +void debug(char *str, TileIndex tile) +{ + if (tile != INVALID_TILE) + CmdPlaceSign(tile, DC_EXEC, 0, 0, str); +} + +/** + * used as the user_data for FindFurthestFromWater + * tile holds the tile that was found + * max holds the distance that tile is from the water + * radius tells us how far to search + */ +struct spot_data { TileIndex tile; uint max; uint radius; }; + +/** + * 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. + */ +static bool FindFurthestFromWater(TileIndex tile, void *user_data) +{ + spot_data *sp = (spot_data*)user_data; + uint dist = GetClosestWaterDistance(tile, true); + + char d[5] = "\0\0\0\0"; + sprintf(&d[0], "%i", dist); + debug(&d[0], tile); + + if (IsTileType(tile, MP_CLEAR) && + GetTileSlope(tile, NULL) == SLOPE_FLAT && + dist > sp->max) { + sp->tile = tile; + sp->max = dist; + } + + return false; +} + +/** + * 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. + */ +static TileIndex FindNearestGoodCoastalTownSpot(TileIndex tile) +{ + spot_data sp = { INVALID_TILE, 0, 10 }; + + debug("start", tile); + TileIndex coast = tile; + if (CircularTileSearch(&coast, 40, FindNearestFlatLand, NULL)) { + debug("coast", coast); + CircularTileSearch(&coast, sp.radius, FindFurthestFromWater, &sp); + debug("final", sp.tile); + return sp.tile; + } + + // if we get here, all we can do is give up and return the original tile + return tile; +} + Town *CreateRandomTown(uint attempts, TownSize size, bool city, TownLayout layout) { if (!Town::CanAllocateItem()) return NULL; @@ -1572,6 +1641,14 @@ break; default: break; } + + /* 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); + if (tile == INVALID_TILE) continue; + } + if (DistanceFromEdge(tile) < 20) continue; /* Make sure the tile is plain */ @@ -1580,16 +1657,22 @@ /* Check not too close to a town */ if (IsCloseToTown(tile, 20)) continue; + /* Get a unique name for the town. */ uint32 townnameparts; - - /* Get a unique name for the town. */ if (!CreateTownName(&townnameparts)) break; /* Allocate a town struct */ Town *t = new Town(tile); DoCreateTown(t, tile, townnameparts, size, city, layout); - return t; + + // this apparently didn't happen very often before, but it was + // always a possibility + if (t->population > 0) { + return t; + } else { + delete t; + } } while (--attempts != 0); return NULL; Index: src/map_func.h =================================================================== --- src/map_func.h (revision 15495) +++ src/map_func.h (working copy) @@ -399,6 +399,11 @@ bool CircularTileSearch(TileIndex *tile, uint radius, uint w, uint h, TestTileOnSearchProc proc, void *user_data); /** + * CircularTileSearch callback; finds the nearest land tile + */ +bool FindNearestFlatLand(TileIndex tile, void *user_data); + +/** * Get a random tile out of a given seed. * @param r the random 'seed' * @return a valid tile @@ -416,4 +421,13 @@ */ #define RandomTile() RandomTileSeed(Random()) +/** + * Finds the distance for the closest tile with water/land given a tile + * @param tile the tile to find the distance too + * @param water whether to find water or land + * @return distance to nearest water (max 0x7F) / land (max 0x1FF; 0x200 if there is no land) + * @note FAILS when an industry should be seen as water + */ +uint GetClosestWaterDistance(TileIndex tile, bool water); + #endif /* MAP_FUNC_H */ Index: src/map.cpp =================================================================== --- src/map.cpp (revision 15495) +++ src/map.cpp (working copy) @@ -8,6 +8,7 @@ #include "core/alloc_func.hpp" #include "core/math_func.hpp" #include "map_func.h" +#include "tile_map.h" #if defined(_MSC_VER) /* Why the hell is that not in all MSVC headers?? */ @@ -332,3 +333,64 @@ *tile = INVALID_TILE; return false; } + +/** + * Finds the distance for the closest tile with water/land given a tile + * @param tile the tile to find the distance too + * @param water whether to find water or land + * @return distance to nearest water (max 0x7F) / land (max 0x1FF; 0x200 if there is no land) + * @note FAILS when an industry should be seen as water + */ +uint GetClosestWaterDistance(TileIndex tile, bool water) +{ + if (IsTileType(tile, MP_WATER) == water) return 0; + + uint max_dist = water ? 0x7F : 0x200; + + int x = TileX(tile); + int y = TileY(tile); + + uint max_x = MapMaxX(); + uint max_y = MapMaxY(); + uint min_xy = _settings_game.construction.freeform_edges ? 1 : 0; + + /* go in a 'spiral' with increasing manhattan distance in each iteration */ + for (uint dist = 1; dist < max_dist; dist++) { + /* next 'diameter' */ + y--; + + /* going counter-clockwise around this square */ + for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { + static const int8 ddx[DIAGDIR_END] = { -1, 1, 1, -1}; + static const int8 ddy[DIAGDIR_END] = { 1, 1, -1, -1}; + + int dx = ddx[dir]; + int dy = ddy[dir]; + + /* each side of this square has length 'dist' */ + for (uint a = 0; a < dist; a++) { + /* MP_VOID tiles are not checked (interval is [min; max) for IsInsideMM())*/ + if (IsInsideMM(x, min_xy, max_x) && IsInsideMM(y, min_xy, max_y)) { + TileIndex t = TileXY(x, y); + if (IsTileType(t, MP_WATER) == water) return dist; + } + x += dx; + y += dy; + } + } + } + + if (!water) { + /* no land found - is this a water-only map? */ + for (TileIndex t = 0; t < MapSize(); t++) { + if (!IsTileType(t, MP_VOID) && !IsTileType(t, MP_WATER)) return 0x1FF; + } + } + + return max_dist; +} + +bool FindNearestFlatLand(TileIndex tile, void *user_data) +{ + return IsTileType(tile, MP_CLEAR); +} Index: src/newgrf_industries.cpp =================================================================== --- src/newgrf_industries.cpp (revision 15495) +++ src/newgrf_industries.cpp (working copy) @@ -36,62 +36,6 @@ return _industry_mngr.GetID(GB(grf_type, 0, 6), grf_id); } -/** - * Finds the distance for the closest tile with water/land given a tile - * @param tile the tile to find the distance too - * @param water whether to find water or land - * @return distance to nearest water (max 0x7F) / land (max 0x1FF; 0x200 if there is no land) - * @note FAILS when an industry should be seen as water - */ -static uint GetClosestWaterDistance(TileIndex tile, bool water) -{ - if (IsTileType(tile, MP_WATER) == water) return 0; - - uint max_dist = water ? 0x7F : 0x200; - - int x = TileX(tile); - int y = TileY(tile); - - uint max_x = MapMaxX(); - uint max_y = MapMaxY(); - uint min_xy = _settings_game.construction.freeform_edges ? 1 : 0; - - /* go in a 'spiral' with increasing manhattan distance in each iteration */ - for (uint dist = 1; dist < max_dist; dist++) { - /* next 'diameter' */ - y--; - - /* going counter-clockwise around this square */ - for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { - static const int8 ddx[DIAGDIR_END] = { -1, 1, 1, -1}; - static const int8 ddy[DIAGDIR_END] = { 1, 1, -1, -1}; - - int dx = ddx[dir]; - int dy = ddy[dir]; - - /* each side of this square has length 'dist' */ - for (uint a = 0; a < dist; a++) { - /* MP_VOID tiles are not checked (interval is [min; max) for IsInsideMM())*/ - if (IsInsideMM(x, min_xy, max_x) && IsInsideMM(y, min_xy, max_y)) { - TileIndex t = TileXY(x, y); - if (IsTileType(t, MP_WATER) == water) return dist; - } - x += dx; - y += dy; - } - } - } - - if (!water) { - /* no land found - is this a water-only map? */ - for (TileIndex t = 0; t < MapSize(); t++) { - if (!IsTileType(t, MP_VOID) && !IsTileType(t, MP_WATER)) return 0x1FF; - } - } - - return max_dist; -} - /** Make an analysis of a tile and check for its belonging to the same * industry, and/or the same grf file * @param tile TileIndex of the tile to query