1 /***********************************************************************
2 Freeciv - Copyright (C) 2002 - The Freeciv Team
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
13
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17
18 /* utility */
19 #include "log.h"
20
21 /* common */
22 #include "combat.h"
23 #include "game.h"
24 #include "map.h"
25 #include "movement.h"
26 #include "player.h"
27 #include "pf_tools.h"
28 #include "unit.h"
29
30 /* server */
31 #include "citytools.h"
32 #include "maphand.h"
33 #include "srv_log.h"
34 #include "unithand.h"
35 #include "unittools.h"
36
37 /* server/advisors */
38 #include "advbuilding.h"
39 #include "advgoto.h"
40
41 /* ai */
42 #include "handicaps.h"
43
44 /* ai/default */
45 #include "aicity.h"
46 #include "aiplayer.h"
47 #include "ailog.h"
48 #include "aitools.h"
49 #include "aiunit.h"
50
51 #include "aiair.h"
52
53 /******************************************************************//**
54 How fast does a unit regenerate at another tile after moves to it.
55 Kludgy function worth some generalization.
56 **********************************************************************/
regen_turns(struct unit * punit,struct tile * ptile,int lost_hp)57 static inline int regen_turns(struct unit *punit, struct tile *ptile,
58 int lost_hp)
59 {
60 struct tile *real_tile = unit_tile(punit);
61 int res, regen, recov;
62
63 punit->tile = ptile;
64 /* unit_list_prepend(ptile, punit); ... (handle "MaxUnitsOnTile" etc.) */
65 regen = hp_gain_coord(punit);
66 recov = get_unit_bonus(punit, EFT_UNIT_RECOVER);
67 if (lost_hp - recov <= 0) {
68 res = 0;
69 } else {
70 res = 1 + (lost_hp - recov) / (recov + regen);
71 }
72 punit->tile = real_tile;
73
74 return res;
75 }
76 /**************************************************************************
77 Looks for nearest airbase for punit reachable imediatly.
78 Returns NULL if not found. The path is stored in the path
79 argument if not NULL.
80 If the unit is damaged, flies to an airbase that can repair
81 the unit in a minimal number of turns.
82 FIXME: consider airdrome safety. Can lure enemy bombers
83 to a spare airdrome next to our rifles.
84 TODO: Special handicaps for planes running out of fuel
85 IMO should be less restrictive than general H_MAP, H_FOG
86 *************************************************************************/
find_nearest_airbase(struct unit * punit,struct pf_path ** path)87 static struct tile *find_nearest_airbase(struct unit *punit,
88 struct pf_path **path)
89 {
90 struct player *pplayer = unit_owner(punit);
91 struct pf_parameter parameter;
92 struct pf_map *pfm;
93 struct tile *best = NULL;
94 int lost_hp = unit_type_get(punit)->hp - punit->hp;
95 int best_regt = FC_INFINITY;
96
97 pft_fill_unit_parameter(¶meter, punit);
98 parameter.omniscience = !has_handicap(pplayer, H_MAP);
99 pfm = pf_map_new(¶meter);
100
101 pf_map_move_costs_iterate(pfm, ptile, move_cost, TRUE) {
102 if (move_cost > punit->moves_left) {
103 /* Too far! */
104 break;
105 }
106
107 if (is_airunit_refuel_point(ptile, pplayer, punit)) {
108 if (lost_hp > 0) {
109 int regt = regen_turns(punit, ptile, lost_hp);
110
111 if (regt <= 0) {
112 /* Nothing better to search */
113 best = ptile;
114 break;
115 } else if (!best || regt < best_regt) {
116 /* regenerates faster */
117 best_regt = regt;
118 best = ptile;
119 }
120 } else {
121 best = ptile;
122 break;
123 }
124 }
125 } pf_map_move_costs_iterate_end;
126
127 if (path && best) {
128 *path = pf_map_path(pfm, best);
129 }
130 pf_map_destroy(pfm);
131 return best;
132 }
133
134 /**********************************************************************
135 Very preliminary estimate for our intent to attack the tile (x, y).
136 Used by bombers only.
137 **********************************************************************/
dai_should_we_air_attack_tile(struct ai_type * ait,struct unit * punit,struct tile * ptile)138 static bool dai_should_we_air_attack_tile(struct ai_type *ait,
139 struct unit *punit, struct tile *ptile)
140 {
141 struct city *acity = tile_city(ptile);
142
143 /* For a virtual unit (punit->id == 0), all targets are good */
144 /* TODO: There is a danger of producing too many units that will not
145 * attack anything. Production should not happen if there is an idle
146 * unit of the same type nearby */
147 if (acity && punit->id != 0
148 && def_ai_city_data(acity, ait)->invasion.occupy == 0
149 && !unit_can_take_over(punit)) {
150 /* No units capable of occupying are invading */
151 log_debug("Don't want to attack %s, although we could",
152 city_name_get(acity));
153 return FALSE;
154 }
155
156 return TRUE;
157 }
158
159 /**********************************************************************
160 Returns an estimate for the profit gained through attack.
161 Assumes that the victim is within one day's flight
162 **********************************************************************/
dai_evaluate_tile_for_air_attack(struct unit * punit,struct tile * dst_tile)163 static int dai_evaluate_tile_for_air_attack(struct unit *punit,
164 struct tile *dst_tile)
165 {
166 struct unit *pdefender;
167 /* unit costs in shields */
168 int balanced_cost, unit_cost, victim_cost = 0;
169 /* unit stats */
170 int unit_attack, victim_defence;
171 /* final answer */
172 int profit;
173 /* time spent in the air */
174 int sortie_time;
175 #define PROB_MULTIPLIER 100 /* should unify with those in combat.c */
176
177 if (!can_unit_attack_tile(punit, dst_tile)
178 || !(pdefender = get_defender(punit, dst_tile))) {
179 return 0;
180 }
181
182 /* Ok, we can attack, but is it worth it? */
183
184 /* Cost of our unit */
185 unit_cost = unit_build_shield_cost(punit);
186 /* This is to say "wait, ill unit will get better!" */
187 unit_cost = unit_cost * unit_type_get(punit)->hp / punit->hp;
188
189 /* Determine cost of enemy units */
190 victim_cost = stack_cost(punit, pdefender);
191 if (0 == victim_cost) {
192 return 0;
193 }
194
195 /* Missile would die 100% so we adjust the victim_cost -- GB */
196 if (uclass_has_flag(unit_class_get(punit), UCF_MISSILE)) {
197 victim_cost -= unit_build_shield_cost(punit);
198 }
199
200 unit_attack = (int) (PROB_MULTIPLIER
201 * unit_win_chance(punit, pdefender));
202
203 victim_defence = PROB_MULTIPLIER - unit_attack;
204
205 balanced_cost = build_cost_balanced(unit_type_get(punit));
206
207 sortie_time = (unit_has_type_flag(punit, UTYF_ONEATTACK) ? 1 : 0);
208
209 profit = kill_desire(victim_cost, unit_attack, unit_cost, victim_defence, 1)
210 - SHIELD_WEIGHTING + 2 * TRADE_WEIGHTING;
211 if (profit > 0) {
212 profit = military_amortize(unit_owner(punit),
213 game_city_by_number(punit->homecity),
214 profit, sortie_time, balanced_cost);
215 log_debug("%s at (%d, %d) is a worthy target with profit %d",
216 unit_rule_name(pdefender), TILE_XY(dst_tile), profit);
217 } else {
218 log_debug("%s(%d, %d): %s at (%d, %d) is unworthy with profit %d",
219 unit_rule_name(punit), TILE_XY(unit_tile(punit)),
220 unit_rule_name(pdefender), TILE_XY(dst_tile), profit);
221 profit = 0;
222 }
223
224 return profit;
225 }
226
227
228 /**********************************************************************
229 Find something to bomb
230 Air-units specific victim search
231 Returns the want for the best target. The targets are stored in the
232 path and pptile arguments if not NULL.
233 TODO: take counterattack dangers into account
234 TODO: make separate handicaps for air units seeing targets
235 IMO should be more restrictive than general H_MAP, H_FOG
236 *********************************************************************/
find_something_to_bomb(struct ai_type * ait,struct unit * punit,struct pf_path ** path,struct tile ** pptile)237 static int find_something_to_bomb(struct ai_type *ait, struct unit *punit,
238 struct pf_path **path, struct tile **pptile)
239 {
240 struct player *pplayer = unit_owner(punit);
241 struct pf_parameter parameter;
242 struct pf_map *pfm;
243 struct tile *best_tile = NULL;
244 int best = 0;
245
246 pft_fill_unit_parameter(¶meter, punit);
247 parameter.omniscience = !has_handicap(pplayer, H_MAP);
248 pfm = pf_map_new(¶meter);
249
250 /* Let's find something to bomb */
251 pf_map_move_costs_iterate(pfm, ptile, move_cost, FALSE) {
252 if (move_cost >= punit->moves_left) {
253 /* Too far! */
254 break;
255 }
256
257 if (has_handicap(pplayer, H_MAP) && !map_is_known(ptile, pplayer)) {
258 /* The target tile is unknown */
259 continue;
260 }
261
262 if (has_handicap(pplayer, H_FOG)
263 && !map_is_known_and_seen(ptile, pplayer, V_MAIN)) {
264 /* The tile is fogged */
265 continue;
266 }
267
268 if (is_enemy_unit_tile(ptile, pplayer)
269 && dai_should_we_air_attack_tile(ait, punit, ptile)
270 && can_unit_attack_tile(punit, ptile)) {
271 int new_best = dai_evaluate_tile_for_air_attack(punit, ptile);
272
273 if (new_best > best) {
274 best_tile = ptile;
275 best = new_best;
276 log_debug("%s wants to attack tile (%d, %d)",
277 unit_rule_name(punit), TILE_XY(ptile));
278 }
279 }
280 } pf_map_move_costs_iterate_end;
281
282 /* Return the best values. */
283 if (pptile) {
284 *pptile = best_tile;
285 }
286 if (path) {
287 *path = best_tile ? pf_map_path(pfm, best_tile) : NULL;
288 }
289
290 pf_map_destroy(pfm);
291 return best;
292 }
293
294 /******************************************************************//**
295 Iterates through reachable refuel points and appraises them
296 as a possible base for air operations by (air)unit punit.
297 Returns NULL if not found (or we just should stay here).
298 The path is stored in the path argument if not NULL.
299 1. If the unit is damaged, looks for the fastest full regeneration.
300 (Should we set the unit task to AIUNIT_RECOVER? Currently, no gain)
301 2. Goes to the nearest city that needs a defender immediately.
302 3. Evaluates bombing targets from the bases and chooses the best.
303 FIXME: consider airbase safety! Don't be lured to enemy airbases!
304 **********************************************************************/
dai_find_strategic_airbase(struct ai_type * ait,struct unit * punit,struct pf_path ** path)305 static struct tile *dai_find_strategic_airbase(struct ai_type *ait,
306 struct unit *punit,
307 struct pf_path **path)
308 {
309 struct player *pplayer = unit_owner(punit);
310 struct pf_parameter parameter;
311 struct pf_map *pfm;
312 struct tile *best_tile = NULL;
313 struct city *pcity;
314 struct unit *pvirtual = NULL;
315 int best_worth = 0, target_worth;
316 int lost_hp = unit_type_get(punit)->hp - punit->hp;
317 int regen_turns_min = FC_INFINITY;
318 bool defend = FALSE; /* Used only for lost_hp > 0 */
319 bool refuel_start = FALSE; /* Used for not a "grave danger" start */
320
321 /* Consider staying at the current position
322 * before we generate the map, maybe we should not */
323 if (is_unit_being_refueled(punit)) {
324 /* We suppose here for speed that the recovery effect is global.
325 * It's so in the standard rulesets but might be not elsewhere */
326 int recov = get_unit_bonus(punit, EFT_UNIT_RECOVER);
327 int regen = hp_gain_coord(punit);
328 const struct tile *ptile = unit_tile(punit);
329
330 if (lost_hp > 0 && regen + recov > 0) {
331 regen_turns_min =
332 punit->moved ? lost_hp - recov : lost_hp - (regen + recov);
333 if (regen_turns_min <= 0) {
334 if (lost_hp - recov > 0) {
335 /* Probably, nothing can repair us faster */
336 log_debug("Repairment of %s is almost finished, stays here",
337 unit_rule_name(punit));
338 def_ai_unit_data(punit, ait)->done = TRUE;
339 return NULL;
340 } else {
341 regen_turns_min = 0;
342 }
343 } else {
344 regen_turns_min /= regen + recov;
345 regen_turns_min += 1;
346 }
347 }
348 pcity = tile_city(ptile);
349 if (pcity
350 && def_ai_city_data(pcity, ait)->grave_danger
351 > (unit_list_size(ptile->units) - 1) << 1) {
352 if (lost_hp <= 0 || regen_turns_min <= 1) {
353 log_debug("%s stays defending %s",
354 unit_rule_name(punit), city_name_get(pcity));
355 return NULL;
356 } else {
357 /* We may find a city in grave danger that restores faster */
358 defend = TRUE;
359 }
360 } else {
361 refuel_start = TRUE;
362 }
363 }
364 pft_fill_unit_parameter(¶meter, punit);
365 parameter.omniscience = !has_handicap(pplayer, H_MAP);
366 pfm = pf_map_new(¶meter);
367 pf_map_move_costs_iterate(pfm, ptile, move_cost, FALSE) {
368 bool chg_for_regen = FALSE;
369
370 if (move_cost >= punit->moves_left) {
371 break; /* Too far! */
372 }
373
374 if (!is_airunit_refuel_point(ptile, pplayer, punit)) {
375 continue; /* Cannot refuel here. */
376 }
377
378 if (lost_hp > 0) {
379 /* Don't fly to a point where we'll regenerate longer */
380 int regen_tn = regen_turns(punit, ptile, lost_hp);
381
382 if (regen_turns_min < regen_tn) {
383 log_debug("%s knows a better repair base than %d,%d",
384 unit_rule_name(punit), TILE_XY(ptile));
385 continue;
386 } else if (regen_turns_min > regen_tn) {
387 regen_turns_min = regen_tn;
388 best_tile = ptile;
389 best_worth = 0; /* to be calculated if necessary */
390 chg_for_regen = TRUE;
391 }
392 }
393 if ((pcity = tile_city(ptile))
394 /* Two defenders per attacker is enough,
395 * at least considering that planes are usually
396 * expensive and weak city defenders */
397 && def_ai_city_data(pcity, ait)->grave_danger
398 > unit_list_size(ptile->units) << 1) {
399 if (lost_hp <= 0) {
400 best_tile = ptile;
401 break; /* Fly there immediately!! */
402 } else {
403 if (!defend) {
404 /* We maybe have equally regenerating base but not in danger */
405 best_tile = ptile;
406 defend = TRUE;
407 }
408 continue;
409 }
410 } else if (defend) {
411 if (chg_for_regen) {
412 /* We better regenerate faster and take a revenge a bit later */
413 defend = FALSE;
414 } else {
415 /* We already have a base in grave danger that restores not worse */
416 continue;
417 }
418 }
419
420 if (!pvirtual) {
421 pvirtual =
422 unit_virtual_create(pplayer,
423 player_city_by_number(pplayer, punit->homecity),
424 unit_type_get(punit), punit->veteran);
425 if (refuel_start) {
426 /* What worth really worth moving out? */
427 int start_worth;
428
429 unit_tile_set(pvirtual, unit_tile(punit));
430 start_worth = find_something_to_bomb(ait, pvirtual, NULL, NULL);
431 best_worth = MAX(start_worth, 0);
432 }
433 }
434
435 unit_tile_set(pvirtual, ptile);
436 target_worth = find_something_to_bomb(ait, pvirtual, NULL, NULL);
437 if (target_worth > best_worth) {
438 /* It's either a first find or it's better than the previous. */
439 best_worth = target_worth;
440 best_tile = ptile;
441 /* We can still look for something better. */
442 }
443 } pf_map_move_costs_iterate_end;
444
445 if (pvirtual) {
446 unit_virtual_destroy(pvirtual);
447 }
448
449 if (path) {
450 /* Stores the path. */
451 *path = best_tile ? pf_map_path(pfm, best_tile) : NULL;
452 }
453 pf_map_destroy(pfm);
454
455 return best_tile;
456 }
457
458 /************************************************************************
459 Trying to manage bombers and stuff.
460 If we are in the open {
461 if moving intelligently on a valid GOTO, {
462 carry on doing it.
463 } else {
464 go refuel
465 }
466 } else {
467 try to attack something
468 }
469 TODO: distant target selection, support for fuel > 2
470 ***********************************************************************/
dai_manage_airunit(struct ai_type * ait,struct player * pplayer,struct unit * punit)471 void dai_manage_airunit(struct ai_type *ait, struct player *pplayer,
472 struct unit *punit)
473 {
474 struct tile *dst_tile = unit_tile(punit);
475 /* Loop prevention */
476 int moves = punit->moves_left;
477 int id = punit->id;
478 struct pf_parameter parameter;
479 struct pf_map *pfm;
480 struct pf_path *path;
481
482 CHECK_UNIT(punit);
483
484 if (!is_unit_being_refueled(punit)) {
485 /* We are out in the open, what shall we do? */
486 if (punit->activity == ACTIVITY_GOTO
487 /* We are on a GOTO. Check if it will get us anywhere */
488 && NULL != punit->goto_tile
489 && !same_pos(unit_tile(punit), punit->goto_tile)
490 && is_airunit_refuel_point(punit->goto_tile, pplayer, punit)) {
491 pfm = pf_map_new(¶meter);
492 path = pf_map_path(pfm, punit->goto_tile);
493 if (path) {
494 bool alive = adv_follow_path(punit, path, punit->goto_tile);
495
496 pf_path_destroy(path);
497 pf_map_destroy(pfm);
498 if (alive && punit->moves_left > 0) {
499 /* Maybe do something else. */
500 dai_manage_airunit(ait, pplayer, punit);
501 }
502 return;
503 }
504 pf_map_destroy(pfm);
505 } else if ((dst_tile = find_nearest_airbase(punit, &path))) {
506 /* Go refuelling */
507 if (!adv_follow_path(punit, path, dst_tile)) {
508 pf_path_destroy(path);
509 return; /* The unit died. */
510 }
511 pf_path_destroy(path);
512 } else {
513 if (punit->fuel == 1) {
514 UNIT_LOG(LOG_DEBUG, punit, "Oops, fallin outta the sky");
515 }
516 def_ai_unit_data(punit, ait)->done = TRUE; /* Won't help trying again */
517 return;
518 }
519
520 } else if (punit->fuel == unit_type_get(punit)->fuel) {
521 /* We only leave a refuel point when we are on full fuel */
522
523 if (find_something_to_bomb(ait, punit, &path, &dst_tile) > 0) {
524 /* Found target, coordinates are in punit's goto_dest.
525 * TODO: separate attacking into a function, check for the best
526 * tile to attack from */
527 fc_assert_ret(path != NULL && dst_tile != NULL);
528 if (!adv_follow_path(punit, path, dst_tile)) {
529 pf_path_destroy(path);
530 return; /* The unit died. */
531 }
532 pf_path_destroy(path);
533 unit_activity_handling(punit, ACTIVITY_IDLE);
534 } else if ((dst_tile = dai_find_strategic_airbase(ait, punit, &path))) {
535 log_debug("%s will fly to (%i, %i) (%s) to fight there",
536 unit_rule_name(punit), TILE_XY(dst_tile),
537 tile_city(dst_tile) ? city_name_get(tile_city(dst_tile)) : "");
538 def_ai_unit_data(punit, ait)->done = TRUE; /* Wait for next turn */
539 if (!adv_follow_path(punit, path, dst_tile)) {
540 pf_path_destroy(path);
541 return; /* The unit died. */
542 }
543 pf_path_destroy(path);
544 } else {
545 log_debug("%s cannot find anything to kill and is staying put",
546 unit_rule_name(punit));
547 def_ai_unit_data(punit, ait)->done = TRUE;
548 unit_activity_handling(punit, ACTIVITY_IDLE);
549 }
550 }
551
552 if ((punit = game_unit_by_number(id)) != NULL && punit->moves_left > 0
553 && punit->moves_left != moves) {
554 /* We have moved this turn, might have ended up stuck out in the fields
555 * so, as a safety measure, let's manage again */
556 dai_manage_airunit(ait, pplayer, punit);
557 }
558
559 }
560
561 /*******************************************************************
562 Chooses the best available and usable air unit and records it in
563 choice, if it's better than previous choice
564 The interface is somewhat different from other ai_choose, but
565 that's what it should be like, I believe -- GB
566 ******************************************************************/
dai_choose_attacker_air(struct ai_type * ait,struct player * pplayer,struct city * pcity,struct adv_choice * choice)567 bool dai_choose_attacker_air(struct ai_type *ait, struct player *pplayer,
568 struct city *pcity, struct adv_choice *choice)
569 {
570 bool want_something = FALSE;
571
572 /* This AI doesn't know to build planes */
573 if (has_handicap(pplayer, H_NOPLANES)) {
574 return FALSE;
575 }
576
577 /* military_advisor_choose_build does something idiotic,
578 * this function should not be called if there is danger... */
579 if (choice->want >= 100 && choice->type != CT_ATTACKER) {
580 return FALSE;
581 }
582
583 if (!player_knows_techs_with_flag(pplayer, TF_BUILD_AIRBORNE)) {
584 return FALSE;
585 }
586
587 unit_type_iterate(punittype) {
588 struct unit_class *pclass = utype_class(punittype);
589
590 if (pclass->adv.land_move == MOVE_NONE
591 || pclass->adv.sea_move == MOVE_NONE
592 || uclass_has_flag(pclass, UCF_TERRAIN_SPEED)
593 || unit_type_is_losing_hp(pplayer, punittype)) {
594 /* We don't consider this a plane */
595 continue;
596 }
597 if (can_city_build_unit_now(pcity, punittype)) {
598 struct unit *virtual_unit =
599 unit_virtual_create(pplayer, pcity, punittype,
600 do_make_unit_veteran(pcity, punittype));
601 int profit = find_something_to_bomb(ait, virtual_unit, NULL, NULL);
602
603 if (profit > choice->want){
604 /* Update choice */
605 choice->want = profit;
606 choice->value.utype = punittype;
607 choice->type = CT_ATTACKER;
608 choice->need_boat = FALSE;
609 want_something = TRUE;
610 log_debug("%s wants to build %s (want=%d)",
611 city_name_get(pcity), utype_rule_name(punittype), profit);
612 } else {
613 log_debug("%s doesn't want to build %s (want=%d)",
614 city_name_get(pcity), utype_rule_name(punittype), profit);
615 }
616 unit_virtual_destroy(virtual_unit);
617 }
618 } unit_type_iterate_end;
619
620 return want_something;
621 }
622