fix github issue #1046 - tutorial anomalies
Reported by Noisytoot: going from level tut-1 to tut-2 returned the hero's starting equipment too soon, and exiting the tutorial from tut-2 let the hero keep any equipment acquired within the tutorial. Entering and leaving the tutorial was being handled by lua code in the level description of tut-1 and adding a second level messed that up. I didn't see any way of handing that with level-specific lua code so I made it become the core's responsibility. gotolevel() knows when the hero is moving from one dungeon branch to another so it can recognize entry to or exit from the tutorial easily. While fixing this, prevent #invoke of the Eye of the Aethiopica from offering the tutorial as a candidate destination (was feasible if it had been entered at start of game). Not fixed: levels visited in the tutorial become part of #overview. Show location as "Tutorial:1" instead of "Dlvl:1" on status lines. Only tested with tty; some interfaces handle location themselves and may need their own fixup for this. Fixes #1046
This commit is contained in:
@@ -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");
|
||||
|
||||
----------------
|
||||
|
||||
@@ -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 <pet> 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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
22
src/do.c
22
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!");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
70
src/nhlua.c
70
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},
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user