Help with knowing when to call a function

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!

What I want to achieve is a good way to determine whether a function should be called or not, preferably while avoiding cluttering scripts with too many if statements.

  1. What is the issue? Include screenshots / videos if possible!

The issue is that my functions interfere with each other. The first triggers an automatic fire procedure, where an object is set on fire, the power goes out, emergency power & alarm & fire extinguishers turn on, which are then disabled and power restored. The issue is that while this function is running, another function that makes a blackout may run. Consequently, the first or the other takes each other out. One function creates a blackout, the other function restores power seconds after. Is there any good way to solve this?

Example:

local function fireAndAutomaticSupression()
   print("Start sequence of events")
   moduleScript.StartFire()
   moduleScript.Blackout()
   task.wait(3)
   moduleScript.EmergencyPower()
   moduleScript.EnableAlarm()
   moduleScript.EnableFireExtinguisher()
   task.wait(5)
   moduleScript.StopFire()
   task.wait(1)
   moduleScript.DisableFireExtinguisher()
   moduleScript.StopAlarm()
   moduleScript.RestorePower()
   print("Finish sequence of events")
end

This works well, until I add a new function that manipulates the same instances:

local function createBlackoutFire(part)
   moduleScript.StartFire()
   wait(5)
   moduleScript.StopAlarm()
   moduleScript.StopAllFireExtinguishers()
   moduleScript.Blackout()
end

  1. What solutions have you tried so far? Did you look for solutions on the Developer Hub?

I looked a bit on the developer hub, but I did not really find a clear answer. I think I might be able to implement a state machine, where the function checks if it is possible to turn on power, enable / stop fire extinguishers and alarm, et cetera. But then it might look something like:

local function createBlackoutFire(part)
 if canStartFire() then
  moduleScript.StartFire()
 end
   wait(5)
   if canStopAlarm() then
    moduleScript.StopAlarm()
   end
   if canStopFireExtinguishers() then
      moduleScript.StopAllFireExtinguishers()
   end
   if canBlackout() then
    moduleScript.Blackout()
   end
end

local function fireAndAutomaticSupression()
   print("Start sequence of events")
   if canStartFire() then
    moduleScript.StartFire()
   end
   if canBlackout() then
    moduleScript.Blackout()
   end
   task.wait(3)
   if canEmergencyPower() then
    moduleScript.EmergencyPower()
   end
   if canEnableAlarm() then
    moduleScript.EnableAlarm()
   end
   if canEnableFireExtinguisher() then
    moduleScript.EnableFireExtinguisher()
   end
   task.wait(5)
   if canStopFire() then
    moduleScript.StopFire()
   end
   task.wait(1)
   if canDisableFireExtinguisher() then
    moduleScript.DisableFireExtinguisher()
   end
   if canStopAlarm() then
    moduleScript.StopAlarm()
   end
   if canRestorePower() then
    moduleScript.RestorePower()
   end
   print("Finish sequence of events")
end

The latter if statement solution is like taking the lines of code and multiplicating it with 6 or more per function call. This solution therefore seems pretty inefficient and probably easily gets messy and buggy, so are there any other good alternatives? Or would a state machine work if I do it in a better way?

Any suggestions would be very helpful!

Here is my actual code I got right now, as the above was just an example, which I think is a bit unclear.

local function simulateAttackOnBase(player, facility, hallway, triggerPart)
	--Can run at any time (damage systems)
	powerInteractions.PlaceExplosives(player, triggerPart)
	powerInteractions.PlayPlaceExplosivesTimerSound(triggerPart)
	powerInteractions.ExplodeBomb(triggerPart)
	powerInteractions.AutoDamageHallway(hallway)
	powerInteractions.DisableAllLightsPermanent(facility)
	powerInteractions.ForcedPowerOutage(facility)
	coroutine.wrap(powerInteractions.StartFire)(facility)
	
	wait(10)
	
	--May not happen at any time for realism: (fighting systems), but it should eventually happen automatically ideally, so if it can't happen, for some reason, then how do I do it so that it runs later?
	
	--: Only if emergency power generators are turned on
	powerInteractions.EnableFromDisableAllLightsPermanent(facility)
	powerInteractions.TemporaryPower(facility)
	powerInteractions.AutomaticEmergencyPower(facility)
	wait(7)
	--Only if there is no damage to water pipes and water available
	powerInteractions.startPlayingFireEmergencyAlarm(facility)
	powerInteractions.EnableFireExtinguisher(facility)
	print("Wait 60 seconds before stopping fire.")
	wait(60)
	
	--may not happen at any time (reset systems)
	--Only if nothing else in the facility is causing fire
	powerInteractions.StopFire(facility)
	wait(5)
	powerInteractions.DisableFireExtinguisher(facility) 
	powerInteractions.stopPlayingFireEmergencyAlarm(facility) --only if it is nothing else that needs the alarm to sound.
	wait(10)
	powerInteractions.RestorePower(facility)	--only if there is sufficent power and reason
	wait(5)
	powerInteractions.AddRepairParts(facility) --only if there is no meltdown, no fire, no nothing.
	wait(10)
	triggerPart.ProximityPrompt.Enabled = true
end

for i, faci in pairs(facs) do
	for i, v in ipairs(faci:GetChildren()) do
		if v:FindFirstChild("Fire") then
			if v.Fire:FindFirstChild("SaboutagePromptPart") then
				v.Fire.SaboutagePromptPart.ProximityPrompt.Triggered:Connect(function(player)
					simulateAttackOnBase(player, faci, v, v.Fire.SaboutagePromptPart)
				end)
			end
		end
	end
end

A solution I would use is to implement a module that manages power, alarm and extinguisher and which you can ask to enable something for X seconds. When you ask it, it should internally set a timer and update it every RunService.Heartbeat. When timer reaches 0 it stops the event. If you ask it to enable something, but it’s timer is already running, it overwrites it, but only if new timer is longer.
As a result your function should look like this:

EventManager.StartFireForSeconds(8)
EventManager.BlackoutForSeconds(9)
task.wait(3)
EventManager.EnableEmergencyPowerForSeconds(6)
EventManager.EnableFireAlarmForSeconds(6)
EventManager.EnableExtinguisherForSeconds(6)

This is a bit nicer version:

EventManagerModule = {}

local EventFunctions = {
    Fire = {
        Start = function() --[[start fire]] end
        Stop = function() --[[stop fire]] end
    }
   --more events here
}

local Timers = {}

game:GetService("RunService").Heartbeat:Connect(function(deltaTime)
    for EventName, Time in pairs(Timers) do
        local NewTime = Time - dt
        if NewTime <= 0 then
            Timers[EventName] = nil
            local status, err = pcall(EventFunctions[EventName].Stop)
            if not status then warn(err) end
        else
            Timers[EventName] = NewTime
        end
    end
end)

EventManagerModule.StartEventForSeconds(EventName, Time)
    local CurrentTimer = Timers[EventName]
    if (not CurrentTimer) or (CurrentTimer < Time) then
        Timers[EventName] = Time
    end

    EventFunctions[EventName].Start()
end

return EventManagerModule
EventManager.StartEventForSeconds("Fire", 8)
EventManager.StartEventForSeconds("Blackout", 9)
task.wait(3)
EventManager.StartEventForSeconds("EmergencyPower", 6)
EventManager.StartEventForSeconds("FireAlarm", 6)
EventManager.StartEventForSeconds("Extinguisher", 6)