Complete.Org: Mailing Lists: Archives: freeciv-dev: March 2005:
[Freeciv-Dev] (PR#12555) AI unit management cleanup
Home

[Freeciv-Dev] (PR#12555) AI unit management cleanup

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
Subject: [Freeciv-Dev] (PR#12555) AI unit management cleanup
From: "Per I. Mathisen" <per@xxxxxxxxxxx>
Date: Sat, 19 Mar 2005 16:26:06 -0800
Reply-to: bugs@xxxxxxxxxxx

<URL: http://bugs.freeciv.org/Ticket/Display.html?id=12555 >

This patch cleans up the iteration of unit management in the AI.
Basically, where we used to iterate over all units twice, we now do it
just once.

We no longer manage passengers in ferries. Instead, ferries hand over
management to its chosen passenger (each ferry has a "boss" that says
where it should go). When we arrive, the ferry manages each passenger, to
allow them to disembark.

We also try very hard to avoid managing a unit twice by doing it once by
ferry/other and once by ai_manage_units(). This is the reason for
punit->ai.done (besides debugging).

The AI defense code has been cleaned up significantly as well. It was
necessary to do simulatenously, unfortunately. The design is much the
same, but much much cleaner.

We now fortify our defenders, and sentry units that are not designated as
defenders, but are in a city anyway, such as units waiting for a ferry.
This makes it easier to debug the AI by looking at what units are doing.
(Fortifying units in cities does nothing. They get the defense bonus
anyway.)

The AIUNIT_PILLAGE, AIUNIT_RUNAWAY and AIUNIT_FORTIFY roles have been
removed, since they were not/no longer used.

Extensive testing has gone into this, but it is a radical change. So
please read it carefully :) Comments most welcome.

Speed: I tested a very large game on auto for two turns.

w/o patch: user 0m50.199s
w. patch: user 0m38.798s

  - Per

Index: ai/aiair.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiair.c,v
retrieving revision 1.29
diff -u -r1.29 aiair.c
--- ai/aiair.c  14 Mar 2005 20:26:23 -0000      1.29
+++ ai/aiair.c  20 Mar 2005 00:07:32 -0000
@@ -31,6 +31,7 @@
 #include "unithand.h"
 #include "unittools.h"
 
+#include "ailog.h"
 #include "aitools.h"
 #include "aiunit.h"
 
@@ -312,9 +313,9 @@
       ai_unit_goto(punit, punit->goto_tile);
     } else {
       if (punit->fuel == 1) {
-       freelog(LOG_DEBUG, "Oops, %s is fallin outta sky", 
-               unit_type(punit)->name);
+       UNIT_LOG(LOG_DEBUG, punit, "Oops, fallin outta the sky");
       }
+      punit->ai.done = TRUE; /* Won't help trying again */
       return;
     }
 
@@ -344,10 +345,12 @@
               (map_get_city(dst_tile) ? 
                map_get_city(dst_tile)->name : ""));
       punit->goto_tile = dst_tile;
-      ai_unit_goto(punit, punit->goto_tile);
+      punit->ai.done = TRUE; /* Wait for next turn */
+      (void) ai_unit_goto(punit, punit->goto_tile);
     } else {
       freelog(LOG_DEBUG, "%s cannot find anything to kill and is staying put", 
               unit_type(punit)->name);
+      punit->ai.done = TRUE;
       handle_unit_activity_request(punit, ACTIVITY_IDLE);
     }
   }
Index: ai/aidiplomat.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidiplomat.c,v
retrieving revision 1.47
diff -u -r1.47 aidiplomat.c
--- ai/aidiplomat.c     18 Mar 2005 11:26:23 -0000      1.47
+++ ai/aidiplomat.c     20 Mar 2005 00:07:33 -0000
@@ -574,6 +574,7 @@
     UNIT_LOG(LOG_DIPLOMAT, punit, "stays to protect %s (urg %d)", 
              pcity->name, pcity->ai.urgency);
     ai_unit_new_role(punit, AIUNIT_NONE, NULL); /* abort mission */
+    punit->ai.done = TRUE;
     pf_destroy_map(map);
     return;
   }
@@ -643,6 +644,7 @@
       UNIT_LOG(LOG_DIPLOMAT, punit, "going idle");
     } else {
       UNIT_LOG(LOG_DIPLOMAT, punit, "could not find a job");
+      punit->ai.done = TRUE;
       pf_destroy_map(map);
       return;
     }
@@ -679,6 +681,8 @@
       }
     }
     pf_destroy_path(path);
+  } else {
+    punit->ai.done = TRUE;
   }
   pf_destroy_map(map);
 }
Index: ai/aiferry.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiferry.c,v
retrieving revision 1.16
diff -u -r1.16 aiferry.c
--- ai/aiferry.c        19 Mar 2005 21:58:17 -0000      1.16
+++ ai/aiferry.c        20 Mar 2005 00:07:33 -0000
@@ -138,15 +138,19 @@
 **************************************************************************/
 static void aiferry_request_boat(struct unit *punit)
 {
-    struct ai_data *ai = ai_data_get(unit_owner(punit));
+  struct ai_data *ai = ai_data_get(unit_owner(punit));
 
-    /* First clear the previous assignments (just in case) */
-    aiferry_clear_boat(punit);
-    /* Now add ourselves to the list of potential passengers */
-    UNIT_LOG(LOG_DEBUG, punit, "requests a boat.");
-    ai->stats.passengers++;
-    punit->ai.ferryboat = FERRY_WANTED;
+  /* First clear the previous assignments (just in case). */
+  aiferry_clear_boat(punit);
+
+  /* Now add ourselves to the list of potential passengers */
+  ai->stats.passengers++;
+  UNIT_LOG(LOG_DEBUG, punit, "requests a boat (total passengers=%d).",
+           ai->stats.passengers);
+  punit->ai.ferryboat = FERRY_WANTED;
 
+  /* Lastly, wait for ferry. */
+  punit->ai.done = TRUE;
 }
 
 /**************************************************************************
@@ -419,6 +423,7 @@
       if (boatid <= 0) {
         UNIT_LOG(LOGLEVEL_GOBYBOAT, punit, 
                 "in ai_gothere cannot find any boats.");
+        punit->ai.done = TRUE; /* Nothing to do */
         return FALSE;
       }
 
@@ -517,6 +522,7 @@
         } else if (bodyguard->moves_left <= 0) {
           /* Wait for me, I'm cooooming!! */
           UNIT_LOG(LOGLEVEL_GOBYBOAT, punit, "waiting for bodyguard");
+          punit->ai.done = TRUE;
           return FALSE;
         } else {
           /* Crap bodyguard. Got stuck somewhere. Ditch it! */
@@ -537,6 +543,7 @@
       if (!is_tiles_adjacent(ferryboat->tile, beach_tile)
           && !same_pos(ferryboat->tile, beach_tile)) {
         /* We are in still transit */
+        punit->ai.done = TRUE;
         return FALSE;
       }
     } else {
@@ -549,7 +556,18 @@
 
     UNIT_LOG(LOGLEVEL_GOBYBOAT, punit, "Our boat has arrived "
             "[%d](moves left: %d)", ferryboat->id, ferryboat->moves_left);
-    handle_unit_activity_request(punit, ACTIVITY_IDLE);
+    unit_list_iterate(punit->tile->units, aunit) {
+      if (aunit->transported_by == ferryboat->id) {
+        handle_unit_activity_request(aunit, ACTIVITY_IDLE);
+        aunit->ai.done = FALSE;
+        /* Offload entire cargo except ourselves and our bodyguard.
+         * Since we do not "manage" extra units stored on a transport,
+         * this is their only chance to get off. */
+        if (aunit != punit && aunit->ai.charge != punit->id) {
+          ai_manage_unit(pplayer, aunit);
+        }
+      }
+    } unit_list_iterate_end;
   }
 
   return TRUE;
@@ -731,6 +749,7 @@
       && (pcity = map_get_city(punit->tile))) {
     UNIT_LOG(LOGLEVEL_FERRY, punit, "waiting in %s to recover hitpoints", 
              pcity->name);
+    punit->ai.done = TRUE;
     return;
   }
 
@@ -764,8 +783,10 @@
       /* Try to select passanger-in-charge from among our passengers */
       unit_list_iterate(ptile->units, aunit) {
         if (unit_owner(aunit) != pplayer 
-            || (aunit->ai.ferryboat != punit->id 
-                && aunit->ai.ferryboat != FERRY_WANTED)) {
+            || aunit->transported_by != punit->id) {
+          /* We used to check if ferryboat was set to us or to
+           * FERRY_WANTED too, but this was a bit strict. Especially
+           * when we don't save these values in a savegame. */
           continue;
         }
       
@@ -808,6 +829,8 @@
       if (find_unit_by_id(bossid)) {
        if (same_pos(punit->tile, boss->tile)) {
          /* The boss decided to stay put on the ferry. We aren't moving. */
+          UNIT_LOG(LOG_DEBUG, boss, "drove ferry - done for now");
+          boss->ai.done = TRUE;
          return;
        } else if (get_transporter_occupancy(punit) != 0) {
          /* The boss isn't on the ferry, and we have other passengers?
@@ -826,6 +849,7 @@
   if (IS_ATTACKER(punit) && punit->moves_left > 0) {
      /* AI used to build frigates to attack and then use them as ferries 
       * -- Syela */
+     ai_unit_new_role(punit, AIUNIT_ATTACK, NULL);
      UNIT_LOG(LOGLEVEL_FERRY, punit, "passing ferry over to attack code");
      ai_manage_military(pplayer, punit);
      return;
@@ -842,7 +866,16 @@
   if (aiferry_findcargo(punit)) {
     UNIT_LOG(LOGLEVEL_FERRY, punit, "picking up cargo (moves left: %d)",
             punit->moves_left);
-    ai_unit_goto(punit, punit->goto_tile);
+    if (ai_unit_goto(punit, punit->goto_tile)) {
+      if (is_tiles_adjacent(punit->tile, punit->goto_tile)
+          || same_pos(punit->tile, punit->goto_tile)) {
+        struct unit *cargo = find_unit_by_id(punit->ai.passenger);
+
+        /* See if passenger can jump on board! */
+        assert(cargo != punit);
+        ai_manage_unit(pplayer, cargo);
+      }
+    }
     return;
   }
 
@@ -850,22 +883,30 @@
   if (aiferry_find_interested_city(punit)) {
     if (same_pos(punit->tile, punit->goto_tile)) {
       UNIT_LOG(LOGLEVEL_FERRY, punit, "staying in city that needs us");
+      punit->ai.done = TRUE;
       return;
     } else {
       UNIT_LOG(LOGLEVEL_FERRY, punit, "going to city that needs us");
-      (void) ai_unit_goto(punit, punit->goto_tile);
+      if (ai_unit_goto(punit, punit->goto_tile)
+          && same_pos(punit->tile, punit->goto_tile)) {
+        punit->ai.done = TRUE; /* save some CPU */
+      }
       return;
     }
   }
 
   UNIT_LOG(LOGLEVEL_FERRY, punit, "Passing control of ferry to explorer code");
-  (void) ai_manage_explorer(punit);
+  if (!ai_manage_explorer(punit)) {
+    punit->ai.done = TRUE;
+  }
 
   if (find_unit_by_id(sanity) && punit->moves_left > 0) {
     struct city *pcity = find_nearest_safe_city(punit);
     if (pcity) {
       punit->goto_tile = pcity->tile;
       UNIT_LOG(LOGLEVEL_FERRY, punit, "No work, going home");
+      punit->ai.done = TRUE;
+      ai_unit_new_role(punit, AIUNIT_NONE, NULL);
       (void) ai_unit_goto(punit, pcity->tile);
     }
   }
Index: ai/aitools.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.c,v
retrieving revision 1.139
diff -u -r1.139 aitools.c
--- ai/aitools.c        14 Mar 2005 20:26:24 -0000      1.139
+++ ai/aitools.c        20 Mar 2005 00:07:33 -0000
@@ -346,6 +346,11 @@
   }
   punit->ai.charge = BODYGUARD_NONE;
 
+  /* Record the city to defend; our goto may be to transport. */
+  if (task == AIUNIT_DEFEND_HOME && ptile && ptile->city) {
+    punit->ai.charge = ptile->city->id;
+  }
+
   punit->ai.ai_role = task;
 
   /* Verify and set the goto destination.  Eventually this can be a lot more
Index: ai/aiunit.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiunit.c,v
retrieving revision 1.346
diff -u -r1.346 aiunit.c
--- ai/aiunit.c 14 Mar 2005 20:26:24 -0000      1.346
+++ ai/aiunit.c 20 Mar 2005 00:07:33 -0000
@@ -74,7 +74,7 @@
 static bool ai_military_rampage(struct unit *punit, int thresh_adj, 
                                 int thresh_move);
 static void ai_military_findjob(struct player *pplayer,struct unit *punit);
-static void ai_military_gohome(struct player *pplayer,struct unit *punit);
+static void ai_military_defend(struct player *pplayer,struct unit *punit);
 static void ai_military_attack(struct player *pplayer,struct unit *punit);
 
 static int unit_move_turns(struct unit *punit, struct tile *ptile);
@@ -328,54 +328,6 @@
 }
 
 /**************************************************************************
-  Return whether we should stay and defend a square, usually a city. Will
-  protect allied cities temporarily in case of grave danger.
-
-  FIXME: We should check for fortresses here.
-**************************************************************************/
-static bool stay_and_defend(struct unit *punit)
-{
-  struct city *pcity = map_get_city(punit->tile);
-  bool has_defense = FALSE;
-  int mydef;
-  int units = -2; /* WAG for grave danger threshold, seems to work */
-
-  if (!pcity) {
-    return FALSE;
-  }
-  mydef = assess_defense_unit(pcity, punit, FALSE);
-
-  unit_list_iterate((pcity->tile)->units, pdef) {
-    if (assess_defense_unit(pcity, pdef, FALSE) >= mydef
-       && pdef != punit
-       && pdef->homecity == pcity->id) {
-      has_defense = TRUE;
-    }
-    units++;
-  } unit_list_iterate_end;
- 
-  /* Guess I better stay / you can live at home now */
-  if (!has_defense && pcity->ai.danger > 0 && punit->owner == pcity->owner) {
-    /* Change homecity to this city */
-    if (ai_unit_make_homecity(punit, pcity)) {
-      /* Very important, or will not stay -- Syela */
-      ai_unit_new_role(punit, AIUNIT_DEFEND_HOME, pcity->tile);
-      return TRUE;
-    } /* else city cannot upkeep us! */
-  }
-
-  /* Treat grave danger anyway if danger is over threshold, which is the
-   * number of units currently in the city.  However, to avoid AI panics
-   * (this is common when enemy is huge), add a ceiling. */
-  if (pcity->ai.grave_danger > units && units <= 2) {
-    ai_unit_new_role(punit, AIUNIT_DEFEND_HOME, pcity->tile);
-    return TRUE;
-  }
-
-  return FALSE;
-}
-
-/**************************************************************************
   Attack rating of this kind of unit.
 **************************************************************************/
 int unittype_att_rating(Unit_Type_id type, int veteran,
@@ -817,8 +769,11 @@
   /* I had these guys set to just fortify, which is so dumb. -- Syela
    * Instead we can attack adjacent units and maybe even pick up some free 
    * cities! */
-  (void) ai_military_rampage(punit, BODYGUARD_RAMPAGE_THRESHOLD,
-                             RAMPAGE_FREE_CITY_OR_BETTER);
+  if (ai_military_rampage(punit, BODYGUARD_RAMPAGE_THRESHOLD,
+                          RAMPAGE_FREE_CITY_OR_BETTER)
+      && same_pos(punit->tile, ptile)) {
+    punit->ai.done = TRUE; /* Stay with charge */
+  }
 }
 
 /*************************************************************************
@@ -993,98 +948,43 @@
 }
 
 /********************************************************************** 
-  Find something to do with a unit. Also, check sanity of existing
-  missions.
+  See if we have a specific job for the unit.
 ***********************************************************************/
 static void ai_military_findjob(struct player *pplayer,struct unit *punit)
 {
-  struct city *pcity = NULL, *acity = NULL;
-  struct unit *aunit;
-  int val, def;
-  int q = 0;
   struct unit_type *punittype = get_unit_type(punit->type);
 
   CHECK_UNIT(punit);
 
-/* tired of AI abandoning its cities! -- Syela */
-  if (punit->homecity != 0 && (pcity = find_city_by_id(punit->homecity))) {
-    if (pcity->ai.danger != 0) { /* otherwise we can attack */
-      def = assess_defense(pcity);
-      if (same_pos(punit->tile, pcity->tile)) {
-        /* I'm home! */
-        val = assess_defense_unit(pcity, punit, FALSE); 
-        def -= val; /* old bad kluge fixed 980803 -- Syela */
-/* only the least defensive unit may leave home */
-/* and only if this does not jeopardize the city */
-/* def is the defense of the city without punit */
-        if (unit_flag(punit, F_FIELDUNIT)) val = -1;
-        unit_list_iterate((pcity->tile)->units, pdef)
-          if (is_military_unit(pdef) 
-              && pdef != punit 
-              && !unit_flag(pdef, F_FIELDUNIT)
-              && pdef->owner == punit->owner) {
-            if (assess_defense_unit(pcity, pdef, FALSE) >= val) val = 0;
-          }
-        unit_list_iterate_end; /* was getting confused without the is_military 
part in */
-        if (unit_def_rating_basic_sq(punit) == 0) {
-          /* thanks, JMT, Paul */
-          q = 0;
-        } else { 
-          /* this was a WAG, but it works, so now it's just good code! 
-           * -- Syela */
-          q = (pcity->ai.danger * 2 
-               - (def * unit_type(punit)->attack_strength /
-                  unit_type(punit)->defense_strength));
-        }
-        if (val > 0 || q > 0) { /* Guess I better stay */
-          ;
-        } else q = 0;
-      } /* end if home */
-    } /* end if home is in danger */
-  } /* end if we have a home */
-
   /* keep barbarians aggresive and primitive */
   if (is_barbarian(pplayer)) {
     if (can_unit_do_activity(punit, ACTIVITY_PILLAGE)
        && is_land_barbarian(pplayer)) {
       /* land barbarians pillage */
-      ai_unit_new_role(punit, AIUNIT_PILLAGE, NULL);
-    } else {
-      ai_unit_new_role(punit, AIUNIT_ATTACK, NULL);
+      handle_unit_activity_request(punit, ACTIVITY_PILLAGE);
     }
+    ai_unit_new_role(punit, AIUNIT_NONE, NULL);
     return;
   }
 
-  if (punit->ai.charge != BODYGUARD_NONE) { /* I am a bodyguard */
-    aunit = player_find_unit_by_id(pplayer, punit->ai.charge);
-    acity = find_city_by_id(punit->ai.charge);
+  /* I am a bodyguard, check if I do my job! */
+  if (punit->ai.charge != BODYGUARD_NONE
+      && punit->ai.ai_role == AIUNIT_ESCORT) {
+    struct unit *aunit = player_find_unit_by_id(pplayer, punit->ai.charge);
+    struct city *acity = find_city_by_id(punit->ai.charge);
 
-    /* Check if city we are on our way to rescue is still in danger,
-     * or unit we should protect is still alive */
+    /* Check if the city we are on our way to rescue is still in danger,
+     * or the unit we should protect is still alive... */
     if ((aunit && aunit->ai.bodyguard != BODYGUARD_NONE 
          && unit_def_rating_basic(punit) > unit_def_rating_basic(aunit)) 
         || (acity && acity->owner == punit->owner && acity->ai.urgency != 0 
             && acity->ai.danger > assess_defense_quadratic(acity))) {
-      assert(punit->ai.ai_role == AIUNIT_ESCORT);
-      return;
+      return; /* Yep! */
     } else {
-      ai_unit_new_role(punit, AIUNIT_NONE, NULL);
+      ai_unit_new_role(punit, AIUNIT_NONE, NULL); /* Nope! */
     }
   }
 
-  /* ok, what if I'm somewhere new? - ugly, kludgy code by Syela */
-  if (stay_and_defend(punit)) {
-    UNIT_LOG(LOG_DEBUG, punit, "stays to defend %s",
-             map_get_city(punit->tile)->name);
-    return;
-  }
-
-  if (pcity && q > 0 && pcity->ai.urgency > 0) {
-    UNIT_LOG(LOG_DEBUG, punit, "decides to camp at home in %s", pcity->name);
-    ai_unit_new_role(punit, AIUNIT_DEFEND_HOME, pcity->tile);
-    return;
-  }
-
   /* Is the unit badly damaged? */
   if ((punit->ai.ai_role == AIUNIT_RECOVER
        && punit->hp < punittype->hp)
@@ -1107,55 +1007,51 @@
     }
   }
 
-/* I'm not 100% sure this is the absolute best place for this... -- Syela */
-  generate_warmap(map_get_city(punit->tile), punit);
-/* I need this in order to call unit_move_turns, here and in look_for_charge */
-
-  if (pcity && q > 0) {
-    q *= 100;
-    q /= unit_def_rating_basic_sq(punit);
-    q >>= unit_move_turns(punit, pcity->tile);
-  }
-
-  val = 0; acity = NULL; aunit = NULL;
   if (unit_role_defender(punit->type)) {
     /* 
      * This is a defending unit that doesn't need to stay put.
      * It needs to defend something, but not necessarily where it's at.
      * Therefore, it will consider becoming a bodyguard. -- Syela 
      */
+    struct city *acity = NULL; 
+    struct unit *aunit = NULL;
+    int val;
+
+    generate_warmap(map_get_city(punit->tile), punit);
+
     val = look_for_charge(pplayer, punit, &aunit, &acity);
-  }
-  if (pcity && q > val) {
-    UNIT_LOG(LOG_DEBUG, punit, "decided not to go anywhere, sits in %s",
-             pcity->name);
-    ai_unit_new_role(punit, AIUNIT_DEFEND_HOME, pcity->tile);
-    return;
-  }
-  /* this is bad; riflemen might rather attack if val is low -- Syela */
-  if (acity) {
-    ai_unit_new_role(punit, AIUNIT_ESCORT, acity->tile);
-    punit->ai.charge = acity->id;
-    BODYGUARD_LOG(LOG_DEBUG, punit, "going to defend city");
-  } else if (aunit) {
-    ai_unit_new_role(punit, AIUNIT_ESCORT, aunit->tile);
-    punit->ai.charge = aunit->id;
-    BODYGUARD_LOG(LOG_DEBUG, punit, "going to defend unit");
-  } else if (ai_unit_attack_desirability(punit->type) != 0 ||
-      (pcity && !same_pos(pcity->tile, punit->tile))) {
-     ai_unit_new_role(punit, AIUNIT_ATTACK, NULL);
-  } else {
-    UNIT_LOG(LOG_DEBUG, punit, "nothing to do, sit where we are");
-    ai_unit_new_role(punit, AIUNIT_DEFEND_HOME, NULL); /* for default */
+    if (acity) {
+      ai_unit_new_role(punit, AIUNIT_ESCORT, acity->tile);
+      punit->ai.charge = acity->id;
+      BODYGUARD_LOG(LOG_DEBUG, punit, "going to defend city");
+      return;
+    } else if (aunit) {
+      ai_unit_new_role(punit, AIUNIT_ESCORT, aunit->tile);
+      punit->ai.charge = aunit->id;
+      BODYGUARD_LOG(LOG_DEBUG, punit, "going to defend unit");
+      return;
+    }
   }
 }
 
 /********************************************************************** 
-  Send a unit to its homecity.
+  Send a unit to the city it should defend. If we already have a city
+  it should defend, use the punit->ai.charge field to denote this.
+  Otherwise, it will stay put in the city it is in, or find a city
+  to reside in, or travel all the way home.
+
+  TODO: Add make homecity.
+  TODO: Add better selection of city to defend.
 ***********************************************************************/
-static void ai_military_gohome(struct player *pplayer,struct unit *punit)
+static void ai_military_defend(struct player *pplayer,struct unit *punit)
 {
-  struct city *pcity = find_city_by_id(punit->homecity);
+  struct city *pcity = find_city_by_id(punit->ai.charge);
+
+  CHECK_UNIT(punit);
+
+  if (!pcity) {
+    pcity = punit->tile->city;
+  }
 
   if (!pcity) {
     /* Try to find a place to rest. Sitting duck out in the wilderness
@@ -1164,21 +1060,20 @@
     pcity = find_closest_owned_city(pplayer, punit->tile, FALSE, NULL);
   }
 
-  CHECK_UNIT(punit);
-
-  if (pcity) {
-    UNIT_LOG(LOG_DEBUG, punit, "go home to %s(%d,%d)",
-             pcity->name, TILE_XY(pcity->tile)); 
-    if (same_pos(punit->tile, pcity->tile)) {
-      UNIT_LOG(LOG_DEBUG, punit, "go home successful; role AI_NONE");
-      ai_unit_new_role(punit, AIUNIT_NONE, NULL);
+  if (!pcity) {
+    pcity = find_city_by_id(punit->homecity);
+  }
 
-      /* aggro defense goes here -- Syela */
-      /* Attack anything that won't kill us */
-      (void) ai_military_rampage(punit, RAMPAGE_ANYTHING, 
-                                 RAMPAGE_ANYTHING);
-    } else {
-      (void) ai_gothere(pplayer, punit, pcity->tile);
+  if (ai_military_rampage(punit, RAMPAGE_ANYTHING, RAMPAGE_ANYTHING)) {
+    /* ... we survived */
+    if (pcity) {
+      UNIT_LOG(LOG_DEBUG, punit, "go to defend %s", pcity->name);
+      if (same_pos(punit->tile, pcity->tile)) {
+        UNIT_LOG(LOG_DEBUG, punit, "go defend successful");
+        punit->ai.done = TRUE;
+      } else {
+        (void) ai_gothere(pplayer, punit, pcity->tile);
+      }
     }
   }
 }
@@ -1360,11 +1255,13 @@
 
   if (!is_ground_unit(punit) && !is_sailing_unit(punit)) {
     /* Don't know what to do with them! */
+    UNIT_LOG(LOG_ERROR, punit, "bad unit type passed to fstk");
     return 0;
   }
 
   if (attack_value == 0) {
     /* A very poor attacker... */
+    UNIT_LOG(LOG_ERROR, punit, "non-attacker passed to fstk");
     return 0;
   }
 
@@ -1780,13 +1677,6 @@
 
   /* Main attack loop */
   do {
-    if (stay_and_defend(punit)) {
-      /* This city needs defending, don't go outside! */
-      UNIT_LOG(LOG_DEBUG, punit, "stayed to defend %s", 
-               map_get_city(punit->tile)->name);
-      return;
-    }
-
     /* Then find enemies the hard way */
     find_something_to_kill(pplayer, punit, &dest_tile);
     if (!same_pos(punit->tile, dest_tile)) {
@@ -1872,14 +1762,12 @@
     struct city *pcity = map_get_city(punit->tile);
 
     if (pcity) {
-      ai_unit_new_role(punit, AIUNIT_DEFEND_HOME, pcity->tile);
-      /* FIXME: Send unit to nearest city needing more defence */
-      UNIT_LOG(LOG_DEBUG, punit, "could not find work, sitting duck");
+      punit->ai.done = TRUE;
+      UNIT_LOG(LOG_DEBUG, punit, "could not find work");
+      ai_unit_new_role(punit, AIUNIT_NONE, NULL);
     } else {
-      /* Going home */
-      UNIT_LOG(LOG_DEBUG, punit, "sent home");
-      /* FIXME: Rehome & send us to nearest city needing more defence */
-      ai_military_gohome(pplayer, punit);
+      UNIT_LOG(LOG_DEBUG, punit, "we are homeless and useless :(");
+      ai_unit_new_role(punit, AIUNIT_NONE, NULL);
     }
   }
 }
@@ -2007,6 +1895,8 @@
     UNIT_LOG(LOGLEVEL_RECOVERY, punit, "ready to kick ass again!");
     ai_unit_new_role(punit, AIUNIT_NONE, NULL);  
     return;
+  } else {
+    punit->ai.done = TRUE; /* sit tight */
   }
 }
 
@@ -2021,7 +1911,8 @@
   CHECK_UNIT(punit);
 
   /* "Escorting" aircraft should not do anything. They are activated
-   * by their transport or charge. */
+   * by their transport or charge.  We do _NOT_ set them to 'done'
+   * since they may need be activated once our charge moves. */
   if (punit->ai.ai_role == AIUNIT_ESCORT && is_air_unit(punit)) {
     return;
   }
@@ -2031,6 +1922,7 @@
       && ai_handicap(pplayer, H_AWAY)) {
     /* Don't move sentried or fortified units controlled by a player
      * in away mode. */
+    punit->ai.done = TRUE;
     return;
   }
 
@@ -2038,38 +1930,36 @@
      we must make sure that previously reserved ferry is freed. */
   aiferry_clear_boat(punit);
 
+  /* Do we have a specific job for this unit? If not, we default
+   * to attack. */
   ai_military_findjob(pplayer, punit);
 
   switch (punit->ai.ai_role) {
   case AIUNIT_AUTO_SETTLER:
   case AIUNIT_BUILD_CITY:
-    ai_unit_new_role(punit, AIUNIT_NONE, NULL);
+    assert(FALSE); /* This is not the place for this role */
     break;
   case AIUNIT_DEFEND_HOME:
-    ai_military_gohome(pplayer, punit);
+    ai_military_defend(pplayer, punit);
     break;
   case AIUNIT_ATTACK:
+  case AIUNIT_NONE:
     ai_military_attack(pplayer, punit);
     break;
-  case AIUNIT_FORTIFY:
-    ai_military_gohome(pplayer, punit);
-    break;
-  case AIUNIT_RUNAWAY: 
-    break;
   case AIUNIT_ESCORT: 
     ai_military_bodyguard(pplayer, punit);
     break;
-  case AIUNIT_PILLAGE:
-    handle_unit_activity_request(punit, ACTIVITY_PILLAGE);
-    return; /* when you pillage, you have moves left, avoid later fortify */
   case AIUNIT_EXPLORE:
-    (void) ai_manage_explorer(punit);
+    punit->ai.done = !(ai_manage_explorer(punit) && punit->moves_left > 0);
     break;
   case AIUNIT_RECOVER:
     ai_manage_hitpoint_recovery(punit);
     break;
   case AIUNIT_HUNTER:
-    ai_hunter_manage(pplayer, punit);
+    if (!ai_hunter_manage(pplayer, punit)) {
+      /* Try something else */
+      ai_military_bodyguard(pplayer, punit);
+    }
     break;
   default:
     assert(FALSE);
@@ -2079,8 +1969,17 @@
   if ((punit = find_unit_by_id(id))) {
     if (unit_list_find(punit->tile->units, punit->ai.ferryboat)) {
       handle_unit_activity_request(punit, ACTIVITY_SENTRY);
-    } else if (punit->activity == ACTIVITY_IDLE) {
-      handle_unit_activity_request(punit, ACTIVITY_FORTIFYING);
+    } else if (punit->tile->city || punit->activity == ACTIVITY_IDLE) {
+      /* We do not need to fortify in cities - we fortify and sentry
+       * according to home defense setup, for easy debugging. */
+      if (!punit->tile->city || punit->ai.ai_role == AIUNIT_DEFEND_HOME) {
+        if (punit->activity == ACTIVITY_IDLE
+            || punit->activity == ACTIVITY_SENTRY) {
+          handle_unit_activity_request(punit, ACTIVITY_FORTIFYING);
+        }
+      } else {
+        handle_unit_activity_request(punit, ACTIVITY_SENTRY);
+      }
     }
   }
 }
@@ -2128,6 +2027,7 @@
   /* Don't manage the unit if it is under human orders. */
   if (unit_has_orders(punit)) {
     punit->ai.ai_role = AIUNIT_NONE;
+    punit->ai.done = TRUE;
     return;
   }
 
@@ -2135,15 +2035,10 @@
      function */
   if( is_barbarian(pplayer) ) {
     /* Todo: should be configurable */
-    if( unit_can_be_retired(punit) && myrand(100) > 90 ) {
+    if (unit_can_be_retired(punit) && myrand(100) > 90) {
       wipe_unit(punit);
       return;
     }
-    if( !is_military_unit(punit)
-       && !unit_has_role(punit->type, L_BARBARIAN_LEADER)) {
-      freelog(LOG_VERBOSE, "Barbarians picked up non-military unit.");
-      return;
-    }
   }
 
   /* Check if we have lost our bodyguard. If we never had one, all
@@ -2155,6 +2050,7 @@
 
   if (punit->moves_left <= 0) {
     /* Can do nothing */
+    punit->ai.done = TRUE;
     return;
   }
 
@@ -2185,6 +2081,7 @@
   } else if (is_heli_unit(punit)) {
     /* TODO: We can try using air-unit code for helicopters, just
      * pretend they have fuel = HP / 3 or something. */
+    punit->ai.done = TRUE; /* we did our best, which was ... nothing */
     return;
   } else if (is_military_unit(punit)) {
     ai_manage_military(pplayer,punit); 
@@ -2195,25 +2092,95 @@
     if (!ai_manage_explorer(punit)
         && find_unit_by_id(id)) {
       ai_unit_new_role(punit, AIUNIT_DEFEND_HOME, NULL);
-      ai_military_gohome(pplayer, punit);
+      ai_military_defend(pplayer, punit);
     }
     return;
   }
 }
 
 /**************************************************************************
+  Master city defense function.  We try to pick up the best available
+  defenders, and not disrupt existing roles.
+
+  TODO: Make homecity, respect homecity.
+**************************************************************************/
+static void ai_set_defenders(struct player *pplayer)
+{
+  city_list_iterate(pplayer->cities, pcity) {
+    /* The idea here is that we should never keep more than two
+     * units in permanent defense. */
+    int total_defense = 0;
+    int total_attack = pcity->ai.danger;
+    bool emergency = FALSE;
+    int count = 0;
+
+    while (total_defense < total_attack) {
+      int best_want = 0;
+      struct unit *best = NULL;
+
+      unit_list_iterate(pcity->tile->units, punit) {
+       if ((punit->ai.ai_role == AIUNIT_NONE || emergency)
+           && punit->ai.ai_role != AIUNIT_DEFEND_HOME) {
+          int want = assess_defense_unit(pcity, punit, FALSE);
+
+          if (want > best_want) {
+            best_want = want;
+            best = punit;
+          }
+        }
+      } unit_list_iterate_end;
+      if (best == NULL) {
+        /* Ooops - try to grab any unit as defender! */
+        if (emergency) {
+          CITY_LOG(LOG_DEBUG, pcity, "Not defended properly");
+          break;
+        }
+        emergency = TRUE;
+      } else {
+        int loglevel = pcity->debug ? LOG_NORMAL : LOG_DEBUG;
+
+        total_defense += best_want;
+        UNIT_LOG(loglevel, best, "Defending city");
+        ai_unit_new_role(best, AIUNIT_DEFEND_HOME, pcity->tile);
+        count++;
+      }
+    }
+    CITY_LOG(LOG_DEBUG, pcity, "Evaluating defense: %d defense, %d incoming"
+             " %d defenders (out of %d)", total_defense, total_attack, count,
+             unit_list_size(pcity->tile->units));
+  } city_list_iterate_end;
+}
+
+/**************************************************************************
   Master manage unit function.
+
+  A manage function should set the unit to 'done' when it should no
+  longer be touched by this code, and its role should be reset to IDLE
+  when its role has accomplished its mission or the manage function
+  fails to have or no longer has any use for the unit.
 **************************************************************************/
 void ai_manage_units(struct player *pplayer) 
 {
   ai_airlift(pplayer);
+
+  /* Clear previous orders, if desirable, here. */
+  unit_list_iterate(pplayer->units, punit) {
+    punit->ai.done = FALSE;
+    if (punit->ai.ai_role == AIUNIT_DEFEND_HOME) {
+      ai_unit_new_role(punit, AIUNIT_NONE, NULL);
+    }
+  } unit_list_iterate_end;
+
+  /* Find and set city defenders first - figure out which units are
+   * allowed to leave home. */
+  ai_set_defenders(pplayer);
+
   unit_list_iterate_safe(pplayer->units, punit) {
-    ai_manage_unit(pplayer, punit);
-  } unit_list_iterate_safe_end;
-  /* Sometimes units wait for other units to move so we crudely
-   * solve it by moving everything again */ 
-  unit_list_iterate_safe(pplayer->units, punit) {
-    ai_manage_unit(pplayer, punit);
+    if (punit->transported_by <= 0 && !punit->ai.done) {
+      /* Though it is usually the passenger who drives the transport,
+       * the transporter is responsible for managing its passengers. */
+      ai_manage_unit(pplayer, punit);
+    }
   } unit_list_iterate_safe_end;
 }
 
Index: common/unit.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/unit.c,v
retrieving revision 1.231
diff -u -r1.231 unit.c
--- common/unit.c       19 Mar 2005 21:58:17 -0000      1.231
+++ common/unit.c       20 Mar 2005 00:07:34 -0000
@@ -1444,6 +1444,7 @@
   if (is_barbarian(pplayer)) {
     punit->fuel = BARBARIAN_LIFE;
   }
+  punit->ai.done = FALSE;
   punit->ai.cur_pos = NULL;
   punit->ai.prev_pos = NULL;
   punit->ai.target = 0;
Index: common/unit.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/unit.h,v
retrieving revision 1.138
diff -u -r1.138 unit.h
--- common/unit.h       19 Mar 2005 21:58:17 -0000      1.138
+++ common/unit.h       20 Mar 2005 00:07:34 -0000
@@ -55,9 +55,8 @@
 };
 
 enum ai_unit_task { AIUNIT_NONE, AIUNIT_AUTO_SETTLER, AIUNIT_BUILD_CITY,
-                    AIUNIT_DEFEND_HOME, AIUNIT_ATTACK, AIUNIT_FORTIFY,
-                    AIUNIT_RUNAWAY, AIUNIT_ESCORT, AIUNIT_EXPLORE,
-                    AIUNIT_PILLAGE, AIUNIT_RECOVER, AIUNIT_HUNTER };
+                    AIUNIT_DEFEND_HOME, AIUNIT_ATTACK, AIUNIT_ESCORT, 
+                    AIUNIT_EXPLORE, AIUNIT_RECOVER, AIUNIT_HUNTER };
 
 enum goto_move_restriction {
   GOTO_MOVE_ANY,
@@ -119,6 +118,7 @@
 
   int target; /* target we hunt */
   int hunted; /* if a player is hunting us, set by that player */
+  bool done;  /* we are done controlling this unit this turn */
 };
 
 struct unit {
Index: server/settlers.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/settlers.c,v
retrieving revision 1.222
diff -u -r1.222 settlers.c
--- server/settlers.c   14 Mar 2005 20:26:26 -0000      1.222
+++ server/settlers.c   20 Mar 2005 00:07:34 -0000
@@ -147,6 +147,7 @@
 void ai_manage_settler(struct player *pplayer, struct unit *punit)
 {
   punit->ai.control = TRUE;
+  punit->ai.done = TRUE; /* we will manage this unit later... ugh */
   /* if BUILD_CITY must remain BUILD_CITY, otherwise turn into autosettler */
   if (punit->ai.ai_role == AIUNIT_NONE) {
     ai_unit_new_role(punit, AIUNIT_AUTO_SETTLER, NULL);

[Prev in Thread] Current Thread [Next in Thread]
  • [Freeciv-Dev] (PR#12555) AI unit management cleanup, Per I. Mathisen <=