From 75e6620dc088ed4a547c6ecc1e7c2ba565df039e Mon Sep 17 00:00:00 2001
From: Marko Lindqvist <cazfi74@gmail.com>
Date: Mon, 8 Jan 2024 23:09:31 +0200
Subject: [PATCH 40/40] Add requirement type MinCities

See RM #151

Signed-off-by: Marko Lindqvist <cazfi74@gmail.com>
---
 ai/default/daieffects.c    |  4 +++
 ai/default/daimilitary.c   |  1 +
 common/fc_types.h          |  3 ++
 common/reqtext.c           | 30 ++++++++++++++++++++
 common/requirements.c      | 57 ++++++++++++++++++++++++++++++++++++++
 doc/README.effects         |  1 +
 server/cityturn.c          | 15 ++++++++++
 server/ruleset/rssanity.c  |  1 +
 tools/ruledit/univ_value.c |  4 +++
 9 files changed, 116 insertions(+)

diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c
index 686bc0c5c3..d663c22785 100644
--- a/ai/default/daieffects.c
+++ b/ai/default/daieffects.c
@@ -882,6 +882,10 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq,
     /* Can always be achieved. */
     return TRUE;
 
+  case VUT_MINCITIES:
+    /* We don't WANT to lose cities */
+    return preq->present;
+
   case VUT_IMPR_FLAG:
     /* TODO: Have at least some checks for this. */
     break;
diff --git a/ai/default/daimilitary.c b/ai/default/daimilitary.c
index 8ef4f19163..566955ab84 100644
--- a/ai/default/daimilitary.c
+++ b/ai/default/daimilitary.c
@@ -450,6 +450,7 @@ tactical_req_cb(const struct req_context *context,
   case VUT_PLAYER_STATE:
   case VUT_MINCULTURE:
   case VUT_MINTECHS:
+  case VUT_MINCITIES:
   case VUT_ORIGINAL_OWNER:
   case VUT_ROADFLAG:
   case VUT_TERRAIN:
diff --git a/common/fc_types.h b/common/fc_types.h
index c765cfa9fd..1d1eef7066 100644
--- a/common/fc_types.h
+++ b/common/fc_types.h
@@ -759,6 +759,7 @@ typedef union {
   int age;
   int form_age;
   int min_techs;
+  int min_cities;
   int latitude;
 
   enum topo_flag topo_property;
@@ -890,6 +891,8 @@ typedef union {
 #define SPECENUM_VALUE56NAME "PlayerState"
 #define SPECENUM_VALUE57 VUT_FORM_AGE
 #define SPECENUM_VALUE57NAME "FormAge"
+#define SPECENUM_VALUE58 VUT_MINCITIES
+#define SPECENUM_VALUE58NAME "MinCities"
 
 /* Keep this last. */
 #define SPECENUM_COUNT VUT_COUNT
diff --git a/common/reqtext.c b/common/reqtext.c
index b86b2d1835..be72a231f4 100644
--- a/common/reqtext.c
+++ b/common/reqtext.c
@@ -2922,6 +2922,36 @@ bool req_text_insert(char *buf, size_t bufsz, struct player *pplayer,
     }
     break;
 
+  case VUT_MINCITIES:
+    switch (preq->range) {
+    case REQ_RANGE_PLAYER:
+      fc_strlcat(buf, prefix, bufsz);
+      if (preq->present) {
+        cat_snprintf(buf, bufsz,
+                     _("Requires player to have at least %d cities."),
+                     preq->source.value.min_cities);
+      } else {
+        cat_snprintf(buf, bufsz,
+                     _("Prevented when player has at least %d cities."),
+                     preq->source.value.min_cities);
+      }
+      return TRUE;
+    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_CONTINENT:
+    case REQ_RANGE_TEAM:
+    case REQ_RANGE_ALLIANCE:
+    case REQ_RANGE_COUNT:
+      /* Not supported. */
+      break;
+    }
+    break;
+
   case VUT_TERRAINALTER:
     switch (preq->range) {
     case REQ_RANGE_TILE:
diff --git a/common/requirements.c b/common/requirements.c
index 52be9e3577..48a46ae697 100644
--- a/common/requirements.c
+++ b/common/requirements.c
@@ -463,6 +463,12 @@ void universal_value_from_str(struct universal *source, const char *value)
       return;
     }
     break;
+  case VUT_MINCITIES:
+    source->value.min_cities = atoi(value);
+    if (source->value.min_cities > 0) {
+      return;
+    }
+    break;
   case VUT_ACTION:
     source->value.action = action_by_rule_name(value);
     if (source->value.action != NULL) {
@@ -757,6 +763,9 @@ struct universal universal_by_number(const enum universals_n kind,
   case VUT_MINTECHS:
     source.value.min_techs = value;
     return source;
+  case VUT_MINCITIES:
+    source.value.min_cities = value;
+    return source;
   case VUT_ACTION:
     source.value.action = action_by_number(value);
     if (source.value.action != NULL) {
@@ -931,6 +940,8 @@ int universal_number(const struct universal *source)
     return source->value.form_age;
   case VUT_MINTECHS:
     return source->value.min_techs;
+  case VUT_MINCITIES:
+    return source->value.min_cities;
   case VUT_ACTION:
     return action_number(source->value.action);
   case VUT_OTYPE:
@@ -1113,6 +1124,7 @@ struct requirement req_from_str(const char *type, const char *range,
       case VUT_AI_LEVEL:
       case VUT_PLAYER_FLAG:
       case VUT_PLAYER_STATE:
+      case VUT_MINCITIES:
         req.range = REQ_RANGE_PLAYER;
         break;
       case VUT_MINYEAR:
@@ -1158,6 +1170,7 @@ struct requirement req_from_str(const char *type, const char *range,
     case VUT_GOVERNMENT:
     case VUT_AI_LEVEL:
     case VUT_STYLE:
+    case VUT_MINCITIES:
       invalid = (req.range != REQ_RANGE_PLAYER);
       break;
     case VUT_MINSIZE:
@@ -1365,6 +1378,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_MINCITIES:
     case VUT_MINLATITUDE:
     case VUT_MAXLATITUDE:
       /* Most requirements don't support 'survives'. */
@@ -3551,6 +3565,37 @@ is_mintechs_req_active(const struct civ_map *nmap,
   }
 }
 
+/**********************************************************************//**
+  Determine whether a minimum cities count requirement is satisfied in a
+  given context, ignoring parts of the requirement that can be handled
+  uniformly for all requirement types.
+
+  context and req must not be null, and req must be a mincities requirement
+**************************************************************************/
+static enum fc_tristate
+is_mincities_req_active(const struct civ_map *nmap,
+                       const struct req_context *context,
+                       const struct player *other_player,
+                       const struct requirement *req)
+{
+  IS_REQ_ACTIVE_VARIANT_ASSERT(VUT_MINCITIES);
+
+  switch (req->range) {
+  case REQ_RANGE_PLAYER:
+    if (context->player == NULL) {
+      return TRI_MAYBE;
+    } else {
+      /* "None" does not count */
+      return BOOL_TO_TRISTATE(
+                  city_list_size(context->player->cities)
+                  >= req->source.value.min_cities
+              );
+    }
+  default:
+    return TRI_MAYBE;
+  }
+}
+
 /**********************************************************************//**
   Determine whether an AI level requirement is satisfied in a given
   context, ignoring parts of the requirement that can be handled uniformly
@@ -5368,6 +5413,7 @@ static struct req_def req_definitions[VUT_COUNT] = {
   [VUT_MINMOVES] = {is_minmovefrags_req_active, REQUCH_NO},
   [VUT_MINSIZE] = {is_minsize_req_active, REQUCH_NO},
   [VUT_MINTECHS] = {is_mintechs_req_active, REQUCH_ACT, REQUC_WORLD},
+  [VUT_MINCITIES] = {is_mincities_req_active, REQUCH_NO},
   [VUT_MINVETERAN] = {is_minveteran_req_active, REQUCH_SCRIPTS, REQUC_PRESENT},
   [VUT_MINYEAR] = {is_minyear_req_active, REQUCH_HACK, REQUC_PRESENT},
   [VUT_NATION] = {is_nation_req_active, REQUCH_HACK, REQUC_NALLY},
@@ -5887,6 +5933,7 @@ bool universal_never_there(const struct universal *source)
   case VUT_MINCULTURE:
   case VUT_MINFOREIGNPCT:
   case VUT_MINTECHS:
+  case VUT_MINCITIES:
   case VUT_NATIONALITY:
   case VUT_ORIGINAL_OWNER: /* As long as midgame player creation or civil war possible */
   case VUT_DIPLREL:
@@ -6528,6 +6575,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_MINCITIES:
+    return psource1->value.min_cities == psource2->value.min_cities;
   case VUT_ACTION:
     return (action_number(psource1->value.action)
             == action_number(psource2->value.action));
@@ -6687,6 +6736,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_MINCITIES:
+    fc_snprintf(buffer, sizeof(buffer), "%d", psource->value.min_cities);
+
     return buffer;
   case VUT_ACTION:
     return action_rule_name(psource->value.action);
@@ -6927,6 +6980,10 @@ const char *universal_name_translation(const struct universal *psource,
     cat_snprintf(buf, bufsz, _("%d Techs"),
                  psource->value.min_techs);
     return buf;
+  case VUT_MINCITIES:
+    cat_snprintf(buf, bufsz, _("%d Cities"),
+                 psource->value.min_cities);
+    return buf;
   case VUT_ACTION:
     fc_strlcat(buf, action_name_translation(psource->value.action),
                bufsz);
diff --git a/doc/README.effects b/doc/README.effects
index 08e133b463..4b5350abcf 100644
--- a/doc/README.effects
+++ b/doc/README.effects
@@ -58,6 +58,7 @@ Requirement types and supported ranges
 Tech:            World, Alliance, Team, Player
 TechFlag:        World, Alliance, Team, Player
 MinTechs:        World, Player
+MinCities:       Player
 Achievement:     World, Alliance, Team, Player
 Counter:         City
 Gov:             Player
diff --git a/server/cityturn.c b/server/cityturn.c
index 2a75bc6437..8b296ca914 100644
--- a/server/cityturn.c
+++ b/server/cityturn.c
@@ -1786,6 +1786,21 @@ static bool worklist_item_postpone_req_vec(struct universal *target,
           purge = TRUE;
         }
         break;
+      case VUT_MINCITIES:
+        if (preq->present) {
+          notify_player(pplayer, city_tile(pcity),
+                        E_CITY_CANTBUILD, ftc_server,
+                        _("%s can't build %s from the worklist; "
+                          "Must own %d cities. Postponing..."),
+                        city_link(pcity),
+                        tgt_name,
+                        preq->source.value.min_cities);
+          script_server_signal_emit(signal_name, ptarget,
+                                    pcity, "need_mincities");
+        } else {
+          purge = TRUE;
+        }
+        break;
       case VUT_MAXTILEUNITS:
         if (preq->present) {
           notify_player(pplayer, city_tile(pcity),
diff --git a/server/ruleset/rssanity.c b/server/ruleset/rssanity.c
index 94069d6646..3ce717e8d3 100644
--- a/server/ruleset/rssanity.c
+++ b/server/ruleset/rssanity.c
@@ -341,6 +341,7 @@ static bool sanity_check_req_set(rs_conversion_logger logger,
      case VUT_OTYPE:
      case VUT_SPECIALIST:
      case VUT_MINSIZE: /* Breaks nothing, but has no sense either */
+     case VUT_MINCITIES:
      case VUT_MINFOREIGNPCT:
      case VUT_MINMOVES: /* Breaks nothing, but has no sense either */
      case VUT_MINVETERAN: /* Breaks nothing, but has no sense either */
diff --git a/tools/ruledit/univ_value.c b/tools/ruledit/univ_value.c
index aa8840bbdb..0904a659d6 100644
--- a/tools/ruledit/univ_value.c
+++ b/tools/ruledit/univ_value.c
@@ -242,6 +242,9 @@ bool universal_value_initial(struct universal *src)
   case VUT_MINTECHS:
     src->value.min_techs = 0;
     return TRUE;
+  case VUT_MINCITIES:
+    src->value.min_cities = 0;
+    return TRUE;
   case VUT_EXTRAFLAG:
     src->value.extraflag = EF_NATIVE_TILE;
     return TRUE;
@@ -498,6 +501,7 @@ void universal_kind_values(struct universal *univ,
   case VUT_AGE:
   case VUT_FORM_AGE:
   case VUT_MINTECHS:
+  case VUT_MINCITIES:
   case VUT_MINLATITUDE:
   case VUT_MAXLATITUDE:
     /* Requirement types having numerical value */
-- 
2.43.0

