forked from 0ad/0ad
Clean-up of Aegis scripts. Most deprecated functions have been removed, some comments updated. Also makes resource maps 8-bit to save a little memory.
Fix the placement of the "civ info" button to avoid overlapping on 1024*768. This was SVN commit r13298.
This commit is contained in:
parent
d70db48173
commit
d709fe50ed
@ -66,7 +66,7 @@
|
||||
type="button"
|
||||
sprite="iconInfoGold"
|
||||
sprite_over="iconInfoWhite"
|
||||
size="85%-26 0 85%-10 16"
|
||||
size="85%-8 0 85%+8 16"
|
||||
tooltip_style="onscreenToolTip"
|
||||
tooltip="View civilization info"
|
||||
>
|
||||
|
@ -205,14 +205,13 @@ SharedScript.prototype.ApplyEntitiesDelta = function(state)
|
||||
}
|
||||
else if (evt.type == "Destroy")
|
||||
{
|
||||
// A small warning: javascript "delete" does not actually delete, it only removes the reference in this object/
|
||||
// A small warning: javascript "delete" does not actually delete, it only removes the reference in this object.
|
||||
// the "deleted" object remains in memory, and any older reference to it will still reference it as if it were not "deleted".
|
||||
// Worse, they might prevent it from being garbage collected, thus making it stay alive and consuming ram needlessly.
|
||||
// So take care, and if you encounter a weird bug with deletion not appearing to work correctly, this is probably why.
|
||||
|
||||
if (!this._entities[evt.msg.entity])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// The entity was destroyed but its data may still be useful, so
|
||||
// remember the entity and this AI's metadata concerning it
|
||||
@ -221,8 +220,6 @@ SharedScript.prototype.ApplyEntitiesDelta = function(state)
|
||||
for (i in this._players)
|
||||
evt.msg.metadata[this._players[i]] = this._entityMetadata[this._players[i]][evt.msg.entity];
|
||||
|
||||
//evt.msg.metadata[this._player] = this._entityMetadata[evt.msg.entity];
|
||||
|
||||
for each (var entCol in this._entityCollections)
|
||||
{
|
||||
entCol.removeEnt(this._entities[evt.msg.entity]);
|
||||
|
@ -1,4 +1,11 @@
|
||||
// basically an attack plan. The name is an artifact.
|
||||
/* This is an attack plan (despite the name, it's a relic of older times).
|
||||
* It deals with everything in an attack, from picking a target to picking a path to it
|
||||
* To making sure units rae built, and pushing elements to the queue manager otherwise
|
||||
* It also handles the actual attack, though much work is needed on that.
|
||||
* These should be extremely flexible with only minimal work.
|
||||
* There is a basic support for naval expeditions here.
|
||||
*/
|
||||
|
||||
function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , targetFinder) {
|
||||
|
||||
//This is the list of IDs of the units in the plan
|
||||
@ -53,10 +60,11 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
|
||||
this.onArrivalReaction = "proceedOnTargets";
|
||||
|
||||
// priority is relative. If all are 0, the only relevant criteria is "currentsize/targetsize".
|
||||
// if not, this is a "bonus". The higher the priority, the more this unit will get built.
|
||||
// if not, this is a "bonus". The higher the priority, the faster this unit will get built.
|
||||
// Should really be clamped to [0.1-1.5] (assuming 1 is default/the norm)
|
||||
// Eg: if all are priority 1, and the siege is 0.5, the siege units will get built
|
||||
// only once every other category is at least 50% of its target size.
|
||||
// note: siege build order is currently added by the military manager if a fortress is there.
|
||||
this.unitStat = {};
|
||||
this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 4, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Ranged"],
|
||||
"interests" : [ ["canGather", 2], ["strength",2], ["cost",1] ], "templates" : [] };
|
||||
@ -80,6 +88,7 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
|
||||
} else if (type === "superSized") {
|
||||
// our first attack has started worst case at the 14th minute, we want to attack another time by the 21th minute, so we rock 6.5 minutes
|
||||
this.maxPreparationTime = 480000;
|
||||
// basically we want a mix of citizen soldiers so our barracks have a purpose, and champion units.
|
||||
this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 20, "batchSize" : 5, "classes" : ["Infantry","Ranged", "CitizenSoldier"],
|
||||
"interests" : [["strength",3], ["cost",1] ], "templates" : [] };
|
||||
this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 20, "batchSize" : 5, "classes" : ["Infantry","Melee", "CitizenSoldier" ],
|
||||
@ -356,9 +365,9 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
|
||||
// Thus I will not do everything at once.
|
||||
// It will probably carry over a few turns but that's no issue.
|
||||
if (this.path === undefined)
|
||||
this.path = this.pathFinder.getPath(this.rallyPoint,this.targetPos, this.pathSampling, this.pathWidth,250);//,gameState);
|
||||
this.path = this.pathFinder.getPath(this.rallyPoint,this.targetPos, this.pathSampling, this.pathWidth,250,gameState);
|
||||
else if (this.path === "toBeContinued")
|
||||
this.path = this.pathFinder.continuePath();//gameState);
|
||||
this.path = this.pathFinder.continuePath(gameState);
|
||||
|
||||
if (this.path === undefined) {
|
||||
if (this.pathWidth == 6)
|
||||
|
@ -23,6 +23,10 @@ var EconomyManager = function() {
|
||||
|
||||
this.dockFailed = false; // sanity check
|
||||
|
||||
// A few notes about these maps. They're updated by checking for "create" and "destroy" events.
|
||||
// They are also updated by dropsites, as resources that are seen as part of a dropsite are
|
||||
// removed from the map. The AI otherwise tries to build tons of dropsites next to each other.
|
||||
// It might actually be better to create when needed over a few frames. Dunno.
|
||||
this.resourceMaps = {}; // Contains maps showing the density of wood, stone and metal
|
||||
this.CCResourceMaps = {}; // Contains maps showing the density of wood, stone and metal, optimized for CC placement.
|
||||
|
||||
@ -94,11 +98,13 @@ EconomyManager.prototype.init = function(gameState, events){
|
||||
|
||||
// okay, so here we'll create both females and male workers.
|
||||
// We'll try to keep close to the "ratio" defined atop.
|
||||
// qBot picks the best citizen soldier available: the cheapest and the fastest walker
|
||||
// some civs such as Macedonia have 2 kinds of citizen soldiers: phalanx that are slow
|
||||
// (speed:6) and peltasts that are very fast (speed: 11). Here, qBot will choose the peltast
|
||||
// resulting in faster resource gathering.
|
||||
// I'll also avoid creating citizen soldiers in the beginning because it's slower.
|
||||
// Choice of citizen soldier is a bit messy.
|
||||
// Before having 100 workers it focuses on speed, cost, and won't choose units that cost stone/metal
|
||||
// After 100 it just picks the strongest;
|
||||
// TODO: This should probably be changed to favor a more mixed approach for better defense.
|
||||
// (or even to adapt based on estimated enemy strategy).
|
||||
// Also deals with setting the watned numbers of dropsites and fields since it's practical,
|
||||
// this function should probably be renamed.
|
||||
EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) {
|
||||
// Count the workers in the world and in progress
|
||||
var numFemales = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("units/{civ}_support_female_citizen"));
|
||||
@ -181,6 +187,7 @@ EconomyManager.prototype.tryResearchTechs = function(gameState, queues) {
|
||||
}
|
||||
|
||||
// picks the best template based on parameters and classes
|
||||
// Similar to the one used in the Military manager but not quite.
|
||||
EconomyManager.prototype.findBestTrainableUnit = function(gameState, classes, parameters) {
|
||||
var units = gameState.findTrainableUnits(classes);
|
||||
|
||||
@ -334,12 +341,11 @@ EconomyManager.prototype.workersBySubrole = function(gameState, subrole) {
|
||||
};
|
||||
|
||||
EconomyManager.prototype.assignToFoundations = function(gameState, noRepair) {
|
||||
// If we have some foundations, and we don't have enough
|
||||
// builder-workers,
|
||||
// If we have some foundations, and we don't have enough builder-workers,
|
||||
// try reassigning some other workers who are nearby
|
||||
|
||||
// up to 2.5 buildings at once (that is 3, but one won't be complete).
|
||||
|
||||
// AI tries to use builders sensibly, not completely stopping its econ.
|
||||
|
||||
var foundations = gameState.getOwnFoundations().toEntityArray();
|
||||
var damagedBuildings = gameState.getOwnEntities().filter(function (ent) { if (ent.needsRepair() && ent.getMetadata(PlayerID, "plan") == undefined) { return true; } return false; }).toEntityArray();
|
||||
|
||||
@ -474,14 +480,14 @@ EconomyManager.prototype.buildNewCC= function(gameState, queues) {
|
||||
return (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_civil_centre")) == 0 && gameState.currentPhase > 1);
|
||||
};
|
||||
|
||||
// TODO: make it regularly update stone+metal mines
|
||||
// TODO: make it regularly update stone+metal mines and their resource levels.
|
||||
// creates and maintains a map of unused resource density
|
||||
// this also takes dropsites into account.
|
||||
// resources that are "part" of a dropsite are not counted.
|
||||
EconomyManager.prototype.updateResourceMaps = function(gameState, events) {
|
||||
|
||||
// By how much to divide the resource amount for plotting.
|
||||
var decreaseFactor = {'wood': 25.0, 'stone': 40.0, 'metal': 40.0, 'food': 20.0};
|
||||
var decreaseFactor = {'wood': 100.0, 'stone': 180.0, 'metal': 180.0, 'food': 80.0};
|
||||
// This is the maximum radius of the influence
|
||||
var dpRadius = 10;
|
||||
var radius = {'wood':10.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0};
|
||||
@ -497,19 +503,12 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events) {
|
||||
for (var resource in radius){
|
||||
// if there is no resourceMap create one with an influence for everything with that resource
|
||||
if (! this.resourceMaps[resource]){
|
||||
this.resourceMaps[resource] = new Map(gameState);
|
||||
this.CCResourceMaps[resource] = new Map(gameState);
|
||||
// disabled as the game sends "create" events for all entities created at game start.
|
||||
/*var supplies = gameState.getResourceSupplies(resource);
|
||||
supplies.forEach(function(ent){
|
||||
if (!ent.position()){
|
||||
return;
|
||||
}
|
||||
var x = Math.round(ent.position()[0] / gameState.cellSize);
|
||||
var z = Math.round(ent.position()[1] / gameState.cellSize);
|
||||
var strength = Math.round(ent.resourceSupplyMax()/decreaseFactor[resource]);
|
||||
self.resourceMaps[resource].addInfluence(x, z, radius[resource], strength);
|
||||
});*/
|
||||
// We're creting them 8-bit. Things could go above 255 if there are really tons of resources
|
||||
// But at that point the precision is not really important anyway. And it saves memory.
|
||||
this.resourceMaps[resource] = new Map(gameState, new Uint8Array(gameState.getMap().data.length));
|
||||
this.resourceMaps[resource].setMaxVal(255);
|
||||
this.CCResourceMaps[resource] = new Map(gameState, new Uint8Array(gameState.getMap().data.length));
|
||||
this.CCResourceMaps[resource].setMaxVal(255);
|
||||
}
|
||||
}
|
||||
|
||||
@ -658,12 +657,12 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events) {
|
||||
|
||||
/*if (gameState.ai.playedTurn % 20 === 1)
|
||||
{
|
||||
this.resourceMaps['wood'].dumpIm("s_tree_density_ " + gameState.getTimeElapsed() +".png", 1001);
|
||||
this.resourceMaps['stone'].dumpIm("stone_density_ " + gameState.getTimeElapsed() +".png", 1001);
|
||||
this.resourceMaps['metal'].dumpIm("s_metal_density_ " + gameState.getTimeElapsed() +".png", 1001);
|
||||
this.CCResourceMaps['wood'].dumpIm("CC_TREE " + gameState.getTimeElapsed() +".png", 1001);
|
||||
this.CCResourceMaps['stone'].dumpIm("CC_STONE " + gameState.getTimeElapsed() +".png", 1001);
|
||||
this.CCResourceMaps['metal'].dumpIm("CC_METAL " + gameState.getTimeElapsed() +".png", 1001);
|
||||
this.resourceMaps['wood'].dumpIm("s_tree_density_ " + gameState.getTimeElapsed() +".png", 255);
|
||||
this.resourceMaps['stone'].dumpIm("stone_density_ " + gameState.getTimeElapsed() +".png", 255);
|
||||
this.resourceMaps['metal'].dumpIm("s_metal_density_ " + gameState.getTimeElapsed() +".png", 255);
|
||||
this.CCResourceMaps['wood'].dumpIm("CC_TREE " + gameState.getTimeElapsed() +".png", 255);
|
||||
this.CCResourceMaps['stone'].dumpIm("CC_STONE " + gameState.getTimeElapsed() +".png", 255);
|
||||
this.CCResourceMaps['metal'].dumpIm("CC_METAL " + gameState.getTimeElapsed() +".png", 255);
|
||||
}*/
|
||||
};
|
||||
|
||||
@ -679,7 +678,7 @@ EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource
|
||||
var territory = Map.createTerritoryMap(gameState);
|
||||
|
||||
var obstructions = Map.createObstructionMap(gameState);
|
||||
obstructions.expandInfluences(255);
|
||||
obstructions.expandInfluences();
|
||||
|
||||
var myDropsites = gameState.getOwnEntities().filter(Filters.isDropsite(resource));
|
||||
|
||||
@ -718,8 +717,8 @@ EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource
|
||||
|
||||
//debug ("Have " + best[1] + " for " + resource);
|
||||
|
||||
// 300, from empirical values, seems reasonable.
|
||||
if (best[1] <= 300 && gameState.currentPhase() >= 2)
|
||||
// 75, from empirical values, seems reasonable.
|
||||
if (best[1] <= 75 && gameState.currentPhase() >= 2)
|
||||
{
|
||||
// restart the search this time for a CC
|
||||
friendlyTiles = new Map(gameState);
|
||||
@ -785,7 +784,7 @@ EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource
|
||||
}
|
||||
|
||||
// tell the dropsite builder we haven't found anything satisfactory.
|
||||
if (best[1] < 250)
|
||||
if (best[1] < 60)
|
||||
return [false, [-1,0]];
|
||||
|
||||
var x = ((bestIdx % friendlyTiles.width) + 0.5) * gameState.cellSize;
|
||||
@ -793,6 +792,7 @@ EconomyManager.prototype.getBestResourceBuildSpot = function(gameState, resource
|
||||
|
||||
return [isCivilCenter, [x,z]];
|
||||
};
|
||||
|
||||
EconomyManager.prototype.updateResourceConcentrations = function(gameState, resource){
|
||||
var self = this;
|
||||
gameState.getOwnDropsites(resource).forEach(function(dropsite) { //}){
|
||||
@ -825,7 +825,7 @@ EconomyManager.prototype.updateNearbyResources = function(gameState,resource){
|
||||
var resourceSupplies;
|
||||
|
||||
// By how much to divide the resource amount for plotting.
|
||||
var decreaseFactor = {'wood': 25.0, 'stone': 40.0, 'metal': 40.0, 'food': 20.0};
|
||||
var decreaseFactor = {'wood': 100.0, 'stone': 180.0, 'metal': 180.0, 'food': 80.0};
|
||||
// This is the maximum radius of the influence
|
||||
var radius = {'wood':10.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0};
|
||||
|
||||
@ -995,9 +995,9 @@ EconomyManager.prototype.buildDock = function(gameState, queues){
|
||||
}
|
||||
};
|
||||
|
||||
// if qBot has resources it doesn't need, it'll try to barter it for resources it needs
|
||||
// if Aegis has resources it doesn't need, it'll try to barter it for resources it needs
|
||||
// once per turn because the info doesn't update between a turn and I don't want to fix it.
|
||||
// pretty efficient.
|
||||
// Not sure how efficient it is but it seems to be sane, at least.
|
||||
EconomyManager.prototype.tryBartering = function(gameState){
|
||||
var done = false;
|
||||
if (gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_market")) >= 1) {
|
||||
@ -1019,7 +1019,9 @@ EconomyManager.prototype.tryBartering = function(gameState){
|
||||
}
|
||||
}
|
||||
};
|
||||
// so this always try to build dropsites.
|
||||
|
||||
// TODO: while the algorithm for dropsite placement is quite good
|
||||
// This is bad. Choosing when to place dropsites should be improved.
|
||||
EconomyManager.prototype.buildDropsites = function(gameState, queues){
|
||||
if ( queues.dropsites.totalLength() === 0 && gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_mill")) === 0 &&
|
||||
gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_civil_centre")) === 0){
|
||||
@ -1059,6 +1061,7 @@ EconomyManager.prototype.buildDropsites = function(gameState, queues){
|
||||
}
|
||||
};
|
||||
// build more houses if needed.
|
||||
// kinda ugly, lots of special cases to both build enough houses but not tooo many…
|
||||
EconomyManager.prototype.buildMoreHouses = function(gameState, queues) {
|
||||
if ( (this.fastStart && gameState.getTimeElapsed() < 10000) || gameState.getTimeElapsed() < 35000)
|
||||
return;
|
||||
@ -1095,6 +1098,9 @@ EconomyManager.prototype.buildMoreHouses = function(gameState, queues) {
|
||||
};
|
||||
|
||||
// Change our priorities based on our gathering statistics.
|
||||
// TODO: this is currently unused, I'm not sure how sensible it is
|
||||
// This should probably be scrapped in favor of improving the queueManager's detection
|
||||
// of future needs.
|
||||
EconomyManager.prototype.rePrioritize = function(gameState) {
|
||||
var statG = gameState.playerData.statistics.resourcesGathered;
|
||||
var statU = gameState.playerData.statistics.resourcesUsed;
|
||||
@ -1168,7 +1174,6 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
|
||||
Engine.ProfileStop();
|
||||
}
|
||||
|
||||
// TODO: do this incrementally a la defence.js
|
||||
Engine.ProfileStart("Run Workers");
|
||||
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){
|
||||
if (!ent.getMetadata(PlayerID, "worker-object")){
|
||||
@ -1176,27 +1181,13 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
|
||||
}
|
||||
ent.getMetadata(PlayerID, "worker-object").update(gameState);
|
||||
});
|
||||
// Gatherer count updates for non-workers
|
||||
/*var filter = Filters.and(Filters.not(Filters.byMetadata(PlayerID, "worker-object", undefined)),
|
||||
Filters.not(Filters.byMetadata(PlayerID, "role", "worker")));
|
||||
gameState.updatingCollection("reassigned-workers", filter, gameState.getOwnEntities()).forEach(function(ent){
|
||||
ent.getMetadata(PlayerID, "worker-object").updateGathererCounts(gameState);
|
||||
});
|
||||
|
||||
// Gatherer count updates for destroyed units
|
||||
for (var i in events) {
|
||||
var e = events[i];
|
||||
|
||||
if (e.type === "Destroy") {
|
||||
if (e.msg.metadata && e.msg.metadata[PlayerID] && e.msg.metadata[PlayerID]["worker-object"]){
|
||||
e.msg.metadata[PlayerID]["worker-object"].updateGathererCounts(gameState, true);
|
||||
delete e.msg.metadata[PlayerID]["worker-object"];
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
Engine.ProfileStop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal run
|
||||
|
||||
this.numWorkers = gameState.getOwnEntitiesByRole("worker").filter(Filters.not(Filters.byHasMetadata(PlayerID,"plan"))).length;
|
||||
|
||||
// this function also deals with a few things that are number-of-workers related
|
||||
@ -1282,6 +1273,7 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
|
||||
|
||||
this.buildFarmstead(gameState, queues);
|
||||
this.buildMarket(gameState, queues);
|
||||
// Deactivated: the temple had no useful purpose for the AI now.
|
||||
//if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_market")) === 1)
|
||||
// this.buildTemple(gameState, queues);
|
||||
this.buildDock(gameState, queues); // not if not a water map.
|
||||
@ -1289,7 +1281,7 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
|
||||
if (gameState.ai.playedTurn % 10 === 0){
|
||||
this.setWorkersIdleByPriority(gameState);
|
||||
}
|
||||
if (gameState.ai.playedTurn % 3 === 0)
|
||||
if (gameState.ai.playedTurn % 3 === 1)
|
||||
{
|
||||
Engine.ProfileStart("Reassign Idle Workers");
|
||||
this.reassignIdleWorkers(gameState);
|
||||
@ -1337,29 +1329,10 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
|
||||
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){
|
||||
if (!ent.getMetadata(PlayerID, "worker-object"))
|
||||
ent.setMetadata(PlayerID, "worker-object", new Worker(ent));
|
||||
|
||||
if ((ent.id() + gameState.ai.playedTurn) % 3 === 0) // should make it significantly faster without much drawbacks.
|
||||
ent.getMetadata(PlayerID, "worker-object").update(gameState);
|
||||
});
|
||||
// Gatherer count updates for non-workers
|
||||
var filter = Filters.and(Filters.not(Filters.byMetadata(PlayerID, "worker-object", undefined)),
|
||||
Filters.not(Filters.byMetadata(PlayerID, "role", "worker")));
|
||||
gameState.updatingCollection("reassigned-workers", filter, gameState.getOwnEntities()).forEach(function(ent){
|
||||
ent.getMetadata(PlayerID, "worker-object").updateGathererCounts(gameState);
|
||||
});
|
||||
|
||||
// Gatherer count updates for destroyed units
|
||||
for (var i in events) {
|
||||
var e = events[i];
|
||||
|
||||
if (e.type === "Destroy") {
|
||||
if (e.msg.metadata && e.msg.metadata[PlayerID] && e.msg.metadata[PlayerID]["worker-object"]){
|
||||
e.msg.metadata[PlayerID]["worker-object"].updateGathererCounts(gameState, true);
|
||||
delete e.msg.metadata[PlayerID]["worker-object"];
|
||||
}
|
||||
}
|
||||
}
|
||||
Engine.ProfileStop();
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
const TERRITORY_PLAYER_MASK = 0x3F;
|
||||
|
||||
//TODO: Make this cope with negative cell values
|
||||
// This is by default a 16-bit map but can be adapted into 8-bit.
|
||||
function Map(gameState, originalMap, actualCopy){
|
||||
// get the map to find out the correct dimensions
|
||||
var gameMap = gameState.getMap();
|
||||
@ -9,6 +10,7 @@ function Map(gameState, originalMap, actualCopy){
|
||||
this.length = gameMap.data.length;
|
||||
|
||||
if (originalMap && actualCopy){
|
||||
this.maxVal = 65535;
|
||||
this.map = new Uint16Array(this.length);
|
||||
for (var i = 0; i < originalMap.length; ++i)
|
||||
this.map[i] = originalMap[i];
|
||||
@ -16,9 +18,13 @@ function Map(gameState, originalMap, actualCopy){
|
||||
this.map = originalMap;
|
||||
} else {
|
||||
this.map = new Uint16Array(this.length);
|
||||
this.maxVal = 65535;
|
||||
}
|
||||
this.cellSize = gameState.cellSize;
|
||||
}
|
||||
Map.prototype.setMaxVal = function(val){
|
||||
this.maxVal = val;
|
||||
};
|
||||
|
||||
Map.prototype.gamePosToMapPos = function(p){
|
||||
return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
|
||||
@ -29,6 +35,7 @@ Map.prototype.point = function(p){
|
||||
return this.map[q[0] + this.width * q[1]];
|
||||
};
|
||||
|
||||
// returns an 8-bit map.
|
||||
Map.createObstructionMap = function(gameState, template){
|
||||
var passabilityMap = gameState.getMap();
|
||||
var territoryMap = gameState.ai.territoryMap;
|
||||
@ -121,6 +128,7 @@ Map.createObstructionMap = function(gameState, template){
|
||||
}
|
||||
|
||||
var map = new Map(gameState, obstructionTiles);
|
||||
map.setMaxVal(255);
|
||||
|
||||
if (template && template.buildDistance()){
|
||||
var minDist = template.buildDistance().MinDistance;
|
||||
@ -196,11 +204,12 @@ Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) {
|
||||
quant = str;
|
||||
break;
|
||||
}
|
||||
if (-1 * quant > this.map[x + y * this.width]){
|
||||
this.map[x + y * this.width] = 0; //set anything which would have gone negative to 0
|
||||
}else{
|
||||
if (this.map[x + y * this.width] + quant < 0)
|
||||
this.map[x + y * this.width] = 0;
|
||||
else if (this.map[x + y * this.width] + quant > this.maxVal)
|
||||
this.map[x + y * this.width] = this.maxVal; // avoids overflow.
|
||||
else
|
||||
this.map[x + y * this.width] += quant;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,15 +258,17 @@ Map.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type) {
|
||||
break;
|
||||
}
|
||||
var machin = this.map[x + y * this.width] * quant;
|
||||
if (machin <= 0){
|
||||
this.map[x + y * this.width] = 0; //set anything which would have gone negative to 0
|
||||
}else{
|
||||
if (machin < 0)
|
||||
this.map[x + y * this.width] = 0;
|
||||
else if (machin > this.maxVal)
|
||||
this.map[x + y * this.width] = this.maxVal;
|
||||
else
|
||||
this.map[x + y * this.width] = machin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// doesn't check for overflow.
|
||||
Map.prototype.setInfluence = function(cx, cy, maxDist, value) {
|
||||
value = value ? value : 0;
|
||||
|
||||
@ -279,40 +290,17 @@ Map.prototype.setInfluence = function(cx, cy, maxDist, value) {
|
||||
}
|
||||
};
|
||||
|
||||
Map.prototype.sumInfluence = function(cx, cy, radius){
|
||||
var x0 = Math.max(0, cx - radius);
|
||||
var y0 = Math.max(0, cy - radius);
|
||||
var x1 = Math.min(this.width, cx + radius);
|
||||
var y1 = Math.min(this.height, cy + radius);
|
||||
var radius2 = radius * radius;
|
||||
|
||||
var sum = 0;
|
||||
|
||||
for ( var y = y0; y < y1; ++y) {
|
||||
for ( var x = x0; x < x1; ++x) {
|
||||
var dx = x - cx;
|
||||
var dy = y - cy;
|
||||
var r2 = dx*dx + dy*dy;
|
||||
if (r2 < radius2){
|
||||
sum += this.map[x + y * this.width];
|
||||
}
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make each cell's 16-bit value at least one greater than each of its
|
||||
* Make each cell's 16-bit/8-bit value at least one greater than each of its
|
||||
* neighbours' values. (If the grid is initialised with 0s and 65535s or 255s, the
|
||||
* result of each cell is its Manhattan distance to the nearest 0.)
|
||||
*/
|
||||
Map.prototype.expandInfluences = function(minVal) {
|
||||
var minValue = minVal ? minVal : 65535;
|
||||
Map.prototype.expandInfluences = function() {
|
||||
var w = this.width;
|
||||
var h = this.height;
|
||||
var grid = this.map;
|
||||
for ( var y = 0; y < h; ++y) {
|
||||
var min = minValue;
|
||||
var min = this.maxVal;
|
||||
for ( var x = 0; x < w; ++x) {
|
||||
var g = grid[x + y * w];
|
||||
if (g > min)
|
||||
@ -333,7 +321,7 @@ Map.prototype.expandInfluences = function(minVal) {
|
||||
}
|
||||
|
||||
for ( var x = 0; x < w; ++x) {
|
||||
var min = minValue;
|
||||
var min = this.maxVal;
|
||||
for ( var y = 0; y < h; ++y) {
|
||||
var g = grid[x + y * w];
|
||||
if (g > min)
|
||||
@ -371,65 +359,20 @@ Map.prototype.findBestTile = function(radius, obstructionTiles){
|
||||
return [bestIdx, bestVal];
|
||||
};
|
||||
|
||||
// Multiplies current map by 3 if in my territory
|
||||
Map.prototype.multiplyTerritory = function(gameState,map,evenNeutral){
|
||||
for (var i = 0; i < this.length; ++i){
|
||||
if (map.getOwnerIndex(i) === PlayerID)
|
||||
this.map[i] *= 2.5;
|
||||
else if (map.getOwnerIndex(i) !== PlayerID && (map.getOwnerIndex(i) !== 0 || evenNeutral))
|
||||
this.map[i] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// sets to 0 any enemy territory
|
||||
Map.prototype.annulateTerritory = function(gameState,map,evenNeutral){
|
||||
for (var i = 0; i < this.length; ++i){
|
||||
if (map.getOwnerIndex(i) !== PlayerID && (map.getOwnerIndex(i) !== 0 || evenNeutral))
|
||||
this.map[i] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Multiplies current map by the parameter map pixelwise
|
||||
Map.prototype.multiply = function(map, onlyBetter,divider,maxMultiplier){
|
||||
for (var i = 0; i < this.length; ++i){
|
||||
if (map.map[i]/divider > 1)
|
||||
this.map[i] = Math.min(maxMultiplier*this.map[i], this.map[i] * (map.map[i]/divider));
|
||||
}
|
||||
};
|
||||
// add to current map by the parameter map pixelwise
|
||||
Map.prototype.add = function(map){
|
||||
for (var i = 0; i < this.length; ++i){
|
||||
this.map[i] += +map.map[i];
|
||||
}
|
||||
};
|
||||
// add to current map by the parameter map pixelwise with a multiplier
|
||||
Map.prototype.madd = function(map, multiplier){
|
||||
for (var i = 0; i < this.length; ++i){
|
||||
this.map[i] += +map.map[i]*multiplier;
|
||||
}
|
||||
};
|
||||
// add to current map if the map is not null in that point
|
||||
Map.prototype.addIfNotNull = function(map){
|
||||
for (var i = 0; i < this.length; ++i){
|
||||
if (this.map[i] !== 0)
|
||||
this.map[i] += +map.map[i];
|
||||
}
|
||||
};
|
||||
// add to current map by the parameter map pixelwise
|
||||
Map.prototype.subtract = function(map){
|
||||
for (var i = 0; i < this.length; ++i){
|
||||
this.map[i] = this.map[i] - map.map[i] < 0 ? 0 : this.map[i] - map.map[i];
|
||||
}
|
||||
};
|
||||
// add to current map by the parameter map pixelwise with a multiplier
|
||||
Map.prototype.subtractMultiplied = function(map,multiple){
|
||||
for (var i = 0; i < this.length; ++i){
|
||||
this.map[i] = this.map[i] - map.map[i]*multiple < 0 ? 0 : this.map[i] - map.map[i]*multiple;
|
||||
for (var i = 0; i < this.length; ++i) {
|
||||
if (this.map[i] + map.map[i] < 0)
|
||||
this.map[i] = 0;
|
||||
else if (this.map[i] + map.map[i] > this.maxVal)
|
||||
this.map[i] = this.maxVal;
|
||||
else
|
||||
this.map[i] += map.map[i];
|
||||
}
|
||||
};
|
||||
|
||||
Map.prototype.dumpIm = function(name, threshold){
|
||||
name = name ? name : "default.png";
|
||||
threshold = threshold ? threshold : 65500;
|
||||
threshold = threshold ? threshold : this.maxVal;
|
||||
Engine.DumpImage(name, this.map, this.width, this.height, threshold);
|
||||
};
|
||||
|
@ -1,5 +1,9 @@
|
||||
/*
|
||||
* Military Manager. NOT cleaned up from qBot, many of the functions here are deprecated for functions in attack_plan.js
|
||||
* Military Manager.
|
||||
* Basically this deals with constructing defense and attack buildings, but it's not very developped yet.
|
||||
* There's a lot of work still to do here.
|
||||
* It also handles the attack plans (see attack_plan.js)
|
||||
* Not completely cleaned up from the original version in qBot.
|
||||
*/
|
||||
|
||||
var MilitaryAttackManager = function() {
|
||||
@ -46,9 +50,6 @@ MilitaryAttackManager.prototype.init = function(gameState) {
|
||||
this.bFort[i] = gameState.applyCiv(this.bFort[i]);
|
||||
}
|
||||
|
||||
this.getEconomicTargets = function(gameState, militaryManager){
|
||||
return militaryManager.getEnemyBuildings(gameState, "Economic");
|
||||
};
|
||||
// TODO: figure out how to make this generic
|
||||
for (var i in this.attackManagers){
|
||||
this.availableAttacks[i] = new this.attackManagers[i](gameState, this);
|
||||
@ -72,72 +73,6 @@ MilitaryAttackManager.prototype.init = function(gameState) {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @param (GameState) gameState
|
||||
* @param (string) soldierTypes
|
||||
* @returns array of soldiers for which training buildings exist
|
||||
*/
|
||||
MilitaryAttackManager.prototype.findTrainableUnits = function(gameState, soldierType){
|
||||
var allTrainable = [];
|
||||
gameState.getOwnEntities().forEach(function(ent) {
|
||||
var trainable = ent.trainableEntities();
|
||||
for (var i in trainable){
|
||||
if (allTrainable.indexOf(trainable[i]) === -1){
|
||||
allTrainable.push(trainable[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var ret = [];
|
||||
for (var i in allTrainable){
|
||||
var template = gameState.getTemplate(allTrainable[i]);
|
||||
if (soldierType == this.getSoldierType(template)){
|
||||
ret.push(allTrainable[i]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Returns the type of a soldier, either citizenSoldier, advanced or siege
|
||||
MilitaryAttackManager.prototype.getSoldierType = function(ent){
|
||||
if (ent.hasClass("Hero")){
|
||||
return undefined;
|
||||
}
|
||||
if (ent.hasClass("CitizenSoldier") && !ent.hasClass("Cavalry")){
|
||||
return "citizenSoldier";
|
||||
}else if (ent.hasClass("Champion") || ent.hasClass("CitizenSoldier")){
|
||||
return "advanced";
|
||||
}else if (ent.hasClass("Siege")){
|
||||
return "siege";
|
||||
}else{
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the unit type we should begin training. (Currently this is whatever
|
||||
* we have least of.)
|
||||
*/
|
||||
MilitaryAttackManager.prototype.findBestNewUnit = function(gameState, queue, soldierType) {
|
||||
var units = this.findTrainableUnits(gameState, soldierType);
|
||||
// Count each type
|
||||
var types = [];
|
||||
for ( var tKey in units) {
|
||||
var t = units[tKey];
|
||||
types.push([t, gameState.countEntitiesAndQueuedByType(gameState.applyCiv(t))
|
||||
+ queue.countAllByType(gameState.applyCiv(t)) ]);
|
||||
}
|
||||
|
||||
// Sort by increasing count
|
||||
types.sort(function(a, b) {
|
||||
return a[1] - b[1];
|
||||
});
|
||||
|
||||
if (types.length === 0){
|
||||
return false;
|
||||
}
|
||||
return types[0][0];
|
||||
};
|
||||
// picks the best template based on parameters and classes
|
||||
MilitaryAttackManager.prototype.findBestTrainableUnit = function(gameState, classes, parameters) {
|
||||
var units = gameState.findTrainableUnits(classes);
|
||||
@ -186,200 +121,9 @@ MilitaryAttackManager.prototype.findBestTrainableUnit = function(gameState, clas
|
||||
return units[0][0];
|
||||
};
|
||||
|
||||
|
||||
|
||||
MilitaryAttackManager.prototype.registerSoldiers = function(gameState) {
|
||||
var soldiers = gameState.getOwnEntitiesByRole("soldier");
|
||||
var self = this;
|
||||
|
||||
soldiers.forEach(function(ent) {
|
||||
ent.setMetadata(PlayerID, "role", "military");
|
||||
ent.setMetadata(PlayerID, "military", "unassigned");
|
||||
});
|
||||
};
|
||||
|
||||
// return count of enemy buildings for a given building class
|
||||
MilitaryAttackManager.prototype.getEnemyBuildings = function(gameState,cls) {
|
||||
var targets = gameState.entities.filter(function(ent) {
|
||||
return (gameState.isEntityEnemy(ent) && ent.hasClass("Structure") && ent.hasClass(cls) && ent.owner() !== 0 && ent.position());
|
||||
});
|
||||
return targets;
|
||||
};
|
||||
|
||||
// return n available units and makes these units unavailable
|
||||
MilitaryAttackManager.prototype.getAvailableUnits = function(n, filter) {
|
||||
var ret = [];
|
||||
var count = 0;
|
||||
|
||||
var units = undefined;
|
||||
|
||||
if (filter){
|
||||
units = this.getUnassignedUnits().filter(filter);
|
||||
}else{
|
||||
units = this.getUnassignedUnits();
|
||||
}
|
||||
|
||||
units.forEach(function(ent){
|
||||
ret.push(ent.id());
|
||||
ent.setMetadata(PlayerID, "military", "assigned");
|
||||
ent.setMetadata(PlayerID, "role", "military");
|
||||
count++;
|
||||
if (count >= n) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Takes a single unit id, and marks it unassigned
|
||||
MilitaryAttackManager.prototype.unassignUnit = function(unit){
|
||||
this.entity(unit).setMetadata(PlayerID, "military", "unassigned");
|
||||
};
|
||||
|
||||
// Takes an array of unit id's and marks all of them unassigned
|
||||
MilitaryAttackManager.prototype.unassignUnits = function(units){
|
||||
for (var i in units){
|
||||
this.unassignUnit(units[i]);
|
||||
}
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.getUnassignedUnits = function(){
|
||||
return this.gameState.getOwnEntitiesByMetadata("military", "unassigned");
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.countAvailableUnits = function(filter){
|
||||
var count = 0;
|
||||
if (filter){
|
||||
return this.getUnassignedUnits().filter(filter).length;
|
||||
}else{
|
||||
return this.getUnassignedUnits().length;
|
||||
}
|
||||
};
|
||||
|
||||
// Takes an entity id and returns an entity object or undefined if there is no entity with that id
|
||||
// Also sends a debug message warning if the id has no entity
|
||||
MilitaryAttackManager.prototype.entity = function(id) {
|
||||
return this.gameState.getEntityById(id);
|
||||
};
|
||||
|
||||
// Returns the military strength of unit
|
||||
MilitaryAttackManager.prototype.getUnitStrength = function(ent){
|
||||
var strength = 0.0;
|
||||
var attackTypes = ent.attackTypes();
|
||||
var armourStrength = ent.armourStrengths();
|
||||
var hp = 2 * ent.hitpoints() / (160 + 1*ent.maxHitpoints()); //100 = typical number of hitpoints
|
||||
for (var typeKey in attackTypes) {
|
||||
var type = attackTypes[typeKey];
|
||||
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]);
|
||||
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;
|
||||
};
|
||||
|
||||
// Returns the strength of the available units of ai army
|
||||
MilitaryAttackManager.prototype.measureAvailableStrength = function(){
|
||||
var strength = 0.0;
|
||||
var self = this;
|
||||
this.getUnassignedUnits(this.gameState).forEach(function(ent){
|
||||
strength += self.getUnitStrength(ent);
|
||||
});
|
||||
return strength;
|
||||
};
|
||||
|
||||
MilitaryAttackManager.prototype.getEnemySoldiers = function(){
|
||||
return this.enemySoldiers;
|
||||
};
|
||||
|
||||
// Returns the number of units in the largest enemy army
|
||||
MilitaryAttackManager.prototype.measureEnemyCount = function(gameState){
|
||||
// Measure enemy units
|
||||
var isEnemy = gameState.playerData.isEnemy;
|
||||
var enemyCount = [];
|
||||
var maxCount = 0;
|
||||
for ( var i = 1; i < isEnemy.length; i++) {
|
||||
enemyCount[i] = 0;
|
||||
}
|
||||
|
||||
// Loop through the enemy soldiers and add one to the count for that soldiers player's count
|
||||
this.enemySoldiers.forEach(function(ent) {
|
||||
enemyCount[ent.owner()]++;
|
||||
|
||||
if (enemyCount[ent.owner()] > maxCount) {
|
||||
maxCount = enemyCount[ent.owner()];
|
||||
}
|
||||
});
|
||||
|
||||
return maxCount;
|
||||
};
|
||||
|
||||
// Returns the strength of the largest enemy army
|
||||
MilitaryAttackManager.prototype.measureEnemyStrength = function(gameState){
|
||||
// Measure enemy strength
|
||||
var isEnemy = gameState.playerData.isEnemy;
|
||||
var enemyStrength = [];
|
||||
var maxStrength = 0;
|
||||
var self = this;
|
||||
|
||||
for ( var i = 1; i < isEnemy.length; i++) {
|
||||
enemyStrength[i] = 0;
|
||||
}
|
||||
|
||||
// Loop through the enemy soldiers and add the strength to that soldiers player's total strength
|
||||
this.enemySoldiers.forEach(function(ent) {
|
||||
enemyStrength[ent.owner()] += self.getUnitStrength(ent);
|
||||
|
||||
if (enemyStrength[ent.owner()] > maxStrength) {
|
||||
maxStrength = enemyStrength[ent.owner()];
|
||||
}
|
||||
});
|
||||
|
||||
return maxStrength;
|
||||
};
|
||||
|
||||
// Adds towers to the defenceBuilding queue
|
||||
// Deals with building fortresses and towers.
|
||||
// Currently build towers next to every useful dropsites.
|
||||
// TODO: Fortresses are placed randomly atm.
|
||||
MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
|
||||
|
||||
var workersNumber = gameState.getOwnEntitiesByRole("worker").filter(Filters.not(Filters.byHasMetadata(PlayerID,"plan"))).length;
|
||||
@ -433,12 +177,12 @@ MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
|
||||
}
|
||||
};
|
||||
|
||||
// Deals with constructing military buildings (barracks, stables…)
|
||||
// They are mostly defined by Config.js. This is unreliable since changes could be done easily.
|
||||
// TODO: We need to determine these dynamically. Also doesn't build fortresses since the above function does that.
|
||||
// TODO: building placement is bad. Choice of buildings is also fairly dumb.
|
||||
MilitaryAttackManager.prototype.constructTrainingBuildings = function(gameState, queues) {
|
||||
// Build more military buildings
|
||||
// TODO: make military building better
|
||||
Engine.ProfileStart("Build buildings");
|
||||
|
||||
|
||||
var workersNumber = gameState.getOwnEntitiesByRole("worker").filter(Filters.not(Filters.byHasMetadata(PlayerID, "plan"))).length;
|
||||
|
||||
if (workersNumber > 30 && (gameState.currentPhase() > 1 || gameState.isResearching("phase_town"))) {
|
||||
@ -493,7 +237,7 @@ MilitaryAttackManager.prototype.constructTrainingBuildings = function(gameState,
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
|
||||
// TODO: use pop()
|
||||
// TODO: use pop(). Currently unused as this is too gameable.
|
||||
MilitaryAttackManager.prototype.garrisonAllFemales = function(gameState) {
|
||||
var buildings = gameState.getOwnEntities().filter(Filters.byCanGarrison()).toEntityArray();
|
||||
var females = gameState.getOwnEntities().filter(Filters.byClass("Support"));
|
||||
@ -576,40 +320,10 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
|
||||
this.buildDefences(gameState, queues);
|
||||
Engine.ProfileStop();
|
||||
|
||||
//Engine.ProfileStart("Updating enemy watchers");
|
||||
//this.enemyWatchers[ this.ennWatcherIndex[gameState.ai.playedTurn % this.ennWatcherIndex.length] ].detectArmies(gameState,this);
|
||||
//Engine.ProfileStop();
|
||||
|
||||
this.defenceManager.update(gameState, events, this);
|
||||
|
||||
/*Engine.ProfileStart("Plan new attacks");
|
||||
// Look for attack plans which can be executed, only do this once every minute
|
||||
for (var i = 0; i < this.availableAttacks.length; i++){
|
||||
if (this.availableAttacks[i].canExecute(gameState, this)){
|
||||
this.availableAttacks[i].execute(gameState, this);
|
||||
this.currentAttacks.push(this.availableAttacks[i]);
|
||||
//debug("Attacking!");
|
||||
}
|
||||
this.availableAttacks.splice(i, 1, new this.attackManagers[i](gameState, this));
|
||||
}
|
||||
Engine.ProfileStop();
|
||||
|
||||
Engine.ProfileStart("Update attacks");
|
||||
// Keep current attacks updated
|
||||
for (var i in this.currentAttacks){
|
||||
this.currentAttacks[i].update(gameState, this, events);
|
||||
}
|
||||
Engine.ProfileStop();*/
|
||||
|
||||
Engine.ProfileStart("Looping through attack plans");
|
||||
// create plans if I'm at peace. I'm not starting plans if there is a sizable force in my realm (hence defcon 4+)
|
||||
|
||||
//if (gameState.defcon() >= 4 && this.canStartAttacks === true) {
|
||||
//if ((this.preparingNormal) == 0 && this.BuildingInfoManager.getNumberBuiltByRole("Barracks") > 0) {
|
||||
|
||||
// this will updats plans. Plans can be updated up to defcon 2, where they'll be paused (TODO)
|
||||
//if (0 == 1) // remove to activate attacks
|
||||
//if (gameState.defcon() >= 3) {
|
||||
// TODO: implement some form of check before starting a new attack plans. Sometimes it is not the priority.
|
||||
if (1) {
|
||||
for (attackType in this.upcomingAttacks) {
|
||||
for (var i = 0;i < this.upcomingAttacks[attackType].length; ++i) {
|
||||
@ -630,58 +344,55 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
|
||||
debug("No attack path found. Aborting.");
|
||||
}
|
||||
attack.Abort(gameState, this);
|
||||
//this.abortedAttacks.push(attack);
|
||||
|
||||
this.upcomingAttacks[attackType].splice(i--,1);
|
||||
} else if (updateStep === 2) {
|
||||
var chatText = "I am launching an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name;
|
||||
var chatText = "I am launching an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
|
||||
if (Math.random() < 0.2)
|
||||
chatText = "Attacking " + gameState.sharedScript.playersData[attack.targetPlayer].name;
|
||||
chatText = "Attacking " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
|
||||
else if (Math.random() < 0.3)
|
||||
chatText = "Starting to attack " + gameState.sharedScript.playersData[attack.targetPlayer].name;
|
||||
chatText = "I have sent an army against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
|
||||
else if (Math.random() < 0.3)
|
||||
chatText = "I'm starting an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name;
|
||||
chatText = "I'm starting an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
|
||||
gameState.ai.chatTeam(chatText);
|
||||
|
||||
debug ("Military Manager: Starting " +attack.getType() +" plan " +attack.getName());
|
||||
attack.StartAttack(gameState,this);
|
||||
this.startedAttacks[attackType].push(attack);
|
||||
this.upcomingAttacks[attackType].splice(i--,1);
|
||||
}
|
||||
} else {
|
||||
var chatText = "I am launching an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name;
|
||||
var chatText = "I am launching an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
|
||||
if (Math.random() < 0.2)
|
||||
chatText = "Attacking " + gameState.sharedScript.playersData[attack.targetPlayer].name;
|
||||
chatText = "Attacking " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
|
||||
else if (Math.random() < 0.3)
|
||||
chatText = "Starting to attack " + gameState.sharedScript.playersData[attack.targetPlayer].name;
|
||||
chatText = "I have sent an army against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
|
||||
else if (Math.random() < 0.3)
|
||||
chatText = "I'm starting an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name;
|
||||
chatText = "I'm starting an attack against " + gameState.sharedScript.playersData[attack.targetPlayer].name + ".";
|
||||
gameState.ai.chatTeam(chatText);
|
||||
|
||||
debug ("Military Manager: Starting " +attack.getType() +" plan " +attack.getName());
|
||||
this.startedAttacks[attackType].push(attack);
|
||||
this.upcomingAttacks[attackType].splice(i--,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
//if (this.abortedAttacks.length !== 0)
|
||||
// this.abortedAttacks[gameState.ai.mainCounter % this.abortedAttacks.length].releaseAnyUnit(gameState);
|
||||
}
|
||||
for (attackType in this.startedAttacks) {
|
||||
for (var i = 0; i < this.startedAttacks[attackType].length; ++i) {
|
||||
var attack = this.startedAttacks[attackType][i];
|
||||
// okay so then we'll update the raid.
|
||||
// okay so then we'll update the attack.
|
||||
if (!attack.isPaused())
|
||||
{
|
||||
var remaining = attack.update(gameState,this,events);
|
||||
if (remaining == 0 || remaining == undefined) {
|
||||
debug ("Military Manager: " +attack.getType() +" plan " +attack.getName() +" is now finished.");
|
||||
attack.Abort(gameState);
|
||||
|
||||
//this.abortedAttacks.push(attack);
|
||||
this.startedAttacks[attackType].splice(i--,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: these indications of "rush" are currently unused.
|
||||
if (gameState.ai.strategy === "rush" && this.startedAttacks["CityAttack"].length !== 0) {
|
||||
// and then we revert.
|
||||
gameState.ai.strategy = "normal";
|
||||
@ -727,7 +438,10 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// very old relic. This should be reimplemented someday so the code stays here.
|
||||
|
||||
if (this.HarassRaiding && this.preparingRaidNumber + this.startedRaidNumber < 1 && gameState.getTimeElapsed() < 780000) {
|
||||
var Lalala = new CityAttack(gameState, this,this.totalStartedAttackNumber, -1, "harass_raid");
|
||||
if (!Lalala.createSupportPlans(gameState, this, )) {
|
||||
@ -745,16 +459,5 @@ MilitaryAttackManager.prototype.update = function(gameState, queues, events) {
|
||||
|
||||
|
||||
Engine.ProfileStop();
|
||||
|
||||
|
||||
/*Engine.ProfileStart("Use idle military as workers");
|
||||
// Set unassigned to be workers TODO: fix this so it doesn't scan all units every time
|
||||
this.getUnassignedUnits(gameState).forEach(function(ent){
|
||||
if (self.getSoldierType(ent) === "citizenSoldier"){
|
||||
ent.setMetadata(PlayerID, "role", "worker");
|
||||
}
|
||||
});
|
||||
Engine.ProfileStop();*/
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
|
@ -70,7 +70,7 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
|
||||
//obstructionMap.dumpIm(template.buildCategory() + "_obstructions.png");
|
||||
|
||||
if (template.buildCategory() !== "Dock")
|
||||
obstructionMap.expandInfluences(255);
|
||||
obstructionMap.expandInfluences();
|
||||
|
||||
// Compute each tile's closeness to friendly structures:
|
||||
|
||||
|
@ -231,6 +231,7 @@ QBotAI.prototype.OnUpdate = function(sharedScript) {
|
||||
this.queueManager.update(gameState);
|
||||
|
||||
/*
|
||||
// Use this to debug informations about the metadata.
|
||||
if (this.playedTurn % 10 === 0)
|
||||
{
|
||||
// some debug informations about units.
|
||||
|
@ -1,18 +1,23 @@
|
||||
//This takes the input queues and picks which items to fund with resources until no more resources are left to distribute.
|
||||
// This takes the input queues and picks which items to fund with resources until no more resources are left to distribute.
|
||||
//
|
||||
//In this manager all resources are 'flattened' into a single type=(food+wood+metal+stone+pop*50 (see resources.js))
|
||||
//the following refers to this simple as resource
|
||||
// Currently this manager keeps accounts for each queue, split between the 4 main resources
|
||||
//
|
||||
// Each queue has an account which records the amount of resource it can spend. If no queue has an affordable item
|
||||
// then the amount of resource is increased to all accounts in direct proportion to the priority until an item on one
|
||||
// of the queues becomes affordable.
|
||||
// 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 is that a rarely used queue will end up with a very large account. I am unsure if this
|
||||
// is good or bad or neither.
|
||||
// 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.
|
||||
//
|
||||
// Each queue object has two queues in it, one with items waiting for resources and the other with items which have been
|
||||
// allocated resources and are due to be executed. The secondary queues are helpful because then units can be trained
|
||||
// in groups of 5 and buildings are built once per turn to avoid placement clashes.
|
||||
// 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.
|
||||
//
|
||||
// The fact that there is an outqueue is mostly a relic of qBot.
|
||||
//
|
||||
// This system should be improved. It's probably not flexible enough.
|
||||
|
||||
var QueueManager = function(queues, priorities) {
|
||||
this.queues = queues;
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Used to know which templates I have, which templates I know I can train, things like that.
|
||||
* Mostly unused.
|
||||
*/
|
||||
|
||||
var TemplateManager = function(gameState) {
|
||||
|
@ -103,100 +103,6 @@ Worker.prototype.update = function(gameState) {
|
||||
} else {
|
||||
this.startApproachingResourceTime = gameState.getTimeElapsed();
|
||||
}
|
||||
|
||||
Engine.ProfileStart("Update Gatherer Counts");
|
||||
this.updateGathererCounts(gameState);
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
|
||||
Worker.prototype.updateGathererCounts = function(gameState, dead){
|
||||
// update gatherer counts for the resources
|
||||
if (this.ent.unitAIState().split(".")[1] === "GATHER" && !dead){
|
||||
if (this.gatheringFrom !== this.ent.unitAIOrderData()[0].target){
|
||||
if (this.gatheringFrom){
|
||||
var ent = gameState.getEntityById(this.gatheringFrom);
|
||||
if (ent && ent.resourceSupplyType()){
|
||||
ent.setMetadata(PlayerID, "gatherer-count", ent.getMetadata(PlayerID, "gatherer-count") - 1);
|
||||
this.markFull(gameState,ent);
|
||||
}
|
||||
}
|
||||
this.gatheringFrom = this.ent.unitAIOrderData()[0].target;
|
||||
if (this.gatheringFrom){
|
||||
var ent = gameState.getEntityById(this.gatheringFrom);
|
||||
if (ent && ent.resourceSupplyType()){
|
||||
ent.setMetadata(PlayerID, "gatherer-count", (ent.getMetadata(PlayerID, "gatherer-count") || 0) + 1);
|
||||
this.markFull(gameState,ent);
|
||||
}
|
||||
}
|
||||
this.startApproachingResourceTime = gameState.getTimeElapsed();
|
||||
}
|
||||
} else if (this.ent.unitAIState().split(".")[1] === "RETURNRESOURCE" && !dead) {
|
||||
// We remove us from the counting is we have no following order or its not "return to collected resource".
|
||||
if (this.ent.unitAIOrderData().length === 1) {
|
||||
var ent = gameState.getEntityById(this.gatheringFrom);
|
||||
if (ent && ent.resourceSupplyType()){
|
||||
ent.setMetadata(PlayerID, "gatherer-count", ent.getMetadata(PlayerID, "gatherer-count") - 1);
|
||||
this.markFull(gameState,ent);
|
||||
}
|
||||
this.gatheringFrom = undefined;
|
||||
} else if (!this.ent.unitAIOrderData()[1].target || this.gatheringFrom !== this.ent.unitAIOrderData()[1].target){
|
||||
if (this.gatheringFrom){
|
||||
var ent = gameState.getEntityById(this.gatheringFrom);
|
||||
if (ent && ent.resourceSupplyType()){
|
||||
ent.setMetadata(PlayerID, "gatherer-count", ent.getMetadata(PlayerID, "gatherer-count") - 1);
|
||||
this.markFull(gameState,ent);
|
||||
}
|
||||
}
|
||||
this.gatheringFrom = undefined;
|
||||
}
|
||||
} else {
|
||||
if (this.gatheringFrom){
|
||||
var ent = gameState.getEntityById(this.gatheringFrom);
|
||||
if (ent && ent.resourceSupplyType()){
|
||||
ent.setMetadata(PlayerID, "gatherer-count", ent.getMetadata(PlayerID, "gatherer-count") - 1);
|
||||
this.markFull(gameState,ent);
|
||||
}
|
||||
this.gatheringFrom = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Worker.prototype.markFull = function(gameState,ent){
|
||||
var maxCounts = {"food": 15, "wood": 6, "metal": 15, "stone": 15, "treasure": 1};
|
||||
var resource = ent.resourceSupplyType().generic;
|
||||
if (ent.resourceSupplyType() && ent.getMetadata(PlayerID, "gatherer-count") >= maxCounts[resource]){
|
||||
if (!ent.getMetadata(PlayerID, "full")){
|
||||
ent.setMetadata(PlayerID, "full", true);
|
||||
// update the dropsite
|
||||
var dropsite = gameState.getEntityById(ent.getMetadata(PlayerID, "linked-dropsite"));
|
||||
if (dropsite == undefined || dropsite.getMetadata(PlayerID, "linked-resources-" + resource) === undefined)
|
||||
return;
|
||||
if (ent.getMetadata(PlayerID, "linked-dropsite-nearby") == true) {
|
||||
dropsite.setMetadata(PlayerID, "resource-quantity-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) - (+ent.getMetadata(PlayerID, "dp-update-value")));
|
||||
dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(ent);
|
||||
dropsite.getMetadata(PlayerID, "nearby-resources-" + resource).updateEnt(ent);
|
||||
} else {
|
||||
dropsite.setMetadata(PlayerID, "resource-quantity-far-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) - (+ent.getMetadata(PlayerID, "dp-update-value")));
|
||||
dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(ent);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if (ent.getMetadata(PlayerID, "full")){
|
||||
ent.setMetadata(PlayerID, "full", false);
|
||||
// update the dropsite
|
||||
var dropsite = gameState.getEntityById(ent.getMetadata(PlayerID, "linked-dropsite"));
|
||||
if (dropsite == undefined || dropsite.getMetadata(PlayerID, "linked-resources-" + resource) === undefined)
|
||||
return;
|
||||
if (ent.getMetadata(PlayerID, "linked-dropsite-nearby") == true) {
|
||||
dropsite.setMetadata(PlayerID, "resource-quantity-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) + ent.resourceSupplyAmount());
|
||||
dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(ent);
|
||||
dropsite.getMetadata(PlayerID, "nearby-resources-" + resource).updateEnt(ent);
|
||||
} else {
|
||||
dropsite.setMetadata(PlayerID, "resource-quantity-far-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) + ent.resourceSupplyAmount());
|
||||
dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Worker.prototype.startGathering = function(gameState){
|
||||
|
Loading…
Reference in New Issue
Block a user