From e1d6803b01bc276904cddf07645b1888039dacba Mon Sep 17 00:00:00 2001 From: Alina Lenk Date: Thu, 16 May 2024 00:28:25 +0200 Subject: [PATCH 7/7] Add MaxDistanceSq requirement (maximum squared distance) See RM #616 Signed-off-by: Alina Lenk --- ai/default/daieffects.c | 1 + ai/default/daimilitary.c | 1 + common/fc_types.h | 1 + common/reqtext.c | 61 ++++++++++++++++++++ common/requirements.c | 83 +++++++++++++++++++++++++++- doc/README.actions | 2 + doc/README.effects | 4 ++ gen_headers/enums/fc_types_enums.def | 1 + server/cityturn.c | 1 + server/ruleset/rssanity.c | 16 ++++++ tools/ruledit/univ_value.c | 4 ++ 11 files changed, 173 insertions(+), 2 deletions(-) diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c index 28b0ffe3eb..204abe11a2 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_ACTION: case VUT_GOOD: case VUT_MINCALFRAG: + case VUT_MAX_DISTANCE_SQ: case VUT_COUNT: /* No sensible implementation possible with data available. */ break; diff --git a/ai/default/daimilitary.c b/ai/default/daimilitary.c index ec9ecf1339..5bf24ad888 100644 --- a/ai/default/daimilitary.c +++ b/ai/default/daimilitary.c @@ -459,6 +459,7 @@ tactical_req_cb(const struct req_context *context, case VUT_TERRAINCLASS: case VUT_TERRFLAG: case VUT_TERRAINALTER: + case VUT_MAX_DISTANCE_SQ: 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 7a97b4087c..6f78baa95c 100644 --- a/common/fc_types.h +++ b/common/fc_types.h @@ -759,6 +759,7 @@ typedef union { int min_techs; int min_cities; int latitude; + int distance_sq; enum topo_flag topo_property; enum wrap_flag wrap_property; diff --git a/common/reqtext.c b/common/reqtext.c index be72a231f4..6bba4582ec 100644 --- a/common/reqtext.c +++ b/common/reqtext.c @@ -3292,6 +3292,67 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer, } break; + case VUT_MAX_DISTANCE_SQ: + switch (preq->range) { + case REQ_RANGE_TILE: + fc_strlcat(buf, prefix, bufsz); + /* Test some special cases */ + switch (preq->source.value.distance_sq) + { + case 0: + if (preq->present) { + fc_strlcat(buf, _("Must be the same tile."), bufsz); + } else { + fc_strlcat(buf, _("Must not be the same tile."), bufsz); + } + break; + case 1: + if (preq->present) { + fc_strlcat(buf, _("Must be cardinally adjacent."), bufsz); + } else { + fc_strlcat(buf, _("Must not be cardinally adjacent."), bufsz); + } + break; + case 2: + case 3: + if (preq->present) { + fc_strlcat(buf, _("Must be adjacent."), bufsz); + } else { + fc_strlcat(buf, _("Must not be adjacent."), bufsz); + } + break; + + default: + if (preq->present) { + cat_snprintf(buf, bufsz, + _("The squared distance between the tiles " + "must be at most %d."), + preq->source.value.distance_sq); + } else { + cat_snprintf(buf, bufsz, + _("The squared distance between the tiles " + "must be at least %d."), + preq->source.value.distance_sq + 1); + } + break; + } + return TRUE; + case REQ_RANGE_CADJACENT: + case REQ_RANGE_ADJACENT: + 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 VUT_COUNT: break; } diff --git a/common/requirements.c b/common/requirements.c index cbb37e1263..9fd8dc2f69 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -611,6 +611,12 @@ void universal_value_from_str(struct universal *source, const char *value) return; } break; + case VUT_MAX_DISTANCE_SQ: + source->value.distance_sq = atoi(value); + if (0 <= source->value.distance_sq) { + return; + } + break; case VUT_COUNT: break; } @@ -843,6 +849,9 @@ struct universal universal_by_number(const enum universals_n kind, case VUT_MAXLATITUDE: source.value.latitude = value; return source; + case VUT_MAX_DISTANCE_SQ: + source.value.distance_sq = value; + return source; case VUT_COUNT: break; } @@ -994,6 +1003,8 @@ int universal_number(const struct universal *source) case VUT_MINLATITUDE: case VUT_MAXLATITUDE: return source->value.latitude; + case VUT_MAX_DISTANCE_SQ: + return source->value.distance_sq; case VUT_COUNT: break; } @@ -1106,6 +1117,7 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_MAXTILEUNITS: case VUT_MINLATITUDE: case VUT_MAXLATITUDE: + case VUT_MAX_DISTANCE_SQ: req.range = REQ_RANGE_TILE; break; case VUT_COUNTER: @@ -1263,6 +1275,7 @@ struct requirement req_from_str(const char *type, const char *range, && req.range != REQ_RANGE_ADJACENT); break; case VUT_TERRAINALTER: /* XXX could in principle support C/ADJACENT */ + case VUT_MAX_DISTANCE_SQ: invalid = (req.range != REQ_RANGE_TILE); break; case VUT_CITYTILE: @@ -1396,6 +1409,7 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_MINCITIES: case VUT_MINLATITUDE: case VUT_MAXLATITUDE: + case VUT_MAX_DISTANCE_SQ: /* Most requirements don't support 'survives'. */ invalid = survives; break; @@ -1853,6 +1867,21 @@ bool are_requirements_contradictions(const struct requirement *req1, } return FALSE; + case VUT_MAX_DISTANCE_SQ: + if (req2->source.kind != VUT_MAX_DISTANCE_SQ) { + /* 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 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); default: /* No special knowledge exists. The requirements aren't the exact * opposite of each other per the initial check. */ @@ -1945,9 +1974,10 @@ static inline bool players_in_same_range(const struct player *pplayer1, #define IS_REQ_ACTIVE_VARIANT_ASSERT(_kind) \ { \ - fc_assert_ret_val(req != NULL, TRI_MAYBE); \ + fc_assert_ret_val(req != nullptr, TRI_MAYBE); \ fc_assert_ret_val(req->source.kind == _kind, TRI_MAYBE); \ - fc_assert(context != NULL); \ + fc_assert(context != nullptr); \ + fc_assert(other_context != nullptr); \ } /**********************************************************************//** @@ -5531,6 +5561,42 @@ is_latitude_req_active(const struct civ_map *nmap, return TRI_MAYBE; } +/**********************************************************************//** + Determine whether a maximum squared distance 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 squared distance requirement +**************************************************************************/ +static enum fc_tristate +is_max_distance_sq_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_MAX_DISTANCE_SQ); + + switch (req->range) { + case REQ_RANGE_TILE: + if (context->tile == nullptr || other_context->tile == nullptr) { + return TRI_MAYBE; + } + return BOOL_TO_TRISTATE( + sq_map_distance(context->tile, other_context->tile) + <= req->source.value.distance_sq + ); + default: + break; + } + + fc_assert_msg(FALSE, + "Illegal range %d for max squared distance requirement.", + req->range); + + 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 @@ -5659,6 +5725,7 @@ static struct req_def req_definitions[VUT_COUNT] = { [VUT_IMPR_FLAG] = {is_buildingflag_req_active, REQUCH_YES}, [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_MAXLATITUDE] = {is_latitude_req_active, REQUCH_YES}, [VUT_MAXTILEUNITS] = {is_maxunitsontile_req_active, REQUCH_NO}, [VUT_MINCALFRAG] = {is_mincalfrag_req_active, REQUCH_NO}, @@ -6269,6 +6336,7 @@ bool universal_never_there(const struct universal *source) case VUT_TERRFLAG: case VUT_TERRAINALTER: case VUT_MINYEAR: + case VUT_MAX_DISTANCE_SQ: case VUT_NONE: case VUT_COUNT: /* Not implemented. */ @@ -6928,6 +6996,8 @@ bool are_universals_equal(const struct universal *psource1, case VUT_MINLATITUDE: case VUT_MAXLATITUDE: return psource1->value.latitude == psource2->value.latitude; + case VUT_MAX_DISTANCE_SQ: + return psource1->value.distance_sq == psource2->value.distance_sq; case VUT_COUNT: break; } @@ -7085,6 +7155,10 @@ const char *universal_rule_name(const struct universal *psource) case VUT_MAXLATITUDE: fc_snprintf(buffer, sizeof(buffer), "%d", psource->value.latitude); + return buffer; + case VUT_MAX_DISTANCE_SQ: + fc_snprintf(buffer, sizeof(buffer), "%d", psource->value.distance_sq); + return buffer; case VUT_COUNT: break; @@ -7442,6 +7516,11 @@ const char *universal_name_translation(const struct universal *psource, cat_snprintf(buf, bufsz, _("Latitude <= %d"), psource->value.latitude); return buf; + case VUT_MAX_DISTANCE_SQ: + /* TRANS: here <= means 'less than or equal'. */ + cat_snprintf(buf, bufsz, _("Squared distance <= %d"), + psource->value.distance_sq); + return buf; case VUT_COUNT: break; } diff --git a/doc/README.actions b/doc/README.actions index 148e26e082..d4462bbeb9 100644 --- a/doc/README.actions +++ b/doc/README.actions @@ -102,6 +102,8 @@ actor requirements. target requirement vector to the actor requirement vector. * Asymmetric local DiplRel requirements must test for the same thing in the opposite direction. Example: "Hosts embassy" -> "Has embassy" +A "MaxDistanceSq" requirement with the range "Tile" should always be put in +the actor requirements, for the same reasons. Actions and Lua =============== diff --git a/doc/README.effects b/doc/README.effects index 37880482d2..69c493c236 100644 --- a/doc/README.effects +++ b/doc/README.effects @@ -118,6 +118,7 @@ Activity: Local MinMoveFrags: Local MinVeteran: Local MinHitPoints: Local +MaxDistanceSq: Tile MinSize is the minimum size of a city required. @@ -130,6 +131,9 @@ 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). +MaxDistanceSq is about the (squared) distance between two tiles; currently + only available for action enablers (see also README.actions) and a select + few effects. 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 0040bd7c6a..bdc141845b 100644 --- a/gen_headers/enums/fc_types_enums.def +++ b/gen_headers/enums/fc_types_enums.def @@ -98,4 +98,5 @@ values PLAYER_STATE "PlayerState" FORM_AGE "FormAge" MINCITIES "MinCities" + MAX_DISTANCE_SQ "MaxDistanceSq" end diff --git a/server/cityturn.c b/server/cityturn.c index f4c67de5c4..60934a0747 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -1969,6 +1969,7 @@ static bool worklist_item_postpone_req_vec(struct universal *target, case VUT_ACTION: case VUT_OTYPE: case VUT_SPECIALIST: + case VUT_MAX_DISTANCE_SQ: 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 671f2d79e2..5ca9b6a01d 100644 --- a/server/ruleset/rssanity.c +++ b/server/ruleset/rssanity.c @@ -354,6 +354,7 @@ static bool sanity_check_req_set(rs_conversion_logger logger, case VUT_IMPR_GENUS: 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 */ /* There can be only one requirement of these types (with current * range limitations) * Requirements might be identical, but we consider multiple @@ -1343,6 +1344,21 @@ bool sanity_check_ruleset_data(struct rscompat_info *compat) req_to_fstring(preq, &astr)); astr_free(&astr); ok = FALSE; + } else if (preq->source.kind == VUT_MAX_DISTANCE_SQ + && preq->range == REQ_RANGE_TILE) { + struct astring astr; + + /* A Tile-ranged MaxDistanceSq requirement can be expressed as a + * requirement in actor_reqs. Demand that it is there. */ + ruleset_error(logger, LOG_ERROR, + _("Action enabler for %s has a tile MaxDistanceSq " + "requirement %s in target_reqs! Please read the " + "section \"Requirement vector rules\" in " + "doc/README.actions"), + action_id_rule_name(act), + req_to_fstring(preq, &astr)); + astr_free(&astr); + ok = FALSE; } } requirement_vector_iterate_end; diff --git a/tools/ruledit/univ_value.c b/tools/ruledit/univ_value.c index 0904a659d6..5a7923c363 100644 --- a/tools/ruledit/univ_value.c +++ b/tools/ruledit/univ_value.c @@ -252,6 +252,9 @@ bool universal_value_initial(struct universal *src) case VUT_MAXLATITUDE: src->value.latitude = 0; return TRUE; + case VUT_MAX_DISTANCE_SQ: + src->value.distance_sq = 0; + return TRUE; case VUT_COUNT: fc_assert(src->kind != VUT_COUNT); return FALSE; @@ -504,6 +507,7 @@ void universal_kind_values(struct universal *univ, case VUT_MINCITIES: case VUT_MINLATITUDE: case VUT_MAXLATITUDE: + case VUT_MAX_DISTANCE_SQ: /* Requirement types having numerical value */ cb(nullptr, FALSE, data); break; -- 2.34.1