forked from 0ad/0ad
544 lines
19 KiB
JavaScript
544 lines
19 KiB
JavaScript
var EconomyManager = function() {
|
|
this.targetNumBuilders = 5; // number of workers we want building stuff
|
|
this.targetNumFields = 3;
|
|
|
|
this.resourceMaps = {}; // Contains maps showing the density of wood, stone and metal
|
|
|
|
this.setCount = 0; //stops villagers being reassigned to other resources too frequently, count a set number of
|
|
//turns before trying to reassign them.
|
|
|
|
this.dropsiteNumbers = {wood: 2, stone: 1, metal: 1};
|
|
};
|
|
// More initialisation for stuff that needs the gameState
|
|
EconomyManager.prototype.init = function(gameState){
|
|
this.targetNumWorkers = Math.max(Math.floor(gameState.getPopulationMax()/3), 1);
|
|
};
|
|
|
|
EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) {
|
|
// Count the workers in the world and in progress
|
|
var numWorkers = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("units/{civ}_support_female_citizen"));
|
|
numWorkers += queues.villager.countTotalQueuedUnits();
|
|
|
|
// If we have too few, train more
|
|
if (numWorkers < this.targetNumWorkers) {
|
|
for ( var i = 0; i < this.targetNumWorkers - numWorkers; i++) {
|
|
queues.villager.addItem(new UnitTrainingPlan(gameState, "units/{civ}_support_female_citizen", {
|
|
"role" : "worker"
|
|
}));
|
|
}
|
|
}
|
|
};
|
|
|
|
// Pick the resource which most needs another worker
|
|
EconomyManager.prototype.pickMostNeededResources = function(gameState) {
|
|
|
|
var self = this;
|
|
|
|
// Find what resource type we're most in need of
|
|
if (!gameState.turnCache["gather-weights-calculated"]){
|
|
this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState);
|
|
gameState.turnCache["gather-weights-calculated"] = true;
|
|
}
|
|
|
|
var numGatherers = {};
|
|
for ( var type in this.gatherWeights){
|
|
numGatherers[type] = gameState.updatingCollection("workers-gathering-" + type,
|
|
Filters.byMetadata("gather-type", type), gameState.getOwnEntitiesByRole("worker")).length;
|
|
}
|
|
|
|
var types = Object.keys(this.gatherWeights);
|
|
types.sort(function(a, b) {
|
|
// Prefer fewer gatherers (divided by weight)
|
|
var va = numGatherers[a] / (self.gatherWeights[a]+1);
|
|
var vb = numGatherers[b] / (self.gatherWeights[b]+1);
|
|
return va-vb;
|
|
});
|
|
|
|
return types;
|
|
};
|
|
|
|
EconomyManager.prototype.reassignRolelessUnits = function(gameState) {
|
|
//TODO: Move this out of the economic section
|
|
var roleless = gameState.getOwnEntitiesByRole(undefined);
|
|
|
|
roleless.forEach(function(ent) {
|
|
if (ent.hasClass("Worker")){
|
|
ent.setMetadata("role", "worker");
|
|
}else if(ent.hasClass("CitizenSoldier") || ent.hasClass("Champion")){
|
|
ent.setMetadata("role", "soldier");
|
|
}else{
|
|
ent.setMetadata("role", "unknown");
|
|
}
|
|
});
|
|
};
|
|
|
|
// If the numbers of workers on the resources is unbalanced then set some of workers to idle so
|
|
// they can be reassigned by reassignIdleWorkers.
|
|
EconomyManager.prototype.setWorkersIdleByPriority = function(gameState){
|
|
this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState);
|
|
|
|
var numGatherers = {};
|
|
var totalGatherers = 0;
|
|
var totalWeight = 0;
|
|
for ( var type in this.gatherWeights){
|
|
numGatherers[type] = 0;
|
|
totalWeight += this.gatherWeights[type];
|
|
}
|
|
|
|
gameState.getOwnEntitiesByRole("worker").forEach(function(ent) {
|
|
if (ent.getMetadata("subrole") === "gatherer"){
|
|
numGatherers[ent.getMetadata("gather-type")] += 1;
|
|
totalGatherers += 1;
|
|
}
|
|
});
|
|
|
|
for ( var type in this.gatherWeights){
|
|
var allocation = Math.floor(totalGatherers * (this.gatherWeights[type]/totalWeight));
|
|
if (allocation < numGatherers[type]){
|
|
var numToTake = numGatherers[type] - allocation;
|
|
gameState.getOwnEntitiesByRole("worker").forEach(function(ent) {
|
|
if (ent.getMetadata("subrole") === "gatherer" && ent.getMetadata("gather-type") === type && numToTake > 0){
|
|
ent.setMetadata("subrole", "idle");
|
|
numToTake -= 1;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
EconomyManager.prototype.reassignIdleWorkers = function(gameState) {
|
|
|
|
var self = this;
|
|
|
|
// Search for idle workers, and tell them to gather resources based on demand
|
|
var filter = Filters.or(Filters.isIdle(), Filters.byMetadata("subrole", "idle"));
|
|
var idleWorkers = gameState.updatingCollection("idle-workers", filter, gameState.getOwnEntitiesByRole("worker"));
|
|
|
|
if (idleWorkers.length) {
|
|
var resourceSupplies;
|
|
|
|
idleWorkers.forEach(function(ent) {
|
|
// Check that the worker isn't garrisoned
|
|
if (ent.position() === undefined){
|
|
return;
|
|
}
|
|
|
|
var types = self.pickMostNeededResources(gameState);
|
|
|
|
ent.setMetadata("subrole", "gatherer");
|
|
ent.setMetadata("gather-type", types[0]);
|
|
});
|
|
}
|
|
};
|
|
|
|
EconomyManager.prototype.workersBySubrole = function(gameState, subrole) {
|
|
var workers = gameState.getOwnEntitiesByRole("worker");
|
|
return gameState.updatingCollection("subrole-" + subrole, Filters.byMetadata("subrole", subrole), workers);
|
|
};
|
|
|
|
EconomyManager.prototype.assignToFoundations = function(gameState) {
|
|
// If we have some foundations, and we don't have enough
|
|
// builder-workers,
|
|
// try reassigning some other workers who are nearby
|
|
|
|
var foundations = gameState.getOwnFoundations();
|
|
|
|
// Check if nothing to build
|
|
if (!foundations.length){
|
|
return;
|
|
}
|
|
|
|
var workers = gameState.getOwnEntitiesByRole("worker");
|
|
|
|
var builderWorkers = this.workersBySubrole(gameState, "builder");
|
|
|
|
// Check if enough builders
|
|
var extraNeeded = this.targetNumBuilders - builderWorkers.length;
|
|
if (extraNeeded <= 0){
|
|
return;
|
|
}
|
|
|
|
// Pick non-builders who are closest to the first foundation,
|
|
// and tell them to start building it
|
|
|
|
var target = foundations.toEntityArray()[0];
|
|
|
|
var nonBuilderWorkers = workers.filter(function(ent) {
|
|
// check position so garrisoned units aren't tasked
|
|
return (ent.getMetadata("subrole") !== "builder" && ent.position() !== undefined);
|
|
});
|
|
|
|
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), extraNeeded);
|
|
|
|
// Order each builder individually, not as a formation
|
|
nearestNonBuilders.forEach(function(ent) {
|
|
ent.setMetadata("subrole", "builder");
|
|
ent.setMetadata("target-foundation", target);
|
|
});
|
|
};
|
|
|
|
EconomyManager.prototype.buildMoreFields = function(gameState, queues) {
|
|
// give time for treasures to be gathered
|
|
if (gameState.getTimeElapsed() < 30 * 1000)
|
|
return;
|
|
|
|
var numFood = 0;
|
|
|
|
gameState.updatingCollection("active-dropsite-food", Filters.byMetadata("active-dropsite-food", true),
|
|
gameState.getOwnDropsites("food")).forEach(function (dropsite){
|
|
numFood += dropsite.getMetadata("nearby-resources-food").length;
|
|
});
|
|
|
|
numFood += gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_field"));
|
|
numFood += queues.field.totalLength();
|
|
|
|
for ( var i = numFood; i < this.targetNumFields; i++) {
|
|
queues.field.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_field"));
|
|
}
|
|
};
|
|
|
|
// If all the CC's are destroyed then build a new one
|
|
EconomyManager.prototype.buildNewCC= function(gameState, queues) {
|
|
var numCCs = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_civil_centre"));
|
|
numCCs += queues.civilCentre.totalLength();
|
|
|
|
for ( var i = numCCs; i < 1; i++) {
|
|
queues.civilCentre.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre"));
|
|
}
|
|
};
|
|
|
|
//creates and maintains a map of tree density
|
|
EconomyManager.prototype.updateResourceMaps = function(gameState, events){
|
|
// The weight of the influence function is amountOfResource/decreaseFactor
|
|
var decreaseFactor = {'wood': 15, 'stone': 100, 'metal': 100, 'food': 20};
|
|
// This is the maximum radius of the influence
|
|
var radius = {'wood':13, 'stone': 10, 'metal': 10, 'food': 10};
|
|
|
|
var self = this;
|
|
|
|
for (var resource in radius){
|
|
// if there is no resourceMap create one with an influence for everything with that resource
|
|
if (! this.resourceMaps[resource]){
|
|
this.resourceMaps[resource] = new Map(gameState);
|
|
|
|
var supplies = gameState.getResourceSupplies(resource);
|
|
supplies.forEach(function(ent){
|
|
if (!ent.position()){
|
|
return;
|
|
}
|
|
var x = Math.round(ent.position()[0] / gameState.cellSize);
|
|
var z = Math.round(ent.position()[1] / gameState.cellSize);
|
|
var strength = Math.round(ent.resourceSupplyMax()/decreaseFactor[resource]);
|
|
self.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
|
|
});
|
|
}
|
|
// TODO: fix for treasure and move out of loop
|
|
// Look for destroy events and subtract the entities original influence from the resourceMap
|
|
for (var i in events) {
|
|
var e = events[i];
|
|
|
|
if (e.type === "Destroy") {
|
|
if (e.msg.entityObj){
|
|
var ent = e.msg.entityObj;
|
|
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic === resource){
|
|
var x = Math.round(ent.position()[0] / gameState.cellSize);
|
|
var z = Math.round(ent.position()[1] / gameState.cellSize);
|
|
var strength = Math.round(ent.resourceSupplyMax()/decreaseFactor[resource]);
|
|
this.resourceMaps[resource].addInfluence(x, z, radius[resource], -strength);
|
|
}
|
|
}
|
|
}else if (e.type === "Create") {
|
|
if (e.msg.entityObj){
|
|
var ent = e.msg.entityObj;
|
|
if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic === resource){
|
|
var x = Math.round(ent.position()[0] / gameState.cellSize);
|
|
var z = Math.round(ent.position()[1] / gameState.cellSize);
|
|
var strength = Math.round(ent.resourceSupplyMax()/decreaseFactor[resource]);
|
|
this.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//this.resourceMaps['wood'].dumpIm("tree_density.png");
|
|
};
|
|
|
|
// Returns the position of the best place to build a new dropsite for the specified resource
|
|
EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource){
|
|
// A map which gives a positive weight for all CCs and adds a negative weight near all dropsites
|
|
var friendlyTiles = new Map(gameState);
|
|
gameState.getOwnEntities().forEach(function(ent) {
|
|
// We want to build near a CC of ours
|
|
if (ent.hasClass("CivCentre")){
|
|
var infl = 200;
|
|
|
|
var pos = ent.position();
|
|
var x = Math.round(pos[0] / gameState.cellSize);
|
|
var z = Math.round(pos[1] / gameState.cellSize);
|
|
friendlyTiles.addInfluence(x, z, infl, 0.1 * infl);
|
|
friendlyTiles.addInfluence(x, z, infl/2, 0.1 * infl);
|
|
}
|
|
// We don't want multiple dropsites at one spot so add a negative for all dropsites
|
|
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){
|
|
var infl = 20;
|
|
|
|
var pos = ent.position();
|
|
var x = Math.round(pos[0] / gameState.cellSize);
|
|
var z = Math.round(pos[1] / gameState.cellSize);
|
|
|
|
friendlyTiles.addInfluence(x, z, infl, -50, 'quadratic');
|
|
}
|
|
});
|
|
|
|
// Multiply by tree density to get a combination of the two maps
|
|
friendlyTiles.multiply(this.resourceMaps[resource]);
|
|
|
|
//friendlyTiles.dumpIm(resource + "_density_fade.png", 10000);
|
|
|
|
var obstructions = Map.createObstructionMap(gameState);
|
|
obstructions.expandInfluences();
|
|
|
|
var bestIdx = friendlyTiles.findBestTile(4, obstructions)[0];
|
|
|
|
// Convert from 1d map pixel coordinates to game engine coordinates
|
|
var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
|
|
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize;
|
|
return [x,z];
|
|
};
|
|
|
|
EconomyManager.prototype.updateResourceConcentrations = function(gameState){
|
|
var self = this;
|
|
var resources = ["food", "wood", "stone", "metal"];
|
|
for (var key in resources){
|
|
var resource = resources[key];
|
|
gameState.getOwnEntities().forEach(function(ent) {
|
|
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){
|
|
var radius = 14;
|
|
|
|
var pos = ent.position();
|
|
var x = Math.round(pos[0] / gameState.cellSize);
|
|
var z = Math.round(pos[1] / gameState.cellSize);
|
|
|
|
var quantity = self.resourceMaps[resource].sumInfluence(x, z, radius);
|
|
|
|
ent.setMetadata("resourceQuantity_" + resource, quantity);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
// Stores lists of nearby resources
|
|
EconomyManager.prototype.updateNearbyResources = function(gameState){
|
|
var self = this;
|
|
var resources = ["food", "wood", "stone", "metal"];
|
|
var resourceSupplies;
|
|
var radius = 100;
|
|
for (var key in resources){
|
|
var resource = resources[key];
|
|
|
|
gameState.getOwnDropsites(resource).forEach(function(ent) {
|
|
if (ent.getMetadata("nearby-resources-" + resource) === undefined){
|
|
var filterPos = Filters.byStaticDistance(ent.position(), radius);
|
|
|
|
var collection = gameState.getResourceSupplies(resource).filter(filterPos);
|
|
collection.registerUpdates();
|
|
|
|
ent.setMetadata("nearby-resources-" + resource, collection);
|
|
ent.setMetadata("active-dropsite-" + resource, true);
|
|
}
|
|
|
|
if (ent.getMetadata("nearby-resources-" + resource).length === 0){
|
|
ent.setMetadata("active-dropsite-" + resource, false);
|
|
}else{
|
|
ent.setMetadata("active-dropsite-" + resource, true);
|
|
}
|
|
/*
|
|
// Make resources glow wildly
|
|
if (resource == "food"){
|
|
ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){
|
|
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [10,0,0]});
|
|
});
|
|
}
|
|
if (resource == "wood"){
|
|
ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){
|
|
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,10,0]});
|
|
});
|
|
}
|
|
if (resource == "metal"){
|
|
ent.getMetadata("nearby-resources-" + resource).forEach(function(ent){
|
|
Engine.PostCommand({"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0,10]});
|
|
});
|
|
}*/
|
|
});
|
|
}
|
|
};
|
|
|
|
//return the number of resource dropsites with an acceptable amount of the resource nearby
|
|
EconomyManager.prototype.checkResourceConcentrations = function(gameState, resource){
|
|
//TODO: make these values adaptive
|
|
var requiredInfluence = {wood: 16000, stone: 300, metal: 300};
|
|
var count = 0;
|
|
gameState.getOwnEntities().forEach(function(ent) {
|
|
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){
|
|
var quantity = ent.getMetadata("resourceQuantity_" + resource);
|
|
|
|
if (quantity >= requiredInfluence[resource]){
|
|
count ++;
|
|
}
|
|
}
|
|
});
|
|
return count;
|
|
};
|
|
|
|
EconomyManager.prototype.buildMarket = function(gameState, queues){
|
|
if (gameState.getTimeElapsed() > 600 * 1000){
|
|
if (queues.economicBuilding.totalLength() === 0 &&
|
|
gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_market")) === 0){
|
|
//only ever build one storehouse/CC/market at a time
|
|
queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_market"));
|
|
}
|
|
}
|
|
};
|
|
|
|
EconomyManager.prototype.buildDropsites = function(gameState, queues){
|
|
if (queues.economicBuilding.totalLength() === 0 &&
|
|
gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_storehouse")) === 0 &&
|
|
gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_civil_centre")) === 0){
|
|
//only ever build one storehouse/CC/market at a time
|
|
if (gameState.getTimeElapsed() > 30 * 1000){
|
|
for (var resource in this.dropsiteNumbers){
|
|
if (this.checkResourceConcentrations(gameState, resource) < this.dropsiteNumbers[resource]){
|
|
var spot = this.getBestResourceBuildSpot(gameState, resource);
|
|
|
|
var myCivCentres = gameState.getOwnEntities().filter(function(ent) {
|
|
if (!ent.hasClass("CivCentre") || ent.position() === undefined){
|
|
return false;
|
|
}
|
|
var dx = (spot[0]-ent.position()[0]);
|
|
var dy = (spot[1]-ent.position()[1]);
|
|
var dist2 = dx*dx + dy*dy;
|
|
return (ent.hasClass("CivCentre") && dist2 < 180*180);
|
|
});
|
|
|
|
if (myCivCentres.length === 0){
|
|
queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre", spot));
|
|
}else{
|
|
queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_storehouse", spot));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
EconomyManager.prototype.update = function(gameState, queues, events) {
|
|
Engine.ProfileStart("economy update");
|
|
|
|
this.reassignRolelessUnits(gameState);
|
|
|
|
this.buildNewCC(gameState,queues);
|
|
|
|
Engine.ProfileStart("Train workers and build farms");
|
|
this.trainMoreWorkers(gameState, queues);
|
|
|
|
this.buildMoreFields(gameState, queues);
|
|
Engine.ProfileStop();
|
|
|
|
//Later in the game we want to build stuff faster.
|
|
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.5) {
|
|
this.targetNumBuilders = 10;
|
|
}else{
|
|
this.targetNumBuilders = 5;
|
|
}
|
|
|
|
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.8) {
|
|
this.dropsiteNumbers = {wood: 3, stone: 2, metal: 2};
|
|
}else{
|
|
this.dropsiteNumbers = {wood: 2, stone: 1, metal: 1};
|
|
}
|
|
|
|
Engine.ProfileStart("Update Resource Maps and Concentrations");
|
|
this.updateResourceMaps(gameState, events);
|
|
this.updateResourceConcentrations(gameState);
|
|
this.updateNearbyResources(gameState);
|
|
Engine.ProfileStop();
|
|
|
|
Engine.ProfileStart("Build new Dropsites");
|
|
this.buildDropsites(gameState, queues);
|
|
Engine.ProfileStop();
|
|
|
|
this.buildMarket(gameState, queues);
|
|
|
|
// TODO: implement a timer based system for this
|
|
this.setCount += 1;
|
|
if (this.setCount >= 20){
|
|
this.setWorkersIdleByPriority(gameState);
|
|
this.setCount = 0;
|
|
}
|
|
|
|
Engine.ProfileStart("Reassign Idle Workers");
|
|
this.reassignIdleWorkers(gameState);
|
|
Engine.ProfileStop();
|
|
|
|
Engine.ProfileStart("Swap Workers");
|
|
var gathererGroups = {};
|
|
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){
|
|
var key = uneval(ent.resourceGatherRates());
|
|
if (!gathererGroups[key]){
|
|
gathererGroups[key] = {"food": [], "wood": [], "metal": [], "stone": []};
|
|
}
|
|
if (ent.getMetadata("gather-type") in gathererGroups[key]){
|
|
gathererGroups[key][ent.getMetadata("gather-type")].push(ent);
|
|
}
|
|
});
|
|
|
|
for (var i in gathererGroups){
|
|
for (var j in gathererGroups){
|
|
var a = eval(i);
|
|
var b = eval(j);
|
|
if (a["food.grain"]/b["food.grain"] > a["wood.tree"]/b["wood.tree"] && gathererGroups[i]["wood"].length > 0 && gathererGroups[j]["food"].length > 0){
|
|
for (var k = 0; k < Math.min(gathererGroups[i]["wood"].length, gathererGroups[j]["food"].length); k++){
|
|
gathererGroups[i]["wood"][k].setMetadata("gather-type", "food");
|
|
gathererGroups[j]["food"][k].setMetadata("gather-type", "wood");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Engine.ProfileStop();
|
|
|
|
Engine.ProfileStart("Assign builders");
|
|
this.assignToFoundations(gameState);
|
|
Engine.ProfileStop();
|
|
|
|
Engine.ProfileStart("Run Workers");
|
|
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){
|
|
if (!ent.getMetadata("worker-object")){
|
|
ent.setMetadata("worker-object", new Worker(ent));
|
|
}
|
|
ent.getMetadata("worker-object").update(gameState);
|
|
});
|
|
|
|
// Gatherer count updates for non-workers
|
|
var filter = Filters.and(Filters.not(Filters.byMetadata("worker-object", undefined)),
|
|
Filters.not(Filters.byMetadata("role", "worker")));
|
|
gameState.updatingCollection("reassigned-workers", filter, gameState.getOwnEntities()).forEach(function(ent){
|
|
ent.getMetadata("worker-object").updateGathererCounts(gameState);
|
|
});
|
|
|
|
// Gatherer count updates for destroyed units
|
|
for (var i in events) {
|
|
var e = events[i];
|
|
|
|
if (e.type === "Destroy") {
|
|
if (e.msg.metadata && e.msg.metadata[gameState.getPlayerID()] && e.msg.metadata[gameState.getPlayerID()]["worker-object"]){
|
|
e.msg.metadata[gameState.getPlayerID()]["worker-object"].updateGathererCounts(gameState, true);
|
|
}
|
|
}
|
|
}
|
|
Engine.ProfileStop();
|
|
|
|
Engine.ProfileStop();
|
|
};
|