diff --git a/source.list b/source.list --- a/source.list +++ b/source.list @@ -100,6 +100,7 @@ aircraft.h airport.h airport_movement.h articulated_vehicles.h +autoslope.h aystar.h bmp.h cargopacket.h diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -36,6 +36,7 @@ #include "newgrf_industrytiles.h" #include "newgrf_callbacks.h" #include "misc/autoptr.hpp" +#include "autoslope.h" void ShowIndustryViewWindow(int industry); void BuildOilRig(TileIndex tile); @@ -1974,6 +1975,30 @@ Money IndustrySpec::GetConstructionCost( static CommandCost TerraformTile_Industry(TileIndex tile, uint32 flags, uint z_new, Slope tileh_new) { + if (AutoslopeEnabled()) { + /* We imitate here TTDP's behaviour: + * - Both new and old slope must not be steep. + * - TileMaxZ must not be changed. + * - Allow autoslope by default. + * - Disallow autoslope if callback succeeds and returns non-zero. + */ + Slope tileh_old = GetTileSlope(tile, NULL); + /* TileMaxZ must not be changed. Slopes must not be steep. */ + if (!IsSteepSlope(tileh_old) && !IsSteepSlope(tileh_new) && (GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new))) { + const IndustryGfx gfx = GetIndustryGfx(tile); + const IndustryTileSpec *itspec = GetIndustryTileSpec(gfx); + + /* Call callback 3C 'disable autosloping for industry tiles'. */ + if (HASBIT(itspec->callback_flags, CBM_INDT_AUTOSLOPE)) { + /* If the callback fails, allow autoslope. */ + uint16 res = GetIndustryTileCallback(CBID_INDUSTRY_AUTOSLOPE, 0, 0, gfx, GetIndustryByTile(tile), tile); + if ((res == 0) || (res == CALLBACK_FAILED)) return _price.terraform; + } else { + // allow autoslope + return _price.terraform; + } + } + } return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); // funny magic bulldozer } diff --git a/src/lang/english.txt b/src/lang/english.txt --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1020,6 +1020,7 @@ STR_CONFIG_PATCHES_OFF STR_CONFIG_PATCHES_ON :On STR_CONFIG_PATCHES_VEHICLESPEED :{LTBLUE}Show vehicle speed in status bar: {ORANGE}{STRING1} STR_CONFIG_PATCHES_BUILDONSLOPES :{LTBLUE}Allow building on slopes and coasts: {ORANGE}{STRING1} +STR_CONFIG_PATCHES_AUTOSLOPE :{LTBLUE}Allow terraforming under buildings, tracks, etc. (autoslope): {ORANGE}{STRING1} STR_CONFIG_PATCHES_CATCHMENT :{LTBLUE}Allow more realistically sized catchment areas: {ORANGE}{STRING1} STR_CONFIG_PATCHES_EXTRADYNAMITE :{LTBLUE}Allow removal of more town-owned roads, bridges, etc: {ORANGE}{STRING1} STR_CONFIG_PATCHES_MAMMOTHTRAINS :{LTBLUE}Enable building very long trains: {ORANGE}{STRING1} diff --git a/src/newgrf_callbacks.h b/src/newgrf_callbacks.h --- a/src/newgrf_callbacks.h +++ b/src/newgrf_callbacks.h @@ -160,7 +160,7 @@ enum CallbackID { CBID_INDUSTRY_SPECIAL_EFFECT = 0x3B, /** Called to determine if industry can alter the ground below industry tile */ - CBID_INDUSTRY_AUTOSLOPE = 0x3C, // not implemented + CBID_INDUSTRY_AUTOSLOPE = 0x3C, /** Called to determine if the industry can still accept or refuse more cargo arrival */ CBID_INDUSTRY_REFUSE_CARGO = 0x3D, diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -39,6 +39,7 @@ #include "newgrf_station.h" #include "train.h" #include "misc/autoptr.hpp" +#include "autoslope.h" const byte _track_sloped_sprites[14] = { 14, 15, 22, 13, @@ -219,8 +220,11 @@ static CommandCost CheckRailSlope(Slope { if (IsSteepSlope(tileh)) { if (_patches.build_on_slopes && existing == 0) { - TrackBits valid = TRACK_BIT_CROSS | (HASBIT(1 << SLOPE_STEEP_W | 1 << SLOPE_STEEP_E, tileh) ? TRACK_BIT_VERT : TRACK_BIT_HORZ); - if (valid & rail_bits) return _price.terraform; + /* There may only be one track on steep slopes. (Autoslope calls with multiple bits in rail_bits) */ + if (KILL_FIRST_BIT(rail_bits & TRACK_BIT_MASK) == 0) { + TrackBits valid = TRACK_BIT_CROSS | (HASBIT(1 << SLOPE_STEEP_W | 1 << SLOPE_STEEP_E, tileh) ? TRACK_BIT_VERT : TRACK_BIT_HORZ); + if (valid & rail_bits) return _price.terraform; + } } } else { rail_bits |= existing; @@ -2189,15 +2193,62 @@ static uint32 VehicleEnter_Track(Vehicle return VETSB_CONTINUE; } +/** + * Tests if autoslope is allowed. + * + * @param tile The tile. + * @param flags Terraform command flags. + * @param z_old Old TileZ. + * @param tileh_old Old TileSlope. + * @param z_new New TileZ. + * @param tileh_new New TileSlope. + * @param rail_bits Trackbits. + */ +static CommandCost TestAutoslopeOnRailTile(TileIndex tile, uint flags, uint z_old, Slope tileh_old, uint z_new, Slope tileh_new, TrackBits rail_bits) +{ + if (!_patches.build_on_slopes || !AutoslopeEnabled()) return CMD_ERROR; + + /* Is the slope-rail_bits combination valid in general? I.e. is it save to call GetRailFoundation() ? */ + if (CmdFailed(CheckRailSlope(tileh_new, rail_bits, TRACK_BIT_NONE, tile))) return CMD_ERROR; + + /* Get the slopes on top of the foundations */ + z_old += ApplyFoundationToSlope(GetRailFoundation(tileh_old, rail_bits), &tileh_old); + z_new += ApplyFoundationToSlope(GetRailFoundation(tileh_new, rail_bits), &tileh_new); + + Slope track_corner; + switch (rail_bits) { + case TRACK_BIT_LEFT: track_corner = SLOPE_W; break; + case TRACK_BIT_LOWER: track_corner = SLOPE_S; break; + case TRACK_BIT_RIGHT: track_corner = SLOPE_E; break; + case TRACK_BIT_UPPER: track_corner = SLOPE_N; break; + + /* Surface slope must not be changed */ + default: return (((z_old != z_new) || (tileh_old != tileh_new)) ? CMD_ERROR : _price.terraform); + } + + /* The height of the track_corner must not be changed. The rest ensures GetRailFoundation() already. */ + if ((tileh_old & track_corner) != 0) z_old += TILE_HEIGHT; + if ((tileh_new & track_corner) != 0) z_new += TILE_HEIGHT; + if (z_old != z_new) return CMD_ERROR; + + /* Make the ground dirty, if surface slope has changed */ + if ((tileh_old != tileh_new) && ((flags & DC_EXEC) != 0)) SetRailGroundType(tile, RAIL_GROUND_BARREN); + + return _price.terraform; +} + static CommandCost TerraformTile_Track(TileIndex tile, uint32 flags, uint z_new, Slope tileh_new) { + uint z_old; + Slope tileh_old = GetTileSlope(tile, &z_old); if (IsPlainRailTile(tile)) { - uint z_old; - Slope tileh_old = GetTileSlope(tile, &z_old); TrackBits rail_bits = GetTrackBits(tile); _error_message = STR_1008_MUST_REMOVE_RAILROAD_TRACK; + /* First test autoslope. However if it succeeds we still have to test the rest, because non-autoslope terraforming is cheaper. */ + CommandCost autoslope_result = TestAutoslopeOnRailTile(tile, flags, z_old, tileh_old, z_new, tileh_new, rail_bits); + /* When there is only a single horizontal/vertical track, one corner can be terraformed. */ Slope allowed_corner; switch (rail_bits) { @@ -2205,7 +2256,7 @@ static CommandCost TerraformTile_Track(T case TRACK_BIT_UPPER: allowed_corner = SLOPE_S; break; case TRACK_BIT_LEFT: allowed_corner = SLOPE_E; break; case TRACK_BIT_LOWER: allowed_corner = SLOPE_N; break; - default: return CMD_ERROR; + default: return autoslope_result; } Slope track_corners = ComplementSlope(allowed_corner); @@ -2221,28 +2272,28 @@ static CommandCost TerraformTile_Track(T z_new += TILE_HEIGHT; } else { /* do not build a foundation */ - if ((tileh_new != SLOPE_FLAT) && (tileh_new != allowed_corner)) return CMD_ERROR; + if ((tileh_new != SLOPE_FLAT) && (tileh_new != allowed_corner)) return autoslope_result; } /* Track height must remain unchanged */ - if (z_old != z_new) return CMD_ERROR; + if (z_old != z_new) return autoslope_result; break; case FOUNDATION_LEVELED: /* Is allowed_corner covered by the foundation? */ - if ((tileh_old & allowed_corner) == 0) return CMD_ERROR; + if ((tileh_old & allowed_corner) == 0) return autoslope_result; /* allowed_corner may only be raised -> steep slope */ - if ((z_old != z_new) || (tileh_new != (tileh_old | SLOPE_STEEP))) return CMD_ERROR; + if ((z_old != z_new) || (tileh_new != (tileh_old | SLOPE_STEEP))) return autoslope_result; break; case FOUNDATION_STEEP_LOWER: /* Only allow to lower highest corner */ - if ((z_old != z_new) || (tileh_new != (tileh_old & ~SLOPE_STEEP))) return CMD_ERROR; + if ((z_old != z_new) || (tileh_new != (tileh_old & ~SLOPE_STEEP))) return autoslope_result; break; case FOUNDATION_STEEP_HIGHER: - return CMD_ERROR; + return autoslope_result; default: NOT_REACHED(); } @@ -2252,6 +2303,22 @@ static CommandCost TerraformTile_Track(T /* allow terraforming, no extra costs */ return CommandCost(); + } else { + if (_patches.build_on_slopes && AutoslopeEnabled()) { + switch (GetRailTileType(tile)) { + case RAIL_TILE_WAYPOINT: { + CommandCost cost = TestAutoslopeOnRailTile(tile, flags, z_old, tileh_old, z_new, tileh_new, GetRailWaypointBits(tile)); + if (!CmdFailed(cost)) return cost; // allow autoslope + break; + } + + case RAIL_TILE_DEPOT: + if (AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRailDepotDirection(tile))) return _price.terraform; + break; + + default: NOT_REACHED(); + } + } } return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); } diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -32,6 +32,12 @@ #include "station_map.h" #include "tunnel_map.h" #include "misc/autoptr.hpp" +#include "autoslope.h" + +#define M(x) (1 << (x)) +/* Level crossings may only be built on these slopes */ +static const uint32 VALID_LEVEL_CROSSING_SLOPES = (M(SLOPE_SEN) | M(SLOPE_ENW) | M(SLOPE_NWS) | M(SLOPE_NS) | M(SLOPE_WSE) | M(SLOPE_EW) | M(SLOPE_FLAT)); +#undef M bool CheckAllowRemoveRoad(TileIndex tile, RoadBits remove, Owner owner, bool *edge_road, RoadType rt) { @@ -405,12 +411,10 @@ CommandCost CmdBuildRoad(TileIndex tile, return_cmd_error(STR_1000_LAND_SLOPED_IN_WRONG_DIRECTION); } -#define M(x) (1 << (x)) /* Level crossings may only be built on these slopes */ - if (!HASBIT(M(SLOPE_SEN) | M(SLOPE_ENW) | M(SLOPE_NWS) | M(SLOPE_NS) | M(SLOPE_WSE) | M(SLOPE_EW) | M(SLOPE_FLAT), tileh)) { + if (!HASBIT(VALID_LEVEL_CROSSING_SLOPES, tileh)) { return_cmd_error(STR_1000_LAND_SLOPED_IN_WRONG_DIRECTION); } -#undef M if (GetRailTileType(tile) != RAIL_TILE_NORMAL) goto do_clear; switch (GetTrackBits(tile)) { @@ -1372,6 +1376,41 @@ static void ChangeTileOwner_Road(TileInd static CommandCost TerraformTile_Road(TileIndex tile, uint32 flags, uint z_new, Slope tileh_new) { + if (_patches.build_on_slopes && AutoslopeEnabled()) { + switch (GetRoadTileType(tile)) { + case ROAD_TILE_CROSSING: + if (!IsSteepSlope(tileh_new) && (GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new)) && HASBIT(VALID_LEVEL_CROSSING_SLOPES, tileh_new)) return _price.terraform; + break; + + case ROAD_TILE_DEPOT: + if (AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRoadDepotDirection(tile))) return _price.terraform; + break; + + case ROAD_TILE_NORMAL: { + RoadBits bits = GetAllRoadBits(tile); + RoadBits bits_copy = bits; + /* Check if the slope-road_bits combination is valid at all, i.e. it is save to call GetRoadFoundation(). */ + if (!CmdFailed(CheckRoadSlope(tileh_new, &bits_copy, ROAD_NONE))) { + /* CheckRoadSlope() sometimes changes the road_bits, if it does not agree with them. */ + if (bits == bits_copy) { + uint z_old; + Slope tileh_old = GetTileSlope(tile, &z_old); + + /* Get the slope on top of the foundation */ + z_old += ApplyFoundationToSlope(GetRoadFoundation(tileh_old, bits), &tileh_old); + z_new += ApplyFoundationToSlope(GetRoadFoundation(tileh_new, bits), &tileh_new); + + /* The surface slope must not be changed */ + if ((z_old == z_new) && (tileh_old == tileh_new)) return _price.terraform; + } + } + break; + } + + default: NOT_REACHED(); + } + } + return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); } diff --git a/src/saveload.cpp b/src/saveload.cpp --- a/src/saveload.cpp +++ b/src/saveload.cpp @@ -29,7 +29,7 @@ #include "strings.h" #include -extern const uint16 SAVEGAME_VERSION = 74; +extern const uint16 SAVEGAME_VERSION = 75; 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 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1362,6 +1362,7 @@ const SettingDesc _patch_settings[] = { /***************************************************************************/ /* Construction section of the GUI-configure patches window */ SDT_BOOL(Patches, build_on_slopes, 0, 0, true, STR_CONFIG_PATCHES_BUILDONSLOPES, NULL), + SDT_CONDBOOL(Patches, autoslope, 75, SL_MAX_VERSION, 0, 0, true, STR_CONFIG_PATCHES_AUTOSLOPE, NULL), SDT_BOOL(Patches, extra_dynamite, 0, 0, false, STR_CONFIG_PATCHES_EXTRADYNAMITE, NULL), SDT_BOOL(Patches, longbridges, 0, 0, true, STR_CONFIG_PATCHES_LONGBRIDGES, NULL), SDT_BOOL(Patches, signal_side, N, 0, true, STR_CONFIG_PATCHES_SIGNALSIDE, RedrawScreen), diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -664,6 +664,7 @@ static const char *_patches_ui[] = { static const char *_patches_construction[] = { "build_on_slopes", + "autoslope", "extra_dynamite", "longbridges", "signal_side", diff --git a/src/slope.h b/src/slope.h --- a/src/slope.h +++ b/src/slope.h @@ -90,6 +90,19 @@ static inline byte GetHighestSlopeCorner } } +/** + * Returns the height of the highest corner of a slope relative to TileZ (= minimal height) + * + * @param s The #Slope. + * @return Relative height of highest corner. + */ +static inline uint GetSlopeMaxZ(Slope s) +{ + if (s == SLOPE_FLAT) return 0; + if (IsSteepSlope(s)) return 2 * TILE_HEIGHT; + return TILE_HEIGHT; +} + /** * Enumeration for Foundations. diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -42,6 +42,7 @@ #include "road.h" #include "cargotype.h" #include "strings.h" +#include "autoslope.h" DEFINE_OLD_POOL_GENERIC(Station, Station) DEFINE_OLD_POOL_GENERIC(RoadStop, RoadStop) @@ -2880,6 +2881,36 @@ void AfterLoadStations() static CommandCost TerraformTile_Station(TileIndex tile, uint32 flags, uint z_new, Slope tileh_new) { + if (_patches.build_on_slopes && AutoslopeEnabled()) { + /* TODO: If you implement newgrf callback 149 'land slope check', you have to decide what to do with it here. + * TTDP does not call it. + */ + if (!IsSteepSlope(tileh_new) && (GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new))) { + switch (GetStationType(tile)) { + case STATION_RAIL: { + DiagDirection direction = AxisToDiagDir(GetRailStationAxis(tile)); + if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction)) break; + if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) break; + return _price.terraform; + } + + case STATION_AIRPORT: + return _price.terraform; + + case STATION_TRUCK: + case STATION_BUS: { + DiagDirection direction = GetRoadStopDir(tile); + if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction)) break; + if (IsDriveThroughStopTile(tile)) { + if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) break; + } + return _price.terraform; + } + + default: break; + } + } + } return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); } diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -41,6 +41,7 @@ #include "newgrf_commons.h" #include "newgrf_townname.h" #include "misc/autoptr.hpp" +#include "autoslope.h" /* Initialize the town-pool */ DEFINE_OLD_POOL_GENERIC(Town, Town) @@ -2309,6 +2310,15 @@ void InitializeTowns() static CommandCost TerraformTile_Town(TileIndex tile, uint32 flags, uint z_new, Slope tileh_new) { + if (AutoslopeEnabled()) { + HouseID house = GetHouseType(tile); + HouseSpec *hs = GetHouseSpecs(house); + + /* Here we differ from TTDP by checking TILE_NOT_SLOPED */ + if (((hs->building_flags & TILE_NOT_SLOPED) == 0) && !IsSteepSlope(tileh_new) && + (GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new))) return _price.terraform; + } + return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); } diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -32,6 +32,7 @@ #include "yapf/yapf.h" #include "date.h" #include "newgrf_sound.h" +#include "autoslope.h" #include "table/bridge_land.h" @@ -1416,6 +1417,30 @@ static uint32 VehicleEnter_TunnelBridge( static CommandCost TerraformTile_TunnelBridge(TileIndex tile, uint32 flags, uint z_new, Slope tileh_new) { + if (_patches.build_on_slopes && AutoslopeEnabled() && IsBridge(tile)) { + DiagDirection direction = GetBridgeRampDirection(tile); + Axis axis = DiagDirToAxis(direction); + CommandCost res; + + /* Check if new slope is valid for bridges in general (so we can savely call GetBridgeFoundation()) */ + if ((direction == DIAGDIR_NW) || (direction == DIAGDIR_NE)) { + res = CheckBridgeSlopeSouth(axis, tileh_new); + } else { + res = CheckBridgeSlopeNorth(axis, tileh_new); + } + + if (!CmdFailed(res)) { + uint z_old; + Slope tileh_old = GetTileSlope(tile, &z_old); + + z_old += ApplyFoundationToSlope(GetBridgeFoundation(tileh_old, axis), &tileh_old); + z_new += ApplyFoundationToSlope(GetBridgeFoundation(tileh_new, axis), &tileh_new); + + /* Surface slope remains unchanged? */ + if ((z_old == z_new) && (tileh_old == tileh_new)) return _price.terraform; + } + } + return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); } diff --git a/src/unmovable_cmd.cpp b/src/unmovable_cmd.cpp --- a/src/unmovable_cmd.cpp +++ b/src/unmovable_cmd.cpp @@ -24,6 +24,7 @@ #include "table/unmovable_land.h" #include "genworld.h" #include "bridge.h" +#include "autoslope.h" /** Destroy a HQ. * During normal gameplay you can only implicitely destroy a HQ when you are @@ -408,6 +409,10 @@ static CommandCost TerraformTile_Unmovab /* Owned land remains unsold */ if (IsOwnedLand(tile) && CheckTileOwnership(tile)) return CommandCost(); + if (AutoslopeEnabled() && (IsStatue(tile) || IsCompanyHQ(tile))) { + if (!IsSteepSlope(tileh_new) && (z_new + GetSlopeMaxZ(tileh_new) == GetTileMaxZ(tile))) return _price.terraform; + } + return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); } diff --git a/src/variables.h b/src/variables.h --- a/src/variables.h +++ b/src/variables.h @@ -240,6 +240,8 @@ struct Patches { bool timetabling; ///< Whether to allow timetabling. bool timetable_in_ticks; ///< Whether to show the timetable in ticks rather than days. + + bool autoslope; ///< Allow terraforming under things. }; VARDEF Patches _patches;