LCOV - code coverage report
Current view: top level - simulation/ai/petra - diplomacyManager.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 285 0.0 %
Date: 2023-04-02 12:52:40 Functions: 0 14 0.0 %

          Line data    Source code
       1             : /**
       2             :  * Manage the diplomacy:
       3             :  *     update our cooperative trait
       4             :  *     sent tribute to allies
       5             :  *     decide which player to turn against in "Last Man Standing" mode
       6             :  *     respond to diplomacy requests
       7             :  *     send diplomacy requests to other players (rarely)
       8             :  */
       9             : 
      10             : /**
      11             :  * If a player sends us an ally or neutral request, an Object in this.receivedDiplomacyRequests will be created
      12             :  * that includes the request status, and the amount and type of the resource tribute (if any)
      13             :  * that they must send in order for us to accept their request.
      14             :  * In addition, a message will be sent if the player has not sent us a tribute within a minute.
      15             :  * If two minutes pass without a tribute, we will decline their request.
      16             :  *
      17             :  * If we send a diplomacy request to another player, an Object in this.sentDiplomacyRequests will be created,
      18             :  * which consists of the requestType (i.e. "ally" or "neutral") and the timeSent. A chat message will be sent
      19             :  * to the other player, and AI players will actually be informed of the request by a DiplomacyRequest event
      20             :  * sent through AIInterface. It is expected that the other player will change their diplomacy stance to the stance
      21             :  * that we suggested within a period of time, or else the request will be deleted from this.sentDiplomacyRequests.
      22             :  */
      23           0 : PETRA.DiplomacyManager = function(Config)
      24             : {
      25           0 :         this.Config = Config;
      26           0 :         this.nextTributeUpdate = 90;
      27           0 :         this.nextTributeRequest = new Map();
      28           0 :         this.nextTributeRequest.set("all", 240);
      29           0 :         this.betrayLapseTime = -1;
      30           0 :         this.waitingToBetray = false;
      31           0 :         this.betrayWeighting = 150;
      32           0 :         this.receivedDiplomacyRequests = new Map();
      33           0 :         this.sentDiplomacyRequests = new Map();
      34           0 :         this.sentDiplomacyRequestLapseTime = 120 + randFloat(10, 100);
      35             : };
      36             : 
      37             : /**
      38             :  * If there are any players that are allied/neutral with us but we are not allied/neutral with them,
      39             :  * treat this situation like an ally/neutral request.
      40             :  */
      41           0 : PETRA.DiplomacyManager.prototype.init = function(gameState)
      42             : {
      43           0 :         this.lastManStandingCheck(gameState);
      44             : 
      45           0 :         for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
      46             :         {
      47           0 :                 if (i === PlayerID)
      48           0 :                         continue;
      49             : 
      50           0 :                 if (gameState.isPlayerMutualAlly(i))
      51           0 :                         this.receivedDiplomacyRequests.set(i, { "requestType": "ally", "status": "accepted" });
      52           0 :                 else if (gameState.sharedScript.playersData[i].isAlly[PlayerID])
      53           0 :                         this.handleDiplomacyRequest(gameState, i, "ally");
      54           0 :                 else if (gameState.sharedScript.playersData[i].isNeutral[PlayerID] && gameState.isPlayerEnemy(i))
      55           0 :                         this.handleDiplomacyRequest(gameState, i, "neutral");
      56             :         }
      57             : };
      58             : 
      59             : /**
      60             :  * Check if any allied needs help (tribute) and sent it if we have enough resource
      61             :  * or ask for a tribute if we are in need and one ally can help
      62             :  */
      63           0 : PETRA.DiplomacyManager.prototype.tributes = function(gameState)
      64             : {
      65           0 :         this.nextTributeUpdate = gameState.ai.elapsedTime + 30;
      66           0 :         let resTribCodes = Resources.GetTributableCodes();
      67           0 :         if (!resTribCodes.length)
      68           0 :                 return;
      69           0 :         let totalResources = gameState.getResources();
      70           0 :         let availableResources = gameState.ai.queueManager.getAvailableResources(gameState);
      71             :         let mostNeeded;
      72           0 :         for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
      73             :         {
      74           0 :                 if (i === PlayerID || !gameState.isPlayerAlly(i) || gameState.ai.HQ.attackManager.defeated[i])
      75           0 :                         continue;
      76           0 :                 let donor = gameState.getAlliedVictory() || gameState.getEntities(i).length < gameState.getOwnEntities().length;
      77           0 :                 let allyResources = gameState.sharedScript.playersData[i].resourceCounts;
      78           0 :                 let allyPop = gameState.sharedScript.playersData[i].popCount;
      79           0 :                 let tribute = {};
      80           0 :                 let toSend = false;
      81           0 :                 for (let res of resTribCodes)
      82             :                 {
      83           0 :                         if (donor && availableResources[res] > 200 && allyResources[res] < 0.2 * availableResources[res])
      84             :                         {
      85           0 :                                 tribute[res] = Math.floor(0.3*availableResources[res] - allyResources[res]);
      86           0 :                                 toSend = true;
      87             :                         }
      88           0 :                         else if (donor && allyPop < Math.min(30, 0.5*gameState.getPopulation()) && totalResources[res] > 500 && allyResources[res] < 100)
      89             :                         {
      90           0 :                                 tribute[res] = 100;
      91           0 :                                 toSend = true;
      92             :                         }
      93           0 :                         else if (this.Config.chat && availableResources[res] === 0 && allyResources[res] > totalResources[res] + 600)
      94             :                         {
      95           0 :                                 if (gameState.ai.elapsedTime < this.nextTributeRequest.get("all"))
      96           0 :                                         continue;
      97           0 :                                 if (this.nextTributeRequest.has(res) && gameState.ai.elapsedTime < this.nextTributeRequest.get(res))
      98           0 :                                         continue;
      99           0 :                                 if (!mostNeeded)
     100           0 :                                         mostNeeded = gameState.ai.HQ.pickMostNeededResources(gameState, resTribCodes);
     101           0 :                                 for (let k = 0; k < mostNeeded.length; ++k)
     102             :                                 {
     103           0 :                                         if (mostNeeded[k].type == res && mostNeeded[k].wanted > 0)
     104             :                                         {
     105           0 :                                                 this.nextTributeRequest.set("all", gameState.ai.elapsedTime + 90);
     106           0 :                                                 this.nextTributeRequest.set(res, gameState.ai.elapsedTime + 240);
     107           0 :                                                 PETRA.chatRequestTribute(gameState, res);
     108           0 :                                                 if (this.Config.debug > 1)
     109           0 :                                                         API3.warn("Tribute on " + res + " requested to player " + i);
     110           0 :                                                 break;
     111             :                                         }
     112             :                                 }
     113             :                         }
     114             :                 }
     115           0 :                 if (!toSend)
     116           0 :                         continue;
     117           0 :                 if (this.Config.debug > 1)
     118           0 :                         API3.warn("Tribute " + uneval(tribute) + " sent to player " + i);
     119           0 :                 if (this.Config.chat)
     120           0 :                         PETRA.chatSentTribute(gameState, i);
     121           0 :                 Engine.PostCommand(PlayerID, { "type": "tribute", "player": i, "amounts": tribute });
     122             :         }
     123             : };
     124             : 
     125           0 : PETRA.DiplomacyManager.prototype.checkEvents = function(gameState, events)
     126             : {
     127             :         // Increase slowly the cooperative personality trait either when we receive tribute from our allies
     128             :         // or if our allies attack enemies inside our territory
     129           0 :         for (let evt of events.TributeExchanged)
     130             :         {
     131           0 :                 if (evt.to === PlayerID && !gameState.isPlayerAlly(evt.from) && this.receivedDiplomacyRequests.has(evt.from))
     132             :                 {
     133           0 :                         let request = this.receivedDiplomacyRequests.get(evt.from);
     134           0 :                         if (request.status === "waitingForTribute" && request.type in evt.amounts)
     135             :                         {
     136           0 :                                 request.wanted -= evt.amounts[request.type];
     137             : 
     138           0 :                                 if (request.wanted <= 0)
     139             :                                 {
     140           0 :                                         if (this.Config.debug > 1)
     141           0 :                                                 API3.warn("Player " + uneval(evt.from) + " has sent the required tribute amount");
     142             : 
     143           0 :                                         this.changePlayerDiplomacy(gameState, evt.from, request.requestType);
     144           0 :                                         request.status = "accepted";
     145             :                                 }
     146           0 :                                 else if (evt.amounts[request.type] > 0)
     147             :                                 {
     148             :                                         // Reset the warning sent to the player that reminds them to speed up the tributes
     149           0 :                                         request.warnTime = gameState.ai.elapsedTime + 60;
     150           0 :                                         request.sentWarning = false;
     151             :                                 }
     152             :                         }
     153             :                 }
     154             : 
     155           0 :                 if (evt.to !== PlayerID || !gameState.isPlayerAlly(evt.from))
     156           0 :                         continue;
     157           0 :                 let tributes = 0;
     158           0 :                 for (let key in evt.amounts)
     159             :                 {
     160           0 :                         if (key === "food")
     161           0 :                                 tributes += evt.amounts[key];
     162             :                         else
     163           0 :                                 tributes += 2*evt.amounts[key];
     164             :                 }
     165           0 :                 this.Config.personality.cooperative = Math.min(1, this.Config.personality.cooperative + 0.0001 * tributes);
     166             :         }
     167             : 
     168           0 :         for (let evt of events.Attacked)
     169             :         {
     170           0 :                 let target = gameState.getEntityById(evt.target);
     171           0 :                 if (!target || !target.position() ||
     172             :                         gameState.ai.HQ.territoryMap.getOwner(target.position()) !== PlayerID ||
     173             :                         !gameState.isPlayerEnemy(target.owner()))
     174           0 :                         continue;
     175           0 :                 let attacker = gameState.getEntityById(evt.attacker);
     176           0 :                 if (!attacker || attacker.owner() === PlayerID || !gameState.isPlayerAlly(attacker.owner()))
     177           0 :                         continue;
     178           0 :                 this.Config.personality.cooperative = Math.min(1, this.Config.personality.cooperative + 0.003);
     179             :         }
     180             : 
     181           0 :         if (events.DiplomacyChanged.length || events.PlayerDefeated.length || events.CeasefireEnded.length)
     182           0 :                 this.lastManStandingCheck(gameState);
     183             : 
     184           0 :         for (let evt of events.PlayerDefeated)
     185             :         {
     186           0 :                 this.receivedDiplomacyRequests.delete(evt.playerId);
     187           0 :                 this.sentDiplomacyRequests.delete(evt.playerId);
     188             :         }
     189             : 
     190           0 :         for (let evt of events.DiplomacyChanged)
     191             :         {
     192           0 :                 if (evt.otherPlayer !== PlayerID)
     193           0 :                         continue;
     194             : 
     195           0 :                 if (this.sentDiplomacyRequests.has(evt.player)) // If another player has accepted a diplomacy request we sent
     196             :                 {
     197           0 :                         let sentRequest = this.sentDiplomacyRequests.get(evt.player);
     198           0 :                         if (gameState.sharedScript.playersData[evt.player].isAlly[PlayerID] && sentRequest.requestType === "ally" ||
     199             :                             gameState.sharedScript.playersData[evt.player].isNeutral[PlayerID] && sentRequest.requestType === "neutral")
     200           0 :                                 this.changePlayerDiplomacy(gameState, evt.player, sentRequest.requestType);
     201             : 
     202             :                         // Just remove the request if the other player switched their stance to a different and/or more negative state
     203             :                         // TODO: Keep this send request and take it into account for later diplomacy changes (maybe be less inclined to offer to this player)
     204           0 :                         this.sentDiplomacyRequests.delete(evt.player);
     205           0 :                         continue;
     206             :                 }
     207             : 
     208           0 :                 let request = this.receivedDiplomacyRequests.get(evt.player);
     209           0 :                 if (request !== undefined &&
     210             :                    (!gameState.sharedScript.playersData[evt.player].isAlly[PlayerID] && request.requestType === "ally" ||
     211             :                      gameState.sharedScript.playersData[evt.player].isEnemy[PlayerID] && request.requestType === "neutral"))
     212             :                 {
     213             :                         // a player that had requested to be allies changed their stance with us
     214           0 :                         if (request.status === "accepted")
     215           0 :                                 request.status = "allianceBroken";
     216           0 :                         else if (request.status !== "allianceBroken")
     217           0 :                                 request.status = "declinedRequest";
     218             :                 }
     219           0 :                 else if (gameState.sharedScript.playersData[evt.player].isAlly[PlayerID] && gameState.isPlayerEnemy(evt.player))
     220             :                 {
     221           0 :                         let response = request !== undefined && (request.status === "declinedRequest" || request.status === "allianceBroken") ?
     222             :                                 "decline" : "declineSuggestNeutral";
     223           0 :                         PETRA.chatAnswerRequestDiplomacy(gameState, evt.player, "ally", response);
     224             :                 }
     225           0 :                 else if (gameState.sharedScript.playersData[evt.player].isAlly[PlayerID] && gameState.isPlayerNeutral(evt.player))
     226           0 :                         this.handleDiplomacyRequest(gameState, evt.player, "ally");
     227           0 :                 else if (gameState.sharedScript.playersData[evt.player].isNeutral[PlayerID] && gameState.isPlayerEnemy(evt.player))
     228           0 :                         this.handleDiplomacyRequest(gameState, evt.player, "neutral");
     229             :         }
     230             : 
     231             :         // These events will only be sent by other AI players
     232           0 :         for (let evt of events.DiplomacyRequest)
     233             :         {
     234           0 :                 if (evt.player !== PlayerID)
     235           0 :                         continue;
     236             : 
     237           0 :                 this.handleDiplomacyRequest(gameState, evt.source, evt.to);
     238           0 :                 let request = this.receivedDiplomacyRequests.get(evt.source);
     239           0 :                 if (this.Config.debug > 0)
     240           0 :                         API3.warn("Responding to diplomacy request from AI player " + evt.source + " with " + uneval(request));
     241             : 
     242             :                 // Our diplomacy will have changed already if the response was "accept"
     243           0 :                 if (request.status === "waitingForTribute")
     244             :                 {
     245           0 :                         Engine.PostCommand(PlayerID, {
     246             :                                 "type": "tribute-request",
     247             :                                 "source": PlayerID,
     248             :                                 "player": evt.source,
     249             :                                 "resourceWanted": request.wanted,
     250             :                                 "resourceType": request.type
     251             :                         });
     252             :                 }
     253             :         }
     254             : 
     255             :         // An AI player we sent a diplomacy request to demanded we send them a tribute
     256           0 :         for (let evt of events.TributeRequest)
     257             :         {
     258           0 :                 if (evt.player !== PlayerID)
     259           0 :                         continue;
     260             : 
     261           0 :                 let availableResources = gameState.ai.queueManager.getAvailableResources(gameState);
     262             :                 // TODO: Save this event and wait until we get more resources if we don't have enough
     263           0 :                 if (evt.resourceWanted < availableResources[evt.resourceType])
     264             :                 {
     265           0 :                         let responseTribute = {};
     266           0 :                         responseTribute[evt.resourceType] = evt.resourceWanted;
     267           0 :                         if (this.Config.debug > 0)
     268           0 :                                 API3.warn("Responding to tribute request from AI player " + evt.source + " with " + uneval(responseTribute));
     269           0 :                         Engine.PostCommand(PlayerID, { "type": "tribute", "player": evt.source, "amounts": responseTribute });
     270           0 :                         this.nextTributeUpdate = gameState.ai.elapsedTime + 15;
     271             :                 }
     272             :         }
     273             : };
     274             : 
     275             : /**
     276             :  * If the "Last Man Standing" option is enabled, check if the only remaining players are allies or neutral.
     277             :  * If so, turn against the strongest first, but be more likely to first turn against neutral players, if there are any.
     278             :  */
     279           0 : PETRA.DiplomacyManager.prototype.lastManStandingCheck = function(gameState)
     280             : {
     281           0 :         if (gameState.sharedScript.playersData[PlayerID].teamsLocked || gameState.isCeasefireActive() ||
     282             :             gameState.getAlliedVictory() && gameState.hasAllies())
     283           0 :                 return;
     284             : 
     285           0 :         if (gameState.hasEnemies())
     286             :         {
     287           0 :                 this.waitingToBetray = false;
     288           0 :                 return;
     289             :         }
     290             : 
     291           0 :         if (!gameState.hasAllies() && !gameState.hasNeutrals())
     292           0 :                 return;
     293             : 
     294             :         // wait a bit before turning
     295           0 :         if (!this.waitingToBetray)
     296             :         {
     297           0 :                 this.betrayLapseTime = gameState.ai.elapsedTime + randFloat(10, 110);
     298           0 :                 this.waitingToBetray = true;
     299           0 :                 return;
     300             :         }
     301             : 
     302             :         // do not turn against a player yet if we are not strong enough
     303           0 :         if (gameState.getOwnUnits().length < 50)
     304             :         {
     305           0 :                 this.betrayLapseTime += 60;
     306           0 :                 return;
     307             :         }
     308             : 
     309             :         let playerToTurnAgainst;
     310           0 :         let turnFactor = 0;
     311           0 :         let max = 0;
     312             : 
     313             :         // count the amount of entities remaining players have
     314           0 :         for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
     315             :         {
     316           0 :                 if (i === PlayerID || gameState.ai.HQ.attackManager.defeated[i])
     317           0 :                         continue;
     318             : 
     319           0 :                 turnFactor = gameState.getEntities(i).length;
     320             : 
     321           0 :                 if (gameState.isPlayerNeutral(i)) // be more inclined to turn against neutral players
     322           0 :                         turnFactor += this.betrayWeighting;
     323             : 
     324           0 :                 if (gameState.getVictoryConditions().has("wonder"))
     325             :                 {
     326           0 :                         let wonder = gameState.getEnemyStructures(i).filter(API3.Filters.byClass("Wonder"))[0];
     327           0 :                         if (wonder)
     328             :                         {
     329           0 :                                 let wonderProgess = wonder.foundationProgress();
     330           0 :                                 if (wonderProgess === undefined)
     331             :                                 {
     332           0 :                                         playerToTurnAgainst = i;
     333           0 :                                         break;
     334             :                                 }
     335           0 :                                 turnFactor += wonderProgess * 2.5 + this.betrayWeighting;
     336             :                         }
     337             :                 }
     338             : 
     339           0 :                 if (gameState.getVictoryConditions().has("capture_the_relic"))
     340             :                 {
     341           0 :                         let relicsCount = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic"))
     342           0 :                                 .filter(relic => relic.owner() === i).length;
     343           0 :                         turnFactor += relicsCount * this.betrayWeighting;
     344             :                 }
     345             : 
     346           0 :                 if (turnFactor < max)
     347           0 :                         continue;
     348             : 
     349           0 :                 max = turnFactor;
     350           0 :                 playerToTurnAgainst = i;
     351             :         }
     352             : 
     353           0 :         if (playerToTurnAgainst)
     354             :         {
     355           0 :                 this.changePlayerDiplomacy(gameState, playerToTurnAgainst, "enemy");
     356           0 :                 let request = this.receivedDiplomacyRequests.get(playerToTurnAgainst);
     357           0 :                 if (request && request.status !== "allianceBroken")
     358             :                 {
     359           0 :                         if (request.status === "waitingForTribute")
     360           0 :                                 PETRA.chatAnswerRequestDiplomacy(gameState, player, request.requestType, "decline");
     361           0 :                         request.status = request.status === "accepted" ? "allianceBroken" : "declinedRequest";
     362             :                 }
     363             :                 // If we had sent this player a diplomacy request, just rescind it
     364           0 :                 this.sentDiplomacyRequests.delete(playerToTurnAgainst);
     365             :         }
     366           0 :         this.betrayLapseTime = -1;
     367           0 :         this.waitingToBetray = false;
     368             : };
     369             : 
     370             : /**
     371             :  * Do not become allies with a player if the game would be over.
     372             :  * Overall, be reluctant to become allies with any one player, but be more likely to accept neutral requests.
     373             :  */
     374           0 : PETRA.DiplomacyManager.prototype.handleDiplomacyRequest = function(gameState, player, requestType)
     375             : {
     376           0 :         if (gameState.sharedScript.playersData[PlayerID].teamsLocked)
     377           0 :                 return;
     378             :         let response;
     379             :         let requiredTribute;
     380           0 :         let request = this.receivedDiplomacyRequests.get(player);
     381           0 :         let moreEnemiesThanAllies = gameState.getEnemies().length > gameState.getMutualAllies().length;
     382             : 
     383             :         // For any given diplomacy request be likely to permanently decline
     384           0 :         if (!request && gameState.getPlayerCiv() !== gameState.getPlayerCiv(player) && randBool(0.6) ||
     385             :             !moreEnemiesThanAllies || gameState.ai.HQ.attackManager.currentEnemyPlayer === player)
     386             :         {
     387           0 :                 this.receivedDiplomacyRequests.set(player, { "requestType": requestType, "status": "declinedRequest" });
     388           0 :                 response = "decline";
     389             :         }
     390           0 :         else if (request && request.status !== "accepted" && request.requestType !== "ally")
     391             :         {
     392           0 :                 if (request.status === "declinedRequest")
     393           0 :                         response = "decline";
     394           0 :                 else if (request.status === "allianceBroken") // Previous alliance was broken, so decline
     395           0 :                         response = "declineRepeatedOffer";
     396           0 :                 else if (request.status === "waitingForTribute")
     397             :                 {
     398           0 :                         response = "waitingForTribute";
     399           0 :                         requiredTribute = request;
     400             :                 }
     401             :         }
     402           0 :         else if (requestType === "ally" && gameState.getEntities(player).length < gameState.getOwnEntities().length && randBool(0.4) ||
     403             :                  requestType === "neutral" && moreEnemiesThanAllies && randBool(0.8))
     404             :         {
     405           0 :                 response = "accept";
     406           0 :                 this.changePlayerDiplomacy(gameState, player, requestType);
     407           0 :                 this.receivedDiplomacyRequests.set(player, { "requestType": requestType, "status": "accepted" });
     408             :         }
     409             :         else
     410             :         {
     411             :                 // Try to request a tribute.
     412             :                 // If a resource is not tributable, do not request it.
     413             :                 // If no resources are tributable, decline.
     414           0 :                 let resTribCodes = Resources.GetTributableCodes();
     415           0 :                 if (resTribCodes.length)
     416             :                 {
     417           0 :                         requiredTribute = gameState.ai.HQ.pickMostNeededResources(gameState, resTribCodes)[0];
     418           0 :                         response = "acceptWithTribute";
     419           0 :                         requiredTribute.wanted = Math.max(1000, gameState.getOwnUnits().length * (requestType === "ally" ? 10 : 5));
     420           0 :                         this.receivedDiplomacyRequests.set(player, {
     421             :                                 "status": "waitingForTribute",
     422             :                                 "wanted": requiredTribute.wanted,
     423             :                                 "type": requiredTribute.type,
     424             :                                 "warnTime": gameState.ai.elapsedTime + 60,
     425             :                                 "sentWarning": false,
     426             :                                 "requestType": requestType
     427             :                         });
     428             :                 }
     429             :                 else
     430             :                 {
     431           0 :                         this.receivedDiplomacyRequests.set(player, { "requestType": requestType, "status": "declinedRequest" });
     432           0 :                         response = "decline";
     433             :                 }
     434             :         }
     435           0 :         PETRA.chatAnswerRequestDiplomacy(gameState, player, requestType, response, requiredTribute);
     436             : };
     437             : 
     438           0 : PETRA.DiplomacyManager.prototype.changePlayerDiplomacy = function(gameState, player, newDiplomaticStance)
     439             : {
     440           0 :         if (gameState.isPlayerEnemy(player) && (newDiplomaticStance === "ally" || newDiplomaticStance === "neutral"))
     441           0 :                 gameState.ai.HQ.attackManager.cancelAttacksAgainstPlayer(gameState, player);
     442           0 :         Engine.PostCommand(PlayerID, { "type": "diplomacy", "player": player, "to": newDiplomaticStance });
     443           0 :         if (this.Config.debug > 1)
     444           0 :                 API3.warn("diplomacy stance with player " + player + " is now " + newDiplomaticStance);
     445           0 :         if (this.Config.chat)
     446           0 :                 PETRA.chatNewDiplomacy(gameState, player, newDiplomaticStance);
     447             : };
     448             : 
     449           0 : PETRA.DiplomacyManager.prototype.checkRequestedTributes = function(gameState)
     450             : {
     451           0 :         for (let [player, data] of this.receivedDiplomacyRequests)
     452           0 :                 if (data.status === "waitingForTribute" && gameState.ai.elapsedTime > data.warnTime)
     453             :                 {
     454           0 :                         if (data.sentWarning)
     455             :                         {
     456           0 :                                 this.receivedDiplomacyRequests.delete(player);
     457           0 :                                 PETRA.chatAnswerRequestDiplomacy(gameState, player, data.requestType, "decline");
     458             :                         }
     459             :                         else
     460             :                         {
     461           0 :                                 data.sentWarning = true;
     462           0 :                                 data.warnTime = gameState.ai.elapsedTime + 60;
     463           0 :                                 PETRA.chatAnswerRequestDiplomacy(gameState, player, data.requestType, "waitingForTribute", {
     464             :                                         "wanted": data.wanted,
     465             :                                         "type": data.type
     466             :                                 });
     467             :                         }
     468             :                 }
     469             : };
     470             : 
     471             : /**
     472             :  * Try to become allies with a player who has a lot of mutual enemies in common with us.
     473             :  * TODO: Possibly let human players demand tributes from AIs who send diplomacy requests.
     474             :  */
     475           0 : PETRA.DiplomacyManager.prototype.sendDiplomacyRequest = function(gameState)
     476             : {
     477             :         let player;
     478           0 :         let max = 0;
     479           0 :         for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
     480             :         {
     481           0 :                 let mutualEnemies = 0;
     482           0 :                 let request = this.receivedDiplomacyRequests.get(i); // Do not send to players we have already rejected before
     483           0 :                 if (i === PlayerID || gameState.isPlayerMutualAlly(i) || gameState.ai.HQ.attackManager.defeated[i] ||
     484             :                     gameState.ai.HQ.attackManager.currentEnemyPlayer === i ||
     485             :                     this.sentDiplomacyRequests.get(i) !== undefined || request && request.status === "declinedRequest")
     486           0 :                         continue;
     487             : 
     488           0 :                 for (let j = 1; j < gameState.sharedScript.playersData.length; ++j)
     489             :                 {
     490           0 :                         if (gameState.sharedScript.playersData[i].isEnemy[j] && gameState.isPlayerEnemy(j) &&
     491             :                             !gameState.ai.HQ.attackManager.defeated[j])
     492           0 :                                 ++mutualEnemies;
     493             : 
     494           0 :                         if (mutualEnemies < max)
     495           0 :                                 continue;
     496             : 
     497           0 :                         max = mutualEnemies;
     498           0 :                         player = i;
     499             :                 }
     500             :         }
     501           0 :         if (!player)
     502           0 :                 return;
     503             : 
     504           0 :         let requestType = gameState.isPlayerNeutral(player) ? "ally" : "neutral";
     505             : 
     506           0 :         this.sentDiplomacyRequests.set(player, {
     507             :                 "requestType": requestType,
     508             :                 "timeSent": gameState.ai.elapsedTime
     509             :         });
     510             : 
     511           0 :         if (this.Config.debug > 0)
     512           0 :                 API3.warn("Sending diplomacy request to player " + player + " with " + requestType);
     513           0 :         Engine.PostCommand(PlayerID, { "type": "diplomacy-request", "source": PlayerID, "player": player, "to": requestType });
     514           0 :         PETRA.chatNewRequestDiplomacy(gameState, player, requestType, "sendRequest");
     515             : };
     516             : 
     517           0 : PETRA.DiplomacyManager.prototype.checkSentDiplomacyRequests = function(gameState)
     518             : {
     519           0 :         for (let [player, data] of this.sentDiplomacyRequests)
     520           0 :                 if (gameState.ai.elapsedTime > data.timeSent + 60 && !gameState.ai.HQ.saveResources &&
     521             :                     gameState.getPopulation() > 70)
     522             :                 {
     523           0 :                         PETRA.chatNewRequestDiplomacy(gameState, player, data.requestType, "requestExpired");
     524           0 :                         this.sentDiplomacyRequests.delete(player);
     525             :                 }
     526             : };
     527             : 
     528           0 : PETRA.DiplomacyManager.prototype.update = function(gameState, events)
     529             : {
     530           0 :         this.checkEvents(gameState, events);
     531             : 
     532           0 :         if (Resources.GetTributableCodes().length && !gameState.ai.HQ.saveResources && gameState.ai.elapsedTime > this.nextTributeUpdate)
     533           0 :                 this.tributes(gameState);
     534             : 
     535           0 :         if (this.waitingToBetray && gameState.ai.elapsedTime > this.betrayLapseTime)
     536           0 :                 this.lastManStandingCheck(gameState);
     537             : 
     538           0 :         this.checkRequestedTributes(gameState);
     539             : 
     540           0 :         if (gameState.sharedScript.playersData[PlayerID].teamsLocked || gameState.isCeasefireActive())
     541           0 :                 return;
     542             : 
     543             :         // Be unlikely to send diplomacy requests to other players
     544           0 :         if (gameState.ai.elapsedTime > this.sentDiplomacyRequestLapseTime)
     545             :         {
     546           0 :                 this.sentDiplomacyRequestLapseTime = gameState.ai.elapsedTime + 300 + randFloat(10, 100);
     547           0 :                 let numEnemies = gameState.getEnemies().length;
     548             :                 // Don't consider gaia
     549           0 :                 if (numEnemies > 2 && gameState.getMutualAllies().length < numEnemies - 1 && randBool(0.1))
     550           0 :                         this.sendDiplomacyRequest(gameState);
     551             :         }
     552             : 
     553           0 :         this.checkSentDiplomacyRequests(gameState);
     554             : };
     555             : 
     556           0 : PETRA.DiplomacyManager.prototype.Serialize = function()
     557             : {
     558           0 :         return {
     559             :                 "nextTributeUpdate": this.nextTributeUpdate,
     560             :                 "nextTributeRequest": this.nextTributeRequest,
     561             :                 "betrayLapseTime": this.betrayLapseTime,
     562             :                 "waitingToBetray": this.waitingToBetray,
     563             :                 "betrayWeighting": this.betrayWeighting,
     564             :                 "receivedDiplomacyRequests": this.receivedDiplomacyRequests,
     565             :                 "sentDiplomacyRequests": this.sentDiplomacyRequests,
     566             :                 "sentDiplomacyRequestLapseTime": this.sentDiplomacyRequestLapseTime
     567             :         };
     568             : };
     569             : 
     570           0 : PETRA.DiplomacyManager.prototype.Deserialize = function(data)
     571             : {
     572           0 :         for (let key in data)
     573           0 :                 this[key] = data[key];
     574             : };

Generated by: LCOV version 1.14