From c2857a78b6f89f8352ea50cfd20ff8e8cfaaa298 Mon Sep 17 00:00:00 2001 From: Alina Lenk Date: Sun, 19 May 2024 23:22:21 +0200 Subject: [PATCH 7/9] Support MaxRegionTiles at C/Adjacent ranges See RM #652 Signed-off-by: Alina Lenk --- common/reqtext.c | 64 ++++++++++++++++++++++++++++++- common/requirements.c | 80 +++++++++++++++++++++++++++++---------- doc/README.effects | 6 ++- server/cityturn.c | 2 +- server/ruleset/rssanity.c | 2 +- 5 files changed, 128 insertions(+), 26 deletions(-) diff --git a/common/reqtext.c b/common/reqtext.c index d2f50bf5e7..eccf61734a 100644 --- a/common/reqtext.c +++ b/common/reqtext.c @@ -3355,6 +3355,68 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer, case VUT_MAX_REGION_TILES: switch (preq->range) { + case REQ_RANGE_CADJACENT: + fc_strlcat(buf, prefix, bufsz); + /* Off-by-one: The requirement counts the tile itself, we're phrasing + * the helptext in terms of *other* tiles only. */ + if (preq->present) { + if (preq->source.value.region_tiles == 1) { + /* Special case for zero */ + fc_strlcat(buf, + _("No other cardinally adjacent tile may be part of " + "the same continent or ocean."), + bufsz); + } else { + cat_snprintf(buf, bufsz, + PL_("No more than %d other cardinally adjacent tile " + "may be part of the same continent or ocean.", + "No more than %d other cardinally adjacent tiles " + "may be part of the same continent or ocean.", + preq->source.value.region_tiles - 1), + preq->source.value.region_tiles - 1); + } + } else { + cat_snprintf(buf, bufsz, + PL_("Requires at least %d other cardinally adjacent " + "tile of the same continent or ocean.", + "Requires at least %d other cardinally adjacent " + "tiles of the same continent or ocean.", + preq->source.value.region_tiles), + preq->source.value.region_tiles); + } + + return TRUE; + case REQ_RANGE_ADJACENT: + fc_strlcat(buf, prefix, bufsz); + /* Off-by-one: The requirement counts the tile itself, we're phrasing + * the helptext in terms of *other* tiles only. */ + if (preq->present) { + if (preq->source.value.region_tiles == 1) { + /* Special case for zero */ + fc_strlcat(buf, + _("No other adjacent tile may be part of the same " + "continent or ocean."), + bufsz); + } else { + cat_snprintf(buf, bufsz, + PL_("No more than %d other adjacent tile may be " + "part of the same continent or ocean.", + "No more than %d other adjacent tiles may be " + "part of the same continent or ocean.", + preq->source.value.region_tiles - 1), + preq->source.value.region_tiles - 1); + } + } else { + cat_snprintf(buf, bufsz, + PL_("Requires at least %d other adjacent tile of the " + "same continent or ocean.", + "Requires at least %d other adjacent tiles of the " + "same continent or ocean.", + preq->source.value.region_tiles), + preq->source.value.region_tiles); + } + + return TRUE; case REQ_RANGE_CONTINENT: fc_strlcat(buf, prefix, bufsz); if (preq->present) { @@ -3374,8 +3436,6 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer, 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: diff --git a/common/requirements.c b/common/requirements.c index 163168e6cf..129d752490 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -1342,7 +1342,9 @@ struct requirement req_from_str(const char *type, const char *range, invalid = (req.range != REQ_RANGE_PLAYER); break; case VUT_MAX_REGION_TILES: - invalid = (req.range != REQ_RANGE_CONTINENT); + invalid = (req.range != REQ_RANGE_CONTINENT + && req.range != REQ_RANGE_CADJACENT + && req.range != REQ_RANGE_ADJACENT); break; case VUT_IMPROVEMENT: /* Valid ranges depend on the building genus (wonder/improvement), @@ -1911,7 +1913,9 @@ bool are_requirements_contradictions(const struct requirement *req1, * for MaxRegionTiles requirements. */ return FALSE; } else if (req1->range != req2->range) { - /* Counting completely separate things */ + /* FIXME: Finding contradictions across ranges not yet supported. + * In particular, a max at a small range and a min at a larger range + * needs extra work to figure out. */ return FALSE; } return are_bounds_contradictions( @@ -5646,12 +5650,43 @@ is_max_region_tiles_req_active(const struct civ_map *nmap, const struct req_context *other_context, const struct requirement *req) { - int known_tiles; - bool known_accurate; + int max_tiles, min_tiles = 1; IS_REQ_ACTIVE_VARIANT_ASSERT(VUT_MAX_REGION_TILES); switch (req->range) { + case REQ_RANGE_CADJACENT: + case REQ_RANGE_ADJACENT: + /* The tile itself is included in the range */ + max_tiles = 1 + ((req->range == REQ_RANGE_CADJACENT) + ? nmap->num_cardinal_dirs + : nmap->num_valid_dirs); + + if (context->tile == nullptr || tile_continent(context->tile) == 0) { + /* Note: If we know the tile's position, we could come up with a + * better upper bound by checking how many [cardinally] adjacent + * tiles share the same continent/ocean among each other. + * Probably not worth the effort though. */ + break; + } else { + Continent_id cont = tile_continent(context->tile); + const enum direction8 *dirlist = ((req->range == REQ_RANGE_CADJACENT) + ? nmap->cardinal_dirs + : nmap->valid_dirs); + int dircount = max_tiles - 1; + + adjc_dirlist_iterate(nmap, context->tile, adj_tile, _dir, + dirlist, dircount) { + Continent_id adj_cont = tile_continent(adj_tile); + + if (adj_cont == cont) { + min_tiles++; + } else if (adj_cont != 0) { + max_tiles--; + } + } adjc_dirlist_iterate_end; + } + break; case REQ_RANGE_CONTINENT: { Continent_id cont = context->tile ? tile_continent(context->tile) : 0; @@ -5659,34 +5694,39 @@ is_max_region_tiles_req_active(const struct civ_map *nmap, 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) { - known_tiles = nmap->continent_sizes[cont]; - known_accurate = (is_server() - || (nmap->client.continent_unknown_adj_counts[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) { - 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; + 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 tile number requirement.", + "Illegal range %d for max region tiles requirement.", req->range); return TRI_MAYBE; } - if (known_tiles > req->source.value.region_tiles) { - /* We already know about more tiles than the limit */ + if (min_tiles > req->source.value.region_tiles) { return TRI_NO; - } else if (known_accurate) { - /* We know there aren't any more tiles that could break the limit */ + } else if (max_tiles <= req->source.value.region_tiles) { return TRI_YES; } return TRI_MAYBE; diff --git a/doc/README.effects b/doc/README.effects index 1fa1f8901c..8ee0056d5a 100644 --- a/doc/README.effects +++ b/doc/README.effects @@ -119,7 +119,7 @@ MinMoveFrags: Local MinVeteran: Local MinHitPoints: Local MaxDistanceSq: Tile -MaxRegionTiles: Continent +MaxRegionTiles: Continent, Adjacent, CAdjacent MinSize is the minimum size of a city required. @@ -135,7 +135,9 @@ 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). +MaxRegionTiles is about the number of tiles of the same continent or ocean as + the target tile in the given range (including the tile itself). At "Continent" + range this is just the full continent/ocean size. CityStatus is "OwnedByOriginal", "Transferred", "Starved", "Disorder", or "Celebration" The difference between "OwnedByOriginal" and "Transferred" is that diff --git a/server/cityturn.c b/server/cityturn.c index 8653a0ac11..6be16364f7 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -1874,7 +1874,7 @@ static bool worklist_item_postpone_req_vec(struct universal *target, break; case VUT_MAX_REGION_TILES: /* Changing the continent size is hard; cf. VUT_TERRAINCLASS above. - * Change this when we support other ranges. */ + * Change this when we support less fixed ranges (e.g. city?). */ purge = TRUE; break; case VUT_ROADFLAG: diff --git a/server/ruleset/rssanity.c b/server/ruleset/rssanity.c index f57655cdff..e28056da49 100644 --- a/server/ruleset/rssanity.c +++ b/server/ruleset/rssanity.c @@ -355,7 +355,6 @@ 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 @@ -456,6 +455,7 @@ static bool sanity_check_req_set(rs_conversion_logger logger, /* Can have multiple requirements of these types */ case VUT_MINLATITUDE: case VUT_MAXLATITUDE: + case VUT_MAX_REGION_TILES: /* Can have multiple requirements at different ranges. * TODO: Compare to number of legal ranges? */ break; -- 2.34.1