From d65456a1c07e06d7e3ce342d1bdff8a2461de993 Mon Sep 17 00:00:00 2001 From: Alina Lenk Date: Sun, 19 May 2024 15:24:02 +0200 Subject: [PATCH 6/8] 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 | 151 +++++++++++++++++++++++---- 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, 176 insertions(+), 22 deletions(-) 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 0d1553995f..163168e6cf 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; @@ -1692,6 +1710,28 @@ bool req_implies_req(const struct requirement *req1, return are_requirements_contradictions(req1, &nreq2); } +/**********************************************************************//** + Returns TRUE iff two bounds that could each be either an upper or lower + bound are contradicting each other. This function assumes that of the + upper and lower bounds, one will be inclusive, the other exclusive. +**************************************************************************/ +static inline bool are_bounds_contradictions(int bound1, bool is_upper1, + int bound2, bool is_upper2) +{ + /* If the bounds are on opposite sides, and one is inclusive, the other + * exclusive, the number of values that satisfy both bounds is exactly + * their difference, (upper bound) - (lower bound). + * The bounds contradict each other iff this difference is 0 or less, + * i.e. iff (upper bound) <= (lower bound) */ + if (is_upper1 && !is_upper2) { + return bound1 <= bound2; + } else if (!is_upper1 && is_upper2) { + return bound1 >= bound2; + } + /* Both are upper or both are lower ~> no contradiction possible */ + return FALSE; +} + /**********************************************************************//** Returns TRUE if req1 and req2 contradicts each other. @@ -1771,21 +1811,10 @@ bool are_requirements_contradictions(const struct requirement *req1, /* Finding contradictions across requirement kinds aren't supported * for MinMoveFrags requirements. */ return FALSE; - } else if (req1->present == req2->present) { - /* No contradiction possible. */ - return FALSE; - } else { - /* Number of move fragments left can't be larger than the number - * required to be present and smaller than the number required to not - * be present when the number required to be present is smaller than - * the number required to not be present. */ - if (req1->present) { - return req1->source.value.minmoves >= req2->source.value.minmoves; - } else { - return req1->source.value.minmoves <= req2->source.value.minmoves; - } } - break; + return are_bounds_contradictions( + req1->source.value.minmoves, !req1->present, + req2->source.value.minmoves, !req2->present); case VUT_MINLATITUDE: case VUT_MAXLATITUDE: if (req2->source.kind != VUT_MINLATITUDE @@ -1872,16 +1901,22 @@ bool are_requirements_contradictions(const struct requirement *req1, /* Finding contradictions across requirement kinds isn't supported * for MaxDistanceSq requirements. */ return FALSE; - } else if (req1->present == req2->present) { - /* Both bounds are on the same side, can't contradict */ + } + 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; + } else if (req1->range != req2->range) { + /* Counting completely separate things */ return FALSE; } - /* Different bounds ~> contradiction possible. - * Need (negated req value) < (actual value) <= (present req value) - * to be satisfied; impossible iff (present) <= (negated). */ - return req1->present - ? (req1->source.value.distance_sq <= req2->source.value.distance_sq) - : (req1->source.value.distance_sq >= req2->source.value.distance_sq); + 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. */ @@ -5597,6 +5632,66 @@ 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 known_tiles; + bool known_accurate; + + 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); + + if (cont > 0) { + known_tiles = nmap->continent_sizes[cont]; + known_accurate = (is_server() + || (nmap->client.continent_unknown_adj_counts[cont] == 0)); + } else if (cont < 0) { + known_tiles = nmap->ocean_sizes[-cont]; + known_accurate = (is_server() + || (nmap->client.ocean_unknown_adj_counts[-cont] == 0)); + } else { + /* Even if we don't know the continent, we know it has at least one + * tile (the target tile) */ + known_tiles = 1; + known_accurate = FALSE; + } + } + break; + default: + fc_assert_msg(FALSE, + "Illegal range %d for max tile number requirement.", + req->range); + return TRI_MAYBE; + } + + if (known_tiles > req->source.value.region_tiles) { + /* We already know about more tiles than the limit */ + return TRI_NO; + } else if (known_accurate) { + /* We know there aren't any more tiles that could break the limit */ + 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 @@ -5726,6 +5821,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}, @@ -6337,6 +6433,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. */ @@ -6998,6 +7095,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; } @@ -7159,6 +7258,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; @@ -7521,6 +7624,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 b1337b6b38..1fa1f8901c 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 bdc141845b..94678f2e58 100644 --- a/gen_headers/enums/fc_types_enums.def +++ b/gen_headers/enums/fc_types_enums.def @@ -99,4 +99,5 @@ values FORM_AGE "FormAge" MINCITIES "MinCities" MAX_DISTANCE_SQ "MaxDistanceSq" + MAX_REGION_TILES "MaxRegionTiles" end diff --git a/server/cityturn.c b/server/cityturn.c index 60934a0747..8653a0ac11 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 other ranges. */ + 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 5ca9b6a01d..f57655cdff 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