1
0
forked from mirrors/0ad

AI diplomacy support for lastManStanding, patch by Sandarac, tests reviewed by elexis, fixes #4143

This was SVN commit r18945.
This commit is contained in:
mimo
2016-11-15 18:48:20 +00:00
parent a96335fad1
commit f352e2c725
10 changed files with 176 additions and 15 deletions
@@ -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()
@@ -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;
@@ -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);
@@ -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;
@@ -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);
};
/**
@@ -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.
@@ -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)
@@ -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);
@@ -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);
@@ -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}}
});