Jump to content
Sign in to follow this  
mikemhz

Vote Script: Ways to implement voting in MP missions.

Recommended Posts

My intention:

  • I want to iterate through all units, setting their unique voteValue to [0,0,0,0].
  • I want all units to be able to change that value through my dialog (for example change theirs to [0,1,0,0] indicating they chose the second option).
  • During the count down I want to iterate through all units, getting their voteValue to find the total votes and display that on screen.
  • Then I will do something with the total votes once the countdown ends.

The reason I'm doing it like this, is to avoid duplicate votes and a large array of players which is more complicated to code. Suggestions are welcome!

My code:

init.sqf:

TOTAL_votes = [0,0,0,0];
execVM "countdown.sqf";

countdown.sqf

_countdown = 30;
while {_countdown > 0} do {

   TOTAL_votes = [0,0,0,0];

   {
       [color="#FF0000"]_VoteArray = _x getVariable "_voteValue";[/color] [color="#008000"]//This is where I get an error. Undefined variable.[/color]
       //count votes
   } forEach allUnits;

   _NW_votes = TOTAL_votes select 0;
   _NE_votes = TOTAL_votes select 1;
   _SW_votes = TOTAL_votes select 2;
   _SE_votes = TOTAL_votes select 3;

   hint format ["%1\nNW:%2\nNE:%3\nSW:%4\nSE:%5",_countdown,_NW_votes,_NE_votes,_SW_votes,_SE_votes];

   sleep 1;

   _countdown = _countdown - 1;

}

I create a multiplayer mission in the editor with an object and a player.

I initialise the player with "_voteValue = [0,0,0,0];".

I initialise the object ("polling booth") with an addaction which opens my voting dialog.

For each option in the dialog there is a button. On each button I run this action:

action = "[Player,[1,0,0,0]] execVM ""addVote.sqf""";

addVote.sqf

_thePlayer = _this select 0;
_theArray = _this select 1;
_thePlayer setVariable ["_voteValue",_theArray];
hint format ["%1 's VoteValue = %2",_thePlayer,_thePlayer getVariable "_voteValue"];

Edited by mikemhz

Share this post


Link to post
Share on other sites

You dont want to be counting votes while your coutingDown, only do it at the end once the voting time is over.

For _voteArray use the getVariable with a default e.g _VoteArray = _x getVariable ["_voteValue", [0,0,0,0]]; then if the client has not voted you get the default, also saves having to init a default value on clients.

Do not use allUnits as this will be AI aswell. Use playableUnits with a check for isPlayer or (playableUnits + switchableUnits) to allow you to test in the editor/SP.

Your foreach does not actually add anything up. e.g

	//Add all the client votes together

//Reset total votes
TOTAL_votes = [0,0,0,0];

//Loop around all clients
{
	//If the client is an actual player
	if (isPlayer _x) then {
		//Get client vote value
		_clientVote = _x getVariable ["_voteValue",[0,0,0,0]];

		//For each clients vote value
		{
			//Add client value to total votes
			TOTAL_votes set [_forEachIndex, ((TOTAL_votes select _forEachIndex) + (_x))];
		}forEach _clientVote;

		//Reset clients vote to [0,0,0,0] as it has been processed
		_x setVariable ["_voteValue", [0,0,0,0], true];
	};
}forEach (playableUnits + switchableUnits);

Your countdown.sqf should be serverSide only.

You could also use this to initialize a variable to allow the pollingbooth addaction (use the PVed variable in the addActions condition). e.g

//Init of object on clients
pollingBoothObject addAction ["VOTE!!",{createDialog ??}, [], 1, true, true, "", "pollingBoothOpen"];

//INIT server and client with
pollingBoothOpen = false;

//On the server when countDown starts
pollingBoothOpen = true;
publicVariable "pollingBoothOpen"; //This will enable the action to be seen
//When countDown has finished
pollingBoothOpen = false;
publicVariable "pollingBoothOpen"; //The action will not be available

//You could even use this PV to automatically close the dialog if the voting time is finished
"pollingBoothOpen" addPublicVariableEventHandler {
if (!(_this select 1)) then {
	closeDialog ??;
};
};	

No real need for the addVote script, you could just do it from the action. e.g

action = "player setVariable [""_voteValue"",[1,0,0,0],true]; hintSilent ""you have voted for option 1"";"

Remember to add the true param to the setVariable to broadcast it to the server.

Well theres some fixes and some ideas to maybe try out

Edited by Larrow

Share this post


Link to post
Share on other sites

THANKS!

I do actually want to count votes while counting down. A live update for all users on the server so that they can make an informed vote and change it at any time during the countdown. If there's any special reason why this is unwise for performance or security then let me know.

Also, when the mission starts, there will be no AI (they will be spawned after the vote, depending on the outcome).

The idea is to have a period of voting at the start of the mission and then have the polling station deleted from the map, all the variables cleared and the appropriate script executed, which will teleport all players, initialise all props, AI and objectives.

So here's the working code:

init.sqf

TOTAL_votes = [0,0,0,0];
execVM "countdown.sqf";

countdown.sqf

//Begin countdown
_countdown = 30;
while {_countdown > 0} do {

//Re-count votes
TOTAL_votes = [0,0,0,0];

//For each client
{
	//If client is player
	if (isPlayer _x) then {
		//Get client vote value
		_clientVote = _x getVariable ["_voteValue",[0,0,0,0]];
		//For each clients vote value
           {
               //Add client value to total votes
               TOTAL_votes set [_forEachIndex, ((TOTAL_votes select _forEachIndex) + (_x))];
           }forEach _clientVote;
	};

} forEach (playableUnits + switchableUnits);

//Display current standings to all players
_NW_votes = TOTAL_votes select 0;
_NE_votes = TOTAL_votes select 1;
_SW_votes = TOTAL_votes select 2;
_SE_votes = TOTAL_votes select 3;

hint format ["%1\nNW:%2\nNE:%3\nSW:%4\nSE:%5",_countdown,_NW_votes,_NE_votes,_SW_votes,_SE_votes];

//Continue countdown
sleep 1;

_countdown = _countdown - 1;

};
execVM "voteResults.sqf";

voteResults.sqf

//Array of max votes in case it's a draw
_winners = [];
//To keep track of the highest vote value
_maxValue = 0;
//For naming the winner at the end
_winnerName = "Nothing";

//Find the highest number of votes in TOTAL_votes array.
{
if (TOTAL_votes select _forEachIndex > _maxValue) then {
	_maxValue = TOTAL_votes select _forEachIndex;
}
}forEach TOTAL_votes;

//Find all options which have the maximum number of votes and add them to _winners array
{
if (TOTAL_votes select _forEachIndex == _maxValue) then {
	_winners = _winners + [_forEachIndex];
}
}forEach TOTAL_votes;

//Count the numebr of winners
_numberOfWinners = count _winners;

//Select at random one winner from the array of winners
_winner = _winners select (floor(random _numberOfWinners));

//Name the winner.
if(_winner == 0) then { _winnerName = "NW" };
if(_winner == 1) then { _winnerName = "NE" };
if(_winner == 2) then { _winnerName = "SW" };
if(_winner == 3) then { _winnerName = "SE" };

//Display the winner along with the number of votes it recieved.
hint format ["The winner is %1 with %2 votes.",_winnerName,_maxValue];

dialog action:

action = "player setVariable [""_voteValue"",[0,1,0,0],true]";

Edited by mikemhz

Share this post


Link to post
Share on other sites
I do actually want to count votes while counting down. A live update for all users on the server so that they can make an informed vote and change it at any time during the countdown.
Ok that makes sense.

The only problem i see in what you have written above is that this is running on every machine. It would be better if this only ran server side, one machine doing the math and making the final decision. At the moment if you had more than one winner all machines could come up with a different winner due to the random selection from _winners.

Share this post


Link to post
Share on other sites

Thanks for the heads up. I wrapped everything in my init.sqf with if(isServer) then {};

But this creates problems. E.g. hint, setPos.

Is there a way to send generic commands to each player/globally from within a script running only on the server?

Share this post


Link to post
Share on other sites

Setpos should not be a problem as it is a global command.

Hint via public Variable

//init.sqf
if (!(isDedicated)) then {
//client side PVEH
"globalHint" addPublicVariableEventHandler  {
	hint (_this select 1);
};
};

//where ever you need to send a hint
globalHint = "my message to all";
publicVariable "globalHint";
//Hint the server aswell if it is a hosted server
if (!(isNull player)) then {
hint globalHint;
};

Hint via BIS_fnc_MP

//init.sqf
if (!(isDedicated)) then {
//client side function
myTag_fnc_globalHint = {
	hint _this;
};
};

//where ever you need to send a hint
//true hints all clients you can change this for side, a player, an array of player etc
["my message to all","myTag_fnc_globalHint",true,false] call BIS_fnc_MP;

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
Sign in to follow this  

×