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 *
unit_nameof(struct empobj * gp)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
unit_list(struct emp_qelem * unit_list)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
unit_view(struct emp_qelem * list)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
unit_rad_map_set(struct emp_qelem * list)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 *
get_leader(struct emp_qelem * list)161 get_leader(struct emp_qelem *list)
162 {
163     return &((struct ulist *)(list->q_back))->unit.gen;
164 }
165 
166 static void
switch_leader(struct emp_qelem * list,char * arg)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 *
unit_move_parse(char * cp,char * arg1_default)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 *
unit_move_non_dir(struct emp_qelem * list,char * cp,int * map_shown)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 	c_sonar();
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 	    c_mine();
272 	else
273 	    c_lmine();
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 *
unit_move_getpath(struct emp_qelem * list,int suppress_map,char * path)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 *
unit_move_route(struct empobj * unit,char * buf,size_t bufsz)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
unit_move(struct emp_qelem * list)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
unit_teleport(struct empobj * unit,coord x,coord y)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
unit_update_cargo(struct empobj * carrier)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
unit_drop_cargo(struct empobj * unit,natid newown)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
unit_give_away(struct empobj * unit,natid recipient,natid giver)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
unit_wipe_orders(struct empobj * unit)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 }
631