------------------------------------------------------------------------------ -- lm_fog.lua: -- Fog machines. -- -- There are three different "pure" ways to use a fog machine marker: -- -- 1) Repeatedly lay down medium to large clouds on top of the marker -- and let them pile up on one another. (One of the cloud grids in -- the first laid cloud has to decay away before this is this really -- starts working. -- -- 2) Perform random walks from the marker and place a single-grid cloud -- at the destination of each walk. -- -- 3) Place a single-grid cloud on the marker and let it spread out. -- -- Combining these different methods, along with varying the different -- parameters, can be used to achieve different effects. -- -- Fog machines can be "chained" together (activated at the same time) by using -- the convenience function "chained_fog_machine" with the normal paramaters -- which are accepted by "fog_machine". -- -- Marker parameters: -- -- cloud_type: The name of the cloud type to use. Possible cloud types are: -- flame, noxious fumes, freezing vapour, poison gases, -- grey smoke, blue smoke, translocational energy, steam, rain, -- foul pestilence, black smoke, mutagenic fog, thin mist (the default). -- walk_dist: The distance to move over the course of one random walk. -- defaults to 0. -- pow_min: The minimum "power" (lifetime) of each cloud; defaults to 1. -- pow_max: The maximum power of each cloud; must be provided. -- pow_rolls: The number of rolls of [pow_min, pow_max], with the average -- value uses; increasing the values makes the average value more likely -- and extreme values less likely. Defaults to 1. -- delay, delay_min and delay_max: The delay between laying down one cloud -- and the next. 10 is equal to normal-speed player turn. Either -- delay or delay_max and delay_min must be provided. Providing just -- "delay" is equivalent to delay_min and delay_max being equal. -- size, size_min and size_max: The number of grids each cloud will cover. -- Either size or size_max and size_min must be provided. Providing -- just "size" is equivalent to size_min and size_max being equal. -- size_buildup_amnt, size_buildup_time: Increase the cloud size over time. -- Adds (size_buildup_amnt / size_buildup_time * turns_since_made) -- to size_min and size_max, maxing out at size_buildup_amnt. -- spread_rate: The rate at which a cloud spreads. Must either be -- -1 (default spread rate that varies by cloud type) or between -- 0 and 100 inclusive. -- spread_rate_amnt, spread_rate_buildup: Similar to size_buildup_amnt -- and size_buildup_time -- 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. -- colour: A string value with which to recolour the cloud. -- name: A string value with which to rebrand (specifically, rename) the -- cloud in question. -- tile: A string value with which to retile the cloud. -- listener: A table with a function field called 'func'. Will be called -- whenever the countdown is activated, and whenever the fog -- machine is reset. It will be called with a reference to the table, -- the fog machine, the Triggerer which was the cause, the marker, -- and the vevent which was the cause. -- ------------------------------------------------------------------------------ require('clua/lm_trig.lua') FogMachine = util.subclass(Triggerable) FogMachine.CLASS = "FogMachine" function FogMachine:_new() local m = self.super.new(self) setmetatable(m, self) self.__index = self return m end function FogMachine:new(pars) if not pars then error("No parameters provided") end if not pars.pow_max then error("No pow_max provided.") end if not (pars.delay or (pars.delay_min and pars.delay_max)) then error("Delay parameters not provided.") end if not (pars.size or (pars.size_min and pars.size_max)) then error("Size parameters not provided.") end local m = FogMachine:_new() m.cloud_type = pars.cloud_type or "thin mist" m.walk_dist = pars.walk_dist or 0 m.pow_min = pars.pow_min or 1 m.pow_max = pars.pow_max m.pow_rolls = pars.pow_rolls or 3 m.kill_cat = pars.kill_cat or "other" m.size_min = pars.size_min or pars.size or 1 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.colour = pars.colour or "" m.name = pars.name or "" m.tile = pars.tile or "" m.size_buildup_amnt = pars.size_buildup_amnt or 0 m.size_buildup_time = pars.size_buildup_time or 1 m.spread_buildup_amnt = pars.spread_buildup_amnt or 0 m.spread_buildup_time = pars.spread_buildup_time or 1 m.buildup_turns = 0 local tick_pars = {} tick_pars.delay_min = pars.delay_min or pars.delay or 1 tick_pars.delay_max = pars.delay_max or pars.delay tick_pars.type = "turn" m:add_triggerer( DgnTriggerer:new (tick_pars) ) m:add_triggerer( DgnTriggerer:new { type = "entered_level" } ) if pars.listener then m:add_listener(pars.listener) end return m end function FogMachine:apply_cloud(point, pow_min, pow_max, pow_rolls, size, cloud_type, kill_cat, spread, colour, name, tile) dgn.apply_area_cloud(point.x, point.y, pow_min, pow_max, pow_rolls, size, cloud_type, kill_cat, spread, colour, name, tile) end function FogMachine:do_fog(point) local p = point if self.walk_dist > 0 then p = dgn.point(dgn.random_walk(p.x, p.y, self.walk_dist)) end local buildup_turns = self.buildup_turns -- Size buildup if buildup_turns > self.size_buildup_time then buildup_turns = self.size_buildup_time end local size_buildup = self.size_buildup_amnt * buildup_turns / self.size_buildup_time local size_min = self.size_min + size_buildup local size_max = self.size_max + size_buildup if (size_min < 0) then size_min = 0 end -- Spread buildup buildup_turns = self.buildup_turns if buildup_turns > self.spread_buildup_time then buildup_turns = self.spread_buildup_time end local spread = self.spread_rate + (self.spread_buildup_amnt * buildup_turns / self.spread_buildup_time) 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, self.colour, self.name, self.tile) end function FogMachine:do_trigger(triggerer, marker, ev) -- Override do_trigger for things that we want to do only once if -- there's multiple markers slaved to this one. if triggerer.type == "turn" then self.buildup_turns = self.buildup_turns + ev:ticks() if (self.buildup_turns > self.size_buildup_time) then self.buildup_turns = self.size_buildup_time end elseif triggerer.type == "entered_level" then local et = dgn.dgn_event_type("turn") for _, trig_idx in ipairs(self.dgn_trigs_by_type[et]) do local trig = self.triggerers[trig_idx] trig:reset_countdown() end end -- Turns passing without reaching the countdown shouldn't generate -- fog. if triggerer.type == "turn" and triggerer.sub_type ~= "countdown" then triggerer.listener_only = true else triggerer.listener_only = false end -- This will call on_trigger() for all the slaves. FogMachine.super.do_trigger(self, triggerer, marker, ev) end function FogMachine:on_trigger(triggerer, marker, ev) if triggerer.type == 'turn' then self:do_fog(dgn.point(marker:pos())) elseif triggerer.type == 'entered_level' then for i = 1, self.start_clouds do self:do_fog(dgn.point(marker:pos())) end end end function FogMachine:write(marker, th) FogMachine.super.write(self, marker, th) file.marshall(th, self.cloud_type) file.marshall(th, self.walk_dist) file.marshall(th, self.pow_min) file.marshall(th, self.pow_max) file.marshall(th, self.pow_rolls) file.marshall(th, self.kill_cat) file.marshall(th, self.size_min) file.marshall(th, self.size_max) file.marshall(th, self.spread_rate) file.marshall(th, self.start_clouds) file.marshall(th, self.size_buildup_amnt) file.marshall(th, self.size_buildup_time) file.marshall(th, self.spread_buildup_amnt) file.marshall(th, self.spread_buildup_time) file.marshall(th, self.buildup_turns) file.marshall(th, self.colour) file.marshall(th, self.name) file.marshall(th, self.tile) end function FogMachine:read(marker, th) FogMachine.super.read(self, marker, th) self.cloud_type = file.unmarshall_string(th) self.walk_dist = file.unmarshall_number(th) self.pow_min = file.unmarshall_number(th) self.pow_max = file.unmarshall_number(th) self.pow_rolls = file.unmarshall_number(th) self.kill_cat = file.unmarshall_string(th) self.size_min = file.unmarshall_number(th) self.size_max = file.unmarshall_number(th) self.spread_rate = file.unmarshall_number(th) self.start_clouds = file.unmarshall_number(th) self.size_buildup_amnt = file.unmarshall_number(th) self.size_buildup_time = file.unmarshall_number(th) self.spread_buildup_amnt = file.unmarshall_number(th) self.spread_buildup_time = file.unmarshall_number(th) self.buildup_turns = file.unmarshall_number(th) self.colour = file.unmarshall_string(th) self.name = file.unmarshall_string(th) self.tile = file.unmarshall_string(th) setmetatable(self, FogMachine) return self end function fog_machine(pars) return FogMachine:new(pars) end function chained_fog_machine(pars) return Triggerable.synchronized_markers(FogMachine:new(pars)) end function fog_machine_geyser(cloud_type, size, power, buildup_amnt, buildup_time) return FogMachine:new { cloud_type = cloud_type, pow_max = power, size = size, delay_min = power , delay_max = power * 2, size_buildup_amnt = buildup_amnt or 0, size_buildup_time = buildup_time or 1 } end function fog_machine_spread(cloud_type, size, power, buildup_amnt, buildup_time) return FogMachine:new { cloud_type = cloud_type, pow_max = power, spread_rate = size, size = 1, delay_min = 5, delay_max = 15, spread_buildup_amnt = buildup_amnt or 0, spread_buildup_time = buildup_time or 1 } end function fog_machine_brownian(cloud_type, size, power) return FogMachine:new { cloud_type = cloud_type, size = 1, pow_max = power, walk_dist = size, delay_min = 1, delay_max = power / size } 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. -- -- * 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. -- -- 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, see_func) if trns == nil or (see_mesg == nil and cantsee_mesg == nil) then error("WarningMachine requires turns and message!") end local function warning_func (self, fog_machine, triggerer, marker, ev) local countdown = triggerer.countdown local point = dgn.point(marker:pos()) if triggerer.type == "turn" and triggerer.sub_type == "tick" and countdown <= self.turns then if not self.warning_done then if self.see_message and self.see_function(point.x, point.y) then crawl.mpr(self.see_message, "warning") elseif self.cantsee_message then crawl.mpr(self.cantsee_message, "warning") end self.warning_done = true end elseif event_name == "trigger" then self.warning_done = false end end if not see_func then see_func = you.see_cell end pars = { see_message = see_mesg, cantsee_message = cantsee_mesg, turns = trns * 10, warning_done = false, see_function = see_func } pars.func = warning_func return pars end function trigger_machine (cantsee_mesg, see_mesg, chan, see_func) if see_mesg == nil and cantsee_mesg == nil then error("Triggermachine requires a message!") end local function trigger_func (self, fog_machine, triggerer, marker, ev) local point = dgn.point(marker:pos()) if triggerer.type == "turn" and triggerer.sub_type == "countdown" then channel = self.channel or "" if self.see_message ~= nil and self.see_function(point.x, point.y) then crawl.mpr(self.see_message, channel) elseif self.cantsee_message ~= nil then crawl.mpr(self.cantsee_message, channel) end end end if not see_func then see_func = you.see_cell end pars = { channel = chan or nil, see_message = see_mesg, cantsee_message = cantsee_mesg, see_function = see_func } pars.func = trigger_func return pars end function tw_machine (warn_turns, warn_cantsee_message, trig_cantsee_message, trig_channel, trig_see_message, warn_see_message, see_func) 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 (self, fog_machine, triggerer, marker, ev) local point = dgn.point(marker:pos()) local countdown = triggerer.countdown if triggerer.type == "turn" and triggerer.sub_type == "tick" and countdown <= self.warning_turns then if self.warning_done ~= true then if self.warning_see_message and self.see_function(point.x, point.y) then crawl.mpr(self.warning_see_message, "warning") elseif self.warning_cantsee_message ~= nil then crawl.mpr(self.warning_cantsee_message, "warning") end self.warning_done = true end elseif triggerer.type == "turn" and triggerer.sub_type == "countdown" then self.warning_done = false channel = self.trigger_channel or "" if self.trigger_see_message and self.see_function(point.x, point.y) then crawl.mpr(self.trigger_see_message, channel) elseif self.trigger_cantsee_message ~= nil then crawl.mpr(self.trigger_cantsee_message, channel) end end end if not see_func then see_func = you.see_cell end pars = { 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, see_function = see_func } pars.func = tw_func return pars end