Source: gamesettings/GameSettings.js

/**
 * Data store for game settings.
 *
 * This is intended as a helper to create the settings object for a game.
 * This object is referred to as:
 *  - g_InitAttributes in the GUI session context
 *  - InitAttributes in the JS simulation context
 *  - Either InitAttributes or MapSettings in the C++ simulation.
 * Settings can depend on each other, and the map provides many.
 * This class's job is thus to provide a simpler interface around that.
 */
class GameSettings
{
	init(mapCache)
	{
		if (!mapCache)
			mapCache = new MapCache();
		Object.defineProperty(this, "mapCache", {
			"value": mapCache,
		});

		// Load all possible civ data - don't presume that some will be available.
		Object.defineProperty(this, "civData", {
			"value": loadCivData(false, false),
		});

		Object.defineProperty(this, "isNetworked", {
			"value": Engine.HasNetClient(),
		});

		// Load attributes as regular enumerable (i.e. iterable) properties.
		for (let comp in GameSettings.prototype.Attributes)
		{
			let name = comp[0].toLowerCase() + comp.substr(1);
			if (name in this)
				error("Game Settings attribute '" + name + "' is already used.");
			this[name] = new GameSettings.prototype.Attributes[comp](this);
		}
		for (let comp in this)
			this[comp].init();

		return this;
	}

	/**
	 * 'Serialize' the settings into the InitAttributes format,
	 * which can then be saved as JSON.
	 * Used to set the InitAttributes, for network synching, for hotloading & for persistence.
	 * TODO: it would probably be better to have different paths for at least a few of these.
	 */
	toInitAttributes()
	{
		let attribs = {
			"settings": {}
		};
		for (let comp in this)
			if (this[comp].toInitAttributes)
				this[comp].toInitAttributes(attribs);

		return attribs;
	}

	/**
	 * Deserialize from a the InitAttributes format (i.e. parsed JSON).
	 * TODO: this could/should maybe support partial deserialization,
	 * which means MP might actually send only the bits that change.
	 */
	fromInitAttributes(attribs)
	{
		// Settings depend on the map, but some settings
		// may also be illegal for a given map.
		// It would be good to validate, but just bulk-accept at the moment.
		// There is some light order-dependency between settings.
		// First deserialize the map, then the player #, then victory conditions, then the rest.
		// TODO: there's a DAG in there.
		this.map.fromInitAttributes(attribs);
		this.playerCount.fromInitAttributes(attribs);
		this.victoryConditions.fromInitAttributes(attribs);
		for (let comp in this)
			if (this[comp].fromInitAttributes &&
				comp !== "map" && comp !== "playerCount" && comp !== "victoryConditions")
				this[comp].fromInitAttributes(attribs);
	}

	/**
	 * Change "random" settings into their proper settings.
	 */
	pickRandomItems()
	{
		let components = Object.keys(this);
		let i = 0;
		while (components.length && i < 100)
		{
			// Re-pick if any random setting was unrandomised,
			// to make sure dependencies are cleared.
			// TODO: there's probably a better way to handle this.
			components = components.filter(comp => this[comp].pickRandomItems ?
				!!this[comp].pickRandomItems() : false);
			++i;
		}
		if (i === 100)
		{
			throw new Error("Infinite loop picking random items, remains : " + uneval(components));
		}
	}

	/**
	 * Start the game & switch to the loading page.
	 * This is here because there's limited value in having a separate folder/file for it,
	 * since you'll need a GameSettings object anyways.
	 * @param playerAssignments - A dict of 'local'/GUID per player and their name/slot.
	 */
	launchGame(playerAssignments)
	{
		this.pickRandomItems();

		Engine.SetRankedGame(this.rating.enabled);

		// Replace player names with the real players.
		for (let guid in playerAssignments)
			if (playerAssignments[guid].player !== -1)
				this.playerName.values[playerAssignments[guid].player -1] = playerAssignments[guid].name;

		// NB: for multiplayer support, the clients must be listening to "start" net messages.
		if (this.isNetworked)
			Engine.StartNetworkGame(this.toInitAttributes());
		else
			Engine.StartGame(this.toInitAttributes(), playerAssignments.local.player);
	}
}

Object.defineProperty(GameSettings.prototype, "Attributes", {
	"value": {},
	"enumerable": false,
	"writable": true,
});