Advertisement
Guest User

Untitled

a guest
Jan 2nd, 2012
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. var EconomyManager = function() {
  2.     this.targetNumBuilders = 5; // number of workers we want building stuff
  3.     this.targetNumFields = 5;
  4.    
  5.     this.resourceMaps = {}; // Contains maps showing the density of wood, stone and metal
  6.    
  7.     this.setCount = 0;  //stops villagers being reassigned to other resources too frequently, count a set number of
  8.                         //turns before trying to reassign them.
  9.    
  10.     this.dropsiteNumbers = {wood: 2, stone: 1, metal: 1};
  11. };
  12. // More initialisation for stuff that needs the gameState
  13. EconomyManager.prototype.init = function(gameState){
  14.     this.targetNumWorkers = Math.floor(gameState.getPopulationMax()/3);
  15. };
  16.  
  17. EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) {
  18.     // Count the workers in the world and in progress
  19.     var numWorkers = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("units/{civ}_support_female_citizen"));
  20.     numWorkers += queues.villager.countTotalQueuedUnits();
  21.  
  22.     // If we have too few, train more
  23.     if (numWorkers < this.targetNumWorkers) {
  24.         for ( var i = 0; i < this.targetNumWorkers - numWorkers; i++) {
  25.             queues.villager.addItem(new UnitTrainingPlan(gameState, "units/{civ}_support_female_citizen", {
  26.                 "role" : "worker"
  27.             }));
  28.         }
  29.     }
  30. };
  31.  
  32. // Pick the resource which most needs another worker
  33. EconomyManager.prototype.pickMostNeededResources = function(gameState) {
  34.  
  35.     var self = this;
  36.  
  37.     // Find what resource type we're most in need of
  38.     this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState);
  39.  
  40.     var numGatherers = {};
  41.     for ( var type in this.gatherWeights)
  42.         numGatherers[type] = 0;
  43.  
  44.     gameState.getOwnEntitiesWithRole("worker").forEach(function(ent) {
  45.         if (ent.getMetadata("subrole") === "gatherer")
  46.             numGatherers[ent.getMetadata("gather-type")] += 1;
  47.     });
  48.  
  49.     var types = Object.keys(this.gatherWeights);
  50.     types.sort(function(a, b) {
  51.         // Prefer fewer gatherers (divided by weight)
  52.         var va = numGatherers[a] / (self.gatherWeights[a]+1);
  53.         var vb = numGatherers[b] / (self.gatherWeights[b]+1);
  54.         return va-vb;
  55.     });
  56.  
  57.     return types;
  58. };
  59.  
  60. EconomyManager.prototype.reassignRolelessUnits = function(gameState) {
  61.     //TODO: Move this out of the economic section
  62.     var roleless = gameState.getOwnEntitiesWithRole(undefined);
  63.  
  64.     roleless.forEach(function(ent) {
  65.         if (ent.hasClass("Worker")){
  66.             ent.setMetadata("role", "worker");
  67.         }else if(ent.hasClass("CitizenSoldier") || ent.hasClass("Super")){
  68.             ent.setMetadata("role", "soldier");
  69.         }else{
  70.             ent.setMetadata("role", "unknown");
  71.         }
  72.     });
  73. };
  74.  
  75. // If the numbers of workers on the resources is unbalanced then set some of workers to idle so
  76. // they can be reassigned by reassignIdleWorkers.
  77. EconomyManager.prototype.setWorkersIdleByPriority = function(gameState){
  78.     this.gatherWeights = gameState.ai.queueManager.futureNeeds(gameState);
  79.    
  80.     var numGatherers = {};
  81.     var totalGatherers = 0;
  82.     var totalWeight = 0;
  83.     for ( var type in this.gatherWeights){
  84.         numGatherers[type] = 0;
  85.         totalWeight += this.gatherWeights[type];
  86.     }
  87.  
  88.     gameState.getOwnEntitiesWithRole("worker").forEach(function(ent) {
  89.         if (ent.getMetadata("subrole") === "gatherer"){
  90.             numGatherers[ent.getMetadata("gather-type")] += 1;
  91.             totalGatherers += 1;
  92.         }
  93.     });
  94.  
  95.     for ( var type in this.gatherWeights){
  96.         var allocation = Math.floor(totalGatherers * (this.gatherWeights[type]/totalWeight));
  97.         if (allocation < numGatherers[type]){
  98.             var numToTake = numGatherers[type] - allocation;
  99.             gameState.getOwnEntitiesWithRole("worker").forEach(function(ent) {
  100.                 if (ent.getMetadata("subrole") === "gatherer" && ent.getMetadata("gather-type") === type && numToTake > 0){
  101.                     ent.setMetadata("subrole", "idle");
  102.                     numToTake -= 1;
  103.                 }
  104.             });
  105.         }
  106.     }
  107. };
  108.  
  109. EconomyManager.prototype.reassignIdleWorkers = function(gameState) {
  110.    
  111.     var self = this;
  112.  
  113.     // Search for idle workers, and tell them to gather resources based on demand
  114.  
  115.     var idleWorkers = gameState.getOwnEntitiesWithRole("worker").filter(function(ent) {
  116.         return (ent.isIdle() || ent.getMetadata("subrole") === "idle");
  117.     });
  118.    
  119.     if (idleWorkers.length) {
  120.         var resourceSupplies = gameState.findResourceSupplies();
  121.         var territoryMap = Map.createTerritoryMap(gameState);
  122.  
  123.         idleWorkers.forEach(function(ent) {
  124.             // Check that the worker isn't garrisoned
  125.             if (ent.position() === undefined){
  126.                 return;
  127.             }
  128.  
  129.             var types = self.pickMostNeededResources(gameState);
  130.             //debug("Most Needed Resources: " + uneval(types));
  131.             for ( var typeKey in types) {
  132.                 var type = types[typeKey];
  133.                 // Make sure there are actually some resources of that type
  134.                 if (!resourceSupplies[type]){
  135.                     debug("No " + type + " found!");
  136.                     continue;
  137.                 }
  138.                 var numSupplies = resourceSupplies[type].length;
  139.  
  140.                 // TODO: we should care about gather rates of workers
  141.                
  142.                 // Find the nearest dropsite for this resource from the worker
  143.                 var nearestDropsite = undefined;
  144.                 var minDropsiteDist = Math.min(); // set to infinity initially
  145.                 gameState.getOwnEntities().forEach(function(dropsiteEnt) {
  146.                     if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.resourceDropsiteTypes().indexOf(type) !== -1){
  147.                         if (dropsiteEnt.position() && dropsiteEnt.getMetadata("resourceQuantity_" + type) > 0){
  148.                             var dist = VectorDistance(ent.position(), dropsiteEnt.position());
  149.                             if (dist < minDropsiteDist){
  150.                                 nearestDropsite = dropsiteEnt;
  151.                                 minDropsiteDist = dist;
  152.                             }
  153.                         }
  154.                     }
  155.                 });
  156.                
  157.                 var workerPosition = ent.position();
  158.                 var supplies = [];
  159.                 var resources = [];
  160.                 var treasures = [];
  161.                 resourceSupplies[type].forEach(function(supply) {
  162.                     // Skip targets that are too hard to hunt
  163.                     if (supply.entity.isUnhuntable()){
  164.                         return;
  165.                     }
  166.                    
  167.                     // And don't go for the bloody fish! TODO: remove after alpha 8
  168.                     if (supply.entity.hasClass("SeaCreature")){
  169.                         return;
  170.                     }
  171.                    
  172.                     // Don't go for floating treasures since we won't be able to reach them and it kills the pathfinder.
  173.                     if (supply.entity.templateName() == "other/special_treasure_shipwreck_debris" ||
  174.                             supply.entity.templateName() == "other/special_treasure_shipwreck" ){
  175.                         return;
  176.                     }
  177.                    
  178.                     // Check we can actually reach the resource
  179.                     if (!gameState.ai.accessibility.isAccessible(supply.position)){
  180.                         return;
  181.                     }
  182.                    
  183.                     // Don't gather in enemy territory
  184.                     var territory = territoryMap.point(supply.position);
  185.                     if (territory != 0 && gameState.isPlayerEnemy(territory)){
  186.                         return;
  187.                     }
  188.                    
  189.                     // measure the distance to the resource
  190.                     var dist = VectorDistance(supply.position, workerPosition);
  191.                     // Add on a factor for the nearest dropsite if one exists
  192.                     if (nearestDropsite){
  193.                         dist += 5 * VectorDistance(supply.position, nearestDropsite.position());
  194.                     }
  195.  
  196.                     // Skip targets that are far too far away (e.g. in the
  197.                     // enemy base), only do this for common supplies
  198.                     if (dist > 6072 && numSupplies > 100){
  199.                         return;
  200.                     }
  201.                    
  202.                     if (supply.entity.templateName().search(/treasure/)) {
  203.                         treasures.push({
  204.                             dist : dist,
  205.                             entity : supply.entity
  206.                         });
  207.                         return;
  208.                     }
  209.                    
  210.                     resources.push({
  211.                         dist : dist,
  212.                         entity : supply.entity
  213.                     });
  214.                 });
  215.  
  216.                 resources.sort(function(a, b) {
  217.                     // Prefer smaller distances
  218.                     if (a.dist != b.dist)
  219.                         return a.dist - b.dist;
  220.  
  221.                     return false;
  222.                 });
  223.                 treasures.sort(function(a, b) {
  224.                     // Prefer smaller distances
  225.                     if (a.dist != b.dist)
  226.                         return a.dist - b.dist;
  227.  
  228.                     return false;
  229.                 });
  230.                
  231.                 supplies =treasures.concat(resources);
  232.                
  233.                 // Start gathering the best resource (by distance from the dropsite and unit)
  234.                 if (supplies.length) {
  235.                     ent.gather(supplies[0].entity);
  236.                     ent.setMetadata("subrole", "gatherer");
  237.                     ent.setMetadata("gather-type", type);
  238.                     return;
  239.                 }else{
  240.                     debug("No " + type + " found!");
  241.                 }
  242.             }
  243.  
  244.             // Couldn't find any types to gather
  245.             ent.setMetadata("subrole", "idle");
  246.         });
  247.     }
  248. };
  249.  
  250. EconomyManager.prototype.assignToFoundations = function(gameState) {
  251.     // If we have some foundations, and we don't have enough
  252.     // builder-workers,
  253.     // try reassigning some other workers who are nearby
  254.  
  255.     var foundations = gameState.findFoundations();
  256.  
  257.     // Check if nothing to build
  258.     if (!foundations.length){
  259.         return;
  260.     }
  261.  
  262.     var workers = gameState.getOwnEntitiesWithRole("worker");
  263.  
  264.     var builderWorkers = workers.filter(function(ent) {
  265.         return (ent.getMetadata("subrole") === "builder");
  266.     });
  267.  
  268.     // Check if enough builders
  269.     var extraNeeded = this.targetNumBuilders - builderWorkers.length;
  270.     if (extraNeeded <= 0){
  271.         return;
  272.     }
  273.  
  274.     // Pick non-builders who are closest to the first foundation,
  275.     // and tell them to start building it
  276.  
  277.     var target = foundations.toEntityArray()[0];
  278.  
  279.     var nonBuilderWorkers = workers.filter(function(ent) {
  280.         // check position so garrisoned units aren't tasked
  281.         return (ent.getMetadata("subrole") !== "builder" && ent.position() !== undefined);
  282.     });
  283.  
  284.     var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), extraNeeded);
  285.  
  286.     // Order each builder individually, not as a formation
  287.     nearestNonBuilders.forEach(function(ent) {
  288.         ent.repair(target);
  289.         ent.setMetadata("subrole", "builder");
  290.     });
  291. };
  292.  
  293. EconomyManager.prototype.buildMoreFields = function(gameState, queues) {
  294.     // give time for treasures to be gathered
  295.     if (gameState.getTimeElapsed() < 30 * 1000)
  296.         return;
  297.     var numFields = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("structures/{civ}_field"));
  298.     numFields += queues.field.totalLength();
  299.  
  300.     for ( var i = numFields; i < this.targetNumFields; i++) {
  301.         queues.field.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_field"));
  302.     }
  303. };
  304.  
  305. // If all the CC's are destroyed then build a new one
  306. EconomyManager.prototype.buildNewCC= function(gameState, queues) {
  307.     var numCCs = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("structures/{civ}_civil_centre"));
  308.     numCCs += queues.civilCentre.totalLength();
  309.  
  310.     for ( var i = numCCs; i < 1; i++) {
  311.         queues.civilCentre.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre"));
  312.     }
  313. };
  314.  
  315. //creates and maintains a map of tree density
  316. EconomyManager.prototype.updateResourceMaps = function(gameState, events){
  317.     // The weight of the influence function is amountOfResource/decreaseFactor
  318.     var decreaseFactor = {'wood': 15, 'stone': 100, 'metal': 100, 'food': 20};
  319.     // This is the maximum radius of the influence
  320.     var radius = {'wood':13, 'stone': 10, 'metal': 10, 'food': 10};
  321.    
  322.     for (var resource in radius){
  323.         // if there is no resourceMap create one with an influence for everything with that resource
  324.         if (! this.resourceMaps[resource]){
  325.             this.resourceMaps[resource] = new Map(gameState);
  326.  
  327.             var supplies = gameState.findResourceSupplies();
  328.             if (supplies[resource]){
  329.                 for (var i in supplies[resource]){
  330.                     var current = supplies[resource][i];
  331.                     var x = Math.round(current.position[0] / gameState.cellSize);
  332.                     var z = Math.round(current.position[1] / gameState.cellSize);
  333.                     var strength = Math.round(current.entity.resourceSupplyMax()/decreaseFactor[resource]);
  334.                     this.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
  335.                 }
  336.             }
  337.         }
  338.         // Look for destroy events and subtract the entities original influence from the resourceMap
  339.         for (var i in events) {
  340.             var e = events[i];
  341.  
  342.             if (e.type === "Destroy") {
  343.                 if (e.msg.rawEntity.template){
  344.                     var ent = new Entity(gameState.ai, e.msg.rawEntity);
  345.                     if (ent && ent.resourceSupplyType() && ent.resourceSupplyType().generic === resource){
  346.                         var x = Math.round(ent.position()[0] / gameState.cellSize);
  347.                         var z = Math.round(ent.position()[1] / gameState.cellSize);
  348.                         var strength = Math.round(ent.resourceSupplyMax()/decreaseFactor[resource]);
  349.                         this.resourceMaps[resource].addInfluence(x, z, radius[resource], -1*strength);
  350.                     }
  351.                 }
  352.             }
  353.         }
  354.     }
  355.    
  356.     //this.resourceMaps[resource].dumpIm("tree_density.png");
  357. };
  358.  
  359. // Returns the position of the best place to build a new dropsite for the specified resource
  360. EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource){
  361.     // A map which gives a positive weight for all CCs and adds a negative weight near all dropsites
  362.     var friendlyTiles = new Map(gameState);
  363.     gameState.getOwnEntities().forEach(function(ent) {
  364.         // We want to build near a CC of ours
  365.         if (ent.hasClass("CivCentre")){
  366.             var infl = 200;
  367.  
  368.             var pos = ent.position();
  369.             var x = Math.round(pos[0] / gameState.cellSize);
  370.             var z = Math.round(pos[1] / gameState.cellSize);
  371.             friendlyTiles.addInfluence(x, z, infl, 0.1 * infl);
  372.             friendlyTiles.addInfluence(x, z, infl/2, 0.1 * infl);
  373.         }
  374.         // We don't want multiple dropsites at one spot so add a negative for all dropsites
  375.         if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){
  376.             var infl = 20;
  377.            
  378.             var pos = ent.position();
  379.             var x = Math.round(pos[0] / gameState.cellSize);
  380.             var z = Math.round(pos[1] / gameState.cellSize);
  381.            
  382.             friendlyTiles.addInfluence(x, z, infl, -50, 'quadratic');
  383.         }
  384.     });
  385.    
  386.     // Multiply by tree density to get a combination of the two maps
  387.     friendlyTiles.multiply(this.resourceMaps[resource]);
  388.    
  389.     //friendlyTiles.dumpIm(resource + "_density_fade.png", 10000);
  390.    
  391.     var obstructions = Map.createObstructionMap(gameState);
  392.     obstructions.expandInfluences();
  393.    
  394.     var bestIdx = friendlyTiles.findBestTile(4, obstructions)[0];
  395.    
  396.     // Convert from 1d map pixel coordinates to game engine coordinates
  397.     var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
  398.     var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize;
  399.     return [x,z];
  400. };
  401.  
  402. EconomyManager.prototype.updateResourceConcentrations = function(gameState){
  403.     var self = this;
  404.     var resources = ["food", "wood", "stone", "metal"];
  405.     for (key in resources){
  406.         var resource = resources[key];
  407.         gameState.getOwnEntities().forEach(function(ent) {
  408.             if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){
  409.                 var radius = 14;
  410.                
  411.                 var pos = ent.position();
  412.                 var x = Math.round(pos[0] / gameState.cellSize);
  413.                 var z = Math.round(pos[1] / gameState.cellSize);
  414.                
  415.                 var quantity = self.resourceMaps[resource].sumInfluence(x, z, radius);
  416.                
  417.                 ent.setMetadata("resourceQuantity_" + resource, quantity);
  418.             }
  419.         });
  420.     }
  421. };
  422.  
  423. //return the number of resource dropsites with an acceptable amount of the resource nearby
  424. EconomyManager.prototype.checkResourceConcentrations = function(gameState, resource){
  425.     //TODO: make these values adaptive
  426.     var requiredInfluence = {wood: 16000, stone: 300, metal: 300};
  427.     var count = 0;
  428.     gameState.getOwnEntities().forEach(function(ent) {
  429.         if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf(resource) !== -1){
  430.             var quantity = ent.getMetadata("resourceQuantity_" + resource);
  431.            
  432.             if (quantity >= requiredInfluence[resource]){
  433.                 count ++;
  434.             }
  435.         }
  436.     });
  437.     return count;
  438. };
  439.  
  440. EconomyManager.prototype.buildDropsites = function(gameState, queues){
  441.     if (queues.economicBuilding.totalLength() === 0 &&
  442.             gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_mill")) === 0 &&
  443.             gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_civil_centre")) === 0){
  444.             //only ever build one mill/CC at a time
  445.         if (gameState.getTimeElapsed() > 30 * 1000){
  446.             for (var resource in this.dropsiteNumbers){
  447.                 if (this.checkResourceConcentrations(gameState, resource) < this.dropsiteNumbers[resource]){
  448.                     var spot = this.getBestResourceBuildSpot(gameState, resource);
  449.                    
  450.                     var myCivCentres = gameState.getOwnEntities().filter(function(ent) {
  451.                         if (!ent.hasClass("CivCentre") || ent.position() === undefined){
  452.                             return false;
  453.                         }
  454.                         var dx = (spot[0]-ent.position()[0]);
  455.                         var dy = (spot[1]-ent.position()[1]);
  456.                         var dist2 = dx*dx + dy*dy;
  457.                         return (ent.hasClass("CivCentre") && dist2 < 180*180);
  458.                     });
  459.                    
  460.                     if (myCivCentres.length === 0){
  461.                         queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre", spot));
  462.                     }else{
  463.                         queues.economicBuilding.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_mill", spot));
  464.                     }
  465.                     break;
  466.                 }
  467.             }
  468.         }
  469.     }
  470. };
  471.  
  472. EconomyManager.prototype.update = function(gameState, queues, events) {
  473.     Engine.ProfileStart("economy update");
  474.    
  475.     this.reassignRolelessUnits(gameState);
  476.    
  477.     this.buildNewCC(gameState,queues);
  478.  
  479.     Engine.ProfileStart("Train workers and build farms");
  480.     this.trainMoreWorkers(gameState, queues);
  481.  
  482.     this.buildMoreFields(gameState, queues);
  483.     Engine.ProfileStop();
  484.    
  485.     //Later in the game we want to build stuff faster.
  486.     if (gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.5) {
  487.         this.targetNumBuilders = 10;
  488.     }else{
  489.         this.targetNumBuilders = 5;
  490.     }
  491.    
  492.     if (gameState.countEntitiesWithType(gameState.applyCiv("units/{civ}_support_female_citizen")) > this.targetNumWorkers * 0.8) {
  493.         this.dropsiteNumbers = {wood: 3, stone: 2, metal: 2};
  494.     }else{
  495.         this.dropsiteNumbers = {wood: 2, stone: 1, metal: 1};
  496.     }
  497.    
  498.     Engine.ProfileStart("Update Resource Maps and Concentrations");
  499.     this.updateResourceMaps(gameState, events);
  500.     this.updateResourceConcentrations(gameState);
  501.     Engine.ProfileStop();
  502.    
  503.     this.buildDropsites(gameState, queues);
  504.    
  505.    
  506.     // TODO: implement a timer based system for this
  507.     this.setCount += 1;
  508.     if (this.setCount >= 20){
  509.         this.setWorkersIdleByPriority(gameState);
  510.         this.setCount = 0;
  511.     }
  512.    
  513.     Engine.ProfileStart("Reassign Idle Workers");
  514.     this.reassignIdleWorkers(gameState);
  515.     Engine.ProfileStop();
  516.    
  517.     Engine.ProfileStart("Assign builders");
  518.     this.assignToFoundations(gameState);
  519.     Engine.ProfileStop();
  520.  
  521.     Engine.ProfileStop();
  522. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement