Index: binaries/data/mods/public/simulation/ai/petra/attackManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/attackManager.js +++ binaries/data/mods/public/simulation/ai/petra/attackManager.js @@ -9,8 +9,18 @@ this.attackNumber = 0; this.rushNumber = 0; this.raidNumber = 0; - this.upcomingAttacks = { "Rush": [], "Raid": [], "Attack": [], "HugeAttack": [] }; - this.startedAttacks = { "Rush": [], "Raid": [], "Attack": [], "HugeAttack": [] }; + this.upcomingAttacks = { + [PETRA.AttackPlan.TYPE_RUSH]: [], + [PETRA.AttackPlan.TYPE_RAID]: [], + [PETRA.AttackPlan.TYPE_DEFAULT]: [], + [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: [] + }; + this.startedAttacks = { + [PETRA.AttackPlan.TYPE_RUSH]: [], + [PETRA.AttackPlan.TYPE_RAID]: [], + [PETRA.AttackPlan.TYPE_DEFAULT]: [], + [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: [] + }; this.bombingAttacks = new Map();// Temporary attacks for siege units while waiting their current attack to start this.debugTime = 0; this.maxRushes = 0; @@ -63,7 +73,7 @@ { for (let attack of this.upcomingAttacks[attackType]) { - if (attack.state === "completing") + if (attack.state === PETRA.AttackPlan.STATE_COMPLETING) { if (attack.targetPlayer === targetPlayer) available += attack.unitCollection.length; @@ -85,7 +95,7 @@ { for (let attack of this.upcomingAttacks[attackType]) { - if (attack.state === "completing" || + if (attack.state === PETRA.AttackPlan.STATE_COMPLETING || attack.targetPlayer !== targetPlayer || attack.unitCollection.length < 3) continue; @@ -252,8 +262,12 @@ } this.checkEvents(gameState, events); - - let unexecutedAttacks = { "Rush": 0, "Raid": 0, "Attack": 0, "HugeAttack": 0 }; + const unexecutedAttacks = { + [PETRA.AttackPlan.TYPE_RUSH]: 0, + [PETRA.AttackPlan.TYPE_RAID]: 0, + [PETRA.AttackPlan.TYPE_DEFAULT]: 0, + [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: 0 + }; for (let attackType in this.upcomingAttacks) { for (let i = 0; i < this.upcomingAttacks[attackType].length; ++i) @@ -266,20 +280,20 @@ let updateStep = attack.updatePreparation(gameState); // now we're gonna check if the preparation time is over - if (updateStep == 1 || attack.isPaused()) + if (updateStep == PETRA.AttackPlan.PREPARATION_KEEP_GOING || attack.isPaused()) { // just chillin' - if (attack.state == "unexecuted") + if (attack.state == PETRA.AttackPlan.STATE_UNEXECUTED) ++unexecutedAttacks[attackType]; } - else if (updateStep == 0) + else if (updateStep == PETRA.AttackPlan.PREPARATION_FAILED) { if (this.Config.debug > 1) API3.warn("Attack Manager: " + attack.getType() + " plan " + attack.getName() + " aborted."); attack.Abort(gameState); this.upcomingAttacks[attackType].splice(i--, 1); } - else if (updateStep == 2) + else if (updateStep == PETRA.AttackPlan.PREPARATION_START) { if (attack.StartAttack(gameState)) { @@ -321,30 +335,32 @@ let barracksNb = gameState.getOwnEntitiesByClass("Barracks", true).filter(API3.Filters.isBuilt()).length; if (this.rushNumber < this.maxRushes && barracksNb >= 1) { - if (unexecutedAttacks.Rush === 0) + if (unexecutedAttacks[PETRA.AttackPlan.TYPE_RUSH] === 0) { // we have a barracks and we want to rush, rush. let data = { "targetSize": this.rushSize[this.rushNumber] }; - let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, "Rush", data); + const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, PETRA.AttackPlan.TYPE_RUSH, data); if (!attackPlan.failed) { if (this.Config.debug > 1) API3.warn("Military Manager: Rushing plan " + this.totalNumber + " with maxRushes " + this.maxRushes); this.totalNumber++; attackPlan.init(gameState); - this.upcomingAttacks.Rush.push(attackPlan); + this.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].push(attackPlan); } this.rushNumber++; } } - else if (unexecutedAttacks.Attack == 0 && unexecutedAttacks.HugeAttack == 0 && - this.startedAttacks.Attack.length + this.startedAttacks.HugeAttack.length < Math.min(2, 1 + Math.round(gameState.getPopulationMax()/100)) && - (this.startedAttacks.Attack.length + this.startedAttacks.HugeAttack.length == 0 || gameState.getPopulationMax() - gameState.getPopulation() > 12)) + else if (unexecutedAttacks[PETRA.AttackPlan.TYPE_DEFAULT] == 0 && unexecutedAttacks[PETRA.AttackManager.TYPE_HUGE_ATTACK] == 0 && + this.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + this.startedAttacks[PETRA.AttackManager.TYPE_HUGE_ATTACK].length < + Math.min(2, 1 + Math.round(gameState.getPopulationMax()/100)) && + (this.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + this.startedAttacks[PETRA.AttackManager.TYPE_HUGE_ATTACK].length == 0 || + gameState.getPopulationMax() - gameState.getPopulation() > 12)) { if (barracksNb >= 1 && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.getPhaseName(2))) || !gameState.ai.HQ.hasPotentialBase()) // if we have no base ... nothing else to do than attack { - let type = this.attackNumber < 2 || this.startedAttacks.HugeAttack.length > 0 ? "Attack" : "HugeAttack"; + const type = this.attackNumber < 2 || this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length > 0 ? PETRA.AttackPlan.TYPE_DEFAULT : PETRA.AttackPlan.TYPE_HUGE_ATTACK; let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, type); if (attackPlan.failed) this.attackPlansEncounteredWater = true; // hack @@ -360,7 +376,7 @@ } } - if (unexecutedAttacks.Raid === 0 && gameState.ai.HQ.defenseManager.targetList.length) + if (unexecutedAttacks[PETRA.AttackPlan.TYPE_RAID] === 0 && gameState.ai.HQ.defenseManager.targetList.length) { let target; for (let targetId of gameState.ai.HQ.defenseManager.targetList) @@ -465,7 +481,7 @@ for (let i in this.defeated) veto[i] = true; // No rush if enemy too well defended (i.e. iberians) - if (attack.type == "Rush") + if (attack.type == PETRA.AttackPlan.TYPE_RUSH) { for (let i = 1; i < gameState.sharedScript.playersData.length; ++i) { @@ -484,7 +500,7 @@ // then if not a huge attack, continue attacking our previous target as long as it has some entities, // otherwise target the most accessible one - if (attack.type != "HugeAttack") + if (attack.type != PETRA.AttackPlan.TYPE_HUGE_ATTACK) { if (attack.targetPlayer === undefined && this.currentEnemyPlayer !== undefined && !this.defeated[this.currentEnemyPlayer] && @@ -640,7 +656,7 @@ PETRA.AttackManager.prototype.raidTargetEntity = function(gameState, ent) { let data = { "target": ent }; - let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, "Raid", data); + const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, PETRA.AttackPlan.TYPE_RAID, data); if (attackPlan.failed) return null; if (this.Config.debug > 1) @@ -648,7 +664,7 @@ this.raidNumber++; this.totalNumber++; attackPlan.init(gameState); - this.upcomingAttacks.Raid.push(attackPlan); + this.upcomingAttacks[PETRA.AttackPlan.TYPE_RAID].push(attackPlan); return attackPlan; }; @@ -686,7 +702,7 @@ } let attackData = data.uniqueTarget ? { "uniqueTargetId": target.id() } : undefined; let pos = target.position(); - let attackType = "Attack"; + const attackType = PETRA.AttackPlan.TYPE_DEFAULT; let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, attackType, attackData); if (attackPlan.failed) return false; @@ -733,7 +749,7 @@ attackPlan.targetPlayer = target.owner(); attackPlan.targetPos = pos; attackPlan.target = target; - attackPlan.state = "arrived"; + attackPlan.state = PETRA.AttackPlan.STATE_ARRIVED; return true; }; Index: binaries/data/mods/public/simulation/ai/petra/attackPlan.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/attackPlan.js +++ binaries/data/mods/public/simulation/ai/petra/attackPlan.js @@ -4,13 +4,12 @@ * To making sure units are built, and pushing elements to the queue manager otherwise * It also handles the actual attack, though much work is needed on that. */ - PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data) { this.Config = Config; this.name = uniqueID; - this.type = type || "Attack"; - this.state = "unexecuted"; + this.type = type || PETRA.AttackPlan.TYPE_DEFAULT; + this.state = PETRA.AttackPlan.STATE_UNEXECUTED; this.forced = false; // true when this attacked has been forced to help an ally if (data && data.target) @@ -114,7 +113,7 @@ this.unitStat = {}; // neededShips is the minimal number of ships which should be available for transport - if (type == "Rush") + if (type == PETRA.AttackPlan.TYPE_RUSH) { priority = 250; this.unitStat.Infantry = { "priority": 1, "minSize": 10, "targetSize": 20, "batchSize": 2, "classes": ["Infantry"], @@ -125,14 +124,14 @@ this.unitStat.Infantry.targetSize = data.targetSize; this.neededShips = 1; } - else if (type == "Raid") + else if (type == PETRA.AttackPlan.TYPE_RAID) { priority = 150; this.unitStat.FastMoving = { "priority": 1, "minSize": 3, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving+CitizenSoldier"], "interests": [ ["strength", 1] ] }; this.neededShips = 1; } - else if (type == "HugeAttack") + else if (type == PETRA.AttackPlan.TYPE_HUGE_ATTACK) { priority = 90; // basically we want a mix of citizen soldiers so our barracks have a purpose, and champion units. @@ -216,7 +215,7 @@ // each array is [ratio, [associated classes], associated EntityColl, associated unitStat, name ] this.buildOrders = []; this.canBuildUnits = gameState.ai.HQ.canBuildUnits; - this.siegeState = 0; // 0 = not yet tested, 1 = not yet any siege trainer, 2 = siege added in build orders + this.siegeState = PETRA.AttackPlan.SIEGE_NOT_TESTED; // some variables used during the attack this.position5TurnsAgo = [0, 0]; @@ -227,6 +226,32 @@ return true; }; +PETRA.AttackPlan.PREPARATION_FAILED = 0; +PETRA.AttackPlan.PREPARATION_KEEP_GOING = 1; +PETRA.AttackPlan.PREPARATION_START = 2; + +/** + * Not yet tested + */ +PETRA.AttackPlan.SIEGE_NOT_TESTED = 0; +/** + * Not yet any siege trainer + */ +PETRA.AttackPlan.SIEGE_NO_TRAINER = 1; // Doesn't seem to be used. +/** + * Siege added in build orders + */ +PETRA.AttackPlan.SIEGE_ADDED = 2; + +PETRA.AttackPlan.STATE_UNEXECUTED = "unexecuted"; +PETRA.AttackPlan.STATE_COMPLETING = "completing"; +PETRA.AttackPlan.STATE_ARRIVED = "arrived"; + +PETRA.AttackPlan.TYPE_DEFAULT = "Attack"; +PETRA.AttackPlan.TYPE_HUGE_ATTACK = "HugeAttack"; +PETRA.AttackPlan.TYPE_RAID = "Raid"; +PETRA.AttackPlan.TYPE_RUSH = "Rush"; + PETRA.AttackPlan.prototype.init = function(gameState) { this.queue = gameState.ai.queues["plan_" + this.name]; @@ -262,7 +287,7 @@ PETRA.AttackPlan.prototype.isStarted = function() { - return this.state !== "unexecuted" && this.state !== "completing"; + return this.state !== PETRA.AttackPlan.STATE_UNEXECUTED && this.state !== PETRA.AttackPlan.STATE_COMPLETING; }; PETRA.AttackPlan.prototype.isPaused = function() @@ -316,7 +341,7 @@ if (MaxReachedEverywhere) return true; if (MinReachedEverywhere) - return this.type == "Raid" && this.target && this.target.foundationProgress() && + return this.type == PETRA.AttackPlan.TYPE_RAID && this.target && this.target.foundationProgress() && this.target.foundationProgress() > 50; return false; }; @@ -364,7 +389,7 @@ PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState) { - if (this.siegeState == 2 || this.state !== "unexecuted") + if (this.siegeState == PETRA.AttackPlan.SIEGE_ADDED || this.state !== PETRA.AttackPlan.STATE_UNEXECUTED) return false; let civ = gameState.getPlayerCiv(); @@ -397,13 +422,13 @@ i = ++i % classes.length; } - this.siegeState = 2; + this.siegeState = PETRA.AttackPlan.SIEGE_ADDED; let targetSize; if (this.Config.difficulty < 3) - targetSize = this.type == "HugeAttack" ? Math.max(this.Config.difficulty, 1) : Math.max(this.Config.difficulty - 1, 0); + targetSize = this.type == PETRA.AttackPlan.TYPE_HUGE_ATTACK ? Math.max(this.Config.difficulty, 1) : Math.max(this.Config.difficulty - 1, 0); else - targetSize = this.type == "HugeAttack" ? this.Config.difficulty + 1 : this.Config.difficulty - 1; - targetSize = Math.max(Math.round(this.Config.popScaling * targetSize), this.type == "HugeAttack" ? 1 : 0); + targetSize = this.type == PETRA.AttackPlan.TYPE_HUGE_ATTACK ? this.Config.difficulty + 1 : this.Config.difficulty - 1; + targetSize = Math.max(Math.round(this.Config.popScaling * targetSize), this.type == PETRA.AttackPlan.TYPE_HUGE_ATTACK ? 1 : 0); if (!targetSize) return true; // no minsize as we don't want the plan to fail at the last minute though. @@ -418,23 +443,23 @@ { // the completing step is used to return resources and regroup the units // so we check that we have no more forced order before starting the attack - if (this.state == "completing") + if (this.state == PETRA.AttackPlan.STATE_COMPLETING) { // if our target was destroyed, go back to "unexecuted" state if (this.targetPlayer === undefined || !this.target || !gameState.getEntityById(this.target.id())) { - this.state = "unexecuted"; + this.state = PETRA.AttackPlan.STATE_UNEXECUTED; this.target = undefined; } else { // check that all units have finished with their transport if needed if (this.waitingForTransport()) - return 1; + return PETRA.AttackPlan.PREPARATION_KEEP_GOING; // bloqued units which cannot finish their order should not stop the attack if (gameState.ai.elapsedTime < this.maxCompletingTime && this.hasForceOrder()) - return 1; - return 2; + return PETRA.AttackPlan.PREPARATION_KEEP_GOING; + return PETRA.AttackPlan.PREPARATION_START; } } @@ -443,11 +468,11 @@ // if we need a transport, wait for some transport ships if (this.overseas && !gameState.ai.HQ.navalManager.seaTransportShips[this.overseas].length) - return 1; + return PETRA.AttackPlan.PREPARATION_KEEP_GOING; - if (this.type != "Raid" || !this.forced) // Forced Raids have special purposes (as relic capture) + if (this.type != PETRA.AttackPlan.TYPE_RAID || !this.forced) // Forced Raids have special purposes (as relic capture) this.assignUnits(gameState); - if (this.type != "Raid" && gameState.ai.HQ.attackManager.getAttackInPreparation("Raid") !== undefined) + if (this.type != PETRA.AttackPlan.TYPE_RAID && gameState.ai.HQ.attackManager.getAttackInPreparation(PETRA.AttackPlan.TYPE_RAID) !== undefined) this.reassignFastUnit(gameState); // reassign some fast units (if any) to fasten raid preparations // Fasten the end game. @@ -481,16 +506,16 @@ if (this.Config.debug > 1) { let am = gameState.ai.HQ.attackManager; - API3.warn(" attacks upcoming: raid " + am.upcomingAttacks.Raid.length + - " rush " + am.upcomingAttacks.Rush.length + - " attack " + am.upcomingAttacks.Attack.length + - " huge " + am.upcomingAttacks.HugeAttack.length); - API3.warn(" attacks started: raid " + am.startedAttacks.Raid.length + - " rush " + am.startedAttacks.Rush.length + - " attack " + am.startedAttacks.Attack.length + - " huge " + am.startedAttacks.HugeAttack.length); + API3.warn(" attacks upcoming: raid " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_RAID].length + + " rush " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].length + + " attack " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + + " huge " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length); + API3.warn(" attacks started: raid " + am.startedAttacks[PETRA.AttackPlan.TYPE_RAID].length + + " rush " + am.startedAttacks[PETRA.AttackPlan.TYPE_RUSH].length + + " attack " + am.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + + " huge " + am.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length); } - return 0; + return PETRA.AttackPlan.PREPARATION_FAILED; } } else if (this.mustStart()) @@ -499,7 +524,7 @@ { // keep on while the units finish being trained, then we'll start this.emptyQueues(); - return 1; + return PETRA.AttackPlan.PREPARATION_KEEP_GOING; } } else @@ -507,30 +532,31 @@ if (this.canBuildUnits) { // We still have time left to recruit units and do stuffs. - if (this.siegeState == 0 || this.siegeState == 1 && gameState.ai.playedTurn % 5 == 0) + if (this.siegeState == PETRA.AttackPlan.SIEGE_NOT_TESTED || + this.siegeState == PETRA.AttackPlan.SIEGE_NO_TRAINER && gameState.ai.playedTurn % 5 == 0) this.addSiegeUnits(gameState); this.trainMoreUnits(gameState); // may happen if we have no more training facilities and build orders are canceled if (!this.buildOrders.length) - return 0; // will abort the plan + return PETRA.AttackPlan.PREPARATION_FAILED; // will abort the plan } - return 1; + return PETRA.AttackPlan.PREPARATION_KEEP_GOING; } // if we're here, it means we must start - this.state = "completing"; + this.state = PETRA.AttackPlan.STATE_COMPLETING; // Raids have their predefined target if (!this.target && !this.chooseTarget(gameState)) - return 0; + return PETRA.AttackPlan.PREPARATION_FAILED; if (!this.overseas) this.getPathToTarget(gameState); - if (this.type == "Raid") + if (this.type == PETRA.AttackPlan.TYPE_RAID) this.maxCompletingTime = this.forced ? 0 : gameState.ai.elapsedTime + 20; else { - if (this.type == "Rush" || this.forced) + if (this.type == PETRA.AttackPlan.TYPE_RUSH || this.forced) this.maxCompletingTime = gameState.ai.elapsedTime + 40; else this.maxCompletingTime = gameState.ai.elapsedTime + 60; @@ -576,7 +602,7 @@ // reset all queued units this.removeQueues(gameState); - return 1; + return PETRA.AttackPlan.PREPARATION_KEEP_GOING; }; PETRA.AttackPlan.prototype.trainMoreUnits = function(gameState) @@ -681,7 +707,7 @@ return added; } - if (this.type == "Raid") + if (this.type == PETRA.AttackPlan.TYPE_RAID) { // Raids are quick attacks: assign all FastMoving soldiers except some for hunting. let num = 0; @@ -727,7 +753,7 @@ let num = 0; const numbase = {}; - let keep = this.type != "Rush" ? + let keep = this.type != PETRA.AttackPlan.TYPE_RUSH ? 6 + 4 * gameState.getNumPlayerEnemies() + 8 * this.Config.personality.defensive : 8; keep = Math.round(this.Config.popScaling * keep); for (const ent of gameState.getOwnEntitiesByRole("worker", true).values()) @@ -745,7 +771,7 @@ } if (num++ < keep || numbase[baseID] < 5) continue; - if (this.type != "Rush" && ent.getMetadata(PlayerID, "subrole") != "idle") + if (this.type != PETRA.AttackPlan.TYPE_RUSH && ent.getMetadata(PlayerID, "subrole") != "idle") continue; ent.setMetadata(PlayerID, "plan", plan); this.unitCollection.updateEnt(ent); @@ -775,7 +801,7 @@ continue; if (!ent.hasClasses(["FastMoving", "CitizenSoldier"])) continue; - let raid = gameState.ai.HQ.attackManager.getAttackInPreparation("Raid"); + const raid = gameState.ai.HQ.attackManager.getAttackInPreparation(PETRA.AttackPlan.TYPE_RAID); ent.setMetadata(PlayerID, "plan", raid.name); this.unitCollection.updateEnt(ent); raid.unitCollection.updateEnt(ent); @@ -887,9 +913,9 @@ } else { - if (this.type == "Raid") + if (this.type == PETRA.AttackPlan.TYPE_RAID) targets = this.raidTargetFinder(gameState); - else if (this.type == "Rush" || this.type == "Attack") + else if (this.type == PETRA.AttackPlan.TYPE_RUSH || this.type == PETRA.AttackPlan.TYPE_DEFAULT) { targets = this.rushTargetFinder(gameState, this.targetPlayer); if (!targets.hasEntities() && (this.hasSiegeUnits() || this.forced)) @@ -914,7 +940,7 @@ continue; let dist = API3.SquareVectorDistance(ent.position(), position); // In normal attacks, disfavor fields - if (this.type != "Rush" && this.type != "Raid" && ent.hasClass("Field")) + if (this.type != PETRA.AttackPlan.TYPE_RUSH && this.type != PETRA.AttackPlan.TYPE_RAID && ent.hasClass("Field")) dist += 100000; if (dist < minDist) { @@ -1034,7 +1060,7 @@ if (target) targets.addEnt(target); - if (!targets.hasEntities() && this.type == "Rush" && playerEnemy) + if (!targets.hasEntities() && this.type == PETRA.AttackPlan.TYPE_RUSH && playerEnemy) targets = this.rushTargetFinder(gameState); return targets; @@ -1285,7 +1311,7 @@ return 0; } - if (this.state == "arrived") + if (this.state == PETRA.AttackPlan.STATE_ARRIVED) { // let's proceed on with whatever happens now. this.state = ""; @@ -1294,7 +1320,7 @@ ent.stopMoving(); ent.setMetadata(PlayerID, "subrole", "attacking"); }); - if (this.type == "Rush") // try to find a better target for rush + if (this.type == PETRA.AttackPlan.TYPE_RUSH) // try to find a better target for rush { let newtarget = this.getNearestTarget(gameState, this.position); if (newtarget) @@ -1450,7 +1476,7 @@ let targetClassesUnit; let targetClassesSiege; - if (this.type == "Rush") + if (this.type == PETRA.AttackPlan.TYPE_RUSH) targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Palisade", "Wall", "Tower", "Fortress"], "vetoEntities": veto }; else { @@ -1767,7 +1793,7 @@ if (done) { - this.state = "arrived"; + this.state = PETRA.AttackPlan.STATE_ARRIVED; return; } @@ -1818,7 +1844,7 @@ { if (gameState.ai.HQ.territoryMap.getOwner(this.position) === this.targetPlayer || attackedNB > 3) { - this.state = "arrived"; + this.state = PETRA.AttackPlan.STATE_ARRIVED; return true; } } @@ -1858,7 +1884,7 @@ { if (this.Config.debug > 1) API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and is not happy."); - this.state = "arrived"; + this.state = PETRA.AttackPlan.STATE_ARRIVED; return true; } // abort plan @@ -1876,7 +1902,7 @@ { if (this.Config.debug > 1) API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination."); - this.state = "arrived"; + this.state = PETRA.AttackPlan.STATE_ARRIVED; return true; } else if (this.path.length && API3.SquareVectorDistance(this.position, this.path[0]) < 1600) @@ -1888,7 +1914,7 @@ { if (this.Config.debug > 1) API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination."); - this.state = "arrived"; + this.state = PETRA.AttackPlan.STATE_ARRIVED; return true; } } @@ -2052,7 +2078,7 @@ { if (!this.target || this.target.id() != evt.entity) continue; - if (this.type == "Raid" && !this.isStarted()) + if (this.type == PETRA.AttackPlan.TYPE_RAID && !this.isStarted()) this.target = undefined; else this.target = gameState.getEntityById(evt.newentity); @@ -2072,7 +2098,7 @@ this.target = undefined; } - if (!this.overseas || this.state !== "unexecuted") + if (!this.overseas || this.state !== PETRA.AttackPlan.STATE_UNEXECUTED) return; // let's check if an enemy has built a structure at our access for (let evt of events.Create) Index: binaries/data/mods/public/simulation/ai/petra/baseManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/baseManager.js +++ binaries/data/mods/public/simulation/ai/petra/baseManager.js @@ -35,11 +35,28 @@ this.timeNextIdleCheck = 0; }; +/** + * New base with an anchor + */ +PETRA.BaseManager.STATE_WITH_ANCHOR = "anchored"; +/** + * New base with a foundation anchor + */ +PETRA.BaseManager.STATE_UNCONSTRUCTED = "unconstructed"; +/** + * Captured base with an anchor + */ +PETRA.BaseManager.STATE_CAPTURED = "captured"; +/** + * Anchorless base, currently with dock + */ +PETRA.BaseManager.STATE_ANCHORLESS = "anchorless"; + PETRA.BaseManager.prototype.init = function(gameState, state) { - if (state == "unconstructed") + if (state == PETRA.BaseManager.STATE_UNCONSTRUCTED) this.constructing = true; - else if (state != "captured") + else if (state != PETRA.BaseManager.STATE_CAPTURED) this.neededDefenders = 0; this.workerObject = new PETRA.Worker(this); // entitycollections @@ -66,12 +83,12 @@ PETRA.BaseManager.prototype.reset = function(gameState, state) { - if (state == "unconstructed") + if (state == PETRA.BaseManager.STATE_UNCONSTRUCTED) this.constructing = true; else this.constructing = false; - if (state != "captured" || this.Config.difficulty < 3) + if (state != PETRA.BaseManager.STATE_CAPTURED || this.Config.difficulty < 3) this.neededDefenders = 0; else this.neededDefenders = 3 + 2 * (this.Config.difficulty - 3); Index: binaries/data/mods/public/simulation/ai/petra/basesManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/basesManager.js +++ binaries/data/mods/public/simulation/ai/petra/basesManager.js @@ -30,9 +30,9 @@ for (const cc of gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).values()) if (cc.foundationProgress() === undefined) - this.createBase(gameState, cc); + this.createBase(gameState, cc, PETRA.BaseManager.STATE_WITH_ANCHOR); else - this.createBase(gameState, cc, "unconstructed"); + this.createBase(gameState, cc, PETRA.BaseManager.STATE_UNCONSTRUCTED); }; /** @@ -65,12 +65,8 @@ * 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) +PETRA.BasesManager.prototype.createBase = function(gameState, ent, type = PETRA.BaseManager.STATE_WITH_ANCHOR) { const access = PETRA.getLandAccess(gameState, ent); let newbase; @@ -78,9 +74,9 @@ { if (base.accessIndex != access) continue; - if (type != "anchorless" && base.anchor) + if (type != PETRA.BaseManager.STATE_ANCHORLESS && base.anchor) continue; - if (type != "anchorless") + if (type != PETRA.BaseManager.STATE_ANCHORLESS) { // TODO we keep the first one, we should rather use the nearest if buildings // and possibly also cut on distance @@ -116,7 +112,7 @@ else newbase.reset(type); - if (type != "anchorless") + if (type != PETRA.BaseManager.STATE_ANCHORLESS) newbase.setAnchor(gameState, ent); else newbase.setAnchorlessEntity(gameState, ent); @@ -172,7 +168,7 @@ 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"); + const newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED); // Let's get a few units from other bases there to build this. const builders = this.bulkPickWorkers(gameState, newbase, 10); if (builders !== false) @@ -186,7 +182,7 @@ } else if (ent.getMetadata(PlayerID, "base") == -2) // anchorless base around a dock { - const newbase = this.createBase(gameState, ent, "anchorless"); + const newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS); // Let's get a few units from other bases there to build this. const builders = this.bulkPickWorkers(gameState, newbase, 4); if (builders != false) @@ -250,10 +246,10 @@ { let newbase; if (ent.foundationProgress() !== undefined) - newbase = this.createBase(gameState, ent, "unconstructed"); + newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED); else { - newbase = this.createBase(gameState, ent, "captured"); + newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_CAPTURED); addBase = true; } newbase.assignEntity(gameState, ent); @@ -263,7 +259,7 @@ let base; // If dropsite on new island, create a base around it if (!ent.decaying() && ent.resourceDropsiteTypes()) - base = this.createBase(gameState, ent, "anchorless"); + base = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS); else base = PETRA.getBestBase(gameState, ent) || this.noBase; base.assignEntity(gameState, ent); @@ -582,7 +578,7 @@ if (!bestbase) // entity outside our territory { if (ent.hasClass("Structure") && !ent.decaying() && ent.resourceDropsiteTypes()) - bestbase = this.createBase(gameState, ent, "anchorless"); + bestbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS); else bestbase = PETRA.getBestBase(gameState, ent) || this.noBase; bestbase.assignEntity(gameState, ent); Index: binaries/data/mods/public/simulation/ai/petra/chatHelper.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/chatHelper.js +++ binaries/data/mods/public/simulation/ai/petra/chatHelper.js @@ -136,7 +136,7 @@ { Engine.PostCommand(PlayerID, { "type": "aichat", - "message": "/allies " + pickRandom(this.launchAttackMessages[type === "HugeAttack" ? "hugeAttack" : "other"]), + "message": "/allies " + pickRandom(this.launchAttackMessages[type === PETRA.AttackPlan.TYPE_HUGE_ATTACK ? "hugeAttack" : "other"]), "translateMessage": true, "translateParameters": ["_player_"], "parameters": { "_player_": player } Index: binaries/data/mods/public/simulation/ai/petra/defenseManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/defenseManager.js +++ binaries/data/mods/public/simulation/ai/petra/defenseManager.js @@ -606,7 +606,7 @@ if (plan !== undefined && plan >= 0) { let attack = gameState.ai.HQ.attackManager.getPlan(plan); - if (attack && attack.state != "unexecuted") + if (attack && attack.state != PETRA.AttackPlan.STATE_UNEXECUTED) continue; } @@ -752,7 +752,7 @@ let access = PETRA.getLandAccess(gameState, target); let garrisonManager = gameState.ai.HQ.garrisonManager; let garrisonArrowClasses = target.getGarrisonArrowClasses(); - let typeGarrison = data.type || "protection"; + const typeGarrison = data.type || PETRA.GarrisonManager.TYPE_PROTECTION; let allowMelee = gameState.ai.HQ.garrisonManager.allowMelee(target); if (allowMelee === undefined) { @@ -767,7 +767,7 @@ return false; if (!ent.hasClasses(garrisonArrowClasses)) return false; - if (typeGarrison != "decay" && !allowMelee && ent.attackTypes().indexOf("Melee") != -1) + if (typeGarrison != PETRA.GarrisonManager.TYPE_DECAY && !allowMelee && ent.attackTypes().indexOf("Melee") != -1) return false; if (ent.getMetadata(PlayerID, "transport") !== undefined) return false; @@ -778,7 +778,7 @@ { let subrole = ent.getMetadata(PlayerID, "subrole"); // When structure decaying (usually because we've just captured it in enemy territory), also allow units from an attack plan. - if (typeGarrison != "decay" && subrole && (subrole == "completing" || subrole == "walking" || subrole == "attacking")) + if (typeGarrison != PETRA.GarrisonManager.TYPE_DECAY && subrole && (subrole == "completing" || subrole == "walking" || subrole == "attacking")) return false; } if (PETRA.getLandAccess(gameState, ent) != access) @@ -832,7 +832,7 @@ nearest = ent; } if (nearest) - garrisonManager.garrison(gameState, unit, nearest, "protection"); + garrisonManager.garrison(gameState, unit, nearest, PETRA.GarrisonManager.TYPE_PROTECTION); return nearest !== undefined; }; @@ -873,13 +873,13 @@ if (!emergency) { - garrisonManager.garrison(gameState, unit, nearest, "protection"); + garrisonManager.garrison(gameState, unit, nearest, PETRA.GarrisonManager.TYPE_PROTECTION); return true; } if (garrisonManager.numberOfGarrisonedSlots(nearest) >= nearest.garrisonMax()) // make room for this ent nearest.unload(nearest.garrisoned()[0]); - garrisonManager.garrison(gameState, unit, nearest, nearest.buffHeal() ? "protection" : "emergency"); + garrisonManager.garrison(gameState, unit, nearest, nearest.buffHeal() ? PETRA.GarrisonManager.TYPE_PROTECTION : PETRA.GarrisonManager.TYPE_EMERGENCY); return true; }; Index: binaries/data/mods/public/simulation/ai/petra/garrisonManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/garrisonManager.js +++ binaries/data/mods/public/simulation/ai/petra/garrisonManager.js @@ -13,6 +13,12 @@ this.decayingStructures = new Map(); }; +PETRA.GarrisonManager.TYPE_FORCE = "force"; +PETRA.GarrisonManager.TYPE_TRADE = "trade"; +PETRA.GarrisonManager.TYPE_PROTECTION = "protection"; +PETRA.GarrisonManager.TYPE_DECAY = "decay"; +PETRA.GarrisonManager.TYPE_EMERGENCY = "emergency"; + PETRA.GarrisonManager.prototype.update = function(gameState, events) { // First check for possible upgrade of a structure @@ -194,7 +200,7 @@ if (!ent || ent.owner() !== PlayerID) this.decayingStructures.delete(id); else if (this.numberOfGarrisonedSlots(ent) < gmin) - gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, { "min": gmin, "type": "decay" }); + gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, { "min": gmin, "type": PETRA.GarrisonManager.TYPE_DECAY }); } }; @@ -284,11 +290,11 @@ { switch (ent.getMetadata(PlayerID, "garrisonType")) { - case 'force': // force the ungarrisoning + case PETRA.GarrisonManager.TYPE_FORCE: // force the ungarrisoning return false; - case 'trade': // trader garrisoned in ship + case PETRA.GarrisonManager.TYPE_TRADE: // trader garrisoned in ship return true; - case 'protection': // hurt unit for healing or infantry for defense + case PETRA.GarrisonManager.TYPE_PROTECTION: // hurt unit for healing or infantry for defense if (holder.buffHeal() && ent.isHealable() && ent.healthLevel() < this.Config.garrisonHealthLevel.high) return true; let capture = ent.capturePoints(); @@ -309,9 +315,9 @@ if (PETRA.isSiegeUnit(ent)) return around.meleeSiege; return holder.buffHeal() && ent.needsHeal(); - case 'decay': + case PETRA.GarrisonManager.TYPE_DECAY: return this.decayingStructures.has(holder.id()); - case 'emergency': // f.e. hero in regicide mode + case PETRA.GarrisonManager.TYPE_EMERGENCY: // f.e. hero in regicide mode if (holder.buffHeal() && ent.isHealable() && ent.healthLevel() < this.Config.garrisonHealthLevel.high) return true; if (around.unit || around.defenseStructure || around.meleeSiege || @@ -324,7 +330,7 @@ API3.warn("unknown type in garrisonManager " + ent.getMetadata(PlayerID, "garrisonType") + " for " + ent.genericName() + " id " + ent.id() + " inside " + holder.genericName() + " id " + holder.id()); - ent.setMetadata(PlayerID, "garrisonType", "protection"); + ent.setMetadata(PlayerID, "garrisonType", PETRA.GarrisonManager.TYPE_PROTECTION); return true; } }; Index: binaries/data/mods/public/simulation/ai/petra/headquarters.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/headquarters.js +++ binaries/data/mods/public/simulation/ai/petra/headquarters.js @@ -210,7 +210,7 @@ if (plan !== undefined && plan >= 0) { let attack = this.attackManager.getPlan(plan); - if (!attack || attack.state != "unexecuted") + if (!attack || attack.state != PETRA.AttackPlan.STATE_UNEXECUTED) ent.setMetadata(PlayerID, "plan", -1); } } @@ -441,7 +441,7 @@ let alpha = 0.85; if (!gameState.isTemplateAvailable(gameState.applyCiv("structures/{civ}/field"))) supportRatio = Math.min(this.supportRatio, 0.1); - if (this.attackManager.rushNumber < this.attackManager.maxRushes || this.attackManager.upcomingAttacks.Rush.length) + if (this.attackManager.rushNumber < this.attackManager.maxRushes || this.attackManager.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].length) alpha = 0.7; if (gameState.isCeasefireActive()) alpha += (1 - alpha) * Math.min(Math.max(gameState.ceasefireTimeRemaining - 120, 0), 180) / 180; @@ -1869,7 +1869,7 @@ } let metadata = { "role": "worker", "base": nearestAnchor.getMetadata(PlayerID, "base"), "plan": -1, "trainer": nearestAnchor.id() }; if (autogarrison) - metadata.garrisonType = "protection"; + metadata.garrisonType = PETRA.GarrisonManager.TYPE_PROTECTION; gameState.ai.queues.emergency.addPlan(new PETRA.TrainingPlan(gameState, templateFound[0], metadata, 1, 1)); return true; }; Index: binaries/data/mods/public/simulation/ai/petra/victoryManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/victoryManager.js +++ binaries/data/mods/public/simulation/ai/petra/victoryManager.js @@ -679,7 +679,7 @@ if (plan !== undefined && plan >= 0) { let attack = gameState.ai.HQ.attackManager.getPlan(plan); - if (attack && (attack.state != "unexecuted" || attack.type == "Raid")) + if (attack && (attack.state != PETRA.AttackPlan.STATE_UNEXECUTED || attack.type == PETRA.AttackPlan.TYPE_RAID)) return false; } if (PETRA.getLandAccess(gameState, ent) != access)