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