]> git.pond.sub.org Git - empserver/blob - src/lib/commands/mfir.c
retreat: Fix group retreat after failed board sinks ship
[empserver] / src / lib / commands / mfir.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2015, 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  *  multifire.c: Fire at other sectors/ships
28  *
29  *  Known contributors to this file:
30  *     Steve McClure, 2000
31  *     Markus Armbruster, 2004-2015
32  */
33
34 #include <config.h>
35
36 #include "chance.h"
37 #include "commands.h"
38 #include "empobj.h"
39 #include "news.h"
40 #include "optlist.h"
41 #include "retreat.h"
42
43 enum targ_type {
44     targ_land, targ_ship, targ_sub, targ_bogus
45 };
46
47 struct flist {
48     struct emp_qelem queue;     /* list of fired things */
49     short type;                 /* EF_SECTOR, EF_SHIP or EF_LAND */
50     int uid;
51     coord x, y;
52     int defdam;                 /* damage defenders did */
53     natid victim;
54 };
55
56 static int defend(struct emp_qelem *, struct emp_qelem *,
57                   struct empobj *, natid, int *);
58 static void do_defdam(struct emp_qelem *, double);
59 static int quiet_bigdef(int, struct emp_qelem *, natid, natid, coord,
60                         coord, int *);
61 static void add_to_flist(struct emp_qelem *, struct empobj *, int, natid);
62 static void free_flist(struct emp_qelem *);
63 static struct flist *search_flist(struct emp_qelem *, struct empobj *);
64
65 int
66 multifire(void)
67 {
68     static int ef_with_guns[] = { EF_SECTOR, EF_SHIP, EF_LAND, EF_BAD };
69     char *ptr;
70     double range;
71     int trange, range2;
72     coord fx;
73     coord fy;
74     coord x;
75     coord y;
76     int dam;
77     int totaldefdam = 0;
78     int vshipno;
79     natid vict;
80     struct shpstr fship;
81     struct lndstr fland;
82     struct sctstr fsect;
83     char *sep = "";
84     struct shpstr vship;
85     struct sctstr vsect;
86     enum targ_type target;
87     struct nstr_item nbst;
88     int type;
89     struct empobj *attgp;
90     char *p;
91     int nfiring = 0;
92     int ndefending = 0;
93     union empobj_storage item;
94     struct emp_qelem fired, defended;
95     double odds;
96     char buf[1024];
97
98     emp_initque(&fired);
99     emp_initque(&defended);
100     p = getstarg(player->argp[1],
101                  "Firing from ship(s), sect(s), or land unit(s)? ", buf);
102     if (!p)
103         return RET_SYN;
104     type = ef_byname_from(p, ef_with_guns);
105     if (opt_NO_FORT_FIRE && type == EF_SECTOR) {
106         pr("Fort firing is disabled.\n");
107         return RET_FAIL;
108     }
109     if (type < 0) {
110         pr("Ships, land units or sectors only!\n");
111         return RET_SYN;
112     }
113     if (!snxtitem(&nbst, type, player->argp[2], "Firing from? "))
114         return RET_SYN;
115
116     while (nxtitem(&nbst, &item)) {
117         if (type == EF_LAND) {
118             if (!getland(item.land.lnd_uid, &fland))
119                 continue;
120             if (!getsect(item.land.lnd_x, item.land.lnd_y, &fsect))
121                 continue;
122             if (item.land.lnd_own != player->cnum)
123                 continue;
124
125             if (lchr[fland.lnd_type].l_dam == 0) {
126                 pr("Unit %d cannot fire!\n", fland.lnd_uid);
127                 continue;
128             }
129             if (fland.lnd_item[I_MILIT] < 1) {
130                 pr("Unit %d cannot fire because it has no military!\n",
131                    fland.lnd_uid);
132                 continue;
133             }
134             if (fland.lnd_ship >= 0) {
135                 pr("Unit %d cannot fire because it is on a ship!\n",
136                    fland.lnd_uid);
137                 continue;
138             }
139             if (fland.lnd_land >= 0) {
140                 pr("Unit %d cannot fire because it is on a land unit!\n",
141                    fland.lnd_uid);
142                 continue;
143             }
144             if (fland.lnd_effic < LAND_MINFIREEFF) {
145                 pr("Unit %d cannot fire because it is less than %d%% efficient\n",
146                    fland.lnd_uid, LAND_MINFIREEFF);
147                 continue;
148             }
149             if (fland.lnd_item[I_GUN] == 0) {
150                 pr("%s -- not enough guns\n", prland(&fland));
151                 continue;
152             }
153
154             if (fland.lnd_item[I_SHELL] == 0) {
155                 pr("%s -- not enough shells\n", prland(&fland));
156                 continue;
157             }
158             pr("%s%s ready to fire\n", sep, prland(&fland));
159             fx = fland.lnd_x;
160             fy = fland.lnd_y;
161         } else if (type == EF_SHIP) {
162             if (!getship(item.ship.shp_uid, &fship))
163                 continue;
164             if (item.ship.shp_own != player->cnum)
165                 continue;
166             if (item.ship.shp_item[I_MILIT] < 1) {
167                 pr("Not enough mil on ship #%d\n", item.ship.shp_uid);
168                 continue;
169             }
170             if (mchr[item.ship.shp_type].m_glim == 0
171                 && !(mchr[fship.shp_type].m_flags & M_DCH)) {
172                 pr("Ships %d cannot fire guns!\n", item.ship.shp_uid);
173                 continue;
174             }
175             if (item.ship.shp_item[I_GUN] == 0) {
176                 pr("Not enough guns on ship #%d\n", item.ship.shp_uid);
177                 continue;
178             }
179             if (item.ship.shp_item[I_SHELL] == 0) {
180                 pr("Not enough shells on ship #%d\n", item.ship.shp_uid);
181                 continue;
182             }
183             if (item.ship.shp_effic < 60) {
184                 pr("Ship #%d is crippled!\n", item.ship.shp_uid);
185                 continue;
186             }
187             pr("%s%s ready to fire\n", sep, prship(&fship));
188             fx = fship.shp_x;
189             fy = fship.shp_y;
190         } else {
191             if (!getsect(item.sect.sct_x, item.sect.sct_y, &fsect))
192                 continue;
193             if (item.sect.sct_own != player->cnum)
194                 continue;
195             if (item.sect.sct_type != SCT_FORTR)
196                 continue;
197             if (item.sect.sct_effic < FORTEFF) {
198                 pr("Fort not efficient enough to fire!\n");
199                 continue;
200             }
201             if (item.sect.sct_item[I_GUN] == 0) {
202                 pr("Not enough guns in sector %s!\n",
203                    xyas(item.sect.sct_x, item.sect.sct_y, player->cnum));
204                 continue;
205             }
206             if (item.sect.sct_item[I_SHELL] == 0) {
207                 pr("Not enough shells in sector %s!\n",
208                    xyas(item.sect.sct_x, item.sect.sct_y, player->cnum));
209                 continue;
210             }
211             if (item.sect.sct_item[I_MILIT] < 5) {
212                 pr("Not enough military in sector %s!\n",
213                    xyas(item.sect.sct_x, item.sect.sct_y, player->cnum));
214                 continue;
215             }
216             pr("%sSector %s ready to fire\n", sep,
217                xyas(item.sect.sct_x, item.sect.sct_y, player->cnum));
218             fx = fsect.sct_x;
219             fy = fsect.sct_y;
220         }
221         sep = "\n";
222
223         ptr = getstarg(player->argp[3], "Firing at? ", buf);
224         if (!ptr)
225             return RET_FAIL;
226         if (!*ptr)
227             continue;
228         if (!issector(ptr)) {
229             vshipno = atoi(ptr);
230             if (vshipno < 0 || !getship(vshipno, &vship) ||
231                 (!vship.shp_own)) {
232                 pr("No such ship exists!\n");
233                 continue;
234             }
235             target = (mchr[(int)vship.shp_type].m_flags & M_SUB) ?
236                 targ_sub : targ_ship;
237             vict = vship.shp_own;
238             x = vship.shp_x;
239             y = vship.shp_y;
240             if (!getsect(x, y, &vsect)) {
241                 pr("No such sector exists!\n");
242                 continue;
243             }
244         } else {
245             if (!sarg_xy(ptr, &x, &y) || !getsect(x, y, &vsect)) {
246                 pr("No such sector exists!\n");
247                 continue;
248             }
249             /* We check the sector type, but we only use it for damage, not
250                reporting.  That way, you don't get extra information you wouldn't
251                normally get.  Besides, what if they want to slam water? :)  */
252             if (vsect.sct_type == SCT_SANCT || vsect.sct_type == SCT_WATER)
253                 target = targ_bogus;
254             else
255                 target = targ_land;
256             vict = vsect.sct_own;
257             x = vsect.sct_x;
258             y = vsect.sct_y;
259         }
260
261         trange = mapdist(x, y, fx, fy);
262
263         if (type == EF_SHIP) {
264             if (!check_ship_ok(&fship))
265                 return RET_FAIL;
266             if (target == targ_sub || target == targ_ship) {
267                 if (fship.shp_uid == vship.shp_uid) {
268                     pr("You can't fire upon yourself!\n");
269                     continue;
270                 }
271             }
272             range = shp_fire_range(&fship);
273             range2 = roundrange(range);
274             pr("range is %d.00 (%.2f)\n", range2, range);
275             if (target == targ_sub
276                 && trange <= range2
277                 && (mchr[(int)fship.shp_type].m_flags & M_DCH)) {
278                 dam = shp_dchrg(&fship);
279             } else {
280                 if (target == targ_sub)
281                     /* Don't tell it's a sub */
282                     range2 = -1;
283                 if (fship.shp_item[I_GUN] == 0) {
284                     pr("Insufficient arms.\n");
285                     continue;
286                 }
287                 dam = shp_fire(&fship);
288             }
289             putship(fship.shp_uid, &fship);
290             if (CANT_HAPPEN(dam < 0)) {
291                 pr("Klick!     ...\n");
292                 continue;
293             }
294             if (opt_NOMOBCOST == 0) {
295                 fship.shp_mobil = MAX(fship.shp_mobil - 15, -100);
296                 putship(fship.shp_uid, &fship);
297             }
298         } else if (type == EF_LAND) {
299             if (!check_land_ok(&fland))
300                 return RET_FAIL;
301             if (target == targ_land) {
302                 if (fland.lnd_x == vsect.sct_x
303                     && fland.lnd_y == vsect.sct_y) {
304                     pr("You can't fire upon yourself!\n");
305                     continue;
306                 }
307             }
308
309             range = lnd_fire_range(&fland);
310             range2 = roundrange(range);
311             pr("range is %d.00 (%.2f)\n", range2, range);
312             if (target == targ_sub) {
313                 /* Don't tell it's a sub */
314                 range2 = -1;
315             }
316
317             dam = lnd_fire(&fland);
318             putland(fland.lnd_uid, &fland);
319             if (CANT_HAPPEN(dam < 0)) {
320                 pr("Klick!     ...\n");
321                 continue;
322             }
323             if (target == targ_ship) {
324                 if (chance(lnd_acc(&fland) / 100.0))
325                     dam = ldround(dam / 2.0, 1);
326             }
327         } else {
328             if (!check_sect_ok(&fsect))
329                 return RET_FAIL;
330             if (target == targ_land) {
331                 if (fsect.sct_x == vsect.sct_x
332                     && fsect.sct_y == vsect.sct_y) {
333                     pr("You can't fire upon yourself!\n");
334                     continue;
335                 }
336             }
337             dam = fort_fire(&fsect);
338             putsect(&fsect);
339             if (CANT_HAPPEN(dam < 0)) {
340                 pr("Klick!     ...\n");
341                 continue;
342             }
343             range = fortrange(&fsect);
344             range2 = roundrange(range);
345             pr("range is %d.00 (%.2f)\n", range2, range);
346             if (target == targ_sub) {
347                 /* Don't tell it's a sub */
348                 range2 = -1;
349             }
350         }
351         if (trange > range2) {
352             pr("Target out of range.\n");
353             switch (type) {
354             case EF_SECTOR:
355                 putsect(&fsect);
356                 break;
357             case EF_LAND:
358                 fland.lnd_mission = 0;
359                 putland(fland.lnd_uid, &fland);
360                 break;
361             case EF_SHIP:
362                 fship.shp_mission = 0;
363                 putship(fship.shp_uid, &fship);
364                 break;
365             default:
366                 CANT_REACH();
367             }
368             continue;
369         }
370
371         nfiring++;
372         switch (target) {
373         case targ_sub:
374             pr_beep();
375             pr("Kawhomp!!!\n");
376             break;
377         default:
378             pr_beep();
379             pr("Kaboom!!!\n");
380             break;
381         }
382         switch (target) {
383         case targ_bogus:
384         case targ_land:
385             nreport(player->cnum, N_SCT_SHELL, vict, 1);
386             if (vict && vict != player->cnum)
387                 wu(0, vict,
388                    "Country #%d shelled sector %s for %d damage.\n",
389                    player->cnum, xyas(x, y, vict), dam);
390             pr("Shells hit sector %s for %d damage.\n",
391                xyas(x, y, player->cnum), dam);
392             break;
393         case targ_ship:
394             nreport(player->cnum, N_SHP_SHELL, vict, 1);
395             /* fall through */
396         default:
397             if (vict && vict != player->cnum) {
398                 wu(0, vict,
399                    "Country #%d shelled %s in %s for %d damage.\n",
400                    player->cnum, prship(&vship),
401                    xyas(vship.shp_x, vship.shp_y, vict), dam);
402             }
403             pr("Shells hit %s in %s for %d damage.\n",
404                prsub(&vship),
405                xyas(vship.shp_x, vship.shp_y, player->cnum), dam);
406             break;
407         }
408         /*  Ok, now, check if we had a bogus target.  If so,
409            just continue on, since there is no defender. */
410         if (target == targ_bogus)
411             continue;
412         attgp = &item.gen;
413         if (type == EF_LAND) {
414             getsect(fland.lnd_x, fland.lnd_y, &fsect);
415             attgp = (struct empobj *)&fsect;
416         }
417         totaldefdam = defend(&fired, &defended, attgp, vict, &ndefending);
418         switch (target) {
419         case targ_land:
420             getsect(x, y, &vsect);
421             sectdamage(&vsect, dam);
422             putsect(&vsect);
423             break;
424         default:
425             getship(vshipno, &vship);
426             shipdamage(&vship, dam);
427             if (vship.shp_effic < SHIP_MINEFF)
428                 pr("%s sunk!\n", prsub(&vship));
429             if (dam && (vship.shp_rflags & RET_INJURED))
430                 retreat_ship(&vship, vict, 'i');
431             else if (target == targ_sub && (vship.shp_rflags & RET_DCHRGED))
432                 retreat_ship(&vship, vict, 'd');
433             else if (totaldefdam == 0 && (vship.shp_rflags & RET_HELPLESS))
434                 retreat_ship(&vship, vict, 'h');
435             putship(vship.shp_uid, &vship);
436             break;
437         }
438         switch (attgp->ef_type) {
439         case EF_SECTOR:
440             putsect(&fsect);
441             break;
442         case EF_SHIP:
443             if ((target == targ_ship) || (target == targ_sub)) {
444                 if (fship.shp_effic > SHIP_MINEFF) {
445                     shp_missdef(&fship, vict);
446                 }
447             }
448             putship(fship.shp_uid, &fship);
449             break;
450         default:
451             CANT_REACH();
452         }
453     }
454
455     free_flist(&defended);
456     if (nfiring)
457         odds = ((double)ndefending) / ((double)nfiring);
458     else
459         odds = 1.0;
460     do_defdam(&fired, odds);
461     return RET_OK;
462 }
463
464 static int
465 defend(struct emp_qelem *al, struct emp_qelem *dl,
466        struct empobj *attgp, natid vict, int *nd)
467 {
468     int dam;
469     int nfiring = 0;
470
471     dam = quiet_bigdef(attgp->ef_type, dl, vict,
472                        attgp->own, attgp->x, attgp->y, &nfiring);
473     if (dam) {
474         if (nfiring > *nd)
475             *nd = nfiring;
476         add_to_flist(al, attgp, dam, vict);
477     }
478
479     return dam;
480 }
481
482 static void
483 do_defdam(struct emp_qelem *list, double odds)
484 {
485
486     int dam, first = 1;
487     natid vict;
488     struct flist *fp;
489     struct shpstr ship;
490     struct sctstr sect;
491     struct emp_qelem *qp, *next;
492
493     for (qp = list->q_forw; qp != list; qp = next) {
494         next = qp->q_forw;
495         fp = (struct flist *)qp;
496         if (fp->type == EF_SHIP) {
497             if (!getship(fp->uid, &ship) || !ship.shp_own)
498                 continue;
499         }
500         if (first) {
501             pr_beep();
502             pr("\nDefenders fire back!\n");
503             first = 0;
504         }
505         dam = odds * fp->defdam;
506
507         if (fp->type == EF_SHIP) {
508             vict = fp->victim;
509             pr("Return fire hit %s in %s for %d damage.\n",
510                prship(&ship),
511                xyas(ship.shp_x, ship.shp_y, player->cnum), dam);
512             if (vict)
513                 wu(0, vict,
514                    "Return fire hit %s in %s for %d damage.\n",
515                    prsub(&ship), xyas(ship.shp_x, ship.shp_y, vict), dam);
516             shipdamage(&ship, dam);
517             putship(ship.shp_uid, &ship);
518         } else {
519             CANT_HAPPEN(fp->type != EF_SECTOR);
520             getsect(fp->x, fp->y, &sect);
521             vict = fp->victim;
522             pr("Return fire hit sector %s for %d damage.\n",
523                xyas(fp->x, fp->y, player->cnum), dam);
524             sectdamage(&sect, dam);
525             putsect(&sect);
526             if (vict)
527                 wu(0, vict, "Return fire hit sector %s for %d damage.\n",
528                    xyas(fp->x, fp->y, vict), dam);
529         }
530         emp_remque(&fp->queue);
531         free(fp);
532     }
533 }
534
535 static int
536 quiet_bigdef(int type, struct emp_qelem *list, natid own, natid aown,
537              coord ax, coord ay, int *nfiring)
538 {
539     double erange;
540     struct shpstr ship;
541     struct lndstr land;
542     struct nstr_item ni;
543     int dam, dam2;
544     struct sctstr firing;
545     struct nstr_sect ns;
546     struct flist *fp;
547
548     if (own == 0)
549         return 0;
550     dam = 0;
551     snxtitem_dist(&ni, EF_SHIP, ax, ay, 8);
552     while (nxtitem(&ni, &ship)) {
553         if (!feels_like_helping(ship.shp_own, own, aown))
554             continue;
555
556         if ((mchr[ship.shp_type].m_flags & M_SUB) && type != EF_SHIP)
557             continue;
558
559         if (mchr[(int)ship.shp_type].m_flags & M_SUB) {
560             erange = torprange(&ship);
561             if (roundrange(erange) < ni.curdist)
562                 continue;
563             if (!line_of_sight(NULL, ship.shp_x, ship.shp_y, ax, ay))
564                 continue;
565             fp = search_flist(list, (struct empobj *)&ship);
566             if (fp)
567                 dam2 = fp->defdam;
568             else {
569                 dam2 = shp_torp(&ship, 0);
570                 putship(ship.shp_uid, &ship);
571             }
572             if (dam2 < 0)
573                 continue;
574             if (!chance(shp_torp_hitchance(&ship, ni.curdist)))
575                 dam2 = 0;
576         } else {
577             erange = shp_fire_range(&ship);
578             if (roundrange(erange) < ni.curdist)
579                 continue;
580             fp = search_flist(list, (struct empobj *)&ship);
581             if (fp)
582                 dam2 = fp->defdam;
583             else {
584                 dam2 = shp_fire(&ship);
585                 putship(ship.shp_uid, &ship);
586             }
587             if (dam2 < 0)
588                 continue;
589             nreport(ship.shp_own, N_FIRE_BACK, player->cnum, 1);
590         }
591         (*nfiring)++;
592         if (!fp)
593             add_to_flist(list, (struct empobj *)&ship, dam2, 0);
594         dam += dam2;
595     }
596     snxtitem_dist(&ni, EF_LAND, ax, ay, 8);
597     while (nxtitem(&ni, &land)) {
598         if (!feels_like_helping(land.lnd_own, own, aown))
599             continue;
600
601         erange = lnd_fire_range(&land);
602         if (roundrange(erange) < ni.curdist)
603             continue;
604
605         fp = search_flist(list, (struct empobj *)&land);
606         if (fp)
607             dam2 = fp->defdam;
608         else {
609             dam2 = lnd_fire(&land);
610             putland(land.lnd_uid, &land);
611         }
612         if (dam2 < 0)
613             continue;
614
615         (*nfiring)++;
616         if (!fp)
617             add_to_flist(list, (struct empobj *)&land, dam2, 0);
618         nreport(land.lnd_own, N_FIRE_BACK, player->cnum, 1);
619         if (type == EF_SHIP) {
620             if (chance(lnd_acc(&land) / 100.0))
621                 dam2 = ldround(dam2 / 2.0, 1);
622         }
623         dam += dam2;
624     }
625
626     /*
627      * Determine if any nearby gun-equipped sectors are within
628      * range and able to fire at an attacker.  Firing sectors
629      * need to have guns, shells, and military.  Sector being
630      * attacked is x,y -- attacker is at ax,ay.
631      */
632
633     if (!opt_NO_FORT_FIRE) {
634         snxtsct_dist(&ns, ax, ay, 8);
635         while (nxtsct(&ns, &firing)) {
636             if (!feels_like_helping(firing.sct_own, own, aown))
637                 continue;
638
639             erange = fortrange(&firing);
640             if (roundrange(erange) < ns.curdist)
641                 continue;
642
643             fp = search_flist(list, (struct empobj *)&firing);
644             if (fp)
645                 dam2 = fp->defdam;
646             else {
647                 dam2 = fort_fire(&firing);
648                 putsect(&firing);
649             }
650             if (dam2 < 0)
651                 continue;
652             (*nfiring)++;
653             if (!fp)
654                 add_to_flist(list, (struct empobj *)&firing, dam2, 0);
655             nreport(firing.sct_own, N_FIRE_BACK, player->cnum, 1);
656             dam += dam2;
657         }
658     }
659
660     return *nfiring == 0 ? 0 : dam / *nfiring;
661 }
662
663 static void
664 add_to_flist(struct emp_qelem *list,
665              struct empobj *gp, int dam, natid victim)
666 {
667     struct flist *fp;
668
669     fp = malloc(sizeof(struct flist));
670     fp->type = gp->ef_type;
671     fp->uid = gp->uid;
672     fp->x = gp->x;
673     fp->y = gp->y;
674     fp->defdam = dam;
675     fp->victim = victim;
676     emp_insque(&fp->queue, list);
677 }
678
679 static void
680 free_flist(struct emp_qelem *list)
681 {
682     struct emp_qelem *qp, *next;
683     struct flist *fp;
684
685     for (qp = list->q_forw; qp != list; qp = next) {
686         next = qp->q_forw;
687         fp = (struct flist *)qp;
688         emp_remque(&fp->queue);
689         free(fp);
690     }
691 }
692
693 static int
694 uid_eq(struct emp_qelem *elem, void *key)
695 {
696     return ((struct flist *)elem)->uid == ((struct empobj *)key)->uid;
697 }
698
699 static struct flist *
700 search_flist(struct emp_qelem *list, struct empobj *gp)
701 {
702     return (struct flist *)emp_searchque(list, gp, uid_eq);
703 }