]> git.pond.sub.org Git - empserver/blob - src/lib/commands/mfir.c
Update copyright notice
[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-2014
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             check_retreat_and_do_shipdamage(&vship, dam);
427             if (vship.shp_effic < SHIP_MINEFF)
428                 pr("%s sunk!\n", prsub(&vship));
429             else if (target == targ_sub
430                 && (vship.shp_rflags & RET_DCHRGED)
431                 && !(vship.shp_rflags & RET_INJURED))
432                 retreat_ship(&vship, 'd');
433             else if (totaldefdam == 0
434                      && (vship.shp_rflags & RET_HELPLESS)
435                      && !(vship.shp_rflags & RET_INJURED))
436                 retreat_ship(&vship, 'h');
437             putship(vship.shp_uid, &vship);
438             break;
439         }
440         switch (attgp->ef_type) {
441         case EF_SECTOR:
442             putsect(&fsect);
443             break;
444         case EF_SHIP:
445             if ((target == targ_ship) || (target == targ_sub)) {
446                 if (fship.shp_effic > SHIP_MINEFF) {
447                     shp_missdef(&fship, vict);
448                 }
449             }
450             putship(fship.shp_uid, &fship);
451             break;
452         default:
453             CANT_REACH();
454         }
455     }
456
457     free_flist(&defended);
458     if (nfiring)
459         odds = ((double)ndefending) / ((double)nfiring);
460     else
461         odds = 1.0;
462     do_defdam(&fired, odds);
463     return RET_OK;
464 }
465
466 static int
467 defend(struct emp_qelem *al, struct emp_qelem *dl,
468        struct empobj *attgp, natid vict, int *nd)
469 {
470     int dam;
471     int nfiring = 0;
472
473     dam = quiet_bigdef(attgp->ef_type, dl, vict,
474                        attgp->own, attgp->x, attgp->y, &nfiring);
475     if (dam) {
476         if (nfiring > *nd)
477             *nd = nfiring;
478         add_to_flist(al, attgp, dam, vict);
479     }
480
481     return dam;
482 }
483
484 static void
485 do_defdam(struct emp_qelem *list, double odds)
486 {
487
488     int dam, first = 1;
489     natid vict;
490     struct flist *fp;
491     struct shpstr ship;
492     struct sctstr sect;
493     struct emp_qelem *qp, *next;
494
495     for (qp = list->q_forw; qp != list; qp = next) {
496         next = qp->q_forw;
497         fp = (struct flist *)qp;
498         if (fp->type == EF_SHIP) {
499             if (!getship(fp->uid, &ship) || !ship.shp_own)
500                 continue;
501         }
502         if (first) {
503             pr_beep();
504             pr("\nDefenders fire back!\n");
505             first = 0;
506         }
507         dam = odds * fp->defdam;
508
509         if (fp->type == EF_SHIP) {
510             vict = fp->victim;
511             pr("Return fire hit %s in %s for %d damage.\n",
512                prship(&ship),
513                xyas(ship.shp_x, ship.shp_y, player->cnum), dam);
514             if (vict)
515                 wu(0, vict,
516                    "Return fire hit %s in %s for %d damage.\n",
517                    prsub(&ship), xyas(ship.shp_x, ship.shp_y, vict), dam);
518             shipdamage(&ship, dam);
519             putship(ship.shp_uid, &ship);
520         } else {
521             CANT_HAPPEN(fp->type != EF_SECTOR);
522             getsect(fp->x, fp->y, &sect);
523             vict = fp->victim;
524             pr("Return fire hit sector %s for %d damage.\n",
525                xyas(fp->x, fp->y, player->cnum), dam);
526             sectdamage(&sect, dam);
527             putsect(&sect);
528             if (vict)
529                 wu(0, vict, "Return fire hit sector %s for %d damage.\n",
530                    xyas(fp->x, fp->y, vict), dam);
531         }
532         emp_remque(&fp->queue);
533         free(fp);
534     }
535 }
536
537 static int
538 quiet_bigdef(int type, struct emp_qelem *list, natid own, natid aown,
539              coord ax, coord ay, int *nfiring)
540 {
541     double erange;
542     struct shpstr ship;
543     struct lndstr land;
544     struct nstr_item ni;
545     int dam, dam2;
546     struct sctstr firing;
547     struct nstr_sect ns;
548     struct flist *fp;
549
550     if (own == 0)
551         return 0;
552     dam = 0;
553     snxtitem_dist(&ni, EF_SHIP, ax, ay, 8);
554     while (nxtitem(&ni, &ship)) {
555         if (!feels_like_helping(ship.shp_own, own, aown))
556             continue;
557
558         if ((mchr[ship.shp_type].m_flags & M_SUB) && type != EF_SHIP)
559             continue;
560
561         if (mchr[(int)ship.shp_type].m_flags & M_SUB) {
562             erange = torprange(&ship);
563             if (roundrange(erange) < ni.curdist)
564                 continue;
565             if (!line_of_sight(NULL, ship.shp_x, ship.shp_y, ax, ay))
566                 continue;
567             fp = search_flist(list, (struct empobj *)&ship);
568             if (fp)
569                 dam2 = fp->defdam;
570             else {
571                 dam2 = shp_torp(&ship, 0);
572                 putship(ship.shp_uid, &ship);
573             }
574             if (dam2 < 0)
575                 continue;
576             if (!chance(shp_torp_hitchance(&ship, ni.curdist)))
577                 dam2 = 0;
578         } else {
579             erange = shp_fire_range(&ship);
580             if (roundrange(erange) < ni.curdist)
581                 continue;
582             fp = search_flist(list, (struct empobj *)&ship);
583             if (fp)
584                 dam2 = fp->defdam;
585             else {
586                 dam2 = shp_fire(&ship);
587                 putship(ship.shp_uid, &ship);
588             }
589             if (dam2 < 0)
590                 continue;
591             nreport(ship.shp_own, N_FIRE_BACK, player->cnum, 1);
592         }
593         (*nfiring)++;
594         if (!fp)
595             add_to_flist(list, (struct empobj *)&ship, dam2, 0);
596         dam += dam2;
597     }
598     snxtitem_dist(&ni, EF_LAND, ax, ay, 8);
599     while (nxtitem(&ni, &land)) {
600         if (!feels_like_helping(land.lnd_own, own, aown))
601             continue;
602
603         erange = lnd_fire_range(&land);
604         if (roundrange(erange) < ni.curdist)
605             continue;
606
607         fp = search_flist(list, (struct empobj *)&land);
608         if (fp)
609             dam2 = fp->defdam;
610         else {
611             dam2 = lnd_fire(&land);
612             putland(land.lnd_uid, &land);
613         }
614         if (dam2 < 0)
615             continue;
616
617         (*nfiring)++;
618         if (!fp)
619             add_to_flist(list, (struct empobj *)&land, dam2, 0);
620         nreport(land.lnd_own, N_FIRE_BACK, player->cnum, 1);
621         if (type == EF_SHIP) {
622             if (chance(lnd_acc(&land) / 100.0))
623                 dam2 = ldround(dam2 / 2.0, 1);
624         }
625         dam += dam2;
626     }
627
628     /*
629      * Determine if any nearby gun-equipped sectors are within
630      * range and able to fire at an attacker.  Firing sectors
631      * need to have guns, shells, and military.  Sector being
632      * attacked is x,y -- attacker is at ax,ay.
633      */
634
635     if (!opt_NO_FORT_FIRE) {
636         snxtsct_dist(&ns, ax, ay, 8);
637         while (nxtsct(&ns, &firing)) {
638             if (!feels_like_helping(firing.sct_own, own, aown))
639                 continue;
640
641             erange = fortrange(&firing);
642             if (roundrange(erange) < ns.curdist)
643                 continue;
644
645             fp = search_flist(list, (struct empobj *)&firing);
646             if (fp)
647                 dam2 = fp->defdam;
648             else {
649                 dam2 = fort_fire(&firing);
650                 putsect(&firing);
651             }
652             if (dam2 < 0)
653                 continue;
654             (*nfiring)++;
655             if (!fp)
656                 add_to_flist(list, (struct empobj *)&firing, dam2, 0);
657             nreport(firing.sct_own, N_FIRE_BACK, player->cnum, 1);
658             dam += dam2;
659         }
660     }
661
662     return *nfiring == 0 ? 0 : dam / *nfiring;
663 }
664
665 static void
666 add_to_flist(struct emp_qelem *list,
667              struct empobj *gp, int dam, natid victim)
668 {
669     struct flist *fp;
670
671     fp = malloc(sizeof(struct flist));
672     fp->type = gp->ef_type;
673     fp->uid = gp->uid;
674     fp->x = gp->x;
675     fp->y = gp->y;
676     fp->defdam = dam;
677     fp->victim = victim;
678     emp_insque(&fp->queue, list);
679 }
680
681 static void
682 free_flist(struct emp_qelem *list)
683 {
684     struct emp_qelem *qp, *next;
685     struct flist *fp;
686
687     for (qp = list->q_forw; qp != list; qp = next) {
688         next = qp->q_forw;
689         fp = (struct flist *)qp;
690         emp_remque(&fp->queue);
691         free(fp);
692     }
693 }
694
695 static int
696 uid_eq(struct emp_qelem *elem, void *key)
697 {
698     return ((struct flist *)elem)->uid == ((struct empobj *)key)->uid;
699 }
700
701 static struct flist *
702 search_flist(struct emp_qelem *list, struct empobj *gp)
703 {
704     return (struct flist *)emp_searchque(list, gp, uid_eq);
705 }