LCOV - code coverage report
Current view: top level - simulation/ai/common-api - shared.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 247 0.0 %
Date: 2023-04-02 12:52:40 Functions: 0 22 0.0 %

          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             : 

Generated by: LCOV version 1.14