/*
  File name: GameControl.cpp
  Copyright: (C) 2004 - 2009 Tomasz Kazmierczak

  Creation date: 08.05.2004
  Last modification date: 01.03.2009

  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 <cstdlib>
#include <cassert>
#include <math.h>
#include "GameControl.h"

#define MACHINEGUN_FIRE_DELAY 450
#define PLAYER_BURN_DELAY 250
GameControl *createGameCtrlInterface()
{
	return new GameControl;
}

bool GameControl::init(UpdateData *data, GameObjectsManager *objects, COGLClass *ogl, CInfoText *texts)
{
	mObjects = objects;
	mOGL = ogl;
	mSDL = CSDLClass::instance();
	mTexts = texts;
	mConfig = CCytadelaConfig::instance();
	mLocInterface = Localization::instance();
	mCameraPosition.x = *(data->x);
	mCameraPosition.z = *(data->z);
	mCameraDirection.x = data->dirX;
	mCameraDirection.z = data->dirZ;
	mCameraAngles.y = data->rotY;
	mMiliseconds = data->miliseconds;
	mPrevPosition[0] = mPosition[0] = data->position[0] = (uint8_t)(*(data->x) / 100);
	mPrevPosition[1] = mPosition[1] = data->position[1] = (uint8_t)-(*(data->z) / 100);
	mCamRay = 35.0f;
	mEnemyRay = 35.0f;
	mAuxVTime = 0;
	mRocketLoadTime = 0;
	mMGFireDiff = MACHINEGUN_FIRE_DELAY; //no firing delay for mashinegun at the begining
	mPlayerBurnDiff = PLAYER_BURN_DELAY; //the same for player burn
	mLastRoarTime = 0;
	mPrevCamPosition = mCameraPosition;
	mLevelMap = mObjects->getMapDataPtr();
	mImmortal = false;
	mHeartBeat = 11.0f;
	mExhaust = 0.0f;

	//create mesh bases for temporary objects
	//small explosion
	mSmallExplosionBase = mObjects->createMeshBase(mObjects->generateMeshBaseUID());
	if(mSmallExplosionBase == 0) {
		fprintf(stderr, "GameControl::init(): !mSmallExplosionBase\n");
		return false;
	}
	Triangle3Dg trian =
		{{Point3Dg(-5.0f, -5.0f, 0.0f), //A: x, y, z
		Point3Dg(0.0f, 0.0f, 1.0f), {0.0f, 0.0f}},    //Nx, Ny, Nz; Tx, Ty
		{Point3Dg(5.0f, 5.0f, 0.0f), //B: x, y, z
		Point3Dg(0.0f, 0.0f, 1.0f), {1.0f, 1.0f}},    //Nx, Ny, Nz; Tx, Ty
		{Point3Dg(-5.0f, 5.0f, 0.0f), //C: x, y, z
		Point3Dg(0.0f, 0.0f, 1.0f), {0.0f, 1.0f}}};   //Nx, Ny, Nz; Tx, Ty
	mSmallExplosionBase->addTrian(trian);
	trian.vertexC = trian.vertexB;
	trian.vertexB.coords.x = 5.0f;
	trian.vertexB.coords.y = -5.0f;
	trian.vertexB.tex[1] = 0.0f;
	mSmallExplosionBase->addTrian(trian);
	//big explosion
	mBigExplosionBase = mObjects->createMeshBase(mObjects->generateMeshBaseUID());
	if(mBigExplosionBase == 0) {
		fprintf(stderr, "GameControl::init(): !mBigExplosionBase\n");
		return false;
	}
	trian.vertexA.coords.x = -30.0f;
	trian.vertexA.coords.y = -30.0f;
	trian.vertexB.coords.x = 30.0f;
	trian.vertexB.coords.y = 30.0f;
	trian.vertexC.coords.x = -30.0f;
	trian.vertexC.coords.y = 30.0f;
	trian.vertexB.tex[1] = 1.0f;
	trian.vertexC.tex[0] = 0.0f;
	mBigExplosionBase->addTrian(trian);
	trian.vertexC = trian.vertexB;
	trian.vertexB.coords.x = 30.0f;
	trian.vertexB.coords.y = -30.0f;
	trian.vertexB.tex[1] = 0.0f;
	mBigExplosionBase->addTrian(trian);

	//find a pointer to equipment base
	for(int i = 0; i < 32760; i+= 8) {
		if(mLevelMap[i+6] != 0) {
			mEquipmentBase = mObjects->meshes[mLevelMap[i+6]]->getBase();
			break;
		}
	}

	//gather info about animated objects
	for(MeshMap3Dg::iterator i = mObjects->meshes.begin(); i != mObjects->meshes.end(); i++) {
		Mesh3Dg *mesh = i->second;
		if(mesh->animation.geomAnimTime != 0) {
			GameObject *obj = (GameObject *)mesh->getChild();
			if(obj->getType() == GOTYPE_DOOR) {
				Mesh3Dg *pFrameMesh = ((GODoor *)obj)->getFrameMesh();

				//if the door is closed
				if(pFrameMesh->materialNumber == mesh->animation.beginMtl)
					mesh->animation.elapsedTime = 0;

				if(pFrameMesh->materialNumber == mesh->animation.beginMtl + 2) {
					uint32_t time = mesh->animation.geomAnimTime / 4;
					float *point = mesh->animation.translation.pt;
					float x = point[0] * time, y = point[1] * time, z = point[2] * time;
					mesh->addTranslation(x, y, z);
					mesh->animation.elapsedTime = time;
				}

				if(pFrameMesh->materialNumber == mesh->animation.beginMtl + 1) {
					uint32_t time = mesh->animation.geomAnimTime;
					float *point = mesh->animation.translation.pt;
					float x = point[0] * time, y = point[1] * time, z = point[2] * time;
					mesh->addTranslation(x, y, z);
					mesh->animation.elapsedTime = time;
				}
			} else mesh->animation.elapsedTime = 0;
			mesh->animation.animStatus = ANIM_STOP;
			continue;
		}
		if(mesh->animation.texAnimTime != 0) {
			mesh->animation.elapsedTime = 0;
			mesh->animation.animStatus = ANIM_FORWARD;
		}
	}

	//rotate sprites and find enemies
	for(GameObjectsMap::iterator i = mObjects->begin(); i != mObjects->end(); i++) {
		GameObject *obj = i->second;
		if(obj->getType() == GOTYPE_SPRITE) {
			obj->getMesh()->rotate(0.0f, -mCameraAngles.y, 0.0f);
			if(((GOSprite *)obj)->getSubType() == GOTYPE_ENEMY)
				mEnemies.push_back((Enemy *)obj);
		}
	}

	//if the difficulty level is EASY then remove every fourth enemy
	//and decrease agression to 4/5 of it's original value
	if(not mHighDificulty) {
		EnemiesList::size_type counter = 0;
		EnemiesList::iterator i = mEnemies.begin();
		while(i != mEnemies.end()) {
			counter++;
			if(counter % 4 == 0) {
				//hadle the iterator properly
				EnemiesList::iterator j = i;
				i++;
				//get pointers to the game object and the mesh
				GameObject *obj = *j;
				Mesh3Dg *mesh = obj->getMesh();
				//remove the enemy from the enemies list
				mEnemies.erase(j);
				//remowe the enemy as a rendered object
				mOGL->remMeshFromRenderList(mesh->getUID());
				//destroy the game object
				mObjects->destroyGameObject(obj);
				//and the mesh
				mObjects->destroyMesh(mesh->getUID());
				//reset the counter and continue
				counter = 0;
				continue;
			}
			//agression
			Enemy *enemy = *i;
			enemy->setAgression(enemy->getAgression() * 4 / 5);
			i++;
		}
	}

	return true;
}

void GameControl::release()
{
	mEnemies.clear();
	mTempObjects.clear();
	delete this;
}

void GameControl::animate()
{
	//animations
	uint32_t diff = mMiliseconds;

	for(GameObjectsMap::iterator i = mObjects->begin(); i != mObjects->end(); i++) {
		GameObject *obj = i->second;
		Mesh3Dg *mesh = i->second->getMesh();
		if(obj->getType() == GOTYPE_SPRITE)
			mesh->rotate(0.0f, -mCameraAngles.y, 0.0f);

		MeshAnim3Dg *animation = &mesh->animation;
		//if the object isn't beeing animated at the moment
		if(animation->animStatus == ANIM_STOP)
			continue;
		//inc/dec anim time
		animation->elapsedTime += diff * animation->animStatus;
		//if the object has animated texture
		if(animation->texAnimTime != 0) {
			//if texture display time went out of range then substract
			//as long as it gets back into the range
			while(animation->elapsedTime >= (int32_t)animation->texAnimTime)
				animation->elapsedTime -= (int32_t)animation->texAnimTime;
			//set next material
			mesh->materialNumber = animation->beginMtl +
			                       (animation->elapsedTime * animation->numMtls) /
			                       animation->texAnimTime;
			continue;
		}

		//animate doors - if time out of range then decrease
		if(animation->elapsedTime > (int32_t)animation->geomAnimTime) {
			diff -= animation->elapsedTime - animation->geomAnimTime;
			animation->elapsedTime = animation->geomAnimTime;
		} else if(animation->elapsedTime < 0) {
			diff += animation->elapsedTime;
			animation->elapsedTime = 0;
		}
		//calculate translation
		float *data = animation->translation.pt;
		float x = data[0] * diff * animation->animStatus;
		float y = data[1] * diff * animation->animStatus;
		float z = data[2] * diff * animation->animStatus;
		mesh->addTranslation(x, y, z);
		//change door frame texture if anim finished
		uint32_t elapsedTime = animation->elapsedTime;
		if(obj->getType() != GOTYPE_DOOR)
			continue;
		if(elapsedTime == animation->geomAnimTime)
			((GODoor *)obj)->getFrameMesh()->materialNumber = animation->beginMtl + 1;
		else if(elapsedTime == 0)
			((GODoor *)obj)->getFrameMesh()->materialNumber = animation->beginMtl;
		else continue;
		animation->animStatus = ANIM_STOP;
		diff = mMiliseconds;
	}
	//temporary objects
	TempObjList::iterator i = mTempObjects.begin();
	while(i != mTempObjects.end()) {
		TemporaryObject *obj = *i;
		//moving objects
		if(obj->getRange() > 0.0f) {
			Weapon *weapon = (Weapon *)obj->getUserData();
			float speed = 0.2f;
			if(weapon->getType() == UIT_FLAMER)
				speed = 0.15f;
			float dist = mMiliseconds*speed*mConfig->getGameSpeed();
			Mesh3Dg *mesh = obj->getMesh();
			Vector3Dg dir = *obj->getVelocityDirection();
			if(bulletCollision(weapon, dist, mesh->geomCenter(), dir, obj->getOrigin()) or
			   not obj->update(mMiliseconds, dist)) {
				TempObjList::iterator j = i;
				i++;
				mTempObjects.erase(j);
				mOGL->remMeshFromRenderList(mesh->getUID());
				mObjects->destroyGameObject(obj);
				mObjects->destroyMesh(mesh->getUID());
			} else {
				float x = dir.x * dist;
				float z = dir.z * dist;
				mesh->addTranslation(x, 0.0f, z);
				i++;
			}
		//static objects
		} else if(obj->getLifeTime() > 0) {
			if(not obj->update(mMiliseconds)) {
				TempObjList::iterator j = i;
				i++;
				mTempObjects.erase(j);
				Mesh3Dg *mesh = obj->getMesh();
				mOGL->remMeshFromRenderList(mesh->getUID());
				mObjects->destroyGameObject(obj);
				mObjects->destroyMesh(mesh->getUID());
			}
			else i++;
		}
	}
}

bool GameControl::update(UpdateData *data)
{
	mVelocity.x = *(data->x) - mPrevCamPosition.x;
	mVelocity.z = *(data->z) - mPrevCamPosition.z;
	mLostEnergy = 0;
	//inertia simulation (after bumping walls, beeing shot, etc.)
	if(mAuxVTime > 0) {
		mVelocity = mAuxiliaryV;
		if(mMiliseconds) {
			mAuxVTime -= (int32_t)mMiliseconds;
			//inertia should last 250ms
			float dissipation = (0.004 * (float)mMiliseconds);
			if(dissipation < 0.0f)
				dissipation = 0.0f;
			//dissipation
			mAuxiliaryV *= 1.0f - dissipation;
		}
	}
	if(mAuxVTime < 0) {
		mAuxiliaryV.x = 0.0f;
		mAuxiliaryV.z = 0.0f;
		mAuxVTime = 0;
	}

	//calm down (if moving than calm down slower)
	if(mVelocity.x != 0.0f or mVelocity.z != 0.0f)
		mHeartBeat -= 0.004f * mMiliseconds;
	else
		mHeartBeat -= 0.008f * mMiliseconds;
	if(mHeartBeat < 11.0f)
		mHeartBeat = 11.0f;
	//set heart beat speed in OSD
	mOGL->osd.setHeartSpeed(mHeartBeat);
	mExhaust = (mHeartBeat - 11.0f) / 45.0f;

	mCameraAngles.y = data->rotY;
	mCameraDirection.x = data->dirX;
	mCameraDirection.z = data->dirZ;
	mSinCameraRotation = sinf(mCameraAngles.y);
	mCosCameraRotation = cosf(mCameraAngles.y);
	//offset to field on which is camera
	uint32_t posOffset = mPosition[0] * 8 + mPosition[1] * 512;
	//first collisions with walls, door and edges
	bool edges[] = {false, false, false, false, false};
	//get info about existing walls in actual square
	uint16_t walls = mLevelMap[posOffset + 4];
	for(uint32_t i = 0; i < 4; ++i) {
		if(walls & (1 << i)) {
			Mesh3DgUID meshUID = mLevelMap[posOffset + i];
			edges[i] = edges[i + 1] = true;
			GameObject *obj = mObjects->getGameObject(meshUID);
			if(obj->getPlayerCollisionType() != PCTYPE_BUMP)
				continue;
			switch(obj->getType()) {
				case GOTYPE_WALL:
					mLostEnergy += wallCollision(obj, i);
					break;
				case GOTYPE_DOOR: {
					//only if not open
					if(isDoorOpen((GODoor *)obj))
						mLostEnergy += wallCollision(obj, i);
					break;
				}
				default:
					break;
			}
		}
	}
	edges[0] = edges[0] or edges[4];
	//search for edges of walls from neighbour squares
	searchForEdges(edges, mPosition[0], mPosition[1]);
	//check edges collisions
	edgesCollision(edges);

	//actualize map
	actualizeMap(mPosition[0], mPosition[1], walls);

	//check for equipment
	if(mLevelMap[posOffset + 4] & 32) {
		Mesh3Dg *mesh = mObjects->meshes[mLevelMap[posOffset + 6]];
		if(equipmentCollision(mesh, posOffset)) {
			data->takenItem = mesh->materialNumber;
			//remove info from map
			mLevelMap[posOffset + 4] &= ~32;
			//don't render
			mesh->setProperty(MRP_RENDERABLE, false);
		}
	}
	//check for anything that may block the way
	if(mLevelMap[posOffset + 4] & 16) {
		Mesh3DgUID meshUID = mLevelMap[posOffset + 5];
		Mesh3Dg *mesh = mObjects->meshes[meshUID];
		if(((GameObject *)mesh->getChild())->getPlayerCollisionType() == PCTYPE_STOP)
			spriteCollision(mesh);
	}
	//enemy also blocks the way (and a burning enemy can burn the player,
	//so he can lost energy)
	mLostEnergy += enemyCollision();

	//actualize position
	mCameraPosition += mVelocity;

	//close doors from previous field (if any)
	uint16_t prevOffset = (uint16_t)(mPrevPosition[0] * 8 + mPrevPosition[1] * 512);
	if(not (mLevelMap[prevOffset + 4] & 64) and prevOffset != posOffset)
		closePrevDoor(prevOffset, posOffset);
	//open unlocked doors on actual position
	for(uint32_t i = 0; i < 4; ++i) {
		if((walls & (1 << i)) == 0)
			continue;
		Mesh3DgUID num = mLevelMap[posOffset + i];
		GameObject *obj = mObjects->getGameObject(num);
		//if it is door and it isn't locked
		if((obj->getType() == GOTYPE_DOOR) and not (mLevelMap[posOffset + 4] & 64))
			openDoor(num, (GODoor *)obj);
	}

	mPrevPosition[0] = mPosition[0];
	mPrevPosition[1] = mPosition[1];
	mPosition[0] = (uint8_t)(*(data->x) / 100.0f);
	mPosition[1] = (uint8_t)-(*(data->z) / 100.0f);
	posOffset = mPosition[0] * 8 + mPosition[1] * 512;
	mMiliseconds = data->miliseconds;

	//do animations
	animate();

	//check if teleport collision
	if(mLevelMap[posOffset + 4] & 16) {
		Mesh3Dg *mesh = mObjects->meshes[mLevelMap[posOffset + 5]];
		if(((GameObject *)mesh->getChild())->getPlayerCollisionType() == PCTYPE_TELEPORT) {
			uint32_t pastTime = 0;
			//if teleportCollision() returned 'false' then a collision with
			//an ending teleport ocured
			if(not teleportCollision(mesh, posOffset, pastTime))
				return false;
			data->teleportationTime = pastTime;
		}
	}

	*(data->x) = mPrevCamPosition.x = mCameraPosition.x;
	*(data->z) = mPrevCamPosition.z = mCameraPosition.z;
	data->position[0] = mPosition[0];
	data->position[1] = mPosition[1];

	//update enemies
	for(EnemiesList::iterator i = mEnemies.begin(); i != mEnemies.end(); i++) {
		Enemy *enemy = *i;
		EnemyUpdateResult result = enemy->update(mMiliseconds, mCameraPosition);
		switch(result) {
			case EUR_AIMING: {
				Vector3Dg vec = mCameraPosition - enemy->getPosition();
				float distaince = vec.length();
				//find apropriate weapon in map
				UsableItemType type = UsableItemType(enemy->getWeaponType());
				WeaponsMap::iterator iter = mEnemyWeapons.find(type);
				if(iter == mEnemyWeapons.end())
					break;
				if(distaince > iter->second.getRange())
					break;
				if(canShoot(distaince, enemy->getPosition(), enemy->getDirection()))
					enemy->shoot();
				break;
			}
			case EUR_SHOT: {
				Vector3Dg pos = enemy->getPosition();
				mSDL->playSound(mWeaponSounds[enemy->getWeaponType()-1], 3,
				                calculateVolume(pos, VOLDIST_TYPE2));
				//find apropriate weapon in map
				UsableItemType type = UsableItemType(enemy->getWeaponType());
				WeaponsMap::iterator iter = mEnemyWeapons.find(type);
				if(iter == mEnemyWeapons.end())
					break;
				Weapon *weapon = &(iter->second);
				switch(type) {
					case UIT_HANDGUN:
					case UIT_SHOTGUN:
					case UIT_MASHINEGUN: {
						for(uint16_t i = 0; i < weapon->getBullets(); i++)
							ammoCollision(weapon, pos, enemy->getDirection());
						break;
					}
					case UIT_FLAMER:
					case UIT_BLASTER:
					case UIT_ROCKETLAUNCHER: {
						pos = pos + (enemy->getDirection() * 25.0f);
						pos.x += mCosCameraRotation * enemy->getAmmoXPosShift();
						pos.z += mSinCameraRotation * enemy->getAmmoXPosShift();
						pos.y = enemy->getAmmoYPosShift();
						Vector3Dg dir = mCameraPosition - pos;
						dir.y = 0.0f;
						dir.normalize();
						fireAmmo(weapon, pos, dir);
						break;
					}
					default:
						break;
				}
				break;
			}
			case EUR_WALK: {
				//random roar
				if(mLastRoarTime > 100) {
					randomRoar(enemy);
					mLastRoarTime = 0;
				}
				//NOTE: don't break here! we also want to do things that are in the next case
			}
			case EUR_BURNING: {
				checkEnemyCollisions(enemy);
				enemy->move();
				break;
			}

			default:
				break;
		}
		mLastRoarTime += mMiliseconds;
	}

	data->lostEnergy = mLostEnergy;

	//decrease mashinegun next firing delay
	if(mMGFireDiff < MACHINEGUN_FIRE_DELAY)
		mMGFireDiff += mMiliseconds;
	//the same for payer burn delay
	if(mPlayerBurnDiff < PLAYER_BURN_DELAY)
		mPlayerBurnDiff += mMiliseconds;
	return true;
}

void GameControl::searchForEdges(bool *edges, uint8_t posX, uint8_t posZ)
{
	//search for edges of walls from neighbour squares
	uint16_t posWInfoOffs = (uint16_t)(getRelativePosOffset(posX, posZ, -1, 0) + 4);
	uint16_t posEInfoOffs = (uint16_t)(getRelativePosOffset(posX, posZ, 1, 0) + 4);
	uint16_t posSInfoOffs = (uint16_t)(getRelativePosOffset(posX, posZ, 0, -1) + 4);
	uint16_t posNInfoOffs = (uint16_t)(getRelativePosOffset(posX, posZ, 0, 1) + 4);
	uint16_t xm1 = mLevelMap[posWInfoOffs];
	uint16_t xp1 = mLevelMap[posEInfoOffs];
	uint16_t zm1 = mLevelMap[posSInfoOffs];
	uint16_t zp1 = mLevelMap[posNInfoOffs];
	if((xm1 & 1) or (zp1 & 8)) edges[0] = true;
	if((xp1 & 1) or (zp1 & 2)) edges[1] = true;
	if((xp1 & 4) or (zm1 & 2)) edges[2] = true;
	if((xm1 & 4) or (zm1 & 8)) edges[3] = true;
}

uint16_t GameControl::getRelativePosOffset(int8_t posX, int8_t posZ, int8_t relX, int8_t relZ)
{
	int8_t x = posX + relX;
	if(x > 64)
		x -= 64;
	if(x < 0)
		x += 64;
	int8_t z = posZ + relZ;
	if(z > 64)
		z -= 64;
	if(z < 0)
		z += 64;
	return (uint16_t)(x * 8 + z * 512);
}

void GameControl::revealMap()
{
	for(int32_t i = 0; i < 0x8000; i += 8)
		mLevelMap[i + 4] |= 128;
}

void GameControl::actualizeMap(int8_t x, int8_t z, uint16_t walls)
{
	//uncover actual position
	mLevelMap[(x * 8 + z * 512) + 4] |= 128;
	//and other around
	uint16_t posWInfoOffs = (uint16_t)(getRelativePosOffset(x, z, -1, 0) + 4);
	uint16_t posEInfoOffs = (uint16_t)(getRelativePosOffset(x, z, 1, 0) + 4);
	uint16_t posSInfoOffs = (uint16_t)(getRelativePosOffset(x, z, 0, -1) + 4);
	uint16_t posNInfoOffs = (uint16_t)(getRelativePosOffset(x, z, 0, 1) + 4);
	uint16_t xm1 = mLevelMap[posWInfoOffs];
	uint16_t xp1 = mLevelMap[posEInfoOffs];
	uint16_t zm1 = mLevelMap[posSInfoOffs];
	uint16_t zp1 = mLevelMap[posNInfoOffs];
	//if these is no northern wall
	if(not (walls & 1)) {
		//reveal position to the north and, if necessary, left and right from there
		mLevelMap[posNInfoOffs] |= 128;
		if(not (zp1 & 8)) mLevelMap[getRelativePosOffset(x, z, -1, 1) + 4] |= 128;
		if(not (zp1 & 2)) mLevelMap[getRelativePosOffset(x, z, 1, 1) + 4] |= 128;
	}
	//eastern
	if(not (walls & 2)) {
		mLevelMap[posEInfoOffs] |= 128;
		if(not (xp1 & 1)) mLevelMap[getRelativePosOffset(x, z, 1, 1) + 4] |= 128;
		if(not (xp1 & 4)) mLevelMap[getRelativePosOffset(x, z, 1, -1) + 4] |= 128;
	}
	//southern
	if(not (walls & 4)) {
		mLevelMap[posSInfoOffs] |= 128;
		if(not (zm1 & 8)) mLevelMap[getRelativePosOffset(x, z, -1, -1) + 4] |= 128;
		if(not (zm1 & 2)) mLevelMap[getRelativePosOffset(x, z, 1, -1) + 4] |= 128;
	}
	//western
	if(not (walls & 8)) {
		mLevelMap[posWInfoOffs] |= 128;
		if(not (xm1 & 1)) mLevelMap[getRelativePosOffset(x, z, -1, 1) + 4] |= 128;
		if(not (xm1 & 4)) mLevelMap[getRelativePosOffset(x, z, -1, -1) + 4] |= 128;
	}
}

void GameControl::redrawMap()
{
	//redraw map
	for(int32_t i = 0; i < 0x8000; i += 8) {
		if(not (mLevelMap[i + 4] & 128))
			continue;
		uint8_t z = (uint8_t)(i / 512);
		uint8_t x = (uint8_t)((i % 512) / 8);
		mOGL->osd.drawMapPos(&mLevelMap[i], x, z, mObjects);
	}
}

uint32_t GameControl::getFaceDir()
{
	float rotY = mCameraAngles.y;
	//north
	if(rotY > ANG_315DEG or rotY <= ANG_45DEG)
		return 0;
	//east
	if(rotY > ANG_45DEG and rotY <= ANG_135DEG)
		return 1;
	//south
	if(rotY > ANG_135DEG and rotY <= ANG_225DEG)
		return 2;
	//west - no need to check - only one possibility left
	//if(rotY > ANG_225DEG and rotY <= ANG_315DEG)
	return 3;
}

void GameControl::handCollision(UsableItem *hand)
{
	uint32_t posOffset = mPosition[0] * 8 + mPosition[1] * 512;
	uint32_t faceDir = getFaceDir();
	uint32_t objOffset = posOffset + faceDir;
	Mesh3DgUID meshUID = mLevelMap[objOffset];
	//if these's a wall
	if((mLevelMap[posOffset + 4] & (1 << faceDir))) {
		MapDataPtr data = &mLevelMap[0x8000];
		uint32_t dataOffset = getDataOffset(objOffset, data);
		uint32_t mtlNum = mObjects->meshes[meshUID]->materialNumber;
		switch(mtlNum) {
			//if button, and it is switched-off - switch it on
			case 21:
			case 104:
			case 121:
			case 138: {
				//change material
				mObjects->meshes[meshUID]->materialNumber++;
				//display text
				mOGL->osd.addText(mTexts[BUTTON_TEXTS].getText());
				mSDL->playSound(hand->useSoundNumber, 1);
				//if action desc not found
				if(dataOffset == 0)
					return;
				dataOffset++;
				//do the action
				while(data[dataOffset] < 0xFFFE) {
					doActions(dataOffset, data);
					dataOffset += 2;
				}
				return;
			}
			//if switched-on button
			case 22:
			case 105:
			case 122:
			case 139: {
				//change material
				mObjects->meshes[meshUID]->materialNumber--;
				//display text
				mOGL->osd.addText(mTexts[BUTTON_TEXTS].getText());
				mSDL->playSound(hand->useSoundNumber, 1);
				//if action desc not found
				if(dataOffset == 0)
					return;
				//search for 'switch off button' action
				while(data[dataOffset] != 0xFFFE) {
					dataOffset++;
					//if we got to next object action desc than this button
					//does not have switch off action
					if(data[dataOffset] == 0xFFFF)
						return;
				}
				//do the action
				dataOffset++;
				while(data[dataOffset] != 0xFFFF) {
					doActions(dataOffset, data);
					dataOffset += 2;
				}
				return;
			}
			//display apropirate texts
			//wall with no collision
			case 26:
				mOGL->osd.addText(mTexts[HAND_NOTHING_TEXTS].getText());
				return;
			//if door
			case 14:
			case 17: {
				GameObject *obj = mObjects->getGameObject(meshUID);
				if(obj->getType() != GOTYPE_DOOR)
					return;
				//get frame's material number
				Mesh3Dg *mesh = ((GODoor *)obj)->getFrameMesh();
				mtlNum = mesh->materialNumber;
				switch(mtlNum) {
					//closed
					case 14:
					case 17:
						mOGL->osd.addText(mLocInterface->getText(LOC_GAME_CLOSED_DOOR));
						return;
					//opening
					case 16:
					case 19:
						mOGL->osd.addText(mLocInterface->getText(LOC_GAME_OPENING_DOOR));
						return;
					//open
					case 15:
					case 18:
						mOGL->osd.addText(mLocInterface->getText(LOC_GAME_OPEN_DOOR));
						return;
				}
				return;
			}
			//wall
			default: {
				GameObject *obj = mObjects->getGameObject(meshUID);
				if(obj->getType() != GOTYPE_WALL)
					return;
				//with slot
				Mesh3Dg *slot = ((GOWall *)obj)->getSlotMesh();
				if(slot) {
					switch(slot->materialNumber) {
						case 51:
						case 142:
						case 145:
						case 148:
							mOGL->osd.addText(mTexts[HAND_SLOT_TEXTS].getText());
							return;
						case 52:
						case 143:
						case 146:
						case 149:
							mOGL->osd.addText(mTexts[HAND_SLOT_USED_TEXTS].getText());
							return;
						default:
							break;
					}
				}
				//with blood
				if(mObjects->materials[obj->getMesh()->materialNumber]->texturesQuantity == 2) {
					mOGL->osd.addText(mTexts[HAND_BLOOD_TEXTS].getText());
					return;
				}
				//normal wall
				mOGL->osd.addText(mTexts[HAND_WALL_TEXTS].getText());
				return;
			}
		}
		return;
	}

	//enemy
	for(EnemiesList::iterator i = mEnemies.begin(); i != mEnemies.end(); i++) {
		Enemy *enemy = *i;
		Vector3Dg pos = enemy->getPosition();
		if(((int)pos.x)/100 == mPosition[0] and ((int)-pos.z)/100 == mPosition[1]) {
			if(enemy->isAlive())
				mOGL->osd.addText(mTexts[HAND_ENEMY_TEXTS].getText());
			else mOGL->osd.addText(mTexts[HAND_CORPS_TEXTS].getText());
			return;
		}
	}
	//equipment
	if(mLevelMap[posOffset + 4] & 32) {
		mOGL->osd.addText(mTexts[HAND_EQUIPMENT_TEXTS].getText());
		return;
	}
	//corps
	if(mLevelMap[posOffset + 7]) {
		mOGL->osd.addText(mTexts[HAND_CORPS_TEXTS].getText());
		return;
	}
	//sprite/blocade
	if(mLevelMap[posOffset + 4] & 16) {
		meshUID = mLevelMap[posOffset + 5];
		uint32_t mtlNum = mObjects->meshes[meshUID]->materialNumber;
		if(mtlNum == 27 or mtlNum == 28)
			mOGL->osd.addText(mTexts[HAND_BLOCADE_TEXTS].getText());
		else
			mOGL->osd.addText(mTexts[HAND_SPRITE_TEXTS].getText());
		return;
	}
	mOGL->osd.addText(mTexts[HAND_NOTHING_TEXTS].getText());
}

bool GameControl::isDoorOpen(GODoor *door)
{
	Mesh3Dg *mesh = door->getMesh();
	if(mesh->animation.beginMtl + 1 != door->getFrameMesh()->materialNumber)
		return true;
	return false;
}

float GameControl::wallCollisionAmmo(Vector3Dg &begin, Vector3Dg &end, uint8_t xpos, uint8_t zpos,
                                   uint32_t walldir, GameObject *obj)
{
	//copy the coordinates of the first vertex in the first triangle
	Point3Dg pt = *obj->getMesh()->getBase()->getVCoordsStream();
	switch(walldir) {
		case 1:
		case 3: {
			pt.z = (pt.x - begin.x) * ((end.z - begin.z) / (end.x - begin.x)) + begin.z;
			float max = -(float)(zpos * 100.0f), min = -(float)((zpos+1) * 100.0f);
			//if the coordinate is not on the wall
			if(pt.z < min or pt.z > max)
				return 0.0f;
			break;
		}
		case 0:
		case 2: {
			pt.x = (pt.z - begin.z) * ((end.x - begin.x) / (end.z - begin.z)) + begin.x;
			float min = xpos * 100.0f, max = (xpos+1) * 100.0f;
			if(pt.x < min or pt.x > max)
				return 0.0f;
			break;
		}
	}
	Vector3Dg vec(pt.x - begin.x, 0.0f, pt.z - begin.z);
	return vec.length();
}

float GameControl::spriteCollisionAmmo(Vector3Dg &begin, Vector3Dg &end,
                                     uint8_t xpos, uint8_t zpos, float range)
{
	Vector3Dg spritepos(xpos * 100.0f + 50.0f, begin.y, -(float)(zpos * 100.0f + 50.0f));
	Vector3Dg ammoway = end - begin;
	//a vector from 'begin point' to 'sprite pos'
	Vector3Dg vec = spritepos - begin;
	float dotprod = vec.dotProduct(ammoway);
	//if the sprite is behind the player
	if(dotprod < 0.0f)
		return 0.0f;
	//calculate the distaince from the sprite's plane
	float spritedist = dotprod / range;
	//if the sprite is out of ammo's range
	if(spritedist > range)
		return 0.0f;
	//calculate the intersection point
	ammoway *= spritedist / range;
	vec = begin + ammoway;
	//calculate the distaince between the intersection point and the sprite's position
	vec -= spritepos;
	if(vec.length() > 25.0f)
		return 0.0f;
	return ammoway.length();
}

float GameControl::enemyCollisionAmmo(Vector3Dg &begin, Vector3Dg &end, Vector3Dg &enemypos,
                                    float range)
{
	Vector3Dg ammoway = end - begin;
	ammoway.y = 0.0f;
	//a vector from 'begin point' to 'sprite pos'
	Vector3Dg vec = enemypos - begin;
	vec.y = 0.0f;
	float dotprod = vec.dotProduct(ammoway);
	//if the elemy is behind the player
	if(dotprod < 0.0f)
		return 0.0f;
	//calculate the distaince from the sprite's plane
	float enemydist = dotprod / range;
	//if the sprite is out of ammo's range
	if(enemydist > range)
		return 0.0f;
	//calculate the intersection point
	ammoway *= enemydist / range;
	vec = begin + ammoway;
	//calculate the distaince between the intersection point and the sprite's position
	vec -= enemypos;
	vec.y = 0.0f;
	if(vec.length() > 25.0f)
		return 0.0f;
	return ammoway.length();
}

float GameControl::playerCollisionAmmo(Vector3Dg &begin, Vector3Dg &end, float range)
{
	Vector3Dg ammoway = end - begin;
	//a vector from 'begin point' to 'sprite pos'
	Vector3Dg vec = mCameraPosition - begin;
	vec.y = 0.0f;
	float dotprod = vec.dotProduct(ammoway);
	//if the player is behind the shooter
	if(dotprod < 0.0f)
		return 0.0f;
	//calculate the distaince from the player
	float dist = dotprod / range;
	//if the player is out of ammo's range
	if(dist > range)
		return 0.0f;
	//calculate the intersection point
	ammoway *= dist / range;
	vec = begin + ammoway;
	vec.y = 0.0f;
	//calculate the distaince between the intersection point and the player's position
	vec -= mCameraPosition;
	if(vec.length() > mCamRay)
		return 0.0f;
	return ammoway.length();
}

void GameControl::fireAmmo(Weapon *weapon, Vector3Dg position, Vector3Dg direction)
{
	Mesh3Dg *mesh = mObjects->createMesh(mObjects->generateMeshUID());
	if(mesh == 0)
		return;
	TemporaryObject *obj = mObjects->createTempGameObject(mesh, weapon->getRange(), direction);
	if(obj == 0) {
		mObjects->destroyMesh(mesh->getUID());
		return;
	}
	//user data
	obj->setUserData(weapon);
	//render properties for mesh
	mesh->setProperty(MRP_TRANSPARENT, true);
	mesh->setProperty(MRP_Z_MINUS_OFFSET, true);
	//position and rotation
	mesh->setPosition(position.x, 50.0f + position.y, position.z);
	mesh->rotate(0.0f, -mCameraAngles.y, 0.0f);
	//origin of the object
	obj->updateOrigin();

	switch(weapon->getType()) {
		case UIT_FLAMER: {
			//mesh base
			mesh->setBase(mBigExplosionBase);
			//animation
			mesh->animation.texAnimTime = 400;
			mesh->animation.beginMtl = 75;
			mesh->animation.numMtls = 2;
			mesh->materialNumber = mesh->animation.beginMtl;
			break;
		}
		case UIT_BLASTER: {
			//mesh base
			mesh->setBase(mSmallExplosionBase);
			//animation
			mesh->animation.texAnimTime = 400;
			mesh->animation.beginMtl = 77;
			mesh->animation.numMtls = 2;
			mesh->materialNumber = mesh->animation.beginMtl;
			break;
		}
		case UIT_ROCKETLAUNCHER: {
			//mesh base
			mesh->setBase(mSmallExplosionBase);
			//animation
			mesh->animation.texAnimTime = 400;
			mesh->animation.beginMtl = 79;
			mesh->animation.numMtls = 2;
			mesh->materialNumber = mesh->animation.beginMtl;
			break;
		}
		default: {
			mObjects->destroyGameObject(obj);
			mObjects->destroyMesh(mesh->getUID());
			return;
		}
	}
	//add the object to the render and temp-objects lists
	mTempObjects.push_back(obj);
	mOGL->addMeshToRenderList(mesh);
}

bool GameControl::bulletCollision(Weapon *weapon, float range, Vector3Dg position,
                                  Vector3Dg direction, Vector3Dg &origin)
{
	//calculate the end point of the ammo's way
	Vector3Dg end = position + (direction * range);
	//calculate the boundaries of potentially affected area
	int8_t beginx = (int8_t)(position.x / 100), beginz = (int8_t)(-position.z / 100);
	int8_t endx = (int8_t)(end.x / 100), endz = (int8_t)(-end.z / 100);

	//if out of bounds
	if(beginx < 0 or beginx > 63 or beginz < 0 or beginz > 63 or
	   endx < 0 or endx > 63 or endz < 0 or endz > 63)
		return false;

	//position's offset in the map buffer
	uint32_t offset = beginx * 8 + beginz * 512;
	//pointer to the closest object on the bullet's way
	GameObject *closestobject = 0;
	float distaince = range;

	//if the bullet's way doesn't intersect any wall's plane
	if(beginx == endx and beginz == endz)
		goto SpriteCollisionCheck;

	//which walls to check
	{
		uint32_t count = 0, check[2];
		//north
		if(direction.z < 0.0f) {
			check[count] = 0;
			count++;
		}
		//south
		else if(direction.z > 0.0f) {
			check[count] = 2;
			count++;
		}
		//east
		if(direction.x > 0.0f) {
			check[count] = 1;
			count++;
		}
		//west
		else if(direction.x < 0.0f) {
			check[count] = 3;
			count++;
		}

		bool edges[] = {false, false, false, false, false};
		//position's offset in the map buffer
		uint16_t walls = mLevelMap[offset + 4];
		for(uint32_t i = 0; i < count; ++i) {
			if((walls & (1 << check[i])) == 0)
				continue;
			Mesh3DgUID meshnum = mLevelMap[offset + check[i]];
			edges[check[i]] = edges[check[i] + 1] = true;
			GameObject *obj = mObjects->getGameObject(meshnum);
			if(obj->getAmmoCollisionType() == ACTYPE_NONE)
				continue;
			float dist = 0.0f;
			switch(obj->getType()) {
				case GOTYPE_WALL:
					dist = wallCollisionAmmo(position, end, beginx, beginz, check[i], obj);
					break;
				case GOTYPE_DOOR: {
					//only if not open
					if(isDoorOpen((GODoor *)obj))
						dist = wallCollisionAmmo(position, end, beginx, beginz, check[i], obj);
					break;
				}
				default:
					break;
			}
			if(dist != 0.0f and dist < distaince) {
				distaince = dist;
				closestobject = obj;
			}
		}
		edges[0] = edges[0] or edges[4];
		searchForEdges(edges, beginx, beginz);
		//check edges collisions
		Vector3Dg explosionpos;
		if(bulletEdgesCollision(edges, position, end, beginx, beginz, 4.0f, explosionpos)) {
			explosionpos.y = position.y;
			switch(weapon->getType()) {
				case UIT_FLAMER:
					mSDL->playSound(mCollisionSounds[CSND_EXPLOSION], 3, calculateVolume(explosionpos, VOLDIST_TYPE1));
					displayExplosion(explosionpos, EXPLT_BIG, 0, false);
				break;
				case UIT_BLASTER:
					displayExplosion(explosionpos, EXPLT_ELECTRIC, 0, false);
				break;
				case UIT_ROCKETLAUNCHER:
					mSDL->playSound(mCollisionSounds[CSND_EXPLOSION], 3, calculateVolume(explosionpos, VOLDIST_TYPE1));
					displayHugeExplosion(explosionpos, direction);
					explosionCollision(explosionpos);
				break;
				default:
					return false;
			}
			return true;
		}
	}

	SpriteCollisionCheck:
	//sprites
	Mesh3DgUID meshnum = mLevelMap[offset + 5];
	if(mLevelMap[offset + 4] & 16) {
		GameObject *obj = mObjects->getGameObject(meshnum);
		if(obj->getAmmoCollisionType() != ACTYPE_NONE) {
			float dist = spriteCollisionAmmo(position, end, beginx, beginz, range);
			if(dist != 0.0f and dist < distaince) {
				distaince = dist;
				closestobject = obj;
			}
		}
	}

	//now check for enemies
	for(EnemiesList::iterator i = mEnemies.begin(); i != mEnemies.end(); i++) {
		GameObject *obj = (GameObject *)*i;
		if(obj->getAmmoCollisionType() != ACTYPE_NONE) {
			Vector3Dg enemypos = ((Enemy *)obj)->getPosition();
			float dist = enemyCollisionAmmo(position, end, enemypos, weapon->getRange());
			if(dist != 0.0f and dist < distaince) {
				distaince = dist;
				closestobject = obj;
			}
		}
	}

	//check the player
	float playerDistaince = playerCollisionAmmo(position, end, weapon->getRange());
	//if the distaince to the player is less than the distaince to the closest object
	//then the player is hit
	if(playerDistaince > 0.0 and playerDistaince < distaince) {
		playerHit(weapon->getPower(), direction);
		return true;
	}

	//if any object has been hit
	if(closestobject) {
		Vector3Dg explosionpos = position + (direction * (distaince - 5.0f));
		switch(closestobject->getAmmoCollisionType()) {
			case ACTYPE_UNBREAKABLE: {
				switch(weapon->getType()) {
					case UIT_FLAMER:
						mSDL->playSound(mCollisionSounds[CSND_EXPLOSION], 3, calculateVolume(explosionpos, VOLDIST_TYPE1));
						displayExplosion(explosionpos, EXPLT_BIG, 0, false);
					break;
					case UIT_BLASTER:
						displayExplosion(explosionpos, EXPLT_ELECTRIC, 0, false);
					break;
					case UIT_ROCKETLAUNCHER:
						mSDL->playSound(mCollisionSounds[CSND_EXPLOSION], 3, calculateVolume(explosionpos, VOLDIST_TYPE1));
						displayHugeExplosion(explosionpos, direction);
						explosionCollision(explosionpos);
					break;
					default:
						return false;
				}
				return true;
			}
			case ACTYPE_EXPLODABLE: {
				if(closestobject->getType() != GOTYPE_SPRITE)
					return false;
				//destroy the object if the weapon was not a blaster
				if(weapon->getType() == UIT_BLASTER)
					displayExplosion(explosionpos, EXPLT_ELECTRIC, 0, false);
				else {
					closestobject->getMesh()->setProperty(MRP_RENDERABLE, false);
					//rem info in map
					mLevelMap[offset + 4] &= ~16;
					//if there is an equipment near the explodable object then destroy it to
					if(mLevelMap[offset + 4] & 32) {
						mObjects->meshes[mLevelMap[offset + 6]]->setProperty(MRP_RENDERABLE, false);
						//rem info in map
						mLevelMap[offset + 4] &= ~32;
					}
					displayHugeExplosion(explosionpos, direction);
					explosionCollision(explosionpos);
					mSDL->playSound(mCollisionSounds[CSND_EXPLOSION], 3, calculateVolume(explosionpos, VOLDIST_TYPE1));
				}
				return true;
			}
			case ACTYPE_ENEMY: {
				Enemy *enemy = (Enemy *)closestobject;
				//decrease the HP and die, if necessary
				//(yes, in the original game, the enemy died after his HP went below 0)
				if(not enemy->isBurning()) {
					switch(weapon->getType()) {
						case UIT_FLAMER: {
							if(enemy->burn()) {
								//turn off other fire sounds
								unregisterFireSounds();
								mSDL->playSound(mEnemySounds[ESND_FIRE], 5, 128, -1);
								enemy->registerSound(5);
							} else hitRoar(enemy);
							break;
						}
						case UIT_BLASTER: {
							if(not enemy->canBeParalized() or not enemy->isAlive())
								break;
							if(enemy->hit(weapon->getPower(), mCameraPosition) < 0) {
								enemy->die();
								//sometimes leave a present
								leaveAmmo(enemy);
								//blood
								Vector3Dg way = explosionpos - origin;
								spreadBlood(enemy, direction, way.length());

								mSDL->playSound(mEnemySounds[ESND_DEATH], 4, calculateVolume(explosionpos, VOLDIST_TYPE3));
							} else hitRoar(enemy);
							checkEnemyCollisions(enemy);
							enemy->move();
							break;
						}
						case UIT_ROCKETLAUNCHER: {
							if(enemy->isAlive()) {
								if(enemy->hit(weapon->getPower(), mCameraPosition) < 0) {
									enemy->die();
									//sometimes leave a present
									leaveAmmo(enemy);
									//blood
									Vector3Dg way = explosionpos - origin;
									spreadBlood(enemy, direction, way.length());
	
									mSDL->playSound(mEnemySounds[ESND_DEATH], 4, calculateVolume(explosionpos, VOLDIST_TYPE3));
								}
								checkEnemyCollisions(enemy);
								enemy->move();
							}
							break;
						}
						default:
							break;
					}
				}

				//explosion and sound
				if(weapon->getType() == UIT_BLASTER)
					displayExplosion(explosionpos, EXPLT_ELECTRIC, 0, false);
				else {
					if(weapon->getType() == UIT_ROCKETLAUNCHER) {
						displayHugeExplosion(explosionpos, direction);
						explosionCollision(explosionpos);
					} else displayExplosion(explosionpos, EXPLT_BIG, 0, false);
					mSDL->playSound(mCollisionSounds[CSND_EXPLOSION], 3, calculateVolume(explosionpos, VOLDIST_TYPE1));
				}
				return true;
			}
			default:
				return false;
		}
	}
	return false;
}

void GameControl::ammoCollision(Weapon *weapon, Vector3Dg position, Vector3Dg direction)
{
	//calculate the end point of the ammo's way
	Vector3Dg end = position + (direction * weapon->getRange());
	//random shift in direction
	float shift = 25.0f - (rand() / (double)RAND_MAX) * 50.0f;
	end.x += direction.z * shift;
	shift = 25.0f - (rand() / (double)RAND_MAX) * 50.0f;
	end.z += direction.x * shift;
	//calculate the direction once again 'cause we gonna need it
	direction = end - position;
	direction.normalize();
	//calculate the boundaries of potentially affected area
	int8_t beginx = (int8_t)(position.x / 100), beginz = (int8_t)(-position.z / 100);
	int8_t endx = (int8_t)(end.x / 100), endz = (int8_t)(-end.z / 100);
	//make sure that begining values are lesser than ending ones
	if(endx < 0)
		endx = 0;
	if(endz < 0)
		endz = 0;
	if(beginx > endx) {
		int8_t tmp = beginx;
		beginx = endx;
		endx = tmp;
	}
	if(beginz > endz) {
		int8_t tmp = beginz;
		beginz = endz;
		endz = tmp;
	}
	//make sure we are not out of bounds
	if(beginx < 0)
		beginx = 0;
	if(beginz < 0)
		beginz = 0;
	if(endx > 63)
		endx = 63;
	if(endz > 63)
		endz = 63;

	//which walls to check
	uint32_t count = 0, check[2];
	//north
	if(direction.z < 0.0f) {
		check[count] = 0;
		count++;
	}
	//south
	else if(direction.z > 0.0f) {
		check[count] = 2;
		count++;
	}
	//east
	if(direction.x > 0.0f) {
		check[count] = 1;
		count++;
	}
	//west
	else if(direction.x < 0.0f) {
		check[count] = 3;
		count++;
	}

	//distaince of the closest object
	float distaince = weapon->getRange();
	GameObject *closestobject = 0;
	uint32_t closestobjoffset;

	//check all fields for collisions
	for(int8_t c = beginx; c <= endx; c++) {
		for(int8_t r = beginz; r <= endz; r++) {
			//position's offset in the map buffer
			uint32_t offset = c * 8 + r * 512;

			//get info about existing walls in the square
			uint16_t walls = mLevelMap[offset + 4];
			for(uint32_t i = 0; i < count; ++i) {
				if((walls & (1 << check[i])) == 0)
					continue;
				Mesh3DgUID meshnum = mLevelMap[offset + check[i]];
				GameObject *obj = mObjects->getGameObject(meshnum);
				if(obj->getAmmoCollisionType() == ACTYPE_NONE)
					continue;
				float dist = 0.0f;
				switch(obj->getType()) {
					case GOTYPE_WALL:
						dist = wallCollisionAmmo(position, end, c, r, check[i], obj);
						break;
					case GOTYPE_DOOR: {
						//only if not open
						if(isDoorOpen((GODoor *)obj))
							dist = wallCollisionAmmo(position, end, c, r, check[i], obj);
						break;
					}
					default:
						break;
				}
				if(dist != 0.0f and dist < distaince) {
					distaince = dist;
					closestobject = obj;
					closestobjoffset = offset;
				}
			}

			//sprites
			Mesh3DgUID meshnum = mLevelMap[offset + 5];
			if(mLevelMap[offset + 4] & 16) {
				GameObject *obj = mObjects->getGameObject(meshnum);
				if(obj->getAmmoCollisionType() != ACTYPE_NONE) {
					float dist = spriteCollisionAmmo(position, end, c, r, weapon->getRange());
					if(dist != 0.0f and dist < distaince) {
						distaince = dist;
						closestobject = obj;
						closestobjoffset = offset;
					}
				}
			}
		}
	}

	//now check for enemies
	for(EnemiesList::iterator i = mEnemies.begin(); i != mEnemies.end(); i++) {
		GameObject *obj = (GameObject *)*i;
		if(obj->getAmmoCollisionType() != ACTYPE_NONE) {
			Vector3Dg enemypos = ((Enemy *)obj)->getPosition();
			enemypos.y = 0.0f;
			float dist = enemyCollisionAmmo(position, end, enemypos, weapon->getRange());
			if(dist != 0.0f and dist < distaince) {
				distaince = dist;
				closestobject = obj;
			}
		}
	}

	//check the player
	float playerDistaince = playerCollisionAmmo(position, end, weapon->getRange());
	//if the distaince to the player is less than the distaince to the closest object
	//then the player is hit
	if(playerDistaince > 0.0 and playerDistaince < distaince) {
		playerHit(weapon->getPower(), direction);
		return;
	}

	//if any object has been hit
	if(closestobject) {
		Vector3Dg explosionpos = position + (direction * (distaince - 5.0f));
		switch(closestobject->getAmmoCollisionType()) {
			case ACTYPE_UNBREAKABLE:
				displayExplosion(explosionpos, EXPLT_SMALL, 0, true);
				return;
			case ACTYPE_EXPLODABLE: {
				if(closestobject->getType() != GOTYPE_SPRITE)
					return;
				//if run out of the hp
				if(((GOSprite *)closestobject)->decreaseHP(weapon->getPower()) <= 0) {
					closestobject->getMesh()->setProperty(MRP_RENDERABLE, false);
					//rem info in map
					mLevelMap[closestobjoffset + 4] &= ~16;
					//if there is an equipment near the explodable object then destroy it to
					if(mLevelMap[closestobjoffset + 4] & 32) {
						Mesh3DgUID meshnum = mLevelMap[closestobjoffset + 6];
						mObjects->meshes[meshnum]->setProperty(MRP_RENDERABLE, false);
						//rem info in map
						mLevelMap[closestobjoffset + 4] &= ~32;
					}
					explosionCollision(explosionpos);
					displayHugeExplosion(explosionpos, direction);
					mSDL->playSound(mCollisionSounds[CSND_EXPLOSION], 3, calculateVolume(explosionpos, VOLDIST_TYPE1));
				}
				//else - only display the ammo's explosion
				else displayExplosion(explosionpos, EXPLT_SMALL, 0, true);
				return;
			}
			case ACTYPE_ENEMY: {
				displayExplosion(explosionpos, EXPLT_SMALL, 0, true);

				Enemy *enemy = (Enemy *)closestobject;
				//decrease the HP and die, if necessary
				//(yes, in the original game, the enemy died after his HP went below 0)
				if(enemy->isBurning() or not enemy->isAlive())
					return;
				if(enemy->hit(weapon->getPower(), mCameraPosition) < 0) {
					enemy->die();
					//sometimes leave a present
					leaveAmmo(enemy);
					//blood
					spreadBlood(enemy, direction, distaince);
					mSDL->playSound(mEnemySounds[ESND_DEATH], 4, calculateVolume(explosionpos, VOLDIST_TYPE3));
				} else hitRoar(enemy);
				checkEnemyCollisions(enemy);
				enemy->move();
				return;
			}
			default:
				return;
		}
	}
}

void GameControl::displayHugeExplosion(Vector3Dg &position, Vector3Dg &direction)
{
	displayExplosion(position, EXPLT_BIG, 0, false);

	Vector3Dg vec(-direction.z, direction.y, direction.x);
	Vector3Dg vec2 = position - (direction * 10.0f);
	displayExplosion(vec2, EXPLT_BIG, 200, true);
	vec2 = position - (vec * 100.0f) - (direction * 10.0f);
	displayExplosion(vec2, EXPLT_BIG, 200, true);
	vec2 = position + (vec * 100.0f) - (direction * 10.0f);
	displayExplosion(vec2, EXPLT_BIG, 200, true);
	vec2 = position - (vec * 80.0f) + (direction * 10.0f);
	displayExplosion(vec2, EXPLT_BIG, 200, true);
	vec2 = position + (vec * 80.0f) + (direction * 10.0f);
	displayExplosion(vec2, EXPLT_BIG, 200, true);
}

void GameControl::displayExplosion(Vector3Dg &position, ExplosionType type, uint32_t delay,
                                 bool yshift)
{
	Mesh3Dg *mesh = mObjects->createMesh(mObjects->generateMeshUID());
	if(mesh == 0)
		return;
	uint32_t time = 0;
	switch(type) {
		case EXPLT_SMALL:
		case EXPLT_ELECTRIC:
			time = 600;
			break;
		case EXPLT_BIG:
			time = 800;
			break;
		break;
	}
	TemporaryObject *obj = mObjects->createTempGameObject(mesh, time);
	if(obj == 0) {
		mObjects->destroyMesh(mesh->getUID());
		return;
	}
	//set the animation start delay
	if(delay > 0) {
		obj->setAnimStartDelay(delay);
		mesh->setProperty(MRP_RENDERABLE, false);
	}
	//render properties for mesh
	mesh->setProperty(MRP_TRANSPARENT, true);
	mesh->setProperty(MRP_Z_MINUS_OFFSET, true);
	//position
	float y = position.y;
	if(yshift)
		y += 5.0f - (rand() / (double)RAND_MAX) * 10.0f;
	mesh->setPosition(position.x, y, position.z);
	switch(type) {
		case EXPLT_SMALL: {
			//mesh base
			mesh->setBase(mSmallExplosionBase);
			//animation
			mesh->animation.texAnimTime = time;
			mesh->animation.beginMtl = 85;
			mesh->animation.numMtls = 3;
			break;
		}
		case EXPLT_BIG: {
			//mesh base
			mesh->setBase(mBigExplosionBase);
			//animation
			mesh->animation.texAnimTime = time;
			mesh->animation.beginMtl = 81;
			mesh->animation.numMtls = 4;
			break;
		}
		case EXPLT_ELECTRIC: {
			//mesh base
			mesh->setBase(mSmallExplosionBase);
			//animation
			mesh->animation.texAnimTime = time;
			mesh->animation.beginMtl = 88;
			mesh->animation.numMtls = 3;
			break;
		}
	}

	//add the object to the render and temp-objects lists
	mTempObjects.push_back(obj);
	mOGL->addMeshToRenderList(mesh);
}

bool GameControl::cardCollision(UsableItemType cardColor)
{
	uint32_t posOffset = mPosition[0] * 8 + mPosition[1] * 512;
	uint32_t faceDir = getFaceDir();
	Mesh3DgUID meshUID = mLevelMap[posOffset + faceDir];
	//if there is no wall
	if(not (mLevelMap[posOffset + 4] & (1 << faceDir))) {
		mOGL->osd.addText(mTexts[NO_SLOT_TEXTS].getText());
		return false;
	}
	//check for slot
	GameObject *obj = mObjects->getGameObject(meshUID);
	if(obj->getType() == GOTYPE_DOOR) {
		mOGL->osd.addText(mTexts[NO_SLOT_TEXTS].getText());
		return false;
	}

	assert(obj->getType() == GOTYPE_WALL);

	Mesh3Dg *slotMesh = ((GOWall *)obj)->getSlotMesh();
	if(slotMesh == 0) {
		mOGL->osd.addText(mTexts[NO_SLOT_TEXTS].getText());
		return false;
	}
	//check if the slot isn't used
	uint32_t mtlNum = slotMesh->materialNumber;
	switch(mtlNum) {
		case 51:
		case 142:
		case 145:
		case 148:
			break;
		case 52:
		case 143:
		case 146:
		case 149:
			mOGL->osd.addText(mTexts[SLOT_USED_TEXTS].getText());
			return false;
		default:
			mOGL->osd.addText(mTexts[NO_SLOT_TEXTS].getText());
			return false;
	}
	//check for action desc
	MapDataPtr data = &mLevelMap[0x8000];
	uint32_t dataOffset = getDataOffset(((GOWall *)obj)->getSlotActionDataOffset() + 1, data);

	//if no action desc found then the slot is broken
	if(dataOffset == 0) {
		mOGL->osd.addText(mTexts[SLOT_BROKEN_TEXTS].getText());
		return false;
	}
	dataOffset++;
	//if wrong color
	uint16_t cardColorNumber;
	switch(cardColor) {
		case UIT_RED_CARD:
			cardColorNumber = 1;
			break;
		case UIT_GREEN_CARD:
			cardColorNumber = 2;
			break;
		case UIT_BLUE_CARD:
			cardColorNumber = 3;
			break;
		//should not happen
		default:
			return false;
	}
	if(data[dataOffset] != cardColorNumber) {
		mOGL->osd.addText(mTexts[CARD_WRONG_TEXTS].getText());
		return false;
	}
	mOGL->osd.addText(mTexts[CARD_INSERT_TEXTS].getText());
	//change material
	slotMesh->materialNumber++;
	((GOWall *)obj)->setBasicSlotMaterial(((GOWall *)obj)->getBasicSlotMaterial() + 1);
		
	dataOffset++;
	//do action
	while(data[dataOffset] != 0xFFFF) {
		doActions(dataOffset, data);
		dataOffset += 2;
	}
	return true;
}

void GameControl::doActions(uint32_t dataOffset, MapDataPtr data)
{
	uint8_t byte = (uint8_t)data[dataOffset];
	uint8_t z = (uint8_t)(data[dataOffset + 1] / 512);
	uint8_t x = (uint8_t)((data[dataOffset + 1] % 512) / 8);
	switch(byte) {
		//open the door
		case 0: {
			Mesh3DgUID meshUID = mLevelMap[data[dataOffset + 1]];
			//if closed then open
			GODoor *door = (GODoor *)mObjects->getGameObject(meshUID);
			uint32_t doorType = door->getDoorType();
			animDoorOpen(door, doorType);
			//if double door - open the second part too
			if(doorType == DOORTYPE_HORIZONTAL) {
				door = (GODoor *)mObjects->getGameObject(meshUID+1);
				animDoorOpen(door, DOORTYPE_UNDEFINED);
			}
			break;
		}
		//close the door
		case 1: {
			Mesh3DgUID meshUID = mLevelMap[data[dataOffset + 1]];
			//if opened then close
			GODoor *door = (GODoor *)mObjects->getGameObject(meshUID);
			uint32_t doorType = door->getDoorType();
			animDoorClose(door, doorType);
			//if double door
			if(doorType == DOORTYPE_HORIZONTAL) {
				door = (GODoor *)mObjects->getGameObject(meshUID+1);
				animDoorClose(door, DOORTYPE_UNDEFINED);
			}
			break;
		}
		//set door blocade
		case 2:
			mLevelMap[z * 512 + x * 8 + 4] |= 64;
			break;
		//remove door blocade
		case 3:
			mLevelMap[z * 512 + x * 8 + 4] &= ~64;
			break;
		//set/remove wall
		case 4: {
			uint8_t wallMtl = (uint8_t)(data[dataOffset] >> 8);
			MapDataType offset = data[dataOffset + 1];
			Mesh3DgUID meshUID = mLevelMap[offset];
			uint8_t z = (uint8_t)(offset / 512);
			uint8_t x = (uint8_t)((offset % 512) / 8);
			//calculate the offset once again
			uint16_t offset2 = (uint16_t)(x * 8 + z * 512);
			uint16_t wallNum = (uint16_t)(offset - offset2);
			//if the wall is visible
			bool isWall = (mLevelMap[offset2 + 4] & (1 << wallNum)) ? true : false;
			//remove the wall only if it is there
			if(wallMtl == 0 and isWall) {
				//don't render
				mObjects->meshes[meshUID]->setProperty(MRP_RENDERABLE, false);
				//remove info in the map
				mLevelMap[offset2 + 4] &= ~(1 << wallNum);
				break;
			}
			//if only changing the wall then just switch materials
			if(wallMtl != 0 and isWall) {
				Mesh3Dg *mesh = mObjects->meshes[meshUID];
				mesh->materialNumber = wallMtl - 1;
				GameObject *obj = (GameObject *)mesh->getChild();
				//collision type for new wall based on the material
				if((wallMtl - 1) == 26) {
					obj->setPlayerCollisionType(PCTYPE_NONE);
					obj->setAmmoCollisionType(ACTYPE_NONE);
					//remove info in the map
					mLevelMap[offset2 + 4] &= ~(1 << wallNum);
				} else {
					obj->setPlayerCollisionType(PCTYPE_BUMP);
					obj->setAmmoCollisionType(ACTYPE_UNBREAKABLE);
					//add info in map
					mLevelMap[offset2 + 4] |= (1 << wallNum);
				}
				break;
			}
			//set the wall, only if it isn't there
			if(wallMtl != 0 and not isWall) {
				//make renderable
				mObjects->meshes[meshUID]->setProperty(MRP_RENDERABLE, true);
				//add info in map
				mLevelMap[offset2 + 4] |= (1 << wallNum);
				break;
			}
			break;
		}
		//set/remove equipment
		case 5: {
			uint8_t eqMtl = (uint8_t)(data[dataOffset] >> 8);
			MapDataType offset = data[dataOffset + 1];
			//calculate position offset w/o shift
			//(in action data the offset is sometimes shifted (why?))
			uint8_t z = (uint8_t)(offset / 512);
			uint8_t x = (uint8_t)((offset % 512) / 8);
			//calculate offset shifted by 6
			offset = (uint16_t)(x * 8 + z * 512 + 6);
			Mesh3DgUID meshUID = mLevelMap[offset];
			bool isEquipment = (mLevelMap[offset - 2] & 32) ? true : false;
			//remove equipment - only if it is there
			if(eqMtl == 0 and isEquipment) {
				mObjects->meshes[meshUID]->setProperty(MRP_RENDERABLE, false);
				//rem info in map
				mLevelMap[offset - 2] &= ~32;
				break;
			}
			//change equipment
			if(eqMtl != 0 and isEquipment) {
				mObjects->meshes[meshUID]->materialNumber = eqMtl - 1;
				break;
			}
			//set equipment
			if(eqMtl != 0 and not isEquipment) {
				//make renderable
				mObjects->meshes[meshUID]->setProperty(MRP_RENDERABLE, true);
				//add info in map
				mLevelMap[offset - 2] |= 32;
				break;
			}
			break;
		}
		//set/remove sprite
		case 6: {
			uint8_t spriteMtl = (uint8_t)(data[dataOffset] >> 8);
			MapDataType offset = data[dataOffset + 1];
			//calculate position offset w/o shift
			//(in action data the offset is sometimes shifted (why?))
			uint8_t z = (uint8_t)(offset / 512);
			uint8_t x = (uint8_t)((offset % 512) / 8);
			//calculate offset shifted by 5
			offset = (uint16_t)(x * 8 + z * 512 + 5);
			Mesh3DgUID meshUID = mLevelMap[offset];
			bool isSprite = (mLevelMap[offset - 1] & 16) ? true : false;
			//remove sprite - only if it is there
			if(spriteMtl == 0 and isSprite) {
				mObjects->meshes[meshUID]->setProperty(MRP_RENDERABLE, false);
				//rem info in map
				mLevelMap[offset - 1] &= ~16;
				break;
			}
			//change sprite
			if(spriteMtl != 0 and isSprite) {
				--spriteMtl;
				Mesh3Dg *mesh = mObjects->meshes[meshUID];
				mesh->materialNumber = spriteMtl;
				GameObject *obj = (GameObject *)mesh->getChild();
				switch(spriteMtl) {
					case 30:
						obj->setPlayerCollisionType(PCTYPE_TELEPORT);
						break;
					case 27:
					case 28:
						obj->setPlayerCollisionType(PCTYPE_STOP);
						obj->setAmmoCollisionType(ACTYPE_UNBREAKABLE);
						break;
					case 44:
						obj->setPlayerCollisionType(PCTYPE_STOP);
						obj->setAmmoCollisionType(ACTYPE_EXPLODABLE);
						break;
					default:
						obj->setPlayerCollisionType(PCTYPE_NONE);
						break;
				}
				break;
			}
			//set sprite
			if(spriteMtl != 0 and not isSprite) {
				//make renderable
				mObjects->meshes[meshUID]->setProperty(MRP_RENDERABLE, true);
				//add info in map
				mLevelMap[offset - 1] |= 16;
				break;
			}
			break;
		}
	}
}

void GameControl::enemyBackWallCollision(Enemy *enemy, Mesh3Dg *wallMesh, int8_t wallDir, float ray)
{
	//calculate normal vector for the wall
	Vector3Dg normal(0.0f, 0.0f, 0.0f);
	switch(wallDir) {
		case 0: normal.z = -1.0f; break;
		case 1: normal.x = 1.0f; break;
		case 2: normal.z = 1.0f; break;
		case 3: normal.x = -1.0f; break;
	}
	//coordinates of the first vertex in the first triangle (a pointer is enough)
	const Point3Dg *pt = wallMesh->getBase()->getVCoordsStream();
	//the distaince between the enemy and the wall
	float dist = -normal.x * (pt->x - enemy->getPosition().x)
	             -normal.z * (pt->z - enemy->getPosition().z);
	//if distance from wall is lesser than the enemy's ray
	if(dist < ray) {
		Vector3Dg vec = enemy->getActualVelocity();
		vec.normalize();
		//if angle between direction and wall normal is < 45 deg
		//(can happen after going through a wall from the back)
		if(vec.dotProduct(normal) > 0.0f)
			return;
		//slide on the wall
		vec.x = vec.y = vec.z = 0.0f;
		enemy->setActualVelocity(vec);
	}
}

void GameControl::enemyWallCollision(Enemy *enemy, Mesh3Dg *wallMesh, int8_t wallDir, float ray)
{
	//calculate normal vector for the wall
	Vector3Dg normal(0.0f, 0.0f, 0.0f);
	switch(wallDir) {
		case 0: normal.z = 1.0f; break;
		case 1: normal.x = -1.0f; break;
		case 2: normal.z = -1.0f; break;
		case 3: normal.x = 1.0f; break;
	}
	//coordinates of the first vertex in the first triangle (a pointer is enough)
	const Point3Dg *pt = wallMesh->getBase()->getVCoordsStream();
	//the distaince between the enemy and the wall
	float dist = -normal.x * (pt->x - enemy->getPosition().x)
	             -normal.z * (pt->z - enemy->getPosition().z);
	//if distance from wall is lesser than the enemy's ray
	if(dist < ray) {
		Vector3Dg vec = enemy->getActualVelocity();
		vec.normalize();
		//if angle between direction and wall normal is < 45 deg
		//(can happen after going through a wall from the back)
		if(vec.dotProduct(normal) > SQR2BY2)
			return;
		//slide on the wall
		normal *= ray - dist;
		vec = enemy->getActualVelocity() + normal;
		enemy->setActualVelocity(vec);
	}
}

bool GameControl::bulletEdgesCollision(bool *edges, Vector3Dg &begin, Vector3Dg &end, int8_t x, int8_t z, float edgeRay, Vector3Dg &destination)
{
	Vector3Dg way = end - begin;

	//calculate coordinates of all edges and put them into arrays
	float posX = x * 100.0f;
	float posZ = -(z * 100.0f);
	float edgesXCoords[] = {posX, posX + 100.0f, posX + 100.0f, posX};
	float edgesZCoords[] = {posZ - 100.0f, posZ - 100.0f, posZ, posZ};
	//check collisions with all edges
	for(int i = 0; i < 4; i++) {
		if(not edges[i])
			continue;
		//a vector connecting the begin point with the edge
		Vector3Dg vec(edgesXCoords[i] - begin.x, 0.0f, edgesZCoords[i] - begin.z);
		//calculate a projection of the edge on the way vector in coords relative to the
		//vector {begin -> 0.0f; end -> 1.0f)
		float projectionPos = way.dotProduct(vec) / way.lengthPow2();
		//calculate the coords of a point that we gonna use to calculate the distaince
		//between the way and the edge (store it in the destination vector);
		//the distaince will be calculated either from the begining of the way,
		if(projectionPos <= 0.0f)
			destination = begin;
		//the ending of the way,
		else if(projectionPos >= 1.0f)
			destination = end;
		//or from a point being the edge projected on the way
		else {
			//calculate the coords of the projection:
			//shorten the way vector so that it points to the projection position
			way *= projectionPos;
			//the projected point itself:
			destination = begin + way;
		}
		//finally, calculate the distaince between the point and the edge
		//(we can use the power of 2 - less calculations (no sqrtf))
		float distaincePow2 = (edgesXCoords[i] - destination.x) * (edgesXCoords[i] - destination.x) +
		                      (edgesZCoords[i] - destination.z) * (edgesZCoords[i] - destination.z);
		if(distaincePow2 < edgeRay * edgeRay)
			return true;
	}
	return false;
}

void GameControl::enemyEdgesCollision(bool *edges, Enemy *enemy, int8_t x, int8_t z, float ray)
{
	float posX = x * 100.0f - enemy->getPosition().x;
	float posZ = -(z * 100.0f) - enemy->getPosition().z;

	if(edges[0]) {
		float x = posX;
		float z = posZ - 100.0f;
		if(x * x + z * z < ray * ray) {
			float length = sqrtf(x * x + z * z);
			length = ray / length - 1.0f;
			Vector3Dg vec = enemy->getActualVelocity();
			vec.x -= x * length;
			vec.z -= z * length;
			enemy->setActualVelocity(vec);
			return;
		}
	}
	if(edges[1]) {
		float x = posX + 100.0f;
		float z = posZ - 100.0f;
		if(x * x + z * z < ray * ray) {
			float length = sqrtf(x * x + z * z);
			length = ray / length - 1.0f;
			Vector3Dg vec = enemy->getActualVelocity();
			vec.x -= x * length;
			vec.z -= z * length;
			enemy->setActualVelocity(vec);
			return;
		}
	}
	if(edges[2]) {
		float x = posX + 100.0f;
		float z = posZ;
		if(x * x + z * z < ray * ray) {
			float length = sqrtf(x * x + z * z);
			length = ray / length - 1.0f;
			Vector3Dg vec = enemy->getActualVelocity();
			vec.x -= x * length;
			vec.z -= z * length;
			enemy->setActualVelocity(vec);
			return;
		}
	}
	if(edges[3]) {
		float x = posX;
		float z = posZ;
		if(x * x + z * z < ray * ray) {
			float length = sqrtf(x * x + z * z);
			length = ray / length - 1.0f;
			Vector3Dg vec = enemy->getActualVelocity();
			vec.x -= x * length;
			vec.z -= z * length;
			enemy->setActualVelocity(vec);
		}
	}
}

uint16_t GameControl::wallCollision(GameObject *obj, int8_t wallDir)
{
	//calculate normal vector for the wall
	Vector3Dg normal(0.0f, 0.0f, 0.0f);
	switch(wallDir) {
		case 0: normal.z = 1.0f; break;
		case 1: normal.x = -1.0f; break;
		case 2: normal.z = -1.0f; break;
		case 3: normal.x = 1.0f; break;
	}
	Mesh3Dg *mesh = obj->getMesh();
	//coordinates of the first vertex in the first triangle (a pointer is enough)
	const Point3Dg *pt = mesh->getBase()->getVCoordsStream();

	float dist = -normal.x * (pt->x - mPrevCamPosition.x - mVelocity.x)
	             -normal.z * (pt->z - mPrevCamPosition.z - mVelocity.z);
	//if distance from wall is lesser than camera ray (dist is always > 0)
	if(dist < mCamRay) {
		Vector3Dg normalizedV = mVelocity;
		normalizedV.normalize();
		float dotProd = normalizedV.dotProduct(normal);
		//if angle between velocity and wall normal is < 45 deg
		//(can happen after going through a wall from the back)
		if(dotProd > SQR2BY2)
			return 0;
		//bump the wall
		if(dotProd < -SQR2BY2) {
			intfloat ix, iz;
			//module
			ix.f = mVelocity.x; ix.i &= 0x7FFFFFFF;
			iz.f = mVelocity.z; iz.i &= 0x7FFFFFFF;
			//calculate the auxiliary velocity that will be used to
			//simulate the inertia after bumping the wall
			//(we don't zero the regular velocity here 'cause we want the
			//bump to begin at the position to which the player actually
			//got; the regular velocity will be zeroed as soon as the
			//inertia simulation starts)
			mAuxiliaryV.x = mVelocity.x + normal.x * ix.f * 2;
			mAuxiliaryV.z = mVelocity.z + normal.z * iz.f * 2;
			mAuxVTime = 250;
			//if door collision
			if(obj->getType() == GOTYPE_DOOR) {
				//and its material number
				uint32_t mtlNum = ((GODoor *)obj)->getFrameMesh()->materialNumber;
				switch(mtlNum) {
					//closed or opening?
					case 14:
					case 17:
						mOGL->osd.addText(mTexts[BUMP_DOOR_TEXTS].getText());
						break;
					case 16:
					case 19:
						mOGL->osd.addText(mTexts[BUMP_OPENING_TEXTS].getText());
						break;
				}
			}
			//normal wall collision
			else mOGL->osd.addText(mTexts[BUMP_WALL_TEXTS].getText());
			excite();
			mSDL->playSound(mCollisionSounds[CSND_WALL_BUMP], 1);
			if(mVisionNoise)
				displayNoise();
			if(mHighDificulty and not mImmortal)
				return 1;
			return 0;
		}
		//if no bumping then just move the player away from the wall to a
		//distaince equal to the camera's ray
		normal *= mCamRay - dist;
		mVelocity += normal;
	}
	return 0;
}

void GameControl::edgesCollision(bool *pEdges)
{
	float posX = mPosition[0] * 100.0f - mPrevCamPosition.x - mVelocity.x;
	float posZ = -(mPosition[1] * 100.0f) - mPrevCamPosition.z - mVelocity.z;

	if(pEdges[0]) {
		float x = posX;
		float z = posZ - 100.0f;
		if(x * x + z * z < mCamRay * mCamRay) {
			float length = sqrtf(x * x + z * z);
			length = mCamRay / length - 1.0f;
			mVelocity.x -= x * length;
			mVelocity.z -= z * length;
			return;
		}
	}
	if(pEdges[1]) {
		float x = posX + 100.0f;
		float z = posZ - 100.0f;
		if(x * x + z * z < mCamRay * mCamRay) {
			float length = sqrtf(x * x + z * z);
			length = mCamRay / length - 1.0f;
			mVelocity.x -= x * length;
			mVelocity.z -= z * length;
			return;
		}
	}
	if(pEdges[2]) {
		float x = posX + 100.0f;
		float z = posZ;
		if(x * x + z * z < mCamRay * mCamRay) {
			float length = sqrtf(x * x + z * z);
			length = mCamRay / length - 1.0f;
			mVelocity.x -= x * length;
			mVelocity.z -= z * length;
			return;
		}
	}
	if(pEdges[3]) {
		float x = posX;
		float z = posZ;
		if(x * x + z * z < mCamRay * mCamRay) {
			float length = sqrtf(x * x + z * z);
			length = mCamRay / length - 1.0f;
			mVelocity.x -= x * length;
			mVelocity.z -= z * length;
		}
	}
}

void GameControl::openDoor(Mesh3DgUID meshUID, GODoor *door)
{
	//door material number
	uint32_t doorType = door->getDoorType();
	//open the door only if not fully opened
	if(door->getMesh()->animation.beginMtl + 1 != door->getFrameMesh()->materialNumber) {
		animDoorOpen(door, doorType);
		//if double door
		if(doorType == DOORTYPE_HORIZONTAL) {
			door = (GODoor *)mObjects->getGameObject(meshUID+1);
			animDoorOpen(door, DOORTYPE_UNDEFINED);
		}
	}
}

void GameControl::closePrevDoor(uint16_t prevOffset, uint16_t currentPosOffset)
{
	bool currentPosLocked = mLevelMap[currentPosOffset + 4] & 64;
	//check all doors at previous position
	for(uint32_t i = 0; i < 4; i++) {
		//i-th mesh on the previous position
		Mesh3DgUID meshUID = mLevelMap[prevOffset + i];
		if(meshUID == 0)
			continue;
		//number of a mesh on the current position and of direction oposite
		//to the meshUID (ie. if meshUID is faced EAST then meshNum2 is
		//faced WEST)
		Mesh3DgUID meshUID2 = mLevelMap[currentPosOffset + ((i+2) % 4)];
		//if it is the same mesh then these are the door the player just have
		//went through; in such case don't close this door if they aren't locked
		//on the current position
		if((meshUID == meshUID2) and not currentPosLocked)
			continue;
		//if there are any
		GameObject *obj = mObjects->getGameObject(meshUID);
		if(obj->getType() == GOTYPE_DOOR) {
			GODoor *door = (GODoor *)obj;
			uint32_t doorType = door->getDoorType();
			animDoorClose(door, doorType);
			//if double door
			if(doorType == DOORTYPE_HORIZONTAL) {
				door = (GODoor *)mObjects->getGameObject(meshUID+1);
				animDoorClose(door, DOORTYPE_UNDEFINED);
			}
		}
	}
}

void GameControl::animDoorOpen(GODoor *door, uint32_t doorType)
{
	//don't start the 'opening' animation if it is already in progress
	Mesh3Dg *mesh = door->getMesh();
	if(mesh->animation.animStatus != ANIM_FORWARD and
	   mesh->animation.elapsedTime != (int32_t)mesh->animation.geomAnimTime) {
		mSDL->playSound(mDoorSounds[doorType], 6, calculateVolume(mesh->geomCenter(), VOLDIST_TYPE1));
		mesh->animation.animStatus = ANIM_FORWARD;
		door->getFrameMesh()->materialNumber = mesh->animation.beginMtl + 2;
	}
}

void GameControl::animDoorClose(GODoor *door, uint32_t doorType)
{
	Mesh3Dg *mesh = door->getMesh();
	if(mesh->animation.animStatus != ANIM_BACKWARD and mesh->animation.elapsedTime != 0) {
		mSDL->playSound(mDoorSounds[doorType], 6, calculateVolume(mesh->geomCenter(), VOLDIST_TYPE1));
		mesh->animation.animStatus = ANIM_BACKWARD;
		door->getFrameMesh()->materialNumber = mesh->animation.beginMtl + 2;
	}
}

bool GameControl::enemyObjectCollision(Vector3Dg &objPos, Enemy *enemy, float collisionDistance)
{
	Vector3Dg &pos = enemy->getPosition();
	Vector3Dg &vel = enemy->getActualVelocity();
	//a vector connecting the enemy and the object
	//(this code is a bit faster than the overloaded + and - operators in the Vector3Dg class
	//and here the speed is critical, as this function is called in a nested loop)
	Vector3Dg vec(objPos.x - (pos.x + vel.x), 0.0f, objPos.z - (pos.z + vel.z));
	//it's length (squared, so we won't call the expensive sqrt() unnecessarily)
	float dist = vec.x * vec.x + vec.z * vec.z;
	//if distaince is greater than the enemy's ray - there is no collision
	if(dist < collisionDistance * collisionDistance) {
		//compare the enemy's direction with the calculated vector's direction
		//(we do not need to normalize the vector here, as we only check if the dot product
		//is greater than 0)
		//if the angle between the vectors is less than 90 deg, then the enemy had collided
		//with the object
		if(enemy->getDirection().dotProduct(vec) > 0.0f) {
			vec.x = vec.y = vec.z = 0.0f;
			enemy->setActualVelocity(vec);
			return true;
		}
	}
	return false;
}

void GameControl::enemyCollision(Enemy *enemyToCheck)
{
	//check all enemies
	for(EnemiesList::iterator i = mEnemies.begin(); i != mEnemies.end(); i++) {
		Enemy *enemy = *i;
		//don't check against itself
		if(enemy != enemyToCheck and enemy->isAlive())
			enemyObjectCollision(enemy->getPosition(), enemyToCheck, 50.0f);
	}
}

bool GameControl::spriteCollision(Mesh3Dg *mesh)
{
	//a vector connecting the player and the mesh
	Vector3Dg vec(mesh->translation.r4c1, 0.0f, mesh->translation.r4c3);
	vec = vec - (mPrevCamPosition + mVelocity);
	//it's lenght
	float dist = vec.length();
	//if distaince is greater than camera ray - there is no collision
	if(dist < mCamRay) {
		float length = mVelocity.length();
		if(length == 0.0f)
			return true;
		mVelocity *= (length - mCamRay + dist) / length;
		return true;
	}
	return false;
}

uint16_t GameControl::enemyCollision()
{
	//check all enemies
	for(EnemiesList::iterator i = mEnemies.begin(); i != mEnemies.end(); i++) {
		Enemy *enemy = *i;
		if(enemy->getPlayerCollisionType() != PCTYPE_NONE and spriteCollision(enemy->getMesh())) {
			if(enemy->isBurning() and mPlayerBurnDiff >= PLAYER_BURN_DELAY) {
				mPlayerBurnDiff = 0;
				mOGL->osd.addText(mLocInterface->getText(LOC_GAME_HOT));
				mSDL->playSound(mCollisionSounds[CSND_TOUCH_FLAMES], 1);
				excite();
				if(mHighDificulty and not mImmortal)
					return 1;
			}
			return 0;
		}
	}
	return 0;
}

void GameControl::explosionCollision(Vector3Dg &position)
{
	//if to close to the explosion then decrease energy
	Vector3Dg vec = mCameraPosition - position;
	float dist = vec.length();
	vec /= dist;

	if(dist < mCamRay+50.0f) {
		if(dist < mCamRay+30.0f) {
			playerHit(25);
			mAuxiliaryV = vec * 2.0f;
		} else {
			playerHit(5);
			mAuxiliaryV = vec;
		}
		//calculate the auxiliary velocity that will be used to
		//simulate the inertia effect
		mAuxiliaryV.y = 0.0f;
		mAuxVTime = 250;
	}

	//on which field the explosion took place?
	uint32_t x = ((int)position.x/100);
	uint32_t z = -((int)position.z/100);
	uint32_t posOffset = x * 8 + z * 512;

	//burn enemies that stand to close
	for(EnemiesList::iterator i = mEnemies.begin(); i != mEnemies.end(); i++) {
		Enemy *enemy = *i;
		if(not enemy->isAlive())
			continue;
		//get the enemy position
		Vector3Dg enemypos = enemy->getPosition();
		enemypos.y = 0.0f;
		//offset of the field on which the enemy is standing
		uint32_t ex = ((int)enemypos.x/100);
		uint32_t ez = -((int)enemypos.z/100);
		uint32_t enemyPosOffset = ex * 8 + ez * 512;
		//if the enemy is standing on other field than the explosion took place, then check
		//if there is a wall between the explosion and the enemy
		if(enemyPosOffset != posOffset) {
			MapDataType walls = mLevelMap[posOffset + 4];
			if((walls & 1 and ez > z) or (walls & 2 and ex > x) or
				(walls & 4 and ez < z) or (walls & 8 and ex < x))
				continue;
		}

		//calculate a vector from the explosion's position to the enemy
		vec = enemypos - position;
		dist = vec.lengthPow2();
		//burn if to close
		if(dist < (75.0f * 75.f)) {
			if(enemy->burn()) {
				//turn off other fire sounds
				unregisterFireSounds();
				mSDL->playSound(mEnemySounds[ESND_FIRE], 5, 128, -1);
				enemy->registerSound(5);
			}
		}
	}
}

void GameControl::playerHit(uint16_t lostHP, Vector3Dg direction)
{
	//lose some energy
	if(not mImmortal)
		mLostEnergy += lostHP;
	//display vision noise
	if(mVisionNoise)
		displayNoise();
	//move away as a consequence of being hit
	mAuxiliaryV = direction * mConfig->getGameSpeed();
	mAuxVTime = 250;
	//inform the player
	mOGL->osd.addText(mTexts[HIT_TEXTS].getText());
	mSDL->playSound(mCollisionSounds[CSND_PLAYER_HIT], 1);
	//increase the heart beat, etc.
	excite();
}

void GameControl::excite()
{
	mHeartBeat += 11.0f;
	if(mHeartBeat > 56.0f)
		mHeartBeat = 56.0f;
	mOGL->osd.setHeartSpeed(mHeartBeat);
	mExhaust = (mHeartBeat - 11.0f) / 45.0f;
}

bool GameControl::equipmentCollision(Mesh3Dg *mesh, uint32_t posOffset)
{
	//camera distaince from object
	Vector3Dg vec(mesh->translation.r4c1, 0.0f, mesh->translation.r4c3);
	vec = vec - (mPrevCamPosition + mVelocity);
	//we don't have to calculate sqrtf - we can compare the dist^2 and ray^2
	float dist = vec.lengthPow2();

	float spriteRay = mCamRay;
	//if equipment lays on a barrel or sth. then make the sprite ray longer
	if((mLevelMap[posOffset + 4] & 16))
		spriteRay += 5.0f;
	//if distaince is greater than the sprite ray then there is no collision
	if(dist <= spriteRay * spriteRay)
		return true;
	return false;
}

bool GameControl::teleportCollision(Mesh3Dg *mesh, MapDataType posOffset, uint32_t &pastTime)
{
	//camera distaince from teleport
	Vector3Dg vec(mesh->translation.r4c1, 0.0f, mesh->translation.r4c3);
	vec = vec - (mPrevCamPosition + mVelocity);
	//we don't have to calculate sqrtf - we can compare the dist^2 and ray^2
	float dist = vec.lengthPow2();
	if(dist <= (mCamRay * mCamRay)) {
		MapDataPtr data = &mLevelMap[0x8000];
		//add 4 to the posOffset, because we look for the offset of the teleport, which
		//is equal to position offset + 4
		uint32_t dataOffset = getDataOffset(posOffset+4, data);
		//if it is finishing teleport
		if(dataOffset == 0)
			return false;
		dataOffset++;
		//if not working teleport (shouldn't happen, I think)
		if(data[dataOffset] == 0x8888)
			return true;
		float z = -(data[dataOffset] / 512 * 100.0f) - 50.0f,
			  x = ((data[dataOffset] % 512) / 8) * 100.0f + 50.0f;
		teleportCamera(x, z);
		pastTime = teleportDissolve(x, z);
		closePrevDoor(posOffset, data[dataOffset]);
	}
	return true;
}

uint32_t GameControl::teleportDissolve(float x, float z)
{
	//redraw the most recent frame in position before teleportation
	mOGL->renderFrame();
	//copy the frame to the accumulation buffer
	mOGL->preDissolve(0.0f, 0.0f, 0.0f, DT_DISSOLVE);
	//teleport the camera and render the frame after teleportation
	mOGL->setCamMatrix(x, z, mSinCameraRotation, mCosCameraRotation);
	mOGL->renderFrame();
	uint32_t lastTime = mSDL->getTicks(), pastTime = 0;
	while(pastTime < 750) {
		uint32_t curTime = mSDL->getTicks();
		uint32_t diff = curTime - lastTime;
		//frame rate limiter (max 100 fps during teleportation)
		//it helps controling the screen dissolve process
		if(diff < 10)
			continue;
		lastTime = curTime;
		//calculate the fade value
		float fadeValue = 0.005 * (float)diff;
		pastTime += diff;
		mOGL->dissolveFrame(DT_DISSOLVE, fadeValue);
		mOGL->osd.draw(mSinCameraRotation, mCosCameraRotation, 0);
		mSDL->swapBuffers();
	}
	mSDL->clearEventQueue();
	return pastTime;
}

void GameControl::teleportCamera(float x, float z)
{
	mCameraPosition.x = mPrevCamPosition.x = x;
	mCameraPosition.z = mPrevCamPosition.z = z;
	mPrevPosition[0] = mPosition[0];
	mPrevPosition[1] = mPosition[1];
	mPosition[0] = (uint8_t)(x / 100);
	mPosition[1] = (uint8_t)(-z / 100);
}

void GameControl::damegeWeapon(Weapon *weapon)
{
	int minshots = mHighDificulty ? 100 : 200;
	float randomnumber = (rand() / (double)RAND_MAX) * (mHighDificulty ? 100.0f : 1000.0f);
	weapon->damage(minshots, randomnumber);
}

void GameControl::useItem(UsableItem *item)
{
	switch(item->getType()) {
		case UIT_HAND:
			handCollision(item);
			break;
		case UIT_RED_CARD:
		case UIT_GREEN_CARD:
		case UIT_BLUE_CARD: {
			if(not ((Card *)item)->cardsCount)
				mOGL->osd.addText(mTexts[NO_CARDS_TEXTS].getText());
			else if(cardCollision(item->getType())) {
				--((Card *)item)->cardsCount;
				mSDL->playSound(item->useSoundNumber, 1);
			}
			break;
		}
		//regular weapons
		default: {
			//if the weapon is damaged
			if(item->getStatus() == WPNS_DAMAGED) {
				if(item->getType() == UIT_MASHINEGUN) {
					if(mMGFireDiff >= MACHINEGUN_FIRE_DELAY) {
						mOGL->osd.addText(mLocInterface->getText(LOC_GAME_WEAPON_DAMAGED));
						mSDL->playSound(((Weapon *)item)->noAmmoSoundNumber, 1);
						//delay next firing
						mMGFireDiff = 0;
					}
				} else {
					mOGL->osd.addText(mLocInterface->getText(LOC_GAME_WEAPON_DAMAGED));
					mSDL->playSound(((Weapon *)item)->noAmmoSoundNumber, 1);
				}
				break;
			}
			//if no ammo
			//(for mashinegun we handle the lack of ammo in a bit different way)
			if(not ((Weapon *)item)->getAmmo()->getAmount() and item->getType() != UIT_MASHINEGUN) {
				mOGL->osd.addText(mTexts[NO_AMMO_TEXTS].getText());
				mSDL->playSound(((Weapon *)item)->noAmmoSoundNumber, 1);
				break;
			}
			switch(item->getType()) {
				case UIT_HANDGUN:
				case UIT_SHOTGUN: {
					Weapon *weapon = (Weapon *)item;
					weapon->getAmmo()->use();
					damegeWeapon(weapon);
					mSDL->playSound(weapon->useSoundNumber, 1);
					Vector3Dg pos = mCameraPosition;
					pos.y = 50.0f;
					for(uint16_t i = 0; i < weapon->getBullets(); i++)
						ammoCollision(weapon, pos, mCameraDirection);
					break;
				}
				case UIT_MASHINEGUN: {
					if(mMGFireDiff >= MACHINEGUN_FIRE_DELAY) {
						//delay next firing
						mMGFireDiff = 0;
						Weapon *weapon = (Weapon *)item;
						if(not weapon->getAmmo()->getAmount()) {
							mOGL->osd.addText(mTexts[NO_AMMO_TEXTS].getText());
							mSDL->playSound(weapon->noAmmoSoundNumber, 1);
							break;
						}
						weapon->getAmmo()->use();
						damegeWeapon(weapon);
						mSDL->playSound(weapon->useSoundNumber, 1);
						Vector3Dg pos = mCameraPosition;
						pos.y = 50.0f;
						for(uint16_t i = 0; i < weapon->getBullets(); i++)
							ammoCollision(weapon, pos, mCameraDirection);
					}
					break;
				}
				case UIT_FLAMER:
				case UIT_BLASTER: {
					Weapon *weapon = (Weapon *)item;
					weapon->getAmmo()->use();
					damegeWeapon(weapon);
					mSDL->playSound(item->useSoundNumber, 1);
					Vector3Dg pos = mCameraPosition + (mCameraDirection * 25.0f);
					fireAmmo(weapon, pos, mCameraDirection);
					break;
				}
				case UIT_ROCKETLAUNCHER: {
					Weapon *weapon = (Weapon *)item;
					if(mSDL->getTicks() - mRocketLoadTime < 1000)
						mOGL->osd.addText(mLocInterface->getText(LOC_GAME_LOADING_LAUNCHER));
					else {
						weapon->getAmmo()->use();
						damegeWeapon(weapon);
						Vector3Dg pos = mCameraPosition + (mCameraDirection * 25.0f);
						fireAmmo(weapon, pos, mCameraDirection);
						mSDL->playSound(item->useSoundNumber, 1);
						//delay next firing
						mRocketLoadTime = mSDL->getTicks();
					}
					break;
				}
				default:
					break;
			}
			break;
		}
	}
}

uint32_t GameControl::getDataOffset(MapDataType objOffset, MapDataPtr data)
{
	uint32_t dataSize = (mObjects->getMapDataSize() / sizeof(MapDataType)) - 0x8000;
	for(uint32_t i = 1; i < dataSize; i++) {
		if(data[i] == objOffset and data[i - 1] == 0xFFFF)
			return i;
	}
	return 0;
}

int32_t GameControl::calculateVolume(Vector3Dg position, VolumeCalculationMethod method)
{
	Vector3Dg vec = position - mCameraPosition;
	float distaince = vec.length();
	float max = 0.0f;
	switch(method) {
		case VOLDIST_TYPE1:
			max = 1000.0f;
			break;
		case VOLDIST_TYPE2:
			max = 800.0f;
			break;
		case VOLDIST_TYPE3:
			max = 700.0f;
			break;
	}

	if(distaince > max)
		return 0;
	distaince *= 128.0f / max;
	return 128 - (int32_t)distaince;
}

void GameControl::displayNoise()
{
	uint32_t noiseLines[10];
	for(int32_t i = 0; i < 10; ++i)
		noiseLines[i] = rand();
	mOGL->osd.addNoise(noiseLines);
}

uint16_t GameControl::getNumberOfKilledEnemies()
{
	uint16_t counter = 0;
	for(EnemiesList::iterator i = mEnemies.begin(); i != mEnemies.end(); i++)
		if(not ((Enemy *)*i)->isAlive())
			counter++;

	return counter;
}

void GameControl::leaveAmmo(Enemy *enemy)
{
	//leave the ammo randomly (the probability is 0.25)
	if(not randomTrue(0.25f))
		return;

	//check if there is a place for ammo
	Vector3Dg pos = enemy->getPosition();
	uint32_t x = ((int)pos.x/100);
	uint32_t z = -((int)pos.z/100);
	uint32_t posOffset = x * 8 + z * 512;
	if(mLevelMap[posOffset + 4] & 32)
		return;

	//is there already an equipment mesh (at this point it can be only an unused mesh)?
	Mesh3DgUID uid = mLevelMap[posOffset + 6];
	Mesh3Dg *mesh;
	if(uid == 0) {
		uid = mObjects->generateMeshUID();
		mesh = mObjects->createMesh(uid);
		//no memory? no big deal - just don't create the equipment
		if(mesh == 0)
			return;
		mesh->setProperty(MRP_TRANSPARENT, true);
		//game object
		if(mObjects->createGameObject(GOTYPE_SPRITE, mesh) == 0) {
			mObjects->destroyMesh(uid);
			return;
		}
		//set the position
		pos.x = ((int)pos.x/100) * 100.0f + 50.0f;
		pos.z = ((int)pos.z/100) * 100.0f - 50.0f;
		mesh->setPosition(pos.x, 25.0f, pos.z);
		mesh->setBase(mEquipmentBase);
		mOGL->addMeshToRenderList(mesh);
		mLevelMap[posOffset + 6] = uid;
	} else {
		mesh = mObjects->meshes[uid];
		mesh->setProperty(MRP_RENDERABLE, true);
	}

	//the equipment type
	mesh->materialNumber = 61 + enemy->getWeaponType();
	//info about equipment on the map
	mLevelMap[posOffset + 4] |= 32;
}

void GameControl::spreadBlood(Enemy *enemy, Vector3Dg direction, float shooterDistaince)
{
	//calculate the position offset in map
	Vector3Dg pos = enemy->getPosition();
	uint32_t x = ((int)pos.x/100);
	uint32_t z = -((int)pos.z/100);
	uint32_t posOffset = x * 8 + z * 512;

	//check which walls are present on this field
	MapDataType walls = mLevelMap[posOffset + 4];
	for(uint32_t i = 0; i < 4; ++i) {
		if(walls & (1 << i)) {
			//calculate the dot product of the direction vector and the wall's normal vector:
			//the normal vector of the wall
			Vector3Dg normal(0.0f, 0.0f, 0.0f);
			switch(i) {
				case 0: normal.z = 1.0f; break;
				case 1: normal.x = -1.0f; break;
				case 2: normal.z = -1.0f; break;
				case 3: normal.x = 1.0f; break;
			}
			//the dot product - dependant on it's value, we can determine the wall's position
			//relative to the shooter
			float dotP = direction.dotProduct(normal);
			//if the wall is behind the enemy - spread the blood on this wall (choose one of
			//two blood textures that are suitable for this situation)
			if(dotP < -SQR2BY2) {
				//set the blood material
				GameObject *obj = mObjects->getGameObject(mLevelMap[posOffset + i]);
				if(obj->getType() != GOTYPE_WALL)
					continue;
				((GOWall *)obj)->setBlood(randomTrue(0.5f) ? 0 : 1);
				continue;
			}

			//the wall is in front of the enemy
			if(dotP > SQR2BY2)
				continue;

			//if the wall is at the side (only one blood texture for spreading on side walls)
			//spread the blood with some probability, dependant on the shooter's distainde from
			//the enemy
			if(not randomTrue((1000.0f - shooterDistaince) / 1000.0f))
				continue;
			GameObject *obj = mObjects->getGameObject(mLevelMap[posOffset + i]);
			if(obj->getType() != GOTYPE_WALL)
				continue;
			((GOWall *)obj)->setBlood(2);
		}
	}
}

bool GameControl::randomTrue(float probability)
{
	float randomnumber = (rand() / (double)RAND_MAX);
	if(randomnumber < probability or probability >= 1.0)
		return true;
	else return false;
}

void GameControl::unregisterFireSounds()
{
	for(EnemiesList::iterator i = mEnemies.begin(); i != mEnemies.end(); i++)
		((Enemy *)*i)->unregisterSound();
}

void GameControl::randomRoar(Enemy *enemy)
{
	if(not randomTrue(0.008))
		return;
	Vector3Dg pos = enemy->getPosition();
	int32_t volume = calculateVolume(pos, VOLDIST_TYPE3);
	if(volume == 0)
		return;

	int num = (int)((rand() / (double)RAND_MAX) * 3.0) % 3;
	mSDL->playSound(mEnemySounds[ESND_ROAR1 + num], 4, volume);
}

void GameControl::hitRoar(Enemy *enemy)
{
	if(not randomTrue(0.5))
		return;

	Vector3Dg pos = enemy->getPosition();
	mSDL->playSound(mEnemySounds[ESND_ROAR2], 4, calculateVolume(pos, VOLDIST_TYPE3));
}

void GameControl::checkEnemyCollisions(Enemy *enemy)
{
	Vector3Dg pos = enemy->getPosition();
	int8_t enemyPosX = (int8_t)((uint32_t)pos.x/100);
	int8_t enemyPosZ = (int8_t)((uint32_t)-pos.z/100);
	uint32_t posOffset = enemyPosX * 8 + enemyPosZ * 512;

	//collisions with walls, door and edges
	bool edges[] = {false, false, false, false, false};
	//get info about existing walls in the square
	uint16_t walls = mLevelMap[posOffset + 4];
	for(uint32_t i = 0; i < 4; ++i) {
		if(walls & (1 << i)) {
			Mesh3DgUID meshUID = mLevelMap[posOffset + i];
			edges[i] = edges[i + 1] = true;
			GameObject *obj = mObjects->getGameObject(meshUID);
			if(obj->getPlayerCollisionType() != PCTYPE_BUMP)
				continue;
			switch(obj->getType()) {
				case GOTYPE_WALL:
					enemyWallCollision(enemy, obj->getMesh(), i, mEnemyRay);
					break;
				case GOTYPE_DOOR: {
					//only if not open
					if(isDoorOpen((GODoor *)obj))
						enemyWallCollision(enemy, obj->getMesh(), i, mEnemyRay);
					break;
				}
				default:
					break;
			}
		} else {
			//check if there is a wall on the neighbouring field
			uint16_t newPosOffset;
			switch(i) {
				case 0:
					newPosOffset = getRelativePosOffset(enemyPosX, enemyPosZ, 0, 1);
					break;
				case 1:
					newPosOffset = getRelativePosOffset(enemyPosX, enemyPosZ, 1, 0);
					break;
				case 2:
					newPosOffset = getRelativePosOffset(enemyPosX, enemyPosZ, 0, -1);
					break;
				case 3:
					newPosOffset = getRelativePosOffset(enemyPosX, enemyPosZ, -1, 0);
					break;
			}
			uint16_t newWalls = mLevelMap[newPosOffset + 4];
			//prevent the enemy from going through the wall even from the back side
			uint32_t j = (i + 2) % 4;
			if(newWalls & (1 << j)) {
				Mesh3DgUID meshUID = mLevelMap[newPosOffset + j];
				GameObject *obj = mObjects->getGameObject(meshUID);
				if(obj->getPlayerCollisionType() != PCTYPE_BUMP)
					continue;
				enemyBackWallCollision(enemy, obj->getMesh(), j, mEnemyRay);
			}
		}
	}
	edges[0] = edges[0] or edges[4];
	searchForEdges(edges, enemyPosX, enemyPosZ);
	//check edges collisions
	enemyEdgesCollision(edges, enemy, enemyPosX, enemyPosZ, mEnemyRay);

	//collision with player
	if(enemyObjectCollision(mCameraPosition, enemy, mCamRay+mEnemyRay)) {
		//if the enemy is under fire then the player must get burnt
		if(enemy->isBurning() and mPlayerBurnDiff >= PLAYER_BURN_DELAY) {
			mPlayerBurnDiff = 0;
			mOGL->osd.addText(mLocInterface->getText(LOC_GAME_HOT));
			mSDL->playSound(mCollisionSounds[CSND_TOUCH_FLAMES], 1);
			excite();
			if(mHighDificulty and not mImmortal)
				mLostEnergy++;
		}
	}

	//collision with other enemies
	enemyCollision(enemy);
	//don't check other collisions if enemy is outside the level map
	if(pos.x > 6400.0f or pos.x < 0.0f or pos.z < -6400.0f or pos.z > 0.0f)
		return;

	//collision with sprites
	if(mLevelMap[posOffset + 4] & 16) {
		Mesh3DgUID meshUID = mLevelMap[posOffset + 5];
		Mesh3Dg *mesh = mObjects->meshes[meshUID];
		if(((GameObject *)mesh->getChild())->getPlayerCollisionType() == PCTYPE_STOP) {
			pos = mesh->geomCenter();
			enemyObjectCollision(pos, enemy, mEnemyRay);
		}
	}
}

bool GameControl::canShoot(float range, Vector3Dg position, Vector3Dg direction)
{
	//calculate the end point of the ammo's way
	Vector3Dg end = position + (direction * range);
	//random shift in direction
	float shift = 25.0f - (rand() / (double)RAND_MAX) * 50.0f;
	end.x += direction.z * shift;
	shift = 25.0f - (rand() / (double)RAND_MAX) * 50.0f;
	end.z += direction.x * shift;
	//calculate the direction once again 'cause we gonna need it
	direction = end - position;
	direction.normalize();
	//calculate the boundaries of potentially affected area
	int8_t beginx = (int8_t)(position.x / 100), beginz = (int8_t)(-position.z / 100);
	int8_t endx = (int8_t)(end.x / 100), endz = (int8_t)(-end.z / 100);
	//make sure that begining values are lesser than ending ones
	if(endx < 0)
		endx = 0;
	if(endz < 0)
		endz = 0;
	if(beginx > endx) {
		int8_t tmp = beginx;
		beginx = endx;
		endx = tmp;
	}
	if(beginz > endz) {
		int8_t tmp = beginz;
		beginz = endz;
		endz = tmp;
	}
	//make sure we are not out of bounds
	if(beginx < 0)
		beginx = 0;
	if(beginz < 0)
		beginz = 0;
	if(endx > 63)
		endx = 63;
	if(endz > 63)
		endz = 63;

	//which walls to check
	uint32_t count = 0, check[2];
	//north
	if(direction.z < 0.0f) {
		check[count] = 0;
		count++;
	}
	//south
	else if(direction.z > 0.0f) {
		check[count] = 2;
		count++;
	}
	//east
	if(direction.x > 0.0f) {
		check[count] = 1;
		count++;
	}
	//west
	else if(direction.x < 0.0f) {
		check[count] = 3;
		count++;
	}

	//distaince of the closest object
	float distaince = range;
	GameObject *closestobject = 0;

	//check all fields for collisions
	for(int8_t c = beginx; c <= endx; c++) {
		for(int8_t r = beginz; r <= endz; r++) {
			//position's offset in the map buffer
			uint32_t offset = c * 8 + r * 512;

			//get info about existing walls in the square
			uint16_t walls = mLevelMap[offset + 4];
			for(uint32_t i = 0; i < count; ++i) {
				if(not (walls & (1 << check[i])))
					continue;
				Mesh3DgUID meshnum = mLevelMap[offset + check[i]];
				GameObject *obj = mObjects->getGameObject(meshnum);
				if(obj->getAmmoCollisionType() == ACTYPE_NONE)
					continue;
				float dist = 0.0f;
				switch(obj->getType()) {
					case GOTYPE_WALL: {
						if(((GOWall *)obj)->getTransparentForEnemy())
							break;
						dist = wallCollisionAmmo(position, end, c, r, check[i], obj);
						break;
					}
					case GOTYPE_DOOR: {
						//only if not open
						if(isDoorOpen((GODoor *)obj))
							dist = wallCollisionAmmo(position, end, c, r, check[i], obj);
						break;
					}
					default:
						break;
				}
				if(dist != 0.0f and dist < distaince) {
					distaince = dist;
					closestobject = obj;
				}
			}

			//sprites
			Mesh3DgUID meshnum = mLevelMap[offset + 5];
			if(mLevelMap[offset + 4] & 16) {
				GameObject *obj = mObjects->getGameObject(meshnum);
				if(obj->getAmmoCollisionType() == ACTYPE_UNBREAKABLE) {
					float dist = spriteCollisionAmmo(position, end, c, r, range);
					if(dist != 0.0f and dist < distaince) {
						distaince = dist;
						closestobject = obj;
					}
				}
			}
		}
	}

	if(closestobject == 0)
		return true;
	else return false;
}
