]> granicus.if.org Git - nethack/commitdiff
Adjust shopkeeper damage fixing
authorPasi Kallinen <paxed@alt.org>
Fri, 25 Jun 2021 16:50:02 +0000 (19:50 +0300)
committerPasi Kallinen <paxed@alt.org>
Fri, 25 Jun 2021 17:02:06 +0000 (20:02 +0300)
Allow shopkeeper to remove webs and pits.

Change the damage fix messaging to be more specific when
shopkeeper removes a trap. Before this the message was
"A trap was removed from the floor", which sounds really silly
when it comes to holes.

Change the damage fixing so the shopkeeper will fix one damage spot
at a time (instead of all at once), so it's more like a monster action.

Some code cleanup, splitting into smaller functions.

While doing this, I noticed that shopkeepers don't actually bill
the hero for the damage, but that'll have to be another commit...

doc/fixes37.0
include/extern.h
include/hack.h
src/dig.c
src/monmove.c
src/polyself.c
src/restore.c
src/shk.c

index c00799c406656a4c72b2e235562b33be58ae912c..e08eefa4f2b861594f682642b56040461dc9478f 100644 (file)
@@ -549,6 +549,7 @@ make fire-command autowield an appropriate launcher and add fireassist boolean
        option to toggle the assistance off
 Angels and priests were always described as "the {Angel,priest,high priest} of
        <deity>" when first two should have been "{an Angel,a high priest}..."
+shopkeepers can remove pits and webs
 
 
 Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
index 43ba4d2a13b35061422e53101110166f5f8aee1a..15480b6d445732bc4446fb86732cff076600aa0d 100644 (file)
@@ -2342,7 +2342,7 @@ extern void sellobj(struct obj *, xchar, xchar);
 extern int doinvbill(int);
 extern struct monst *shkcatch(struct obj *, xchar, xchar);
 extern void add_damage(xchar, xchar, long);
-extern int repair_damage(struct monst *, struct damage *, int *, boolean);
+extern int repair_damage(struct monst *, struct damage *, boolean);
 extern int shk_move(struct monst *);
 extern void after_shk_move(struct monst *);
 extern boolean is_fshk(struct monst *);
index 17aaa1243fecf7ec01733ac8dd266c3bdc994129..792d02d561e9f43ad5b6e57929208960748d78d4 100644 (file)
@@ -50,6 +50,8 @@ enum encumbrance_types {
 #define SHOP_HOLE_COST 200L /* cost of making hole/trapdoor */
 #define SHOP_WALL_COST 200L /* cost of destroying a wall */
 #define SHOP_WALL_DMG  (10L * ACURRSTR) /* damaging a wall */
+#define SHOP_PIT_COST  100L /* cost of making a pit */
+#define SHOP_WEB_COST   30L /* cost of removing a web */
 
 /* hunger states - see hu_stat in eat.c */
 enum hunger_state_types {
index 16948cd7744060baa30e4b2c7def682e0a49bdbd..060f5433fcdcc1d21800fe54d1740b3b195edbef 100644 (file)
--- a/src/dig.c
+++ b/src/dig.c
@@ -606,6 +606,8 @@ digactualhole(int x, int y, struct monst *madeby, int ttyp)
                 You("dig a pit in the %s.", surface_type);
             if (shopdoor)
                 pay_for_damage("ruin", FALSE);
+            else
+                add_damage(x, y, madeby_u ? SHOP_PIT_COST : 0L);
         } else if (!madeby_obj && canseemon(madeby)) {
             pline("%s digs a pit in the %s.", Monnam(madeby), surface_type);
         } else if (cansee(x, y) && flags.verbose) {
@@ -1191,8 +1193,10 @@ use_pick_axe2(struct obj *obj)
             assign_level(&g.context.digging.level, &u.uz);
             g.context.digging.effort = 0;
             You("start %s downward.", verbing);
-            if (*u.ushops)
+            if (*u.ushops) {
                 shopdig(0);
+                add_damage(u.ux, u.uy, SHOP_PIT_COST);
+            }
         } else
             You("continue %s downward.", verbing);
         g.did_dig_msg = FALSE;
index f7cf941ad9b4f84bc117c666eacc00fbe3e94935..43c43689c5b9f7a236c9b9115ccf860360f9547e 100644 (file)
@@ -907,6 +907,8 @@ maybe_spin_web(struct monst *mtmp)
                 pline("%s spins a web.", upstart(mbuf));
                 trap->tseen = 1;
             }
+            if (*in_rooms(mtmp->mx, mtmp->my, SHOPBASE))
+                add_damage(mtmp->mx, mtmp->my, 0L);
         }
     }
 }
index cb39e6e09a7f92f4f13951945683994b3895d50e..22e927caa8b6dc557a9b67b845aa3ba9333c3f77 100644 (file)
@@ -1338,6 +1338,8 @@ dospinweb(void)
     if (ttmp) {
         ttmp->madeby_u = 1;
         feeltrap(ttmp);
+        if (*in_rooms(u.ux, u.uy, SHOPBASE))
+            add_damage(u.ux, u.uy, SHOP_WEB_COST);
     }
     return 1;
 }
index 896384f12767cfb73ebb6fff57906160f4f1ddec..0e62ac05b027b2b6cf08263d2bb6efdd8d70f295 100644 (file)
@@ -175,7 +175,7 @@ restdamage(NHFILE* nhfp)
                 struct monst *shkp = shop_keeper(*shp);
 
                 if (shkp && inhishop(shkp)
-                    && repair_damage(shkp, tmp_dam, (int *) 0, TRUE))
+                    && repair_damage(shkp, tmp_dam, TRUE))
                     break;
             }
         }
index f4075b7234427238bd9be7046e1efd2f48d1e37a..e2caffee3234b369cf638c35385a808e92bb0e66 100644 (file)
--- a/src/shk.c
+++ b/src/shk.c
@@ -58,7 +58,12 @@ static long get_pricing_units(struct obj *);
 static boolean angry_shk_exists(void);
 static void rile_shk(struct monst *);
 static void rouse_shk(struct monst *, boolean);
-static void remove_damage(struct monst *, boolean);
+static boolean shk_impaired(struct monst *);
+static boolean repairable_damage(struct damage *, struct monst *);
+static struct damage *find_damage(struct monst *);
+static void discard_damage_struct(struct damage *);
+static void discard_damage_owned_by(struct monst *);
+static void shk_fixes_damage(struct monst *);
 static xchar *litter_getpos(int *, xchar, xchar, struct monst *);
 static void litter_scatter(xchar *, int, xchar, xchar, struct monst *);
 static void litter_newsyms(xchar *, xchar, xchar);
@@ -185,7 +190,7 @@ shkgone(struct monst* mtmp)
     /* [BUG: some of this should be done on the shop level */
     /*       even when the shk dies on a different level.] */
     if (on_level(&eshk->shoplevel, &u.uz)) {
-        remove_damage(mtmp, TRUE);
+        discard_damage_owned_by(mtmp);
         sroom->resident = (struct monst *) 0;
         if (!search_special(ANY_SHOP))
             g.level.flags.has_shop = 0;
@@ -3428,145 +3433,141 @@ add_damage(
         levl[x][y].seenv = SVALL;
 }
 
-/*
- * Do something about damage. Either (!croaked) try to repair it, or
- * (croaked) just discard damage structs for non-shared locations, since
- * they'll never get repaired. Assume that shared locations will get
- * repaired eventually by the other shopkeeper(s). This might be an erroneous
- * assumption (they might all be dead too), but we have no reasonable way of
- * telling that.
- */
-static
-void
-remove_damage(struct monst* shkp, boolean croaked)
+/* is shopkeeper impaired, so they cannot act? */
+static boolean
+shk_impaired(struct monst *shkp)
 {
-    struct damage *tmp_dam, *tmp2_dam;
-    struct obj *shk_inv = shkp->minvent;
-    boolean did_repair = FALSE, saw_door = FALSE, saw_floor = FALSE,
-            stop_picking = FALSE, doorway_trap = FALSE, skip_msg = FALSE;
-    int saw_walls = 0, saw_untrap = 0, feedback;
-    char trapmsg[BUFSZ];
-
-    feedback = !croaked; /* 1 => give feedback, 0 => don't or already did */
-    tmp_dam = g.level.damagelist;
-    tmp2_dam = 0;
-    while (tmp_dam) {
-        register xchar x = tmp_dam->place.x, y = tmp_dam->place.y;
-        char shops[5];
-        int disposition;
-        unsigned old_doormask = 0;
-
-        disposition = 0;
-        Strcpy(shops, in_rooms(x, y, SHOPBASE));
-        if (index(shops, ESHK(shkp)->shoproom)) {
-            if (IS_DOOR(levl[x][y].typ))
-                old_doormask = levl[x][y].doormask;
-
-            if (croaked) {
-                disposition = (shops[1]) ? 0 : 1;
-            } else if (stop_picking) {
-                disposition = repair_damage(shkp, tmp_dam, &feedback, FALSE);
-            } else {
-                /* Defer the stop_occupation() until after repair msgs */
-                if (closed_door(x, y))
-                    stop_picking = picking_at(x, y);
-                disposition = repair_damage(shkp, tmp_dam, &feedback, FALSE);
-                if (!disposition)
-                    stop_picking = FALSE;
-            }
-        }
+    if (!shkp || !shkp->isshk || !inhishop(shkp))
+        return TRUE;
+    if (shkp->msleeping || !shkp->mcanmove || ESHK(shkp)->following)
+        return TRUE;
+    return FALSE;
+}
 
-        if (!disposition) {
-            tmp2_dam = tmp_dam;
-            tmp_dam = tmp_dam->next;
-            continue;
-        }
+/* is damage dam repairable by shopkeeper shkp? */
+static boolean
+repairable_damage(struct damage *dam, struct monst *shkp)
+{
+    xchar x = dam->place.x, y = dam->place.y;
+    struct trap* ttmp;
+    struct monst *mtmp;
 
-        if (disposition > 1) {
-            did_repair = TRUE;
-            if (cansee(x, y)) {
-                if (IS_WALL(levl[x][y].typ)) {
-                    saw_walls++;
-                } else if (IS_DOOR(levl[x][y].typ)
-                           /* an existing door here implies trap removal */
-                           && !(old_doormask & (D_ISOPEN | D_CLOSED))) {
-                    saw_door = TRUE;
-                } else if (disposition == 3) { /* untrapped */
-                    saw_untrap++;
-                    if (IS_DOOR(levl[x][y].typ))
-                        doorway_trap = TRUE;
-                } else {
-                    saw_floor = TRUE;
-                }
-            }
-        }
+    if (!dam || shk_impaired(shkp))
+        return FALSE;
 
-        tmp_dam = tmp_dam->next;
-        if (!tmp2_dam) {
-            free((genericptr_t) g.level.damagelist);
-            g.level.damagelist = tmp_dam;
-        } else {
-            free((genericptr_t) tmp2_dam->next);
-            tmp2_dam->next = tmp_dam;
-        }
+    /* too soon to fix it? */
+    if ((g.monstermoves - dam->when) < REPAIR_DELAY)
+        return FALSE;
+    /* is it a wall? don't fix if anyone is in the way */
+    if (!IS_ROOM(dam->typ)) {
+        if ((x == u.ux && y == u.uy && !Passes_walls)
+            || (x == shkp->mx && y == shkp->my)
+            || ((mtmp = m_at(x, y)) != 0 && !passes_walls(mtmp->data)))
+            return FALSE;
     }
-    if (!did_repair)
+    /* is it a trap? don't fix if hero or monster is in it */
+    ttmp = t_at(x, y);
+    if (ttmp) {
+        if (x == u.ux && y == u.uy)
+            return FALSE;
+        if ((mtmp = m_at(x,y)) != 0 && mtmp->mtrapped)
+            return FALSE;
+    }
+    /* does it belong to shkp? */
+    if (!index(in_rooms(x, y, SHOPBASE), ESHK(shkp)->shoproom))
+        return FALSE;
+
+    return TRUE;
+}
+
+/* find any damage shopkeeper shkp could repair. returns NULL is none found */
+static struct damage *
+find_damage(struct monst *shkp)
+{
+    struct damage *dam = g.level.damagelist;
+
+    if (shk_impaired(shkp))
+        return NULL;
+
+    while (dam) {
+        if (repairable_damage(dam, shkp))
+            return dam;
+
+        dam = dam->next;
+    }
+
+    return NULL;
+}
+
+static void
+discard_damage_struct(struct damage *dam)
+{
+    if (!dam)
         return;
 
-    trapmsg[0] = '\0'; /* not just lint suppression... */
-    shk_inv = (shkp->minvent != shk_inv) ? shkp->minvent : 0;
-    if (saw_untrap == 1 && shk_inv
-        && (shk_inv->otyp == BEARTRAP || shk_inv->otyp == LAND_MINE)
-        && canseemon(shkp)) {
-        pline("%s untraps %s.", Shknam(shkp), ansimpleoname(shk_inv));
-        /* we've already reported this trap (and know it's the only one) */
-        saw_untrap = 0;
-        skip_msg = !(saw_walls || saw_door || saw_floor);
-    } else if (saw_untrap) {
-        Sprintf(trapmsg, "%s trap%s",
-                (saw_untrap > 3) ? "several" : (saw_untrap > 1) ? "some"
-                                                                : "a",
-                plur(saw_untrap));
-        Sprintf(eos(trapmsg), " %s", vtense(trapmsg, "are"));
-        Sprintf(eos(trapmsg), " removed from the %s",
-                (doorway_trap && saw_untrap == 1) ? "doorway" : "floor");
-    }
-
-    if (skip_msg) {
-        ; /* already gave an untrap message which covered the only repair */
-    } else if (saw_walls) {
-        char wallbuf[BUFSZ];
-
-        Sprintf(wallbuf, "section%s", plur(saw_walls));
-        pline("Suddenly, %s %s of wall %s up!",
-              (saw_walls == 1) ? "a" : (saw_walls <= 3) ? "some" : "several",
-              wallbuf, vtense(wallbuf, "close"));
-
-        if (saw_door)
-            pline_The("shop door reappears!");
-        if (saw_floor)
-            pline_The("floor is repaired!");
-        if (saw_untrap)
-            pline("%s!", upstart(trapmsg));
+    if (dam == g.level.damagelist) {
+        g.level.damagelist = dam->next;
     } else {
-        if (saw_door || saw_floor || saw_untrap)
-            pline("Suddenly, %s%s%s%s%s!",
-                  saw_door ? "the shop door reappears" : "",
-                  (saw_door && saw_floor) ? " and " : "",
-                  saw_floor ? "the floor damage is gone" : "",
-                  ((saw_door || saw_floor) && *trapmsg) ? " and " : "",
-                  trapmsg);
-        /* FIXME:
-         *  these messages aren't right if the unseen repairs were only
-         *  for trap removal (except for hole and possibly trap door).
-         */
-        else if (inside_shop(u.ux, u.uy) == ESHK(shkp)->shoproom)
-            You_feel("more claustrophobic than before.");
-        else if (!Deaf && !rn2(10))
-            Norep("The dungeon acoustics noticeably change.");
+        struct damage *prev = g.level.damagelist;
+
+        while (prev && prev->next != dam)
+            prev = prev->next;
+        if (prev)
+            prev->next = dam->next;
     }
-    if (stop_picking)
-        stop_occupation();
+    (void) memset(dam, 0, sizeof(struct damage));
+    free((genericptr_t) dam);
+}
+
+/* discard all damage structs owned by shopkeeper */
+static void
+discard_damage_owned_by(struct monst *shkp)
+{
+    struct damage *dam = g.level.damagelist, *dam2, *prevdam = NULL;
+
+    while (dam) {
+        xchar x = dam->place.x, y = dam->place.y;
+
+        if (index(in_rooms(x, y, SHOPBASE), ESHK(shkp)->shoproom)) {
+            dam2 = dam->next;
+            if (prevdam)
+                prevdam->next = dam2;
+            if (dam == g.level.damagelist)
+                g.level.damagelist = dam2;
+            (void) memset(dam, 0, sizeof(struct damage));
+            free((genericptr_t) dam);
+            dam = dam2;
+        } else {
+            prevdam = dam;
+            dam2 = dam->next;
+        }
+
+        dam = dam2;
+    }
+}
+
+/* Shopkeeper tries to repair damage belonging to them */
+static void
+shk_fixes_damage(struct monst* shkp)
+{
+    struct damage *dam = find_damage(shkp);
+    boolean shk_closeby;
+
+    if (!dam)
+        return;
+
+    shk_closeby = (distu(shkp->mx, shkp->my)
+                   <= (BOLT_LIM / 2) * (BOLT_LIM / 2));
+
+    if (canseemon(shkp))
+        pline("%s whispers %s.", Shknam(shkp),
+              shk_closeby ? "an incantation" : "something");
+    else if (!Deaf && shk_closeby)
+        You_hear("someone muttering an incantation.");
+
+    (void) repair_damage(shkp, dam, FALSE);
+
+    discard_damage_struct(dam);
 }
 
 #define LITTER_UPDATE 0x01
@@ -3685,55 +3686,50 @@ int
 repair_damage(
     struct monst *shkp,
     struct damage *tmp_dam,
-    int *once,
     boolean catchup) /* restoring a level */
 {
     xchar x, y;
     xchar *litter;
-    struct monst *mtmp;
     struct obj *otmp;
     struct trap *ttmp;
     int k, disposition = 1;
+    boolean stop_picking = FALSE;
 
-    if ((g.monstermoves - tmp_dam->when) < REPAIR_DELAY)
-        return 0;
-    if (shkp->msleeping || !shkp->mcanmove || ESHK(shkp)->following)
+    if (!repairable_damage(tmp_dam, shkp))
         return 0;
+
     x = tmp_dam->place.x;
     y = tmp_dam->place.y;
-    if (!IS_ROOM(tmp_dam->typ)) {
-        if ((x == u.ux && y == u.uy && !Passes_walls)
-            || (x == shkp->mx && y == shkp->my)
-            || ((mtmp = m_at(x, y)) != 0 && !passes_walls(mtmp->data)))
-            return 0;
-    }
-    ttmp = t_at(x, y);
-    if (ttmp && x == u.ux && y == u.uy && !Passes_walls)
-        return 0;
 
-    if (once && *once) {
-        boolean shk_closeby = (distu(shkp->mx, shkp->my)
-                               <= (BOLT_LIM / 2) * (BOLT_LIM / 2));
+    ttmp = t_at(x, y);
 
-        /* this is suboptimal if we eventually give a "shk untraps"
-           message for the only repair, but perhaps the shop repair
-           incantation means that shk's untrap attempt will never fail */
-        if (canseemon(shkp))
-            pline("%s whispers %s.", Shknam(shkp),
-                  shk_closeby ? "an incantation" : "something");
-        else if (!Deaf && shk_closeby)
-            You_hear("someone muttering an incantation.");
-        *once = 0;
-    }
     if (ttmp) {
-        if ((ttmp->ttyp == LANDMINE || ttmp->ttyp == BEAR_TRAP)
-            && dist2(x, y, shkp->mx, shkp->my) <= 2) {
+        switch (ttmp->ttyp) {
+        case LANDMINE:
+        case BEAR_TRAP:
             /* convert to an object */
             otmp = mksobj((ttmp->ttyp == LANDMINE) ? LAND_MINE : BEARTRAP,
                           TRUE, FALSE);
             otmp->quan = 1L;
             otmp->owt = weight(otmp);
+            if (!catchup) {
+                if (canseemon(shkp) && dist2(x, y, shkp->mx, shkp->my) <= 2)
+                    pline("%s untraps %s.", Shknam(shkp), ansimpleoname(otmp));
+                else if (ttmp->tseen && cansee(ttmp->tx, ttmp->ty))
+                    pline("The %s vanishes.", trapname(ttmp->ttyp, TRUE));
+            }
             (void) mpickobj(shkp, otmp);
+            break;
+        case HOLE:
+        case PIT:
+        case SPIKED_PIT:
+            if (!catchup && ttmp->tseen && cansee(ttmp->tx, ttmp->ty))
+                pline("The %s is filled in.", trapname(ttmp->ttyp, TRUE));
+            break;
+        default:
+            if (!catchup && ttmp->tseen && cansee(ttmp->tx, ttmp->ty))
+                pline("The %s vanishes.", trapname(ttmp->ttyp, TRUE));
+            break;
         }
         deltrap(ttmp);
         if (cansee(x, y))
@@ -3747,6 +3743,9 @@ repair_damage(
         /* no terrain fix necessary (trap removal or manually repaired) */
         return disposition;
 
+    if (closed_door(x, y))
+        stop_picking = picking_at(x, y);
+
     /* door or wall repair; trap, if any, is now gone;
        restore original terrain type and move any items away */
     levl[x][y].typ = tmp_dam->typ;
@@ -3761,12 +3760,24 @@ repair_damage(
 
     block_point(x, y);
     if (cansee(x, y)) {
-        if (IS_WALL(tmp_dam->typ))
+        if (IS_WALL(tmp_dam->typ)) {
             /* player sees actual repair process, so KNOWS it's a wall */
             levl[x][y].seenv = SVALL;
+            pline("Suddenly, a section of the wall closes up!");
+        } else if (IS_DOOR(tmp_dam->typ)) {
+            pline("Suddenly, the shop door reappears!");
+        }
         newsym(x, y);
+    } else if (IS_WALL(tmp_dam->typ)) {
+        if (inside_shop(u.ux, u.uy) == ESHK(shkp)->shoproom)
+            You_feel("more claustrophobic than before.");
+        else if (!Deaf && !rn2(10))
+            Norep("The dungeon acoustics noticeably change.");
     }
 
+    if (stop_picking)
+        stop_occupation();
+
     litter_newsyms(litter, x, y);
 
     if (disposition < 3)
@@ -3791,7 +3802,7 @@ shk_move(struct monst* shkp)
     omy = shkp->my;
 
     if (inhishop(shkp))
-        remove_damage(shkp, FALSE);
+        shk_fixes_damage(shkp);
 
     if ((udist = distu(omx, omy)) < 3 && (shkp->data != &mons[PM_GRID_BUG]
                                           || (omx == u.ux || omy == u.uy))) {