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