diff --git a/binaries/data/mods/public/simulation/ai/common-api-v2/base.js b/binaries/data/mods/public/simulation/ai/common-api-v2/base.js deleted file mode 100644 index cc034353c0..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api-v2/base.js +++ /dev/null @@ -1,311 +0,0 @@ -var PlayerID = -1; - -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; - - this.turn = 0; -} - -//Return a simple object (using no classes etc) that will be serialized -//into saved games -BaseAI.prototype.Serialize = function() -{ - var rawEntities = {}; - - for (var id in this._entities) - { - rawEntities[id] = this._entities[id]._entity; - } - - return { - _rawEntities: rawEntities, - _entityMetadata: this._entityMetadata, - }; - - // TODO: ought to get the AI script subclass to serialize its own state -}; - -//Called after the constructor when loading a saved game, with 'data' being -//whatever Serialize() returned -BaseAI.prototype.Deserialize = function(data) -{ - var rawEntities = data._rawEntities; - this._entityMetadata = data._entityMetadata; - this._entities = {} - - for (var id in rawEntities) - { - this._entities[id] = new Entity(this, rawEntities[id]); - } - - // TODO: ought to get the AI script subclass to deserialize its own state -}; - -// 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 = { - "ProductionQueue": 1, - "ResourceSupply": 1, - "ResourceDropsite": 1, - "GarrisonHolder": 1, -}; - -// Components that will be disabled in resource entity templates. -// Roughly the inverse of CCmpTemplateManager::CopyResourceSubset. -var g_ResourceForbiddenComponents = { - "Cost": 1, - "Decay": 1, - "Health": 1, - "UnitAI": 1, - "UnitMotion": 1, - "Vision": 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.indexOf("foundation|") !== -1) - { - 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; - } - else if (name.indexOf("resource|") !== -1) - { - var base = this.GetTemplate(name.substr(9)); - - var resource = {}; - for (var key in base) - if (!g_ResourceForbiddenComponents[key]) - resource[key] = base[key]; - - this._derivedTemplates[name] = resource; - return resource; - } - - error("Tried to retrieve invalid template '"+name+"'"); - return null; -}; - -BaseAI.prototype.HandleMessage = function(state, playerID) -{ - PlayerID = playerID; - 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) - { - this._entities[id] = new Entity(this, state.entities[id]); - } - } - 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 (var 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 - // They should do at least this.turn++; -}; - -BaseAI.prototype.chat = function(message) -{ - Engine.PostCommand(PlayerID, {"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] !== undefined) - { - 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]; -} diff --git a/binaries/data/mods/public/simulation/ai/common-api-v2/class.js b/binaries/data/mods/public/simulation/ai/common-api-v2/class.js deleted file mode 100644 index d2bfdcb133..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api-v2/class.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * 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"); -//*/ diff --git a/binaries/data/mods/public/simulation/ai/common-api-v2/entity.js b/binaries/data/mods/public/simulation/ai/common-api-v2/entity.js deleted file mode 100644 index 1bb7d420ee..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api-v2/entity.js +++ /dev/null @@ -1,454 +0,0 @@ -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 || !this._template.Identity.Classes._string) - 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() - { - if (this._template.Health !== undefined) - return this._template.Health.Max; - return 0; - }, - isHealable: function() - { - if (this._template.Health !== undefined) - return this._template.Health.Unhealable !== "true"; - return false; - }, - isRepairable: function() - { - if (this._template.Health !== undefined) - return this._template.Health.Repairable === "true"; - return false; - }, - - 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; - if (!this._template.Builder.Entities._string) - return []; - 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.ProductionQueue || !this._template.ProductionQueue.Entities || !this._template.ProductionQueue.Entities._string) - return undefined; - var civ = this.civ(); - var templates = this._template.ProductionQueue.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; - }, - - maxGatherers: function() - { - if (this._template.ResourceSupply !== undefined) - return this._template.ResourceSupply.MaxGatherers; - return 0; - }, - - 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 || !this._template.GarrisonHolder.List._string) - return undefined; - return this._template.GarrisonHolder.List._string.split(/\s+/); - }, - - - /** - * Returns whether this is an animal that is too difficult to hunt. - * (Currently this includes all non-domestic animals.) - */ - isUnhuntable: function() { - if (!this._template.UnitAI || !this._template.UnitAI.NaturalBehaviour) - return false; - - // Ideally other animals should be huntable, but e.g. skittish animals - // must be hunted by ranged units, and some animals may be too tough. - return (this._template.UnitAI.NaturalBehaviour != "domestic"); - }, - - 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() { if (this._entity.hitpoints !== undefined) return this._entity.hitpoints; return undefined; }, - 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() { - if(this._entity.resourceSupplyAmount === undefined) - return undefined; - return this._entity.resourceSupplyAmount; - }, - - resourceSupplyGatherers: function() - { - if (this._entity.resourceSupplyGatherers !== undefined) - return this._entity.resourceSupplyGatherers; - return []; - }, - - isFull: function() - { - if (this._entity.resourceSupplyGatherers !== undefined) - return (this.maxGatherers() === this._entity.resourceSupplyGatherers.length); - return undefined; - }, - - resourceCarrying: function() { - if(this._entity.resourceCarrying === undefined) - return undefined; - 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(PlayerID, {"type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued}); - return this; - }, - - garrison: function(target) { - Engine.PostCommand(PlayerID, {"type": "garrison", "entities": [this.id()], "target": target.id(),"queued": false}); - return this; - }, - - attack: function(unitId) { - Engine.PostCommand(PlayerID, {"type": "attack", "entities": [this.id()], "target": unitId, "queued": false}); - return this; - }, - - gather: function(target, queued) { - queued = queued || false; - Engine.PostCommand(PlayerID, {"type": "gather", "entities": [this.id()], "target": target.id(), "queued": queued}); - return this; - }, - - repair: function(target, queued) { - queued = queued || false; - Engine.PostCommand(PlayerID, {"type": "repair", "entities": [this.id()], "target": target.id(), "autocontinue": false, "queued": queued}); - return this; - }, - - returnResources: function(target, queued) { - queued = queued || false; - Engine.PostCommand(PlayerID, {"type": "returnresource", "entities": [this.id()], "target": target.id(), "queued": queued}); - return this; - }, - - destroy: function() { - Engine.PostCommand(PlayerID, {"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(PlayerID, { - "type": "train", - "entities": [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(PlayerID, { - "type": "construct", - "entities": [this.id()], - "template": template, - "x": x, - "z": z, - "angle": angle, - "autorepair": false, - "autocontinue": false, - "queued": false - }); - return this; - }, -}); - diff --git a/binaries/data/mods/public/simulation/ai/common-api-v2/entitycollection.js b/binaries/data/mods/public/simulation/ai/common-api-v2/entitycollection.js deleted file mode 100644 index f68aa9a59d..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api-v2/entitycollection.js +++ /dev/null @@ -1,200 +0,0 @@ -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(PlayerID, {"type": "walk", "entities": this.toIdArray(), "x": x, "z": z, "queued": queued}); - return this; -}; - -EntityCollection.prototype.destroy = function() -{ - Engine.PostCommand(PlayerID, {"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()]) - { - // Checking length may initialize it, so do it before deleting. - if (this.length !== undefined) - this._length--; - delete this._entities[ent.id()]; - 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 - { - // Checking length may initialize it, so do it before adding. - if (this.length !== undefined) - this._length++; - this._entities[ent.id()] = ent; - 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; -}; diff --git a/binaries/data/mods/public/simulation/ai/common-api-v2/filters.js b/binaries/data/mods/public/simulation/ai/common-api-v2/filters.js deleted file mode 100644 index a3fa29c4b1..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api-v2/filters.js +++ /dev/null @@ -1,196 +0,0 @@ -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)}; - }, - - not: function(filter){ - return {"func": function(ent){ - return !filter.func(ent); - }, - "dynamicProperties": filter.dynamicProperties}; - }, - - byOwner: function(owner){ - return {"func" : function(ent){ - return (ent.owner() === owner); - }, - "dynamicProperties": ['owner']}; - }, - - byNotOwner: function(owner){ - return {"func" : function(ent){ - return (ent.owner() !== owner); - }, - "dynamicProperties": ['owner']}; - }, - - byOwners: function(owners){ - return {"func" : function(ent){ - for (var i in owners){ - if (ent.owner() == +owners[i]){ - return true; - } - } - return false; - }, - "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() === undefined){ - return false; - }else{ - return (SquareVectorDistance(startPoint, ent.position()) < dist*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 (SquareVectorDistance(startPoint, ent.position()) < dist*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.resourceSupplyMax(); - 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; - } - - // too many workers trying to gather from this resource - if (ent.getMetadata("full") === true){ - return false; - } - - if (type.generic == "treasure"){ - return (resourceType == type.specific); - } else { - return (resourceType == type.generic); - } - }, - "dynamicProperties": [/*"resourceSupplyAmount", */"owner", "metadata.inaccessible", "metadata.full"]}; - } -}; diff --git a/binaries/data/mods/public/simulation/ai/common-api-v2/utils.js b/binaries/data/mods/public/simulation/ai/common-api-v2/utils.js deleted file mode 100644 index 5567db5a34..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api-v2/utils.js +++ /dev/null @@ -1,51 +0,0 @@ -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)]; - } -} diff --git a/binaries/data/mods/public/simulation/ai/common-api/base.js b/binaries/data/mods/public/simulation/ai/common-api/base.js deleted file mode 100644 index 1fbd58f207..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api/base.js +++ /dev/null @@ -1,218 +0,0 @@ -function BaseAI(settings) -{ - if (!settings) - return; - - // Copies of static engine data (not serialized) - this._player = settings.player; - this._templates = settings.templates; - this._derivedTemplates = {}; - - // Representation of the current world state (requires serialization) - this._rawEntities = null; - this._ownEntities = {}; - this._entityMetadata = {}; -} - -// Return a simple object (using no classes etc) that will be serialized -// into saved games -BaseAI.prototype.Serialize = function() -{ - return { - _rawEntities: this._rawEntities, - _ownEntities: this._ownEntities, - _entityMetadata: this._entityMetadata, - }; - - // TODO: ought to get the AI script subclass to serialize its own state -}; - -// Called after the constructor when loading a saved game, with 'data' being -// whatever Serialize() returned -BaseAI.prototype.Deserialize = function(data) -{ - this._rawEntities = data._rawEntities; - this._ownEntities = data._ownEntities; - this._entityMetadata = data._entityMetadata; - - // TODO: ought to get the AI script subclass to deserialize its own state -}; - -// 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 = { - "ProductionQueue": 1, - "ResourceSupply": 1, - "ResourceDropsite": 1, - "GarrisonHolder": 1, -}; - -// Components that will be disabled in resource entity templates. -// Roughly the inverse of CCmpTemplateManager::CopyResourceSubset. -var g_ResourceForbiddenComponents = { - "Cost": 1, - "Decay": 1, - "Health": 1, - "UnitAI": 1, - "UnitMotion": 1, - "Vision": 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.indexOf("foundation|") !== -1) - { - 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; - } - else if (name.indexOf("resource|") !== -1) - { - var base = this.GetTemplate(name.substr(9)); - - var resource = {}; - for (var key in base) - if (!g_ResourceForbiddenComponents[key]) - resource[key] = base[key]; - - this._derivedTemplates[name] = resource; - return resource; - } - - error("Tried to retrieve invalid template '"+name+"'"); - return null; -}; - -BaseAI.prototype.HandleMessage = function(state) -{ - if (!this._rawEntities) - { - // Do a (shallow) clone of all the initial entity properties (in order - // to copy into our own script context and minimise cross-context - // weirdness), and remember the entities owned by our player - this._rawEntities = {}; - for (var id in state.entities) - { - var ent = state.entities[id]; - - this._rawEntities[id] = {}; - for (var prop in ent) - this._rawEntities[id][prop] = ent[prop]; - - if (ent.owner === this._player) - this._ownEntities[id] = this._rawEntities[id]; - } - } - else - { - this.ApplyEntitiesDelta(state); - } - - Engine.ProfileStart("HandleMessage setup"); - - this.entities = new EntityCollection(this, this._rawEntities); - 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") - { - this._rawEntities[evt.msg.entity] = {}; - } - else if (evt.type == "Destroy") - { - // The entity was destroyed but its data may still be useful, so - // remember the raw entity and this AI's metadata concerning it - evt.msg.metadata = (evt.msg.metadata || []); - evt.msg.rawEntity = (evt.msg.rawEntity || this._rawEntities[evt.msg.entity]); - evt.msg.metadata[this._player] = this._entityMetadata[evt.msg.entity]; - - delete this._rawEntities[evt.msg.entity]; - delete this._entityMetadata[evt.msg.entity]; - delete this._ownEntities[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) - this._entityMetadata[ent] = ShallowClone(evt.msg.metadata); - } - } - - for (var id in state.entities) - { - var changes = state.entities[id]; - - if ("owner" in changes) - { - var wasOurs = (this._rawEntities[id].owner !== undefined - && this._rawEntities[id].owner === this._player); - - var isOurs = (changes.owner === this._player); - - if (wasOurs && !isOurs) - delete this._ownEntities[id]; - else if (!wasOurs && isOurs) - this._ownEntities[id] = this._rawEntities[id]; - } - - for (var prop in changes) - this._rawEntities[id][prop] = changes[prop]; - } - - Engine.ProfileStop(); -}; - -BaseAI.prototype.OnUpdate = function() -{ - // AIs override this function -}; - -BaseAI.prototype.chat = function(message) -{ - Engine.PostCommand({"type": "chat", "message": message}); -}; diff --git a/binaries/data/mods/public/simulation/ai/common-api/class.js b/binaries/data/mods/public/simulation/ai/common-api/class.js deleted file mode 100644 index d2bfdcb133..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api/class.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * 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"); -//*/ diff --git a/binaries/data/mods/public/simulation/ai/common-api/entity.js b/binaries/data/mods/public/simulation/ai/common-api/entity.js deleted file mode 100644 index 4a522341bb..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api/entity.js +++ /dev/null @@ -1,453 +0,0 @@ -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 || !this._template.Identity.Classes._string) - 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.Unhealable !== "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; - if (!this._template.Builder.Entities._string) - return []; - 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.ProductionQueue || !this._template.ProductionQueue.Entities || !this._template.ProductionQueue.Entities._string) - return undefined; - var civ = this.civ(); - var templates = this._template.ProductionQueue.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 || !this._template.GarrisonHolder.List._string) - return undefined; - return this._template.GarrisonHolder.List._string.split(/\s+/); - }, - - garrisonMax: function() { - if (!this._template.GarrisonHolder) - return undefined; - - return this._template.GarrisonHolder.Max; - }, - - //"Population Bonus" is how much a building raises your population cap. - getPopulationBonus: function() { - if (!this._template.Cost) - return undefined; - - return this._template.Cost.PopulationBonus; - }, - - /** - * Returns whether this is an animal that is too difficult to hunt. - * (Currently this includes all non-domestic animals.) - */ - isUnhuntable: function() { - if (!this._template.UnitAI || !this._template.UnitAI.NaturalBehaviour) - return false; - - // Ideally other animals should be huntable, but e.g. skittish animals - // must be hunted by ranged units, and some animals may be too tough. - return (this._template.UnitAI.NaturalBehaviour != "domestic"); - }, - - 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(id) { - var metadata = this._ai._entityMetadata[this.id()]; - if (!metadata || !(id in metadata)) - return undefined; - return metadata[id]; - }, - - /** - * Sets extra data to be associated with this entity. - */ - setMetadata: function(id, value) { - var metadata = this._ai._entityMetadata[this.id()]; - if (!metadata) - metadata = this._ai._entityMetadata[this.id()] = {}; - metadata[id] = value; - }, - - position: function() { return this._entity.position; }, - - isIdle: function() { - if (this._entity.idle === undefined) - return undefined; // Prevent warning about reference to undefined property - return this._entity.idle; - }, - - 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 (this._entity.foundationProgress === undefined) - return undefined; // Prevent warning about reference to undefined property - return this._entity.foundationProgress; - }, - - owner: function() { - return this._entity.owner; - }, - - isOwn: function() { - if (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; }, - -//Garrison related - - garrisoned: function() { return new EntityCollection(this._ai, this._entity.garrisoned); }, - - garrisonSpaceAvailable: function() - { - return (this.garrisonMax() - this.garrisoned().length); - }, - - canGarrisonInto: function(target) { - var allowedClasses = target.garrisonableClasses(); - // return false if the target is full or doesn't have any allowed classes - if (target.garrisonSpaceAvaliable() <=0 || allowedClasses === undefined) - return false; - - // Check that this unit is a member of at least one of the allowed garrison classes - var hasClasses = this.classes(); - for (var i = 0; i < hasClasses.length; i++) - { - var hasClass = hasClasses[i]; - if (allowedClasses.indexOf(hasClass) >= 0) - return true; - } - - return false; - }, - - // TODO: visibility - - attack: function(target) { - Engine.PostCommand({"type": "attack", "entities": [this.id()], "target": target.id(), "queued": false}); - return this; - }, - - move: function(x, z, queued) { - queued = queued || false; - Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued}); - 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", - "entities": [this.id()], - "template": type, - "count": count, - "metadata": metadata - }); - return this; - }, - - //ungarrison a specific unit in this building - unload: function(unit) { - if (!this._template.GarrisonHolder) - return; - - Engine.PostCommand({"type": "unload", "garrisonHolder": this.id(), "entities": [unit.id()]}); - }, - - unloadAll: function() { - if (!this._template.GarrisonHolder) - return; - - Engine.PostCommand({"type": "unload-all", "garrisonHolder": this.id()}); - }, - - 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; - }, -}); - diff --git a/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js b/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js deleted file mode 100644 index 686b2a53a5..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js +++ /dev/null @@ -1,115 +0,0 @@ -function EntityCollection(baseAI, entities) -{ - this._ai = baseAI; - this._entities = entities; - - // Compute length lazily on demand, since it can be - // expensive for large collections - var length = undefined; - Object.defineProperty(this, "length", { - get: function () { - if (length === undefined) - { - length = 0; - for (var id in entities) - ++length; - } - return 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(new Entity(this._ai, 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(callback, thisp) -{ - var ret = {}; - for (var id in this._entities) - { - var ent = this._entities[id]; - var val = new Entity(this._ai, ent); - if (callback.call(thisp, val, id, this)) - ret[id] = ent; - } - return new EntityCollection(this._ai, ret); -}; - -EntityCollection.prototype.filter_raw = function(callback, thisp) -{ - var ret = {}; - for (var id in this._entities) - { - var ent = this._entities[id]; - if (callback.call(thisp, ent, 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]; - var val = new Entity(this._ai, ent); - callback.call(thisp, val, 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; -}; diff --git a/binaries/data/mods/public/simulation/ai/common-api/utils.js b/binaries/data/mods/public/simulation/ai/common-api/utils.js deleted file mode 100644 index 5567db5a34..0000000000 --- a/binaries/data/mods/public/simulation/ai/common-api/utils.js +++ /dev/null @@ -1,51 +0,0 @@ -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)]; - } -} diff --git a/binaries/data/mods/public/simulation/ai/qbot/_init.js b/binaries/data/mods/public/simulation/ai/qbot/_init.js deleted file mode 100644 index a693a4cb24..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/_init.js +++ /dev/null @@ -1 +0,0 @@ -Engine.IncludeModule("common-api-v2"); \ No newline at end of file diff --git a/binaries/data/mods/public/simulation/ai/qbot/attackMoveToCC.js b/binaries/data/mods/public/simulation/ai/qbot/attackMoveToCC.js deleted file mode 100644 index aad20894ff..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/attackMoveToCC.js +++ /dev/null @@ -1,205 +0,0 @@ -function AttackMoveToCC(gameState, militaryManager){ - this.minAttackSize = 20; - this.maxAttackSize = 60; - this.idList=[]; - - this.previousTime = 0; - this.state = "unexecuted"; - - this.healthRecord = []; -}; - -// Returns true if the attack can be executed at the current time -AttackMoveToCC.prototype.canExecute = function(gameState, militaryManager){ - var enemyStrength = militaryManager.measureEnemyStrength(gameState); - var enemyCount = militaryManager.measureEnemyCount(gameState); - - // We require our army to be >= this strength - var targetStrength = enemyStrength * 1.5; - - var availableCount = militaryManager.countAvailableUnits(); - var availableStrength = militaryManager.measureAvailableStrength(); - - debug("Troops needed for attack: " + this.minAttackSize + " Have: " + availableCount); - debug("Troops strength for attack: " + targetStrength + " Have: " + availableStrength); - - return ((availableStrength >= targetStrength && availableCount >= this.minAttackSize) - || availableCount >= this.maxAttackSize); -}; - -// Executes the attack plan, after this is executed the update function will be run every turn -AttackMoveToCC.prototype.execute = function(gameState, militaryManager){ - var availableCount = militaryManager.countAvailableUnits(); - this.idList = militaryManager.getAvailableUnits(availableCount); - - var pending = EntityCollectionFromIds(gameState, this.idList); - - // Find the critical enemy buildings we could attack - var targets = militaryManager.getEnemyBuildings(gameState,"ConquestCritical"); - // If there are no critical structures, attack anything else that's critical - if (targets.length == 0) { - targets = gameState.entities.filter(function(ent) { - return (gameState.isEntityEnemy(ent) && ent.hasClass("ConquestCritical") && ent.owner() !== 0 && ent.position()); - }); - } - // If there's nothing, attack anything else that's less critical - if (targets.length == 0) { - targets = militaryManager.getEnemyBuildings(gameState,"Town"); - } - if (targets.length == 0) { - targets = militaryManager.getEnemyBuildings(gameState,"Village"); - } - - // If we have a target, move to it - if (targets.length) { - // Add an attack role so the economic manager doesn't try and use them - pending.forEach(function(ent) { - ent.setMetadata("role", "attack"); - }); - - var curPos = pending.getCentrePosition(); - - var target = targets.toEntityArray()[0]; - this.targetPos = target.position(); - - // Find possible distinct paths to the enemy - var pathFinder = new PathFinder(gameState); - var pathsToEnemy = pathFinder.getPaths(curPos, this.targetPos); - if (! pathsToEnemy){ - pathsToEnemy = [this.targetPos]; - } - - var rand = Math.floor(Math.random() * pathsToEnemy.length); - this.path = pathsToEnemy[rand]; - - pending.move(this.path[0][0], this.path[0][1]); - } else if (targets.length == 0 ) { - gameState.ai.gameFinished = true; - } - - this.state = "walking"; -}; - -// Runs every turn after the attack is executed -// This removes idle units from the attack -AttackMoveToCC.prototype.update = function(gameState, militaryManager, events){ - - // keep the list of units in good order by pruning ids with no corresponding entities (i.e. dead units) - var removeList = []; - var totalHealth = 0; - for (var idKey in this.idList){ - var id = this.idList[idKey]; - var ent = militaryManager.entity(id); - if (ent === undefined){ - removeList.push(id); - }else{ - if (ent.hitpoints()){ - totalHealth += ent.hitpoints(); - } - } - } - for (var i in removeList){ - this.idList.splice(this.idList.indexOf(removeList[i]),1); - } - - var units = EntityCollectionFromIds(gameState, this.idList); - - if (this.path.length === 0){ - var idleCount = 0; - var self = this; - units.forEach(function(ent){ - if (ent.isIdle()){ - if (ent.position() && VectorDistance(ent.position(), self.targetPos) > 30){ - ent.move(self.targetPos[0], self.targetPos[1]); - }else{ - militaryManager.unassignUnit(ent.id()); - } - } - }); - return; - } - - var deltaHealth = 0; - var deltaTime = 1; - var time = gameState.getTimeElapsed(); - this.healthRecord.push([totalHealth, time]); - if (this.healthRecord.length > 1){ - for (var i = this.healthRecord.length - 1; i >= 0; i--){ - deltaHealth = totalHealth - this.healthRecord[i][0]; - deltaTime = time - this.healthRecord[i][1]; - if (this.healthRecord[i][1] < time - 5*1000){ - break; - } - } - } - - var numUnits = this.idList.length; - if (numUnits < 1) return; - var damageRate = -deltaHealth / deltaTime * 1000; - var centrePos = units.getCentrePosition(); - if (! centrePos) return; - - var idleCount = 0; - // Looks for idle units away from the formations centre - for (var idKey in this.idList){ - var id = this.idList[idKey]; - var ent = militaryManager.entity(id); - if (ent.isIdle()){ - if (ent.position() && VectorDistance(ent.position(), centrePos) > 20){ - var dist = VectorDistance(ent.position(), centrePos); - var vector = [centrePos[0] - ent.position()[0], centrePos[1] - ent.position()[1]]; - vector[0] *= 10/dist; - vector[1] *= 10/dist; - ent.move(centrePos[0] + vector[0], centrePos[1] + vector[1]); - }else{ - idleCount++; - } - } - } - - if ((damageRate / Math.sqrt(numUnits)) > 2){ - if (this.state === "walking"){ - var sumAttackerPos = [0,0]; - var numAttackers = 0; - - for (var key in events){ - var e = events[key]; - //{type:"Attacked", msg:{attacker:736, target:1133, type:"Melee"}} - if (e.type === "Attacked" && e.msg){ - if (this.idList.indexOf(e.msg.target) !== -1){ - var attacker = militaryManager.entity(e.msg.attacker); - if (attacker && attacker.position()){ - sumAttackerPos[0] += attacker.position()[0]; - sumAttackerPos[1] += attacker.position()[1]; - numAttackers += 1; - } - } - } - } - if (numAttackers > 0){ - var avgAttackerPos = [sumAttackerPos[0]/numAttackers, sumAttackerPos[1]/numAttackers]; - // Stop moving - units.move(centrePos[0], centrePos[1]); - this.state = "attacking"; - } - } - }else{ - if (this.state === "attacking"){ - units.move(this.path[0][0], this.path[0][1]); - this.state = "walking"; - } - } - - if (this.state === "walking"){ - if (VectorDistance(centrePos, this.path[0]) < 20 || idleCount/numUnits > 0.8){ - this.path.shift(); - if (this.path.length > 0){ - units.move(this.path[0][0], this.path[0][1]); - } - } - } - - this.previousTime = time; - this.previousHealth = totalHealth; -}; - diff --git a/binaries/data/mods/public/simulation/ai/qbot/attackMoveToLocation.js b/binaries/data/mods/public/simulation/ai/qbot/attackMoveToLocation.js deleted file mode 100644 index abc66f19ad..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/attackMoveToLocation.js +++ /dev/null @@ -1,240 +0,0 @@ -function AttackMoveToLocation(gameState, Config, militaryManager, minAttackSize, maxAttackSize, targetFinder){ - - this.Config = Config; - this.minAttackSize = minAttackSize || Config.attack.minAttackSize; - this.maxAttackSize = maxAttackSize || Config.attack.maxAttackSize; - this.idList=[]; - - this.previousTime = 0; - this.state = "unexecuted"; - - this.targetFinder = targetFinder || this.defaultTargetFinder; - - this.healthRecord = []; -}; - -// Returns true if the attack can be executed at the current time -AttackMoveToLocation.prototype.canExecute = function(gameState, militaryManager){ - var enemyStrength = militaryManager.measureEnemyStrength(gameState); - var enemyCount = militaryManager.measureEnemyCount(gameState); - - // We require our army to be >= this strength - var targetStrength = enemyStrength * this.Config.attack.enemyRatio; - - var availableCount = militaryManager.countAvailableUnits(); - var availableStrength = militaryManager.measureAvailableStrength(); - - debug("Troops needed for attack: " + this.minAttackSize + " Have: " + availableCount); - debug("Troops strength for attack: " + targetStrength + " Have: " + availableStrength); - - return ((availableStrength >= targetStrength && availableCount >= this.minAttackSize) - || availableCount >= this.maxAttackSize); -}; - -// Default target finder aims for conquest critical targets -AttackMoveToLocation.prototype.defaultTargetFinder = function(gameState, militaryManager){ - // Find the critical enemy buildings we could attack - var targets = militaryManager.getEnemyBuildings(gameState,"ConquestCritical"); - // If there are no critical structures, attack anything else that's critical - if (targets.length == 0) { - targets = gameState.entities.filter(function(ent) { - return (gameState.isEntityEnemy(ent) && ent.hasClass("ConquestCritical") && ent.owner() !== 0 && ent.position()); - }); - } - // If there's nothing, attack anything else that's less critical - if (targets.length == 0) { - targets = militaryManager.getEnemyBuildings(gameState,"Town"); - } - if (targets.length == 0) { - targets = militaryManager.getEnemyBuildings(gameState,"Village"); - } - return targets; -}; - -// Executes the attack plan, after this is executed the update function will be run every turn -AttackMoveToLocation.prototype.execute = function(gameState, militaryManager){ - var availableCount = militaryManager.countAvailableUnits(); - var numWanted = Math.min(availableCount, this.maxAttackSize); - this.idList = militaryManager.getAvailableUnits(numWanted); - - var pending = EntityCollectionFromIds(gameState, this.idList); - - var targets = this.targetFinder(gameState, militaryManager); - - if (targets.length === 0){ - targets = this.defaultTargetFinder(gameState, militaryManager); - } - - // If we have a target, move to it - if (targets.length) { - // Add an attack role so the economic manager doesn't try and use them - pending.forEach(function(ent) { - ent.setMetadata("role", "attack"); - }); - - var curPos = pending.getCentrePosition(); - - // pick a random target from the list - this.targetPos = undefined; - var count = 0; - while (!this.targetPos) { - var rand = Math.floor((Math.random()*targets.length)); - var target = targets.toEntityArray()[rand]; - this.targetPos = target.position(); - count++; - if (count > 1000) { - warn("No target with a valid position found"); - return; - } - } - - // Find possible distinct paths to the enemy - var pathFinder = new PathFinder(gameState); - var pathsToEnemy = pathFinder.getPaths(curPos, this.targetPos); - if (!pathsToEnemy || !pathsToEnemy[0] || pathsToEnemy[0][0] === undefined || pathsToEnemy[0][1] === undefined) { - pathsToEnemy = [[this.targetPos]]; - } - - var randPath = Math.floor(Math.random() * pathsToEnemy.length); - this.path = pathsToEnemy[randPath]; - - pending.move(this.path[0][0], this.path[0][1]); - } else if (targets.length == 0 ) { - gameState.ai.gameFinished = true; - return; - } - - this.state = "walking"; -}; - -// Runs every turn after the attack is executed -// 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; - for (var idKey in this.idList){ - var id = this.idList[idKey]; - var ent = militaryManager.entity(id); - if (ent === undefined){ - removeList.push(id); - }else{ - if (ent.hitpoints()){ - totalHealth += ent.hitpoints(); - } - } - } - for (var i in removeList){ - this.idList.splice(this.idList.indexOf(removeList[i]),1); - } - - var units = EntityCollectionFromIds(gameState, this.idList); - - if (!this.path || this.path.length === 0){ - var idleCount = 0; - var self = this; - units.forEach(function(ent){ - if (ent.isIdle()){ - if (ent.position() && VectorDistance(ent.position(), self.targetPos) > 30){ - ent.move(self.targetPos[0], self.targetPos[1]); - }else{ - militaryManager.unassignUnit(ent.id()); - } - } - }); - return; - } - - var deltaHealth = 0; - var deltaTime = 1; - var time = gameState.getTimeElapsed(); - this.healthRecord.push([totalHealth, time]); - if (this.healthRecord.length > 1){ - for (var i = this.healthRecord.length - 1; i >= 0; i--){ - deltaHealth = totalHealth - this.healthRecord[i][0]; - deltaTime = time - this.healthRecord[i][1]; - if (this.healthRecord[i][1] < time - 5*1000){ - break; - } - } - } - - var numUnits = this.idList.length; - if (numUnits < 1) return; - var damageRate = -deltaHealth / deltaTime * 1000; - var centrePos = units.getCentrePosition(); - if (! centrePos) return; - - var idleCount = 0; - // Looks for idle units away from the formations centre - for (var idKey in this.idList){ - var id = this.idList[idKey]; - var ent = militaryManager.entity(id); - if (ent.isIdle()){ - if (ent.position() && VectorDistance(ent.position(), centrePos) > 20){ - var dist = VectorDistance(ent.position(), centrePos); - var vector = [centrePos[0] - ent.position()[0], centrePos[1] - ent.position()[1]]; - vector[0] *= 10/dist; - vector[1] *= 10/dist; - ent.move(centrePos[0] + vector[0], centrePos[1] + vector[1]); - }else{ - idleCount++; - } - } - } - - if ((damageRate / Math.sqrt(numUnits)) > 2){ - if (this.state === "walking"){ - var sumAttackerPos = [0,0]; - var numAttackers = 0; - - for (var key in events){ - var e = events[key]; - //{type:"Attacked", msg:{attacker:736, target:1133, type:"Melee"}} - if (e.type === "Attacked" && e.msg){ - if (this.idList.indexOf(e.msg.target) !== -1){ - var attacker = militaryManager.entity(e.msg.attacker); - if (attacker && attacker.position()){ - sumAttackerPos[0] += attacker.position()[0]; - sumAttackerPos[1] += attacker.position()[1]; - numAttackers += 1; - } - } - } - } - if (numAttackers > 0){ - var avgAttackerPos = [sumAttackerPos[0]/numAttackers, sumAttackerPos[1]/numAttackers]; - // Stop moving - units.move(centrePos[0], centrePos[1]); - this.state = "attacking"; - } - } - }else{ - if (this.state === "attacking"){ - units.move(this.path[0][0], this.path[0][1]); - this.state = "walking"; - } - } - - if (this.state === "walking"){ - if (VectorDistance(centrePos, this.path[0]) < 20 || idleCount/numUnits > 0.8){ - this.path.shift(); - if (this.path.length > 0){ - units.move(this.path[0][0], this.path[0][1]); - } - } - } - - this.previousTime = time; - this.previousHealth = totalHealth; -}; - diff --git a/binaries/data/mods/public/simulation/ai/qbot/config.js b/binaries/data/mods/public/simulation/ai/qbot/config.js deleted file mode 100644 index 91c610da24..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/config.js +++ /dev/null @@ -1,58 +0,0 @@ -function Config() { - this.debug = false - - this.attack = { - "minAttackSize" : 20, // attackMoveToLocation - "maxAttackSize" : 60, // attackMoveToLocation - "enemyRatio" : 1.5, // attackMoveToLocation - "groupSize" : 10 // military - }; - - // defence - this.defence = { - "acquireDistance" : 220, - "releaseDistance" : 250, - "groupRadius" : 20, - "groupBreakRadius" : 40, - "groupMergeRadius" : 10, - "defenderRatio" : 2 - }; - - // military - this.buildings = { - "moderate" : { - "default" : [ "structures/{civ}_barracks" ] - }, - "advanced" : { - "hele" : [ "structures/{civ}_gymnasion", "structures/{civ}_fortress" ], - "athen" : [ "structures/{civ}_gymnasion", "structures/{civ}_fortress" ], - "spart" : [ "structures/{civ}_syssiton", "structures/{civ}_fortress" ], - "mace" : [ "structures/{civ}_fortress" ], - "cart" : [ "structures/{civ}_fortress", "structures/{civ}_embassy_celtic", - "structures/{civ}_embassy_iberian", "structures/{civ}_embassy_italiote" ], - "celt" : [ "structures/{civ}_kennel", "structures/{civ}_fortress_b", "structures/{civ}_fortress_g" ], - "iber" : [ "structures/{civ}_fortress" ], - "pers" : [ "structures/{civ}_fortress", "structures/{civ}_stables", "structures/{civ}_apadana" ], - "rome" : [ "structures/{civ}_army_camp", "structures/{civ}_fortress" ] - }, - "fort" : { - "default" : [ "structures/{civ}_fortress" ], - "celt" : [ "structures/{civ}_fortress_b", "structures/{civ}_fortress_g" ] - } - }; - - // qbot - this.priorities = { // Note these are dynamic, you are only setting the initial values - "house" : 500, - "citizenSoldier" : 100, - "villager" : 100, - "economicBuilding" : 30, - "field" : 20, - "advancedSoldier" : 30, - "siege" : 10, - "militaryBuilding" : 50, - "defenceBuilding" : 17, - "civilCentre" : 1000 - }; -}; - diff --git a/binaries/data/mods/public/simulation/ai/qbot/data.json b/binaries/data/mods/public/simulation/ai/qbot/data.json deleted file mode 100644 index 2f59b71760..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/data.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "qBot", - "description": "Quantumstate's improved version of the Test Bot", - "constructor": "QBotAI", - "useShared" : false -} diff --git a/binaries/data/mods/public/simulation/ai/qbot/defence.js b/binaries/data/mods/public/simulation/ai/qbot/defence.js deleted file mode 100644 index 97ee747b6d..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/defence.js +++ /dev/null @@ -1,278 +0,0 @@ -function Defence(Config){ - this.ACQUIRE_DIST = Config.defence.acquireDistance; - this.RELEASE_DIST = Config.defence.releaseDistance; - - this.GROUP_RADIUS = Config.defence.groupRadius; // units will be added to a group if they are within this radius - this.GROUP_BREAK_RADIUS = Config.defence.groupBreakRadius; // units will leave a group if they are outside of this radius - this.GROUP_MERGE_RADIUS = Config.defence.groupMergeRadius; // Two groups with centres this far apart will be merged - - this.DEFENCE_RATIO = Config.defence.defenderRatio; // How many defenders we want per attacker. Need to balance fewer losses vs. lost economy - - // These are objects with the keys being entity ids and values being the entity objects - // NOTE: It is assumed that all attackers have a valid position, the attackers list must be kept up to date so this - // property is maintained - this.attackers = {}; // Enemy soldiers which are attacking our base - this.defenders = {}; // Our soldiers currently being used for defence - - // A list of groups, enemy soldiers are clumped together in groups. - this.groups = []; -} - -Defence.prototype.update = function(gameState, events, militaryManager){ - Engine.ProfileStart("Defence Manager"); - var enemyTroops = militaryManager.getEnemySoldiers(); - - this.updateAttackers(gameState, events, enemyTroops); - this.updateGroups(); - var unassignedDefenders = this.updateDefenders(gameState); - this.assignDefenders(gameState, militaryManager, unassignedDefenders); - - Engine.ProfileStop(); -}; - -Defence.prototype.assignDefenders = function(gameState, militaryManager, unassignedDefenders){ - var numAttackers = Object.keys(this.attackers).length; - var numDefenders = Object.keys(this.defenders).length; - var numUnassignedDefenders = unassignedDefenders.length; - var numAssignedDefenders = numDefenders - numUnassignedDefenders; - - // TODO: this is non optimal, we may have unevenly distributed defenders - // Unassign defenders which aren't needed - if (numAttackers * this.DEFENCE_RATIO <= numAssignedDefenders){ - militaryManager.unassignUnits(unassignedDefenders); - - var CCs = gameState.getOwnEntities().filter(Filters.byClass("CivCentre")); - - for (var i in unassignedDefenders){ - var pos = this.defenders[unassignedDefenders[i]].position(); - - // Move back to nearest CC - if (pos){ - var nearestCCArray = CCs.filterNearest(pos, 1).toEntityArray(); - if (nearestCCArray.length > 0){ - var movePos = nearestCCArray[0].position(); - this.defenders[unassignedDefenders[i]].move(movePos[0], movePos[1]); - } - } - delete this.defenders[unassignedDefenders[i]]; - } - return; - } - - // Check to see if we need to recruit more defenders - if (numAttackers * this.DEFENCE_RATIO > numDefenders){ - var numNeeded = Math.ceil(numAttackers * this.DEFENCE_RATIO - numDefenders); - var numIdleAvailable = militaryManager.countAvailableUnits(Filters.isIdle()); - - if (numIdleAvailable > numNeeded){ - var newUnits = militaryManager.getAvailableUnits(numNeeded, Filters.isIdle()); - for (var i in newUnits){ - var ent = gameState.getEntityById(newUnits[i]); - } - unassignedDefenders = unassignedDefenders.concat(newUnits); - }else{ - var newUnits = militaryManager.getAvailableUnits(numNeeded); - for (var i in newUnits){ - var ent = gameState.getEntityById(newUnits[i]); - ent.setMetadata("initialPosition", ent.position()); - } - unassignedDefenders = unassignedDefenders.concat(newUnits); - } - } - - // Now distribute the unassigned defenders among the attacking groups. - for (var i in unassignedDefenders){ - var id = unassignedDefenders[i]; - var ent = gameState.getEntityById(id); - if (!ent.position()){ - debug("Defender with no position! (shouldn't happen)"); - debug(ent); - continue; - } - - var minDist = Math.min(); - var closestGroup = undefined; - for (var j in this.groups){ - var dist = VectorDistance(this.groups[j].position, ent.position()); - if (dist < minDist && this.groups[j].members.length * this.DEFENCE_RATIO > this.groups[j].defenders.length){ - minDist = dist; - closestGroup = this.groups[j]; - } - } - - if (closestGroup !== undefined){ - var rand = Math.floor(Math.random()*closestGroup.members.length); - ent.attack(closestGroup.members[rand]); - this.defenders[id] = ent; - closestGroup.defenders.push(id); - } - } -}; - -Defence.prototype.updateDefenders = function(gameState){ - var newDefenders = {}; - var unassignedDefenders = []; - - for (var i in this.groups){ - this.removeDestroyed(gameState, this.groups[i].defenders); - for (var j in this.groups[i].defenders){ - var id = this.groups[i].defenders[j]; - newDefenders[id] = this.defenders[id]; - var ent = gameState.getEntityById(id); - - // If the defender is idle then set it to attack another member of the group it is targetting - if (ent && ent.isIdle()){ - var rand = Math.floor(Math.random()*this.groups[i].members.length); - ent.attack(this.groups[i].members[rand]); - } - } - } - - for (var id in this.defenders){ - if (!gameState.getEntityById(id)){ - delete this.defenders[id]; - } else if (!newDefenders[id]){ - unassignedDefenders.push(id); - } - } - - return unassignedDefenders; -}; - -// Returns an entity collection of key buildings which should be defended. -// Currently just returns civ centres -Defence.prototype.getKeyBuildings = function(gameState){ - return gameState.getOwnEntities().filter(Filters.byClass("CivCentre")); -}; - -/* - * This function puts all attacking enemy troops into this.attackers, the list from the turn before is put into - * this.oldAttackers, also any new attackers have their id's listed in this.newAttackers. - */ -Defence.prototype.updateAttackers = function(gameState, events, enemyTroops){ - var self = this; - - var keyBuildings = this.getKeyBuildings(gameState); - - this.newAttackers = []; - this.oldAttackers = this.attackers; - this.attackers = {}; - - enemyTroops.forEach(function(ent){ - if (ent.position()){ - var minDist = Math.min(); - keyBuildings.forEach(function(building){ - if (building.position() && VectorDistance(ent.position(), building.position()) < minDist){ - minDist = VectorDistance(ent.position(), building.position()); - } - }); - - if (self.oldAttackers[ent.id()]){ - if (minDist < self.RELEASE_DIST){ - self.attackers[ent.id()] = ent; - } - }else{ - if (minDist < self.ACQUIRE_DIST){ - self.attackers[ent.id()] = ent; - self.newAttackers.push(ent.id()); - } - } - } - }); -}; - -Defence.prototype.removeDestroyed = function(gameState, entList){ - for (var i = 0; i < entList.length; i++){ - if (!gameState.getEntityById(entList[i])){ - entList.splice(i, 1); - i--; - } - } -}; - -Defence.prototype.updateGroups = function(){ - // clean up groups by removing members and removing empty groups - for (var i = 0; i < this.groups.length; i++){ - var group = this.groups[i]; - // remove members which are no longer attackers - for (var j = 0; j < group.members.length; j++){ - if (!this.attackers[group.members[j]]){ - group.members.splice(j, 1); - j--; - } - } - // recalculate centre of group - group.sumPosition = [0,0]; - for (var j = 0; j < group.members.length; j++){ - group.sumPosition[0] += this.attackers[group.members[j]].position()[0]; - group.sumPosition[1] += this.attackers[group.members[j]].position()[1]; - } - group.position[0] = group.sumPosition[0]/group.members.length; - group.position[1] = group.sumPosition[1]/group.members.length; - - // remove members that are too far away - for (var j = 0; j < group.members.length; j++){ - if ( VectorDistance(this.attackers[group.members[j]].position(), group.position) > this.GROUP_BREAK_RADIUS){ - this.newAttackers.push(group.members[j]); - group.sumPosition[0] -= this.attackers[group.members[j]].position()[0]; - group.sumPosition[1] -= this.attackers[group.members[j]].position()[1]; - group.members.splice(j, 1); - j--; - } - } - - if (group.members.length === 0){ - this.groups.splice(i, 1); - i--; - } - - group.position[0] = group.sumPosition[0]/group.members.length; - group.position[1] = group.sumPosition[1]/group.members.length; - } - - // add ungrouped attackers to groups - for (var j in this.newAttackers){ - var ent = this.attackers[this.newAttackers[j]]; - var foundGroup = false; - for (var i in this.groups){ - if (VectorDistance(ent.position(), this.groups[i].position) <= this.GROUP_RADIUS){ - this.groups[i].members.push(ent.id()); - - this.groups[i].sumPosition[0] += ent.position()[0]; - this.groups[i].sumPosition[1] += ent.position()[1]; - this.groups[i].position[0] = this.groups[i].sumPosition[0]/this.groups[i].members.length; - this.groups[i].position[1] = this.groups[i].sumPosition[1]/this.groups[i].members.length; - - foundGroup = true; - break; - } - } - if (!foundGroup){ - this.groups.push({"members": [ent.id()], - "position": [ent.position()[0], ent.position()[1]], - "sumPosition": [ent.position()[0], ent.position()[1]], - "defenders": []}); - } - } - - // merge groups which are close together - for (var i = 0; i < this.groups.length; i++){ - for (var j = 0; j < this.groups.length; j++){ - if (this.groups[i].members.length < this.groups[j].members.length){ - if (VectorDistance(this.groups[i].position, this.groups[j].position) < this.GROUP_MERGE_RADIUS){ - this.groups[j].members = this.groups[i].members.concat(this.groups[j].members); - this.groups[j].defenders = this.groups[i].defenders.concat(this.groups[j].defenders); - - this.groups[j].sumPosition[0] += this.groups[i].sumPosition[0]; - this.groups[j].sumPosition[1] += this.groups[i].sumPosition[1]; - this.groups[j].position[0] = this.groups[j].sumPosition[0]/this.groups[j].members.length; - this.groups[j].position[1] = this.groups[j].sumPosition[1]/this.groups[j].members.length; - - this.groups.splice(i, 1); - i--; - break; - } - } - } - } -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/economy.js b/binaries/data/mods/public/simulation/ai/qbot/economy.js deleted file mode 100644 index 9081efdf33..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/economy.js +++ /dev/null @@ -1,543 +0,0 @@ -var EconomyManager = function() { - this.targetNumBuilders = 5; // number of workers we want building stuff - this.targetNumFields = 3; - - this.resourceMaps = {}; // Contains maps showing the density of wood, stone and metal - - this.setCount = 0; //stops villagers being reassigned to other resources too frequently, count a set number of - //turns before trying to reassign them. - - this.dropsiteNumbers = {wood: 2, stone: 1, metal: 1}; -}; -// More initialisation for stuff that needs the gameState -EconomyManager.prototype.init = function(gameState){ - this.targetNumWorkers = Math.max(Math.floor(gameState.getPopulationMax()/3), 1); -}; - -EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) { - // Count the workers in the world and in progress - var numWorkers = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("units/{civ}_support_female_citizen")); - numWorkers += queues.villager.countTotalQueuedUnits(); - - // If we have too few, train more - if (numWorkers < this.targetNumWorkers) { - for ( var i = 0; i < this.targetNumWorkers - numWorkers; i++) { - queues.villager.addItem(new UnitTrainingPlan(gameState, "units/{civ}_support_female_citizen", { - "role" : "worker" - })); - } - } -}; - -// Pick the resource which most needs another worker -EconomyManager.prototype.pickMostNeededResources = function(gameState) { - - var self = this; - - // Find what resource type we're most in need of - 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] = 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) { - // Prefer fewer gatherers (divided by weight) - var va = numGatherers[a] / (self.gatherWeights[a]+1); - var vb = numGatherers[b] / (self.gatherWeights[b]+1); - return va-vb; - }); - - return types; -}; - -EconomyManager.prototype.reassignRolelessUnits = function(gameState) { - //TODO: Move this out of the economic section - var roleless = gameState.getOwnEntitiesByRole(undefined); - - roleless.forEach(function(ent) { - if (ent.hasClass("Worker")){ - ent.setMetadata("role", "worker"); - }else if(ent.hasClass("CitizenSoldier") || ent.hasClass("Champion")){ - ent.setMetadata("role", "soldier"); - }else{ - ent.setMetadata("role", "unknown"); - } - }); -}; - -// If the numbers of workers on the resources is unbalanced then set some of workers to idle so -// they can be reassigned by reassignIdleWorkers. -EconomyManager.prototype.setWorkersIdleByPriority = function(gameState){ - this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState); - - var numGatherers = {}; - var totalGatherers = 0; - var totalWeight = 0; - for ( var type in this.gatherWeights){ - numGatherers[type] = 0; - totalWeight += this.gatherWeights[type]; - } - - gameState.getOwnEntitiesByRole("worker").forEach(function(ent) { - if (ent.getMetadata("subrole") === "gatherer"){ - numGatherers[ent.getMetadata("gather-type")] += 1; - totalGatherers += 1; - } - }); - - for ( var type in this.gatherWeights){ - var allocation = Math.floor(totalGatherers * (this.gatherWeights[type]/totalWeight)); - if (allocation < numGatherers[type]){ - var numToTake = numGatherers[type] - allocation; - gameState.getOwnEntitiesByRole("worker").forEach(function(ent) { - if (ent.getMetadata("subrole") === "gatherer" && ent.getMetadata("gather-type") === type && numToTake > 0){ - ent.setMetadata("subrole", "idle"); - numToTake -= 1; - } - }); - } - } -}; - -EconomyManager.prototype.reassignIdleWorkers = function(gameState) { - - var self = this; - - // Search for idle workers, and tell them to gather resources based on demand - 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; - - idleWorkers.forEach(function(ent) { - // Check that the worker isn't garrisoned - if (ent.position() === undefined){ - return; - } - - var types = self.pickMostNeededResources(gameState); - - 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.getOwnFoundations(); - - // Check if nothing to build - if (!foundations.length){ - return; - } - - var workers = gameState.getOwnEntitiesByRole("worker"); - - var builderWorkers = this.workersBySubrole(gameState, "builder"); - - // Check if enough builders - var extraNeeded = this.targetNumBuilders - builderWorkers.length; - if (extraNeeded <= 0){ - return; - } - - // Pick non-builders who are closest to the first foundation, - // and tell them to start building it - - var target = foundations.toEntityArray()[0]; - - var nonBuilderWorkers = workers.filter(function(ent) { - // check position so garrisoned units aren't tasked - return (ent.getMetadata("subrole") !== "builder" && ent.position() !== undefined); - }); - - var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), extraNeeded); - - // Order each builder individually, not as a formation - nearestNonBuilders.forEach(function(ent) { - ent.setMetadata("subrole", "builder"); - ent.setMetadata("target-foundation", target); - }); -}; - -EconomyManager.prototype.buildMoreFields = function(gameState, queues) { - // give time for treasures to be gathered - if (gameState.getTimeElapsed() < 30 * 1000) - return; - - var numFood = 0; - - gameState.updatingCollection("active-dropsite-food", Filters.byMetadata("active-dropsite-food", true), - gameState.getOwnDropsites("food")).forEach(function (dropsite){ - numFood += dropsite.getMetadata("nearby-resources-food").length; - }); - - numFood += gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_field")); - numFood += queues.field.totalLength(); - - for ( var i = numFood; i < this.targetNumFields; i++) { - queues.field.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_field")); - } -}; - -// If all the CC's are destroyed then build a new one -EconomyManager.prototype.buildNewCC= function(gameState, queues) { - var numCCs = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_civil_centre")); - numCCs += queues.civilCentre.totalLength(); - - for ( var i = numCCs; i < 1; i++) { - queues.civilCentre.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre")); - } -}; - -//creates and maintains a map of tree density -EconomyManager.prototype.updateResourceMaps = function(gameState, events){ - // The weight of the influence function is amountOfResource/decreaseFactor - var decreaseFactor = {'wood': 15, 'stone': 100, 'metal': 100, 'food': 20}; - // 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.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.entityObj){ - var ent = e.msg.entityObj; - if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic === resource){ - 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]); - this.resourceMaps[resource].addInfluence(x, z, radius[resource], -strength); - } - } - }else if (e.type === "Create") { - if (e.msg.entityObj){ - var ent = e.msg.entityObj; - if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic === resource){ - 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]); - this.resourceMaps[resource].addInfluence(x, z, radius[resource], strength); - } - } - } - } - } - - //this.resourceMaps['wood'].dumpIm("tree_density.png"); -}; - -// Returns the position of the best place to build a new dropsite for the specified resource -EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource){ - // A map which gives a positive weight for all CCs and adds a negative weight near all dropsites - var friendlyTiles = new Map(gameState); - gameState.getOwnEntities().forEach(function(ent) { - // We want to build near a CC of ours - if (ent.hasClass("CivCentre")){ - var infl = 200; - - var pos = ent.position(); - var x = Math.round(pos[0] / gameState.cellSize); - var z = Math.round(pos[1] / gameState.cellSize); - friendlyTiles.addInfluence(x, z, infl, 0.1 * infl); - friendlyTiles.addInfluence(x, z, infl/2, 0.1 * infl); - } - // We don't want multiple dropsites at one spot so add a negative for all dropsites - if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){ - var infl = 20; - - var pos = ent.position(); - var x = Math.round(pos[0] / gameState.cellSize); - var z = Math.round(pos[1] / gameState.cellSize); - - friendlyTiles.addInfluence(x, z, infl, -50, 'quadratic'); - } - }); - - // Multiply by tree density to get a combination of the two maps - friendlyTiles.multiply(this.resourceMaps[resource]); - - //friendlyTiles.dumpIm(resource + "_density_fade.png", 10000); - - var obstructions = Map.createObstructionMap(gameState); - obstructions.expandInfluences(); - - var bestIdx = friendlyTiles.findBestTile(4, obstructions)[0]; - - // Convert from 1d map pixel coordinates to game engine coordinates - var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize; - var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize; - return [x,z]; -}; - -EconomyManager.prototype.updateResourceConcentrations = function(gameState){ - var self = this; - var resources = ["food", "wood", "stone", "metal"]; - for (var key in resources){ - var resource = resources[key]; - gameState.getOwnEntities().forEach(function(ent) { - if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){ - var radius = 14; - - var pos = ent.position(); - var x = Math.round(pos[0] / gameState.cellSize); - var z = Math.round(pos[1] / gameState.cellSize); - - var quantity = self.resourceMaps[resource].sumInfluence(x, z, radius); - - ent.setMetadata("resourceQuantity_" + resource, quantity); - } - }); - } -}; - -// Stores lists of nearby resources -EconomyManager.prototype.updateNearbyResources = function(gameState){ - var self = this; - var resources = ["food", "wood", "stone", "metal"]; - var resourceSupplies; - var radius = 100; - for (var key in resources){ - var resource = resources[key]; - - gameState.getOwnDropsites(resource).forEach(function(ent) { - if (ent.getMetadata("nearby-resources-" + resource) === undefined){ - var filterPos = Filters.byStaticDistance(ent.position(), radius); - - var collection = gameState.getResourceSupplies(resource).filter(filterPos); - collection.registerUpdates(); - - 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); - }else{ - ent.setMetadata("active-dropsite-" + resource, true); - } - /* - // Make resources glow wildly - if (resource == "food"){ - ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){ - Engine.PostCommand(PlayerID, {"type": "set-shading-color", "entities": [ent.id()], "rgb": [10,0,0]}); - }); - } - if (resource == "wood"){ - ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){ - Engine.PostCommand(PlayerID, {"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,10,0]}); - }); - } - if (resource == "metal"){ - ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){ - Engine.PostCommand(PlayerID, {"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0,10]}); - }); - }*/ - }); - } -}; - -//return the number of resource dropsites with an acceptable amount of the resource nearby -EconomyManager.prototype.checkResourceConcentrations = function(gameState, resource){ - //TODO: make these values adaptive - var requiredInfluence = {wood: 16000, stone: 300, metal: 300}; - var count = 0; - gameState.getOwnEntities().forEach(function(ent) { - if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){ - var quantity = ent.getMetadata("resourceQuantity_" + resource); - - if (quantity >= requiredInfluence[resource]){ - count ++; - } - } - }); - return count; -}; - -EconomyManager.prototype.buildMarket = function(gameState, queues){ - if (gameState.getTimeElapsed() > 600 * 1000){ - if (queues.economicBuilding.totalLength() === 0 && - gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_market")) === 0){ - //only ever build one storehouse/CC/market at a time - queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_market")); - } - } -}; - -EconomyManager.prototype.buildDropsites = function(gameState, queues){ - if (queues.economicBuilding.totalLength() === 0 && - gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_storehouse")) === 0 && - gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_civil_centre")) === 0){ - //only ever build one storehouse/CC/market at a time - if (gameState.getTimeElapsed() > 30 * 1000){ - for (var resource in this.dropsiteNumbers){ - if (this.checkResourceConcentrations(gameState, resource) < this.dropsiteNumbers[resource]){ - var spot = this.getBestResourceBuildSpot(gameState, resource); - - var myCivCentres = gameState.getOwnEntities().filter(function(ent) { - if (!ent.hasClass("CivCentre") || ent.position() === undefined){ - return false; - } - var dx = (spot[0]-ent.position()[0]); - var dy = (spot[1]-ent.position()[1]); - var dist2 = dx*dx + dy*dy; - return (ent.hasClass("CivCentre") && dist2 < 180*180); - }); - - if (myCivCentres.length === 0){ - queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre", spot)); - }else{ - queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_storehouse", spot)); - } - break; - } - } - } - } -}; - -EconomyManager.prototype.update = function(gameState, queues, events) { - Engine.ProfileStart("economy update"); - - this.reassignRolelessUnits(gameState); - - this.buildNewCC(gameState,queues); - - Engine.ProfileStart("Train workers and build farms"); - this.trainMoreWorkers(gameState, queues); - - this.buildMoreFields(gameState, queues); - Engine.ProfileStop(); - - //Later in the game we want to build stuff faster. - if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.5) { - this.targetNumBuilders = 10; - }else{ - this.targetNumBuilders = 5; - } - - 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}; - } - - Engine.ProfileStart("Update Resource Maps and Concentrations"); - this.updateResourceMaps(gameState, events); - this.updateResourceConcentrations(gameState); - this.updateNearbyResources(gameState); - Engine.ProfileStop(); - - Engine.ProfileStart("Build new Dropsites"); - this.buildDropsites(gameState, queues); - Engine.ProfileStop(); - - this.buildMarket(gameState, queues); - - // TODO: implement a timer based system for this - this.setCount += 1; - if (this.setCount >= 20){ - this.setWorkersIdleByPriority(gameState); - this.setCount = 0; - } - - Engine.ProfileStart("Reassign Idle Workers"); - this.reassignIdleWorkers(gameState); - Engine.ProfileStop(); - - Engine.ProfileStart("Swap Workers"); - var gathererGroups = {}; - gameState.getOwnEntitiesByRole("worker").forEach(function(ent){ - var key = uneval(ent.resourceGatherRates()); - if (!gathererGroups[key]){ - gathererGroups[key] = {"food": [], "wood": [], "metal": [], "stone": []}; - } - if (ent.getMetadata("gather-type") in gathererGroups[key]){ - gathererGroups[key][ent.getMetadata("gather-type")].push(ent); - } - }); - - for (var i in gathererGroups){ - for (var j in gathererGroups){ - var a = eval(i); - var b = eval(j); - if (a["food.grain"]/b["food.grain"] > a["wood.tree"]/b["wood.tree"] && gathererGroups[i]["wood"].length > 0 && gathererGroups[j]["food"].length > 0){ - for (var k = 0; k < Math.min(gathererGroups[i]["wood"].length, gathererGroups[j]["food"].length); k++){ - gathererGroups[i]["wood"][k].setMetadata("gather-type", "food"); - gathererGroups[j]["food"][k].setMetadata("gather-type", "wood"); - } - } - } - } - Engine.ProfileStop(); - - Engine.ProfileStart("Assign builders"); - this.assignToFoundations(gameState); - Engine.ProfileStop(); - - Engine.ProfileStart("Run Workers"); - gameState.getOwnEntitiesByRole("worker").forEach(function(ent){ - if (!ent.getMetadata("worker-object")){ - ent.setMetadata("worker-object", new Worker(ent)); - } - ent.getMetadata("worker-object").update(gameState); - }); - - // Gatherer count updates for non-workers - var filter = Filters.and(Filters.not(Filters.byMetadata("worker-object", undefined)), - Filters.not(Filters.byMetadata("role", "worker"))); - gameState.updatingCollection("reassigned-workers", filter, gameState.getOwnEntities()).forEach(function(ent){ - ent.getMetadata("worker-object").updateGathererCounts(gameState); - }); - - // Gatherer count updates for destroyed units - for (var i in events) { - var e = events[i]; - - if (e.type === "Destroy") { - if (e.msg.metadata && e.msg.metadata[gameState.getPlayerID()] && e.msg.metadata[gameState.getPlayerID()]["worker-object"]){ - e.msg.metadata[gameState.getPlayerID()]["worker-object"].updateGathererCounts(gameState, true); - } - } - } - Engine.ProfileStop(); - - Engine.ProfileStop(); -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/entity-extend.js b/binaries/data/mods/public/simulation/ai/qbot/entity-extend.js deleted file mode 100644 index b358426867..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/entity-extend.js +++ /dev/null @@ -1,14 +0,0 @@ -Entity.prototype.deleteMetadata = function(id) { - delete this._ai._entityMetadata[this.id()]; -}; - -Entity.prototype.garrison = function(target) { - Engine.PostCommand(PlayerID, {"type": "garrison", "entities": [this.id()], "target": target.id(),"queued": false}); - return this; -}; - -Entity.prototype.attack = function(unitId) -{ - Engine.PostCommand(PlayerID, {"type": "attack", "entities": [this.id()], "target": unitId, "queued": false}); - return this; -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/entitycollection-extend.js b/binaries/data/mods/public/simulation/ai/qbot/entitycollection-extend.js deleted file mode 100644 index 732c81ed68..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/entitycollection-extend.js +++ /dev/null @@ -1,40 +0,0 @@ -EntityCollection.prototype.attack = function(unit) -{ - var unitId; - if (typeOf(unit) === "Entity"){ - unitId = unit.id(); - }else{ - unitId = unit; - } - - Engine.PostCommand(PlayerID, {"type": "attack", "entities": this.toIdArray(), "target": unitId, "queued": false}); - return this; -}; - -function EntityCollectionFromIds(gameState, idList){ - var ents = {}; - for (var i in idList){ - var id = idList[i]; - if (gameState.entities._entities[id]) { - ents[id] = gameState.entities._entities[id]; - } - } - return new EntityCollection(gameState.ai, ents); -} - -EntityCollection.prototype.getCentrePosition = function(){ - var sumPos = [0, 0]; - var count = 0; - this.forEach(function(ent){ - if (ent.position()){ - sumPos[0] += ent.position()[0]; - sumPos[1] += ent.position()[1]; - count ++; - } - }); - if (count === 0){ - return undefined; - }else{ - return [sumPos[0]/count, sumPos[1]/count]; - } -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/gamestate.js b/binaries/data/mods/public/simulation/ai/qbot/gamestate.js deleted file mode 100644 index c9471e41c2..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/gamestate.js +++ /dev/null @@ -1,319 +0,0 @@ -/** - * 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 = 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; - 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() { - return this.timeElapsed; -}; - -GameState.prototype.getTemplate = function(type) { - if (!this.templates[type]){ - return null; - } - - return new EntityTemplate(this.templates[type]); -}; - -GameState.prototype.applyCiv = function(str) { - return str.replace(/\{civ\}/g, this.playerData.civ); -}; - -/** - * @returns {Resources} - */ -GameState.prototype.getResources = function() { - return new Resources(this.playerData.resourceCounts); -}; - -GameState.prototype.getMap = function() { - return this.ai.passabilityMap; -}; - -GameState.prototype.getTerritoryMap = function() { - return Map.createTerritoryMap(this); -}; - -GameState.prototype.getPopulation = function() { - return this.playerData.popCount; -}; - -GameState.prototype.getPopulationLimit = function() { - return this.playerData.popLimit; -}; - -GameState.prototype.getPopulationMax = function() { - return this.playerData.popMax; -}; - -GameState.prototype.getPassabilityClassMask = function(name) { - if (!(name in this.ai.passabilityClasses)){ - error("Tried to use invalid passability class name '" + name + "'"); - } - return this.ai.passabilityClasses[name]; -}; - -GameState.prototype.getPlayerID = function() { - return this.player; -}; - -GameState.prototype.isPlayerAlly = function(id) { - return this.playerData.isAlly[id]; -}; - -GameState.prototype.isPlayerEnemy = function(id) { - return this.playerData.isEnemy[id]; -}; - -GameState.prototype.getEnemies = function(){ - var ret = []; - for (var 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()]; - } else if (ent && ent.owner){ - return this.playerData.isAlly[ent.owner]; - } - return false; -}; - -GameState.prototype.isEntityEnemy = function(ent) { - if (ent && ent.owner && (typeof ent.owner) === "function"){ - return this.playerData.isEnemy[ent.owner()]; - } else if (ent && ent.owner){ - return this.playerData.isEnemy[ent.owner]; - } - return false; -}; - -GameState.prototype.isEntityOwn = function(ent) { - if (ent && ent.owner && (typeof ent.owner) === "function"){ - return ent.owner() == this.player; - } else if (ent && ent.owner){ - return ent.owner == this.player; - } - return false; -}; - -GameState.prototype.getOwnEntities = function() { - 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() { - return this.entities; -}; - -GameState.prototype.getEntityById = function(id){ - if (this.entities._entities[id]) { - return this.entities._entities[id]; - }else{ - //debug("Entity " + id + " requested does not exist"); - } - return undefined; -}; - -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.getOwnEntitiesByRole = function(role){ - return this.getOwnEntitiesByMetadata("role", role); -}; - -// 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()); -}; - -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 animal resources - count += this.countEntitiesByType("resource|" + 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; -}; - -GameState.prototype.countFoundationsWithType = function(type) { - var foundationType = "foundation|" + type; - var count = 0; - this.getOwnEntities().forEach(function(ent) { - var t = ent.templateName(); - if (t == foundationType) - ++count; - }); - return count; -}; - -GameState.prototype.countOwnEntitiesByRole = function(role) { - return this.getOwnEntitiesByRole(role).length; -}; - -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; -}; - -/** - * Find buildings that are capable of training the given unit type, and aren't - * already too busy. - */ -GameState.prototype.findTrainers = function(template) { - 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) - 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. - */ -GameState.prototype.findBuilders = function(template) { - return this.getOwnEntities().filter(function(ent) { - - var buildable = ent.buildableEntities(); - if (!buildable || buildable.indexOf(template) == -1) - return false; - - return true; - }); -}; - -GameState.prototype.getOwnFoundations = function() { - return this.updatingCollection("ownFoundations", Filters.isFoundation(), this.getOwnEntities()); -}; - -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.getEntityLimits = function() { - return this.playerData.entityLimits; -}; - -GameState.prototype.getEntityCounts = function() { - return this.playerData.entityCounts; -}; - -// Checks whether the maximum number of buildings have been constructed for a certain catergory -GameState.prototype.isEntityLimitReached = function(category) { - if(this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined) - return false; - return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]); -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/housing.js b/binaries/data/mods/public/simulation/ai/qbot/housing.js deleted file mode 100644 index 3b07026863..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/housing.js +++ /dev/null @@ -1,29 +0,0 @@ -// Decides when to a new house needs to be built -var HousingManager = function() { - -}; - -HousingManager.prototype.buildMoreHouses = function(gameState, queues) { - // temporary 'remaining population space' based check, need to do - // predictive in future - if (gameState.getPopulationLimit() - gameState.getPopulation() < 20 - && gameState.getPopulationLimit() < gameState.getPopulationMax()) { - 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) - - numConstructing - numPlanned; - - for ( var i = 0; i < additional; i++) { - queues.house.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_house")); - } - } -}; - -HousingManager.prototype.update = function(gameState, queues) { - Engine.ProfileStart("housing update"); - - this.buildMoreHouses(gameState, queues); - - Engine.ProfileStop(); -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/license_gpl-2.0.txt b/binaries/data/mods/public/simulation/ai/qbot/license_gpl-2.0.txt deleted file mode 100644 index 82fa1daad4..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/license_gpl-2.0.txt +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/binaries/data/mods/public/simulation/ai/qbot/map-module.js b/binaries/data/mods/public/simulation/ai/qbot/map-module.js deleted file mode 100644 index 3df3b30fe9..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/map-module.js +++ /dev/null @@ -1,263 +0,0 @@ -const TERRITORY_PLAYER_MASK = 0x3F; - -//TODO: Make this cope with negative cell values -function Map(gameState, originalMap){ - // get the map to find out the correct dimensions - var gameMap = gameState.getMap(); - this.width = gameMap.width; - this.height = gameMap.height; - this.length = gameMap.data.length; - if (originalMap){ - this.map = originalMap; - }else{ - this.map = new Uint16Array(this.length); - } - this.cellSize = gameState.cellSize; -} - -Map.prototype.gamePosToMapPos = function(p){ - return [Math.round(p[0]/this.cellSize), Math.round(p[1]/this.cellSize)]; -}; - -Map.prototype.point = function(p){ - var q = this.gamePosToMapPos(p); - return this.map[q[0] + this.width * q[1]]; -}; - -Map.createObstructionMap = function(gameState, template){ - var passabilityMap = gameState.getMap(); - var territoryMap = gameState.ai.territoryMap; - - // default values - var placementType = "land"; - var buildOwn = true; - var buildAlly = true; - var buildNeutral = true; - var buildEnemy = false; - // If there is a template then replace the defaults - if (template){ - placementType = template.buildPlacementType(); - buildOwn = template.hasBuildTerritory("own"); - buildAlly = template.hasBuildTerritory("ally"); - buildNeutral = template.hasBuildTerritory("neutral"); - buildEnemy = template.hasBuildTerritory("enemy"); - } - - var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction"); - // Only accept valid land tiles (we don't handle docks yet) - switch(placementType){ - case "shore": - obstructionMask |= gameState.getPassabilityClassMask("building-shore"); - break; - case "land": - default: - obstructionMask |= gameState.getPassabilityClassMask("building-land"); - break; - } - - var playerID = gameState.getPlayerID(); - - var obstructionTiles = new Uint16Array(passabilityMap.data.length); - for (var i = 0; i < passabilityMap.data.length; ++i) - { - var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK); - var invalidTerritory = ( - (!buildOwn && tilePlayer == playerID) || - (!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) || - (!buildNeutral && tilePlayer == 0) || - (!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer != 0) - ); - var tileAccessible = (gameState.ai.accessibility.map[i] == 1); - obstructionTiles[i] = (!tileAccessible || invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535; - } - - var map = new Map(gameState, obstructionTiles); - - if (template && template.buildDistance()){ - var minDist = template.buildDistance().MinDistance; - var category = template.buildDistance().FromCategory; - if (minDist !== undefined && category !== undefined){ - gameState.getOwnEntities().forEach(function(ent) { - if (ent.buildCategory() === category && ent.position()){ - var pos = ent.position(); - var x = Math.round(pos[0] / gameState.cellSize); - var z = Math.round(pos[1] / gameState.cellSize); - map.addInfluence(x, z, minDist/gameState.cellSize, -65535, 'constant'); - } - }); - } - } - - return map; -}; - -Map.createTerritoryMap = function(gameState) { - var map = gameState.ai.territoryMap; - - var ret = new Map(gameState, map.data); - - ret.getOwner = function(p) { - return this.point(p) & TERRITORY_PLAYER_MASK; - } - - return ret; -}; - -Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) { - strength = strength ? strength : maxDist; - type = type ? type : 'linear'; - - var x0 = Math.max(0, cx - maxDist); - var y0 = Math.max(0, cy - maxDist); - var x1 = Math.min(this.width, cx + maxDist); - var y1 = Math.min(this.height, cy + maxDist); - var maxDist2 = maxDist * maxDist; - - var str = 0; - switch (type){ - case 'linear': - str = strength / maxDist; - break; - case 'quadratic': - str = strength / maxDist2; - break; - case 'constant': - str = strength; - break; - } - - for ( var y = y0; y < y1; ++y) { - for ( var x = x0; x < x1; ++x) { - var dx = x - cx; - var dy = y - cy; - var r2 = dx*dx + dy*dy; - if (r2 < maxDist2){ - var quant = 0; - switch (type){ - case 'linear': - var r = Math.sqrt(r2); - quant = str * (maxDist - r); - break; - case 'quadratic': - quant = str * (maxDist2 - r2); - break; - case 'constant': - quant = str; - break; - } - - if (-1 * quant > this.map[x + y * this.width]){ - this.map[x + y * this.width] = 0; //set anything which would have gone negative to 0 - }else{ - this.map[x + y * this.width] += quant; - } - } - } - } -}; - -Map.prototype.sumInfluence = function(cx, cy, radius){ - var x0 = Math.max(0, cx - radius); - var y0 = Math.max(0, cy - radius); - var x1 = Math.min(this.width, cx + radius); - var y1 = Math.min(this.height, cy + radius); - var radius2 = radius * radius; - - var sum = 0; - - for ( var y = y0; y < y1; ++y) { - for ( var x = x0; x < x1; ++x) { - var dx = x - cx; - var dy = y - cy; - var r2 = dx*dx + dy*dy; - if (r2 < radius2){ - sum += this.map[x + y * this.width]; - } - } - } - return sum; -}; - -/** - * Make each cell's 16-bit value at least one greater than each of its - * neighbours' values. (If the grid is initialised with 0s and 65535s, the - * result of each cell is its Manhattan distance to the nearest 0.) - * - * TODO: maybe this should be 8-bit (and clamp at 255)? - */ -Map.prototype.expandInfluences = function() { - var w = this.width; - var h = this.height; - var grid = this.map; - for ( var y = 0; y < h; ++y) { - var min = 65535; - for ( var x = 0; x < w; ++x) { - var g = grid[x + y * w]; - if (g > min) - grid[x + y * w] = min; - else if (g < min) - min = g; - ++min; - } - - for ( var x = w - 2; x >= 0; --x) { - var g = grid[x + y * w]; - if (g > min) - grid[x + y * w] = min; - else if (g < min) - min = g; - ++min; - } - } - - for ( var x = 0; x < w; ++x) { - var min = 65535; - for ( var y = 0; y < h; ++y) { - var g = grid[x + y * w]; - if (g > min) - grid[x + y * w] = min; - else if (g < min) - min = g; - ++min; - } - - for ( var y = h - 2; y >= 0; --y) { - var g = grid[x + y * w]; - if (g > min) - grid[x + y * w] = min; - else if (g < min) - min = g; - ++min; - } - } -}; - -Map.prototype.findBestTile = function(radius, obstructionTiles){ - // Find the best non-obstructed tile - var bestIdx = 0; - var bestVal = -1; - for ( var i = 0; i < this.length; ++i) { - if (obstructionTiles.map[i] > radius) { - var v = this.map[i]; - if (v > bestVal) { - bestVal = v; - bestIdx = i; - } - } - } - - return [bestIdx, bestVal]; -}; - -// Multiplies current map by the parameter map pixelwise -Map.prototype.multiply = function(map){ - for (var i = 0; i < this.length; i++){ - this.map[i] *= map.map[i]; - } -}; - -Map.prototype.dumpIm = function(name, threshold){ - name = name ? name : "default.png"; - threshold = threshold ? threshold : 256; - Engine.DumpImage(name, this.map, this.width, this.height, threshold); -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/military.js b/binaries/data/mods/public/simulation/ai/qbot/military.js deleted file mode 100755 index f9e8e959f0..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/military.js +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Military strategy: - * * Try training an attack squad of a specified size - * * When it's the appropriate size, send it to attack the enemy - * * Repeat forever - * - */ - -var MilitaryAttackManager = function(Config) { - - this.Config = Config - // these use the structure soldiers[unitId] = true|false to register the units - this.attackManagers = [AttackMoveToLocation]; - this.availableAttacks = []; - this.currentAttacks = []; - - // Counts how many attacks we have sent at the enemy. - this.attackCount = 0; - this.lastAttackTime = 0; - - this.defenceManager = new Defence(Config); -}; - -MilitaryAttackManager.prototype.init = function(gameState) { - var civ = gameState.playerData.civ; - - // load units and buildings from the config files - - if (civ in this.Config.buildings.moderate){ - this.bModerate = this.Config.buildings.moderate[civ]; - }else{ - this.bModerate = this.Config.buildings.moderate['default']; - } - - if (civ in this.Config.buildings.advanced){ - this.bAdvanced = this.Config.buildings.advanced[civ]; - }else{ - this.bAdvanced = this.Config.buildings.advanced['default']; - } - - if (civ in this.Config.buildings.fort){ - this.bFort = this.Config.buildings.fort[civ]; - }else{ - this.bFort = this.Config.buildings.fort['default']; - } - - for (var i in this.bAdvanced){ - this.bAdvanced[i] = gameState.applyCiv(this.bAdvanced[i]); - } - for (var i in this.bFort){ - this.bFort[i] = gameState.applyCiv(this.bFort[i]); - } - - this.getEconomicTargets = function(gameState, militaryManager){ - return militaryManager.getEnemyBuildings(gameState, "Economic"); - }; - // TODO: figure out how to make this generic - for (var i in this.attackManagers){ - this.availableAttacks[i] = new this.attackManagers[i](gameState, this.Config, this); - } - - var enemies = gameState.getEnemyEntities(); - var filter = Filters.byClassesOr(["CitizenSoldier", "Champion", "Hero", "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, soldierType){ - var allTrainable = []; - gameState.getOwnEntities().forEach(function(ent) { - var trainable = ent.trainableEntities(); - for (var i in trainable){ - if (allTrainable.indexOf(trainable[i]) === -1){ - allTrainable.push(trainable[i]); - } - } - }); - - 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("Hero")){ - return undefined; - } - if (ent.hasClass("CitizenSoldier") && !ent.hasClass("Cavalry")){ - return "citizenSoldier"; - }else if (ent.hasClass("Champion") || 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, 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.countEntitiesAndQueuedByType(gameState.applyCiv(t)) - + queue.countAllByType(gameState.applyCiv(t)) ]); - } - - // Sort by increasing count - types.sort(function(a, b) { - return a[1] - b[1]; - }); - - if (types.length === 0){ - return false; - } - return types[0][0]; -}; - -MilitaryAttackManager.prototype.registerSoldiers = function(gameState) { - var soldiers = gameState.getOwnEntitiesByRole("soldier"); - var self = this; - - soldiers.forEach(function(ent) { - ent.setMetadata("role", "military"); - ent.setMetadata("military", "unassigned"); - }); -}; - -// return count of enemy buildings for a given building class -MilitaryAttackManager.prototype.getEnemyBuildings = function(gameState,cls) { - var targets = gameState.entities.filter(function(ent) { - return (gameState.isEntityEnemy(ent) && ent.hasClass("Structure") && ent.hasClass(cls) && ent.owner() !== 0 && ent.position()); - }); - return targets; -}; - -// return n available units and makes these units unavailable -MilitaryAttackManager.prototype.getAvailableUnits = function(n, filter) { - var ret = []; - var count = 0; - - var units = undefined; - - if (filter){ - units = this.getUnassignedUnits().filter(filter); - }else{ - units = this.getUnassignedUnits(); - } - - units.forEach(function(ent){ - 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.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.unassignUnit(units[i]); - } -}; - -MilitaryAttackManager.prototype.getUnassignedUnits = function(){ - return this.gameState.getOwnEntitiesByMetadata("military", "unassigned"); -}; - -MilitaryAttackManager.prototype.countAvailableUnits = function(filter){ - var count = 0; - if (filter){ - return this.getUnassignedUnits().filter(filter).length; - }else{ - return this.getUnassignedUnits().length; - } -}; - -// Takes an entity id and returns an entity object or undefined 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) { - return this.gameState.getEntityById(id); -}; - -// Returns the military strength of unit -MilitaryAttackManager.prototype.getUnitStrength = function(ent){ - var strength = 0.0; - var attackTypes = ent.attackTypes(); - var armourStrength = ent.armourStrengths(); - var hp = 2 * ent.hitpoints() / (160 + 1*ent.maxHitpoints()); //100 = typical number of hitpoints - for (var typeKey in attackTypes) { - var type = attackTypes[typeKey]; - var attackStrength = ent.attackStrengths(type); - var attackRange = ent.attackRange(type); - var attackTimes = ent.attackTimes(type); - for (var str in attackStrength) { - var val = parseFloat(attackStrength[str]); - switch (str) { - case "crush": - strength += (val * 0.085) / 3; - break; - case "hack": - strength += (val * 0.075) / 3; - break; - case "pierce": - strength += (val * 0.065) / 3; - break; - } - } - if (attackRange){ - strength += (attackRange.max * 0.0125) ; - } - for (var str in attackTimes) { - var val = parseFloat(attackTimes[str]); - switch (str){ - case "repeat": - strength += (val / 100000); - break; - case "prepare": - strength -= (val / 100000); - break; - } - } - } - for (var str in armourStrength) { - var val = parseFloat(armourStrength[str]); - switch (str) { - case "crush": - strength += (val * 0.085) / 3; - break; - case "hack": - strength += (val * 0.075) / 3; - break; - case "pierce": - strength += (val * 0.065) / 3; - break; - } - } - return strength * hp; -}; - -// Returns the strength of the available units of ai army -MilitaryAttackManager.prototype.measureAvailableStrength = function(){ - var strength = 0.0; - var self = this; - this.getUnassignedUnits(this.gameState).forEach(function(ent){ - strength += self.getUnitStrength(ent); - }); - return strength; -}; - -MilitaryAttackManager.prototype.getEnemySoldiers = function(){ - return this.enemySoldiers; -}; - -// Returns the number of units in the largest enemy army -MilitaryAttackManager.prototype.measureEnemyCount = function(gameState){ - // Measure enemy units - var isEnemy = gameState.playerData.isEnemy; - var enemyCount = []; - var maxCount = 0; - for ( var i = 1; i < isEnemy.length; i++) { - enemyCount[i] = 0; - } - - // Loop through the enemy soldiers and add one to the count for that soldiers player's count - this.enemySoldiers.forEach(function(ent) { - enemyCount[ent.owner()]++; - - if (enemyCount[ent.owner()] > maxCount) { - maxCount = enemyCount[ent.owner()]; - } - }); - - return maxCount; -}; - -// Returns the strength of the largest enemy army -MilitaryAttackManager.prototype.measureEnemyStrength = function(gameState){ - // Measure enemy strength - var isEnemy = gameState.playerData.isEnemy; - var enemyStrength = []; - var maxStrength = 0; - var self = this; - - for ( var i = 1; i < isEnemy.length; i++) { - enemyStrength[i] = 0; - } - - // Loop through the enemy soldiers and add the strength to that soldiers player's total strength - this.enemySoldiers.forEach(function(ent) { - enemyStrength[ent.owner()] += self.getUnitStrength(ent); - - if (enemyStrength[ent.owner()] > maxStrength) { - maxStrength = enemyStrength[ent.owner()]; - } - }); - - return maxStrength; -}; - -// Adds towers to the defenceBuilding queue -MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){ - if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower')) - + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"]) { - - - gameState.getOwnEntities().forEach(function(dropsiteEnt) { - if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.getMetadata("defenseTower") !== true){ - var position = dropsiteEnt.position(); - if (position){ - queues.defenceBuilding.addItem(new BuildingConstructionPlan(gameState, 'structures/{civ}_defense_tower', position)); - } - dropsiteEnt.setMetadata("defenseTower", true); - } - }); - } - - var numFortresses = 0; - for (var i in this.bFort){ - numFortresses += gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bFort[i])); - } - - if (numFortresses + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["Fortress"]) { - if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > gameState.ai.modules["economy"].targetNumWorkers * 0.5){ - if (gameState.getTimeElapsed() > 350 * 1000 * numFortresses){ - if (gameState.ai.pathsToMe && gameState.ai.pathsToMe.length > 0){ - var position = gameState.ai.pathsToMe.shift(); - // TODO: pick a fort randomly from the list. - queues.defenceBuilding.addItem(new BuildingConstructionPlan(gameState, this.bFort[0], position)); - }else{ - queues.defenceBuilding.addItem(new BuildingConstructionPlan(gameState, this.bFort[0])); - } - } - } - } -}; - -MilitaryAttackManager.prototype.constructTrainingBuildings = function(gameState, queues) { - // Build more military buildings - // TODO: make military building better - 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.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > - gameState.ai.modules["economy"].targetNumWorkers * 0.7){ - if (queues.militaryBuilding.totalLength() === 0){ - for (var i in this.bAdvanced){ - if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bAdvanced[i])) < 1){ - queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bAdvanced[i])); - } - } - } - } - Engine.ProfileStop(); -}; - -MilitaryAttackManager.prototype.trainMilitaryUnits = function(gameState, queues){ - 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, "citizenSoldier"); - if (newUnit){ - queues.citizenSoldier.addItem(new UnitTrainingPlan(gameState, newUnit, { - "role" : "soldier" - }, 5)); - } - } - if (queues.advancedSoldier.length() < 2) { - var newUnit = this.findBestNewUnit(gameState, queues.advancedSoldier, "advanced"); - if (newUnit){ - queues.advancedSoldier.addItem(new UnitTrainingPlan(gameState, newUnit, { - "role" : "soldier" - }, 5)); - } - } - if (queues.siege.length() < 4) { - var newUnit = this.findBestNewUnit(gameState, queues.siege, "siege"); - if (newUnit){ - queues.siege.addItem(new UnitTrainingPlan(gameState, newUnit, { - "role" : "soldier" - }, 2)); - } - } - Engine.ProfileStop(); -}; - -MilitaryAttackManager.prototype.update = function(gameState, queues, events) { - var self = this; - Engine.ProfileStart("military update"); - this.gameState = gameState; - - this.registerSoldiers(gameState); - - this.trainMilitaryUnits(gameState, queues); - - this.constructTrainingBuildings(gameState, queues); - - this.buildDefences(gameState, queues); - - this.defenceManager.update(gameState, events, this); - - 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!"); - } - this.availableAttacks.splice(i, 1, new this.attackManagers[i](gameState, this.Config, this)); - } - Engine.ProfileStop(); - - Engine.ProfileStart("Update attacks"); - // Keep current attacks updated - for (var i in this.currentAttacks){ - this.currentAttacks[i].update(gameState, this, events); - } - Engine.ProfileStop(); - - 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(); - - Engine.ProfileStop(); -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/plan-building.js b/binaries/data/mods/public/simulation/ai/qbot/plan-building.js deleted file mode 100644 index 63e731a44d..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/plan-building.js +++ /dev/null @@ -1,124 +0,0 @@ -var BuildingConstructionPlan = function(gameState, type, position) { - this.type = gameState.applyCiv(type); - this.position = position; - - var template = gameState.getTemplate(this.type); - if (!template) { - this.invalidTemplate = true; - debug("Cannot build " + this.type); - return; - } - this.category = "building"; - this.cost = new Resources(template.cost()); - this.number = 1; // The number of buildings to build -}; - -BuildingConstructionPlan.prototype.canExecute = function(gameState) { - if (this.invalidTemplate){ - return false; - } - - // TODO: verify numeric limits etc - - var builders = gameState.findBuilders(this.type); - - return (builders.length != 0); -}; - -BuildingConstructionPlan.prototype.execute = function(gameState) { - - var builders = gameState.findBuilders(this.type).toEntityArray(); - - // We don't care which builder we assign, since they won't actually - // do the building themselves - all we care about is that there is - // some unit that can start the foundation - - var pos = this.findGoodPosition(gameState); - if (!pos){ - debug("No room to place " + this.type); - return; - } - - builders[0].construct(this.type, pos.x, pos.z, pos.angle); -}; - -BuildingConstructionPlan.prototype.getCost = function() { - return this.cost; -}; - -BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) { - var template = gameState.getTemplate(this.type); - - var cellSize = gameState.cellSize; // size of each tile - - // First, find all tiles that are far enough away from obstructions: - - var obstructionMap = Map.createObstructionMap(gameState,template); - - //obstructionMap.dumpIm("obstructions.png"); - - obstructionMap.expandInfluences(); - - // Compute each tile's closeness to friendly structures: - - var friendlyTiles = new Map(gameState); - - // If a position was specified then place the building as close to it as possible - if (this.position){ - var x = Math.round(this.position[0] / cellSize); - var z = Math.round(this.position[1] / cellSize); - friendlyTiles.addInfluence(x, z, 200); - //friendlyTiles.dumpIm("pos.png", 200); - }else{ - // No position was specified so try and find a sensible place to build - gameState.getOwnEntities().forEach(function(ent) { - if (ent.hasClass("Structure")) { - var infl = 32; - if (ent.hasClass("CivCentre")) - infl *= 4; - - var pos = ent.position(); - var x = Math.round(pos[0] / cellSize); - var z = Math.round(pos[1] / cellSize); - if (template._template.BuildRestrictions.Category === "Field"){ - // Only care about being near a place where we can deposit food for fields - if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf("food") !== -1){ - friendlyTiles.addInfluence(x, z, infl, infl); - } - }else{ - friendlyTiles.addInfluence(x, z, infl); - // If this is not a field add a negative influence near the CivCentre because we want to leave this - // area for fields. - if (ent.hasClass("CivCentre")){ - friendlyTiles.addInfluence(x, z, infl/8, -infl/2); - } - } - } - }); - } - - // Find target building's approximate obstruction radius, and expand by a bit to make sure we're not too close, this - // allows room for units to walk between buildings. - var radius = Math.ceil(template.obstructionRadius() / cellSize) + 2; - - // Find the best non-obstructed tile - var bestTile = friendlyTiles.findBestTile(radius, obstructionMap); - var bestIdx = bestTile[0]; - var bestVal = bestTile[1]; - - if (bestVal === -1){ - return false; - } - - var x = ((bestIdx % friendlyTiles.width) + 0.5) * cellSize; - var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * cellSize; - - // default angle - var angle = 3*Math.PI/4; - - return { - "x" : x, - "z" : z, - "angle" : angle - }; -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/plan-training.js b/binaries/data/mods/public/simulation/ai/qbot/plan-training.js deleted file mode 100644 index 48688017c6..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/plan-training.js +++ /dev/null @@ -1,56 +0,0 @@ -var UnitTrainingPlan = function(gameState, type, metadata, number) { - this.type = gameState.applyCiv(type); - this.metadata = metadata; - - var template = gameState.getTemplate(this.type); - if (!template) { - this.invalidTemplate = true; - return; - } - this.category= "unit"; - this.cost = new Resources(template.cost(), template._template.Cost.Population); - if (!number){ - this.number = 1; - }else{ - this.number = number; - } -}; - -UnitTrainingPlan.prototype.canExecute = function(gameState) { - if (this.invalidTemplate) - return false; - - // TODO: we should probably check pop caps - - var trainers = gameState.findTrainers(this.type); - - return (trainers.length != 0); -}; - -UnitTrainingPlan.prototype.execute = function(gameState) { - //warn("Executing UnitTrainingPlan " + uneval(this)); - - var trainers = gameState.findTrainers(this.type).toEntityArray(); - - // Prefer training buildings with short queues - // (TODO: this should also account for units added to the queue by - // plans that have already been executed this turn) - if (trainers.length > 0){ - trainers.sort(function(a, b) { - return a.trainingQueueTime() - b.trainingQueueTime(); - }); - - trainers[0].train(this.type, this.number, this.metadata); - } -}; - -UnitTrainingPlan.prototype.getCost = function(){ - var multCost = new Resources(); - multCost.add(this.cost); - multCost.multiply(this.number); - return multCost; -}; - -UnitTrainingPlan.prototype.addItem = function(){ - this.number += 1; -}; \ No newline at end of file diff --git a/binaries/data/mods/public/simulation/ai/qbot/qbot.js b/binaries/data/mods/public/simulation/ai/qbot/qbot.js deleted file mode 100644 index 3b31180798..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/qbot.js +++ /dev/null @@ -1,184 +0,0 @@ -var g_debugEnabled = false; - -function QBotAI(settings) { - BaseAI.call(this, settings); - - this.turn = 0; - - this.Config = new Config(); - - this.modules = { - "economy": new EconomyManager(), - "military": new MilitaryAttackManager(this.Config), - "housing": new HousingManager() - }; - - - // this.queues cannot be modified past initialisation or queue-manager will break - this.queues = { - house : new Queue(), - citizenSoldier : new Queue(), - villager : new Queue(), - economicBuilding : new Queue(), - field : new Queue(), - advancedSoldier : new Queue(), - siege : new Queue(), - militaryBuilding : new Queue(), - defenceBuilding : new Queue(), - civilCentre: new Queue() - }; - - this.productionQueues = []; - - this.priorities = this.Config.priorities; - - this.queueManager = new QueueManager(this.queues, this.priorities); - - this.firstTime = true; - - this.savedEvents = []; -} - -QBotAI.prototype = new BaseAI(); - -//Some modules need the gameState to fully initialise -QBotAI.prototype.runInit = function(gameState){ - if (this.firstTime){ - for (var i in this.modules){ - if (this.modules[i].init){ - this.modules[i].init(gameState); - } - } - - this.timer = new Timer(); - - this.firstTime = false; - - var myKeyEntities = gameState.getOwnEntities().filter(function(ent) { - return ent.hasClass("CivCentre"); - }); - - if (myKeyEntities.length == 0){ - myKeyEntities = gameState.getOwnEntities(); - } - - - var filter = Filters.byClass("CivCentre"); - var enemyKeyEntities = gameState.getEnemyEntities().filter(filter); - - if (enemyKeyEntities.length == 0){ - 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'); - } -}; - -QBotAI.prototype.OnUpdate = function() { - if (this.gameFinished){ - return; - } - - if (this.events.length > 0){ - this.savedEvents = this.savedEvents.concat(this.events); - } - - // Run the update every n turns, offset depending on player ID to balance - // the load - if ((this.turn + this.player) % 10 == 0) { - Engine.ProfileStart("qBot"); - - var gameState = new GameState(this); - - if (gameState.getOwnEntities().length === 0){ - Engine.ProfileStop(); - return; // With no entities to control the AI cannot do anything - } - - this.runInit(gameState); - - for (var i in this.modules){ - this.modules[i].update(gameState, this.queues, this.savedEvents); - } - - this.updateDynamicPriorities(gameState, this.queues); - - this.queueManager.update(gameState); - - // Generate some entropy in the random numbers (against humans) until the engine gets random initialised numbers - // TODO: remove this when the engine gives a random seed - var n = this.savedEvents.length % 29; - for (var i = 0; i < n; i++){ - Math.random(); - } - - delete this.savedEvents; - this.savedEvents = []; - - Engine.ProfileStop(); - } - - this.turn++; -}; - -QBotAI.prototype.updateDynamicPriorities = function(gameState, queues){ - // Dynamically change priorities - Engine.ProfileStart("Change Priorities"); - var females = gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")); - var femalesTarget = this.modules["economy"].targetNumWorkers; - var enemyStrength = this.modules["military"].measureEnemyStrength(gameState); - var availableStrength = this.modules["military"].measureAvailableStrength(); - - var additionalPriority = (enemyStrength - availableStrength) * 5; - additionalPriority = Math.min(Math.max(additionalPriority, -50), 220); - - var advancedProportion = (availableStrength / 40) * (females/femalesTarget); - advancedProportion = Math.min(advancedProportion, 0.7); - - this.priorities.citizenSoldier = (1-advancedProportion) * (150 + additionalPriority) + 1; - this.priorities.advancedSoldier = advancedProportion * (150 + additionalPriority) + 1; - - if (females/femalesTarget > 0.7){ - this.priorities.defenceBuilding = 70; - } - Engine.ProfileStop(); -}; - -// TODO: Remove override when the whole AI state is serialised -QBotAI.prototype.Deserialize = function(data) -{ - BaseAI.prototype.Deserialize.call(this, data); -}; - -// Override the default serializer -QBotAI.prototype.Serialize = function() -{ - var ret = BaseAI.prototype.Serialize.call(this); - ret._entityMetadata = {}; - return ret; -}; - -function debug(output){ - if (g_debugEnabled){ - if (typeof output === "string"){ - warn(output); - }else{ - warn(uneval(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]; - } -} diff --git a/binaries/data/mods/public/simulation/ai/qbot/queue-manager.js b/binaries/data/mods/public/simulation/ai/qbot/queue-manager.js deleted file mode 100644 index a9f6d262c2..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/queue-manager.js +++ /dev/null @@ -1,302 +0,0 @@ -//This takes the input queues and picks which items to fund with resources until no more resources are left to distribute. -// -//In this manager all resources are 'flattened' into a single type=(food+wood+metal+stone+pop*50 (see resources.js)) -//the following refers to this simple as resource -// -// Each queue has an account which records the amount of resource it can spend. If no queue has an affordable item -// then the amount of resource is increased to all accounts in direct proportion to the priority until an item on one -// of the queues becomes affordable. -// -// A consequence of the system is that a rarely used queue will end up with a very large account. I am unsure if this -// is good or bad or neither. -// -// Each queue object has two queues in it, one with items waiting for resources and the other with items which have been -// allocated resources and are due to be executed. The secondary queues are helpful because then units can be trained -// in groups of 5 and buildings are built once per turn to avoid placement clashes. - -var QueueManager = function(queues, priorities) { - this.queues = queues; - this.priorities = priorities; - this.account = {}; - for (var p in this.queues) { - this.account[p] = 0; - } - this.curItemQueue = []; -}; - -QueueManager.prototype.getAvailableResources = function(gameState) { - var resources = gameState.getResources(); - for (var key in this.queues) { - resources.subtract(this.queues[key].outQueueCost()); - } - return resources; -}; - -QueueManager.prototype.futureNeeds = function(gameState) { - // Work out which plans will be executed next using priority and return the total cost of these plans - var recurse = function(queues, qm, number, depth){ - var needs = new Resources(); - var totalPriority = 0; - for (var i = 0; i < queues.length; i++){ - totalPriority += qm.priorities[queues[i]]; - } - for (var i = 0; i < queues.length; i++){ - var num = Math.round(((qm.priorities[queues[i]]/totalPriority) * number)); - if (num < qm.queues[queues[i]].countQueuedUnits()){ - var cnt = 0; - for ( var j = 0; cnt < num; j++) { - cnt += qm.queues[queues[i]].queue[j].number; - needs.add(qm.queues[queues[i]].queue[j].getCost()); - number -= qm.queues[queues[i]].queue[j].number; - } - }else{ - for ( var j = 0; j < qm.queues[queues[i]].length(); j++) { - needs.add(qm.queues[queues[i]].queue[j].getCost()); - number -= qm.queues[queues[i]].queue[j].number; - } - queues.splice(i, 1); - i--; - } - } - // Check that more items were selected this call and that there are plans left to be allocated - // Also there is a fail-safe max depth - if (queues.length > 0 && number > 0 && depth < 20){ - needs.add(recurse(queues, qm, number, depth + 1)); - } - return needs; - }; - - //number of plans to look at - var current = this.getAvailableResources(gameState); - - var futureNum = 20; - var queues = []; - for (var q in this.queues){ - queues.push(q); - } - var needs = recurse(queues, this, futureNum, 0); - // Return predicted values minus the current stockpiles along with a base rater for all resources - return { - "food" : Math.max(needs.food - current.food, 0) + 150, - "wood" : Math.max(needs.wood + 15*needs.population - current.wood, 0) + 150, //TODO: read the house cost in case it changes in the future - "stone" : Math.max(needs.stone - current.stone, 0) + 50, - "metal" : Math.max(needs.metal - current.metal, 0) + 100 - }; -}; - -// runs through the curItemQueue and allocates resources be sending the -// affordable plans to the Out Queues. Returns a list of the unneeded resources -// so they can be used by lower priority plans. -QueueManager.prototype.affordableToOutQueue = function(gameState) { - var availableRes = this.getAvailableResources(gameState); - if (this.curItemQueue.length === 0) { - return availableRes; - } - - var resources = this.getAvailableResources(gameState); - - // Check everything in the curItemQueue, if it is affordable then mark it - // for execution - for ( var i = 0; i < this.curItemQueue.length; i++) { - availableRes.subtract(this.queues[this.curItemQueue[i]].getNext().getCost()); - if (resources.canAfford(this.queues[this.curItemQueue[i]].getNext().getCost())) { - this.account[this.curItemQueue[i]] -= this.queues[this.curItemQueue[i]].getNext().getCost().toInt(); - this.queues[this.curItemQueue[i]].nextToOutQueue(); - resources = this.getAvailableResources(gameState); - this.curItemQueue[i] = null; - } - } - - // Clear the spent items - var tmpQueue = []; - for ( var i = 0; i < this.curItemQueue.length; i++) { - if (this.curItemQueue[i] !== null) { - tmpQueue.push(this.curItemQueue[i]); - } - } - this.curItemQueue = tmpQueue; - - return availableRes; -}; - -QueueManager.prototype.onlyUsesSpareAndUpdateSpare = function(unitCost, spare){ - // This allows plans to be given resources if there are >500 spare after all the - // higher priority plan queues have been looked at and there are still enough resources - // We make it >0 so that even if we have no stone available we can still have non stone - // plans being given resources. - var spareNonNegRes = { - food: Math.max(0, spare.food - 500), - wood: Math.max(0, spare.wood - 500), - stone: Math.max(0, spare.stone - 500), - metal: Math.max(0, spare.metal - 500) - }; - var spareNonNeg = new Resources(spareNonNegRes); - var ret = false; - if (spareNonNeg.canAfford(unitCost)){ - ret = true; - } - - // If there are no negative resources then there weren't any higher priority items so we - // definitely want to say that this can be added to the list. - var tmp = true; - for (var key in spare.types){ - var type = spare.types[key]; - if (spare[type] < 0){ - tmp = false; - } - } - // If either to the above sections returns true then - ret = ret || tmp; - - spare.subtract(unitCost); // take the resources of the current unit from spare since this - // must be higher priority than any which are looked at - // afterwards. - - return ret; -}; - -String.prototype.rpad = function(padString, length) { - var str = this; - while (str.length < length) - str = str + padString; - return str; -}; - -QueueManager.prototype.printQueues = function(gameState){ - debug("OUTQUEUES"); - for (var i in this.queues){ - var qStr = ""; - var q = this.queues[i]; - for (var j in q.outQueue){ - qStr += q.outQueue[j].type + " "; - if (q.outQueue[j].number) - qStr += "x" + q.outQueue[j].number; - } - if (qStr != ""){ - debug((i + ":").rpad(" ", 20) + qStr); - } - } - - debug("INQUEUES"); - for (var i in this.queues){ - var qStr = ""; - var q = this.queues[i]; - for (var j in q.queue){ - qStr += q.queue[j].type + " "; - if (q.queue[j].number) - qStr += "x" + q.queue[j].number; - qStr += " "; - } - if (qStr != ""){ - debug((i + ":").rpad(" ", 20) + qStr); - } - } - debug("Accounts: " + uneval(this.account)); - debug("Needed Resources:" + uneval(this.futureNeeds(gameState))); -}; - -QueueManager.prototype.update = function(gameState) { - - for (var i in this.priorities){ - if (!(this.priorities[i] > 0)){ - this.priorities[i] = 1; // TODO: make the Queue Manager not die when priorities are zero. - warn("QueueManager received bad priorities, please report this error: " + uneval(this.priorities)); - } - } - - Engine.ProfileStart("Queue Manager"); - //this.printQueues(gameState); - - Engine.ProfileStart("Pick items from queues"); - // See if there is a high priority item from last time. - this.affordableToOutQueue(gameState); - do { - // pick out all affordable items, and list the ratios of (needed - // cost)/priority for unaffordable items. - var ratio = {}; - var ratioMin = 1000000; - var ratioMinQueue = undefined; - for (var p in this.queues) { - if (this.queues[p].length() > 0 && this.curItemQueue.indexOf(p) === -1) { - var cost = this.queues[p].getNext().getCost().toInt(); - if (cost < this.account[p]) { - this.curItemQueue.push(p); - // break; - } else { - ratio[p] = (cost - this.account[p]) / this.priorities[p]; - if (ratio[p] < ratioMin) { - ratioMin = ratio[p]; - ratioMinQueue = p; - } - } - } - } - - // Checks to see that there is an item in at least one queue, otherwise - // breaks the loop. - if (this.curItemQueue.length === 0 && ratioMinQueue === undefined) { - break; - } - - var availableRes = this.affordableToOutQueue(gameState); - - var allSpare = availableRes["food"] > 0 && availableRes["wood"] > 0 && availableRes["stone"] > 0 && availableRes["metal"] > 0; - // if there are no affordable items use any resources which aren't - // wanted by a higher priority item - if ((availableRes["food"] > 0 || availableRes["wood"] > 0 || availableRes["stone"] > 0 || availableRes["metal"] > 0) - && ratioMinQueue !== undefined) { - while (Object.keys(ratio).length > 0 && (availableRes["food"] > 0 || availableRes["wood"] > 0 || availableRes["stone"] > 0 || availableRes["metal"] > 0)){ - ratioMin = Math.min(); //biggest value - for (var key in ratio){ - if (ratio[key] < ratioMin){ - ratioMin = ratio[key]; - ratioMinQueue = key; - } - } - if (this.onlyUsesSpareAndUpdateSpare(this.queues[ratioMinQueue].getNext().getCost(), availableRes)){ - if (allSpare){ - for (var p in this.queues) { - this.account[p] += ratioMin * this.priorities[p]; - } - } - //this.account[ratioMinQueue] -= this.queues[ratioMinQueue].getNext().getCost().toInt(); - this.curItemQueue.push(ratioMinQueue); - allSpare = availableRes["food"] > 0 && availableRes["wood"] > 0 && availableRes["stone"] > 0 && availableRes["metal"] > 0; - } - delete ratio[ratioMinQueue]; - } - - } - this.affordableToOutQueue(gameState); - } while (this.curItemQueue.length === 0); - Engine.ProfileStop(); - - Engine.ProfileStart("Execute items"); - - // Handle output queues by executing items where possible - for (var p in this.queues) { - while (this.queues[p].outQueueLength() > 0) { - var next = this.queues[p].outQueueNext(); - if (next.category === "building") { - if (gameState.buildingsBuilt == 0) { - if (this.queues[p].outQueueNext().canExecute(gameState)) { - this.queues[p].executeNext(gameState); - gameState.buildingsBuilt += 1; - } else { - break; - } - } else { - break; - } - } else { - if (this.queues[p].outQueueNext().canExecute(gameState)){ - this.queues[p].executeNext(gameState); - }else{ - break; - } - } - } - } - Engine.ProfileStop(); - Engine.ProfileStop(); -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/queue.js b/binaries/data/mods/public/simulation/ai/qbot/queue.js deleted file mode 100644 index 7dd3e2ab0b..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/queue.js +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Holds a list of wanted items to train or construct - */ - -var Queue = function() { - this.queue = []; - this.outQueue = []; -}; - -Queue.prototype.addItem = function(plan) { - this.queue.push(plan); -}; - -Queue.prototype.getNext = function() { - if (this.queue.length > 0) { - return this.queue[0]; - } else { - return null; - } -}; - -Queue.prototype.outQueueNext = function(){ - if (this.outQueue.length > 0) { - return this.outQueue[0]; - } else { - return null; - } -}; - -Queue.prototype.outQueueCost = function(){ - var cost = new Resources(); - for (var key in this.outQueue){ - cost.add(this.outQueue[key].getCost()); - } - return cost; -}; - -Queue.prototype.nextToOutQueue = function(){ - if (this.queue.length > 0){ - if (this.outQueue.length > 0 && - this.getNext().category === "unit" && - this.outQueue[this.outQueue.length-1].type === this.getNext().type && - this.outQueue[this.outQueue.length-1].number < 5){ - this.queue.shift(); - this.outQueue[this.outQueue.length-1].addItem(); - }else{ - this.outQueue.push(this.queue.shift()); - } - } -}; - -Queue.prototype.executeNext = function(gameState) { - if (this.outQueue.length > 0) { - this.outQueue.shift().execute(gameState); - return true; - } else { - return false; - } -}; - -Queue.prototype.length = function() { - return this.queue.length; -}; - -Queue.prototype.countQueuedUnits = function(){ - var count = 0; - for (var i in this.queue){ - count += this.queue[i].number; - } - return count; -}; - -Queue.prototype.countOutQueuedUnits = function(){ - var count = 0; - for (var i in this.outQueue){ - count += this.outQueue[i].number; - } - return count; -}; - -Queue.prototype.countTotalQueuedUnits = function(){ - var count = 0; - for (var i in this.queue){ - count += this.queue[i].number; - } - for (var i in this.outQueue){ - count += this.outQueue[i].number; - } - return count; -}; - -Queue.prototype.totalLength = function(){ - return this.queue.length + this.outQueue.length; -}; - -Queue.prototype.outQueueLength = function(){ - return this.outQueue.length; -}; - -Queue.prototype.countAllByType = function(t){ - var count = 0; - - for (var i = 0; i < this.queue.length; i++){ - if (this.queue[i].type === t){ - count += this.queue[i].number; - } - } - for (var i = 0; i < this.outQueue.length; i++){ - if (this.outQueue[i].type === t){ - count += this.outQueue[i].number; - } - } - return count; -}; \ No newline at end of file diff --git a/binaries/data/mods/public/simulation/ai/qbot/readme.txt b/binaries/data/mods/public/simulation/ai/qbot/readme.txt deleted file mode 100644 index a788d33032..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/readme.txt +++ /dev/null @@ -1,6 +0,0 @@ -This is an AI for 0 A.D. (http://play0ad.com/) based on the testBot. - -Install by placing the files into the data/mods/public/simulation/ai/qbot folder. - -If you are developing you might find it helpful to change the debugOn line in qBot.js. This will make it spew random warnings depending on what I have been working on. Use the debug() function to make your own warnings. - diff --git a/binaries/data/mods/public/simulation/ai/qbot/resources.js b/binaries/data/mods/public/simulation/ai/qbot/resources.js deleted file mode 100644 index 4f00c7de84..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/resources.js +++ /dev/null @@ -1,66 +0,0 @@ -function Resources(amounts, population) { - if (amounts === undefined) { - amounts = { - food : 0, - wood : 0, - stone : 0, - metal : 0 - }; - } - for ( var tKey in this.types) { - var t = this.types[tKey]; - this[t] = amounts[t] || 0; - } - - if (population > 0) { - this.population = parseInt(population); - } else { - this.population = 0; - } -} - -Resources.prototype.types = [ "food", "wood", "stone", "metal" ]; - -Resources.prototype.canAfford = function(that) { - for ( var tKey in this.types) { - var t = this.types[tKey]; - if (this[t] < that[t]) { - return false; - } - } - return true; -}; - -Resources.prototype.add = function(that) { - for ( var tKey in this.types) { - var t = this.types[tKey]; - this[t] += that[t]; - } - this.population += that.population; -}; - -Resources.prototype.subtract = function(that) { - for ( var tKey in this.types) { - var t = this.types[tKey]; - this[t] -= that[t]; - } - this.population += that.population; -}; - -Resources.prototype.multiply = function(n) { - for ( var tKey in this.types) { - var t = this.types[tKey]; - this[t] *= n; - } - this.population *= n; -}; - -Resources.prototype.toInt = function() { - var sum = 0; - for ( var tKey in this.types) { - var t = this.types[tKey]; - sum += this[t]; - } - sum += this.population * 50; // based on typical unit costs - return sum; -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/terrain-analysis.js b/binaries/data/mods/public/simulation/ai/qbot/terrain-analysis.js deleted file mode 100644 index 8e9904035f..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/terrain-analysis.js +++ /dev/null @@ -1,350 +0,0 @@ -/* - * TerrainAnalysis inherits from Map - * - * This creates a suitable passability map for pathfinding units and provides the findClosestPassablePoint() function. - * This is intended to be a base object for the terrain analysis modules to inherit from. - */ - -function TerrainAnalysis(gameState){ - var passabilityMap = gameState.getMap(); - - var obstructionMask = gameState.getPassabilityClassMask("pathfinderObstruction"); - obstructionMask |= gameState.getPassabilityClassMask("default"); - - var obstructionTiles = new Uint16Array(passabilityMap.data.length); - for (var i = 0; i < passabilityMap.data.length; ++i) - { - obstructionTiles[i] = (passabilityMap.data[i] & obstructionMask) ? 0 : 65535; - } - - this.Map(gameState, obstructionTiles); -}; - -copyPrototype(TerrainAnalysis, Map); - -// Returns the (approximately) closest point which is passable by searching in a spiral pattern -TerrainAnalysis.prototype.findClosestPassablePoint = function(startPoint, quick, limitDistance){ - var w = this.width; - var p = startPoint; - var direction = 1; - - if (p[0] + w*p[1] > 0 && p[0] + w*p[1] < this.length && - this.map[p[0] + w*p[1]] != 0){ - if (this.countConnected(p, 10) >= 10){ - return p; - } - } - - var count = 0; - // search in a spiral pattern. - for (var i = 1; i < w; i++){ - for (var j = 0; j < 2; j++){ - for (var k = 0; k < i; k++){ - p[j] += direction; - if (p[0] + w*p[1] > 0 && p[0] + w*p[1] < this.length && - this.map[p[0] + w*p[1]] != 0){ - if (quick || this.countConnected(p, 10) >= 10){ - return p; - } - } - if (limitDistance && count > 40){ - return undefined; - } - count += 1; - } - } - direction *= -1; - } - - return undefined; -}; - -// Counts how many accessible tiles there are connected to the start Point. If there are >= maxCount then it stops. -// This is inefficient for large areas so maxCount should be kept small for efficiency. -TerrainAnalysis.prototype.countConnected = function(startPoint, maxCount, curCount, checked){ - curCount = curCount || 0; - checked = checked || []; - - var w = this.width; - - var positions = [[0,1], [0,-1], [1,0], [-1,0]]; - - curCount += 1; // add 1 for the current point - checked.push(startPoint); - if (curCount >= maxCount){ - return curCount; - } - - for (var i in positions){ - var p = [startPoint[0] + positions[i][0], startPoint[1] + positions[i][1]]; - if (p[0] + w*p[1] > 0 && p[0] + w*p[1] < this.length && - this.map[p[0] + w*p[1]] != 0 && !(p in checked)){ - curCount += this.countConnected(p, maxCount, curCount, checked); - } - } - return curCount; -}; - -/* - * PathFinder inherits from TerrainAnalysis - * - * Used to create a list of distinct paths between two points. - * - * Currently it works with a basic implementation which should be improved. - * - * TODO: Make this use territories. - */ - - -function PathFinder(gameState){ - this.TerrainAnalysis(gameState); -} - -copyPrototype(PathFinder, TerrainAnalysis); - -/* - * Returns a list of distinct paths to the destination. Currently paths are distinct if they are more than - * blockRadius apart at a distance of blockPlacementRadius from the destination. Where blockRadius and - * blockPlacementRadius are defined in walkGradient - */ -PathFinder.prototype.getPaths = function(start, end, mode){ - var s = this.findClosestPassablePoint(this.gamePosToMapPos(start)); - var e = this.findClosestPassablePoint(this.gamePosToMapPos(end)); - - if (!s || !e){ - return undefined; - } - - var paths = []; - while (true){ - this.makeGradient(s,e); - var curPath = this.walkGradient(e, mode); - - if (curPath !== undefined){ - paths.push(curPath); - }else{ - break; - } - - this.wipeGradient(); - } - - //this.dumpIm("terrainanalysis.png", 511); - - if (paths.length > 0){ - return paths; - }else{ - return undefined; - } -}; - -// Creates a potential gradient with the start point having the lowest potential -PathFinder.prototype.makeGradient = function(start, end){ - var w = this.width; - var map = this.map; - - // Holds the list of current points to work outwards from - var stack = []; - // We store the next level in its own stack - var newStack = []; - // Relative positions or new cells from the current one. We alternate between the adjacent 4 and 8 cells - // so that there is an average 1.5 distance for diagonals which is close to the actual sqrt(2) ~ 1.41 - var positions = [[[0,1], [0,-1], [1,0], [-1,0]], - [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]]]; - - //Set the distance of the start point to be 1 to distinguish it from the impassable areas - map[start[0] + w*(start[1])] = 1; - stack.push(start); - - // while there are new points being added to the stack - while (stack.length > 0){ - //run through the current stack - while (stack.length > 0){ - var cur = stack.pop(); - // stop when we reach the end point - if (cur[0] == end[0] && cur[1] == end[1]){ - return; - } - - var dist = map[cur[0] + w*(cur[1])] + 1; - // Check the positions adjacent to the current cell - for (var i = 0; i < positions[dist % 2].length; i++){ - var pos = positions[dist % 2][i]; - var cell = cur[0]+pos[0] + w*(cur[1]+pos[1]); - if (cell >= 0 && cell < this.length && map[cell] > dist){ - map[cell] = dist; - newStack.push([cur[0]+pos[0], cur[1]+pos[1]]); - } - } - } - // Replace the old empty stack with the newly filled one. - stack = newStack; - newStack = []; - } - -}; - -// Clears the map to just have the obstructions marked on it. -PathFinder.prototype.wipeGradient = function(){ - for (var i = 0; i < this.length; i++){ - if (this.map[i] > 0){ - this.map[i] = 65535; - } - } -}; - -// Returns the path down a gradient from the start to the bottom of the gradient, returns a point for every 20 cells in normal mode -// in entryPoints mode this returns the point where the path enters the region near the destination, currently defined -// by blockPlacementRadius. Note doesn't return a path when the destination is within the blockpoint radius. -PathFinder.prototype.walkGradient = function(start, mode){ - var positions = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [-1,-1], [1,-1], [-1,1]]; - - var path = [[start[0]*this.cellSize, start[1]*this.cellSize]]; - - var blockPoint = undefined; - var blockPlacementRadius = 45; - var blockRadius = 23; - var count = 0; - - var cur = start; - var w = this.width; - var dist = this.map[cur[0] + w*cur[1]]; - var moved = false; - while (this.map[cur[0] + w*cur[1]] !== 0){ - for (var i = 0; i < positions.length; i++){ - var pos = positions[i]; - var cell = cur[0]+pos[0] + w*(cur[1]+pos[1]); - if (cell >= 0 && cell < this.length && this.map[cell] > 0 && this.map[cell] < dist){ - dist = this.map[cell]; - cur = [cur[0]+pos[0], cur[1]+pos[1]]; - moved = true; - count++; - // Mark the point to put an obstruction at before calculating the next path - if (count === blockPlacementRadius){ - blockPoint = cur; - } - // Add waypoints to the path, fairly well spaced apart. - if (count % 40 === 0){ - path.unshift([cur[0]*this.cellSize, cur[1]*this.cellSize]); - } - break; - } - } - if (!moved){ - break; - } - moved = false; - } - if (blockPoint === undefined){ - return undefined; - } - - // Add an obstruction to the map at the blockpoint so the next path will take a different route. - this.addInfluence(blockPoint[0], blockPoint[1], blockRadius, -1000000, 'constant'); - if (mode === 'entryPoints'){ - // returns the point where the path enters the blockPlacementRadius - return [blockPoint[0] * this.cellSize, blockPoint[1] * this.cellSize]; - }else{ - // return a path of points 20 squares apart on the route - return path; - } -}; - -// Would be used to calculate the width of a chokepoint -// NOTE: Doesn't currently work. -PathFinder.prototype.countAttached = function(pos){ - var positions = [[0,1], [0,-1], [1,0], [-1,0]]; - var w = this.width; - var val = this.map[pos[0] + w*pos[1]]; - - var stack = [pos]; - var used = {}; - - while (stack.length > 0){ - var cur = stack.pop(); - used[cur[0] + " " + cur[1]] = true; - for (var i = 0; i < positions.length; i++){ - var p = positions[i]; - var cell = cur[0]+p[0] + w*(cur[1]+p[1]); - - } - } -}; - -/* - * Accessibility inherits from TerrainAnalysis - * - * Determines whether there is a path from one point to another. It is initialised with a single point (p1) and then - * can efficiently determine if another point is reachable from p1. Initialising the object is costly so it should be - * cached. - */ - -function Accessibility(gameState, location){ - this.TerrainAnalysis(gameState); - - var start = this.findClosestPassablePoint(this.gamePosToMapPos(location)); - - // Check that the accessible region is a decent size, otherwise obstacles close to the start point can create - // tiny accessible areas which makes the rest of the map inaceesible. - var iterations = 0; - while (this.floodFill(start) < 20 && iterations < 30){ - this.map[start[0] + this.width*(start[1])] = 0; - start = this.findClosestPassablePoint(this.gamePosToMapPos(location)); - iterations += 1; - } - -} - -copyPrototype(Accessibility, TerrainAnalysis); - -// Return true if the given point is accessible from the point given when initialising the Accessibility object. # -// If the given point is impassable the closest passable point is used. -Accessibility.prototype.isAccessible = function(position){ - var s = this.findClosestPassablePoint(this.gamePosToMapPos(position), true, true); - if (!s) - return false; - - return this.map[s[0] + this.width * s[1]] === 1; -}; - -// fill all of the accessible areas with value 1 -Accessibility.prototype.floodFill = function(start){ - var w = this.width; - var map = this.map; - - // Holds the list of current points to work outwards from - var stack = []; - // We store new points to be added to the stack temporarily in here while we run through the current stack - var newStack = []; - // Relative positions or new cells from the current one. - var positions = [[0,1], [0,-1], [1,0], [-1,0]]; - - // Set the start point to be accessible - map[start[0] + w*(start[1])] = 1; - stack.push(start); - - var count = 0; - - // while there are new points being added to the stack - while (stack.length > 0){ - //run through the current stack - while (stack.length > 0){ - var cur = stack.pop(); - - // Check the positions adjacent to the current cell - for (var i = 0; i < positions.length; i++){ - var pos = positions[i]; - var cell = cur[0]+pos[0] + w*(cur[1]+pos[1]); - if (cell >= 0 && cell < this.length && map[cell] > 1){ - map[cell] = 1; - newStack.push([cur[0]+pos[0], cur[1]+pos[1]]); - count += 1; - } - } - } - // Replace the old empty stack with the newly filled one. - stack = newStack; - newStack = []; - } - return count; -}; \ No newline at end of file diff --git a/binaries/data/mods/public/simulation/ai/qbot/timer.js b/binaries/data/mods/public/simulation/ai/qbot/timer.js deleted file mode 100644 index fea000ede9..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/timer.js +++ /dev/null @@ -1,104 +0,0 @@ -//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). -// 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 // -var Timer = function() { - ///Private array. - var alarmList = []; - - ///Private methods - function num_alarms() { - return alarmList.length; - }; - - function get_alarm(id) { - return alarmList[id]; - }; - - function add_alarm(index, alarm) { - alarmList[index] = alarm; - }; - - function delete_alarm(id) { - // Set the array element to undefined - delete alarmList[id]; - }; - - ///Privileged methods - // Add an new alarm to the list - this.setTimer = function(gameState, interval, delay, repeat) { - delay = delay || 0; - repeat = repeat || -1; - - var index = num_alarms(); - - //Add a new alarm to the list - add_alarm(index, new alarm(gameState, index, interval, delay, repeat)); - return index; - }; - - - // Check if a alarm has reached its interval. - this.checkTimer = function(gameState,id) { - var alarm = get_alarm(id); - if (alarm === undefined) - return false; - if (!alarm.active) - return false; - var time = gameState.getTimeElapsed(); - var alarmState = false; - - // If repeat forever (repeat is -1). Or if the alarm has rung less times than repeat. - if (alarm.repeat < 0 || alarm.counter < alarm.repeat) { - var time_diffrence = time - alarm.start_time - alarm.delay - alarm.interval * alarm.counter; - if (time_diffrence > alarm.interval) { - alarmState = true; - alarm.counter++; - } - } - - // Check if the alarm has rung 'alarm.repeat' times if so, delete the alarm. - if (alarm.counter >= alarm.repeat && alarm.repeat != -1) { - this.clearTimer(id); - } - - return alarmState; - }; - - // Remove an alarm from the list. - this.clearTimer = function(id) { - delete_alarm(id); - }; - - // Activate a deactivated alarm. - this.activateTimer = function(id) { - var alarm = get_alarm(id); - alarm.active = true; - }; - - // Deactivate an active alarm but don't delete it. - this.deactivateTimer = function(id) { - var alarm = get_alarm(id); - alarm.active = false; - }; -}; - - -//-EmjeR-// Alarm class // -function alarm(gameState, id, interval, delay, repeat) { - this.id = id; - this.interval = interval; - this.delay = delay; - this.repeat = repeat; - - this.start_time = gameState.getTimeElapsed(); - this.active = true; - this.counter = 0; -}; diff --git a/binaries/data/mods/public/simulation/ai/qbot/walkToCC.js b/binaries/data/mods/public/simulation/ai/qbot/walkToCC.js deleted file mode 100644 index 3237b2ab2d..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/walkToCC.js +++ /dev/null @@ -1,85 +0,0 @@ -var WalkToCC = function(gameState, militaryManager){ - this.minAttackSize = 20; - this.maxAttackSize = 60; - this.idList=[]; -}; - -// Returns true if the attack can be executed at the current time -WalkToCC.prototype.canExecute = function(gameState, militaryManager){ - var enemyStrength = militaryManager.measureEnemyStrength(gameState); - var enemyCount = militaryManager.measureEnemyCount(gameState); - - // We require our army to be >= this strength - var targetStrength = enemyStrength * 1.5; - - var availableCount = militaryManager.countAvailableUnits(); - var availableStrength = militaryManager.measureAvailableStrength(); - - debug("Troops needed for attack: " + this.minAttackSize + " Have: " + availableCount); - debug("Troops strength for attack: " + targetStrength + " Have: " + availableStrength); - - return ((availableStrength >= targetStrength && availableCount >= this.minAttackSize) - || availableCount >= this.maxAttackSize); -}; - -// Executes the attack plan, after this is executed the update function will be run every turn -WalkToCC.prototype.execute = function(gameState, militaryManager){ - var availableCount = militaryManager.countAvailableUnits(); - this.idList = militaryManager.getAvailableUnits(availableCount); - - var pending = EntityCollectionFromIds(gameState, this.idList); - - // Find the critical enemy buildings we could attack - var targets = militaryManager.getEnemyBuildings(gameState,"ConquestCritical"); - // If there are no critical structures, attack anything else that's critical - if (targets.length == 0) { - targets = gameState.entities.filter(function(ent) { - return (gameState.isEntityEnemy(ent) && ent.hasClass("ConquestCritical") && ent.owner() !== 0); - }); - } - // If there's nothing, attack anything else that's less critical - if (targets.length == 0) { - targets = militaryManager.getEnemyBuildings(gameState,"Town"); - } - if (targets.length == 0) { - targets = militaryManager.getEnemyBuildings(gameState,"Village"); - } - - // If we have a target, move to it - if (targets.length) { - // Remove the pending role - pending.forEach(function(ent) { - ent.setMetadata("role", "attack"); - }); - - var target = targets.toEntityArray()[0]; - var targetPos = target.position(); - - // TODO: this should be an attack-move command - pending.move(targetPos[0], targetPos[1]); - } else if (targets.length == 0 ) { - gameState.ai.gameFinished = true; - } -}; - -// Runs every turn after the attack is executed -// This removes idle units from the attack -WalkToCC.prototype.update = function(gameState, militaryManager){ - var removeList = []; - for (var idKey in this.idList){ - var id = this.idList[idKey]; - var ent = militaryManager.entity(id); - if(ent) - { - if(ent.isIdle()) { - militaryManager.unassignUnit(id); - removeList.push(id); - } - } else { - removeList.push(id); - } - } - for (var i in removeList){ - this.idList.splice(this.idList.indexOf(removeList[i]),1); - } -}; \ No newline at end of file diff --git a/binaries/data/mods/public/simulation/ai/qbot/worker.js b/binaries/data/mods/public/simulation/ai/qbot/worker.js deleted file mode 100644 index 2bb4fc0f87..0000000000 --- a/binaries/data/mods/public/simulation/ai/qbot/worker.js +++ /dev/null @@ -1,251 +0,0 @@ -/** - * This class makes a worker do as instructed by the economy manager - */ - -var Worker = function(ent) { - this.ent = ent; - this.approachCount = 0; -}; - -Worker.prototype.update = function(gameState) { - var subrole = this.ent.getMetadata("subrole"); - - if (!this.ent.position()){ - // If the worker has no position then no work can be done - return; - } - - if (subrole === "gatherer"){ - if (!(this.ent.unitAIState().split(".")[1] === "GATHER" && this.ent.unitAIOrderData()[0].type - && this.getResourceType(this.ent.unitAIOrderData()[0].type) === this.ent.getMetadata("gather-type")) - && !(this.ent.unitAIState().split(".")[1] === "RETURNRESOURCE")){ - // TODO: handle combat for hunting animals - if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 || - this.ent.resourceCarrying()[0].type === this.ent.getMetadata("gather-type")){ - Engine.ProfileStart("Start Gathering"); - this.startGathering(gameState); - Engine.ProfileStop(); - } else if (this.ent.unitAIState().split(".")[1] !== "RETURNRESOURCE") { - // Should deposit resources - Engine.ProfileStart("Return Resources"); - this.returnResources(gameState); - Engine.ProfileStop(); - } - - this.startApproachingResourceTime = gameState.getTimeElapsed(); - - //Engine.PostCommand(PlayerID, {"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [10,0,0]}); - }else{ - // If we haven't reached the resource in 2 minutes twice in a row and none of the resource has been - // gathered then mark it as inaccessible. - if (gameState.getTimeElapsed() - this.startApproachingResourceTime > 120000){ - if (this.gatheringFrom){ - var ent = gameState.getEntityById(this.gatheringFrom); - if (ent && ent.resourceSupplyAmount() == ent.resourceSupplyMax()){ - if (this.approachCount > 0){ - ent.setMetadata("inaccessible", true); - this.ent.setMetadata("subrole", "idle"); - } - this.approachCount++; - }else{ - this.approachCount = 0; - } - - this.startApproachingResourceTime = gameState.getTimeElapsed(); - } - } - } - }else if(subrole === "builder"){ - if (this.ent.unitAIState().split(".")[1] !== "REPAIR"){ - var target = this.ent.getMetadata("target-foundation"); - this.ent.repair(target); - } - - //Engine.PostCommand(PlayerID, {"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [0,10,0]}); - } - - Engine.ProfileStart("Update Gatherer Counts"); - this.updateGathererCounts(gameState); - Engine.ProfileStop(); -}; - -Worker.prototype.updateGathererCounts = function(gameState, dead){ - // update gatherer counts for the resources - if (this.ent.unitAIState().split(".")[2] === "GATHERING" && !dead){ - if (this.gatheringFrom !== this.ent.unitAIOrderData()[0].target){ - if (this.gatheringFrom){ - var ent = gameState.getEntityById(this.gatheringFrom); - if (ent){ - ent.setMetadata("gatherer-count", ent.getMetadata("gatherer-count") - 1); - this.markFull(ent); - } - } - this.gatheringFrom = this.ent.unitAIOrderData()[0].target; - if (this.gatheringFrom){ - var ent = gameState.getEntityById(this.gatheringFrom); - if (ent){ - ent.setMetadata("gatherer-count", (ent.getMetadata("gatherer-count") || 0) + 1); - this.markFull(ent); - } - } - } - }else{ - if (this.gatheringFrom){ - var ent = gameState.getEntityById(this.gatheringFrom); - if (ent){ - ent.setMetadata("gatherer-count", ent.getMetadata("gatherer-count") - 1); - this.markFull(ent); - } - this.gatheringFrom = undefined; - } - } -}; - -Worker.prototype.markFull = function(ent){ - var maxCounts = {"food": 20, "wood": 5, "metal": 20, "stone": 20, "treasure": 1}; - if (ent.resourceSupplyType() && ent.getMetadata("gatherer-count") >= maxCounts[ent.resourceSupplyType().generic]){ - if (!ent.getMetadata("full")){ - ent.setMetadata("full", true); - } - }else{ - if (ent.getMetadata("full")){ - ent.setMetadata("full", false); - } - } -}; - -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.position(), dropsite.position()); - if (dist < minDropsiteDist){ - minDropsiteDist = dist; - nearestResources = dropsite.getMetadata("nearby-resources-" + resource); - nearestDropsite = dropsite; - } - } - }); - - if (!nearestResources || nearestResources.length === 0){ - nearestResources = gameState.getResourceSupplies(resource); - gameState.getOwnDropsites(resource).forEach(function (dropsite){ - if (dropsite.position()){ - var dist = VectorDistance(ent.position(), dropsite.position()); - if (dist < minDropsiteDist){ - minDropsiteDist = dist; - nearestDropsite = dropsite; - } - } - }); - } - - if (nearestResources.length === 0){ - debug("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; - } - - if (supply.isFull() === true) { - 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 < 1000 && supply.resourceSupplyType().generic == "treasure"){ - dist /= 1000; - } - - if (dist < nearestSupplyDist){ - nearestSupplyDist = dist; - nearestSupply = supply; - } - }); - - if (nearestSupply) { - var pos = nearestSupply.position(); - var territoryOwner = gameState.getTerritoryMap().getOwner(pos); - if (!gameState.ai.accessibility.isAccessible(pos) || - (territoryOwner != gameState.getPlayerID() && territoryOwner != 0)){ - nearestSupply.setMetadata("inaccessible", true); - }else{ - ent.gather(nearestSupply); - } - }else{ - debug("No " + resource + " found! (2)"); - } -}; - -// Makes the worker deposit the currently carried resources at the closest dropsite -Worker.prototype.returnResources = function(gameState){ - if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0){ - return; - } - var resource = this.ent.resourceCarrying()[0].type; - var self = this; - - if (!this.ent.position()){ - // TODO: work out what to do when entity has no position - return; - } - - var closestDropsite = undefined; - var dist = Math.min(); - gameState.getOwnDropsites(resource).forEach(function(dropsite){ - if (dropsite.position()){ - var d = VectorDistance(self.ent.position(), dropsite.position()); - if (d < dist){ - dist = d; - closestDropsite = dropsite; - } - } - }); - - if (!closestDropsite){ - debug("No dropsite found for " + resource); - return; - } - - this.ent.returnResources(closestDropsite); -}; - -Worker.prototype.getResourceType = function(type){ - if (!type || !type.generic){ - return undefined; - } - - if (type.generic === "treasure"){ - return type.specific; - }else{ - return type.generic; - } -}; diff --git a/binaries/data/mods/public/simulation/ai/scaredybot/data.json b/binaries/data/mods/public/simulation/ai/scaredybot/data.json deleted file mode 100644 index ac3e91845d..0000000000 --- a/binaries/data/mods/public/simulation/ai/scaredybot/data.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Scaredy Bot", - "description": "An AI that is terrified by the mere possibility of having to fight.", - "constructor": "ScaredyBotAI", - "hidden": true -} diff --git a/binaries/data/mods/public/simulation/ai/scaredybot/scaredybot.js b/binaries/data/mods/public/simulation/ai/scaredybot/scaredybot.js deleted file mode 100644 index 03a67dd834..0000000000 --- a/binaries/data/mods/public/simulation/ai/scaredybot/scaredybot.js +++ /dev/null @@ -1,28 +0,0 @@ -Engine.IncludeModule("common-api"); - -function ScaredyBotAI(settings) -{ -// warn("Constructing ScaredyBotAI for player "+settings.player); - - BaseAI.call(this, settings); - - this.turn = 0; - this.suicideTurn = 20; -} - -ScaredyBotAI.prototype = new BaseAI(); - -ScaredyBotAI.prototype.OnUpdate = function() -{ - if (this.turn == 0) - this.chat("Good morning."); - - if (this.turn == this.suicideTurn) - { - this.chat("I quake in my boots! My troops cannot hope to survive against a power such as yours."); - - this.entities.filter(function(ent) { return ent.isOwn(); }).destroy(); - } - - this.turn++; -}; diff --git a/binaries/data/mods/public/simulation/ai/testbot/_init.js b/binaries/data/mods/public/simulation/ai/testbot/_init.js deleted file mode 100644 index ef146454ab..0000000000 --- a/binaries/data/mods/public/simulation/ai/testbot/_init.js +++ /dev/null @@ -1 +0,0 @@ -Engine.IncludeModule("common-api"); diff --git a/binaries/data/mods/public/simulation/ai/testbot/data.json b/binaries/data/mods/public/simulation/ai/testbot/data.json deleted file mode 100644 index c36f84b66d..0000000000 --- a/binaries/data/mods/public/simulation/ai/testbot/data.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Demo Bot", - "description": "A simple opponent, mainly designed for testing our AI scripting framework.", - "constructor": "TestBotAI", - "hidden": true -} diff --git a/binaries/data/mods/public/simulation/ai/testbot/economy.js b/binaries/data/mods/public/simulation/ai/testbot/economy.js deleted file mode 100644 index 99be5de422..0000000000 --- a/binaries/data/mods/public/simulation/ai/testbot/economy.js +++ /dev/null @@ -1,242 +0,0 @@ -var EconomyManager = Class({ - - _init: function() - { - this.targetNumWorkers = 30; // minimum number of workers we want - this.targetNumBuilders = 5; // number of workers we want working on construction - - // (This is a stupid design where we just construct certain numbers - // of certain buildings in sequence) - this.targetBuildings = [ - { - "template": "structures/{civ}_civil_centre", - "priority": 500, - "count": 1, - }, - { - "template": "structures/{civ}_house", - "priority": 100, - "count": 5, - }, - { - "template": "structures/{civ}_barracks", - "priority": 75, - "count": 1, - }, - { - "template": "structures/{civ}_field", - "priority": 50, - "count": 5, - }, - ]; - - // Relative proportions of workers to assign to each resource type - this.gatherWeights = { - "food": 150, - "wood": 100, - "stone": 50, - "metal": 100, - }; - }, - - buildMoreBuildings: function(gameState, planGroups) - { - // Limit ourselves to constructing one building at a time - if (gameState.findFoundations().length) - return; - - for each (var building in this.targetBuildings) - { - var numBuildings = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv(building.template)); - - // If we have too few, build another - if (numBuildings < building.count) - { - planGroups.economyConstruction.addPlan(building.priority, - new BuildingConstructionPlan(gameState, building.template) - ); - } - } - }, - - trainMoreWorkers: function(gameState, planGroups) - { - // Count the workers in the world and in progress - var numWorkers = gameState.countEntitiesAndQueuedWithRole("worker"); - - // If we have too few, train another -// print("Want "+this.targetNumWorkers+" workers; got "+numWorkers+"\n"); - if (numWorkers < this.targetNumWorkers) - { - planGroups.economyPersonnel.addPlan(100, - new UnitTrainingPlan(gameState, - "units/{civ}_support_female_citizen", 1, { "role": "worker" }) - ); - } - }, - - pickMostNeededResources: function(gameState) - { - var self = this; - - // Find what resource type we're most in need of - 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; - }); - - var types = Object.keys(this.gatherWeights); - types.sort(function(a, b) { - // Prefer fewer gatherers (divided by weight) - var va = numGatherers[a] / self.gatherWeights[a]; - var vb = numGatherers[b] / self.gatherWeights[b]; - return va - vb; - }); - - return types; - }, - - reassignRolelessUnits: function(gameState) - { - var roleless = gameState.getOwnEntitiesWithRole(undefined); - - roleless.forEach(function(ent) { - if (ent.hasClass("Worker")) - ent.setMetadata("role", "worker"); - else - ent.setMetadata("role", "unknown"); - }); - }, - - reassignIdleWorkers: function(gameState, planGroups) - { - var self = this; - - // Search for idle workers, and tell them to gather resources - // Maybe just pick a random nearby resource type at first; - // later we could add some timer that redistributes workers based on - // resource demand. - - var idleWorkers = gameState.getOwnEntitiesWithRole("worker").filter(function(ent) { - return ent.isIdle(); - }); - - if (idleWorkers.length) - { - var resourceSupplies = gameState.findResourceSupplies(); - - idleWorkers.forEach(function(ent) { - - var types = self.pickMostNeededResources(gameState); - for each (var type in types) - { - // Make sure there's actually some of that type - if (!resourceSupplies[type]) - continue; - - // Pick the closest one. - // TODO: we should care about distance to dropsites, not (just) to the worker, - // and gather rates of workers - - var workerPosition = ent.position(); - var supplies = []; - resourceSupplies[type].forEach(function(supply) { - // Skip targets that are too hard to hunt - if (supply.entity.isUnhuntable()) - return; - - var dist = VectorDistance(supply.position, workerPosition); - - // Skip targets that are far too far away (e.g. in the enemy base) - if (dist > 512) - 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 false; - }); - - // Start gathering - if (supplies.length) - { - ent.gather(supplies[0].entity); - ent.setMetadata("subrole", "gatherer"); - ent.setMetadata("gather-type", type); - return; - } - } - - // Couldn't find any types to gather - ent.setMetadata("subrole", "idle"); - }); - } - }, - - assignToFoundations: function(gameState, planGroups) - { - // 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(); - - // Check if nothing to build - if (!foundations.length) - return; - - var workers = gameState.getOwnEntitiesWithRole("worker"); - - var builderWorkers = workers.filter(function(ent) { - return (ent.getMetadata("subrole") === "builder"); - }); - - // Check if enough builders - var extraNeeded = this.targetNumBuilders - builderWorkers.length; - if (extraNeeded <= 0) - return; - - // Pick non-builders who are closest to the first foundation, - // and tell them to start building it - - var target = foundations.toEntityArray()[0]; - - var nonBuilderWorkers = workers.filter(function(ent) { - return (ent.getMetadata("subrole") !== "builder"); - }); - - var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), extraNeeded); - - // Order each builder individually, not as a formation - nearestNonBuilders.forEach(function(ent) { - ent.repair(target); - ent.setMetadata("subrole", "builder"); - }); - }, - - update: function(gameState, planGroups) - { - Engine.ProfileStart("economy update"); - - this.reassignRolelessUnits(gameState); - - this.buildMoreBuildings(gameState, planGroups); - - this.trainMoreWorkers(gameState, planGroups); - - this.reassignIdleWorkers(gameState, planGroups); - - this.assignToFoundations(gameState, planGroups); - - Engine.ProfileStop(); - }, - -}); diff --git a/binaries/data/mods/public/simulation/ai/testbot/gamestate.js b/binaries/data/mods/public/simulation/ai/testbot/gamestate.js deleted file mode 100644 index f414aadec6..0000000000 --- a/binaries/data/mods/public/simulation/ai/testbot/gamestate.js +++ /dev/null @@ -1,297 +0,0 @@ -/** - * 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; - }, - - 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); - }, - - 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; - }, - - getOwnEntities: (function() - { - return new EntityCollection(this.ai, this.ai._ownEntities); - }), - - 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); - }); - }), - - 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 resourceType = "resource|" + type; - var count = 0; - this.getOwnEntities().forEach(function(ent) { - - var t = ent.templateName(); - if (t == type || t == foundationType || t == resourceType) - ++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; - }, - - /** - * 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. - */ - getEntityLimits: function() - { - return this.playerData.entityLimits; - }, - - /** - * Returns player entity counts - * an object where each key is a category corresponding to the current entity count for the player. - */ - getEntityCounts: function() - { - return this.playerData.entityCounts; - }, - - /** - * Checks if the player's entity limit has been reached for the given category. - * The category comes from the entity template, specifically the - * BuildRestrictions/TrainingRestrictions components. - */ - isEntityLimitReached: function(category) - { - if (this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined) - return false; - return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]); - }, -}); diff --git a/binaries/data/mods/public/simulation/ai/testbot/military.js b/binaries/data/mods/public/simulation/ai/testbot/military.js deleted file mode 100644 index edd36f7246..0000000000 --- a/binaries/data/mods/public/simulation/ai/testbot/military.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Military strategy: - * * Try training an attack squad of a specified size - * * When it's the appropriate size, send it to attack the enemy - * * Repeat forever - * - */ - -var MilitaryAttackManager = Class({ - - _init: function() - { - this.targetSquadSize = 10; - this.squadTypes = [ - "units/{civ}_infantry_spearman_b", - "units/{civ}_infantry_javelinist_b", -// "units/{civ}_infantry_archer_b", // TODO: should only include this if hele - ]; - }, - - /** - * Returns the unit type we should begin training. - * (Currently this is whatever we have least of.) - */ - findBestNewUnit: function(gameState) - { - // Count each type - var types = []; - for each (var t in this.squadTypes) - types.push([t, gameState.countEntitiesAndQueuedWithType(gameState.applyCiv(t))]); - - // Sort by increasing count - types.sort(function (a, b) { return a[1] - b[1]; }); - - // TODO: we shouldn't return units that we don't have any - // buildings capable of training - - return types[0][0]; - }, - - update: function(gameState, planGroups) - { - // Pause for a minute before starting any work, to give the economy a chance - // to start up - if (gameState.getTimeElapsed() < 60*1000) - return; - - Engine.ProfileStart("military update"); - - // Continually try training new units, in batches of 5 - planGroups.militaryPersonnel.addPlan(100, - new UnitTrainingPlan(gameState, - this.findBestNewUnit(gameState), 5, { "role": "attack-pending" }) - ); - - // Find the units ready to join the attack - var pending = gameState.getOwnEntitiesWithRole("attack-pending"); - - // If we have enough units yet, start the attack - if (pending.length >= this.targetSquadSize) - { - // Find the enemy CCs we could attack - var targets = gameState.entities.filter(function(ent) { - return (ent.isEnemy() && ent.hasClass("CivCentre")); - }); - - // If there's no CCs, attack anything else that's critical - if (targets.length == 0) - { - targets = gameState.entities.filter(function(ent) { - return (ent.isEnemy() && ent.hasClass("ConquestCritical")); - }); - } - - // If we have a target, move to it - if (targets.length) - { - // Remove the pending role - pending.forEach(function(ent) { - ent.setMetadata("role", "attack"); - }); - - var target = targets.toEntityArray()[0]; - var targetPos = target.position(); - - // TODO: this should be an attack-move command - pending.move(targetPos[0], targetPos[1]); - } - } - - Engine.ProfileStop(); - }, - -}); diff --git a/binaries/data/mods/public/simulation/ai/testbot/plan-building.js b/binaries/data/mods/public/simulation/ai/testbot/plan-building.js deleted file mode 100644 index 9ca75a5394..0000000000 --- a/binaries/data/mods/public/simulation/ai/testbot/plan-building.js +++ /dev/null @@ -1,228 +0,0 @@ -var BuildingConstructionPlan = Class({ - - _init: function(gameState, type) - { - this.type = gameState.applyCiv(type); - - var template = gameState.getTemplate(this.type); - if (!template) - { - this.invalidTemplate = true; - return; - } - - this.cost = new Resources(template.cost()); - }, - - canExecute: function(gameState) - { - if (this.invalidTemplate) - return false; - - // TODO: verify numeric limits etc - - var builders = gameState.findBuilders(this.type); - - return (builders.length != 0); - }, - - execute: function(gameState) - { -// warn("Executing BuildingConstructionPlan "+uneval(this)); - - var builders = gameState.findBuilders(this.type).toEntityArray(); - - // We don't care which builder we assign, since they won't actually - // do the building themselves - all we care about is that there is - // some unit that can start the foundation - - var pos = this.findGoodPosition(gameState); - - builders[0].construct(this.type, pos.x, pos.z, pos.angle); - }, - - getCost: function() - { - return this.cost; - }, - - /** - * Make each cell's 16-bit value at least one greater than each of its - * neighbours' values. (If the grid is initialised with 0s and 65535s, - * the result of each cell is its Manhattan distance to the nearest 0.) - * - * TODO: maybe this should be 8-bit (and clamp at 255)? - */ - expandInfluences: function(grid, w, h) - { - for (var y = 0; y < h; ++y) - { - var min = 65535; - for (var x = 0; x < w; ++x) - { - var g = grid[x + y*w]; - if (g > min) grid[x + y*w] = min; - else if (g < min) min = g; - ++min; - } - - for (var x = w-2; x >= 0; --x) - { - var g = grid[x + y*w]; - if (g > min) grid[x + y*w] = min; - else if (g < min) min = g; - ++min; - } - } - - for (var x = 0; x < w; ++x) - { - var min = 65535; - for (var y = 0; y < h; ++y) - { - var g = grid[x + y*w]; - if (g > min) grid[x + y*w] = min; - else if (g < min) min = g; - ++min; - } - - for (var y = h-2; y >= 0; --y) - { - var g = grid[x + y*w]; - if (g > min) grid[x + y*w] = min; - else if (g < min) min = g; - ++min; - } - } - }, - - /** - * Add a circular linear-falloff shape to a grid. - */ - addInfluence: function(grid, w, h, cx, cy, maxDist) - { - var x0 = Math.max(0, cx - maxDist); - var y0 = Math.max(0, cy - maxDist); - var x1 = Math.min(w, cx + maxDist); - var y1 = Math.min(h, cy + maxDist); - for (var y = y0; y < y1; ++y) - { - for (var x = x0; x < x1; ++x) - { - var dx = x - cx; - var dy = y - cy; - var r = Math.sqrt(dx*dx + dy*dy); - if (r < maxDist) - grid[x + y*w] += maxDist - r; - } - } - }, - - findGoodPosition: function(gameState) - { - var self = this; - - var cellSize = 4; // size of each tile - - var template = gameState.getTemplate(this.type); - - // Find all tiles in valid territory that are far enough away from obstructions: - var passabilityMap = gameState.getPassabilityMap(); - var territoryMap = gameState.getTerritoryMap(); - const TERRITORY_PLAYER_MASK = 0x3F; - var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction"); - - if (passabilityMap.data.length != territoryMap.data.length) - error("passability and territory data are not matched!"); - - // See BuildRestrictions.js - switch(template.buildPlacementType()) - { - case "shore": - obstructionMask |= gameState.getPassabilityClassMask("building-shore"); - break; - case "land": - default: - obstructionMask |= gameState.getPassabilityClassMask("building-land"); - } - - var playerID = gameState.getPlayerID(); - var buildOwn = template.hasBuildTerritory("own"); - var buildAlly = template.hasBuildTerritory("ally"); - var buildNeutral = template.hasBuildTerritory("neutral"); - var buildEnemy = template.hasBuildTerritory("enemy"); - - var obstructionTiles = new Uint16Array(passabilityMap.data.length); - for (var i = 0; i < passabilityMap.data.length; ++i) - { - var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK); - var invalidTerritory = ( - (!buildOwn && tilePlayer == playerID) || - (!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) || - (!buildNeutral && tilePlayer == 0) || - (!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer !=0) - ); - obstructionTiles[i] = (invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535; - } - -// Engine.DumpImage("obstruction"+playerID+".png", obstructionTiles, passabilityMap.width, passabilityMap.height, 64); - - this.expandInfluences(obstructionTiles, passabilityMap.width, passabilityMap.height); - - // TODO: handle distance restrictions for e.g. CivCentres - - // Compute each tile's closeness to friendly structures: - - var friendlyTiles = new Uint16Array(passabilityMap.data.length); - - gameState.getOwnEntities().forEach(function(ent) { - if (ent.hasClass("Structure")) - { - var infl = 32; - if (ent.hasClass("CivCentre")) - infl *= 4; - - var pos = ent.position(); - var x = Math.round(pos[0] / cellSize); - var z = Math.round(pos[1] / cellSize); - self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl); - } - }); - - // Find target building's approximate obstruction radius, - // and expand by a bit to make sure we're not too close - var radius = Math.ceil(template.obstructionRadius() / cellSize) + 2; - - // Find the best non-obstructed tile - var bestIdx = 0; - var bestVal = -1; - for (var i = 0; i < passabilityMap.data.length; ++i) - { - if (obstructionTiles[i] > radius) - { - var v = friendlyTiles[i]; - if (v > bestVal) - { - bestVal = v; - bestIdx = i; - } - } - } - var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize; - var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize; - -// Engine.DumpImage("influences"+playerID+".png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32); -// Engine.DumpImage("friendly"+playerID+".png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256); - - // TODO: special dock placement requirements - - // Fixed angle to match fixed starting cam - var angle = 0.75*Math.PI; - - return { - "x": x, - "z": z, - "angle": angle - }; - }, -}); diff --git a/binaries/data/mods/public/simulation/ai/testbot/plan-training.js b/binaries/data/mods/public/simulation/ai/testbot/plan-training.js deleted file mode 100644 index b8e4b7b91a..0000000000 --- a/binaries/data/mods/public/simulation/ai/testbot/plan-training.js +++ /dev/null @@ -1,52 +0,0 @@ -var UnitTrainingPlan = Class({ - - _init: function(gameState, type, amount, metadata) - { - this.type = gameState.applyCiv(type); - this.amount = amount; - this.metadata = metadata; - - var template = gameState.getTemplate(this.type); - if (!template) - { - this.invalidTemplate = true; - return; - } - - this.cost = new Resources(template.cost()); - this.cost.multiply(amount); // (assume no batch discount) - }, - - canExecute: function(gameState) - { - if (this.invalidTemplate) - return false; - - // TODO: we should probably check pop caps - - var trainers = gameState.findTrainers(this.type); - - return (trainers.length != 0); - }, - - execute: function(gameState) - { -// warn("Executing UnitTrainingPlan "+uneval(this)); - - var trainers = gameState.findTrainers(this.type).toEntityArray(); - - // Prefer training buildings with short queues - // (TODO: this should also account for units added to the queue by - // plans that have already been executed this turn) - trainers.sort(function(a, b) { - return a.trainingQueueTime() - b.trainingQueueTime(); - }); - - trainers[0].train(this.type, this.amount, this.metadata); - }, - - getCost: function() - { - return this.cost; - }, -}); diff --git a/binaries/data/mods/public/simulation/ai/testbot/plan.js b/binaries/data/mods/public/simulation/ai/testbot/plan.js deleted file mode 100644 index 6a49552df4..0000000000 --- a/binaries/data/mods/public/simulation/ai/testbot/plan.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * All plan classes must implement this interface. - */ -var IPlan = Class({ - - _init: function() { /* ... */ }, - - canExecute: function(gameState) { /* ... */ }, - - execute: function(gameState) { /* ... */ }, - - getCost: function() { /* ... */ }, -}); - -/** - * Represents a prioritised collection of plans. - */ -var PlanGroup = Class({ - - _init: function() - { - this.escrow = new Resources({}); - this.plans = []; - }, - - addPlan: function(priority, plan) - { - this.plans.push({"priority": priority, "plan": plan}); - }, - - /** - * Executes all plans that we can afford, ordered by priority, - * and returns the highest-priority plan we couldn't afford (or null - * if none). - */ - executePlans: function(gameState) - { - // Ignore impossible plans - var plans = this.plans.filter(function(p) { return p.plan.canExecute(gameState); }); - - // Sort by decreasing priority - plans.sort(function(a, b) { return b.priority - a.priority; }); - - // Execute as many plans as we can afford - while (plans.length && this.escrow.canAfford(plans[0].plan.getCost())) - { - var plan = plans.shift().plan; - this.escrow.subtract(plan.getCost()); - plan.execute(gameState); - } - - if (plans.length) - return plans[0]; - else - return null; - }, - - resetPlans: function() - { - this.plans = []; - }, - - getEscrow: function() - { - return this.escrow; - }, -}); diff --git a/binaries/data/mods/public/simulation/ai/testbot/resources.js b/binaries/data/mods/public/simulation/ai/testbot/resources.js deleted file mode 100644 index 979f29dd3a..0000000000 --- a/binaries/data/mods/public/simulation/ai/testbot/resources.js +++ /dev/null @@ -1,36 +0,0 @@ -var Resources = Class({ - - types: ["food", "wood", "stone", "metal"], - - _init: function(amounts) - { - for each (var t in this.types) - this[t] = amounts[t] || 0; - }, - - canAfford: function(that) - { - for each (var t in this.types) - if (this[t] < that[t]) - return false; - return true; - }, - - add: function(that) - { - for each (var t in this.types) - this[t] += that[t]; - }, - - subtract: function(that) - { - for each (var t in this.types) - this[t] -= that[t]; - }, - - multiply: function(n) - { - for each (var t in this.types) - this[t] *= n; - }, -}); diff --git a/binaries/data/mods/public/simulation/ai/testbot/testbot.js b/binaries/data/mods/public/simulation/ai/testbot/testbot.js deleted file mode 100644 index 502e61f570..0000000000 --- a/binaries/data/mods/public/simulation/ai/testbot/testbot.js +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This is a primitive initial attempt at an AI player. - * The design isn't great and maybe the whole thing should be rewritten - - * the aim here is just to have something that basically works, and to - * learn more about what's really needed for a decent AI design. - * - * The basic idea is we have a collection of independent modules - * (EconomyManager, etc) which produce a list of plans. - * The modules are mostly stateless - each turn they look at the current - * world state, and produce some plans that will improve the state. - * E.g. if there's too few worker units, they'll do a plan to train - * another one. Plans are discarded after the current turn, if they - * haven't been executed. - * - * Plans are grouped into a small number of PlanGroups, and for each - * group we try to execute the highest-priority plans. - * If some plan groups need more resources to execute their highest-priority - * plan, we'll distribute any unallocated resources to that group's - * escrow account. Eventually they'll accumulate enough to afford their plan. - * (The purpose is to ensure resources are shared fairly between all the - * plan groups - none of them should be starved even if they're trying to - * execute a really expensive plan.) - */ - -/* - * Lots of things we should fix: - * - * * Find entities with no assigned role, and give them something to do - * * Keep some units back for defence - * * Consistent terminology (type vs template etc) - * * ... - * - */ - - -function TestBotAI(settings) -{ -// warn("Constructing TestBotAI for player "+settings.player); - - BaseAI.call(this, settings); - - this.turn = 0; - - this.modules = [ - new EconomyManager(), - new MilitaryAttackManager(), - ]; - - this.planGroups = { - economyPersonnel: new PlanGroup(), - economyConstruction: new PlanGroup(), - militaryPersonnel: new PlanGroup(), - }; -} - -TestBotAI.prototype = new BaseAI(); - -TestBotAI.prototype.ShareResources = function(remainingResources, unaffordablePlans) -{ - // Share our remaining resources among the plangroups that need - // to accumulate more resources, in proportion to their priorities - - for each (var type in remainingResources.types) - { - // Skip resource types where we don't have any spare - if (remainingResources[type] <= 0) - continue; - - // Find the plans that require some of this resource type, - // and the sum of their priorities - var ps = []; - var sumPriority = 0; - for each (var p in unaffordablePlans) - { - if (p.plan.getCost()[type] > p.group.getEscrow()[type]) - { - ps.push(p); - sumPriority += p.priority; - } - } - - // Avoid divisions-by-zero - if (!sumPriority) - continue; - - // Share resources by priority, clamped to the amount the plan actually needs - for each (var p in ps) - { - var amount = Math.floor(remainingResources[type] * p.priority / sumPriority); - var max = p.plan.getCost()[type] - p.group.getEscrow()[type]; - p.group.getEscrow()[type] += Math.min(max, amount); - } - } -}; - -TestBotAI.prototype.OnUpdate = function() -{ - // Run the update every n turns, offset depending on player ID to balance the load - if ((this.turn + this.player) % 4 == 0) - { - var gameState = new GameState(this); - - // Find the resources we have this turn that haven't already - // been allocated to an escrow account. - // (We need to do this before executing any plans, because those will - // distort the escrow figures.) - var remainingResources = gameState.getResources(); - for each (var planGroup in this.planGroups) - remainingResources.subtract(planGroup.getEscrow()); - - Engine.ProfileStart("plan setup"); - - // Compute plans from each module - for each (var module in this.modules) - module.update(gameState, this.planGroups); - -// print(uneval(this.planGroups)+"\n"); - - Engine.ProfileStop(); - Engine.ProfileStart("plan execute"); - - // Execute as many plans as possible, and keep a record of - // which ones we can't afford yet - var unaffordablePlans = []; - for each (var planGroup in this.planGroups) - { - var plan = planGroup.executePlans(gameState); - if (plan) - unaffordablePlans.push({"group": planGroup, "priority": plan.priority, "plan": plan.plan}); - } - - Engine.ProfileStop(); - - this.ShareResources(remainingResources, unaffordablePlans); - -// print(uneval(this.planGroups)+"\n"); - - // Reset the temporary plan data - for each (var planGroup in this.planGroups) - planGroup.resetPlans(); - } - - this.turn++; -};