diff --git a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js index 2aad561ae5..f80fddc182 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js +++ b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js @@ -21,6 +21,8 @@ m.GameState.prototype.init = function(SharedScript, state, player) { this.playerData = SharedScript.playersData[this.player]; this.barterPrices = SharedScript.barterPrices; this.gameType = SharedScript.gameType; + this.alliedVictory = SharedScript.alliedVictory; + this.ceasefireActive = SharedScript.ceasefireActive; // get the list of possible phases for this civ: // we assume all of them are researchable from the civil centre @@ -55,6 +57,7 @@ m.GameState.prototype.update = function(SharedScript) this.timeElapsed = SharedScript.timeElapsed; this.playerData = SharedScript.playersData[this.player]; this.barterPrices = SharedScript.barterPrices; + this.ceasefireActive = SharedScript.ceasefireActive; }; m.GameState.prototype.updatingCollection = function(id, filter, collection) @@ -117,6 +120,16 @@ m.GameState.prototype.getGameType = function() return this.gameType; }; +m.GameState.prototype.getAlliedVictory = function() +{ + return this.alliedVictory; +}; + +m.GameState.prototype.isCeasefireActive = function() +{ + return this.ceasefireActive; +}; + m.GameState.prototype.getTemplate = function(type) { if (this.techTemplates[type] !== undefined) @@ -291,11 +304,35 @@ m.GameState.prototype.getPlayerID = function() m.GameState.prototype.hasAllies = function() { for (let i in this.playerData.isAlly) - if (this.playerData.isAlly[i] && +i !== this.player) + if (this.playerData.isAlly[i] && +i !== this.player && + this.sharedScript.playersData[i].state !== "defeated") return true; return false; }; +m.GameState.prototype.hasEnemies = function() +{ + for (let i in this.playerData.isEnemy) + if (this.playerData.isEnemy[i] && +i !== 0 && + this.sharedScript.playersData[i].state !== "defeated") + return true; + return false; +}; + +m.GameState.prototype.hasNeutrals = function() +{ + for (let i in this.playerData.isNeutral) + if (this.playerData.isNeutral[i] && + this.sharedScript.playersData[i].state !== "defeated") + return true; + return false; +}; + +m.GameState.prototype.isPlayerNeutral = function(id) +{ + return this.playerData.isNeutral[id]; +}; + m.GameState.prototype.isPlayerAlly = function(id) { return this.playerData.isAlly[id]; @@ -383,9 +420,12 @@ m.GameState.prototype.getEntityById = function(id) return undefined; }; -m.GameState.prototype.getEntities = function() +m.GameState.prototype.getEntities = function(id) { - return this.entities; + if (id === undefined) + return this.entities; + + return this.updatingGlobalCollection("" + id + "-entities", m.Filters.byOwner(id)); }; m.GameState.prototype.getStructures = function() diff --git a/binaries/data/mods/public/simulation/ai/common-api/shared.js b/binaries/data/mods/public/simulation/ai/common-api/shared.js index 68ab45dd2b..99258a95d4 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/shared.js +++ b/binaries/data/mods/public/simulation/ai/common-api/shared.js @@ -142,6 +142,8 @@ m.SharedScript.prototype.init = function(state, deserialization) this.mapSize = state.mapSize; this.gameType = state.gameType; this.barterPrices = state.barterPrices; + this.alliedVictory = state.alliedVictory; + this.ceasefireActive = state.ceasefireActive; this.passabilityMap = state.passabilityMap; if (this.mapSize % this.passabilityMap.width !== 0) @@ -241,6 +243,7 @@ m.SharedScript.prototype.onUpdate = function(state) this.playersData = state.players; this.timeElapsed = state.timeElapsed; this.barterPrices = state.barterPrices; + this.ceasefireActive = state.ceasefireActive; this.passabilityMap = state.passabilityMap; this.passabilityMap.cellSize = this.mapSize / this.passabilityMap.width; diff --git a/binaries/data/mods/public/simulation/ai/petra/chatHelper.js b/binaries/data/mods/public/simulation/ai/petra/chatHelper.js index afd3818955..3fbb4bf90c 100644 --- a/binaries/data/mods/public/simulation/ai/petra/chatHelper.js +++ b/binaries/data/mods/public/simulation/ai/petra/chatHelper.js @@ -115,11 +115,9 @@ m.chatNewTradeRoute = function(gameState, player) m.chatNewPhase = function(gameState, phase, started) { - let message; - if (started) - message = markForTranslation("I am advancing to the %(phase)s."); - else - message = markForTranslation("I have reached the %(phase)s."); + let message = started ? + markForTranslation("I am advancing to the %(phase)s.") : + markForTranslation("I have reached the %(phase)s."); Engine.PostCommand(PlayerID, { "type": "aichat", @@ -130,5 +128,20 @@ m.chatNewPhase = function(gameState, phase, started) }); }; +m.chatNewDiplomacy = function(gameState, player, enemy) +{ + let message = enemy ? + markForTranslation("%(_player_)s and I are now enemies.") : + markForTranslation("%(_player_)s and I are now allies."); + + Engine.PostCommand(PlayerID, { + "type": "aichat", + "message": message, + "translateMessage": true, + "translateParameters": ["_player_"], + "parameters": {"_player_": player} + }); +}; + return m; }(PETRA); diff --git a/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js b/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js index 1e3a0adb6c..d410556e0d 100644 --- a/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js +++ b/binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js @@ -13,6 +13,8 @@ m.DiplomacyManager = function(Config) this.nextTributeUpdate = -1; this.nextTributeRequest = new Map(); this.nextTributeRequest.set("all", 240); + this.betrayLapseTime = -1; + this.waitingToBetray = false; }; /** @@ -108,8 +110,86 @@ m.DiplomacyManager.prototype.checkEvents = function (gameState, events) continue; this.Config.personality.cooperative = Math.min(1, this.Config.personality.cooperative + 0.003); } + + if (events.DiplomacyChanged.length || events.PlayerDefeated.length || events.CeasefireEnded.length) + this.lastManStandingCheck(gameState); }; +/** + * Check the diplomacy at the start of the game + */ +m.DiplomacyManager.prototype.diplomacyCheck = function(gameState) +{ + if (!gameState.getAlliedVictory() && !gameState.isCeasefireActive()) + this.lastManStandingCheck(gameState); +}; + +/** + * If the "Last Man Standing" option is enabled, check if the only remaining players are allies or neutral. + * If so, turn against the strongest first, but be more likely to first turn against neutral players, if there are any. + */ +m.DiplomacyManager.prototype.lastManStandingCheck = function(gameState) +{ + if (gameState.getAlliedVictory() || gameState.isCeasefireActive()) + return; + + if (gameState.hasEnemies()) + { + this.waitingToBetray = false; + return; + } + + if (!gameState.hasAllies() && !gameState.hasNeutrals()) + return; + + // wait a bit before turning + if (!this.waitingToBetray) + { + this.betrayLapseTime = gameState.ai.elapsedTime + Math.random() * 100 + 10; + this.waitingToBetray = true; + return; + } + + // do not turn against a player yet if we are not strong enough + if (gameState.getOwnUnits().length < 50) + { + this.betrayLapseTime += 60; + return; + } + + let playerToTurnAgainst; + let turnFactor = 0; + let max = 0; + + // count the amount of entities remaining players have + for (let i = 1; i < gameState.sharedScript.playersData.length; ++i) + { + if (i === PlayerID || gameState.ai.HQ.attackManager.defeated[i]) + continue; + + turnFactor = gameState.getEntities(i).length; + + if (gameState.isPlayerNeutral(i)) // be more inclined to turn against neutral players + turnFactor += 150; + + if (turnFactor < max) + continue; + + max = turnFactor; + playerToTurnAgainst = i; + } + + if (playerToTurnAgainst) + { + Engine.PostCommand(PlayerID, { "type": "diplomacy", "player": playerToTurnAgainst, "to": "enemy" }); + if (this.Config.debug > 1) + API3.warn("player " + playerToTurnAgainst + " is now an enemy"); + if (this.Config.chat) + m.chatNewDiplomacy(gameState, playerToTurnAgainst, true); + } + this.betrayLapseTime = -1; + this.waitingToBetray = false; +}; m.DiplomacyManager.prototype.update = function(gameState, events) { @@ -117,17 +197,25 @@ m.DiplomacyManager.prototype.update = function(gameState, events) if (!gameState.ai.HQ.saveResources && gameState.ai.elapsedTime > this.nextTributeUpdate) this.tributes(gameState); + + if (this.waitingToBetray && gameState.ai.elapsedTime > this.betrayLapseTime) + this.lastManStandingCheck(gameState); }; m.DiplomacyManager.prototype.Serialize = function() { - return { "nextTributeUpdate": this.nextTributeUpdate, "nextTributeRequest": this.nextTributeRequest }; + return { + "nextTributeUpdate": this.nextTributeUpdate, + "nextTributeRequest": this.nextTributeRequest, + "betrayLapseTime": this.betrayLapseTime, + "waitingToBetray": this.waitingToBetray + }; }; m.DiplomacyManager.prototype.Deserialize = function(data) { - this.nextTributeUpdate = data.nextTributeUpdate; - this.nextTributeRequest = data.nextTributeRequest; + for (let key in data) + this[key] = data[key]; }; return m; diff --git a/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js b/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js index d6f2ee009a..7c1932ade9 100644 --- a/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js +++ b/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js @@ -54,6 +54,8 @@ m.HQ.prototype.gameAnalysis = function(gameState) // configure our first base strategy if (this.baseManagers.length > 1) this.configFirstBase(gameState); + + this.diplomacyManager.diplomacyCheck(gameState); }; /** diff --git a/binaries/data/mods/public/simulation/components/AIInterface.js b/binaries/data/mods/public/simulation/components/AIInterface.js index bee644901a..965671db64 100644 --- a/binaries/data/mods/public/simulation/components/AIInterface.js +++ b/binaries/data/mods/public/simulation/components/AIInterface.js @@ -20,7 +20,8 @@ AIInterface.prototype.EventNames = [ "TerritoriesChanged", "TerritoryDecayChanged", "TributeExchanged", - "AttackRequest" + "AttackRequest", + "CeasefireEnded" ]; AIInterface.prototype.Init = function() @@ -197,6 +198,11 @@ AIInterface.prototype.OnTerritoriesChanged = function(msg) this.events.TerritoriesChanged.push(msg); }; +AIInterface.prototype.OnCeasefireEnded = function(msg) +{ + this.events.CeasefireEnded.push(msg); +}; + /** * When a new technology is researched, check which templates it affects, * and send the updated values to the AI. diff --git a/binaries/data/mods/public/simulation/components/EndGameManager.js b/binaries/data/mods/public/simulation/components/EndGameManager.js index ccf4ee1f4e..d6a8d54e0b 100644 --- a/binaries/data/mods/public/simulation/components/EndGameManager.js +++ b/binaries/data/mods/public/simulation/components/EndGameManager.js @@ -76,6 +76,11 @@ EndGameManager.prototype.SetAlliedVictory = function(flag) this.alliedVictory = flag; }; +EndGameManager.prototype.GetAlliedVictory = function() +{ + return this.alliedVictory; +}; + EndGameManager.prototype.AlliedVictoryCheck = function() { if (this.skipAlliedVictoryCheck) diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 4918b732eb..d36a60e1e4 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -144,9 +144,10 @@ GuiInterface.prototype.GetSimulationState = function() ret.ceasefireTimeRemaining = ret.ceasefireActive ? cmpCeasefireManager.GetCeasefireStartedTime() + cmpCeasefireManager.GetCeasefireTime() - ret.timeElapsed : 0; } - // Add the game type + // Add the game type and allied victory let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); ret.gameType = cmpEndGameManager.GetGameType(); + ret.alliedVictory = cmpEndGameManager.GetAlliedVictory(); // Add bartering prices let cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter); diff --git a/binaries/data/mods/public/simulation/components/tests/test_EndGameManager.js b/binaries/data/mods/public/simulation/components/tests/test_EndGameManager.js index c4467e7199..d2bc05bbd2 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_EndGameManager.js +++ b/binaries/data/mods/public/simulation/components/tests/test_EndGameManager.js @@ -22,7 +22,7 @@ AddMock(playerEnt1, IID_Player, { TS_ASSERT_EQUALS(cmpEndGameManager.skipAlliedVictoryCheck, true); cmpEndGameManager.SetAlliedVictory(true); -TS_ASSERT_EQUALS(cmpEndGameManager.alliedVictory, true); +TS_ASSERT_EQUALS(cmpEndGameManager.GetAlliedVictory(), true); cmpEndGameManager.SetGameType("wonder", { "wonderDuration": wonderDuration }); TS_ASSERT_EQUALS(cmpEndGameManager.CheckGameType("regicide"), false); TS_ASSERT_EQUALS(cmpEndGameManager.skipAlliedVictoryCheck, false); diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js index eb9eb3d375..4955836f2c 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -47,7 +47,8 @@ AddMock(SYSTEM_ENTITY, IID_Barter, { }); AddMock(SYSTEM_ENTITY, IID_EndGameManager, { - GetGameType: function() { return "conquest"; } + GetGameType: function() { return "conquest"; }, + GetAlliedVictory: function() { return false; } }); AddMock(SYSTEM_ENTITY, IID_PlayerManager, { @@ -331,6 +332,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), { circularMap: false, timeElapsed: 0, gameType: "conquest", + alliedVictory: false, barterPrices: {buy: {food: 150}, sell: {food: 25}} }); @@ -450,6 +452,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), { circularMap: false, timeElapsed: 0, gameType: "conquest", + alliedVictory: false, barterPrices: {buy: {food: 150}, sell: {food: 25}} });