]> git.pond.sub.org Git - empserver/blobdiff - src/lib/commands/edit.c
edit: Generalize edit to multiple objects
[empserver] / src / lib / commands / edit.c
index 018e256c9a2f07922582b850a393b1c5e0949189..dacf508fc211820966ad67a61f4d004cfaf0629e 100644 (file)
@@ -47,6 +47,7 @@
 #include "plague.h"
 #include "plane.h"
 #include "ship.h"
+#include "unit.h"
 
 static void print_sect(struct sctstr *);
 static void print_nat(struct natstr *);
@@ -62,170 +63,125 @@ static int edit_plane(struct plnstr *, char *, char *);
 int
 edit(void)
 {
-    struct sctstr sect;
-    struct plnstr plane;
-    struct shpstr ship;
-    struct lndstr land;
+    union empobj_storage item;
     char *what;
+    struct nstr_item ni;
     char *key, *ptr;
-    int num;
-    int err;
-    int arg_index = 3;
-    coord x, y;
     struct natstr *np;
+    int type, arg_index, ret;
     char buf[1024];
-    char ewhat;
 
     what = getstarg(player->argp[1],
                    "Edit what (country, land, ship, plane, nuke, unit)? ",
                    buf);
     if (!what)
        return RET_SYN;
-    ewhat = what[0];
-    switch (ewhat) {
+    switch (what[0]) {
     case 'l':
-       if (!(ptr = getstarg(player->argp[2], "Sector : ", buf)))
-           return RET_FAIL;
-       if (!sarg_xy(ptr, &x, &y))
-           return RET_FAIL;
-       if (!getsect(x, y, &sect))
-           return RET_FAIL;
-       break;
-    case 'c':
-       np = natargp(player->argp[2], "Country? ");
-       if (!np)
-           return RET_SYN;
+       type = EF_SECTOR;
        break;
     case 'p':
-       if ((num = onearg(player->argp[2], "Plane number? ")) < 0)
-           return RET_SYN;
-       if (!getplane(num, &plane))
-           return RET_SYN;
+       type = EF_PLANE;
        break;
     case 's':
-       if ((num = onearg(player->argp[2], "Ship number? ")) < 0)
-           return RET_SYN;
-       if (!getship(num, &ship))
-           return RET_SYN;
+       type = EF_SHIP;
        break;
     case 'u':
-       if ((num = onearg(player->argp[2], "Unit number? ")) < 0)
-           return RET_SYN;
-       if (!getland(num, &land))
-           return RET_SYN;
+       type = EF_LAND;
        break;
     case 'n':
        pr("Not implemented yet.\n");
+       return RET_FAIL;
+    case 'c':
+       type = EF_NATION;
        break;
     default:
        pr("huh?\n");
        return RET_SYN;
     }
-    if (!player->argp[3]) {
-       switch (ewhat) {
-       case 'l':
-           print_sect(&sect);
-           break;
-       case 'c':
-           print_nat(np);
-           break;
-       case 'p':
-           print_plane(&plane);
-           break;
-       case 's':
-           print_ship(&ship);
-           break;
-       case 'u':
-           print_land(&land);
-           break;
+
+    if (!snxtitem(&ni, type, player->argp[2], NULL))
+       return RET_SYN;
+    while (nxtitem(&ni, &item)) {
+       if (!player->argp[3]) {
+           switch (type) {
+           case EF_SECTOR:
+               print_sect(&item.sect);
+               break;
+           case EF_SHIP:
+               print_ship(&item.ship);
+               break;
+           case EF_PLANE:
+               print_plane(&item.plane);
+               break;
+           case EF_LAND:
+               print_land(&item.land);
+               break;
+           case EF_NATION:
+               print_nat(&item.nat);
+               break;
+           default:
+               CANT_REACH();
+           }
        }
-    }
-    for (;;) {
-       if (player->argp[arg_index]) {
-           if (player->argp[arg_index+1]) {
-               key = player->argp[arg_index++];
-               ptr = player->argp[arg_index++];
-           } else
-               return RET_SYN;
-       } else if (arg_index == 3) {
-           key = getin(buf, &ptr);
-           if (!key)
-               return RET_SYN;
-           if (!*key) {
-               switch (ewhat) {
-               case 'c':
-                   print_nat(np);
-                   break;
-               case 'l':
-                   print_sect(&sect);
-                   break;
-               case 's':
-                   print_ship(&ship);
-                   break;
-               case 'u':
-                   print_land(&land);
-                   break;
-               case 'p':
-                   print_plane(&plane);
+
+       arg_index = 3;
+       for (;;) {
+           if (player->argp[arg_index]) {
+               if (player->argp[arg_index+1]) {
+                   key = player->argp[arg_index++];
+                   ptr = player->argp[arg_index++];
+               } else
+                   return RET_SYN;
+           } else if (arg_index == 3) {
+               key = getin(buf, &ptr);
+               if (!key)
+                   return RET_SYN;
+               if (!*key)
                    break;
-               }
-               return RET_OK;
-           }
-       } else
-           return RET_OK;
+           } else
+               break;
 
-       switch (ewhat) {
-       case 'c':
-           if ((err = edit_nat(np, key, ptr)) != RET_OK)
-               return err;
-           break;
-       case 'l':
-           if (!check_sect_ok(&sect))
+           if (!check_obj_ok(&item.gen))
                return RET_FAIL;
-           if ((err = edit_sect(&sect, key, ptr)) != RET_OK)
-               return err;
-           if (!putsect(&sect))
-               return RET_FAIL;
-           break;
-       case 's':
-           if (!check_ship_ok(&ship))
-               return RET_FAIL;
-           if ((err = edit_ship(&ship, key, ptr)) != RET_OK)
-               return err;
-           if (!ef_ensure_space(EF_SHIP, ship.shp_uid, 50))
-               return RET_FAIL;
-           if (!putship(ship.shp_uid, &ship))
-               return RET_FAIL;
-           break;
-       case 'u':
-           if (!check_land_ok(&land))
-               return RET_FAIL;
-           if ((err = edit_land(&land, key, ptr)) != RET_OK)
-               return err;
-           if (!ef_ensure_space(EF_LAND, land.lnd_uid, 50))
-               return RET_FAIL;
-           if (!putland(land.lnd_uid, &land))
-               return RET_FAIL;
-           break;
-       case 'p':
-           if (!check_plane_ok(&plane))
-               return RET_FAIL;
-           if ((err = edit_plane(&plane, key, ptr)) != RET_OK)
-               return err;
-           if (!ef_ensure_space(EF_PLANE, plane.pln_uid, 50))
-               return RET_FAIL;
-           if (!putplane(plane.pln_uid, &plane))
+           switch (type) {
+           case EF_NATION:
+               /*
+                * edit_nat() may update the edited country by sending
+                * it bulletins.  Writing back item.nat would trigger
+                * a seqno mismatch oops.  Workaround: edit in-place.
+                */
+               np = getnatp(item.nat.nat_cnum);
+               ret = edit_nat(np, key, ptr);
+               if (ret != RET_OK)
+                   return ret;
+               if (!putnat(np))
+                   return RET_FAIL;
+               item.nat = *np;
+               continue;
+           case EF_SECTOR:
+               ret = edit_sect(&item.sect, key, ptr);
+               break;
+           case EF_SHIP:
+               ret = edit_ship(&item.ship, key, ptr);
+               break;
+           case EF_LAND:
+               ret = edit_land(&item.land, key, ptr);
+               break;
+           case EF_PLANE:
+               ret = edit_plane(&item.plane, key, ptr);
+               break;
+           default:
+               CANT_REACH();
+           }
+           if (ret != RET_OK)
+               return ret;
+           if (!put_empobj(type, item.gen.uid, &item.gen))
                return RET_FAIL;
-           break;
        }
     }
-}
 
-static void
-benefit(natid who, int goodness)
-{
-    if (opt_GODNEWS && getnatp(who)->nat_stat != STAT_GOD && goodness)
-       nreport(player->cnum, goodness > 0 ? N_AIDS : N_HURTS, who, 1);
+    return RET_OK;
 }
 
 static void
@@ -629,182 +585,288 @@ edit_sect(struct sctstr *sect, char *key, char *p)
     return RET_OK;
 }
 
+static void
+edit_level(struct natstr *np, int lvl, char *name, char *p)
+{
+    float new = (float)atof(p);
+
+    new = MAX(0.0, new);
+    divine_nat_change(np, name,
+                     new != np->nat_level[lvl],
+                     (new > np->nat_level[lvl]) - (new < np->nat_level[lvl]),
+                     "from %.2f to %.2f", np->nat_level[lvl], new);
+    np->nat_level[lvl] = new;
+}
+
 static int
 edit_nat(struct natstr *np, char *key, char *p)
 {
     coord newx, newy;
     natid nat = np->nat_cnum;
     int arg = atoi(p);
-    float farg = (float)atof(p);
 
     switch (*key) {
     case 'n':
        if (!check_nat_name(p, nat))
            return RET_SYN;
-       pr("Country name changed from %s to %s\n", np->nat_cnam, p);
+       divine_nat_change(np, "Country name", strcmp(np->nat_cnam, p), 0,
+                         "from %s to %s", np->nat_cnam, p);
+       if (opt_GODNEWS)
+           nreport(player->cnum, N_NAME_CHNG, 0, 1);
        strcpy(np->nat_cnam, p);
        break;
     case 'r':
-       pr("Country representative changed from %s to %s\n",
-          np->nat_pnam, p);
+       divine_nat_change(np, "Country representative",
+               strncmp(p, np->nat_pnam, sizeof(np->nat_pnam) - 1), 0,
+               "from %s to %.*s",
+               np->nat_pnam, (int)sizeof(np->nat_pnam) - 1, p);
        strncpy(np->nat_pnam, p, sizeof(np->nat_pnam) - 1);
        break;
     case 't':
        arg = LIMIT_TO(arg, 0, USHRT_MAX);
+       divine_nat_change_quiet(np, "Number of unread telegrams",
+                               arg != np->nat_tgms,
+                               "from %d to %d", np->nat_tgms, arg);
        np->nat_tgms = arg;
        break;
     case 'b':
        arg = LIMIT_TO(arg, 0, max_btus);
-       pr("BTU's changed from %d to %d\n", np->nat_btu, arg);
+       divine_nat_change(np, "BTUs",
+                         arg != np->nat_btu, arg - np->nat_btu,
+                         "from %d to %d", np->nat_btu, arg);
        np->nat_btu = arg;
        break;
     case 'm':
        arg = LIMIT_TO(arg, 0, INT_MAX);
-       benefit(nat, arg - np->nat_reserve);
-       pr("Military reserves changed from %d to %d\n",
-          np->nat_reserve, arg);
-       if (arg == np->nat_reserve)
-           break;
-       if (nat != player->cnum)
-           wu(0, nat,
-              "Military reserves changed from %d to %d by an act of %s\n",
-              np->nat_reserve, arg, cname(player->cnum));
+       divine_nat_change(np, "Military reserves",
+                         arg != np->nat_reserve, arg - np->nat_reserve,
+                         "from %d to %d", np->nat_reserve, arg);
        np->nat_reserve = arg;
        break;
     case 'c':
        if (!sarg_xy(p, &newx, &newy))
            return RET_SYN;
-       pr("Capital coordinates changed from %s to %s\n",
-          xyas(np->nat_xcap, np->nat_ycap, player->cnum),
-          xyas(newx, newy, player->cnum));
+       if (newx == np->nat_xcap && newy == np->nat_ycap)
+           pr("Capital unchanged\n");
+       else {
+           pr("Capital moved from %s to %s\n",
+              xyas(np->nat_xcap, np->nat_ycap, player->cnum),
+              xyas(newx, newy, player->cnum));
+           if (nat != player->cnum)
+               wu(0, nat,
+                  "Capital moved from %s to %s by an act of %s!\n",
+                  xyas(np->nat_xcap, np->nat_ycap, nat),
+                  xyas(newx, newy, nat), cname(player->cnum));
+       }
        np->nat_xcap = newx;
        np->nat_ycap = newy;
        break;
     case 'o':
        if (!sarg_xy(p, &newx, &newy))
            return RET_SYN;
-       pr("Origin coordinates changed from %s to %s\n",
-          xyas(np->nat_xorg, np->nat_yorg, player->cnum),
-          xyas(newx, newy, player->cnum));
+       if (newx == np->nat_xorg && newy == np->nat_yorg)
+           pr("Origin unchanged\n");
+       else {
+           pr("Origin moved from %s to %s\n",
+              xyas(np->nat_xorg, np->nat_yorg, player->cnum),
+              xyas(newx, newy, player->cnum));
+           if (nat != player->cnum)
+               wu(0, nat,
+                  "Origin moved from %s to %s by an act of %s!\n",
+                  xyas(np->nat_xorg, np->nat_yorg, nat),
+                  xyas(newx, newy, nat), cname(player->cnum));
+       }
        np->nat_xorg = newx;
        np->nat_yorg = newy;
        break;
     case 's':
-       np->nat_stat = LIMIT_TO(arg, STAT_UNUSED, STAT_GOD);
+       arg = LIMIT_TO(arg, STAT_UNUSED, STAT_GOD);
+       divine_nat_change(np, "Status",
+                         (enum nat_status)arg != np->nat_stat,
+                         0, "to %s", nation_status[arg].name);
+       np->nat_stat = arg;
        break;
     case 'u':
        arg = LIMIT_TO(arg, 0, m_m_p_d * 60);
-       pr("Number of seconds used changed from %d to %d.\n",
-          np->nat_timeused, arg);
+       divine_nat_change(np, "Number of seconds used",
+                         arg != np->nat_timeused, arg - np->nat_timeused,
+                         "from %d to %d", np->nat_timeused, arg);
        np->nat_timeused = arg;
        break;
     case 'M':
-       pr("Money changed from %d to %d\n", np->nat_money, arg);
-       if (arg == np->nat_money)
-           break;
-       if (nat != player->cnum)
-           wu(0, nat, "Money changed from %d to %d by an act of %s\n",
-              np->nat_money, arg, cname(player->cnum));
+       divine_nat_change(np, "Money",
+                         arg != np->nat_money, arg - np->nat_money,
+                         "from %d to %d", np->nat_money, arg);
        np->nat_money = arg;
        break;
     case 'T':
-       farg = MAX(0.0, farg);
-       pr("Tech changed from %.2f to %.2f.\n",
-          np->nat_level[NAT_TLEV], farg);
-       np->nat_level[NAT_TLEV] = farg;
+       edit_level(np, NAT_TLEV, "Technology", p);
        break;
     case 'R':
-       farg = MAX(0.0, farg);
-       pr("Research changed from %.2f to %.2f.\n",
-          np->nat_level[NAT_RLEV], farg);
-       np->nat_level[NAT_RLEV] = farg;
+       edit_level(np, NAT_RLEV, "Research", p);
        break;
     case 'E':
-       farg = MAX(0.0, farg);
-       pr("Education changed from %.2f to %.2f.\n",
-          np->nat_level[NAT_ELEV], farg);
-       np->nat_level[NAT_ELEV] = farg;
+       edit_level(np, NAT_ELEV, "Education", p);
        break;
     case 'H':
-       farg = MAX(0.0, farg);
-       pr("Happiness changed from %.2f to %.2f.\n",
-          np->nat_level[NAT_HLEV], farg);
-       np->nat_level[NAT_HLEV] = farg;
+       edit_level(np, NAT_HLEV, "Happiness", p);
        break;
     default:
        pr("huh? (%s)\n", key);
        break;
     }
-    putnat(np);
     return RET_OK;
 }
 
 static int
-edit_ship(struct shpstr *ship, char *key, char *p)
+edit_unit(struct empobj *unit, char *key, char *p,
+         int mineff, char *group_name, int on_carrier)
 {
-    struct mchrstr *mcp = &mchr[ship->shp_type];
     int arg = atoi(p);
     coord newx, newy;
-    struct ichrstr *ip;
-
-    newx = newy = 0;
-    switch (*key) {
-    case 'a':
-       arg = LIMIT_TO(arg, 0, PLG_EXPOSED);
-       ship->shp_pstage = arg;
-       break;
-    case 'b':
-       arg = LIMIT_TO(arg, 0, 32767);
-       ship->shp_ptime = arg;
-       break;
-    case 'R':
-       strncpy(ship->shp_rpath, p, sizeof(ship->shp_rpath) - 1);
-       break;
-    case 'W':
-       ship->shp_rflags = arg;
-       break;
+    union empobj_storage newunit;
+    char newgroup;
+    switch (toupper(*key)) {
     case 'U':
-       ef_set_uid(EF_SHIP, ship, arg);
+       if (arg < 0)
+           return RET_SYN;
+       if (arg == unit->uid) {
+           pr("%s unchanged\n", unit_nameof(unit));
+           break;
+       }
+       if (!ef_ensure_space(unit->ef_type, arg, 50)) {
+           pr("Can't copy to %s #%d\n", ef_nameof(unit->ef_type), arg);
+           return RET_FAIL;
+       }
+       pr("%s duplicated to (#%d)\n", unit_nameof(unit), arg);
+       ef_set_uid(unit->ef_type, unit, arg);
+       if (get_empobj(unit->ef_type, arg, &newunit) && newunit.gen.own) {
+           pr("Replacing %s of %s\n",
+              unit_nameof(&newunit.gen), prnatid(newunit.gen.own));
+           report_god_takes("", unit_nameof(&newunit.gen),
+                            newunit.gen.own);
+       }
+       report_god_gives("", unit_nameof(unit), unit->own);
        break;
     case 'O':
        if (arg < 0 || arg >= MAXNOC)
            return RET_SYN;
-       if (arg == ship->shp_own)
-           break;
-       if (ship->shp_own && ship->shp_own != player->cnum)
-           wu(0, ship->shp_own, "%s taken from you by an act of %s!\n",
-              prship(ship), cname(player->cnum));
-       if (arg && arg != player->cnum)
-           wu(0, arg, "%s given to you by an act of %s!\n",
-              prship(ship), cname(player->cnum));
-       ship->shp_own = arg;
+       divine_unit_change_quiet(unit, "Owner", arg != unit->own,
+                                "from %s to %s",
+                                prnatid(unit->own), prnatid(arg));
+       if (arg != unit->own) {
+           report_god_takes("", unit_nameof(unit), unit->own);
+           report_god_gives("", unit_nameof(unit), arg);
+       }
+       unit->own = arg;
        break;
     case 'L':
        if (!sarg_xy(p, &newx, &newy))
            return RET_SYN;
-       ship->shp_x = newx;
-       ship->shp_y = newy;
-       break;
-    case 'T':
-       arg = LIMIT_TO(arg, mcp->m_tech, SHRT_MAX);
-       shp_set_tech(ship, arg);
+       if (on_carrier && (newx != unit->x || newy != unit->y)) {
+           pr("Can't move %s while it's loaded\n", unit_nameof(unit));
+           return RET_FAIL;
+       }
+       divine_unit_change_quiet(unit, "Location",
+                                unit->own && unit->own != player->cnum,
+                                "from %s to %s",
+                                xyas(unit->x, unit->y, player->cnum),
+                                xyas(newx, newy, player->cnum));
+       if (unit->own && unit->own != player->cnum)
+           wu(0, unit->own,
+              "Location of %s changed from %s to %s by an act of %s!\n",
+              unit_nameof(unit),
+              xyas(unit->x, unit->y, unit->own),
+              xyas(newx, newy, unit->own),
+              cname(player->cnum));
+       unit->x = newx;
+       unit->y = newy;
        break;
     case 'E':
-       ship->shp_effic = LIMIT_TO(arg, SHIP_MINEFF, 100);
+       arg = LIMIT_TO(arg, mineff, 100);
+       divine_unit_change(unit, "Efficiency",
+                          arg != unit->effic, arg - unit->effic,
+                          "from %d to %d", unit->effic, arg);
+       unit->effic = arg;
        break;
     case 'M':
        arg = LIMIT_TO(arg, -127, 127);
-       ship->shp_mobil = arg;
+       divine_unit_change(unit, "Mobility",
+                          arg != unit->mobil, arg - unit->mobil,
+                          "from %d to %d", unit->mobil, arg);
+       unit->mobil = arg;
        break;
     case 'F':
+    case 'W':
+    case 'A':
        if (p[0] == '~')
-           ship->shp_fleet = 0;
+           newgroup = 0;
        else if (isalpha(p[0]))
-           ship->shp_fleet = p[0];
+           newgroup = p[0];
        else {
-           pr("%c: invalid fleet\n", p[0]);
+           pr("%c: invalid %s\n", p[0], group_name);
            return RET_FAIL;
        }
+       divine_unit_change(unit, "Assignment", newgroup != unit->group, 0,
+                          "from %s %c to %c", group_name,
+                          unit->group ? unit->group : '~', p[0]);
+       unit->group = newgroup;
+       break;
+    default:
+       CANT_REACH();
+    }
+    return RET_OK;
+}
+
+static int
+edit_ship(struct shpstr *ship, char *key, char *p)
+{
+    struct mchrstr *mcp = &mchr[ship->shp_type];
+    int arg = atoi(p);
+    struct ichrstr *ip;
+
+    switch (*key) {
+    case 'U':
+    case 'O':
+    case 'L':
+    case 'E':
+    case 'M':
+    case 'F':
+       return edit_unit((struct empobj *)ship, key, p,
+                        SHIP_MINEFF, "fleet", 0);
+    case 'T':
+       arg = LIMIT_TO(arg, mcp->m_tech, SHRT_MAX);
+       divine_unit_change((struct empobj *)ship, "Tech level",
+                          arg != ship->shp_tech, arg - ship->shp_tech,
+                          "from %d to %d", ship->shp_tech, arg);
+       shp_set_tech(ship, arg);
+       break;
+    case 'a':
+       arg = LIMIT_TO(arg, 0, PLG_EXPOSED);
+       divine_unit_change_quiet((struct empobj *)ship, "Plague stage",
+                                arg != ship->shp_pstage,
+                                "from %d to %d", ship->shp_pstage, arg);
+       ship->shp_pstage = arg;
+       break;
+    case 'b':
+       arg = LIMIT_TO(arg, 0, 32767);
+       divine_unit_change_quiet((struct empobj *)ship, "Plague time",
+                                arg != ship->shp_ptime,
+                                "from %d to %d", ship->shp_ptime, arg);
+       ship->shp_ptime = arg;
+       break;
+    case 'R':
+       divine_unit_change((struct empobj *)ship, "Retreat path",
+               strncmp(p, ship->shp_rpath, sizeof(ship->shp_rpath) - 1),
+               0, "from %s to %.*s",
+               ship->shp_rpath, (int)sizeof(ship->shp_rpath) - 1, p);
+       strncpy(ship->shp_rpath, p, sizeof(ship->shp_rpath) - 1);
+       break;
+    case 'W':
+       divine_flag_change((struct empobj *)ship, "Retreat conditions",
+                          ship->shp_rflags, arg, retreat_flags);
+       ship->shp_rflags = arg;
        break;
     case 'c':
     case 'm':
@@ -841,79 +903,79 @@ edit_land(struct lndstr *land, char *key, char *p)
 {
     struct lchrstr *lcp = &lchr[land->lnd_type];
     int arg = atoi(p);
-    coord newx, newy;
     struct ichrstr *ip;
 
-    newx = newy = 0;
     switch (*key) {
-    case 'Y':
-       if (arg < -1 || arg >= ef_nelem(EF_LAND))
-           return RET_SYN;
-       if (arg >= 0 && arg != land->lnd_land)
-           land->lnd_ship = -1;
-       land->lnd_land = arg;
-       break;
     case 'U':
-       ef_set_uid(EF_LAND, land, arg);
-       break;
     case 'O':
-       if (arg < 0 || arg >= MAXNOC)
-           return RET_SYN;
-       if (arg == land->lnd_own)
-           break;
-       if (land->lnd_own && land->lnd_own != player->cnum)
-           wu(0, land->lnd_own, "%s taken from you by an act of %s!\n",
-              prland(land), cname(player->cnum));
-       if (arg && arg != player->cnum)
-           wu(0, arg, "%s given to you by an act of %s!\n",
-              prland(land), cname(player->cnum));
-       land->lnd_own = arg;
-       break;
     case 'L':
-       if (!sarg_xy(p, &newx, &newy))
-           return RET_SYN;
-       land->lnd_x = newx;
-       land->lnd_y = newy;
-       break;
     case 'e':
-       land->lnd_effic = LIMIT_TO(arg, LAND_MINEFF, 100);
-       break;
     case 'M':
-       arg = LIMIT_TO(arg, -127, 127);
-       land->lnd_mobil = arg;
-       break;
+    case 'a':
+       return edit_unit((struct empobj *)land, key, p,
+                        LAND_MINEFF, "army",
+                        land->lnd_ship >= 0 || land->lnd_land >= 0);
     case 't':
        arg = LIMIT_TO(arg, lcp->l_tech, SHRT_MAX);
+       divine_unit_change((struct empobj *)land, "Tech level",
+                          arg != land->lnd_tech, arg - land->lnd_tech,
+                          "from %d to %d", land->lnd_tech, arg);
        lnd_set_tech(land, arg);
        break;
-    case 'a':
-       if (p[0] == '~')
-           land->lnd_army = 0;
-       else if (isalpha(p[0]))
-           land->lnd_army = p[0];
-       else {
-           pr("%c: invalid army\n", p[0]);
-           return RET_FAIL;
-       }
-       break;
     case 'F':
-       land->lnd_harden = LIMIT_TO(arg, 0, 127);
+       arg = LIMIT_TO(arg, 0, 127);
+       divine_unit_change((struct empobj *)land, "Fortification",
+                          arg != land->lnd_harden, arg - land->lnd_harden,
+                          "from %d to %d", land->lnd_harden, arg);
+       land->lnd_harden = arg;
        break;
     case 'S':
        if (arg < -1 || arg >= ef_nelem(EF_SHIP))
            return RET_SYN;
-       if (arg >= 0 && arg != land->lnd_ship)
+       if (arg == land->lnd_ship) {
+           pr("Ship of %s unchanged\n", prland(land));
+           break;
+       }
+       divine_unload((struct empobj *)land, EF_SHIP, land->lnd_ship);
+       if (arg >= 0) {
+           divine_unload((struct empobj *)land, EF_LAND, land->lnd_land);
            land->lnd_land = -1;
+       }
+       divine_load((struct empobj *)land, EF_SHIP, arg);
        land->lnd_ship = arg;
        break;
+    case 'Y':
+       if (arg < -1 || arg >= ef_nelem(EF_LAND))
+           return RET_SYN;
+       if (arg == land->lnd_land) {
+           pr("Land unit of %s unchanged\n", prland(land));
+           break;
+       }
+       divine_unload((struct empobj *)land, EF_LAND, land->lnd_land);
+       if (arg >= 0) {
+           divine_unload((struct empobj *)land, EF_SHIP, land->lnd_ship);
+           land->lnd_ship = -1;
+       }
+       divine_load((struct empobj *)land, EF_LAND, arg);
+       land->lnd_land = arg;
+       break;
     case 'Z':
        arg = LIMIT_TO(arg, 0, 100);
+       divine_unit_change((struct empobj *)land, "Retreat percentage",
+                          arg != land->lnd_retreat, 0,
+                          "from %d to %d", land->lnd_retreat, arg);
        land->lnd_retreat = arg;
        break;
     case 'R':
+       divine_unit_change((struct empobj *)land, "Retreat path",
+               strncmp(p, land->lnd_rpath, sizeof(land->lnd_rpath) - 1),
+               0, "from %s to %.*s",
+               land->lnd_rpath, (int)sizeof(land->lnd_rpath) - 1, p);
        strncpy(land->lnd_rpath, p, sizeof(land->lnd_rpath) - 1);
        break;
     case 'W':
+       divine_flag_change((struct empobj *)land, "Retreat condition",
+                          land->lnd_rflags, arg, retreat_flags);
        land->lnd_rflags = arg;
        break;
     case 'c':
@@ -951,70 +1013,64 @@ edit_plane(struct plnstr *plane, char *key, char *p)
 {
     struct plchrstr *pcp = &plchr[plane->pln_type];
     int arg = atoi(p);
-    coord newx, newy;
 
     switch (*key) {
     case 'U':
-       ef_set_uid(EF_PLANE, plane, arg);
-       break;
-    case 'l':
-       if (!sarg_xy(p, &newx, &newy))
-           return RET_SYN;
-       plane->pln_x = newx;
-       plane->pln_y = newy;
-       break;
     case 'O':
-       if (arg < 0 || arg >= MAXNOC)
-           return RET_SYN;
-       if (arg == plane->pln_own)
-           break;
-       if (plane->pln_own && plane->pln_own != player->cnum)
-           wu(0, plane->pln_own, "%s taken from you by an act of %s!\n",
-              prplane(plane), cname(player->cnum));
-       if (arg && arg != player->cnum)
-           wu(0, arg, "%s given to you by an act of %s!\n",
-              prplane(plane), cname(player->cnum));
-       plane->pln_own = arg;
-       break;
+    case 'l':
     case 'e':
-       plane->pln_effic = LIMIT_TO(arg, PLANE_MINEFF, 100);
-       break;
     case 'm':
-       plane->pln_mobil = LIMIT_TO(arg, -127, 127);
-       break;
+    case 'w':
+       return edit_unit((struct empobj *)plane, key, p,
+                        PLANE_MINEFF, "wing",
+                        plane->pln_ship >= 0 || plane->pln_land >= 0);
     case 't':
        arg = LIMIT_TO(arg, pcp->pl_tech, SHRT_MAX);
+       divine_unit_change((struct empobj *)plane, "Tech level",
+                          arg != plane->pln_tech, arg - plane->pln_tech,
+                          "from %d to %d", plane->pln_tech, arg);
        pln_set_tech(plane, arg);
        break;
-    case 'w':
-       if (p[0] == '~')
-           plane->pln_wing = 0;
-       else if (isalpha(p[0]))
-           plane->pln_wing = p[0];
-       else {
-           pr("%c: invalid wing\n", p[0]);
-           return RET_FAIL;
-       }
-       break;
     case 'r':
        arg = LIMIT_TO(arg, 0, pl_range(pcp, plane->pln_tech));
+       divine_unit_change((struct empobj *)plane, "Range",
+                          arg != plane->pln_range, 0,
+                          "from %d to %d", plane->pln_range, arg);
        plane->pln_range = (unsigned char)arg;
        break;
     case 's':
        if (arg < -1 || arg >= ef_nelem(EF_SHIP))
            return RET_SYN;
-       if (arg >= 0 && arg != plane->pln_ship)
+       if (arg == plane->pln_ship) {
+           pr("Ship of %s unchanged\n", prplane(plane));
+           break;
+       }
+       divine_unload((struct empobj *)plane, EF_SHIP, plane->pln_ship);
+       if (arg >= 0) {
+           divine_unload((struct empobj *)plane, EF_LAND, plane->pln_land);
            plane->pln_land = -1;
+       }
+       divine_load((struct empobj *)plane, EF_SHIP, arg);
        plane->pln_ship = arg;
        break;
     case 'y':
        if (arg < -1 || arg >= ef_nelem(EF_LAND))
            return RET_SYN;
-       if (arg >= 0 && arg != plane->pln_land)
+       if (arg == plane->pln_land) {
+           pr("Land unit of %s unchanged\n", prplane(plane));
+           break;
+       }
+       divine_unload((struct empobj *)plane, EF_LAND, plane->pln_land);
+       if (arg >= 0) {
+           divine_unload((struct empobj *)plane, EF_SHIP, plane->pln_ship);
            plane->pln_ship = -1;
+       }
+       divine_load((struct empobj *)plane, EF_LAND, arg);
        plane->pln_land = arg;
        break;
     case 'f':
+       divine_flag_change((struct empobj *)plane, "Flags",
+                          plane->pln_flags, arg, plane_flags);
        plane->pln_flags = arg;
        break;
     default: