/*
* Copyright 2010-2013 OpenXcom Developers.
*
* This file is part of OpenXcom.
*
* OpenXcom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenXcom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenXcom. If not, see .
*/
#include "Game.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include
#include
#endif
#include
#include
#include "State.h"
#include "Screen.h"
#include "Language.h"
#include "Logger.h"
#include "../Interface/Cursor.h"
#include "../Interface/FpsCounter.h"
#include "../Resource/ResourcePack.h"
#include "../Ruleset/Ruleset.h"
#include "../Savegame/SavedGame.h"
#include "Palette.h"
#include "Action.h"
#include "Exception.h"
#include "InteractiveSurface.h"
#include "Options.h"
#include "CrossPlatform.h"
namespace OpenXcom
{
/**
* Starts up SDL with all the subsystems and SDL_mixer for audio processing,
* creates the display screen and sets up the cursor.
* @param title Title of the game window.
*/
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)
{
// Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
throw Exception(SDL_GetError());
}
Log(LOG_INFO) << "SDL initialized successfully.";
if (!Options::getBool("mute"))
{
// Initialize SDL_mixer
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
{
Log(LOG_ERROR) << SDL_GetError();
Log(LOG_WARNING) << "No sound device detected, audio disabled.";
Options::setBool("mute", true);
}
else
{
Uint16 format;
if (Options::getInt("audioBitDepth") == 8)
format = AUDIO_S8;
else
format = AUDIO_S16SYS;
if (Mix_OpenAudio(Options::getInt("audioSampleRate"), format, 2, 1024) != 0)
{
Log(LOG_ERROR) << Mix_GetError();
Log(LOG_WARNING) << "No sound device detected, audio disabled.";
Options::setBool("mute", true);
}
else
{
Mix_AllocateChannels(16);
}
}
Log(LOG_INFO) << "SDL_mixer initialized successfully.";
}
// Set the window caption
SDL_WM_SetCaption(title.c_str(), 0);
#ifdef _WIN32
// Set the window icon
HINSTANCE handle = GetModuleHandle(NULL);
HICON icon = LoadIcon(handle, MAKEINTRESOURCE(103));
SDL_SysWMinfo wminfo;
SDL_VERSION(&wminfo.version)
if (SDL_GetWMInfo(&wminfo))
{
HWND hwnd = wminfo.window;
SetClassLongPtr(hwnd, GCLP_HICON, (LONG_PTR)icon);
}
#endif
SDL_EnableUNICODE(1);
// Create display
Screen::BASE_WIDTH = Options::getInt("baseXResolution");
Screen::BASE_HEIGHT = Options::getInt("baseYResolution");
_screen = new Screen(Options::getInt("displayWidth"), Options::getInt("displayHeight"), 0, Options::getBool("fullscreen"), Options::getInt("windowedModePositionX"), Options::getInt("windowedModePositionY"));
// Create cursor
_cursor = new Cursor(9, 13);
_cursor->setColor(Palette::blockOffset(15)+12);
// Create fps counter
_fpsCounter = new FpsCounter(15, 5, 0, 0);
// Create blank language
_lang = new Language();
_alienContainmentHasUpperLimit = Options::getBool("alienContainmentHasUpperLimit");
}
/**
* Deletes the display screen, cursor, states and shuts down all the SDL subsystems.
*/
Game::~Game()
{
Mix_HaltChannel(-1);
for (std::list::iterator i = _states.begin(); i != _states.end(); ++i)
{
delete *i;
}
delete _cursor;
delete _lang;
delete _res;
delete _rules;
delete _save;
delete _screen;
delete _fpsCounter;
Mix_CloseAudio();
SDL_Quit();
}
/**
* The state machine takes care of passing all the events from SDL to the
* active state, running any code within and blitting all the states and
* cursor to the screen. This is run indefinitely until the game quits.
*/
void Game::run()
{
enum ApplicationState { RUNNING = 0, SLOWED = 1, PAUSED = 2 } runningState = RUNNING;
static const ApplicationState kbFocusRun[4] = { RUNNING, RUNNING, SLOWED, PAUSED };
static const ApplicationState stateRun[4] = { SLOWED, PAUSED, PAUSED, PAUSED };
int pauseMode = Options::getInt("pauseMode");
if (pauseMode > 3)
pauseMode = 3;
while (!_quit)
{
// Clean up states
while (!_deleted.empty())
{
delete _deleted.back();
_deleted.pop_back();
}
// Initialize active state
if (!_init)
{
_states.back()->init();
_init = true;
// Unpress buttons
_states.back()->resetAll();
// Refresh mouse position
SDL_Event ev;
int x, y;
SDL_GetMouseState(&x, &y);
ev.type = SDL_MOUSEMOTION;
ev.motion.x = x;
ev.motion.y = y;
Action action = Action(&ev, _screen->getXScale(), _screen->getYScale());
_states.back()->handle(&action);
}
// Process events
while (SDL_PollEvent(&_event))
{
switch (_event.type)
{
case SDL_QUIT: _quit = true; break;
case SDL_ACTIVEEVENT:
switch (reinterpret_cast(&_event)->state)
{
case SDL_APPACTIVE:
runningState = reinterpret_cast(&_event)->gain ? RUNNING : stateRun[pauseMode];
break;
case SDL_APPMOUSEFOCUS:
// We consciously ignore it.
break;
case SDL_APPINPUTFOCUS:
runningState = reinterpret_cast(&_event)->gain ? RUNNING : kbFocusRun[pauseMode];
break;
}
break;
case SDL_VIDEORESIZE:
Options::setInt("displayWidth", _event.resize.w);
Options::setInt("displayHeight", _event.resize.h);
_screen->setResolution(_event.resize.w, _event.resize.h);
break;
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// Skip mouse events if they're disabled
if (!_mouseActive) continue;
// re-gain focus on mouse-over or keypress.
runningState = RUNNING;
// Go on, feed the event to others
default:
Action action = Action(&_event, _screen->getXScale(), _screen->getYScale());
_screen->handle(&action);
_cursor->handle(&action);
_fpsCounter->handle(&action);
_states.back()->handle(&action);
break;
}
}
// Process rendering
if (runningState != PAUSED)
{
// Process logic
_fpsCounter->think();
_states.back()->think();
if (_init)
{
_screen->clear();
std::list::iterator i = _states.end();
do
{
--i;
}
while(i != _states.begin() && !(*i)->isScreen());
for (; i != _states.end(); ++i)
{
(*i)->blit();
}
_fpsCounter->blit(_screen->getSurface());
_cursor->blit(_screen->getSurface());
}
_screen->flip();
}
// Save on CPU
switch (runningState)
{
case RUNNING: SDL_Delay(1); break; //Save CPU from going 100%
case SLOWED: case PAUSED:
SDL_Delay(100); break; //More slowing down.
}
}
}
/**
* Stops the state machine and the game is shut down.
*/
void Game::quit()
{
_quit = true;
}
/**
* Changes the audio volume of the music and
* sound effect channels.
* @param sound Sound volume, from 0 to MIX_MAX_VOLUME.
* @param music Music volume, from 0 to MIX_MAX_VOLUME.
*/
void Game::setVolume(int sound, int music)
{
if (!Options::getBool("mute"))
{
if (sound >= 0)
Mix_Volume(-1, sound);
if (music >= 0)
Mix_VolumeMusic(music);
}
}
/**
* Returns the display screen used by the game.
* @return Pointer to the screen.
*/
Screen *Game::getScreen() const
{
return _screen;
}
/**
* Returns the mouse cursor used by the game.
* @return Pointer to the cursor.
*/
Cursor *Game::getCursor() const
{
return _cursor;
}
/**
* Returns the FpsCounter used by the game.
* @return Pointer to the FpsCounter.
*/
FpsCounter *Game::getFpsCounter() const
{
return _fpsCounter;
}
/**
* Replaces a certain amount of colors in the palettes of the game's
* screen and resources.
* @param colors Pointer to the set of colors.
* @param firstcolor Offset of the first color to replace.
* @param ncolors Amount of colors to replace.
*/
void Game::setPalette(SDL_Color *colors, int firstcolor, int ncolors)
{
_screen->setPalette(colors, firstcolor, ncolors);
_cursor->setPalette(colors, firstcolor, ncolors);
_cursor->draw();
_fpsCounter->setPalette(colors, firstcolor, ncolors);
if (_res != 0)
{
_res->setPalette(colors, firstcolor, ncolors);
}
}
/**
* Pops all the states currently in stack and pushes in the new state.
* A shortcut for cleaning up all the old states when they're not necessary
* like in one-way transitions.
* @param state Pointer to the new state.
*/
void Game::setState(State *state)
{
while (!_states.empty())
{
popState();
}
pushState(state);
_init = false;
}
/**
* Pushes a new state into the top of the stack and initializes it.
* The new state will be used once the next game cycle starts.
* @param state Pointer to the new state.
*/
void Game::pushState(State *state)
{
_states.push_back(state);
_init = false;
}
/**
* Pops the last state from the top of the stack. Since states
* can't actually be deleted mid-cycle, it's moved into a separate queue
* which is cleared at the start of every cycle, so the transition
* is seamless.
*/
void Game::popState()
{
_deleted.push_back(_states.back());
_states.pop_back();
_init = false;
}
/**
* Returns the language currently in use by the game.
* @return Pointer to the language.
*/
Language *Game::getLanguage() const
{
return _lang;
}
/**
* Changes the language currently in use by the game.
* @param filename Filename of the language file.
*/
void Game::loadLanguage(const std::string &filename)
{
std::stringstream ss, ss2;
ss << "Language/" << filename << ".lng";
ss2 << "Language/" << filename << ".geo";
_lang->loadLng(CrossPlatform::getDataFile(ss.str()));
std::auto_ptr sidebar(new Surface(64, 154));
if (CrossPlatform::getDataFile(ss2.str()) != "")
{
sidebar->setPalette(_res->getSurface("GEOBORD.SCR")->getPalette());
sidebar->loadScr(CrossPlatform::getDataFile(ss2.str()));
}
sidebar->setX(256);
sidebar->setY(0);
sidebar->blit(_res->getSurface("GEOBORD.SCR"));
Options::setString("language", filename);
}
/**
* Returns the resource pack currently in use by the game.
* @return Pointer to the resource pack.
*/
ResourcePack *Game::getResourcePack() const
{
return _res;
}
/**
* Sets a new resource pack for the game to use.
* @param res Pointer to the resource pack.
*/
void Game::setResourcePack(ResourcePack *res)
{
_res = res;
}
/**
* Returns the saved game currently in use by the game.
* @return Pointer to the saved game.
*/
SavedGame *Game::getSavedGame() const
{
return _save;
}
/**
* Sets a new saved game for the game to use.
* @param save Pointer to the saved game.
*/
void Game::setSavedGame(SavedGame *save)
{
delete _save;
_save = save;
}
/**
* Returns the ruleset currently in use by the game.
* @return Pointer to the ruleset.
*/
Ruleset *Game::getRuleset() const
{
return _rules;
}
/**
* Changes the ruleset currently in use by the game.
* @param filename Filename of the language file.
*/
void Game::loadRuleset()
{
_rules = new Ruleset();
std::vector rulesets = Options::getRulesets();
for (std::vector::iterator i = rulesets.begin(); i != rulesets.end(); ++i)
{
_rules->load(*i);
}
}
/**
* Sets whether the mouse is activated.
* If it is, mouse events are processed, otherwise
* they are ignored and the cursor is hidden.
* @param active Is mouse activated?
*/
void Game::setMouseActive(bool active)
{
_mouseActive = active;
_cursor->setVisible(active);
}
/**
* Sets the value of alienContainmentHasUpperLimit.
*/
void Game::setAlienContainmentHasUpperLimit(bool setting)
{
_alienContainmentHasUpperLimit = setting;
}
/**
* Gets the value of alienContainmentHasUpperLimit.
* @return if alienContainmentHasUpperLimit is true.
*/
bool Game::getAlienContainmentHasUpperLimit() const
{
return _alienContainmentHasUpperLimit;
}
/**
* @brief Returns whether current state is *state
* @param state The state to test against the stack state
*/
bool Game::isState(State *state) const
{
return _states.size() > 0 && _states.back() == state;
}
/**
* @return whether the game is shutting down or not.
*/
bool Game::isQuitting() const
{
return _quit;
}
}