From 64c38f3c8cfe78654d7a7de00d17c5792a8ed6eb Mon Sep 17 00:00:00 2001
From: Marko Lindqvist <cazfi74@gmail.com>
Date: Tue, 10 Jun 2025 08:03:47 +0300
Subject: [PATCH 89/89] Add support for government user flags

There's no other kind of government flags. This adds
all of the government flags support.

Flags are not yet transferred to the client.
None of the supplied rulesets set any government flags.
The flags do nothing as of now, they just exist.

See RM #1432

Signed-off-by: Marko Lindqvist <cazfi74@gmail.com>
---
 common/government.c                    | 86 ++++++++++++++++++++++++++
 common/government.h                    | 20 +++++-
 common/improvement.c                   |  5 +-
 data/ruledit/comments-3.4.txt          |  9 +++
 gen_headers/enums/Makefile.am          |  2 +
 gen_headers/enums/government_enums.def | 19 ++++++
 meson.build                            |  1 +
 server/ruleset/ruleload.c              | 30 +++++++++
 tools/ruleutil/comments.c              | 11 ++++
 tools/ruleutil/comments.h              |  1 +
 tools/ruleutil/rulesave.c              | 22 +++++++
 11 files changed, 202 insertions(+), 4 deletions(-)
 create mode 100644 gen_headers/enums/government_enums.def

diff --git a/common/government.c b/common/government.c
index 5d37a3c367..3c1a0bc13a 100644
--- a/common/government.c
+++ b/common/government.c
@@ -33,6 +33,8 @@
 
 struct government *governments = NULL;
 
+static struct user_flag user_gov_flags[MAX_NUM_USER_GOVERNMENT_FLAGS];
+
 /**********************************************************************//**
   Returns the government that has the given (translated) name.
   Returns NULL if none match.
@@ -571,5 +573,89 @@ bool untargeted_revolution_allowed(void)
      * in order to know how long anarchy will last. */
     return FALSE;
   }
+
   return TRUE;
 }
+
+/**********************************************************************//**
+  Return TRUE if the government has this flag, otherwise FALSE
+**************************************************************************/
+bool government_has_flag(const struct government *pgov,
+                         enum gov_flag_id flag)
+{
+  fc_assert_ret_val(gov_flag_id_is_valid(flag), FALSE);
+
+  return BV_ISSET(pgov->flags, flag);
+}
+
+
+/************************************************************************//**
+  Initialize user government flags.
+****************************************************************************/
+void user_gov_flags_init(void)
+{
+  int i;
+
+  for (i = 0; i < MAX_NUM_USER_GOVERNMENT_FLAGS; i++) {
+    user_flag_init(&user_gov_flags[i]);
+  }
+}
+
+/************************************************************************//**
+  Frees the memory associated with all government flags
+****************************************************************************/
+void gov_flags_free(void)
+{
+  int i;
+
+  for (i = 0; i < MAX_NUM_USER_GOVERNMENT_FLAGS; i++) {
+    user_flag_free(&user_gov_flags[i]);
+  }
+}
+
+/************************************************************************//**
+  Sets user defined name for government flag.
+****************************************************************************/
+void set_user_gov_flag_name(enum gov_flag_id id, const char *name,
+                            const char *helptxt)
+{
+  int gfid = id - GOVF_USER_FLAG_1;
+
+  fc_assert_ret(id >= GOVF_USER_FLAG_1 && id <= GOVF_LAST_USER_FLAG);
+
+  free(user_gov_flags[gfid].name);
+  user_gov_flags[gfid].name = nullptr;
+
+  if (name && name[0] != '\0') {
+    user_gov_flags[gfid].name = fc_strdup(name);
+  }
+
+  free(user_gov_flags[gfid].helptxt);
+  user_gov_flags[gfid].helptxt = nullptr;
+
+  if (helptxt && helptxt[0] != '\0') {
+    user_gov_flags[gfid].helptxt = fc_strdup(helptxt);
+  }
+}
+
+/************************************************************************//**
+  Government flag name callback, called from specenum code.
+****************************************************************************/
+const char *gov_flag_id_name_cb(enum gov_flag_id flag)
+{
+  if (flag < GOVF_USER_FLAG_1 || flag > GOVF_LAST_USER_FLAG) {
+    return nullptr;
+  }
+
+  return user_gov_flags[flag - GOVF_USER_FLAG_1].name;
+}
+
+/************************************************************************//**
+  Return the (untranslated) help text of the user government flag.
+****************************************************************************/
+const char *gov_flag_helptxt(enum gov_flag_id id)
+{
+  fc_assert(id >= GOVF_USER_FLAG_1 && id <= GOVF_LAST_USER_FLAG);
+
+  return user_gov_flags[id - GOVF_USER_FLAG_1].helptxt;
+}
diff --git a/common/government.h b/common/government.h
index a7e0161378..754f1d18d3 100644
--- a/common/government.h
+++ b/common/government.h
@@ -25,6 +25,10 @@ extern "C" {
 #include "name_translation.h"
 #include "requirements.h"
 
+#include "government_enums_gen.h"
+
+#define GOVF_LAST_USER_FLAG GOVF_USER_FLAG_8
+#define MAX_NUM_USER_GOVERNMENT_FLAGS (GOVF_LAST_USER_FLAG - GOVF_USER_FLAG_1 + 1)
 
 struct strvec;          /* Actually defined in "utility/string_vector.h". */
 
@@ -47,7 +51,7 @@ struct ruler_title;     /* Opaque type. */
  * protocol, which generally uses government_count()). */
 #define G_LAST (127)
 
-/* This is struct government itself.  All information about a form of
+/* This is struct government itself. All information about a form of
  * government is contained inhere. -- SKi */
 struct government {
   Government_type_id item_number;
@@ -62,11 +66,12 @@ struct government {
   struct requirement_vector reqs;
   struct ruler_title_hash *ruler_titles;
   int changed_to_times;
+  bv_gov_flags flags;
   struct strvec *helptext;
 
   /* AI cached data for this government. */
   struct {
-    struct government *better; /* hint: a better government (or NULL) */
+    struct government *better; /* Hint: A better government (or nullptr) */
   } ai;
 };
 
@@ -136,6 +141,17 @@ struct iterator *government_iter_init(struct government_iter *it);
 
 bool untargeted_revolution_allowed(void);
 
+/* General government flag accessor routines */
+bool government_has_flag(const struct government *pgov,
+                         enum gov_flag_id flag);
+
+void user_gov_flags_init(void);
+void gov_flags_free(void);
+void set_user_gov_flag_name(enum gov_flag_id id,
+                            const char *name,
+                            const char *helptxt);
+const char *gov_flag_helptxt(enum gov_flag_id id);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/common/improvement.c b/common/improvement.c
index 9b752fcc78..8ec30ac0a0 100644
--- a/common/improvement.c
+++ b/common/improvement.c
@@ -378,12 +378,13 @@ struct impr_type *improvement_by_rule_name(const char *name)
 }
 
 /**********************************************************************//**
-  Return TRUE if the impr has this flag otherwise FALSE
+  Return TRUE if the impr has this flag, otherwise FALSE
 **************************************************************************/
 bool improvement_has_flag(const struct impr_type *pimprove,
 			  enum impr_flag_id flag)
 {
   fc_assert_ret_val(impr_flag_id_is_valid(flag), FALSE);
+
   return BV_ISSET(pimprove->flags, flag);
 }
 
@@ -1190,7 +1191,7 @@ void impr_flags_free(void)
   Sets user defined name for building flag.
 ****************************************************************************/
 void set_user_impr_flag_name(enum impr_flag_id id, const char *name,
-                              const char *helptxt)
+                             const char *helptxt)
 {
   int bfid = id - IF_USER_FLAG_1;
 
diff --git a/data/ruledit/comments-3.4.txt b/data/ruledit/comments-3.4.txt
index 7b6d89a304..bb2b4a5e1d 100644
--- a/data/ruledit/comments-3.4.txt
+++ b/data/ruledit/comments-3.4.txt
@@ -1365,6 +1365,15 @@ building = "\
 ;                 (optional)\n\
 "
 
+government = "\
+; Names for custom government flags. There can be up to 8 of these.\n\
+; name          = rule name; In some circumstances user may see this\n\
+;                 as part of some sentences, so try to make it descriptive\n\
+;                 and sensible.\n\
+; helptxt       = displayed in the help for government types with this flag\n\
+;                 (optional)\n\
+"
+
 [sectiondoc]
 ; This section has comments documenting other sections
 
diff --git a/gen_headers/enums/Makefile.am b/gen_headers/enums/Makefile.am
index 0a93471c8e..285434f88f 100644
--- a/gen_headers/enums/Makefile.am
+++ b/gen_headers/enums/Makefile.am
@@ -5,6 +5,7 @@ ENUMS_GEN = \
 		city_enums_gen.h	\
 		effects_enums_gen.h	\
 		fc_types_enums_gen.h	\
+		government_enums_gen.h	\
 		manual_enums_gen.h	\
 		terrain_enums_gen.h
 
@@ -13,6 +14,7 @@ EXTRA_DIST = \
 		city_enums.def		\
 		effects_enums.def	\
 		fc_types_enums.def	\
+		government_enums.def	\
 		manual_enums.def	\
 		terrain_enums.def	\
 		$(ENUMS_GEN)
diff --git a/gen_headers/enums/government_enums.def b/gen_headers/enums/government_enums.def
new file mode 100644
index 0000000000..62a45af39d
--- /dev/null
+++ b/gen_headers/enums/government_enums.def
@@ -0,0 +1,19 @@
+# Specenum definitions for common/government.h
+# See gen_headers/generate_enums.py for syntax
+
+/* Used in the network protocol. */
+enum gov_flag_id
+  prefix GOVF_
+  name-override
+  bitvector bv_gov_flags
+  count
+values
+  USER_FLAG_1
+  USER_FLAG_2
+  USER_FLAG_3
+  USER_FLAG_4
+  USER_FLAG_5
+  USER_FLAG_6
+  USER_FLAG_7
+  USER_FLAG_8
+end
diff --git a/meson.build b/meson.build
index 11a26683e8..790f404b0c 100644
--- a/meson.build
+++ b/meson.build
@@ -1212,6 +1212,7 @@ enum_defs = {
     'city_enums': ['city_enums.def', 'city_enums_gen.h'],
     'effects_enums': ['effects_enums.def', 'effects_enums_gen.h'],
     'fc_types_enums': ['fc_types_enums.def', 'fc_types_enums_gen.h'],
+    'government_enums': ['government_enums.def', 'government_enums_gen.h'],
     'terrain_enums': ['terrain_enums.def', 'terrain_enums_gen.h'],
   },
   'manual': {
diff --git a/server/ruleset/ruleload.c b/server/ruleset/ruleload.c
index 0924a8a90f..fbbff29bff 100644
--- a/server/ruleset/ruleload.c
+++ b/server/ruleset/ruleload.c
@@ -4301,6 +4301,8 @@ static bool load_government_names(struct section_file *file,
   struct section_list *sec;
   const char *filename = secfile_name(file);
   bool ok = TRUE;
+  const char *flag;
+  int i;
 
   if (!rscompat_check_cap_and_version(file, filename, compat)) {
     return FALSE;
@@ -4368,6 +4370,34 @@ static bool load_government_names(struct section_file *file,
     }
   }
 
+  /* User government flag names */
+  for (i = 0;
+       (flag = secfile_lookup_str_default(file, nullptr,
+                                          "control.government_flags%d.name",
+                                          i));
+       i++) {
+    const char *helptxt = secfile_lookup_str_default(file, nullptr,
+        "control.government_flags%d.helptxt", i);
+
+    if (gov_flag_id_by_name(flag, fc_strcasecmp)
+        != gov_flag_id_invalid()) {
+      ruleset_error(nullptr, LOG_ERROR,
+                    "\"%s\": Duplicate government flag name '%s'",
+                    filename, flag);
+      ok = FALSE;
+      break;
+    }
+    if (i > MAX_NUM_USER_GOVERNMENT_FLAGS) {
+      ruleset_error(NULL, LOG_ERROR,
+                    "\"%s\": Too many user government flags!",
+                    filename);
+      ok = FALSE;
+      break;
+    }
+
+    set_user_gov_flag_name(GOVF_USER_FLAG_1 + i, flag, helptxt);
+  }
+
   section_list_destroy(sec);
 
   return ok;
diff --git a/tools/ruleutil/comments.c b/tools/ruleutil/comments.c
index f317391732..e34bd9faf7 100644
--- a/tools/ruleutil/comments.c
+++ b/tools/ruleutil/comments.c
@@ -51,6 +51,7 @@ static struct {
   char *uflags_extra;
   char *uflags_tech;
   char *uflags_building;
+  char *uflags_government;
   char *trade_settings;
   char *goods;
   char *actions;
@@ -174,6 +175,8 @@ bool comments_load(void)
                comment_file, "uflag_types.tech");
   comment_load(comments_storage.uflags_building,
                comment_file, "uflag_types.building");
+  comment_load(comments_storage.uflags_government,
+               comment_file, "uflag_types.government");
   comment_load(comments_storage.trade_settings,
                comment_file, "typedoc.trade_settings");
   comment_load(comments_storage.goods, comment_file, "typedoc.goods");
@@ -511,6 +514,14 @@ void comment_uflags_building(struct section_file *sfile)
   comment_entry_write(sfile, comments_storage.uflags_building, "control");
 }
 
+/**********************************************************************//**
+  Write header for government user flags.
+**************************************************************************/
+void comment_uflags_government(struct section_file *sfile)
+{
+  comment_entry_write(sfile, comments_storage.uflags_government, "control");
+}
+
 /**********************************************************************//**
   Write trade settings header.
 **************************************************************************/
diff --git a/tools/ruleutil/comments.h b/tools/ruleutil/comments.h
index e20e8809a8..58ead74170 100644
--- a/tools/ruleutil/comments.h
+++ b/tools/ruleutil/comments.h
@@ -63,6 +63,7 @@ void comment_uflags_terrain(struct section_file *sfile);
 void comment_uflags_extra(struct section_file *sfile);
 void comment_uflags_tech(struct section_file *sfile);
 void comment_uflags_building(struct section_file *sfile);
+void comment_uflags_government(struct section_file *sfile);
 
 /* Other section comments */
 void comment_trade_settings(struct section_file *sfile);
diff --git a/tools/ruleutil/rulesave.c b/tools/ruleutil/rulesave.c
index c7e68bb31a..55bc706614 100644
--- a/tools/ruleutil/rulesave.c
+++ b/tools/ruleutil/rulesave.c
@@ -1916,11 +1916,33 @@ static bool save_governments_ruleset(const char *filename, const char *name)
 {
   struct section_file *sfile = create_ruleset_file(name, "government");
   int sect_idx;
+  bool uflags_government = FALSE;
+  int i;
 
   if (sfile == nullptr) {
     return FALSE;
   }
 
+  for (i = 0; i < MAX_NUM_USER_GOVERNMENT_FLAGS; i++) {
+    const char *flagname = gov_flag_id_name_cb(i + GOVF_USER_FLAG_1);
+    const char *helptxt = gov_flag_helptxt(i + GOVF_USER_FLAG_1);
+
+    if (flagname != nullptr) {
+      if (!uflags_government) {
+        comment_uflags_government(sfile);
+        uflags_government = TRUE;
+      }
+
+      secfile_insert_str(sfile, flagname, "control.government_flags%d.name", i);
+
+      /* Save the user flag help text even when it is undefined. That makes
+       * the formatting code happy. The resulting "" is ignored when the
+       * ruleset is loaded. */
+      secfile_insert_str(sfile, helptxt,
+                         "control.government_flags%d.helptxt", i);
+    }
+  }
+
   save_gov_ref(sfile, game.government_during_revolution, "governments",
                "during_revolution");
 
-- 
2.47.2

