]> git.pond.sub.org Git - empserver/blob - src/lib/subs/lndsub.c
ab21fc6f63837095b64f6bfa2483c2db285975fd
[empserver] / src / lib / subs / lndsub.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2014, 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  *  lndsub.c: Land unit subroutines
28  *
29  *  Known contributors to this file:
30  *     Ken Stevens, 1995
31  *     Steve McClure, 1998-2000
32  *     Markus Armbruster, 2004-2014
33  */
34
35 #include <config.h>
36
37 #include <math.h>
38 #include <stdlib.h>
39 #include "chance.h"
40 #include "combat.h"
41 #include "damage.h"
42 #include "empobj.h"
43 #include "file.h"
44 #include "misc.h"
45 #include "mission.h"
46 #include "news.h"
47 #include "nsc.h"
48 #include "optlist.h"
49 #include "path.h"
50 #include "player.h"
51 #include "prototypes.h"
52 #include "unit.h"
53 #include "xy.h"
54
55 static int lnd_check_one_mines(struct ulist *, int);
56 static void lnd_stays(natid, char *, struct ulist *);
57 static int lnd_hit_mine(struct lndstr *);
58 static int has_helpful_engineer(coord, coord, natid);
59
60 double
61 attack_val(int combat_mode, struct lndstr *lp)
62 {
63     int men;
64     double value;
65     struct lchrstr *lcp;
66
67     if (lp->lnd_effic < LAND_MINEFF) {
68         putland(lp->lnd_uid, lp);
69         return 0;
70     }
71
72     lcp = &lchr[(int)lp->lnd_type];
73
74 /* Spies always count as 1 during assaults.  If they are the only ones
75    in the assault, they get to sneak on anyway. */
76
77     if (lcp->l_flags & L_SPY && combat_mode == A_ASSAULT)
78         return 1;
79
80     men = lp->lnd_item[I_MILIT];
81     value = men * lnd_att(lp) * lp->lnd_effic / 100.0;
82
83     switch (combat_mode) {
84     case A_ATTACK:
85         return value;
86     case A_ASSAULT:
87         if (!(lcp->l_flags & L_MARINE))
88             return assault_penalty * value;
89         break;
90     case A_BOARD:
91         if (!(lcp->l_flags & L_MARINE))
92             return assault_penalty * men;
93     }
94
95     return value;
96 }
97
98 double
99 defense_val(struct lndstr *lp)
100 {
101     int men;
102     double value;
103     struct lchrstr *lcp;
104
105     if (lp->lnd_effic < LAND_MINEFF) {
106         putland(lp->lnd_uid, lp);
107         return 0;
108     }
109
110     lcp = &lchr[(int)lp->lnd_type];
111
112     men = lp->lnd_item[I_MILIT];
113
114     if ((lp->lnd_ship >= 0 || lp->lnd_land >= 0) &&
115         !(lcp->l_flags & L_MARINE))
116         return men;
117
118     value = men * lnd_def(lp) * lp->lnd_effic / 100.0;
119     value *= ((double)land_mob_max + lp->lnd_harden) / land_mob_max;
120
121     /* If there are military on the unit, you get at least a 1
122        man defensive unit, except for spies */
123     if (value < 1.0 && men > 0 && !(lcp->l_flags & L_SPY))
124         return 1;
125
126     return value;
127 }
128
129 int
130 lnd_reaction_range(struct lndstr *lp)
131 {
132     struct sctstr sect;
133
134     getsect(lp->lnd_x, lp->lnd_y, &sect);
135     if (sect.sct_type == SCT_HEADQ && sect.sct_effic >= 60)
136         return lchr[lp->lnd_type].l_rad + 1;
137     return lchr[lp->lnd_type].l_rad;
138 }
139
140 void
141 lnd_print(natid actor, struct ulist *llp, char *s)
142 {
143     if (actor == player->cnum)
144         pr("%s %s\n", prland(&llp->unit.land), s);
145     else
146         wu(0, actor, "%s %s\n", prland(&llp->unit.land), s);
147 }
148
149 int
150 lnd_take_casualty(int combat_mode, struct ulist *llp, int cas)
151                         /* attacking or assaulting or paratrooping? */
152                         /* number of casualties to take */
153 {
154     int eff_eq;
155     int n;
156     int biggest;
157     int civs;
158     coord ret_x, ret_y;
159     coord bx, by;
160     struct sctstr sect;
161     int ret_chance;
162     char buf[1024];
163     int taken;
164     int nowhere_to_go = 0;
165     double mobcost, bmcost;
166     signed char orig;
167     int mob;
168
169     taken = llp->unit.land.lnd_item[I_MILIT];
170     /* Spies always die */
171     if (lchr[llp->unit.land.lnd_type].l_flags & L_SPY)
172         llp->unit.land.lnd_effic = 0;
173     else {
174         eff_eq = ldround(cas * 100.0 /
175             lchr[llp->unit.land.lnd_type].l_item[I_MILIT], 1);
176         llp->unit.land.lnd_effic -= eff_eq;
177         lnd_submil(&llp->unit.land, cas);
178     }
179
180     if (llp->unit.land.lnd_effic < LAND_MINEFF) {
181         sprintf(buf, "dies %s %s!",
182                 combat_mode ? att_mode[combat_mode] : "defending",
183                 xyas(llp->unit.land.lnd_x, llp->unit.land.lnd_y,
184                      llp->unit.land.lnd_own));
185         lnd_print(llp->unit.land.lnd_own, llp, buf);
186         lnd_put_one(llp);
187         /* Since we killed the unit, we killed all the mil on it */
188         return taken;
189     } else {
190         /* Ok, now, how many did we take off? (sould be the diff) */
191         taken = taken - llp->unit.land.lnd_item[I_MILIT];
192     }
193
194     if (llp->unit.land.lnd_effic >= llp->unit.land.lnd_retreat)
195         return taken;
196
197     /* we're being boarded */
198     if (llp->unit.land.lnd_ship >= 0 && combat_mode == A_DEFEND)
199         return taken;
200
201     /* we're being boarded */
202     if (llp->unit.land.lnd_land >= 0 && combat_mode == A_DEFEND)
203         return taken;
204
205     /* Have to make a retreat check */
206
207     ret_chance = llp->unit.land.lnd_retreat - llp->unit.land.lnd_effic;
208     if (pct_chance(ret_chance)) {
209         pr("\n");
210         lnd_print(llp->unit.land.lnd_own, llp, "fails morale check!");
211         llp->unit.land.lnd_mission = 0;
212         llp->unit.land.lnd_harden = 0;
213         if (llp->unit.land.lnd_ship >= 0 || llp->unit.land.lnd_land >= 0)
214             nowhere_to_go = 1;
215         else if (combat_mode == A_DEFEND) {
216             /*
217              * defending unit.. find a place to send it
218              * strategy: look for the most-populated
219              * adjacent sector that is owned by the unit
220              * owner. Charge mob..
221              */
222             biggest = -1;
223             for (n = 1; n <= 6; ++n) {
224                 ret_x = llp->unit.land.lnd_x + diroff[n][0];
225                 ret_y = llp->unit.land.lnd_y + diroff[n][1];
226                 getsect(ret_x, ret_y, &sect);
227                 if (sect.sct_own != llp->unit.land.lnd_own)
228                     continue;
229                 if (sect.sct_type == SCT_MOUNT)
230                     continue;
231                 mobcost = lnd_mobcost(&llp->unit.land, &sect);
232                 if (mobcost < 0)
233                     continue;
234                 civs = sect.sct_item[I_CIVIL];
235                 if (civs > biggest) {
236                     biggest = civs;
237                     bx = sect.sct_x;
238                     by = sect.sct_y;
239                     bmcost = mobcost;
240                 }
241             }
242             if (biggest < 0)
243                 nowhere_to_go = 1;
244             else {
245                 /* retreat to bx,by */
246                 llp->unit.land.lnd_x = bx;
247                 llp->unit.land.lnd_y = by;
248                 /* FIXME landmines */
249                 mob = llp->unit.land.lnd_mobil - (int)bmcost;
250                 if (mob < -127)
251                     mob = -127;
252                 orig = llp->unit.land.lnd_mobil;
253                 llp->unit.land.lnd_mobil = (signed char)mob;
254                 if (llp->unit.land.lnd_mobil > orig)
255                     llp->unit.land.lnd_mobil = -127;
256                 sprintf(buf, "retreats at %d%% efficiency to %s!",
257                         llp->unit.land.lnd_effic,
258                         xyas(bx, by, llp->unit.land.lnd_own));
259                 lnd_print(llp->unit.land.lnd_own, llp, buf);
260                 lnd_put_one(llp);
261             }
262         } else {                /* attacking from a sector */
263             sprintf(buf, "leaves the battlefield at %d%% efficiency",
264                     llp->unit.land.lnd_effic);
265             if ((llp->unit.land.lnd_mobil - (int)llp->mobil) < -127)
266                 llp->unit.land.lnd_mobil = -127;
267             else
268                 llp->unit.land.lnd_mobil -= (int)llp->mobil;
269             llp->mobil = 0.0;
270             lnd_print(llp->unit.land.lnd_own, llp, buf);
271             lnd_put_one(llp);
272         }
273     }
274     if (nowhere_to_go) {
275         /* nowhere to go.. take more casualties */
276         llp->unit.land.lnd_effic -= 10;
277         lnd_submil(&llp->unit.land,
278                    lchr[llp->unit.land.lnd_type].l_item[I_MILIT] / 10);
279         if (llp->unit.land.lnd_effic < LAND_MINEFF) {
280             lnd_print(llp->unit.land.lnd_own, llp,
281                       "has nowhere to retreat, and dies!");
282             lnd_put_one(llp);
283         } else
284             lnd_print(llp->unit.land.lnd_own, llp,
285                       "has nowhere to retreat and takes extra losses!");
286     }
287
288     return taken;
289 }
290
291 void
292 lnd_takemob(struct emp_qelem *list, double loss)
293 {
294     struct emp_qelem *qp, *next;
295     struct ulist *llp;
296     int new;
297     int mcost = ldround(combat_mob * loss, 1);
298
299     for (qp = list->q_forw; qp != list; qp = next) {
300         next = qp->q_forw;
301         llp = (struct ulist *)qp;
302 #if 0
303         if (chance(loss))
304             use_supply(&llp->unit.land);
305 #endif
306         new = llp->unit.land.lnd_mobil - mcost;
307         if (new < -127)
308             new = -127;
309         llp->unit.land.lnd_mobil = (signed char)new;
310     }
311 }
312
313 void
314 lnd_submil(struct lndstr *lp, int num)
315 {
316     int new = lp->lnd_item[I_MILIT] - num;
317     lp->lnd_item[I_MILIT] = new < 0 ? 0 : new;
318 }
319
320 int
321 lnd_spyval(struct lndstr *lp)
322 {
323     if (lchr[(int)lp->lnd_type].l_flags & L_RECON)
324         return lchr[lp->lnd_type].l_spy * (lp->lnd_effic / 100.0) + 2;
325     else
326         return lchr[lp->lnd_type].l_spy * (lp->lnd_effic / 100.0);
327 }
328
329 void
330 intelligence_report(int destination, struct lndstr *lp, int spy,
331                     char *mess)
332 {
333     int vis = lnd_vis(lp);
334     char buf1[80], buf2[80], buf3[80];
335
336     if (destination == 0)
337         return;
338
339     if (lp->lnd_own == 0)
340         return;
341
342     memset(buf1, 0, sizeof(buf1));
343     memset(buf2, 0, sizeof(buf2));
344     memset(buf3, 0, sizeof(buf3));
345     if (chance((spy + vis) / 10.0)) {
346         if (destination == player->cnum)
347             pr("%s %s", mess, prland(lp));
348         else
349             sprintf(buf1, "%s %s", mess, prland(lp));
350
351         if (chance((spy + vis) / 20.0)) {
352             if (destination == player->cnum)
353                 pr(" (eff %d, mil %d",
354                    roundintby(lp->lnd_effic, 5),
355                    roundintby(lp->lnd_item[I_MILIT], 10));
356             else
357                 sprintf(buf2, " (eff %d, mil %d",
358                         roundintby(lp->lnd_effic, 5),
359                         roundintby(lp->lnd_item[I_MILIT], 10));
360
361             if (chance((spy + vis) / 20.0)) {
362                 int t;
363                 t = lp->lnd_tech - 20 + roll(40);
364                 t = MAX(t, 0);
365                 if (destination == player->cnum)
366                     pr(", tech %d)\n", t);
367                 else
368                     sprintf(buf3, ", tech %d)\n", t);
369             } else {
370                 if (destination == player->cnum)
371                     pr(")\n");
372                 else
373                     sprintf(buf3, ")\n");
374             }
375         } else {
376             if (destination == player->cnum)
377                 pr("\n");
378             else
379                 sprintf(buf2, "\n");
380         }
381     }
382
383     if (destination != player->cnum) {
384         wu(0, destination, "%s%s%s", buf1, buf2, buf3);
385     }
386 }
387
388 void
389 lnd_sel(struct nstr_item *ni, struct emp_qelem *list)
390 {
391     struct lndstr land;
392     int this_mot;
393     int mobtype = MOB_MOVE;     /* indeterminate */
394
395     emp_initque(list);
396     while (nxtitem(ni, &land)) {
397         /*
398          * It would be nice to let deities march foreign land units,
399          * but much of the code assumes that only the land unit's
400          * owner can march it.
401          */
402         if (!land.lnd_own || land.lnd_own != player->cnum)
403             continue;
404         if (opt_MARKET) {
405             if (ontradingblock(EF_LAND, &land)) {
406                 pr("unit #%d inelligible - it's for sale.\n",
407                    land.lnd_uid);
408                 continue;
409             }
410         }
411         /*
412          * The marching code gets confused when trains and non-trains
413          * march together.  Disallow for now.
414          */
415         this_mot = lnd_mobtype(&land);
416         if (this_mot != mobtype) {
417             if (mobtype == MOB_MOVE)
418                 mobtype = this_mot;
419             else if (mobtype == MOB_MARCH) {
420                 pr("%s is a train and can't march with the leader.\n",
421                    prland(&land));
422                 continue;
423             } else {
424                 pr("%s can't rail-march with the leading train.\n",
425                    prland(&land));
426                 continue;
427             }
428         }
429
430         land.lnd_mission = 0;
431         land.lnd_rflags = 0;
432         land.lnd_harden = 0;
433         memset(land.lnd_rpath, 0, sizeof(land.lnd_rpath));
434         putland(land.lnd_uid, &land);
435         lnd_insque(&land, list);
436     }
437 }
438
439 /*
440  * Append LP to LIST.
441  * Return the new list link.
442  */
443 struct ulist *
444 lnd_insque(struct lndstr *lp, struct emp_qelem *list)
445 {
446     struct ulist *mlp = malloc(sizeof(struct ulist));
447
448     mlp->unit.land = *lp;
449     mlp->mobil = lp->lnd_mobil;
450     emp_insque(&mlp->queue, list);
451     return mlp;
452 }
453
454 /* This function assumes that the list was created by lnd_sel */
455 void
456 lnd_mar(struct emp_qelem *list, double *minmobp, double *maxmobp,
457         natid actor)
458 {
459     struct emp_qelem *qp;
460     struct emp_qelem *next;
461     struct ulist *llp;
462     struct lndstr *lp, *ldr = NULL;
463     struct sctstr sect;
464     char mess[128];
465
466     *minmobp = 9876.0;
467     *maxmobp = -9876.0;
468     for (qp = list->q_back; qp != list; qp = next) {
469         next = qp->q_back;
470         llp = (struct ulist *)qp;
471         lp = &llp->unit.land;
472         getland(lp->lnd_uid, lp);
473         if (lp->lnd_own != actor) {
474             mpr(actor, "%s was disbanded at %s\n",
475                 prland(lp), xyas(lp->lnd_x, lp->lnd_y, actor));
476             emp_remque(&llp->queue);
477             free(llp);
478             continue;
479         }
480         if (lp->lnd_ship >= 0) {
481             lnd_stays(actor, "is on a ship", llp);
482             continue;
483         }
484         if (lp->lnd_land >= 0) {
485             lnd_stays(actor, "is on a unit", llp);
486             continue;
487         }
488         if (!getsect(lp->lnd_x, lp->lnd_y, &sect)) {
489             lnd_stays(actor, "was sucked into the sky by a strange looking spaceland", llp);    /* heh -KHS */
490             continue;
491         }
492         if (!(lchr[lp->lnd_type].l_flags & L_SPY) &&
493             !(lchr[lp->lnd_type].l_flags & L_TRAIN) &&
494             lp->lnd_item[I_MILIT] == 0) {
495             lnd_stays(actor, "has no mil on it to guide it", llp);
496             continue;
497         }
498         switch (lnd_check_mar(lp, &sect)) {
499         case LND_STUCK_NOT:
500             break;
501         case LND_STUCK_NO_RAIL:
502             lnd_stays(actor, "is stuck off the rail system", llp);
503             continue;
504         default:
505             CANT_REACH();
506             /* fall through */
507         case LND_STUCK_IMPASSABLE:
508             lnd_stays(actor, "is stuck", llp);
509             continue;
510         }
511         if (relations_with(sect.sct_own, actor) != ALLIED &&
512             !(lchr[lp->lnd_type].l_flags & L_SPY) &&
513             sect.sct_own) {
514             sprintf(mess, "has been kidnapped by %s", cname(sect.sct_own));
515             lnd_stays(actor, mess, llp);
516             continue;
517         }
518         if (!ldr)
519             ldr = lp;
520         else if (lp->lnd_x != ldr->lnd_x || lp->lnd_y != ldr->lnd_y) {
521             lnd_stays(actor, "is not with the leader", llp);
522             continue;
523         }
524         if (lp->lnd_mobil + 1 < (int)llp->mobil) {
525             llp->mobil = lp->lnd_mobil;
526         }
527         if (llp->mobil < *minmobp)
528             *minmobp = llp->mobil;
529         if (llp->mobil > *maxmobp)
530             *maxmobp = llp->mobil;
531     }
532 }
533
534 static void
535 lnd_mar_put_one(struct ulist *llp)
536 {
537     if (llp->mobil < -127)
538         llp->mobil = -127;
539     llp->unit.land.lnd_mobil = llp->mobil;
540     lnd_put_one(llp);
541 }
542
543 static void
544 lnd_mar_put(struct emp_qelem *list, natid actor)
545 {
546     struct emp_qelem *qp, *next;
547     struct ulist *llp;
548     struct lndstr *lp;
549
550     for (qp = list->q_back; qp != list; qp = next) {
551         next = qp->q_back;
552         llp = (struct ulist *)qp;
553         lp = &llp->unit.land;
554         mpr(actor, "%s stopped at %s\n",
555             prland(lp), xyas(lp->lnd_x, lp->lnd_y, actor));
556         lnd_mar_put_one(llp);
557     }
558 }
559
560 void
561 lnd_put(struct emp_qelem *list)
562 {
563     struct emp_qelem *qp, *next;
564
565     for (qp = list->q_back; qp != list; qp = next) {
566         next = qp->q_back;
567         lnd_put_one((struct ulist *)qp);
568     }
569 }
570
571 void
572 lnd_put_one(struct ulist *llp)
573 {
574     putland(llp->unit.land.lnd_uid, &llp->unit.land);
575     emp_remque(&llp->queue);
576     free(llp);
577 }
578
579 /*
580  * Sweep landmines with engineers in LAND_LIST for ACTOR.
581  * If EXPLICIT is non-zero, this is for an explicit sweep command from
582  * a player.  Else it's an automatic "on the move" sweep.
583  * If TAKEMOB is non-zero, require and charge mobility.
584  */
585 int
586 lnd_sweep(struct emp_qelem *land_list, int explicit, int takemob,
587           natid actor)
588 {
589     struct emp_qelem *qp;
590     struct emp_qelem *next;
591     struct ulist *llp;
592     struct sctstr sect;
593     int mines, m, max, sshells, lshells;
594     int stopping = 0;
595
596     for (qp = land_list->q_back; qp != land_list; qp = next) {
597         next = qp->q_back;
598         llp = (struct ulist *)qp;
599         if (!(lchr[llp->unit.land.lnd_type].l_flags & L_ENGINEER)) {
600             if (explicit)
601                 mpr(actor, "%s is not an engineer!\n",
602                     prland(&llp->unit.land));
603             continue;
604         }
605         if (takemob && llp->mobil <= 0.0) {
606             if (explicit)
607                 mpr(actor, "%s is out of mobility!\n",
608                     prland(&llp->unit.land));
609             continue;
610         }
611         getsect(llp->unit.land.lnd_x, llp->unit.land.lnd_y, &sect);
612         if (!explicit && relations_with(sect.sct_oldown, actor) == ALLIED)
613             continue;
614         if (SCT_MINES_ARE_SEAMINES(&sect)) {
615             if (explicit)
616                 mpr(actor, "%s is in a %s sector.  No landmines there!\n",
617                     prland(&llp->unit.land), dchr[sect.sct_type].d_name);
618             continue;
619         }
620         if (takemob) {
621             llp->mobil -= lnd_pathcost(&llp->unit.land, 0.2);
622             llp->unit.land.lnd_mobil = (int)llp->mobil;
623             llp->unit.land.lnd_harden = 0;
624         }
625         putland(llp->unit.land.lnd_uid, &llp->unit.land);
626         if (!(mines = sect.sct_mines))
627             continue;
628         max = lchr[llp->unit.land.lnd_type].l_item[I_SHELL];
629         lshells = llp->unit.land.lnd_item[I_SHELL];
630         sshells = sect.sct_item[I_SHELL];
631         for (m = 0; mines > 0 && m < max * 2; m++) {
632             if (chance(0.5 * lchr[llp->unit.land.lnd_type].l_att)) {
633                 mpr(actor, "Sweep...\n");
634                 mines--;
635                 if (lshells < max)
636                     ++lshells;
637                 else if (sshells < ITEM_MAX)
638                     ++sshells;
639             }
640         }
641         sect.sct_mines = mines;
642         llp->unit.land.lnd_item[I_SHELL] = lshells;
643         sect.sct_item[I_SHELL] = sshells;
644         putland(llp->unit.land.lnd_uid, &llp->unit.land);
645         putsect(&sect);
646         if (lnd_check_one_mines(llp, 1)) {
647             stopping = 1;
648             emp_remque(qp);
649             free(qp);
650         }
651     }
652     return stopping;
653 }
654
655 static int
656 contains_engineer(struct emp_qelem *list)
657 {
658     struct emp_qelem *qp;
659     struct emp_qelem *next;
660     struct ulist *llp;
661
662     for (qp = list->q_back; qp != list; qp = next) {
663         next = qp->q_back;
664         llp = (struct ulist *)qp;
665         if (lchr[llp->unit.land.lnd_type].l_flags & L_ENGINEER)
666             return 1;
667     }
668     return 0;
669 }
670
671 static int
672 lnd_check_one_mines(struct ulist *llp, int with_eng)
673 {
674     struct sctstr sect;
675
676     getsect(llp->unit.land.lnd_x, llp->unit.land.lnd_y, &sect);
677     if (SCT_LANDMINES(&sect) == 0)
678         return 0;
679     if (relations_with(sect.sct_oldown, llp->unit.land.lnd_own) == ALLIED)
680         return 0;
681     if (chance(DMINE_LHITCHANCE(sect.sct_mines) / (1 + 2 * with_eng))) {
682         lnd_hit_mine(&llp->unit.land);
683         sect.sct_mines--;
684         putsect(&sect);
685         putland(llp->unit.land.lnd_uid, &llp->unit.land);
686         if (!llp->unit.land.lnd_own)
687             return 1;
688     }
689     return 0;
690 }
691
692 int
693 lnd_check_mines(struct emp_qelem *land_list)
694 {
695     struct emp_qelem *qp;
696     struct emp_qelem *next;
697     struct ulist *llp;
698     int stopping = 0;
699     int with_eng = contains_engineer(land_list);
700
701     for (qp = land_list->q_back; qp != land_list; qp = next) {
702         next = qp->q_back;
703         llp = (struct ulist *)qp;
704         if (lnd_check_one_mines(llp, with_eng)) {
705             stopping = 1;
706             emp_remque(qp);
707             free(qp);
708         }
709     }
710     return stopping;
711 }
712
713 static void
714 lnd_stays(natid actor, char *str, struct ulist *llp)
715 {
716     mpr(actor, "%s %s & stays in %s\n",
717         prland(&llp->unit.land), str,
718         xyas(llp->unit.land.lnd_x, llp->unit.land.lnd_y, actor));
719     lnd_mar_put_one(llp);
720 }
721
722 /* Return whether and why SP would be stuck in SECTP.  */
723 enum lnd_stuck
724 lnd_check_mar(struct lndstr *lp, struct sctstr *sectp)
725 {
726     if (dchr[sectp->sct_type].d_mob0 < 0)
727         return LND_STUCK_IMPASSABLE;
728     if (lnd_mobtype(lp) == MOB_RAIL && !SCT_HAS_RAIL(sectp))
729         return LND_STUCK_NO_RAIL;
730     return LND_STUCK_NOT;
731 }
732
733 static int
734 lnd_damage(struct emp_qelem *list, int totdam)
735 {
736     struct emp_qelem *qp;
737     struct emp_qelem *next;
738     struct ulist *llp;
739     int dam;
740     int count;
741
742     if (!totdam || !(count = emp_quelen(list)))
743         return 0;
744     dam = ldround((double)totdam / count, 1);
745     for (qp = list->q_back; qp != list; qp = next) {
746         next = qp->q_back;
747         llp = (struct ulist *)qp;
748         /* land unit might have changed (launched SAMs, collateral dmg) */
749         getland(llp->unit.land.lnd_uid, &llp->unit.land);
750         landdamage(&llp->unit.land, dam);
751         putland(llp->unit.land.lnd_uid, &llp->unit.land);
752         if (!llp->unit.land.lnd_own) {
753             emp_remque(qp);
754             free(qp);
755         }
756     }
757     return dam;
758 }
759
760 static int
761 lnd_easiest_target(struct emp_qelem *list)
762 {
763     struct emp_qelem *qp;
764     struct emp_qelem *next;
765     struct ulist *llp;
766     int hard;
767     int easiest = 9876;         /* things start great for victim */
768     int count = 0;
769
770     for (qp = list->q_back; qp != list; qp = next) {
771         next = qp->q_back;
772         llp = (struct ulist *)qp;
773         hard = lnd_hardtarget(&llp->unit.land);
774         if (hard < easiest)
775             easiest = hard;     /* things get worse for victim */
776         ++count;
777     }
778     return easiest - count;
779 }
780
781 static int
782 lnd_missile_interdiction(struct emp_qelem *list, coord newx, coord newy,
783                          natid victim)
784 {
785     int mindam = emp_quelen(list) * 20;
786     int hardtarget = lnd_easiest_target(list);
787     int dam, newdam, sublaunch;
788     int stopping = 0;
789     struct plist *plp;
790     struct emp_qelem msl_list, *qp, *newqp;
791
792     msl_sel(&msl_list, newx, newy, victim, P_T, P_MAR, MI_INTERDICT);
793
794     dam = 0;
795     for (qp = msl_list.q_back; qp != &msl_list; qp = newqp) {
796         newqp = qp->q_back;
797         plp = (struct plist *)qp;
798
799         if (dam < mindam && mission_pln_equip(plp, NULL, 'p') >= 0) {
800             if (msl_launch(&plp->plane, EF_LAND, "troops",
801                            newx, newy, victim, &sublaunch) < 0)
802                 goto use_up_msl;
803             stopping = 1;
804             if (msl_hit(&plp->plane, hardtarget, EF_LAND,
805                         N_LND_MISS, N_LND_SMISS, sublaunch, victim)) {
806                 newdam = pln_damage(&plp->plane, 'p', 1);
807                 dam += newdam;
808             } else {
809                 newdam = pln_damage(&plp->plane, 'p', 0);
810                 collateral_damage(newx, newy, newdam);
811             }
812         use_up_msl:
813             plp->plane.pln_effic = 0;
814             putplane(plp->plane.pln_uid, &plp->plane);
815         }
816         emp_remque(qp);
817         free(qp);
818     }
819
820     if (dam) {
821         mpr(victim, "missile interdiction mission does %d damage!\n", dam);
822         collateral_damage(newx, newy, dam);
823         lnd_damage(list, dam);
824     }
825     return stopping;
826 }
827
828 #if 0
829 /* Steve M. - commented out for now until abuse is decided upon */
830 /* risner: allow forts to interdict land units. */
831 static int
832 lnd_fort_interdiction(struct emp_qelem *list,
833                       coord newx, coord newy, natid victim)
834 {
835     struct nstr_sect ns;
836     struct sctstr fsect;
837     int trange, range;
838     int dam;
839     int stopping = 0;
840     int totdam = 0;
841
842     snxtsct_dist(&ns, newx, newy, fort_max_interdiction_range);
843     while (nxtsct(&ns, &fsect)) {
844         if (fsect.sct_own == 0)
845             continue;
846         if (relations_with(fsect.sct_own, victim) >= NEUTRAL)
847             continue;
848         range = roundrange(fortrange(&fsect));
849         trange = mapdist(newx, newy, fsect.sct_x, fsect.sct_y);
850         if (trange > range)
851             continue;
852         dam = fort_fire(&fsect);
853         putsect(&fsect);
854         if (dam < 0)
855             continue;
856         stopping = 1;
857         totdam += dam;
858         mpr(victim, "Incoming fire does %d damage!\n", dam);
859         wu(0, fsect.sct_own,
860            "%s fires at %s land units in %s for %d!\n",
861            xyas(fsect.sct_x, fsect.sct_y,
862                 fsect.sct_own),
863            cname(victim), xyas(newx, newy, fsect.sct_own), dam);
864         nreport(fsect.sct_own, N_SCT_SHELL, victim, 1);
865     }
866     if (totdam > 0)
867         lnd_damage(list, totdam);
868     return stopping;
869 }
870 #endif
871
872 static int
873 lnd_mission_interdiction(struct emp_qelem *list, coord x, coord y,
874                          natid victim)
875 {
876     int dam;
877
878     dam = unit_interdict(x, y, victim, "land units",
879                          lnd_easiest_target(list),
880                          MI_INTERDICT);
881     if (dam >= 0)
882         lnd_damage(list, dam);
883     return dam >= 0;
884 }
885
886 int
887 lnd_interdict(struct emp_qelem *list, coord newx, coord newy, natid victim)
888 {
889     int stopping = 0;
890
891 #if 0
892     if (!opt_NO_FORT_FIRE)
893 /* Steve M. - commented out for now until abuse is decided upon */
894         stopping |= lnd_fort_interdiction(list, newx, newy, victim);
895 #endif
896
897     stopping |= lnd_mission_interdiction(list, newx, newy, victim);
898     stopping |= lnd_missile_interdiction(list, newx, newy, victim);
899     return stopping;
900 }
901
902 /* high value of hardtarget is harder to hit */
903 int
904 lnd_hardtarget(struct lndstr *lp)
905 {
906     struct sctstr sect;
907
908     getsect(lp->lnd_x, lp->lnd_y, &sect);
909     return (int)((lp->lnd_effic / 100.0) *
910                  (10 + dchr[sect.sct_type].d_dstr * 2 + lnd_spd(lp) / 2.0
911                   - lnd_vis(lp)));
912 }
913
914 static int
915 lnd_hit_mine(struct lndstr *lp)
916 {
917     int m;
918
919     mpr(lp->lnd_own, "Blammo! Landmines detected in %s!\n",
920         xyas(lp->lnd_x, lp->lnd_y, lp->lnd_own));
921
922     nreport(lp->lnd_own, N_LHIT_MINE, 0, 1);
923
924     m = MINE_LDAMAGE();
925     if (lchr[lp->lnd_type].l_flags & L_ENGINEER)
926         m /= 2;
927
928     landdamage(lp, m);
929     return m;
930 }
931
932 double
933 lnd_pathcost(struct lndstr *lp, double pathcost)
934 {
935     double effspd;
936
937     effspd = lnd_spd(lp);
938     if (lchr[(int)lp->lnd_type].l_flags & L_SUPPLY)
939         effspd *= lp->lnd_effic * 0.01;
940
941     /*
942      * The return value must be PATHCOST times a factor that depends
943      * only on the land unit.  Anything else breaks path finding.  In
944      * particular, you can't add or enforce a minimum cost here.  Do
945      * it in sector_mcost().
946      */
947     return pathcost * 5.0 * speed_factor(effspd, lp->lnd_tech);
948 }
949
950 int
951 lnd_mobtype(struct lndstr *lp)
952 {
953     return (lchr[(int)lp->lnd_type].l_flags & L_TRAIN)
954         ? MOB_RAIL : MOB_MARCH;
955 }
956
957 double
958 lnd_mobcost(struct lndstr *lp, struct sctstr *sp)
959 {
960     return lnd_pathcost(lp, sector_mcost(sp, lnd_mobtype(lp)));
961 }
962
963 /*
964  * Ask user to confirm sector abandonment, if any.
965  * All land units in LIST must be in the same sector.
966  * If removing the land units in LIST would abandon their sector, ask
967  * the user to confirm.
968  * Return zero when abandonment was declined, else non-zero.
969  */
970 int lnd_abandon_askyn(struct emp_qelem *list)
971 {
972     struct ulist *llp;
973     struct sctstr sect;
974     struct emp_qelem *qp;
975
976     if (QEMPTY(list))
977         return 1;
978     llp = (struct ulist *)list->q_back;
979     getsect(llp->unit.land.lnd_x, llp->unit.land.lnd_y, &sect);
980     if (!abandon_askyn(&sect, I_CIVIL, 0, llp))
981         return 0;
982     if (!check_sect_ok(&sect))
983         return 0;
984     for (qp = list->q_back; qp != list; qp = qp->q_back) {
985         if (!check_land_ok(&((struct ulist *)qp)->unit.land))
986             return 0;
987     }
988     return 1;
989 }
990
991 int
992 lnd_mar_one_sector(struct emp_qelem *list, int dir, natid actor)
993 {
994     struct sctstr sect, osect;
995     struct emp_qelem *qp;
996     struct emp_qelem *next;
997     struct ulist *llp;
998     coord dx;
999     coord dy;
1000     coord newx;
1001     coord newy;
1002     int move;
1003     int stopping = 0;
1004     int visible;
1005     char dp[80];
1006     int rel;
1007     int oldown;
1008
1009     if (CANT_HAPPEN(QEMPTY(list)))
1010         return 1;
1011
1012     if (dir <= DIR_STOP || dir >= DIR_VIEW) {
1013         lnd_mar_put(list, actor);
1014         return 1;
1015     }
1016     dx = diroff[dir][0];
1017     dy = diroff[dir][1];
1018
1019     llp = (struct ulist *)list->q_back;
1020     getsect(llp->unit.land.lnd_x, llp->unit.land.lnd_y, &osect);
1021     oldown = osect.sct_own;
1022     newx = xnorm(llp->unit.land.lnd_x + dx);
1023     newy = ynorm(llp->unit.land.lnd_y + dy);
1024     getsect(newx, newy, &sect);
1025     rel = sect.sct_own ? relations_with(sect.sct_own, actor) : ALLIED;
1026
1027     move = 0;
1028     for (qp = list->q_back; qp != list; qp = next) {
1029         next = qp->q_back;
1030         llp = (struct ulist *)qp;
1031         switch (lnd_check_mar(&llp->unit.land, &sect)) {
1032         case LND_STUCK_NOT:
1033             if (rel == ALLIED
1034                 || (lchr[llp->unit.land.lnd_type].l_flags & L_SPY))
1035                 move = 1;
1036             break;
1037         case LND_STUCK_NO_RAIL:
1038             if (rel == ALLIED)
1039                 mpr(actor, "no rail system in %s\n",
1040                     xyas(newx, newy, actor));
1041             else
1042                 mpr(actor, "can't go to %s\n", xyas(newx, newy, actor));
1043             return 1;
1044         default:
1045             CANT_REACH();
1046             /* fall through */
1047         case LND_STUCK_IMPASSABLE:
1048             mpr(actor, "can't go to %s\n", xyas(newx, newy, actor));
1049             return 1;
1050         }
1051     }
1052     if (!move) {
1053         mpr(actor, "can't go to %s\n", xyas(newx, newy, actor));
1054         return 1;
1055     }
1056
1057     for (qp = list->q_back; qp != list; qp = next) {
1058         next = qp->q_back;
1059         llp = (struct ulist *)qp;
1060         if (rel != ALLIED
1061             && !(lchr[llp->unit.land.lnd_type].l_flags & L_SPY)) {
1062             sprintf(dp, "can't go to %s", xyas(newx, newy, actor));
1063             lnd_stays(actor, dp, llp);
1064             continue;
1065         }
1066         if (llp->mobil <= 0.0) {
1067             lnd_stays(actor, "is out of mobility", llp);
1068             continue;
1069         }
1070         llp->unit.land.lnd_x = newx;
1071         llp->unit.land.lnd_y = newy;
1072         llp->mobil -= lnd_mobcost(&llp->unit.land, &sect);
1073         llp->unit.land.lnd_mobil = (int)llp->mobil;
1074         llp->unit.land.lnd_harden = 0;
1075         putland(llp->unit.land.lnd_uid, &llp->unit.land);
1076         putsect(&osect);
1077         getsect(osect.sct_x, osect.sct_y, &osect);
1078         if (osect.sct_own != oldown && oldown == actor) {
1079             /* It was your sector, now it's not.  Simple :) */
1080             mpr(actor, "You no longer own %s\n",
1081                 xyas(osect.sct_x, osect.sct_y, actor));
1082         }
1083         if (rel != ALLIED) {
1084             /* must be a spy */
1085             /* Always a 10% chance of getting caught. */
1086             if (chance(LND_SPY_DETECT_CHANCE(llp->unit.land.lnd_effic))) {
1087                 if (rel == NEUTRAL || rel == FRIENDLY) {
1088                     wu(0, sect.sct_own,
1089                        "%s unit spotted in %s\n", cname(actor),
1090                        xyas(sect.sct_x, sect.sct_y, sect.sct_own));
1091                     setrel(sect.sct_own, llp->unit.land.lnd_own, HOSTILE);
1092                 } else if (rel <= HOSTILE) {
1093                     wu(0, sect.sct_own,
1094                        "%s spy shot in %s\n", cname(actor),
1095                        xyas(sect.sct_x, sect.sct_y, sect.sct_own));
1096                     mpr(actor, "%s was shot and killed.\n",
1097                         prland(&llp->unit.land));
1098                     llp->unit.land.lnd_effic = 0;
1099                     putland(llp->unit.land.lnd_uid, &llp->unit.land);
1100                     lnd_put_one(llp);
1101                 }
1102             }
1103         }
1104     }
1105     if (QEMPTY(list))
1106         return stopping;
1107     stopping |= lnd_sweep(list, 0, 1, actor);
1108     if (QEMPTY(list))
1109         return stopping;
1110     stopping |= lnd_check_mines(list);
1111     if (QEMPTY(list))
1112         return stopping;
1113
1114     visible = 0;
1115     for (qp = list->q_back; qp != list; qp = next) {
1116         next = qp->q_back;
1117         llp = (struct ulist *)qp;
1118         if (!(lchr[(int)llp->unit.land.lnd_type].l_flags & L_SPY))
1119             visible = 1;
1120     }
1121     if (visible)
1122         stopping |= lnd_interdict(list, newx, newy, actor);
1123
1124     return stopping;
1125 }
1126
1127 /*
1128  * Fire land unit support against VICTIM for ATTACKER, at X,Y.
1129  * If DEFENDING, this is defensive support, else offensive support.
1130  * Return total damage.
1131  */
1132 int
1133 lnd_support(natid victim, natid attacker, coord x, coord y, int defending)
1134 {
1135     struct nstr_item ni;
1136     struct lndstr land;
1137     int dam, dam2;
1138     int dist;
1139     int range;
1140
1141     dam = 0;
1142     snxtitem_all(&ni, EF_LAND);
1143     while (nxtitem(&ni, &land)) {
1144         if ((land.lnd_x == x) && (land.lnd_y == y))
1145             continue;
1146         if (!feels_like_helping(land.lnd_own, attacker, victim))
1147             continue;
1148
1149         /* are we in range? */
1150         dist = mapdist(land.lnd_x, land.lnd_y, x, y);
1151
1152         range = roundrange(lnd_fire_range(&land));
1153         if (dist > range)
1154             continue;
1155
1156         dam2 = lnd_fire(&land);
1157         putland(land.lnd_uid, &land);
1158         if (dam2 < 0)
1159             continue;
1160
1161         if (defending)
1162             nreport(land.lnd_own, N_FIRE_BACK, victim, 1);
1163         else
1164             nreport(land.lnd_own, N_FIRE_L_ATTACK, victim, 1);
1165         if (pct_chance(lnd_acc(&land) - 1))
1166             dam2 /= 2;
1167         dam += dam2;
1168         if (land.lnd_own != attacker)
1169             wu(0, land.lnd_own,
1170                "%s supported %s at %s\n",
1171                prland(&land), cname(attacker), xyas(x, y, land.lnd_own));
1172     }
1173     return dam;
1174 }
1175
1176 int
1177 lnd_can_attack(struct lndstr *lp)
1178 {
1179     struct lchrstr *lcp = &lchr[(int)lp->lnd_type];
1180
1181     if (lcp->l_flags & L_SUPPLY)
1182         return 0;
1183
1184     return 1;
1185 }
1186
1187 /*
1188  * Increase fortification value of LP.
1189  * Fortification costs mobility.  Use up to MOB mobility.
1190  * Return actual fortification increase.
1191  */
1192 int
1193 lnd_fortify(struct lndstr *lp, int mob)
1194 {
1195     int hard_amt;
1196     double mob_used, mult;
1197
1198     if (lp->lnd_ship >= 0 || lp->lnd_land >= 0)
1199         return 0;
1200
1201     mob_used = MIN(lp->lnd_mobil, mob);
1202     if (mob_used < 0)
1203         return 0;
1204
1205     mult = has_helpful_engineer(lp->lnd_x, lp->lnd_y, lp->lnd_own)
1206         ? 1.5 : 1.0;
1207
1208     hard_amt = (int)(mob_used * mult);
1209     if (lp->lnd_harden + hard_amt > land_mob_max) {
1210         hard_amt = land_mob_max - lp->lnd_harden;
1211         mob_used = ceil(hard_amt / mult);
1212     }
1213
1214     lp->lnd_mobil -= (int)mob_used;
1215     lp->lnd_harden += hard_amt;
1216     lp->lnd_harden = MIN(lp->lnd_harden, land_mob_max);
1217
1218     return hard_amt;
1219 }
1220
1221 /*
1222  * Is there a engineer unit at X,Y that can help nation CN?
1223  */
1224 static int
1225 has_helpful_engineer(coord x, coord y, natid cn)
1226 {
1227     struct nstr_item ni;
1228     struct lndstr land;
1229
1230     snxtitem_xy(&ni, EF_LAND, x, y);
1231     while (nxtitem(&ni, &land)) {
1232         if (relations_with(land.lnd_own, cn) != ALLIED)
1233             continue;
1234         if (lchr[(int)land.lnd_type].l_flags & L_ENGINEER)
1235             return 1;
1236     }
1237
1238     return 0;
1239 }
1240
1241 /*
1242  * Set LP's tech to TLEV along with everything else that depends on it.
1243  */
1244 void
1245 lnd_set_tech(struct lndstr *lp, int tlev)
1246 {
1247     struct lchrstr *lcp = lchr + lp->lnd_type;
1248
1249     if (CANT_HAPPEN(tlev < lcp->l_tech))
1250         tlev = lcp->l_tech;
1251
1252     lp->lnd_tech = tlev;
1253 }