[freeciv-ai] (PR#11995) Stupid AI Creates Tall Stacks
[Top] [All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
<URL: http://bugs.freeciv.org/Ticket/Display.html?id=11995 >
I applied the aicore and server parts of the patch. Attached is the
rest. Per you may want to take this.
-jason
Index: ai/aiferry.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiferry.c,v
retrieving revision 1.17
diff -u -r1.17 aiferry.c
--- ai/aiferry.c 21 Mar 2005 12:28:00 -0000 1.17
+++ ai/aiferry.c 31 Mar 2005 18:29:02 -0000
@@ -275,7 +275,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
@@ -391,6 +391,128 @@
/* ============================= go by boat ============================== */
+/**************************************************************************
+ Manage the passengers on a ferry, even if they are asleep.
+ This is suitable for when the commander of a ferry has left;
+ it gives a chance for another passenger to take control.
+**************************************************************************/
+static void ai_activate_passengers(struct unit *ferry)
+{
+ unit_list_iterate(ferry->tile->units, aunit) {
+ if (aunit->transported_by == ferry->id) {
+ handle_unit_activity_request(aunit, ACTIVITY_IDLE);
+ aunit->ai.done = FALSE;
+ ai_manage_unit(unit_owner(aunit), aunit);
+ }
+ } unit_list_iterate_end;
+}
+
+/**************************************************************************
+ 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(¶meter->combined);
+ path = pf_get_path(map, ptile);
+
+ if (path) {
+ ai_log_path(passenger, path, ¶meter->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.
+ * Give a chance for another passenger to take command
+ * of the ferry.
+ */
+ UNIT_LOG(LOG_DEBUG, ferry, "Activating passengers");
+ ai_activate_passengers(ferry);
+ /* A passenger might sail the ferry to its doom, so we can no longer
+ * trust the ferry pointer, but nothing can destroy punit. */
+ }
+ }
+ /* 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(¶meter.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(¶meter.sea, &sea_risk_cost, ferry, ptile);
+ pft_fill_amphibious_parameter(¶meter);
+
+ /* 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, ¶meter);
+}
+
/****************************************************************************
This function is to be called if punit needs to use a boat to get to the
destination.
@@ -400,10 +522,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)
@@ -484,7 +602,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,
@@ -492,30 +609,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;
@@ -536,12 +635,14 @@
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)) {
+ if (same_pos(punit->tile, dest_tile)) {
+ /* Arrived */
+ handle_unit_activity_request(punit, ACTIVITY_IDLE);
+ } else {
/* We are in still transit */
punit->ai.done = TRUE;
return FALSE;
@@ -553,21 +654,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);
- 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;
Index: ai/aiferry.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiferry.h,v
retrieving revision 1.3
diff -u -r1.3 aiferry.h
--- ai/aiferry.h 29 Sep 2004 02:24:18 -0000 1.3
+++ ai/aiferry.h 31 Mar 2005 18:29:02 -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
Index: ai/aihunt.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aihunt.c,v
retrieving revision 1.15
diff -u -r1.15 aihunt.c
--- ai/aihunt.c 19 Mar 2005 23:45:34 -0000 1.15
+++ ai/aihunt.c 31 Mar 2005 18:29:02 -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;
}
Index: ai/aitools.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.c,v
retrieving revision 1.142
diff -u -r1.142 aitools.c
--- ai/aitools.c 21 Mar 2005 12:28:00 -0000 1.142
+++ ai/aitools.c 31 Mar 2005 18:29:02 -0000
@@ -131,13 +131,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 */
@@ -153,8 +156,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);
@@ -244,9 +250,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)
@@ -299,30 +302,436 @@
}
/**************************************************************************
- 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_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(¶meter, &risk_cost, punit, ptile);
+ return ai_unit_goto_constrained(punit, ptile, ¶meter);
}
/**************************************************************************
@@ -531,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.
@@ -542,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");
@@ -560,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) {
@@ -584,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;
Index: ai/aitools.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.h,v
retrieving revision 1.51
diff -u -r1.51 aitools.h
--- ai/aitools.h 20 Mar 2005 11:02:32 -0000 1.51
+++ ai/aitools.h 31 Mar 2005 18:29:02 -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,
Index: ai/aiunit.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiunit.c,v
retrieving revision 1.348
diff -u -r1.348 aiunit.c
--- ai/aiunit.c 22 Mar 2005 20:18:52 -0000 1.348
+++ ai/aiunit.c 31 Mar 2005 18:29:03 -0000
@@ -627,6 +627,10 @@
struct player *pplayer = unit_owner(punit);
pft_fill_unit_attack_param(¶meter, 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(¶meter);
while (pf_next(tgt_map)) {
@@ -757,7 +761,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;
}
@@ -766,9 +770,8 @@
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.*/
if (ai_military_rampage(punit, BODYGUARD_RAMPAGE_THRESHOLD,
RAMPAGE_FREE_CITY_OR_BETTER)
&& same_pos(punit->tile, ptile)) {
@@ -778,12 +781,12 @@
/*************************************************************************
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;
@@ -828,32 +831,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?
**************************************************************************/
@@ -1646,6 +1623,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
@@ -1657,6 +1667,7 @@
int id = punit->id;
int ct = 10;
struct city *pcity = NULL;
+ struct tile *start_tile = punit->tile;
CHECK_UNIT(punit);
@@ -1689,6 +1700,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) {
@@ -1741,22 +1760,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);
Index: ai/aiunit.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiunit.h,v
retrieving revision 1.57
diff -u -r1.57 aiunit.h
--- ai/aiunit.h 3 Feb 2005 09:48:53 -0000 1.57
+++ ai/aiunit.h 31 Mar 2005 18:29:03 -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,
|
|