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(&parameter, punit);
98   parameter.omniscience = !has_handicap(pplayer, H_MAP);
99   pfm = pf_map_new(&parameter);
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(&parameter, punit);
247   parameter.omniscience = !has_handicap(pplayer, H_MAP);
248   pfm = pf_map_new(&parameter);
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(&parameter, punit);
365   parameter.omniscience = !has_handicap(pplayer, H_MAP);
366   pfm = pf_map_new(&parameter);
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(&parameter);
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