Source: components/AIInterface.js

function AIInterface() {}

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

AIInterface.prototype.EventNames = [
	"Create",
	"Destroy",
	"Attacked",
	"ConstructionFinished",
	"TrainingStarted",
	"TrainingFinished",
	"AIMetadata",
	"PlayerDefeated",
	"EntityRenamed",
	"OwnershipChanged",
	"Garrison",
	"UnGarrison",
	"TerritoryDecayChanged",
	"TributeExchanged",
	"AttackRequest"
];

AIInterface.prototype.Init = function()
{
	this.events = {};
	for (let name of this.EventNames)
		this.events[name] = [];

	this.changedEntities = {};

	// cache for technology changes;
	// this one is PlayerID->TemplateName->{StringForTheValue, ActualValue}
	this.changedTemplateInfo = {};
	// this is for auras and is EntityID->{StringForTheValue, ActualValue}
	this.changedEntityTemplateInfo = {};
	this.enabled = true;
};

AIInterface.prototype.Serialize = function()
{
	var state = {};
	for (var key in this)
	{
		if (!this.hasOwnProperty(key))
			continue;
		if (typeof this[key] == "function")
			continue;
		state[key] = this[key];
	}
	return state;
};

AIInterface.prototype.Deserialize = function(data)
{
	for (var key in data)
	{
		if (!data.hasOwnProperty(key))
			continue;
		this[key] = data[key];
	}
	if (!this.enabled)
		this.Disable();
};

/**
 * Disable all registering functions for this component
 * Gets called in case no AI players are present to save resources
 */
AIInterface.prototype.Disable = function()
{
	this.enabled = false;
	var nop = function(){};
	this.ChangedEntity = nop;
	this.PushEvent = nop;
	this.OnGlobalPlayerDefeated = nop;
	this.OnGlobalEntityRenamed = nop;
	this.OnGlobalTributeExchanged = nop;
	this.OnTemplateModification = nop;
	this.OnGlobalValueModification = nop;
};

AIInterface.prototype.GetNonEntityRepresentation = function()
{
	var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);

	// Return the same game state as the GUI uses
	var state = cmpGuiInterface.GetSimulationState(-1);
	
	// Add some extra AI-specific data
	// add custom events and reset them for the next turn
	state.events = {};
	for (let name of this.EventNames)
	{
		state.events[name] = this.events[name];
		this.events[name] = [];
	}

	return state;
};

AIInterface.prototype.GetRepresentation = function()
{
	var state = this.GetNonEntityRepresentation();

	// Add entity representations
	Engine.ProfileStart("proxy representations");
	state.entities = {};
	for (var id in this.changedEntities)
	{
		var aiProxy = Engine.QueryInterface(+id, IID_AIProxy);
		if (aiProxy)
			state.entities[id] = aiProxy.GetRepresentation();
	}
	this.changedEntities = {};
	Engine.ProfileStop();

	state.changedTemplateInfo = this.changedTemplateInfo;
	this.changedTemplateInfo = {};
	state.changedEntityTemplateInfo = this.changedEntityTemplateInfo;
	this.changedEntityTemplateInfo = {};

	return state;
};

// Intended to be called first, during the map initialization: no caching
AIInterface.prototype.GetFullRepresentation = function(flushEvents)
{	
	var state = this.GetNonEntityRepresentation();

	if (flushEvents)
		for (let name of this.EventNames)
			state.events[name] = [];

	// Add entity representations
	Engine.ProfileStart("proxy representations");
	state.entities = {};
	// all entities are changed in the initial state.
	for (let id of Engine.GetEntitiesWithInterface(IID_AIProxy))
		state.entities[id] = Engine.QueryInterface(id, IID_AIProxy).GetFullRepresentation();
	Engine.ProfileStop();
	
	state.changedTemplateInfo = this.changedTemplateInfo;
	this.changedTemplateInfo = {};
	state.changedEntityTemplateInfo = this.changedEntityTemplateInfo;
	this.changedEntityTemplateInfo = {};

	return state;
};

AIInterface.prototype.ChangedEntity = function(ent)
{
	this.changedEntities[ent] = 1;
};

// AIProxy sets up a load of event handlers to capture interesting things going on
// in the world, which we will report to AI. Handle those, and add a few more handlers
// for events that AIProxy won't capture.

AIInterface.prototype.PushEvent = function(type, msg)
{
	if (this.events[type] === undefined)
		warn("Tried to push unknown event type " + type +", please add it to AIInterface.js");
	this.events[type].push(msg);
};

AIInterface.prototype.OnGlobalPlayerDefeated = function(msg)
{
	this.events["PlayerDefeated"].push(msg);
};

AIInterface.prototype.OnGlobalEntityRenamed = function(msg)
{
	var cmpMirage = Engine.QueryInterface(msg.entity, IID_Mirage);
	if (!cmpMirage)
		this.events["EntityRenamed"].push(msg);
};

AIInterface.prototype.OnGlobalTributeExchanged = function(msg)
{
	this.events["TributeExchanged"].push(msg);
};

// When a new technology is researched, check which templates it affects,
// and send the updated values to the AI.
// this relies on the fact that any "value" in a technology can only ever change
// one template value, and that the naming is the same (with / in place of .)
// it's not incredibly fast but it's not incredibly slow.
AIInterface.prototype.OnTemplateModification = function(msg)
{
	let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
	if (!this.templates)
	{
		this.templates = cmpTemplateManager.FindAllTemplates(false);
		for (let i = 0; i < this.templates.length; ++i)
		{
			// remove templates that we obviously don't care about.
			if (this.templates[i].startsWith("skirmish/"))
				this.templates.splice(i--,1);
			else
			{
				let template = cmpTemplateManager.GetTemplateWithoutValidation(this.templates[i]);
				if (!template || !template.Identity || !template.Identity.Civ)
					this.templates.splice(i--,1);
			}
		}
	}

	for (let name of this.templates)
	{
		let template = cmpTemplateManager.GetTemplateWithoutValidation(name);

		for (let valName of msg.valueNames)
		{
			// let's get the base template value.
			let strings = valName.split("/");
			let item = template;
			let ended = true;
			for (let str of strings)
			{
				if (item !== undefined && item[str] !== undefined)
					item = item[str];
				else
					ended = false;
			}
			if (!ended)
				continue;
			// item now contains the template value for this.
			let oldValue = +item;
			let newValue = ApplyValueModificationsToTemplate(valName, oldValue, msg.player, template);
			// Apply the same roundings as in the components
			if (valName === "Health/Max" || valName === "Player/MaxPopulation"
				|| valName === "Cost/Population" || valName === "Cost/PopulationBonus")
				newValue = Math.round(newValue);

			// TODO in some cases, we can have two opposite changes which bring us to the old value,
			// and we should keep it. But how to distinguish it ?
			if(newValue == oldValue)
				continue;
			if (!this.changedTemplateInfo[msg.player])
				this.changedTemplateInfo[msg.player] = {};
			if (!this.changedTemplateInfo[msg.player][name])
				this.changedTemplateInfo[msg.player][name] = [{"variable": valName, "value": newValue}];
			else
				this.changedTemplateInfo[msg.player][name].push({"variable": valName, "value": newValue});
		}
	}
};

AIInterface.prototype.OnGlobalValueModification = function(msg)
{
	let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
	for (let ent of msg.entities)
	{
		let templateName = cmpTemplateManager.GetCurrentTemplateName(ent);
		// if there's no template name, the unit is probably killed, ignore it.
		if (!templateName || !templateName.length)
			continue;
		let template = cmpTemplateManager.GetTemplateWithoutValidation(templateName);
		for (let valName of msg.valueNames)
		{
			// let's get the base template value.
			let strings = valName.split("/");
			let item = template;
			let ended = true;
			for (let str of strings)
			{
				if (item !== undefined && item[str] !== undefined)
					item = item[str];
				else
					ended = false;
			}
			if (!ended)
				continue;
			// "item" now contains the unmodified template value for this.
			let oldValue = +item;
			let newValue = ApplyValueModificationsToEntity(valName, oldValue, ent);
			// Apply the same roundings as in the components
			if (valName === "Health/Max" || valName === "Player/MaxPopulation"
				|| valName === "Cost/Population" || valName === "Cost/PopulationBonus")
				newValue = Math.round(newValue);
			// TODO in some cases, we can have two opposite changes which bring us to the old value,
			// and we should keep it. But how to distinguish it ?
			if (newValue == oldValue)
				continue;
			if (!this.changedEntityTemplateInfo[ent])
				this.changedEntityTemplateInfo[ent] = [{"variable": valName, "value": newValue}];
			else
				this.changedEntityTemplateInfo[ent].push({"variable": valName, "value": newValue});
		}
	}
};

Engine.RegisterSystemComponentType(IID_AIInterface, "AIInterface", AIInterface);