From 227afd02cc77ca696132ce1fde5a82b47d0dcb43 Mon Sep 17 00:00:00 2001
From: Marko Lindqvist <cazfi74@gmail.com>
Date: Sun, 25 Feb 2024 05:25:05 +0200
Subject: [PATCH 41/41] Add Tech_Leakage enablement effect

See RM #277

Signed-off-by: Marko Lindqvist <cazfi74@gmail.com>
---
 ai/default/daieffects.c            |  25 +++++
 common/effects.h                   |  18 ++--
 common/research.c                  | 158 +++++++++++++++--------------
 common/tech.c                      |  10 +-
 data/alien/effects.ruleset         |   4 +
 data/civ1/effects.ruleset          |   4 +
 data/civ2/effects.ruleset          |   4 +
 data/civ2civ3/effects.ruleset      |   4 +
 data/classic/effects.ruleset       |   4 +
 data/goldkeep/effects.ruleset      |   4 +
 data/granularity/effects.ruleset   |   4 +
 data/multiplayer/effects.ruleset   |   4 +
 data/sandbox/effects.ruleset       |   4 +
 data/webperimental/effects.ruleset |   4 +
 doc/README.effects                 |   5 +
 server/ruleset/rscompat.c          |   4 +
 16 files changed, 172 insertions(+), 88 deletions(-)

diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c
index 2ca58d55be..eec2e5debd 100644
--- a/ai/default/daieffects.c
+++ b/ai/default/daieffects.c
@@ -630,6 +630,31 @@ adv_want dai_effect_value(struct player *pplayer,
   case EFT_TECH_COST_FACTOR:
     v -= amount * 50;
     break;
+  case EFT_TECH_LEAKAGE:
+    {
+      int leak_val = 0;
+
+      switch (game.info.tech_leakage) {
+      case TECH_LEAKAGE_NONE:
+        break;
+      case TECH_LEAKAGE_EMBASSIES:
+        leak_val = (normal_player_count() - 1) * 2;
+        break;
+      case TECH_LEAKAGE_PLAYERS:
+        leak_val = (normal_player_count() - 1) * 5;
+        break;
+      case TECH_LEAKAGE_NO_BARBS:
+        leak_val = (normal_player_count() - 1) * 5 + 2 * 3;
+        break;
+      }
+
+      if (amount > 0 && get_player_bonus(pplayer, EFT_TECH_LEAKAGE) <= 0) {
+        v += leak_val;
+      } else if (amount < 0) {
+        v -= leak_val;
+      }
+    }
+    break;
   case EFT_IMPR_BUILD_COST_PCT:
   case EFT_UNIT_BUILD_COST_PCT:
     v -= amount * 30;
diff --git a/common/effects.h b/common/effects.h
index 8075de0ec3..ff0f4a0290 100644
--- a/common/effects.h
+++ b/common/effects.h
@@ -340,15 +340,17 @@ struct multiplier;
 #define SPECENUM_VALUE137NAME "Surplus_Waste_Pct"
 #define SPECENUM_VALUE138 EFT_SURPLUS_WASTE_PCT_BY_REL_DISTANCE
 #define SPECENUM_VALUE138NAME "Surplus_Waste_Pct_By_Rel_Distance"
+#define SPECENUM_VALUE139 EFT_TECH_LEAKAGE
+#define SPECENUM_VALUE139NAME "Tech_Leakage"
 /* Ruleset specific effects for use from Lua scripts */
-#define SPECENUM_VALUE139 EFT_USER_EFFECT_1
-#define SPECENUM_VALUE139NAME "User_Effect_1"
-#define SPECENUM_VALUE140 EFT_USER_EFFECT_2
-#define SPECENUM_VALUE140NAME "User_Effect_2"
-#define SPECENUM_VALUE141 EFT_USER_EFFECT_3
-#define SPECENUM_VALUE141NAME "User_Effect_3"
-#define SPECENUM_VALUE142 EFT_USER_EFFECT_4
-#define SPECENUM_VALUE142NAME "User_Effect_4"
+#define SPECENUM_VALUE140 EFT_USER_EFFECT_1
+#define SPECENUM_VALUE140NAME "User_Effect_1"
+#define SPECENUM_VALUE141 EFT_USER_EFFECT_2
+#define SPECENUM_VALUE141NAME "User_Effect_2"
+#define SPECENUM_VALUE142 EFT_USER_EFFECT_3
+#define SPECENUM_VALUE142NAME "User_Effect_3"
+#define SPECENUM_VALUE143 EFT_USER_EFFECT_4
+#define SPECENUM_VALUE143NAME "User_Effect_4"
 /* Keep this last */
 #define SPECENUM_COUNT EFT_COUNT
 #include "specenum_gen.h"
diff --git a/common/research.c b/common/research.c
index 866a841293..01f60fad40 100644
--- a/common/research.c
+++ b/common/research.c
@@ -871,7 +871,7 @@ int research_total_bulbs_required(const struct research *presearch,
   enum tech_cost_style tech_cost_style = game.info.tech_cost_style;
   int members;
   double base_cost, total_cost;
-  double leak = 0.0;
+  bool leakage = FALSE;
 
   if (valid_advance_by_number(tech) == NULL) {
     return 0;
@@ -925,6 +925,9 @@ int research_total_bulbs_required(const struct research *presearch,
     members++;
     total_cost += (base_cost
                    * get_player_bonus(pplayer, EFT_TECH_COST_FACTOR));
+    if (!leakage && get_player_bonus(pplayer, EFT_TECH_LEAKAGE)) {
+      leakage = TRUE;
+    }
   } research_players_iterate_end;
   if (0 == members) {
     /* There is no more alive players for this research, no need to apply
@@ -933,93 +936,98 @@ int research_total_bulbs_required(const struct research *presearch,
   }
   base_cost = total_cost / members;
 
-  fc_assert_msg(tech_leakage_style_is_valid(game.info.tech_leakage),
-                "Invalid tech_leakage %d", game.info.tech_leakage);
-  switch (game.info.tech_leakage) {
-  case TECH_LEAKAGE_NONE:
-    /* no change */
-    break;
+  if (leakage) {
+    double leak = 0.0;
 
-  case TECH_LEAKAGE_EMBASSIES:
-    {
-      int players = 0, players_with_tech_and_embassy = 0;
+    fc_assert_msg(tech_leakage_style_is_valid(game.info.tech_leakage),
+                  "Invalid tech_leakage %d", game.info.tech_leakage);
+
+    switch (game.info.tech_leakage) {
+    case TECH_LEAKAGE_NONE:
+      /* No change */
+      break;
 
-      players_iterate_alive(aplayer) {
-        const struct research *aresearch = research_get(aplayer);
+    case TECH_LEAKAGE_EMBASSIES:
+      {
+        int players = 0, players_with_tech_and_embassy = 0;
 
-        players++;
-        if (aresearch == presearch
-            || (A_FUTURE == tech
-                ? aresearch->future_tech <= presearch->future_tech
-                : TECH_KNOWN != research_invention_state(aresearch, tech))) {
-          continue;
-        }
+        players_iterate_alive(aplayer) {
+          const struct research *aresearch = research_get(aplayer);
 
-        research_players_iterate(presearch, pplayer) {
-          if (player_has_embassy(pplayer, aplayer)) {
-            players_with_tech_and_embassy++;
-            break;
+          players++;
+          if (aresearch == presearch
+              || (A_FUTURE == tech
+                  ? aresearch->future_tech <= presearch->future_tech
+                  : TECH_KNOWN != research_invention_state(aresearch, tech))) {
+            continue;
           }
-        } research_players_iterate_end;
-      } players_iterate_alive_end;
 
-      fc_assert_ret_val(0 < players, base_cost);
-      fc_assert(players >= players_with_tech_and_embassy);
-      leak = base_cost * players_with_tech_and_embassy
-             * game.info.tech_leak_pct / players / 100;
-    }
-    break;
+          research_players_iterate(presearch, pplayer) {
+            if (player_has_embassy(pplayer, aplayer)) {
+              players_with_tech_and_embassy++;
+              break;
+            }
+          } research_players_iterate_end;
+        } players_iterate_alive_end;
+
+        fc_assert_ret_val(0 < players, base_cost);
+        fc_assert(players >= players_with_tech_and_embassy);
+        leak = base_cost * players_with_tech_and_embassy
+          * game.info.tech_leak_pct / players / 100;
+      }
+      break;
 
-  case TECH_LEAKAGE_PLAYERS:
-    {
-      int players = 0, players_with_tech = 0;
-
-      players_iterate_alive(aplayer) {
-        players++;
-        if (A_FUTURE == tech
-            ? research_get(aplayer)->future_tech > presearch->future_tech
-            : TECH_KNOWN == research_invention_state(research_get(aplayer),
-                                                     tech)) {
-          players_with_tech++;
-        }
-      } players_iterate_alive_end;
+    case TECH_LEAKAGE_PLAYERS:
+      {
+        int players = 0, players_with_tech = 0;
+
+        players_iterate_alive(aplayer) {
+          players++;
+          if (A_FUTURE == tech
+              ? research_get(aplayer)->future_tech > presearch->future_tech
+              : TECH_KNOWN == research_invention_state(research_get(aplayer),
+                                                       tech)) {
+            players_with_tech++;
+          }
+        } players_iterate_alive_end;
 
-      fc_assert_ret_val(0 < players, base_cost);
-      fc_assert(players >= players_with_tech);
-      leak = base_cost * players_with_tech * game.info.tech_leak_pct
-             / players / 100;
-    }
-    break;
+        fc_assert_ret_val(0 < players, base_cost);
+        fc_assert(players >= players_with_tech);
+        leak = base_cost * players_with_tech * game.info.tech_leak_pct
+          / players / 100;
+      }
+      break;
 
-  case TECH_LEAKAGE_NO_BARBS:
-    {
-      int players = 0, players_with_tech = 0;
+    case TECH_LEAKAGE_NO_BARBS:
+      {
+        int players = 0, players_with_tech = 0;
 
-      players_iterate_alive(aplayer) {
-        if (is_barbarian(aplayer)) {
-          continue;
-        }
-        players++;
-        if (A_FUTURE == tech
-            ? research_get(aplayer)->future_tech > presearch->future_tech
-            : TECH_KNOWN == research_invention_state(research_get(aplayer),
-                                                     tech)) {
-          players_with_tech++;
-        }
-      } players_iterate_alive_end;
+        players_iterate_alive(aplayer) {
+          if (is_barbarian(aplayer)) {
+            continue;
+          }
+          players++;
+          if (A_FUTURE == tech
+              ? research_get(aplayer)->future_tech > presearch->future_tech
+              : TECH_KNOWN == research_invention_state(research_get(aplayer),
+                                                       tech)) {
+            players_with_tech++;
+          }
+        } players_iterate_alive_end;
 
-      fc_assert_ret_val(0 < players, base_cost);
-      fc_assert(players >= players_with_tech);
-      leak = base_cost * players_with_tech * game.info.tech_leak_pct
-             / players / 100;
+        fc_assert_ret_val(0 < players, base_cost);
+        fc_assert(players >= players_with_tech);
+        leak = base_cost * players_with_tech * game.info.tech_leak_pct
+          / players / 100;
+      }
+      break;
     }
-    break;
-  }
 
-  if (leak > base_cost) {
-    base_cost = 0.0;
-  } else {
-    base_cost -= leak;
+    if (leak > base_cost) {
+      base_cost = 0.0;
+    } else {
+      base_cost -= leak;
+    }
   }
 
   /* Assign a science penalty to the AI at easier skill levels. This code
diff --git a/common/tech.c b/common/tech.c
index d1bea7dd9a..5d0042f79b 100644
--- a/common/tech.c
+++ b/common/tech.c
@@ -440,12 +440,12 @@ const char *tech_flag_helptxt(enum tech_flag_id id)
 }
 
 /**********************************************************************//**
- Returns true if the costs for the given technology will stay constant
- during the game. False otherwise.
+  Returns true if the costs for the given technology will stay constant
+  during the game. False otherwise.
 
- Checking every tech_cost_style with fixed costs seems a waste of system
- resources, when we can check that it is not the one style without fixed
- costs.
+  Checking every tech_cost_style with fixed costs seems a waste of system
+  resources, when we can check that it is not the one style without fixed
+  costs.
 **************************************************************************/
 bool techs_have_fixed_costs(void)
 {
diff --git a/data/alien/effects.ruleset b/data/alien/effects.ruleset
index 1facafaf65..4fe6c48395 100644
--- a/data/alien/effects.ruleset
+++ b/data/alien/effects.ruleset
@@ -721,6 +721,10 @@ value   = 1
 type    = "Tech_Cost_Factor"
 value   = 1
 
+[effect_tech_leakage]
+type    = "Tech_Leakage"
+value   = 1
+
 [effect_mood_center]
 type    = "Make_Content"
 value	= 3
diff --git a/data/civ1/effects.ruleset b/data/civ1/effects.ruleset
index d0089b6d58..96d7783f7b 100644
--- a/data/civ1/effects.ruleset
+++ b/data/civ1/effects.ruleset
@@ -1256,6 +1256,10 @@ reqs    =
       "MinYear", "1", "World"
     }
 
+[effect_tech_leakage]
+type    = "Tech_Leakage"
+value   = 1
+
 ; Cities can always work tiles
 [effect_tile_workable]
 type    = "Tile_Workable"
diff --git a/data/civ2/effects.ruleset b/data/civ2/effects.ruleset
index 0fb2f6fefb..b8e476ed4c 100644
--- a/data/civ2/effects.ruleset
+++ b/data/civ2/effects.ruleset
@@ -2260,6 +2260,10 @@ reqs    =
       "MinYear", "1", "World"
     }
 
+[effect_tech_leakage]
+type    = "Tech_Leakage"
+value   = 1
+
 ; Cities can always work tiles
 [effect_tile_workable]
 type    = "Tile_Workable"
diff --git a/data/civ2civ3/effects.ruleset b/data/civ2civ3/effects.ruleset
index 12cb331cf8..784dd942b4 100644
--- a/data/civ2civ3/effects.ruleset
+++ b/data/civ2civ3/effects.ruleset
@@ -3790,6 +3790,10 @@ value   = 3
 type    = "Tech_Cost_Factor"
 value   = 3
 
+[effect_tech_leakage]
+type    = "Tech_Leakage"
+value   = 1
+
 ; Cities can always work tiles
 [effect_tile_workable]
 type    = "Tile_Workable"
diff --git a/data/classic/effects.ruleset b/data/classic/effects.ruleset
index 426f571b70..cae07651a5 100644
--- a/data/classic/effects.ruleset
+++ b/data/classic/effects.ruleset
@@ -2283,6 +2283,10 @@ reqs    =
 type    = "Tech_Cost_Factor"
 value   = 1
 
+[effect_tech_leakage]
+type    = "Tech_Leakage"
+value   = 1
+
 ; Cities can always work tiles
 [effect_tile_workable]
 type    = "Tile_Workable"
diff --git a/data/goldkeep/effects.ruleset b/data/goldkeep/effects.ruleset
index 76c0cc1425..0fc2993f76 100644
--- a/data/goldkeep/effects.ruleset
+++ b/data/goldkeep/effects.ruleset
@@ -2504,6 +2504,10 @@ reqs    =
 type    = "Tech_Cost_Factor"
 value   = 1
 
+[effect_tech_leakage]
+type    = "Tech_Leakage"
+value   = 1
+
 ; Cities can always work tiles
 [effect_tile_workable]
 type    = "Tile_Workable"
diff --git a/data/granularity/effects.ruleset b/data/granularity/effects.ruleset
index 13df83827e..939138c348 100644
--- a/data/granularity/effects.ruleset
+++ b/data/granularity/effects.ruleset
@@ -88,6 +88,10 @@ value   = 50
 type    = "Tech_Cost_Factor"
 value   = 1
 
+[effect_tech_leakage]
+type    = "Tech_Leakage"
+value   = 1
+
 [effect_tile_land_workable]
 type    = "Tile_Workable"
 value   = 1
diff --git a/data/multiplayer/effects.ruleset b/data/multiplayer/effects.ruleset
index 6970378876..90c958ec67 100644
--- a/data/multiplayer/effects.ruleset
+++ b/data/multiplayer/effects.ruleset
@@ -2312,6 +2312,10 @@ reqs    =
 type    = "Tech_Cost_Factor"
 value   = 1
 
+[effect_tech_leakage]
+type    = "Tech_Leakage"
+value   = 1
+
 ; Cities can always work tiles
 [effect_tile_workable]
 type    = "Tile_Workable"
diff --git a/data/sandbox/effects.ruleset b/data/sandbox/effects.ruleset
index 1bfd714e8a..41d6dbb54e 100644
--- a/data/sandbox/effects.ruleset
+++ b/data/sandbox/effects.ruleset
@@ -4003,6 +4003,10 @@ value   = 3
 type    = "Tech_Cost_Factor"
 value   = 3
 
+[effect_tech_leakage]
+type    = "Tech_Leakage"
+value   = 1
+
 ; Cities can always work tiles
 [effect_tile_workable]
 type    = "Tile_Workable"
diff --git a/data/webperimental/effects.ruleset b/data/webperimental/effects.ruleset
index d6546e73ca..3b9a8e3db7 100644
--- a/data/webperimental/effects.ruleset
+++ b/data/webperimental/effects.ruleset
@@ -2309,6 +2309,10 @@ reqs    =
 type    = "Tech_Cost_Factor"
 value   = 1
 
+[effect_tech_leakage]
+type    = "Tech_Leakage"
+value   = 1
+
 ; Cities can always work tiles
 [effect_tile_workable]
 type    = "Tile_Workable"
diff --git a/doc/README.effects b/doc/README.effects
index 46845d2b3e..9d515806ed 100644
--- a/doc/README.effects
+++ b/doc/README.effects
@@ -670,6 +670,11 @@ Surplus_Waste_Pct_By_Rel_Distance
 Tech_Cost_Factor
     Factor for research costs.
 
+Tech_Leakage
+    If value is positive, tech leakage towards the player is enabled.
+    The amount of leakage is controlled by the game.ruleset setting "tech_leakage",
+    and can be even zero.
+
 Tech_Parasite
     Gain any advance known already by amount number of other teams,
     if team_pooled_research is enabled, or amount number of other players
diff --git a/server/ruleset/rscompat.c b/server/ruleset/rscompat.c
index 7bc7c9cbec..3674ecf216 100644
--- a/server/ruleset/rscompat.c
+++ b/server/ruleset/rscompat.c
@@ -419,6 +419,10 @@ void rscompat_postprocess(struct rscompat_info *info)
    * the new effects from being upgraded by accident. */
   iterate_effect_cache(effect_list_compat_cb, info);
 
+  /* Old hardcoded behavior always had tech leakage enabled,
+   * thought limited by game.info.tech_leakage setting. */
+  effect_new(EFT_TECH_LEAKAGE, 1, nullptr);
+
   /* Make sure that all action enablers added or modified by the
    * compatibility post processing fulfills all hard action requirements. */
   rscompat_enablers_add_obligatory_hard_reqs();
-- 
2.43.0

