diff --git a/src/heightmap.cpp b/src/heightmap.cpp index f8c7369954..0dee452593 100644 --- a/src/heightmap.cpp +++ b/src/heightmap.cpp @@ -473,6 +473,11 @@ void FixSlopes() } } } + + extern bool CheckMapEdgesAreWater(); + if (_settings_game.construction.map_edge_mode != 0 && !CheckMapEdgesAreWater()) { + _settings_game.construction.map_edge_mode = 0; + } } /** diff --git a/src/lang/extra/english.txt b/src/lang/extra/english.txt index ce31fa7211..c9ab72294e 100644 --- a/src/lang/extra/english.txt +++ b/src/lang/extra/english.txt @@ -566,6 +566,12 @@ STR_CONFIG_SETTING_MIN_LAND_AREA_ZERO :Off STR_CONFIG_SETTING_FLOOD_FROM_EDGES :Water floods from map edges: {STRING2} STR_CONFIG_SETTING_FLOOD_FROM_EDGES_HELPTEXT :Whether water floods into edge tiles at sea level from the map edge. +STR_CONFIG_SETTING_MAP_EDGE_MODE :Map edge behaviour: {STRING2} +STR_CONFIG_SETTING_MAP_EDGE_MODE_HELPTEXT :Whether raising the map edges above sea-level is allowed, and how the void area outside the map is displayed +STR_CONFIG_SETTING_MAP_EDGE_MODE_DEFAULT :Default +STR_CONFIG_SETTING_MAP_EDGE_MODE_SEA_LEVEL :Map edges restricted to sea level +STR_CONFIG_SETTING_MAP_EDGE_MODE_SEA_LEVEL_OUTSIDE_WATER :Map edges restricted to sea level, outside map shown as water + STR_CONFIG_SETTING_TREES_AROUND_SNOWLINE :Adjusted arctic tree placement: {STRING2} STR_CONFIG_SETTING_TREES_AROUND_SNOWLINE_HELPTEXT :Adjust placement of trees around snow line in artic climate. Trees thin out above snowline. Trees are a mix of arctic and temperate just below snowline. Below that trees are temperate. STR_CONFIG_SETTING_TREES_AROUND_SNOWLINE_RANGE :Arctic tree range: {STRING2} diff --git a/src/settings.cpp b/src/settings.cpp index 096ae08f99..da11570823 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1462,6 +1462,15 @@ static void ViewportMapLandscapeModeChanged(int32 new_value) MarkAllViewportMapLandscapesDirty(); } +static void MarkAllViewportsDirty(int32 new_value) +{ + extern void MarkAllViewportMapLandscapesDirty(); + MarkAllViewportMapLandscapesDirty(); + + extern void MarkWholeNonMapViewportsDirty(); + MarkWholeNonMapViewportsDirty(); +} + static void UpdateLinkgraphColours(int32 new_value) { BuildLinkStatsLegend(); @@ -1655,6 +1664,35 @@ static void UpdateFreeformEdges(int32 new_value) MarkWholeScreenDirty(); } +bool CheckMapEdgesAreWater() +{ + auto check_tile = [&](uint x, uint y) -> bool { + int h = 0; + Slope slope = GetTilePixelSlopeOutsideMap(x, y, &h); + return slope == SLOPE_FLAT && h == 0; + }; + for (uint x = 0; x <= MapMaxX(); x++) { + if (!check_tile(x, 0) || !check_tile(x, MapMaxY())) return false; + } + for (uint y = 1; y < MapMaxY(); y++) { + if (!check_tile(0, y) || !check_tile(MapMaxX(), y)) return false; + } + + return true; +} + +static bool CheckMapEdgeMode(int32 &new_value) +{ + if (_game_mode == GM_MENU || !_settings_game.construction.freeform_edges || new_value == 0) return true; + + if (!CheckMapEdgesAreWater()) { + ShowErrorMessage(STR_CONFIG_SETTING_EDGES_NOT_WATER, INVALID_STRING_ID, WL_ERROR); + return false; + } + + return true; +} + /** * Changing the setting "allow multiple NewGRF sets" is not allowed * if there are vehicles. diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index c7ceeeb1c1..3262d1ff82 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2414,6 +2414,7 @@ static SettingsContainer &GetSettingsTree() } environment->Add(new SettingEntry("construction.flood_from_edges")); + environment->Add(new SettingEntry("construction.map_edge_mode")); environment->Add(new SettingEntry("economy.day_length_factor")); environment->Add(new SettingEntry("station.modified_catchment")); environment->Add(new SettingEntry("station.catchment_increase")); diff --git a/src/settings_type.h b/src/settings_type.h index f5ce05b060..cc2232f8c4 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -508,6 +508,7 @@ struct ConstructionSettings { uint8 industry_platform; ///< the amount of flat land around an industry bool freeform_edges; ///< allow terraforming the tiles at the map edges bool flood_from_edges; ///< whether water floods from map edges + uint8 map_edge_mode; ///< map edge mode uint8 extra_tree_placement; ///< (dis)allow building extra trees in-game uint8 trees_around_snow_line_range; ///< range around snowline for mixed and arctic forest. bool trees_around_snow_line_enabled; ///< enable mixed and arctic forest around snowline, and no trees above snowline diff --git a/src/sl/saveload.cpp b/src/sl/saveload.cpp index a3bc8d86f8..0ec42558c9 100644 --- a/src/sl/saveload.cpp +++ b/src/sl/saveload.cpp @@ -3544,6 +3544,9 @@ static SaveOrLoadResult DoLoad(LoadFilter *reader, bool load_check) * No pools are loaded. References are not possible, and thus do not need resolving. */ SlLoadCheckChunks(); } else { + /* Unconditionally default this to 0 when loading a savegame */ + _settings_game.construction.map_edge_mode = 0; + /* Load chunks and resolve references */ SlLoadChunks(); SlFixPointers(); @@ -3676,6 +3679,9 @@ SaveOrLoadResult SaveOrLoad(const std::string &filename, SaveLoadOperation fop, InitializeGame(256, 256, true, true); // set a mapsize of 256x256 for TTDPatch games or it might get confused + /* Unconditionally default this to 0 when loading a savegame */ + _settings_game.construction.map_edge_mode = 0; + /* TTD/TTO savegames have no NewGRFs, TTDP savegame have them * and if so a new NewGRF list will be made in LoadOldSaveGame. * Note: this is done here because AfterLoadGame is also called diff --git a/src/table/settings/world_settings.ini b/src/table/settings/world_settings.ini index 69e850ef60..2a2a5a62b2 100644 --- a/src/table/settings/world_settings.ini +++ b/src/table/settings/world_settings.ini @@ -13,8 +13,10 @@ static bool CheckMaxHeightLevel(int32_t &new_value); static bool CheckFreeformEdges(int32_t &new_value); static void UpdateFreeformEdges(int32_t new_value); +static bool CheckMapEdgeMode(int32_t &new_value); static void ClimateThresholdModeChanged(int32 new_value); static void PublicRoadsSettingChange(int32 new_value); +static void MarkAllViewportsDirty(int32 new_value); static bool AllowRoadStopsUnderBridgesSettingGUI(SettingOnGuiCtrlData &data); static bool TreePlacerSettingGUI(SettingOnGuiCtrlData &data); @@ -929,6 +931,20 @@ strhelp = STR_CONFIG_SETTING_FLOOD_FROM_EDGES_HELPTEXT cat = SC_ADVANCED patxname = ""construction.flood_from_edges"" +[SDT_VAR] +var = construction.map_edge_mode +type = SLE_UINT8 +flags = SF_GUI_DROPDOWN | SF_PATCH +def = 0 +min = 0 +max = 2 +str = STR_CONFIG_SETTING_MAP_EDGE_MODE +strhelp = STR_CONFIG_SETTING_MAP_EDGE_MODE_HELPTEXT +strval = STR_CONFIG_SETTING_MAP_EDGE_MODE_DEFAULT +pre_cb = CheckMapEdgeMode +post_cb = MarkAllViewportsDirty +cat = SC_ADVANCED + [SDT_VAR] var = construction.extra_tree_placement type = SLE_UINT8 diff --git a/src/terraform_cmd.cpp b/src/terraform_cmd.cpp index 3ff930e745..cf80425342 100644 --- a/src/terraform_cmd.cpp +++ b/src/terraform_cmd.cpp @@ -227,7 +227,15 @@ CommandCost CmdTerraformLand(TileIndex tile, DoCommandFlag flags, uint32 p1, uin assert(t < MapSize()); /* MP_VOID tiles can be terraformed but as tunnels and bridges * cannot go under / over these tiles they don't need checking. */ - if (IsTileType(t, MP_VOID)) continue; + if (IsTileType(t, MP_VOID)) { + if (_settings_game.construction.map_edge_mode != 0) { + CommandCost err(STR_ERROR_TOO_CLOSE_TO_EDGE_OF_MAP); + err.SetTile(t); + return err; + } else { + continue; + } + } /* Find new heights of tile corners */ int z_N = TerraformGetHeightOfTile(&ts, t + TileDiffXY(0, 0)); diff --git a/src/viewport.cpp b/src/viewport.cpp index 25b12bdd61..b205c7b645 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -1752,6 +1752,12 @@ static void ViewportAddLandscape() /* Outside of map. If we are on the north border of the map, there may still be a bridge visible, * so we need to loop over more rows to possibly find one. */ if ((tilecoord.x <= 0 || tilecoord.y <= 0) && min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false; + + if (_settings_game.construction.map_edge_mode == 2 && _cur_ti.tileh == SLOPE_FLAT && _cur_ti.z == 0 && min_visible_height <= 0) { + last_row = false; + AddTileSpriteToDraw(SPR_FLAT_WATER_TILE, PAL_NONE, _cur_ti.x, _cur_ti.y, _cur_ti.z); + continue; + } } if (tile_visible) { @@ -3255,34 +3261,39 @@ static inline TileIndex ViewportMapGetMostSignificantTileType(const Viewport * c return result; } +static uint32 ViewportMapVoidColour() +{ + return (_settings_game.construction.map_edge_mode == 2) ? _vp_map_water_colour[SLOPE_FLAT] : 0; +} + /** Get the colour of a tile, can be 32bpp RGB or 8bpp palette index. */ template uint32 ViewportMapGetColour(const Viewport * const vp, int x, int y, const uint colour_index) { - if (x >= static_cast(MapMaxX() * TILE_SIZE) || y >= static_cast(MapMaxY() * TILE_SIZE)) return 0; + if (x >= static_cast(MapMaxX() * TILE_SIZE) || y >= static_cast(MapMaxY() * TILE_SIZE)) return ViewportMapVoidColour(); /* Very approximative but fast way to get the tile when taking Z into account. */ const TileIndex tile_tmp = TileVirtXY(std::max(0, x), std::max(0, y)); const int z = TileHeight(tile_tmp) * 4; if (x + z < 0 || y + z < 0 || static_cast(x + z) >= MapSizeX() << 4) { /* Wrapping of tile X coordinate causes a graphic glitch below south west border. */ - return 0; + return ViewportMapVoidColour(); } TileIndex tile = TileVirtXY(x + z, y + z); - if (tile >= MapSize()) return 0; + if (tile >= MapSize()) return ViewportMapVoidColour(); const int z2 = TileHeight(tile) * 4; if (unlikely(z2 != z)) { const int approx_z = (z + z2) / 2; if (x + approx_z < 0 || y + approx_z < 0 || static_cast(x + approx_z) >= MapSizeX() << 4) { /* Wrapping of tile X coordinate causes a graphic glitch below south west border. */ - return 0; + return ViewportMapVoidColour(); } tile = TileVirtXY(x + approx_z, y + approx_z); - if (tile >= MapSize()) return 0; + if (tile >= MapSize()) return ViewportMapVoidColour(); } TileType tile_type = MP_VOID; tile = ViewportMapGetMostSignificantTileType(vp, tile, &tile_type); - if (tile_type == MP_VOID) return 0; + if (tile_type == MP_VOID) return ViewportMapVoidColour(); /* Return the colours. */ switch (vp->map_type) {