/*
  File name: CCytadelaMain.cpp
  Copyright: (C) 2005 - 2010 Tomasz Kazmierczak

  This file is part of Cytadela

 *   This program 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.                                   *
 *                                                                         *
 *   This program 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 this program. If not, see <http://www.gnu.org/licenses/>.  *

*/

#include "CCytadelaMain.h"
#include "CCytadelaMenu.h"
#include <memory.h>
#include <dirent.h>
#include <sys/types.h>
#include <iostream>
#include <stdio.h>

using namespace std;

CCytadelaMain::CCytadelaMain(const char *sysDirPath)
{
	memset(mState.ammo, 0, sizeof(mState.ammo));
	mLevel = NO_LEVEL;
	mState.difficult = true;
	mState.totalTime = 0;
	mState.energy = 0;
	mState.killed = 0;
	mState.bomb = 0;
	mSDL = CSDLClass::instance();
	mLocalization = Localization::instance();
	strcpy(mSysDirPath, sysDirPath);

	mLevels[0] = "obj/Podziemia1.3dg";
	mLevels[1] = "obj/Podziemia2.3dg";
	mLevels[2] = "obj/Elektrownia1.3dg";
	mLevels[3] = "obj/Elektrownia2.3dg";
	mLevels[4] = "obj/Elektrownia3.3dg";
	mLevels[5] = "obj/Magazyny1.3dg";
	mLevels[6] = "obj/Magazyny2.3dg";
	mLevels[7] = "obj/Magazyny3.3dg";
	mLevels[8] = "obj/Hangar1.3dg";
	mLevels[9] = "obj/Hangar2.3dg";
	mLevels[10] = "obj/Hangar3.3dg";
	mLevels[11] = "obj/Laboratoria1.3dg";
	mLevels[12] = "obj/Laboratoria2.3dg";
	mLevels[13] = "obj/Laboratoria3.3dg";
	mLevels[14] = "obj/Kanaly1.3dg";
	mLevels[15] = "obj/Kanaly2.3dg";
	mLevels[16] = "obj/Kanaly3.3dg";
	mLevels[17] = "obj/Wiezienie1.3dg";
	mLevels[18] = "obj/Wiezienie2.3dg";
	mLevels[19] = "obj/Wiezienie3.3dg";
	mLevels[20] = "obj/Centrum.3dg";
	mLevels[21] = "obj/PodziemiaT.3dg";
	mLevels[22] = "obj/MagazynyT1.3dg";
	mLevels[23] = "obj/MagazynyT2.3dg";
	mLevels[24] = "obj/LaboratoriaT.3dg";
	mLevels[25] = "obj/KanalyT.3dg";

	mCplxStatus[SEWERS] = ACTIVE;
	mCplxStatus[LABORATORY] = ACTIVE;
	mCplxStatus[POWER_PLANT] = ACTIVE;
	mCplxStatus[STORES] = INACTIVE;
	mCplxStatus[HANGAR] = INACTIVE;
	mCplxStatus[PRISON] = INACTIVE;
	mCplxStatus[CENTER] = INACTIVE;

	mBeginLevel[SEWERS] = LEVEL06A;
	mBeginLevel[LABORATORY] = LEVEL05A;
	mBeginLevel[POWER_PLANT] = LEVEL02A;
	mBeginLevel[STORES] = LEVEL03A;
	mBeginLevel[HANGAR] = LEVEL04A;
	mBeginLevel[PRISON] = LEVEL07A;
	mBeginLevel[CENTER] = LEVEL08A;
	mCplxCounter = 1;
	mActualComplex = 0;
	mState.exitGame = false;
}

void CCytadelaMain::reset(CCytadelaConfig *config)
{
	mCplxStatus[SEWERS] = ACTIVE;
	mCplxStatus[LABORATORY] = ACTIVE;
	mCplxStatus[POWER_PLANT] = ACTIVE;
	mCplxStatus[STORES] = INACTIVE;
	mCplxStatus[HANGAR] = INACTIVE;
	mCplxStatus[PRISON] = INACTIVE;
	mCplxStatus[CENTER] = INACTIVE;
	memset(mState.ammo, 0, sizeof(mState.ammo));
	mCplxCounter = 1;
	mActualComplex = 0;
	mState.bomb = 0;
	mState.energy = 150;
	mState.weapons = 0;
	mState.killed = 0;
	mState.totalTime = 0;
	mState.exitGame = false;
	mState.difficult = config->isDifficult();
	mState.visionNoise = true;
	for(int32_t i = 0; i < TEXT_CLASSES_COUNT; ++i)
		mTexts[i].resetTextCounter();
}

void CCytadelaMain::setLevelTexts()
{
	mSaveNames[0] = mLocalization->getText(LOC_SAVE_DUNGEONS_1);
	mSaveNames[1] = mLocalization->getText(LOC_SAVE_DUNGEONS_2);
	mSaveNames[2] = mLocalization->getText(LOC_SAVE_POWER_PLANT_1);
	mSaveNames[3] = mLocalization->getText(LOC_SAVE_POWER_PLANT_2);
	mSaveNames[4] = mLocalization->getText(LOC_SAVE_POWER_PLANT_3);
	mSaveNames[5] = mLocalization->getText(LOC_SAVE_STORES_1);
	mSaveNames[6] = mLocalization->getText(LOC_SAVE_STORES_2);
	mSaveNames[7] = mLocalization->getText(LOC_SAVE_STORES_3);
	mSaveNames[8] = mLocalization->getText(LOC_SAVE_HANGAR_1);
	mSaveNames[9] = mLocalization->getText(LOC_SAVE_HANGAR_2);
	mSaveNames[10] = mLocalization->getText(LOC_SAVE_HANGAR_3);
	mSaveNames[11] = mLocalization->getText(LOC_SAVE_LABORATORY_1);
	mSaveNames[12] = mLocalization->getText(LOC_SAVE_LABORATORY_2);
	mSaveNames[13] = mLocalization->getText(LOC_SAVE_LABORATORY_3);
	mSaveNames[14] = mLocalization->getText(LOC_SAVE_SEWERS_1);
	mSaveNames[15] = mLocalization->getText(LOC_SAVE_SEWERS_2);
	mSaveNames[16] = mLocalization->getText(LOC_SAVE_SEWERS_3);
	mSaveNames[17] = mLocalization->getText(LOC_SAVE_PRISON_1);
	mSaveNames[18] = mLocalization->getText(LOC_SAVE_PRISON_2);
	mSaveNames[19] = mLocalization->getText(LOC_SAVE_PRISON_3);
	mSaveNames[20] = mLocalization->getText(LOC_SAVE_CENTER_1);

	mCplxNames[SEWERS] = mLocalization->getText(LOC_CPLX_SEWERS);
	mCplxNames[LABORATORY] = mLocalization->getText(LOC_CPLX_LABORATORY);
	mCplxNames[POWER_PLANT] = mLocalization->getText(LOC_CPLX_POWER_PLANT);
	mCplxNames[STORES] = mLocalization->getText(LOC_CPLX_STORES);
	mCplxNames[HANGAR] = mLocalization->getText(LOC_CPLX_HANGAR);
	mCplxNames[PRISON] = mLocalization->getText(LOC_CPLX_PRISON);
	mCplxNames[CENTER] = mLocalization->getText(LOC_CPLX_CENTER);

	mCplxStatusName[ACTIVE] = mLocalization->getText(LOC_CPLX_STATUS_UNEXPLORED);
	mCplxStatusName[INACTIVE] = mLocalization->getText(LOC_CPLX_STATUS_NO_ACCESS);
	mCplxStatusName[FINISHED] = mLocalization->getText(LOC_CPLX_STATUS_EXPLORED);
	mCplxStatusName[CENTERINACTIVE] = mLocalization->getText(LOC_CPLX_STATUS_NO_ACCESS);
}

bool CCytadelaMain::configScreen(CCytadelaConfig *config)
{
	fprintf(stdout, "------------------------------------------------------------\n");
	fprintf(stdout, "Reading the OpenGL vrsion and extensions...   \n");
	//switch to OpenGL mode to check the version
	if(not mSDL->switchVideoMode(CSDL_DEFAULT_SCREEN_W, CSDL_DEFAULT_SCREEN_H, MODE_3D)) {
		fprintf(stdout, "\nError: Could not initialize OpenGL!\nExiting...\n");
		return false;
	}

	std::string version((const char *)glGetString(GL_VERSION));
	std::string renderer((const char *)glGetString(GL_RENDERER));
	std::string extensions((const char *)glGetString(GL_EXTENSIONS));

	fprintf(stdout, "Done\n");
	fprintf(stdout, "Switching to 2D video mode...   ");
	//now switch to the 2D mode
	if(not mSDL->switchVideoMode(CSDL_DEFAULT_SCREEN_W, CSDL_DEFAULT_SCREEN_H, MODE_2D)) {
		fprintf(stdout, "\nError: Could not initialize SDL Video mode!\nExiting...\n");
		return false;
	}
	fprintf(stdout, "Ok!\n");

	fprintf(stdout, "Initializing configuration menu...\n");
	uint32_t imgNum, fontImgNum;
	//load startup screen
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "config.tga", imgNum)) {
		fprintf(stdout, "\nError: Could not load menu image!\nExiting...\n");
		return false;
	}
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "configfont.tga", fontImgNum)) {
		mSDL->mImgArray.free();
		fprintf(stdout, "\nError: Could not load font image!\nExiting...\n");
		return false;
	}
	int32_t sound = mSDL->loadSound("sounds/configmenu.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound) {
		mSDL->mImgArray.free();
		fprintf(stdout, "\nError: Could not load menu sound!\nExiting...\n");
		return false;
	}
	fprintf(stdout, "Done\n");

	mSDL->playSoundFadeIn(sound, sound, -1, 500);
	mSDL->displayImage(imgNum, 0, 0, 0);
	mSDL->displayText(fontImgNum, "  CYTADELA CONFIGURATION", 112, 90);

	fprintf(stdout, "Cheching the OpenGL version and extensions...\n\n");
	//OpenGL version and extensions
	char versionString[4];
	memcpy(versionString, version.c_str(), 3 * sizeof(char));
	versionString[3] = '\0';
	float openGLVersion = atof(versionString);

	mSDL->displayText(fontImgNum, "GL VERSION: ", 80, 130);
	mSDL->displayText(fontImgNum, version.c_str(), 272, 130);
	mSDL->displayText(fontImgNum, "GL RENDERER: ", 64, 150);
	mSDL->displayText(fontImgNum, renderer.c_str(), 272, 150);
	//check for version and extensions
	bool versionOK = true;
	if(openGLVersion < 1.4f) {
		if(openGLVersion < 1.2f) {
			versionOK = false;
			mSDL->displayText(fontImgNum, "CYTADELA REQUIRES AT LEAST OPENGL 1.2!", 16, 230);
			mSDL->displayText(fontImgNum, "PRESS \'ENTER\' OR \'ESC\' TO EXIT", 80, 250);
			goto AfterCheck;
		}
		mSDL->displayText(fontImgNum, "NEEDED OPENGL EXTENSIONS: ", 96, 190);
		mSDL->displayText(fontImgNum, "GL_ARB_TEXTURE_MIRRORED_REPEAT: ", 8, 210);
		if(checkGLExtension("GL_ARB_texture_mirrored_repeat", (const GLubyte *)extensions.c_str()))
			mSDL->displayText(fontImgNum, "YES", 536, 210);
		else {
			versionOK = false;
			mSDL->displayText(fontImgNum, "NO", 536, 210);
			mSDL->displayText(fontImgNum, "PRESS \'ENTER\' OR \'ESC\' TO EXIT", 80, 320);
		}
		if(openGLVersion == 1.3f)
			goto AfterCheck;
		mSDL->displayText(fontImgNum, "GL_ARB_TEXTURE_BORDER_CLAMP: ", 8, 230);
		if(checkGLExtension("GL_ARB_texture_border_clamp", (const GLubyte *)extensions.c_str()))
			mSDL->displayText(fontImgNum, "YES", 536, 230);
		else {
			versionOK = false;
			mSDL->displayText(fontImgNum, "NO", 536, 230);
			mSDL->displayText(fontImgNum, "PRESS \'ENTER\' OR \'ESC\' TO EXIT", 80, 320);
		}
		mSDL->displayText(fontImgNum, "GL_ARB_MULTITEXTURE: ", 8, 250);
		if(checkGLExtension("GL_ARB_multitexture", (const GLubyte *)extensions.c_str()))
			mSDL->displayText(fontImgNum, "YES", 536, 250);
		else {
			versionOK = false;
			mSDL->displayText(fontImgNum, "NO", 536, 250);
			mSDL->displayText(fontImgNum, "PRESS \'ENTER\' OR \'ESC\' TO EXIT", 80, 320);
		}
		mSDL->displayText(fontImgNum, "GL_ARB_TEXTURE_ENV_ADD: ", 8, 270);
		if(checkGLExtension("GL_ARB_texture_env_add", (const GLubyte *)extensions.c_str()))
			mSDL->displayText(fontImgNum, "YES", 536, 270);
		else {
			versionOK = false;
			mSDL->displayText(fontImgNum, "NO", 536, 270);
			mSDL->displayText(fontImgNum, "PRESS \'ENTER\' OR \'ESC\' TO EXIT", 80, 320);
		}
		mSDL->displayText(fontImgNum, "GL_ARB_TEXTURE_ENV_COMBINE: ", 8, 290);
		if(checkGLExtension("GL_ARB_texture_env_combine", (const GLubyte *)extensions.c_str()))
			mSDL->displayText(fontImgNum, "YES", 536, 290);
		else {
			versionOK = false;
			mSDL->displayText(fontImgNum, "NO", 536, 290);
			mSDL->displayText(fontImgNum, "PRESS \'ENTER\' OR \'ESC\' TO EXIT", 80, 320);
		}
	}

	fprintf(stdout, "OpenGL version: %s\nOpenGL renderer: %s\n", version.c_str(), renderer.c_str());
	AfterCheck:

	if(not versionOK) {
		mSDL->drawFrame();
		SDLKey key;
		while(true) {
			key = mSDL->readInputMenu();
			if(key == SDLK_RETURN or key == SDLK_ESCAPE)
				break;
		}
		fprintf(stdout, "\nOpenGL version to low or missing some needed GL extensions - exiting...\n");
		mSDL->stopSound(sound, 500);
		mSDL->delay(500);
		mSDL->freeSounds();
		mSDL->mImgArray.free();
		return false;
	}

	//localizations
	fprintf(stdout, "Checking for available localizations (at \"locale/\")...   ");

	DIR *directory = opendir("locale");
	if(directory == 0) {
		fprintf(stdout, "\nError: could not open \"locale/\" directory!\nExiting...\n");
		mSDL->freeSounds();
		mSDL->mImgArray.free();
		return false;
	}

	//+1 because the first will be the CYTADELA_DEFAULT_LOCALIZATION
	long localesCount = 1;
	long filesCount = 0;
	while(true) {
		dirent *dirEntry = readdir(directory);
		if(dirEntry == 0)
			break;
		filesCount++;
		if(strstr(dirEntry->d_name, ".loc"))
			localesCount++;
	}
	rewinddir(directory);
	//strings to store filenames
	string *entryNames = new string[localesCount];
	if(entryNames == 0) {
		fprintf(stdout, "\nError: out of memory!\nExiting...\n");
		mSDL->freeSounds();
		mSDL->mImgArray.free();
		closedir(directory);
		return false;
	}
	entryNames[0] = CYTADELA_DEFAULT_LOCALIZATION;

	for(long l = 0, m = 1; l < filesCount; ++l) {
		dirent *dirEntry = readdir(directory);
		if(strstr(dirEntry->d_name, ".loc")) {
			entryNames[m] = dirEntry->d_name;
			//remove the extension from the filename
			string::size_type stPos = entryNames[m].find(".loc");
			entryNames[m].erase(stPos);
			m++;
		}
	}

	closedir(directory);
	fprintf(stdout, "Done\n");

	//find the position of localization read from config file (set it initially
	//to the default value in case it is not found in the config)
	long chosenLocalization = 0;
	for(long l = 0; l < localesCount; ++l) {
		if(entryNames[l].find(config->getLocalization(), 0) != string::npos) {
			chosenLocalization = l;
			break;
		}
	}

	fprintf(stdout, "Entering the config menu main loop...\n");
	SDLKey key;
	char localizationName[FILENAME_MAX];
	mSDL->clearEventQueue();
	while(true) {
		key = mSDL->readInputMenu();
		//if user chose to exit
		if(key == SDLK_ESCAPE) {
			delete[] entryNames;
			fprintf(stdout, "Program terminated by user - exiting...\n");
			mSDL->stopSound(sound, 500);
			mSDL->delay(500);
			mSDL->freeSounds();
			mSDL->mImgArray.free();
			return false;
		}
		if(key == SDLK_RETURN) {
			sprintf(localizationName, entryNames[chosenLocalization].c_str());
			break;
		}
		if(key == SDLK_UP) {
			chosenLocalization--;
			if(chosenLocalization < 0)
				chosenLocalization = 0;
		}
		if(key == SDLK_DOWN) {
			chosenLocalization++;
			if(chosenLocalization == localesCount)
				chosenLocalization = localesCount-1;
		}
		//redraw the localization menu
		SDL_Rect srcRect = {0, 310, 640, 480};
		mSDL->displayImage(imgNum, &srcRect, 0, 310);
		mSDL->displayText(fontImgNum, "CHOOSE LANGUAGE:", 96, 370);
		mSDL->displayText(fontImgNum, "(CURSOR UP/DOWN)", 96, 390);
		for(long l = 0; l < localesCount; ++l) {
			//display only 3 before and 3 after the chosen
			if(l > chosenLocalization - 2 and l < chosenLocalization + 4) {
				mSDL->displayText(fontImgNum, (const char *)entryNames[l].c_str(), 400,
									370 + (l - chosenLocalization)*20);
			}
		}
		int16_t posL = 0;
		int16_t posR = 16 * entryNames[chosenLocalization].length();
		if(mSDL->getTicks() % 1000 > 500) {
			posL += 8;
			posR -= 8;
		}
		mSDL->displayText(fontImgNum, ">", 368 + posL, 370);
		mSDL->displayText(fontImgNum, " <", 400 + posR, 370);

		if(mSDL->getTicks() % 500 > 250) {
			if(chosenLocalization > 1)
				mSDL->displayText(fontImgNum, "^", 400, 330);
			if(localesCount > chosenLocalization + 4)
				mSDL->displayText(fontImgNum, "v", 400, 450);
		}

		mSDL->drawFrame();
	}
	delete[] entryNames;

	//NOTE: localization can be set only once and it should be done at the begining!
	if(not mLocalization->loadLocalization(localizationName, mTexts)) {
		fprintf(stderr, "Error while loading localization from locale/%s.txt: %s\n"
		                "Using default (%s) localization\n", localizationName,
		                getLocErrorDesc(mLocalization->getLastError()),
		                CYTADELA_DEFAULT_LOCALIZATION);
		mSDL->displayImage(imgNum, 0, 0, 0);

		mSDL->displayText(fontImgNum, "ERROR:", 272, 170);
		mSDL->displayText(fontImgNum, "COULD NOT LOAD CHOSEN LOCALIZATION:", 48, 210);
		mSDL->displayText(fontImgNum, localizationName, 272, 230);
		char errorText[FILENAME_MAX];
		sprintf(errorText, "USING DEFAULT (%s) LOCALIZATION", CYTADELA_DEFAULT_LOCALIZATION);
		mSDL->displayText(fontImgNum, errorText, 48, 270);
		mSDL->drawFrame();
		mLocalization->setDefaultLocalization();
		while(SDLK_RETURN != mSDL->readInputMenu());
	}
	else fprintf(stdout, "Chosen localization: %s\n", localizationName);
	config->setLocalization(localizationName);

	fprintf(stdout, "Initial check and configuration finished successfuly!\n");
	fprintf(stdout, "------------------------------------------------------------\n");
	mSDL->stopSound(sound, 500);
	mSDL->delay(500);
	mSDL->freeSounds();
	mSDL->mImgArray.free();
	return true;
}

bool CCytadelaMain::startupScreen()
{
	fprintf(stdout, "Displaying startup screen:\n");
	fprintf(stdout, "Initializing...   \n");
	uint32_t imgNum;
	//load startup screen
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "cytadela.tga", imgNum))
		return false;
	if(not mSDL->loadMusic("music/Intro.mod")) {
		mSDL->mImgArray.free();
		return false;
	}
	fprintf(stdout, "   Done!\n");
	mSDL->playMusic(1);
	//display it (with animation)
	fprintf(stdout, "Running the animation...\n");
	uint32_t y = 0;
	uint32_t t0 = mSDL->getTicks();
	uint32_t beginTime = t0;
	while(y < 480) {
		y = (mSDL->getTicks() - t0);
		if(y > 480)
			y = 480;
		mSDL->blackScreen();
		mSDL->displayImage(imgNum, 0, 0, 480 - y);
		mSDL->drawFrame();
	}
	y = 0;
	t0 = mSDL->getTicks();
	while(y <= 50) {
		y = (mSDL->getTicks() - t0) / 10;
		mSDL->blackScreen();
		mSDL->displayImage(imgNum, 0, 0, y);
		mSDL->drawFrame();
	}
	t0 = mSDL->getTicks();
	while(y < 480) {
		y = 430 + (mSDL->getTicks() - t0) / 6;
		if(y > 480)
			y = 480;
		mSDL->blackScreen();
		mSDL->displayImage(imgNum, 0, 0, 480 - y);
		mSDL->drawFrame();
	}
	y = 0;
	t0 = mSDL->getTicks();
	while(y <= 10) {
		y = (mSDL->getTicks() - t0) / 30;
		mSDL->blackScreen();
		mSDL->displayImage(imgNum, 0, 0, y);
		mSDL->drawFrame();
	}
	t0 = mSDL->getTicks();
	while(y < 480) {
		y = 470 + (mSDL->getTicks() - t0) / 20;
		if(y > 480)
			y = 480;
		mSDL->blackScreen();
		mSDL->displayImage(imgNum, 0, 0, 480 - y);
		mSDL->drawFrame();
	}
	mSDL->displayImage(imgNum, 0, 0, 0);
	mSDL->drawFrame();
	fprintf(stdout, "Animation finished.\n");
	mSDL->clearEventQueue();
	//we wait till user presses some key or time elapses
	while(SDLK_RETURN != mSDL->readInputMenu() and (mSDL->getTicks() - beginTime) < 17500);
	fprintf(stdout, "Scrolling down...\n");
	//scroll down
	y = 0;
	t0 = mSDL->getTicks();
	while(y <= 480) {
		y = (mSDL->getTicks() - t0);
		mSDL->blackScreen();
		mSDL->displayImage(imgNum, 0, 0, y);
		mSDL->drawFrame();
	}
	mSDL->stopMusic();
	mSDL->freeMusic();
	mSDL->mImgArray.free();
	fprintf(stdout, "Done with the startup screen!\n");
	fprintf(stdout, "------------------------------------------------------------\n");
	return true;
}

bool CCytadelaMain::controllersScreen()
{
	uint32_t imgNum;
	//load the first screen
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "info1.tga", imgNum))
		return false;

	//display it (with animation)
	uint32_t y = 0;
	uint32_t t0 = mSDL->getTicks();
	while(y < 480) {
		y = (mSDL->getTicks() - t0);
		if(y > 480)
			y = 480;
		mSDL->blackScreen();
		mSDL->displayImage(imgNum, 0, 0, 480 - y);
		mSDL->drawFrame();
	}
	//wait till user presses some key or the time elapses
	uint32_t beginTime = mSDL->getTicks();
	while(SDLK_RETURN != mSDL->readInputMenu() and (mSDL->getTicks() - beginTime) < 5000);

	//load the second screen
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "info2.tga", imgNum)) {
		mSDL->mImgArray.free();
		return false;
	}
	//display it (with animation)
	y = 0;
	t0 = mSDL->getTicks();
	while(y < 480) {
		y = (mSDL->getTicks() - t0);
		if(y > 480)
			y = 480;
		mSDL->blackScreen();
		mSDL->displayImage(imgNum, 0, 0, 480 - y);
		mSDL->drawFrame();
	}
	//wait till user presses some key or time elapses
	beginTime = mSDL->getTicks();
	while(SDLK_RETURN != mSDL->readInputMenu() and (mSDL->getTicks() - beginTime) < 5000);

	mSDL->blackScreen();
	mSDL->mImgArray.free();
	return true;
}

bool CCytadelaMain::endLevel()
{
	uint32_t imgNum, font, endCur, saveCur;
	//load end-level screen, etc.
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "info.tga", imgNum))
		return false;
	//load font for chosen localization
	char fontPath[FILENAME_MAX];
	sprintf(fontPath, "tex/fonts/%s.far", mLocalization->getCurrentLocalization());
	if(not mSDL->mImgArray.loadImg(fontPath, "endfont.tga", font))
		return false;
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "endcur.tga", endCur))
		return false;
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "savecur.tga", saveCur))
		return false;
	if(not mSDL->loadMusic("music/EndLevel.mod"))
		return false;
	mSDL->displayImage(imgNum, 0, 0, 0);
	mSDL->drawFrame();
	mSDL->playMusic(-1);
	//display text
	char text[40];
	sprintf(text, mSaveNames[mLevel]);
	char levelNum = text[strlen(text) - 1];
	text[strlen(text) - 1] = 0;
	sprintf(text, "%s%s %c %s!", text, mLocalization->getText(LOC_END_LEVEL),
	        levelNum, mLocalization->getText(LOC_END_COMPLETED));
	mSDL->displayText(font, text, 626 - 16 * strlen(text), 92);
	mSDL->displayText(font, mLocalization->getText(LOC_END_KILLED), 20, 212);
	mSDL->displayText(font, mLocalization->getText(LOC_END_TIME), 20, 242);
	mSDL->displayText(font, mLocalization->getText(LOC_END_BOMB), 20, 272);
	sprintf(text, "%d", mState.killed);
	mSDL->displayText(font, text, 484, 212);
	mState.totalTime /= 1000;
	uint32_t hours = mState.totalTime / 3600;
	uint32_t minutes = (mState.totalTime - hours * 3600) / 60;
	uint32_t seconds = mState.totalTime - hours * 3600 - minutes * 60;
	sprintf(text, "%d:", hours);
	if(minutes >= 10)
		sprintf(text + 2, "%d:", minutes);
	else sprintf(text + 2, "0%d:", minutes);
	if(seconds >= 10)
		sprintf(text + 5, "%d", seconds);
	else sprintf(text + 5, "0%d", seconds);
	mSDL->displayText(font, text, 484, 242);
	sprintf(text, "%d", mState.bomb);
	mSDL->displayText(font, text, 484, 272);
	mSDL->displayText(font, mLocalization->getText(LOC_END_SAVE_QUESTION), 52, 362);
	mSDL->displayText(font, mLocalization->getText(LOC_END_NO_YES), 212, 398);
	mSDL->drawFrame();
	bool save = false;
	uint32_t imgNums[] = {imgNum, font, endCur, saveCur};
	mSDL->clearEventQueue();
	while(true) {
		SDLKey key = mSDL->readInputMenu();
		if(key == SDLK_ESCAPE) {
			mState.exitGame = true;
			break;
		}
		if(key == SDLK_RIGHT or key == SDLK_LEFT)
			save = not save;
		if(key == SDLK_RETURN or key == SDLK_SPACE) {
			if(save) {
				if(not saveScreen(imgNums)) {
					mSDL->stopMusic();
					mSDL->freeMusic();
					mSDL->mImgArray.free();
					return false;
				}
				break;
			}
			else break;
		}
		//animate cursor
		SDL_Rect srcRect = {0, 390, 640, 90};
		mSDL->displayImage(imgNum, &srcRect, 0, 390);
		mSDL->displayText(font, mLocalization->getText(LOC_END_NO_YES), 212, 398);
		srcRect.x = 0;
		srcRect.y = ((mSDL->getTicks() / 500) & 1) * 34;
		srcRect.w = 52;
		srcRect.h = 34;
		mSDL->displayImage(endCur, &srcRect, 210 + save * 192, 394);
		mSDL->drawFrame();
	}
	mSDL->stopMusic();
	mSDL->blackScreen();
	mSDL->drawFrame();
	mSDL->freeMusic();
	mSDL->mImgArray.free();
	return true;
}

bool CCytadelaMain::saveScreen(uint32_t *imgNums)
{
	bool endLoop = false;
	uint32_t num = 0;
	CytadelaSaveFile saveFile;
	if(not saveFile.read(mSysDirPath))
		return false;
	mSDL->clearEventQueue();
	while(not endLoop) {
		mSDL->displayImage(imgNums[0], 0, 0, 0);
		mSDL->displayText(imgNums[1], mLocalization->getText(LOC_END_SAVE_CHOOSE_POS), 132, 72);
		for(uint32_t i = 0; i < NumberOfSaves; ++i) {
			uint8_t level = saveFile.getSave(i).level;
			if(level == NO_LEVEL)
				mSDL->displayText(imgNums[1], mLocalization->getText(LOC_SAVE_EMPTY),
				                  196, 132 + 40 * i);
			else mSDL->displayText(imgNums[1], mSaveNames[level], 196, 132 + 40 * i);
		}
		mSDL->displayText(imgNums[1], mLocalization->getText(LOC_END_SAVE_QUIT), 244, 352);
		//animate cursor
		SDL_Rect srcRect = {0, ((mSDL->getTicks() / 500) & 1) * 34, 260, 34};
		if(num != NumberOfSaves)
			mSDL->displayImage(imgNums[3], &srcRect, 192, 128 + num * 40);
		else
			mSDL->displayImage(imgNums[3], &srcRect, 192, 348);
		mSDL->drawFrame();
		//read input
		SDLKey key = mSDL->readInputMenu();
		switch(key) {
			case SDLK_UP: {
				if(num == 0) num = NumberOfSaves;
				else num--;
				break;
			}
			case SDLK_DOWN: {
				if(num == NumberOfSaves) num = 0;
				else num++;
				break;
			}
			case SDLK_ESCAPE:
			case SDLK_RETURN:
			case SDLK_SPACE: {
				if(num < NumberOfSaves) {
					SaveStruct save;
					//save all needed info
					for(int32_t i = 0; i < NumberOfComplexes; ++i)
						save.cplxStatus[i] = mCplxStatus[i];
					for(int32_t i = 0; i < NumberOfWeaponTypes; ++i)
						save.ammo[i] = mState.ammo[i];
					save.level = mLevel;
					save.difficult = mState.difficult;
					save.actualComplex = mActualComplex;
					save.bomb = mState.bomb;
					save.cplxCounter = mCplxCounter;
					save.energy = mState.energy;
					save.weapons = mState.weapons;
					save.visionNoise = mState.visionNoise;
					saveFile.setSave(num, save);
					if(not saveFile.write(mSysDirPath))
						return false;
					//display new save name in list
					mSDL->displayImage(imgNums[0], 0, 0, 0);
					mSDL->displayText(imgNums[1],
					                  mLocalization->getText(LOC_END_SAVE_CHOOSE_POS), 132, 72);
					for(uint32_t i = 0; i < NumberOfSaves; ++i) {
						uint8_t level = saveFile.getSave(i).level;
						if(level == NO_LEVEL)
							mSDL->displayText(imgNums[1], mLocalization->getText(LOC_SAVE_EMPTY),
							                  196, 132 + 40 * i);
						else mSDL->displayText(imgNums[1], mSaveNames[level], 196, 132 + 40 * i);
					}
					mSDL->displayText(imgNums[1],
					                  mLocalization->getText(LOC_END_SAVE_QUIT), 244, 352);
					mSDL->drawFrame();
					mSDL->delay(1000);
					//display "ok!"
					mSDL->displayImage(imgNums[0], 0, 0, 0);
					mSDL->displayText(imgNums[1], "OK!", 308, 192);
					mSDL->drawFrame();
					mSDL->delay(1500);
				}
				endLoop = true;
				break;
			}
			default:
				break;
		}
	}
	return true;
}

bool CCytadelaMain::chooseComplex()
{
	uint32_t mapImg, cursorImg, fontImg;
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "map.tga", mapImg))
		return false;
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "mapcur.tga", cursorImg))
		return false;
	//load font for chosen localization
	char fontPath[FILENAME_MAX];
	sprintf(fontPath, "tex/fonts/%s.far", mLocalization->getCurrentLocalization());
	if(not mSDL->mImgArray.loadImg(fontPath, "mapfont.tga", fontImg))
		return false;
	if(not mSDL->loadMusic("music/Map.mod"))
		return false;
	mSDL->displayImage(mapImg, 0, 0, 0);
	mSDL->drawFrame();
	mSDL->playMusic(-1);
	uint32_t num = mActualComplex;
	uint32_t dwLastTime = mSDL->getTicks();
	uint32_t dwCursorFrame = 0;
	bool endLoop = false;
	char text[56];
	mSDL->clearEventQueue();
	//the status of the Center
	if(mCplxCounter == 7 and mState.bomb == 6)
		mCplxStatus[CENTER] = ACTIVE;
	while(not endLoop) {
		mSDL->displayImage(mapImg, 0, 0, 0);
		//animate cursor
		animateMapCursor(cursorImg, &dwLastTime, &dwCursorFrame, num);
		sprintf(text, "%s:%s", mLocalization->getText(LOC_CPLX_MENU_COMPLEX),
		        mCplxNames[num]);
		mSDL->displayText(fontImg, text, 8, 426);
		sprintf(text, "%s:%s", mLocalization->getText(LOC_CPLX_MENU_STATUS),
		        mCplxStatusName[mCplxStatus[num]]);
		mSDL->displayText(fontImg, text, 344, 426);
		mSDL->drawFrame();
		//read input
		SDLKey key = mSDL->readInputMenu();
		switch(key) {
			case SDLK_DOWN:
			case SDLK_LEFT: {
				if(num == 0) num = 6;
				else num--;
				break;
			}
			case SDLK_UP:
			case SDLK_RIGHT: {
				if(num == 6) num = 0;
				else num++;
				break;
			}
			case SDLK_ESCAPE: {
				mState.exitGame = true;
				endLoop = true;
				break;
			}
			case SDLK_RETURN:
			case SDLK_SPACE: {
				switch(mCplxStatus[num]) {
					case ACTIVE: {
						mLevel = mBeginLevel[num];
						mCplxStatus[num] = FINISHED;
						mActualComplex = num;
						mCplxCounter++;
						switch(mCplxCounter) {
							case 2:
								mCplxStatus[STORES] = ACTIVE;
								break;
							case 3:
								mCplxStatus[HANGAR] = ACTIVE;
								break;
							case 4:
								mCplxStatus[PRISON] = ACTIVE;
								break;
							case 5:
								mCplxStatus[CENTER] = CENTERINACTIVE;
								break;
						}
						uint32_t t0 = mSDL->getTicks();
						while(mSDL->getTicks() - t0 < 2000) {
							mSDL->displayImage(mapImg, 0, 0, 0);
							animateMapCursor(cursorImg, &dwLastTime, &dwCursorFrame, num);
							sprintf(text, "%s %s", mLocalization->getText(LOC_CPLX_MENU_CHOSEN),
							                                                mCplxNames[num]);
							//if power station - change 'A' to 'e' (for Polish localization;)
							if(num == 4 and strcmp(mLocalization->getCurrentLocalization(),
							                       CYTADELA_DEFAULT_LOCALIZATION) == 0)
								text[19] = 'e';
							mSDL->displayText(fontImg, text, 184, 426);
							mSDL->drawFrame();
						}
						endLoop = true;
						break;
					}
					case INACTIVE: {
						uint32_t t0 = mSDL->getTicks();
						while(mSDL->getTicks() - t0 < 2000) {
							mSDL->displayImage(mapImg, 0, 0, 0);
							animateMapCursor(cursorImg, &dwLastTime, &dwCursorFrame, num);
							mSDL->displayText(fontImg,
							                  mLocalization->getText(LOC_CPLX_MENU_NO_ACCESS_YET),
							                  8, 426);
							mSDL->drawFrame();
						}
						mSDL->displayText(fontImg, text, 8, 426);
						mSDL->clearEventQueue();
						break;
					}
					case FINISHED: {
						uint32_t t0 = mSDL->getTicks();
						while(mSDL->getTicks() - t0 < 2000) {
							mSDL->displayImage(mapImg, 0, 0, 0);
							animateMapCursor(cursorImg, &dwLastTime, &dwCursorFrame, num);
							mSDL->displayText(fontImg,
							                  mLocalization->getText(LOC_CPLX_MENU_ALREADY_EXPLORED),
							                  8, 426);
							mSDL->drawFrame();
						}
						mSDL->displayText(fontImg, text, 8, 426);
						mSDL->clearEventQueue();
						break;
					}
					case CENTERINACTIVE: {
						uint32_t t0 = mSDL->getTicks();
						while(mSDL->getTicks() - t0 < 2000) {
							mSDL->displayImage(mapImg, 0, 0, 0);
							animateMapCursor(cursorImg, &dwLastTime, &dwCursorFrame, num);
							mSDL->displayText(fontImg,
							                  mLocalization->getText(LOC_CPLX_MENU_NEED_WHOLE_BOMB),
							                  8, 426);
							mSDL->drawFrame();
						}
						mSDL->displayText(fontImg, text, 8, 426);
						mSDL->clearEventQueue();
						break;
					}
					default:
						break;
				}
				break;
			}
			default:
				break;
		}
	}
	mSDL->stopMusic();
	mSDL->blackScreen();
	mSDL->drawFrame();
	mSDL->freeMusic();
	mSDL->mImgArray.free();
	return true;
}

void CCytadelaMain::animateMapCursor(uint32_t cursorImg, uint32_t *lastTime,
                                     uint32_t *cursorFrame, uint32_t num)
{
	uint32_t curTime = mSDL->getTicks();
	uint32_t miliseconds = curTime - *lastTime;
	//now last time is current time minus difference between now and
	//the time when cursor should have been changed (and wasn't, cause
	//something else was beeing done in program)
	*lastTime = curTime - (miliseconds % 27);
	//how many times cursor should have changed since last time
	*cursorFrame += miliseconds / 27;
	while(*cursorFrame >= 24)
		*cursorFrame -= 24;
	SDL_Rect srcRect = {26 * (*cursorFrame), 0, 26, 38};
	uint16_t curPositions[NumberOfComplexes][2] = {{330, 164}, {106, 230}, {42, 108}, {170, 74}, {426, 48}, {554, 160}, {552, 282}};
	mSDL->displayImage(cursorImg, &srcRect, curPositions[num][0], curPositions[num][1]);
}

bool CCytadelaMain::gameOver()
{
	uint32_t bkgndImg, gameImg, overImg;
	//load startup screen
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "gameover.tga", bkgndImg))
		return false;
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "game.tga", gameImg))
		return false;
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "over.tga", overImg))
		return false;
	if(not mSDL->loadMusic("music/GameOver.mod"))
		return false;
	uint32_t diff = 0;
	bool secondScreen = false;
	mSDL->displayImage(bkgndImg, 0, 0, 0);
	mSDL->playMusic(1);
	uint32_t startTime = mSDL->getTicks();
	mSDL->drawFrame();
	mSDL->clearEventQueue();
	while(diff < 86000) {
		if(not secondScreen) {
			if(SDLK_RETURN == mSDL->readInputMenu() or diff >= 40000) {
				uint32_t t0 = mSDL->getTicks(), y = 0;
				while(y < 196) {
					SDL_Rect srcRect = {0, 196 - y, 624, 4 + y};
					mSDL->displayImage(gameImg, &srcRect, 8, 43);
					srcRect.y = 0;
					srcRect.h = y;
					mSDL->displayImage(overImg, &srcRect, 8, 439 - y);
					mSDL->drawFrame();
					y = ((mSDL->getTicks() - t0) * 196) / 1000;
				}
				mSDL->displayImage(gameImg, 0, 8, 43);
				mSDL->displayImage(overImg, 0, 8, 243);
				mSDL->drawFrame();
				secondScreen = true;
				mSDL->clearEventQueue();
			}
		}
		else if(SDLK_RETURN == mSDL->readInputMenu())
			break;
		diff = mSDL->getTicks() - startTime;
	}
	mSDL->stopMusic();
	mSDL->freeMusic();
	mSDL->blackScreen();
	mSDL->drawFrame();
	mSDL->mImgArray.free();
	return true;
}

bool CCytadelaMain::playIntro()
{
	mSDL->blackScreen();
	mSDL->drawFrame();

	bool ok;
	if(strcmp(mLocalization->getCurrentLocalization(), CYTADELA_DEFAULT_LOCALIZATION) == 0)
		ok = mSDL->playVideo("video/intro_pl.ogv", true, true);
	else ok = mSDL->playVideo("video/intro_en.ogv", true, true);
	if(not ok)
		return false;
	
	mSDL->blackScreen();
	mSDL->drawFrame();
	mSDL->delay(1000);
	
	return true;
}

bool CCytadelaMain::playOutro()
{
	mSDL->blackScreen();
	mSDL->drawFrame();
	mSDL->delay(1000);

	if(not mSDL->loadMusic("music/EndGame.mod"))
		return false;
	mSDL->playMusic(-1);

	bool ok;
	if(strcmp(mLocalization->getCurrentLocalization(), CYTADELA_DEFAULT_LOCALIZATION) == 0)
		ok = mSDL->playVideo("video/outro_pl.ogv", false, false);
	else ok = mSDL->playVideo("video/outro_en.ogv", false, false);
	if(not ok)
		return false;

	mSDL->stopMusic();
	mSDL->freeMusic();

	mSDL->blackScreen();
	mSDL->drawFrame();
	mSDL->delay(1000);

	return true;
}

//max 40 chars
bool CCytadelaMain::errorScreen(const char *errText)
{
	uint32_t fontImg;
	if(not mSDL->mImgArray.loadImg("tex/Menu.far", "configfont.tga", fontImg))
		return false;

	mSDL->blackScreen();
	mSDL->displayText(fontImg, errText, 320 - (strlen(errText) * 8), 200);
	mSDL->displayText(fontImg, "FOR MORE DESCRIPTIVE INFORMATION ON\n"
	                           "  THE ERROR PLEASE SEE THE GAME'S\n"
	                           "          STANDARD OUTPUT", 40, 300);
	mSDL->displayText(fontImg, "     PRESS 'ENTER' TO CONTINUE", 40, 460);
	mSDL->drawFrame();

	//wait for the user's input
	while(SDLK_RETURN != mSDL->readInputMenu());

	mSDL->blackScreen();
	mSDL->drawFrame();
	mSDL->mImgArray.free();

	return true;
}

void CCytadelaMain::runMain()
{
	fprintf(stdout, "Welcome to the Citadel!\n\n");
	CCytadelaConfig *config = CCytadelaConfig::instance();
	config->init(mSysDirPath);
	mSDL->setAudioEnabled(config->getAudioStatus());
	if(not configScreen(config))
		return;
	setLevelTexts();

	if(not playIntro()) {
		errorScreen("ERROR: COULDN'T DISPLAY THE INTRO!");
		fprintf(stdout, "\nAbnormal program termination!\n");
		return;
	}

	//display startup screen
	if(not startupScreen()) {
		errorScreen("ERROR: COULDN'T DISPLAY STARTUP SCREEN!");
		fprintf(stdout, "\nAbnormal program termination!\n");
		return;
	}
	mSDL->blackScreen();
	mSDL->drawFrame();
	CCytadelaMenu menu;
	CCytadelaGame game;
	COGLClass openGL;
	//menu<->gameplay switching loop
	while(true) {
		reset(config);

		//copy savenames
		CytadelaSaveFile saveFile;
		if(not saveFile.read(mSysDirPath)) {
			fprintf(stderr, "Failed to read save file - writing empty one\n");
			saveFile.reset();
			if(not saveFile.write(mSysDirPath)) {
				fprintf(stderr, "Error: Failed to write empty save file - exiting\n");
				//display information about the error that occured
				errorScreen("ERROR WHILE READING SAVE FILE!");
				break;
			}
		}
		const char *saveNames[NumberOfSaves];
		for(uint32_t i = 0; i < NumberOfSaves; ++i) {
			uint8_t level = saveFile.getSave(i).level;
			if(level == NO_LEVEL)
				saveNames[i] = mLocalization->getText(LOC_SAVE_EMPTY);
			else saveNames[i] = mSaveNames[level];
		}

		//init the menu
		if(not menu.initMenu(saveNames)) {
			menu.freeMenu();
			fprintf(stderr, "Error: Failed to initialize menu - exiting\n");
			//display information about the error that occured
			errorScreen("ERROR WHILE INITIALIZING MENU!");
			break;
		}
		//run the menu
		MenuAction result = menu.runMenu();
		menu.freeMenu();
		bool showControllersHelp = false;
		switch(result) {
			case MENUACT_EXIT:
				fprintf(stdout, "Program terminated on user request\n");
				return;
			case MENUACT_START_GAME:
				mLevel = LEVEL01A;
				break;
			case MENUACT_LOAD_1:
			case MENUACT_LOAD_2:
			case MENUACT_LOAD_3:
			case MENUACT_LOAD_4:
			case MENUACT_LOAD_5: {
				SaveStruct save = saveFile.getSave(result - MENUACT_LOAD_1);
				for(int32_t i = 0; i < NumberOfComplexes; ++i)
					mCplxStatus[i] = save.cplxStatus[i];
				for(int32_t i = 0; i < NumberOfWeaponTypes; ++i)
					mState.ammo[i] = save.ammo[i];
				mLevel = save.level;
				mState.difficult = save.difficult;
				mActualComplex = save.actualComplex;
				mState.bomb = save.bomb;
				mCplxCounter = save.cplxCounter;
				mState.energy = save.energy;
				mState.weapons = save.weapons;
				mState.visionNoise = save.visionNoise;
				goto loaded;
			}
			case MENUACT_START_TR1:
				mLevel = TRAINING1;
				showControllersHelp = true;
				break;
			case MENUACT_START_TR2:
				mLevel = TRAINING2;
				showControllersHelp = true;
				break;
			case MENUACT_START_TR3:
				mLevel = TRAINING3;
				showControllersHelp = true;
				break;
			case MENUACT_START_TR4:
				mLevel = TRAINING4;
				showControllersHelp = true;
				break;
			case MENUACT_START_TR5:
				mLevel = TRAINING5;
				showControllersHelp = true;
				break;
			default:
				break;
		}

		gameplay: {
			//display controllers screen
			if(showControllersHelp and not controllersScreen()) {
				errorScreen("ERROR: COULDN'T DISPLAY CONTROLLERS SCREEN!");
				fprintf(stdout, "\nAbnormal program termination!\n");
				return;
			}
			//check the configuration that might have changed in the menu
			const int32_t *screenSize = config->getScreenSize();
			mState.difficult = config->isDifficult();
			if(not mSDL->switchVideoMode(screenSize[0], screenSize[1], MODE_3D)) {
				if(not mSDL->switchVideoMode(CSDL_DEFAULT_SCREEN_W, CSDL_DEFAULT_SCREEN_H, MODE_2D))
					break;
				//display information about the error that occured
				if(not errorScreen("ERROR WHILE SETTING VIDEO MODE!"))
					break;
				continue;
			}
			if(not openGL.initOGL(screenSize[0], screenSize[1])) {
				openGL.freeOGL();
				if(not mSDL->switchVideoMode(CSDL_DEFAULT_SCREEN_W, CSDL_DEFAULT_SCREEN_H, MODE_2D))
					break;
				//display information about the error that occured
				if(not errorScreen("ERROR WHILE INITIALIZING OPENGL!"))
					break;
				continue;
			}
			if(not game.initGame(&openGL, &mState, mTexts) or
				not game.runGame(mLevels[mLevel], &mState)) {
				game.freeGame();
				openGL.freeOGL();
				if(not mSDL->switchVideoMode(CSDL_DEFAULT_SCREEN_W, CSDL_DEFAULT_SCREEN_H, MODE_2D))
					break;
				//display information about the error that occured
				if(not errorScreen(game.getLastErrorDesc()))
					break;
				continue;
			}
			game.freeGame();
			openGL.freeOGL();
			if(not mSDL->switchVideoMode(CSDL_DEFAULT_SCREEN_W, CSDL_DEFAULT_SCREEN_H, MODE_2D))
				break;

			mSDL->blackScreen();
			mSDL->drawFrame();

			if(mState.energy == 0) {
				if(not gameOver())
					errorScreen("COULDN'T DISPLAY 'GAME OVER' SCREEN :(");
				continue;
			}
			if(mState.exitGame or mLevel >= TRAINING1)
				continue;
			if(not endLevel()) {
				errorScreen("ERROR: COULDN'T DISPLAY LEVEL SUMMARY!");
				break;
			}
		}

		loaded:
		//player may exit the game during endLevel()
		if(mState.exitGame)
			continue;
		if(mLevel == LEVEL08A) {
			if(not playOutro()) {
				errorScreen("ERROR: COULDN'T DISPLAY THE OUTRO!");
				break;
			}
			continue;
		}
		if(((mLevel - 1) % 3) == 0) {
			if(not chooseComplex()) {
				errorScreen("ERROR: COULDN'T DISPLAY COMPLEX MENU!");
				break;
			}
		}
		else mLevel = (Level)(mLevel + 1);
		//player may exit the game during chooseComplex()
		if(not mState.exitGame)
			goto gameplay;
	}
	fprintf(stdout, "\nAbnormal program termination!\n");
}
