[Freeciv-Dev] (PR#2415) AI only autoattack patch
[Top] [All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
This patch gives only the AI access to autoattack. Further expansion of
this feature to human players will come in separate patch(es). For now
only 'experimental' AI gets access to this feature.
The autoattack code is massively rewritten based on previous comments. We
now first put all eligible attackers into a unit list, sort it based on
our win chances, and then iterate through it for autoattacks against the
moved unit.
In addition, I have moved the maybe_make_first_contact and
wakeup_neighbor_sentries up in the code hierarchy, so that I can avoid
them being called for each unit being moved that is inside a transport.
This is unnecessary and therefore a waste of CPU.
This patch makes the code less tolerant to simple mistakes that were
ignored previously, such as assuming a unit will always a goto or when
moving onto a bribed unit. It is therefore highly likely that this patch
will reveal new, exciting bugs. The patch contains several fixes for such
bugs already (some previously posted separately, some not), but even
though I've run extensive autogame testing, there are probably more
remaining. Testers wanted. Comments also.
- Per
Index: ai/aiair.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiair.c,v
retrieving revision 1.6
diff -u -r1.6 aiair.c
--- ai/aiair.c 2002/12/22 18:14:43 1.6
+++ ai/aiair.c 2002/12/23 22:30:11
@@ -338,7 +338,9 @@
* TODO: separate attacking into a function, check for the best
* tile to attack from */
set_unit_activity(punit, ACTIVITY_GOTO);
- (void) do_unit_goto(punit, GOTO_MOVE_ANY, FALSE);
+ if (!ai_unit_goto(punit, GOTO_MOVE_ANY, FALSE)) {
+ return; /* died */
+ }
/* goto would be aborted: "Aborting GOTO for AI attack procedures"
* now actually need to attack */
Index: ai/aidiplomat.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidiplomat.c,v
retrieving revision 1.3
diff -u -r1.3 aidiplomat.c
--- ai/aidiplomat.c 2002/12/18 17:36:18 1.3
+++ ai/aidiplomat.c 2002/12/23 22:30:11
@@ -427,6 +427,7 @@
int gold_avail = pplayer->economic.gold - pplayer->ai.est_upkeep;
int cost, destx, desty;
bool threat = FALSE;
+ int sanity = punit->id;
/* Check ALL possible targets */
whole_map_iterate(x, y) {
@@ -514,7 +515,12 @@
packet.target_id = pvictim->id;
packet.action_type = DIPLOMAT_BRIBE;
handle_diplomat_action(pplayer, &packet);
- return (punit->moves_left > 0);
+ /* autoattack might kill us as we move in */
+ if (find_unit_by_id(sanity) && punit->moves_left > 0) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
} else {
/* usually because we ended move early due to another unit */
UNIT_LOG(LOG_DIPLOMAT, punit, "could not bribe target "
Index: ai/aiunit.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiunit.c,v
retrieving revision 1.241
diff -u -r1.241 aiunit.c
--- ai/aiunit.c 2002/12/21 09:39:23 1.241
+++ ai/aiunit.c 2002/12/23 22:30:11
@@ -1980,7 +1980,7 @@
repeat++;
if (!is_tiles_adjacent(punit->x, punit->y, dest_x, dest_y)
|| !can_unit_attack_tile(punit, dest_x, dest_y)
- || could_unit_move_to_tile(punit, dest_x, dest_y) == 0) {
+ || !could_unit_move_to_tile(punit, dest_x, dest_y) == 0) {
/* Can't attack or move usually means we are adjacent but
* on a ferry. This fixes the problem (usually). */
int i = ai_military_gothere(pplayer, punit, dest_x, dest_y);
@@ -1996,6 +1996,9 @@
UNIT_LOG(LOG_DEBUG, punit, "mil att bash -> %d, %d", dest_x, dest_y);
ai_unit_attack(punit, dest_x, dest_y);
}
+ if (!(punit = find_unit_by_id(id))) {
+ return; /* we died */
+ }
} /* while */
} else {
/* FIXME: This happens a bit too often! */
@@ -2004,9 +2007,6 @@
ct = 0;
}
- if (!(punit = find_unit_by_id(id))) {
- return; /* we died */
- }
ct--; /* infinite loops from railroads must be stopped */
} while (punit->moves_left > 0 && ct > 0);
@@ -2043,6 +2043,7 @@
}
if ((punit = find_unit_by_id(id)) && punit->moves_left > 0) {
struct city *pcity = map_get_city(punit->x, punit->y);
+
if (pcity && pcity->id == punit->homecity) {
/* We're needlessly idle in our homecity */
UNIT_LOG(LOG_DEBUG, punit, "fstk could not find work for me!");
Index: common/player.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/player.h,v
retrieving revision 1.89
diff -u -r1.89 player.h
--- common/player.h 2002/12/16 05:35:31 1.89
+++ common/player.h 2002/12/23 22:30:11
@@ -38,17 +38,17 @@
SEA_BARBARIAN = 2
};
+/* See doc/README.AI for documention on these */
enum handicap_type {
H_NONE=0, /* no handicaps */
H_DIPLOMAT=1, /* can't build offensive diplomats */
H_MAP=2, /* only knows map_get_known tiles */
H_LIMITEDHUTS=4, /* Can get only 25 gold and barbs from huts */
H_CITYBUILDINGS=8, /* doesn't know what buildings are in enemy cities */
- H_CITYUNITS=16, /* doesn't know what units are in enemy cities */
+ H_NOAUTOATTACK=16, /* cannot use autoattack */
H_DEFENSIVE=32, /* builds lots of defensive buildings without calculating
need */
H_EXPERIMENTAL=64, /* enable experimental AI features (for testing) */
H_SUB=128, /* doesn't know where subs may be lurking */
-/* below this point are milder handicaps that I can actually implement --
Syela */
H_RATES=256, /* can't set its rates beyond government limits */
H_TARGETS=512, /* can't target anything it doesn't know exists */
H_HUTS=1024, /* doesn't know which unseen tiles have huts on them */
Index: server/stdinhand.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/stdinhand.c,v
retrieving revision 1.269
diff -u -r1.269 stdinhand.c
--- server/stdinhand.c 2002/12/21 09:45:42 1.269
+++ server/stdinhand.c 2002/12/23 22:30:12
@@ -1628,11 +1628,12 @@
H_NONE,
H_NONE,
/* easy */ H_RATES | H_TARGETS | H_HUTS | H_NOPLANES
- | H_DIPLOMAT | H_LIMITEDHUTS | H_DEFENSIVE,
+ | H_DIPLOMAT | H_LIMITEDHUTS | H_DEFENSIVE
+ | H_NOAUTOATTACK,
H_NONE,
- /* medium */ H_RATES | H_TARGETS | H_HUTS | H_DIPLOMAT,
+ /* medium */ H_RATES | H_TARGETS | H_HUTS | H_DIPLOMAT | H_NOAUTOATTACK,
H_NONE,
- /* hard */ H_NONE,
+ /* hard */ H_NOAUTOATTACK,
H_NONE,
H_NONE,
/* testing */ H_EXPERIMENTAL,
Index: server/unittools.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/unittools.c,v
retrieving revision 1.202
diff -u -r1.202 unittools.c
--- server/unittools.c 2002/12/18 17:36:20 1.202
+++ server/unittools.c 2002/12/23 22:30:12
@@ -54,6 +54,8 @@
#include "unittools.h"
+/* We need this globally static for our sort algorithm */
+static struct unit *autoattack_target = NULL;
static void unit_restore_hitpoints(struct player *pplayer, struct unit *punit);
static void unit_restore_movepoints(struct player *pplayer, struct unit
*punit);
@@ -2814,8 +2816,103 @@
}
/*****************************************************************
-Will wake up any neighboring enemy sentry units or patrolling units
+ This function is passed to unit_list_sort() to sort a list of
+ units according to their win chance against autoattack_target.
+ If the unit is being transported, then push it to the front of
+ the list, since we wish to leave its transport out of combat
+ if at all possible.
*****************************************************************/
+static int compare_units(const void *p, const void *q)
+{
+ struct unit *p1 = (struct unit *)*(void**)p;
+ struct unit *q1 = (struct unit *)*(void**)q;
+ int p1uwc = unit_win_chance(p1, autoattack_target);
+ int q1uwc = unit_win_chance(q1, autoattack_target);
+
+ if (p1uwc < q1uwc || q1->transported_by > 0) {
+ return -1; /* q is better */
+ } else if (p1uwc == q1uwc) {
+ return 0;
+ } else {
+ return 1; /* p is better */
+ }
+}
+
+/*****************************************************************
+ Check if unit survives enemy autoattacks.
+*****************************************************************/
+static bool unit_survive_autoattack(struct unit *punit)
+{
+ struct unit_list autoattack;
+ int moves = punit->moves_left;
+ int sanity1 = punit->id;
+
+ /* Kludge to prevent attack power from dropping to zero during calc */
+ punit->moves_left = MAX(punit->moves_left, 1);
+
+ unit_list_init(&autoattack);
+ adjc_iterate(punit->x, punit->y, x, y) {
+ /* First add all eligible units to a unit list */
+ unit_list_iterate(map_get_tile(x, y)->units, penemy) {
+ struct player *enemyplayer = unit_owner(penemy);
+
+ if (enemyplayer->ai.control
+ && !ai_handicap(enemyplayer, H_NOAUTOATTACK)
+ && penemy->moves_left > 0
+ && pplayers_at_war(unit_owner(punit), unit_owner(penemy))
+ && map_get_known_and_seen(punit->x, punit->y, unit_owner(penemy))
+ && player_can_see_unit(unit_owner(penemy), punit)
+ && can_unit_attack_unit_at_tile(penemy, punit, punit->x, punit->y)) {
+ unit_list_insert(&autoattack, penemy);
+ }
+ } unit_list_iterate_end;
+ } adjc_iterate_end;
+
+ /* The unit list is now sorted according to win chance against punit */
+ autoattack_target = punit; /* global static to pass an arg to
compare_units() */
+ if (unit_list_size(&autoattack) >= 2) {
+ unit_list_sort(&autoattack, &compare_units);
+ }
+
+ unit_list_iterate_safe(autoattack, penemy) {
+ int sanity2 = penemy->id;
+ double punitwin = unit_win_chance(punit, penemy);
+ double penemywin = unit_win_chance(penemy, punit);
+
+ freelog(LOG_DEBUG, "autoattack if %f < %f with %s -> %s at (%d, %d)",
+ punitwin, penemywin, unit_type(penemy)->name,
+ unit_type(punit)->name, punit->x, punit->y);
+
+ if (penemywin > 1.0 - punitwin || unit_flag(punit, F_DIPLOMAT)) {
+ /* Yep, that's all there is to it! */
+ handle_unit_activity_request(penemy, ACTIVITY_IDLE);
+ (void) handle_unit_move_request(penemy, punit->x,
+ punit->y, FALSE, FALSE);
+ }
+
+ if (find_unit_by_id(sanity2)) {
+ send_unit_info(NULL, penemy);
+ }
+ if (find_unit_by_id(sanity1)) {
+ send_unit_info(NULL, punit);
+ } else {
+ return FALSE; /* done, gone */
+ }
+ } unit_list_iterate_safe_end;
+
+ unit_list_unlink_all(&autoattack);
+ if (find_unit_by_id(sanity1)) {
+ punit->moves_left = moves;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+/*****************************************************************
+ Will wake up any neighboring enemy sentry units or patrolling
+ units.
+*****************************************************************/
static void wakeup_neighbor_sentries(struct unit *punit)
{
/* There may be sentried units with a sightrange>3, but we don't
@@ -2861,12 +2958,7 @@
Does: 1) updates the units homecity and the city it enters/leaves (the
cities happiness varies). This also takes into account if the
unit enters/leaves a fortress.
- 2) handles any huts at the units destination.
- 3) awakes any sentried units on neightboring tiles.
- 4) updates adjacent cities' unavailable tiles.
-
-FIXME: Sometimes it is not neccesary to send cities because the goverment
- doesn't care if a unit is away or not.
+ 2) updates adjacent cities' unavailable tiles.
**************************************************************************/
static void handle_unit_move_consequences(struct unit *punit, int src_x, int
src_y,
int dest_x, int dest_y)
@@ -2881,9 +2973,6 @@
if (punit->homecity != 0)
homecity = find_city_by_id(punit->homecity);
- wakeup_neighbor_sentries(punit);
- maybe_make_first_contact(dest_x, dest_y, unit_owner(punit));
-
if (tocity)
handle_unit_enter_city(punit, tocity);
@@ -3107,6 +3196,11 @@
unit_type(punit)->vision_range);
handle_unit_move_consequences(punit, src_x, src_y, dest_x, dest_y);
+ wakeup_neighbor_sentries(punit);
+ if (!unit_survive_autoattack(punit)) {
+ return FALSE;
+ }
+ maybe_make_first_contact(dest_x, dest_y, unit_owner(punit));
conn_list_do_unbuffer(&pplayer->connections);
|
|