summaryrefslogtreecommitdiffstats
path: root/crawl-ref
diff options
context:
space:
mode:
authordshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2007-07-26 11:06:09 +0000
committerdshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2007-07-26 11:06:09 +0000
commitf2a19d9971bb076f4a80a9e41ba601bc862d0fdf (patch)
treebe553fe84132e1f25d5f33fb98f703378096b961 /crawl-ref
parentbc1a54904787e2f4779a99f2be1f72f6fd87679b (diff)
downloadcrawl-ref-f2a19d9971bb076f4a80a9e41ba601bc862d0fdf.tar.gz
crawl-ref-f2a19d9971bb076f4a80a9e41ba601bc862d0fdf.zip
Replaced timed markers with Lua markers. Breaks save compatibility.
KFEAT: feature names are now as in the dungeon_feature_type enum. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@1930 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'crawl-ref')
-rw-r--r--crawl-ref/docs/level-design.txt14
-rw-r--r--crawl-ref/source/clua.cc96
-rw-r--r--crawl-ref/source/clua.h31
-rw-r--r--crawl-ref/source/dat/bazaar.des8
-rw-r--r--crawl-ref/source/dat/clua/lm_1way.lua35
-rw-r--r--crawl-ref/source/dat/clua/lm_timed.lua105
-rw-r--r--crawl-ref/source/dat/clua/lm_tmsg.lua106
-rw-r--r--crawl-ref/source/dat/clua/luamark.lua88
-rw-r--r--crawl-ref/source/dat/float.des2
-rw-r--r--crawl-ref/source/dat/hells.des4
-rw-r--r--crawl-ref/source/dat/lab.des7
-rw-r--r--crawl-ref/source/dat/large.des2
-rw-r--r--crawl-ref/source/dat/mini.des22
-rw-r--r--crawl-ref/source/dat/orc.des6
-rw-r--r--crawl-ref/source/dat/temple.des24
-rw-r--r--crawl-ref/source/dgnevent.h5
-rw-r--r--crawl-ref/source/direct.cc25
-rw-r--r--crawl-ref/source/dungeon.cc1
-rw-r--r--crawl-ref/source/externs.h1
-rw-r--r--crawl-ref/source/files.cc1
-rw-r--r--crawl-ref/source/libutil.cc27
-rw-r--r--crawl-ref/source/libutil.h3
-rw-r--r--crawl-ref/source/luadgn.cc367
-rw-r--r--crawl-ref/source/luadgn.h13
-rw-r--r--crawl-ref/source/mapdef.cc40
-rw-r--r--crawl-ref/source/mapdef.h2
-rw-r--r--crawl-ref/source/mapmark.cc298
-rw-r--r--crawl-ref/source/mapmark.h48
-rw-r--r--crawl-ref/source/message.cc6
-rw-r--r--crawl-ref/source/misc.cc36
-rw-r--r--crawl-ref/source/misc.h4
-rw-r--r--crawl-ref/source/mon-util.cc12
-rw-r--r--crawl-ref/source/monstuff.cc16
-rw-r--r--crawl-ref/source/mstuff2.cc6
-rw-r--r--crawl-ref/source/overmap.cc72
-rw-r--r--crawl-ref/source/overmap.h3
-rw-r--r--crawl-ref/source/player.cc9
-rw-r--r--crawl-ref/source/tags.cc9
-rw-r--r--crawl-ref/source/tags.h2
-rw-r--r--crawl-ref/source/util/levcomp.lpp26
-rw-r--r--crawl-ref/source/util/levcomp.ypp27
-rw-r--r--crawl-ref/source/view.cc5
-rw-r--r--crawl-ref/source/view.h1
43 files changed, 1267 insertions, 348 deletions
diff --git a/crawl-ref/docs/level-design.txt b/crawl-ref/docs/level-design.txt
index 2c42429363..33d52687c2 100644
--- a/crawl-ref/docs/level-design.txt
+++ b/crawl-ref/docs/level-design.txt
@@ -517,7 +517,7 @@ NSUBST: ? = 3:w / *:l
omit the initial N: or N=, then 1= is assumed, except for the
last spec where *= is assumed.
-KFEAT: Z = C / needle trap / antique armour shop / altar of Zin
+KFEAT: Z = C / needle trap / antique armour shop / altar_zin
The KFEAT: directive allows you to specify a placeholder symbol
that is replaced with another symbol, named feature, trap, or
shop. For example, the line above will replace occurrences of Z
@@ -530,11 +530,11 @@ KFEAT: Z = C / needle trap / antique armour shop / altar of Zin
You'll notice that 'Z' is the symbol of the Orb of Zot. Kxxx
directives allow you to assign arbitrary definitions to any symbol.
- KFEAT features are specified as a unique substring of the in-game
- description of the feature; simple * and ? wildcards can also be
- used. For instance, to place a portal to the Abyss, you can use:
+ KFEAT features are specified as a feature name (see section I
+ for a full list of feature names). As another example, you can
+ place a portal to the Abyss as:
- KFEAT: A = gate to the * Abyss
+ KFEAT: A = enter_abyss
If you want no feature as an option in a KFEAT line, use 'floor'.
If you do not want to specify the type of shop, use 'any shop' or
@@ -611,8 +611,8 @@ MARKER: A = feat:<feature_name> or timer:
feature to deep water at the end of it.
Feature names used in markers must be names matching the
- names in the source code, not the descriptions as used in
- KFEAT. There's a full list of feature names at the end of this
+ names in the source code. There's a full list of feature
+ names in section I (Feature names) at the end of this
document.
diff --git a/crawl-ref/source/clua.cc b/crawl-ref/source/clua.cc
index d16c2bf31d..717707c1cc 100644
--- a/crawl-ref/source/clua.cc
+++ b/crawl-ref/source/clua.cc
@@ -11,6 +11,7 @@
#include "chardump.h"
#include "cio.h"
#include "delay.h"
+#include "dgnevent.h"
#include "files.h"
#include "food.h"
#include "invent.h"
@@ -32,6 +33,7 @@
#include "spl-util.h"
#include "stuff.h"
#include "travel.h"
+#include "view.h"
#include <cstring>
#include <map>
@@ -215,7 +217,7 @@ int CLua::loadstring(const char *s, const char *context)
return loadbuffer(s, strlen(s), context);
}
-int CLua::execstring(const char *s, const char *context)
+int CLua::execstring(const char *s, const char *context, int nresults)
{
int err = 0;
if ((err = loadstring(s, context)))
@@ -223,7 +225,7 @@ int CLua::execstring(const char *s, const char *context)
lua_State *ls = state();
lua_call_throttle strangler(this);
- err = lua_pcall(ls, 0, 0, 0);
+ err = lua_pcall(ls, 0, nresults, 0);
set_error(err, ls);
return err;
}
@@ -266,6 +268,9 @@ int CLua::execfile(const char *filename, bool trusted, bool die_on_fail)
if (!err)
err = lua_pcall(ls, 0, 0, 0);
set_error(err);
+ if (die_on_fail && !error.empty())
+ end(1, false, "Lua execfile error (%s): %s",
+ filename, dlua.error.c_str());
return (err);
}
@@ -411,6 +416,9 @@ int CLua::push_args(lua_State *ls, const char *format, va_list args,
case 'b':
lua_pushboolean(ls, va_arg(args, int));
break;
+ case 'D':
+ clua_push_dgn_event(ls, va_arg(args, const dgn_event *));
+ break;
case 'm':
clua_push_map(ls, va_arg(args, map_def *));
break;
@@ -579,6 +587,8 @@ void CLua::init_lua()
if (!_state)
return;
+ lua_stack_cleaner clean(_state);
+
lua_atpanic(_state, clua_panic);
luaopen_base(_state);
@@ -601,16 +611,25 @@ void CLua::init_lua()
load_cmacro();
load_chooks();
+ lua_register(_state, "loadfile", clua_loadfile);
+ lua_register(_state, "dofile", clua_dofile);
+
if (managed_vm)
{
lua_register(_state, "pcall", clua_guarded_pcall);
- lua_register(_state, "loadfile", clua_loadfile);
- lua_register(_state, "dofile", clua_dofile);
-
execfile("clua/userbase.lua", true, true);
- }
+ }
- lua_settop(_state, 1);
+ lua_pushboolean(_state, managed_vm);
+ setregistry("lua_vm_is_managed");
+}
+
+bool CLua::is_managed_vm(lua_State *ls)
+{
+ lua_stack_cleaner clean(ls);
+ lua_pushstring(ls, "lua_vm_is_managed");
+ lua_gettable(ls, LUA_REGISTRYINDEX);
+ return (lua_toboolean(ls, -1));
}
void CLua::load_chooks()
@@ -652,15 +671,6 @@ static void clua_register_metatable(lua_State *ls, const char *tn,
lua_settop(ls, top);
}
-template <class T> T *clua_new_userdata(
- lua_State *ls, const char *mt)
-{
- void *udata = lua_newuserdata( ls, sizeof(T) );
- luaL_getmetatable(ls, mt);
- lua_setmetatable(ls, -2);
- return static_cast<T*>( udata );
-}
-
/////////////////////////////////////////////////////////////////////
// Bindings to get information on the player
//
@@ -724,6 +734,9 @@ LUARET1(you_branch, string, level_id::current().describe(false, false).c_str())
LUARET1(you_subdepth, number, level_id::current().depth)
LUARET1(you_absdepth, number, you.your_level + 1)
LUAWRAP(you_stop_activity, interrupt_activity(AI_FORCE_INTERRUPT))
+LUARET1(you_turns, number, you.num_turns)
+LUARET1(you_see_grid, boolean,
+ see_grid(luaL_checkint(ls, 1), luaL_checkint(ls, 2)))
void lua_push_floor_items(lua_State *ls);
static int you_floor_items(lua_State *ls)
@@ -764,6 +777,7 @@ static int l_you_abils(lua_State *ls)
static const struct luaL_reg you_lib[] =
{
{ "turn_is_over", you_turn_is_over },
+ { "turns" , you_turns },
{ "spells" , l_you_spells },
{ "abilities" , l_you_abils },
{ "name" , you_name },
@@ -800,6 +814,9 @@ static const struct luaL_reg you_lib[] =
{ "branch", you_branch },
{ "subdepth", you_subdepth },
{ "absdepth", you_absdepth },
+
+ { "see_grid", you_see_grid },
+
{ NULL, NULL },
};
@@ -1268,33 +1285,6 @@ static int l_item_worn(lua_State *ls)
return (2);
}
-static description_level_type desc_code(const char *desc)
-{
- if (!desc)
- return DESC_PLAIN;
-
- if (!strcmp("The", desc))
- return DESC_CAP_THE;
- else if (!strcmp("the", desc))
- return DESC_NOCAP_THE;
- else if (!strcmp("A", desc))
- return DESC_CAP_A;
- else if (!strcmp("a", desc))
- return DESC_NOCAP_A;
- else if (!strcmp("Your", desc))
- return DESC_CAP_YOUR;
- else if (!strcmp("your", desc))
- return DESC_NOCAP_YOUR;
- else if (!strcmp("its", desc))
- return DESC_NOCAP_ITS;
- else if (!strcmp("worn", desc))
- return DESC_INVENTORY_EQUIP;
- else if (!strcmp("inv", desc))
- return DESC_INVENTORY;
-
- return DESC_PLAIN;
-}
-
static int l_item_name(lua_State *ls)
{
LUA_ITEM(item, 1);
@@ -1302,7 +1292,9 @@ static int l_item_name(lua_State *ls)
{
description_level_type ndesc = DESC_PLAIN;
if (lua_isstring(ls, 2))
- ndesc = desc_code(lua_tostring(ls, 2));
+ ndesc = description_type_by_name(lua_tostring(ls, 2));
+ else if (lua_isnumber(ls, 2))
+ ndesc = static_cast<description_level_type>(luaL_checkint(ls, 2));
bool terse = lua_toboolean(ls, 3);
lua_pushstring(ls, item->name(ndesc, terse).c_str());
}
@@ -1916,6 +1908,9 @@ LUARET1(crawl_random2, number, random2( luaL_checkint(ls, 1) ))
LUARET1(crawl_one_chance_in, boolean, one_chance_in( luaL_checkint(ls, 1) ))
LUARET1(crawl_random2avg, number,
random2avg( luaL_checkint(ls, 1), luaL_checkint(ls, 2) ))
+LUARET1(crawl_random_range, number,
+ random_range( luaL_checkint(ls, 1), luaL_checkint(ls, 2),
+ lua_isnumber(ls, 3)? luaL_checkint(ls, 3) : 1 ))
LUARET1(crawl_coinflip, boolean, coinflip())
static int crawl_err_trace(lua_State *ls)
@@ -1959,6 +1954,7 @@ static const struct luaL_reg crawl_lib[] =
{ "one_chance_in", crawl_one_chance_in },
{ "random2avg" , crawl_random2avg },
{ "coinflip", crawl_coinflip },
+ { "random_range", crawl_random_range },
{ "redraw_screen", crawl_redraw_screen },
{ "input_line", crawl_input_line },
{ "c_input_line", crawl_c_input_line},
@@ -1996,7 +1992,6 @@ void luaopen_crawl(lua_State *ls)
///////////////////////////////////////////////////////////
// File operations
-
static const struct luaL_reg file_lib[] =
{
{ "write", CLua::file_write },
@@ -2262,6 +2257,13 @@ void clua_push_map(lua_State *ls, map_def *map)
*mapref = map;
}
+void clua_push_dgn_event(lua_State *ls, const dgn_event *devent)
+{
+ const dgn_event **de =
+ clua_new_userdata<const dgn_event *>(ls, DEVENT_METATABLE);
+ *de = devent;
+}
+
void luaopen_monsters(lua_State *ls)
{
luaL_newmetatable(ls, MONS_METATABLE);
@@ -2626,7 +2628,7 @@ static int clua_loadfile(lua_State *ls)
if (!file)
return (0);
- const int err = CLua::loadfile(ls, file);
+ const int err = CLua::loadfile(ls, file, !CLua::is_managed_vm(ls));
if (err)
{
const int place = lua_gettop(ls);
@@ -2643,7 +2645,7 @@ static int clua_dofile(lua_State *ls)
if (!file)
return (0);
- const int err = CLua::loadfile(ls, file);
+ const int err = CLua::loadfile(ls, file, !CLua::is_managed_vm(ls));
if (err)
return (lua_error(ls));
diff --git a/crawl-ref/source/clua.h b/crawl-ref/source/clua.h
index 44da65b057..99a4e24bc3 100644
--- a/crawl-ref/source/clua.h
+++ b/crawl-ref/source/clua.h
@@ -23,6 +23,17 @@ extern "C" {
#endif
class CLua;
+
+class lua_stack_cleaner
+{
+public:
+ lua_stack_cleaner(lua_State *_ls) : ls(_ls), top(lua_gettop(_ls)) { }
+ ~lua_stack_cleaner() { lua_settop(ls, top); }
+private:
+ lua_State *ls;
+ int top;
+};
+
class lua_call_throttle
{
public:
@@ -55,7 +66,7 @@ public:
void setglobal(const char *name);
void getglobal(const char *name);
-
+
// Assigns the value on top of the stack to a unique name in the registry
// and returns the name.
std::string setuniqregistry();
@@ -65,7 +76,8 @@ public:
int loadbuffer(const char *buf, size_t size, const char *context);
int loadstring(const char *str, const char *context);
- int execstring(const char *str, const char *context = "init.txt");
+ int execstring(const char *str, const char *context = "init.txt",
+ int nresults = 0);
int execfile(const char *filename, bool trusted = false,
bool die_on_fail = false);
@@ -80,6 +92,8 @@ public:
bool trusted = false, bool die_on_fail = false);
static bool is_path_safe(std::string file, bool trusted = false);
+ static bool is_managed_vm(lua_State *ls);
+
public:
std::string error;
@@ -208,8 +222,21 @@ inline static T *clua_get_userdata(lua_State *ls, const char *mt)
std::string quote_lua_string(const std::string &s);
class map_def;
+class dgn_event;
void clua_push_map(lua_State *ls, map_def *map);
+void clua_push_dgn_event(lua_State *ls, const dgn_event *devent);
+
+template <class T> T *clua_new_userdata(
+ lua_State *ls, const char *mt)
+{
+ void *udata = lua_newuserdata( ls, sizeof(T) );
+ luaL_getmetatable(ls, mt);
+ lua_setmetatable(ls, -2);
+ return static_cast<T*>( udata );
+}
#define MAP_METATABLE "dgn.mtmap"
+#define DEVENT_METATABLE "dgn.devent"
+#define MAPMARK_METATABLE "dgn.mapmark"
#endif
diff --git a/crawl-ref/source/dat/bazaar.des b/crawl-ref/source/dat/bazaar.des
index 618509f125..cc1cb17d0b 100644
--- a/crawl-ref/source/dat/bazaar.des
+++ b/crawl-ref/source/dat/bazaar.des
@@ -9,8 +9,14 @@
lua {{
function check_expire_marker(e)
+ e.messager =
+ bell_clock_msg { initmsg="You hear coins being counted." }
if not crawl.one_chance_in(3) then
- e.marker("O = timer: 1000-1500")
+ e.marker( [[ O = lua: timed_marker {
+ low=1000, high=1500, msg=messager }
+ ]] )
+ else
+ e.marker("O = lua: one_way_stair()")
end
end
}}
diff --git a/crawl-ref/source/dat/clua/lm_1way.lua b/crawl-ref/source/dat/clua/lm_1way.lua
new file mode 100644
index 0000000000..6ce1e6d722
--- /dev/null
+++ b/crawl-ref/source/dat/clua/lm_1way.lua
@@ -0,0 +1,35 @@
+------------------------------------------------------------------------------
+-- lm_1way.lua:
+-- One-way stair marker.
+------------------------------------------------------------------------------
+
+OneWayStair = { }
+OneWayStair.__index = OneWayStair
+
+function OneWayStair.new()
+ local ows = { }
+ setmetatable(ows, OneWayStair)
+ return ows
+end
+
+function OneWayStair:activate(marker)
+ local ev = dgn.dgn_event_type('player_climb')
+ dgn.register_listener(ev, marker, marker:pos())
+end
+
+function OneWayStair:event(marker, ev)
+ if ev:type() == dgn.dgn_event_type('player_climb') then
+ local x, y = ev:pos()
+ dgn.terrain_changed(x, y, 'floor', false)
+ dgn.remove_listener(marker, ev:pos())
+ dgn.remove_marker(marker)
+ end
+end
+
+function OneWayStair.read(marker, th)
+ return OneWayStair.new()
+end
+
+function one_way_stair()
+ return OneWayStair.new()
+end
diff --git a/crawl-ref/source/dat/clua/lm_timed.lua b/crawl-ref/source/dat/clua/lm_timed.lua
new file mode 100644
index 0000000000..8b9f1aaf26
--- /dev/null
+++ b/crawl-ref/source/dat/clua/lm_timed.lua
@@ -0,0 +1,105 @@
+------------------------------------------------------------------------------
+-- lm_timed.lua:
+-- Lua timed map feature markers.
+------------------------------------------------------------------------------
+
+dofile('clua/lm_tmsg.lua')
+
+TimedMarker = { }
+TimedMarker.__index = TimedMarker
+
+function TimedMarker._new()
+ local marker = { }
+ setmetatable(marker, TimedMarker)
+ return marker
+end
+
+function TimedMarker.new(pars)
+ pars = pars or { }
+ if not pars.msg then
+ error("No messaging object provided (msg = nil)")
+ end
+ pars.high = pars.high or pars.low or pars.turns or 1
+ pars.low = pars.low or pars.high or pars.turns or 1
+ local dur = crawl.random_range(pars.low, pars.high, pars.navg or 1)
+ local feat = pars.feat or 'floor'
+ local fnum = dgn.feature_number(feat)
+ if fnum == dgn.feature_number('unseen') then
+ error("Bad feature name: " .. feat)
+ end
+
+ local tmarker = TimedMarker._new()
+ tmarker.dur = dur * 10
+ tmarker.fnum = fnum
+ tmarker.feat = feat
+ tmarker.msg = pars.msg
+ return tmarker
+end
+
+function TimedMarker:activate(marker)
+ self.msg:init(self, marker)
+
+ dgn.register_listener(dgn.dgn_event_type('turn'), marker)
+ dgn.register_listener(dgn.dgn_event_type('player_climb'),
+ marker, marker:pos())
+end
+
+function TimedMarker:timeout(marker, verbose, affect_player)
+ local x, y = marker:pos()
+ if verbose then
+ if you.see_grid(marker:pos()) then
+ crawl.mpr(dgn.feature_desc(dgn.grid(x, y), "The") ..
+ " disappears!")
+ else
+ crawl.mpr("The walls and floor vibrate strangely for a moment.")
+ end
+ end
+ dgn.terrain_changed(x, y, self.fnum, affect_player)
+ dgn.remove_listener(marker)
+ dgn.remove_listener(marker, marker:pos())
+ dgn.remove_marker(marker)
+end
+
+function TimedMarker:event(marker, ev)
+ self.ticktype = self.ticktype or dgn.dgn_event_type('turn')
+ self.stairtype = self.stairtype or dgn.dgn_event_type('player_climb')
+
+ if ev:type() == self.stairtype then
+ local mx, my = marker:pos()
+ local ex, ey = ev:pos()
+ if mx == ex and my == ey then
+ self:timeout(marker, false, false)
+ end
+ return
+ end
+
+ if ev:type() ~= self.ticktype then
+ return
+ end
+ self.dur = self.dur - ev:ticks()
+ self.msg:event(self, marker, ev)
+ if self.dur <= 0 then
+ self:timeout(marker, true, true)
+ end
+end
+
+function TimedMarker:describe(marker)
+ return self.feat .. "/" .. tostring(self.dur)
+end
+
+function TimedMarker.read(marker, th)
+ local marker = TimedMarker._new()
+ marker.dur = file.unmarshall_number(th)
+ marker.fnum = file.unmarshall_number(th)
+ marker.feat = file.unmarshall_string(th)
+ marker.msg = file.unmarshall_fn(th)(th)
+ return marker
+end
+
+function TimedMarker:write(marker, th)
+ file.marshall(th, self.dur)
+ file.marshall(th, self.fnum)
+ file.marshall(th, self.feat)
+ file.marshall(th, self.msg.read)
+ self.msg:write(th)
+end
diff --git a/crawl-ref/source/dat/clua/lm_tmsg.lua b/crawl-ref/source/dat/clua/lm_tmsg.lua
new file mode 100644
index 0000000000..1448e7ade0
--- /dev/null
+++ b/crawl-ref/source/dat/clua/lm_tmsg.lua
@@ -0,0 +1,106 @@
+------------------------------------------------------------------------------
+-- lm_tmsg.lua:
+-- Messaging for timed Lua markers.
+------------------------------------------------------------------------------
+
+TimedMessaging = { }
+TimedMessaging.__index = TimedMessaging
+
+function TimedMessaging._new()
+ local m = { }
+ setmetatable(m, TimedMessaging)
+ return m
+end
+
+function TimedMessaging.new(pars)
+ pars = pars or { }
+ local m = TimedMessaging._new()
+ m.noisemaker = pars.noisemaker
+ m.verb = pars.verb
+ m.finalmsg = pars.finalmsg
+ m.ranges = pars.ranges
+ m.initmsg = pars.initmsg or ''
+ return m
+end
+
+function TimedMessaging:init(tmarker, cm)
+ local lab = dgn.grid(cm:pos()) == dgn.feature_number('enter_labyrinth')
+ if not self.noisemaker then
+ self.noisemaker = lab and "an ancient clock" or "a massive bell"
+ end
+
+ self.verb = self.verb or (lab and 'ticking' or 'tolling')
+
+ if not self.finalmsg then
+ self.finalmsg = lab and "last, dying ticks of the clock"
+ or "last, dying notes of the bell"
+ end
+
+ if not self.ranges then
+ self.ranges = { { 5000, 'stately ' }, { 4000, '' },
+ { 2500, 'brisk ' }, { 1500, 'urgent ' },
+ { 0, 'frantic ' } }
+ end
+
+ self.check = self.check or tmarker.dur - 500
+ if self.check < 50 then
+ self.check = 50
+ end
+
+ if #self.initmsg > 0 and you.hear_pos(cm:pos()) then
+ crawl.mpr(self.initmsg, "sound")
+ end
+end
+
+function TimedMessaging:say_message(dur)
+ self.sound_channel = self.sound_channel or crawl.msgch_num('sound')
+ if dur <= 0 then
+ crawl.mpr("You hear the " .. self.finalmsg .. ".", self.sound_channel)
+ return
+ end
+ for _, chk in ipairs(self.ranges) do
+ if dur > chk[1] then
+ crawl.mpr("You hear the " .. chk[2] .. self.verb
+ .. " of " .. self.noisemaker .. ".",
+ self.sound_channel)
+ break
+ end
+ end
+end
+
+function TimedMessaging:event(luamark, cmarker, event)
+ if luamark.dur < self.check or luamark.dur <= 0 then
+ self.check = luamark.dur - 250
+ if luamark.dur > 900 then
+ self.check = self.check - 250
+ end
+
+ if you.hear_pos(cmarker:pos()) then
+ self:say_message(luamark.dur)
+ end
+ end
+end
+
+function TimedMessaging:write(th)
+ file.marshall(th, self.check)
+ file.marshall(th, self.noisemaker)
+ file.marshall(th, self.verb)
+ file.marshall(th, self.initmsg)
+ file.marshall(th, self.finalmsg)
+ lmark.marshall_table(th, self.ranges)
+end
+
+function TimedMessaging.read(th)
+ local tm = TimedMessaging._new()
+ tm.check = file.unmarshall_number(th)
+ tm.noisemaker = file.unmarshall_string(th)
+ tm.verb = file.unmarshall_string(th)
+ tm.initmsg = file.unmarshall_string(th)
+ tm.finalmsg = file.unmarshall_string(th)
+ tm.ranges = lmark.unmarshall_table(th)
+ return tm
+end
+
+function bell_clock_msg(pars)
+ return TimedMessaging.new(pars)
+end
diff --git a/crawl-ref/source/dat/clua/luamark.lua b/crawl-ref/source/dat/clua/luamark.lua
new file mode 100644
index 0000000000..d2d12ce34e
--- /dev/null
+++ b/crawl-ref/source/dat/clua/luamark.lua
@@ -0,0 +1,88 @@
+------------------------------------------------------------------------------
+-- luamark.lua:
+-- Lua map marker handling.
+------------------------------------------------------------------------------
+
+dofile('clua/lm_timed.lua')
+dofile('clua/lm_1way.lua')
+
+function dlua_marker_function(table, name)
+ return table[name]
+end
+
+function dlua_marker_method(table, name, marker, ...)
+ if table[name] then
+ return table[name](table, marker, ...)
+ end
+end
+
+function dlua_marker_read(fn, marker, th)
+ return fn(marker, th)
+end
+
+function timed_marker(pars)
+ return TimedMarker.new(pars)
+end
+
+lmark = { }
+
+-- Marshalls a table comprising of keys that are strings or numbers only,
+-- and values that are strings, numbers, functions, or tables only. The table
+-- cannot have cycles, and the table's metatable is not preserved.
+function lmark.marshall_table(th, table)
+ if not table then
+ file.marshall(th, -1)
+ return
+ end
+
+ -- Count the number of elements first (ugh)
+ local nsize = 0
+ local tsize = 0
+ for _, v in pairs(table) do
+ if type(v) == 'table' then
+ tsize = tsize + 1
+ else
+ nsize = nsize + 1
+ end
+ end
+
+ file.marshall(th, nsize)
+ for key, value in pairs(table) do
+ if type(value) ~= 'table' then
+ file.marshall_meta(th, key)
+ file.marshall_meta(th, value)
+ end
+ end
+
+ file.marshall(th, tsize)
+ for key, value in pairs(table) do
+ if type(value) == 'table' then
+ file.marshall_meta(th, key)
+ lmark.marshall_table(th, value)
+ end
+ end
+end
+
+-- Unmarshals a table marshaled by marshall_table.
+function lmark.unmarshall_table(th)
+ local nsize = file.unmarshall_number(th)
+
+ if nsize == -1 then
+ return nil
+ end
+
+ local ret = { }
+ for i = 1, nsize do
+ local key = file.unmarshall_meta(th)
+ local val = file.unmarshall_meta(th)
+ ret[key] = val
+ end
+
+ local tsize = file.unmarshall_number(th)
+ for i = 1, tsize do
+ local key = file.unmarshall_meta(th)
+ local val = lmark.unmarshall_table(th)
+ ret[key] = val
+ end
+ return ret
+end
diff --git a/crawl-ref/source/dat/float.des b/crawl-ref/source/dat/float.des
index 4922553783..175ef6ee52 100644
--- a/crawl-ref/source/dat/float.des
+++ b/crawl-ref/source/dat/float.des
@@ -283,7 +283,7 @@ MONS: rat/grey rat/w:6 green rat/w:3 orange rat/w:20 giant bat
MONS: flying skull, human zombie
MONS: vampire, vampire mage, vampire knight
KITEM: > = any good_item
-KFEAT: ; = altar of Yredelemnul/altar of Kikubaaqudgha
+KFEAT: ; = altar_yredelemnul/altar_kikubaaqudgha
SHUFFLE: defg
SHUFFLE: *s/!?
SUBST: d = =, e = x, f = x, g = x
diff --git a/crawl-ref/source/dat/hells.des b/crawl-ref/source/dat/hells.des
index 05e7d75db8..a98f785006 100644
--- a/crawl-ref/source/dat/hells.des
+++ b/crawl-ref/source/dat/hells.des
@@ -111,7 +111,9 @@ ENDMAP
NAME: vestibule_of_hell
PLACE: Hell
ORIENT: encompass
-MARKER: D=feat:enter_dis, G=feat:enter_gehenna, C=feat:enter_cocytus
+MARKER: D=feat:enter_dis
+MARKER: G=feat:enter_gehenna
+MARKER: C=feat:enter_cocytus
MARKER: T=feat:enter_tartarus
SUBST: D=A, G=A, C=A, T=A
diff --git a/crawl-ref/source/dat/lab.des b/crawl-ref/source/dat/lab.des
index a5a4e68cec..72056aa1d4 100644
--- a/crawl-ref/source/dat/lab.des
+++ b/crawl-ref/source/dat/lab.des
@@ -12,7 +12,10 @@ NAME: lab_entry_generic
TAGS: lab_entry transparent
DEPTH: 12-26
ORIENT: float
-MARKER: O = timer:400-600
+: messager = bell_clock_msg { initmsg="You hear a distant snort." }
+MARKER: O = lua: timed_marker { \
+ low=400, high=600, msg=messager \
+ }
MAP
O
ENDMAP
@@ -344,7 +347,7 @@ ENDMAP
# Teaser: fake exit
NAME: labyrinth_fake_exit
TAGS: lab
-KFEAT: X = gate * Abyss
+KFEAT: X = enter_abyss
MAP
........
.vvvvvv.
diff --git a/crawl-ref/source/dat/large.des b/crawl-ref/source/dat/large.des
index 3261f5b342..e34102e181 100644
--- a/crawl-ref/source/dat/large.des
+++ b/crawl-ref/source/dat/large.des
@@ -781,7 +781,7 @@ FLAGS: no_rotate
DEPTH: D:14-26, Orc
MONS: orc warlord, orc priest, orc high priest, orc warrior, orc wizard
MONS: orc knight, orc sorcerer
-KFEAT: C = altar of Beogh
+KFEAT: C = altar_beogh
MAP
xxxxxxxxxxxxxxxxxxxxxxx@xxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx
diff --git a/crawl-ref/source/dat/mini.des b/crawl-ref/source/dat/mini.des
index bb0b679b8d..97472702c6 100644
--- a/crawl-ref/source/dat/mini.des
+++ b/crawl-ref/source/dat/mini.des
@@ -398,7 +398,7 @@ NAME: david_defended_altar
DEPTH: 7-20, !Lair, !Swamp, !Shoal
SHUFFLE: 1ID/2TC
MONS: orc priest, deep elf priest / w:30 nothing
-KFEAT: D = altar of Beogh / w:2 altar of Okawaru / w:2 altar of Makhleb
+KFEAT: D = altar_beogh / w:2 altar_okawaru / w:2 altar_makhleb
MAP
ccccc...
c1..c...
@@ -1106,11 +1106,11 @@ SHUFFLE: !;
SUBST: " = .:80 W:30
SUBST: ; = .:80 W:30
SUBST: ! = w:20 W ? c
-KFEAT: 3 = deep water
-KFEAT: 2 = shallow water / .
-KFEAT: 1 = shallow water / . w:30
-KFEAT: d = shallow water / .
-KFEAT: e = shallow water / .
+KFEAT: 3 = w
+KFEAT: 2 = W / .
+KFEAT: 1 = W / . w:30
+KFEAT: d = W / .
+KFEAT: e = W / .
KFEAT: ? = spear trap / blade trap
MAP
.........
@@ -1135,11 +1135,11 @@ MONS: yaktaur captain, grey snake, storm dragon
ITEM: potion of cure mutation/potion of gain dexterity/potion of gain strength
ITEM: potion of gain intelligence/potion of experience/w:40 potion of heal wound
SHUFFLE: de, !;
-KFEAT: 3 = deep water / .
-KFEAT: | = shallow water / .
-KFEAT: d = shallow water / .
-KFEAT: e = shallow water / .
-KFEAT: 2 = shallow water
+KFEAT: 3 = w / .
+KFEAT: | = W / .
+KFEAT: d = W / .
+KFEAT: e = W / .
+KFEAT: 2 = W
SUBST: ; = .:80 W:30
SUBST: " = .:80 W:30
SUBST: ! = w:20 W ? c
diff --git a/crawl-ref/source/dat/orc.des b/crawl-ref/source/dat/orc.des
index b541934960..f170b12959 100644
--- a/crawl-ref/source/dat/orc.des
+++ b/crawl-ref/source/dat/orc.des
@@ -148,7 +148,7 @@ ENDMAP
NAME: david_orc_5
TAGS: orc_entry
ORIENT: float
-KFEAT: C = altar of Beogh w:70 / altar of Yredelemnul / altar of Makhleb / altar of Trog
+KFEAT: C = altar_beogh w:70 / altar_yredelemnul / altar_makhleb / altar_trog
SHUFFLE: EFGH
SUBST: E=3
SUBST: F=1, G=1, H=1
@@ -478,7 +478,7 @@ ENDMAP
###################################
# Beogh 1
NAME: mines1_david
-KFEAT: C = altar of Beogh
+KFEAT: C = altar_beogh
MAP
...........
.xxxx.xxxx.
@@ -496,7 +496,7 @@ ENDMAP
###################################
# Beogh 2
NAME: mines2_david
-KFEAT: C = altar of Beogh
+KFEAT: C = altar_beogh
MONS: orc / orc warrior w:1
SUBST: . = . 1:2
MAP
diff --git a/crawl-ref/source/dat/temple.des b/crawl-ref/source/dat/temple.des
index e958377c35..a64b684bde 100644
--- a/crawl-ref/source/dat/temple.des
+++ b/crawl-ref/source/dat/temple.des
@@ -458,18 +458,18 @@ ORIENT: encompass
SHUFFLE: ABC/DEF/GHI/JKL/MNO
SHUFFLE: ABC, DEF, GHI, JKL, MNO
SUBST: A=[, B=(, C={
-KFEAT: D = altar of Trog
-KFEAT: E = altar of Makhleb
-KFEAT: F = altar of Okawaru
-KFEAT: G = altar of The Shining One
-KFEAT: H = altar of Zin
-KFEAT: I = altar of Elyvilon
-KFEAT: J = altar of Sif Muna
-KFEAT: K = altar of Vehumet
-KFEAT: L = altar of Kikubaaqudgha
-KFEAT: M = altar of Yredelemnul
-KFEAT: N = altar of Xom
-KFEAT: O = altar of Nemelex Xobeh
+KFEAT: D = altar_trog
+KFEAT: E = altar_makhleb
+KFEAT: F = altar_okawaru
+KFEAT: G = altar_shining_one
+KFEAT: H = altar_zin
+KFEAT: I = altar_elyvilon
+KFEAT: J = altar_sif_muna
+KFEAT: K = altar_vehumet
+KFEAT: L = altar_kikubaaqudgha
+KFEAT: M = altar_yredelemnul
+KFEAT: N = altar_xom
+KFEAT: O = altar_nemelex_xobeh
SUBST: X : GUT
MAP
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/crawl-ref/source/dgnevent.h b/crawl-ref/source/dgnevent.h
index 9b22d8c477..efc5cdd59a 100644
--- a/crawl-ref/source/dgnevent.h
+++ b/crawl-ref/source/dgnevent.h
@@ -12,6 +12,7 @@
#include "externs.h"
#include <list>
+// Keep event names in luadgn.cc in sync.
enum dgn_event_type
{
DET_NONE = 0x0000,
@@ -20,7 +21,9 @@ enum dgn_event_type
DET_MONSTER_MOVED = 0x0002,
DET_PLAYER_MOVED = 0x0004,
DET_LEAVING_LEVEL = 0x0008,
- DET_ENTERING_LEVEL = 0x0010
+ DET_ENTERING_LEVEL = 0x0010,
+ DET_PLAYER_IN_LOS = 0x0020, // Player just entered LOS.
+ DET_PLAYER_CLIMBS = 0x0040 // Player climbing stairs.
};
class dgn_event
diff --git a/crawl-ref/source/direct.cc b/crawl-ref/source/direct.cc
index 21ffebe163..675e214549 100644
--- a/crawl-ref/source/direct.cc
+++ b/crawl-ref/source/direct.cc
@@ -1211,7 +1211,20 @@ std::string feature_description(dungeon_feature_type grid,
if (add_stop)
desc += ".";
if (dtype == DESC_PLAIN || (!grid_is_trap(grid) && isupper(desc[0])))
+ {
+ if (isupper(desc[0]))
+ {
+ switch (dtype)
+ {
+ case DESC_PLAIN: case DESC_NOCAP_THE: case DESC_NOCAP_A:
+ desc[0] = tolower(desc[0]);
+ break;
+ default:
+ break;
+ }
+ }
return (desc);
+ }
switch (dtype)
{
@@ -1465,10 +1478,7 @@ std::string feature_description(int mx, int my, description_level_type dtype,
case DNGN_ENTER_SHOP:
return (shop_name(mx, my, add_stop));
default:
- return (feature_description(
- grid, NUM_TRAPS,
- env_find_marker(coord_def(mx, my), MAT_TIMED_FEATURE),
- dtype, add_stop));
+ return (feature_description(grid, NUM_TRAPS, false, dtype, add_stop));
}
}
@@ -1703,7 +1713,12 @@ static void describe_cell(int mx, int my)
#ifdef DEBUG_DIAGNOSTICS
std::string marker;
if (map_marker *mark = env_find_marker(coord_def(mx, my), MAT_ANY))
- marker = " (" + mark->describe() + ")";
+ {
+ std::string desc = mark->describe();
+ if (desc.empty())
+ desc = "?";
+ marker = " (" + desc + ")";
+ }
const std::string traveldest =
stair_destination_description(coord_def(mx, my));
mprf("(%d,%d): %s - %s%s%s", mx, my,
diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc
index 7919634957..5a0f9e20b4 100644
--- a/crawl-ref/source/dungeon.cc
+++ b/crawl-ref/source/dungeon.cc
@@ -4433,6 +4433,7 @@ static void place_shops(int level_number, int nshops)
{
place_specific_stair(DNGN_ENTER_BAZAAR, "bzr_entry",
level_number, true);
+ allow_bazaars = false;
}
else
{
diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h
index ceb0f5c0bc..ade4bf5014 100644
--- a/crawl-ref/source/externs.h
+++ b/crawl-ref/source/externs.h
@@ -1027,6 +1027,7 @@ public:
public:
bool has_action_energy() const;
void check_redraw(const coord_def &oldpos) const;
+ void apply_location_effects();
kill_category kill_alignment() const;
diff --git a/crawl-ref/source/files.cc b/crawl-ref/source/files.cc
index 2b64064856..e7e6762ec2 100644
--- a/crawl-ref/source/files.cc
+++ b/crawl-ref/source/files.cc
@@ -931,6 +931,7 @@ bool load( dungeon_feature_type stair_taken, load_mode_type load_mode,
sanity_test_monster_inventory();
+ dungeon_events.fire_event(DET_ENTERING_LEVEL);
// Things to update for player entering level
if (load_mode == LOAD_ENTER_LEVEL)
{
diff --git a/crawl-ref/source/libutil.cc b/crawl-ref/source/libutil.cc
index e1cca6a43e..845f96b380 100644
--- a/crawl-ref/source/libutil.cc
+++ b/crawl-ref/source/libutil.cc
@@ -46,6 +46,33 @@
#include <regex.h>
#endif
+description_level_type description_type_by_name(const char *desc)
+{
+ if (!desc)
+ return DESC_PLAIN;
+
+ if (!strcmp("The", desc))
+ return DESC_CAP_THE;
+ else if (!strcmp("the", desc))
+ return DESC_NOCAP_THE;
+ else if (!strcmp("A", desc))
+ return DESC_CAP_A;
+ else if (!strcmp("a", desc))
+ return DESC_NOCAP_A;
+ else if (!strcmp("Your", desc))
+ return DESC_CAP_YOUR;
+ else if (!strcmp("your", desc))
+ return DESC_NOCAP_YOUR;
+ else if (!strcmp("its", desc))
+ return DESC_NOCAP_ITS;
+ else if (!strcmp("worn", desc))
+ return DESC_INVENTORY_EQUIP;
+ else if (!strcmp("inv", desc))
+ return DESC_INVENTORY;
+
+ return DESC_PLAIN;
+}
+
// Should return true if the filename contains nothing that
// the shell can do damage with.
bool shell_safe(const char *file)
diff --git a/crawl-ref/source/libutil.h b/crawl-ref/source/libutil.h
index 9af70c60ad..183f6fac2f 100644
--- a/crawl-ref/source/libutil.h
+++ b/crawl-ref/source/libutil.h
@@ -15,12 +15,15 @@
#include "AppHdr.h"
#include "defines.h"
+#include "enum.h"
#include <cctype>
#include <string>
#include <vector>
extern const char *standard_plural_qualifiers[];
+description_level_type description_type_by_name(const char *desc);
+
std::string lowercase_string(std::string s);
std::string &lowercase(std::string &s);
std::string &uppercase(std::string &s);
diff --git a/crawl-ref/source/luadgn.cc b/crawl-ref/source/luadgn.cc
index 2cb194a44e..041b0460ad 100644
--- a/crawl-ref/source/luadgn.cc
+++ b/crawl-ref/source/luadgn.cc
@@ -7,12 +7,14 @@
#include "AppHdr.h"
#include "clua.h"
+#include "direct.h"
#include "files.h"
#include "luadgn.h"
#include "mapdef.h"
#include "mapmark.h"
#include "maps.h"
#include "stuff.h"
+#include "tags.h"
#include "dungeon.h"
#include <sstream>
@@ -41,6 +43,14 @@ int dlua_stringtable(lua_State *ls, const std::vector<std::string> &s)
return dlua_gentable(ls, s, dlua_pushcxxstring);
}
+static int dlua_compiled_chunk_writer(lua_State *ls, const void *p,
+ size_t sz, void *ud)
+{
+ std::ostringstream &out = *static_cast<std::ostringstream*>(ud);
+ out.write((const char *) p, sz);
+ return (0);
+}
+
///////////////////////////////////////////////////////////////////////////
// dlua_chunk
@@ -51,6 +61,31 @@ dlua_chunk::dlua_chunk(const std::string &_context)
clear();
}
+// Initialises a chunk from the function on the top of stack.
+// This function must not be a closure, i.e. must not have any upvalues.
+dlua_chunk::dlua_chunk(lua_State *ls)
+ : file(), chunk(), compiled(), context(), first(-1), last(-1), error()
+{
+ clear();
+
+ lua_stack_cleaner cln(ls);
+ std::ostringstream out;
+ const int err = lua_dump(ls, dlua_compiled_chunk_writer, &out);
+ if (err)
+ {
+ const char *e = lua_tostring(ls, -1);
+ error = e? e : "Unknown error compiling chunk";
+ }
+ compiled = out.str();
+}
+
+dlua_chunk dlua_chunk::precompiled(const std::string &chunk)
+{
+ dlua_chunk dchunk;
+ dchunk.compiled = chunk;
+ return (dchunk);
+}
+
void dlua_chunk::write(FILE *outf) const
{
if (empty())
@@ -132,14 +167,6 @@ int dlua_chunk::check_op(CLua &interp, int err)
return (err);
}
-static int dlua_compiled_chunk_writer(lua_State *ls, const void *p,
- size_t sz, void *ud)
-{
- std::ostringstream &out = *static_cast<std::ostringstream*>(ud);
- out.write((const char *) p, sz);
- return (0);
-}
-
int dlua_chunk::load(CLua &interp)
{
if (!compiled.empty())
@@ -263,6 +290,10 @@ std::string dlua_chunk::get_chunk_prefix(const std::string &sorig) const
#define MAP(ls, n, var) \
map_def *var = *(map_def **) luaL_checkudata(ls, n, MAP_METATABLE)
+#define DEVENT(ls, n, var) \
+ dgn_event *var = *(dgn_event **) luaL_checkudata(ls, n, DEVENT_METATABLE)
+#define MAPMARKER(ls, n, var) \
+ map_marker *var = *(map_marker **) luaL_checkudata(ls, n, MAPMARK_METATABLE)
void dgn_reset_default_depth()
{
@@ -859,6 +890,123 @@ static int dgn_feature_name(lua_State *ls)
dungeon_feature_name(static_cast<dungeon_feature_type>(feat)));
}
+static const char *dgn_event_type_names[] =
+{
+ "none", "turn", "mons_move", "player_move", "leave_level", "enter_level",
+ "player_los", "player_climb"
+};
+
+static dgn_event_type dgn_event_type_by_name(const std::string &name)
+{
+ for (unsigned i = 0; i < ARRAYSIZE(dgn_event_type_names); ++i)
+ if (dgn_event_type_names[i] == name)
+ return static_cast<dgn_event_type>(i? 1 << (i - 1) : 0);
+ return (DET_NONE);
+}
+
+static const char *dgn_event_type_name(unsigned evmask)
+{
+ if (evmask == 0)
+ return (dgn_event_type_names[0]);
+
+ for (unsigned i = 1; i < ARRAYSIZE(dgn_event_type_names); ++i)
+ if (evmask & (1 << (i - 1)))
+ return (dgn_event_type_names[i]);
+
+ return (dgn_event_type_names[0]);
+}
+
+static void dgn_push_event_type(lua_State *ls, int n)
+{
+ if (lua_isstring(ls, n))
+ lua_pushnumber(ls, dgn_event_type_by_name(lua_tostring(ls, n)));
+ else if (lua_isnumber(ls, n))
+ lua_pushstring(ls, dgn_event_type_name(luaL_checkint(ls, n)));
+ else
+ lua_pushnil(ls);
+}
+
+static int dgn_dgn_event(lua_State *ls)
+{
+ const int start = lua_isuserdata(ls, 1)? 2 : 1;
+ int retvals = 0;
+ for (int i = start, nargs = lua_gettop(ls); i <= nargs; ++i)
+ {
+ dgn_push_event_type(ls, i);
+ retvals++;
+ }
+ return (retvals);
+}
+
+static int dgn_register_listener(lua_State *ls)
+{
+ unsigned mask = luaL_checkint(ls, 1);
+ MAPMARKER(ls, 2, mark);
+ map_lua_marker *listener = dynamic_cast<map_lua_marker*>(mark);
+ coord_def pos;
+ // Was a position supplied?
+ if (lua_gettop(ls) == 4)
+ {
+ pos.x = luaL_checkint(ls, 3);
+ pos.y = luaL_checkint(ls, 4);
+ }
+
+ dungeon_events.register_listener(mask, listener, pos);
+ return (0);
+}
+
+static int dgn_remove_listener(lua_State *ls)
+{
+ MAPMARKER(ls, 1, mark);
+ map_lua_marker *listener = dynamic_cast<map_lua_marker*>(mark);
+ coord_def pos;
+ // Was a position supplied?
+ if (lua_gettop(ls) == 3)
+ {
+ pos.x = luaL_checkint(ls, 2);
+ pos.y = luaL_checkint(ls, 3);
+ }
+ dungeon_events.remove_listener(listener, pos);
+ return (0);
+}
+
+static int dgn_remove_marker(lua_State *ls)
+{
+ MAPMARKER(ls, 1, mark);
+ env_remove_marker(mark);
+ return (0);
+}
+
+static int dgn_feature_desc(lua_State *ls)
+{
+ const dungeon_feature_type feat =
+ static_cast<dungeon_feature_type>(luaL_checkint(ls, 1));
+ const description_level_type dtype =
+ lua_isnumber(ls, 2)?
+ static_cast<description_level_type>(luaL_checkint(ls, 2)) :
+ description_type_by_name(lua_tostring(ls, 2));
+ const bool need_stop = lua_isboolean(ls, 3)? lua_toboolean(ls, 3) : false;
+ const std::string s =
+ feature_description(feat, NUM_TRAPS, false, dtype, need_stop);
+ lua_pushstring(ls, s.c_str());
+ return (1);
+}
+
+static int dgn_terrain_changed(lua_State *ls)
+{
+ dungeon_feature_type type = DNGN_UNSEEN;
+ if (lua_isnumber(ls, 3))
+ type = static_cast<dungeon_feature_type>(luaL_checkint(ls, 3));
+ else if (lua_isstring(ls, 3))
+ type = dungeon_feature_by_name(lua_tostring(ls, 3));
+ const bool affect_player =
+ lua_isboolean(ls, 4)? lua_toboolean(ls, 4) : true;
+ dungeon_terrain_changed( coord_def( luaL_checkint(ls, 1),
+ luaL_checkint(ls, 2) ),
+ type, affect_player );
+ return (0);
+}
+
static const struct luaL_reg dgn_lib[] =
{
{ "default_depth", dgn_default_depth },
@@ -883,6 +1031,7 @@ static const struct luaL_reg dgn_lib[] =
{ "kitem", dgn_kitem },
{ "kmons", dgn_kmons },
{ "grid", dgn_grid },
+ { "terrain_changed", dgn_terrain_changed },
{ "points_connected", dgn_points_connected },
{ "any_point_connected", dgn_any_point_connected },
{ "has_exit_from", dgn_has_exit_from },
@@ -892,6 +1041,11 @@ static const struct luaL_reg dgn_lib[] =
{ "load_des_file", dgn_load_des_file },
{ "feature_number", dgn_feature_number },
{ "feature_name", dgn_feature_name },
+ { "dgn_event_type", dgn_dgn_event },
+ { "register_listener", dgn_register_listener },
+ { "remove_listener", dgn_remove_listener },
+ { "remove_marker", dgn_remove_marker },
+ { "feature_desc", dgn_feature_desc },
{ NULL, NULL }
};
@@ -906,16 +1060,207 @@ static const struct luaL_reg crawl_lib[] =
{ NULL, NULL }
};
+static int file_marshall(lua_State *ls)
+{
+ if (lua_gettop(ls) != 2)
+ luaL_error(ls, "Need two arguments: tag header and value");
+ tagHeader &th(*static_cast<tagHeader*>( lua_touserdata(ls, 1) ));
+ if (lua_isnumber(ls, 2))
+ marshallLong(th, luaL_checklong(ls, 2));
+ else if (lua_isstring(ls, 2))
+ marshallString(th, lua_tostring(ls, 2));
+ else if (lua_isfunction(ls, 2))
+ {
+ dlua_chunk chunk(ls);
+ marshallString(th, chunk.compiled_chunk());
+ }
+ return (0);
+}
+
+static int file_unmarshall_number(lua_State *ls)
+{
+ if (lua_gettop(ls) != 1)
+ luaL_error(ls, "Need tag header as one argument");
+ tagHeader &th(*static_cast<tagHeader*>( lua_touserdata(ls, 1) ));
+ lua_pushnumber(ls, unmarshallLong(th));
+ return (1);
+}
+
+static int file_unmarshall_string(lua_State *ls)
+{
+ if (lua_gettop(ls) != 1)
+ luaL_error(ls, "Need tag header as one argument");
+ tagHeader &th(*static_cast<tagHeader*>( lua_touserdata(ls, 1) ));
+ lua_pushstring(ls, unmarshallString(th).c_str());
+ return (1);
+}
+
+static int file_unmarshall_fn(lua_State *ls)
+{
+ if (lua_gettop(ls) != 1)
+ luaL_error(ls, "Need tag header as one argument");
+ tagHeader &th(*static_cast<tagHeader*>( lua_touserdata(ls, 1) ));
+ const std::string s(unmarshallString(th, LUA_CHUNK_MAX_SIZE));
+ dlua_chunk chunk = dlua_chunk::precompiled(s);
+ if (chunk.load(dlua))
+ lua_pushnil(ls);
+ return (1);
+}
+
+enum lua_persist_type
+{
+ LPT_NONE,
+ LPT_NUMBER,
+ LPT_STRING,
+ LPT_FUNCTION
+};
+
+static int file_marshall_meta(lua_State *ls)
+{
+ if (lua_gettop(ls) != 2)
+ luaL_error(ls, "Need two arguments: tag header and value");
+
+ tagHeader &th(*static_cast<tagHeader*>( lua_touserdata(ls, 1) ));
+
+ lua_persist_type ptype = LPT_NONE;
+ if (lua_isnumber(ls, 2))
+ ptype = LPT_NUMBER;
+ else if (lua_isstring(ls, 2))
+ ptype = LPT_STRING;
+ else if (lua_isfunction(ls, 2))
+ ptype = LPT_FUNCTION;
+ else
+ luaL_error(ls, "Can marshall only numbers, strings and functions.");
+ marshallByte(th, ptype);
+ file_marshall(ls);
+ return (0);
+}
+
+static int file_unmarshall_meta(lua_State *ls)
+{
+ tagHeader &th(*static_cast<tagHeader*>( lua_touserdata(ls, 1) ));
+ const lua_persist_type ptype =
+ static_cast<lua_persist_type>(unmarshallByte(th));
+ switch (ptype)
+ {
+ case LPT_NUMBER:
+ return file_unmarshall_number(ls);
+ case LPT_STRING:
+ return file_unmarshall_string(ls);
+ case LPT_FUNCTION:
+ return file_unmarshall_fn(ls);
+ default:
+ luaL_error(ls, "Unexpected type signature.");
+ }
+ // Never get here.
+ return (0);
+}
+
+static const struct luaL_reg file_lib[] =
+{
+ { "marshall", file_marshall },
+ { "marshall_meta", file_marshall_meta },
+ { "unmarshall_meta", file_unmarshall_meta },
+ { "unmarshall_number", file_unmarshall_number },
+ { "unmarshall_string", file_unmarshall_string },
+ { "unmarshall_fn", file_unmarshall_fn },
+ { NULL, NULL }
+};
+
+LUARET1(you_can_hear_pos, boolean,
+ player_can_hear(luaL_checkint(ls,1), luaL_checkint(ls, 2)))
+
+static const struct luaL_reg you_lib[] =
+{
+ { "hear_pos", you_can_hear_pos },
+ { NULL, NULL }
+};
+
+static int dgnevent_type(lua_State *ls)
+{
+ DEVENT(ls, 1, dev);
+ PLUARET(number, dev->type);
+}
+
+static int dgnevent_place(lua_State *ls)
+{
+ DEVENT(ls, 1, dev);
+ lua_pushnumber(ls, dev->place.x);
+ lua_pushnumber(ls, dev->place.y);
+ return (2);
+}
+
+static int dgnevent_ticks(lua_State *ls)
+{
+ DEVENT(ls, 1, dev);
+ PLUARET(number, dev->elapsed_ticks);
+}
+
+static const struct luaL_reg dgnevent_lib[] =
+{
+ { "type", dgnevent_type },
+ { "pos", dgnevent_place },
+ { "ticks", dgnevent_ticks },
+ { NULL, NULL }
+};
+
+static void luaopen_setmeta(lua_State *ls,
+ const char *global,
+ const luaL_reg *lua_lib,
+ const char *meta)
+{
+ luaL_newmetatable(ls, meta);
+ lua_setglobal(ls, global);
+
+ luaL_openlib(ls, global, lua_lib, 0);
+
+ // Do <global>.__index = <global>
+ lua_pushstring(ls, "__index");
+ lua_pushvalue(ls, -2);
+ lua_settable(ls, -3);
+}
+
+static void luaopen_dgnevent(lua_State *ls)
+{
+ luaopen_setmeta(ls, "dgnevent", dgnevent_lib, DEVENT_METATABLE);
+}
+
+static int mapmarker_pos(lua_State *ls)
+{
+ MAPMARKER(ls, 1, mark);
+ lua_pushnumber(ls, mark->pos.x);
+ lua_pushnumber(ls, mark->pos.y);
+ return (2);
+}
+
+static const struct luaL_reg mapmarker_lib[] =
+{
+ { "pos", mapmarker_pos },
+ { NULL, NULL }
+};
+
+static void luaopen_mapmarker(lua_State *ls)
+{
+ luaopen_setmeta(ls, "mapmarker", mapmarker_lib, MAPMARK_METATABLE);
+}
+
void init_dungeon_lua()
{
+ lua_stack_cleaner clean(dlua);
+
luaL_openlib(dlua, "dgn", dgn_lib, 0);
// Add additional function to the Crawl module.
luaL_openlib(dlua, "crawl", crawl_lib, 0);
+ luaL_openlib(dlua, "file", file_lib, 0);
+ luaL_openlib(dlua, "you", you_lib, 0);
+
dlua.execfile("clua/dungeon.lua", true, true);
- if (!dlua.error.empty())
- end(1, false, "Lua error: %s", dlua.error.c_str());
+ dlua.execfile("clua/luamark.lua", true, true);
+
lua_getglobal(dlua, "dgn_run_map");
luaopen_debug(dlua);
luaL_newmetatable(dlua, MAP_METATABLE);
- lua_settop(dlua, 1);
+
+ luaopen_dgnevent(dlua);
+ luaopen_mapmarker(dlua);
}
diff --git a/crawl-ref/source/luadgn.h b/crawl-ref/source/luadgn.h
index d717f38f5f..04e85c2de0 100644
--- a/crawl-ref/source/luadgn.h
+++ b/crawl-ref/source/luadgn.h
@@ -43,6 +43,10 @@ public:
public:
dlua_chunk(const std::string &_context = "dlua_chunk");
+ dlua_chunk(lua_State *ls);
+
+ static dlua_chunk precompiled(const std::string &compiled);
+
void clear();
void add(int line, const std::string &line);
void set_chunk(const std::string &s);
@@ -57,6 +61,8 @@ public:
bool empty() const;
+ const std::string &compiled_chunk() const { return compiled; }
+
void write(FILE *) const;
void read(FILE *);
};
@@ -69,6 +75,13 @@ int dlua_stringtable(lua_State *ls, const std::vector<std::string> &s);
dungeon_feature_type dungeon_feature_by_name(const std::string &name);
const char *dungeon_feature_name(dungeon_feature_type feat);
+template <typename T>
+inline void dlua_push_userdata(lua_State *ls, T udata, const char *meta)
+{
+ T *de = clua_new_userdata<T>(ls, meta);
+ *de = udata;
+}
+
//////////////////////////////////////////////////////////////////////////
#endif
diff --git a/crawl-ref/source/mapdef.cc b/crawl-ref/source/mapdef.cc
index a5854ae236..6e5e5c7c8d 100644
--- a/crawl-ref/source/mapdef.cc
+++ b/crawl-ref/source/mapdef.cc
@@ -302,9 +302,9 @@ map_transformer::~map_transformer()
// map_lines
map_lines::map_lines()
- : transforms(), markers(), lines(), map_width(0), solid_north(false),
- solid_east(false), solid_south(false), solid_west(false),
- solid_checked(false)
+ : transforms(), markers(), lines(), map_width(0),
+ solid_north(false), solid_east(false), solid_south(false),
+ solid_west(false), solid_checked(false)
{
}
@@ -1023,9 +1023,9 @@ dlua_set_map::~dlua_set_map()
map_def::map_def()
: name(), tags(), place(), depths(), orient(), chance(),
- map(), mons(), items(), keyspecs(), prelude("dlprelude"), main("dlmain"),
- validate("dlvalidate"), veto("dlveto"), index_only(false),
- cache_offset(0L)
+ map(), mons(), items(), keyspecs(), prelude("dlprelude"),
+ main("dlmain"), validate("dlvalidate"), veto("dlveto"),
+ index_only(false), cache_offset(0L)
{
init();
}
@@ -1330,6 +1330,7 @@ std::string map_def::validate_map_def()
break;
}
+ dlua_set_map dl(this);
return (map.apply_transforms());
}
@@ -1574,6 +1575,7 @@ void map_def::normalise()
std::string map_def::resolve()
{
+ dlua_set_map dl(this);
return map.apply_transforms();
}
@@ -2306,27 +2308,27 @@ std::string shuffle_spec::describe() const
std::string map_marker_spec::apply_transform(map_lines &map)
{
std::vector<coord_def> positions = map.find_glyph(key);
- if (positions.size() == 1)
+ if (positions.empty())
+ return make_stringf("cant find key '%c' for marker", key);
+
+ for (int i = 0, size = positions.size(); i < size; ++i)
{
try
{
- map_marker *mark = map_marker::parse_marker(marker);
+ map_marker *mark =
+ map_marker::parse_marker(marker);
if (!mark)
return make_stringf("Unable to parse marker from %s",
marker.c_str());
- mark->pos = positions[0];
+ mark->pos = positions[i];
map.add_marker(mark);
- return ("");
}
catch (const std::string &err)
{
return (err);
}
}
- else if (positions.empty())
- return make_stringf("cant find key '%c' for marker", key);
- else
- return make_stringf("too many matches for key '%c' for marker", key);
+ return ("");
}
map_transformer::transform_type map_marker_spec::type() const
@@ -2436,15 +2438,13 @@ feature_spec_list keyed_mapspec::parse_feature(const std::string &str)
list.push_back( parse_shop(s, weight) );
return (list);
}
-
- std::vector<dungeon_feature_type> feats =
- features_by_desc( glob_pattern(s, true) );
- if (!feats.empty())
- list.push_back( feature_spec(feats[0], weight) );
- if (feats.empty())
+ const dungeon_feature_type ftype = dungeon_feature_by_name(s);
+ if (ftype == DNGN_UNSEEN)
err = make_stringf("no features matching \"%s\"",
str.c_str());
+ else
+ list.push_back( feature_spec( ftype, weight ) );
return (list);
}
diff --git a/crawl-ref/source/mapdef.h b/crawl-ref/source/mapdef.h
index 9ff325bcca..d1a0316aad 100644
--- a/crawl-ref/source/mapdef.h
+++ b/crawl-ref/source/mapdef.h
@@ -167,6 +167,7 @@ struct map_marker_spec : public map_transformer
map_transformer *clone() const;
};
+class map_def;
class map_lines
{
public:
@@ -260,6 +261,7 @@ private:
friend class subst_spec;
friend class nsubst_spec;
friend class shuffle_spec;
+ friend class map_marker_spec;
private:
std::vector<map_transformer *> transforms;
diff --git a/crawl-ref/source/mapmark.cc b/crawl-ref/source/mapmark.cc
index 696f042b4c..4867225072 100644
--- a/crawl-ref/source/mapmark.cc
+++ b/crawl-ref/source/mapmark.cc
@@ -9,6 +9,7 @@
#include "AppHdr.h"
#include "mapmark.h"
+#include "clua.h"
#include "direct.h"
#include "libutil.h"
#include "luadgn.h"
@@ -22,13 +23,13 @@
map_marker::marker_reader map_marker::readers[NUM_MAP_MARKER_TYPES] =
{
&map_feature_marker::read,
- &map_timed_feature_marker::read,
+ &map_lua_marker::read,
};
map_marker::marker_parser map_marker::parsers[NUM_MAP_MARKER_TYPES] =
{
&map_feature_marker::parse,
- &map_timed_feature_marker::parse,
+ &map_lua_marker::parse,
};
map_marker::map_marker(map_marker_type t, const coord_def &p)
@@ -64,13 +65,14 @@ map_marker *map_marker::read_marker(tagHeader &inf)
return readers[type]? (*readers[type])(inf, type) : NULL;
}
-map_marker *map_marker::parse_marker(const std::string &s) throw (std::string)
+map_marker *map_marker::parse_marker(
+ const std::string &s, const std::string &ctx) throw (std::string)
{
for (int i = 0; i < NUM_MAP_MARKER_TYPES; ++i)
{
if (parsers[i])
{
- if (map_marker *m = parsers[i](s))
+ if (map_marker *m = parsers[i](s, ctx))
return (m);
}
}
@@ -112,7 +114,8 @@ map_marker *map_feature_marker::read(tagHeader &inf, map_marker_type)
return (mapf);
}
-map_marker *map_feature_marker::parse(const std::string &s) throw (std::string)
+map_marker *map_feature_marker::parse(
+ const std::string &s, const std::string &) throw (std::string)
{
if (s.find("feat:") != 0)
return (NULL);
@@ -132,190 +135,195 @@ std::string map_feature_marker::describe() const
}
////////////////////////////////////////////////////////////////////////////
-// map_feature_marker
+// map_lua_marker
-map_timed_feature_marker::map_timed_feature_marker(
- const coord_def &_pos,
- int duration_turns,
- dungeon_feature_type _feat)
- : map_feature_marker(_pos, _feat), duration_ticks(duration_turns * 10),
- warn_threshold(-1000)
+map_lua_marker::map_lua_marker()
+ : map_marker(MAT_LUA_MARKER, coord_def()), initialised(false)
{
- type = MAT_TIMED_FEATURE;
}
-void map_timed_feature_marker::activate()
+map_lua_marker::map_lua_marker(const std::string &s, const std::string &)
+ : map_marker(MAT_LUA_MARKER, coord_def()), initialised(false)
{
- dungeon_events.register_listener(DET_TURN_ELAPSED, this);
+ lua_stack_cleaner clean(dlua);
+ if (dlua.loadstring(("return " + s).c_str(), "lua_marker"))
+ mprf(MSGCH_WARN, "lua_marker load error: %s", dlua.error.c_str());
+ if (!dlua.callfn("dgn_run_map", 1, 1))
+ mprf(MSGCH_WARN, "lua_marker exec error: %s", dlua.error.c_str());
+ check_register_table();
+}
- if (player_can_hear(pos))
+map_lua_marker::~map_lua_marker()
+{
+ // Remove the Lua marker table from the registry.
+ if (initialised)
{
- const dungeon_feature_type ft = grd(pos);
- switch (ft)
- {
- case DNGN_ENTER_BAZAAR:
- mprf(MSGCH_SOUND, "You %shear coins being counted.",
- duration_ticks < 1000? "can faintly " : "");
- break;
- case DNGN_ENTER_LABYRINTH:
- mprf(MSGCH_SOUND, "You hear a faint echoing snort.");
- break;
- default:
- break;
- }
+ lua_pushlightuserdata(dlua, this);
+ lua_pushnil(dlua);
+ lua_settable(dlua, LUA_REGISTRYINDEX);
}
}
-void map_timed_feature_marker::write(tagHeader &th) const
+void map_lua_marker::check_register_table()
{
- map_feature_marker::write(th);
- marshallShort(th, duration_ticks);
- marshallShort(th, warn_threshold);
-}
+ if (!lua_istable(dlua, -1))
+ {
+ mprf(MSGCH_WARN, "lua_marker: Expected table, didn't get it.");
+ initialised = false;
+ return;
+ }
-void map_timed_feature_marker::read(tagHeader &th)
-{
- map_feature_marker::read(th);
- duration_ticks = unmarshallShort(th);
- warn_threshold = unmarshallShort(th);
-}
+ // Got a table. Save it in the registry.
-std::string map_timed_feature_marker::describe() const
-{
- return make_stringf("timer: %d ticks (%s)",
- duration_ticks, dungeon_feature_name(feat));
+ // Key is this.
+ lua_pushlightuserdata(dlua, this);
+ // Move key before value.
+ lua_insert(dlua, -2);
+ lua_settable(dlua, LUA_REGISTRYINDEX);
+
+ initialised = true;
}
-const char *map_timed_feature_marker::bell_urgency(int ticks) const
+bool map_lua_marker::get_table() const
{
- if (ticks > 5000)
- return "stately ";
- else if (ticks > 4000)
- return "";
- else if (ticks > 2500)
- return "brisk ";
- else if (ticks > 1500)
- return "urgent ";
- else if (ticks > 0)
- return "frantic ";
- else
- return "last, dying notes of the ";
+ // First save the unmarshall Lua function.
+ lua_pushlightuserdata(dlua, const_cast<map_lua_marker*>(this));
+ lua_gettable(dlua, LUA_REGISTRYINDEX);
+ return (lua_istable(dlua, -1));
}
-const char *map_timed_feature_marker::noise_maker(int ticks) const
+void map_lua_marker::write(tagHeader &th) const
{
- switch (grd(pos))
+ map_marker::write(th);
+
+ lua_stack_cleaner clean(dlua);
+ bool init = initialised;
+ if (!get_table())
{
- case DNGN_ENTER_LABYRINTH:
- return (ticks > 0? "tolling of a bell" : "bell");
- case DNGN_ENTER_BAZAAR:
- return (ticks > 0? "ticking of an ancient clock" : "clock");
- default:
- return (ticks > 0?
- "trickling of a stream filled with giant, killer bugs."
- : "stream");
+ mprf(MSGCH_WARN, "Couldn't find table.");
+ init = false;
}
+
+ marshallByte(th, init);
+ if (!init)
+ return;
+
+ // Call dlua_marker_function(table, 'read')
+ lua_pushstring(dlua, "read");
+ if (!dlua.callfn("dlua_marker_function", 2, 1))
+ end(1, false, "lua_marker: write error: %s", dlua.error.c_str());
+
+ // Right, what's on top should be a function. Save it.
+ dlua_chunk reader(dlua);
+ if (!reader.error.empty())
+ end(1, false, "lua_marker: couldn't save read function: %s",
+ reader.error.c_str());
+
+ marshallString(th, reader.compiled_chunk());
+
+ // Okay, saved the reader. Now ask the writer to do its thing.
+
+ // Call: dlua_marker_method(table, fname, marker)
+ get_table();
+ lua_pushstring(dlua, "write");
+ lua_pushlightuserdata(dlua, const_cast<map_lua_marker*>(this));
+ lua_pushlightuserdata(dlua, &th);
+
+ if (!dlua.callfn("dlua_marker_method", 4))
+ end(1, false, "lua_marker::write error: %s", dlua.error.c_str());
}
-void map_timed_feature_marker::notify_dgn_event(const dgn_event &e)
+void map_lua_marker::read(tagHeader &th)
{
- if (!e.elapsed_ticks || e.type != DET_TURN_ELAPSED)
+ map_marker::read(th);
+
+ if (!(initialised = unmarshallByte(th)))
return;
- if (warn_threshold == -1000)
- warn_threshold = std::max(50, duration_ticks - 500);
-
- duration_ticks -= e.elapsed_ticks;
+ lua_stack_cleaner cln(dlua);
+ // Read the Lua chunk we saved.
+ const std::string compiled = unmarshallString(th, LUA_CHUNK_MAX_SIZE);
- if (duration_ticks < warn_threshold || duration_ticks <= 0)
- {
- if (duration_ticks > 900)
- warn_threshold = duration_ticks - 500;
- else
- warn_threshold = duration_ticks - 250;
-
- if (duration_ticks > 0 && player_can_hear(pos))
- mprf(MSGCH_SOUND, "You hear the %s%s.",
- bell_urgency(duration_ticks),
- noise_maker(duration_ticks));
-
- if (duration_ticks <= 0)
- timeout(true);
- }
+ dlua_chunk chunk = dlua_chunk::precompiled(compiled);
+ if (chunk.load(dlua))
+ end(1, false, "lua_marker::read error: %s", chunk.error.c_str());
+ dlua_push_userdata(dlua, this, MAPMARK_METATABLE);
+ lua_pushlightuserdata(dlua, &th);
+ if (!dlua.callfn("dlua_marker_read", 3, 1))
+ end(1, false, "lua_marker::read error: %s", dlua.error.c_str());
+
+ // Right, what's on top had better be a table.
+ check_register_table();
}
-void map_timed_feature_marker::timeout(bool verbose)
+map_marker *map_lua_marker::read(tagHeader &th, map_marker_type)
{
- if (verbose)
- {
- if (see_grid(pos))
- mprf("%s disappears!",
- feature_description(grd(pos), NUM_TRAPS, false,
- DESC_CAP_THE, false).c_str());
- else
- mpr("The walls and floor vibrate strangely for a moment.");
- }
+ map_marker *marker = new map_lua_marker;
+ marker->read(th);
+ return (marker);
+}
- // And it's gone forever.
- grd(pos) = feat;
+void map_lua_marker::push_fn_args(const char *fn) const
+{
+ get_table();
+ lua_pushstring(dlua, fn);
+ dlua_push_userdata(dlua, this, MAPMARK_METATABLE);
+}
- dungeon_terrain_changed(pos);
+bool map_lua_marker::callfn(const char *fn, bool warn_err) const
+{
+ const int top = lua_gettop(dlua);
+ push_fn_args(fn);
+ const bool res =
+ dlua.callfn("dlua_marker_method", lua_gettop(dlua) - top, 1);
+ if (!res && warn_err)
+ mprf(MSGCH_WARN, "mlua error: %s", dlua.error.c_str());
+ return (res);
+}
- // Stop listening for further ticks.
- dungeon_events.remove_listener(this);
+void map_lua_marker::activate()
+{
+ lua_stack_cleaner clean(dlua);
+ callfn("activate", true);
+}
- // Kill this marker.
- env_remove_marker(this);
+void map_lua_marker::notify_dgn_event(const dgn_event &e)
+{
+ lua_stack_cleaner clean(dlua);
+ push_fn_args("event");
+ clua_push_dgn_event(dlua, &e);
+ if (!dlua.callfn("dlua_marker_method", 4, 0))
+ mprf(MSGCH_WARN, "notify_dgn_event: Lua error: %s",
+ dlua.error.c_str());
}
-map_marker *map_timed_feature_marker::read(tagHeader &th, map_marker_type)
+std::string map_lua_marker::describe() const
{
- map_marker *mt = new map_timed_feature_marker();
- mt->read(th);
- return (mt);
+ lua_stack_cleaner cln(dlua);
+ if (!callfn("describe"))
+ return make_stringf("error: %s", dlua.error.c_str());
+
+ std::string desc;
+ if (lua_isstring(dlua, -1))
+ desc = lua_tostring(dlua, -1);
+ return desc;
}
-map_marker *map_timed_feature_marker::parse(const std::string &s)
- throw (std::string)
+map_marker *map_lua_marker::parse(
+ const std::string &s, const std::string &ctx) throw (std::string)
{
- if (s.find("timer:") != 0)
+ if (s.find("lua:") != 0)
return (NULL);
std::string raw = s;
- strip_tag(raw, "timer:", true);
-
- int navg = strip_number_tag(raw, "avg:");
- if (navg == TAG_UNFOUND)
- navg = 1;
-
- if (navg < 1 || navg > 20)
- throw make_stringf("Bad marker spec '%s' (avg out of bounds)",
- s.c_str());
-
- dungeon_feature_type feat = DNGN_FLOOR;
- std::string fname = strip_tag_prefix(raw, "feat:");
- if (!fname.empty()
- && (feat = dungeon_feature_by_name(fname)) == DNGN_UNSEEN)
+ strip_tag(raw, "lua:");
+ map_lua_marker *mark = new map_lua_marker(raw, ctx);
+ if (!mark->initialised)
{
- throw make_stringf("Bad feature name (%s) in marker spec '%s'",
- fname.c_str(), s.c_str());
+ delete mark;
+ throw make_stringf("Unable to initialise Lua marker from '%s'",
+ raw.c_str());
}
-
- std::vector<std::string> limits = split_string("-", raw);
- const int nlims = limits.size();
- if (nlims < 1 || nlims > 2)
- throw make_stringf("Malformed turn range (%s) in marker '%s'",
- raw.c_str(), s.c_str());
-
- const int low = atoi(limits[0].c_str());
- const int high = nlims == 1? low : atoi(limits[1].c_str());
-
- if (low == 0 || high < low)
- throw make_stringf("Malformed turn range (%s) in marker '%s'",
- raw.c_str(), s.c_str());
-
- const int duration = low == high? low : random_range(low, high, navg);
- return new map_timed_feature_marker(coord_def(0, 0),
- duration, feat);
+ return (mark);
}
//////////////////////////////////////////////////////////////////////////
diff --git a/crawl-ref/source/mapmark.h b/crawl-ref/source/mapmark.h
index 31d0b74e29..eacddcf266 100644
--- a/crawl-ref/source/mapmark.h
+++ b/crawl-ref/source/mapmark.h
@@ -3,6 +3,8 @@
#include "dungeon.h"
#include "dgnevent.h"
+#include "clua.h"
+#include "luadgn.h"
//////////////////////////////////////////////////////////////////////////
// Map markers
@@ -11,7 +13,7 @@
enum map_marker_type
{
MAT_FEATURE, // Stock marker.
- MAT_TIMED_FEATURE,
+ MAT_LUA_MARKER,
NUM_MAP_MARKER_TYPES,
MAT_ANY
};
@@ -30,7 +32,8 @@ public:
virtual std::string describe() const = 0;
static map_marker *read_marker(tagHeader&);
- static map_marker *parse_marker(const std::string &text)
+ static map_marker *parse_marker(const std::string &text,
+ const std::string &ctx = "")
throw (std::string);
public:
@@ -40,7 +43,8 @@ protected:
map_marker_type type;
typedef map_marker *(*marker_reader)(tagHeader &, map_marker_type);
- typedef map_marker *(*marker_parser)(const std::string &);
+ typedef map_marker *(*marker_parser)(const std::string &,
+ const std::string &);
static marker_reader readers[NUM_MAP_MARKER_TYPES];
static marker_parser parsers[NUM_MAP_MARKER_TYPES];
};
@@ -55,39 +59,39 @@ public:
void read(tagHeader &);
std::string describe() const;
static map_marker *read(tagHeader &, map_marker_type);
- static map_marker *parse(const std::string &s) throw (std::string);
+ static map_marker *parse(const std::string &s, const std::string &)
+ throw (std::string);
public:
dungeon_feature_type feat;
};
-class map_timed_feature_marker : public map_feature_marker, dgn_event_listener
+// A marker powered by Lua.
+class map_lua_marker : public map_marker, public dgn_event_listener
{
public:
- map_timed_feature_marker(const coord_def &pos = coord_def(),
- int duration_turns = 0,
- dungeon_feature_type feat = DNGN_FLOOR);
+ map_lua_marker();
+ map_lua_marker(const std::string &s, const std::string &ctx);
+ ~map_lua_marker();
+
void activate();
+
void write(tagHeader &) const;
void read(tagHeader &);
std::string describe() const;
-
+
void notify_dgn_event(const dgn_event &e);
-
- // Expires this marker *now* and cleans it up.
- void timeout(bool verbose);
-
+
static map_marker *read(tagHeader &, map_marker_type);
- static map_marker *parse(const std::string &s) throw (std::string);
-
+ static map_marker *parse(const std::string &s, const std::string &)
+ throw (std::string);
private:
- const char *bell_urgency(int ticks) const;
- const char *noise_maker(int ticks) const;
-
-public:
- // Ticks are a tenth of a turn.
- int duration_ticks;
- int warn_threshold;
+ bool initialised;
+private:
+ void check_register_table();
+ bool get_table() const;
+ void push_fn_args(const char *fn) const;
+ bool callfn(const char *fn, bool warn_err = false) const;
};
void env_activate_markers();
diff --git a/crawl-ref/source/message.cc b/crawl-ref/source/message.cc
index 9789881fbb..bef5843dca 100644
--- a/crawl-ref/source/message.cc
+++ b/crawl-ref/source/message.cc
@@ -372,6 +372,12 @@ void mprf( const char *format, ... )
void mpr(const char *inf, int channel, int param)
{
+ if (!crawl_state.io_inited)
+ {
+ if (channel == MSGCH_WARN)
+ fprintf(stderr, "%s\n", inf);
+ return;
+ }
char mbuf[400];
size_t i = 0;
const int stepsize = get_number_of_cols() - 1;
diff --git a/crawl-ref/source/misc.cc b/crawl-ref/source/misc.cc
index 94bf165bf4..f94624ff39 100644
--- a/crawl-ref/source/misc.cc
+++ b/crawl-ref/source/misc.cc
@@ -535,10 +535,20 @@ static void dgn_check_terrain_monsters(const coord_def &pos)
}
}
-void dungeon_terrain_changed(const coord_def &pos)
+void dungeon_terrain_changed(const coord_def &pos,
+ dungeon_feature_type nfeat,
+ bool affect_player)
{
+ if (nfeat != DNGN_UNSEEN)
+ {
+ unnotice_feature(level_pos(level_id::current(), pos));
+ grd(pos) = nfeat;
+ if (is_notable_terrain(nfeat) && see_grid(pos))
+ seen_notable_thing(nfeat, pos.x, pos.y);
+ }
+
dgn_check_terrain_items(pos);
- if (pos == you.pos())
+ if (affect_player && pos == you.pos())
{
if (!grid_is_solid(grd(pos)))
{
@@ -776,6 +786,12 @@ static void climb_message(dungeon_feature_type stair, bool going_up)
mpr(going_up? "You climb upwards." : "You climb downwards.");
}
+static void leaving_level_now()
+{
+ dungeon_events.fire_position_event(DET_PLAYER_CLIMBS, you.pos());
+ dungeon_events.fire_event(DET_LEAVING_LEVEL);
+}
+
void up_stairs(void)
{
dungeon_feature_type stair_find = grd[you.x_pos][you.y_pos];
@@ -828,6 +844,7 @@ void up_stairs(void)
}
// Checks are done, the character is committed to moving between levels.
+ leaving_level_now();
exit_stair_message(stair_find, true);
int old_level = you.your_level;
@@ -1028,6 +1045,9 @@ void down_stairs( int old_level, dungeon_feature_type force_stair )
}
// All checks are done, the player is on the move now.
+
+ // Fire level-leaving trigger.
+ leaving_level_now();
exit_stair_message(stair_find, false);
#ifdef DGL_MILESTONES
@@ -1132,16 +1152,8 @@ void down_stairs( int old_level, dungeon_feature_type force_stair )
}
}
- if (stair_find == DNGN_ENTER_LABYRINTH || stair_find == DNGN_ENTER_BAZAAR)
- {
- // no longer a feature
- if (stair_find == DNGN_ENTER_LABYRINTH)
- unnotice_labyrinth_portal();
- grd[you.x_pos][you.y_pos] = DNGN_FLOOR;
- // remove any markers that were going to expire this labyrinth.
- if (map_marker *marker = env_find_marker(you.pos(), MAT_TIMED_FEATURE))
- dynamic_cast<map_timed_feature_marker*>(marker)->timeout(false);
- }
+ if (stair_find == DNGN_ENTER_LABYRINTH)
+ dungeon_terrain_changed(you.pos(), DNGN_FLOOR);
if (stair_find == DNGN_ENTER_LABYRINTH)
{
diff --git a/crawl-ref/source/misc.h b/crawl-ref/source/misc.h
index 37477745fd..7bbfd4a602 100644
--- a/crawl-ref/source/misc.h
+++ b/crawl-ref/source/misc.h
@@ -171,7 +171,9 @@ void setup_environment_effects();
void run_environment_effects();
// Terrain changed under 'pos', perform necessary effects.
-void dungeon_terrain_changed(const coord_def &pos);
+void dungeon_terrain_changed(const coord_def &pos,
+ dungeon_feature_type feat = DNGN_UNSEEN,
+ bool affect_player = true);
//////////////////////////////////////////////////////////////////////
// Places and names
diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc
index 1dc3d462fd..15194b25b2 100644
--- a/crawl-ref/source/mon-util.cc
+++ b/crawl-ref/source/mon-util.cc
@@ -33,6 +33,7 @@
#include "beam.h"
#include "debug.h"
#include "delay.h"
+#include "dgnevent.h"
#include "itemname.h"
#include "itemprop.h"
#include "items.h"
@@ -4225,6 +4226,17 @@ void monsters::check_redraw(const coord_def &old) const
}
}
+void monsters::apply_location_effects()
+{
+ dungeon_events.fire_position_event(DET_MONSTER_MOVED, pos());
+
+ // monsters stepping on traps:
+ mons_trap(this);
+
+ if (alive())
+ mons_check_pool(this);
+}
+
/////////////////////////////////////////////////////////////////////////
// mon_enchant
diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc
index f7b3f495fc..13ef4ef37f 100644
--- a/crawl-ref/source/monstuff.cc
+++ b/crawl-ref/source/monstuff.cc
@@ -1189,6 +1189,7 @@ bool monster_blink(monsters *monster)
mgrd[monster->x][monster->y] = NON_MONSTER;
+ const coord_def oldplace = monster->pos();
monster->x = nx;
monster->y = ny;
@@ -1197,6 +1198,9 @@ bool monster_blink(monsters *monster)
if (player_monster_visible(monster) && mons_near(monster))
seen_monster(monster);
+ monster->check_redraw(oldplace);
+ monster->apply_location_effects();
+
return (true);
} // end monster_blink()
@@ -4131,9 +4135,8 @@ static bool monster_swaps_places( monsters *mon, int mx, int my )
immobile_monster[m2i] = true;
mon->check_redraw(coord_def(cx, cy));
-
- mons_trap(mon);
- mons_trap(m2);
+ mon->apply_location_effects();
+ m2->apply_location_effects();
return (false);
}
@@ -4186,12 +4189,7 @@ static void do_move_monster(monsters *monster, int xi, int yi)
mgrd[monster->x][monster->y] = monster_index(monster);
monster->check_redraw(monster->pos() - coord_def(xi, yi));
-
- // monsters stepping on traps:
- mons_trap(monster);
-
- if (monster->alive())
- mons_check_pool(monster);
+ monster->apply_location_effects();
}
void mons_check_pool(monsters *mons, killer_type killer, int killnum)
diff --git a/crawl-ref/source/mstuff2.cc b/crawl-ref/source/mstuff2.cc
index 5612953152..2a48a3fc35 100644
--- a/crawl-ref/source/mstuff2.cc
+++ b/crawl-ref/source/mstuff2.cc
@@ -835,8 +835,9 @@ void monster_teleport(struct monsters *monster, bool instan, bool silent)
if (!silent)
simple_monster_message(monster, " disappears!");
+ const coord_def oldplace = monster->pos();
// pick the monster up
- mgrd[monster->x][monster->y] = NON_MONSTER;
+ mgrd(oldplace) = NON_MONSTER;
int newx, newy;
while(true)
@@ -880,6 +881,9 @@ void monster_teleport(struct monsters *monster, bool instan, bool silent)
if (player_monster_visible(monster) && now_visible)
seen_monster(monster);
+ monster->check_redraw(oldplace);
+ monster->apply_location_effects();
+
// Teleporting mimics change form - if they reappear out of LOS, they are
// no longer known.
if (mons_is_mimic(monster->type))
diff --git a/crawl-ref/source/overmap.cc b/crawl-ref/source/overmap.cc
index 1fa8d5ddee..078e3f3415 100644
--- a/crawl-ref/source/overmap.cc
+++ b/crawl-ref/source/overmap.cc
@@ -26,6 +26,7 @@
#include "externs.h"
#include "branch.h"
+#include "dgnevent.h"
#include "direct.h"
#include "files.h"
#include "menu.h"
@@ -46,11 +47,16 @@ altar_map_type altars_present;
portal_map_type portals_present;
static void seen_altar( god_type god, const coord_def& pos );
-static void seen_staircase(unsigned char which_staircase,const coord_def& pos);
-static void seen_other_thing(unsigned char which_thing, const coord_def& pos);
+static void seen_staircase(dungeon_feature_type which_staircase,
+ const coord_def& pos);
+static void seen_other_thing(dungeon_feature_type which_thing,
+ const coord_def& pos);
void seen_notable_thing( dungeon_feature_type which_thing, int x, int y )
{
+ // Tell the world first.
+ dungeon_events.fire_position_event(DET_PLAYER_IN_LOS, coord_def(x, y));
+
// Don't record in temporary terrain
if (you.level_type != LEVEL_DUNGEON)
return;
@@ -348,16 +354,55 @@ std::string overview_description_string()
return disp;
}
-void unnotice_labyrinth_portal()
+template <typename Z, typename Key>
+inline static bool find_erase(Z &map, const Key &k)
{
- level_pos curpos(level_id::current());
- // XXX Is there really no better way to do this?
- curpos.pos.x = you.x_pos;
- curpos.pos.y = you.y_pos;
- if ( portals_present.find(curpos) != portals_present.end() )
- portals_present.erase(curpos);
- else
- mprf(MSGCH_DIAGNOSTICS, "Oops - tried to unnotice bad portal.");
+ if (map.find(k) != map.end())
+ {
+ map.erase(k);
+ return (true);
+ }
+ return (false);
+}
+
+static bool unnotice_portal(const level_pos &pos)
+{
+ return find_erase(portals_present, pos);
+}
+
+static bool unnotice_altar(const level_pos &pos)
+{
+ return find_erase(altars_present, pos);
+}
+
+static bool unnotice_shop(const level_pos &pos)
+{
+ return find_erase(shops_present, pos);
+}
+
+static bool unnotice_stair(const level_pos &pos)
+{
+ const dungeon_feature_type feat = grd(pos.pos);
+ if (grid_is_branch_stairs(feat))
+ {
+ for (int i = 0; i < NUM_BRANCHES; ++i)
+ {
+ if (branches[i].entry_stairs == feat)
+ {
+ const branch_type br = static_cast<branch_type>(i);
+ return (find_erase(stair_level, br));
+ }
+ }
+ }
+ return (false);
+}
+
+bool unnotice_feature(const level_pos &pos)
+{
+ return (unnotice_portal(pos)
+ || unnotice_altar(pos)
+ || unnotice_shop(pos)
+ || unnotice_stair(pos));
}
void display_overmap()
@@ -369,7 +414,8 @@ void display_overmap()
redraw_screen();
}
-void seen_staircase( unsigned char which_staircase, const coord_def& pos )
+void seen_staircase( dungeon_feature_type which_staircase,
+ const coord_def& pos )
{
// which_staircase holds the grid value of the stair, must be converted
// Only handles stairs, not gates or arches
@@ -427,7 +473,7 @@ portal_type feature_to_portal( unsigned char feat )
}
// if player has seen any other thing; record it
-void seen_other_thing( unsigned char which_thing, const coord_def& pos )
+void seen_other_thing( dungeon_feature_type which_thing, const coord_def& pos )
{
if ( you.level_type != LEVEL_DUNGEON ) // can't record in abyss or pan.
return;
diff --git a/crawl-ref/source/overmap.h b/crawl-ref/source/overmap.h
index 214073cecf..f9f16a71ad 100644
--- a/crawl-ref/source/overmap.h
+++ b/crawl-ref/source/overmap.h
@@ -18,8 +18,7 @@
void seen_notable_thing( dungeon_feature_type which_thing, int x, int y );
bool overmap_knows_portal(dungeon_feature_type portal);
void display_overmap();
-void unnotice_labyrinth_portal();
-void unnotice_altar();
+bool unnotice_feature(const level_pos &pos);
std::string overview_description_string();
#endif
diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc
index c3458c2eb7..1c6d08afe3 100644
--- a/crawl-ref/source/player.cc
+++ b/crawl-ref/source/player.cc
@@ -34,6 +34,7 @@
#include "clua.h"
#include "delay.h"
+#include "dgnevent.h"
#include "effects.h"
#include "fight.h"
#include "food.h"
@@ -5523,14 +5524,16 @@ bool player::is_icy() const
void player::moveto(int x, int y)
{
- x_pos = x;
- y_pos = y;
- crawl_view.set_player_at(coord_def(x, y));
+ moveto(coord_def(x, y));
}
void player::moveto(const coord_def &c)
{
+ const bool real_move = c != pos();
x_pos = c.x;
y_pos = c.y;
crawl_view.set_player_at(c);
+
+ if (real_move)
+ dungeon_events.fire_position_event(DET_PLAYER_MOVED, c);
}
diff --git a/crawl-ref/source/tags.cc b/crawl-ref/source/tags.cc
index 5748975294..cc0e1648d9 100644
--- a/crawl-ref/source/tags.cc
+++ b/crawl-ref/source/tags.cc
@@ -374,7 +374,7 @@ void marshallString(struct tagHeader &th, const std::string &data, int maxSize)
}
// string -- unmarshall length & string data
-void unmarshallCString(struct tagHeader &th, char *data, int maxSize)
+int unmarshallCString(struct tagHeader &th, char *data, int maxSize)
{
// get length
short len = unmarshallShort(th);
@@ -388,6 +388,8 @@ void unmarshallCString(struct tagHeader &th, char *data, int maxSize)
data[copylen] = 0;
th.offset += len;
+
+ return (copylen);
}
std::string unmarshallString(tagHeader &th, int maxSize)
@@ -398,10 +400,9 @@ std::string unmarshallString(tagHeader &th, int maxSize)
if (!buffer)
return ("");
*buffer = 0;
- unmarshallCString(th, buffer, maxSize);
- const std::string res = buffer;
+ const int slen = unmarshallCString(th, buffer, maxSize);
+ const std::string res(buffer, slen);
delete [] buffer;
-
return (res);
}
diff --git a/crawl-ref/source/tags.h b/crawl-ref/source/tags.h
index ebc61c6d4d..f07ada57ab 100644
--- a/crawl-ref/source/tags.h
+++ b/crawl-ref/source/tags.h
@@ -52,7 +52,7 @@ short unmarshallShort(struct tagHeader &th);
long unmarshallLong(struct tagHeader &th);
float unmarshallFloat(struct tagHeader &th);
bool unmarshallBoolean(struct tagHeader &th);
-void unmarshallCString(struct tagHeader &th, char *data, int maxSize);
+int unmarshallCString(struct tagHeader &th, char *data, int maxSize);
std::string unmarshallString(tagHeader &th, int maxSize = 1000);
void unmarshallCoord(tagHeader &th, coord_def &c);
diff --git a/crawl-ref/source/util/levcomp.lpp b/crawl-ref/source/util/levcomp.lpp
index 6ec10124ad..e234f90b2d 100644
--- a/crawl-ref/source/util/levcomp.lpp
+++ b/crawl-ref/source/util/levcomp.lpp
@@ -39,14 +39,12 @@ static void clean()
alloced = false;
}
-static void settext(bool trim_right = false, int strip_trailing = 0)
+static char *copy_text(bool trim_right, int strip_trailing)
{
- clean();
char *newstring = NULL;
if ((yylval.text = newstring = strdup(yytext)))
{
alloced = true;
-
char *s = NULL;
if (trim_right)
{
@@ -62,6 +60,14 @@ static void settext(bool trim_right = false, int strip_trailing = 0)
*s-- = 0;
}
}
+ return (newstring);
+}
+
+static void settext(bool trim_right = false, int strip_trailing = 0)
+{
+ clean();
+ char *newstring = copy_text(trim_right, strip_trailing);
+ yylval.text = newstring;
}
%}
@@ -73,6 +79,7 @@ static void settext(bool trim_right = false, int strip_trailing = 0)
%s MNAME
%s KEYWORDS
%x ITEM_LIST
+%x TOEOL
%option yylineno
%option never-interactive
@@ -152,6 +159,17 @@ NSPACE [^\ \t\r\n]
<ARGUMENT>\r?\n { BEGIN(INITIAL); }
+<TOEOL>.*\\[ \t]*$ {
+ settext(true, 1);
+ return STRING;
+ }
+
+<TOEOL>.+$ {
+ BEGIN(INITIAL);
+ settext();
+ return STRING;
+ }
+
^[ \t]*#.* ;
^\s*MAP[ \t]*$ { BEGIN(MAPDEF); }
@@ -178,7 +196,7 @@ SUBST: { BEGIN(ITEM_LIST); return SUBST; }
NSUBST: { BEGIN(ITEM_LIST); return NSUBST; }
MONS: { BEGIN(MNAME); return MONS; }
ITEM: { BEGIN(ITEM_LIST); return ITEM; }
-MARKER: { BEGIN(ITEM_LIST); return MARKER; }
+MARKER: { BEGIN(TOEOL); return MARKER; }
SHUFFLE: { BEGIN(ITEM_LIST); return SHUFFLE; }
KFEAT: { BEGIN(ARGUMENT); return KFEAT; }
diff --git a/crawl-ref/source/util/levcomp.ypp b/crawl-ref/source/util/levcomp.ypp
index 0da88b1ce1..10b74dcc04 100644
--- a/crawl-ref/source/util/levcomp.ypp
+++ b/crawl-ref/source/util/levcomp.ypp
@@ -15,6 +15,8 @@ int yylex();
extern int yylineno;
+static bool start_marker_segment = false;
+
void yyerror(const char *e)
{
if (strstr(e, lc_desfile.c_str()) == e)
@@ -280,19 +282,32 @@ tagstring : STRING
}
;
-marker : MARKER marker_specs { }
+marker : MARKER
+ {
+ lc_map.main.add(yylineno, "marker(");
+ start_marker_segment = true;
+ }
+ marker_spec
+ {
+ lc_map.main.add(yylineno, ")");
+ }
;
-marker_specs : marker_spec { }
- | marker_specs COMMA marker_spec { }
+marker_spec : mspec_segments
+
+mspec_segments : /* nothing */
+ | mspec_segments mspec_segment
;
-marker_spec : ITEM_INFO
+mspec_segment : STRING
{
lc_map.main.add(
- yylineno,
- make_stringf("marker(\"%s\")",
+ yylineno,
+ make_stringf(
+ "%s\"%s\"",
+ start_marker_segment? "" : " .. ",
quote_lua_string($1).c_str()));
+ start_marker_segment = false;
}
;
diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc
index 85e780d42d..67e9274d22 100644
--- a/crawl-ref/source/view.cc
+++ b/crawl-ref/source/view.cc
@@ -232,6 +232,11 @@ void clear_envmap_grid( int x, int y )
env.map[x][y].clear();
}
+bool is_notable_terrain(dungeon_feature_type ftype)
+{
+ return Feature[ftype].notable;
+}
+
#if defined(WIN32CONSOLE) || defined(DOS)
static unsigned colflag2brand(int colflag)
{
diff --git a/crawl-ref/source/view.h b/crawl-ref/source/view.h
index 19f0cedbd5..a5357ce218 100644
--- a/crawl-ref/source/view.h
+++ b/crawl-ref/source/view.h
@@ -124,6 +124,7 @@ bool is_terrain_known( int x, int y );
bool is_terrain_seen( int x, int y );
bool is_terrain_changed( int x, int y );
bool is_terrain_known(const coord_def &p);
+bool is_notable_terrain(dungeon_feature_type ftype);
inline bool is_terrain_seen(const coord_def &c)
{