I’ve been attempting to script something that allows players to vote on one map option, out of 3 randomly selected ones. Basically:
-Three options are randomly selected from a given bunch of maps from ServerStorage ()
-These three options are sent to the client and representing on a screen GUI with 3 buttons, one representing each choice.
-Once a player clicks a choice, it should update the votes for that map on the server, and then after a set amount of seconds establish a winner.
Basically something similar to Phantom Forces.
I’ve spent more time than I’d like to admit on this, trying to come up with solutions but I just don’t seem to know how to get it to work no matter what I do. I know that it’s most likely a flaw in the way(s) I tried to handle client-server communication. Any examples or in depth explanation on how I could tackle this would be highly appreciated.
I’m in the process of making something similar myself… and my draft will more or less be implemented like so:
Draft
--// Select the "3" random maps and setup a selection pool like so:
local SelectionPool = {Selection1 = 0; Selection2 = 0 Selection3 = 0}
--// Asynchronously send all three choice names to all clients (+ timeout)..
local getPlayerVotes;
function getPlayerVotes(timeout)
local current = coroutine.running()
local tasks = {}
--|Participants| = players in-game;
for player in Participants do
--|GetPlayerVote| = relevant `RemoteFunction`
--|Selection(n)| = choice from pool
local vote = function()
local success, vote = pcall(GetPlayerVote.InvokeClient, GetPlayerVote, player, {selection1, selection2, selection3})
if success then
-- update pool to reflect player choice
SelectionPool[vote] = SelectionPool[vote] + 1;
end
end
tasks[#tasks+1] = vote;
end
local sleep = tick() + timeout
while true do
if tick() < sleep then
coroutine.resume(current)
break;
end
for i = 1, #tasks do
--InvokeClient = yielding function, so run in separate thread
coroutine.wrap(tasks[i])()
end
coroutine.yield()
end
end
--// Get choice with the most votes w/ table.sort
--//CLIENT
GetPlayerVote.OnClientInvoke = function(data)
-- Create choices from `data`
local connection;
connection = choiceButton.MouseButtion1Clicked:connect(function()
connection:Disconnect();
return choiceButton.Name -- or Text
end)
end
I haven’t tested these snippets myself. This is simply a general idea of how I’d go about doing it!
EDIT: I just finally got this this point in my own project. Using RemoteEvents instead, much more manageable than the draft above…
--|pool| = map choices
function getPlayerVotes(players, pool, timeout)
local voteSignal = Instance.new("BindableEvent")
local voteTimeout= tick() + (timeout or 30)
local awaitVote;
local votes = pool;
local tasks = {}
for _,player in next, players do
local vote = function()
local success, err = pcall(GetPlayerVote.FireClient, GetPlayerVote, player, votes)
if success then
--//sent successfully
end
end
tasks[#tasks+1] = vote;
end
coroutine.wrap(function()
repeat wait() until tick() >= voteTimeout;
voteSignal:Fire()
end)
for i, task in next, tasks do
coroutine.wrap(tasks[i])()
end
awaitVote = GetPlayerVote.OnServerEvent:Connect(function(player, vote)
if player and vote then
votes[vote] = (votes[vote] or 0) + 1
voteSignal:Fire()
end
end)
voteSignal.Event:Wait()
awaitVote:Disconnect()
table.sort(votes, function(x,y) return x > y end)
return next(votes);
end
For something like this, seeing as RemoteFunction is yielding and players won’t instantly send in their vote, what about using a RemoteEvent which goes both ways?
It also means you could just use :FireAllClients (assuming you want all players = participants) to start the voting, and you don’t need to get into so many different coroutines
The workflow I had in my head was something like this:
New Vote Starts on Server with 3 random maps
Server uses :FireAllClients() to send the 3 new maps
Clients send back their vote with :FireServer()
Server checks if vote is still active and the user can vote
A few things that need to be picked up upon are:
Users should only receive one vote; if they select a map then change it, you need to make sure you remove the vote from the old selection and add it to the new one [this is assuming you can change your vote after you select it]
New players which join the server after the votes have been fired to the clients will need to retrieve these votes separately
Interesting… the way I saw it, assuming that RemoteFunctions did yield indefinitely, the timeout + remotefunction combination would eliminate the need for two separate RemoveEvents. My implementation would centralize all these “checks”, vs having to handle them in separate environments.
It doesn’t take into account new players (non-participants?) or vote changes (as you mentioned), but the that logic can squeezed somewhere in there.
Disconnecting the connection after the vote takes care of the one vote per player issue (but doesn’t allow changes)