diff --git a/dat/tut-1.lua b/dat/tut-1.lua index af5db7aeb..b1937f7b8 100644 --- a/dat/tut-1.lua +++ b/dat/tut-1.lua @@ -255,8 +255,9 @@ des.trap({ type = "magic portal", coord = { 66,2 }, seen = true }); ---------------- nh.callback("cmd_before", "tutorial_cmd_before"); -nh.callback("level_enter", "tutorial_enter"); -nh.callback("level_leave", "tutorial_leave"); +-- entering and leaving tutorial _branch_ now handled by core +-- // nh.callback("level_enter", "tutorial_enter"); +-- // nh.callback("level_leave", "tutorial_leave"); nh.callback("end_turn", "tutorial_turn"); ---------------- diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index fd4c4d094..0acee27b9 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -1604,6 +1604,11 @@ running the tutorial until completion and returning to normal play but left when a magic whistle moved a hidden pet it brought that out of hiding and gave a message about "your appears" but the map was updated while it was still hidden so nothing seemed to appear +entering the tutorial stashed hero's equipment for eventual return to normal + play but going down stairs to the second tutorial level returned it + too soon and allowed any items gathered on the first level to be kept +entering the tutorial and then returning to play made the tutorial branch be + eligible for a portal destination via wizard's Eye of the Aethiopica Fixes to 3.7.0-x Platform and/or Interface Problems Exposed Via git Repository diff --git a/include/extern.h b/include/extern.h index 65d2026a5..1edaf34f1 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1899,6 +1899,7 @@ extern int str_lines_max_width(const char *); extern char *stripdigits(char *); extern const char *get_lua_version(void); extern void nhl_pushhooked_open_table(lua_State *L); +extern void tutorial(boolean); #endif /* !CROSSCOMPILE || CROSSCOMPILE_TARGET */ #endif /* MAKEDEFS_C MDLIB_C CPPREGEX_C */ diff --git a/include/hack.h b/include/hack.h index 66f8220b7..aadac06bb 100644 --- a/include/hack.h +++ b/include/hack.h @@ -354,6 +354,7 @@ struct dgn_topology { /* special dungeon levels for speed */ xint16 d_tower_dnum; xint16 d_sokoban_dnum; xint16 d_mines_dnum, d_quest_dnum; + xint16 d_tutorial_dnum; d_level d_qstart_level, d_qlocate_level, d_nemesis_level; d_level d_knox_level; d_level d_mineend_level; @@ -386,6 +387,7 @@ struct dgn_topology { /* special dungeon levels for speed */ #define sokoban_dnum (gd.dungeon_topology.d_sokoban_dnum) #define mines_dnum (gd.dungeon_topology.d_mines_dnum) #define quest_dnum (gd.dungeon_topology.d_quest_dnum) +#define tutorial_dnum (gd.dungeon_topology.d_tutorial_dnum) #define qstart_level (gd.dungeon_topology.d_qstart_level) #define qlocate_level (gd.dungeon_topology.d_qlocate_level) #define nemesis_level (gd.dungeon_topology.d_nemesis_level) diff --git a/src/artifact.c b/src/artifact.c index 04e219bb2..abd57e10d 100644 --- a/src/artifact.c +++ b/src/artifact.c @@ -1770,6 +1770,8 @@ arti_invoke(struct obj *obj) for (i = num_ok_dungeons = 0; i < gn.n_dgns; i++) { if (!gd.dungeons[i].dunlev_ureached) continue; + if (i == tutorial_dnum) /* can't portal into tutorial */ + continue; any.a_int = i + 1; add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, diff --git a/src/botl.c b/src/botl.c index b5bc04be6..0159e86aa 100644 --- a/src/botl.c +++ b/src/botl.c @@ -445,7 +445,9 @@ describe_level( addbranch = FALSE; } else { /* ports with more room may expand this one */ - if (!addbranch) + if (u.uz.dnum == tutorial_dnum) + Sprintf(buf, "Tutorial:%-2d", depth(&u.uz)); + else if (!addbranch) Sprintf(buf, "Dlvl:%-2d", depth(&u.uz)); else Sprintf(buf, "level %d", depth(&u.uz)); diff --git a/src/do.c b/src/do.c index 68999acd5..7512c91c2 100644 --- a/src/do.c +++ b/src/do.c @@ -1411,11 +1411,19 @@ goto_level( if (dunlev(newlevel) > dunlevs_in_dungeon(newlevel)) newlevel->dlevel = dunlevs_in_dungeon(newlevel); - if (newdungeon && In_endgame(newlevel)) { /* 1st Endgame Level !!! */ - if (!u.uhave.amulet) - return; /* must have the Amulet */ - if (!wizard) /* wizard ^V can bypass Earth level */ - assign_level(newlevel, &earth_level); /* (redundant) */ + if (newdungeon) { + if (In_endgame(newlevel)) { /* 1st Endgame Level !!! */ + if (!u.uhave.amulet) + return; /* must have the Amulet */ + if (!wizard) /* wizard ^V can bypass Earth level */ + assign_level(newlevel, &earth_level); /* (redundant) */ + } else if (newlevel->dnum == tutorial_dnum) { + tutorial(TRUE); /* entering tutorial */ + } else if (u.uz.dnum == tutorial_dnum) { + tutorial(FALSE); /* leaving tutorial */ + up = FALSE; /* re-enter level 1 as if starting new game */ + /* TODO: remove tutorial level(s) from #overview data */ + } } new_ledger = ledger_no(newlevel); if (new_ledger <= 0) @@ -1623,7 +1631,6 @@ goto_level( if (portal && !In_endgame(&u.uz)) { /* find the portal on the new level */ register struct trap *ttrap; - struct stairway *stway; for (ttrap = gf.ftrap; ttrap; ttrap = ttrap->ntrap) if (ttrap->ttyp == MAGIC_PORTAL) @@ -1636,9 +1643,6 @@ goto_level( after already getting expelled once. The portal back doesn't exist anymore - see expulsion(). */ u_on_rndspot(0); - } else if ((stway = stairway_find_dir(TRUE)) != 0) { - /* returning from tutorial via portal */ - u_on_newpos(stway->sx, stway->sy); } else { if (!iflags.debug_fuzzer) impossible("goto_level: no corresponding portal!"); diff --git a/src/dungeon.c b/src/dungeon.c index f503ca878..d03ca20bc 100644 --- a/src/dungeon.c +++ b/src/dungeon.c @@ -741,8 +741,9 @@ get_dgn_flags(lua_State *L) static const char *const flagstrs[] = { "town", "hellish", "mazelike", "roguelike", "unconnected", NULL }; - static const int flagstrs2i[] = { TOWN, HELLISH, MAZELIKE, ROGUELIKE, - UNCONNECTED, 0 }; + static const int flagstrs2i[] = { + TOWN, HELLISH, MAZELIKE, ROGUELIKE, UNCONNECTED, 0 + }; lua_getfield(L, -1, "flags"); if (lua_type(L, -1) == LUA_TTABLE) { @@ -1220,6 +1221,7 @@ init_dungeons(void) sokoban_dnum = dname_to_dnum("Sokoban"); mines_dnum = dname_to_dnum("The Gnomish Mines"); tower_dnum = dname_to_dnum("Vlad's Tower"); + tutorial_dnum = dname_to_dnum("The Tutorial"); /* one special fixup for dummy surface level */ if ((x = find_level("dummy")) != 0) { diff --git a/src/nhlua.c b/src/nhlua.c index b272d567b..dd1070b16 100644 --- a/src/nhlua.c +++ b/src/nhlua.c @@ -1575,46 +1575,57 @@ nhl_callback(lua_State *L) static int nhl_gamestate(lua_State *L) { + static struct obj *gmst_invent = NULL; + static long gmst_moves = 0; + static boolean gmst_stored = FALSE; + static struct you gmst_ubak; + long wornmask; + struct obj *otmp; int argc = lua_gettop(L); - boolean reststate = argc > 0 ? lua_toboolean(L, -1) : FALSE; - static struct obj *invent = NULL; - static long moves = 0; - static boolean stored = FALSE; - static struct you ubak; + boolean reststate = (argc > 0) ? lua_toboolean(L, -1) : FALSE; + + debugpline4("gamestate: %d:%d (%c vs %c)", u.uz.dnum, u.uz.dlevel, + reststate ? 'T' : 'F', gmst_stored ? 't' : 'f'); + + if (reststate && gmst_stored) { + d_level cur_uz = u.uz, cur_uz0 = u.uz0; - if (reststate && stored) { /* restore game state */ - gm.moves = moves; + gm.moves = gmst_moves; gl.lastinvnr = 51; while (gi.invent) useupall(gi.invent); - while (invent) { - struct obj *otmp = invent; - long wornmask = otmp->owornmask; + while ((otmp = gmst_invent) != NULL) { + wornmask = otmp->owornmask; otmp->owornmask = 0L; - extract_nobj(otmp, &invent); + extract_nobj(otmp, &gmst_invent); addinv(otmp); if (wornmask) setworn(otmp, wornmask); } - u = ubak; + u = gmst_ubak; + /* some restored state would confuse the level change in progress */ + u.uz = cur_uz, u.uz0 = cur_uz0; init_uhunger(); - stored = FALSE; - } else { + gmst_stored = FALSE; + } else if (!reststate && !gmst_stored) { /* store game state */ - while (gi.invent) { - struct obj *otmp = gi.invent; - long wornmask = otmp->owornmask; + while ((otmp = gi.invent) != NULL) { + wornmask = otmp->owornmask; setnotworn(otmp); freeinv(otmp); - otmp->nobj = invent; + otmp->nobj = gmst_invent; otmp->owornmask = wornmask; - invent = otmp; + gmst_invent = otmp; } - gl.lastinvnr = 51; - moves = gm.moves; - ubak = u; - stored = TRUE; + gl.lastinvnr = 51; /* next inv letter to try to use will be 'a' */ + gmst_moves = gm.moves; + gmst_ubak = u; + gmst_stored = TRUE; + } else { + impossible("nhl_gamestate: inconsistent state (%s vs %s)", + reststate ? "restore" : "save", + gmst_stored ? "already stored" : "not stored"); } update_inventory(); return 0; @@ -1622,6 +1633,19 @@ nhl_gamestate(lua_State *L) RESTORE_WARNING_UNREACHABLE_CODE +/* called from gotolevel(do.c) */ +void +tutorial(boolean entering) +{ + lua_State *L = gl.luacore; + + /* pass FALSE when entering, to save state; pass TRUE when leaving, + to restore state */ + lua_pushboolean(L, !entering); + (void) nhl_gamestate(L); + lua_pop(L, 1); +} + static const struct luaL_Reg nhl_functions[] = { {"test", nhl_test}, diff --git a/src/teleport.c b/src/teleport.c index 2c60752bd..a723013f4 100644 --- a/src/teleport.c +++ b/src/teleport.c @@ -1391,7 +1391,7 @@ void domagicportal(struct trap *ttmp) { struct d_level target_level; - s_level *tutlvl = find_level("tut-1"); + int totype; const char *stunmsg = (char *) 0; if (u.utrap && u.utraptype == TT_BURIEDBALL) @@ -1421,13 +1421,17 @@ domagicportal(struct trap *ttmp) target_level = ttmp->dst; /* coming back from tutorial doesn't trigger stunning */ - if (!(tutlvl && tutlvl->dlevel.dnum == u.uz.dnum)) { + if (u.uz.dnum == tutorial_dnum && target_level.dnum != tutorial_dnum) { + /* returning to normal play => arrive on level 1 stairs */ + totype = UTOTYPE_ATSTAIRS; + } else { + totype = UTOTYPE_PORTAL; stunmsg = !Stunned ? "You feel slightly dizzy." : "You feel dizzier."; make_stunned((HStun & TIMEOUT) + 3L, FALSE); } - schedule_goto(&target_level, UTOTYPE_PORTAL, stunmsg, (char *) 0); + schedule_goto(&target_level, totype, stunmsg, (char *) 0); } void