diff --git a/doc/fixes37.0 b/doc/fixes37.0 index c00799c40..e08eefa4f 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -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 " 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 diff --git a/include/extern.h b/include/extern.h index 43ba4d2a1..15480b6d4 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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 *); diff --git a/include/hack.h b/include/hack.h index 17aaa1243..792d02d56 100644 --- a/include/hack.h +++ b/include/hack.h @@ -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 { diff --git a/src/dig.c b/src/dig.c index 16948cd77..060f5433f 100644 --- 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; diff --git a/src/monmove.c b/src/monmove.c index f7cf941ad..43c43689c 100644 --- a/src/monmove.c +++ b/src/monmove.c @@ -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); } } } diff --git a/src/polyself.c b/src/polyself.c index cb39e6e09..22e927caa 100644 --- a/src/polyself.c +++ b/src/polyself.c @@ -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; } diff --git a/src/restore.c b/src/restore.c index 896384f12..0e62ac05b 100644 --- a/src/restore.c +++ b/src/restore.c @@ -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; } } diff --git a/src/shk.c b/src/shk.c index f4075b723..e2caffee3 100644 --- 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]; + if (!shkp || !shkp->isshk || !inhishop(shkp)) + return TRUE; + if (shkp->msleeping || !shkp->mcanmove || ESHK(shkp)->following) + return TRUE; + return FALSE; +} - 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; +/* 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; - 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 (!dam || shk_impaired(shkp)) + return FALSE; - 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 (!disposition) { - tmp2_dam = tmp_dam; - tmp_dam = tmp_dam->next; - continue; - } - - 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; - } - } - } - - 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)); - - /* 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))) {