Tutorial level

Add a tutorial level to teach commands to new players.
Very much a WIP.

Breaks save and bones compat.
This commit is contained in:
Pasi Kallinen
2023-02-25 20:37:01 +02:00
parent 7aead98a49
commit fc7a32b86e
38 changed files with 646 additions and 27 deletions

View File

@@ -315,4 +315,15 @@ dungeon = {
},
}
},
{
name = "The Tutorial",
base = 1,
flags = { "mazelike", "unconnected" },
levels = {
{
name = "tut-1",
base = 1,
},
}
},
}

View File

@@ -14,6 +14,45 @@ function get_variables_string()
return "nh_lua_variables=" .. table_stringify(nh_lua_variables) .. ";";
end
function nh_callback_set(cb, fn)
local cbname = "_CB_" .. cb;
-- pline("callback_set(%s,%s)", cb, fn);
if (type(nh_lua_variables[cbname]) ~= "table") then
nh_lua_variables[cbname] = {};
end
nh_lua_variables[cbname][fn] = true;
end
function nh_callback_rm(cb, fn)
local cbname = "_CB_" .. cb;
-- pline("callback_RM(%s,%s)", cb, fn);
if (type(nh_lua_variables[cbname]) ~= "table") then
nh_lua_variables[cbname] = {};
end
nh_lua_variables[cbname][fn] = nil;
end
function nh_callback_run(cb, ...)
local cbname = "_CB_" .. cb;
-- pline("callback_run(%s)", cb);
-- pline("TYPE:%s", type(nh_lua_variables[cbname]));
if (type(nh_lua_variables[cbname]) ~= "table") then
nh_lua_variables[cbname] = {};
end
for k, v in pairs(nh_lua_variables[cbname]) do
if (not _G[k](table.unpack{...})) then
return false;
end
end
return true;
end
-- This is an example of generating an external file during gameplay,
-- which is updated periodically.
-- Intended for public servers using dgamelaunch as their login manager.

View File

@@ -174,3 +174,55 @@ function table_stringify(tbl)
-- pline("table_stringify:(%s)", str);
return "{" .. str .. "}";
end
--
-- TUTORIAL
--
-- extended commands available in tutorial
local tutorial_whitelist_commands = {
["movesouth"] = true,
["movenorth"] = true,
["moveeast"] = true,
["movewest"] = true,
["movesoutheast"] = true,
["movenorthwest"] = true,
["movenortheast"] = true,
["movesouthwest"] = true,
["kick"] = true,
["search"] = true,
["pickup"] = true,
["wear"] = true,
["wield"] = true,
-- ["save"] = true,
};
function tutorial_cmd_before(cmd)
-- nh.pline("TUT:cmd_before:" .. cmd);
if (tutorial_whitelist_commands[cmd]) then
return true;
else
return false;
end
end
function tutorial_enter()
-- nh.pline("TUT:enter");
nh.gamestate();
end
function tutorial_leave()
-- nh.pline("TUT:leave");
-- remove the tutorial level callbacks
nh.callback("cmd_before", "tutorial_cmd_before", true);
nh.callback("level_enter", "tutorial_enter", true);
nh.callback("level_leave", "tutorial_leave", true);
nh.callback("end_turn", "tutorial_turn", true);
nh.gamestate(true);
end
function tutorial_turn()
-- nh.pline("TUT:turn");
end

143
dat/tut-1.lua Normal file
View File

@@ -0,0 +1,143 @@
des.level_init({ style = "solidfill", fg = " " });
des.level_flags("mazelevel", "noflip",
"nomongen", "nodeathdrops", "noautosearch");
des.map([[
---------------------------------------------------------------------------
|-.--|.......|....|.......................................................|
|.-..........|....+.......................................................|
||.--|.......|....|.......................................................|
||.|.|.......|....|.......................................................|
||.|.|.......|....|.......................................................|
|-+-S-------------|.......................................................|
|......| |.......................................................|
|......| ###### |.......................................................|
|----.-| -+- # |.......................................................|
|----+----.----+---.......................................................|
|........|.|......|.......................................................|
|.P......-S|......|------.................................................|
|..........|......+.|...|.................................................|
|.W......---......|.|.|.|.................................................|
|....Z.L.|.F......|.|.|.|+---.............................................|
|........|--......|...|.....|.............................................|
---------------------------------------------------------------------------
]]);
des.region(selection.area(01,01, 73, 16), "lit");
des.non_diggable();
des.teleport_region({ region = { 9,3, 9,3 } });
-- TODO:
-- - save hero state when entering, restore hero state when leaving
-- - quit-command should maybe exit the tutorial?
-- turn on some newbie-friendly options
nh.parse_config("OPTIONS=mention_walls");
nh.parse_config("OPTIONS=autoopen");
nh.parse_config("OPTIONS=lit_corridor");
local movekeys = nh.eckey("movewest") .. " " ..
nh.eckey("movesouth") .. " " ..
nh.eckey("movenorth") .. " " ..
nh.eckey("moveeast");
local diagmovekeys = nh.eckey("movesouthwest") .. " " ..
nh.eckey("movenortheast") .. " " ..
nh.eckey("movesoutheast") .. " " ..
nh.eckey("movenorthwest");
des.engraving({ coord = { 9,3 }, type = "engrave", text = "Move around with " .. movekeys, degrade = false });
des.engraving({ coord = { 5,2 }, type = "engrave", text = "Move diagonally with " .. diagmovekeys, degrade = false });
--
des.engraving({ coord = { 2,5 }, type = "engrave", text = "Open the door by moving into it", degrade = false });
des.door({ coord = { 2,6 }, state = "closed" });
--
des.engraving({ coord = { 4,5 }, type = "engrave", text = "You can leave the tutorial via the magic portal.", degrade = false });
des.trap({ type = "magic portal", coord = { 4,4 }, seen = true });
--
des.engraving({ coord = { 5,9 }, type = "engrave", text = "This door is locked. Kick it with " .. nh.eckey("kick"), degrade = false });
des.door({ coord = { 5,10 }, state = "locked" });
--
des.engraving({ coord = { 10,13 }, type = "engrave", text = "Use " .. nh.eckey("search") .. " to search for secret doors", degrade = false });
--
des.engraving({ coord = { 10,10 }, type = "engrave", text = "Behind this door is a dark corridor", degrade = false });
des.door({ coord = { 10,9 }, state = percent(50) and "locked" or "closed" });
des.region(selection.match("#"), "unlit");
des.region(selection.match(" "), "unlit");
des.door({ coord = { 15,10 }, state = percent(50) and "locked" or "closed" });
--
des.engraving({ coord = { 15,11 }, type = "engrave", text = "There are four traps next to you! Search for them.", degrade = false });
local locs = { {14,11}, {14,12}, {15,12}, {16,12}, {16,11} };
shuffle(locs);
for i = 1, 4 do
des.trap({ type = percent(50) and "sleep gas" or "board",
coord = locs[i], victim = false });
end
--
des.door({ coord = { 18,13 }, state = "closed" });
des.engraving({ coord = { 19,13 }, type = "engrave", text = "Pick up items with '" .. nh.eckey("pickup") .. "'", degrade = false });
local armor = (u.role == "Monk") and "leather gloves" or "leather armor";
des.object({ id = armor, spe = 0, buc = "not-cursed", coord = { 19,14} });
des.engraving({ coord = { 19,15 }, type = "engrave", text = "Wear armor with '" .. nh.eckey("wear") .. "'", degrade = false });
des.object({ id = "dagger", spe = 0, buc = "not-cursed", coord = { 21,15} });
des.engraving({ coord = { 21,14 }, type = "engrave", text = "Wield weapons with '" .. nh.eckey("wield") .. "'", degrade = false });
des.engraving({ coord = { 22,13 }, type = "engrave", text = "Hit monsters by walking into them.", degrade = false });
des.monster({ id = "lichen", coord = { 23,15 }, waiting = true, countbirth = false });
--
des.door({ coord = { 25,15 }, state = percent(50) and "locked" or "closed" });
des.engraving({ coord = { 24,16 }, type = "engrave", text = "Now you know the very basics. You can leave the tutorial via the magic portal.", degrade = false });
des.trap({ type = "magic portal", coord = { 27,16 }, seen = true });
--
des.engraving({ coord = { 25,14 }, type = "burn", text = "UNDER CONSTRUCTION", degrade = false });
--
des.door({ coord = { 18,2 }, state = percent(50) and "locked" or "closed" });
----------------
nh.callback("cmd_before", "tutorial_cmd_before");
nh.callback("level_enter", "tutorial_enter");
nh.callback("level_leave", "tutorial_leave");
nh.callback("end_turn", "tutorial_turn");
----------------
-- temporary stuff here
-- des.trap({ type = "magic portal", coord = { 9,5 }, seen = true });
-- des.trap({ type = "magic portal", coord = { 9,1 }, seen = true });
-- des.object({ id = "leather armor", spe = 0, coord = { 9,2} });

View File

@@ -4546,6 +4546,9 @@ Persistent.
." .lp travel_debug
." Display intended path during each step of travel (default off).
." Debug mode only.
.lp tutorial
Play a tutorial level at the start of the game.
Setting this option on or off in the config file will skip the query.
.lp "verbose "
Provide more commentary during the game (default on). Persistent.
.lp whatis_coord

View File

@@ -4979,6 +4979,10 @@ command. Persistent.
% Display intended path during each step of travel (default off).
% Debug mode only.
%.lp
\item[\ib{tutorial}]
Play a tutorial level at the start of the game.
Setting this option on or off in the config file will skip the query.
%.lp
\item[\ib{verbose}]
Provide more commentary during the game (default on). Persistent.
%.lp

View File

@@ -1987,6 +1987,7 @@ add items given a Japanese name when playing as a Samurai to discoveries list
make music improvisations more varied and interesting, as well as useful
give a helpful tip when first entering "farlook" mode
add a boolean option tips to disable all of the helpful tips
add a tutorial level
Platform- and/or Interface-Specific New Features

View File

@@ -33,6 +33,26 @@ Example:
local str = nh.an("unicorn");
=== callback
Add or remove a lua function to callback list.
First argument is the callback list, second is the name of the lua function to be called.
Two arguments adds the callback, if optional 3rd argument is true, removes the callback.
Cannot add the same function to the same callback list does nothing.
|===
| cmd_before | called before an extended command is executed. The command name is given as a parameter. If this function returns false, the command will not execute.
| level_enter | called when hero enters the level for the first time.
| level_leave | called when hero leaves the level.
| end_turn | called after player input is handled. May not be exact turn, if eg. hero is running or otherwise occupied.
|===
Example:
nh.callback("level_enter", "tutorial_enter");
nh.callback("level_enter", "tutorial_enter", true);
=== debug_flags
Set debugging flags.
@@ -90,6 +110,16 @@ Example:
local filename = nh.dump_fmtstr("/tmp/nethack.%n.%d.log");
=== eckey
Return the key bound to an extended command, or the full extended
command name, if it is not bound to any key.
Example:
local k = nh.eckey("help");
=== getlin
Asks the player for a text to enter, and returns the entered string.
@@ -482,6 +512,8 @@ Example:
Create an engraving.
* type is one of "dust", "engrave", "burn", "mark", or "blood".
* optional boolean `degrade` defaults to true; engraving can degrade or be wiped out.
* optional boolean `guardobjects` defaults to false (unless making a level and the text is "Elbereth"); are items on the engraving protected from monsters.
Example:
@@ -579,6 +611,8 @@ Set flags for this level.
| hot | Level is hot. Dungeon flag "hellish" automatically sets this.
| cold | Level is cold.
| temperate | Level is neither hot nor cold.
| nomongen | Prevents random monster generation.
| nodeathdrops | Prevents killed monsters from dropping corpses or random death drops.
|===
Example:

View File

@@ -245,6 +245,10 @@ extern const struct class_sym def_monsyms[MAXMCLASSES];
/* current mon class symbols */
extern uchar monsyms[MAXMCLASSES];
/* lua callback queue names */
extern const char * const nhcb_name[];
extern int nhcb_counts[];
#include "obj.h"
extern NEARDATA struct obj *uarm, *uarmc, *uarmh, *uarms, *uarmg, *uarmf,
*uarmu, /* under-wear, so to speak */

View File

@@ -53,10 +53,11 @@ struct tmpbranch {
/*
* Flags that map into the dungeon flags bitfields.
*/
#define TOWN 1 /* levels only */
#define HELLISH 2
#define MAZELIKE 4
#define ROGUELIKE 8
#define TOWN 0x01 /* levels only */
#define HELLISH 0x02
#define MAZELIKE 0x04
#define ROGUELIKE 0x08
#define UNCONNECTED 0x10
#define D_ALIGN_NONE 0
#define D_ALIGN_CHAOTIC (AM_CHAOTIC << 4)

View File

@@ -19,7 +19,7 @@ typedef struct d_flags { /* dungeon/level type flags */
Bitfield(maze_like, 1); /* is this a maze? */
Bitfield(rogue_like, 1); /* is this an old-fashioned presentation? */
Bitfield(align, 3); /* dungeon alignment. */
Bitfield(unused, 1); /* etc... */
Bitfield(unconnected, 1); /* dungeon not connected to any branch */
} d_flags;
typedef struct s_level { /* special dungeon level element */

View File

@@ -24,7 +24,8 @@ struct engr {
* against monsters when an object is present
* even when hero isn't (so behaves similarly
* to how Elbereth did in 3.4.3) */
/* 7 free bits */
Bitfield(nowipeout, 1); /* this engraving will not degrade */
/* 6 free bits */
};
#define newengr(lth) \

View File

@@ -245,6 +245,7 @@ extern int domonability(void);
extern const struct ext_func_tab *ext_func_tab_from_func(int(*)(void));
extern char cmd_from_func(int(*)(void));
extern char cmd_from_dir(int, int);
extern char *cmd_from_ecname(const char *);
extern const char *cmdname_from_func(int(*)(void), char *, boolean);
extern boolean redraw_cmd(char);
extern const char *levltyp_to_name(int);
@@ -2008,6 +2009,7 @@ extern int shiny_obj(char);
/* ### options.c ### */
extern boolean ask_do_tutorial(void);
extern boolean match_optname(const char *, const char *, int, boolean);
extern uchar txt2key(char *);
extern void initoptions(void);

View File

@@ -35,6 +35,7 @@ struct flag {
boolean goldX; /* for BUCX filtering, whether gold is X or U */
boolean help; /* look in data file for info about stuff */
boolean tips; /* show helpful hints? */
boolean tutorial; /* ask if player wants tutorial level? */
boolean ignintr; /* ignore interrupts */
boolean implicit_uncursed; /* maybe omit "uncursed" status in inventory */
boolean ins_chkpt; /* checkpoint as appropriate; INSURANCE */

View File

@@ -524,6 +524,16 @@ enum nhcore_calls {
NUM_NHCORE_CALLS
};
/* Lua callbacks. TODO: Merge with NHCORE */
enum nhcb_calls {
NHCB_CMD_BEFORE = 0,
NHCB_LVL_ENTER,
NHCB_LVL_LEAVE,
NHCB_END_TURN,
NUM_NHCB
};
/* Macros for messages referring to hands, eyes, feet, etc... */
enum bodypart_types {
ARM = 0,

View File

@@ -628,6 +628,8 @@ static int optfn_##a(int, int, boolean, char *, char *);
NHOPTB(travel_debug, Advanced, 0, opt_out, set_wizonly,
Off, No, No, No, NoAlias, (boolean *) 0, Term_False)
#endif
NHOPTB(tutorial, Advanced, 0, opt_out, set_in_config,
On, Yes, No, No, NoAlias, &flags.tutorial, Term_False)
NHOPTB(use_darkgray, Advanced, 0, opt_out, set_in_config,
On, Yes, No, No, NoAlias, &iflags.wc2_darkgray, Term_False)
NHOPTB(use_inverse, Advanced, 0, opt_out, set_in_game,

View File

@@ -17,7 +17,7 @@
* Incrementing EDITLEVEL can be used to force invalidation of old bones
* and save files.
*/
#define EDITLEVEL 74
#define EDITLEVEL 75
/*
* Development status possibilities.

View File

@@ -389,6 +389,10 @@ struct levelflags {
normal mode descendant of such) */
Bitfield(corrmaze, 1); /* Whether corridors are used for the maze
rather than ROOM */
Bitfield(rndmongen, 1); /* random monster generation allowed? */
Bitfield(deathdrops, 1); /* monsters may drop corpses/death drops */
Bitfield(noautosearch, 1); /* automatic searching disabled */
schar temperature; /* +1 == hot, -1 == cold */
};

View File

@@ -355,6 +355,8 @@ struct you {
d_level uz, uz0; /* your level on this and the previous turn */
d_level utolev; /* level monster teleported you to, or uz */
uchar utotype; /* bitmask of goto_level() flags for utolev */
d_level ucamefrom; /* level where you came from; used for tutorial */
boolean nofollowers; /* level change ignores monster followers/pets */
boolean umoved; /* changed map location (post-move) */
int last_str_turn; /* 0: none, 1: half turn, 2: full turn
* +: turn right, -: turn left */

View File

@@ -14,6 +14,7 @@
static void moveloop_preamble(boolean);
static void u_calc_moveamt(int);
static void maybe_do_tutorial(void);
#ifdef POSITIONBAR
static void do_positionbar(void);
#endif
@@ -303,7 +304,7 @@ moveloop_core(void)
}
}
if (Searching && gm.multi >= 0)
if (!gl.level.flags.noautosearch && Searching && gm.multi >= 0)
(void) dosearch0(1);
if (Warning)
warnreveal();
@@ -498,12 +499,41 @@ moveloop_core(void)
/* [should this be flush_screen() instead?] */
display_nhwindow(WIN_MAP, FALSE);
}
if (gl.luacore && nhcb_counts[NHCB_END_TURN]) {
lua_getglobal(gl.luacore, "nh_callback_run");
lua_pushstring(gl.luacore, nhcb_name[NHCB_END_TURN]);
nhl_pcall(gl.luacore, 1, 0);
}
}
static void
maybe_do_tutorial(void)
{
s_level *sp = find_level("tut-1");
if (!sp)
return;
if (ask_do_tutorial()) {
assign_level(&u.ucamefrom, &u.uz);
u.nofollowers = TRUE;
schedule_goto(&sp->dlevel, UTOTYPE_NONE, (char *) 0, (char *) 0);
deferred_goto();
vision_recalc(0);
docrt();
u.nofollowers = FALSE;
}
}
void
moveloop(boolean resuming)
{
moveloop_preamble(resuming);
if (!resuming)
maybe_do_tutorial();
for (;;) {
moveloop_core();
}

View File

@@ -482,6 +482,15 @@ can_do_extcmd(const struct ext_func_tab *extcmd)
{
int ecflags = extcmd->flags;
if (gl.luacore && nhcb_counts[NHCB_CMD_BEFORE]) {
lua_getglobal(gl.luacore, "nh_callback_run");
lua_pushstring(gl.luacore, nhcb_name[NHCB_CMD_BEFORE]);
lua_pushstring(gl.luacore, extcmd->ef_txt);
nhl_pcall(gl.luacore, 2, 1);
if (!lua_toboolean(gl.luacore, -1))
return FALSE;
}
if (!wizard && (ecflags & WIZMODECMD)) {
pline(unavailcmd, extcmd->ef_txt);
return FALSE;
@@ -3607,6 +3616,29 @@ cmd_from_func(int (*fn)(void))
return '\0';
}
/* return visual interpretation of the key bound to extended command,
or the ext cmd name if not bound to any key. */
char *
cmd_from_ecname(const char *ecname)
{
static char cmdnamebuf[QBUFSZ];
const struct ext_func_tab *extcmd;
for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd)
if (!strcmp(extcmd->ef_txt, ecname)) {
char key = cmd_from_func(extcmd->ef_funct);
if (key)
Sprintf(cmdnamebuf, "%s", visctrl(key));
else
Sprintf(cmdnamebuf, "#%s", ecname);
return cmdnamebuf;
}
cmdnamebuf[0] = '\0';
return cmdnamebuf;
}
static const char *
ecname_from_fn(int (*fn)(void))
{

View File

@@ -45,6 +45,14 @@ const int shield_static[SHIELD_COUNT] = {
S_ss1, S_ss2, S_ss3, S_ss2, S_ss1, S_ss2, S_ss4,
};
const char * const nhcb_name[NUM_NHCB] = {
"cmd_before",
"level_enter",
"level_leave",
"end_turn",
};
int nhcb_counts[NUM_NHCB] = DUMMY;
NEARDATA const struct c_color_names c_color_names = {
"black", "amber", "golden", "light blue", "red", "green",

View File

@@ -1481,6 +1481,12 @@ goto_level(
if (on_level(newlevel, &u.uz))
return; /* this can happen */
if (gl.luacore && nhcb_counts[NHCB_LVL_LEAVE]) {
lua_getglobal(gl.luacore, "nh_callback_run");
lua_pushstring(gl.luacore, nhcb_name[NHCB_LVL_LEAVE]);
nhl_pcall(gl.luacore, 1, 0);
}
/* tethered movement makes level change while trapped feasible */
if (u.utrap && u.utraptype == TT_BURIEDBALL)
buried_ball_to_punishment(); /* (before we save/leave old level) */
@@ -1511,7 +1517,8 @@ goto_level(
set_ustuck((struct monst *) 0); /* clear u.ustuck and u.uswallow */
set_uinwater(0); /* u.uinwater = 0 */
u.uundetected = 0; /* not hidden, even if means are available */
keepdogs(FALSE);
if (!u.nofollowers)
keepdogs(FALSE);
recalc_mapseen(); /* recalculate map overview before we leave the level */
/*
* We no longer see anything on the level. Make sure that this
@@ -1611,6 +1618,7 @@ 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)
@@ -1623,6 +1631,9 @@ 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 {
panic("goto_level: no corresponding portal!");
}

View File

@@ -714,9 +714,10 @@ get_dgn_flags(lua_State *L)
{
int dgn_flags = 0;
static const char *const flagstrs[] = {
"town", "hellish", "mazelike", "roguelike", NULL
"town", "hellish", "mazelike", "roguelike", "unconnected", NULL
};
static const int flagstrs2i[] = { TOWN, HELLISH, MAZELIKE, ROGUELIKE, 0 };
static const int flagstrs2i[] = { TOWN, HELLISH, MAZELIKE, ROGUELIKE,
UNCONNECTED, 0 };
lua_getfield(L, -1, "flags");
if (lua_type(L, -1) == LUA_TTABLE) {
@@ -1040,6 +1041,7 @@ init_dungeons(void)
gd.dungeons[i].flags.maze_like = !!(dgn_flags & MAZELIKE);
gd.dungeons[i].flags.rogue_like = !!(dgn_flags & ROGUELIKE);
gd.dungeons[i].flags.align = dgn_align;
gd.dungeons[i].flags.unconnected = !!(dgn_flags & UNCONNECTED);
/*
* Set the entry level for this dungeon. The entry value means:
@@ -1063,7 +1065,9 @@ init_dungeons(void)
gd.dungeons[i].entry_lev = 1; /* defaults to top level */
}
if (i) { /* set depth */
if (gd.dungeons[i].flags.unconnected) {
gd.dungeons[i].depth_start = 1;
} else if (i) { /* set depth */
branch *br;
schar from_depth;
boolean from_up;

View File

@@ -296,8 +296,8 @@ wipe_engr_at(coordxy x, coordxy y, xint16 cnt, boolean magical)
{
register struct engr *ep = engr_at(x, y);
/* Headstones are indelible */
if (ep && ep->engr_type != HEADSTONE) {
/* Headstones and some specially marked engravings are indelible */
if (ep && ep->engr_type != HEADSTONE && !ep->nowipeout) {
debugpline1("asked to erode %d characters", cnt);
if (ep->engr_type != BURN || is_ice(x, y) || (magical && !rn2(2))) {
if (ep->engr_type != DUST && ep->engr_type != ENGR_BLOOD) {

View File

@@ -1141,7 +1141,7 @@ makemon(
fakemon = cg.zeromonst;
cc.x = cc.y = 0;
if (iflags.debug_mongen)
if (iflags.debug_mongen || (!gl.level.flags.rndmongen && !ptr))
return (struct monst *) 0;
/* if caller wants random location, do it here */

View File

@@ -1016,6 +1016,12 @@ makelevel(void)
for (i = 0; i < gn.nroom; ++i) {
fill_special_room(&gr.rooms[i]);
}
if (gl.luacore && nhcb_counts[NHCB_LVL_ENTER]) {
lua_getglobal(gl.luacore, "nh_callback_run");
lua_pushstring(gl.luacore, nhcb_name[NHCB_LVL_ENTER]);
nhl_pcall(gl.luacore, 1, 0);
}
}
/*
@@ -1518,6 +1524,9 @@ mktrap(
(void) makemon(&mons[PM_GIANT_SPIDER], m.x, m.y, NO_MM_FLAGS);
if (t && (mktrapflags & MKTRAP_SEEN))
t->tseen = TRUE;
if (kind == MAGIC_PORTAL && (u.ucamefrom.dnum || u.ucamefrom.dlevel)) {
assign_level(&t->dst, &u.ucamefrom);
}
/* The hero isn't the only person who's entered the dungeon in
search of treasure. On the very shallowest levels, there's a

View File

@@ -34,6 +34,7 @@ static void pacify_guard(struct monst *);
#define LEVEL_SPECIFIC_NOCORPSE(mdat) \
(Is_rogue_level(&u.uz) \
|| !gl.level.flags.deathdrops \
|| (gl.level.flags.graveyard && is_undead(mdat) && rn2(3)))
/* A specific combination of x_monnam flags for livelogging. The livelog

View File

@@ -36,6 +36,9 @@ static int nhl_timer_has_at(lua_State *);
static int nhl_timer_peek_at(lua_State *);
static int nhl_timer_stop_at(lua_State *);
static int nhl_timer_start_at(lua_State *);
static int nhl_get_cmd_key(lua_State *);
static int nhl_callback(lua_State *);
static int nhl_gamestate(lua_State *);
static int nhl_test(lua_State *);
static int nhl_getmap(lua_State *);
static char splev_typ2chr(schar);
@@ -1474,6 +1477,140 @@ nhl_timer_start_at(lua_State *L)
return 0;
}
/* returns the visual interpretation of the key bound to an extended command,
or the ext cmd name if not bound to any key */
/* local helpkey = eckey("help"); */
static int
nhl_get_cmd_key(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
const char *cmd = luaL_checkstring(L, 1);
char *key = cmd_from_ecname(cmd);
lua_pushstring(L, key);
return 1;
}
return 0;
}
/* add or remove a lua function callback */
/* callback("level_enter", "function_name"); */
/* callback("level_enter", "function_name", true); */
/* level_enter, level_leave, cmd_before */
static int
nhl_callback(lua_State *L)
{
int argc = lua_gettop(L);
int i;
if (argc == 2) {
const char *fn = luaL_checkstring(L, -1);
const char *cb = luaL_checkstring(L, -2);
if (!gl.luacore) {
nhl_error(L, "nh luacore not inited");
/*NOTREACHED*/
return 0;
}
for (i = 0; i < NUM_NHCB; i++)
if (!strcmp(cb, nhcb_name[i]))
break;
if (i >= NUM_NHCB)
return 0;
nhcb_counts[i]++;
lua_getglobal(gl.luacore, "nh_callback_set");
lua_pushstring(gl.luacore, cb);
lua_pushstring(gl.luacore, fn);
nhl_pcall(gl.luacore, 2, 0);
} else if (argc == 3) {
boolean rm = lua_toboolean(L, -1);
const char *fn = luaL_checkstring(L, -2);
const char *cb = luaL_checkstring(L, -3);
if (!gl.luacore) {
nhl_error(L, "nh luacore not inited");
/*NOTREACHED*/
return 0;
}
for (i = 0; i < NUM_NHCB; i++)
if (!strcmp(cb, nhcb_name[i]))
break;
if (i >= NUM_NHCB)
return 0;
if (rm) {
nhcb_counts[i]--;
if (nhcb_counts[i] < 0)
impossible("nh.callback counts are wrong");
} else {
nhcb_counts[i]++;
}
lua_getglobal(gl.luacore, rm ? "nh_callback_rm" : "nh_callback_set");
lua_pushstring(gl.luacore, cb);
lua_pushstring(gl.luacore, fn);
nhl_pcall(gl.luacore, 2, 0);
}
return 0;
}
/* store or restore game state */
/* NOTE: doesn't work when saving/restoring the game */
/* currently handles inventory and turns. */
/* gamestate(); -- save state */
/* gamestate(true); -- restore state */
static int
nhl_gamestate(lua_State *L)
{
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;
if (reststate && stored) {
/* restore game state */
gm.moves = moves;
while (gi.invent)
useupall(gi.invent);
while (invent) {
struct obj *otmp = invent;
long wornmask = otmp->owornmask;
otmp->owornmask = 0L;
extract_nobj(otmp, &invent);
addinv(otmp);
if (wornmask)
setworn(otmp, wornmask);
}
stored = FALSE;
} else {
/* store game state */
while (gi.invent) {
struct obj *otmp = gi.invent;
long wornmask = otmp->owornmask;
setnotworn(otmp);
freeinv(otmp);
otmp->nobj = invent;
otmp->owornmask = wornmask;
invent = otmp;
}
moves = gm.moves;
stored = TRUE;
}
update_inventory();
return 0;
}
RESTORE_WARNING_UNREACHABLE_CODE
static const struct luaL_Reg nhl_functions[] = {
@@ -1499,6 +1636,9 @@ static const struct luaL_Reg nhl_functions[] = {
{"menu", nhl_menu},
{"text", nhl_text},
{"getlin", nhl_getlin},
{"eckey", nhl_get_cmd_key},
{"callback", nhl_callback},
{"gamestate", nhl_gamestate},
{"makeplural", nhl_makeplural},
{"makesingular", nhl_makesingular},

View File

@@ -368,6 +368,52 @@ extern int curses_read_attrs(const char *attrs);
extern char *curses_fmt_attrs(char *);
#endif
/* ask user if they want a tutorial, except if tutorial boolean option has been
set in config - either on or off - in which case just obey that setting
without asking. */
boolean
ask_do_tutorial(void)
{
boolean dotut = flags.tutorial;
if (!opt_set_in_config[opt_tutorial]) {
winid win;
menu_item *sel;
anything any;
int n;
do {
win = create_nhwindow(NHW_MENU);
start_menu(win, MENU_BEHAVE_STANDARD);
any = cg.zeroany;
any.a_char = 'y';
add_menu(win, &nul_glyphinfo, &any, any.a_char, 0,
ATR_NONE, 0, "Yes, do a tutorial", MENU_ITEMFLAGS_NONE);
any.a_char = 'n';
add_menu(win, &nul_glyphinfo, &any, any.a_char, 0,
ATR_NONE, 0, "No", MENU_ITEMFLAGS_NONE);
any = cg.zeroany;
add_menu(win, &nul_glyphinfo, &any, 0, 0,
ATR_NONE, 0, "", MENU_ITEMFLAGS_NONE);
add_menu(win, &nul_glyphinfo, &any, 0, 0,
ATR_NONE, 0, "", MENU_ITEMFLAGS_NONE);
add_menu(win, &nul_glyphinfo, &any, 0, 0,
ATR_NONE, 0, "Put \"OPTIONS=notutorial\" in the config file to skip this query.", MENU_ITEMFLAGS_NONE);
end_menu(win, "Do you want a tutorial?");
n = select_menu(win, PICK_ONE, &sel);
destroy_nhwindow(win);
} while (n <= 0 && !gp.program_state.done_hup);
if (n > 0) {
dotut = (sel[0].item.a_char == 'y');
free((genericptr_t) sel);
}
}
return dotut;
}
/*
**********************************
*

View File

@@ -3712,6 +3712,12 @@ lspo_level_flags(lua_State *L)
gl.level.flags.temperature = 1;
else if (!strcmpi(s, "cold"))
gl.level.flags.temperature = -1;
else if (!strcmpi(s, "nomongen"))
gl.level.flags.rndmongen = 0;
else if (!strcmpi(s, "nodeathdrops"))
gl.level.flags.deathdrops = 0;
else if (!strcmpi(s, "noautosearch"))
gl.level.flags.noautosearch = 1;
else {
char buf[BUFSZ];
Sprintf(buf, "Unknown level flag %s", s);
@@ -3783,6 +3789,9 @@ lspo_engraving(lua_State *L)
long ecoord;
coordxy x = -1, y = -1;
int argc = lua_gettop(L);
boolean guardobjs = FALSE;
boolean wipeout = TRUE;
struct engr *ep;
create_des_coder();
@@ -3795,6 +3804,8 @@ lspo_engraving(lua_State *L)
y = ey;
etyp = engrtypes2i[get_table_option(L, "type", "engrave", engrtypes)];
txt = get_table_str(L, "text");
wipeout = get_table_boolean_opt(L, "degrade", TRUE);
guardobjs = get_table_boolean_opt(L, "guardobjects", FALSE);
} else if (argc == 3) {
lua_Integer ex, ey;
(void) get_coord(L, 1, &ex, &ey);
@@ -3814,6 +3825,11 @@ lspo_engraving(lua_State *L)
get_location_coord(&x, &y, DRY, gc.coder->croom, ecoord);
make_engr_at(x, y, txt, 0L, etyp);
Free(txt);
ep = engr_at(x, y);
if (ep) {
ep->guardobjects = guardobjs;
ep->nowipeout = !wipeout;
}
return 0;
}
@@ -6826,6 +6842,8 @@ sp_level_coder_init(void)
gl.level.flags.is_maze_lev = 0;
gl.level.flags.temperature = In_hell(&u.uz) ? 1 : 0;
gl.level.flags.rndmongen = 1;
gl.level.flags.deathdrops = 1;
reset_xystart_size();

View File

@@ -1128,7 +1128,8 @@ void
domagicportal(struct trap *ttmp)
{
struct d_level target_level;
boolean already_stunned;
s_level *tutlvl = find_level("tut-1");
const char *stunmsg = (char *) 0;
if (u.utrap && u.utraptype == TT_BURIEDBALL)
buried_ball_to_punishment();
@@ -1154,13 +1155,16 @@ domagicportal(struct trap *ttmp)
return;
}
already_stunned = !!Stunned;
make_stunned((HStun & TIMEOUT) + 3L, FALSE);
target_level = ttmp->dst;
schedule_goto(&target_level, UTOTYPE_PORTAL,
!already_stunned ? "You feel slightly dizzy."
: "You feel dizzier.",
(char *) 0);
/* coming back from tutorial doesn't trigger stunning */
if (!(tutlvl && tutlvl->dlevel.dnum == u.uz.dnum)) {
stunmsg = !Stunned ? "You feel slightly dizzy."
: "You feel dizzier.";
make_stunned((HStun & TIMEOUT) + 3L, FALSE);
}
schedule_goto(&target_level, UTOTYPE_PORTAL, stunmsg, (char *) 0);
}
void

View File

@@ -97,7 +97,7 @@ SPEC_LEVS = asmodeus.lua baalz.lua bigrm-*.lua castle.lua fakewiz?.lua \
juiblex.lua knox.lua medusa-?.lua minend-?.lua minefill.lua \
minetn-?.lua oracle.lua orcus.lua sanctum.lua soko?-?.lua \
tower?.lua valley.lua wizard?.lua nhcore.lua nhlib.lua themerms.lua \
astral.lua air.lua earth.lua fire.lua water.lua hellfill.lua
astral.lua air.lua earth.lua fire.lua water.lua hellfill.lua tut-?.lua
QUEST_LEVS = ???-goal.lua ???-fil?.lua ???-loca.lua ???-strt.lua
DATNODLB = $(VARDATND) license symbols

View File

@@ -1387,6 +1387,7 @@
"$(NH_DAT_DIR)/tower1.lua",
"$(NH_DAT_DIR)/tower2.lua",
"$(NH_DAT_DIR)/tower3.lua",
"$(NH_DAT_DIR)/tut-1.lua",
"$(NH_DAT_DIR)/Val-fila.lua",
"$(NH_DAT_DIR)/Val-filb.lua",
"$(NH_DAT_DIR)/Val-goal.lua",
@@ -1411,7 +1412,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd \"${NH_DAT_DIR}\"\n\"${NH_UTIL_DIR}\"/dlb cf nhdat help hh cmdhelp keyhelp history opthelp optmenu wizhelp dungeon.lua tribute asmodeus.lua baalz.lua bigrm-*.lua castle.lua fakewiz?.lua juiblex.lua knox.lua medusa-?.lua minend-?.lua minefill.lua minetn-?.lua oracle.lua orcus.lua sanctum.lua soko?-?.lua tower?.lua valley.lua wizard?.lua nhcore.lua nhlib.lua themerms.lua hellfill.lua astral.lua air.lua earth.lua fire.lua water.lua ???-goal.lua ???-fil?.lua ???-loca.lua ???-strt.lua bogusmon data engrave epitaph oracles options quest.lua rumors\n";
shellScript = "cd \"${NH_DAT_DIR}\"\n\"${NH_UTIL_DIR}\"/dlb cf nhdat help hh cmdhelp keyhelp history opthelp optmenu wizhelp dungeon.lua tribute asmodeus.lua baalz.lua bigrm-*.lua castle.lua fakewiz?.lua juiblex.lua knox.lua medusa-?.lua minend-?.lua minefill.lua minetn-?.lua oracle.lua orcus.lua sanctum.lua soko?-?.lua tower?.lua tut-?.lua valley.lua wizard?.lua nhcore.lua nhlib.lua themerms.lua hellfill.lua astral.lua air.lua earth.lua fire.lua water.lua ???-goal.lua ???-fil?.lua ???-loca.lua ???-strt.lua bogusmon data engrave epitaph oracles options quest.lua rumors\n";
};
3192867021A39F6A00325BEB /* Install */ = {
isa = PBXShellScriptBuildPhase;

View File

@@ -40,7 +40,7 @@ $ spec_files = "air.lua,asmodeus.lua,astral.lua,baalz.lua," -
+ "fire.lua,juiblex.lua,knox.lua,medusa-%.lua," -
+ "minefill.lua,minetn-%.lua,minend-%.lua,nhlib.lua," -
+ "oracle.lua,orcus.lua,sanctum.lua,soko%-%.lua," -
+ "tower%.lua,valley.lua,water.lua,wizard%.lua,hellfill.lua"
+ "tower%.lua,valley.lua,water.lua,wizard%.lua,hellfill.lua,tut-%.lua"
$ qstl_files = "%%%-goal.lua,%%%-fil%.lua,%%%-loca.lua,%%%-strt.lua"
$ dngn_files = "dungeon.lua"
$!

View File

@@ -443,7 +443,7 @@ LUALIST = air Arc-fila Arc-filb Arc-goal Arc-loca Arc-strt \
Tou-strt tower1 tower2 tower3 Val-fila Val-filb \
Val-goal Val-loca Val-strt valley water Wiz-fila \
Wiz-filb Wiz-goal Wiz-loca Wiz-strt wizard1 wizard2 \
wizard3
wizard3 tut-1
LUAFILES = $(addprefix $(DAT)/, $(addsuffix .lua, $(LUALIST)))

View File

@@ -523,7 +523,7 @@ LUA_FILES = $(DAT)\air.lua $(DAT)\Arc-fila.lua $(DAT)\Arc-filb.lua \
$(DAT)\valley.lua $(DAT)\water.lua $(DAT)\Wiz-fila.lua \
$(DAT)\Wiz-filb.lua $(DAT)\Wiz-goal.lua $(DAT)\Wiz-loca.lua \
$(DAT)\Wiz-strt.lua $(DAT)\wizard1.lua $(DAT)\wizard2.lua \
$(DAT)\wizard3.lua
$(DAT)\wizard3.lua $(DAT)\tut-1.lua
#
# Utility Objects.
#

View File

@@ -127,6 +127,7 @@
<Luafiles Include = "tower1.lua"/>
<Luafiles Include = "tower2.lua"/>
<Luafiles Include = "tower3.lua"/>
<Luafiles Include = "tut-1.lua"/>
<Luafiles Include = "Val-fila.lua"/>
<Luafiles Include = "Val-filb.lua"/>
<Luafiles Include = "Val-goal.lua"/>