Jump to content


  • Content Count

  • Joined

  • Last visited

  • Medals

Posts posted by pedeathtrian

  1. mission.sqm is being preprocessed on every load/save. Errors in possibly included files could cause that error, not only mission.sqm itself.

    Also, just in case, make sure all your included files end with a newline. Some end with a line having a #define, e.g.

    #define IDC_PROGRESSBAR_TEXT 200

    but not having a newline at the end of file. This file being #include'd will eat up the next line after #include. So in the end it might look like

    #define WORLD_CENTER getArray (configFile >> "CfgWorlds" >> worldName >> "centerPosition")version=53;

    Not having explicit #includes in mission.sqm does not mean the preprocessing is not done on other files.

  2. Just now, _JB said:

    I have to convert the displayname back to

    It's generally a bad idea to rely on displayed text in listbox (and other controls).

    Collection display controls have special mechanisms to associate data with elements. Check out this example as well as commands lbSetData and lbData.

    Adding items with smth like this:

    // adding
    _idxLastAdded = _listBox lbAdd "<displayname here>";
    _listBox lbSetData [_idxLastAdded, "<classname here>"];

    Later, using:

    // using
    _idxSelected = lbCurSel _listBox;
    _className = _listBox lbData _idxSelected;


    UPD. Data is limited to a string type, but you can always make it a key for associative array, where value is of any type.

    • Like 2

  3. On 12.10.2018 at 3:11 AM, NumbNutsJunior said:

    Just wondering the game hides the locations of all the built in towns somewhere really

    All locations are in config files with their positions. Check entries in this config: configFile >> "CfgWorlds" >> worldName >> "Names", filter by type, PROFIT.

      diag_log format ["%1: %2", getText(_x >> "name"), str (getArray (_x>>"position"))]
    } forEach (
      'getText(_x >> "type") == "NameCity" || getText(_x >> "type") == "NameCityCapital"' configClasses (configFile >> "CfgWorlds" >> worldName >> "Names")


    UPD: Output:

    17:06:12 "Katkoula: [5684.68,3993.67]"
    17:06:12 "Balavu: [2677.42,7441.56]"
    17:06:12 "Blue Pearl industrial port: [13523,12134.8]"
    17:06:12 "Georgetown: [5396.22,10334.7]"
    17:06:12 "Lijnhaven: [11802,2662.98]"
    17:06:12 "Tuvanaka: [1579.49,11937.8]"
    17:06:12 "Moddergat: [9407.35,4133.13]"
    17:06:12 "Doodstil: [12861.9,4691.1]"
    17:06:12 "Harcourt: [11122.5,5342.93]"
    17:06:12 "Tanouka: [9014.23,10214.2]"
    17:06:12 "Lifou: [7080.21,8004.08]"
    17:06:12 "La Rochelle: [9549.78,13673.4]"
    17:06:12 "Nicolet: [6164.67,12864.7]"
    17:06:12 "Ouméré: [12984.3,7321.96]"
    17:06:12 "Saint-Julien: [5808.6,11213.3]"


    • Like 1

  4. 1. Correctness

    2. Readability and maintainability

    3. Testibility

    4. Reusability

    5. Performance

    Some things one should remember when measuring code quality (list is neither comprehensive nor mandatory). Most of the time I'd redcommend exactly this order (yes, performance is the last thing). So before you ask yourself, "does <some code> perform good?", you should probably go through steps 1-4.


    On 10.10.2018 at 3:45 PM, xjoker_ said:

    defining one big function which handle everything with a switch/case and params

    might fail badly at least on steps 2 and 4 and 

    On 10.10.2018 at 3:45 PM, xjoker_ said:

    defining multiple small fonctions in cfgFunctions

    has all the chances to be good in all aspects.

    • Like 2

  5. Hey guys.

    "rhs_weap_mk18_grip2_eotech_usmc" seems to be no longer in RHS, could you please remove it from UnitClasses.sqf. It gives quite  annoying popup error message box at most unsuitable moments (well, there are no suitable moments in Escape)

    Also this might be a good idea to have a function that runs every mission start (in debug mode) and checks for all UnitClasses' class names for their validity. That will help detect obsolete classnames sooner. All mission variant will only benefit from it, not just ones using RHS.


    • Like 1
    • Thanks 1

  6. 16 minutes ago, fn_Quiksilver said:

    does _cnt really need to be "private" in the function?


    Functions have access to variables of the scope where they were called.


    func1 = {
      _cnt = _cnt + 1;
    func2 = {
      private _cnt = 2;
      systemChat format ["_cnt = %1", _cnt];
      [] call func1;
      systemChat format ["_cnt = %1", _cnt];
    [] call func2;

    will print 


    _cnt = 2
    _cnt = 3

    even though _cnt was declared private in func2. func1's author might never know how and where it's used and what variable names are used there. So to avoid name clashes local variables in functions MUST be declared private.


    Now this

    func1 = {
      private _cnt /* this one is to be private after this line */ = _cnt /* this is not private */ + 1;
      systemChat format ["f1: _cnt = %1", _cnt];
    func2 = {
      private _cnt = 2;
      systemChat format ["f2: _cnt = %1", _cnt];
      [] call func1;
      systemChat format ["f2: _cnt = %1", _cnt];
    [] call func2;

    prints what everyone would expect


    f2: _cnt = 2

    f1: _cnt = 3

    f2: _cnt = 2


    • Like 1

  7. Three more performance considerations addressed in some form in wiki page and linked topic:

    1. It is actually not necessary to randomise picking last item. By the time there's only one left, the whole sequence is already randomized enough, and moving item from front to back does not add or substrct from its randomness. So you can save one iteration.

    2. Removing and inserting items usually considered expensive for arrays (and quite cheap for lists for example). That is for real arrays of items placed consequently in memory. Therefore modern Fisher-Yates algo uses swapping of items (which for lists is quite similar in terms of operations cost as removing and inserting). A3 arrays, however, very well might not be real arrays, so better measure here too. Also, arrays in A3 usually not big enough for algorithmical differences to be significant.

    3. Shuffling in place is probably a better option to have rather than returning a shuffled copy. You can always do a copy yourself and shuffle it in-place. BIS_fnc_arrayShuffle was returning shuffled copy from the beginning afair, so it better stay so (people expect it to work that way and no modify passed array). So the best thing is to have other function to shuffle in place.

    • Like 1

  8. 9 minutes ago, Tankbuster said:

    I use it often. It was certainly working as expected yesterday

    And what exactly do you expect from it actually? :)


    Okay, measuring...

    some_array = [];
    // filling array with 100 numbers from 0 to 99 sequentially
    for "_i" from 0 to 99 do
      some_array pushBack _i;
    zero_on_first = 0;
    tests = 10000;
    for "_i" from 0 to (tests-1) do
      some_array_copy = some_array call BIS_fnc_arrayShuffle;
      if (0 == (some_array_copy select 0)) then {
        zero_on_first = zero_on_first + 1;
    systemChat format ["First element unchanged %1 times out of %2", zero_on_first, tests];

    I expected it would say "First element unchanged 100 times out of 10000" (for equally likely permutations), but it gave me 3664!

    • Like 2
    • Thanks 1

  9. As of today (A3 v1.80), BIS_fnc_arrayShuffle looks like this:

    		Nelson Duarte, optimised by Killzone_Kid
    		This returns a new array with randomized order of elements from input array
    		_this: ARRAY
    	[1, 2, 3] call BIS_fnc_arrayShuffle
    	Returns: [2, 3, 1] (For example)
    /// --- validate general input
    #include "..\paramsCheck.inc"
    _this = +_this;
    private _cnt = count _this;
    for "_i" from 1 to _cnt do 
    	_this pushBack (_this deleteAt floor random _cnt);


    That is, it is still broken in the way that it does not generate a fair shuffle (where all permutations are equally likely).

    What this variant does: it takes random element and moves it to back of array, thus after iterations you will have randomized back and ordered (thinned out though) front. The problem is every next random picking can pick from already randomized back too, thus leaving front less randomized.

    Consider line

    _this pushBack (_this deleteAt floor random _cnt);

    To generate truly equally-possible permutations it should have been

    _this pushBack (_this deleteAt floor random (_cnt + 1 - _i));

    thus only picking from ordered front (of decreasing size) and putting picked element to the back.


    From the mentioned in linked topic Fisher-Yates shuffle algorithm wiki page:

    -- To shuffle an array a of n elements (indices 0..n-1):
    for i from n−1 downto 1 do
         j ← random integer such that 0 ≤ j ≤ i
         exchange a[j] and a[i]

    Note picking j from range 0 ≤ j ≤ i, BIS_fnc_arrayShuffle still picks from 0 ≤ j ≤ (n-1) (considering indexes 0 to n-1)




    _this = +_this;

    might not be good enough. Depending on element types,

    _this = []+_this;

    (shallow copy) can perform better.

    • Like 3

  10. From what I see in this thread, OP is trying to randomly pick some elements from array without repetitions.

    Probably the easiest and most effective way is to deleteAt from copy of input array. For array of heavy objects, better use indices.

    pick_random_no_repetitions = {
    	// Usage:
    	// [ ARRAY, COUNT ] call pick_random_no_repetitions;
    	//     ARRAY: array of items to select from
    	//     COUNT: count of items to select
    	// Returns: array of COUNT (or less, if there are not enough) items selected from input ARRAY, no repetitions
    	// Complexity: linear on (COUNT min (count ARRAY))
    	params [["_array", [], [[]]], ["_count", 0, [0]]];
    	private _array_shallow_copy = [] + _array;
    	private _count_avail = _count min (count _array_shallow_copy);
    	private _count_remain = _count_avail;
    	private _ret = [];
    	for "_i" from 0 to (_count_avail-1) do {
    		_ret pushBack (_array_shallow_copy deleteAt (floor random _count_remain));
    		_count_remain = _count_remain - 1;


    Some other considerations.

    Whenever you feel the need to use lots of _varNameNUMBER variables, you're most likely on a wrong path.

    Whenever you have to take enormous amount (usually indefinite) of tries to complete the task, you're most likely on a wrong path.

    Best to reconsider your approach in general, e.g. try another algorithm.


    Have a nice day.

    • Like 1

  11. You only check distance to the last placed IED and not all of the placed IEDs. This way you might end up with IEDs placed on adjacent road segments. So either check for distance to all placed IEDs or better filter out road segments within _minSpacing:

    // ... ditto
    _roads = _searchFrom nearRoads _area;
    while {(count _roads) > 0} do {
    	_iedPos = getPos (_roads select 0);
    	// unconditionally place IED here
    	_roads = _roads select {(_iedPos distance2d (getPos _x)) > _minSpacing};


    • Like 2

  12. One way to improve performance of your code working with static map objects is hardcoding result into mission.

    Of course, you still need the code to do the search, but now its performance is way less significant than before. Divide the code into debug mode and release mode. In debug version search code always runs, compares results to hardcoded values and warns if there are any differences. In release hardcoded values always used.

    One more option for release is to associate hardcoded values with map version (hardcoded too), that is if map is updated you still do the search. That guarantees mission will work correctly even if not maintained anymore. (in this variant code performance is still significant)

    You might say this is not very elegant way of doing things. That's true. Until you concerned enough about performance. In general, everything that can be calculated during build time should be calculated during build time.

  13. 4 hours ago, mrcurry said:

    It's entirely possible BIS_fnc_baseWeapon does not strip away some weapons' bipod. Do you have any examples of weapons where this happens cause that might require a bug report.

    I checked the configFile with this code:

    (("isClass(_x) && (getNumber(_x >> 'scope') > 0)" configClasses (configFile >> "CfgWeapons")) apply {configName _x}) select {(_x == (_x call BIS_fnc_baseWeapon)) && (isClass(configFile >> "CfgWeapons" >> _x >> "LinkedItems"))}

    It finds weapons which return themselves as base weapons with BIS_fnc_baseWeapon and have LinkedItems config entry.

    On my system it returned array


    For these wepons there's no good way to add them to container without attachments.

    One can use unit or game logic intermediary though: add weapon to unit, strip attachments, add weapon to container. Though this is cumbersome, slow, requires more code and additional objects... only makes sense if absense of attachments is critical.

    • Like 1

  14. 5 hours ago, Grumpy Old Man said:


    [random 10000] call BIS_fnc_numberDigits; //0.0538 ms
    toarray str random 10000 apply {parsenumber (tostring [_x])}; //0.01 ms


    Careful with fraction part. parseNumber parses decimal dot into zero, so for 12.34 you will get [1,2] for BIS_fnc_numberDigits and, surprise, [1,2,0,3,4] for parseNumber variant.


    UPD. If it only used for integers, this can be even faster:

    toarray str random 10000 apply {_x-48};

    (decimal point converted to -2 in this variant)


    UPD2. And negatives! parseNumber parses minus sign into zero too. {_x-48} makes -3 out of minus sign. Not very comfy, but at least distinguishable from real number digits.

    • Like 1

  15. 5 hours ago, beno_83au said:

    Inconsistencies are correct. While a vanilla weapon returns an array of suppressors, a mod weapon (in this case from RHS) returns an empty array.

    It is not necessary an array. Check the type. Works with both vanilla and RHS:

    pdth_fnc_weapon_slot_comp_items = {
    		Return list of compatible attachments suitable for specified weapon and for specifiied slot
    		_this: Array: [ className, slotName ]:
    			className: String: weapon class name
    			slotName: String: slot class name, representing class entry in ()
    				Commonly used slot names are:
    					"CowsSlot": optics
    					"PointerSlot": pointers, flashlights, etc.
    					"MuzzleSlot": muzzle flash suppressors, silencers, etc.
    					"UnderBarrelSlot": bipods
    		@return Array: [(item1, item2, ...)]
    			item1, item2, ...: Strings
    			// returns ["acc_flashlight", "acc_pointer_IR"]
    			_arrPointers = ["arifle_MX_F", "PointerSlot"] call pdth_fnc_weapon_slot_comp_items;
    	params [["_clName", "", [""]], ["_slName", "", [""]]];
    	private _ret = [];
    	if ((isClass (configFile >> "CfgWeapons" >> _clName)) && (isClass (configFile >> "CfgWeapons" >> _clName >> "WeaponSlotsInfo" >> _slName))) then {
    		private _compItems = (configFile >> "CfgWeapons" >> _clName >> "WeaponSlotsInfo" >> _slName >> "compatibleItems");
    		if (isClass _compItems) then {
    				_ret pushBack (configName _x);
    			} forEach (configProperties [_compItems, "(isNumber _x) && ((getNumber _x) > 0)"]);
    		} else {
    			if (isArray _compItems) then {
    				_ret = getArray(_compItems);



  16. 12 hours ago, barbolani said:

    What kind of number returns your function?

    It is logarithmic measure of how much fog decreases visibility of object. Being logarithmic means every unit added to the measure means visibility divided by correspondent value.


    According to the setFog wiki page of BISim (thanks, @pierremgi), there might be another approach used to determine object visibility based on view distance: renderer cuts off objects based on this info and not on some cryptic optical transmittance values. View distance seem to be dependent on maximum view distance and fog value. So you take that calculated average fog value and apply it to second graph on the page to determine maximum view distance for that fog. If object is farther than this (or, say, 0.5 of this max distance), it should be considered not visible. Averaged fog should give better results than suggested maximum fog value of two points.

    As using graph is not very practical in scripts, I tried to determine what this curve is. At first glance it looks like just exponential based on maximum view distance, fog value (same as for fog effective value based on fogValue and fogDecay) and some coefficient defining the speed of exponenta falling.

    This btw fits way better to my experimental measurements than 1Ge96nI.png assumtion which gives hyperbola, not exponenta.


    I represented the graph with this function: 6eW4z75.png

    where f is fog value, lmax is the maximum view distance, and R is factor showing by how many times view distance decreased with transition from f = 0 to f =1.

    On my system lmax is 6 km. On windows versions it seems to be 10 km. R on my machine appeared to be ~120. Might be same on windows version.

    So here's what I got for this approximation.


    Vertical axis is view distace (logarithmic scale), blue for calculated, red for experimental measure

    Horizontal axis is fog value.


    Though there's some excess near fog=1, in general, this estimate fits pretty well.

    Excess might be caused by poor measurement "by eye", or probably because on short distances objects are better visible because they're close.


    Now I suggest that when distance to object is greater than calculated view distance (or other threshold based on it), then object is not visible (visibility = 0). When object is lcoser, its visibility grows linearly from 0 (at max view distance) to 1 (at zero distance).


    Fixed version, returning 0 for not visible and 1 for fully visible objects.

    // takes two positions in ASL format;
    params [["_pos0", [0,0,0], [[]], 3], ["_pos1", [0,0,0], [[]], 3]];
    private _MaxViewDistance = 10000;
    private _ViewDistanceDecayRate = 120;
    private _z0 = _pos0 param [2, 0, [0]];
    private _z1 = _pos1 param [2, 0, [0]];
    private _l = _pos0 distance _pos1;
    fogParams params ["_fogValue", "_fogDecay", "_fogBase"];
    _fogValue = _fogValue min 1.0;
    _fogValue = _fogValue max 0.0;
    _fogDecay = _fogDecay min 1.0;
    _fogDecay = _fogDecay max -1.0;
    _fogBase = _fogBase min 5000;
    _fogBase = _fogBase max -5000;
    private _dz = _z1 - _z0;
    private _fogCoeff = 1.0;
    if (_dz !=0 && _fogDecay != 0) then {
    	private _cl = -_fogDecay * _dz;
    	private _d = -_fogDecay * (_z0 - _fogBase);
    	// lim [(exp(x)-1) / x] = 1 as x->0
    	if (abs(_cl) > 1e-4) then {
    		_fogCoeff = exp(_d) * ( exp (_cl) - 1.0) / _cl;
    	} else {
    		_fogCoeff = exp(_d);
    private _fogAverage = _fogValue * _fogCoeff;
    private _fogViewDistance = 0.9 * _MaxViewDistance * exp (- _fogAverage * ln(_ViewDistanceDecayRate));
    0 max (1.0 - _l/_fogViewDistance)

    This function returns 0.1 when object is at 0.9 of fog-defined view distance; 0.2 when object is at 0.8 of that distance, etc.

    This function might require some tweaks, so any feedback is appreciated.

    • Like 1

  17. Do we have a command or function to get maximum limit of view distance we can set with setViewDistance?

    Different numbers appear in comments (10 km, 15 km), also, in ports this distance is decreased, AFAIK, to 6 km (?).

    I can use the (productVersion select 6) to get the platform, but then what values do I use?

    If somebody gives me values and says they're not the subject to change, that'd be fine too.


    Why these values? According to BISim::setFog they affect the view distance limitation made by fog. I experimented with different view distance and fog settings and got somewhat similar (exponential downfall) character of view distance dependency on fog value (same rules might apply in BISim and Arma). However, view distance set by setViewDistance does not have any influence on fog effect and only works as cut-off. Thus I need the limit(s).


  18. 8 hours ago, Tankbuster said:


    if (_defdvar1 and {_maybeundef1}) then {do stuff}

    Will this break if _defdvar is false and _maybeundef1 is undefined?

    Undefined variable in code clock does not cause error, it cause return of nil. Which in turn causes if to stop evaluating condition and neither then-statement nor else-statement is executed.

    Code blocks can't be the first in if condition though and must evaluate to boolean type or nil; nil or boolean value can be placed the last to ensure that.

    So you can get this combination working if maybeundef1 and maybeundef2 represent boolean types if defined:

    if (true && {maybeundef1} && {maybeundef2} && {some code returning nil or boolean} && {finally do some stuff; nil});