This was SVN commit r10654.
This commit is contained in:
parent
8383f35eb8
commit
b146f53d7b
1
binaries/data/mods/public/simulation/ai/qbot/_init.js
Normal file
1
binaries/data/mods/public/simulation/ai/qbot/_init.js
Normal file
@ -0,0 +1 @@
|
||||
Engine.IncludeModule("common-api");
|
206
binaries/data/mods/public/simulation/ai/qbot/attackMoveToCC.js
Normal file
206
binaries/data/mods/public/simulation/ai/qbot/attackMoveToCC.js
Normal file
@ -0,0 +1,206 @@
|
||||
var AttackMoveToCC = function(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);
|
||||
});
|
||||
}
|
||||
// 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();
|
||||
|
||||
|
||||
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;
|
||||
};
|
||||
|
5
binaries/data/mods/public/simulation/ai/qbot/data.json
Normal file
5
binaries/data/mods/public/simulation/ai/qbot/data.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "qBot",
|
||||
"description": "Quantumstate's improved version of the Test Bot",
|
||||
"constructor": "QBotAI"
|
||||
}
|
482
binaries/data/mods/public/simulation/ai/qbot/economy.js
Normal file
482
binaries/data/mods/public/simulation/ai/qbot/economy.js
Normal file
@ -0,0 +1,482 @@
|
||||
var EconomyManager = function() {
|
||||
this.targetNumBuilders = 5; // number of workers we want building stuff
|
||||
this.targetNumFields = 5;
|
||||
|
||||
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.floor(gameState.getPopulationMax()/3);
|
||||
};
|
||||
|
||||
EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) {
|
||||
// Count the workers in the world and in progress
|
||||
var numWorkers = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("units/{civ}_support_female_citizen"));
|
||||
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
|
||||
this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState);
|
||||
|
||||
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]+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.getOwnEntitiesWithRole(undefined);
|
||||
|
||||
roleless.forEach(function(ent) {
|
||||
if (ent.hasClass("Worker")){
|
||||
ent.setMetadata("role", "worker");
|
||||
}else if(ent.hasClass("CitizenSoldier") || ent.hasClass("Super")){
|
||||
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.getOwnEntitiesWithRole("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.getOwnEntitiesWithRole("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 idleWorkers = gameState.getOwnEntitiesWithRole("worker").filter(function(ent) {
|
||||
return (ent.isIdle() || ent.getMetadata("subrole") === "idle");
|
||||
});
|
||||
|
||||
if (idleWorkers.length) {
|
||||
var resourceSupplies = gameState.findResourceSupplies();
|
||||
|
||||
idleWorkers.forEach(function(ent) {
|
||||
// Check that the worker isn't garrisoned
|
||||
if (ent.position() === undefined){
|
||||
return;
|
||||
}
|
||||
|
||||
var types = self.pickMostNeededResources(gameState);
|
||||
for ( var typeKey in types) {
|
||||
var type = types[typeKey];
|
||||
// Make sure there are actually some resources of that type
|
||||
if (!resourceSupplies[type])
|
||||
continue;
|
||||
|
||||
// TODO: we should care about gather rates of workers
|
||||
|
||||
// Find the nearest dropsite for this resource from the worker
|
||||
var nearestDropsite = undefined;
|
||||
var minDropsiteDist = Math.min(); // set to infinity initially
|
||||
gameState.getOwnEntities().forEach(function(dropsiteEnt) {
|
||||
if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.resourceDropsiteTypes().indexOf(type) !== -1){
|
||||
if (dropsiteEnt.position() && dropsiteEnt.getMetadata("resourceQuantity_" + type) > 0){
|
||||
var dist = VectorDistance(ent.position(), dropsiteEnt.position());
|
||||
if (dist < minDropsiteDist){
|
||||
nearestDropsite = dropsiteEnt;
|
||||
minDropsiteDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var workerPosition = ent.position();
|
||||
var supplies = [];
|
||||
resourceSupplies[type].forEach(function(supply) {
|
||||
// Skip targets that are too hard to hunt
|
||||
if (supply.entity.isUnhuntable()){
|
||||
return;
|
||||
}
|
||||
|
||||
// And don't go for the bloody fish!
|
||||
if (supply.entity.hasClass("SeaCreature")){
|
||||
return;
|
||||
}
|
||||
|
||||
// Check we can actually reach the resource
|
||||
if (!gameState.ai.accessibility.isAccessible(supply.position)){
|
||||
return;
|
||||
}
|
||||
|
||||
// measure the distance to the resource
|
||||
var dist = VectorDistance(supply.position, workerPosition);
|
||||
// Add on a factor for the nearest dropsite if one exists
|
||||
if (nearestDropsite){
|
||||
dist += 5 * VectorDistance(supply.position, nearestDropsite.position());
|
||||
}
|
||||
|
||||
// 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 the best resource (by distance from the dropsite and unit)
|
||||
if (supplies.length) {
|
||||
ent.gather(supplies[0].entity);
|
||||
ent.setMetadata("subrole", "gatherer");
|
||||
ent.setMetadata("gather-type", type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't find any types to gather
|
||||
ent.setMetadata("subrole", "idle");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
EconomyManager.prototype.assignToFoundations = function(gameState) {
|
||||
// If we have some foundations, and we don't have enough
|
||||
// builder-workers,
|
||||
// try reassigning some other workers who are nearby
|
||||
|
||||
var foundations = gameState.findFoundations();
|
||||
|
||||
// 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) {
|
||||
// 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.repair(target);
|
||||
ent.setMetadata("subrole", "builder");
|
||||
});
|
||||
};
|
||||
|
||||
EconomyManager.prototype.buildMoreFields = function(gameState, queues) {
|
||||
// give time for treasures to be gathered
|
||||
if (gameState.getTimeElapsed() < 30 * 1000)
|
||||
return;
|
||||
var numFields = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("structures/{civ}_field"));
|
||||
numFields += queues.field.totalLength();
|
||||
|
||||
for ( var i = numFields; 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.countEntitiesAndQueuedWithType(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};
|
||||
|
||||
for (var resource in radius){
|
||||
// if there is no resourceMap create one with an influence for everything with that resource
|
||||
if (! this.resourceMaps[resource]){
|
||||
this.resourceMaps[resource] = new Map(gameState);
|
||||
|
||||
var supplies = gameState.findResourceSupplies();
|
||||
for (var i in supplies[resource]){
|
||||
var current = supplies[resource][i];
|
||||
var x = Math.round(current.position[0] / gameState.cellSize);
|
||||
var z = Math.round(current.position[1] / gameState.cellSize);
|
||||
var strength = Math.round(current.entity.resourceSupplyMax()/decreaseFactor[resource]);
|
||||
this.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
|
||||
}
|
||||
}
|
||||
// Look for destroy events and subtract the entities original influence from the resourceMap
|
||||
for (var i in events) {
|
||||
var e = events[i];
|
||||
|
||||
if (e.type === "Destroy") {
|
||||
if (e.msg.rawEntity.template){
|
||||
var ent = new Entity(gameState.ai, e.msg.rawEntity);
|
||||
if (ent && ent.resourceSupplyType() && ent.resourceSupplyType().generic === resource){
|
||||
var x = Math.round(ent.position()[0] / gameState.cellSize);
|
||||
var z = Math.round(ent.position()[1] / gameState.cellSize);
|
||||
var strength = Math.round(ent.resourceSupplyMax()/decreaseFactor[resource]);
|
||||
this.resourceMaps[resource].addInfluence(x, z, radius[resource], -1*strength);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//this.resourceMaps[resource].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 (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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//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.buildDropsites = function(gameState, queues){
|
||||
if (queues.economicBuilding.totalLength() === 0 &&
|
||||
gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_mill")) === 0 &&
|
||||
gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_civil_centre")) === 0){
|
||||
//only ever build one mill/CC 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}_mill", 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.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.5) {
|
||||
this.targetNumBuilders = 10;
|
||||
}else{
|
||||
this.targetNumBuilders = 5;
|
||||
}
|
||||
|
||||
if (gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.8) {
|
||||
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);
|
||||
Engine.ProfileStop();
|
||||
|
||||
this.buildDropsites(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("Assign builders");
|
||||
this.assignToFoundations(gameState);
|
||||
Engine.ProfileStop();
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
Entity.prototype.deleteMetadata = function(id) {
|
||||
delete this._ai._entityMetadata[this.id()];
|
||||
};
|
||||
|
||||
Entity.prototype.garrisonMax = function() {
|
||||
if (!this._template.GarrisonHolder)
|
||||
return undefined;
|
||||
return this._template.GarrisonHolder.Max;
|
||||
};
|
||||
|
||||
Entity.prototype.garrison = function(target) {
|
||||
Engine.PostCommand({"type": "garrison", "entities": [this.id()], "target": target.id(),"queued": false});
|
||||
return this;
|
||||
};
|
||||
|
||||
Entity.prototype.unload = function(id) {
|
||||
if (!this._template.GarrisonHolder)
|
||||
return undefined;
|
||||
Engine.PostCommand({"type": "unload", "garrisonHolder": this.id(), "entity": id});
|
||||
return this;
|
||||
};
|
||||
|
||||
Entity.prototype.unloadAll = function() {
|
||||
if (!this._template.GarrisonHolder)
|
||||
return undefined;
|
||||
Engine.PostCommand({"type": "unload-all", "garrisonHolder": this.id()});
|
||||
return this;
|
||||
};
|
@ -0,0 +1,103 @@
|
||||
EntityCollection.prototype.attack = function(unit)
|
||||
{
|
||||
var unitId;
|
||||
if (typeOf(unit) === "Entity"){
|
||||
unitId = unit.id();
|
||||
}else{
|
||||
unitId = unit;
|
||||
}
|
||||
|
||||
Engine.PostCommand({"type": "walk", "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.attackMove = function(x, z){
|
||||
Engine.PostCommand({"type": "attack-move", "entities": this.toIdArray(), "x": x, "z": z, "queued": false});
|
||||
return this;
|
||||
};
|
||||
|
||||
// Do naughty stuff to replace the entity collection constructor for updating entity collections
|
||||
var tmpEntityCollection = function(baseAI, entities, filter, gameState){
|
||||
this._ai = baseAI;
|
||||
this._entities = entities;
|
||||
if (filter){
|
||||
var tmp = 3;
|
||||
this.filterFunc = filter;
|
||||
this._entities = this.filter(function(ent){
|
||||
return filter(ent, gameState);
|
||||
})._entities;
|
||||
this._ai.registerUpdate(this);
|
||||
}
|
||||
|
||||
// Compute length lazily on demand, since it can be
|
||||
// expensive for large collections
|
||||
// This is updated by the update() function.
|
||||
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;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
tmpEntityCollection.prototype = new EntityCollection;
|
||||
EntityCollection = tmpEntityCollection;
|
||||
|
||||
// Keeps an EntityCollection with a filter function up to date by watching for events
|
||||
EntityCollection.prototype.update = function(gameState, events){
|
||||
if (!this.filterFunc)
|
||||
return;
|
||||
for (var i in events){
|
||||
if (events[i].type === "Create"){
|
||||
var ent = gameState.getEntityById(events[i].msg.entity);
|
||||
if (ent){
|
||||
var raw_ent = ent._entity;
|
||||
if (ent && this.filterFunc(ent, gameState)){
|
||||
this._entities[events[i].msg.entity] = raw_ent;
|
||||
if (this._length !== undefined)
|
||||
this._length ++;
|
||||
}
|
||||
}
|
||||
}else if(events[i].type === "Destroy"){
|
||||
if (this._entities[events[i].msg.entity]){
|
||||
delete this._entities[events[i].msg.entity];
|
||||
if (this._length !== undefined)
|
||||
this._length --;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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];
|
||||
}
|
||||
};
|
51
binaries/data/mods/public/simulation/ai/qbot/filters.js
Normal file
51
binaries/data/mods/public/simulation/ai/qbot/filters.js
Normal file
@ -0,0 +1,51 @@
|
||||
var Filters = {
|
||||
byClass: function(cls){
|
||||
return function(ent){
|
||||
return ent.hasClass(cls);
|
||||
};
|
||||
},
|
||||
|
||||
byClassesAnd: function(clsList){
|
||||
return function(ent){
|
||||
var ret = true;
|
||||
for (var i in clsList){
|
||||
ret = ret && ent.hasClass(clsList[i]);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
},
|
||||
|
||||
byClassesOr: function(clsList){
|
||||
return function(ent){
|
||||
var ret = false;
|
||||
for (var i in clsList){
|
||||
ret = ret || ent.hasClass(clsList[i]);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
},
|
||||
|
||||
and: function(filter1, filter2){
|
||||
return function(ent, gameState){
|
||||
return filter1(ent, gameState) && filter2(ent, gameState);
|
||||
};
|
||||
},
|
||||
|
||||
or: function(filter1, filter2){
|
||||
return function(ent, gameState){
|
||||
return filter1(ent, gameState) || filter2(ent, gameState);
|
||||
};
|
||||
},
|
||||
|
||||
isEnemy: function(){
|
||||
return function(ent, gameState){
|
||||
return gameState.isEntityEnemy(ent);
|
||||
};
|
||||
},
|
||||
|
||||
isSoldier: function(){
|
||||
return function(ent){
|
||||
return Filters.byClassesOr(["CitizenSoldier", "Super"])(ent);
|
||||
};
|
||||
}
|
||||
};
|
288
binaries/data/mods/public/simulation/ai/qbot/gamestate.js
Normal file
288
binaries/data/mods/public/simulation/ai/qbot/gamestate.js
Normal file
@ -0,0 +1,288 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
this.cellSize = 4; // Size of each map tile
|
||||
};
|
||||
|
||||
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 this.ai.territoryMap;
|
||||
};
|
||||
|
||||
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.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() {
|
||||
return new EntityCollection(this.ai, this.ai._ownEntities);
|
||||
};
|
||||
|
||||
GameState.prototype.getEntities = function() {
|
||||
return this.entities;
|
||||
};
|
||||
|
||||
GameState.prototype.getEntityById = function(id){
|
||||
if (this.entities._entities[id]) {
|
||||
return new Entity(this.ai, this.entities._entities[id]);
|
||||
}else{
|
||||
debug("Entity " + id + " requested does not exist");
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
GameState.prototype.getOwnEntitiesWithRole = Memoize('getOwnEntitiesWithRole', function(role) {
|
||||
var metas = this.ai._entityMetadata;
|
||||
if (role === undefined)
|
||||
return this.getOwnEntities().filter_raw(function(ent) {
|
||||
var metadata = metas[ent.id];
|
||||
if (!metadata || !('role' in metadata))
|
||||
return true;
|
||||
return (metadata.role === undefined);
|
||||
});
|
||||
else
|
||||
return this.getOwnEntities().filter_raw(function(ent) {
|
||||
var metadata = metas[ent.id];
|
||||
if (!metadata || !('role' in metadata))
|
||||
return false;
|
||||
return (metadata.role === role);
|
||||
});
|
||||
});
|
||||
|
||||
GameState.prototype.countEntitiesWithType = function(type) {
|
||||
var count = 0;
|
||||
this.getOwnEntities().forEach(function(ent) {
|
||||
var t = ent.templateName();
|
||||
if (t == type)
|
||||
++count;
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
GameState.prototype.countEntitiesAndQueuedWithType = function(type) {
|
||||
var foundationType = "foundation|" + type;
|
||||
var count = 0;
|
||||
this.getOwnEntities().forEach(function(ent) {
|
||||
|
||||
var t = ent.templateName();
|
||||
if (t == type || t == foundationType)
|
||||
++count;
|
||||
|
||||
var queue = ent.trainingQueue();
|
||||
if (queue) {
|
||||
queue.forEach(function(item) {
|
||||
if (item.template == type)
|
||||
count += item.count;
|
||||
});
|
||||
}
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
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.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.
|
||||
*/
|
||||
GameState.prototype.findTrainers = function(template) {
|
||||
var maxQueueLength = 2; // 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.
|
||||
*/
|
||||
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.findFoundations = function(template) {
|
||||
return this.getOwnEntities().filter(function(ent) {
|
||||
return (typeof ent.foundationProgress() !== "undefined");
|
||||
});
|
||||
};
|
||||
|
||||
GameState.prototype.findResourceSupplies = function() {
|
||||
var supplies = {};
|
||||
this.entities.forEach(function(ent) {
|
||||
var type = ent.resourceSupplyType();
|
||||
if (!type)
|
||||
return;
|
||||
var amount = ent.resourceSupplyAmount();
|
||||
if (!amount)
|
||||
return;
|
||||
|
||||
var reportedType;
|
||||
if (type.generic == "treasure")
|
||||
reportedType = type.specific;
|
||||
else
|
||||
reportedType = type.generic;
|
||||
|
||||
if (!supplies[reportedType])
|
||||
supplies[reportedType] = [];
|
||||
|
||||
supplies[reportedType].push({
|
||||
"entity" : ent,
|
||||
"amount" : amount,
|
||||
"type" : type,
|
||||
"position" : ent.position()
|
||||
});
|
||||
});
|
||||
return supplies;
|
||||
};
|
||||
|
||||
|
||||
GameState.prototype.getBuildLimits = function() {
|
||||
return this.playerData.buildLimits;
|
||||
};
|
||||
|
||||
GameState.prototype.getBuildCounts = function() {
|
||||
return this.playerData.buildCounts;
|
||||
};
|
||||
|
||||
GameState.prototype.isBuildLimitReached = function(category) {
|
||||
if(this.playerData.buildLimits[category] === undefined || this.playerData.buildCounts[category] === undefined)
|
||||
return false;
|
||||
if(this.playerData.buildLimits[category].LimitsPerCivCentre != undefined)
|
||||
return (this.playerData.buildCounts[category] >= this.playerData.buildCounts["CivilCentre"]*this.playerData.buildLimits[category].LimitPerCivCentre);
|
||||
else
|
||||
return (this.playerData.buildCounts[category] >= this.playerData.buildLimits[category]);
|
||||
};
|
29
binaries/data/mods/public/simulation/ai/qbot/housing.js
Normal file
29
binaries/data/mods/public/simulation/ai/qbot/housing.js
Normal file
@ -0,0 +1,29 @@
|
||||
// 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.countEntitiesWithType(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();
|
||||
};
|
339
binaries/data/mods/public/simulation/ai/qbot/license_gpl-2.0.txt
Normal file
339
binaries/data/mods/public/simulation/ai/qbot/license_gpl-2.0.txt
Normal file
@ -0,0 +1,339 @@
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
<signature of Ty Coon>, 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.
|
257
binaries/data/mods/public/simulation/ai/qbot/map-module.js
Normal file
257
binaries/data/mods/public/simulation/ai/qbot/map-module.js
Normal file
@ -0,0 +1,257 @@
|
||||
// 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.createObstructionMap = function(gameState, template){
|
||||
var passabilityMap = gameState.getMap();
|
||||
var territoryMap = gameState.getTerritoryMap();
|
||||
|
||||
const TERRITORY_PLAYER_MASK = 0x7F;
|
||||
|
||||
// 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 obstructionTiles = new Uint16Array(map.data.length);
|
||||
for ( var i = 0; i < map.data.length; ++i){
|
||||
obstructionTiles[i] = map.data[i] & 0x7F;
|
||||
}
|
||||
|
||||
return new Map(gameState, obstructionTiles);
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
657
binaries/data/mods/public/simulation/ai/qbot/military.js
Executable file
657
binaries/data/mods/public/simulation/ai/qbot/military.js
Executable file
@ -0,0 +1,657 @@
|
||||
/*
|
||||
* 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() {
|
||||
this.targetSquadSize = 10;
|
||||
this.targetScoutTowers = 10;
|
||||
|
||||
// these use the structure soldiers[unitId] = true|false to register the
|
||||
// units
|
||||
this.soldiers = {};
|
||||
this.assigned = {};
|
||||
this.unassigned = {};
|
||||
this.garrisoned = {};
|
||||
this.enemyAttackers = {};
|
||||
|
||||
this.attackManagers = [AttackMoveToCC];
|
||||
this.availableAttacks = [];
|
||||
this.currentAttacks = [];
|
||||
|
||||
this.defineUnitsAndBuildings();
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.init = function(gameState) {
|
||||
var civ = gameState.playerData.civ;
|
||||
if (civ in this.uCivCitizenSoldier) {
|
||||
this.uCitizenSoldier = this.uCivCitizenSoldier[civ];
|
||||
this.uAdvanced = this.uCivAdvanced[civ];
|
||||
this.uSiege = this.uCivSiege[civ];
|
||||
|
||||
this.bAdvanced = this.bCivAdvanced[civ];
|
||||
}
|
||||
|
||||
for (var i in this.uCitizenSoldier){
|
||||
this.uCitizenSoldier[i] = gameState.applyCiv(this.uCitizenSoldier[i]);
|
||||
}
|
||||
for (var i in this.uAdvanced){
|
||||
this.uAdvanced[i] = gameState.applyCiv(this.uAdvanced[i]);
|
||||
}
|
||||
for (var i in this.uSiege){
|
||||
this.uSiege[i] = gameState.applyCiv(this.uSiege[i]);
|
||||
}
|
||||
|
||||
for (var i in this.attackManagers){
|
||||
this.availableAttacks[i] = new this.attackManagers[i](gameState, this);
|
||||
}
|
||||
|
||||
var filter = Filters.and(Filters.isEnemy(), Filters.byClassesOr(["CitizenSoldier", "Super"]));
|
||||
this.enemySoldiers = new EntityCollection(gameState.ai, gameState.entities._entities, filter, gameState);
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.defineUnitsAndBuildings = function(){
|
||||
// units
|
||||
this.uCivCitizenSoldier= {};
|
||||
this.uCivAdvanced = {};
|
||||
this.uCivSiege = {};
|
||||
|
||||
this.uCivCitizenSoldier.hele = [ "units/hele_infantry_spearman_b", "units/hele_infantry_javelinist_b", "units/hele_infantry_archer_b" ];
|
||||
this.uCivAdvanced.hele = [ "units/hele_cavalry_swordsman_b", "units/hele_cavalry_javelinist_b", "units/hele_champion_cavalry_mace", "units/hele_champion_infantry_mace", "units/hele_champion_infantry_polis", "units/hele_champion_ranged_polis" , "units/thebes_sacred_band_hoplitai", "units/thespian_melanochitones","units/sparta_hellenistic_phalangitai", "units/thrace_black_cloak"];
|
||||
this.uCivSiege.hele = [ "units/hele_mechanical_siege_oxybeles", "units/hele_mechanical_siege_lithobolos" ];
|
||||
|
||||
this.uCivCitizenSoldier.cart = [ "units/cart_infantry_spearman_b", "units/cart_infantry_archer_b" ];
|
||||
this.uCivAdvanced.cart = [ "units/cart_cavalry_javelinist_b", "units/cart_champion_cavalry", "units/cart_infantry_swordsman_2_b", "units/cart_cavalry_spearman_b", "units/cart_infantry_javelinist_b", "units/cart_infantry_slinger_b", "units/cart_cavalry_swordsman_b", "units/cart_infantry_swordsman_b", "units/cart_cavalry_swordsman_2_b", "units/cart_sacred_band_cavalry"];
|
||||
this.uCivSiege.cart = ["units/cart_mechanical_siege_ballista", "units/cart_mechanical_siege_oxybeles"];
|
||||
|
||||
this.uCivCitizenSoldier.celt = [ "units/celt_infantry_spearman_b", "units/celt_infantry_javelinist_b" ];
|
||||
this.uCivAdvanced.celt = [ "units/celt_cavalry_javelinist_b", "units/celt_cavalry_swordsman_b", "units/celt_champion_cavalry_gaul", "units/celt_champion_infantry_gaul", "units/celt_champion_cavalry_brit", "units/celt_champion_infantry_brit", "units/celt_fanatic" ];
|
||||
this.uCivSiege.celt = ["units/celt_mechanical_siege_ram"];
|
||||
|
||||
this.uCivCitizenSoldier.iber = [ "units/iber_infantry_spearman_b", "units/iber_infantry_slinger_b", "units/iber_infantry_swordsman_b", "units/iber_infantry_javelinist_b" ];
|
||||
this.uCivAdvanced.iber = ["units/iber_cavalry_spearman_b", "units/iber_champion_cavalry", "units/iber_champion_infantry" ];
|
||||
this.uCivSiege.iber = ["units/iber_mechanical_siege_ram"];
|
||||
|
||||
//defaults
|
||||
this.uCitizenSoldier = ["units/{civ}_infantry_spearman_b", "units/{civ}_infantry_slinger_b", "units/{civ}_infantry_swordsman_b", "units/{civ}_infantry_javelinist_b", "units/{civ}_infantry_archer_b" ];
|
||||
this.uAdvanced = ["units/{civ}_cavalry_spearman_b", "units/{civ}_cavalry_javelinist_b", "units/{civ}_champion_cavalry", "units/{civ}_champion_infantry"];
|
||||
this.uSiege = ["units/{civ}_mechanical_siege_oxybeles", "units/{civ}_mechanical_siege_lithobolos", "units/{civ}_mechanical_siege_ballista","units/{civ}_mechanical_siege_ram"];
|
||||
|
||||
// buildings
|
||||
this.bModerate = [ "structures/{civ}_barracks" ]; //same for all civs
|
||||
|
||||
this.bCivAdvanced = {};
|
||||
this.bCivAdvanced.hele = [ "structures/{civ}_gymnasion", "structures/{civ}_fortress" ];
|
||||
this.bCivAdvanced.cart = [ "structures/{civ}_fortress", "structures/{civ}_embassy_celtic", "structures/{civ}_embassy_iberian", "structures/{civ}_embassy_italiote" ];
|
||||
this.bCivAdvanced.celt = [ "structures/{civ}_kennel", "structures/{civ}_fortress_b", "structures/{civ}_fortress_g" ];
|
||||
this.bCivAdvanced.iber = [ "structures/{civ}_fortress" ];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param (GameState) gameState
|
||||
* @returns array of soldiers for which training buildings exist
|
||||
*/
|
||||
MilitaryAttackManager.prototype.findTrainableUnits = function(gameState, soldierTypes){
|
||||
var ret = [];
|
||||
gameState.getOwnEntities().forEach(function(ent) {
|
||||
var trainable = ent.trainableEntities();
|
||||
for (var i in trainable){
|
||||
if (soldierTypes.indexOf(trainable[i]) !== -1){
|
||||
if (ret.indexOf(trainable[i]) === -1){
|
||||
ret.push(trainable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the unit type we should begin training. (Currently this is whatever
|
||||
* we have least of.)
|
||||
*/
|
||||
MilitaryAttackManager.prototype.findBestNewUnit = function(gameState, queue, soldierTypes) {
|
||||
var units = this.findTrainableUnits(gameState, soldierTypes);
|
||||
// Count each type
|
||||
var types = [];
|
||||
for ( var tKey in units) {
|
||||
var t = units[tKey];
|
||||
types.push([t, gameState.countEntitiesAndQueuedWithType(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.attackElephants = function(gameState) {
|
||||
var eles = gameState.entities.filter(function(ent) {
|
||||
return (ent.templateName().indexOf("elephant") > -1);
|
||||
});
|
||||
|
||||
warn(uneval(eles._entities));
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.registerSoldiers = function(gameState) {
|
||||
var soldiers = gameState.getOwnEntitiesWithRole("soldier");
|
||||
var self = this;
|
||||
|
||||
soldiers.forEach(function(ent) {
|
||||
ent.setMetadata("role", "registeredSoldier");
|
||||
self.soldiers[ent.id()] = true;
|
||||
self.unassigned[ent.id()] = true;
|
||||
});
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.defence = function(gameState) {
|
||||
var ents = gameState.entities._entities;
|
||||
|
||||
var myCivCentres = gameState.getOwnEntities().filter(function(ent) {
|
||||
return ent.hasClass("CivCentre");
|
||||
});
|
||||
|
||||
if (myCivCentres.length === 0)
|
||||
return;
|
||||
|
||||
var defenceRange = 200; // just beyond town centres territory influence
|
||||
var self = this;
|
||||
|
||||
var newEnemyAttackers = {};
|
||||
|
||||
myCivCentres.forEach(function(ent) {
|
||||
var pos = ent.position();
|
||||
self.getEnemySoldiers(gameState).forEach(function(ent) {
|
||||
if (gameState.playerData.isEnemy[ent.owner()]
|
||||
&& (ent.hasClass("CitizenSoldier") || ent.hasClass("Super"))
|
||||
&& ent.position()) {
|
||||
var dist = VectorDistance(ent.position(), pos);
|
||||
if (dist < defenceRange) {
|
||||
newEnemyAttackers[ent.id()] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
for (var id in this.enemyAttackers){
|
||||
if (!newEnemyAttackers[id]){
|
||||
this.unassignDefenders(gameState, id);
|
||||
}
|
||||
}
|
||||
|
||||
this.enemyAttackers = newEnemyAttackers;
|
||||
|
||||
var enemyAttackStrength = 0;
|
||||
var availableStrength = this.measureAvailableStrength();
|
||||
var garrisonedStrength = 0;
|
||||
for (var i in this.garrisoned){
|
||||
if (this.entity(i) !== undefined){
|
||||
if (Filters.isSoldier()(this.entity(i))){
|
||||
garrisonedStrength += this.getUnitStrength(this.entity(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var id in this.enemyAttackers) {
|
||||
var ent = new Entity(gameState.ai, ents[id]);
|
||||
enemyAttackStrength+= this.getUnitStrength(ent);
|
||||
}
|
||||
|
||||
if(2 * enemyAttackStrength < availableStrength + garrisonedStrength) {
|
||||
this.ungarrisonAll(gameState);
|
||||
return;
|
||||
} else {
|
||||
this.garrisonCitizens(gameState);
|
||||
}
|
||||
|
||||
if(enemyAttackStrength > availableStrength + garrisonedStrength) {
|
||||
this.garrisonSoldiers(gameState);
|
||||
}
|
||||
|
||||
for (id in this.enemyAttackers) {
|
||||
if(!this.assignDefenders(gameState,id)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.assignDefenders = function(gameState,target) {
|
||||
var defendersPerAttacker = 3;
|
||||
var ent = new Entity(gameState.ai, gameState.entities._entities[target]);
|
||||
if (ent.getMetadata("attackers") === undefined || ent.getMetadata("attackers").length < defendersPerAttacker) {
|
||||
var tasked = this.getAvailableUnits(3);
|
||||
if (tasked.length > 0) {
|
||||
Engine.PostCommand({
|
||||
"type" : "attack",
|
||||
"entities" : tasked,
|
||||
"target" : ent.id(),
|
||||
"queued" : false
|
||||
});
|
||||
ent.setMetadata("attackers", tasked);
|
||||
for (var i in tasked) {
|
||||
this.entity(tasked[i]).setMetadata("attacking", id);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.unassignDefenders = function(gameState, target){
|
||||
var myCivCentres = gameState.getOwnEntities().filter(function(ent) {
|
||||
return ent.hasClass("CivCentre");
|
||||
}).toEntityArray();
|
||||
var pos = undefined;
|
||||
if (myCivCentres.length > 0 && myCivCentres[0].position()){
|
||||
pos = myCivCentres[0].position();
|
||||
}
|
||||
|
||||
var ent = this.entity(target);
|
||||
if (ent && ent.getMetadata() && ent.getMetadata().attackers){
|
||||
for (var i in ent.metadata.attackers){
|
||||
var attacker = this.entity(ent.getMetadata().attackers[i]);
|
||||
if (attacker){
|
||||
attacker.deleteMetadata('attacking');
|
||||
if (pos){
|
||||
attacker.move(pos[0], pos[1]);
|
||||
}
|
||||
this.unassignUnit(attacker.id());
|
||||
}
|
||||
}
|
||||
ent.deleteMetadata('attackers');
|
||||
}
|
||||
};
|
||||
|
||||
// Ungarrisons all units
|
||||
MilitaryAttackManager.prototype.ungarrisonAll = function(gameState) {
|
||||
debug("ungarrison units");
|
||||
|
||||
this.getGarrisonBuildings(gameState).forEach(function(bldg){
|
||||
bldg.unloadAll();
|
||||
});
|
||||
|
||||
for ( var i in this.garrisoned) {
|
||||
if(this.assigned[i]) {
|
||||
this.unassignUnit(i);
|
||||
}
|
||||
if (this.entity(i)){
|
||||
this.entity(i).setMetadata("subrole","idle");
|
||||
}
|
||||
}
|
||||
this.garrisoned = {};
|
||||
};
|
||||
|
||||
//Garrisons citizens
|
||||
MilitaryAttackManager.prototype.garrisonCitizens = function(gameState) {
|
||||
var self = this;
|
||||
debug("garrison Citizens");
|
||||
gameState.getOwnEntities().forEach(function(ent) {
|
||||
var dogarrison = false;
|
||||
// Look for workers which have a position (i.e. not garrisoned)
|
||||
if(ent.hasClass("Worker") && ent.position()) {
|
||||
for (id in self.enemyAttackers) {
|
||||
if(self.entity(id).visionRange() >= VectorDistance(self.entity(id).position(),ent.position())) {
|
||||
dogarrison = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(dogarrison) {
|
||||
self.garrisonUnit(gameState,ent.id());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
// garrison the soldiers
|
||||
MilitaryAttackManager.prototype.garrisonSoldiers = function(gameState) {
|
||||
debug("garrison Soldiers");
|
||||
var units = this.getAvailableUnits(this.countAvailableUnits());
|
||||
for (var i in units) {
|
||||
this.garrisonUnit(gameState,units[i]);
|
||||
if(!this.garrisoned[units[i]]) {
|
||||
this.unassignUnit(units[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.garrisonUnit = function(gameState,id) {
|
||||
if (this.entity(id).position() === undefined){
|
||||
return;
|
||||
}
|
||||
var garrisonBuildings = this.getGarrisonBuildings(gameState).toEntityArray();
|
||||
var bldgDistance = [];
|
||||
for (var i in garrisonBuildings) {
|
||||
var bldg = garrisonBuildings[i];
|
||||
if(bldg.garrisoned().length <= bldg.garrisonMax()) {
|
||||
bldgDistance.push([i,VectorDistance(bldg.position(),this.entity(id).position())]);
|
||||
}
|
||||
}
|
||||
if(bldgDistance.length > 0) {
|
||||
bldgDistance.sort(function(a,b) { return (a[1]-b[1]); });
|
||||
var building = garrisonBuildings[bldgDistance[0][0]];
|
||||
//debug("garrison id "+id+"into building "+building.id()+"walking distance "+bldgDistance[0][1]);
|
||||
this.entity(id).garrison(building);
|
||||
this.garrisoned[id] = true;
|
||||
this.entity(id).setMetadata("subrole","garrison");
|
||||
}
|
||||
};
|
||||
|
||||
// return count of enemy buildings for a given building class
|
||||
MilitaryAttackManager.prototype.getEnemyBuildings = function(gameState,cls) {
|
||||
var targets = gameState.entities.filter(function(ent) {
|
||||
return (gameState.isEntityEnemy(ent) && ent.hasClass("Structure") && ent.hasClass(cls) && ent.owner() !== 0);
|
||||
});
|
||||
return targets;
|
||||
};
|
||||
|
||||
// return count of own buildings for a given building class
|
||||
MilitaryAttackManager.prototype.getGarrisonBuildings = function(gameState) {
|
||||
var targets = gameState.getOwnEntities().filter(function(ent) {
|
||||
return (ent.hasClass("Structure") && ent.garrisonableClasses());
|
||||
});
|
||||
return targets;
|
||||
};
|
||||
|
||||
// return n available units and makes these units unavailable
|
||||
MilitaryAttackManager.prototype.getAvailableUnits = function(n) {
|
||||
var ret = [];
|
||||
var count = 0;
|
||||
for (var i in this.unassigned) {
|
||||
ret.push(+i);
|
||||
delete this.unassigned[i];
|
||||
this.assigned[i] = true;
|
||||
this.entity(i).setMetadata("role", "soldier");
|
||||
this.entity(i).setMetadata("subrole", "unavailable");
|
||||
count++;
|
||||
if (count >= n) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Takes a single unit id, and marks it unassigned
|
||||
MilitaryAttackManager.prototype.unassignUnit = function(unit){
|
||||
this.unassigned[unit] = true;
|
||||
this.assigned[unit] = false;
|
||||
};
|
||||
|
||||
// Takes an array of unit id's and marks all of them unassigned
|
||||
MilitaryAttackManager.prototype.unassignUnits = function(units){
|
||||
for (var i in units){
|
||||
this.unassigned[unit[i]] = true;
|
||||
this.assigned[unit[i]] = false;
|
||||
}
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.countAvailableUnits = function(){
|
||||
var count = 0;
|
||||
for (var i in this.unassigned){
|
||||
if (this.unassigned[i]){
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.handleEvents = function(gameState, events) {
|
||||
for (var i in events) {
|
||||
var e = events[i];
|
||||
|
||||
if (e.type === "Destroy") {
|
||||
var id = e.msg.entity;
|
||||
delete this.unassigned[id];
|
||||
delete this.assigned[id];
|
||||
delete this.soldiers[id];
|
||||
var metadata = e.msg.metadata[gameState.ai._player];
|
||||
if (metadata && metadata.attacking){
|
||||
var attacking = this.entity(metadata.attacking);
|
||||
if (attacking && attacking.getMetadata('attackers')){
|
||||
var attackers = attacking.getMetadata('attackers');
|
||||
attackers.splice(attackers.indexOf(metadata.attacking), 1);
|
||||
attacking.setMetadata('attackers', attackers);
|
||||
}
|
||||
}
|
||||
if (metadata && metadata.attackers){
|
||||
for (var i in metadata.attackers){
|
||||
var attacker = this.entity(metadata.attackers[i]);
|
||||
if (attacker && attacker.getMetadata('attacking')){
|
||||
attacker.deleteMetadata('attacking');
|
||||
this.unassignUnit(attacker.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Takes an entity id and returns an entity object or false if there is no entity with that id
|
||||
// Also sends a debug message warning if the id has no entity
|
||||
MilitaryAttackManager.prototype.entity = function(id) {
|
||||
if (this.gameState.entities._entities[id]) {
|
||||
return new Entity(this.gameState.ai, this.gameState.entities._entities[id]);
|
||||
}else{
|
||||
debug("Entity " + id + " requested does not exist");
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// 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;
|
||||
for (var i in this.unassigned){
|
||||
if (this.unassigned[i]){
|
||||
strength += this.getUnitStrength(this.entity(i));
|
||||
}
|
||||
}
|
||||
return strength;
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.getEnemySoldiers = function(gameState){
|
||||
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.countEntitiesAndQueuedWithType(gameState.applyCiv('structures/{civ}_scout_tower'))
|
||||
+ queues.defenceBuilding.totalLength() <= gameState.getBuildLimits()["ScoutTower"]) {
|
||||
queues.defenceBuilding.addItem(new BuildingConstructionPlan(gameState, 'structures/{civ}_scout_tower'));
|
||||
}
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
|
||||
|
||||
Engine.ProfileStart("military update");
|
||||
this.gameState = gameState;
|
||||
|
||||
this.handleEvents(gameState, events);
|
||||
|
||||
// this.attackElephants(gameState);
|
||||
this.registerSoldiers(gameState);
|
||||
this.defence(gameState);
|
||||
this.buildDefences(gameState, queues);
|
||||
|
||||
// Continually try training new units, in batches of 5
|
||||
if (queues.citizenSoldier.length() < 6) {
|
||||
var newUnit = this.findBestNewUnit(gameState, queues.citizenSoldier, this.uCitizenSoldier);
|
||||
if (newUnit){
|
||||
queues.citizenSoldier.addItem(new UnitTrainingPlan(gameState, newUnit, {
|
||||
"role" : "soldier"
|
||||
}, 5));
|
||||
}
|
||||
}
|
||||
if (queues.advancedSoldier.length() < 2) {
|
||||
var newUnit = this.findBestNewUnit(gameState, queues.advancedSoldier, this.uAdvanced);
|
||||
if (newUnit){
|
||||
queues.advancedSoldier.addItem(new UnitTrainingPlan(gameState, newUnit, {
|
||||
"role" : "soldier"
|
||||
}, 5));
|
||||
}
|
||||
}
|
||||
if (queues.siege.length() < 4) {
|
||||
var newUnit = this.findBestNewUnit(gameState, queues.siege, this.uSiege);
|
||||
if (newUnit){
|
||||
queues.siege.addItem(new UnitTrainingPlan(gameState, newUnit, {
|
||||
"role" : "soldier"
|
||||
}, 2));
|
||||
}
|
||||
}
|
||||
|
||||
// Build more military buildings
|
||||
// TODO: make military building better
|
||||
if (gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) > 30) {
|
||||
if (gameState.countEntitiesAndQueuedWithType(gameState.applyCiv(this.bModerate[0]))
|
||||
+ queues.militaryBuilding.totalLength() < 1) {
|
||||
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bModerate[0]));
|
||||
}
|
||||
}
|
||||
//build advanced military buildings
|
||||
if (gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) >
|
||||
gameState.ai.modules[0].targetNumWorkers * 0.8){
|
||||
if (queues.militaryBuilding.totalLength() === 0){
|
||||
for (var i in this.bAdvanced){
|
||||
if (gameState.countEntitiesAndQueuedWithType(gameState.applyCiv(this.bAdvanced[i])) < 1){
|
||||
queues.militaryBuilding.addItem(new BuildingConstructionPlan(gameState, this.bAdvanced[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for attack plans which can be executed
|
||||
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]);
|
||||
this.availableAttacks.splice(i, 1, new this.attackManagers[i](gameState, this));
|
||||
}
|
||||
}
|
||||
// Keep current attacks updated
|
||||
for (i in this.currentAttacks){
|
||||
this.currentAttacks[i].update(gameState, this, events);
|
||||
}
|
||||
|
||||
// Set unassigned to be workers
|
||||
for (var i in this.unassigned){
|
||||
if (this.entity(i).hasClass("CitizenSoldier") && ! this.entity(i).hasClass("Cavalry")){
|
||||
this.entity(i).setMetadata("role", "worker");
|
||||
}
|
||||
}
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
126
binaries/data/mods/public/simulation/ai/qbot/plan-building.js
Normal file
126
binaries/data/mods/public/simulation/ai/qbot/plan-building.js
Normal file
@ -0,0 +1,126 @@
|
||||
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{
|
||||
// Not 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
|
||||
};
|
||||
};
|
@ -0,0 +1,56 @@
|
||||
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;
|
||||
};
|
143
binaries/data/mods/public/simulation/ai/qbot/qbot.js
Normal file
143
binaries/data/mods/public/simulation/ai/qbot/qbot.js
Normal file
@ -0,0 +1,143 @@
|
||||
|
||||
function QBotAI(settings) {
|
||||
BaseAI.call(this, settings);
|
||||
|
||||
this.turn = 0;
|
||||
|
||||
this.modules = [ new EconomyManager(), new MilitaryAttackManager(), 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 = [];
|
||||
|
||||
var priorities = {
|
||||
house : 500,
|
||||
citizenSoldier : 100,
|
||||
villager : 100,
|
||||
economicBuilding : 30,
|
||||
field: 4,
|
||||
advancedSoldier : 30,
|
||||
siege : 10,
|
||||
militaryBuilding : 30,
|
||||
defenceBuilding: 5,
|
||||
civilCentre: 1000
|
||||
};
|
||||
this.queueManager = new QueueManager(this.queues, priorities);
|
||||
|
||||
this.firstTime = true;
|
||||
|
||||
this.savedEvents = [];
|
||||
|
||||
this.toUpdate = [];
|
||||
}
|
||||
|
||||
QBotAI.prototype = new BaseAI();
|
||||
|
||||
//Some modules need the gameState to fully initialise
|
||||
QBotAI.prototype.runInit = function(gameState){
|
||||
if (this.firstTime){
|
||||
for (var i = 0; i < this.modules.length; i++){
|
||||
if (this.modules[i].init){
|
||||
this.modules[i].init(gameState);
|
||||
}
|
||||
}
|
||||
|
||||
var myCivCentres = gameState.getOwnEntities().filter(function(ent) {
|
||||
return ent.hasClass("CivCentre");
|
||||
});
|
||||
|
||||
var filter = Filters.and(Filters.isEnemy(), Filters.byClass("CivCentre"));
|
||||
var enemyCivCentres = gameState.getEntities().filter(function(ent) {
|
||||
return ent.hasClass("CivCentre") && gameState.isEntityEnemy(ent);
|
||||
});
|
||||
|
||||
this.accessibility = new Accessibility(gameState, myCivCentres.toEntityArray()[0].position());
|
||||
|
||||
var pathFinder = new PathFinder(gameState);
|
||||
this.pathsToMe = pathFinder.getPaths(enemyCivCentres.toEntityArray()[0].position(), myCivCentres.toEntityArray()[0].position(), 'entryPoints');
|
||||
|
||||
this.firstTime = false;
|
||||
}
|
||||
};
|
||||
|
||||
QBotAI.prototype.registerUpdate = function(obj){
|
||||
this.toUpdate.push(obj);
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// Run these updates before the init so they don't get hammered by the initial creation
|
||||
// events at the start of the game.
|
||||
for (var i = 0; i < this.toUpdate.length; i++){
|
||||
this.toUpdate[i].update(gameState, this.savedEvents);
|
||||
}
|
||||
|
||||
this.runInit(gameState);
|
||||
|
||||
for (var i = 0; i < this.modules.length; i++){
|
||||
this.modules[i].update(gameState, this.queues, this.savedEvents);
|
||||
}
|
||||
|
||||
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++;
|
||||
};
|
||||
|
||||
var debugOn = false;
|
||||
|
||||
function debug(output){
|
||||
if (debugOn){
|
||||
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];
|
||||
}
|
||||
}
|
293
binaries/data/mods/public/simulation/ai/qbot/queue-manager.js
Normal file
293
binaries/data/mods/public/simulation/ai/qbot/queue-manager.js
Normal file
@ -0,0 +1,293 @@
|
||||
//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 (p in this.queues) {
|
||||
this.account[p] = 0;
|
||||
}
|
||||
this.curItemQueue = [];
|
||||
};
|
||||
|
||||
QueueManager.prototype.getAvailableResources = function(gameState) {
|
||||
var resources = gameState.getResources();
|
||||
for (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 (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 (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(){
|
||||
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));
|
||||
};
|
||||
|
||||
QueueManager.prototype.update = function(gameState) {
|
||||
Engine.ProfileStart("Queue Manager");
|
||||
//this.printQueues();
|
||||
|
||||
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 (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 (key in ratio){
|
||||
if (ratio[key] < ratioMin){
|
||||
ratioMin = ratio[key];
|
||||
ratioMinQueue = key;
|
||||
}
|
||||
}
|
||||
if (this.onlyUsesSpareAndUpdateSpare(this.queues[ratioMinQueue].getNext().getCost(), availableRes)){
|
||||
if (allSpare){
|
||||
for (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 (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();
|
||||
};
|
114
binaries/data/mods/public/simulation/ai/qbot/queue.js
Normal file
114
binaries/data/mods/public/simulation/ai/qbot/queue.js
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 (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;
|
||||
};
|
6
binaries/data/mods/public/simulation/ai/qbot/readme.txt
Normal file
6
binaries/data/mods/public/simulation/ai/qbot/readme.txt
Normal file
@ -0,0 +1,6 @@
|
||||
This is an AI for 0 AD 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.
|
||||
|
66
binaries/data/mods/public/simulation/ai/qbot/resources.js
Normal file
66
binaries/data/mods/public/simulation/ai/qbot/resources.js
Normal file
@ -0,0 +1,66 @@
|
||||
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;
|
||||
};
|
302
binaries/data/mods/public/simulation/ai/qbot/terrain-analysis.js
Normal file
302
binaries/data/mods/public/simulation/ai/qbot/terrain-analysis.js
Normal file
@ -0,0 +1,302 @@
|
||||
/*
|
||||
* 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){
|
||||
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){
|
||||
return p;
|
||||
}
|
||||
|
||||
// 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){
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
direction *= -1;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/*
|
||||
* PathFinder inherits from TerrainAnalysis
|
||||
*
|
||||
* Used to create a list of distinct paths between two points.
|
||||
*
|
||||
* Currently it works basically.
|
||||
*
|
||||
* TODO: Make this use territories.
|
||||
*/
|
||||
|
||||
|
||||
function PathFinder(gameState){
|
||||
this.TerrainAnalysis(gameState);
|
||||
|
||||
this.territoryMap = Map.createTerritoryMap(gameState);
|
||||
}
|
||||
|
||||
copyPrototype(PathFinder, TerrainAnalysis);
|
||||
|
||||
/*
|
||||
* Returns a list of distinct paths to the destination. Curerntly 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 = 30;
|
||||
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, -10000, 'constant');
|
||||
if (mode === 'entryPoints'){
|
||||
// returns the point where the path enters the blockPlacementRadius
|
||||
return blockPoint;
|
||||
}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));
|
||||
|
||||
// Put the value 1 in all accessible points on the map
|
||||
this.floodFill(start);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
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);
|
||||
|
||||
// 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]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Replace the old empty stack with the newly filled one.
|
||||
stack = newStack;
|
||||
newStack = [];
|
||||
}
|
||||
};
|
85
binaries/data/mods/public/simulation/ai/qbot/walkToCC.js
Normal file
85
binaries/data/mods/public/simulation/ai/qbot/walkToCC.js
Normal file
@ -0,0 +1,85 @@
|
||||
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);
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user