Source: PlayerManager.js

/**
 * @class
 */
function PlayerManager() {}

PlayerManager.prototype.Schema =
	"<a:component type='system'/><empty/>";

PlayerManager.prototype.Init = function()
{
	// List of player entity IDs.
	this.playerEntities = [];
};

/**
 * @param {string} templateName - The template name of the player to add.
 * @return {number} - The player's ID (player number).
 */
PlayerManager.prototype.AddPlayer = function(templateName)
{
	const ent = Engine.AddEntity(templateName);
	const id = this.playerEntities.length;
	const cmpPlayer = Engine.QueryInterface(ent, IID_Player);
	cmpPlayer.SetPlayerID(id);
	this.playerEntities.push(ent);

	const newDiplo = [];
	for (let i = 0; i < id; i++)
	{
		Engine.QueryInterface(this.GetPlayerByID(i), IID_Player).diplomacy[id] = -1;
		newDiplo[i] = -1;
	}
	newDiplo[id] = 1;
	cmpPlayer.SetDiplomacy(newDiplo);

	Engine.BroadcastMessage(MT_PlayerEntityChanged, {
		"player": id,
		"from": INVALID_ENTITY,
		"to": ent
	});

	return id;
};

/**
 * To avoid possible problems,
 * we first remove all entities from this player, and add them back after the replacement.
 * Note: This should only be called during setup/init and not during the game
 * @param {number} id - The player number to replace.
 * @param {string} newTemplateName - The new template name for the player.
 */
PlayerManager.prototype.ReplacePlayerTemplate = function(id, newTemplateName)
{
	const ent = Engine.AddEntity(newTemplateName);
	const entities = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetEntitiesByPlayer(id);
	for (const e of entities)
		Engine.QueryInterface(e, IID_Ownership)?.SetOwner(INVALID_PLAYER);

	const oldent = this.playerEntities[id];
	const oldCmpPlayer = Engine.QueryInterface(oldent, IID_Player);
	const newCmpPlayer = Engine.QueryInterface(ent, IID_Player);

	newCmpPlayer.SetPlayerID(id);
	this.playerEntities[id] = ent;

	newCmpPlayer.SetColor(oldCmpPlayer.GetColor());
	newCmpPlayer.SetDiplomacy(oldCmpPlayer.GetDiplomacy());

	Engine.BroadcastMessage(MT_PlayerEntityChanged, {
		"player": id,
		"from": oldent,
		"to": ent
	});

	for (const e of entities)
		Engine.QueryInterface(e, IID_Ownership)?.SetOwner(id);

	Engine.DestroyEntity(oldent);
	Engine.FlushDestroyedEntities();
};

/**
 * Returns the player entity ID for the given player ID.
 * The player ID must be valid (else there will be an error message).
 */
PlayerManager.prototype.GetPlayerByID = function(id)
{
	if (id in this.playerEntities)
		return this.playerEntities[id];

	// Observers don't have player data.
	if (id == INVALID_PLAYER)
		return INVALID_ENTITY;

	const stack = new Error().stack.trimRight().replace(/^/mg, '  '); // indent each line
	warn("GetPlayerByID: no player defined for id '"+id+"'\n"+stack);

	return INVALID_ENTITY;
};

/**
 * Returns the number of players including gaia.
 */
PlayerManager.prototype.GetNumPlayers = function()
{
	return this.playerEntities.length;
};

/**
 * Returns IDs of all players including gaia.
 */
PlayerManager.prototype.GetAllPlayers = function()
{
	const players = [];
	for (let i = 0; i < this.playerEntities.length; ++i)
		players.push(i);
	return players;
};

/**
 * Returns IDs of all players excluding gaia.
 */
PlayerManager.prototype.GetNonGaiaPlayers = function()
{
	const players = [];
	for (let i = 1; i < this.playerEntities.length; ++i)
		players.push(i);
	return players;
};

/**
 * Returns IDs of all players excluding gaia that are not defeated nor have won.
 */
PlayerManager.prototype.GetActivePlayers = function()
{
	return this.GetNonGaiaPlayers().filter(playerID =>
		Engine.QueryInterface(this.GetPlayerByID(playerID), IID_Player).IsActive()
	);
};

/**
 * Note: This should only be called during setup/init and not during a match
 * since it doesn't change the owned entities.
 */
PlayerManager.prototype.RemoveLastPlayer = function()
{
	if (!this.playerEntities.length)
		return;

	const lastId = this.playerEntities.pop();
	Engine.BroadcastMessage(MT_PlayerEntityChanged, {
		"player": this.playerEntities.length + 1,
		"from": lastId,
		"to": INVALID_ENTITY
	});
	Engine.DestroyEntity(lastId);
};

PlayerManager.prototype.SetMaxWorldPopulation = function(max)
{
	this.maxWorldPopulation = max;
	this.RedistributeWorldPopulation();
};

PlayerManager.prototype.GetMaxWorldPopulation = function()
{
	return this.maxWorldPopulation;
};

PlayerManager.prototype.RedistributeWorldPopulation = function()
{
	const worldPopulation = this.GetMaxWorldPopulation();
	if (!worldPopulation)
		return;

	const activePlayers = this.GetActivePlayers();
	if (!activePlayers.length)
		return;

	const newMaxPopulation = worldPopulation / activePlayers.length;
	for (const playerID of activePlayers)
		Engine.QueryInterface(this.GetPlayerByID(playerID), IID_Player).SetMaxPopulation(newMaxPopulation);
};

PlayerManager.prototype.OnGlobalPlayerDefeated = function(msg)
{
	this.RedistributeWorldPopulation();
};

Engine.RegisterSystemComponentType(IID_PlayerManager, "PlayerManager", PlayerManager);