From 780dd4c54a6254d9cc918949a56331f615500a87 Mon Sep 17 00:00:00 2001
From: Ihnatus <ignatus31oct@mail.ru>
Date: Mon, 7 Jul 2025 01:03:13 +0300
Subject: [PATCH] Correct airlift action possibility check

Don't airlift on non-allied unit tile.
Consider airlift to ally cities not blocked by counters if it is nowhere.

See RM#1564

Signed-off-by: Ihnatus <ignatus31oct@mail.ru>
---
 common/unit.c      | 65 +++++++++++++++++++++++++---------------------
 server/unittools.c |  2 +-
 2 files changed, 37 insertions(+), 30 deletions(-)

diff --git a/common/unit.c b/common/unit.c
index 8b5084be0d..73d9a7c39f 100644
--- a/common/unit.c
+++ b/common/unit.c
@@ -85,6 +85,7 @@ enum unit_airlift_result
 {
   const struct city *psrc_city = tile_city(unit_tile(punit));
   const struct player *punit_owner;
+  struct tile *dst_tile = nullptr;
   enum unit_airlift_result ok_result = AR_OK;
 
   if (0 == punit->moves_left
@@ -103,7 +104,7 @@ enum unit_airlift_result
     return AR_OCCUPIED;
   }
 
-  if (NULL == psrc_city) {
+  if (nullptr == psrc_city) {
     /* No city there. */
     return AR_NOT_IN_CITY;
   }
@@ -113,13 +114,16 @@ enum unit_airlift_result
     return AR_BAD_DST_CITY;
   }
 
-  if (pdest_city
-      && (NULL == restriction
-          || (tile_get_known(city_tile(pdest_city), restriction)
-              == TILE_KNOWN_SEEN))
-      && !can_unit_exist_at_tile(nmap, punit, city_tile(pdest_city))) {
-    /* Can't exist at the destination tile. */
-    return AR_BAD_DST_CITY;
+  if (nullptr != pdest_city) {
+    dst_tile = city_tile(pdest_city);
+
+    if ((nullptr == restriction
+         || (tile_get_known(dst_tile, restriction)
+             == TILE_KNOWN_SEEN))
+        && !can_unit_exist_at_tile(nmap, punit, dst_tile)) {
+      /* Can't exist at the destination tile. */
+      return AR_BAD_DST_CITY;
+    }
   }
 
   punit_owner = unit_owner(punit);
@@ -134,35 +138,38 @@ enum unit_airlift_result
     return AR_BAD_SRC_CITY;
   }
 
-  if (pdest_city
+  if (nullptr != pdest_city
       && punit_owner != city_owner(pdest_city)
-      && !(game.info.airlifting_style & AIRLIFTING_ALLIED_DEST
-           && pplayers_allied(punit_owner, city_owner(pdest_city)))) {
+      && (!(game.info.airlifting_style & AIRLIFTING_ALLIED_DEST
+            && pplayers_allied(punit_owner, city_owner(pdest_city)))
+          || is_non_allied_unit_tile(dst_tile, punit_owner,
+                                     unit_has_type_flag(punit, UTYF_FLAGLESS)))) {
     /* Not allowed to airlift to this destination. */
     return AR_BAD_DST_CITY;
   }
 
-  if (NULL == restriction || city_owner(psrc_city) == restriction) {
-    /* We know for sure whether or not src can airlift this turn. */
-    if (0 >= psrc_city->airlift
-        && (!(game.info.airlifting_style & AIRLIFTING_UNLIMITED_SRC)
-            || !game.info.airlift_from_always_enabled)) {
-      /* The source cannot airlift for this turn (maybe already airlifted
-       * or no airport).
-       * See also do_airline() in server/unittools.h. */
-      return AR_SRC_NO_FLIGHTS;
-    } /* else, there is capacity; continue to other checks */
-  } else {
-    /* We don't have access to the 'airlift' field. Assume it's OK; can
-     * only find out for sure by trying it. */
-    ok_result = AR_OK_SRC_UNKNOWN;
+  /* Test airlift counters */
+  if (!game.info.airlift_from_always_enabled) {
+    if (nullptr == restriction || city_owner(psrc_city) == restriction) {
+      /* We know for sure whether or not src can airlift this turn. */
+      if (0 >= psrc_city->airlift
+          && !(game.info.airlifting_style & AIRLIFTING_UNLIMITED_SRC)) {
+        /* The source cannot airlift for this turn (maybe already airlifted
+         * or no airport).
+         * See also do_airline() in server/unittools.h. */
+        return AR_SRC_NO_FLIGHTS;
+      } /* else, there is capacity; continue to other checks */
+    } else {
+      /* We don't have access to the 'airlift' field. Assume it's OK; can
+       * only find out for sure by trying it. */
+      ok_result = AR_OK_SRC_UNKNOWN;
+    }
   }
 
-  if (pdest_city) {
-    if (NULL == restriction || city_owner(pdest_city) == restriction) {
+  if (nullptr != pdest_city && !game.info.airlift_to_always_enabled) {
+    if (nullptr == restriction || city_owner(pdest_city) == restriction) {
       if (0 >= pdest_city->airlift
-          && (!(game.info.airlifting_style & AIRLIFTING_UNLIMITED_DEST)
-              || !game.info.airlift_to_always_enabled)) {
+          && !(game.info.airlifting_style & AIRLIFTING_UNLIMITED_DEST)) {
         /* The destination cannot support airlifted units for this turn
          * (maybe already airlifted or no airport).
          * See also do_airline() in server/unittools.h. */
diff --git a/server/unittools.c b/server/unittools.c
index 8e0cf8d8e3..50ea65a4c8 100644
--- a/server/unittools.c
+++ b/server/unittools.c
@@ -4072,7 +4072,7 @@ static struct unit_move_data_list *construct_move_data_list(struct unit *punit,
 /**********************************************************************//**
   Moves a unit. No checks whatsoever! This is meant as a practical
   function for other functions, like do_airline(), which do the checking
-  themselves.
+  either themselves or by their callers via is_action_possible()
 
   If you move a unit you should always use this function, as it also sets
   the transport status of the unit correctly. Note that the source tile (the
-- 
2.45.2

