var EconomyManager = Class({ _init: function() { this.baseNumWorkers = 30; // minimum number of workers we want this.targetNumWorkers = 55; // minimum number of workers we want this.targetNumBuilders = 6; // number of workers we want working on construction this.changetimeRegBui = 180*1000; this.changetimeWorkers = 60*1000; this.changetimeBoost = 30*1000; this.worknumbers = 1.5; // (This is a stupid design where we just construct certain numbers // of certain buildings in sequence) // Greek building list // Relative proportions of workers to assign to each resource type this.gatherWeights = { "food": 180, "wood": 180, "stone": 45, "metal": 120, }; }, villageBuildingList: function (gameState) { if (gameState.displayCiv() == "hele"){ this.villageBuildings = [ { "template": "structures/{civ}_scout_tower", "priority": 105, "count": 1, }, { "template": "structures/{civ}_field", "priority": 103, "count": 1, }, { "template": "structures/{civ}_barracks", "priority": 101, "count": 1, }, { "template": "structures/{civ}_field", "priority": 70, "count": 2, }, ]; } // Celt building list else if (gameState.displayCiv() == "celt"){ this.villageBuildings = [ { "template": "structures/{civ}_scout_tower", "priority": 105, "count": 1, }, { "template": "structures/{civ}_field", "priority": 103, "count": 1, }, { "template": "structures/{civ}_barracks", "priority": 101, "count": 1, }, { "template": "structures/{civ}_field", "priority": 70, "count": 2, }, ]; } // Carthage building list else if (gameState.displayCiv() == "cart"){ this.villageBuildings = [ { "template": "structures/{civ}_scout_tower", "priority": 105, "count": 1, }, { "template": "structures/{civ}_field", "priority": 103, "count": 1, }, { "template": "structures/{civ}_barracks", "priority": 101, "count": 1, }, { "template": "structures/{civ}_field", "priority": 70, "count": 2, }, ]; } // Iberian building list else if (gameState.displayCiv() == "iber"){ this.villageBuildings = [ { "template": "structures/{civ}_scout_tower", "priority": 105, "count": 1, }, { "template": "structures/{civ}_field", "priority": 103, "count": 1, }, { "template": "structures/{civ}_barracks", "priority": 101, "count": 1, }, { "template": "structures/{civ}_field", "priority": 70, "count": 2, }, ]; } // Persian building list else if (gameState.displayCiv() == "pers"){ this.villageBuildings = [ { "template": "structures/{civ}_scout_tower", "priority": 105, "count": 1, }, { "template": "structures/{civ}_field", "priority": 103, "count": 1, }, { "template": "structures/{civ}_barracks", "priority": 101, "count": 1, }, { "template": "structures/{civ}_field", "priority": 70, "count": 2, }, ]; } // Fallback option just in case else { this.villageBuildings = [ { "template": "structures/{civ}_scout_tower", "priority": 105, "count": 1, }, { "template": "structures/{civ}_field", "priority": 100, "count": 2, }, { "template": "structures/{civ}_barracks", "priority": 99, "count": 1, }, { "template": "structures/{civ}_scout_tower", "priority": 60, "count": 4, }, { "template": "structures/{civ}_field", "priority": 40, "count": 5, }, ]; } }, checkBuildingList: function (gameState) { if (gameState.displayCiv() == "hele"){ this.targetBuildings = [ { "template": "structures/hele_gymnasion", "priority": 80, "count": 1, }, { "template": "structures/{civ}_field", "priority": 70, "count": 2, }, { "template": "structures/hele_fortress", "priority": 60, "count": 1, }, { "template": "structures/{civ}_field", "priority": 40, "count": 3, }, ]; } // Celt building list else if (gameState.displayCiv() == "celt"){ this.targetBuildings = [ { "template": "structures/{civ}_barracks", "priority": 90, "count": 1, }, { "template": "structures/celt_fortress_b", "priority": 80, "count": 1, }, ]; } // Carthage building list else if (gameState.displayCiv() == "cart"){ this.targetBuildings = [ { "template": "structures/cart_fortress", "priority": 80, "count": 1, }, { "template": "structures/cart_temple", "priority": 75, "count": 1, }, { "template": "structures/cart_embassy_celtic", "priority": 50, "count": 1, }, { "template": "structures/cart_embassy_iberian", "priority": 50, "count": 1, }, { "template": "structures/cart_embassy_italiote", "priority": 50, "count": 1, }, ]; } // Celt building list else if (gameState.displayCiv() == "iber"){ this.targetBuildings = [ { "template": "structures/iber_fortress", "priority": 80, "count": 1, }, ]; } // Perian building list else if (gameState.displayCiv() == "pers"){ this.targetBuildings = [ { "template": "structures/pers_fortress", "priority": 80, "count": 1, }, { "template": "structures/pers_stables", "priority": 75, "count": 1, }, { "template": "structures/pers_apadana", "priority": 50, "count": 1, }, ]; } // Fallback option just in case else { this.targetBuildings = [ { "template": "structures/{civ}_field", "priority": 100, "count": 2, }, { "template": "structures/{civ}_barracks", "priority": 99, "count": 1, }, { "template": "structures/{civ}_scout_tower", "priority": 60, "count": 4, }, { "template": "structures/{civ}_field", "priority": 40, "count": 5, }, ]; } }, buildMoreBuildings: function(gameState, planGroups) { var numCCs = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv("structures/{civ}_civil_centre")); if (numCCs < 1) { planGroups.economyConstruction.addPlan(1000, new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre", 1) ); return; } // Limit ourselves to constructing two buildings at a time if (gameState.findFoundations().length > 0) return; var pop = gameState.getPopulation(); var poplim = gameState.getPopulationLimit(); var space = poplim - pop; if (space < 9) { planGroups.economyConstruction.addPlan(160, new BuildingConstructionPlan(gameState, "structures/{civ}_house", 1) ); return; } if (gameState.findFoundations().length > 0) return; // START BY GETTING ALL CCs UP TO SMALL VILLAGE LEVEL for each (var building in this.villageBuildings) { var numBuildings = gameState.countEntitiesAndQueuedWithType(gameState.applyCiv(building.template)); var wantedtotal = building.count * numCCs; // If we have too few, build another if (numBuildings < wantedtotal && building.template != gameState.applyCiv("structures/{civ}_field")) { planGroups.economyConstruction.addPlan(building.priority, new BuildingConstructionPlan(gameState, building.template, 1) ); return; } else if (numBuildings < wantedtotal && building.template == gameState.applyCiv("structures/{civ}_field")) { planGroups.economyConstruction.addPlan(building.priority, new BuildingConstructionPlanResources(gameState, building.template, 1) ); return; } } // THEN BUILD THE MAIN BASE INTO A TOWN 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 && building.template != gameState.applyCiv("structures/{civ}_field")) { planGroups.economyConstruction.addPlan(building.priority, new BuildingConstructionPlan(gameState, building.template, 1) ); return; } else if (numBuildings < building.count && building.template == gameState.applyCiv("structures/{civ}_field")) { planGroups.economyConstruction.addPlan(building.priority, new BuildingConstructionPlanResources(gameState, building.template, 1) ); return; } } }, trainMoreWorkers: function(gameState, planGroups) { if (gameState.getTimeElapsed() > this.changetime){ this.worknumbers = Math.random()*2; this.changetimeWorkers = this.changetime + (30*1000); } // Count the workers in the world and in progress var numWorkers = gameState.countEntitiesAndQueuedWithRoles("worker", "militia"); var workNumMod = this.worknumbers; var miliNo = gameState.countEntitiesAndQueuedWithRole("militia"); var workNo = gameState.countEntitiesAndQueuedWithRole("worker"); if (miliNo > workNo){ workNumMod = workNumMod - 0.6; } if (gameState.getTimeElapsed() < 150 * 100){ workNumMod = workNumMod - 0.6; } // If we have too few, train another // print("Want "+this.targetNumWorkers+" workers; got "+numWorkers+"\n"); if (numWorkers < this.baseNumWorkers) { var priority = 180; } else if (numWorkers < this.targetNumWorkers) { var priority = 140; } if (gameState.displayCiv() == "hele" || gameState.displayCiv() == "celt"){ if (workNumMod < 0.95){ planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_support_female_citizen", 2, { "role": "worker" }) ); } else if (workNumMod > 1.6) { planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_infantry_spearman_b", 2, { "role": "militia" }) ); } else { planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_infantry_javelinist_b", 2, { "role": "militia" }) ); } } else if (gameState.displayCiv() == "cart"){ if (workNumMod < 0.95){ planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_support_female_citizen", 2, { "role": "worker" }) ); } else if (workNumMod > 1.6) { planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_infantry_spearman_b", 2, { "role": "militia" }) ); } else { planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_infantry_archer_b", 2, { "role": "militia" }) ); } } else if (gameState.displayCiv() == "iber"){ if (workNumMod < 0.95){ planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_support_female_citizen", 2, { "role": "worker" }) ); } else if (workNumMod > 1.6) { planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_infantry_swordsman_b", 2, { "role": "militia" }) ); } else { planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_infantry_javelinist_b", 2, { "role": "militia" }) ); } } else if (gameState.displayCiv() == "pers"){ if (workNumMod < 0.95){ planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_support_female_citizen", 2, { "role": "worker" }) ); } else if (workNumMod > 1.6) { planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_infantry_spearman_b", 2, { "role": "militia" }) ); } else { planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_infantry_archer_b", 2, { "role": "militia" }) ); } } else { planGroups.economyPersonnel.addPlan(priority, new UnitTrainingPlan(gameState, "units/{civ}_support_female_citizen", 2, { "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; }); gameState.getOwnEntitiesWithRole("militia").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 if (ent.hasClass("CitizenSoldier") && ent.hasClass("Infantry")){ var currentPosition = ent.position(); var targets = gameState.entities.filter(function(enten) { var foeposition = enten.position(); if (foeposition){ var dist = SquareVectorDistance(foeposition, currentPosition); return (enten.isEnemy() && enten.owner()!= 0 && dist < 2500); } else { return false; } }); if (targets.length == 0){ // If we're clear go back to work ent.setMetadata("role", "militia"); } else { // If not, go home! var targets = gameState.entities.filter(function(squeak) { return (!squeak.isEnemy() && squeak.hasClass("Village")); }); // If we have a target, move to it if (targets.length) { var targetrandomiser = Math.floor(Math.random()*targets.length); var target = targets.toEntityArray()[targetrandomiser]; var targetPos = target.position(); // TODO: this should be an attack-move command ent.move(targetPos[0], targetPos[1]); } } } else { ent.setMetadata("role", "randomcannonfodder"); } }); }, reassignIdleWorkers: function(gameState, planGroups) { var self = this; var allWorkers = gameState.getOwnEntitiesWithTwoRoles("worker", "militia") allWorkers.forEach(function(worker){ var shallIstop = Math.random(); if (shallIstop > 0.9975){ var targetPos = worker.position(); worker.move(targetPos[0], targetPos[1]); } }); // 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.getOwnEntitiesWithTwoRoles("worker", "militia").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; // The types are food wood stone metal // 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; // And don't go for the bloody fish! if (supply.entity.hasClass("SeaCreature")) return; var distcheck = 10000000000; var supplydistcheck = 100000000000; gameState.getOwnEntities().forEach(function(centre) { if (centre.hasClass("CivCentre")) { var centrePosition = centre.position(); var currentsupplydistcheck = SquareVectorDistance(supply.position, centrePosition); if (currentsupplydistcheck < currentsupplydistcheck){ supplydistcheck = currentsupplydistcheck; } var currentdistcheck = SquareVectorDistance(supply.position, centrePosition); if (currentdistcheck < distcheck){ distcheck = currentdistcheck; } // Skip targets that are far too far away (e.g. in the enemy base) } else if (centre.hasClass("DropsiteFood") && type == "food") { var centrePosition = centre.position(); var currentsupplydistcheck = SquareVectorDistance(supply.position, centrePosition); if (currentsupplydistcheck < currentsupplydistcheck){ supplydistcheck = currentsupplydistcheck; } // Skip targets that are far too far away (e.g. in the enemy base) } else if (centre.hasClass("DropsiteWood") && type == "wood") { var centrePosition = centre.position(); var currentsupplydistcheck = SquareVectorDistance(supply.position, centrePosition); if (currentsupplydistcheck < currentsupplydistcheck){ supplydistcheck = currentsupplydistcheck; } // Skip targets that are far too far away (e.g. in the enemy base) } else if (centre.hasClass("DropsiteStone") && type == "stone") { var centrePosition = centre.position(); var currentsupplydistcheck = SquareVectorDistance(supply.position, centrePosition); if (currentsupplydistcheck < currentsupplydistcheck){ supplydistcheck = currentsupplydistcheck; } // Skip targets that are far too far away (e.g. in the enemy base) } else if (centre.hasClass("DropsiteMetal") && type == "metal") { var centrePosition = centre.position(); var currentsupplydistcheck = SquareVectorDistance(supply.position, centrePosition); if (currentsupplydistcheck < currentsupplydistcheck){ supplydistcheck = currentsupplydistcheck; } // Skip targets that are far too far away (e.g. in the enemy base) } }); if (distcheck > 500000) return; var dist = SquareVectorDistance(supply.position, workerPosition); // Skip targets that are far too far away (e.g. in the enemy base) if (dist > 500000) 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; }); if (type == "food"){ var whatshallwebuild = "structures/{civ}_farmstead" } else { var whatshallwebuild = "structures/{civ}_mill" } // Start gathering if (supplies.length) { // THIS SHOULD BE A GLOBAL VARIABLE var currentposformill = supplies[0].entity.position(); var distcheckoldII = 1000000000; // CHECK DISTANCE gameState.getOwnEntities().forEach(function(centre) { if (centre.hasClass("CivCentre")) { var centrePosition = centre.position(); var distcheckII = SquareVectorDistance(currentposformill, centrePosition); if (distcheckII < distcheckoldII){ distcheckoldII = distcheckII; } } else if (centre.hasClass("DropsiteFood") && type == "food") { var centrePosition = centre.position(); var distcheckII = SquareVectorDistance(currentposformill, centrePosition); if (distcheckII < distcheckoldII){ distcheckoldII = distcheckII; } } else if (centre.hasClass("DropsiteWood") && type == "wood") { var centrePosition = centre.position(); var distcheckII = SquareVectorDistance(currentposformill, centrePosition); if (distcheckII < distcheckoldII){ distcheckoldII = distcheckII; } } else if (centre.hasClass("DropsiteMetal") && type == "metal") { var centrePosition = centre.position(); var distcheckII = SquareVectorDistance(currentposformill, centrePosition); if (distcheckII < distcheckoldII){ distcheckoldII = distcheckII; } } else if (centre.hasClass("DropsiteStone") && type == "stone") { var centrePosition = centre.position(); var distcheckII = SquareVectorDistance(currentposformill, centrePosition); if (distcheckII < distcheckoldII){ distcheckoldII = distcheckII; } } }); var foundationsyes = false; if (gameState.findFoundations().length > 1){ foundationsyes = false; } else{ foundationsyes = true; } //warn(type + " is the resource and " + distcheckoldII + " is the distance."); if (distcheckoldII > 5000 && foundationsyes == true){ //JuBotAI.prototype.chat("Building Mill"); planGroups.economyConstruction.addPlan(100, new BuildingConstructionPlanEcon(gameState, whatshallwebuild, 1, currentposformill) ); //JuBotAI.prototype.chat("Gathering"); ent.gather(supplies[0].entity); ent.setMetadata("subrole", "gatherer"); ent.setMetadata("gather-type", type); return; } else { //JuBotAI.prototype.chat("Gathering"); 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.getOwnEntitiesWithTwoRoles("worker", "militia"); 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"); }); }, // This function recalls builders to the CC every 2 minutes; theoretically, this prevents the issue where they build a farm and people try to cross it and there's a traffic jam which wrecks the AI economy. buildRegroup: function(gameState, planGroups) { if (gameState.getTimeElapsed() > this.changetimeRegBui){ var buildregroupers = gameState.getOwnEntitiesWithTwoRoles("worker", "militia"); buildregroupers.forEach(function(shirk) { if (shirk.getMetadata("subrole") == "builder"){ var targets = gameState.entities.filter(function(ent) { return (!ent.isEnemy() && ent.hasClass("CivCentre")); }); if (targets.length){ var target = targets.toEntityArray()[0]; var targetPos = target.position(); shirk.move(targetPos[0], targetPos[1]); } } }); // Wait 4 mins to do this again. this.changetimeRegBui = this.changetimeRegBui + (120*1000); } }, update: function(gameState, planGroups) { Engine.ProfileStart("economy update"); //this.buildRegroup(gameState, planGroups) this.checkBuildingList(gameState); this.villageBuildingList(gameState); this.reassignRolelessUnits(gameState); this.buildMoreBuildings(gameState, planGroups); this.trainMoreWorkers(gameState, planGroups); this.reassignIdleWorkers(gameState, planGroups); this.assignToFoundations(gameState, planGroups); Engine.ProfileStop(); }, });