diff --git a/binaries/data/mods/public/simulation/ai/aegis/aegis.js b/binaries/data/mods/public/simulation/ai/aegis/aegis.js index 5692eb4872..d7c3a12322 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/aegis.js +++ b/binaries/data/mods/public/simulation/ai/aegis/aegis.js @@ -45,7 +45,7 @@ m.AegisBot.prototype.CustomInit = function(gameState, sharedScript) { m.playerGlobals[PlayerID].uniqueIDTPlans = 1; // transport plans. starts at 1 because 0 might be used as none. m.playerGlobals[PlayerID].uniqueIDDefManagerArmy = 0; - this.HQ.init(gameState,sharedScript.events,this.queues); + this.HQ.init(gameState,this.queues); m.debug ("Initialized with the difficulty " + this.Config.difficulty); var ents = gameState.getEntities().filter(API3.Filters.byOwner(this.player)); diff --git a/binaries/data/mods/public/simulation/ai/aegis/base-manager.js b/binaries/data/mods/public/simulation/ai/aegis/base-manager.js index e497dc4be7..f1fb4eb5c6 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/base-manager.js +++ b/binaries/data/mods/public/simulation/ai/aegis/base-manager.js @@ -34,7 +34,7 @@ m.BaseManager = function(Config) { this.territoryIndices = []; }; -m.BaseManager.prototype.init = function(gameState, events, unconstructed){ +m.BaseManager.prototype.init = function(gameState, unconstructed){ this.constructing = unconstructed; // entitycollections this.units = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "base", this.ID)); @@ -584,7 +584,7 @@ m.BaseManager.prototype.checkResourceLevels = function (gameState,queues) { { if (this.willGather[type] === 0) continue; - if (type !== "food" && gameState.playedTurn % 10 === 4 && this.getResourceLevel(gameState,type, "all") < 200) + if (type !== "food" && gameState.ai.playedTurn % 10 === 4 && this.getResourceLevel(gameState,type, "all") < 200) this.willGather[type] = 0; // won't gather at all if (this.willGather[type] === 2) continue; @@ -901,13 +901,13 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) { continue; var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length; - if (assigned < this.targetNumBuilders/3) { - if (builderWorkers.length + addedWorkers < this.targetNumBuilders*2) { + if (assigned < targetNB/3) { + if (builderWorkers.length + addedWorkers < targetNB*2) { var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); }); if (gameState.defcon() < 5) nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.hasClass("Female") && ent.position() !== undefined); }); - var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), this.targetNumBuilders/3 - assigned); + var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), targetNB/3 - assigned); nearestNonBuilders.forEach(function(ent) { ent.stopMoving(); diff --git a/binaries/data/mods/public/simulation/ai/aegis/config.js b/binaries/data/mods/public/simulation/ai/aegis/config.js index 44c77f8a4e..72d3662d32 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/config.js +++ b/binaries/data/mods/public/simulation/ai/aegis/config.js @@ -10,7 +10,7 @@ m.Config = function() { "defenceBuildingTime" : 600, // Time to wait before building towers or fortresses "attackPlansStartTime" : 0, // time to wait before attacking. Start as soon as possible (first barracks) "techStartTime" : 120, // time to wait before teching. Will only start after town phase so it's irrelevant. - "popForBarracks1" : 15, + "popForBarracks1" : 20, "popForBarracks2" : 95, "timeForBlacksmith" : 900, }; @@ -63,21 +63,20 @@ m.Config = function() { } }; - // qbot this.priorities = - { // Note these are dynamic, you are only setting the initial values - "house" : 350, - "villager" : 40, + { + "villager" : 30, // should be slightly lower than the citizen soldier one because otherwise they get all the food "citizenSoldier" : 60, "ships" : 70, - "economicBuilding" : 90, + "house" : 350, "dropsites" : 120, "field" : 500, - "militaryBuilding" : 110, + "economicBuilding" : 90, + "militaryBuilding" : 140, // TODO: set to a lower value after the first barracks. "defenceBuilding" : 70, + "civilCentre" : 400, "majorTech" : 700, - "minorTech" : 50, - "civilCentre" : 400 + "minorTech" : 40 }; }; diff --git a/binaries/data/mods/public/simulation/ai/aegis/headquarters.js b/binaries/data/mods/public/simulation/ai/aegis/headquarters.js index 486111aece..3590e77228 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/headquarters.js +++ b/binaries/data/mods/public/simulation/ai/aegis/headquarters.js @@ -30,6 +30,9 @@ m.HQ = function(Config) { this.baseManagers = {}; + // cache the rates currently want for resource gathering. + this.wantedRates = {}; + // this means we'll have about a big third of women, and thus we can maximize resource gathering rates. this.femaleRatio = this.Config.Economy.femaleRatio; @@ -47,7 +50,7 @@ m.HQ = function(Config) { }; // More initialisation for stuff that needs the gameState -m.HQ.prototype.init = function(gameState, events, queues){ +m.HQ.prototype.init = function(gameState, queues){ // initialize base map. Each pixel is a base ID, or 0 if none this.basesMap = new API3.Map(gameState.sharedScript, new Uint8Array(gameState.getMap().data.length)); this.basesMap.setMaxVal(255); @@ -84,7 +87,7 @@ m.HQ.prototype.init = function(gameState, events, queues){ treasureAmount[i] += ent.resourceSupplyMax(); }); this.baseManagers[1] = new m.BaseManager(this.Config); - this.baseManagers[1].init(gameState, events); + this.baseManagers[1].init(gameState); this.baseManagers[1].setAnchor(CC); this.baseManagers[1].initTerritory(this, gameState); this.baseManagers[1].initGatheringFunctions(this, gameState); @@ -121,7 +124,7 @@ m.HQ.prototype.init = function(gameState, events, queues){ //this.reassignIdleWorkers(gameState); - this.navalManager.init(gameState, events, queues); + this.navalManager.init(gameState, queues); // TODO: change that to something dynamic. var civ = gameState.playerData.civ; @@ -214,20 +217,14 @@ m.HQ.prototype.checkEvents = function (gameState, events, queues) { } }; -// okay, so here we'll create both females and male workers. -// We'll try to keep close to the "ratio" defined atop. -// Choice of citizen soldier is a bit messy. -// Before having 100 workers it focuses on speed, cost, and won't choose units that cost stone/metal -// After 100 it just picks the strongest; -// TODO: This should probably be changed to favor a more mixed approach for better defense. -// (or even to adapt based on estimated enemy strategy). -// TODO: this should probably set which base it wants them in. +// This code trains females and citizen workers, trying to keep close to a ratio of females/CS +// TODO: this should choose a base depending on which base need workers +// TODO: also there are several things that could be greatly improved here. m.HQ.prototype.trainMoreWorkers = function(gameState, queues) { // Get some data. // Count the workers in the world and in progress var numFemales = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("units/{civ}_support_female_citizen"), true); - numFemales += queues.villager.countQueuedUnitsWithClass("Support"); // counting the workers that aren't part of a plan var numWorkers = 0; @@ -243,43 +240,50 @@ m.HQ.prototype.trainMoreWorkers = function(gameState, queues) numInTraining += item.count; }); }); - var numQueued = queues.villager.countQueuedUnits() + queues.citizenSoldier.countQueuedUnits(); + var numQueuedF = queues.villager.countQueuedUnits(); + var numQueuedS = queues.citizenSoldier.countQueuedUnits(); + var numQueued = numQueuedS + numQueuedF; var numTotal = numWorkers + numQueued; // If we have too few, train more // should plan enough to always have females… // TODO: 15 here should be changed to something more sensible, such as nb of producing buildings. - if (numTotal < this.targetNumWorkers && numQueued < 50 && (queues.villager.length() + queues.citizenSoldier.length()) < 120 && numInTraining < 15) { - var template = gameState.applyCiv("units/{civ}_support_female_citizen"); - - var size = Math.min(5, Math.ceil(numTotal / 10)); - if (numFemales/numTotal > this.femaleRatio && (numTotal > 20 || (this.fastStart && numTotal > 10))) { - if (numTotal < 100) - template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["cost",1], ["speed",0.5], ["costsResource", 0.5, "stone"], ["costsResource", 0.5, "metal"]]); - else - template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["strength",1] ]); + if (numTotal > this.targetNumWorkers || numQueued > 50 || (numQueuedF > 20 && numQueuedS > 20) || numInTraining > 15) + return; - if (!template) - template = gameState.applyCiv("units/{civ}_support_female_citizen"); - if (gameState.currentPhase() === 1) - size = 2; - } - - // TODO: improve that logic. - if (numFemales/numTotal > this.femaleRatio * 1.3 && numWorkers > 25) - queues.villager.paused = true; - else if ((numFemales/numTotal < this.femaleRatio * 1.1) || gameState.ai.queueManager.getAvailableResources(gameState)["food"] > 250 - || numWorkers <= 25) - queues.villager.paused = false; - - // TODO: perhaps assign them a default resource and check the base according to that. - - // base "0" means "auto" - if (template === gameState.applyCiv("units/{civ}_support_female_citizen")) - queues.villager.addItem(new m.TrainingPlan(gameState, template, { "role" : "worker", "base" : 0 }, size, 0, -1, size )); + // default template and size + var template = gameState.applyCiv("units/{civ}_support_female_citizen"); + var size = Math.min(5, Math.ceil(numTotal / 10)); + + // Choose whether we want soldiers instead. + // TODO: we might want to adjust our female ratio. + if ((numFemales+numQueuedF)/numTotal > this.femaleRatio && numQueuedS < 20) { + if (numTotal < 35) + template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["cost",1], ["speed",0.5], ["costsResource", 0.5, "stone"], ["costsResource", 0.5, "metal"]]); else - queues.citizenSoldier.addItem(new m.TrainingPlan(gameState, template, { "role" : "worker", "base" : 0 }, size, 0, -1, size)); + template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["strength",1] ]); + + if (!template) + template = gameState.applyCiv("units/{civ}_support_female_citizen"); + else + size = Math.min(5, Math.ceil(numTotal / 12)); } + + // TODO: improve that logic. + /* + if (numFemales/numWorkers > this.femaleRatio && numQueuedS > 0 && numWorkers > 25) + queues.villager.paused = true; + else + queues.villager.paused = false; + */ + + // TODO: perhaps assign them a default resource and check the base according to that. + + // base "0" means "auto" + if (template === gameState.applyCiv("units/{civ}_support_female_citizen")) + queues.villager.addItem(new m.TrainingPlan(gameState, template, { "role" : "worker", "base" : 0 }, size, 0, -1, size )); + else + queues.citizenSoldier.addItem(new m.TrainingPlan(gameState, template, { "role" : "worker", "base" : 0 }, size, 0, -1, size)); }; // picks the best template based on parameters and classes @@ -457,7 +461,13 @@ m.HQ.prototype.GetCurrentGatherRates = function(gameState) { }; -// Pick the resource which most needs another worker +/* Pick the resource which most needs another worker + * How this works: + * We get the rates we would want to have to be able to deal with our plans + * We get our current rates + * We compare; we pick the one where the discrepancy is highest. + * Need to balance long-term needs and possible short-term needs. + */ m.HQ.prototype.pickMostNeededResources = function(gameState) { var self = this; @@ -491,7 +501,7 @@ m.HQ.prototype.pickMostNeededResources = function(gameState) { var va = (Math.max(0,self.wantedRates[a] - currentRates[a]))/ (currentRates[a]+1); var vb = (Math.max(0,self.wantedRates[b] - currentRates[b]))/ (currentRates[b]+1); - // If they happen to be equal (generally this means "0" aka no need), make it equitable. + // If they happen to be equal (generally this means "0" aka no need), make it fair. if (va === vb) return (self.wantedRates[b]/(currentRates[b]+1)) - (self.wantedRates[a]/(currentRates[a]+1)); return vb-va; @@ -1054,7 +1064,7 @@ m.HQ.prototype.update = function(gameState, queues, events) { this.GetCurrentGatherRates(gameState); - if (gameState.getTimeElapsed() > this.techStartTime && gameState.currentPhase() > 2) + if (gameState.getTimeElapsed() > this.techStartTime && gameState.currentPhase() > 2 ) this.tryResearchTechs(gameState,queues); if (this.Config.difficulty > 1) diff --git a/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js b/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js index c63dc40350..4c22f9bf84 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js +++ b/binaries/data/mods/public/simulation/ai/aegis/naval-manager.js @@ -35,7 +35,7 @@ m.NavalManager = function() { }; // More initialisation for stuff that needs the gameState -m.NavalManager.prototype.init = function(gameState, events, queues) { +m.NavalManager.prototype.init = function(gameState, queues) { // finished docks this.docks = gameState.getOwnStructures().filter(API3.Filters.and(API3.Filters.byClass("Dock"), API3.Filters.not(API3.Filters.isFoundation()))); this.docks.allowQuickIter(); diff --git a/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js b/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js index cb212e8188..53c82ecd06 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js +++ b/binaries/data/mods/public/simulation/ai/aegis/queue-manager.js @@ -111,10 +111,14 @@ m.QueueManager.prototype.futureNeeds = function(gameState) { }; }; -// calculate the gather rates we'd want to be able to use all elements in our queues +// calculate the gather rates we'd want to be able to start all elements in our queues +// TODO: many things. m.QueueManager.prototype.wantedGatherRates = function(gameState) { + // global rates var rates = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 }; + // per-queue. var qTime = gameState.getTimeElapsed(); + var time = gameState.getTimeElapsed(); var qCosts = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 }; var currentRess = this.getAvailableResources(gameState); @@ -127,32 +131,37 @@ m.QueueManager.prototype.wantedGatherRates = function(gameState) { var name = this.queueArrays[i][0]; var queue = this.queueArrays[i][1]; + // we'll move temporally along the queue. for (var j = 0; j < queue.length(); ++j) { var elem = queue.queue[j]; var cost = elem.getCost(); + if (qTime < elem.startTime) qTime = elem.startTime; + // TODO: what is the else case here? + if (!elem.isGo(gameState)) { - // assume 2 minutes. - // TODO work on this. + // assume we'll be wanted in four minutes. + // TODO: work on this. for (var type in qCosts) - qCosts[type] += cost[type]; - qTime += 120000; + qCosts[type] += cost[type] / (qTime/time); + qTime += 240000; break; // disregard other stuffs. } if (!elem.endTime) { - // estimate time based on priority + cost + nb + // Assume we want it in 30 seconds from current time. + // Costs are made higher based on priority and lower based on current time. // TODO: work on this. for (var type in qCosts) - qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name])); + qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name])) / (qTime/time); qTime += 30000; } else { // TODO: work on this. for (var type in qCosts) - qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name])); + qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name])) / (qTime/time); // TODO: refine based on % completed. qTime += (elem.endTime-elem.startTime); } @@ -166,6 +175,7 @@ m.QueueManager.prototype.wantedGatherRates = function(gameState) { rates[j] += qCosts[j]/(qTime/1000); } } + return rates; }; @@ -311,8 +321,14 @@ m.QueueManager.prototype.HTMLprintQueues = function(gameState){ if (q.queue[j].number) qStr += q.queue[j].number + " "; qStr += q.queue[j].type; + qStr += "
"; + var costs = q.queue[j].getCost(); + for each (var k in costs.types) { + qStr += costs[k] + k.substr(0,1).toUpperCase() ; + if (k != "metal") qStr += " / "; + } + qStr += ""; log (qStr); - log (""); } log (""); } @@ -322,7 +338,6 @@ m.QueueManager.prototype.HTMLprintQueues = function(gameState){ { log("

" + p + ": " + uneval(this.accounts[p]) + "

"); }*/ - log ("

Needed Resources:" + uneval(this.futureNeeds(gameState,false)) + "

"); log ("

Wanted Gather Rate:" + uneval(this.wantedGatherRates(gameState)) + "

"); log ("

Current Resources:" + uneval(gameState.getResources()) + "

"); log ("

Available Resources:" + uneval(this.getAvailableResources(gameState)) + "

"); diff --git a/binaries/data/mods/public/simulation/ai/common-api/map-module.js b/binaries/data/mods/public/simulation/ai/common-api/map-module.js index 44e85c152a..f3d0258768 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/map-module.js +++ b/binaries/data/mods/public/simulation/ai/common-api/map-module.js @@ -45,8 +45,8 @@ m.Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) { var x0 = Math.max(0, cx - maxDist); var y0 = Math.max(0, cy - maxDist); - var x1 = Math.min(this.width, cx + maxDist); - var y1 = Math.min(this.height, cy + maxDist); + var x1 = Math.min(this.width-1, cx + maxDist); + var y1 = Math.min(this.height-1, cy + maxDist); var maxDist2 = maxDist * maxDist; var str = 0.0; @@ -70,11 +70,11 @@ m.Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) { var dx = x - cx; var dy = y - cy; var r2 = dx*dx + dy*dy; - if (r2 < maxDist2){ + if (r2 < maxDist2) { var quant = 0; var r = Math.sqrt(r2); quant = str * (maxDist - r); - + if (this.map[x + y * this.width] + quant < 0) this.map[x + y * this.width] = 0; else if (this.map[x + y * this.width] + quant > this.maxVal) diff --git a/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis-pathfinder.js b/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis-pathfinder.js index 292cca59c4..794b764f22 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis-pathfinder.js +++ b/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis-pathfinder.js @@ -205,7 +205,7 @@ m.aStarPath.prototype.continuePath = function(gamestate) { var index = 0 + this.currentSquare +positions[i][0]*this.Sampling +w*this.Sampling*positions[i][1]; if (this.widthMap[index] >= this.minWidth || (this.onWater && this.map[index] > 0 && this.map[index] !== 200 && this.map[index] !== 201) - || (!this.onWater && this.map[this.index] === 200)) + || (!this.onWater && this.map[index] === 200)) { if(this.isOpened[index] === undefined) {