1
0
forked from 0ad/0ad
0ad/binaries/data/mods/public/simulation/ai/testbot/economy.js
Ykkrosh 8a0cbe009a Stop the AI wasting time chasing after any animals except chickens.
Make the AI behave better when it can't find any viable food sources.

This was SVN commit r9046.
2011-03-08 01:40:44 +00:00

243 lines
6.1 KiB
JavaScript

var EconomyManager = Class({
_init: function()
{
this.targetNumWorkers = 30; // minimum number of workers we want
this.targetNumBuilders = 5; // number of workers we want working on construction
// (This is a stupid design where we just construct certain numbers
// of certain buildings in sequence)
this.targetBuildings = [
{
"template": "structures/{civ}_civil_centre",
"priority": 500,
"count": 1,
},
{
"template": "structures/{civ}_house",
"priority": 100,
"count": 5,
},
{
"template": "structures/{civ}_barracks",
"priority": 75,
"count": 1,
},
{
"template": "structures/{civ}_field",
"priority": 50,
"count": 5,
},
];
// Relative proportions of workers to assign to each resource type
this.gatherWeights = {
"food": 150,
"wood": 100,
"stone": 50,
"metal": 100,
};
},
buildMoreBuildings: function(gameState, planGroups)
{
// Limit ourselves to constructing one building at a time
if (gameState.findFoundations().length)
return;
for each (var building in this.targetBuildings)
{
var numBuildings = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv(building.template));
// If we have too few, build another
if (numBuildings < building.count)
{
planGroups.economyConstruction.addPlan(building.priority,
new BuildingConstructionPlan(gameState, building.template)
);
}
}
},
trainMoreWorkers: function(gameState, planGroups)
{
// Count the workers in the world and in progress
var numWorkers = gameState.countEntitiesAndQueuedWithRole("worker");
// If we have too few, train another
// print("Want "+this.targetNumWorkers+" workers; got "+numWorkers+"\n");
if (numWorkers < this.targetNumWorkers)
{
planGroups.economyPersonnel.addPlan(100,
new UnitTrainingPlan(gameState,
"units/{civ}_support_female_citizen", 1, { "role": "worker" })
);
}
},
pickMostNeededResources: function(gameState)
{
var self = this;
// Find what resource type we're most in need of
var numGatherers = {};
for (var type in this.gatherWeights)
numGatherers[type] = 0;
gameState.getOwnEntitiesWithRole("worker").forEach(function(ent) {
if (ent.getMetadata("subrole") === "gatherer")
numGatherers[ent.getMetadata("gather-type")] += 1;
});
var types = Object.keys(this.gatherWeights);
types.sort(function(a, b) {
// Prefer fewer gatherers (divided by weight)
var va = numGatherers[a] / self.gatherWeights[a];
var vb = numGatherers[b] / self.gatherWeights[b];
return va - vb;
});
return types;
},
reassignRolelessUnits: function(gameState)
{
var roleless = gameState.getOwnEntitiesWithRole(undefined);
roleless.forEach(function(ent) {
if (ent.hasClass("Worker"))
ent.setMetadata("role", "worker");
else
ent.setMetadata("role", "unknown");
});
},
reassignIdleWorkers: function(gameState, planGroups)
{
var self = this;
// Search for idle workers, and tell them to gather resources
// Maybe just pick a random nearby resource type at first;
// later we could add some timer that redistributes workers based on
// resource demand.
var idleWorkers = gameState.getOwnEntitiesWithRole("worker").filter(function(ent) {
return ent.isIdle();
});
if (idleWorkers.length)
{
var resourceSupplies = gameState.findResourceSupplies();
idleWorkers.forEach(function(ent) {
var types = self.pickMostNeededResources(gameState);
for each (var type in types)
{
// Make sure there's actually some of that type
if (!resourceSupplies[type])
continue;
// Pick the closest one.
// TODO: we should care about distance to dropsites, not (just) to the worker,
// and gather rates of workers
var workerPosition = ent.position();
var supplies = [];
resourceSupplies[type].forEach(function(supply) {
// Skip targets that are too hard to hunt
if (supply.entity.isUnhuntable())
return;
var dist = VectorDistance(supply.position, workerPosition);
// Skip targets that are far too far away (e.g. in the enemy base)
if (dist > 512)
return;
supplies.push({ dist: dist, entity: supply.entity });
});
supplies.sort(function (a, b) {
// Prefer smaller distances
if (a.dist != b.dist)
return a.dist - b.dist;
return false;
});
// Start gathering
if (supplies.length)
{
ent.gather(supplies[0].entity);
ent.setMetadata("subrole", "gatherer");
ent.setMetadata("gather-type", type);
return;
}
}
// Couldn't find any types to gather
ent.setMetadata("subrole", "idle");
});
}
},
assignToFoundations: function(gameState, planGroups)
{
// If we have some foundations, and we don't have enough builder-workers,
// try reassigning some other workers who are nearby
var foundations = gameState.findFoundations();
// Check if nothing to build
if (!foundations.length)
return;
var workers = gameState.getOwnEntitiesWithRole("worker");
var builderWorkers = workers.filter(function(ent) {
return (ent.getMetadata("subrole") === "builder");
});
// Check if enough builders
var extraNeeded = this.targetNumBuilders - builderWorkers.length;
if (extraNeeded <= 0)
return;
// Pick non-builders who are closest to the first foundation,
// and tell them to start building it
var target = foundations.toEntityArray()[0];
var nonBuilderWorkers = workers.filter(function(ent) {
return (ent.getMetadata("subrole") !== "builder");
});
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), extraNeeded);
// Order each builder individually, not as a formation
nearestNonBuilders.forEach(function(ent) {
ent.repair(target);
ent.setMetadata("subrole", "builder");
});
},
update: function(gameState, planGroups)
{
Engine.ProfileStart("economy update");
this.reassignRolelessUnits(gameState);
this.buildMoreBuildings(gameState, planGroups);
this.trainMoreWorkers(gameState, planGroups);
this.reassignIdleWorkers(gameState, planGroups);
this.assignToFoundations(gameState, planGroups);
Engine.ProfileStop();
},
});