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