Line data Source code
1 : function AIInterface() {}
2 :
3 0 : AIInterface.prototype.Schema =
4 : "<a:component type='system'/><empty/>";
5 :
6 0 : AIInterface.prototype.EventNames = [
7 : "Create",
8 : "Destroy",
9 : "Attacked",
10 : "ConstructionFinished",
11 : "DiplomacyChanged",
12 : "TrainingStarted",
13 : "TrainingFinished",
14 : "AIMetadata",
15 : "PlayerDefeated",
16 : "EntityRenamed",
17 : "ValueModification",
18 : "OwnershipChanged",
19 : "Garrison",
20 : "UnGarrison",
21 : "TerritoriesChanged",
22 : "TerritoryDecayChanged",
23 : "TributeExchanged",
24 : "AttackRequest",
25 : "CeasefireEnded",
26 : "DiplomacyRequest",
27 : "TributeRequest"
28 : ];
29 :
30 0 : AIInterface.prototype.Init = function()
31 : {
32 0 : this.events = {};
33 0 : for (let name of this.EventNames)
34 0 : this.events[name] = [];
35 :
36 0 : this.changedEntities = {};
37 :
38 : // cache for technology changes;
39 : // this one is PlayerID->TemplateName->{StringForTheValue, ActualValue}
40 0 : this.changedTemplateInfo = {};
41 : // this is for auras and is EntityID->{StringForTheValue, ActualValue}
42 0 : this.changedEntityTemplateInfo = {};
43 0 : this.enabled = true;
44 : };
45 :
46 0 : AIInterface.prototype.Serialize = function()
47 : {
48 0 : let state = {};
49 0 : for (var key in this)
50 : {
51 0 : if (!this.hasOwnProperty(key))
52 0 : continue;
53 0 : if (typeof this[key] == "function")
54 0 : continue;
55 0 : if (key == "templates")
56 0 : continue;
57 0 : state[key] = this[key];
58 : }
59 0 : return state;
60 : };
61 :
62 0 : AIInterface.prototype.Deserialize = function(data)
63 : {
64 0 : for (let key in data)
65 : {
66 0 : if (!data.hasOwnProperty(key))
67 0 : continue;
68 0 : this[key] = data[key];
69 : }
70 0 : if (!this.enabled)
71 0 : this.Disable();
72 : };
73 :
74 : /**
75 : * Disable all registering functions for this component
76 : * Gets called in case no AI players are present to save resources
77 : */
78 0 : AIInterface.prototype.Disable = function()
79 : {
80 0 : this.enabled = false;
81 0 : let nop = function(){};
82 0 : this.ChangedEntity = nop;
83 0 : this.PushEvent = nop;
84 0 : this.OnGlobalPlayerDefeated = nop;
85 0 : this.OnGlobalEntityRenamed = nop;
86 0 : this.OnGlobalTributeExchanged = nop;
87 0 : this.OnTemplateModification = nop;
88 0 : this.OnGlobalValueModification = nop;
89 : };
90 :
91 0 : AIInterface.prototype.GetNonEntityRepresentation = function()
92 : {
93 0 : let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
94 :
95 : // Return the same game state as the GUI uses
96 0 : let state = cmpGuiInterface.GetSimulationState();
97 :
98 : // Add some extra AI-specific data
99 : // add custom events and reset them for the next turn
100 0 : state.events = {};
101 0 : for (let name of this.EventNames)
102 : {
103 0 : state.events[name] = this.events[name];
104 0 : this.events[name] = [];
105 : }
106 :
107 0 : return state;
108 : };
109 :
110 0 : AIInterface.prototype.GetRepresentation = function()
111 : {
112 0 : let state = this.GetNonEntityRepresentation();
113 :
114 : // Add entity representations
115 0 : Engine.ProfileStart("proxy representations");
116 0 : state.entities = {};
117 0 : for (let id in this.changedEntities)
118 : {
119 0 : let cmpAIProxy = Engine.QueryInterface(+id, IID_AIProxy);
120 0 : if (cmpAIProxy)
121 0 : state.entities[id] = cmpAIProxy.GetRepresentation();
122 : }
123 0 : this.changedEntities = {};
124 0 : Engine.ProfileStop();
125 :
126 0 : state.changedTemplateInfo = this.changedTemplateInfo;
127 0 : this.changedTemplateInfo = {};
128 0 : state.changedEntityTemplateInfo = this.changedEntityTemplateInfo;
129 0 : this.changedEntityTemplateInfo = {};
130 :
131 0 : return state;
132 : };
133 :
134 : /**
135 : * Intended to be called first, during the map initialization: no caching
136 : */
137 0 : AIInterface.prototype.GetFullRepresentation = function(flushEvents)
138 : {
139 0 : let state = this.GetNonEntityRepresentation();
140 :
141 0 : if (flushEvents)
142 0 : for (let name of this.EventNames)
143 0 : state.events[name] = [];
144 :
145 : // Add entity representations
146 0 : Engine.ProfileStart("proxy representations");
147 0 : state.entities = {};
148 : // all entities are changed in the initial state.
149 0 : for (let id of Engine.GetEntitiesWithInterface(IID_AIProxy))
150 0 : state.entities[id] = Engine.QueryInterface(id, IID_AIProxy).GetFullRepresentation();
151 0 : Engine.ProfileStop();
152 :
153 0 : state.changedTemplateInfo = this.changedTemplateInfo;
154 0 : this.changedTemplateInfo = {};
155 0 : state.changedEntityTemplateInfo = this.changedEntityTemplateInfo;
156 0 : this.changedEntityTemplateInfo = {};
157 :
158 0 : return state;
159 : };
160 :
161 0 : AIInterface.prototype.ChangedEntity = function(ent)
162 : {
163 0 : this.changedEntities[ent] = 1;
164 : };
165 :
166 : /**
167 : * AIProxy sets up a load of event handlers to capture interesting things going on
168 : * in the world, which we will report to AI. Handle those, and add a few more handlers
169 : * for events that AIProxy won't capture.
170 : */
171 0 : AIInterface.prototype.PushEvent = function(type, msg)
172 : {
173 0 : if (this.events[type] === undefined)
174 0 : warn("Tried to push unknown event type " + type +", please add it to AIInterface.js");
175 0 : this.events[type].push(msg);
176 : };
177 :
178 0 : AIInterface.prototype.OnDiplomacyChanged = function(msg)
179 : {
180 0 : this.events.DiplomacyChanged.push(msg);
181 : };
182 :
183 0 : AIInterface.prototype.OnGlobalPlayerDefeated = function(msg)
184 : {
185 0 : this.events.PlayerDefeated.push(msg);
186 : };
187 :
188 0 : AIInterface.prototype.OnGlobalEntityRenamed = function(msg)
189 : {
190 0 : if (!Engine.QueryInterface(msg.entity, IID_Mirage))
191 0 : this.events.EntityRenamed.push(msg);
192 : };
193 :
194 0 : AIInterface.prototype.OnGlobalTributeExchanged = function(msg)
195 : {
196 0 : this.events.TributeExchanged.push(msg);
197 : };
198 :
199 0 : AIInterface.prototype.OnTerritoriesChanged = function(msg)
200 : {
201 0 : this.events.TerritoriesChanged.push(msg);
202 : };
203 :
204 0 : AIInterface.prototype.OnCeasefireEnded = function(msg)
205 : {
206 0 : this.events.CeasefireEnded.push(msg);
207 : };
208 :
209 : /**
210 : * When a new technology is researched, check which templates it affects,
211 : * and send the updated values to the AI.
212 : * this relies on the fact that any "value" in a technology can only ever change
213 : * one template value, and that the naming is the same (with / in place of .)
214 : */
215 0 : AIInterface.prototype.OnTemplateModification = function(msg)
216 : {
217 0 : let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
218 0 : if (!this.templates)
219 : {
220 0 : this.templates = [];
221 0 : for (let templateName of cmpTemplateManager.FindAllTemplates(false))
222 : {
223 : // Remove templates that we obviously don't care about.
224 0 : if (templateName.startsWith("campaigns/") || templateName.startsWith("rubble/") ||
225 : templateName.startsWith("skirmish/"))
226 0 : continue;
227 0 : let template = cmpTemplateManager.GetTemplateWithoutValidation(templateName);
228 0 : if (!template || !template.Identity || !template.Identity.Civ)
229 0 : continue;
230 0 : this.templates.push(templateName);
231 : }
232 : }
233 :
234 0 : for (let name of this.templates)
235 : {
236 0 : let template = cmpTemplateManager.GetTemplateWithoutValidation(name);
237 0 : if (!template || !template[msg.component])
238 0 : continue;
239 0 : for (let valName of msg.valueNames)
240 : {
241 : // let's get the base template value.
242 0 : let strings = valName.split("/");
243 0 : let item = template;
244 0 : let ended = true;
245 0 : for (let str of strings)
246 : {
247 0 : if (item !== undefined && item[str] !== undefined)
248 0 : item = item[str];
249 : else
250 0 : ended = false;
251 : }
252 0 : if (!ended)
253 0 : continue;
254 : // item now contains the template value for this.
255 0 : let oldValue = +item == item ? +item : item;
256 0 : let newValue = ApplyValueModificationsToTemplate(valName, oldValue, msg.player, template);
257 : // Apply the same roundings as in the components
258 0 : if (valName === "Player/MaxPopulation" || valName === "Cost/Population" ||
259 : valName === "Population/Bonus")
260 0 : newValue = Math.round(newValue);
261 : // TODO in some cases, we can have two opposite changes which bring us to the old value,
262 : // and we should keep it. But how to distinguish it ?
263 0 : if(newValue == oldValue)
264 0 : continue;
265 0 : if (!this.changedTemplateInfo[msg.player])
266 0 : this.changedTemplateInfo[msg.player] = {};
267 0 : if (!this.changedTemplateInfo[msg.player][name])
268 0 : this.changedTemplateInfo[msg.player][name] = [{ "variable": valName, "value": newValue }];
269 : else
270 0 : this.changedTemplateInfo[msg.player][name].push({ "variable": valName, "value": newValue });
271 : }
272 : }
273 : };
274 :
275 0 : AIInterface.prototype.OnGlobalValueModification = function(msg)
276 : {
277 0 : this.events.ValueModification.push(msg);
278 0 : let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
279 0 : for (let ent of msg.entities)
280 : {
281 0 : let templateName = cmpTemplateManager.GetCurrentTemplateName(ent);
282 : // if there's no template name, the unit is probably killed, ignore it.
283 0 : if (!templateName || !templateName.length)
284 0 : continue;
285 0 : let template = cmpTemplateManager.GetTemplateWithoutValidation(templateName);
286 0 : if (!template || !template[msg.component])
287 0 : continue;
288 0 : for (let valName of msg.valueNames)
289 : {
290 : // let's get the base template value.
291 0 : let strings = valName.split("/");
292 0 : let item = template;
293 0 : let ended = true;
294 0 : for (let str of strings)
295 : {
296 0 : if (item !== undefined && item[str] !== undefined)
297 0 : item = item[str];
298 : else
299 0 : ended = false;
300 : }
301 0 : if (!ended)
302 0 : continue;
303 : // "item" now contains the unmodified template value for this.
304 0 : let oldValue = +item == item ? +item : item;
305 0 : let newValue = ApplyValueModificationsToEntity(valName, oldValue, ent);
306 : // Apply the same roundings as in the components
307 0 : if (valName === "Player/MaxPopulation" || valName === "Cost/Population" ||
308 : valName === "Population/Bonus")
309 0 : newValue = Math.round(newValue);
310 : // TODO in some cases, we can have two opposite changes which bring us to the old value,
311 : // and we should keep it. But how to distinguish it ?
312 0 : if (newValue == oldValue)
313 0 : continue;
314 0 : if (!this.changedEntityTemplateInfo[ent])
315 0 : this.changedEntityTemplateInfo[ent] = [{ "variable": valName, "value": newValue }];
316 : else
317 0 : this.changedEntityTemplateInfo[ent].push({ "variable": valName, "value": newValue });
318 : }
319 : }
320 : };
321 :
322 0 : Engine.RegisterSystemComponentType(IID_AIInterface, "AIInterface", AIInterface);
|