Jump to content

Recommended Posts

I have a bunch of groups, which might or might not have been created so far (depending on the path the player has chosen). At some point I want to delete all groups that have been created so far.

I've tried an isNil check for an array of all groupnames, which works well when all groups have been created. But if any of those groups has not been created, it throws up an undefined variable error.

Different variants I have tried, so far to no avail:

{if (!isNil _x) then {deleteGroup _x}} forEach  [grp1, grp2, grp3]; // Undefined Variable

{if (!isNil "_x") then {deleteGroup _x}} forEach  [grp1, grp2, grp3]; // Undefined Variable

{if (!isNil {_x}) then {deleteGroup _x}} forEach  [grp1, grp2, grp3]; // Undefined Variable

{if (!isNil str (_x)) then {deleteGroup _x}} forEach  [grp1, grp2, grp3]; // Undefined Variable

{if (!isNil _x) then {deleteGroup _x}} forEach  ["grp1", "grp2", "grp3"]; // deleteGroup: Type String, expected Group

Of course I could check each group individually, or add all created groups to an array and use this instead (which I'll probably do anyway).

But I'd like to understand why this variant does not work.

Anyone can enlighten me?^^

Share this post


Link to post
Share on other sites
{
	deleteGroup (missionNamespace getVariable [_x, grpNull]);
} 
forEach  ["grp1", "grp2", "grp3"];

 

  • Thanks 1

Share this post


Link to post
Share on other sites

That did the trick, thanks. Although I don't quite understand what I am doing here.

Is it all about different data types? getVariable using a string as argument, while missionNamespace returning the actual group?

Share this post


Link to post
Share on other sites

the groups you name in editor are stored in missionNamespace variables with the same name

Share this post


Link to post
Share on other sites

Actually I am facing a similar issue here!

 

All I need is to ignore or delete this "this_trigger_doesnt_exist" from the array _spwnDelayMethods.

 

_spwnDelayMethods = [this_trigger_doesnt_exist];

{
    if ( isNil "_x" ) exitWith {

        missionNamespace setVariable ["_x", nil];
      
    };
  
} forEach _spwnDelayMethods;

// Output: Arma is printing out the error "_x is an undefined variable!".

 

Share this post


Link to post
Share on other sites
2 hours ago, thy_ said:

Actually I am facing a similar issue here!

 

All I need is to ignore or delete this "this_trigger_doesnt_exist" from the array _spwnDelayMethods.

 


_spwnDelayMethods = [this_trigger_doesnt_exist];

{
    if ( isNil "_x" ) exitWith {

        missionNamespace setVariable ["_x", nil];
      
    };
  
} forEach _spwnDelayMethods;

// Output: Arma is printing out the error "_x is an undefined variable!".

 

Either:

_array = ["this_trigger_doesnt_exist"];

{

    if( isNil _x ) then {

          // Trigger doesn't exist, do what you must 

    } else {

          // Trigger exists, we can get the reference using:

          private _trigger = missionNamespace getVariable _x;

    };

} forEach _array;

Or:

_array = ["this_trigger_doesnt_exist"];



{

   private _trigger = missionNamespace getVariable [_x, objNull];

    if( isNull _trigger  ) then {

          // Trigger doesn't exist

    } else {

          // Trigger exists

    };

} forEach _array;

 

  • Like 3

Share this post


Link to post
Share on other sites

Nothing yet... still printing out the same error.

_spwnDelayMethods = [this_trigger_doesnt_exist];

{
    if ( isNil (str _x) ) exitWith {

        missionNamespace setVariable ["_x", nil];
      
    };
  
} forEach _spwnDelayMethods;

// Output: Arma is printing out the error "_x is an undefined variable!".

nothing... same error.

_spwnDelayMethods = [this_trigger_doesnt_exist];

{
    if ( isNull _x ) exitWith {};
  
} forEach _spwnDelayMethods;

// Output: Arma is printing out the error "_x is an undefined variable!".

Nothing, @mrcurry, both of your tries are printing out the same error!

 

This this_trigger_doesnt_exist comes from Eden, so it should be not a string in the array. If I'm reading right on biki, isNil is the way to check it, and to use isNil this value (the variable-name of the trigger) should be a string. So I guess I am doing go about the isNil, but something I am clearly mission.

 

Share this post


Link to post
Share on other sites
57 minutes ago, thy_ said:

 This this_trigger_doesnt_exist comes from Eden

This is why describing the whole scenario from the get go is useful. There are many ways to skin a programmable cat. 🙂

 

 

Hmm, isNil "_x" should work...

 

Try just checking if the value is undefined using:

isNil { _x }

as the condition. Just curly brackets and no stringification needed.

 

If that fails I'm gonna have to boot up the game for some tests.

 

If you're parsing "user input" to your function you can use param to retrieve the value instead like so:

_x = _spwnDelayMethods param [_forEachIndex, objNull];

 

This way _x will fall back to objNull if its undefined.

Put that at the start of your loop and you can just check

isNull _x and do comparisons with isEqualX commands etc. Nice and clean.

 

 

Share this post


Link to post
Share on other sites
_spwnDelayMethods = [this_trigger_doesnt_exist];
{
  if ( !isNil "_x" ) exitWith {hint format ["%1 is a trigger",_x]};
  systemChat "still not a trigger";
} forEach _spwnDelayMethods;

 

works without throwing error, even if no trigger at all.

Are you sure you don't have any other part of code invoking _x ?

Share this post


Link to post
Share on other sites

At first, thank you @mrcurry and @pierremgi to be here.

 

Well, it's obvious I'm missing something, so let's show the entire flow from the row where we set the trigger (that should be a reference of a true Eden trigger) until the validation of that.

 

Error itself:

Spoiler

lnSe1Az.png

 

File where we set the trigger:

Spoiler

 


// fn_CSWR_population.sqf file:
if ( !isServer || !CSWR_isOn ) exitWith {};

[] spawn {
      
    if ( CSWR_isOnBLU && count CSWR_spwnsAllBLU > 0 ) then {
        // <code not envolving the trigger>
        
        // Blufor population:
        [BLUFOR, [CSWR_spwnsBLU], CSWR_group_BLU_light, _form_BLU_2, _be_SAFE, [_move_ANY], [this_trigger_doesnt_exist]] call THY_fnc_CSWR_add_group;
    
    };
};

true;

 

 

THY_fnc_CSWR_add_group function:

Spoiler

// File: fn_CSWR_globalFunctions.sqf

params ["_side", ["_spwnsInfo", [[], ""]], ["_grpClasses", []], ["_form", ""], ["_behavior", ""], ["_destsInfo", ["", ""]], ["_spwnDelayMethods", 0]];
private [...];

// <code not envolving the _spwnDelayMethods>

// Spawn Schedule:
[_spwnsInfo, _spwnDelayMethods, _grpInfo, false, _validBehavior # 0, _validDest # 0] spawn THY_fnc_CSWR_spawn_and_go;

true;

 

 

THY_fnc_CSWR_spawn_and_go function:

Spoiler

// File: fn_CSWR_globalFunctions.sqf

params ["_spwnsInfo", "_spwnDelayMethods", "_grpInfo", "_isVeh", "_behavior", "_destsInfo"];
private [...];

// <code not envolving the _spwnDelayMethods>
             
// Check if _spwnDelayMethods is in the correct format:
if ( typeName _spwnDelayMethods isEqualTo "ARRAY" ) then {

		// SPAWN DELAY SECTION:
		// If _spwnDelayMethods is not empty, it's coz the group needs Spawn Delay:
		if ( count _spwnDelayMethods > 0 ) then {
			
			// Spawn Delay Validation Step 1/4:
			// Escape > If the object doesn't exist:
			{
				if ( isNil "_x" ) exitWith {
					// Flag to abort the group/vehicle spawn:
					_canSpwn = false;
					// Warning:
					systemChat format ["%1 SPAWN DELAY > %2 Make sure you SPELLED the trigger or target name(s) CORRECTLY. %3",
					CSWR_txtWarnHeader, _txt1, _txt2]; 
					sleep 5;
				};
			} forEach _spwnDelayMethods;

			// If the object doesn't exist, the spawn isn't available, so there's no reason the keep the validation process running:
			if !_canSpwn exitWith {};

			// Spawn Delay Validation Step 2/4:
			// Escape > If there is more than 1 timer, abort the spawn:
			{  // forEach _spwnDelayMethods:
				if ( { typeName _x isEqualTo "SCALAR" } count _spwnDelayMethods > 1 ) exitWith {
					// Flag to abort the group/vehicle spawn:
					_canSpwn = false;
					// Warning:
					systemChat format ["%1 SPAWN DELAY > %2 It's NOT allowed a group with more than 1 TIMER. %3",
					CSWR_txtWarnHeader, _txt1, _txt2]; sleep 5;
				};
			} forEach _spwnDelayMethods;
			// If the object doesn't exist, the spawn isn't available, so there's no reason the keep the validation process running:
			if !_canSpwn exitWith {};

			// Spawn Delay Validation Step 3/4:
			// Errors handling > If the method is Timer:
			{
				if ( typeName _x isEqualTo "SCALAR" ) then {
					// If the Timer value is 0 (invalid), delete it from the array and keep going the validation:
					// Important: editors during the mission edition will use "0" in array istead of leave that empty.
					if ( _x isEqualTo 0 || _x < 0 ) then { _spwnDelayMethods deleteAt _forEachIndex };
				};
			} forEachReversed _spwnDelayMethods;
			// Escape:
			if (count _spwnDelayMethods isEqualTo 0) exitWith {};

			// Spawn Delay Validation Step 4/4:
			// Escape > If some spawn delay method is in a wrong format, abort the spawn:
			{  // forEach _spwnDelayMethods:
				if ( !(typeName _x in ["OBJECT", "SCALAR"]) ) exitWith { 
					// Flag to abort the group/vehicle spawn:
					_canSpwn = false;
					// Warning:
					systemChat format ["%1 SPAWN DELAY > %2 Make sure you're using a timer, triggers, and targets without quotes, e.g: [5] or [varname_1] or [varname_1, varname_2] or [5, varname_1, varname_2]. %3",
					CSWR_txtWarnHeader, _txt1, _txt2]; sleep 5;
				};
			} forEach _spwnDelayMethods;

			// If everything is fine, Spawn Delay:
			if _canSpwn then {
				// Debug:
				if CSWR_isOnDebug then {
					// Debug monitor > How many units will spawn soon:
					CSWR_spwnDelayQueueAmount = CSWR_spwnDelayQueueAmount + _grpSize;
					publicVariable "CSWR_spwnDelayQueueAmount";
					// Debug:
					systemChat format ["%1 SPAWN DELAY > %2",CSWR_txtDebugHeader, _txt3];
					// Reading breather:
					sleep 2;
				};
				// Verify all Spawn Delay methods the group will use:
				[_tag, _spwnDelayMethods, _isVeh, _grpSize] call THY_fnc_CSWR_spawn_delay;
			};
		};
	// Otherwise, if the _spwnDelayMethods is not an array:
	} else {
		// Flag to abort the group/vehicle spawn:
		_canSpwn = false;
		// Warning:
		systemChat format ["%1 SPAWN DELAY > %2 %3",
		CSWR_txtWarnHeader, _txt1, _txt2];
		// Reading breather:
		sleep 5;
	};

// <code not envolving the _spwnDelayMethods>

true;

 

 

 

Both files on GitHub (i think it's not needed but):
https://github.com/aldolammel/Arma-3-Controlled-Spawn-And-Waypoints-Randomizr-Script/blob/main/cswr_for_your_mission/CSWRandomizr/fn_CSWR_population.sqf

https://github.com/aldolammel/Arma-3-Controlled-Spawn-And-Waypoints-Randomizr-Script/blob/main/cswr_for_your_mission/CSWRandomizr/fn_CSWR_globalFunctions.sqf

 

Share this post


Link to post
Share on other sites

 

 

["_spwnDelayMethods", 0] as parameter in fn_CSWR_globalFunctions.sqf ? why a number if you check an array:

if ( typeName _spwnDelayMethods isEqualTo "ARRAY" )... in fn_CSWR_globalFunctions.sqf?

 

Note : if (_spwnDelayMethods isEqualType [] ) ... is faster.

 

Share this post


Link to post
Share on other sites
1 hour ago, pierremgi said:

["_spwnDelayMethods", 0] as parameter in fn_CSWR_globalFunctions.sqf ? why a number if you check an array:

if ( typeName _spwnDelayMethods isEqualTo "ARRAY" )... in fn_CSWR_globalFunctions.sqf?

 

That was a typo. It should be:

["_spwnDelayMethods", [0]] or ["_spwnDelayMethods", []]

That zero is important for other stuff. Anyway, that's why I'm checking each value this function is receiving once many levels of Mission Editors are using this script and might sometimes face typos where they have no clue what's happening.

1 hour ago, pierremgi said:

if ( typeName _spwnDelayMethods isEqualTo "ARRAY" )... in fn_CSWR_globalFunctions.sqf?

I don't get the question. Yes, I'm checking if it's an array. Why?

Share this post


Link to post
Share on other sites
1 hour ago, thy_ said:

That zero is important for other stuff. Anyway, that's why I'm checking each value this function is receiving once many levels of Mission Editors are using this script and might sometimes face typos where they have no clue what's happening.

I don't get the question. Yes, I'm checking if it's an array. Why?

 

Because it was hard to understand why you parameter _spwnDelayMethods with zero by default then check if an array.
Anyway, both are possible. See params

params ["_side", ["_spwnsInfo", [[], ""]], ["_grpClasses", []], ["_form", ""], ["_behavior", ""], ["_destsInfo", ["", ""]], ["_spwnDelayMethods", 0,[0,[]]];

 

Anyway, two possible tracks:

- what happens if you name a trigger this_trigger_doesnt_exist ? (or any existing trigger?)

- did you check for ghost extra character? (they can be visible if you encode your code in ANSI for example (instead of UTF-8)
There are plenty of ghost characters when copying/pasting code.

Example:
 

Spoiler

// fn_CSWR_population.sqf file:
if ( !isServer || !CSWR_isOn ) exitWith {};
?
[] spawn {
      
    if ( CSWR_isOnBLU && count CSWR_spwnsAllBLU > 0 ) then {
        // <code not envolving the trigger>
        
        // Blufor population:
        [BLUFOR, [CSWR_spwnsBLU], CSWR_group_BLU_light, _form_BLU_2, _be_SAFE, [_move_ANY], [this_trigger_doesnt_exist?]]? call THY_fnc_CSWR_add_group;
    
    };
};?
?
true;

 

Same for other scripts.

That said, I didn't find any error, in rpt file or screen, testing parts of your code, after removing these ghost characters.
 

 

 

  • Like 2

Share this post


Link to post
Share on other sites
4 hours ago, pierremgi said:

- what happens if you name a trigger this_trigger_doesnt_exist ? (or any existing trigger?)

 

QblHGSL.png

 

If there's that trigger, everything runs as expected: well.

 

4 hours ago, pierremgi said:

- did you check for ghost extra character? (they can be visible if you encode your code in ANSI for example (instead of UTF-8)

 

Yes, I'm aware of it. VSCode flags this thing. Just for example (below) but here in my code this is not the problem.
 

Spoiler

sxCM3ob.png

 

 

Share this post


Link to post
Share on other sites
4 hours ago, pierremgi said:

That said, I didn't find any error, in rpt file or screen, testing parts of your code, after removing these ghost characters.

 

No worries. This post is all I need to find the problem! And be sure if I find where the inconsistency is, I register the information here. S2.

Share this post


Link to post
Share on other sites
10 hours ago, thy_ said:

Well, it's obvious I'm missing something, so let's show the entire flow from the row where we set the trigger (that should be a reference of a true Eden trigger) until the validation of that.

 

Now we're getting somewhere. The error message is quite clear. It's not your condition checking that's the problem. It errors on this line:

 

[BLUFOR, [CSWR_spwnsBLU], CSWR_group_BLU_light, _form_BLU_2, _be_SAFE, [_move_ANY], [this_trigger_doesnt_exist]] call THY_fnc_CSWR_add_group;

 

cause when you "set the trigger" you build an array like this: [this_trigger_doesnt_exist]

This essentially does two operations in order: 

  1. Read value from variable "this_trigger_doesnt_exist"
  2. Create an array and insert said reference in the first index.

In step 1 if the variable is nil (undefined) you will be reading the value from an undefined location in memory.

In essence you are asking "What's in the box?" and the game goes "What box?" and throws the error.

You simply cannot do that directly.

 

So what to do? That depends on what your requirement is. 

 

Do you know what the trigger will be named? Great. Just use getVariable to retrieve the value with a reasonable default e.g. objNull. Use isNull to verify the quality of the input.

 

Is the trigger input by the user? Have the user input a string instead/as well and parse it, getVariable is your friend here too.

 

Is the value set by another script?

Prior to creating the array verify that the expected variable is actually defined using isNil "variable_name".

 

  • Like 1

Share this post


Link to post
Share on other sites
24 minutes ago, mrcurry said:

Is the trigger input by the user? Have the user input a string instead/as well and parse it, getVariable is your friend here too.

 

This is the case!

 

That sets an array because the user can add more than one trigger. They actually can do:

  • [my_trigger_1, my_target_1, 10]   // There are 3 ways to activate a spawn here: by trigger, by killing/destroying, by timer (minutes); The operation among the options is "OR", never "AND";
  • [my_trigger_1, my_trigger_2my_trigger_3 // Max 3 elements.
  • [my_target_1, my_target_2, my_target_3]
  • [10]
  • [0] or []  // no spawn delay, insta spawn

 

24 minutes ago, mrcurry said:

In step 1 if the variable is nil (undefined) you will be reading the value from an undefined location in memory.

In essence you are asking "What's in the box?" and the game goes "What box?" and throws the error.

You simply cannot do that directly.

 

Interesting. I will do some tests here now.

  • Like 1

Share this post


Link to post
Share on other sites
6 hours ago, thy_ said:

That sets an array because the user can add more than one trigger. They actually can do:

  • [my_trigger_1, my_target_1, 10]   // There are 3 ways to activate a spawn here: by trigger, by killing/destroying, by timer (minutes); The operation among the options is "OR", never "AND";
  • [my_trigger_1, my_trigger_2my_trigger_3 // Max 3 elements.
  • [my_target_1, my_target_2, my_target_3]
  • [10]
  • [0] or []  // no spawn delay, insta spawn

 

Neat!

You know, you could just say that it's the users responsibility to verify that the trigger exists before it's defined like that... 😛

 

But incase you don't wanna go that route I'd go with requesting the variable name of the trigger or target as a string instead and then at the start of your loop where you validate the list read from missionNamespace to get the reference and update the input array with the correct value. Any code after won't know the difference.

 

Example:

Assume _inputArray contains the list to validate.

{

     if( _x isEqualType "" ) then {

          if( isNil _x ) then {

               // It's not a valid variable name, either throw an error or handle it as regular string 

          } else {

              // It's a valid variable, get the value

              private _val = missionNamespace getVariable _x;

              // Here you could do further validation that _val contains an expected value i.e. it's an Object.

 

              _x = _val; // set local ref for further validation in the loop

              _inputArray set [_forEachIndex, _val]; // set array ref for use later in the script

          };

     };

 

     // Handle _x == Object and _x == Number after

     

} forEach _inputArray;

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
4 hours ago, mrcurry said:

you could just say that it's the users responsibility to verify that the trigger exists before it's defined like that... 😛

 

Trust me, I do with 50+ pages, including how works spawn-delay (this issue we are talking about). 😅 But you know only advanced editors will check documentation...

Spoiler

CXTzSnb.pngLI6rEqY.png

 

 

 

4 hours ago, mrcurry said:

But incase you don't wanna go that route I'd go with requesting the variable name of the trigger or target as a string instead and then at the start of your loop where you validate the list read from missionNamespace to get the reference and update the input array with the correct value. Any code after won't know the difference.

 

Topic solved! 😎

See you at my next unsolved challenge, hehe.

  • Like 1

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×