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.
EXBEP5ZCPT3BLZQ63WN3KM44SP5SPHBPWUBPK2F5ZFRVKVS5A3RQC
OTQIWZDDZHNEFZRD4LGORAFWA7KEQYVNKUQ4WXAX72FTCDMIUNVQC
3M2NC6ARDXO35MB3IAXS5Q6ZJZ37BQOGWQR6YJNMMLXDKN5O7WIAC
CPGFXEIHLYNGWTPJGZOP4XJ3YEWLH5BINXNMLJ2GWNATIOTFG5HAC
IJHYMJBOPNFORSDVM4RBZ5ZPLGSFSG6JRTPPYOD4YYWPT4YXNRLQC
EJ4GIPFSSQCQASUMRF4CR2WPUQOTEHFRGLOYEZ7BH6YEMIR6DN4QC
JPFT2IRK2624J6QS72PSRMPYJQRIWSV567GFLC2JOJD7246XHOTAC
GQL5SIGBHLU3FMCE54XVGLRY5AZHRM6DUEB722REA2DPLGJSN6EQC
MT3256427VMCV4JUFWCN7ULY4KXSND5ZL5THDKYNWWYOXXR5DLIQC
IHV7JHD4E67NEGLZEO3FPQGJPJF3IAV6QV5A63FPG4SU2VRFV47QC
AUXHSGS4EFOPZ6TVZYWNVOUDO7NYKUKE3HBKGQQWTALSVFOE3HAAC
XP3H2HRV7VTJ6G723BCW4U6WWZHK5Y5OUORRUVP7F4UCKURT3YWQC
LE5U6CTXEIETQN5GOVYF2K2VCISRXR3ULORXDKIKWYDVBG5GS3WAC
FKRLQJYHK3GB6HHSNNFQ7L57ZAR52LSTBG5B6SNJBYZ2LQ36P67AC
SM6YRPYZS6LMDQA6X3VAOK2PGMUFKPD7JMWJISOQSMX2CBR4ISPAC
7Y5HSDFKA5TPLS2TWTRFMQVX6UXUDHXU5MUMXQSDFAIY4THQ3BIQC
D6A2MLKI7UQPZ4A4D6QSM6STFVDY77IY5APXCBT3LNH6CDVKT67QC
X7MFMKQTNZ2IWBFVGS6WQV7NRNKJ3DWQAW2X7IQMFQQXW24AHPZQC
NKONHW4JNY6HP2M63MNPM3H64ZWSUNUT5FX2STW4KTS4AMXJXXVQC
CPBVQFT7NWEYLYD34D5VYZIEOZ32YADXYTSFMS635YNXX4OFIZVAC
QP5NJZGX7NQ5IUD3NC4MOZVVMFEDURLPRCZRRH3F67TM4LHE2Y7QC
HNXKX6ZDQJV33E7UKZOLBYWJMRZ4QLEMXVXJZNRCTOIG2KVRTIEAC
BGWSWJV2FNPFPBFZ67CLWC6KFOQ3UGOY4R7N23PPJMRRYQ3EHMSAC
ZLQAAP55CJ77XIJN3DZVPT4GTTVLIBFJLIJJKI6L5UBSHX7VUK6AC
RVTOZCO22HWZUP7WA3VRBYJIHJP2F6DYO4VW2BCIVLUVCVJNKKHQC
IGUSSYANPKGMX5YDQC7AI5ZWZLEQPIXA4KG3RZHNPMERL554HQWQC
SDGK6QEAHVCMRJVWSHZUECXXC5ZSJ2PQYTZWN3KX5UQFGAWI4Q3AC
4INPF5IJVGBGPKVKFSWITIPKV2LC3XENRZLDYO5HQWRHNNV47RGQC
JDPJS5SNW6ZTY5DYV5QUBG2WVJ45FJ4CAOK6KDJKPEI2HLIHVLRQC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
Q2FZIIGQECGP2FKKWLGDBBQVGCFZ4JTY53PFPJP6X7YKC23AQGFQC
SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC
JTTHP2BEYEPBQMSDM7IKANTMKRPY6ACGL2JN4D3OBZ7HFXKAYEGQC
FAGA7XVY7FB5VEMJZ625GU5CPQMTY4KTX2NCIXQWFPH7ZQYJSAFAC
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<coord_def> find_marker_positions_by_prop(
const std::string &prop,
const std::string &expected = "",
unsigned maxresults = 0);
std::vector<map_marker*> find_markers_by_prop(
const std::string &prop,
const std::string &expected = "",
unsigned maxresults = 0);
return (*i);
{
marker_positions.push_back(*i);
if (maxresults && marker_positions.size() >= maxresults)
return (marker_positions);
}
}
return (marker_positions);
}
std::vector<map_marker*> find_markers_by_prop(
const std::string &prop,
const std::string &expected,
unsigned maxresults)
{
std::vector<map_marker*> markers;
for (rectangle_iterator pos(0, 0); pos; ++pos)
{
const std::vector<map_marker*> 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);
}
}
}
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<coord_def> 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<map_marker*> places =
find_markers_by_prop(prop, value, limit);
clua_gentable(ls, places, _push_mapmarker);
return (1);
# [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
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
}
-- 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
----------------------------------------------------------------------------
-- 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(<marker>, <trigger-function-names>)
--
-- 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
-- * "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.
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)
-- 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.
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
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)
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)
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)
-- * 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.
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")
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
}
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.")
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
}
template <typename list, typename lpush>
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);
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);
}
// 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<std::string> 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;
}
}
}