From b15cae058850c9696343a907215980fc22bdb987 Mon Sep 17 00:00:00 2001
From: Sveinung Kvilhaugsvik <sveinung84@users.sourceforge.net>
Date: Tue, 23 Feb 2021 11:09:36 +0100
Subject: [PATCH] ruleup: purge duplicate requirements by default.

Duplicate requirements may have crept into a requirement by the ruleset
update code. Purge them from action enablers and from effects unless the
--dirty option is specified.

See osdn #41640
---
 server/ruleset.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++
 server/ruleset.h |   1 +
 tools/ruleup.c   |   6 +++
 3 files changed, 133 insertions(+)

diff --git a/server/ruleset.c b/server/ruleset.c
index 65093d4552..772a64d838 100644
--- a/server/ruleset.c
+++ b/server/ruleset.c
@@ -295,6 +295,132 @@ int ruleset_purge_unused_entities(void)
   return purged;
 }
 
+/**********************************************************************//**
+  Purge the first redundant requirement in a requirement vector.
+  @return TRUE if a requirement was purged.
+**************************************************************************/
+static bool purge_redundant_req_vec(const struct requirement_vector *reqs,
+                                    const char *msg)
+{
+  struct req_vec_problem *problem;
+  bool result;
+
+  problem = req_vec_get_first_redundant_req(reqs, req_vec_vector_number,
+                                            reqs);
+
+  if (problem == NULL) {
+    /* No problem. */
+    return FALSE;
+  }
+
+  if (problem->num_suggested_solutions == 0) {
+    /* No solution. */
+    req_vec_problem_free(problem);
+    return FALSE;
+  }
+
+  if (problem->num_suggested_solutions == 2
+      && problem->suggested_solutions[0].operation == RVCO_REMOVE
+      && problem->suggested_solutions[1].operation == RVCO_REMOVE
+      && are_requirements_equal(&problem->suggested_solutions[0].req,
+                                &problem->suggested_solutions[1].req)) {
+    /* Simple duplication is handled. */
+    log_normal("%s", msg);
+    result = req_vec_change_apply(&problem->suggested_solutions[1],
+                                  req_vec_by_number, reqs);
+    req_vec_problem_free(problem);
+    return result;
+  }
+
+  /* Requirements of different kinds making each other redundant isn't
+   * supported yet. It could be done by always removing the most general
+   * requirement. So unit type is kept when redundant with unit flag, unit
+   * class and unit class flag etc. */
+  req_vec_problem_free(problem);
+  return FALSE;
+}
+
+/**********************************************************************//**
+  Purge redundant requirements from action enablers.
+  @return the number of purged requirements.
+**************************************************************************/
+static int ruleset_purge_redundant_reqs_enablers(void)
+{
+  int purged = 0;
+
+  action_iterate(act_id) {
+    struct action *paction = action_by_number(act_id);
+    char actor_reqs[MAX_LEN_NAME * 2];
+    char target_reqs[MAX_LEN_NAME * 2];
+
+    /* Error log text */
+    fc_snprintf(actor_reqs, sizeof(actor_reqs),
+                "Purged redundant requirement in"
+                " %s in action enabler for %s",
+                "actor_reqs", action_rule_name(paction));
+    fc_snprintf(target_reqs, sizeof(target_reqs),
+                "Purged redundant requirement in"
+                " %s in action enabler for %s",
+                "target_reqs", action_rule_name(paction));
+
+    /* Do the purging. */
+    action_enabler_list_iterate(action_enablers_for_action(paction->id),
+                                ae) {
+      while (!ae->disabled
+             && (purge_redundant_req_vec(&ae->actor_reqs, actor_reqs)
+                 || purge_redundant_req_vec(&ae->target_reqs,
+                                            target_reqs))) {
+        purged++;
+      }
+    } action_enabler_list_iterate_end;
+  } action_iterate_end;
+
+  return purged;
+}
+
+/**********************************************************************//**
+  Purge redundant requirements from effects.
+  @return the number of purged requirements.
+**************************************************************************/
+static int ruleset_purge_redundant_reqs_effects(void)
+{
+  int purged = 0;
+  enum effect_type type;
+
+  for (type = effect_type_begin(); type != effect_type_end();
+       type = effect_type_next(type)) {
+    char msg[MAX_LEN_NAME * 2];
+
+    /* Error log text */
+    fc_snprintf(msg, sizeof(msg),
+                "Purged redundant requirement in effect of type %s",
+                effect_type_name(type));
+
+    /* Do the purging. */
+    effect_list_iterate(get_effects(type), eft) {
+      while (purge_redundant_req_vec(&eft->reqs, msg)) {
+        purged++;
+      }
+    } effect_list_iterate_end;
+  }
+
+  return purged;
+}
+
+/**********************************************************************//**
+  Purge redundant requirement in requirement vectors.
+  @return the number of purged requirements.
+**************************************************************************/
+int ruleset_purge_redundant_reqs(void)
+{
+  int purged = 0;
+
+  purged += ruleset_purge_redundant_reqs_enablers();
+  purged += ruleset_purge_redundant_reqs_effects();
+
+  return purged;
+}
+
 /**********************************************************************//**
   datafilename() wrapper: tries to match in two ways.
   Returns NULL on failure, the (statically allocated) filename on success.
diff --git a/server/ruleset.h b/server/ruleset.h
index f886e3db45..a7e19227ff 100644
--- a/server/ruleset.h
+++ b/server/ruleset.h
@@ -61,6 +61,7 @@ char *get_script_buffer(void);
 char *get_parser_buffer(void);
 
 int ruleset_purge_unused_entities(void);
+int ruleset_purge_redundant_reqs(void);
 
 /* Default ruleset values that are not settings (in game.h) */
 
diff --git a/tools/ruleup.c b/tools/ruleup.c
index 8cbda6df32..e1c7f47009 100644
--- a/tools/ruleup.c
+++ b/tools/ruleup.c
@@ -207,6 +207,12 @@ int main(int argc, char **argv)
         log_normal("Purged %d unused entities after the ruleset upgrade",
                    purged);
       }
+
+      purged = ruleset_purge_redundant_reqs();
+      if (purged > 0) {
+        log_normal("Purged %d redundant requirements after the ruleset"
+                   " upgrade", purged);
+      }
     }
 
     save_ruleset(tgt_dir, game.control.name, &data);
-- 
2.20.1