ef7c9cea2ce5f5a02118265c3e49c56dfb3240d1
[empserver] / src / lib / subs / unitsub.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2021, 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  *  unitsub.c: Common subroutines for multiple type of units
28  *
29  *  Known contributors to this file:
30  *     Ron Koenderink, 2007
31  *     Markus Armbruster, 2009-2015
32  */
33
34 #include <config.h>
35
36 #include <math.h>
37 #include "map.h"
38 #include "optlist.h"
39 #include "path.h"
40 #include "player.h"
41 #include "prototypes.h"
42 #include "unit.h"
43
44 char *
45 unit_nameof(struct empobj *gp)
46 {
47     switch (gp->ef_type) {
48     case EF_SHIP:
49         return prship((struct shpstr *)gp);
50     case EF_PLANE:
51         return prplane((struct plnstr *)gp);
52     case EF_LAND:
53         return prland((struct lndstr *)gp);
54     case EF_NUKE:
55         return prnuke((struct nukstr *)gp);
56     }
57     CANT_REACH();
58     return "The Beast #666";
59 }
60
61 static void
62 unit_list(struct emp_qelem *unit_list)
63 {
64     struct emp_qelem *qp;
65     struct emp_qelem *next;
66     struct ulist *ulp;
67     int type, npln, nch, nxl;
68     struct empobj *unit;
69     struct lndstr *lnd;
70     struct shpstr *shp;
71
72     if (CANT_HAPPEN(QEMPTY(unit_list)))
73         return;
74     qp = unit_list->q_back;
75     ulp = (struct ulist *)qp;
76     type = ulp->unit.gen.ef_type;
77     if (CANT_HAPPEN(type != EF_LAND && type != EF_SHIP))
78         return;
79
80     if (type == EF_LAND)
81         pr("lnd#     land type       x,y    a  eff mil  sh gun xl ln  mu tech retr\n");
82     else
83         pr("shp#     ship type       x,y   fl  eff mil  sh gun pn he xl ln mob tech\n");
84
85     for (; qp != unit_list; qp = next) {
86         next = qp->q_back;
87         ulp = (struct ulist *)qp;
88         lnd = &ulp->unit.land;
89         shp = &ulp->unit.ship;
90         unit = &ulp->unit.gen;
91         if (CANT_HAPPEN(type != unit->ef_type))
92             continue;
93         pr("%4d ", unit->uid);
94         pr("%-16.16s ", empobj_chr_name(unit));
95         prxy("%4d,%-4d ", unit->x, unit->y);
96         pr("%1.1s", &unit->group);
97         pr("%4d%%", unit->effic);
98         if (type == EF_LAND) {
99             pr("%4d", lnd->lnd_item[I_MILIT]);
100             pr("%4d", lnd->lnd_item[I_SHELL]);
101             pr("%4d", lnd->lnd_item[I_GUN]);
102             pr("%3d%3d", lnd_nxlight(lnd), lnd_nland(lnd));
103         } else {
104             pr("%4d", shp->shp_item[I_MILIT]);
105             pr("%4d", shp->shp_item[I_SHELL]);
106             pr("%4d", shp->shp_item[I_GUN]);
107             npln = shp_nplane(shp, &nch, &nxl, NULL);
108             pr("%3d%3d%3d", npln - nch - nxl, nch, nxl);
109             pr("%3d", shp_nland(shp));
110         }
111         pr("%4d", unit->mobil);
112         pr("%4d", unit->tech);
113         if (type == EF_LAND) {
114             pr("%4d%%", lnd->lnd_retreat);
115         }
116         pr("\n");
117     }
118 }
119
120 static void
121 unit_view(struct emp_qelem *list)
122 {
123     struct sctstr sect;
124     struct emp_qelem *qp;
125     struct emp_qelem *next;
126     struct ulist *ulp;
127
128     for (qp = list->q_back; qp != list; qp = next) {
129         next = qp->q_back;
130         ulp = (struct ulist *)qp;
131         if (CANT_HAPPEN(!(ef_flags(ulp->unit.gen.ef_type) & EFF_XY)))
132             continue;
133         getsect(ulp->unit.gen.x, ulp->unit.gen.y, &sect);
134         if (ulp->unit.gen.ef_type == EF_SHIP) {
135             if (mchr[ulp->unit.ship.shp_type].m_flags & M_FOOD)
136                 pr("[fert:%d] ", sect.sct_fertil);
137             if (mchr[ulp->unit.ship.shp_type].m_flags & M_OIL)
138                 pr("[oil:%d] ", sect.sct_oil);
139         }
140         pr("%s @ %s %d%% %s\n", unit_nameof(&ulp->unit.gen),
141            xyas(ulp->unit.gen.x, ulp->unit.gen.y, player->cnum),
142            sect.sct_effic, dchr[sect.sct_type].d_name);
143     }
144 }
145
146 void
147 unit_rad_map_set(struct emp_qelem *list)
148 {
149     struct emp_qelem *qp;
150     struct empobj *unit;
151
152     for (qp = list->q_back; qp != list; qp = qp->q_back) {
153         unit = &((struct ulist *)qp)->unit.gen;
154         rad_map_set(unit->own, unit->x, unit->y, unit->effic, unit->tech,
155                     unit->ef_type == EF_SHIP
156                     ? mchr[unit->type].m_vrnge : lchr[unit->type].l_spy);
157     }
158 }
159
160 static struct empobj *
161 get_leader(struct emp_qelem *list)
162 {
163     return &((struct ulist *)(list->q_back))->unit.gen;
164 }
165
166 static void
167 switch_leader(struct emp_qelem *list, char *arg)
168 {
169     int uid = arg ? atoi(arg) : -1;
170
171     struct emp_qelem *qp, *save;
172     struct ulist *ulp;
173
174     if (QEMPTY(list))
175         return;
176
177     save = qp = list->q_back;
178     do {
179         emp_remque(qp);
180         emp_insque(qp, list);
181         qp = list->q_back;
182         ulp = (struct ulist *)qp;
183         if (ulp->unit.gen.uid == uid || uid == -1)
184             break;
185     } while (list->q_back != save);
186 }
187
188 static char *
189 unit_move_parse(char *cp, char *arg1_default)
190 {
191     int ac;
192
193     ac = parse(cp, player->argbuf, player->argp, NULL, NULL, NULL);
194     if (CANT_HAPPEN(ac <= 0)) {
195         player->argp[0] = "";
196         return "";
197     }
198     if (ac == 1) {
199         player->argp[1] = arg1_default;
200         return cp + 1;
201     }
202     return "";
203 }
204
205 static char *
206 unit_move_non_dir(struct emp_qelem *list, char *cp, int *map_shown)
207 {
208     struct empobj *leader = get_leader(list);
209     int bmap = 0, stopping;
210     char leader_str[32];
211
212     *map_shown = 0;
213     sprintf(leader_str, "%d", leader->uid);
214
215     switch (*cp) {
216     case 'B':
217         bmap = 'b';
218         /* fall through */
219     case 'M':
220         cp = unit_move_parse(cp, leader_str);
221         display_region_map(bmap, leader->ef_type, leader->x, leader->y,
222                            player->argp[1], player->argp[2]);
223         *map_shown = 1;
224         break;
225     case 'f':
226         cp = unit_move_parse(cp, NULL);
227         switch_leader(list, player->argp[1]);
228         break;
229     case 'i':
230         cp++;
231         unit_list(list);
232         break;
233     case 'm':
234         cp++;
235         if (leader->ef_type == EF_SHIP)
236             stopping = shp_sweep(list, 1, 1, player->cnum);
237         else
238             stopping = lnd_sweep(list, 1, 1, player->cnum);
239         if (stopping)
240             cp = "";
241         break;
242     case 'r':
243         cp = unit_move_parse(cp, leader_str);
244         radar(leader->ef_type);
245         player->btused++;       /* FIXME use player_coms[].c_cost */
246         *map_shown = 1;
247         break;
248     case 'l':
249         cp = unit_move_parse(cp, leader_str);
250         do_look(leader->ef_type);
251         player->btused++;       /* FIXME likewise */
252         break;
253     case 's':
254         if (leader->ef_type != EF_SHIP)
255             return NULL;
256         cp = unit_move_parse(cp, leader_str);
257         sona();
258         player->btused++;       /* FIXME likewise */
259         *map_shown = 1;
260         break;
261     case 'd':
262         cp = unit_move_parse(cp, NULL);
263         if (!player->argp[1]) {
264             player->argp[1] = leader_str;
265             player->argp[2] = "1";
266         } else if (!player->argp[2]) {
267             player->argp[2] = player->argp[1];
268             player->argp[1] = leader_str;
269         }
270         if (leader->ef_type == EF_SHIP)
271             mine();
272         else
273             landmine();
274         player->btused++;       /* FIXME likewise */
275         *map_shown = 1;
276         break;
277     case 'v':
278         cp++;
279         unit_view(list);
280         break;
281     default:
282         return NULL;
283     }
284
285     return cp;
286 }
287
288 static char *
289 unit_move_getpath(struct emp_qelem *list, int suppress_map, char *path)
290 {
291     struct empobj *leader = get_leader(list);
292     double minmob, maxmob;
293     struct emp_qelem *qp;
294     struct ulist *ulp;
295     char prompt[64];
296
297     minmob = HUGE_VAL;
298     maxmob = -HUGE_VAL;
299     for (qp = list->q_back; qp != list; qp = qp->q_back) {
300         ulp = (struct ulist *)qp;
301         if (ulp->mobil < minmob)
302             minmob = ulp->mobil;
303         if (ulp->mobil > maxmob)
304             maxmob = ulp->mobil;
305     }
306     if (!suppress_map)
307         nav_map(leader->x, leader->y,
308                 leader->ef_type == EF_SHIP
309                 ? !(mchr[leader->type].m_flags & M_SUB) : 1);
310     snprintf(prompt, sizeof(prompt), "<%.1f:%.1f: %s> ",
311              maxmob, minmob,
312              xyas(leader->x, leader->y, player->cnum));
313     return getstring(prompt, path);
314 }
315
316 static char *
317 unit_move_route(struct empobj *unit, char *buf, size_t bufsz)
318 {
319     coord destx;
320     coord desty;
321     struct sctstr sect;
322     size_t len;
323     double c;
324     int mtype;
325
326     if (CANT_HAPPEN(unit->ef_type != EF_LAND && unit->ef_type != EF_SHIP))
327         return NULL;
328
329     if (!sarg_xy(buf, &destx, &desty))
330         return buf;
331     if (unit->ef_type == EF_SHIP) {
332         c = path_find(unit->x, unit->y, destx, desty,
333                       player->cnum, MOB_SAIL);
334         if (c < 0 || unit->mobil <= 0) {
335             pr("Can't get to '%s' right now.\n",
336                xyas(destx, desty, player->cnum));
337             return NULL;
338         }
339     } else {
340         getsect(unit->x, unit->y, &sect);
341         mtype = lnd_mobtype((struct lndstr *)unit);
342         /*
343          * Note: passing sect.sct_own for actor is funny, but works:
344          * its only effect is to confine the search to that nation's
345          * land.  It doesn't affect mobility costs.  The real actor is
346          * different for marching in allied land, and passing it would
347          * break path finding there.
348          */
349         c = path_find(unit->x, unit->y, destx, desty, sect.sct_own, mtype);
350         if (c < 0) {
351             pr("No owned %s from %s to %s!\n",
352                mtype == MOB_RAIL ? "railway" : "path",
353                xyas(unit->x, unit->y, player->cnum),
354                xyas(destx, desty, player->cnum));
355             return NULL;
356         }
357     }
358     len = path_find_route(buf, bufsz, unit->x, unit->y, destx, desty);
359     if (len == 0 || unit->ef_type == EF_LAND) {
360         if (len + 1 < bufsz)
361             strcpy(buf + len, "h");
362         len++;
363     }
364     if (len >= bufsz) {
365         pr("Can't handle path to %s, it's too long, sorry\n",
366            xyas(destx, desty, player->cnum));
367         return NULL;
368     }
369     pr("Using path '%s'\n", buf);
370     return buf;
371 }
372
373 int
374 unit_move(struct emp_qelem *list)
375 {
376     struct empobj *leader = get_leader(list);
377     int leader_uid = leader->uid;
378     int type = leader->ef_type;
379     int moved, suppress_map, dir, stopping;
380     char *cp;
381     char path[1024];
382
383     unit_rad_map_set(list);
384
385     pr("%s is %s\n",
386         type == EF_SHIP ? "Flagship" : "Leader",
387         unit_nameof(leader));
388
389     cp = "";
390     if (player->argp[2]) {
391         strcpy(path, player->argp[2]);
392         cp = unit_move_route(leader, path, sizeof(path));
393         if (!cp)
394             cp = "";
395     }
396
397     moved = suppress_map = 0;
398     for (;;) {
399         /*
400          * Invariants:
401          * - shp_may_nav() true for all ships
402          * - lnd_may_mar() true for all land units
403          * - leader is up-to-date
404          * Implies all are in the same sector
405          */
406         if (!*cp) {
407             cp = unit_move_getpath(list, suppress_map, path);
408             if (!cp) {
409                 if (type == EF_SHIP) {
410                     shp_nav_stay_behind(list, player->cnum);
411                     shp_nav_put(list, player->cnum);
412                 } else {
413                     lnd_mar_stay_behind(list, player->cnum);
414                     lnd_mar_put(list, player->cnum);
415                 }
416                 return RET_FAIL;
417             }
418             cp = unit_move_route(leader, path, sizeof(path));
419             if (!cp || !*cp)
420                 cp = "h";
421             suppress_map = 0;
422         } else if ((dir = chkdir(*cp, DIR_STOP, DIR_LAST)) >= 0) {
423             cp++;
424             if (type == EF_SHIP)
425                 stopping = shp_nav_dir(list, dir, player->cnum)
426                     || shp_nav_gauntlet(list, 1, player->cnum);
427             else {
428                 if (!moved && !lnd_abandon_askyn(list)) {
429                     lnd_mar_put(list, player->cnum);
430                     return RET_FAIL;
431                 }
432                 stopping = lnd_mar_dir(list, dir, player->cnum)
433                     || lnd_mar_gauntlet(list, 1, player->cnum);
434             }
435             if (dir == DIR_STOP) {
436                 CANT_HAPPEN(!QEMPTY(list));
437                 return RET_OK;
438             }
439             moved = 1;
440             if (stopping)
441                 cp = "";
442         } else {
443             cp = unit_move_non_dir(list, cp, &suppress_map);
444             if (!cp) {
445                 direrr("`%c' to stop", ", `%c' to view", NULL);
446                 pr(", `i' to list %s, `f' to change %s,\n",
447                    type == EF_SHIP ? "ships" : "units",
448                    type == EF_SHIP ? "flagship" : "leader");
449                 pr("`r' to radar, %s`l' to look, `M' to map, `B' to bmap,\n",
450                    type == EF_SHIP ? "`s' to sonar, " : "");
451                 pr("`d' to drop mines, and `m' to sweep mines\n");
452                 cp = "";
453             }
454         }
455
456         if (type == EF_SHIP)
457             shp_nav_stay_behind(list, player->cnum);
458         else
459             lnd_mar_stay_behind(list, player->cnum);
460
461         if (QEMPTY(list)) {
462             pr("No %s left\n", type == EF_SHIP ? "ships" : "lands");
463             return RET_OK;
464         }
465
466         leader = get_leader(list);
467         if (leader->uid != leader_uid) {
468             leader_uid = leader->uid;
469             pr("Changing %s to %s\n",
470                leader->ef_type == EF_SHIP ? "flagship" : "leader",
471                unit_nameof(leader));
472         }
473         unit_rad_map_set(list);
474     }
475 }
476
477 /*
478  * Teleport @unit to @x,@y.
479  * If @unit's mission op-area is centered on it, keep it centered.
480  */
481 void
482 unit_teleport(struct empobj *unit, coord x, coord y)
483 {
484     if (unit->opx == unit->x && unit->opy == unit->y) {
485         unit->opx = x;
486         unit->opy = y;
487     }
488     unit->x = x;
489     unit->y = y;
490 }
491
492 /*
493  * Update cargo of @carrier for movement or destruction.
494  * If the carrier is destroyed, destroy its cargo (planes, land units,
495  * nukes).
496  * Else update their location to the carrier's.  Any op sectors equal
497  * to location get updated, too.
498  * Return number of units updated.
499  */
500 int
501 unit_update_cargo(struct empobj *carrier)
502 {
503     int cargo_type;
504     struct nstr_item ni;
505     union empobj_storage obj;
506     int n = 0;
507
508     for (cargo_type = EF_PLANE; cargo_type <= EF_NUKE; cargo_type++) {
509         snxtitem_cargo(&ni, cargo_type, carrier->ef_type, carrier->uid);
510         while (nxtitem(&ni, &obj)) {
511             if (carrier->own)
512                 unit_teleport(&obj.gen, carrier->x, carrier->y);
513             else {
514                 mpr(obj.gen.own, "%s lost!\n", unit_nameof(&obj.gen));
515                 obj.gen.effic = 0;
516             }
517             put_empobj(cargo_type, obj.gen.uid, &obj);
518             n++;
519         }
520     }
521     return n;
522 }
523
524 /*
525  * Drop cargo of @unit.
526  * Give it to @newown, unless it's zero.
527  */
528 void
529 unit_drop_cargo(struct empobj *unit, natid newown)
530 {
531     int type;
532     struct nstr_item ni;
533     union empobj_storage cargo;
534
535     for (type = EF_PLANE; type <= EF_NUKE; type++) {
536         snxtitem_cargo(&ni, type, unit->ef_type, unit->uid);
537         while (nxtitem(&ni, &cargo)) {
538             switch (type) {
539             case EF_PLANE:
540                 cargo.plane.pln_ship = cargo.plane.pln_land = -1;
541                 break;
542             case EF_LAND:
543                 cargo.land.lnd_ship = cargo.land.lnd_land = -1;
544                 break;
545             case EF_NUKE:
546                 cargo.nuke.nuk_plane = -1;
547                 break;
548             }
549             mpr(cargo.gen.own, "%s transferred off %s %d to %s\n",
550                 unit_nameof(&cargo.gen),
551                 ef_nameof(unit->ef_type), unit->uid,
552                 xyas(cargo.gen.x, cargo.gen.y, cargo.gen.own));
553             if (newown)
554                 unit_give_away(&cargo.gen, newown, cargo.gen.own);
555             put_empobj(type, cargo.gen.uid, &cargo.gen);
556         }
557     }
558 }
559
560 /*
561  * Give @unit and its cargo to @recipient.
562  * No action if @recipient already owns @unit.
563  * If @giver is non-zero, inform @recipient and @giver of the transaction.
564  * Clears mission and group on the units given away.
565  */
566 void
567 unit_give_away(struct empobj *unit, natid recipient, natid giver)
568 {
569     int type;
570     struct nstr_item ni;
571     union empobj_storage cargo;
572
573     if (unit->own == recipient)
574         return;
575
576     if (giver) {
577         mpr(unit->own, "%s given to %s\n",
578             unit_nameof(unit), cname(recipient));
579         mpr(recipient, "%s given to you by %s\n",
580             unit_nameof(unit), cname(giver));
581     }
582
583     unit->own = recipient;
584     unit_wipe_orders(unit);
585     put_empobj(unit->ef_type, unit->uid, unit);
586
587     for (type = EF_PLANE; type <= EF_NUKE; type++) {
588         snxtitem_cargo(&ni, type, unit->ef_type, unit->uid);
589         while (nxtitem(&ni, &cargo))
590             unit_give_away(&cargo.gen, recipient, giver);
591     }
592 }
593
594 /*
595  * Wipe orders and such from @unit.
596  */
597 void
598 unit_wipe_orders(struct empobj *unit)
599 {
600     struct shpstr *sp;
601     struct plnstr *pp;
602     struct lndstr *lp;
603
604     unit->group = 0;
605     unit->opx = unit->opy = 0;
606     unit->mission = 0;
607     unit->radius = 0;
608
609     switch (unit->ef_type) {
610     case EF_SHIP:
611         sp = (struct shpstr *)unit;
612         sp->shp_rflags = 0;
613         sp->shp_rpath[0] = 0;
614         break;
615     case EF_PLANE:
616         pp = (struct plnstr *)unit;
617         pp->pln_range = pln_range_max(pp);
618         break;
619     case EF_LAND:
620         lp = (struct lndstr *)unit;
621         lp->lnd_retreat = morale_base;
622         lp->lnd_rflags = 0;
623         lp->lnd_rpath[0] = 0;
624         break;
625     case EF_NUKE:
626         break;
627     default:
628         CANT_REACH();
629     }
630 }