forked from 0ad/0ad
448 lines
11 KiB
JavaScript
448 lines
11 KiB
JavaScript
/**
|
|
* Provides an API for the rest of the AI scripts to query the world state
|
|
* at a higher level than the raw data.
|
|
*/
|
|
var GameState = Class({
|
|
|
|
_init: function(ai)
|
|
{
|
|
MemoizeInit(this);
|
|
|
|
this.ai = ai;
|
|
this.timeElapsed = ai.timeElapsed;
|
|
this.templates = ai.templates;
|
|
this.entities = ai.entities;
|
|
this.player = ai.player;
|
|
this.playerData = ai.playerData;
|
|
|
|
if (!this.ai._gameStateStore){
|
|
this.ai._gameStateStore = {};
|
|
}
|
|
this.store = this.ai._gameStateStore;
|
|
},
|
|
|
|
getTimeElapsed: function()
|
|
{
|
|
return this.timeElapsed;
|
|
},
|
|
|
|
getTemplate: function(type)
|
|
{
|
|
if (!this.templates[type])
|
|
return null;
|
|
return new EntityTemplate(this.templates[type]);
|
|
},
|
|
|
|
applyCiv: function(str)
|
|
{
|
|
return str.replace(/\{civ\}/g, this.playerData.civ);
|
|
},
|
|
|
|
displayCiv: function()
|
|
{
|
|
return this.playerData.civ;
|
|
},
|
|
|
|
getResources: function()
|
|
{
|
|
return new Resources(this.playerData.resourceCounts);
|
|
},
|
|
|
|
getPassabilityMap: function()
|
|
{
|
|
return this.ai.passabilityMap;
|
|
},
|
|
|
|
getPassabilityClassMask: function(name)
|
|
{
|
|
if (!(name in this.ai.passabilityClasses))
|
|
error("Tried to use invalid passability class name '"+name+"'");
|
|
return this.ai.passabilityClasses[name];
|
|
},
|
|
|
|
getTerritoryMap: function()
|
|
{
|
|
return this.ai.territoryMap;
|
|
},
|
|
|
|
getEntities: function() {
|
|
return this.entities;
|
|
},
|
|
|
|
updatingCollection: function(id, filter, collection){
|
|
if (!this.store[id]){
|
|
this.store[id] = collection.filter(filter);
|
|
this.store[id].registerUpdates();
|
|
}
|
|
|
|
return this.store[id];
|
|
},
|
|
|
|
getOwnEntities: function()
|
|
{
|
|
if (!this.store.ownEntities){
|
|
this.store.ownEntities = this.getEntities().filter(Filters.byOwner(this.player));
|
|
this.store.ownEntities.registerUpdates();
|
|
}
|
|
|
|
return this.store.ownEntities;
|
|
},
|
|
|
|
getOwnRoleGroup: function(role) {
|
|
return this.updatingCollection("RoleGroup" + role, Filters.byMetadata("role", role), this.getOwnEntities());
|
|
},
|
|
|
|
getOwnWithClass: function(aclass) {
|
|
return this.updatingCollection("ClassGroup" + aclass, Filters.byClass(aclass), this.getOwnEntities());
|
|
},
|
|
|
|
getNotGaia: function() {
|
|
var collection = this.updatingCollection("NotGaia", Filters.byNotOwner(0), this.getEntities());
|
|
return collection;
|
|
},
|
|
|
|
getJustEnemies: function() {
|
|
var collection = this.updatingCollection("JustEnemies", Filters.byNotOwner(this.player), this.getNotGaia());
|
|
//warn(collection.length + " enemy unit objects")
|
|
return collection;
|
|
},
|
|
|
|
getEnemiesWithClass: function(aclass) {
|
|
var collection = this.updatingCollection("EnemyClassGroup" + aclass, Filters.byClass(aclass), this.getJustEnemies());
|
|
//warn(collection.length + " enemy unit objects")
|
|
return collection;
|
|
},
|
|
|
|
// These get enemies things are copied from qbot, to avoid duplication of a ton of effort (yay laziness)
|
|
getEnemies: function(){
|
|
var ret = [];
|
|
for (i in this.playerData.isEnemy){
|
|
if (this.playerData.isEnemy[i]){
|
|
ret.push(i);
|
|
}
|
|
}
|
|
return ret;
|
|
},
|
|
|
|
getEnemyEntities: function() {
|
|
var diplomacyChange = false;
|
|
var enemies = this.getEnemies();
|
|
warn(enemies.length + " enemy factions")
|
|
if (this.store.enemies){
|
|
if (this.store.enemies.length != enemies.length){
|
|
diplomacyChange = true;
|
|
}
|
|
else{
|
|
for (var i = 0; i < enemies.length; i++){
|
|
if (enemies[i] !== this.store.enemies[i]){
|
|
diplomacyChange = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!this.store.enemyEntities){
|
|
var filter = Filters.byOwners(enemies);
|
|
this.store.enemyEntities = this.getEntities().filter(filter);
|
|
this.store.enemyEntities.registerUpdates();
|
|
this.store.enemies = enemies;
|
|
}
|
|
if (diplomacyChange == true){
|
|
var filter = Filters.byOwners(enemies);
|
|
this.store.enemyEntities = this.getEntities().filter(filter);
|
|
this.store.enemyEntities.registerUpdates();
|
|
this.store.enemies = enemies;
|
|
}
|
|
warn(this.store.enemyEntities.length + " enemy objects")
|
|
return this.store.enemyEntities;
|
|
},
|
|
/// Laziness ends here.
|
|
|
|
getOwnEntitiesWithRole: Memoize('getOwnEntitiesWithRole', function(role)
|
|
{
|
|
var metas = this.ai._entityMetadata;
|
|
if (role === undefined)
|
|
return this.getOwnEntities().filter_raw(function(ent) {
|
|
var metadata = metas[ent.id];
|
|
if (!metadata || !('role' in metadata))
|
|
return true;
|
|
return (metadata.role === undefined);
|
|
});
|
|
else
|
|
return this.getOwnEntities().filter_raw(function(ent) {
|
|
var metadata = metas[ent.id];
|
|
if (!metadata || !('role' in metadata))
|
|
return false;
|
|
return (metadata.role === role);
|
|
});
|
|
}),
|
|
|
|
getOwnEntitiesWithTwoRoles: Memoize('getOwnEntitiesWithRole', function(role, role2)
|
|
{
|
|
var metas = this.ai._entityMetadata;
|
|
if (role === undefined)
|
|
return this.getOwnEntities().filter_raw(function(ent) {
|
|
var metadata = metas[ent.id];
|
|
if (!metadata || !('role' in metadata))
|
|
return true;
|
|
return (metadata.role === undefined);
|
|
});
|
|
else if (role2 === undefined)
|
|
return this.getOwnEntities().filter_raw(function(ent) {
|
|
var metadata = metas[ent.id];
|
|
if (!metadata || !('role' in metadata))
|
|
return true;
|
|
return (metadata.role === undefined);
|
|
});
|
|
else
|
|
return this.getOwnEntities().filter_raw(function(ent) {
|
|
var metadata = metas[ent.id];
|
|
if (!metadata || !('role' in metadata))
|
|
return false;
|
|
return (metadata.role === role || metadata.role === role2);
|
|
});
|
|
}),
|
|
|
|
countEntitiesWithType: function(type)
|
|
{
|
|
var count = 0;
|
|
this.getOwnEntities().forEach(function(ent) {
|
|
var t = ent.templateName();
|
|
if (t == type)
|
|
++count;
|
|
});
|
|
return count;
|
|
},
|
|
|
|
countEntitiesAndQueuedWithType: function(type)
|
|
{
|
|
var foundationType = "foundation|" + type;
|
|
var count = 0;
|
|
this.getOwnEntities().forEach(function(ent) {
|
|
|
|
var t = ent.templateName();
|
|
if (t == type || t == foundationType)
|
|
++count;
|
|
|
|
var queue = ent.trainingQueue();
|
|
if (queue)
|
|
{
|
|
queue.forEach(function(item) {
|
|
if (item.template == type)
|
|
count += item.count;
|
|
});
|
|
}
|
|
});
|
|
return count;
|
|
},
|
|
|
|
countEntitiesAndQueuedWithRole: function(role)
|
|
{
|
|
var count = 0;
|
|
this.getOwnEntities().forEach(function(ent) {
|
|
|
|
if (ent.getMetadata("role") == role)
|
|
++count;
|
|
|
|
var queue = ent.trainingQueue();
|
|
if (queue)
|
|
{
|
|
queue.forEach(function(item) {
|
|
if (item.metadata && item.metadata.role == role)
|
|
count += item.count;
|
|
});
|
|
}
|
|
});
|
|
return count;
|
|
},
|
|
|
|
countEntitiesAndQueuedWithRoles: function(role, role2)
|
|
{
|
|
var count = 0;
|
|
this.getOwnEntities().forEach(function(ent) {
|
|
|
|
if (ent.getMetadata("role") == role)
|
|
++count;
|
|
else if (ent.getMetadata("role") == role2)
|
|
++count;
|
|
|
|
var queue = ent.trainingQueue();
|
|
if (queue)
|
|
{
|
|
queue.forEach(function(item) {
|
|
if (item.metadata && item.metadata.role == role)
|
|
count += item.count;
|
|
else if (item.metadata && item.metadata.role == role2)
|
|
count += item.count;
|
|
});
|
|
}
|
|
});
|
|
return count;
|
|
},
|
|
|
|
/**
|
|
* Find buildings that are capable of training the given unit type,
|
|
* and aren't already too busy.
|
|
*/
|
|
findTrainers: function(template)
|
|
{
|
|
var maxQueueLength = 3; // avoid tying up resources in giant training queues
|
|
|
|
return this.getOwnEntities().filter(function(ent) {
|
|
|
|
var trainable = ent.trainableEntities();
|
|
if (!trainable || trainable.indexOf(template) == -1)
|
|
return false;
|
|
|
|
var queue = ent.trainingQueue();
|
|
if (queue)
|
|
{
|
|
if (queue.length >= maxQueueLength)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Find units that are capable of constructing the given building type.
|
|
*/
|
|
findBuilders: function(template)
|
|
{
|
|
return this.getOwnEntities().filter(function(ent) {
|
|
|
|
var buildable = ent.buildableEntities();
|
|
if (!buildable || buildable.indexOf(template) == -1)
|
|
return false;
|
|
|
|
return true;
|
|
});
|
|
},
|
|
|
|
findFoundations: function(template)
|
|
{
|
|
return this.getOwnEntities().filter(function(ent) {
|
|
return (ent.foundationProgress() !== undefined);
|
|
});
|
|
},
|
|
|
|
findResourceSupplies: function()
|
|
{
|
|
var supplies = {};
|
|
this.entities.forEach(function(ent) {
|
|
var type = ent.resourceSupplyType();
|
|
if (!type)
|
|
return;
|
|
var amount = ent.resourceSupplyAmount();
|
|
if (!amount)
|
|
return;
|
|
|
|
var reportedType;
|
|
if (type.generic == "treasure")
|
|
reportedType = type.specific;
|
|
else
|
|
reportedType = type.generic;
|
|
|
|
if (!supplies[reportedType])
|
|
supplies[reportedType] = [];
|
|
|
|
supplies[reportedType].push({
|
|
"entity": ent,
|
|
"amount": amount,
|
|
"type": type,
|
|
"position": ent.position(),
|
|
});
|
|
});
|
|
return supplies;
|
|
},
|
|
|
|
getPopulationLimit: function()
|
|
{
|
|
return this.playerData.popLimit;
|
|
},
|
|
|
|
getPopulation: function()
|
|
{
|
|
return this.playerData.popCount;
|
|
},
|
|
|
|
getPlayerID: function()
|
|
{
|
|
return this.player;
|
|
},
|
|
|
|
/**
|
|
* Checks whether the player with the given id is an ally of the AI player
|
|
*/
|
|
isPlayerAlly: function(id)
|
|
{
|
|
return this.playerData.isAlly[id];
|
|
},
|
|
|
|
/**
|
|
* Checks whether the player with the given id is an enemy of the AI player
|
|
*/
|
|
isPlayerEnemy: function(id)
|
|
{
|
|
return this.playerData.isEnemy[id];
|
|
},
|
|
|
|
/**
|
|
* Checks whether an Entity object is owned by an ally of the AI player (or self)
|
|
*/
|
|
isEntityAlly: function(ent)
|
|
{
|
|
return (ent && ent.owner() !== undefined && this.playerData.isAlly[ent.owner()]);
|
|
},
|
|
|
|
/**
|
|
* Checks whether an Entity object is owned by an enemy of the AI player
|
|
*/
|
|
isEntityEnemy: function(ent)
|
|
{
|
|
return (ent && ent.owner() !== undefined && this.playerData.isEnemy[ent.owner()]);
|
|
},
|
|
|
|
/**
|
|
* Checks whether an Entity object is owned by the AI player
|
|
*/
|
|
isEntityOwn: function(ent)
|
|
{
|
|
return (ent && ent.owner() !== undefined && ent.owner() == this.player);
|
|
},
|
|
|
|
/**
|
|
* Returns player build limits
|
|
* an object where each key is a category corresponding to a build limit for the player.
|
|
*/
|
|
getBuildLimits: function()
|
|
{
|
|
return this.playerData.buildLimits;
|
|
},
|
|
|
|
/**
|
|
* Returns player build counts
|
|
* an object where each key is a category corresponding to the current building count for the player.
|
|
*/
|
|
getBuildCounts: function()
|
|
{
|
|
return this.playerData.buildCounts;
|
|
},
|
|
|
|
/**
|
|
* Checks if the player's build limit has been reached for the given category.
|
|
* The category comes from the entity tenplate, specifically the BuildRestrictions component.
|
|
*/
|
|
isBuildLimitReached: function(category)
|
|
{
|
|
if (this.playerData.buildLimits[category] === undefined || this.playerData.buildCounts[category] === undefined)
|
|
return false;
|
|
|
|
// There's a special case of build limits per civ centre, so check that first
|
|
if (this.playerData.buildLimits[category].LimitPerCivCentre !== undefined)
|
|
return (this.playerData.buildCounts[category] >= this.playerData.buildCounts["CivilCentre"]*this.playerData.buildLimits[category].LimitPerCivCentre);
|
|
else
|
|
return (this.playerData.buildCounts[category] >= this.playerData.buildLimits[category]);
|
|
},
|
|
});
|