From 4a2bc13b10fce37c49c180c83315ea9e9905a91f Mon Sep 17 00:00:00 2001 From: Alina Lenk Date: Fri, 25 Mar 2022 17:59:03 +0100 Subject: [PATCH] Expand latitude requirements Extend MinLatitude requirements to more ranges and add MaxLatitude requirements See osdn #44182 Signed-off-by: Alina Lenk --- ai/default/daieffects.c | 1 + common/fc_types.h | 2 + common/reqtext.c | 108 ++++++++++++++++++++++ common/requirements.c | 178 ++++++++++++++++++++++++++++++++----- doc/README.effects | 6 +- server/cityturn.c | 1 + server/rssanity.c | 11 ++- tools/ruledit/univ_value.c | 2 + 8 files changed, 285 insertions(+), 24 deletions(-) diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c index 4157b572b2..bce8c56d67 100644 --- a/ai/default/daieffects.c +++ b/ai/default/daieffects.c @@ -800,6 +800,7 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, case VUT_AI_LEVEL: case VUT_SERVERSETTING: case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: /* Beyond player control. */ return FALSE; diff --git a/common/fc_types.h b/common/fc_types.h index 0c700b515c..29a30e0572 100644 --- a/common/fc_types.h +++ b/common/fc_types.h @@ -765,6 +765,8 @@ typedef union { #define SPECENUM_VALUE48NAME "DiplRelUnitAnyOther" #define SPECENUM_VALUE49 VUT_MINLATITUDE #define SPECENUM_VALUE49NAME "MinLatitude" +#define SPECENUM_VALUE50 VUT_MAXLATITUDE +#define SPECENUM_VALUE50NAME "MaxLatitude" /* Keep this last. */ #define SPECENUM_COUNT VUT_COUNT #include "specenum_gen.h" diff --git a/common/reqtext.c b/common/reqtext.c index 09f5ee2311..66beace99b 100644 --- a/common/reqtext.c +++ b/common/reqtext.c @@ -2947,6 +2947,20 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer, case VUT_MINLATITUDE: switch (preq->range) { + case REQ_RANGE_WORLD: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + cat_snprintf(buf, bufsz, + _("Some part of the world must be at latitude %d or " + "further north."), + preq->source.value.latitude); + } else { + cat_snprintf(buf, bufsz, + _("The entire world must be at latitude %d or " + "further south."), + preq->source.value.latitude - 1); + } + return TRUE; case REQ_RANGE_TILE: fc_strlcat(buf, prefix, bufsz); if (preq->present) { @@ -2960,14 +2974,108 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer, } return TRUE; case REQ_RANGE_CADJACENT: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + cat_snprintf(buf, bufsz, + _("A cardinally adjacent tile must be at latitude " + "%d or further north."), + preq->source.value.latitude); + } else { + cat_snprintf(buf, bufsz, + _("All cardinally adjacent tiles must be at latitude " + "%d or further south."), + preq->source.value.latitude - 1); + } + return TRUE; case REQ_RANGE_ADJACENT: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + cat_snprintf(buf, bufsz, + _("An adjacent tile must be at latitude %d or " + "further north."), + preq->source.value.latitude); + } else { + cat_snprintf(buf, bufsz, + _("All adjacent tiles must be at latitude %d or " + "further south."), + preq->source.value.latitude - 1); + } + return TRUE; case REQ_RANGE_CITY: case REQ_RANGE_TRADEROUTE: case REQ_RANGE_CONTINENT: case REQ_RANGE_PLAYER: case REQ_RANGE_TEAM: case REQ_RANGE_ALLIANCE: + case REQ_RANGE_LOCAL: + case REQ_RANGE_COUNT: + /* Not supported. */ + break; + } + break; + + case VUT_MAXLATITUDE: + switch (preq->range) { case REQ_RANGE_WORLD: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + cat_snprintf(buf, bufsz, + _("Some part of the world must be at latitude %d or " + "further south."), + preq->source.value.latitude); + } else { + cat_snprintf(buf, bufsz, + _("The entire world must be at latitude %d or " + "further north."), + preq->source.value.latitude + 1); + } + return TRUE; + case REQ_RANGE_TILE: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + cat_snprintf(buf, bufsz, + _("Tile must be at latitude %d or further south."), + preq->source.value.latitude); + } else { + cat_snprintf(buf, bufsz, + _("Tile must be at latitude %d or further north."), + preq->source.value.latitude + 1); + } + return TRUE; + case REQ_RANGE_CADJACENT: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + cat_snprintf(buf, bufsz, + _("A cardinally adjacent tile must be at latitude " + "%d or further south."), + preq->source.value.latitude); + } else { + cat_snprintf(buf, bufsz, + _("All cardinally adjacent tiles must be at latitude " + "%d or further north."), + preq->source.value.latitude + 1); + } + return TRUE; + case REQ_RANGE_ADJACENT: + fc_strlcat(buf, prefix, bufsz); + if (preq->present) { + cat_snprintf(buf, bufsz, + _("An adjacent tile must be at latitude %d or " + "further south."), + preq->source.value.latitude); + } else { + cat_snprintf(buf, bufsz, + _("All adjacent tiles must be at latitude %d or " + "further north."), + preq->source.value.latitude + 1); + } + return TRUE; + case REQ_RANGE_CITY: + case REQ_RANGE_TRADEROUTE: + case REQ_RANGE_CONTINENT: + case REQ_RANGE_PLAYER: + case REQ_RANGE_TEAM: + case REQ_RANGE_ALLIANCE: case REQ_RANGE_LOCAL: case REQ_RANGE_COUNT: /* Not supported. */ diff --git a/common/requirements.c b/common/requirements.c index 1ae3d2ab11..aa5ce858ee 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -379,6 +379,7 @@ void universal_value_from_str(struct universal *source, const char *value) } break; case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: source->value.latitude = atoi(value); if (source->value.latitude >= -MAP_MAX_LATITUDE && source->value.latitude <= MAP_MAX_LATITUDE) { @@ -587,6 +588,7 @@ struct universal universal_by_number(const enum universals_n kind, source.value.citystatus = value; return source; case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: source.value.latitude = value; return source; case VUT_COUNT: @@ -713,6 +715,7 @@ int universal_number(const struct universal *source) case VUT_CITYSTATUS: return source->value.citystatus; case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: return source->value.latitude; case VUT_COUNT: break; @@ -814,6 +817,7 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_CITYTILE: case VUT_MAXTILEUNITS: case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: req.range = REQ_RANGE_TILE; break; case VUT_MINSIZE: @@ -955,7 +959,6 @@ struct requirement req_from_str(const char *type, const char *range, invalid = (req.range != REQ_RANGE_LOCAL); break; case VUT_TERRAINALTER: /* XXX could in principle support C/ADJACENT */ - case VUT_MINLATITUDE: invalid = (req.range != REQ_RANGE_TILE); break; case VUT_CITYTILE: @@ -964,6 +967,17 @@ struct requirement req_from_str(const char *type, const char *range, && req.range != REQ_RANGE_CADJACENT && req.range != REQ_RANGE_ADJACENT); break; + case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: + invalid = (req.range != REQ_RANGE_TILE + && req.range != REQ_RANGE_CADJACENT + && req.range != REQ_RANGE_ADJACENT + && req.range != REQ_RANGE_WORLD) + /* Avoid redundancy at tile range: no negated requirements + * that could be emulated by a present requirement of the + * other type */ + || (req.range == REQ_RANGE_TILE && !req.present); + break; case VUT_MINYEAR: case VUT_MINCALFRAG: case VUT_TOPO: @@ -1053,6 +1067,7 @@ struct requirement req_from_str(const char *type, const char *range, case VUT_MAXTILEUNITS: case VUT_MINTECHS: case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: /* Most requirements don't support 'survives'. */ invalid = survives; break; @@ -1317,23 +1332,54 @@ bool are_requirements_contradictions(const struct requirement *req1, } break; case VUT_MINLATITUDE: - if (req2->source.kind != VUT_MINLATITUDE) { - /* Finding contradictions across requirement kinds aren't supported - * for MinLatitude requirements. */ - return FALSE; - } else if (req1->present == req2->present) { - /* No contradiction possible. */ + case VUT_MAXLATITUDE: + if (req2->source.kind != VUT_MINLATITUDE + && req2->source.kind != VUT_MAXLATITUDE) { + /* Finding contradictions across requirement kinds other than each + * other is not supported for MinLatitude and MaxLatitude. */ return FALSE; } else { - /* Contradiction when lower bound (present requirement) is not less - * (i.e. greater or equal) than upper bound (negated requirement) */ - if (req1->present) { - return (req1->source.value.latitude - >= req2->source.value.latitude); - } else { - return (req1->source.value.latitude - <= req2->source.value.latitude); + /* For a contradiction, we need + * - a minimum (present MinLatitude or negated MaxLatitude) + * - a maximum (negated MinLatitude or present MaxLatitude) + * - the maximum to be less than the minimum + * - a requirement at the larger range that applies to the entire + * range (i.e. a negated requirement, unless the range is Tile) + * Otherwise, the two requirements could still be fulfilled + * simultaneously by different tiles in the range */ + + /* Initial values beyond the boundaries to avoid edge cases */ + int minimum = -MAP_MAX_LATITUDE - 1, maximum = MAP_MAX_LATITUDE + 1; + enum req_range covered_range = REQ_RANGE_TILE; + +#define EXTRACT_INFO(req) \ + if (req->present) { \ + if (req->source.kind == VUT_MINLATITUDE) { \ + /* present MinLatitude */ \ + minimum = MAX(minimum, req->source.value.latitude); \ + } else { \ + /* present MaxLatitude */ \ + maximum = MIN(maximum, req->source.value.latitude); \ + } \ + } else { \ + covered_range = MAX(covered_range, req->range); \ + if (req1->source.kind == VUT_MINLATITUDE) { \ + /* negated MinLatitude */ \ + maximum = MIN(maximum, req->source.value.latitude - 1); \ + } else { \ + /* negated MaxLatitude */ \ + minimum = MAX(minimum, req->source.value.latitude + 1); \ + } \ } + + EXTRACT_INFO(req1); + EXTRACT_INFO(req2); + +#undef EXTRACT_INFO + + return (maximum < minimum + && covered_range >= req1->range + && covered_range >= req2->range); } break; case VUT_NATION: @@ -3247,6 +3293,88 @@ is_achievement_in_range(const struct player *target_player, return TRI_MAYBE; } +/**********************************************************************//** + Is a tile with at least/most the given latitude in range. + + kind must be VUT_MINLATITUDE or VUT_MAXLATITUDE +**************************************************************************/ +static enum fc_tristate +is_latitude_in_range(const struct tile *target_tile, enum req_range range, + enum universals_n kind, int latitude) +{ + int min = -MAP_MAX_LATITUDE, max = MAP_MAX_LATITUDE; + + switch (kind) { + case VUT_MINLATITUDE: + min = latitude; + break; + case VUT_MAXLATITUDE: + max = latitude; + break; + default: + fc_assert(kind == VUT_MINLATITUDE || kind == VUT_MAXLATITUDE); + break; + } + + switch (range) { + case REQ_RANGE_WORLD: + return BOOL_TO_TRISTATE(min <= MAP_MAX_REAL_LATITUDE(wld.map) + && max >= MAP_MIN_REAL_LATITUDE(wld.map)); + + case REQ_RANGE_TILE: + if (target_tile == NULL) { + return TRI_MAYBE; + } else { + int tile_lat = map_signed_latitude(target_tile); + + return BOOL_TO_TRISTATE(min <= tile_lat && max >= tile_lat); + } + + case REQ_RANGE_CADJACENT: + if (target_tile == NULL) { + return TRI_MAYBE; + } + + cardinal_adjc_iterate(&(wld.map), target_tile, adjc_tile) { + int tile_lat = map_signed_latitude(target_tile); + + if (min <= tile_lat && max >= tile_lat) { + return TRI_YES; + } + } cardinal_adjc_iterate_end; + return TRI_NO; + + case REQ_RANGE_ADJACENT: + if (!target_tile) { + return TRI_MAYBE; + } + + adjc_iterate(&(wld.map), target_tile, adjc_tile) { + int tile_lat = map_signed_latitude(target_tile); + + if (min <= tile_lat && max >= tile_lat) { + return TRI_YES; + } + } adjc_iterate_end; + return TRI_NO; + + case REQ_RANGE_CITY: + case REQ_RANGE_TRADEROUTE: + case REQ_RANGE_CONTINENT: + case REQ_RANGE_PLAYER: + case REQ_RANGE_TEAM: + case REQ_RANGE_ALLIANCE: + case REQ_RANGE_LOCAL: + case REQ_RANGE_COUNT: + break; + } + + fc_assert_msg(FALSE, + "Illegal range %d for latitude requirement.", range); + + return TRI_MAYBE; +} + /**********************************************************************//** Checks the requirement to see if it is active on the given target. @@ -3626,12 +3754,10 @@ bool is_req_active(const struct req_context *context, } break; case VUT_MINLATITUDE: - if (context->tile == NULL) { - eval = TRI_MAYBE; - } else { - eval = BOOL_TO_TRISTATE(map_signed_latitude(context->tile) - >= req->source.value.latitude); - } + case VUT_MAXLATITUDE: + eval = is_latitude_in_range(context->tile, req->range, + req->source.kind, + req->source.value.latitude); break; case VUT_COUNT: log_error("is_req_active(): invalid source kind %d.", req->source.kind); @@ -3703,6 +3829,7 @@ bool is_req_unchanging(const struct requirement *req) case VUT_TOPO: case VUT_SERVERSETTING: case VUT_MINLATITUDE: /* Might change if more ranges are supported */ + case VUT_MAXLATITUDE: /* Might change if more ranges are supported */ return TRUE; case VUT_NATION: case VUT_NATIONGROUP: @@ -3814,6 +3941,8 @@ bool universal_never_there(const struct universal *source) return !extra_flag_is_in_use(source->value.extraflag); case VUT_MINLATITUDE: return source->value.latitude > MAP_MAX_REAL_LATITUDE(wld.map); + case VUT_MAXLATITUDE: + return source->value.latitude < MAP_MIN_REAL_LATITUDE(wld.map); case VUT_OTYPE: case VUT_SPECIALIST: case VUT_AI_LEVEL: @@ -4472,6 +4601,7 @@ bool are_universals_equal(const struct universal *psource1, case VUT_CITYSTATUS: return psource1->value.citystatus == psource2->value.citystatus; case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: return psource1->value.latitude == psource2->value.latitude; case VUT_COUNT: break; @@ -4607,6 +4737,7 @@ const char *universal_rule_name(const struct universal *psource) case VUT_TERRAINALTER: return terrain_alteration_name(psource->value.terrainalter); case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: fc_snprintf(buffer, sizeof(buffer), "%d", psource->value.latitude); return buffer; @@ -4914,6 +5045,11 @@ const char *universal_name_translation(const struct universal *psource, cat_snprintf(buf, bufsz, _("Latitude >= %d"), psource->value.latitude); return buf; + case VUT_MAXLATITUDE: + /* TRANS: here <= means 'greater than or equal'. */ + cat_snprintf(buf, bufsz, _("Latitude <= %d"), + psource->value.latitude); + return buf; case VUT_COUNT: break; } diff --git a/doc/README.effects b/doc/README.effects index 8bc9472461..da085e6c39 100644 --- a/doc/README.effects +++ b/doc/README.effects @@ -92,7 +92,8 @@ MaxUnitsOnTile: Tile, Adjacent, CAdjacent TerrainClass: Tile, Adjacent, CAdjacent, Traderoute, City TerrainFlag: Tile, Adjacent, CAdjacent, Traderoute, City TerrainAlter: Tile -MinLatitude: Tile +MinLatitude: Tile, Adjacent, CAdjacent, World +MaxLatitude: Tile, Adjacent, CAdjacent, World CityTile: Tile, Adjacent, CAdjacent CityStatus: Traderoute, City Style: Player @@ -109,7 +110,8 @@ TerrainClass is either "Land" or "Oceanic". TerrainAlter is "CanIrrigate", "CanMine", "CanRoad", "CanBase", or "CanPlace" CityTile is "Center" (city center), "Claimed" (tile owned), "Extras Owned" (extra on tile owned), or "Worked" (worked by any city) -MinLatitude is a number from -1000 (south pole) to 1000 (north pole). +MinLatitude and MaxLatitude are numbers from -1000 (south pole) to 1000 +(north pole). CityStatus is "OwnedByOriginal", "Starved", "Disorder", or "Celebration" DiplRel is a diplomatic relationship. MaxUnitsOnTile is about the number of units present on a tile. diff --git a/server/cityturn.c b/server/cityturn.c index a17e0f7fa8..adbad7b099 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -1740,6 +1740,7 @@ static bool worklist_item_postpone_req_vec(struct universal *target, } break; case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: /* Can't change where the city is located. */ purge = TRUE; break; diff --git a/server/rssanity.c b/server/rssanity.c index 8640b1b0e4..bb021eecf1 100644 --- a/server/rssanity.c +++ b/server/rssanity.c @@ -288,6 +288,13 @@ static bool sanity_check_req_set(int reqs_of_type[], return FALSE; } break; + case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: + if (tile_reqs_of_type[preq->range] > 1) { + log_error("%s: Requirement list has duplicate %s requirement at Tile range", + list_for, universal_type_rule_name(&preq->source)); + return FALSE; + } default: break; } @@ -315,7 +322,6 @@ static bool sanity_check_req_set(int reqs_of_type[], case VUT_STYLE: case VUT_IMPR_GENUS: case VUT_CITYSTATUS: - case VUT_MINLATITUDE: /* 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 @@ -399,6 +405,9 @@ static bool sanity_check_req_set(int reqs_of_type[], case VUT_DIPLREL_UNITANY: case VUT_DIPLREL_UNITANY_O: /* Can have multiple requirements of these types */ + case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: + /* Can have multiple requirements at different ranges */ break; case VUT_COUNT: /* Should never be in requirement vector */ diff --git a/tools/ruledit/univ_value.c b/tools/ruledit/univ_value.c index cb18f04b9a..d1771a8780 100644 --- a/tools/ruledit/univ_value.c +++ b/tools/ruledit/univ_value.c @@ -217,6 +217,7 @@ bool universal_value_initial(struct universal *src) src->value.extraflag = EF_NATIVE_TILE; return TRUE; case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: src->value.latitude = 0; return TRUE; case VUT_COUNT: @@ -438,6 +439,7 @@ void universal_kind_values(struct universal *univ, case VUT_AGE: case VUT_MINTECHS: case VUT_MINLATITUDE: + case VUT_MAXLATITUDE: /* Requirement types having numerical value */ cb(NULL, FALSE, data); break; -- 2.17.1