Line data Source code
1 : function AIProxy() {}
2 :
3 0 : AIProxy.prototype.Schema =
4 : "<empty/>";
5 :
6 : /**
7 : * AIProxy passes its entity's state data to AI scripts.
8 : *
9 : * Efficiency is critical: there can be many thousands of entities,
10 : * and the data returned by this component is serialized and copied to
11 : * the AI thread every turn, so it can be quite expensive.
12 : *
13 : * We omit all data that can be derived statically from the template XML
14 : * files - the AI scripts can parse the templates themselves.
15 : * This violates the component interface abstraction and is potentially
16 : * fragile if the template formats change (since both the component code
17 : * and the AI will have to be updated in sync), but it's not *that* bad
18 : * really and it helps performance significantly.
19 : *
20 : * We also add an optimisation to avoid copying non-changing values.
21 : * The first call to GetRepresentation calls GetFullRepresentation,
22 : * which constructs the complete entity state representation.
23 : * After that, we simply listen to events from the rest of the gameplay code,
24 : * and store the changed data in this.changes.
25 : * Properties in this.changes will override those previously returned
26 : * from GetRepresentation; if a property isn't overridden then the AI scripts
27 : * will keep its old value.
28 : *
29 : * The event handlers should set this.changes.whatever to exactly the
30 : * same as GetFullRepresentation would set.
31 : */
32 :
33 0 : AIProxy.prototype.Init = function()
34 : {
35 0 : this.changes = null;
36 0 : this.needsFullGet = true;
37 0 : this.cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface);
38 : };
39 :
40 0 : AIProxy.prototype.Serialize = null; // we have no dynamic state to save
41 :
42 0 : AIProxy.prototype.Deserialize = function()
43 : {
44 0 : this.Init();
45 : };
46 :
47 0 : AIProxy.prototype.GetRepresentation = function()
48 : {
49 : // Return the full representation the first time we're called
50 : let ret;
51 0 : if (this.needsFullGet)
52 0 : ret = this.GetFullRepresentation();
53 : else
54 0 : ret = this.changes;
55 :
56 : // Initialise changes to null instead of {}, to avoid memory allocations in the
57 : // common case where there will be no changes; event handlers should each reset
58 : // it to {} if needed
59 0 : this.changes = null;
60 :
61 0 : return ret;
62 : };
63 :
64 0 : AIProxy.prototype.NotifyChange = function()
65 : {
66 0 : if (this.needsFullGet)
67 : {
68 : // not yet notified, be sure that the owner is set before doing so
69 : // as the Create event is sent only on first ownership changed
70 0 : let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
71 0 : if (!cmpOwnership || cmpOwnership.GetOwner() < 0)
72 0 : return false;
73 : }
74 :
75 0 : if (!this.changes)
76 : {
77 0 : this.changes = {};
78 0 : this.cmpAIInterface.ChangedEntity(this.entity);
79 : }
80 0 : return true;
81 : };
82 :
83 : // AI representation-updating event handlers:
84 :
85 0 : AIProxy.prototype.OnPositionChanged = function(msg)
86 : {
87 0 : if (!this.NotifyChange())
88 0 : return;
89 :
90 0 : if (msg.inWorld)
91 : {
92 0 : this.changes.position = [msg.x, msg.z];
93 0 : this.changes.angle = msg.a;
94 : }
95 : else
96 : {
97 0 : this.changes.position = undefined;
98 0 : this.changes.angle = undefined;
99 : }
100 : };
101 :
102 0 : AIProxy.prototype.OnHealthChanged = function(msg)
103 : {
104 0 : if (!this.NotifyChange())
105 0 : return;
106 0 : this.changes.hitpoints = msg.to;
107 : };
108 :
109 0 : AIProxy.prototype.OnGarrisonedStateChanged = function(msg)
110 : {
111 0 : if (!this.NotifyChange())
112 0 : return;
113 0 : this.changes.garrisonHolderID = msg.holderID;
114 : };
115 :
116 0 : AIProxy.prototype.OnCapturePointsChanged = function(msg)
117 : {
118 0 : if (!this.NotifyChange())
119 0 : return;
120 0 : this.changes.capturePoints = msg.capturePoints;
121 : };
122 :
123 0 : AIProxy.prototype.OnInvulnerabilityChanged = function(msg)
124 : {
125 0 : if (!this.NotifyChange())
126 0 : return;
127 0 : this.changes.invulnerability = msg.invulnerability;
128 : };
129 :
130 0 : AIProxy.prototype.OnUnitIdleChanged = function(msg)
131 : {
132 0 : if (!this.NotifyChange())
133 0 : return;
134 0 : this.changes.idle = msg.idle;
135 : };
136 :
137 0 : AIProxy.prototype.OnUnitStanceChanged = function(msg)
138 : {
139 0 : if (!this.NotifyChange())
140 0 : return;
141 0 : this.changes.stance = msg.to;
142 : };
143 :
144 0 : AIProxy.prototype.OnUnitAIStateChanged = function(msg)
145 : {
146 0 : if (!this.NotifyChange())
147 0 : return;
148 0 : this.changes.unitAIState = msg.to;
149 : };
150 :
151 0 : AIProxy.prototype.OnUnitAIOrderDataChanged = function(msg)
152 : {
153 0 : if (!this.NotifyChange())
154 0 : return;
155 0 : this.changes.unitAIOrderData = msg.to;
156 : };
157 :
158 0 : AIProxy.prototype.OnProductionQueueChanged = function(msg)
159 : {
160 0 : if (!this.NotifyChange())
161 0 : return;
162 0 : let cmpProductionQueue = Engine.QueryInterface(this.entity, IID_ProductionQueue);
163 0 : this.changes.trainingQueue = cmpProductionQueue.GetQueue();
164 : };
165 :
166 0 : AIProxy.prototype.OnGarrisonedUnitsChanged = function(msg)
167 : {
168 0 : if (!this.NotifyChange())
169 0 : return;
170 :
171 0 : let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
172 0 : this.changes.garrisoned = cmpGarrisonHolder.GetEntities();
173 :
174 : // Send a message telling a unit garrisoned or ungarrisoned.
175 : // I won't check if the unit is still alive so it'll be up to the AI.
176 0 : for (let ent of msg.added)
177 0 : this.cmpAIInterface.PushEvent("Garrison", { "entity": ent, "holder": this.entity });
178 0 : for (let ent of msg.removed)
179 0 : this.cmpAIInterface.PushEvent("UnGarrison", { "entity": ent, "holder": this.entity });
180 : };
181 :
182 0 : AIProxy.prototype.OnFoundationProgressChanged = function(msg)
183 : {
184 0 : if (!this.NotifyChange())
185 0 : return;
186 0 : this.changes.foundationProgress = msg.to;
187 : };
188 :
189 0 : AIProxy.prototype.OnFoundationBuildersChanged = function(msg)
190 : {
191 0 : if (!this.NotifyChange())
192 0 : return;
193 0 : this.changes.foundationBuilders = msg.to;
194 : };
195 :
196 0 : AIProxy.prototype.OnDropsiteSharingChanged = function(msg)
197 : {
198 0 : if (!this.NotifyChange())
199 0 : return;
200 0 : this.changes.sharedDropsite = msg.shared;
201 : };
202 :
203 0 : AIProxy.prototype.OnTerritoryDecayChanged = function(msg)
204 : {
205 0 : if (!this.NotifyChange())
206 0 : return;
207 0 : this.changes.decaying = msg.to;
208 0 : this.cmpAIInterface.PushEvent("TerritoryDecayChanged", msg);
209 : };
210 :
211 : // TODO: event handlers for all the other things
212 :
213 0 : AIProxy.prototype.GetFullRepresentation = function()
214 : {
215 0 : this.needsFullGet = false;
216 0 : let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
217 :
218 0 : let ret = {
219 : // These properties are constant and won't need to be updated
220 : "id": this.entity,
221 : "template": cmpTemplateManager.GetCurrentTemplateName(this.entity)
222 : };
223 :
224 0 : let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
225 0 : if (cmpPosition)
226 : {
227 : // Updated by OnPositionChanged
228 :
229 0 : if (cmpPosition.IsInWorld())
230 : {
231 0 : let pos = cmpPosition.GetPosition2D();
232 0 : ret.position = [pos.x, pos.y];
233 0 : ret.angle = cmpPosition.GetRotation().y;
234 : }
235 : else
236 : {
237 0 : ret.position = undefined;
238 0 : ret.angle = undefined;
239 : }
240 : }
241 :
242 0 : let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
243 0 : if (cmpHealth)
244 : {
245 : // Updated by OnHealthChanged
246 0 : ret.hitpoints = cmpHealth.GetHitpoints();
247 : }
248 :
249 0 : let cmpResistance = Engine.QueryInterface(this.entity, IID_Resistance);
250 0 : if (cmpResistance)
251 0 : ret.invulnerability = cmpResistance.IsInvulnerable();
252 :
253 0 : let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
254 0 : if (cmpOwnership)
255 : {
256 : // Updated by OnOwnershipChanged
257 0 : ret.owner = cmpOwnership.GetOwner();
258 : }
259 :
260 0 : let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
261 0 : if (cmpUnitAI)
262 : {
263 : // Updated by OnUnitIdleChanged
264 0 : ret.idle = cmpUnitAI.IsIdle();
265 : // Updated by OnUnitStanceChanged
266 0 : ret.stance = cmpUnitAI.GetStanceName();
267 : // Updated by OnUnitAIStateChanged
268 0 : ret.unitAIState = cmpUnitAI.GetCurrentState();
269 : // Updated by OnUnitAIOrderDataChanged
270 0 : ret.unitAIOrderData = cmpUnitAI.GetOrderData();
271 : }
272 :
273 0 : let cmpProductionQueue = Engine.QueryInterface(this.entity, IID_ProductionQueue);
274 0 : if (cmpProductionQueue)
275 : {
276 : // Updated by OnProductionQueueChanged
277 0 : ret.trainingQueue = cmpProductionQueue.GetQueue();
278 : }
279 :
280 0 : let cmpFoundation = Engine.QueryInterface(this.entity, IID_Foundation);
281 0 : if (cmpFoundation)
282 : {
283 : // Updated by OnFoundationProgressChanged
284 0 : ret.foundationProgress = cmpFoundation.GetBuildPercentage();
285 : }
286 :
287 0 : let cmpResourceDropsite = Engine.QueryInterface(this.entity, IID_ResourceDropsite);
288 0 : if (cmpResourceDropsite)
289 : {
290 : // Updated by OnDropsiteSharingChanged
291 0 : ret.sharedDropsite = cmpResourceDropsite.IsShared();
292 : }
293 :
294 0 : let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
295 0 : if (cmpGarrisonHolder)
296 : {
297 : // Updated by OnGarrisonedUnitsChanged
298 0 : ret.garrisoned = cmpGarrisonHolder.GetEntities();
299 : }
300 :
301 0 : let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable);
302 0 : if (cmpGarrisonable)
303 : {
304 : // Updated by OnGarrisonedStateChanged
305 0 : ret.garrisonHolderID = cmpGarrisonable.HolderID();
306 : }
307 :
308 0 : let cmpTerritoryDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay);
309 0 : if (cmpTerritoryDecay)
310 0 : ret.decaying = cmpTerritoryDecay.IsDecaying();
311 :
312 0 : let cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable);
313 0 : if (cmpCapturable)
314 0 : ret.capturePoints = cmpCapturable.GetCapturePoints();
315 :
316 0 : return ret;
317 : };
318 :
319 : // AI event handlers:
320 : // (These are passed directly as events to the AI scripts, rather than updating
321 : // our proxy representation.)
322 : // (This shouldn't include extremely high-frequency events, like PositionChanged,
323 : // because that would be very expensive and AI will rarely care about all those
324 : // events.)
325 :
326 : // special case: this changes the state and sends an event.
327 0 : AIProxy.prototype.OnOwnershipChanged = function(msg)
328 : {
329 0 : this.NotifyChange();
330 :
331 0 : if (msg.from == INVALID_PLAYER)
332 : {
333 0 : this.cmpAIInterface.PushEvent("Create", { "entity": msg.entity });
334 0 : return;
335 : }
336 0 : if (msg.to == INVALID_PLAYER)
337 : {
338 0 : this.cmpAIInterface.PushEvent("Destroy", { "entity": msg.entity });
339 0 : return;
340 : }
341 :
342 0 : this.changes.owner = msg.to;
343 0 : this.cmpAIInterface.PushEvent("OwnershipChanged", msg);
344 : };
345 :
346 0 : AIProxy.prototype.OnAttacked = function(msg)
347 : {
348 0 : this.cmpAIInterface.PushEvent("Attacked", msg);
349 : };
350 :
351 0 : AIProxy.prototype.OnConstructionFinished = function(msg)
352 : {
353 0 : this.cmpAIInterface.PushEvent("ConstructionFinished", msg);
354 : };
355 :
356 0 : AIProxy.prototype.OnTrainingStarted = function(msg)
357 : {
358 0 : this.cmpAIInterface.PushEvent("TrainingStarted", msg);
359 : };
360 :
361 0 : AIProxy.prototype.OnTrainingFinished = function(msg)
362 : {
363 0 : this.cmpAIInterface.PushEvent("TrainingFinished", msg);
364 : };
365 :
366 0 : AIProxy.prototype.OnAIMetadata = function(msg)
367 : {
368 0 : this.cmpAIInterface.PushEvent("AIMetadata", msg);
369 : };
370 :
371 0 : Engine.RegisterComponentType(IID_AIProxy, "AIProxy", AIProxy);
|