diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index beb6c0b6c4..4163d168a9 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -199,7 +199,7 @@ void StartGeneratingLandscape(glwp_modes mode) } } -static void HeightmapScaledTooMuchCallback(Window *w, bool confirmed) +static void LandscapeGenerationCallback(Window *w, bool confirmed) { if (confirmed) StartGeneratingLandscape((glwp_modes)w->window_number); } @@ -354,14 +354,26 @@ static void GenerateLandscapeWndProc(Window *w, WindowEvent *e) SetWindowDirty(w); break; case GLAND_GENERATE_BUTTON: // Generate - if (mode == GLWP_HEIGHTMAP && ( - _heightmap_x * 2 < (1U << _patches_newgame.map_x) || _heightmap_x / 2 > (1U << _patches_newgame.map_x) || - _heightmap_y * 2 < (1U << _patches_newgame.map_y) || _heightmap_y / 2 > (1U << _patches_newgame.map_y))) { + + UpdatePatches(); + + if (_patches.town_layout == TL_NO_ROADS) { + ShowQuery( + STR_TOWN_LAYOUT_WARNING_CAPTION, + STR_TOWN_LAYOUT_WARNING_MESSAGE, + w, + LandscapeGenerationCallback); + } else if (mode == GLWP_HEIGHTMAP && + (_heightmap_x * 2 < (1U << _patches_newgame.map_x) || + _heightmap_x / 2 > (1U << _patches_newgame.map_x) || + _heightmap_y * 2 < (1U << _patches_newgame.map_y) || + _heightmap_y / 2 > (1U << _patches_newgame.map_y))) { ShowQuery( STR_HEIGHTMAP_SCALE_WARNING_CAPTION, STR_HEIGHTMAP_SCALE_WARNING_MESSAGE, w, - HeightmapScaledTooMuchCallback); + LandscapeGenerationCallback); + } else { StartGeneratingLandscape(mode); } @@ -545,6 +557,10 @@ void ShowHeightmapLoad() void StartScenarioEditor() { + if (_patches_newgame.town_layout == TL_NO_ROADS) { + _patches_newgame.town_layout = TL_ORIGINAL; + } + StartGeneratingLandscape(GLWP_SCENARIO); } diff --git a/src/lang/english.txt b/src/lang/english.txt index c8fea1eaa4..b10e857b1a 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1131,6 +1131,15 @@ STR_CONFIG_PATCHES_SMOOTH_ECONOMY :{LTBLUE}Enable STR_CONFIG_PATCHES_ALLOW_SHARES :{LTBLUE}Allow buying shares from other companies STR_CONFIG_PATCHES_DRAG_SIGNALS_DENSITY :{LTBLUE}When dragging, place signals every: {ORANGE}{STRING1} tile(s) STR_CONFIG_PATCHES_SEMAPHORE_BUILD_BEFORE_DATE :{LTBLUE}Automatically build semaphores before: {ORANGE}{STRING1} + +STR_CONFIG_PATCHES_TOWN_LAYOUT_INVALID :{WHITE}The town layout "no more roads" isn't valid in the scenario editor +STR_CONFIG_PATCHES_TOWN_LAYOUT :{LTBLUE}Select town-road layout: {ORANGE}{STRING1} +STR_CONFIG_PATCHES_TOWN_LAYOUT_NO_ROADS :no more roads +STR_CONFIG_PATCHES_TOWN_LAYOUT_DEFAULT :default +STR_CONFIG_PATCHES_TOWN_LAYOUT_BETTER_ROADS :better roads +STR_CONFIG_PATCHES_TOWN_LAYOUT_2X2_GRID :2x2 grid +STR_CONFIG_PATCHES_TOWN_LAYOUT_3X3_GRID :3x3 grid + STR_CONFIG_PATCHES_TOOLBAR_POS :{LTBLUE}Position of main toolbar: {ORANGE}{STRING1} STR_CONFIG_PATCHES_TOOLBAR_POS_LEFT :Left STR_CONFIG_PATCHES_TOOLBAR_POS_CENTER :Centre @@ -3104,6 +3113,8 @@ STR_SNOW_LINE_QUERY_CAPT :{WHITE}Change s STR_START_DATE_QUERY_CAPT :{WHITE}Change starting year STR_HEIGHTMAP_SCALE_WARNING_CAPTION :{WHITE}Scale warning STR_HEIGHTMAP_SCALE_WARNING_MESSAGE :{YELLOW}Resizing source map too much is not recommended. Continue with the generation? +STR_TOWN_LAYOUT_WARNING_CAPTION :{WHITE}Town layout warning +STR_TOWN_LAYOUT_WARNING_MESSAGE :{YELLOW}The town layout "no more roads" is not recommended. Continue with the generation? STR_SNOW_LINE_HEIGHT_NUM :{NUM} STR_HEIGHTMAP_NAME :{BLACK}Heightmap name: STR_HEIGHTMAP_SIZE :{BLACK}Size: {ORANGE}{NUM} x {NUM} diff --git a/src/map.h b/src/map.h index 5b11e26273..b97eca52a0 100644 --- a/src/map.h +++ b/src/map.h @@ -209,6 +209,23 @@ static inline TileIndex AddTileIndexDiffCWrap(TileIndex tile, TileIndexDiffC dif return TileXY(x, y); } +/** + * Returns the diff between two tiles + * + * @param tile_a from tile + * @param tile_b to tile + * @return the difference between tila_a and tile_b + */ +static inline TileIndexDiffC TileIndexToTileIndexDiffC(TileIndex tile_a, TileIndex tile_b) +{ + TileIndexDiffC difference; + + difference.x = TileX(tile_a) - TileX(tile_b); + difference.y = TileY(tile_a) - TileY(tile_b); + + return difference; +} + /* Functions to calculate distances */ uint DistanceManhattan(TileIndex, TileIndex); ///< also known as L1-Norm. Is the shortest distance one could go over diagonal tracks (or roads) uint DistanceSquare(TileIndex, TileIndex); ///< euclidian- or L2-Norm squared diff --git a/src/openttd.h b/src/openttd.h index 187804cf9d..529cf66d61 100644 --- a/src/openttd.h +++ b/src/openttd.h @@ -199,6 +199,19 @@ enum { NUM_LANDSCAPE = 4, }; +/** + * Town Layouts + */ +enum TownLayout { + TL_NO_ROADS = 0, ///< Build no more roads, but still build houses + TL_ORIGINAL, ///< Original algorithm (min. 1 distance between roads) + TL_BETTER_ROADS, ///< Extended original algorithm (min. 2 distance between roads) + TL_2X2_GRID, ///< Geometric 2x2 grid algorithm + TL_3X3_GRID, ///< Geometric 3x3 grid algorithm + + NUM_TLS, ///< Number of town layouts +}; + enum { NUM_PRICES = 49, }; diff --git a/src/saveload.cpp b/src/saveload.cpp index a53d1150e7..a4963e7452 100644 --- a/src/saveload.cpp +++ b/src/saveload.cpp @@ -29,7 +29,7 @@ #include #include -extern const uint16 SAVEGAME_VERSION = 58; +extern const uint16 SAVEGAME_VERSION = 59; uint16 _sl_version; ///< the major savegame version identifier byte _sl_minor_version; ///< the minor savegame version, DO NOT USE! diff --git a/src/settings.cpp b/src/settings.cpp index 696ac64fc7..203d5e0ffb 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1174,6 +1174,24 @@ static int32 EngineRenewMoneyUpdate(int32 p1) DoCommandP(0, 2, _patches.autorenew_money, NULL, CMD_SET_AUTOREPLACE); return 0; } + +/** + * Check for right TownLayout usage in editor mode. + * The No Road mode is not desirable since towns have to be + * able to grow. If a user desires to have a town with no road, + * he can easily remove them himself. This would create less confusion + * @param p1 unused + * @return always 0 + */ +static int32 CheckTownLayout(int32 p1) +{ + if (_patches.town_layout == TL_NO_ROADS && _game_mode == GM_EDITOR) { + ShowErrorMessage(INVALID_STRING_ID, STR_CONFIG_PATCHES_TOWN_LAYOUT_INVALID, 0, 0); + _patches.town_layout = TL_ORIGINAL; + } + return 0; +} + /** Conversion callback for _gameopt_settings.landscape * It converts (or try) between old values and the new ones, * without loosing initial setting of the user @@ -1334,6 +1352,7 @@ const SettingDesc _patch_settings[] = { SDT_BOOL(Patches, always_small_airport, 0, 0, false, STR_CONFIG_PATCHES_SMALL_AIRPORTS, NULL), SDT_VAR(Patches, drag_signals_density,SLE_UINT8,S, 0, 4, 1, 20, 0, STR_CONFIG_PATCHES_DRAG_SIGNALS_DENSITY,NULL), SDT_VAR(Patches, semaphore_build_before,SLE_INT32, S, NC, 1975, MIN_YEAR, MAX_YEAR, 1, STR_CONFIG_PATCHES_SEMAPHORE_BUILD_BEFORE_DATE, NULL), + SDT_CONDVAR(Patches, town_layout, SLE_UINT8, 59, SL_MAX_VERSION, 0, MS, TL_ORIGINAL, TL_NO_ROADS, NUM_TLS - 1, 1, STR_CONFIG_PATCHES_TOWN_LAYOUT, CheckTownLayout), /***************************************************************************/ /* Vehicle section of the GUI-configure patches window */ diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index beaa8eff09..f5d846b415 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -609,6 +609,7 @@ static const char *_patches_construction[] = { "drag_signals_density", "oil_refinery_limit", "semaphore_build_before", + "town_layout", }; static const char *_patches_stations[] = { diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 60fcead82e..0d8ebc8edd 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -555,6 +555,14 @@ static const TileIndexDiffC _roadblock_tileadd[] = { { 0, 1} }; +/** + * Distance multiplyer + * Defines the possible distances between 2 road tiles + */ +enum RoadBlockTitleDistance { + RB_TILE_DIST1 = 1, ///< 1 tile between + RB_TILE_DIST2, ///< 2 tiles between +}; static bool GrowTown(Town *t); @@ -607,6 +615,23 @@ static RoadBits GetTownRoadMask(TileIndex tile) return r; } +/** + * Check if a neighboring tile has a road + * + * @param tile curent tile + * @param dir target direction + * @param dist_multi distance multiplyer + * @return true if one of the neighboring tiles at the + * given distance is a road tile else + */ +static bool NeighborIsRoadTile(TileIndex tile, int dir, RoadBlockTitleDistance dist_multi) +{ + return (HASBIT(GetTownRoadMask(TILE_ADD(tile, dist_multi * ToTileIndexDiff(_roadblock_tileadd[dir + 1]))), dir ^ 2) || + HASBIT(GetTownRoadMask(TILE_ADD(tile, dist_multi * ToTileIndexDiff(_roadblock_tileadd[dir + 3]))), dir ^ 2) || + HASBIT(GetTownRoadMask(TILE_ADD(tile, dist_multi * (ToTileIndexDiff(_roadblock_tileadd[dir + 1]) + ToTileIndexDiff(_roadblock_tileadd[dir + 2])))), dir) || + HASBIT(GetTownRoadMask(TILE_ADD(tile, dist_multi * (ToTileIndexDiff(_roadblock_tileadd[dir + 3]) + ToTileIndexDiff(_roadblock_tileadd[dir + 2])))), dir)); +} + static bool IsRoadAllowedHere(TileIndex tile, int dir) { Slope k; @@ -630,16 +655,17 @@ static bool IsRoadAllowedHere(TileIndex tile, int dir) slope = GetTileSlope(tile, NULL); if (slope == SLOPE_FLAT) { no_slope: - /* Tile has no slope - * Disallow the road if any neighboring tile has a road. */ - if (HASBIT(GetTownRoadMask(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[dir + 1]))), dir ^ 2) || - HASBIT(GetTownRoadMask(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[dir + 3]))), dir ^ 2) || - HASBIT(GetTownRoadMask(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[dir + 1]) + ToTileIndexDiff(_roadblock_tileadd[dir + 2]))), dir) || - HASBIT(GetTownRoadMask(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[dir + 3]) + ToTileIndexDiff(_roadblock_tileadd[dir + 2]))), dir)) - return false; + /* Tile has no slope */ + switch (_patches.town_layout) { + default: NOT_REACHED(); - /* Otherwise allow */ - return true; + case TL_ORIGINAL: /* Disallow the road if any neighboring tile has a road (distance: 1) */ + return !NeighborIsRoadTile(tile, dir, RB_TILE_DIST1); + + case TL_BETTER_ROADS: /* Disallow the road if any neighboring tile has a road (distance: 1 and 2). */ + return !(NeighborIsRoadTile(tile, dir, RB_TILE_DIST1) || + NeighborIsRoadTile(tile, dir, RB_TILE_DIST2)); + } } /* If the tile is not a slope in the right direction, then @@ -698,6 +724,127 @@ static void LevelTownLand(TileIndex tile) } } +/** + * Generate the RoadBits of a grid tile + * + * @param t current town + * @param tile tile in reference to the town + * @return the RoadBit of the current tile regarding + * the selected town layout + */ +static RoadBits GetTownRoadGridElement(Town* t, TileIndex tile) +{ + /* align the grid to the downtown */ + TileIndexDiffC grid_pos = TileIndexToTileIndexDiffC(t->xy, tile); ///< Vector from downtown to the tile + + /* lx, ly description: + * @li lx and ly are true if the tile is a crossing tile. + * @li lx xor ly are true if the tile is a straight road tile. + * @li lx and ly are false if the tile is a house tile. + */ + bool lx, ly; + + switch (_patches.town_layout) { + default: NOT_REACHED(); + + case TL_2X2_GRID: + lx = ((grid_pos.x % 3) == 0); + ly = ((grid_pos.y % 3) == 0); + break; + + case TL_3X3_GRID: + lx = ((grid_pos.x % 4) == 0); + ly = ((grid_pos.y % 4) == 0); + break; + } + + /* generate the basic grid structure */ + if (!lx && !ly) { ///< It is a house tile + return ROAD_NONE; + } else if (lx && !ly) { ///< It is a Y-dir road tile + return ROAD_Y; + } else if (!lx && ly) { ///< It is a X-dir road tile + return ROAD_X; + } else { ///< It is a crossing tile + /* Presets for junctions on slopes + * not nice :( */ + switch (GetTileSlope(tile, NULL)) { + case SLOPE_W: + return ROAD_NW | ROAD_SW; + case SLOPE_S: + return ROAD_SE | ROAD_SW; + case SLOPE_SW: + return ROAD_Y | ROAD_SW; + case SLOPE_E: + return ROAD_NE | ROAD_SE; + case SLOPE_SE: + return ROAD_X | ROAD_SE; + case SLOPE_N: + return ROAD_NW | ROAD_NE; + case SLOPE_NW: + return ROAD_X | ROAD_NW; + case SLOPE_NE: + return ROAD_Y | ROAD_NE; + case SLOPE_STEEP_W: + case SLOPE_STEEP_N: + return ROAD_X; + case SLOPE_STEEP_S: + case SLOPE_STEEP_E: + return ROAD_Y; + default: + return ROAD_ALL; + } + } +} + +/** + * Check there are enougth neighbor house tiles next to the current tile + * + * @param tile current tile + * @return true if there are more than 2 house tiles next + * to the current one + */ +static bool NeighborsAreHouseTiles(TileIndex tile) +{ + uint counter = 0; ///< counts the house neighbor tiles + + /* We can't look further than that. */ + if (TileX(tile) < 1 || TileY(tile) < 1) { + return false; + } + + /* Check the tiles E,N,W and S of the current tile. */ + for (uint i = 0; i < 4; i++) { + if (IsTileType(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[i])), MP_HOUSE)) { + counter++; + } + + /* If there are enougth neighbor's stop it here */ + if (counter >= 3) { + return true; + } + } + return false; +} + +/** + * Grows the given town. + * There are at the moment 3 possible way's for + * the town expansion: + * @li Generate a random tile and check if there is a road allowed + * @li TL_ORIGINAL + * @li TL_BETTER_ROADS + * @li Check if the town geometry allows a road and which one + * @li TL_2X2_GRID + * @li TL_3X3_GRID + * @li Forbid roads, only build houses + * @li TL_NO_ROADS + * + * @param tile_ptr current tile + * @param mask current tiles RoadBits + * @param block road block + * @param t1 current town + */ static void GrowTownInTile(TileIndex* tile_ptr, RoadBits mask, int block, Town* t1) { RoadBits rcmd; @@ -720,39 +867,81 @@ static void GrowTownInTile(TileIndex* tile_ptr, RoadBits mask, int block, Town* LevelTownLand(tile); /* Is a road allowed here? */ - if (!IsRoadAllowedHere(tile, block)) return; + switch (_patches.town_layout) { + default: NOT_REACHED(); - /* Randomize new road block numbers */ - a = block; - b = block ^ 2; - if (CHANCE16(1, 4)) { - do { - a = GB(Random(), 0, 2); - } while (a == b); - } - - if (!IsRoadAllowedHere(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[a])), a)) { - /* A road is not allowed to continue the randomized road, - * return if the road we're trying to build is curved. */ - if (a != (b ^ 2)) return; - - /* Return if neither side of the new road is a house */ - if (!IsTileType(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[a + 1])), MP_HOUSE) && - !IsTileType(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[a + 3])), MP_HOUSE)) + case TL_NO_ROADS: /* Disallow Roads */ return; - /* That means that the road is only allowed if there is a house - * at any side of the new road. */ + case TL_3X3_GRID: + case TL_2X2_GRID: + rcmd = GetTownRoadGridElement(t1, tile); + if (rcmd == ROAD_NONE) { + return; + } + break; + + case TL_BETTER_ROADS: + case TL_ORIGINAL: + if (!IsRoadAllowedHere(tile, block)) { + return; + } + + /* Randomize new road block numbers */ + a = block; + b = block ^ 2; + if (CHANCE16(1, 4)) { + do { + a = GB(Random(), 0, 2); + } while (a == b); + } + + if (!IsRoadAllowedHere(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[a])), a)) { + /* A road is not allowed to continue the randomized road, + * return if the road we're trying to build is curved. */ + if (a != (b ^ 2)) { + return; + } + + /* Return if neither side of the new road is a house */ + if (!IsTileType(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[a + 1])), MP_HOUSE) && + !IsTileType(TILE_ADD(tile, ToTileIndexDiff(_roadblock_tileadd[a + 3])), MP_HOUSE)) { + return; + } + + /* That means that the road is only allowed if there is a house + * at any side of the new road. */ + } + + rcmd = (RoadBits)((1 << a) + (1 << b)); + break; } - rcmd = (RoadBits)((1 << a) + (1 << b)); } else if (block < 5 && !HASBIT(mask, block ^ 2)) { /* Continue building on a partial road. * Always OK. */ _grow_town_result = 0; - rcmd = (RoadBits)(1 << (block ^ 2)); + + switch (_patches.town_layout) { + default: NOT_REACHED(); + + case TL_NO_ROADS: /* Disallow Roads */ + return; + + case TL_3X3_GRID: + case TL_2X2_GRID: + rcmd = GetTownRoadGridElement(t1, tile); + break; + + case TL_BETTER_ROADS: + case TL_ORIGINAL: + rcmd = (RoadBits)(1 << (block ^ 2)); + break; + } } else { int i; + bool allow_house = false; + TileIndex tmptile2; /* Reached a tunnel/bridge? Then continue at the other side of it. */ if (IsTileType(tile, MP_TUNNELBRIDGE)) { @@ -775,17 +964,51 @@ static void GrowTownInTile(TileIndex* tile_ptr, RoadBits mask, int block, Town* /* Don't do it if it reaches to water. */ if (IsClearWaterTile(tmptile)) return; - /* Build a house at the edge. 60% chance or - * always ok if no road allowed. */ - if (!IsRoadAllowedHere(tmptile, i) || CHANCE16(6, 10)) { - /* But not if there already is a house there. */ + switch (_patches.town_layout) { + default: NOT_REACHED(); + + case TL_NO_ROADS: + allow_house = true; + break; + + case TL_3X3_GRID: /* Use 2x2 grid afterwards! */ + /* Fill gap if house has enougth neighbors */ + tmptile2 = TILE_ADD(tmptile, ToTileIndexDiff(_roadblock_tileadd[i])); + if (NeighborsAreHouseTiles(tmptile2) && BuildTownHouse(t1, tmptile2)) { + _grow_town_result = -1; + } + + case TL_2X2_GRID: + rcmd = GetTownRoadGridElement(t1, tmptile); + allow_house = (rcmd == ROAD_NONE); + break; + + case TL_BETTER_ROADS: /* Use original afterwards! */ + /* Fill gap if house has enougth neighbors */ + tmptile2 = TILE_ADD(tmptile, ToTileIndexDiff(_roadblock_tileadd[i])); + if (NeighborsAreHouseTiles(tmptile2) && BuildTownHouse(t1, tmptile2)) { + _grow_town_result = -1; + } + + case TL_ORIGINAL: + /* Allow a house at the edge. 60% chance or + * always ok if no road allowed. */ + allow_house = (!IsRoadAllowedHere(tmptile, i) || CHANCE16(6, 10)); + break; + } + + + if (allow_house) { + /* Build a house, but not if there already is a house there. */ if (!IsTileType(tmptile, MP_HOUSE)) { /* Level the land if possible */ LevelTownLand(tmptile); /* And build a house. * Set result to -1 if we managed to build it. */ - if (BuildTownHouse(t1, tmptile)) _grow_town_result = -1; + if (BuildTownHouse(t1, tmptile)) { + _grow_town_result = -1; + } } return; } @@ -813,6 +1036,12 @@ build_road_and_exit: return; } + /* Check if the bridge is in the right direction */ + if ((rcmd == ROAD_X && (i == DIAGDIR_NW || i == DIAGDIR_SE)) || + (rcmd == ROAD_Y && (i == DIAGDIR_NE || i == DIAGDIR_SW))) { + goto build_road_and_exit; + } + tmptile = tile; /* Now it contains the direction of the slope */ @@ -855,8 +1084,23 @@ static int GrowTownAtRoad(Town *t, TileIndex tile) TILE_ASSERT(tile); - /* Number of times to search. */ - _grow_town_result = 10 + t->num_houses * 4 / 9; + /* Number of times to search. + * Better roads, 2X2 and 3X3 grid grow quite fast so we give + * them a little handicap. */ + switch (_patches.town_layout) { + case TL_BETTER_ROADS: + _grow_town_result = 10 + t->num_houses * 2 / 9; + break; + + case TL_3X3_GRID: + case TL_2X2_GRID: + _grow_town_result = 10 + t->num_houses * 1 / 9; + break; + + default: + _grow_town_result = 10 + t->num_houses * 4 / 9; + break; + } do { /* Get a bitmask of the road blocks on a tile */ @@ -930,6 +1174,13 @@ static bool GrowTown(Town *t) { 0, 0} }; + /* Let the town be a ghost town + * The player wanted it in such a way. Thus there he has it. ;) + * Never reached in editor mode. */ + if (_patches.town_layout == TL_NO_ROADS) { + return false; + } + /* Current player is a town */ old_player = _current_player; _current_player = OWNER_TOWN; diff --git a/src/variables.h b/src/variables.h index bebc1cc427..2c32aa7a73 100644 --- a/src/variables.h +++ b/src/variables.h @@ -231,6 +231,8 @@ struct Patches { uint8 initial_city_size; ///< Multiplier for the initial size of the cities compared to towns bool pause_on_newgame; ///< Whether to start new games paused or not. + + TownLayout town_layout; ///< Select town layout }; VARDEF Patches _patches;