Source: gamesetup/Controllers/ReadyController.js

/**
 * Ready system:
 *
 * The ready mechanism protects the players from being assigned to a match with settings they didn't explicitly agree with.
 * It shall be technically possible to start a networked game until all participating players formally agree with the chosen settings.
 *
 * Therefore assume the readystate from the user interface rather than trusting the server whether the current player is ready.
 * The server may set readiness to false but not to true.
 *
 * The ReadyController class stores the ready state of the current player and fires an event if the agreed settings changed.
 */
class ReadyController
{
	constructor(netMessages, gameSettingsController, playerAssignmentsController)
	{
		this.playerAssignmentsController = playerAssignmentsController;
		this.gameSettingsController = gameSettingsController;

		this.resetReadyHandlers = new Set();
		this.previousAssignments = {};

		// This variable keeps track whether the local player is ready
		// As part of cheat prevention, the server may set this to NotReady, but
		// only the UI may set it to Ready or StayReady.
		this.readyState = this.NotReady;

		netMessages.registerNetMessageHandler("ready", this.onReadyMessage.bind(this));
		gameSettingsController.registerSettingsChangeHandler(this.onSettingsChange.bind(this));
		playerAssignmentsController.registerClientJoinHandler(this.onClientJoin.bind(this));
		playerAssignmentsController.registerClientLeaveHandler(this.onClientLeave.bind(this));
	}

	registerResetReadyHandler(handler)
	{
		this.resetReadyHandlers.add(handler);
	}

	onClientJoin(newGUID, newAssignments)
	{
		if (newAssignments[newGUID].player != -1)
			this.resetReady();
	}

	onClientLeave(guid)
	{
		if (g_PlayerAssignments[guid].player != -1)
			this.resetReady();
	}

	onReadyMessage(message)
	{
		let playerAssignment = g_PlayerAssignments[message.guid];
		if (playerAssignment)
		{
			playerAssignment.status = message.status;
			this.playerAssignmentsController.updatePlayerAssignments();
		}
	}

	onPlayerAssignmentsChange()
	{
		// Don't let the host tell you that you're ready when you're not.
		let playerAssignment = g_PlayerAssignments[Engine.GetPlayerGUID()];
		if (playerAssignment && playerAssignment.status > this.readyState)
			playerAssignment.status = this.readyState;

		for (let guid in g_PlayerAssignments)
			if (this.previousAssignments[guid] &&
				this.previousAssignments[guid].player != g_PlayerAssignments[guid].player)
			{
				this.resetReady();
				return;
			}
	}

	onSettingsChange()
	{
		this.resetReady();
	}

	setReady(ready, sendMessage)
	{
		this.readyState = ready;

		if (sendMessage)
			Engine.SendNetworkReady(ready);

		// Update GUI objects instantly if relevant settingchange was detected
		let playerAssignment = g_PlayerAssignments[Engine.GetPlayerGUID()];
		if (playerAssignment)
		{
			playerAssignment.status = ready;
			this.playerAssignmentsController.updatePlayerAssignments();
		}
	}

	resetReady()
	{
		// The gameStarted check is only necessary to allow the host to
		// determine random items after clicking start.
		if (!g_IsNetworked || this.gameSettingsController.gameStarted)
			return;

		for (let handler of this.resetReadyHandlers)
			handler();

		if (g_IsController)
		{
			Engine.ClearAllPlayerReady();
			this.playerAssignmentsController.updatePlayerAssignments();
		}
		else if (this.readyState != this.StayReady)
			this.setReady(this.NotReady, false);
	}

	getLocalReadyState()
	{
		return this.readyState;
	}
}

ReadyController.prototype.NotReady = 0;

ReadyController.prototype.Ready = 1;

ReadyController.prototype.StayReady = 2;