Choose AI behavior in gamesetup

Original patch from Sandarac, reworked by elexis and finalized by mimo

Discussed with elexis

Differential Revision: https://code.wildfiregames.com/D1159
This was SVN commit r20671.
This commit is contained in:
mimo 2017-12-20 22:02:15 +00:00
parent c0c046c273
commit 3319c69d97
16 changed files with 192 additions and 62 deletions

View File

@ -348,6 +348,7 @@ scale = 1.0 ; GUI scaling factor, for improved compatibili
enabletips = true ; Enable/Disable tips during gamesetup (for newcomers)
assignplayers = everyone ; Whether to assign joining clients to free playerslots. Possible values: everyone, buddies, disabled.
aidifficulty = 3 ; Difficulty level, from 0 (easiest) to 5 (hardest)
aibehavior = "random" ; Default behavior of the AI (random, balanced, aggressive or defensive)
[gui.session]
camerajump.threshold = 40 ; How close do we have to be to the actual location in order to jump back to the previous one?

View File

@ -1,6 +1,6 @@
var g_PlayerSlot;
const g_AIDescriptions = [{
var g_AIDescriptions = [{
"id": "",
"data": {
"name": translateWithContext("ai", "None"),
@ -8,28 +8,39 @@ const g_AIDescriptions = [{
}
}].concat(g_Settings.AIDescriptions);
var g_AIControls = {
"aiSelection": {
"labels": g_AIDescriptions.map(ai => ai.data.name),
"selected": settings => g_AIDescriptions.findIndex(ai => ai.id == settings.id)
},
"aiDifficulty": {
"labels": prepareForDropdown(g_Settings.AIDifficulties).Title,
"selected": settings => settings.difficulty
},
"aiBehavior": {
"labels": prepareForDropdown(g_Settings.AIBehaviors).Title,
"selected": settings => g_Settings.AIBehaviors.findIndex(b => b.Name == settings.behavior)
}
};
function init(settings)
{
// Remember the player ID that we change the AI settings for
g_PlayerSlot = settings.playerSlot;
let aiSelection = Engine.GetGUIObjectByName("aiSelection");
aiSelection.list = g_AIDescriptions.map(ai => ai.data.name);
aiSelection.selected = g_AIDescriptions.findIndex(ai => ai.id == settings.id);
aiSelection.hidden = !settings.isController;
for (let name in g_AIControls)
{
let control = Engine.GetGUIObjectByName(name);
control.list = g_AIControls[name].labels;
control.selected = g_AIControls[name].selected(settings);
control.hidden = !settings.isController;
let aiSelectionText = Engine.GetGUIObjectByName("aiSelectionText");
aiSelectionText.caption = aiSelection.list[aiSelection.selected];
aiSelectionText.hidden = settings.isController;
let label = Engine.GetGUIObjectByName(name + "Text");
label.caption = control.list[control.selected];
label.hidden = settings.isController;
}
let aiDiff = Engine.GetGUIObjectByName("aiDifficulty");
aiDiff.list = prepareForDropdown(g_Settings.AIDifficulties).Title;
aiDiff.selected = settings.difficulty;
aiDiff.hidden = !settings.isController;
let aiDiffText = Engine.GetGUIObjectByName("aiDifficultyText");
aiDiffText.caption = aiDiff.list[aiDiff.selected];
aiDiffText.hidden = settings.isController;
checkBehavior();
}
function selectAI(idx)
@ -37,6 +48,19 @@ function selectAI(idx)
Engine.GetGUIObjectByName("aiDescription").caption = g_AIDescriptions[idx].data.description;
}
/** Behavior choice does not apply for Sandbox level */
function checkBehavior()
{
if (g_Settings.AIDifficulties[Engine.GetGUIObjectByName("aiDifficulty").selected].Name != "sandbox")
{
Engine.GetGUIObjectByName("aiBehavior").enabled = true;
return;
}
let aiBehavior = Engine.GetGUIObjectByName("aiBehavior");
aiBehavior.enabled = false;
aiBehavior.selected = g_Settings.AIBehaviors.findIndex(b => b.Name == "balanced");
}
function returnAI(save = true)
{
let idx = Engine.GetGUIObjectByName("aiSelection").selected;
@ -48,6 +72,7 @@ function returnAI(save = true)
"id": g_AIDescriptions[idx].id,
"name": g_AIDescriptions[idx].data.name,
"difficulty": Engine.GetGUIObjectByName("aiDifficulty").selected,
"behavior": g_Settings.AIBehaviors[Engine.GetGUIObjectByName("aiBehavior").selected].Name,
"playerSlot": g_PlayerSlot
});
}

View File

@ -8,7 +8,7 @@
<!-- Add a translucent black background to fade out the menu page -->
<object type="image" sprite="ModernFade"/>
<object type="image" style="ModernDialog" size="50%-230 50%-150 50%+230 50%+150">
<object type="image" style="ModernDialog" size="50%-230 50%-170 50%+230 50%+170">
<object style="ModernLabelText" type="text" size="50%-128 -18 50%+128 14">
<translatableAttribute id="caption">AI Configuration</translatableAttribute>
@ -29,11 +29,19 @@
</object>
<object name="aiDifficulty" type="dropdown" style="ModernDropDown" size="50%-24 35 50%+136 63">
<action on="SelectionChange">checkBehavior();</action>
</object>
<object name="aiDifficultyText" type="text" style="ModernLeftLabelText" size="50%-24 35 50%+136 63"/>
<object type="text" style="ModernRightLabelText" size="-10 70 90 50%+70">
<translatableAttribute id="caption">AI Behavior:</translatableAttribute>
</object>
<object name="aiBehavior" type="dropdown" style="ModernDropDown" size="50%-24 70 50%+136 98"/>
<object name="aiBehaviorText" type="text" style="ModernLeftLabelText" size="50%-24 70 50%+136 98"/>
</object>
<object name="aiDescription" type="text" style="ModernLabelText" size="8% 90 92% 100%-60"/>
<object name="aiDescription" type="text" style="ModernLabelText" size="8% 90 92% 100%-30"/>
<object type="button" style="ModernButtonRed" size="18 100%-45 50%-5 100%-17" hotkey="cancel">
<translatableAttribute id="caption">Cancel</translatableAttribute>

View File

@ -102,19 +102,19 @@ function formatPlayerInfo(playerDataArray, playerStates)
{
if (isActive)
// Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s)");
playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIbehavior)s %(AIname)s)");
else
// Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s, %(state)s)");
playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIbehavior)s %(AIname)s, %(state)s)");
}
else
{
if (isActive)
// Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
playerDescription = translate("%(playerName)s (%(AIdifficulty)s %(AIname)s)");
playerDescription = translate("%(playerName)s (%(AIdifficulty)s %(AIbehavior)s %(AIname)s)");
else
// Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
playerDescription = translate("%(playerName)s (%(AIdifficulty)s %(AIname)s, %(state)s)");
playerDescription = translate("%(playerName)s (%(AIdifficulty)s %(AIbehavior)s %(AIname)s, %(state)s)");
}
}
else
@ -174,7 +174,8 @@ function formatPlayerInfo(playerDataArray, playerStates)
translateWithContext("playerstate", "won"),
"AIname": isAI ? translateAIName(playerData.AI) : "",
"AIdifficulty": isAI ? translateAIDifficulty(playerData.AIDiff) : ""
"AIdifficulty": isAI ? translateAIDifficulty(playerData.AIDiff) : "",
"AIbehavior": isAI ? translateAIBehavior(playerData.AIBehavior) : ""
}));
}

View File

@ -37,6 +37,7 @@ function loadSettingsValues()
var settings = {
"AIDescriptions": loadAIDescriptions(),
"AIDifficulties": loadAIDifficulties(),
"AIBehaviors": loadAIBehaviors(),
"Ceasefire": loadCeasefire(),
"VictoryDurations": loadVictoryDuration(),
"GameSpeeds": loadSettingValuesFile("game_speeds.json"),
@ -138,6 +139,29 @@ function loadAIDifficulties()
];
}
function loadAIBehaviors()
{
return [
{
"Name": "random",
"Title": translateWithContext("aiBehavior", "Random"),
"Default": true
},
{
"Name": "balanced",
"Title": translateWithContext("aiBehavior", "Balanced"),
},
{
"Name": "defensive",
"Title": translateWithContext("aiBehavior", "Defensive")
},
{
"Name": "aggressive",
"Title": translateWithContext("aiBehavior", "Aggressive")
}
];
}
/**
* Loads available victory times for victory conditions like Wonder and Capture the Relic.
*/
@ -356,6 +380,17 @@ function translateAIDifficulty(index)
return difficulty ? difficulty.Title : translateWithContext("AI difficulty", "Unknown");
}
/**
* Returns title or placeholder.
*
* @param {string} aiBehavior - for example "defensive"
*/
function translateAIBehavior(aiBehavior)
{
let behavior = g_Settings.AIBehaviors.find(b => b.Name == aiBehavior);
return behavior ? behavior.Title : translateWithContext("AI behavior", "Default");
}
/**
* Returns title or placeholder.
*

View File

@ -955,9 +955,10 @@ var g_PlayerMiscElements = {
"onPress": (playerIdx) => function() {
openAIConfig(playerIdx);
},
"tooltip": (playerIdx) => sprintf(translate("Configure AI: %(name)s - %(difficulty)s."), {
"tooltip": (playerIdx) => sprintf(translate("Configure AI: %(difficulty)s %(behavior)s %(name)s."), {
"name": translateAIName(g_GameAttributes.settings.PlayerData[playerIdx].AI),
"difficulty": translateAIDifficulty(g_GameAttributes.settings.PlayerData[playerIdx].AIDiff)
"difficulty": translateAIDifficulty(g_GameAttributes.settings.PlayerData[playerIdx].AIDiff),
"behavior": translateAIBehavior(g_GameAttributes.settings.PlayerData[playerIdx].AIBehavior),
}),
},
};
@ -1016,6 +1017,7 @@ function initDefaults()
g_DefaultPlayerData = clone(g_Settings.PlayerDefaults.slice(1));
let aiDifficulty = +Engine.ConfigDB_GetValue("user", "gui.gamesetup.aidifficulty");
let aiBehavior = Engine.ConfigDB_GetValue("user", "gui.gamesetup.aibehavior");
// Don't change the underlying defaults file, as Atlas uses that file too
for (let i in g_DefaultPlayerData)
@ -1023,6 +1025,7 @@ function initDefaults()
g_DefaultPlayerData[i].Civ = "random";
g_DefaultPlayerData[i].Team = -1;
g_DefaultPlayerData[i].AIDiff = aiDifficulty;
g_DefaultPlayerData[i].AIBehavior = aiBehavior;
}
deepfreeze(g_DefaultPlayerData);
@ -2105,7 +2108,8 @@ function openAIConfig(playerSlot)
"isController": g_IsController,
"playerSlot": playerSlot,
"id": g_GameAttributes.settings.PlayerData[playerSlot].AI,
"difficulty": g_GameAttributes.settings.PlayerData[playerSlot].AIDiff
"difficulty": g_GameAttributes.settings.PlayerData[playerSlot].AIDiff,
"behavior": g_GameAttributes.settings.PlayerData[playerSlot].AIBehavior
});
}
@ -2121,6 +2125,7 @@ function AIConfigCallback(ai)
g_GameAttributes.settings.PlayerData[ai.playerSlot].AI = ai.id;
g_GameAttributes.settings.PlayerData[ai.playerSlot].AIDiff = ai.difficulty;
g_GameAttributes.settings.PlayerData[ai.playerSlot].AIBehavior = ai.behavior;
updateGameAttributes();
}
@ -2180,6 +2185,7 @@ function swapPlayers(guidToSwap, newSlot)
// Transfer the AI from the target slot to the current slot.
g_GameAttributes.settings.PlayerData[playerID - 1].AI = g_GameAttributes.settings.PlayerData[newSlot].AI;
g_GameAttributes.settings.PlayerData[playerID - 1].AIDiff = g_GameAttributes.settings.PlayerData[newSlot].AIDiff;
g_GameAttributes.settings.PlayerData[playerID - 1].AIBehavior = g_GameAttributes.settings.PlayerData[newSlot].AIBehavior;
// Swap civilizations and colors if they aren't fixed
if (g_GameAttributes.mapType != "scenario")

View File

@ -369,7 +369,7 @@
},
{
"type": "dropdown",
"label": "Default AI difficulty",
"label": "Default AI Difficulty",
"tooltip": "Default difficulty of the AI.",
"config": "gui.gamesetup.aidifficulty",
"list": [
@ -381,6 +381,18 @@
{ "value": 5, "label": "Very Hard" }
]
},
{
"type": "dropdown",
"label": "Default AI Behavior",
"tooltip": "Default behavior of the AI.",
"config": "gui.gamesetup.aibehavior",
"list": [
{ "value": "random", "label": "Random" },
{ "value": "balanced", "label": "Balanced" },
{ "value": "aggressive", "label": "Aggressive" },
{ "value": "defensive", "label": "Defensive" }
]
},
{
"type": "dropdown",
"label": "Assign Players",

View File

@ -17,7 +17,7 @@ m.PetraBot = function PetraBot(settings)
"transports": 1 // transport plans start at 1 because 0 might be used as none
};
this.Config = new m.Config(settings.difficulty);
this.Config = new m.Config(settings.difficulty, settings.behavior);
this.savedEvents = {};
};

View File

@ -30,17 +30,17 @@ m.AttackManager.prototype.init = function(gameState)
m.AttackManager.prototype.setRushes = function(allowed)
{
if (this.Config.personality.aggressive > 0.8 && allowed > 2)
if (this.Config.personality.aggressive > this.Config.personalityCut.strong && allowed > 2)
{
this.maxRushes = 3;
this.rushSize = [ 16, 20, 24 ];
}
else if (this.Config.personality.aggressive > 0.6 && allowed > 1)
else if (this.Config.personality.aggressive > this.Config.personalityCut.medium && allowed > 1)
{
this.maxRushes = 2;
this.rushSize = [ 18, 22 ];
}
else if (this.Config.personality.aggressive > 0.3 && allowed > 0)
else if (this.Config.personality.aggressive > this.Config.personalityCut.weak && allowed > 0)
{
this.maxRushes = 1;
this.rushSize = [ 20 ];

View File

@ -1,11 +1,14 @@
var PETRA = function(m)
{
m.Config = function(difficulty)
m.Config = function(difficulty, behavior)
{
// 0 is sandbox, 1 is very easy, 2 is easy, 3 is medium, 4 is hard and 5 is very hard.
this.difficulty = difficulty !== undefined ? difficulty : 3;
// for instance "balanced", "aggressive" or "defensive"
this.behavior = behavior || "random";
// debug level: 0=none, 1=sanity checks, 2=debug, 3=detailed debug, -100=serializatio debug
this.debug = 0;
@ -88,6 +91,7 @@ m.Config = function(difficulty)
"emergency": 1000 // used only in emergency situations, should be the highest one
};
// Default personality (will be updated in setConfig)
this.personality =
{
"aggressive": 0.5,
@ -121,18 +125,40 @@ m.Config = function(difficulty)
m.Config.prototype.setConfig = function(gameState)
{
// initialize personality traits
if (this.difficulty > 1)
if (this.difficulty > 0)
{
this.personality.aggressive = randFloat(0, 1);
this.personality.cooperative = randFloat(0, 1);
this.personality.defensive = randFloat(0, 1);
}
else
{
this.personality.aggressive = 0.1;
this.personality.cooperative = 0.9;
// Setup personality traits according to the user choice:
// The parameter used to define the personality is basically the aggressivity or (1-defensiveness)
// as they are anticorrelated, although some small smearing to decorelate them will be added.
// And for each user choice, this parameter can vary between min and max
let personalityList = {
"random": { "min": 0, "max": 1 },
"defensive": { "min": 0, "max": 0.27 },
"balanced": { "min": 0.37, "max": 0.63 },
"aggressive": { "min": 0.73, "max": 1 }
};
let behavior = randFloat(-0.5, 0.5);
// make agressive and defensive quite anticorrelated (aggressive ~ 1 - defensive) but not completelety
let variation = 0.15 * randFloat(-1, 1) * Math.sqrt(Math.square(0.5) - Math.square(behavior));
let aggressive = Math.max(Math.min(behavior + variation, 0.5), -0.5) + 0.5;
let defensive = Math.max(Math.min(-behavior + variation, 0.5), -0.5) + 0.5;
let min = personalityList[this.behavior].min;
let max = personalityList[this.behavior].max;
this.personality = {
"aggressive": min + aggressive * (max - min),
"defensive": 1 - max + defensive * (max - min),
"cooperative": randFloat(0, 1)
};
}
// Petra usually uses the continuous values of personality.aggressive and personality.defensive
// to define its behavior according to personality. But when discontinuous behavior is needed,
// it uses the following personalityCut which should be set such that:
// behavior="aggressive" => personality.aggressive > personalityCut.strong &&
// personality.defensive < personalityCut.weak
// and inversely for behavior="defensive"
this.personalityCut = { "weak": 0.3, "medium": 0.5, "strong": 0.7 };
if (gameState.playerData.teamsLocked)
this.personality.cooperative = Math.min(1, this.personality.cooperative + 0.30);
else if (gameState.getAlliedVictory())

View File

@ -6,63 +6,72 @@
"Civ": "gaia",
"Color": { "r": 255, "g": 255, "b": 255 },
"AI": "",
"AIDiff": 3
"AIDiff": 3,
"AIBehavior": "random"
},
{
"Name": "Player 1",
"Civ": "athen",
"Color": { "r": 21, "g": 55, "b": 149 },
"AI": "",
"AIDiff": 3
"AIDiff": 3,
"AIBehavior": "random"
},
{
"Name": "Player 2",
"Civ": "cart",
"Color": { "r": 150, "g": 20, "b": 20 },
"AI": "petra",
"AIDiff": 3
"AIDiff": 3,
"AIBehavior": "random"
},
{
"Name": "Player 3",
"Civ": "gaul",
"Color": { "r": 86 , "g": 180, "b": 31 },
"AI": "petra",
"AIDiff": 3
"AIDiff": 3,
"AIBehavior": "random"
},
{
"Name": "Player 4",
"Civ": "iber",
"Color": { "r": 231, "g": 200, "b": 5 },
"AI": "petra",
"AIDiff": 3
"AIDiff": 3,
"AIBehavior": "random"
},
{
"Name": "Player 5",
"Civ": "mace",
"Color": { "r": 50, "g": 170, "b": 170 },
"AI": "petra",
"AIDiff": 3
"AIDiff": 3,
"AIBehavior": "random"
},
{
"Name": "Player 6",
"Civ": "maur",
"Color": { "r": 160, "g": 80, "b": 200 },
"AI": "petra",
"AIDiff": 3
"AIDiff": 3,
"AIBehavior": "random"
},
{
"Name": "Player 7",
"Civ": "pers",
"Color": { "r": 220, "g": 115, "b": 16 },
"AI": "petra",
"AIDiff": 3
"AIDiff": 3,
"AIBehavior": "random"
},
{
"Name": "Player 8",
"Civ": "rome",
"Color": { "r": 64, "g": 64, "b": 64 },
"AI": "petra",
"AIDiff": 3
"AIDiff": 3,
"AIBehavior": "random"
}
]
}

View File

@ -51,7 +51,7 @@ function InitGame(settings)
if (settings.PlayerData[i] && settings.PlayerData[i].AI && settings.PlayerData[i].AI != "")
{
let AIDiff = +settings.PlayerData[i].AIDiff;
cmpAIManager.AddPlayer(settings.PlayerData[i].AI, i, AIDiff);
cmpAIManager.AddPlayer(settings.PlayerData[i].AI, i, AIDiff, settings.PlayerData[i].AIBehavior || "random");
cmpPlayer.SetAI(true);
AIDiff = Math.min(AIDiff, rate.length - 1);
cmpPlayer.SetGatherRateMultiplier(rate[AIDiff]);

View File

@ -1367,6 +1367,7 @@ bool Autostart(const CmdLineArgs& args)
CStr name = aiArgs[i].AfterFirst(":");
scriptInterface.SetProperty(player, "AI", std::string(name));
scriptInterface.SetProperty(player, "AIDiff", 3);
scriptInterface.SetProperty(player, "AIBehavior", std::string("balanced"));
scriptInterface.SetPropertyInt(playerData, playerID-offset, player);
}
}

View File

@ -82,9 +82,9 @@ private:
{
NONCOPYABLE(CAIPlayer);
public:
CAIPlayer(CAIWorker& worker, const std::wstring& aiName, player_id_t player, u8 difficulty,
CAIPlayer(CAIWorker& worker, const std::wstring& aiName, player_id_t player, u8 difficulty, const std::wstring& behavior,
shared_ptr<ScriptInterface> scriptInterface) :
m_Worker(worker), m_AIName(aiName), m_Player(player), m_Difficulty(difficulty),
m_Worker(worker), m_AIName(aiName), m_Player(player), m_Difficulty(difficulty), m_Behavior(behavior),
m_ScriptInterface(scriptInterface), m_Obj(scriptInterface->GetJSRuntime())
{
}
@ -148,6 +148,8 @@ private:
m_ScriptInterface->Eval(L"({})", &settings);
m_ScriptInterface->SetProperty(settings, "player", m_Player, false);
m_ScriptInterface->SetProperty(settings, "difficulty", m_Difficulty, false);
m_ScriptInterface->SetProperty(settings, "behavior", m_Behavior, false);
if (!m_UseSharedComponent)
{
ENSURE(m_Worker.m_HasLoadedEntityTemplates);
@ -188,6 +190,7 @@ private:
std::wstring m_AIName;
player_id_t m_Player;
u8 m_Difficulty;
std::wstring m_Behavior;
bool m_UseSharedComponent;
// Take care to keep this declaration before heap rooted members. Destructors of heap rooted
@ -477,9 +480,9 @@ public:
return true;
}
bool AddPlayer(const std::wstring& aiName, player_id_t player, u8 difficulty)
bool AddPlayer(const std::wstring& aiName, player_id_t player, u8 difficulty, const std::wstring& behavior)
{
shared_ptr<CAIPlayer> ai(new CAIPlayer(*this, aiName, player, difficulty, m_ScriptInterface));
shared_ptr<CAIPlayer> ai(new CAIPlayer(*this, aiName, player, difficulty, behavior, m_ScriptInterface));
if (!ai->Initialise())
return false;
@ -495,7 +498,7 @@ public:
bool RunGamestateInit(const shared_ptr<ScriptInterface::StructuredClone>& gameState, const Grid<NavcellData>& passabilityMap, const Grid<u8>& territoryMap,
const std::map<std::string, pass_class_t>& nonPathfindingPassClassMasks, const std::map<std::string, pass_class_t>& pathfindingPassClassMasks)
{
// this will be run last by InitGame.Js, passing the full game representation.
// this will be run last by InitGame.js, passing the full game representation.
// For now it will run for the shared Component.
// This is NOT run during deserialization.
JSContext* cx = m_ScriptInterface->GetContext();
@ -692,6 +695,7 @@ public:
serializer.String("name", m_Players[i]->m_AIName, 1, 256);
serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player);
serializer.NumberU8_Unbounded("difficulty", m_Players[i]->m_Difficulty);
serializer.String("behavior", m_Players[i]->m_Behavior, 1, 256);
serializer.NumberU32_Unbounded("num commands", (u32)m_Players[i]->m_Commands.size());
for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j)
@ -763,10 +767,12 @@ public:
std::wstring name;
player_id_t player;
u8 difficulty;
std::wstring behavior;
deserializer.String("name", name, 1, 256);
deserializer.NumberI32_Unbounded("player", player);
deserializer.NumberU8_Unbounded("difficulty",difficulty);
if (!AddPlayer(name, player, difficulty))
deserializer.String("behavior", behavior, 1, 256);
if (!AddPlayer(name, player, difficulty, behavior))
throw PSERROR_Deserialize_ScriptError();
u32 numCommands;
@ -999,11 +1005,11 @@ public:
m_JustDeserialized = true;
}
virtual void AddPlayer(const std::wstring& id, player_id_t player, u8 difficulty)
virtual void AddPlayer(const std::wstring& id, player_id_t player, u8 difficulty, const std::wstring& behavior)
{
LoadUsedEntityTemplates();
m_Worker.AddPlayer(id, player, difficulty);
m_Worker.AddPlayer(id, player, difficulty, behavior);
// AI players can cheat and see through FoW/SoD, since that greatly simplifies
// their implementation.

View File

@ -25,7 +25,7 @@
#include "ps/Filesystem.h"
BEGIN_INTERFACE_WRAPPER(AIManager)
DEFINE_INTERFACE_METHOD_3("AddPlayer", void, ICmpAIManager, AddPlayer, std::wstring, player_id_t, uint8_t)
DEFINE_INTERFACE_METHOD_4("AddPlayer", void, ICmpAIManager, AddPlayer, std::wstring, player_id_t, uint8_t, std::wstring)
DEFINE_INTERFACE_METHOD_1("SetRNGSeed", void, ICmpAIManager, SetRNGSeed, uint32_t)
DEFINE_INTERFACE_METHOD_0("TryLoadSharedComponent", void, ICmpAIManager, TryLoadSharedComponent)
DEFINE_INTERFACE_METHOD_0("RunGamestateInit", void, ICmpAIManager, RunGamestateInit)

View File

@ -30,7 +30,7 @@ public:
* by @p id (corresponding to a subdirectory in simulation/ai/),
* to control player @p player.
*/
virtual void AddPlayer(const std::wstring& id, player_id_t player, uint8_t difficulty) = 0;
virtual void AddPlayer(const std::wstring& id, player_id_t player, uint8_t difficulty, const std::wstring&) = 0;
virtual void SetRNGSeed(uint32_t seed) = 0;
virtual void TryLoadSharedComponent() = 0;
virtual void RunGamestateInit() = 0;