From 0b99a0b66a9f3d9bff5b325258146dc713f1bc5c Mon Sep 17 00:00:00 2001 From: Tyler Trahan Date: Fri, 26 Sep 2025 10:37:27 -0400 Subject: [PATCH] Feature: Draw infinite water when all borders are water (#13289) --- src/genworld_gui.cpp | 49 ++++++++++++++++----------- src/heightmap.cpp | 5 +++ src/landscape.cpp | 30 ++++++++++++++++ src/landscape.h | 2 ++ src/lang/english.txt | 7 ++-- src/object_cmd.cpp | 5 +++ src/settings_type.h | 8 +++++ src/table/settings/world_settings.ini | 7 ++++ src/void_cmd.cpp | 7 +++- src/water_cmd.cpp | 7 ++++ src/widgets/genworld_widget.h | 2 +- 11 files changed, 105 insertions(+), 24 deletions(-) diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index b91a51945e..f910f75cf3 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -115,7 +115,7 @@ static constexpr NWidgetPart _nested_generate_landscape_widgets[] = { NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_VARIETY_PULLDOWN), SetToolTip(STR_CONFIG_SETTING_VARIETY_HELPTEXT), SetFill(1, 1), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_SMOOTHNESS_PULLDOWN), SetToolTip(STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_HELPTEXT), SetFill(1, 1), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_RIVER_PULLDOWN), SetToolTip(STR_CONFIG_SETTING_RIVER_AMOUNT_HELPTEXT), SetFill(1, 1), - NWidget(WWT_TEXTBTN, COLOUR_ORANGE, WID_GL_BORDERS_RANDOM), SetToolTip(STR_MAPGEN_BORDER_TYPE_TOOLTIP), SetFill(1, 1), + NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_BORDERS_PULLDOWN), SetToolTip(STR_MAPGEN_BORDER_TYPE_TOOLTIP), SetFill(1, 1), EndContainer(), EndContainer(), @@ -379,6 +379,7 @@ static DropDownList BuildTownNameDropDown() static const StringID _elevations[] = {STR_TERRAIN_TYPE_VERY_FLAT, STR_TERRAIN_TYPE_FLAT, STR_TERRAIN_TYPE_HILLY, STR_TERRAIN_TYPE_MOUNTAINOUS, STR_TERRAIN_TYPE_ALPINIST, STR_TERRAIN_TYPE_CUSTOM}; static const StringID _sea_lakes[] = {STR_SEA_LEVEL_VERY_LOW, STR_SEA_LEVEL_LOW, STR_SEA_LEVEL_MEDIUM, STR_SEA_LEVEL_HIGH, STR_SEA_LEVEL_CUSTOM}; static const StringID _rivers[] = {STR_RIVERS_NONE, STR_RIVERS_FEW, STR_RIVERS_MODERATE, STR_RIVERS_LOT}; +static const StringID _borders[] = {STR_MAPGEN_BORDER_RANDOMIZE, STR_MAPGEN_BORDER_MANUAL, STR_MAPGEN_BORDER_INFINITE_WATER}; static const StringID _smoothness[] = {STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_VERY_SMOOTH, STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_SMOOTH, STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_ROUGH, STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_VERY_ROUGH}; static const StringID _rotation[] = {STR_CONFIG_SETTING_HEIGHTMAP_ROTATION_COUNTER_CLOCKWISE, STR_CONFIG_SETTING_HEIGHTMAP_ROTATION_CLOCKWISE}; static const StringID _num_towns[] = {STR_NUM_VERY_LOW, STR_NUM_LOW, STR_NUM_NORMAL, STR_NUM_HIGH, STR_NUM_CUSTOM}; @@ -471,7 +472,7 @@ struct GenerateLandscapeWindow : public Window { case WID_GL_RIVER_PULLDOWN: return GetString(_rivers[_settings_newgame.game_creation.amount_of_rivers]); case WID_GL_SMOOTHNESS_PULLDOWN: return GetString(_smoothness[_settings_newgame.game_creation.tgen_smoothness]); case WID_GL_VARIETY_PULLDOWN: return GetString(_variety[_settings_newgame.game_creation.variety]); - case WID_GL_BORDERS_RANDOM: return GetString((_settings_newgame.game_creation.water_borders == BorderFlag::Random) ? STR_MAPGEN_BORDER_RANDOMIZE : STR_MAPGEN_BORDER_MANUAL); + case WID_GL_BORDERS_PULLDOWN: return GetString(_borders[_settings_newgame.game_creation.water_border_presets]); case WID_GL_WATER_NE: return GetString((_settings_newgame.game_creation.water_borders == BorderFlag::Random) ? STR_MAPGEN_BORDER_RANDOM : _settings_newgame.game_creation.water_borders.Test(BorderFlag::NorthEast) ? STR_MAPGEN_BORDER_WATER : STR_MAPGEN_BORDER_FREEFORM); case WID_GL_WATER_NW: return GetString((_settings_newgame.game_creation.water_borders == BorderFlag::Random) ? STR_MAPGEN_BORDER_RANDOM : _settings_newgame.game_creation.water_borders.Test(BorderFlag::NorthWest) ? STR_MAPGEN_BORDER_WATER : STR_MAPGEN_BORDER_FREEFORM); case WID_GL_WATER_SE: return GetString((_settings_newgame.game_creation.water_borders == BorderFlag::Random) ? STR_MAPGEN_BORDER_RANDOM : _settings_newgame.game_creation.water_borders.Test(BorderFlag::SouthEast) ? STR_MAPGEN_BORDER_WATER : STR_MAPGEN_BORDER_FREEFORM); @@ -507,12 +508,10 @@ struct GenerateLandscapeWindow : public Window { if (mode == GLWM_GENERATE) { this->SetWidgetDisabledState(WID_GL_SMOOTHNESS_PULLDOWN, _settings_newgame.game_creation.land_generator == LG_ORIGINAL); this->SetWidgetDisabledState(WID_GL_VARIETY_PULLDOWN, _settings_newgame.game_creation.land_generator == LG_ORIGINAL); - this->SetWidgetDisabledState(WID_GL_BORDERS_RANDOM, _settings_newgame.game_creation.land_generator == LG_ORIGINAL || !_settings_newgame.construction.freeform_edges); + this->SetWidgetDisabledState(WID_GL_BORDERS_PULLDOWN, _settings_newgame.game_creation.land_generator == LG_ORIGINAL); this->SetWidgetsDisabledState(_settings_newgame.game_creation.land_generator == LG_ORIGINAL || !_settings_newgame.construction.freeform_edges || _settings_newgame.game_creation.water_borders == BorderFlag::Random, WID_GL_WATER_NW, WID_GL_WATER_NE, WID_GL_WATER_SE, WID_GL_WATER_SW); - this->SetWidgetLoweredState(WID_GL_BORDERS_RANDOM, _settings_newgame.game_creation.water_borders == BorderFlag::Random); - this->SetWidgetLoweredState(WID_GL_WATER_NW, _settings_newgame.game_creation.water_borders.Test(BorderFlag::NorthWest)); this->SetWidgetLoweredState(WID_GL_WATER_NE, _settings_newgame.game_creation.water_borders.Test(BorderFlag::NorthEast)); this->SetWidgetLoweredState(WID_GL_WATER_SE, _settings_newgame.game_creation.water_borders.Test(BorderFlag::SouthEast)); @@ -622,10 +621,7 @@ struct GenerateLandscapeWindow : public Window { case WID_GL_SMOOTHNESS_PULLDOWN: strs = _smoothness; break; case WID_GL_VARIETY_PULLDOWN: strs = _variety; break; case WID_GL_HEIGHTMAP_ROTATION_PULLDOWN: strs = _rotation; break; - case WID_GL_BORDERS_RANDOM: - d = maxdim(GetStringBoundingBox(STR_MAPGEN_BORDER_RANDOMIZE), GetStringBoundingBox(STR_MAPGEN_BORDER_MANUAL)); - break; - + case WID_GL_BORDERS_PULLDOWN: strs = _borders; break; case WID_GL_WATER_NE: case WID_GL_WATER_NW: case WID_GL_WATER_SE: @@ -807,7 +803,11 @@ struct GenerateLandscapeWindow : public Window { ShowDropDownMenu(this, _variety, _settings_newgame.game_creation.variety, WID_GL_VARIETY_PULLDOWN, 0, 0); break; - /* Freetype map borders */ + /* Map borders */ + case WID_GL_BORDERS_PULLDOWN: + ShowDropDownMenu(this, _borders, _settings_newgame.game_creation.water_border_presets, WID_GL_BORDERS_PULLDOWN, 0, 0); + break; + case WID_GL_WATER_NW: _settings_newgame.game_creation.water_borders.Flip(BorderFlag::NorthWest); SndClickBeep(); @@ -832,16 +832,6 @@ struct GenerateLandscapeWindow : public Window { this->InvalidateData(); break; - case WID_GL_BORDERS_RANDOM: - if (_settings_newgame.game_creation.water_borders == BorderFlag::Random) { - _settings_newgame.game_creation.water_borders.Reset(); - } else { - _settings_newgame.game_creation.water_borders = BorderFlag::Random; - } - SndClickBeep(); - this->InvalidateData(); - break; - case WID_GL_AI_BUTTON: ///< AI Settings ShowAIConfigWindow(); break; @@ -908,6 +898,25 @@ struct GenerateLandscapeWindow : public Window { break; } + case WID_GL_BORDERS_PULLDOWN: { + switch (index) { + case BFP_RANDOM: + _settings_newgame.game_creation.water_borders = BorderFlag::Random; + _settings_newgame.construction.freeform_edges = true; + break; + case BFP_MANUAL: + _settings_newgame.game_creation.water_borders = {}; + _settings_newgame.construction.freeform_edges = true; + break; + case BFP_INFINITE_WATER: + _settings_newgame.game_creation.water_borders = BORDERFLAGS_ALL; + _settings_newgame.construction.freeform_edges = false; + break; + } + _settings_newgame.game_creation.water_border_presets = static_cast(index); + break; + } + case WID_GL_WATER_PULLDOWN: { if ((uint)index == CUSTOM_SEA_LEVEL_NUMBER_DIFFICULTY) { this->widget_id = widget; diff --git a/src/heightmap.cpp b/src/heightmap.cpp index 543ef17952..9d7a09bf7e 100644 --- a/src/heightmap.cpp +++ b/src/heightmap.cpp @@ -9,6 +9,7 @@ #include "stdafx.h" #include "heightmap.h" +#include "landscape.h" #include "clear_map.h" #include "strings_func.h" #include "void_map.h" @@ -523,6 +524,10 @@ bool LoadHeightmap(DetailedFileType dft, std::string_view filename) GreyscaleToMapHeights(x, y, map); FixSlopes(); + + /* If all map borders are water, we will draw infinite water. */ + _settings_game.construction.freeform_edges = !IsMapSurroundedByWater(); + MarkWholeScreenDirty(); return true; diff --git a/src/landscape.cpp b/src/landscape.cpp index 61aa649874..621de24c01 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -637,6 +637,36 @@ void ClearSnowLine() _snow_line = nullptr; } +/** + * Check if all tiles on the map edge should be considered water borders. + * @return true If the edge of the map is flat and height 0, allowing for infinite water borders. + */ +bool IsMapSurroundedByWater() +{ + auto check_tile = [](uint x, uint y) -> bool { + auto [slope, h] = GetTilePixelSlopeOutsideMap(x, y); + return ((slope == SLOPE_FLAT) && (h == 0)); + }; + + /* Check the map corners. */ + if (!check_tile(0, 0)) return false; + if (!check_tile(0, Map::SizeY())) return false; + if (!check_tile(Map::SizeX(), 0)) return false; + if (!check_tile(Map::SizeX(), Map::SizeY())) return false; + + /* Check the map edges.*/ + for (uint x = 0; x <= Map::SizeX(); x++) { + if (!check_tile(x, 0)) return false; + if (!check_tile(x, Map::SizeY())) return false; + } + for (uint y = 1; y < Map::SizeY(); y++) { + if (!check_tile(0, y)) return false; + if (!check_tile(Map::SizeX(), y)) return false; + } + + return true; +} + /** * Clear a piece of landscape * @param flags of operation to conduct diff --git a/src/landscape.h b/src/landscape.h index db2a9781e4..3f33789694 100644 --- a/src/landscape.h +++ b/src/landscape.h @@ -33,6 +33,8 @@ uint8_t HighestSnowLine(); uint8_t LowestSnowLine(); void ClearSnowLine(); +bool IsMapSurroundedByWater(); + int GetSlopeZInCorner(Slope tileh, Corner corner); std::tuple GetFoundationSlope(TileIndex tile); diff --git a/src/lang/english.txt b/src/lang/english.txt index f33f4848fc..9aab6f47bb 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -3428,8 +3428,11 @@ STR_MAPGEN_SOUTHWEST :{BLACK}Southwes STR_MAPGEN_BORDER_FREEFORM :{BLACK}Freeform STR_MAPGEN_BORDER_WATER :{BLACK}Water STR_MAPGEN_BORDER_RANDOM :{BLACK}Random -STR_MAPGEN_BORDER_RANDOMIZE :{BLACK}Random -STR_MAPGEN_BORDER_MANUAL :{BLACK}Manual + +###length 3 +STR_MAPGEN_BORDER_RANDOMIZE :Random +STR_MAPGEN_BORDER_MANUAL :Manual +STR_MAPGEN_BORDER_INFINITE_WATER :Infinite Water STR_MAPGEN_HEIGHTMAP_ROTATION :{BLACK}Heightmap rotation: STR_MAPGEN_HEIGHTMAP_NAME :{BLACK}Heightmap name: diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp index b17b30c9c3..4cf1c579e8 100644 --- a/src/object_cmd.cpp +++ b/src/object_cmd.cpp @@ -253,6 +253,11 @@ CommandCost CmdBuildObject(DoCommandFlags flags, TileIndex tile, ObjectType type Owner o = GetTileOwner(t); if (o != OWNER_NONE && o != OWNER_WATER) cost.AddCost(CheckOwnership(o, t)); + /* If freeform edges are disabled, don't allow building on edge tiles. */ + if (!_settings_game.construction.freeform_edges && (!IsInsideMM(TileX(t), 1, Map::MaxX() - 1) || !IsInsideMM(TileY(t), 1, Map::MaxY() - 1))) { + return CommandCost(STR_ERROR_TOO_CLOSE_TO_EDGE_OF_MAP); + } + /* However, the tile has to be clear of vehicles. */ cost.AddCost(EnsureNoVehicleOnGround(t)); } diff --git a/src/settings_type.h b/src/settings_type.h index 86350e4054..ebbe874da0 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -64,6 +64,13 @@ enum IndustryDensity : uint8_t { ID_END, ///< Number of industry density settings. }; +/** Possible options for the Borders pulldown in the Genworld GUI. */ +enum BorderFlagPresets : uint8_t { + BFP_RANDOM = 0, + BFP_MANUAL, + BFP_INFINITE_WATER, +}; + /** Possible values for the "timekeeping_units" setting. */ enum TimekeepingUnits : uint8_t { TKU_CALENDAR = 0, @@ -375,6 +382,7 @@ struct GameCreationSettings { uint8_t town_name; ///< the town name generator used for town names LandscapeType landscape; ///< the landscape we're currently in BorderFlags water_borders; ///< bitset of the borders that are water + BorderFlagPresets water_border_presets; ///< presets for map border options uint16_t custom_town_number; ///< manually entered number of towns uint16_t custom_industry_number; ///< manually entered number of industries uint8_t variety; ///< variety level applied to TGP diff --git a/src/table/settings/world_settings.ini b/src/table/settings/world_settings.ini index a4c97cf854..f17bf4d729 100644 --- a/src/table/settings/world_settings.ini +++ b/src/table/settings/world_settings.ini @@ -268,6 +268,13 @@ def = 15 min = 0 max = 16 +[SDT_VAR] +var = game_creation.water_border_presets +type = SLE_UINT8 +def = BFP_RANDOM +min = BFP_RANDOM +max = BFP_INFINITE_WATER + [SDT_VAR] var = game_creation.custom_town_number type = SLE_UINT16 diff --git a/src/void_cmd.cpp b/src/void_cmd.cpp index 2665f4d2bf..a49483e91c 100644 --- a/src/void_cmd.cpp +++ b/src/void_cmd.cpp @@ -21,7 +21,12 @@ static void DrawTile_Void(TileInfo *ti) { - DrawGroundSprite(SPR_FLAT_BARE_LAND + SlopeToSpriteOffset(ti->tileh), PALETTE_ALL_BLACK); + /* If freeform edges are off, draw infinite water off the edges of the map. */ + if (!_settings_game.construction.freeform_edges) { + DrawGroundSprite(SPR_FLAT_WATER_TILE + SlopeToSpriteOffset(ti->tileh), PAL_NONE); + } else { + DrawGroundSprite(SPR_FLAT_BARE_LAND + SlopeToSpriteOffset(ti->tileh), PALETTE_ALL_BLACK); + } } diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 39a06374e1..6533d34f22 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -454,6 +454,13 @@ CommandCost CmdBuildLock(DoCommandFlags flags, TileIndex tile) DiagDirection dir = GetInclinedSlopeDirection(GetTileSlope(tile)); if (dir == INVALID_DIAGDIR) return CommandCost(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); + TileIndex lower_tile = TileAddByDiagDir(tile, ReverseDiagDir(dir)); + + /* If freeform edges are disabled, don't allow building on edge tiles. */ + if (!_settings_game.construction.freeform_edges && (!IsInsideMM(TileX(lower_tile), 1, Map::MaxX() - 1) || !IsInsideMM(TileY(lower_tile), 1, Map::MaxY() - 1))) { + return CommandCost(STR_ERROR_TOO_CLOSE_TO_EDGE_OF_MAP); + } + return DoBuildLock(tile, dir, flags); } diff --git a/src/widgets/genworld_widget.h b/src/widgets/genworld_widget.h index 8d852a117b..e57e4efd08 100644 --- a/src/widgets/genworld_widget.h +++ b/src/widgets/genworld_widget.h @@ -54,7 +54,7 @@ enum GenerateLandscapeWidgets : WidgetID { WID_GL_SMOOTHNESS_PULLDOWN, ///< Dropdown 'Smoothness'. WID_GL_VARIETY_PULLDOWN, ///< Dropdown 'Variety distribution'. - WID_GL_BORDERS_RANDOM, ///< 'Random'/'Manual' borders. + WID_GL_BORDERS_PULLDOWN, ///< Dropdown 'Map edges'. WID_GL_WATER_NW, ///< NW 'Water'/'Freeform'. WID_GL_WATER_NE, ///< NE 'Water'/'Freeform'. WID_GL_WATER_SE, ///< SE 'Water'/'Freeform'.