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