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