From bdcf229666f01667233b6a5b31ad1d0cb865e779 Mon Sep 17 00:00:00 2001
From: Marko Lindqvist <cazfi74@gmail.com>
Date: Fri, 23 May 2025 15:41:19 +0300
Subject: [PATCH 64/64] Add FutureTechs requirement type

Requested by keykeypi

See RM #1425

Signed-off-by: Marko Lindqvist <cazfi74@gmail.com>
---
 ai/default/daieffects.c              |  1 +
 ai/default/daimilitary.c             |  1 +
 common/fc_types.h                    |  1 +
 common/reqtext.c                     | 41 ++++++++++++++++++
 common/requirements.c                | 64 ++++++++++++++++++++++++++++
 doc/README.effects                   |  1 +
 gen_headers/enums/fc_types_enums.def |  1 +
 server/cityturn.c                    | 15 +++++++
 server/ruleset/rssanity.c            |  1 +
 tools/ruledit/univ_value.c           |  4 ++
 10 files changed, 130 insertions(+)

diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c
index d4b8cd9985..ab16eef1ba 100644
--- a/ai/default/daieffects.c
+++ b/ai/default/daieffects.c
@@ -912,6 +912,7 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq,
   case VUT_ACHIEVEMENT:
   case VUT_MINCULTURE:
   case VUT_MINTECHS:
+  case VUT_FUTURETECHS:
     /* No way to remove once present. */
     return preq->present;
 
diff --git a/ai/default/daimilitary.c b/ai/default/daimilitary.c
index cef80608c8..b8aa631a83 100644
--- a/ai/default/daimilitary.c
+++ b/ai/default/daimilitary.c
@@ -455,6 +455,7 @@ tactical_req_cb(const struct req_context *context,
   case VUT_PLAYER_STATE:
   case VUT_MINCULTURE:
   case VUT_MINTECHS:
+  case VUT_FUTURETECHS:
   case VUT_MINCITIES:
   case VUT_ORIGINAL_OWNER:
   case VUT_ROADFLAG:
diff --git a/common/fc_types.h b/common/fc_types.h
index 3803d3c153..bfa2c07d28 100644
--- a/common/fc_types.h
+++ b/common/fc_types.h
@@ -575,6 +575,7 @@ typedef union {
   int age;
   int form_age;
   int min_techs;
+  int future_techs;
   int min_cities;
   int latitude;
   int distance_sq;
diff --git a/common/reqtext.c b/common/reqtext.c
index 7439f222a6..1965ce6fba 100644
--- a/common/reqtext.c
+++ b/common/reqtext.c
@@ -2945,6 +2945,47 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer,
     }
     break;
 
+  case VUT_FUTURETECHS:
+    switch (preq->range) {
+    case REQ_RANGE_WORLD:
+      fc_strlcat(buf, prefix, bufsz);
+      if (preq->present) {
+        cat_snprintf(buf, bufsz,
+                     _("Requires %d future techs to be known in the world."),
+                     preq->source.value.future_techs);
+      } else {
+        cat_snprintf(buf, bufsz,
+                     _("Prevented when %d future techs are known in the world."),
+                     preq->source.value.future_techs);
+      }
+      return TRUE;
+    case REQ_RANGE_PLAYER:
+      fc_strlcat(buf, prefix, bufsz);
+      if (preq->present) {
+        cat_snprintf(buf, bufsz,
+                     _("Requires player to know %d future techs."),
+                     preq->source.value.future_techs);
+      } else {
+        cat_snprintf(buf, bufsz,
+                     _("Prevented when player knows %d future techs."),
+                     preq->source.value.future_techs);
+      }
+      return TRUE;
+    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_CONTINENT:
+    case REQ_RANGE_TEAM:
+    case REQ_RANGE_ALLIANCE:
+    case REQ_RANGE_COUNT:
+      /* Not supported. */
+      break;
+    }
+    break;
+
   case VUT_MINCITIES:
     switch (preq->range) {
     case REQ_RANGE_PLAYER:
diff --git a/common/requirements.c b/common/requirements.c
index e19e904089..db6b863ab6 100644
--- a/common/requirements.c
+++ b/common/requirements.c
@@ -473,6 +473,12 @@ void universal_value_from_str(struct universal *source, const char *value)
       return;
     }
     break;
+  case VUT_FUTURETECHS:
+    source->value.future_techs = atoi(value);
+    if (source->value.future_techs > 0) {
+      return;
+    }
+    break;
   case VUT_MINCITIES:
     source->value.min_cities = atoi(value);
     if (source->value.min_cities > 0) {
@@ -792,6 +798,9 @@ struct universal universal_by_number(const enum universals_n kind,
   case VUT_MINTECHS:
     source.value.min_techs = value;
     return source;
+  case VUT_FUTURETECHS:
+    source.value.future_techs = value;
+    return source;
   case VUT_MINCITIES:
     source.value.min_cities = value;
     return source;
@@ -979,6 +988,8 @@ int universal_number(const struct universal *source)
     return source->value.form_age;
   case VUT_MINTECHS:
     return source->value.min_techs;
+  case VUT_FUTURETECHS:
+    return source->value.future_techs;
   case VUT_MINCITIES:
     return source->value.min_cities;
   case VUT_ACTION:
@@ -1172,6 +1183,7 @@ struct requirement req_from_str(const char *type, const char *range,
       case VUT_PLAYER_FLAG:
       case VUT_PLAYER_STATE:
       case VUT_MINCITIES:
+      case VUT_FUTURETECHS:
         req.range = REQ_RANGE_PLAYER;
         break;
       case VUT_MINYEAR:
@@ -1220,6 +1232,7 @@ struct requirement req_from_str(const char *type, const char *range,
       break;
     case VUT_ACHIEVEMENT:
     case VUT_MINTECHS:
+    case VUT_FUTURETECHS:
       invalid = (req.range < REQ_RANGE_PLAYER);
       break;
     case VUT_ADVANCE:
@@ -1459,6 +1472,7 @@ struct requirement req_from_str(const char *type, const char *range,
     case VUT_DIPLREL_UNITANY_O:
     case VUT_MAXTILEUNITS:
     case VUT_MINTECHS:
+    case VUT_FUTURETECHS:
     case VUT_MINCITIES:
     case VUT_MINLATITUDE:
     case VUT_MAXLATITUDE:
@@ -3796,6 +3810,44 @@ is_mintechs_req_active(const struct civ_map *nmap,
   }
 }
 
+/**********************************************************************//**
+  Determine whether a minimum future technologies 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 mintechs requirement
+**************************************************************************/
+static enum fc_tristate
+is_futuretechs_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_FUTURETECHS);
+
+  switch (req->range) {
+  case REQ_RANGE_WORLD:
+    players_iterate_alive(plr) {
+      if (research_get(plr)->future_tech
+          >= req->source.value.future_techs) {
+        return TRI_YES;
+      }
+    } players_iterate_alive_end;
+
+    return TRI_NO;
+  case REQ_RANGE_PLAYER:
+    if (context->player == nullptr) {
+      return TRI_MAYBE;
+    } else {
+      return BOOL_TO_TRISTATE(research_get(context->player)->future_tech
+                              >= req->source.value.future_techs);
+    }
+  default:
+    return TRI_MAYBE;
+  }
+}
+
 /**********************************************************************//**
   Determine whether a minimum cities count requirement is satisfied in a
   given context, ignoring parts of the requirement that can be handled
@@ -6246,6 +6298,7 @@ static struct req_def req_definitions[VUT_COUNT] = {
   [VUT_DIPLREL_UNITANY_O] = {is_diplrel_unitany_o_req_active, REQUCH_NO},
   [VUT_EXTRA] = {is_extra_req_active, REQUCH_NO, REQUC_LOCAL},
   [VUT_EXTRAFLAG] = {is_extraflag_req_active, REQUCH_NO, REQUC_LOCAL},
+  [VUT_FUTURETECHS] = {is_futuretechs_req_active, REQUCH_ACT, REQUC_WORLD},
   [VUT_GOOD] = {is_good_req_active, REQUCH_NO},
   [VUT_GOVERNMENT] = {is_gov_req_active, REQUCH_NO},
   [VUT_IMPROVEMENT] = {is_building_req_active, REQUCH_NO, REQUC_IMPR},
@@ -6802,6 +6855,7 @@ bool universal_never_there(const struct universal *source)
   case VUT_MINCULTURE:
   case VUT_MINFOREIGNPCT:
   case VUT_MINTECHS:
+  case VUT_FUTURETECHS:
   case VUT_MINCITIES:
   case VUT_NATIONALITY:
   case VUT_ORIGINAL_OWNER: /* As long as midgame player creation or civil war possible */
@@ -7463,6 +7517,8 @@ bool are_universals_equal(const struct universal *psource1,
     return psource1->value.form_age == psource2->value.form_age;
   case VUT_MINTECHS:
     return psource1->value.min_techs == psource2->value.min_techs;
+  case VUT_FUTURETECHS:
+    return psource1->value.future_techs == psource2->value.future_techs;
   case VUT_MINCITIES:
     return psource1->value.min_cities == psource2->value.min_cities;
   case VUT_ACTION:
@@ -7633,6 +7689,10 @@ const char *universal_rule_name(const struct universal *psource)
   case VUT_MINTECHS:
     fc_snprintf(buffer, sizeof(buffer), "%d", psource->value.min_techs);
 
+    return buffer;
+  case VUT_FUTURETECHS:
+    fc_snprintf(buffer, sizeof(buffer), "%d", psource->value.future_techs);
+
     return buffer;
   case VUT_MINCITIES:
     fc_snprintf(buffer, sizeof(buffer), "%d", psource->value.min_cities);
@@ -7896,6 +7956,10 @@ const char *universal_name_translation(const struct universal *psource,
     cat_snprintf(buf, bufsz, _("%d Techs"),
                  psource->value.min_techs);
     return buf;
+  case VUT_FUTURETECHS:
+    cat_snprintf(buf, bufsz, _("%d Future techs"),
+                 psource->value.future_techs);
+    return buf;
   case VUT_MINCITIES:
     cat_snprintf(buf, bufsz, _("%d Cities"),
                  psource->value.min_cities);
diff --git a/doc/README.effects b/doc/README.effects
index 5231d8ceb3..243a6ed468 100644
--- a/doc/README.effects
+++ b/doc/README.effects
@@ -60,6 +60,7 @@ Requirement types and supported ranges
 Tech:            World, Alliance, Team, Player, Local
 TechFlag:        World, Alliance, Team, Player, Local
 MinTechs:        World, Player
+FutureTechs:     World, Player
 MinCities:       Player
 Achievement:     World, Alliance, Team, Player
 Counter:         City
diff --git a/gen_headers/enums/fc_types_enums.def b/gen_headers/enums/fc_types_enums.def
index a7c3cdf4e8..483f7aba40 100644
--- a/gen_headers/enums/fc_types_enums.def
+++ b/gen_headers/enums/fc_types_enums.def
@@ -58,6 +58,7 @@ values
   EXTRA              "Extra"
   EXTRAFLAG          "ExtraFlag"
   FORM_AGE           "FormAge"
+  FUTURETECHS        "FutureTechs"
   GOOD               "Good"
   GOVERNMENT         "Gov"
   IMPROVEMENT        "Building"
diff --git a/server/cityturn.c b/server/cityturn.c
index b8df47e2fd..96c1104474 100644
--- a/server/cityturn.c
+++ b/server/cityturn.c
@@ -1792,6 +1792,21 @@ static bool worklist_item_postpone_req_vec(struct universal *target,
           purge = TRUE;
         }
         break;
+      case VUT_FUTURETECHS:
+        if (preq->present) {
+          notify_player(pplayer, city_tile(pcity),
+                        E_CITY_CANTBUILD, ftc_server,
+                        _("%s can't build %s from the worklist; "
+                          "%d future techs must be known. Postponing..."),
+                        city_link(pcity),
+                        tgt_name,
+                        preq->source.value.future_techs);
+          script_server_signal_emit(signal_name, ptarget,
+                                    pcity, "need_futuretechs");
+        } else {
+          purge = TRUE;
+        }
+        break;
       case VUT_MINCITIES:
         if (preq->present) {
           notify_player(pplayer, city_tile(pcity),
diff --git a/server/ruleset/rssanity.c b/server/ruleset/rssanity.c
index 1deeeb08de..e77f06dbf1 100644
--- a/server/ruleset/rssanity.c
+++ b/server/ruleset/rssanity.c
@@ -400,6 +400,7 @@ static bool sanity_check_req_set(rs_conversion_logger logger,
       break;
 
     case VUT_MINTECHS:
+    case VUT_FUTURETECHS:
       /* At ranges 'Player' and 'World' */
       if (rc > 2) {
         ruleset_error(logger, LOG_ERROR,
diff --git a/tools/ruledit/univ_value.c b/tools/ruledit/univ_value.c
index d158d3fa36..755cdb9a80 100644
--- a/tools/ruledit/univ_value.c
+++ b/tools/ruledit/univ_value.c
@@ -243,6 +243,9 @@ bool universal_value_initial(struct universal *src)
   case VUT_MINTECHS:
     src->value.min_techs = 0;
     return TRUE;
+  case VUT_FUTURETECHS:
+    src->value.future_techs = 0;
+    return TRUE;
   case VUT_MINCITIES:
     src->value.min_cities = 0;
     return TRUE;
@@ -517,6 +520,7 @@ void universal_kind_values(struct universal *univ,
   case VUT_AGE:
   case VUT_FORM_AGE:
   case VUT_MINTECHS:
+  case VUT_FUTURETECHS:
   case VUT_MINCITIES:
   case VUT_MINLATITUDE:
   case VUT_MAXLATITUDE:
-- 
2.47.2

