Aaaaaand, back to triggers again! This time let’s dig a little bit deeper. How about we make a trigger that is not really a trigger, but is kind of a trigger? What if we create “EmptyDetector” with createVehicle instead of createTrigger? Credit goes to SaMatra and Karel Moricky for nudging me towards this discovery.

So, createVehicle a trigger will indeed create a trigger, but it will not be fully functional trigger. For example, among multiple shortcomings, it is also not possible to make it activate via radio. However, it has one major advantage over normal trigger object – it is simulated every frame. The condition of the trigger is checked on each frame and the code placed in the condition is run in unscheduled environment.

Let’s review some of the available events that can run on each frame:

  • onEachFrame – runs every frame no ifs no buts. This is ultimate onEachFrame event that never skips a frame. It runs when game simulation is disabled (pause menu in SP or Alt+Tab). It even runs when you exit preview in editor! The problem here is that you can only have 1 onEachFrame and setting a new one will overwrite the old one. To overcome this limitation Neokika from Bohemia made a function to stack unstackable event handlers (BIS_fnc_addStackedEventHandler) where you can stack “onEachFrame” as well. What it does is it puts your code in a queue and each frame it executes the queue. BIS recommend to use this over onEachFrame so that mode makers can share onEachFrame if needed.
  • FSM Condition Check – tries to run code in FSM condition on each frame. However, it will start skipping frames if you overload the scripting engine with many scripts and some heavy lengthy executions.  How many frames it will skip depends on how much the engine is overloaded. The code in FSM condition will run from start to finish with no interruption or 3ms limit regardless of the load. Will not run when simulation is disabled or user Alt+Tabs in SP.
  • addAction Condition Check – tries to run code on each frame and cannot and will not be suspended, but as with FSM condition check it would also skip frames when overloaded, there is even less guarantee it will stick to each frame than with FSM. Will not run when simulation is disabled because of pause menu or Alt+Tab. Cannot be used on clients without interface, like server or HC.
  • “Draw3D” EH – runs on each frame and cannot and will not be suspend. Will not skip frames under load, however stops running when simulation is disabled in SP, user Alt+Tabs in SP and MP and when in-game map is brought up. Designed for HUD icon drawing, so will stop if HUD is not visible. Will also not run when client has no UI (server, HC). This EH has a bug, in MP it will continue running when user quits the mission.
  • “Draw” EH – runs on each frame and cannot and will not be suspended. Similar to “Draw3D”, will not run if user Alt+Tabs or when simulation is disabled in SP. Will not skip frames under load but also will not run on client with no UI.

And now for my method of getting per frame execution:

  • Trigger Vehicle Condition Check – runs on each frame and cannot and will not be suspended. Does not skip frames under load. No UI needed for it to run, so can be used on the server and HC. Runs even if user Alt+Tabs in MP. Doesn’t run when simulation is disabled in SP (pause menu/Alt+Tab). Versatile, as it has properties of a trigger, so both on activation and on deactivation statements can be used as well. Basically this is the closest thing to onEachFrame event you can have. You can also create multiple scopes, which will all run every frame. Because each scope is tied up to Trigger Vehicle the scopes can be terminated by terminating Trigger Vehicle object.

Lets have a look at different implementations. A straight forward onEachFrame:

<handleObject> = <StringOrCode> call KK_fnc_onEachFrame

KK_fnc_onEachFrame = { private "_oef"; if (typeName _this isEqualTo "CODE") then { _this = format ["%1", _this]; _this = _this select [1, count _this - 2]; }; _oef = "EmptyDetector" createVehicleLocal [0, 0, 0]; _oef setTriggerArea [0, 0, 0, false]; _oef setTriggerStatements [_this, "", ""]; _oef };

This function will take either CODE or STRING, just like onEachFrame and return Trigger Vehicle object. To terminate, deleteVehicle Trigger Vehicle object (also available inside the function in thisTrigger variable). Example (draw 3d icon):

{ private "_private"; _playerPos = getPosATL player; drawIcon3D [ "\a3\ui_f\data\IGUI\Cfg\Radar\radar_ca.paa", [0,0,1,0.5], [_playerPos select 0,_playerPos select 1,2.3], 5, 5, direction player, "COMPASS", 0, 0.03, "PuristaMedium" ]; } call KK_fnc_onEachFrame;

An extended version of KK_fnc_onEachFrame using some of the trigger functionality:

<handleObject> = [<StringOrCode>, <StringOrCodeOnTrue>, <StringOrCodeOnFalse>] call KK_fnc_onEachFrame

KK_fnc_onEachFrameExt = { private ["_fncs", "_oefx"]; _fncs = ["", "", ""]; { if (typeName _x isEqualTo "CODE") then { _x = format ["%1", _x]; _x = _x select [1, count _x - 2]; }; _fncs set [_forEachIndex, _x]; } forEach _this; _oefx = "EmptyDetector" createVehicleLocal [0, 0, 0]; _oefx setTriggerArea [0, 0, 0, false]; _oefx setTriggerActivation ["NONE", "PRESENT", true]; _oefx setTriggerStatements _fncs; _oefx };

Could be used as straight forward onEachFrame if only 1 argument is supplied. Function in 2nd argument will trigger if function in 1st argument returns true, and function in 3rd argument will be executed if false is returned instead. Because default state of the trigger is deactivated (as if false was returned), in order to make both functions trigger, true should be returned first and false second:

[ format ["time < %1", time + 5], {hint "<"}, {hint ">"; deleteVehicle thisTrigger} ] call KK_fnc_onEachFrameExt;

Now lets revisit some timer functions, because using every frame check it is possible to make some very precise timeouts:

<handleObject> = [<functionToCall>, <timeout>] call KK_fnc_setTimeout 

KK_fnc_setTimeout = { private "_timer"; _timer = "EmptyDetector" createVehicleLocal [0, 0, 0]; _timer setTriggerArea [0, 0, 0, false]; _timer setTriggerStatements [ format ["time >= %1", time + (_this select 1)], format ["deleteVehicle thisTrigger; call %1", _this select 0], "" ]; _timer };

The function could be CODE or a variable with CODE. The function will also auto-delete the Trigger Vehicle object to clean after itself:

_fn = {hint str time}; [_fn, 5] call KK_fnc_setTimeout;

And of course the interval, calling specified function at set periods:

<handleObject> = [<functionToCall>, <interval>] call KK_fnc_setInterval 

KK_fnc_setInterval = { private "_timer"; _timer = "EmptyDetector" createVehicleLocal [0, 0, 0]; _timer setTriggerArea [0, 0, 0, false]; _timer setTriggerActivation ["NONE", "PRESENT", true]; _timer setTriggerStatements [ format ["round (time mod %1) isEqualTo 0", _this select 1], format ["call %1", _this select 0], "" ]; _timer };

The interval would also be pretty precise. The range is from 1 second to whatever. However it is also possible to make interval less then 1 second with some additional tweaking of the function if needed:

[{hint str time}, 2.31] call KK_fnc_setInterval;

Enjoy,
KK