0ad/binaries/data/mods/public/simulation/ai/aegis/worker.js

512 lines
18 KiB
JavaScript

/**
* This class makes a worker do as instructed by the economy manager
*/
var Worker = function(ent) {
this.ent = ent;
this.maxApproachTime = 45000;
this.unsatisfactoryResource = false; // if true we'll reguarly check if we can't have better now.
};
Worker.prototype.update = function(gameState) {
var subrole = this.ent.getMetadata(PlayerID, "subrole");
if (!this.ent.position() || (this.ent.getMetadata(PlayerID,"fleeing") && gameState.getTimeElapsed() - this.ent.getMetadata(PlayerID,"fleeing") < 8000)){
// If the worker has no position then no work can be done
return;
}
if (this.ent.getMetadata(PlayerID,"fleeing"))
this.ent.setMetadata(PlayerID,"fleeing", undefined);
if (subrole === "gatherer") {
if (this.ent.unitAIState().split(".")[1] !== "GATHER" && this.ent.unitAIState().split(".")[1] !== "COMBAT" && this.ent.unitAIState().split(".")[1] !== "RETURNRESOURCE"){
// TODO: handle combat for hunting animals
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0 ||
this.ent.resourceCarrying()[0].type === this.ent.getMetadata(PlayerID, "gather-type")){
Engine.ProfileStart("Start Gathering");
this.startGathering(gameState);
Engine.ProfileStop();
} else {
// Should deposit resources
Engine.ProfileStart("Return Resources");
if (!this.returnResources(gameState))
{
// no dropsite, abandon cargo.
// if we have a new order
if (this.ent.resourceCarrying()[0].type !== this.ent.getMetadata(PlayerID, "gather-type"))
this.startGathering(gameState);
else {
this.ent.setMetadata(PlayerID, "gather-type",undefined);
this.ent.setMetadata(PlayerID, "subrole", "idle");
this.ent.stopMoving();
}
}
Engine.ProfileStop();
}
this.startApproachingResourceTime = gameState.getTimeElapsed();
//Engine.PostCommand({"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [10,0,0]});
} else if (this.ent.unitAIState().split(".")[1] === "GATHER") {
if (this.unsatisfactoryResource && (this.ent.id() + gameState.ai.playedTurn) % 20 === 0)
{
Engine.ProfileStart("Start Gathering");
this.startGathering(gameState);
Engine.ProfileStop();
}
/*if (gameState.getTimeElapsed() - this.startApproachingResourceTime > this.maxApproachTime) {
if (this.gatheringFrom) {
var ent = gameState.getEntityById(this.gatheringFrom);
if ((ent && ent.resourceSupplyAmount() == ent.resourceSupplyMax())) {
// if someone gathers from it, it's only that the pathfinder sucks.
debug (ent.toString() + " is inaccessible");
ent.setMetadata(PlayerID, "inaccessible", true);
this.ent.flee(ent);
this.ent.setMetadata(PlayerID, "subrole", "idle");
this.gatheringFrom = undefined;
}
}
}*/
} else if (this.ent.unitAIState().split(".")[1] === "COMBAT") {
/*if (gameState.getTimeElapsed() - this.startApproachingResourceTime > this.maxApproachTime) {
var ent = gameState.getEntityById(this.ent.unitAIOrderData()[0].target);
if (ent && !ent.isHurt()) {
// if someone gathers from it, it's only that the pathfinder sucks.
debug (ent.toString() + " is inaccessible from Combat");
ent.setMetadata(PlayerID, "inaccessible", true);
this.ent.flee(ent);
this.ent.setMetadata(PlayerID, "subrole", "idle");
this.gatheringFrom = undefined;
}
}*/
} else {
this.startApproachingResourceTime = gameState.getTimeElapsed();
}
} else if(subrole === "builder") {
if (this.ent.unitAIState().split(".")[1] !== "REPAIR"){
var target = this.ent.getMetadata(PlayerID, "target-foundation");
if (target.foundationProgress() === undefined && target.needsRepair() == false)
this.ent.setMetadata(PlayerID, "subrole", "idle");
else
this.ent.repair(target);
}
this.startApproachingResourceTime = gameState.getTimeElapsed();
//Engine.PostCommand({"type": "set-shading-color", "entities": [this.ent.id()], "rgb": [0,10,0]});
} else if(subrole === "hunter") {
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0){
Engine.ProfileStart("Start Hunting");
this.startHunting(gameState);
Engine.ProfileStop();
}
} else {
this.startApproachingResourceTime = gameState.getTimeElapsed();
}
};
Worker.prototype.startGathering = function(gameState){
var resource = this.ent.getMetadata(PlayerID, "gather-type");
var ent = this.ent;
if (!ent.position()){
// TODO: work out what to do when entity has no position
return;
}
this.unsatisfactoryResource = false;
// TODO: this is not necessarily optimal.
// find closest dropsite which has nearby resources of the correct type
var minDropsiteDist = Math.min(); // set to infinity initially
var nearestResources = undefined;
var nearestDropsite = undefined;
// first step: count how many dropsites we have that have enough resources "close" to them.
// TODO: this is a huge part of multi-base support. Count only those in the same base as the worker.
var number = 0;
var ourDropsites = gameState.getOwnDropsites(resource);
if (ourDropsites.length === 0)
{
debug ("We do not have a dropsite for " + resource + ", aborting");
return;
}
ourDropsites.forEach(function (dropsite) {
if (dropsite.getMetadata(PlayerID, "linked-resources-" +resource) !== undefined
&& dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) !== undefined && dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 200) {
number++;
}
});
//debug ("Available " +resource + " dropsites: " +ourDropsites.length);
// Allright second step, if there are any such dropsites, we pick the closest.
// we pick one with a lot of resource, or we pick the only one available (if it's high enough, otherwise we'll see with "far" below).
if (number > 0)
{
ourDropsites.forEach(function (dropsite) { //}){
if (dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) == undefined)
return;
if (dropsite.position() && (dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 700 || (number === 1 && dropsite.getMetadata(PlayerID, "resource-quantity-" +resource) > 200) ) ) {
var dist = SquareVectorDistance(ent.position(), dropsite.position());
if (dist < minDropsiteDist){
minDropsiteDist = dist;
nearestResources = dropsite.getMetadata(PlayerID, "linked-resources-" + resource);
nearestDropsite = dropsite;
}
}
});
}
//debug ("Nearest dropsite: " +nearestDropsite);
// Now if we have no dropsites, we repeat the process with resources "far" from dropsites but still linked with them.
// I add the "close" value for code sanity.
// Again, we choose a dropsite with a lot of resources left, or we pick the only one available (in this case whatever happens).
if (!nearestResources || nearestResources.length === 0) {
//debug ("here(1)");
gameState.getOwnDropsites(resource).forEach(function (dropsite){ //}){
var quantity = dropsite.getMetadata(PlayerID, "resource-quantity-" +resource)+dropsite.getMetadata(PlayerID, "resource-quantity-far-" +resource);
if (dropsite.position() && (quantity) > 700 || number === 1) {
var dist = SquareVectorDistance(ent.position(), dropsite.position());
if (dist < minDropsiteDist){
minDropsiteDist = dist;
nearestResources = dropsite.getMetadata(PlayerID, "linked-resources-" + resource);
nearestDropsite = dropsite;
}
}
});
this.unsatisfactoryResource = true;
//debug ("Nearest dropsite: " +nearestDropsite);
}
// If we still haven't found any fitting dropsite...
// Then we'll just pick any resource, and we'll check for the closest dropsite to that one
if (!nearestResources || nearestResources.length === 0){
//debug ("No fitting dropsite for " + resource + " found, iterating the map.");
nearestResources = gameState.getResourceSupplies(resource);
this.unsatisfactoryResource = true;
}
if (nearestResources.length === 0){
if (resource === "food")
{
if (this.buildAnyField(gameState))
return;
debug("No " + resource + " found! (1)");
}
else
debug("No " + resource + " found! (1)");
return;
}
//debug("Found " + nearestResources.length + "spots for " + resource);
/*if (!nearestDropsite) {
debug ("No dropsite for " +resource);
return;
}*/
var supplies = [];
var nearestSupplyDist = Math.min();
var nearestSupply = undefined;
// filter resources
// TODo: add a bonus for resources with a lot of resources left, perhaps, to spread gathering?
nearestResources.forEach(function(supply) { //}){
// sanity check, perhaps sheep could be garrisoned?
if (!supply.position()) {
//debug ("noposition");
return;
}
if (supply.getMetadata(PlayerID, "inaccessible") === true) {
//debug ("inaccessible");
return;
}
if (supply.isFull() === true || (supply.maxGatherers() - supply.resourceSupplyGatherers().length == 0) ||
(gameState.turnCache["ressGathererNB"] && gameState.turnCache["ressGathererNB"][supply.id()]
&& gameState.turnCache["ressGathererNB"][supply.id()] + supply.resourceSupplyGatherers().length >= supply.maxGatherers())) {
return;
}
// Don't gather enemy farms
if (!supply.isOwn(PlayerID) && supply.owner() !== 0) {
//debug ("enemy");
return;
}
// quickscope accessbility check.
if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(), true)) {
//debug ("nopath");
return;
}
// some simple check for chickens: if they're in a square that's inaccessible, we won't gather from them.
if (supply.footprintRadius() < 1)
{
var fakeMap = new Map(gameState,gameState.getMap().data);
var id = fakeMap.gamePosToMapPos(supply.position())[0] + fakeMap.width*fakeMap.gamePosToMapPos(supply.position())[1];
if ( (gameState.sharedScript.passabilityClasses["pathfinderObstruction"] & gameState.getMap().data[id]) )
{
supply.setMetadata(PlayerID, "inaccessible", true)
return;
}
}
// measure the distance to the resource (largely irrelevant)
var dist = SquareVectorDistance(supply.position(), ent.position());
if (dist > 4900 && supply.hasClass("Animal"))
return;
// Add on a factor for the nearest dropsite if one exists
if (nearestDropsite !== undefined ){
dist += 4*SquareVectorDistance(supply.position(), nearestDropsite.position());
dist /= 5.0;
}
var territoryOwner = Map.createTerritoryMap(gameState).getOwner(supply.position());
if (territoryOwner != PlayerID && territoryOwner != 0) {
dist *= 3.0;
//return;
}
// Go for treasure as a priority
if (dist < 40000 && supply.resourceSupplyType().generic == "treasure"){
dist /= 1000;
}
if (dist < nearestSupplyDist) {
nearestSupplyDist = dist;
nearestSupply = supply;
}
});
if (nearestSupply) {
var pos = nearestSupply.position();
// find a fitting dropsites in case we haven't already.
if (!nearestDropsite) {
ourDropsites.forEach(function (dropsite){ //}){
if (dropsite.position()){
var dist = SquareVectorDistance(pos, dropsite.position());
if (dist < minDropsiteDist){
minDropsiteDist = dist;
nearestDropsite = dropsite;
}
}
});
if (!nearestDropsite)
{
debug ("No dropsite for " +resource);
return;
}
}
// if the resource is far away, try to build a farm instead.
var tried = false;
if (resource === "food" && SquareVectorDistance(pos,this.ent.position()) > 22500)
{
tried = this.buildAnyField(gameState);
if (!tried && SquareVectorDistance(pos,this.ent.position()) > 62500) {
return; // wait. a farm should appear.
}
}
if (!tried) {
if (!gameState.turnCache["ressGathererNB"])
{
gameState.turnCache["ressGathererNB"] = {};
gameState.turnCache["ressGathererNB"][nearestSupply.id()] = 1;
} else if (!gameState.turnCache["ressGathererNB"][nearestSupply.id()])
gameState.turnCache["ressGathererNB"][nearestSupply.id()] = 1;
else
gameState.turnCache["ressGathererNB"][nearestSupply.id()]++;
this.maxApproachTime = Math.max(25000, VectorDistance(pos,this.ent.position()) * 1000);
ent.gather(nearestSupply);
ent.setMetadata(PlayerID, "target-foundation", undefined);
// check if the resource we've started gathering from is now full, in which case inform the dropsite.
if (gameState.turnCache["ressGathererNB"][nearestSupply.id()] + nearestSupply.resourceSupplyGatherers().length >= nearestSupply.maxGatherers()
&& nearestSupply.getMetadata(PlayerID, "linked-dropsite") != undefined)
{
var dropsite = gameState.getEntityById(nearestSupply.getMetadata(PlayerID, "linked-dropsite"));
if (dropsite == undefined || dropsite.getMetadata(PlayerID, "linked-resources-" + resource) === undefined)
return;
if (nearestSupply.getMetadata(PlayerID, "linked-dropsite-nearby") == true) {
dropsite.setMetadata(PlayerID, "resource-quantity-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) - (+nearestSupply.getMetadata(PlayerID, "dp-update-value")));
dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(nearestSupply);
dropsite.getMetadata(PlayerID, "nearby-resources-" + resource).updateEnt(nearestSupply);
} else {
dropsite.setMetadata(PlayerID, "resource-quantity-far-" + resource, +dropsite.getMetadata(PlayerID, "resource-quantity-" + resource) - (+nearestSupply.getMetadata(PlayerID, "dp-update-value")));
dropsite.getMetadata(PlayerID, "linked-resources-" + resource).updateEnt(nearestSupply);
}
}
}
} else {
if (resource === "food" && this.buildAnyField(gameState))
return;
debug("No " + resource + " found! (2)");
// If we had a fitting closest dropsite with a lot of resources, mark it as not good. It means it's probably full. Then retry.
// it'll be resetted next time it's counted anyway.
if (nearestDropsite && nearestDropsite.getMetadata(PlayerID, "resource-quantity-" +resource)+nearestDropsite.getMetadata(PlayerID, "resource-quantity-far-" +resource) > 400)
{
nearestDropsite.setMetadata(PlayerID, "resource-quantity-" +resource, 0);
nearestDropsite.setMetadata(PlayerID, "resource-quantity-far-" +resource, 0);
this.startGathering(gameState);
}
}
};
// Makes the worker deposit the currently carried resources at the closest dropsite
Worker.prototype.returnResources = function(gameState){
if (!this.ent.resourceCarrying() || this.ent.resourceCarrying().length === 0){
return true; // assume we're OK.
}
var resource = this.ent.resourceCarrying()[0].type;
var self = this;
if (!this.ent.position()){
// TODO: work out what to do when entity has no position
return true;
}
var closestDropsite = undefined;
var dist = Math.min();
gameState.getOwnDropsites(resource).forEach(function(dropsite){
if (dropsite.position()){
var d = SquareVectorDistance(self.ent.position(), dropsite.position());
if (d < dist){
dist = d;
closestDropsite = dropsite;
}
}
});
if (!closestDropsite){
debug("No dropsite found to deposit " + resource);
return false;
}
this.ent.returnResources(closestDropsite);
return true;
};
Worker.prototype.startHunting = function(gameState){
var ent = this.ent;
if (!ent.position() || ent.getMetadata(PlayerID, "stoppedHunting"))
return;
// So here we're doing it basic. We check what we can hunt, we hunt it. No fancies.
var resources = gameState.getResourceSupplies("food");
if (resources.length === 0){
debug("No food found to hunt!");
return;
}
var supplies = [];
var nearestSupplyDist = Math.min();
var nearestSupply = undefined;
resources.forEach(function(supply) { //}){
if (!supply.position())
return;
if (supply.getMetadata(PlayerID, "inaccessible") === true)
return;
if (supply.isFull() === true)
return;
if (!supply.hasClass("Animal"))
return;
// measure the distance to the resource
var dist = SquareVectorDistance(supply.position(), ent.position());
var territoryOwner = Map.createTerritoryMap(gameState).getOwner(supply.position());
if (territoryOwner != PlayerID && territoryOwner != 0) {
dist *= 3.0;
}
// quickscope accessbility check
if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(), true))
return;
if (dist < nearestSupplyDist) {
nearestSupplyDist = dist;
nearestSupply = supply;
}
});
if (nearestSupply) {
var pos = nearestSupply.position();
var nearestDropsite = 0;
var minDropsiteDist = 1000000;
// find a fitting dropsites in case we haven't already.
gameState.getOwnDropsites("food").forEach(function (dropsite){ //}){
if (dropsite.position()){
var dist = SquareVectorDistance(pos, dropsite.position());
if (dist < minDropsiteDist){
minDropsiteDist = dist;
nearestDropsite = dropsite;
}
}
});
if (!nearestDropsite)
{
ent.setMetadata(PlayerID, "stoppedHunting", true);
ent.setMetadata(PlayerID, "role", undefined);
debug ("No dropsite for hunting food");
return;
}
if (minDropsiteDist > 45000) {
ent.setMetadata(PlayerID, "stoppedHunting", true);
ent.setMetadata(PlayerID, "role", undefined);
} else {
ent.gather(nearestSupply);
ent.setMetadata(PlayerID, "target-foundation", undefined);
}
} else {
ent.setMetadata(PlayerID, "stoppedHunting", true);
ent.setMetadata(PlayerID, "role", undefined);
debug("No food found for hunting! (2)");
}
};
Worker.prototype.getResourceType = function(type){
if (!type || !type.generic){
return undefined;
}
if (type.generic === "treasure"){
return type.specific;
}else{
return type.generic;
}
};
Worker.prototype.buildAnyField = function(gameState){
var self = this;
var okay = false;
var foundations = gameState.getOwnFoundations();
foundations.filterNearest(this.ent.position(), foundations.length);
foundations.forEach(function (found) {
if (found._template.BuildRestrictions.Category === "Field" && !okay) {
self.ent.repair(found);
okay = true;
return;
}
});
return okay;
};