From ad11744e5d49d226fc9c77ed48c3dd8b97921350 Mon Sep 17 00:00:00 2001 From: Darshan Shaligram Date: Fri, 30 Oct 2009 15:46:55 +0530 Subject: lmark.synchronized_markers(): apply one marker's effects to multiple points simultaneously. synchronized_markers() takes a marker, and a list of method names to override, and returns a set of markers that fire simultaneously, to allow, say, fog machines that fire at random intervals, but always fire in unison. lm_mslav.lua has details. volcano.des has an example volcano entry vault that uses this. Allow fetching a list of Lua markers/marker positions by property value. Renamed dgn.find_marker_prop -> dgn.find_marker_position_by_prop. s/helper/listener/ for fog machine listeners. --- crawl-ref/source/clua.cc | 50 +++++++++- crawl-ref/source/clua.h | 2 + crawl-ref/source/cluautil.cc | 25 +++-- crawl-ref/source/cluautil.h | 18 +++- crawl-ref/source/dat/clua/lm_fog.lua | 172 +++++++++++++++++++-------------- crawl-ref/source/dat/clua/lm_func.lua | 33 ++++--- crawl-ref/source/dat/clua/lm_mslav.lua | 157 ++++++++++++++++++++++++++++++ crawl-ref/source/dat/clua/luamark.lua | 3 +- crawl-ref/source/dat/clua/point.lua | 4 + crawl-ref/source/dat/clua/util.lua | 8 ++ crawl-ref/source/dat/clua/ziggurat.lua | 2 +- crawl-ref/source/dat/volcano.des | 51 ++++++++-- crawl-ref/source/l_crawl.cc | 2 +- crawl-ref/source/l_dgn.cc | 40 +++++++- crawl-ref/source/mapmark.cc | 53 +++++++++- crawl-ref/source/mapmark.h | 13 ++- 16 files changed, 506 insertions(+), 127 deletions(-) create mode 100644 crawl-ref/source/dat/clua/lm_mslav.lua diff --git a/crawl-ref/source/clua.cc b/crawl-ref/source/clua.cc index d9273b74c9..64fd63b768 100644 --- a/crawl-ref/source/clua.cc +++ b/crawl-ref/source/clua.cc @@ -12,6 +12,7 @@ #include "l_libs.h" #include "files.h" +#include "libutil.h" #include "state.h" #include "stuff.h" @@ -275,7 +276,7 @@ bool CLua::runhook(const char *hook, const char *params, ...) // Remember top of stack, for debugging porpoises int stack_top = lua_gettop(ls); - lua_getglobal(ls, hook); + pushglobal(hook); if (!lua_istable(ls, -1)) { lua_pop(ls, 1); @@ -475,7 +476,7 @@ bool CLua::callbooleanfn(bool def, const char *fn, const char *params, ...) int stacktop = lua_gettop(ls); - lua_getglobal(ls, fn); + pushglobal(fn); if (!lua_isfunction(ls, -1)) { lua_pop(ls, 1); @@ -497,6 +498,46 @@ bool CLua::proc_returns(const char *par) const return (strchr(par, '>') != NULL); } +// Identical to lua_getglobal for simple names, but will look up +// "a.b.c" names in tables, so you can pushglobal("dgn.point") and get +// _G['dgn']['point'], as expected. +// +// Guarantees to push exactly one value onto the stack. +// +void CLua::pushglobal(const std::string &name) +{ + std::vector pieces = split_string(".", name); + lua_State *ls(state()); + + if (pieces.empty()) + lua_pushnil(ls); + + for (unsigned i = 0, size = pieces.size(); i < size; ++i) + { + if (!i) + lua_getglobal(ls, pieces[i].c_str()); + else + { + if (lua_istable(ls, -1)) + { + lua_pushstring(ls, pieces[i].c_str()); + lua_gettable(ls, -2); + // Swap the value we just found with the table itself. + lua_insert(ls, -2); + // And remove the table. + lua_pop(ls, 1); + } + else + { + // We expected a table here, but got something else. Fail. + lua_pop(ls, 1); + lua_pushnil(ls); + break; + } + } + } +} + bool CLua::callfn(const char *fn, const char *params, ...) { error.clear(); @@ -504,7 +545,7 @@ bool CLua::callfn(const char *fn, const char *params, ...) if (!ls) return (false); - lua_getglobal(ls, fn); + pushglobal(fn); if (!lua_isfunction(ls, -1)) { lua_pop(ls, 1); @@ -536,7 +577,7 @@ bool CLua::callfn(const char *fn, int nargs, int nret) // If a function is not provided on the stack, get the named function. if (fn) { - lua_getglobal(ls, fn); + pushglobal(fn); if (!lua_isfunction(ls, -1)) { lua_settop(ls, -nargs - 2); @@ -1139,4 +1180,3 @@ bool lua_datum::is_udata() const { LUA_CHECK_TYPE(lua_isuserdata); } - diff --git a/crawl-ref/source/clua.h b/crawl-ref/source/clua.h index a72d56159b..1bbf70c720 100644 --- a/crawl-ref/source/clua.h +++ b/crawl-ref/source/clua.h @@ -126,6 +126,8 @@ public: int execfile(const char *filename, bool trusted = false, bool die_on_fail = false); + void pushglobal(const std::string &name); + bool callbooleanfn(bool defval, const char *fn, const char *params, ...); bool callfn(const char *fn, int nargs, int nret = 1); bool callfn(const char *fn, const char *params, ...); diff --git a/crawl-ref/source/cluautil.cc b/crawl-ref/source/cluautil.cc index e55b836f3e..a9cb1cec7b 100644 --- a/crawl-ref/source/cluautil.cc +++ b/crawl-ref/source/cluautil.cc @@ -99,25 +99,24 @@ void clua_register_metatable(lua_State *ls, const char *tn, } } - -template -static int dlua_gentable(lua_State *ls, const list &strings, lpush push) +int clua_pushcxxstring(lua_State *ls, const std::string &s) { - lua_newtable(ls); - for (int i = 0, size = strings.size(); i < size; ++i) - { - push(ls, strings[i]); - lua_rawseti(ls, -2, i + 1); - } + lua_pushstring(ls, s.c_str()); return (1); } -inline static void dlua_pushcxxstring(lua_State *ls, const std::string &s) +int clua_stringtable(lua_State *ls, const std::vector &s) { - lua_pushstring(ls, s.c_str()); + return clua_gentable(ls, s, clua_pushcxxstring); } -int dlua_stringtable(lua_State *ls, const std::vector &s) +int clua_pushpoint(lua_State *ls, const coord_def &pos) { - return dlua_gentable(ls, s, dlua_pushcxxstring); + lua_pushnumber(ls, pos.x); + lua_pushnumber(ls, pos.y); + CLua &vm(CLua::get_vm(ls)); + if (!vm.callfn("dgn.point", 2, 1)) + luaL_error(ls, "dgn.point(%d,%d) failed: %s", + pos.x, pos.y, vm.error.c_str()); + return (1); } diff --git a/crawl-ref/source/cluautil.h b/crawl-ref/source/cluautil.h index 8ea98028ee..0f02555750 100644 --- a/crawl-ref/source/cluautil.h +++ b/crawl-ref/source/cluautil.h @@ -57,7 +57,7 @@ void clua_register_metatable(lua_State *ls, const char *tn, const luaL_reg *lr, int (*gcfn)(lua_State *ls) = NULL); -int dlua_stringtable(lua_State *ls, const std::vector &s); +int clua_stringtable(lua_State *ls, const std::vector &s); /* * User-data templates. @@ -175,4 +175,20 @@ 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) + +template +static int clua_gentable(lua_State *ls, const list &strings, lpush push) +{ + lua_newtable(ls); + for (int i = 0, size = strings.size(); i < size; ++i) + { + push(ls, strings[i]); + lua_rawseti(ls, -2, i + 1); + } + return (1); +} + +int clua_pushcxxstring(lua_State *ls, const std::string &s); +int clua_pushpoint(lua_State *ls, const coord_def &pos); + #endif diff --git a/crawl-ref/source/dat/clua/lm_fog.lua b/crawl-ref/source/dat/clua/lm_fog.lua index d893a150d2..97bc5750c4 100644 --- a/crawl-ref/source/dat/clua/lm_fog.lua +++ b/crawl-ref/source/dat/clua/lm_fog.lua @@ -48,11 +48,13 @@ -- start_clouds: The number of clouds to lay when the level containing -- the cloud machine is entered. This is necessary since clouds -- are cleared when the player leaves a level. --- helper: A FunctionMachine helper marker. Will be called whenever the countdown --- is activated, and whenever the fog machine is reset. It will be called --- with the FogMachine's marker, a string containing the event ("decrement", --- "trigger"), the actual event object, and a copy of the FogMachine itself. --- See the section "Messages for fog machines" at the end of the file. +-- listener: A FunctionMachine listener marker. Will be called +-- whenever the countdown is activated, and whenever the fog +-- machine is reset. It will be called with the FogMachine's +-- marker, a string containing the event ("decrement", "trigger"), +-- the actual event object, and a copy of the FogMachine itself. +-- See the section "Messages for fog machines" at the end of the +-- file. -- ------------------------------------------------------------------------------ @@ -96,7 +98,7 @@ function FogMachine:new(pars) m.size_max = pars.size_max or pars.size m.spread_rate = pars.spread_rate or -1 m.start_clouds = pars.start_clouds or 1 - m.helper = pars.helper or nil + m.listener = pars.listener or nil m.size_buildup_amnt = pars.size_buildup_amnt or 0 m.size_buildup_time = pars.size_buildup_time or 1 @@ -109,10 +111,16 @@ function FogMachine:new(pars) return m end -function FogMachine:do_fog(marker) - local x, y = marker:pos() +function FogMachine:apply_cloud(point, pow_min, pow_max, pow_rolls, + size, cloud_type, kill_cat, spread) + dgn.apply_area_cloud(point.x, point.y, pow_min, pow_max, pow_rolls, size, + cloud_type, kill_cat, spread) +end + +function FogMachine:do_fog(point) + local p = point if self.walk_dist > 0 then - x, y = dgn.random_walk(x, y, self.walk_dist) + p = dgn.point(dgn.random_walk(p.x, p.y, self.walk_dist)) end local buildup_turns = self.buildup_turns @@ -142,9 +150,9 @@ function FogMachine:do_fog(marker) local spread = self.spread_rate + (self.spread_buildup_amnt * buildup_turns / self.spread_buildup_time) - dgn.apply_area_cloud(x, y, self.pow_min, self.pow_max, self.pow_rolls, - crawl.random_range(size_min, size_max, 1), - self.cloud_type, self.kill_cat, spread) + self:apply_cloud(p, self.pow_min, self.pow_max, self.pow_rolls, + crawl.random_range(size_min, size_max, 1), + self.cloud_type, self.kill_cat, spread) end function FogMachine:activate(marker, verbose) @@ -153,6 +161,12 @@ function FogMachine:activate(marker, verbose) dgn.register_listener(dgn.dgn_event_type('entered_level'), marker) end +function FogMachine:notify_listener(point, event, evobj) + if self.listener then + return self.listener:do_function(point, event, ev, self) + end +end + function FogMachine:event(marker, ev) local _x, _y = marker:pos() if ev:type() == dgn.dgn_event_type('turn') then @@ -164,23 +178,21 @@ function FogMachine:event(marker, ev) self.buildup_turns = self.size_buildup_time end - if self.helper ~= nil and self.countdown > 0 then - self.helper:do_function(marker, "decrement", ev, self) - elseif self.helper ~= nil and self.countdown <= 0 then - self.helper:do_function(marker, "trigger", ev, self) + if self.countdown > 0 then + self:notify_listener(dgn.point(marker:pos()), "decrement", ev) + elseif self.countdown <= 0 then + self:notify_listener(dgn.point(marker:pos()), "trigger", ev) end while self.countdown <= 0 do - self:do_fog(marker) - self.countdown = self.countdown + + self:do_fog(dgn.point(marker:pos())) + self.countdown = self.countdown + crawl.random_range(self.delay_min, self.delay_max, 1) end elseif ev:type() == dgn.dgn_event_type('entered_level') then for i = 1, self.start_clouds do - self:do_fog(marker) - if self.helper ~= nil then - self.helper:do_function(marker, "trigger", ev, self) - end + self:do_fog(dgn.point(marker:pos())) + self:notify_listener(dgn.point(marker:pos()), "trigger", ev) self.countdown = crawl.random_range(self.delay_min, self.delay_max, 1) self.buildup_turns = 0 end @@ -206,9 +218,9 @@ function FogMachine:write(marker, th) file.marshall(th, self.spread_buildup_time) file.marshall(th, self.buildup_turns) file.marshall(th, self.countdown) - if self.helper ~= nil then + if self.listener then file.marshall_meta(th, true) - self.helper:write(marker, th) + self.listener:write(marker, th) else file.marshall_meta(th, false) end @@ -233,12 +245,15 @@ function FogMachine:read(marker, th) self.spread_buildup_time = file.unmarshall_number(th) self.buildup_turns = file.unmarshall_number(th) self.countdown = file.unmarshall_number(th) - got_helper = file.unmarshall_meta(th) - if got_helper == true then - self.helper = function_machine ({marker_type = "helper", func = (function() end)}) - self.helper:read(marker, th) + got_listener = file.unmarshall_meta(th) + if got_listener == true then + self.listener = function_machine { + marker_type = "listener", + func = (function() end) + } + self.listener:read(marker, th) else - self.helper = nil + self.listener = nil end setmetatable(self, FogMachine) @@ -280,41 +295,46 @@ end ------------------------------------------------------------------------------- -- Messages for fog machines. -- --- * warning_machine: Takes three parameters: turns, cantsee_message, and, --- optionally, see_message. Turns is the value of player turns before to --- trigger the message before the fog machine is fired. If only see_message --- is provided, the message will only be printed if the player can see the --- fog machine. If only cantsee_mesage is provided, the message will be --- displayed regardless. In combination, the message will be different --- depending on whether or not the player can see the marker. By default, the --- message will be displaying using the "warning" channel. +-- * warning_machine: Takes three parameters: turns, cantsee_message, +-- and, optionally, see_message. Turns is the value of player +-- turns before to trigger the message before the fog machine is +-- fired. If only see_message is provided, the message will only +-- be printed if the player can see the fog machine. If only +-- cantsee_mesage is provided, the message will be displayed +-- regardless. In combination, the message will be different +-- depending on whether or not the player can see the marker. By +-- default, the message will be displaying using the "warning" +-- channel. -- --- * trigger_machine: Takes three parameters: cantsee_message, see_message, and, --- optionally, channel. The functionality is identical to a warning_machine, --- only the message is instead displayed (or not displayed) when the fog machine --- is triggered. The message channel can be provided. +-- * trigger_machine: Takes three parameters: cantsee_message, +-- see_message, and, optionally, channel. The functionality is +-- identical to a warning_machine, only the message is instead +-- displayed (or not displayed) when the fog machine is +-- triggered. The message channel can be provided. -- --- * tw_machine: Combines the above two message machines, providing warning messages --- as well as messages when triggered. Takes the parameters: warn_turns, --- warning_cantsee_message, trigger_cantsee_message, trigger_channel, --- trigger_see_message, warning_see_message. Parameters work as described above. +-- * tw_machine: Combines the above two message machines, providing +-- warning messages as well as messages when triggered. Takes the +-- parameters: warn_turns, warning_cantsee_message, +-- trigger_cantsee_message, trigger_channel, trigger_see_message, +-- warning_see_message. Parameters work as described above. -- --- In all instances, the "cantsee" form of the message parameter cannot be null, --- and for warning and dual trigger/warning machines, the turns parameter cannot --- be null. All other parameters are considered optional. +-- In all instances, the "cantsee" form of the message parameter +-- cannot be null, and for warning and dual trigger/warning machines, +-- the turns parameter cannot be null. All other parameters are +-- considered optional. function warning_machine (trns, cantsee_mesg, see_mesg) if trns == nil or (see_mesg == nil and cantsee_mesg == nil) then error("WarningMachine requires turns and message!") end - local function warning_func (marker, mtable, m2, event_name, event, fm) + local function warning_func (point, mtable, event_name, event, fm) local countdown = fm.countdown if event_name == "decrement" and countdown <= mtable.turns then - if mtable.warning_done ~= true then - if mtable.see_message ~= nil and you.see_cell(marker:pos()) then - crawl.mpr(mtable.see_message, "warning") - elseif mtable.cantsee_message ~= nil then - crawl.mpr(mtable.cantsee_message, "warning") + if not mtable.warning_done then + if mtable.see_message and you.see_cell(point.x, point.y) then + crawl.mpr(mtable.see_message, "warning") + elseif mtable.cantsee_message then + crawl.mpr(mtable.cantsee_message, "warning") end mtable.warning_done = true end @@ -322,7 +342,7 @@ function warning_machine (trns, cantsee_mesg, see_mesg) mtable.warning_done = false end end - pars = {marker_type = "helper"} + pars = {marker_type = "listener"} pars.marker_params = {see_message = see_mesg, cantsee_message = cantsee_mesg, turns = trns * 10, warning_done = false} pars.func = warning_func @@ -333,34 +353,40 @@ function trigger_machine (cantsee_mesg, see_mesg, chan) if see_mesg == nil and cantsee_mesg == nil then error("Triggermachine requires a message!") end - local function trigger_func (marker, mtable, m2, event_name, event, fm) + local function trigger_func (point, mtable, event_name, event, fm) local countdown = fm.countdown if event_name == "trigger" then channel = mtable.channel or "" - if mtable.see_message ~= nil and you.see_cell(marker:pos()) then + if mtable.see_message ~= nil and you.see_cell(point.x, point.y) then crawl.mpr(mtable.see_message, channel) elseif mtable.cantsee_message ~= nil then crawl.mpr(mtable.cantsee_message, channel) end end end - pars = {marker_type = "helper"} - pars.marker_params = {channel = chan or nil, see_message = see_mesg, cantsee_message = cantsee_mesg} + pars = {marker_type = "listener"} + pars.marker_params = { + channel = chan or nil, + see_message = see_mesg, + cantsee_message = cantsee_mesg + } pars.func = trigger_func return FunctionMachine:new(pars) end -function tw_machine (warn_turns, warn_cantsee_message, trig_cantsee_message, trig_channel, +function tw_machine (warn_turns, warn_cantsee_message, + trig_cantsee_message, trig_channel, trig_see_message, warn_see_message) - if warn_turns == nil or (warn_see_message == nil and warn_cantsee_message == nil) - or (trig_see_message == nil and trig_cantsee_message == nil) then - error("TWMachine needs warning turns, warning message and triggeing message.") + if (not warn_turns or (not warn_see_message and not warn_cantsee_message) + or (not trig_see_message and not trig_cantsee_message)) then + error("TWMachine needs warning turns, warning message and " + .. "triggering message.") end - local function tw_func (marker, mtable, m2, event_name, event, fm) + local function tw_func (point, mtable, event_name, event, fm) local countdown = fm.countdown if event_name == "decrement" and countdown <= mtable.warning_turns then if mtable.warning_done ~= true then - if mtable.warning_see_message ~= nil and you.see_cell(marker:pos()) then + if mtable.warning_see_message and you.see_cell(point.x, point.y) then crawl.mpr(mtable.warning_see_message, "warning") elseif mtable.warning_cantsee_message ~= nil then crawl.mpr(mtable.warning_cantsee_message, "warning") @@ -370,17 +396,23 @@ function tw_machine (warn_turns, warn_cantsee_message, trig_cantsee_message, tri elseif event_name == "trigger" then mtable.warning_done = false channel = mtable.trigger_channel or "" - if mtable.trigger_see_message ~= nil and you.see_cell(marker:pos()) then + if mtable.trigger_see_message and you.see_cell(point.x, point.y) then crawl.mpr(mtable.trigger_see_message, channel) elseif mtable.trigger_cantsee_message ~= nil then crawl.mpr(mtable.trigger_cantsee_message, channel) end end end - pars = {marker_type = "helper"} - pars.marker_params = {warning_see_message = warn_see_message, warning_cantsee_message = warn_cantsee_message, - warning_turns = warn_turns * 10, warning_done = false, trigger_see_message = trig_see_message, - trigger_cantsee_message = trig_cantsee_message, trigger_channel = trig_channel or nil} + pars = {marker_type = "listener"} + pars.marker_params = { + warning_see_message = warn_see_message, + warning_cantsee_message = warn_cantsee_message, + warning_turns = warn_turns * 10, + warning_done = false, + trigger_see_message = trig_see_message, + trigger_cantsee_message = trig_cantsee_message, + trigger_channel = trig_channel or nil + } pars.func = tw_func return FunctionMachine:new(pars) end diff --git a/crawl-ref/source/dat/clua/lm_func.lua b/crawl-ref/source/dat/clua/lm_func.lua index b76d6cdc9a..cd83cf61a3 100644 --- a/crawl-ref/source/dat/clua/lm_func.lua +++ b/crawl-ref/source/dat/clua/lm_func.lua @@ -21,12 +21,14 @@ -- * "player_at": Calls the function whenever the player is at the -- same position as the marker. Takes the same "repeated" -- parameter as "in_los". --- * "helper": A function machine that can be linked into other lua markers --- and machines. It is not triggered independantly, but called by the "parent" --- marker, though always with the same marker_table parameter as other --- machines. May take further parameters, see the parent's documentation. +-- * "helper": A function machine that can be linked into other lua +-- markers and machines. It is not triggered independantly, but +-- called by the "parent" marker, though always with the same +-- marker_table parameter as other machines. May take further +-- parameters, see the parent's documentation. -- --- marker_table: Table to be passed to the function when called. Defaults to {}. +-- marker_table: Table to be passed to the function when called. +-- Defaults to {}. -- -- Specific markers take specific parameters, as listed under marker_type. -- @@ -95,13 +97,12 @@ function FunctionMachine:new(pars) return m end -function FunctionMachine:do_function(...) - marker = arg[1] - local _x, _y = marker:pos() - if #arg == 1 then - self.func(marker, self.marker_params) +function FunctionMachine:do_function(position, ...) + local largs = { ... } + if #largs == 0 then + self.func(position, self.marker_params) else - self.func(marker, self.marker_params, unpack(arg)) + self.func(position, self.marker_params, unpack(largs)) end end @@ -121,7 +122,7 @@ function FunctionMachine:event(marker, ev) self.countdown = self.countdown - ev:ticks() while self.countdown <= 0 do - self:do_function(marker) + self:do_function(dgn.point(marker:pos())) self.activated = true self.countdown = self.countdown + crawl.random_range(self.turns_min, self.turns_max, 1) @@ -129,7 +130,7 @@ function FunctionMachine:event(marker, ev) elseif self.marker_type == "in_los" then if you.see_cell(x, y) then if not self.activated or self.repeated then - self:do_function(marker) + self:do_function(dgn.point(marker:pos())) self.activated = true end end @@ -137,7 +138,7 @@ function FunctionMachine:event(marker, ev) you_x, you_y = you.pos() if you_x == x and you_y == y then if not self.activated or self.repeated then - self:do_function(marker) + self:do_function(dgn.point(marker:pos())) self.activated = true end end @@ -178,7 +179,9 @@ end function message_machine (pars) local channel = pars.channel or false local mtable = {message = pars.message, channel = pars.channel} - pars.func = (function(marker, mtable) crawl.mpr(mtable.message, mtable.channel) end) + pars.func = function (position, mtable) + crawl.mpr(mtable.message, mtable.channel) + end pars.marker_params = mtable return FunctionMachine:new(pars) end diff --git a/crawl-ref/source/dat/clua/lm_mslav.lua b/crawl-ref/source/dat/clua/lm_mslav.lua new file mode 100644 index 0000000000..141dea7521 --- /dev/null +++ b/crawl-ref/source/dat/clua/lm_mslav.lua @@ -0,0 +1,157 @@ +---------------------------------------------------------------------------- +-- lm_mslav.lua +-- +-- Wraps a marker to act as a master firing synchronized events to its +-- own position, and to any number of (or zero) slave markers' +-- positions. +-- +-- API: lmark.synchronized_markers(, ) +-- +-- Usage: +-- ------ +-- +-- You can use synchronized_markers() if you have a marker that +-- performs an activity at random intervals, and you want to apply +-- this marker's effects to multiple locations at the same time. +-- +-- As an example, take a fog machine: +-- 1) Create the fog machine as you would normally: +-- local fog = fog_machine { +-- cloud_type = 'flame', +-- size = 3, pow_min=2, +-- pow_max = 5, delay_min = 22, delay_max = 120, +-- } +-- +-- 2) Apply it as a Lua marker to one or more locations, wrapping it +-- with synchronized_markers(): +-- lua_marker('m', lmark.synchronized_markers(fog, 'do_fog')) +-- Where 'do_fog' is the name of the trigger method on the +-- underlying marker (here the fog machine) that performs the +-- activity of interest (generating fog at some point). The first +-- parameter of this overridden method must be a dgn.point that +-- specifies where the effect occurs. The method may also take any +-- number of additional parameters. +-- +-- You may override multiple methods on the base marker: +-- lmark.synchronized_markers(fog, 'do_fog', 'notify_listener') +-- The only requirement for an overridden method is that it take a +-- dgn.point as its first parameter. +-- +-- Internals: +-- --------- +-- synchronized_markers() takes one marker instance, and creates one +-- master marker (which is based on the given marker instance) and +-- multiple slave markers (which are simple PortalDescriptor markers). +-- The only purpose of the slave markers is to be discoverable by +-- dgn.find_marker_positions_by_prop, given a unique, autogenerated +-- slave id. +-- +-- The master marker operates normally, but calls to any of the trigger +-- methods (say 'do_fog') are intercepted. Every trigger call is performed +-- on the master's position, and then on all the slaves' positions. +---------------------------------------------------------------------------- + +util.namespace('lmark') + +lmark.slave_cookie = 0 + +function lmark.next_slave_id() + local slave_id = "marker_slave" .. lmark.slave_cookie + lmark.slave_cookie = lmark.slave_cookie + 1 + return slave_id +end + +function lmark.saveable_slave_table(slave) + local saveable = { + slave_id = slave.slave_id, + triggers = slave.triggers, + old_read = slave.old_read + } + return saveable +end + +function lmark:master_trigger_fn(trigger_name, point, ...) + local old_trigger = self.slave_table.old_triggers[trigger_name] + -- Pull the trigger on the master first. + old_trigger(self, point, ...) + + local slave_points = + dgn.find_marker_positions_by_prop("slave_id", self.slave_table.slave_id) + for _, slave_pos in ipairs(slave_points) do + old_trigger(self, slave_pos, ...) + end +end + +function lmark:master_write(marker, th) + -- Save the slave table first. + lmark.marshall_table(th, lmark.saveable_slave_table(self.slave_table)) + self.slave_table.old_write(self, marker, th) +end + + +function lmark:master_read(marker, th) + -- Load the slave table. + local slave_table = lmark.unmarshall_table(th) + + local cookie_number = string.match(slave_table.slave_id, "marker_slave(%d+)") + -- [ds] Try to avoid reusing the same cookie as one we've reloaded. + -- This is only necessary to avoid collisions with cookies generated + -- for future vaults placed on this level (such as by the Trowel + -- card). + if cookie_number then + cookie_number = tonumber(cookie_number) + if lmark.slave_cookie <= cookie_number then + lmark.slave_cookie = cookie_number + 1 + end + end + + -- Call the old read function. + local newself = slave_table.old_read(self, marker, th) + -- And redecorate the marker as a master marker. + return lmark.make_master(newself, slave_table.slave_id, + slave_table.triggers) +end + +function lmark.make_master(lmarker, slave_id, triggers) + local old_trigger_map = { } + for _, trigger_name in ipairs(triggers) do + old_trigger_map[trigger_name] = lmarker[trigger_name] + lmarker[trigger_name] = + function (self, ...) + return lmark.master_trigger_fn(self, trigger_name, ...) + end + end + + lmarker.slave_table = { + slave_id = slave_id, + triggers = triggers, + old_write = lmarker.write, + old_triggers = old_trigger_map, + old_read = lmarker.read + } + + lmarker.write = lmark.master_write + lmarker.read = lmark.master_read + + return lmarker +end + +function lmark.make_slave(slave_id) + return portal_desc { slave_id = slave_id } +end + +function lmark.synchronized_markers(master, ...) + local first = true + local slave_id = lmark.next_slave_id() + local triggers = { ... } + assert(#triggers > 0, + "Please provide one or more trigger functions on the master marker") + return function () + if first then + first = false + return lmark.make_master(master, slave_id, triggers) + else + return lmark.make_slave(slave_id) + end + end +end \ No newline at end of file diff --git a/crawl-ref/source/dat/clua/luamark.lua b/crawl-ref/source/dat/clua/luamark.lua index 86b125a53c..10eb05945c 100644 --- a/crawl-ref/source/dat/clua/luamark.lua +++ b/crawl-ref/source/dat/clua/luamark.lua @@ -13,6 +13,7 @@ require('clua/lm_fog.lua') require('clua/lm_props.lua') require('clua/lm_monst.lua') require('clua/lm_func.lua') +require('clua/lm_mslav.lua') function dlua_marker_function(table, name) return table[name] @@ -28,7 +29,7 @@ function dlua_marker_read(fn, marker, th) return fn({ }, marker, th) end -lmark = { } +util.namespace('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 diff --git a/crawl-ref/source/dat/clua/point.lua b/crawl-ref/source/dat/clua/point.lua index b750372197..f186dc62f5 100644 --- a/crawl-ref/source/dat/clua/point.lua +++ b/crawl-ref/source/dat/clua/point.lua @@ -22,6 +22,10 @@ local function sgn(x) end end +function point_metatable:xy() + return self.x, self.y +end + point_metatable.sgn = function (p) return dgn.point(sgn(p.x), sgn(p.y)) end diff --git a/crawl-ref/source/dat/clua/util.lua b/crawl-ref/source/dat/clua/util.lua index 1ed3670172..5ef063d9fb 100644 --- a/crawl-ref/source/dat/clua/util.lua +++ b/crawl-ref/source/dat/clua/util.lua @@ -326,3 +326,11 @@ function util.copy_table(object) return _copy(object) end +-- Initialises a namespace that has functions spread across multiple files. +-- If the namespace table does not exist, it is created. If it already exists, +-- it is not modified. +function util.namespace(table_name) + if _G[table_name] == nil then + _G[table_name] = { } + end +end \ No newline at end of file diff --git a/crawl-ref/source/dat/clua/ziggurat.lua b/crawl-ref/source/dat/clua/ziggurat.lua index ba35595d70..8ac89bc460 100644 --- a/crawl-ref/source/dat/clua/ziggurat.lua +++ b/crawl-ref/source/dat/clua/ziggurat.lua @@ -600,7 +600,7 @@ local function ziggurat_create_loot_vault(entry, exit) return exit else -- Find the square to drop the loot. - local lootx, looty = dgn.find_marker_prop("ziggurat_loot") + local lootx, looty = dgn.find_marker_position_by_prop("ziggurat_loot") if lootx and looty then return dgn.point(lootx, looty) diff --git a/crawl-ref/source/dat/volcano.des b/crawl-ref/source/dat/volcano.des index fdfd592fe9..1b248e11ed 100644 --- a/crawl-ref/source/dat/volcano.des +++ b/crawl-ref/source/dat/volcano.des @@ -156,28 +156,28 @@ function place_large_volcano(e) e.kfeat("V = l") e.lua_marker('V', fog_machine { cloud_type = "flame", walk_dist=15, pow_max=6, delay = 300, size = 10000, spread_rate = 30, - helper = large_warning}) + listener = large_warning}) end function place_medium_volcano(e) e.kfeat("V = l") e.lua_marker('V', fog_machine { cloud_type = "flame", walk_dist=15, pow_max=6, delay = 300, size = 3000, spread_rate = 30, - helper = small_warning}) + listener = small_warning}) end function place_small_volcano(e) e.kfeat("V = l") e.lua_marker('V', fog_machine { cloud_type = "flame", walk_dist=15, pow_max=6, delay = 300, size = 800, spread_rate = 30, - helper = small_warning}) + listener = small_warning}) end function place_tiny_volcano(e) e.kfeat("V = l") e.lua_marker('V', fog_machine { cloud_type = "flame", walk_dist=10, pow_max=6, delay = 800, size = 80, spread_rate = 10, - helper = large_warning}) + listener = large_warning}) end function place_medium_flame_cloud (e, glyph, nolava) @@ -389,6 +389,33 @@ default-depth: Lair:1-8, Orc:1-4, Hive:1 ############################################################################### # Entries: + +# [ds] A dummy entry that's a proof-of-concept for synchronized fog machines +# that trigger at multiple places at the same instant, but still have a random +# delay. +NAME: enter_volcano_snarktest101 +TAGS: uniq_volcano +ORIENT: float +WEIGHT: 0 +{{ + local fog = fog_machine { cloud_type = 'flame', + size = 3, pow_min=2, + pow_max = 5, delay_min = 22, delay_max = 120, + } + lua_marker('m', lmark.synchronized_markers(fog, 'do_fog')) +}} +SUBST: m = . +: volcano_portal(_G) +MAP +....... +...m... +....... +.m.O.m. +....... +...m... +....... +ENDMAP + NAME: enter_volcano_1 TAGS: uniq_volcano ORIENT: float @@ -841,8 +868,8 @@ LFLAGS: no_tele_control {{ local mytable = { total_doorways=8, total_collapsed=0, ac = {} } -local function collapse_doorways (marker, mytable) - local x, y = marker:pos() +local function collapse_doorways (point, mytable) + local x, y = point:xy() local you_x, you_y = you.pos() if mytable.total_collapsed == mytable.total_doorways then return @@ -856,7 +883,7 @@ local function collapse_doorways (marker, mytable) local ack = x .. "/" .. y if mytable.ac[ack] ~= true then mytable.ac[ack] = true - if you.see_cell(marker:pos()) then + if you.see_cell(x, y) then crawl.mpr("The volcano erupts! Nearby, a roof collapses.", "warning") else crawl.mpr("There is a rumble as the volcano erupts. The roof shakes.", @@ -868,8 +895,14 @@ local function collapse_doorways (marker, mytable) end end -local collapse_marker = function_machine ( {marker_type = "random", turns_min=30, - turns_max=40, func=collapse_doorways, marker_params=mytable } ) +local collapse_marker = function_machine { + marker_type = "random", + turns_min=30, + turns_max=40, + func=collapse_doorways, + marker_params=mytable +} + }} KPROP: RXZ12< = no_rtele_into SUBST: X = . diff --git a/crawl-ref/source/l_crawl.cc b/crawl-ref/source/l_crawl.cc index d7571a63e7..a1682c59b3 100644 --- a/crawl-ref/source/l_crawl.cc +++ b/crawl-ref/source/l_crawl.cc @@ -603,7 +603,7 @@ void cluaopen_crawl(lua_State *ls) LUAFN(_crawl_args) { - return dlua_stringtable(ls, SysEnv.cmd_args); + return clua_stringtable(ls, SysEnv.cmd_args); } LUAFN(_crawl_milestone) diff --git a/crawl-ref/source/l_dgn.cc b/crawl-ref/source/l_dgn.cc index 86d142192e..180ffc87ab 100644 --- a/crawl-ref/source/l_dgn.cc +++ b/crawl-ref/source/l_dgn.cc @@ -356,7 +356,7 @@ static int dgn_map(lua_State *ls) { MAP(ls, 1, map); if (lua_gettop(ls) == 1) - return dlua_stringtable(ls, map->map.get_lines()); + return clua_stringtable(ls, map->map.get_lines()); if (lua_isnil(ls, 2)) { @@ -1407,12 +1407,12 @@ LUAFN(_dgn_in_vault) return (1); } -LUAFN(_dgn_find_marker_prop) +LUAFN(_dgn_find_marker_position_by_prop) { const char *prop = luaL_checkstring(ls, 1); const std::string value( lua_gettop(ls) >= 2 ? luaL_checkstring(ls, 2) : ""); - const coord_def place = find_marker_prop(prop, value); + const coord_def place = find_marker_position_by_prop(prop, value); if (map_bounds(place)) clua_push_coord(ls, place); else @@ -1423,6 +1423,36 @@ LUAFN(_dgn_find_marker_prop) return (2); } +LUAFN(_dgn_find_marker_positions_by_prop) +{ + const char *prop = luaL_checkstring(ls, 1); + const std::string value( + lua_gettop(ls) >= 2 ? luaL_checkstring(ls, 2) : ""); + const unsigned limit(lua_gettop(ls) >= 3 ? luaL_checkint(ls, 3) : 0); + const std::vector places = + find_marker_positions_by_prop(prop, value, limit); + clua_gentable(ls, places, clua_pushpoint); + return (1); +} + +static int _push_mapmarker(lua_State *ls, map_marker *marker) +{ + dlua_push_userdata(ls, marker, MAPMARK_METATABLE); + return (1); +} + +LUAFN(_dgn_find_markers_by_prop) +{ + const char *prop = luaL_checkstring(ls, 1); + const std::string value( + lua_gettop(ls) >= 2 ? luaL_checkstring(ls, 2) : ""); + const unsigned limit(lua_gettop(ls) >= 3 ? luaL_checkint(ls, 3) : 0); + const std::vector places = + find_markers_by_prop(prop, value, limit); + clua_gentable(ls, places, _push_mapmarker); + return (1); +} + extern spec_room lua_special_room_spec; extern int lua_special_room_level; @@ -1588,7 +1618,9 @@ const struct luaL_reg dgn_dlib[] = { "resolve_map", _dgn_resolve_map }, { "in_vault", _dgn_in_vault }, -{ "find_marker_prop", _dgn_find_marker_prop }, +{ "find_marker_position_by_prop", _dgn_find_marker_position_by_prop }, +{ "find_marker_positions_by_prop", _dgn_find_marker_positions_by_prop }, +{ "find_markers_by_prop", _dgn_find_markers_by_prop }, { "get_special_room_info", dgn_get_special_room_info }, diff --git a/crawl-ref/source/mapmark.cc b/crawl-ref/source/mapmark.cc index 253855eb23..e7d3abf65a 100644 --- a/crawl-ref/source/mapmark.cc +++ b/crawl-ref/source/mapmark.cc @@ -824,16 +824,59 @@ bool feature_marker_at(const coord_def &pos, dungeon_feature_type feat) return (false); } -const coord_def find_marker_prop(const std::string &prop, - const std::string &expected) +coord_def find_marker_position_by_prop(const std::string &prop, + const std::string &expected) { + const std::vector markers = + find_marker_positions_by_prop(prop, expected, 1); + if (markers.empty()) + { + const coord_def nowhere(-1, -1); + return (nowhere); + } + return markers[0]; +} + +std::vector find_marker_positions_by_prop( + const std::string &prop, + const std::string &expected, + unsigned maxresults) +{ + std::vector marker_positions; for (rectangle_iterator i(0, 0); i; ++i) { const std::string value = env.markers.property_at(*i, MAT_ANY, prop); if (!value.empty() && (expected.empty() || value == expected)) - return (*i); + { + marker_positions.push_back(*i); + if (maxresults && marker_positions.size() >= maxresults) + return (marker_positions); + } + } + return (marker_positions); +} + +std::vector find_markers_by_prop( + const std::string &prop, + const std::string &expected, + unsigned maxresults) +{ + std::vector markers; + for (rectangle_iterator pos(0, 0); pos; ++pos) + { + const std::vector markers_here = + env.markers.get_markers_at(*pos); + for (unsigned i = 0, size = markers_here.size(); i < size; ++i) + { + const std::string value(markers_here[i]->property(prop)); + if (!value.empty() && (expected.empty() || value == expected)) + { + markers.push_back(markers_here[i]); + if (maxresults && markers.size() >= maxresults) + return (markers); + } + } } - const coord_def nowhere(-1, -1); - return (nowhere); + return (markers); } diff --git a/crawl-ref/source/mapmark.h b/crawl-ref/source/mapmark.h index 71a142ce13..67e64a2804 100644 --- a/crawl-ref/source/mapmark.h +++ b/crawl-ref/source/mapmark.h @@ -13,6 +13,7 @@ #include "dlua.h" #include #include +#include #include ////////////////////////////////////////////////////////////////////////// @@ -23,8 +24,16 @@ class writer; bool marker_vetoes_operation(const char *op); bool feature_marker_at(const coord_def &pos, dungeon_feature_type feat); -const coord_def find_marker_prop(const std::string &prop, - const std::string &expected = ""); +coord_def find_marker_position_by_prop(const std::string &prop, + const std::string &expected = ""); +std::vector find_marker_positions_by_prop( + const std::string &prop, + const std::string &expected = "", + unsigned maxresults = 0); +std::vector find_markers_by_prop( + const std::string &prop, + const std::string &expected = "", + unsigned maxresults = 0); class map_marker { -- cgit v1.2.3-54-g00ecf