Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/ai/petra/basesManager.js
- This file was added.
/** | |||||
* Bases Manager | |||||
* Manages the list of available bases. | |||||
* 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; | |||||
const ccEnts = gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")); | |||||
for (const cc of ccEnts.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(); | |||||
baseBest.push(this.noBase); | |||||
// We can also use workers without a base. | |||||
// sorting bases by whether they are on the same accessindex or not. | |||||
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) | |||||
{ | |||||
const total = {}; | |||||
for (const res of Resources.GetCodes()) | |||||
total[res] = 0; | |||||
for (const base of this.baseManagers) | |||||
for (const res in total) | |||||
total[res] += base.getResourceLevel(gameState, res, ["nearby", "medium"]); | |||||
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) | |||||
{ | |||||
let currentRates = {}; | |||||
Lint: prefer-const: 'currentRates' is never reassigned. Use 'const' instead. | |||||
for (let res of Resources.GetCodes()) | |||||
Lint: prefer-const 'res' is never reassigned. Use 'const' instead. Lint: prefer-const: 'res' is never reassigned. Use 'const' instead. | |||||
currentRates[res] = 0.5 * this.GetTCResGatherer(res); | |||||
this.addGatherRates(gameState, currentRates); | |||||
for (let res of Resources.GetCodes()) | |||||
Lint: prefer-const 'res' is never reassigned. Use 'const' instead. Lint: prefer-const: 'res' is never reassigned. Use 'const' instead. | |||||
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) | |||||
{ | |||||
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.BasesManager.prototype.GetTCResGatherer = function(resource) | |||||
{ | |||||
if (this.turnCache["resourceGatherer-" + resource]) | |||||
return this.turnCache["resourceGatherer-" + resource]; | |||||
return 0; | |||||
}; | |||||
/** | |||||
* flag a resource as exhausted | |||||
*/ | |||||
PETRA.BasesManager.prototype.isResourceExhausted = function(resource) | |||||
{ | |||||
if (this.turnCache["exhausted-" + resource] == undefined) | |||||
this.turnCache["exhausted-" + resource] = this.basesManager.isResourceExhausted(resource); | |||||
return this.turnCache["exhausted-" + resource]; | |||||
Done Inline Actionstoo much "exhausted-" + resource I would make variable from that Silier: too much `"exhausted-" + resource` I would make variable from that | |||||
}; | |||||
/** | |||||
* @return {Object} - The number of active and potential bases. | |||||
* return.potential {number} - Bases that may or may not still be a foundation. | |||||
* return.active {number} - Usable bases. | |||||
*/ | |||||
PETRA.BasesManager.prototype.numberOfBases = function() | |||||
Done Inline Actionsmissleading name Silier: missleading name | |||||
Done Inline ActionsI can (and will) ditch this function and icorporate it in the update function. Freagarach: I can (and will) ditch this function and icorporate it in the update function. | |||||
{ | |||||
const result = { "active": 0, "potential": 0 }; | |||||
for (const base of this.baseManagers) | |||||
{ | |||||
if (!base.anchor) | |||||
continue; | |||||
++result.potential; | |||||
if (base.anchor.foundationProgress() === undefined) | |||||
++result.active; | |||||
} | |||||
return result; | |||||
}; | |||||
/** | |||||
* 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.numPotentialBases = function() | |||||
{ | |||||
if (!this.turnCache.base) | |||||
Done Inline Actionswrong, you should check what Deserialize of base does and what you are sending in second param Silier: wrong, you should check what Deserialize of base does and what you are sending in second param | |||||
Done Inline ActionsOh sorry, I was spending too much time on this patch yesterday. I misread. Freagarach: Oh sorry, I was spending too much time on this patch yesterday. I misread. | |||||
this.updateBaseCache(); | |||||
return this.turnCache.base.potential; | |||||
}; | |||||
PETRA.BasesManager.prototype.updateBaseCache = function() | |||||
{ | |||||
this.turnCache.base = this.numberOfBases(); | |||||
}; | |||||
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; | |||||
let orders = worker.unitAIOrderData(); | |||||
Lint: prefer-const 'orders' is never reassigned. Use 'const' instead. Lint: prefer-const: 'orders' is never reassigned. Use 'const' instead. | |||||
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; | |||||
}; | |||||
/** | |||||
* 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 HQ.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); | |||||
} | |||||
}; |
Wildfire Games · Phabricator
'currentRates' is never reassigned. Use 'const' instead.