/*
  File name: CCytadelaGame.cpp
  Copyright: (C) 2005 - 2008 Tomasz Kazmierczak

  Creation date: 18.07.2005 15:37
  Last modification date: 15.12.2008

  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/>.  *

  game main loop and some other functions
*/

#include <iostream>
#include <exception>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <memory.h>
#include "CCytadelaGame.h"
#include "cmf/CMFile.h"

using namespace std;

enum {
	OSDIMG_PANEL = 0,
	OSDIMG_WEAPON_HAND,
	OSDIMG_WEAPON_HG,
	OSDIMG_WEAPON_SG,
	OSDIMG_WEAPON_MG,
	OSDIMG_WEAPON_FT,
	OSDIMG_WEAPON_BL,
	OSDIMG_WEAPON_RL,
	OSDIMG_WEAPON_RC,
	OSDIMG_WEAPON_GC,
	OSDIMG_WEAPON_BC,
	OSDIMG_LDIGIT_NA,
	OSDIMG_LDIGIT_0,
	OSDIMG_SDIGIT_0 = 22,
	OSDIMG_RDIGIT_0 = 32,
	OSDIMG_BOMB_0 = 42,
	OSDIMG_PLOTTER = 49,
	OSDIMG_OSDFONT,

	OSDIMG_IMAGES_COUNT
};

bool CCytadelaGame::initGame(COGLClass *ogl, GameState *state, CInfoText *texts)
{
	//copy pointers to objects
	mSDL = CSDLClass::instance();
	mOGL = ogl;
	mTexts = texts;
	mLocInterface = Localization::instance();
	mConfig = CCytadelaConfig::instance();

	//initialize global variables
	mRotY = mSin = mCos = mX = mZ = 0.0f;
	mEnergy = state->energy;
	mBomb = state->bomb;
	mDifficult = state->difficult;
	mPause = false;
	mFireOnce = false;
	mMap = false;
	mTotalTime = 0;
	mLastErrorDesc = "NO ERROR";

	//a buffer to store the standard walk speed - it shouldn't be changed
	//during the gameplay
	//(in the original Cytadela it was an integer equal to 250 - 1/4 field per
	//one frame)
	//multiply it by game speed
	mStandardWalkSpeed = 0.1f * mConfig->getGameSpeed();
	//at the begining the player walks with the standard speed
	mWalkSpeed = mStandardWalkSpeed;
	mPotion = 0;
	mBeer = 0;
	mDiff = 0;

	mFinishedLevel = false;

	for(uint32_t i = 0; i < (uint32_t)ACT_NUM_ACTIONS; ++i)
		mActionMap[i] = false;

	try {
		//init hand object
		mUsableItems[UIT_HAND] = (UsableItem *)(new Hand);
		//select the hand
		mSelectedItem = mUsableItems[UIT_HAND];
		//weapons
		mUsableItems[UIT_HANDGUN] = (UsableItem *)(new Weapon(UIT_HANDGUN, 1, 5, 800.0f, Ammo(10)));
		mUsableItems[UIT_SHOTGUN] = (UsableItem *)(new Weapon(UIT_SHOTGUN, 3, 5, 1000.0f, Ammo(5)));
		mUsableItems[UIT_MASHINEGUN] = (UsableItem *)(new Weapon(UIT_MASHINEGUN, 2, 5, 1000.0f, Ammo(10)));
		mUsableItems[UIT_FLAMER] = (UsableItem *)(new Weapon(UIT_FLAMER, 1, 25, 1200.0f, Ammo(5)));
		mUsableItems[UIT_BLASTER] = (UsableItem *)(new Weapon(UIT_BLASTER, 1, 20, 1200.0f, Ammo(5)));
		mUsableItems[UIT_ROCKETLAUNCHER] = (UsableItem *)(new Weapon(UIT_ROCKETLAUNCHER, 1, 40, 1200.0f, Ammo(1)));
		//cards
		mUsableItems[UIT_RED_CARD] = (UsableItem *)(new Card(UIT_RED_CARD));
		mUsableItems[UIT_GREEN_CARD] = (UsableItem *)(new Card(UIT_GREEN_CARD));
		mUsableItems[UIT_BLUE_CARD] = (UsableItem *)(new Card(UIT_BLUE_CARD));
	} catch(bad_alloc) {
		fprintf(stderr, "CCytadelaGame::initGame(): !mUsableItems[i] - Out of memory\n");
		mLastErrorDesc = "ERROR: OUT OF MEMORY!";
		return false;
	}

	//set the state of the weapons
	//handgun
	Weapon *weapon = (Weapon *)mUsableItems[UIT_HANDGUN];
	if(state->weapons & GSWPN_HANDGUN)
		weapon->setStatus(WPNS_ACTIVE);
	weapon->getAmmo()->setAmount(state->ammo[HANDGUN]);
	//shotgun
	weapon = (Weapon *)mUsableItems[UIT_SHOTGUN];
	if(state->weapons & GSWPN_SHOTGUN)
		weapon->setStatus(WPNS_ACTIVE);
	weapon->getAmmo()->setAmount(state->ammo[SHOTGUN]);
	//mashinegun
	weapon = (Weapon *)mUsableItems[UIT_MASHINEGUN];
	if(state->weapons & GSWPN_MASHINEGUN)
		weapon->setStatus(WPNS_ACTIVE);
	weapon->getAmmo()->setAmount(state->ammo[MASHINEGUN]);
	//flamer
	weapon = (Weapon *)mUsableItems[UIT_FLAMER];
	if(state->weapons & GSWPN_FLAMER)
		weapon->setStatus(WPNS_ACTIVE);
	weapon->getAmmo()->setAmount(state->ammo[FLAMER]);
	//blaster
	weapon = (Weapon *)mUsableItems[UIT_BLASTER];
	if(state->weapons & GSWPN_BLASTER)
		weapon->setStatus(WPNS_ACTIVE);
	weapon->getAmmo()->setAmount(state->ammo[BLASTER]);
	//launcher
	weapon = (Weapon *)mUsableItems[UIT_ROCKETLAUNCHER];
	if(state->weapons & GSWPN_ROCKETLAUNCHER)
		weapon->setStatus(WPNS_ACTIVE);
	weapon->getAmmo()->setAmount(state->ammo[ROCKETLAUNCHER]);

	//simple items
	try {
		mSimpleItems[SIT_MEDIPACK] = new Item(30);
		mSimpleItems[SIT_POTION] = new Item(75000); //300 * 250ms
		mSimpleItems[SIT_BEER] = new Item(47500); //190 * 250ms
		mSimpleItems[SIT_BOMB] = new Item(1);
	} catch(bad_alloc) {
		fprintf(stderr, "CCytadelaGame::initGame(): !mSimpleItems[i] - Out of memory\n");
		mLastErrorDesc = "ERROR: OUT OF MEMORY!";
		return false;
	}

	//run the random numbers' generator
	srand(time(0));
	return true;
}

void CCytadelaGame::freeGame()
{
	for(SimpleItemsMap::iterator i = mSimpleItems.begin(); i != mSimpleItems.end(); i++) {
		if(i->second != 0)
			delete i->second;
	}
	mSimpleItems.clear();
	for(UsableItemsMap::iterator i = mUsableItems.begin(); i != mUsableItems.end(); i++) {
		if(i->second != 0)
			delete i->second;
	}
	mUsableItems.clear();

	mSDL->freeSounds();
	mSDL = 0;
	mOGL = 0;
}

const char *CCytadelaGame::getLastErrorDesc()
{
	return mLastErrorDesc;
}

bool CCytadelaGame::loadImages()
{
	//image names
	const char *imgNames[] = {"Okno.tga", "e01.tga", "e02.tga",
		"e03.tga", "e04.tga", "e05.tga", "e06.tga", "e07.tga", "e14.tga",
		"e15.tga", "e16.tga", "ldn.tga", "ld0.tga", "ld1.tga", "ld2.tga",
		"ld3.tga", "ld4.tga", "ld5.tga", "ld6.tga", "ld7.tga", "ld8.tga",
		"ld9.tga", "sd0.tga", "sd1.tga", "sd2.tga", "sd3.tga", "sd4.tga",
		"sd5.tga", "sd6.tga", "sd7.tga", "sd8.tga", "sd9.tga", "rd0.tga",
		"rd1.tga", "rd2.tga", "rd3.tga", "rd4.tga", "rd5.tga", "rd6.tga",
		"rd7.tga", "rd8.tga", "rd9.tga", "b0.tga", "b1.tga", "b2.tga",
		"b3.tga", "b4.tga", "b5.tga", "b6.tga", "plot.tga", "gamefont.tga"};
	//image paths
	const char *imgPaths[OSDIMG_IMAGES_COUNT];
	//the same for (almost) all
	for(int32_t i = 0; i < OSDIMG_IMAGES_COUNT; ++i)
		imgPaths[i] = "tex/Gfx.far";
	//font path depends on the chosen localization
	char fontPath[FILENAME_MAX];
	sprintf(fontPath, "tex/fonts/%s.far", mLocInterface->getCurrentLocalization());
	imgPaths[OSDIMG_OSDFONT] = fontPath;

	//create an images array
	if(not mOGL->osd.createImagesArray(OSDIMG_IMAGES_COUNT))
		return false;
	//load the images
	for(uint32_t i = 0; i < OSDIMG_IMAGES_COUNT; i++)
		if(not mOGL->osd.loadImg(i, imgPaths[i], imgNames[i]))
			return false;

	return true;
}

bool CCytadelaGame::loadItemSounds()
{
	int32_t sound = mSDL->loadSound("sounds/handgun.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mUsableItems[UIT_HANDGUN]->useSoundNumber = sound;
	sound = mSDL->loadSound("sounds/shotgun.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mUsableItems[UIT_SHOTGUN]->useSoundNumber = sound;
	sound = mSDL->loadSound("sounds/machine2.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mUsableItems[UIT_MASHINEGUN]->useSoundNumber = sound;
	sound = mSDL->loadSound("sounds/flamer.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mUsableItems[UIT_FLAMER]->useSoundNumber = sound;
	sound = mSDL->loadSound("sounds/blaster.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mUsableItems[UIT_BLASTER]->useSoundNumber = sound;
	sound = mSDL->loadSound("sounds/launcher.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mUsableItems[UIT_ROCKETLAUNCHER]->useSoundNumber = sound;

	sound = mSDL->loadSound("sounds/noammo.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	((Weapon *)(mUsableItems[UIT_HANDGUN]))->noAmmoSoundNumber = sound;
	((Weapon *)(mUsableItems[UIT_SHOTGUN]))->noAmmoSoundNumber = sound;
	((Weapon *)(mUsableItems[UIT_MASHINEGUN]))->noAmmoSoundNumber = sound;
	((Weapon *)(mUsableItems[UIT_FLAMER]))->noAmmoSoundNumber = sound;
	((Weapon *)(mUsableItems[UIT_BLASTER]))->noAmmoSoundNumber = sound;
	((Weapon *)(mUsableItems[UIT_ROCKETLAUNCHER]))->noAmmoSoundNumber = sound;

	sound = mSDL->loadSound("sounds/ammotake.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	((Weapon *)(mUsableItems[UIT_HANDGUN]))->getAmmo()->takeSoundNumber = sound;
	((Weapon *)(mUsableItems[UIT_SHOTGUN]))->getAmmo()->takeSoundNumber = sound;
	((Weapon *)(mUsableItems[UIT_MASHINEGUN]))->getAmmo()->takeSoundNumber = sound;
	((Weapon *)(mUsableItems[UIT_FLAMER]))->getAmmo()->takeSoundNumber = sound;
	((Weapon *)(mUsableItems[UIT_BLASTER]))->getAmmo()->takeSoundNumber = sound;
	((Weapon *)(mUsableItems[UIT_ROCKETLAUNCHER]))->getAmmo()->takeSoundNumber = sound;

	sound = mSDL->loadSound("sounds/ooopstryk.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mUsableItems[UIT_HANDGUN]->takeSoundNumber = sound;
	mUsableItems[UIT_SHOTGUN]->takeSoundNumber = sound;
	mUsableItems[UIT_MASHINEGUN]->takeSoundNumber = sound;
	mUsableItems[UIT_FLAMER]->takeSoundNumber = sound;
	mUsableItems[UIT_BLASTER]->takeSoundNumber = sound;
	mUsableItems[UIT_ROCKETLAUNCHER]->takeSoundNumber = sound;

	sound = mSDL->loadSound("sounds/karta.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mUsableItems[UIT_RED_CARD]->useSoundNumber = sound;
	mUsableItems[UIT_GREEN_CARD]->useSoundNumber = sound;
	mUsableItems[UIT_BLUE_CARD]->useSoundNumber = sound;

	sound = mSDL->loadSound("sounds/ooo.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mUsableItems[UIT_RED_CARD]->takeSoundNumber = sound;
	mUsableItems[UIT_GREEN_CARD]->takeSoundNumber = sound;
	mUsableItems[UIT_BLUE_CARD]->takeSoundNumber = sound;
	mSimpleItems[SIT_BOMB]->takeSoundNumber = sound;

	sound = mSDL->loadSound("sounds/apteczka.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mSimpleItems[SIT_MEDIPACK]->takeSoundNumber = sound;
	mSimpleItems[SIT_POTION]->takeSoundNumber = sound;
	mSimpleItems[SIT_BEER]->takeSoundNumber = sound;

	sound = mSDL->loadSound("sounds/switch.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mUsableItems[UIT_HAND]->useSoundNumber = sound;

	sound = mSDL->loadSound("sounds/pik.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mLowEnergySoundNumber = sound;
	return true;
}

bool CCytadelaGame::loadGameCtrlSounds()
{
	//collision sounds
	int32_t sound = mSDL->loadSound("sounds/arrgh2.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mCollisionSounds[CSND_WALL_BUMP] = sound;

	sound = mSDL->loadSound("sounds/arrgh.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mCollisionSounds[CSND_PLAYER_HIT] = sound;
	mGameCtrl->mCollisionSounds[CSND_TOUCH_FLAMES] = sound;

	sound = mSDL->loadSound("sounds/explode.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mCollisionSounds[CSND_EXPLOSION] = sound;

	//door sounds
	mGameCtrl->mDoorSounds[DOORTYPE_UNDEFINED] = -1;
	sound = mSDL->loadSound("sounds/door1.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mDoorSounds[DOORTYPE_VERTICAL_1] = sound;
	sound = mSDL->loadSound("sounds/door3.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mDoorSounds[DOORTYPE_VERTICAL_2] = sound;
	sound = mSDL->loadSound("sounds/door2.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mDoorSounds[DOORTYPE_HORIZONTAL] = sound;

	//enemy sounds
	sound = mSDL->loadSound("sounds/en1.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mEnemySounds[ESND_DEATH] = sound;
	sound = mSDL->loadSound("sounds/en2.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mEnemySounds[ESND_ROAR1] = sound;
	sound = mSDL->loadSound("sounds/en3.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mEnemySounds[ESND_ROAR2] = sound;
	sound = mSDL->loadSound("sounds/en4.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mEnemySounds[ESND_ROAR3] = sound;
	sound = mSDL->loadSound("sounds/fire.wav");
	if(CSDL_LOAD_SOUND_FAILED == sound)
		return false;
	mGameCtrl->mEnemySounds[ESND_FIRE] = sound;

	//weapon sounds
	mGameCtrl->mWeaponSounds[WSND_HANDGUN] = mUsableItems[UIT_HANDGUN]->useSoundNumber;
	mGameCtrl->mWeaponSounds[WSND_SHOTGUN] = mUsableItems[UIT_SHOTGUN]->useSoundNumber;
	mGameCtrl->mWeaponSounds[WSND_MASHINEGUN] = mUsableItems[UIT_MASHINEGUN]->useSoundNumber;
	mGameCtrl->mWeaponSounds[WSND_FLAMER] = mUsableItems[UIT_FLAMER]->useSoundNumber;
	mGameCtrl->mWeaponSounds[WSND_BLASTER] = mUsableItems[UIT_BLASTER]->useSoundNumber;
	mGameCtrl->mWeaponSounds[WSND_ROCKETLAUNCHER] = mUsableItems[UIT_ROCKETLAUNCHER]->useSoundNumber;

	return true;
}

void CCytadelaGame::initUserInterface()
{
	//assign images to OSD elements
	//background
	mOGL->osd.setImage(OSDIMG_PANEL, OSD_PANEL);
	//hand
	mOGL->osd.setImage(OSDIMG_WEAPON_HAND, OSD_WEAPON);
	//bomb
	mOGL->osd.setImage(OSDIMG_BOMB_0 + mBomb, OSD_BOMB);
	//plotter
	mOGL->osd.setImage(OSDIMG_PLOTTER, OSD_PLOTTER);
	//text
	mOGL->osd.setImage(OSDIMG_OSDFONT, OSD_TEXT);

	//draw the counters
	drawEnergyCounter();
	drawAmmoCounter();
	drawCardsCounters();
}

bool CCytadelaGame::runGame(const char *szLevel, GameState *state)
{
	if(not loadImages()) {
		mLastErrorDesc = "ERROR: COULD NOT LOAD GAME'S RESOURCES!";
		return false;
	}
	if(not loadItemSounds()) {
		mLastErrorDesc = "ERROR: COULD NOT LOAD GAME'S RESOURCES!";
		return false;
	}
	//create game objects interface and load a file into it
	GameObjectsManager objects;
	CMFile file;
	if(not file.open(szLevel)) {
		fprintf(stderr, "CCytadelaGame::runGame(): !CMFile::open(%s)\n", szLevel);
		mLastErrorDesc = "ERROR: COULD NOT LOAD A LEVEL MAP!";
		return false;
	}
	if(not file.load(&objects)) {
		fprintf(stderr, "CCytadelaGame::runGame(): !CMFile::load(%s)\n", szLevel);
		mLastErrorDesc = "ERROR: COULD NOT LOAD A LEVEL MAP!";
		file.close();
		return false;
	}
	//add 3Dg interface to rendering interface
	if(not mOGL->setGOManager(&objects)) {
		fprintf(stderr, "CCytadelaGame::runGame(): !setGOInterface()\n");
		mLastErrorDesc = "ERROR: COULD NOT LOAD A LEVEL MAP!";
		file.close();
		return false;
	}
	//initial position
	Point3Dg point;
	if(not file.readInitialPosition(point)) {
		fprintf(stderr, "CCytadelaGame::runGame(): !CMFile::readInitialPosition()\n");
		mLastErrorDesc = "ERROR: COULD NOT LOAD A LEVEL MAP!";
		file.close();
		return false;
	}
	mX = point.x;
	mZ = point.z;
	//initial direction
	if(not file.readInitialDirection(point)) {
		fprintf(stderr, "CCytadelaGame::runGame(): !CMFile::readInitialDirection()\n");
		mLastErrorDesc = "ERROR: COULD NOT LOAD A LEVEL MAP!";
		file.close();
		return false;
	}
	mRotY = point.y;
	processRotationAngle();
	file.close();

	//create game control interface
	mGameCtrl = createGameCtrlInterface();
	if(mGameCtrl == 0) {
		fprintf(stderr, "CCytadelaGame::runGame(): !mGameCtrl - Out of memory\n");
		mLastErrorDesc = "ERROR: OUT OF MEMORY!";
		return false;
	}
	//set initial values
	mGameCtrl->mHighDificulty = mDifficult;
	mGameCtrl->mVisionNoise = state->visionNoise;
	//weapons for enemies
	mGameCtrl->mEnemyWeapons.insert(make_pair(UIT_HANDGUN, *(Weapon *)mUsableItems[UIT_HANDGUN]));
	mGameCtrl->mEnemyWeapons.insert(make_pair(UIT_SHOTGUN, *(Weapon *)mUsableItems[UIT_SHOTGUN]));
	mGameCtrl->mEnemyWeapons.insert(make_pair(UIT_MASHINEGUN, *(Weapon *)mUsableItems[UIT_MASHINEGUN]));
	mGameCtrl->mEnemyWeapons.insert(make_pair(UIT_FLAMER, *(Weapon *)mUsableItems[UIT_FLAMER]));
	mGameCtrl->mEnemyWeapons.insert(make_pair(UIT_BLASTER, *(Weapon *)mUsableItems[UIT_BLASTER]));
	mGameCtrl->mEnemyWeapons.insert(make_pair(UIT_ROCKETLAUNCHER, *(Weapon *)mUsableItems[UIT_ROCKETLAUNCHER]));
	UpdateData sUpdData = {&mX, &mZ, mSin, -mCos, mRotY, mDiff, 0, 0, 0, mPosition};
	if(not mGameCtrl->init(&sUpdData, &objects, mOGL, mTexts)) {
		fprintf(stderr, "CCytadelaGame::runGame(): !mGameCtrl->init()\n");
		mLastErrorDesc = "ERROR: COULD NOT INITIALIZE THE GAME!";
		mGameCtrl->release();
		return false;
	}
	if(not loadGameCtrlSounds()) {
		mLastErrorDesc = "ERROR: COULD NOT LOAD GAME'S RESOURCES!";
		mGameCtrl->release();
		return false;
	}

	initUserInterface();

	//beer related variables
	uint32_t beerFrameTime = 0;
	float drunkRotY = 0.0f, drunkX = 0.0f, drunkZ = 0.0f;
	//local time-related variables
	uint32_t curTime, deathTimeDelay = 0;
	int32_t lowEnergySoundDelay = 1000;

	//set the view
	mOGL->setCamMatrix(mX, mZ, mSin, mCos);
	//fade in
	mOGL->renderFrame();
	fadeIn();
	//"Welcome!" text
	mOGL->osd.addText(mLocInterface->getText(LOC_GAME_WELCOME));
	//clear the input event queue
	mSDL->clearEventQueue();
	//get the current time and enter the main loop
	mLastTime = mSDL->getTicks();
#if defined(DEBUG) || defined(CALC_FPS)
double fps = 0.0;
double avfps = 0.0;
uint32_t cumtime = 0;
uint32_t frames = 0;
#endif
	while(true) {
		//time
		curTime = mSDL->getTicks();
		mDiff = curTime - mLastTime;
		if(mDiff == 0)
			continue;
		mLastTime = curTime;
		if(mEnergy < 10) {
			lowEnergySoundDelay -= mDiff;
			if(lowEnergySoundDelay <= 0) {
				mSDL->playSound(mLowEnergySoundNumber, 5);
				lowEnergySoundDelay = 1000;
			}
		}
#if defined(DEBUG) || defined(CALC_FPS)
fps = 1000.0/(double)mDiff;
frames++;
cumtime += mDiff;
if(cumtime >= 1500) {
	avfps = 1000.0 * frames / (double)cumtime;
	cumtime = frames = 0;
printf("%f, av=%f\n", fps, avfps);
}
#endif
		//if runnin' on slow machine then simulate at least 10 FPS
		//(to eliminate bugs such as walking through walls, etc.)
		if(mDiff > 100)
			mDiff = 100;
		//handle the input
		if(not processInput()) break;
		if(mPause) {
			//mPauseStartTime sometimes can be greater than curTime
			//so we need a signed variable for the difference
			int32_t pauseTime = curTime - mPauseStartTime;
			if(pauseTime > 10000) {
				mOGL->osd.addText(mLocInterface->getText(LOC_GAME_PAUSED));
				mPauseStartTime = curTime;
			}
			mOGL->renderFrame();
			mOGL->osd.draw(mSin, mCos, mDiff);
			mSDL->swapBuffers();
			continue;
		}
		if(mMap) {
			//draw map and OSD (the map is redrawn because on slow systems
			//it may flicker if it is not redrawn, but don't know why)
			mOGL->osd.drawMap(mPosition[0], mPosition[1]);
			mOGL->osd.draw(mSin, mCos, mDiff);
			mSDL->swapBuffers();
			continue;
		}
		mTotalTime += mDiff;
		//new walk speed dependant on exhaust
		if(not mPotion)
			mWalkSpeed = mStandardWalkSpeed * (1.0f - mGameCtrl->mExhaust / 2.27f);
		//but if potion, the exhust desn't matter
		else {
			if(mDiff > mPotion)
				mPotion = 0;
			else
				mPotion -= mDiff;
		}
		//if drunk
		if(mBeer > 0) {
			mBeer -= mDiff;
			beerFrameTime += mDiff;
			//chnge the values max 10 times a second
			if(beerFrameTime > 50) {
				//random rotation in random direction
				drunkRotY = (float)((rand()%3) - 1) / 1000.0f;
				//random movement
				drunkX = ((rand()%3) - 1) / 20.0f;
				drunkZ = ((rand()%3) - 1) / 20.0f;
				beerFrameTime = 0;
			}
			mRotY += drunkRotY * mDiff;
			processRotationAngle();
			mX += drunkX * mDiff;
			mZ += drunkZ * mDiff;
		}
		//if player went out of level area
		if(mX > 6400.0f) mX -= 6400.0f;
		if(mX < 0.0f) mX += 6400.0f;
		if(mZ < -6400.0f) mZ += 6400.0f;
		if(mZ > 0.0f) mZ -= 6400.0f;
		//update the game control interface
		UpdateData upddata = {&mX, &mZ, mSin, -mCos, mRotY, mDiff, 0, 0, 0, mPosition};
		//if returned 'false' then the level has been finished
		if(not mGameCtrl->update(&upddata)) {
			//render actualized scene before fade-out so the most recent image
			//will be faded-out
			mOGL->setCamMatrix(mX, mZ, mSin, mCos);
			mOGL->renderFrame();
			fadeOut();
			mFinishedLevel = true;
			break;
		}
		//if teleport collision occured then substract the time spent on it
		//from the total gameplay time (actualize the mLastTime)
		mLastTime += upddata.teleportationTime;
		//if equipment collision (iTakenItem == 0 is not an equipment
		//material for sure - the eq mtl nums are in range from 56 to 74)
		if(upddata.takenItem)
			takeItem(upddata.takenItem);
		//decrease the energy (and finish the game if no energy left, but delay
		//the death for 0.5 second)
		if(not decEnergy(upddata.lostEnergy)) {
			if(deathTimeDelay > 500) {
				mOGL->osd.addText(mLocInterface->getText(LOC_GAME_YOU_ARE_DEAD));
				drawBloodOnScreen();
				break;
			}
			deathTimeDelay += mDiff;
		}
		//draw frame
		mOGL->setCamMatrix(mX, mZ, mSin, mCos);
		mOGL->renderFrame();
		mOGL->osd.draw(mSin, mCos, mDiff);
		mSDL->swapBuffers();
	}
	//copy game state
	state->totalTime = mTotalTime;
	state->energy = mEnergy > 500 ? 500 : mEnergy;
	state->ammo[HANDGUN] = ((Weapon *)mUsableItems[UIT_HANDGUN])->getAmmo()->getAmount();
	state->ammo[SHOTGUN] = ((Weapon *)mUsableItems[UIT_SHOTGUN])->getAmmo()->getAmount();
	state->ammo[MASHINEGUN] = ((Weapon *)mUsableItems[UIT_MASHINEGUN])->getAmmo()->getAmount();
	state->ammo[FLAMER] = ((Weapon *)mUsableItems[UIT_FLAMER])->getAmmo()->getAmount();
	state->ammo[BLASTER] = ((Weapon *)mUsableItems[UIT_BLASTER])->getAmmo()->getAmount();
	state->ammo[ROCKETLAUNCHER] = ((Weapon *)mUsableItems[UIT_ROCKETLAUNCHER])->getAmmo()->getAmount();
	//clear the weapons in the game state before copying
	state->weapons = 0;
	if(mUsableItems[UIT_HANDGUN]->getStatus() == WPNS_ACTIVE)
		state->weapons |= GSWPN_HANDGUN;
	if(mUsableItems[UIT_SHOTGUN]->getStatus() == WPNS_ACTIVE)
		state->weapons |= GSWPN_SHOTGUN;
	if(mUsableItems[UIT_MASHINEGUN]->getStatus() == WPNS_ACTIVE)
		state->weapons |= GSWPN_MASHINEGUN;
	if(mUsableItems[UIT_FLAMER]->getStatus() == WPNS_ACTIVE)
		state->weapons |= GSWPN_FLAMER;
	if(mUsableItems[UIT_BLASTER]->getStatus() == WPNS_ACTIVE)
		state->weapons |= GSWPN_BLASTER;
	if(mUsableItems[UIT_ROCKETLAUNCHER]->getStatus() == WPNS_ACTIVE)
		state->weapons |= GSWPN_ROCKETLAUNCHER;
	state->killed = mGameCtrl->getNumberOfKilledEnemies();
	state->bomb = mBomb;
	state->difficult = mDifficult;
	state->exitGame = not mFinishedLevel;
	state->visionNoise = mGameCtrl->mVisionNoise;
	//free everything
	mGameCtrl->release();
	mOGL->osd.deleteImages();
	mSDL->freeSounds();
	return true;
}

void CCytadelaGame::takeWeapon(Weapon *weapon)
{
	weapon->setStatus(WPNS_ACTIVE);
	mSDL->playSound(weapon->takeSoundNumber, 1);
}

void CCytadelaGame::takeCard(Card *card)
{
	card->cardsCount++;
	mSDL->playSound(card->takeSoundNumber, 1);
}

void CCytadelaGame::takeAmmo(Ammo *ammo)
{
	ammo->take();
	mSDL->playSound(ammo->takeSoundNumber, 2);
}

void CCytadelaGame::takeItem(int32_t mtlNum)
{
	switch(mtlNum) {
		case 56:
			takeWeapon((Weapon *)mUsableItems[UIT_HANDGUN]);
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_WEAPON_1));
			return;
		case 57:
			takeWeapon((Weapon *)mUsableItems[UIT_SHOTGUN]);
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_WEAPON_2));
			return;
		case 58:
			takeWeapon((Weapon *)mUsableItems[UIT_MASHINEGUN]);
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_WEAPON_3));
			return;
		case 59:
			takeWeapon((Weapon *)mUsableItems[UIT_FLAMER]);
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_WEAPON_4));
			return;
		case 60:
			takeWeapon((Weapon *)mUsableItems[UIT_BLASTER]);
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_WEAPON_5));
			return;
		case 61:
			takeWeapon((Weapon *)mUsableItems[UIT_ROCKETLAUNCHER]);
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_WEAPON_6));
			return;
		case 62:
			takeAmmo(((Weapon *)mUsableItems[UIT_HANDGUN])->getAmmo());
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_AMMO_1));
			break;
		case 63:
			takeAmmo(((Weapon *)mUsableItems[UIT_SHOTGUN])->getAmmo());
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_AMMO_2));
			break;
		case 64:
			takeAmmo(((Weapon *)mUsableItems[UIT_MASHINEGUN])->getAmmo());
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_AMMO_3));
			break;
		case 65:
			takeAmmo(((Weapon *)mUsableItems[UIT_FLAMER])->getAmmo());
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_AMMO_4));
			break;
		case 66:
			takeAmmo(((Weapon *)mUsableItems[UIT_BLASTER])->getAmmo());
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_AMMO_5));
			break;
		case 67:
			takeAmmo(((Weapon *)mUsableItems[UIT_ROCKETLAUNCHER])->getAmmo());
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_AMMO_6));
			break;
		case 68: {
			takeCard((Card *)mUsableItems[UIT_RED_CARD]);
			drawCardsCounters();
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_CARD_1));
			break;
		}
		case 69: {
			takeCard((Card *)mUsableItems[UIT_GREEN_CARD]);
			drawCardsCounters();
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_CARD_2));
			break;
		}
		case 70: {
			takeCard((Card *)mUsableItems[UIT_BLUE_CARD]);
			drawCardsCounters();
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_CARD_3));
			break;
		}
		case 71: {
			if(not mGameCtrl->mImmortal) {
				mEnergy += mSimpleItems[SIT_MEDIPACK]->getTakeAmount();
				if(mEnergy > 999)
					mEnergy = 999;
				drawEnergyCounter();
			}
			mSDL->playSound(mSimpleItems[SIT_MEDIPACK]->takeSoundNumber, 1);
			mOGL->osd.addText(mTexts[MEDIPACK_TEXTS].getText());
			return;
		}
		case 72: {
			mWalkSpeed = mStandardWalkSpeed * 1.2f;
			mPotion = mSimpleItems[SIT_POTION]->getTakeAmount();
			mSDL->playSound(mSimpleItems[SIT_POTION]->takeSoundNumber, 1);
			mOGL->osd.addText(mTexts[POTION_TEXTS].getText());
			return;
		}
		case 73: {
			mBeer +=  mSimpleItems[SIT_BEER]->getTakeAmount();
			mSDL->playSound(mSimpleItems[SIT_BEER]->takeSoundNumber, 1);
			mOGL->osd.addText(mTexts[BEER_TEXTS].getText());
			return;
		}
		case 74: {
			mBomb += mSimpleItems[SIT_BOMB]->getTakeAmount();
			mSDL->playSound(mSimpleItems[SIT_BOMB]->takeSoundNumber, 1);
			//can happen when the player used a cheat code to get the bomb
			if(mBomb > 6)
				mBomb = 6;
			else {
				switch(mBomb) {
				case 1:
					mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_BOMB_1));
					break;
				case 2:
					mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_BOMB_2));
					break;
				case 3:
					mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_BOMB_3));
					break;
				case 4:
					mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_BOMB_4));
					break;
				case 5:
					mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_BOMB_5));
					break;
				case 6:
					mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_BOMB_6));
					break;
				}
			}
			mOGL->osd.setImage(OSDIMG_BOMB_0 + mBomb, OSD_BOMB);
			return;
		}
	}
	drawAmmoCounter();
}

bool CCytadelaGame::decEnergy(uint16_t energy)
{
	if(energy > mEnergy)
		mEnergy = 0;
	else mEnergy -= energy;
	drawEnergyCounter();
	if(not mEnergy)
		return false;
	return true;
}

bool CCytadelaGame::processInput()
{
	InputArray inputArray;
	memset(&inputArray, 0, sizeof(inputArray));
	mSDL->readInputGame(&inputArray);
	//check actions
	for(uint32_t i = 0; i < 5; ++i) {
		if(inputArray.key[i] == SDLK_ESCAPE and not mMap)
			return false;
		if(inputArray.key[i] == SDLK_p and inputArray.status[i] and not mMap) {
			mPause = not mPause;
			mOGL->osd.setPause(mPause);
			if(mPause) {
				mPauseStartTime = mSDL->getTicks();
				mOGL->osd.addText(mTexts[PAUSE_ON_TEXTS].getText());
			} else mOGL->osd.addText(mTexts[PAUSE_OFF_TEXTS].getText());
		}
		//in map we don't check for codes
		if(mMap) continue;

		//process the only pressed keys
		if(not inputArray.status[i]) continue;

		//increase game speed (not used any more)
/*		if(inputArray.key[i] == SDLK_KP_PLUS) {
			mConfig->setNextGameSpeed();
			float gameSpeed = mConfig->getGameSpeed();
			mStandardWalkSpeed = 0.1f * gameSpeed;
			if(mPotion)
				mWalkSpeed = mStandardWalkSpeed * 1.2f;
			//display the current speed
			string gameSpeed = mLocInterface->getText(LOC_GAME_GAME_SPEED);
			char speed[16];
			sprintf(speed, " %.0f", gameSpeed * 100.0f);
			gameSpeed.append(speed);
			gameSpeed.append("%%");
			mOGL->osd.addText((char*)gameSpeed.c_str());
		}
		//decrease game speed
		if(inputArray.key[i] == SDLK_KP_MINUS) {
			mConfig->setPrevGameSpeed();
			float gameSpeed = mConfig->getGameSpeed();
			mStandardWalkSpeed = 0.1f * gameSpeed;
			if(mPotion)
				mWalkSpeed = mStandardWalkSpeed * 1.2f;
			//display the current speed
			string gameSpeed = mLocInterface->getText(LOC_GAME_GAME_SPEED);
			char speed[16];
			sprintf(speed, " %.0f", gameSpeed * 100.0f);
			gameSpeed.append(speed);
			gameSpeed.append("%%");
			mOGL->osd.addText((char*)gameSpeed.c_str());
		}*/

		//increase mouse speed
		if(inputArray.key[i] == SDLK_RIGHTBRACKET) {
			mConfig->setNextMouseSpeed();
			//display the current speed
			string mouseSpeed = mLocInterface->getText(LOC_GAME_MOUSE_SPEED);
			char speed[16];
			sprintf(speed, " %.1f", mConfig->getMouseSpeed());
			mouseSpeed.append(speed);
			mOGL->osd.addText((char*)mouseSpeed.c_str());
		}
		//decrease mouse speed
		if(inputArray.key[i] == SDLK_LEFTBRACKET) {
			mConfig->setPrevMouseSpeed();
			//display the current speed
			string mouseSpeed = mLocInterface->getText(LOC_GAME_MOUSE_SPEED);
			char speed[16];
			sprintf(speed, " %.1f", mConfig->getMouseSpeed());
			mouseSpeed.append(speed);
			mOGL->osd.addText((char*)mouseSpeed.c_str());
		}

		//write key code to the text buffer
		mPlayerText[USERTEXT_LENMINUS1] = (char)inputArray.key[i];
		//check the text and move it afterwards
		if(isCode("hotkiss")) {
			//ammo
			((Weapon *)mUsableItems[UIT_HANDGUN])->getAmmo()->setAmount(0x29A);
			((Weapon *)mUsableItems[UIT_SHOTGUN])->getAmmo()->setAmount(0x29A);
			((Weapon *)mUsableItems[UIT_MASHINEGUN])->getAmmo()->setAmount(0x29A);
			((Weapon *)mUsableItems[UIT_FLAMER])->getAmmo()->setAmount(0x29A);
			((Weapon *)mUsableItems[UIT_BLASTER])->getAmmo()->setAmount(0x29A);
			((Weapon *)mUsableItems[UIT_ROCKETLAUNCHER])->getAmmo()->setAmount(0x29A);
			//weapons - damage 'em randomly
			int randnum = rand();
			mUsableItems[UIT_HANDGUN]->setStatus((WeaponState)(1 + (1 & (randnum))));
			mUsableItems[UIT_SHOTGUN]->setStatus((WeaponState)(1 + (1 & (randnum >> 1))));
			mUsableItems[UIT_MASHINEGUN]->setStatus((WeaponState)(1 + (1 & (randnum >> 2))));
			mUsableItems[UIT_FLAMER]->setStatus((WeaponState)(1 + (1 & (randnum >> 3))));
			mUsableItems[UIT_BLASTER]->setStatus((WeaponState)(1 + (1  & (randnum >> 4))));
			mUsableItems[UIT_ROCKETLAUNCHER]->setStatus((WeaponState)(1 + (1 & (randnum >> 5))));
			//cards
			((Card *)mUsableItems[UIT_RED_CARD])->cardsCount =
				((Card *)mUsableItems[UIT_GREEN_CARD])->cardsCount =
				((Card *)mUsableItems[UIT_BLUE_CARD])->cardsCount = 0x6;
			drawAmmoCounter();
			drawCardsCounters();
			for(int32_t j = 0; j < USERTEXT_LEN; ++j)
				mPlayerText[j] = mPlayerText[j + 1];
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_CODE_WEAPONS));
			continue;
		}
		if(isCode("hotkiwi")) {
			//god mode
			mGameCtrl->mImmortal = not mGameCtrl->mImmortal;
			for(int32_t j = 0; j < USERTEXT_LEN; ++j)
				mPlayerText[j] = mPlayerText[j + 1];
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_CODE_ENERGY));
			continue;
		}
		if(isCode("kbbmorms")) {
			//energy
			mEnergy = 0x29A;
			drawEnergyCounter();
			for(int32_t j = 0; j < USERTEXT_LEN; ++j)
				mPlayerText[j] = mPlayerText[j + 1];
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_CODE_ENERGY));
			continue;
		}
		if(isCode("alibaba")) {
			//bomb
			mBomb = 6;
			mOGL->osd.setImage(OSDIMG_BOMB_0 + 6, OSD_BOMB);
			for(int32_t j = 0; j < USERTEXT_LEN; ++j)
				mPlayerText[j] = mPlayerText[j + 1];
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_TAKEN_BOMB_6));
			continue;
		}
		if(isCode("idaho")) {
			//1 square ahead
			mZ -= mCos * 100.0f;
			mX += mSin * 100.0f;

			if(mX > 6400.0f)
				mX -= 6400.0f;
			if(mX < 0.0f)
				mX += 6400.0f;
			if(mZ < -6400.0f)
				mZ += 6400.0f;
			if(mZ > 0.0f)
				mZ -= 6400.0f;
			uint16_t prevOffset = (uint16_t)(mPosition[0] * 8 + mPosition[1] * 512);
			mGameCtrl->teleportCamera(mX, mZ);
			mPosition[0] = (uint8_t)(mX / 100);
			mPosition[1] = (uint8_t)(-mZ / 100);
			uint16_t wCurrentOffset = (uint16_t)(mPosition[0] * 8 + mPosition[1] * 512);
			mGameCtrl->closePrevDoor(prevOffset, wCurrentOffset);
			for(int32_t j = 0; j < USERTEXT_LEN; ++j)
				mPlayerText[j] = mPlayerText[j + 1];
			continue;
		}
		if(isCode("lorien")) {
			//reveal map
			mGameCtrl->revealMap();

			for(int32_t j = 0; j < USERTEXT_LEN; ++j)
				mPlayerText[j] = mPlayerText[j + 1];
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_CODE_MAP));
			continue;
		}
		if(isCode("kitiara")) {
			//next level
			//render actualized scene before fade-out so the most recent image
			//will be faded-out
			mOGL->setCamMatrix(mX, mZ, mSin, mCos);
			mOGL->renderFrame();
			fadeOut();
			mFinishedLevel = true;
			for(int32_t j = 0; j < USERTEXT_LEN; ++j)
				mPlayerText[j] = mPlayerText[j + 1];
			return false;
		}
		for(int32_t j = 0; j < USERTEXT_LEN; ++j)
			mPlayerText[j] = mPlayerText[j + 1];
	}

	//actualize action map
	uint8_t *keyMap = inputArray.keyMap;

	//if level map on display
	if(mMap) {
		if(keyMap[SDLK_RETURN] or keyMap[SDLK_KP_ENTER] or keyMap[SDLK_LSHIFT] or
				keyMap[SDLK_RSHIFT] or inputArray.rmb or inputArray.joyButtonPressed)
			mMap = false;
		for(uint32_t i = 0; i < 5; ++i) {
			if(inputArray.status[i] and inputArray.key[i] == SDLK_TAB) {
				//if status was left 'true' it would turn on the map again
				//in handleInput()
				inputArray.status[i] = false;
				mMap = false;
			}
		}
		if(not mMap) {
			fadeOut();
			//turn map off so if there was vision noise than it will be rendered
			//to fade-in frame
			mOGL->osd.mapOff();
			mOGL->renderFrame();
			mOGL->osd.draw(mSin, mCos, 0);
			//now the eventual vision noise is not to be rendered (because it
			//would not be faded than) so turn map on again (but the map won't
			//be displayed because now we draw only pre rendered image without
			//map)
			mOGL->osd.mapOn();
			fadeIn();
			mOGL->osd.mapOff();
			mLastTime = mSDL->getTicks();
		}
		else return true;
	}

	//fire
	if(keyMap[SDLK_RETURN] or keyMap[SDLK_KP_ENTER] or keyMap[SDLK_LSHIFT] or
				keyMap[SDLK_RSHIFT] or inputArray.rmb or inputArray.joyButtonPressed)
		mActionMap[ACT_FIRE] = true;
	else {
		mActionMap[ACT_FIRE] = false;
		mFireOnce = false;
	}
	//forward
	if(keyMap[SDLK_UP] or keyMap[SDLK_KP8] or keyMap[SDLK_w] or inputArray.lmb or
	   inputArray.joyUp)
		mActionMap[ACT_FORWARD] = true;
	else mActionMap[ACT_FORWARD] = false;
	//backward
	if(keyMap[SDLK_DOWN] or keyMap[SDLK_KP5] or keyMap[SDLK_s] or inputArray.joyDown)
		mActionMap[ACT_BACKWARD] = true;
	else mActionMap[ACT_BACKWARD] = false;
	//turn left
	if(keyMap[SDLK_LEFT] or keyMap[SDLK_KP7] or inputArray.joyLeft)
		mActionMap[ACT_TURN_LEFT] = true;
	else mActionMap[ACT_TURN_LEFT] = false;
	//turn right
	if(keyMap[SDLK_RIGHT] or keyMap[SDLK_KP9] or inputArray.joyRight)
		mActionMap[ACT_TURN_RIGHT] = true;
	else mActionMap[ACT_TURN_RIGHT] = false;
	//strafe left
	if(keyMap[SDLK_KP4] or keyMap[SDLK_a])
		mActionMap[ACT_STRAFE_LEFT] = true;
	else mActionMap[ACT_STRAFE_LEFT] = false;
	//strafe right
	if(keyMap[SDLK_KP6] or keyMap[SDLK_d])
		mActionMap[ACT_STRAFE_RIGHT] = true;
	else mActionMap[ACT_STRAFE_RIGHT] = false;

	//if paused than exit here
	if(mPause)
		return true;

	//do actions
	for(uint32_t i = 0; i < 5; ++i)
		if(inputArray.status[i])
			handleInput(inputArray.key[i]);
	for(uint32_t i = 0; i < (uint32_t)ACT_NUM_ACTIONS; ++i)
		if(mActionMap[i])
			doActions((Actions)i);

	//mouse rotation (only if mouse moved)
	if(inputArray.mouseX) {
		mRotY += inputArray.mouseX * 0.01f * mConfig->getMouseSpeed();
		processRotationAngle();
	}
	return true;
}

void CCytadelaGame::processRotationAngle()
{
	while(mRotY >= 6.2831853f)
		mRotY -= 6.2831853f;
	while(mRotY < 0.0f)
		mRotY += 6.2831853f;
	//calculate sinus and cosinus of rotation angle
	mSin = sinf(mRotY);
	mCos = cosf(mRotY);
}

bool CCytadelaGame::isCode(const char *code)
{
	size_t codeLength = strlen(code);
	for(size_t i = codeLength - 1; i > 0; --i) {
		if(code[i] != mPlayerText[USERTEXT_LEN - codeLength + i])
			return false;
	}
	return true;
}

void CCytadelaGame::drawAmmoCounter()
{
	if(mSelectedItem->getType() == UIT_HAND) {
		mOGL->osd.setImage(OSDIMG_LDIGIT_NA, OSD_AMMO_O);
		mOGL->osd.setImage(OSDIMG_LDIGIT_NA, OSD_AMMO_T);
		mOGL->osd.setImage(OSDIMG_LDIGIT_NA, OSD_AMMO_H);
		return;
	}
	uint32_t count = mSelectedItem->getAmountForCounter();
	uint32_t h = count / 100;
	uint32_t t = (count - (h * 100)) / 10;
	uint32_t o = count - (h * 100) - (t * 10);
	//digits
	mOGL->osd.setImage(OSDIMG_LDIGIT_0 + o, OSD_AMMO_O);
	mOGL->osd.setImage(OSDIMG_LDIGIT_0 + t, OSD_AMMO_T);
	mOGL->osd.setImage(OSDIMG_LDIGIT_0 + h, OSD_AMMO_H);
}

void CCytadelaGame::drawEnergyCounter()
{
	int32_t h = mEnergy / 100;
	int32_t t = (mEnergy - (h * 100)) / 10;
	int32_t o = mEnergy - (h * 100) - (t * 10);
	if(mEnergy >= 10) {
		mOGL->osd.setImage(OSDIMG_LDIGIT_0 + o, OSD_NRG_O);
		mOGL->osd.setImage(OSDIMG_LDIGIT_0 + t, OSD_NRG_T);
		mOGL->osd.setImage(OSDIMG_LDIGIT_0 + h, OSD_NRG_H);
	} else {
		mOGL->osd.setImage(OSDIMG_RDIGIT_0 + o, OSD_NRG_O);
		mOGL->osd.setImage(OSDIMG_RDIGIT_0 + t, OSD_NRG_T);
		mOGL->osd.setImage(OSDIMG_RDIGIT_0 + h, OSD_NRG_H);
	}
}

void CCytadelaGame::drawCardsCounters()
{
	//red
	uint16_t cardsCount = ((Card *)mUsableItems[UIT_RED_CARD])->cardsCount;
	int32_t h = cardsCount / 100;
	int32_t t = (cardsCount - (h * 100)) / 10;
	int32_t o = cardsCount - (h * 100) - (t * 10);
	mOGL->osd.setImage(OSDIMG_SDIGIT_0 + o, OSD_RED_CARD);
	//green
	cardsCount = ((Card *)mUsableItems[UIT_GREEN_CARD])->cardsCount;
	h = cardsCount / 100;
	t = (cardsCount - (h * 100)) / 10;
	o = cardsCount - (h * 100) - (t * 10);
	mOGL->osd.setImage(OSDIMG_SDIGIT_0 + o, OSD_GREEN_CARD);
	//blue
	cardsCount = ((Card *)mUsableItems[UIT_BLUE_CARD])->cardsCount;
	h = cardsCount / 100;
	t = (cardsCount - (h * 100)) / 10;
	o = cardsCount - (h * 100) - (t * 10);
	mOGL->osd.setImage(OSDIMG_SDIGIT_0 + o, OSD_BLUE_CARD);
}

void CCytadelaGame::doActions(Actions eAction)
{
	switch(eAction) {
		case ACT_FIRE: {
			if(mFireOnce)
				break;
			fire();
			return;
		}
		//forward
		case ACT_FORWARD:
			mZ -= mCos * mDiff * mWalkSpeed;
			mX += mSin * mDiff * mWalkSpeed;
			return;
		//backward
		case ACT_BACKWARD:
			mZ += mCos * mDiff * mWalkSpeed;
			mX -= mSin * mDiff * mWalkSpeed;
			return;
		//rotations
		case ACT_TURN_LEFT: {
			mRotY -= mDiff * 0.001f * mConfig->getGameSpeed();
			processRotationAngle();
			return;
		}
		case ACT_TURN_RIGHT: {
			mRotY += mDiff * 0.001f * mConfig->getGameSpeed();
			processRotationAngle();
			return;
		}
		//strafe
		case ACT_STRAFE_LEFT:
			mX -= mCos * mDiff * mWalkSpeed;
			mZ -= mSin * mDiff * mWalkSpeed;
			return;
		case ACT_STRAFE_RIGHT:
			mX += mCos * mDiff * mWalkSpeed;
			mZ += mSin * mDiff * mWalkSpeed;
			return;
		case ACT_NUM_ACTIONS:
			break;
	}
}

void CCytadelaGame::handleInput(SDLKey key)
{
	switch(key) {
		case SDLK_SPACE:
			mGameCtrl->useItem(mUsableItems[UIT_HAND]);
			return;
		//weapons
		case SDLK_F1:
		case SDLK_1:
			//if actual weapon is damaged then throw it away
			if(mSelectedItem->getStatus() == WPNS_DAMAGED)
				mSelectedItem->setStatus(WPNS_NA);
			mSelectedItem = mUsableItems[UIT_HAND];
			mOGL->osd.setImage(OSDIMG_WEAPON_HAND, OSD_WEAPON);
			break;
		case SDLK_F2:
		case SDLK_2: {
			//if changing to the same weapon then just break (do nothing)
			if(mSelectedItem->getType() == UIT_HANDGUN)
				break;
			//only if player has the weapon
			if(mUsableItems[UIT_HANDGUN]->getStatus() != WPNS_NA) {
				//if previous weapon is damaged then throw it away
				if(mSelectedItem->getStatus() == WPNS_DAMAGED)
					mSelectedItem->setStatus(WPNS_NA);
				mSelectedItem = mUsableItems[UIT_HANDGUN];
				mOGL->osd.setImage(OSDIMG_WEAPON_HG, OSD_WEAPON);
			}
			break;
		}
		case SDLK_F3:
		case SDLK_3: {
			if(mSelectedItem->getType() == UIT_SHOTGUN)
				break;
			if(mUsableItems[UIT_SHOTGUN]->getStatus() != WPNS_NA) {
				if(mSelectedItem->getStatus() == WPNS_DAMAGED)
					mSelectedItem->setStatus(WPNS_NA);
				mSelectedItem = mUsableItems[UIT_SHOTGUN];
				mOGL->osd.setImage(OSDIMG_WEAPON_SG, OSD_WEAPON);
			}
			break;
		}
		case SDLK_F4:
		case SDLK_4: {
			if(mSelectedItem->getType() == UIT_MASHINEGUN)
				break;
			if(mUsableItems[UIT_MASHINEGUN]->getStatus() != WPNS_NA) {
				if(mSelectedItem->getStatus() == WPNS_DAMAGED)
					mSelectedItem->setStatus(WPNS_NA);
				mSelectedItem = mUsableItems[UIT_MASHINEGUN];
				mOGL->osd.setImage(OSDIMG_WEAPON_MG, OSD_WEAPON);
			}
			break;
		}
		case SDLK_F5:
		case SDLK_5: {
			if(mSelectedItem->getType() == UIT_FLAMER)
				break;
			if(mUsableItems[UIT_FLAMER]->getStatus() != WPNS_NA) {
				if(mSelectedItem->getStatus() == WPNS_DAMAGED)
					mSelectedItem->setStatus(WPNS_NA);
				mSelectedItem = mUsableItems[UIT_FLAMER];
				mOGL->osd.setImage(OSDIMG_WEAPON_FT, OSD_WEAPON);
			}
			break;
		}
		case SDLK_F6:
		case SDLK_6: {
			if(mSelectedItem->getType() == UIT_BLASTER)
				break;
			if(mUsableItems[UIT_BLASTER]->getStatus() != WPNS_NA) {
				if(mSelectedItem->getStatus() == WPNS_DAMAGED)
					mSelectedItem->setStatus(WPNS_NA);
				mSelectedItem = mUsableItems[UIT_BLASTER];
				mOGL->osd.setImage(OSDIMG_WEAPON_BL, OSD_WEAPON);
			}
			break;
		}
		case SDLK_F7:
		case SDLK_7: {
			if(mSelectedItem->getType() == UIT_ROCKETLAUNCHER)
				break;
			if(mUsableItems[UIT_ROCKETLAUNCHER]->getStatus() != WPNS_NA) {
				if(mSelectedItem->getStatus() == WPNS_DAMAGED)
					mSelectedItem->setStatus(WPNS_NA);
				mSelectedItem = mUsableItems[UIT_ROCKETLAUNCHER];
				mOGL->osd.setImage(OSDIMG_WEAPON_RL, OSD_WEAPON);
			}
			break;
		}
		case SDLK_F8:
		case SDLK_8:
			if(mSelectedItem->getStatus() == WPNS_DAMAGED)
				mSelectedItem->setStatus(WPNS_NA);
			mSelectedItem = mUsableItems[UIT_RED_CARD];
			mOGL->osd.setImage(OSDIMG_WEAPON_RC, OSD_WEAPON);
			break;

		case SDLK_F9:
		case SDLK_9:
			if(mSelectedItem->getStatus() == WPNS_DAMAGED)
				mSelectedItem->setStatus(WPNS_NA);
			mSelectedItem = mUsableItems[UIT_GREEN_CARD];
			mOGL->osd.setImage(OSDIMG_WEAPON_GC, OSD_WEAPON);
			break;
		case SDLK_F10:
		case SDLK_0:
			if(mSelectedItem->getStatus() == WPNS_DAMAGED)
				mSelectedItem->setStatus(WPNS_NA);
			mSelectedItem = mUsableItems[UIT_BLUE_CARD];
			mOGL->osd.setImage(OSDIMG_WEAPON_BC, OSD_WEAPON);
			break;
		//map
		case SDLK_TAB: {
			mMap = true;
			//redraw map in memory
			mGameCtrl->redrawMap();
			//render actualized scene before fade-out so the most recent image
			//will be faded-out
			mOGL->setCamMatrix(mX, mZ, mSin, mCos);
			mOGL->renderFrame();
			mOGL->osd.draw(mSin, mCos, 0);
			//turn map on in OSD (we do it before fade-out so vision noise will
			//also fade-out - the vision noise is rendered with the OSD, and the OSD
			//is rendered in every fade-out frame, so if we would not turn on the
			//map before fade-out the vision noise would not fade out)
			mOGL->osd.mapOn();
			fadeOut();
			//render the map and fade it in
			mOGL->osd.drawMap(mPosition[0], mPosition[1]);
			fadeIn();
			string strPosition = mLocInterface->getText(LOC_GAME_LOCATION);
			char szPos[16];
			sprintf(szPos, ": %d - %d", mPosition[0], mPosition[1]);
			strPosition.append(szPos);
			mOGL->osd.addText((char*)strPosition.c_str());
			return;
		}
		case SDLK_z: {
			mGameCtrl->mVisionNoise = !mGameCtrl->mVisionNoise;
			if(mGameCtrl->mVisionNoise)
				mOGL->osd.addText(mLocInterface->getText(LOC_GAME_VISION_NOISE_ON));
			if(not mGameCtrl->mVisionNoise)
				mOGL->osd.addText(mLocInterface->getText(LOC_GAME_VISION_NOISE_OFF));
			return;
		}
		default:
			break;
	}
	drawAmmoCounter();
}

void CCytadelaGame::fire()
{
	if(mSelectedItem->getType() != UIT_MASHINEGUN)
		mFireOnce = true;

	mGameCtrl->useItem(mSelectedItem);
	drawAmmoCounter();
	drawCardsCounters();
}

void CCytadelaGame::drawBloodOnScreen()
{
	uint32_t time = mSDL->getTicks(), pastTime = 0, diff = 0, fillInterval = 0;
	mOGL->osd.freeNoise();
	//there are 320 blood lines and this array stores their lengths
	uint8_t bloodLines[320];
	memset(bloodLines, 0, sizeof(bloodLines));
	//five seconds
	while(pastTime < 5000) {
		if(fillInterval >= 24) {
			for(int32_t i = 0; i < 320; ++i)
				bloodLines[i] += (rand() & 1)*fillInterval/12;
			fillInterval = 0;
		}
		mOGL->osd.addBlood(bloodLines, 320);
		mOGL->osd.draw(mSin, mCos, diff);
		mOGL->osd.drawBlood();
		mSDL->swapBuffers();
		diff = mSDL->getTicks() - time - pastTime;
		pastTime += diff;
		fillInterval += diff;
	}
	mOGL->osd.freeBlood();
}

void CCytadelaGame::fadeIn()
{
	mOGL->osd.draw(mSin, mCos, 0);
	mOGL->preDissolve(0.0f, 0.0f, 0.0f, DT_FADEIN);
	uint32_t time = mSDL->getTicks(), pastTime = 0;
	while(pastTime < 250) {
		float fadeValue = (float)pastTime / 250.0f;
		mOGL->dissolveFrame(DT_FADEIN, fadeValue);
		mOGL->osd.draw(mSin, mCos, 0);
		mSDL->swapBuffers();
		pastTime = mSDL->getTicks() - time;
	}
}

void CCytadelaGame::fadeOut()
{
	mOGL->preDissolve(0.0f, 0.0f, 0.0f, DT_FADEOUT);
	uint32_t time = mSDL->getTicks(), pastTime = 0;
	while(pastTime < 250) {
		float fadeValue = (float)pastTime / 250.0f;
		mOGL->dissolveFrame(DT_FADEOUT, fadeValue);
		mOGL->osd.draw(mSin, mCos, 0);
		mSDL->swapBuffers();
		pastTime = mSDL->getTicks() - time;
	}
}
