[freeciv-ai] (PR#8777) Find ferry
[Top] [All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
<URL: http://rt.freeciv.org/Ticket/Display.html?id=8777 >
> [glip - Wed May 19 18:37:19 2004]:
>
> Here is the implementation of find_ferry function torn out from the new
> settler code. It is tested using the old settler code and it works, see
> settler4.gz. Now even settlers inland can find a ferry far away in the
> seas to take them to a new continent.
>
> New find_ferry should eventually replace all uses of find_boat.
> Changes to server/settler.c are for testing only and can be removed.
An updated code. Now it won't look for a ferry if there are none to be
found. How does it know? aidata does the counting.
I attach:
find_ferry2.diff -- the patch in the form that should go in
find_ferry_test.diff -- with various debug messages switched on and
server/settlers.c wired to work with it
settler4.gz -- a test game
Per, I recommend you to have a look at the performance of Syela's
settler code in this case. Modulo minor problems like caravel going off
to explore while a city is building the settler -- instead of waiting
for the settler to be finished (this is actually settler code fault), it
performs very well and covers the island with 4 cities, something I am
not sure your/our code does. Maybe you can tune the parameters, but
after I fix some stuff with ferries building.
G.
? ferry_and_settler.txt
? settle4.gz
? stuck.gz
? ai/aisettler.c
? ai/aisettler.h
? common/aicore/citymap.c
? common/aicore/citymap.h
Index: ai/aidata.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidata.c,v
retrieving revision 1.24
diff -u -r1.24 aidata.c
--- ai/aidata.c 1 May 2004 03:22:11 -0000 1.24
+++ ai/aidata.c 21 May 2004 04:57:53 -0000
@@ -395,8 +395,9 @@
}
/**************************************************************************
- Use this wrapper to correctly update the statistics. Use NULL to
- unregister any ferry that might be there.
+ Use this wrapper to correctly update the statistics. Use ferry=NULL to
+ request a ferry. Should be used in conjunction with ai_set_passenger
+ if ferry!=NULL.
**************************************************************************/
void ai_set_ferry(struct unit *punit, struct unit *ferry)
{
@@ -409,7 +410,6 @@
} else if (ferry) {
/* Make sure we delete punit from the list of potential passengers */
ai_clear_ferry(punit);
- ferry->ai.passenger = punit->id;
punit->ai.ferryboat = ferry->id;
}
}
@@ -458,6 +458,37 @@
}
/**************************************************************************
+ Returns the number of available boats. A simple accessor made to perform
+ debug checks.
+**************************************************************************/
+int ai_available_boats(struct player *pplayer)
+{
+ struct ai_data *ai = ai_data_get(pplayer);
+
+ /* To developer: Switch this checking on when testing some new
+ * ferry code. */
+#if 1
+ int boats = 0;
+
+ unit_list_iterate(pplayer->units, punit) {
+ struct tile *ptile = map_get_tile(punit->x, punit->y);
+
+ if (is_sailing_unit(punit) && is_ground_units_transport(punit)
+ && punit->ai.passenger == FERRY_AVAILABLE) {
+ boats++;
+ }
+ } unit_list_iterate_end;
+
+ if (boats != ai->stats.available_boats) {
+ freelog(LOG_ERROR, "Boats miscounted: recorded %d but in reality %d",
+ ai->stats.available_boats, boats);
+ }
+#endif
+
+ return ai->stats.available_boats;
+}
+
+/**************************************************************************
Deinitialize data
**************************************************************************/
void ai_data_done(struct player *pplayer)
Index: ai/aidata.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidata.h,v
retrieving revision 1.11
diff -u -r1.11 aidata.h
--- ai/aidata.h 9 Oct 2003 00:07:33 -0000 1.11
+++ ai/aidata.h 21 May 2004 04:57:54 -0000
@@ -138,6 +138,7 @@
void ai_set_passenger(struct unit *punit, struct unit *passenger);
void ai_set_ferry(struct unit *punit, struct unit *ferry);
void ai_clear_ferry(struct unit *punit);
+int ai_available_boats(struct player *pplayer);
#endif
Index: ai/aitools.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.c,v
retrieving revision 1.103
diff -u -r1.103 aitools.c
--- ai/aitools.c 4 May 2004 17:40:25 -0000 1.103
+++ ai/aitools.c 21 May 2004 04:58:00 -0000
@@ -97,8 +97,8 @@
This is a function to execute paths returned by the path-finding engine.
It is analogous to goto_route_execute, only much simpler.
- We use ai_unit_attack which means "move if target unoccupied, attack
- otherwise" and also brings our bodyguard along.
+ Brings our bodyguard along.
+ Returns FALSE only if died.
*************************************************************************/
bool ai_unit_execute_path(struct unit *punit, struct pf_path *path)
{
@@ -107,7 +107,6 @@
/* We start with i = 1 for i = 0 is our present position */
for (i = 1; i < path->length; i++) {
int x = path->positions[i].x, y = path->positions[i].y;
- bool result;
int id = punit->id;
/* We use ai_unit_move() for everything but the last step
@@ -115,12 +114,11 @@
* shows up. Any enemy on the target tile is expected to
* be our target and any attack there intentional. */
if (i == path->length - 1) {
- result = ai_unit_attack(punit, x, y);
+ (void) ai_unit_attack(punit, x, y);
} else {
- ai_unit_move(punit, x, y);
- result = (find_unit_by_id(id) != NULL);
+ (void) ai_unit_move(punit, x, y);
}
- if (!result) {
+ if (!find_unit_by_id(id)) {
/* Died... */
return FALSE;
}
@@ -206,7 +204,137 @@
/* What if we have a bodyguard, but don't need one? */
}
-#define LOGLEVEL_GOTHERE LOG_DEBUG
+/****************************************************************************
+ Combined cost function for a land unit looking for a ferry. The path
+ finding first goes over the continent and then into the ocean where we
+ actually look for ferry. Thus moves land-to-sea are allowed and moves
+ sea-to-land are not. A consequence is that we don't get into the cities
+ on other continent, which might station boats. This defficiency seems to
+ be impossible to fix with the current PF structure, so it has to be
+ accounted for in the actual ferry search function.
+
+ For movements sea-to-sea the cost is collected via the extra cost
+ call-back. Doesn't care for enemy/neutral tiles, these should be excluded
+ using a TB call-back.
+****************************************************************************/
+static int combined_land_sea_move(int x, int y, enum direction8 dir,
+ int x1, int y1,
+ struct pf_parameter *param)
+{
+ struct tile *src_tile = map_get_tile(x, y);
+ struct tile *tgt_tile = map_get_tile(x1, y1);
+ int move_cost;
+
+ if (is_ocean(tgt_tile->terrain)) {
+ /* Any-to-Sea */
+ move_cost = 0;
+ } else if (src_tile->terrain == T_OCEAN) {
+ /* Sea-to-Land */
+ move_cost = PF_IMPOSSIBLE_MC;
+ } else {
+ /* Land-to-Land */
+ move_cost = src_tile->move_cost[dir];
+ }
+
+ return move_cost;
+}
+
+/****************************************************************************
+ EC callback to account for the cost of sea moves by a ferry hurrying to
+ pick our unit up.
+****************************************************************************/
+static int sea_move(int x, int y, enum known_type known,
+ struct pf_parameter *param)
+{
+ if (is_ocean(map_get_tile(x, y)->terrain)) {
+ /* Approximately TURN_FACTOR / average ferry move rate
+ * we can pass a better guess of the move rate through param->data
+ * but we don't know which boat we will find out there */
+ return SINGLE_MOVE * PF_TURN_FACTOR / 12;
+ } else {
+ return 0;
+ }
+}
+
+#define LOGLEVEL_FINDFERRY LOG_NORMAL
+/****************************************************************************
+ Proper and real PF function for finding a boat. If you don't require
+ the path to the ferry, pass path=NULL.
+
+ WARNING: Due to the nature of this function and PF (see the comment of
+ combined_land_sea_move), the path won't lead onto the boat itself.
+
+ FIXME: Actually check the capacity.
+****************************************************************************/
+int find_ferry(struct unit *punit, int cap, struct pf_path **path)
+{
+ int best_turns = FC_INFINITY;
+ int best_id = 0;
+ struct pf_parameter param;
+ struct pf_map *search_map;
+
+
+ UNIT_LOG(LOGLEVEL_FINDFERRY, punit, "asked find_ferry for a boat");
+
+ if (ai_available_boats(unit_owner(punit)) <= 0
+ && punit->ai.ferryboat <= 0) {
+ /* No boats to be found (the second check is to ensure that we are not
+ * the ones keeping the last boat busy) */
+ return 0;
+ }
+
+ pft_fill_unit_parameter(¶m, punit);
+ param.turn_mode = TM_WORST_TIME;
+ param.get_TB = no_fights_or_unknown;
+ param.get_EC = sea_move;
+ param.get_MC = combined_land_sea_move;
+
+ search_map = pf_create_map(¶m);
+
+ pf_iterator(search_map, pos) {
+ int radius = (is_ocean(map_get_tile(pos.x, pos.y)->terrain) ? 1 : 0);
+
+ if (pos.turn + pos.total_EC/PF_TURN_FACTOR > best_turns) {
+ /* Won't find anything better */
+ /* FIXME: This condition is somewhat dodgy */
+ break;
+ }
+
+ square_iterate(pos.x, pos.y, radius, x, y) {
+ struct tile *ptile = map_get_tile(x, y);
+
+ unit_list_iterate(ptile->units, aunit) {
+ if (is_ground_units_transport(aunit)
+ && (aunit->ai.passenger == FERRY_AVAILABLE
+ || aunit->ai.passenger == punit->id)) {
+ /* Turns for the unit to get to rendezvous pnt */
+ int u_turns = pos.turn;
+ /* Turns for the boat to get to the rendezvous pnt */
+ int f_turns = ((pos.total_EC / PF_TURN_FACTOR * 16
+ - aunit->moves_left)
+ / unit_type(aunit)->move_rate);
+ int turns = MAX(u_turns, f_turns);
+
+ if (turns < best_turns) {
+ UNIT_LOG(LOGLEVEL_FINDFERRY, punit,
+ "Found a potential boat %s[%d](%d,%d)",
+ unit_type(aunit)->name, aunit->id, aunit->x, aunit->y);
+ if (path) {
+ *path = pf_next_get_path(search_map);
+ }
+ best_turns = turns;
+ best_id = aunit->id;
+ }
+ }
+ } unit_list_iterate_end;
+ } square_iterate_end;
+ } pf_iterator_end;
+ pf_destroy_map(search_map);
+
+ return best_id;
+}
+
+#define LOGLEVEL_GOTHERE LOG_NORMAL
/****************************************************************************
This is ferry-enabled goto. Should not normally be used for non-ferried
units (i.e. planes or ships), use ai_unit_goto instead.
@@ -220,6 +348,7 @@
int dest_x, int dest_y)
{
struct unit *bodyguard = find_unit_by_id(punit->ai.bodyguard);
+ struct pf_path *path_to_ferry = NULL;
CHECK_UNIT(punit);
@@ -242,8 +371,7 @@
dest_x, dest_y);
if (boatid <= 0) {
- int bx, by;
- boatid = find_boat(pplayer, &bx, &by, 2);
+ boatid = find_ferry(punit, 2, &path_to_ferry);
}
ferryboat = find_unit_by_id(boatid);
@@ -259,21 +387,29 @@
}
ai_set_ferry(punit, ferryboat);
- if (!same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)
- && (!is_at_coast(punit->x, punit->y)
- || is_tiles_adjacent(punit->x, punit->y,
- ferryboat->x, ferryboat->y))) {
+ ai_set_passenger(ferryboat, punit);
+
+ if (!is_tiles_adjacent(punit->x, punit->y, ferryboat->x, ferryboat->y)
+ && !same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)
+ && !is_at_coast(punit->x, punit->y)) {
/* Go to the boat only if it cannot reach us or it's parked
* next to us. Otherwise just wait (boats are normally faster) */
- /* FIXME: agree on a rendez-vous point */
- /* FIXME: this can lose bodyguard */
+ /* TODO: agree on a rendez-vous point */
UNIT_LOG(LOGLEVEL_GOTHERE, punit, "going to boat[%d](%d,%d).", boatid,
ferryboat->x, ferryboat->y);
- if (!ai_unit_goto(punit, ferryboat->x, ferryboat->y)) {
+ /* The path can be amphibious so we will stop at the coast.
+ * It might not lead _onto_ the boat. */
+ if (!ai_unit_execute_path(punit, path_to_ferry)) {
/* Died. */
+ pf_destroy_path(path_to_ferry);
return FALSE;
}
}
+ pf_destroy_path(path_to_ferry);
+
+ if (is_tiles_adjacent(punit->x, punit->y, ferryboat->x, ferryboat->y)) {
+ (void) ai_unit_move(punit, ferryboat->x, ferryboat->y);
+ }
if (!same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)) {
/* Didn't get to the boat */
Index: ai/aitools.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.h,v
retrieving revision 1.41
diff -u -r1.41 aitools.h
--- ai/aitools.h 9 Jan 2004 16:59:49 -0000 1.41
+++ ai/aitools.h 21 May 2004 04:58:00 -0000
@@ -78,5 +78,7 @@
bool ai_wants_no_science(struct player *pplayer);
bool is_player_dangerous(struct player *pplayer, struct player *aplayer);
+int find_ferry(struct unit *punit, int cap, struct pf_path **path);
+
#endif /* FC__AITOOLS_H */
Index: ai/aiunit.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiunit.c,v
retrieving revision 1.315
diff -u -r1.315 aiunit.c
--- ai/aiunit.c 19 May 2004 14:40:15 -0000 1.315
+++ ai/aiunit.c 21 May 2004 04:58:16 -0000
@@ -2415,8 +2415,6 @@
static void ai_manage_ferryboat(struct player *pplayer, struct unit *punit)
{
struct city *pcity;
- int oldbossid = -1; /* Loop prevention. If boss doesn't want to move,
- * neither do we. */
CHECK_UNIT(punit);
@@ -2438,17 +2436,20 @@
/* Do we have the passenger-in-charge on board? */
struct tile *ptile = map_get_tile(punit->x, punit->y);
- if (punit->ai.passenger > 0
- && !unit_list_find(&ptile->units, punit->ai.passenger)) {
- UNIT_LOG(LOGLEVEL_FERRY, punit,
- "lost passenger-in-charge[%d], resetting",
- punit->ai.passenger);
- ai_set_passenger(punit, NULL);
- } else if (oldbossid > 0) {
- /* Need to look for a new boss */
- UNIT_LOG(LOGLEVEL_FERRY, punit, "taking control back from [%d]",
- oldbossid);
- ai_set_passenger(punit, NULL);
+ if (punit->ai.passenger > 0) {
+ struct unit *psngr = find_unit_by_id(punit->ai.passenger);
+
+ /* If the passenger-in-charge is adjacent, we should wait for it to
+ * board. We will pass control to it later.
+ * FIXME: A possible side-effect: a boat will linger near a passenger
+ * which already landed. */
+ if (!psngr
+ || real_map_distance(punit->x, punit->y, psngr->x, psngr->y) > 1) {
+ UNIT_LOG(LOGLEVEL_FERRY, punit,
+ "lost passenger-in-charge[%d], resetting",
+ punit->ai.passenger);
+ punit->ai.passenger = 0;
+ }
}
if (punit->ai.passenger <= 0) {
@@ -2470,11 +2471,6 @@
}
} unit_list_iterate_end;
- if (candidate && candidate->id == oldbossid) {
- /* The boss decided to stay put on the ferry. We aren't moving. */
- return;
- }
-
if (candidate) {
UNIT_LOG(LOGLEVEL_FERRY, punit,
"appointed %s[%d] our passenger-in-charge",
@@ -2487,11 +2483,11 @@
}
if (punit->ai.passenger > 0) {
+ int bossid = punit->ai.passenger; /* Loop prevention */
struct unit *boss = find_unit_by_id(punit->ai.passenger);
int id = punit->id; /* To check if survived */
assert(boss != NULL);
- oldbossid = punit->ai.passenger;
if (unit_flag(boss, F_SETTLERS) || unit_flag(boss, F_CITIES)) {
/* Temporary hack: settlers all go in the end, forcing them
@@ -2506,6 +2502,11 @@
if (!find_unit_by_id(id) || punit->moves_left <= 0) {
return;
}
+ if (find_unit_by_id(bossid)
+ && same_pos(punit->x, punit->y, boss->x, boss->y)) {
+ /* The boss decided to stay put on the ferry. We aren't moving. */
+ return;
+ }
} else {
/* Cannot select a passenger-in-charge */
break;
Index: server/settlers.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/settlers.c,v
retrieving revision 1.181
diff -u -r1.181 settlers.c
--- server/settlers.c 19 May 2004 14:40:15 -0000 1.181
+++ server/settlers.c 21 May 2004 04:58:35 -0000
@@ -928,11 +928,11 @@
int food_upkeep = unit_food_upkeep(punit);
int food_cost = unit_foodbox_cost(punit);
- int boatid, bx = 0, by = 0; /* as returned by find_boat */
+ int boatid, bx = punit->x, by = punit->y; /* as returned by find_boat */
enemy_mask my_enemies = enemies[pplayer->player_no]; /* optimalization */
if (pplayer->ai.control)
- boatid = find_boat(pplayer, &bx, &by, 1); /* might need 2 for bodyguard */
+ boatid = find_ferry(punit, 2, NULL); /* might need 2 for bodyguard */
else
boatid = 0;
*ferryboat = unit_list_find(&(map_get_tile(punit->x, punit->y)->units),
boatid);
@@ -969,9 +969,7 @@
} else if (!goto_is_sane(punit, x, y, TRUE) ||
WARMAP_COST(x, y) > THRESHOLD * mv_rate) {
/* for Rome->Carthage */
- if (!is_ocean_near_tile(x, y)) {
- mv_cost = 9999;
- } else if (boatid != 0) {
+ if (boatid != 0) {
if (punit->id == 0 && mycity->id == boatid) {
w_virtual = TRUE;
}
@@ -1028,7 +1026,7 @@
}
#endif
- if (map_get_continent(x, y) != ucont && !nav_known && near >= 8) {
+ if (map_get_continent(x, y) != ucont && !nav_known && near >= 10) {
#ifdef REALLY_DEBUG_THIS
freelog(LOG_DEBUG,
"%s (%d, %d) rejected city at (%d, %d) to %d, newv = %d, moves
= %d" \
Index: ai/aidata.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidata.c,v
retrieving revision 1.24
diff -u -r1.24 aidata.c
--- ai/aidata.c 1 May 2004 03:22:11 -0000 1.24
+++ ai/aidata.c 21 May 2004 05:00:53 -0000
@@ -395,8 +395,9 @@
}
/**************************************************************************
- Use this wrapper to correctly update the statistics. Use NULL to
- unregister any ferry that might be there.
+ Use this wrapper to correctly update the statistics. Use ferry=NULL to
+ request a ferry. Should be used in conjunction with ai_set_passenger
+ if ferry!=NULL.
**************************************************************************/
void ai_set_ferry(struct unit *punit, struct unit *ferry)
{
@@ -409,7 +410,6 @@
} else if (ferry) {
/* Make sure we delete punit from the list of potential passengers */
ai_clear_ferry(punit);
- ferry->ai.passenger = punit->id;
punit->ai.ferryboat = ferry->id;
}
}
@@ -458,6 +458,37 @@
}
/**************************************************************************
+ Returns the number of available boats. A simple accessor made to perform
+ debug checks.
+**************************************************************************/
+int ai_available_boats(struct player *pplayer)
+{
+ struct ai_data *ai = ai_data_get(pplayer);
+
+ /* To developer: Switch this checking on when testing some new
+ * ferry code. */
+#if 0
+ int boats = 0;
+
+ unit_list_iterate(pplayer->units, punit) {
+ struct tile *ptile = map_get_tile(punit->x, punit->y);
+
+ if (is_sailing_unit(punit) && is_ground_units_transport(punit)
+ && punit->ai.passenger == FERRY_AVAILABLE) {
+ boats++;
+ }
+ } unit_list_iterate_end;
+
+ if (boats != ai->stats.available_boats) {
+ freelog(LOG_ERROR, "Boats miscounted: recorded %d but in reality %d",
+ ai->stats.available_boats, boats);
+ }
+#endif
+
+ return ai->stats.available_boats;
+}
+
+/**************************************************************************
Deinitialize data
**************************************************************************/
void ai_data_done(struct player *pplayer)
Index: ai/aidata.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidata.h,v
retrieving revision 1.11
diff -u -r1.11 aidata.h
--- ai/aidata.h 9 Oct 2003 00:07:33 -0000 1.11
+++ ai/aidata.h 21 May 2004 05:00:53 -0000
@@ -138,6 +138,7 @@
void ai_set_passenger(struct unit *punit, struct unit *passenger);
void ai_set_ferry(struct unit *punit, struct unit *ferry);
void ai_clear_ferry(struct unit *punit);
+int ai_available_boats(struct player *pplayer);
#endif
Index: ai/aitools.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.c,v
retrieving revision 1.103
diff -u -r1.103 aitools.c
--- ai/aitools.c 4 May 2004 17:40:25 -0000 1.103
+++ ai/aitools.c 21 May 2004 05:00:59 -0000
@@ -97,8 +97,8 @@
This is a function to execute paths returned by the path-finding engine.
It is analogous to goto_route_execute, only much simpler.
- We use ai_unit_attack which means "move if target unoccupied, attack
- otherwise" and also brings our bodyguard along.
+ Brings our bodyguard along.
+ Returns FALSE only if died.
*************************************************************************/
bool ai_unit_execute_path(struct unit *punit, struct pf_path *path)
{
@@ -107,7 +107,6 @@
/* We start with i = 1 for i = 0 is our present position */
for (i = 1; i < path->length; i++) {
int x = path->positions[i].x, y = path->positions[i].y;
- bool result;
int id = punit->id;
/* We use ai_unit_move() for everything but the last step
@@ -115,12 +114,11 @@
* shows up. Any enemy on the target tile is expected to
* be our target and any attack there intentional. */
if (i == path->length - 1) {
- result = ai_unit_attack(punit, x, y);
+ (void) ai_unit_attack(punit, x, y);
} else {
- ai_unit_move(punit, x, y);
- result = (find_unit_by_id(id) != NULL);
+ (void) ai_unit_move(punit, x, y);
}
- if (!result) {
+ if (!find_unit_by_id(id)) {
/* Died... */
return FALSE;
}
@@ -206,6 +204,136 @@
/* What if we have a bodyguard, but don't need one? */
}
+/****************************************************************************
+ Combined cost function for a land unit looking for a ferry. The path
+ finding first goes over the continent and then into the ocean where we
+ actually look for ferry. Thus moves land-to-sea are allowed and moves
+ sea-to-land are not. A consequence is that we don't get into the cities
+ on other continent, which might station boats. This defficiency seems to
+ be impossible to fix with the current PF structure, so it has to be
+ accounted for in the actual ferry search function.
+
+ For movements sea-to-sea the cost is collected via the extra cost
+ call-back. Doesn't care for enemy/neutral tiles, these should be excluded
+ using a TB call-back.
+****************************************************************************/
+static int combined_land_sea_move(int x, int y, enum direction8 dir,
+ int x1, int y1,
+ struct pf_parameter *param)
+{
+ struct tile *src_tile = map_get_tile(x, y);
+ struct tile *tgt_tile = map_get_tile(x1, y1);
+ int move_cost;
+
+ if (is_ocean(tgt_tile->terrain)) {
+ /* Any-to-Sea */
+ move_cost = 0;
+ } else if (src_tile->terrain == T_OCEAN) {
+ /* Sea-to-Land */
+ move_cost = PF_IMPOSSIBLE_MC;
+ } else {
+ /* Land-to-Land */
+ move_cost = src_tile->move_cost[dir];
+ }
+
+ return move_cost;
+}
+
+/****************************************************************************
+ EC callback to account for the cost of sea moves by a ferry hurrying to
+ pick our unit up.
+****************************************************************************/
+static int sea_move(int x, int y, enum known_type known,
+ struct pf_parameter *param)
+{
+ if (is_ocean(map_get_tile(x, y)->terrain)) {
+ /* Approximately TURN_FACTOR / average ferry move rate
+ * we can pass a better guess of the move rate through param->data
+ * but we don't know which boat we will find out there */
+ return SINGLE_MOVE * PF_TURN_FACTOR / 12;
+ } else {
+ return 0;
+ }
+}
+
+#define LOGLEVEL_FINDFERRY LOG_DEBUG
+/****************************************************************************
+ Proper and real PF function for finding a boat. If you don't require
+ the path to the ferry, pass path=NULL.
+
+ WARNING: Due to the nature of this function and PF (see the comment of
+ combined_land_sea_move), the path won't lead onto the boat itself.
+
+ FIXME: Actually check the capacity.
+****************************************************************************/
+int find_ferry(struct unit *punit, int cap, struct pf_path **path)
+{
+ int best_turns = FC_INFINITY;
+ int best_id = 0;
+ struct pf_parameter param;
+ struct pf_map *search_map;
+
+
+ UNIT_LOG(LOGLEVEL_FINDFERRY, punit, "asked find_ferry for a boat");
+
+ if (ai_available_boats(unit_owner(punit)) <= 0
+ && punit->ai.ferryboat <= 0) {
+ /* No boats to be found (the second check is to ensure that we are not
+ * the ones keeping the last boat busy) */
+ return 0;
+ }
+
+ pft_fill_unit_parameter(¶m, punit);
+ param.turn_mode = TM_WORST_TIME;
+ param.get_TB = no_fights_or_unknown;
+ param.get_EC = sea_move;
+ param.get_MC = combined_land_sea_move;
+
+ search_map = pf_create_map(¶m);
+
+ pf_iterator(search_map, pos) {
+ int radius = (is_ocean(map_get_tile(pos.x, pos.y)->terrain) ? 1 : 0);
+
+ if (pos.turn + pos.total_EC/PF_TURN_FACTOR > best_turns) {
+ /* Won't find anything better */
+ /* FIXME: This condition is somewhat dodgy */
+ break;
+ }
+
+ square_iterate(pos.x, pos.y, radius, x, y) {
+ struct tile *ptile = map_get_tile(x, y);
+
+ unit_list_iterate(ptile->units, aunit) {
+ if (is_ground_units_transport(aunit)
+ && (aunit->ai.passenger == FERRY_AVAILABLE
+ || aunit->ai.passenger == punit->id)) {
+ /* Turns for the unit to get to rendezvous pnt */
+ int u_turns = pos.turn;
+ /* Turns for the boat to get to the rendezvous pnt */
+ int f_turns = ((pos.total_EC / PF_TURN_FACTOR * 16
+ - aunit->moves_left)
+ / unit_type(aunit)->move_rate);
+ int turns = MAX(u_turns, f_turns);
+
+ if (turns < best_turns) {
+ UNIT_LOG(LOGLEVEL_FINDFERRY, punit,
+ "Found a potential boat %s[%d](%d,%d)",
+ unit_type(aunit)->name, aunit->id, aunit->x, aunit->y);
+ if (path) {
+ *path = pf_next_get_path(search_map);
+ }
+ best_turns = turns;
+ best_id = aunit->id;
+ }
+ }
+ } unit_list_iterate_end;
+ } square_iterate_end;
+ } pf_iterator_end;
+ pf_destroy_map(search_map);
+
+ return best_id;
+}
+
#define LOGLEVEL_GOTHERE LOG_DEBUG
/****************************************************************************
This is ferry-enabled goto. Should not normally be used for non-ferried
@@ -220,6 +348,7 @@
int dest_x, int dest_y)
{
struct unit *bodyguard = find_unit_by_id(punit->ai.bodyguard);
+ struct pf_path *path_to_ferry = NULL;
CHECK_UNIT(punit);
@@ -242,8 +371,7 @@
dest_x, dest_y);
if (boatid <= 0) {
- int bx, by;
- boatid = find_boat(pplayer, &bx, &by, 2);
+ boatid = find_ferry(punit, 2, &path_to_ferry);
}
ferryboat = find_unit_by_id(boatid);
@@ -259,21 +387,29 @@
}
ai_set_ferry(punit, ferryboat);
- if (!same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)
- && (!is_at_coast(punit->x, punit->y)
- || is_tiles_adjacent(punit->x, punit->y,
- ferryboat->x, ferryboat->y))) {
+ ai_set_passenger(ferryboat, punit);
+
+ if (!is_tiles_adjacent(punit->x, punit->y, ferryboat->x, ferryboat->y)
+ && !same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)
+ && !is_at_coast(punit->x, punit->y)) {
/* Go to the boat only if it cannot reach us or it's parked
* next to us. Otherwise just wait (boats are normally faster) */
- /* FIXME: agree on a rendez-vous point */
- /* FIXME: this can lose bodyguard */
+ /* TODO: agree on a rendez-vous point */
UNIT_LOG(LOGLEVEL_GOTHERE, punit, "going to boat[%d](%d,%d).", boatid,
ferryboat->x, ferryboat->y);
- if (!ai_unit_goto(punit, ferryboat->x, ferryboat->y)) {
+ /* The path can be amphibious so we will stop at the coast.
+ * It might not lead _onto_ the boat. */
+ if (!ai_unit_execute_path(punit, path_to_ferry)) {
/* Died. */
+ pf_destroy_path(path_to_ferry);
return FALSE;
}
}
+ pf_destroy_path(path_to_ferry);
+
+ if (is_tiles_adjacent(punit->x, punit->y, ferryboat->x, ferryboat->y)) {
+ (void) ai_unit_move(punit, ferryboat->x, ferryboat->y);
+ }
if (!same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)) {
/* Didn't get to the boat */
Index: ai/aitools.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.h,v
retrieving revision 1.41
diff -u -r1.41 aitools.h
--- ai/aitools.h 9 Jan 2004 16:59:49 -0000 1.41
+++ ai/aitools.h 21 May 2004 05:01:00 -0000
@@ -78,5 +78,7 @@
bool ai_wants_no_science(struct player *pplayer);
bool is_player_dangerous(struct player *pplayer, struct player *aplayer);
+int find_ferry(struct unit *punit, int cap, struct pf_path **path);
+
#endif /* FC__AITOOLS_H */
Index: ai/aiunit.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiunit.c,v
retrieving revision 1.315
diff -u -r1.315 aiunit.c
--- ai/aiunit.c 19 May 2004 14:40:15 -0000 1.315
+++ ai/aiunit.c 21 May 2004 05:01:15 -0000
@@ -2415,8 +2415,6 @@
static void ai_manage_ferryboat(struct player *pplayer, struct unit *punit)
{
struct city *pcity;
- int oldbossid = -1; /* Loop prevention. If boss doesn't want to move,
- * neither do we. */
CHECK_UNIT(punit);
@@ -2438,17 +2436,20 @@
/* Do we have the passenger-in-charge on board? */
struct tile *ptile = map_get_tile(punit->x, punit->y);
- if (punit->ai.passenger > 0
- && !unit_list_find(&ptile->units, punit->ai.passenger)) {
- UNIT_LOG(LOGLEVEL_FERRY, punit,
- "lost passenger-in-charge[%d], resetting",
- punit->ai.passenger);
- ai_set_passenger(punit, NULL);
- } else if (oldbossid > 0) {
- /* Need to look for a new boss */
- UNIT_LOG(LOGLEVEL_FERRY, punit, "taking control back from [%d]",
- oldbossid);
- ai_set_passenger(punit, NULL);
+ if (punit->ai.passenger > 0) {
+ struct unit *psngr = find_unit_by_id(punit->ai.passenger);
+
+ /* If the passenger-in-charge is adjacent, we should wait for it to
+ * board. We will pass control to it later.
+ * FIXME: A possible side-effect: a boat will linger near a passenger
+ * which already landed. */
+ if (!psngr
+ || real_map_distance(punit->x, punit->y, psngr->x, psngr->y) > 1) {
+ UNIT_LOG(LOGLEVEL_FERRY, punit,
+ "lost passenger-in-charge[%d], resetting",
+ punit->ai.passenger);
+ punit->ai.passenger = 0;
+ }
}
if (punit->ai.passenger <= 0) {
@@ -2470,11 +2471,6 @@
}
} unit_list_iterate_end;
- if (candidate && candidate->id == oldbossid) {
- /* The boss decided to stay put on the ferry. We aren't moving. */
- return;
- }
-
if (candidate) {
UNIT_LOG(LOGLEVEL_FERRY, punit,
"appointed %s[%d] our passenger-in-charge",
@@ -2487,11 +2483,11 @@
}
if (punit->ai.passenger > 0) {
+ int bossid = punit->ai.passenger; /* Loop prevention */
struct unit *boss = find_unit_by_id(punit->ai.passenger);
int id = punit->id; /* To check if survived */
assert(boss != NULL);
- oldbossid = punit->ai.passenger;
if (unit_flag(boss, F_SETTLERS) || unit_flag(boss, F_CITIES)) {
/* Temporary hack: settlers all go in the end, forcing them
@@ -2506,6 +2502,11 @@
if (!find_unit_by_id(id) || punit->moves_left <= 0) {
return;
}
+ if (find_unit_by_id(bossid)
+ && same_pos(punit->x, punit->y, boss->x, boss->y)) {
+ /* The boss decided to stay put on the ferry. We aren't moving. */
+ return;
+ }
} else {
/* Cannot select a passenger-in-charge */
break;
settle4.gz
Description: GNU Zip compressed data
|
|