SNKRX/engine/game/trigger.lua

280 lines
12 KiB
Lua

-- The base Trigger class.
-- A global instance of this called "trigger" is available by default.
Trigger = Object:extend()
function Trigger:init()
self.triggers = {}
end
-- Calls the action every frame until it's cancelled via trigger:cancel.
-- The tag must be passed in otherwise there will be no way to stop this from running.
-- If after is passed in then it is called after the run is cancelled.
function Trigger:run(action, after, tag)
local tag = tag or random:uid()
local after = after or function() end
self.triggers[tag] = {type = "run", timer = 0, after = after, action = action}
end
-- Calls the action after delay seconds.
-- Or calls the action after the condition is true.
-- If tag is passed in then any other trigger actions with the same tag are automatically cancelled.
-- trigger:after(2, function() print(1) end) -> prints 1 after 2 seconds
-- trigger:after(function() return self.should_print_1 end, function() print(1) end) -> prints 1 after self.should_print_1 is set to true
function Trigger:after(delay, action, tag)
local tag = tag or random:uid()
if type(delay) == "number" or type(delay) == "table" then
self.triggers[tag] = {type = "after", timer = 0, unresolved_delay = delay, delay = self:resolve_delay(delay), action = action}
else
self.triggers[tag] = {type = "conditional_after", condition = delay, action = action}
end
end
-- Calls the action every delay seconds if the condition is true.
-- If the condition isn't true when delay seconds are up then it waits and only performs the action and resets the timer when that happens.
-- If times is passed in then it only calls action for that amount of times.
-- If after is passed in then it is called after the last time action is called.
-- If tag is passed in then any other trigger actions with the same tag are automatically cancelled.
-- trigger:cooldown(2, function() return #self:get_objects_in_shape(self.attack_sensor, enemies) > 0 end, function() self:attack() end) -> only attacks when 2 seconds have passed and there are more than 0 enemies around
function Trigger:cooldown(delay, condition, action, times, after, tag)
local times = times or 0
local after = after or function() end
local tag = tag or random:uid()
self.triggers[tag] = {type = "cooldown", timer = 0, unresolved_delay = delay, delay = self:resolve_delay(delay), condition = condition, action = action, times = times, max_times = times, after = after, multiplier = 1}
end
-- Calls the action every delay seconds.
-- Or calls the action once every time the condition becomes true.
-- If times is passed in then it only calls action for that amount of times.
-- If after is passed in then it is called after the last time action is called.
-- If tag is passed in then any other trigger actions with the same tag are automatically cancelled.
-- trigger:every(2, function() print(1) end) -> prints 1 every 2 seconds
-- trigger:every(2, function() print(1) end, 5, function() print(2) end) -> prints 1 every 2 seconds 5 times, and then prints 2
-- trigger:every(function() return player.hit end, function() print(1) end) -> prints 1 every time the player is hit
-- trigger:every(function() return player.grounded end, function() print(1), 5, function() print(2) end) -> prints 1 every time the player becomes grounded 5 times, and then prints 2
-- Note that if using this as a condition, the action will only be triggered when the condition jumps from being false to true.
-- If the condition remains true for multiple frames then the action won't be triggered further, unless it becomes false and then becomes true again.
function Trigger:every(delay, action, times, after, tag)
local times = times or 0
local after = after or function() end
local tag = tag or random:uid()
if type(delay) == "number" or type(delay) == "table" then
self.triggers[tag] = {type = "every", timer = 0, unresolved_delay = delay, delay = self:resolve_delay(delay), action = action, times = times, max_times = times, after = after, multiplier = 1}
else
self.triggers[tag] = {type = "conditional_every", condition = delay, last_condition = false, action = action, times = times, max_times = times, after = after}
end
end
-- Same as every except the action is called immediately when this function is called, and then every delay seconds.
function Trigger:every_immediate(delay, action, times, after, tag)
local times = times or 0
local after = after or function() end
local tag = tag or random:uid()
self.triggers[tag] = {type = "every", timer = 0, unresolved_delay = delay, delay = self:resolve_delay(delay), action = action, times = times, max_times = times, after = after, multiplier = 1}
action()
end
-- Calls the action every frame for delay seconds.
-- Or calls the action every frame the condition is true.
-- If after is passed in then it is called after the duration ends or after the condition becomes false.
-- If tag is passed in then any other trigger actions with the same tag are automatically cancelled.
-- trigger:during(5, function() print(random:float(0, 100)) end)
-- trigger:during(function() return self.should_print_random_float end, function() print(random:float(0, 100)) end) -> prints the random float as long as self.should_print_random_float is true
function Trigger:during(delay, action, after, tag)
local after = after or function() end
local tag = tag or random:uid()
if type(delay) == "number" or type(delay) == "table" then
self.triggers[tag] = {type = "during", timer = 0, unresolved_delay = delay, delay = self:resolve_delay(delay), action = action, after = after}
elseif type(delay) == "function" then
self.triggers[tag] = {type = "conditional_during", condition = delay, last_condition = false, action = action, after = after}
end
end
-- Tweens the target's values specified by the source table for delay seconds using the given tweening method.
-- All tween methods can be found in the math/math file.
-- If after is passed in then it is called after the duration ends.
-- If tag is passed in then any other trigger actions with the same tag are automatically cancelled.
-- trigger:tween(0.2, self, {sx = 0, sy = 0}, math.linear) -> tweens this object's scale variables to 0 linearly over 0.2 seconds
-- trigger:tween(0.2, self, {sx = 0, sy = 0}, math.linear, function() self.dead = true end) -> tweens this object's scale variables to 0 linearly over 0.2 seconds and then kills it
function Trigger:tween(delay, target, source, method, after, tag)
local method = method or math.linear
local after = after or function() end
local tag = tag or random:uid()
local initial_values = {}
for k, _ in pairs(source) do initial_values[k] = target[k] end
self.triggers[tag] = {type = "tween", timer = 0, unresolved_delay = delay, delay = self:resolve_delay(delay), target = target, initial_values = initial_values, source = source, method = method, after = after}
end
-- Cancels a trigger action based on its tag.
-- This is automatically called if repeated tags are given to trigger actions.
function Trigger:cancel(tag)
if self.triggers[tag] and self.triggers[tag].type == "run" then
self.triggers[tag].after()
end
self.triggers[tag] = nil
end
-- Resets the timer for a tag.
-- Useful when you need to start counting that tag from 0 after an event happens.
function Trigger:reset(tag)
self.triggers[tag].timer = 0
end
-- Returns the delay of a given tag.
-- This is useful when delays are set randomly (trigger:every({2, 4}, ...) would set the delay at a random number between 2 and 4) and you need to know what the value chosen was.
function Trigger:get_delay(tag)
return self.triggers[tag].delay
end
-- Returns the current iteration of an every trigger action with the given tag.
-- Useful if you need to know that its the nth time an every action has been called.
function Trigger:get_every_iteration(tag)
return self.triggers[tag].max_times - self.triggers[tag].times
end
-- Sets a multiplier for an every tag.
-- This is useful when you need the event to happen in a varying interval, like based on the player's attack speed, which might change every frame based on buffs.
-- Call this on the update function with the appropriate multiplier.
function Trigger:set_every_multiplier(tag, multiplier)
if not self.triggers[tag] then return end
self.triggers[tag].multiplier = multiplier or 1
end
function Trigger:get_every_multiplier(tag)
if not self.triggers[tag] then return end
return self.triggers[tag].multiplier
end
-- Returns the elapsed time of a given trigger as a number between 0 and 1.
-- Useful if you need to know where you currently are in the duration of a during call.
function Trigger:get_during_elapsed_time(tag)
if not self.triggers[tag] then return end
return self.triggers[tag].timer/self.triggers[tag].delay
end
function Trigger:get_timer_and_delay(tag)
if not self.triggers[tag] then return end
return self.triggers[tag].timer, self.triggers[tag].delay
end
function Trigger:resolve_delay(delay)
if type(delay) == "table" then
return random:float(delay[1], delay[2])
else
return delay
end
end
function Trigger:destroy()
self.triggers = nil
end
function Trigger:update(dt)
for tag, trigger in pairs(self.triggers) do
if trigger.timer then
trigger.timer = trigger.timer + dt
end
if trigger.type == "run" then
trigger.action()
elseif trigger.type == "cooldown" then
if trigger.timer > trigger.delay*trigger.multiplier and trigger.condition() then
trigger.action()
trigger.timer = 0
trigger.delay = self:resolve_delay(trigger.unresolved_delay)
if trigger.times > 0 then
trigger.times = trigger.times - 1
if trigger.times <= 0 then
trigger.after()
self.triggers[tag] = nil
end
end
end
elseif trigger.type == "after" then
if trigger.timer > trigger.delay then
trigger.action()
self.triggers[tag] = nil
end
elseif trigger.type == "conditional_after" then
if trigger.condition() then
trigger.action()
self.triggers[tag] = nil
end
elseif trigger.type == "every" then
if trigger.timer > trigger.delay*trigger.multiplier then
trigger.action()
trigger.timer = trigger.timer - trigger.delay*trigger.multiplier
trigger.delay = self:resolve_delay(trigger.unresolved_delay)
if trigger.times > 0 then
trigger.times = trigger.times - 1
if trigger.times <= 0 then
trigger.after()
self.triggers[tag] = nil
end
end
end
elseif trigger.type == "conditional_every" then
local condition = trigger.condition()
if condition and not trigger.last_condition then
trigger.action()
if trigger.times > 0 then
trigger.times = trigger.times - 1
if trigger.times <= 0 then
trigger.after()
self.triggers[tag] = nil
end
end
end
trigger.last_condition = condition
elseif trigger.type == "during" then
trigger.action(dt)
if trigger.timer > trigger.delay then
trigger.after()
self.triggers[tag] = nil
end
elseif trigger.type == "conditional_during" then
local condition = trigger.condition()
if condition then
trigger.action()
end
if trigger.last_condition and not condition then
trigger.after()
end
trigger.last_condition = condition
elseif trigger.type == "tween" then
local t = trigger.method(trigger.timer/trigger.delay)
for k, v in pairs(trigger.source) do
trigger.target[k] = math.lerp(t, trigger.initial_values[k], v)
end
if trigger.timer > trigger.delay then
trigger.after()
self.triggers[tag] = nil
end
end
end
end