From 277e47530e7ff37ba812845781deb0c42e06853f Mon Sep 17 00:00:00 2001 From: Alina Lenk Date: Thu, 30 May 2024 23:43:51 +0200 Subject: [PATCH 06/10] Add TileRel requirement type See RM #614 Signed-off-by: Alina Lenk --- ai/default/daieffects.c | 1 + ai/default/daimilitary.c | 1 + common/fc_types.h | 1 + common/metaknowledge.c | 39 ++++++ common/reqtext.c | 98 ++++++++++++++ common/requirements.c | 187 +++++++++++++++++++++++++++ doc/README.effects | 7 + gen_headers/enums/fc_types_enums.def | 12 ++ server/cityturn.c | 1 + server/ruleset/rssanity.c | 1 + tools/ruledit/univ_value.c | 8 ++ 11 files changed, 356 insertions(+) diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c index 2483cc0548..bc91e7b406 100644 --- a/ai/default/daieffects.c +++ b/ai/default/daieffects.c @@ -956,6 +956,7 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, case VUT_MINCALFRAG: case VUT_MAX_DISTANCE_SQ: case VUT_MAX_REGION_TILES: + case VUT_TILE_REL: case VUT_COUNT: /* No sensible implementation possible with data available. */ break; diff --git a/ai/default/daimilitary.c b/ai/default/daimilitary.c index 4f7e1fb0a9..e9dd1443e1 100644 --- a/ai/default/daimilitary.c +++ b/ai/default/daimilitary.c @@ -461,6 +461,7 @@ tactical_req_cb(const struct req_context *context, case VUT_TERRAINALTER: case VUT_MAX_DISTANCE_SQ: case VUT_MAX_REGION_TILES: + case VUT_TILE_REL: 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 daf7f8163f..f9c6798bf4 100644 --- a/common/fc_types.h +++ b/common/fc_types.h @@ -709,6 +709,7 @@ typedef union { enum citytile_type citytile; enum citystatus_type citystatus; enum plrstate_type plrstate; + enum tilerel_type tilerel; int minsize; int minculture; int minforeignpct; diff --git a/common/metaknowledge.c b/common/metaknowledge.c index 38ff4bb7b9..73765112da 100644 --- a/common/metaknowledge.c +++ b/common/metaknowledge.c @@ -749,6 +749,45 @@ static bool is_req_knowable(const struct player *pov_player, } } + if (req->source.kind == VUT_TILE_REL) { + if (context->tile == NULL || other_context->tile == NULL) { + /* The tile may exist but not be passed when the problem type is + * RPT_POSSIBLE. */ + return prob_type == RPT_CERTAIN; + } + if (tile_get_known(other_context->tile, pov_player) == TILE_UNKNOWN) { + return FALSE; + } + + switch (req->range) { + case REQ_RANGE_TILE: + case REQ_RANGE_CADJACENT: + case REQ_RANGE_ADJACENT: + /* TODO: Known tiles might be enough to determine the answer already; + * should we check on an individual requirement basis? */ + if (tile_get_known(context->tile, pov_player) == TILE_UNKNOWN) { + return FALSE; + } + range_adjc_iterate(&(wld.map), context->tile, req->range, adj_tile) { + if (tile_get_known(adj_tile, pov_player) == TILE_UNKNOWN) { + return FALSE; + } + } range_adjc_iterate_end; + return TRUE; + case REQ_RANGE_CITY: + case REQ_RANGE_TRADE_ROUTE: + case REQ_RANGE_CONTINENT: + case REQ_RANGE_PLAYER: + case REQ_RANGE_ALLIANCE: + case REQ_RANGE_TEAM: + case REQ_RANGE_WORLD: + case REQ_RANGE_LOCAL: + case REQ_RANGE_COUNT: + /* Non existing range for requirement types. */ + return FALSE; + } + } + if (req->source.kind == VUT_ACTION || req->source.kind == VUT_OTYPE) { /* This requirement type is intended to specify the situation. */ diff --git a/common/reqtext.c b/common/reqtext.c index eccf61734a..b2cd4b2a48 100644 --- a/common/reqtext.c +++ b/common/reqtext.c @@ -3444,6 +3444,104 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer, } break; + case VUT_TILE_REL: + switch (preq->source.value.tilerel) { + case TREL_SAME_REGION: + switch (preq->range) { + case REQ_RANGE_TILE: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + fc_strlcat(buf, _("Must be on the same continent or ocean."), + bufsz); + } else { + fc_strlcat(buf, _("Must be on a different continent or ocean."), + bufsz); + } + return TRUE; + case REQ_RANGE_CADJACENT: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + fc_strlcat(buf, _("Must be cardinally adjacent to the same " + "continent or ocean."), + bufsz); + } else { + fc_strlcat(buf, _("Must not be cardinally adjacent to the same " + "continent or ocean."), + bufsz); + } + return TRUE; + case REQ_RANGE_ADJACENT: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + fc_strlcat(buf, _("Must be adjacent to the same continent or " + "ocean."), + bufsz); + } else { + fc_strlcat(buf, _("Must not be adjacent to the same continent " + "or ocean."), + bufsz); + } + return TRUE; + case REQ_RANGE_CITY: + case REQ_RANGE_TRADE_ROUTE: + case REQ_RANGE_CONTINENT: + case REQ_RANGE_PLAYER: + case REQ_RANGE_TEAM: + case REQ_RANGE_ALLIANCE: + case REQ_RANGE_WORLD: + case REQ_RANGE_LOCAL: + case REQ_RANGE_COUNT: + /* Not supported. */ + break; + } + break; + case TREL_ONLY_OTHER_REGION: + switch (preq->range) { + case REQ_RANGE_CADJACENT: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + fc_strlcat(buf, _("May only be cardinally adjacent to this " + "other continent or ocean."), + bufsz); + } else { + fc_strlcat(buf, _("Must be cardinally adjacent to more than " + "just this other continent or ocean."), + bufsz); + } + return TRUE; + case REQ_RANGE_ADJACENT: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + fc_strlcat(buf, _("May only be adjacent to this other continent " + "or ocean."), + bufsz); + } else { + fc_strlcat(buf, _("Must be adjacent to more than just this " + "other continent or ocean."), + bufsz); + } + return TRUE; + case REQ_RANGE_CITY: + case REQ_RANGE_TRADE_ROUTE: + case REQ_RANGE_CONTINENT: + 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_COUNT: + /* Not supported. */ + break; + } + break; + + case TREL_COUNT: + /* Invalid. */ + break; + } + break; + case VUT_COUNT: break; } diff --git a/common/requirements.c b/common/requirements.c index 9771104f94..723b427af9 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -623,6 +623,12 @@ void universal_value_from_str(struct universal *source, const char *value) return; } break; + case VUT_TILE_REL: + source->value.tilerel = tilerel_type_by_name(value, fc_strcasecmp); + if (source->value.tilerel != TREL_COUNT) { + return; + } + break; case VUT_COUNT: break; } @@ -861,6 +867,9 @@ struct universal universal_by_number(const enum universals_n kind, case VUT_MAX_REGION_TILES: source.value.region_tiles = value; return source; + case VUT_TILE_REL: + source.value.tilerel = value; + return source; case VUT_COUNT: break; } @@ -1016,6 +1025,8 @@ int universal_number(const struct universal *source) return source->value.distance_sq; case VUT_MAX_REGION_TILES: return source->value.region_tiles; + case VUT_TILE_REL: + return source->value.tilerel; case VUT_COUNT: break; } @@ -1168,6 +1179,13 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_MAX_REGION_TILES: req.range = REQ_RANGE_CONTINENT; break; + case VUT_TILE_REL: + req.range = REQ_RANGE_TILE; + if (req.source.value.tilerel == TREL_ONLY_OTHER_REGION) { + /* Not available at Tile range */ + req.range = REQ_RANGE_ADJACENT; + } + break; } } @@ -1346,6 +1364,14 @@ struct requirement req_from_str(const char *type, const char *range, && req.range != REQ_RANGE_CADJACENT && req.range != REQ_RANGE_ADJACENT); break; + case VUT_TILE_REL: + invalid = (req.range != REQ_RANGE_ADJACENT + && req.range != REQ_RANGE_CADJACENT + && req.range != REQ_RANGE_TILE) + /* TREL_ONLY_OTHER_REGION not supported at Tile range */ + || (req.source.value.tilerel == TREL_ONLY_OTHER_REGION + && req.range == REQ_RANGE_TILE); + break; case VUT_IMPROVEMENT: /* Valid ranges depend on the building genus (wonder/improvement), * which might not have been loaded from the ruleset yet. @@ -1430,6 +1456,7 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_MAXLATITUDE: case VUT_MAX_DISTANCE_SQ: case VUT_MAX_REGION_TILES: + case VUT_TILE_REL: /* Most requirements don't support 'survives'. */ invalid = survives; break; @@ -1921,6 +1948,40 @@ bool are_requirements_contradictions(const struct requirement *req1, return are_bounds_contradictions( req1->source.value.region_tiles, req1->present, req2->source.value.region_tiles, req2->present); + case VUT_TILE_REL: + if (req2->source.kind != VUT_TILE_REL) { + /* Finding contradictions across requirement kinds isn't supported + * for TileRel requirements. */ + return FALSE; + } + if (req1->source.value.tilerel == req2->source.value.tilerel) { + /* Same requirement at different ranges. Note that same range is + * already covered by are_requirements_opposites() above. */ + switch (req1->source.value.tilerel) { + case TREL_SAME_REGION: + /* Negated req at larger range contradicts present req at + * smaller range. */ + if (req1->range > req2->range) { + return !req1->present && req2->present; + } else { + return req1->present && !req2->present; + } + break; + case TREL_ONLY_OTHER_REGION: + /* Present req at larger range contradicts negated req at + * smaller range */ + if (req1->range > req2->range) { + return req1->present && !req2->present; + } else { + return !req1->present && req2->present; + } + break; + default: + return FALSE; + } + } + /* No further contradictions we can detect */ + return FALSE; default: /* No special knowledge exists. The requirements aren't the exact * opposite of each other per the initial check. */ @@ -4754,6 +4815,112 @@ is_form_age_req_active(const struct civ_map *nmap, } } +/**********************************************************************//** + Determine whether a tile relationship 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 tile relationship requirement +**************************************************************************/ +static enum fc_tristate +is_tile_rel_req_active(const struct civ_map *nmap, + const struct req_context *context, + const struct req_context *other_context, + const struct requirement *req) +{ + IS_REQ_ACTIVE_VARIANT_ASSERT(VUT_TILE_REL); + + if (context->tile == nullptr || other_context->tile == nullptr) { + /* Note: For some values, we might be able to give a definitive + * TRI_NO answer even if one of the tiles is missing, but that's + * probably not worth the added effort. */ + return TRI_MAYBE; + } + + switch (req->source.value.tilerel) { + case TREL_SAME_REGION: + if (tile_continent(other_context->tile) == 0) { + return TRI_MAYBE; + } + fc_assert_ret_val_msg((req->range == REQ_RANGE_TILE + || req->range == REQ_RANGE_CADJACENT + || req->range == REQ_RANGE_ADJACENT), + TRI_MAYBE, + "Invalid range %d for tile relation \"%s\" req", + req->range, tilerel_type_name(TREL_SAME_REGION)); + + if (tile_continent(context->tile) + == tile_continent(other_context->tile)) { + return TRI_YES; + } else { + bool seen_unknown = (tile_continent(context->tile) == 0); + Continent_id cont = tile_continent(other_context->tile); + + range_adjc_iterate(nmap, context->tile, req->range, adj_tile) { + Continent_id adj_cont = tile_continent(adj_tile); + + if (adj_cont == cont) { + return TRI_YES; + } else if (adj_cont == 0) { + seen_unknown = TRUE; + } + } range_adjc_iterate_end; + + if (seen_unknown) { + return TRI_MAYBE; + } else { + return TRI_NO; + } + } + break; + case TREL_ONLY_OTHER_REGION: + if (tile_continent(context->tile) == 0 + || tile_continent(other_context->tile) == 0) { + /* Note: We could still give a definitive TRI_NO answer if there are + * too many different adjacent continents, but that's probably not + * worth the added effort. */ + return TRI_MAYBE; + } + fc_assert_ret_val_msg((req->range == REQ_RANGE_CADJACENT + || req->range == REQ_RANGE_ADJACENT), + TRI_MAYBE, + "Invalid range %d for tile relation \"%s\" req", + req->range, + tilerel_type_name(TREL_ONLY_OTHER_REGION)); + + { + bool seen_unknown = FALSE; + Continent_id cont = tile_continent(context->tile); + Continent_id other_cont = tile_continent(other_context->tile); + + range_adjc_iterate(nmap, context->tile, req->range, adj_tile) { + Continent_id adj_cont = tile_continent(adj_tile); + + if (adj_cont == 0) { + seen_unknown = TRUE; + } else if (adj_cont != cont && adj_cont != other_cont) { + return TRI_NO; + } + } range_adjc_iterate_end; + + if (seen_unknown) { + return TRI_MAYBE; + } else { + return TRI_YES; + } + } + break; + default: + break; + } + + fc_assert_msg(FALSE, + "Illegal value %d for tile relationship requirement.", + req->source.value.tilerel); + return TRI_MAYBE; +} + /**********************************************************************//** Is center of given city in tile. If city is NULL, any city will do. **************************************************************************/ @@ -5891,6 +6058,7 @@ static struct req_def req_definitions[VUT_COUNT] = { [VUT_TERRAINALTER] = {is_terrainalter_req_active, REQUCH_NO}, [VUT_TERRAINCLASS] = {is_terrainclass_req_active, REQUCH_NO}, [VUT_TERRFLAG] = {is_terrainflag_req_active, REQUCH_NO}, + [VUT_TILE_REL] = {is_tile_rel_req_active, REQUCH_NO}, [VUT_TOPO] = {is_topology_req_active, REQUCH_YES}, [VUT_WRAP] = {is_wrap_req_active, REQUCH_YES}, [VUT_UCFLAG] = {is_unitclassflag_req_active, REQUCH_YES}, @@ -6435,6 +6603,7 @@ bool universal_never_there(const struct universal *source) case VUT_MINYEAR: case VUT_MAX_DISTANCE_SQ: case VUT_MAX_REGION_TILES: + case VUT_TILE_REL: case VUT_NONE: case VUT_COUNT: /* Not implemented. */ @@ -7091,6 +7260,8 @@ bool are_universals_equal(const struct universal *psource1, return psource1->value.citytile == psource2->value.citytile; case VUT_CITYSTATUS: return psource1->value.citystatus == psource2->value.citystatus; + case VUT_TILE_REL: + return psource1->value.tilerel == psource2->value.tilerel; case VUT_MINLATITUDE: case VUT_MAXLATITUDE: return psource1->value.latitude == psource2->value.latitude; @@ -7123,6 +7294,8 @@ const char *universal_rule_name(const struct universal *psource) return citytile_type_name(psource->value.citytile); case VUT_CITYSTATUS: return citystatus_type_name(psource->value.citystatus); + case VUT_TILE_REL: + return tilerel_type_name(psource->value.tilerel); case VUT_MINYEAR: fc_snprintf(buffer, sizeof(buffer), "%d", psource->value.minyear); @@ -7610,6 +7783,20 @@ const char *universal_name_translation(const struct universal *psource, break; } return buf; + case VUT_TILE_REL: + switch (psource->value.tilerel) { + case TREL_SAME_REGION: + fc_strlcat(buf, _("Same continent/ocean"), bufsz); + break; + case TREL_ONLY_OTHER_REGION: + fc_strlcat(buf, _("Only other continent/ocean"), bufsz); + break; + case TREL_COUNT: + fc_assert(psource->value.tilerel != TREL_COUNT); + fc_strlcat(buf, "error", bufsz); + break; + } + return buf; case VUT_MINLATITUDE: /* TRANS: here >= means 'greater than or equal'. */ cat_snprintf(buf, bufsz, _("Latitude >= %d"), diff --git a/doc/README.effects b/doc/README.effects index d3c44b4829..125e9dcd5f 100644 --- a/doc/README.effects +++ b/doc/README.effects @@ -118,6 +118,7 @@ Activity: Local MinMoveFrags: Local MinVeteran: Local MinHitPoints: Local +TileRel: Tile, Adjacent, CAdjacent MaxDistanceSq: Tile MaxRegionTiles: Continent, Adjacent, CAdjacent @@ -132,6 +133,12 @@ CityTile is "Center" (city center), "Claimed" (tile owned by any player), is a port, it's a tile of the nearby ocean but not of its continent). MinLatitude and MaxLatitude are numbers from -1000 (south pole) to 1000 (north pole). +TileRel is about the relation to a specific other tile; currently only available + for action enablers (see also README.actions) and a select few effects. It is + one of "Same Region" (on or adjacent to the same continent/ocean as the other + tile, depending on range) or "Only Other Region" (only adjacent to tiles of its + own or the other tile's continent/ocean, not a third one; not available at Tile + range). MaxDistanceSq is about the (squared) distance between two tiles; currently only available for action enablers (see also README.actions) and a select few effects. diff --git a/gen_headers/enums/fc_types_enums.def b/gen_headers/enums/fc_types_enums.def index 504454da61..ae5df4e26f 100644 --- a/gen_headers/enums/fc_types_enums.def +++ b/gen_headers/enums/fc_types_enums.def @@ -97,6 +97,7 @@ values /* More generic terrain type currently "Land" or "Ocean" */ TERRAINCLASS "TerrainClass" TERRFLAG "TerrainFlag" + TILE_REL "TileRel" TOPO "Topology" UCFLAG "UnitClassFlag" UCLASS "UnitClass" @@ -105,3 +106,14 @@ values UTYPE "UnitType" WRAP "Wrap" end + +/* TileRel requirement types. + * Used in the network protocol */ +enum tilerel_type + prefix TREL_ + count + style identifiers sorted +values + ONLY_OTHER_REGION "Only Other Region" + SAME_REGION "Same Region" +end diff --git a/server/cityturn.c b/server/cityturn.c index 6dbc3bdf90..669031e866 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -1975,6 +1975,7 @@ static bool worklist_item_postpone_req_vec(struct universal *target, case VUT_OTYPE: case VUT_SPECIALIST: case VUT_MAX_DISTANCE_SQ: + case VUT_TILE_REL: case VUT_TERRAINALTER: /* XXX could do this in principle */ /* Will only happen with a bogus ruleset. */ log_error("worklist_change_build_target() has bogus preq"); diff --git a/server/ruleset/rssanity.c b/server/ruleset/rssanity.c index 6d38330898..ace3cd9bb1 100644 --- a/server/ruleset/rssanity.c +++ b/server/ruleset/rssanity.c @@ -435,6 +435,7 @@ static bool sanity_check_req_set(rs_conversion_logger logger, case VUT_GOOD: case VUT_UTYPE: case VUT_UCLASS: + case VUT_TILE_REL: /* Can check different properties. */ case VUT_UTFLAG: case VUT_UCFLAG: diff --git a/tools/ruledit/univ_value.c b/tools/ruledit/univ_value.c index a8d6915ea6..c2d164765c 100644 --- a/tools/ruledit/univ_value.c +++ b/tools/ruledit/univ_value.c @@ -258,6 +258,9 @@ bool universal_value_initial(struct universal *src) case VUT_MAX_REGION_TILES: src->value.region_tiles = 0; return TRUE; + case VUT_TILE_REL: + src->value.tilerel = TREL_SAME_REGION; + return TRUE; case VUT_COUNT: fc_assert(src->kind != VUT_COUNT); return FALSE; @@ -407,6 +410,11 @@ void universal_kind_values(struct universal *univ, cb(citystatus_type_name(i), univ->value.citystatus == i, data); } break; + case VUT_TILE_REL: + for (i = 0; i < TREL_COUNT; i++) { + cb(tilerel_type_name(i), univ->value.tilerel == i, data); + } + break; case VUT_ACHIEVEMENT: achievements_re_active_iterate(pach) { cb(achievement_rule_name(pach), univ->value.achievement == pach, data); -- 2.34.1