Add a -autostart-nonvisual option. Patch by sacha_vrand. Fixes #4577.

This allows automated testing of AIs without any GUI or sound (similar
to non-visual replays).

Differential Revision: https://code.wildfiregames.com/D379
This was SVN commit r19645.
This commit is contained in:
leper 2017-05-23 19:26:33 +00:00
parent 1bcad4698d
commit a533fff883
11 changed files with 219 additions and 22 deletions

View File

@ -177,6 +177,7 @@
{"nick": "Riemer"},
{"name": "Rolf Sievers"},
{"nick": "s0600204", "name": "Matthew Norwood"},
{"nick": "sacha_vrand", "name": "Sacha Vrand"},
{"nick": "SafaAlfulaij"},
{"nick": "Sandarac"},
{"nick": "sanderd17", "name": "Sander Deryckere"},

View File

@ -0,0 +1,32 @@
/**
* This will print the statistics at the end of a game.
* In order for this to work, the player's state has to be changed before the event.
*/
Trigger.prototype.EndGameAction = function()
{
for (let playerId = 1; playerId < TriggerHelper.GetNumberOfPlayers(); ++playerId)
{
let cmpPlayer = QueryPlayerIDInterface(playerId);
if (cmpPlayer && cmpPlayer.GetState() === "active")
return;
}
if (!this.once)
return;
this.once = false;
for (let player of Engine.GetEntitiesWithInterface(IID_StatisticsTracker))
{
let cmpStatisticsTracker = Engine.QueryInterface(player, IID_StatisticsTracker);
if (cmpStatisticsTracker)
print(cmpStatisticsTracker.GetStatisticsJSON() + "\n");
}
};
{
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.RegisterTrigger("OnPlayerWon", "EndGameAction", { "enabled": true });
cmpTrigger.RegisterTrigger("OnPlayerDefeated", "EndGameAction", { "enabled": true });
cmpTrigger.once = true;
}

View File

@ -216,6 +216,23 @@ StatisticsTracker.prototype.GetSequences = function()
return ret;
};
/**
* Used to print statistics for non-visual autostart games.
* @return The player's statistics as a JSON string beautified with some indentations.
*/
StatisticsTracker.prototype.GetStatisticsJSON = function()
{
let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
let playerStatistics = {
"playerID": cmpPlayer.GetPlayerID(),
"playerState": cmpPlayer.GetState(),
"statistics": this.GetStatistics()
};
return JSON.stringify(playerStatistics, null, "\t");
};
/**
* Increments counter associated with certain entity/counter and type of given entity.
* @param cmpIdentity - the entity identity component.

View File

@ -13,6 +13,9 @@ Autostart:
-autostart-aiseed=AISEED sets the seed used for the AI random generator (default 0, use -1 for random)
-autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV (skirmish and random maps only)
-autostart-team=PLAYER:TEAM sets the team for PLAYER (e.g. 2:2).
-autostart-nonvisual disable any graphics and sounds
-autostart-victory=SCRIPTNAME sets the victory conditions with SCRIPTNAME located in simulation/data/settings/victory_conditions/
-autostart-victoryduration=NUM sets the victory duration NUM for specific victory conditions
Multiplayer:
-autostart-playername=NAME sets local player NAME (default 'anonymous')
-autostart-host sets multiplayer host mode

View File

@ -77,6 +77,7 @@ that of Atlas depending on commandline parameters.
#include "renderer/Renderer.h"
#include "scriptinterface/ScriptEngine.h"
#include "simulation2/Simulation2.h"
#include "simulation2/system/TurnManager.h"
#if OS_UNIX
#include <unistd.h> // geteuid
@ -380,6 +381,24 @@ static void Frame()
LimitFPS();
}
static void NonVisualFrame()
{
g_Profiler2.RecordFrameStart();
PROFILE2("frame");
g_Profiler2.IncrementFrameNumber();
PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber());
static u32 turn = 0;
debug_printf("Turn %u (%u)...\n", turn++, DEFAULT_TURN_LENGTH_SP);
g_Game->GetSimulation2()->Update(DEFAULT_TURN_LENGTH_SP);
g_Profiler.Frame();
if (g_Game->IsGameFinished())
kill_mainloop();
}
static void MainControllerInit()
{
@ -438,8 +457,15 @@ static void RunGameOrAtlas(int argc, const char* argv[])
return;
}
if (args.Has("autostart-nonvisual") && args.Get("autostart").empty())
{
LOGERROR("-autostart-nonvisual cant be used alone. A map with -autostart=\"TYPEDIR/MAPNAME\" is needed.");
return;
}
const bool isVisualReplay = args.Has("replay-visual");
const bool isNonVisualReplay = args.Has("replay");
const bool isNonVisual = args.Has("autostart-nonvisual");
const CStr replayFile =
isVisualReplay ? args.Get("replay-visual") :
@ -540,10 +566,21 @@ static void RunGameOrAtlas(int argc, const char* argv[])
Shutdown(SHUTDOWN_FROM_CONFIG);
continue;
}
InitGraphics(args, 0);
MainControllerInit();
while (!quit)
Frame();
if (isNonVisual)
{
InitNonVisual(args);
while (!quit)
NonVisualFrame();
}
else
{
InitGraphics(args, 0);
MainControllerInit();
while (!quit)
Frame();
}
Shutdown(0);
MainControllerShutdown();
flags &= ~INIT_MODS;

View File

@ -461,3 +461,15 @@ CColor CGame::GetPlayerColor(player_id_t player) const
return m_PlayerColors[player];
}
bool CGame::IsGameFinished() const
{
for (const std::pair<entity_id_t, IComponent*>& p : m_Simulation2->GetEntitiesWithInterface(IID_Player))
{
CmpPtr<ICmpPlayer> cmpPlayer(*m_Simulation2, p.first);
if (cmpPlayer && cmpPlayer->GetState() == "won")
return true;
}
return false;
}

View File

@ -112,6 +112,14 @@ public:
int GetViewedPlayerID();
void SetViewedPlayerID(player_id_t playerID);
/**
* Check if the game is finished by testing if there's a winner.
* It is used to end a non visual autostarted game.
*
* @return true if there's a winner, false otherwise.
*/
bool IsGameFinished() const;
/**
* Retrieving player colors from scripts is slow, so this updates an
* internal cache of all players' colors.
@ -132,6 +140,16 @@ public:
return m_GameStarted;
}
/**
* Get if the graphics is disabled.
*
* @return bool true if the m_GameView is NULL, false otherwise.
*/
inline bool IsGraphicsDisabled() const
{
return !m_GameView;
}
/**
* Get m_IsVisualReplay.
*

View File

@ -85,6 +85,7 @@
#include "renderer/ModelRenderer.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptStats.h"
#include "scriptinterface/ScriptConversions.h"
#include "simulation2/Simulation2.h"
#include "lobby/IXmppClient.h"
#include "soundmanager/scripting/JSInterface_Sound.h"
@ -711,22 +712,27 @@ static void ShutdownSDL()
void EndGame()
{
const bool nonVisual = g_Game && g_Game->IsGraphicsDisabled();
if (g_Game && g_Game->IsGameStarted() && !g_Game->IsVisualReplay() &&
g_AtlasGameLoop && !g_AtlasGameLoop->running)
g_AtlasGameLoop && !g_AtlasGameLoop->running && !nonVisual)
VisualReplay::SaveReplayMetadata(g_GUI->GetActiveGUI()->GetScriptInterface().get());
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_NetServer);
SAFE_DELETE(g_Game);
ISoundManager::CloseGame();
g_Renderer.ResetState();
if (!nonVisual)
{
ISoundManager::CloseGame();
g_Renderer.ResetState();
}
}
void Shutdown(int flags)
{
const bool nonVisual = g_Game && g_Game->IsGraphicsDisabled();
if ((flags & SHUTDOWN_FROM_CONFIG))
goto from_config;
@ -740,11 +746,14 @@ void Shutdown(int flags)
delete &g_TexMan;
TIMER_END(L"shutdown TexMan");
// destroy renderer
TIMER_BEGIN(L"shutdown Renderer");
delete &g_Renderer;
g_VBMan.Shutdown();
TIMER_END(L"shutdown Renderer");
// destroy renderer if it was initialised
if (!nonVisual)
{
TIMER_BEGIN(L"shutdown Renderer");
delete &g_Renderer;
g_VBMan.Shutdown();
TIMER_END(L"shutdown Renderer");
}
g_Profiler2.ShutdownGPU();
@ -761,7 +770,8 @@ void Shutdown(int flags)
ShutdownSDL();
TIMER_END(L"shutdown SDL");
g_VideoMode.Shutdown();
if (!nonVisual)
g_VideoMode.Shutdown();
TIMER_BEGIN(L"shutdown UserReporter");
g_UserReporter.Deinitialize();
@ -964,7 +974,8 @@ bool Init(const CmdLineArgs& args, int flags)
CNetHost::Initialize();
#if CONFIG2_AUDIO
ISoundManager::CreateSoundManager();
if (!args.Has("autostart-nonvisual"))
ISoundManager::CreateSoundManager();
#endif
// Check if there are mods specified on the command line,
@ -1125,6 +1136,15 @@ void InitGraphics(const CmdLineArgs& args, int flags)
}
}
void InitNonVisual(const CmdLineArgs& args)
{
// Need some stuff for terrain movement costs:
// (TODO: this ought to be independent of any graphics code)
new CTerrainTextureManager;
g_TexMan.LoadTerrainTextures();
Autostart(args);
}
void RenderGui(bool RenderingState)
{
g_DoRenderGui = RenderingState;
@ -1199,6 +1219,10 @@ CStr8 LoadSettingsOfScenarioMap(const VfsPath &mapPath)
* -autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV
* (skirmish and random maps only)
* -autostart-team=PLAYER:TEAM sets the team for PLAYER (e.g. 2:2).
* -autostart-nonvisual disable any graphics and sounds
* -autostart-victory=SCRIPTNAME sets the victory conditions with SCRIPTNAME
* located in simulation/data/settings/victory_conditions/
* -autostart-victoryduration=NUM sets the victory duration NUM for specific victory conditions
*
* Multiplayer:
* -autostart-playername=NAME sets local player NAME (default 'anonymous')
@ -1228,7 +1252,8 @@ bool Autostart(const CmdLineArgs& args)
if (autoStartName.empty())
return false;
g_Game = new CGame();
const bool nonVisual = args.Has("autostart-nonvisual");
g_Game = new CGame(nonVisual, !nonVisual);
ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
JSContext* cx = scriptInterface.GetContext();
@ -1488,6 +1513,47 @@ bool Autostart(const CmdLineArgs& args)
if (args.Has("autostart-playername"))
userName = args.Get("autostart-playername").FromUTF8();
// Add additional scripts to the TriggerScripts property
std::vector<CStrW> triggerScriptsVector;
JS::RootedValue triggerScripts(cx);
if (scriptInterface.HasProperty(settings, "TriggerScripts"))
{
scriptInterface.GetProperty(settings, "TriggerScripts", &triggerScripts);
FromJSVal_vector(cx, triggerScripts, triggerScriptsVector);
}
if (nonVisual)
{
CStr nonVisualScript = "scripts/NonVisualTrigger.js";
triggerScriptsVector.push_back(nonVisualScript.FromUTF8());
}
if (args.Has("autostart-victory"))
{
CStrW scriptName = args.Get("autostart-victory").FromUTF8();
CStrW scriptPath = L"simulation/data/settings/victory_conditions/" + scriptName + L".json";
JS::RootedValue scriptData(cx);
JS::RootedValue data(cx);
JS::RootedValue victoryScripts(cx);
scriptInterface.ReadJSONFile(scriptPath, &scriptData);
if (!scriptData.isUndefined() && scriptInterface.GetProperty(scriptData, "Data", &data) && !data.isUndefined()
&& scriptInterface.GetProperty(data, "Scripts", &victoryScripts) && !victoryScripts.isUndefined())
{
std::vector<CStrW> victoryScriptsVector;
FromJSVal_vector(cx, victoryScripts, victoryScriptsVector);
triggerScriptsVector.insert(triggerScriptsVector.end(), victoryScriptsVector.begin(), victoryScriptsVector.end());
}
}
ToJSVal_vector(cx, &triggerScripts, triggerScriptsVector);
scriptInterface.SetProperty(settings, "TriggerScripts", triggerScripts);
if (args.Has("autostart-victoryduration"))
scriptInterface.SetProperty(settings, "VictoryDuration", args.Get("autostart-victoryduration").ToInt());
if (args.Has("autostart-host"))
{
InitPs(true, L"page_loading.xml", &scriptInterface, mpInitData);
@ -1531,6 +1597,9 @@ bool Autostart(const CmdLineArgs& args)
PSRETURN ret = g_Game->ReallyStartGame();
ENSURE(ret == PSRETURN_OK);
if (nonVisual)
return true;
InitPs(true, L"page_session.xml", NULL, JS::UndefinedHandleValue);
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2014 Wildfire Games.
/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -86,6 +86,7 @@ extern void MountMods(const Paths& paths, const std::vector<CStr>& mods);
*/
extern bool Init(const CmdLineArgs& args, int flags);
extern void InitGraphics(const CmdLineArgs& args, int flags);
extern void InitNonVisual(const CmdLineArgs& args);
extern void Shutdown(int flags);
extern void CancelLoad(const CStrW& message);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -57,6 +57,11 @@ public:
{
return m_Script.Call<bool>("HasStartingCamera");
}
virtual std::string GetState()
{
return m_Script.Call<std::string>("GetState");
}
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(PlayerScripted)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -25,8 +25,9 @@ class CFixedVector3D;
/**
* Player data.
* (This interface only includes the functions needed by native code for loading maps,
* and for minimap rendering; most player interaction is handled by scripts instead.)
* (This interface includes the functions needed by native code for loading maps,
* and for minimap rendering; most player interaction is handled by scripts instead.
* Also includes some functions needed for the non visual autostart.)
*/
class ICmpPlayer : public IComponent
{
@ -37,6 +38,7 @@ public:
virtual CFixedVector3D GetStartingCameraRot() = 0;
virtual bool HasStartingCamera() = 0;
virtual std::string GetState() = 0;
DECLARE_INTERFACE_TYPE(Player)
};