Jump to content
KutPax

Requesting help with missionNamespace set/getVariable for Multiplayer

Recommended Posts

Hiya,

I'm in a little bit of a pickle. I'm trying to make a GUI menu which has buttons in them. When one button is pressed, it will change the text or color of the other button and save this information to the server or mission so that whenever another player opens the GUI menu, it displays the same it does for the other player.

Now I've tried to find the information I need on a lot of websites (Including the wiki), only without luck. I'll show you an example of what I have right now:

fn_GUITest.sqf

Quote

createDialog "GUITest";

buttonSetAction [6961, "player exec ""Test\Script1.sqf"""];
buttonSetAction [6962, "player exec ""Test\Script2.sqf"""];

_Button1String = missionNamespace getVariable "Button01";
_Button2String = missionNamespace getVariable "Button02";

publicVariable "_Button1String";
publicVariable "_Button2String";

ctrlSetText [6961, _Button1String];
ctrlSetText [6962, _Button2String];


Script1.sqf
 

Quote

missionNamespace setVariable ["Button02","BUTTON02"];

_Button1String = missionNamespace getVariable "Button01";
_Button2String = missionNamespace getVariable "Button02";

publicVariable "_Button1String";
publicVariable "_Button2String";

ctrlSetText [6962, _Button2String];


Script2.sqf

Quote

missionNamespace setVariable ["Button01","BUTTON01"];

_Button1String = missionNamespace getVariable "Button01";
_Button2String = missionNamespace getVariable "Button02";

publicVariable "_Button1String";
publicVariable "_Button2String";

ctrlSetText [6961, _Button1String];


Any help would be greatly appreciated!

Share this post


Link to post
Share on other sites

Oof several mistakes there:

1) 

Quote

 


buttonSetAction [6961, "player exec ""Test\Script1.sqf"""];

 

buttonSetAction only accepts sqs (old) code, sqf is what you want. Same applies to exec and execVM, exec is for sqs, execVM is for sqf. The modern User Interface Event Handlers (UIEH) are the solution, specifically the onButtonClick one:

_display = findDisplay IDD;
_ctrl = _display displayCtrl 6961;
_ctrl ctrlAddEventhandler ["ButtonClick",{
	player execVM "script1.sqf";
}];

2)

Quote

_Button1String = missionNamespace getVariable "Button01";

 

Button01 is not defined in missionNamespace at that point.

 

3)

Quote

 


publicVariable "_Button1String";

 

publicVariable can only broadcast global variables. Underscore means the variable is local (only accessible in the script).

 

4)

Quote

 


missionNamespace setVariable ["Button02","BUTTON02"];

 

You are storing the literal string "BUTTON02" into missionNamespace, not the control, which wouldn't be possible either way (you can't store UI related variables, eg. displays and controls, in missionNamespace).

 

So, let's see what we can do here. First of all, your display config should look like this at the beginning:

class GUITest
{
	idd = 12345;
	class controls 
	{
		...

Remember the idd you write here. We can use it in the script as follows:

createDialog "GUITest";
_display = findDisplay 12345;

Let's make use of those UIEH I mentioned earlier:

_button1 = _display displayCtrl 6961;
_button1 ctrlAddEventhandler ["ButtonClick",{
	params ["_button1"];
	missionNamespace setVariable ["TAG_button1State", true, true];
	_button1 ctrlSetText "ACTIVE";
	_display = ctrlParent _button1;
	_button2 = _display displayCtrl 6962;
	_button2 ctrlSetText "INACTIVE";
}];
// Same thing for button 2:
_button2 = _display displayCtrl 6962;
_button2 ctrlAddEventhandler ["ButtonClick",{
	params ["_button2"];
	missionNamespace setVariable ["TAG_button1State", false, true];
	_button2 ctrlSetText "ACTIVE";
	_display = ctrlParent _button2;
	_button1 = _display displayCtrl 6961;
	_button1 ctrlSetText "INACTIVE";
}];

To change the buttons locally we just update the them with the usual ui command ctrlSetText. To broadcast the change to the other players we use the setVariable command with the following syntax:

missionNamespace // which namespace? the global one, global as in the player's game
setVariable // the command
[
	"TAG_button1State", // TAG is your name tag to make it unique, eg for me it is TER
	true, // the value that the global variable should have
	true // true to set the variable in the global namespace of the other players
];

And lastly add these lines to the script (full one will be at the end of the post again), to get and set the states when opening the dialog:

_state = missionNamespace getVariable ["TAG_button1State", true];
if (_state) then {
	_button1 ctrlSetText "ACTIVE";
	_button2 ctrlSetText "INACTIVE";
} else {
	_button1 ctrlSetText "INACTIVE";
	_button2 ctrlSetText "ACTIVE";
};

Explanation:

_state = missionNamespace getVariable ["TAG_button1State", true];

Get the global variable from the global namespace. We can't be sure it was set yet by another client or by ourselves, so if it does not exist it will fall back to true.

if (_state) then {
//...
} else {
//...
};

There are only two possible states. Either button1 is active or button2 is.

 

Here is the full script then:

createDialog "GUITest";
_display = findDisplay 12345;

_button1 = _display displayCtrl 6961;
_button1 ctrlAddEventhandler ["ButtonClick",{
	params ["_button1"];
	missionNamespace setVariable ["TAG_button1State", true, true];
	_button1 ctrlSetText "ACTIVE";
	_display = ctrlParent _button1;
	_button2 = _display displayCtrl 6962;
	_button2 ctrlSetText "INACTIVE";
}];
// Same thing for button 2:
_button2 = _display displayCtrl 6962;
_button2 ctrlAddEventhandler ["ButtonClick",{
	params ["_button2"];
	missionNamespace setVariable ["TAG_button1State", false, true];
	_button2 ctrlSetText "ACTIVE";
	_display = ctrlParent _button2;
	_button1 = _display displayCtrl 6961;
	_button1 ctrlSetText "INACTIVE";
}];

_state = missionNamespace getVariable ["TAG_button1State", true];
if (_state) then {
	_button1 ctrlSetText "ACTIVE";
	_button2 ctrlSetText "INACTIVE";
} else {
	_button1 ctrlSetText "INACTIVE";
	_button2 ctrlSetText "ACTIVE";
};

 

Note 1: The display is only updated when opening it. It is "easy" (in theory) to update the display when another user clicks on a button while all other have it open but it is a fairly advanced topic and complicated to explain 😅 (if you need it I can try to though).

Note 2: The script can be optimized in several ways. I have gone for the most simple solution. If you want to have the best possible solution (which might not be understandable at first) I can also write that one out.

 

Well that is all. I haven't tested it but I am confident in my UI skills 🙂 If you have any questions after trying it out feel free to ask.

 

  • Like 3

Share this post


Link to post
Share on other sites

Thanks for the response! I'll go give this a try and I'll let you know if it works or not

Share this post


Link to post
Share on other sites

It works like a charm @7erra, thanks for the help and thanks for teaching me some new things!

Share this post


Link to post
Share on other sites

@7erra I'm now trying to make something with RscEdit where the player can enter text into it and save it by pressing enter or by pressing a button, do you have any idea how I could do that?

Share this post


Link to post
Share on other sites

You can return the text of RscEdit control with ctrlText and save it with a global variable:

_btnSave = _display displayCtrl 1234;
_btnSave ctrlAddEventhanddler ["ButtonClick",{
	params ["_btnSave"];
	_display = ctrlParent _btnSave;
	_ctrlEdit = _display displayCtrl 5678;
	TAG_enteredText = ctrlText _ctrlEdit;
}];

Where 1234 is the idc of the button controls and 5678 is the idc of the RscEdit.

  • Like 1

Share this post


Link to post
Share on other sites

It does not seem to want to save the text I've written in the RscEdit, do I need to do something with missionNamespace?

Share this post


Link to post
Share on other sites
_btnSave = _display displayCtrl 1234;
_btnSave ctrlAddEventhanddler ["ButtonClick",{
	params ["_btnSave"];
	_display = ctrlParent _btnSave;
	_ctrlEdit = _display displayCtrl 5678;
	TAG_enteredText = ctrlText _ctrlEdit;
	publicVariable "TAG_enteredText";
}];
_ctrlEdit = _display displayCtrl 5678;
_ctrlEdit ctrlSetText (missionNamespace getVariable ["TAG_enteredText", ""]);

To load the text when display is opened.

Edited by 7erra
publicVariable
  • Like 1

Share this post


Link to post
Share on other sites

You're a legend, thx @7erra ! I'll let you know when I've tested it in MP if it works

Share this post


Link to post
Share on other sites

Oh if you want to have all players have the same RscEdit text you have to broadcast it after declaring it. I added it to my last post.

  • Like 1

Share this post


Link to post
Share on other sites

@7erra Is it publicVariable which does this? If so, do I need to add that to the buttons aswell?

Share this post


Link to post
Share on other sites

@7erra I've been playing around a little bit and if I add publicVariable "TAG_enteredText"; the script doesn't work anymore (Doesn't want to save anything). Any idea on how I can deal with this?

I'm also trying to make this work with 3 buttons now. Whenever 1 button gets pressed, it gets a certain color background, and the other two also get a specific color background (Press button 1, button 1 turns red, button 2 and 3 turn blue). Any idea how I can adapt the lines you've sent me for this?

Cheers!

Share this post


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

I've been playing around a little bit and if I add publicVariable "TAG_enteredText"; the script doesn't work anymore (Doesn't want to save anything). Any idea on how I can deal with this?

Hm seems weird. Maybe try this instead:

missionNamespace setVariable ["TAG_enteredText", ctrlText _ctrlEdit];
/* instead of:
TAG_enteredText = ctrlText _ctrlEdit;
publicVariable "TAG_enteredText";
*/

 

1 hour ago, KutPax said:

I'm also trying to make this work with 3 buttons now

That's going to change the script significantly since we can't use booleans (true/false) anymore. We can either do it with the same logic as previously:

Spoiler

createDialog "GUITest";
_display = findDisplay 12345;

_button1 = _display displayCtrl 6961;
_button1 ctrlAddEventhandler ["ButtonClick",{
	params ["_button1"];
	missionNamespace setVariable ["TAG_activeButton", 1, true];
	_button1 ctrlSetText "ACTIVE";
	_display = ctrlParent _button1;
	_button2 = _display displayCtrl 6962;
	_button2 ctrlSetText "INACTIVE";
	_button3 = _display displayCtrl 6963;
	_button3 ctrlSetText "INACTIVE";
}];
// Same thing for button 2:
_button2 = _display displayCtrl 6962;
_button2 ctrlAddEventhandler ["ButtonClick",{
	params ["_button2"];
	missionNamespace setVariable ["TAG_activeButton", 2, true];
	_button2 ctrlSetText "ACTIVE";
	_display = ctrlParent _button2;
	_button1 = _display displayCtrl 6961;
	_button1 ctrlSetText "INACTIVE";
	_button3 = _display displayCtrl 6963;
	_button3 ctrlSetText "INACTIVE";
}];
// Same thing for button 3:
_button3 = _display displayCtrl 6963;
_button2 ctrlAddEventhandler ["ButtonClick",{
	params ["_button3"];
	missionNamespace setVariable ["TAG_activeButton", 3, true];
	_button3 ctrlSetText "ACTIVE";
	_display = ctrlParent _button3;
	_button1 = _display displayCtrl 6961;
	_button1 ctrlSetText "INACTIVE";
	_button2 = _display displayCtrl 6962;
	_button2 ctrlSetText "INACTIVE";
}];

_state = missionNamespace getVariable ["TAG_activeButton", 1];
switch _state do {
	case 1: {
		_button1 ctrlSetText "ACTIVE";
		_button2 ctrlSetText "INACTIVE";
		_button3 ctrlSetText "INACTIVE";
	};
	case 2: {
		_button1 ctrlSetText "INACTIVE";
		_button2 ctrlSetText "ACTIVE";
		_button3 ctrlSetText "INACTIVE";
	};
	case 3: {
		_button1 ctrlSetText "INACTIVE";
		_button2 ctrlSetText "INACTIVE";
		_button3 ctrlSetText "ACTIVE";
	};
};

Or we do it smart:

#define IDC_MIN 6961
#define IDC_MAX 6963
private _fncUpdateButtonStates = {
	params ["_ctrlButtonActive"];
	private _idcActive = ctrlIDC _ctrlButtonActive;
	private _display = ctrlParent _ctrlButtonActive;
	for "_idc" from IDC_MIN to IDC_MAX do {
		private _ctrlButton = _display displayCtrl _idc;
		if (_idc == _idcActive) then {
			_ctrlButton ctrlSetText "ACTIVE";
		} else {
			_ctrlButton ctrlSetText "INACTIVE";
		};
	};
	if (missionNamespace getVariable ["TAG_idcActive", MIN_IDC] != _idcActive) then {
		missionNamespace setVariable ["TAG_idcActive", _idcActive, true];
	};
};

createDialog "GUITest";
private _display = findDisplay 12345;
private _idcActive = missionNamespace getVariable ["TAG_idcActive", IDC_MIN];
for "_idc" from IDC_MIN to IDC_MAX do {
	private _ctrlButton = _display displayCtrl _idc;
	_ctrlButton ctrlAddEventhandler ["ButtonClick", _fncUpdateButtonStates];
	if (_idc == _idcActive) then {[_ctrlButton] call _fncUpdateButtonStates};
};

The logic behind the second code is a bit more advanced but you can have as many buttons as you want. I also added some performance optimizations (not that they really matter in UIs).

 

At least I hope those codes work...

  • Like 1

Share this post


Link to post
Share on other sites

I'll give both of them a go when I get home from work tomorrow, thanks for the response again, you've been a huge help so far!

Share this post


Link to post
Share on other sites

Heya @7erra ,

Everything works great! I can't even express how thankful I am!

I've only got one more question and then I'm out of your hair. Earlier you've sent the following code:
 

Quote

_btnSave = _display displayCtrl 1234; _btnSave ctrlAddEventhandler ["ButtonClick",{ params ["_btnSave"]; _display = ctrlParent _btnSave; _ctrlEdit = _display displayCtrl 5678; TAG_enteredText = ctrlText _ctrlEdit; publicVariable "TAG_enteredText"; }]; _ctrlEdit = _display displayCtrl 5678; _ctrlEdit ctrlSetText (missionNamespace getVariable ["TAG_enteredText", ""]);


This code allows me to save the text an rscedit box contains with the press of a button (And it works great!), only I was hoping to save the text in a different manner. I was thinking of using oneachframe or whenever closeDialog 0; is executed. Do you have any idea how I can formulate something like this?

Share this post


Link to post
Share on other sites

Each frame would be very excessive. You would be saving the variable 60 (or 30 times, depending on fps) every second. Broadcasting it too would be an absolute nightmare for performance. Instead you can save it when the display is closed:

_display displayAddEventhandler ["Unload", {
	params ["_display"];
	private _ctrlEdit = _display displayCtrl 9876;
	missionNamespace setVariable ["TAG_enteredText", ctrlText _ctrlEdit, true];
}];

It would also be possible to keep track of the text while it is entered but that would lead to more network traffic while only giving a little bit more advantage over this approach.

  • Like 2

Share this post


Link to post
Share on other sites

It worked like a champ, I'd like to sincerely thank you for your help, not only have you fixed this script, but you've also taught me a lot of new tips and tricks. Thank you!

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

×