]> granicus.if.org Git - nethack/commitdiff
Tutorial level
authorPasi Kallinen <paxed@alt.org>
Sat, 25 Feb 2023 18:37:01 +0000 (20:37 +0200)
committerPasi Kallinen <paxed@alt.org>
Wed, 1 Mar 2023 12:00:29 +0000 (14:00 +0200)
Add a tutorial level to teach commands to new players.
Very much a WIP.

Breaks save and bones compat.

38 files changed:
dat/dungeon.lua
dat/nhcore.lua
dat/nhlib.lua
dat/tut-1.lua [new file with mode: 0644]
doc/Guidebook.mn
doc/Guidebook.tex
doc/fixes3-7-0.txt
doc/lua.adoc
include/decl.h
include/dgn_file.h
include/dungeon.h
include/engrave.h
include/extern.h
include/flag.h
include/hack.h
include/optlist.h
include/patchlevel.h
include/rm.h
include/you.h
src/allmain.c
src/cmd.c
src/decl.c
src/do.c
src/dungeon.c
src/engrave.c
src/makemon.c
src/mklev.c
src/mon.c
src/nhlua.c
src/options.c
src/sp_lev.c
src/teleport.c
sys/unix/Makefile.top
sys/unix/NetHack.xcodeproj/project.pbxproj
sys/vms/install.com
sys/windows/Makefile.mingw32
sys/windows/Makefile.nmake
sys/windows/vs/files.props

index c4371e5cb1cce7cbba199783b44fda17a66a1c82..24bd27dbf6f13c66506b59adba935aec2606d573 100644 (file)
@@ -315,4 +315,15 @@ dungeon = {
          },
       }
    },
+   {
+      name = "The Tutorial",
+      base = 1,
+      flags = { "mazelike", "unconnected" },
+      levels = {
+         {
+            name = "tut-1",
+            base = 1,
+         },
+      }
+   },
 }
index c3887c3e89e10bb5210b1ad7ffd0a9467102fe53..5b188efb921631331d3d77290e12ef4a65e6caec 100644 (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.
index fc05c6c8d52dc8fe3f63d225671e8dced4c73a3a..849188e7f2032a072c70251264fe357836502b36 100644 (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
diff --git a/dat/tut-1.lua b/dat/tut-1.lua
new file mode 100644 (file)
index 0000000..85f7929
--- /dev/null
@@ -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} });
+
index eaec202cf9ca8bd2071776a253ef53b20c3a17a2..c934ced219f02131371ba14db3baea591e9bd566 100644 (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
index 198eb94a5c3e6f9a240d0e912fbb919b77dd86a5..6c29cd0aa7a02f339c30b905673df1670ddc60b9 100644 (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
index 7f7a6e8ce71db8e11e2b9b51e17501e6431e388b..1e580e39cab85a687ca1e81e87f3c1c5f989f18c 100644 (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
index a2d575eeafc965081ceca878e216192bd5eb4645..67ed82c7ec6c5b636e247c83024faf3fbdf4a327 100644 (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:
index 4a89d1492be1d1d492e7aa28d2170320266a2900..3a5edf54d50f24dc841cd3b49cb35f69646a7190 100644 (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 */
index 84e523a9450555f819bfeb838f90591b0116f2fa..106de5655ed067edb9225f1032c8b71403b66aba 100644 (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)
index 5beb89815b6ce74988aa7f920fa502270800adae..e389b3641a21a45a595ed0dc68d7313b0607a7d8 100644 (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 */
index c09cc5eaaa81d90a5363b47465c952a893aca86c..380f8227a649baab939b416e15693b1cb2346ce9 100644 (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) \
index 3ec710d20798974d1131066c2318ef28433b4886..2d141e8f11fdde59ae56c587bf7e7bda5a2cd8b6 100644 (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);
index e78a2183476b1547a0d5a57307d9a6e4e6deb430..00acc4562aef9280bc00323c224c0074f7bbca2a 100644 (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 */
index 4fecb3c1fe5912a08b88dfddaf56d7685506afc8..c7c33f40d7b9f4a15215a379fe5ea8e482cb0ec7 100644 (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,
index ab833e0dacea09a695fb83585e79a830bf04f750..ca59e5b2690093482d252a9558107e5a19a7de92 100644 (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,
index c49335cbda0f2740f2ae960b3c462549924aa416..2c35824f5dce369f8a982733167429715c6191e9 100644 (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.
index 86ba94c05ba57415bf4d9c368f6e88612b8b0bff..4f99824920a191a6a62e0e786de695b9983675a6 100644 (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 */
 };
 
index 86035b38d25a14a93e5e91806dd9f57e64f559de..c85db2581eb23b7d257d12e19d46a0c951dfcc1c 100644 (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 */
index 39213e172dce1167a9076e5371b3fe361ad6e901..5f8adb43781a401ad3c81faccd6607bfd8d439c8 100644 (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();
     }
index 408a41f0cb318ce394ca8c00efbc3ce7c3b200cf..70fba772bd8157ba9606ad3d77857c11e5550353 100644 (file)
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -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))
 {
index 3af274ae5ab7276aa1d1b47bed9ec881ea997f71..77955cdc0103cc69a5ca2805ccc5ab8ab2fca309 100644 (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",
index 29418b56866cbea570e23a75cdd604b283190729..f668751cf5c330b8e5987a353b439e6b2c65b0ab 100644 (file)
--- a/src/do.c
+++ b/src/do.c
@@ -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!");
             }
index 0ef3aa6be47be89f6fe0b79753c1ff989dbee862..8621431ac247491a80c51eef8cb5aefebcabe50c 100644 (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;
index c2e0e52081d00df8d45a393711652277abaf3f5e..6f43656f8b19dc52a77e9afbd6526e3528efe838 100644 (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) {
index 5e63da39a5ea3a5265c3ca0200bf0d317f1b0df1..08b59ca2e5c79af621db8c69c24657b286c52c0b 100644 (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 */
index d6f4a588fe74cf563b5248ceb98193584bb7a248..503cdb43cf308e1337bf91ec42aa4f954daadb4c 100644 (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
index 0303b53a85d78fdb11bafdd82b8f35792f3710fd..a3cf91b48ef1dbc8281cd3e5aef64196d0dcd5a8 100644 (file)
--- a/src/mon.c
+++ b/src/mon.c
@@ -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
index 1a67b185a1a65bbe3b903239f81c13bd5b527f7f..26793e90ad773687e8d7ab80d1e10cac9182a9ac 100644 (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},
index e86ba433fa099c509318bebc9d07a3025e308356..e9ddb21fbf4c74809fcc6a5371fed1316e112086 100644 (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;
+}
+
 /*
  **********************************
  *
index 87d1c25b22625017b814884a2f92a65ad1b7e131..eecbf43d6eb3246b524387117e1a9ee2d99a6634 100644 (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();
 
index 70d4b50f350e052fa0ae63898135ea25f97a1557..9027dbef9293795a33d940232ebc9beae40a8a6f 100644 (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
index d837e9ed158ab7339126fee65d4bc389568ac1af..d66d4bdca7f93eb26b64573a312b333270955eff 100644 (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
index 47b763017618c9e4869898c7abdb84b854e9bea5..6dd49f8f06bd627b69fb5095c0d54c77d22f6b2d 100644 (file)
                                "$(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;
index 06902247faa394b66923c9654dd68c8d1f1a6245..68f36e966c131d65a811b675e834236952c808ac 100755 (executable)
@@ -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"
 $!
index 6ac77b42802ea6cd6e4e988e73ba508095612081..ea5f90f1faf752c939d5c89ceaaeefa474f983ca 100644 (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)))
 
index 90021f65dca53ca497d798645d9f05e8a36e93da..1de0c79888c2ac253617daf4f20ac23217b2ae57 100644 (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.
 #
index 073e55429872af0fb3bc007cb220284e5691a81f..c9aa1a0f860386bf806174fdc09b4301ed060c8e 100644 (file)
     <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"/>