]> git.pond.sub.org Git - empserver/blobdiff - src/lib/commands/tend.c
commands: Rename the command functions
[empserver] / src / lib / commands / tend.c
index db3618fedb391e0b2dd871a0c59352395e0a57db..5bce717724129dbddd6a99bc12bc5c1f372948f6 100644 (file)
@@ -1,11 +1,11 @@
 /*
  *  Empire - A multi-player, client/server Internet based war game.
- *  Copyright (C) 1986-2000, 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,
  *  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/>.
  *
  *  ---
  *
- *  See the "LEGAL", "LICENSE", "CREDITS" and "README" files for all the
- *  related information and legal notices. It is expected that any future
- *  projects/authors will amend these files as needed.
+ *  See files README, COPYING and CREDITS in the root of the source
+ *  tree for related information and legal notices.  It is expected
+ *  that future projects/authors will amend these files as needed.
  *
  *  ---
  *
  *  tend.c: Transfer goodies from one ship to another.
- * 
+ *
  *  Known contributors to this file:
  *     Dave Pare, 1986
  *     Thomas Ruschak, 1992
  *     Steve McClure, 2000
+ *     Markus Armbruster, 2004-2017
  */
 
-#include <string.h>
-#include "misc.h"
-#include "player.h"
-#include "var.h"
-#include "xy.h"
-#include "file.h"
-#include "ship.h"
+#include <config.h>
+
+#include "commands.h"
 #include "item.h"
-#include "nsc.h"
-#include "nat.h"
 #include "land.h"
-#include "plane.h"
-#include "nuke.h"
-#include "genitem.h"
-#include "commands.h"
+#include "plague.h"
+#include "ship.h"
 
+static int can_tend_to(struct shpstr *, int, struct shpstr *, int);
+static int tend_comm_to(struct shpstr *, struct ichrstr *, int,
+                       struct shpstr *);
 static void expose_ship(struct shpstr *s1, struct shpstr *s2);
-static int tend_land(struct shpstr *tenderp, s_char *units);
+static int tend_land(struct shpstr *tenderp, int, char *units);
 
 int
-tend(void)
+c_tend(void)
 {
     struct nstr_item targets;
     struct nstr_item tenders;
@@ -63,51 +58,58 @@ tend(void)
     struct mchrstr *vbase;
     int amt;
     int retval;
-    int ontender;
-    int ontarget;
     int maxtender;
-    int maxtarget;
     int transfer;
     int total;
     int type;
-    s_char *p;
-    s_char prompt[512];
-    s_char buf[1024];
+    char *p;
+    char prompt[512];
+    char buf[1024];
 
-    if (!(p = getstarg(player->argp[1],
-                      "Tend what commodity (or 'land')? ", buf)) || !*p)
+    p = getstarg(player->argp[1], "Tend what commodity (or 'land')? ",
+                buf);
+    if (!p || !*p)
        return RET_SYN;
 
     if (!strncmp(p, "land", 4))
        type = EF_LAND;
-    else if (NULL != (ip = whatitem(p, (s_char *)0)))
+    else if (NULL != (ip = item_by_name(p)))
        type = EF_SECTOR;
     else {
-       pr("Bad commodity.\n");
+       pr("Can't tend '%s'\n", p);
        return RET_SYN;
     }
 
-    if (!snxtitem(&tenders, EF_SHIP,
-                 getstarg(player->argp[2], "Tender(s)? ", buf)))
+    if (!snxtitem(&tenders, EF_SHIP, player->argp[2], "Tender(s)? "))
        return RET_SYN;
 
-    while (nxtitem(&tenders, (s_char *)&tender)) {
-       if (!player->owner)
+    while (nxtitem(&tenders, &tender)) {
+       if (!player->owner || !tender.shp_own) {
+           if (tenders.sel == NS_LIST)
+               pr("You don't own ship #%d!\n", tender.shp_uid);
            continue;
+       }
        if (type == EF_LAND) {
            sprintf(prompt, "Land unit(s) to tend from %s? ",
                    prship(&tender));
-           if (!(p = getstarg(player->argp[3], prompt, buf)) || !*p)
+           p = getstarg(player->argp[3], prompt, buf);
+           if (!p)
+               return RET_FAIL;
+           if (!*p)
                continue;
            if (!check_ship_ok(&tender))
                return RET_SYN;
-           if (0 != (retval = tend_land(&tender, p)))
+           retval = tend_land(&tender, tenders.sel == NS_LIST, p);
+           if (retval)
                return retval;
            continue;
        }
        sprintf(prompt, "Number of %s to tend from %s? ",
                ip->i_name, prship(&tender));
-       if (!(p = getstarg(player->argp[3], prompt, buf)) || !*p)
+       p = getstarg(player->argp[3], prompt, buf);
+       if (!p)
+           return RET_FAIL;
+       if (!*p)
            continue;
        if (!check_ship_ok(&tender))
            return RET_SYN;
@@ -115,82 +117,149 @@ tend(void)
            pr("Amount must be non-zero!\n");
            return RET_SYN;
        }
-       ontender = tender.shp_item[ip->i_vtype];
-       if (ontender == 0 && amt > 0) {
+       if (!tender.shp_item[ip->i_uid] && amt > 0) {
            pr("No %s on %s\n", ip->i_name, prship(&tender));
-           return RET_FAIL;
+           continue;
        }
        vbase = &mchr[(int)tender.shp_type];
-       maxtender = vl_find(ip->i_vtype, vbase->m_vtype,
-                           vbase->m_vamt, (int)vbase->m_nv);
+       maxtender = vbase->m_item[ip->i_uid];
        if (maxtender == 0) {
-           pr("A %s cannot hold any %s\n",
-              mchr[(int)tender.shp_type].m_name, ip->i_name);
-           break;
+           pr("%s cannot hold any %s\n", prship(&tender), ip->i_name);
+           continue;
        }
        if (!snxtitem(&targets, EF_SHIP,
-                     getstarg(player->argp[4], "Ships to be tended? ",
-                              buf)))
-           break;
+                     player->argp[4], "Ships to be tended? "))
+           return RET_FAIL;
        if (!check_ship_ok(&tender))
            return RET_SYN;
        total = 0;
-       while (tend_nxtitem(&targets, (s_char *)&target)) {
-           if (!player->owner &&
-               (getrel(getnatp(target.shp_own), player->cnum) < FRIENDLY))
-               continue;
-           if (target.shp_uid == tender.shp_uid)
-               continue;
-           if (tender.shp_x != target.shp_x ||
-               tender.shp_y != target.shp_y)
-               continue;
-           ontarget = target.shp_item[ip->i_vtype];
-           if (ontarget == 0 && amt < 0) {
-               pr("No %s on %s\n", ip->i_name, prship(&target));
-               continue;
-           }
-           vbase = &mchr[(int)target.shp_type];
-           maxtarget = vl_find(ip->i_vtype, vbase->m_vtype,
-                               vbase->m_vamt, (int)vbase->m_nv);
-           if (amt < 0) {
-               if (!player->owner)
-                   amt = 0;
-
-               /* take from target and give to tender */
-               transfer = min(ontarget, -amt);
-               transfer = min(maxtender - ontender, transfer);
-               if (transfer == 0)
+       while (nxtitem(&targets, &target)) {
+           if (amt > 0) {
+               if (!can_tend_to(&tender, tenders.sel == NS_LIST,
+                                &target, targets.sel == NS_LIST))
                    continue;
-               target.shp_item[ip->i_vtype] = ontarget - transfer;
-               ontender += transfer;
-               total += transfer;
+               transfer = tend_comm_to(&tender, ip, amt, &target);
            } else {
-               /* give to target from tender */
-               transfer = min(ontender, amt);
-               transfer = min(transfer, maxtarget - ontarget);
-               if (transfer == 0)
+               if (!player->owner) {
+                   if (targets.sel == NS_LIST)
+                       pr("You don't own ship #%d!\n", target.shp_uid);
+                   continue;
+               }
+               if (!can_tend_to(&target, targets.sel == NS_LIST,
+                                &tender, tenders.sel == NS_LIST))
                    continue;
-               target.shp_item[ip->i_vtype] = ontarget + transfer;
-               ontender -= transfer;
-               total += transfer;
+               transfer = tend_comm_to(&target, ip, -amt, &tender);
            }
+           if (!transfer)
+               continue;
+           total += transfer;
            expose_ship(&tender, &target);
            putship(target.shp_uid, &target);
-           if (amt > 0 && ontender == 0) {
+           if (amt > 0 && !tender.shp_item[ip->i_uid]) {
                pr("%s out of %s\n", prship(&tender), ip->i_name);
                break;
            }
+           if (amt < 0 && tender.shp_item[ip->i_uid] == maxtender)
+               break;
        }
        pr("%d total %s transferred %s %s\n",
           total, ip->i_name, (amt > 0) ? "off of" : "to",
           prship(&tender));
-       tender.shp_item[ip->i_vtype] = ontender;
        tender.shp_mission = 0;
        putship(tender.shp_uid, &tender);
     }
     return RET_OK;
 }
 
+static int
+can_tend_to(struct shpstr *from, int noisy_from,
+           struct shpstr *to, int noisy_to)
+{
+    /*
+     * Careful: error messages must not disclose anything on foreign
+     * @to the player doesn't already know, or could trivially learn.
+     */
+    if (!to->shp_own) {
+       if (noisy_to)
+           pr("You don't own ship #%d!\n", to->shp_uid);
+       return 0;
+    }
+    if (from->shp_uid == to->shp_uid) {
+       if (noisy_from && noisy_to)
+           pr("%s won't tend to itself\n", prship(from));
+       return 0;
+    }
+    if (from->shp_x != to->shp_x || from->shp_y != to->shp_y) {
+       if (noisy_from && noisy_to) {
+           /* Don't disclose foreign @to exists elsewhere */
+           if (player->god || to->shp_own == player->cnum)
+               pr("%s is not in the same sector as %s\n",
+                  prship(to), prship(from));
+           else
+               pr("You don't own ship #%d!\n", to->shp_uid);
+       }
+       return 0;
+    }
+    if (!player->god && to->shp_own != player->cnum
+       && relations_with(to->shp_own, player->cnum) < FRIENDLY) {
+       if (noisy_to) {
+           /*
+            * Don't disclose unfriendly @to exists here unless
+            * lookout from @from would see it.
+            */
+           if ((mchr[from->shp_type].m_flags & M_SUB)
+               || (mchr[to->shp_type].m_flags & M_SUB))
+               pr("You don't own ship #%d!\n", from->shp_uid);
+           else
+               pr("You are not on friendly terms with"
+                  " the owner of ship #%d!\n",
+                  to->shp_uid);
+       }
+       return 0;
+    }
+    return 1;
+}
+
+static int
+tend_comm_to(struct shpstr *from, struct ichrstr *ip, int amt,
+            struct shpstr *to)
+{
+    int can_give = from->shp_item[ip->i_uid];
+    int to_max = mchr[to->shp_type].m_item[ip->i_uid];
+    int can_take = to_max - to->shp_item[ip->i_uid];
+    int transfer;
+
+    if (!to_max) {
+       pr("%s cannot hold any %s\n", prship(to), ip->i_name);
+       return 0;
+    }
+    if (ip->i_uid == I_CIVIL && from->shp_own != to->shp_own) {
+       pr("%s civilians refuse to board %s!\n",
+          from->shp_own == player->cnum ? "Your" : "Foreign",
+          prship(to));
+       return 0;
+    }
+    if (!can_give) {
+       pr("No %s on %s\n", ip->i_name, prship(from));
+       return 0;
+    }
+    if (!can_take) {
+       pr("%s can't hold more %s\n", prship(to), ip->i_name);
+       return 0;
+    }
+
+    transfer = MIN(can_give, amt);
+    transfer = MIN(can_take, transfer);
+    from->shp_item[ip->i_uid] -= transfer;
+    to->shp_item[ip->i_uid] += transfer;
+    if (to->shp_own != player->cnum) {
+       wu(0, to->shp_own, "%s tended %d %s to %s\n",
+          cname(player->cnum), transfer, ip->i_name, prship(to));
+    }
+
+    return transfer;
+}
+
 static void
 expose_ship(struct shpstr *s1, struct shpstr *s2)
 {
@@ -200,127 +269,65 @@ expose_ship(struct shpstr *s1, struct shpstr *s2)
        s1->shp_pstage = PLG_EXPOSED;
 }
 
-/*
- * tend_nxtitem.c
- *
- * get next item from list. Stolen from nxtitem to make 1 itsy-bitsy change
- *
- * Dave Pare, 1989
- */
-
-int
-tend_nxtitem(struct nstr_item *np, caddr_t ptr)
-{
-    struct genitem *gp;
-    int selected;
-
-    if (np->sel == NS_UNDEF)
-       return 0;
-    gp = (struct genitem *)ptr;
-    do {
-       if (np->sel == NS_LIST) {
-           np->index++;
-           if (np->index >= np->size)
-               return 0;
-           np->cur = np->list[np->index];
-       } else {
-           np->cur++;
-       }
-       if (!np->read(np->type, np->cur, ptr)) {
-           /* if read fails, fatal */
-           return 0;
-       }
-       selected = 1;
-       switch (np->sel) {
-       case NS_LIST:
-           /* The change is to take the player->owner check out here */
-           break;
-       case NS_ALL:
-           /* XXX maybe combine NS_LIST and NS_ALL later */
-           break;
-       case NS_DIST:
-           if (!xyinrange(gp->x, gp->y, &np->range)) {
-               selected = 0;
-               break;
-           }
-           np->curdist = mapdist((int)gp->x, (int)gp->y,
-                                 (int)np->cx, (int)np->cy);
-           if (np->curdist > np->dist)
-               selected = 0;
-           break;
-       case NS_AREA:
-           if (!xyinrange(gp->x, gp->y, &np->range))
-               selected = 0;
-           if (gp->x == np->range.hx || gp->y == np->range.hy)
-               selected = 0;
-           break;
-       case NS_XY:
-           if (gp->x != np->cx || gp->y != np->cy)
-               selected = 0;
-           break;
-       case NS_GROUP:
-           if (np->group != gp->group)
-               selected = 0;
-           break;
-       default:
-           logerror("nxtitem: bad selector %d\n", np->sel);
-           return 0;
-       }
-       if (selected && np->ncond) {
-           /* nstr_exec is expensive, so we do it last */
-           if (!nstr_exec(np->cond, np->ncond, ptr))
-               selected = 0;
-       }
-    } while (!selected);
-    return 1;
-}
-
 static int
-tend_land(struct shpstr *tenderp, s_char *units)
+tend_land(struct shpstr *tenderp, int noisy, char *units)
 {
     struct nstr_item lni;
     struct nstr_item targets;
     struct shpstr target;
     struct lndstr land;
-    struct plnstr plane;
-    struct nstr_item pni;
-    s_char buf[1024];
+    char buf[1024];
 
-    if (!snxtitem(&lni, EF_LAND, units))
+    if (!snxtitem(&lni, EF_LAND, units, NULL))
        return RET_SYN;
 
-    while (nxtitem(&lni, (s_char *)&land)) {
-       if (!player->owner)
+    while (nxtitem(&lni, &land)) {
+       if (!player->owner || !land.lnd_own) {
+           if (lni.sel == NS_LIST)
+               pr("You don't own land unit #%d!\n", land.lnd_uid);
            continue;
+       }
        if (land.lnd_ship != tenderp->shp_uid) {
-           pr("%s is not on %s!\n", prland(&land), prship(tenderp));
+           if (lni.sel == NS_LIST)
+               pr("%s is not on %s!\n", prland(&land), prship(tenderp));
            continue;
        }
        if (!(lchr[(int)land.lnd_type].l_flags & L_ASSAULT)) {
-           pr("%s does not have \"assault\" capability and can't be tended\n", prland(&land));
+           pr("%s does not have \"assault\" capability and can't be tended\n",
+              prland(&land));
            continue;
        }
        if (!snxtitem(&targets, EF_SHIP,
-                     getstarg(player->argp[4], "Ship to be tended? ",
-                              buf)))
-           break;
-       if (!check_land_ok(&land))
+                     player->argp[4], "Ship to be tended? "))
+           return RET_FAIL;
+       if (!check_ship_ok(tenderp) || !check_land_ok(&land))
            return RET_SYN;
-       while (tend_nxtitem(&targets, (s_char *)&target)) {
-           if (!player->owner &&
-               (getrel(getnatp(target.shp_own), player->cnum) < FRIENDLY))
-               continue;
-           if (target.shp_uid == tenderp->shp_uid)
-               continue;
-           if (tenderp->shp_x != target.shp_x ||
-               tenderp->shp_y != target.shp_y)
+       while (nxtitem(&targets, &target)) {
+           if (!can_tend_to(tenderp, noisy,
+                            &target, targets.sel == NS_LIST))
                continue;
 
            /* Fit unit on ship */
-           count_units(&target);
            getship(target.shp_uid, &target);
 
-           if (target.shp_nland >= mchr[(int)target.shp_type].m_nland) {
+           if ((!(lchr[(int)land.lnd_type].l_flags & L_LIGHT)) &&
+               (!((mchr[(int)target.shp_type].m_flags & M_SUPPLY) &&
+                  (!(mchr[(int)target.shp_type].m_flags & M_SUB))))) {
+               pr("You can only load light units onto ships,\n"
+                  "unless the ship is a non-sub supply ship\n"
+                  "%s not tended\n", prland(&land));
+               continue;
+           }
+
+           if ((mchr[(int)target.shp_type].m_flags & M_SUB) &&
+               (lchr[(int)land.lnd_type].l_flags & L_SPY) &&
+               !mchr[(int)target.shp_type].m_nland) {
+               if (shp_nland(&target) > 1) {
+                   pr("%s doesn't have room for more than two spy units!\n",
+                      prship(&target));
+                   continue;
+               }
+           } else if (shp_nland(&target) >= mchr[target.shp_type].m_nland) {
                if (mchr[(int)target.shp_type].m_nland)
                    pr("%s doesn't have room for any more land units!\n",
                       prship(&target));
@@ -333,39 +340,14 @@ tend_land(struct shpstr *tenderp, s_char *units)
            sprintf(buf, "loaded on your %s at %s",
                    prship(&target), xyas(target.shp_x, target.shp_y,
                                          target.shp_own));
-           gift(target.shp_own, player->cnum, (s_char *)&land,
-                EF_LAND, buf);
-           makelost(EF_LAND, land.lnd_own, land.lnd_uid, land.lnd_x,
-                    land.lnd_y);
-           land.lnd_own = target.shp_own;
-           makenotlost(EF_LAND, land.lnd_own, land.lnd_uid, land.lnd_x,
-                       land.lnd_y);
+           gift(target.shp_own, player->cnum, &land, buf);
            land.lnd_ship = target.shp_uid;
            land.lnd_harden = 0;
-           land.lnd_mission = 0;
-           target.shp_nland++;
            putland(land.lnd_uid, &land);
            expose_ship(tenderp, &target);
            putship(target.shp_uid, &target);
-           count_units(tenderp);
            putship(tenderp->shp_uid, tenderp);
-           snxtitem_xy(&pni, EF_PLANE, land.lnd_x, land.lnd_y);
-           while (nxtitem(&pni, (s_char *)&plane)) {
-               if (plane.pln_flags & PLN_LAUNCHED)
-                   continue;
-               if (plane.pln_land != land.lnd_uid)
-                   continue;
-               sprintf(buf, "loaded on %s", prship(&target));
-               gift(target.shp_own, player->cnum, (s_char *)&plane,
-                    EF_PLANE, buf);
-               makelost(EF_PLANE, plane.pln_own, plane.pln_uid,
-                        plane.pln_x, plane.pln_y);
-               plane.pln_own = target.shp_own;
-               makenotlost(EF_PLANE, plane.pln_own, plane.pln_uid,
-                           plane.pln_x, plane.pln_y);
-               plane.pln_mission = 0;
-               putplane(plane.pln_uid, &plane);
-           }
+           break;
        }
     }
     return 0;