mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-06-21 18:43:39 +00:00
AI diplomacy support for lastManStanding, patch by Sandarac, tests reviewed by elexis, fixes #4143
This was SVN commit r18945.
This commit is contained in:
@@ -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}}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user