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