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