]> git.pond.sub.org Git - empserver/blob - src/lib/subs/aircombat.c
New macro ARRAY_SIZE()
[empserver] / src / lib / subs / aircombat.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2020, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                Ken Stevens, Steve McClure, Markus Armbruster
5  *
6  *  Empire is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  *  ---
20  *
21  *  See files README, COPYING and CREDITS in the root of the source
22  *  tree for related information and legal notices.  It is expected
23  *  that future projects/authors will amend these files as needed.
24  *
25  *  ---
26  *
27  *  aircombat.c: Deal with air to air combat
28  *
29  *  Known contributors to this file:
30  *     Dave Pare, 1986
31  *     Thomas Ruschak, 1992
32  *     Steve McClure, 1996
33  *     Markus Armbruster, 2006-2020
34  */
35
36 #include <config.h>
37
38 #include "chance.h"
39 #include "land.h"
40 #include "map.h"
41 #include "misc.h"
42 #include "mission.h"
43 #include "nat.h"
44 #include "news.h"
45 #include "nsc.h"
46 #include "optlist.h"
47 #include "path.h"
48 #include "plague.h"
49 #include "plane.h"
50 #include "player.h"
51 #include "prototypes.h"
52 #include "sect.h"
53 #include "ship.h"
54 #include "xy.h"
55
56 #define FLAK_GUN_MAX 14
57
58 static void ac_intercept(struct emp_qelem *, struct emp_qelem *,
59                          struct emp_qelem *, natid, coord, coord, int);
60 static void ac_combat_headers(natid, natid);
61 static void ac_airtoair(struct emp_qelem *, struct emp_qelem *);
62 static void ac_dog(struct plist *, struct plist *);
63 static void ac_putplane(struct plist *, int);
64 static void ac_doflak(struct emp_qelem *, struct sctstr *);
65 static void ac_landflak(struct emp_qelem *, coord, coord);
66 static void ac_shipflak(struct emp_qelem *, coord, coord);
67 static void ac_fireflak(struct emp_qelem *, natid, int);
68 static void getilists(struct emp_qelem *, enum relations[], natid);
69 static int do_evade(struct emp_qelem *, struct emp_qelem *);
70
71 void
72 ac_encounter(struct emp_qelem *bomb_list, struct emp_qelem *esc_list,
73              coord x, coord y, char *path, int mission_flags)
74 {
75     int val;
76     int dir;
77     unsigned char gotships[MAXNOC];
78     unsigned char gotlands[MAXNOC];
79     enum relations rel[MAXNOC];
80     int overfly[MAXNOC];
81     int flags;
82     struct emp_qelem ilist[MAXNOC];
83     natid plane_owner;
84     struct sctstr sect;
85     struct shpstr ship;
86     struct lndstr land;
87     struct nstr_item ni;
88     natid cn;
89     struct plist *plp;
90     int evaded;
91     struct shiplist *head = NULL;
92     int changed = 0;
93 /* We want to only intercept once per sector per owner.  So, if we overfly
94    a sector, and then overfly some land units or ships, we don't want to
95    potentially intercept 3 times. */
96
97     plp = (struct plist *)bomb_list->q_forw;
98     plane_owner = plp->plane.pln_own;
99
100     memset(overfly, 0, sizeof(overfly));
101     getilists(ilist, rel, plane_owner);
102
103     if (CANT_HAPPEN(mission_flags && plane_owner != player->cnum))
104         mission_flags = 0;
105
106     if (mission_flags & PM_R) {
107         flags = pln_caps(bomb_list);
108         if (flags & P_S) {
109             pr("\nSPY Plane report\n");
110             prdate();
111             sathead();
112         } else if (flags & P_A) {
113             pr("\nAnti-Sub Patrol report\n");
114         } else {
115             pr("\nReconnaissance report\n");
116             prdate();
117         }
118     }
119
120     for (;;) {
121         getsect(x, y, &sect);
122         memset(gotships, 0, sizeof(gotships));
123         snxtitem_xy(&ni, EF_SHIP, x, y);
124         while (nxtitem(&ni, &ship)) {
125             if (mchr[(int)ship.shp_type].m_flags & M_SUB)
126                 continue;
127             gotships[ship.shp_own] = 1;
128         }
129         memset(gotlands, 0, sizeof(gotlands));
130         snxtitem_xy(&ni, EF_LAND, x, y);
131         while (nxtitem(&ni, &land)) {
132             if (land.lnd_ship >= 0 || land.lnd_land >= 0)
133                 continue;
134             gotlands[land.lnd_own] = 1;
135         }
136
137         if (mission_flags & PM_R) {
138             flags = pln_caps(bomb_list);
139             if (opt_HIDDEN)
140                 setcont(player->cnum, sect.sct_own, FOUND_FLY);
141             if (sect.sct_type == SCT_WATER)
142                 pr("flying over %s at %s\n",
143                    dchr[sect.sct_type].d_name, xyas(x, y, player->cnum));
144             else if (flags & P_S)
145                 satdisp_sect(&sect, flags & P_I ? 10 : 50);
146             else
147                 look_at_sect(&sect, 25);
148
149             if (flags & P_S)
150                 satdisp_units(sect.sct_x, sect.sct_y);
151             else {
152                 for (cn = 1; cn < MAXNOC; cn++) {
153                     if (cn == player->cnum)
154                         continue;
155                     if (gotships[cn])
156                         pr("Flying over %s ships in %s\n",
157                            cname(cn), xyas(x, y, player->cnum));
158                     if (gotlands[cn])
159                         pr("Flying over %s land units in %s\n",
160                            cname(cn), xyas(x, y, player->cnum));
161                 }
162             }
163         } else
164             mpr(plane_owner, "flying over %s at %s\n",
165                 dchr[sect.sct_type].d_name, xyas(x, y, plane_owner));
166         changed += map_set(plane_owner, sect.sct_x, sect.sct_y,
167                            dchr[sect.sct_type].d_mnem, 0);
168
169         evaded = do_evade(bomb_list, esc_list);
170         if (!evaded) {
171             overfly[sect.sct_own]++;
172             for (cn = 1; cn < MAXNOC; cn++) {
173                 if (rel[cn] == ALLIED)
174                     continue;
175                 if (cn != sect.sct_own && !gotships[cn] && !gotlands[cn])
176                     continue;
177                 mpr(cn, "%s planes spotted over %s\n",
178                     cname(plane_owner), xyas(x, y, cn));
179                 if (opt_HIDDEN)
180                     setcont(cn, plane_owner, FOUND_FLY);
181             }
182
183             /* Fire flak */
184             if (rel[sect.sct_own] <= HOSTILE)
185                 ac_doflak(bomb_list, &sect);
186             /* If bombers left, fire flak from units and ships */
187             if (!QEMPTY(bomb_list))
188                 ac_landflak(bomb_list, x, y);
189             if (!QEMPTY(bomb_list))
190                 ac_shipflak(bomb_list, x, y);
191             /* mission planes aborted due to flak -- don't send escorts */
192             if (QEMPTY(bomb_list))
193                 break;
194
195             for (cn = 1; cn < MAXNOC && !QEMPTY(bomb_list); cn++) {
196                 if (rel[cn] > HOSTILE)
197                     continue;
198                 ac_intercept(bomb_list, esc_list, &ilist[cn], cn, x, y,
199                              !(cn == sect.sct_own
200                                || gotships[cn] || gotlands[cn]));
201             }
202         }
203
204         if (mission_flags & PM_R) {
205             flags = pln_caps(bomb_list);
206             if (sect.sct_type == SCT_WATER && mission_flags & PM_S)
207                 plane_sweep(bomb_list, x, y);
208             if (sect.sct_type == SCT_WATER && flags & P_A)
209                 plane_sona(bomb_list, x, y, &head);
210         }
211
212         dir = *path++;
213         if (!dir || QEMPTY(bomb_list) || (val = diridx(dir)) == DIR_STOP)
214             break;
215         x = xnorm(x + diroff[val][0]);
216         y = ynorm(y + diroff[val][1]);
217     }
218
219     /* Let's report all of the overflights even if aborted */
220     for (cn = 1; cn < MAXNOC; cn++) {
221         if (overfly[cn] > 0 && rel[cn] != ALLIED)
222             nreport(plane_owner, N_OVFLY_SECT, cn, overfly[cn]);
223     }
224     /* If the map changed, update it */
225     if (changed)
226         writemap(plane_owner);
227
228     free_shiplist(&head);
229     for (cn = 1; cn < MAXNOC; cn++)
230         pln_put(&ilist[cn]);
231 }
232
233 static void
234 sam_intercept(struct emp_qelem *att_list, struct emp_qelem *def_list,
235               natid def_own, natid plane_owner, coord x, coord y,
236               int only_mission)
237 {
238     struct emp_qelem *aqp;
239     struct emp_qelem *anext;
240     struct emp_qelem *dqp;
241     struct emp_qelem *dnext;
242     struct plist *aplp;
243     struct plist *dplp;
244     struct plnstr *pp;
245     int first = 1;
246
247     for (aqp = att_list->q_forw,
248          dqp = def_list->q_forw;
249          aqp != att_list && dqp != def_list; aqp = anext) {
250         anext = aqp->q_forw;
251         aplp = (struct plist *)aqp;
252         if (aplp->pcp->pl_cost < 1000)
253             continue;
254         for (; dqp != def_list; dqp = dnext) {
255             dnext = dqp->q_forw;
256             dplp = (struct plist *)dqp;
257             pp = &dplp->plane;
258             if (!(dplp->pcp->pl_flags & P_M))
259                 continue;
260             if (only_mission && !pp->pln_mission)
261                 continue;
262             if (pp->pln_range < mapdist(x, y, pp->pln_x, pp->pln_y))
263                 continue;
264             if (pp->pln_mission
265                 && pp->pln_radius < mapdist(x, y, pp->pln_opx, pp->pln_opy))
266                 continue;
267             if (CANT_HAPPEN(pp->pln_flags & PLN_LAUNCHED)
268                 || mission_pln_equip(dplp, NULL, 0) < 0) {
269                 emp_remque(dqp);
270                 free(dqp);
271                 continue;
272             }
273             pp->pln_flags |= PLN_LAUNCHED;
274             putplane(pp->pln_uid, pp);
275             if (first) {
276                 first = 0;
277                 mpr(plane_owner, "%s launches SAMs!\n", cname(def_own));
278                 mpr(def_own, "Launching SAMs at %s planes over %s!\n",
279                     cname(plane_owner), xyas(x, y, def_own));
280                 ac_combat_headers(plane_owner, def_own);
281             }
282             ac_dog(aplp, dplp);
283             dqp = dnext;
284             break;
285         }
286     }
287     if (!first) {
288         mpr(plane_owner, "\n");
289         mpr(def_own, "\n");
290     }
291 }
292
293 static void
294 ac_intercept(struct emp_qelem *bomb_list, struct emp_qelem *esc_list,
295              struct emp_qelem *def_list, natid def_own, coord x, coord y,
296              int only_mission)
297 {
298     struct plnstr *pp;
299     struct plist *plp;
300     int icount;
301     struct emp_qelem *next;
302     struct emp_qelem *qp;
303     struct emp_qelem int_list;
304     int att_count;
305     natid plane_owner;
306     int dist;
307
308     plp = (struct plist *)bomb_list->q_forw;
309     plane_owner = plp->plane.pln_own;
310
311     sam_intercept(bomb_list, def_list, def_own, plane_owner, x, y,
312                   only_mission);
313     sam_intercept(esc_list, def_list, def_own, plane_owner, x, y,
314                   only_mission);
315
316     att_count = emp_quelen(bomb_list) + emp_quelen(esc_list);
317     if (!att_count)
318         return;
319
320     emp_initque(&int_list);
321     icount = 0;
322     for (qp = def_list->q_forw; qp != def_list; qp = next) {
323         next = qp->q_forw;
324         plp = (struct plist *)qp;
325         pp = &plp->plane;
326         /* SAMs interdict separately */
327         if (plp->pcp->pl_flags & P_M)
328             continue;
329         if (only_mission && !pp->pln_mission)
330             continue;
331         dist = mapdist(x, y, pp->pln_x, pp->pln_y) * 2;
332         if (pp->pln_range < dist)
333             continue;
334         if (pp->pln_mission
335             && pp->pln_radius < mapdist(x, y, pp->pln_opx, pp->pln_opy))
336             continue;
337         if (CANT_HAPPEN(pp->pln_flags & PLN_LAUNCHED)
338             || mission_pln_equip(plp, NULL, 0) < 0) {
339             emp_remque(qp);
340             free(qp);
341             continue;
342         }
343         /* got one; delete from def_list, add to int_list */
344         emp_remque(qp);
345         emp_insque(qp, &int_list);
346         pp->pln_flags |= PLN_LAUNCHED;
347         pp->pln_mobil -= pln_mobcost(dist, pp, 0);
348         putplane(pp->pln_uid, pp);
349         icount++;
350         if (icount > att_count)
351             break;
352     }
353     if (icount == 0)
354         return;
355     mpr(plane_owner, "%d %s fighter%s rising to intercept!\n",
356         icount, cname(def_own), icount == 1 ? " is" : "s are");
357     mpr(def_own, "%d fighter%s intercepting %s planes over %s!\n",
358         icount, icount == 1 ? " is" : "s are", cname(plane_owner),
359         xyas(x, y, def_own));
360     ac_combat_headers(plane_owner, def_own);
361     ac_airtoair(esc_list, &int_list);
362     ac_airtoair(bomb_list, &int_list);
363     mpr(plane_owner, "\n");
364     mpr(def_own, "\n");
365     pln_put(&int_list);
366 }
367
368 static void
369 ac_combat_headers(natid plane_owner, natid def_own)
370 {
371     mpr(plane_owner,
372         " %-10.10s %-10.10s  strength int odds  damage           results\n",
373         cname(plane_owner), cname(def_own));
374     mpr(def_own,
375         " %-10.10s %-10.10s  strength int odds  damage           results\n",
376         cname(def_own), cname(plane_owner));
377 }
378
379 /*
380  * air-to-air combat.
381  */
382 static void
383 ac_airtoair(struct emp_qelem *att_list, struct emp_qelem *int_list)
384 {
385     struct plist *attacker;
386     struct plist *interceptor;
387     struct emp_qelem *att;
388     struct emp_qelem *in;
389     int more_att;
390     int more_int;
391     struct emp_qelem *att_next;
392     struct emp_qelem *in_next;
393
394     att = att_list->q_forw;
395     in = int_list->q_forw;
396     more_att = 1;
397     more_int = 1;
398     if (QEMPTY(att_list) || QEMPTY(int_list)) {
399         more_att = 0;
400         more_int = 0;
401     }
402     while (more_att || more_int) {
403         in_next = in->q_forw;
404         att_next = att->q_forw;
405         attacker = (struct plist *)att;
406         interceptor = (struct plist *)in;
407         ac_dog(attacker, interceptor);
408         in = in_next;
409         att = att_next;
410         if (att == att_list) {
411             more_att = 0;
412             if (QEMPTY(att_list))
413                 more_int = 0;
414             else
415                 att = att->q_forw;
416         }
417         if (in == int_list) {
418             more_int = 0;
419             if (QEMPTY(int_list))
420                 more_att = 0;
421             else
422                 in = in->q_forw;
423         }
424     }
425 }
426
427 static void
428 ac_dog_report(natid to, int intensity, double odds,
429               struct plist *p1, int val1, int dam1, char *dam_mesg1,
430               struct plist *p2, int val2, int dam2, char *dam_mesg2)
431 {
432     mpr(to, " %3.3s #%-4d  %3.3s #%-4d   %3d/%-3d %3d  %3.2f  %3d/%-3d"
433         "%-13.13s %-13.13s\n",
434         p1->pcp->pl_name, p1->plane.pln_uid,
435         p2->pcp->pl_name, p2->plane.pln_uid,
436         val1, val2, intensity, odds, dam1, dam2,
437         dam_mesg1, dam_mesg2);
438 }
439
440 static void
441 ac_dog(struct plist *ap, struct plist *dp)
442 {
443     int att, def;
444     double odds;
445     int intensity, i;
446     natid att_own, def_own;
447     int adam, ddam, adisp, ddisp;
448     char adam_mesg[14], ddam_mesg[14];
449
450     att_own = ap->plane.pln_own;
451     def_own = dp->plane.pln_own;
452
453     att = pln_att(&ap->plane);
454     if (att == 0)
455         att = pln_def(&ap->plane);
456     att = att * ap->plane.pln_effic / 100;
457     att = MAX(att, ap->pcp->pl_def / 2);
458
459     def = pln_def(&dp->plane) * dp->plane.pln_effic / 100;
460     def = MAX(def, dp->pcp->pl_def / 2);
461
462     if ((ap->pcp->pl_flags & P_F) && ap->load != 0)
463         att -= 2;
464     if ((dp->pcp->pl_flags & P_F) && dp->load != 0)
465         def -= 2;
466     att += ap->pcp->pl_stealth / 25.0;
467     def += dp->pcp->pl_stealth / 25.0;
468     if (att < 1) {
469         def += 1 - att;
470         att = 1;
471     }
472     if (def < 1) {
473         att += 1 - def;
474         def = 1;
475     }
476     odds = ((double)att / ((double)def + (double)att));
477     if (odds <= 0.05)
478         odds = 0.05;
479     intensity = roll(20) + roll(20) + roll(20) + roll(20) + 1;
480
481     adam = 0;
482     ddam = 0;
483     for (i = 0; i < intensity; i++) {
484         if (chance(odds)) {
485             ddam++;
486             if (dp->plane.pln_effic - ddam < PLANE_MINEFF)
487                 break;
488         } else {
489             adam++;
490             if (ap->plane.pln_effic - adam < PLANE_MINEFF)
491                 break;
492         }
493     }
494
495     if (dp->pcp->pl_flags & P_M)
496         ddam = 100;
497
498     adisp = ac_damage_plane(&ap->plane, def_own, adam, 0, adam_mesg);
499     ddisp = ac_damage_plane(&dp->plane, att_own, ddam, 0, ddam_mesg);
500     ac_dog_report(att_own, intensity, odds,
501                   ap, att, adam, adam_mesg,
502                   dp, def, ddam, ddam_mesg);
503     ac_dog_report(def_own, intensity, odds,
504                   dp, def, ddam, ddam_mesg,
505                   ap, att, adam, adam_mesg);
506     ac_putplane(ap, adisp);
507     ac_putplane(dp, ddisp);
508
509     if (opt_HIDDEN) {
510         setcont(att_own, def_own, FOUND_FLY);
511         setcont(def_own, att_own, FOUND_FLY);
512     }
513 }
514
515 int
516 ac_damage_plane(struct plnstr *pp, natid from, int dam, int flak,
517                 char *mesg)
518 {
519     int eff, disp;
520
521     *mesg = 0;
522     if (dam <= 0) {
523         if (!flak)
524             snprintf(mesg, 14, " no damage");
525         return 0;
526     }
527
528     eff = pp->pln_effic - dam;
529     if (eff < 0)
530         eff = 0;
531
532     disp = 0;
533     if (eff < PLANE_MINEFF) {
534         snprintf(mesg, 14, " shot down");
535         disp = 1;
536     } else if (eff < 80 && chance((80 - eff) / 100.0)) {
537         snprintf(mesg, 14, " aborted @%2d%%", eff);
538         disp = 2;
539     } else if (!flak)
540         snprintf(mesg, 14, " cleared");
541
542     pp->pln_effic = eff;
543     pp->pln_mobil -= MIN(32 + pp->pln_mobil, dam / 2);
544
545     if (disp == 1 && from != 0 && !(plchr[pp->pln_type].pl_flags & P_M))
546         nreport(from, N_DOWN_PLANE, pp->pln_own, 1);
547     return disp;
548 }
549
550 /*
551  * NOTE: This routine may remove the appropriate plane element from the
552  * queue if it gets destroyed.  That means that the caller must assume
553  * that the current queue pointer is invalid on return from the
554  * call.  (this has caused bugs in the past)
555  */
556 static void
557 ac_putplane(struct plist *plp, int disp)
558 {
559     if (disp)
560         pln_put1(plp);
561     else
562         putplane(plp->plane.pln_uid, &plp->plane);
563 }
564
565 static void
566 ac_doflak(struct emp_qelem *list, struct sctstr *from)
567 {
568     int gun;
569     natid plane_owner;
570     struct plist *plp;
571
572     plp = (struct plist *)list->q_forw;
573     plane_owner = plp->plane.pln_own;
574
575     gun = MIN(FLAK_GUN_MAX, from->sct_item[I_GUN]);
576     gun = roundavg(tfact(from->sct_own, 2.0 * gun));
577     if (gun > 0) {
578         mpr(plane_owner, "firing %d flak guns in %s...\n",
579             gun, xyas(from->sct_x, from->sct_y, plane_owner));
580         mpr(from->sct_own, "firing %d flak guns in %s...\n",
581             gun, xyas(from->sct_x, from->sct_y, from->sct_own));
582         ac_fireflak(list, from->sct_own, gun);
583     }
584 }
585
586 static void
587 ac_shipflak(struct emp_qelem *list, coord x, coord y)
588 {
589     struct nstr_item ni;
590     struct shpstr ship;
591     struct mchrstr *mcp;
592     double flak, total, ngun;
593     int gun;
594     struct plist *plp;
595     natid plane_owner;
596     natid from;
597
598     plp = (struct plist *)list->q_forw;
599     plane_owner = plp->plane.pln_own;
600
601     total = ngun = 0;
602     snxtitem_xy(&ni, EF_SHIP, x, y);
603     while (!QEMPTY(list) && nxtitem(&ni, &ship)) {
604         if (ship.shp_own == 0 || ship.shp_own == plane_owner)
605             continue;
606         mcp = &mchr[(int)ship.shp_type];
607         if (mcp->m_flags & M_SUB)
608             continue;
609         if (relations_with(ship.shp_own, plane_owner) > HOSTILE)
610             continue;
611         gun = shp_usable_guns(&ship);
612         if (gun == 0)
613             continue;
614         flak = gun * (ship.shp_effic / 100.0);
615         ngun += flak;
616         total += techfact(ship.shp_tech, flak * 2.0);
617
618         mpr(ship.shp_own, "firing %.0f flak guns from %s...\n",
619             flak, prship(&ship));
620         from = ship.shp_own;
621     }
622
623     /* Limit to FLAK_GUN_MAX guns of average tech factor */
624     if (ngun > FLAK_GUN_MAX)
625         total *= FLAK_GUN_MAX / ngun;
626
627     gun = roundavg(total);
628     if (gun > 0) {
629         mpr(plane_owner, "Flak!  Ships firing %d flak guns...\n", gun);
630         ac_fireflak(list, from, gun);
631     }
632 }
633
634 static void
635 ac_landflak(struct emp_qelem *list, coord x, coord y)
636 {
637     struct nstr_item ni;
638     struct lndstr land;
639     struct lchrstr *lcp;
640     double flak, total, ngun;
641     int aaf, gun;
642     struct plist *plp;
643     natid plane_owner;
644     natid from;
645
646     plp = (struct plist *)list->q_forw;
647     plane_owner = plp->plane.pln_own;
648
649     total = ngun = 0;
650     snxtitem_xy(&ni, EF_LAND, x, y);
651     while (!QEMPTY(list) && nxtitem(&ni, &land)) {
652         if (land.lnd_own == 0 || land.lnd_own == plane_owner)
653             continue;
654         lcp = &lchr[(int)land.lnd_type];
655         aaf = lnd_aaf(&land);
656         if ((lcp->l_flags & L_FLAK) == 0 || aaf == 0)
657             continue;
658         if (land.lnd_ship >= 0 || land.lnd_land >= 0)
659             continue;
660         if (relations_with(land.lnd_own, plane_owner) > HOSTILE)
661             continue;
662         flak = aaf * 1.5 * land.lnd_effic / 100.0;
663         ngun += flak;
664         total += techfact(land.lnd_tech, flak * 2.0);
665
666         mpr(land.lnd_own, "firing flak guns from unit %s (AA rating %d)\n",
667             prland(&land), aaf);
668         from = land.lnd_own;
669     }
670
671     /* Limit to FLAK_GUN_MAX guns of average tech factor */
672     if (ngun > FLAK_GUN_MAX)
673         total *= FLAK_GUN_MAX / ngun;
674
675     gun = roundavg(total);
676     if (gun > 0) {
677         mpr(plane_owner, "Flak!  Land units firing %d flak guns...\n", gun);
678         ac_fireflak(list, from, gun);
679     }
680 }
681
682 /*
683  * Called from shipflak, landflak, and doflak.
684  */
685 static void
686 ac_fireflak(struct emp_qelem *list, natid from, int guns)
687 {
688     struct plist *plp;
689     int n, disp;
690     struct emp_qelem *qp;
691     struct emp_qelem *next;
692     char msg[14];
693
694     for (qp = list->q_forw; qp != list; qp = next) {
695         next = qp->q_forw;
696         plp = (struct plist *)qp;
697         n = ac_flak_dam(guns, pln_def(&plp->plane), plp->pcp->pl_flags);
698         disp = ac_damage_plane(&plp->plane, from, n, 1, msg);
699         mpr(plp->plane.pln_own, "    %s takes %d%s%s.\n",
700             prplane(&plp->plane), n, *msg ? " --" : "", msg);
701         ac_putplane(plp, disp);
702     }
703 }
704
705 /*
706  * Calculate flak damage
707  */
708 int
709 ac_flak_dam(int guns, int def, int pl_flags)
710 {
711     int flak, dam;
712     float mult;
713     /*                             <-7      -7     -6     -5     -4 */
714     static float flaktable[18] = { 0.132f, 0.20f, 0.20f, 0.25f, 0.30f,
715     /*    -3     -2     -1      0     +1     +2     +3     +4 */
716          0.35f, 0.40f, 0.45f, 0.50f, 0.50f, 0.55f, 0.60f, 0.65f,
717     /*    +5    +6     +7     +8    >+8 */
718          0.70f,0.75f, 0.80f, 0.85f, 1.1305f };
719     enum { FLAK_MAX = ARRAY_SIZE(flaktable) - 1 };
720
721     flak = guns - def;
722     if ((pl_flags & P_T) == 0)
723         flak--;
724
725     if (flak > 8)
726         mult = flaktable[FLAK_MAX];
727     else if (flak < -7)
728         mult = flaktable[0];
729     else {
730         flak += 8;
731         mult = flaktable[flak];
732     }
733     mult *= flakscale;
734     dam = (int)((roll(8) + 2) * mult);
735     if (dam > 100)
736         dam = 100;
737     return dam;
738 }
739
740 /*
741  * Get planes available for interception duties.
742  */
743 static void
744 getilists(struct emp_qelem *list, enum relations rel[], natid intruder)
745 {
746     natid cn;
747     struct plchrstr *pcp;
748     struct plnstr plane;
749     struct nstr_item ni;
750     struct plist *ip;
751
752     rel[0] = NEUTRAL;
753     for (cn = 1; cn < MAXNOC; cn++) {
754         rel[cn] = relations_with(cn, intruder);
755         emp_initque(&list[cn]);
756     }
757
758     snxtitem_all(&ni, EF_PLANE);
759     while (nxtitem(&ni, &plane)) {
760         if (rel[plane.pln_own] > HOSTILE)
761             continue;
762         pcp = &plchr[(int)plane.pln_type];
763         if ((pcp->pl_flags & P_F) == 0)
764             continue;
765         if (plane.pln_flags & PLN_LAUNCHED)
766             continue;
767         if (plane.pln_mission && plane.pln_mission != MI_AIR_DEFENSE)
768             continue;
769         if (plane.pln_mobil <= 0)
770             continue;
771         if (plane.pln_effic < 40)
772             continue;
773         if (opt_MARKET) {
774             if (ontradingblock(EF_PLANE, &plane))
775                 continue;
776         }
777         if (!pln_airbase_ok(&plane, 0, 0))
778             continue;
779         /* got one! */
780         ip = malloc(sizeof(*ip));
781         ip->load = 0;
782         ip->pstage = PLG_HEALTHY;
783         ip->pcp = &plchr[(int)plane.pln_type];
784         ip->plane = plane;
785         emp_insque(&ip->queue, &list[plane.pln_own]);
786     }
787 }
788
789 static int
790 do_evade(struct emp_qelem *bomb_list, struct emp_qelem *esc_list)
791 {
792     struct emp_qelem *qp;
793     double evade;
794     struct plist *plp;
795
796     evade = 100.0;
797     for (qp = bomb_list->q_forw; qp != bomb_list; qp = qp->q_forw) {
798         plp = (struct plist *)qp;
799         if (evade > plp->pcp->pl_stealth / 100.0)
800             evade = plp->pcp->pl_stealth / 100.0;
801     }
802     for (qp = esc_list->q_forw; qp != esc_list; qp = qp->q_forw) {
803         plp = (struct plist *)qp;
804         if (evade > plp->pcp->pl_stealth / 100.0)
805             evade = plp->pcp->pl_stealth / 100.0;
806     }
807
808     if (chance(evade))
809         return 1;
810
811     return 0;
812 }