summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/dat/clua
diff options
context:
space:
mode:
authorMatthew Cline <zelgadis@sourceforge.net>2009-10-22 03:53:05 -0700
committerMatthew Cline <zelgadis@sourceforge.net>2009-10-22 03:53:05 -0700
commit49870f2bc48989467ea4f8c847fd1274cec73944 (patch)
tree050245a946eb520236d028cec8c661743c76d501 /crawl-ref/source/dat/clua
parenta724290e7ae62bc20013919bc14c16e91d842083 (diff)
downloadcrawl-ref-49870f2bc48989467ea4f8c847fd1274cec73944.tar.gz
crawl-ref-49870f2bc48989467ea4f8c847fd1274cec73944.zip
Observerable/observer-ish pattern for Lua markers
A new framework for Lua markers, similar to the observable/observer design pattern, which decouples the thing being activated from the thing watching for the activating condition. This makes it easier to create new types of Lua markers which are triggered by dungeon events, and easier to add new triggering conditions to already existing marker types. Currently only ChangeFlags (clua/lm_flags.lua) and MonsterOnTrigger (clua/lm_monst.lua) use it.
Diffstat (limited to 'crawl-ref/source/dat/clua')
-rw-r--r--crawl-ref/source/dat/clua/lm_flags.lua267
-rw-r--r--crawl-ref/source/dat/clua/lm_monst.lua135
-rw-r--r--crawl-ref/source/dat/clua/lm_trig.lua352
-rw-r--r--crawl-ref/source/dat/clua/luamark.lua2
-rw-r--r--crawl-ref/source/dat/clua/util.lua20
5 files changed, 500 insertions, 276 deletions
diff --git a/crawl-ref/source/dat/clua/lm_flags.lua b/crawl-ref/source/dat/clua/lm_flags.lua
index 4660fa5abb..3a2f3e1b5b 100644
--- a/crawl-ref/source/dat/clua/lm_flags.lua
+++ b/crawl-ref/source/dat/clua/lm_flags.lua
@@ -53,51 +53,43 @@
-- "item", which is the plain name of the item its watching
-- (i.e., "Orb of Zot" and "golden rune of Zot" rather than
-- "the Orb of Zot" or "a golden rune of Zot").
+--
+-- ChangeFlags is a Triggerable subclass, and the above three functions
+-- are just convenience functions which add a single triggerer. Other
+-- triggerers (or more than one) can be added.
--------------------------------------------------------------------------
-
-ChangeFlags = { CLASS = "ChangeFlags" }
-ChangeFlags.__index = ChangeFlags
-function ChangeFlags:_new()
- local cf = { }
- setmetatable(cf, self)
- self.__index = self
+require('clua/lm_trig.lua')
- return cf
-end
+ChangeFlags = util.subclass(Triggerable)
+ChangeFlags.CLASS = "ChangeFlags"
function ChangeFlags:new(pars)
pars = pars or { }
+ local cf = self.super.new(self)
+
pars.level_flags = pars.level_flags or ""
pars.branch_flags = pars.branch_flags or ""
pars.msg = pars.msg or ""
- if pars.level_flags == "" and pars.branch_flags == ""
- and not pars.trigger
- then
- error("Must provide at least one of level_flags, branch_flags, or trigger.")
+ if pars.level_flags == "" and pars.branch_flags == "" then
+ error("Must provide at least one of level_flags or branch_flags.")
end
- local cf = self:_new()
cf.level_flags = pars.level_flags
cf.branch_flags = pars.branch_flags
cf.msg = pars.msg
- cf.trigger = pars.trigger
cf.props = { flag_group = pars.group }
return cf
end
-function ChangeFlags:do_change(marker)
+function ChangeFlags:on_trigger(triggerer, marker, ev)
local did_change1 = false
local did_change2 = false
local silent = self.msg and self.msg ~= ""
- if self.trigger then
- self.trigger(marker)
- end
-
if self.props.flag_group and self.props.flag_group ~= "" then
local num_markers = dgn.num_matching_markers("flag_group",
self.props.group)
@@ -115,6 +107,8 @@ function ChangeFlags:do_change(marker)
did_change2 = dgn.change_branch_flags(self.branch_flags, silent)
end
+ self:remove(marker)
+
if did_change1 or did_change2 then
if self.msg and self.msg ~= "" then
crawl.mpr(self.smg)
@@ -131,19 +125,22 @@ function ChangeFlags:property(marker, pname)
end
function ChangeFlags:write(marker, th)
+ ChangeFlags.super.write(self, marker, th)
+
file.marshall(th, self.level_flags)
file.marshall(th, self.branch_flags)
file.marshall(th, self.msg)
- file.marshall_meta(th, self.trigger)
lmark.marshall_table(th, self.props)
end
function ChangeFlags:read(marker, th)
+ ChangeFlags.super.read(self, marker, th)
+
self.level_flags = file.unmarshall_string(th)
self.branch_flags = file.unmarshall_string(th)
self.msg = file.unmarshall_string(th)
- self.trigger = file.unmarshall_meta(th)
self.props = lmark.unmarshall_table(th)
+
setmetatable(self, ChangeFlags)
return self
@@ -152,214 +149,56 @@ end
--------------------------------------------------------------------------
--------------------------------------------------------------------------
-MonDiesChangeFlags = ChangeFlags:_new()
-MonDiesChangeFlags.__index = MonDiesChangeFlags
-
-function MonDiesChangeFlags:_new(pars)
- local mdcf
-
- if pars then
- mdcf = ChangeFlags:new(pars)
- else
- mdcf = ChangeFlags:_new()
- end
-
- setmetatable(mdcf, self)
- self.__index = self
-
- return mdcf
-end
-
-function MonDiesChangeFlags:new(pars)
- pars = pars or { }
-
- if not pars.mon_name then
- error("No monster name provided.")
- end
-
- local mdcf = self:_new(pars)
- mdcf.mon_name = pars.mon_name
-
- return mdcf
-end
-
-function MonDiesChangeFlags:activate(marker)
- dgn.register_listener(dgn.dgn_event_type('monster_dies'), marker)
-end
-
-function MonDiesChangeFlags:event(marker, ev)
- local midx = ev:arg1()
- local mons = dgn.mons_from_index(midx)
-
- if not mons then
- error("MonDiesChangeFlags:event() didn't get a valid monster index")
- end
-
- if mons.name == self.mon_name then
- ChangeFlags.do_change(self, marker)
- dgn.remove_listener(marker)
- dgn.remove_marker(marker)
- end
-end
-
-function MonDiesChangeFlags:write(marker, th)
- ChangeFlags.write(self, marker, th)
- file.marshall(th, self.mon_name)
-end
-
-function MonDiesChangeFlags:read(marker, th)
- ChangeFlags.read(self, marker, th)
- self.mon_name = file.unmarshall_string(th)
- setmetatable(self, MonDiesChangeFlags)
-
- return self
-end
-
function mons_dies_change_flags(pars)
- return MonDiesChangeFlags:new(pars)
-end
-
------------------------------------------------------------------------------
------------------------------------------------------------------------------
-FeatChangeChangeFlags = ChangeFlags:_new()
-FeatChangeChangeFlags.__index = FeatChangeChangeFlags
-
-function FeatChangeChangeFlags:_new(pars)
- local fccf
-
- if pars then
- fccf = ChangeFlags:new(pars)
- else
- fccf = ChangeFlags:_new()
- end
-
- setmetatable(fccf, self)
- self.__index = self
-
- return fccf
-end
-
-function FeatChangeChangeFlags:new(pars)
- pars = pars or { }
-
- local fccf = self:_new(pars)
-
- fccf.final_feat = pars.final_feat
-
- return fccf
-end
-
-function FeatChangeChangeFlags:activate(marker)
- dgn.register_listener(dgn.dgn_event_type('feat_change'), marker,
- marker:pos())
-end
-
-function FeatChangeChangeFlags:event(marker, ev)
- if self.final_feat and self.final_feat ~= "" then
- local feat = dgn.feature_name(dgn.grid(ev:pos()))
- if not string.find(feat, self.final_feat) then
- return
- end
- end
+ local mon_name = pars.mon_name or pars.target
- ChangeFlags.do_change(self, marker)
- dgn.remove_listener(marker, marker:pos())
- dgn.remove_marker(marker)
-end
+ pars.mon_name = nil
+ pars.target = nil
-function FeatChangeChangeFlags:write(marker, th)
- ChangeFlags.write(self, marker, th)
- file.marshall(th, self.final_feat)
-end
+ local cf = ChangeFlags:new(pars)
-function FeatChangeChangeFlags:read(marker, th)
- ChangeFlags.read(self, marker, th)
- self.final_feat = file.unmarshall_string(th)
- setmetatable(self, FeatChangeChangeFlags)
+ cf:add_triggerer(
+ DgnTriggerer:new {
+ type = "monster_dies",
+ target = mon_name
+ }
+ )
- return self
+ return cf
end
function feat_change_change_flags(pars)
- return FeatChangeChangeFlags:new(pars)
-end
+ local final_feat = pars.final_feat or pars.target
---------------------------------------------------------------------------
---------------------------------------------------------------------------
+ pars.final_feat = nil
+ pars.target = nil
-ItemPickupChangeFlags = ChangeFlags:_new()
-ItemPickupChangeFlags.__index = ItemPickupChangeFlags
+ local cf = ChangeFlags:new(pars)
-function ItemPickupChangeFlags:_new(pars)
- local ipcf
+ cf:add_triggerer(
+ DgnTriggerer:new {
+ type = "feat_change",
+ target = final_feat
+ }
+ )
- if pars then
- ipcf = ChangeFlags:new(pars)
- else
- ipcf = ChangeFlags:_new()
- end
-
- setmetatable(ipcf, self)
- self.__index = self
-
- return ipcf
-end
-
-function ItemPickupChangeFlags:new(pars)
- pars = pars or { }
-
- if not pars.item then
- error("No item name provided.")
- end
-
- local ipcf = self:_new(pars)
- ipcf.item = pars.item
-
- return ipcf
-end
-
-function ItemPickupChangeFlags:activate(marker)
- dgn.register_listener(dgn.dgn_event_type('item_pickup'), marker,
- marker:pos())
- dgn.register_listener(dgn.dgn_event_type('item_moved'), marker,
- marker:pos())
+ return cf
end
-function ItemPickupChangeFlags:event(marker, ev)
- local obj_idx = ev:arg1()
- local it = dgn.item_from_index(obj_idx)
-
- if not it then
- error("ItemPickupChangeFlags:event() didn't get a valid item index")
- end
-
- if item.name(it) == self.item then
- local picked_up = ev:type() == dgn.dgn_event_type('item_pickup')
- if picked_up then
- ChangeFlags.do_change(self, marker)
- dgn.remove_listener(marker, marker:pos())
- dgn.remove_marker(marker)
- else
- dgn.remove_listener(marker, marker:pos())
- marker:move(ev:dest())
- self:activate(marker)
- end
- end
-end
+function item_pickup_change_flags(pars)
+ local item = pars.item or pars.target
-function ItemPickupChangeFlags:write(marker, th)
- ChangeFlags.write(self, marker, th)
- file.marshall(th, self.item)
-end
+ pars.item = nil
+ pars.target = nil
-function ItemPickupChangeFlags:read(marker, th)
- ChangeFlags.read(self, marker, th)
- self.item = file.unmarshall_string(th)
- setmetatable(self, ItemPickupChangeFlags)
+ local cf = ChangeFlags:new(pars)
- return self
-end
+ cf:add_triggerer(
+ DgnTriggerer:new {
+ type = "item_pickup",
+ target = item
+ }
+ )
-function item_pickup_change_flags(pars)
- return ItemPickupChangeFlags:new(pars)
+ return cf
end
diff --git a/crawl-ref/source/dat/clua/lm_monst.lua b/crawl-ref/source/dat/clua/lm_monst.lua
index f26e73e89b..539b7b140b 100644
--- a/crawl-ref/source/dat/clua/lm_monst.lua
+++ b/crawl-ref/source/dat/clua/lm_monst.lua
@@ -3,11 +3,12 @@
-- Create a monster on certain events
--------------------------------------------------------------------------
---------------------------------------------------------------------------------------
--- This marker creates a monster on certain events. It uses the following parameters:
+-------------------------------------------------------------------------------
+-- This marker creates a monster on certain events. It uses the following
+-- parameters:
--
--- * death_monster: The name of the monster who's death triggers the creation of
--- the new monster.
+-- * death_monster: The name of the monster who's death triggers the creation
+-- of the new monster.
--
-- * new_monster: The name of the monster to create.
--
@@ -20,30 +21,21 @@
--
-- NOTE: If the feature where the marker is isn't habitable by the new monster,
-- the feature will be changed to the monster's primary habitat.
---------------------------------------------------------------------------------------
+-------------------------------------------------------------------------------
-- TODO:
-- * Place more than one monster.
-- * Place monster displaced from marker.
+-- * Be triggered more than once.
-MonsterOnTrigger = { CLASS = "MonsterOnTrigger" }
-MonsterOnTrigger.__index = MonsterOnTrigger
+require('clua/lm_trig.lua')
-function MonsterOnTrigger:_new()
- local pm = { }
- setmetatable(pm, self)
- self.__index = self
-
- return pm
-end
+MonsterOnTrigger = util.subclass(Triggerable)
+MonsterOnTrigger.CLASS = "MonsterOnTrigger"
function MonsterOnTrigger:new(pars)
pars = pars or { }
- if not pars.death_monster or pars.death_monster == "" then
- error("Must provide death_monster")
- end
-
if not pars.new_monster or pars.new_monster == "" then
error("Must provide new_monster")
end
@@ -51,14 +43,15 @@ function MonsterOnTrigger:new(pars)
pars.message_seen = pars.message_seen or pars.message or ""
pars.message_unseen = pars.message_unseen or pars.message or ""
- local pm = self:_new()
- pm.message_seen = pars.message_seen
- pm.message_unseen = pars.message_unseen
- pm.death_monster = pars.death_monster
- pm.new_monster = pars.new_monster
- pm.props = pars
+ local mot = self.super.new(self)
- return pm
+ mot.message_seen = pars.message_seen
+ mot.message_unseen = pars.message_unseen
+ mot.death_monster = pars.death_monster
+ mot.new_monster = pars.new_monster
+ mot.props = pars
+
+ return mot
end
function MonsterOnTrigger:property(marker, pname)
@@ -66,53 +59,71 @@ function MonsterOnTrigger:property(marker, pname)
end
function MonsterOnTrigger:write(marker, th)
- lmark.marshall_table(th, self)
+ MonsterOnTrigger.super.write(self, marker, th)
+
+ file.marshall(th, self.message_seen)
+ file.marshall(th, self.message_unseen)
+ file.marshall(th, self.new_monster)
+ lmark.marshall_table(th, self.props)
end
function MonsterOnTrigger:read(marker, th)
- return MonsterOnTrigger:new(lmark.unmarshall_table(th))
-end
+ MonsterOnTrigger.super.read(self, marker, th)
+
+ self.message_seen = file.unmarshall_string(th)
+ self.message_unseen = file.unmarshall_string(th)
+ self.new_monster = file.unmarshall_string(th)
+ self.props = lmark.unmarshall_table(th)
-function MonsterOnTrigger:activate(marker)
- dgn.register_listener(dgn.dgn_event_type('monster_dies'), marker)
+ setmetatable(self, MonsterOnTrigger)
+
+ return self
end
-function MonsterOnTrigger:event(marker, ev)
- local midx = ev:arg1()
- local mons = dgn.mons_from_index(midx)
+function MonsterOnTrigger:on_trigger(triggerer, marker, ev)
+ local x, y = marker:pos()
+ local you_x, you_y = you.pos()
+
+ if x == you_x and y == you_y then
+ return
+ end
- if not mons then
- error("MonsterOnTrigger:event() didn't get a valid monster index")
+ if dgn.mons_at(x, y) then
+ return
end
- if mons.name == self.death_monster then
- local x, y = marker:pos()
- local you_x, you_y = you.pos()
-
- if x == you_x and y == you_y then
- return
- end
-
- -- you.losight() seems to be necessary if the player has been moved by
- -- a wizard command and then the marker triggered by another wizard
- -- command, since then no turns will have been taken and the LOS info
- -- won't have been updated.
- you.losight()
- local see_cell = you.see_cell(x, y)
-
- if (not dgn.create_monster(x, y, self.new_monster)) then
- return
- elseif self.message_seen ~= "" and see_cell then
- crawl.mpr(self.message_seen)
- elseif self.message_unseen ~= "" and not see_cell then
- crawl.mpr(self.message_unseen)
- end
-
- dgn.remove_listener(marker)
- dgn.remove_marker(marker)
+ -- you.losight() seems to be necessary if the player has been moved by
+ -- a wizard command and then the marker triggered by another wizard
+ -- command, since then no turns will have been taken and the LOS info
+ -- won't have been updated.
+ you.losight()
+ local see_cell = you.see_cell(x, y)
+
+ if (not dgn.create_monster(x, y, self.new_monster)) then
+ return
+ elseif self.message_seen ~= "" and see_cell then
+ crawl.mpr(self.message_seen)
+ elseif self.message_unseen ~= "" and not see_cell then
+ crawl.mpr(self.message_unseen)
end
+
+ self:remove(marker)
end
-function monster_on_trigger(pars)
- return MonsterOnTrigger:new(pars)
+function monster_on_death(pars)
+ local death_monster = pars.death_monster or pars.target
+
+ pars.death_monster = nil
+ pars.target = nil
+
+ local mod = MonsterOnTrigger:new(pars)
+
+ mod:add_triggerer(
+ DgnTriggerer:new {
+ type = "monster_dies",
+ target = death_monster
+ }
+ )
+
+ return mod
end
diff --git a/crawl-ref/source/dat/clua/lm_trig.lua b/crawl-ref/source/dat/clua/lm_trig.lua
new file mode 100644
index 0000000000..f435fd2424
--- /dev/null
+++ b/crawl-ref/source/dat/clua/lm_trig.lua
@@ -0,0 +1,352 @@
+------------------------------------------------------------------------------
+-- dungeon.lua:
+-- DgnTriggerers and triggerables:
+--
+-- This is similar to the overvable/observer design pattern: a triggerable
+-- class which does something and a triggerrer which sets it off. As an
+-- example, the ChangeFlags class (clua/lm_flags.lua), rather than having
+-- three subclasses (for monster death, feature change and item pickup)
+-- needs no subclasses, but is just one class which is given triggerers
+-- which listen for different events. Additionally, new types of triggerers
+-- can be developed and used without have to update the ChangeFlags code.
+--
+-- Unlike with the overvable/observer design pattern, each triggerer is
+-- associated with a signle triggerable, rather than there being one observable
+-- and multiple observers, since each triggerer might have a data payload which
+-- is meant to be different for each triggerable.
+--
+-- A triggerable class just needs to subclass Triggerable and define an
+-- "on_trigger" method.
+------------------------------------------------------------------------------
+
+Triggerable = { CLASS = "Triggerable" }
+Triggerable.__index = Triggerable
+
+function Triggerable:new()
+ local tr = { }
+ setmetatable(tr, self)
+ self.__index = self
+
+ tr.triggerers = { }
+ tr.dgn_trigs_by_type = { }
+
+ return tr
+end
+
+function Triggerable:add_triggerer(triggerer)
+ if not triggerer.type then
+ error("triggerer has no type")
+ end
+
+ table.insert(self.triggerers, triggerer)
+
+ if (triggerer.method == "dgn_event") then
+ local et = dgn.dgn_event_type(triggerer.type)
+ if not self.dgn_trigs_by_type[et] then
+ self.dgn_trigs_by_type[et] = {}
+ end
+
+ table.insert(self.dgn_trigs_by_type[et], #self.triggerers)
+ else
+ local method = triggerer.method or "(nil)"
+
+ local class
+ local meta = getmetatable(triggerer)
+ if not meta then
+ class = "(no meta table)"
+ elseif not meta.CLASS then
+ class = "(no class name)"
+ end
+
+ error("Unknown triggerer method '" .. method .. "' for trigger class '"
+ .. class .. "'")
+ end
+
+ triggerer:added(self)
+end
+
+function Triggerable:move(marker, dest, y)
+ local was_activated = self.activated
+
+ self:remove_all_triggerers(marker)
+
+ -- XXX: Are coordinated passed around as single objects?
+ if y then
+ marker:move(dest, y)
+ else
+ marker:move(dest)
+ end
+
+ if was_activated then
+ self.activated = false
+ self:activate(marker)
+ end
+end
+
+function Triggerable:remove(marker)
+ if self.removed then
+ error("Trigerrable already removed")
+ end
+
+ self:remove_all_triggerers(marker)
+ dgn.remove_marker(marker)
+
+ self.removed = true
+end
+
+function Triggerable:remove_all_triggerers(marker)
+ for _, trig in ipairs(self.triggerers) do
+ trig:remove(self, marker)
+ end
+end
+
+function Triggerable:activate(marker)
+ if self.removed then
+ error("Can't activate, trigerrable removed")
+ end
+
+ if self.activating then
+ error("Triggerable already activating")
+ end
+
+ if self.activated then
+ error("Triggerable already activated")
+ end
+
+ self.activating = true
+ for _, trig in ipairs(self.triggerers) do
+ trig:activate(self, marker)
+ end
+ self.activating = false
+ self.activated = true
+end
+
+function Triggerable:event(marker, ev)
+ local et = ev:type()
+
+ local trig_list = self.dgn_trigs_by_type[et]
+
+ if not trig_list then
+ local class = getmetatable(self).CLASS
+ local x, y = marker:pos()
+ local e_type = dgn.dgn_event_type(et)
+
+ error("Triggerable type " .. class .. " at (" ..x .. ", " .. y .. ") " ..
+ "has no triggerers for dgn_event " .. e_type )
+ end
+
+ for _, trig_idx in ipairs(trig_list) do
+ self.triggerers[trig_idx]:event(self, marker, ev)
+
+ if self.removed then
+ return
+ end
+ end
+end
+
+function Triggerable:write(marker, th)
+ file.marshall(th, #self.triggerers)
+ for _, trig in ipairs(self.triggerers) do
+ -- We'll be handling the de-serialization of the triggerer, so we need to
+ -- save the class name.
+ file.marshall(th, getmetatable(trig).CLASS)
+ trig:write(marker, th)
+ end
+
+ lmark.marshall_table(th, self.dgn_trigs_by_type)
+end
+
+function Triggerable:read(marker, th)
+ self.triggerers = {}
+
+ local num_trigs = file.unmarshall_number(th)
+ for i = 1, num_trigs do
+ -- Hack to let triggerer classes de-serialize themselves.
+ local trig_class = file.unmarshall_string(th)
+
+ -- _G is the global symbol table, and the class name of the triggerer is
+ -- the name of that class's class object
+ local trig_table = _G[trig_class].read(nil, marker, th)
+ table.insert(self.triggerers, trig_table)
+ end
+
+ self.dgn_trigs_by_type = lmark.unmarshall_table(th)
+
+ setmetatable(self, Triggerable)
+ return self
+end
+
+-------------------------------------------------------------------------------
+-- NOTE: The CLASS string of a triggerer class *MUST* be exactly the same as
+-- the triggerer class name, or it won't be able to deserialize properly.
+--
+-- NOTE: A triggerer shouldn't store a reference to the triggerable it
+-- belongs to, and if it does then it must not save/restore that reference.
+-------------------------------------------------------------------------------
+
+-- DgnTriggerer listens for dungeon events of these types:
+--
+-- * monster_dies: Waits for a monster to die. Needs the parameter
+-- "target", who's value is the name of the monster who's death
+-- we're wating for. Doesn't matter where the triggerable/marker
+-- is placed.
+--
+-- * feat_change: Waits for a cell's feature to change. Accepts the
+-- optional parameter "target", which if set delays the trigger
+-- until the feature the cell turns into contains the target as a
+-- substring. The triggerable/marker must be placed on top of the
+-- cell who's feature you wish to monitor.
+--
+-- * item_moved: Wait for an item to move from one cell to another.
+-- Needs the parameter "target", who's value is the name of the
+-- item that is being tracked. The triggerable/marker must be placed
+-- on top of the cell containing the item.
+--
+-- * item_pickup: Wait for an item to be picked up. Needs the parameter
+-- "target", who's value is the name of the item that is being tracked.
+-- The triggerable/marker must be placed on top of the cell containing
+-- the item. Automatically takes care of the item moving from one
+-- square to another without being picked up.
+
+DgnTriggerer = { CLASS = "DgnTriggerer" }
+DgnTriggerer.__index = DgnTriggerer
+
+function DgnTriggerer:new(pars)
+ pars = pars or {}
+
+ if not pars.type then
+ error("DgnTriggerer must have a type")
+ end
+
+ if pars.type == "monster_dies" or pars.type == "item_moved"
+ or pars.type == "item_pickup"
+ then
+ if not pars.target then
+ error(pars.type .. " DgnTriggerer must have parameter 'target'")
+ end
+ end
+
+ local tr = util.copy_table(pars)
+ setmetatable(tr, self)
+ self.__index = self
+
+ tr:setup()
+
+ return tr
+end
+
+function DgnTriggerer:setup()
+ self.method = "dgn_event"
+end
+
+function DgnTriggerer:added(triggerable)
+ if self.type == "item_pickup" then
+ -- Automatically move the triggerable if the item we're watching is moved
+ -- before it it's picked up.
+ local mover = util.copy_table(self)
+
+ mover.type = "item_moved"
+ mover.marker_mover = true
+
+ triggerable:add_triggerer( DgnTriggerer:new(mover) )
+ end
+end
+
+function DgnTriggerer:activate(triggerable, marker)
+ if not (triggerable.activated or triggerable.activating) then
+ error("DgnTriggerer:activate(): triggerable is not activated")
+ end
+
+ local et = dgn.dgn_event_type(self.type)
+
+ if (dgn.dgn_event_is_position(et)) then
+ dgn.register_listener(et, marker, marker:pos())
+ else
+ dgn.register_listener(et, marker)
+ end
+end
+
+function DgnTriggerer:remove(triggerable, marker)
+ if not triggerable.activated then
+ return
+ end
+
+ local et = dgn.dgn_event_type(self.type)
+
+ if (dgn.dgn_event_is_position(et)) then
+ dgn.remove_listener(marker, marker:pos())
+ else
+ dgn.remove_listener(marker)
+ end
+end
+
+function DgnTriggerer:event(triggerable, marker, ev)
+ if self.type == "monster_dies" then
+ local midx = ev:arg1()
+ local mons = dgn.mons_from_index(midx)
+
+ if not mons then
+ error("DgnTriggerer:event() didn't get a valid monster index")
+ end
+
+ if mons.name == self.target then
+ triggerable:on_trigger(self, marker, ev)
+ end
+ elseif self.type == "feat_change" then
+ if self.target and self.target ~= "" then
+ local feat = dgn.feature_name(dgn.grid(ev:pos()))
+ if not string.find(feat, self.target) then
+ return
+ end
+ end
+ triggerable:on_trigger(self, marker, ev)
+ elseif self.type == "item_moved" then
+ local obj_idx = ev:arg1()
+ local it = dgn.item_from_index(obj_idx)
+
+ if not it then
+ error("DgnTriggerer:event() didn't get a valid item index")
+ end
+
+ if item.name(it) == self.target then
+ if self.marker_mover then
+ -- We only exist to move the triggerable if the item moves
+ triggerable:move(marker, ev:dest())
+ else
+ triggerable:on_trigger(self, marker, ev)
+ end
+ end
+ elseif self.type == "item_pickup" then
+ local obj_idx = ev:arg1()
+ local it = dgn.item_from_index(obj_idx)
+
+ if not it then
+ error("DgnTriggerer:event() didn't get a valid item index")
+ end
+
+ if item.name(it) == self.target then
+ triggerable:on_trigger(self, marker, ev)
+ end
+ else
+ local e_type = dgn.dgn_event_type(et)
+
+ error("DgnTriggerer can't handle events of type " .. e_type)
+ end
+end
+
+function DgnTriggerer:write(marker, th)
+ -- Will always be "dgn_event", so we don't need to save it.
+ self.method = nil
+
+ lmark.marshall_table(th, self)
+end
+
+function DgnTriggerer:read(marker, th)
+ local tr = lmark.unmarshall_table(th)
+
+ setmetatable(tr, DgnTriggerer)
+
+ tr:setup()
+
+ return tr
+end
diff --git a/crawl-ref/source/dat/clua/luamark.lua b/crawl-ref/source/dat/clua/luamark.lua
index 43ec7f94ae..d987e88ef1 100644
--- a/crawl-ref/source/dat/clua/luamark.lua
+++ b/crawl-ref/source/dat/clua/luamark.lua
@@ -3,6 +3,8 @@
-- Lua map marker handling.
------------------------------------------------------------------------------
+require('clua/lm_trig.lua')
+
require('clua/lm_pdesc.lua')
require('clua/lm_1way.lua')
require('clua/lm_timed.lua')
diff --git a/crawl-ref/source/dat/clua/util.lua b/crawl-ref/source/dat/clua/util.lua
index 4894f1fc67..1ed3670172 100644
--- a/crawl-ref/source/dat/clua/util.lua
+++ b/crawl-ref/source/dat/clua/util.lua
@@ -306,3 +306,23 @@ function table_to_string(table, depth)
return str
end
+
+-- Copied from http://lua-users.org/wiki/CopyTable
+function util.copy_table(object)
+ local lookup_table = {}
+ local function _copy(object)
+ if type(object) ~= "table" then
+ return object
+ elseif lookup_table[object] then
+ return lookup_table[object]
+ end
+ local new_table = {}
+ lookup_table[object] = new_table
+ for index, value in pairs(object) do
+ new_table[_copy(index)] = _copy(value)
+ end
+ return setmetatable(new_table, getmetatable(object))
+ end
+ return _copy(object)
+end
+