{"name":"Arma 3: Writing a Function","title":"Arma 3: Writing a Function","type":"topic","url":"https://community.bistudio.com/wiki/Arma_3:_Writing_a_Function","revid":341512,"updated":"2023-06-07T07:16:14Z","aliases":[],"markdown":"The most important thing to remember when writing a function is that other people are going to use it.\nMost of them won't understand how it works, expecting it to do its job without problems.\n\nThe function must be **robust**. It should not allow passing arguments of incorrect Data Types in.\nWhen some values are incorrect, it should throw an error explaining what went wrong and how to fix it.\nAnd above all, its header must provide complete explanation of usage.\n\n> [!IMPORTANT]\n>\n\n### Header\n\n| Template | Example |\n|---|---|\n| `/* 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 */` |\n\n### Taking Arguments\n\nArguments are the only way to interact with a function. Let's now see how to make sure they are loaded properly.\n\nHere is this very simple function which will let a unit watch a position:\n\n```sqf\nparams [\"_unit\", \"_target\"];\n_unit doWatch _target;\n```\n\nExpected way how to call the function is by correctly defining all arguments:\n\n|  |  |\n|---|---|\n| ✓ | ```sqf<br>[player, getPosASL myCar] call TAG_fnc_myFunction;<br>``` |\n\nHowever, the function will break down when tried with only one argument:\n\n|  |  |\n|---|---|\n| ✗ | ```sqf<br>[player] call TAG_fnc_myFunction;<br>``` |\n\nFurthermore, using wrong data type will also lead to a problem:\n\n|  |  |\n|---|---|\n| ✗ | ```sqf<br>[player, 0] call TAG_fnc_myFunction;<br>``` |\n\nVariable *_target* expects position array in format [x,y,z]. Scripting error will appear when a different number of elements is used:\n\n|  |  |\n|---|---|\n| ✗ | ```sqf<br>[player, [1, 2, 3, 4]] call TAG_fnc_myFunction;<br>``` |\n\nAs shown there, the most common problems are:\n1. **Param of wrong data type is sent**\n1. **Param is missing**\n1. **Param is an array expecting specific number of elements, but different number is sent**\n\nRather than check for all these exceptions manually, it is more than advised to use the param command:\n _unit = param [0, objNull, [objNull]];\nFor multiple parameters, use the params command instead.\n params [\n \t[\"_unit\", objNull, [objNull]],\n \t[\"_target\", [0, 0, 0], [[], objNull], [2, 3]]\n ];\n _unit doWatch _target;\n- In a params array first argument is the name of the **private variable**. In param it is the **index** number.\n- Second argument is the **default value**. It will be used when the argument is missing, is nil or when wrong data type is used.\n- 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 the function, param will log an error message explaining what went wrong and use the default value.\n- 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 the function, param will log an error message explaining what went wrong and use the default value.\n\nLet's see what happens with wrong examples now:\n\n|  |  |\n|---|---|\n| ✓ | ```sqf<br>[player] call TAG_fnc_myFunction;<br>``` |\n\n*_target* is undefined. Default [0, 0, 0] is used instead. No error message is logged.\n\n|  |  |\n|---|---|\n| ✓ | ```sqf<br>[nil, getPosASL myCar] call TAG_fnc_myFunction;<br>``` |\n\n*_unit* is undefined (nil is passed instead). Default objNull is used instead. No error message is logged.\n\n|  |  |\n|---|---|\n|  | ```sqf<br>[player, 0] call TAG_fnc_myFunction;<br>``` |\n\n*_target* has wrong type. Default [0, 0, 0] is used instead. Error message is logged.\n\n|  |  |\n|---|---|\n|  | ```sqf<br>[player, [1, 2, 3, 4]] call TAG_fnc_myFunction;<br>``` |\n\n*_target* has wrong size. Default [0, 0, 0] is used instead. Error message is logged.\n\nAdditionally, when only one argument is used, it can be sent into the function directly without the need to have it in an array.\n\n|  |  |\n|---|---|\n| ✓ | ```sqf<br>player call TAG_fnc_myFunction;<br>``` |\n\n### Returning Value\n\nA function can return a result (object reference, script handle, etc) that can then be saved by the user. If no value is returned, the variable would be nil and could lead to script errors.\n\n```sqf\n_myVar = [player, getPosASL myCar] call TAG_fnc_myFunction;\n```\n\nIt is a good practice to **always** return a value, even if it would be a simple true marking the function as completed. Let's use the example function from above:\n\n```sqf\nparams [\n    [\"_unit\", objNull, [objNull]],\n    [\"_target\", [0, 0, 0], [[], objNull], [2, 3]]\n];\n_unit doWatch _target;\ntrue;\n```\n\n> [!NOTE]\n> Note that a spawned function will always return a Script Handle, as per spawn command's behaviour.\n\n### Showing Errors\n\nWhile param and params can filter out the most common issues, the function will sometimes have special rules which will need to be handled.\nLet's return back to the example function, where we would want to terminate the function with error when *_unit* is dead:\n\n```sqf\nparams [[\"_unit\", objNull, [objNull]], [\"_target\", [0, 0, 0], [[], objNull], [2, 3]]];\nif (!alive _unit) exitWith { [\"Unit %1 must be alive.\", _unit] call BIS_fnc_error; false; };\n_unit doWatch _target;\ntrue;\n```\n\nNotice that we're returning false at the end of exitWith code.\n\n> [!IMPORTANT]\n> Error states must always return value of the same type as when everything is fine (Boolean in this case).\n\nBIS_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.\n\n|  |  |\n|---|---|\n| RPT | \"User1/log: ERROR: [BIS_fnc_respawnTickets] #0: 0 is type SCALAR, must be NAMESPACE, SIDE, GROUP, OBJECT, BOOL. true used instead.\" |\n| In-game |  |\n\n### Logging\n\nApart from errors, it is possible to print any needed debug message. Use one of the following functions:\n- BIS_fnc_log - log a data of any type (e.g., String, Number, Object, ...)\n- BIS_fnc_logFormat - log formatted text\nProfile name and function name will automatically appear in the output text, to help identifying the source.\n\nUsage examples:\n\n| Expression | RPT Output |\n|---|---|\n| \"Hello World\" call BIS_fnc_log; | \"User1/BIS_fnc_log: [TAG_fnc_myFunction] Hello World\" |\n| 42 call BIS_fnc_log; | \"User1/BIS_fnc_log: [TAG_fnc_myFunction] 42\" |\n| [\"I'm playing %1\", missionName] call BIS_fnc_logFormat; | \"User1/BIS_fnc_log: [TAG_fnc_myFunction] I'm playing FalconWing\" |\n\nTo 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:\n\n```cpp\nallowFunctionsLog = 1;\n```\n\n### Recompiling\n\nOnce compiled, functions remain unchanged and editing their file won't have any effect in the game.\nTo adjust functions on the fly, their recompilation can be manually triggered - see Arma 3: Functions Viewer and BIS_fnc_recompile.\n\n### Meta Variables\n\nSystem is adding header with basic meta data to all functions. Following local variables are declared there:\n- **_fnc_scriptName**: String - Function name (e.g., TAG_fnc_myFunction)\n- **_fnc_scriptNameParent**: String - Name of q function from which the current one was called (_fnc_scriptName used when not defined)\n\n> [!IMPORTANT]\n> Do not modify these values!\n\n### See Also\n\n- Arma 3: Functions Library\n- Arma 2: Functions Library\n- Take On Helicopters: Functions Library\n\nArma 3 Tutorials","sections":[{"heading":null,"level":1,"markdown":"The most important thing to remember when writing a function is that other people are going to use it.\nMost of them won't understand how it works, expecting it to do its job without problems.\n\nThe function must be **robust**. It should not allow passing arguments of incorrect Data Types in.\nWhen some values are incorrect, it should throw an error explaining what went wrong and how to fix it.\nAnd above all, its header must provide complete explanation of usage.\n\n> [!IMPORTANT]\n>"},{"heading":"Header","level":3,"markdown":"| Template | Example |\n|---|---|\n| `/* 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 */` |"},{"heading":"Taking Arguments","level":3,"markdown":"Arguments are the only way to interact with a function. Let's now see how to make sure they are loaded properly.\n\nHere is this very simple function which will let a unit watch a position:\n\n```sqf\nparams [\"_unit\", \"_target\"];\n_unit doWatch _target;\n```\n\nExpected way how to call the function is by correctly defining all arguments:\n\n|  |  |\n|---|---|\n| ✓ | ```sqf<br>[player, getPosASL myCar] call TAG_fnc_myFunction;<br>``` |\n\nHowever, the function will break down when tried with only one argument:\n\n|  |  |\n|---|---|\n| ✗ | ```sqf<br>[player] call TAG_fnc_myFunction;<br>``` |\n\nFurthermore, using wrong data type will also lead to a problem:\n\n|  |  |\n|---|---|\n| ✗ | ```sqf<br>[player, 0] call TAG_fnc_myFunction;<br>``` |\n\nVariable *_target* expects position array in format [x,y,z]. Scripting error will appear when a different number of elements is used:\n\n|  |  |\n|---|---|\n| ✗ | ```sqf<br>[player, [1, 2, 3, 4]] call TAG_fnc_myFunction;<br>``` |\n\nAs shown there, the most common problems are:\n1. **Param of wrong data type is sent**\n1. **Param is missing**\n1. **Param is an array expecting specific number of elements, but different number is sent**\n\nRather than check for all these exceptions manually, it is more than advised to use the param command:\n _unit = param [0, objNull, [objNull]];\nFor multiple parameters, use the params command instead.\n params [\n \t[\"_unit\", objNull, [objNull]],\n \t[\"_target\", [0, 0, 0], [[], objNull], [2, 3]]\n ];\n _unit doWatch _target;\n- In a params array first argument is the name of the **private variable**. In param it is the **index** number.\n- Second argument is the **default value**. It will be used when the argument is missing, is nil or when wrong data type is used.\n- 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 the function, param will log an error message explaining what went wrong and use the default value.\n- 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 the function, param will log an error message explaining what went wrong and use the default value.\n\nLet's see what happens with wrong examples now:\n\n|  |  |\n|---|---|\n| ✓ | ```sqf<br>[player] call TAG_fnc_myFunction;<br>``` |\n\n*_target* is undefined. Default [0, 0, 0] is used instead. No error message is logged.\n\n|  |  |\n|---|---|\n| ✓ | ```sqf<br>[nil, getPosASL myCar] call TAG_fnc_myFunction;<br>``` |\n\n*_unit* is undefined (nil is passed instead). Default objNull is used instead. No error message is logged.\n\n|  |  |\n|---|---|\n|  | ```sqf<br>[player, 0] call TAG_fnc_myFunction;<br>``` |\n\n*_target* has wrong type. Default [0, 0, 0] is used instead. Error message is logged.\n\n|  |  |\n|---|---|\n|  | ```sqf<br>[player, [1, 2, 3, 4]] call TAG_fnc_myFunction;<br>``` |\n\n*_target* has wrong size. Default [0, 0, 0] is used instead. Error message is logged.\n\nAdditionally, when only one argument is used, it can be sent into the function directly without the need to have it in an array.\n\n|  |  |\n|---|---|\n| ✓ | ```sqf<br>player call TAG_fnc_myFunction;<br>``` |"},{"heading":"Returning Value","level":3,"markdown":"A function can return a result (object reference, script handle, etc) that can then be saved by the user. If no value is returned, the variable would be nil and could lead to script errors.\n\n```sqf\n_myVar = [player, getPosASL myCar] call TAG_fnc_myFunction;\n```\n\nIt is a good practice to **always** return a value, even if it would be a simple true marking the function as completed. Let's use the example function from above:\n\n```sqf\nparams [\n    [\"_unit\", objNull, [objNull]],\n    [\"_target\", [0, 0, 0], [[], objNull], [2, 3]]\n];\n_unit doWatch _target;\ntrue;\n```\n\n> [!NOTE]\n> Note that a spawned function will always return a Script Handle, as per spawn command's behaviour."},{"heading":"Showing Errors","level":3,"markdown":"While param and params can filter out the most common issues, the function will sometimes have special rules which will need to be handled.\nLet's return back to the example function, where we would want to terminate the function with error when *_unit* is dead:\n\n```sqf\nparams [[\"_unit\", objNull, [objNull]], [\"_target\", [0, 0, 0], [[], objNull], [2, 3]]];\nif (!alive _unit) exitWith { [\"Unit %1 must be alive.\", _unit] call BIS_fnc_error; false; };\n_unit doWatch _target;\ntrue;\n```\n\nNotice that we're returning false at the end of exitWith code.\n\n> [!IMPORTANT]\n> Error states must always return value of the same type as when everything is fine (Boolean in this case).\n\nBIS_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.\n\n|  |  |\n|---|---|\n| RPT | \"User1/log: ERROR: [BIS_fnc_respawnTickets] #0: 0 is type SCALAR, must be NAMESPACE, SIDE, GROUP, OBJECT, BOOL. true used instead.\" |\n| In-game |  |"},{"heading":"Logging","level":3,"markdown":"Apart from errors, it is possible to print any needed debug message. Use one of the following functions:\n- BIS_fnc_log - log a data of any type (e.g., String, Number, Object, ...)\n- BIS_fnc_logFormat - log formatted text\nProfile name and function name will automatically appear in the output text, to help identifying the source.\n\nUsage examples:\n\n| Expression | RPT Output |\n|---|---|\n| \"Hello World\" call BIS_fnc_log; | \"User1/BIS_fnc_log: [TAG_fnc_myFunction] Hello World\" |\n| 42 call BIS_fnc_log; | \"User1/BIS_fnc_log: [TAG_fnc_myFunction] 42\" |\n| [\"I'm playing %1\", missionName] call BIS_fnc_logFormat; | \"User1/BIS_fnc_log: [TAG_fnc_myFunction] I'm playing FalconWing\" |\n\nTo 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:\n\n```cpp\nallowFunctionsLog = 1;\n```"},{"heading":"Recompiling","level":3,"markdown":"Once compiled, functions remain unchanged and editing their file won't have any effect in the game.\nTo adjust functions on the fly, their recompilation can be manually triggered - see Arma 3: Functions Viewer and BIS_fnc_recompile."},{"heading":"Meta Variables","level":3,"markdown":"System is adding header with basic meta data to all functions. Following local variables are declared there:\n- **_fnc_scriptName**: String - Function name (e.g., TAG_fnc_myFunction)\n- **_fnc_scriptNameParent**: String - Name of q function from which the current one was called (_fnc_scriptName used when not defined)\n\n> [!IMPORTANT]\n> Do not modify these values!"},{"heading":"See Also","level":3,"markdown":"- Arma 3: Functions Library\n- Arma 2: Functions Library\n- Take On Helicopters: Functions Library\n\nArma 3 Tutorials"}],"categories":["Arma 3: Tutorials","Scripting Topics"],"groups":["Scripting"]}