From 1459836b7a25945f24d49c3295c1588a5d184002 Mon Sep 17 00:00:00 2001 From: Samu Date: Fri, 17 Jan 2020 14:26:00 +0000 Subject: [PATCH] Feature: Permanent rivers - Rivers are indestructible, unless cheating or in the scenario editor. - Added a game setting that can turn on or off this feature. - Savegame conversion. Other changes: - Demolishing a canal that was built on rivers will restore the river, regardless of setting value. - Can no longer use terraform tool to remove rivers, must demolish them first, regardless of setting value. --- docs/landscape.html | 4 ++ docs/landscape_grid.html | 35 +++++++------ src/industry_cmd.cpp | 2 + src/lang/english.txt | 5 ++ src/saveload/afterload.cpp | 5 ++ src/saveload/saveload.h | 2 + src/settings_gui.cpp | 1 + src/settings_type.h | 1 + src/station_cmd.cpp | 2 + src/station_map.h | 2 +- src/table/settings/world_settings.ini | 8 +++ src/water_cmd.cpp | 73 +++++++++++++++++++++++---- src/water_map.h | 40 +++++++++++++++ 13 files changed, 154 insertions(+), 26 deletions(-) diff --git a/docs/landscape.html b/docs/landscape.html index ab4ad989e33ad..105f4928c0d50 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -998,6 +998,7 @@

Landscape

  • m7: animation frame (railway stations/waypoints, airports)
  • m8 bits 11..6: Tramtype
  • m8 bits 5..0: track type for railway stations/waypoints
  • +
  • m8 bit 15: set if a river was originally present under water based station tiles when current water class is canal (dock, buoy, oilrig)
  • @@ -1108,6 +1109,7 @@

    Landscape

    +
  • m8 bit 15: set if a river was originally present at the tile when current water class is canal (canal, ship depot, lock)
  • @@ -1450,6 +1452,7 @@

    Landscape

  • m6 bits 5..3: random triggers (NewGRF)
  • m6 bit 2: bit 8 of type (see m5)
  • m7: animation frame
  • +
  • m8 bit 15: set if a river was originally present at the tile when current water class is canal (oilrig) @@ -1622,6 +1625,7 @@

    Landscape

  • m3: random bits
  • m5: index into the array of objects, bits 16 to 23 (lower bits in m2)
  • m7: animation counter
  • +
  • m8 bit 15: set if a river was originally present at the tile when current water class is canal
  • diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html index fc0c6e0cfc86b..90780f2855f0e 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -79,8 +79,8 @@

    Landscape

    0 ground - XXXX XX XX - XXXX XXXX + XXXX XX XX + XXXX XXXX OOO1 OOOO OOOO OOOO OOOO OOOO XXX XOOOO @@ -211,7 +211,7 @@

    Landscape

    OOOO OOOO XXXX XXXX XXXX XXXX - OOOO OOOO OOOO OOOO + OOOO OOOO OOOO OOOO dock @@ -219,6 +219,7 @@

    Landscape

    OOOO OOOO OOOO OXXX OOOO OOOO + XOOO OOOO OOOO OOOO buoy @@ -235,21 +236,25 @@

    Landscape

    OOOO OOOO - 6 + 6 sea, shore - X XX XXXXX - OOOO OOOO OOOO OOOO - OOOO OOOO + X XX XXXXX + OOOO OOOO OOOO OOOO + OOOO OOOO OOOO OOOO OOOO OOOX - OOOO OOOO - OOOO OOOO - OOOO OOOO OOOO OOOO + OOOO OOOO + OOOO OOOO + OOOO OOOO OOOO OOOO + + + river + XXXX XXXX + OOOO OOOO - canal, river - XXXX XXXX - OOOO OOOO + canal + XOOO OOOO OOOO OOOO lock @@ -272,7 +277,7 @@

    Landscape

    XXXX XXXX OOXXX XOO XXXX XXXX - OOOO OOOO OOOO OOOO + XOOO OOOO OOOO OOOO industry under construction @@ -305,7 +310,7 @@

    Landscape

    XXXX XXXX OOOO OOOO XXXX XXXX - OOOO OOOO OOOO OOOO + XOOO OOOO OOOO OOOO bits diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 385a952b60774..b1248d88bec38 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -1884,10 +1884,12 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, IndustryType type, i->location.Add(cur_tile); WaterClass wc = (IsWaterTile(cur_tile) ? GetWaterClass(cur_tile) : WATER_CLASS_INVALID); + bool river = HasTileCanalOnRiver(cur_tile); Command::Do(DC_EXEC | DC_NO_TEST_TOWN_RATING | DC_NO_MODIFY_TOWN_RATING, cur_tile); MakeIndustry(cur_tile, i->index, it.gfx, Random(), wc); + if (river) SetCanalOnRiver(cur_tile); if (_generating_world) { SetIndustryConstructionCounter(cur_tile, 3); diff --git a/src/lang/english.txt b/src/lang/english.txt index 083bb2ee518d5..3b0905f5ddede 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1280,6 +1280,9 @@ STR_CONFIG_SETTING_SERVE_NEUTRAL_INDUSTRIES_HELPTEXT :When enabled, i STR_CONFIG_SETTING_EXTRADYNAMITE :Allow removal of more town-owned roads, bridges and tunnels: {STRING2} STR_CONFIG_SETTING_EXTRADYNAMITE_HELPTEXT :Make it easier to remove town-owned infrastructure and buildings +STR_CONFIG_SETTING_DYNAMITE_RIVER :Allow removal of rivers: {STRING2} +STR_CONFIG_SETTING_DYNAMITE_RIVER_HELPTEXT :If disabled, rivers are indestructible and become a permanent part of the landscape + STR_CONFIG_SETTING_TRAIN_LENGTH :Maximum length of trains: {STRING2} STR_CONFIG_SETTING_TRAIN_LENGTH_HELPTEXT :Set the maximum length of trains STR_CONFIG_SETTING_TILE_LENGTH :{COMMA} tile{P 0 "" s} @@ -3005,6 +3008,7 @@ STR_LAI_STATION_DESCRIPTION_WAYPOINT :Waypoint STR_LAI_WATER_DESCRIPTION_WATER :Water STR_LAI_WATER_DESCRIPTION_CANAL :Canal +STR_LAI_WATER_DESCRIPTION_CANALISED_RIVER :Canalised river STR_LAI_WATER_DESCRIPTION_LOCK :Lock STR_LAI_WATER_DESCRIPTION_RIVER :River STR_LAI_WATER_DESCRIPTION_COAST_OR_RIVERBANK :Coast or riverbank @@ -4890,6 +4894,7 @@ STR_ERROR_CAN_T_BUILD_ON_CANAL :{WHITE}... can' STR_ERROR_CAN_T_BUILD_ON_RIVER :{WHITE}... can't build on river STR_ERROR_MUST_DEMOLISH_CANAL_FIRST :{WHITE}Must demolish canal first STR_ERROR_CAN_T_BUILD_AQUEDUCT_HERE :{WHITE}Can't build aqueduct here... +STR_ERROR_MUST_CLEAR_RIVER_FIRST :{WHITE}Must clear river first # Tree related errors STR_ERROR_TREE_ALREADY_HERE :{WHITE}... tree already here diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index acc16a1781eac..f4f153e3b46de 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -3148,6 +3148,11 @@ bool AfterLoadGame() } } + if (IsSavegameVersionBefore(SLV_PERMANENT_RIVERS)) { + /* When loading an older savegame version, treat 'dynamite river' as enabled. */ + _settings_game.construction.dynamite_river = true; + } + /* Compute station catchment areas. This is needed here in case UpdateStationAcceptance is called below. */ Station::RecomputeCatchmentForAll(); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 0d4e0fb9845a0..a5e0962705422 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -341,6 +341,8 @@ enum SaveLoadVersion : uint16 { SLV_DOCK_DOCKINGTILES, ///< 298 PR#9578 All tiles around docks may be docking tiles. SLV_REPAIR_OBJECT_DOCKING_TILES, ///< 299 PR#9594 v12.0 Fixing issue with docking tiles overlapping objects. + SLV_PERMANENT_RIVERS, ///< 300 PR#8461 Permanent rivers. + SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index bc7eb985958d6..bca26f11a8126 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1737,6 +1737,7 @@ static SettingsContainer &GetSettingsTree() limitations->Add(new SettingEntry("construction.command_pause_level")); limitations->Add(new SettingEntry("construction.autoslope")); limitations->Add(new SettingEntry("construction.extra_dynamite")); + limitations->Add(new SettingEntry("construction.dynamite_river")); limitations->Add(new SettingEntry("construction.map_height_limit")); limitations->Add(new SettingEntry("construction.max_bridge_length")); limitations->Add(new SettingEntry("construction.max_bridge_height")); diff --git a/src/settings_type.h b/src/settings_type.h index 65597154e24f6..82b15ad8246a1 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -343,6 +343,7 @@ struct ConstructionSettings { bool extra_dynamite; ///< extra dynamite bool road_stop_on_town_road; ///< allow building of drive-through road stops on town owned roads bool road_stop_on_competitor_road; ///< allow building of drive-through road stops on roads owned by competitors + bool dynamite_river; ///< allow removal of rivers uint8 raw_industry_construction; ///< type of (raw) industry construction (none, "normal", prospecting) uint8 industry_platform; ///< the amount of flat land around an industry bool freeform_edges; ///< allow terraforming the tiles at the map edges diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 2c038c548cc30..bbafe17cf6a36 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -2511,6 +2511,7 @@ CommandCost CmdBuildDock(DoCommandFlag flags, TileIndex tile, StationID station_ /* Get the water class of the water tile before it is cleared.*/ WaterClass wc = GetWaterClass(tile_cur); + bool river = HasTileCanalOnRiver(tile_cur); ret = Command::Do(flags, tile_cur); if (ret.Failed()) return ret; @@ -2549,6 +2550,7 @@ CommandCost CmdBuildDock(DoCommandFlag flags, TileIndex tile, StationID station_ Company::Get(st->owner)->infrastructure.station += 2; MakeDock(tile, st->owner, st->index, direction, wc); + if (river) SetCanalOnRiver(tile + TileOffsByDiagDir(direction)); UpdateStationDockingTiles(st); st->AfterStationTileSetChange(true, STATION_DOCK); diff --git a/src/station_map.h b/src/station_map.h index dbb00ef2f5e93..53c300e0b0128 100644 --- a/src/station_map.h +++ b/src/station_map.h @@ -542,7 +542,7 @@ static inline void MakeStation(TileIndex t, Owner o, StationID sid, StationType SB(_me[t].m6, 2, 1, 0); SB(_me[t].m6, 3, 3, st); _me[t].m7 = 0; - _me[t].m8 = 0; + SB(_me[t].m8, 0, 15, 0); } /** diff --git a/src/table/settings/world_settings.ini b/src/table/settings/world_settings.ini index 5912f89f4cac6..410e127c5dcb4 100644 --- a/src/table/settings/world_settings.ini +++ b/src/table/settings/world_settings.ini @@ -429,6 +429,14 @@ def = true str = STR_CONFIG_SETTING_EXTRADYNAMITE strhelp = STR_CONFIG_SETTING_EXTRADYNAMITE_HELPTEXT +[SDT_BOOL] +var = construction.dynamite_river +from = SLV_PERMANENT_RIVERS +def = true +str = STR_CONFIG_SETTING_DYNAMITE_RIVER +strhelp = STR_CONFIG_SETTING_DYNAMITE_RIVER_HELPTEXT +cat = SC_BASIC + [SDT_VAR] var = construction.max_bridge_length type = SLE_UINT16 diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 211e89b4a0d43..92b5700e83bb0 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -37,6 +37,7 @@ #include "company_gui.h" #include "newgrf_generic.h" #include "industry.h" +#include "cheat_type.h" #include "water_cmd.h" #include "landscape_cmd.h" @@ -238,15 +239,27 @@ void MakeWaterKeepingClass(TileIndex tile, Owner o) wc = WATER_CLASS_CANAL; } + bool river = HasTileCanalOnRiver(tile); /* Zero map array and terminate animation */ DoClearSquare(tile); /* Maybe change to water */ switch (wc) { - case WATER_CLASS_SEA: MakeSea(tile); break; - case WATER_CLASS_CANAL: MakeCanal(tile, o, Random()); break; - case WATER_CLASS_RIVER: MakeRiver(tile, Random()); break; - default: break; + case WATER_CLASS_SEA: + MakeSea(tile); + break; + + case WATER_CLASS_CANAL: + MakeCanal(tile, o, Random()); + if (river) SetCanalOnRiver(tile); + break; + + case WATER_CLASS_RIVER: + MakeRiver(tile, Random()); + break; + + default: + break; } if (wc != WATER_CLASS_INVALID) CheckForDockingTile(tile); @@ -472,6 +485,7 @@ CommandCost CmdBuildCanal(DoCommandFlag flags, TileIndex tile, TileIndex start_t /* Outside the editor, prevent building canals over your own or OWNER_NONE owned canals */ if (water && IsCanal(current_tile) && _game_mode != GM_EDITOR && (IsTileOwner(current_tile, _current_company) || IsTileOwner(current_tile, OWNER_NONE))) continue; + bool river = (HasTileCanalOnRiver(current_tile) && wc == WATER_CLASS_CANAL) || (HasTileWaterClass(current_tile) && GetWaterClass(current_tile) == WATER_CLASS_RIVER); ret = Command::Do(flags, current_tile); if (ret.Failed()) return ret; @@ -503,6 +517,7 @@ CommandCost CmdBuildCanal(DoCommandFlag flags, TileIndex tile, TileIndex start_t } MakeCanal(current_tile, _current_company, Random()); + if (river) SetCanalOnRiver(current_tile); break; } MarkTileDirtyByTile(current_tile); @@ -543,15 +558,24 @@ static CommandCost ClearTile_Water(TileIndex tile, DoCommandFlag flags) if (ret.Failed()) return ret; } + if (IsRiver(tile) && _game_mode == GM_NORMAL && !_settings_game.construction.dynamite_river && !_cheats.magic_bulldozer.value) { + return CommandCost(); + } + if (flags & DC_EXEC) { if (IsCanal(tile) && Company::IsValidID(owner)) { Company::Get(owner)->infrastructure.water--; DirtyCompanyInfrastructureWindows(owner); } bool remove = IsDockingTile(tile); + bool river = HasTileCanalOnRiver(tile); DoClearSquare(tile); + if (river) { + MakeRiver(tile, Random()); + } else if (remove) { + RemoveDockingTile(tile); + } MarkCanalsAndRiversAroundDirty(tile); - if (remove) RemoveDockingTile(tile); } return CommandCost(EXPENSES_CONSTRUCTION, base_cost); @@ -955,10 +979,24 @@ static void GetTileDesc_Water(TileIndex tile, TileDesc *td) switch (GetWaterTileType(tile)) { case WATER_TILE_CLEAR: switch (GetWaterClass(tile)) { - case WATER_CLASS_SEA: td->str = STR_LAI_WATER_DESCRIPTION_WATER; break; - case WATER_CLASS_CANAL: td->str = STR_LAI_WATER_DESCRIPTION_CANAL; break; - case WATER_CLASS_RIVER: td->str = STR_LAI_WATER_DESCRIPTION_RIVER; break; - default: NOT_REACHED(); + case WATER_CLASS_SEA: + td->str = STR_LAI_WATER_DESCRIPTION_WATER; + break; + + case WATER_CLASS_CANAL: + if (HasTileCanalOnRiver(tile)) { + td->str = STR_LAI_WATER_DESCRIPTION_CANALISED_RIVER; + } else { + td->str = STR_LAI_WATER_DESCRIPTION_CANAL; + } + break; + + case WATER_CLASS_RIVER: + td->str = STR_LAI_WATER_DESCRIPTION_RIVER; + break; + + default: + NOT_REACHED(); } break; case WATER_TILE_COAST: td->str = STR_LAI_WATER_DESCRIPTION_COAST_OR_RIVERBANK; break; @@ -1373,8 +1411,23 @@ static VehicleEnterTileStatus VehicleEnter_Water(Vehicle *v, TileIndex tile, int static CommandCost TerraformTile_Water(TileIndex tile, DoCommandFlag flags, int z_new, Slope tileh_new) { + bool no_err_message = _game_mode == GM_NORMAL && !_settings_game.construction.dynamite_river && !_cheats.magic_bulldozer.value; + /* Canals can't be terraformed */ - if (IsWaterTile(tile) && IsCanal(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_CANAL_FIRST); + if (IsCanal(tile)) { + if (HasTileCanalOnRiver(tile) && no_err_message) { + return CMD_ERROR; + } + return_cmd_error(STR_ERROR_MUST_DEMOLISH_CANAL_FIRST); + } + + /* Rivers can't be terraformed */ + if (IsRiver(tile)) { + if (no_err_message) { + return CMD_ERROR; + } + return_cmd_error(STR_ERROR_MUST_CLEAR_RIVER_FIRST); + } return Command::Do(flags, tile); } diff --git a/src/water_map.h b/src/water_map.h index ff2c38a7bcd51..a6e4288907a99 100644 --- a/src/water_map.h +++ b/src/water_map.h @@ -64,6 +64,12 @@ static inline bool IsValidWaterClass(WaterClass wc) return wc < WATER_CLASS_INVALID; } +/** Bases of a canal (for #WATER_CLASS_CANAL class of water). */ +enum CanalBase { + WCC_WITHOUT_RIVER = 0, ///< Without a river under it + WCC_WITH_RIVER = 1, ///< With a river under it +}; + /** Sections of the water depot. */ enum DepotPart { DEPOT_PART_NORTH = 0, ///< Northern part of a depot. @@ -132,6 +138,40 @@ static inline void SetWaterClass(TileIndex t, WaterClass wc) SB(_m[t].m1, 5, 2, wc); } +/** + * Get the base of the canal at a tile. + * @param t Water tile to query. + * @pre GetWaterClass(t) == WATER_CLASS_CANAL + * @return Base of the canal at the tile. + */ +static inline CanalBase GetCanalBase(TileIndex t) +{ + assert(GetWaterClass(t) == WATER_CLASS_CANAL); + return (CanalBase)HasBit(_me[t].m8, 15) ? WCC_WITH_RIVER : WCC_WITHOUT_RIVER; +} + +/** + * Set the base of the canal to indicate there is a river under a tile. + * @param t Water tile of water class canal to query. + * @pre GetWaterClass(t) == WATER_CLASS_CANAL + */ +static inline void SetCanalOnRiver(TileIndex t) +{ + assert(GetWaterClass(t) == WATER_CLASS_CANAL); + SB(_me[t].m8, 15, 1, WCC_WITH_RIVER); +} + +/** + * Checks whether the canal at a tile has a river under it. + * @param t Water tile of water class canal to query. + * @pre GetWaterClass(t) == WATER_CLASS_CANAL + * @return true if the canal has a river under it. + */ +static inline bool HasTileCanalOnRiver(TileIndex t) +{ + return HasTileWaterClass(t) && GetWaterClass(t) == WATER_CLASS_CANAL && GetCanalBase(t) == WCC_WITH_RIVER; +} + /** * Tests if the tile was built on water. * @param t the tile to check