]> git.pond.sub.org Git - empserver/blob - src/lib/subs/aircombat.c
Refactor interception over ships and land units
[empserver] / src / lib / subs / aircombat.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2008, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                           Ken Stevens, Steve McClure
5  *
6  *  This program 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 2 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, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  *  ---
21  *
22  *  See files README, COPYING and CREDITS in the root of the source
23  *  tree for related information and legal notices.  It is expected
24  *  that future projects/authors will amend these files as needed.
25  *
26  *  ---
27  *
28  *  aircombat.c: Deal with air to air combat
29  *
30  *  Known contributors to this file:
31  *     Dave Pare, 1986
32  *     Thomas Ruschak, 1992
33  *     Steve McClure, 1996
34  *     Markus Armbruster, 2006-2008
35  */
36
37 #include <config.h>
38
39 #include "file.h"
40 #include "land.h"
41 #include "map.h"
42 #include "misc.h"
43 #include "nat.h"
44 #include "news.h"
45 #include "nsc.h"
46 #include "optlist.h"
47 #include "path.h"
48 #include "plane.h"
49 #include "player.h"
50 #include "prototypes.h"
51 #include "sect.h"
52 #include "ship.h"
53 #include "xy.h"
54
55 #define FLAK_GUN_MAX 14
56
57 static int plane_caps(struct emp_qelem *);
58 static void ac_intercept(struct emp_qelem *, struct emp_qelem *,
59                          struct emp_qelem *, natid, coord, coord);
60 static int all_missiles(struct emp_qelem *);
61 static void ac_dog(struct plist *, struct plist *);
62 static void ac_planedamage(struct plist *, natid, int, natid, int,
63                            int, char *);
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 getilist(struct emp_qelem *, 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              int no_air_defense)
75 {
76     int val, non_missiles;
77     int rel;
78     int dir;
79     int nats[MAXNOC];
80     int lnats[MAXNOC];
81     int gotilist[MAXNOC];
82     int unfriendly[MAXNOC];
83     int overfly[MAXNOC];
84     int flags;
85     struct emp_qelem ilist[MAXNOC], *qp;
86     char mypath[1024];
87     int myp;
88     int civ, mil;
89     natid plane_owner;
90     struct sctstr sect;
91     struct shpstr ship;
92     struct lndstr land;
93     struct nstr_item ni;
94     natid cn;
95     struct natstr *over, *mynatp;
96     struct plist *plp;
97     int evaded;
98     struct shiplist *head = NULL;
99     int changed = 0;
100 /* We want to only intercept once per sector per owner.  So, if we overfly
101    a sector, and then overfly some land units or ships, we don't want to
102    potentially intercept 3 times. */
103
104     plp = (struct plist *)bomb_list->q_forw;
105     plane_owner = plp->plane.pln_own;
106
107     strncpy(mypath, path, sizeof(mypath));
108     myp = 0;
109
110     memset(overfly, 0, sizeof(overfly));
111     memset(gotilist, 0, sizeof(gotilist));
112     memset(unfriendly, 0, sizeof(unfriendly));
113     for (cn = 1; cn < MAXNOC; cn++) {
114         if ((mynatp = getnatp(cn)) == 0)
115             continue;
116         rel = getrel(mynatp, plane_owner);
117         if (rel > HOSTILE)
118             continue;
119         if (plane_owner == cn)
120             continue;
121         unfriendly[cn]++;
122     }
123     if (mission_flags & PM_R) {
124         flags = plane_caps(bomb_list);
125         if (flags & P_S) {
126             PR(plane_owner, "\nSPY Plane report\n");
127             PRdate(plane_owner);
128             sathead();
129         } else if (flags & P_A) {
130             PR(plane_owner, "\nAnti-Sub Patrol report\n");
131         } else {
132             PR(plane_owner, "\nReconnaissance report\n");
133             PRdate(plane_owner);
134         }
135     }
136
137     while ((dir = mypath[myp++]) && !QEMPTY(bomb_list)) {
138         if ((val = diridx(dir)) == DIR_STOP)
139             break;
140         /* XXX using xnorm is probably bad */
141         x = xnorm(x + diroff[val][0]);
142         y = ynorm(y + diroff[val][1]);
143         getsect(x, y, &sect);
144         over = getnatp(sect.sct_own);
145
146         if (mission_flags & PM_R) {
147             flags = plane_caps(bomb_list);
148             if (opt_HIDDEN)
149                 setcont(plane_owner, sect.sct_own, FOUND_FLY);
150             if (sect.sct_type == SCT_WATER) {
151                 PR(plane_owner, "flying over %s at %s\n",
152                    dchr[sect.sct_type].d_name, xyas(x, y, plane_owner));
153                 if (mission_flags & PM_S)
154                     plane_sweep(bomb_list, x, y);
155                 if (flags & P_A)
156                     plane_sona(bomb_list, x, y, &head);
157                 changed += map_set(plane_owner,
158                                    sect.sct_x, sect.sct_y,
159                                    dchr[sect.sct_type].d_mnem, 0);
160             } else if (flags & P_S) {
161                 satdisp_sect(&sect, flags & P_I ? 10 : 50);
162             } else {
163                 /* This is borrowed from lookout */
164                 if (sect.sct_own == plane_owner)
165                     PR(plane_owner, "Your ");
166                 else
167                     PR(plane_owner, "%s (#%d) ",
168                        cname(sect.sct_own), sect.sct_own);
169                 PR(plane_owner, "%s", dchr[sect.sct_type].d_name);
170                 changed += map_set(plane_owner,
171                                    sect.sct_x, sect.sct_y,
172                                    dchr[sect.sct_type].d_mnem, 0);
173                 PR(plane_owner, " %d%% efficient ",
174                    (sect.sct_own == plane_owner) ?
175                    sect.sct_effic : roundintby((int)sect.sct_effic, 25));
176                 civ = sect.sct_item[I_CIVIL];
177                 mil = sect.sct_item[I_MILIT];
178                 if (civ)
179                     PR(plane_owner, "with %s%d civ ",
180                        (sect.sct_own == plane_owner) ?
181                        "" : "approx ",
182                        (sect.sct_own == plane_owner) ?
183                        civ : roundintby(civ, 25));
184                 if (mil)
185                     PR(plane_owner, "with %s%d mil ",
186                        (sect.sct_own == plane_owner) ?
187                        "" : "approx ",
188                        (sect.sct_own == plane_owner) ?
189                        mil : roundintby(mil, 25));
190                 PR(plane_owner, "@ %s\n", xyas(x, y, plane_owner));
191             }
192             if (flags & P_S)
193                 satdisp_units(sect.sct_x, sect.sct_y);
194         } else {
195             PR(plane_owner, "flying over %s at %s\n",
196                dchr[sect.sct_type].d_name, xyas(x, y, plane_owner));
197             changed += map_set(plane_owner, sect.sct_x, sect.sct_y,
198                                dchr[sect.sct_type].d_mnem, 0);
199         }
200         if ((rel = getrel(over, plane_owner)) == ALLIED)
201             continue;
202
203         evaded = do_evade(bomb_list, esc_list);
204
205         if (sect.sct_own != 0 && sect.sct_own != plane_owner && !evaded) {
206             /* We only show planes overhead if they didn't
207              * evade radar */
208             overfly[sect.sct_own]++;
209             PR(sect.sct_own, "%s planes spotted over %s\n",
210                cname(plane_owner), xyas(x, y, sect.sct_own));
211             if (opt_HIDDEN)
212                 setcont(cn, plane_owner, FOUND_FLY);
213         }
214
215         if (!evaded) {
216             /* Fire flak */
217             if (unfriendly[sect.sct_own])
218                 ac_doflak(bomb_list, &sect);
219             /* If bombers left, fire flak from units and ships */
220             if (!QEMPTY(bomb_list))
221                 ac_landflak(bomb_list, x, y);
222             if (!QEMPTY(bomb_list))
223                 ac_shipflak(bomb_list, x, y);
224         }
225         /* mission planes aborted due to flak -- don't send escorts */
226         if (QEMPTY(bomb_list))
227             break;
228         if (!no_air_defense && !evaded)
229             air_defense(x, y, plane_owner, bomb_list, esc_list);
230
231         if (sect.sct_own == 0 || sect.sct_own == plane_owner)
232             continue;
233
234         if (evaded)
235             continue;
236
237         non_missiles = 0;
238         for (qp = bomb_list->q_forw; qp != bomb_list; qp = qp->q_forw) {
239             struct plist *ip = (struct plist *)qp;
240             if (!(plchr[(int)ip->plane.pln_type].pl_flags & P_M))
241                 non_missiles = 1;
242         }
243
244         if (!non_missiles)
245             continue;
246
247         if (unfriendly[sect.sct_own] && !gotilist[sect.sct_own]) {
248             getilist(&ilist[sect.sct_own], sect.sct_own);
249             gotilist[sect.sct_own]++;
250         }
251         if (rel > HOSTILE)
252             continue;
253         ac_intercept(bomb_list, esc_list, &ilist[sect.sct_own],
254                      sect.sct_own, x, y);
255     }
256
257     /* Let's report all of the overflights even if aborted */
258     for (cn = 1; cn < MAXNOC; cn++) {
259         if (plane_owner == cn)
260             continue;
261         if (overfly[cn] > 0)
262             nreport(plane_owner, N_OVFLY_SECT, cn, overfly[cn]);
263     }
264     /* If the map changed, update it */
265     if (changed)
266         writemap(player->cnum);
267     /* Now, if the bomber and escort lists are empty, we are done */
268     if (QEMPTY(bomb_list) && QEMPTY(esc_list))
269         goto out;
270
271     /* Something made it through */
272     /* Go figure out if there are ships in this sector, and who's they are */
273     memset(nats, 0, sizeof(nats));
274     snxtitem_xy(&ni, EF_SHIP, x, y);
275     while (nxtitem(&ni, &ship)) {
276         if (mchr[(int)ship.shp_type].m_flags & M_SUB)
277             continue;
278         nats[ship.shp_own]++;
279     }
280     /* Go figure out if there are units in this sector, and who's they are */
281     memset(lnats, 0, sizeof(lnats));
282     snxtitem_xy(&ni, EF_LAND, x, y);
283     while (nxtitem(&ni, &land)) {
284         if (land.lnd_ship >= 0 || land.lnd_land >= 0)
285             continue;
286         lnats[land.lnd_own]++;
287     }
288
289     /* Now, let's make life a little rougher. */
290     for (cn = 1; cn < MAXNOC && !QEMPTY(bomb_list); cn++) {
291         if (plane_owner == cn)
292             continue;
293         if (nats[cn] != 0) {
294             PR(plane_owner, "Flying over %s ships in %s\n",
295                cname(cn), xyas(x, y, plane_owner));
296             PR(cn, "%s planes spotted over ships in %s\n",
297                cname(plane_owner), xyas(x, y, cn));
298         }
299         if (lnats[cn] != 0) {
300             PR(plane_owner, "Flying over %s land units in %s\n",
301                cname(cn), xyas(x, y, plane_owner));
302             PR(cn, "%s planes spotted over land units in %s\n",
303                cname(plane_owner), xyas(x, y, cn));
304         }
305         if (nats[cn] || lnats[cn]) {
306             if (opt_HIDDEN)
307                 setcont(cn, plane_owner, FOUND_FLY);
308             if (unfriendly[cn] && !evaded) {
309                 if (!gotilist[cn]) {
310                     getilist(&ilist[cn], cn);
311                     gotilist[cn]++;
312                 }
313                 ac_intercept(bomb_list, esc_list, &ilist[cn], cn, x, y);
314             }
315         }
316     }
317 out:
318     free_shiplist(&head);
319     for (cn = 1; cn < MAXNOC; cn++) {
320         if (gotilist[cn])
321             pln_put(&ilist[cn]);
322     }
323 }
324
325 static int
326 plane_caps(struct emp_qelem *list)
327 {
328     struct emp_qelem *qp;
329     struct plist *plp;
330     int fl;
331
332     fl = 0;
333     for (qp = list->q_forw; qp != list; qp = qp->q_forw) {
334         plp = (struct plist *)qp;
335         fl |= plp->pcp->pl_flags;
336     }
337
338     return fl;
339 }
340
341 static int
342 count_non_missiles(struct emp_qelem *list)
343 {
344     struct emp_qelem *qp;
345     struct plist *plp;
346     int att_count = 0;
347
348     /* don't intercept missiles */
349     for (qp = list->q_forw; qp != list; qp = qp->q_forw) {
350         plp = (struct plist *)qp;
351         if (!(plp->pcp->pl_flags & P_M))
352             att_count++;
353     }
354     return att_count;
355 }
356
357 void
358 sam_intercept(struct emp_qelem *att_list, struct emp_qelem *def_list,
359               natid def_own, natid plane_owner, coord x, coord y,
360               int delete_missiles)
361 {
362     struct emp_qelem *aqp;
363     struct emp_qelem *anext;
364     struct emp_qelem *dqp;
365     struct emp_qelem *dnext;
366     struct plist *aplp;
367     struct plist *dplp;
368     int first = 1;
369
370     for (aqp = att_list->q_forw,
371          dqp = def_list->q_forw;
372          aqp != att_list && dqp != def_list; aqp = anext) {
373         anext = aqp->q_forw;
374         aplp = (struct plist *)aqp;
375         if (aplp->pcp->pl_flags & P_M)
376             continue;
377         if (aplp->pcp->pl_cost < 1000)
378             continue;
379         for (; dqp != def_list; dqp = dnext) {
380             dnext = dqp->q_forw;
381             dplp = (struct plist *)dqp;
382             if (!(dplp->pcp->pl_flags & P_M))
383                 continue;
384
385             if (dplp->plane.pln_range <
386                 mapdist(x, y, dplp->plane.pln_x, dplp->plane.pln_y)) {
387                 emp_remque(dqp);
388                 free(dqp);
389                 continue;
390             }
391             if (CANT_HAPPEN(dplp->plane.pln_flags & PLN_LAUNCHED)
392                 || mission_pln_equip(dplp, 0, P_F, 0) < 0) {
393                 emp_remque(dqp);
394                 free(dqp);
395                 continue;
396             }
397             dplp->plane.pln_flags |= PLN_LAUNCHED;
398             putplane(dplp->plane.pln_uid, &dplp->plane);
399             if (first) {
400                 first = 0;
401                 PR(plane_owner, "%s launches SAMs!\n", cname(def_own));
402                 PR(def_own, "Launching SAMs at %s planes over %s!\n",
403                    cname(plane_owner), xyas(x, y, def_own));
404                 ac_combat_headers(plane_owner, def_own);
405             }
406             ac_dog(aplp, dplp);
407             dqp = dnext;
408             break;
409         }
410     }
411     if (!first) {
412         PR(plane_owner, "\n");
413         PR(def_own, "\n");
414     }
415     if (delete_missiles) {
416         for (; dqp != def_list; dqp = dnext) {
417             dnext = dqp->q_forw;
418             dplp = (struct plist *)dqp;
419             if (!(dplp->pcp->pl_flags & P_M))
420                 continue;
421             emp_remque(dqp);
422             free(dqp);
423             continue;
424         }
425     }
426 }
427
428 static void
429 ac_intercept(struct emp_qelem *bomb_list, struct emp_qelem *esc_list,
430              struct emp_qelem *def_list, natid def_own, coord x, coord y)
431 {
432     struct plnstr *pp;
433     struct plist *plp;
434     int icount;
435     struct emp_qelem *next;
436     struct emp_qelem *qp;
437     struct emp_qelem int_list;
438     int att_count;
439     natid plane_owner;
440     int dist;
441
442     plp = (struct plist *)bomb_list->q_forw;
443     plane_owner = plp->plane.pln_own;
444
445     icount = 0;
446
447     sam_intercept(bomb_list, def_list, def_own, plane_owner, x, y, 0);
448     sam_intercept(esc_list, def_list, def_own, plane_owner, x, y, 1);
449     if (!(att_count = count_non_missiles(bomb_list) +
450           count_non_missiles(esc_list)))
451         return;
452
453     emp_initque(&int_list);
454     for (qp = def_list->q_forw; qp != def_list; qp = next) {
455         next = qp->q_forw;
456         plp = (struct plist *)qp;
457         pp = &plp->plane;
458         /* SAMs interdict separately */
459         if (plp->pcp->pl_flags & P_M)
460             continue;
461         dist = mapdist(x, y, pp->pln_x, pp->pln_y) * 2;
462         if (pp->pln_range < dist)
463             continue;
464         if (CANT_HAPPEN(pp->pln_flags & PLN_LAUNCHED)
465             || mission_pln_equip(plp, 0, P_F, 0) < 0) {
466             emp_remque(qp);
467             free(qp);
468             continue;
469         }
470         /* got one; delete from def_list, add to int_list */
471         emp_remque(qp);
472         emp_insque(qp, &int_list);
473         pp->pln_flags |= PLN_LAUNCHED;
474         pp->pln_mobil -= pln_mobcost(dist, pp, P_F);
475         putplane(pp->pln_uid, pp);
476         icount++;
477         if (icount > att_count)
478             break;
479     }
480     if (icount == 0)
481         return;
482     PR(plane_owner, "%d %s fighter%s rising to intercept!\n",
483        icount, cname(def_own), icount == 1 ? " is" : "s are");
484     PR(def_own, "%d fighter%s intercepting %s planes over %s!\n",
485        icount, icount == 1 ? " is" : "s are", cname(plane_owner),
486        xyas(x, y, def_own));
487     ac_combat_headers(plane_owner, def_own);
488     ac_airtoair(esc_list, &int_list);
489     ac_airtoair(bomb_list, &int_list);
490     PR(plane_owner, "\n");
491     PR(def_own, "\n");
492     pln_put(&int_list);
493 }
494
495 void
496 ac_combat_headers(natid plane_owner, natid def_own)
497 {
498     PR(plane_owner,
499        " %-10.10s %-10.10s  strength int odds  damage           results\n",
500        cname(plane_owner), cname(def_own));
501     PR(def_own,
502        " %-10.10s %-10.10s  strength int odds  damage           results\n",
503        cname(def_own), cname(plane_owner));
504 }
505
506 /*
507  * air-to-air combat.
508  */
509 void
510 ac_airtoair(struct emp_qelem *att_list, struct emp_qelem *int_list)
511 {
512     struct plist *attacker;
513     struct plist *interceptor;
514     struct emp_qelem *att;
515     struct emp_qelem *in;
516     int nplanes;
517     int more_att;
518     int more_int;
519     struct emp_qelem *att_next;
520     struct emp_qelem *in_next;
521
522     att = att_list->q_forw;
523     in = int_list->q_forw;
524     more_att = 1;
525     more_int = 1;
526     if (QEMPTY(att_list) || QEMPTY(int_list)) {
527         more_att = 0;
528         more_int = 0;
529     }
530     while (more_att || more_int) {
531         in_next = in->q_forw;
532         att_next = att->q_forw;
533         attacker = (struct plist *)att;
534
535         /* skip missiles. If only missiles left, we're done */
536         if (plchr[(int)attacker->plane.pln_type].pl_flags & P_M) {
537             att = att_next;
538             if (att == att_list) {
539                 more_att = 0;
540                 if (QEMPTY(att_list))
541                     more_int = 0;
542                 else
543                     att = att->q_forw;
544             }
545             if (all_missiles(att_list))
546                 more_att = 0;
547             continue;
548         }
549         interceptor = (struct plist *)in;
550         nplanes = attacker->plane.pln_effic;
551         if (nplanes > interceptor->plane.pln_effic)
552             nplanes = interceptor->plane.pln_effic;
553         ac_dog(attacker, interceptor);
554         in = in_next;
555         att = att_next;
556         if (att == att_list) {
557             more_att = 0;
558             if (QEMPTY(att_list))
559                 more_int = 0;
560             else
561                 att = att->q_forw;
562         }
563         if (in == int_list) {
564             more_int = 0;
565             if (QEMPTY(int_list))
566                 more_att = 0;
567             else
568                 in = in->q_forw;
569         }
570     }
571 }
572
573 static int
574 all_missiles(struct emp_qelem *att_list)
575 {
576     struct emp_qelem *qp;
577     struct plist *p;
578
579     qp = att_list->q_forw;
580     while (qp != att_list) {
581         p = (struct plist *)qp;
582         if (!(plchr[(int)p->plane.pln_type].pl_flags & P_M))
583             return 0;
584
585         qp = qp->q_forw;
586     }
587     return 1;
588 }
589
590 static void
591 ac_dog(struct plist *ap, struct plist *dp)
592 {
593     int att, def;
594     double odds;
595     int intensity;
596     natid att_own, def_own;
597     int adam, ddam;
598     char mesg[1024];
599     char temp[14];
600
601     att_own = ap->plane.pln_own;
602     def_own = dp->plane.pln_own;
603
604     PR(att_own, " %3.3s #%-4d  %3.3s #%-4d",
605        ap->pcp->pl_name,
606        ap->plane.pln_uid, dp->pcp->pl_name, dp->plane.pln_uid);
607     if (def_own)
608         PR(def_own, " %3.3s #%-4d  %3.3s #%-4d",
609            dp->pcp->pl_name,
610            dp->plane.pln_uid, ap->pcp->pl_name, ap->plane.pln_uid);
611     att = pln_att(&ap->plane);
612     if (att == 0)
613         att = pln_def(&ap->plane);
614     att = att * ap->plane.pln_effic / 100;
615     att = MAX(att, ap->pcp->pl_def / 2);
616
617     def = pln_def(&dp->plane) * dp->plane.pln_effic / 100;
618     def = MAX(def, dp->pcp->pl_def / 2);
619
620     if ((ap->pcp->pl_flags & P_F) && ap->bombs != 0)
621         att -= 2;
622     if ((dp->pcp->pl_flags & P_F) && dp->bombs != 0)
623         def -= 2;
624     att += ap->pcp->pl_stealth / 25.0;
625     def += dp->pcp->pl_stealth / 25.0;
626     if (att < 1) {
627         def += 1 - att;
628         att = 1;
629     }
630     if (def < 1) {
631         att += 1 - def;
632         def = 1;
633     }
634     odds = ((double)att / ((double)def + (double)att));
635     if (odds <= 0.05)
636         odds = 0.05;
637     intensity = roll(20) + roll(20) + roll(20) + roll(20) + 1;
638
639     PR(att_own, "   %3d/%-3d %3d  %3.2f  ", att, def, intensity, odds);
640     PR(def_own, "   %3d/%-3d %3d  %3.2f  ", def, att, intensity, odds);
641
642     adam = 0;
643     ddam = 0;
644     while ((intensity--) > 0) {
645
646         if (chance(odds)) {
647             ddam += 1;
648             if ((dp->plane.pln_effic - ddam) < PLANE_MINEFF)
649                 intensity = 0;
650         } else {
651             adam += 1;
652             if ((ap->plane.pln_effic - adam) < PLANE_MINEFF)
653                 intensity = 0;
654         }
655     }
656
657     if (dp->pcp->pl_flags & P_M)
658         ddam = 100;
659
660     PR(att_own, "%3d/%-3d", adam, ddam);
661     PR(def_own, "%3d/%-3d", ddam, adam);
662     ac_planedamage(ap, def_own, adam, def_own, 1, 0, mesg);
663     strncpy(temp, mesg, 14);
664     ac_planedamage(dp, att_own, ddam, att_own, 1, 0, mesg);
665     PR(att_own, "%-13.13s %-13.13s\n", temp, mesg);
666     PR(def_own, "%-13.13s %-13.13s\n", mesg, temp);
667
668     if (opt_HIDDEN) {
669         setcont(att_own, def_own, FOUND_FLY);
670         setcont(def_own, att_own, FOUND_FLY);
671     }
672 }
673
674 /*
675  * zap plane associated with plp.
676  * Damaging country is "from", damage is "dam".
677  * def_own is the country on the other side of the conflict from the plane
678  * owner. The only time def_own != from is when the interceptor is getting
679  * damaged.
680  *
681  * NOTE: This routine removes the appropriate plane element from the
682  * queue if it gets destroyed.  That means that the caller must assume
683  * that the current queue pointer is invalid on return from the ac_planedamage
684  * call.  (this has caused bugs in the past)
685  */
686 static void
687 ac_planedamage(struct plist *plp, natid from, int dam, natid other,
688                int checkabort, int show, char *mesg)
689 {
690     struct plnstr *pp;
691     int disp;
692     char dmess[255];
693     int eff;
694     natid plane_owner;
695
696     disp = 0;
697     pp = &plp->plane;
698     plane_owner = pp->pln_own;
699     eff = pp->pln_effic;
700     sprintf(dmess, " no damage");
701     if (dam <= 0) {
702         strcpy(mesg, dmess);
703         return;
704     }
705     memset(dmess, 0, sizeof(dmess));
706     eff -= dam;
707     if (eff < 0)
708         eff = 0;
709     if (eff < PLANE_MINEFF) {
710         sprintf(dmess, " shot down");
711         disp = 1;
712     } else if (eff < 80 && chance((80 - eff) / 100.0) && checkabort) {
713         sprintf(dmess, " aborted @%2d%%", eff);
714         disp = 2;
715     } else if (show == 0) {
716         sprintf(dmess, " cleared");
717     }
718
719     if ((plp->pcp->pl_flags & P_M) == 0) {
720         if (show) {
721             PR(plane_owner, "    %s %s takes %d%s.\n",
722                cname(pp->pln_own), prplane(pp), dam, dmess);
723             if (other)
724                 PR(other, "    %s %s takes %d%s.\n",
725                    cname(pp->pln_own), prplane(pp), dam, dmess);
726         }
727     }
728     if (show && checkabort == 1) {
729         PR(plane_owner, "\n");
730         if (other)
731             PR(other, "\n");
732     }
733
734     pp->pln_effic = eff;
735     pp->pln_mobil -= MIN(32 + pp->pln_mobil, dam / 2);
736     if (disp) {
737         if (disp == 1 && from != 0 && (plp->pcp->pl_flags & P_M) == 0)
738             nreport(from, N_DOWN_PLANE, pp->pln_own, 1);
739         pln_put1(plp);
740     } else
741         putplane(pp->pln_uid, pp);
742     strcpy(mesg, dmess);
743 }
744
745 static void
746 ac_doflak(struct emp_qelem *list, struct sctstr *from)
747 {
748     int shell;
749     int gun;
750     natid plane_owner;
751     struct plist *plp;
752
753     plp = (struct plist *)list->q_forw;
754     plane_owner = plp->plane.pln_own;
755
756     gun = MIN(FLAK_GUN_MAX, from->sct_item[I_GUN]);
757     shell = from->sct_item[I_SHELL];
758     if (gun > shell * 2) {
759         shell += supply_commod(from->sct_own, from->sct_x, from->sct_y,
760                                I_SHELL, (gun + 1) / 2 - shell);
761         from->sct_item[I_SHELL] = shell;
762         putsect(from);
763     }
764     if (gun > shell * 2)
765         gun = shell * 2;
766
767     gun = roundavg(tfact(from->sct_own, 2.0 * gun));
768     if (gun > 0) {
769         PR(plane_owner, "firing %d flak guns in %s...\n",
770            gun, xyas(from->sct_x, from->sct_y, plane_owner));
771         if (from->sct_own != 0)
772             PR(from->sct_own, "firing %d flak guns in %s...\n",
773                gun, xyas(from->sct_x, from->sct_y, from->sct_own));
774         ac_fireflak(list, from->sct_own, gun);
775     }
776 }
777
778 static void
779 ac_shipflak(struct emp_qelem *list, coord x, coord y)
780 {
781     struct nstr_item ni;
782     struct shpstr ship;
783     struct mchrstr *mcp;
784     double flak, total, ngun;
785     int gun, shell;
786     int rel;
787     struct plist *plp;
788     natid plane_owner;
789     natid from;
790     int nats[MAXNOC];
791
792     plp = (struct plist *)list->q_forw;
793     plane_owner = plp->plane.pln_own;
794
795     memset(nats, 0, sizeof(nats));
796     total = ngun = 0;
797     snxtitem_xy(&ni, EF_SHIP, x, y);
798     while (!QEMPTY(list) && nxtitem(&ni, &ship)) {
799         if (ship.shp_own == 0 || ship.shp_own == plane_owner)
800             continue;
801         mcp = &mchr[(int)ship.shp_type];
802         if (mcp->m_flags & M_SUB)
803             continue;
804         rel = getrel(getnatp(ship.shp_own), plane_owner);
805         if (rel > HOSTILE)
806             continue;
807         shell = 0;
808         gun = shp_usable_guns(&ship);
809         if (gun) {
810             shell = ship.shp_item[I_SHELL];
811             if (shell <= 0) {
812                 shell = supply_commod(ship.shp_own, ship.shp_x, ship.shp_y,
813                                       I_SHELL, 1);
814                 ship.shp_item[I_SHELL] = shell;
815                 putship(ship.shp_uid, &ship);
816             }
817         }
818         if (gun == 0 || shell == 0)
819             continue;
820         flak = gun * (ship.shp_effic / 100.0);
821         ngun += flak;
822         total += techfact(ship.shp_tech, flak * 2.0);
823
824         if (!nats[ship.shp_own]) {
825             /* First time here, print the message */
826             PR(ship.shp_own, "%s planes spotted over ships in %s\n",
827                cname(plane_owner), xyas(x, y, ship.shp_own));
828             PR(plane_owner, "Flying over %s ships in %s\n",
829                cname(ship.shp_own), xyas(x, y, plane_owner));
830             nats[ship.shp_own] = 1;
831         }
832         PR(ship.shp_own, "firing %.0f flak guns from %s...\n",
833            flak, prship(&ship));
834         from = ship.shp_own;
835     }
836
837     /* Limit to FLAK_GUN_MAX guns of average tech factor */
838     if (ngun > FLAK_GUN_MAX)
839         total *= FLAK_GUN_MAX / ngun;
840
841     gun = roundavg(total);
842     if (gun > 0) {
843         PR(plane_owner, "Flak!  Ships firing %d flak guns...\n", gun);
844         ac_fireflak(list, from, gun);
845     }
846 }
847
848 static void
849 ac_landflak(struct emp_qelem *list, coord x, coord y)
850 {
851     struct nstr_item ni;
852     struct lndstr land;
853     struct lchrstr *lcp;
854     double flak, total, ngun;
855     int aaf, gun;
856     int rel;
857     struct plist *plp;
858     natid plane_owner;
859     natid from;
860     int nats[MAXNOC];
861
862     plp = (struct plist *)list->q_forw;
863     plane_owner = plp->plane.pln_own;
864
865     memset(nats, 0, sizeof(nats));
866     total = ngun = 0;
867     snxtitem_xy(&ni, EF_LAND, x, y);
868     while (!QEMPTY(list) && nxtitem(&ni, &land)) {
869         if (land.lnd_own == 0 || land.lnd_own == plane_owner)
870             continue;
871         lcp = &lchr[(int)land.lnd_type];
872         aaf = lnd_aaf(&land);
873         if ((lcp->l_flags & L_FLAK) == 0 || aaf == 0)
874             continue;
875         if (land.lnd_ship >= 0 || land.lnd_land >= 0)
876             continue;
877         rel = getrel(getnatp(land.lnd_own), plane_owner);
878         if (rel > HOSTILE)
879             continue;
880         flak = aaf * 1.5 * land.lnd_effic / 100.0;
881         ngun += flak;
882         total += techfact(land.lnd_tech, flak * 2.0);
883
884         if (!nats[land.lnd_own]) {
885             /* First time here, print the message */
886             PR(land.lnd_own, "%s planes spotted over land units in %s\n",
887                cname(plane_owner), xyas(x, y, land.lnd_own));
888             PR(plane_owner, "Flying over %s land units in %s\n",
889                cname(land.lnd_own), xyas(x, y, plane_owner));
890             nats[land.lnd_own] = 1;
891         }
892         PR(land.lnd_own, "firing flak guns from unit %s (aa rating %d)\n",
893            prland(&land), aaf);
894         from = land.lnd_own;
895     }
896
897     /* Limit to FLAK_GUN_MAX guns of average tech factor */
898     if (ngun > FLAK_GUN_MAX)
899         total *= FLAK_GUN_MAX / ngun;
900
901     gun = roundavg(total);
902     if (gun > 0) {
903         PR(plane_owner, "Flak!  Land units firing %d flak guns...\n", gun);
904         ac_fireflak(list, from, gun);
905     }
906 }
907
908 /*
909  * Called from shipflak, landflak, and doflak.
910  */
911 static void
912 ac_fireflak(struct emp_qelem *list, natid from, int guns)
913 {
914     struct plist *plp;
915     int n;
916     struct emp_qelem *qp;
917     struct emp_qelem *next;
918     char msg[255];
919
920     plp = (struct plist *)list->q_forw;
921
922     for (qp = list->q_forw; qp != list; qp = next) {
923         next = qp->q_forw;
924         plp = (struct plist *)qp;
925         n = ac_flak_dam(guns, pln_def(&plp->plane), plp->pcp->pl_flags);
926         ac_planedamage(plp, from, n, 0, 2, 1, msg);
927     }
928 }
929
930 /*
931  * Calculate flak damage
932  */
933 int
934 ac_flak_dam(int guns, int def, int pl_flags)
935 {
936     int flak, dam;
937     float mult;
938     /*                             <-7      -7     -6     -5     -4 */
939     static float flaktable[18] = { 0.132f, 0.20f, 0.20f, 0.25f, 0.30f,
940     /*    -3     -2     -1      0     +1     +2     +3     +4 */
941          0.35f, 0.40f, 0.45f, 0.50f, 0.50f, 0.55f, 0.60f, 0.65f,
942     /*    +5    +6     +7     +8    >+8 */
943          0.70f,0.75f, 0.80f, 0.85f, 1.1305f };
944     enum { FLAK_MAX = sizeof(flaktable)/sizeof(flaktable[0]) - 1 };
945
946     flak = guns - def;
947     if ((pl_flags & P_T) == 0)
948         flak--;
949     if (pl_flags & P_X)
950         flak -= 2;
951     if (pl_flags & P_H)
952         flak -= 1;
953
954     if (flak > 8)
955         mult = flaktable[FLAK_MAX];
956     else if (flak < -7)
957         mult = flaktable[0];
958     else {
959         flak += 8;
960         mult = flaktable[flak];
961     }
962     mult *= flakscale;
963     dam = (int)((roll(8) + 2) * mult);
964     if (dam > 100)
965         dam = 100;
966     return dam;
967 }
968
969 /*
970  * Get a list of planes available for interception duties.
971  */
972 static void
973 getilist(struct emp_qelem *list, natid own)
974 {
975     struct plchrstr *pcp;
976     struct plnstr plane;
977     struct nstr_item ni;
978     struct plist *ip;
979
980     emp_initque(list);
981     snxtitem_all(&ni, EF_PLANE);
982     while (nxtitem(&ni, &plane)) {
983         if (plane.pln_own != own)
984             continue;
985         pcp = &plchr[(int)plane.pln_type];
986         if ((pcp->pl_flags & P_F) == 0)
987             continue;
988         if (plane.pln_flags & PLN_LAUNCHED)
989             continue;
990         if (plane.pln_mission != 0)
991             continue;
992         if (plane.pln_mobil <= 0)
993             continue;
994         if (plane.pln_effic < 40)
995             continue;
996         if (!pln_airbase_ok(&plane, 0, 0))
997             continue;
998         /* got one! */
999         ip = malloc(sizeof(*ip));
1000         ip->bombs = 0;
1001         ip->misc = 0;
1002         ip->pcp = &plchr[(int)plane.pln_type];
1003         ip->plane = plane;
1004         emp_insque(&ip->queue, list);
1005     }
1006 }
1007
1008 static int
1009 do_evade(struct emp_qelem *bomb_list, struct emp_qelem *esc_list)
1010 {
1011     struct emp_qelem *qp;
1012     double evade;
1013     struct plist *plp;
1014
1015     evade = 100.0;
1016     for (qp = bomb_list->q_forw; qp != bomb_list; qp = qp->q_forw) {
1017         plp = (struct plist *)qp;
1018         if (evade > plp->pcp->pl_stealth / 100.0)
1019             evade = plp->pcp->pl_stealth / 100.0;
1020     }
1021     for (qp = esc_list->q_forw; qp != esc_list; qp = qp->q_forw) {
1022         plp = (struct plist *)qp;
1023         if (evade > plp->pcp->pl_stealth / 100.0)
1024             evade = plp->pcp->pl_stealth / 100.0;
1025     }
1026
1027     if (chance(evade))
1028         return 1;
1029
1030     return 0;
1031 }