Skip to content

Instantly share code, notes, and snippets.

@commy2
Last active November 11, 2017 10:56
Show Gist options
  • Save commy2/46d26d9cff33fafec70e181d45a1ffb3 to your computer and use it in GitHub Desktop.
Save commy2/46d26d9cff33fafec70e181d45a1ffb3 to your computer and use it in GitHub Desktop.

Why you should use "param" more often.

####The basics of "param"

param and the related, but not to be confused with, params command are fairly new additions to the Arma 3 SQF library (- they came with v1.48).
While it feels to me like params is rightfully catching up in usage in the Arma scripting community, replacing ugly and slow _this select N constructions that take up multiple lines and are tiring to look at, param is still largely ignored.

param was introduced during the initiative to replace common BI functions (here: BIS_fnc_param) with native SQF commands, in an effort to increase code performance and readability.
Other notable examples are sort (v1.44), arrayIntersect (v1.48), remoteExec(Call) (v1.50) and the new "conditional select" select CODE (v1.56)

param is really just a port of BIS_fnc_param from SQF to C++. It's purpose is to pick out an element of an array by index number and to provide a default value in case the element was undefined. Optionally it can also check for data types, or - in case the element itself is an array - for array size.
The main use of this is to check the argument input of called (or spawned) SQF functions.

Example for [4] spawn BIS_fnc_earthquake, before param:

_magnitude = [_this, 0, 2, [0]] call BIS_fnc_param;

and after param:

_magnitude = _this param [0, 2, [0]];

0 is the index position of the _magnitude variable in the passed arguments of the function. 2 is the default value and [0] ensures, that only numbers are accepted as argument.
There is no reason to still use the old SQF function over the new command. The command is faster and not as verbose.

There are two syntaxes of param. The regular one (param ARRAY) is a unary command and always accesses _this as array to pick from. The alternative syntax (ARRAY param ARRAY) is a binary command and the elements are picked out of the array on the left side of the command. If the variable on the left side is something else than an array, it is treated as if it were an array with only that one element.
I don't know why BI uses _this param [...] instead of param [...]. All it does is add small overhead and lead to error messages in scheduled environment should the function be called with the unary syntax of call while _this is undefined. I guess that doesn't happen very often, but still.

Since v1.54 param (and params) report an error message if the data types don't match any of those provided in the optional paramater in addition to falling back to the provided default value. While this can be annoying if you wanted to allow these cases, having variables with changing data types is considered bad style. There are probably better solutions anyway.
Initially the expected and encountered data types were reported in a rather confusing order, which led to amusing messages like:
Error Type Number,Not a Number, expected Not a Number
This has since been fixed.

####When not to use "param"

So why not replace all occurances of BIS_fnc_param with param? Even if you avoided BIS_fnc_param due to it's relatively big overhead, param is rather lightweight. Because params (with s) is even better!

Just take a look at BIS_fnc_fadeEffect:

private ["_BIS_fadeInOrOut","_BIS_fadeColor","_BIS_fadeDuration","_BIS_blur","_BIS_music","_BIS_ending","_BIS_failMissionOrEndMission"];

_BIS_fadeInOrOut = _this param [0,1,[0,1]];
_BIS_fadeColor = _this param [1,"BLACK",["BLACK","WHITE"]];
_BIS_fadeDuration = _this param [2,3,[0]];
_BIS_blur = _this param [3,0,[0,1]];
_BIS_music = _this param [4,"",[""]];
_BIS_ending = _this param [5,"",[""]];
_BIS_failMissionOrEndMission = _this param [6,1,[0,1]];
...

While param is certainly an improvement over BIS_fnc_param, here is how it would look like if it used params instead:

params [
    ["_BIS_fadeInOrOut", 1, [0,1]];
    ["_BIS_fadeColor", "BLACK", ["BLACK", "WHITE"]];
    ["_BIS_fadeDuration", 3, [0]];
    ["_BIS_blur", 0, [0,1]];
    ["_BIS_music" , "", [""]];
    ["_BIS_ending", "", [""]];
    ["_BIS_failMissionOrEndMission", 1, [0,1]];
];
...

No more redundant information!

An important difference of param and params is, that params automatically sets the current scope for the defined variable(s) as "private". The ability to set the home scope of an arbitrary variable (as in not _x, _forEachIndex or similar "magic" variables) is only shared with private (command and keyword), as well as the "variable to increment" in for-loops (though limited to NUMBERs).

param does not do that, because it doesn't define a variable itself, but rather has it as it's return value. Therefore you have to (pretty much always) combine it with the private keyword:
private _unit = _x param [0, objNull];

####Another niche for "param"

So if you actually think about what param does instead of focusing on it's name or on the related params, you will notice that it basically is another version of ARRAY select NUMBER. A much more powerful version even.
It picks an element by index number like select, but unlike select it can immediately apply default values without additional isNil checks:

_arr = ["a", nil];

private _elem2 = _arr select 1;
if (isNil "_elem2") then {
    _elem2 = "none";
};
_arr = ["a", nil];

private _elem2 = _arr param [1, "none"];

Additionally it does not have annoying limitations with out of bounds indices like select has:

_arr = ["a", "b"];
_arr select 1; // "b"
_arr select 2; // nil, no error
_arr select 3; // nil WITH ERROR POP UP!

_arr param [3]; // nil, no error

So here is an example of a function that does report all hit selections of an object using getAllHitPointsDamage and select:

PZG_fnc_getDamageSelections = {
    params [["_vehicle", objNull, [objNull]]];
    
    private _selections = getAllHitPointsDamage _vehicle select 1;
    
    // filter out hit points with non-existing selections like "HitEngine3" for the little bird
    _selections - [""] // return
};

So what is the problem here?
If you call this function with cursorTarget, but the player is not looking at anything currently, this will error out! This is because getAllHitPointsDamage actually returns [] for <null> objects (instead of [[],[],[]]).
You could say that you could add an isNull _vehicle check somewhere, but even that won't make your function safe if you are taking advantage of the scheduler. An asynchronous running cleanup script could delete the vehicle after the isNull check, but before getAllHitPointsDamage.
Even if you are absolutely sure that the function is never called with objNull and the _vehicle is not deleted, getAllHitPointsDamage does also return [] for objects that have no hitpoints, like hesco barriers, ambient objects or game logics.
The easiest solution is replacing select with param and adding a default value:
private _selections = getAllHitPointsDamage _vehicle select 1;
becomes:
private _selections = getAllHitPointsDamage _vehicle param [1, []];

No error message due to an out of bounds index and the function still returns a meaningfull empty array. (A hesco barrier has no damageable selections after all).

Other examples:

_unit = player;
private _gLauncherMagazine = primaryWeaponMagazine _unit param [1, ""];
private _laserBattery = getUnitLoadout _unit param [8, []] param [4, ["",0]] select 0
private _weaponAttachments = [_unit weaponAccessories _weapon] param [0, ["","","",""]];
private _isKeyHeld = GVAR(heldKeys) param [_dik, false];

####The bug with "param"

param could also be combined with find as an easy way to create associative arrays. Those are two arrays with the same length that store two sets of variables which correspond to each other in some meaningful way by their index number. This way you could associate color-arrays with the strings returned by the assignedTeam command to basically add colored nametags for teams to your favourite name tag script.

PZG_Colors = [
    [1,0,0,1],
    [0,1,0,1],
    [0,0,1,1],
    [1,1,0,1]
];
PZG_Teams = ["RED", "GREEN", "BLUE", "YELLOW"];

_unit = cursorTarget;

private _color = PZG_Colors param [PZG_Teams find assignedTeam _unit, [1,1,1,1]];

The default value would be the standard color (white) if the unit is in no team (reported as "MAIN" by assignedTeam). find would return -1 and param should pick the default value, right?
Unfortunately not... There is this really strange and annoying bug, that param still picks the first element (index: 0) if the index-to-pick is anywhere between -1.4999999 and 0.4999999. This is obviously wrong, because it picks the default value just fine with -2 or anything below (select would just error with anything below -0.5). Every nametag of units in no team would be displayed red!
The only way around that is to cleverly put the default value at the first positions in those associative arrays.

~ commy2, May 12th 2016

Sources:

  • BI wiki (community.bistudio.com)
  • Arma 3 ingame functions library
@Neviothr
Copy link

Interesting read.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment