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.
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;
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"));
32 let civ = this.getPlayerCiv();
33 let techs = cctemplate.researchableTechs(civ);
34 for (let phase of this.phases)
36 phase.requirements = [];
37 let k = techs.indexOf(phase.name);
40 let reqs = DeriveTechnologyRequirements(this.getTemplate(techs[k])._template, civ);
43 phase.requirements = reqs;
47 for (let tech of techs)
49 let template = this.getTemplate(tech)._template;
50 if (template.replaces && template.replaces.indexOf(phase.name) != -1)
52 let reqs = DeriveTechnologyRequirements(template, civ);
56 phase.requirements = reqs;
62 // Then check if this mod has an additionnal phase
63 for (let tech of techs)
65 let template = this.getTemplate(tech)._template;
66 if (!template.supersedes || template.supersedes != this.phases[2].name)
68 let reqs = DeriveTechnologyRequirements(template, civ);
70 this.phases.push({ "name": tech, "requirements": reqs });
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);
105 m.GameState.prototype.destroyGlobalCollection = function(gid)
107 if (!this.EntCollecNames.has(gid))
110 this.sharedScript.removeUpdatingEntityCollection(this.EntCollecNames.get(gid));
111 this.EntCollecNames.delete(gid);
115 * Reset the entities collections which depend on diplomacy
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])
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|"))
165 warn("Foundation " + foundationName + " not recognised as a foundation.");
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))
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)
203 for (let requirement of this.phases[i-1].requirements)
205 if (!requirement.entities)
207 for (let entity of requirement.entities)
208 if (entity.check == "count")
210 "class": entity.class,
211 "count": entity.number
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])
236 let template = this.getTemplate(techTemplateName);
240 // researching or already researched: NOO.
241 if (this.playerData.researchQueued[techTemplateName] ||
242 this.playerData.researchStarted[techTemplateName] ||
243 this.playerData.researchedTechs[techTemplateName])
246 if (noRequirementCheck)
249 // if this is a pair, we must check that the pair tech is not being researched
252 let other = template.pairedWith();
253 if (this.playerData.researchQueued[other] ||
254 this.playerData.researchStarted[other] ||
255 this.playerData.researchedTechs[other])
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
267 m.GameState.prototype.checkTechRequirements = function(reqs)
275 function doesEntitySpecPass(entity)
277 switch (entity.check)
280 if (!this.playerData.classCounts[entity.class] || this.playerData.classCounts[entity.class] < entity.number)
285 if (!this.playerData.typeCountsByClass[entity.class] || Object.keys(this.playerData.typeCountsByClass[entity.class]).length < entity.number)
292 return reqs.some(req => {
293 return Object.keys(req).every(type => {
297 return req[type].every(tech => !!this.playerData.researchedTechs[tech]);
300 return req[type].every(doesEntitySpecPass, this);
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()
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")
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")
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")
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()
392 for (let i in this.playerData.isEnemy)
393 if (this.playerData.isEnemy[i])
398 m.GameState.prototype.getNeutrals = function()
401 for (let i in this.playerData.isNeutral)
402 if (this.playerData.isNeutral[i])
407 m.GameState.prototype.getAllies = function()
410 for (let i in this.playerData.isAlly)
411 if (this.playerData.isAlly[i])
416 m.GameState.prototype.getExclusiveAllies = function()
417 { // Player is not included
419 for (let i in this.playerData.isAlly)
420 if (this.playerData.isAlly[i] && +i !== this.player)
425 m.GameState.prototype.getMutualAllies = function()
428 for (let i in this.playerData.isMutualAlly)
429 if (this.playerData.isMutualAlly[i] &&
430 this.sharedScript.playersData[i].isMutualAlly[this.player])
435 m.GameState.prototype.isEntityAlly = function(ent)
439 return this.playerData.isAlly[ent.owner()];
442 m.GameState.prototype.isEntityExclusiveAlly = function(ent)
446 return this.playerData.isAlly[ent.owner()] && ent.owner() !== this.player;
449 m.GameState.prototype.isEntityEnemy = function(ent)
453 return this.playerData.isEnemy[ent.owner()];
456 m.GameState.prototype.isEntityOwn = function(ent)
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);
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);
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);
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);
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);
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)
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;
628 this.getOwnStructures().forEach(function(ent) {
629 if (ent.templateName() == foundationType)
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)
653 m.GameState.prototype.countOwnQueuedEntitiesWithMetadata = function(data, value)
655 // Count entities in building production queues
657 this.getOwnTrainingFacilities().forEach(function(ent) {
658 for (let item of ent.trainingQueue())
659 if (item.metadata && item.metadata[data] && item.metadata[data] == value)
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)
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)
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);
708 for (let unit of trainable)
709 if (allTrainable.indexOf(unit) === -1)
710 allTrainable.push(unit);
713 let limits = this.getEntityLimits();
714 let current = this.getEntityCounts();
715 for (let trainable of allTrainable)
717 if (this.isTemplateDisabled(trainable))
719 let template = this.getTemplate(trainable);
720 if (!template || !template.available(this))
724 for (let clas of classes)
726 if (template.hasClass(clas))
734 for (let clas of anticlasses)
736 if (!template.hasClass(clas))
744 let category = template.trainingCategory();
745 if (category && limits[category] && current[category] >= limits[category])
748 ret.push( [trainable, template] );
754 * Return all techs which can currently be researched
755 * Does not factor cost.
756 * If there are pairs, both techs are returned.
758 m.GameState.prototype.findAvailableTech = function()
760 let allResearchable = [];
761 let civ = this.playerData.civ;
762 for (let ent of this.getOwnEntities().values())
764 let searchable = ent.researchableTechs(civ);
767 for (let tech of searchable)
768 if (!this.playerData.disabledTechnologies[tech] && allResearchable.indexOf(tech) === -1)
769 allResearchable.push(tech);
773 for (let tech of allResearchable)
775 let template = this.getTemplate(tech);
776 if (template.pairDef())
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]] );
784 else if (this.canResearch(tech))
786 // Phases are treated separately
787 if (this.phases.every(phase => template._templateName != phase.name))
788 ret.push( [tech, template] );
795 * Return true if we have a building able to train that template
797 m.GameState.prototype.hasTrainer = function(template)
799 let civ = this.playerData.civ;
800 for (let ent of this.getOwnTrainingFacilities().values())
802 let trainable = ent.trainableEntities(civ);
803 if (trainable && trainable.indexOf(template) !== -1)
810 * Find buildings able to train that template.
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;
822 * Get any unit that is capable of constructing the given building type.
824 m.GameState.prototype.findBuilder = function(template)
826 for (let ent of this.getOwnUnits().values())
828 let buildable = ent.buildableEntities();
829 if (buildable && buildable.indexOf(template) !== -1)
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))
842 let civ = this.playerData.civ;
844 for (let ent of this.getOwnResearchFacilities().values())
846 let techs = ent.researchableTechs(civ);
847 for (let tech of techs)
849 let temp = this.getTemplate(tech);
852 let pairedTechs = temp.getPairedTechs();
853 if (pairedTechs[0]._templateName == templateName ||
854 pairedTechs[1]._templateName == templateName)
857 else if (tech == templateName)
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))
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)
878 let thisTemp = self.getTemplate(tech);
879 if (thisTemp.pairDef())
881 let pairedTechs = thisTemp.getPairedTechs();
882 if (pairedTechs[0]._templateName == templateName ||
883 pairedTechs[1]._templateName == templateName)
886 else if (tech == templateName)
894 * Get any buildable structure with a given class
895 * TODO when several available, choose the best one
897 m.GameState.prototype.findStructureWithClass = function(classes)
899 let entTemplates = new Set();
900 for (let ent of this.getOwnUnits().values())
902 if (entTemplates.has(ent.templateName()))
904 let buildables = ent.buildableEntities();
905 for (let buildable of buildables)
907 if (this.isTemplateDisabled(buildable))
909 let template = this.getTemplate(buildable);
910 if (!template || !template.available(this))
912 if (MatchesClassList(template.classes(), classes))
915 entTemplates.add(ent.templateName());
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])
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)
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);
958 "navalGainMultiplier": shipMerchantTemplate && shipMerchantTemplate.gainMultiplier(),
959 "landGainMultiplier": supportTraderTemplate && supportTraderTemplate.gainMultiplier()