Complete.Org: Mailing Lists: Archives: freeciv-ai: March 2005:
[freeciv-ai] Re: [Freeciv-Dev] Re: (PR#11995) Stupid AI Creates Tall Sta
Home

[freeciv-ai] Re: [Freeciv-Dev] Re: (PR#11995) Stupid AI Creates Tall Sta

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
Subject: [freeciv-ai] Re: [Freeciv-Dev] Re: (PR#11995) Stupid AI Creates Tall Stacks
From: "Benedict Adamson" <badamson@xxxxxxxxxxx>
Date: Tue, 22 Mar 2005 16:57:03 -0800
Reply-to: bugs@xxxxxxxxxxx

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

Here is the fourth version of my patch, which is applicable to the CVS 
version of 2005-03-20.

Per I. Mathisen wrote:
...
> Hunters are not meant to hunt across continents.

Corrected.

...
> Unfortunately, this clashes with my unit management cleanup patch. I will
> commit this patch very soon, so you will have to find some way to
> incorporate the change in how "extra" passengers are handled: In the patch
> they are "managed" once the ferry arrives at its destination.
...
 > Looks like the simplest solution would be to move my new code to
 > ai_amphibious_goto_constrained().

I added some conditional code and a TODO comment to 
ai_amphibious_goto_constrained() where I think the additional code will 
be necessary.

...
> Rename [p_killed_at] to something intelligible, please.

Now called chance_killed_at. The implementation of the function is a 
quick and dirty kludge until the next stage of fixing this PR, which 
will be to use the movemep in the implementation. I've added explanatory 
commentary.

...
> +static int stack_risk(const struct tile *ptile,
> 
> Looking at the building cost of units when moving around may not be a good
> idea

I think it is; I've added commentary explaining my reasoning.

> There also seems to be no accounting for stacking
> in order to _reduce_ risk, by adding a better best defender to the stack.
> (Although I am not sure if adding this is a good idea.)

This would only be worthwhile if you can guarantee that a particular 
unit will not move off that tile this turn. I think your unit management 
patch might provide the information needed. Something for a later stage 
of fixing this PR.

...
> +  Avoiding tall stacks *all* along a path is useful because a unit following 
> a
> +  path might have to stop early because of ZoC or random movement.
...
> Random movement will be gone shortly.

Comment amended.

> 
> +  } else if (is_military_unit(punit)) {
> +    switch (punit->ai.ai_role) {
> +    case AIUNIT_AUTO_SETTLER:
> +    case AIUNIT_BUILD_CITY:
> +      UNIT_LOG(LOG_ERROR, punit, "military settler or worker");
> 
> This is actually a possible and valid combination.

Can the expression !unit_flag(punit, F_SETTLERS) && 
is_military_unit(punit) && punit->ai.ai_role == AIUNIT_AUTO_SETTLER 
really be true?
Removed the error message and have the function do something sensible 
instead, just in case.

...
> -  assert(unit_owner(punit)->ai.control);
> -  assert(is_tiles_adjacent(punit->tile, ptile));
> 
> What is the reason for removing these asserts?

I should not have removed the second. A complication is that the AI 
movement code is used for units, such as auto-explorers, that are under 
only temporary AI control, so several assertions like the first are 
invalid.  I've added comments to clarify this. More importantly, the 
patch was mishandling such units in a way that provided a cheat and 
might get units killed. Corrected.

...
> Instead of adding a lot of code here, is it not possible to remove this
> special casing for barbarians?

By moving more code to ai_fill_unit_param this is much reduced.

> Cosmetic nitpick: Please leave a line of space betweeen variable
> declarations and the rest of a bracket of code.

Corrected.


diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/ai/aiferry.c freeciv.PR11995/ai/aiferry.c
--- vendor.freeciv.current/ai/aiferry.c 2005-03-20 12:29:33.000000000 +0000
+++ freeciv.PR11995/ai/aiferry.c        2005-03-23 00:33:08.000000000 +0000
@@ -271,7 +271,7 @@
   Runs a few checks to determine if "boat" is a free boat that can carry
   "cap" units of the same type as "punit".
 ****************************************************************************/
-static bool is_boat_free(struct unit *boat, struct unit *punit, int cap)
+bool is_boat_free(struct unit *boat, struct unit *punit, int cap)
 {
   /* - Only transporters capable of transporting this unit are eligible.
    * - Units with orders are skipped (the AI doesn't control units with
@@ -387,6 +387,106 @@
 
 /* ============================= go by boat ============================== */
 
+/**************************************************************************
+  Move a passenger on a ferry to a specified destination.
+  The passenger is assumed to be on the given ferry.
+  The destination may be inland, in which case the passenger will ride
+  the ferry to a beach head, disembark, then continue on land.
+  Return FALSE iff we died.
+**************************************************************************/
+bool ai_amphibious_goto_constrained(struct unit *ferry,
+                                   struct unit *passenger,
+                                   struct tile *ptile,
+                                   struct pft_amphibious *parameter)
+{
+  bool alive = TRUE;
+  struct player *pplayer = unit_owner(passenger);
+  struct pf_map *map = NULL;
+  struct pf_path *path = NULL;
+
+  assert(pplayer->ai.control);
+  assert(!unit_has_orders(passenger));
+  assert(ferry->tile == passenger->tile);
+
+  ptile = immediate_destination(passenger, ptile);
+
+  if (same_pos(passenger->tile, ptile)) {
+    /* Not an error; sometimes immediate_destination instructs the unit
+     * to stay here. For example, to refuel.*/
+    send_unit_info(NULL, passenger);
+    return TRUE;
+  } else if (passenger->moves_left == 0 && ferry->moves_left == 0) {
+    send_unit_info(NULL, passenger);
+    return TRUE;
+  }
+
+  map = pf_create_map(&parameter->combined);
+  path = pf_get_path(map, ptile);
+
+  if (path) {
+    ai_log_path(passenger, path, &parameter->combined);
+    /* Sea leg */
+    alive = ai_follow_path(ferry, path, ptile);
+    if (alive && passenger->tile != ptile) {
+      /* Ferry has stopped; it is at the landing beach or
+       * has run out of movement points */
+      struct tile *next_tile;
+
+      pft_advance_path(path, passenger->tile);
+      next_tile = path->positions[1].tile;
+      if (!is_ocean(next_tile->terrain)) {
+       UNIT_LOG(LOG_DEBUG, passenger, "Our boat has arrived "
+                "[%d](moves left: %d)", ferry->id, ferry->moves_left);
+       UNIT_LOG(LOG_DEBUG, passenger, "Disembarking to (%d,%d)",
+                TILE_XY(next_tile));
+       /* Land leg */
+       alive = ai_follow_path(passenger, path, ptile);
+       if (0 < ferry->moves_left
+            && (!alive || ferry->tile != passenger->tile)) {
+         /* The passenger is no longer on the ferry,
+          * and the ferry can still act */
+         /* TODO: don't just stand there, do something! */
+       }
+      }
+      /* else at sea */
+    }
+    /* else dead or arrived */
+  } else {
+    /* Not always an error; enemy units might block all paths. */
+    UNIT_LOG(LOG_DEBUG, passenger, "no path to destination");
+  }
+
+  pf_destroy_path(path);
+  pf_destroy_map(map);
+
+  return alive;
+}
+
+/**************************************************************************
+  Move a passenger on a ferry to a specified destination.
+  Return FALSE iff we died.
+**************************************************************************/
+bool aiferry_goto_amphibious(struct unit *ferry,
+                            struct unit *passenger, struct tile *ptile)
+{
+  struct pft_amphibious parameter;
+  struct ai_risk_cost land_risk_cost;
+  struct ai_risk_cost sea_risk_cost;
+  ai_fill_unit_param(&parameter.land, &land_risk_cost, passenger, ptile);
+  if (parameter.land.get_TB != no_fights) {
+    /* Use the ferry to go around danger areas: */
+    parameter.land.get_TB = no_intermediate_fights;
+  }
+  ai_fill_unit_param(&parameter.sea, &sea_risk_cost, ferry, ptile);
+  pft_fill_amphibious_parameter(&parameter);
+
+  /* Move as far along the path to the destination as we can;
+   * that is, ignore the presence of enemy units when computing the
+   * path */
+  parameter.combined.get_zoc = NULL;
+  return ai_amphibious_goto_constrained(ferry, passenger, ptile, &parameter);
+}
+
 /****************************************************************************
   This function is to be called if punit needs to use a boat to get to the 
   destination.
@@ -396,10 +496,6 @@
   TODO: A big one is rendezvous points between units and boats.  When this is 
   implemented, we won't have to be at the coast to ask for a boat to come 
   to us.
-
-  You MUST have warmap created before calling this function in order for 
-  find_beachhead to work here.  This requirement should be removed.  For 
-  example, we can require that (dest_x,dest_y) is on a coast.  
 ****************************************************************************/
 bool aiferry_gobyboat(struct player *pplayer, struct unit *punit,
                      struct tile *dest_tile)
@@ -479,7 +575,6 @@
 
     /* Check if we are the passenger-in-charge */
     if (is_boat_free(ferryboat, punit, 0)) {
-      struct tile *beach_tile;     /* Destination for the boat */
       struct unit *bodyguard = find_unit_by_id(punit->ai.bodyguard);
 
       UNIT_LOG(LOGLEVEL_GOBYBOAT, punit, 
@@ -487,30 +582,12 @@
                ferryboat->id, ferryboat->moves_left, TILE_XY(dest_tile));
       aiferry_psngr_meet_boat(punit, ferryboat);
 
-      /* If the location is not accessible directly from sea
-       * or is defended and we are not marines, we will need a 
-       * landing beach */
-      if (!is_ocean_near_tile(dest_tile)
-          ||((is_non_allied_city_tile(dest_tile, pplayer) 
-              || is_non_allied_unit_tile(dest_tile, pplayer))
-             && !unit_flag(punit, F_MARINES))) {
-        if (!find_beachhead(punit, dest_tile, &beach_tile)) {
-          /* Nowhere to go */
-          return FALSE;
-        }
-        UNIT_LOG(LOGLEVEL_GOBYBOAT, punit, 
-                 "Found beachhead (%d,%d)", TILE_XY(beach_tile));
-      } else {
-       beach_tile = dest_tile;
-      }
-
-      ferryboat->goto_tile = beach_tile;
       punit->goto_tile = dest_tile;
       /* Grab bodyguard */
       if (bodyguard
           && !same_pos(punit->tile, bodyguard->tile)) {
         if (!goto_is_sane(bodyguard, punit->tile, TRUE)
-            || !ai_unit_goto(punit, punit->tile)) {
+            || !ai_unit_goto(bodyguard, punit->tile)) {
           /* Bodyguard can't get there or died en route */
           punit->ai.bodyguard = BODYGUARD_WANTED;
           bodyguard = NULL;
@@ -530,14 +607,12 @@
         assert(same_pos(punit->tile, bodyguard->tile));
        handle_unit_load(pplayer, bodyguard->id, ferryboat->id);
       }
-      if (!ai_unit_goto(ferryboat, beach_tile)) {
+      if(!aiferry_goto_amphibious(ferryboat, punit, dest_tile)) {
         /* died */
         return FALSE;
       }
-      if (!is_tiles_adjacent(ferryboat->tile, beach_tile)
-          && !same_pos(ferryboat->tile, beach_tile)) {
-        /* We are in still transit */
-        return FALSE;
+      if (same_pos(punit->tile, dest_tile)) {
+       handle_unit_activity_request(punit, ACTIVITY_IDLE);
       }
     } else {
       /* Waiting for the boss to load and move us */
@@ -546,10 +621,6 @@
                ferryboat->id, ferryboat->ai.passenger);
       return FALSE;
     }
-
-    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);
   }
 
   return TRUE;
diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/ai/aiferry.h freeciv.PR11995/ai/aiferry.h
--- vendor.freeciv.current/ai/aiferry.h 2005-01-24 20:43:05.000000000 +0000
+++ freeciv.PR11995/ai/aiferry.h        2005-03-23 00:33:08.000000000 +0000
@@ -18,6 +18,7 @@
 #include "fc_types.h"
 
 struct pf_path;
+struct pft_amphibious;
 
 /* 
  * Initialize ferrybaot-related statistics in the ai data.
@@ -36,10 +37,24 @@
 
 /*
  * Go to the destination by hitching a ride on a boat.  Will try to find 
- * a beachhead but it works better if (dest_x, dest_y) is on the coast.
+ * a beachhead but it works better if dst_tile is on the coast.
+ * Loads a bodyguard too, if necessary.
  */
 bool aiferry_gobyboat(struct player *pplayer, struct unit *punit,
                      struct tile *dst_tile);
+/*
+ * Go to the destination on a particular boat.  Will try to find 
+ * a beachhead but it works better if ptile is on the coast.
+ */
+bool aiferry_goto_amphibious(struct unit *ferry,
+                            struct unit *passenger, struct tile *ptile);
+
+bool ai_amphibious_goto_constrained(struct unit *ferry,
+                                   struct unit *passenger,
+                                   struct tile *ptile,
+                                   struct pft_amphibious *parameter);
+
+bool is_boat_free(struct unit *boat, struct unit *punit, int cap);
 
 /*
  * Main boat managing function.  Gets units on board to where they want to
diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/ai/aihunt.c freeciv.PR11995/ai/aihunt.c
--- vendor.freeciv.current/ai/aihunt.c  2005-03-20 12:29:33.000000000 +0000
+++ freeciv.PR11995/ai/aihunt.c 2005-03-23 00:33:08.000000000 +0000
@@ -472,7 +472,9 @@
     return TRUE;
   }
 
-  /* Go towards it. */
+  /* Go towards it.
+   * Land hunters should never hunt targets on different continents,
+   * so no need to use a ferry-enabled goto. */
   if (!ai_unit_goto(punit, target->tile)) {
     return TRUE;
   }
diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/ai/aitools.c freeciv.PR11995/ai/aitools.c
--- vendor.freeciv.current/ai/aitools.c 2005-03-20 12:29:33.000000000 +0000
+++ freeciv.PR11995/ai/aitools.c        2005-03-23 00:33:08.000000000 +0000
@@ -135,13 +135,16 @@
 }
 
 /*************************************************************************
-  This is a function to execute paths returned by the path-finding engine.
+  This is a function to execute paths returned by the path-finding engine,
+  for AI units and units (such as auto explorers) temporarily controlled
+  by the AI.
 
   Brings our bodyguard along.
   Returns FALSE only if died.
 *************************************************************************/
 bool ai_unit_execute_path(struct unit *punit, struct pf_path *path)
 {
+  const bool is_ai = unit_owner(punit)->ai.control;
   int i;
 
   /* We start with i = 1 for i = 0 is our present position */
@@ -157,8 +160,11 @@
     /* We use ai_unit_move() for everything but the last step
      * of the way so that we abort if unexpected opposition
      * shows up. Any enemy on the target tile is expected to
-     * be our target and any attack there intentional. */
-    if (i == path->length - 1) {
+     * be our target and any attack there intentional.
+     * However, do not annoy human players by automatically attacking
+     * using units temporarily under AI control (such as auto-explorers)
+     */
+    if (is_ai && i == path->length - 1) {
       (void) ai_unit_attack(punit, ptile);
     } else {
       (void) ai_unit_move(punit, ptile);
@@ -248,9 +254,6 @@
 
   TODO: A big one is rendezvous points.  When this is implemented, we won't
   have to be at the coast to ask for a boat to come to us.
-
-  You MUST have warmap created before calling this function in order for 
-  find_beachhead to work here. This requirement should be removed.
 ****************************************************************************/
 bool ai_gothere(struct player *pplayer, struct unit *punit,
                 struct tile *dest_tile)
@@ -303,30 +306,437 @@
 }
 
 /**************************************************************************
-  Go to specified destination but do not disturb existing role or activity
-  and do not clear the role's destination. Return FALSE iff we died.
+  Returns the destination for a unit moving towards a given final destination.
+  That is, it gives a suitable way-point, if necessary.
+  For example, aircraft need these way-points to refuel.
+**************************************************************************/
+struct tile *immediate_destination(struct unit *punit,
+                                  struct tile *dest_tile)
+{
+  if (!same_pos(punit->tile, dest_tile) && is_air_unit(punit)) {
+    struct tile *waypoint_tile = punit->goto_tile;
+    if (find_air_first_destination(punit, &waypoint_tile)) {
+      return waypoint_tile;
+    } else {
+      struct player *pplayer = unit_owner(punit);
+      freelog(LOG_VERBOSE, "Did not find an air-route for "
+             "%s's %s at (%d, %d) -> (%d, %d)",
+             pplayer->name, unit_type(punit)->name,
+             TILE_XY(punit->tile), TILE_XY(dest_tile));
+      /* Prevent take off */
+      return punit->tile;
+    }
+  }
+  /* else does not need way-points */
+  return dest_tile;
+}
 
-  FIXME: add some logging functionality to replace GOTO_LOG()
+/**************************************************************************
+  Move a unit along a path without disturbing its activity, role
+  or assigned destination
+  Return FALSE iff we died.
 **************************************************************************/
-bool ai_unit_goto(struct unit *punit, struct tile *ptile)
+bool ai_follow_path(struct unit *punit, struct pf_path *path,
+                   struct tile *ptile)
 {
-  enum goto_result result;
-  struct tile *old_tile;
+  struct tile *old_tile = punit->goto_tile;
   enum unit_activity activity = punit->activity;
+  bool alive;
 
-  old_tile = punit->goto_tile; /* May be NULL. */
-
-  CHECK_UNIT(punit);
-  /* TODO: log error on same_pos with punit->x|y */
   punit->goto_tile = ptile;
   handle_unit_activity_request(punit, ACTIVITY_GOTO);
-  result = do_unit_goto(punit, GOTO_MOVE_ANY, FALSE);
-  if (result != GR_DIED) {
+  alive = ai_unit_execute_path(punit, path);
+  if (alive) {
+    handle_unit_activity_request(punit, ACTIVITY_IDLE);
+    send_unit_info(NULL, punit);
     handle_unit_activity_request(punit, activity);
     punit->goto_tile = old_tile; /* May be NULL. */
+    send_unit_info(NULL, punit);
+  }
+  return alive;
+}
+
+/**************************************************************************
+  Log the cost of travelling a path.
+**************************************************************************/
+void ai_log_path(struct unit *punit,
+                struct pf_path *path, struct pf_parameter *parameter)
+{
+  struct pf_position *last = pf_last_position(path);
+  const int cc = PF_TURN_FACTOR * last->total_MC
+                 + parameter->move_rate * last->total_EC;
+  const int tc = cc / (PF_TURN_FACTOR *parameter->move_rate); 
+
+  UNIT_LOG(LOG_DEBUG, punit, "path L=%d T=%d(%d) MC=%d EC=%d CC=%d",
+          path->length - 1, last->turn, tc,
+          last->total_MC, last->total_EC, cc);
+}
+
+/**************************************************************************
+  Go to specified destination, subject to given PF constraints,
+  but do not disturb existing role or activity
+  and do not clear the role's destination. Return FALSE iff we died.
+
+  parameter: the PF constraints on the computed path. The unit will move
+  as far along the computed path is it can; the movement code will impose
+  all the real constraints (ZoC, etc).
+**************************************************************************/
+bool ai_unit_goto_constrained(struct unit *punit, struct tile *ptile,
+                             struct pf_parameter *parameter)
+{
+  bool alive = TRUE;
+  struct pf_map *map = NULL;
+  struct pf_path *path = NULL;
+
+  ptile = immediate_destination(punit, ptile);
+
+  if (same_pos(punit->tile, ptile)) {
+    /* Not an error; sometimes immediate_destination instructs the unit
+     * to stay here. For example, to refuel.*/
+    send_unit_info(NULL, punit);
+    return TRUE;
+  } else if (!goto_is_sane(punit, ptile, FALSE)) {
+    punit->activity = ACTIVITY_IDLE;
+    send_unit_info(NULL, punit);
+    return TRUE;
+  } else if(punit->moves_left == 0) {
+    send_unit_info(NULL, punit);
     return TRUE;
   }
-  return FALSE;
+
+  map = pf_create_map(parameter);
+  path = pf_get_path(map, ptile);
+
+  if (path) {
+    ai_log_path(punit, path, parameter);
+    alive = ai_follow_path(punit, path, ptile);
+  } else {
+    UNIT_LOG(LOG_DEBUG, punit, "no path to destination");
+  }
+
+  pf_destroy_path(path);
+  pf_destroy_map(map);
+
+  return alive;
+}
+
+
+/*********************************************************************
+  The value of the units belonging to a given player on a given tile.
+*********************************************************************/
+static int stack_value(const struct tile *ptile,
+                      const struct player *pplayer)
+{
+  int cost = 0;
+
+  if (is_stack_vulnerable(ptile)) {
+    unit_list_iterate(ptile->units, punit) {
+      if (unit_owner(punit) == pplayer) {
+       cost += unit_build_shield_cost(punit->type);
+      }
+    } unit_list_iterate_end;
+  }
+
+  return cost;
+}
+
+/*********************************************************************
+  How dangerous would it be stop on a particular tile,
+  because of enemy attacks,
+  expressed as the probability of being killed.
+
+  TODO: This implementation is a kludge until we compute a more accurate
+  probability using the movemap.
+  Also, we should take into account the reduced probability of death
+  if we have a bodyguard travelling with us.
+*********************************************************************/
+static double chance_killed_at(const struct tile *ptile,
+                              struct ai_risk_cost *risk_cost,
+                              struct pf_parameter *param)
+{
+  double db;
+  /* Compute the basic probability */
+  /* WAG */
+  /* In the early stages of a typical game, ferries
+   * are effectively invulnerable (not until Frigates set sail),
+   * so we make seas appear safer.
+   * If we don't do this, the amphibious movement code has too strong a
+   * desire to minimise the length of the path,
+   * leading to poor choice for landing beaches */
+  double p = is_ocean(ptile->terrain)? 0.05: 0.15;
+
+  /* If we are on defensive terrain, we are more likely to survive */
+  db = get_tile_type(ptile->terrain)->defense_bonus;
+  if (map_has_special(ptile, S_RIVER)) {
+    db += (db * terrain_control.river_defense_bonus) / 100;
+  }
+  p *= 10.0 / db;
+
+  return p;
+}
+
+/*********************************************************************
+  PF stack risk cost. How undesirable is passing through a tile
+  because of risks?
+  Weight by the cost of destruction, for risks that can kill the unit.
+
+  Why use the build cost when assessing the cost of destruction?
+  The reasoning is thus.
+  - Assume that all our units are doing necessary jobs;
+    none are surplus to requirements.
+    If that is not the case, we have problems elsewhere :-)
+  - Then any units that are destroyed will have to be replaced.
+  - The cost of replacing them will be their build cost.
+  - Therefore the total (re)build cost is a good representation of the
+    the cost of destruction.
+*********************************************************************/
+static int stack_risk(const struct tile *ptile,
+                     struct ai_risk_cost *risk_cost,
+                     struct pf_parameter *param)
+{
+  double risk = 0;
+  /* Compute the risk of destruction, assuming we will stop at this tile */
+  const double value = risk_cost->base_value
+                       + stack_value(ptile, param->owner);
+  const double p_killed = chance_killed_at(ptile, risk_cost, param);
+  double danger = value * p_killed;
+  if (terrain_has_flag(ptile->terrain, TER_UNSAFE)) {
+    danger += risk_cost->unsafe_terrain_cost;
+  }
+  if (is_ocean(ptile->terrain) && !is_safe_ocean(ptile)) {
+    danger += risk_cost->ocean_cost;
+  }
+
+  /* Adjust for the fact that we might not stop at this tile,
+   * and for our fearfulness */
+  risk += danger * risk_cost->fearfulness;
+
+  /* Adjust for the risk that we might become stuck (for an indefinite period)
+   * if we enter or try to enter the tile. */
+  if (risk_cost->enemy_zoc_cost != 0
+      && (is_non_allied_city_tile(ptile, param->owner)
+         || !is_my_zoc(param->owner, ptile)
+         || is_non_allied_unit_tile(ptile, param->owner))) {
+    /* We could become stuck. */
+    risk += risk_cost->enemy_zoc_cost;
+  }
+
+  return risk;
+}
+
+/*********************************************************************
+  PF extra cost call back to avoid creating tall stacks or
+  crossing dangerous tiles.
+  By setting this as an extra-cost call-back, paths will avoid tall stacks.
+  Avoiding tall stacks *all* along a path is useful because a unit following a
+  path might have to stop early because of ZoCs.
+*********************************************************************/
+static int prefer_short_stacks(const struct tile *ptile,
+                              enum known_type known,
+                              struct pf_parameter *param)
+{
+  return stack_risk(ptile, (struct ai_risk_cost *)param->data, param);
+}
+
+/**********************************************************************
+  Set PF call-backs to favour paths that do not create tall stacks
+  or cross dangerous tiles.
+***********************************************************************/
+void ai_avoid_risks(struct pf_parameter *parameter,
+                   struct ai_risk_cost *risk_cost,
+                   struct unit *punit,
+                   const double fearfulness)
+{
+  const struct player *pplayer = unit_owner(punit);
+  /* If we stay a short time on each tile, the danger of each individual tile
+   * is reduced. If we do not do this,
+   * we will not favour longer but faster routs. */
+  const double linger_fraction = (double)SINGLE_MOVE / parameter->move_rate;
+
+  parameter->data = risk_cost;
+  parameter->get_EC = prefer_short_stacks;
+  parameter->turn_mode = TM_WORST_TIME;
+  risk_cost->base_value = unit_build_shield_cost(punit->type);
+  risk_cost->fearfulness = fearfulness * linger_fraction;
+  if (unit_flag(punit, F_TRIREME)) {
+    risk_cost->ocean_cost = risk_cost->base_value
+      * (double)base_trireme_loss_pct(pplayer, punit)
+      / 100.0;
+  } else {
+    risk_cost->ocean_cost = 0;
+  }
+  risk_cost->unsafe_terrain_cost = risk_cost->base_value
+    * (double)base_unsafe_terrain_loss_pct(pplayer, punit) / 100.0;
+  risk_cost->enemy_zoc_cost = PF_TURN_FACTOR * 20;
+}
+
+/*
+ * The length of time, in turns, which is long enough to be optimistic
+ * that enemy units will have moved from their current position.
+ * WAG
+ */
+#define LONG_TIME 4
+/**************************************************************************
+  Set up the constraints on a path for an AI unit,
+  of for a unit (such as an auto-explorer) temporarily under AI control.
+
+  For non-AI units, take care to prevent cheats, because the AI is omniscient
+  but the players are not.
+
+  parameter:
+     constraints (output)
+  risk_cost:
+     auxiliary data used by the constraints (output)
+  ptile:
+     the destination of the unit.
+     For ferries, the destination may be a coastal land tile,
+     in which case the ferry should stop on an adjacent tile.
+**************************************************************************/
+void ai_fill_unit_param(struct pf_parameter *parameter,
+                       struct ai_risk_cost *risk_cost,
+                       struct unit *punit, struct tile *ptile)
+{
+  const bool is_ferry = get_transporter_capacity(punit) > 0
+                        && !unit_flag(punit, F_MISSILE_CARRIER)
+                        && punit->ai.ai_role != AIUNIT_HUNTER;
+  const bool is_air = is_air_unit(punit)
+                      && punit->ai.ai_role != AIUNIT_ESCORT;
+  const bool long_path = LONG_TIME < (map_distance(punit->tile, punit->tile)
+                                     * SINGLE_MOVE
+                                     / unit_type(punit)->move_rate);
+  const bool barbarian = is_barbarian(unit_owner(punit));
+  const bool is_ai = unit_owner(punit)->ai.control;
+
+  if (is_ferry) {
+    /* The destination may be a coastal land tile,
+     * in which case the ferry should stop on an adjacent tile. */
+    pft_fill_unit_overlap_param(parameter, punit);
+  } else if (is_ai && !is_air && is_military_unit(punit)
+            && (punit->ai.ai_role == AIUNIT_DEFEND_HOME
+                || punit->ai.ai_role == AIUNIT_ATTACK
+                || punit->ai.ai_role ==  AIUNIT_ESCORT
+                || punit->ai.ai_role == AIUNIT_HUNTER)) {
+    /* Use attack movement for defenders and escorts so they can
+     * make defensive attacks */
+    pft_fill_unit_attack_param(parameter, punit);
+  } else {
+    pft_fill_unit_parameter(parameter, punit);
+  }
+
+  /* Should we use the risk avoidance code?
+   * The risk avoidance code uses omniscience, so do not use for
+   * human-player units under temporary AI control.
+   * Air units are immune to most risks, especially dangerous terrain.
+   * Barbarians bravely/stupidly ignore risks
+   */
+  if (is_ai && !is_air && !barbarian) {
+    ai_avoid_risks(parameter, risk_cost, punit, NORMAL_STACKING_FEARFULNESS);
+  }
+
+  /* Should we absolutely forbid ending a turn on a dangerous tile?
+   * Do not annoy human players by killing their units for them.
+   * For AI units be optimistic; allows attacks across dangerous terrain,
+   * and polar settlements.
+   * TODO: This is compatible with old code,
+   * but probably ought to be more cautious for non military units
+   */
+  if (is_ai && !is_ferry && !is_air) {
+    parameter->is_pos_dangerous = NULL;
+  }
+
+  if (is_ai && long_path) {
+    /* Move as far along the path to the destination as we can;
+     * that is, ignore the presence of enemy units when computing the
+     * path.
+     * Hopefully, ai_avoid_risks will have produced a path that avoids enemy
+     * ZoCs. Ignoring ZoCs allows us to move closer to a destination
+     * for which there is not yet a clear path.
+     * That is good if the destination is several turns away,
+     * so we can reasonably expect blocking enemy units to move or
+     * be destroyed. But it can be bad if the destination is one turn away
+     * or our destination is far but there are enemy units near us and on the
+     * shortest path to the destination.
+     */
+    parameter->get_zoc = NULL;
+  }
+
+  if (!is_ai) {
+    /* Do not annoy human players by killing their units for them.
+     * Do not cheat by using information about tiles unknown to the player.
+     */
+    parameter->get_TB = no_fights_or_unknown;
+  } else if ((unit_flag(punit, F_DIPLOMAT))
+      || (unit_flag(punit, F_SPY))) {
+    /* Default tile behaviour */
+  } else if (unit_flag(punit, F_SETTLERS)) {
+    parameter->get_TB = no_fights;
+  } else if (long_path && unit_flag(punit, F_CITIES)) {
+    /* Default tile behaviour;
+     * move as far along the path to the destination as we can;
+     * that is, ignore the presence of enemy units when computing the
+     * path.
+     */
+  } else if (unit_flag(punit, F_CITIES)) {
+    /* Short path */
+    parameter->get_TB = no_fights;
+  } else if (unit_flag(punit, F_TRADE_ROUTE)
+             || unit_flag(punit, F_HELP_WONDER)) {
+    parameter->get_TB = no_fights;
+  } else if (unit_has_role(punit->type, L_BARBARIAN_LEADER)) {
+    /* Avoid capture */
+    parameter->get_TB = no_fights;
+  } else if (is_ferry) {
+    /* Ferries are not warships */
+    parameter->get_TB = no_fights;
+  } else if (is_air) {
+    /* Default tile behaviour */
+  } else if (is_heli_unit(punit)) {
+    /* Default tile behaviour */
+  } else if (is_military_unit(punit)) {
+    switch (punit->ai.ai_role) {
+    case AIUNIT_AUTO_SETTLER:
+    case AIUNIT_BUILD_CITY:
+      /* Strange, but not impossible */
+      parameter->get_TB = no_fights;
+      break;
+    case AIUNIT_DEFEND_HOME:
+    case AIUNIT_ATTACK:
+    case AIUNIT_ESCORT:
+    case AIUNIT_HUNTER:
+      parameter->get_TB = no_intermediate_fights;
+      break;
+    case AIUNIT_RUNAWAY: 
+    case AIUNIT_EXPLORE:
+    case AIUNIT_RECOVER:
+      parameter->get_TB = no_fights;
+      break;
+    default:
+      /* Default tile behaviour */
+      break;
+    }
+  } else {
+    /* Probably an explorer */
+    parameter->get_TB = no_fights;
+  }
+
+  if (is_ferry) {
+    /* Must use TM_WORST_TIME, so triremes move safely */
+    parameter->turn_mode = TM_WORST_TIME;
+    /* Show the destination in the client when watching an AI: */
+    punit->goto_tile = ptile;
+  }
+}
+
+/**************************************************************************
+  Go to specified destination but do not disturb existing role or activity
+  and do not clear the role's destination. Return FALSE iff we died.
+**************************************************************************/
+bool ai_unit_goto(struct unit *punit, struct tile *ptile)
+{
+  struct pf_parameter parameter;
+  struct ai_risk_cost risk_cost;
+  ai_fill_unit_param(&parameter, &risk_cost, punit, ptile);
+  return ai_unit_goto_constrained(punit, ptile, &parameter);
 }
 
 /**************************************************************************
@@ -530,7 +940,9 @@
 }
 
 /**************************************************************************
-  Move an ai unit. Do not attack. Do not leave bodyguard.
+  Move a unit. Do not attack. Do not leave bodyguard.
+  For AI units and units (such as auto explorers) temporarily controlled
+  by the AI.
 
   This function returns only when we have a reply from the server and
   we can tell the calling function what happened to the move request.
@@ -541,12 +953,13 @@
   struct unit *bodyguard;
   int sanity = punit->id;
   struct player *pplayer = unit_owner(punit);
+  const bool is_ai = pplayer->ai.control;
 
   CHECK_UNIT(punit);
-  assert(unit_owner(punit)->ai.control);
   assert(is_tiles_adjacent(punit->tile, ptile));
 
-  /* if enemy, stop and let ai attack function take this case */
+  /* if enemy, stop and give a chance for the ai attack function
+   * or the human player to handle this case */
   if (is_enemy_unit_tile(ptile, pplayer)
       || is_enemy_city_tile(ptile, pplayer)) {
     UNIT_LOG(LOG_DEBUG, punit, "movement halted due to enemy presence");
@@ -559,7 +972,7 @@
   }
 
   /* don't leave bodyguard behind */
-  if (has_bodyguard(punit)
+  if (is_ai && has_bodyguard(punit)
       && (bodyguard = find_unit_by_id(punit->ai.bodyguard))
       && same_pos(punit->tile, bodyguard->tile)
       && bodyguard->moves_left == 0) {
@@ -583,7 +996,7 @@
 
   /* handle the results */
   if (find_unit_by_id(sanity) && same_pos(ptile, punit->tile)) {
-    if (has_bodyguard(punit)) {
+    if (is_ai && has_bodyguard(punit)) {
       ai_unit_bodyguard_move(punit->ai.bodyguard, ptile);
     }
     return TRUE;
diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/ai/aitools.h freeciv.PR11995/ai/aitools.h
--- vendor.freeciv.current/ai/aitools.h 2005-03-20 12:29:33.000000000 +0000
+++ freeciv.PR11995/ai/aitools.h        2005-03-23 00:33:08.000000000 +0000
@@ -21,6 +21,14 @@
 
 struct ai_choice;
 struct pf_path;
+struct pf_parameter;
+struct pft_amphibious;
+
+/*
+ * WAGs: how hard to avoid tall stacks of units.
+ * Pass as fearfulness values to ai_avoid_risks.
+ */
+#define NORMAL_STACKING_FEARFULNESS ((double)PF_TURN_FACTOR / 36.0)
 
 #ifdef DEBUG
 #define CHECK_UNIT(punit)                                        \
@@ -37,15 +45,42 @@
   BODYGUARD_NONE
 };
 
+/*
+ * Initialise using ai_avoid_risks()
+ */
+struct ai_risk_cost
+{
+  double base_value;
+  double fearfulness;
+  double ocean_cost;
+  double unsafe_terrain_cost;
+  double enemy_zoc_cost;
+};
+
 const char *get_ai_role_str(enum ai_unit_task task);
 
 int military_amortize(struct player *pplayer, struct city *pcity, 
                       int value, int delay, int build_cost);
 int stack_cost(struct unit *pdef);
 
+void ai_avoid_risks(struct pf_parameter *parameter,
+                   struct ai_risk_cost *risk_cost,
+                   struct unit *punit,
+                   const double fearfulness);
+void ai_fill_unit_param(struct pf_parameter *parameter,
+                       struct ai_risk_cost *risk_cost,
+                       struct unit *punit, struct tile *ptile);
 bool ai_unit_execute_path(struct unit *punit, struct pf_path *path);
 bool ai_gothere(struct player *pplayer, struct unit *punit, 
                 struct tile *dst_tile);
+struct tile *immediate_destination(struct unit *punit,
+                                  struct tile *dest_tile);
+void ai_log_path(struct unit *punit,
+                struct pf_path *path, struct pf_parameter *parameter);
+bool ai_follow_path(struct unit *punit, struct pf_path *path,
+                   struct tile *ptile);
+bool ai_unit_goto_constrained(struct unit *punit, struct tile *ptile,
+                             struct pf_parameter *parameter);
 bool ai_unit_goto(struct unit *punit, struct tile *ptile);
 
 void ai_unit_new_role(struct unit *punit, enum ai_unit_task task, 
diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/ai/aiunit.c freeciv.PR11995/ai/aiunit.c
--- vendor.freeciv.current/ai/aiunit.c  2005-03-18 22:28:29.000000000 +0000
+++ freeciv.PR11995/ai/aiunit.c 2005-03-23 00:33:08.000000000 +0000
@@ -675,6 +675,10 @@
   struct player *pplayer = unit_owner(punit);
  
   pft_fill_unit_attack_param(&parameter, punit);
+  /* When trying to find rampage targets we ignore risks such as
+   * enemy units because we are looking for trouble!
+   * Hence no call ai_avoid_risks()
+   */
   
   tgt_map = pf_create_map(&parameter);
   while (pf_next(tgt_map)) {
@@ -805,7 +809,7 @@
 
   if (!same_pos(punit->tile, ptile)) {
     if (goto_is_sane(punit, ptile, TRUE)) {
-      if (!ai_unit_goto(punit, ptile)) {
+      if (!ai_gothere(pplayer, punit, ptile)) {
         /* We died */
         return;
       }
@@ -814,21 +818,20 @@
       ai_unit_new_role(punit, AIUNIT_NONE, NULL);
     }
   }
-  /* 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! */
+  /* We might have stopped because of an enemy nearby.
+   * Perhaps we can kill it.*/
   (void) ai_military_rampage(punit, BODYGUARD_RAMPAGE_THRESHOLD,
                              RAMPAGE_FREE_CITY_OR_BETTER);
 }
 
 /*************************************************************************
   Tries to find a land tile adjacent to water and to our target 
-  (dest_x, dest_y).  Prefers tiles which are more defensible and/or
+  (dest_tile).  Prefers tiles which are more defensible and/or
   where we will have moves left.
   FIXME: It checks if the ocean tile is in our Zone of Control?!
 **************************************************************************/
-bool find_beachhead(struct unit *punit, struct tile *dest_tile,
-                   struct tile **beachhead_tile)
+static bool find_beachhead(struct unit *punit, struct tile *dest_tile,
+                          struct tile **beachhead_tile)
 {
   int ok, best = 0;
   Terrain_type_id t;
@@ -873,32 +876,6 @@
   return (best > 0);
 }
 
-/**************************************************************************
-find_beachhead() works only when city is not further that 1 tile from
-the sea. But Sea Raiders might want to attack cities inland.
-So this finds the nearest land tile on the same continent as the city.
-**************************************************************************/
-static void find_city_beach(struct city *pc, struct unit *punit,
-                           struct tile **dest_tile)
-{
-  struct tile *best_tile = punit->tile;
-  int dist = 100;
-  int search_dist = real_map_distance(pc->tile, punit->tile) - 1;
-
-  CHECK_UNIT(punit);
-  
-  square_iterate(punit->tile, search_dist, tile1) {
-    if (map_get_continent(tile1) == map_get_continent(pc->tile)
-        && real_map_distance(punit->tile, tile1) < dist) {
-
-      dist = real_map_distance(punit->tile, tile1);
-      best_tile = tile1;
-    }
-  } square_iterate_end;
-
-  *dest_tile = best_tile;
-}
-
 /*************************************************************************
   Does the unit with the id given have the flag L_DEFEND_GOOD?
 **************************************************************************/
@@ -1749,6 +1726,39 @@
 }
 
 /*************************************************************************
+  Go berserk, assuming there are no targets nearby.
+  TODO: Is it not possible to remove this special casing for barbarians?
+**************************************************************************/
+static void ai_military_attack_barbarian(struct player *pplayer,
+                                        struct unit *punit)
+{
+  struct city *pc;
+
+  if ((pc = dist_nearest_city(pplayer, punit->tile, FALSE, TRUE))) {
+    if (!is_ocean(map_get_terrain(punit->tile))) {
+      UNIT_LOG(LOG_DEBUG, punit, "Barbarian marching to conquer %s", pc->name);
+      (void) ai_gothere(pplayer, punit, pc->tile);
+    } else {
+      struct unit *ferry = NULL;
+
+      unit_list_iterate(punit->tile->units, aunit) {
+       if (is_boat_free(aunit, punit, 2)) {
+         ferry = aunit;
+         break;
+       }
+      } unit_list_iterate_end;
+      if (ferry) {
+       UNIT_LOG(LOG_DEBUG, punit, "Barbarian sailing to conquer %s",
+                pc->name);
+       (void)aiferry_goto_amphibious(ferry, punit, pc->tile);
+      } else {
+       UNIT_LOG(LOG_ERROR, punit, "unable to find barbarian ferry");
+      }
+    }
+  }
+}
+
+/*************************************************************************
   This does the attack until we have used up all our movement, unless we
   should safeguard a city.  First we rampage nearby, then we go
   looking for trouble elsewhere. If there is nothing to kill, sailing units 
@@ -1760,6 +1770,7 @@
   int id = punit->id;
   int ct = 10;
   struct city *pcity = NULL;
+  struct tile *start_tile = punit->tile;
 
   CHECK_UNIT(punit);
 
@@ -1799,6 +1810,14 @@
                  dest_tile->x, dest_tile->y);
         if (!ai_gothere(pplayer, punit, dest_tile)) {
           /* Died or got stuck */
+         if (find_unit_by_id(id)
+             && punit->moves_left && punit->tile != start_tile) {
+           /* Got stuck. Possibly because of adjacency to an
+            * enemy unit. Perhaps we are in luck and are now next to a
+            * tempting target? Let's find out... */
+           (void) ai_military_rampage(punit,
+                                      RAMPAGE_ANYTHING, RAMPAGE_ANYTHING);
+         }
           return;
         }
         if (punit->moves_left <= 0) {
@@ -1851,22 +1870,7 @@
   } else {
     /* You can still have some moves left here, but barbarians should
        not sit helplessly, but advance towards nearest known enemy city */
-    struct city *pc;
-    struct tile *ftile;
-
-    if ((pc = dist_nearest_city(pplayer, punit->tile, FALSE, TRUE))) {
-      if (!is_ocean(map_get_terrain(punit->tile))) {
-        UNIT_LOG(LOG_DEBUG, punit, "Barbarian marching to conquer %s", 
pc->name);
-        (void) ai_gothere(pplayer, punit, pc->tile);
-      } else {
-        /* sometimes find_beachhead is not enough */
-        if (!find_beachhead(punit, pc->tile, &ftile)) {
-          find_city_beach(pc, punit, &ftile);
-        }
-        UNIT_LOG(LOG_DEBUG, punit, "Barbarian sailing to %s", pc->name);
-        (void) ai_gothere(pplayer, punit, ftile);
-      }
-    }
+    ai_military_attack_barbarian(pplayer, punit);
   }
   if ((punit = find_unit_by_id(id)) && punit->moves_left > 0) {
     struct city *pcity = map_get_city(punit->tile);
diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/ai/aiunit.h freeciv.PR11995/ai/aiunit.h
--- vendor.freeciv.current/ai/aiunit.h  2005-02-13 15:07:21.000000000 +0000
+++ freeciv.PR11995/ai/aiunit.h 2005-03-23 00:33:08.000000000 +0000
@@ -62,8 +62,6 @@
                         Unit_Type_id enemy_type);
 int find_something_to_kill(struct player *pplayer, struct unit *punit, 
                           struct tile **ptile);
-bool find_beachhead(struct unit *punit, struct tile *dst_tile,
-                   struct tile **ptile);
 
 int build_cost_balanced(Unit_Type_id type);
 int unittype_att_rating(Unit_Type_id type, int veteran,
diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/common/aicore/path_finding.c 
freeciv.PR11995/common/aicore/path_finding.c
--- vendor.freeciv.current/common/aicore/path_finding.c 2005-03-18 
22:28:28.000000000 +0000
+++ freeciv.PR11995/common/aicore/path_finding.c        2005-03-23 
00:33:07.000000000 +0000
@@ -222,11 +222,15 @@
     /* 2 means can move unrestricted from/into it, 
      * 1 means can move unrestricted into it, but not necessarily from it */
     node->zoc_number = (my_zoc ? 2 : (occupied ? 1 : 0));
+  } else {
+    node->zoc_number = 0;
   }
 
   /* Evaluate the extra cost of the destination */
   if (params->get_EC) {
     node->extra_tile = params->get_EC(ptile, node->node_known_type, params);
+  } else {
+    node->extra_tile = 0;
   }
 }
 
diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/common/aicore/pf_tools.c 
freeciv.PR11995/common/aicore/pf_tools.c
--- vendor.freeciv.current/common/aicore/pf_tools.c     2005-03-18 
22:28:28.000000000 +0000
+++ freeciv.PR11995/common/aicore/pf_tools.c    2005-03-23 00:33:07.000000000 
+0000
@@ -38,10 +38,8 @@
 static int seamove(const struct tile *ptile, enum direction8 dir,
                    const struct tile *ptile1, struct pf_parameter *param)
 {
-  /* MOVE_COST_FOR_VALID_SEA_STEP means ships can move between */
-  if (ptile->move_cost[dir] == MOVE_COST_FOR_VALID_SEA_STEP
-      || is_non_allied_unit_tile(ptile1, param->owner)
-      || is_non_allied_city_tile(ptile1, param->owner)) {
+  if (is_ocean(ptile1->terrain) || ptile1->city
+      || is_non_allied_unit_tile(ptile1, param->owner)) {
     return SINGLE_MOVE;
   } else {
     return PF_IMPOSSIBLE_MC;
@@ -325,6 +323,49 @@
 #endif
 
 
+/*************************************************************
+  A cost function for amphibious movement.
+*************************************************************/
+static int amphibious_move(const struct tile *ptile, enum direction8 dir,
+                          const struct tile *ptile1,
+                          struct pf_parameter *param)
+{
+  const bool ocean = is_ocean(ptile->terrain);
+  const bool ocean1 = is_ocean(ptile1->terrain);
+  struct pft_amphibious *amphibious = (struct pft_amphibious *)param->data;
+  int cost, scale;
+
+  if (ocean && ocean1) {
+    /* Sea move */
+    cost = amphibious->sea.get_MC(ptile, dir, ptile1, &amphibious->sea);
+    scale = amphibious->sea_scale;
+  } else if (ocean && is_allied_city_tile(ptile1, param->owner)) {
+    /* Entering port */
+    cost = amphibious->sea.get_MC(ptile, dir, ptile1, &amphibious->sea);
+    scale = amphibious->sea_scale;
+  } else if (ocean) {
+    /* Disembark; use land movement function to handle F_MARINES */
+    cost = amphibious->land.get_MC(ptile, dir, ptile1, &amphibious->land);
+    scale = amphibious->land_scale;
+  } else if (is_allied_city_tile(ptile, param->owner) && ocean1) {
+    /* Leaving port */
+    cost = amphibious->sea.get_MC(ptile, dir, ptile1, &amphibious->sea);
+    scale = amphibious->sea_scale;
+  } else if (ocean1) {
+    /* Now we have disembarked, our ferry can not help us */
+    cost = PF_IMPOSSIBLE_MC;
+    scale = amphibious->land_scale;
+  } else {
+    /* land move */
+    cost = amphibious->land.get_MC(ptile, dir, ptile1, &amphibious->land);
+    scale = amphibious->land_scale;
+  }
+  if (cost != PF_IMPOSSIBLE_MC) {
+    cost *= scale;
+  }
+  return cost;
+}
+
 /* ===================== Extra Cost Callbacks ======================== */
 
 /*********************************************************************
@@ -344,6 +385,37 @@
 }
 #endif
 
+/*********************************************************************
+  Extra cost call back for amphibious movement
+*********************************************************************/
+static int amphibious_extra_cost(const struct tile *ptile,
+                                enum known_type known,
+                                struct pf_parameter *param)
+{
+  struct pft_amphibious *amphibious = (struct pft_amphibious *)param->data;
+  const bool ocean = is_ocean(ptile->terrain);
+  int cost, scale;
+  if (known == TILE_UNKNOWN) {
+    /* We can travel almost anywhere */
+    cost = SINGLE_MOVE;
+    scale = MAX(amphibious->sea_scale, amphibious->land_scale);
+  } else if (ocean && amphibious->sea.get_EC) {
+    cost = amphibious->sea.get_EC(ptile, known, &amphibious->sea);
+    scale = amphibious->sea_scale;
+  } else if (!ocean && amphibious->land.get_EC) {
+    cost = amphibious->land.get_EC(ptile, known, &amphibious->land);
+    scale = amphibious->land_scale;
+  } else {
+    cost = 0;
+    scale = 1;
+  }
+
+  if (cost != PF_IMPOSSIBLE_MC) {
+    cost *= scale;
+  }
+  return cost;
+}
+
 
 /* ===================== Tile Behaviour Callbacks ==================== */
 
@@ -392,12 +464,44 @@
   return TB_NORMAL;
 }
 
+/********************************************************************** 
+  PF callback to prohibit attacking anyone, except at the destination.
+***********************************************************************/
+enum tile_behavior no_intermediate_fights(const struct tile *ptile,
+                                         enum known_type known,
+                                         struct pf_parameter *param)
+{
+  if (is_non_allied_unit_tile(ptile, param->owner)
+      || is_non_allied_city_tile(ptile, param->owner)) {
+    return TB_DONT_LEAVE;
+  }
+  return TB_NORMAL;
+}
+
+/*********************************************************************
+  A callback for amphibious movement
+*********************************************************************/
+static enum tile_behavior amphibious_behaviour(const struct tile *ptile,
+                                              enum known_type known,
+                                              struct pf_parameter *param)
+{
+  struct pft_amphibious *amphibious = (struct pft_amphibious *)param->data;
+  const bool ocean = is_ocean(ptile->terrain);
+  if (ocean && amphibious->sea.get_TB) {
+    return amphibious->sea.get_TB(ptile, known, &amphibious->sea);
+  } else if (!ocean && amphibious->land.get_TB) {
+    return amphibious->land.get_TB(ptile, known, &amphibious->land);
+  }
+  return TB_NORMAL;
+}
 
 /* =====================  Postion Dangerous Callbacks ================ */
 
 /**********************************************************************
   An example of position-dangerous callback.  For triremes.
   FIXME: it cheats.
+  Allow one move onto land (for use for ferries and land
+  bombardment)
 ***********************************************************************/
 static bool trireme_is_pos_dangerous(const struct tile *ptile,
                                     enum known_type known,
@@ -406,8 +510,10 @@
   /* We test TER_UNSAFE even though under the current ruleset there is no
    * way for a trireme to be on a TER_UNSAFE tile. */
   /* Unsafe or unsafe-ocean tiles without cities are dangerous. */
-  return ((terrain_has_flag(ptile->terrain, TER_UNSAFE) 
-         || (is_ocean(ptile->terrain) && !is_safe_ocean(ptile)))
+  /* Pretend all land tiles are safe. */
+  return (is_ocean(ptile->terrain)
+         && (terrain_has_flag(ptile->terrain, TER_UNSAFE) 
+             || (is_ocean(ptile->terrain) && !is_safe_ocean(ptile)))
          && ptile->city == NULL);
 }
 /****************************************************************************
@@ -434,7 +540,23 @@
 }
 
 /**********************************************************************
-  Position-dangerous callback for all units other than triremes.
+  Position-dangerous callback for sea units other than triremes.
+  Allow one move onto land (for use for ferries and land
+  bombardment)
+***********************************************************************/
+static bool is_overlap_pos_dangerous(const struct tile *ptile,
+                                    enum known_type known,
+                                    struct pf_parameter *param)
+{
+  /* Unsafe tiles without cities are dangerous. */
+  /* Pretend all land tiles are safe. */
+  return (is_ocean(ptile->terrain)
+         && terrain_has_flag(ptile->terrain, TER_UNSAFE)
+         && ptile->city == NULL);
+}
+
+/**********************************************************************
+  Position-dangerous callback for typical units.
 ***********************************************************************/
 static bool is_pos_dangerous(const struct tile *ptile, enum known_type known,
                             struct pf_parameter *param)
@@ -444,6 +566,23 @@
          && ptile->city == NULL);
 }
 
+/**********************************************************************
+  Position-dangerous callback for amphibious movement.
+***********************************************************************/
+static bool amphibious_is_pos_dangerous(const struct tile *ptile,
+                                       enum known_type known,
+                                       struct pf_parameter *param)
+{
+  struct pft_amphibious *amphibious = (struct pft_amphibious *)param->data;
+  const bool ocean = is_ocean(ptile->terrain);
+  if (ocean && amphibious->sea.is_pos_dangerous) {
+    return amphibious->sea.is_pos_dangerous(ptile, known, param);
+  } else if (!ocean && amphibious->land.is_pos_dangerous) {
+    return amphibious->land.is_pos_dangerous(ptile, known, param);
+  }
+  return FALSE;
+}
+
 /* =====================  Tools for filling parameters =============== */
 
 /**********************************************************************
@@ -513,15 +652,31 @@
 void pft_fill_unit_overlap_param(struct pf_parameter *parameter,
                                 struct unit *punit)
 {
+  const bool trireme_danger = unit_flag(punit, F_TRIREME)
+                     && base_trireme_loss_pct(unit_owner(punit), punit) > 0;
+  const bool danger = base_unsafe_terrain_loss_pct(unit_owner(punit), punit)
+                      > 0;
+
   pft_fill_unit_default_parameter(parameter, punit);
 
   switch (unit_type(punit)->move_type) {
   case LAND_MOVING:
     parameter->get_MC = land_overlap_move;
     parameter->get_TB = dont_cross_ocean;
+
+    assert(!trireme_danger);
+    if (danger) {
+      parameter->is_pos_dangerous = is_pos_dangerous;
+    }
     break;
   case SEA_MOVING:
     parameter->get_MC = sea_overlap_move;
+
+    if (trireme_danger) {
+      parameter->is_pos_dangerous = trireme_is_pos_dangerous;
+    } else if (danger) {
+      parameter->is_pos_dangerous = is_overlap_pos_dangerous;
+    }
     break;
   case AIR_MOVING:
   case HELI_MOVING:
@@ -529,13 +684,6 @@
   }
 
   parameter->get_zoc = NULL;
-
-  if (unit_flag(punit, F_TRIREME)
-      && base_trireme_loss_pct(unit_owner(punit), punit) > 0) {
-    parameter->is_pos_dangerous = trireme_is_pos_dangerous;
-  } else if (base_unsafe_terrain_loss_pct(unit_owner(punit), punit) > 0) {
-    parameter->is_pos_dangerous = is_pos_dangerous;
-  }
 }
 
 /**********************************************************************
@@ -570,6 +718,37 @@
 }
 
 /**********************************************************************
+  Fill parameters for combined sea-land movement.
+  This is suitable for the case of a land unit riding a ferry.
+  The starting position of the ferry is taken to be the starting position for
+  the PF. The passenger is assumed to initailly be on the given ferry.
+  The destination may be inland, in which case the passenger will ride
+  the ferry to a beach head, disembark, then continue on land.
+  One complexity of amphibious movement is that the movement rate on land
+  might be different from that at sea. We therefore scale up the movement rates
+  (and the corresponding movement consts) to the product of the two rates.
+***********************************************************************/
+void pft_fill_amphibious_parameter(struct pft_amphibious *parameter)
+{
+  int move_rate;
+  parameter->combined = parameter->sea;
+  move_rate = parameter->land.move_rate * parameter->sea.move_rate;
+  parameter->land_scale = move_rate / parameter->land.move_rate;
+  parameter->sea_scale = move_rate / parameter->sea.move_rate;
+  parameter->combined.moves_left_initially *= parameter->sea_scale;
+  parameter->combined.move_rate = move_rate;
+  /* To ensure triremes behave correctly: */
+  parameter->combined.turn_mode = TM_WORST_TIME;
+  parameter->combined.get_MC = amphibious_move;
+  parameter->combined.get_TB = amphibious_behaviour;
+  parameter->combined.get_EC = amphibious_extra_cost;
+  parameter->combined.is_pos_dangerous = amphibious_is_pos_dangerous;
+  BV_CLR_ALL(parameter->combined.unit_flags);
+
+  parameter->combined.data = parameter;
+}
+
+/**********************************************************************
   Fill general use parameters to defaults
 ***********************************************************************/
 static void pft_fill_unit_default_parameter(struct pf_parameter *parameter,
@@ -626,3 +805,27 @@
   }
   return dest_path;
 }
+
+/**********************************************************************
+  Remove the part of a path leading up to a given tile.
+  The given tile must be on the path.
+***********************************************************************/
+void pft_advance_path(struct pf_path *path,
+                     struct tile *ptile)
+{
+  int i;
+  struct pf_position *new_positions;
+  for(i = 0; i < path->length; i++) {
+    if (path->positions[i].tile == ptile) {
+      break;
+    }
+  }
+  assert(i < path->length);
+  path->length -= i;
+  new_positions = 
+    fc_malloc(sizeof(*path->positions) * path->length);
+  memcpy(new_positions, path->positions + i,
+        path->length * sizeof(*path->positions));
+  free(path->positions);
+  path->positions = new_positions;
+}
diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/common/aicore/pf_tools.h 
freeciv.PR11995/common/aicore/pf_tools.h
--- vendor.freeciv.current/common/aicore/pf_tools.h     2005-01-24 
20:43:03.000000000 +0000
+++ freeciv.PR11995/common/aicore/pf_tools.h    2005-03-23 00:33:07.000000000 
+0000
@@ -15,19 +15,49 @@
 
 #include "path_finding.h"
 
+
+
+/* 
+ * Use to create 'amphibious' paths. An amphibious path starts on a sea tile,
+ * perhaps goes over some other sea tiles, then perhaps goes over some land
+ * tiles. This is suitable for a land unit riding on a ferry.
+ * If you want, you can use different call-backs for the land and sea legs,
+ * by changing the 'land' and 'sea' fields.
+ * Initialise the 'land' and 'sea' fields, then initialise the other
+ * fields using using pft_fill_amphibious_parameter().
+ * Give the 'combined' field to pf_create_map to create a map
+ * for finding the path.
+ */
+struct pft_amphibious
+{
+  struct pf_parameter combined;
+  struct pf_parameter land;
+  struct pf_parameter sea;
+  int land_scale;
+  int sea_scale;
+};
+
+
+
 struct pf_path *pft_concat(struct pf_path *dest_path,
                           const struct pf_path *src_path);
+void pft_advance_path(struct pf_path *path,
+                     struct tile *ptile);
 void pft_fill_unit_parameter(struct pf_parameter *parameter,
                             struct unit *punit);
 void pft_fill_unit_overlap_param(struct pf_parameter *parameter,
                                 struct unit *punit);
 void pft_fill_unit_attack_param(struct pf_parameter *parameter,
                                 struct unit *punit);
+void pft_fill_amphibious_parameter(struct pft_amphibious *parameter);
 enum tile_behavior no_fights_or_unknown(const struct tile *ptile,
                                         enum known_type known,
                                         struct pf_parameter *param);
 enum tile_behavior no_fights(const struct tile *ptile, enum known_type known,
                             struct pf_parameter *param);
+enum tile_behavior no_intermediate_fights(const struct tile *ptile,
+                                         enum known_type known,
+                                         struct pf_parameter *param);
 
 #define pf_iterator(map, position) {                       \
   struct pf_position position;                             \
diff -ruN -Xvendor.freeciv.current/diff_ignore 
vendor.freeciv.current/server/unittools.c freeciv.PR11995/server/unittools.c
--- vendor.freeciv.current/server/unittools.c   2005-03-18 22:28:27.000000000 
+0000
+++ freeciv.PR11995/server/unittools.c  2005-03-23 00:32:44.000000000 +0000
@@ -1924,8 +1924,8 @@
 
 /**************************************************************************
   Send the unit into to those connections in dest which can see the units
-  at it's position, or the specified (x,y) (if different).
-  Eg, use x and y as where the unit came from, so that the info can be
+  at it's position, or the specified ptile (if different).
+  Eg, use ptile as where the unit came from, so that the info can be
   sent if the other players can see either the target or destination tile.
   dest = NULL means all connections (game.game_connections)
 **************************************************************************/

[Prev in Thread] Current Thread [Next in Thread]