]> git.pond.sub.org Git - empserver/blob - src/lib/commands/mfir.c
fire: Always clear mission when firing
[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             fship.shp_mission = 0;
290             putship(fship.shp_uid, &fship);
291             if (CANT_HAPPEN(dam < 0)) {
292                 pr("Klick!     ...\n");
293                 continue;
294             }
295             if (opt_NOMOBCOST == 0) {
296                 fship.shp_mobil = MAX(fship.shp_mobil - 15, -100);
297                 putship(fship.shp_uid, &fship);
298             }
299         } else if (type == EF_LAND) {
300             if (!check_land_ok(&fland))
301                 return RET_FAIL;
302             if (target == targ_land) {
303                 if (fland.lnd_x == vsect.sct_x
304                     && fland.lnd_y == vsect.sct_y) {
305                     pr("You can't fire upon yourself!\n");
306                     continue;
307                 }
308             }
309
310             range = lnd_fire_range(&fland);
311             range2 = roundrange(range);
312             pr("range is %d.00 (%.2f)\n", range2, range);
313             if (target == targ_sub) {
314                 /* Don't tell it's a sub */
315                 range2 = -1;
316             }
317
318             dam = lnd_fire(&fland);
319             fland.lnd_mission = 0;
320             putland(fland.lnd_uid, &fland);
321             if (CANT_HAPPEN(dam < 0)) {
322                 pr("Klick!     ...\n");
323                 continue;
324             }
325             if (target == targ_ship) {
326                 if (chance(lnd_acc(&fland) / 100.0))
327                     dam = ldround(dam / 2.0, 1);
328             }
329         } else {
330             if (!check_sect_ok(&fsect))
331                 return RET_FAIL;
332             if (target == targ_land) {
333                 if (fsect.sct_x == vsect.sct_x
334                     && fsect.sct_y == vsect.sct_y) {
335                     pr("You can't fire upon yourself!\n");
336                     continue;
337                 }
338             }
339             dam = fort_fire(&fsect);
340             putsect(&fsect);
341             if (CANT_HAPPEN(dam < 0)) {
342                 pr("Klick!     ...\n");
343                 continue;
344             }
345             range = fortrange(&fsect);
346             range2 = roundrange(range);
347             pr("range is %d.00 (%.2f)\n", range2, range);
348             if (target == targ_sub) {
349                 /* Don't tell it's a sub */
350                 range2 = -1;
351             }
352         }
353         if (trange > range2) {
354             pr("Target out of range.\n");
355             continue;
356         }
357
358         nfiring++;
359         switch (target) {
360         case targ_sub:
361             pr_beep();
362             pr("Kawhomp!!!\n");
363             break;
364         default:
365             pr_beep();
366             pr("Kaboom!!!\n");
367             break;
368         }
369         switch (target) {
370         case targ_bogus:
371         case targ_land:
372             nreport(player->cnum, N_SCT_SHELL, vict, 1);
373             if (vict && vict != player->cnum)
374                 wu(0, vict,
375                    "Country #%d shelled sector %s for %d damage.\n",
376                    player->cnum, xyas(x, y, vict), dam);
377             pr("Shells hit sector %s for %d damage.\n",
378                xyas(x, y, player->cnum), dam);
379             break;
380         case targ_ship:
381             nreport(player->cnum, N_SHP_SHELL, vict, 1);
382             /* fall through */
383         default:
384             if (vict && vict != player->cnum) {
385                 wu(0, vict,
386                    "Country #%d shelled %s in %s for %d damage.\n",
387                    player->cnum, prship(&vship),
388                    xyas(vship.shp_x, vship.shp_y, vict), dam);
389             }
390             pr("Shells hit %s in %s for %d damage.\n",
391                prsub(&vship),
392                xyas(vship.shp_x, vship.shp_y, player->cnum), dam);
393             break;
394         }
395         /*  Ok, now, check if we had a bogus target.  If so,
396            just continue on, since there is no defender. */
397         if (target == targ_bogus)
398             continue;
399         attgp = &item.gen;
400         if (type == EF_LAND) {
401             getsect(fland.lnd_x, fland.lnd_y, &fsect);
402             attgp = (struct empobj *)&fsect;
403         }
404         totaldefdam = defend(&fired, &defended, attgp, vict, &ndefending);
405         switch (target) {
406         case targ_land:
407             getsect(x, y, &vsect);
408             sectdamage(&vsect, dam);
409             putsect(&vsect);
410             break;
411         default:
412             getship(vshipno, &vship);
413             shipdamage(&vship, dam);
414             if (vship.shp_effic < SHIP_MINEFF)
415                 pr("%s sunk!\n", prsub(&vship));
416             putship(vship.shp_uid, &vship);
417             if (dam && (vship.shp_rflags & RET_INJURED))
418                 retreat_ship(&vship, vict, 'i');
419             else if (target == targ_sub && (vship.shp_rflags & RET_DCHRGED))
420                 retreat_ship(&vship, vict, 'd');
421             else if (totaldefdam == 0 && (vship.shp_rflags & RET_HELPLESS))
422                 retreat_ship(&vship, vict, 'h');
423             break;
424         }
425         switch (attgp->ef_type) {
426         case EF_SECTOR:
427             break;
428         case EF_SHIP:
429             if ((target == targ_ship) || (target == targ_sub)) {
430                 if (fship.shp_effic > SHIP_MINEFF) {
431                     shp_missdef(&fship, vict);
432                 }
433             }
434             break;
435         default:
436             CANT_REACH();
437         }
438     }
439
440     free_flist(&defended);
441     if (nfiring)
442         odds = ((double)ndefending) / ((double)nfiring);
443     else
444         odds = 1.0;
445     do_defdam(&fired, odds);
446     return RET_OK;
447 }
448
449 static int
450 defend(struct emp_qelem *al, struct emp_qelem *dl,
451        struct empobj *attgp, natid vict, int *nd)
452 {
453     int dam;
454     int nfiring = 0;
455
456     dam = quiet_bigdef(attgp->ef_type, dl, vict,
457                        attgp->own, attgp->x, attgp->y, &nfiring);
458     if (dam) {
459         if (nfiring > *nd)
460             *nd = nfiring;
461         add_to_flist(al, attgp, dam, vict);
462     }
463
464     return dam;
465 }
466
467 static void
468 do_defdam(struct emp_qelem *list, double odds)
469 {
470
471     int dam, first = 1;
472     natid vict;
473     struct flist *fp;
474     struct shpstr ship;
475     struct sctstr sect;
476     struct emp_qelem *qp, *next;
477
478     for (qp = list->q_forw; qp != list; qp = next) {
479         next = qp->q_forw;
480         fp = (struct flist *)qp;
481         if (fp->type == EF_SHIP) {
482             if (!getship(fp->uid, &ship) || !ship.shp_own)
483                 continue;
484         }
485         if (first) {
486             pr_beep();
487             pr("\nDefenders fire back!\n");
488             first = 0;
489         }
490         dam = odds * fp->defdam;
491
492         if (fp->type == EF_SHIP) {
493             vict = fp->victim;
494             pr("Return fire hit %s in %s for %d damage.\n",
495                prship(&ship),
496                xyas(ship.shp_x, ship.shp_y, player->cnum), dam);
497             if (vict)
498                 wu(0, vict,
499                    "Return fire hit %s in %s for %d damage.\n",
500                    prsub(&ship), xyas(ship.shp_x, ship.shp_y, vict), dam);
501             shipdamage(&ship, dam);
502             putship(ship.shp_uid, &ship);
503         } else {
504             CANT_HAPPEN(fp->type != EF_SECTOR);
505             getsect(fp->x, fp->y, &sect);
506             vict = fp->victim;
507             pr("Return fire hit sector %s for %d damage.\n",
508                xyas(fp->x, fp->y, player->cnum), dam);
509             sectdamage(&sect, dam);
510             putsect(&sect);
511             if (vict)
512                 wu(0, vict, "Return fire hit sector %s for %d damage.\n",
513                    xyas(fp->x, fp->y, vict), dam);
514         }
515         emp_remque(&fp->queue);
516         free(fp);
517     }
518 }
519
520 static int
521 quiet_bigdef(int type, struct emp_qelem *list, natid own, natid aown,
522              coord ax, coord ay, int *nfiring)
523 {
524     double erange;
525     struct shpstr ship;
526     struct lndstr land;
527     struct nstr_item ni;
528     int dam, dam2;
529     struct sctstr firing;
530     struct nstr_sect ns;
531     struct flist *fp;
532
533     if (own == 0)
534         return 0;
535     dam = 0;
536     snxtitem_dist(&ni, EF_SHIP, ax, ay, 8);
537     while (nxtitem(&ni, &ship)) {
538         if (!feels_like_helping(ship.shp_own, own, aown))
539             continue;
540
541         if ((mchr[ship.shp_type].m_flags & M_SUB) && type != EF_SHIP)
542             continue;
543
544         if (mchr[(int)ship.shp_type].m_flags & M_SUB) {
545             erange = torprange(&ship);
546             if (roundrange(erange) < ni.curdist)
547                 continue;
548             if (!line_of_sight(NULL, ship.shp_x, ship.shp_y, ax, ay))
549                 continue;
550             fp = search_flist(list, (struct empobj *)&ship);
551             if (fp)
552                 dam2 = fp->defdam;
553             else {
554                 dam2 = shp_torp(&ship, 0);
555                 putship(ship.shp_uid, &ship);
556             }
557             if (dam2 < 0)
558                 continue;
559             if (!chance(shp_torp_hitchance(&ship, ni.curdist)))
560                 dam2 = 0;
561         } else {
562             erange = shp_fire_range(&ship);
563             if (roundrange(erange) < ni.curdist)
564                 continue;
565             fp = search_flist(list, (struct empobj *)&ship);
566             if (fp)
567                 dam2 = fp->defdam;
568             else {
569                 dam2 = shp_fire(&ship);
570                 putship(ship.shp_uid, &ship);
571             }
572             if (dam2 < 0)
573                 continue;
574             nreport(ship.shp_own, N_FIRE_BACK, player->cnum, 1);
575         }
576         (*nfiring)++;
577         if (!fp)
578             add_to_flist(list, (struct empobj *)&ship, dam2, 0);
579         dam += dam2;
580     }
581     snxtitem_dist(&ni, EF_LAND, ax, ay, 8);
582     while (nxtitem(&ni, &land)) {
583         if (!feels_like_helping(land.lnd_own, own, aown))
584             continue;
585
586         erange = lnd_fire_range(&land);
587         if (roundrange(erange) < ni.curdist)
588             continue;
589
590         fp = search_flist(list, (struct empobj *)&land);
591         if (fp)
592             dam2 = fp->defdam;
593         else {
594             dam2 = lnd_fire(&land);
595             putland(land.lnd_uid, &land);
596         }
597         if (dam2 < 0)
598             continue;
599
600         (*nfiring)++;
601         if (!fp)
602             add_to_flist(list, (struct empobj *)&land, dam2, 0);
603         nreport(land.lnd_own, N_FIRE_BACK, player->cnum, 1);
604         if (type == EF_SHIP) {
605             if (chance(lnd_acc(&land) / 100.0))
606                 dam2 = ldround(dam2 / 2.0, 1);
607         }
608         dam += dam2;
609     }
610
611     /*
612      * Determine if any nearby gun-equipped sectors are within
613      * range and able to fire at an attacker.  Firing sectors
614      * need to have guns, shells, and military.  Sector being
615      * attacked is x,y -- attacker is at ax,ay.
616      */
617
618     if (!opt_NO_FORT_FIRE) {
619         snxtsct_dist(&ns, ax, ay, 8);
620         while (nxtsct(&ns, &firing)) {
621             if (!feels_like_helping(firing.sct_own, own, aown))
622                 continue;
623
624             erange = fortrange(&firing);
625             if (roundrange(erange) < ns.curdist)
626                 continue;
627
628             fp = search_flist(list, (struct empobj *)&firing);
629             if (fp)
630                 dam2 = fp->defdam;
631             else {
632                 dam2 = fort_fire(&firing);
633                 putsect(&firing);
634             }
635             if (dam2 < 0)
636                 continue;
637             (*nfiring)++;
638             if (!fp)
639                 add_to_flist(list, (struct empobj *)&firing, dam2, 0);
640             nreport(firing.sct_own, N_FIRE_BACK, player->cnum, 1);
641             dam += dam2;
642         }
643     }
644
645     return *nfiring == 0 ? 0 : dam / *nfiring;
646 }
647
648 static void
649 add_to_flist(struct emp_qelem *list,
650              struct empobj *gp, int dam, natid victim)
651 {
652     struct flist *fp;
653
654     fp = malloc(sizeof(struct flist));
655     fp->type = gp->ef_type;
656     fp->uid = gp->uid;
657     fp->x = gp->x;
658     fp->y = gp->y;
659     fp->defdam = dam;
660     fp->victim = victim;
661     emp_insque(&fp->queue, list);
662 }
663
664 static void
665 free_flist(struct emp_qelem *list)
666 {
667     struct emp_qelem *qp, *next;
668     struct flist *fp;
669
670     for (qp = list->q_forw; qp != list; qp = next) {
671         next = qp->q_forw;
672         fp = (struct flist *)qp;
673         emp_remque(&fp->queue);
674         free(fp);
675     }
676 }
677
678 static int
679 uid_eq(struct emp_qelem *elem, void *key)
680 {
681     return ((struct flist *)elem)->uid == ((struct empobj *)key)->uid;
682 }
683
684 static struct flist *
685 search_flist(struct emp_qelem *list, struct empobj *gp)
686 {
687     return (struct flist *)emp_searchque(list, gp, uid_eq);
688 }