Add a tutorial level to teach commands to new players.
Very much a WIP.
Breaks save and bones compat.
},
}
},
+ {
+ name = "The Tutorial",
+ base = 1,
+ flags = { "mazelike", "unconnected" },
+ levels = {
+ {
+ name = "tut-1",
+ base = 1,
+ },
+ }
+ },
}
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.
-- 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
--- /dev/null
+
+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} });
+
." .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
% 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
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
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.
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.
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:
| 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:
/* 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 */
/*
* 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)
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 */
* 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) \
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);
/* ### 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);
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 */
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,
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,
* Incrementing EDITLEVEL can be used to force invalidation of old bones
* and save files.
*/
-#define EDITLEVEL 74
+#define EDITLEVEL 75
/*
* Development status possibilities.
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 */
};
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 */
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
}
}
- if (Searching && gm.multi >= 0)
+ if (!gl.level.flags.noautosearch && Searching && gm.multi >= 0)
(void) dosearch0(1);
if (Warning)
warnreveal();
/* [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();
}
{
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;
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))
{
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",
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) */
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
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)
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!");
}
{
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) {
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:
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;
{
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) {
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 */
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);
+ }
}
/*
(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
#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
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);
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[] = {
{"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},
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;
+}
+
/*
**********************************
*
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);
long ecoord;
coordxy x = -1, y = -1;
int argc = lua_gettop(L);
+ boolean guardobjs = FALSE;
+ boolean wipeout = TRUE;
+ struct engr *ep;
create_des_coder();
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);
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;
}
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();
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();
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
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
"$(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",
);
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;
+ "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"
$!
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)))
$(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.
#
<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"/>