forked from 0ad/0ad
Ykkrosh
8a0cbe009a
Make the AI behave better when it can't find any viable food sources. This was SVN commit r9046.
243 lines
6.1 KiB
JavaScript
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();
|
|
},
|
|
|
|
});
|