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