[PetraAI] - Manage bases in a separate BasesManager.
The HQ should only care about high-level stuff, hence something like managing/looping individual bases is now done in a `BasesManager`, similar to the `AttackManager`. Differential revision: https://code.wildfiregames.com/D4192 Comments by: @Angen Fixes: #6185 This was SVN commit r25876.
This commit is contained in:
parent
2c4427b488
commit
4e664dd712
@ -342,7 +342,7 @@ PETRA.AttackManager.prototype.update = function(gameState, queues, events)
|
||||
(this.startedAttacks.Attack.length + this.startedAttacks.HugeAttack.length == 0 || gameState.getPopulationMax() - gameState.getPopulation() > 12))
|
||||
{
|
||||
if (barracksNb >= 1 && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.getPhaseName(2))) ||
|
||||
!gameState.ai.HQ.baseManagers[1]) // if we have no base ... nothing else to do than attack
|
||||
!gameState.ai.HQ.hasPotentialBase()) // if we have no base ... nothing else to do than attack
|
||||
{
|
||||
let type = this.attackNumber < 2 || this.startedAttacks.HugeAttack.length > 0 ? "Attack" : "HugeAttack";
|
||||
let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, type);
|
||||
|
@ -32,7 +32,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
|
||||
let rallyPoint;
|
||||
let rallyAccess;
|
||||
let allAccesses = {};
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
continue;
|
||||
@ -816,7 +816,7 @@ PETRA.AttackPlan.prototype.chooseTarget = function(gameState)
|
||||
let rallySame;
|
||||
let distminDiff = Math.min();
|
||||
let rallyDiff;
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
let anchor = base.anchor;
|
||||
if (!anchor || !anchor.position())
|
||||
@ -2001,7 +2001,7 @@ PETRA.AttackPlan.prototype.Abort = function(gameState)
|
||||
dist = API3.SquareVectorDistance(this.position, rallyPoint);
|
||||
}
|
||||
// Then check if we have a nearer base (in case this attack has captured one)
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
continue;
|
||||
@ -2083,7 +2083,7 @@ PETRA.AttackPlan.prototype.checkEvents = function(gameState, events)
|
||||
if (!gameState.isPlayerEnemy(ent.owner()))
|
||||
continue;
|
||||
let access = PETRA.getLandAccess(gameState, ent);
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
continue;
|
||||
|
@ -10,10 +10,11 @@
|
||||
* -updating whatever needs updating, keeping track of stuffs (rebuilding needs…)
|
||||
*/
|
||||
|
||||
PETRA.BaseManager = function(gameState, Config)
|
||||
PETRA.BaseManager = function(gameState, basesManager)
|
||||
{
|
||||
this.Config = Config;
|
||||
this.Config = basesManager.Config;
|
||||
this.ID = gameState.ai.uniqueIDs.bases++;
|
||||
this.basesManager = basesManager;
|
||||
|
||||
// anchor building: seen as the main building of the base. Needs to have territorial influence
|
||||
this.anchor = undefined;
|
||||
@ -95,7 +96,7 @@ PETRA.BaseManager.prototype.setAnchor = function(gameState, anchorEntity)
|
||||
this.anchor = anchorEntity;
|
||||
this.anchorId = anchorEntity.id();
|
||||
this.anchor.setMetadata(PlayerID, "baseAnchor", true);
|
||||
gameState.ai.HQ.resetBaseCache();
|
||||
this.basesManager.resetBaseCache();
|
||||
}
|
||||
anchorEntity.setMetadata(PlayerID, "base", this.ID);
|
||||
this.buildings.updateEnt(anchorEntity);
|
||||
@ -109,7 +110,7 @@ PETRA.BaseManager.prototype.anchorLost = function(gameState, ent)
|
||||
this.anchor = undefined;
|
||||
this.anchorId = undefined;
|
||||
this.neededDefenders = 0;
|
||||
gameState.ai.HQ.resetBaseCache();
|
||||
this.basesManager.resetBaseCache();
|
||||
};
|
||||
|
||||
/** Set a building of an anchorless base */
|
||||
@ -148,7 +149,7 @@ PETRA.BaseManager.prototype.assignResourceToDropsite = function(gameState, drops
|
||||
let dropsiteId = dropsite.id();
|
||||
this.dropsites[dropsiteId] = true;
|
||||
|
||||
if (this.ID == gameState.ai.HQ.baseManagers[0].ID)
|
||||
if (this.ID == this.basesManager.baselessBase().ID)
|
||||
accessIndex = PETRA.getLandAccess(gameState, dropsite);
|
||||
|
||||
let maxDistResourceSquare = this.maxDistResourceSquare;
|
||||
@ -357,27 +358,18 @@ PETRA.BaseManager.prototype.findBestDropsiteLocation = function(gameState, resou
|
||||
return { "quality": bestVal, "pos": [x, z] };
|
||||
};
|
||||
|
||||
PETRA.BaseManager.prototype.getResourceLevel = function(gameState, type, nearbyOnly = false)
|
||||
PETRA.BaseManager.prototype.getResourceLevel = function(gameState, type, distances = ["nearby", "medium", "faraway"])
|
||||
{
|
||||
let count = 0;
|
||||
let check = {};
|
||||
for (let supply of this.dropsiteSupplies[type].nearby)
|
||||
{
|
||||
if (check[supply.id]) // avoid double counting as same resource can appear several time
|
||||
continue;
|
||||
check[supply.id] = true;
|
||||
count += supply.ent.resourceSupplyAmount();
|
||||
}
|
||||
if (nearbyOnly)
|
||||
return count;
|
||||
|
||||
for (let supply of this.dropsiteSupplies[type].medium)
|
||||
{
|
||||
if (check[supply.id])
|
||||
continue;
|
||||
check[supply.id] = true;
|
||||
count += 0.6*supply.ent.resourceSupplyAmount();
|
||||
}
|
||||
for (const proxim of distances)
|
||||
for (const supply of this.dropsiteSupplies[type][proxim])
|
||||
{
|
||||
if (check[supply.id]) // avoid double counting as same resource can appear several time
|
||||
continue;
|
||||
check[supply.id] = true;
|
||||
count += supply.ent.resourceSupplyAmount();
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
@ -388,9 +380,12 @@ PETRA.BaseManager.prototype.checkResourceLevels = function(gameState, queues)
|
||||
{
|
||||
if (type == "food")
|
||||
{
|
||||
const prox = ["nearby"];
|
||||
if (gameState.currentPhase() < 2)
|
||||
prox.push("medium");
|
||||
if (gameState.ai.HQ.canBuild(gameState, "structures/{civ}/field")) // let's see if we need to add new farms.
|
||||
{
|
||||
let count = this.getResourceLevel(gameState, type, gameState.currentPhase() > 1); // animals are not accounted
|
||||
const count = this.getResourceLevel(gameState, type, prox); // animals are not accounted
|
||||
let numFarms = gameState.getOwnStructures().filter(API3.Filters.byClass("Field")).length; // including foundations
|
||||
let numQueue = queues.field.countQueuedUnits();
|
||||
|
||||
@ -420,7 +415,7 @@ PETRA.BaseManager.prototype.checkResourceLevels = function(gameState, queues)
|
||||
if (!gameState.getOwnEntitiesByClass("Corral", true).hasEntities() &&
|
||||
!queues.corral.hasQueuedUnits() && gameState.ai.HQ.canBuild(gameState, "structures/{civ}/corral"))
|
||||
{
|
||||
let count = this.getResourceLevel(gameState, type, gameState.currentPhase() > 1); // animals are not accounted
|
||||
const count = this.getResourceLevel(gameState, type, prox); // animals are not accounted
|
||||
if (count < 900)
|
||||
{
|
||||
queues.corral.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/corral", { "favoredBase": this.ID }));
|
||||
@ -559,9 +554,9 @@ PETRA.BaseManager.prototype.setWorkersIdleByPriority = function(gameState)
|
||||
if (lastFailed && gameState.ai.elapsedTime - lastFailed < 20)
|
||||
continue;
|
||||
// Ensure that the most wanted resource is not exhausted
|
||||
if (moreNeed.type != "food" && gameState.ai.HQ.isResourceExhausted(moreNeed.type))
|
||||
if (moreNeed.type != "food" && this.basesManager.isResourceExhausted(moreNeed.type))
|
||||
{
|
||||
if (lessNeed.type != "food" && gameState.ai.HQ.isResourceExhausted(lessNeed.type))
|
||||
if (lessNeed.type != "food" && this.basesManager.isResourceExhausted(lessNeed.type))
|
||||
continue;
|
||||
|
||||
// And if so, move the gatherer to the less wanted one.
|
||||
@ -573,7 +568,7 @@ PETRA.BaseManager.prototype.setWorkersIdleByPriority = function(gameState)
|
||||
// If we assume a mean rate of 0.5 per gatherer, this diff should be > 1
|
||||
// but we require a bit more to avoid too frequent changes
|
||||
if (scale*moreNeed.wanted - moreNeed.current - scale*lessNeed.wanted + lessNeed.current > 1.5 ||
|
||||
lessNeed.type != "food" && gameState.ai.HQ.isResourceExhausted(lessNeed.type))
|
||||
lessNeed.type != "food" && this.basesManager.isResourceExhausted(lessNeed.type))
|
||||
{
|
||||
nb = this.switchGatherer(gameState, lessNeed.type, moreNeed.type, nb);
|
||||
if (nb == 0)
|
||||
@ -609,7 +604,7 @@ PETRA.BaseManager.prototype.switchGatherer = function(gameState, from, to, numbe
|
||||
--num;
|
||||
ent.stopMoving();
|
||||
ent.setMetadata(PlayerID, "gather-type", to);
|
||||
gameState.ai.HQ.AddTCResGatherer(to);
|
||||
this.basesManager.AddTCResGatherer(to);
|
||||
}
|
||||
return num;
|
||||
};
|
||||
@ -645,11 +640,11 @@ PETRA.BaseManager.prototype.reassignIdleWorkers = function(gameState, idleWorker
|
||||
let lastFailed = gameState.ai.HQ.lastFailedGather[needed.type];
|
||||
if (lastFailed && gameState.ai.elapsedTime - lastFailed < 20)
|
||||
continue;
|
||||
if (needed.type != "food" && gameState.ai.HQ.isResourceExhausted(needed.type))
|
||||
if (needed.type != "food" && this.basesManager.isResourceExhausted(needed.type))
|
||||
continue;
|
||||
ent.setMetadata(PlayerID, "subrole", "gatherer");
|
||||
ent.setMetadata(PlayerID, "gather-type", needed.type);
|
||||
gameState.ai.HQ.AddTCResGatherer(needed.type);
|
||||
this.basesManager.AddTCResGatherer(needed.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -749,7 +744,7 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
|
||||
|
||||
if (workers.length < 3)
|
||||
{
|
||||
let fromOtherBase = gameState.ai.HQ.bulkPickWorkers(gameState, this, 2);
|
||||
const fromOtherBase = this.basesManager.bulkPickWorkers(gameState, this, 2);
|
||||
if (fromOtherBase)
|
||||
{
|
||||
let baseID = this.ID;
|
||||
@ -816,8 +811,7 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
|
||||
maxTotalBuilders = Math.max(maxTotalBuilders, 15);
|
||||
}
|
||||
|
||||
// if no base yet, everybody should build
|
||||
if (gameState.ai.HQ.numActiveBases() == 0)
|
||||
if (!this.basesManager.hasActiveBase())
|
||||
{
|
||||
targetNB = workers.length;
|
||||
maxTotalBuilders = targetNB;
|
||||
@ -947,11 +941,11 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
|
||||
/** Return false when the base is not active (no workers on it) */
|
||||
PETRA.BaseManager.prototype.update = function(gameState, queues, events)
|
||||
{
|
||||
if (this.ID == gameState.ai.HQ.baseManagers[0].ID) // base for unaffected units
|
||||
if (this.ID == this.basesManager.baselessBase().ID)
|
||||
{
|
||||
// if some active base, reassigns the workers/buildings
|
||||
// otherwise look for anything useful to do, i.e. treasures to gather
|
||||
if (gameState.ai.HQ.numActiveBases() > 0)
|
||||
if (this.basesManager.hasActiveBase())
|
||||
{
|
||||
for (let ent of this.units.values())
|
||||
{
|
||||
@ -965,7 +959,7 @@ PETRA.BaseManager.prototype.update = function(gameState, queues, events)
|
||||
if (!bestBase)
|
||||
{
|
||||
if (ent.hasClass("Dock"))
|
||||
API3.warn("Petra: dock in baseManager[0]. It may be useful to do an anchorless base for " + ent.templateName());
|
||||
API3.warn("Petra: dock in 'noBase' baseManager. It may be useful to do an anchorless base for " + ent.templateName());
|
||||
continue;
|
||||
}
|
||||
if (ent.resourceDropsiteTypes())
|
||||
@ -1056,7 +1050,7 @@ PETRA.BaseManager.prototype.update = function(gameState, queues, events)
|
||||
if (API3.SquareVectorDistance(cc.position(), this.anchor.position()) > 8000)
|
||||
continue;
|
||||
this.anchor.destroy();
|
||||
gameState.ai.HQ.resetBaseCache();
|
||||
this.basesManager.resetBaseCache();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1080,6 +1074,21 @@ PETRA.BaseManager.prototype.update = function(gameState, queues, events)
|
||||
return true;
|
||||
};
|
||||
|
||||
PETRA.BaseManager.prototype.AddTCGatherer = function(supplyID)
|
||||
{
|
||||
return this.basesManager.AddTCGatherer(supplyID);
|
||||
};
|
||||
|
||||
PETRA.BaseManager.prototype.RemoveTCGatherer = function(supplyID)
|
||||
{
|
||||
this.basesManager.RemoveTCGatherer(supplyID);
|
||||
};
|
||||
|
||||
PETRA.BaseManager.prototype.GetTCGatherer = function(supplyID)
|
||||
{
|
||||
return this.basesManager.GetTCGatherer(supplyID);
|
||||
};
|
||||
|
||||
PETRA.BaseManager.prototype.Serialize = function()
|
||||
{
|
||||
return {
|
||||
|
792
binaries/data/mods/public/simulation/ai/petra/basesManager.js
Normal file
792
binaries/data/mods/public/simulation/ai/petra/basesManager.js
Normal file
@ -0,0 +1,792 @@
|
||||
/**
|
||||
* Bases Manager
|
||||
* Manages the list of available bases and queries information from those (e.g. resource levels).
|
||||
* Only one base is run every turn.
|
||||
*/
|
||||
|
||||
PETRA.BasesManager = function(Config)
|
||||
{
|
||||
this.Config = Config;
|
||||
|
||||
this.currentBase = 0;
|
||||
|
||||
// Cache some quantities for performance.
|
||||
this.turnCache = {};
|
||||
|
||||
// Deals with unit/structure without base.
|
||||
this.noBase = undefined;
|
||||
|
||||
this.baseManagers = [];
|
||||
};
|
||||
|
||||
PETRA.BasesManager.prototype.init = function(gameState)
|
||||
{
|
||||
// Initialize base map. Each pixel is a base ID, or 0 if not or not accessible.
|
||||
this.basesMap = new API3.Map(gameState.sharedScript, "territory");
|
||||
|
||||
this.noBase = new PETRA.BaseManager(gameState, this);
|
||||
this.noBase.init(gameState);
|
||||
this.noBase.accessIndex = 0;
|
||||
|
||||
for (const cc of gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).values())
|
||||
if (cc.foundationProgress() === undefined)
|
||||
this.createBase(gameState, cc);
|
||||
else
|
||||
this.createBase(gameState, cc, "unconstructed");
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialization needed after deserialization (only called when deserialising).
|
||||
*/
|
||||
PETRA.BasesManager.prototype.postinit = function(gameState)
|
||||
{
|
||||
// Rebuild the base maps from the territory indices of each base.
|
||||
this.basesMap = new API3.Map(gameState.sharedScript, "territory");
|
||||
for (const base of this.baseManagers)
|
||||
for (const j of base.territoryIndices)
|
||||
this.basesMap.map[j] = base.ID;
|
||||
|
||||
for (const ent of gameState.getOwnEntities().values())
|
||||
{
|
||||
if (!ent.resourceDropsiteTypes() || !ent.hasClass("Structure"))
|
||||
continue;
|
||||
// Entities which have been built or have changed ownership after the last AI turn have no base.
|
||||
// they will be dealt with in the next checkEvents
|
||||
const baseID = ent.getMetadata(PlayerID, "base");
|
||||
if (baseID === undefined)
|
||||
continue;
|
||||
const base = this.getBaseByID(baseID);
|
||||
base.assignResourceToDropsite(gameState, ent);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new base in the baseManager:
|
||||
* If an existing one without anchor already exist, use it.
|
||||
* Otherwise create a new one.
|
||||
* TODO when buildings, criteria should depend on distance
|
||||
* allowedType: undefined => new base with an anchor
|
||||
* "unconstructed" => new base with a foundation anchor
|
||||
* "captured" => captured base with an anchor
|
||||
* "anchorless" => anchorless base, currently with dock
|
||||
*/
|
||||
PETRA.BasesManager.prototype.createBase = function(gameState, ent, type)
|
||||
{
|
||||
const access = PETRA.getLandAccess(gameState, ent);
|
||||
let newbase;
|
||||
for (const base of this.baseManagers)
|
||||
{
|
||||
if (base.accessIndex != access)
|
||||
continue;
|
||||
if (type != "anchorless" && base.anchor)
|
||||
continue;
|
||||
if (type != "anchorless")
|
||||
{
|
||||
// TODO we keep the first one, we should rather use the nearest if buildings
|
||||
// and possibly also cut on distance
|
||||
newbase = base;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO here also test on distance instead of first
|
||||
if (newbase && !base.anchor)
|
||||
continue;
|
||||
newbase = base;
|
||||
if (newbase.anchor)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.Config.debug > 0)
|
||||
{
|
||||
API3.warn(" ----------------------------------------------------------");
|
||||
API3.warn(" BasesManager createBase entrance avec access " + access + " and type " + type);
|
||||
API3.warn(" with access " + uneval(this.baseManagers.map(base => base.accessIndex)) +
|
||||
" and base nbr " + uneval(this.baseManagers.map(base => base.ID)) +
|
||||
" and anchor " + uneval(this.baseManagers.map(base => !!base.anchor)));
|
||||
}
|
||||
|
||||
if (!newbase)
|
||||
{
|
||||
newbase = new PETRA.BaseManager(gameState, this);
|
||||
newbase.init(gameState, type);
|
||||
this.baseManagers.push(newbase);
|
||||
}
|
||||
else
|
||||
newbase.reset(type);
|
||||
|
||||
if (type != "anchorless")
|
||||
newbase.setAnchor(gameState, ent);
|
||||
else
|
||||
newbase.setAnchorlessEntity(gameState, ent);
|
||||
|
||||
return newbase;
|
||||
};
|
||||
|
||||
/** TODO check if the new anchorless bases should be added to addBase */
|
||||
PETRA.BasesManager.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
let addBase = false;
|
||||
|
||||
for (const evt of events.Destroy)
|
||||
{
|
||||
// Let's check we haven't lost an important building here.
|
||||
if (evt && !evt.SuccessfulFoundation && evt.entityObj && evt.metadata && evt.metadata[PlayerID] &&
|
||||
evt.metadata[PlayerID].base)
|
||||
{
|
||||
const ent = evt.entityObj;
|
||||
if (ent.owner() != PlayerID)
|
||||
continue;
|
||||
// A new base foundation was created and destroyed on the same (AI) turn
|
||||
if (evt.metadata[PlayerID].base == -1 || evt.metadata[PlayerID].base == -2)
|
||||
continue;
|
||||
const base = this.getBaseByID(evt.metadata[PlayerID].base);
|
||||
if (ent.resourceDropsiteTypes() && ent.hasClass("Structure"))
|
||||
base.removeDropsite(gameState, ent);
|
||||
if (evt.metadata[PlayerID].baseAnchor && evt.metadata[PlayerID].baseAnchor === true)
|
||||
base.anchorLost(gameState, ent);
|
||||
}
|
||||
}
|
||||
|
||||
for (const evt of events.EntityRenamed)
|
||||
{
|
||||
const ent = gameState.getEntityById(evt.newentity);
|
||||
if (!ent || ent.owner() != PlayerID || ent.getMetadata(PlayerID, "base") === undefined)
|
||||
continue;
|
||||
const base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
|
||||
if (!base.anchorId || base.anchorId != evt.entity)
|
||||
continue;
|
||||
base.anchorId = evt.newentity;
|
||||
base.anchor = ent;
|
||||
}
|
||||
|
||||
for (const evt of events.Create)
|
||||
{
|
||||
// Let's check if we have a valuable foundation needing builders quickly
|
||||
// (normal foundations are taken care in baseManager.assignToFoundations)
|
||||
const ent = gameState.getEntityById(evt.entity);
|
||||
if (!ent || ent.owner() != PlayerID || ent.foundationProgress() === undefined)
|
||||
continue;
|
||||
|
||||
if (ent.getMetadata(PlayerID, "base") == -1) // Standard base around a cc
|
||||
{
|
||||
// Okay so let's try to create a new base around this.
|
||||
const newbase = this.createBase(gameState, ent, "unconstructed");
|
||||
// Let's get a few units from other bases there to build this.
|
||||
const builders = this.bulkPickWorkers(gameState, newbase, 10);
|
||||
if (builders !== false)
|
||||
{
|
||||
builders.forEach(worker => {
|
||||
worker.setMetadata(PlayerID, "base", newbase.ID);
|
||||
worker.setMetadata(PlayerID, "subrole", "builder");
|
||||
worker.setMetadata(PlayerID, "target-foundation", ent.id());
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (ent.getMetadata(PlayerID, "base") == -2) // anchorless base around a dock
|
||||
{
|
||||
const newbase = this.createBase(gameState, ent, "anchorless");
|
||||
// Let's get a few units from other bases there to build this.
|
||||
const builders = this.bulkPickWorkers(gameState, newbase, 4);
|
||||
if (builders != false)
|
||||
{
|
||||
builders.forEach(worker => {
|
||||
worker.setMetadata(PlayerID, "base", newbase.ID);
|
||||
worker.setMetadata(PlayerID, "subrole", "builder");
|
||||
worker.setMetadata(PlayerID, "target-foundation", ent.id());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const evt of events.ConstructionFinished)
|
||||
{
|
||||
if (evt.newentity == evt.entity) // repaired building
|
||||
continue;
|
||||
const ent = gameState.getEntityById(evt.newentity);
|
||||
if (!ent || ent.owner() != PlayerID)
|
||||
continue;
|
||||
if (ent.getMetadata(PlayerID, "base") === undefined)
|
||||
continue;
|
||||
const base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
|
||||
base.buildings.updateEnt(ent);
|
||||
if (ent.resourceDropsiteTypes())
|
||||
base.assignResourceToDropsite(gameState, ent);
|
||||
|
||||
if (ent.getMetadata(PlayerID, "baseAnchor") === true)
|
||||
{
|
||||
if (base.constructing)
|
||||
base.constructing = false;
|
||||
addBase = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const evt of events.OwnershipChanged)
|
||||
{
|
||||
if (evt.from == PlayerID)
|
||||
{
|
||||
const ent = gameState.getEntityById(evt.entity);
|
||||
if (!ent || ent.getMetadata(PlayerID, "base") === undefined)
|
||||
continue;
|
||||
const base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
|
||||
if (ent.resourceDropsiteTypes() && ent.hasClass("Structure"))
|
||||
base.removeDropsite(gameState, ent);
|
||||
if (ent.getMetadata(PlayerID, "baseAnchor") === true)
|
||||
base.anchorLost(gameState, ent);
|
||||
}
|
||||
|
||||
if (evt.to != PlayerID)
|
||||
continue;
|
||||
const ent = gameState.getEntityById(evt.entity);
|
||||
if (!ent)
|
||||
continue;
|
||||
if (ent.hasClass("Unit"))
|
||||
{
|
||||
PETRA.getBestBase(gameState, ent).assignEntity(gameState, ent);
|
||||
continue;
|
||||
}
|
||||
if (ent.hasClass("CivCentre")) // build a new base around it
|
||||
{
|
||||
let newbase;
|
||||
if (ent.foundationProgress() !== undefined)
|
||||
newbase = this.createBase(gameState, ent, "unconstructed");
|
||||
else
|
||||
{
|
||||
newbase = this.createBase(gameState, ent, "captured");
|
||||
addBase = true;
|
||||
}
|
||||
newbase.assignEntity(gameState, ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
let base;
|
||||
// If dropsite on new island, create a base around it
|
||||
if (!ent.decaying() && ent.resourceDropsiteTypes())
|
||||
base = this.createBase(gameState, ent, "anchorless");
|
||||
else
|
||||
base = PETRA.getBestBase(gameState, ent) || this.noBase;
|
||||
base.assignEntity(gameState, ent);
|
||||
}
|
||||
}
|
||||
|
||||
for (const evt of events.TrainingFinished)
|
||||
{
|
||||
for (const entId of evt.entities)
|
||||
{
|
||||
const ent = gameState.getEntityById(entId);
|
||||
if (!ent || !ent.isOwn(PlayerID))
|
||||
continue;
|
||||
|
||||
// Assign it immediately to something useful to do.
|
||||
if (ent.getMetadata(PlayerID, "role") == "worker")
|
||||
{
|
||||
let base;
|
||||
if (ent.getMetadata(PlayerID, "base") === undefined)
|
||||
{
|
||||
base = PETRA.getBestBase(gameState, ent);
|
||||
base.assignEntity(gameState, ent);
|
||||
}
|
||||
else
|
||||
base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
|
||||
base.reassignIdleWorkers(gameState, [ent]);
|
||||
base.workerObject.update(gameState, ent);
|
||||
}
|
||||
else if (ent.resourceSupplyType() && ent.position())
|
||||
{
|
||||
const type = ent.resourceSupplyType();
|
||||
if (!type.generic)
|
||||
continue;
|
||||
const dropsites = gameState.getOwnDropsites(type.generic);
|
||||
const pos = ent.position();
|
||||
const access = PETRA.getLandAccess(gameState, ent);
|
||||
let distmin = Math.min();
|
||||
let goal;
|
||||
for (const dropsite of dropsites.values())
|
||||
{
|
||||
if (!dropsite.position() || PETRA.getLandAccess(gameState, dropsite) != access)
|
||||
continue;
|
||||
const dist = API3.SquareVectorDistance(pos, dropsite.position());
|
||||
if (dist > distmin)
|
||||
continue;
|
||||
distmin = dist;
|
||||
goal = dropsite.position();
|
||||
}
|
||||
if (goal)
|
||||
ent.moveToRange(goal[0], goal[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addBase)
|
||||
gameState.ai.HQ.handleNewBase(gameState);
|
||||
};
|
||||
|
||||
/**
|
||||
* returns an entity collection of workers through BaseManager.pickBuilders
|
||||
* TODO: when same accessIndex, sort by distance
|
||||
*/
|
||||
PETRA.BasesManager.prototype.bulkPickWorkers = function(gameState, baseRef, number)
|
||||
{
|
||||
const accessIndex = baseRef.accessIndex;
|
||||
if (!accessIndex)
|
||||
return false;
|
||||
const baseBest = this.baseManagers.slice();
|
||||
// We can also use workers without a base.
|
||||
baseBest.push(this.noBase);
|
||||
baseBest.sort((a, b) => {
|
||||
if (a.accessIndex == accessIndex && b.accessIndex != accessIndex)
|
||||
return -1;
|
||||
else if (b.accessIndex == accessIndex && a.accessIndex != accessIndex)
|
||||
return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
let needed = number;
|
||||
const workers = new API3.EntityCollection(gameState.sharedScript);
|
||||
for (const base of baseBest)
|
||||
{
|
||||
if (base.ID == baseRef.ID)
|
||||
continue;
|
||||
base.pickBuilders(gameState, workers, needed);
|
||||
if (workers.length >= number)
|
||||
break;
|
||||
needed = number - workers.length;
|
||||
}
|
||||
if (!workers.length)
|
||||
return false;
|
||||
return workers;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Object} - Resources (estimation) still gatherable in our territory.
|
||||
*/
|
||||
PETRA.BasesManager.prototype.getTotalResourceLevel = function(gameState, resources = Resources.GetCodes(), proximity = ["nearby", "medium"])
|
||||
{
|
||||
const total = {};
|
||||
for (const res of resources)
|
||||
total[res] = 0;
|
||||
for (const base of this.baseManagers)
|
||||
for (const res in total)
|
||||
total[res] += base.getResourceLevel(gameState, res, proximity);
|
||||
|
||||
return total;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current gather rate
|
||||
* This is not per-se exact, it performs a few adjustments ad-hoc to account for travel distance, stuffs like that.
|
||||
*/
|
||||
PETRA.BasesManager.prototype.GetCurrentGatherRates = function(gameState)
|
||||
{
|
||||
if (!this.turnCache.currentRates)
|
||||
{
|
||||
const currentRates = {};
|
||||
for (const res of Resources.GetCodes())
|
||||
currentRates[res] = 0.5 * this.GetTCResGatherer(res);
|
||||
|
||||
this.addGatherRates(gameState, currentRates);
|
||||
|
||||
for (const res of Resources.GetCodes())
|
||||
currentRates[res] = Math.max(currentRates[res], 0);
|
||||
|
||||
this.turnCache.currentRates = currentRates;
|
||||
}
|
||||
|
||||
return this.turnCache.currentRates;
|
||||
};
|
||||
|
||||
/** Some functions that register that we assigned a gatherer to a resource this turn */
|
||||
|
||||
/** Add a gatherer to the turn cache for this supply. */
|
||||
PETRA.BasesManager.prototype.AddTCGatherer = function(supplyID)
|
||||
{
|
||||
if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID] !== undefined)
|
||||
++this.turnCache.resourceGatherer[supplyID];
|
||||
else
|
||||
{
|
||||
if (!this.turnCache.resourceGatherer)
|
||||
this.turnCache.resourceGatherer = {};
|
||||
this.turnCache.resourceGatherer[supplyID] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
/** Remove a gatherer from the turn cache for this supply. */
|
||||
PETRA.BasesManager.prototype.RemoveTCGatherer = function(supplyID)
|
||||
{
|
||||
if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID])
|
||||
--this.turnCache.resourceGatherer[supplyID];
|
||||
else
|
||||
{
|
||||
if (!this.turnCache.resourceGatherer)
|
||||
this.turnCache.resourceGatherer = {};
|
||||
this.turnCache.resourceGatherer[supplyID] = -1;
|
||||
}
|
||||
};
|
||||
|
||||
PETRA.BasesManager.prototype.GetTCGatherer = function(supplyID)
|
||||
{
|
||||
if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID])
|
||||
return this.turnCache.resourceGatherer[supplyID];
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
/** The next two are to register that we assigned a gatherer to a resource this turn. */
|
||||
PETRA.BasesManager.prototype.AddTCResGatherer = function(resource)
|
||||
{
|
||||
const check = "resourceGatherer-" + resource;
|
||||
if (this.turnCache[check])
|
||||
++this.turnCache[check];
|
||||
else
|
||||
this.turnCache[check] = 1;
|
||||
|
||||
if (this.turnCache.currentRates)
|
||||
this.turnCache.currentRates[resource] += 0.5;
|
||||
};
|
||||
|
||||
PETRA.BasesManager.prototype.GetTCResGatherer = function(resource)
|
||||
{
|
||||
const check = "resourceGatherer-" + resource;
|
||||
if (this.turnCache[check])
|
||||
return this.turnCache[check];
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* flag a resource as exhausted
|
||||
*/
|
||||
PETRA.BasesManager.prototype.isResourceExhausted = function(resource)
|
||||
{
|
||||
const check = "exhausted-" + resource;
|
||||
if (this.turnCache[check] == undefined)
|
||||
this.turnCache[check] = this.basesManager.isResourceExhausted(resource);
|
||||
|
||||
return this.turnCache[check];
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the number of bases with a cc
|
||||
* ActiveBases includes only those with a built cc
|
||||
* PotentialBases includes also those with a cc in construction
|
||||
*/
|
||||
PETRA.BasesManager.prototype.numActiveBases = function()
|
||||
{
|
||||
if (!this.turnCache.base)
|
||||
this.updateBaseCache();
|
||||
return this.turnCache.base.active;
|
||||
};
|
||||
|
||||
PETRA.BasesManager.prototype.hasActiveBase = function()
|
||||
{
|
||||
return !!this.numActiveBases();
|
||||
};
|
||||
|
||||
PETRA.BasesManager.prototype.numPotentialBases = function()
|
||||
{
|
||||
if (!this.turnCache.base)
|
||||
this.updateBaseCache();
|
||||
return this.turnCache.base.potential;
|
||||
};
|
||||
|
||||
PETRA.BasesManager.prototype.hasPotentialBase = function()
|
||||
{
|
||||
return !!this.numPotentialBases();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the number of active and potential bases.
|
||||
* .potential {number} - Bases that may or may not still be a foundation.
|
||||
* .active {number} - Usable bases.
|
||||
*/
|
||||
PETRA.BasesManager.prototype.updateBaseCache = function()
|
||||
{
|
||||
this.turnCache.base = { "active": 0, "potential": 0 };
|
||||
for (const base of this.baseManagers)
|
||||
{
|
||||
if (!base.anchor)
|
||||
continue;
|
||||
++this.turnCache.base.potential;
|
||||
if (base.anchor.foundationProgress() === undefined)
|
||||
++this.turnCache.base.active;
|
||||
}
|
||||
};
|
||||
|
||||
PETRA.BasesManager.prototype.resetBaseCache = function()
|
||||
{
|
||||
this.turnCache.base = undefined;
|
||||
};
|
||||
|
||||
PETRA.BasesManager.prototype.baselessBase = function()
|
||||
{
|
||||
return this.noBase;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} baseID
|
||||
* @return {Object} - The base corresponding to baseID.
|
||||
*/
|
||||
PETRA.BasesManager.prototype.getBaseByID = function(baseID)
|
||||
{
|
||||
if (this.noBase.ID === baseID)
|
||||
return this.noBase;
|
||||
return this.baseManagers.find(base => base.ID === baseID);
|
||||
};
|
||||
|
||||
/**
|
||||
* flag a resource as exhausted
|
||||
*/
|
||||
PETRA.BasesManager.prototype.isResourceExhausted = function(resource)
|
||||
{
|
||||
return this.baseManagers.every(base =>
|
||||
!base.dropsiteSupplies[resource].nearby.length &&
|
||||
!base.dropsiteSupplies[resource].medium.length &&
|
||||
!base.dropsiteSupplies[resource].faraway.length);
|
||||
};
|
||||
|
||||
/**
|
||||
* Count gatherers returning resources in the number of gatherers of resourceSupplies
|
||||
* to prevent the AI always reassigning idle workers to these resourceSupplies (specially in naval maps).
|
||||
*/
|
||||
PETRA.BasesManager.prototype.assignGatherers = function()
|
||||
{
|
||||
for (const base of this.baseManagers)
|
||||
for (const worker of base.workers.values())
|
||||
{
|
||||
if (worker.unitAIState().split(".").indexOf("RETURNRESOURCE") === -1)
|
||||
continue;
|
||||
const orders = worker.unitAIOrderData();
|
||||
if (orders.length < 2 || !orders[1].target || orders[1].target != worker.getMetadata(PlayerID, "supply"))
|
||||
continue;
|
||||
this.AddTCGatherer(orders[1].target);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Assign an entity to the closest base.
|
||||
* Used by the starting strategy.
|
||||
*/
|
||||
PETRA.BasesManager.prototype.assignEntity = function(gameState, ent, territoryIndex)
|
||||
{
|
||||
let bestbase;
|
||||
for (const base of this.baseManagers)
|
||||
{
|
||||
if ((!ent.getMetadata(PlayerID, "base") || ent.getMetadata(PlayerID, "base") != base.ID) &&
|
||||
base.territoryIndices.indexOf(territoryIndex) == -1)
|
||||
continue;
|
||||
base.assignEntity(gameState, ent);
|
||||
bestbase = base;
|
||||
break;
|
||||
}
|
||||
if (!bestbase) // entity outside our territory
|
||||
{
|
||||
if (ent.hasClass("Structure") && !ent.decaying() && ent.resourceDropsiteTypes())
|
||||
bestbase = this.createBase(gameState, ent, "anchorless");
|
||||
else
|
||||
bestbase = PETRA.getBestBase(gameState, ent) || this.noBase;
|
||||
bestbase.assignEntity(gameState, ent);
|
||||
}
|
||||
// now assign entities garrisoned inside this entity
|
||||
if (ent.isGarrisonHolder() && ent.garrisoned().length)
|
||||
for (const id of ent.garrisoned())
|
||||
bestbase.assignEntity(gameState, gameState.getEntityById(id));
|
||||
// and find something useful to do if we already have a base
|
||||
if (ent.position() && bestbase.ID !== this.noBase.ID)
|
||||
{
|
||||
bestbase.assignRolelessUnits(gameState, [ent]);
|
||||
if (ent.getMetadata(PlayerID, "role") === "worker")
|
||||
{
|
||||
bestbase.reassignIdleWorkers(gameState, [ent]);
|
||||
bestbase.workerObject.update(gameState, ent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the gather rates of individual bases to a shared object.
|
||||
* @param {Object} gameState
|
||||
* @param {Object} rates - The rates to add the gather rates to.
|
||||
*/
|
||||
PETRA.BasesManager.prototype.addGatherRates = function(gameState, rates)
|
||||
{
|
||||
for (const base of this.baseManagers)
|
||||
base.addGatherRates(gameState, rates);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} territoryIndex
|
||||
* @return {number} - The ID of the base at the given territory index.
|
||||
*/
|
||||
PETRA.BasesManager.prototype.baseAtIndex = function(territoryIndex)
|
||||
{
|
||||
return this.basesMap.map[territoryIndex];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} territoryIndex
|
||||
*/
|
||||
PETRA.BasesManager.prototype.removeBaseFromTerritoryIndex = function(territoryIndex)
|
||||
{
|
||||
const baseID = this.basesMap.map[territoryIndex];
|
||||
if (baseID == 0)
|
||||
return;
|
||||
const base = this.getBaseByID(baseID);
|
||||
if (base)
|
||||
{
|
||||
const index = base.territoryIndices.indexOf(territoryIndex);
|
||||
if (index != -1)
|
||||
base.territoryIndices.splice(index, 1);
|
||||
else
|
||||
API3.warn(" problem in headquarters::updateTerritories for base " + baseID);
|
||||
}
|
||||
else
|
||||
API3.warn(" problem in headquarters::updateTerritories without base " + baseID);
|
||||
this.basesMap.map[territoryIndex] = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} - Whether the index was added to a base.
|
||||
*/
|
||||
PETRA.BasesManager.prototype.addTerritoryIndexToBase = function(gameState, territoryIndex, passabilityMap)
|
||||
{
|
||||
if (this.baseAtIndex(territoryIndex) != 0)
|
||||
return false;
|
||||
let landPassable = false;
|
||||
const ind = API3.getMapIndices(territoryIndex, gameState.ai.HQ.territoryMap, passabilityMap);
|
||||
let access;
|
||||
for (const k of ind)
|
||||
{
|
||||
if (!gameState.ai.HQ.landRegions[gameState.ai.accessibility.landPassMap[k]])
|
||||
continue;
|
||||
landPassable = true;
|
||||
access = gameState.ai.accessibility.landPassMap[k];
|
||||
break;
|
||||
}
|
||||
if (!landPassable)
|
||||
return false;
|
||||
let distmin = Math.min();
|
||||
let baseID;
|
||||
const pos = [gameState.ai.HQ.territoryMap.cellSize * (territoryIndex % gameState.ai.HQ.territoryMap.width + 0.5), gameState.ai.HQ.territoryMap.cellSize * (Math.floor(territoryIndex / gameState.ai.HQ.territoryMap.width) + 0.5)];
|
||||
for (const base of this.baseManagers)
|
||||
{
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
continue;
|
||||
if (base.accessIndex != access)
|
||||
continue;
|
||||
const dist = API3.SquareVectorDistance(base.anchor.position(), pos);
|
||||
if (dist >= distmin)
|
||||
continue;
|
||||
distmin = dist;
|
||||
baseID = base.ID;
|
||||
}
|
||||
if (!baseID)
|
||||
return false;
|
||||
this.getBaseByID(baseID).territoryIndices.push(territoryIndex);
|
||||
this.basesMap.map[territoryIndex] = baseID;
|
||||
return true;
|
||||
};
|
||||
|
||||
/** Reassign territories when a base is going to be deleted */
|
||||
PETRA.BasesManager.prototype.reassignTerritories = function(deletedBase, territoryMap)
|
||||
{
|
||||
const cellSize = territoryMap.cellSize;
|
||||
const width = territoryMap.width;
|
||||
for (let j = 0; j < territoryMap.length; ++j)
|
||||
{
|
||||
if (this.basesMap.map[j] != deletedBase.ID)
|
||||
continue;
|
||||
if (territoryMap.getOwnerIndex(j) != PlayerID)
|
||||
{
|
||||
API3.warn("Petra reassignTerritories: should never happen");
|
||||
this.basesMap.map[j] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
let distmin = Math.min();
|
||||
let baseID;
|
||||
const pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
|
||||
for (const base of this.baseManagers)
|
||||
{
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
continue;
|
||||
if (base.accessIndex != deletedBase.accessIndex)
|
||||
continue;
|
||||
const dist = API3.SquareVectorDistance(base.anchor.position(), pos);
|
||||
if (dist >= distmin)
|
||||
continue;
|
||||
distmin = dist;
|
||||
baseID = base.ID;
|
||||
}
|
||||
if (baseID)
|
||||
{
|
||||
this.getBaseByID(baseID).territoryIndices.push(j);
|
||||
this.basesMap.map[j] = baseID;
|
||||
}
|
||||
else
|
||||
this.basesMap.map[j] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* We will loop only on one active base per turn.
|
||||
*/
|
||||
PETRA.BasesManager.prototype.update = function(gameState, queues, events)
|
||||
{
|
||||
Engine.ProfileStart("BasesManager update");
|
||||
|
||||
this.turnCache = {};
|
||||
this.assignGatherers();
|
||||
let nbBases = this.baseManagers.length;
|
||||
let activeBase;
|
||||
this.noBase.update(gameState, queues, events);
|
||||
do
|
||||
{
|
||||
this.currentBase %= this.baseManagers.length;
|
||||
activeBase = this.baseManagers[this.currentBase++].update(gameState, queues, events);
|
||||
--nbBases;
|
||||
// TODO what to do with this.reassignTerritories(this.baseManagers[this.currentBase]);
|
||||
}
|
||||
while (!activeBase && nbBases != 0);
|
||||
|
||||
Engine.ProfileStop();
|
||||
};
|
||||
|
||||
PETRA.BasesManager.prototype.Serialize = function()
|
||||
{
|
||||
const properties = {
|
||||
"currentBase": this.currentBase
|
||||
};
|
||||
|
||||
const baseManagers = [];
|
||||
for (const base of this.baseManagers)
|
||||
baseManagers.push(base.Serialize());
|
||||
|
||||
return {
|
||||
"properties": properties,
|
||||
"noBase": this.noBase.Serialize(),
|
||||
"baseManagers": baseManagers
|
||||
};
|
||||
};
|
||||
|
||||
PETRA.BasesManager.prototype.Deserialize = function(gameState, data)
|
||||
{
|
||||
for (const key in data.properties)
|
||||
this[key] = data.properties[key];
|
||||
|
||||
this.noBase = new PETRA.BaseManager(gameState, this);
|
||||
this.noBase.Deserialize(gameState, data.noBase);
|
||||
this.noBase.init(gameState);
|
||||
this.noBase.Deserialize(gameState, data.noBase);
|
||||
|
||||
this.baseManagers = [];
|
||||
for (const basedata of data.baseManagers)
|
||||
{
|
||||
// The first call to deserialize set the ID base needed by entitycollections.
|
||||
const newbase = new PETRA.BaseManager(gameState, this);
|
||||
newbase.Deserialize(gameState, basedata);
|
||||
newbase.init(gameState);
|
||||
newbase.Deserialize(gameState, basedata);
|
||||
this.baseManagers.push(newbase);
|
||||
}
|
||||
};
|
@ -250,7 +250,7 @@ PETRA.DefenseManager.prototype.checkEnemyUnits = function(gameState)
|
||||
this.makeIntoArmy(gameState, ent.id());
|
||||
}
|
||||
|
||||
if (i != 0 || this.armies.length > 1 || gameState.ai.HQ.numActiveBases() == 0)
|
||||
if (i != 0 || this.armies.length > 1 || !gameState.ai.HQ.hasActiveBase())
|
||||
return;
|
||||
// Look for possible gaia buildings inside our territory (may happen when enemy resign or after structure decay)
|
||||
// and attack it only if useful (and capturable) or dangereous.
|
||||
@ -588,7 +588,7 @@ PETRA.DefenseManager.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
let base = gameState.ai.HQ.getBaseByID(target.getMetadata(PlayerID, "base"));
|
||||
if (this.territoryMap.isBlinking(target.position()) && !gameState.ai.HQ.isDefendable(target) ||
|
||||
!base || gameState.ai.HQ.baseManagers.every(b => !b.anchor || b.accessIndex != base.accessIndex))
|
||||
!base || gameState.ai.HQ.baseManagers().every(b => !b.anchor || b.accessIndex != base.accessIndex))
|
||||
{
|
||||
let capture = target.capturePoints();
|
||||
if (!capture)
|
||||
|
@ -254,13 +254,13 @@ PETRA.returnResources = function(gameState, ent)
|
||||
PETRA.IsSupplyFull = function(gameState, ent)
|
||||
{
|
||||
return ent.isFull() === true ||
|
||||
ent.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(ent.id()) >= ent.maxGatherers();
|
||||
ent.resourceSupplyNumGatherers() + gameState.ai.HQ.basesManager.GetTCGatherer(ent.id()) >= ent.maxGatherers();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the best base (in terms of distance and accessIndex) for an entity.
|
||||
* It should be on the same accessIndex for structures.
|
||||
* If nothing found, return the base[0] for units and undefined for structures.
|
||||
* If nothing found, return the noBase for units and undefined for structures.
|
||||
* If exclude is given, we exclude the base with ID = exclude.
|
||||
*/
|
||||
PETRA.getBestBase = function(gameState, ent, onlyConstructedBase = false, exclude = false)
|
||||
@ -274,7 +274,7 @@ PETRA.getBestBase = function(gameState, ent, onlyConstructedBase = false, exclud
|
||||
{
|
||||
API3.warn("Petra error: entity without position, but not garrisoned");
|
||||
PETRA.dumpEntity(ent);
|
||||
return gameState.ai.HQ.baseManagers[0];
|
||||
return gameState.ai.HQ.basesManager.baselessBase();
|
||||
}
|
||||
pos = holder.position();
|
||||
accessIndex = PETRA.getLandAccess(gameState, holder);
|
||||
@ -285,9 +285,9 @@ PETRA.getBestBase = function(gameState, ent, onlyConstructedBase = false, exclud
|
||||
let distmin = Math.min();
|
||||
let dist;
|
||||
let bestbase;
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (base.ID == gameState.ai.HQ.baseManagers[0].ID || exclude && base.ID == exclude)
|
||||
if (base.ID == gameState.ai.HQ.basesManager.baselessBase().ID || exclude && base.ID == exclude)
|
||||
continue;
|
||||
if (onlyConstructedBase && (!base.anchor || base.anchor.foundationProgress() !== undefined))
|
||||
continue;
|
||||
@ -319,7 +319,7 @@ PETRA.getBestBase = function(gameState, ent, onlyConstructedBase = false, exclud
|
||||
bestbase = base;
|
||||
}
|
||||
if (!bestbase && !ent.hasClass("Structure"))
|
||||
bestbase = gameState.ai.HQ.baseManagers[0];
|
||||
bestbase = gameState.ai.HQ.basesManager.baselessBase();
|
||||
return bestbase;
|
||||
};
|
||||
|
||||
|
@ -21,7 +21,6 @@ PETRA.HQ = function(Config)
|
||||
this.lastFailedGather = {};
|
||||
|
||||
this.firstBaseConfig = false;
|
||||
this.currentBase = 0; // Only one base (from baseManager) is run every turn.
|
||||
|
||||
// Workers configuration.
|
||||
this.targetNumWorkers = this.Config.Economy.targetNumWorkers;
|
||||
@ -35,7 +34,7 @@ PETRA.HQ = function(Config)
|
||||
this.extraTowers = Math.round(Math.min(this.Config.difficulty, 3) * this.Config.personality.defensive);
|
||||
this.extraFortresses = Math.round(Math.max(Math.min(this.Config.difficulty - 1, 2), 0) * this.Config.personality.defensive);
|
||||
|
||||
this.baseManagers = [];
|
||||
this.basesManager = new PETRA.BasesManager(this.Config);
|
||||
this.attackManager = new PETRA.AttackManager(this.Config);
|
||||
this.buildManager = new PETRA.BuildManager();
|
||||
this.defenseManager = new PETRA.DefenseManager(this.Config);
|
||||
@ -54,8 +53,6 @@ PETRA.HQ = function(Config)
|
||||
PETRA.HQ.prototype.init = function(gameState, queues)
|
||||
{
|
||||
this.territoryMap = PETRA.createTerritoryMap(gameState);
|
||||
// initialize base map. Each pixel is a base ID, or 0 if not or not accessible
|
||||
this.basesMap = new API3.Map(gameState.sharedScript, "territory");
|
||||
// create borderMap: flag cells on the border of the map
|
||||
// then this map will be completed with our frontier in updateTerritories
|
||||
this.borderMap = PETRA.createBorderMap(gameState);
|
||||
@ -76,92 +73,10 @@ PETRA.HQ.prototype.init = function(gameState, queues)
|
||||
*/
|
||||
PETRA.HQ.prototype.postinit = function(gameState)
|
||||
{
|
||||
// Rebuild the base maps from the territory indices of each base
|
||||
this.basesMap = new API3.Map(gameState.sharedScript, "territory");
|
||||
for (let base of this.baseManagers)
|
||||
for (let j of base.territoryIndices)
|
||||
this.basesMap.map[j] = base.ID;
|
||||
|
||||
for (let ent of gameState.getOwnEntities().values())
|
||||
{
|
||||
if (!ent.resourceDropsiteTypes() || !ent.hasClass("Structure"))
|
||||
continue;
|
||||
// Entities which have been built or have changed ownership after the last AI turn have no base.
|
||||
// they will be dealt with in the next checkEvents
|
||||
let baseID = ent.getMetadata(PlayerID, "base");
|
||||
if (baseID === undefined)
|
||||
continue;
|
||||
let base = this.getBaseByID(baseID);
|
||||
base.assignResourceToDropsite(gameState, ent);
|
||||
}
|
||||
|
||||
this.basesManager.postinit(gameState);
|
||||
this.updateTerritories(gameState);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new base in the baseManager:
|
||||
* If an existing one without anchor already exist, use it.
|
||||
* Otherwise create a new one.
|
||||
* TODO when buildings, criteria should depend on distance
|
||||
* allowedType: undefined => new base with an anchor
|
||||
* "unconstructed" => new base with a foundation anchor
|
||||
* "captured" => captured base with an anchor
|
||||
* "anchorless" => anchorless base, currently with dock
|
||||
*/
|
||||
PETRA.HQ.prototype.createBase = function(gameState, ent, type)
|
||||
{
|
||||
let access = PETRA.getLandAccess(gameState, ent);
|
||||
let newbase;
|
||||
for (let base of this.baseManagers)
|
||||
{
|
||||
if (base.accessIndex != access)
|
||||
continue;
|
||||
if (type != "anchorless" && base.anchor)
|
||||
continue;
|
||||
if (type != "anchorless")
|
||||
{
|
||||
// TODO we keep the fisrt one, we should rather use the nearest if buildings
|
||||
// and possibly also cut on distance
|
||||
newbase = base;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO here also test on distance instead of first
|
||||
if (newbase && !base.anchor)
|
||||
continue;
|
||||
newbase = base;
|
||||
if (newbase.anchor)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.Config.debug > 0)
|
||||
{
|
||||
API3.warn(" ----------------------------------------------------------");
|
||||
API3.warn(" HQ createBase entrance avec access " + access + " and type " + type);
|
||||
API3.warn(" with access " + uneval(this.baseManagers.map(base => base.accessIndex)) +
|
||||
" and base nbr " + uneval(this.baseManagers.map(base => base.ID)) +
|
||||
" and anchor " + uneval(this.baseManagers.map(base => !!base.anchor)));
|
||||
}
|
||||
|
||||
if (!newbase)
|
||||
{
|
||||
newbase = new PETRA.BaseManager(gameState, this.Config);
|
||||
newbase.init(gameState, type);
|
||||
this.baseManagers.push(newbase);
|
||||
}
|
||||
else
|
||||
newbase.reset(type);
|
||||
|
||||
if (type != "anchorless")
|
||||
newbase.setAnchor(gameState, ent);
|
||||
else
|
||||
newbase.setAnchorlessEntity(gameState, ent);
|
||||
|
||||
return newbase;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the sea index linking regions 1 and region 2 (supposed to be different land region)
|
||||
* otherwise return undefined
|
||||
@ -182,11 +97,8 @@ PETRA.HQ.prototype.getSeaBetweenIndices = function(gameState, index1, index2)
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** TODO check if the new anchorless bases should be added to addBase */
|
||||
PETRA.HQ.prototype.checkEvents = function(gameState, events)
|
||||
{
|
||||
let addBase = false;
|
||||
|
||||
this.buildManager.checkEvents(gameState, events);
|
||||
|
||||
if (events.TerritoriesChanged.length || events.DiplomacyChanged.length)
|
||||
@ -201,76 +113,7 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events)
|
||||
break;
|
||||
}
|
||||
|
||||
for (let evt of events.Destroy)
|
||||
{
|
||||
// Let's check we haven't lost an important building here.
|
||||
if (evt && !evt.SuccessfulFoundation && evt.entityObj && evt.metadata && evt.metadata[PlayerID] &&
|
||||
evt.metadata[PlayerID].base)
|
||||
{
|
||||
let ent = evt.entityObj;
|
||||
if (ent.owner() != PlayerID)
|
||||
continue;
|
||||
// A new base foundation was created and destroyed on the same (AI) turn
|
||||
if (evt.metadata[PlayerID].base == -1 || evt.metadata[PlayerID].base == -2)
|
||||
continue;
|
||||
let base = this.getBaseByID(evt.metadata[PlayerID].base);
|
||||
if (ent.resourceDropsiteTypes() && ent.hasClass("Structure"))
|
||||
base.removeDropsite(gameState, ent);
|
||||
if (evt.metadata[PlayerID].baseAnchor && evt.metadata[PlayerID].baseAnchor === true)
|
||||
base.anchorLost(gameState, ent);
|
||||
}
|
||||
}
|
||||
|
||||
for (let evt of events.EntityRenamed)
|
||||
{
|
||||
let ent = gameState.getEntityById(evt.newentity);
|
||||
if (!ent || ent.owner() != PlayerID || ent.getMetadata(PlayerID, "base") === undefined)
|
||||
continue;
|
||||
let base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
|
||||
if (!base.anchorId || base.anchorId != evt.entity)
|
||||
continue;
|
||||
base.anchorId = evt.newentity;
|
||||
base.anchor = ent;
|
||||
}
|
||||
|
||||
for (let evt of events.Create)
|
||||
{
|
||||
// Let's check if we have a valuable foundation needing builders quickly
|
||||
// (normal foundations are taken care in baseManager.assignToFoundations)
|
||||
let ent = gameState.getEntityById(evt.entity);
|
||||
if (!ent || ent.owner() != PlayerID || ent.foundationProgress() === undefined)
|
||||
continue;
|
||||
|
||||
if (ent.getMetadata(PlayerID, "base") == -1) // Standard base around a cc
|
||||
{
|
||||
// Okay so let's try to create a new base around this.
|
||||
let newbase = this.createBase(gameState, ent, "unconstructed");
|
||||
// Let's get a few units from other bases there to build this.
|
||||
let builders = this.bulkPickWorkers(gameState, newbase, 10);
|
||||
if (builders !== false)
|
||||
{
|
||||
builders.forEach(worker => {
|
||||
worker.setMetadata(PlayerID, "base", newbase.ID);
|
||||
worker.setMetadata(PlayerID, "subrole", "builder");
|
||||
worker.setMetadata(PlayerID, "target-foundation", ent.id());
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (ent.getMetadata(PlayerID, "base") == -2) // anchorless base around a dock
|
||||
{
|
||||
let newbase = this.createBase(gameState, ent, "anchorless");
|
||||
// Let's get a few units from other bases there to build this.
|
||||
let builders = this.bulkPickWorkers(gameState, newbase, 4);
|
||||
if (builders != false)
|
||||
{
|
||||
builders.forEach(worker => {
|
||||
worker.setMetadata(PlayerID, "base", newbase.ID);
|
||||
worker.setMetadata(PlayerID, "subrole", "builder");
|
||||
worker.setMetadata(PlayerID, "target-foundation", ent.id());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.basesManager.checkEvents(gameState, events);
|
||||
|
||||
for (let evt of events.ConstructionFinished)
|
||||
{
|
||||
@ -281,84 +124,17 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events)
|
||||
continue;
|
||||
if (ent.hasClass("Market") && this.maxFields)
|
||||
this.maxFields = false;
|
||||
if (ent.getMetadata(PlayerID, "base") === undefined)
|
||||
continue;
|
||||
let base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
|
||||
base.buildings.updateEnt(ent);
|
||||
if (ent.resourceDropsiteTypes())
|
||||
base.assignResourceToDropsite(gameState, ent);
|
||||
|
||||
if (ent.getMetadata(PlayerID, "baseAnchor") === true)
|
||||
{
|
||||
if (base.constructing)
|
||||
base.constructing = false;
|
||||
addBase = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (let evt of events.OwnershipChanged) // capture events
|
||||
{
|
||||
if (evt.from == PlayerID)
|
||||
{
|
||||
let ent = gameState.getEntityById(evt.entity);
|
||||
if (!ent || ent.getMetadata(PlayerID, "base") === undefined)
|
||||
continue;
|
||||
let base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
|
||||
if (ent.resourceDropsiteTypes() && ent.hasClass("Structure"))
|
||||
base.removeDropsite(gameState, ent);
|
||||
if (ent.getMetadata(PlayerID, "baseAnchor") === true)
|
||||
base.anchorLost(gameState, ent);
|
||||
}
|
||||
|
||||
if (evt.to != PlayerID)
|
||||
continue;
|
||||
let ent = gameState.getEntityById(evt.entity);
|
||||
if (!ent)
|
||||
continue;
|
||||
if (ent.hasClass("Unit"))
|
||||
if (!ent.hasClass("Unit"))
|
||||
{
|
||||
PETRA.getBestBase(gameState, ent).assignEntity(gameState, ent);
|
||||
ent.setMetadata(PlayerID, "role", undefined);
|
||||
ent.setMetadata(PlayerID, "subrole", undefined);
|
||||
ent.setMetadata(PlayerID, "plan", undefined);
|
||||
ent.setMetadata(PlayerID, "PartOfArmy", undefined);
|
||||
if (ent.hasClass("Trader"))
|
||||
{
|
||||
ent.setMetadata(PlayerID, "role", "trader");
|
||||
ent.setMetadata(PlayerID, "route", undefined);
|
||||
}
|
||||
if (ent.hasClass("Worker"))
|
||||
{
|
||||
ent.setMetadata(PlayerID, "role", "worker");
|
||||
ent.setMetadata(PlayerID, "subrole", "idle");
|
||||
}
|
||||
if (ent.hasClass("Ship"))
|
||||
PETRA.setSeaAccess(gameState, ent);
|
||||
if (!ent.hasClasses(["Support", "Ship"]) && ent.attackTypes() !== undefined)
|
||||
ent.setMetadata(PlayerID, "plan", -1);
|
||||
continue;
|
||||
}
|
||||
if (ent.hasClass("CivCentre")) // build a new base around it
|
||||
{
|
||||
let newbase;
|
||||
if (ent.foundationProgress() !== undefined)
|
||||
newbase = this.createBase(gameState, ent, "unconstructed");
|
||||
else
|
||||
{
|
||||
newbase = this.createBase(gameState, ent, "captured");
|
||||
addBase = true;
|
||||
}
|
||||
newbase.assignEntity(gameState, ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
let base;
|
||||
// If dropsite on new island, create a base around it
|
||||
if (!ent.decaying() && ent.resourceDropsiteTypes())
|
||||
base = this.createBase(gameState, ent, "anchorless");
|
||||
else
|
||||
base = PETRA.getBestBase(gameState, ent) || this.baseManagers[0];
|
||||
base.assignEntity(gameState, ent);
|
||||
if (ent.decaying())
|
||||
{
|
||||
if (ent.isGarrisonHolder() && this.garrisonManager.addDecayingStructure(gameState, evt.entity, true))
|
||||
@ -366,7 +142,27 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events)
|
||||
if (!this.decayingStructures.has(evt.entity))
|
||||
this.decayingStructures.add(evt.entity);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ent.setMetadata(PlayerID, "role", undefined);
|
||||
ent.setMetadata(PlayerID, "subrole", undefined);
|
||||
ent.setMetadata(PlayerID, "plan", undefined);
|
||||
ent.setMetadata(PlayerID, "PartOfArmy", undefined);
|
||||
if (ent.hasClass("Trader"))
|
||||
{
|
||||
ent.setMetadata(PlayerID, "role", "trader");
|
||||
ent.setMetadata(PlayerID, "route", undefined);
|
||||
}
|
||||
if (ent.hasClass("Worker"))
|
||||
{
|
||||
ent.setMetadata(PlayerID, "role", "worker");
|
||||
ent.setMetadata(PlayerID, "subrole", "idle");
|
||||
}
|
||||
if (ent.hasClass("Ship"))
|
||||
PETRA.setSeaAccess(gameState, ent);
|
||||
if (!ent.hasClasses(["Support", "Ship"]) && ent.attackTypes() !== undefined)
|
||||
ent.setMetadata(PlayerID, "plan", -1);
|
||||
}
|
||||
|
||||
// deal with the different rally points of training units: the rally point is set when the training starts
|
||||
@ -417,43 +213,6 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events)
|
||||
if (!attack || attack.state != "unexecuted")
|
||||
ent.setMetadata(PlayerID, "plan", -1);
|
||||
}
|
||||
// Assign it immediately to something useful to do
|
||||
if (ent.getMetadata(PlayerID, "role") == "worker")
|
||||
{
|
||||
let base;
|
||||
if (ent.getMetadata(PlayerID, "base") === undefined)
|
||||
{
|
||||
base = PETRA.getBestBase(gameState, ent);
|
||||
base.assignEntity(gameState, ent);
|
||||
}
|
||||
else
|
||||
base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
|
||||
base.reassignIdleWorkers(gameState, [ent]);
|
||||
base.workerObject.update(gameState, ent);
|
||||
}
|
||||
else if (ent.resourceSupplyType() && ent.position())
|
||||
{
|
||||
let type = ent.resourceSupplyType();
|
||||
if (!type.generic)
|
||||
continue;
|
||||
let dropsites = gameState.getOwnDropsites(type.generic);
|
||||
let pos = ent.position();
|
||||
let access = PETRA.getLandAccess(gameState, ent);
|
||||
let distmin = Math.min();
|
||||
let goal;
|
||||
for (let dropsite of dropsites.values())
|
||||
{
|
||||
if (!dropsite.position() || PETRA.getLandAccess(gameState, dropsite) != access)
|
||||
continue;
|
||||
let dist = API3.SquareVectorDistance(pos, dropsite.position());
|
||||
if (dist > distmin)
|
||||
continue;
|
||||
distmin = dist;
|
||||
goal = dropsite.position();
|
||||
}
|
||||
if (goal)
|
||||
ent.moveToRange(goal[0], goal[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -473,22 +232,6 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events)
|
||||
this.garrisonManager.removeDecayingStructure(evt.entity);
|
||||
}
|
||||
|
||||
if (addBase)
|
||||
{
|
||||
if (!this.firstBaseConfig)
|
||||
{
|
||||
// This is our first base, let us configure our starting resources
|
||||
this.configFirstBase(gameState);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let us hope this new base will fix our possible resource shortage
|
||||
this.saveResources = undefined;
|
||||
this.saveSpace = undefined;
|
||||
this.maxFields = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Then deals with decaying structures: destroy them if being lost to enemy (except in easier difficulties)
|
||||
if (this.Config.difficulty < 2)
|
||||
return;
|
||||
@ -529,6 +272,20 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events)
|
||||
}
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.handleNewBase = function(gameState)
|
||||
{
|
||||
if (!this.firstBaseConfig)
|
||||
// This is our first base, let us configure our starting resources.
|
||||
this.configFirstBase(gameState);
|
||||
else
|
||||
{
|
||||
// Let us hope this new base will fix our possible resource shortage.
|
||||
this.saveResources = undefined;
|
||||
this.saveSpace = undefined;
|
||||
this.maxFields = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** Ensure that all requirements are met when phasing up*/
|
||||
PETRA.HQ.prototype.checkPhaseRequirements = function(gameState, queues)
|
||||
{
|
||||
@ -803,44 +560,12 @@ PETRA.HQ.prototype.findBestTrainableUnit = function(gameState, classes, requirem
|
||||
*/
|
||||
PETRA.HQ.prototype.bulkPickWorkers = function(gameState, baseRef, number)
|
||||
{
|
||||
let accessIndex = baseRef.accessIndex;
|
||||
if (!accessIndex)
|
||||
return false;
|
||||
// sorting bases by whether they are on the same accessindex or not.
|
||||
let baseBest = this.baseManagers.slice().sort((a, b) => {
|
||||
if (a.accessIndex == accessIndex && b.accessIndex != accessIndex)
|
||||
return -1;
|
||||
else if (b.accessIndex == accessIndex && a.accessIndex != accessIndex)
|
||||
return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
let needed = number;
|
||||
let workers = new API3.EntityCollection(gameState.sharedScript);
|
||||
for (let base of baseBest)
|
||||
{
|
||||
if (base.ID == baseRef.ID)
|
||||
continue;
|
||||
base.pickBuilders(gameState, workers, needed);
|
||||
if (workers.length >= number)
|
||||
break;
|
||||
needed = number - workers.length;
|
||||
}
|
||||
if (!workers.length)
|
||||
return false;
|
||||
return workers;
|
||||
return this.basesManager.bulkPickWorkers(gameState, baseRef, number);
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.getTotalResourceLevel = function(gameState)
|
||||
PETRA.HQ.prototype.getTotalResourceLevel = function(gameState, resources, proximity)
|
||||
{
|
||||
let total = {};
|
||||
for (let res of Resources.GetCodes())
|
||||
total[res] = 0;
|
||||
for (let base of this.baseManagers)
|
||||
for (let res in total)
|
||||
total[res] += base.getResourceLevel(gameState, res);
|
||||
|
||||
return total;
|
||||
return this.basesManager.getTotalResourceLevel(gameState, resources, proximity);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -849,22 +574,7 @@ PETRA.HQ.prototype.getTotalResourceLevel = function(gameState)
|
||||
*/
|
||||
PETRA.HQ.prototype.GetCurrentGatherRates = function(gameState)
|
||||
{
|
||||
if (!this.turnCache.currentRates)
|
||||
{
|
||||
let currentRates = {};
|
||||
for (let res of Resources.GetCodes())
|
||||
currentRates[res] = 0.5 * this.GetTCResGatherer(res);
|
||||
|
||||
for (let base of this.baseManagers)
|
||||
base.addGatherRates(gameState, currentRates);
|
||||
|
||||
for (let res of Resources.GetCodes())
|
||||
currentRates[res] = Math.max(currentRates[res], 0);
|
||||
|
||||
this.turnCache.currentRates = currentRates;
|
||||
}
|
||||
|
||||
return this.turnCache.currentRates;
|
||||
return this.basesManager.GetCurrentGatherRates(gameState);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1105,7 +815,7 @@ PETRA.HQ.prototype.findEconomicCCLocation = function(gameState, template, resour
|
||||
|
||||
// Define a minimal number of wanted ships in the seas reaching this new base
|
||||
let indexIdx = gameState.ai.accessibility.landPassMap[bestIdx];
|
||||
for (let base of this.baseManagers)
|
||||
for (const base of this.baseManagers())
|
||||
{
|
||||
if (!base.anchor || base.accessIndex == indexIdx)
|
||||
continue;
|
||||
@ -1248,7 +958,7 @@ PETRA.HQ.prototype.findStrategicCCLocation = function(gameState, template)
|
||||
|
||||
// Define a minimal number of wanted ships in the seas reaching this new base
|
||||
let indexIdx = gameState.ai.accessibility.landPassMap[bestIdx];
|
||||
for (let base of this.baseManagers)
|
||||
for (const base of this.baseManagers())
|
||||
{
|
||||
if (!base.anchor || base.accessIndex == indexIdx)
|
||||
continue;
|
||||
@ -1306,7 +1016,7 @@ PETRA.HQ.prototype.findMarketLocation = function(gameState, template)
|
||||
// do not try on the narrow border of our territory
|
||||
if (this.borderMap.map[j] & PETRA.narrowFrontier_Mask)
|
||||
continue;
|
||||
if (this.basesMap.map[j] == 0) // only in our territory
|
||||
if (this.baseAtIndex(j) == 0) // only in our territory
|
||||
continue;
|
||||
// with enough room around to build the market
|
||||
let i = this.territoryMap.getNonObstructedTile(j, radius, obstructions);
|
||||
@ -1376,7 +1086,7 @@ PETRA.HQ.prototype.findMarketLocation = function(gameState, template)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
idx = this.basesMap.map[bestJdx];
|
||||
idx = this.baseAtIndex(bestJdx);
|
||||
|
||||
let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
|
||||
let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
|
||||
@ -1451,7 +1161,7 @@ PETRA.HQ.prototype.findDefensiveLocation = function(gameState, template)
|
||||
if (this.borderMap.map[j] & PETRA.largeFrontier_Mask && isTower)
|
||||
continue;
|
||||
}
|
||||
if (this.basesMap.map[j] == 0) // inaccessible cell
|
||||
if (this.baseAtIndex(j) == 0) // inaccessible cell
|
||||
continue;
|
||||
// with enough room around to build the cc
|
||||
let i = this.territoryMap.getNonObstructedTile(j, radius, obstructions);
|
||||
@ -1518,7 +1228,7 @@ PETRA.HQ.prototype.findDefensiveLocation = function(gameState, template)
|
||||
|
||||
let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
|
||||
let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
|
||||
return [x, z, this.basesMap.map[bestJdx]];
|
||||
return [x, z, this.baseAtIndex(bestJdx)];
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.buildTemple = function(gameState, queues)
|
||||
@ -1770,7 +1480,7 @@ PETRA.HQ.prototype.checkBaseExpansion = function(gameState, queues)
|
||||
if (queues.civilCentre.hasQueuedUnits())
|
||||
return;
|
||||
// First build one cc if all have been destroyed
|
||||
if (this.numPotentialBases() == 0)
|
||||
if (!this.hasPotentialBase())
|
||||
{
|
||||
this.buildFirstBase(gameState);
|
||||
return;
|
||||
@ -1800,7 +1510,7 @@ PETRA.HQ.prototype.checkBaseExpansion = function(gameState, queues)
|
||||
|
||||
PETRA.HQ.prototype.buildNewBase = function(gameState, queues, resource)
|
||||
{
|
||||
if (this.numPotentialBases() > 0 && this.currentPhase == 1 && !gameState.isResearching(gameState.getPhaseName(2)))
|
||||
if (this.hasPotentialBase() && this.currentPhase == 1 && !gameState.isResearching(gameState.getPhaseName(2)))
|
||||
return false;
|
||||
if (gameState.getOwnFoundations().filter(API3.Filters.byClass("CivCentre")).hasEntities() || queues.civilCentre.hasQueuedUnits())
|
||||
return false;
|
||||
@ -2073,7 +1783,7 @@ PETRA.HQ.prototype.trainEmergencyUnits = function(gameState, positions)
|
||||
{
|
||||
let access = gameState.ai.accessibility.getAccessValue(pos);
|
||||
// check nearest base anchor
|
||||
for (let base of this.baseManagers)
|
||||
for (const base of this.baseManagers())
|
||||
{
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
continue;
|
||||
@ -2195,7 +1905,7 @@ PETRA.HQ.prototype.canBuild = function(gameState, structure)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.numActiveBases() < 1)
|
||||
if (!this.hasActiveBase())
|
||||
{
|
||||
// if no base, check that we can build outside our territory
|
||||
let buildTerritories = template.buildTerritories();
|
||||
@ -2237,23 +1947,7 @@ PETRA.HQ.prototype.updateTerritories = function(gameState)
|
||||
this.borderMap.map[j] &= ~PETRA.fullFrontier_Mask; // reset the frontier
|
||||
|
||||
if (this.territoryMap.getOwnerIndex(j) != PlayerID)
|
||||
{
|
||||
// If this tile was already accounted, remove it
|
||||
if (this.basesMap.map[j] == 0)
|
||||
continue;
|
||||
let base = this.getBaseByID(this.basesMap.map[j]);
|
||||
if (base)
|
||||
{
|
||||
let index = base.territoryIndices.indexOf(j);
|
||||
if (index != -1)
|
||||
base.territoryIndices.splice(index, 1);
|
||||
else
|
||||
API3.warn(" problem in headquarters::updateTerritories for base " + this.basesMap.map[j]);
|
||||
}
|
||||
else
|
||||
API3.warn(" problem in headquarters::updateTerritories without base " + this.basesMap.map[j]);
|
||||
this.basesMap.map[j] = 0;
|
||||
}
|
||||
this.basesManager.removeBaseFromTerritoryIndex(j);
|
||||
else
|
||||
{
|
||||
// Update the frontier
|
||||
@ -2291,42 +1985,8 @@ PETRA.HQ.prototype.updateTerritories = function(gameState)
|
||||
if (onFrontier && !(this.borderMap.map[j] & PETRA.narrowFrontier_Mask))
|
||||
this.borderMap.map[j] |= PETRA.largeFrontier_Mask;
|
||||
|
||||
// If this tile was not already accounted, add it.
|
||||
if (this.basesMap.map[j] != 0)
|
||||
continue;
|
||||
let landPassable = false;
|
||||
let ind = API3.getMapIndices(j, this.territoryMap, passabilityMap);
|
||||
let access;
|
||||
for (let k of ind)
|
||||
{
|
||||
if (!this.landRegions[gameState.ai.accessibility.landPassMap[k]])
|
||||
continue;
|
||||
landPassable = true;
|
||||
access = gameState.ai.accessibility.landPassMap[k];
|
||||
break;
|
||||
}
|
||||
if (!landPassable)
|
||||
continue;
|
||||
let distmin = Math.min();
|
||||
let baseID;
|
||||
let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
|
||||
for (let base of this.baseManagers)
|
||||
{
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
continue;
|
||||
if (base.accessIndex != access)
|
||||
continue;
|
||||
let dist = API3.SquareVectorDistance(base.anchor.position(), pos);
|
||||
if (dist >= distmin)
|
||||
continue;
|
||||
distmin = dist;
|
||||
baseID = base.ID;
|
||||
}
|
||||
if (!baseID)
|
||||
continue;
|
||||
this.getBaseByID(baseID).territoryIndices.push(j);
|
||||
this.basesMap.map[j] = baseID;
|
||||
expansion++;
|
||||
if (this.basesManager.addTerritoryIndexToBase(gameState, j, passabilityMap))
|
||||
expansion++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2340,57 +2000,12 @@ PETRA.HQ.prototype.updateTerritories = function(gameState)
|
||||
this.tradeManager.routeProspection = true;
|
||||
};
|
||||
|
||||
/** Reassign territories when a base is going to be deleted */
|
||||
PETRA.HQ.prototype.reassignTerritories = function(deletedBase)
|
||||
{
|
||||
let cellSize = this.territoryMap.cellSize;
|
||||
let width = this.territoryMap.width;
|
||||
for (let j = 0; j < this.territoryMap.length; ++j)
|
||||
{
|
||||
if (this.basesMap.map[j] != deletedBase.ID)
|
||||
continue;
|
||||
if (this.territoryMap.getOwnerIndex(j) != PlayerID)
|
||||
{
|
||||
API3.warn("Petra reassignTerritories: should never happen");
|
||||
this.basesMap.map[j] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
let distmin = Math.min();
|
||||
let baseID;
|
||||
let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
|
||||
for (let base of this.baseManagers)
|
||||
{
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
continue;
|
||||
if (base.accessIndex != deletedBase.accessIndex)
|
||||
continue;
|
||||
let dist = API3.SquareVectorDistance(base.anchor.position(), pos);
|
||||
if (dist >= distmin)
|
||||
continue;
|
||||
distmin = dist;
|
||||
baseID = base.ID;
|
||||
}
|
||||
if (baseID)
|
||||
{
|
||||
this.getBaseByID(baseID).territoryIndices.push(j);
|
||||
this.basesMap.map[j] = baseID;
|
||||
}
|
||||
else
|
||||
this.basesMap.map[j] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the base corresponding to baseID
|
||||
*/
|
||||
PETRA.HQ.prototype.getBaseByID = function(baseID)
|
||||
{
|
||||
for (let base of this.baseManagers)
|
||||
if (base.ID == baseID)
|
||||
return base;
|
||||
|
||||
return undefined;
|
||||
return this.basesManager.getBaseByID(baseID);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2400,54 +2015,22 @@ PETRA.HQ.prototype.getBaseByID = function(baseID)
|
||||
*/
|
||||
PETRA.HQ.prototype.numActiveBases = function()
|
||||
{
|
||||
if (!this.turnCache.base)
|
||||
this.updateBaseCache();
|
||||
return this.turnCache.base.active;
|
||||
return this.basesManager.numActiveBases();
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.hasActiveBase = function()
|
||||
{
|
||||
return this.basesManager.hasActiveBase();
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.numPotentialBases = function()
|
||||
{
|
||||
if (!this.turnCache.base)
|
||||
this.updateBaseCache();
|
||||
return this.turnCache.base.potential;
|
||||
return this.basesManager.numPotentialBases();
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.updateBaseCache = function()
|
||||
PETRA.HQ.prototype.hasPotentialBase = function()
|
||||
{
|
||||
this.turnCache.base = { "active": 0, "potential": 0 };
|
||||
for (let base of this.baseManagers)
|
||||
{
|
||||
if (!base.anchor)
|
||||
continue;
|
||||
++this.turnCache.base.potential;
|
||||
if (base.anchor.foundationProgress() === undefined)
|
||||
++this.turnCache.base.active;
|
||||
}
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.resetBaseCache = function()
|
||||
{
|
||||
this.turnCache.base = undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Count gatherers returning resources in the number of gatherers of resourceSupplies
|
||||
* to prevent the AI always reassigning idle workers to these resourceSupplies (specially in naval maps).
|
||||
*/
|
||||
PETRA.HQ.prototype.assignGatherers = function()
|
||||
{
|
||||
for (let base of this.baseManagers)
|
||||
{
|
||||
for (let worker of base.workers.values())
|
||||
{
|
||||
if (worker.unitAIState().split(".").indexOf("RETURNRESOURCE") === -1)
|
||||
continue;
|
||||
let orders = worker.unitAIOrderData();
|
||||
if (orders.length < 2 || !orders[1].target || orders[1].target != worker.getMetadata(PlayerID, "supply"))
|
||||
continue;
|
||||
this.AddTCGatherer(orders[1].target);
|
||||
}
|
||||
}
|
||||
return this.basesManager.hasPotentialBase();
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.isDangerousLocation = function(gameState, pos, radius)
|
||||
@ -2528,76 +2111,6 @@ PETRA.HQ.prototype.updateCaptureStrength = function(gameState)
|
||||
this.capturableTargetsTime = gameState.ai.elapsedTime;
|
||||
};
|
||||
|
||||
/** Some functions that register that we assigned a gatherer to a resource this turn */
|
||||
|
||||
/** add a gatherer to the turn cache for this supply. */
|
||||
PETRA.HQ.prototype.AddTCGatherer = function(supplyID)
|
||||
{
|
||||
if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID] !== undefined)
|
||||
++this.turnCache.resourceGatherer[supplyID];
|
||||
else
|
||||
{
|
||||
if (!this.turnCache.resourceGatherer)
|
||||
this.turnCache.resourceGatherer = {};
|
||||
this.turnCache.resourceGatherer[supplyID] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
/** remove a gatherer to the turn cache for this supply. */
|
||||
PETRA.HQ.prototype.RemoveTCGatherer = function(supplyID)
|
||||
{
|
||||
if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID])
|
||||
--this.turnCache.resourceGatherer[supplyID];
|
||||
else
|
||||
{
|
||||
if (!this.turnCache.resourceGatherer)
|
||||
this.turnCache.resourceGatherer = {};
|
||||
this.turnCache.resourceGatherer[supplyID] = -1;
|
||||
}
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.GetTCGatherer = function(supplyID)
|
||||
{
|
||||
if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID])
|
||||
return this.turnCache.resourceGatherer[supplyID];
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
/** The next two are to register that we assigned a gatherer to a resource this turn. */
|
||||
PETRA.HQ.prototype.AddTCResGatherer = function(resource)
|
||||
{
|
||||
if (this.turnCache["resourceGatherer-" + resource])
|
||||
++this.turnCache["resourceGatherer-" + resource];
|
||||
else
|
||||
this.turnCache["resourceGatherer-" + resource] = 1;
|
||||
|
||||
if (this.turnCache.currentRates)
|
||||
this.turnCache.currentRates[resource] += 0.5;
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.GetTCResGatherer = function(resource)
|
||||
{
|
||||
if (this.turnCache["resourceGatherer-" + resource])
|
||||
return this.turnCache["resourceGatherer-" + resource];
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* flag a resource as exhausted
|
||||
*/
|
||||
PETRA.HQ.prototype.isResourceExhausted = function(resource)
|
||||
{
|
||||
if (this.turnCache["exhausted-" + resource] == undefined)
|
||||
this.turnCache["exhausted-" + resource] = this.baseManagers.every(base =>
|
||||
!base.dropsiteSupplies[resource].nearby.length &&
|
||||
!base.dropsiteSupplies[resource].medium.length &&
|
||||
!base.dropsiteSupplies[resource].faraway.length);
|
||||
|
||||
return this.turnCache["exhausted-" + resource];
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a structure in blinking territory should/can be defended (currently if it has some attacking armies around)
|
||||
*/
|
||||
@ -2656,6 +2169,20 @@ PETRA.HQ.prototype.getAccountedWorkers = function(gameState)
|
||||
return this.turnCache.accountedWorkers;
|
||||
};
|
||||
|
||||
PETRA.HQ.prototype.baseManagers = function()
|
||||
{
|
||||
return this.basesManager.baseManagers;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} territoryIndex - The index to get the map for.
|
||||
* @return {number} - The ID of the base at the given territory index.
|
||||
*/
|
||||
PETRA.HQ.prototype.baseAtIndex = function(territoryIndex)
|
||||
{
|
||||
return this.basesManager.baseAtIndex(territoryIndex);
|
||||
};
|
||||
|
||||
/**
|
||||
* Some functions are run every turn
|
||||
* Others once in a while
|
||||
@ -2700,7 +2227,7 @@ PETRA.HQ.prototype.update = function(gameState, queues, events)
|
||||
else
|
||||
this.researchManager.checkPhase(gameState, queues);
|
||||
|
||||
if (this.numActiveBases() > 0)
|
||||
if (this.hasActiveBase())
|
||||
{
|
||||
if (gameState.ai.playedTurn % 4 == 0)
|
||||
this.trainMoreWorkers(gameState, queues);
|
||||
@ -2718,7 +2245,7 @@ PETRA.HQ.prototype.update = function(gameState, queues, events)
|
||||
this.researchManager.update(gameState, queues);
|
||||
}
|
||||
|
||||
if (this.numPotentialBases() < 1 ||
|
||||
if (!this.hasPotentialBase() ||
|
||||
this.canExpand && gameState.ai.playedTurn % 10 == 7 && this.currentPhase > 1)
|
||||
this.checkBaseExpansion(gameState, queues);
|
||||
|
||||
@ -2750,21 +2277,11 @@ PETRA.HQ.prototype.update = function(gameState, queues, events)
|
||||
this.buildDefenses(gameState, queues);
|
||||
}
|
||||
|
||||
this.assignGatherers();
|
||||
let nbBases = this.baseManagers.length;
|
||||
let activeBase; // We will loop only on 1 active base per turn
|
||||
do
|
||||
{
|
||||
this.currentBase %= this.baseManagers.length;
|
||||
activeBase = this.baseManagers[this.currentBase++].update(gameState, queues, events);
|
||||
--nbBases;
|
||||
// TODO what to do with this.reassignTerritories(this.baseManagers[this.currentBase]);
|
||||
}
|
||||
while (!activeBase && nbBases != 0);
|
||||
this.basesManager.update(gameState, queues, events);
|
||||
|
||||
this.navalManager.update(gameState, queues, events);
|
||||
|
||||
if (this.Config.difficulty > 0 && (this.numActiveBases() > 0 || !this.canBuildUnits))
|
||||
if (this.Config.difficulty > 0 && (this.hasActiveBase() || !this.canBuildUnits))
|
||||
this.attackManager.update(gameState, queues, events);
|
||||
|
||||
this.diplomacyManager.update(gameState, events);
|
||||
@ -2782,7 +2299,6 @@ PETRA.HQ.prototype.Serialize = function()
|
||||
{
|
||||
let properties = {
|
||||
"phasing": this.phasing,
|
||||
"currentBase": this.currentBase,
|
||||
"lastFailedGather": this.lastFailedGather,
|
||||
"firstBaseConfig": this.firstBaseConfig,
|
||||
"supportRatio": this.supportRatio,
|
||||
@ -2807,15 +2323,11 @@ PETRA.HQ.prototype.Serialize = function()
|
||||
"capturableTargetsTime": this.capturableTargetsTime
|
||||
};
|
||||
|
||||
let baseManagers = [];
|
||||
for (let base of this.baseManagers)
|
||||
baseManagers.push(base.Serialize());
|
||||
|
||||
if (this.Config.debug == -100)
|
||||
{
|
||||
API3.warn(" HQ serialization ---------------------");
|
||||
API3.warn(" properties " + uneval(properties));
|
||||
API3.warn(" baseManagers " + uneval(baseManagers));
|
||||
API3.warn(" baseManagers " + uneval(this.basesManager.Serialize()));
|
||||
API3.warn(" attackManager " + uneval(this.attackManager.Serialize()));
|
||||
API3.warn(" buildManager " + uneval(this.buildManager.Serialize()));
|
||||
API3.warn(" defenseManager " + uneval(this.defenseManager.Serialize()));
|
||||
@ -2830,7 +2342,7 @@ PETRA.HQ.prototype.Serialize = function()
|
||||
return {
|
||||
"properties": properties,
|
||||
|
||||
"baseManagers": baseManagers,
|
||||
"basesManager": this.basesManager.Serialize(),
|
||||
"attackManager": this.attackManager.Serialize(),
|
||||
"buildManager": this.buildManager.Serialize(),
|
||||
"defenseManager": this.defenseManager.Serialize(),
|
||||
@ -2848,16 +2360,10 @@ PETRA.HQ.prototype.Deserialize = function(gameState, data)
|
||||
for (let key in data.properties)
|
||||
this[key] = data.properties[key];
|
||||
|
||||
this.baseManagers = [];
|
||||
for (let base of data.baseManagers)
|
||||
{
|
||||
// the first call to deserialize set the ID base needed by entitycollections
|
||||
let newbase = new PETRA.BaseManager(gameState, this.Config);
|
||||
newbase.Deserialize(gameState, base);
|
||||
newbase.init(gameState);
|
||||
newbase.Deserialize(gameState, base);
|
||||
this.baseManagers.push(newbase);
|
||||
}
|
||||
|
||||
this.basesManager = new PETRA.BasesManager(this.Config);
|
||||
this.basesManager.init(gameState);
|
||||
this.basesManager.Deserialize(gameState, data.basesManager);
|
||||
|
||||
this.navalManager = new PETRA.NavalManager(this.Config);
|
||||
this.navalManager.init(gameState, true);
|
||||
|
@ -708,7 +708,7 @@ PETRA.NavalManager.prototype.moveApart = function(gameState)
|
||||
|
||||
PETRA.NavalManager.prototype.buildNavalStructures = function(gameState, queues)
|
||||
{
|
||||
if (!gameState.ai.HQ.navalMap || !gameState.ai.HQ.baseManagers[1])
|
||||
if (!gameState.ai.HQ.navalMap || !gameState.ai.HQ.hasPotentialBase())
|
||||
return;
|
||||
|
||||
if (gameState.ai.HQ.getAccountedPopulation(gameState) > this.Config.Economy.popForDock)
|
||||
@ -718,7 +718,7 @@ PETRA.NavalManager.prototype.buildNavalStructures = function(gameState, queues)
|
||||
gameState.ai.HQ.canBuild(gameState, "structures/{civ}/dock"))
|
||||
{
|
||||
let dockStarted = false;
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (dockStarted)
|
||||
break;
|
||||
@ -757,7 +757,7 @@ PETRA.NavalManager.prototype.buildNavalStructures = function(gameState, queues)
|
||||
else
|
||||
return;
|
||||
let wantedLand = {};
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
if (base.anchor)
|
||||
wantedLand[base.accessIndex] = true;
|
||||
let sea = this.docks.toEntityArray()[0].getMetadata(PlayerID, "sea");
|
||||
|
@ -419,7 +419,7 @@ PETRA.QueueManager.prototype.checkPausedQueues = function(gameState)
|
||||
for (let q in this.queues)
|
||||
{
|
||||
let toBePaused = false;
|
||||
if (gameState.ai.HQ.numPotentialBases() == 0)
|
||||
if (!gameState.ai.HQ.hasPotentialBase())
|
||||
toBePaused = q != "dock" && q != "civilCentre";
|
||||
else if (numWorkers < workersMin / 3)
|
||||
toBePaused = q != "citizenSoldier" && q != "villager" && q != "emergency";
|
||||
|
@ -197,13 +197,13 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState)
|
||||
{
|
||||
let base = this.metadata.base;
|
||||
for (let j = 0; j < placement.map.length; ++j)
|
||||
if (HQ.basesMap.map[j] == base)
|
||||
if (HQ.baseAtIndex(j) == base)
|
||||
placement.set(j, 45);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (let j = 0; j < placement.map.length; ++j)
|
||||
if (HQ.basesMap.map[j] != 0)
|
||||
if (HQ.baseAtIndex(j) != 0)
|
||||
placement.set(j, 45);
|
||||
}
|
||||
|
||||
@ -266,7 +266,7 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState)
|
||||
let base = this.metadata.base;
|
||||
for (let j = 0; j < placement.map.length; ++j)
|
||||
{
|
||||
if (HQ.basesMap.map[j] != base)
|
||||
if (HQ.baseAtIndex(j) != base)
|
||||
placement.map[j] = 0;
|
||||
else if (placement.map[j] > 0)
|
||||
{
|
||||
@ -286,7 +286,7 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState)
|
||||
{
|
||||
for (let j = 0; j < placement.map.length; ++j)
|
||||
{
|
||||
if (HQ.basesMap.map[j] == 0)
|
||||
if (HQ.baseAtIndex(j) == 0)
|
||||
placement.map[j] = 0;
|
||||
else if (placement.map[j] > 0)
|
||||
{
|
||||
@ -299,7 +299,7 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState)
|
||||
let z = (Math.floor(j / placement.width) + 0.5) * cellSize;
|
||||
if (HQ.isNearInvadingArmy([x, z]))
|
||||
placement.map[j] = 0;
|
||||
else if (favoredBase && HQ.basesMap.map[j] == favoredBase)
|
||||
else if (favoredBase && HQ.baseAtIndex(j) == favoredBase)
|
||||
placement.set(j, placement.map[j] + 100);
|
||||
}
|
||||
}
|
||||
@ -346,7 +346,7 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState)
|
||||
let territorypos = placement.gamePosToMapPos([x, z]);
|
||||
let territoryIndex = territorypos[0] + territorypos[1]*placement.width;
|
||||
// default angle = 3*Math.PI/4;
|
||||
return { "x": x, "z": z, "angle": 3*Math.PI/4, "base": HQ.basesMap.map[territoryIndex] };
|
||||
return { "x": x, "z": z, "angle": 3*Math.PI/4, "base": HQ.baseAtIndex(territoryIndex) };
|
||||
};
|
||||
|
||||
/**
|
||||
@ -524,7 +524,7 @@ PETRA.ConstructionPlan.prototype.findDockPosition = function(gameState)
|
||||
let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
|
||||
|
||||
// Assign this dock to a base
|
||||
let baseIndex = gameState.ai.HQ.basesMap.map[bestJdx];
|
||||
let baseIndex = gameState.ai.HQ.baseAtIndex(bestJdx);
|
||||
if (!baseIndex)
|
||||
baseIndex = -2; // We'll do an anchorless base around it
|
||||
|
||||
|
@ -19,16 +19,7 @@ PETRA.HQ.prototype.gameAnalysis = function(gameState)
|
||||
this.structureAnalysis(gameState);
|
||||
|
||||
// Let's get our initial situation here.
|
||||
let nobase = new PETRA.BaseManager(gameState, this.Config);
|
||||
nobase.init(gameState);
|
||||
nobase.accessIndex = 0;
|
||||
this.baseManagers.push(nobase); // baseManagers[0] will deal with unit/structure without base
|
||||
let ccEnts = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre"));
|
||||
for (let cc of ccEnts.values())
|
||||
if (cc.foundationProgress() === undefined)
|
||||
this.createBase(gameState, cc);
|
||||
else
|
||||
this.createBase(gameState, cc, "unconstructed");
|
||||
this.basesManager.init(gameState);
|
||||
this.updateTerritories(gameState);
|
||||
|
||||
// Assign entities and resources in the different bases
|
||||
@ -54,7 +45,7 @@ PETRA.HQ.prototype.gameAnalysis = function(gameState)
|
||||
}
|
||||
|
||||
// configure our first base strategy
|
||||
if (this.baseManagers.length > 1)
|
||||
if (!this.hasPotentialBase())
|
||||
this.configFirstBase(gameState);
|
||||
};
|
||||
|
||||
@ -94,41 +85,10 @@ PETRA.HQ.prototype.assignStartingEntities = function(gameState)
|
||||
for (let id of ent.garrisoned())
|
||||
ent.unload(id);
|
||||
|
||||
let bestbase;
|
||||
let territorypos = this.territoryMap.gamePosToMapPos(pos);
|
||||
let territoryIndex = territorypos[0] + territorypos[1]*this.territoryMap.width;
|
||||
for (let i = 1; i < this.baseManagers.length; ++i)
|
||||
{
|
||||
let base = this.baseManagers[i];
|
||||
if ((!ent.getMetadata(PlayerID, "base") || ent.getMetadata(PlayerID, "base") != base.ID) &&
|
||||
base.territoryIndices.indexOf(territoryIndex) == -1)
|
||||
continue;
|
||||
base.assignEntity(gameState, ent);
|
||||
bestbase = base;
|
||||
break;
|
||||
}
|
||||
if (!bestbase) // entity outside our territory
|
||||
{
|
||||
if (ent.hasClass("Structure") && !ent.decaying() && ent.resourceDropsiteTypes())
|
||||
bestbase = this.createBase(gameState, ent, "anchorless");
|
||||
else
|
||||
bestbase = PETRA.getBestBase(gameState, ent) || this.baseManagers[0];
|
||||
bestbase.assignEntity(gameState, ent);
|
||||
}
|
||||
// now assign entities garrisoned inside this entity
|
||||
if (ent.isGarrisonHolder() && ent.garrisoned().length)
|
||||
for (let id of ent.garrisoned())
|
||||
bestbase.assignEntity(gameState, gameState.getEntityById(id));
|
||||
// and find something useful to do if we already have a base
|
||||
if (pos && bestbase.ID !== this.baseManagers[0].ID)
|
||||
{
|
||||
bestbase.assignRolelessUnits(gameState, [ent]);
|
||||
if (ent.getMetadata(PlayerID, "role") === "worker")
|
||||
{
|
||||
bestbase.reassignIdleWorkers(gameState, [ent]);
|
||||
bestbase.workerObject.update(gameState, ent);
|
||||
}
|
||||
}
|
||||
|
||||
this.basesManager.assignEntity(gameState, ent, territoryIndex);
|
||||
}
|
||||
};
|
||||
|
||||
@ -434,7 +394,7 @@ PETRA.HQ.prototype.dispatchUnits = function(gameState)
|
||||
*/
|
||||
PETRA.HQ.prototype.configFirstBase = function(gameState)
|
||||
{
|
||||
if (this.baseManagers.length < 2)
|
||||
if (!this.hasPotentialBase())
|
||||
return;
|
||||
|
||||
this.firstBaseConfig = true;
|
||||
@ -443,7 +403,7 @@ PETRA.HQ.prototype.configFirstBase = function(gameState)
|
||||
let startingLand = [];
|
||||
for (let region in this.landRegions)
|
||||
{
|
||||
for (let base of this.baseManagers)
|
||||
for (const base of this.baseManagers())
|
||||
{
|
||||
if (!base.anchor || base.accessIndex != +region)
|
||||
continue;
|
||||
@ -477,20 +437,8 @@ PETRA.HQ.prototype.configFirstBase = function(gameState)
|
||||
|
||||
// - count the available food resource, and react accordingly
|
||||
let startingFood = gameState.getResources().food;
|
||||
let check = {};
|
||||
for (let proxim of ["nearby", "medium", "faraway"])
|
||||
{
|
||||
for (let base of this.baseManagers)
|
||||
{
|
||||
for (let supply of base.dropsiteSupplies.food[proxim])
|
||||
{
|
||||
if (check[supply.id]) // avoid double counting as same resource can appear several time
|
||||
continue;
|
||||
check[supply.id] = true;
|
||||
startingFood += supply.ent.resourceSupplyAmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
startingFood += this.getTotalResourceLevel(gameState, ["food"], ["nearby", "medium", "faraway"]).food;
|
||||
|
||||
if (startingFood < 800)
|
||||
{
|
||||
if (startingSize < 25000)
|
||||
@ -503,20 +451,8 @@ PETRA.HQ.prototype.configFirstBase = function(gameState)
|
||||
}
|
||||
// - count the available wood resource, and allow rushes only if enough (we should otherwise favor expansion)
|
||||
let startingWood = gameState.getResources().wood;
|
||||
check = {};
|
||||
for (let proxim of ["nearby", "medium", "faraway"])
|
||||
{
|
||||
for (let base of this.baseManagers)
|
||||
{
|
||||
for (let supply of base.dropsiteSupplies.wood[proxim])
|
||||
{
|
||||
if (check[supply.id]) // avoid double counting as same resource can appear several time
|
||||
continue;
|
||||
check[supply.id] = true;
|
||||
startingWood += supply.ent.resourceSupplyAmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
startingWood += this.getTotalResourceLevel(gameState, ["wood"], ["nearby", "medium", "faraway"]).wood;
|
||||
|
||||
if (this.Config.debug > 1)
|
||||
API3.warn("startingWood: " + startingWood + " (cut at 8500 for no rush and 6000 for saveResources)");
|
||||
if (startingWood < 6000)
|
||||
@ -547,7 +483,7 @@ PETRA.HQ.prototype.configFirstBase = function(gameState)
|
||||
// immediatly build a wood dropsite if possible.
|
||||
if (!gameState.getOwnEntitiesByClass("DropsiteWood", true).hasEntities())
|
||||
{
|
||||
const newDP = this.baseManagers[1].findBestDropsiteAndLocation(gameState, "wood");
|
||||
const newDP = this.baseManagers()[0].findBestDropsiteAndLocation(gameState, "wood");
|
||||
if (newDP.quality > 40 && this.canBuild(gameState, newDP.templateName))
|
||||
{
|
||||
// if we start with enough workers, put our available resources in this first dropsite
|
||||
@ -559,7 +495,7 @@ PETRA.HQ.prototype.configFirstBase = function(gameState)
|
||||
const cost = new API3.Resources(gameState.getTemplate(newDP.templateName).cost());
|
||||
gameState.ai.queueManager.setAccounts(gameState, cost, "dropsites");
|
||||
}
|
||||
gameState.ai.queues.dropsites.addPlan(new PETRA.ConstructionPlan(gameState, newDP.templateName, { "base": this.baseManagers[1].ID }, newDP.pos));
|
||||
gameState.ai.queues.dropsites.addPlan(new PETRA.ConstructionPlan(gameState, newDP.templateName, { "base": this.baseManagers()[0].ID }, newDP.pos));
|
||||
}
|
||||
}
|
||||
// and build immediately a corral if needed
|
||||
@ -567,6 +503,6 @@ PETRA.HQ.prototype.configFirstBase = function(gameState)
|
||||
{
|
||||
const template = gameState.applyCiv("structures/{civ}/corral");
|
||||
if (!gameState.getOwnEntitiesByClass("Corral", true).hasEntities() && this.canBuild(gameState, template))
|
||||
gameState.ai.queues.corral.addPlan(new PETRA.ConstructionPlan(gameState, template, { "base": this.baseManagers[1].ID }));
|
||||
gameState.ai.queues.corral.addPlan(new PETRA.ConstructionPlan(gameState, template, { "base": this.baseManagers()[0].ID }));
|
||||
}
|
||||
};
|
||||
|
@ -253,7 +253,7 @@ PETRA.TransportPlan.prototype.cancelTransport = function(gameState)
|
||||
let base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base"));
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
{
|
||||
for (let newbase of gameState.ai.HQ.baseManagers)
|
||||
for (const newbase of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (!newbase.anchor || !newbase.anchor.position())
|
||||
continue;
|
||||
|
@ -66,8 +66,8 @@ PETRA.Worker.prototype.update = function(gameState, ent)
|
||||
}
|
||||
|
||||
this.entAccess = PETRA.getLandAccess(gameState, ent);
|
||||
// base 0 for unassigned entities has no accessIndex, so take the one from the entity
|
||||
if (this.baseID == gameState.ai.HQ.baseManagers[0].ID)
|
||||
// Base for unassigned entities has no accessIndex, so take the one from the entity.
|
||||
if (this.baseID == gameState.ai.HQ.basesManager.baselessBase().ID)
|
||||
this.baseAccess = this.entAccess;
|
||||
else
|
||||
this.baseAccess = this.base.accessIndex;
|
||||
@ -222,10 +222,10 @@ PETRA.Worker.prototype.update = function(gameState, ent)
|
||||
if (supply && !supply.hasClasses(["Field", "Animal"]) &&
|
||||
supplyId != ent.getMetadata(PlayerID, "supply"))
|
||||
{
|
||||
let nbGatherers = supply.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(supplyId);
|
||||
const nbGatherers = supply.resourceSupplyNumGatherers() + this.base.GetTCGatherer(supplyId);
|
||||
if (nbGatherers > 1 && supply.resourceSupplyAmount()/nbGatherers < 30)
|
||||
{
|
||||
gameState.ai.HQ.RemoveTCGatherer(supplyId);
|
||||
this.base.RemoveTCGatherer(supplyId);
|
||||
this.startGathering(gameState);
|
||||
}
|
||||
else
|
||||
@ -236,7 +236,7 @@ PETRA.Worker.prototype.update = function(gameState, ent)
|
||||
ent.setMetadata(PlayerID, "supply", supplyId);
|
||||
else if (nearby.length)
|
||||
{
|
||||
gameState.ai.HQ.RemoveTCGatherer(supplyId);
|
||||
this.base.RemoveTCGatherer(supplyId);
|
||||
this.startGathering(gameState);
|
||||
}
|
||||
else
|
||||
@ -244,7 +244,7 @@ PETRA.Worker.prototype.update = function(gameState, ent)
|
||||
let medium = this.base.dropsiteSupplies[gatherType].medium;
|
||||
if (medium.length && !medium.some(sup => sup.id == supplyId))
|
||||
{
|
||||
gameState.ai.HQ.RemoveTCGatherer(supplyId);
|
||||
this.base.RemoveTCGatherer(supplyId);
|
||||
this.startGathering(gameState);
|
||||
}
|
||||
else
|
||||
@ -309,7 +309,7 @@ PETRA.Worker.prototype.update = function(gameState, ent)
|
||||
ent.setMetadata(PlayerID, "target-foundation", undefined);
|
||||
ent.setMetadata(PlayerID, "subrole", "idle");
|
||||
ent.stopMoving();
|
||||
if (this.baseID != gameState.ai.HQ.baseManagers[0].ID)
|
||||
if (this.baseID != gameState.ai.HQ.basesManager.baselessBase().ID)
|
||||
{
|
||||
// reassign it to something useful
|
||||
this.base.reassignIdleWorkers(gameState, [ent]);
|
||||
@ -330,7 +330,7 @@ PETRA.Worker.prototype.update = function(gameState, ent)
|
||||
{
|
||||
ent.setMetadata(PlayerID, "subrole", "idle");
|
||||
ent.setMetadata(PlayerID, "target-foundation", undefined);
|
||||
if (this.baseID != gameState.ai.HQ.baseManagers[0].ID)
|
||||
if (this.baseID != gameState.ai.HQ.basesManager.baselessBase().ID)
|
||||
{
|
||||
// reassign it to something useful
|
||||
this.base.reassignIdleWorkers(gameState, [ent]);
|
||||
@ -357,7 +357,7 @@ PETRA.Worker.prototype.update = function(gameState, ent)
|
||||
{
|
||||
// nothing to hunt around. Try another region if any
|
||||
let nowhereToHunt = true;
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (!base.anchor || !base.anchor.position())
|
||||
continue;
|
||||
@ -448,7 +448,8 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
if (resource == "food" && this.startHunting(gameState))
|
||||
return true;
|
||||
|
||||
let findSupply = function(ent, supplies) {
|
||||
const findSupply = function(worker, supplies) {
|
||||
const ent = worker.ent;
|
||||
let ret = false;
|
||||
let gatherRates = ent.resourceGatherRates();
|
||||
for (let i = 0; i < supplies.length; ++i)
|
||||
@ -468,7 +469,7 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
if (!gatherRates[supplyType])
|
||||
continue;
|
||||
// check if available resource is worth one additionnal gatherer (except for farms)
|
||||
let nbGatherers = supplies[i].ent.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(supplies[i].id);
|
||||
const nbGatherers = supplies[i].ent.resourceSupplyNumGatherers() + worker.base.GetTCGatherer(supplies[i].id);
|
||||
if (supplies[i].ent.resourceSupplyType().specific != "grain" && nbGatherers > 0 &&
|
||||
supplies[i].ent.resourceSupplyAmount()/(1+nbGatherers) < 30)
|
||||
continue;
|
||||
@ -476,7 +477,7 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(supplies[i].ent.position());
|
||||
if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner)) // player is its own ally
|
||||
continue;
|
||||
gameState.ai.HQ.AddTCGatherer(supplies[i].id);
|
||||
worker.base.AddTCGatherer(supplies[i].id);
|
||||
ent.setMetadata(PlayerID, "supply", supplies[i].id);
|
||||
ret = supplies[i].ent;
|
||||
break;
|
||||
@ -490,7 +491,7 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
// first look in our own base if accessible from our present position
|
||||
if (this.baseAccess == this.entAccess)
|
||||
{
|
||||
supply = findSupply(this.ent, this.base.dropsiteSupplies[resource].nearby);
|
||||
supply = findSupply(this, this.base.dropsiteSupplies[resource].nearby);
|
||||
if (supply)
|
||||
{
|
||||
this.ent.gather(supply);
|
||||
@ -512,7 +513,7 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
supply = findSupply(this.ent, this.base.dropsiteSupplies[resource].medium);
|
||||
supply = findSupply(this, this.base.dropsiteSupplies[resource].medium);
|
||||
if (supply)
|
||||
{
|
||||
this.ent.gather(supply);
|
||||
@ -521,13 +522,13 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
}
|
||||
// So if we're here we have checked our whole base for a proper resource (or it was not accessible)
|
||||
// --> check other bases directly accessible
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (base.ID == this.baseID)
|
||||
continue;
|
||||
if (base.accessIndex != this.entAccess)
|
||||
continue;
|
||||
supply = findSupply(this.ent, base.dropsiteSupplies[resource].nearby);
|
||||
supply = findSupply(this, base.dropsiteSupplies[resource].nearby);
|
||||
if (supply)
|
||||
{
|
||||
this.ent.setMetadata(PlayerID, "base", base.ID);
|
||||
@ -537,7 +538,7 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
}
|
||||
if (resource == "food") // --> for food, try to gather from fields if any, otherwise build one if any
|
||||
{
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (base.ID == this.baseID)
|
||||
continue;
|
||||
@ -559,13 +560,13 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (base.ID == this.baseID)
|
||||
continue;
|
||||
if (base.accessIndex != this.entAccess)
|
||||
continue;
|
||||
supply = findSupply(this.ent, base.dropsiteSupplies[resource].medium);
|
||||
supply = findSupply(this, base.dropsiteSupplies[resource].medium);
|
||||
if (supply)
|
||||
{
|
||||
this.ent.setMetadata(PlayerID, "base", base.ID);
|
||||
@ -596,11 +597,11 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
return true;
|
||||
|
||||
// Still nothing ... try bases which need a transport
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (base.accessIndex == this.entAccess)
|
||||
continue;
|
||||
supply = findSupply(this.ent, base.dropsiteSupplies[resource].nearby);
|
||||
supply = findSupply(this, base.dropsiteSupplies[resource].nearby);
|
||||
if (supply && navalManager.requireTransport(gameState, this.ent, this.entAccess, base.accessIndex, supply.position()))
|
||||
{
|
||||
if (base.ID != this.baseID)
|
||||
@ -610,7 +611,7 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
}
|
||||
if (resource == "food") // --> for food, try to gather from fields if any, otherwise build one if any
|
||||
{
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (base.accessIndex == this.entAccess)
|
||||
continue;
|
||||
@ -630,11 +631,11 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (base.accessIndex == this.entAccess)
|
||||
continue;
|
||||
supply = findSupply(this.ent, base.dropsiteSupplies[resource].medium);
|
||||
supply = findSupply(this, base.dropsiteSupplies[resource].medium);
|
||||
if (supply && navalManager.requireTransport(gameState, this.ent, this.entAccess, base.accessIndex, supply.position()))
|
||||
{
|
||||
if (base.ID != this.baseID)
|
||||
@ -680,20 +681,20 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
{
|
||||
if (this.baseAccess == this.entAccess)
|
||||
{
|
||||
supply = findSupply(this.ent, this.base.dropsiteSupplies[resource].faraway);
|
||||
supply = findSupply(this, this.base.dropsiteSupplies[resource].faraway);
|
||||
if (supply)
|
||||
{
|
||||
this.ent.gather(supply);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (base.ID == this.baseID)
|
||||
continue;
|
||||
if (base.accessIndex != this.entAccess)
|
||||
continue;
|
||||
supply = findSupply(this.ent, base.dropsiteSupplies[resource].faraway);
|
||||
supply = findSupply(this, base.dropsiteSupplies[resource].faraway);
|
||||
if (supply)
|
||||
{
|
||||
this.ent.setMetadata(PlayerID, "base", base.ID);
|
||||
@ -701,11 +702,11 @@ PETRA.Worker.prototype.startGathering = function(gameState)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (let base of gameState.ai.HQ.baseManagers)
|
||||
for (const base of gameState.ai.HQ.baseManagers())
|
||||
{
|
||||
if (base.accessIndex == this.entAccess)
|
||||
continue;
|
||||
supply = findSupply(this.ent, base.dropsiteSupplies[resource].faraway);
|
||||
supply = findSupply(this, base.dropsiteSupplies[resource].faraway);
|
||||
if (supply && navalManager.requireTransport(gameState, this.ent, this.entAccess, base.accessIndex, supply.position()))
|
||||
{
|
||||
if (base.ID != this.baseID)
|
||||
@ -781,7 +782,7 @@ PETRA.Worker.prototype.startHunting = function(gameState, position)
|
||||
if (PETRA.IsSupplyFull(gameState, supply))
|
||||
continue;
|
||||
// Check if available resource is worth one additionnal gatherer (except for farms).
|
||||
let nbGatherers = supply.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(supply.id());
|
||||
const nbGatherers = supply.resourceSupplyNumGatherers() + this.base.GetTCGatherer(supply.id());
|
||||
if (nbGatherers > 0 && supply.resourceSupplyAmount()/(1+nbGatherers) < 30)
|
||||
continue;
|
||||
|
||||
@ -826,7 +827,7 @@ PETRA.Worker.prototype.startHunting = function(gameState, position)
|
||||
{
|
||||
if (position)
|
||||
return true;
|
||||
gameState.ai.HQ.AddTCGatherer(nearestSupply.id());
|
||||
this.base.AddTCGatherer(nearestSupply.id());
|
||||
this.ent.gather(nearestSupply);
|
||||
this.ent.setMetadata(PlayerID, "supply", nearestSupply.id());
|
||||
this.ent.setMetadata(PlayerID, "target-foundation", undefined);
|
||||
@ -893,7 +894,7 @@ PETRA.Worker.prototype.startFishing = function(gameState)
|
||||
if (PETRA.IsSupplyFull(gameState, supply))
|
||||
return;
|
||||
// check if available resource is worth one additionnal gatherer (except for farms)
|
||||
let nbGatherers = supply.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(supply.id());
|
||||
const nbGatherers = supply.resourceSupplyNumGatherers() + this.base.GetTCGatherer(supply.id());
|
||||
if (nbGatherers > 0 && supply.resourceSupplyAmount()/(1+nbGatherers) < 30)
|
||||
return;
|
||||
|
||||
@ -919,7 +920,7 @@ PETRA.Worker.prototype.startFishing = function(gameState)
|
||||
|
||||
if (nearestSupply)
|
||||
{
|
||||
gameState.ai.HQ.AddTCGatherer(nearestSupply.id());
|
||||
this.base.AddTCGatherer(nearestSupply.id());
|
||||
this.ent.gather(nearestSupply);
|
||||
this.ent.setMetadata(PlayerID, "supply", nearestSupply.id());
|
||||
this.ent.setMetadata(PlayerID, "target-foundation", undefined);
|
||||
@ -948,7 +949,7 @@ PETRA.Worker.prototype.gatherNearestField = function(gameState, baseID)
|
||||
let diminishing = field.getDiminishingReturns();
|
||||
if (diminishing < 1)
|
||||
{
|
||||
let num = field.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(field.id());
|
||||
const num = field.resourceSupplyNumGatherers() + this.base.GetTCGatherer(field.id());
|
||||
if (num > 0)
|
||||
rate = Math.pow(diminishing, num);
|
||||
}
|
||||
@ -961,7 +962,7 @@ PETRA.Worker.prototype.gatherNearestField = function(gameState, baseID)
|
||||
if (!bestFarm || bestFarm.rate < 0.70 &&
|
||||
gameState.getOwnFoundations().filter(API3.Filters.byClass("Field")).filter(API3.Filters.byMetadata(PlayerID, "base", baseID)).hasEntities())
|
||||
return false;
|
||||
gameState.ai.HQ.AddTCGatherer(bestFarm.ent.id());
|
||||
this.base.AddTCGatherer(bestFarm.ent.id());
|
||||
this.ent.setMetadata(PlayerID, "supply", bestFarm.ent.id());
|
||||
return bestFarm.ent;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user