From 8d66894257ba1e33dd649162768920d16ce8fc97 Mon Sep 17 00:00:00 2001 From: Alina Lenk Date: Thu, 30 May 2024 23:30:14 +0200 Subject: [PATCH 04/10] Add MaxRegionTiles (continent size) requirement type See RM #629 Signed-off-by: Alina Lenk --- ai/default/daieffects.c | 1 + ai/default/daimilitary.c | 1 + common/fc_types.h | 1 + common/reqtext.c | 31 ++++++++ common/requirements.c | 103 +++++++++++++++++++++++++++ doc/README.effects | 2 + gen_headers/enums/fc_types_enums.def | 1 + server/cityturn.c | 5 ++ server/ruleset/rssanity.c | 1 + tools/ruledit/univ_value.c | 4 ++ 10 files changed, 150 insertions(+) diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c index 7c7f086dcc..2483cc0548 100644 --- a/ai/default/daieffects.c +++ b/ai/default/daieffects.c @@ -955,6 +955,7 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, case VUT_GOOD: case VUT_MINCALFRAG: case VUT_MAX_DISTANCE_SQ: + case VUT_MAX_REGION_TILES: case VUT_COUNT: /* No sensible implementation possible with data available. */ break; diff --git a/ai/default/daimilitary.c b/ai/default/daimilitary.c index 5bf24ad888..4f7e1fb0a9 100644 --- a/ai/default/daimilitary.c +++ b/ai/default/daimilitary.c @@ -460,6 +460,7 @@ tactical_req_cb(const struct req_context *context, case VUT_TERRFLAG: case VUT_TERRAINALTER: case VUT_MAX_DISTANCE_SQ: + case VUT_MAX_REGION_TILES: case VUT_NONE: return tri_req_active(context, other_context, req); case VUT_COUNT: diff --git a/common/fc_types.h b/common/fc_types.h index 0f0f140fe6..daf7f8163f 100644 --- a/common/fc_types.h +++ b/common/fc_types.h @@ -740,6 +740,7 @@ typedef union { int min_cities; int latitude; int distance_sq; + int region_tiles; enum topo_flag topo_property; enum wrap_flag wrap_property; diff --git a/common/reqtext.c b/common/reqtext.c index 6bba4582ec..d2f50bf5e7 100644 --- a/common/reqtext.c +++ b/common/reqtext.c @@ -3353,6 +3353,37 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer, } break; + case VUT_MAX_REGION_TILES: + switch (preq->range) { + case REQ_RANGE_CONTINENT: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + cat_snprintf(buf, bufsz, + _("Requires a continent or ocean size of at most %d."), + preq->source.value.region_tiles); + } else { + cat_snprintf(buf, bufsz, + _("Requires a continent or ocean size of at least %d."), + preq->source.value.region_tiles + 1); + } + + return TRUE; + case REQ_RANGE_PLAYER: + case REQ_RANGE_TEAM: + case REQ_RANGE_ALLIANCE: + case REQ_RANGE_WORLD: + case REQ_RANGE_LOCAL: + case REQ_RANGE_TILE: + case REQ_RANGE_CADJACENT: + case REQ_RANGE_ADJACENT: + case REQ_RANGE_CITY: + case REQ_RANGE_TRADE_ROUTE: + case REQ_RANGE_COUNT: + /* Not supported. */ + break; + } + break; + case VUT_COUNT: break; } diff --git a/common/requirements.c b/common/requirements.c index bfece8311e..022cfe04bc 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -617,6 +617,12 @@ void universal_value_from_str(struct universal *source, const char *value) return; } break; + case VUT_MAX_REGION_TILES: + source->value.region_tiles = atoi(value); + if (0 < source->value.region_tiles) { + return; + } + break; case VUT_COUNT: break; } @@ -852,6 +858,9 @@ struct universal universal_by_number(const enum universals_n kind, case VUT_MAX_DISTANCE_SQ: source.value.distance_sq = value; return source; + case VUT_MAX_REGION_TILES: + source.value.region_tiles = value; + return source; case VUT_COUNT: break; } @@ -1005,6 +1014,8 @@ int universal_number(const struct universal *source) return source->value.latitude; case VUT_MAX_DISTANCE_SQ: return source->value.distance_sq; + case VUT_MAX_REGION_TILES: + return source->value.region_tiles; case VUT_COUNT: break; } @@ -1154,6 +1165,9 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_SERVERSETTING: req.range = REQ_RANGE_WORLD; break; + case VUT_MAX_REGION_TILES: + req.range = REQ_RANGE_CONTINENT; + break; } } @@ -1327,6 +1341,9 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_PLAYER_STATE: invalid = (req.range != REQ_RANGE_PLAYER); break; + case VUT_MAX_REGION_TILES: + invalid = (req.range != REQ_RANGE_CONTINENT); + break; case VUT_IMPROVEMENT: /* Valid ranges depend on the building genus (wonder/improvement), * which might not have been loaded from the ruleset yet. @@ -1410,6 +1427,7 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_MINLATITUDE: case VUT_MAXLATITUDE: case VUT_MAX_DISTANCE_SQ: + case VUT_MAX_REGION_TILES: /* Most requirements don't support 'survives'. */ invalid = survives; break; @@ -1887,6 +1905,15 @@ bool are_requirements_contradictions(const struct requirement *req1, return are_bounds_contradictions( req1->source.value.distance_sq, req1->present, req2->source.value.distance_sq, req2->present); + case VUT_MAX_REGION_TILES: + if (req2->source.kind != VUT_MAX_REGION_TILES) { + /* Finding contradictions across requirement kinds isn't supported + * for MaxRegionTiles requirements. */ + return FALSE; + } + return are_bounds_contradictions( + req1->source.value.region_tiles, req1->present, + req2->source.value.region_tiles, req2->present); default: /* No special knowledge exists. The requirements aren't the exact * opposite of each other per the initial check. */ @@ -5609,6 +5636,70 @@ is_max_distance_sq_req_active(const struct civ_map *nmap, return TRI_MAYBE; } +/**********************************************************************//** + Determine whether a maximum tiles of same region requirement is satisfied + in a given context, ignoring parts of the requirement that can be handled + uniformly for all requirement types. + + context, other_context and req must not be null, + and req must be a max region tiles requirement +**************************************************************************/ +static enum fc_tristate +is_max_region_tiles_req_active(const struct civ_map *nmap, + const struct req_context *context, + const struct req_context *other_context, + const struct requirement *req) +{ + int max_tiles, min_tiles = 1; + + IS_REQ_ACTIVE_VARIANT_ASSERT(VUT_MAX_REGION_TILES); + + switch (req->range) { + case REQ_RANGE_CONTINENT: + { + Continent_id cont = context->tile ? tile_continent(context->tile) : 0; + + fc_assert_ret_val(cont <= nmap->num_continents, TRI_MAYBE); + fc_assert_ret_val(-cont <= nmap->num_oceans, TRI_MAYBE); + + /* Note: We could come up with a better upper bound by subtracting + * all other continent/ocean sizes, or all except the largest if we + * don't know the tile. + * We could even do a flood-fill count of the unknown area bordered + * by known tiles of the continent. + * Probably not worth the effort though. */ + max_tiles = nmap->xsize * nmap->ysize; + + if (cont > 0) { + min_tiles = nmap->continent_sizes[cont]; + if (is_server() || (nmap->client.continent_unknown_adj_counts[cont] + == 0)) { + max_tiles = min_tiles; + } + } else if (cont < 0) { + min_tiles = nmap->ocean_sizes[-cont]; + if (is_server() || (nmap->client.ocean_unknown_adj_counts[-cont] + == 0)) { + max_tiles = min_tiles; + } + } + } + break; + default: + fc_assert_msg(FALSE, + "Illegal range %d for max region tiles requirement.", + req->range); + return TRI_MAYBE; + } + + if (min_tiles > req->source.value.region_tiles) { + return TRI_NO; + } else if (max_tiles <= req->source.value.region_tiles) { + return TRI_YES; + } + return TRI_MAYBE; +} + /**********************************************************************//** Determine whether a minimum year requirement is satisfied in a given context, ignoring parts of the requirement that can be handled uniformly @@ -5738,6 +5829,7 @@ static struct req_def req_definitions[VUT_COUNT] = { [VUT_PLAYER_FLAG] = {is_plr_flag_req_active, REQUCH_NO}, [VUT_PLAYER_STATE] = {is_plr_state_req_active, REQUCH_NO}, [VUT_MAX_DISTANCE_SQ] = {is_max_distance_sq_req_active, REQUCH_YES}, + [VUT_MAX_REGION_TILES] = {is_max_region_tiles_req_active, REQUCH_NO}, [VUT_MAXLATITUDE] = {is_latitude_req_active, REQUCH_YES}, [VUT_MAXTILEUNITS] = {is_maxunitsontile_req_active, REQUCH_NO}, [VUT_MINCALFRAG] = {is_mincalfrag_req_active, REQUCH_NO}, @@ -6308,6 +6400,7 @@ bool universal_never_there(const struct universal *source) case VUT_TERRAINALTER: case VUT_MINYEAR: case VUT_MAX_DISTANCE_SQ: + case VUT_MAX_REGION_TILES: case VUT_NONE: case VUT_COUNT: /* Not implemented. */ @@ -6969,6 +7062,8 @@ bool are_universals_equal(const struct universal *psource1, return psource1->value.latitude == psource2->value.latitude; case VUT_MAX_DISTANCE_SQ: return psource1->value.distance_sq == psource2->value.distance_sq; + case VUT_MAX_REGION_TILES: + return psource1->value.region_tiles == psource2->value.region_tiles; case VUT_COUNT: break; } @@ -7130,6 +7225,10 @@ const char *universal_rule_name(const struct universal *psource) case VUT_MAX_DISTANCE_SQ: fc_snprintf(buffer, sizeof(buffer), "%d", psource->value.distance_sq); + return buffer; + case VUT_MAX_REGION_TILES: + fc_snprintf(buffer, sizeof(buffer), "%d", psource->value.region_tiles); + return buffer; case VUT_COUNT: break; @@ -7492,6 +7591,10 @@ const char *universal_name_translation(const struct universal *psource, cat_snprintf(buf, bufsz, _("Squared distance <= %d"), psource->value.distance_sq); return buf; + case VUT_MAX_REGION_TILES: + cat_snprintf(buf, bufsz, _("%d or fewer region tiles"), + psource->value.region_tiles); + return buf; case VUT_COUNT: break; } diff --git a/doc/README.effects b/doc/README.effects index 6eee8dc151..870a9f71e2 100644 --- a/doc/README.effects +++ b/doc/README.effects @@ -119,6 +119,7 @@ MinMoveFrags: Local MinVeteran: Local MinHitPoints: Local MaxDistanceSq: Tile +MaxRegionTiles: Continent MinSize is the minimum size of a city required. @@ -134,6 +135,7 @@ MinLatitude and MaxLatitude are numbers from -1000 (south pole) to 1000 MaxDistanceSq is about the (squared) distance between two tiles; currently only available for action enablers (see also README.actions) and a select few effects. +MaxRegionTiles is continent or ocean size (number of tiles). CityStatus is "OwnedByOriginal", "Transferred", "Starved", "Disorder", or "Celebration" The difference between "OwnedByOriginal" and "Transferred" is that diff --git a/gen_headers/enums/fc_types_enums.def b/gen_headers/enums/fc_types_enums.def index 6cd13392e3..504454da61 100644 --- a/gen_headers/enums/fc_types_enums.def +++ b/gen_headers/enums/fc_types_enums.def @@ -66,6 +66,7 @@ values MAXLATITUDE "MaxLatitude" MAXTILEUNITS "MaxUnitsOnTile" MAX_DISTANCE_SQ "MaxDistanceSq" + MAX_REGION_TILES "MaxRegionTiles" MINCALFRAG "MinCalFrag" MINCITIES "MinCities" MINCULTURE "MinCulture" diff --git a/server/cityturn.c b/server/cityturn.c index 400f395397..6dbc3bdf90 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -1872,6 +1872,11 @@ static bool worklist_item_postpone_req_vec(struct universal *target, pcity, "have_terrainflag"); } break; + case VUT_MAX_REGION_TILES: + /* Changing the continent size is hard; cf. VUT_TERRAINCLASS above. + * Change this when we support less fixed ranges (e.g. city?). */ + purge = TRUE; + break; case VUT_ROADFLAG: if (preq->present) { notify_player(pplayer, city_tile(pcity), diff --git a/server/ruleset/rssanity.c b/server/ruleset/rssanity.c index 0697e9cc95..564bdb89fa 100644 --- a/server/ruleset/rssanity.c +++ b/server/ruleset/rssanity.c @@ -355,6 +355,7 @@ static bool sanity_check_req_set(rs_conversion_logger logger, case VUT_ORIGINAL_OWNER: /* City range -> only one original owner */ case VUT_FORM_AGE: case VUT_MAX_DISTANCE_SQ: /* Breaks nothing, but has no sense either */ + case VUT_MAX_REGION_TILES: /* Breaks nothing, but has no sense either */ /* There can be only one requirement of these types (with current * range limitations) * Requirements might be identical, but we consider multiple diff --git a/tools/ruledit/univ_value.c b/tools/ruledit/univ_value.c index 5a7923c363..a8d6915ea6 100644 --- a/tools/ruledit/univ_value.c +++ b/tools/ruledit/univ_value.c @@ -255,6 +255,9 @@ bool universal_value_initial(struct universal *src) case VUT_MAX_DISTANCE_SQ: src->value.distance_sq = 0; return TRUE; + case VUT_MAX_REGION_TILES: + src->value.region_tiles = 0; + return TRUE; case VUT_COUNT: fc_assert(src->kind != VUT_COUNT); return FALSE; @@ -508,6 +511,7 @@ void universal_kind_values(struct universal *univ, case VUT_MINLATITUDE: case VUT_MAXLATITUDE: case VUT_MAX_DISTANCE_SQ: + case VUT_MAX_REGION_TILES: /* Requirement types having numerical value */ cb(nullptr, FALSE, data); break; -- 2.34.1