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(); }, });