0ad/binaries/data/mods/public/simulation/ai/qbot-wc/qbot.js
2013-03-24 09:10:32 +00:00

368 lines
12 KiB
JavaScript

function QBotAI(settings) {
BaseAI.call(this, settings);
Config.updateDifficulty(settings.difficulty);
this.turn = 0;
this.playedTurn = 0;
this.modules = {
"economy": new EconomyManager(),
"military": new MilitaryAttackManager()
};
// this.queues can only be modified by the queue manager or things will go awry.
this.queues = {
house : new Queue(),
citizenSoldier : new Queue(),
villager : new Queue(),
economicBuilding : new Queue(),
dropsites : new Queue(),
field : new Queue(),
militaryBuilding : new Queue(),
defenceBuilding : new Queue(),
civilCentre: new Queue(),
majorTech: new Queue(),
minorTech: new Queue()
};
this.productionQueues = [];
this.priorities = Config.priorities;
this.queueManager = new QueueManager(this.queues, this.priorities);
this.firstTime = true;
this.savedEvents = [];
this.waterMap = false;
this.defcon = 5;
this.defconChangeTime = -10000000;
}
QBotAI.prototype = new BaseAI();
// Bit of a hack: I run the pathfinder early, before the map apears, to avoid a sometimes substantial lag right at the start.
QBotAI.prototype.InitShared = function(gameState, sharedScript) {
var ents = gameState.getEntities().filter(Filters.byOwner(PlayerID));
var myKeyEntities = ents.filter(function(ent) {
return ent.hasClass("CivCentre");
});
if (myKeyEntities.length == 0){
myKeyEntities = gameState.getEntities().filter(Filters.byOwner(PlayerID));
}
var filter = Filters.byClass("CivCentre");
var enemyKeyEntities = gameState.getEntities().filter(Filters.not(Filters.byOwner(PlayerID))).filter(filter);
if (enemyKeyEntities.length == 0){
enemyKeyEntities = gameState.getEntities().filter(Filters.not(Filters.byOwner(PlayerID)));
}
this.pathFinder = new aStarPath(gameState, false, true);
this.pathsToMe = [];
this.pathInfo = { "angle" : 0, "needboat" : true, "mkeyPos" : myKeyEntities.toEntityArray()[0].position(), "ekeyPos" : enemyKeyEntities.toEntityArray()[0].position() };
// First path has a sampling of 3, which ensures we'll get at least one path even on Acropolis. The others are 6 so might fail.
var pos = [this.pathInfo.mkeyPos[0] + 150*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 150*Math.sin(this.pathInfo.angle)];
var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 2, 2);// uncomment for debug:*/, 300000, gameState);
//Engine.DumpImage("initialPath" + PlayerID + ".png", this.pathFinder.TotorMap.map, this.pathFinder.TotorMap.width,this.pathFinder.TotorMap.height,255);
if (path !== undefined && path[1] !== undefined && path[1] == false) {
// path is viable and doesn't require boating.
// blackzone the last two waypoints.
this.pathFinder.markImpassableArea(path[0][0][0],path[0][0][1],20);
this.pathsToMe.push(path[0][0][0]);
this.pathInfo.needboat = false;
}
this.pathInfo.angle += Math.PI/3.0;
}
//Some modules need the gameState to fully initialise
QBotAI.prototype.runInit = function(gameState, events){
this.chooseRandomStrategy();
for (var i in this.modules){
if (this.modules[i].init){
this.modules[i].init(gameState, events);
}
}
debug ("Inited, diff is " + Config.difficulty);
this.timer = new Timer();
var ents = gameState.getOwnEntities();
var myKeyEntities = gameState.getOwnEntities().filter(function(ent) {
return ent.hasClass("CivCentre");
});
if (myKeyEntities.length == 0){
myKeyEntities = gameState.getOwnEntities();
}
// disband the walls themselves
if (gameState.playerData.civ == "iber") {
gameState.getOwnEntities().filter(function(ent) { //}){
if (ent.hasClass("StoneWall") && !ent.hasClass("Tower"))
ent.destroy();
});
}
var filter = Filters.byClass("CivCentre");
var enemyKeyEntities = gameState.getEnemyEntities().filter(filter);
if (enemyKeyEntities.length == 0){
enemyKeyEntities = gameState.getEnemyEntities();
}
//this.accessibility = new Accessibility(gameState, myKeyEntities.toEntityArray()[0].position());
this.myIndex = this.accessibility.getAccessValue(myKeyEntities.toEntityArray()[0].position());
if (enemyKeyEntities.length == 0)
return;
this.templateManager = new TemplateManager(gameState);
};
QBotAI.prototype.OnUpdate = function(sharedScript) {
if (this.gameFinished){
return;
}
if (this.events.length > 0){
this.savedEvents = this.savedEvents.concat(this.events);
}
// Run the update every n turns, offset depending on player ID to balance the load
// this also means that init at turn 0 always happen and is never run in parallel to the first played turn so I use an else if.
if (this.turn == 0) {
//Engine.DumpImage("terrain.png", this.accessibility.map, this.accessibility.width,this.accessibility.height,255)
//Engine.DumpImage("Access.png", this.accessibility.passMap, this.accessibility.width,this.accessibility.height,this.accessibility.regionID+1)
var gameState = sharedScript.gameState[PlayerID];
gameState.ai = this;
this.runInit(gameState, this.savedEvents);
// Delete creation events
delete this.savedEvents;
this.savedEvents = [];
} else if ((this.turn + this.player) % 8 == 5) {
Engine.ProfileStart("Aegis bot");
this.playedTurn++;
var gameState = sharedScript.gameState[PlayerID];
gameState.ai = this;
if (gameState.getOwnEntities().length === 0){
Engine.ProfileStop();
return; // With no entities to control the AI cannot do anything
}
if (this.pathInfo !== undefined)
{
var pos = [this.pathInfo.mkeyPos[0] + 150*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 150*Math.sin(this.pathInfo.angle)];
var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 6, 5);// uncomment for debug:*/, 300000, gameState);
if (path !== undefined && path[1] !== undefined && path[1] == false) {
// path is viable and doesn't require boating.
// blackzone the last two waypoints.
this.pathFinder.markImpassableArea(path[0][0][0],path[0][0][1],20);
this.pathsToMe.push(path[0][0][0]);
this.pathInfo.needboat = false;
}
this.pathInfo.angle += Math.PI/3.0;
if (this.pathInfo.angle > Math.PI*2.0)
{
if (this.pathInfo.needboat)
{
debug ("Assuming this is a water map");
this.waterMap = true;
}
delete this.pathFinder;
delete this.pathInfo;
}
}
// try going up phases.
if (gameState.canResearch("phase_town",true) && gameState.getTimeElapsed() > (Config.Economy.townPhase*1000)
&& gameState.findResearchers("phase_town",true).length != 0 && this.queues.majorTech.totalLength() === 0) {
this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_town",true)); // we rush the town phase.
debug ("Trying to reach town phase");
var nb = gameState.getOwnEntities().filter(Filters.byClass("Village")).length-1;
if (nb < 5)
{
while (nb < 5 && ++nb)
this.queues.house.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_house"));
}
} else if (gameState.canResearch("phase_city_generic",true) && gameState.getTimeElapsed() > (Config.Economy.cityPhase*1000)
&& gameState.getOwnEntitiesByRole("worker").length > 85
&& gameState.findResearchers("phase_city_generic", true).length != 0 && this.queues.majorTech.totalLength() === 0) {
debug ("Trying to reach city phase");
this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_city_generic"));
}
// defcon cooldown
if (this.defcon < 5 && gameState.timeSinceDefconChange() > 20000)
{
this.defcon++;
debug ("updefconing to " +this.defcon);
if (this.defcon >= 4 && this.modules.military.hasGarrisonedFemales)
this.modules.military.ungarrisonAll(gameState);
}
for (var i in this.modules){
this.modules[i].update(gameState, this.queues, this.savedEvents);
}
this.queueManager.update(gameState);
/*
// Use this to debug informations about the metadata.
if (this.playedTurn % 10 === 0)
{
// some debug informations about units.
var units = gameState.getOwnEntities();
for (var i in units._entities)
{
var ent = units._entities[i];
if (!ent.isIdle())
continue;
warn ("Unit " + ent.id() + " is a " + ent._templateName);
if (sharedScript._entityMetadata[PlayerID][ent.id()])
{
var metadata = sharedScript._entityMetadata[PlayerID][ent.id()];
for (j in metadata)
{
warn ("Metadata " + j);
if (typeof(metadata[j]) == "object")
warn ("Object");
else if (typeof(metadata[j]) == undefined)
warn ("Undefined");
else
warn(uneval(metadata[j]));
}
}
}
}*/
//if (this.playedTurn % 5 === 0)
// this.queueManager.printQueues(gameState);
// Generate some entropy in the random numbers (against humans) until the engine gets random initialised numbers
// TODO: remove this when the engine gives a random seed
var n = this.savedEvents.length % 29;
for (var i = 0; i < n; i++){
Math.random();
}
delete this.savedEvents;
this.savedEvents = [];
Engine.ProfileStop();
}
this.turn++;
};
QBotAI.prototype.chooseRandomStrategy = function()
{
// deactivated for now.
this.strategy = "normal";
// rarely and if we can assume it's not a water map.
if (!this.pathInfo.needboat && 0)//Math.random() < 0.2 && Config.difficulty == 2)
{
this.strategy = "rush";
// going to rush.
this.modules.economy.targetNumWorkers = 0;
Config.Economy.townPhase = 480;
Config.Economy.cityPhase = 900;
Config.Economy.farmsteadStartTime = 600;
Config.Economy.femaleRatio = 0; // raise it since we'll want to rush age 2.
}
};
// TODO: Remove override when the whole AI state is serialised
// TODO: this currently is very much equivalent to "rungamestateinit" with a few hacks. Should deserialize/serialize properly someday.
QBotAI.prototype.Deserialize = function(data, sharedScript)
{
BaseAI.prototype.Deserialize.call(this, data);
var ents = sharedScript.entities.filter(Filters.byOwner(PlayerID));
var myKeyEntities = ents.filter(function(ent) {
return ent.hasClass("CivCentre");
});
if (myKeyEntities.length == 0){
myKeyEntities = sharedScript.entities.filter(Filters.byOwner(PlayerID));
}
var filter = Filters.byClass("CivCentre");
var enemyKeyEntities = sharedScript.entities.filter(Filters.not(Filters.byOwner(PlayerID))).filter(filter);
if (enemyKeyEntities.length == 0){
enemyKeyEntities = sharedScript.entities.filter(Filters.not(Filters.byOwner(PlayerID)));
}
this.terrainAnalyzer = sharedScript.terrainAnalyzer;
this.passabilityMap = sharedScript.passabilityMap;
var fakeState = { "ai" : this, "sharedScript" : sharedScript };
this.pathFinder = new aStarPath(fakeState, false, true);
this.pathsToMe = [];
this.pathInfo = { "angle" : 0, "needboat" : true, "mkeyPos" : myKeyEntities.toEntityArray()[0].position(), "ekeyPos" : enemyKeyEntities.toEntityArray()[0].position() };
// First path has a sampling of 3, which ensures we'll get at least one path even on Acropolis. The others are 6 so might fail.
var pos = [this.pathInfo.mkeyPos[0] + 150*Math.cos(this.pathInfo.angle),this.pathInfo.mkeyPos[1] + 150*Math.sin(this.pathInfo.angle)];
var path = this.pathFinder.getPath(this.pathInfo.ekeyPos, pos, 2, 2);
if (path !== undefined && path[1] !== undefined && path[1] == false) {
// path is viable and doesn't require boating.
// blackzone the last two waypoints.
this.pathFinder.markImpassableArea(path[0][0][0],path[0][0][1],20);
this.pathsToMe.push(path[0][0][0]);
this.pathInfo.needboat = false;
}
this.pathInfo.angle += Math.PI/3.0;
};
// Override the default serializer
QBotAI.prototype.Serialize = function()
{
//var ret = BaseAI.prototype.Serialize.call(this);
return {};
};
function debug(output){
if (Config.debug){
if (typeof output === "string"){
warn(output);
}else{
warn(uneval(output));
}
}
}
function copyPrototype(descendant, parent) {
var sConstructor = parent.toString();
var aMatch = sConstructor.match( /\s*function (.*)\(/ );
if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; }
for (var m in parent.prototype) {
descendant.prototype[m] = parent.prototype[m];
}
}