From 55421e7c372ada68589beb03d7f27e49a60cdb1b Mon Sep 17 00:00:00 2001
From: Marko Lindqvist <cazfi74@gmail.com>
Date: Tue, 30 Jan 2024 19:26:02 +0200
Subject: [PATCH 04/21] AI: Stop attempts to recover Helicopter HP in the open

They would be losing HP instead.

See RM #207

Signed-off-by: Marko Lindqvist <cazfi74@gmail.com>
---
 ai/default/daiunit.c | 27 +++++++++++-----------
 common/unit.c        | 54 ++++++++++++++++++++++++++++++++++++++------
 common/unit.h        |  3 ++-
 server/unittools.c   | 38 ++++++-------------------------
 4 files changed, 70 insertions(+), 52 deletions(-)

diff --git a/ai/default/daiunit.c b/ai/default/daiunit.c
index fb16538b4b..619bcdab8f 100644
--- a/ai/default/daiunit.c
+++ b/ai/default/daiunit.c
@@ -2427,24 +2427,24 @@ static void dai_manage_hitpoint_recovery(struct ai_type *ait,
 
   CHECK_UNIT(punit);
 
-  if (pcity) {
-    /* rest in city until the hitpoints are recovered, but attempt
-       to protect city from attack (and be opportunistic too)*/
+  if (pcity != NULL) {
+    /* Rest in the city until the hitpoints are recovered, but attempt
+     * to protect city from attack (and be opportunistic too)*/
     if (dai_military_rampage(punit, RAMPAGE_ANYTHING, 
                              RAMPAGE_FREE_CITY_OR_BETTER)) {
       UNIT_LOG(LOGLEVEL_RECOVERY, punit, "recovering hit points.");
     } else {
-      return; /* we died heroically defending our city */
+      return; /* We died heroically defending our city */
     }
   } else {
-    /* goto to nearest city to recover hit points */
-    /* just before, check to see if we can occupy an undefended enemy city */
+    /* Goto to nearest city to recover hit points */
+    /* Just before, check to see if we can occupy an undefended enemy city */
     if (!dai_military_rampage(punit, RAMPAGE_FREE_CITY_OR_BETTER, 
                               RAMPAGE_FREE_CITY_OR_BETTER)) { 
-      return; /* oops, we died */
+      return; /* Oops, we died */
     }
 
-    /* find city to stay and go there */
+    /* Find a city to stay and go there */
     safe = find_nearest_safe_city(punit);
     if (safe) {
       UNIT_LOG(LOGLEVEL_RECOVERY, punit, "going to %s to recover",
@@ -2454,7 +2454,7 @@ static void dai_manage_hitpoint_recovery(struct ai_type *ait,
         return;
       }
     } else {
-      /* oops */
+      /* Oops */
       UNIT_LOG(LOGLEVEL_RECOVERY, punit, "didn't find a city to recover in!");
       dai_unit_new_task(ait, punit, AIUNIT_NONE, NULL);
       dai_military_attack(ait, pplayer, punit);
@@ -2462,14 +2462,15 @@ static void dai_manage_hitpoint_recovery(struct ai_type *ait,
     }
   }
 
-  /* is the unit still damaged? if true recover hit points, if not idle */
-  if (punit->hp == punittype->hp) {
-    /* we are ready to go out and kick ass again */
+  /* Is the unit still damaged? If true, and could recover hit points, do so.
+   * Don't wait if would be losing hitpoints that way! */
+  if (punit->hp == punittype->hp || unit_gain_hitpoints(punit) <= 0) {
+    /* We are ready to go out and kick ass again */
     UNIT_LOG(LOGLEVEL_RECOVERY, punit, "ready to kick ass again!");
     dai_unit_new_task(ait, punit, AIUNIT_NONE, NULL);  
     return;
   } else {
-    def_ai_unit_data(punit, ait)->done = TRUE; /* sit tight */
+    def_ai_unit_data(punit, ait)->done = TRUE; /* Sit tight */
   }
 }
 
diff --git a/common/unit.c b/common/unit.c
index 595b7eb179..37cdd0c024 100644
--- a/common/unit.c
+++ b/common/unit.c
@@ -2148,14 +2148,9 @@ int unit_pays_mp_for_action(const struct action *paction,
 }
 
 /**********************************************************************//**
-  returns how many hp's a unit will gain standing still on current tile
-  depends on whether or not it's inside city or fortress.
-  barracks will regen landunits completely
-  airports will regen airunits  completely
-  ports    will regen navalunits completely
-  fortify will add a little extra.
+  Returns how many hp's a unit will gain standing still on current tile.
 **************************************************************************/
-int hp_gain_coord(struct unit *punit)
+int hp_gain_coord(const struct unit *punit)
 {
   int hp = 0;
   const int base = unit_type_get(punit)->hp;
@@ -2173,6 +2168,51 @@ int hp_gain_coord(struct unit *punit)
   return MAX(hp, 0);
 }
 
+/**********************************************************************//**
+  How many hitpoints does unit recover?
+**************************************************************************/
+int unit_gain_hitpoints(const struct unit *punit)
+{
+  const struct unit_type *utype = unit_type_get(punit);
+  struct unit_class *pclass = unit_class_get(punit);
+  struct city *pcity = tile_city(unit_tile(punit));
+  int gain;
+
+  if (!punit->moved) {
+    gain = hp_gain_coord(punit);
+  } else {
+    gain = 0;
+  }
+
+  gain += get_unit_bonus(punit, EFT_UNIT_RECOVER);
+
+  if (!punit->homecity && 0 < game.server.killunhomed
+      && !unit_has_type_flag(punit, UTYF_GAMELOSS)) {
+    /* Hit point loss of units without homecity; at least 1 hp! */
+    /* Gameloss units are immune to this effect. */
+    int hp_loss = MAX(utype->hp * game.server.killunhomed / 100, 1);
+
+    if (gain > hp_loss) {
+      gain = -1;
+    } else {
+      gain -= hp_loss;
+    }
+  }
+
+  if (pcity == NULL && !tile_has_native_base(unit_tile(punit), utype)
+      && !unit_transported(punit)) {
+    gain -= utype->hp * pclass->hp_loss_pct / 100;
+  }
+
+  if (punit->hp + gain > utype->hp) {
+    gain = utype->hp - punit->hp;
+  } else if (punit->hp + gain < 0) {
+    gain = -punit->hp;
+  }
+
+  return gain;
+}
+
 /**********************************************************************//**
   Does unit lose hitpoints each turn?
 **************************************************************************/
diff --git a/common/unit.h b/common/unit.h
index c916e5d34f..af9381a955 100644
--- a/common/unit.h
+++ b/common/unit.h
@@ -510,7 +510,8 @@ bool unit_can_convert(const struct civ_map *nmap, const struct unit *punit);
 int unit_pays_mp_for_action(const struct action *paction,
                             const struct unit *punit);
 
-int hp_gain_coord(struct unit *punit);
+int hp_gain_coord(const struct unit *punit);
+int unit_gain_hitpoints(const struct unit *punit);
 bool is_losing_hp(const struct unit *punit);
 bool unit_type_is_losing_hp(const struct player *pplayer,
                             const struct unit_type *punittype);
diff --git a/server/unittools.c b/server/unittools.c
index 0f5238d68d..72069ac82f 100644
--- a/server/unittools.c
+++ b/server/unittools.c
@@ -618,7 +618,7 @@ void player_restore_units(struct player *pplayer)
 }
 
 /**********************************************************************//**
-  add hitpoints to the unit, hp_gain_coord returns the amount to add
+  Add hitpoints to the unit, hp_gain_coord returns the amount to add
   united nations will speed up the process by 2 hp's / turn, means helicopters
   will actually not lose hp's every turn if player have that wonder.
   Units which have moved don't gain hp, except the United Nations and
@@ -630,45 +630,21 @@ void player_restore_units(struct player *pplayer)
 static void unit_restore_hitpoints(struct unit *punit)
 {
   bool was_lower;
-  int save_hp;
-  struct unit_class *pclass = unit_class_get(punit);
-  struct city *pcity = tile_city(unit_tile(punit));
-
-  was_lower = (punit->hp < unit_type_get(punit)->hp);
-  save_hp = punit->hp;
-
-  if (!punit->moved) {
-    punit->hp += hp_gain_coord(punit);
-  }
+  const struct unit_type *utype = unit_type_get(punit);
 
-  /* Bonus recovery HP (traditionally from the United Nations) */
-  punit->hp += get_unit_bonus(punit, EFT_UNIT_RECOVER);
+  was_lower = (punit->hp < utype->hp);
 
-  if (!punit->homecity && 0 < game.server.killunhomed
-      && !unit_has_type_flag(punit, UTYF_GAMELOSS)) {
-    /* Hit point loss of units without homecity; at least 1 hp! */
-    /* Gameloss units are immune to this effect. */
-    int hp_loss = MAX(unit_type_get(punit)->hp * game.server.killunhomed / 100,
-                      1);
-    punit->hp = MIN(punit->hp - hp_loss, save_hp - 1);
-  }
+  punit->hp += unit_gain_hitpoints(punit);
 
-  if (!pcity && !tile_has_native_base(unit_tile(punit), unit_type_get(punit))
-      && !unit_transported(punit)) {
-    punit->hp -= unit_type_get(punit)->hp * pclass->hp_loss_pct / 100;
-  }
+  fc_assert(punit->hp >= 0);
+  fc_assert(punit->hp <= utype->hp);
 
-  if (punit->hp >= unit_type_get(punit)->hp) {
-    punit->hp = unit_type_get(punit)->hp;
+  if (punit->hp == utype->hp) {
     if (was_lower && punit->activity == ACTIVITY_SENTRY) {
       set_unit_activity(punit, ACTIVITY_IDLE);
     }
   }
 
-  if (punit->hp < 0) {
-    punit->hp = 0;
-  }
-
   punit->moved = FALSE;
   punit->paradropped = FALSE;
 }
-- 
2.43.0

