From 3393ca852954ce184adc31db251411dd0f695db7 Mon Sep 17 00:00:00 2001
From: Marko Lindqvist <cazfi74@gmail.com>
Date: Tue, 7 May 2024 01:47:20 +0300
Subject: [PATCH 05/83] Add improvement flag "Infra"

For improvements that convert production to infrapoints.

See RM #563

Signed-off-by: Marko Lindqvist <cazfi74@gmail.com>
---
 ai/default/daicity.c                 |  2 ++
 client/citydlg_common.c              | 18 +++++++----
 client/cityrepdata.c                 |  3 +-
 client/gui-qt/citydlg.cpp            |  6 ++--
 client/gui-sdl2/wldlg.c              | 45 +++++++++++++++++++---------
 client/gui-sdl3/wldlg.c              | 45 +++++++++++++++++++---------
 common/improvement.c                 |  6 +++-
 data/alien/buildings.ruleset         |  2 ++
 data/civ1/buildings.ruleset          |  2 ++
 data/civ2/buildings.ruleset          |  2 ++
 data/civ2civ3/buildings.ruleset      |  2 ++
 data/classic/buildings.ruleset       |  2 ++
 data/goldkeep/buildings.ruleset      |  2 ++
 data/granularity/buildings.ruleset   |  2 ++
 data/multiplayer/buildings.ruleset   |  2 ++
 data/ruledit/comments-3.3.txt        |  2 ++
 data/sandbox/buildings.ruleset       |  2 ++
 data/stub/buildings.ruleset          |  2 ++
 data/webperimental/buildings.ruleset |  2 ++
 gen_headers/enums/fc_types_enums.def |  2 ++
 server/cityturn.c                    |  2 ++
 server/ruleset/rssanity.c            | 28 ++++++++++++++---
 22 files changed, 138 insertions(+), 43 deletions(-)

diff --git a/ai/default/daicity.c b/ai/default/daicity.c
index 21b5f42685..c5a26e8c5e 100644
--- a/ai/default/daicity.c
+++ b/ai/default/daicity.c
@@ -1633,6 +1633,8 @@ static void adjust_improvement_wants_by_effects(struct ai_type *ait,
      */
     if (improvement_has_flag(pimprove, IF_GOLD)) {
       v += TRADE_WEIGHTING / 10;
+    } else if (improvement_has_flag(pimprove, IF_INFRA)) {
+      v += INFRA_WEIGHTING / 10;
     }
     /* Without relevant flags, base want remains 0. */
   } else {
diff --git a/client/citydlg_common.c b/client/citydlg_common.c
index 113d5d0be8..db18c73c57 100644
--- a/client/citydlg_common.c
+++ b/client/citydlg_common.c
@@ -279,12 +279,16 @@ void get_city_dialog_production(struct city *pcity,
   }
 
   if (city_production_is_genus(pcity, IG_CONVERT)) {
-    if (city_production_has_flag(pcity, IF_GOLD)) {
-      int gold = MAX(0, pcity->surplus[O_SHIELD]);
+    int output = MAX(0, pcity->surplus[O_SHIELD]);
 
+    if (city_production_has_flag(pcity, IF_GOLD)) {
+      fc_snprintf(buffer, buffer_len,
+                  PL_("%3d gold per turn", "%3d gold per turn", output),
+                  output);
+    } else if (city_production_has_flag(pcity, IF_INFRA)) {
       fc_snprintf(buffer, buffer_len,
-                  PL_("%3d gold per turn", "%3d gold per turn", gold),
-                  gold);
+                  PL_("%3d infrapoint per turn", "%3d infrapoints per turn", output),
+                  output);
     } else {
       fc_strlcpy(buffer, "---", buffer_len);
     }
@@ -341,7 +345,8 @@ void get_city_dialog_production_full(char *buffer, size_t buffer_len,
 
     if (is_convert_improvement(target->value.building)) {
       fc_strlcat(buffer, " (--) ", buffer_len);
-      if (improvement_has_flag(target->value.building, IF_GOLD)) {
+      if (improvement_has_flag(target->value.building, IF_GOLD)
+          || improvement_has_flag(target->value.building, IF_INFRA)) {
         cat_snprintf(buffer, buffer_len, _("%d/turn"),
                      MAX(0, pcity->surplus[O_SHIELD]));
       } else {
@@ -452,7 +457,8 @@ void get_city_dialog_production_row(char *buf[], size_t column_size,
     if (VUT_IMPROVEMENT == target->kind
         && is_convert_improvement(target->value.building)) {
       /* Coinage-like improvements: print yield-per-turn instead */
-      if (improvement_has_flag(target->value.building, IF_GOLD)) {
+      if (improvement_has_flag(target->value.building, IF_GOLD)
+          || improvement_has_flag(target->value.building, IF_INFRA)) {
         fc_snprintf(buf[3], column_size, _("%d/turn"),
                     MAX(0, pcity->surplus[O_SHIELD]));
       } else {
diff --git a/client/cityrepdata.c b/client/cityrepdata.c
index cc4699e2a8..09d57cbe76 100644
--- a/client/cityrepdata.c
+++ b/client/cityrepdata.c
@@ -602,7 +602,8 @@ static const char *cr_entry_building(const struct city *pcity,
     gui_options.concise_city_production ? "+" : _("(worklist)");
 
   if (city_production_is_genus(pcity, IG_CONVERT)) {
-    if (city_production_has_flag(pcity, IF_GOLD)) {
+    if (city_production_has_flag(pcity, IF_GOLD)
+        || city_production_has_flag(pcity, IF_INFRA)) {
       fc_snprintf(buf, sizeof(buf), "%s (%d)%s",
                   city_production_name_translation(pcity),
                   MAX(0, pcity->surplus[O_SHIELD]), from_worklist);
diff --git a/client/gui-qt/citydlg.cpp b/client/gui-qt/citydlg.cpp
index 8eb04d2b89..6179827e8e 100644
--- a/client/gui-qt/citydlg.cpp
+++ b/client/gui-qt/citydlg.cpp
@@ -4052,7 +4052,7 @@ void city_production_delegate::paint(QPainter *painter,
   QRect rect2;
   struct sprite *sprite;
   bool useless = false;
-  bool is_coinage = false;
+  bool is_convert = false;
   bool is_neutral = false;
   bool is_sea = false;
   bool is_flying = false;
@@ -4118,7 +4118,7 @@ void city_production_delegate::paint(QPainter *painter,
     name = improvement_name_translation(target->value.building);
     sprite = get_building_sprite(tileset, target->value.building);
     useless = is_improvement_redundant(pcity, target->value.building);
-    is_coinage = improvement_has_flag(target->value.building, IF_GOLD);
+    is_convert = (target->value.building->genus == IG_CONVERT);
   }
 
   if (sprite != nullptr) {
@@ -4157,7 +4157,7 @@ void city_production_delegate::paint(QPainter *painter,
     QItemDelegate::drawDecoration(painter, option, option.rect, pix_dec);
   }
 
-  if (is_coinage) {
+  if (is_convert) {
     pix_dec.fill(QColor(255, 255, 0, 70));
     QItemDelegate::drawDecoration(painter, option, option.rect, pix_dec);
   }
diff --git a/client/gui-sdl2/wldlg.c b/client/gui-sdl2/wldlg.c
index 9dd9972c5d..dac9288580 100644
--- a/client/gui-sdl2/wldlg.c
+++ b/client/gui-sdl2/wldlg.c
@@ -945,14 +945,20 @@ static void refresh_production_label(int stock)
                                          editor->currently_building, &cost);
 
   if (convert_prod) {
+    int output = MAX(0, editor->pcity->surplus[O_SHIELD]);
+
     if (improvement_has_flag(editor->currently_building.value.building,
                              IF_GOLD)) {
-      int gold = MAX(0, editor->pcity->surplus[O_SHIELD]);
-
       fc_snprintf(cbuf, sizeof(cbuf),
                   PL_("%s\n%d gold per turn",
-                      "%s\n%d gold per turn", gold),
-                  name, gold);
+                      "%s\n%d gold per turn", output),
+                  name, output);
+    } else if (improvement_has_flag(editor->currently_building.value.building,
+                                    IF_INFRA)) {
+      fc_snprintf(cbuf, sizeof(cbuf),
+                  PL_("%s\n%d infrapoint per turn",
+                      "%s\n%d infrapoints per turn", output),
+                  name, output);
     } else {
       fc_snprintf(cbuf, sizeof(cbuf), "%s\n-", name);
     }
@@ -1155,13 +1161,18 @@ void popup_worklist_editor(struct city *pcity, struct global_worklist *gwl)
     count = city_production_build_shield_cost(pcity);
 
     if (convert_prod) {
-      if (city_production_has_flag(pcity, IF_GOLD)) {
-        int gold = MAX(0, pcity->surplus[O_SHIELD]);
+      int output = MAX(0, pcity->surplus[O_SHIELD]);
 
+      if (city_production_has_flag(pcity, IF_GOLD)) {
         fc_snprintf(cbuf, sizeof(cbuf),
                     PL_("%s\n%d gold per turn",
-                        "%s\n%d gold per turn", gold),
-                    name, gold);
+                        "%s\n%d gold per turn", output),
+                    name, output);
+      } else if (city_production_has_flag(pcity, IF_INFRA)) {
+        fc_snprintf(cbuf, sizeof(cbuf),
+                    PL_("%s\n%d infrapoint per turn",
+                        "%s\n%d infrapoints per turn", output),
+                    name, output);
       } else {
         fc_snprintf(cbuf, sizeof(cbuf), "%s\n-", name);
       }
@@ -1486,19 +1497,23 @@ void popup_worklist_editor(struct city *pcity, struct global_worklist *gwl)
             }
           }
         } else {
-          /* coinage-like */
-          if (improvement_has_flag(pimprove, IF_GOLD)) {
-            int gold = MAX(0, pcity->surplus[O_SHIELD]);
+          int output = MAX(0, pcity->surplus[O_SHIELD]);
 
+          /* Coinage-like */
+          if (improvement_has_flag(pimprove, IF_GOLD)) {
             fc_snprintf(cbuf, sizeof(cbuf), PL_("%d gold per turn",
-                                                "%d gold per turn", gold),
-                        gold);
+                                                "%d gold per turn", output),
+                        output);
+          } else if (improvement_has_flag(pimprove, IF_INFRA)) {
+            fc_snprintf(cbuf, sizeof(cbuf), PL_("%d infrapoint per turn",
+                                                "%d infrapoints per turn", output),
+                        output);
           } else {
             fc_strlcpy(cbuf, "-", sizeof(cbuf));
           }
         }
       } else {
-        /* non city mode */
+        /* Non city mode */
         if (!is_convert_improvement(pimprove)) {
           int cost = impr_build_shield_cost(NULL, pimprove);
 
@@ -1514,6 +1529,8 @@ void popup_worklist_editor(struct city *pcity, struct global_worklist *gwl)
         } else {
           if (improvement_has_flag(pimprove, IF_GOLD)) {
             fc_strlcpy(cbuf, _("shields into gold"), sizeof(cbuf));
+          } else if (improvement_has_flag(pimprove, IF_INFRA)) {
+            fc_strlcpy(cbuf, _("shields into infrapoints"), sizeof(cbuf));
           } else {
             fc_strlcpy(cbuf, "-", sizeof(cbuf));
           }
diff --git a/client/gui-sdl3/wldlg.c b/client/gui-sdl3/wldlg.c
index 1312b08e08..8bbf3bd1b4 100644
--- a/client/gui-sdl3/wldlg.c
+++ b/client/gui-sdl3/wldlg.c
@@ -941,14 +941,20 @@ static void refresh_production_label(int stock)
                                          editor->currently_building, &cost);
 
   if (convert_prod) {
+    int output = MAX(0, editor->pcity->surplus[O_SHIELD]);
+
     if (improvement_has_flag(editor->currently_building.value.building,
                              IF_GOLD)) {
-      int gold = MAX(0, editor->pcity->surplus[O_SHIELD]);
-
       fc_snprintf(cbuf, sizeof(cbuf),
                   PL_("%s\n%d gold per turn",
-                      "%s\n%d gold per turn", gold),
-                  name, gold);
+                      "%s\n%d gold per turn", output),
+                  name, output);
+    } else if (improvement_has_flag(editor->currently_building.value.building,
+                                    IF_INFRA)) {
+      fc_snprintf(cbuf, sizeof(cbuf),
+                  PL_("%s\n%d infrapoint per turn",
+                      "%s\n%d infrapoints per turn", output),
+                  name, output);
     } else {
       fc_snprintf(cbuf, sizeof(cbuf), "%s\n-", name);
     }
@@ -1151,13 +1157,18 @@ void popup_worklist_editor(struct city *pcity, struct global_worklist *gwl)
     count = city_production_build_shield_cost(pcity);
 
     if (convert_prod) {
-      if (city_production_has_flag(pcity, IF_GOLD)) {
-        int gold = MAX(0, pcity->surplus[O_SHIELD]);
+      int output = MAX(0, pcity->surplus[O_SHIELD]);
 
+      if (city_production_has_flag(pcity, IF_GOLD)) {
         fc_snprintf(cbuf, sizeof(cbuf),
                     PL_("%s\n%d gold per turn",
-                        "%s\n%d gold per turn", gold),
-                    name, gold);
+                        "%s\n%d gold per turn", output),
+                    name, output);
+      } else if (city_production_has_flag(pcity, IF_INFRA)) {
+        fc_snprintf(cbuf, sizeof(cbuf),
+                    PL_("%s\n%d infrapoint per turn",
+                        "%s\n%d infrapoints per turn", output),
+                    name, output);
       } else {
         fc_snprintf(cbuf, sizeof(cbuf), "%s\n-", name);
       }
@@ -1482,19 +1493,23 @@ void popup_worklist_editor(struct city *pcity, struct global_worklist *gwl)
             }
           }
         } else {
-          /* coinage-like */
-          if (improvement_has_flag(pimprove, IF_GOLD)) {
-            int gold = MAX(0, pcity->surplus[O_SHIELD]);
+          int output = MAX(0, pcity->surplus[O_SHIELD]);
 
+          /* Coinage-like */
+          if (improvement_has_flag(pimprove, IF_GOLD)) {
             fc_snprintf(cbuf, sizeof(cbuf), PL_("%d gold per turn",
-                                                "%d gold per turn", gold),
-                        gold);
+                                                "%d gold per turn", output),
+                        output);
+          } else if (improvement_has_flag(pimprove, IF_INFRA)) {
+            fc_snprintf(cbuf, sizeof(cbuf), PL_("%d infrapoint per turn",
+                                                "%d infrapoints per turn", output),
+                        output);
           } else {
             fc_strlcpy(cbuf, "-", sizeof(cbuf));
           }
         }
       } else {
-        /* non city mode */
+        /* Non city mode */
         if (!is_convert_improvement(pimprove)) {
           int cost = impr_build_shield_cost(NULL, pimprove);
 
@@ -1510,6 +1525,8 @@ void popup_worklist_editor(struct city *pcity, struct global_worklist *gwl)
         } else {
           if (improvement_has_flag(pimprove, IF_GOLD)) {
             fc_strlcpy(cbuf, _("shields into gold"), sizeof(cbuf));
+          } else if (improvement_has_flag(pimprove, IF_INFRA)) {
+            fc_strlcpy(cbuf, _("shields into infrapoints"), sizeof(cbuf));
           } else {
             fc_strlcpy(cbuf, "-", sizeof(cbuf));
           }
diff --git a/common/improvement.c b/common/improvement.c
index 12004daafc..662e85afcd 100644
--- a/common/improvement.c
+++ b/common/improvement.c
@@ -622,6 +622,7 @@ bool is_improvement_productive(const struct city *pcity,
 {
     return (!improvement_obsolete(city_owner(pcity), pimprove, pcity)
             && (improvement_has_flag(pimprove, IF_GOLD)
+                || improvement_has_flag(pimprove, IF_INFRA)
                 || improvement_has_side_effects(pcity, pimprove)
                 || improvement_has_effects(pcity, pimprove)));
 }
@@ -633,7 +634,7 @@ bool is_improvement_productive(const struct city *pcity,
    - all of its effects (if any) are provided by other means, or it's
      obsolete (and thus assumed to have no effect); and
    - it's not enabling the city to build some kind of units; and
-   - it's not Coinage (IF_GOLD).
+   - it's not convert production (IF_GOLD or IF_INFRA).
   (Note that it's not impossible that this improvement could become useful
   if circumstances changed, say if a new government enabled the building
   of its special units.)
@@ -645,6 +646,9 @@ bool is_improvement_redundant(const struct city *pcity,
   if (improvement_has_flag(pimprove, IF_GOLD)) {
     return FALSE;
   }
+  if (improvement_has_flag(pimprove, IF_INFRA)) {
+    return FALSE;
+  }
 
   /* If an improvement has side effects, don't claim it's redundant. */
   if (improvement_has_side_effects(pcity, pimprove)) {
diff --git a/data/alien/buildings.ruleset b/data/alien/buildings.ruleset
index a6a58c8508..7847ed0016 100644
--- a/data/alien/buildings.ruleset
+++ b/data/alien/buildings.ruleset
@@ -73,6 +73,8 @@ format_version = 40
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/data/civ1/buildings.ruleset b/data/civ1/buildings.ruleset
index 660322971c..b91ad329a9 100644
--- a/data/civ1/buildings.ruleset
+++ b/data/civ1/buildings.ruleset
@@ -71,6 +71,8 @@ building_flags =
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/data/civ2/buildings.ruleset b/data/civ2/buildings.ruleset
index 9805a65b51..dc11b82476 100644
--- a/data/civ2/buildings.ruleset
+++ b/data/civ2/buildings.ruleset
@@ -73,6 +73,8 @@ building_flags =
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/data/civ2civ3/buildings.ruleset b/data/civ2civ3/buildings.ruleset
index b833c5330f..8f65343444 100644
--- a/data/civ2civ3/buildings.ruleset
+++ b/data/civ2civ3/buildings.ruleset
@@ -73,6 +73,8 @@ building_flags =
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/data/classic/buildings.ruleset b/data/classic/buildings.ruleset
index 76710cb87d..43c1a1795c 100644
--- a/data/classic/buildings.ruleset
+++ b/data/classic/buildings.ruleset
@@ -73,6 +73,8 @@ building_flags =
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/data/goldkeep/buildings.ruleset b/data/goldkeep/buildings.ruleset
index ff77aa6136..b4f7a90b74 100644
--- a/data/goldkeep/buildings.ruleset
+++ b/data/goldkeep/buildings.ruleset
@@ -75,6 +75,8 @@ building_flags =
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/data/granularity/buildings.ruleset b/data/granularity/buildings.ruleset
index 29b27162c7..9bb7a63b66 100644
--- a/data/granularity/buildings.ruleset
+++ b/data/granularity/buildings.ruleset
@@ -72,6 +72,8 @@ format_version = 40
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/data/multiplayer/buildings.ruleset b/data/multiplayer/buildings.ruleset
index dd5c6439c4..679d8ba769 100644
--- a/data/multiplayer/buildings.ruleset
+++ b/data/multiplayer/buildings.ruleset
@@ -72,6 +72,8 @@ building_flags =
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/data/ruledit/comments-3.3.txt b/data/ruledit/comments-3.3.txt
index 87218b0e0e..15ad0f7e0e 100644
--- a/data/ruledit/comments-3.3.txt
+++ b/data/ruledit/comments-3.3.txt
@@ -65,6 +65,8 @@ buildings = "\
 ; \"Gold\"             = Not a real building; production turned into gold\n\
 ;                      indefinitely (capitalization/coinage).\n\
 ;                      Genus should be \"Convert\".\n\
+; \"Infra\"            = Not a real building; production turned into infrapoints\n\
+;                      indefinitely. Genus should be \"Convert\".\n\
 ; \"DisasterProof\"    = Disasters never destroy this building. Is meaningful\n\
 ;                      only for genus \"Improvement\" buildings as others are\n\
 ;                      automatically disaster proof.\n\
diff --git a/data/sandbox/buildings.ruleset b/data/sandbox/buildings.ruleset
index 22b7cc978e..9726b81a6c 100644
--- a/data/sandbox/buildings.ruleset
+++ b/data/sandbox/buildings.ruleset
@@ -73,6 +73,8 @@ building_flags =
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/data/stub/buildings.ruleset b/data/stub/buildings.ruleset
index 9c8446017c..416ca1ebc0 100644
--- a/data/stub/buildings.ruleset
+++ b/data/stub/buildings.ruleset
@@ -63,6 +63,8 @@ format_version = 40
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/data/webperimental/buildings.ruleset b/data/webperimental/buildings.ruleset
index 0cec1caf79..286170030a 100644
--- a/data/webperimental/buildings.ruleset
+++ b/data/webperimental/buildings.ruleset
@@ -73,6 +73,8 @@ building_flags =
 ; "Gold"             = Not a real building; production turned into gold
 ;                      indefinitely (capitalization/coinage).
 ;                      Genus should be "Convert".
+; "Infra"            = Not a real building; production turned into infrapoints
+;                      indefinitely. Genus should be "Convert".
 ; "DisasterProof"    = Disasters never destroy this building. Is meaningful
 ;                      only for genus "Improvement" buildings as others are
 ;                      automatically disaster proof.
diff --git a/gen_headers/enums/fc_types_enums.def b/gen_headers/enums/fc_types_enums.def
index 48713c2164..ef7b744983 100644
--- a/gen_headers/enums/fc_types_enums.def
+++ b/gen_headers/enums/fc_types_enums.def
@@ -12,6 +12,8 @@ values
   SAVE_SMALL_WONDER  "SaveSmallWonder"
   /* When built, gives gold */
   GOLD               "Gold"
+  /* When built, gives infrapoints */
+  INFRA              "Infra"
   /* Never destroyed by disasters */
   DISASTER_PROOF     "DisasterProof"
   /* Never destroyed by a surgical strike */
diff --git a/server/cityturn.c b/server/cityturn.c
index f4c67de5c4..bea071e0da 100644
--- a/server/cityturn.c
+++ b/server/cityturn.c
@@ -2624,6 +2624,8 @@ static bool city_build_building(struct player *pplayer, struct city *pcity)
      * this turn. */
     if (city_production_has_flag(pcity, IF_GOLD)) {
       pplayer->economic.gold += pcity->before_change_shields;
+    } else if (city_production_has_flag(pcity, IF_INFRA)) {
+      pplayer->economic.infra_points += pcity->before_change_shields;
     }
 
     pcity->before_change_shields = 0;
diff --git a/server/ruleset/rssanity.c b/server/ruleset/rssanity.c
index 671f2d79e2..6cf43712bb 100644
--- a/server/ruleset/rssanity.c
+++ b/server/ruleset/rssanity.c
@@ -755,14 +755,34 @@ static bool rs_buildings(rs_conversion_logger logger)
 {
   /* Special Genus */
   improvement_re_active_iterate(pimprove) {
-    if (improvement_has_flag(pimprove, IF_GOLD)
-        && pimprove->genus != IG_CONVERT) {
+    if (improvement_has_flag(pimprove, IF_GOLD)) {
+      if (pimprove->genus != IG_CONVERT) {
+        ruleset_error(logger, LOG_ERROR,
+                      _("Gold producing improvement %s with genus other than \"Convert\""),
+                      improvement_rule_name(pimprove));
+
+        return FALSE;
+      }
+      if (improvement_has_flag(pimprove, IF_INFRA)) {
+        ruleset_error(logger, LOG_ERROR,
+                      _("The same improvement has both \"Gold\" and \"Infra\" flags"));
+        return FALSE;
+      }
+    } else if (improvement_has_flag(pimprove, IF_INFRA)) {
+      if (pimprove->genus != IG_CONVERT) {
+        ruleset_error(logger, LOG_ERROR,
+                      _("Infrapoints producing improvement %s with genus other than \"Convert\""),
+                      improvement_rule_name(pimprove));
+
+        return FALSE;
+      }
+    } else if (pimprove->genus == IG_CONVERT) {
       ruleset_error(logger, LOG_ERROR,
-                    _("Gold producing improvement %s with genus other than \"Convert\""),
+                    _("Improvement %s with no conversion target with genus \"Convert\""),
                     improvement_rule_name(pimprove));
-
       return FALSE;
     }
+
     if (improvement_has_flag(pimprove, IF_DISASTER_PROOF)
         && pimprove->genus != IG_IMPROVEMENT) {
       ruleset_error(logger, LOG_ERROR,
-- 
2.43.0

