diff --git a/doc/fixes35.0 b/doc/fixes35.0 index a38bbbeaa..771d4d470 100644 --- a/doc/fixes35.0 +++ b/doc/fixes35.0 @@ -520,6 +520,8 @@ adopt/adapt/improve the Paranoid_Quit patch; default is paranoid_confirm:pray paranoid_confirm:pray y to confirm #pray; supersedes prayconfirm paranoid_confirm:Remove always pick from inventory for 'R' and 'T' flexibility for specifying "detect " vs " detection" when wishing +when a sokoban puzzle has been completed (last pit or hole filled in), + stop assessing luck penalties and lift most movement restrictions Platform- and/or Interface-Specific New Features diff --git a/include/extern.h b/include/extern.h index c99cc0714..d029c70d6 100644 --- a/include/extern.h +++ b/include/extern.h @@ -2318,6 +2318,7 @@ E void NDECL(force_launch_placement); E boolean FDECL(uteetering_at_seen_pit, (struct trap *)); E boolean NDECL(lava_effects); E void NDECL(sink_into_lava); +E void NDECL(sokoban_guilt); /* ### u_init.c ### */ diff --git a/include/patchlevel.h b/include/patchlevel.h index 9d2c149fb..dc3ddb294 100644 --- a/include/patchlevel.h +++ b/include/patchlevel.h @@ -13,7 +13,7 @@ * Incrementing EDITLEVEL can be used to force invalidation of old bones * and save files. */ -#define EDITLEVEL 46 +#define EDITLEVEL 47 #define COPYRIGHT_BANNER_A \ "NetHack, Copyright 1985-2011" diff --git a/include/rm.h b/include/rm.h index c3a9e4eb8..2dc83368b 100644 --- a/include/rm.h +++ b/include/rm.h @@ -512,8 +512,9 @@ struct levelflags { Bitfield(hero_memory,1); /* hero has memory */ Bitfield(shortsighted,1); /* monsters are shortsighted */ Bitfield(graveyard,1); /* has_morgue, but remains set */ - Bitfield(is_maze_lev,1); + Bitfield(sokoban_rules,1); /* fill pits and holes w/ boulders */ + Bitfield(is_maze_lev,1); Bitfield(is_cavernous_lev,1); Bitfield(arboreal, 1); /* Trees replace rock */ }; @@ -578,4 +579,7 @@ extern dlevel_t level; /* structure describing the current level */ #define m_buried_at(x,y) (MON_BURIED_AT(x,y) ? level.monsters[x][y] : \ (struct monst *)0) +/* restricted movement, potential luck penalties */ +#define Sokoban level.flags.sokoban_rules + #endif /* RM_H */ diff --git a/src/apply.c b/src/apply.c index 979a63860..5c3eee86e 100644 --- a/src/apply.c +++ b/src/apply.c @@ -1526,12 +1526,8 @@ int magic; /* 0=Physical, otherwise skill level */ if (range < temp) range = temp; (void) walk_path(&uc, &cc, hurtle_step, (genericptr_t)&range); - - /* A little Sokoban guilt... */ - if (In_sokoban(&u.uz)) - change_luck(-1); - teleds(cc.x, cc.y, TRUE); + sokoban_guilt(); nomul(-1); nomovemsg = ""; morehungry(rnd(25)); diff --git a/src/ball.c b/src/ball.c index 66b28f2a5..84972fd4b 100644 --- a/src/ball.c +++ b/src/ball.c @@ -1,5 +1,4 @@ /* NetHack 3.5 ball.c $Date$ $Revision$ */ -/* SCCS Id: @(#)ball.c 3.5 2007/03/24 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -740,8 +739,7 @@ xchar x, y; newsym(u.ux0,u.uy0); /* clean up old position */ if (u.ux0 != u.ux || u.uy0 != u.uy) { spoteffects(TRUE); - if (In_sokoban(&u.uz)) - change_luck(-1); /* Sokoban guilt */ + sokoban_guilt(); } } } diff --git a/src/cmd.c b/src/cmd.c index 320456938..c1ffd7be6 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -892,6 +892,7 @@ wiz_map_terrain(VOID_ARGS) if (level.flags.is_maze_lev) Strcat(dsc, " maze"); if (level.flags.is_cavernous_lev) Strcat(dsc, " cave"); if (level.flags.arboreal) Strcat(dsc, " tree"); + if (Sokoban) Strcat(dsc, " sokoban-rules"); /* non-flag info; probably should include dungeon branching checks (extra stairs and magic portals) here */ if (Invocation_lev(&u.uz)) Strcat(dsc, " invoke"); diff --git a/src/detect.c b/src/detect.c index 038914c68..bb77ec0e3 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1,5 +1,4 @@ /* NetHack 3.5 detect.c $Date$ $Revision$ */ -/* SCCS Id: @(#)detect.c 3.5 2007/11/05 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1363,15 +1362,17 @@ sokoban_detect() levl[x][y].seenv = SVALL; levl[x][y].waslit = TRUE; map_background(x, y, 1); - for (obj = level.objects[x][y]; obj; obj = obj->nexthere) - if (obj->otyp == BOULDER) - map_object(obj, 1); + if ((obj = sobj_at(BOULDER, x, y)) != 0) + map_object(obj, 1); } /* Map the traps */ for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) { ttmp->tseen = 1; map_trap(ttmp, 1); + /* set sokoban_rules when there is at least one pit or hole */ + if (ttmp->ttyp == PIT || ttmp->ttyp == HOLE) + Sokoban = 1; } } diff --git a/src/dothrow.c b/src/dothrow.c index cc94a5c2b..7f6c63c99 100644 --- a/src/dothrow.c +++ b/src/dothrow.c @@ -596,7 +596,7 @@ hurtle_step(arg, x, y) if ((u.ux - x) && (u.uy - y) && bad_rock(youmonst.data,u.ux,y) && bad_rock(youmonst.data,x,u.uy)) { /* Move at a diagonal. */ - if (In_sokoban(&u.uz)) { + if (Sokoban) { You("come to an abrupt halt!"); return FALSE; } @@ -624,7 +624,7 @@ hurtle_step(arg, x, y) dotrap(ttmp,0); } else if ((ttmp->ttyp == PIT || ttmp->ttyp == SPIKED_PIT || ttmp->ttyp == HOLE || ttmp->ttyp == TRAPDOOR) && - In_sokoban(&u.uz)) { + Sokoban) { /* Air currents overcome the recoil */ dotrap(ttmp,0); *range = 0; @@ -711,8 +711,7 @@ hurtle(dx, dy, range, verbose) You("%s in the opposite direction.", range > 1 ? "hurtle" : "float"); /* if we're in the midst of shooting multiple projectiles, stop */ endmultishot(TRUE); - if (In_sokoban(&u.uz)) - change_luck(-1); /* Sokoban guilt */ + sokoban_guilt(); uc.x = u.ux; uc.y = u.uy; /* this setting of cc is only correct if dx and dy are [-1,0,1] only */ diff --git a/src/hack.c b/src/hack.c index d86a39333..77820e514 100644 --- a/src/hack.c +++ b/src/hack.c @@ -1,5 +1,4 @@ /* NetHack 3.5 hack.c $Date$ $Revision$ */ -/* SCCS Id: @(#)hack.c 3.5 2008/01/22 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -135,7 +134,7 @@ moverock() mtmp = m_at(rx, ry); /* KMH -- Sokoban doesn't let you push boulders diagonally */ - if (In_sokoban(&u.uz) && u.dx && u.dy) { + if (Sokoban && u.dx && u.dy) { if (Blind) feel_location(sx,sy); pline("%s won't roll diagonally on this %s.", The(xname(otmp)), surface(sx, sy)); @@ -319,17 +318,15 @@ moverock() #ifdef STEED if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) { You("aren't skilled enough to %s %s from %s.", - (flags.pickup && !In_sokoban(&u.uz)) - ? "pick up" : "push aside", + (flags.pickup && !Sokoban) ? "pick up" : "push aside", the(xname(otmp)), y_monnam(u.usteed)); } else #endif { pline("However, you can easily %s.", - (flags.pickup && !In_sokoban(&u.uz)) - ? "pick it up" : "push it aside"); - if (In_sokoban(&u.uz)) - change_luck(-1); /* Sokoban guilt */ + (flags.pickup && !Sokoban) ? + "pick it up" : "push it aside"); + sokoban_guilt(); break; } break; @@ -344,8 +341,7 @@ moverock() && IS_ROCK(levl[sx][u.uy].typ)))) || verysmall(youmonst.data))) { pline("However, you can squeeze yourself into a small opening."); - if (In_sokoban(&u.uz)) - change_luck(-1); /* Sokoban guilt */ + sokoban_guilt(); break; } else return (-1); @@ -571,7 +567,7 @@ bad_rock(mdat,x,y) struct permonst *mdat; register xchar x,y; { - return((boolean) ((In_sokoban(&u.uz) && sobj_at(BOULDER,x,y)) || + return((boolean) ((Sokoban && sobj_at(BOULDER,x,y)) || (IS_ROCK(levl[x][y].typ) && (!tunnels(mdat) || needspick(mdat) || !may_dig(x,y)) && !(passes_walls(mdat) && may_passwall(x,y))))); @@ -598,7 +594,7 @@ struct monst *mon; if (amt > 600) return 2; /* Sokoban restriction applies to hero only */ - if (mon == &youmonst && In_sokoban(&u.uz)) return 3; + if (mon == &youmonst && Sokoban) return 3; /* can squeeze through */ return 0; @@ -647,6 +643,7 @@ int mode; if (mode == DO_MOVE) { if (Is_stronghold(&u.uz) && is_db_wall(x,y)) pline_The("drawbridge is up!"); + /* sokoban restriction stays even after puzzle is solved */ if (Passes_walls && !may_passwall(x,y) && In_sokoban(&u.uz)) pline_The("Sokoban walls resist your ability."); } @@ -737,13 +734,13 @@ int mode; return FALSE; } - if (sobj_at(BOULDER,x,y) && (In_sokoban(&u.uz) || !Passes_walls)) { + if (sobj_at(BOULDER,x,y) && (Sokoban || !Passes_walls)) { if (!(Blind || Hallucination) && (context.run >= 2) && mode != TEST_TRAV) return FALSE; if (mode == DO_MOVE) { /* tunneling monsters will chew before pushing */ if (tunnels(youmonst.data) && !needspick(youmonst.data) && - !In_sokoban(&u.uz)) { + !Sokoban) { if (still_chewing(x,y)) return FALSE; } else if (moverock() < 0) return FALSE; @@ -751,7 +748,7 @@ int mode; struct obj* obj; /* don't pick two boulders in a row, unless there's a way thru */ - if (sobj_at(BOULDER,ux,uy) && !In_sokoban(&u.uz)) { + if (sobj_at(BOULDER,ux,uy) && !Sokoban) { if (!Passes_walls && !(tunnels(youmonst.data) && !needspick(youmonst.data)) && !carrying(PICK_AXE) && !carrying(DWARVISH_MATTOCK) && diff --git a/src/mklev.c b/src/mklev.c index f510b97a8..813e7fc89 100644 --- a/src/mklev.c +++ b/src/mklev.c @@ -1,5 +1,4 @@ /* NetHack 3.5 mklev.c $Date$ $Revision$ */ -/* SCCS Id: @(#)mklev.c 3.5 2009/02/21 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -590,9 +589,10 @@ clear_level_structures() level.flags.nommap = 0; level.flags.hero_memory = 1; level.flags.shortsighted = 0; - level.flags.arboreal = 0; + level.flags.sokoban_rules = 0; level.flags.is_maze_lev = 0; level.flags.is_cavernous_lev = 0; + level.flags.arboreal = 0; nroom = 0; rooms[0].hx = -1; diff --git a/src/mon.c b/src/mon.c index 12a6ebaed..e36482dd1 100644 --- a/src/mon.c +++ b/src/mon.c @@ -1161,7 +1161,7 @@ impossible("A monster looked at a very strange trap of type %d.", ttmp->ttyp); || (!is_flyer(mdat) && !is_floater(mdat) && !is_clinger(mdat)) - || In_sokoban(&u.uz)) + || Sokoban) && (ttmp->ttyp != SLP_GAS_TRAP || !resists_sleep(mon)) && (ttmp->ttyp != BEAR_TRAP || diff --git a/src/monmove.c b/src/monmove.c index 45aa8443f..531d8a789 100644 --- a/src/monmove.c +++ b/src/monmove.c @@ -832,7 +832,7 @@ not_special: && pctload < 75); likeobjs = (likes_objs(ptr) && pctload < 75); likemagic = (likes_magic(ptr) && pctload < 85); - likerock = (throws_rocks(ptr) && pctload < 50 && !In_sokoban(&u.uz)); + likerock = (throws_rocks(ptr) && pctload < 50 && !Sokoban); conceals = hides_under(ptr); setlikes = TRUE; } @@ -1247,8 +1247,7 @@ postmov: && pctload < 75); likeobjs = (likes_objs(ptr) && pctload < 75); likemagic = (likes_magic(ptr) && pctload < 85); - likerock = (throws_rocks(ptr) && pctload < 50 && - !In_sokoban(&u.uz)); + likerock = (throws_rocks(ptr) && pctload < 50 && !Sokoban); conceals = hides_under(ptr); } diff --git a/src/muse.c b/src/muse.c index 3619d1f01..ff6c20a75 100644 --- a/src/muse.c +++ b/src/muse.c @@ -1,5 +1,4 @@ /* NetHack 3.5 muse.c $Date$ $Revision$ */ -/* SCCS Id: @(#)muse.c 3.5 2007/04/14 */ /* Copyright (C) 1990 by Ken Arromdee */ /* NetHack may be freely redistributed. See license for details. */ @@ -448,7 +447,7 @@ struct monst *mtmp; && !mtmp->isshk && !mtmp->isgd && !mtmp->ispriest && !is_floater(mtmp->data) /* monsters digging in Sokoban can ruin things */ - && !In_sokoban(&u.uz) + && !Sokoban /* digging wouldn't be effective; assume they know that */ && !(levl[x][y].wall_info & W_NONDIGGABLE) && !(Is_botlevel(&u.uz) || In_endgame(&u.uz)) diff --git a/src/pickup.c b/src/pickup.c index 0871b8ff9..bcf9f4c52 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -1225,7 +1225,7 @@ boolean telekinesis; { int result, old_wt, new_wt, prev_encumbr, next_encumbr; - if (obj->otyp == BOULDER && In_sokoban(&u.uz)) { + if (obj->otyp == BOULDER && Sokoban) { You("cannot get your %s around this %s.", body_part(HAND), xname(obj)); return -1; diff --git a/src/read.c b/src/read.c index b139e8266..70ede8718 100644 --- a/src/read.c +++ b/src/read.c @@ -563,7 +563,7 @@ forget_map(howmuch) { register int zx, zy; - if (In_sokoban(&u.uz)) + if (Sokoban) return; known = TRUE; @@ -1329,17 +1329,16 @@ struct obj *sobj; /* scroll, or fake spellbook object for scroll-like spell */ #ifdef REINCARNATION !Is_rogue_level(&u.uz) && #endif - (!In_endgame(&u.uz) || Is_earthlevel(&u.uz))) { - register int x, y; + (!In_endgame(&u.uz) || Is_earthlevel(&u.uz))) { + register int x, y; - /* Identify the scroll */ - pline_The("%s rumbles %s you!", ceiling(u.ux,u.uy), - sblessed ? "around" : "above"); - known = 1; - if (In_sokoban(&u.uz)) - change_luck(-1); /* Sokoban guilt */ + /* Identify the scroll */ + pline_The("%s rumbles %s you!", ceiling(u.ux,u.uy), + sblessed ? "around" : "above"); + known = 1; + sokoban_guilt(); - /* Loop through the surrounding squares */ + /* Loop through the surrounding squares */ if (!scursed) for (x = u.ux-1; x <= u.ux+1; x++) { for (y = u.uy-1; y <= u.uy+1; y++) { diff --git a/src/trap.c b/src/trap.c index 44123680a..67371fcd6 100644 --- a/src/trap.c +++ b/src/trap.c @@ -30,10 +30,11 @@ STATIC_DCL void FDECL(join_adjacent_pits, (struct trap *)); #endif STATIC_DCL void FDECL(clear_conjoined_pits, (struct trap *)); #ifdef STEED -STATIC_OVL int FDECL(steedintrap, (struct trap *, struct obj *)); -STATIC_OVL boolean FDECL(keep_saddle_with_steedcorpse, +STATIC_DCL int FDECL(steedintrap, (struct trap *, struct obj *)); +STATIC_DCL boolean FDECL(keep_saddle_with_steedcorpse, (unsigned, struct obj *, struct obj *)); #endif +STATIC_DCL void NDECL(maybe_finish_sokoban); /* mintrap() should take a flags argument, but for time being we use this */ STATIC_VAR int force_mintrap = 0; @@ -328,8 +329,7 @@ register int x, y, typ; unearth_objs(x, y); break; } - if (ttmp->ttyp == HOLE) ttmp->tseen = 1; /* You can't hide a hole */ - else ttmp->tseen = 0; + ttmp->tseen = (ttmp->ttyp == HOLE); /* hide non-holes */ ttmp->once = 0; ttmp->madeby_u = 0; ttmp->dst.dnum = -1; @@ -337,6 +337,11 @@ register int x, y, typ; if (!oldplace) { ttmp->ntrap = ftrap; ftrap = ttmp; + } else { + /* oldplace; + it shouldn't be possible to override a sokoban pit or hole + with some other trap, but we'll check just to be safe */ + if (Sokoban) maybe_finish_sokoban(); } return(ttmp); } @@ -350,8 +355,9 @@ boolean td; /* td == TRUE : trap door or hole */ const char *dont_fall = 0; int newlevel, bottom; - /* KMH -- You can't escape the Sokoban level traps */ - if(Blind && Levitation && !In_sokoban(&u.uz)) return; + /* we'll fall even while levitating in Sokoban; otherwise, if we + won't fall and won't be told that we aren't falling, give up now */ + if (Blind && Levitation && !Sokoban) return; bottom = dunlevs_in_dungeon(&u.uz); /* when in the upper half of the quest, don't fall past the @@ -370,8 +376,9 @@ boolean td; /* td == TRUE : trap door or hole */ if(td) { struct trap *t = t_at(u.ux,u.uy); + feeltrap(t); - if (!In_sokoban(&u.uz)) { + if (!Sokoban) { if (t->ttyp == TRAPDOOR) pline("A trap door opens up under you!"); else @@ -379,7 +386,7 @@ boolean td; /* td == TRUE : trap door or hole */ } } else pline_The("%s opens up under you!", surface(u.ux,u.uy)); - if (In_sokoban(&u.uz) && Can_fall_thru(&u.uz)) + if (Sokoban && Can_fall_thru(&u.uz)) ; /* KMH -- You can't escape the Sokoban level traps */ else if(Levitation || u.ustuck || (!Can_fall_thru(&u.uz) && !levl[u.ux][u.uy].candig) @@ -694,7 +701,7 @@ unsigned trflags; nomul(0); /* KMH -- You can't escape the Sokoban level traps */ - if (In_sokoban(&u.uz) && + if (Sokoban && (ttype == PIT || ttype == SPIKED_PIT || ttype == HOLE || ttype == TRAPDOOR)) { /* The "air currents" message is still appropriate -- even when @@ -703,8 +710,8 @@ unsigned trflags; * check, clinging to the ceiling, etc. */ pline("Air currents pull you down into %s %s!", - a_your[trap->madeby_u], - defsyms[trap_to_defsym(ttype)].explanation); + a_your[trap->madeby_u], + defsyms[trap_to_defsym(ttype)].explanation); /* then proceed to normal trap effect */ } else if (already_seen && !forcetrap) { if ((Levitation || Flying) && @@ -984,9 +991,9 @@ glovecheck: (void) rust_dmg(uarmg, "gauntlets", 1, TRUE, &youmonst); case PIT: case SPIKED_PIT: /* KMH -- You can't escape the Sokoban level traps */ - if (!In_sokoban(&u.uz) && (Levitation || Flying)) break; + if (!Sokoban && (Levitation || Flying)) break; feeltrap(trap); - if (!In_sokoban(&u.uz) && is_clinger(youmonst.data)) { + if (!Sokoban && is_clinger(youmonst.data)) { if(trap->tseen) { You_see("%s %spit below you.", a_your[trap->madeby_u], ttype == SPIKED_PIT ? "spiked " : ""); @@ -998,7 +1005,7 @@ glovecheck: (void) rust_dmg(uarmg, "gauntlets", 1, TRUE, &youmonst); } break; } - if (!In_sokoban(&u.uz)) { + if (!Sokoban) { char verbbuf[BUFSZ]; #ifdef STEED if (u.usteed) { @@ -1949,7 +1956,7 @@ register struct monst *mtmp; boolean in_sight, tear_web, see_it, inescapable = force_mintrap || ((tt == HOLE || tt == PIT) && - In_sokoban(&u.uz) && !trap->madeby_u); + Sokoban && !trap->madeby_u); const char *fallverb; #ifdef STEED @@ -2211,7 +2218,7 @@ glovecheck: target = which_armor(mtmp, W_ARMG); if (is_flyer(mptr) || is_floater(mptr) || (mtmp->wormno && count_wsegs(mtmp) > 5) || is_clinger(mptr)) { - if (force_mintrap && !In_sokoban(&u.uz)) { + if (force_mintrap && !Sokoban) { /* openfallingtrap; not inescapable here */ if (in_sight) { seetrap(trap); @@ -2251,7 +2258,7 @@ glovecheck: target = which_armor(mtmp, W_ARMG); mptr == &mons[PM_WUMPUS] || (mtmp->wormno && count_wsegs(mtmp) > 5) || mptr->msize >= MZ_HUGE) { - if (force_mintrap && !In_sokoban(&u.uz)) { + if (force_mintrap && !Sokoban) { /* openfallingtrap; not inescapable here */ if (in_sight) { seetrap(trap); @@ -2718,7 +2725,7 @@ long hmask, emask; /* might cancel timeout */ if (!(emask & W_SADDLE)) #endif { - if (In_sokoban(&u.uz) && trap) { + if (Sokoban && trap) { /* Justification elsewhere for Sokoban traps * is based on air currents. This is * consistent with that. @@ -2793,7 +2800,7 @@ climb_pit() display_nhwindow(WIN_MESSAGE, FALSE); clear_nhwindow(WIN_MESSAGE); You("free your %s.", body_part(LEG)); - } else if (Flying && !In_sokoban(&u.uz)) { + } else if (Flying && !Sokoban) { /* eg fell in pit, poly'd to a flying monster */ You("fly from the pit."); u.utrap = 0; @@ -2801,7 +2808,7 @@ climb_pit() vision_full_recalc = 1; /* vision limits change */ } else if (!(--u.utrap)) { You("%s to the edge of the pit.", - (In_sokoban(&u.uz) && Levitation) ? + (Sokoban && Levitation) ? "struggle against the air currents and float" : #ifdef STEED u.usteed ? "ride" : @@ -4437,6 +4444,8 @@ register struct trap *trap; for(ttmp = ftrap; ttmp->ntrap != trap; ttmp = ttmp->ntrap) ; ttmp->ntrap = trap->ntrap; } + if (Sokoban && (trap->ttyp == PIT || trap->ttyp == HOLE)) + maybe_finish_sokoban(); dealloc_trap(trap); } @@ -4802,4 +4811,46 @@ sink_into_lava() } } +/* called when something has been done (breaking a boulder, for instance) + which entails a luck penalty if performed on a sokoban level */ +void +sokoban_guilt() +{ + if (Sokoban) { + change_luck(-1); + /* TODO: issue some feedback so that player can learn that whatever + he/she just did is a naughty thing to do in sokoban and should + probably be avoided in future.... + Caveat: doing this might introduce message sequencing issues, + depending upon feedback during the various actions which trigger + Sokoban luck penalties. */ + } +} + +/* called when a trap has been deleted or had its ttyp replaced */ +STATIC_OVL void +maybe_finish_sokoban() +{ + struct trap *t; + + if (Sokoban && !in_mklev) { + /* scan all remaining traps, ignoring any created by the hero; + if this level has no more pits or holes, the current sokoban + puzzle has been solved */ + for (t = ftrap; t; t = t->ntrap) { + if (t->madeby_u) continue; + if (t->ttyp == PIT || t->ttyp == HOLE) break; + } + if (!t) { + /* we've passed the last trap without finding a pit or hole; + clear the sokoban_rules flag so that luck penalties for + things like breaking boulders or jumping will no longer + be given, and restrictions on diagonal moves are lifted */ + Sokoban = 0; /* clear level.flags.sokoban_rules */ + /* TODO: give some feedback about solving the sokoban puzzle + (perhaps say "congratulations" in Japanese?) */ + } + } +} + /*trap.c*/ diff --git a/src/zap.c b/src/zap.c index 4e641cbae..ac22d7781 100644 --- a/src/zap.c +++ b/src/zap.c @@ -1307,8 +1307,8 @@ poly_obj(obj, id) boolean can_merge = (id == STRANGE_OBJECT); int obj_location = obj->where; - if (obj->otyp == BOULDER && In_sokoban(&u.uz)) - change_luck(-1); /* Sokoban guilt */ + if (obj->otyp == BOULDER) + sokoban_guilt(); if (id == STRANGE_OBJECT) { /* preserve symbol */ int try_limit = 3; unsigned magic_obj = objects[obj->otyp].oc_magic; @@ -3152,7 +3152,7 @@ struct obj **pobj; /* object tossed/used, set to NULL pline("%s jerks to an abrupt halt.", The(distant_name(obj, xname))); /* lame */ range = 0; - } else if (In_sokoban(&u.uz) && (t = t_at(x, y)) != 0 && + } else if (Sokoban && (t = t_at(x, y)) != 0 && (t->ttyp == PIT || t->ttyp == SPIKED_PIT || t->ttyp == HOLE || t->ttyp == TRAPDOOR)) { /* hero falls into the trap, so ball stops */ @@ -4270,10 +4270,8 @@ register struct obj *obj; /* no texts here! */ breakobj(obj, x, y, TRUE, FALSE); /* charges for shop goods */ } } - - /* A little Sokoban guilt... */ - if (by_you && obj->otyp == BOULDER && In_sokoban(&u.uz)) - change_luck(-1); + if (by_you && obj->otyp == BOULDER) + sokoban_guilt(); obj->otyp = ROCK; obj->oclass = GEM_CLASS;