summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/dat/clua/lm_mslav.lua
blob: b601028d5ce6e2a062fe97d623f7fbefcdefd68c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
----------------------------------------------------------------------------
-- 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>)
-- 
-- (Some markers may already provide convenience functionality for the
--  synchronized_markers call, so check the relevant marker file.)
--
-- 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