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:
wraitii 2013-03-16 09:59:43 +00:00
parent d70db48173
commit d709fe50ed
11 changed files with 145 additions and 607 deletions

View File

@ -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"
>

View File

@ -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]);

View File

@ -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)

View File

@ -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();
};

View File

@ -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);
};

View File

@ -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();
};

View File

@ -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:

View File

@ -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.

View File

@ -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;

View File

@ -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) {

View File

@ -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){