diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index 6fee9de03f..7946681e31 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -90,6 +90,7 @@ enum GenerateLandscapeWindowWidgets { GLAND_TERRAIN_PULLDOWN, GLAND_WATER_PULLDOWN, GLAND_SMOOTHNESS_PULLDOWN, + GLAND_VARIETY_PULLDOWN, GLAND_BORDER_TYPES, GLAND_BORDERS_RANDOM, @@ -130,6 +131,7 @@ static const NWidgetPart _nested_generate_landscape_widgets[] = { NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_RANDOM_SEED, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_QUANTITY_OF_SEA_LAKES, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_TREE_PLACER, STR_NULL), SetFill(1, 1), + NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_VARIETY, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_BORDER_TYPES), SetDataTip(STR_MAPGEN_BORDER_TYPE, STR_NULL), SetFill(1, 1), EndContainer(), /* Widgets at the right of the labels. */ @@ -146,6 +148,7 @@ static const NWidgetPart _nested_generate_landscape_widgets[] = { NWidget(WWT_EDITBOX, COLOUR_WHITE, GLAND_RANDOM_EDITBOX), SetDataTip(STR_MAPGEN_RANDOM_SEED_OSKTITLE, STR_MAPGEN_RANDOM_SEED_HELP), SetFill(1, 1), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_WATER_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TREE_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), + NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_VARIETY_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_BORDERS_RANDOM), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), EndContainer(), EndContainer(), @@ -239,6 +242,7 @@ static const NWidgetPart _nested_heightmap_load_widgets[] = { NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_NUMBER_OF_INDUSTRIES, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_RANDOM_SEED, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_TREE_PLACER, STR_NULL), SetFill(1, 1), + NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_VARIETY, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_HEIGHTMAP_ROTATION, STR_NULL), SetFill(1, 1), EndContainer(), /* Widgets at the right of the labels. */ @@ -254,6 +258,7 @@ static const NWidgetPart _nested_heightmap_load_widgets[] = { NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_INDUSTRY_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), NWidget(WWT_EDITBOX, COLOUR_WHITE, GLAND_RANDOM_EDITBOX), SetDataTip(STR_MAPGEN_RANDOM_SEED_OSKTITLE, STR_MAPGEN_RANDOM_SEED_HELP), SetFill(1, 1), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TREE_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), + NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_VARIETY_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_HEIGHTMAP_ROTATION_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), EndContainer(), NWidget(NWID_VERTICAL), SetPIP(0, 4, 0), @@ -330,6 +335,7 @@ static const StringID _rotation[] = {STR_CONFIG_SETTING_HEIGHTMAP_ROTATION_CO static const StringID _landscape[] = {STR_CONFIG_SETTING_LAND_GENERATOR_ORIGINAL, STR_CONFIG_SETTING_LAND_GENERATOR_TERRA_GENESIS, INVALID_STRING_ID}; static const StringID _num_towns[] = {STR_NUM_VERY_LOW, STR_NUM_LOW, STR_NUM_NORMAL, STR_NUM_HIGH, STR_NUM_CUSTOM, INVALID_STRING_ID}; static const StringID _num_inds[] = {STR_NONE, STR_NUM_VERY_LOW, STR_NUM_LOW, STR_NUM_NORMAL, STR_NUM_HIGH, INVALID_STRING_ID}; +static const StringID _variety[] = {STR_NONE, STR_NUM_VERY_LOW, STR_NUM_LOW, STR_NUM_MEDIUM, STR_NUM_HIGH, STR_NUM_VERY_HIGH, INVALID_STRING_ID}; struct GenerateLandscapeWindow : public QueryStringBaseWindow { uint widget_id; @@ -369,6 +375,7 @@ struct GenerateLandscapeWindow : public QueryStringBaseWindow { case GLAND_TERRAIN_PULLDOWN: SetDParam(0, _elevations[_settings_newgame.difficulty.terrain_type]); break; case GLAND_WATER_PULLDOWN: SetDParam(0, _sea_lakes[_settings_newgame.difficulty.quantity_sea_lakes]); break; case GLAND_SMOOTHNESS_PULLDOWN: SetDParam(0, _smoothness[_settings_newgame.game_creation.tgen_smoothness]); break; + case GLAND_VARIETY_PULLDOWN: SetDParam(0, _variety[_settings_newgame.game_creation.variety]); break; case GLAND_BORDERS_RANDOM: SetDParam(0, (_settings_newgame.game_creation.water_borders == BORDERS_RANDOM) ? STR_MAPGEN_BORDER_RANDOMIZE : STR_MAPGEN_BORDER_MANUAL); break; case GLAND_WATER_NE: SetDParam(0, (_settings_newgame.game_creation.water_borders == BORDERS_RANDOM) ? STR_MAPGEN_BORDER_RANDOM : HasBit(_settings_newgame.game_creation.water_borders, BORDER_NE) ? STR_MAPGEN_BORDER_WATER : STR_MAPGEN_BORDER_FREEFORM); break; case GLAND_WATER_NW: SetDParam(0, (_settings_newgame.game_creation.water_borders == BORDERS_RANDOM) ? STR_MAPGEN_BORDER_RANDOM : HasBit(_settings_newgame.game_creation.water_borders, BORDER_NW) ? STR_MAPGEN_BORDER_WATER : STR_MAPGEN_BORDER_FREEFORM); break; @@ -421,6 +428,7 @@ struct GenerateLandscapeWindow : public QueryStringBaseWindow { case GLAND_TERRAIN_PULLDOWN: strs = _elevations; break; case GLAND_WATER_PULLDOWN: strs = _sea_lakes; break; case GLAND_SMOOTHNESS_PULLDOWN: strs = _smoothness; break; + case GLAND_VARIETY_PULLDOWN: strs = _variety; break; case GLAND_HEIGHTMAP_ROTATION_PULLDOWN: strs = _rotation; break; case GLAND_BORDERS_RANDOM: *size = maxdim(GetStringBoundingBox(STR_MAPGEN_BORDER_RANDOMIZE), GetStringBoundingBox(STR_MAPGEN_BORDER_MANUAL)); @@ -469,6 +477,7 @@ struct GenerateLandscapeWindow : public QueryStringBaseWindow { /* You can't select smoothness / non-water borders if not terragenesis */ if (mode == GLWP_GENERATE) { this->SetWidgetDisabledState(GLAND_SMOOTHNESS_PULLDOWN, _settings_newgame.game_creation.land_generator == 0); + this->SetWidgetDisabledState(GLAND_VARIETY_PULLDOWN, _settings_newgame.game_creation.land_generator == 0); this->SetWidgetDisabledState(GLAND_BORDERS_RANDOM, _settings_newgame.game_creation.land_generator == 0 || !_settings_newgame.construction.freeform_edges); this->SetWidgetsDisabledState(_settings_newgame.game_creation.land_generator == 0 || !_settings_newgame.construction.freeform_edges || _settings_newgame.game_creation.water_borders == BORDERS_RANDOM, GLAND_WATER_NW, GLAND_WATER_NE, GLAND_WATER_SE, GLAND_WATER_SW, WIDGET_LIST_END); @@ -614,6 +623,10 @@ struct GenerateLandscapeWindow : public QueryStringBaseWindow { ShowDropDownMenu(this, _smoothness, _settings_newgame.game_creation.tgen_smoothness, GLAND_SMOOTHNESS_PULLDOWN, 0, 0); break; + case GLAND_VARIETY_PULLDOWN: // Map variety + ShowDropDownMenu(this, _variety, _settings_newgame.game_creation.variety, GLAND_VARIETY_PULLDOWN, 0, 0); + break; + /* Freetype map borders */ case GLAND_WATER_NW: _settings_newgame.game_creation.water_borders = ToggleBit(_settings_newgame.game_creation.water_borders, BORDER_NW); @@ -673,6 +686,7 @@ struct GenerateLandscapeWindow : public QueryStringBaseWindow { case GLAND_MAPSIZE_Y_PULLDOWN: _settings_newgame.game_creation.map_y = index; break; case GLAND_TREE_PULLDOWN: _settings_newgame.game_creation.tree_placer = index; break; case GLAND_SMOOTHNESS_PULLDOWN: _settings_newgame.game_creation.tgen_smoothness = index; break; + case GLAND_VARIETY_PULLDOWN: _settings_newgame.game_creation.variety = index; break; case GLAND_TOWN_PULLDOWN: if ((uint)index == CUSTOM_TOWN_NUMBER_DIFFICULTY) { diff --git a/src/lang/english.txt b/src/lang/english.txt index e21ff69a0b..1e10826255 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1008,6 +1008,9 @@ STR_NUM_NORMAL :Normal STR_NUM_HIGH :High STR_NUM_CUSTOM :Custom +STR_NUM_MEDIUM :Medium +STR_NUM_VERY_HIGH :Very High + STR_AI_SPEED_VERY_SLOW :Very Slow STR_AI_SPEED_SLOW :Slow STR_AI_SPEED_MEDIUM :Medium @@ -1032,6 +1035,7 @@ STR_TERRAIN_TYPE_VERY_FLAT :Very Flat STR_TERRAIN_TYPE_FLAT :Flat STR_TERRAIN_TYPE_HILLY :Hilly STR_TERRAIN_TYPE_MOUNTAINOUS :Mountainous +STR_TERRAIN_TYPE_MIXED :Mixed STR_ECONOMY_STEADY :Steady STR_ECONOMY_FLUCTUATING :Fluctuating @@ -2231,6 +2235,7 @@ STR_MAPGEN_TREE_PLACER :{BLACK}Tree alg STR_MAPGEN_TERRAIN_TYPE :{BLACK}Terrain type: STR_MAPGEN_QUANTITY_OF_SEA_LAKES :{BLACK}Sea level: STR_MAPGEN_SMOOTHNESS :{BLACK}Smoothness: +STR_MAPGEN_VARIETY :{BLACK}Variety distribution: STR_MAPGEN_GENERATE :{WHITE}Generate # Strings for map borders at game generation diff --git a/src/settings_type.h b/src/settings_type.h index 7547a87f82..801ca05f5c 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -169,6 +169,7 @@ struct GameCreationSettings { byte snow_line; ///< the snowline level in this game byte water_borders; ///< bitset of the borders that are water uint16 custom_town_number; ///< manually entered number of towns + byte variety; ///< variety level applied to TGP }; /** Settings related to construction in-game */ diff --git a/src/table/settings.h b/src/table/settings.h index 62213dc10e..b006b0294e 100644 --- a/src/table/settings.h +++ b/src/table/settings.h @@ -513,6 +513,7 @@ const SettingDesc _settings[] = { SDT_CONDVAR(GameSettings, game_creation.land_generator, SLE_UINT8, 30, SL_MAX_VERSION, 0,MS, 1, 0, 1, 0, STR_CONFIG_SETTING_LAND_GENERATOR, NULL), SDT_CONDVAR(GameSettings, game_creation.oil_refinery_limit, SLE_UINT8, 30, SL_MAX_VERSION, 0, 0, 32, 12, 48, 0, STR_CONFIG_SETTING_OIL_REF_EDGE_DISTANCE, NULL), SDT_CONDVAR(GameSettings, game_creation.tgen_smoothness, SLE_UINT8, 30, SL_MAX_VERSION, 0,MS, 1, 0, 3, 0, STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN, NULL), + SDT_VAR(GameSettings, game_creation.variety, SLE_UINT8, S, 0, 0, 0, 5, 0, STR_NULL, NULL), SDT_CONDVAR(GameSettings, game_creation.generation_seed, SLE_UINT32, 30, SL_MAX_VERSION, 0, 0, GENERATE_NEW_SEED, 0, UINT32_MAX, 0, STR_NULL, NULL), SDT_CONDVAR(GameSettings, game_creation.tree_placer, SLE_UINT8, 30, SL_MAX_VERSION, 0,MS, 2, 0, 2, 0, STR_CONFIG_SETTING_TREE_PLACER, NULL), SDT_VAR(GameSettings, game_creation.heightmap_rotation, SLE_UINT8, S,MS, 0, 0, 1, 0, STR_CONFIG_SETTING_HEIGHTMAP_ROTATION, NULL), diff --git a/src/tgp.cpp b/src/tgp.cpp index ba223938f7..99ef12bb95 100644 --- a/src/tgp.cpp +++ b/src/tgp.cpp @@ -510,6 +510,141 @@ static void HeightMapSineTransform(height_t h_min, height_t h_max) } } +/* Additional map variety is provided by applying different curve maps + * to different parts of the map. A randomized low resolution grid contains + * which curve map to use on each part of the make. This filtered non-linearly + * to smooth out transitions between curves, so each tile could have between + * 100% of one map applied or 25% of four maps. + * + * The curve maps define different land styles, i.e. lakes, low-lands, hills + * and mountain ranges, although these are dependent on the landscape style + * chosen as well. + * + * The level parameter dictates the resolution of the grid. A low resolution + * grid will result in larger continuous areas of a land style, a higher + * resolution grid splits the style into smaller areas. + * + * At this point in map generation, all height data has been normalized to 0 + * to 239. + */ +struct control_point_t { + height_t x; + height_t y; +}; + +struct control_point_list_t { + size_t length; + const control_point_t *list; +}; + +static const control_point_t _curve_map_1[] = { + { 0, 0 }, { 48, 24 }, { 192, 32 }, { 240, 96 } +}; + +static const control_point_t _curve_map_2[] = { + { 0, 0 }, { 16, 24 }, { 128, 32 }, { 192, 64 }, { 240, 144 } +}; + +static const control_point_t _curve_map_3[] = { + { 0, 0 }, { 16, 24 }, { 128, 64 }, { 192, 144 }, { 240, 192 } +}; + +static const control_point_t _curve_map_4[] = { + { 0, 0 }, { 16, 24 }, { 96, 72 }, { 160, 192 }, { 220, 239 }, { 240, 239 } +}; + +static const control_point_list_t _curve_maps[] = { + { lengthof(_curve_map_1), _curve_map_1 }, + { lengthof(_curve_map_2), _curve_map_2 }, + { lengthof(_curve_map_3), _curve_map_3 }, + { lengthof(_curve_map_4), _curve_map_4 }, +}; + +static void HeightMapCurves(uint level) +{ + height_t ht[lengthof(_curve_maps)]; + + /* Set up a grid to choose curve maps based on location */ + uint sx = Clamp(1 << level, 2, 32); + uint sy = Clamp(1 << level, 2, 32); + byte *c = (byte *)alloca(sx * sy); + + for (uint i = 0; i < sx * sy; i++) { + c[i] = Random() % lengthof(_curve_maps); + } + + /* Apply curves */ + for (uint x = 0; x < _height_map.size_x; x++) { + + /* Get our X grid positions and bi-linear ratio */ + float fx = (float)(sx * x) / _height_map.size_x + 0.5f; + uint x1 = fx; + uint x2 = x1; + float xr = 2.0f * (fx - x1) - 1.0f; + xr = sin(xr * M_PI_2); + xr = sin(xr * M_PI_2); + xr = 0.5f * (xr + 1.0f); + float xri = 1.0f - xr; + + if (x1 > 0) { + x1--; + if (x2 >= sx) x2--; + } + + for (uint y = 0; y < _height_map.size_y; y++) { + + /* Get our Y grid position and bi-linear ratio */ + float fy = (float)(sy * y) / _height_map.size_y + 0.5f; + uint y1 = fy; + uint y2 = y1; + float yr = 2.0f * (fy - y1) - 1.0f; + yr = sin(yr * M_PI_2); + yr = sin(yr * M_PI_2); + yr = 0.5f * (yr + 1.0f); + float yri = 1.0f - yr; + + if (y1 > 0) { + y1--; + if (y2 >= sy) y2--; + } + + uint corner_a = c[x1 + sx * y1]; + uint corner_b = c[x1 + sx * y2]; + uint corner_c = c[x2 + sx * y1]; + uint corner_d = c[x2 + sx * y2]; + + /* Bitmask of which curve maps are chosen, so that we do not bother + * calculating a curve which won't be used. */ + uint corner_bits = 0; + corner_bits |= 1 << corner_a; + corner_bits |= 1 << corner_b; + corner_bits |= 1 << corner_c; + corner_bits |= 1 << corner_d; + + height_t *h = &_height_map.height(x, y); + + /* Apply all curve maps that are used on this tile. */ + for (uint t = 0; t < lengthof(_curve_maps); t++) { + if (!HasBit(corner_bits, t)) continue; + + const control_point_t *cm = _curve_maps[t].list; + for (uint i = 0; i < _curve_maps[t].length - 1; i++) { + const control_point_t &p1 = cm[i]; + const control_point_t &p2 = cm[i + 1]; + + if (*h >= p1.x && *h < p2.x) { + ht[t] = p1.y + (*h - p1.x) * (p2.y - p1.y) / (p2.x - p1.x); + break; + } + } + } + + /* Apply interpolation of curve map results. */ + *h = (ht[corner_a] * yri + ht[corner_b] * yr) * xri + (ht[corner_c] * yri + ht[corner_d] * yr) * xr; + } + } +} + /** Adjusts heights in height map to contain required amount of water tiles */ static void HeightMapAdjustWaterLevel(amplitude_t water_percent, height_t h_max_new) { @@ -728,6 +863,11 @@ static void HeightMapNormalize() HeightMapSmoothSlopes(roughness); HeightMapSineTransform(12, h_max_new); + + if (_settings_game.game_creation.variety > 0) { + HeightMapCurves(_settings_game.game_creation.variety); + } + HeightMapSmoothSlopes(16); }