forked from 0ad/0ad
commit first version of new ai bot (named Petra)
This was SVN commit r14865.
This commit is contained in:
parent
b03e3644ac
commit
97afd25171
284
binaries/data/mods/public/simulation/ai/petra/_petrabot.js
Normal file
284
binaries/data/mods/public/simulation/ai/petra/_petrabot.js
Normal file
@ -0,0 +1,284 @@
|
||||
Engine.IncludeModule("common-api");
|
||||
|
||||
var PETRA = (function() {
|
||||
var m = {};
|
||||
|
||||
// "local" global variables for stuffs that will need a unique ID
|
||||
// Note that since order of loading is alphabetic, this means this file must go before any other file using them.
|
||||
m.playerGlobals = [];
|
||||
|
||||
m.PetraBot = function PetraBot(settings)
|
||||
{
|
||||
API3.BaseAI.call(this, settings);
|
||||
|
||||
this.turn = 0;
|
||||
this.playedTurn = 0;
|
||||
|
||||
this.Config = new m.Config();
|
||||
this.Config.updateDifficulty(settings.difficulty);
|
||||
//this.Config.personality = settings.personality;
|
||||
|
||||
this.savedEvents = {};
|
||||
|
||||
this.defcon = 5;
|
||||
this.defconChangeTime = -10000000;
|
||||
};
|
||||
|
||||
m.PetraBot.prototype = new API3.BaseAI();
|
||||
|
||||
m.PetraBot.prototype.CustomInit = function(gameState, sharedScript)
|
||||
{
|
||||
this.initPersonality();
|
||||
|
||||
this.priorities = this.Config.priorities;
|
||||
// this.queues can only be modified by the queue manager or things will go awry.
|
||||
this.queues = {};
|
||||
for (var i in this.priorities)
|
||||
this.queues[i] = new m.Queue();
|
||||
|
||||
this.queueManager = new m.QueueManager(this.Config, this.queues, this.priorities);
|
||||
|
||||
this.HQ = new m.HQ(this.Config);
|
||||
gameState.Config = this.Config;
|
||||
|
||||
m.playerGlobals[PlayerID] = {};
|
||||
m.playerGlobals[PlayerID].uniqueIDBOPlans = 0; // training/building/research plans
|
||||
m.playerGlobals[PlayerID].uniqueIDBases = 1; // base manager ID. Starts at one because "0" means "no base" on the map
|
||||
m.playerGlobals[PlayerID].uniqueIDTPlans = 1; // transport plans. starts at 1 because 0 might be used as none.
|
||||
m.playerGlobals[PlayerID].uniqueIDArmy = 0;
|
||||
|
||||
this.HQ.init(gameState,this.queues);
|
||||
|
||||
var filter = API3.Filters.byClass("CivCentre");
|
||||
var myKeyEntities = gameState.getOwnEntities().filter(filter);
|
||||
if (myKeyEntities.length == 0)
|
||||
myKeyEntities = gameState.getOwnEntities();
|
||||
|
||||
var enemyKeyEntities = gameState.getEnemyEntities().filter(filter);
|
||||
if (enemyKeyEntities.length == 0)
|
||||
enemyKeyEntities = gameState.getEnemyEntities();
|
||||
|
||||
this.myIndex = this.accessibility.getAccessValue(myKeyEntities.toEntityArray()[0].position());
|
||||
|
||||
this.pathFinder = new API3.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" + this.player + ".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;
|
||||
};
|
||||
|
||||
m.PetraBot.prototype.OnUpdate = function(sharedScript)
|
||||
{
|
||||
if (this.gameFinished)
|
||||
return;
|
||||
|
||||
for (var i in this.events)
|
||||
{
|
||||
if(this.savedEvents[i] !== undefined)
|
||||
this.savedEvents[i] = this.savedEvents[i].concat(this.events[i]);
|
||||
else
|
||||
this.savedEvents[i] = this.events[i];
|
||||
}
|
||||
|
||||
// Run the update every n turns, offset depending on player ID to balance the load
|
||||
if ((this.turn + this.player) % 8 == 5)
|
||||
{
|
||||
Engine.ProfileStart("PetraBot bot (player " + this.player +")");
|
||||
|
||||
this.playedTurn++;
|
||||
|
||||
if (this.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, this.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)
|
||||
{
|
||||
m.debug ("Assuming this is a water map");
|
||||
this.HQ.waterMap = true;
|
||||
}
|
||||
delete this.pathFinder;
|
||||
delete this.pathInfo;
|
||||
}
|
||||
}
|
||||
|
||||
var townPhase = this.gameState.townPhase();
|
||||
var cityPhase = this.gameState.cityPhase();
|
||||
|
||||
// try going up phases.
|
||||
// TODO: softcode this more
|
||||
if (this.gameState.canResearch(townPhase,true) && this.gameState.getPopulation() >= this.Config.Economy.villagePopCap - 10
|
||||
&& this.gameState.findResearchers(townPhase,true).length != 0 && this.queues.majorTech.length() === 0)
|
||||
{
|
||||
var plan = new m.ResearchPlan(this.gameState, townPhase, true);
|
||||
plan.lastIsGo = false;
|
||||
plan.onStart = function (gameState) { gameState.ai.HQ.econState = "growth"; gameState.ai.HQ.OnTownPhase(gameState) };
|
||||
plan.isGo = function (gameState) {
|
||||
var ret = gameState.getPopulation() >= gameState.Config.Economy.villagePopCap
|
||||
if (ret && !this.lastIsGo)
|
||||
this.onGo(gameState);
|
||||
else if (!ret && this.lastIsGo)
|
||||
this.onNotGo(gameState);
|
||||
this.lastIsGo = ret;
|
||||
return ret;
|
||||
};
|
||||
plan.onGo = function (gameState) { gameState.ai.HQ.econState = "townPhasing"; m.debug ("Trying to reach TownPhase"); };
|
||||
plan.onNotGo = function (gameState) { gameState.ai.HQ.econState = "growth"; };
|
||||
|
||||
this.queues.majorTech.addItem(plan);
|
||||
}
|
||||
else if (this.gameState.canResearch(cityPhase,true) && this.gameState.getTimeElapsed() > (this.Config.Economy.cityPhase*1000)
|
||||
&& this.gameState.getOwnEntitiesByRole("worker", true).length > 85
|
||||
&& this.gameState.findResearchers(cityPhase, true).length != 0 && this.queues.majorTech.length() === 0
|
||||
&& this.queues.civilCentre.length() === 0)
|
||||
{
|
||||
var plan = new m.ResearchPlan(this.gameState, cityPhase, true);
|
||||
plan.onStart = function (gameState) { gameState.ai.HQ.OnCityPhase(gameState) };
|
||||
this.queues.majorTech.addItem(plan);
|
||||
|
||||
}
|
||||
// defcon cooldown
|
||||
if (this.defcon < 5 && this.gameState.timeSinceDefconChange() > 20000)
|
||||
{
|
||||
this.defcon++;
|
||||
m.debug ("updefconing to " +this.defcon);
|
||||
if (this.defcon >= 4 && this.HQ.hasGarrisonedFemales)
|
||||
this.HQ.ungarrisonAll(this.gameState);
|
||||
}
|
||||
|
||||
this.HQ.update(this.gameState, this.queues, this.savedEvents);
|
||||
|
||||
this.queueManager.update(this.gameState);
|
||||
|
||||
/*
|
||||
// Use this to debug informations about the metadata.
|
||||
if (this.playedTurn % 10 === 0)
|
||||
{
|
||||
// some debug informations about units.
|
||||
var units = this.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 (var 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(this.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["Create"].length % 29;
|
||||
for (var i = 0; i < n; i++)
|
||||
Math.random();
|
||||
|
||||
for (var i in this.savedEvents)
|
||||
this.savedEvents[i] = [];
|
||||
|
||||
Engine.ProfileStop();
|
||||
}
|
||||
|
||||
this.turn++;
|
||||
};
|
||||
|
||||
// defines our core components strategy-wise.
|
||||
// TODO: the sky's the limit here.
|
||||
m.PetraBot.prototype.initPersonality = function()
|
||||
{
|
||||
if (this.Config.difficulty >= 2)
|
||||
{
|
||||
this.Config.personality.aggressive = Math.random();
|
||||
this.Config.personality.cooperative = Math.random();
|
||||
}
|
||||
|
||||
if (this.Config.personality.aggressive > 0.7)
|
||||
{
|
||||
this.Config.Military.popForBarracks1 = 0;
|
||||
this.Config.Economy.villagePopCap = 75;
|
||||
this.Config.Economy.cityPhase = 900;
|
||||
this.Config.Economy.popForMarket = 80;
|
||||
this.Config.Economy.targetNumBuilders = 2;
|
||||
this.Config.Economy.femaleRatio = 0.3;
|
||||
this.Config.Defense.prudence = 0.5;
|
||||
this.Config.priorities.defenseBuilding = 60;
|
||||
}
|
||||
|
||||
if (this.Config.debug == 0)
|
||||
return;
|
||||
warn(" >>> Petra bot: personality = " + uneval(this.Config.personality));
|
||||
};
|
||||
|
||||
/*m.PetraBot.prototype.Deserialize = function(data, sharedScript)
|
||||
{
|
||||
};
|
||||
|
||||
// Override the default serializer
|
||||
PetraBot.prototype.Serialize = function()
|
||||
{
|
||||
return {};
|
||||
};*/
|
||||
|
||||
// For the moment we just use the debugging flag and the debugging function from the API.
|
||||
// Maybe it will make sense in the future to separate them.
|
||||
m.DebugEnabled = function()
|
||||
{
|
||||
return API3.DebugEnabled;
|
||||
}
|
||||
|
||||
m.debug = function(output)
|
||||
{
|
||||
API3.debug(output);
|
||||
}
|
||||
|
||||
|
||||
return m;
|
||||
}());
|
377
binaries/data/mods/public/simulation/ai/petra/army.js
Normal file
377
binaries/data/mods/public/simulation/ai/petra/army.js
Normal file
@ -0,0 +1,377 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
/* Defines an army
|
||||
* An army is a collection of own entities and enemy entities.
|
||||
* This doesn't use entity collections are they aren't really useful
|
||||
* and it would probably slow the rest of the system down too much.
|
||||
* All entities are therefore lists of ID
|
||||
* Inherited by the defense manager and several of the attack manager's attack plan.
|
||||
*/
|
||||
|
||||
m.Army = function(gameState, owner, ownEntities, foeEntities)
|
||||
{
|
||||
this.ID = m.playerGlobals[PlayerID].uniqueIDArmy++;
|
||||
|
||||
this.Config = owner.Config;
|
||||
this.defenseRatio = this.Config.Defense.defenseRatio;
|
||||
this.compactSize = this.Config.Defense.armyCompactSize;
|
||||
this.breakawaySize = this.Config.Defense.armyBreakawaySize;
|
||||
|
||||
// average
|
||||
this.foePosition = [0,0];
|
||||
this.ownPosition = [0,0];
|
||||
this.positionLastUpdate = gameState.getTimeElapsed();
|
||||
|
||||
// Some caching
|
||||
// A list of our defenders that were tasked with attacking a particular unit
|
||||
// This doesn't mean that they actually are since they could move on to something else on their own.
|
||||
this.assignedAgainst = {};
|
||||
// who we assigned against, for quick removal.
|
||||
this.assignedTo = {};
|
||||
|
||||
// For substrengths, format is "name": [classes]
|
||||
|
||||
this.foeEntities = [];
|
||||
this.foeStrength = 0;
|
||||
this.foeSubStrength = {};
|
||||
|
||||
this.ownEntities = [];
|
||||
this.ownStrength = 0;
|
||||
this.ownSubStrength = {};
|
||||
|
||||
// actually add units
|
||||
for (var i in foeEntities)
|
||||
this.addFoe(gameState,foeEntities[i], true);
|
||||
for (var i in ownEntities)
|
||||
this.addOwn(gameState,ownEntities[i]);
|
||||
|
||||
this.recalculatePosition(gameState, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if not forced, will only recalculate if on a different turn.
|
||||
m.Army.prototype.recalculatePosition = function(gameState, force)
|
||||
{
|
||||
if (!force && this.positionLastUpdate === gameState.getTimeElapsed())
|
||||
return;
|
||||
var pos = [0,0];
|
||||
if (this.foeEntities.length !== 0)
|
||||
{
|
||||
for each (var id in this.foeEntities)
|
||||
{
|
||||
var ent = gameState.getEntityById(id);
|
||||
var epos = ent.position();
|
||||
pos[0] += epos[0];
|
||||
pos[1] += epos[1];
|
||||
}
|
||||
this.foePosition[0] = pos[0]/this.foeEntities.length;
|
||||
this.foePosition[1] = pos[1]/this.foeEntities.length;
|
||||
} else
|
||||
this.foePosition = [0,0];
|
||||
|
||||
pos = [0,0];
|
||||
if (this.ownEntities.length !== 0)
|
||||
{
|
||||
for each (var id in this.ownEntities)
|
||||
{
|
||||
var ent = gameState.getEntityById(id);
|
||||
var epos = ent.position();
|
||||
pos[0] += epos[0];
|
||||
pos[1] += epos[1];
|
||||
}
|
||||
this.ownPosition[0] = pos[0]/this.ownEntities.length;
|
||||
this.ownPosition[1] = pos[1]/this.ownEntities.length;
|
||||
} else
|
||||
this.ownPosition = [0,0];
|
||||
|
||||
this.positionLastUpdate = gameState.getTimeElapsed();
|
||||
}
|
||||
|
||||
// helper
|
||||
m.Army.prototype.recalculateStrengths = function (gameState)
|
||||
{
|
||||
this.ownStrength = 0;
|
||||
this.foeStrength = 0;
|
||||
|
||||
// todo: deal with specifics.
|
||||
|
||||
for each (var id in this.foeEntities)
|
||||
this.evaluateStrength(gameState.getEntityById(id));
|
||||
for each (var id in this.ownEntities)
|
||||
this.evaluateStrength(gameState.getEntityById(id), true);
|
||||
}
|
||||
|
||||
// adds or remove the strength of the entity either to the enemy or to our units.
|
||||
m.Army.prototype.evaluateStrength = function (ent, isOwn, remove)
|
||||
{
|
||||
var entStrength = m.getMaxStrength(ent);
|
||||
if (remove)
|
||||
entStrength *= -1;
|
||||
|
||||
if (isOwn)
|
||||
this.ownStrength += entStrength;
|
||||
else
|
||||
this.foeStrength += entStrength;
|
||||
|
||||
// todo: deal with specifics.
|
||||
}
|
||||
|
||||
// add an entity to the enemy army
|
||||
// Will return true if the entity was added and false otherwise.
|
||||
// won't recalculate our position but will dirty it.
|
||||
m.Army.prototype.addFoe = function (gameState, enemyID, force)
|
||||
{
|
||||
if (this.foeEntities.indexOf(enemyID) !== -1)
|
||||
return false;
|
||||
var ent = gameState.getEntityById(enemyID);
|
||||
if (ent === undefined || ent.position() === undefined)
|
||||
return false;
|
||||
|
||||
// check distance
|
||||
if (!force && API3.SquareVectorDistance(ent.position(), this.foePosition) > this.compactSize)
|
||||
return false;
|
||||
|
||||
this.foeEntities.push(enemyID);
|
||||
this.assignedAgainst[enemyID] = [];
|
||||
this.positionLastUpdate = 0;
|
||||
this.evaluateStrength(ent);
|
||||
ent.setMetadata(PlayerID, "PartOfArmy", this.ID);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns true if the entity was removed and false otherwise.
|
||||
// TODO: when there is a technology update, we should probably recompute the strengths, or weird stuffs will happen.
|
||||
m.Army.prototype.removeFoe = function (gameState, enemyID, enemyEntity)
|
||||
{
|
||||
var idx = this.foeEntities.indexOf(enemyID);
|
||||
if (idx === -1)
|
||||
return false;
|
||||
var ent = enemyEntity === undefined ? gameState.getEntityById(enemyID) : enemyEntity;
|
||||
if (ent === undefined)
|
||||
{
|
||||
warn("Trying to remove a non-existing enemy entity, crashing for stacktrace");
|
||||
xgzrg();
|
||||
}
|
||||
this.foeEntities.splice(idx, 1);
|
||||
this.evaluateStrength(ent, false, true);
|
||||
ent.setMetadata(PlayerID, "PartOfArmy", undefined);
|
||||
|
||||
this.assignedAgainst[enemyID] = undefined;
|
||||
for (var to in this.assignedTo)
|
||||
if (this.assignedTo[to] == enemyID)
|
||||
this.assignedTo[to] = undefined;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// adds a defender but doesn't assign him yet.
|
||||
m.Army.prototype.addOwn = function (gameState, ID)
|
||||
{
|
||||
if (this.ownEntities.indexOf(ID) !== -1)
|
||||
return false;
|
||||
var ent = gameState.getEntityById(ID);
|
||||
if (ent === undefined || ent.position() === undefined)
|
||||
return false;
|
||||
|
||||
this.ownEntities.push(ID);
|
||||
this.evaluateStrength(ent, true);
|
||||
ent.setMetadata(PlayerID, "PartOfArmy", this.ID);
|
||||
this.assignedTo[ID] = 0;
|
||||
|
||||
var formerSubrole = ent.getMetadata(PlayerID, "subrole");
|
||||
if (formerSubrole && formerSubrole === "defender") // can happen when armies are merged for example
|
||||
return true;
|
||||
if (formerSubrole !== undefined)
|
||||
ent.setMetadata(PlayerID, "formerSubrole", formerSubrole);
|
||||
ent.setMetadata(PlayerID, "subrole", "defender");
|
||||
return true;
|
||||
}
|
||||
|
||||
m.Army.prototype.removeOwn = function (gameState, ID, Entity)
|
||||
{
|
||||
var idx = this.ownEntities.indexOf(ID);
|
||||
if (idx === -1)
|
||||
return false;
|
||||
var ent = Entity === undefined ? gameState.getEntityById(ID) : Entity;
|
||||
if (ent === undefined)
|
||||
{
|
||||
warn( ID);
|
||||
warn("Trying to remove a non-existing entity, crashing for stacktrace");
|
||||
xgzrg();
|
||||
}
|
||||
|
||||
this.ownEntities.splice(idx, 1);
|
||||
this.evaluateStrength(ent, true, true);
|
||||
ent.setMetadata(PlayerID, "PartOfArmy", undefined);
|
||||
|
||||
if (this.assignedTo[ID] !== 0)
|
||||
{
|
||||
var temp = this.assignedAgainst[this.assignedTo[ID]];
|
||||
if (temp)
|
||||
temp.splice(temp.indexOf(ID), 1);
|
||||
}
|
||||
this.assignedTo[ID] = undefined;
|
||||
|
||||
|
||||
var formerSubrole = ent.getMetadata(PlayerID, "formerSubrole");
|
||||
if (formerSubrole !== undefined)
|
||||
ent.setMetadata(PlayerID, "subrole", formerSubrole);
|
||||
else
|
||||
ent.setMetadata(PlayerID, "subrole", undefined);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// this one is "undefined entity" proof because it's called at odd times.
|
||||
// Orders a unit to attack an enemy.
|
||||
// overridden by specific army classes.
|
||||
m.Army.prototype.assignUnit = function (gameState, entID)
|
||||
{
|
||||
}
|
||||
|
||||
// resets the army properly.
|
||||
// assumes we already cleared dead units.
|
||||
m.Army.prototype.clear = function (gameState, events)
|
||||
{
|
||||
while(this.foeEntities.length > 0)
|
||||
this.removeFoe(gameState,this.foeEntities[0]);
|
||||
while(this.ownEntities.length > 0)
|
||||
this.removeOwn(gameState,this.ownEntities[0]);
|
||||
|
||||
this.assignedAgainst = {};
|
||||
this.assignedTo = {};
|
||||
|
||||
this.recalculateStrengths(gameState);
|
||||
this.recalculatePosition(gameState);
|
||||
}
|
||||
|
||||
// merge this army with another properly.
|
||||
// assumes units are in only one army.
|
||||
// also assumes that all have been properly cleaned up (no dead units).
|
||||
m.Army.prototype.merge = function (gameState, otherArmy)
|
||||
{
|
||||
// copy over all parameters.
|
||||
for (var i in otherArmy.assignedAgainst)
|
||||
{
|
||||
if (this.assignedAgainst[i] === undefined)
|
||||
this.assignedAgainst[i] = otherArmy.assignedAgainst[i];
|
||||
else
|
||||
this.assignedAgainst[i] = this.assignedAgainst[i].concat(otherArmy.assignedAgainst[i]);
|
||||
}
|
||||
for (var i in otherArmy.assignedTo)
|
||||
this.assignedTo[i] = otherArmy.assignedTo[i];
|
||||
|
||||
for each (var id in otherArmy.foeEntities)
|
||||
this.addFoe(gameState, id);
|
||||
// TODO: reassign those ?
|
||||
for each (var id in otherArmy.ownEntities)
|
||||
this.addOwn(gameState, id);
|
||||
|
||||
this.recalculatePosition(gameState, true);
|
||||
this.recalculateStrengths(gameState);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: when there is a technology update, we should probably recompute the strengths, or weird stuffs might happen.
|
||||
m.Army.prototype.checkEvents = function (gameState, events)
|
||||
{
|
||||
var renameEvents = events["EntityRenamed"]; // take care of promoted and packed units
|
||||
var destroyEvents = events["Destroy"];
|
||||
var convEvents = events["OwnershipChanged"];
|
||||
var garriEvents = events["Garrison"];
|
||||
|
||||
// Warning the metadata is already cloned in shared.js. Futhermore, changes should be done before destroyEvents
|
||||
// otherwise it would remove the old entity from this army list
|
||||
// TODO we should may-be reevaluate the strength
|
||||
for each (var msg in renameEvents)
|
||||
{
|
||||
if (this.foeEntities.indexOf(msg.entity) !== -1)
|
||||
{
|
||||
var idx = this.foeEntities.indexOf(msg.entity);
|
||||
this.foeEntities[idx] = msg.newentity;
|
||||
this.assignedAgainst[msg.newentity] = this.assignedAgainst[msg.entity];
|
||||
this.assignedAgainst[msg.entity] = undefined;
|
||||
for (var to in this.assignedTo)
|
||||
if (this.assignedTo[to] == msg.entity)
|
||||
this.assignedTo[to] = msg.newentity;
|
||||
}
|
||||
else if (this.ownEntities.indexOf(msg.entity) !== -1)
|
||||
{
|
||||
var idx = this.ownEntities.indexOf(msg.entity);
|
||||
this.ownEntities[idx] = msg.newentity;
|
||||
this.assignedTo[msg.newentity] = this.assignedTo[msg.entity];
|
||||
this.assignedTo[msg.entity] = undefined;
|
||||
for (var against in this.assignedAgainst)
|
||||
{
|
||||
if (!this.assignedAgainst[against])
|
||||
continue;
|
||||
if (this.assignedAgainst[against].indexOf(msg.entity) !== -1)
|
||||
this.assignedAgainst[against][this.assignedAgainst[against].indexOf(msg.entity)] = msg.newentity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for each (var msg in destroyEvents)
|
||||
{
|
||||
if (msg.entityObj === undefined)
|
||||
continue;
|
||||
if (msg.entityObj._entity.owner === PlayerID)
|
||||
this.removeOwn(gameState, msg.entity, msg.entityObj);
|
||||
else
|
||||
this.removeFoe(gameState, msg.entity, msg.entityObj);
|
||||
}
|
||||
|
||||
for each (var msg in garriEvents)
|
||||
this.removeFoe(gameState, msg.entity);
|
||||
|
||||
for each (var msg in convEvents)
|
||||
{
|
||||
if (msg.to === PlayerID)
|
||||
{
|
||||
// we have converted an enemy, let's assign it as a defender
|
||||
if (this.removeFoe(gameState, msg.entity))
|
||||
this.addOwn(gameState, msg.entity);
|
||||
} else if (msg.from === PlayerID)
|
||||
this.removeOwn(gameState, msg.entity); // TODO: add allies
|
||||
}
|
||||
}
|
||||
|
||||
// assumes cleaned army.
|
||||
// this only checks for breakaways.
|
||||
m.Army.prototype.onUpdate = function (gameState)
|
||||
{
|
||||
var breakaways = [];
|
||||
// TODO: assign unassigned defenders, cleanup of a few things.
|
||||
// perhaps occasional strength recomputation
|
||||
|
||||
// occasional update or breakaways, positions…
|
||||
if (gameState.getTimeElapsed() - this.positionLastUpdate > 5000)
|
||||
{
|
||||
this.recalculatePosition(gameState);
|
||||
this.positionLastUpdate = gameState.getTimeElapsed();
|
||||
|
||||
// Check for breakaways.
|
||||
for (var i = 0; i < this.foeEntities.length; ++i)
|
||||
{
|
||||
var id = this.foeEntities[i];
|
||||
var ent = gameState.getEntityById(id);
|
||||
if (API3.SquareVectorDistance(ent.position(), this.foePosition) > this.breakawaySize)
|
||||
{
|
||||
breakaways.push(id);
|
||||
if(this.removeFoe(gameState, id))
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
this.recalculatePosition(gameState);
|
||||
}
|
||||
|
||||
return breakaways;
|
||||
}
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
286
binaries/data/mods/public/simulation/ai/petra/attackManager.js
Normal file
286
binaries/data/mods/public/simulation/ai/petra/attackManager.js
Normal file
@ -0,0 +1,286 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
/* Attack Manager
|
||||
*/
|
||||
|
||||
m.AttackManager = function(Config)
|
||||
{
|
||||
this.Config = Config;
|
||||
|
||||
this.totalNumber = 0;
|
||||
this.attackNumber = 0;
|
||||
this.rushNumber = 0;
|
||||
this.raidNumber = 0;
|
||||
this.upcomingAttacks = { "CityAttack": [], "Rush": [], "Raid": [] };
|
||||
this.startedAttacks = { "CityAttack": [], "Rush": [], "Raid": [] };
|
||||
this.debugTime = 0;
|
||||
};
|
||||
|
||||
// More initialisation for stuff that needs the gameState
|
||||
m.AttackManager.prototype.init = function(gameState, queues)
|
||||
{
|
||||
this.maxRushes = 0;
|
||||
if (this.Config.personality.aggressive > 0.9)
|
||||
this.maxRushes = 2
|
||||
else if (this.Config.personality.aggressive > 0.7)
|
||||
this.maxRushes = 1;
|
||||
else
|
||||
this.maxRushes = 0;
|
||||
};
|
||||
|
||||
// Some functions are run every turn
|
||||
// Others once in a while
|
||||
m.AttackManager.prototype.update = function(gameState, queues, events)
|
||||
{
|
||||
if (this.Config.debug == 2 && gameState.getTimeElapsed() > this.debugTime + 60000)
|
||||
{
|
||||
this.debugTime = gameState.getTimeElapsed();
|
||||
warn(" upcoming attacks =================");
|
||||
for (var attackType in this.upcomingAttacks)
|
||||
{
|
||||
for (var i = 0; i < this.upcomingAttacks[attackType].length; ++i)
|
||||
{
|
||||
var attack = this.upcomingAttacks[attackType][i];
|
||||
warn(" type " + attackType + " state " + attack.state + " paused " + attack.isPaused());
|
||||
}
|
||||
}
|
||||
warn(" started attacks ==================");
|
||||
for (var attackType in this.startedAttacks)
|
||||
{
|
||||
for (var i = 0; i < this.startedAttacks[attackType].length; ++i)
|
||||
{
|
||||
var attack = this.startedAttacks[attackType][i];
|
||||
warn(" type " + attackType + " state " + attack.state + " paused " + attack.isPaused());
|
||||
}
|
||||
}
|
||||
warn(" ==================================");
|
||||
}
|
||||
|
||||
for (var attackType in this.upcomingAttacks)
|
||||
{
|
||||
for (var i = 0; i < this.upcomingAttacks[attackType].length; ++i)
|
||||
{
|
||||
var attack = this.upcomingAttacks[attackType][i];
|
||||
attack.checkEvents(gameState, events, queues);
|
||||
|
||||
// okay so we'll get the support plan
|
||||
if (!attack.isStarted())
|
||||
{
|
||||
var updateStep = attack.updatePreparation(gameState, this,events);
|
||||
|
||||
// now we're gonna check if the preparation time is over
|
||||
if (updateStep === 1 || attack.isPaused() )
|
||||
{
|
||||
// just chillin'
|
||||
}
|
||||
else if (updateStep === 0 || updateStep === 3)
|
||||
{
|
||||
if (this.Config.debug)
|
||||
warn("Attack Manager: " + attack.getType() + " plan " + attack.getName() + " aborted.");
|
||||
if (updateStep === 3)
|
||||
this.attackPlansEncounteredWater = true;
|
||||
attack.Abort(gameState, this);
|
||||
this.upcomingAttacks[attackType].splice(i--,1);
|
||||
}
|
||||
else if (updateStep === 2)
|
||||
{
|
||||
if (attack.StartAttack(gameState,this))
|
||||
{
|
||||
var targetName = gameState.sharedScript.playersData[attack.targetPlayer].name;
|
||||
var proba = Math.random();
|
||||
if (proba < 0.2)
|
||||
var chatText = "I am launching an attack against " + targetName + ".";
|
||||
else if (proba < 0.4)
|
||||
var chatText = "Attacking " + targetName + ".";
|
||||
else if (proba < 0.7)
|
||||
var chatText = "I have sent an army against " + targetName + ".";
|
||||
else
|
||||
var chatText = "I'm starting an attack against " + targetName + ".";
|
||||
gameState.ai.chatTeam(chatText);
|
||||
|
||||
if (this.Config.debug)
|
||||
warn("Attack Manager: Starting " + attack.getType() + " plan " + attack.getName());
|
||||
this.startedAttacks[attackType].push(attack);
|
||||
}
|
||||
else
|
||||
attack.Abort(gameState, this);
|
||||
this.upcomingAttacks[attackType].splice(i--,1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetName = gameState.sharedScript.playersData[attack.targetPlayer].name;
|
||||
var proba = Math.random();
|
||||
if (proba < 0.2)
|
||||
var chatText = "I am launching an attack against " + targetName + ".";
|
||||
else if (proba < 0.4)
|
||||
var chatText = "Attacking " + targetName + ".";
|
||||
else if (proba < 0.7)
|
||||
var chatText = "I have sent an army against " + targetName + ".";
|
||||
else
|
||||
var chatText = "I'm starting an attack against " + targetName + ".";
|
||||
gameState.ai.chatTeam(chatText);
|
||||
|
||||
if (this.Config.debug)
|
||||
warn("Attack Manager: Starting " + attack.getType() + " plan " + attack.getName());
|
||||
this.startedAttacks[attackType].push(attack);
|
||||
this.upcomingAttacks[attackType].splice(i--,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var attackType in this.startedAttacks)
|
||||
{
|
||||
for (var i = 0; i < this.startedAttacks[attackType].length; ++i)
|
||||
{
|
||||
var attack = this.startedAttacks[attackType][i];
|
||||
attack.checkEvents(gameState, events, queues);
|
||||
// okay so then we'll update the attack.
|
||||
if (attack.isPaused())
|
||||
continue;
|
||||
var remaining = attack.update(gameState,this,events);
|
||||
if (!remaining)
|
||||
{
|
||||
if (this.Config.debug)
|
||||
warn("Military Manager: " + attack.getType() + " plan " + attack.getName() + " is finished with remaining " + remaining);
|
||||
attack.Abort(gameState);
|
||||
this.startedAttacks[attackType].splice(i--,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// creating plans after updating because an aborted plan might be reused in that case.
|
||||
|
||||
// TODO: remove the limitation to attacks when on water maps.
|
||||
if (!gameState.ai.HQ.waterMap && !this.attackPlansEncounteredWater)
|
||||
{
|
||||
if (this.rushNumber < this.maxRushes && gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_barracks"), true) >= 1)
|
||||
{
|
||||
if (this.upcomingAttacks["Rush"].length === 0)
|
||||
{
|
||||
// we have a barracks and we want to rush, rush.
|
||||
var attackPlan = new m.AttackPlan(gameState, this.Config, this.totalNumber, -1, "Rush");
|
||||
if (this.Config.debug)
|
||||
warn("Headquarters: Rushing plan " + this.totalNumber + " with maxRushes " + this.maxRushes);
|
||||
this.rushNumber++;
|
||||
this.totalNumber++;
|
||||
this.upcomingAttacks["Rush"].push(attackPlan);
|
||||
}
|
||||
}
|
||||
// if we have a barracks, there's no water, we're at age >= 1 and we've decided to attack.
|
||||
else if (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_barracks"), true) >= 1
|
||||
&& (gameState.currentPhase() > 1 || gameState.isResearching(gameState.townPhase())))
|
||||
{
|
||||
if (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_dock"), true) === 0 && gameState.ai.HQ.waterMap)
|
||||
{
|
||||
// wait till we get a dock.
|
||||
}
|
||||
else if (this.upcomingAttacks["CityAttack"].length === 0)
|
||||
{
|
||||
if (this.attackNumber < 2)
|
||||
var attackPlan = new m.AttackPlan(gameState, this.Config, this.totalNumber, -1);
|
||||
else
|
||||
var attackPlan = new m.AttackPlan(gameState, this.Config, this.totalNumber, -1, "superSized");
|
||||
|
||||
if (attackPlan.failed)
|
||||
this.attackPlansEncounteredWater = true; // hack
|
||||
else
|
||||
{
|
||||
if (this.Config.debug)
|
||||
warn("Military Manager: Creating the plan " + this.totalNumber);
|
||||
this.attackNumber++;
|
||||
this.totalNumber++;
|
||||
this.upcomingAttacks["CityAttack"].push(attackPlan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.upcomingAttacks["Raid"].length === 999)
|
||||
{
|
||||
var enemyCC = gameState.getEnemyStructures().filter(API3.Filters.and(API3.Filters.byClass("CivCentre"), API3.Filters.isFoundation()));
|
||||
if (enemyCC.length > 0)
|
||||
{
|
||||
// prepare some raid on this CC
|
||||
var enemy = enemyCC.toEntityArray()[0].owner();
|
||||
var attackPlan = new m.AttackPlan(gameState, this.Config, this.totalNumber, enemy, "Raid");
|
||||
if (this.Config.debug)
|
||||
warn("Headquarters: Raiding plan " + this.totalNumber);
|
||||
this.raidNumber++;
|
||||
this.totalNumber++;
|
||||
this.upcomingAttacks["Raid"].push(attackPlan);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
m.AttackManager.prototype.pausePlan = function(gameState, planName)
|
||||
{
|
||||
for (var attackType in this.upcomingAttacks)
|
||||
{
|
||||
for (var i in this.upcomingAttacks[attackType])
|
||||
{
|
||||
var attack = this.upcomingAttacks[attackType][i];
|
||||
if (attack.getName() == planName)
|
||||
attack.setPaused(true);
|
||||
}
|
||||
}
|
||||
|
||||
for (var attackType in this.startedAttacks)
|
||||
{
|
||||
for (var i in this.startedAttacks[attackType])
|
||||
{
|
||||
var attack = this.startedAttacks[attackType][i];
|
||||
if (attack.getName() == planName)
|
||||
attack.setPaused(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
m.AttackManager.prototype.unpausePlan = function(gameState, planName)
|
||||
{
|
||||
for (var attackType in this.upcomingAttacks)
|
||||
{
|
||||
for (var i in this.upcomingAttacks[attackType])
|
||||
{
|
||||
var attack = this.upcomingAttacks[attackType][i];
|
||||
if (attack.getName() == planName)
|
||||
attack.setPaused(false);
|
||||
}
|
||||
}
|
||||
|
||||
for (var attackType in this.startedAttacks)
|
||||
{
|
||||
for (var i in this.startedAttacks[attackType])
|
||||
{
|
||||
var attack = this.startedAttacks[attackType][i];
|
||||
if (attack.getName() == planName)
|
||||
attack.setPaused(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
m.AttackManager.prototype.pauseAllPlans = function(gameState)
|
||||
{
|
||||
for (var attackType in this.upcomingAttacks)
|
||||
for (var i in this.upcomingAttacks[attackType])
|
||||
this.upcomingAttacks[attackType][i].setPaused(true);
|
||||
|
||||
for (var attackType in this.startedAttacks)
|
||||
for (var i in this.startedAttacks[attackType])
|
||||
this.startedAttacks[attackType][i].setPaused(true);
|
||||
};
|
||||
|
||||
m.AttackManager.prototype.unpauseAllPlans = function(gameState)
|
||||
{
|
||||
for (var attackType in this.upcomingAttacks)
|
||||
for (var i in this.upcomingAttacks[attackType])
|
||||
this.upcomingAttacks[attackType][i].setPaused(false);
|
||||
|
||||
for (var attackType in this.startedAttacks)
|
||||
for (var i in this.startedAttacks[attackType])
|
||||
this.startedAttacks[attackType][i].setPaused(false);
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
1346
binaries/data/mods/public/simulation/ai/petra/attackPlan.js
Normal file
1346
binaries/data/mods/public/simulation/ai/petra/attackPlan.js
Normal file
File diff suppressed because it is too large
Load Diff
840
binaries/data/mods/public/simulation/ai/petra/baseManager.js
Normal file
840
binaries/data/mods/public/simulation/ai/petra/baseManager.js
Normal file
@ -0,0 +1,840 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
/* Base Manager
|
||||
* Handles lower level economic stuffs.
|
||||
* Some tasks:
|
||||
-tasking workers: gathering/hunting/building/repairing?/scouting/plans.
|
||||
-giving feedback/estimates on GR
|
||||
-achieving building stuff plans (scouting/getting ressource/building) or other long-staying plans, if I ever get any.
|
||||
-getting good spots for dropsites
|
||||
-managing dropsite use in the base
|
||||
> warning HQ if we'll need more space
|
||||
-updating whatever needs updating, keeping track of stuffs (rebuilding needs…)
|
||||
*/
|
||||
|
||||
m.BaseManager = function(Config)
|
||||
{
|
||||
this.Config = Config;
|
||||
this.ID = m.playerGlobals[PlayerID].uniqueIDBases++;
|
||||
|
||||
// anchor building: seen as the main building of the base. Needs to have territorial influence
|
||||
this.anchor = undefined;
|
||||
this.accessIndex = undefined;
|
||||
|
||||
// Maximum distance (from any dropsite) to look for resources
|
||||
// 3 areas are used: from 0 to max/4, from max/4 to max/2 and from max/2 to max
|
||||
this.maxDistResourceSquare = 360*360;
|
||||
|
||||
this.constructing = false;
|
||||
|
||||
// vector for iterating, to check one use the HQ map.
|
||||
this.territoryIndices = [];
|
||||
};
|
||||
|
||||
m.BaseManager.prototype.init = function(gameState, unconstructed)
|
||||
{
|
||||
this.constructing = unconstructed;
|
||||
// entitycollections
|
||||
this.units = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "base", this.ID));
|
||||
this.workers = this.units.filter(API3.Filters.byMetadata(PlayerID,"role","worker"));
|
||||
this.buildings = gameState.getOwnStructures().filter(API3.Filters.byMetadata(PlayerID, "base", this.ID));
|
||||
|
||||
this.units.allowQuickIter();
|
||||
this.workers.allowQuickIter();
|
||||
this.buildings.allowQuickIter();
|
||||
|
||||
this.units.registerUpdates();
|
||||
this.workers.registerUpdates();
|
||||
this.buildings.registerUpdates();
|
||||
|
||||
// array of entity IDs, with each being
|
||||
this.dropsites = {};
|
||||
this.dropsiteSupplies = {};
|
||||
this.gatherers = {};
|
||||
for each (var type in this.Config.resources)
|
||||
{
|
||||
this.dropsiteSupplies[type] = {"nearby": [], "medium": [], "faraway": []};
|
||||
this.gatherers[type] = {"nextCheck": 0, "used": 0, "lost": 0};
|
||||
}
|
||||
};
|
||||
|
||||
m.BaseManager.prototype.assignEntity = function(unit)
|
||||
{
|
||||
unit.setMetadata(PlayerID, "base", this.ID);
|
||||
this.units.updateEnt(unit);
|
||||
this.workers.updateEnt(unit);
|
||||
this.buildings.updateEnt(unit);
|
||||
};
|
||||
|
||||
m.BaseManager.prototype.setAnchor = function(gameState, anchorEntity)
|
||||
{
|
||||
if (!anchorEntity.hasClass("Structure") || !anchorEntity.hasTerritoryInfluence())
|
||||
{
|
||||
warn("Error: Petra base " + this.ID + " has been assigned an anchor building that has no territorial influence. Please report this on the forum.")
|
||||
return false;
|
||||
}
|
||||
this.anchor = anchorEntity;
|
||||
this.anchor.setMetadata(PlayerID, "base", this.ID);
|
||||
this.anchor.setMetadata(PlayerID, "baseAnchor", true);
|
||||
this.buildings.updateEnt(this.anchor);
|
||||
this.accessIndex = gameState.ai.accessibility.getAccessValue(this.anchor.position());
|
||||
return true;
|
||||
};
|
||||
|
||||
m.BaseManager.prototype.checkEvents = function (gameState, events, queues)
|
||||
{
|
||||
var renameEvents = events["EntityRenamed"];
|
||||
var destEvents = events["Destroy"];
|
||||
var createEvents = events["Create"];
|
||||
var cFinishedEvents = events["ConstructionFinished"];
|
||||
|
||||
for (var i in renameEvents)
|
||||
{
|
||||
var ent = gameState.getEntityById(renameEvents[i].newentity);
|
||||
var workerObject = ent.getMetadata(PlayerID, "worker-object");
|
||||
if (workerObject)
|
||||
workerObject.ent = ent;
|
||||
}
|
||||
|
||||
for (var i in destEvents)
|
||||
{
|
||||
var evt = destEvents[i];
|
||||
// let's check we haven't lost an important building here.
|
||||
if (evt != undefined && !evt.SuccessfulFoundation && evt.entityObj != undefined && evt.metadata !== undefined && evt.metadata[PlayerID] &&
|
||||
evt.metadata[PlayerID]["base"] !== undefined && evt.metadata[PlayerID]["base"] == this.ID)
|
||||
{
|
||||
var ent = evt.entityObj;
|
||||
if (ent.resourceDropsiteTypes() && !ent.hasClass("Elephant"))
|
||||
this.removeDropsite(gameState, ent);
|
||||
if (evt.metadata[PlayerID]["baseAnchor"] && evt.metadata[PlayerID]["baseAnchor"] == true)
|
||||
{
|
||||
// sounds like we lost our anchor. Let's try rebuilding it.
|
||||
// TODO: currently the HQ manager sets us as initgathering, we probably ouht to do it
|
||||
this.anchor = undefined;
|
||||
|
||||
this.constructing = true; // let's switch mode.
|
||||
this.workers.forEach( function (worker) { worker.stopMoving(); });
|
||||
queues.civilCentre.addItem(new m.ConstructionPlan(gameState, gameState.ai.HQ.bBase[0], { "base": this.ID, "baseAnchor": true }, ent.position()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
for (var i in cFinishedEvents)
|
||||
{
|
||||
var evt = cFinishedEvents[i];
|
||||
if (evt && evt.newentity)
|
||||
{
|
||||
var ent = gameState.getEntityById(evt.newentity);
|
||||
if (ent === undefined)
|
||||
continue;
|
||||
|
||||
if (ent.getMetadata(PlayerID,"base") == this.ID)
|
||||
{
|
||||
if (ent.resourceDropsiteTypes() && !ent.hasClass("Elephant"))
|
||||
this.assignResourceToDropsite(gameState, ent);
|
||||
if (ent.resourceDropsiteTypes() && !ent.hasClass("Elephant") && this.Config.debug)
|
||||
for each (var ress in ent.resourceDropsiteTypes())
|
||||
warn(" DPresource " + ress + " = " + this.getResourceLevel(gameState, ress));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i in createEvents)
|
||||
{
|
||||
var evt = createEvents[i];
|
||||
if (evt && evt.entity)
|
||||
{
|
||||
var ent = gameState.getEntityById(evt.entity);
|
||||
if (ent === undefined)
|
||||
continue;
|
||||
|
||||
// do necessary stuff here
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Assign the resources around the dropsites of this basis in three areas according to distance, and sort them in each area.
|
||||
* Moving resources (animals) and buildable resources (fields) are treated elsewhere.
|
||||
*/
|
||||
m.BaseManager.prototype.assignResourceToDropsite = function (gameState, dropsite)
|
||||
{
|
||||
if (this.dropsites[dropsite.id()])
|
||||
{
|
||||
if (this.Config.debug)
|
||||
warn("assignResourceToDropsite: dropsite already in the list. Should never happen");
|
||||
return;
|
||||
}
|
||||
this.dropsites[dropsite.id()] = true;
|
||||
|
||||
var self = this;
|
||||
for each (var type in dropsite.resourceDropsiteTypes())
|
||||
{
|
||||
var resources = gameState.getResourceSupplies(type);
|
||||
if (resources.length === 0)
|
||||
continue;
|
||||
|
||||
var nearby = this.dropsiteSupplies[type]["nearby"];
|
||||
var medium = this.dropsiteSupplies[type]["medium"];
|
||||
var faraway = this.dropsiteSupplies[type]["faraway"];
|
||||
|
||||
resources.forEach(function(supply)
|
||||
{
|
||||
if (!supply.position())
|
||||
return;
|
||||
if (supply.getMetadata(PlayerID, "inaccessible") === true)
|
||||
return;
|
||||
if (supply.hasClass("Animal")) // moving resources are treated differently TODO
|
||||
return;
|
||||
if (supply.hasClass("Field")) // fields are treated separately
|
||||
return;
|
||||
// quickscope accessibility check
|
||||
if (!gameState.ai.accessibility.pathAvailable(gameState, dropsite.position(), supply.position(),false, true))
|
||||
return;
|
||||
|
||||
var dist = API3.SquareVectorDistance(supply.position(), dropsite.position());
|
||||
if (dist < self.maxDistResourceSquare)
|
||||
{
|
||||
if (supply.resourceSupplyType()["generic"] == "treasure")
|
||||
{
|
||||
if (dist < self.maxDistResourceSquare/4)
|
||||
dist = 0;
|
||||
else
|
||||
dist = self.maxDistResourceSquare/16;
|
||||
}
|
||||
if (dist < self.maxDistResourceSquare/16) // distmax/4
|
||||
nearby.push({ "dropsite": dropsite.id(), "id": supply.id(), "ent": supply, "dist": dist });
|
||||
else if (dist < self.maxDistResourceSquare/4) // distmax/2
|
||||
medium.push({ "dropsite": dropsite.id(), "id": supply.id(), "ent": supply, "dist": dist });
|
||||
else
|
||||
faraway.push({ "dropsite": dropsite.id(), "id": supply.id(), "ent": supply, "dist": dist });
|
||||
}
|
||||
});
|
||||
|
||||
nearby.sort(function(r1, r2) { return (r1.dist - r2.dist);});
|
||||
medium.sort(function(r1, r2) { return (r1.dist - r2.dist);});
|
||||
faraway.sort(function(r1, r2) { return (r1.dist - r2.dist);});
|
||||
|
||||
/* var debug = false;
|
||||
if (debug)
|
||||
{
|
||||
faraway.forEach(function(res){
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [res.ent.id()], "rgb": [2,0,0]});
|
||||
});
|
||||
medium.forEach(function(res){
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [res.ent.id()], "rgb": [0,2,0]});
|
||||
});
|
||||
nearby.forEach(function(res){
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [res.ent.id()], "rgb": [0,0,2]});
|
||||
});
|
||||
} */
|
||||
}
|
||||
};
|
||||
|
||||
// completely remove the dropsite resources from our list.
|
||||
m.BaseManager.prototype.removeDropsite = function (gameState, ent)
|
||||
{
|
||||
if (!ent.id())
|
||||
return;
|
||||
|
||||
var removeSupply = function(entId, supply){
|
||||
for (var i = 0; i < supply.length; ++i)
|
||||
{
|
||||
// exhausted resource, remove it from this list
|
||||
if (!supply[i].ent || !gameState.getEntityById(supply[i].id))
|
||||
supply.splice(i--, 1);
|
||||
// resource assigned to the removed dropsite, remove it
|
||||
else if (supply["dropsite"] === entId)
|
||||
supply.splice(i--, 1);
|
||||
}
|
||||
};
|
||||
|
||||
for (var type in this.dropsiteSupplies)
|
||||
{
|
||||
removeSupply(ent.id(), this.dropsiteSupplies[type]["nearby"]);
|
||||
removeSupply(ent.id(), this.dropsiteSupplies[type]["medium"]);
|
||||
removeSupply(ent.id(), this.dropsiteSupplies[type]["faraway"]);
|
||||
}
|
||||
|
||||
this.dropsites[ent.id()] = undefined;
|
||||
return;
|
||||
};
|
||||
|
||||
// Returns the position of the best place to build a new dropsite for the specified resource
|
||||
m.BaseManager.prototype.findBestDropsiteLocation = function(gameState, resource)
|
||||
{
|
||||
|
||||
var storeHousePlate = gameState.getTemplate(gameState.applyCiv("structures/{civ}_storehouse"));
|
||||
|
||||
// This builds a map. The procedure is fairly simple. It adds the resource maps
|
||||
// (which are dynamically updated and are made so that they will facilitate DP placement)
|
||||
// Then checks for a good spot in the territory. If none, and town/city phase, checks outside
|
||||
// The AI will currently not build a CC if it wouldn't connect with an existing CC.
|
||||
|
||||
var obstructions = m.createObstructionMap(gameState, this.accessIndex, storeHousePlate);
|
||||
obstructions.expandInfluences();
|
||||
|
||||
// copy the resource map as initialization.
|
||||
var locateMap = new API3.Map(gameState.sharedScript, gameState.sharedScript.resourceMaps[resource].map, true);
|
||||
|
||||
var DPFoundations = gameState.getOwnFoundations().filter(API3.Filters.byType(gameState.applyCiv("foundation|structures/{civ}_storehouse")));
|
||||
|
||||
var ccEnts = gameState.getOwnEntities().filter(API3.Filters.byClass("CivCentre")).toEntityArray();
|
||||
|
||||
// TODO: might be better to check dropsites someplace else.
|
||||
// loop over this in this.terrytoryindices. It's usually a little too much, but it's always enough.
|
||||
for (var p = 0; p < this.territoryIndices.length; ++p)
|
||||
{
|
||||
var j = this.territoryIndices[p];
|
||||
locateMap.map[j] *= 2;
|
||||
|
||||
// only add where the map is currently not null, ie in our territory and some "Resource" would be close.
|
||||
// This makes the placement go from "OK" to "human-like".
|
||||
for (var i in gameState.sharedScript.resourceMaps)
|
||||
if (locateMap.map[j] !== 0 && i !== "food")
|
||||
locateMap.map[j] += gameState.sharedScript.resourceMaps[i].map[j];
|
||||
|
||||
locateMap.map[j] *= 0.7; // Just a normalisation factor as the max is 255
|
||||
|
||||
var pos = [j%locateMap.width+0.5, Math.floor(j/locateMap.width)+0.5];
|
||||
pos = [gameState.cellSize*pos[0], gameState.cellSize*pos[1]];
|
||||
for (var i in this.dropsites)
|
||||
{
|
||||
if (!gameState.getEntityById(i))
|
||||
continue;
|
||||
var dpPos = gameState.getEntityById(i).position();
|
||||
if (!dpPos)
|
||||
continue;
|
||||
var dist = API3.SquareVectorDistance(dpPos, pos);
|
||||
if (dist < 3600)
|
||||
{
|
||||
locateMap.map[j] = 0;
|
||||
break;
|
||||
}
|
||||
else if (dist < 6400)
|
||||
locateMap.map[j] /= 2;
|
||||
}
|
||||
if (locateMap.map[j] == 0)
|
||||
continue;
|
||||
|
||||
for (var i in DPFoundations._entities)
|
||||
{
|
||||
var dpPos = gameState.getEntityById(i).position();
|
||||
if (!dpPos)
|
||||
continue;
|
||||
var dist = API3.SquareVectorDistance(dpPos, pos);
|
||||
if (dist < 3600)
|
||||
{
|
||||
locateMap.map[j] = 0;
|
||||
break;
|
||||
}
|
||||
else if (dist < 6400)
|
||||
locateMap.map[j] /= 2;
|
||||
}
|
||||
if (locateMap.map[j] == 0)
|
||||
continue;
|
||||
|
||||
for each (var cc in ccEnts)
|
||||
{
|
||||
var ccPos = cc.position();
|
||||
if (!ccPos)
|
||||
continue;
|
||||
var dist = API3.SquareVectorDistance(ccPos, pos);
|
||||
if (dist < 3600)
|
||||
{
|
||||
locateMap.map[j] = 0;
|
||||
break;
|
||||
}
|
||||
else if (dist < 6400)
|
||||
locateMap.map[j] /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
var best = locateMap.findBestTile(2, obstructions); // try to find a spot to place a DP.
|
||||
var bestIdx = best[0];
|
||||
|
||||
if (this.Config.debug == 2)
|
||||
warn("for dropsite best is " + best[1] + " at " + gameState.getTimeElapsed());
|
||||
|
||||
// tell the dropsite builder we haven't found anything satisfactory.
|
||||
// var cutbest = 60;
|
||||
// // being less demanding for first dropsite
|
||||
// if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_storehouse")) == 0)
|
||||
// var cutbest = 50;
|
||||
|
||||
// if (best[1] < cutbest)
|
||||
// return false;
|
||||
|
||||
var quality = best[1];
|
||||
if (quality <= 0)
|
||||
return {"quality": quality, "pos": [0, 0]};
|
||||
var x = ((bestIdx % locateMap.width) + 0.5) * gameState.cellSize;
|
||||
var z = (Math.floor(bestIdx / locateMap.width) + 0.5) * gameState.cellSize;
|
||||
return {"quality": quality, "pos": [x, z]};
|
||||
};
|
||||
|
||||
m.BaseManager.prototype.getResourceLevel = function (gameState, type)
|
||||
{
|
||||
var count = 0;
|
||||
var check = {};
|
||||
var nearby = this.dropsiteSupplies[type]["nearby"];
|
||||
for each (var supply in nearby)
|
||||
{
|
||||
if (check[supply.id]) // avoid double counting as same resource can appear several time
|
||||
continue;
|
||||
check[supply.id] = true;
|
||||
count += supply.ent.resourceSupplyAmount();
|
||||
}
|
||||
var medium = this.dropsiteSupplies[type]["medium"];
|
||||
for each (var supply in medium)
|
||||
{
|
||||
if (check[supply.id])
|
||||
continue;
|
||||
check[supply.id] = true;
|
||||
count += 0.6*supply.ent.resourceSupplyAmount();
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
// check our resource levels and react accordingly
|
||||
m.BaseManager.prototype.checkResourceLevels = function (gameState, queues)
|
||||
{
|
||||
for each (var type in this.Config.resources)
|
||||
{
|
||||
if (type == "food")
|
||||
{
|
||||
var count = this.getResourceLevel(gameState, type); // TODO animals are not accounted, may-be we should
|
||||
var numFarms = gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_field"), true);
|
||||
var numFound = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_field"), true);
|
||||
var numQueue = queues.field.countQueuedUnits();
|
||||
|
||||
// TODO if not yet farms, add a check on time used/lost and build farmstead if needed
|
||||
if (count < 1200 && numFarms + numFound + numQueue === 0) // tell the queue manager we'll be trying to build fields shortly.
|
||||
{
|
||||
for (var i = 0; i < this.Config.Economy.initialFields; ++i)
|
||||
{
|
||||
var plan = new m.ConstructionPlan(gameState, "structures/{civ}_field", { "base" : this.ID });
|
||||
plan.isGo = function() { return false; }; // don't start right away.
|
||||
queues.field.addItem(plan);
|
||||
}
|
||||
}
|
||||
else if (count < 400 && numFarms + numFound === 0)
|
||||
{
|
||||
for (var i in queues.field.queue)
|
||||
queues.field.queue[i].isGo = function() { return true; }; // start them
|
||||
}
|
||||
else if(gameState.ai.HQ.canBuild(gameState, "structures/{civ}_field")) // let's see if we need to add new farms.
|
||||
{
|
||||
if (numFound < 2 && numFound + numQueue < 3)
|
||||
queues.field.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_field", { "base" : this.ID }));
|
||||
}
|
||||
}
|
||||
else if (queues.dropsites.length() === 0 && gameState.countFoundationsByType(gameState.applyCiv("structures/{civ}_storehouse"), true) === 0)
|
||||
{
|
||||
if (gameState.ai.playedTurn > this.gatherers[type].nextCheck)
|
||||
{
|
||||
var self = this;
|
||||
this.gatherersByType(gameState, type).forEach(function (ent) {
|
||||
if (ent.unitAIState() === "INDIVIDUAL.GATHER.GATHERING")
|
||||
++self.gatherers[type].used;
|
||||
else if (ent.unitAIState() === "INDIVIDUAL.RETURNRESOURCE.APPROACHING")
|
||||
++self.gatherers[type].lost;
|
||||
});
|
||||
// TODO add also a test on remaining resources
|
||||
var total = this.gatherers[type].used + this.gatherers[type].lost;
|
||||
if (total > 150 || (total > 60 && type !== "wood"))
|
||||
{
|
||||
var ratio = this.gatherers[type].lost / total;
|
||||
if (ratio > 0.15)
|
||||
{
|
||||
var newDP = this.findBestDropsiteLocation(gameState, type);
|
||||
if (newDP.quality > 50 && gameState.ai.HQ.canBuild(gameState, "structures/{civ}_storehouse"))
|
||||
{
|
||||
queues.dropsites.addItem(new m.ConstructionPlan(gameState, "structures/{civ}_storehouse", { "base": this.ID }, newDP.pos));
|
||||
if (!gameState.isResearched("gather_capacity_wheelbarrow") && !gameState.isResearching("gather_capacity_wheelbarrow"))
|
||||
queues.minorTech.addItem(new m.ResearchPlan(gameState, "gather_capacity_wheelbarrow"));
|
||||
}
|
||||
else
|
||||
gameState.ai.HQ.buildNewBase(gameState, queues, type);
|
||||
}
|
||||
this.gatherers[type].nextCheck = gameState.ai.playedTurn + 20;
|
||||
this.gatherers[type].used = 0;
|
||||
this.gatherers[type].lost = 0;
|
||||
}
|
||||
else if (total === 0)
|
||||
this.gatherers[type].nextCheck = gameState.ai.playedTurn + 10;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.gatherers[type].nextCheck = gameState.ai.playedTurn;
|
||||
this.gatherers[type].used = 0;
|
||||
this.gatherers[type].lost = 0;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// let's return the estimated gather rates.
|
||||
m.BaseManager.prototype.getGatherRates = function(gameState, currentRates)
|
||||
{
|
||||
for (var i in currentRates)
|
||||
{
|
||||
// I calculate the exact gathering rate for each unit.
|
||||
// I must then lower that to account for travel time.
|
||||
// Given that the faster you gather, the more travel time matters,
|
||||
// I use some logarithms.
|
||||
// TODO: this should take into account for unit speed and/or distance to target
|
||||
|
||||
var units = this.gatherersByType(gameState, i);
|
||||
units.forEach(function (ent) {
|
||||
var gRate = ent.currentGatherRate();
|
||||
if (gRate !== undefined)
|
||||
currentRates[i] += Math.log(1+gRate)/1.1;
|
||||
});
|
||||
if (i === "food")
|
||||
{
|
||||
units = this.workers.filter(API3.Filters.byMetadata(PlayerID, "subrole", "hunter"));
|
||||
units.forEach(function (ent) {
|
||||
var gRate = ent.currentGatherRate()
|
||||
if (gRate !== undefined)
|
||||
currentRates[i] += Math.log(1+gRate)/1.1;
|
||||
});
|
||||
}
|
||||
currentRates[i] += 0.5*m.GetTCRessGatherer(gameState, i);
|
||||
}
|
||||
};
|
||||
|
||||
m.BaseManager.prototype.assignRolelessUnits = function(gameState)
|
||||
{
|
||||
// TODO: make this cleverer.
|
||||
var roleless = this.units.filter(API3.Filters.not(API3.Filters.byHasMetadata(PlayerID, "role")));
|
||||
var self = this;
|
||||
roleless.forEach(function(ent) {
|
||||
if (ent.hasClass("Worker") || ent.hasClass("CitizenSoldier"))
|
||||
ent.setMetadata(PlayerID, "role", "worker");
|
||||
else if (ent.hasClass("Support") && ent.hasClass("Elephant"))
|
||||
ent.setMetadata(PlayerID, "role", "worker");
|
||||
});
|
||||
};
|
||||
|
||||
// If the numbers of workers on the resources is unbalanced then set some of workers to idle so
|
||||
// they can be reassigned by reassignIdleWorkers.
|
||||
// TODO: actually this probably should be in the HQ.
|
||||
m.BaseManager.prototype.setWorkersIdleByPriority = function(gameState)
|
||||
{
|
||||
if (gameState.currentPhase() < 2)
|
||||
return;
|
||||
|
||||
var resources = gameState.ai.queueManager.getAvailableResources(gameState);
|
||||
|
||||
var avgOverdraft = 0;
|
||||
for each (var type in resources.types)
|
||||
avgOverdraft += resources[type];
|
||||
avgOverdraft /= 4;
|
||||
|
||||
var mostNeeded = gameState.ai.HQ.pickMostNeededResources(gameState);
|
||||
for each (var type in resources.types)
|
||||
{
|
||||
if (type === mostNeeded[0])
|
||||
continue;
|
||||
if (resources[type] > avgOverdraft + 200 || (resources[type] > avgOverdraft && avgOverdraft > 200))
|
||||
{
|
||||
if (this.gatherersByType(gameState, type).length === 0)
|
||||
continue;
|
||||
// TODO: perhaps change this?
|
||||
var nb = 2;
|
||||
this.gatherersByType(gameState, type).forEach( function (ent) {
|
||||
if (nb == 0)
|
||||
return;
|
||||
nb--;
|
||||
// TODO: might want to direct assign.
|
||||
ent.stopMoving();
|
||||
ent.setMetadata(PlayerID, "subrole","idle");
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: work on this.
|
||||
m.BaseManager.prototype.reassignIdleWorkers = function(gameState)
|
||||
{
|
||||
// Search for idle workers, and tell them to gather resources based on demand
|
||||
var filter = API3.Filters.or(API3.Filters.byMetadata(PlayerID,"subrole","idle"), API3.Filters.not(API3.Filters.byHasMetadata(PlayerID,"subrole")));
|
||||
var idleWorkers = gameState.updatingCollection("idle-workers-base-" + this.ID, filter, this.workers);
|
||||
|
||||
var self = this;
|
||||
if (idleWorkers.length) {
|
||||
idleWorkers.forEach(function(ent)
|
||||
{
|
||||
// Check that the worker isn't garrisoned
|
||||
if (ent.position() === undefined)
|
||||
return;
|
||||
// Support elephant can only be builders
|
||||
if (ent.hasClass("Support") && ent.hasClass("Elephant")) {
|
||||
ent.setMetadata(PlayerID, "subrole", "idle");
|
||||
return;
|
||||
}
|
||||
if (ent.hasClass("Worker"))
|
||||
{
|
||||
if (self.anchor && self.anchor.needsRepair() === true)
|
||||
ent.repair(self.anchor);
|
||||
else
|
||||
{
|
||||
var types = gameState.ai.HQ.pickMostNeededResources(gameState);
|
||||
ent.setMetadata(PlayerID, "subrole", "gatherer");
|
||||
ent.setMetadata(PlayerID, "gather-type", types[0]);
|
||||
m.AddTCRessGatherer(gameState, types[0]);
|
||||
}
|
||||
}
|
||||
else if (ent.hasClass("Cavalry"))
|
||||
ent.setMetadata(PlayerID, "subrole", "hunter");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
m.BaseManager.prototype.workersBySubrole = function(gameState, subrole)
|
||||
{
|
||||
return gameState.updatingCollection("subrole-" + subrole +"-base-" + this.ID, API3.Filters.byMetadata(PlayerID, "subrole", subrole), this.workers, true);
|
||||
};
|
||||
|
||||
m.BaseManager.prototype.gatherersByType = function(gameState, type)
|
||||
{
|
||||
return gameState.updatingCollection("workers-gathering-" + type +"-base-" + this.ID, API3.Filters.byMetadata(PlayerID, "gather-type", type), this.workersBySubrole(gameState, "gatherer"));
|
||||
};
|
||||
|
||||
|
||||
// returns an entity collection of workers.
|
||||
// They are idled immediatly and their subrole set to idle.
|
||||
m.BaseManager.prototype.pickBuilders = function(gameState, workers, number)
|
||||
{
|
||||
var availableWorkers = this.workers.filter(API3.Filters.not(API3.Filters.byClass("Cavalry"))).toEntityArray();
|
||||
availableWorkers.sort(function (a,b) {
|
||||
var vala = 0, valb = 0;
|
||||
if (a.getMetadata(PlayerID, "subrole") == "builder")
|
||||
vala = 100;
|
||||
if (b.getMetadata(PlayerID, "subrole") == "builder")
|
||||
valb = 100;
|
||||
if (a.getMetadata(PlayerID, "subrole") == "idle")
|
||||
vala = -20;
|
||||
if (b.getMetadata(PlayerID, "subrole") == "idle")
|
||||
valb = -20;
|
||||
if (a.getMetadata(PlayerID, "plan") != undefined)
|
||||
vala = -100;
|
||||
if (b.getMetadata(PlayerID, "plan") != undefined)
|
||||
valb = -100;
|
||||
return (vala - valb);
|
||||
});
|
||||
var needed = Math.min(number, availableWorkers.length);
|
||||
for (var i = 0; i < needed; ++i)
|
||||
{
|
||||
availableWorkers[i].stopMoving();
|
||||
availableWorkers[i].setMetadata(PlayerID, "subrole", "idle");
|
||||
workers.addEnt(availableWorkers[i]);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
|
||||
{
|
||||
// If we have some foundations, and we don't have enough builder-workers,
|
||||
// try reassigning some other workers who are nearby
|
||||
// AI tries to use builders sensibly, not completely stopping its econ.
|
||||
|
||||
var self = this;
|
||||
|
||||
// TODO: this is not perfect performance-wise.
|
||||
var foundations = this.buildings.filter(API3.Filters.and(API3.Filters.isFoundation(),API3.Filters.not(API3.Filters.byClass("Field")))).toEntityArray();
|
||||
|
||||
var damagedBuildings = this.buildings.filter(function (ent) {
|
||||
if (ent.foundationProgress() === undefined && ent.needsRepair())
|
||||
return true;
|
||||
return false;
|
||||
}).toEntityArray();
|
||||
|
||||
// Check if nothing to build
|
||||
if (!foundations.length && !damagedBuildings.length){
|
||||
return;
|
||||
}
|
||||
var workers = this.workers.filter(API3.Filters.not(API3.Filters.byClass("Cavalry")));
|
||||
var builderWorkers = this.workersBySubrole(gameState, "builder");
|
||||
var idleBuilderWorkers = this.workersBySubrole(gameState, "builder").filter(API3.Filters.isIdle());
|
||||
|
||||
// if we're constructing and we have the foundations to our base anchor, only try building that.
|
||||
if (this.constructing == true && this.buildings.filter(API3.Filters.and(API3.Filters.isFoundation(), API3.Filters.byMetadata(PlayerID, "baseAnchor", true))).length !== 0)
|
||||
{
|
||||
foundations = this.buildings.filter(API3.Filters.byMetadata(PlayerID, "baseAnchor", true)).toEntityArray();
|
||||
var tID = foundations[0].id();
|
||||
workers.forEach(function (ent) { //}){
|
||||
var target = ent.getMetadata(PlayerID, "target-foundation");
|
||||
if (target && target != tID)
|
||||
{
|
||||
ent.stopMoving();
|
||||
ent.setMetadata(PlayerID, "target-foundation", tID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (workers.length < 2)
|
||||
{
|
||||
var noobs = gameState.ai.HQ.bulkPickWorkers(gameState, this.ID, 2);
|
||||
if(noobs)
|
||||
{
|
||||
noobs.forEach(function (worker) { //}){
|
||||
worker.setMetadata(PlayerID,"base", self.ID);
|
||||
worker.setMetadata(PlayerID,"subrole", "builder");
|
||||
workers.updateEnt(worker);
|
||||
builderWorkers.updateEnt(worker);
|
||||
idleBuilderWorkers.updateEnt(worker);
|
||||
});
|
||||
}
|
||||
}
|
||||
var addedWorkers = 0;
|
||||
|
||||
var maxTotalBuilders = Math.ceil(workers.length * 0.2);
|
||||
if (this.constructing == true && maxTotalBuilders < 15)
|
||||
maxTotalBuilders = 15;
|
||||
|
||||
for (var i in foundations) {
|
||||
var target = foundations[i];
|
||||
|
||||
if (target.hasClass("Field"))
|
||||
continue; // we do not build fields
|
||||
|
||||
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length;
|
||||
var targetNB = this.Config.Economy.targetNumBuilders; // TODO: dynamic that.
|
||||
if (target.hasClass("House"))
|
||||
targetNB *= 2;
|
||||
else if (target.hasClass("Barracks") || target.hasClass("Tower"))
|
||||
targetNB = 4;
|
||||
else if (target.hasClass("Fortress"))
|
||||
targetNB = 7;
|
||||
if (target.getMetadata(PlayerID, "baseAnchor") == true)
|
||||
targetNB = 15;
|
||||
|
||||
if (assigned < targetNB) {
|
||||
if (builderWorkers.length - idleBuilderWorkers.length + addedWorkers < maxTotalBuilders) {
|
||||
|
||||
var addedToThis = 0;
|
||||
|
||||
idleBuilderWorkers.forEach(function(ent) {
|
||||
if (ent.position() && API3.SquareVectorDistance(ent.position(), target.position()) < 10000 && assigned + addedToThis < targetNB)
|
||||
{
|
||||
addedWorkers++;
|
||||
addedToThis++;
|
||||
ent.setMetadata(PlayerID, "target-foundation", target.id());
|
||||
}
|
||||
});
|
||||
if (assigned + addedToThis < targetNB)
|
||||
{
|
||||
var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); }).toEntityArray();
|
||||
var time = target.buildTime();
|
||||
nonBuilderWorkers.sort(function (workerA,workerB)
|
||||
{
|
||||
var coeffA = API3.SquareVectorDistance(target.position(),workerA.position());
|
||||
// elephant moves slowly, so when far away they are only useful if build time is long
|
||||
if (workerA.hasClass("Elephant"))
|
||||
coeffA *= 0.5 * (1 + (Math.sqrt(coeffA)/150)*(30/time));
|
||||
else if (workerA.getMetadata(PlayerID, "gather-type") === "food")
|
||||
coeffA *= 3;
|
||||
var coeffB = API3.SquareVectorDistance(target.position(),workerB.position());
|
||||
if (workerB.hasClass("Elephant"))
|
||||
coeffB *= 0.5 * (1 + (Math.sqrt(coeffB)/150)*(30/time));
|
||||
else if (workerB.getMetadata(PlayerID, "gather-type") === "food")
|
||||
coeffB *= 3;
|
||||
return (coeffA - coeffB);
|
||||
});
|
||||
var current = 0;
|
||||
while (assigned + addedToThis < targetNB && current < nonBuilderWorkers.length)
|
||||
{
|
||||
addedWorkers++;
|
||||
addedToThis++;
|
||||
var ent = nonBuilderWorkers[current++];
|
||||
ent.stopMoving();
|
||||
ent.setMetadata(PlayerID, "subrole", "builder");
|
||||
ent.setMetadata(PlayerID, "target-foundation", target.id());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't repair if we're still under attack, unless it's like a vital (civcentre or wall) building that's getting destroyed.
|
||||
for (var i in damagedBuildings) {
|
||||
var target = damagedBuildings[i];
|
||||
if (gameState.defcon() < 5) {
|
||||
if (target.healthLevel() > 0.5 || !target.hasClass("CivCentre") || !target.hasClass("StoneWall")) {
|
||||
continue;
|
||||
}
|
||||
} else if (noRepair && !target.hasClass("CivCentre"))
|
||||
continue;
|
||||
|
||||
var territory = m.createTerritoryMap(gameState);
|
||||
if (territory.getOwner(target.position()) !== PlayerID || territory.getOwner([target.position()[0] + 5, target.position()[1]]) !== PlayerID)
|
||||
continue;
|
||||
|
||||
var assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length;
|
||||
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(), targetNB/3 - assigned);
|
||||
|
||||
nearestNonBuilders.forEach(function(ent) {
|
||||
ent.stopMoving();
|
||||
addedWorkers++;
|
||||
ent.setMetadata(PlayerID, "subrole", "builder");
|
||||
ent.setMetadata(PlayerID, "target-foundation", target.id());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
m.BaseManager.prototype.update = function(gameState, queues, events)
|
||||
{
|
||||
Engine.ProfileStart("Base update - base " + this.ID);
|
||||
var self = this;
|
||||
|
||||
this.checkResourceLevels(gameState, queues);
|
||||
this.assignToFoundations(gameState);
|
||||
|
||||
if (this.constructing && this.anchor)
|
||||
{
|
||||
var owner = m.createTerritoryMap(gameState).getOwner(this.anchor.position());
|
||||
if(owner !== 0 && !gameState.isPlayerAlly(owner))
|
||||
{
|
||||
// we're in enemy territory. If we're too close from the enemy, destroy us.
|
||||
var eEnts = gameState.getEnemyStructures().filter(API3.Filters.byClass("CivCentre")).toEntityArray();
|
||||
for (var i in eEnts)
|
||||
{
|
||||
var entPos = eEnts[i].position();
|
||||
if (API3.SquareVectorDistance(entPos, this.anchor.position()) < 8000)
|
||||
this.anchor.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (gameState.ai.playedTurn % 2 === 0)
|
||||
this.setWorkersIdleByPriority(gameState);
|
||||
|
||||
this.assignRolelessUnits(gameState);
|
||||
|
||||
// should probably be last to avoid reallocations of units that would have done stuffs otherwise.
|
||||
this.reassignIdleWorkers(gameState);
|
||||
|
||||
// TODO: do this incrementally a la defense.js
|
||||
this.workers.forEach(function(ent) {
|
||||
if (!ent.getMetadata(PlayerID, "worker-object"))
|
||||
ent.setMetadata(PlayerID, "worker-object", new m.Worker(ent));
|
||||
ent.getMetadata(PlayerID, "worker-object").update(self, gameState);
|
||||
});
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
|
||||
return m;
|
||||
|
||||
}(PETRA);
|
124
binaries/data/mods/public/simulation/ai/petra/config.js
Normal file
124
binaries/data/mods/public/simulation/ai/petra/config.js
Normal file
@ -0,0 +1,124 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
// this defines the medium difficulty
|
||||
m.Config = function() {
|
||||
this.difficulty = 2; // 0 is sandbox, 1 is easy, 2 is medium, 3 is hard, 4 is very hard.
|
||||
this.debug = 0;
|
||||
|
||||
this.Military = {
|
||||
"towerLapseTime" : 90, // Time to wait between building 2 towers
|
||||
"fortressLapseTime" : 420, // Time to wait between building 2 fortresses
|
||||
"popForBarracks1" : 25,
|
||||
"popForBarracks2" : 95,
|
||||
"timeForBlacksmith" : 900,
|
||||
};
|
||||
this.Economy = {
|
||||
"villagePopCap" : 40, // How many units we want before aging to town.
|
||||
"cityPhase" : 840, // time to start trying to reach city phase
|
||||
"popForMarket" : 50,
|
||||
"dockStartTime" : 240, // Time to wait before building the dock
|
||||
"targetNumBuilders" : 1.5, // Base number of builders per foundation.
|
||||
"targetNumTraders" : 4, // Target number of traders
|
||||
"femaleRatio" : 0.5, // percent of females among the workforce.
|
||||
"initialFields" : 5
|
||||
};
|
||||
|
||||
// Note: attack settings are set directly in attack_plan.js
|
||||
// defense
|
||||
this.Defense =
|
||||
{
|
||||
"defenseRatio" : 2, // see defense.js for more info.
|
||||
"armyCompactSize" : 2000, // squared. Half-diameter of an army.
|
||||
"armyBreakawaySize" : 3500, // squared.
|
||||
"armyMergeSize" : 1400, // squared.
|
||||
"armyStrengthWariness" : 2, // Representation of how important army strength is for its "watch level".
|
||||
"prudence" : 1 // Representation of how quickly we'll forget about a dangerous army.
|
||||
};
|
||||
|
||||
// military
|
||||
this.buildings =
|
||||
{
|
||||
"base" : {
|
||||
"default" : [ "structures/{civ}_civil_centre" ],
|
||||
"ptol" : [ "structures/{civ}_military_colony" ],
|
||||
"sele" : [ "structures/{civ}_military_colony" ]
|
||||
},
|
||||
"advanced" : {
|
||||
"default" : [],
|
||||
"hele" : [ "structures/{civ}_gymnasion" ],
|
||||
"athen" : [ "structures/{civ}_gymnasion" ],
|
||||
"spart" : [ "structures/{civ}_syssiton" ],
|
||||
"cart" : [ "structures/{civ}_embassy_celtic",
|
||||
"structures/{civ}_embassy_iberian", "structures/{civ}_embassy_italiote" ],
|
||||
"celt" : [ "structures/{civ}_kennel" ],
|
||||
"pers" : [ "structures/{civ}_fortress", "structures/{civ}_stables", "structures/{civ}_apadana" ],
|
||||
"rome" : [ "structures/{civ}_army_camp" ],
|
||||
"mace" : [ "structures/{civ}_siege_workshop"],
|
||||
"maur" : [ "structures/{civ}_elephant_stables"]
|
||||
},
|
||||
"fort" : {
|
||||
"default" : [ "structures/{civ}_fortress" ],
|
||||
"celt" : [ "structures/{civ}_fortress_b", "structures/{civ}_fortress_g" ]
|
||||
}
|
||||
};
|
||||
|
||||
this.priorities =
|
||||
{
|
||||
"villager" : 30, // should be slightly lower than the citizen soldier one because otherwise they get all the food
|
||||
"citizenSoldier" : 60,
|
||||
"trader" : 50,
|
||||
"ships" : 70,
|
||||
"house" : 350,
|
||||
"dropsites" : 120,
|
||||
"field" : 500,
|
||||
"economicBuilding" : 90,
|
||||
"militaryBuilding" : 240, // set to something lower after the first barracks.
|
||||
"defenseBuilding" : 70,
|
||||
"civilCentre" : 950,
|
||||
"majorTech" : 700,
|
||||
"minorTech" : 40
|
||||
};
|
||||
|
||||
this.personality =
|
||||
{
|
||||
"aggressive": 0.5,
|
||||
"cooperative": 0.5
|
||||
};
|
||||
|
||||
this.resources = ["food", "wood", "stone", "metal"];
|
||||
};
|
||||
|
||||
//Config.prototype = new BaseConfig();
|
||||
|
||||
m.Config.prototype.updateDifficulty = function(difficulty)
|
||||
{
|
||||
this.difficulty = difficulty;
|
||||
// changing settings based on difficulty.
|
||||
this.targetNumTraders = 2 * this.difficulty;
|
||||
if (this.difficulty === 1)
|
||||
{
|
||||
this.Military.popForBarracks1 = 35;
|
||||
this.Military.popForBarracks2 = 150; // shouldn't reach it
|
||||
this.Military.popForBlacksmith = 150; // shouldn't reach it
|
||||
|
||||
this.Economy.cityPhase = 1800;
|
||||
this.Economy.popForMarket = 80;
|
||||
this.Economy.femaleRatio = 0.6;
|
||||
this.Economy.initialFields = 2;
|
||||
}
|
||||
else if (this.difficulty === 0)
|
||||
{
|
||||
this.Military.popForBarracks1 = 60;
|
||||
this.Military.popForBarracks2 = 150; // shouldn't reach it
|
||||
this.Military.popForBlacksmith = 150; // shouldn't reach it
|
||||
|
||||
this.Economy.cityPhase = 240000;
|
||||
this.Economy.popForMarket = 200;
|
||||
this.Economy.femaleRatio = 0.7;
|
||||
this.Economy.initialFields = 1;
|
||||
}
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
7
binaries/data/mods/public/simulation/ai/petra/data.json
Normal file
7
binaries/data/mods/public/simulation/ai/petra/data.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Petra Bot",
|
||||
"description": "Based on Aegis, but heavily modified and expected to be more robust",
|
||||
"moduleName" : "PETRA",
|
||||
"constructor": "PetraBot",
|
||||
"useShared": true
|
||||
}
|
180
binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
Normal file
180
binaries/data/mods/public/simulation/ai/petra/defenseArmy.js
Normal file
@ -0,0 +1,180 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
// Specialization of Armies used by the defense manager.
|
||||
m.DefenseArmy = function(gameState, defManager, ownEntities, foeEntities)
|
||||
{
|
||||
if (!m.Army.call(this, gameState, defManager, ownEntities, foeEntities))
|
||||
return false;
|
||||
|
||||
this.watchTSMultiplicator = this.Config.Defense.armyStrengthWariness;
|
||||
this.watchDecrement = this.Config.Defense.prudence;
|
||||
|
||||
this.foeSubStrength = {
|
||||
"spear" : ["Infantry", "Spear"], //also pikemen
|
||||
"sword" : ["Infantry", "Sword"],
|
||||
"ranged" : ["Infantry", "Ranged"],
|
||||
"meleeCav" : ["Cavalry", "Melee"],
|
||||
"rangedCav" : ["Cavalry", "Ranged"],
|
||||
"Elephant" : ["Elephant"],
|
||||
"meleeSiege" : ["Siege", "Melee"],
|
||||
"rangedSiege" : ["Siege", "Ranged"]
|
||||
};
|
||||
this.ownSubStrength = {
|
||||
"spear" : ["Infantry", "Spear"], //also pikemen
|
||||
"sword" : ["Infantry", "Sword"],
|
||||
"ranged" : ["Infantry", "Ranged"],
|
||||
"meleeCav" : ["Cavalry", "Melee"],
|
||||
"rangedCav" : ["Cavalry", "Ranged"],
|
||||
"Elephant" : ["Elephant"],
|
||||
"meleeSiege" : ["Siege", "Melee"],
|
||||
"rangedSiege" : ["Siege", "Ranged"]
|
||||
};
|
||||
|
||||
this.checkDangerosity(gameState); // might push us to 1.
|
||||
this.watchLevel = this.foeStrength * this.watchTSMultiplicator;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
m.DefenseArmy.prototype = Object.create(m.Army.prototype);
|
||||
|
||||
m.DefenseArmy.prototype.assignUnit = function (gameState, entID)
|
||||
{
|
||||
// we'll assume this defender is ours already.
|
||||
// we'll also override any previous assignment
|
||||
|
||||
var ent = gameState.getEntityById(entID);
|
||||
if (!ent || !ent.position())
|
||||
return false;
|
||||
|
||||
var idMin = undefined;
|
||||
var distMin = undefined;
|
||||
var idMinAll = undefined;
|
||||
var distMinAll = undefined;
|
||||
for each (var id in this.foeEntities)
|
||||
{
|
||||
var eEnt = gameState.getEntityById(id);
|
||||
if (!eEnt || !eEnt.position()) // probably can't happen.
|
||||
continue;
|
||||
|
||||
if (eEnt.unitAIOrderData().length && eEnt.unitAIOrderData()[0]["target"] &&
|
||||
eEnt.unitAIOrderData()[0]["target"] === entID)
|
||||
{ // being attacked >>> target the unit
|
||||
idMin = id;
|
||||
break;
|
||||
}
|
||||
|
||||
var dist = API3.SquareVectorDistance(ent.position(), eEnt.position());
|
||||
if (idMinAll === undefined || dist < distMin)
|
||||
{
|
||||
idMinAll = id;
|
||||
distMinAll = dist;
|
||||
}
|
||||
if (this.assignedAgainst[id].length > 2) // already enough units against it
|
||||
continue;
|
||||
var dist = API3.SquareVectorDistance(ent.position(), eEnt.position());
|
||||
if (idMin === undefined || dist < distMin)
|
||||
{
|
||||
idMin = id;
|
||||
distMin = dist;
|
||||
}
|
||||
}
|
||||
|
||||
if (idMin !== undefined)
|
||||
{
|
||||
this.assignedTo[entID] = idMin;
|
||||
this.assignedAgainst[idMin].push(entID);
|
||||
ent.attack(idMin);
|
||||
return true;
|
||||
}
|
||||
else if (idMinAll !== undefined)
|
||||
{
|
||||
this.assignedTo[entID] = idMinAll;
|
||||
this.assignedAgainst[idMinAll].push(entID);
|
||||
ent.attack(idMinAll);
|
||||
return true;
|
||||
}
|
||||
|
||||
this.recalculatePosition(gameState);
|
||||
ent.attackMove(this.foePosition[0], this.foePosition[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: this should return cleverer results ("needs anti-elephant"…)
|
||||
m.DefenseArmy.prototype.needsDefenders = function (gameState, events)
|
||||
{
|
||||
// some preliminary checks because we don't update for tech
|
||||
if (this.foeStrength < 0 || this.ownStrength < 0)
|
||||
this.recalculateStrengths(gameState);
|
||||
|
||||
if (this.foeStrength * this.defenseRatio <= this.ownStrength)
|
||||
return false;
|
||||
return this.foeStrength * this.defenseRatio - this.ownStrength;
|
||||
}
|
||||
|
||||
m.DefenseArmy.prototype.getState = function (gameState)
|
||||
{
|
||||
if (this.foeEntities.length === 0)
|
||||
return 0;
|
||||
if (this.state === 2)
|
||||
return 2;
|
||||
if (this.watchLevel > 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// check if we should remain at state 2 or drift away
|
||||
m.DefenseArmy.prototype.checkDangerosity = function (gameState)
|
||||
{
|
||||
this.territoryMap = m.createTerritoryMap(gameState);
|
||||
// right now we'll check if our position is "enemy" or not.
|
||||
if (this.territoryMap.getOwner(this.ownPosition) !== PlayerID)
|
||||
this.state = 1;
|
||||
else if (this.state === 1)
|
||||
this.state = 2;
|
||||
}
|
||||
|
||||
m.DefenseArmy.prototype.update = function (gameState)
|
||||
{
|
||||
var breakaways = this.onUpdate(gameState);
|
||||
|
||||
this.checkDangerosity(gameState);
|
||||
|
||||
var normalWatch = this.foeStrength * this.watchTSMultiplicator;
|
||||
if (this.state === 2)
|
||||
this.watchLevel = normalWatch;
|
||||
else if (this.watchLevel > normalWatch)
|
||||
this.watchLevel = normalWatch;
|
||||
else
|
||||
this.watchLevel -= this.watchDecrement;
|
||||
|
||||
// TODO: deal with watchLevel?
|
||||
|
||||
return breakaways;
|
||||
}
|
||||
|
||||
m.DefenseArmy.prototype.debug = function (gameState)
|
||||
{
|
||||
m.debug(" ");
|
||||
m.debug ("Army " + this.ID)
|
||||
// m.debug ("state " + this.state);
|
||||
// m.debug ("WatchLevel " + this.watchLevel);
|
||||
// m.debug ("Entities " + this.foeEntities.length);
|
||||
// m.debug ("Strength " + this.foeStrength);
|
||||
// debug (gameState.getEntityById(ent)._templateName + ", ID " + ent);
|
||||
//debug ("Defenders " + this.ownEntities.length);
|
||||
for each (ent in this.foeEntities)
|
||||
{
|
||||
if (gameState.getEntityById(ent) !== undefined)
|
||||
{
|
||||
m.debug (gameState.getEntityById(ent)._templateName + ", ID " + ent);
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent], "rgb": [0.5,0,0]});
|
||||
} else
|
||||
m.debug("ent " + ent);
|
||||
}
|
||||
m.debug ("");
|
||||
|
||||
}
|
||||
return m;
|
||||
}(PETRA);
|
445
binaries/data/mods/public/simulation/ai/petra/defenseManager.js
Normal file
445
binaries/data/mods/public/simulation/ai/petra/defenseManager.js
Normal file
@ -0,0 +1,445 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
m.DefenseManager = function(Config)
|
||||
{
|
||||
this.armies = []; // array of "army" Objects
|
||||
this.Config = Config;
|
||||
this.targetList = [];
|
||||
}
|
||||
|
||||
m.DefenseManager.prototype.init = function(gameState)
|
||||
{
|
||||
this.armyMergeSize = this.Config.Defense.armyMergeSize;
|
||||
};
|
||||
|
||||
m.DefenseManager.prototype.update = function(gameState, events)
|
||||
{
|
||||
this.territoryMap = m.createTerritoryMap(gameState);
|
||||
|
||||
this.checkEnemyArmies(gameState, events);
|
||||
this.checkEnemyUnits(gameState);
|
||||
this.assignDefenders(gameState);
|
||||
|
||||
this.MessageProcess(gameState,events);
|
||||
};
|
||||
|
||||
m.DefenseManager.prototype.makeIntoArmy = function(gameState, entityID)
|
||||
{
|
||||
// Try to add it to an existing army.
|
||||
for (var o in this.armies)
|
||||
if (this.armies[o].addFoe(gameState,entityID))
|
||||
return; // over
|
||||
|
||||
// Create a new army for it.
|
||||
var army = new m.DefenseArmy(gameState, this, [], [entityID]);
|
||||
this.armies.push(army);
|
||||
};
|
||||
|
||||
// TODO: this algorithm needs to be improved, sorta.
|
||||
m.DefenseManager.prototype.isDangerous = function(gameState, entity)
|
||||
{
|
||||
if (!entity.position())
|
||||
return false;
|
||||
|
||||
if (this.territoryMap.getOwner(entity.position()) === entity.owner())
|
||||
return false;
|
||||
|
||||
// check if the entity is trying to build a new base near our buildings, and if yes, add this base in our target list
|
||||
if (entity.unitAIState() == "INDIVIDUAL.REPAIR.REPAIRING")
|
||||
{
|
||||
var targetId = entity.unitAIOrderData()[0]["target"];
|
||||
if (this.targetList.indexOf(targetId) !== -1)
|
||||
return true;
|
||||
var target = gameState.getEntityById(targetId);
|
||||
if (target && target.hasClass("CivCentre"))
|
||||
{
|
||||
var myBuildings = gameState.getOwnStructures();
|
||||
for (var i in myBuildings._entities)
|
||||
{
|
||||
if (API3.SquareVectorDistance(myBuildings._entities[i].position(), entity.position()) > 30000)
|
||||
continue;
|
||||
this.targetList.push(targetId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.attackTypes() === undefined || entity.hasClass("Support"))
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < this.targetList.length; ++i)
|
||||
{
|
||||
var target = gameState.getEntityById(this.targetList[i]);
|
||||
if (!target || !target.position()) // the enemy base is either destroyed or built
|
||||
this.targetList.splice(i--, 1);
|
||||
else if (API3.SquareVectorDistance(target.position(), entity.position()) < 6000)
|
||||
return true;
|
||||
}
|
||||
|
||||
var myCCFoundations = gameState.getOwnFoundations().filter(API3.Filters.byClass("CivCentre"));
|
||||
for (var i in myCCFoundations._entities)
|
||||
{
|
||||
if (!myCCFoundations._entities[i].getBuildersNb())
|
||||
continue;
|
||||
if (API3.SquareVectorDistance(myCCFoundations._entities[i].position(), entity.position()) < 6000)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.Config.personality.cooperative > 0.3)
|
||||
{
|
||||
var allyCC = gameState.getAllyEntities().filter(API3.Filters.byClass("CivCentre"));
|
||||
for (var i in allyCC._entities)
|
||||
{
|
||||
if (this.Config.personality.cooperative < 0.6 && allyCC._entities[i].foundationProgress() !== undefined)
|
||||
continue;
|
||||
if (API3.SquareVectorDistance(allyCC._entities[i].position(), entity.position()) < 6000)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var myBuildings = gameState.getOwnStructures();
|
||||
for (var i in myBuildings._entities)
|
||||
if (API3.SquareVectorDistance(myBuildings._entities[i].position(), entity.position()) < 6000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
m.DefenseManager.prototype.checkEnemyUnits = function(gameState)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
// loop through enemy units
|
||||
var nbPlayers = gameState.sharedScript.playersData.length - 1;
|
||||
var i = 1 + gameState.ai.playedTurn % nbPlayers;
|
||||
if (i === PlayerID || gameState.isPlayerAlly(i))
|
||||
return;
|
||||
|
||||
var filter = API3.Filters.and(API3.Filters.byClass("Unit"), API3.Filters.byOwner(i));
|
||||
var enemyUnits = gameState.updatingGlobalCollection("player-" +i + "-units", filter);
|
||||
|
||||
enemyUnits.forEach( function (ent) {
|
||||
// first check: is this unit already part of an army.
|
||||
if (ent.getMetadata(PlayerID, "PartOfArmy") !== undefined)
|
||||
return;
|
||||
|
||||
// TODO what to do for ships ?
|
||||
if (ent.hasClass("Ship") || ent.hasClass("Trader"))
|
||||
return;
|
||||
|
||||
// check if unit is dangerous "a priori"
|
||||
if (self.isDangerous(gameState, ent))
|
||||
self.makeIntoArmy(gameState, ent.id());
|
||||
});
|
||||
};
|
||||
|
||||
m.DefenseManager.prototype.checkEnemyArmies = function(gameState, events)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
for (var o = 0; o < this.armies.length; ++o)
|
||||
{
|
||||
var army = this.armies[o];
|
||||
army.checkEvents(gameState, events); // must be called every turn for all armies
|
||||
|
||||
// this returns a list of IDs: the units that broke away from the army for being too far.
|
||||
var breakaways = army.update(gameState);
|
||||
|
||||
for (var u in breakaways)
|
||||
{
|
||||
// assume dangerosity
|
||||
this.makeIntoArmy(gameState,breakaways[u]);
|
||||
}
|
||||
|
||||
if (army.getState(gameState) === 0)
|
||||
{
|
||||
army.clear(gameState);
|
||||
this.armies.splice(o--,1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Check if we can't merge it with another.
|
||||
for (var o = 0; o < this.armies.length; ++o)
|
||||
{
|
||||
var army = this.armies[o];
|
||||
for (var p = o+1; p < this.armies.length; ++p)
|
||||
{
|
||||
var otherArmy = this.armies[p];
|
||||
if (otherArmy.state !== army.state)
|
||||
continue;
|
||||
|
||||
if (API3.SquareVectorDistance(army.foePosition, otherArmy.foePosition) < this.armyMergeSize)
|
||||
{
|
||||
// no need to clear here.
|
||||
army.merge(gameState, otherArmy);
|
||||
this.armies.splice(p--,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gameState.ai.playedTurn % 5 !== 0)
|
||||
return;
|
||||
// Check if any army is no more dangerous (possibly because it has defeated us and destroyed our base)
|
||||
for (var o = 0; o < this.armies.length; ++o)
|
||||
{
|
||||
var army = this.armies[o];
|
||||
army.recalculatePosition(gameState);
|
||||
var owner = this.territoryMap.getOwner(army.foePosition);
|
||||
if (gameState.isPlayerAlly(owner))
|
||||
continue;
|
||||
else if (owner !== 0) // enemy army back in its territory
|
||||
{
|
||||
army.clear(gameState);
|
||||
this.armies.splice(o--,1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// army in neutral territory // TODO check smaller distance with all our buildings instead of only ccs with big distance
|
||||
var stillDangerous = false;
|
||||
var bases = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).toEntityArray();
|
||||
if (this.Config.personality.cooperative > 0.3)
|
||||
{
|
||||
var allyCC = gameState.getAllyEntities().filter(API3.Filters.byClass("CivCentre")).toEntityArray();
|
||||
bases = bases.concat(allyCC);
|
||||
}
|
||||
for (var i in bases)
|
||||
{
|
||||
if (API3.SquareVectorDistance(bases[i].position(), army.foePosition) < 40000)
|
||||
{
|
||||
if(this.Config.debug > 0)
|
||||
warn("but still near one of our CC");
|
||||
stillDangerous = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stillDangerous)
|
||||
continue;
|
||||
|
||||
army.clear(gameState);
|
||||
this.armies.splice(o--,1);
|
||||
}
|
||||
};
|
||||
|
||||
m.DefenseManager.prototype.assignDefenders = function(gameState, events)
|
||||
{
|
||||
if (this.armies.length === 0)
|
||||
return;
|
||||
|
||||
var armiesNeeding = [];
|
||||
// Okay, let's add defenders
|
||||
// TODO: this is dumb.
|
||||
for (var i in this.armies)
|
||||
{
|
||||
var army = this.armies[i];
|
||||
var needsDef = army.needsDefenders(gameState);
|
||||
if (needsDef === false)
|
||||
continue;
|
||||
|
||||
// Okay for now needsDef is the total needed strength.
|
||||
// we're dumb so we don't choose if we have a defender shortage.
|
||||
armiesNeeding.push( {"army": army, "need": needsDef} );
|
||||
}
|
||||
|
||||
if (armiesNeeding.length === 0)
|
||||
return;
|
||||
|
||||
// let's get our potential units
|
||||
var potentialDefenders = [];
|
||||
gameState.getOwnUnits().forEach(function(ent) {
|
||||
if (ent.getMetadata(PlayerID, "PartOfArmy") !== undefined)
|
||||
return;
|
||||
if (ent.getMetadata(PlayerID, "plan") !== undefined)
|
||||
{
|
||||
var subrole = ent.getMetadata(PlayerID, "subrole");
|
||||
if (subrole && (subrole === "completing" || subrole === "walking" || subrole === "attacking"))
|
||||
return;
|
||||
if (ent.hasClass("Siege"))
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ent.hasClass("Infantry") && !ent.hasClass("Cavalry"))
|
||||
return;
|
||||
}
|
||||
potentialDefenders.push(ent.id());
|
||||
});
|
||||
|
||||
for (var a = 0; a < armiesNeeding.length; ++a)
|
||||
armiesNeeding[a]["army"].recalculatePosition(gameState);
|
||||
|
||||
for (var i = 0; i < potentialDefenders.length; ++i)
|
||||
{
|
||||
var ent = gameState.getEntityById(potentialDefenders[i]);
|
||||
if (!ent.position())
|
||||
continue;
|
||||
var aMin = undefined;
|
||||
var distMin = undefined;
|
||||
for (var a = 0; a < armiesNeeding.length; ++a)
|
||||
{
|
||||
var dist = API3.SquareVectorDistance(ent.position(), armiesNeeding[a]["army"].foePosition);
|
||||
if (aMin !== undefined && dist > distMin)
|
||||
continue;
|
||||
aMin = a;
|
||||
distMin = dist;
|
||||
}
|
||||
if (aMin === undefined)
|
||||
{
|
||||
for (var a = 0; a < armiesNeeding.length; ++a)
|
||||
warn(" defense/armiesNeeding " + uneval(armiesNeeding[a]["need"]));
|
||||
}
|
||||
|
||||
var str = m.getMaxStrength(ent);
|
||||
armiesNeeding[aMin]["need"] -= str;
|
||||
armiesNeeding[aMin]["army"].addOwn(gameState, potentialDefenders[i]);
|
||||
armiesNeeding[aMin]["army"].assignUnit(gameState, potentialDefenders[i]);
|
||||
|
||||
if (armiesNeeding[aMin]["need"] <= 0)
|
||||
armiesNeeding.splice(aMin, 1);
|
||||
if (armiesNeeding.length === 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// If shortage of defenders: increase the priority of soldiers queues
|
||||
if (armiesNeeding.length !== 0)
|
||||
gameState.ai.HQ.boostSoldiers(gameState, 10000, true);
|
||||
else
|
||||
gameState.ai.HQ.unboostSoldiers(gameState);
|
||||
};
|
||||
|
||||
// this processes the attackmessages
|
||||
// So that a unit that gets attacked will not be completely dumb.
|
||||
// warning: big levels of indentation coming.
|
||||
m.DefenseManager.prototype.MessageProcess = function(gameState,events) {
|
||||
/* var self = this;
|
||||
var attackedEvents = events["Attacked"];
|
||||
for (var key in attackedEvents){
|
||||
var e = attackedEvents[key];
|
||||
if (gameState.isEntityOwn(gameState.getEntityById(e.target))) {
|
||||
var attacker = gameState.getEntityById(e.attacker);
|
||||
var ourUnit = gameState.getEntityById(e.target);
|
||||
|
||||
// the attacker must not be already dead, and it must not be me (think catapults that miss).
|
||||
if (attacker === undefined || attacker.owner() === PlayerID || attacker.position() === undefined)
|
||||
continue;
|
||||
|
||||
var mapPos = this.dangerMap.gamePosToMapPos(attacker.position());
|
||||
this.dangerMap.addInfluence(mapPos[0], mapPos[1], 4, 1, 'constant');
|
||||
|
||||
// disregard units from attack plans and defense.
|
||||
if (ourUnit.getMetadata(PlayerID, "role") == "defense" || ourUnit.getMetadata(PlayerID, "role") == "attack")
|
||||
continue;
|
||||
|
||||
var territory = this.territoryMap.getOwner(attacker.position());
|
||||
|
||||
if (attacker.owner() == 0)
|
||||
{
|
||||
if (ourUnit !== undefined && ourUnit.hasClass("Unit") && !ourUnit.hasClass("Support"))
|
||||
ourUnit.attack(e.attacker);
|
||||
else
|
||||
{
|
||||
ourUnit.flee(attacker);
|
||||
ourUnit.setMetadata(PlayerID,"fleeing", gameState.getTimeElapsed());
|
||||
}
|
||||
if (territory === PlayerID)
|
||||
{
|
||||
// anyway we'll register the animal as dangerous, and attack it (note: only on our territory. Don't care otherwise).
|
||||
this.listOfWantedUnits[attacker.id()] = new EntityCollection(gameState.sharedScript);
|
||||
this.listOfWantedUnits[attacker.id()].addEnt(attacker);
|
||||
this.listOfWantedUnits[attacker.id()].freeze();
|
||||
this.listOfWantedUnits[attacker.id()].registerUpdates();
|
||||
|
||||
var filter = Filters.byTargetedEntity(attacker.id());
|
||||
this.WantedUnitsAttacker[attacker.id()] = this.myUnits.filter(filter);
|
||||
this.WantedUnitsAttacker[attacker.id()].registerUpdates();
|
||||
}
|
||||
} // Disregard military units except in our territory. Disregard all calls in enemy territory.
|
||||
else if (territory == PlayerID || (territory != attacker.owner() && ourUnit.hasClass("Support")))
|
||||
{
|
||||
// TODO: this does not differentiate with buildings...
|
||||
// These ought to be treated differently.
|
||||
// units in attack plans will react independently, but we still list the attacks here.
|
||||
if (attacker.hasClass("Structure")) {
|
||||
// todo: we ultimately have to check wether it's a danger point or an isolated area, and if it's a danger point, mark it as so.
|
||||
|
||||
// Right now, to make the AI less gameable, we'll mark any surrounding resource as inaccessible.
|
||||
// usual tower range is 80. Be on the safe side.
|
||||
var close = gameState.getResourceSupplies("wood").filter(Filters.byDistance(attacker.position(), 90));
|
||||
close.forEach(function (supply) { //}){
|
||||
supply.setMetadata(PlayerID, "inaccessible", true);
|
||||
});
|
||||
} else {
|
||||
// TODO: right now a soldier always retaliate... Perhaps it should be set in "Defense" mode.
|
||||
|
||||
// TODO: handle the ship case
|
||||
if (attacker.hasClass("Ship"))
|
||||
continue;
|
||||
|
||||
// This unit is dangerous. if it's in an army, it's being dealt with.
|
||||
// if it's not in an army, it means it's either a lone raider, or it has got friends.
|
||||
// In which case we must check for other dangerous units around, and perhaps armify them.
|
||||
// TODO: perhaps someday army detection will have improved and this will require change.
|
||||
var armyID = attacker.getMetadata(PlayerID, "inArmy");
|
||||
if (armyID == undefined || !this.enemyArmy[attacker.owner()] || !this.enemyArmy[attacker.owner()][armyID]) {
|
||||
if (this.reevaluateEntity(gameState, attacker))
|
||||
{
|
||||
var position = attacker.position();
|
||||
var close = HQ.enemyWatchers[attacker.owner()].enemySoldiers.filter(Filters.byDistance(position, self.armyCompactSize));
|
||||
|
||||
if (close.length > 2 || ourUnit.hasClass("Support") || attacker.hasClass("Siege"))
|
||||
{
|
||||
// armify it, then armify units close to him.
|
||||
this.armify(gameState,attacker);
|
||||
armyID = attacker.getMetadata(PlayerID, "inArmy");
|
||||
|
||||
close.forEach(function (ent) { //}){
|
||||
if (API3.SquareVectorDistance(position, ent.position()) < self.armyCompactSize)
|
||||
{
|
||||
ent.setMetadata(PlayerID, "inArmy", armyID);
|
||||
self.enemyArmy[ent.owner()][armyID].addEnt(ent);
|
||||
}
|
||||
});
|
||||
return; // don't use too much processing power. If there are other cases, they'll be processed soon enough.
|
||||
}
|
||||
}
|
||||
// Defensemanager will deal with them in the next turn.
|
||||
}
|
||||
if (ourUnit !== undefined && ourUnit.hasClass("Unit")) {
|
||||
if (ourUnit.hasClass("Support")) {
|
||||
// let's try to garrison this support unit.
|
||||
if (ourUnit.position())
|
||||
{
|
||||
var buildings = gameState.getOwnEntities().filter(Filters.byCanGarrison()).filterNearest(ourUnit.position(),4).toEntityArray();
|
||||
var garrisoned = false;
|
||||
for (var i in buildings)
|
||||
{
|
||||
var struct = buildings[i];
|
||||
if (struct.garrisoned() && struct.garrisonMax() - struct.garrisoned().length > 0)
|
||||
{
|
||||
garrisoned = true;
|
||||
ourUnit.garrison(struct);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!garrisoned) {
|
||||
ourUnit.flee(attacker);
|
||||
ourUnit.setMetadata(PlayerID,"fleeing", gameState.getTimeElapsed());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// It's a soldier. Right now we'll retaliate
|
||||
// TODO: check for stronger units against this type, check for fleeing options, etc.
|
||||
// Check also for neighboring towers and garrison there perhaps?
|
||||
ourUnit.attack(e.attacker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}; // nice sets of closing brackets, isn't it?
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
@ -0,0 +1,69 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
// returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too.
|
||||
m.getMaxStrength = function(ent, againstClass)
|
||||
{
|
||||
var strength = 0.0;
|
||||
var attackTypes = ent.attackTypes();
|
||||
var armourStrength = ent.armourStrengths();
|
||||
var hp = ent.maxHitpoints() / 100.0; // some normalization
|
||||
for (var typeKey in attackTypes) {
|
||||
var type = attackTypes[typeKey];
|
||||
|
||||
if (type == "Slaughter" || type == "Charged")
|
||||
continue;
|
||||
|
||||
var attackStrength = ent.attackStrengths(type);
|
||||
var attackRange = ent.attackRange(type);
|
||||
var attackTimes = ent.attackTimes(type);
|
||||
for (var str in attackStrength) {
|
||||
var val = parseFloat(attackStrength[str]);
|
||||
if (againstClass)
|
||||
val *= ent.getMultiplierAgainst(type, againstClass);
|
||||
switch (str) {
|
||||
case "crush":
|
||||
strength += (val * 0.085) / 3;
|
||||
break;
|
||||
case "hack":
|
||||
strength += (val * 0.075) / 3;
|
||||
break;
|
||||
case "pierce":
|
||||
strength += (val * 0.065) / 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attackRange){
|
||||
strength += (attackRange.max * 0.0125) ;
|
||||
}
|
||||
for (var str in attackTimes) {
|
||||
var val = parseFloat(attackTimes[str]);
|
||||
switch (str){
|
||||
case "repeat":
|
||||
strength += (val / 100000);
|
||||
break;
|
||||
case "prepare":
|
||||
strength -= (val / 100000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var str in armourStrength) {
|
||||
var val = parseFloat(armourStrength[str]);
|
||||
switch (str) {
|
||||
case "crush":
|
||||
strength += (val * 0.085) / 3;
|
||||
break;
|
||||
case "hack":
|
||||
strength += (val * 0.075) / 3;
|
||||
break;
|
||||
case "pierce":
|
||||
strength += (val * 0.065) / 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return strength * hp;
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
@ -0,0 +1,16 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
m.EntityCollectionFromIds = function(gameState, idList){
|
||||
var ents = {};
|
||||
for (var i in idList){
|
||||
var id = idList[i];
|
||||
if (gameState.entities._entities[id]) {
|
||||
ents[id] = gameState.entities._entities[id];
|
||||
}
|
||||
}
|
||||
return new API3.EntityCollection(gameState.sharedScript, ents);
|
||||
}
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
@ -0,0 +1,70 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
// Some functions that could be part of the gamestate but are Aegis specific.
|
||||
|
||||
// The next three are to register that we assigned a gatherer to a resource this turn.
|
||||
// expects an entity
|
||||
m.IsSupplyFull = function(gamestate, supply)
|
||||
{
|
||||
if (supply.isFull() === true)
|
||||
return true;
|
||||
var count = supply.resourceSupplyGatherers().length;
|
||||
if (gamestate.turnCache["ressourceGatherer"] && gamestate.turnCache["ressourceGatherer"][supply.id()])
|
||||
count += gamestate.turnCache["ressourceGatherer"][supply.id()];
|
||||
if (count >= supply.maxGatherers())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// add a gatherer to the turn cache for this supply.
|
||||
m.AddTCGatherer = function(gamestate, supplyID)
|
||||
{
|
||||
if (gamestate.turnCache["ressourceGatherer"] && gamestate.turnCache["ressourceGatherer"][supplyID] !== undefined)
|
||||
++gamestate.turnCache["ressourceGatherer"][supplyID];
|
||||
else
|
||||
{
|
||||
if (!gamestate.turnCache["ressourceGatherer"])
|
||||
gamestate.turnCache["ressourceGatherer"] = {};
|
||||
gamestate.turnCache["ressourceGatherer"][supplyID] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// remove a gatherer to the turn cache for this supply.
|
||||
m.RemoveTCGatherer = function(gamestate, supplyID)
|
||||
{
|
||||
if (gamestate.turnCache["ressourceGatherer"] && gamestate.turnCache["ressourceGatherer"][supplyID])
|
||||
--gamestate.turnCache["ressourceGatherer"][supplyID];
|
||||
else
|
||||
if (!gamestate.turnCache["ressourceGatherer"])
|
||||
gamestate.turnCache["ressourceGatherer"] = {};
|
||||
gamestate.turnCache["ressourceGatherer"][supplyID] = -1;
|
||||
}
|
||||
|
||||
m.GetTCGatherer = function(gamestate, supplyID)
|
||||
{
|
||||
if (gamestate.turnCache["ressourceGatherer"] && gamestate.turnCache["ressourceGatherer"][supplyID])
|
||||
return gamestate.turnCache["ressourceGatherer"][supplyID];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The next two are to register that we assigned a gatherer to a resource this turn.
|
||||
m.AddTCRessGatherer = function(gamestate, resource)
|
||||
{
|
||||
if (gamestate.turnCache["ressourceGatherer-" + resource])
|
||||
++gamestate.turnCache["ressourceGatherer-" + resource];
|
||||
else
|
||||
gamestate.turnCache["ressourceGatherer-" + resource] = 1;
|
||||
}
|
||||
|
||||
m.GetTCRessGatherer = function(gamestate, resource)
|
||||
{
|
||||
if (gamestate.turnCache["ressourceGatherer-" + resource])
|
||||
return gamestate.turnCache["ressourceGatherer-" + resource];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
1615
binaries/data/mods/public/simulation/ai/petra/headquarters.js
Normal file
1615
binaries/data/mods/public/simulation/ai/petra/headquarters.js
Normal file
File diff suppressed because it is too large
Load Diff
237
binaries/data/mods/public/simulation/ai/petra/map-module.js
Normal file
237
binaries/data/mods/public/simulation/ai/petra/map-module.js
Normal file
@ -0,0 +1,237 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
// other map functions
|
||||
m.TERRITORY_PLAYER_MASK = 0x3F;
|
||||
|
||||
m.createObstructionMap = function(gameState, accessIndex, template){
|
||||
var passabilityMap = gameState.getMap();
|
||||
var territoryMap = gameState.ai.territoryMap;
|
||||
|
||||
// default values
|
||||
var placementType = "land";
|
||||
var buildOwn = true;
|
||||
var buildAlly = true;
|
||||
var buildNeutral = true;
|
||||
var buildEnemy = false;
|
||||
// If there is a template then replace the defaults
|
||||
if (template)
|
||||
{
|
||||
placementType = template.buildPlacementType();
|
||||
buildOwn = template.hasBuildTerritory("own");
|
||||
buildAlly = template.hasBuildTerritory("ally");
|
||||
buildNeutral = template.hasBuildTerritory("neutral");
|
||||
buildEnemy = template.hasBuildTerritory("enemy");
|
||||
}
|
||||
|
||||
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction") | gameState.getPassabilityClassMask("building-land");
|
||||
|
||||
if (placementType == "shore")
|
||||
{
|
||||
// TODO: this won't change much, should be cached, it's slow.
|
||||
var obstructionTiles = new Uint8Array(passabilityMap.data.length);
|
||||
var okay = false;
|
||||
for (var x = 0; x < passabilityMap.width; ++x)
|
||||
{
|
||||
for (var y = 0; y < passabilityMap.height; ++y)
|
||||
{
|
||||
var i = x + y*passabilityMap.width;
|
||||
var tilePlayer = (territoryMap.data[i] & m.TERRITORY_PLAYER_MASK);
|
||||
|
||||
if (gameState.ai.myIndex !== gameState.ai.accessibility.landPassMap[i])
|
||||
{
|
||||
obstructionTiles[i] = 0;
|
||||
continue;
|
||||
}
|
||||
if (gameState.isPlayerEnemy(tilePlayer) && tilePlayer !== 0)
|
||||
{
|
||||
obstructionTiles[i] = 0;
|
||||
continue;
|
||||
}
|
||||
if ((passabilityMap.data[i] & (gameState.getPassabilityClassMask("building-shore") | gameState.getPassabilityClassMask("default"))))
|
||||
{
|
||||
obstructionTiles[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
okay = false;
|
||||
var positions = [[0,1], [1,1], [1,0], [1,-1], [0,-1], [-1,-1], [-1,0], [-1,1]];
|
||||
var available = 0;
|
||||
for each (var stuff in positions)
|
||||
{
|
||||
var index = x + stuff[0] + (y+stuff[1])*passabilityMap.width;
|
||||
var index2 = x + stuff[0]*2 + (y+stuff[1]*2)*passabilityMap.width;
|
||||
var index3 = x + stuff[0]*3 + (y+stuff[1]*3)*passabilityMap.width;
|
||||
var index4 = x + stuff[0]*4 + (y+stuff[1]*4)*passabilityMap.width;
|
||||
|
||||
if ((passabilityMap.data[index] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index,true) > 500)
|
||||
if ((passabilityMap.data[index2] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index2,true) > 500)
|
||||
if ((passabilityMap.data[index3] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index3,true) > 500)
|
||||
if ((passabilityMap.data[index4] & gameState.getPassabilityClassMask("default")) && gameState.ai.accessibility.getRegionSizei(index4,true) > 500)
|
||||
{
|
||||
if (available < 2)
|
||||
available++;
|
||||
else
|
||||
okay = true;
|
||||
}
|
||||
}
|
||||
// checking for accessibility: if a neighbor is inaccessible, this is too. If it's not on the same "accessible map" as us, we crash-i~u.
|
||||
var radius = 3;
|
||||
for (var xx = -radius;xx <= radius; xx++)
|
||||
for (var yy = -radius;yy <= radius; yy++)
|
||||
{
|
||||
var id = x + xx + (y+yy)*passabilityMap.width;
|
||||
if (id > 0 && id < passabilityMap.data.length)
|
||||
if (gameState.ai.terrainAnalyzer.map[id] === 0 || gameState.ai.terrainAnalyzer.map[id] == 30 || gameState.ai.terrainAnalyzer.map[id] == 40)
|
||||
okay = false;
|
||||
}
|
||||
obstructionTiles[i] = okay ? 255 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var playerID = PlayerID;
|
||||
|
||||
var obstructionTiles = new Uint8Array(passabilityMap.data.length);
|
||||
for (var i = 0; i < passabilityMap.data.length; ++i)
|
||||
{
|
||||
var tilePlayer = (territoryMap.data[i] & m.TERRITORY_PLAYER_MASK);
|
||||
var invalidTerritory = (
|
||||
(!buildOwn && tilePlayer == playerID) ||
|
||||
(!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) ||
|
||||
(!buildNeutral && tilePlayer == 0) ||
|
||||
(!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer != 0)
|
||||
);
|
||||
if (accessIndex)
|
||||
var tileAccessible = (accessIndex === gameState.ai.accessibility.landPassMap[i]);
|
||||
else
|
||||
var tileAccessible = true;
|
||||
if (placementType === "shore")
|
||||
tileAccessible = true;
|
||||
obstructionTiles[i] = (!tileAccessible || invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 255;
|
||||
}
|
||||
}
|
||||
|
||||
var map = new API3.Map(gameState.sharedScript, obstructionTiles);
|
||||
map.setMaxVal(255);
|
||||
|
||||
if (template && template.buildDistance()) {
|
||||
var minDist = template.buildDistance().MinDistance;
|
||||
var category = template.buildDistance().FromCategory;
|
||||
if (minDist !== undefined && category !== undefined){
|
||||
gameState.getOwnStructures().forEach(function(ent) {
|
||||
if (ent.buildCategory() === category && ent.position()){
|
||||
var pos = ent.position();
|
||||
var x = Math.round(pos[0] / gameState.cellSize);
|
||||
var z = Math.round(pos[1] / gameState.cellSize);
|
||||
map.addInfluence(x, z, minDist/gameState.cellSize, -255, 'constant');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
m.createTerritoryMap = function(gameState) {
|
||||
var map = gameState.ai.territoryMap;
|
||||
|
||||
var ret = new API3.Map(gameState.sharedScript, map.data);
|
||||
ret.getOwner = function(p) { return this.point(p) & m.TERRITORY_PLAYER_MASK; };
|
||||
ret.getOwnerIndex = function(p) { return this.map[p] & m.TERRITORY_PLAYER_MASK; };
|
||||
return ret;
|
||||
};
|
||||
|
||||
m.createFrontierMap = function(gameState, borderMap)
|
||||
{
|
||||
var territory = m.createTerritoryMap(gameState);
|
||||
var around = [ [-0.7,0.7], [0,1], [0.7,0.7], [1,0], [0.7,-0.7], [0,-1], [-0.7,-0.7], [-1,0] ];
|
||||
|
||||
var map = new API3.Map(gameState.sharedScript);
|
||||
var width = map.width;
|
||||
var insideSmall = 10;
|
||||
var insideLarge = 15;
|
||||
|
||||
for (var j = 0; j < territory.length; ++j)
|
||||
{
|
||||
if (territory.getOwnerIndex(j) !== PlayerID || borderMap.map[j] === 2)
|
||||
continue;
|
||||
var ix = j%width;
|
||||
var iz = Math.floor(j/width);
|
||||
for each (var a in around)
|
||||
{
|
||||
var jx = ix + Math.round(insideSmall*a[0]);
|
||||
if (jx < 0 || jx >= width)
|
||||
continue;
|
||||
var jz = iz + Math.round(insideSmall*a[1]);
|
||||
if (jz < 0 || jz >= width)
|
||||
continue;
|
||||
if (borderMap && borderMap.map[jx+width*jz] > 1)
|
||||
continue;
|
||||
if (!gameState.isPlayerAlly(territory.getOwnerIndex(jx+width*jz)))
|
||||
{
|
||||
map.map[j] = 2;
|
||||
break;
|
||||
}
|
||||
jx = ix + Math.round(insideLarge*a[0]);
|
||||
if (jx < 0 || jx >= width)
|
||||
continue;
|
||||
jz = iz + Math.round(insideLarge*a[1]);
|
||||
if (jz < 0 || jz >= width)
|
||||
continue;
|
||||
if (borderMap && borderMap.map[jx+width*jz] > 1)
|
||||
continue;
|
||||
if (!gameState.isPlayerAlly(territory.getOwnerIndex(jx+width*jz)))
|
||||
map.map[j] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// m.debugMap(gameState, map);
|
||||
return map;
|
||||
};
|
||||
|
||||
// TODO foresee the case of square maps
|
||||
m.createBorderMap = function(gameState)
|
||||
{
|
||||
var map = new API3.Map(gameState.sharedScript);
|
||||
var width = map.width;
|
||||
var ic = (width - 1) / 2;
|
||||
var radmax = (ic-2)*(ic-2); // we assume two inaccessible cells all around
|
||||
for (var j = 0; j < map.length; ++j)
|
||||
{
|
||||
var dx = j%width - ic;
|
||||
var dy = Math.floor(j/width) - ic;
|
||||
var radius = dx*dx + dy*dy;
|
||||
if (radius > radmax)
|
||||
map.map[j] = 2;
|
||||
else if (radius > (ic - 18)*(ic - 18))
|
||||
map.map[j] = 1;
|
||||
}
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
m.debugMap = function(gameState, map)
|
||||
{
|
||||
var width = map.width;
|
||||
var cell = map.cellSize;
|
||||
gameState.getEntities().forEach( function (ent) {
|
||||
var pos = ent.position();
|
||||
if (!pos)
|
||||
return;
|
||||
var x = Math.round(pos[0] / cell);
|
||||
var z = Math.round(pos[1] / cell);
|
||||
var id = x + width*z;
|
||||
if (map.map[id] == 1)
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [2,0,0]});
|
||||
else if (map.map[id] == 2)
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,2,0]});
|
||||
else if (map.map[id] == 3)
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [0,0,2]});
|
||||
});
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
290
binaries/data/mods/public/simulation/ai/petra/navalManager.js
Normal file
290
binaries/data/mods/public/simulation/ai/petra/navalManager.js
Normal file
@ -0,0 +1,290 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
/* Naval Manager
|
||||
Will deal with anything ships.
|
||||
-Basically trade over water (with fleets and goals commissioned by the economy manager)
|
||||
-Defense over water (commissioned by the defense manager)
|
||||
-subtask being patrols, escort, naval superiority.
|
||||
-Transport of units over water (a few units).
|
||||
-Scouting, ultimately.
|
||||
Also deals with handling docks, making sure we have access and stuffs like that.
|
||||
Does not build them though, that's for the base manager to handle.
|
||||
*/
|
||||
|
||||
m.NavalManager = function() {
|
||||
// accessibility zones for which we have a dock.
|
||||
// Connexion is described as [landindex] = [seaIndexes];
|
||||
// technically they also exist for sea zones but I don't care.
|
||||
this.landZoneDocked = [];
|
||||
|
||||
// list of seas I have a dock on.
|
||||
this.accessibleSeas = [];
|
||||
|
||||
// ship subCollections. Also exist for land zones, idem, not caring.
|
||||
this.seaShips = [];
|
||||
this.seaTpShips = [];
|
||||
this.seaWarships = [];
|
||||
|
||||
// wanted NB per zone.
|
||||
this.wantedTpShips = [];
|
||||
this.wantedWarships = [];
|
||||
|
||||
this.transportPlans = [];
|
||||
this.askedPlans = [];
|
||||
};
|
||||
|
||||
// More initialisation for stuff that needs the gameState
|
||||
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();
|
||||
this.docks.registerUpdates();
|
||||
|
||||
this.ships = gameState.getOwnEntities().filter(API3.Filters.byClass("Ship"));
|
||||
// note: those two can overlap (some transport ships are warships too and vice-versa).
|
||||
this.tpShips = this.ships.filter(API3.Filters.byCanGarrison());
|
||||
this.warships = this.ships.filter(API3.Filters.byClass("Warship"));
|
||||
|
||||
this.ships.registerUpdates();
|
||||
this.tpShips.registerUpdates();
|
||||
this.warships.registerUpdates();
|
||||
|
||||
for (var i = 0; i < gameState.ai.accessibility.regionSize.length; ++i)
|
||||
{
|
||||
if (gameState.ai.accessibility.regionType[i] !== "water")
|
||||
{
|
||||
// push dummies
|
||||
this.seaShips.push(new API3.EntityCollection(gameState.sharedScript));
|
||||
this.seaTpShips.push(new API3.EntityCollection(gameState.sharedScript));
|
||||
this.seaWarships.push(new API3.EntityCollection(gameState.sharedScript));
|
||||
this.wantedTpShips.push(0);
|
||||
this.wantedWarships.push(0);
|
||||
} else {
|
||||
var collec = this.ships.filter(API3.Filters.byStaticMetadata(PlayerID, "sea", i));
|
||||
collec.registerUpdates();
|
||||
this.seaShips.push(collec);
|
||||
collec = this.tpShips.filter(API3.Filters.byStaticMetadata(PlayerID, "sea", i));
|
||||
collec.registerUpdates();
|
||||
this.seaTpShips.push(collec);
|
||||
var collec = this.warships.filter(API3.Filters.byStaticMetadata(PlayerID, "sea", i));
|
||||
collec.registerUpdates();
|
||||
this.seaWarships.push(collec);
|
||||
|
||||
this.wantedTpShips.push(1);
|
||||
this.wantedWarships.push(1);
|
||||
}
|
||||
|
||||
this.landZoneDocked.push([]);
|
||||
}
|
||||
};
|
||||
|
||||
m.NavalManager.prototype.getUnconnectedSeas = function (gameState, region) {
|
||||
var seas = gameState.ai.accessibility.regionLinks[region]
|
||||
if (seas.length === 0)
|
||||
return [];
|
||||
for (var i = 0; i < seas.length; ++i)
|
||||
{
|
||||
if (this.landZoneDocked[region].indexOf(seas[i]) !== -1)
|
||||
seas.splice(i--,1);
|
||||
}
|
||||
return seas;
|
||||
};
|
||||
|
||||
// returns true if there is a path from A to B and we have docks.
|
||||
m.NavalManager.prototype.canReach = function (gameState, regionA, regionB) {
|
||||
var path = gameState.ai.accessibility.getTrajectToIndex(regionA, regionB);
|
||||
if (!path)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < path.length - 1; ++i)
|
||||
{
|
||||
if (gameState.ai.accessibility.regionType[path[i]] == "land")
|
||||
if (this.accessibleSeas.indexOf(path[i+1]) === -1)
|
||||
{
|
||||
m.debug ("cannot reach because of " + path[i+1]);
|
||||
return false; // we wn't be able to board on that sea
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
m.NavalManager.prototype.checkEvents = function (gameState, queues, events) {
|
||||
var evts = events["ConstructionFinished"];
|
||||
// TODO: probably check stuffs like a base destruction.
|
||||
for (var i in evts)
|
||||
{
|
||||
var evt = evts[i];
|
||||
if (evt && evt.newentity)
|
||||
{
|
||||
var entity = gameState.getEntityById(evt.newentity);
|
||||
if (entity && entity.hasClass("Dock") && entity.isOwn(PlayerID))
|
||||
{
|
||||
// okay we have a dock whose construction is finished.
|
||||
// let's assign it to us.
|
||||
var pos = entity.position();
|
||||
var li = gameState.ai.accessibility.getAccessValue(pos);
|
||||
var ni = entity.getMetadata(PlayerID, "sea");
|
||||
if (this.landZoneDocked[li].indexOf(ni) === -1)
|
||||
this.landZoneDocked[li].push(ni);
|
||||
if (this.accessibleSeas.indexOf(ni) === -1)
|
||||
this.accessibleSeas.push(ni);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
m.NavalManager.prototype.addPlan = function(plan) {
|
||||
this.transportPlans.push(plan);
|
||||
};
|
||||
|
||||
// will create a plan at the end of the turn.
|
||||
// many units can call this separately and end up in the same plan
|
||||
// which can be useful.
|
||||
m.NavalManager.prototype.askForTransport = function(entity, startPos, endPos) {
|
||||
this.askedPlans.push([entity, startPos, endPos]);
|
||||
};
|
||||
|
||||
// creates aforementionned plans
|
||||
m.NavalManager.prototype.createPlans = function(gameState) {
|
||||
var startID = {};
|
||||
|
||||
for (var i in this.askedPlans)
|
||||
{
|
||||
var plan = this.askedPlans[i];
|
||||
var startIndex = gameState.ai.accessibility.getAccessValue(plan[1]);
|
||||
var endIndex = gameState.ai.accessibility.getAccessValue(plan[2]);
|
||||
if (startIndex === 1 || endIndex === -1)
|
||||
continue;
|
||||
if (!startID[startIndex])
|
||||
{
|
||||
startID[startIndex] = {};
|
||||
startID[startIndex][endIndex] = { "dest" : plan[2], "units": [plan[0]]};
|
||||
}
|
||||
else if (!startID[startIndex][endIndex])
|
||||
startID[startIndex][endIndex] = { "dest" : plan[2], "units": [plan[0]]};
|
||||
else
|
||||
startID[startIndex][endIndex].units.push(plan[0]);
|
||||
}
|
||||
for (var i in startID)
|
||||
for (var k in startID[i])
|
||||
{
|
||||
var tpPlan = new m.TransportPlan(gameState, startID[i][k].units, startID[i][k].dest, false)
|
||||
this.transportPlans.push (tpPlan);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: work on this.
|
||||
m.NavalManager.prototype.maintainFleet = function(gameState, queues, events) {
|
||||
// check if we have enough transport ships.
|
||||
// check per region.
|
||||
for (var i = 0; i < this.seaShips.length; ++i)
|
||||
{
|
||||
var tpNb = gameState.countOwnQueuedEntitiesWithMetadata("sea", i);
|
||||
if (this.accessibleSeas.indexOf(i) !== -1 && this.seaTpShips[i].length < this.wantedTpShips[i]
|
||||
&& tpNb + queues.ships.length() === 0 && gameState.getTemplate(gameState.applyCiv("units/{civ}_ship_bireme")).available(gameState))
|
||||
{
|
||||
// TODO: check our dock can build the wanted ship types, for Carthage.
|
||||
queues.ships.addItem(new m.TrainingPlan(gameState, "units/{civ}_ship_bireme", { "sea" : i }, 1, 1 ));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// bumps up the number of ships we want if we need more.
|
||||
m.NavalManager.prototype.checkLevels = function(gameState, queues) {
|
||||
if (queues.ships.length() !== 0)
|
||||
return;
|
||||
for (var i = 0; i < this.transportPlans.length; ++i)
|
||||
{
|
||||
var plan = this.transportPlans[i];
|
||||
if (plan.needTpShips())
|
||||
{
|
||||
var zone = plan.neededShipsZone();
|
||||
if (zone && gameState.countOwnQueuedEntitiesWithMetadata("sea", zone) > 0)
|
||||
continue;
|
||||
if (zone && this.wantedTpShips[i] === 0)
|
||||
this.wantedTpShips[i]++;
|
||||
else if (zone && plan.allAtOnce)
|
||||
this.wantedTpShips[i]++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// assigns free ships to plans that need some
|
||||
m.NavalManager.prototype.assignToPlans = function(gameState, queues, events) {
|
||||
for (var i = 0; i < this.transportPlans.length; ++i)
|
||||
{
|
||||
var plan = this.transportPlans[i];
|
||||
if (plan.needTpShips())
|
||||
{
|
||||
// assign one per go.
|
||||
var zone = plan.neededShipsZone();
|
||||
if (zone)
|
||||
{
|
||||
for each (var ship in this.seaTpShips[zone]._entities)
|
||||
{
|
||||
if (!ship.getMetadata(PlayerID, "tpplan"))
|
||||
{
|
||||
m.debug ("Assigning ship " + ship.id() + " to plan" + plan.ID);
|
||||
plan.assignShip(gameState, ship);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
m.NavalManager.prototype.checkActivePlan = function(ID) {
|
||||
for (var i = 0; i < this.transportPlans.length; ++i)
|
||||
if (this.transportPlans[i].ID === ID)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Some functions are run every turn
|
||||
// Others once in a while
|
||||
m.NavalManager.prototype.update = function(gameState, queues, events) {
|
||||
Engine.ProfileStart("Naval Manager update");
|
||||
|
||||
this.checkEvents(gameState, queues, events);
|
||||
|
||||
if (gameState.ai.playedTurn % 10 === 0)
|
||||
{
|
||||
this.maintainFleet(gameState, queues, events);
|
||||
this.checkLevels(gameState, queues);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.transportPlans.length; ++i)
|
||||
if (!this.transportPlans[i].carryOn(gameState, this))
|
||||
{
|
||||
// whatever the reason, this plan needs to be ended
|
||||
// it could be that it's finished though.
|
||||
var seaZone = this.transportPlans[i].neededShipsZone();
|
||||
|
||||
var rallyPos = [];
|
||||
this.docks.forEach(function (dock) {
|
||||
if (dock.getMetadata(PlayerID,"sea") == seaZone)
|
||||
rallyPos = dock.position();
|
||||
});
|
||||
this.transportPlans[i].ships.move(rallyPos);
|
||||
this.transportPlans[i].releaseAll(gameState);
|
||||
this.transportPlans.splice(i,1);
|
||||
--i;
|
||||
}
|
||||
|
||||
this.assignToPlans(gameState, queues, events);
|
||||
if (gameState.ai.playedTurn % 10 === 2)
|
||||
{
|
||||
this.createPlans(gameState);
|
||||
this.askedPlans = [];
|
||||
}
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
478
binaries/data/mods/public/simulation/ai/petra/plan-transport.js
Normal file
478
binaries/data/mods/public/simulation/ai/petra/plan-transport.js
Normal file
@ -0,0 +1,478 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
/*
|
||||
Describes a transport plan
|
||||
Constructor assign units (units is an ID array, or an ID), a destionation (position, ingame), and a wanted escort size.
|
||||
If "onlyIfOk" is true, then the plan will only start if the wanted escort size is met.
|
||||
The naval manager will try to deal with it accordingly.
|
||||
|
||||
By this I mean that the naval manager will find how to go from access point 1 to access point 2 (relying on in-game pathfinder for mvt)
|
||||
And then carry units from there.
|
||||
If units are over multiple accessibility indexes (ie different islands) it will first group them
|
||||
|
||||
Note: only assign it units currently over land, or it won't work.
|
||||
Also: destination should probably be land, otherwise the units will be lost at sea.
|
||||
*/
|
||||
|
||||
// TODO: finish the support of multiple accessibility indexes.
|
||||
// TODO: this doesn't check we can actually reach in the init, which we might want?
|
||||
|
||||
m.TransportPlan = function(gameState, units, destination, allAtOnce, escortSize, onlyIfOK) {
|
||||
var self = this;
|
||||
|
||||
this.ID = m.playerGlobals[PlayerID].uniqueIDTPlans++;
|
||||
|
||||
var unitsID = [];
|
||||
if (units.length !== undefined)
|
||||
unitsID = units;
|
||||
else
|
||||
unitsID = [units];
|
||||
|
||||
this.units = m.EntityCollectionFromIds(gameState, unitsID);
|
||||
this.units.forEach(function (ent) { //}){
|
||||
ent.setMetadata(PlayerID, "tpplan", self.ID);
|
||||
ent.setMetadata(PlayerID, "formerRole", ent.getMetadata(PlayerID, "role"));
|
||||
ent.setMetadata(PlayerID, "role", "transport");
|
||||
});
|
||||
|
||||
this.units.freeze();
|
||||
this.units.registerUpdates();
|
||||
|
||||
m.debug ("Starting a new plan with ID " + this.ID + " to " + destination);
|
||||
m.debug ("units are " + uneval (units));
|
||||
|
||||
this.destination = destination;
|
||||
this.destinationIndex = gameState.ai.accessibility.getAccessValue(destination);
|
||||
|
||||
if (allAtOnce)
|
||||
this.allAtOnce = allAtOnce;
|
||||
else
|
||||
this.allAtOnce = false;
|
||||
|
||||
if (escortSize)
|
||||
this.escortSize = escortSize;
|
||||
else
|
||||
this.escortSize = 0;
|
||||
|
||||
if (onlyIfOK)
|
||||
this.onlyIfOK = onlyIfOK;
|
||||
else
|
||||
this.onlyIfOK = false;
|
||||
|
||||
this.state = "unstarted";
|
||||
|
||||
this.ships = gameState.ai.HQ.navalManager.ships.filter(Filters.byMetadata(PlayerID, "tpplan", this.ID));
|
||||
// note: those two can overlap (some transport ships are warships too and vice-versa).
|
||||
this.transportShips = gameState.ai.HQ.navalManager.tpShips.filter(Filters.byMetadata(PlayerID, "tpplan", this.ID));
|
||||
this.escortShips = gameState.ai.HQ.navalManager.warships.filter(Filters.byMetadata(PlayerID, "tpplan", this.ID));
|
||||
|
||||
this.ships.registerUpdates();
|
||||
this.transportShips.registerUpdates();
|
||||
this.escortShips.registerUpdates();
|
||||
};
|
||||
|
||||
// count available slots
|
||||
m.TransportPlan.prototype.countFreeSlots = function(onlyTrulyFree)
|
||||
{
|
||||
var slots = 0;
|
||||
this.transportShips.forEach(function (ent) { //}){
|
||||
slots += ent.garrisonMax();
|
||||
if (onlyTrulyFree)
|
||||
slots -= ent.garrisoned().length;
|
||||
});
|
||||
}
|
||||
|
||||
m.TransportPlan.prototype.assignShip = function(gameState, ship)
|
||||
{
|
||||
ship.setMetadata(PlayerID,"tpplan", this.ID);
|
||||
}
|
||||
|
||||
m.TransportPlan.prototype.releaseAll = function(gameState)
|
||||
{
|
||||
this.ships.forEach(function (ent) { ent.setMetadata(PlayerID,"tpplan", undefined) });
|
||||
this.units.forEach(function (ent) {
|
||||
var fRole = ent.getMetadata(PlayerID, "formerRole");
|
||||
if (fRole)
|
||||
ent.setMetadata(PlayerID,"role", fRole);
|
||||
ent.setMetadata(PlayerID,"tpplan", undefined)
|
||||
});
|
||||
}
|
||||
|
||||
m.TransportPlan.prototype.releaseAllShips = function(gameState)
|
||||
{
|
||||
this.ships.forEach(function (ent) { ent.setMetadata(PlayerID,"tpplan", undefined) });
|
||||
}
|
||||
|
||||
m.TransportPlan.prototype.needTpShips = function()
|
||||
{
|
||||
if ((this.allAtOnce && this.countFreeSlots() >= this.units.length) || this.transportShips.length > 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
m.TransportPlan.prototype.needEscortShips = function()
|
||||
{
|
||||
return !((this.onlyIfOK && this.escortShips.length < this.escortSize) || !this.onlyIfOK);
|
||||
}
|
||||
|
||||
// returns the zone for which we are needing our ships
|
||||
m.TransportPlan.prototype.neededShipsZone = function()
|
||||
{
|
||||
if (!this.seaZone)
|
||||
return false;
|
||||
return this.seaZone;
|
||||
}
|
||||
|
||||
|
||||
// try to move on.
|
||||
/* several states:
|
||||
"unstarted" is the initial state, and will determine wether we follow basic or grouping path
|
||||
Basic path:
|
||||
- "waitingForBoarding" means we wait 'till we have enough transport ships and escort ships to move stuffs.
|
||||
- "Boarding" means we're trying to board units onto our ships
|
||||
- "Moving" means we're moving ships
|
||||
- "Unboarding" means we're unbording
|
||||
- Once we're unboarded, we either return to boarding point (if we still have units to board) or we clear.
|
||||
> there is the possibility that we'll be moving units on land, but that's basically a restart too, with more clearing.
|
||||
Grouping Path is basically the same with "grouping" and we never unboard (unless there is a need to)
|
||||
*/
|
||||
m.TransportPlan.prototype.carryOn = function(gameState, navalManager)
|
||||
{
|
||||
if (this.state === "unstarted")
|
||||
{
|
||||
// Okay so we can start the plan.
|
||||
// So what we'll do is check what accessibility indexes our units are.
|
||||
var unitIndexes = [];
|
||||
this.units.forEach( function (ent) { //}){
|
||||
var idx = gameState.ai.accessibility.getAccessValue(ent.position());
|
||||
if (unitIndexes.indexOf(idx) === -1 && idx !== 1)
|
||||
unitIndexes.push(idx);
|
||||
});
|
||||
|
||||
// we have indexes. If there is more than 1, we'll try and regroup them.
|
||||
if (unitIndexes.length > 1)
|
||||
{
|
||||
warn("Transport Plan path is too complicated, aborting");
|
||||
return false;
|
||||
/*
|
||||
this.state = "waitingForGrouping";
|
||||
// get the best index for grouping, ie start by the one farthest away in terms of movement.
|
||||
var idxLength = {};
|
||||
for (var i = 0; i < unitIndexes.length; ++i)
|
||||
idxLength[unitIndexes[i]] = gameState.ai.accessibility.getTrajectToIndex(unitIndexes[i], this.destinationIndex).length;
|
||||
var sortedArray = unitIndexes.sort(function (a,b) { //}){
|
||||
return idxLength[b] - idxLength[a];
|
||||
});
|
||||
this.startIndex = sortedArray[0];
|
||||
// okay so we'll board units from this index and we'll try to join them with units of the next index.
|
||||
// this might not be terribly efficient but it won't be efficient anyhow.
|
||||
return true;*/
|
||||
}
|
||||
this.state = "waitingForBoarding";
|
||||
|
||||
// let's get our index this turn.
|
||||
this.startIndex = unitIndexes[0];
|
||||
|
||||
m.debug ("plan " + this.ID + " from " + this.startIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
if (this.state === "waitingForBoarding")
|
||||
{
|
||||
|
||||
if (!this.path)
|
||||
{
|
||||
this.path = gameState.ai.accessibility.getTrajectToIndex(this.startIndex, this.destinationIndex);
|
||||
if (!this.path || this.path.length === 0 || this.path.length % 2 === 0)
|
||||
return false; // TODO: improve error handling
|
||||
if (this.path[0] !== this.startIndex)
|
||||
{
|
||||
warn ("Start point of the path is not the start index, aborting transport plan");
|
||||
return false;
|
||||
}
|
||||
// we have a path, register the first sea zone.
|
||||
this.seaZone = this.path[1];
|
||||
m.debug ("Plan " + this.ID + " over seazone " + this.seaZone);
|
||||
}
|
||||
// if we currently have no baoarding spot, try and find one.
|
||||
if (!this.boardingSpot)
|
||||
{
|
||||
// TODO: improve on this whenever we have danger maps.
|
||||
// okay so we have units over an accessibility index.
|
||||
// we'll get a map going on.
|
||||
var Xibility = gameState.ai.accessibility;
|
||||
|
||||
// custom obstruction map that uses the shore as the obstruction map
|
||||
// but doesn't really check like for a building.
|
||||
// created realtime with the other map.
|
||||
var passabilityMap = gameState.getMap();
|
||||
var territoryMap = gameState.ai.territoryMap;
|
||||
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction") | gameState.getPassabilityClassMask("building-shore");
|
||||
var obstructions = new API3.Map(gameState.sharedScript);
|
||||
|
||||
// wanted map.
|
||||
var friendlyTiles = new API3.Map(gameState.sharedScript);
|
||||
|
||||
for (var j = 0; j < friendlyTiles.length; ++j)
|
||||
{
|
||||
// only on the wanted island
|
||||
if (Xibility.landPassMap[j] !== this.startIndex)
|
||||
continue;
|
||||
|
||||
// setting obstructions
|
||||
var tilePlayer = (territoryMap.data[j] & TERRITORY_PLAYER_MASK);
|
||||
// invalid is enemy-controlled or not on the right sea/land (we need a shore for this, we might want to check neighbhs instead).
|
||||
var invalidTerritory = (gameState.isPlayerEnemy(tilePlayer) && tilePlayer != 0)
|
||||
|| (Xibility.navalPassMap[j] !== this.path[1]);
|
||||
obstructions.map[j] = (invalidTerritory || (passabilityMap.data[j] & obstructionMask)) ? 0 : 255;
|
||||
|
||||
// currently we'll just like better on our territory
|
||||
if (tilePlayer == PlayerID)
|
||||
friendlyTiles.map[j] = 100;
|
||||
}
|
||||
|
||||
obstructions.expandInfluences();
|
||||
|
||||
var best = friendlyTiles.findBestTile(4, obstructions);
|
||||
var bestIdx = best[0];
|
||||
|
||||
// not good enough.
|
||||
if (best[1] <= 0)
|
||||
{
|
||||
best = friendlyTiles.findBestTile(1, obstructions);
|
||||
bestIdx = best[0];
|
||||
if (best[1] <= 0)
|
||||
return false; // apparently we won't be able to board.
|
||||
}
|
||||
|
||||
var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
|
||||
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize;
|
||||
|
||||
// we have the spot we want to board at.
|
||||
this.boardingSpot = [x,z];
|
||||
m.debug ("Plan " + this.ID + " new boarding spot is " + this.boardingSpot);
|
||||
}
|
||||
|
||||
// if all at once we need to be full, else we just need enough escort ships.
|
||||
if (!this.needTpShips() && !this.needEscortShips())
|
||||
{
|
||||
// preparing variables
|
||||
// TODO: destroy former entity collection.
|
||||
this.garrisoningUnits = this.units.filter(Filters.not(Filters.isGarrisoned()));
|
||||
this.garrisoningUnits.registerUpdates();
|
||||
this.garrisoningUnits.freeze();
|
||||
|
||||
this.garrisonShipID = -1;
|
||||
|
||||
m.debug ("Boarding");
|
||||
this.state = "boarding";
|
||||
}
|
||||
return true;
|
||||
} else if (this.state === "waitingForGrouping")
|
||||
{
|
||||
// TODO: this.
|
||||
return true;
|
||||
}
|
||||
if (this.state === "boarding" && gameState.ai.playedTurn % 5 === 0)
|
||||
{
|
||||
// TODO: improve error recognition.
|
||||
if (this.units.length === 0)
|
||||
return false;
|
||||
if (!this.boardingSpot)
|
||||
return false;
|
||||
if (this.needTpShips())
|
||||
{
|
||||
this.state = "waitingForBoarding";
|
||||
return true;
|
||||
}
|
||||
if (this.needEscortShips())
|
||||
{
|
||||
this.state = "waitingForBoarding";
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if we aren't actually finished.
|
||||
if (this.units.getCentrePosition() == undefined || this.countFreeSlots(true) === 0)
|
||||
{
|
||||
delete this.boardingSpot;
|
||||
this.garrisoningUnits.unregister();
|
||||
this.state = "moving";
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if we need to move our units and ships closer together
|
||||
var stillMoving = false;
|
||||
if (API3.SquareVectorDistance(this.ships.getCentrePosition(),this.boardingSpot) > 1600)
|
||||
{
|
||||
this.ships.move(this.boardingSpot[0],this.boardingSpot[1]);
|
||||
stillMoving = true; // wait till ships are in position
|
||||
}
|
||||
if (API3.SquareVectorDistance(this.units.getCentrePosition(),this.boardingSpot) > 1600)
|
||||
{
|
||||
this.units.move(this.boardingSpot[0],this.boardingSpot[1]);
|
||||
stillMoving = true; // wait till units are in position
|
||||
}
|
||||
if (stillMoving)
|
||||
{
|
||||
return true; // wait.
|
||||
}
|
||||
// check if we need to try and board units.
|
||||
var garrisonShip = gameState.getEntityById(this.garrisonShipID);
|
||||
var self = this;
|
||||
// check if ship we're currently garrisoning in is full
|
||||
if (garrisonShip && garrisonShip.canGarrisonInside())
|
||||
{
|
||||
// okay garrison units
|
||||
var nbStill = garrisonShip.garrisonMax() - garrisonShip.garrisoned().length;
|
||||
if (this.garrisoningUnits.length < nbStill)
|
||||
{
|
||||
Engine.PostCommand({"type": "garrison", "entities": this.garrisoningUnits.toIdArray(), "target": garrisonShip.id(),"queued": false});
|
||||
}
|
||||
return true;
|
||||
} else if (garrisonShip)
|
||||
{
|
||||
// full ship, abort
|
||||
this.garrisonShipID = -1;
|
||||
garrisonShip = false; // will enter next if.
|
||||
}
|
||||
if (!garrisonShip)
|
||||
{
|
||||
// could have died or could have be full
|
||||
// we'll pick a new one, one that isn't full
|
||||
for (var i in this.transportShips._entities)
|
||||
{
|
||||
if (this.transportShips._entities[i].canGarrisonInside())
|
||||
{
|
||||
this.garrisonShipID = this.transportShips._entities[i].id();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true; // wait.
|
||||
}
|
||||
// could I actually get here?
|
||||
return true;
|
||||
}
|
||||
if (this.state === "moving")
|
||||
{
|
||||
if (!this.unboardingSpot)
|
||||
{
|
||||
// TODO: improve on this whenever we have danger maps.
|
||||
// okay so we have units over an accessibility index.
|
||||
// we'll get a map going on.
|
||||
var Xibility = gameState.ai.accessibility;
|
||||
|
||||
// custom obstruction map that uses the shore as the obstruction map
|
||||
// but doesn't really check like for a building.
|
||||
// created realtime with the other map.
|
||||
var passabilityMap = gameState.getMap();
|
||||
var territoryMap = gameState.ai.territoryMap;
|
||||
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction") | gameState.getPassabilityClassMask("building-shore");
|
||||
var obstructions = new API3.Map(gameState.sharedScript);
|
||||
|
||||
// wanted map.
|
||||
var friendlyTiles = new API3.Map(gameState.sharedScript);
|
||||
|
||||
var wantedIndex = -1;
|
||||
|
||||
if (this.path.length >= 3)
|
||||
{
|
||||
this.path.splice(0,2);
|
||||
wantedIndex = this.path[0];
|
||||
} else {
|
||||
m.debug ("too short at " +uneval(this.path));
|
||||
return false; // Incomputable
|
||||
}
|
||||
|
||||
for (var j = 0; j < friendlyTiles.length; ++j)
|
||||
{
|
||||
// only on the wanted island
|
||||
if (Xibility.landPassMap[j] !== wantedIndex)
|
||||
continue;
|
||||
|
||||
// setting obstructions
|
||||
var tilePlayer = (territoryMap.data[j] & TERRITORY_PLAYER_MASK);
|
||||
// invalid is not on the right land (we need a shore for this, we might want to check neighbhs instead).
|
||||
var invalidTerritory = (Xibility.landPassMap[j] !== wantedIndex);
|
||||
obstructions.map[j] = (invalidTerritory || (passabilityMap.data[j] & obstructionMask)) ? 0 : 255;
|
||||
|
||||
// currently we'll just like better on our territory
|
||||
if (tilePlayer == PlayerID)
|
||||
friendlyTiles.map[j] = 100;
|
||||
else if (gameState.isPlayerEnemy(tilePlayer) && tilePlayer != 0)
|
||||
friendlyTiles.map[j] = 4;
|
||||
else
|
||||
friendlyTiles.map[j] = 50;
|
||||
}
|
||||
|
||||
obstructions.expandInfluences();
|
||||
|
||||
var best = friendlyTiles.findBestTile(4, obstructions);
|
||||
var bestIdx = best[0];
|
||||
|
||||
// not good enough.
|
||||
if (best[1] <= 0)
|
||||
{
|
||||
best = friendlyTiles.findBestTile(1, obstructions);
|
||||
bestIdx = best[0];
|
||||
if (best[1] <= 0)
|
||||
return false; // apparently we won't be able to unboard.
|
||||
}
|
||||
|
||||
var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
|
||||
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * gameState.cellSize;
|
||||
|
||||
// we have the spot we want to board at.
|
||||
this.unboardingSpot = [x,z];
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: improve error recognition.
|
||||
if (this.units.length === 0)
|
||||
return false;
|
||||
if (!this.unboardingSpot)
|
||||
return false;
|
||||
|
||||
// check if we need to move ships
|
||||
if (API3.SquareVectorDistance(this.ships.getCentrePosition(),this.unboardingSpot) > 400)
|
||||
{
|
||||
this.ships.move(this.unboardingSpot[0],this.unboardingSpot[1]);
|
||||
} else {
|
||||
this.state = "unboarding";
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (this.state === "unboarding")
|
||||
{
|
||||
// TODO: improve error recognition.
|
||||
if (this.units.length === 0)
|
||||
return false;
|
||||
|
||||
// check if we need to move ships
|
||||
if (API3.SquareVectorDistance(this.ships.getCentrePosition(),this.unboardingSpot) > 400)
|
||||
{
|
||||
this.ships.move(this.unboardingSpot[0],this.unboardingSpot[1]);
|
||||
} else {
|
||||
this.transportShips.forEach( function (ent) { ent.unloadAll() });
|
||||
// TODO: improve on this.
|
||||
if (this.path.length > 1)
|
||||
{
|
||||
m.debug ("plan " + this.ID + " going back for more");
|
||||
// basically reset.
|
||||
delete this.boardingSpot;
|
||||
delete this.unboardingSpot;
|
||||
this.state = "unstarted";
|
||||
this.releaseAllShips();
|
||||
return true;
|
||||
}
|
||||
m.debug ("plan " + this.ID + " is finished");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
131
binaries/data/mods/public/simulation/ai/petra/queue.js
Normal file
131
binaries/data/mods/public/simulation/ai/petra/queue.js
Normal file
@ -0,0 +1,131 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
/*
|
||||
* Holds a list of wanted items to train or construct
|
||||
*/
|
||||
|
||||
m.Queue = function()
|
||||
{
|
||||
this.queue = [];
|
||||
this.paused = false;
|
||||
this.switched = 0;
|
||||
};
|
||||
|
||||
m.Queue.prototype.empty = function()
|
||||
{
|
||||
this.queue = [];
|
||||
};
|
||||
|
||||
m.Queue.prototype.addItem = function(plan)
|
||||
{
|
||||
if (!plan) warn(" essaie d'ajout d un plan vide");
|
||||
if (!plan)
|
||||
return;
|
||||
for (var i in this.queue)
|
||||
{
|
||||
if (plan.category === "unit" && this.queue[i].type == plan.type && this.queue[i].number + plan.number <= this.queue[i].maxMerge)
|
||||
{
|
||||
this.queue[i].addItem(plan.number)
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.queue.push(plan);
|
||||
};
|
||||
|
||||
m.Queue.prototype.check= function(gameState)
|
||||
{
|
||||
while (this.queue.length > 0)
|
||||
{
|
||||
if (!this.queue[0].isInvalid(gameState))
|
||||
return;
|
||||
warn(" plan " + this.queue[0].type + " invalid and suppressed");
|
||||
this.queue.shift();
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
m.Queue.prototype.getNext = function()
|
||||
{
|
||||
if (this.queue.length > 0)
|
||||
return this.queue[0];
|
||||
else
|
||||
return null;
|
||||
};
|
||||
|
||||
m.Queue.prototype.startNext = function(gameState)
|
||||
{
|
||||
if (this.queue.length > 0)
|
||||
{
|
||||
this.queue.shift().start(gameState);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
};
|
||||
|
||||
// returns the maximal account we'll accept for this queue.
|
||||
// Currently all the cost of the first element and fraction of that of the second
|
||||
m.Queue.prototype.maxAccountWanted = function(gameState, fraction)
|
||||
{
|
||||
var cost = new API3.Resources();
|
||||
if (this.queue.length > 0 && this.queue[0].isGo(gameState))
|
||||
cost.add(this.queue[0].getCost());
|
||||
if (this.queue.length > 1 && this.queue[1].isGo(gameState) && fraction > 0)
|
||||
{
|
||||
var costs = this.queue[1].getCost();
|
||||
costs.multiply(fraction);
|
||||
cost.add(costs);
|
||||
}
|
||||
return cost;
|
||||
};
|
||||
|
||||
m.Queue.prototype.queueCost = function()
|
||||
{
|
||||
var cost = new API3.Resources();
|
||||
for (var key in this.queue)
|
||||
cost.add(this.queue[key].getCost());
|
||||
return cost;
|
||||
};
|
||||
|
||||
m.Queue.prototype.length = function()
|
||||
{
|
||||
return this.queue.length;
|
||||
};
|
||||
|
||||
m.Queue.prototype.countQueuedUnits = function()
|
||||
{
|
||||
var count = 0;
|
||||
for (var i in this.queue)
|
||||
count += this.queue[i].number;
|
||||
return count;
|
||||
};
|
||||
|
||||
m.Queue.prototype.countQueuedUnitsWithClass = function(classe)
|
||||
{
|
||||
var count = 0;
|
||||
for (var i in this.queue)
|
||||
if (this.queue[i].template && this.queue[i].template.hasClass(classe))
|
||||
count += this.queue[i].number;
|
||||
return count;
|
||||
};
|
||||
m.Queue.prototype.countQueuedUnitsWithMetadata = function(data,value)
|
||||
{
|
||||
var count = 0;
|
||||
for (var i in this.queue)
|
||||
if (this.queue[i].metadata[data] && this.queue[i].metadata[data] == value)
|
||||
count += this.queue[i].number;
|
||||
return count;
|
||||
};
|
||||
|
||||
m.Queue.prototype.countAllByType = function(t)
|
||||
{
|
||||
var count = 0;
|
||||
for (var i in this.queue)
|
||||
if (this.queue[i].type === t)
|
||||
count += this.queue[i].number;
|
||||
return count;
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
619
binaries/data/mods/public/simulation/ai/petra/queueManager.js
Normal file
619
binaries/data/mods/public/simulation/ai/petra/queueManager.js
Normal file
@ -0,0 +1,619 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
// This takes the input queues and picks which items to fund with resources until no more resources are left to distribute.
|
||||
//
|
||||
// Currently this manager keeps accounts for each queue, split between the 4 main resources
|
||||
//
|
||||
// Each time resources are available (ie not in any account), it is split between the different queues
|
||||
// Mostly based on priority of the queue, and existing needs.
|
||||
// Each turn, the queue Manager checks if a queue can afford its next item, then it does.
|
||||
//
|
||||
// A consequence of the system it's not really revertible. Once a queue has an account of 500 food, it'll keep it
|
||||
// If for some reason the AI stops getting new food, and this queue lacks, say, wood, no other queues will
|
||||
// be able to benefit form the 500 food (even if they only needed food).
|
||||
// This is not to annoying as long as all goes well. If the AI loses many workers, it starts being problematic.
|
||||
//
|
||||
// It also has the effect of making the AI more or less always sit on a few hundreds resources since most queues
|
||||
// get some part of the total, and if all queues have 70% of their needs, nothing gets done
|
||||
// Particularly noticeable when phasing: the AI often overshoots by a good 200/300 resources before starting.
|
||||
//
|
||||
// This system should be improved. It's probably not flexible enough.
|
||||
|
||||
m.QueueManager = function(Config, queues, priorities)
|
||||
{
|
||||
this.Config = Config;
|
||||
this.queues = queues;
|
||||
this.priorities = priorities;
|
||||
this.accounts = {};
|
||||
|
||||
// the sorting is updated on priority change.
|
||||
var self = this;
|
||||
this.queueArrays = [];
|
||||
for (var p in this.queues)
|
||||
{
|
||||
this.accounts[p] = new API3.Resources();
|
||||
this.queueArrays.push([p,this.queues[p]]);
|
||||
}
|
||||
this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
|
||||
|
||||
this.curItemQueue = [];
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.getAvailableResources = function(gameState, noAccounts)
|
||||
{
|
||||
var resources = gameState.getResources();
|
||||
if (noAccounts)
|
||||
return resources;
|
||||
for (var key in this.queues)
|
||||
resources.subtract(this.accounts[key]);
|
||||
return resources;
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.getTotalAccountedResources = function(gameState)
|
||||
{
|
||||
var resources = new API3.Resources();
|
||||
for (var key in this.queues)
|
||||
resources.add(this.accounts[key]);
|
||||
return resources;
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.currentNeeds = function(gameState)
|
||||
{
|
||||
var needed = new API3.Resources();
|
||||
//queueArrays because it's faster.
|
||||
for (var i in this.queueArrays)
|
||||
{
|
||||
var name = this.queueArrays[i][0];
|
||||
var queue = this.queueArrays[i][1];
|
||||
if (queue.length() == 0 || !queue.queue[0].isGo(gameState))
|
||||
continue;
|
||||
// we need resource if the account is smaller than the cost
|
||||
var costs = queue.queue[0].getCost();
|
||||
for each (var ress in costs.types)
|
||||
costs[ress] = Math.max(0, costs[ress] - this.accounts[name][ress]);
|
||||
|
||||
needed.add(costs);
|
||||
}
|
||||
return needed;
|
||||
};
|
||||
|
||||
// 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)
|
||||
{
|
||||
// get out current resources, not removing accounts.
|
||||
var current = this.getAvailableResources(gameState, true);
|
||||
// short queue is the first item of a queue, assumed to be ready in 30s
|
||||
// medium queue is the second item of a queue, assumed to be ready in 60s
|
||||
// long queue contains the is the isGo=false items, assumed to be ready in 300s
|
||||
var totalShort = { "food": 0, "wood": 0, "stone": 0, "metal": 0 };
|
||||
var totalMedium = { "food": 0, "wood": 0, "stone": 0, "metal": 0 };
|
||||
var totalLong = { "food": 0, "wood": 0, "stone": 0, "metal": 0 };
|
||||
var total;
|
||||
//queueArrays because it's faster.
|
||||
for (var i in this.queueArrays)
|
||||
{
|
||||
var name = this.queueArrays[i][0];
|
||||
var queue = this.queueArrays[i][1];
|
||||
if (queue.paused)
|
||||
continue;
|
||||
for (var j = 0; j < queue.length(); ++j)
|
||||
{
|
||||
if (j > 1)
|
||||
break;
|
||||
var cost = queue.queue[j].getCost();
|
||||
if (queue.queue[j].isGo(gameState))
|
||||
{
|
||||
if (j == 0)
|
||||
total = totalShort;
|
||||
else
|
||||
total = totalMedium;
|
||||
}
|
||||
else
|
||||
total = totalLong;
|
||||
for (var type in total)
|
||||
total[type] += cost[type];
|
||||
if (!queue.queue[j].isGo(gameState))
|
||||
break;
|
||||
}
|
||||
}
|
||||
// global rates
|
||||
var rates = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 };
|
||||
var diff;
|
||||
for (var type in rates)
|
||||
{
|
||||
if (current[type] > 0)
|
||||
{
|
||||
diff = Math.min(current[type], totalShort[type]);
|
||||
totalShort[type] -= diff;
|
||||
current[type] -= diff;
|
||||
if (current[type] > 0)
|
||||
{
|
||||
diff = Math.min(current[type], totalMedium[type]);
|
||||
totalMedium[type] -= diff;
|
||||
current[type] -= diff;
|
||||
if (current[type] > 0)
|
||||
{
|
||||
diff = Math.min(current[type], totalLong[type]);
|
||||
totalLong[type] -= diff;
|
||||
current[type] -= diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
rates[type] = totalShort[type]/30 + totalMedium[type]/60 + totalLong[type]/300;
|
||||
}
|
||||
|
||||
// if (this.Config.debug)
|
||||
// {
|
||||
// var oldRates = this.oldWantedGatherRates(gameState);
|
||||
// warn(" old Rates " + uneval(oldRates) + " new rates " + uneval(rates));
|
||||
// }
|
||||
return rates;
|
||||
// return oldRates;
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.oldWantedGatherRates = 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);
|
||||
|
||||
//queueArrays because it's faster.
|
||||
for (var i in this.queueArrays)
|
||||
{
|
||||
qCosts = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0 };
|
||||
qTime = gameState.getTimeElapsed();
|
||||
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();
|
||||
|
||||
var timeMultiplier = Math.max(1,(qTime-time)/25000);
|
||||
|
||||
if (!elem.isGo(gameState))
|
||||
{
|
||||
// assume we'll be wanted in four minutes.
|
||||
// TODO: work on this.
|
||||
for (var type in qCosts)
|
||||
qCosts[type] += cost[type] / timeMultiplier;
|
||||
qTime += 240000;
|
||||
break; // disregard other stuffs.
|
||||
}
|
||||
// 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)
|
||||
{
|
||||
if (cost[type] === 0)
|
||||
continue;
|
||||
qCosts[type] += (cost[type] + Math.min(cost[type],this.priorities[name])) / timeMultiplier;
|
||||
}
|
||||
qTime += 30000; // TODO: this needs a lot more work.
|
||||
}
|
||||
for (var j in qCosts)
|
||||
{
|
||||
qCosts[j] -= this.accounts[name][j];
|
||||
var diff = Math.min(qCosts[j], currentRess[j]);
|
||||
qCosts[j] -= diff;
|
||||
currentRess[j] -= diff;
|
||||
rates[j] += qCosts[j]/(qTime/1000);
|
||||
}
|
||||
}
|
||||
|
||||
return rates;
|
||||
};
|
||||
|
||||
/*m.QueueManager.prototype.logNeeds = function(gameState) {
|
||||
if (!this.totor)
|
||||
{
|
||||
this.totor = [];
|
||||
this.currentGathR = [];
|
||||
this.currentGathRWanted = [];
|
||||
this.ressLev = [];
|
||||
}
|
||||
|
||||
if (gameState.ai.playedTurn % 10 !== 0)
|
||||
return;
|
||||
|
||||
|
||||
var array = this.wantedGatherRates(gameState);
|
||||
this.totor.push( array );
|
||||
|
||||
|
||||
var currentRates = {};
|
||||
for (var type in array)
|
||||
currentRates[type] = 0;
|
||||
for (var i in gameState.ai.HQ.baseManagers)
|
||||
{
|
||||
var base = gameState.ai.HQ.baseManagers[i];
|
||||
for (var type in array)
|
||||
{
|
||||
base.gatherersByType(gameState,type).forEach (function (ent) { //}){
|
||||
var worker = ent.getMetadata(PlayerID, "worker-object");
|
||||
if (worker)
|
||||
currentRates[type] += worker.getGatherRate(gameState);
|
||||
});
|
||||
}
|
||||
}
|
||||
this.currentGathR.push( currentRates );
|
||||
|
||||
var types = Object.keys(array);
|
||||
|
||||
types.sort(function(a, b) {
|
||||
var va = (Math.max(0,array[a] - currentRates[a]))/ (currentRates[a]+1);
|
||||
var vb = (Math.max(0,array[b] - currentRates[b]))/ (currentRates[b]+1);
|
||||
if (va === vb)
|
||||
return (array[b]/(currentRates[b]+1)) - (array[a]/(currentRates[a]+1));
|
||||
return vb-va;
|
||||
});
|
||||
this.currentGathRWanted.push( types );
|
||||
|
||||
var rss = gameState.getResources();
|
||||
this.ressLev.push( {"food" : rss["food"],"stone" : rss["stone"],"wood" : rss["wood"],"metal" : rss["metal"]} );
|
||||
|
||||
if (gameState.getTimeElapsed() > 20*60*1000 && !this.once)
|
||||
{
|
||||
this.once = true;
|
||||
for (var j in array)
|
||||
{
|
||||
log (j + ";");
|
||||
for (var i = 0; i < this.totor.length; ++i)
|
||||
{
|
||||
log (this.totor[i][j] + ";");
|
||||
}
|
||||
}
|
||||
log();
|
||||
for (var j in array)
|
||||
{
|
||||
log (j + ";");
|
||||
for (var i = 0; i < this.totor.length; ++i)
|
||||
{
|
||||
log (this.currentGathR[i][j] + ";");
|
||||
}
|
||||
}
|
||||
log();
|
||||
for (var j in array)
|
||||
{
|
||||
log (j + ";");
|
||||
for (var i = 0; i < this.totor.length; ++i)
|
||||
{
|
||||
log (this.currentGathRWanted[i].indexOf(j) + ";");
|
||||
}
|
||||
}
|
||||
log();
|
||||
for (var j in array)
|
||||
{
|
||||
log (j + ";");
|
||||
for (var i = 0; i < this.totor.length; ++i)
|
||||
{
|
||||
log (this.ressLev[i][j] + ";");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
m.QueueManager.prototype.printQueues = function(gameState)
|
||||
{
|
||||
warn("QUEUES");
|
||||
for (var i in this.queues)
|
||||
{
|
||||
var qStr = "";
|
||||
var q = this.queues[i];
|
||||
if (q.queue.length > 0)
|
||||
{
|
||||
warn(i + ": ( paused " + q.paused + " with priority " + this.priorities[i] +" and accounts " + uneval(this.accounts[i]) +")");
|
||||
warn(" while maxAccountWanted(0.6) is " + uneval(q.maxAccountWanted(gameState, 0.6)));
|
||||
}
|
||||
for (var j in q.queue)
|
||||
{
|
||||
qStr = " " + q.queue[j].type + " ";
|
||||
if (q.queue[j].number)
|
||||
qStr += "x" + q.queue[j].number;
|
||||
qStr += " isGo " + q.queue[j].isGo(gameState);
|
||||
warn(qStr);
|
||||
}
|
||||
}
|
||||
warn("Accounts");
|
||||
for (var p in this.accounts)
|
||||
warn(p + ": " + uneval(this.accounts[p]));
|
||||
warn("Current Resources:" + uneval(gameState.getResources()));
|
||||
warn("Available Resources:" + uneval(this.getAvailableResources(gameState)));
|
||||
warn("Wanted Gather Rates:" + uneval(this.wantedGatherRates(gameState)));
|
||||
warn("Aegis Wanted Gather Rates:" + uneval(this.oldWantedGatherRates(gameState)));
|
||||
warn("Current Gather Rates:" + uneval(gameState.ai.HQ.GetCurrentGatherRates(gameState)));
|
||||
warn(" - - - - - - - -");
|
||||
for (var i in gameState.ai.HQ.baseManagers)
|
||||
for (var ress in gameState.ai.HQ.wantedRates)
|
||||
warn(" DPresource " + i + " ress " + ress + " = " + gameState.ai.HQ.baseManagers[i].getResourceLevel(gameState, ress));
|
||||
warn(" - - - - - - - -");
|
||||
};
|
||||
|
||||
// nice readable HTML version.
|
||||
m.QueueManager.prototype.HTMLprintQueues = function(gameState)
|
||||
{
|
||||
if (!m.DebugEnabled())
|
||||
return;
|
||||
var strToSend = [];
|
||||
strToSend.push("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\"> <html> <head> <title>Aegis Queue Manager</title> <link rel=\"stylesheet\" href=\"table.css\"> </head> <body> <table> <caption>Aegis Build Order</caption> ");
|
||||
for (var i in this.queues)
|
||||
{
|
||||
strToSend.push("<tr>");
|
||||
|
||||
var q = this.queues[i];
|
||||
var str = "<th>" + i + " (" + this.priorities[i] + ")<br><span class=\"ressLevel\">";
|
||||
for each (var k in this.accounts[i].types)
|
||||
if(k != "population")
|
||||
{
|
||||
str += this.accounts[i][k] + k.substr(0,1).toUpperCase() ;
|
||||
if (k != "metal") str += " / ";
|
||||
}
|
||||
strToSend.push(str + "</span></th>");
|
||||
for (var j in q.queue) {
|
||||
if (q.queue[j].isGo(gameState))
|
||||
strToSend.push("<td>");
|
||||
else
|
||||
strToSend.push("<td class=\"NotGo\">");
|
||||
|
||||
var qStr = "";
|
||||
if (q.queue[j].number)
|
||||
qStr += q.queue[j].number + " ";
|
||||
qStr += q.queue[j].type;
|
||||
qStr += "<br><span class=\"ressLevel\">";
|
||||
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 += "</span></td>";
|
||||
strToSend.push(qStr);
|
||||
}
|
||||
strToSend.push("</tr>");
|
||||
}
|
||||
strToSend.push("</table>");
|
||||
/*strToSend.push("<h3>Accounts</h3>");
|
||||
for (var p in this.accounts)
|
||||
{
|
||||
strToSend.push("<p>" + p + ": " + uneval(this.accounts[p]) + " </p>");
|
||||
}*/
|
||||
strToSend.push("<p>Wanted Gather Rate:" + uneval(this.wantedGatherRates(gameState)) + "</p>");
|
||||
strToSend.push("<p>Current Resources:" + uneval(gameState.getResources()) + "</p>");
|
||||
strToSend.push("<p>Available Resources:" + uneval(this.getAvailableResources(gameState)) + "</p>");
|
||||
strToSend.push("</body></html>");
|
||||
for each (var logged in strToSend)
|
||||
log(logged);
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.clear = function()
|
||||
{
|
||||
this.curItemQueue = [];
|
||||
for (var i in this.queues)
|
||||
this.queues[i].empty();
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.update = function(gameState)
|
||||
{
|
||||
for (var i in this.queues)
|
||||
{
|
||||
this.queues[i].check(gameState); // do basic sanity checks on the queue
|
||||
if (this.priorities[i] > 0)
|
||||
continue;
|
||||
warn("QueueManager received bad priorities, please report this error: " + uneval(this.priorities));
|
||||
this.priorities[i] = 1; // TODO: make the Queue Manager not die when priorities are zero.
|
||||
}
|
||||
|
||||
Engine.ProfileStart("Queue Manager");
|
||||
|
||||
// Let's assign resources to plans that need'em
|
||||
var availableRes = this.getAvailableResources(gameState);
|
||||
for (var ress in availableRes)
|
||||
{
|
||||
if (ress === "population")
|
||||
continue;
|
||||
|
||||
if (availableRes[ress] > 0)
|
||||
{
|
||||
var totalPriority = 0;
|
||||
var tempPrio = {};
|
||||
var maxNeed = {};
|
||||
// Okay so this is where it gets complicated.
|
||||
// If a queue requires "ress" for the next elements (in the queue)
|
||||
// And the account is not high enough for it.
|
||||
// Then we add it to the total priority.
|
||||
// To try and be clever, we don't want a long queue to hog all resources. So two things:
|
||||
// -if a queue has enough of resource X for the 1st element, its priority is decreased (factor 2).
|
||||
// -queues accounts are capped at "resources for the first + 60% of the next"
|
||||
// This avoids getting a high priority queue with many elements hogging all of one resource
|
||||
// uselessly while it awaits for other resources.
|
||||
for (var j in this.queues)
|
||||
{
|
||||
// returns exactly the correct amount, ie 0 if we're not go.
|
||||
var queueCost = this.queues[j].maxAccountWanted(gameState, 0.6);
|
||||
if (this.queues[j].length() > 0 && this.accounts[j][ress] < queueCost[ress] && !this.queues[j].paused)
|
||||
{
|
||||
// adding us to the list of queues that need an update.
|
||||
tempPrio[j] = this.priorities[j];
|
||||
maxNeed[j] = queueCost[ress] - this.accounts[j][ress];
|
||||
// if we have enough of that resource for our first item in the queue, diminish our priority.
|
||||
if (this.accounts[j][ress] >= this.queues[j].getNext().getCost()[ress])
|
||||
tempPrio[j] /= 2;
|
||||
|
||||
if (tempPrio[j])
|
||||
totalPriority += tempPrio[j];
|
||||
}
|
||||
else if (this.accounts[j][ress] > queueCost[ress])
|
||||
this.accounts[j][ress] = queueCost[ress];
|
||||
}
|
||||
// Now we allow resources to the accounts. We can at most allow "TempPriority/totalpriority*available"
|
||||
// But we'll sometimes allow less if that would overflow.
|
||||
for (var j in tempPrio)
|
||||
{
|
||||
// we'll add at much what can be allowed to this queue.
|
||||
var toAdd = Math.floor(availableRes[ress] * tempPrio[j]/totalPriority);
|
||||
var maxAdd = Math.min(maxNeed[j], toAdd);
|
||||
this.accounts[j][ress] += maxAdd;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have no available resources, see if we can't "compact" them in one queue.
|
||||
// compare queues 2 by 2, and if one with a higher priority could be completed by our amount, give it.
|
||||
// TODO: this isn't perfect compression.
|
||||
for (var j in this.queues)
|
||||
{
|
||||
if (this.queues[j].length() === 0 || this.queues[j].paused)
|
||||
continue;
|
||||
|
||||
var queue = this.queues[j];
|
||||
var queueCost = queue.maxAccountWanted(gameState, 0);
|
||||
if (this.accounts[j][ress] >= queueCost[ress])
|
||||
continue;
|
||||
|
||||
for (var i in this.queues)
|
||||
{
|
||||
if (i === j)
|
||||
continue;
|
||||
var otherQueue = this.queues[i];
|
||||
if (this.priorities[i] >= this.priorities[j] || otherQueue.switched !== 0)
|
||||
continue;
|
||||
if (this.accounts[j][ress] + this.accounts[i][ress] < queueCost[ress])
|
||||
continue;
|
||||
|
||||
var diff = queueCost[ress] - this.accounts[j][ress];
|
||||
this.accounts[j][ress] += diff;
|
||||
this.accounts[i][ress] -= diff;
|
||||
++otherQueue.switched;
|
||||
if (this.Config.debug)
|
||||
warn ("switching queue " + ress + " from " + i + " to " + j + " in amount " + diff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start the next item in the queue if we can afford it.
|
||||
for (var i in this.queueArrays)
|
||||
{
|
||||
var name = this.queueArrays[i][0];
|
||||
var queue = this.queueArrays[i][1];
|
||||
if (queue.length() > 0 && !queue.paused)
|
||||
{
|
||||
var item = queue.getNext();
|
||||
var total = new API3.Resources();
|
||||
total.add(this.accounts[name]);
|
||||
if (total.canAfford(item.getCost()))
|
||||
{
|
||||
if (item.canStart(gameState))
|
||||
{
|
||||
this.accounts[name].subtract(item.getCost());
|
||||
queue.startNext(gameState);
|
||||
queue.switched = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (queue.length() === 0)
|
||||
{
|
||||
this.accounts[name].reset();
|
||||
queue.switched = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.pauseQueue = function(queue, scrapAccounts)
|
||||
{
|
||||
if (this.queues[queue])
|
||||
{
|
||||
this.queues[queue].paused = true;
|
||||
if (scrapAccounts)
|
||||
this.accounts[queue].reset();
|
||||
}
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.unpauseQueue = function(queue)
|
||||
{
|
||||
if (this.queues[queue])
|
||||
this.queues[queue].paused = false;
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.pauseAll = function(scrapAccounts, but)
|
||||
{
|
||||
for (var p in this.queues)
|
||||
{
|
||||
if (p != but)
|
||||
{
|
||||
if (scrapAccounts)
|
||||
this.accounts[p].reset();
|
||||
this.queues[p].paused = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.unpauseAll = function(but)
|
||||
{
|
||||
for (var p in this.queues)
|
||||
if (p != but)
|
||||
this.queues[p].paused = false;
|
||||
};
|
||||
|
||||
|
||||
m.QueueManager.prototype.addQueue = function(queueName, priority)
|
||||
{
|
||||
if (this.queues[queueName] == undefined)
|
||||
{
|
||||
this.queues[queueName] = new m.Queue();
|
||||
this.priorities[queueName] = priority;
|
||||
this.accounts[queueName] = new API3.Resources();
|
||||
|
||||
var self = this;
|
||||
this.queueArrays = [];
|
||||
for (var p in this.queues)
|
||||
this.queueArrays.push([p,this.queues[p]]);
|
||||
this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
|
||||
}
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.removeQueue = function(queueName)
|
||||
{
|
||||
if (this.queues[queueName] !== undefined)
|
||||
{
|
||||
if (this.curItemQueue.indexOf(queueName) !== -1)
|
||||
this.curItemQueue.splice(this.curItemQueue.indexOf(queueName),1);
|
||||
delete this.queues[queueName];
|
||||
delete this.priorities[queueName];
|
||||
delete this.accounts[queueName];
|
||||
|
||||
var self = this;
|
||||
this.queueArrays = [];
|
||||
for (var p in this.queues)
|
||||
this.queueArrays.push([p,this.queues[p]]);
|
||||
this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
|
||||
}
|
||||
};
|
||||
|
||||
m.QueueManager.prototype.changePriority = function(queueName, newPriority)
|
||||
{
|
||||
var self = this;
|
||||
if (this.queues[queueName] !== undefined)
|
||||
this.priorities[queueName] = newPriority;
|
||||
this.queueArrays = [];
|
||||
for (var p in this.queues)
|
||||
this.queueArrays.push([p,this.queues[p]]);
|
||||
this.queueArrays.sort(function (a,b) { return (self.priorities[b[0]] - self.priorities[a[0]]) });
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
81
binaries/data/mods/public/simulation/ai/petra/queueplan--.js
Normal file
81
binaries/data/mods/public/simulation/ai/petra/queueplan--.js
Normal file
@ -0,0 +1,81 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
/*
|
||||
* Common functions and variables to all queue plans.
|
||||
* has a "--" suffix because it needs to be loaded before the other queueplan files.
|
||||
*/
|
||||
|
||||
m.QueuePlan = function(gameState, type, metadata)
|
||||
{
|
||||
this.type = gameState.applyCiv(type);
|
||||
this.metadata = metadata;
|
||||
|
||||
this.template = gameState.getTemplate(this.type);
|
||||
if (!this.template)
|
||||
{
|
||||
warn("Tried to add the inexisting template " + this.type + " to Petra. Please report this on the forums")
|
||||
return false;
|
||||
}
|
||||
this.ID = m.playerGlobals[PlayerID].uniqueIDBOPlans++;
|
||||
this.cost = new API3.Resources(this.template.cost());
|
||||
this.number = 1;
|
||||
|
||||
this.category = "";
|
||||
this.lastIsGo = undefined;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Check the content of this queue
|
||||
m.QueuePlan.prototype.isInvalid = function(gameState)
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
// if true, the queue manager will begin increasing this plan's account.
|
||||
m.QueuePlan.prototype.isGo = function(gameState)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
// can we start this plan immediately?
|
||||
m.QueuePlan.prototype.canStart = function(gameState)
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
// process the plan.
|
||||
m.QueuePlan.prototype.start = function(gameState)
|
||||
{
|
||||
// should call onStart.
|
||||
};
|
||||
|
||||
m.QueuePlan.prototype.getCost = function()
|
||||
{
|
||||
var costs = new API3.Resources();
|
||||
costs.add(this.cost);
|
||||
if (this.number !== 1)
|
||||
costs.multiply(this.number);
|
||||
return costs;
|
||||
};
|
||||
|
||||
// On Event functions.
|
||||
// Can be used to do some specific stuffs
|
||||
// Need to be updated to actually do something if you want them to.
|
||||
// this is called by "Start" if it succeeds.
|
||||
m.QueuePlan.prototype.onStart = function(gameState)
|
||||
{
|
||||
};
|
||||
|
||||
// This is called by "isGo()" if it becomes true while it was false.
|
||||
m.QueuePlan.prototype.onGo = function(gameState)
|
||||
{
|
||||
};
|
||||
|
||||
// This is called by "isGo()" if it becomes false while it was true.
|
||||
m.QueuePlan.prototype.onNotGo = function(gameState)
|
||||
{
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
@ -0,0 +1,262 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
// Defines a construction plan, ie a building.
|
||||
// We'll try to fing a good position if non has been provided
|
||||
|
||||
m.ConstructionPlan = function(gameState, type, metadata, position)
|
||||
{
|
||||
if (!m.QueuePlan.call(this, gameState, type, metadata))
|
||||
return false;
|
||||
|
||||
this.position = position ? position : 0;
|
||||
|
||||
this.category = "building";
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
m.ConstructionPlan.prototype = Object.create(m.QueuePlan.prototype);
|
||||
|
||||
// checks other than resource ones.
|
||||
// TODO: change this.
|
||||
// TODO: if there are specific requirements here, maybe try to do them?
|
||||
m.ConstructionPlan.prototype.canStart = function(gameState)
|
||||
{
|
||||
if (gameState.buildingsBuilt > 0) // do not start another building if already one this turn
|
||||
return false;
|
||||
|
||||
if (!this.isGo(gameState))
|
||||
return false;
|
||||
|
||||
// TODO: verify numeric limits etc
|
||||
if (this.template.requiredTech() && !gameState.isResearched(this.template.requiredTech()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var builders = gameState.findBuilders(this.type);
|
||||
|
||||
return (builders.length != 0);
|
||||
};
|
||||
|
||||
m.ConstructionPlan.prototype.start = function(gameState)
|
||||
{
|
||||
|
||||
var builders = gameState.findBuilders(this.type).toEntityArray();
|
||||
|
||||
// We don't care which builder we assign, since they won't actually
|
||||
// do the building themselves - all we care about is that there is
|
||||
// some unit that can start the foundation
|
||||
|
||||
var pos = this.findGoodPosition(gameState);
|
||||
if (!pos){
|
||||
if (this.template.hasClass("Naval"))
|
||||
gameState.ai.HQ.dockFailed = true;
|
||||
gameState.ai.HQ.stopBuilding.push(this.type);
|
||||
return;
|
||||
}
|
||||
this.buildingsBuilt++;
|
||||
|
||||
if (this.metadata === undefined)
|
||||
this.metadata = { "base": pos.base };
|
||||
else if (this.metadata.base === undefined)
|
||||
this.metadata.base = pos.base;
|
||||
|
||||
if (gameState.getTemplate(this.type).buildCategory() === "Dock")
|
||||
{
|
||||
for (var angle = 0; angle < Math.PI * 2; angle += Math.PI/4)
|
||||
builders[0].construct(this.type, pos.x, pos.z, angle, this.metadata);
|
||||
}
|
||||
else
|
||||
{
|
||||
// try with the lowest, move towards us unless we're same
|
||||
if (pos.x == pos.xx && pos.z == pos.zz)
|
||||
builders[0].construct(this.type, pos.x, pos.z, pos.angle, this.metadata);
|
||||
else
|
||||
{
|
||||
for (var step = 0; step <= 1; step += 0.2)
|
||||
builders[0].construct(this.type, (step*pos.x + (1-step)*pos.xx), (step*pos.z + (1-step)*pos.zz), pos.angle, this.metadata);
|
||||
}
|
||||
}
|
||||
this.onStart(gameState);
|
||||
};
|
||||
|
||||
m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
|
||||
{
|
||||
var template = gameState.getTemplate(this.type);
|
||||
|
||||
if (!this.position)
|
||||
{
|
||||
if (template.hasClass("CivCentre"))
|
||||
{
|
||||
if (this.metadata.type)
|
||||
var pos = gameState.ai.HQ.findEconomicCCLocation(gameState, this.metadata.type);
|
||||
else
|
||||
var pos = gameState.ai.HQ.findStrategicCCLocation(gameState);
|
||||
|
||||
if (pos)
|
||||
return { "x": pos[0], "z": pos[1], "angle": 3*Math.PI/4, "xx": pos[0], "zz": pos[1], "base": 0 };
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else if (template.hasClass("Tower") || template.hasClass("Fortress"))
|
||||
{
|
||||
var pos = gameState.ai.HQ.findDefensiveLocation(gameState, template);
|
||||
|
||||
if (pos)
|
||||
return { "x": pos[0], "z": pos[1], "angle": 3*Math.PI/4, "xx": pos[0], "zz": pos[1], "base": pos[2] };
|
||||
else
|
||||
{
|
||||
gameState.ai.HQ.stopBuilding.push(this.type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cellSize = gameState.cellSize; // size of each tile
|
||||
|
||||
// First, find all tiles that are far enough away from obstructions:
|
||||
|
||||
var obstructionMap = m.createObstructionMap(gameState, 0, template);
|
||||
if (template.buildCategory() !== "Dock")
|
||||
obstructionMap.expandInfluences();
|
||||
|
||||
//obstructionMap.dumpIm(template.buildCategory() + "_obstructions.png");
|
||||
|
||||
// Compute each tile's closeness to friendly structures:
|
||||
|
||||
var friendlyTiles = new API3.Map(gameState.sharedScript);
|
||||
|
||||
var alreadyHasHouses = false;
|
||||
|
||||
if (this.position) // If a position was specified then place the building as close to it as possible
|
||||
{
|
||||
var x = Math.floor(this.position[0] / cellSize);
|
||||
var z = Math.floor(this.position[1] / cellSize);
|
||||
friendlyTiles.addInfluence(x, z, 255);
|
||||
}
|
||||
else // No position was specified so try and find a sensible place to build
|
||||
{
|
||||
// give a small > 0 level as the result of addInfluence is constrained to be > 0
|
||||
if (this.metadata && this.metadata.base !== undefined)
|
||||
{
|
||||
var base = this.metadata.base;
|
||||
for (var j = 0; j < friendlyTiles.map.length; ++j)
|
||||
if (gameState.ai.HQ.basesMap.map[j] === base)
|
||||
friendlyTiles.map[j] = 30;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var j = 0; j < friendlyTiles.map.length; ++j)
|
||||
if (gameState.ai.HQ.basesMap.map[j] !== 0)
|
||||
friendlyTiles.map[j] = 30;
|
||||
}
|
||||
|
||||
gameState.getOwnStructures().forEach(function(ent) {
|
||||
var pos = ent.position();
|
||||
var x = Math.round(pos[0] / cellSize);
|
||||
var z = Math.round(pos[1] / cellSize);
|
||||
|
||||
if (ent.resourceDropsiteTypes() && ent.resourceDropsiteTypes().indexOf("food") !== -1)
|
||||
{
|
||||
if (template.hasClass("Field"))
|
||||
friendlyTiles.addInfluence(x, z, 20, 50);
|
||||
else // If this is not a field add a negative influence because we want to leave this area for fields
|
||||
friendlyTiles.addInfluence(x, z, 20, -20);
|
||||
}
|
||||
else if (template.hasClass("House"))
|
||||
{
|
||||
if (ent.hasClass("House"))
|
||||
{
|
||||
friendlyTiles.addInfluence(x, z, 15, 40); // houses are close to other houses
|
||||
alreadyHasHouses = true;
|
||||
}
|
||||
else
|
||||
friendlyTiles.addInfluence(x, z, 15, -40); // and further away from other stuffs
|
||||
}
|
||||
else if (template.hasClass("Farmstead") && !ent.hasClass("Field"))
|
||||
friendlyTiles.addInfluence(x, z, 25, -25); // move farmsteads away to make room.
|
||||
else if (template.hasClass("GarrisonFortress") && ent.genericName() == "House")
|
||||
friendlyTiles.addInfluence(x, z, 30, -50);
|
||||
else if (template.hasClass("Military"))
|
||||
friendlyTiles.addInfluence(x, z, 10, -40);
|
||||
});
|
||||
|
||||
if (template.hasClass("Farmstead"))
|
||||
{
|
||||
for (var j = 0; j < friendlyTiles.map.length; ++j)
|
||||
{
|
||||
var value = friendlyTiles.map[j] - (gameState.sharedScript.resourceMaps["wood"].map[j])/3;
|
||||
friendlyTiles.map[j] = value >= 0 ? value : 0;
|
||||
if (gameState.ai.HQ.borderMap.map[j] > 0)
|
||||
friendlyTiles.map[j] /= 2; // we need space around farmstead, so disfavor map border
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// requires to be inside our territory, and inside our base territory if required
|
||||
if (this.metadata && this.metadata.base !== undefined)
|
||||
{
|
||||
var base = this.metadata.base;
|
||||
for (var j = 0; j < friendlyTiles.map.length; ++j)
|
||||
if (gameState.ai.HQ.basesMap.map[j] !== base)
|
||||
friendlyTiles.map[j] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var j = 0; j < friendlyTiles.map.length; ++j)
|
||||
if (gameState.ai.HQ.basesMap.map[j] === 0)
|
||||
friendlyTiles.map[j] = 0;
|
||||
}
|
||||
|
||||
// Find target building's approximate obstruction radius, and expand by a bit to make sure we're not too close, this
|
||||
// allows room for units to walk between buildings.
|
||||
// note: not for houses and dropsites who ought to be closer to either each other or a resource.
|
||||
// also not for fields who can be stacked quite a bit
|
||||
|
||||
var radius = 0;
|
||||
if (template.hasClass("Fortress") || this.type == "structures/{civ}_siege_workshop")
|
||||
radius = Math.floor(template.obstructionRadius() / cellSize) + 3;
|
||||
else if (template.buildCategory() === "Dock")
|
||||
radius = 1;
|
||||
else if (template.resourceDropsiteTypes() === undefined)
|
||||
radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
|
||||
else
|
||||
radius = Math.ceil(template.obstructionRadius() / cellSize);
|
||||
|
||||
// Find the best non-obstructed
|
||||
if (template.hasClass("House") && !alreadyHasHouses)
|
||||
{
|
||||
// try to get some space first
|
||||
var bestTile = friendlyTiles.findBestTile(10, obstructionMap);
|
||||
var bestIdx = bestTile[0];
|
||||
var bestVal = bestTile[1];
|
||||
}
|
||||
|
||||
if (bestVal === undefined || bestVal === -1)
|
||||
{
|
||||
var bestTile = friendlyTiles.findBestTile(radius, obstructionMap);
|
||||
var bestIdx = bestTile[0];
|
||||
var bestVal = bestTile[1];
|
||||
}
|
||||
|
||||
if (bestVal <= 0)
|
||||
{
|
||||
gameState.ai.HQ.stopBuilding.push(this.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
var x = ((bestIdx % friendlyTiles.width) + 0.5) * cellSize;
|
||||
var z = (Math.floor(bestIdx / friendlyTiles.width) + 0.5) * cellSize;
|
||||
|
||||
if (template.hasClass("House") || template.hasClass("Field") || template.resourceDropsiteTypes() !== undefined)
|
||||
var secondBest = obstructionMap.findLowestNeighbor(x,z);
|
||||
else
|
||||
var secondBest = [x,z];
|
||||
|
||||
// default angle = 3*Math.PI/4;
|
||||
return { "x": x, "z": z, "angle": 3*Math.PI/4, "xx": secondBest[0], "zz": secondBest[1], "base": gameState.ai.HQ.basesMap.map[bestIdx] };
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
@ -0,0 +1,58 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
m.ResearchPlan = function(gameState, type, rush)
|
||||
{
|
||||
if (!m.QueuePlan.call(this, gameState, type, {}))
|
||||
return false;
|
||||
|
||||
if (this.template.researchTime === undefined)
|
||||
return false;
|
||||
|
||||
this.category = "technology";
|
||||
|
||||
this.rush = rush ? true : false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
m.ResearchPlan.prototype = Object.create(m.QueuePlan.prototype);
|
||||
|
||||
m.ResearchPlan.prototype.canStart = function(gameState)
|
||||
{
|
||||
// also checks canResearch
|
||||
return (gameState.findResearchers(this.type).length !== 0);
|
||||
};
|
||||
|
||||
m.ResearchPlan.prototype.isInvalid = function(gameState)
|
||||
{
|
||||
return (gameState.isResearched(this.type) || gameState.isResearching(this.type));
|
||||
};
|
||||
|
||||
m.ResearchPlan.prototype.start = function(gameState)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
//m.debug ("Starting the research plan for " + this.type);
|
||||
var trainers = gameState.findResearchers(this.type).toEntityArray();
|
||||
|
||||
//for (var i in trainers)
|
||||
// warn (this.type + " - " +trainers[i].genericName());
|
||||
|
||||
// Prefer training buildings with short queues
|
||||
// (TODO: this should also account for units added to the queue by
|
||||
// plans that have already been executed this turn)
|
||||
if (trainers.length > 0){
|
||||
trainers.sort(function(a, b) {
|
||||
return (a.trainingQueueTime() - b.trainingQueueTime());
|
||||
});
|
||||
// drop anything in the queue if we rush it.
|
||||
if (this.rush)
|
||||
trainers[0].stopAllProduction(0.45);
|
||||
trainers[0].research(this.type);
|
||||
}
|
||||
this.onStart(gameState);
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
@ -0,0 +1,72 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
m.TrainingPlan = function(gameState, type, metadata, number, maxMerge)
|
||||
{
|
||||
if (!m.QueuePlan.call(this, gameState, type, metadata))
|
||||
{
|
||||
warn(" Plan training " + type + " canceled");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.category = "unit";
|
||||
this.cost = new API3.Resources(this.template.cost(), this.template._template.Cost.Population);
|
||||
|
||||
this.number = number !== undefined ? number : 1;
|
||||
this.maxMerge = maxMerge !== undefined ? maxMerge : 5;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
m.TrainingPlan.prototype = Object.create(m.QueuePlan.prototype);
|
||||
|
||||
m.TrainingPlan.prototype.canStart = function(gameState)
|
||||
{
|
||||
if (this.invalidTemplate)
|
||||
return false;
|
||||
|
||||
// TODO: we should probably check pop caps
|
||||
|
||||
var trainers = gameState.findTrainers(this.type);
|
||||
|
||||
return (trainers.length != 0);
|
||||
};
|
||||
|
||||
m.TrainingPlan.prototype.start = function(gameState)
|
||||
{
|
||||
//warn("Executing TrainingPlan " + uneval(this));
|
||||
var self = this;
|
||||
var trainers = gameState.findTrainers(this.type).toEntityArray();
|
||||
|
||||
// Prefer training buildings with short queues
|
||||
// (TODO: this should also account for units added to the queue by
|
||||
// plans that have already been executed this turn)
|
||||
if (trainers.length > 0)
|
||||
{
|
||||
trainers.sort(function(a, b) {
|
||||
var aa = a.trainingQueueTime();
|
||||
var bb = b.trainingQueueTime();
|
||||
if (a.hasClass("Civic") && !self.template.hasClass("Support"))
|
||||
aa += 0.9;
|
||||
if (b.hasClass("Civic") && !self.template.hasClass("Support"))
|
||||
bb += 0.9;
|
||||
return (aa - bb);
|
||||
});
|
||||
if (this.metadata && this.metadata.base !== undefined && this.metadata.base === 0)
|
||||
this.metadata.base = trainers[0].getMetadata(PlayerID,"base");
|
||||
trainers[0].train(this.type, this.number, this.metadata);
|
||||
}
|
||||
else
|
||||
warn("pas de trainers for this queue " + this.type);
|
||||
this.onStart(gameState);
|
||||
};
|
||||
|
||||
m.TrainingPlan.prototype.addItem = function(amount)
|
||||
{
|
||||
if (amount === undefined)
|
||||
amount = 1;
|
||||
this.number += amount;
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
123
binaries/data/mods/public/simulation/ai/petra/templateManager.js
Normal file
123
binaries/data/mods/public/simulation/ai/petra/templateManager.js
Normal file
@ -0,0 +1,123 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
/*
|
||||
* Used to know which templates I have, which templates I know I can train, things like that.
|
||||
* Mostly unused.
|
||||
*/
|
||||
|
||||
m.TemplateManager = function(gameState) {
|
||||
var self = this;
|
||||
|
||||
this.knownTemplatesList = [];
|
||||
this.buildingTemplates = [];
|
||||
this.unitTemplates = [];
|
||||
this.templateCounters = {};
|
||||
this.templateCounteredBy = {};
|
||||
|
||||
// this will store templates that exist
|
||||
this.AcknowledgeTemplates(gameState);
|
||||
this.getBuildableSubtemplates(gameState);
|
||||
this.getTrainableSubtemplates(gameState);
|
||||
this.getBuildableSubtemplates(gameState);
|
||||
this.getTrainableSubtemplates(gameState);
|
||||
// should be enough in 100% of the cases.
|
||||
|
||||
this.getTemplateCounters(gameState);
|
||||
|
||||
};
|
||||
m.TemplateManager.prototype.AcknowledgeTemplates = function(gameState)
|
||||
{
|
||||
var self = this;
|
||||
var myEntities = gameState.getOwnEntities();
|
||||
myEntities.forEach(function(ent)
|
||||
{
|
||||
var template = ent._templateName;
|
||||
if (self.knownTemplatesList.indexOf(template) === -1) {
|
||||
self.knownTemplatesList.push(template);
|
||||
if (ent.hasClass("Unit") && self.unitTemplates.indexOf(template) === -1)
|
||||
self.unitTemplates.push(template);
|
||||
else if (self.buildingTemplates.indexOf(template) === -1)
|
||||
self.buildingTemplates.push(template);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
m.TemplateManager.prototype.getBuildableSubtemplates = function(gameState)
|
||||
{
|
||||
for each (var templateName in this.knownTemplatesList) {
|
||||
var template = gameState.getTemplate(templateName);
|
||||
if (template !== null) {
|
||||
var buildable = template.buildableEntities();
|
||||
if (buildable !== undefined)
|
||||
for each (var subtpname in buildable) {
|
||||
if (this.knownTemplatesList.indexOf(subtpname) === -1) {
|
||||
this.knownTemplatesList.push(subtpname);
|
||||
var subtemplate = gameState.getTemplate(subtpname);
|
||||
if (subtemplate.hasClass("Unit") && this.unitTemplates.indexOf(subtpname) === -1)
|
||||
this.unitTemplates.push(subtpname);
|
||||
else if (this.buildingTemplates.indexOf(subtpname) === -1)
|
||||
this.buildingTemplates.push(subtpname);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.TemplateManager.prototype.getTrainableSubtemplates = function(gameState)
|
||||
{
|
||||
for each (var templateName in this.knownTemplatesList) {
|
||||
var template = gameState.getTemplate(templateName);
|
||||
if (template !== null) {
|
||||
var trainables = template.trainableEntities();
|
||||
if (trainables !== undefined)
|
||||
for each (var subtpname in trainables) {
|
||||
if (this.knownTemplatesList.indexOf(subtpname) === -1) {
|
||||
this.knownTemplatesList.push(subtpname);
|
||||
var subtemplate = gameState.getTemplate(subtpname);
|
||||
if (subtemplate.hasClass("Unit") && this.unitTemplates.indexOf(subtpname) === -1)
|
||||
this.unitTemplates.push(subtpname);
|
||||
else if (this.buildingTemplates.indexOf(subtpname) === -1)
|
||||
this.buildingTemplates.push(subtpname);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.TemplateManager.prototype.getTemplateCounters = function(gameState)
|
||||
{
|
||||
for (var i in this.unitTemplates)
|
||||
{
|
||||
var tp = gameState.getTemplate(this.unitTemplates[i]);
|
||||
var tpname = this.unitTemplates[i];
|
||||
this.templateCounters[tpname] = tp.getCounteredClasses();
|
||||
}
|
||||
}
|
||||
// features auto-caching
|
||||
m.TemplateManager.prototype.getCountersToClasses = function(gameState,classes,templateName)
|
||||
{
|
||||
if (templateName !== undefined && this.templateCounteredBy[templateName])
|
||||
return this.templateCounteredBy[templateName];
|
||||
|
||||
var templates = [];
|
||||
for (var i in this.templateCounters) {
|
||||
var okay = false;
|
||||
for each (var ticket in this.templateCounters[i]) {
|
||||
var okaya = true;
|
||||
for (var a in ticket[0]) {
|
||||
if (classes.indexOf(ticket[0][a]) === -1)
|
||||
okaya = false;
|
||||
}
|
||||
if (okaya && templates.indexOf(i) === -1)
|
||||
templates.push([i, ticket[1]]);
|
||||
}
|
||||
}
|
||||
templates.sort (function (a,b) { return -a[1] + b[1]; });
|
||||
|
||||
if (templateName !== undefined)
|
||||
this.templateCounteredBy[templateName] = templates;
|
||||
return templates;
|
||||
}
|
||||
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
109
binaries/data/mods/public/simulation/ai/petra/tradeManager.js
Normal file
109
binaries/data/mods/public/simulation/ai/petra/tradeManager.js
Normal file
@ -0,0 +1,109 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
/**
|
||||
* Manage the trade
|
||||
*/
|
||||
|
||||
m.TradeManager = function(Config)
|
||||
{
|
||||
this.Config = Config;
|
||||
this.tradeRoute = undefined;
|
||||
this.targetNumTraders = this.Config.Economy.targetNumTraders;
|
||||
};
|
||||
|
||||
m.TradeManager.prototype.init = function(gameState)
|
||||
{
|
||||
this.traders = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "role", "trader"));
|
||||
this.traders.allowQuickIter();
|
||||
this.traders.registerUpdates();
|
||||
};
|
||||
|
||||
m.TradeManager.prototype.setTradeRoute = function(market1, market2)
|
||||
{
|
||||
this.tradeRoute = { "source": market1, "target": market2 };
|
||||
};
|
||||
|
||||
m.TradeManager.prototype.hasTradeRoute = function()
|
||||
{
|
||||
return (this.tradeRoute !== undefined);
|
||||
};
|
||||
|
||||
m.TradeManager.prototype.assignTrader = function(ent)
|
||||
{
|
||||
unit.setMetadata(PlayerID, "role", "trader");
|
||||
this.traders.updateEnt(unit);
|
||||
};
|
||||
|
||||
// TODO take trader ships into account
|
||||
m.TradeManager.prototype.trainMoreTraders = function(gameState, queues)
|
||||
{
|
||||
var numTraders = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("units/{civ}_support_trader"), true);
|
||||
if (numTraders < this.targetNumTraders && queues.trader.countQueuedUnits() === 0)
|
||||
{
|
||||
var template = gameState.applyCiv("units/{civ}_support_trader")
|
||||
queues.trader.addItem(new m.TrainingPlan(gameState, template, { "role": "trader", "base": 0 }, 1, 1));
|
||||
}
|
||||
};
|
||||
|
||||
m.TradeManager.prototype.update = function(gameState, queues)
|
||||
{
|
||||
if (!this.tradeRoute)
|
||||
return;
|
||||
var self = this;
|
||||
if (gameState.ai.playedTurn % 100 === 9)
|
||||
this.setTradingGoods(gameState);
|
||||
this.trainMoreTraders(gameState, queues);
|
||||
this.traders.forEach(function(ent) { self.updateTrader(ent) });
|
||||
};
|
||||
|
||||
// TODO deal with garrisoned trader & check if the trade route (i.e. its markets) still exist
|
||||
m.TradeManager.prototype.updateTrader = function(ent)
|
||||
{
|
||||
if (!ent.isIdle() || !ent.position())
|
||||
return;
|
||||
|
||||
if (API3.SquareVectorDistance(this.tradeRoute.target.position(), ent.position()) > API3.SquareVectorDistance(this.tradeRoute.source.position(), ent.position()))
|
||||
ent.tradeRoute(this.tradeRoute.target, this.tradeRoute.source);
|
||||
else
|
||||
ent.tradeRoute(this.tradeRoute.source, this.tradeRoute.target);
|
||||
};
|
||||
|
||||
m.TradeManager.prototype.setTradingGoods = function(gameState)
|
||||
{
|
||||
var tradingGoods = { "food": 0, "wood": 0, "stone": 0, "metal": 0 };
|
||||
// first, try to anticipate future needs
|
||||
var stocks = gameState.ai.HQ.GetTotalResourceLevel(gameState);
|
||||
var remaining = 100;
|
||||
this.targetNumTraders = this.Config.Economy.targetNumTraders;
|
||||
for (var type in stocks)
|
||||
{
|
||||
if (type == "food")
|
||||
continue;
|
||||
if (stocks[type] < 200)
|
||||
{
|
||||
tradingGoods[type] = 20;
|
||||
this.targetNumTraders += 2;
|
||||
}
|
||||
else if (stocks[type] < 500)
|
||||
{
|
||||
tradingGoods[type] = 10;
|
||||
this.targetNumTraders += 1;
|
||||
}
|
||||
remaining -= tradingGoods[type];
|
||||
}
|
||||
|
||||
// then add what is needed now
|
||||
var mainNeed = Math.floor(remaining * 70 / 100)
|
||||
var nextNeed = remaining - mainNeed;
|
||||
|
||||
var mostNeeded = gameState.ai.HQ.pickMostNeededResources(gameState);
|
||||
tradingGoods[mostNeeded[0]] += mainNeed;
|
||||
tradingGoods[mostNeeded[1]] += nextNeed;
|
||||
Engine.PostCommand(PlayerID, {"type": "set-trading-goods", "tradingGoods": tradingGoods});
|
||||
if (this.Config.debug == 2)
|
||||
warn(" trading goods set to " + uneval(tradingGoods));
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
@ -0,0 +1,32 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
m.AssocArraytoArray = function(assocArray) {
|
||||
var endArray = [];
|
||||
for (var i in assocArray)
|
||||
endArray.push(assocArray[i]);
|
||||
return endArray;
|
||||
};
|
||||
|
||||
// A is the reference, B must be in "range" of A
|
||||
// this supposes the range is already squared
|
||||
m.inRange = function(a, b, range)// checks for X distance
|
||||
{
|
||||
// will avoid unnecessary checking for position in some rare cases... I'm lazy
|
||||
if (a === undefined || b === undefined || range === undefined)
|
||||
return undefined;
|
||||
|
||||
var dx = a[0] - b[0];
|
||||
var dz = a[1] - b[1];
|
||||
return ((dx*dx + dz*dz ) < range);
|
||||
}
|
||||
// slower than SquareVectorDistance, faster than VectorDistance but not exactly accurate.
|
||||
m.ManhattanDistance = function(a, b)
|
||||
{
|
||||
var dx = a[0] - b[0];
|
||||
var dz = a[1] - b[1];
|
||||
return Math.abs(dx) + Math.abs(dz);
|
||||
}
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
520
binaries/data/mods/public/simulation/ai/petra/worker.js
Normal file
520
binaries/data/mods/public/simulation/ai/petra/worker.js
Normal file
@ -0,0 +1,520 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
/**
|
||||
* This class makes a worker do as instructed by the economy manager
|
||||
*/
|
||||
|
||||
m.Worker = function(ent) {
|
||||
this.ent = ent;
|
||||
this.baseID = 0;
|
||||
this.lastUpdate = undefined;
|
||||
};
|
||||
|
||||
m.Worker.prototype.update = function(baseManager, gameState) {
|
||||
this.lastUpdate = gameState.ai.playedTurn;
|
||||
this.baseID = baseManager.ID;
|
||||
var subrole = this.ent.getMetadata(PlayerID, "subrole");
|
||||
|
||||
if (!this.ent.position() || (this.ent.getMetadata(PlayerID,"fleeing") && gameState.getTimeElapsed() - this.ent.getMetadata(PlayerID,"fleeing") < 8000)){
|
||||
// If the worker has no position then no work can be done
|
||||
return;
|
||||
}
|
||||
if (this.ent.getMetadata(PlayerID,"fleeing"))
|
||||
this.ent.setMetadata(PlayerID,"fleeing", undefined);
|
||||
|
||||
// Okay so we have a few tasks.
|
||||
// If we're gathering, we'll check that we haven't run idle.
|
||||
// ANd we'll also check that we're gathering a resource we want to gather.
|
||||
|
||||
// If we're fighting or hunting, let's not start gathering, heh?
|
||||
if (this.ent.unitAIState().split(".")[1] === "COMBAT" || this.ent.getMetadata(PlayerID, "role") === "transport")
|
||||
return;
|
||||
|
||||
/* if (this.ent.unitAIState() == "INDIVIDUAL.IDLE" && subrole != "hunter")
|
||||
{
|
||||
var role = this.ent.getMetadata(PlayerID, "role");
|
||||
var base = this.ent.getMetadata(PlayerID, "base");
|
||||
var founda = this.ent.getMetadata(PlayerID, "target-foundation");
|
||||
warn(" unit idle " + this.ent.id() + " role " + role + " subrole " + subrole + " base " + base + " founda " + founda);
|
||||
} */
|
||||
|
||||
if (subrole === "gatherer")
|
||||
{
|
||||
if (this.ent.isIdle())
|
||||
{
|
||||
// if we aren't storing resources or it's the same type as what we're about to gather,
|
||||
// let's just pick a new resource.
|
||||
// TODO if we already carry the max we can -> returnresources
|
||||
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 ||
|
||||
this.ent.resourceCarrying()[0].type === this.ent.getMetadata(PlayerID, "gather-type"))
|
||||
{
|
||||
this.startGathering(gameState, baseManager);
|
||||
}
|
||||
else if (!this.returnResources(gameState)) // try to deposit resources
|
||||
{
|
||||
// no dropsite, abandon old resources and start gathering new ones
|
||||
this.startGathering(gameState, baseManager);
|
||||
}
|
||||
}
|
||||
else if (this.ent.unitAIState().split(".")[1] === "GATHER")
|
||||
{
|
||||
// we're already gathering. But let's check from time to time if there is nothing better
|
||||
// in case UnitAI did something bad
|
||||
if (this.ent.unitAIOrderData().length)
|
||||
{
|
||||
var supplyId = this.ent.unitAIOrderData()[0]["target"];
|
||||
var supply = gameState.getEntityById(supplyId);
|
||||
if (supply && !supply.hasClass("Field") && !supply.hasClass("Animal")
|
||||
&& supplyId !== this.ent.getMetadata(PlayerID, "supply"))
|
||||
{
|
||||
var nbGatherers = supply.resourceSupplyGatherers().length
|
||||
+ m.GetTCGatherer(gameState, supplyId);
|
||||
if ((nbGatherers > 0 && supply.resourceSupplyAmount()/nbGatherers < 40))
|
||||
{
|
||||
m.RemoveTCGatherer(gameState, supplyId);
|
||||
this.startGathering(gameState, baseManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
var gatherType = this.ent.getMetadata(PlayerID, "gather-type");
|
||||
var nearby = baseManager.dropsiteSupplies[gatherType]["nearby"];
|
||||
var isNearby = nearby.some(function(sup) {
|
||||
if (sup.id === supplyId)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
if (nearby.length === 0 || isNearby)
|
||||
this.ent.setMetadata(PlayerID, "supply", supplyId);
|
||||
else
|
||||
{
|
||||
m.RemoveTCGatherer(gameState, supplyId);
|
||||
this.startGathering(gameState, baseManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (subrole === "builder")
|
||||
{
|
||||
if (this.ent.unitAIState().split(".")[1] === "REPAIR")
|
||||
return;
|
||||
// okay so apparently we aren't working.
|
||||
// Unless we've been explicitely told to keep our role, make us idle.
|
||||
var target = gameState.getEntityById(this.ent.getMetadata(PlayerID, "target-foundation"));
|
||||
if (!target || (target.foundationProgress() === undefined && target.needsRepair() === false))
|
||||
{
|
||||
this.ent.setMetadata(PlayerID, "subrole", "idle");
|
||||
this.ent.setMetadata(PlayerID, "target-foundation", undefined);
|
||||
// If worker elephant, move away to avoid being trapped in between constructions
|
||||
if (this.ent.hasClass("Elephant"))
|
||||
this.moveAway(baseManager, gameState);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (target && target.foundationProgress() === undefined && target.needsRepair() === true)
|
||||
warn(" target " + target.id() + " needs repair " + target.hitpoints() + " max " + target.maxHitpoints());
|
||||
this.ent.repair(target);
|
||||
}
|
||||
}
|
||||
else if (subrole === "hunter")
|
||||
{
|
||||
if (this.ent.isIdle())
|
||||
this.startHunting(gameState, baseManager);
|
||||
}
|
||||
};
|
||||
|
||||
m.Worker.prototype.startGathering = function(gameState, baseManager)
|
||||
{
|
||||
if (!this.ent.position()) // TODO: work out what to do when entity has no position
|
||||
return false;
|
||||
|
||||
var resource = this.ent.getMetadata(PlayerID, "gather-type");
|
||||
|
||||
// Then if we are gathering food, try to hunt
|
||||
if (resource === "food" && this.startHunting(gameState, baseManager))
|
||||
return true;
|
||||
|
||||
var foundSupply = function(ent, supply) {
|
||||
var ret = false;
|
||||
for (var i = 0; i < supply.length; ++i)
|
||||
{
|
||||
// exhausted resource, remove it from this list
|
||||
if (!supply[i].ent || !gameState.getEntityById(supply[i].id))
|
||||
{
|
||||
supply.splice(i--, 1);
|
||||
continue;
|
||||
}
|
||||
if (m.IsSupplyFull(gameState, supply[i].ent) === true)
|
||||
continue;
|
||||
// check if available resource is worth one additionnal gatherer (except for farms)
|
||||
var nbGatherers = supply[i].ent.resourceSupplyGatherers().length
|
||||
+ m.GetTCGatherer(gameState, supply[i].id);
|
||||
if (supply[i].ent.resourceSupplyType()["specific"] !== "grain"
|
||||
&& nbGatherers > 0 && supply[i].ent.resourceSupplyAmount()/(1+nbGatherers) < 40)
|
||||
continue;
|
||||
// not in ennemy territory
|
||||
var territoryOwner = m.createTerritoryMap(gameState).getOwner(supply[i].ent.position());
|
||||
if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner)) // player is its own ally
|
||||
continue;
|
||||
m.AddTCGatherer(gameState, supply[i].id);
|
||||
ent.gather(supply[i].ent);
|
||||
ent.setMetadata(PlayerID, "supply", supply[i].id);
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
var nearby = baseManager.dropsiteSupplies[resource]["nearby"];
|
||||
if (foundSupply(this.ent, nearby))
|
||||
return true;
|
||||
|
||||
// --> for food, try to gather from fields if any, otherwise build one if any
|
||||
if (resource === "food" && (this.gatherNearestField(gameState) || this.buildAnyField(gameState)))
|
||||
return true;
|
||||
|
||||
var medium = baseManager.dropsiteSupplies[resource]["medium"];
|
||||
if (foundSupply(this.ent, medium))
|
||||
return true;
|
||||
|
||||
// So if we're here we have checked our whole base for a proper resource without success
|
||||
// --> check other bases before going back to faraway resources
|
||||
for each (var base in gameState.ai.HQ.baseManagers)
|
||||
{
|
||||
if (base.ID === this.baseID)
|
||||
continue;
|
||||
var nearby = base.dropsiteSupplies[resource]["nearby"];
|
||||
if (foundSupply(this.ent, nearby))
|
||||
{
|
||||
this.ent.setMetadata(PlayerID, "base", base.ID);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for each (var base in gameState.ai.HQ.baseManagers)
|
||||
{
|
||||
if (base.ID === this.baseID)
|
||||
continue;
|
||||
var medium = base.dropsiteSupplies[resource]["medium"];
|
||||
if (foundSupply(this.ent, medium))
|
||||
{
|
||||
this.ent.setMetadata(PlayerID, "base", base.ID);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Okay so we haven't found any appropriate dropsite anywhere.
|
||||
// Try to help building one if any foundation available in the same base
|
||||
var self = this;
|
||||
var foundations = gameState.getOwnFoundations().toEntityArray();
|
||||
var shouldBuild = foundations.some(function(foundation) {
|
||||
if (!foundation || foundation.getMetadata(PlayerID, "base") != self.baseID)
|
||||
return false;
|
||||
if ((resource !== "food" && foundation.hasClass("Storehouse")) ||
|
||||
(resource === "food" && foundation.hasClass("DropsiteFood")))
|
||||
{
|
||||
self.ent.repair(foundation);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (shouldBuild)
|
||||
return true;
|
||||
|
||||
// Still nothing, we look now for faraway resources, first in this base, then in others
|
||||
var faraway = baseManager.dropsiteSupplies[resource]["faraway"];
|
||||
if (foundSupply(this.ent, faraway))
|
||||
return true;
|
||||
for each (var base in gameState.ai.HQ.baseManagers)
|
||||
{
|
||||
if (base.ID === this.baseID)
|
||||
continue;
|
||||
var faraway = base.dropsiteSupplies[resource]["faraway"];
|
||||
if (foundSupply(this.ent, faraway))
|
||||
{
|
||||
this.ent.setMetadata(PlayerID, "base", base.ID);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Makes the worker deposit the currently carried resources at the closest dropsite
|
||||
m.Worker.prototype.returnResources = function(gameState)
|
||||
{
|
||||
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 || !this.ent.position())
|
||||
return false;
|
||||
|
||||
var resource = this.ent.resourceCarrying()[0].type;
|
||||
var self = this;
|
||||
|
||||
var closestDropsite = undefined;
|
||||
var dist = Math.min();
|
||||
gameState.getOwnDropsites(resource).forEach(function(dropsite){
|
||||
if (dropsite.position())
|
||||
{
|
||||
var d = API3.SquareVectorDistance(self.ent.position(), dropsite.position());
|
||||
if (d < dist)
|
||||
{
|
||||
dist = d;
|
||||
closestDropsite = dropsite;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!closestDropsite)
|
||||
return false;
|
||||
this.ent.returnResources(closestDropsite);
|
||||
return true;
|
||||
};
|
||||
|
||||
m.Worker.prototype.startHunting = function(gameState, baseManager)
|
||||
{
|
||||
if (!this.ent.position())
|
||||
return false;
|
||||
|
||||
// So here we're doing it basic. We check what we can hunt, we hunt it. No fancies.
|
||||
|
||||
var resources = gameState.getHuntableSupplies();
|
||||
if (resources.length === 0)
|
||||
return false;
|
||||
|
||||
var nearestSupplyDist = Math.min();
|
||||
var nearestSupply = undefined;
|
||||
|
||||
var isCavalry = this.ent.hasClass("Cavalry");
|
||||
var isRanged = this.ent.hasClass("Ranged");
|
||||
var entPosition = this.ent.position();
|
||||
|
||||
var nearestDropsiteDist = function(supply){
|
||||
var distMin = 1000000;
|
||||
var pos = supply.position();
|
||||
gameState.getOwnDropsites("food").forEach(function (dropsite){
|
||||
if (!dropsite.position())
|
||||
return;
|
||||
var dist = API3.SquareVectorDistance(pos, dropsite.position());
|
||||
if (dist < distMin)
|
||||
distMin = dist;
|
||||
});
|
||||
return distMin;
|
||||
};
|
||||
|
||||
resources.forEach(function(supply)
|
||||
{
|
||||
if (!supply.position())
|
||||
return;
|
||||
|
||||
if (supply.getMetadata(PlayerID, "inaccessible") === true)
|
||||
return;
|
||||
|
||||
if (m.IsSupplyFull(gameState, supply) === true)
|
||||
return;
|
||||
// check if available resource is worth one additionnal gatherer (except for farms)
|
||||
var nbGatherers = supply.resourceSupplyGatherers().length
|
||||
+ m.GetTCGatherer(gameState, supply.id());
|
||||
if (nbGatherers > 0 && supply.resourceSupplyAmount()/(1+nbGatherers) < 40)
|
||||
return;
|
||||
|
||||
// Only cavalry and range units should hunt fleeing animals
|
||||
if (!supply.hasClass("Domestic") && !isCavalry && !isRanged)
|
||||
return;
|
||||
|
||||
// quickscope accessbility check
|
||||
if (!gameState.ai.accessibility.pathAvailable(gameState, entPosition, supply.position(),false, true))
|
||||
return;
|
||||
|
||||
// measure the distance to the resource
|
||||
var dist = API3.SquareVectorDistance(entPosition, supply.position());
|
||||
// Only cavalry should hunt faraway
|
||||
if (!isCavalry && dist > 25000)
|
||||
return;
|
||||
|
||||
// some simple check for chickens: if they're in a inaccessible square, we won't gather from them.
|
||||
// TODO: make sure this works with rounding.
|
||||
if (supply.footprintRadius() < 1)
|
||||
{
|
||||
var fakeMap = new API3.Map(gameState.sharedScript, gameState.getMap().data);
|
||||
var id = fakeMap.gamePosToMapPos(supply.position())[0] + fakeMap.width*fakeMap.gamePosToMapPos(supply.position())[1];
|
||||
if ((gameState.sharedScript.passabilityClasses["pathfinderObstruction"] & gameState.getMap().data[id]))
|
||||
{
|
||||
supply.setMetadata(PlayerID, "inaccessible", true)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid ennemy territory
|
||||
var territoryOwner = m.createTerritoryMap(gameState).getOwner(supply.position());
|
||||
if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner)) // player is its own ally
|
||||
return;
|
||||
|
||||
var dropsiteDist = nearestDropsiteDist(supply);
|
||||
if (dropsiteDist > 35000)
|
||||
return;
|
||||
// Only cavalry should hunt faraway (specially for non domestic animals which flee)
|
||||
if (!isCavalry && (dropsiteDist > 10000 || ((dropsiteDist > 7000 || territoryOwner == 0 ) && !supply.hasClass("Domestic"))))
|
||||
return;
|
||||
|
||||
if (dist < nearestSupplyDist)
|
||||
{
|
||||
nearestSupplyDist = dist;
|
||||
nearestSupply = supply;
|
||||
}
|
||||
});
|
||||
|
||||
if (nearestSupply)
|
||||
{
|
||||
m.AddTCGatherer(gameState, nearestSupply.id());
|
||||
this.ent.gather(nearestSupply);
|
||||
this.ent.setMetadata(PlayerID, "supply", nearestSupply.id());
|
||||
this.ent.setMetadata(PlayerID, "target-foundation", undefined);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.ent.getMetadata(PlayerID,"subrole") === "hunter")
|
||||
this.ent.setMetadata(PlayerID, "subrole", "idle");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
m.Worker.prototype.getResourceType = function(type){
|
||||
if (!type || !type.generic)
|
||||
return undefined;
|
||||
|
||||
if (type.generic === "treasure")
|
||||
return type.specific;
|
||||
else
|
||||
return type.generic;
|
||||
};
|
||||
|
||||
m.Worker.prototype.getGatherRate = function(gameState) {
|
||||
if (this.ent.getMetadata(PlayerID,"subrole") !== "gatherer")
|
||||
return 0;
|
||||
var rates = this.ent.resourceGatherRates();
|
||||
|
||||
if (this.ent.unitAIOrderData().length && this.ent.unitAIState().split(".")[1] === "GATHER" && this.ent.unitAIOrderData()[0]["target"])
|
||||
{
|
||||
var ress = gameState.getEntityById(this.ent.unitAIOrderData()[0]["target"]);
|
||||
if (!ress)
|
||||
return 0;
|
||||
var type = ress.resourceSupplyType();
|
||||
if (type.generic == "treasure")
|
||||
return 1000;
|
||||
var tstring = type.generic + "." + type.specific;
|
||||
//m.debug (+rates[tstring] + " for " + tstring + " for " + this.ent._templateName);
|
||||
if (rates[tstring])
|
||||
return rates[tstring];
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
m.Worker.prototype.gatherNearestField = function(gameState){
|
||||
if (!this.ent.position())
|
||||
return false;
|
||||
|
||||
var self = this;
|
||||
var ownFields = gameState.getOwnEntitiesByType(gameState.applyCiv("structures/{civ}_field"), true);
|
||||
var bestFarmEnt = undefined;
|
||||
var bestFarmDist = 10000000;
|
||||
|
||||
ownFields.forEach(function (field) {
|
||||
if (m.IsSupplyFull(gameState, field) === true)
|
||||
return;
|
||||
var dist = API3.SquareVectorDistance(field.position(), self.ent.position());
|
||||
if (dist < bestFarmDist)
|
||||
{
|
||||
bestFarmEnt = field;
|
||||
bestFarmDist = dist;
|
||||
}
|
||||
});
|
||||
if (bestFarmEnt !== undefined)
|
||||
{
|
||||
this.ent.setMetadata(PlayerID, "base", bestFarmEnt.getMetadata(PlayerID, "base"));
|
||||
m.AddTCGatherer(gameState, bestFarmEnt.id());
|
||||
this.ent.gather(bestFarmEnt);
|
||||
this.ent.setMetadata(PlayerID, "supply", bestFarmEnt.id());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* WARNING with the present options of AI orders, the unit will not gather after building the farm.
|
||||
* This is done by calling the gatherNearestField function when construction is completed.
|
||||
*/
|
||||
m.Worker.prototype.buildAnyField = function(gameState){
|
||||
var self = this;
|
||||
var foundations = gameState.getOwnFoundations();
|
||||
var baseFoundations = foundations.filter(API3.Filters.byMetadata(PlayerID, "base", this.baseID));
|
||||
|
||||
var maxGatherers = gameState.getTemplate(gameState.applyCiv("structures/{civ}_field")).maxGatherers();
|
||||
|
||||
var bestFarmEnt = undefined;
|
||||
var bestFarmDist = 10000000;
|
||||
baseFoundations.forEach(function (found) {
|
||||
if (found.hasClass("Field")) {
|
||||
var current = found.getBuildersNb();
|
||||
if (current === undefined || current >= maxGatherers)
|
||||
return;
|
||||
var dist = API3.SquareVectorDistance(found.position(), self.ent.position());
|
||||
if (dist < bestFarmDist)
|
||||
{
|
||||
bestFarmEnt = found;
|
||||
bestFarmDist = dist;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (bestFarmEnt !== undefined)
|
||||
{
|
||||
this.ent.repair(bestFarmEnt);
|
||||
return true;
|
||||
}
|
||||
// No farms found, search in other bases
|
||||
foundations.forEach(function (found) {
|
||||
if (found.hasClass("Field")) {
|
||||
var current = found.getBuildersNb();
|
||||
if (current === undefined || current >= maxGatherers)
|
||||
return;
|
||||
var dist = API3.SquareVectorDistance(found.position(), self.ent.position());
|
||||
if (dist < bestFarmDist)
|
||||
{
|
||||
bestFarmEnt = found;
|
||||
bestFarmDist = dist;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (bestFarmEnt !== undefined)
|
||||
{
|
||||
this.ent.repair(bestFarmEnt);
|
||||
this.ent.setMetadata(PlayerID, "base", bestFarmEnt.getMetadata(PlayerID, "base"));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Workers elephant should move away from the buildings they've built to avoid being trapped in between constructions
|
||||
// For the time being, we move towards the nearest gatherer (providing him a dropsite)
|
||||
m.Worker.prototype.moveAway = function(baseManager, gameState){
|
||||
var gatherers = baseManager.workersBySubrole(gameState, "gatherer").toEntityArray();
|
||||
var pos = this.ent.position();
|
||||
var dist = Math.min();
|
||||
var destination = pos;
|
||||
for (var i = 0; i < gatherers.length; ++i)
|
||||
{
|
||||
if (gatherers[i].isIdle())
|
||||
continue;
|
||||
var distance = API3.SquareVectorDistance(pos, gatherers[i].position());
|
||||
if (distance > dist)
|
||||
continue;
|
||||
dist = distance;
|
||||
destination = gatherers[i].position();
|
||||
}
|
||||
this.ent.move(destination[0], destination[1]);
|
||||
};
|
||||
|
||||
return m;
|
||||
}(PETRA);
|
Loading…
Reference in New Issue
Block a user