(svn r14591) -Fix [FS#2388](r14528): cached nearest town could be invalid after importing older savegame and during town generation

-Codechange: rewrite parts of code responsible for caching index of nearest town
This commit is contained in:
smatz 2008-11-18 22:43:59 +00:00
parent e1a36e90a4
commit 07e3c096b3
7 changed files with 70 additions and 41 deletions

View File

@ -513,7 +513,7 @@
<td valign=top nowrap>&nbsp;</td> <td valign=top nowrap>&nbsp;</td>
<td> <td>
<ul> <ul>
<li>m2: Index into the array of towns (owning town for town roads; closest town otherwise, INVALID_TOWN if not yet calculated)</li> <li>m2: Index into the array of towns (owning town for town roads; closest town otherwise, INVALID_TOWN if there is no town or we are creating a town)</li>
<li>m3 bit 7 set = on snow or desert</li> <li>m3 bit 7 set = on snow or desert</li>
<li>m7 bits 7..5: present road types <li>m7 bits 7..5: present road types
<table> <table>

View File

@ -2563,7 +2563,7 @@ bool AfterLoadGame()
if (CheckSavegameVersion(103)) { if (CheckSavegameVersion(103)) {
/* Non-town-owned roads now store the closest town */ /* Non-town-owned roads now store the closest town */
InvalidateTownForRoadTile(); UpdateNearestTownForRoadTiles(false);
/* signs with invalid owner left from older savegames */ /* signs with invalid owner left from older savegames */
Sign *si; Sign *si;

View File

@ -319,9 +319,9 @@ static CommandCost RemoveRoad(TileIndex tile, uint32 flags, RoadBits pieces, Roa
DoClearSquare(tile); DoClearSquare(tile);
} else { } else {
if (rt == ROADTYPE_ROAD && IsRoadOwner(tile, ROADTYPE_ROAD, OWNER_TOWN)) { if (rt == ROADTYPE_ROAD && IsRoadOwner(tile, ROADTYPE_ROAD, OWNER_TOWN)) {
/* Promote ownership from tram or highway and invalidate town index */ /* Update nearest-town index */
SetRoadOwner(tile, ROADTYPE_ROAD, GetRoadOwner(tile, (HasBit(rts, ROADTYPE_TRAM) ? ROADTYPE_TRAM : ROADTYPE_HWAY))); const Town *town = CalcClosestTownFromTile(tile, UINT_MAX);
SetTownIndex(tile, (TownID)INVALID_TOWN); SetTownIndex(tile, town == NULL ? (TownID)INVALID_TOWN : town->index);
} }
SetRoadBits(tile, ROAD_NONE, rt); SetRoadBits(tile, ROAD_NONE, rt);
SetRoadTypes(tile, rts); SetRoadTypes(tile, rts);
@ -348,7 +348,7 @@ static CommandCost RemoveRoad(TileIndex tile, uint32 flags, RoadBits pieces, Roa
} }
/* Don't allow road to be removed from the crossing when there is tram; /* Don't allow road to be removed from the crossing when there is tram;
* we can't draw the crossing without trambits ;) */ * we can't draw the crossing without roadbits ;) */
if (rt == ROADTYPE_ROAD && HasTileRoadType(tile, ROADTYPE_TRAM) && (flags & DC_EXEC || crossing_check)) return CMD_ERROR; if (rt == ROADTYPE_ROAD && HasTileRoadType(tile, ROADTYPE_TRAM) && (flags & DC_EXEC || crossing_check)) return CMD_ERROR;
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
@ -1274,14 +1274,22 @@ void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt)
} }
} }
void InvalidateTownForRoadTile() /** Updates cached nearest town for all road tiles
* @param invalidate are we just invalidating cached data?
* @pre invalidate == true implies _generating_world == true
*/
void UpdateNearestTownForRoadTiles(bool invalidate)
{ {
TileIndex map_size = MapSize(); assert(!invalidate || _generating_world);
for (TileIndex t = 0; t < map_size; t++) { for (TileIndex t = 0; t < MapSize(); t++) {
if (IsTileType(t, MP_ROAD) && GetRoadOwner(t, ROADTYPE_ROAD) != OWNER_TOWN) { if (IsTileType(t, MP_ROAD) && !HasTownOwnedRoad(t)) {
/* GetRoadOwner(t, ROADTYPE_ROAD) is valid for road tiles even when there is no road */ TownID tid = (TownID)INVALID_TOWN;
SetTownIndex(t, (TownID)INVALID_TOWN); if (!invalidate) {
const Town *town = CalcClosestTownFromTile(t, UINT_MAX);
if (town != NULL) tid = town->index;
}
SetTownIndex(t, tid);
} }
} }
} }

View File

@ -8,6 +8,6 @@
#include "direction_type.h" #include "direction_type.h"
void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt); void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt);
void InvalidateTownForRoadTile(); void UpdateNearestTownForRoadTiles(bool invalidate);
#endif /* ROAD_CMD_H */ #endif /* ROAD_CMD_H */

View File

@ -189,10 +189,21 @@ static inline void SetRoadOwner(TileIndex t, RoadType rt, Owner o)
static inline bool IsRoadOwner(TileIndex t, RoadType rt, Owner o) static inline bool IsRoadOwner(TileIndex t, RoadType rt, Owner o)
{ {
assert(rt == ROADTYPE_ROAD || HasTileRoadType(t, rt)); assert(HasTileRoadType(t, rt));
return (GetRoadOwner(t, rt) == o); return (GetRoadOwner(t, rt) == o);
} }
/** Checks if given tile has town owned road
* @param t tile to check
* @return true iff tile has road and the road is owned by a town
* @pre IsTileType(t, MP_ROAD)
*/
static inline bool HasTownOwnedRoad(TileIndex t)
{
assert(IsTileType(t, MP_ROAD));
return HasTileRoadType(t, ROADTYPE_ROAD) && IsRoadOwner(t, ROADTYPE_ROAD, OWNER_TOWN);
}
/** Which directions are disallowed ? */ /** Which directions are disallowed ? */
enum DisallowedRoadDirections { enum DisallowedRoadDirections {
DRD_NONE, ///< None of the directions are disallowed DRD_NONE, ///< None of the directions are disallowed

View File

@ -94,12 +94,12 @@ Town::~Town()
break; break;
case MP_ROAD: case MP_ROAD:
if (!IsRoadOwner(tile, ROADTYPE_ROAD, OWNER_TOWN) && GetTownIndex(tile) == this->index) { /* Cached nearest town is updated later (after this town has been deleted) */
/* Town-owned roads get cleared soon, anyway */ if (HasTownOwnedRoad(tile) && GetTownIndex(tile) == this->index) {
SetTownIndex(tile, (TownID)INVALID_TOWN); DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR);
break;
} }
/* Fall-through */ break;
case MP_TUNNELBRIDGE: case MP_TUNNELBRIDGE:
if (IsTileOwner(tile, OWNER_TOWN) && if (IsTileOwner(tile, OWNER_TOWN) &&
ClosestTownFromTile(tile, UINT_MAX) == this) ClosestTownFromTile(tile, UINT_MAX) == this)
@ -116,6 +116,8 @@ Town::~Town()
MarkWholeScreenDirty(); MarkWholeScreenDirty();
this->xy = 0; this->xy = 0;
UpdateNearestTownForRoadTiles(false);
} }
/** /**
@ -1564,8 +1566,9 @@ CommandCost CmdBuildTown(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
Town *t = new Town(tile); Town *t = new Town(tile);
_generating_world = true; _generating_world = true;
UpdateNearestTownForRoadTiles(true);
DoCreateTown(t, tile, townnameparts, (TownSizeMode)p2, p1); DoCreateTown(t, tile, townnameparts, (TownSizeMode)p2, p1);
InvalidateTownForRoadTile(); UpdateNearestTownForRoadTiles(false);
_generating_world = false; _generating_world = false;
} }
return CommandCost(); return CommandCost();
@ -2471,25 +2474,31 @@ Town *CalcClosestTownFromTile(TileIndex tile, uint threshold)
Town *ClosestTownFromTile(TileIndex tile, uint threshold) Town *ClosestTownFromTile(TileIndex tile, uint threshold)
{ {
if (IsTileType(tile, MP_HOUSE) || ( switch (GetTileType(tile)) {
IsTileType(tile, MP_ROAD) && HasTileRoadType(tile, ROADTYPE_ROAD) && case MP_ROAD:
IsRoadOwner(tile, ROADTYPE_ROAD, OWNER_TOWN) if (!HasTownOwnedRoad(tile)) {
)) { TownID tid = GetTownIndex(tile);
return GetTownByTile(tile); if (tid == (TownID)INVALID_TOWN) {
} else if (IsTileType(tile, MP_ROAD)) { /* in the case we are generating "many random towns", this value may be INVALID_TOWN */
TownID town_id = GetTownIndex(tile); if (_generating_world) CalcClosestTownFromTile(tile, threshold);
Town *town; assert(GetNumTowns() == 0);
return NULL;
if (town_id == INVALID_TOWN) {
town = CalcClosestTownFromTile(tile, UINT_MAX);
if (town != NULL) SetTownIndex(tile, town->index);
} else {
town = GetTown(town_id);
} }
if (town != NULL && town->IsValid() && DistanceManhattan(tile, town->xy) < threshold) return town; Town *town = GetTown(tid);
return NULL; assert(town->IsValid());
} else { assert(town == CalcClosestTownFromTile(tile, UINT_MAX));
if (DistanceManhattan(tile, town->xy) >= threshold) town = NULL;
return town;
}
/* FALL THROUGH */
case MP_HOUSE:
return GetTownByTile(tile);
default:
return CalcClosestTownFromTile(tile, threshold); return CalcClosestTownFromTile(tile, threshold);
} }
} }

View File

@ -725,14 +725,15 @@ public:
this->HandleButtonClick(TSEW_RANDOMTOWN); this->HandleButtonClick(TSEW_RANDOMTOWN);
_generating_world = true; _generating_world = true;
UpdateNearestTownForRoadTiles(true);
t = CreateRandomTown(20, mode, size); t = CreateRandomTown(20, mode, size);
UpdateNearestTownForRoadTiles(false);
_generating_world = false; _generating_world = false;
if (t == NULL) { if (t == NULL) {
ShowErrorMessage(STR_NO_SPACE_FOR_TOWN, STR_CANNOT_GENERATE_TOWN, 0, 0); ShowErrorMessage(STR_NO_SPACE_FOR_TOWN, STR_CANNOT_GENERATE_TOWN, 0, 0);
} else { } else {
ScrollMainWindowToTile(t->xy); ScrollMainWindowToTile(t->xy);
InvalidateTownForRoadTile();
} }
} break; } break;
@ -740,11 +741,11 @@ public:
this->HandleButtonClick(TSEW_MANYRANDOMTOWNS); this->HandleButtonClick(TSEW_MANYRANDOMTOWNS);
_generating_world = true; _generating_world = true;
UpdateNearestTownForRoadTiles(true);
if (!GenerateTowns()) { if (!GenerateTowns()) {
ShowErrorMessage(STR_NO_SPACE_FOR_TOWN, STR_CANNOT_GENERATE_TOWN, 0, 0); ShowErrorMessage(STR_NO_SPACE_FOR_TOWN, STR_CANNOT_GENERATE_TOWN, 0, 0);
} else {
InvalidateTownForRoadTile();
} }
UpdateNearestTownForRoadTiles(false);
_generating_world = false; _generating_world = false;
break; break;