Index: ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json +++ ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json @@ -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"}, Index: ps/trunk/binaries/data/mods/public/maps/scripts/NonVisualTrigger.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/scripts/NonVisualTrigger.js +++ ps/trunk/binaries/data/mods/public/maps/scripts/NonVisualTrigger.js @@ -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; +} Index: ps/trunk/binaries/data/mods/public/simulation/components/StatisticsTracker.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/StatisticsTracker.js +++ ps/trunk/binaries/data/mods/public/simulation/components/StatisticsTracker.js @@ -217,6 +217,23 @@ }; /** + * 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. * @param counter - the name of the counter to increment (e.g. "unitsTrained"). Index: ps/trunk/binaries/system/readme.txt =================================================================== --- ps/trunk/binaries/system/readme.txt +++ ps/trunk/binaries/system/readme.txt @@ -13,6 +13,9 @@ -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 Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp +++ ps/trunk/source/main.cpp @@ -77,6 +77,7 @@ #include "renderer/Renderer.h" #include "scriptinterface/ScriptEngine.h" #include "simulation2/Simulation2.h" +#include "simulation2/system/TurnManager.h" #if OS_UNIX #include // geteuid @@ -380,6 +381,24 @@ 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 @@ 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 @@ 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; Index: ps/trunk/source/ps/Game.h =================================================================== --- ps/trunk/source/ps/Game.h +++ ps/trunk/source/ps/Game.h @@ -113,6 +113,14 @@ 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. * Call this just before rendering, so it will always have the latest @@ -133,6 +141,16 @@ } /** + * 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. * * @return bool the value of m_IsVisualReplay. Index: ps/trunk/source/ps/Game.cpp =================================================================== --- ps/trunk/source/ps/Game.cpp +++ ps/trunk/source/ps/Game.cpp @@ -461,3 +461,15 @@ return m_PlayerColors[player]; } + +bool CGame::IsGameFinished() const +{ + for (const std::pair& p : m_Simulation2->GetEntitiesWithInterface(IID_Player)) + { + CmpPtr cmpPlayer(*m_Simulation2, p.first); + if (cmpPlayer && cmpPlayer->GetState() == "won") + return true; + } + + return false; +} Index: ps/trunk/source/ps/GameSetup/GameSetup.h =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.h +++ ps/trunk/source/ps/GameSetup/GameSetup.h @@ -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 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); Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp @@ -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 @@ 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 @@ 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 @@ 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 @@ 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 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 @@ * -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 @@ 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 @@ if (args.Has("autostart-playername")) userName = args.Get("autostart-playername").FromUTF8(); + // Add additional scripts to the TriggerScripts property + std::vector 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 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 @@ PSRETURN ret = g_Game->ReallyStartGame(); ENSURE(ret == PSRETURN_OK); + if (nonVisual) + return true; + InitPs(true, L"page_session.xml", NULL, JS::UndefinedHandleValue); } Index: ps/trunk/source/simulation2/components/ICmpPlayer.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpPlayer.h +++ ps/trunk/source/simulation2/components/ICmpPlayer.h @@ -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 @@ /** * 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 @@ virtual CFixedVector3D GetStartingCameraRot() = 0; virtual bool HasStartingCamera() = 0; + virtual std::string GetState() = 0; DECLARE_INTERFACE_TYPE(Player) }; Index: ps/trunk/source/simulation2/components/ICmpPlayer.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpPlayer.cpp +++ ps/trunk/source/simulation2/components/ICmpPlayer.cpp @@ -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 @@ { return m_Script.Call("HasStartingCamera"); } + + virtual std::string GetState() + { + return m_Script.Call("GetState"); + } }; REGISTER_COMPONENT_SCRIPT_WRAPPER(PlayerScripted)