1 /*
2    Copyright (C) 2004 by James Gregory
3    Part of the GalaxyHack project
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License.
7    This program is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY.
9 
10    See the COPYING file for more details.
11 */
12 
13 #include "Globals.h"
14 #include "Group.h"
15 #include "MainMenu.h"
16 #include "SetupBattle.h"
17 #include "PreBattle.h"
18 #include "RTS.h"
19 #include "Score.h"
20 #include "ForceSelect.h"
21 #include "LookupTables.h"
22 #include "Stuff.h"
23 
24 #include <sstream>
25 #include <iostream>
26 #include <stdexcept>
27 #include <iterator>
28 #include <boost/filesystem/operations.hpp>
29 
30 using std::cout;
31 using std::endl;
32 using std::ofstream;
33 using std::runtime_error;
34 using std::terminate;
35 using std::istringstream;
36 using std::istream_iterator;
37 using std::find;
38 using std::getline;
39 
40 //Global just to this file:
41 GameState* game = 0;
42 int fpsState = 0;
43 string userHomePath;
44 int lastSwitchTime; //set in init
45 
46 // Function declarations //////////////////////////////////
47 void FindHomePath();
48 bool DealWithArgs(int argc, char* argv[], vector<string>& preloadSides);
49 void LoadSettings(char* argv[]);
50 void SaveSettings();
51 void GameInit(char* argv[]);
52 bool GameMain();
53 void PollInput();
54 void DealWithEvent(SDL_Event& event);
55 void FPSCounter();
56 void GameShutdown(bool saveSettings = true);
57 void ExceptionShutdown(runtime_error e);
58 
main(int argc,char * argv[])59 int main(int argc, char* argv[]) {
60 	vector<string> preloadSides;
61 
62 	try {
63 		if (!DealWithArgs(argc, argv, preloadSides))
64 			return 0;
65 
66 		GameInit(argv);
67 
68 		if (preloadSides.size()) {
69 			for (int i = 0; i != preloadSides.size(); ++i) {
70 				sides.push_back(Side(preloadSides[i]));
71 				sides[i].FilesToSDStruct();
72 				sides[i].LoadData(false);
73 			}
74 			gsTo = GST_PreBattle;
75 			PreBattle::pbState = PBS_PreBattle;
76 		}
77 	} catch(runtime_error e) {
78 		const char* error = e.what();
79 		WriteLog(error);
80 		GameShutdown(false);
81 		terminate();
82 	}
83 
84 	try {
85 		while (GameMain());
86 	} catch(runtime_error e) {
87 		ExceptionShutdown(e);
88 	}
89 
90 	GameShutdown();
91 
92 	return 0;
93 }
94 
FindHomePath()95 void FindHomePath() {
96 #ifndef WIN32
97 	char *home = 0;
98 
99 	home = getenv("HOME");
100 
101 	if (home) {
102 		userHomePath = home;
103 		userHomePath += "/.galaxyhack/";
104 	}
105 #endif
106 }
107 
DealWithArgs(int argc,char * argv[],vector<string> & preloadSides)108 bool DealWithArgs(int argc, char* argv[], vector<string>& preloadSides) {
109 	if (argc > 1) {
110 		bool validArgs = 1;
111 
112 		for (int i = 1; i != argc; ++i) {
113 			string theArg = argv[i];
114 
115 			if (theArg == "-driver" && argc > i + 1) {
116 					globalSettings.videoDriver = argv[i+1];
117 					cout << "Ignoring settings file and using video driver " + globalSettings.videoDriver << endl << endl;
118 					++i;
119 			} else if (theArg == "-x" && argc > i + 2) {
120 					preloadSides.push_back(argv[i+1]);
121 					preloadSides.push_back(argv[i+2]);
122 					i +=2;
123 			} else if (theArg == "-b" && argc > i + 1) {
124 					globalSettings.batch = true;
125 					string framesString = argv[i+1];
126 					string::const_iterator begin = framesString.begin();
127 					string::const_iterator end = framesString.end();
128 					globalSettings.maxFrames = IterToInt(begin, end);
129 					#ifndef WIN32
130 					cout << "Batch mode initiated, max frames set to " + globalSettings.maxFrames << endl << endl;
131 					#endif
132 					++i;
133 			} else if (theArg == "-u" && argc > i + 1) {
134 					string speedString = argv[i+1];
135 					string::const_iterator begin = speedString.begin();
136 					string::const_iterator end = speedString.end();
137 					worldUpdateInterval = IterToInt(begin, end);
138 					#ifndef WIN32
139 					cout << "World update interval set to" + worldUpdateInterval << endl << endl;
140 					#endif
141 					++i;
142 			} else if (theArg == "-r" && argc > i + 1) {
143 					string randomString = argv[i+1];
144 					string::const_iterator begin = randomString.begin();
145 					string::const_iterator end = randomString.end();
146 					globalSettings.randomSeed = IterToInt(begin, end);
147 					#ifndef WIN32
148 					cout << "Random seed set to" + globalSettings.randomSeed << endl << endl;
149 					#endif
150 					++i;
151 			} else
152 				validArgs = 0;
153 		}
154 
155 		if (validArgs == 0) {
156 			cout << "Usage: ./galaxyhack [OPTIONS]" << endl << endl;
157 
158 			cout << "GalaxyHack - AI script based computer game" << endl << endl;
159 
160 			cout << "Options:" << endl;
161 			cout << "  -driver <videodriver>: use the specified video driver" << endl << endl;
162 
163 			cout << "  \"x11\" is the default but runs rather slowly." << endl;
164 			cout << "  \"dga\" will make the game run faster, but requires root permissions (run rootperms.sh) and only works with certain video cards." << endl << endl;
165 
166 			cout << "  -x <fleet1> <fleet2>: skip menu screen and immediately start a battle between the two fleets given" << endl;
167 			cout << "  -b <number of frames>: batch mode, see manual for details" << endl;
168 			cout << "  -u <number of frames>: world update interval, see manual for details" << endl;
169 			cout << "  -r <seed>: set the seed for the random number generator" << endl << endl;
170 
171 			cout << "A number of other options can be changed by editing ~/.galaxyhack/settings.dat" << endl << endl;
172 
173 			return false;
174 		}
175 	}
176 
177 	return true;
178 }
179 
LoadSettings(char * argv[])180 void LoadSettings(char* argv[]) {
181 #ifndef WIN32
182 	string settingsPath = userHomePath + "settings.dat";
183 	if (!DoesFileExist(settingsPath))
184 		settingsPath = "settings.dat";
185 #else
186 	string settingsPath = "settings.dat";
187 #endif
188 
189 	if (!DoesFileExist(settingsPath)) {
190 #ifndef WIN32
191 		string error = "Couldn't find settings.dat in $HOME/.galaxyhack or in present working directory";
192 #else
193 		string error = "Couldn't find settings.dat";
194 #endif
195 		throw runtime_error(error.c_str());
196 	}
197 
198 	string inputStr;
199 	FileToString(settingsPath, inputStr);
200 	istringstream input(inputStr);
201 
202 	istream_iterator<char> iter = input;
203 	istream_iterator<char> fileEnd = istream_iterator<char>();
204 
205 	#ifndef WIN32
206 		cout << "Using " + settingsPath + ":" << endl << endl;
207 		cout << inputStr;
208 	#endif
209 
210 	//commander
211 	iter = find(iter, fileEnd, ':');
212 	input.ignore();
213 	getline(input, globalSettings.commander);
214 	iter = input;
215 
216 	//data path
217 	iter = find(iter, fileEnd, ':');
218 	input.ignore();
219 	getline(input, globalSettings.bdp);
220 	if (globalSettings.bdp == "pwd")
221 		globalSettings.bdp = "";
222 	else if (globalSettings.bdp[globalSettings.bdp.size() -1] != '/')
223 		globalSettings.bdp += '/';
224 	iter = input;
225 
226 	//video driver
227 	iter = find(iter, fileEnd, ':');
228 	input.ignore();
229 	//may have been set on command line
230 	if (globalSettings.videoDriver == "")
231 		getline(input, globalSettings.videoDriver);
232 	iter = input;
233 
234 	//fullscreen
235 	iter = find(iter, fileEnd, ':');
236 	++iter;
237 	globalSettings.fullScreen = static_cast<bool>(IterToInt(iter, fileEnd));
238 
239 	//screen resolution
240 	iter = find(iter, fileEnd, ':');
241 	++iter;
242 	globalSettings.screenWidth = IterToInt(iter, fileEnd);
243 
244 	iter = find(iter, fileEnd, ':');
245 	++iter;
246 	globalSettings.screenHeight = IterToInt(iter, fileEnd);
247 
248 	//music
249 	iter = find(iter, fileEnd, ':');
250 	++iter;
251 	globalSettings.bMusic = static_cast<bool>(IterToInt(iter, fileEnd));
252 
253 	//disable sound
254 	iter = find(iter, fileEnd, ':');
255 	++iter;
256 	globalSettings.disableSound = static_cast<bool>(IterToInt(iter, fileEnd));
257 
258 	//default pics
259 	iter = find(iter, fileEnd, ':');
260 	input.ignore();
261 	getline(input, globalSettings.defaultCSPic);
262 	iter = input;
263 
264 	iter = find(iter, fileEnd, ':');
265 	input.ignore();
266 	getline(input, globalSettings.defaultFrPic);
267 	iter = input;
268 
269 	iter = find(iter, fileEnd, ':');
270 	input.ignore();
271 	getline(input, globalSettings.defaultSSPic);
272 	iter = input;
273 
274 	//remembered fleets
275 	for (int i = 0; i != maxPlayers; ++i) {
276 		iter = find(iter, fileEnd, ':');
277 		input.ignore();
278 		string tempStr;
279 		getline(input, tempStr);
280 		globalSettings.rememberFleets.push_back(tempStr);
281 		iter = input;
282 	}
283 
284 	iter = find(iter, fileEnd, ':');
285 	++iter;
286 	globalSettings.howGreenIsGreen = IterToInt(iter, fileEnd);
287 
288 	oldGlobalSettings = globalSettings;
289 
290 	if (globalSettings.batch) {
291 		globalSettings.disableSound = true;
292 		globalSettings.dontWriteSound = true;
293 		worldUpdateInterval = 0;
294 	}
295 }
296 
SaveSettings()297 void SaveSettings() {
298 #ifndef WIN32
299 	namespace fs = boost::filesystem;
300 	fs::path userHomePathPath(userHomePath);
301 	if (!fs::exists(userHomePathPath))
302 		fs::create_directory(userHomePathPath);
303 #endif
304 	string settingsStr = userHomePath + "settings.dat";
305 	ofstream output(settingsStr.c_str(), std::ios::trunc | std::ios::out);
306 
307 	output << "- there must be exactly one space between each colon and the setting that follows" << endl;
308 	output << endl;
309 
310 	output << "Commander: " << globalSettings.commander << endl;
311 	if (globalSettings.bdp.size())
312 		output << "Base data path: " << globalSettings.bdp << endl << endl;
313 	else
314 		output << "Base data path: pwd" << endl << endl;
315 
316 	output << "Video driver: " << globalSettings.videoDriver << endl;
317 
318 	output << "Fullscreen: " << globalSettings.fullScreen << endl;
319 	output << "Screen width: " << globalSettings.screenWidth << endl;
320 	output << "Screen height: " << globalSettings.screenHeight << endl << endl;
321 
322 	output << "Music: " << globalSettings.bMusic << endl;
323 
324 	if (globalSettings.dontWriteSound)
325 		output << "Disable sound: " << oldGlobalSettings.disableSound << endl << endl;
326 	else
327 		output << "Disable sound: " << globalSettings.disableSound << endl << endl;
328 
329 	output << "Default cap ship pic: " << globalSettings.defaultCSPic << endl;
330 	output << "Default frigate pic: " << globalSettings.defaultFrPic << endl;
331 	output << "Default small ship pic: " << globalSettings.defaultSSPic << endl << endl;
332 
333 	for (int i = 0; i != globalSettings.rememberFleets.size(); ++i)
334 		output << "Fleet " << i + 1 << ": " << globalSettings.rememberFleets[i] << endl;
335 	output << endl;
336 
337 	output << "How green is green: " << globalSettings.howGreenIsGreen << endl;
338 }
339 
GameInit(char * argv[])340 void GameInit(char* argv[]) {
341 	namespace fs = boost::filesystem;
342 
343 	FindHomePath();
344 	LoadSettings(argv);
345 
346 	string tmpStr = "SDL_VIDEODRIVER=" + globalSettings.videoDriver;
347 	char* sillyness = c_strNC(tmpStr);
348 	putenv(sillyness);
349 
350 	JSDL.Init();
351 	SetupLookupTables();
352 	LoadStandardGraphics();
353 
354 	lastSwitchTime = SDL_GetTicks();
355 }
356 
357 
GameMain()358 bool GameMain() {
359 	now = SDL_GetTicks();
360 
361 	int timePerFrame;
362 	//1000ms/60fps = 16 ish, though this will get rounded to 20
363 	if (worldUpdateInterval > 0)
364 		timePerFrame = 16;
365 	//1000ms/20fps = 50
366 	else
367 		timePerFrame = 50;
368 	if (now - lastSwitchTime > timePerFrame) {
369 		skipDisplayFrame = false;
370 		lastSwitchTime = now;
371 	} else
372 		skipDisplayFrame = true;
373 
374 	PollInput();
375 	UpdateWindows();
376 
377 	if (gsTo == gsCurrent)
378 		game->Main();
379 
380 	else try {
381 			SafeDelete(game);
382 
383 			if (gsTo == GST_Reload)
384 				gsTo = gsCurrent;
385 
386 			switch (gsTo) {
387 			case GST_MainMenu:
388 				game = new MainMenu::MainMenu_State;
389 				break;
390 
391 			case GST_SetupBattle:
392 				game = new SetupBattle::SetupBattle_State;
393 				break;
394 
395 			case GST_PreBattle:
396 				game = new PreBattle::PreBattle_State;
397 				break;
398 
399 			case GST_Battle:
400 				game = new RTS::RTS_State;
401 				break;
402 
403 			case GST_Score:
404 				game = new Score::Score_State;
405 				break;
406 
407 			case GST_ForceSelect:
408 				game = new ForceSelect::ForceSelect_State;
409 				break;
410 
411 			case GST_TheOS:
412 				return false;
413 				break;
414 			}
415 
416 			//make sure menus have a starting event to force them to update screen before user
417 			//input in spite of SDL_WaitEvent
418 			SDL_Event event;
419 			event.type = SDL_USEREVENT;
420 			SDL_PushEvent(&event);
421 
422 			//make sure we don't get stuck in poll event loop before display is updated on fast computers
423 			lastSwitchTime -= 1000;
424 
425 			gsCurrent = gsTo;
426 		} catch(runtime_error e) {
427 			if (gsTo != GST_MainMenu) {
428 				sides.clear();
429 				KillAllWindows();
430 				const char* error = e.what();
431 				WriteLog(error);
432 				globalErrorString = error;
433 
434 				if (gsCurrent == GST_MainMenu)
435 					gsCurrent = GST_TheOS;
436 				gsTo = GST_MainMenu;
437 			} else
438 				throw runtime_error(e);
439 		}
440 
441 	if (fpsState == 1)
442 		FPSCounter();
443 
444 	if (!globalSettings.batch) {
445 		static SDL_Rect mouseRect = {0, 0, genPictures[GENPIC_CURSOR]->w, genPictures[GENPIC_CURSOR]->h};
446 
447 		int mx, my;
448 		SDL_GetMouseState(&mx, &my);
449 		mouseRect.x = mx;
450 		mouseRect.y = my;
451 
452 		JSDL.Blt(genPictures[GENPIC_CURSOR], mouseRect);
453 	}
454 
455 	//this does two things:
456 
457 	//1. in windowed mode, it prevents it going so fast
458 	//it actually slows down to a crawl, I think maybe
459 	//because blits start having to wait for other ones to finish
460 
461 	//2. In full screen mode, it means if you have the game
462 	//set to go more than 60fps it won't have a limit of the
463 	//monitor refresh rate
464 
465 	if (!skipDisplayFrame)
466 		JSDL.Flip();
467 
468 	return true;
469 }
470 
PollInput()471 void PollInput() {
472 	if (globalSettings.batch)
473 		return;
474 
475 	SDL_Event event;
476 
477 	if (gsCurrent == GST_MainMenu || gsCurrent == GST_SetupBattle
478 	|| gsCurrent == GST_Score || gsCurrent == GST_ForceSelect) {
479 		int result = SDL_WaitEvent(&event);
480 		if (!result)
481 			throw runtime_error("Error when waiting for event");
482 		DealWithEvent(event);
483 		return;
484 	}
485 
486 	while (SDL_PollEvent(&event))
487 		DealWithEvent(event);
488 }
489 
DealWithEvent(SDL_Event & event)490 void DealWithEvent(SDL_Event& event) {
491 	switch (event.type) {
492 	case SDL_MOUSEBUTTONDOWN:
493 		if (game && !WinMouseD(event.button.button, event.button.x, event.button.y))
494 			game->MouseD(event.button.button, event.button.x, event.button.y);
495 		break;
496 
497 	case SDL_MOUSEBUTTONUP:
498 		if (game)
499 			game->MouseU(event.button.button, event.button.x, event.button.y);
500 		break;
501 
502 	case SDL_MOUSEMOTION:
503 		WinMouseM(event.motion.state, event.motion.x, event.motion.y);
504 		if (game)
505 			game->MouseM(event.motion.state, event.motion.x, event.motion.y);
506 		break;
507 
508 	case SDL_KEYDOWN:
509 		if (WinKeyboard(event.key.keysym))
510 			break;
511 
512 		switch(event.key.keysym.sym) {
513 		case SDLK_F4:
514 			CycleGroupInfoType();
515 			break;
516 
517 		case SDLK_F8:
518 			if (fpsState == false)
519 				fpsState = true;
520 			else
521 				fpsState = false;
522 			break;
523 		}
524 
525 		if (game)
526 			game->Keyboard(event.key.keysym);
527 		break;
528 
529 	/* FIXME this doesn't work at all
530 	case SDL_ACTIVEEVENT:
531 		if (event.active.state & SDL_APPACTIVE && event.active.gain == 0) {
532 			while (1) {
533 				if (SDL_PollEvent(&event)) {
534 					if (event.active.state == SDL_APPACTIVE && event.active.gain == 1)
535 						break;
536 				}
537 				SDL_Delay(500);
538 			}
539 		}
540 		break;
541 	*/
542 
543 	case SDL_QUIT:
544 		gsTo = GST_TheOS;
545 		break;
546 
547 	default:
548 		break;
549 	}
550 }
551 
FPSCounter()552 void FPSCounter() {
553 	static int fpsCounter = 0;
554 	static int fpsSave = 0;
555 	static unsigned int fpsTimer = SDL_GetTicks();
556 
557 	if (now - fpsTimer > 1000) {
558 		fpsSave = fpsCounter;
559 		fpsCounter = 0;
560 		fpsTimer = now;
561 	} else
562 		++fpsCounter;
563 
564 	char output[60];
565 	sprintf(output, "FPS (sort of): %d", fpsSave);
566 	normalFonts.BlitString(50, globalSettings.screenHeight - 20, 0, output);
567 }
568 
GameShutdown(bool saveSettings)569 void GameShutdown(bool saveSettings) {
570 	if (saveSettings)
571 		SaveSettings();
572 
573 	ClearStandardGraphics();
574 
575 	JSDL.Shutdown();
576 
577 	//should already be deleted unless an exception has been thrown
578 	SafeDelete(game);
579 }
580 
ExceptionShutdown(runtime_error e)581 void ExceptionShutdown(runtime_error e) {
582 	const string eWhat = e.what();
583 
584 	const string errorLog = "Exception shutdown due to: " + eWhat;
585 	WriteLog(errorLog.c_str());
586 
587 	if (globalSettings.batch) {
588 		GameShutdown();
589 		terminate();
590 	}
591 
592 	if (JSDL.screen->locked)
593 		JSDL.UnlockBack();
594 
595 	const string errorWin = "Major problem:\n" + eWhat + "\n\nClick the left mouse button to exit";
596 	CreateInfoString(errorWin);
597 	list<GenWindow>::reverse_iterator iter = myWindows.rbegin();
598 	iter->DrawSelf();
599 
600 	JSDL.ForceFlip();
601 
602 	while (1) {
603 		SDL_Event event;
604 		while (SDL_PollEvent(&event)) {
605 			if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_KEYDOWN) {
606 				GameShutdown();
607 				terminate();
608 			}
609 		}
610 	}
611 }
612 
613