]> git.pond.sub.org Git - empserver/blobdiff - src/lib/subs/attsub.c
Update copyright notice
[empserver] / src / lib / subs / attsub.c
index 0c67580af863434f9344098c6669c7f931f58659..b9242f962fc4ac69f2ac37edc594e7210972d87f 100644 (file)
@@ -1,11 +1,11 @@
 /*
  *  Empire - A multi-player, client/server Internet based war game.
- *  Copyright (C) 1986-2008, Dave Pare, Jeff Bailey, Thomas Ruschak,
- *                           Ken Stevens, Steve McClure
+ *  Copyright (C) 1986-2021, Dave Pare, Jeff Bailey, Thomas Ruschak,
+ *                Ken Stevens, Steve McClure, Markus Armbruster
  *
- *  This program is free software; you can redistribute it and/or modify
+ *  Empire is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
+ *  the Free Software Foundation, either version 3 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
@@ -14,8 +14,7 @@
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  *  ---
  *
  *  Known contributors to this file:
  *     Ken Stevens, 1995
  *     Steve McClure, 1996-2000
- *     Markus Armbruster, 2006-2008
+ *     Markus Armbruster, 2006-2016
  */
 
 #include <config.h>
 
 #include <ctype.h>
 #include <math.h>
+#include "chance.h"
 #include "combat.h"
-#include "file.h"
+#include "empobj.h"
 #include "map.h"
 #include "misc.h"
 #include "mission.h"
+#include "news.h"
 #include "nsc.h"
 #include "optlist.h"
 #include "path.h"
 #include "plague.h"
 #include "player.h"
 #include "prototypes.h"
-#include "xy.h"
-#include "empobj.h"
 #include "unit.h"
+#include "xy.h"
 
 #define CASUALTY_LUMP  1       /* How big casualty chunks should be */
 
@@ -69,7 +69,7 @@ static int board_abort(struct combat *off, struct combat *def);
 static int land_board_abort(struct combat *off, struct combat *def);
 static int ask_off(int combat_mode, struct combat *off,
                   struct combat *def);
-static void get_dlist(struct combat *def, struct emp_qelem *list, int a_spy,
+static void get_dlist(struct combat *def, struct emp_qelem *, int a_spy,
                      int *d_spyp);
 static int get_ototal(int combat_mode, struct combat *off,
                      struct emp_qelem *olist, double osupport, int check);
@@ -83,8 +83,8 @@ static void send_reacting_units_home(struct emp_qelem *list);
 static int take_def(int combat_mode, struct emp_qelem *list,
                    struct combat *off, struct combat *def);
 
-static int get_land(int combat_mode, struct combat *def, int uid,
-                   struct ulist *llp, int victim_land);
+static int get_oland(int, struct ulist *);
+static int get_dland(struct combat *, struct ulist *);
 
 char *att_mode[] = {
     /* must match combat types in combat.h */
@@ -136,8 +136,7 @@ pr_com(int inon, struct combat *com, natid who)
     } else if (com->type == EF_SHIP) {
        return prbuf("%s%s %s(#%d)",
                     inon ? inon == 1 ? "on " : "onto " : "",
-                    com->shp_mcp->m_name, com->shp_name,
-                    com->shp_uid);
+                    com->shp_mcp->m_name, com->shp_name, com->shp_uid);
     } else if (com->type == EF_LAND) {
        return prbuf("%s%s #%d",
                     inon ? inon == 1 ? "on " : "onto " : "",
@@ -189,7 +188,8 @@ att_get_combat(struct combat *com, int isdef)
        y = com->y;
        break;
     case EF_LAND:
-       if (!getland(com->lnd_uid, &land) || !land.lnd_own) {
+       if (!getland(com->lnd_uid, &land) || !land.lnd_own
+           || land.lnd_ship >= 0 || land.lnd_land >= 0) {
            if (isdef)
                pr("Land unit #%d is not in the same sector!\n",
                   com->lnd_uid);
@@ -217,8 +217,7 @@ att_get_combat(struct combat *com, int isdef)
            return att_combat_init(com, EF_BAD);
        }
        if (opt_MARKET) {
-           if (isdef && player->owner &&
-               ontradingblock(EF_SHIP, &ship)) {
+           if (isdef && player->owner && ontradingblock(EF_SHIP, &ship)) {
                pr("%s is on the trading block.\n", prcom(0, com));
                return att_combat_init(com, EF_BAD);
            }
@@ -287,15 +286,15 @@ att_get_combat(struct combat *com, int isdef)
                   mil);
            com->troops = mil;
        } else {                /* attacker */
-           if (owner != player->cnum && getrel(getnatp(owner), player->cnum) != ALLIED) {
+           if (owner != com->own && owner != player->cnum) {
                /* must be EF_SECTOR */
-               if (com->mil)
+               if (com->troops)
                    pr("WARNING: Your %d mil in %s were destroyed because %s just took the sector!\n",
                       com->mil, xyas(com->x, com->y, player->cnum),
                       cname(owner));
                else
-                   pr("You no longer own %s\n",
-                      xyas(com->x, com->y, player->cnum));
+                   pr("%s just took %s!\n",
+                      cname(owner), xyas(com->x, com->y, player->cnum));
                return att_combat_init(com, EF_BAD);
            }
            if (com->troops && com->troops + 1 > mil) {
@@ -353,12 +352,12 @@ put_combat(struct combat *com)
                if ((com->mob - com->mobcost) < -127)
                    sect.sct_mobil = -127;
                else
-                   sect.sct_mobil = (short)(com->mob - com->mobcost);
+                   sect.sct_mobil = com->mob - com->mobcost;
            } else {
                if ((com->mob - com->mobcost) < 0)
                    sect.sct_mobil = 0;
                else
-                   sect.sct_mobil = (short)(com->mob - com->mobcost);
+                   sect.sct_mobil = com->mob - com->mobcost;
            }
        }
        sect.sct_own = com->own;
@@ -416,6 +415,11 @@ put_combat(struct combat *com)
        putship(com->shp_uid, &ship);
     }
     com->mobcost = 0;
+    /*
+     * FIXME if we just sank the ship, att_get_combat() will report
+     * "not in the same sector", and proceed to clobber *com.  See
+     * also the workaround in boar().
+     */
     att_get_combat(com, com->own != player->cnum);
 }
 
@@ -436,9 +440,6 @@ int
 att_abort(int combat_mode, struct combat *off, struct combat *def)
 {
     struct sctstr sect;
-    int rel;
-    char y_or_n[512];
-    struct natstr *natp;
 
     if (player->aborted)
        return 1;
@@ -514,23 +515,7 @@ att_abort(int combat_mode, struct combat *off, struct combat *def)
        setcont(player->cnum, def->own, FOUND_SPY);
        setcont(def->own, player->cnum, FOUND_SPY);
     }
-    if (opt_SLOW_WAR && def->own != player->cnum) {
-       natp = getnatp(player->cnum);
-       rel = getrel(natp, def->own);
 
-       if (rel == ALLIED) {
-           sprintf(y_or_n, "Sector is owned by %s, your ally, %s [yn]? ",
-                   cname(def->own), att_mode[combat_mode]);
-           if (!confirm(y_or_n))
-               return abort_attack();
-
-       }
-       if ((rel != AT_WAR) && (def->own) &&
-           (sect.sct_oldown != player->cnum)) {
-           pr("You're not at war with them!\n");
-           return abort_attack();
-       }
-    }
     return 0;
 }
 
@@ -570,8 +555,8 @@ board_abort(struct combat *off, struct combat *def)
            pr("Victim ship moves faster than you do!\n");
            if (def->own)
                wu(0, def->own,
-                  "%s (#%d) %s failed to catch %s\n",
-                  cname(aship.shp_own), aship.shp_own,
+                  "%s %s failed to catch %s\n",
+                  prnatid(aship.shp_own),
                   pr_com(0, off, def->own), pr_com(0, def, def->own));
            return abort_attack();
        }
@@ -659,14 +644,10 @@ att_approach(struct combat *off, struct combat *def)
 
 /* The attack is valid.  Tell the attacker about what they're going to hit */
 
-int
+void
 att_show(struct combat *def)
 {
-    /* Note that we tell the player about the treaty BEFORE we tell them
-       about the item.  If we didn't, then they gain free information */
     if (def->type == EF_SECTOR) {
-       if (!trechk(player->cnum, def->own, LANATT))
-           return abort_attack();
        pr("%s is a %d%% %s %s with approximately %d military.\n",
           xyas(def->x, def->y, player->cnum),
           roundintby((int)def->eff, 10),
@@ -675,17 +656,10 @@ att_show(struct combat *def)
        if (map_set(player->cnum, def->x, def->y, def->sct_dcp->d_mnem, 0))
            writemap(player->cnum);
     } else if (def->type == EF_SHIP || def->type == EF_LAND) {
-       if (def->type == EF_SHIP) {
-           if (!trechk(player->cnum, def->own, SEAATT))
-               return abort_attack();
-       } else {
-           if (!trechk(player->cnum, def->own, LNDATT))
-               return abort_attack();
-       }
-       pr("%s is about %d%% efficient and has approximately %d mil on board.\n", prcom(0, def), roundintby((int)def->eff, 10), roundintby(def->troops, 10));
+       pr("%s is about %d%% efficient and has approximately %d mil on board.\n",
+          prcom(0, def), roundintby((int)def->eff, 10),
+          roundintby(def->troops, 10));
     }
-    /* Ok, everything is fine */
-    return 0;
 }
 
 /* Attack and assault ask the user which kind of support they want */
@@ -709,29 +683,29 @@ att_ask_support(int offset, int *fortp, int *shipp, int *landp,
        *fortp = *shipp = 0;
        *landp = *planep = 0;
 
-       if (!(p = getstarg(player->argp[offset], "Use fort support? ",
-                          buf)))
+       p = getstarg(player->argp[offset], "Use fort support? ", buf);
+       if (!p)
            return RET_SYN;
 
        if ((*p == 'y') || (*p == 'Y'))
            *fortp = 1;
 
-       if (!(p = getstarg(player->argp[offset + 1], "Use ship support? ",
-                          buf)))
+       p = getstarg(player->argp[offset + 1], "Use ship support? ", buf);
+       if (!p)
            return RET_SYN;
 
        if ((*p == 'y') || (*p == 'Y'))
            *shipp = 1;
 
-       if (!(p = getstarg(player->argp[offset + 2], "Use land support? ",
-                          buf)))
+       p = getstarg(player->argp[offset + 2], "Use land support? ", buf);
+       if (!p)
            return RET_SYN;
 
        if ((*p == 'y') || (*p == 'Y'))
            *landp = 1;
 
-       if (!(p = getstarg(player->argp[offset + 3], "Use plane support? ",
-                          buf)))
+       p = getstarg(player->argp[offset + 3], "Use plane support? ", buf);
+       if (!p)
            return RET_SYN;
 
        if ((*p == 'y') || (*p == 'Y'))
@@ -771,8 +745,8 @@ att_ask_offense(int combat_mode, struct combat *off, struct combat *def,
 }
 
 /*
- * Return path cost for ATTACKER to enter sector given by DEF.
- * MOBTYPE is a mobility type accepted by sector_mcost().
+ * Return path cost for @attacker to enter sector given by @def.
+ * @mobtype is a mobility type accepted by sector_mcost().
  */
 static double
 att_mobcost(natid attacker, struct combat *def, int mobtype)
@@ -882,7 +856,7 @@ calc_mobcost(int combat_mode, struct combat *off, struct combat *def,
        case EF_SHIP:
            /* the 2 in the formula below is a fudge factor */
            getship(def->shp_uid, &ship);
-           off->mobcost += (def->eff / 100) * (shp_speed(&ship) / 2);
+           off->mobcost += shp_speed(&ship) / 2 * def->eff / 100;
        }
     }
 }
@@ -912,7 +886,7 @@ ask_off(int combat_mode, struct combat *off, struct combat *def)
        sprintf(prompt, "Number of mil from %s (max %d) : ",
                prcom(0, off), mob_support);
     }
-    if ((attacking_mil = onearg(0, prompt)) < 0)
+    if ((attacking_mil = onearg(NULL, prompt)) < 0)
        abort_attack();
     if (att_abort(combat_mode, off, def))
        return 0;
@@ -966,7 +940,8 @@ ask_olist(int combat_mode, struct combat *off, struct combat *def,
 {
     struct nstr_item ni;
     struct lndstr land;
-    double mobcost;
+    double pathcost, mobcost;
+    int reqmob;
     struct ulist *llp;
     struct lchrstr *lcp;
     double att_val;
@@ -984,7 +959,7 @@ ask_olist(int combat_mode, struct combat *off, struct combat *def,
     while (nxtitem(&ni, &land)) {
        if (land.lnd_own != player->cnum)
            continue;
-       if (land.lnd_effic < LAND_MINEFF)
+       if (!land.lnd_own)
            continue;
        if (land_answer[(int)land.lnd_army] == 'N')
            continue;
@@ -1040,17 +1015,18 @@ ask_olist(int combat_mode, struct combat *off, struct combat *def,
             * of high-mobility sectors (mountains): for those we
             * still require attack mobility.
             */
-           mobcost = att_mobcost(off->own, def, lnd_mobtype(&land));
-           if (mobcost < 1.0) {
+           pathcost = att_mobcost(land.lnd_own, def, lnd_mobtype(&land));
+           mobcost = lnd_pathcost(&land, pathcost);
+           if (pathcost < 1.0) {
                if (land.lnd_mobil <= 0) {
                    pr("%s is out of mobility\n", prland(&land));
                    continue;
                }
            } else {
-               mobcost = lnd_pathcost(&land, mobcost);
-               if (land.lnd_mobil < mobcost) {
+               reqmob = MIN(land_mob_max, (int)ceil(mobcost));
+               if (land.lnd_mobil < reqmob) {
                    pr("%s does not have enough mobility (%d needed)\n",
-                      prland(&land), (int)ceil(mobcost));
+                      prland(&land), reqmob);
                    continue;
                }
            }
@@ -1066,13 +1042,18 @@ ask_olist(int combat_mode, struct combat *off, struct combat *def,
            return;
        }
        att_val = attack_val(combat_mode, &land);
-       if (att_val < 1.0) {
+       /*
+        * We need to let spies assault even though they have no
+        * offensive strength, because assault is how they sneak
+        * ashore.  If this assault turns out to be a fight, they'll
+        * be removed by get_ototal().
+        */
+       if (att_val < 1.0
+           && !(combat_mode == A_ASSAULT && (lcp->l_flags & L_SPY))) {
            pr("%s has no offensive strength\n", prland(&land));
            continue;
        }
-       resupply_all(&land);
-       putland(land.lnd_uid, &land);
-       if (!has_supply(&land)) {
+       if (!lnd_supply_all(&land)) {
            pr("%s is out of supply, and cannot %s\n",
               prland(&land), att_mode[combat_mode]);
            continue;
@@ -1099,19 +1080,15 @@ ask_olist(int combat_mode, struct combat *off, struct combat *def,
                land_answer[(int)land.lnd_army] != 'Y')
                continue;
        }
-       if (!(llp = malloc(sizeof(struct ulist)))) {
-           logerror("Malloc failed in attack!\n");
-           abort_attack();
-           return;
-       }
-       memset(llp, 0, sizeof(struct ulist));
-       emp_insque(&llp->queue, olist);
+       llp = lnd_insque(&land, olist);
+       llp->supplied = 1;
        llp->mobil = mobcost;
-       if (!get_land(combat_mode, def, land.lnd_uid, llp, 0))
-           continue;
+       llp->x = llp->unit.land.lnd_x;
+       llp->y = llp->unit.land.lnd_y;
+       llp->eff = llp->unit.land.lnd_effic;
        if (lnd_spyval(&land) > *a_spyp)
            *a_spyp = lnd_spyval(&land);
-       if (((struct lchrstr *)llp->chrp)->l_flags & L_ENGINEER)
+       if (lchr[land.lnd_type].l_flags & L_ENGINEER)
            ++*a_engineerp;
        if (def->type == EF_SHIP && ++count >= maxland)
            break;
@@ -1220,22 +1197,20 @@ get_dlist(struct combat *def, struct emp_qelem *list, int a_spy,
            continue;
        if (def->type == EF_SECTOR && land.lnd_land >= 0)
            continue;
+       if (def->type == EF_SECTOR && (lchr[land.lnd_type].l_flags & L_SPY))
+           continue;
        if (def->type == EF_SHIP && land.lnd_ship != def->shp_uid)
            continue;
        if (def->type == EF_LAND && land.lnd_land != def->lnd_uid)
            continue;
        intelligence_report(player->cnum, &land, a_spy,
                            "Scouts report defending unit:");
-       if (!(llp = malloc(sizeof(struct ulist)))) {
-           logerror("Malloc failed in attack!\n");
-           abort_attack();
-           return;
-       }
-       memset(llp, 0, sizeof(struct ulist));
-       emp_insque(&llp->queue, list);
-       llp->supplied = has_supply(&land);
-       if (!get_land(A_DEFEND, def, land.lnd_uid, llp, 1))
-           continue;
+       llp = lnd_insque(&land, list);
+       llp->supplied = lnd_supply_all(&land);
+       llp->mobil = 0.0;
+       llp->x = llp->unit.land.lnd_x;
+       llp->y = llp->unit.land.lnd_y;
+       llp->eff = llp->unit.land.lnd_effic;
        if (lnd_spyval(&land) > *d_spyp)
            *d_spyp = lnd_spyval(&land);
     }
@@ -1251,6 +1226,7 @@ get_ototal(int combat_mode, struct combat *off, struct emp_qelem *olist,
     struct emp_qelem *qp, *next;
     struct ulist *llp;
     int n, w;
+    double att_val;
 
     /*
      * first, total the attacking mil
@@ -1271,8 +1247,20 @@ get_ototal(int combat_mode, struct combat *off, struct emp_qelem *olist,
     for (qp = olist->q_forw; qp != olist; qp = next) {
        next = qp->q_forw;
        llp = (struct ulist *)qp;
-       if (check && !get_land(combat_mode, 0, llp->unit.land.lnd_uid, llp, 0))
+       if (check && !get_oland(combat_mode, llp))
+           continue;
+       att_val = attack_val(combat_mode, &llp->unit.land);
+       if (check && att_val < 1.0) {
+           /*
+            * No offensive strength, and fighting hasn't even begun.
+            * Since ask_olist() doesn't offer such land units, except
+            * for spies sometimes, it's either a spy, or the strength
+            * must have been destroyed since then.  Leave it behind.
+            */
+           lnd_print(player->cnum, llp, "has no offensive strength");
+           lnd_put_one(llp);
            continue;
+       }
        if (combat_mode == A_ATTACK) {
            w = -1;
            for (n = 0; n <= off->last; ++n) {
@@ -1283,14 +1271,14 @@ get_ototal(int combat_mode, struct combat *off, struct emp_qelem *olist,
                    w = n;
            }
            if (w < 0) {
-               lnd_delete(llp, "is in a sector not owned by you");
+               lnd_print(player->cnum, llp,
+                         "can't attack from this sector now");
+               lnd_put_one(llp);
                continue;
            }
-           ototal += attack_val(combat_mode, &llp->unit.land) *
-               att_combat_eff(off + w);
-       } else {
-           ototal += attack_val(combat_mode, &llp->unit.land);
+           att_val *= att_combat_eff(off + w);
        }
+       ototal += att_val;
     }
     ototal *= osupport;
 
@@ -1320,7 +1308,7 @@ get_dtotal(struct combat *def, struct emp_qelem *list, double dsupport,
     for (qp = list->q_forw; qp != list; qp = next) {
        next = qp->q_forw;
        llp = (struct ulist *)qp;
-       if (check && !get_land(A_DEFEND, def, llp->unit.land.lnd_uid, llp, 1))
+       if (check && !get_dland(def, llp))
            continue;
        d_unit = defense_val(&llp->unit.land);
        if (!llp->supplied)
@@ -1334,69 +1322,65 @@ get_dtotal(struct combat *def, struct emp_qelem *list, double dsupport,
 }
 
 /*
- * This is the land unit integrity check.  Note that we don't print
- * warnings about victim land units because the attacker may not have seen them
+ * This is the land unit integrity check.
  */
 
 static int
-get_land(int combat_mode, struct combat *def, int uid, struct ulist *llp,
-        int victim_land)
+get_oland(int combat_mode, struct ulist *llp)
 {
     struct lndstr *lp = &llp->unit.land;
     char buf[512];
 
-    getland(uid, lp);
+    getland(llp->unit.land.lnd_uid, lp);
 
-    if (!llp->chrp) {          /* first time */
-       llp->x = llp->unit.land.lnd_x;
-       llp->y = llp->unit.land.lnd_y;
-       llp->chrp = (struct empobj_chr *)&lchr[(int)llp->unit.land.lnd_type];
-    } else {                   /* not first time */
-       if (lp->lnd_effic < LAND_MINEFF) {
-           sprintf(buf, "was destroyed and is no longer a part of the %s",
-                   att_mode[combat_mode]);
-           lnd_delete(llp, buf);
-           return 0;
-       }
-       if (victim_land) {
-           if (lp->lnd_x != def->x || lp->lnd_y != def->y) {
-               lnd_delete(llp,
-                          "left to go fight another battle and is no longer a part of the defense");
-               return 0;
-           }
-       } else {
-           if (lp->lnd_own != player->cnum) {
-               sprintf(buf,
-                       "was destroyed and is no longer a part of the %s",
-                       att_mode[combat_mode]);
-               lnd_delete(llp, buf);
-               return 0;
-           }
-           if (lp->lnd_x != llp->x || lp->lnd_y != llp->y) {
-               sprintf(buf,
-                       "left to fight another battle and is no longer a part of the %s",
-                       att_mode[combat_mode]);
-               lnd_delete(llp, buf);
-               return 0;
-           }
-           if (lp->lnd_effic < llp->eff) {
-               sprintf(buf, "damaged from %d%% to %d%%",
-                       llp->eff, lp->lnd_effic);
-               lnd_print(llp, buf);
-           }
-       }
+    if (lp->lnd_own != player->cnum) {
+       sprintf(buf, "was destroyed and is no longer a part of the %s",
+               att_mode[combat_mode]);
+       lnd_print(player->cnum, llp, buf);
+       lnd_put_one(llp);
+       return 0;
+    }
+    if (lp->lnd_x != llp->x || lp->lnd_y != llp->y) {
+       sprintf(buf,
+               "left to fight another battle and is no longer a part of the %s",
+               att_mode[combat_mode]);
+       lnd_print(player->cnum, llp, buf);
+       lnd_put_one(llp);
+       return 0;
+    }
+    if (lp->lnd_effic < llp->eff) {
+       sprintf(buf, "damaged from %d%% to %d%%",
+               llp->eff, lp->lnd_effic);
+       lnd_print(player->cnum, llp, buf);
     }
-    llp->eff = llp->unit.land.lnd_effic;
 
+    llp->eff = llp->unit.land.lnd_effic;
     return 1;
 }
 
-/*
- * Put the land unit on the disk.  If there was some mobility cost, then
- * subtract it from the units mobility.  Note that this works the same way
- * as sectors & ships in that no mobility is actually taken until the attacker
- * has committed to attacking.
- */
+static int
+get_dland(struct combat *def, struct ulist *llp)
+{
+    struct lndstr *lp = &llp->unit.land;
+
+    getland(llp->unit.land.lnd_uid, lp);
+
+    if (lp->lnd_own != def->own) {
+       lnd_print(llp->unit.land.lnd_own, llp,
+                 "was destroyed and is no longer a part of the defense");
+       lnd_put_one(llp);
+       return 0;
+    }
+    if (lp->lnd_x != def->x || lp->lnd_y != def->y) {
+       lnd_print(llp->unit.land.lnd_own, llp,
+                 "left to go fight another battle and is no longer a part of the defense");
+       lnd_put_one(llp);
+       return 0;
+    }
+
+    llp->eff = llp->unit.land.lnd_effic;
+    return 1;
+}
 
 static void
 kill_land(struct emp_qelem *list)
@@ -1409,7 +1393,9 @@ kill_land(struct emp_qelem *list)
        llp = (struct ulist *)qp;
        if (llp->unit.land.lnd_ship >= 0) {
            llp->unit.land.lnd_effic = 0;
-           lnd_delete(llp, "cannot return to the ship, and dies!");
+           lnd_print(player->cnum, llp,
+                     "cannot return to the ship, and dies!");
+           lnd_put_one(llp);
        }
     }
 }
@@ -1430,8 +1416,15 @@ att_infect_units(struct emp_qelem *list, int plague)
     }
 }
 
+/*
+ * Put the land unit on the disk.  If there was some mobility cost, then
+ * subtract it from the units mobility.  Note that this works the same way
+ * as sectors & ships in that no mobility is actually taken until the attacker
+ * has committed to attacking.
+ */
+
 static void
-put_land(struct emp_qelem *list)
+put_oland(struct emp_qelem *list)
 {
     struct emp_qelem *qp, *next;
     struct ulist *llp;
@@ -1445,10 +1438,10 @@ put_land(struct emp_qelem *list)
        llp->mobil = 0.0;
        putland(llp->unit.land.lnd_uid, &llp->unit.land);
        if (llp->unit.land.lnd_own != player->cnum) {
-           emp_remque((struct emp_qelem *)llp);
+           emp_remque(&llp->queue);
            free(llp);
        } else
-           get_land(A_ATTACK, 0, llp->unit.land.lnd_uid, llp, 0);
+           get_oland(A_ATTACK, llp);
     }
 }
 
@@ -1463,17 +1456,13 @@ att_reacting_units(struct combat *def, struct emp_qelem *list, int a_spy,
 {
     struct nstr_item ni;
     struct lndstr land;
-    struct sctstr sect, dsect;
     struct ulist *llp;
     int dtotal;
     double new_land = 0;
     double mobcost;
     double pathcost;
-    int dist;
-    int radius;
     int origx, origy;
     double eff = att_combat_eff(def);
-    char buf[1024];
 
     if (list)
        dtotal = get_dtotal(def, list, 1.0, 1);
@@ -1483,7 +1472,7 @@ att_reacting_units(struct combat *def, struct emp_qelem *list, int a_spy,
     while (nxtitem(&ni, &land) && dtotal + new_land * eff < 1.2 * ototal) {
        if (!land.lnd_own)
            continue;
-       if (!land.lnd_rad_max)
+       if (land.lnd_mission != MI_RESERVE)
            continue;
        if ((land.lnd_x == def->x) && (land.lnd_y == def->y))
            continue;
@@ -1499,26 +1488,16 @@ att_reacting_units(struct combat *def, struct emp_qelem *list, int a_spy,
            continue;
 
        /* Only supplied units can react */
-       if (!has_supply(&land))
+       if (list ? !lnd_supply_all(&land) : !lnd_could_be_supplied(&land))
            continue;
 
-       dist = mapdist(land.lnd_x, land.lnd_y, def->x, def->y);
-
-       getsect(land.lnd_x, land.lnd_y, &sect);
-       /* Units on efficient headquarters can react 1 farther */
-       if ((sect.sct_type == SCT_HEADQ) && (sect.sct_effic >= 60))
-           radius = land.lnd_rad_max + 1;
-       else
-           radius = land.lnd_rad_max;
-
-       if (dist > radius)
+       if (!in_oparea((struct empobj *)&land, def->x, def->y))
            continue;
 
-       getsect(def->x, def->y, &dsect);
-       if (!BestLandPath(buf, &sect, &dsect, &pathcost,
-                         lnd_mobtype(&land)))
+       pathcost = path_find(land.lnd_x, land.lnd_y, def->x, def->y,
+                           def->own, lnd_mobtype(&land));
+       if (pathcost < 0)
            continue;
-
        mobcost = lnd_pathcost(&land, pathcost);
        if (land.lnd_mobil < mobcost)
            continue;
@@ -1538,15 +1517,12 @@ att_reacting_units(struct combat *def, struct emp_qelem *list, int a_spy,
        wu(0, land.lnd_own, "%s reacts to %s.\n",
           prland(&land), xyas(land.lnd_x, land.lnd_y, land.lnd_own));
 
-       llp = malloc(sizeof(struct ulist));
-
-       memset(llp, 0, sizeof(struct ulist));
+       llp = lnd_insque(&land, list);
        llp->supplied = 1;
+       llp->mobil = 0.0;
        llp->x = origx;
        llp->y = origy;
-       llp->chrp = (struct empobj_chr *)&lchr[(int)land.lnd_type];
-       llp->unit.land = land;
-       emp_insque(&llp->queue, list);
+       llp->eff = land.lnd_effic;
        if (lnd_spyval(&land) > *d_spyp)
            *d_spyp = lnd_spyval(&land);
 
@@ -1590,8 +1566,7 @@ get_osupport(char *outs, struct combat *def, int fort_sup, int ship_sup,
        ap = dam / 100.0;
        osupport += ap;
     }
-    sprintf(outs, "attacker\t%1.2f\t%1.2f\t%1.2f\t%1.2f\n", af, as, au,
-           ap);
+    sprintf(outs, "attacker\t%1.2f\t%1.2f\t%1.2f\t%1.2f", af, as, au, ap);
     return osupport;
 }
 
@@ -1638,8 +1613,8 @@ get_dsupport(char *outs, struct emp_qelem *list, struct combat *def,
     if (good)
        *outs = '\0';
     else
-       sprintf(outs, "defender\t%1.2f\t%1.2f\t%1.2f\t%1.2f\n\n", df, ds,
-               du, dp);
+       sprintf(outs, "defender\t%1.2f\t%1.2f\t%1.2f\t%1.2f",
+               df, ds, du, dp);
     if (def->own) {
        if (good < 0)
            wu(0, def->own,
@@ -1665,7 +1640,8 @@ get_mine_dsupport(struct combat *def, int a_engineer)
     getsect(def->x, def->y, &sect);
 
     if (sect.sct_oldown != player->cnum) {
-       mines = MIN(sect.sct_mines, 20);
+       mines = SCT_LANDMINES(&sect);
+       mines = MIN(mines, 20);
        if (a_engineer)
            mines = ldround(mines / 2.0, 1);
        if (mines > 0) {
@@ -1722,16 +1698,18 @@ att_get_support(int combat_mode, int ofort, int oship, int oland,
        pr("\n\t\tsupport values\n");
        pr("\t\tforts\tships\tunits\tplanes\n");
        if (*osupportp != 1.0)
-           pr("%s", osupports);
+           pr("%s\n", osupports);
        if (*dsupportp != 1.0)
-           pr("%s", dsupports);
+           pr("%s\n", dsupports);
+       pr("\n");
        if (def->own) {
            wu(0, def->own, "\n\t\tsupport values\n");
            wu(0, def->own, "\t\tforts\tships\tunits\tplanes\n");
            if (*osupportp != 1.0)
-               wu(0, def->own, "%s", osupports);
+               wu(0, def->own, "%s\n", osupports);
            if (*dsupportp != 1.0)
-               wu(0, def->own, "%s", dsupports);
+               wu(0, def->own, "%s\n", dsupports);
+           wu(0, def->own, "\n");
        }
     }
 
@@ -1853,12 +1831,12 @@ att_fight(int combat_mode, struct combat *off, struct emp_qelem *olist,
      * since a single dead guy normally wouldn't cause a commander to
      * rethink his strategies, but 50 dead guys might.
      */
-    odds += (random() % 11 - 5) / 100.0;
+    odds += (roll(11) - 6) / 100.0;
     if (odds < 0.0)
        odds = 0.1;
     if (odds > 1.0)
        odds = 1.0;
-    recalctime = 8 + (random() % 43);
+    recalctime = 7 + roll(43);
     while (!success && ototal) {
        if (chance(odds)) {
            pr("!");
@@ -1874,9 +1852,9 @@ att_fight(int combat_mode, struct combat *off, struct emp_qelem *olist,
        if (((a_cas + d_cas) % 70) == 69)
            pr("\n");
        if (recalctime-- <= 0) {
-           recalctime = 8 + (random() % 43);
+           recalctime = 7 + roll(43);
            odds = att_calcodds(ototal, dtotal);
-           odds += (random() % 11 - 5) / 100.0;
+           odds += (roll(11) - 6) / 100.0;
            if (odds < 0.0)
                odds = 0.1;
            if (odds > 1.0)
@@ -1924,7 +1902,7 @@ att_fight(int combat_mode, struct combat *off, struct emp_qelem *olist,
 
     pr("- Casualties -\n     Yours: %d\n", a_cas);
     pr("    Theirs: %d\n", d_cas);
-    pr("Papershuffling ... %.1f B.T.U\n", (d_cas + a_cas) * 0.15);
+    pr("Paper-shuffling ... %.1f BTU\n", (d_cas + a_cas) * 0.15);
     player->btused += (int)((d_cas + a_cas) * 0.015 + 0.5);
 
     if (success) {
@@ -1998,18 +1976,18 @@ att_fight(int combat_mode, struct combat *off, struct emp_qelem *olist,
     nreport(player->cnum, news_item, def->own, 1);
     if (def->own) {
        wu(0, def->own,
-          "%s (#%d) lost %d troops %s %s\nWe lost %d troops defending\n",
-          cname(player->cnum), player->cnum, a_cas,
+          "%s lost %d troops %s %s\nWe lost %d troops defending\n",
+          prnatid(player->cnum), a_cas,
           action, pr_com(0, def, def->own), d_cas);
     }
 
     send_reacting_units_home(dlist);
 
     /* putland the defending land */
-    unit_put(dlist, 0);
+    lnd_put(dlist);
 
     /* putland the attacking land */
-    put_land(olist);
+    put_oland(olist);
 
     /* put the victim sector/ship/land */
     if (!success || !take_def(combat_mode, olist, off, def))
@@ -2066,8 +2044,8 @@ take_casualty(int combat_mode, struct combat *off, struct emp_qelem *olist)
     int to_take = CASUALTY_LUMP;
     int biggest_troops = 0, index = -1;
     int n, tot_troops = 0, biggest_mil, cas;
-    struct emp_qelem *qp, *biggest;
-    struct ulist *llp;
+    struct emp_qelem *qp;
+    struct ulist *llp, *biggest;
 
     for (n = 0; n <= off->last; ++n) {
        if (off[n].type != EF_BAD) {
@@ -2128,14 +2106,13 @@ take_casualty(int combat_mode, struct combat *off, struct emp_qelem *olist)
 
        if (llp->unit.land.lnd_item[I_MILIT] > biggest_mil) {
            biggest_mil = llp->unit.land.lnd_item[I_MILIT];
-           biggest = qp;
+           biggest = llp;
        }
     }
     if (biggest == NULL)
        return CASUALTY_LUMP - to_take;
 
-    llp = (struct ulist *)biggest;
-    cas = lnd_take_casualty(combat_mode, llp, to_take);
+    cas = lnd_take_casualty(combat_mode, biggest, to_take);
     return CASUALTY_LUMP - (to_take - cas);
 }
 
@@ -2157,7 +2134,8 @@ send_reacting_units_home(struct emp_qelem *list)
                    xyas(llp->x, llp->y, llp->unit.land.lnd_own));
            llp->unit.land.lnd_x = llp->x;
            llp->unit.land.lnd_y = llp->y;
-           lnd_delete(llp, buf);
+           lnd_print(llp->unit.land.lnd_own, llp, buf);
+           lnd_put_one(llp);
        }
     }
 }
@@ -2170,8 +2148,8 @@ att_empty_attack(int combat_mode, int ototal, struct combat *def)
     if (ototal <= 0) {
        if (def->own && player->cnum != def->own) {
            wu(0, def->own,
-              "%s (#%d) considered %sing you @%s\n",
-              cname(player->cnum), player->cnum,
+              "%s considered %sing you @%s\n",
+              prnatid(player->cnum),
               att_mode[combat_mode], xyas(def->x, def->y, def->own));
        }
        pr("No troops for %s...\n", att_mode[combat_mode]);
@@ -2191,7 +2169,7 @@ take_def(int combat_mode, struct emp_qelem *list, struct combat *off,
 {
     int n;
     int occuppied = 0;
-    struct ulist *llp, *delete_me = 0;
+    struct ulist *llp, *delete_me = NULL;
     char buf[1024];
     struct sctstr sect;
     struct shpstr ship;
@@ -2230,12 +2208,14 @@ take_def(int combat_mode, struct emp_qelem *list, struct combat *off,
            if (def->type == EF_SHIP) {
                llp->unit.land.lnd_ship = def->shp_uid;
                sprintf(buf, "boards %s", prcom(0, def));
+               lnd_print(player->cnum, llp, buf);
                delete_me = llp;
            } else {
                llp->unit.land.lnd_ship = -1;
                sprintf(buf, "moves in to occupy %s",
                        xyas(def->x, def->y, player->cnum));
-               lnd_delete(llp, buf);
+               lnd_print(player->cnum, llp, buf);
+               lnd_put_one(llp);
            }
        }
     }
@@ -2257,7 +2237,7 @@ take_def(int combat_mode, struct emp_qelem *list, struct combat *off,
        putland(land.lnd_uid, &land);
     }
     if (delete_me)
-       lnd_delete(delete_me, buf);
+       lnd_put_one(delete_me);
     att_get_combat(def, 0);
     return 1;
 }
@@ -2298,9 +2278,9 @@ ask_move_in(struct combat *off, struct emp_qelem *olist,
            *answerp = 'N';
        if (*answerp == 'Y')
            continue;
+       if (!get_oland(A_ATTACK, llp))
+           continue;
        if (*answerp != 'N') {
-           if (!get_land(A_ATTACK, def, llp->unit.land.lnd_uid, llp, 0))
-               continue;
            sprintf(prompt, "Move in with %s (%c %d%%) [ynYNq?] ",
                    prland(&llp->unit.land),
                    llp->unit.land.lnd_army ? llp->unit.land.lnd_army : '~',
@@ -2308,7 +2288,7 @@ ask_move_in(struct combat *off, struct emp_qelem *olist,
            *answerp = att_prompt(prompt, llp->unit.land.lnd_army);
            if (player->aborted || att_get_combat(def, 0) < 0)
                *answerp = 'N';
-           if (!get_land(A_ATTACK, def, llp->unit.land.lnd_uid, llp, 0))
+           if (!get_oland(A_ATTACK, llp))
                continue;
        }
        if (*answerp == 'y' || *answerp == 'Y')
@@ -2316,7 +2296,8 @@ ask_move_in(struct combat *off, struct emp_qelem *olist,
        sprintf(buf, "stays in %s",
                xyas(llp->unit.land.lnd_x, llp->unit.land.lnd_y,
                     player->cnum));
-       lnd_delete(llp, buf);
+       lnd_print(player->cnum, llp, buf);
+       lnd_put_one(llp);
     }
     if (QEMPTY(olist))
        return;
@@ -2324,12 +2305,13 @@ ask_move_in(struct combat *off, struct emp_qelem *olist,
        for (qp = olist->q_forw; qp != olist; qp = next) {
            next = qp->q_forw;
            llp = (struct ulist *)qp;
-           if (!get_land(A_ATTACK, def, llp->unit.land.lnd_uid, llp, 0))
+           if (!get_oland(A_ATTACK, llp))
                continue;
            sprintf(buf, "stays in %s",
                    xyas(llp->unit.land.lnd_x, llp->unit.land.lnd_y,
                         player->cnum));
-           lnd_delete(llp, buf);
+           lnd_print(player->cnum, llp, buf);
+           lnd_put_one(llp);
        }
        return;
     }
@@ -2338,22 +2320,17 @@ ask_move_in(struct combat *off, struct emp_qelem *olist,
     move_in_land(A_ATTACK, off, olist, def);
 }
 
-/* Move offensive land units to the conquered sector or ship */
-
-static void
-move_in_land(int combat_mode, struct combat *off, struct emp_qelem *olist,
-            struct combat *def)
+void
+att_move_land(int combat_mode, struct combat *off, struct emp_qelem *olist,
+             struct combat *def)
 {
     struct emp_qelem *qp, *next;
     struct ulist *llp;
-    char buf[512];
 
-    if (QEMPTY(olist))
-       return;
     for (qp = olist->q_forw; qp != olist; qp = next) {
        next = qp->q_forw;
        llp = (struct ulist *)qp;
-       if (!get_land(combat_mode, def, llp->unit.land.lnd_uid, llp, 0))
+       if (!get_oland(combat_mode, llp))
            continue;
        take_move_in_mob(combat_mode, llp, off, def);
        llp->unit.land.lnd_x = def->x;
@@ -2363,27 +2340,37 @@ move_in_land(int combat_mode, struct combat *off, struct emp_qelem *olist,
        else
            llp->unit.land.lnd_ship = -1;
     }
-    if (QEMPTY(olist))
-       return;
+}
+
+/* Move offensive land units to the conquered sector or ship */
+
+static void
+move_in_land(int combat_mode, struct combat *off, struct emp_qelem *olist,
+            struct combat *def)
+{
+    struct emp_qelem *qp, *next;
+    struct ulist *llp;
+    char buf[512];
+
+    att_move_land(combat_mode, off, olist, def);
+
     if (def->type == EF_SECTOR) {
        if (opt_INTERDICT_ATT) {
-           lnd_sweep(olist, 0, 0, def->own);
+           lnd_sweep(olist, 0, 0, player->cnum);
            lnd_check_mines(olist);
        }
        sprintf(buf, "now occupies %s", prcom(0, def));
     } else {
        sprintf(buf, "boards %s", prcom(0, def));
     }
-    if (QEMPTY(olist))
-       return;
+
     for (qp = olist->q_forw; qp != olist; qp = next) {
        next = qp->q_forw;
        llp = (struct ulist *)qp;
-       lnd_print(llp, buf);
+       lnd_print(player->cnum, llp, buf);
     }
-    if (QEMPTY(olist))
-       return;
-    unit_put(olist, 0);
+
+    lnd_put(olist);
 }
 
 /*
@@ -2460,7 +2447,8 @@ ask_move_in_off(struct combat *off, struct combat *def)
        return;
     sprintf(prompt, "How many mil to move in from %s (%d max)? ",
            xyas(off->x, off->y, player->cnum), mob_support);
-    if (!(p = getstring(prompt, buf)) || !*p || (num_mil = atoi(p)) <= 0)
+    p = getstring(prompt, buf);
+    if (!p || !*p || (num_mil = atoi(p)) <= 0)
        return;
 /* Make sure we don't move in more than we can support mobility-wise */
     if (num_mil > mob_support)
@@ -2492,7 +2480,10 @@ ask_move_in_off(struct combat *off, struct combat *def)
        left = commdamage(num_mil, dam, I_MILIT);
        if (left < num_mil) {
            if (left) {
-               pr("%d of the mil you were moving were destroyed!\nOnly %d mil made it to %s\n", num_mil - left, left, xyas(def->x, def->y, player->cnum));
+               pr("%d of the mil you were moving were destroyed!\n"
+                  "Only %d mil made it to %s\n",
+                  num_mil - left, left,
+                  xyas(def->x, def->y, player->cnum));
            } else {
                pr("All of the mil you were moving were destroyed!\n");
            }
@@ -2519,7 +2510,7 @@ take_move_in_mob(int combat_mode, struct ulist *llp, struct combat *off,
     switch (combat_mode) {
     case A_ATTACK:
        mobcost = lnd_pathcost(&llp->unit.land,
-                              att_mobcost(off->own, def,
+                              att_mobcost(llp->unit.land.lnd_own, def,
                                           lnd_mobtype(&llp->unit.land)));
        break;
     case A_ASSAULT:
@@ -2527,7 +2518,7 @@ take_move_in_mob(int combat_mode, struct ulist *llp, struct combat *off,
         * Set mobcost to basic assault cost, moblim to maximum
         * mobility to keep when assaulting from non-landing ship
         */
-       if (((struct lchrstr *)llp->chrp)->l_flags & L_MARINE) {
+       if (lchr[llp->unit.land.lnd_type].l_flags & L_MARINE) {
            mobcost = gain / 2.0;
            moblim = 0;
        } else {
@@ -2539,7 +2530,7 @@ take_move_in_mob(int combat_mode, struct ulist *llp, struct combat *off,
            mobcost = MAX(mobcost, mob - moblim);
        break;
     case A_BOARD:
-       if (((struct lchrstr *)llp->chrp)->l_flags & L_MARINE)
+       if (lchr[llp->unit.land.lnd_type].l_flags & L_MARINE)
            mobcost = 10;
        else
            mobcost = 40;
@@ -2594,9 +2585,5 @@ sector_strength(struct sctstr *sp)
     double base = sp->sct_type == SCT_MOUNT ? 2.0 : 1.0;
     double d = base + (dchr[sp->sct_type].d_dstr - base) * def;
 
-    if (d > dchr[sp->sct_type].d_dstr)
-       d = dchr[sp->sct_type].d_dstr;
-    if (d < base)
-       d = base;
-    return d;
+    return LIMIT_TO(d, base, dchr[sp->sct_type].d_dstr);
 }