1 /*
2  * Copyright 2010-2014 OpenXcom Developers.
3  *
4  * This file is part of OpenXcom.
5  *
6  * OpenXcom is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * OpenXcom is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with OpenXcom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 #include "Game.h"
20 #include <cmath>
21 #include <sstream>
22 #include <SDL_mixer.h>
23 #include "Adlib/adlplayer.h"
24 #include "State.h"
25 #include "Screen.h"
26 #include "Sound.h"
27 #include "Music.h"
28 #include "Language.h"
29 #include "Logger.h"
30 #include "../Interface/Cursor.h"
31 #include "../Interface/FpsCounter.h"
32 #include "../Resource/ResourcePack.h"
33 #include "../Ruleset/Ruleset.h"
34 #include "../Savegame/SavedGame.h"
35 #include "Palette.h"
36 #include "Action.h"
37 #include "Exception.h"
38 #include "InteractiveSurface.h"
39 #include "Options.h"
40 #include "CrossPlatform.h"
41 #include "../Menu/TestState.h"
42 
43 namespace OpenXcom
44 {
45 
46 const double Game::VOLUME_GRADIENT = 10.0;
47 
48 /**
49  * Starts up SDL with all the subsystems and SDL_mixer for audio processing,
50  * creates the display screen and sets up the cursor.
51  * @param title Title of the game window.
52  */
Game(const std::string & title)53 Game::Game(const std::string &title) : _screen(0), _cursor(0), _lang(0), _states(), _deleted(), _res(0), _save(0), _rules(0), _quit(false), _init(false), _mouseActive(true), _timeUntilNextFrame(0)
54 {
55 	Options::reload = false;
56 	Options::mute = false;
57 
58 	// Initialize SDL
59 	if (SDL_Init(SDL_INIT_VIDEO) < 0)
60 	{
61 		throw Exception(SDL_GetError());
62 	}
63 	Log(LOG_INFO) << "SDL initialized successfully.";
64 
65 	// Initialize SDL_mixer
66 	if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
67 	{
68 		Log(LOG_ERROR) << SDL_GetError();
69 		Log(LOG_WARNING) << "No sound device detected, audio disabled.";
70 		Options::mute = true;
71 	}
72 	else
73 	{
74 		initAudio();
75 	}
76 
77 	// trap the mouse inside the window
78 	SDL_WM_GrabInput(Options::captureMouse);
79 
80 	// Set the window icon
81 	CrossPlatform::setWindowIcon(103, "openxcom.png");
82 
83 	// Set the window caption
84 	SDL_WM_SetCaption(title.c_str(), 0);
85 
86 	SDL_EnableUNICODE(1);
87 
88 	// Create display
89 	_screen = new Screen();
90 
91 	// Create cursor
92 	_cursor = new Cursor(9, 13);
93 	_cursor->setColor(Palette::blockOffset(15)+12);
94 
95 	// Create invisible hardware cursor to workaround bug with absolute positioning pointing devices
96 	SDL_ShowCursor(SDL_ENABLE);
97 	Uint8 cursor = 0;
98 	SDL_SetCursor(SDL_CreateCursor(&cursor, &cursor, 1,1,0,0));
99 
100 	// Create fps counter
101 	_fpsCounter = new FpsCounter(15, 5, 0, 0);
102 
103 	// Create blank language
104 	_lang = new Language();
105 
106 	_timeOfLastFrame = 0;
107 }
108 
109 /**
110  * Deletes the display screen, cursor, states and shuts down all the SDL subsystems.
111  */
~Game()112 Game::~Game()
113 {
114 	Sound::stop();
115 	Music::stop();
116 
117 	for (std::list<State*>::iterator i = _states.begin(); i != _states.end(); ++i)
118 	{
119 		delete *i;
120 	}
121 
122 	SDL_FreeCursor(SDL_GetCursor());
123 
124 	delete _cursor;
125 	delete _lang;
126 	delete _res;
127 	delete _save;
128 	delete _rules;
129 	delete _screen;
130 	delete _fpsCounter;
131 
132 	Mix_CloseAudio();
133 
134 	SDL_Quit();
135 }
136 
137 /**
138  * The state machine takes care of passing all the events from SDL to the
139  * active state, running any code within and blitting all the states and
140  * cursor to the screen. This is run indefinitely until the game quits.
141  */
run()142 void Game::run()
143 {
144 	enum ApplicationState { RUNNING = 0, SLOWED = 1, PAUSED = 2 } runningState = RUNNING;
145 	static const ApplicationState kbFocusRun[4] = { RUNNING, RUNNING, SLOWED, PAUSED };
146 	static const ApplicationState stateRun[4] = { SLOWED, PAUSED, PAUSED, PAUSED };
147 	// this will avoid processing SDL's resize event on startup, workaround for the heap allocation error it causes.
148 	bool startupEvent = Options::allowResize;
149 	while (!_quit)
150 	{
151 		// Clean up states
152 		while (!_deleted.empty())
153 		{
154 			delete _deleted.back();
155 			_deleted.pop_back();
156 		}
157 
158 		// Initialize active state
159 		if (!_init)
160 		{
161 			_init = true;
162 			_states.back()->init();
163 
164 			// Unpress buttons
165 			_states.back()->resetAll();
166 
167 			// Refresh mouse position
168 			SDL_Event ev;
169 			int x, y;
170 			SDL_GetMouseState(&x, &y);
171 			ev.type = SDL_MOUSEMOTION;
172 			ev.motion.x = x;
173 			ev.motion.y = y;
174 			Action action = Action(&ev, _screen->getXScale(), _screen->getYScale(), _screen->getCursorTopBlackBand(), _screen->getCursorLeftBlackBand());
175 			_states.back()->handle(&action);
176 		}
177 
178 		// Process events
179 		while (SDL_PollEvent(&_event))
180 		{
181 			if (CrossPlatform::isQuitShortcut(_event))
182 				_event.type = SDL_QUIT;
183 			switch (_event.type)
184 			{
185 				case SDL_QUIT:
186 					quit();
187 					break;
188 				case SDL_ACTIVEEVENT:
189 					switch (reinterpret_cast<SDL_ActiveEvent*>(&_event)->state)
190 					{
191 						case SDL_APPACTIVE:
192 							runningState = reinterpret_cast<SDL_ActiveEvent*>(&_event)->gain ? RUNNING : stateRun[Options::pauseMode];
193 							break;
194 						case SDL_APPMOUSEFOCUS:
195 							// We consciously ignore it.
196 							break;
197 						case SDL_APPINPUTFOCUS:
198 							runningState = reinterpret_cast<SDL_ActiveEvent*>(&_event)->gain ? RUNNING : kbFocusRun[Options::pauseMode];
199 							break;
200 					}
201 					break;
202 				case SDL_VIDEORESIZE:
203 					if (Options::allowResize)
204 					{
205 						if (!startupEvent)
206 						{
207 							Options::newDisplayWidth = Options::displayWidth = std::max(Screen::ORIGINAL_WIDTH, _event.resize.w);
208 							Options::newDisplayHeight = Options::displayHeight = std::max(Screen::ORIGINAL_HEIGHT, _event.resize.h);
209 							int dX = 0, dY = 0;
210 							Screen::updateScale(Options::battlescapeScale, Options::battlescapeScale, Options::baseXBattlescape, Options::baseYBattlescape, false);
211 							Screen::updateScale(Options::geoscapeScale, Options::geoscapeScale, Options::baseXGeoscape, Options::baseYGeoscape, false);
212 							for (std::list<State*>::iterator i = _states.begin(); i != _states.end(); ++i)
213 							{
214 								(*i)->resize(dX, dY);
215 							}
216 							_screen->resetDisplay();
217 						}
218 						else
219 						{
220 							startupEvent = false;
221 						}
222 					}
223 					break;
224 				case SDL_MOUSEMOTION:
225 				case SDL_MOUSEBUTTONDOWN:
226 				case SDL_MOUSEBUTTONUP:
227 					// Skip mouse events if they're disabled
228 					if (!_mouseActive) continue;
229 					// re-gain focus on mouse-over or keypress.
230 					runningState = RUNNING;
231 					// Go on, feed the event to others
232 				default:
233 					Action action = Action(&_event, _screen->getXScale(), _screen->getYScale(), _screen->getCursorTopBlackBand(), _screen->getCursorLeftBlackBand());
234 					_screen->handle(&action);
235 					_cursor->handle(&action);
236 					_fpsCounter->handle(&action);
237 					_states.back()->handle(&action);
238 					if (action.getDetails()->type == SDL_KEYDOWN)
239 					{
240 						// "ctrl-g" grab input
241 						if (action.getDetails()->key.keysym.sym == SDLK_g && (SDL_GetModState() & KMOD_CTRL) != 0)
242 						{
243 							Options::captureMouse = (SDL_GrabMode)(!Options::captureMouse);
244 							SDL_WM_GrabInput(Options::captureMouse);
245 						}
246 						else if (Options::debug)
247 						{
248 							if (action.getDetails()->key.keysym.sym == SDLK_t && (SDL_GetModState() & KMOD_CTRL) != 0)
249 							{
250 								setState(new TestState(this));
251 							}
252 							// "ctrl-u" debug UI
253 							else if (action.getDetails()->key.keysym.sym == SDLK_u && (SDL_GetModState() & KMOD_CTRL) != 0)
254 							{
255 								Options::debugUi = !Options::debugUi;
256 								_states.back()->redrawText();
257 							}
258 						}
259 					}
260 					break;
261 			}
262 		}
263 
264 		// Process rendering
265 		if (runningState != PAUSED)
266 		{
267 			// Process logic
268 			_states.back()->think();
269 			_fpsCounter->think();
270 			if (Options::FPS > 0 && !(Options::useOpenGL && Options::vSyncForOpenGL))
271 			{
272 				// Update our FPS delay time based on the time of the last draw.
273 				_timeUntilNextFrame = (1000.0f / Options::FPS) - (SDL_GetTicks() - _timeOfLastFrame);
274 			}
275 			else
276 			{
277 				_timeUntilNextFrame = 0;
278 			}
279 
280 			if (_init && _timeUntilNextFrame <= 0)
281 			{
282 				// make a note of when this frame update occured.
283 				_timeOfLastFrame = SDL_GetTicks();
284 				_fpsCounter->addFrame();
285 				_screen->clear();
286 				std::list<State*>::iterator i = _states.end();
287 				do
288 				{
289 					--i;
290 				}
291 				while(i != _states.begin() && !(*i)->isScreen());
292 
293 				for (; i != _states.end(); ++i)
294 				{
295 					(*i)->blit();
296 				}
297 				_fpsCounter->blit(_screen->getSurface());
298 				_cursor->blit(_screen->getSurface());
299 				_screen->flip();
300 			}
301 		}
302 
303 		// Save on CPU
304 		switch (runningState)
305 		{
306 			case RUNNING:
307 				SDL_Delay(1); //Save CPU from going 100%
308 				break;
309 			case SLOWED: case PAUSED:
310 				SDL_Delay(100); break; //More slowing down.
311 		}
312 	}
313 
314 	Options::save();
315 }
316 
317 /**
318  * Stops the state machine and the game is shut down.
319  */
quit()320 void Game::quit()
321 {
322 	// Always save ironman
323 	if (_save != 0 && _save->isIronman() && !_save->getName().empty())
324 	{
325 		std::string filename = CrossPlatform::sanitizeFilename(Language::wstrToFs(_save->getName())) + ".sav";
326 		_save->save(filename);
327 	}
328 	_quit = true;
329 }
330 
331 /**
332  * Changes the audio volume of the music and
333  * sound effect channels.
334  * @param sound Sound volume, from 0 to MIX_MAX_VOLUME.
335  * @param music Music volume, from 0 to MIX_MAX_VOLUME.
336  * @param ui UI volume, from 0 to MIX_MAX_VOLUME.
337  */
setVolume(int sound,int music,int ui)338 void Game::setVolume(int sound, int music, int ui)
339 {
340 	if (!Options::mute)
341 	{
342 		if (sound >= 0)
343 		{
344 			sound = volumeExponent(sound) * (double)SDL_MIX_MAXVOLUME;
345 			Mix_Volume(-1, sound);
346 		}
347 		if (music >= 0)
348 		{
349 			music = volumeExponent(music) * (double)SDL_MIX_MAXVOLUME;
350 			Mix_VolumeMusic(music);
351 		}
352 		if (ui >= 0)
353 		{
354 			ui = volumeExponent(ui) * (double)SDL_MIX_MAXVOLUME;
355 			Mix_Volume(1, ui);
356 			Mix_Volume(2, ui);
357 		}
358 	}
359 }
360 
volumeExponent(int volume)361 double Game::volumeExponent(int volume)
362 {
363 	return (exp(log(Game::VOLUME_GRADIENT + 1.0) * volume / (double)SDL_MIX_MAXVOLUME) -1.0 ) / Game::VOLUME_GRADIENT;
364 }
365 /**
366  * Returns the display screen used by the game.
367  * @return Pointer to the screen.
368  */
getScreen() const369 Screen *Game::getScreen() const
370 {
371 	return _screen;
372 }
373 
374 /**
375  * Returns the mouse cursor used by the game.
376  * @return Pointer to the cursor.
377  */
getCursor() const378 Cursor *Game::getCursor() const
379 {
380 	return _cursor;
381 }
382 
383 /**
384  * Returns the FpsCounter used by the game.
385  * @return Pointer to the FpsCounter.
386  */
getFpsCounter() const387 FpsCounter *Game::getFpsCounter() const
388 {
389 	return _fpsCounter;
390 }
391 
392 /**
393  * Pops all the states currently in stack and pushes in the new state.
394  * A shortcut for cleaning up all the old states when they're not necessary
395  * like in one-way transitions.
396  * @param state Pointer to the new state.
397  */
setState(State * state)398 void Game::setState(State *state)
399 {
400 	while (!_states.empty())
401 	{
402 		popState();
403 	}
404 	pushState(state);
405 	_init = false;
406 }
407 
408 /**
409  * Pushes a new state into the top of the stack and initializes it.
410  * The new state will be used once the next game cycle starts.
411  * @param state Pointer to the new state.
412  */
pushState(State * state)413 void Game::pushState(State *state)
414 {
415 	_states.push_back(state);
416 	_init = false;
417 }
418 
419 /**
420  * Pops the last state from the top of the stack. Since states
421  * can't actually be deleted mid-cycle, it's moved into a separate queue
422  * which is cleared at the start of every cycle, so the transition
423  * is seamless.
424  */
popState()425 void Game::popState()
426 {
427 	_deleted.push_back(_states.back());
428 	_states.pop_back();
429 	_init = false;
430 }
431 
432 /**
433  * Returns the language currently in use by the game.
434  * @return Pointer to the language.
435  */
getLanguage() const436 Language *Game::getLanguage() const
437 {
438 	return _lang;
439 }
440 
441 /**
442 * Changes the language currently in use by the game.
443 * @param filename Filename of the language file.
444 */
loadLanguage(const std::string & filename)445 void Game::loadLanguage(const std::string &filename)
446 {
447 	std::ostringstream ss;
448 	ss << "Language/" << filename << ".yml";
449 
450 	ExtraStrings *strings = 0;
451 	std::map<std::string, ExtraStrings *> extraStrings = _rules->getExtraStrings();
452 	if (!extraStrings.empty())
453 	{
454 		if (extraStrings.find(filename) != extraStrings.end())
455 		{
456 			strings = extraStrings[filename];
457 		}
458 		// Fallback
459 		else if (extraStrings.find("en-US") != extraStrings.end())
460 		{
461 			strings = extraStrings["en-US"];
462 		}
463 		else if (extraStrings.find("en-GB") != extraStrings.end())
464 		{
465 			strings = extraStrings["en-GB"];
466 		}
467 		else
468 		{
469 			strings = extraStrings.begin()->second;
470 		}
471 	}
472 
473 	_lang->load(CrossPlatform::getDataFile(ss.str()), strings);
474 
475 	Options::language = filename;
476 }
477 
478 /**
479  * Returns the resource pack currently in use by the game.
480  * @return Pointer to the resource pack.
481  */
getResourcePack() const482 ResourcePack *Game::getResourcePack() const
483 {
484 	return _res;
485 }
486 
487 /**
488  * Sets a new resource pack for the game to use.
489  * @param res Pointer to the resource pack.
490  */
setResourcePack(ResourcePack * res)491 void Game::setResourcePack(ResourcePack *res)
492 {
493 	delete _res;
494 	_res = res;
495 }
496 
497 /**
498  * Returns the saved game currently in use by the game.
499  * @return Pointer to the saved game.
500  */
getSavedGame() const501 SavedGame *Game::getSavedGame() const
502 {
503 	return _save;
504 }
505 
506 /**
507  * Sets a new saved game for the game to use.
508  * @param save Pointer to the saved game.
509  */
setSavedGame(SavedGame * save)510 void Game::setSavedGame(SavedGame *save)
511 {
512 	delete _save;
513 	_save = save;
514 }
515 
516 /**
517  * Returns the ruleset currently in use by the game.
518  * @return Pointer to the ruleset.
519  */
getRuleset() const520 Ruleset *Game::getRuleset() const
521 {
522 	return _rules;
523 }
524 
525 /**
526  * Loads the rulesets specified in the game options.
527  */
loadRuleset()528 void Game::loadRuleset()
529 {
530 	Options::badMods.clear();
531 	_rules = new Ruleset();
532 	if (Options::rulesets.empty())
533 	{
534 		Options::rulesets.push_back("Xcom1Ruleset");
535 	}
536 	for (std::vector<std::string>::iterator i = Options::rulesets.begin(); i != Options::rulesets.end();)
537 	{
538 		try
539 		{
540 			_rules->load(*i);
541 			++i;
542 		}
543 		catch (YAML::Exception &e)
544 		{
545 			Log(LOG_WARNING) << e.what();
546 			Options::badMods.push_back(*i);
547 			Options::badMods.push_back(e.what());
548 			i = Options::rulesets.erase(i);
549 		}
550 	}
551 	if (Options::rulesets.empty())
552 	{
553 		throw Exception("Failed to load ruleset");
554 	}
555 	_rules->sortLists();
556 }
557 
558 /**
559  * Sets whether the mouse is activated.
560  * If it is, mouse events are processed, otherwise
561  * they are ignored and the cursor is hidden.
562  * @param active Is mouse activated?
563  */
setMouseActive(bool active)564 void Game::setMouseActive(bool active)
565 {
566 	_mouseActive = active;
567 	_cursor->setVisible(active);
568 }
569 
570 /**
571  * Returns whether current state is *state
572  * @param state The state to test against the stack state
573  * @return Is state the current state?
574  */
isState(State * state) const575 bool Game::isState(State *state) const
576 {
577 	return !_states.empty() && _states.back() == state;
578 }
579 
580 /**
581  * Checks if the game is currently quitting.
582  * @return whether the game is shutting down or not.
583  */
isQuitting() const584 bool Game::isQuitting() const
585 {
586 	return _quit;
587 }
588 
589 /**
590  * Loads the most appropriate language
591  * given current system and game options.
592  */
defaultLanguage()593 void Game::defaultLanguage()
594 {
595 	std::string defaultLang = "en-US";
596 	// No language set, detect based on system
597 	if (Options::language.empty())
598 	{
599 		std::string locale = CrossPlatform::getLocale();
600 		std::string lang = locale.substr(0, locale.find_first_of('-'));
601 		// Try to load full locale
602 		try
603 		{
604 			loadLanguage(locale);
605 		}
606 		catch (std::exception)
607 		{
608 			// Try to load language locale
609 			try
610 			{
611 				loadLanguage(lang);
612 			}
613 			// Give up, use default
614 			catch (std::exception)
615 			{
616 				loadLanguage(defaultLang);
617 			}
618 		}
619 	}
620 	else
621 	{
622 		// Use options language
623 		try
624 		{
625 			loadLanguage(Options::language);
626 		}
627 		// Language not found, use default
628 		catch (std::exception)
629 		{
630 			loadLanguage(defaultLang);
631 		}
632 	}
633 }
634 
635 /**
636  * Initializes the audio subsystem.
637  */
initAudio()638 void Game::initAudio()
639 {
640 	Uint16 format;
641 	if (Options::audioBitDepth == 8)
642 		format = AUDIO_S8;
643 	else
644 		format = AUDIO_S16SYS;
645 	if (Mix_OpenAudio(Options::audioSampleRate, format, 2, 1024) != 0)
646 	{
647 		Log(LOG_ERROR) << Mix_GetError();
648 		Log(LOG_WARNING) << "No sound device detected, audio disabled.";
649 		Options::mute = true;
650 	}
651 	else
652 	{
653 		Mix_AllocateChannels(16);
654 		// Set up UI channels
655 		Mix_ReserveChannels(3);
656 		Mix_GroupChannels(1, 2, 0);
657 		Log(LOG_INFO) << "SDL_mixer initialized successfully.";
658 		setVolume(Options::soundVolume, Options::musicVolume, Options::uiVolume);
659 	}
660 }
661 
662 }
663