1 #include "GameLoop.h"
2 #include "GameVersion.h"
3 #include "Local.h"
4 #include "SGP.h"
5 #include "Screens.h"
6 #include "ShopKeeper_Interface.h"
7 #include "Tactical_Placement_GUI.h"
8 #include "Cursors.h"
9 #include "Init.h"
10 #include "Music_Control.h"
11 #include "Sys_Globals.h"
12 #include "Laptop.h"
13 #include "MapScreen.h"
14 #include "Game_Clock.h"
15 #include "Overhead.h"
16 #include "Map_Screen_Interface.h"
17 #include "Tactical_Save.h"
18 #include "Interface.h"
19 #include "GameSettings.h"
20 #include "Text.h"
21 #include "HelpScreen.h"
22 #include "SaveLoadGame.h"
23 #include "Finances.h"
24 #include "Options_Screen.h"
25 #include "Debug.h"
26 #include "Video.h"
27 #include "MemMan.h"
28 #include "Button_System.h"
29 #include "Font_Control.h"
30 #include "UILayout.h"
31 #include "GameState.h"
32 #include "sgp/FileMan.h"
33 #include "Logger.h"
34 
35 #include <string_theory/format>
36 #include <string_theory/string>
37 
38 #include <stdexcept>
39 
40 
41 ScreenID guiCurrentScreen = ERROR_SCREEN; // XXX TODO001A had no explicit initialisation
42 ScreenID guiPendingScreen = NO_PENDING_SCREEN;
43 
44 static BOOLEAN gfCheckForFreeSpaceOnHardDrive = FALSE;
45 
46 // The InitializeGame function is responsible for setting up all data and Gaming Engine
47 // tasks which will run the game
48 
InitializeGame(void)49 void InitializeGame(void)
50 {
51 	UINT32				uiIndex;
52 
53 	// Initlaize mouse subsystems
54 	MSYS_Init( );
55 	InitButtonSystem();
56 	InitCursors( );
57 
58 	// Init Fonts
59 	InitializeFonts();
60 
61 	InitTacticalSave();
62 
63 	SLOGI("Version Label: %s", g_version_label);
64 	SLOGI("Version #:     %s", g_version_number);
65 
66 	// Initialize Game Screens.
67 	for (uiIndex = 0; uiIndex < MAX_SCREENS; uiIndex++)
68 	{
69 		void (*const init)(void) = GameScreens[uiIndex].InitializeScreen;
70 		if (init) init();
71 	}
72 
73 	//Init the help screen system
74 	InitHelpScreenSystem();
75 
76 	//Loads the saved (if any) general JA2 game settings
77 	LoadGameSettings();
78 
79 	//Initialize the Game options ( Gun nut, scifi and dif. levels
80 	InitGameOptions();
81 
82 	// preload mapscreen graphics
83 	HandlePreloadOfMapGraphics( );
84 
85 	guiCurrentScreen = INIT_SCREEN;
86 }
87 
88 
89 // The ShutdownGame function will free up/undo all things that were started in InitializeGame()
90 // It will also be responsible to making sure that all Gaming Engine tasks exit properly
91 
ShutdownGame(void)92 void    ShutdownGame(void)
93 {
94 	// handle shutdown of game with respect to preloaded mapscreen graphics
95 	HandleRemovalOfPreLoadedMapGraphics( );
96 
97 	ShutdownJA2( );
98 
99 	//Save the general save game settings to disk
100 	SaveGameSettings();
101 
102 	InitTacticalSave();
103 }
104 
105 
106 static void HandleNewScreenChange(UINT32 uiNewScreen, UINT32 uiOldScreen);
107 
108 
109 // This is the main Gameloop. This should eventually by one big switch statement which represents
110 // the state of the game (i.e. Main Menu, PC Generation, Combat loop, etc....)
111 // This function exits constantly and reenters constantly
GameLoop(void)112 void GameLoop(void)
113 try
114 {
115 	InputAtom InputEvent;
116 	ScreenID uiOldScreen = guiCurrentScreen;
117 
118 	SGPPoint MousePos;
119 	GetMousePos(&MousePos);
120 	// Hook into mouse stuff for MOVEMENT MESSAGES
121 	MouseSystemHook(MOUSE_POS, MousePos.iX, MousePos.iY);
122 	MusicPoll();
123 
124 	while (DequeueSpecificEvent(&InputEvent, MOUSE_EVENTS))
125 	{
126 		MouseSystemHook(InputEvent.usEvent, MousePos.iX, MousePos.iY);
127 	}
128 
129 
130 	if ( gfGlobalError )
131 	{
132 		guiCurrentScreen = ERROR_SCREEN;
133 	}
134 
135 
136 	//if we are to check for free space on the hard drive
137 	if( gfCheckForFreeSpaceOnHardDrive )
138 	{
139 		//only if we are in a screen that can get this check
140 		if( guiCurrentScreen == MAP_SCREEN || guiCurrentScreen == GAME_SCREEN || guiCurrentScreen == SAVE_LOAD_SCREEN )
141 		{
142 			// Make sure the user has enough hard drive space
143 			uint64_t uiSpaceOnDrive = GetFreeSpaceOnHardDriveWhereGameIsRunningFrom();
144 			if( uiSpaceOnDrive < REQUIRED_FREE_SPACE )
145 			{
146 				ST::string zSpaceOnDrive = ST::format("{.2f}", uiSpaceOnDrive / (FLOAT)BYTESINMEGABYTE);
147 
148 				ST::string zText = st_format_printf(pMessageStrings[ MSG_LOWDISKSPACE_WARNING ], zSpaceOnDrive, "20");
149 
150 				if( guiPreviousOptionScreen == MAP_SCREEN )
151 					DoMapMessageBox( MSG_BOX_BASIC_STYLE, zText, MAP_SCREEN, MSG_BOX_FLAG_OK, NULL );
152 				else
153 					DoMessageBox( MSG_BOX_BASIC_STYLE, zText, GAME_SCREEN, MSG_BOX_FLAG_OK, NULL, NULL );
154 			}
155 			gfCheckForFreeSpaceOnHardDrive = FALSE;
156 		}
157 	}
158 
159 	// ATE: Force to be in message box screen!
160 	if ( gfInMsgBox )
161 	{
162 		guiPendingScreen = MSG_BOX_SCREEN;
163 	}
164 
165 	if ( guiPendingScreen != NO_PENDING_SCREEN )
166 	{
167 		// Based on active screen, deinit!
168 		if( guiPendingScreen != guiCurrentScreen )
169 		{
170 			switch( guiCurrentScreen )
171 			{
172 				case MAP_SCREEN:
173 					if( guiPendingScreen != MSG_BOX_SCREEN )
174 					{
175 						EndMapScreen( FALSE );
176 					}
177 					break;
178 				case LAPTOP_SCREEN:
179 					ExitLaptop();
180 					break;
181 				default:
182 					break;
183 			}
184 		}
185 
186 		// if the screen has chnaged
187 		if( uiOldScreen != guiPendingScreen )
188 		{
189 			// Set the fact that the screen has changed
190 			uiOldScreen = guiPendingScreen;
191 
192 			HandleNewScreenChange( guiPendingScreen, guiCurrentScreen );
193 		}
194 		guiCurrentScreen = guiPendingScreen;
195 		guiPendingScreen = NO_PENDING_SCREEN;
196 
197 	}
198 
199 
200 
201 	uiOldScreen = (*(GameScreens[guiCurrentScreen].HandleScreen))();
202 
203 	// if the screen has chnaged
204 	if( uiOldScreen != guiCurrentScreen )
205 	{
206 		HandleNewScreenChange( uiOldScreen, guiCurrentScreen );
207 		guiCurrentScreen = uiOldScreen;
208 	}
209 
210 	RefreshScreen();
211 
212 	guiGameCycleCounter++;
213 
214 	UpdateClock();
215 
216 }
217 catch (std::exception const& e)
218 {
219 	guiPreviousOptionScreen = guiCurrentScreen;
220 	char const* what;
221 	char const* success = "failed";
222 	char const* attach  = "";
223 
224 	if (gfEditMode && GameState::getInstance()->isEditorMode())
225 	{
226 		what = "map";
227 		if (SaveWorld("error.dat"))
228 		{
229 			success = "succeeded (error.dat)";
230 			attach  = " Do not forget to attach the map.";
231 		}
232 	}
233 	else
234 	{
235 		what = "savegame";
236 		if (SaveGame(SAVE__ERROR_NUM, "error savegame"))
237 		{
238 			success = "succeeded (error.sav)";
239 			attach  = " Do not forget to attach the savegame.";
240 		}
241 	}
242 	char msg[2048];
243 	snprintf(msg, lengthof(msg),
244 		"%s\n"
245 		"Creating an emergency %s %s.\n"
246 		"Please report this error with a description of the circumstances.%s",
247 		e.what(), what, success, attach
248 	);
249 	throw std::runtime_error(msg);
250 }
251 
252 
SetPendingNewScreen(ScreenID const uiNewScreen)253 void SetPendingNewScreen(ScreenID const uiNewScreen)
254 {
255 	guiPendingScreen = uiNewScreen;
256 }
257 
258 
259 // Gets called when the screen changes, place any needed in code in here
HandleNewScreenChange(UINT32 uiNewScreen,UINT32 uiOldScreen)260 static void HandleNewScreenChange(UINT32 uiNewScreen, UINT32 uiOldScreen)
261 {
262 	//if we are not going into the message box screen, and we didnt just come from it
263 	if( ( uiNewScreen != MSG_BOX_SCREEN && uiOldScreen != MSG_BOX_SCREEN ) )
264 	{
265 		//reset the help screen
266 		NewScreenSoResetHelpScreen( );
267 	}
268 }
269 
270 
HandleShortCutExitState()271 void HandleShortCutExitState()
272 {
273 	SLOGI("User pressed ESCape, TERMINATING");
274 
275 	// Use YES/NO pop up box, setup for particular screen
276 	switch (guiCurrentScreen)
277 	{
278 		case DEBUG_SCREEN:
279 		case EDIT_SCREEN:
280 		case ERROR_SCREEN:
281 			// Do not prompt if error or editor
282 			requestGameExit();
283 			break;
284 
285 		case LAPTOP_SCREEN:
286 			DoLapTopSystemMessageBox(pMessageStrings[MSG_EXITGAME], LAPTOP_SCREEN, MSG_BOX_FLAG_YESNO, EndGameMessageBoxCallBack);
287 			break;
288 
289 		case MAP_SCREEN:
290 			DoMapMessageBox(MSG_BOX_BASIC_STYLE, pMessageStrings[MSG_EXITGAME], MAP_SCREEN, MSG_BOX_FLAG_YESNO, EndGameMessageBoxCallBack);
291 			return;
292 
293 		case SHOPKEEPER_SCREEN:
294 			DoSkiMessageBox(pMessageStrings[MSG_EXITGAME], SHOPKEEPER_SCREEN, MSG_BOX_FLAG_YESNO, EndGameMessageBoxCallBack);
295 			break;
296 
297 		default:
298 		{ // set up for all otherscreens
299 			SGPBox const pCenteringRect = { 0, 0, SCREEN_WIDTH, INV_INTERFACE_START_Y };
300 			DoMessageBox(MSG_BOX_BASIC_STYLE, pMessageStrings[MSG_EXITGAME], guiCurrentScreen, MSG_BOX_FLAG_YESNO, EndGameMessageBoxCallBack, &pCenteringRect);
301 			break;
302 		}
303 	}
304 }
305 
306 
EndGameMessageBoxCallBack(MessageBoxReturnValue const bExitValue)307 void EndGameMessageBoxCallBack(MessageBoxReturnValue const bExitValue)
308 {
309 	// yes, so start over, else stay here and do nothing for now
310 	if( bExitValue == MSG_BOX_RETURN_YES )
311 	{
312 		requestGameExit();
313 	}
314 
315 	//If we are in the tactical placement gui, we need this flag set so the interface is updated.
316 	if( gfTacticalPlacementGUIActive )
317 	{
318 		gfTacticalPlacementGUIDirty = TRUE;
319 		gfValidLocationsChanged = TRUE;
320 	}
321 }
322 
323 
NextLoopCheckForEnoughFreeHardDriveSpace()324 void NextLoopCheckForEnoughFreeHardDriveSpace()
325 {
326 	gfCheckForFreeSpaceOnHardDrive = TRUE;
327 }
328