1 /* Copyright (C) 2018 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "Game.h"
21 
22 #include "graphics/GameView.h"
23 #include "graphics/LOSTexture.h"
24 #include "graphics/ParticleManager.h"
25 #include "graphics/UnitManager.h"
26 #include "gui/GUIManager.h"
27 #include "gui/CGUI.h"
28 #include "lib/config2.h"
29 #include "lib/timer.h"
30 #include "network/NetClient.h"
31 #include "network/NetServer.h"
32 #include "ps/CConsole.h"
33 #include "ps/CLogger.h"
34 #include "ps/CStr.h"
35 #include "ps/Loader.h"
36 #include "ps/LoaderThunks.h"
37 #include "ps/Profile.h"
38 #include "ps/Replay.h"
39 #include "ps/Shapes.h"
40 #include "ps/World.h"
41 #include "ps/GameSetup/GameSetup.h"
42 #include "renderer/Renderer.h"
43 #include "renderer/TimeManager.h"
44 #include "renderer/WaterManager.h"
45 #include "scriptinterface/ScriptInterface.h"
46 #include "simulation2/Simulation2.h"
47 #include "simulation2/components/ICmpPlayer.h"
48 #include "simulation2/components/ICmpPlayerManager.h"
49 #include "simulation2/system/ReplayTurnManager.h"
50 #include "soundmanager/ISoundManager.h"
51 
52 #include "tools/atlas/GameInterface/GameLoop.h"
53 
54 extern bool g_GameRestarted;
55 extern GameLoopState* g_AtlasGameLoop;
56 
57 /**
58  * Globally accessible pointer to the CGame object.
59  **/
60 CGame *g_Game=NULL;
61 
62 /**
63  * Constructor
64  *
65  **/
CGame(bool disableGraphics,bool replayLog)66 CGame::CGame(bool disableGraphics, bool replayLog):
67 	m_World(new CWorld(this)),
68 	m_Simulation2(new CSimulation2(&m_World->GetUnitManager(), g_ScriptRuntime, m_World->GetTerrain())),
69 	m_GameView(disableGraphics ? NULL : new CGameView(this)),
70 	m_GameStarted(false),
71 	m_Paused(false),
72 	m_SimRate(1.0f),
73 	m_PlayerID(-1),
74 	m_ViewedPlayerID(-1),
75 	m_IsSavedGame(false),
76 	m_IsVisualReplay(false),
77 	m_ReplayStream(NULL)
78 {
79 	// TODO: should use CDummyReplayLogger unless activated by cmd-line arg, perhaps?
80 	if (replayLog)
81 		m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());
82 	else
83 		m_ReplayLogger = new CDummyReplayLogger();
84 
85 	// Need to set the CObjectManager references after various objects have
86 	// been initialised, so do it here rather than via the initialisers above.
87 	if (m_GameView)
88 		m_World->GetUnitManager().SetObjectManager(m_GameView->GetObjectManager());
89 
90 	m_TurnManager = new CLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client
91 
92 	m_Simulation2->LoadDefaultScripts();
93 }
94 
95 /**
96  * Destructor
97  *
98  **/
~CGame()99 CGame::~CGame()
100 {
101 	// Again, the in-game call tree is going to be different to the main menu one.
102 	if (CProfileManager::IsInitialised())
103 		g_Profiler.StructuralReset();
104 
105 	delete m_TurnManager;
106 	delete m_GameView;
107 	delete m_Simulation2;
108 	delete m_World;
109 	delete m_ReplayLogger;
110 	delete m_ReplayStream;
111 }
112 
SetTurnManager(CTurnManager * turnManager)113 void CGame::SetTurnManager(CTurnManager* turnManager)
114 {
115 	if (m_TurnManager)
116 		delete m_TurnManager;
117 
118 	m_TurnManager = turnManager;
119 
120 	if (m_TurnManager)
121 		m_TurnManager->SetPlayerID(m_PlayerID);
122 }
123 
LoadVisualReplayData()124 int CGame::LoadVisualReplayData()
125 {
126 	ENSURE(m_IsVisualReplay);
127 	ENSURE(!m_ReplayPath.empty());
128 	ENSURE(m_ReplayStream);
129 
130 	CReplayTurnManager* replayTurnMgr = static_cast<CReplayTurnManager*>(GetTurnManager());
131 
132 	u32 currentTurn = 0;
133 	std::string type;
134 	while ((*m_ReplayStream >> type).good())
135 	{
136 		if (type == "turn")
137 		{
138 			u32 turn = 0;
139 			u32 turnLength = 0;
140 			*m_ReplayStream >> turn >> turnLength;
141 			ENSURE(turn == currentTurn && "You tried to replay a commands.txt file of a rejoined client. Please use the host's file.");
142 			replayTurnMgr->StoreReplayTurnLength(currentTurn, turnLength);
143 		}
144 		else if (type == "cmd")
145 		{
146 			player_id_t player;
147 			*m_ReplayStream >> player;
148 
149 			std::string line;
150 			std::getline(*m_ReplayStream, line);
151 			replayTurnMgr->StoreReplayCommand(currentTurn, player, line);
152 		}
153 		else if (type == "hash" || type == "hash-quick")
154 		{
155 			bool quick = (type == "hash-quick");
156 			std::string replayHash;
157 			*m_ReplayStream >> replayHash;
158 			replayTurnMgr->StoreReplayHash(currentTurn, replayHash, quick);
159 		}
160 		else if (type == "end")
161 			++currentTurn;
162 		else
163 			CancelLoad(L"Failed to load replay data (unrecognized content)");
164 	}
165 	SAFE_DELETE(m_ReplayStream);
166 	m_FinalReplayTurn = currentTurn > 0 ? currentTurn - 1 : 0;
167 	replayTurnMgr->StoreFinalReplayTurn(m_FinalReplayTurn);
168 	return 0;
169 }
170 
StartVisualReplay(const OsPath & replayPath)171 bool CGame::StartVisualReplay(const OsPath& replayPath)
172 {
173 	debug_printf("Starting to replay %s\n", replayPath.string8().c_str());
174 
175 	m_IsVisualReplay = true;
176 
177 	SetTurnManager(new CReplayTurnManager(*m_Simulation2, GetReplayLogger()));
178 
179 	m_ReplayPath = replayPath;
180 	m_ReplayStream = new std::ifstream(OsString(replayPath).c_str());
181 
182 	std::string type;
183 	ENSURE((*m_ReplayStream >> type).good() && type == "start");
184 
185 	std::string line;
186 	std::getline(*m_ReplayStream, line);
187 
188 	const ScriptInterface& scriptInterface = m_Simulation2->GetScriptInterface();
189 	JSContext* cx = scriptInterface.GetContext();
190 	JSAutoRequest rq(cx);
191 
192 	JS::RootedValue attribs(cx);
193 	scriptInterface.ParseJSON(line, &attribs);
194 	StartGame(&attribs, "");
195 
196 	return true;
197 }
198 
199 /**
200  * Initializes the game with the set of attributes provided.
201  * Makes calls to initialize the game view, world, and simulation objects.
202  * Calls are made to facilitate progress reporting of the initialization.
203  **/
RegisterInit(const JS::HandleValue attribs,const std::string & savedState)204 void CGame::RegisterInit(const JS::HandleValue attribs, const std::string& savedState)
205 {
206 	const ScriptInterface& scriptInterface = m_Simulation2->GetScriptInterface();
207 	JSContext* cx = scriptInterface.GetContext();
208 	JSAutoRequest rq(cx);
209 
210 	m_InitialSavedState = savedState;
211 	m_IsSavedGame = !savedState.empty();
212 
213 	m_Simulation2->SetInitAttributes(attribs);
214 
215 	std::string mapType;
216 	scriptInterface.GetProperty(attribs, "mapType", mapType);
217 
218 	float speed;
219 	if (scriptInterface.HasProperty(attribs, "gameSpeed") && scriptInterface.GetProperty(attribs, "gameSpeed", speed))
220 		SetSimRate(speed);
221 
222 	LDR_BeginRegistering();
223 
224 	RegMemFun(m_Simulation2, &CSimulation2::ProgressiveLoad, L"Simulation init", 1000);
225 
226 	// RC, 040804 - GameView needs to be initialized before World, otherwise GameView initialization
227 	// overwrites anything stored in the map file that gets loaded by CWorld::Initialize with default
228 	// values.  At the minute, it's just lighting settings, but could be extended to store camera position.
229 	// Storing lighting settings in the game view seems a little odd, but it's no big deal; maybe move it at
230 	// some point to be stored in the world object?
231 	if (m_GameView)
232 		m_GameView->RegisterInit();
233 
234 	if (mapType == "random")
235 	{
236 		// Load random map attributes
237 		std::wstring scriptFile;
238 		JS::RootedValue settings(cx);
239 
240 		scriptInterface.GetProperty(attribs, "script", scriptFile);
241 		scriptInterface.GetProperty(attribs, "settings", &settings);
242 
243 		m_World->RegisterInitRMS(scriptFile, scriptInterface.GetJSRuntime(), settings, m_PlayerID);
244 	}
245 	else
246 	{
247 		std::wstring mapFile;
248 		JS::RootedValue settings(cx);
249 		scriptInterface.GetProperty(attribs, "map", mapFile);
250 		scriptInterface.GetProperty(attribs, "settings", &settings);
251 
252 		m_World->RegisterInit(mapFile, scriptInterface.GetJSRuntime(), settings, m_PlayerID);
253 	}
254 	if (m_GameView)
255 		RegMemFun(g_Renderer.GetSingletonPtr()->GetWaterManager(), &WaterManager::LoadWaterTextures, L"LoadWaterTextures", 80);
256 
257 	if (m_IsSavedGame)
258 		RegMemFun(this, &CGame::LoadInitialState, L"Loading game", 1000);
259 
260 	if (m_IsVisualReplay)
261 		RegMemFun(this, &CGame::LoadVisualReplayData, L"Loading visual replay data", 1000);
262 
263 	LDR_EndRegistering();
264 }
265 
LoadInitialState()266 int CGame::LoadInitialState()
267 {
268 	ENSURE(m_IsSavedGame);
269 	ENSURE(!m_InitialSavedState.empty());
270 
271 	std::string state;
272 	m_InitialSavedState.swap(state); // deletes the original to save a bit of memory
273 
274 	std::stringstream stream(state);
275 
276 	bool ok = m_Simulation2->DeserializeState(stream);
277 	if (!ok)
278 	{
279 		CancelLoad(L"Failed to load saved game state. It might have been\nsaved with an incompatible version of the game.");
280 		return 0;
281 	}
282 
283 	return 0;
284 }
285 
286 /**
287  * Game initialization has been completed. Set game started flag and start the session.
288  *
289  * @return PSRETURN 0
290  **/
ReallyStartGame()291 PSRETURN CGame::ReallyStartGame()
292 {
293 	JSContext* cx = m_Simulation2->GetScriptInterface().GetContext();
294 	JSAutoRequest rq(cx);
295 
296 	// Call the script function InitGame only for new games, not saved games
297 	if (!m_IsSavedGame)
298 	{
299 		// Perform some simulation initializations (replace skirmish entities, explore territories, etc.)
300 		// that needs to be done before setting up the AI and shouldn't be done in Atlas
301 		if (!g_AtlasGameLoop->running)
302 			m_Simulation2->PreInitGame();
303 
304 		m_Simulation2->InitGame();
305 	}
306 
307 	// We need to do an initial Interpolate call to set up all the models etc,
308 	// because Update might never interpolate (e.g. if the game starts paused)
309 	// and we could end up rendering before having set up any models (so they'd
310 	// all be invisible)
311 	Interpolate(0, 0);
312 
313 	m_GameStarted=true;
314 
315 	// Render a frame to begin loading assets
316 	if (CRenderer::IsInitialised())
317 		Render();
318 
319 	if (g_NetClient)
320 		g_NetClient->LoadFinished();
321 
322 	// Call the reallyStartGame GUI function, but only if it exists
323 	if (g_GUI && g_GUI->HasPages())
324 	{
325 		JS::RootedValue global(cx, g_GUI->GetActiveGUI()->GetGlobalObject());
326 		if (g_GUI->GetActiveGUI()->GetScriptInterface()->HasProperty(global, "reallyStartGame"))
327 			g_GUI->GetActiveGUI()->GetScriptInterface()->CallFunctionVoid(global, "reallyStartGame");
328 	}
329 
330 	debug_printf("GAME STARTED, ALL INIT COMPLETE\n");
331 
332 	// The call tree we've built for pregame probably isn't useful in-game.
333 	if (CProfileManager::IsInitialised())
334 		g_Profiler.StructuralReset();
335 
336 	g_GameRestarted = true;
337 
338 	return 0;
339 }
340 
GetPlayerID()341 int CGame::GetPlayerID()
342 {
343 	return m_PlayerID;
344 }
345 
SetPlayerID(player_id_t playerID)346 void CGame::SetPlayerID(player_id_t playerID)
347 {
348 	m_PlayerID = playerID;
349 	m_ViewedPlayerID = playerID;
350 
351 	if (m_TurnManager)
352 		m_TurnManager->SetPlayerID(m_PlayerID);
353 }
354 
GetViewedPlayerID()355 int CGame::GetViewedPlayerID()
356 {
357 	return m_ViewedPlayerID;
358 }
359 
SetViewedPlayerID(player_id_t playerID)360 void CGame::SetViewedPlayerID(player_id_t playerID)
361 {
362 	m_ViewedPlayerID = playerID;
363 }
364 
StartGame(JS::MutableHandleValue attribs,const std::string & savedState)365 void CGame::StartGame(JS::MutableHandleValue attribs, const std::string& savedState)
366 {
367 	if (m_ReplayLogger)
368 		m_ReplayLogger->StartGame(attribs);
369 
370 	RegisterInit(attribs, savedState);
371 }
372 
373 // TODO: doInterpolate is optional because Atlas interpolates explicitly,
374 // so that it has more control over the update rate. The game might want to
375 // do the same, and then doInterpolate should be redundant and removed.
376 
Update(const double deltaRealTime,bool doInterpolate)377 void CGame::Update(const double deltaRealTime, bool doInterpolate)
378 {
379 	if (m_Paused || !m_TurnManager)
380 		return;
381 
382 	const double deltaSimTime = deltaRealTime * m_SimRate;
383 
384 	if (deltaSimTime)
385 	{
386 		// To avoid confusing the profiler, we need to trigger the new turn
387 		// while we're not nested inside any PROFILE blocks
388 		if (m_TurnManager->WillUpdate(deltaSimTime))
389 			g_Profiler.Turn();
390 
391 		// At the normal sim rate, we currently want to render at least one
392 		// frame per simulation turn, so let maxTurns be 1. But for fast-forward
393 		// sim rates we want to allow more, so it's not bounded by framerate,
394 		// so just use the sim rate itself as the number of turns per frame.
395 		size_t maxTurns = (size_t)m_SimRate;
396 
397 		if (m_TurnManager->Update(deltaSimTime, maxTurns))
398 		{
399 			{
400 				PROFILE3("gui sim update");
401 				g_GUI->SendEventToAll("SimulationUpdate");
402 			}
403 
404 			GetView()->GetLOSTexture().MakeDirty();
405 		}
406 
407 		if (CRenderer::IsInitialised())
408 			g_Renderer.GetTimeManager().Update(deltaSimTime);
409 	}
410 
411 	if (doInterpolate)
412 	{
413 		m_TurnManager->Interpolate(deltaSimTime, deltaRealTime);
414 
415 		if ( g_SoundManager )
416 			g_SoundManager->IdleTask();
417 	}
418 }
419 
Interpolate(float simFrameLength,float realFrameLength)420 void CGame::Interpolate(float simFrameLength, float realFrameLength)
421 {
422 	if (!m_TurnManager)
423 		return;
424 
425 	m_TurnManager->Interpolate(simFrameLength, realFrameLength);
426 }
427 
428 
429 static CColor BrokenColor(0.3f, 0.3f, 0.3f, 1.0f);
430 
CachePlayerColors()431 void CGame::CachePlayerColors()
432 {
433 	m_PlayerColors.clear();
434 
435 	CmpPtr<ICmpPlayerManager> cmpPlayerManager(*m_Simulation2, SYSTEM_ENTITY);
436 	if (!cmpPlayerManager)
437 		return;
438 
439 	int numPlayers = cmpPlayerManager->GetNumPlayers();
440 	m_PlayerColors.resize(numPlayers);
441 
442 	for (int i = 0; i < numPlayers; ++i)
443 	{
444 		CmpPtr<ICmpPlayer> cmpPlayer(*m_Simulation2, cmpPlayerManager->GetPlayerByID(i));
445 		if (!cmpPlayer)
446 			m_PlayerColors[i] = BrokenColor;
447 		else
448 			m_PlayerColors[i] = cmpPlayer->GetDisplayedColor();
449 	}
450 }
451 
452 
GetPlayerColor(player_id_t player) const453 CColor CGame::GetPlayerColor(player_id_t player) const
454 {
455 	if (player < 0 || player >= (int)m_PlayerColors.size())
456 		return BrokenColor;
457 
458 	return m_PlayerColors[player];
459 }
460 
IsGameFinished() const461 bool CGame::IsGameFinished() const
462 {
463 	for (const std::pair<entity_id_t, IComponent*>& p : m_Simulation2->GetEntitiesWithInterface(IID_Player))
464 	{
465 		CmpPtr<ICmpPlayer> cmpPlayer(*m_Simulation2, p.first);
466 		if (cmpPlayer && cmpPlayer->GetState() == "won")
467 			return true;
468 	}
469 
470 	return false;
471 }
472