1
0
forked from 0ad/0ad

Revert 9c180f660f until there is someone maintaining AI behaviors, refs D746.

This was SVN commit r20654.
This commit is contained in:
elexis 2017-12-14 13:31:00 +00:00
parent 198c702e6a
commit 571562d7d7
16 changed files with 55 additions and 152 deletions

View File

@ -348,7 +348,6 @@ 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, generalist, 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,11 +1,6 @@
var g_AIBehaviorList = [{
"Name": "random",
"Title": translateWithContext("AI Behavior", "Random")
}].concat(g_Settings.AIBehaviors);
var g_PlayerSlot;
var g_AIDescriptions = [{
const g_AIDescriptions = [{
"id": "",
"data": {
"name": translateWithContext("ai", "None"),
@ -13,37 +8,28 @@ var 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_AIBehaviorList).Title,
"selected": settings => g_AIBehaviorList.findIndex(b => b.Name == settings.behavior)
}
};
function init(settings)
{
// Remember the player ID that we change the AI settings for
g_PlayerSlot = settings.playerSlot;
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 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;
let label = Engine.GetGUIObjectByName(name + "Text");
label.caption = control.list[control.selected];
label.hidden = settings.isController;
}
let aiSelectionText = Engine.GetGUIObjectByName("aiSelectionText");
aiSelectionText.caption = aiSelection.list[aiSelection.selected];
aiSelectionText.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;
}
function selectAI(idx)
@ -62,7 +48,6 @@ function returnAI(save = true)
"id": g_AIDescriptions[idx].id,
"name": g_AIDescriptions[idx].data.name,
"difficulty": Engine.GetGUIObjectByName("aiDifficulty").selected,
"behavior": g_AIBehaviorList[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%-170 50%+230 50%+170">
<object type="image" style="ModernDialog" size="50%-230 50%-150 50%+230 50%+150">
<object style="ModernLabelText" type="text" size="50%-128 -18 50%+128 14">
<translatableAttribute id="caption">AI Configuration</translatableAttribute>
@ -31,17 +31,9 @@
<object name="aiDifficulty" type="dropdown" style="ModernDropDown" size="50%-24 35 50%+136 63">
</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>
<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%-30"/>
<object name="aiDescription" type="text" style="ModernLabelText" size="8% 90 92% 100%-60"/>
<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 %(AIbehavior)s %(AIname)s)");
playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)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 %(AIbehavior)s %(AIname)s, %(state)s)");
playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)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 %(AIbehavior)s %(AIname)s)");
playerDescription = translate("%(playerName)s (%(AIdifficulty)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 %(AIbehavior)s %(AIname)s, %(state)s)");
playerDescription = translate("%(playerName)s (%(AIdifficulty)s %(AIname)s, %(state)s)");
}
}
else
@ -174,8 +174,7 @@ function formatPlayerInfo(playerDataArray, playerStates)
translateWithContext("playerstate", "won"),
"AIname": isAI ? translateAIName(playerData.AI) : "",
"AIdifficulty": isAI ? translateAIDifficulty(playerData.AIDiff) : "",
"AIbehavior": isAI ? translateAIBehavior(playerData.AIBehavior) : ""
"AIdifficulty": isAI ? translateAIDifficulty(playerData.AIDiff) : ""
}));
}

View File

@ -37,7 +37,6 @@ function loadSettingsValues()
var settings = {
"AIDescriptions": loadAIDescriptions(),
"AIDifficulties": loadAIDifficulties(),
"AIBehaviors": loadAIBehaviors(),
"Ceasefire": loadCeasefire(),
"VictoryDurations": loadVictoryDuration(),
"GameSpeeds": loadSettingValuesFile("game_speeds.json"),
@ -139,25 +138,6 @@ function loadAIDifficulties()
];
}
function loadAIBehaviors()
{
return [
{
"Name": "generalist",
"Title": translateWithContext("aiBehavior", "Generalist"),
"Default": true
},
{
"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.
*/
@ -376,17 +356,6 @@ 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,10 +955,9 @@ var g_PlayerMiscElements = {
"onPress": (playerIdx) => function() {
openAIConfig(playerIdx);
},
"tooltip": (playerIdx) => sprintf(translate("Configure AI: %(difficulty)s %(behavior)s %(name)s."), {
"tooltip": (playerIdx) => sprintf(translate("Configure AI: %(name)s - %(difficulty)s."), {
"name": translateAIName(g_GameAttributes.settings.PlayerData[playerIdx].AI),
"difficulty": translateAIDifficulty(g_GameAttributes.settings.PlayerData[playerIdx].AIDiff),
"behavior": translateAIBehavior(g_GameAttributes.settings.PlayerData[playerIdx].AIBehavior),
"difficulty": translateAIDifficulty(g_GameAttributes.settings.PlayerData[playerIdx].AIDiff)
}),
},
};
@ -1017,7 +1016,6 @@ 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)
@ -1025,7 +1023,6 @@ 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);
@ -1927,13 +1924,10 @@ function launchGame()
}
g_GameAttributes.settings.PlayerData[i].Civ = chosenCiv;
// Pick a random behavior and one of the available botnames for the chosen civ
// Pick one of the available botnames for the chosen civ
if (g_GameAttributes.mapType === "scenario" || !g_GameAttributes.settings.PlayerData[i].AI)
continue;
if (g_GameAttributes.settings.PlayerData[i].AIBehavior == "random")
g_GameAttributes.settings.PlayerData[i].AIBehavior = pickRandom(g_Settings.AIBehaviors).Name;
let chosenName = pickRandom(g_CivData[chosenCiv].AINames);
if (!g_IsNetworked)
@ -2111,8 +2105,7 @@ function openAIConfig(playerSlot)
"isController": g_IsController,
"playerSlot": playerSlot,
"id": g_GameAttributes.settings.PlayerData[playerSlot].AI,
"difficulty": g_GameAttributes.settings.PlayerData[playerSlot].AIDiff,
"behavior": g_GameAttributes.settings.PlayerData[playerSlot].AIBehavior
"difficulty": g_GameAttributes.settings.PlayerData[playerSlot].AIDiff
});
}
@ -2128,7 +2121,6 @@ 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();
}
@ -2188,7 +2180,6 @@ 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,18 +381,6 @@
{ "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": "Generalist", "label": "Generalist" },
{ "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, settings.behavior);
this.Config = new m.Config(settings.difficulty);
this.savedEvents = {};
};

View File

@ -318,7 +318,7 @@ m.AttackManager.prototype.update = function(gameState, queues, events)
// creating plans after updating because an aborted plan might be reused in that case.
let barracksNb = gameState.getOwnEntitiesByClass("Barracks", true).filter(API3.Filters.isBuilt()).length;
if (this.rushNumber < this.maxRushes && barracksNb >= 1 && this.Config.behavior != "defensive")
if (this.rushNumber < this.maxRushes && barracksNb >= 1)
{
if (unexecutedAttacks.Rush === 0)
{
@ -338,8 +338,7 @@ m.AttackManager.prototype.update = function(gameState, queues, events)
}
else if (unexecutedAttacks.Attack == 0 && unexecutedAttacks.HugeAttack == 0 &&
this.startedAttacks.Attack.length + this.startedAttacks.HugeAttack.length < Math.min(2, 1 + Math.round(gameState.getPopulationMax()/100)) &&
(this.startedAttacks.Attack.length + this.startedAttacks.HugeAttack.length == 0 || gameState.getPopulationMax() - gameState.getPopulation() > 12) &&
(this.Config.behavior != "defensive" || gameState.ai.HQ.defenseManager.targetList.length))
(this.startedAttacks.Attack.length + this.startedAttacks.HugeAttack.length == 0 || gameState.getPopulationMax() - gameState.getPopulation() > 12))
{
if (barracksNb >= 1 && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.getPhaseName(2))) ||
!gameState.ai.HQ.baseManagers[1]) // if we have no base ... nothing else to do than attack

View File

@ -1,14 +1,11 @@
var PETRA = function(m)
{
m.Config = function(difficulty, behavior)
m.Config = function(difficulty)
{
// 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 "generalist", "aggressive" or "defensive"
this.behavior = behavior || "generalist";
// debug level: 0=none, 1=sanity checks, 2=debug, 3=detailed debug, -100=serializatio debug
this.debug = 0;
@ -127,9 +124,9 @@ m.Config.prototype.setConfig = function(gameState)
// initialize personality traits
if (this.difficulty > 1)
{
this.personality.aggressive = this.behavior === "aggressive" ? randFloat(0.7, 1) : randFloat(0, 0.6);
this.personality.aggressive = randFloat(0, 1);
this.personality.cooperative = randFloat(0, 1);
this.personality.defensive = this.behavior === "defensive" ? randFloat(0.7, 1) : randFloat(0, 0.6);
this.personality.defensive = randFloat(0, 1);
}
else
{

View File

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

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, settings.PlayerData[i].AIBehavior);
cmpAIManager.AddPlayer(settings.PlayerData[i].AI, i, AIDiff);
cmpPlayer.SetAI(true);
AIDiff = Math.min(AIDiff, rate.length - 1);
cmpPlayer.SetGatherRateMultiplier(rate[AIDiff]);

View File

@ -1367,7 +1367,6 @@ 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("generalist"));
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, const std::wstring& behavior,
CAIPlayer(CAIWorker& worker, const std::wstring& aiName, player_id_t player, u8 difficulty,
shared_ptr<ScriptInterface> scriptInterface) :
m_Worker(worker), m_AIName(aiName), m_Player(player), m_Difficulty(difficulty), m_Behavior(behavior),
m_Worker(worker), m_AIName(aiName), m_Player(player), m_Difficulty(difficulty),
m_ScriptInterface(scriptInterface), m_Obj(scriptInterface->GetJSRuntime())
{
}
@ -148,8 +148,6 @@ 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);
@ -190,7 +188,6 @@ 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
@ -480,9 +477,9 @@ public:
return true;
}
bool AddPlayer(const std::wstring& aiName, player_id_t player, u8 difficulty, const std::wstring& behavior)
bool AddPlayer(const std::wstring& aiName, player_id_t player, u8 difficulty)
{
shared_ptr<CAIPlayer> ai(new CAIPlayer(*this, aiName, player, difficulty, behavior, m_ScriptInterface));
shared_ptr<CAIPlayer> ai(new CAIPlayer(*this, aiName, player, difficulty, m_ScriptInterface));
if (!ai->Initialise())
return false;
@ -498,7 +495,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();
@ -695,7 +692,6 @@ 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)
@ -767,12 +763,10 @@ 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);
deserializer.String("behavior", behavior, 1, 256);
if (!AddPlayer(name, player, difficulty, behavior))
if (!AddPlayer(name, player, difficulty))
throw PSERROR_Deserialize_ScriptError();
u32 numCommands;
@ -1005,11 +999,11 @@ public:
m_JustDeserialized = true;
}
virtual void AddPlayer(const std::wstring& id, player_id_t player, u8 difficulty, const std::wstring& behavior)
virtual void AddPlayer(const std::wstring& id, player_id_t player, u8 difficulty)
{
LoadUsedEntityTemplates();
m_Worker.AddPlayer(id, player, difficulty, behavior);
m_Worker.AddPlayer(id, player, difficulty);
// 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_4("AddPlayer", void, ICmpAIManager, AddPlayer, std::wstring, player_id_t, uint8_t, std::wstring)
DEFINE_INTERFACE_METHOD_3("AddPlayer", void, ICmpAIManager, AddPlayer, std::wstring, player_id_t, uint8_t)
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, const std::wstring&) = 0;
virtual void AddPlayer(const std::wstring& id, player_id_t player, uint8_t difficulty) = 0;
virtual void SetRNGSeed(uint32_t seed) = 0;
virtual void TryLoadSharedComponent() = 0;
virtual void RunGamestateInit() = 0;