1
0
forked from 0ad/0ad

Removes JuBot AI which is no longer maintained and has been superseded by qBot. The latest code will remain on Trac for reference.

This was SVN commit r12833.
This commit is contained in:
historic_bruno 2012-11-07 21:11:50 +00:00
parent 7e21db08d5
commit fa56c11820
13 changed files with 0 additions and 4276 deletions

View File

@ -1 +0,0 @@
Engine.IncludeModule("common-api-v2");

View File

@ -1,5 +0,0 @@
{
"name": "JuBot",
"description": "Jubal's improved version of the 0AD TestBot.",
"constructor": "JuBotAI"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,450 +0,0 @@
/**
* Provides an API for the rest of the AI scripts to query the world state
* at a higher level than the raw data.
*/
var GameState = Class({
_init: function(ai)
{
MemoizeInit(this);
this.ai = ai;
this.timeElapsed = ai.timeElapsed;
this.templates = ai.templates;
this.entities = ai.entities;
this.player = ai.player;
this.playerData = ai.playerData;
if (!this.ai._gameStateStore){
this.ai._gameStateStore = {};
}
this.store = this.ai._gameStateStore;
},
getTimeElapsed: function()
{
return this.timeElapsed;
},
getTemplate: function(type)
{
if (!this.templates[type])
return null;
return new EntityTemplate(this.templates[type]);
},
applyCiv: function(str)
{
return str.replace(/\{civ\}/g, this.playerData.civ);
},
displayCiv: function()
{
return this.playerData.civ;
},
getResources: function()
{
return new Resources(this.playerData.resourceCounts);
},
getPassabilityMap: function()
{
return this.ai.passabilityMap;
},
getPassabilityClassMask: function(name)
{
if (!(name in this.ai.passabilityClasses))
error("Tried to use invalid passability class name '"+name+"'");
return this.ai.passabilityClasses[name];
},
getTerritoryMap: function()
{
return this.ai.territoryMap;
},
getEntities: function() {
return this.entities;
},
updatingCollection: function(id, filter, collection){
if (!this.store[id]){
this.store[id] = collection.filter(filter);
this.store[id].registerUpdates();
}
return this.store[id];
},
getOwnEntities: function()
{
if (!this.store.ownEntities){
this.store.ownEntities = this.getEntities().filter(Filters.byOwner(this.player));
this.store.ownEntities.registerUpdates();
}
return this.store.ownEntities;
},
getOwnRoleGroup: function(role) {
return this.updatingCollection("RoleGroup" + role, Filters.byMetadata("role", role), this.getOwnEntities());
},
getOwnWithClass: function(aclass) {
return this.updatingCollection("ClassGroup" + aclass, Filters.byClass(aclass), this.getOwnEntities());
},
getNotGaia: function() {
var collection = this.updatingCollection("NotGaia", Filters.byNotOwner(0), this.getEntities());
return collection;
},
getJustEnemies: function() {
var collection = this.updatingCollection("JustEnemies", Filters.byNotOwner(this.player), this.getNotGaia());
//warn(collection.length + " enemy unit objects")
return collection;
},
getEnemiesWithClass: function(aclass) {
var collection = this.updatingCollection("EnemyClassGroup" + aclass, Filters.byClass(aclass), this.getJustEnemies());
//warn(collection.length + " enemy unit objects")
return collection;
},
// These get enemies things are copied from qbot, to avoid duplication of a ton of effort (yay laziness)
getEnemies: function(){
var ret = [];
for (i in this.playerData.isEnemy){
if (this.playerData.isEnemy[i]){
ret.push(i);
}
}
return ret;
},
getEnemyEntities: function() {
var diplomacyChange = false;
var enemies = this.getEnemies();
warn(enemies.length + " enemy factions")
if (this.store.enemies){
if (this.store.enemies.length != enemies.length){
diplomacyChange = true;
}
else{
for (var i = 0; i < enemies.length; i++){
if (enemies[i] !== this.store.enemies[i]){
diplomacyChange = true;
}
}
}
}
if (!this.store.enemyEntities){
var filter = Filters.byOwners(enemies);
this.store.enemyEntities = this.getEntities().filter(filter);
this.store.enemyEntities.registerUpdates();
this.store.enemies = enemies;
}
if (diplomacyChange == true){
var filter = Filters.byOwners(enemies);
this.store.enemyEntities = this.getEntities().filter(filter);
this.store.enemyEntities.registerUpdates();
this.store.enemies = enemies;
}
warn(this.store.enemyEntities.length + " enemy objects")
return this.store.enemyEntities;
},
/// Laziness ends here.
getOwnEntitiesWithRole: Memoize('getOwnEntitiesWithRole', function(role)
{
var metas = this.ai._entityMetadata;
if (role === undefined)
return this.getOwnEntities().filter_raw(function(ent) {
var metadata = metas[ent.id];
if (!metadata || !('role' in metadata))
return true;
return (metadata.role === undefined);
});
else
return this.getOwnEntities().filter_raw(function(ent) {
var metadata = metas[ent.id];
if (!metadata || !('role' in metadata))
return false;
return (metadata.role === role);
});
}),
getOwnEntitiesWithTwoRoles: Memoize('getOwnEntitiesWithRole', function(role, role2)
{
var metas = this.ai._entityMetadata;
if (role === undefined)
return this.getOwnEntities().filter_raw(function(ent) {
var metadata = metas[ent.id];
if (!metadata || !('role' in metadata))
return true;
return (metadata.role === undefined);
});
else if (role2 === undefined)
return this.getOwnEntities().filter_raw(function(ent) {
var metadata = metas[ent.id];
if (!metadata || !('role' in metadata))
return true;
return (metadata.role === undefined);
});
else
return this.getOwnEntities().filter_raw(function(ent) {
var metadata = metas[ent.id];
if (!metadata || !('role' in metadata))
return false;
return (metadata.role === role || metadata.role === role2);
});
}),
countEntitiesWithType: function(type)
{
var count = 0;
this.getOwnEntities().forEach(function(ent) {
var t = ent.templateName();
if (t == type)
++count;
});
return count;
},
countEntitiesAndQueuedWithType: function(type)
{
var foundationType = "foundation|" + type;
var resourceType = "resource|" + type;
var count = 0;
this.getOwnEntities().forEach(function(ent) {
var t = ent.templateName();
if (t == type || t == foundationType || t == resourceType)
++count;
var queue = ent.trainingQueue();
if (queue)
{
queue.forEach(function(item) {
if (item.template == type)
count += item.count;
});
}
});
return count;
},
countEntitiesAndQueuedWithRole: function(role)
{
var count = 0;
this.getOwnEntities().forEach(function(ent) {
if (ent.getMetadata("role") == role)
++count;
var queue = ent.trainingQueue();
if (queue)
{
queue.forEach(function(item) {
if (item.metadata && item.metadata.role == role)
count += item.count;
});
}
});
return count;
},
countEntitiesAndQueuedWithRoles: function(role, role2)
{
var count = 0;
this.getOwnEntities().forEach(function(ent) {
if (ent.getMetadata("role") == role)
++count;
else if (ent.getMetadata("role") == role2)
++count;
var queue = ent.trainingQueue();
if (queue)
{
queue.forEach(function(item) {
if (item.metadata && item.metadata.role == role)
count += item.count;
else if (item.metadata && item.metadata.role == role2)
count += item.count;
});
}
});
return count;
},
/**
* Find buildings that are capable of training the given unit type,
* and aren't already too busy.
*/
findTrainers: function(template)
{
var maxQueueLength = 3; // avoid tying up resources in giant training queues
return this.getOwnEntities().filter(function(ent) {
var trainable = ent.trainableEntities();
if (!trainable || trainable.indexOf(template) == -1)
return false;
var queue = ent.trainingQueue();
if (queue)
{
if (queue.length >= maxQueueLength)
return false;
}
return true;
});
},
/**
* Find units that are capable of constructing the given building type.
*/
findBuilders: function(template)
{
return this.getOwnEntities().filter(function(ent) {
var buildable = ent.buildableEntities();
if (!buildable || buildable.indexOf(template) == -1)
return false;
return true;
});
},
findFoundations: function(template)
{
return this.getOwnEntities().filter(function(ent) {
return (ent.foundationProgress() !== undefined);
});
},
findResourceSupplies: function()
{
var supplies = {};
this.entities.forEach(function(ent) {
var type = ent.resourceSupplyType();
if (!type)
return;
var amount = ent.resourceSupplyAmount();
if (!amount)
return;
var reportedType;
if (type.generic == "treasure")
reportedType = type.specific;
else
reportedType = type.generic;
if (!supplies[reportedType])
supplies[reportedType] = [];
supplies[reportedType].push({
"entity": ent,
"amount": amount,
"type": type,
"position": ent.position(),
});
});
return supplies;
},
getPopulationLimit: function()
{
return this.playerData.popLimit;
},
getPopulation: function()
{
return this.playerData.popCount;
},
getPlayerID: function()
{
return this.player;
},
/**
* Checks whether the player with the given id is an ally of the AI player
*/
isPlayerAlly: function(id)
{
return this.playerData.isAlly[id];
},
/**
* Checks whether the player with the given id is an enemy of the AI player
*/
isPlayerEnemy: function(id)
{
return this.playerData.isEnemy[id];
},
/**
* Checks whether an Entity object is owned by an ally of the AI player (or self)
*/
isEntityAlly: function(ent)
{
return (ent && ent.owner() !== undefined && this.playerData.isAlly[ent.owner()]);
},
/**
* Checks whether an Entity object is owned by an enemy of the AI player
*/
isEntityEnemy: function(ent)
{
return (ent && ent.owner() !== undefined && this.playerData.isEnemy[ent.owner()]);
},
/**
* Checks whether an Entity object is owned by the AI player
*/
isEntityOwn: function(ent)
{
return (ent && ent.owner() !== undefined && ent.owner() == this.player);
},
/**
* Returns player build limits
* an object where each key is a category corresponding to a build limit for the player.
*/
getEntityLimits: function()
{
return this.playerData.entityLimits;
},
/**
* Returns player build counts
* an object where each key is a category corresponding to the current building count for the player.
*/
getEntityCounts: function()
{
return this.playerData.entityCounts;
},
/**
* Checks if the player's build limit has been reached for the given category.
* The category comes from the entity template, specifically the
* BuildRestrictions/TrainingRestrictions components.
*/
isEntityLimitReached: function(category)
{
if (this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined)
return false;
// There's a special case of build limits per civ centre, so check that first
if (this.playerData.entityLimits[category].LimitPerCivCentre !== undefined)
return (this.playerData.entityCounts[category] >=
this.playerData.entityCounts["CivilCentre"] * this.playerData.entityLimits[category].LimitPerCivCentre);
else
return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]);
},
});

View File

@ -1,146 +0,0 @@
/*
* This is a primitive initial attempt at an AI player.
* The design isn't great and maybe the whole thing should be rewritten -
* the aim here is just to have something that basically works, and to
* learn more about what's really needed for a decent AI design.
*
* The basic idea is we have a collection of independent modules
* (EconomyManager, etc) which produce a list of plans.
* The modules are mostly stateless - each turn they look at the current
* world state, and produce some plans that will improve the state.
* E.g. if there's too few worker units, they'll do a plan to train
* another one. Plans are discarded after the current turn, if they
* haven't been executed.
*
* Plans are grouped into a small number of PlanGroups, and for each
* group we try to execute the highest-priority plans.
* If some plan groups need more resources to execute their highest-priority
* plan, we'll distribute any unallocated resources to that group's
* escrow account. Eventually they'll accumulate enough to afford their plan.
* (The purpose is to ensure resources are shared fairly between all the
* plan groups - none of them should be starved even if they're trying to
* execute a really expensive plan.)
*/
/*
* Lots of things we should fix:
*
* * Find entities with no assigned role, and give them something to do
* * Keep some units back for defence
* * Consistent terminology (type vs template etc)
* * ...
*
*/
function JuBotAI(settings)
{
// warn("Constructing JuBotAI for player "+settings.player);
BaseAI.call(this, settings);
this.turn = 0;
this.modules = [
new EconomyManager(),
new MilitaryAttackManager(),
];
this.planGroups = {
economyPersonnel: new PlanGroup(),
economyConstruction: new PlanGroup(),
militaryPersonnel: new PlanGroup(),
};
}
JuBotAI.prototype = new BaseAI();
JuBotAI.prototype.ShareResources = function(remainingResources, unaffordablePlans)
{
// Share our remaining resources among the plangroups that need
// to accumulate more resources, in proportion to their priorities
for each (var type in remainingResources.types)
{
// Skip resource types where we don't have any spare
if (remainingResources[type] <= 0)
continue;
// Find the plans that require some of this resource type,
// and the sum of their priorities
var ps = [];
var sumPriority = 0;
for each (var p in unaffordablePlans)
{
if (p.plan.getCost()[type] > p.group.getEscrow()[type])
{
ps.push(p);
sumPriority += p.priority;
}
}
// Avoid divisions-by-zero
if (!sumPriority)
continue;
// Share resources by priority, clamped to the amount the plan actually needs
for each (var p in ps)
{
var amount = Math.floor(remainingResources[type] * p.priority / sumPriority);
var max = p.plan.getCost()[type] - p.group.getEscrow()[type];
p.group.getEscrow()[type] += Math.min(max, amount);
}
}
};
JuBotAI.prototype.OnUpdate = function()
{
// Run the update every n turns, offset depending on player ID to balance the load
if ((this.turn + this.player) % 4 == 0)
{
var gameState = new GameState(this);
// Find the resources we have this turn that haven't already
// been allocated to an escrow account.
// (We need to do this before executing any plans, because those will
// distort the escrow figures.)
var remainingResources = gameState.getResources();
for each (var planGroup in this.planGroups)
remainingResources.subtract(planGroup.getEscrow());
Engine.ProfileStart("plan setup");
// Compute plans from each module
for each (var module in this.modules)
module.update(gameState, this.planGroups);
// print(uneval(this.planGroups)+"\n");
Engine.ProfileStop();
Engine.ProfileStart("plan execute");
// Execute as many plans as possible, and keep a record of
// which ones we can't afford yet
var unaffordablePlans = [];
for each (var planGroup in this.planGroups)
{
var plan = planGroup.executePlans(gameState, this.modules);
if (plan)
unaffordablePlans.push({"group": planGroup, "priority": plan.priority, "plan": plan.plan});
}
Engine.ProfileStop();
this.ShareResources(remainingResources, unaffordablePlans);
// print(uneval(this.planGroups)+"\n");
// Reset the temporary plan data
for each (var planGroup in this.planGroups)
planGroup.resetPlans();
}
// if (this.turn == 0){
// this.chat("Good morning. Please prepare for annihilation. Jubal apologises for any inconvenience likely to be caused by your imminent demise.");
// }
this.turn++;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,284 +0,0 @@
var BuildingConstructionPlan = Class({
_init: function(gameState, type, indno)
{
this.type = gameState.applyCiv(type);
var template = gameState.getTemplate(this.type);
if (!template)
{
this.invalidTemplate = true;
return;
}
this.cost = new Resources(template.cost());
},
canExecute: function(gameState)
{
if (this.invalidTemplate)
return false;
// TODO: verify numeric limits etc
var builders = gameState.findBuilders(this.type);
return (builders.length != 0);
},
execute: function(gameState)
{
// warn("Executing BuildingConstructionPlan "+uneval(this));
var builders = gameState.findBuilders(this.type).toEntityArray();
// We don't care which builder we assign, since they won't actually
// do the building themselves - all we care about is that there is
// some unit that can start the foundation
var pos = this.findGoodPosition(gameState);
builders[0].construct(this.type, pos.x, pos.z, pos.angle);
},
getCost: function()
{
return this.cost;
},
/**
* Make each cell's 16-bit value at least one greater than each of its
* neighbours' values. (If the grid is initialised with 0s and 65535s,
* the result of each cell is its Manhattan distance to the nearest 0.)
*
* TODO: maybe this should be 8-bit (and clamp at 255)?
*/
expandInfluences: function(grid, w, h)
{
for (var y = 0; y < h; ++y)
{
var min = 65535;
for (var x = 0; x < w; ++x)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
for (var x = w-2; x >= 0; --x)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
}
for (var x = 0; x < w; ++x)
{
var min = 65535;
for (var y = 0; y < h; ++y)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
for (var y = h-2; y >= 0; --y)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
}
},
/**
* Add a circular linear-falloff shape to a grid.
*/
addInfluence: function(grid, w, h, cx, cy, maxDist)
{
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(w, cx + maxDist);
var y1 = Math.min(h, cy + maxDist);
for (var y = y0; y < y1; ++y)
{
for (var x = x0; x < x1; ++x)
{
var dx = x - cx;
var dy = y - cy;
var r = Math.sqrt(dx*dx + dy*dy);
if (r < maxDist)
grid[x + y*w] += maxDist - r;
}
}
},
/**
* Add a circular linear-falloff shape to a grid.
*/
subtractInfluence: function(grid, w, h, cx, cy, maxDist)
{
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(w, cx + maxDist);
var y1 = Math.min(h, cy + maxDist);
for (var y = y0; y < y1; ++y)
{
for (var x = x0; x < x1; ++x)
{
var dx = x - cx;
var dy = y - cy;
var r = Math.sqrt(dx*dx + dy*dy);
if (r < maxDist)
grid[x + y*w] -= maxDist - r;
}
}
},
findGoodPosition: function(gameState)
{
var self = this;
var cellSize = 4; // size of each tile
var template = gameState.getTemplate(this.type);
// Find all tiles in valid territory that are far enough away from obstructions:
var passabilityMap = gameState.getPassabilityMap();
var territoryMap = gameState.getTerritoryMap();
const TERRITORY_PLAYER_MASK = 0x3F;
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction");
if (passabilityMap.data.length != territoryMap.data.length)
error("passability and territory data are not matched!");
// See BuildRestrictions.js
switch(template.buildPlacementType())
{
case "shore":
obstructionMask |= gameState.getPassabilityClassMask("building-shore");
break;
case "land":
default:
obstructionMask |= gameState.getPassabilityClassMask("building-land");
}
var playerID = gameState.getPlayerID();
var buildOwn = template.hasBuildTerritory("own");
var buildAlly = template.hasBuildTerritory("ally");
var buildNeutral = template.hasBuildTerritory("neutral");
var buildEnemy = template.hasBuildTerritory("enemy");
var obstructionTiles = new Uint16Array(passabilityMap.data.length);
for (var i = 0; i < passabilityMap.data.length; ++i)
{
var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK);
var invalidTerritory = (
(!buildOwn && tilePlayer == playerID) ||
(!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) ||
(!buildNeutral && tilePlayer == 0) ||
(!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer !=0)
);
obstructionTiles[i] = (invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535;
}
// Engine.DumpImage("tiles0.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 64);
this.expandInfluences(obstructionTiles, passabilityMap.width, passabilityMap.height);
// TODO: handle distance restrictions for e.g. CivCentres
// Compute each tile's closeness to friendly structures:
var friendlyTiles = new Uint16Array(passabilityMap.data.length);
gameState.getOwnEntities().forEach(function(ent) {
if (ent.hasClass("Structure"))
{
var infl = 15;
if (ent.hasClass("CivCentre"))
{
infl = infl*5;
}
else if (ent.hasClass("Village"))
{
infl = 0;
}
else if (ent.hasClass("Economic"))
{
infl = 20;
}
else
{
infl = 0;
}
var pos = ent.position();
var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / cellSize);
self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl);
}
});
// var foetargets = gameState.entities.filter(function(ent) {
// return (ent.isEnemy());
// });
// foetargets.forEach(function(ent) {
// if (ent.hasClass("CivCentre"))
// {
// var infl = 100;
// var pos = ent.position();
// var x = Math.round(pos[0] / cellSize);
// var z = Math.round(pos[1] / cellSize);
// self.subtractInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl);
// }
// });
// Find target building's approximate obstruction radius,
// and expand by a bit to make sure we're not too close
var radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
// Find the best non-obstructed tile
var bestIdx = 0;
var bestVal = -1;
for (var i = 0; i < passabilityMap.data.length; ++i)
{
if (obstructionTiles[i] > radius)
{
var v = friendlyTiles[i];
//var foe = enemyTiles[i];
//JuBotAI.prototype.chat(v);
//JuBotAI.prototype.chat(i);
//JuBotAI.prototype.chat(foe);
if (v >= bestVal)
{
bestVal = v;
bestIdx = i;
//JuBotAI.prototype.chat("BestVal is " + bestVal + ", and bestIdx is " + bestIdx + ".");
}
}
}
var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize;
// Engine.DumpImage("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32);
// Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256);
// TODO: special dock placement requirements
// Fixed angle to match fixed starting cam
var angle = 0.75*Math.PI;
return {
"x": x,
"z": z,
"angle": angle
};
},
});

View File

@ -1,277 +0,0 @@
var BuildingConstructionPlanDefensePoints = Class({
_init: function(gameState, type, indno)
{
this.type = gameState.applyCiv(type);
var template = gameState.getTemplate(this.type);
if (!template)
{
this.invalidTemplate = true;
return;
}
this.cost = new Resources(template.cost());
},
canExecute: function(gameState)
{
if (this.invalidTemplate)
return false;
// TODO: verify numeric limits etc
var builders = gameState.findBuilders(this.type);
return (builders.length != 0);
},
execute: function(gameState)
{
// warn("Executing BuildingConstructionPlan "+uneval(this));
var builders = gameState.findBuilders(this.type).toEntityArray();
// We don't care which builder we assign, since they won't actually
// do the building themselves - all we care about is that there is
// some unit that can start the foundation
var pos = this.findGoodPosition(gameState);
builders[0].construct(this.type, pos.x, pos.z, pos.angle);
},
getCost: function()
{
return this.cost;
},
/**
* Make each cell's 16-bit value at least one greater than each of its
* neighbours' values. (If the grid is initialised with 0s and 65535s,
* the result of each cell is its Manhattan distance to the nearest 0.)
*
* TODO: maybe this should be 8-bit (and clamp at 255)?
*/
expandInfluences: function(grid, w, h)
{
for (var y = 0; y < h; ++y)
{
var min = 65535;
for (var x = 0; x < w; ++x)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
for (var x = w-2; x >= 0; --x)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
}
for (var x = 0; x < w; ++x)
{
var min = 65535;
for (var y = 0; y < h; ++y)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
for (var y = h-2; y >= 0; --y)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
}
},
/**
* Add a circular linear-falloff shape to a grid.
*/
addInfluence: function(grid, w, h, cx, cy, maxDist)
{
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(w, cx + maxDist);
var y1 = Math.min(h, cy + maxDist);
for (var y = y0; y < y1; ++y)
{
for (var x = x0; x < x1; ++x)
{
var dx = x - cx;
var dy = y - cy;
var r = Math.sqrt(dx*dx + dy*dy);
if (r < maxDist)
grid[x + y*w] += maxDist - r;
}
}
},
/**
* Add a circular linear-falloff shape to a grid.
*/
subtractInfluence: function(grid, w, h, cx, cy, maxDist)
{
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(w, cx + maxDist);
var y1 = Math.min(h, cy + maxDist);
for (var y = y0; y < y1; ++y)
{
for (var x = x0; x < x1; ++x)
{
var dx = x - cx;
var dy = y - cy;
var r = Math.sqrt(dx*dx + dy*dy);
if (r < maxDist)
grid[x + y*w] -= maxDist - r;
}
}
},
findGoodPosition: function(gameState)
{
var self = this;
var cellSize = 4; // size of each tile
var template = gameState.getTemplate(this.type);
// Find all tiles in valid territory that are far enough away from obstructions:
var passabilityMap = gameState.getPassabilityMap();
var territoryMap = gameState.getTerritoryMap();
const TERRITORY_PLAYER_MASK = 0x7F;
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction");
if (passabilityMap.data.length != territoryMap.data.length)
error("passability and territory data are not matched!");
// See BuildRestrictions.js
switch(template.buildPlacementType())
{
case "shore":
obstructionMask |= gameState.getPassabilityClassMask("building-shore");
break;
case "land":
default:
obstructionMask |= gameState.getPassabilityClassMask("building-land");
}
var playerID = gameState.getPlayerID();
var buildOwn = template.hasBuildTerritory("own");
var buildAlly = template.hasBuildTerritory("ally");
var buildNeutral = template.hasBuildTerritory("neutral");
var buildEnemy = template.hasBuildTerritory("enemy");
var obstructionTiles = new Uint16Array(passabilityMap.data.length);
for (var i = 0; i < passabilityMap.data.length; ++i)
{
var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK);
var invalidTerritory = (
(!buildOwn && tilePlayer == playerID) ||
(!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) ||
(!buildNeutral && tilePlayer == 0) ||
(!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer !=0)
);
obstructionTiles[i] = (invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535;
}
// Engine.DumpImage("tiles0.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 64);
this.expandInfluences(obstructionTiles, passabilityMap.width, passabilityMap.height);
// TODO: handle distance restrictions for e.g. CivCentres
// Compute each tile's closeness to friendly structures:
var friendlyTiles = new Uint16Array(passabilityMap.data.length);
gameState.getOwnEntities().forEach(function(ent) {
if (ent.hasClass("Structure"))
{
var infl = 0;
if (ent.hasClass("Economic") && ent.getMetadata("tower") != 1)
{
infl = 20;
ent.setMetadata("tower", 1);
}
else
{
infl = 0;
}
var pos = ent.position();
var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / cellSize);
self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl);
}
});
// var foetargets = gameState.entities.filter(function(ent) {
// return (ent.isEnemy());
// });
// foetargets.forEach(function(ent) {
// if (ent.hasClass("CivCentre"))
// {
// var infl = 100;
// var pos = ent.position();
// var x = Math.round(pos[0] / cellSize);
// var z = Math.round(pos[1] / cellSize);
// self.subtractInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl);
// }
// });
// Find target building's approximate obstruction radius,
// and expand by a bit to make sure we're not too close
var radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
// Find the best non-obstructed tile
var bestIdx = 0;
var bestVal = -1;
for (var i = 0; i < passabilityMap.data.length; ++i)
{
if (obstructionTiles[i] > radius)
{
var v = friendlyTiles[i];
//var foe = enemyTiles[i];
//JuBotAI.prototype.chat(v);
//JuBotAI.prototype.chat(i);
//JuBotAI.prototype.chat(foe);
if (v >= bestVal)
{
bestVal = v;
bestIdx = i;
//JuBotAI.prototype.chat("BestVal is " + bestVal + ", and bestIdx is " + bestIdx + ".");
}
}
}
var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize;
// Engine.DumpImage("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32);
// Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256);
// TODO: special dock placement requirements
// Fixed angle to match fixed starting cam
var angle = 0.75*Math.PI;
return {
"x": x,
"z": z,
"angle": angle
};
},
});

View File

@ -1,412 +0,0 @@
var BuildingConstructionPlanEcon = Class({
_init: function(gameState, type, indno, resourcepos)
{
this.type = gameState.applyCiv(type);
this.resourceposition = resourcepos;
this.pos = this.findGoodPosition(gameState);
var template = gameState.getTemplate(this.type);
if (!template)
{
this.invalidTemplate = true;
return;
}
this.cost = new Resources(template.cost());
},
canExecute: function(gameState)
{
if (this.invalidTemplate)
return false;
// TODO: verify numeric limits etc
var builders = gameState.findBuilders(this.type);
return (builders.length != 0);
},
execute: function(gameState)
{
// warn("Executing BuildingConstructionPlan "+uneval(this));
var builders = gameState.findBuilders(this.type).toEntityArray();
// We don't care which builder we assign, since they won't actually
// do the building themselves - all we care about is that there is
// some unit that can start the foundation
//Check distance from Pos to CC - we don't want to get too far from the centre, for now.
// THIS SHOULD BE A GLOBAL VARIABLE
//var distcheckold = 10000;
// CHECK DISTANCE
//gameState.getOwnEntities().forEach(function(centre) {
//if (centre.hasClass("CivCentre"))
// {
//var centrePosition = centre.position();
//var dx = pos.x - centrePosition[0];
//var dz = pos.z - centrePosition[1];
//var distcheck = Math.sqrt(dx*dx + dz*dz);
//if (distcheck < distcheckold){
//distcheckold = distcheck;
//}
//}
//});
//Distcheck is thus the distance to the nearest CC - we only build if it's low enough.
///if (distcheckold < 400){
builders[0].construct(this.type, this.pos.x, this.pos.z, this.pos.angle);
//}
//else {
//var pos = this.findGoodPositionOldStyle(gameState);
//builders[0].construct(this.type, pos.x, pos.z, pos.angle);
//}
},
getCost: function()
{
return this.cost;
},
/**
* Make each cell's 16-bit value at least one greater than each of its
* neighbours' values. (If the grid is initialised with 0s and 65535s,
* the result of each cell is its Manhattan distance to the nearest 0.)
*
* TODO: maybe this should be 8-bit (and clamp at 255)?
*/
expandInfluences: function(grid, w, h)
{
for (var y = 0; y < h; ++y)
{
var min = 65535;
for (var x = 0; x < w; ++x)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
for (var x = w-2; x >= 0; --x)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
}
for (var x = 0; x < w; ++x)
{
var min = 65535;
for (var y = 0; y < h; ++y)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
for (var y = h-2; y >= 0; --y)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
}
},
/**
* Add a circular linear-falloff shape to a grid.
*/
addInfluence: function(grid, w, h, cx, cy, maxDist)
{
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(w, cx + maxDist);
var y1 = Math.min(h, cy + maxDist);
for (var y = y0; y < y1; ++y)
{
for (var x = x0; x < x1; ++x)
{
var dx = x - cx;
var dy = y - cy;
var r = Math.sqrt(dx*dx + dy*dy);
if (r < maxDist)
grid[x + y*w] += maxDist - r;
}
}
},
findGoodPosition: function(gameState)
{
var self = this;
var cellSize = 4; // size of each tile
var template = gameState.getTemplate(this.type);
// Find all tiles in valid territory that are far enough away from obstructions:
var passabilityMap = gameState.getPassabilityMap();
var territoryMap = gameState.getTerritoryMap();
const TERRITORY_PLAYER_MASK = 0x7F;
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction");
if (passabilityMap.data.length != territoryMap.data.length)
error("passability and territory data are not matched!");
// See BuildRestrictions.js
switch(template.buildPlacementType())
{
case "shore":
obstructionMask |= gameState.getPassabilityClassMask("building-shore");
break;
case "land":
default:
obstructionMask |= gameState.getPassabilityClassMask("building-land");
}
var playerID = gameState.getPlayerID();
var buildOwn = template.hasBuildTerritory("own");
var buildAlly = template.hasBuildTerritory("ally");
var buildNeutral = template.hasBuildTerritory("neutral");
var buildEnemy = template.hasBuildTerritory("enemy");
var obstructionTiles = new Uint16Array(passabilityMap.data.length);
for (var i = 0; i < passabilityMap.data.length; ++i)
{
var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK);
var invalidTerritory = (
(!buildOwn && tilePlayer == playerID) ||
(!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) ||
(!buildNeutral && tilePlayer == 0) ||
(!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer !=0)
);
obstructionTiles[i] = (invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535;
}
// Engine.DumpImage("tiles0.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 64);
this.expandInfluences(obstructionTiles, passabilityMap.width, passabilityMap.height);
// TODO: handle distance restrictions for e.g. CivCentres
// Compute each tile's closeness to friendly structures:
var friendlyTiles = new Uint16Array(passabilityMap.data.length);
var infl = 25;
var pos = this.resourceposition;
var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / cellSize);
self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl);
//Look at making sure we're a long way from enemy civ centres as well.
// Find target building's approximate obstruction radius,
// and expand by a bit to make sure we're not too close
var template = gameState.getTemplate(this.type);
var radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
// Find the best non-obstructed tile
var bestIdx = 0;
var bestVal = -1;
for (var i = 0; i < passabilityMap.data.length; ++i)
{
if (obstructionTiles[i] > radius)
{
var v = friendlyTiles[i];
//var foe = enemyTiles[i];
//JuBotAI.prototype.chat(v);
//JuBotAI.prototype.chat(i);
//JuBotAI.prototype.chat(foe);
if (v >= bestVal)
{
bestVal = v;
bestIdx = i;
}
}
}
//warn("BestVal is " + bestVal + ", and bestIdx is " + bestIdx + ".");
if (bestVal <= 5) {
//warn("Trying to build Civ Centre");
this.type = gameState.applyCiv("structures/{civ}_civil_centre");
var playerID = gameState.getPlayerID();
var buildOwn = template.hasBuildTerritory("own");
var buildAlly = template.hasBuildTerritory("ally");
var buildNeutral = template.hasBuildTerritory("neutral");
var buildEnemy = template.hasBuildTerritory("enemy");
// Since this is for CCs, only refrain from building on actual enemy territory.
var obstructionTilesII = new Uint16Array(passabilityMap.data.length);
for (var i = 0; i < passabilityMap.data.length; ++i)
{
var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK);
var invalidTerritory = (
(!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer !=0)
);
obstructionTilesII[i] = (invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535;
}
// Engine.DumpImage("tiles0.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 64);
this.expandInfluences(obstructionTilesII, passabilityMap.width, passabilityMap.height);
// TODO: handle distance restrictions for e.g. CivCentres
// Compute each tile's closeness to friendly structures:
var friendlyTiles = new Uint16Array(passabilityMap.data.length);
var infl = 50;
var pos = this.resourceposition;
var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / cellSize);
self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl);
var template = gameState.getTemplate(this.type);
var radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
var bestIdx = 0;
var bestVal = -1;
for (var i = 0; i < passabilityMap.data.length; ++i)
{
if (obstructionTilesII[i] > radius)
{
var v = friendlyTiles[i];
//var foe = enemyTiles[i];
//JuBotAI.prototype.chat(v);
//JuBotAI.prototype.chat(i);
//JuBotAI.prototype.chat(foe);
if (v >= bestVal)
{
bestVal = v;
bestIdx = i;
}
}
}
//warn("For CC, BestVal is " + bestVal + ", and bestIdx is " + bestIdx + ".");
}
var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize;
// Engine.DumpImage("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32);
// Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256);
// Fixed angle to match fixed starting cam
var angle = 0.75*Math.PI;
return {
"canbuild": true,
"x": x,
"z": z,
"angle": angle
};
},
findGoodPositionOldStyle: function(gameState)
{
var self = this;
var cellSize = 4; // size of each tile
// First, find all tiles that are far enough away from obstructions:
var passabilityMap = gameState.getPassabilityMap();
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction");
// Only accept valid land tiles (we don't handle docks yet)
obstructionMask |= gameState.getPassabilityClassMask("building-land");
var obstructionTiles = new Uint16Array(passabilityMap.data.length);
for (var i = 0; i < passabilityMap.data.length; ++i)
obstructionTiles[i] = (passabilityMap.data[i] & obstructionMask) ? 0 : 65535;
// Engine.DumpImage("tiles0.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 64);
this.expandInfluences(obstructionTiles, passabilityMap.width, passabilityMap.height);
// Compute each tile's closeness to friendly structures:
var friendlyTiles = new Uint16Array(passabilityMap.data.length);
gameState.getOwnEntities().forEach(function(ent) {
if (ent.hasClass("Structure"))
{
var infl = 32;
if (ent.hasClass("CivCentre"))
infl *= 4;
var pos = ent.position();
var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / cellSize);
self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl);
}
});
//Look at making sure we're a long way from enemy civ centres as well.
// var enemyTiles = new Uint16Array(passabilityMap.data.length);
//
// var foetargets = gameState.entities.filter(function(ent) {
// return (ent.isEnemy());
// });
// foetargets.forEach(function(ent) {
// if (ent.hasClass("CivCentre"))
// {
// var infl = 32;
// var pos = ent.position();
// var x = Math.round(pos[0] / cellSize);
// var z = Math.round(pos[1] / cellSize);
// self.addInfluence(enemyTiles, passabilityMap.width, passabilityMap.height, x, z, infl);
// }
// });
// Find target building's approximate obstruction radius,
// and expand by a bit to make sure we're not too close
var template = gameState.getTemplate(this.type);
var radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
// Find the best non-obstructed tile
var bestIdx = 0;
var bestVal = -1;
for (var i = 0; i < passabilityMap.data.length; ++i)
{
if (obstructionTiles[i] > radius)
{
var v = friendlyTiles[i];
//var foe = enemyTiles[i];
//JuBotAI.prototype.chat(v);
//JuBotAI.prototype.chat(i);
//JuBotAI.prototype.chat(foe);
if (v >= bestVal)
{
bestVal = v;
bestIdx = i;
//JuBotAI.prototype.chat("BestVal is " + bestVal + ", and bestIdx is " + bestIdx + ".");
}
}
}
var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize;
// Engine.DumpImage("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32);
// Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256);
// Randomise the angle a little, to look less artificial
//var angle = Math.PI + (Math.random()*2-1) * Math.PI/24;
var angle = Math.PI + (Math.PI / 4);
return {
"x": x,
"z": z,
"angle": angle
};
},
});

View File

@ -1,277 +0,0 @@
// THIS CLASS IS FOR BUILDING FARMS AND ANIMAL PENS ONLY
var BuildingConstructionPlanResources = Class({
_init: function(gameState, type, indno)
{
this.type = gameState.applyCiv(type);
var template = gameState.getTemplate(this.type);
if (!template)
{
this.invalidTemplate = true;
return;
}
this.cost = new Resources(template.cost());
},
canExecute: function(gameState)
{
if (this.invalidTemplate)
return false;
// TODO: verify numeric limits etc
var builders = gameState.findBuilders(this.type);
return (builders.length != 0);
},
execute: function(gameState)
{
// warn("Executing BuildingConstructionPlan "+uneval(this));
var builders = gameState.findBuilders(this.type).toEntityArray();
// We don't care which builder we assign, since they won't actually
// do the building themselves - all we care about is that there is
// some unit that can start the foundation
var pos = this.findGoodPosition(gameState);
builders[0].construct(this.type, pos.x, pos.z, pos.angle);
},
getCost: function()
{
return this.cost;
},
/**
* Make each cell's 16-bit value at least one greater than each of its
* neighbours' values. (If the grid is initialised with 0s and 65535s,
* the result of each cell is its Manhattan distance to the nearest 0.)
*
* TODO: maybe this should be 8-bit (and clamp at 255)?
*/
expandInfluences: function(grid, w, h)
{
for (var y = 0; y < h; ++y)
{
var min = 65535;
for (var x = 0; x < w; ++x)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
for (var x = w-2; x >= 0; --x)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
}
for (var x = 0; x < w; ++x)
{
var min = 65535;
for (var y = 0; y < h; ++y)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
for (var y = h-2; y >= 0; --y)
{
var g = grid[x + y*w];
if (g > min) grid[x + y*w] = min;
else if (g < min) min = g;
++min;
}
}
},
/**
* Add a circular linear-falloff shape to a grid.
*/
addInfluence: function(grid, w, h, cx, cy, maxDist)
{
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(w, cx + maxDist);
var y1 = Math.min(h, cy + maxDist);
for (var y = y0; y < y1; ++y)
{
for (var x = x0; x < x1; ++x)
{
var dx = x - cx;
var dy = y - cy;
var r = Math.sqrt(dx*dx + dy*dy);
if (r < maxDist)
grid[x + y*w] += maxDist - r;
}
}
},
/**
* Add a circular linear-falloff shape to a grid.
*/
subtractInfluence: function(grid, w, h, cx, cy, maxDist)
{
var x0 = Math.max(0, cx - maxDist);
var y0 = Math.max(0, cy - maxDist);
var x1 = Math.min(w, cx + maxDist);
var y1 = Math.min(h, cy + maxDist);
for (var y = y0; y < y1; ++y)
{
for (var x = x0; x < x1; ++x)
{
var dx = x - cx;
var dy = y - cy;
var r = Math.sqrt(dx*dx + dy*dy);
if (r < maxDist)
grid[x + y*w] -= maxDist - r;
}
}
},
findGoodPosition: function(gameState)
{
var self = this;
var cellSize = 4; // size of each tile
var template = gameState.getTemplate(this.type);
// Find all tiles in valid territory that are far enough away from obstructions:
var passabilityMap = gameState.getPassabilityMap();
var territoryMap = gameState.getTerritoryMap();
const TERRITORY_PLAYER_MASK = 0x7F;
var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction");
if (passabilityMap.data.length != territoryMap.data.length)
error("passability and territory data are not matched!");
// See BuildRestrictions.js
switch(template.buildPlacementType())
{
case "shore":
obstructionMask |= gameState.getPassabilityClassMask("building-shore");
break;
case "land":
default:
obstructionMask |= gameState.getPassabilityClassMask("building-land");
}
var playerID = gameState.getPlayerID();
var buildOwn = template.hasBuildTerritory("own");
var buildAlly = template.hasBuildTerritory("ally");
var buildNeutral = template.hasBuildTerritory("neutral");
var buildEnemy = template.hasBuildTerritory("enemy");
var obstructionTiles = new Uint16Array(passabilityMap.data.length);
for (var i = 0; i < passabilityMap.data.length; ++i)
{
var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK);
var invalidTerritory = (
(!buildOwn && tilePlayer == playerID) ||
(!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) ||
(!buildNeutral && tilePlayer == 0) ||
(!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer !=0)
);
obstructionTiles[i] = (invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535;
}
// Engine.DumpImage("tiles0.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 64);
this.expandInfluences(obstructionTiles, passabilityMap.width, passabilityMap.height);
// TODO: handle distance restrictions for e.g. CivCentres
// Compute each tile's closeness to friendly structures:
var friendlyTiles = new Uint16Array(passabilityMap.data.length);
gameState.getOwnEntities().forEach(function(ent) {
if (ent.hasClass("Structure"))
{
var infl = 0;
if (ent.hasClass("CivCentre"))
{
infl = 5;
}
else if (ent.hasClass("DropsiteFood"))
{
infl = 40;
}
var pos = ent.position();
var x = Math.round(pos[0] / cellSize);
var z = Math.round(pos[1] / cellSize);
self.addInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl);
}
});
// var foetargets = gameState.entities.filter(function(ent) {
// return (ent.isEnemy());
// });
// foetargets.forEach(function(ent) {
// if (ent.hasClass("CivCentre"))
// {
// var infl = 100;
// var pos = ent.position();
// var x = Math.round(pos[0] / cellSize);
// var z = Math.round(pos[1] / cellSize);
// self.subtractInfluence(friendlyTiles, passabilityMap.width, passabilityMap.height, x, z, infl);
// }
// });
// Find target building's approximate obstruction radius,
// and expand by a bit to make sure we're not too close
var radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
// Find the best non-obstructed tile
var bestIdx = 0;
var bestVal = -1;
for (var i = 0; i < passabilityMap.data.length; ++i)
{
if (obstructionTiles[i] > radius)
{
var v = friendlyTiles[i];
//var foe = enemyTiles[i];
//JuBotAI.prototype.chat(v);
//JuBotAI.prototype.chat(i);
//JuBotAI.prototype.chat(foe);
if (v >= bestVal)
{
bestVal = v;
bestIdx = i;
//JuBotAI.prototype.chat("BestVal is " + bestVal + ", and bestIdx is " + bestIdx + ".");
}
}
}
var x = ((bestIdx % passabilityMap.width) + 0.5) * cellSize;
var z = (Math.floor(bestIdx / passabilityMap.width) + 0.5) * cellSize;
// Engine.DumpImage("tiles1.png", obstructionTiles, passabilityMap.width, passabilityMap.height, 32);
// Engine.DumpImage("tiles2.png", friendlyTiles, passabilityMap.width, passabilityMap.height, 256);
// TODO: special dock placement requirements
// Fixed angle to match fixed starting cam
var angle = 0.75*Math.PI;
return {
"x": x,
"z": z,
"angle": angle
};
},
});

View File

@ -1,52 +0,0 @@
var UnitTrainingPlan = Class({
_init: function(gameState, type, amount, metadata)
{
this.type = gameState.applyCiv(type);
this.amount = amount;
this.metadata = metadata;
var template = gameState.getTemplate(this.type);
if (!template)
{
this.invalidTemplate = true;
return;
}
this.cost = new Resources(template.cost());
this.cost.multiply(amount); // (assume no batch discount)
},
canExecute: function(gameState)
{
if (this.invalidTemplate)
return false;
// TODO: we should probably check pop caps
var trainers = gameState.findTrainers(this.type);
return (trainers.length != 0);
},
execute: function(gameState)
{
// warn("Executing UnitTrainingPlan "+uneval(this));
var trainers = gameState.findTrainers(this.type).toEntityArray();
// Prefer training buildings with short queues
// (TODO: this should also account for units added to the queue by
// plans that have already been executed this turn)
trainers.sort(function(a, b) {
return a.trainingQueueTime() - b.trainingQueueTime();
});
trainers[0].train(this.type, this.amount, this.metadata);
},
getCost: function()
{
return this.cost;
},
});

View File

@ -1,97 +0,0 @@
/**
* All plan classes must implement this interface.
*/
var IPlan = Class({
_init: function() { /* ... */ },
canExecute: function(gameState) { /* ... */ },
execute: function(gameState) { /* ... */ },
getCost: function() { /* ... */ },
});
/**
* Represents a prioritised collection of plans.
*/
var PlanGroup = Class({
_init: function()
{
this.escrow = new Resources({});
this.plans = [];
this.resourcetime = 10 * 1000;
this.resourcePlus = "food";
},
addPlan: function(priority, plan)
{
this.plans.push({"priority": priority, "plan": plan});
},
/**
* Executes all plans that we can afford, ordered by priority,
* and returns the highest-priority plan we couldn't afford (or null
* if none).
*/
executePlans: function(gameState, modules)
{
// Ignore impossible plans
var plans = this.plans.filter(function(p) { return p.plan.canExecute(gameState); });
// Sort by decreasing priority
plans.sort(function(a, b) { return b.priority - a.priority; });
// Execute as many plans as we can afford
while (plans.length && this.escrow.canAfford(plans[0].plan.getCost()))
{
var plan = plans.shift().plan;
this.escrow.subtract(plan.getCost());
plan.execute(gameState);
}
if (plans.length){
//if (gameState.getTimeElapsed() > this.resourcetime){
var tempcheck = new Resources(this.escrow)
for each (var pln in plans){
tempcheck.subtract(pln.plan.getCost());
}
var squid = Object.keys(tempcheck);
squid.sort(function(a, b) {
return tempcheck[a] - tempcheck[b];
});
var str = "Resource check";
for each (t in tempcheck.types){
//JuBotAI.prototype.chat(str + " " + tempcheck[t] + " " + t);
}
//JuBotAI.prototype.chat(tempcheck.types.forEach(function(type) { str + " " + tempcheck[type] + " " + type }));
//JuBotAI.prototype.chat("Resource order check " + squid[0] + " " + squid[1] + " " + squid[2] + " " + squid[3]);
//EconomyManager.prototype.resourcePlus = squid[0];
//JuBotAI.prototype.chat(EconomyManager.prototype.resourcePlus);
//JuBotAI.prototype.chat(squid[0]);
var str = squid[0];
//warn(squid[0]);
//JuBotAI.prototype.chat(str);
modules[0].gatherWeights[squid[0]] = modules[0].gatherWeights[squid[0]] + 1;
//warn(modules[0].gatherWeights[squid[0]]);
//JuBotAI.prototype.chat(lolwut);
//this.resourcetime = this.resourcetime + (60*1000);
//}
return plans[0];
}
else{
return null;
}
},
resetPlans: function()
{
this.plans = [];
},
getEscrow: function()
{
return this.escrow;
},
});

View File

@ -1,36 +0,0 @@
var Resources = Class({
types: ["food", "wood", "stone", "metal"],
_init: function(amounts)
{
for each (var t in this.types)
this[t] = amounts[t] || 0;
},
canAfford: function(that)
{
for each (var t in this.types)
if (this[t] < that[t])
return false;
return true;
},
add: function(that)
{
for each (var t in this.types)
this[t] += that[t];
},
subtract: function(that)
{
for each (var t in this.types)
this[t] -= that[t];
},
multiply: function(n)
{
for each (var t in this.types)
this[t] *= n;
},
});