From 4949912834d07ee9b0bbf3c68d3bb6d7c8ce5eeb Mon Sep 17 00:00:00 2001
From: Marko Lindqvist <cazfi74@gmail.com>
Date: Sat, 23 Mar 2024 08:50:37 +0200
Subject: [PATCH 48/48] Add support for UnitType requirements on Tile -
 Adjacent ranges

See RM #347

Signed-off-by: Marko Lindqvist <cazfi74@gmail.com>
---
 common/requirements.c     |  69 +++++++++++++++++++++++---
 doc/README.effects        |   2 +-
 server/ruleset/rssanity.c | 100 +++++++++++++++++++-------------------
 3 files changed, 113 insertions(+), 58 deletions(-)

diff --git a/common/requirements.c b/common/requirements.c
index c714212085..45637e9d0a 100644
--- a/common/requirements.c
+++ b/common/requirements.c
@@ -1095,7 +1095,7 @@ struct requirement req_from_str(const char *type, const char *range,
       case VUT_EXTRA:
       case VUT_ROADFLAG:
       case VUT_EXTRAFLAG:
-        /* keep old behavior */
+        /* Keep old behavior */
         req.range = REQ_RANGE_TILE;
         break;
       case VUT_TERRAIN:
@@ -1240,7 +1240,6 @@ struct requirement req_from_str(const char *type, const char *range,
                  && req.range != REQ_RANGE_ALLIANCE
                  && req.range != REQ_RANGE_WORLD);
       break;
-    case VUT_UTYPE:
     case VUT_UTFLAG:
     case VUT_UCLASS:
     case VUT_UCFLAG:
@@ -1254,6 +1253,12 @@ struct requirement req_from_str(const char *type, const char *range,
     case VUT_SPECIALIST:
       invalid = (req.range != REQ_RANGE_LOCAL);
       break;
+    case VUT_UTYPE:
+      invalid = (req.range != REQ_RANGE_LOCAL
+                 && req.range != REQ_RANGE_TILE
+                 && req.range != REQ_RANGE_CADJACENT
+                 && req.range != REQ_RANGE_ADJACENT);
+      break;
     case VUT_TERRAINALTER: /* XXX could in principle support C/ADJACENT */
       invalid = (req.range != REQ_RANGE_TILE);
       break;
@@ -4071,14 +4076,64 @@ is_unittype_req_active(const struct civ_map *nmap,
 
   punittype = req->source.value.utype;
 
-  if (req->range != REQ_RANGE_LOCAL) {
+  switch (req->range) {
+  case REQ_RANGE_LOCAL:
+    if (!context->unittype) {
+      return TRI_MAYBE;
+    }
+    return BOOL_TO_TRISTATE(context->unittype == punittype);
+  case REQ_RANGE_TILE:
+  case REQ_RANGE_CADJACENT:
+  case REQ_RANGE_ADJACENT:
+    if (context->tile == nullptr) {
+      return TRI_MAYBE;
+    }
+
+    unit_list_iterate(context->tile->units, punit) {
+      if (punit->utype == punittype) {
+        return TRI_YES;
+      }
+    } unit_list_iterate_end;
+
+    if (req->range == REQ_RANGE_TILE) {
+      return TRI_NO;
+    }
+
+    if (req->range == REQ_RANGE_CADJACENT) {
+      cardinal_adjc_iterate(nmap, context->tile, adjc_tile) {
+        unit_list_iterate(adjc_tile->units, punit) {
+          if (punit->utype == punittype) {
+            return TRI_YES;
+          }
+        } unit_list_iterate_end;
+      } cardinal_adjc_iterate_end;
+    } else {
+      fc_assert(req->range == REQ_RANGE_ADJACENT);
+
+      adjc_iterate(nmap, context->tile, adjc_tile) {
+        unit_list_iterate(adjc_tile->units, punit) {
+          if (punit->utype == punittype) {
+            return TRI_YES;
+          }
+        } unit_list_iterate_end;
+      } adjc_iterate_end;
+    }
+
     return TRI_NO;
-  }
-  if (!context->unittype) {
-    return TRI_MAYBE;
+
+  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_COUNT:
+    fc_assert(FALSE);
+    break;
   }
 
-  return BOOL_TO_TRISTATE(context->unittype == punittype);
+  return TRI_NO;
 }
 
 /**********************************************************************//**
diff --git a/doc/README.effects b/doc/README.effects
index e00231ab33..7d68e094b9 100644
--- a/doc/README.effects
+++ b/doc/README.effects
@@ -71,7 +71,7 @@ RoadFlag:        Local, Tile, Adjacent, CAdjacent, Traderoute, City
 ExtraFlag:       Local, Tile, Adjacent, CAdjacent, Traderoute, City
 Terrain:         Tile, Adjacent, CAdjacent, Traderoute, City
 Good:            City
-UnitType:        Local
+UnitType:        Local, Tile, CAdjacent, Adjacent
 UnitFlag:        Local
 UnitClass:       Local
 UnitClassFlag:   Local
diff --git a/server/ruleset/rssanity.c b/server/ruleset/rssanity.c
index cf9fc2c5cb..32caddc8bb 100644
--- a/server/ruleset/rssanity.c
+++ b/server/ruleset/rssanity.c
@@ -334,58 +334,57 @@ static bool sanity_check_req_set(rs_conversion_logger logger,
   if (rc > 1 && preq->present) {
     /* Multiple requirements of the same type */
     switch (preq->source.kind) {
-     case VUT_GOVERNMENT:
-     case VUT_UTYPE:
-     case VUT_UCLASS:
-     case VUT_ACTION:
-     case VUT_ACTIVITY:
-     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 */
-     case VUT_MINHP: /* Breaks nothing, but has no sense either */
-     case VUT_MINYEAR:
-     case VUT_MINCALFRAG:
-     case VUT_AI_LEVEL:
-     case VUT_TERRAINALTER: /* Local range only */
-     case VUT_STYLE:
-     case VUT_IMPR_GENUS:
-     case VUT_ORIGINAL_OWNER: /* City range -> only one original owner */
-     case VUT_FORM_AGE:
-       /* There can be only one requirement of these types (with current
-        * range limitations)
-        * Requirements might be identical, but we consider multiple
-        * declarations error anyway. */
-
-       ruleset_error(logger, LOG_ERROR,
-                     "%s: Requirement list has multiple %s requirements",
-                     list_for, universal_type_rule_name(&preq->source));
-       return FALSE;
-       break;
+    case VUT_GOVERNMENT:
+    case VUT_UCLASS:
+    case VUT_ACTION:
+    case VUT_ACTIVITY:
+    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 */
+    case VUT_MINHP: /* Breaks nothing, but has no sense either */
+    case VUT_MINYEAR:
+    case VUT_MINCALFRAG:
+    case VUT_AI_LEVEL:
+    case VUT_TERRAINALTER: /* Local range only */
+    case VUT_STYLE:
+    case VUT_IMPR_GENUS:
+    case VUT_ORIGINAL_OWNER: /* City range -> only one original owner */
+    case VUT_FORM_AGE:
+      /* There can be only one requirement of these types (with current
+       * range limitations)
+       * Requirements might be identical, but we consider multiple
+       * declarations error anyway. */
 
-     case VUT_TERRAIN:
-       /* There can be only up to max_tiles requirements of these types */
-       if (max_tiles != -1 && rc > max_tiles) {
-         ruleset_error(logger, LOG_ERROR,
-                       "%s: Requirement list has more %s requirements than "
-                       "can ever be fulfilled.", list_for,
-                       universal_type_rule_name(&preq->source));
-         return FALSE;
-       }
-       break;
+      ruleset_error(logger, LOG_ERROR,
+                    "%s: Requirement list has multiple %s requirements",
+                    list_for, universal_type_rule_name(&preq->source));
+      return FALSE;
+      break;
 
-     case VUT_TERRAINCLASS:
-       if (rc > 2 || (max_tiles != -1 && rc > max_tiles)) {
-         ruleset_error(logger, LOG_ERROR,
-                       "%s: Requirement list has more %s requirements than "
-                       "can ever be fulfilled.", list_for,
-                       universal_type_rule_name(&preq->source));
-         return FALSE;
-       }
-       break;
+    case VUT_TERRAIN:
+      /* There can be only up to max_tiles requirements of these types */
+      if (max_tiles != -1 && rc > max_tiles) {
+        ruleset_error(logger, LOG_ERROR,
+                      "%s: Requirement list has more %s requirements than "
+                      "can ever be fulfilled.", list_for,
+                      universal_type_rule_name(&preq->source));
+        return FALSE;
+      }
+      break;
+
+    case VUT_TERRAINCLASS:
+      if (rc > 2 || (max_tiles != -1 && rc > max_tiles)) {
+        ruleset_error(logger, LOG_ERROR,
+                      "%s: Requirement list has more %s requirements than "
+                      "can ever be fulfilled.", list_for,
+                      universal_type_rule_name(&preq->source));
+        return FALSE;
+      }
+      break;
 
     case VUT_AGE:
       /* There can be age of the city, unit, and player */
@@ -433,6 +432,7 @@ static bool sanity_check_req_set(rs_conversion_logger logger,
     case VUT_UNITSTATE:
     case VUT_CITYTILE:
     case VUT_GOOD:
+    case VUT_UTYPE:
       /* Can check different properties. */
     case VUT_UTFLAG:
     case VUT_UCFLAG:
-- 
2.43.0

