Writing a Function – Arma 3

From Bohemia Interactive Community
Revision as of 21:22, 4 August 2021 by Lou Montana (talk | contribs) (Page creation)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

The most important thing to remember when writing a function is that other people are going to use it. Most of them won't understand how it works, expecting it to do its job without problems.

The function must be robust. It should not allow passing arguments of incorrect Data Types in. When some values are incorrect, it should throw an error explaining what went wrong and how to fix it. And above all, its header must provide complete explanation of usage.


What is not documented does not exist!


Header

Template Example
/*
	Author: <author nickname>

	Description:
		<function description>

	Parameter(s):
		0: can be one of:
			<type> - <description>
			<type> - <description>
		1: <type> - (Optional, default <default value>) <description>




	Returns:
		<return type>

	Examples:
		<example>
*/
/*
	Author: Karel Moricky

	Description:
		Ends mission with specific ending.

	Parameter(s):
		0: can be one of:
			STRING - (Optional, default "end1") end name
			ARRAY in format [endName, ID] - will be assembled as "endName_ID" string
		1: BOOLEAN - (Optional, default true) true to end mission, false to fail mission
		2: (Optional, default true) can be one of:
			BOOLEAN - true for signature closing shot (default: true)
			NUMBER - duration of a simple fade out to black

	Returns:
		BOOLEAN

	Example:
		[] call BIS_fnc_endMission
*/

Loading Parameters

Arguments are the only way how to interact with your function. Let's now see how to make sure they are loaded properly.

We have this very simple function which will let a unit watch a position:

params ["_unit", "_target"];
_unit doWatch _target;

Expected way how to call the function is by correctly defining all arguments:

Checked
[player, position myCar] call myTag_fnc_myFunction;

However, the function will break down when you try to send only one argument in:

Unchecked
[player] call myTag_fnc_myFunction;

Furthermore, using wrong data type will also lead to a problem:

Unchecked
[player, 0] call myTag_fnc_myFunction;

Variable _target expects position array in format [x,y,z]. Scripting error will appear when a different number of elements is used:

Unchecked
[player, [1, 2, 3, 4]] call myTag_fnc_myFunction;


As shown there, the most common problems are:

  1. Param of wrong data type is sent
  2. Param is missing
  3. Param is an array expecting specific number of elements, but different number is sent

Rather than check for all these exceptions yourself, using the param command will do it for you:

_unit = param [0, objNull, [objNull]];

For multiple parameters, use the params command instead.

params [
	["_unit", objNull, [objNull]],
	["_target", [0, 0, 0], {{Color|crimson|[[], objNull]}}, [2, 3]]
];
_unit doWatch _target;
  • In a params array first argument is the name of the private variable. In param it is the index number.
  • Second argument is the default value. It will be used when the argument is missing, is nil or when wrong data type is used.
  • Next is optional array of compatible data types. They are defined by an example of the type, e.g. objNull will mean an object is allowed. When wrong data type is sent into your function, BIS_fnc_param will log an error message explaining what went wrong and use the default value.
  • The last, also optional argument is an array of required array sizes. [2,3] means only array with 2 or 3 elements are allowed. When incorrectly large array is sent into your function, BIS_fnc_param will log an error message explaining what went wrong and use the default value.


Let's see what will happen when you try to use the wrong examples now:

Checked
[player] call myTag_fnc_myFunction;
_target is undefined. Default [0, 0, 0] is used instead. No error message is logged.
Checked
[nil, position myCar] call myTag_fnc_myFunction;
_unit is undefined (nil is passed instead). Default objNull is used instead. No error message is logged.
Warning
[player, 0] call myTag_fnc_myFunction;
_target has wrong type. Default [0, 0, 0] is used instead. Error message is logged.
Warning
[player, [1, 2, 3, 4]] call myTag_fnc_myFunction;
_target has wrong size. Default [0, 0, 0] is used instead. Error message is logged.


Additionally, when only one argument is used, you can send it into the function directly without need to have it in an array.

Checked
player call myTag_fnc_myFunction;


Returning Value

Users will often save result of your function to a variable. If no value is returned, the variable would be nil and could lead to script errors.

_myVar = [player, position myCar] call myTag_fnc_myFunction;

It's good practice to always return a value, even if it would be simple true marking the function as completed. Let's use the example function from above:

params [
	["_unit", objNull, [objNull]],
	["_target", [0, 0, 0], [[], objNull], [2, 3]]
];
_unit doWatch _target;
true


Showing Errors

While param and params can filter out the most common issues, sometimes your function will have special rules which will need to be handled. Let's return back to our example function, where we'd want to terminate the function with error when _unit is dead:

params [["_unit", objNull, [objNull]], ["_target", [0, 0, 0], [[], objNull], [2, 3]]];
if (!alive _unit) exitWith { ["Unit %1 must be alive.", _unit] call BIS_fnc_error; false };
_unit doWatch _target;
true

Notice that we're returning false at the end of exitWith code.

Error states must always return value of the same type as when everything is fine (Boolean in this case).


BIS_fnc_error accepts String and Array of formatted ext. The error is logged into RPT and if the mission is previewd from the editor, it will also appear on screen.

RPT
"User1/log: ERROR: [BIS_fnc_respawnTickets] #0: 0 is type SCALAR, must be NAMESPACE, SIDE, GROUP, OBJECT, BOOL. true used instead."
In-game On-screen error


Logging

Apart from errors, you can print any debug message you need. Use one of the following functions:

Profile name and function name will automatically appear in the output text, helping you identify the source.


Usage examples:

Expression RPT Output
"Hello World" call BIS_fnc_log;
"User1/BIS_fnc_log: [myTag_fnc_myFunction] Hello World"
42 call BIS_fnc_log;
"User1/BIS_fnc_log: [myTag_fnc_myFunction] 42"
["I'm playing %1", missionName] call BIS_fnc_logFormat;
"User1/BIS_fnc_log: [myTag_fnc_myFunction] I'm playing FalconWing"


To prevent RPT spam, logging is by default enabled only when previewing a mission from the editor. To force it in the mission everywhere, use the following Description.ext attribute:

allowFunctionsLog = 1;


Recompiling

Once compiled, functions remain unchanged and editing their file won't have any effect in the game. To adjust functions on the fly, you can manually trigger their recompilation.

1 call BIS_fnc_recompile;
Recompiles all functions. Can be also achieved by clicking on RECOMPILE button in the Functions Viewer
"functionName" call BIS_fnc_recompile;
Recompile the given function

As a security measure, functions are by default protected against rewriting during the mission. This restriction does not apply in missions previewed from the editor and in missions with the following attribute in Description.ext:

allowFunctionsRecompile = 1;

"Recompile" button in the functions viewer will be enabled only when recompiling is allowed.


Meta Variables

System is adding header with basic meta data to all functions. Following local variables are declared there:

  • _fnc_scriptName: String - Function name (e.g., myTag_fnc_myFunction)
  • _fnc_scriptNameParent: String - Name of q function from which the current one was called (_fnc_scriptName used when not defined)
Do not modify these values!


See Also