1
0
forked from 0ad/0ad

AI common-api-v2 and a qbot which works with the new API but it not fully transitioned to make use of it properly

This was SVN commit r11429.
This commit is contained in:
Jonathan Waller 2012-04-04 20:23:41 +00:00
parent 21a39dedfb
commit 7eb5480494
22 changed files with 1623 additions and 653 deletions

View File

@ -0,0 +1,250 @@
function BaseAI(settings)
{
if (!settings)
return;
// Make some properties non-enumerable, so they won't be serialised
Object.defineProperty(this, "_player", {value: settings.player, enumerable: false});
Object.defineProperty(this, "_templates", {value: settings.templates, enumerable: false});
Object.defineProperty(this, "_derivedTemplates", {value: {}, enumerable: false});
this._entityMetadata = {};
this._entityCollections = [];
this._entityCollectionsByDynProp = {};
this._entityCollectionsUID = 0;
}
// Components that will be disabled in foundation entity templates.
// (This is a bit yucky and fragile since it's the inverse of
// CCmpTemplateManager::CopyFoundationSubset and only includes components
// that our EntityTemplate class currently uses.)
var g_FoundationForbiddenComponents = {
"TrainingQueue": 1,
"ResourceSupply": 1,
"ResourceDropsite": 1,
"GarrisonHolder": 1,
};
BaseAI.prototype.GetTemplate = function(name)
{
if (this._templates[name])
return this._templates[name];
if (this._derivedTemplates[name])
return this._derivedTemplates[name];
// If this is a foundation template, construct it automatically
if (name.substr(0, 11) === "foundation|")
{
var base = this.GetTemplate(name.substr(11));
var foundation = {};
for (var key in base)
if (!g_FoundationForbiddenComponents[key])
foundation[key] = base[key];
this._derivedTemplates[name] = foundation;
return foundation;
}
error("Tried to retrieve invalid template '"+name+"'");
return null;
};
BaseAI.prototype.HandleMessage = function(state)
{
if (!this._entities)
{
// Do a (shallow) clone of all the initial entity properties (in order
// to copy into our own script context and minimise cross-context
// weirdness)
this._entities = {};
for (var id in state.entities)
{
var ent = new Entity(this, state.entities[id]);
this._entities[id] = ent;
}
}
else
{
this.ApplyEntitiesDelta(state);
}
Engine.ProfileStart("HandleMessage setup");
this.entities = new EntityCollection(this, this._entities);
this.events = state.events;
this.passabilityClasses = state.passabilityClasses;
this.passabilityMap = state.passabilityMap;
this.player = this._player;
this.playerData = state.players[this._player];
this.templates = this._templates;
this.territoryMap = state.territoryMap;
this.timeElapsed = state.timeElapsed;
Engine.ProfileStop();
this.OnUpdate();
// Clean up temporary properties, so they don't disturb the serializer
delete this.entities;
delete this.events;
delete this.passabilityClasses;
delete this.passabilityMap;
delete this.player;
delete this.playerData;
delete this.templates;
delete this.territoryMap;
delete this.timeElapsed;
};
BaseAI.prototype.ApplyEntitiesDelta = function(state)
{
Engine.ProfileStart("ApplyEntitiesDelta");
for each (var evt in state.events)
{
if (evt.type == "Create")
{
if (! state.entities[evt.msg.entity])
{
continue; // Sometimes there are things like foundations which get destroyed too fast
}
this._entities[evt.msg.entity] = new Entity(this, state.entities[evt.msg.entity]);
// Update all the entity collections since the create operation affects static properties as well as dynamic
for each (var entCollection in this._entityCollections)
{
entCollection.updateEnt(this._entities[evt.msg.entity]);
}
}
else if (evt.type == "Destroy")
{
if (!this._entities[evt.msg.entity])
{
continue;
}
// The entity was destroyed but its data may still be useful, so
// remember the entity and this AI's metadata concerning it
evt.msg.metadata = (evt.msg.metadata || []);
evt.msg.entityObj = (evt.msg.entityObj || this._entities[evt.msg.entity]);
evt.msg.metadata[this._player] = this._entityMetadata[evt.msg.entity];
for each (var entCol in this._entityCollections)
{
entCol.removeEnt(this._entities[evt.msg.entity]);
}
delete this._entities[evt.msg.entity];
delete this._entityMetadata[evt.msg.entity];
}
else if (evt.type == "TrainingFinished")
{
// Apply metadata stored in training queues, but only if they
// look like they were added by us
if (evt.msg.owner === this._player)
{
for each (var ent in evt.msg.entities)
{
for (key in evt.msg.metadata)
{
this.setMetadata(this._entities[ent], key, evt.msg.metadata[key])
}
}
}
}
}
for (var id in state.entities)
{
var changes = state.entities[id];
for (var prop in changes)
{
this._entities[id]._entity[prop] = changes[prop];
this.updateEntityCollections(prop, this._entities[id]);
}
}
Engine.ProfileStop();
};
BaseAI.prototype.OnUpdate = function()
{ // AIs override this function
};
BaseAI.prototype.chat = function(message)
{
Engine.PostCommand({"type": "chat", "message": message});
};
BaseAI.prototype.registerUpdatingEntityCollection = function(entCollection)
{
entCollection.setUID(this._entityCollectionsUID);
this._entityCollections.push(entCollection);
for each (var prop in entCollection.dynamicProperties())
{
this._entityCollectionsByDynProp[prop] = this._entityCollectionsByDynProp[prop] || [];
this._entityCollectionsByDynProp[prop].push(entCollection);
}
this._entityCollectionsUID++;
};
BaseAI.prototype.removeUpdatingEntityCollection = function(entCollection)
{
for (var i in this._entityCollections)
{
if (this._entityCollections[i].getUID() === entCollection.getUID())
{
this._entityCollections.splice(i, 1);
}
}
for each (var prop in entCollection.dynamicProperties())
{
for (var i in this._entityCollectionsByDynProp[prop])
{
if (this._entityCollectionsByDynProp[prop][i].getUID() === entCollection.getUID())
{
this._entityCollectionsByDynProp[prop].splice(i, 1);
}
}
}
};
BaseAI.prototype.updateEntityCollections = function(property, ent)
{
if (this._entityCollectionsByDynProp[property])
{
for each (var entCollection in this._entityCollectionsByDynProp[property])
{
entCollection.updateEnt(ent);
}
}
}
BaseAI.prototype.setMetadata = function(ent, key, value)
{
var metadata = this._entityMetadata[ent.id()];
if (!metadata)
metadata = this._entityMetadata[ent.id()] = {};
metadata[key] = value;
this.updateEntityCollections('metadata', ent);
this.updateEntityCollections('metadata.' + key, ent);
}
BaseAI.prototype.getMetadata = function(ent, key)
{
var metadata = this._entityMetadata[ent.id()];
if (!metadata || !(key in metadata))
return undefined;
return metadata[key];
}

View File

@ -0,0 +1,36 @@
/**
* Provides a nicer syntax for defining classes,
* with support for OO-style inheritance.
*/
function Class(data)
{
var ctor;
if (data._init)
ctor = data._init;
else
ctor = function() { };
if (data._super)
{
ctor.prototype = { "__proto__": data._super.prototype };
}
for (var key in data)
{
ctor.prototype[key] = data[key];
}
return ctor;
}
/* Test inheritance:
var A = Class({foo:1, bar:10});
print((new A).foo+" "+(new A).bar+"\n");
var B = Class({foo:2, bar:20});
print((new A).foo+" "+(new A).bar+"\n");
print((new B).foo+" "+(new B).bar+"\n");
var C = Class({_super:A, foo:3});
print((new A).foo+" "+(new A).bar+"\n");
print((new B).foo+" "+(new B).bar+"\n");
print((new C).foo+" "+(new C).bar+"\n");
//*/

View File

@ -0,0 +1,409 @@
var EntityTemplate = Class({
_init: function(template)
{
this._template = template;
},
rank: function() {
if (!this._template.Identity)
return undefined;
return this._template.Identity.Rank;
},
classes: function() {
if (!this._template.Identity || !this._template.Identity.Classes)
return undefined;
return this._template.Identity.Classes._string.split(/\s+/);
},
hasClass: function(name) {
var classes = this.classes();
return (classes && classes.indexOf(name) != -1);
},
civ: function() {
if (!this._template.Identity)
return undefined;
return this._template.Identity.Civ;
},
cost: function() {
if (!this._template.Cost)
return undefined;
var ret = {};
for (var type in this._template.Cost.Resources)
ret[type] = +this._template.Cost.Resources[type];
return ret;
},
/**
* Returns the radius of a circle surrounding this entity's
* obstruction shape, or undefined if no obstruction.
*/
obstructionRadius: function() {
if (!this._template.Obstruction)
return undefined;
if (this._template.Obstruction.Static)
{
var w = +this._template.Obstruction.Static["@width"];
var h = +this._template.Obstruction.Static["@depth"];
return Math.sqrt(w*w + h*h) / 2;
}
if (this._template.Obstruction.Unit)
return +this._template.Obstruction.Unit["@radius"];
return 0; // this should never happen
},
maxHitpoints: function() { return this._template.Health.Max; },
isHealable: function() { return this._template.Health.Healable === "true"; },
isRepairable: function() { return this._template.Health.Repairable === "true"; },
armourStrengths: function() {
if (!this._template.Armour)
return undefined;
return {
hack: +this._template.Armour.Hack,
pierce: +this._template.Armour.Pierce,
crush: +this._template.Armour.Crush
};
},
attackTypes: function() {
if (!this._template.Attack)
return undefined;
var ret = [];
for (var type in this._template.Attack)
ret.push(type);
return ret;
},
attackRange: function(type) {
if (!this._template.Attack || !this._template.Attack[type])
return undefined;
return {
max: +this._template.Attack[type].MaxRange,
min: +(this._template.Attack[type].MinRange || 0)
};
},
attackStrengths: function(type) {
if (!this._template.Attack || !this._template.Attack[type])
return undefined;
return {
hack: +(this._template.Attack[type].Hack || 0),
pierce: +(this._template.Attack[type].Pierce || 0),
crush: +(this._template.Attack[type].Crush || 0)
};
},
attackTimes: function(type) {
if (!this._template.Attack || !this._template.Attack[type])
return undefined;
return {
prepare: +(this._template.Attack[type].PrepareTime || 0),
repeat: +(this._template.Attack[type].RepeatTime || 1000)
};
},
buildableEntities: function() {
if (!this._template.Builder)
return undefined;
var civ = this.civ();
var templates = this._template.Builder.Entities._string.replace(/\{civ\}/g, civ).split(/\s+/);
return templates; // TODO: map to Entity?
},
trainableEntities: function() {
if (!this._template.TrainingQueue)
return undefined;
var civ = this.civ();
var templates = this._template.TrainingQueue.Entities._string.replace(/\{civ\}/g, civ).split(/\s+/);
return templates;
},
resourceSupplyType: function() {
if (!this._template.ResourceSupply)
return undefined;
var [type, subtype] = this._template.ResourceSupply.Type.split('.');
return { "generic": type, "specific": subtype };
},
resourceSupplyMax: function() {
if (!this._template.ResourceSupply)
return undefined;
return +this._template.ResourceSupply.Amount;
},
resourceGatherRates: function() {
if (!this._template.ResourceGatherer)
return undefined;
var ret = {};
for (var r in this._template.ResourceGatherer.Rates)
ret[r] = this._template.ResourceGatherer.Rates[r] * this._template.ResourceGatherer.BaseSpeed;
return ret;
},
resourceDropsiteTypes: function() {
if (!this._template.ResourceDropsite)
return undefined;
return this._template.ResourceDropsite.Types.split(/\s+/);
},
garrisonableClasses: function() {
if (!this._template.GarrisonHolder)
return undefined;
return this._template.GarrisonHolder.List._string.split(/\s+/);
},
/**
* Returns whether this is an animal that is too difficult to hunt.
* (Currently this just includes skittish animals, which are probably
* too fast to chase.)
*/
isUnhuntable: function() {
if (!this._template.UnitAI || !this._template.UnitAI.NaturalBehaviour)
return false;
// return (this._template.UnitAI.NaturalBehaviour == "skittish");
// Actually, since the AI is currently rubbish at hunting, skip all animals
// that aren't really weak:
return this._template.Health.Max >= 10;
},
buildCategory: function() {
if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.Category)
return undefined;
return this._template.BuildRestrictions.Category;
},
buildDistance: function() {
if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.Distance)
return undefined;
return this._template.BuildRestrictions.Distance;
},
buildPlacementType: function() {
if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.PlacementType)
return undefined;
return this._template.BuildRestrictions.PlacementType;
},
buildTerritories: function() {
if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.Territory)
return undefined;
return this._template.BuildRestrictions.Territory.split(/\s+/);
},
hasBuildTerritory: function(territory) {
var territories = this.buildTerritories();
return (territories && territories.indexOf(territory) != -1);
},
visionRange: function() {
if (!this._template.Vision)
return undefined;
return this._template.Vision.Range;
}
});
var Entity = Class({
_super: EntityTemplate,
_init: function(baseAI, entity)
{
this._super.call(this, baseAI.GetTemplate(entity.template));
this._ai = baseAI;
this._templateName = entity.template;
this._entity = entity;
},
toString: function() {
return "[Entity " + this.id() + " " + this.templateName() + "]";
},
id: function() {
return this._entity.id;
},
templateName: function() {
return this._templateName;
},
/**
* Returns extra data that the AI scripts have associated with this entity,
* for arbitrary local annotations.
* (This data is not shared with any other AI scripts.)
*/
getMetadata: function(key) {
return this._ai.getMetadata(this, key);
},
/**
* Sets extra data to be associated with this entity.
*/
setMetadata: function(key, value) {
this._ai.setMetadata(this, key, value);
},
deleteMetadata: function() {
delete this._ai._entityMetadata[this.id()];
},
position: function() { return this._entity.position; },
isIdle: function() {
if (typeof this._entity.idle === "undefined")
return undefined;
return this._entity.idle;
},
unitAIState: function() { return this._entity.unitAIState; },
unitAIOrderData: function() { return this._entity.unitAIOrderData; },
hitpoints: function() { return this._entity.hitpoints; },
isHurt: function() { return this.hitpoints() < this.maxHitpoints(); },
needsHeal: function() { return this.isHurt() && this.isHealable(); },
needsRepair: function() { return this.isHurt() && this.isRepairable(); },
/**
* Returns the current training queue state, of the form
* [ { "id": 0, "template": "...", "count": 1, "progress": 0.5, "metadata": ... }, ... ]
*/
trainingQueue: function() {
var queue = this._entity.trainingQueue;
return queue;
},
trainingQueueTime: function() {
var queue = this._entity.trainingQueue;
if (!queue)
return undefined;
// TODO: compute total time for units in training queue
return queue.length;
},
foundationProgress: function() {
if (typeof this._entity.foundationProgress === "undefined")
return undefined;
return this._entity.foundationProgress;
},
owner: function() {
return this._entity.owner;
},
isOwn: function() {
if (typeof this._entity.owner === "undefined")
return false;
return this._entity.owner === this._ai._player;
},
isFriendly: function() {
return this.isOwn(); // TODO: diplomacy
},
isEnemy: function() {
return !this.isOwn(); // TODO: diplomacy
},
resourceSupplyAmount: function() { return this._entity.resourceSupplyAmount; },
resourceCarrying: function() { return this._entity.resourceCarrying; },
garrisoned: function() { return new EntityCollection(this._ai, this._entity.garrisoned); },
// TODO: visibility
move: function(x, z, queued) {
queued = queued || false;
Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued});
return this;
},
garrison: function(target) {
Engine.PostCommand({"type": "garrison", "entities": [this.id()], "target": target.id(),"queued": false});
return this;
},
attack: function(unitId) {
Engine.PostCommand({"type": "attack", "entities": [this.id()], "target": unitId, "queued": false});
return this;
},
gather: function(target, queued) {
queued = queued || false;
Engine.PostCommand({"type": "gather", "entities": [this.id()], "target": target.id(), "queued": queued});
return this;
},
repair: function(target, queued) {
queued = queued || false;
Engine.PostCommand({"type": "repair", "entities": [this.id()], "target": target.id(), "autocontinue": false, "queued": queued});
return this;
},
destroy: function() {
Engine.PostCommand({"type": "delete-entities", "entities": [this.id()]});
return this;
},
train: function(type, count, metadata)
{
var trainable = this.trainableEntities();
if (!trainable)
{
error("Called train("+type+", "+count+") on non-training entity "+this);
return this;
}
if (trainable.indexOf(type) === -1)
{
error("Called train("+type+", "+count+") on entity "+this+" which can't train that");
return this;
}
Engine.PostCommand({
"type": "train",
"entity": this.id(),
"template": type,
"count": count,
"metadata": metadata
});
return this;
},
construct: function(template, x, z, angle) {
// TODO: verify this unit can construct this, just for internal
// sanity-checking and error reporting
Engine.PostCommand({
"type": "construct",
"entities": [this.id()],
"template": template,
"x": x,
"z": z,
"angle": angle,
"autorepair": false,
"autocontinue": false,
"queued": false
});
return this;
},
});

View File

@ -0,0 +1,196 @@
function EntityCollection(baseAI, entities, filters)
{
this._ai = baseAI;
this._entities = entities;
this._filters = filters || [];
// Compute length lazily on demand, since it can be
// expensive for large collections
this._length = undefined;
Object.defineProperty(this, "length", {
get: function () {
if (this._length === undefined)
{
this._length = 0;
for (var id in entities)
++this._length;
}
return this._length;
}
});
}
EntityCollection.prototype.toIdArray = function()
{
var ret = [];
for (var id in this._entities)
ret.push(+id);
return ret;
};
EntityCollection.prototype.toEntityArray = function()
{
var ret = [];
for each (var ent in this._entities)
ret.push(ent);
return ret;
};
EntityCollection.prototype.toString = function()
{
return "[EntityCollection " + this.toEntityArray().join(" ") + "]";
};
/**
* Returns the (at most) n entities nearest to targetPos.
*/
EntityCollection.prototype.filterNearest = function(targetPos, n)
{
// Compute the distance of each entity
var data = []; // [ [id, ent, distance], ... ]
for (var id in this._entities)
{
var ent = this._entities[id];
if (ent.position())
data.push([id, ent, VectorDistance(targetPos, ent.position())]);
}
// Sort by increasing distance
data.sort(function (a, b) { return (a[2] - b[2]); });
// Extract the first n
var ret = {};
for each (var val in data.slice(0, n))
ret[val[0]] = val[1];
return new EntityCollection(this._ai, ret);
};
EntityCollection.prototype.filter = function(filter, thisp)
{
if (typeof(filter) == "function")
filter = {"func": filter, "dynamicProperties": []};
var ret = {};
for (var id in this._entities)
{
var ent = this._entities[id];
if (filter.func.call(thisp, ent, id, this))
ret[id] = ent;
}
return new EntityCollection(this._ai, ret, this._filters.concat([filter]));
};
EntityCollection.prototype.filter_raw = function(callback, thisp)
{
var ret = {};
for (var id in this._entities)
{
var ent = this._entities[id];
var val = this._entities[id]._entity;
if (callback.call(thisp, val, id, this))
ret[id] = ent;
}
return new EntityCollection(this._ai, ret);
};
EntityCollection.prototype.forEach = function(callback, thisp)
{
for (var id in this._entities)
{
var ent = this._entities[id];
callback.call(thisp, ent, id, this);
}
return this;
};
EntityCollection.prototype.move = function(x, z, queued)
{
queued = queued || false;
Engine.PostCommand({"type": "walk", "entities": this.toIdArray(), "x": x, "z": z, "queued": queued});
return this;
};
EntityCollection.prototype.destroy = function()
{
Engine.PostCommand({"type": "delete-entities", "entities": this.toIdArray()});
return this;
};
// Removes an entity from the collection, returns true if the entity was a member, false otherwise
EntityCollection.prototype.removeEnt = function(ent)
{
if (this._entities[ent.id()])
{
delete this._entities[ent.id()];
this._length--;
return true;
}
else
{
return false;
}
};
// Adds an entity to the collection, returns true if the entity was not member, false otherwise
EntityCollection.prototype.addEnt = function(ent)
{
if (this._entities[ent.id()])
{
return false;
}
else
{
this._entities[ent.id()] = ent;
this._length++;
return true;
}
};
// Checks the entity against the filters, and adds or removes it appropriately, returns true if the
// entity collection was modified.
EntityCollection.prototype.updateEnt = function(ent)
{
var passesFilters = true;
for each (var filter in this._filters)
{
passesFilters = passesFilters && filter.func(ent);
}
if (passesFilters)
{
return this.addEnt(ent);
}
else
{
return this.removeEnt(ent);
}
};
EntityCollection.prototype.registerUpdates = function()
{
this._ai.registerUpdatingEntityCollection(this);
};
EntityCollection.prototype.dynamicProperties = function()
{
var ret = [];
for each (var filter in this._filters)
{
ret = ret.concat(filter.dynamicProperties);
}
return ret;
};
EntityCollection.prototype.setUID = function(id)
{
this._UID = id;
};
EntityCollection.prototype.getUID = function()
{
return this._UID;
};

View File

@ -0,0 +1,172 @@
var Filters = {
byType: function(type){
return {"func" : function(ent){
return ent.templateName() === type;
},
"dynamicProperties": []};
},
byClass: function(cls){
return {"func" : function(ent){
return ent.hasClass(cls);
},
"dynamicProperties": []};
},
byClassesAnd: function(clsList){
return {"func" : function(ent){
var ret = true;
for (var i in clsList){
ret = ret && ent.hasClass(clsList[i]);
}
return ret;
},
"dynamicProperties": []};
},
byClassesOr: function(clsList){
return {"func" : function(ent){
var ret = false;
for (var i in clsList){
ret = ret || ent.hasClass(clsList[i]);
}
return ret;
},
"dynamicProperties": []};
},
byMetadata: function(key, value){
return {"func" : function(ent){
return (ent.getMetadata(key) == value);
},
"dynamicProperties": ['metadata.' + key]};
},
and: function(filter1, filter2){
return {"func": function(ent){
return filter1.func(ent) && filter2.func(ent);
},
"dynamicProperties": filter1.dynamicProperties.concat(filter2.dynamicProperties)};
},
or: function(filter1, filter2){
return {"func" : function(ent){
return filter1.func(ent) || filter2.func(ent);
},
"dynamicProperties": filter1.dynamicProperties.concat(filter2.dynamicProperties)};
},
byOwner: function(owner){
return {"func" : function(ent){
return (ent.owner() === owner);
},
"dynamicProperties": ['owner']};
},
byOwners: function(owners){
return {"func" : function(ent){
return (owners.indexOf(ent.owner()) !== -1);
},
"dynamicProperties": ['owner']};
},
byTrainingQueue: function(){
return {"func" : function(ent){
return ent.trainingQueue();
},
"dynamicProperties": ['trainingQueue']};
},
isSoldier: function(){
return {"func" : function(ent){
return Filters.byClassesOr(["CitizenSoldier", "Super"])(ent);
},
"dynamicProperties": []};
},
isIdle: function(){
return {"func" : function(ent){
return ent.isIdle();
},
"dynamicProperties": ['idle']};
},
isFoundation: function(){
return {"func": function(ent){
return ent.foundationProgress() !== undefined;
},
"dynamicProperties": []};
},
byDistance: function(startPoint, dist){
return {"func": function(ent){
if (!ent.position()){
return false;
}else{
return (VectorDistance(startPoint, ent.position()) < dist);
}
},
"dynamicProperties": ['position']};
},
// Distance filter with no auto updating, use with care
byStaticDistance: function(startPoint, dist){
return {"func": function(ent){
if (!ent.position()){
return false;
}else{
return (VectorDistance(startPoint, ent.position()) < dist);
}
},
"dynamicProperties": []};
},
isDropsite: function(resourceType){
return {"func": function(ent){
return (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resourceType) !== -1);
},
"dynamicProperties": []};
},
byResource: function(resourceType){
return {"func" : function(ent){
var type = ent.resourceSupplyType();
if (!type)
return false;
var amount = ent.resourceSupplyAmount();
if (!amount)
return false;
// Skip targets that are too hard to hunt
if (ent.isUnhuntable())
return false;
// And don't go for the bloody fish! TODO: better accessibility checks
if (ent.hasClass("SeaCreature")){
return false;
}
// Don't go for floating treasures since we won't be able to reach them and it kills the pathfinder.
if (ent.templateName() == "other/special_treasure_shipwreck_debris" ||
ent.templateName() == "other/special_treasure_shipwreck" ){
return false;
}
// Don't gather enemy farms
if (!ent.isOwn() && ent.owner() !== 0){
return false;
}
if (ent.getMetadata("inaccessible") === true){
return false;
}
if (type.generic == "treasure"){
return (resourceType == type.specific);
} else {
return (resourceType == type.generic);
}
},
"dynamicProperties": ["resourceSupplyAmount", "owner"]};
}
};

View File

@ -0,0 +1,51 @@
function VectorDistance(a, b)
{
var dx = a[0] - b[0];
var dz = a[1] - b[1];
return Math.sqrt(dx*dx + dz*dz);
}
function SquareVectorDistance(a, b)//A sqrtless vector calculator, to see if that improves speed at all.
{
var dx = a[0] - b[0];
var dz = a[1] - b[1];
return (dx*dx + dz*dz);
}
function MemoizeInit(obj)
{
obj._memoizeCache = {};
}
function Memoize(funcname, func)
{
return function() {
var args = funcname + '|' + Array.prototype.join.call(arguments, '|');
if (args in this._memoizeCache)
return this._memoizeCache[args];
var ret = func.apply(this, arguments);
this._memoizeCache[args] = ret;
return ret;
};
}
function ShallowClone(obj)
{
var ret = {};
for (var k in obj)
ret[k] = obj[k];
return ret;
}
// Picks a random element from an array
function PickRandom(list){
if (list.length === 0)
{
return undefined;
}
else
{
return list[Math.floor(Math.random()*list.length)];
}
}

View File

@ -1 +1 @@
Engine.IncludeModule("common-api");
Engine.IncludeModule("common-api-v2");

View File

@ -109,6 +109,14 @@ AttackMoveToLocation.prototype.execute = function(gameState, militaryManager){
// This removes idle units from the attack
AttackMoveToLocation.prototype.update = function(gameState, militaryManager, events){
if (!this.targetPos){
for (var idKey in this.idList){
var id = this.idList[idKey];
militaryManager.unassignUnit(id);
}
this.idList = [];
}
// keep the list of units in good order by pruning ids with no corresponding entities (i.e. dead units)
var removeList = [];
var totalHealth = 0;

View File

@ -36,52 +36,6 @@ var baseConfig = {
}
},
"units" : {
"citizenSoldier" : {
"default" : [ "units/{civ}_infantry_spearman_b", "units/{civ}_infantry_slinger_b",
"units/{civ}_infantry_swordsman_b", "units/{civ}_infantry_javelinist_b",
"units/{civ}_infantry_archer_b" ],
"hele" : [ "units/hele_infantry_spearman_b", "units/hele_infantry_javelinist_b",
"units/hele_infantry_archer_b" ],
"cart" : [ "units/cart_infantry_spearman_b", "units/cart_infantry_archer_b" ],
"celt" : [ "units/celt_infantry_spearman_b", "units/celt_infantry_javelinist_b" ],
"iber" : [ "units/iber_infantry_spearman_b", "units/iber_infantry_slinger_b",
"units/iber_infantry_swordsman_b", "units/iber_infantry_javelinist_b" ],
"pers" : [ "units/pers_infantry_spearman_b", "units/pers_infantry_archer_b",
"units/pers_infantry_javelinist_b" ],
"rome" : [ "units/rome_infantry_swordsman_b", "units/rome_infantry_spearman_a",
"units/rome_infantry_javelinist_b" ]
},
"advanced" : {
"default" : [ "units/{civ}_cavalry_spearman_b", "units/{civ}_cavalry_javelinist_b",
"units/{civ}_champion_cavalry", "units/{civ}_champion_infantry" ],
"hele" : [ "units/hele_cavalry_swordsman_b", "units/hele_cavalry_javelinist_b",
"units/hele_champion_cavalry_mace", "units/hele_champion_infantry_mace",
"units/hele_champion_infantry_polis", "units/hele_champion_ranged_polis",
"units/thebes_sacred_band_hoplitai", "units/thespian_melanochitones",
"units/sparta_hellenistic_phalangitai", "units/thrace_black_cloak" ],
"cart" : [ "units/cart_cavalry_javelinist_b", "units/cart_champion_cavalry",
"units/cart_infantry_swordsman_2_b", "units/cart_cavalry_spearman_b",
"units/cart_infantry_javelinist_b", "units/cart_infantry_slinger_b",
"units/cart_cavalry_swordsman_b", "units/cart_infantry_swordsman_b",
"units/cart_cavalry_swordsman_2_b", "units/cart_sacred_band_cavalry" ],
"celt" : [ "units/celt_cavalry_javelinist_b", "units/celt_cavalry_swordsman_b", "celt_cavalry_spearman_b",
"units/celt_champion_cavalry_gaul", "units/celt_champion_infantry_gaul",
"units/celt_champion_cavalry_brit", "units/celt_champion_infantry_brit", "units/celt_fanatic" ],
"iber" : [ "units/iber_cavalry_spearman_b", "units/iber_champion_cavalry", "units/iber_champion_infantry" ],
"pers" : [ "units/pers_cavalry_javelinist_b", "units/pers_champion_infantry",
"units/pers_champion_cavalry", "units/pers_cavalry_spearman_b", "units/pers_cavalry_swordsman_b",
"units/pers_cavalry_javelinist_b", "units/pers_cavalry_archer_b", "units/pers_kardakes_hoplite",
"units/pers_kardakes_skirmisher", "units/pers_war_elephant" ],
"rome" : [ "units/rome_cavalry_spearman_b", "units/rome_champion_infantry", "units/rome_champion_cavalry" ]
},
"siege" : {
"default" : [ "units/{civ}_mechanical_siege_oxybeles", "units/{civ}_mechanical_siege_lithobolos",
"units/{civ}_mechanical_siege_ballista", "units/{civ}_mechanical_siege_ram",
"units/{civ}_mechanical_siege_scorpio" ]
}
},
// qbot
"priorities" : { // Note these are dynamic, you are only setting the initial values
"house" : 500,

View File

@ -16,7 +16,7 @@ EconomyManager.prototype.init = function(gameState){
EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) {
// Count the workers in the world and in progress
var numWorkers = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("units/{civ}_support_female_citizen"));
var numWorkers = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("units/{civ}_support_female_citizen"));
numWorkers += queues.villager.countTotalQueuedUnits();
// If we have too few, train more
@ -35,16 +35,16 @@ EconomyManager.prototype.pickMostNeededResources = function(gameState) {
var self = this;
// Find what resource type we're most in need of
this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState);
if (!gameState.turnCache["gather-weights-calculated"]){
this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState);
gameState.turnCache["gather-weights-calculated"] = true;
}
var numGatherers = {};
for ( var type in this.gatherWeights)
numGatherers[type] = 0;
gameState.getOwnEntitiesWithRole("worker").forEach(function(ent) {
if (ent.getMetadata("subrole") === "gatherer")
numGatherers[ent.getMetadata("gather-type")] += 1;
});
for ( var type in this.gatherWeights){
numGatherers[type] = gameState.updatingCollection("workers-gathering-" + type,
Filters.byMetadata("gather-type", type), gameState.getOwnEntitiesByRole("worker")).length;
}
var types = Object.keys(this.gatherWeights);
types.sort(function(a, b) {
@ -59,7 +59,7 @@ EconomyManager.prototype.pickMostNeededResources = function(gameState) {
EconomyManager.prototype.reassignRolelessUnits = function(gameState) {
//TODO: Move this out of the economic section
var roleless = gameState.getOwnEntitiesWithRole(undefined);
var roleless = gameState.getOwnEntitiesByRole(undefined);
roleless.forEach(function(ent) {
if (ent.hasClass("Worker")){
@ -85,7 +85,7 @@ EconomyManager.prototype.setWorkersIdleByPriority = function(gameState){
totalWeight += this.gatherWeights[type];
}
gameState.getOwnEntitiesWithRole("worker").forEach(function(ent) {
gameState.getOwnEntitiesByRole("worker").forEach(function(ent) {
if (ent.getMetadata("subrole") === "gatherer"){
numGatherers[ent.getMetadata("gather-type")] += 1;
totalGatherers += 1;
@ -96,7 +96,7 @@ EconomyManager.prototype.setWorkersIdleByPriority = function(gameState){
var allocation = Math.floor(totalGatherers * (this.gatherWeights[type]/totalWeight));
if (allocation < numGatherers[type]){
var numToTake = numGatherers[type] - allocation;
gameState.getOwnEntitiesWithRole("worker").forEach(function(ent) {
gameState.getOwnEntitiesByRole("worker").forEach(function(ent) {
if (ent.getMetadata("subrole") === "gatherer" && ent.getMetadata("gather-type") === type && numToTake > 0){
ent.setMetadata("subrole", "idle");
numToTake -= 1;
@ -111,176 +111,47 @@ EconomyManager.prototype.reassignIdleWorkers = function(gameState) {
var self = this;
// Search for idle workers, and tell them to gather resources based on demand
var idleWorkers = gameState.getOwnEntitiesWithRole("worker").filter(function(ent) {
return (ent.isIdle() || ent.getMetadata("subrole") === "idle");
});
var filter = Filters.or(Filters.isIdle(), Filters.byMetadata("subrole", "idle"));
var idleWorkers = gameState.updatingCollection("idle-workers", filter, gameState.getOwnEntitiesByRole("worker"));
if (idleWorkers.length) {
var resourceSupplies;
var territoryMap = Map.createTerritoryMap(gameState);
//var territoryMap = Map.createTerritoryMap(gameState);
idleWorkers.forEach(function(ent) {
// Check that the worker isn't garrisoned
if (ent.position() === undefined){
return;
}
var types = self.pickMostNeededResources(gameState);
//debug("Most Needed Resources: " + uneval(types));
for ( var typeKey in types) {
var type = types[typeKey];
// TODO: we should care about gather rates of workers
// Find the nearest dropsite for this resource from the worker
var nearestDropsite = undefined;
var nearbyResources = undefined;
var minDropsiteDist = Math.min(); // set to infinity initially
gameState.getOwnEntities().forEach(function(dropsiteEnt) {
if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.resourceDropsiteTypes().indexOf(type) !== -1){
var nearby = dropsiteEnt.getMetadata("nearbyResources_" + type);
if (dropsiteEnt.position() && nearby && nearby.length > 0){
var dist = VectorDistance(ent.position(), dropsiteEnt.position());
if (dist < minDropsiteDist){
nearestDropsite = dropsiteEnt;
minDropsiteDist = dist;
nearbyResources = nearby;
}
}
}
});
if (!nearbyResources){
resourceSupplies = resourceSupplies || gameState.findResourceSupplies();
nearbyResources = resourceSupplies[type];
}
// Make sure there are actually some resources of that type
if (!nearbyResources){
debug("No " + type + " found! (1)");
continue;
}
var numSupplies = nearbyResources.length;
var workerPosition = ent.position();
var supplies = [];
var count = 0;
while (supplies.length == 0 && count <= 1){
if (count != 0){
resourceSupplies = resourceSupplies || gameState.findResourceSupplies();
nearbyResources = resourceSupplies[type];
if (!nearbyResources){
debug("No " + type + " found! (2)");
continue;
}
}
count += 1;
nearbyResources.forEach(function(supply) {
if (! supply.entity){
supply = {
"entity" : supply,
"amount" : supply.resourceSupplyAmount(),
"type" : supply.resourceSupplyType(),
"position" : supply.position()
};
}
// Skip targets that are too hard to hunt
if (supply.entity.isUnhuntable()){
return;
}
// And don't go for the bloody fish! TODO: better accessibility checks
if (supply.entity.hasClass("SeaCreature")){
return;
}
// Don't go for floating treasures since we won't be able to reach them and it kills the pathfinder.
if (supply.entity.templateName() == "other/special_treasure_shipwreck_debris" ||
supply.entity.templateName() == "other/special_treasure_shipwreck" ){
return;
}
// Check we can actually reach the resource
if (!gameState.ai.accessibility.isAccessible(supply.position)){
return;
}
// Don't gather in enemy territory
var territory = territoryMap.point(supply.position);
if (territory != 0 && gameState.isPlayerEnemy(territory)){
return;
}
// measure the distance to the resource
var dist = VectorDistance(supply.position, workerPosition);
// Add on a factor for the nearest dropsite if one exists
if (nearestDropsite){
dist += 5 * VectorDistance(supply.position, nearestDropsite.position());
}
// Go for treasure as a priority
if (dist < 1200 && supply.type.generic == "treasure"){
dist /= 1000;
}
// Skip targets that are far too far away (e.g. in the
// enemy base), only do this for common supplies
if (dist > 6072 && numSupplies > 100){
return;
}
supplies.push({
dist : dist,
entity : supply.entity
});
});
}
supplies.sort(function(a, b) {
// Prefer smaller distances
if (a.dist != b.dist)
return a.dist - b.dist;
return 0;
});
// Start gathering the best resource (by distance from the dropsite and unit)
if (supplies.length) {
ent.gather(supplies[0].entity);
ent.setMetadata("subrole", "gatherer");
ent.setMetadata("gather-type", type);
return;
}else{
debug("No " + type + " found! (3)");
}
}
// Couldn't find any types to gather
ent.setMetadata("subrole", "idle");
ent.setMetadata("subrole", "gatherer");
ent.setMetadata("gather-type", types[0]);
});
}
};
EconomyManager.prototype.workersBySubrole = function(gameState, subrole) {
var workers = gameState.getOwnEntitiesByRole("worker");
return gameState.updatingCollection("subrole-" + subrole, Filters.byMetadata("subrole", subrole), workers);
};
EconomyManager.prototype.assignToFoundations = function(gameState) {
// If we have some foundations, and we don't have enough
// builder-workers,
// try reassigning some other workers who are nearby
var foundations = gameState.findFoundations();
var foundations = gameState.getOwnFoundations();
// Check if nothing to build
if (!foundations.length){
return;
}
var workers = gameState.getOwnEntitiesWithRole("worker");
var workers = gameState.getOwnEntitiesByRole("worker");
var builderWorkers = workers.filter(function(ent) {
return (ent.getMetadata("subrole") === "builder");
});
var builderWorkers = this.workersBySubrole(gameState, "builder");
// Check if enough builders
var extraNeeded = this.targetNumBuilders - builderWorkers.length;
@ -302,8 +173,8 @@ EconomyManager.prototype.assignToFoundations = function(gameState) {
// Order each builder individually, not as a formation
nearestNonBuilders.forEach(function(ent) {
ent.repair(target);
ent.setMetadata("subrole", "builder");
ent.setMetadata("target-foundation", target);
});
};
@ -311,7 +182,7 @@ EconomyManager.prototype.buildMoreFields = function(gameState, queues) {
// give time for treasures to be gathered
if (gameState.getTimeElapsed() < 30 * 1000)
return;
var numFields = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("structures/{civ}_field"));
var numFields = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_field"));
numFields += queues.field.totalLength();
for ( var i = numFields; i < this.targetNumFields; i++) {
@ -321,7 +192,7 @@ EconomyManager.prototype.buildMoreFields = function(gameState, queues) {
// If all the CC's are destroyed then build a new one
EconomyManager.prototype.buildNewCC= function(gameState, queues) {
var numCCs = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("structures/{civ}_civil_centre"));
var numCCs = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_civil_centre"));
numCCs += queues.civilCentre.totalLength();
for ( var i = numCCs; i < 1; i++) {
@ -336,29 +207,32 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events){
// This is the maximum radius of the influence
var radius = {'wood':13, 'stone': 10, 'metal': 10, 'food': 10};
var self = this;
for (var resource in radius){
// if there is no resourceMap create one with an influence for everything with that resource
if (! this.resourceMaps[resource]){
this.resourceMaps[resource] = new Map(gameState);
var supplies = gameState.findResourceSupplies();
if (supplies[resource]){
for (var i in supplies[resource]){
var current = supplies[resource][i];
var x = Math.round(current.position[0] / gameState.cellSize);
var z = Math.round(current.position[1] / gameState.cellSize);
var strength = Math.round(current.entity.resourceSupplyMax()/decreaseFactor[resource]);
this.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
var supplies = gameState.getResourceSupplies(resource);
supplies.forEach(function(ent){
if (!ent.position()){
return;
}
}
var x = Math.round(ent.position()[0] / gameState.cellSize);
var z = Math.round(ent.position()[1] / gameState.cellSize);
var strength = Math.round(ent.resourceSupplyMax()/decreaseFactor[resource]);
self.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
});
}
// TODO: fix for treasure and move out of loop
// Look for destroy events and subtract the entities original influence from the resourceMap
for (var i in events) {
var e = events[i];
if (e.type === "Destroy") {
if (e.msg.rawEntity.template){
var ent = new Entity(gameState.ai, e.msg.rawEntity);
if (e.msg.entityObj){
var ent = e.msg.entityObj;
if (ent && ent.resourceSupplyType() && ent.resourceSupplyType().generic === resource){
var x = Math.round(ent.position()[0] / gameState.cellSize);
var z = Math.round(ent.position()[1] / gameState.cellSize);
@ -370,7 +244,7 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events){
}
}
//this.resourceMaps[resource].dumpIm("tree_density.png");
//this.resourceMaps['wood'].dumpIm("tree_density.png");
};
// Returns the position of the best place to build a new dropsite for the specified resource
@ -445,20 +319,20 @@ EconomyManager.prototype.updateNearbyResources = function(gameState){
var radius = 64;
for (key in resources){
var resource = resources[key];
gameState.getOwnEntities().forEach(function(ent) {
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1
&& ent.getMetadata("nearbyResources_" + resource) === undefined){
if (!ent.position()){
return;
}
gameState.getOwnDropsites(resource).forEach(function(ent) {
if (ent.getMetadata("nearby-resources-" + resource) === undefined){
var filterPos = Filters.byStaticDistance(ent.position(), radius);
var filterRes = Filters.byResource(resource);
var filterPos = Filters.byDistance(ent.position(), radius);
var filter = Filters.and(filterRes, filterPos);
var collection = gameState.getResourceSupplies(resource).filter(filterPos);
collection.registerUpdates();
var collection = new EntityCollection(gameState.ai, gameState.entities._entities, filter, gameState);
ent.setMetadata("nearbyResources_" + resource, collection);
ent.setMetadata("nearby-resources-" + resource, collection);
ent.setMetadata("active-dropsite-" + resource, true);
}
if (ent.getMetadata("nearby-resources-" + resource).length === 0){
ent.setMetadata("active-dropsite-" + resource, false);
}
});
}
@ -527,13 +401,13 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
Engine.ProfileStop();
//Later in the game we want to build stuff faster.
if (gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.5) {
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.5) {
this.targetNumBuilders = 10;
}else{
this.targetNumBuilders = 5;
}
if (gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.8) {
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.8) {
this.dropsiteNumbers = {wood: 3, stone: 2, metal: 2};
}else{
this.dropsiteNumbers = {wood: 2, stone: 1, metal: 1};
@ -545,8 +419,9 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
this.updateNearbyResources(gameState);
Engine.ProfileStop();
Engine.ProfileStart("Build new Dropsites");
this.buildDropsites(gameState, queues);
Engine.ProfileStop();
// TODO: implement a timer based system for this
this.setCount += 1;
@ -562,6 +437,13 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
Engine.ProfileStart("Assign builders");
this.assignToFoundations(gameState);
Engine.ProfileStop();
Engine.ProfileStart("Run Workers");
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){
var worker = new Worker(ent);
worker.update(gameState);
});
Engine.ProfileStop();
Engine.ProfileStop();
};

View File

@ -22,11 +22,6 @@ function EntityCollectionFromIds(gameState, idList){
return new EntityCollection(gameState.ai, ents);
}
EntityCollection.prototype.attackMove = function(x, z){
Engine.PostCommand({"type": "attack-move", "entities": this.toIdArray(), "x": x, "z": z, "queued": false});
return this;
};
// Do naughty stuff to replace the entity collection constructor for updating entity collections
var tmpEntityCollection = function(baseAI, entities, filter, gameState){
this._ai = baseAI;
@ -56,11 +51,11 @@ var tmpEntityCollection = function(baseAI, entities, filter, gameState){
});
};
tmpEntityCollection.prototype = new EntityCollection;
EntityCollection = tmpEntityCollection;
//tmpEntityCollection.prototype = new EntityCollection;
//EntityCollection = tmpEntityCollection;
// Keeps an EntityCollection with a filter function up to date by watching for events
EntityCollection.prototype.update = function(gameState, events){
tmpEntityCollection.prototype.update = function(gameState, events){
if (!this.filterFunc)
return;
for (var i in events){

View File

@ -1,83 +0,0 @@
var Filters = {
byClass: function(cls){
return function(ent){
return ent.hasClass(cls);
};
},
byClassesAnd: function(clsList){
return function(ent){
var ret = true;
for (var i in clsList){
ret = ret && ent.hasClass(clsList[i]);
}
return ret;
};
},
byClassesOr: function(clsList){
return function(ent){
var ret = false;
for (var i in clsList){
ret = ret || ent.hasClass(clsList[i]);
}
return ret;
};
},
and: function(filter1, filter2){
return function(ent, gameState){
return filter1(ent, gameState) && filter2(ent, gameState);
};
},
or: function(filter1, filter2){
return function(ent, gameState){
return filter1(ent, gameState) || filter2(ent, gameState);
};
},
isEnemy: function(){
return function(ent, gameState){
return gameState.isEntityEnemy(ent);
};
},
isSoldier: function(){
return function(ent){
return Filters.byClassesOr(["CitizenSoldier", "Super"])(ent);
};
},
isIdle: function(){
return function(ent){
return ent.isIdle();
};
},
byDistance: function(startPoint, dist){
return function(ent){
if (!ent.position()){
return false;
}else{
return (VectorDistance(startPoint, ent.position()) < dist);
}
};
},
byResource: function(resourceType){
return function(ent){
var type = ent.resourceSupplyType();
if (!type)
return false;
var amount = ent.resourceSupplyAmount();
if (!amount)
return false;
if (type.generic == "treasure")
return (resourceType == type.specific);
else
return (resourceType == type.generic);
};
}
};

View File

@ -13,7 +13,23 @@ var GameState = function(ai) {
this.playerData = ai.playerData;
this.buildingsBuilt = 0;
if (!this.ai._gameStateStore){
this.ai._gameStateStore = {};
}
this.store = this.ai._gameStateStore;
this.cellSize = 4; // Size of each map tile
this.turnCache = {};
};
GameState.prototype.updatingCollection = function(id, filter, collection){
if (!this.store[id]){
this.store[id] = collection.filter(filter);
this.store[id].registerUpdates();
}
return this.store[id];
};
GameState.prototype.getTimeElapsed = function() {
@ -21,8 +37,10 @@ GameState.prototype.getTimeElapsed = function() {
};
GameState.prototype.getTemplate = function(type) {
if (!this.templates[type])
if (!this.templates[type]){
return null;
}
return new EntityTemplate(this.templates[type]);
};
@ -58,8 +76,9 @@ GameState.prototype.getPopulationMax = function() {
};
GameState.prototype.getPassabilityClassMask = function(name) {
if (!(name in this.ai.passabilityClasses))
if (!(name in this.ai.passabilityClasses)){
error("Tried to use invalid passability class name '" + name + "'");
}
return this.ai.passabilityClasses[name];
};
@ -75,6 +94,16 @@ GameState.prototype.isPlayerEnemy = function(id) {
return this.playerData.isEnemy[id];
};
GameState.prototype.getEnemies = function(){
var ret = [];
for (i in this.playerData.isEnemy){
if (this.playerData.isEnemy[i]){
ret.push(i);
}
}
return ret;
};
GameState.prototype.isEntityAlly = function(ent) {
if (ent && ent.owner && (typeof ent.owner) === "function"){
return this.playerData.isAlly[ent.owner()];
@ -103,7 +132,36 @@ GameState.prototype.isEntityOwn = function(ent) {
};
GameState.prototype.getOwnEntities = function() {
return new EntityCollection(this.ai, this.ai._ownEntities);
if (!this.store.ownEntities){
this.store.ownEntities = this.getEntities().filter(Filters.byOwner(this.player));
this.store.ownEntities.registerUpdates();
}
return this.store.ownEntities;
};
GameState.prototype.getEnemyEntities = function() {
var diplomacyChange = false;
var enemies = this.getEnemies();
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 (diplomacyChange || !this.store.enemyEntities){
var filter = Filters.byOwners(enemies);
this.store.enemyEntities = this.getEntities().filter(filter);
this.store.enemyEntities.registerUpdates();
this.store.enemies = enemies;
}
return this.store.enemyEntities;
};
GameState.prototype.getEntities = function() {
@ -119,51 +177,49 @@ GameState.prototype.getEntityById = function(id){
return undefined;
};
GameState.prototype.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);
});
});
GameState.prototype.countEntitiesWithType = function(type) {
var count = 0;
this.getOwnEntities().forEach(function(ent) {
var t = ent.templateName();
if (t == type)
++count;
});
return count;
GameState.prototype.getOwnEntitiesByMetadata = function(key, value){
if (!this.store[key + "-" + value]){
var filter = Filters.byMetadata(key, value);
this.store[key + "-" + value] = this.getOwnEntities().filter(filter);
this.store[key + "-" + value].registerUpdates();
}
return this.store[key + "-" + value];
};
GameState.prototype.countEntitiesAndQueuedWithType = function(type) {
var foundationType = "foundation|" + type;
var count = 0;
this.getOwnEntities().forEach(function(ent) {
GameState.prototype.getOwnEntitiesByRole = function(role){
return this.getOwnEntitiesByMetadata("role", role);
};
var t = ent.templateName();
if (t == type || t == foundationType)
++count;
// TODO: fix this so it picks up not in use training stuff
GameState.prototype.getOwnTrainingFacilities = function(){
return this.updatingCollection("own-training-facilities", Filters.byTrainingQueue(), this.getOwnEntities());
};
var queue = ent.trainingQueue();
if (queue) {
queue.forEach(function(item) {
if (item.template == type)
count += item.count;
});
}
GameState.prototype.getOwnEntitiesByType = function(type){
var filter = Filters.byType(type);
return this.updatingCollection("own-by-type-" + type, filter, this.getOwnEntities());
};
GameState.prototype.countEntitiesByType = function(type) {
return this.getOwnEntitiesByType(type).length;
};
GameState.prototype.countEntitiesAndQueuedByType = function(type) {
var count = this.countEntitiesByType(type);
// Count building foundations
count += this.countEntitiesByType("foundation|" + type);
// Count entities in building production queues
this.getOwnTrainingFacilities().forEach(function(ent){
ent.trainingQueue().forEach(function(item) {
if (item.template == type){
count += item.count;
}
});
});
return count;
};
@ -178,20 +234,19 @@ GameState.prototype.countFoundationsWithType = function(type) {
return count;
};
GameState.prototype.countEntitiesAndQueuedWithRole = function(role) {
var count = 0;
this.getOwnEntities().forEach(function(ent) {
GameState.prototype.countOwnEntitiesByRole = function(role) {
return this.getOwnEntitiesByRole(role).length;
};
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;
});
}
GameState.prototype.countOwnEntitiesAndQueuedWithRole = function(role) {
var count = this.countOwnEntitiesByRole(role);
// Count entities in building production queues
this.getOwnTrainingFacilities().forEach(function(ent) {
ent.trainingQueue().forEach(function(item) {
if (item.metadata && item.metadata.role == role)
count += item.count;
});
});
return count;
};
@ -201,10 +256,9 @@ GameState.prototype.countEntitiesAndQueuedWithRole = function(role) {
* already too busy.
*/
GameState.prototype.findTrainers = function(template) {
var maxQueueLength = 2; // avoid tying up resources in giant training
// queues
return this.getOwnEntities().filter(function(ent) {
var maxQueueLength = 2; // avoid tying up resources in giant training queues
return this.getOwnTrainingFacilities().filter(function(ent) {
var trainable = ent.trainableEntities();
if (!trainable || trainable.indexOf(template) == -1)
@ -234,41 +288,17 @@ GameState.prototype.findBuilders = function(template) {
});
};
GameState.prototype.findFoundations = function(template) {
return this.getOwnEntities().filter(function(ent) {
return (ent.foundationProgress() !== undefined);
});
GameState.prototype.getOwnFoundations = function() {
return this.updatingCollection("ownFoundations", Filters.isFoundation(), this.getOwnEntities());
};
GameState.prototype.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;
GameState.prototype.getOwnDropsites = function(resource){
return this.updatingCollection("dropsite-own-" + resource, Filters.isDropsite(resource), this.getOwnEntities());
};
GameState.prototype.getResourceSupplies = function(resource){
return this.updatingCollection("resource-" + resource, Filters.byResource(resource), this.getEntities());
};
GameState.prototype.getBuildLimits = function() {
return this.playerData.buildLimits;
@ -278,6 +308,7 @@ GameState.prototype.getBuildCounts = function() {
return this.playerData.buildCounts;
};
// Checks whether the maximum number of buildings have been cnstructed for a certain catergory
GameState.prototype.isBuildLimitReached = function(category) {
if(this.playerData.buildLimits[category] === undefined || this.playerData.buildCounts[category] === undefined)
return false;
@ -285,4 +316,4 @@ GameState.prototype.isBuildLimitReached = function(category) {
return (this.playerData.buildCounts[category] >= this.playerData.buildCounts["CivilCentre"]*this.playerData.buildLimits[category].LimitPerCivCentre);
else
return (this.playerData.buildCounts[category] >= this.playerData.buildLimits[category]);
};
};

View File

@ -8,7 +8,7 @@ HousingManager.prototype.buildMoreHouses = function(gameState, queues) {
// predictive in future
if (gameState.getPopulationLimit() - gameState.getPopulation() < 20
&& gameState.getPopulationLimit() < gameState.getPopulationMax()) {
var numConstructing = gameState.countEntitiesWithType(gameState.applyCiv("foundation|structures/{civ}_house"));
var numConstructing = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_house"));
var numPlanned = queues.house.totalLength();
var additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 10)

View File

@ -8,12 +8,6 @@
var MilitaryAttackManager = function() {
// these use the structure soldiers[unitId] = true|false to register the units
this.soldiers = {};
this.assigned = {};
this.unassigned = {};
this.garrisoned = {};
this.enemyAttackers = {};
this.attackManagers = [AttackMoveToLocation];
this.availableAttacks = [];
this.currentAttacks = [];
@ -29,23 +23,6 @@ MilitaryAttackManager.prototype.init = function(gameState) {
var civ = gameState.playerData.civ;
// load units and buildings from the config files
if (civ in Config.units.citizenSoldier){
this.uCitizenSoldier = Config.units.citizenSoldier[civ];
}else{
this.uCitizenSoldier = Config.units.citizenSoldier['default'];
}
if (civ in Config.units.advanced){
this.uAdvanced = Config.units.advanced[civ];
}else{
this.uAdvanced = Config.units.advanced['default'];
}
if (civ in Config.units.siege){
this.uSiege = Config.units.siege[civ];
}else{
this.uSiege = Config.units.siege['default'];
}
if (civ in Config.buildings.moderate){
this.bModerate = Config.buildings.moderate[civ];
@ -65,15 +42,6 @@ MilitaryAttackManager.prototype.init = function(gameState) {
this.bFort = Config.buildings.fort['default'];
}
for (var i in this.uCitizenSoldier){
this.uCitizenSoldier[i] = gameState.applyCiv(this.uCitizenSoldier[i]);
}
for (var i in this.uAdvanced){
this.uAdvanced[i] = gameState.applyCiv(this.uAdvanced[i]);
}
for (var i in this.uSiege){
this.uSiege[i] = gameState.applyCiv(this.uSiege[i]);
}
for (var i in this.bAdvanced){
this.bAdvanced[i] = gameState.applyCiv(this.bAdvanced[i]);
}
@ -89,41 +57,62 @@ MilitaryAttackManager.prototype.init = function(gameState) {
this.availableAttacks[i] = new this.attackManagers[i](gameState, this);
}
var filter = Filters.and(Filters.isEnemy(), Filters.byClassesOr(["CitizenSoldier", "Super", "Siege"]));
this.enemySoldiers = new EntityCollection(gameState.ai, gameState.entities._entities, filter, gameState);
var enemies = gameState.getEnemyEntities();
var filter = Filters.byClassesOr(["CitizenSoldier", "Super", "Siege"]);
this.enemySoldiers = enemies.filter(filter); // TODO: cope with diplomacy changes
this.enemySoldiers.registerUpdates();
};
/**
* @param (GameState) gameState
* @param (string) soldierTypes
* @returns array of soldiers for which training buildings exist
*/
MilitaryAttackManager.prototype.findTrainableUnits = function(gameState, soldierTypes){
var ret = [];
MilitaryAttackManager.prototype.findTrainableUnits = function(gameState, soldierType){
var allTrainable = [];
gameState.getOwnEntities().forEach(function(ent) {
var trainable = ent.trainableEntities();
for (var i in trainable){
if (soldierTypes.indexOf(trainable[i]) !== -1){
if (ret.indexOf(trainable[i]) === -1){
ret.push(trainable[i]);
}
}
if (allTrainable.indexOf(trainable[i]) === -1){
allTrainable.push(trainable[i]);
}
}
return true;
});
var ret = [];
for (var i in allTrainable){
var template = gameState.getTemplate(allTrainable[i]);
if (soldierType == this.getSoldierType(template)){
ret.push(allTrainable[i]);
}
}
return ret;
};
// Returns the type of a soldier, either citizenSoldier, advanced or siege
MilitaryAttackManager.prototype.getSoldierType = function(ent){
if (ent.hasClass("CitizenSoldier") && !ent.hasClass("Cavalry")){
return "citizenSoldier";
}else if (ent.hasClass("Super") || ent.hasClass("Hero") || ent.hasClass("CitizenSoldier")){
return "advanced";
}else if (ent.hasClass("Siege")){
return "siege";
}else{
return undefined;
}
};
/**
* Returns the unit type we should begin training. (Currently this is whatever
* we have least of.)
*/
MilitaryAttackManager.prototype.findBestNewUnit = function(gameState, queue, soldierTypes) {
var units = this.findTrainableUnits(gameState, soldierTypes);
MilitaryAttackManager.prototype.findBestNewUnit = function(gameState, queue, soldierType) {
var units = this.findTrainableUnits(gameState, soldierType);
// Count each type
var types = [];
for ( var tKey in units) {
var t = units[tKey];
types.push([t, gameState.countEntitiesAndQueuedWithType(gameState.applyCiv(t))
types.push([t, gameState.countEntitiesAndQueuedByType(gameState.applyCiv(t))
+ queue.countAllByType(gameState.applyCiv(t)) ]);
}
@ -139,91 +128,15 @@ MilitaryAttackManager.prototype.findBestNewUnit = function(gameState, queue, sol
};
MilitaryAttackManager.prototype.registerSoldiers = function(gameState) {
var soldiers = gameState.getOwnEntitiesWithRole("soldier");
var soldiers = gameState.getOwnEntitiesByRole("soldier");
var self = this;
soldiers.forEach(function(ent) {
ent.setMetadata("role", "registeredSoldier");
self.soldiers[ent.id()] = true;
self.unassigned[ent.id()] = true;
ent.setMetadata("role", "military");
ent.setMetadata("military", "unassigned");
});
};
// Ungarrisons all units
MilitaryAttackManager.prototype.ungarrisonAll = function(gameState) {
debug("ungarrison units");
this.getGarrisonBuildings(gameState).forEach(function(bldg){
bldg.unloadAll();
});
for ( var i in this.garrisoned) {
if(this.assigned[i]) {
this.unassignUnit(i);
}
if (this.entity(i)){
this.entity(i).setMetadata("subrole","idle");
}
}
this.garrisoned = {};
};
//Garrisons citizens
MilitaryAttackManager.prototype.garrisonCitizens = function(gameState) {
var self = this;
debug("garrison Citizens");
gameState.getOwnEntities().forEach(function(ent) {
var dogarrison = false;
// Look for workers which have a position (i.e. not garrisoned)
if(ent.hasClass("Worker") && ent.position()) {
for (id in self.enemyAttackers) {
if(self.entity(id).visionRange() >= VectorDistance(self.entity(id).position(),ent.position())) {
dogarrison = true;
break;
}
}
if(dogarrison) {
self.garrisonUnit(gameState,ent.id());
}
}
return true;
});
};
// garrison the soldiers
MilitaryAttackManager.prototype.garrisonSoldiers = function(gameState) {
debug("garrison Soldiers");
var units = this.getAvailableUnits(this.countAvailableUnits());
for (var i in units) {
this.garrisonUnit(gameState,units[i]);
if(!this.garrisoned[units[i]]) {
this.unassignUnit(units[i]);
}
}
};
MilitaryAttackManager.prototype.garrisonUnit = function(gameState,id) {
if (this.entity(id).position() === undefined){
return;
}
var garrisonBuildings = this.getGarrisonBuildings(gameState).toEntityArray();
var bldgDistance = [];
for (var i in garrisonBuildings) {
var bldg = garrisonBuildings[i];
if(bldg.garrisoned().length <= bldg.garrisonMax()) {
bldgDistance.push([i,VectorDistance(bldg.position(),this.entity(id).position())]);
}
}
if(bldgDistance.length > 0) {
bldgDistance.sort(function(a,b) { return (a[1]-b[1]); });
var building = garrisonBuildings[bldgDistance[0][0]];
//debug("garrison id "+id+"into building "+building.id()+"walking distance "+bldgDistance[0][1]);
this.entity(id).garrison(building);
this.garrisoned[id] = true;
this.entity(id).setMetadata("subrole","garrison");
}
};
// return count of enemy buildings for a given building class
MilitaryAttackManager.prototype.getEnemyBuildings = function(gameState,cls) {
var targets = gameState.entities.filter(function(ent) {
@ -232,96 +145,63 @@ MilitaryAttackManager.prototype.getEnemyBuildings = function(gameState,cls) {
return targets;
};
// return count of own buildings for a given building class
MilitaryAttackManager.prototype.getGarrisonBuildings = function(gameState) {
var targets = gameState.getOwnEntities().filter(function(ent) {
return (ent.hasClass("Structure") && ent.garrisonableClasses());
});
return targets;
};
// return n available units and makes these units unavailable
MilitaryAttackManager.prototype.getAvailableUnits = function(n, filter) {
var ret = [];
var count = 0;
for (var i in this.unassigned) {
if (this.unassigned[i]){
var ent = this.entity(i);
if (filter){
if (!filter(ent)){
continue;
}
}
ret.push(+i);
delete this.unassigned[i];
this.assigned[i] = true;
ent.setMetadata("role", "assigned");
ent.setMetadata("subrole", "unavailable");
count++;
if (count >= n) {
break;
this.getUnassignedUnits().forEach(function(ent){
if (filter){
if (!filter(ent)){
return;
}
}
}
ret.push(ent.id());
ent.setMetadata("military", "assigned");
ent.setMetadata("role", "military");
count++;
if (count >= n) {
return;
}
});
return ret;
};
// Takes a single unit id, and marks it unassigned
MilitaryAttackManager.prototype.unassignUnit = function(unit){
this.unassigned[unit] = true;
this.assigned[unit] = false;
this.entity(unit).setMetadata("military", "unassigned");
};
// Takes an array of unit id's and marks all of them unassigned
MilitaryAttackManager.prototype.unassignUnits = function(units){
for (var i in units){
this.unassigned[units[i]] = true;
this.assigned[units[i]] = false;
this.unassignUnit(units[i]);
}
};
MilitaryAttackManager.prototype.getUnassignedUnits = function(){
return this.gameState.getOwnEntitiesByMetadata("military", "unassigned");
};
MilitaryAttackManager.prototype.countAvailableUnits = function(filter){
var count = 0;
for (var i in this.unassigned){
if (this.unassigned[i]){
if (filter){
if (filter(this.entity(i))){
count += 1;
}
}else{
count += 1;
this.getUnassignedUnits().forEach(function(ent){
if (filter){
if (filter(ent)){
count += 1;
}
}else{
count += 1;
}
}
});
return count;
};
MilitaryAttackManager.prototype.handleEvents = function(gameState, events) {
var myCivCentres = gameState.getOwnEntities().filter(function(ent) {
return ent.hasClass("CivCentre");
}).toEntityArray();
var pos = undefined;
if (myCivCentres.length > 0 && myCivCentres[0].position()){
pos = myCivCentres[0].position();
}
for (var i in events) {
var e = events[i];
if (e.type === "Destroy") {
var id = e.msg.entity;
delete this.unassigned[id];
delete this.assigned[id];
delete this.soldiers[id];
}
}
};
// Takes an entity id and returns an entity object or false if there is no entity with that id
// Also sends a debug message warning if the id has no entity
MilitaryAttackManager.prototype.entity = function(id) {
if (this.gameState.entities._entities[id]) {
return new Entity(this.gameState.ai, this.gameState.entities._entities[id]);
return this.gameState.entities._entities[id]; // TODO: make this nicer
}else{
//debug("Entity " + id + " requested does not exist");
}
@ -388,11 +268,10 @@ MilitaryAttackManager.prototype.getUnitStrength = function(ent){
// Returns the strength of the available units of ai army
MilitaryAttackManager.prototype.measureAvailableStrength = function(){
var strength = 0.0;
for (var i in this.unassigned){
if (this.unassigned[i] && this.entity(i)){
strength += this.getUnitStrength(this.entity(i));
}
}
var self = this;
this.getUnassignedUnits(this.gameState).forEach(function(ent){
strength += self.getUnitStrength(ent);
});
return strength;
};
@ -448,7 +327,7 @@ MilitaryAttackManager.prototype.measureEnemyStrength = function(gameState){
// Adds towers to the defenceBuilding queue
MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
if (gameState.countEntitiesAndQueuedWithType(gameState.applyCiv('structures/{civ}_defense_tower'))
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower'))
+ queues.defenceBuilding.totalLength() < gameState.getBuildLimits()["DefenseTower"]) {
@ -465,11 +344,11 @@ MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
var numFortresses = 0;
for (var i in this.bFort){
numFortresses += gameState.countEntitiesAndQueuedWithType(gameState.applyCiv(this.bFort[i]));
numFortresses += gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bFort[i]));
}
if (numFortresses + queues.defenceBuilding.totalLength() < gameState.getBuildLimits()["Fortress"]) {
if (gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) > gameState.ai.modules[0].targetNumWorkers * 0.5){
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > gameState.ai.modules[0].targetNumWorkers * 0.5){
if (gameState.getTimeElapsed() > 350 * 1000 * numFortresses){
if (gameState.ai.pathsToMe && gameState.ai.pathsToMe.length > 0){
var position = gameState.ai.pathsToMe.shift();
@ -484,12 +363,10 @@ MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
};
MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
var self = this;
Engine.ProfileStart("military update");
this.gameState = gameState;
this.handleEvents(gameState, events);
// this.attackElephants(gameState);
this.registerSoldiers(gameState);
//this.defence(gameState);
@ -497,9 +374,10 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
this.defenceManager.update(gameState, events, this);
Engine.ProfileStart("Train Units");
// Continually try training new units, in batches of 5
if (queues.citizenSoldier.length() < 6) {
var newUnit = this.findBestNewUnit(gameState, queues.citizenSoldier, this.uCitizenSoldier);
var newUnit = this.findBestNewUnit(gameState, queues.citizenSoldier, "citizenSoldier");
if (newUnit){
queues.citizenSoldier.addItem(new UnitTrainingPlan(gameState, newUnit, {
"role" : "soldier"
@ -507,7 +385,7 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
}
}
if (queues.advancedSoldier.length() < 2) {
var newUnit = this.findBestNewUnit(gameState, queues.advancedSoldier, this.uAdvanced);
var newUnit = this.findBestNewUnit(gameState, queues.advancedSoldier, "advanced");
if (newUnit){
queues.advancedSoldier.addItem(new UnitTrainingPlan(gameState, newUnit, {
"role" : "soldier"
@ -515,59 +393,68 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
}
}
if (queues.siege.length() < 4) {
var newUnit = this.findBestNewUnit(gameState, queues.siege, this.uSiege);
var newUnit = this.findBestNewUnit(gameState, queues.siege, "siege");
if (newUnit){
queues.siege.addItem(new UnitTrainingPlan(gameState, newUnit, {
"role" : "soldier"
}, 2));
}
}
Engine.ProfileStop();
// Build more military buildings
// TODO: make military building better
if (gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) > 30) {
if (gameState.countEntitiesAndQueuedWithType(gameState.applyCiv(this.bModerate[0]))
Engine.ProfileStart("Build buildings");
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > 30) {
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bModerate[0]))
+ queues.militaryBuilding.totalLength() < 1) {
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
}
}
//build advanced military buildings
if (gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) >
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) >
gameState.ai.modules[0].targetNumWorkers * 0.7){
if (queues.militaryBuilding.totalLength() === 0){
for (var i in this.bAdvanced){
if (gameState.countEntitiesAndQueuedWithType(gameState.applyCiv(this.bAdvanced[i])) < 1){
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bAdvanced[i])) < 1){
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bAdvanced[i]));
}
}
}
}
Engine.ProfileStop();
Engine.ProfileStart("Plan new attacks");
// Look for attack plans which can be executed, only do this once every minute
for (var i = 0; i < this.availableAttacks.length; i++){
if (this.availableAttacks[i].canExecute(gameState, this)){
this.availableAttacks[i].execute(gameState, this);
this.currentAttacks.push(this.availableAttacks[i]);
debug("Attacking!");
//debug("Attacking!");
}
this.availableAttacks.splice(i, 1, new this.attackManagers[i](gameState, this));
}
Engine.ProfileStop();
Engine.ProfileStart("Update attacks");
// Keep current attacks updated
for (i in this.currentAttacks){
this.currentAttacks[i].update(gameState, this, events);
}
Engine.ProfileStop();
// Set unassigned to be workers
for (var i in this.unassigned){
if (this.entity(i).hasClass("CitizenSoldier") && ! this.entity(i).hasClass("Cavalry")){
this.entity(i).setMetadata("role", "worker");
Engine.ProfileStart("Use idle military as workers");
// Set unassigned to be workers TODO: fix this so it doesn't scan all units every time
this.getUnassignedUnits(gameState).forEach(function(ent){
if (self.getSoldierType(ent) === "citizenSoldier"){
ent.setMetadata("role", "worker");
}
}
});
Engine.ProfileStop();
// Dynamically change priorities
var females = gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen"));
Engine.ProfileStart("Change Priorities");
var females = gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen"));
var femalesTarget = gameState.ai.modules[0].targetNumWorkers;
var enemyStrength = this.measureEnemyStrength(gameState);
var availableStrength = this.measureAvailableStrength();
@ -582,6 +469,7 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
if (females/femalesTarget > 0.7){
gameState.ai.priorities.defenceBuilding = 70;
}
Engine.ProfileStop();
Engine.ProfileStop();
};

View File

@ -56,21 +56,19 @@ QBotAI.prototype.runInit = function(gameState){
myKeyEntities = gameState.getOwnEntities();
}
var filter = Filters.and(Filters.isEnemy(), Filters.byClass("CivCentre"));
var enemyKeyEntities = gameState.getEntities().filter(function(ent) {
return ent.hasClass("CivCentre") && gameState.isEntityEnemy(ent);
});
var filter = Filters.byClass("CivCentre");
var enemyKeyEntities = gameState.getEnemyEntities().filter(filter);
if (enemyKeyEntities.length == 0){
enemyKeyEntities = gameState.getEntities().filter(function(ent) {
return gameState.isEntityEnemy(ent);
});
enemyKeyEntities = gameState.getEnemyEntities();
}
this.accessibility = new Accessibility(gameState, myKeyEntities.toEntityArray()[0].position());
if (enemyKeyEntities.length == 0)
return;
var pathFinder = new PathFinder(gameState);
this.pathsToMe = pathFinder.getPaths(enemyKeyEntities.toEntityArray()[0].position(), myKeyEntities.toEntityArray()[0].position(), 'entryPoints');
}
@ -159,11 +157,11 @@ function debug(output){
}
}
function copyPrototype(descendant, parent) {
var sConstructor = parent.toString();
var aMatch = sConstructor.match( /\s*function (.*)\(/ );
if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; }
for (var m in parent.prototype) {
descendant.prototype[m] = parent.prototype[m];
}
function copyPrototype(descendant, parent) {
var sConstructor = parent.toString();
var aMatch = sConstructor.match( /\s*function (.*)\(/ );
if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; }
for (var m in parent.prototype) {
descendant.prototype[m] = parent.prototype[m];
}
}

View File

@ -1,11 +1,11 @@
//The Timer class // The instance of this class is created in the qBot object under the name 'timer'
//The methods that are available to call from this instance are:
//timer.setTimer : Creates a new timer with the given interval (miliseconds).
// Optional set dalay or a limited repeat value.
//timer.checkTimer : Gives true if called at the time of the interval.
//timer.clearTimer : Deletes the timer permanently. No way to get the same timer back.
//timer.activateTimer : Sets the status of a deactivated timer to active.
//timer.deactivateTimer : Deactivates a timer. Deactivated timers will never give true.
//timer.setTimer : Creates a new timer with the given interval (miliseconds).
// Optionally set dalay or a limited repeat value.
//timer.checkTimer : Gives true if called at the time of the interval.
//timer.clearTimer : Deletes the timer permanently. No way to get the same timer back.
//timer.activateTimer : Sets the status of a deactivated timer to active.
//timer.deactivateTimer : Deactivates a timer. Deactivated timers will never give true.
//-EmjeR-// Timer class //

View File

@ -0,0 +1,115 @@
/**
* This class makes a worker do as instructed by the economy manager
*/
var Worker = function(ent) {
this.ent = ent;
};
Worker.prototype.update = function(gameState) {
var subrole = this.ent.getMetadata("subrole");
if (subrole === "gatherer"){
if (!(this.ent.unitAIState().split(".")[1] === "GATHER" && this.getResourceType(this.ent.unitAIOrderData().type) === this.ent.getMetadata("gather-type"))
&& !(this.ent.unitAIState().split(".")[1] === "RETURNRESOURCE")){
// TODO: handle combat for hunting animals
Engine.ProfileStart("Start Gathering");
this.startGathering(gameState);
Engine.ProfileStop();
}
}else if(subrole === "builder"){
if (this.ent.unitAIState().split(".")[1] !== "REPAIR"){
var target = this.ent.getMetadata("target-foundation");
this.ent.repair(target);
}
}
};
Worker.prototype.startGathering = function(gameState){
var resource = this.ent.getMetadata("gather-type");
var ent = this.ent;
if (!ent.position()){
// TODO: work out what to do when entity has no position
return;
}
// find closest dropsite which has nearby resources of the correct type
var minDropsiteDist = Math.min(); // set to infinity initially
var nearestResources = undefined;
var nearestDropsite = undefined;
gameState.updatingCollection("active-dropsite-" + resource, Filters.byMetadata("active-dropsite" + resource, true),
gameState.getOwnDropsites(resource)).forEach(function (dropsite){
if (dropsite.position()){
var dist = VectorDistance(ent.postion(), dropsite.position());
if (dist < minDropsiteDist){
minDropsiteDist = dist;
nearestResources = dropsite.getMetadata("nearby-resources-" + type);
nearestDropsite = dropsite;
}
}
});
if (!nearestResources){
nearestResources = gameState.getResourceSupplies(resource);
}
if (nearestResources.length === 0){
warn("No " + resource + " found! (1)");
return;
}
var supplies = [];
var nearestSupplyDist = Math.min();
var nearestSupply = undefined;
nearestResources.forEach(function(supply) {
// TODO: handle enemy territories
if (!supply.position()){
return;
}
// measure the distance to the resource
var dist = VectorDistance(supply.position(), ent.position());
// Add on a factor for the nearest dropsite if one exists
if (nearestDropsite){
dist += 5 * VectorDistance(supply.position(), nearestDropsite.position());
}
// Go for treasure as a priority
if (dist < 200 && supply.resourceSupplyType().generic == "treasure"){
dist /= 1000;
}
// Skip targets that are far too far away (e.g. in the
// enemy base), only do this for common supplies
if (dist > 600){
return;
}
if (dist < nearestSupplyDist){
nearestSupplyDist = dist;
nearestSupply = supply;
}
});
if (nearestSupply) {
ent.gather(nearestSupply);
}else{
debug("No " + resource + " found! (2)");
}
};
Worker.prototype.getResourceType = function(type){
if (!type || !type.generic){
return undefined;
}
if (type.generic === "treasure"){
return type.specific;
}else{
return type.generic;
}
};

View File

@ -105,6 +105,20 @@ AIProxy.prototype.OnUnitIdleChanged = function(msg)
this.changes.idle = msg.idle;
};
AIProxy.prototype.OnUnitAIStateChanged = function(msg)
{
this.NotifyChange();
this.changes.unitAIState = msg.to;
};
AIProxy.prototype.OnUnitAIOrderDataChanged = function(msg)
{
this.NotifyChange();
this.changes.unitAIOrderData = msg.to;
};
AIProxy.prototype.OnTrainingQueueChanged = function(msg)
{
this.NotifyChange();
@ -168,6 +182,10 @@ AIProxy.prototype.GetFullRepresentation = function()
{
// Updated by OnUnitIdleChanged
ret.idle = cmpUnitAI.IsIdle();
// Updated by OnUnitAIStateChanged
ret.unitAIState = cmpUnitAI.GetCurrentState();
// Updated by OnUnitAIOrderDataChanged
ret.unitAIOrderData = cmpUnitAI.GetOrderData();
}
var cmpTrainingQueue = Engine.QueryInterface(this.entity, IID_TrainingQueue);

View File

@ -1486,13 +1486,18 @@ UnitAI.prototype.IsGarrisoned = function()
UnitAI.prototype.OnCreate = function()
{
if (this.IsAnimal())
UnitFsm.Init(this, "ANIMAL.FEEDING");
UnitFsm.Init(this, "ANIMAL.FEEDING", this.StateChanged);
else if (this.IsFormationController())
UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE");
UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE", this.StateChanged);
else
UnitFsm.Init(this, "INDIVIDUAL.IDLE");
UnitFsm.Init(this, "INDIVIDUAL.IDLE", this.StateChanged);
};
UnitAI.prototype.StateChanged = function()
{
Engine.PostMessage(this.entity, MT_UnitAIStateChanged, { "to": this.GetCurrentState()});
}
UnitAI.prototype.OnOwnershipChanged = function(msg)
{
this.SetupRangeQuery();
@ -1557,6 +1562,11 @@ UnitAI.prototype.DeferMessage = function(msg)
UnitFsm.DeferMessage(this, msg);
};
UnitAI.prototype.GetCurrentState = function()
{
return UnitFsm.GetCurrentState(this);
};
/**
* Call when the current order has been completed (or failed).
* Removes the current order from the queue, and processes the
@ -1576,6 +1586,8 @@ UnitAI.prototype.FinishOrder = function()
var ret = UnitFsm.ProcessMessage(this,
{"type": "Order."+this.order.type, "data": this.order.data}
);
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData()});
// If the order was rejected then immediately take it off
// and process the remaining queue
@ -1610,6 +1622,8 @@ UnitAI.prototype.PushOrder = function(type, data)
var ret = UnitFsm.ProcessMessage(this,
{"type": "Order."+this.order.type, "data": this.order.data}
);
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData()});
// If the order was rejected then immediately take it off
// and process the remaining queue
@ -1640,6 +1654,8 @@ UnitAI.prototype.PushOrderFront = function(type, data)
var ret = UnitFsm.ProcessMessage(this,
{"type": "Order."+this.order.type, "data": this.order.data}
);
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData()});
// If the order was rejected then immediately take it off again;
// assume the previous active order is still valid (the short-lived
@ -1682,6 +1698,16 @@ UnitAI.prototype.AddOrders = function(orders)
}
}
UnitAI.prototype.GetOrderData = function()
{
if (this.order && this.order.data)
{
return eval(uneval(this.order.data)); // return a copy
}
else
return undefined;
}
UnitAI.prototype.TimerHandler = function(data, lateness)
{
// Reset the timer

View File

@ -3,3 +3,9 @@ Engine.RegisterInterface("UnitAI");
// Message of the form { "idle": true },
// sent whenever the unit's idle status changes.
Engine.RegisterMessageType("UnitIdleChanged");
// Message of the form { "to": "STATE.NAME" }.
// sent whenever the units changes state
Engine.RegisterMessageType("UnitAIStateChanged");
// Message of the form { "to": orderData }.
// sent whenever the units changes state
Engine.RegisterMessageType("UnitAIOrderDataChanged");

View File

@ -224,13 +224,15 @@ function FSM(spec)
process(this, spec, [], {});
}
FSM.prototype.Init = function(obj, initialState)
FSM.prototype.Init = function(obj, initialState, stateChangedCallback)
{
this.deferFromState = undefined;
obj.fsmStateName = "";
obj.fsmNextState = undefined;
this.SwitchToNextState(obj, initialState);
this.stateChangedCallback = stateChangedCallback;
};
FSM.prototype.SetNextState = function(obj, state)
@ -305,6 +307,11 @@ FSM.prototype.LookupState = function(currentStateName, stateName)
return stateName;
};
FSM.prototype.GetCurrentState = function(obj)
{
return obj.fsmStateName;
};
FSM.prototype.SwitchToNextState = function(obj, nextStateName)
{
var fromState = this.decompose[obj.fsmStateName];
@ -329,7 +336,11 @@ FSM.prototype.SwitchToNextState = function(obj, nextStateName)
{
obj.fsmStateName = fromState[i];
if (leave.apply(obj))
{
if (this.stateChangedCallback)
this.stateChangedCallback.apply(obj);
return;
}
}
}
@ -340,11 +351,18 @@ FSM.prototype.SwitchToNextState = function(obj, nextStateName)
{
obj.fsmStateName = toState[i];
if (enter.apply(obj))
{
if (this.stateChangedCallback)
this.stateChangedCallback.apply(obj);
return;
}
}
}
obj.fsmStateName = nextStateName;
if (this.stateChangedCallback)
this.stateChangedCallback.apply(obj);
};
Engine.RegisterGlobal("FSM", FSM);