Line data Source code
1 0 : var API3 = function(m)
2 : {
3 :
4 : /** Shared script handling templates and basic terrain analysis */
5 0 : m.SharedScript = function(settings)
6 : {
7 0 : if (!settings)
8 0 : return;
9 :
10 0 : this._players = Object.keys(settings.players).map(key => settings.players[key]); // TODO SM55 Object.values(settings.players)
11 0 : this._templates = settings.templates;
12 :
13 0 : this._entityMetadata = {};
14 0 : for (let player of this._players)
15 0 : this._entityMetadata[player] = {};
16 :
17 : // array of entity collections
18 0 : this._entityCollections = new Map();
19 0 : this._entitiesModifications = new Map(); // entities modifications
20 0 : this._templatesModifications = {}; // template modifications
21 : // each name is a reference to the actual one.
22 0 : this._entityCollectionsName = new Map();
23 0 : this._entityCollectionsByDynProp = {};
24 0 : this._entityCollectionsUID = 0;
25 : };
26 :
27 : /** Return a simple object (using no classes etc) that will be serialized into saved games */
28 0 : m.SharedScript.prototype.Serialize = function()
29 : {
30 0 : return {
31 : "players": this._players,
32 : "templatesModifications": this._templatesModifications,
33 : "entitiesModifications": this._entitiesModifications,
34 : "metadata": this._entityMetadata
35 : };
36 : };
37 :
38 : /**
39 : * Called after the constructor when loading a saved game, with 'data' being
40 : * whatever Serialize() returned
41 : */
42 0 : m.SharedScript.prototype.Deserialize = function(data)
43 : {
44 0 : this._players = data.players;
45 0 : this._templatesModifications = data.templatesModifications;
46 0 : this._entitiesModifications = data.entitiesModifications;
47 0 : this._entityMetadata = data.metadata;
48 :
49 0 : this.isDeserialized = true;
50 : };
51 :
52 0 : m.SharedScript.prototype.GetTemplate = function(name)
53 : {
54 0 : if (this._templates[name] === undefined)
55 0 : this._templates[name] = Engine.GetTemplate(name) || null;
56 :
57 0 : return this._templates[name];
58 : };
59 :
60 : /**
61 : * Initialize the shared component.
62 : * We need to know the initial state of the game for this, as we will use it.
63 : * This is called right at the end of the map generation.
64 : */
65 0 : m.SharedScript.prototype.init = function(state, deserialization)
66 : {
67 0 : if (!deserialization)
68 0 : this._entitiesModifications = new Map();
69 :
70 0 : this.ApplyTemplatesDelta(state);
71 :
72 0 : this.passabilityClasses = state.passabilityClasses;
73 0 : this.playersData = state.players;
74 0 : this.timeElapsed = state.timeElapsed;
75 0 : this.circularMap = state.circularMap;
76 0 : this.mapSize = state.mapSize;
77 0 : this.victoryConditions = new Set(state.victoryConditions);
78 0 : this.alliedVictory = state.alliedVictory;
79 0 : this.ceasefireActive = state.ceasefireActive;
80 0 : this.ceasefireTimeRemaining = state.ceasefireTimeRemaining / 1000;
81 :
82 0 : this.passabilityMap = state.passabilityMap;
83 0 : if (this.mapSize % this.passabilityMap.width !== 0)
84 0 : error("AI shared component inconsistent sizes: map=" + this.mapSize + " while passability=" + this.passabilityMap.width);
85 0 : this.passabilityMap.cellSize = this.mapSize / this.passabilityMap.width;
86 0 : this.territoryMap = state.territoryMap;
87 0 : if (this.mapSize % this.territoryMap.width !== 0)
88 0 : error("AI shared component inconsistent sizes: map=" + this.mapSize + " while territory=" + this.territoryMap.width);
89 0 : this.territoryMap.cellSize = this.mapSize / this.territoryMap.width;
90 :
91 : /*
92 : let landPassMap = new Uint8Array(this.passabilityMap.data.length);
93 : let waterPassMap = new Uint8Array(this.passabilityMap.data.length);
94 : let obstructionMaskLand = this.passabilityClasses["default-terrain-only"];
95 : let obstructionMaskWater = this.passabilityClasses["ship-terrain-only"];
96 : for (let i = 0; i < this.passabilityMap.data.length; ++i)
97 : {
98 : landPassMap[i] = (this.passabilityMap.data[i] & obstructionMaskLand) ? 0 : 255;
99 : waterPassMap[i] = (this.passabilityMap.data[i] & obstructionMaskWater) ? 0 : 255;
100 : }
101 : Engine.DumpImage("LandPassMap.png", landPassMap, this.passabilityMap.width, this.passabilityMap.height, 255);
102 : Engine.DumpImage("WaterPassMap.png", waterPassMap, this.passabilityMap.width, this.passabilityMap.height, 255);
103 : */
104 :
105 0 : this._entities = new Map();
106 0 : if (state.entities)
107 0 : for (let id in state.entities)
108 0 : this._entities.set(+id, new m.Entity(this, state.entities[id]));
109 : // entity collection updated on create/destroy event.
110 0 : this.entities = new m.EntityCollection(this, this._entities);
111 :
112 : // create the terrain analyzer
113 0 : this.terrainAnalyzer = new m.TerrainAnalysis();
114 0 : this.terrainAnalyzer.init(this, state);
115 0 : this.accessibility = new m.Accessibility();
116 0 : this.accessibility.init(state, this.terrainAnalyzer);
117 :
118 : // Resource types: ignore = not used for resource maps
119 : // abundant = abundant resource with small amount each
120 : // sparse = sparse resource, but huge amount each
121 : // The following maps are defined in TerrainAnalysis.js and are used for some building placement (cc, dropsites)
122 : // They are updated by checking for create and destroy events for all resources
123 0 : this.normalizationFactor = { "abundant": 50, "sparse": 90 };
124 0 : this.influenceRadius = { "abundant": 36, "sparse": 48 };
125 0 : this.ccInfluenceRadius = { "abundant": 60, "sparse": 120 };
126 0 : this.resourceMaps = {}; // Contains maps showing the density of resources
127 0 : this.ccResourceMaps = {}; // Contains maps showing the density of resources, optimized for CC placement.
128 0 : this.createResourceMaps();
129 :
130 0 : this.gameState = {};
131 0 : for (let player of this._players)
132 : {
133 0 : this.gameState[player] = new m.GameState();
134 0 : this.gameState[player].init(this, state, player);
135 : }
136 : };
137 :
138 : /**
139 : * General update of the shared script, before each AI's update
140 : * applies entity deltas, and each gamestate.
141 : */
142 0 : m.SharedScript.prototype.onUpdate = function(state)
143 : {
144 0 : if (this.isDeserialized)
145 : {
146 0 : this.init(state, true);
147 0 : this.isDeserialized = false;
148 : }
149 :
150 : // deals with updating based on create and destroy messages.
151 0 : this.ApplyEntitiesDelta(state);
152 0 : this.ApplyTemplatesDelta(state);
153 :
154 0 : Engine.ProfileStart("onUpdate");
155 :
156 : // those are dynamic and need to be reset as the "state" object moves in memory.
157 0 : this.events = state.events;
158 0 : this.passabilityClasses = state.passabilityClasses;
159 0 : this.playersData = state.players;
160 0 : this.timeElapsed = state.timeElapsed;
161 0 : this.barterPrices = state.barterPrices;
162 0 : this.ceasefireActive = state.ceasefireActive;
163 0 : this.ceasefireTimeRemaining = state.ceasefireTimeRemaining / 1000;
164 :
165 0 : this.passabilityMap = state.passabilityMap;
166 0 : this.passabilityMap.cellSize = this.mapSize / this.passabilityMap.width;
167 0 : this.territoryMap = state.territoryMap;
168 0 : this.territoryMap.cellSize = this.mapSize / this.territoryMap.width;
169 :
170 0 : for (let i in this.gameState)
171 0 : this.gameState[i].update(this);
172 :
173 : // TODO: merge this with "ApplyEntitiesDelta" since after all they do the same.
174 0 : this.updateResourceMaps(this.events);
175 :
176 0 : Engine.ProfileStop();
177 : };
178 :
179 0 : m.SharedScript.prototype.ApplyEntitiesDelta = function(state)
180 : {
181 0 : Engine.ProfileStart("Shared ApplyEntitiesDelta");
182 :
183 0 : let foundationFinished = {};
184 :
185 : // by order of updating:
186 : // we "Destroy" last because we want to be able to switch Metadata first.
187 :
188 0 : for (let evt of state.events.Create)
189 : {
190 0 : if (!state.entities[evt.entity])
191 0 : continue; // Sometimes there are things like foundations which get destroyed too fast
192 :
193 0 : let entity = new m.Entity(this, state.entities[evt.entity]);
194 0 : this._entities.set(evt.entity, entity);
195 0 : this.entities.addEnt(entity);
196 :
197 : // Update all the entity collections since the create operation affects static properties as well as dynamic
198 0 : for (let entCol of this._entityCollections.values())
199 0 : entCol.updateEnt(entity);
200 : }
201 :
202 0 : for (let evt of state.events.EntityRenamed)
203 : { // Switch the metadata: TODO entityCollections are updated only because of the owner change. Should be done properly
204 0 : for (let player of this._players)
205 : {
206 0 : this._entityMetadata[player][evt.newentity] = this._entityMetadata[player][evt.entity];
207 0 : this._entityMetadata[player][evt.entity] = {};
208 : }
209 : }
210 :
211 0 : for (let evt of state.events.TrainingFinished)
212 : { // Apply metadata stored in training queues
213 0 : for (let entId of evt.entities)
214 0 : if (this._entities.has(entId))
215 0 : for (let key in evt.metadata)
216 0 : this.setMetadata(evt.owner, this._entities.get(entId), key, evt.metadata[key]);
217 : }
218 :
219 0 : for (let evt of state.events.ConstructionFinished)
220 : {
221 : // metada are already moved by EntityRenamed when needed (i.e. construction, not repair)
222 0 : if (evt.entity != evt.newentity)
223 0 : foundationFinished[evt.entity] = true;
224 : }
225 :
226 0 : for (let evt of state.events.AIMetadata)
227 : {
228 0 : if (!this._entities.has(evt.id))
229 0 : continue; // might happen in some rare cases of foundations getting destroyed, perhaps.
230 : // Apply metadata (here for buildings for example)
231 0 : for (let key in evt.metadata)
232 0 : this.setMetadata(evt.owner, this._entities.get(evt.id), key, evt.metadata[key]);
233 : }
234 :
235 0 : for (let evt of state.events.Destroy)
236 : {
237 0 : if (!this._entities.has(evt.entity))
238 0 : continue;// probably should remove the event.
239 :
240 0 : if (foundationFinished[evt.entity])
241 0 : evt.SuccessfulFoundation = true;
242 :
243 : // The entity was destroyed but its data may still be useful, so
244 : // remember the entity and this AI's metadata concerning it
245 0 : evt.metadata = {};
246 0 : evt.entityObj = this._entities.get(evt.entity);
247 0 : for (let player of this._players)
248 0 : evt.metadata[player] = this._entityMetadata[player][evt.entity];
249 :
250 0 : let entity = this._entities.get(evt.entity);
251 0 : for (let entCol of this._entityCollections.values())
252 0 : entCol.removeEnt(entity);
253 0 : this.entities.removeEnt(entity);
254 :
255 0 : this._entities.delete(evt.entity);
256 0 : this._entitiesModifications.delete(evt.entity);
257 0 : for (let player of this._players)
258 0 : delete this._entityMetadata[player][evt.entity];
259 : }
260 :
261 0 : for (let id in state.entities)
262 : {
263 0 : let changes = state.entities[id];
264 0 : let entity = this._entities.get(+id);
265 0 : for (let prop in changes)
266 : {
267 0 : entity._entity[prop] = changes[prop];
268 0 : this.updateEntityCollections(prop, entity);
269 : }
270 : }
271 :
272 : // apply per-entity aura-related changes.
273 : // this supersedes tech-related changes.
274 0 : for (let id in state.changedEntityTemplateInfo)
275 : {
276 0 : if (!this._entities.has(+id))
277 0 : continue; // dead, presumably.
278 0 : let changes = state.changedEntityTemplateInfo[id];
279 0 : if (!this._entitiesModifications.has(+id))
280 0 : this._entitiesModifications.set(+id, new Map());
281 0 : let modif = this._entitiesModifications.get(+id);
282 0 : for (let change of changes)
283 0 : modif.set(change.variable, change.value);
284 : }
285 0 : Engine.ProfileStop();
286 : };
287 :
288 0 : m.SharedScript.prototype.ApplyTemplatesDelta = function(state)
289 : {
290 0 : Engine.ProfileStart("Shared ApplyTemplatesDelta");
291 :
292 0 : for (let player in state.changedTemplateInfo)
293 : {
294 0 : let playerDiff = state.changedTemplateInfo[player];
295 0 : for (let template in playerDiff)
296 : {
297 0 : let changes = playerDiff[template];
298 0 : if (!this._templatesModifications[template])
299 0 : this._templatesModifications[template] = {};
300 0 : if (!this._templatesModifications[template][player])
301 0 : this._templatesModifications[template][player] = new Map();
302 0 : let modif = this._templatesModifications[template][player];
303 0 : for (let change of changes)
304 0 : modif.set(change.variable, change.value);
305 : }
306 : }
307 0 : Engine.ProfileStop();
308 : };
309 :
310 0 : m.SharedScript.prototype.registerUpdatingEntityCollection = function(entCollection)
311 : {
312 0 : entCollection.setUID(this._entityCollectionsUID);
313 0 : this._entityCollections.set(this._entityCollectionsUID, entCollection);
314 0 : for (let prop of entCollection.dynamicProperties())
315 : {
316 0 : if (!this._entityCollectionsByDynProp[prop])
317 0 : this._entityCollectionsByDynProp[prop] = new Map();
318 0 : this._entityCollectionsByDynProp[prop].set(this._entityCollectionsUID, entCollection);
319 : }
320 0 : this._entityCollectionsUID++;
321 : };
322 :
323 0 : m.SharedScript.prototype.removeUpdatingEntityCollection = function(entCollection)
324 : {
325 0 : let uid = entCollection.getUID();
326 :
327 0 : if (this._entityCollections.has(uid))
328 0 : this._entityCollections.delete(uid);
329 :
330 0 : for (let prop of entCollection.dynamicProperties())
331 0 : if (this._entityCollectionsByDynProp[prop].has(uid))
332 0 : this._entityCollectionsByDynProp[prop].delete(uid);
333 : };
334 :
335 0 : m.SharedScript.prototype.updateEntityCollections = function(property, ent)
336 : {
337 0 : if (this._entityCollectionsByDynProp[property] === undefined)
338 0 : return;
339 :
340 0 : for (let entCol of this._entityCollectionsByDynProp[property].values())
341 0 : entCol.updateEnt(ent);
342 : };
343 :
344 0 : m.SharedScript.prototype.setMetadata = function(player, ent, key, value)
345 : {
346 0 : let metadata = this._entityMetadata[player][ent.id()];
347 0 : if (!metadata)
348 : {
349 0 : this._entityMetadata[player][ent.id()] = {};
350 0 : metadata = this._entityMetadata[player][ent.id()];
351 : }
352 0 : metadata[key] = value;
353 :
354 0 : this.updateEntityCollections('metadata', ent);
355 0 : this.updateEntityCollections('metadata.' + key, ent);
356 : };
357 :
358 0 : m.SharedScript.prototype.getMetadata = function(player, ent, key)
359 : {
360 0 : return this._entityMetadata[player][ent.id()]?.[key];
361 : };
362 :
363 0 : m.SharedScript.prototype.deleteMetadata = function(player, ent, key)
364 : {
365 0 : let metadata = this._entityMetadata[player][ent.id()];
366 :
367 0 : if (!metadata || !(key in metadata))
368 0 : return true;
369 0 : metadata[key] = undefined;
370 0 : delete metadata[key];
371 0 : this.updateEntityCollections('metadata', ent);
372 0 : this.updateEntityCollections('metadata.' + key, ent);
373 0 : return true;
374 : };
375 :
376 0 : m.copyPrototype = function(descendant, parent)
377 : {
378 0 : let sConstructor = parent.toString();
379 0 : let aMatch = sConstructor.match(/\s*function (.*)\(/);
380 :
381 0 : if (aMatch != null)
382 0 : descendant.prototype[aMatch[1]] = parent;
383 :
384 0 : for (let p in parent.prototype)
385 0 : descendant.prototype[p] = parent.prototype[p];
386 : };
387 :
388 : /** creates a map of resource density */
389 0 : m.SharedScript.prototype.createResourceMaps = function()
390 : {
391 0 : for (const resource of Resources.GetCodes())
392 : {
393 0 : if (this.resourceMaps[resource] ||
394 : !(Resources.GetResource(resource).aiAnalysisInfluenceGroup in this.normalizationFactor))
395 0 : continue;
396 : // We're creating them 8-bit. Things could go above 255 if there are really tons of resources
397 : // But at that point the precision is not really important anyway. And it saves memory.
398 0 : this.resourceMaps[resource] = new m.Map(this, "resource");
399 0 : this.ccResourceMaps[resource] = new m.Map(this, "resource");
400 : }
401 0 : for (const ent of this._entities.values())
402 0 : this.addEntityToResourceMap(ent);
403 : };
404 :
405 : /**
406 : * @param {Object} events - The events from a turn.
407 : */
408 0 : m.SharedScript.prototype.updateResourceMaps = function(events)
409 : {
410 0 : for (const e of events.Destroy)
411 0 : if (e.entityObj)
412 0 : this.removeEntityFromResourceMap(e.entityObj);
413 :
414 0 : for (const e of events.Create)
415 0 : if (e.entity && this._entities.has(e.entity))
416 0 : this.addEntityToResourceMap(this._entities.get(e.entity));
417 : };
418 :
419 : /**
420 : * @param {entity} entity - The entity to add to the resource map.
421 : */
422 0 : m.SharedScript.prototype.addEntityToResourceMap = function(entity)
423 : {
424 0 : this.changeEntityInResourceMapHelper(entity, 1);
425 : };
426 :
427 : /**
428 : * @param {entity} entity - The entity to remove from the resource map.
429 : */
430 0 : m.SharedScript.prototype.removeEntityFromResourceMap = function(entity)
431 : {
432 0 : this.changeEntityInResourceMapHelper(entity, -1);
433 : };
434 :
435 : /**
436 : * @param {entity} ent - The entity to add to the resource map.
437 : */
438 0 : m.SharedScript.prototype.changeEntityInResourceMapHelper = function(ent, multiplication = 1)
439 : {
440 0 : if (!ent)
441 0 : return;
442 0 : const entPos = ent.position();
443 0 : if (!entPos)
444 0 : return;
445 0 : const resource = ent.resourceSupplyType()?.generic;
446 0 : if (!resource || !this.resourceMaps[resource])
447 0 : return;
448 0 : const cellSize = this.resourceMaps[resource].cellSize;
449 0 : const x = Math.floor(entPos[0] / cellSize);
450 0 : const y = Math.floor(entPos[1] / cellSize);
451 0 : const grp = Resources.GetResource(resource).aiAnalysisInfluenceGroup;
452 0 : const strength = multiplication * ent.resourceSupplyMax() / this.normalizationFactor[grp];
453 0 : this.resourceMaps[resource].addInfluence(x, y, this.influenceRadius[grp] / cellSize, strength / 2, "constant");
454 0 : this.resourceMaps[resource].addInfluence(x, y, this.influenceRadius[grp] / cellSize, strength / 2);
455 0 : this.ccResourceMaps[resource].addInfluence(x, y, this.ccInfluenceRadius[grp] / cellSize, strength, "constant");
456 : };
457 :
458 0 : return m;
459 :
460 : }(API3);
461 :
|