Petra: fix population bonus of foundation when using https://code.wildfiregames.com...
[0ad.git] / binaries / data / mods / public / simulation / ai / common-api / gamestate.js
blobe94c35e53af6350d390293f1f540a42365e40f3e
1 var API3 = function(m)
4 /**
5  * Provides an API for the rest of the AI scripts to query the world state at a
6  * higher level than the raw data.
7  */
8 m.GameState = function() {
9         this.ai = null; // must be updated by the AIs.
12 m.GameState.prototype.init = function(SharedScript, state, player) {
13         this.sharedScript = SharedScript;
14         this.EntCollecNames = SharedScript._entityCollectionsName;
15         this.timeElapsed = SharedScript.timeElapsed;
16         this.circularMap = SharedScript.circularMap;
17         this.templates = SharedScript._templates;
18         this.techTemplates = SharedScript._techTemplates;
19         this.entities = SharedScript.entities;
20         this.player = player;
21         this.playerData = SharedScript.playersData[this.player];
22         this.gameType = SharedScript.gameType;
23         this.alliedVictory = SharedScript.alliedVictory;
24         this.ceasefireActive = SharedScript.ceasefireActive;
26         // get the list of possible phases for this civ:
27         // we assume all of them are researchable from the civil centre
28         this.phases = [{ name: "phase_village" }, { name: "phase_town" }, { name: "phase_city" }];
29         let cctemplate = this.getTemplate(this.applyCiv("structures/{civ}_civil_centre"));
30         if (!cctemplate)
31                 return;
32         let civ = this.getPlayerCiv();
33         let techs = cctemplate.researchableTechs(civ);
34         for (let phase of this.phases)
35         {
36                 phase.requirements = [];
37                 let k = techs.indexOf(phase.name);
38                 if (k !== -1)
39                 {
40                         let reqs = DeriveTechnologyRequirements(this.getTemplate(techs[k])._template, civ);
41                         if (reqs)
42                         {
43                                 phase.requirements = reqs;
44                                 continue;
45                         }
46                 }
47                 for (let tech of techs)
48                 {
49                         let template = this.getTemplate(tech)._template;
50                         if (template.replaces && template.replaces.indexOf(phase.name) != -1)
51                         {
52                                 let reqs = DeriveTechnologyRequirements(template, civ);
53                                 if (reqs)
54                                 {
55                                         phase.name = tech;
56                                         phase.requirements = reqs;
57                                         break;
58                                 }
59                         }
60                 }
61         }
62         // Then check if this mod has an additionnal phase
63         for (let tech of techs)
64         {
65                 let template = this.getTemplate(tech)._template;
66                 if (!template.supersedes || template.supersedes != this.phases[2].name)
67                         continue;
68                 let reqs = DeriveTechnologyRequirements(template, civ);
69                 if (reqs)
70                         this.phases.push({ "name": tech, "requirements": reqs });
71                 break;
72         }
75 m.GameState.prototype.update = function(SharedScript)
77         this.timeElapsed = SharedScript.timeElapsed;
78         this.playerData = SharedScript.playersData[this.player];
79         this.ceasefireActive = SharedScript.ceasefireActive;
82 m.GameState.prototype.updatingCollection = function(id, filter, parentCollection)
84         let gid = "player-" + this.player + "-" + id;   // automatically add the player ID
85         return this.updatingGlobalCollection(gid, filter, parentCollection);
88 m.GameState.prototype.destroyCollection = function(id)
90         let gid = "player-" + this.player + "-" + id;   // automatically add the player ID
91         this.destroyGlobalCollection(gid);
94 m.GameState.prototype.updatingGlobalCollection = function(gid, filter, parentCollection)
96         if (this.EntCollecNames.has(gid))
97                 return this.EntCollecNames.get(gid);
99         let collection = parentCollection ? parentCollection.filter(filter) : this.entities.filter(filter);
100         collection.registerUpdates();
101         this.EntCollecNames.set(gid, collection);
102         return collection;
105 m.GameState.prototype.destroyGlobalCollection = function(gid)
107         if (!this.EntCollecNames.has(gid))
108                 return;
110         this.sharedScript.removeUpdatingEntityCollection(this.EntCollecNames.get(gid));
111         this.EntCollecNames.delete(gid);
115  * Reset the entities collections which depend on diplomacy
116  */
117 m.GameState.prototype.resetOnDiplomacyChanged = function()
119         for (let name of this.EntCollecNames.keys())
120                 if (name.startsWith("player-" + this.player + "-diplo"))
121                         this.destroyGlobalCollection(name);
124 m.GameState.prototype.getTimeElapsed = function()
126         return this.timeElapsed;
129 m.GameState.prototype.getBarterPrices = function()
131         return this.playerData.barterPrices;
134 m.GameState.prototype.getGameType = function()
136         return this.gameType;
139 m.GameState.prototype.getAlliedVictory = function()
141         return this.alliedVictory;
144 m.GameState.prototype.isCeasefireActive = function()
146         return this.ceasefireActive;
149 m.GameState.prototype.getTemplate = function(type)
151         if (this.techTemplates[type] !== undefined)
152                 return new m.Technology(this.techTemplates, type);
154         if (!this.templates[type])
155                 return null;
157         return new m.Template(this.sharedScript, type, this.templates[type]);
160 /** Return the template of the structure built from this foundation */
161 m.GameState.prototype.getBuiltTemplate = function(foundationName)
163         if (!foundationName.startsWith("foundation|"))
164         {
165                 warn("Foundation " + foundationName + " not recognised as a foundation.");
166                 return null;
167         }
168         return this.getTemplate(foundationName.substr(11));
171 m.GameState.prototype.applyCiv = function(str)
173         return str.replace(/\{civ\}/g, this.playerData.civ);
176 m.GameState.prototype.getPlayerCiv = function(player)
178         return player !== undefined ? this.sharedScript.playersData[player].civ : this.playerData.civ;
181 m.GameState.prototype.currentPhase = function()
183         for (let i = this.phases.length; i > 0; --i)
184                 if (this.isResearched(this.phases[i-1].name))
185                         return i;
186         return 0;
189 m.GameState.prototype.getNumberOfPhases = function()
191         return this.phases.length;
194 m.GameState.prototype.getPhaseName = function(i)
196         return this.phases[i-1] ? this.phases[i-1].name : undefined;
199 m.GameState.prototype.getPhaseEntityRequirements = function(i)
201         let entityReqs = [];
203         for (let requirement of this.phases[i-1].requirements)
204         {
205                 if (!requirement.entities)
206                         continue;
207                 for (let entity of requirement.entities)
208                         if (entity.check == "count")
209                                 entityReqs.push({
210                                         "class": entity.class,
211                                         "count": entity.number
212                                 });
213         }
215         return entityReqs;
218 m.GameState.prototype.isResearched = function(template)
220         return this.playerData.researchedTechs[template] !== undefined;
223 /** true if started or queued */
224 m.GameState.prototype.isResearching = function(template)
226         return this.playerData.researchStarted[template] !== undefined ||
227                this.playerData.researchQueued[template] !== undefined;
230 /** this is an "in-absolute" check that doesn't check if we have a building to research from. */
231 m.GameState.prototype.canResearch = function(techTemplateName, noRequirementCheck)
233         if (this.playerData.disabledTechnologies[techTemplateName])
234                 return false;
236         let template = this.getTemplate(techTemplateName);
237         if (!template)
238                 return false;
240         // researching or already researched: NOO.
241         if (this.playerData.researchQueued[techTemplateName] ||
242             this.playerData.researchStarted[techTemplateName] ||
243             this.playerData.researchedTechs[techTemplateName])
244                 return false;
246         if (noRequirementCheck)
247                 return true;
249         // if this is a pair, we must check that the pair tech is not being researched
250         if (template.pair())
251         {
252                 let other = template.pairedWith();
253                 if (this.playerData.researchQueued[other] ||
254                     this.playerData.researchStarted[other] ||
255                     this.playerData.researchedTechs[other])
256                         return false;
257         }
259         return this.checkTechRequirements(template.requirements(this.playerData.civ));
263  * Private function for checking a set of requirements is met.
264  * Basically copies TechnologyManager, but compares against
265  * variables only available within the AI
266  */
267 m.GameState.prototype.checkTechRequirements = function(reqs)
269         if (!reqs)
270                 return false;
272         if (!reqs.length)
273                 return true;
275         function doesEntitySpecPass(entity)
276         {
277                 switch (entity.check)
278                 {
279                 case "count":
280                         if (!this.playerData.classCounts[entity.class] || this.playerData.classCounts[entity.class] < entity.number)
281                                 return false;
282                         break;
284                 case "variants":
285                         if (!this.playerData.typeCountsByClass[entity.class] || Object.keys(this.playerData.typeCountsByClass[entity.class]).length < entity.number)
286                                 return false;
287                         break;
288                 }
289                 return true;
290         }
292         return reqs.some(req => {
293                 return Object.keys(req).every(type => {
294                         switch (type)
295                         {
296                         case "techs":
297                                 return req[type].every(tech => !!this.playerData.researchedTechs[tech]);
299                         case "entities":
300                                 return req[type].every(doesEntitySpecPass, this);
301                         }
302                         return false;
303                 });
304         });
307 m.GameState.prototype.getPassabilityMap = function()
309         return this.sharedScript.passabilityMap;
312 m.GameState.prototype.getPassabilityClassMask = function(name)
314         if (!this.sharedScript.passabilityClasses[name])
315                 error("Tried to use invalid passability class name '" + name + "'");
316         return this.sharedScript.passabilityClasses[name];
319 m.GameState.prototype.getResources = function()
321         return new m.Resources(this.playerData.resourceCounts);
324 m.GameState.prototype.getPopulation = function()
326         return this.playerData.popCount;
329 m.GameState.prototype.getPopulationLimit = function() {
330         return this.playerData.popLimit;
333 m.GameState.prototype.getPopulationMax = function() {
334         return this.playerData.popMax;
337 m.GameState.prototype.getPlayerID = function()
339         return this.player;
342 m.GameState.prototype.hasAllies = function()
344         for (let i in this.playerData.isAlly)
345                 if (this.playerData.isAlly[i] && +i !== this.player &&
346                     this.sharedScript.playersData[i].state !== "defeated")
347                         return true;
348         return false;
351 m.GameState.prototype.hasEnemies = function()
353         for (let i in this.playerData.isEnemy)
354                 if (this.playerData.isEnemy[i] && +i !== 0 &&
355                     this.sharedScript.playersData[i].state !== "defeated")
356                         return true;
357         return false;
360 m.GameState.prototype.hasNeutrals = function()
362         for (let i in this.playerData.isNeutral)
363                 if (this.playerData.isNeutral[i] &&
364                     this.sharedScript.playersData[i].state !== "defeated")
365                         return true;
366         return false;
369 m.GameState.prototype.isPlayerNeutral = function(id)
371         return this.playerData.isNeutral[id];
374 m.GameState.prototype.isPlayerAlly = function(id)
376         return this.playerData.isAlly[id];
379 m.GameState.prototype.isPlayerMutualAlly = function(id)
381         return this.playerData.isMutualAlly[id];
384 m.GameState.prototype.isPlayerEnemy = function(id)
386         return this.playerData.isEnemy[id];
389 m.GameState.prototype.getEnemies = function()
391         let ret = [];
392         for (let i in this.playerData.isEnemy)
393                 if (this.playerData.isEnemy[i])
394                         ret.push(+i);
395         return ret;
398 m.GameState.prototype.getNeutrals = function()
400         let ret = [];
401         for (let i in this.playerData.isNeutral)
402                 if (this.playerData.isNeutral[i])
403                         ret.push(+i);
404         return ret;
407 m.GameState.prototype.getAllies = function()
409         let ret = [];
410         for (let i in this.playerData.isAlly)
411                 if (this.playerData.isAlly[i])
412                         ret.push(+i);
413         return ret;
416 m.GameState.prototype.getExclusiveAllies = function()
417 {       // Player is not included
418         let ret = [];
419         for (let i in this.playerData.isAlly)
420                 if (this.playerData.isAlly[i] && +i !== this.player)
421                         ret.push(+i);
422         return ret;
425 m.GameState.prototype.getMutualAllies = function()
427         let ret = [];
428         for (let i in this.playerData.isMutualAlly)
429                 if (this.playerData.isMutualAlly[i] &&
430                     this.sharedScript.playersData[i].isMutualAlly[this.player])
431                         ret.push(+i);
432         return ret;
435 m.GameState.prototype.isEntityAlly = function(ent)
437         if (!ent)
438                 return false;
439         return this.playerData.isAlly[ent.owner()];
442 m.GameState.prototype.isEntityExclusiveAlly = function(ent)
444         if (!ent)
445                 return false;
446         return this.playerData.isAlly[ent.owner()] && ent.owner() !== this.player;
449 m.GameState.prototype.isEntityEnemy = function(ent)
451         if (!ent)
452                 return false;
453         return this.playerData.isEnemy[ent.owner()];
456 m.GameState.prototype.isEntityOwn = function(ent)
458         if (!ent)
459                 return false;
460         return ent.owner() === this.player;
463 m.GameState.prototype.getEntityById = function(id)
465         if (this.entities._entities.has(+id))
466                 return this.entities._entities.get(+id);
468         return undefined;
471 m.GameState.prototype.getEntities = function(id)
473         if (id === undefined)
474                 return this.entities;
476         return this.updatingGlobalCollection("player-" + id + "-entities", m.Filters.byOwner(id));
479 m.GameState.prototype.getStructures = function()
481         return this.updatingGlobalCollection("structures", m.Filters.byClass("Structure"), this.entities);
484 m.GameState.prototype.getOwnEntities = function()
486         return this.updatingGlobalCollection("player-" + this.player + "-entities", m.Filters.byOwner(this.player));
489 m.GameState.prototype.getOwnStructures = function()
491         return this.updatingGlobalCollection("player-" + this.player + "-structures", m.Filters.byClass("Structure"), this.getOwnEntities());
494 m.GameState.prototype.getOwnUnits = function()
496         return this.updatingGlobalCollection("player-" + this.player + "-units", m.Filters.byClass("Unit"), this.getOwnEntities());
499 m.GameState.prototype.getAllyEntities = function()
501         return this.entities.filter(m.Filters.byOwners(this.getAllies()));
504 m.GameState.prototype.getExclusiveAllyEntities = function()
506         return this.entities.filter(m.Filters.byOwners(this.getExclusiveAllies()));
509 m.GameState.prototype.getAllyStructures = function()
511         return this.updatingCollection("diplo-ally-structures", m.Filters.byClass("Structure"), this.getAllyEntities());
514 m.GameState.prototype.getNeutralStructures = function()
516         return this.getStructures().filter(m.Filters.byOwners(this.getNeutrals()));
519 m.GameState.prototype.getEnemyEntities = function()
521         return this.entities.filter(m.Filters.byOwners(this.getEnemies()));
524 m.GameState.prototype.getEnemyStructures = function(enemyID)
526         if (enemyID === undefined)
527                 return this.updatingCollection("diplo-enemy-structures", m.Filters.byClass("Structure"), this.getEnemyEntities());
529         return this.updatingGlobalCollection("player-" + enemyID + "-structures", m.Filters.byClass("Structure"), this.getEntities(enemyID));
532 m.GameState.prototype.getEnemyUnits = function(enemyID)
534         if (enemyID === undefined)
535                 return this.getEnemyEntities().filter(m.Filters.byClass("Unit"));
537         return this.updatingGlobalCollection("player-" + enemyID + "-units", m.Filters.byClass("Unit"), this.getEntities(enemyID));
540 /** if maintain is true, this will be stored. Otherwise it's one-shot. */
541 m.GameState.prototype.getOwnEntitiesByMetadata = function(key, value, maintain)
543         if (maintain === true)
544                 return this.updatingCollection(key + "-" + value, m.Filters.byMetadata(this.player, key, value),this.getOwnEntities());
545         return this.getOwnEntities().filter(m.Filters.byMetadata(this.player, key, value));
548 m.GameState.prototype.getOwnEntitiesByRole = function(role, maintain)
550         return this.getOwnEntitiesByMetadata("role", role, maintain);
553 m.GameState.prototype.getOwnEntitiesByType = function(type, maintain)
555         let filter = m.Filters.byType(type);
556         if (maintain === true)
557                 return this.updatingCollection("type-" + type, filter, this.getOwnEntities());
558         return this.getOwnEntities().filter(filter);
561 m.GameState.prototype.getOwnEntitiesByClass = function(cls, maintain)
563         let filter = m.Filters.byClass(cls);
564         if (maintain)
565                 return this.updatingCollection("class-" + cls, filter, this.getOwnEntities());
566         return this.getOwnEntities().filter(filter);
569 m.GameState.prototype.getOwnFoundationsByClass = function(cls, maintain)
571         let filter = m.Filters.byClass(cls);
572         if (maintain)
573                 return this.updatingCollection("foundations-class-" + cls, filter, this.getOwnFoundations());
574         return this.getOwnFoundations().filter(filter);
577 m.GameState.prototype.getOwnTrainingFacilities = function()
579         return this.updatingGlobalCollection("player-" + this.player + "-training-facilities", m.Filters.byTrainingQueue(), this.getOwnEntities());
582 m.GameState.prototype.getOwnResearchFacilities = function()
584         return this.updatingGlobalCollection("player-" + this.player + "-research-facilities", m.Filters.byResearchAvailable(this.playerData.civ), this.getOwnEntities());
588 m.GameState.prototype.countEntitiesByType = function(type, maintain)
590         return this.getOwnEntitiesByType(type, maintain).length;
593 m.GameState.prototype.countEntitiesAndQueuedByType = function(type, maintain)
595         let template = this.getTemplate(type);
596         if (!template)
597                 return 0;
599         let count = this.countEntitiesByType(type, maintain);
601         // Count building foundations
602         if (template.hasClass("Structure") === true)
603                 count += this.countFoundationsByType(type, true);
604         else if (template.resourceSupplyType() !== undefined)   // animal resources
605                 count += this.countEntitiesByType("resource|" + type, true);
606         else
607         {
608                 // Count entities in building production queues
609                 // TODO: maybe this fails for corrals.
610                 this.getOwnTrainingFacilities().forEach(function(ent) {
611                         for (let item of ent.trainingQueue())
612                                 if (item.unitTemplate == type)
613                                         count += item.count;
614                 });
615         }
617         return count;
620 m.GameState.prototype.countFoundationsByType = function(type, maintain)
622         let foundationType = "foundation|" + type;
624         if (maintain === true)
625                 return this.updatingCollection("foundation-type-" + type, m.Filters.byType(foundationType), this.getOwnFoundations()).length;
627         let count = 0;
628         this.getOwnStructures().forEach(function(ent) {
629                 if (ent.templateName() == foundationType)
630                         ++count;
631         });
632         return count;
635 m.GameState.prototype.countOwnEntitiesByRole = function(role)
637         return this.getOwnEntitiesByRole(role, "true").length;
640 m.GameState.prototype.countOwnEntitiesAndQueuedWithRole = function(role)
642         let count = this.countOwnEntitiesByRole(role);
644         // Count entities in building production queues
645         this.getOwnTrainingFacilities().forEach(function(ent) {
646                 for (let item of ent.trainingQueue())
647                         if (item.metadata && item.metadata.role && item.metadata.role == role)
648                                 count += item.count;
649         });
650         return count;
653 m.GameState.prototype.countOwnQueuedEntitiesWithMetadata = function(data, value)
655         // Count entities in building production queues
656         let count = 0;
657         this.getOwnTrainingFacilities().forEach(function(ent) {
658                 for (let item of ent.trainingQueue())
659                         if (item.metadata && item.metadata[data] && item.metadata[data] == value)
660                                 count += item.count;
661         });
662         return count;
665 m.GameState.prototype.getOwnFoundations = function()
667         return this.updatingGlobalCollection("player-" + this.player + "-foundations", m.Filters.isFoundation(), this.getOwnStructures());
670 m.GameState.prototype.getOwnDropsites = function(resource)
672         if (resource)
673                 return this.updatingCollection("ownDropsite-" + resource, m.Filters.isDropsite(resource), this.getOwnEntities());
674         return this.updatingCollection("ownDropsite-all", m.Filters.isDropsite(), this.getOwnEntities());
677 m.GameState.prototype.getAnyDropsites = function(resource)
679         if (resource)
680                 return this.updatingGlobalCollection("anyDropsite-" + resource, m.Filters.isDropsite(resource), this.getEntities());
681         return this.updatingGlobalCollection("anyDropsite-all", m.Filters.isDropsite(), this.getEntities());
684 m.GameState.prototype.getResourceSupplies = function(resource)
686         return this.updatingGlobalCollection("resource-" + resource, m.Filters.byResource(resource), this.getEntities());
689 m.GameState.prototype.getHuntableSupplies = function()
691         return this.updatingGlobalCollection("resource-hunt", m.Filters.isHuntable(), this.getEntities());
694 m.GameState.prototype.getFishableSupplies = function()
696         return this.updatingGlobalCollection("resource-fish", m.Filters.isFishable(), this.getEntities());
699 /** This returns only units from buildings. */
700 m.GameState.prototype.findTrainableUnits = function(classes, anticlasses)
702         let allTrainable = [];
703         let civ = this.playerData.civ;
704         this.getOwnStructures().forEach(function(ent) {
705                 let trainable = ent.trainableEntities(civ);
706                 if (!trainable)
707                         return;
708                 for (let unit of trainable)
709                         if (allTrainable.indexOf(unit) === -1)
710                                 allTrainable.push(unit);
711         });
712         let ret = [];
713         let limits = this.getEntityLimits();
714         let current = this.getEntityCounts();
715         for (let trainable of allTrainable)
716         {
717                 if (this.isTemplateDisabled(trainable))
718                         continue;
719                 let template = this.getTemplate(trainable);
720                 if (!template || !template.available(this))
721                         continue;
723                 let okay = true;
724                 for (let clas of classes)
725                 {
726                         if (template.hasClass(clas))
727                                 continue;
728                         okay = false;
729                         break;
730                 }
731                 if (!okay)
732                         continue;
734                 for (let clas of anticlasses)
735                 {
736                         if (!template.hasClass(clas))
737                                 continue;
738                         okay = false;
739                         break;
740                 }
741                 if (!okay)
742                         continue;
744                 let category = template.trainingCategory();
745                 if (category && limits[category] && current[category] >= limits[category])
746                         continue;
748                 ret.push( [trainable, template] );
749         }
750         return ret;
754  * Return all techs which can currently be researched
755  * Does not factor cost.
756  * If there are pairs, both techs are returned.
757  */
758 m.GameState.prototype.findAvailableTech = function()
760         let allResearchable = [];
761         let civ = this.playerData.civ;
762         for (let ent of this.getOwnEntities().values())
763         {
764                 let searchable = ent.researchableTechs(civ);
765                 if (!searchable)
766                         continue;
767                 for (let tech of searchable)
768                         if (!this.playerData.disabledTechnologies[tech] && allResearchable.indexOf(tech) === -1)
769                                 allResearchable.push(tech);
770         }
772         let ret = [];
773         for (let tech of allResearchable)
774         {
775                 let template = this.getTemplate(tech);
776                 if (template.pairDef())
777                 {
778                         let techs = template.getPairedTechs();
779                         if (this.canResearch(techs[0]._templateName))
780                                 ret.push([techs[0]._templateName, techs[0]] );
781                         if (this.canResearch(techs[1]._templateName))
782                                 ret.push([techs[1]._templateName, techs[1]] );
783                 }
784                 else if (this.canResearch(tech))
785                 {
786                         // Phases are treated separately
787                         if (this.phases.every(phase => template._templateName != phase.name))
788                                 ret.push( [tech, template] );
789                 }
790         }
791         return ret;
795  * Return true if we have a building able to train that template
796  */
797 m.GameState.prototype.hasTrainer = function(template)
799         let civ = this.playerData.civ;
800         for (let ent of this.getOwnTrainingFacilities().values())
801         {
802                 let trainable = ent.trainableEntities(civ);
803                 if (trainable && trainable.indexOf(template) !== -1)
804                         return true;
805         }
806         return false;
810  * Find buildings able to train that template.
811  */
812 m.GameState.prototype.findTrainers = function(template)
814         let civ = this.playerData.civ;
815         return this.getOwnTrainingFacilities().filter(function(ent) {
816                 let trainable = ent.trainableEntities(civ);
817                 return trainable && trainable.indexOf(template) !== -1;
818         });
822  * Get any unit that is capable of constructing the given building type.
823  */
824 m.GameState.prototype.findBuilder = function(template)
826         for (let ent of this.getOwnUnits().values())
827         {
828                 let buildable = ent.buildableEntities();
829                 if (buildable && buildable.indexOf(template) !== -1)
830                         return ent;
831         }
832         return undefined;
835 /** Return true if one of our buildings is capable of researching the given tech */
836 m.GameState.prototype.hasResearchers = function(templateName, noRequirementCheck)
838         // let's check we can research the tech.
839         if (!this.canResearch(templateName, noRequirementCheck))
840                 return false;
842         let civ = this.playerData.civ;
844         for (let ent of this.getOwnResearchFacilities().values())
845         {
846                 let techs = ent.researchableTechs(civ);
847                 for (let tech of techs)
848                 {
849                         let temp = this.getTemplate(tech);
850                         if (temp.pairDef())
851                         {
852                                 let pairedTechs = temp.getPairedTechs();
853                                 if (pairedTechs[0]._templateName == templateName ||
854                                     pairedTechs[1]._templateName == templateName)
855                                         return true;
856                         }
857                         else if (tech == templateName)
858                                 return true;
859                 }
860         }
861         return false;
864 /** Find buildings that are capable of researching the given tech */
865 m.GameState.prototype.findResearchers = function(templateName, noRequirementCheck)
867         // let's check we can research the tech.
868         if (!this.canResearch(templateName, noRequirementCheck))
869                 return undefined;
871         let self = this;
872         let civ = this.playerData.civ;
874         return this.getOwnResearchFacilities().filter(function(ent) {
875                 let techs = ent.researchableTechs(civ);
876                 for (let tech of techs)
877                 {
878                         let thisTemp = self.getTemplate(tech);
879                         if (thisTemp.pairDef())
880                         {
881                                 let pairedTechs = thisTemp.getPairedTechs();
882                                 if (pairedTechs[0]._templateName == templateName ||
883                                     pairedTechs[1]._templateName == templateName)
884                                         return true;
885                         }
886                         else if (tech == templateName)
887                                 return true;
888                 }
889                 return false;
890         });
894  * Get any buildable structure with a given class
895  * TODO when several available, choose the best one
896  */
897 m.GameState.prototype.findStructureWithClass = function(classes)
899         let entTemplates = new Set();
900         for (let ent of this.getOwnUnits().values())
901         {
902                 if (entTemplates.has(ent.templateName()))
903                         continue;
904                 let buildables = ent.buildableEntities();
905                 for (let buildable of buildables)
906                 {
907                         if (this.isTemplateDisabled(buildable))
908                                 continue;
909                         let template = this.getTemplate(buildable);
910                         if (!template || !template.available(this))
911                                 continue;
912                         if (MatchesClassList(template.classes(), classes))
913                                 return buildable;
914                 }
915                 entTemplates.add(ent.templateName());
916         }
917         return undefined;
920 m.GameState.prototype.getEntityLimits = function()
922         return this.playerData.entityLimits;
925 m.GameState.prototype.getEntityCounts = function()
927         return this.playerData.entityCounts;
930 m.GameState.prototype.isTemplateAvailable = function(templateName)
932         return this.templates[templateName] && !this.isTemplateDisabled(templateName);
935 m.GameState.prototype.isTemplateDisabled = function(templateName)
937         if (!this.playerData.disabledTemplates[templateName])
938                 return false;
939         return this.playerData.disabledTemplates[templateName];
942 /** Checks whether the maximum number of buildings have been constructed for a certain catergory */
943 m.GameState.prototype.isEntityLimitReached = function(category)
945         if (this.playerData.entityLimits[category] === undefined ||
946             this.playerData.entityCounts[category] === undefined)
947                 return false;
948         return this.playerData.entityCounts[category] >= this.playerData.entityLimits[category];
951 m.GameState.prototype.getTraderTemplatesGains = function()
953         let shipMechantTemplateName = this.applyCiv("units/{civ}_ship_merchant");
954         let supportTraderTemplateName = this.applyCiv("units/{civ}_support_trader");
955         let shipMerchantTemplate = !this.isTemplateDisabled(shipMechantTemplateName) && this.getTemplate(shipMechantTemplateName);
956         let supportTraderTemplate = !this.isTemplateDisabled(supportTraderTemplateName) && this.getTemplate(supportTraderTemplateName);
957         return {
958                 "navalGainMultiplier": shipMerchantTemplate && shipMerchantTemplate.gainMultiplier(),
959                 "landGainMultiplier": supportTraderTemplate && supportTraderTemplate.gainMultiplier()
960         };
963 return m;
965 }(API3);