1
0
forked from mirrors/0ad
Files
0ad/binaries/data/mods/public/simulation/components/AIInterface.js
T
wraitii 70c71bff0f Allow Modifiers to affect tokens.
This adds a new mode to modifications called "tokens" which allows
clever token parsing.
Technologies, auras and modifiers in general can use this to switch out,
add or delete tokens dynamically.

Currently implemented are production and builder queue tokens.

Reviewed By: Freagarach
Differential Revision: https://code.wildfiregames.com/D270
This was SVN commit r23843.
2020-07-17 08:23:45 +00:00

323 lines
9.1 KiB
JavaScript

function AIInterface() {}
AIInterface.prototype.Schema =
"<a:component type='system'/><empty/>";
AIInterface.prototype.EventNames = [
"Create",
"Destroy",
"Attacked",
"ConstructionFinished",
"DiplomacyChanged",
"TrainingStarted",
"TrainingFinished",
"AIMetadata",
"PlayerDefeated",
"EntityRenamed",
"ValueModification",
"OwnershipChanged",
"Garrison",
"UnGarrison",
"TerritoriesChanged",
"TerritoryDecayChanged",
"TributeExchanged",
"AttackRequest",
"CeasefireEnded",
"DiplomacyRequest",
"TributeRequest"
];
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()
{
let state = {};
for (var key in this)
{
if (!this.hasOwnProperty(key))
continue;
if (typeof this[key] == "function")
continue;
if (key == "templates")
continue;
state[key] = this[key];
}
return state;
};
AIInterface.prototype.Deserialize = function(data)
{
for (let 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;
let 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()
{
let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
// Return the same game state as the GUI uses
let state = cmpGuiInterface.GetSimulationState();
// 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()
{
let state = this.GetNonEntityRepresentation();
// Add entity representations
Engine.ProfileStart("proxy representations");
state.entities = {};
for (let id in this.changedEntities)
{
let cmpAIProxy = Engine.QueryInterface(+id, IID_AIProxy);
if (cmpAIProxy)
state.entities[id] = cmpAIProxy.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)
{
let 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.OnDiplomacyChanged = function(msg)
{
this.events.DiplomacyChanged.push(msg);
};
AIInterface.prototype.OnGlobalPlayerDefeated = function(msg)
{
this.events.PlayerDefeated.push(msg);
};
AIInterface.prototype.OnGlobalEntityRenamed = function(msg)
{
if (!Engine.QueryInterface(msg.entity, IID_Mirage))
this.events.EntityRenamed.push(msg);
};
AIInterface.prototype.OnGlobalTributeExchanged = function(msg)
{
this.events.TributeExchanged.push(msg);
};
AIInterface.prototype.OnTerritoriesChanged = function(msg)
{
this.events.TerritoriesChanged.push(msg);
};
AIInterface.prototype.OnCeasefireEnded = function(msg)
{
this.events.CeasefireEnded.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 .)
*/
AIInterface.prototype.OnTemplateModification = function(msg)
{
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
if (!this.templates)
{
this.templates = [];
for (let templateName of cmpTemplateManager.FindAllTemplates(false))
{
// Remove templates that we obviously don't care about.
if (templateName.startsWith("campaigns/") || templateName.startsWith("rubble/") ||
templateName.startsWith("skirmish/"))
continue;
let template = cmpTemplateManager.GetTemplateWithoutValidation(templateName);
if (!template || !template.Identity || !template.Identity.Civ)
continue;
this.templates.push(templateName);
}
}
for (let name of this.templates)
{
let template = cmpTemplateManager.GetTemplateWithoutValidation(name);
if (!template || !template[msg.component])
continue;
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 == item ? +item : item;
let newValue = ApplyValueModificationsToTemplate(valName, oldValue, msg.player, template);
// Apply the same roundings as in the components
if (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)
{
this.events.ValueModification.push(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);
if (!template || !template[msg.component])
continue;
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 == item ? +item : item;
let newValue = ApplyValueModificationsToEntity(valName, oldValue, ent);
// Apply the same roundings as in the components
if (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);