? ai/aisettler.c ? ai/aisettler.h ? common/aicore/citymap.c ? common/aicore/citymap.h Index: ai/Makefile.am =================================================================== RCS file: /home/freeciv/CVS/freeciv/ai/Makefile.am,v retrieving revision 1.17 diff -u -r1.17 Makefile.am --- ai/Makefile.am 2 May 2004 12:13:51 -0000 1.17 +++ ai/Makefile.am 19 May 2004 16:55:17 -0000 @@ -27,6 +27,8 @@ aihunt.h \ ailog.c \ ailog.h \ + aisettler.c \ + aisettler.h \ aitech.c \ aitech.h \ aitools.c \ 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 19 May 2004 16:55:18 -0000 @@ -226,6 +226,8 @@ } } } unit_list_iterate_end; + ai->stats.mean_avail_boats = ai->stats.available_boats + - ai->stats.passengers; /*** Diplomacy ***/ @@ -305,6 +307,9 @@ ai->angry_priority = TRADE_WEIGHTING * 3; /* grave danger */ ai->pollution_priority = POLLUTION_WEIGHTING; + ai->growth_priority = 15; /* WAG */ + + /* Goals */ ai_best_government(pplayer); /*** Interception engine ***/ @@ -368,6 +373,26 @@ struct ai_data *ai = &aidata[pplayer->player_no]; int i; + /* The values below are WAGs that have been optimized to the + * default rules. They are not changed during the game. */ + + /* Perfection gives us an idea of how long to search for optimal + * solutions, instead of going for quick and dirty solutions that + * waste valuable time. Decrease for maps where good city placements + * are hard to find. Lower means more perfection. */ + ai->perfection = 3; + /* How much to deemphasise the potential for city growth for city + * placements. Decrease this value if large cities are important + * or planned. Increase if running strict smallpox. */ + ai->growth_potential_deemphasis = 8; + /* Percentage bonus to city locations near an ocean. */ + ai->naval_emphasis = 20; + /* Modifier for defense bonus that is applied to city location want. + * Increase this to lower emphasis on defensive positions, increase + * it if you want more emphasis on heavily defended cities. */ + ai->defense_emphasis = 4000; + + /* Government variables */ ai->govt_reeval = 0; ai->government_want = fc_calloc(game.government_count + 1, sizeof(int)); @@ -466,3 +491,13 @@ free(ai->government_want); } + +/************************************************************************** + ... +**************************************************************************/ +bool ai_player_has_enough_boats(struct player *pplayer, int enough) +{ + struct ai_data *ai = &aidata[pplayer->player_no]; + + return (ai->stats.mean_avail_boats >= enough); +} 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 19 May 2004 16:55:18 -0000 @@ -91,9 +91,13 @@ struct { int *workers; /* cities to workers on continent*/ int *cities; /* number of cities on continent */ + int passengers; /* number of passengers waiting for boats */ - int boats; - int available_boats; + int boats; /* total number of boats */ + int available_boats; /* number of boats not booked or used */ + int mean_avail_boats; /* excess number of boats somehow averaged + over several turns */ + int average_production; bv_id diplomat_reservations; } stats; @@ -110,6 +114,11 @@ int unhappy_priority; int angry_priority; int pollution_priority; + int growth_priority; + int perfection; + int growth_potential_deemphasis; + int naval_emphasis; + int defense_emphasis; /* Government data */ int *government_want; @@ -138,6 +147,6 @@ 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); - +bool ai_player_has_enough_boats(struct player *pplayer, int enough); #endif Index: ai/ailog.c =================================================================== RCS file: /home/freeciv/CVS/freeciv/ai/ailog.c,v retrieving revision 1.12 diff -u -r1.12 ailog.c --- ai/ailog.c 2 May 2004 12:33:29 -0000 1.12 +++ ai/ailog.c 19 May 2004 16:55:18 -0000 @@ -120,9 +120,6 @@ if (punit->debug) { minlevel = LOG_NORMAL; } else { - if (minlevel > fc_log_level) { - return; - } /* Are we a virtual unit evaluated in a debug city?. */ if (punit->id == 0) { struct city *pcity = map_get_city(punit->x, punit->y); @@ -131,6 +128,9 @@ minlevel = LOG_NORMAL; } } + if (minlevel > fc_log_level) { + return; + } } if (is_goto_dest_set(punit)) { 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 19 May 2004 16:55:18 -0000 @@ -15,6 +15,7 @@ #include #endif +#include #include #include @@ -48,9 +49,119 @@ #include "aidata.h" #include "ailog.h" #include "aiunit.h" +#include "citymap.h" #include "aitools.h" +/**************************************************************************** + 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 */ + return SINGLE_MOVE * PF_TURN_FACTOR / 16; + } else { + return 0; + } +} + +/**************************************************************************** + Proper PF function for finding a boat. + + TODO: Lift the path off the Pf map. +****************************************************************************/ +static int find_ferry(struct unit *punit, int cap) +{ + int best_turns = FC_INFINITY; + int best_id = 0; + struct pf_parameter param; + struct pf_map *search_map; + + 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 condiion 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(LOG_DEBUG, punit, + "Found a potential boat %s[%d](%d,%d)", + unit_type(aunit)->name, aunit->id, aunit->x, aunit->y); + 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); +} + /************************************************************************** Amortize a want modified by the shields (build_cost) we risk losing. We add the build time of the unit(s) we risk to amortize delay. The @@ -242,8 +353,7 @@ dest_x, dest_y); if (boatid <= 0) { - int bx, by; - boatid = find_boat(pplayer, &bx, &by, 2); + boatid = find_ferry(punit, 2); } ferryboat = find_unit_by_id(boatid); @@ -453,7 +563,9 @@ } if (punit->ai.ai_role == AIUNIT_BUILD_CITY) { + assert(is_normal_map_pos(goto_dest_x(punit), goto_dest_y(punit))); remove_city_from_minimap(goto_dest_x(punit), goto_dest_y(punit)); + citymap_free_city_spot(goto_dest_x(punit), goto_dest_y(punit), punit->id); } if (punit->ai.ai_role == AIUNIT_HUNTER) { @@ -488,9 +600,11 @@ ai_unit_new_role(bodyguard, AIUNIT_NONE, -1, -1); } - if (punit->ai.ai_role == AIUNIT_BUILD_CITY) { + /* Reserve city spot, _unless_ we want to add ourselves to a city. */ + if (punit->ai.ai_role == AIUNIT_BUILD_CITY && !map_get_city(x, y)) { assert(is_normal_map_pos(x, y)); add_city_to_minimap(x, y); + citymap_reserve_city_spot(x, y, punit->id); } if (punit->ai.ai_role == AIUNIT_HUNTER) { /* Set victim's hunted bit - the hunt is on! */ 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 19 May 2004 16:55:18 -0000 @@ -2438,12 +2438,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); + 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); + 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]", @@ -2538,7 +2546,7 @@ return; } - (void) ai_manage_explorer(punit); +/* (void) ai_manage_explorer(punit); */ if (punit->moves_left > 0) { struct city *pcity = find_nearest_safe_city(punit); Index: common/aicore/Makefile.am =================================================================== RCS file: /home/freeciv/CVS/freeciv/common/aicore/Makefile.am,v retrieving revision 1.6 diff -u -r1.6 Makefile.am --- common/aicore/Makefile.am 2 May 2004 12:13:51 -0000 1.6 +++ common/aicore/Makefile.am 19 May 2004 16:55:19 -0000 @@ -12,4 +12,6 @@ pf_tools.c \ pf_tools.h \ cm.c \ - cm.h + cm.h \ + citymap.c \ + citymap.h 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 19 May 2004 16:55:19 -0000 @@ -39,8 +39,10 @@ #include "aicity.h" #include "aidata.h" #include "ailog.h" +#include "aisettler.h" #include "aitools.h" #include "aiunit.h" +#include "citymap.h" #include "settlers.h" @@ -54,9 +56,6 @@ BV_DEFINE(enemy_mask, MAX_NUM_PLAYERS + MAX_NUM_BARBARIANS); static enemy_mask enemies[MAX_NUM_PLAYERS + MAX_NUM_BARBARIANS]; -static void auto_settler_findwork(struct player *pplayer, - struct unit *punit); -static void auto_settlers_player(struct player *pplayer); static bool is_already_assigned(struct unit *myunit, struct player *pplayer, int x, int y); static int city_desirability(struct player *pplayer, int x, int y); @@ -69,6 +68,11 @@ int x = punit->x, y = punit->y; struct city *pcity; + handle_unit_activity_request(punit, ACTIVITY_IDLE); + + /* Free city reservations */ + ai_unit_new_role(punit, AIUNIT_NONE, -1, -1); + handle_unit_build_city(pplayer, punit->id, city_name_suggestion(pplayer, x, y)); pcity = map_get_city(x, y); @@ -1207,7 +1211,7 @@ } /************************************************************************** - find some work for the settler + Find some work for the settler **************************************************************************/ static void auto_settler_findwork(struct player *pplayer, struct unit *punit) { @@ -1252,9 +1256,9 @@ return; } - /* If we intent on building a city then reserve the square. */ - if (unit_flag(punit, F_CITIES) && - best_act == ACTIVITY_UNKNOWN /* flag */) { + /* If we intend to build a city then reserve the square. */ + if (unit_flag(punit, F_CITIES) + && best_act == ACTIVITY_UNKNOWN /* flag */) { ai_unit_new_role(punit, AIUNIT_BUILD_CITY, gx, gy); } else { ai_unit_new_role(punit, AIUNIT_AUTO_SETTLER, gx, gy); @@ -1271,7 +1275,6 @@ && same_pos(gx, gy, punit->x, punit->y)) { if (best_act == ACTIVITY_UNKNOWN) { remove_city_from_minimap(gx, gy); /* yeah, I know. -- Syela */ - handle_unit_activity_request(punit, ACTIVITY_IDLE); (void) ai_do_build_city(pplayer, punit); return; } @@ -1282,6 +1285,117 @@ } /************************************************************************** + New implementation of server/settlers.c function with same name +**************************************************************************/ +#define LOG_SETTLER LOG_DEBUG +static void auto_settler_findwork2(struct player *pplayer, struct unit *punit) +{ + struct cityresult result; + int best_impr = 0; /* best terrain improvement we can do */ + enum unit_activity best_act = ACTIVITY_IDLE; /* compat. kludge */ + int gx = -1, gy = -1; + + CHECK_UNIT(punit); + + result.total = 0; + + assert(pplayer && punit); + assert(unit_flag(punit, F_CITIES) || unit_flag(punit, F_SETTLERS)); + + /*** If we are on a city mission: Go where we should ***/ + + if (punit->ai.ai_role == AIUNIT_BUILD_CITY) { + int x = goto_dest_x(punit), y = goto_dest_y(punit), sanity = punit->id; + + /* Check that missions is still possible */ + if (!city_can_be_built_here(x, y, punit)) { + UNIT_LOG(LOG_SETTLER, punit, "city founding mission failed"); + ai_unit_new_role(punit, AIUNIT_NONE, -1, -1); + return; /* avoid recursion at all cost */ + } else { + /* Go there */ + if ((!ai_gothere(pplayer, punit, x, y) && !find_unit_by_id(sanity)) + || punit->moves_left <= 0) { + return; + } + if (same_pos(punit->x, punit->y, x, y)) { + if (!ai_do_build_city(pplayer, punit)) { + UNIT_LOG(LOG_ERROR, punit, "could not make city on %s", + map_get_tile_info_text(punit->x, punit->y)); + ai_unit_new_role(punit, AIUNIT_NONE, -1, -1); + } else { + return; /* We came, we saw, we built... */ + } + } else { + UNIT_LOG(LOG_SETTLER, punit, "could not go to target"); + /* ai_unit_new_role(punit, AIUNIT_NONE, -1, -1); */ + return; + } + } + } + + CHECK_UNIT(punit); + + /*** Try find some work ***/ + + if (unit_flag(punit, F_SETTLERS)) { + best_impr = evaluate_improvements(punit, &best_act, &gx, &gy); + } + + if (unit_flag(punit, F_CITIES)) { + find_best_city_placement(punit, &result); + UNIT_LOG(LOG_SETTLER, punit, "city want %d (impr want %d)", result.total, + best_impr); + if (result.total > best_impr) { + if (map_get_city(result.x, result.y)) { + UNIT_LOG(LOG_SETTLER, punit, "immigrates to %s (%d, %d)", + map_get_city(result.x, result.y), result.x, result.y); + } else { + UNIT_LOG(LOG_SETTLER, punit, "makes city at (%d, %d)", + result.x, result.y); + } + /* Go make a city! */ + ai_unit_new_role(punit, AIUNIT_BUILD_CITY, result.x, result.y); + set_goto_dest(punit, result.x, result.y); /* TMP */ + } else if (best_impr > 0) { + UNIT_LOG(LOG_SETTLER, punit, "improves terrain"); + /* Terrain improvements follows the old model, and is recalculated + * each turn. */ + ai_unit_new_role(punit, AIUNIT_AUTO_SETTLER, gx, gy); + /* Mark the square as taken. */ + if (gx != -1 && gy != -1) { + map_get_tile(gx, gy)->assigned = + map_get_tile(gx, gy)->assigned | 1<player_no; + } else { + UNIT_LOG(LOG_NORMAL, punit, "giving up trying to improve terrain"); + return; /* We cannot do anything */ + } + set_goto_dest(punit, gx, gy); /* TMP */ + if (do_unit_goto(punit, GOTO_MOVE_ANY, FALSE) == GR_DIED) { + return; + } + if (punit->moves_left > 0 + && same_pos(gx, gy, punit->x, punit->y)) { + handle_unit_activity_request(punit, best_act); + send_unit_info(NULL, punit); + return; + } + } else { + UNIT_LOG(LOG_SETTLER, punit, "cannot find work"); + ai_unit_new_role(punit, AIUNIT_NONE, -1, -1); + return; + } + } + + /*** Recurse if we want to found a city ***/ + + if (punit->ai.ai_role == AIUNIT_BUILD_CITY) { + auto_settler_findwork2(pplayer, punit); + } +} +#undef LOG_SETTLER + +/************************************************************************** Do all tile improvement calculations and cache them for later. **************************************************************************/ void initialize_infrastructure_cache(struct city *pcity) @@ -1316,15 +1430,20 @@ } /************************************************************************** - run through all the players settlers and let those on ai.control work - automagically + Run through all the players settlers and let those on ai.control work + automagically. **************************************************************************/ -static void auto_settlers_player(struct player *pplayer) +void auto_settlers_player(struct player *pplayer) { static struct timer *t = NULL; /* alloc once, never free */ t = renew_timer_start(t, TIMER_CPU, TIMER_DEBUG); + if (pplayer->ai.control && ai_handicap(pplayer, H_EXPERIMENTAL)) { + /* Set up our city map. */ + citymap_turn_init(pplayer); + } + city_list_iterate(pplayer->cities, pcity) initialize_infrastructure_cache(pcity); /* saves oodles of time -- Syela */ city_list_iterate_end; @@ -1352,9 +1471,12 @@ handle_unit_activity_request(punit, ACTIVITY_IDLE); } if (punit->activity == ACTIVITY_IDLE) { - auto_settler_findwork(pplayer, punit); + if (pplayer->ai.control && ai_handicap(pplayer, H_EXPERIMENTAL)) { + auto_settler_findwork2(pplayer, punit); + } else { + auto_settler_findwork(pplayer, punit); + } } - freelog(LOG_DEBUG, "Has been processed."); } } unit_list_iterate_end; @@ -1473,7 +1595,6 @@ */ } - /************************************************************************** Recalculate enemies[] table **************************************************************************/ @@ -1489,9 +1610,9 @@ } /************************************************************************** - Do the auto_settler stuff for all the players. + Initialize autosettler code. **************************************************************************/ -void auto_settlers(void) +void auto_settlers_init(void) { assign_settlers(); assign_territory(); @@ -1502,8 +1623,8 @@ } /************************************************************************** -used to use old crappy formulas for settler want, but now using actual -want! + Return want for city settler. Note that we rely here on the fact that + ai_settler_init() has been run while doing autosettlers. **************************************************************************/ void contemplate_new_city(struct city *pcity) { @@ -1523,7 +1644,15 @@ virtualunit = create_unit_virtual(pplayer, pcity, unit_type, 0); virtualunit->x = pcity->x; virtualunit->y = pcity->y; - want = evaluate_city_building(virtualunit, &gx, &gy, &ferryboat); + if (pplayer->ai.control && ai_handicap(pplayer, H_EXPERIMENTAL)) { + struct cityresult result; + + find_best_city_placement(virtualunit, &result); + pcity->ai.founder_want = result.total; + return; + } else { + want = evaluate_city_building(virtualunit, &gx, &gy, &ferryboat); + } free(virtualunit); unit_list_iterate(pplayer->units, qpass) { Index: server/settlers.h =================================================================== RCS file: /home/freeciv/CVS/freeciv/server/settlers.h,v retrieving revision 1.23 diff -u -r1.23 settlers.h --- server/settlers.h 23 Sep 2003 15:59:05 -0000 1.23 +++ server/settlers.h 19 May 2004 16:55:19 -0000 @@ -19,7 +19,8 @@ struct unit; struct city; -void auto_settlers(void); +void auto_settlers_init(void); +void auto_settlers_player(struct player *pplayer); int find_boat(struct player *pplayer, int *x, int *y, int cap); #define MORT 24 Index: server/srv_main.c =================================================================== RCS file: /home/freeciv/CVS/freeciv/server/srv_main.c,v retrieving revision 1.160 diff -u -r1.160 srv_main.c --- server/srv_main.c 2 May 2004 14:47:12 -0000 1.160 +++ server/srv_main.c 19 May 2004 16:55:19 -0000 @@ -97,6 +97,8 @@ #include "advmilitary.h" #include "aidata.h" #include "aihand.h" +#include "aisettler.h" +#include "citymap.h" #include "srv_main.h" @@ -488,8 +490,13 @@ nocity_send = TRUE; /* AI end of turn activities */ + auto_settlers_init(); players_iterate(pplayer) { if (pplayer->ai.control) { + ai_settler_init(pplayer); + } + auto_settlers_player(pplayer); + if (pplayer->ai.control) { ai_do_last_activities(pplayer); } } players_iterate_end; @@ -1403,8 +1410,6 @@ summon_barbarians(); /* wild guess really, no idea where to put it, but I want to give them chance to move their units */ /* Moved this to after the human turn for efficiency -- Syela */ - freelog(LOG_DEBUG, "Autosettlers"); - auto_settlers(); freelog(LOG_DEBUG, "Auto-Attack phase"); auto_attack(); freelog(LOG_DEBUG, "Endturn");