Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/ai/petra/headquarters.js
Show All 15 Lines | PETRA.HQ = function(Config) | ||||
this.Config = Config; | this.Config = Config; | ||||
this.phasing = 0; // existing values: 0 means no, i > 0 means phasing towards phase i | this.phasing = 0; // existing values: 0 means no, i > 0 means phasing towards phase i | ||||
// Cache various quantities. | // Cache various quantities. | ||||
this.turnCache = {}; | this.turnCache = {}; | ||||
this.lastFailedGather = {}; | this.lastFailedGather = {}; | ||||
this.firstBaseConfig = false; | this.firstBaseConfig = false; | ||||
this.currentBase = 0; // Only one base (from baseManager) is run every turn. | |||||
// Workers configuration. | // Workers configuration. | ||||
this.targetNumWorkers = this.Config.Economy.targetNumWorkers; | this.targetNumWorkers = this.Config.Economy.targetNumWorkers; | ||||
this.supportRatio = this.Config.Economy.supportRatio; | this.supportRatio = this.Config.Economy.supportRatio; | ||||
this.fortStartTime = 180; // Sentry towers, will start at fortStartTime + towerLapseTime. | this.fortStartTime = 180; // Sentry towers, will start at fortStartTime + towerLapseTime. | ||||
this.towerStartTime = 0; // Stone towers, will start as soon as available (town phase). | this.towerStartTime = 0; // Stone towers, will start as soon as available (town phase). | ||||
this.towerLapseTime = this.Config.Military.towerLapseTime; | this.towerLapseTime = this.Config.Military.towerLapseTime; | ||||
this.fortressStartTime = 0; // Fortresses, will start as soon as available (city phase). | this.fortressStartTime = 0; // Fortresses, will start as soon as available (city phase). | ||||
this.fortressLapseTime = this.Config.Military.fortressLapseTime; | this.fortressLapseTime = this.Config.Military.fortressLapseTime; | ||||
this.extraTowers = Math.round(Math.min(this.Config.difficulty, 3) * this.Config.personality.defensive); | 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.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.attackManager = new PETRA.AttackManager(this.Config); | ||||
this.buildManager = new PETRA.BuildManager(); | this.buildManager = new PETRA.BuildManager(); | ||||
this.defenseManager = new PETRA.DefenseManager(this.Config); | this.defenseManager = new PETRA.DefenseManager(this.Config); | ||||
this.tradeManager = new PETRA.TradeManager(this.Config); | this.tradeManager = new PETRA.TradeManager(this.Config); | ||||
this.navalManager = new PETRA.NavalManager(this.Config); | this.navalManager = new PETRA.NavalManager(this.Config); | ||||
this.researchManager = new PETRA.ResearchManager(this.Config); | this.researchManager = new PETRA.ResearchManager(this.Config); | ||||
this.diplomacyManager = new PETRA.DiplomacyManager(this.Config); | this.diplomacyManager = new PETRA.DiplomacyManager(this.Config); | ||||
this.garrisonManager = new PETRA.GarrisonManager(this.Config); | this.garrisonManager = new PETRA.GarrisonManager(this.Config); | ||||
this.victoryManager = new PETRA.VictoryManager(this.Config); | this.victoryManager = new PETRA.VictoryManager(this.Config); | ||||
this.capturableTargets = new Map(); | this.capturableTargets = new Map(); | ||||
this.capturableTargetsTime = 0; | this.capturableTargetsTime = 0; | ||||
}; | }; | ||||
/** More initialisation for stuff that needs the gameState */ | /** More initialisation for stuff that needs the gameState */ | ||||
PETRA.HQ.prototype.init = function(gameState, queues) | PETRA.HQ.prototype.init = function(gameState, queues) | ||||
{ | { | ||||
this.territoryMap = PETRA.createTerritoryMap(gameState); | 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 | // create borderMap: flag cells on the border of the map | ||||
// then this map will be completed with our frontier in updateTerritories | // then this map will be completed with our frontier in updateTerritories | ||||
this.borderMap = PETRA.createBorderMap(gameState); | this.borderMap = PETRA.createBorderMap(gameState); | ||||
// list of allowed regions | // list of allowed regions | ||||
this.landRegions = {}; | this.landRegions = {}; | ||||
// try to determine if we have a water map | // try to determine if we have a water map | ||||
this.navalMap = false; | this.navalMap = false; | ||||
this.navalRegions = {}; | this.navalRegions = {}; | ||||
this.treasures = gameState.getEntities().filter(ent => ent.isTreasure()); | this.treasures = gameState.getEntities().filter(ent => ent.isTreasure()); | ||||
this.treasures.registerUpdates(); | this.treasures.registerUpdates(); | ||||
this.currentPhase = gameState.currentPhase(); | this.currentPhase = gameState.currentPhase(); | ||||
this.decayingStructures = new Set(); | this.decayingStructures = new Set(); | ||||
}; | }; | ||||
/** | /** | ||||
* initialization needed after deserialization (only called when deserialization) | * initialization needed after deserialization (only called when deserialization) | ||||
*/ | */ | ||||
PETRA.HQ.prototype.postinit = function(gameState) | PETRA.HQ.prototype.postinit = function(gameState) | ||||
{ | { | ||||
// Rebuild the base maps from the territory indices of each base | this.basesManager.postinit(gameState); | ||||
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.updateTerritories(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) | * returns the sea index linking regions 1 and region 2 (supposed to be different land region) | ||||
* otherwise return undefined | * otherwise return undefined | ||||
* for the moment, only the case land-sea-land is supported | * for the moment, only the case land-sea-land is supported | ||||
*/ | */ | ||||
PETRA.HQ.prototype.getSeaBetweenIndices = function(gameState, index1, index2) | PETRA.HQ.prototype.getSeaBetweenIndices = function(gameState, index1, index2) | ||||
{ | { | ||||
let path = gameState.ai.accessibility.getTrajectToIndex(index1, index2); | let path = gameState.ai.accessibility.getTrajectToIndex(index1, index2); | ||||
if (path && path.length == 3 && gameState.ai.accessibility.regionType[path[1]] == "water") | if (path && path.length == 3 && gameState.ai.accessibility.regionType[path[1]] == "water") | ||||
return path[1]; | return path[1]; | ||||
if (this.Config.debug > 1) | if (this.Config.debug > 1) | ||||
{ | { | ||||
API3.warn("bad path from " + index1 + " to " + index2 + " ??? " + uneval(path)); | API3.warn("bad path from " + index1 + " to " + index2 + " ??? " + uneval(path)); | ||||
API3.warn(" regionLinks start " + uneval(gameState.ai.accessibility.regionLinks[index1])); | API3.warn(" regionLinks start " + uneval(gameState.ai.accessibility.regionLinks[index1])); | ||||
API3.warn(" regionLinks end " + uneval(gameState.ai.accessibility.regionLinks[index2])); | API3.warn(" regionLinks end " + uneval(gameState.ai.accessibility.regionLinks[index2])); | ||||
} | } | ||||
return undefined; | return undefined; | ||||
}; | }; | ||||
/** TODO check if the new anchorless bases should be added to addBase */ | |||||
PETRA.HQ.prototype.checkEvents = function(gameState, events) | PETRA.HQ.prototype.checkEvents = function(gameState, events) | ||||
{ | { | ||||
let addBase = false; | |||||
this.buildManager.checkEvents(gameState, events); | this.buildManager.checkEvents(gameState, events); | ||||
if (events.TerritoriesChanged.length || events.DiplomacyChanged.length) | if (events.TerritoriesChanged.length || events.DiplomacyChanged.length) | ||||
this.updateTerritories(gameState); | this.updateTerritories(gameState); | ||||
for (let evt of events.DiplomacyChanged) | for (let evt of events.DiplomacyChanged) | ||||
{ | { | ||||
if (evt.player != PlayerID && evt.otherPlayer != PlayerID) | if (evt.player != PlayerID && evt.otherPlayer != PlayerID) | ||||
continue; | continue; | ||||
// Reset the entities collections which depend on diplomacy | // Reset the entities collections which depend on diplomacy | ||||
gameState.resetOnDiplomacyChanged(); | gameState.resetOnDiplomacyChanged(); | ||||
break; | break; | ||||
} | } | ||||
for (let evt of events.Destroy) | this.basesManager.checkEvents(gameState, events); | ||||
{ | |||||
// 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()); | |||||
}); | |||||
} | |||||
} | |||||
} | |||||
for (let evt of events.ConstructionFinished) | for (let evt of events.ConstructionFinished) | ||||
{ | { | ||||
if (evt.newentity == evt.entity) // repaired building | if (evt.newentity == evt.entity) // repaired building | ||||
continue; | continue; | ||||
let ent = gameState.getEntityById(evt.newentity); | let ent = gameState.getEntityById(evt.newentity); | ||||
if (!ent || ent.owner() != PlayerID) | if (!ent || ent.owner() != PlayerID) | ||||
continue; | continue; | ||||
if (ent.hasClass("Market") && this.maxFields) | if (ent.hasClass("Market") && this.maxFields) | ||||
this.maxFields = false; | 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 | 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) | |||||
Silier: what if entitiy is from playerId and has no base? | |||||
Not Done Inline ActionsMight have been captured on the same AI-turn it was created? But at least we don't need to worry about "base" stuff then. Freagarach: Might have been captured on the same AI-turn it was created? But at least we don't need to… | |||||
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) | if (evt.to != PlayerID) | ||||
continue; | continue; | ||||
let ent = gameState.getEntityById(evt.entity); | let ent = gameState.getEntityById(evt.entity); | ||||
if (!ent) | if (!ent) | ||||
continue; | continue; | ||||
if (ent.hasClass("Unit")) | if (!ent.hasClass("Unit")) | ||||
{ | { | ||||
PETRA.getBestBase(gameState, ent).assignEntity(gameState, ent); | if (ent.decaying()) | ||||
{ | |||||
if (ent.isGarrisonHolder() && this.garrisonManager.addDecayingStructure(gameState, evt.entity, true)) | |||||
continue; | |||||
if (!this.decayingStructures.has(evt.entity)) | |||||
this.decayingStructures.add(evt.entity); | |||||
} | |||||
continue; | |||||
} | |||||
ent.setMetadata(PlayerID, "role", undefined); | ent.setMetadata(PlayerID, "role", undefined); | ||||
ent.setMetadata(PlayerID, "subrole", undefined); | ent.setMetadata(PlayerID, "subrole", undefined); | ||||
ent.setMetadata(PlayerID, "plan", undefined); | ent.setMetadata(PlayerID, "plan", undefined); | ||||
ent.setMetadata(PlayerID, "PartOfArmy", undefined); | ent.setMetadata(PlayerID, "PartOfArmy", undefined); | ||||
if (ent.hasClass("Trader")) | if (ent.hasClass("Trader")) | ||||
{ | { | ||||
ent.setMetadata(PlayerID, "role", "trader"); | ent.setMetadata(PlayerID, "role", "trader"); | ||||
ent.setMetadata(PlayerID, "route", undefined); | ent.setMetadata(PlayerID, "route", undefined); | ||||
} | } | ||||
if (ent.hasClass("Worker")) | if (ent.hasClass("Worker")) | ||||
{ | { | ||||
ent.setMetadata(PlayerID, "role", "worker"); | ent.setMetadata(PlayerID, "role", "worker"); | ||||
ent.setMetadata(PlayerID, "subrole", "idle"); | ent.setMetadata(PlayerID, "subrole", "idle"); | ||||
} | } | ||||
if (ent.hasClass("Ship")) | if (ent.hasClass("Ship")) | ||||
PETRA.setSeaAccess(gameState, ent); | PETRA.setSeaAccess(gameState, ent); | ||||
if (!ent.hasClasses(["Support", "Ship"]) && ent.attackTypes() !== undefined) | if (!ent.hasClasses(["Support", "Ship"]) && ent.attackTypes() !== undefined) | ||||
ent.setMetadata(PlayerID, "plan", -1); | 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)) | |||||
continue; | |||||
if (!this.decayingStructures.has(evt.entity)) | |||||
this.decayingStructures.add(evt.entity); | |||||
} | |||||
} | |||||
} | } | ||||
// deal with the different rally points of training units: the rally point is set when the training starts | // deal with the different rally points of training units: the rally point is set when the training starts | ||||
// for the time being, only autogarrison is used | // for the time being, only autogarrison is used | ||||
for (let evt of events.TrainingStarted) | for (let evt of events.TrainingStarted) | ||||
{ | { | ||||
let ent = gameState.getEntityById(evt.entity); | let ent = gameState.getEntityById(evt.entity); | ||||
Show All 34 Lines | for (let entId of evt.entities) | ||||
// (happen when the training ends after the attack is started or aborted) | // (happen when the training ends after the attack is started or aborted) | ||||
let plan = ent.getMetadata(PlayerID, "plan"); | let plan = ent.getMetadata(PlayerID, "plan"); | ||||
if (plan !== undefined && plan >= 0) | if (plan !== undefined && plan >= 0) | ||||
{ | { | ||||
let attack = this.attackManager.getPlan(plan); | let attack = this.attackManager.getPlan(plan); | ||||
if (!attack || attack.state != "unexecuted") | if (!attack || attack.state != "unexecuted") | ||||
ent.setMetadata(PlayerID, "plan", -1); | 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]); | |||||
} | |||||
} | } | ||||
} | } | ||||
for (let evt of events.TerritoryDecayChanged) | for (let evt of events.TerritoryDecayChanged) | ||||
{ | { | ||||
let ent = gameState.getEntityById(evt.entity); | let ent = gameState.getEntityById(evt.entity); | ||||
if (!ent || !ent.isOwn(PlayerID) || ent.foundationProgress() !== undefined) | if (!ent || !ent.isOwn(PlayerID) || ent.foundationProgress() !== undefined) | ||||
continue; | continue; | ||||
if (evt.to) | if (evt.to) | ||||
{ | { | ||||
if (ent.isGarrisonHolder() && this.garrisonManager.addDecayingStructure(gameState, evt.entity)) | if (ent.isGarrisonHolder() && this.garrisonManager.addDecayingStructure(gameState, evt.entity)) | ||||
continue; | continue; | ||||
if (!this.decayingStructures.has(evt.entity)) | if (!this.decayingStructures.has(evt.entity)) | ||||
this.decayingStructures.add(evt.entity); | this.decayingStructures.add(evt.entity); | ||||
} | } | ||||
else if (ent.isGarrisonHolder()) | else if (ent.isGarrisonHolder()) | ||||
this.garrisonManager.removeDecayingStructure(evt.entity); | 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) | // Then deals with decaying structures: destroy them if being lost to enemy (except in easier difficulties) | ||||
if (this.Config.difficulty < 2) | if (this.Config.difficulty < 2) | ||||
return; | return; | ||||
for (let entId of this.decayingStructures) | for (let entId of this.decayingStructures) | ||||
{ | { | ||||
let ent = gameState.getEntityById(entId); | let ent = gameState.getEntityById(entId); | ||||
if (ent && ent.decaying() && ent.isOwn(PlayerID)) | if (ent && ent.decaying() && ent.isOwn(PlayerID)) | ||||
{ | { | ||||
Show All 24 Lines | if (ent && ent.decaying() && ent.isOwn(PlayerID)) | ||||
if (captureRatio > ratioMax) | if (captureRatio > ratioMax) | ||||
continue; | continue; | ||||
ent.destroy(); | ent.destroy(); | ||||
} | } | ||||
this.decayingStructures.delete(entId); | this.decayingStructures.delete(entId); | ||||
} | } | ||||
}; | }; | ||||
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*/ | /** Ensure that all requirements are met when phasing up*/ | ||||
PETRA.HQ.prototype.checkPhaseRequirements = function(gameState, queues) | PETRA.HQ.prototype.checkPhaseRequirements = function(gameState, queues) | ||||
{ | { | ||||
if (gameState.getNumberOfPhases() == this.currentPhase) | if (gameState.getNumberOfPhases() == this.currentPhase) | ||||
return; | return; | ||||
let requirements = gameState.getPhaseEntityRequirements(this.currentPhase + 1); | let requirements = gameState.getPhaseEntityRequirements(this.currentPhase + 1); | ||||
let plan; | let plan; | ||||
▲ Show 20 Lines • Show All 258 Lines • ▼ Show 20 Lines | |||||
}; | }; | ||||
/** | /** | ||||
* returns an entity collection of workers through BaseManager.pickBuilders | * returns an entity collection of workers through BaseManager.pickBuilders | ||||
* TODO: when same accessIndex, sort by distance | * TODO: when same accessIndex, sort by distance | ||||
*/ | */ | ||||
PETRA.HQ.prototype.bulkPickWorkers = function(gameState, baseRef, number) | PETRA.HQ.prototype.bulkPickWorkers = function(gameState, baseRef, number) | ||||
{ | { | ||||
let accessIndex = baseRef.accessIndex; | return this.basesManager.bulkPickWorkers(gameState, baseRef, number); | ||||
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; | |||||
}; | }; | ||||
PETRA.HQ.prototype.getTotalResourceLevel = function(gameState) | PETRA.HQ.prototype.getTotalResourceLevel = function(gameState) | ||||
{ | { | ||||
let total = {}; | return this.basesManager.getTotalResourceLevel(gameState); | ||||
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; | |||||
}; | }; | ||||
/** | /** | ||||
* Returns the current gather rate | * 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. | * This is not per-se exact, it performs a few adjustments ad-hoc to account for travel distance, stuffs like that. | ||||
*/ | */ | ||||
PETRA.HQ.prototype.GetCurrentGatherRates = function(gameState) | PETRA.HQ.prototype.GetCurrentGatherRates = function(gameState) | ||||
{ | { | ||||
if (!this.turnCache.currentRates) | return this.basesManager.GetCurrentGatherRates(gameState); | ||||
{ | |||||
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; | |||||
}; | }; | ||||
/** | /** | ||||
* Returns the wanted gather rate. | * Returns the wanted gather rate. | ||||
*/ | */ | ||||
PETRA.HQ.prototype.GetWantedGatherRates = function(gameState) | PETRA.HQ.prototype.GetWantedGatherRates = function(gameState) | ||||
{ | { | ||||
if (!this.turnCache.wantedRates) | if (!this.turnCache.wantedRates) | ||||
▲ Show 20 Lines • Show All 224 Lines • ▼ Show 20 Lines | PETRA.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource, proximity, fromStrategic) | ||||
if (bestVal < cut) | if (bestVal < cut) | ||||
return false; | return false; | ||||
let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize; | let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize; | ||||
let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize; | let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize; | ||||
// Define a minimal number of wanted ships in the seas reaching this new base | // Define a minimal number of wanted ships in the seas reaching this new base | ||||
let indexIdx = gameState.ai.accessibility.landPassMap[bestIdx]; | 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) | if (!base.anchor || base.accessIndex == indexIdx) | ||||
continue; | continue; | ||||
let sea = this.getSeaBetweenIndices(gameState, base.accessIndex, indexIdx); | let sea = this.getSeaBetweenIndices(gameState, base.accessIndex, indexIdx); | ||||
if (sea !== undefined) | if (sea !== undefined) | ||||
this.navalManager.setMinimalTransportShips(gameState, sea, 1); | this.navalManager.setMinimalTransportShips(gameState, sea, 1); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 126 Lines • ▼ Show 20 Lines | PETRA.HQ.prototype.findStrategicCCLocation = function(gameState, template) | ||||
if (bestVal === undefined) | if (bestVal === undefined) | ||||
return undefined; | return undefined; | ||||
let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize; | let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize; | ||||
let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize; | let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize; | ||||
// Define a minimal number of wanted ships in the seas reaching this new base | // Define a minimal number of wanted ships in the seas reaching this new base | ||||
let indexIdx = gameState.ai.accessibility.landPassMap[bestIdx]; | 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) | if (!base.anchor || base.accessIndex == indexIdx) | ||||
continue; | continue; | ||||
let sea = this.getSeaBetweenIndices(gameState, base.accessIndex, indexIdx); | let sea = this.getSeaBetweenIndices(gameState, base.accessIndex, indexIdx); | ||||
if (sea !== undefined) | if (sea !== undefined) | ||||
this.navalManager.setMinimalTransportShips(gameState, sea, 1); | this.navalManager.setMinimalTransportShips(gameState, sea, 1); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | PETRA.HQ.prototype.findMarketLocation = function(gameState, template) | ||||
let traderTemplatesGains = gameState.getTraderTemplatesGains(); | let traderTemplatesGains = gameState.getTraderTemplatesGains(); | ||||
for (let j = 0; j < this.territoryMap.length; ++j) | for (let j = 0; j < this.territoryMap.length; ++j) | ||||
{ | { | ||||
// do not try on the narrow border of our territory | // do not try on the narrow border of our territory | ||||
if (this.borderMap.map[j] & PETRA.narrowFrontier_Mask) | if (this.borderMap.map[j] & PETRA.narrowFrontier_Mask) | ||||
continue; | continue; | ||||
if (this.basesMap.map[j] == 0) // only in our territory | if (this.basesManager.basesMap.map[j] == 0) // only in our territory | ||||
continue; | continue; | ||||
// with enough room around to build the market | // with enough room around to build the market | ||||
let i = this.territoryMap.getNonObstructedTile(j, radius, obstructions); | let i = this.territoryMap.getNonObstructedTile(j, radius, obstructions); | ||||
if (i < 0) | if (i < 0) | ||||
continue; | continue; | ||||
let index = gameState.ai.accessibility.landPassMap[i]; | let index = gameState.ai.accessibility.landPassMap[i]; | ||||
if (!this.landRegions[index]) | if (!this.landRegions[index]) | ||||
continue; | continue; | ||||
▲ Show 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | PETRA.HQ.prototype.findMarketLocation = function(gameState, template) | ||||
{ | { | ||||
if (template.hasClass("Market") && | if (template.hasClass("Market") && | ||||
!gameState.getOwnEntitiesByClass("Market", true).hasEntities()) | !gameState.getOwnEntitiesByClass("Market", true).hasEntities()) | ||||
idx = -1; // Needed by queueplanBuilding manager to keep that Market. | idx = -1; // Needed by queueplanBuilding manager to keep that Market. | ||||
else | else | ||||
return false; | return false; | ||||
} | } | ||||
else | else | ||||
idx = this.basesMap.map[bestJdx]; | idx = this.basesManager.basesMap.map[bestJdx]; | ||||
let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize; | let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize; | ||||
let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize; | let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize; | ||||
return [x, z, idx, expectedGain]; | return [x, z, idx, expectedGain]; | ||||
}; | }; | ||||
/** | /** | ||||
* Returns the best position to build defensive buildings (fortress and towers) | * Returns the best position to build defensive buildings (fortress and towers) | ||||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | for (let j = 0; j < this.territoryMap.length; ++j) | ||||
if (!wonderMode) | if (!wonderMode) | ||||
{ | { | ||||
// do not try if well inside or outside territory | // do not try if well inside or outside territory | ||||
if (!(this.borderMap.map[j] & PETRA.fullFrontier_Mask)) | if (!(this.borderMap.map[j] & PETRA.fullFrontier_Mask)) | ||||
continue; | continue; | ||||
if (this.borderMap.map[j] & PETRA.largeFrontier_Mask && isTower) | if (this.borderMap.map[j] & PETRA.largeFrontier_Mask && isTower) | ||||
continue; | continue; | ||||
} | } | ||||
if (this.basesMap.map[j] == 0) // inaccessible cell | if (this.basesManager.basesMap.map[j] == 0) // inaccessible cell | ||||
continue; | continue; | ||||
// with enough room around to build the cc | // with enough room around to build the cc | ||||
let i = this.territoryMap.getNonObstructedTile(j, radius, obstructions); | let i = this.territoryMap.getNonObstructedTile(j, radius, obstructions); | ||||
if (i < 0) | if (i < 0) | ||||
continue; | continue; | ||||
let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)]; | let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)]; | ||||
// checking distances to other structures | // checking distances to other structures | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | for (let j = 0; j < this.territoryMap.length; ++j) | ||||
bestJdx = j; | bestJdx = j; | ||||
} | } | ||||
if (bestVal === undefined) | if (bestVal === undefined) | ||||
return undefined; | return undefined; | ||||
let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize; | let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize; | ||||
let z = (Math.floor(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.basesManager.basesMap.map[bestJdx]]; | ||||
}; | }; | ||||
PETRA.HQ.prototype.buildTemple = function(gameState, queues) | PETRA.HQ.prototype.buildTemple = function(gameState, queues) | ||||
{ | { | ||||
// at least one market (which have the same queue) should be build before any temple | // at least one market (which have the same queue) should be build before any temple | ||||
if (queues.economicBuilding.hasQueuedUnits() || | if (queues.economicBuilding.hasQueuedUnits() || | ||||
gameState.getOwnEntitiesByClass("Temple", true).hasEntities() || | gameState.getOwnEntitiesByClass("Temple", true).hasEntities() || | ||||
!gameState.getOwnEntitiesByClass("Market", true).hasEntities()) | !gameState.getOwnEntitiesByClass("Market", true).hasEntities()) | ||||
▲ Show 20 Lines • Show All 538 Lines • ▼ Show 20 Lines | PETRA.HQ.prototype.trainEmergencyUnits = function(gameState, positions) | ||||
// find nearest base anchor | // find nearest base anchor | ||||
let distcut = 20000; | let distcut = 20000; | ||||
let nearestAnchor; | let nearestAnchor; | ||||
let distmin; | let distmin; | ||||
for (let pos of positions) | for (let pos of positions) | ||||
{ | { | ||||
let access = gameState.ai.accessibility.getAccessValue(pos); | let access = gameState.ai.accessibility.getAccessValue(pos); | ||||
// check nearest base anchor | // check nearest base anchor | ||||
for (let base of this.baseManagers) | for (const base of this.baseManagers()) | ||||
{ | { | ||||
if (!base.anchor || !base.anchor.position()) | if (!base.anchor || !base.anchor.position()) | ||||
continue; | continue; | ||||
if (PETRA.getLandAccess(gameState, base.anchor) != access) | if (PETRA.getLandAccess(gameState, base.anchor) != access) | ||||
continue; | continue; | ||||
if (!base.anchor.trainableEntities(civ)) // base still in construction | if (!base.anchor.trainableEntities(civ)) // base still in construction | ||||
continue; | continue; | ||||
let queue = base.anchor._entity.trainingQueue; | let queue = base.anchor._entity.trainingQueue; | ||||
▲ Show 20 Lines • Show All 128 Lines • ▼ Show 20 Lines | PETRA.HQ.prototype.canBuild = function(gameState, structure) | ||||
{ | { | ||||
this.buildManager.setUnbuildable(gameState, type, 90, "limit"); | this.buildManager.setUnbuildable(gameState, type, 90, "limit"); | ||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
}; | }; | ||||
PETRA.HQ.prototype.updateTerritories = function(gameState) | PETRA.HQ.prototype.updateTerritories = function(gameState) | ||||
Not Done Inline ActionsOne could argue this does not belong here as well. But in basesManager perhaps? Freagarach: One could argue this does not belong here as well. But in `basesManager` perhaps? | |||||
{ | { | ||||
const around = [ [-0.7, 0.7], [0, 1], [0.7, 0.7], [1, 0], [0.7, -0.7], [0, -1], [-0.7, -0.7], [-1, 0] ]; | const around = [ [-0.7, 0.7], [0, 1], [0.7, 0.7], [1, 0], [0.7, -0.7], [0, -1], [-0.7, -0.7], [-1, 0] ]; | ||||
let alliedVictory = gameState.getAlliedVictory(); | let alliedVictory = gameState.getAlliedVictory(); | ||||
let passabilityMap = gameState.getPassabilityMap(); | let passabilityMap = gameState.getPassabilityMap(); | ||||
let width = this.territoryMap.width; | let width = this.territoryMap.width; | ||||
let cellSize = this.territoryMap.cellSize; | let cellSize = this.territoryMap.cellSize; | ||||
let insideSmall = Math.round(45 / cellSize); | let insideSmall = Math.round(45 / cellSize); | ||||
let insideLarge = Math.round(80 / cellSize); // should be about the range of towers | let insideLarge = Math.round(80 / cellSize); // should be about the range of towers | ||||
let expansion = 0; | let expansion = 0; | ||||
for (let j = 0; j < this.territoryMap.length; ++j) | for (let j = 0; j < this.territoryMap.length; ++j) | ||||
{ | { | ||||
if (this.borderMap.map[j] & PETRA.outside_Mask) | if (this.borderMap.map[j] & PETRA.outside_Mask) | ||||
continue; | continue; | ||||
if (this.borderMap.map[j] & PETRA.fullFrontier_Mask) | if (this.borderMap.map[j] & PETRA.fullFrontier_Mask) | ||||
this.borderMap.map[j] &= ~PETRA.fullFrontier_Mask; // reset the frontier | this.borderMap.map[j] &= ~PETRA.fullFrontier_Mask; // reset the frontier | ||||
if (this.territoryMap.getOwnerIndex(j) != PlayerID) | if (this.territoryMap.getOwnerIndex(j) != PlayerID) | ||||
{ | this.basesManager.removeBaseFromTerritoryIndex(j); | ||||
// 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; | |||||
} | |||||
else | else | ||||
{ | { | ||||
// Update the frontier | // Update the frontier | ||||
let ix = j%width; | let ix = j%width; | ||||
let iz = Math.floor(j/width); | let iz = Math.floor(j/width); | ||||
let onFrontier = false; | let onFrontier = false; | ||||
for (let a of around) | for (let a of around) | ||||
{ | { | ||||
Show All 21 Lines | else | ||||
continue; | continue; | ||||
territoryOwner = this.territoryMap.getOwnerIndex(jx+width*jz); | territoryOwner = this.territoryMap.getOwnerIndex(jx+width*jz); | ||||
if (territoryOwner != PlayerID && !(alliedVictory && gameState.isPlayerAlly(territoryOwner))) | if (territoryOwner != PlayerID && !(alliedVictory && gameState.isPlayerAlly(territoryOwner))) | ||||
onFrontier = true; | onFrontier = true; | ||||
} | } | ||||
if (onFrontier && !(this.borderMap.map[j] & PETRA.narrowFrontier_Mask)) | if (onFrontier && !(this.borderMap.map[j] & PETRA.narrowFrontier_Mask)) | ||||
this.borderMap.map[j] |= PETRA.largeFrontier_Mask; | this.borderMap.map[j] |= PETRA.largeFrontier_Mask; | ||||
// If this tile was not already accounted, add it. | if (this.basesManager.addTerritoryIndexToBase(gameState, j, passabilityMap)) | ||||
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++; | expansion++; | ||||
} | } | ||||
} | } | ||||
if (!expansion) | if (!expansion) | ||||
return; | return; | ||||
// We've increased our territory, so we may have some new room to build | // We've increased our territory, so we may have some new room to build | ||||
this.buildManager.resetMissingRoom(gameState); | this.buildManager.resetMissingRoom(gameState); | ||||
// And if sufficient expansion, check if building a new market would improve our present trade routes | // And if sufficient expansion, check if building a new market would improve our present trade routes | ||||
let cellArea = this.territoryMap.cellSize * this.territoryMap.cellSize; | let cellArea = this.territoryMap.cellSize * this.territoryMap.cellSize; | ||||
if (expansion * cellArea > 960) | if (expansion * cellArea > 960) | ||||
this.tradeManager.routeProspection = true; | this.tradeManager.routeProspection = true; | ||||
}; | }; | ||||
/** Reassign territories when a base is going to be deleted */ | /** Reassign territories when a base is going to be deleted */ | ||||
PETRA.HQ.prototype.reassignTerritories = function(deletedBase) | PETRA.HQ.prototype.reassignTerritories = function(deletedBase) | ||||
{ | { | ||||
let cellSize = this.territoryMap.cellSize; | let cellSize = this.territoryMap.cellSize; | ||||
let width = this.territoryMap.width; | let width = this.territoryMap.width; | ||||
for (let j = 0; j < this.territoryMap.length; ++j) | for (let j = 0; j < this.territoryMap.length; ++j) | ||||
{ | { | ||||
if (this.basesMap.map[j] != deletedBase.ID) | if (this.basesManager.basesMap.map[j] != deletedBase.ID) | ||||
continue; | continue; | ||||
if (this.territoryMap.getOwnerIndex(j) != PlayerID) | if (this.territoryMap.getOwnerIndex(j) != PlayerID) | ||||
{ | { | ||||
API3.warn("Petra reassignTerritories: should never happen"); | API3.warn("Petra reassignTerritories: should never happen"); | ||||
this.basesMap.map[j] = 0; | this.basesManager.basesMap.map[j] = 0; | ||||
continue; | continue; | ||||
} | } | ||||
let distmin = Math.min(); | let distmin = Math.min(); | ||||
let baseID; | let baseID; | ||||
let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)]; | let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)]; | ||||
for (let base of this.baseManagers) | for (const base of this.baseManagers()) | ||||
{ | { | ||||
if (!base.anchor || !base.anchor.position()) | if (!base.anchor || !base.anchor.position()) | ||||
continue; | continue; | ||||
if (base.accessIndex != deletedBase.accessIndex) | if (base.accessIndex != deletedBase.accessIndex) | ||||
continue; | continue; | ||||
let dist = API3.SquareVectorDistance(base.anchor.position(), pos); | let dist = API3.SquareVectorDistance(base.anchor.position(), pos); | ||||
if (dist >= distmin) | if (dist >= distmin) | ||||
continue; | continue; | ||||
distmin = dist; | distmin = dist; | ||||
baseID = base.ID; | baseID = base.ID; | ||||
} | } | ||||
if (baseID) | if (baseID) | ||||
{ | { | ||||
this.getBaseByID(baseID).territoryIndices.push(j); | this.getBaseByID(baseID).territoryIndices.push(j); | ||||
this.basesMap.map[j] = baseID; | this.basesManager.basesMap.map[j] = baseID; | ||||
} | } | ||||
else | else | ||||
this.basesMap.map[j] = 0; | this.basesManager.basesMap.map[j] = 0; | ||||
} | } | ||||
}; | }; | ||||
/** | /** | ||||
* returns the base corresponding to baseID | * returns the base corresponding to baseID | ||||
*/ | */ | ||||
PETRA.HQ.prototype.getBaseByID = function(baseID) | PETRA.HQ.prototype.getBaseByID = function(baseID) | ||||
{ | { | ||||
for (let base of this.baseManagers) | return this.basesManager.getBaseByID(baseID); | ||||
if (base.ID == baseID) | |||||
return base; | |||||
return undefined; | |||||
}; | }; | ||||
/** | /** | ||||
* returns the number of bases with a cc | * returns the number of bases with a cc | ||||
* ActiveBases includes only those with a built cc | * ActiveBases includes only those with a built cc | ||||
* PotentialBases includes also those with a cc in construction | * PotentialBases includes also those with a cc in construction | ||||
*/ | */ | ||||
PETRA.HQ.prototype.numActiveBases = function() | PETRA.HQ.prototype.numActiveBases = function() | ||||
{ | { | ||||
if (!this.turnCache.base) | return this.basesManager.numActiveBases(); | ||||
this.updateBaseCache(); | |||||
return this.turnCache.base.active; | |||||
}; | }; | ||||
PETRA.HQ.prototype.numPotentialBases = function() | PETRA.HQ.prototype.numPotentialBases = function() | ||||
{ | { | ||||
if (!this.turnCache.base) | return this.basesManager.numPotentialBases(); | ||||
this.updateBaseCache(); | |||||
return this.turnCache.base.potential; | |||||
}; | |||||
PETRA.HQ.prototype.updateBaseCache = 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); | |||||
} | |||||
} | |||||
}; | }; | ||||
PETRA.HQ.prototype.isDangerousLocation = function(gameState, pos, radius) | PETRA.HQ.prototype.isDangerousLocation = function(gameState, pos, radius) | ||||
{ | { | ||||
return this.isNearInvadingArmy(pos) || this.isUnderEnemyFire(gameState, pos, radius); | return this.isNearInvadingArmy(pos) || this.isUnderEnemyFire(gameState, pos, radius); | ||||
}; | }; | ||||
/** Check that the chosen position is not too near from an invading army */ | /** Check that the chosen position is not too near from an invading army */ | ||||
▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | for (let entId of capturableTarget.ents) | ||||
if ((orderData[0].attackType == "Capture") !== allowCapture) | if ((orderData[0].attackType == "Capture") !== allowCapture) | ||||
ent.attack(targetId, allowCapture); | ent.attack(targetId, allowCapture); | ||||
} | } | ||||
} | } | ||||
this.capturableTargetsTime = gameState.ai.elapsedTime; | 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) | * Check if a structure in blinking territory should/can be defended (currently if it has some attacking armies around) | ||||
*/ | */ | ||||
PETRA.HQ.prototype.isDefendable = function(ent) | PETRA.HQ.prototype.isDefendable = function(ent) | ||||
{ | { | ||||
if (!this.turnCache.numAround) | if (!this.turnCache.numAround) | ||||
this.turnCache.numAround = {}; | this.turnCache.numAround = {}; | ||||
if (this.turnCache.numAround[ent.id()] === undefined) | if (this.turnCache.numAround[ent.id()] === undefined) | ||||
▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | for (let ent of gameState.getOwnTrainingFacilities().values()) | ||||
workers += item.count; | workers += item.count; | ||||
} | } | ||||
} | } | ||||
this.turnCache.accountedWorkers = workers; | this.turnCache.accountedWorkers = workers; | ||||
} | } | ||||
return this.turnCache.accountedWorkers; | 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} | |||||
*/ | |||||
PETRA.HQ.prototype.baseAtIndex = function(territoryIndex) | |||||
{ | |||||
return this.basesManager.baseAtIndex(territoryIndex); | |||||
}; | |||||
/** | /** | ||||
* Some functions are run every turn | * Some functions are run every turn | ||||
* Others once in a while | * Others once in a while | ||||
*/ | */ | ||||
PETRA.HQ.prototype.update = function(gameState, queues, events) | PETRA.HQ.prototype.update = function(gameState, queues, events) | ||||
{ | { | ||||
Engine.ProfileStart("Headquarters update"); | Engine.ProfileStart("Headquarters update"); | ||||
this.turnCache = {}; | this.turnCache = {}; | ||||
▲ Show 20 Lines • Show All 78 Lines • ▼ Show 20 Lines | PETRA.HQ.prototype.update = function(gameState, queues, events) | ||||
if (gameState.ai.playedTurn % 3 == 0) | if (gameState.ai.playedTurn % 3 == 0) | ||||
{ | { | ||||
this.constructTrainingBuildings(gameState, queues); | this.constructTrainingBuildings(gameState, queues); | ||||
if (this.Config.difficulty > 0) | if (this.Config.difficulty > 0) | ||||
this.buildDefenses(gameState, queues); | this.buildDefenses(gameState, queues); | ||||
} | } | ||||
this.assignGatherers(); | this.basesManager.update(gameState, queues, events); | ||||
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.navalManager.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.numActiveBases() > 0 || !this.canBuildUnits)) | ||||
this.attackManager.update(gameState, queues, events); | this.attackManager.update(gameState, queues, events); | ||||
this.diplomacyManager.update(gameState, events); | this.diplomacyManager.update(gameState, events); | ||||
this.victoryManager.update(gameState, events, queues); | this.victoryManager.update(gameState, events, queues); | ||||
// We update the capture strength at the end as it can change attack orders | // We update the capture strength at the end as it can change attack orders | ||||
if (gameState.ai.elapsedTime - this.capturableTargetsTime > 3) | if (gameState.ai.elapsedTime - this.capturableTargetsTime > 3) | ||||
this.updateCaptureStrength(gameState); | this.updateCaptureStrength(gameState); | ||||
Engine.ProfileStop(); | Engine.ProfileStop(); | ||||
}; | }; | ||||
PETRA.HQ.prototype.Serialize = function() | PETRA.HQ.prototype.Serialize = function() | ||||
{ | { | ||||
let properties = { | let properties = { | ||||
"phasing": this.phasing, | "phasing": this.phasing, | ||||
"currentBase": this.currentBase, | |||||
"lastFailedGather": this.lastFailedGather, | "lastFailedGather": this.lastFailedGather, | ||||
"firstBaseConfig": this.firstBaseConfig, | "firstBaseConfig": this.firstBaseConfig, | ||||
"supportRatio": this.supportRatio, | "supportRatio": this.supportRatio, | ||||
"targetNumWorkers": this.targetNumWorkers, | "targetNumWorkers": this.targetNumWorkers, | ||||
"fortStartTime": this.fortStartTime, | "fortStartTime": this.fortStartTime, | ||||
"towerStartTime": this.towerStartTime, | "towerStartTime": this.towerStartTime, | ||||
"fortressStartTime": this.fortressStartTime, | "fortressStartTime": this.fortressStartTime, | ||||
"bAdvanced": this.bAdvanced, | "bAdvanced": this.bAdvanced, | ||||
"saveResources": this.saveResources, | "saveResources": this.saveResources, | ||||
"saveSpace": this.saveSpace, | "saveSpace": this.saveSpace, | ||||
"needCorral": this.needCorral, | "needCorral": this.needCorral, | ||||
"needFarm": this.needFarm, | "needFarm": this.needFarm, | ||||
"needFish": this.needFish, | "needFish": this.needFish, | ||||
"maxFields": this.maxFields, | "maxFields": this.maxFields, | ||||
"canExpand": this.canExpand, | "canExpand": this.canExpand, | ||||
"canBuildUnits": this.canBuildUnits, | "canBuildUnits": this.canBuildUnits, | ||||
"navalMap": this.navalMap, | "navalMap": this.navalMap, | ||||
"landRegions": this.landRegions, | "landRegions": this.landRegions, | ||||
"navalRegions": this.navalRegions, | "navalRegions": this.navalRegions, | ||||
"decayingStructures": this.decayingStructures, | "decayingStructures": this.decayingStructures, | ||||
"capturableTargets": this.capturableTargets, | "capturableTargets": this.capturableTargets, | ||||
"capturableTargetsTime": this.capturableTargetsTime | "capturableTargetsTime": this.capturableTargetsTime | ||||
}; | }; | ||||
let baseManagers = []; | |||||
for (let base of this.baseManagers) | |||||
baseManagers.push(base.Serialize()); | |||||
if (this.Config.debug == -100) | if (this.Config.debug == -100) | ||||
{ | { | ||||
API3.warn(" HQ serialization ---------------------"); | API3.warn(" HQ serialization ---------------------"); | ||||
API3.warn(" properties " + uneval(properties)); | 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(" attackManager " + uneval(this.attackManager.Serialize())); | ||||
API3.warn(" buildManager " + uneval(this.buildManager.Serialize())); | API3.warn(" buildManager " + uneval(this.buildManager.Serialize())); | ||||
API3.warn(" defenseManager " + uneval(this.defenseManager.Serialize())); | API3.warn(" defenseManager " + uneval(this.defenseManager.Serialize())); | ||||
API3.warn(" tradeManager " + uneval(this.tradeManager.Serialize())); | API3.warn(" tradeManager " + uneval(this.tradeManager.Serialize())); | ||||
API3.warn(" navalManager " + uneval(this.navalManager.Serialize())); | API3.warn(" navalManager " + uneval(this.navalManager.Serialize())); | ||||
API3.warn(" researchManager " + uneval(this.researchManager.Serialize())); | API3.warn(" researchManager " + uneval(this.researchManager.Serialize())); | ||||
API3.warn(" diplomacyManager " + uneval(this.diplomacyManager.Serialize())); | API3.warn(" diplomacyManager " + uneval(this.diplomacyManager.Serialize())); | ||||
API3.warn(" garrisonManager " + uneval(this.garrisonManager.Serialize())); | API3.warn(" garrisonManager " + uneval(this.garrisonManager.Serialize())); | ||||
API3.warn(" victoryManager " + uneval(this.victoryManager.Serialize())); | API3.warn(" victoryManager " + uneval(this.victoryManager.Serialize())); | ||||
} | } | ||||
return { | return { | ||||
"properties": properties, | "properties": properties, | ||||
"baseManagers": baseManagers, | "basesManager": this.basesManager.Serialize(), | ||||
"attackManager": this.attackManager.Serialize(), | "attackManager": this.attackManager.Serialize(), | ||||
"buildManager": this.buildManager.Serialize(), | "buildManager": this.buildManager.Serialize(), | ||||
"defenseManager": this.defenseManager.Serialize(), | "defenseManager": this.defenseManager.Serialize(), | ||||
"tradeManager": this.tradeManager.Serialize(), | "tradeManager": this.tradeManager.Serialize(), | ||||
"navalManager": this.navalManager.Serialize(), | "navalManager": this.navalManager.Serialize(), | ||||
"researchManager": this.researchManager.Serialize(), | "researchManager": this.researchManager.Serialize(), | ||||
"diplomacyManager": this.diplomacyManager.Serialize(), | "diplomacyManager": this.diplomacyManager.Serialize(), | ||||
"garrisonManager": this.garrisonManager.Serialize(), | "garrisonManager": this.garrisonManager.Serialize(), | ||||
"victoryManager": this.victoryManager.Serialize(), | "victoryManager": this.victoryManager.Serialize(), | ||||
}; | }; | ||||
}; | }; | ||||
PETRA.HQ.prototype.Deserialize = function(gameState, data) | PETRA.HQ.prototype.Deserialize = function(gameState, data) | ||||
{ | { | ||||
for (let key in data.properties) | for (let key in data.properties) | ||||
this[key] = data.properties[key]; | this[key] = data.properties[key]; | ||||
this.baseManagers = []; | |||||
for (let base of data.baseManagers) | this.basesManager = new PETRA.BasesManager(this.Config); | ||||
{ | this.basesManager.init(gameState); | ||||
// the first call to deserialize set the ID base needed by entitycollections | this.basesManager.Deserialize(gameState, data.basesManager); | ||||
let newbase = new PETRA.BaseManager(gameState, this.Config); | |||||
newbase.Deserialize(gameState, base); | |||||
Done Inline ActionsNotice base was wrong here. Freagarach: Notice `base` was wrong here. | |||||
Done Inline Actionsrly? why? Silier: rly? why? | |||||
newbase.init(gameState); | |||||
newbase.Deserialize(gameState, base); | |||||
this.baseManagers.push(newbase); | |||||
} | |||||
this.navalManager = new PETRA.NavalManager(this.Config); | this.navalManager = new PETRA.NavalManager(this.Config); | ||||
this.navalManager.init(gameState, true); | this.navalManager.init(gameState, true); | ||||
this.navalManager.Deserialize(gameState, data.navalManager); | this.navalManager.Deserialize(gameState, data.navalManager); | ||||
this.attackManager = new PETRA.AttackManager(this.Config); | this.attackManager = new PETRA.AttackManager(this.Config); | ||||
this.attackManager.Deserialize(gameState, data.attackManager); | this.attackManager.Deserialize(gameState, data.attackManager); | ||||
this.attackManager.init(gameState); | this.attackManager.init(gameState); | ||||
Show All 24 Lines |
Wildfire Games · Phabricator
what if entitiy is from playerId and has no base?