]> git.pond.sub.org Git - empserver/blob - src/lib/commands/mfir.c
Update copyright notice
[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-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                 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     return RET_OK;
431 }
432
433 static int
434 defend(struct emp_qelem *al, struct emp_qelem *dl,
435        struct empobj *attgp, natid vict, int *nd)
436 {
437     int dam;
438     int nfiring = 0;
439
440     dam = quiet_bigdef(attgp->ef_type, dl, vict,
441                        attgp->own, attgp->x, attgp->y, &nfiring);
442     if (dam) {
443         if (nfiring > *nd)
444             *nd = nfiring;
445         add_to_flist(al, attgp, dam, vict);
446     }
447
448     return dam;
449 }
450
451 static void
452 do_defdam(struct emp_qelem *list, double odds)
453 {
454
455     int dam, first = 1;
456     natid vict;
457     struct flist *fp;
458     struct shpstr ship;
459     struct sctstr sect;
460     struct emp_qelem *qp, *next;
461
462     for (qp = list->q_forw; qp != list; qp = next) {
463         next = qp->q_forw;
464         fp = (struct flist *)qp;
465         if (fp->type == EF_SHIP) {
466             if (!getship(fp->uid, &ship) || !ship.shp_own)
467                 continue;
468         }
469         if (first) {
470             pr_beep();
471             pr("\nDefenders fire back!\n");
472             first = 0;
473         }
474         dam = odds * fp->defdam;
475
476         if (fp->type == EF_SHIP) {
477             vict = fp->victim;
478             pr("Return fire hit %s in %s for %d damage.\n",
479                prship(&ship),
480                xyas(ship.shp_x, ship.shp_y, player->cnum), dam);
481             if (vict)
482                 wu(0, vict,
483                    "Return fire hit %s in %s for %d damage.\n",
484                    prship(&ship), xyas(ship.shp_x, ship.shp_y, vict), dam);
485             shipdamage(&ship, dam);
486             putship(ship.shp_uid, &ship);
487         } else {
488             CANT_HAPPEN(fp->type != EF_SECTOR);
489             getsect(fp->x, fp->y, &sect);
490             vict = fp->victim;
491             pr("Return fire hit sector %s for %d damage.\n",
492                xyas(fp->x, fp->y, player->cnum), dam);
493             sectdamage(&sect, dam);
494             putsect(&sect);
495             if (vict)
496                 wu(0, vict, "Return fire hit sector %s for %d damage.\n",
497                    xyas(fp->x, fp->y, vict), dam);
498         }
499         emp_remque(&fp->queue);
500         free(fp);
501     }
502 }
503
504 static int
505 quiet_bigdef(int type, struct emp_qelem *list, natid own, natid aown,
506              coord ax, coord ay, int *nfiring)
507 {
508     double erange;
509     struct shpstr ship;
510     struct lndstr land;
511     struct nstr_item ni;
512     int dam, dam2;
513     struct sctstr firing;
514     struct nstr_sect ns;
515     struct flist *fp;
516
517     if (own == 0)
518         return 0;
519     dam = 0;
520     snxtitem_dist(&ni, EF_SHIP, ax, ay, 8);
521     while (nxtitem(&ni, &ship)) {
522         if (!feels_like_helping(ship.shp_own, own, aown))
523             continue;
524
525         if ((mchr[ship.shp_type].m_flags & M_SUB) && type != EF_SHIP)
526             continue;
527
528         if (mchr[(int)ship.shp_type].m_flags & M_SUB) {
529             erange = torprange(&ship);
530             if (roundrange(erange) < ni.curdist)
531                 continue;
532             if (!line_of_sight(NULL, ship.shp_x, ship.shp_y, ax, ay))
533                 continue;
534             fp = search_flist(list, (struct empobj *)&ship);
535             if (fp)
536                 dam2 = fp->defdam;
537             else {
538                 dam2 = shp_torp(&ship, 0);
539                 putship(ship.shp_uid, &ship);
540             }
541             if (dam2 < 0)
542                 continue;
543             if (!chance(shp_torp_hitchance(&ship, ni.curdist)))
544                 dam2 = 0;
545         } else {
546             erange = shp_fire_range(&ship);
547             if (roundrange(erange) < ni.curdist)
548                 continue;
549             fp = search_flist(list, (struct empobj *)&ship);
550             if (fp)
551                 dam2 = fp->defdam;
552             else {
553                 dam2 = shp_fire(&ship);
554                 putship(ship.shp_uid, &ship);
555             }
556             if (dam2 < 0)
557                 continue;
558             nreport(ship.shp_own, N_FIRE_BACK, player->cnum, 1);
559         }
560         (*nfiring)++;
561         if (!fp)
562             add_to_flist(list, (struct empobj *)&ship, dam2, 0);
563         dam += dam2;
564     }
565     snxtitem_dist(&ni, EF_LAND, ax, ay, 8);
566     while (nxtitem(&ni, &land)) {
567         if (!feels_like_helping(land.lnd_own, own, aown))
568             continue;
569
570         erange = lnd_fire_range(&land);
571         if (roundrange(erange) < ni.curdist)
572             continue;
573
574         fp = search_flist(list, (struct empobj *)&land);
575         if (fp)
576             dam2 = fp->defdam;
577         else {
578             dam2 = lnd_fire(&land);
579             putland(land.lnd_uid, &land);
580         }
581         if (dam2 < 0)
582             continue;
583
584         (*nfiring)++;
585         if (!fp)
586             add_to_flist(list, (struct empobj *)&land, dam2, 0);
587         nreport(land.lnd_own, N_FIRE_BACK, player->cnum, 1);
588         if (type == EF_SHIP) {
589             if (chance(lnd_acc(&land) / 100.0))
590                 dam2 = ldround(dam2 / 2.0, 1);
591         }
592         dam += dam2;
593     }
594
595     /*
596      * Determine if any nearby gun-equipped sectors are within
597      * range and able to fire at an attacker.  Firing sectors
598      * need to have guns, shells, and military.  Sector being
599      * attacked is x,y -- attacker is at ax,ay.
600      */
601
602     if (!opt_NO_FORT_FIRE) {
603         snxtsct_dist(&ns, ax, ay, 8);
604         while (nxtsct(&ns, &firing)) {
605             if (!feels_like_helping(firing.sct_own, own, aown))
606                 continue;
607
608             erange = fortrange(&firing);
609             if (roundrange(erange) < ns.curdist)
610                 continue;
611
612             fp = search_flist(list, (struct empobj *)&firing);
613             if (fp)
614                 dam2 = fp->defdam;
615             else {
616                 dam2 = fort_fire(&firing);
617                 putsect(&firing);
618             }
619             if (dam2 < 0)
620                 continue;
621             (*nfiring)++;
622             if (!fp)
623                 add_to_flist(list, (struct empobj *)&firing, dam2, 0);
624             nreport(firing.sct_own, N_FIRE_BACK, player->cnum, 1);
625             dam += dam2;
626         }
627     }
628
629     return *nfiring == 0 ? 0 : dam / *nfiring;
630 }
631
632 static void
633 add_to_flist(struct emp_qelem *list,
634              struct empobj *gp, int dam, natid victim)
635 {
636     struct flist *fp;
637
638     fp = malloc(sizeof(struct flist));
639     fp->type = gp->ef_type;
640     fp->uid = gp->uid;
641     fp->x = gp->x;
642     fp->y = gp->y;
643     fp->defdam = dam;
644     fp->victim = victim;
645     emp_insque(&fp->queue, list);
646 }
647
648 static void
649 free_flist(struct emp_qelem *list)
650 {
651     struct emp_qelem *qp, *next;
652     struct flist *fp;
653
654     for (qp = list->q_forw; qp != list; qp = next) {
655         next = qp->q_forw;
656         fp = (struct flist *)qp;
657         emp_remque(&fp->queue);
658         free(fp);
659     }
660 }
661
662 static int
663 uid_eq(struct emp_qelem *elem, void *key)
664 {
665     return ((struct flist *)elem)->uid == ((struct empobj *)key)->uid;
666 }
667
668 static struct flist *
669 search_flist(struct emp_qelem *list, struct empobj *gp)
670 {
671     return (struct flist *)emp_searchque(list, gp, uid_eq);
672 }