From 8aa52ca9045fb2f0b28ea903693abefc946af065 Mon Sep 17 00:00:00 2001
From: Marko Lindqvist <cazfi74@gmail.com>
Date: Sun, 20 Apr 2025 08:10:39 +0300
Subject: [PATCH 12/25] Make Upkeep_Factor to consider individual unit's
 properties

See RM #1313

Signed-off-by: Marko Lindqvist <cazfi74@gmail.com>
---
 ai/default/daiair.c         |  3 ++-
 ai/default/daicity.c        |  8 ++++----
 ai/default/daidomestic.c    |  4 ++--
 ai/default/daihunter.c      |  3 ++-
 ai/default/daimilitary.c    |  6 ++++--
 ai/default/daiparadrop.c    |  3 ++-
 client/repodlgs_common.c    |  2 +-
 common/unit.c               | 12 ++++++++++++
 common/unit.h               |  2 ++
 common/unittype.c           | 18 ++++++++++++++++--
 common/unittype.h           |  5 +++--
 doc/README.effects          |  3 +--
 server/citytools.c          |  4 +---
 server/cityturn.c           |  4 ++--
 server/plrhand.c            |  2 +-
 server/savegame/savegame2.c |  2 +-
 server/savegame/savegame3.c |  2 +-
 17 files changed, 57 insertions(+), 26 deletions(-)

diff --git a/ai/default/daiair.c b/ai/default/daiair.c
index 7aa715c03f..309cb7628e 100644
--- a/ai/default/daiair.c
+++ b/ai/default/daiair.c
@@ -618,7 +618,8 @@ bool dai_choose_attacker_air(struct ai_type *ait, const struct civ_map *nmap,
       continue;
     }
 
-    if (!allow_gold_upkeep && utype_upkeep_cost(punittype, pplayer, O_GOLD) > 0) {
+    if (!allow_gold_upkeep
+        && utype_upkeep_cost(punittype, nullptr, pplayer, O_GOLD) > 0) {
       continue;
     }
 
diff --git a/ai/default/daicity.c b/ai/default/daicity.c
index 222ca6b73f..a63c3a0621 100644
--- a/ai/default/daicity.c
+++ b/ai/default/daicity.c
@@ -691,11 +691,11 @@ static void dai_spend_gold(struct ai_type *ait, struct player *pplayer)
 **************************************************************************/
 static int unit_food_upkeep(struct unit *punit)
 {
-  struct player *pplayer = unit_owner(punit);
-  int upkeep = utype_upkeep_cost(unit_type_get(punit), pplayer, O_FOOD);
+  int upkeep = unit_upkeep_cost(punit, O_FOOD);
 
-  if (punit->id != 0 && punit->homecity == 0)
-    upkeep = 0; /* thanks, Peter */
+  if (punit->id != 0 && punit->homecity == 0) {
+    upkeep = 0; /* Thanks, Peter */
+  }
 
   return upkeep;
 }
diff --git a/ai/default/daidomestic.c b/ai/default/daidomestic.c
index 118feaab73..592282410c 100644
--- a/ai/default/daidomestic.c
+++ b/ai/default/daidomestic.c
@@ -515,7 +515,7 @@ struct adv_choice *domestic_advisor_choose_build(struct ai_type *ait,
   }
 
   if (worker_type != NULL
-      && pcity->surplus[O_FOOD] > utype_upkeep_cost(worker_type,
+      && pcity->surplus[O_FOOD] > utype_upkeep_cost(worker_type, nullptr,
                                                     pplayer, O_FOOD)) {
     if (worker_want > 0) {
       CITY_LOG(LOG_DEBUG, pcity,
@@ -559,7 +559,7 @@ struct adv_choice *domestic_advisor_choose_build(struct ai_type *ait,
       / TRAIT_DEFAULT_VALUE;
 
     if (founder_type
-        && pcity->surplus[O_FOOD] >= utype_upkeep_cost(founder_type,
+        && pcity->surplus[O_FOOD] >= utype_upkeep_cost(founder_type, nullptr,
                                                        pplayer, O_FOOD)) {
 
       if (founder_want > choice->want) {
diff --git a/ai/default/daihunter.c b/ai/default/daihunter.c
index 61130c7646..6c11421f80 100644
--- a/ai/default/daihunter.c
+++ b/ai/default/daihunter.c
@@ -91,7 +91,8 @@ static struct unit_type *dai_hunter_guess_best(struct city *pcity,
     struct unit_type_ai *utai = utype_ai_data(ut, ait);
     int desire;
 
-    if (!allow_gold_upkeep && utype_upkeep_cost(ut, pplayer, O_GOLD) > 0) {
+    if (!allow_gold_upkeep && utype_upkeep_cost(ut, nullptr,
+                                                pplayer, O_GOLD) > 0) {
       continue;
     }
 
diff --git a/ai/default/daimilitary.c b/ai/default/daimilitary.c
index fafd02f198..429a15420a 100644
--- a/ai/default/daimilitary.c
+++ b/ai/default/daimilitary.c
@@ -152,7 +152,8 @@ static struct unit_type *dai_choose_attacker(struct ai_type *ait,
   struct player *pplayer = city_owner(pcity);
 
   simple_ai_unit_type_iterate(putype) {
-    if (!allow_gold_upkeep && utype_upkeep_cost(putype, pplayer, O_GOLD) > 0) {
+    if (!allow_gold_upkeep
+        && utype_upkeep_cost(putype, nullptr, pplayer, O_GOLD) > 0) {
       continue;
     }
 
@@ -201,7 +202,8 @@ static struct unit_type *dai_choose_bodyguard(struct ai_type *ait,
       }
     }
 
-    if (!allow_gold_upkeep && utype_upkeep_cost(putype, pplayer, O_GOLD) > 0) {
+    if (!allow_gold_upkeep
+        && utype_upkeep_cost(putype, nullptr, pplayer, O_GOLD) > 0) {
       continue;
     }
 
diff --git a/ai/default/daiparadrop.c b/ai/default/daiparadrop.c
index ccccd2fa47..98f66342d9 100644
--- a/ai/default/daiparadrop.c
+++ b/ai/default/daiparadrop.c
@@ -399,7 +399,8 @@ void dai_choose_paratrooper(struct ai_type *ait, const struct civ_map *nmap,
       continue;
     }
 
-    if (!allow_gold_upkeep && utype_upkeep_cost(u_type, pplayer, O_GOLD) > 0) {
+    if (!allow_gold_upkeep
+        && utype_upkeep_cost(u_type, nullptr, pplayer, O_GOLD) > 0) {
       continue;
     }
 
diff --git a/client/repodlgs_common.c b/client/repodlgs_common.c
index 4393182f8b..f463e0f4a7 100644
--- a/client/repodlgs_common.c
+++ b/client/repodlgs_common.c
@@ -115,7 +115,7 @@ void get_economy_report_units_data(struct unit_entry *entries,
   }
 
   unit_type_iterate(unittype) {
-    cost = utype_upkeep_cost(unittype, client.conn.playing, O_GOLD);
+    cost = utype_upkeep_cost(unittype, nullptr, client_player(), O_GOLD);
 
     if (cost == 0) {
       /* Short-circuit all of the following checks. */
diff --git a/common/unit.c b/common/unit.c
index ff847fc1e7..8b5084be0d 100644
--- a/common/unit.c
+++ b/common/unit.c
@@ -2958,3 +2958,15 @@ enum gen_action activity_default_action(enum unit_activity act)
 
   return act_act[act];
 }
+
+/**********************************************************************//**
+  Get the upkeep of a unit.
+  @param punit Unit to get upkeep for
+  @param otype What kind of upkeep we are interested about
+  @return Upkeep of the specified type
+**************************************************************************/
+int unit_upkeep_cost(const struct unit *punit, Output_type_id otype)
+{
+  return utype_upkeep_cost(unit_type_get(punit), punit, unit_owner(punit),
+                           otype);
+}
diff --git a/common/unit.h b/common/unit.h
index 1733b553ba..b638c2c9a7 100644
--- a/common/unit.h
+++ b/common/unit.h
@@ -548,6 +548,8 @@ int unit_bribe_cost(const struct unit *punit, const struct player *briber,
 int stack_bribe_cost(const struct tile *ptile, const struct player *briber,
                      const struct unit *briber_unit);
 
+int unit_upkeep_cost(const struct unit *punit, Output_type_id otype);
+
 bool unit_transport_load(struct unit *pcargo, struct unit *ptrans,
                          bool force);
 bool unit_transport_unload(struct unit *pcargo);
diff --git a/common/unittype.c b/common/unittype.c
index 7f4bad294a..8aa73e8055 100644
--- a/common/unittype.c
+++ b/common/unittype.c
@@ -129,10 +129,21 @@ const struct unit_type *unit_type_get(const struct unit *punit)
 /**********************************************************************//**
   Returns the upkeep of a unit of this type.
 **************************************************************************/
-int utype_upkeep_cost(const struct unit_type *ut, struct player *pplayer,
+int utype_upkeep_cost(const struct unit_type *ut, const struct unit *punit,
+                      struct player *pplayer,
                       Output_type_id otype)
 {
   int val = ut->upkeep[otype], gold_upkeep_factor;
+  struct tile *ptile;
+  struct city *pcity;
+
+  if (punit != nullptr) {
+    ptile = unit_tile(punit);
+    pcity = game_city_by_number(punit->homecity);
+  } else {
+    ptile = nullptr;
+    pcity = nullptr;
+  }
 
   if (BV_ISSET(ut->flags, UTYF_FANATIC)
       && get_player_bonus(pplayer, EFT_FANATICS) > 0) {
@@ -171,7 +182,10 @@ int utype_upkeep_cost(const struct unit_type *ut, struct player *pplayer,
                                   &(const struct req_context) {
                                     .player   = pplayer,
                                     .output   = get_output_type(otype),
-                                    .unittype = ut
+                                    .unittype = ut,
+                                    .unit     = punit,
+                                    .tile     = ptile,
+                                    .city     = pcity
                                   },
                                   NULL, EFT_UPKEEP_FACTOR);
 
diff --git a/common/unittype.h b/common/unittype.h
index 0c83a6e470..eabfa79748 100644
--- a/common/unittype.h
+++ b/common/unittype.h
@@ -817,8 +817,9 @@ int utype_pop_value(const struct unit_type *punittype, const struct city *pcity)
 enum unit_move_type utype_move_type(const struct unit_type *punittype);
 void set_unit_move_type(struct unit_class *puclass);
 
-/* player related unit functions */
-int utype_upkeep_cost(const struct unit_type *ut, struct player *pplayer,
+/* Player related unit functions */
+int utype_upkeep_cost(const struct unit_type *ut, const struct unit *punit,
+                      struct player *pplayer,
                       Output_type_id otype);
 int utype_happy_cost(const struct unit_type *ut, const struct player *pplayer);
 
diff --git a/doc/README.effects b/doc/README.effects
index 2de4ac02b4..ef889a3fa7 100644
--- a/doc/README.effects
+++ b/doc/README.effects
@@ -816,8 +816,7 @@ Upgrade_Unit
     Upgrade amount obsolete units per turn.
 
 Upkeep_Factor
-    Multiply unit upkeep by amount. This is checked at unit type level,
-    requirements related to individual units do not work.
+    Multiply unit upkeep by amount.
 
 Upkeep_Free
     Improvements with amount or less upkeep cost become free to upkeep (others
diff --git a/server/citytools.c b/server/citytools.c
index 6103250af5..27d35c6fbe 100644
--- a/server/citytools.c
+++ b/server/citytools.c
@@ -3113,13 +3113,11 @@ void building_lost(struct city *pcity, const struct impr_type *pimprove,
 ****************************************************************************/
 void update_unit_upkeep(struct unit *punit, int free_uk[O_LAST])
 {
-  const struct unit_type *ut = unit_type_get(punit);
-  struct player *plr = unit_owner(punit);
   bool update = FALSE;
   int cost;
 
   output_type_iterate(o) {
-    cost = utype_upkeep_cost(ut, plr, o);
+    cost = unit_upkeep_cost(punit, o);
     if (cost > 0) {
       if (free_uk[o] > cost) {
         free_uk[o] -= cost;
diff --git a/server/cityturn.c b/server/cityturn.c
index 18b5c03dab..9c3abdcd77 100644
--- a/server/cityturn.c
+++ b/server/cityturn.c
@@ -2553,7 +2553,7 @@ static bool city_distribute_surplus_shields(struct player *pplayer,
 
   if (pcity->surplus[O_SHIELD] < 0) {
     unit_list_iterate_safe(pcity->units_supported, punit) {
-      if (utype_upkeep_cost(unit_type_get(punit), pplayer, O_SHIELD) > 0
+      if (unit_upkeep_cost(punit, O_SHIELD) > 0
           && pcity->surplus[O_SHIELD] < 0) {
         const char *punit_link = unit_link(punit);
 
@@ -2579,7 +2579,7 @@ static bool city_distribute_surplus_shields(struct player *pplayer,
      * it! If we make it here all normal units are already disbanded, so only
      * undisbandable ones remain. */
     unit_list_iterate_safe(pcity->units_supported, punit) {
-      int upkeep = utype_upkeep_cost(unit_type_get(punit), pplayer, O_SHIELD);
+      int upkeep = unit_upkeep_cost(punit, O_SHIELD);
 
       if (upkeep > 0 && pcity->surplus[O_SHIELD] < 0) {
 
diff --git a/server/plrhand.c b/server/plrhand.c
index a8128ff611..d929b67dd0 100644
--- a/server/plrhand.c
+++ b/server/plrhand.c
@@ -3485,7 +3485,7 @@ void update_national_activities(struct player *pplayer, int old_gold)
       && game.info.homeless_gold_upkeep) {
     unit_list_iterate(pplayer->units, punit) {
       if (is_unit_homeless(punit)) {
-        int gold = utype_upkeep_cost(unit_type_get(punit), pplayer, O_GOLD);
+        int gold = unit_upkeep_cost(punit, O_GOLD);
 
         punit->upkeep[O_GOLD] = gold;
         punit->server.upkeep_paid[O_GOLD] = gold;
diff --git a/server/savegame/savegame2.c b/server/savegame/savegame2.c
index d8bf77123f..fa0465867d 100644
--- a/server/savegame/savegame2.c
+++ b/server/savegame/savegame2.c
@@ -4638,7 +4638,7 @@ static bool sg_load_player_unit(struct loaddata *loading,
    * otherwise these don't get initialized (and AI calculations
    * etc may use junk values). */
   output_type_iterate(o) {
-    punit->upkeep[o] = utype_upkeep_cost(unit_type_get(punit), plr, o);
+    punit->upkeep[o] = unit_upkeep_cost(punit, o);
   } output_type_iterate_end;
 
   punit->action_decision_want
diff --git a/server/savegame/savegame3.c b/server/savegame/savegame3.c
index 9ea250b4d5..8152e5faa7 100644
--- a/server/savegame/savegame3.c
+++ b/server/savegame/savegame3.c
@@ -6374,7 +6374,7 @@ static bool sg_load_player_unit(struct loaddata *loading,
    * otherwise these don't get initialized (and AI calculations
    * etc may use junk values). */
   output_type_iterate(o) {
-    punit->upkeep[o] = utype_upkeep_cost(unit_type_get(punit), plr, o);
+    punit->upkeep[o] = unit_upkeep_cost(punit, o);
   } output_type_iterate_end;
 
   sg_warn_ret_val(secfile_lookup_int(loading->file, &unconverted,
-- 
2.47.2

