/*
===========================================================================
Copyright (C) 1997-2001 Id Software, Inc.
Copyright (C) 2004-2020 Serge Borodulin aka Berserker (tm)
                         <http://berserker.quakegate.ru>

This file is part of Berserker@Quake2 source code.

Berserker@Quake2 source code 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 2 of the License,
or (at your option) any later version.

Berserker@Quake2 source code 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 Berserker@Quake2 source code; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
===========================================================================
*/

#pragma warning( disable: 4305 4309 4244 4018 4551 4116 4146 4800 4996)

#pragma comment (lib, "winmm.lib")
#pragma comment (lib, "libjpeg.lib")
#pragma comment (lib, "libpng10.lib")
#pragma comment (lib, "zlib.lib")
#pragma comment (lib, "opengl32.lib")
#pragma comment (lib, "wsock32.lib")

#include "defs.h"
#include "data.h"
#include "resource.h"













void SinCos( float radians, float *sine, float *cosine )
{
	_asm
	{
		fld	dword ptr [radians]
		fsincos

		mov edx, dword ptr [cosine]
		mov eax, dword ptr [sine]

		fstp dword ptr [edx]
		fstp dword ptr [eax]
	}
}

void Cvar_Concat_f()
{
	int		i, c;
	char	*cvar;

	c = Cmd_Argc();
	if (c < 3)
	{
		Com_Printf ("^3USAGE: concat <inputCvar> <value1> <value2> ...\n");
		return;
	}

	cvar = Cmd_Argv(1);

	for (i=2; i<c; i++)
		Cvar_Set(cvar, va("%s%s", Cvar_VariableString(cvar), Cmd_Argv(i)));
}

void *Hunk_Begin (int maxsize, char *name)
{
	// reserve a huge chunk of memory, but don't commit any yet
	cursize = 0;
	hunkmaxsize = maxsize;
	int l = strlen(name);
	if (l >= MAX_OSPATH)
		l = MAX_OSPATH - 1;
	Memcpy(hunk_name, name, l);
	hunk_name[l] = 0;
	membase = (byte*) calloc (maxsize, 1);
	if (!membase)
		Sys_Error ("Hunk_Begin: failed on reserving of %i bytes for %s", maxsize, hunk_name);
	return (void *)membase;
}

void *Hunk_Alloc (int size, bool crash)
{
	// round to cacheline
	size = (size+31)&~31;
	cursize += size;
	if (cursize > hunkmaxsize)
	{
		if (crash)
			Sys_Error ("Hunk_Alloc overflow on %s:\ncursize = %i hunkmaxsize = %i\nIf crashed during model or sprite loading,\ntry to clear cache and increase value of 'hunk_*' cvar...\nGamedir: %s", hunk_name, cursize, hunkmaxsize, FS_Gamedir());
		else
			return NULL;
	}
	return (void *)(membase+cursize-size);
}


void Hunk_Free (void *base, int size)
{
	if ( base )
		free (base);
	hunkmaxsize = 0;
	if (size)
	{
		hunk_total_size -= size;
		hunkcount--;
	}
}

// free the remaining unused virtual memory
byte *needFree;
int Hunk_End ()
{
	byte *newbase;
	hunk_total_size += cursize;	// учтём размер для статистики
	hunkcount++;
	needFree = NULL;
	if (hunkmaxsize > cursize)
	{
		newbase = (byte*) realloc (membase, cursize);
		if (newbase != membase)
		{	// случилась редкая хуйня: realloc при уменьшении блока памяти всё же переместил данные, сука!
			Com_DPrintf("Hunk_End: realloc() moved memory block, %s will be reload!\n", hunk_name);
			needFree = newbase;
			hunk_total_size -= cursize;	// не будем учитывать, фейл
			hunkcount--;
		}
	}
	hunkmaxsize = 0;
	return cursize;
}



////// Cinematic support ////////////////////////////////////////////////
unsigned r_rawpalette[256];
void R_SetPalette (char *palette)
{
	int		i;

	if (!cl.need_palette)
		return;

	byte *rp = ( byte * ) r_rawpalette;

	if ( palette )
	{
		for ( i = 0; i < 256; i++ )
		{
			rp[i*4+0] = palette[i*3+2];		// BGRA
			rp[i*4+1] = palette[i*3+1];
			rp[i*4+2] = palette[i*3+0];
			rp[i*4+3] = 0xff;
		}
	}
	else
	{
		for ( i = 0; i < 256; i++ )
		{
			rp[i*4+0] = d_8to24table[i*3+2];		// BGRA
			rp[i*4+1] = d_8to24table[i*3+1];
			rp[i*4+2] = d_8to24table[i*3+0];
			rp[i*4+3] = 0xff;
		}
	}

	glClearColor (0, 0, 0, 0);
	glClear (GL_COLOR_BUFFER_BIT);
	glClearColor (0.35, 0.35, 0.35, 0.5);
}

void SCR_StopCinematic ()
{
	cl.cinematictime = 0;	// done
	if (cin.pic)
	{
		Z_Free (cin.pic);
		cin.pic = NULL;
	}
	if (cin.pic_pending)
	{
		Z_Free (cin.pic_pending);
		cin.pic_pending = NULL;
	}
	if (cl.cinematicpalette_active)
	{
		R_SetPalette(NULL);
		cl.cinematicpalette_active = false;
	}
	if (cl.cinematic_file)
	{
		fclose (cl.cinematic_file);
		cl.cinematic_file = NULL;
	}
	if (cin.hnodes1)
	{
		Z_Free (cin.hnodes1);
		cin.hnodes1 = NULL;
	}

	// switch back down to 11 khz sound if necessary
///	if (cin.restart_sound)
///	{
///		cin.restart_sound = false;
///		CL_Snd_Restart_f ();
///	}
}

void Draw_StretchRawPaletted (int sw, int sh, int w, int h, int cols, int rows, byte *data)
{
	unsigned	image32[256*256];
	int			i, j, trows;
	byte		*source;
	int			frac, fracstep;
	float		hscale;
	int			row;
	float		t;

	if (sw || sh)
	{	/// Berserker: clear screen for borders flickering prevent
		GL_MBind2D (GL_TEXTURE0, r_dark->texnum);

		glBegin (GL_QUADS);
		glTexCoord2f (0, 0);
		glVertex2f (0, 0);
		glTexCoord2f (1, 0);
		glVertex2f (viddef.width, 0);
		glTexCoord2f (1, 1);
		glVertex2f (viddef.width, viddef.height);
		glTexCoord2f (0, 1);
		glVertex2f (0, viddef.height);
		glEnd ();
	}

	GL_MBind2D (GL_TEXTURE0, 0);

	/// Berserker: fixed bug - crap cin edges
//	if (rows<=256)
//	{
//		hscale = 1;
//		trows = rows;
//	}
//	else
	{
		hscale = rows/256.0;
		trows = 256;
	}
	t = rows*hscale / 256;

	unsigned *dest;
	for (i=0 ; i<trows ; i++)
	{
		row = (int)(i*hscale);
		if (row > rows)
			break;
		source = data + cols*row;
		dest = &image32[i*256];
		fracstep = cols*0x10000/256;
		frac = fracstep >> 1;
		for (j=0 ; j<256 ; j++)
		{
			dest[j] = r_rawpalette[source[frac>>16]];
			frac += fracstep;
		}
	}

	glTexImage2D (GL_TEXTURE_2D, 0, gl_tex_solid_format, 256, 256, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, image32);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gl_edge_type);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gl_edge_type);

	int x0 = sw;
	int y0 = sh;
	int x1 = sw+w;
	int y1 = sh+h;
///	Adjust320to640(&x0, &y0, &x1, &y1);

	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (x0, y0);
	glTexCoord2f (1, 0);
	glVertex2f (x1, y0);
	glTexCoord2f (1, 1);
	glVertex2f (x1, y1);
	glTexCoord2f (0, 1);
	glVertex2f (x0, y1);
	glEnd ();
}

void Draw_StretchRaw (int sw, int sh, int w, int h, int cols, int rows, byte *data)
{
	if (sw || sh)
	{	/// Berserker: clear screen for borders flickering prevent
		GL_MBind2D (GL_TEXTURE0, r_dark->texnum);

		glBegin (GL_QUADS);
		glTexCoord2f (0, 0);
		glVertex2f (0, 0);
		glTexCoord2f (1, 0);
		glVertex2f (viddef.width, 0);
		glTexCoord2f (1, 1);
		glVertex2f (viddef.width, viddef.height);
		glTexCoord2f (0, 1);
		glVertex2f (0, viddef.height);
		glEnd ();
	}

	GL_MBind2D (GL_TEXTURE0, 0);

	glTexImage2D (GL_TEXTURE_2D, 0, gl_tex_solid_format, cols, rows, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gl_edge_type);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gl_edge_type);

	int x0 = sw;
	int y0 = sh;
	int x1 = sw+w;
	int y1 = sh+h;
///	Adjust320to640(&x0, &y0, &x1, &y1);

	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (x0, y0);
	glTexCoord2f (1, 0);
	glVertex2f (x1, y0);
	glTexCoord2f (1, 1);
	glVertex2f (x1, y1);
	glTexCoord2f (0, 1);
	glVertex2f (x0, y1);
	glEnd ();
}

/*
==================
SCR_DrawCinematic

///Returns true if a cinematic is active, meaning the view rendering
///should be skipped
==================
*/
void SCR_DrawCinematic ()
{
	int	w, h, sw, sh;

	if (cl.cinematictime <= 0)
		return;	/// false;

	if (cls.key_dest == key_menu)
	{	// blank screen and pause if menu is up
		R_SetPalette(NULL);
		cl.cinematicpalette_active = false;
		return;	/// true;
	}

	if (!cl.cinematicpalette_active)
	{
		R_SetPalette(cl.cinematicpalette);
		cl.cinematicpalette_active = true;
	}

	if (!cin.pic)
		return;	/// true;

	/// Berserker: предотвращение искажения видео на широких мониторах.
	if ((float)viddef.width / (float)viddef.height >= 1.3333333333F)
	{
		h = viddef.height;
		sh = 0;
		w = (float)h * 1.3333333333F;
		sw = (viddef.width - w) / 2;
	}
	else
	{
		w = viddef.width;
		sw = 0;
		h = (float)w / 1.3333333333F;
		sh = (viddef.height - h) / 2;
	}

	if (cl.need_palette)
		Draw_StretchRawPaletted (sw, sh, w, h, cin.width, cin.height, cin.pic);
	else
		Draw_StretchRaw (sw, sh, w, h, cin.width, cin.height, cin.pic);

///	return true;
}


int	SmallestNode1 (int numhnodes)
{
	int		i;
	int		best, bestnode;

	best = 99999999;
	bestnode = -1;
	for (i=0 ; i<numhnodes ; i++)
	{
		if (cin.h_used[i])
			continue;
		if (!cin.h_count[i])
			continue;
		if (cin.h_count[i] < best)
		{
			best = cin.h_count[i];
			bestnode = i;
		}
	}

	if (bestnode == -1)
		return -1;

	cin.h_used[bestnode] = true;
	return bestnode;
}

/*
==================
Huff1TableInit

Reads the 64k counts table and initializes the node trees
==================
*/
void Huff1TableInit ()
{
	int		prev;
	int		j;
	int		*node, *nodebase;
	byte	counts[256];
	int		numhnodes;

	cin.hnodes1 = (int*)Z_Malloc(256*256*2*4, true);
///	Memset (cin.hnodes1, 0, 256*256*2*4);

	for (prev=0 ; prev<256 ; prev++)
	{
		Memset (cin.h_count,0,sizeof(cin.h_count));
		Memset (cin.h_used,0,sizeof(cin.h_used));

		// read a row of counts
		FS_Read (counts, sizeof(counts), cl.cinematic_file);
		for (j=0 ; j<256 ; j++)
			cin.h_count[j] = counts[j];

		// build the nodes
		numhnodes = 256;
		nodebase = cin.hnodes1 + prev*256*2;

		while (numhnodes != 511)
		{
			node = nodebase + (numhnodes-256)*2;

			// pick two lowest counts
			node[0] = SmallestNode1 (numhnodes);
			if (node[0] == -1)
				break;	// no more

			node[1] = SmallestNode1 (numhnodes);
			if (node[1] == -1)
				break;

			cin.h_count[numhnodes] = cin.h_count[node[0]] + cin.h_count[node[1]];
			numhnodes++;
		}

		cin.numhnodes1[prev] = numhnodes-1;
	}
}

cblock_t Huff1Decompress (cblock_t in)
{
	byte		*input;
	byte		*out_p;
	int			nodenum;
	int			count;
	cblock_t	out;
	int			inbyte;
	int			*hnodes, *hnodesbase;

	// get decompressed count
	count = in.data[0] + (in.data[1]<<8) + (in.data[2]<<16) + (in.data[3]<<24);
	input = in.data + 4;
	out_p = out.data = (byte*)Z_Malloc(count, true);

	// read bits
	hnodesbase = cin.hnodes1 - 256*2;	// nodes 0-255 aren't stored

	hnodes = hnodesbase;
	nodenum = cin.numhnodes1[0];
	while (count)
	{
		inbyte = *input++;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin.numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin.numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin.numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin.numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin.numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin.numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin.numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
		//-----------
		if (nodenum < 256)
		{
			hnodes = hnodesbase + (nodenum<<9);
			*out_p++ = nodenum;
			if (!--count)
				break;
			nodenum = cin.numhnodes1[nodenum];
		}
		nodenum = hnodes[nodenum*2 + (inbyte&1)];
		inbyte >>=1;
	}

	if (input - in.data != in.count && input - in.data != in.count+1)
		Com_Printf ("Decompression overread by %i", (input - in.data) - in.count);

	out.count = out_p - out.data;
	return out;
}



/*
===============================================================================
Music Streaming
===============================================================================
*/
void S_Streaming_Stop(void)
{
	if (streaming.enabled)
	{
		// Stop the Source and clear the Queue
		alSourceStop(source_name[CH_STREAMING]);
		alSourcei(source_name[CH_STREAMING], AL_BUFFER, 0);

		streaming.enabled = false;
	}
}


bool S_Streaming_Start(int num_bits, int num_channels, ALsizei rate, float volume)
{

	if (!s_initsound->value)
		return false;

	if (streaming.enabled)
	{
		Com_Printf("^3S_Streaming_Start: interrupting active stream\n");
		S_Streaming_Stop();
	}

	if (num_bits == 8 && num_channels == 1)
		streaming.sound_format = AL_FORMAT_MONO8;
	else if (num_bits == 8 && num_channels == 2)
		streaming.sound_format = AL_FORMAT_STEREO8;
	else if (num_bits == 16 && num_channels == 1)
		streaming.sound_format = AL_FORMAT_MONO16;
	else if (num_bits == 16 && num_channels == 2)
		streaming.sound_format = AL_FORMAT_STEREO16;
	else
	{
		Com_Printf("^1S_StreamingStart: unsupported format (%d bits and %d channels)\n", num_bits, num_channels);
		return false;
	}

	alSourcef(source_name[CH_STREAMING], AL_GAIN, volume);
	streaming.sound_rate = rate;
	streaming.bFirst = 0;
	streaming.bNumAvail = NUM_STRBUF;
	streaming.enabled = true;

	return true;
}

int S_Streaming_NumFreeBufs(void)
{
	return streaming.bNumAvail;
}

static __inline void sq_add(ALuint x)
{
	int tail = (streaming.bFirst + streaming.bNumAvail) % NUM_STRBUF;

	// assuming non-full queue
///	assert(streaming.bNumAvail < NUM_STRBUF);

	streaming.buffers[tail] = x;
	streaming.bNumAvail++;
}

static void S_Streaming_RecycleBuffers(void)
{
	// Reclaim buffers that have been played, and keep them in a queue
	if (streaming.bNumAvail < NUM_STRBUF)
	{
		int recycled, i;
		alGetSourcei(source_name[CH_STREAMING], AL_BUFFERS_PROCESSED, &recycled);

		for (i = 0; i < recycled; i++)
		{
			ALuint buf;
			alSourceUnqueueBuffers(source_name[CH_STREAMING], 1, &buf);
			sq_add(buf);
		}
	}


}

static __inline ALuint sq_remove(void)
{
	ALuint r;

	// assuming non-empty queue
///	assert(streaming.bNumAvail > 0);

	r = streaming.buffers[streaming.bFirst];
	streaming.bFirst = (streaming.bFirst + 1) % NUM_STRBUF;
	streaming.bNumAvail--;

	return r;
}


int S_Streaming_Add(const byte *buffer, int num_bytes)
{
	// TODO: add comments

	ALint iState;
	int readacc = 0;

	if (!streaming.enabled)
	{
		Com_DPrintf("S_Streaming_Add: called with %d bytes when disabled\n", num_bytes);
		return 0;
	}

	if (num_bytes == 0)
	{
		Com_DPrintf("S_Streaming_Add: called with empty buffer\n");
		return 0;
	}

	// If there is data to play and free buffers,
	// copy the data and enqueue them.
	while (num_bytes > 0 && streaming.bNumAvail > 0)
	{
		int readcur = min(MAX_STRBUF_SIZE + MUSIC_BUFFER_READ_SIZE, num_bytes);
		const ALuint buf = sq_remove();

		alBufferData(buf, streaming.sound_format, buffer + readacc, readcur, streaming.sound_rate);
		alSourceQueueBuffers(source_name[CH_STREAMING], 1, &buf);

		num_bytes -= readcur;
		readacc += readcur;
	}

	// Check if we have stopped or starved and should restart
	// (the latter includes the case right afer initialization)
	alGetSourcei(source_name[CH_STREAMING], AL_SOURCE_STATE, &iState);
	if (iState != AL_PLAYING)
	{
		ALint iQueuedBuffers;
		alGetSourcei(source_name[CH_STREAMING], AL_BUFFERS_QUEUED, &iQueuedBuffers);
		if (iQueuedBuffers > 0)
		{
			// If we are not playing but there are still enqueued buffers,
			// restart the audio because it starved of data to play
			alSourcePlay(source_name[CH_STREAMING]);
			Com_DPrintf("S_Streaming_Add: restarting stream, starved\n");
		}
		else
		{
			// Finished playing
			Com_DPrintf("S_Streaming_Add: no more buffers to play, stopping\n");
			S_Streaming_Stop();
		}
	}

	if (readacc == 0)
		Com_DPrintf("S_Streaming_Add: added 0 samples to queue but %d given\n", num_bytes);

	return readacc;
}





byte *SCR_ReadNextFrame ()
{
	int		r;
	int		command;
	byte	samples[22050/14*4 + 4096 * 4];
	byte	compressed[0x20000];
	int		size;
	byte	*pic;
	cblock_t	in, huf1;
	int		start, end, count;

	// read the next frame
	r = fread (&command, 4, 1, cl.cinematic_file);
	if (r == 0)		// we'll give it one more chance
		r = fread (&command, 4, 1, cl.cinematic_file);

	if (r != 1)
		return NULL;
	command = LittleLong(command);
	if (command == 2)
		return NULL;	// last frame marker

	if (command == 1)
	{	// read palette
		FS_Read (cl.cinematicpalette, sizeof(cl.cinematicpalette), cl.cinematic_file);
		cl.cinematicpalette_active=0;	// dubious....  exposes an edge case
	}

	// decompress the next frame
	FS_Read (&size, 4, cl.cinematic_file);
	size = LittleLong(size);
	if (size > sizeof(compressed) || size < 1)
		Com_Error (ERR_DROP, "Bad compressed frame size");
	FS_Read (compressed, size, cl.cinematic_file);

	// read sound
	start = cl.cinematicframe*cin.s_rate/14;
	end = (cl.cinematicframe+1)*cin.s_rate/14;
	count = end - start;

	if (cl.cinematicframe == 0)
	{
		const int pattern = (cin.s_width == 2) ? 0 : 0x80;
		const int num = 4096 * cin.s_width * cin.s_channels;
		Memset(samples, pattern, num);
		S_Streaming_Add(samples, num);
	}

	FS_Read (samples, count*cin.s_width*cin.s_channels, cl.cinematic_file);
	// FIXME: convert to little endian if cin.s_width == 2, as in Yamagi Q2
	S_Streaming_Add(samples, count * cin.s_width * cin.s_channels);

	in.data = compressed;
	in.count = size;

	huf1 = Huff1Decompress (in);

	pic = huf1.data;

	cl.cinematicframe++;

	return pic;
}

void RGBA2BGRA(unsigned width, unsigned height, byte *pic)
{	// Berserker: сконвертируем в BGRA
	int		i, j;
	byte	r, b;
	byte	*data = pic;
	for (i=0; i<height; i++)
	{
		for (j=0; j<width; j++, data += 4)
		{
			r = *data;
			b = *(data+2);
			*data = b;
			*(data+2) = r;
		}
	}
}

void SCR_PlayCinematic (char *arg)
{
	int		width, height;
	byte	*palette;
	char	name[MAX_OSPATH], *dot;
///	int		old_khz;
	cl.need_palette = false;

	// make sure CD isn't playing music
	savedTrackName[0] = 0;
	Music_Stop();
//	CDAudio_Stop();
///	S_StopBackgroundTrack();
	// make sure all the audio sources is free and silent
	S_StopAllSounds();

	cl.cinematicframe = 0;
	palette = NULL;
	dot = strstr (arg, ".");
	int format;
	palette = NULL;
	if (dot && (!Q_strcasecmp (dot, ".pcx") || !Q_strcasecmp (dot, ".tga") || !Q_strcasecmp (dot, ".png") || !Q_strcasecmp (dot, ".jpg") || !Q_strcasecmp (dot, ".dds")))
	{	// static pcx image
		Com_sprintf (name, sizeof(name), "pics/%s", arg);
		bool rep = false;
again:	if (!Q_strcasecmp (dot, ".pcx"))
		{
			LoadPCX (name, &cin.pic, &palette, &cin.width, &cin.height);
			if (!palette)
				palette = &d_8to24table[0];
			format = 8;
		}
		else if (!Q_strcasecmp (dot, ".tga"))
		{
			LoadTGA (name, &cin.pic, &cin.width, &cin.height);
			format = 32;
		}
		else if (!Q_strcasecmp (dot, ".png"))
		{
			LoadPNG (name, &cin.pic, &cin.width, &cin.height, &format);
			if (cin.pic && format == 32)
				RGBA2BGRA(cin.width, cin.height, cin.pic);		// RGBA -> BGRA
		}
		else if (!Q_strcasecmp (dot, ".jpg"))
		{
			LoadJPG (name, &cin.pic, &cin.width, &cin.height);
			format = 24;
		}
		else if (!Q_strcasecmp (dot, ".dds"))
		{
			LoadDDS (name, &cin.pic, &cin.width, &cin.height);
			format = 32;
		}
		else
			cin.pic = NULL;

		if (!cin.pic)
		{	// try to view default pic
			if (rep)	// невероятный случай, но пусть будет
				Com_Error(ERR_DROP, "SCR_PlayCinematic: image not found");
			rep = true;
			dot = ".tga";
			Com_sprintf (name, sizeof(name), "pics/conback.tga");
			goto again;
		}

		int	i, scaled_width, scaled_height;
		for (scaled_width = 1 ; scaled_width < cin.width ; scaled_width<<=1);
		for (scaled_height = 1 ; scaled_height < cin.height ; scaled_height<<=1);
		if ((scaled_width > MAX_TEXTURE_SIZE) || (scaled_height > MAX_TEXTURE_SIZE))
			Com_Error (ERR_DROP, "SCR_PlayCinematic: image too large");
		bool remem = false;
		if (scaled_width != cin.width || scaled_height != cin.height)
		{	/// требуется ресэмпл
			remem = true;
			if (format==8)
			{	// требуется преобразовать из 8 бит в 32 бита.
				byte *in = cin.pic;
				unsigned *out = scaled;
				for (i=0; i<cin.width*cin.height; i++)
				{
					int p = in[i] * 3;
					// copy rgb components
					((byte *)&out[i])[0] = ((byte *)&palette[p])[2];	// BGRA
					((byte *)&out[i])[1] = ((byte *)&palette[p])[1];
					((byte *)&out[i])[2] = ((byte *)&palette[p])[0];
					((byte *)&out[i])[3] = 255;
				}
			}
			else if (format==24)
			{	// требуется преобразовать из 24 бит в 32 бита.
				byte *in = cin.pic;
				unsigned *out = scaled;
				for (i=0; i<cin.width*cin.height; i++)
				{
					*out = (255<<24)+(in[0]<<16)+(in[1]<<8)+in[2];		// BGRA
					in+=3;
					out++;
				}
			}
			else	// 32
				Memcpy (scaled, cin.pic, cin.width*cin.height*4);

			// ресэмплируем 32-битную текстуру
			GL_ResampleTexture (scaled, cin.width, cin.height, trans, scaled_width, scaled_height, false);
			cin.width = scaled_width;
			cin.height = scaled_height;
		}
		else
		{
			if (format==8)
			{	// требуется преобразовать из 8 бит в 32 бита.
				byte *in = cin.pic;
				unsigned *out = trans;
				for (i=0; i<cin.width*cin.height; i++)
				{
					int p = in[i] * 3;
					// copy rgb components to bgr
					((byte *)&out[i])[0] = ((byte *)&palette[p])[2];	// BGRA
					((byte *)&out[i])[1] = ((byte *)&palette[p])[1];
					((byte *)&out[i])[2] = ((byte *)&palette[p])[0];
					((byte *)&out[i])[3] = 255;
				}
				remem = true;
			}
			else if (format==24)
			{	// требуется преобразовать из 24 бит в 32 бита.
				byte *in = cin.pic;
				unsigned *out = trans;
				for (i=0; i<cin.width*cin.height; i++)
				{
					*out = (255<<24)+(in[0]<<16)+(in[1]<<8)+in[2];		// BGRA
					in+=3;
					out++;
				}
				remem = true;
			}
		}

		if (remem)
		{
			Z_Free(cin.pic);
			cin.pic = (byte*)Z_Malloc(cin.width*cin.height*4, true);
			Memcpy (cin.pic, trans, cin.width*cin.height*4);
		}

		cl.cinematicframe = -1;
		cl.cinematictime = 1;
		SCR_EndLoadingPlaque ();
		cls.state = ca_active;
		if (!cin.pic)
		{
			Com_Printf ("^1SCR_PlayCinematic: %s not found.\n", name);
			cl.cinematictime = 0;
		}
		else
		{
			if (palette)
			{
				Memcpy (cl.cinematicpalette, palette, sizeof(cl.cinematicpalette));
				if (palette != &d_8to24table[0])
					Z_Free (palette);
			}
		}
		return;
	}

	Com_sprintf (name, sizeof(name), "video/%s", arg);
	FS_FOpenFile (name, &cl.cinematic_file, false, -1);
	if (!cl.cinematic_file)
	{
//		Com_Error (ERR_DROP, "Cinematic %s not found.", name);
		SCR_FinishCinematic ();
		cl.cinematictime = 0;	// done
		return;
	}

	SCR_EndLoadingPlaque ();

	cls.state = ca_active;

	FS_Read (&width, 4, cl.cinematic_file);
	FS_Read (&height, 4, cl.cinematic_file);
	cin.width = LittleLong(width);
	cin.height = LittleLong(height);

	FS_Read (&cin.s_rate, 4, cl.cinematic_file);
	cin.s_rate = LittleLong(cin.s_rate);
	FS_Read (&cin.s_width, 4, cl.cinematic_file);
	cin.s_width = LittleLong(cin.s_width);
	FS_Read (&cin.s_channels, 4, cl.cinematic_file);
	cin.s_channels = LittleLong(cin.s_channels);

	S_Streaming_Start(cin.s_width * 8, cin.s_channels, cin.s_rate, s_musicVolume->value);		/// was 1.0

	Huff1TableInit ();

	cl.need_palette = true;		// cin always need palette

	cl.cinematicframe = 0;
	cin.pic = SCR_ReadNextFrame ();
	cl.cinematictime = Sys_Milliseconds ();
}

void SCR_RunCinematic()
{
	int		frame;

	if (cl.cinematictime <= 0)
	{
		SCR_StopCinematic ();
		return;
	}

	if (cl.cinematicframe == -1)
		return;		// static image

	if (cls.key_dest != key_game)
	{	// pause if menu or console is up
		cl.cinematictime = cls.realtime - cl.cinematicframe*1000/14;
		return;
	}

	frame = (cls.realtime - cl.cinematictime)*14.0/1000;
	if (frame <= cl.cinematicframe)
		return;

	if (frame > cl.cinematicframe+1)
	{
		Com_Printf ("^3Dropped frame: %i > %i\n", frame, cl.cinematicframe+1);
		cl.cinematictime = cls.realtime - cl.cinematicframe*1000/14;
	}

	if (cin.pic)
		Z_Free (cin.pic);

	cin.pic = cin.pic_pending;
	cin.pic_pending = NULL;
	cin.pic_pending = SCR_ReadNextFrame ();
	if (!cin.pic_pending)
	{
		SCR_StopCinematic ();
		SCR_FinishCinematic ();
		cl.cinematictime = 1;	// hack to get the black screen behind loading
		SCR_BeginLoadingPlaque ();
		cl.cinematictime = 0;
		return;
	}
}

/*
====================
SCR_FinishCinematic

Called when either the cinematic completes, or it is aborted
====================
*/
void SCR_FinishCinematic ()
{
	// tell the server to advance to the next map / cinematic
	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
	SZ_Print (&cls.netchan.message, va("nextserver %i\n", cl.servercount));
}

///////////////////////////////////////////////////////////////////////////////////////////



////// CD Music support ////////////////////////////////////////////////
static void CDAudio_Eject()
{
	DWORD	dwReturn;

	if (dwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_DOOR_OPEN, (DWORD)NULL))
		Com_DPrintf("MCI_SET_DOOR_OPEN failed (%i)\n", dwReturn);
}

static void CDAudio_CloseDoor()
{
	DWORD	dwReturn;

	if (dwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_DOOR_CLOSED, (DWORD)NULL))
		Com_DPrintf("MCI_SET_DOOR_CLOSED failed (%i)\n", dwReturn);
}

static int CDAudio_GetAudioDiskInfo()
{
	DWORD				dwReturn;
	MCI_STATUS_PARMS	mciStatusParms;

	cdValid = false;

	mciStatusParms.dwItem = MCI_STATUS_READY;
	dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD) (LPVOID) &mciStatusParms);
	if (dwReturn)
	{
		Com_DPrintf("CDAudio: drive ready test - get status failed\n");
		return -1;
	}
	if (!mciStatusParms.dwReturn)
	{
		Com_DPrintf("CDAudio: drive not ready\n");
		return -1;
	}

	mciStatusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
	dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD) (LPVOID) &mciStatusParms);
	if (dwReturn)
	{
		Com_DPrintf("CDAudio: get tracks - status failed\n");
		return -1;
	}
	if (mciStatusParms.dwReturn < 1)
	{
		Com_DPrintf("CDAudio: no music tracks\n");
		return -1;
	}

	cdValid = true;
	maxTrack = mciStatusParms.dwReturn;

	return 0;
}

void CDAudio_Stop()
{
	DWORD	dwReturn;

	if (!enabled)
		return;
	
	if (!playing)
		return;

	if (dwReturn = mciSendCommand(wDeviceID, MCI_STOP, 0, (DWORD)NULL))
		Com_DPrintf("MCI_STOP failed (%i)", dwReturn);

	wasPlaying = false;
	playing = false;
}

void CDAudio_Pause()
{
	DWORD				dwReturn;
	MCI_GENERIC_PARMS	mciGenericParms;

	if (!enabled)
		return;

	if (!playing)
		return;

	mciGenericParms.dwCallback = (DWORD)cl_hwnd;
	if (dwReturn = mciSendCommand(wDeviceID, MCI_PAUSE, 0, (DWORD)(LPVOID) &mciGenericParms))
		Com_DPrintf("MCI_PAUSE failed (%i)", dwReturn);

	wasPlaying = playing;
	playing = false;
}

void CDAudio_Play2(int track, bool looping)
{
	DWORD				dwReturn;
    MCI_PLAY_PARMS		mciPlayParms;
	MCI_STATUS_PARMS	mciStatusParms;

	if (!enabled)
		return;

	if (!cdValid)
	{
		CDAudio_GetAudioDiskInfo();
		if (!cdValid)
			return;
	}

	if (track < 0)
		track = -track;

	track = remap[track];

	if (track < 1 || track > maxTrack)
	{
		CDAudio_Stop();
		return;
	}

	// don't try to play a non-audio track
	mciStatusParms.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
	mciStatusParms.dwTrack = track;
	dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD) (LPVOID) &mciStatusParms);
	if (dwReturn)
	{
		Com_DPrintf("MCI_STATUS failed (%i)\n", dwReturn);
		return;
	}
	if (mciStatusParms.dwReturn != MCI_CDA_TRACK_AUDIO)
	{
		Com_Printf("^1CDAudio: track %i is not audio\n", track);
		return;
	}

	// get the length of the track to be played
	mciStatusParms.dwItem = MCI_STATUS_LENGTH;
	mciStatusParms.dwTrack = track;
	dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD) (LPVOID) &mciStatusParms);
	if (dwReturn)
	{
		Com_DPrintf("MCI_STATUS failed (%i)\n", dwReturn);
		return;
	}

	if (playing)
	{
		if (playTrack == track)
			return;
		CDAudio_Stop();
	}

	mciPlayParms.dwFrom = MCI_MAKE_TMSF(track, 0, 0, 0);
	mciPlayParms.dwTo = (mciStatusParms.dwReturn << 8) | track;
	mciPlayParms.dwCallback = (DWORD)cl_hwnd;
	dwReturn = mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY | MCI_FROM | MCI_TO, (DWORD)(LPVOID) &mciPlayParms);
	if (dwReturn)
	{
		Com_DPrintf("CDAudio: MCI_PLAY failed (%i)\n", dwReturn);
		return;
	}

	playLooping = looping;
	playTrack = track;
	playing = true;

	if ( Cvar_VariableValue( "cd_nocd" ) )
		CDAudio_Pause ();
}

void CDAudio_Play(int track, bool looping)
{
	// set a loop counter so that this track will change to the
	// looptrack later
	loopcounter = 0;
	CDAudio_Play2(track, looping);
}

void CDAudio_Resume()
{
	DWORD			dwReturn;
    MCI_PLAY_PARMS	mciPlayParms;

	if (!enabled)
		return;

	if (!cdValid)
		return;

	if (!wasPlaying)
		return;

	mciPlayParms.dwFrom = MCI_MAKE_TMSF(playTrack, 0, 0, 0);
	mciPlayParms.dwTo = MCI_MAKE_TMSF(playTrack + 1, 0, 0, 0);
	mciPlayParms.dwCallback = (DWORD)cl_hwnd;
	dwReturn = mciSendCommand(wDeviceID, MCI_PLAY, MCI_TO | MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms);
	if (dwReturn)
	{
		Com_DPrintf("CDAudio: MCI_PLAY failed (%i)\n", dwReturn);
		return;
	}
	playing = true;
}

static void CD_f ()
{
	char	*command;
	int		ret;
	int		n;

	if (Cmd_Argc() < 2)
		return;

	command = Cmd_Argv (1);

	if (Q_strcasecmp(command, "on") == 0)
	{
		enabled = true;
		return;
	}

	if (Q_strcasecmp(command, "off") == 0)
	{
		if (playing)
			CDAudio_Stop();
		enabled = false;
		return;
	}

	if (Q_strcasecmp(command, "reset") == 0)
	{
		enabled = true;
		if (playing)
			CDAudio_Stop();
		for (n = 0; n < 100; n++)
			remap[n] = n;
		CDAudio_GetAudioDiskInfo();
		return;
	}

	if (Q_strcasecmp(command, "remap") == 0)
	{
		ret = Cmd_Argc() - 2;
		if (ret <= 0)
		{
			for (n = 1; n < 100; n++)
				if (remap[n] != n)
					Com_Printf("  %u -> %u\n", n, remap[n]);
			return;
		}
		for (n = 1; n <= ret; n++)
			remap[n] = atoi(Cmd_Argv (n+1));
		return;
	}

	if (Q_strcasecmp(command, "close") == 0)
	{
		CDAudio_CloseDoor();
		return;
	}

	if (!cdValid)
	{
		CDAudio_GetAudioDiskInfo();
		if (!cdValid)
		{
			Com_Printf("No CD in player.\n");
			return;
		}
	}

	if (Q_strcasecmp(command, "play") == 0)
	{
		CDAudio_Play(atoi(Cmd_Argv (2)), false);
		return;
	}

	if (Q_strcasecmp(command, "loop") == 0)
	{
		CDAudio_Play(atoi(Cmd_Argv (2)), true);
		return;
	}

	if (Q_strcasecmp(command, "stop") == 0)
	{
		CDAudio_Stop();
		return;
	}

	if (Q_strcasecmp(command, "pause") == 0)
	{
		CDAudio_Pause();
		return;
	}

	if (Q_strcasecmp(command, "resume") == 0)
	{
		CDAudio_Resume();
		return;
	}

	if (Q_strcasecmp(command, "eject") == 0)
	{
		if (playing)
			CDAudio_Stop();
		CDAudio_Eject();
		cdValid = false;
		return;
	}

	if (Q_strcasecmp(command, "info") == 0)
	{
		Com_Printf("%u tracks\n", maxTrack);
		if (playing)
			Com_Printf("Currently %s track %u\n", playLooping ? "looping" : "playing", playTrack);
		else if (wasPlaying)
			Com_Printf("Paused %s track %u\n", playLooping ? "looping" : "playing", playTrack);
		return;
	}
}

LONG CDAudio_MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if (lParam != wDeviceID)
		return 1;

	switch (wParam)
	{
		case MCI_NOTIFY_SUCCESSFUL:
			if (playing)
			{
				playing = false;
				if (playLooping)
				{
					// if the track has played the given number of times,
					// go to the ambient track
					if (++loopcounter >= cd_loopcount->value)
						CDAudio_Play2(cd_looptrack->value, true);
					else
						CDAudio_Play2(playTrack, true);
				}
			}
			break;

		case MCI_NOTIFY_ABORTED:
		case MCI_NOTIFY_SUPERSEDED:
			break;

		case MCI_NOTIFY_FAILURE:
			Com_DPrintf("MCI_NOTIFY_FAILURE\n");
			CDAudio_Stop ();
			cdValid = false;
			break;

		default:
			Com_DPrintf("Unexpected MM_MCINOTIFY type (%i)\n", wParam);
			return 1;
	}

	return 0;
}

/*
 =================
 CDAudio_MixerShutdown (Q2XP)
 =================
*/
static void CDAudio_MixerShutdown ()
{
	DWORD	dwResult;

	if (!cdAudio.hMixer)
		return;

	if (dwResult = mixerClose(cdAudio.hMixer))
		Com_DPrintf("CDAudio: mixerClose() failed (%i)\n", dwResult);

	cdAudio.hMixer = NULL;
}

/*
 =================
 CDAudio_MixerInit (Q2XP)
 =================
*/
static void CDAudio_MixerInit ()
{
	DWORD	dwResult;

	if (!mixerGetNumDevs())
	{
		Com_DPrintf("CDAudio: no mixer devices found\n");
		return;
	}

	// Open the device
	if (dwResult = mixerOpen(&cdAudio.hMixer, 0, 0, 0, MIXER_OBJECTF_MIXER))
	{
		Com_DPrintf("CDAudio: mixerOpen() failed (%i)\n", dwResult);
		return;
	}

	Memset(&cdAudio.mixerLine, 0, sizeof(MIXERLINE));
	cdAudio.mixerLine.cbStruct = sizeof(MIXERLINE);
	cdAudio.mixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;

	if (dwResult = mixerGetLineInfo((HMIXEROBJ)cdAudio.hMixer, &cdAudio.mixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE))
	{
		Com_DPrintf("CDAudio: mixerGetLineInfo() failed (%i)\n", dwResult);
		CDAudio_MixerShutdown();
		return;
	}

	Memset(&cdAudio.mixerLineControl, 0, sizeof(MIXERLINECONTROLS));
	cdAudio.mixerLineControl.cbStruct = sizeof(MIXERLINECONTROLS);
	cdAudio.mixerLineControl.dwLineID = cdAudio.mixerLine.dwLineID;
	cdAudio.mixerLineControl.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
	cdAudio.mixerLineControl.cControls = 1;
	cdAudio.mixerLineControl.cbmxctrl = sizeof(cdAudio.mixerControl);
	cdAudio.mixerLineControl.pamxctrl = &cdAudio.mixerControl;

	if (dwResult = mixerGetLineControls((HMIXEROBJ)cdAudio.hMixer, &cdAudio.mixerLineControl, MIXER_GETLINECONTROLSF_ONEBYTYPE))
	{
		Com_DPrintf("CDAudio: mixerGetLineControls() failed (%i)\n", dwResult);
		CDAudio_MixerShutdown();
		return;
	}
}
/*
 =================
 CDAudio_GetMixerVolume (Q2XP)
 =================
*/
static int CDAudio_GetMixerVolume ()
{
	DWORD							dwResult;
	MIXERCONTROLDETAILS				mxControlDetails;
	MIXERCONTROLDETAILS_UNSIGNED	mxValue[2];
	MIXERCONTROL					*mxControl;
	MIXERLINE						*mxLine;
	int								value;

	if (!cdAudio.hMixer)
		return 0;

	mxLine = &cdAudio.mixerLine;
	mxControl = &cdAudio.mixerControl;

	Memset(&mxControlDetails, 0, sizeof(MIXERCONTROLDETAILS));
	mxControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
	mxControlDetails.dwControlID = mxControl->dwControlID;
	mxControlDetails.cChannels = mxLine->cChannels;
	mxControlDetails.cbDetails = sizeof(mxValue);
	mxControlDetails.paDetails = &mxValue;

	if (dwResult = mixerGetControlDetails((HMIXEROBJ)cdAudio.hMixer, &mxControlDetails, MIXER_GETCONTROLDETAILSF_VALUE))
	{
		Com_DPrintf("CDAudio: mixerGetControlDetails() failed (%i)\n", dwResult);
		return 0;
	}

	value = mxValue[(mxValue[0].dwValue > mxValue[1].dwValue) ? 0 : 1].dwValue;
	return (255 * (value - mxControl->Bounds.dwMinimum)) / (mxControl->Bounds.dwMaximum - mxControl->Bounds.dwMinimum);
}

/*
 =================
 CDAudio_SetMixerVolume (Q2XP)
 =================
*/
static void CDAudio_SetMixerVolume (float volume)
{
	DWORD							dwResult;
	MIXERCONTROLDETAILS				mxControlDetails;
	MIXERCONTROLDETAILS_UNSIGNED	mxValue[2];
	MIXERCONTROL					*mxControl;
	MIXERLINE						*mxLine;

	if (!cdAudio.hMixer)
		return;

	mxLine = &cdAudio.mixerLine;
	mxControl = &cdAudio.mixerControl;

	Memset(&mxControlDetails, 0, sizeof(MIXERCONTROLDETAILS));
	mxControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
	mxControlDetails.dwControlID = mxControl->dwControlID;
	mxControlDetails.cChannels = mxLine->cChannels;
	mxControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);	/// fixed by Barnes
	mxControlDetails.paDetails = &mxValue;

	mxValue[0].dwValue = mxValue[1].dwValue = (volume * (mxControl->Bounds.dwMaximum - mxControl->Bounds.dwMinimum)) + mxControl->Bounds.dwMinimum;
	if (dwResult = mixerSetControlDetails((HMIXEROBJ)cdAudio.hMixer, &mxControlDetails, MIXER_SETCONTROLDETAILSF_VALUE))
		Com_DPrintf("CDAudio: mixerSetControlDetails() failed (%i)\n", dwResult);
}

int CDAudio_Init()
{
	DWORD	dwReturn;
	MCI_OPEN_PARMS	mciOpenParms;
    MCI_SET_PARMS	mciSetParms;
	int				n;

	cd_drive = Cvar_Get ("cd_drive", "", 0);
	cd_drive->help = "override the automatic assigned CD letter.";
	cd_loopcount = Cvar_Get ("cd_loopcount", "4", 0);
	cd_loopcount->help = "if the track has played the %s times, go to the ambient track.";
	cd_looptrack = Cvar_Get ("cd_looptrack", "11", 0);
	cd_looptrack->help = "ambient track number.";
///	cd_volume = Cvar_Get("cd_volume", "1", CVAR_ARCHIVE);		use s_musicVolume !
	if (cd_nocd->value)
		return -1;

	if (cd_drive->string && cd_drive->string[0])
		q2cd_letter[0] = cd_drive->string[0];		// Berserker: override
	Memset(&mciOpenParms, 0 ,sizeof(MCI_OPEN_PARMS));
	unsigned flags;
	if (q2cd_letter[0])
	{
		mciOpenParms.lpstrElementName = q2cd_letter;
		flags = MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT;
		mciOpenParms.lpstrDeviceType = (LPCSTR) MCI_DEVTYPE_CD_AUDIO;
	}
	else
	{
		flags = 0;		// Berserker: auto select
		mciOpenParms.lpstrDeviceType = "cdaudio";
	}
	if (dwReturn = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_SHAREABLE | flags, (DWORD) (LPVOID) &mciOpenParms))
	{
		Com_Printf("CDAudio_Init: MCI_OPEN failed (%i)\n", dwReturn);
		return -1;
	}
	wDeviceID = mciOpenParms.wDeviceID;

    // Set the time format to track/minute/second/frame (TMSF).
	mciSetParms.dwTimeFormat = MCI_FORMAT_TMSF;
	if (dwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD)(LPVOID) &mciSetParms))
    {
		Com_Printf("MCI_SET_TIME_FORMAT failed (%i)\n", dwReturn);
		mciSendCommand(wDeviceID, MCI_CLOSE, 0, (DWORD)NULL);
		return -1;
    }

	for (n = 0; n < 100; n++)
		remap[n] = n;
	cd_initialized = true;
	enabled = true;

	if (CDAudio_GetAudioDiskInfo())
	{
//		Com_Printf("CDAudio_Init: No CD in player.\n");
		cdValid = false;
		enabled = false;
	}

	Cmd_AddCommand ("cd", CD_f);

	CDAudio_MixerInit();
	cdAudio.cdVolume = CDAudio_GetMixerVolume();

	Com_Printf("CD Audio initialized");
	if (q2cd_letter[0])
		Com_Printf(" for drive %s\n", q2cd_letter);
	else
		Com_Printf("\n");
	return 0;
}

void CDAudio_Shutdown()
{
	if (!cd_initialized)
		return;
	CDAudio_Stop();
	Cmd_RemoveCommand ("cd");	// добавил Berserker.
	if (mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD)NULL))
		Com_DPrintf("CDAudio_Shutdown: MCI_CLOSE failed\n");
	CDAudio_MixerShutdown();
}

void CDAudio_Update()
{
	if ( (bool)cd_nocd->value != !enabled )
	{
		if ( cd_nocd->value )
		{
			CDAudio_Stop();
			enabled = false;
		}
		else
		{
			enabled = true;
			CDAudio_Resume ();
		}
	}


	/// cd volume control from Q2XP
	if (/*cd_volume*/s_musicVolume->modified)
	{
		if (/*cd_volume*/s_musicVolume->value > 1.0)
			Cvar_SetValue(/*"cd_volume"*/"s_musicVolume", 1.0);
		else if (/*cd_volume*/s_musicVolume->value < 0.0)
			Cvar_SetValue(/*"cd_volume"*/"s_musicVolume", 0.0);
		CDAudio_SetMixerVolume(/*cd_volume*/s_musicVolume->value);
		/*cd_volume*/s_musicVolume->modified = false;
	}

	/// Berserker's FIX
	if (cd_nocd->modified)
	{
		if (cd_nocd->value)
		{
			if (cd_initialized)
				CDAudio_Stop();
		}
		else
		{
			if (!cd_initialized)
				CDAudio_Init();
			if (cls.state == ca_active)
				CDAudio_Play (atoi(cl.configstrings[CS_TRACK_AMBIENT]), true);	// start the cd track
		}
		cd_nocd->modified = false;
	}
}

/*
===========
CDAudio_Activate

Called when the main window gains or loses focus.
The window have been destroyed and recreated
between a deactivate and an activate.
===========
*/
void CDAudio_Activate (bool active)
{
	if (active)
		CDAudio_Resume ();
	else
		CDAudio_Pause ();
}


////////////////////////////////////////////////////////////////////////
/*** WAV Music support instead CD Tracks ***/
/********* code from Quake 3 Arena *********/

/*
=================
Q_strncpyz
Safe strncpy that ensures a trailing zero
Q3A code
=================
*/
inline void Q_strncpyz (char *dst, const char *src, int dstSize)
{
	if (!dst)
		Com_Error(ERR_FATAL, "Q_strncpyz: NULL dst");

	if (!src)
		Com_Error(ERR_FATAL, "Q_strncpyz: NULL src");

	if (dstSize < 1)
		Com_Error(ERR_FATAL, "Q_strncpyz: dstSize < 1");

	strncpy( dst, src, dstSize - 1 );
	dst[dstSize - 1] = 0;
}

// Psychospaz's mod detector
bool modType (char *name)
{
	searchpath_t	*search;

	for (search = fs_searchpaths ; search ; search = search->next)
		if (!Q_strcasecmp (search->filename, name))
			return true;

	return false;
}




inline bool PointInBBox(float *point, float *mins, float *maxs)
{
	if (point[0] <= mins[0])	return false;
	if (point[0] >= maxs[0])	return false;
	if (point[1] <= mins[1])	return false;
	if (point[1] >= maxs[1])	return false;
	if (point[2] <= mins[2])	return false;
	if (point[2] >= maxs[2])	return false;
	return true;
}


/// VBO management based on Q2XP2 VBO-manager
inline void GL_BindVBO(vertCache_t *cache)
{
	if (gl_config.vbo)
	{
		if (cache)
		{
			if (gl_state.vbo_id == cache->id)
				return;
			gl_state.vbo_id = cache->id;
		}
		else
		{
			if (!gl_state.vbo_id)
				return;
			gl_state.vbo_id = 0;
		}
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, gl_state.vbo_id);
	}
}

vertCache_t *R_VCFindCache(vertStoreMode_t store, entity_t *ent, int mesh)
{
	model_t		*mod;
	int			frame;
	float		backlerp;
	vec3_t		angles, orgs;
	vertCache_t	*cache, *next;

	if (store == VBO_STORE_XYZ)
	{
		mod = ent->model;
		frame = ent->frame;
///		if ( r_lerpmodels->value )
			backlerp = ent->backlerp;
///		else
///			backlerp = 0;
		angles[0] = ent->angles[0];
		angles[1] = ent->angles[1];
		angles[2] = ent->angles[2];
		orgs[0] = ent->origin[0];
		orgs[1] = ent->origin[1];
		orgs[2] = ent->origin[2];
		for (cache = vcm.activeVertCache.next; cache != &vcm.activeVertCache; cache = next)
		{
			next = cache->next;
			if (backlerp)
			{	// анимированным моделям ОПАСНО шарить один и тот же VBO-кэш, т.к. они могут иметь и разные oldangles, oldorigin и oldframe. Отсюда следует, что геометрия будет немного отличаться. Поэтому данное кэширование будет действовать в пределах одного entity в пределах одного кадра. Выигрыш заметнее в местах с многими источниками света.
				if (cache->store == store && cache->mod == mod && cache->frame == frame && cache->mesh == mesh && cache->backlerp == backlerp && cache->angles[0] == angles[0] && cache->angles[1] == angles[1] && cache->angles[2] == angles[2] && cache->origin[0] == orgs[0] && cache->origin[1] == orgs[1] && cache->origin[2] == orgs[2])
				{	// already cached!
					GL_BindVBO(cache);
					return cache;
				}
			}
			else
			{	// для статичных моделей всё проще...
				if (cache->store == store && cache->mod == mod && cache->frame == frame && cache->mesh == mesh && cache->angles[0] == angles[0] && cache->angles[1] == angles[1] && cache->angles[2] == angles[2])
				{	// already cached!
					GL_BindVBO(cache);
					return cache;
				}
			}
		}
	}
	else if (store == VBO_STORE_NORMAL || store == VBO_STORE_BINORMAL || store == VBO_STORE_TANGENT)
	{
		mod = ent->model;
		frame = ent->frame;
///		if ( r_lerpmodels->value )
			backlerp = ent->backlerp;
///		else
///			backlerp = 0;
		for (cache = vcm.activeVertCache.next; cache != &vcm.activeVertCache; cache = next)
		{
			next = cache->next;
			if (cache->store == store && cache->mod == mod && cache->frame == frame && cache->mesh == mesh && cache->backlerp == backlerp)
			{	// already cached!
				GL_BindVBO(cache);
				return cache;
			}
		}
	}

	return NULL;
}


vertCache_t *R_VCLoadData(vertCacheMode_t mode, int size, void *buffer, vertStoreMode_t store, entity_t *ent, int mesh)
{
	vertCache_t *cache;

	if (!vcm.freeVertCache)
		Com_Error(ERR_FATAL, "VBO cache overflow");

	cache = vcm.freeVertCache;
	cache->mode = mode;
	cache->size = size;
	cache->pointer = buffer;
	cache->store = store;
	if (store != VBO_STORE_ANY)
	{
		cache->mod = ent->model;
		cache->mesh = mesh;
		cache->frame = ent->frame;
		cache->backlerp = ent->backlerp;
		cache->angles[0] = ent->angles[0];
		cache->angles[1] = ent->angles[1];
		cache->angles[2] = ent->angles[2];
		cache->origin[0] = ent->origin[0];
		cache->origin[1] = ent->origin[1];
		cache->origin[2] = ent->origin[2];
	}

	// link
	vcm.freeVertCache = vcm.freeVertCache->next;

	cache->next = vcm.activeVertCache.next;
	cache->prev = &vcm.activeVertCache;

	vcm.activeVertCache.next->prev = cache;
	vcm.activeVertCache.next = cache;

	GL_BindVBO(cache);

	switch (cache->mode)
	{
		case VBO_STATIC:
			glBufferDataARB(GL_ARRAY_BUFFER_ARB, cache->size, cache->pointer, GL_STATIC_DRAW_ARB);
		case VBO_DYNAMIC:
			glBufferDataARB(GL_ARRAY_BUFFER_ARB, cache->size, cache->pointer, GL_DYNAMIC_DRAW_ARB);
	}

	return cache;
}


vertCache_t *R_VCCreate(vertCacheMode_t mode, int size, void *buffer, vertStoreMode_t store, entity_t *ent, int mesh)
{
	vertCache_t	*cache;

	cache = R_VCFindCache(store, ent, mesh);
	if (cache)
		return cache;

	return R_VCLoadData(mode, size, buffer, store, ent, mesh);
}


void R_VCFree(vertCache_t *cache)
{
	if (!cache)
		return;

	// unlink
	cache->prev->next = cache->next;
	cache->next->prev = cache->prev;

	cache->next = vcm.freeVertCache;
	vcm.freeVertCache = cache;
}

void R_VCInit()
{
	int	i;

	if (!gl_config.vbo)
		return;

	Com_Printf("Initializing VBO cache...\n");
	vc_initialised = true;

	Memset(&vcm, 0, sizeof(vcm));

	// setup the linked lists
	vcm.activeVertCache.next = &vcm.activeVertCache;
	vcm.activeVertCache.prev = &vcm.activeVertCache;

	vcm.freeVertCache = vcm.vertCacheList;

	for (i=0; i<MAX_VERTEX_CACHES-1; i++)
		vcm.vertCacheList[i].next = &vcm.vertCacheList[i+1];

	// Создадим хэндлы для всех VBO
	for (i=0; i<MAX_VERTEX_CACHES; i++)
		glGenBuffersARB(1, &vcm.vertCacheList[i].id);
}

/*
===============
R_VCFreeFrame

Вызывается ПЕРЕД рисованием кадра со всеми зеркалами и ПОСЛЕ определения всех entity.
Deletes all non-STATIC buffers from the previous frame.
PS: VBO_STATIC using for ST texture/skin coordinates and static shadow volumes
===============
*/
void R_VCFreeFrame()
{
	vertCache_t	*cache, *next;

	if (!gl_config.vbo)
		return;

	for (cache = vcm.activeVertCache.next; cache != &vcm.activeVertCache; cache = next)
	{
		next = cache->next;
		if (cache->mode != VBO_STATIC)
			R_VCFree(cache);
	}
}

void R_VCShutdown()
{
	int			i;
	vertCache_t	*cache, *next;

	if (!gl_config.vbo)
		return;

	if (!vc_initialised)
		return;
	vc_initialised = false;

	for (cache = vcm.activeVertCache.next; cache != &vcm.activeVertCache; cache = next)
	{
		next = cache->next;
		R_VCFree(cache);
	}

	// Освободим хэндлы всех VBO
	for (i=0; i<MAX_VERTEX_CACHES; i++)
		glDeleteBuffersARB(1, &vcm.vertCacheList[i].id);
}


inline void Com_XOR (const void *b, const byte num)
{
	__asm
	{
		mov ebx, b
		mov al, num
		xor [ebx], al
	}
}


inline void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc)
{
	vecc[0] = veca[0] + scale*vecb[0];
	vecc[1] = veca[1] + scale*vecb[1];
	vecc[2] = veca[2] + scale*vecb[2];
}


int ZLibDecompress (byte *in, int inlen, byte *out, int outlen, int wbits)
{
	z_stream zs;
	int result;

	Memset (&zs, 0, sizeof(zs));

	zs.next_in = in;
	zs.avail_in = 0;

	zs.next_out = out;
	zs.avail_out = outlen;

	result = inflateInit2(&zs, wbits);
	if (result != Z_OK)
	{
		Com_Error (ERR_DROP, "Decompression error! inflateInit returned %d", result);
///		return 0;
	}

	zs.avail_in = inlen;

	result = inflate(&zs, Z_FINISH);
	if (result != Z_STREAM_END)
	{
		if (result == Z_BUF_ERROR && zs.avail_in == 0)	// Berserker: zlib would return Z_BUF_ERROR after completing decompression if avail_in was 0. Thats not a problem.
			goto noErr;			// see the http://gcc.gnu.org/ml/java-patches/2000-q4/msg00263.html
		Com_Error (ERR_DROP, "Decompression error! inflate returned %d", result);
///		zs.total_out = 0;
	}
	else
		result = 0;

noErr:
	result = inflateEnd(&zs);
	if (result != Z_OK)
	{
		Com_Error (ERR_DROP, "Decompression error! inflateEnd returned %d", result);
///		return 0;
	}

	return zs.total_out;
}


int ZLibCompressChunk(byte *in, int len_in, byte *out, int len_out, int method, int wbits)
{
	z_stream zs;
	int result;

	zs.next_in = in;
	zs.avail_in = len_in;
	zs.total_in = 0;

	zs.next_out = out;
	zs.avail_out = len_out;
	zs.total_out = 0;

	zs.msg = NULL;
	zs.state = NULL;
	zs.zalloc = Z_NULL;
	zs.zfree = Z_NULL;
	zs.opaque = NULL;

	zs.data_type = Z_BINARY;
	zs.adler = 0;
	zs.reserved = 0;

	result = deflateInit2 (&zs, method, Z_DEFLATED, wbits, 9, Z_DEFAULT_STRATEGY);
	if (result != Z_OK)
		return -1;

	result = deflate(&zs, Z_FINISH);
	if (result != Z_STREAM_END)
		return -1;

	result = deflateEnd(&zs);
	if (result != Z_OK)
		return -1;

	return zs.total_out;
}


/// Carmack's code
void Com_Prefetch (const void *s, const unsigned bytes/*, e_prefetch type*/)
{
	// write buffer prefetching is performed only if
	// the processor benefits from it. Read and read/write
	// prefetching is always performed.

///	switch (type)
///	{
///		case PRE_WRITE:
///			break;
///		case PRE_READ:
///		case PRE_READ_WRITE:
			__asm
			{
				mov		ebx,s
				mov		ecx,bytes
				cmp		ecx,4096				// clamp to 4kB
				jle		skipClamp
				mov		ecx,4096
skipClamp:
				add		ecx,0x1f
				shr		ecx,5					// number of cache lines
				jz		skip
				jmp		loopie

				align 16
loopie:			test	byte ptr [ebx],al
				add		ebx,32
				dec		ecx
				jnz		loopie
skip:
			}
///			break;
///	}
}


/// Carmack's code
void _copyDWord (unsigned *dest, const unsigned constant, const unsigned count)
{
	__asm
	{
			mov		edx,dest
			mov		eax,constant
			mov		ecx,count
			and		ecx,~7
			jz		padding
			sub		ecx,8
			jmp		loopu
			align	16
loopu:		
			test	[edx+ecx*4 + 28],ebx		// fetch next block destination to L1 cache
			mov		[edx+ecx*4 + 0],eax
			mov		[edx+ecx*4 + 4],eax
			mov		[edx+ecx*4 + 8],eax
			mov		[edx+ecx*4 + 12],eax
			mov		[edx+ecx*4 + 16],eax
			mov		[edx+ecx*4 + 20],eax
			mov		[edx+ecx*4 + 24],eax
			mov		[edx+ecx*4 + 28],eax
			sub		ecx,8
			jge		loopu
padding:	mov		ecx,count
			mov		ebx,ecx
			and		ecx,7
			jz		outta
			and		ebx,~7
			lea		edx,[edx+ebx*4]				// advance dest pointer
			test	[edx+0],eax					// fetch destination to L1 cache
			cmp		ecx,4
			jl		skip4
			mov		[edx+0],eax
			mov		[edx+4],eax
			mov		[edx+8],eax
			mov		[edx+12],eax
			add		edx,16
			sub		ecx,4
skip4:		cmp		ecx,2
			jl		skip2
			mov		[edx+0],eax
			mov		[edx+4],eax
			add		edx,8
			sub		ecx,2
skip2:		cmp		ecx,1
			jl		outta
			mov		[edx+0],eax
outta:
	}
}


/// Carmack's code
// optimized memory copy routine that handles all alignment
// cases and block sizes efficiently
void Com_Memcpy (void* dest, const void* src, const size_t count)
{
	Com_Prefetch (src, count/*, PRE_READ*/);
	__asm
	{
		push	edi
		push	esi
		mov		ecx,count
		cmp		ecx,0						// count = 0 check (just to be on the safe side)
		je		outta
		mov		edx,dest
		mov		ebx,src
		cmp		ecx,32						// padding only?
		jl		padding

		mov		edi,ecx					
		and		edi,~31					// edi = count&~31
		sub		edi,32

		align 16
loopMisAligned:
		mov		eax,[ebx + edi + 0 + 0*8]
		mov		esi,[ebx + edi + 4 + 0*8]
		mov		[edx+edi+0 + 0*8],eax
		mov		[edx+edi+4 + 0*8],esi
		mov		eax,[ebx + edi + 0 + 1*8]
		mov		esi,[ebx + edi + 4 + 1*8]
		mov		[edx+edi+0 + 1*8],eax
		mov		[edx+edi+4 + 1*8],esi
		mov		eax,[ebx + edi + 0 + 2*8]
		mov		esi,[ebx + edi + 4 + 2*8]
		mov		[edx+edi+0 + 2*8],eax
		mov		[edx+edi+4 + 2*8],esi
		mov		eax,[ebx + edi + 0 + 3*8]
		mov		esi,[ebx + edi + 4 + 3*8]
		mov		[edx+edi+0 + 3*8],eax
		mov		[edx+edi+4 + 3*8],esi
		sub		edi,32
		jge		loopMisAligned
		
		mov		edi,ecx
		and		edi,~31
		add		ebx,edi					// increase src pointer
		add		edx,edi					// increase dst pointer
		and		ecx,31					// new count
		jz		outta					// if count = 0, get outta here

padding:
		cmp		ecx,16
		jl		skip16
		mov		eax,dword ptr [ebx]
		mov		dword ptr [edx],eax
		mov		eax,dword ptr [ebx+4]
		mov		dword ptr [edx+4],eax
		mov		eax,dword ptr [ebx+8]
		mov		dword ptr [edx+8],eax
		mov		eax,dword ptr [ebx+12]
		mov		dword ptr [edx+12],eax
		sub		ecx,16
		add		ebx,16
		add		edx,16
skip16:
		cmp		ecx,8
		jl		skip8
		mov		eax,dword ptr [ebx]
		mov		dword ptr [edx],eax
		mov		eax,dword ptr [ebx+4]
		sub		ecx,8
		mov		dword ptr [edx+4],eax
		add		ebx,8
		add		edx,8
skip8:
		cmp		ecx,4
		jl		skip4
		mov		eax,dword ptr [ebx]	// here 4-7 bytes
		add		ebx,4
		sub		ecx,4
		mov		dword ptr [edx],eax
		add		edx,4
skip4:							// 0-3 remaining bytes
		cmp		ecx,2
		jl		skip2
		mov		ax,word ptr [ebx]	// two bytes
		cmp		ecx,3				// less than 3?
		mov		word ptr [edx],ax
		jl		outta
		mov		al,byte ptr [ebx+2]	// last byte
		mov		byte ptr [edx+2],al
		jmp		outta
skip2:
		cmp		ecx,1
		jl		outta
		mov		al,byte ptr [ebx]
		mov		byte ptr [edx],al
outta:
		pop		esi
		pop		edi
	}
}


/// Carmack's code
void Com_Memset (void* dest, const int val, const size_t count)
{
	unsigned fillval;

	if (count < 8)
	{
		__asm
		{
			mov		edx,dest
			mov		eax, val
			mov		ah,al
			mov		ebx,eax
			and		ebx, 0xffff
			shl		eax,16
			add		eax,ebx				// eax now contains pattern
			mov		ecx,count
			cmp		ecx,4
			jl		skip4
			mov		[edx],eax			// copy first dword
			add		edx,4
			sub		ecx,4
	skip4:	cmp		ecx,2
			jl		skip2
			mov		word ptr [edx],ax	// copy 2 bytes
			add		edx,2
			sub		ecx,2
	skip2:	cmp		ecx,0
			je		skip1
			mov		byte ptr [edx],al	// copy single byte
	skip1:
		}
		return;
	}

	fillval = val;
	
	fillval = fillval|(fillval<<8);
	fillval = fillval|(fillval<<16);		// fill dword with 8-bit pattern

	_copyDWord ((unsigned *)(dest),fillval, count/4);
	
	__asm									// padding of 0-3 bytes
	{
		mov		ecx,count
		mov		eax,ecx
		and		ecx,3
		jz		skipA
		and		eax,~3
		mov		ebx,dest
		add		ebx,eax
		mov		eax,fillval
		cmp		ecx,2
		jl		skipB
		mov		word ptr [ebx],ax
		cmp		ecx,2
		je		skipA					
		mov		byte ptr [ebx+2],al		
		jmp		skipA
skipB:		
		cmp		ecx,0
		je		skipA
		mov		byte ptr [ebx],al
skipA:
	}
}


/// Carmack's code
bool Com_Memcmp (const void *src0, const void *src1, const unsigned count)
{
	unsigned i;
	// MMX version anyone?

	if (count >= 16)
	{
		unsigned *dw = (unsigned *)(src0);
		unsigned *sw = (unsigned *)(src1);

		unsigned nm2 = count/16;
		for (i = 0; i < nm2; i+=4)
		{
			unsigned tmp = (dw[i+0]-sw[i+0])|(dw[i+1]-sw[i+1])|
						  (dw[i+2]-sw[i+2])|(dw[i+3]-sw[i+3]);
			if (tmp)
				return false;
		}
	}
	if (count & 15)
	{
		byte *d = (byte*)src0;
		byte *s = (byte*)src1;
		for (i = count & 0xfffffff0; i < count; i++)
		if (d[i]!=s[i])
			return false;
	}

	return true;
}


float	frand()
{
	return (rand()&32767)* (1.0/32767);
}


float	crand()
{
	return (rand()&32767)* (2.0/32767) - 1;
}

/*
==============================================================================
						ZONE MEMORY ALLOCATION
				just cleared malloc with counters now...
==============================================================================
*/

#define	Z_MAGIC		0xdead

typedef struct zhead_s
{
	struct zhead_s	*prev, *next;
	WORD			magic;
	short			tag;			// for group free
	int				size;
} zhead_t;

zhead_t	z_chain;
int		z_count, z_bytes;


void Z_Free (void *ptr)
{
	zhead_t	*z;

	if (!ptr)
		Com_Error (ERR_FATAL, "Z_Free: NULL");

	z = ((zhead_t *)ptr) - 1;

	if (z->magic != Z_MAGIC)
		Com_Error (ERR_FATAL, "Z_Free: bad magic");

	z->prev->next = z->next;
	z->next->prev = z->prev;

	z_count--;
	z_bytes -= z->size;
	free (z);
}


void Z_Stats_f ()
{
	Com_Printf ("Zone_Memory: %i bytes in %i blocks\n", z_bytes, z_count);
}

void H_Stats_f ()
{
	Com_Printf ("Hunk_Memory: %i bytes in %i blocks\n", hunk_total_size, hunkcount);
}

void M_Stats_f ()
{
	Com_Printf ("Memory statistics:\n");
	Z_Stats_f ();
	H_Stats_f ();
	Com_Printf ("Total: %i bytes in %i blocks\n", z_bytes + hunk_total_size, z_count + hunkcount);
}

void Z_FreeTags (int tag)
{
	zhead_t	*z, *next;

	for (z=z_chain.next ; z != &z_chain ; z=next)
	{
		next = z->next;
		if (z->tag == tag)
			Z_Free ((void *)(z+1));
	}
}


void *Z_TagMalloc (int size, int tag, bool crash)
{
	zhead_t	*z;
	char	*stag;

	if (size < 32)
		size = 32;

	size = size + sizeof(zhead_t);
	z = (zhead_t*) malloc(size);
	if (!z)
	{
		if (!crash)
			return NULL;
		if (tag == TAG_GAME)
			stag=" for GAME";
		else if (tag == TAG_LEVEL)
			stag=" for LEVEL";
		else
			stag="";
		Com_Error (ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes%s", size, stag);
	}
	Memset (z, 0, size);
	z_count++;
	z_bytes += size;
	z->magic = Z_MAGIC;
	z->tag = tag;
	z->size = size;

	z->next = z_chain.next;
	z->prev = &z_chain;
	z_chain.next->prev = z;
	z_chain.next = z;

	return (void *)(z+1);
}


void *Z_Malloc (int size, bool crash)
{
	return Z_TagMalloc (size, 0, crash);
}

void *G_TagMalloc (int size, int tag)
{
	return Z_TagMalloc (size, tag, true);
}

//============================================================================


short   ShortSwap (short l)
{
	byte    b1,b2;

	b1 = l&255;
	b2 = (l>>8)&255;

	return (b1<<8) + b2;
}


short	ShortNoSwap (short l)
{
	return l;
}


int    LongSwap (int l)
{
	byte    b1,b2,b3,b4;

	b1 = l&255;
	b2 = (l>>8)&255;
	b3 = (l>>16)&255;
	b4 = (l>>24)&255;

	return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
}


int	LongNoSwap (int l)
{
	return l;
}


float FloatSwap (float f)
{
	union
	{
		float	f;
		byte	b[4];
	} dat1, dat2;

	dat1.f = f;
	dat2.b[0] = dat1.b[3];
	dat2.b[1] = dat1.b[2];
	dat2.b[2] = dat1.b[1];
	dat2.b[3] = dat1.b[0];
	return dat2.f;
}


float FloatNoSwap (float f)
{
	return f;
}


void Swap_Init ()
{
	byte	swaptest[2] = {1,0};

	// set the byte swapping variables in a portable manner	
	if ( *(short *)swaptest == 1)
	{
		_BigShort = ShortSwap;
		_LittleShort = ShortNoSwap;
		_BigLong = LongSwap;
		_LittleLong = LongNoSwap;
		_BigFloat = FloatSwap;
		_LittleFloat = FloatNoSwap;
	}
	else
	{
		_BigShort = ShortNoSwap;
		_LittleShort = ShortSwap;
		_BigLong = LongNoSwap;
		_LittleLong = LongSwap;
		_BigFloat = FloatNoSwap;
		_LittleFloat = FloatSwap;
	}
}


// Fast 15 cycle asm dot product, credits to Golgotha
__forceinline float __cdecl DotProduct(const float v1[3], const float v2[3]) 
{
	FLOAT dotret;

	__asm 
	{
		mov ecx, v1
		mov eax, v2

		;optimized dot product		;15 cycles
		fld dword ptr   [eax+0]     ;starts & ends on cycle 0
		fmul dword ptr  [ecx+0]     ;starts on cycle 1
		fld dword ptr   [eax+4]     ;starts & ends on cycle 2
		fmul dword ptr  [ecx+4]     ;starts on cycle 3
		fld dword ptr   [eax+8]     ;starts & ends on cycle 4
		fmul dword ptr  [ecx+8]     ;starts on cycle 5
		fxch            st(1)       ;no cost
		faddp           st(2),st(0) ;starts on cycle 6, stalls for cycles 7-8
		faddp           st(1),st(0) ;starts on cycle 9, stalls for cycles 10-12
		fstp dword ptr  [dotret]    ;starts on cycle 13, ends on cycle 14
	}

	return dotret;
}


void strRemoveColors(char *str, char *strbuf)
{
	int	i = 0;
	while (str)
	{
		if (Q_IsColorString(str))
			str += 2;
		strbuf[i++] = *(str++);
		if (i == MAX_OSPATH-1)
			break;
	}
	strbuf[i] = 0;
}

void Cmd_Condition_f ()
{
	char	f[MAX_OSPATH];
	char	s0[MAX_OSPATH];
	char	s1[MAX_OSPATH];
	float	val0, val1;

	f[0] = 0;

	if (Cmd_Argc () < 5)
	{
		Com_Printf ("^3USAGE: condition [cond] <command> : conditional execution a command\n");
		Com_Printf ("^3where [cond] is:\n");
		Com_Printf ("^3STRCMP <string0> <string1> : string comparison\n");
		Com_Printf ("^3STRNCMP <string0> <string1> <N> : string comparison (first N chars)\n");
		Com_Printf ("^3STRCASECMP <string0> <string1> : no case sensitive string comparison\n");
		Com_Printf ("^3STRNCASECMP <string0> <string1> <N> : no case sensitive string comparison (first N chars)\n");
		Com_Printf ("^3CMP <value0> [== < <= > >=] <value1> : value comparison\n");
		return;
	}

	if (!Q_strcasecmp (Cmd_Argv(1), "strcmp"))
	{
		strRemoveColors(Cmd_Argv(2), s0);
		strRemoveColors(Cmd_Argv(3), s1);
		if (!strcmp (s0, s1))
			Com_sprintf(f, sizeof(f), "%s\n", Cmd_Argv(4));
	}

	if (!Q_strcasecmp (Cmd_Argv(1), "strncmp"))
	{
		strRemoveColors(Cmd_Argv(2), s0);
		strRemoveColors(Cmd_Argv(3), s1);
		if (!strncmp (s0, s1, atoi(Cmd_Argv(4))))
			Com_sprintf(f, sizeof(f), "%s\n", Cmd_Argv(5));
	}

	if (!Q_strcasecmp (Cmd_Argv(1), "strcasecmp"))
	{
		strRemoveColors(Cmd_Argv(2), s0);
		strRemoveColors(Cmd_Argv(3), s1);
		if (!Q_strcasecmp (s0, s1))
			Com_sprintf(f, sizeof(f), "%s\n", Cmd_Argv(4));
	}

	if (!Q_strcasecmp (Cmd_Argv(1), "strncasecmp"))
	{
		strRemoveColors(Cmd_Argv(2), s0);
		strRemoveColors(Cmd_Argv(3), s1);
		if (!Q_strncasecmp (s0, s1, atoi(Cmd_Argv(4))))
			Com_sprintf(f, sizeof(f), "%s\n", Cmd_Argv(5));
	}

	if (!Q_strcasecmp (Cmd_Argv(1), "cmp"))
	{
		val0 = atof(Cmd_Argv(2));
		val1 = atof(Cmd_Argv(4));
		if (!Q_strcasecmp (Cmd_Argv(3), "=="))
		{
			if (val0 == val1)
				Com_sprintf(f, sizeof(f), "%s\n", Cmd_Argv(5));
		}

		if (!Q_strcasecmp (Cmd_Argv(3), "<"))
		{
			if (val0 < val1)
				Com_sprintf(f, sizeof(f), "%s\n", Cmd_Argv(5));
		}

		if (!Q_strcasecmp (Cmd_Argv(3), "<="))
		{
			if (val0 <= val1)
				Com_sprintf(f, sizeof(f), "%s\n", Cmd_Argv(5));
		}

		if (!Q_strcasecmp (Cmd_Argv(3), ">"))
		{
			if (val0 > val1)
				Com_sprintf(f, sizeof(f), "%s\n", Cmd_Argv(5));
		}

		if (!Q_strcasecmp (Cmd_Argv(3), ">="))
		{
			if (val0 >= val1)
				Com_sprintf(f, sizeof(f), "%s\n", Cmd_Argv(5));
		}
	}

	if (f[0])
		Cbuf_InsertText (f);
}


/*
===========
IN_DeactivateMouse

Called when the window loses focus
===========
*/
void IN_DeactivateMouse ()
{
	if (!mouseactive)
		return;

	if (old_mouse_support)
	{
		if (!mouseinitialized)
			return;

		if (restore_spi)
			SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0);
	}

	/// Berserker's fix
	ClipCursor (NULL);
	ReleaseCapture ();

	mouseactive = false;
	while (ShowCursor (TRUE) < 0);
}


/*
===========
IN_ActivateMouse

Called when the window gains focus or changes in some way
===========
*/
void IN_ActivateMouse ()
{
	int		width, height;

	if (old_mouse_support)
	{
		if (!mouseinitialized)
			return;

		if (!in_mouse->value)
		{
			mouseactive = false;
			return;
		}

		if (mouseactive && !m_xpfix->modified)
			return;

		m_xpfix->modified = false;

		if (mouseparmsvalid)
			restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, m_xpfix->value ? newMouseParmsXP : newmouseparms, 0);
	}
	else
	{
		if (mouseactive)
			return;
	}

	/// Berserker's fix
	width = GetSystemMetrics (SM_CXSCREEN);
	height = GetSystemMetrics (SM_CYSCREEN);

	GetWindowRect (cl_hwnd, &window_rect);
	if (window_rect.left < 0)
		window_rect.left = 0;
	if (window_rect.top < 0)
		window_rect.top = 0;
	if (window_rect.right >= width)
		window_rect.right = width-1;
	if (window_rect.bottom >= height-1)
		window_rect.bottom = height-1;

	window_center_x = (window_rect.right + window_rect.left)/2;
	window_center_y = (window_rect.top + window_rect.bottom)/2;

	old_x = window_center_x;
	old_y = window_center_y;

	SetCursorPos (window_center_x, window_center_y);
	SetCapture ( cl_hwnd );
	ClipCursor (&window_rect);

	mouseactive = true;
	while (ShowCursor (FALSE) >= 0);
}


void IN_Shutdown ()
{
	IN_DeactivateMouse ();
}




void SZ_Init (sizebuf_t *buf, byte *data, int length)
{
	Memset (buf, 0, sizeof(*buf));
	buf->data = data;
	buf->maxsize = length;
}


void SZ_Clear (sizebuf_t *buf)
{
	buf->cursize = 0;
	buf->overflowed = false;
}


char *SZ_GetSpace (sizebuf_t *buf, int length)
{
	char *data;

	if (buf->cursize + length > buf->maxsize)
	{
		SZ_Clear (buf); 
		buf->overflowed = true;
	}

	data = (char*) buf->data + buf->cursize;
	buf->cursize += length;
	
	return data;
}


void SZ_Write (sizebuf_t *buf, void *data, int length)
{
	Memcpy (SZ_GetSpace(buf,length),data,length);
}


void SZ_Print (sizebuf_t *buf, char *data)
{
	int		len;
	
	len = strlen(data)+1;

	if (buf->cursize)
	{
		if (buf->data[buf->cursize-1])
			Memcpy ((byte *)SZ_GetSpace(buf, len),data,len); // no trailing 0
		else
			Memcpy ((byte *)SZ_GetSpace(buf, len-1)-1,data,len); // write over trailing 0
	}
	else
		Memcpy ((byte *)SZ_GetSpace(buf, len),data,len);
}


void Com_sprintf (char *dest, int size, char *fmt, ...)
{
	int		len;
	va_list	argptr;
	char	bigbuffer[0x10000];

	va_start (argptr,fmt);
	len = vsprintf (bigbuffer,fmt,argptr);
	va_end (argptr);
	if (len >= size)
		printf ("Com_sprintf: overflow of %i in %i\n", len, size);
	strncpy (dest, bigbuffer, size-1);
}


char	*va(char *format, ...)
{
	va_list		argptr;
	static char	string[1024];

	va_start (argptr, format);
	vsprintf (string, format,argptr);
	va_end (argptr);

	return string;	
}


int Q_strncasecmp (char *s1, char *s2, int n)
{
	int		c1, c2;

	if(!s1 || !s2)
		return -1;			// one or both strings are NULL
	
	do
	{
		c1 = *s1++;
		c2 = *s2++;

		if (!n--)
			return 0;		// strings are equal until end point
		
		if (c1 != c2)
		{
			if (c1 >= 'a' && c1 <= 'z')
				c1 -= ('a' - 'A');
			if (c2 >= 'a' && c2 <= 'z')
				c2 -= ('a' - 'A');
			if (c1 != c2)
				return -1;		// strings not equal
		}
	} while (c1);
	
	return 0;		// strings are equal
}


int Q_strcasecmp (char *s1, char *s2)
{
	return Q_strncasecmp (s1, s2, 99999);
}


int Q_strnicmp (const char *s1, const char *s2, int n)
{
	int		c1, c2;

	if (!s1)
	{
		if (!s2)
			return 0;
		else
			return -1;
	}
	else if (!s2)
		return 1;

	do
	{
		c1 = *s1++;
		c2 = *s2++;

		if (!n--)
			return 0;	// strings are equal until end point

		if (c1 != c2)
		{
			if (c1 >= 'a' && c1 <= 'z')
				c1 -= ('a' - 'A');
			if (c2 >= 'a' && c2 <= 'z')
				c2 -= ('a' - 'A');

			if (c1 != c2)
				return c1 < c2 ? -1 : 1;
		}
	} while (c1);

	return 0;			// strings are equal
}


int Q_stricmp (const char *s1, const char *s2)
{
	return Q_strnicmp (s1, s2, 99999);
}

void Con_Linefeed ()
{
	con.x = 0;
	if (con.display == con.current)
		con.display++;
	con.current++;
	Memset (&con.text[(con.current%con.totallines)*con.linewidth], ' ', con.linewidth);
	Memset (&con.color[(con.current%con.totallines)*con.linewidth], 7, con.linewidth);
}


/*
================
Con_Print

Handles cursor positioning, line wrapping, etc
All console printing must go through this in order to be logged to disk
If no console is visible, the text will appear at the top of the game window
================
*/
void Con_Print (char *txt)
{
	int		y;
	int		c, l;
	char	*cc;
	static int	cr;
///	int		mask;
	char	cindex;

	if (!con.initialized)
		return;

	if (txt[0] == 1 || txt[0] == 2)
	{
///		mask = 128;		// go to colored text
///		cindex = 2;		PVS-Studio
		txt++;
	}
///	else
///		mask = 0;

	cindex = 7;
	while ( 1 )
	{
povtor:	if (!(c = *txt))
			break;

		if (Q_IsColorString(txt))
		{
			cindex = ColorIndex(txt[1]);
			txt+=2;
			goto povtor;
		}

		// count word length
		int ll=0;
		for (l=0 ; l< con.linewidth ; l++)
		{
			cc = &txt[l];
			if (Q_IsColorString(cc))
				l+=2;
			if (txt[l] <= ' ')
				break;
			ll++;
		}

	// word wrap
		if (ll != con.linewidth && (con.x + ll > con.linewidth) )
			con.x = 0;

		txt++;

		if (cr)
		{
			con.current--;
			cr = false;
		}

		if (!con.x)
		{
			Con_Linefeed ();
		// mark time for transparent overlay
			if (con.current >= 0)
				con.times[con.current % NUM_CON_TIMES] = cls.realtime;
		}

		switch (c)
		{
		case '\n':
			con.x = 0;
			break;

		case '\r':
			con.x = 0;
			cr = 1;
			break;

		default:	// display character and advance
			y = con.current % con.totallines;
			con.text[y*con.linewidth+con.x] = c;/// | mask | con.ormask;
			con.color[y*con.linewidth+con.x] = cindex;
			con.x++;
			if (con.x >= con.linewidth)
				con.x = 0;
			break;
		}
		
	}
}


void Com_Printf_Mono (char *prefix, char *text)
{
	char	c, out[MAXPRINTMSG];
	int		i = 0;

	while (1)
	{
povtor:	if (!(c = *text) || (i==MAXPRINTMSG-1))
		{
			out[i] = 0;
			Com_Printf("%s%s",prefix,out);
			return;
		}

		if (Q_IsColorString(text))
		{
			text+=2;		// Удаляем цветные метки
			goto povtor;
		}

		text++;
		out[i++] = c;
	}
}


/*
================
Sys_ConsoleOutput

Print text to the dedicated console
Berserker: string must be monochrome text
================
*/
void Sys_ConsoleOutput (char *string)
{
	unsigned long	dummy;
	char			text[260];

	if (!dedicated || !dedicated->value)
		return;

	if (console_textlen)
	{
		text[0] = '\r';
		Memset(&text[1], ' ', console_textlen);
		text[console_textlen+1] = '\r';
		text[console_textlen+2] = 0;
		WriteFile(houtput, text, console_textlen+2, &dummy, NULL);
	}

	WriteFile(houtput, string, strlen(string), &dummy, NULL);

	if (console_textlen)
		WriteFile(houtput, console_text, console_textlen, &dummy, NULL);
}


/*
============
FS_Gamedir

Called to find where to write a file (demos, savegames, etc)
============
*/
char *FS_Gamedir ()
{
	if (*fs_gamedir)
		return fs_gamedir;
	else
		return BASEDIRNAME;
}



// Berserker: file system error handling, from MSDN
char *FS_GetError(errno_t err)
{
	static char	errmess[16];
	char		*mess;

	if (!err)
		return NULL;

	switch(err)
	{
		case EPERM:
			mess = "Operation not permitted";
			break;
		case ENOENT:
			mess = "No such file or directory";
			break;
		case ESRCH:
			mess = "No such process";
			break;
		case EINTR:
 			mess = "Interrupted function";
			break;
		case EIO:
 			mess = "I/O error";
			break;
		case ENXIO:
 			mess = "No such device or address";
			break;
		case E2BIG:
 			mess = "Argument list too long";
			break;
		case ENOEXEC:
 			mess = "Exec format error";
			break;
		case EBADF:
 			mess = "Bad file number";
			break;
		case ECHILD:
 			mess = "No spawned processes";
			break;
		case EAGAIN:
 			mess = "No more processes or not enough memory or maximum nesting level reached";
			break;
		case ENOMEM:
 			mess = "Not enough memory";
			break;
		case EACCES:
 			mess = "Permission denied";
			break;
		case EFAULT:
 			mess = "Bad address";
			break;
		case EBUSY:
 			mess = "Device or resource busy";
			break;
		case EEXIST:
 			mess = "File exists";
			break;
		case EXDEV:
 			mess = "Cross-device link";
			break;
		case ENODEV:
 			mess = "No such device";
			break;
		case ENOTDIR:
 			mess = "Not a directory";
			break;
		case EISDIR:
 			mess = "Is a directory";
			break;
		case EINVAL:
 			mess = "Invalid argument";
			break;
		case ENFILE:
 			mess = "Too many files open in system";
			break;
		case EMFILE:
 			mess = "Too many open files";
			break;
		case ENOTTY:
 			mess = "Inappropriate I/O control operation";
			break;
		case EFBIG:
 			mess = "File too large";
			break;
		case ENOSPC:
 			mess = "No space left on device";
			break;
		case ESPIPE:
 			mess = "Invalid seek";
			break;
		case EROFS:
 			mess = "Read-only file system";
			break;
		case EMLINK:
 			mess = "Too many links";
			break;
		case EPIPE:
 			mess = "Broken pipe";
			break;
		case EDOM:
 			mess = "Math argument";
			break;
		case ERANGE:
 			mess = "Result too large";
			break;
		case EDEADLK:
 			mess = "Resource deadlock would occur";
			break;
		case ENAMETOOLONG:
 			mess = "Filename too long";
			break;
		case ENOLCK:
 			mess = "No locks available";
			break;
		case ENOSYS:
 			mess = "Function not supported";
			break;
		case ENOTEMPTY:
 			mess = "Directory not empty";
			break;
		case EILSEQ:
 			mess = "Illegal byte sequence";
			break;
		case STRUNCATE:
 			mess = "String was truncated";
			break;
		default:
			Com_sprintf(errmess, sizeof(errmess), "%i", err);
			mess = errmess;
			break;
	}

	return mess;
}

FILE *FS_Fopen(const char *name, const char *mode)
{
	FILE	*file;
#if 0
	file = fopen (name, mode);
#else
	errno_t	err;
	char	*mess;
	err = fopen_s(&file, name, mode);
	if (err == ENOENT)
		mess = NULL;		// Berserker: отсутствие файла - обычное дело, не будем спамить!
	else
		mess = FS_GetError(err);
	if (mess)
	{
		Com_Printf("FS_Fopen(\"%s\", \"%s\"): %s\n", name, mode, mess);
		if (file)
		{
			fclose(file);
			file = NULL;
		}
	}
#endif
	return file;
}

/*
=============
Com_Printf

Both client and server can use this, and it will output
to the apropriate place.
=============
*/
void Com_Printf (char *fmt, ...)
{
	int			i;
	va_list		argptr;
	char		c;
	char		*in;
	char		msg[MAXPRINTMSG];
	char		msg_mono[MAXPRINTMSG];

	va_start (argptr,fmt);
//vsprintf (msg,fmt,argptr);
  vsnprintf (msg, sizeof(msg), fmt, argptr);
	va_end (argptr);

	if (rd_target)
	{
		if ((strlen (msg) + strlen(rd_buffer)) > (rd_buffersize - 1))
		{
			rd_flush(rd_target, rd_buffer);
			*rd_buffer = 0;
		}
		strcat (rd_buffer, msg);
		return;
	}

	Con_Print (msg);

	// convert colored text to mono
	in = msg;
	i = 0;
	while (1)
	{
povtor:	if (!(c = *in))
			break;
		if (Q_IsColorString(in))
		{
			in+=2;		// Удаляем цветные метки
			goto povtor;
		}
		in++;
		msg_mono[i++] = c;
	}
	msg_mono[i] = 0;

	// also echo to debugging console
	Sys_ConsoleOutput (msg_mono);

	// logfile
	if (logfile_active && logfile_active->value)
	{
		char name[MAX_QPATH];

		if (!logfile)
		{
			Com_sprintf (name, sizeof(name), "%s/q2b_console.log", FS_Gamedir());
			if (logfile_active->value > 2)
				logfile = FS_Fopen (name, "a");
			else
				logfile = FS_Fopen (name, "w");
		}
		if (logfile)
		{
			fprintf (logfile, "%s", msg_mono);
			if (logfile_active->value > 1)
				fflush (logfile);		// force it to save every time
		}
	}
}


void Cmd_Exec_f ()
{
	char	*f, *f2;
	int		len;

	if (Cmd_Argc () != 2)
	{
		Com_Printf ("^3USAGE: exec <filename> : execute a script file\n");
		return;
	}

	len = FS_LoadFile (Cmd_Argv(1), (void **)&f);
	if (!f || len <= 0)
	{
		if (f)
			Z_Free(f);
		Com_Printf ("^1couldn't exec %s\n",Cmd_Argv(1));
		return;
	}
	Com_Printf ("execing %s\n",Cmd_Argv(1));
	
	// the file doesn't have a trailing 0, so we need to copy it off
	f2 = (char*) Z_Malloc(len+1, true);
	Memcpy (f2, f, len);
	f2[len] = 0;

	Cbuf_InsertText (f2);

	Z_Free (f2);
	Z_Free (f);
}


/*
===================
Key_KeynumToString

Returns a string (either a single ascii char, or a K_* name) for the
given keynum.
FIXME: handle quote special (general escape sequence?)
===================
*/
char *Key_KeynumToString (int keynum)
{
	keyname_t	*kn;	
	static	char	tinystr[2];
	
	if (keynum == -1)
		return "<KEY NOT FOUND>";
	if (keynum > 32 && keynum < 127)
	{	// printable ascii
		tinystr[0] = keynum;
		tinystr[1] = 0;
		return tinystr;
	}
	
	for (kn=keynames ; kn->name ; kn++)
		if (keynum == kn->keynum)
			return kn->name;

	return "<UNKNOWN KEYNUM>";
}


/*
============
Key_WriteBindings

Writes lines containing "bind key value"
============
*/
void Key_WriteBindings (FILE *f)
{
	int	i;
	for (i=0 ; i<256 ; i++)
		if (keybindings[i] && keybindings[i][0])
			fprintf (f, "bind %s \"%s\"\n", Key_KeynumToString(i), keybindings[i]);
}


/*
============
Cvar_WriteVariables

Appends lines containing "set variable value" for all variables
with the archive flag set to true.
============
*/
void Cvar_WriteVariables (char *path)
{
	cvar_t	*var;
	char	buffer[1024];
	FILE	*f;

	f = FS_Fopen (path, "a");
	if (f)
	{
		for (var = cvar_vars ; var ; var = var->next)
		{
			if (var->flags & CVAR_ARCHIVE)
			{
				// write the latched value, even if it hasn't taken effect yet
				if ( var->latched_string )
					Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latched_string);
				else if ( var->vid_latched_string )
					Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->vid_latched_string);
				else
					Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string);
				fprintf (f, "%s", buffer);
			}
		}
		fclose (f);
	}
}


/*
===============
CL_WriteConfiguration

Writes key bindings and archived cvars to q2b_config.cfg
===============
*/
void CL_WriteConfiguration ()
{
	FILE	*f;
	char	path[MAX_QPATH];

	if (cls.state == ca_uninitialized)
		return;

	Com_sprintf (path, sizeof(path),"%s/q2b_config.cfg",FS_Gamedir());
	f = FS_Fopen (path, "w");
	if (!f)
	{
		Com_Printf ("^1Couldn't write q2b_config.cfg.\n");
		return;
	}

	fprintf (f, "// generated by Berserker@Quake2, modify as desired\n");
	Key_WriteBindings (f);
	fclose (f);

	Cvar_WriteVariables (path);
}


void Init_Palette ()
{
	byte	*pic, *pal;
	int		width, height;
	bool	mypal;

	if(q2_palette_initialized)
		return;

	// get the palette
	pal = NULL;
	LoadPCX ("pics/colormap.pcx", &pic, &pal, &width, &height);
	if (!pal)
	{
		if(pic)
			Z_Free (pic);
		pal = r_q2pal;
		mypal = true;
	}
	else
		mypal = false;

	Memcpy(d_8to24table, pal, 768);
	q2_palette_initialized = true;

	if(pic)
		Z_Free (pic);
	if(!mypal)
		Z_Free (pal);
}


void RestoreGamma()
{
	if(!vid_ignorehwgamma->value)
		if(gamma_initialized)
			if (!SetDeviceGammaRamp(glw_state.hDC, (void*)system_gamma))
				if (logfile)	// fix: logfile destroyed
					Com_DPrintf("Error during restore DeviceGammaRamp!\n");
}


void SaveGamma()
{
	if(vid_ignorehwgamma->value)
		return;

	Memset (&system_gamma, 0, sizeof(system_gamma));
	gamma_initialized = GetDeviceGammaRamp(glw_state.hDC, (void*)system_gamma);
	if (gamma_initialized)
		Com_DPrintf("DeviceGammaRamp initialized successfully\n");
	else
		Com_DPrintf("Error initialization DeviceGammaRamp!\n");
}


//===========================================================================
// MakeGammaRamp
//  Gamma      - non-linear factor (curvature; >1.0 multiplies)
//  Contrast   - steepness
//  Brightness - uniform offset 
//===========================================================================
void MakeGammaRamp(WORD *ramp, float gamma, float contrast, float bright)
{
#if 1
	gamma *= 2;
	bright = 1 + (((bright - 2) / 4) * 65535);
	contrast = 1 + ((contrast - 1) / 2);

	for (int i = 0; i < 256; i++)
	{
		double value = i * 256;
		value = (pow(value / 65535, 1 / (double)gamma) * 65535) + 0.5;
		value = ((((value / 65535) - 0.5) * contrast) + 0.5) * 65535;
		value += bright;
		if (value < 0)
			value = 0;
		if (value > 65535)
			value = 65535;
		ramp[i] = ramp[i + 256] = ramp[i + 512] = (WORD)value;
	}
#else
	int     i;
	double  ideal[256];			// After processing clamped to WORD.
	double  norm;

	// Init the ramp as a line with the steepness defined by contrast.
	for(i = 0; i < 256; i++)
		ideal[i] = i * contrast - (contrast - 1) * 127;

	// Apply the gamma curve.
	if(gamma != 1)
	{
		if(gamma <= 0.1f)
			gamma = 0.1f;
		norm = pow(255.0, double(1 / gamma - 1));	// Normalizing factor.
		for(i = 0; i < 256; i++)
			ideal[i] = pow(ideal[i], double(1 / gamma)) / norm;
	}

	// The last step is to add the brightness offset.
	for(i = 0; i < 256; i++)
		ideal[i] += bright * 128;

	// Clamp it and write the ramp table.
	for(i = 0; i < 256; i++)
	{
		ideal[i] *= 0x100;		// Byte => word
		if(ideal[i] < 0)
			ideal[i] = 0;
		if(ideal[i] > 0xffff)
			ideal[i] = 0xffff;
		ramp[i] = ramp[i + 256] = ramp[i + 512] = (WORD) ideal[i];
	}
#endif
}


void SetGamma(float Gamma, float Bright, float Contrast)
{
	if(vid_ignorehwgamma->value)
		return;

	if(!gamma_initialized)
		SaveGamma();

	if(gamma_initialized)
	{
		if(Gamma < 0.1)	Gamma = 0.1;
		if(Gamma > 3)	Gamma = 3;

		if(Bright < 0)	Bright = 0;
		if(Bright > 4)	Bright = 4;

		if(Contrast < 0)	Contrast = 0;
		if(Contrast > 2)	Contrast = 2;

		MakeGammaRamp(gammaRamp, Gamma, Contrast, Bright);
		if(!SetDeviceGammaRamp(glw_state.hDC, (void*)gammaRamp))
			Com_DPrintf("Error during change DeviceGammaRamp!\n");
		else
			Com_DPrintf("DeviceGammaRamp changed successfully\n");
	}
}


void	GL_InitImages ()
{
	registration_sequence = 1;

	if(!vid_ignorehwgamma->value)
		return;

	int i, j;
	float g = 1.8 - r_gamma->value;

	// init intensity conversions
	r_intensity = Cvar_Get ("r_intensity", "2", CVAR_ARCHIVE|CVAR_VID_LATCH);

	if ( r_intensity->value < 1 )
		Cvar_Set( "r_intensity", "1" );

	gl_state.inverse_intensity = 1.0 / r_intensity->value;

	for ( i = 0; i < 256; i++ )
	{
		if ( g == 1 )
			gammatable[i] = i;
		else
		{
			float inf;

			inf = 255 * pow ( float((i+0.5)/255.5) , g ) + 0.5;
			if (inf < 0)
				inf = 0;
			if (inf > 255)
				inf = 255;
			gammatable[i] = inf;
		}
	}

	for (i=0 ; i<256 ; i++)
	{
		j = i*r_intensity->value;
		if (j > 255)
			j = 255;
		intensitytable[i] = j;
	}
}


void GL_ShutdownMaterials()
{
	int			i;
	material_t	*material;

	for (i=0, material=materials ; i<nummaterials ; i++, material++)
	{
		if (!material->registration_sequence)
			continue;		// free material_t slot
		// free it
		Memset (material, 0, sizeof(*material));
	}
}


void GL_ShutdownImages()
{
	int		i;
	image_t	*image;
	GLuint	t;

	for (i=0, image=gltextures ; i<numgltextures ; i++, image++)
	{
		if (!image->registration_sequence)
			continue;		// free image_t slot
		// free it
		glDeleteTextures (1, &image->texnum);
		Memset (image, 0, sizeof(*image));
	}

// Berserker's fix for old Q2 bug:
	// free lightmaps
	if (gl_lms.current_lightmap_texture)
	{
		t = TEXNUM_LIGHTMAPS;
		glDeleteTextures (gl_lms.current_lightmap_texture, &t);
	}
}


inline char b_chrt(char sym)
{
	if((sym>0x40)&&(sym<0x5b)) return sym+0x20;		// "a" - "A"
	if(sym==0x5c) return 0x2f;						// "/" - "\"
	return sym;
}

// применять для сравнения имён файлов
bool b_stricmp(char *str1, char *str2)
{
	int i=0;
	while (1)
	{
		char ch1=b_chrt(str1[i]);
		char ch2=b_chrt(str2[i]);
		if((ch1==0)&&(ch2==0)) return false;		// equal
		if(ch1!=ch2) return true;					// not equal
		i++;
	}
}

/*
 =================
 Com_HashKey

 Returns hash key for string
 =================
*/
unsigned Com_HashKey (const char *string)
{
	unsigned	hashKey = 0;
	int			i;
	char		letter;

	for (i = 0; string[i]; i++)
	{
		letter = b_chrt(string[i]);
		hashKey = (hashKey + i) * 37 + letter;
	}

	return hashKey;
}

void	Cmd_RemoveCommand (char *cmd_name)
{
	unsigned		hash = Com_HashKey(cmd_name);
	cmd_function_t	*cmd, **back;

	back = &cmd_functions;
	while (1)
	{
		cmd = *back;
		if (!cmd)
		{
			Com_Printf ("^3Cmd_RemoveCommand: %s not added\n", cmd_name);
			return;
		}
		if (hash == cmd->hash)
		{
			if (!Q_strcasecmp(cmd_name, cmd->name))
			{
				*back = cmd->next;
				Z_Free (cmd);
				return;
			}
		}
		back = &cmd->next;
	}
}


/*
** GLimp_Shutdown
**
** This routine does all OS specific shutdown procedures for the OpenGL
** subsystem.  Under OpenGL this means NULLing out the current DC and
** HGLRC, deleting the rendering context, and releasing the DC acquired
** for the window.  The state structure is also nulled out.
**
*/
void GLimp_Shutdown()
{
	RestoreGamma();

	if ( wglMakeCurrent && !wglMakeCurrent( NULL, NULL ) )
		Com_Printf( "^1GLimp_Shutdown(): wglMakeCurrent failed\n");
	if ( glw_state.hGLRC )
	{
		if ( wglDeleteContext && !wglDeleteContext( glw_state.hGLRC ) )
			Com_Printf( "^1GLimp_Shutdown(): wglDeleteContext failed\n");
		glw_state.hGLRC = NULL;
	}
	if (glw_state.hDC)
	{
		if ( !ReleaseDC( glw_state.hWnd, glw_state.hDC ) )
			Com_Printf( "^1GLimp_Shutdown(): ReleaseDC failed\n" );
		glw_state.hDC   = NULL;
	}
	if (glw_state.hWnd)
	{
		DestroyWindow (	glw_state.hWnd );
		glw_state.hWnd = NULL;
	}

	UnregisterClass (WINDOW_CLASS_NAME, glw_state.hInstance);

	if ( gl_state.fullscreen )
	{
		ChangeDisplaySettings( 0, 0 );
		gl_state.fullscreen = false;
	}
}


void	GL_ImageList_f ()
{
	int		i;
	image_t	*image;
	int		texels;
///	byte	resident;

	Com_Printf("------------------\n");
	texels = 0;

	for (i=0, image=gltextures ; i<numgltextures ; i++, image++)
	{
		if (image->texnum <= 0)
			continue;
		Com_Printf("%4i: ", i);		/// Berserker: prints tex.slot numbers
		texels += image->upload_width*image->upload_height;
/*
		if(glAreTexturesResident(1, &image->texnum, &resident))
			Com_Printf("  ");
		else
			Com_Printf("! ");		// not resident!!!
*/
		switch (image->type)
		{
		case it_skin:
			Com_Printf("Skin ");
			break;
		case it_sprite:
			Com_Printf("Sprit");
			break;
		case it_wall:
			Com_Printf("Wall ");
			break;
		case it_light:
			Com_Printf("Light");
			break;
		case it_pic:
			Com_Printf("Pict ");
			break;
		case it_sky:
			Com_Printf("Sky  ");
			break;
		case it_bump:
			Com_Printf("Bump ");
			break;
		case it_fx:
			Com_Printf("FX   ");
			break;
		default:
			Com_Printf("     ");
			break;
		}

		if(image->has_alpha)
			Com_Printf(" A");
		else
			Com_Printf("  ");

		switch (image->filetype)
		{
		case file_wal:
			Com_Printf(" (WAL)");
			break;
		case file_pcx:
			Com_Printf(" (PCX)");
			break;
		case file_tga:
			Com_Printf(" (TGA)");
			break;
		case file_jpg:
			Com_Printf(" (JPG)");
			break;
		case file_png:
			Com_Printf(" (PNG)");
			break;
		case file_dds:
			Com_Printf(" (DDS)");
			break;
		default:
			Com_Printf("      ");
			break;
		}

		switch (image->fx)
		{
		case fx_chrome:
			Com_Printf(" chrome ");
			break;
		case fx_power:
			Com_Printf(" power  ");
			break;
		case fx_map:
			Com_Printf(" map    ");
			break;
		case fx_distort:
			Com_Printf(" distort");
			break;
		case fx_style:
			Com_Printf(" style  ");
			break;
		default:
			Com_Printf("        ");
			break;
		}

		Com_Printf(" %3ix%3i: %s\n", image->width, image->height, image->name); /// Berserker: подправил... было upload_***
	}
	Com_Printf("Total texel count (not counting mipmaps): %i\n", texels);
}


void FS_PureList_f ()
{
	int i;
	for (i=0 ; i<numpaks ; i++)
		Com_Printf("%s\n", paknames[i]);
}

void	FS_FileList_f ()
{
	if( Cmd_Argc() != 3 )
	{
eee:	Com_Printf("^3Usage: filelist [all|exist|missed] [all|pak|disk]\n");
		return;
	}

	bool all1 = (!Q_strcasecmp(Cmd_Argv(1), "all"));
	bool exist = (!Q_strcasecmp(Cmd_Argv(1), "exist"));
	bool missed = (!Q_strcasecmp(Cmd_Argv(1), "missed"));
	bool all2 = (!Q_strcasecmp(Cmd_Argv(2), "all"));
	bool pk2 = (!Q_strcasecmp(Cmd_Argv(2), "pak"));
	bool disk = (!Q_strcasecmp(Cmd_Argv(2), "disk"));

	if (!all1 && !exist && !missed)
		goto eee;

	if (!all2 && !pk2 && !disk)
		goto eee;

	Com_Printf("List of cached files:\n");

	int counter = 1;
	for (int i=0 ; i<fs_cache_number ; i++)
	{
		if(exist && fs_cache[i].len==-1)
			continue;
		if(missed && fs_cache[i].len!=-1)
			continue;
		if(pk2 && fs_cache[i].pak==-1)
			continue;
		if(disk && fs_cache[i].pak!=-1)
			continue;
		Com_Printf("%5i: ", counter);
		if(fs_cache[i].pak != -1)
			Com_Printf("pak ");
		else
			Com_Printf("    ");
		Com_Printf("%10i %s\n", fs_cache[i].len, fs_cache[i].name);
		counter++;
	}
	Com_Printf("Total cached files: %i\n", fs_cache_number);
}


void	MaterialList_f ()
{
	int			i;
	material_t	*material;
	char		matblend_add[4] = "ADD";
	char		matblend_mul[4] = "MUL";

	Com_Printf("------------------\n");

	for (i=0, material=materials ; i<nummaterials ; i++, material++)
		if(material->registration_sequence)
		{
			Com_Printf("%s:\n", material->name);
			if(material->decal && material->decal->name[0])
				Com_Printf(" Decal image:    %s [%s]\n", material->decal->name, (material->add_blend ? &matblend_add[0] : &matblend_mul[0]));
			if(material->debris && material->debris->name[0])
				Com_Printf(" Debris model:   %s\n", material->debris->name);
			if (material->footstep && material->footstep_name[0])
				Com_Printf(" Footstep sound: %s\n", material->footstep_name);
			if(material->hs_bullet && material->hs_bullet_name[0])
				Com_Printf(" BulletHit sound: %s\n", material->hs_bullet_name);
			if(material->hs_energy && material->hs_energy_name[0])
				Com_Printf(" EnergyHit sound: %s\n", material->hs_energy_name);
		}
}


void png_my_error( png_structp png_ptr, png_const_charp message )
{
	longjmp( png_ptr->jmpbuf, 1 );
}

void png_my_warning( png_structp png_ptr, png_const_charp message )
{
}

void imgfree()
{
	if(img_rowptr)
		Z_Free( img_rowptr );

	if(img_bmpbits)
		Z_Free( img_bmpbits );
}

bool imgalloc(int width, int height)
{
	BYTE *bp,**rp;
	long n;

	int img_rowbytes = ((DWORD)width * 24 + 31) / 32 * 4;		// 24 bits is hard coded for GL_RGB
	int img_imgbytes = img_rowbytes * height;
	img_rowptr = (BYTE**)Z_Malloc( (size_t)height * sizeof(BYTE *), false );
	img_bmpbits = (BYTE*)Z_Malloc( (size_t)img_imgbytes, false );

	if( img_rowptr==NULL || img_bmpbits==NULL )
	{
		imgfree();
		return false;
	}

	n  = height;
	rp = img_rowptr;
	bp = img_bmpbits;

	/* bottom-up */
	bp += img_imgbytes;
	while( --n>=0 )
	{
		/* fill zeros to padding bytes */
		((DWORD *)bp)[-1] = 0;
		bp -= img_rowbytes;
		*(rp++) = bp;
	}

	return true;
}

char	MP_checkname[MAX_OSPATH];
HANDLE	SShot_hThread = NULL;
void MP_ScreenShot_PNG(void *parms)
{
	int			j, size;
	FILE		*f;
	png_structp	png_ptr;
	png_infop	info_ptr;

	if (!vid_ignorehwgamma->value)
	{
		// Apply gamma
		size = vid.width * vid.height * 3;
		for (j = 0; j < size; j += 3)
		{
			img_bmpbits[j] = gammaRamp[img_bmpbits[j]] >> 8;
			img_bmpbits[j+1] = gammaRamp[img_bmpbits[j+1] + 256] >> 8;
			img_bmpbits[j+2] = gammaRamp[img_bmpbits[j+2] + 512] >> 8;
		}
	}

	png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, MP_checkname, png_my_error, png_my_warning );

	if( png_ptr )
	{
		info_ptr = png_create_info_struct( png_ptr );
		if( !info_ptr )
			png_destroy_write_struct( &png_ptr, NULL );
		else
		{
			if( setjmp(png_ptr->jmpbuf) )
				png_destroy_write_struct( &png_ptr, &info_ptr );	// If we get here, we had a problem reading the file
			else
			{
				f = FS_Fopen (MP_checkname, "wb");
				if (f)
				{
					png_init_io( png_ptr, f );
					png_set_compression_level( png_ptr, r_screenshot_png_quality->value );
					png_set_compression_mem_level( png_ptr, MAX_MEM_LEVEL );
					png_set_IHDR( png_ptr, info_ptr, vid.width, vid.height, 8,
						          PNG_COLOR_TYPE_RGB,
								  PNG_INTERLACE_NONE,
								  PNG_COMPRESSION_TYPE_DEFAULT,
								  PNG_FILTER_TYPE_DEFAULT );
					png_write_info( png_ptr, info_ptr );
					png_write_image( png_ptr, img_rowptr );
					png_write_end( png_ptr, info_ptr );
					png_destroy_write_struct( &png_ptr, &info_ptr );
					fclose(f);
					sshot_counter++;
				}
			}
		}
	}

	imgfree();
}

void GL_ScreenShot_PNG(char *checkname, int len_name, bool silent)
{
	int		i;
	FILE	*f;
	char	picname[16];

	if(r_screenshot_png_quality->value<0)	Cvar_SetValue("r_screenshot_png_quality", 0);
	if(r_screenshot_png_quality->value>9)	Cvar_SetValue("r_screenshot_png_quality", 9);

	/// ждём, если параллельный поток ещё не завершил свою работу...
	if (SShot_hThread)
	{
		WaitForSingleObject( SShot_hThread, INFINITE );
		SShot_hThread = NULL;
	}

	// find a file name to save it to
	for (i=sshot_counter ; i<10000 ; i++)
	{
		Com_sprintf (picname, sizeof(picname), "shot%04i.png", i);
		Com_sprintf (checkname, len_name, "%s/screenshots/%s", FS_Gamedir(), picname);
		f = FS_Fopen (checkname, "rb");
		if (!f)
			break;	// file doesn't exist
		fclose (f);
	}
	if (i == 10000)
	{
		Com_Printf("^1GL_ScreenShot_PNG: overflow\n");
		return;
	}

	sshot_counter = i;

	if(imgalloc(vid.width, vid.height))
	{
		glReadPixels (0, 0, vid.width, vid.height, GL_RGB, GL_UNSIGNED_BYTE, img_bmpbits);

		// run on other thread
		Com_sprintf(MP_checkname, sizeof(MP_checkname), checkname);
		SShot_hThread = (HANDLE)_beginthread(MP_ScreenShot_PNG, 0, NULL);
		if (SShot_hThread == (HANDLE)-1)		// if error
		{
			MP_ScreenShot_PNG(NULL);			// run in main thread
			SShot_hThread = NULL;
		}

		if(!silent)
			Com_Printf("Wrote %s [%i]\n", picname, (int)r_screenshot_png_quality->value);
	}
	else
		if(!silent)
			Com_Printf("^1GL_ScreenShot_PNG: out of memory\n"); 
}


void GL_ScreenShot_TGA(char *checkname, int len_name, bool silent)
{
	int		i, c, j, size, w, h;
	char	picname[16];
	FILE	*f;

	// find a file name to save it to
	for (i=sshot_counter ; i<10000 ; i++)
	{
		Com_sprintf (picname, sizeof(picname), "shot%04i.tga", i);
		Com_sprintf (checkname, len_name, "%s/screenshots/%s", FS_Gamedir(), picname);
		f = FS_Fopen (checkname, "rb");
		if (!f)
			break;	// file doesn't exist
		fclose (f);
	}
	if (i == 10000)
	{
		Com_Printf("^GL_ScreenShot_TGA: overflow\n");
		return;
	}

	sshot_counter = i;
	w = vid.width & 0xFFFFFFF0;		/// FIX for 1366x768
	h = vid.height;

#ifdef NDEBUG
	int bytesize = 3;
	int bitsize = bytesize * 8;
	int	format = GL_BGR;
#else
	int bytesize = 4;
	int bitsize = bytesize * 8;
	int	format = GL_BGRA;
#endif
	c = 18+w*h*bytesize;

	img_bmpbits = (byte*) Z_Malloc(c, true);
///	Memset (buffer, 0, 18);
	img_bmpbits[2] = 2;		// uncompressed type
	img_bmpbits[12] = w&255;
	img_bmpbits[13] = w>>8;
	img_bmpbits[14] = h&255;
	img_bmpbits[15] = h>>8;
	img_bmpbits[16] = bitsize;	// pixel size

	glReadPixels (0, 0, w, h, format, GL_UNSIGNED_BYTE, img_bmpbits+18);

	if (!vid_ignorehwgamma->value)
	{
		// Apply gamma
		size = w * h * bytesize + 18;
		for (j = 18; j < size; j += bytesize)
		{
			img_bmpbits[j] = gammaRamp[img_bmpbits[j] + 512] >> 8;
			img_bmpbits[j+1] = gammaRamp[img_bmpbits[j+1] + 256] >> 8;
			img_bmpbits[j+2] = gammaRamp[img_bmpbits[j+2]] >> 8;
		}
	}

	f = FS_Fopen (checkname, "wb");
	if (f)
	{
		fwrite (img_bmpbits, 1, c, f);
		fclose (f);
		if(!silent)
			Com_Printf("Wrote %s\n", picname);
		sshot_counter++;
	}

	Z_Free (img_bmpbits);
}

void WriteJPG( char *name, byte *buffer, int width, int height, int quality )
{
	struct jpeg_compress_struct		cinfo;
	struct jpeg_error_mgr			jerr;
	FILE							*f;
	JSAMPROW						s[1];
	int								offset, w3;

	f = FS_Fopen( name, "wb" );
	if (f)
	{
		// initialize the JPEG compression object
		cinfo.err = jpeg_std_error( &jerr );
		jpeg_create_compress( &cinfo );
		jpeg_stdio_dest( &cinfo, f );

		// setup JPEG parameters
		cinfo.image_width = width;
		cinfo.image_height = height;
		cinfo.in_color_space = JCS_RGB;
		cinfo.input_components = 3;

		jpeg_set_defaults( &cinfo );

		if( (quality > 100) || (quality <= 0) )
			quality = 85;

		jpeg_set_quality( &cinfo, quality, true );

		// start compression
		jpeg_start_compress( &cinfo, true );

		// feed scanline data
		w3 = cinfo.image_width * 3;
		offset = w3 * cinfo.image_height - w3;
		while( cinfo.next_scanline < cinfo.image_height )
		{
			s[0] = &buffer[offset - cinfo.next_scanline * w3];
			jpeg_write_scanlines( &cinfo, s, 1 );
		}

		// finish compression
		jpeg_finish_compress( &cinfo );
		jpeg_destroy_compress( &cinfo );

		fclose ( f );
	}
}

void MP_ScreenShot_JPG(void *parms)
{
	int	j, size, w, h;

	w = vid.width & 0xFFFFFFF0;		/// FIX for 1366x768
	h = vid.height;

	if (!vid_ignorehwgamma->value)
	{
		// Apply gamma
		size = w * h * 3;
		for (j = 0; j < size; j += 3)
		{
			img_bmpbits[j] = gammaRamp[img_bmpbits[j]] >> 8;
			img_bmpbits[j+1] = gammaRamp[img_bmpbits[j+1] + 256] >> 8;
			img_bmpbits[j+2] = gammaRamp[img_bmpbits[j+2] + 512] >> 8;
		}
	}

	WriteJPG( MP_checkname, img_bmpbits, w, h, r_screenshot_jpg_quality->value );

	Z_Free (img_bmpbits);
	sshot_counter++;
}

void GL_ScreenShot_JPG(char *checkname, int len_name, bool silent)
{
	int		i, w, h;
	char	picname[16];
	FILE	*f;

	if(r_screenshot_jpg_quality->value<=0)	Cvar_SetValue("r_screenshot_jpg_quality", 1);
	if(r_screenshot_jpg_quality->value>100)	Cvar_SetValue("r_screenshot_jpg_quality", 100);

	/// ждём, если параллельный поток ещё не завершил свою работу...
	if (SShot_hThread)
	{
		WaitForSingleObject( SShot_hThread, INFINITE );
		SShot_hThread = NULL;
	}

	// find a file name to save it to
	for (i=sshot_counter ; i<10000 ; i++)
	{
		Com_sprintf (picname, sizeof(picname), "shot%04i.jpg", i);
		Com_sprintf (checkname, len_name, "%s/screenshots/%s", FS_Gamedir(), picname);
		f = FS_Fopen (checkname, "rb");
		if (!f)
			break;	// file doesn't exist
		fclose (f);
	}
	if (i == 10000)
	{
		Com_Printf("^GL_ScreenShot_JPG: overflow\n");
		return;
	}

	sshot_counter = i;
	w = vid.width & 0xFFFFFFF0;		/// FIX for 1366x768
	h = vid.height;

	img_bmpbits = (byte*) Z_Malloc(w*h*3, true);
	glReadPixels (0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, img_bmpbits);

	// run on other thread
	Com_sprintf(MP_checkname, sizeof(MP_checkname), checkname);
	SShot_hThread = (HANDLE)_beginthread(MP_ScreenShot_JPG, 0, NULL);
	if (SShot_hThread == (HANDLE)-1)		// if error
	{
		MP_ScreenShot_JPG(NULL);			// run in main thread
		SShot_hThread = NULL;
	}
	if (!silent)
		Com_Printf( "Wrote %s [%i]\n", picname, (int)r_screenshot_jpg_quality->value );
}

int CeilPowerOfTwo(int val)
{
	int	tmp;
	for(tmp = 1; tmp < val; tmp <<= 1)
		;
	return tmp;
}

int FloorPowerOfTwo(int val)
{
	int	tmp = CeilPowerOfTwo(val);
	if (tmp > val)
		return (tmp >> 1);
	else
		return tmp;
}

typedef struct {
	const char	*suffix;
	float		angles[2];
	bool		flipX;
	bool		flipY;
	bool		flipDiagonal;
} envSide_t;

static envSide_t	r_envSides[6] = {
	{"px",   0,   0, true , true , true },
	{"nx",   0, 180, false, false, true },
	{"py",   0,  90, false, true , false},
	{"ny",   0, 270, true , false, false},
	{"pz", -90, 180, false, false, true },
	{"nz",  90, 180, false, false, true }
};

static void FlipImage (byte *in, byte *out, int width, int height, int samples, bool flipX, bool flipY, bool flipDiagonal)
{
	byte	*line, *l;
	int		xStride, yStride, xOffset, yOffset, x, y, i;

	if (flipX)
	{
		xStride = -samples;
		xOffset = (width - 1) * samples;
	}
	else
	{
		xStride = samples;
		xOffset = 0;
	}

	if (flipY)
	{
		yStride = -samples * width;
		yOffset = (height - 1) * width * samples;
	}
	else
	{
		yStride = samples * width;
		yOffset = 0;
	}

	if (flipDiagonal)
	{
		for (x = 0, line = in + xOffset; x < width; x++, line += xStride)
			for (y = 0, l = line + yOffset; y < height; y++, l += yStride, out += samples)
				for (i = 0; i < samples; i++)
					out[i] = l[i];
	}
	else
	{
		for (y = 0, line = in + yOffset; y < height; y++, line += yStride)
			for (x = 0, l = line + xOffset; x < width; x++, l += xStride, out += samples)
				for (i = 0; i < samples; i++)
					out[i] = l[i];
	}
}

void GL_EnvScreenShot_f ()
{
	int		i, j, envShotSize;
	char	envShotName[MAX_OSPATH], fileName[MAX_OSPATH];
	byte	tgaHeader[18], *pixels, *scaled;
	FILE	*f;

	if (cls.state != ca_active)
	{
		Com_Printf("^1You must be in a game.\n");
		return;
	}

	if ( Cmd_Argc() > 3 )
	{
		Com_Printf("^3Usage: %s [name]|<autoname> [size]\n", Cmd_Argv(0));
		return;
	}

	if (Cmd_Argc() == 1)
	{	// "envscreenshot"
		time_t		clock;
		struct tm	*localTime;
		time( &clock );
		localTime = localtime( &clock );
		Com_sprintf(envShotName, sizeof(envShotName), "%s_%4i%02i%02i_%i-%02i-%02i", mapname, localTime->tm_year+1900, localTime->tm_mon+1, localTime->tm_mday, localTime->tm_hour, localTime->tm_min, localTime->tm_sec);
		envShotSize = 256;
	}
	else if (Cmd_Argc() == 2)
	{ // "envscreenshot name"
		Q_strncpyz(envShotName, Cmd_Argv(1), sizeof(envShotName));
		envShotSize = 256;
	}
	else	// if (Cmd_Argc() == 3)
	{ // "envscreenshot name|%auto% size"
		if (!Q_strcasecmp (Cmd_Argv(1), "autoname"))
		{
			time_t		clock;
			struct tm	*localTime;
			time( &clock );
			localTime = localtime( &clock );
			Com_sprintf(envShotName, sizeof(envShotName), "%s_%4i%02i%02i_%i-%02i-%02i", mapname, localTime->tm_year+1900, localTime->tm_mon+1, localTime->tm_mday, localTime->tm_hour, localTime->tm_min, localTime->tm_sec);
		}
		else
			Q_strncpyz(envShotName, Cmd_Argv(1), sizeof(envShotName));
		envShotSize = atoi(Cmd_Argv(2));
		if (envShotSize < 16)
			envShotSize = 16;
		if (envShotSize > MAX_TEXTURE_SIZE)
			envShotSize = MAX_TEXTURE_SIZE;
		else if (envShotSize > vid.width || envShotSize > vid.height)
			envShotSize = min(vid.width, vid.height);
		envShotSize = FloorPowerOfTwo(envShotSize);
	}

	pixels = (byte*)Z_Malloc(envShotSize * envShotSize * 3, false);
	if (!pixels)
		return;

	scaled = (byte*)Z_Malloc(envShotSize * envShotSize * 3, false);
	if (!scaled)
		goto exit1;

	Memset(tgaHeader, 0, 18);
	tgaHeader[2] = 2;
	tgaHeader[12] = tgaHeader[14] = envShotSize & 255;
	tgaHeader[13] = tgaHeader[15] = envShotSize >> 8;
	tgaHeader[16] = 24;

	cl.refdef.x = 0;
	cl.refdef.y = vid.height - envShotSize;
	cl.refdef.width = envShotSize;
	cl.refdef.height = envShotSize;
	cl.refdef.fov_x = cl.refdef.fov_y = 90;
	cl.refdef.viewangles[2] = 0;
	cl.refdef.num_dlights = cl.refdef.num_particles = 0;	///  закомментировать, если хотите чтобы отображались длайты и партикли
	cl.refdef.rdflags = 0;

	for (i=0 ; i<cl.refdef.num_entities ; i++)
	{
		currententity = &cl.refdef.entities[i];
		// grab the weapon/player model
		if (currententity->flags & (RF_WEAPONMODEL | RF_VIEWERMODEL))
			currententity->model = NULL;	// HACK: remove weapon model
	}

	float old1 = r_playershadow->value;
	float old2 = r_drawsun->value;
	float old3 = r_bloom->value;
	float old4 = r_distort->value;
	float old5 = r_showtris->value;
	float old6 = r_drawentities->value;

	r_playershadow->value = 0;
	r_drawsun->value = 0;
	r_bloom->value = 0;
	r_distort->value = 0;
	r_showtris->value = 0;
	r_drawentities->value = 0;

	for (i = 0; i < 6; i++)
	{
		// отрисуем сцену 3 раза (чтоб обойти тройную буферизацию)
		for (j = 0; j < 3; j++)
		{	// render the frame
			BeginFrame();
			cl.refdef.viewangles[0] = r_envSides[i].angles[0];
			cl.refdef.viewangles[1] = r_envSides[i].angles[1];
			R_RenderView (&cl.refdef, false);
			End_Frame();
		}

		// grab the frame
		glReadPixels(0, 0, envShotSize, envShotSize, GL_BGR, GL_UNSIGNED_BYTE, pixels);

		// flip shot
		if (r_envSides[i].flipX || r_envSides[i].flipY || r_envSides[i].flipDiagonal)
			FlipImage(pixels, scaled, envShotSize, envShotSize, 3, r_envSides[i].flipX, r_envSides[i].flipY, r_envSides[i].flipDiagonal);

		// write to file
		Com_sprintf(fileName, sizeof(fileName), "%s/env/%s_%s.tga", FS_Gamedir(), envShotName, r_envSides[i].suffix);
		FS_CreatePath (fileName);	// неоптимально, но пофиг, не критично по скорости...
		f = FS_Fopen (fileName, "wb");
		if (f)
		{
			fwrite (tgaHeader, 1, sizeof(tgaHeader), f);
			if (!r_envSides[i].flipX && !r_envSides[i].flipY && !r_envSides[i].flipDiagonal)
				fwrite (pixels, 1, envShotSize * envShotSize * 3, f);
			else
				fwrite (scaled, 1, envShotSize * envShotSize * 3, f);
			fclose (f);
		}
		else
		{
			Com_Printf("^1Can't open %s for write.\n", fileName);
			goto exit;
		}
	}

	Com_Printf("Wrote env/%s.tga [%i]\n", envShotName, envShotSize);
exit:
	R_SetGL2D();
	Z_Free(scaled);

	r_playershadow->value = old1;
	r_drawsun->value = old2;
	r_bloom->value = old3;
	r_distort->value = old4;
	r_showtris->value = old5;
	r_drawentities->value = old6;

exit1:
	Z_Free(pixels);
}


void GL_ScreenShot_f ()
{
	char	checkname[MAX_OSPATH];

	if( Cmd_Argc() > 3 )
	{
		Com_Printf("^3Usage: screenshot [tga|png|jpg] [silent]\n");
		return;
	}

	if (sshot_counter == 10000)
	{
		Com_Printf("^1\"screenshots\" directory is full.\n");
		return;
 	}

	// create the screenshots directory if it doesn't exist
	Com_sprintf (checkname, sizeof(checkname), "%s/screenshots", FS_Gamedir());
	_mkdir (checkname);

	if (!Q_strcasecmp(Cmd_Argv(1), "png"))
		GL_ScreenShot_PNG(checkname, sizeof(checkname), !Q_strcasecmp(Cmd_Argv(2), "silent"));
	else if (!Q_strcasecmp(Cmd_Argv(1), "tga"))
		GL_ScreenShot_TGA(checkname, sizeof(checkname), !Q_strcasecmp(Cmd_Argv(2), "silent"));
	else
		GL_ScreenShot_JPG(checkname, sizeof(checkname), !Q_strcasecmp(Cmd_Argv(2), "silent"));
} 


void SaveProg(char *prg, char *file)
{
	FILE *	f;
	char	path[MAX_QPATH];

	Com_sprintf (path, sizeof(path), "%s/glProgs/%s", FS_Gamedir(), file);
	Com_Printf("Creating %s", path);
	FS_CreatePath(path);
	f = FS_Fopen (path, "w");
	if (f)
	{
		fprintf(f, prg);
		fclose(f);
	}
	else
		Com_Printf("^1\rError creating %s", path);
	Com_Printf("\n");
}


void GL_GLProgs_f()
{
	SaveProg(fragmentprogram_nofilter6_detail, "nolightfilter6_detail.fp");
	SaveProg(fragmentprogram_filter6_detail, "lightfilter6_detail.fp");
	SaveProg(fragmentprogram_nofilter, "nolightfilter.fp");
	SaveProg(fragmentprogram_filter, "lightfilter6.fp");
	SaveProg(fragmentprogram_filter4, "lightfilter4.fp");
	SaveProg(arbdistort, "distort.fp");
	SaveProg(fragmentprogram_ambient, "ambient.fp");
	SaveProg(vertexprogram_nolightfilter, "nolightfilter.vp");
	SaveProg(vertexprogram_lightfilter6, "lightfilter6.vp");
	SaveProg(vertexprogram_lightfilter4, "lightfilter4.vp");
	SaveProg(vertexprogram_nolightfilter_parallax, "nolightfilter_parallax.vp");
	SaveProg(vertexprogram_lightfilter6_parallax, "lightfilter6_parallax.vp");
	SaveProg(fragmentprogram_nofilter_parallax, "nolightfilter_parallax.fp");
	SaveProg(fragmentprogram_filter_parallax, "lightfilter6_parallax.fp");
	SaveProg(vertexprogram_ambient_parallax, "ambient_parallax.vp");
	SaveProg(fragmentprogram_ambient_parallax, "ambient_parallax.fp");
	SaveProg(vertexprogram_nobump_parallax, "nobump_parallax.vp");
	SaveProg(fragmentprogram_nobump_parallax, "nobump_parallax.fp");
}


void GL_Strings_f()
{
	Com_Printf("GL_VENDOR: %s\n", gl_config.vendor_string );
	Com_Printf("GL_RENDERER: %s\n", gl_config.renderer_string );
	Com_Printf("GL_VERSION: %s\n", gl_config.version_string );
	Com_Printf("GL_EXTENSIONS: %s\n", gl_config.extensions_string );
}


void Mod_Modellist_f ()
{
	int		i;
	model_t	*mod;
	int		total;

	total = 0;
	Com_Printf("Loaded models:\n");
	for (i=0, mod=mod_known ; i < mod_numknown ; i++, mod++)
	{
		if (!mod->name[0])
			continue;
		Com_Printf("%4i: ", i);		/// Berserker: prints mod.slot numbers
		Com_Printf("%8i : %s",mod->extradatasize, mod->name);
		if ((mod->type == mod_alias || mod->type == mod_alias_md3) && mod->scale != 1)
		{
			if (mod->invert)
				Com_Printf(" x%4.2f Inverted", mod->scale);
			else
				Com_Printf(" x%4.2f", mod->scale);
		}
		Com_Printf("\n");
		total += mod->extradatasize;
	}
	Com_Printf("Total resident: %i\n", total);
}


void R_Register ()
{
///	r_ext_draw_range_elements = Cvar_Get ("r_ext_draw_range_elements", "1", CVAR_ARCHIVE);
	r_speeds = Cvar_Get ("r_speeds", "0", 0);
	r_speeds->help = "show some performance info.";
	r_sprite_expl = Cvar_Get ("r_sprite_expl", "1", CVAR_ARCHIVE);
	r_sprite_expl->help = "prefer sprites instead models for explosions.";
///	r_lerpmodels = Cvar_Get ("r_lerpmodels", "1", 0);
///	r_polyblend = Cvar_Get ("r_polyblend", "1", 0);
	r_shadows = Cvar_Get ("r_shadows", "2", CVAR_ARCHIVE );
	r_teshadows = Cvar_Get ("r_teshadows", "1", CVAR_ARCHIVE );
	r_teshadows->help = "temp entities casts shadows.";
	dl_shadows = Cvar_Get ("dl_shadows", "1", CVAR_ARCHIVE );
	r_teshadows->help = "shadows from dynamic lights.";
	r_worldshadows = Cvar_Get ("r_worldshadows", "1", CVAR_ARCHIVE );
	r_worldshadows->help = "world can cast shadows.";
	r_playershadow = Cvar_Get ("r_playershadow", "0", CVAR_ARCHIVE );
	r_playershadow->help = "player can cast shadow.";
	r_parallax = Cvar_Get ("r_parallax", "1", CVAR_ARCHIVE|CVAR_VID_LATCH );							// only for ARB6
	r_parallax_scale = Cvar_Get("r_parallax_scale", "2", CVAR_ARCHIVE | CVAR_VID_LATCH);				// only for ARB6
	r_detailed_bump = Cvar_Get ("r_detailed_bump", "1", CVAR_ARCHIVE|CVAR_VID_LATCH );					// only for ARB6
	r_detailed_bump_default = Cvar_Get ("r_detailed_bump_default", "", CVAR_ARCHIVE|CVAR_VID_LATCH );	// only for ARB6
	r_noselfshadows = Cvar_Get ("r_noselfshadows", "2", CVAR_ARCHIVE );
	r_editor = Cvar_Get ("r_editor", "0", 0 );
	r_editor->help = "switch on in-game editor.";
	r_simple = Cvar_Get ("r_simple", "0", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_simple->help = "simple render like quake2.";
	demofov = Cvar_Get ("demofov", "0", CVAR_ARCHIVE);
	demofov->help = "FOV value which uses in demos. 0 - no override FOV.";
	r_crosshair_image = Cvar_Get ("r_crosshair_image", "", CVAR_ARCHIVE);
	r_crosshair_image->help = "allow to override the standart crosshair.";
	r_editor_layoutstring = Cvar_Get ("r_editor_layoutstring", "", CVAR_ARCHIVE);
	r_editor_layoutstring->help = "path to file with layoutstring for editor.";
	r_personal_gibs = Cvar_Get ("r_personal_gibs", "1", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_md3 = Cvar_Get ("r_md3", "1", CVAR_NOSET );
	r_md3->help = "allow to use models md3 instead md2.";
///	r_iqm = Cvar_Get("r_iqm", "1", CVAR_NOSET);
///	r_iqm->help = "allow to use models iqm instead md2.";
	r_sp3 = Cvar_Get ("r_sp3", "1", CVAR_VID_LATCH );
	r_sp3->help = "allow to use sprites sp3 instead sp2.";
	r_bloom = Cvar_Get ("r_bloom", "2", CVAR_ARCHIVE );		// 0 - no bloom, 1/2/3: 3-brightnest bloom, 1-darker bloom
	r_bloom->help = "fullscreen bloom effect: 0 - disable, from 1 to 3 - increasing bloom intensity.";
	r_showbloom = Cvar_Get ("r_showbloom", "0", 0 );
	r_showbloom->help = "show bloom map (for developer).";
	r_modlights = Cvar_Get ("r_modlights", "3", CVAR_ARCHIVE );
	r_modlights->help = "enables the lights linked to models.";
	r_modemits = Cvar_Get ("r_modemits", "1", CVAR_ARCHIVE );
	r_modemits->help = "enables the emitters linked to models.";
	r_noshells = Cvar_Get ("r_noshells", "0", 0 );
	r_noshells->help = "disables the model's shells.";
	r_offsetFactor = Cvar_Get( "r_offsetfactor", "-0.1", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_offsetUnits = Cvar_Get( "r_offsetunits", "-2", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_offset = Cvar_Get( "r_offset", "1", CVAR_ARCHIVE );
	r_offset->help = "enables the GL_POLYGON_OFFSET_FILL.";
	r_override = Cvar_Get ("r_override", "1", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_override->help = "enable using the textures from 'overrides' folder.";
	r_smoothangle = Cvar_Get ("r_smoothangle", "45", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_smoothangle->help = "maximal angle between planes for smoothing TBN vectors.";
	smooth_cosine = r_smoothangle->value;
	if(smooth_cosine<0.1 || smooth_cosine>=59.9)
	{
		smooth_cosine=45;
		Com_Printf("^3Warning:^7 r_smoothangle must be in [0.1 - 59.9]; Forced to %s\n", r_smoothangle->defaultString);
	}
	smooth_cosine = cos(DEG2RAD(smooth_cosine));
	scr_3dhud = Cvar_Get ("scr_3dhud", "1", CVAR_ARCHIVE );
	scr_3dhud->help = "use 3d models in hud.";
	scr_3dhud_intensity = Cvar_Get ("scr_3dhud_intensity", "0.8", CVAR_ARCHIVE );
	scr_3dhud_intensity->help = "light intensity for hud models.";
	scr_bighud = Cvar_Get ("scr_bighud", "1", CVAR_ARCHIVE );
	scr_bighud->help = "screen scaled hud.";
///	r_predator = Cvar_Get ("r_predator", "0", CVAR_ARCHIVE|CVAR_VID_LATCH );	// changed to sv_predator
///	r_predator_r = Cvar_Get ("r_predator_r", "0.9", CVAR_ARCHIVE );
///	r_predator_g = Cvar_Get ("r_predator_g", "0.9", CVAR_ARCHIVE );
///	r_predator_b = Cvar_Get ("r_predator_b", "0.9", CVAR_ARCHIVE );
	r_texfx = Cvar_Get ("r_texfx", "1", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_texfx->help = "enables the texture's effects.";
	r_distort = Cvar_Get ("r_distort", "1", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_distort->help = "enables the image deformations.";
	r_distort_distance = Cvar_Get ("r_distort_distance", "256", CVAR_ARCHIVE);
	r_distort_distance->help = "maximal distance for doing 'weapon & distort' workaround.";
	r_itemfx = Cvar_Get ("r_itemfx", "0", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_itemfx->help = "override item's effects: 1 - chrome, 2 - power, 3 - shell.";
	r_vbo = Cvar_Get ("r_vbo", "1", CVAR_VID_LATCH );
	r_vbo->help = "enables the Vertex Buffer Objects.";
	r_vp = Cvar_Get ("r_vp", "1", CVAR_VID_LATCH );
	r_vp->help = "enables the vertex programs (for ARB only).";
	r_occlusion = Cvar_Get ("r_occlusion", "1", CVAR_VID_LATCH );
	r_occlusion->help = "enables the hardware occlusion test.";
	r_occlusion_scale = Cvar_Get ("r_occlusion_scale", "0.8", CVAR_ARCHIVE );
	r_occlusion_scale->help = "light radius scale for occlusion test.";
	r_normcube_size = Cvar_Get ("r_normcube_size", "32", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_normcube_size->help = "normalization cubemap size.";
	r_diffuse_modifier = Cvar_Get ("r_diffuse_modifier", "2", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_specular_modifier = Cvar_Get ("r_specular_modifier", "1", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_diffuse_compression = Cvar_Get ("r_diffuse_compression", "0", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_bump_compression = Cvar_Get ("r_bump_compression", "0", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_anisotropy = Cvar_Get ("r_anisotropy", "0", CVAR_ARCHIVE|CVAR_VID_LATCH );
	if(r_anisotropy->value < 2)
		Cvar_SetValue( "r_anisotropy", 0 );
///	r_texture_lod = Cvar_Get ("r_texture_lod", "0", CVAR_ARCHIVE );
///	if(r_texture_lod->value < R_TEXTURE_MAX_LOD)
///		Cvar_SetValue( "r_texture_lod", R_TEXTURE_MAX_LOD );
///	else if(r_texture_lod->value > 0)
///		Cvar_SetValue( "r_texture_lod", 0 );

	mtr_decal = Cvar_Get ("mtr_decal", "1", CVAR_ARCHIVE|CVAR_VID_LATCH );
	mtr_decal->help = "enables the 'decal' token in .mtr files.";
	mtr_footstep = Cvar_Get ("mtr_footstep", "1", CVAR_ARCHIVE|CVAR_VID_LATCH );
	mtr_footstep->help = "enables the 'footstep' token in .mtr files.";
	mtr_debris = Cvar_Get ("mtr_debris", "1", CVAR_ARCHIVE|CVAR_VID_LATCH );
	mtr_debris->help = "enables the 'debris' token in .mtr files.";
	mtr_hitsound = Cvar_Get ("mtr_hitsound", "1", CVAR_ARCHIVE|CVAR_VID_LATCH );
	mtr_hitsound->help = "enables the 'bullethit'/'energyhit' tokens in .mtr files.";

///	r_bitdepth = Cvar_Get( "r_bitdepth", "0", CVAR_ARCHIVE );
	r_mode = Cvar_Get( "r_mode", "3", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_mode->help = "video mode.";
	r_shader = Cvar_Get( "r_shader", "-1", CVAR_ARCHIVE|CVAR_VID_LATCH );	/// По умолчанию ставим некорректное значение, чтобы движок при первом запуске выбрал наилучший рендер.
	r_shader->help = "render path.";
	r_flashblend = Cvar_Get ("r_flashblend", "0", CVAR_ARCHIVE);
	r_bloodlifetime = Cvar_Get( "r_bloodLifeTime", "1", CVAR_ARCHIVE );
	r_bloodlifetime->help = "lifetime scale for blood decals.";
	r_bulletMarkLifeTime = Cvar_Get( "r_bulletMarkLifeTime", "1", CVAR_ARCHIVE );
	r_bulletMarkLifeTime->help = "lifetime scale for bullet hit decals.";
	r_laserMarkLifeTime = Cvar_Get( "r_laserMarkLifeTime", "1", CVAR_ARCHIVE );
	r_laserMarkLifeTime->help = "lifetime scale for laser hit decals.";
	r_railMarkLifeTime = Cvar_Get( "r_railMarkLifeTime", "1", CVAR_ARCHIVE );
	r_railMarkLifeTime->help = "lifetime scale for rail hit decals.";
	r_explosionMarkLifeTime = Cvar_Get( "r_explosionMarkLifeTime", "1", CVAR_ARCHIVE );
	r_explosionMarkLifeTime->help = "lifetime scale for explosion decals.";
	r_bfgMarkLifeTime = Cvar_Get( "r_bfgMarkLifeTime", "1", CVAR_ARCHIVE );
	r_bfgMarkLifeTime->help = "lifetime scale for BFG hit decals.";

///	r_finish = Cvar_Get( "r_finish", "0", CVAR_ARCHIVE );
	r_round_down = Cvar_Get ("r_round_down", "1", 0);
	r_picmip = Cvar_Get ("r_picmip", "0", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_picmip_bump = Cvar_Get ("r_picmip_bump", "0", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_showtris = Cvar_Get ("r_showtris", "0", 0);
	r_showEFXzones = Cvar_Get("r_showEFXzones", "0", 0);
	r_showtexture = Cvar_Get ("r_showtexture", "", 0);
	r_showtexture->help = "same as r_showtexture, but for certain texture.";
	hash_showtexture = Com_HashKey(r_showtexture->string);
	null_hashkey = Com_HashKey("");	// NULL
	r_drawsun = Cvar_Get ("r_drawsun", "2", CVAR_ARCHIVE);
	r_drawsun->help = "0 - no sun, 1 - sun, 2 - sun with flares.";
	r_flares = Cvar_Get ("r_flares", "4", CVAR_ARCHIVE);
	r_flares->help = "number of flare passes.";
	r_flares_segs = Cvar_Get ("r_flares_segs", "10", CVAR_ARCHIVE);
	r_flares_segs->help = "number of flare's segments.";
	r_flares_intensity = Cvar_Get ("r_flares_intensity", "0.25", CVAR_ARCHIVE);
	r_flares_intensity->help = "flare's intensity.";
///	r_DepthBoundsTest = Cvar_Get ("r_DepthBoundsTest", "1", CVAR_ARCHIVE);
	r_mirrors = Cvar_Get ("r_mirrors", "1", CVAR_ARCHIVE);
	r_mirrors->help = "enables the mirrors.";
///	r_mirror_clip = Cvar_Get ("r_mirror_clip", "1", CVAR_ARCHIVE);	// он должен работать всегда!
	r_mirror_models = Cvar_Get ("r_mirror_models", "1", CVAR_ARCHIVE);
	r_mirror_models->help = "draw models in mirrors.";
///	r_mirror_trans = Cvar_Get ("r_mirror_trans", "0.5", CVAR_ARCHIVE);
	r_mirror_frameskip = Cvar_Get ("r_mirror_frameskip", "-25", CVAR_ARCHIVE);
	r_mirror_frameskip->help = "mirror's render speed: for <0: frames per second / for >0: skip # frames.";
	r_mirror_size = Cvar_Get ("r_mirror_size", "512", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_mirror_size->help = "mirror's texture size.";
	r_mirror_distance = Cvar_Get ("r_mirror_distance", "512", CVAR_ARCHIVE);
	r_mirror_distance->help = "maximal distance of mirror's draw.";
	r_mirror_blend = Cvar_Get ("r_mirror_blend", "0.75", CVAR_ARCHIVE);
	r_mirror_blend->help = "%s * r_mirror_distance = distance when mirror will be fade.";
	r_mirror_shadows = Cvar_Get ("r_mirror_shadows", "2", CVAR_ARCHIVE );
	r_mirror_shadows->help = "enables the shadows in mirror: 1 - from world only, 2 - all shadows.";
	r_caustics = Cvar_Get ("r_caustics", "1", CVAR_ARCHIVE);
	r_caustics->help = "underwater caustics.";
	r_specularscale = Cvar_Get ("r_specularscale", "1", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_specularscale->help = "scale for specularmap.";
	r_autobump = Cvar_Get ("r_autobump", "0", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_autobump->help = "enables the automatic bumpmap generation from colormap.";
	r_bumpscale = Cvar_Get ("r_bumpscale", "8", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_bumpscale->help = "scale for auto-bumpmap.";
	r_attenpower = Cvar_Get ("r_attenpower", "1", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_attenpower->help = "pow parameter for attenuation 3d-texture generation.";
///	r_cull = Cvar_Get ("r_cull", "1", 0);
	r_texturemode = Cvar_Get( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_texturealphamode = Cvar_Get( "r_texturealphamode", "default", CVAR_ARCHIVE|CVAR_VID_LATCH );
	r_texturesolidmode = Cvar_Get( "r_texturesolidmode", "default", CVAR_ARCHIVE|CVAR_VID_LATCH );
///	r_novis = Cvar_Get ("r_novis", "0", 0);
	r_lockpvs = Cvar_Get( "r_lockpvs", "0", 0 );
	r_lockpvs->help = "lock PVS (for developer).";
///	r_nocull = Cvar_Get ("r_nocull", "0", 0);
	r_drawentities = Cvar_Get ("r_drawentities", "1", 0);
	r_maxDistance = Cvar_Get ("r_maxDistance", "512", CVAR_ARCHIVE);
	r_maxDistance->help = "maximal distance for tiny models draw.";
	r_brassTimeScale = Cvar_Get ("r_brassTimeScale", "1", CVAR_ARCHIVE);
	r_brassTimeScale->help = "scale for brass shells lifetime.";
	r_debrisTimeScale = Cvar_Get ("r_debrisTimeScale", "1", CVAR_ARCHIVE);
	r_debrisTimeScale->help = "scale for debris lifetime.";
////	gl_modulate = Cvar_Get ("gl_modulate", "1", CVAR_ARCHIVE );
	r_nolights = Cvar_Get("r_nolights", "0", 0);
	r_nolights->help = "disable all static lights.";
///	calc_lni =  Cvar_Get("calc_lni", "0", 0);
	r_fpsThreshold = Cvar_Get("r_fpsThreshold", "15", CVAR_ARCHIVE);
	r_fpsThreshold->help = "FPS threshold for client's models and decals cleaning.";
	r_decals = Cvar_Get("r_decals", "1", CVAR_ARCHIVE);
	r_decals->help = "enable decal system.";
	r_faststencil = Cvar_Get("r_faststencil", "1", CVAR_ARCHIVE);
	r_faststencil->help = "enable one-pass stencil marking for shadow volumes.";
	rfx_mask = Cvar_Get("rfx_mask", "1", CVAR_ARCHIVE);
	rfx_mask->help = "enables the fullscreen mask effect (single/coop).";
	rfx_mask_mp = Cvar_Get("rfx_mask_mp", "0", CVAR_ARCHIVE);
	rfx_mask_mp->help = "enables the fullscreen mask effect (deathmatch).";
	rfx_burning = Cvar_Get("rfx_burning", "1", CVAR_ARCHIVE);
	rfx_burning->help = "enables the fullscreen burning effect.";
	rfx_pain = Cvar_Get("rfx_pain", "1", CVAR_ARCHIVE);
	rfx_pain->help = "enables the fullscreen pain effect.";
	rfx_drowning = Cvar_Get("rfx_drowning", "1", CVAR_ARCHIVE);
	rfx_drowning->help = "enables the fullscreen drowning effect.";
	rfx_underwater = Cvar_Get("rfx_underwater", "1", CVAR_ARCHIVE);
	rfx_underwater->help = "enables the fullscreen underwater effect.";

	r_ext_pointparameters = Cvar_Get ("r_ext_pointparameters", "0", 0);
	r_particle_min_size = Cvar_Get( "r_particle_min_size", "2", CVAR_ARCHIVE );
	r_particle_max_size = Cvar_Get( "r_particle_max_size", "40", CVAR_ARCHIVE );
	r_particle_size = Cvar_Get( "r_particle_size", "40", CVAR_ARCHIVE );
	r_particle_att_a = Cvar_Get( "r_particle_att_a", "0.01", CVAR_ARCHIVE );
	r_particle_att_b = Cvar_Get( "r_particle_att_b", "0.0", CVAR_ARCHIVE );
	r_particle_att_c = Cvar_Get( "r_particle_att_c", "0.01", CVAR_ARCHIVE );

	r_drawbuffer = Cvar_Get( "r_drawbuffer", "GL_BACK", 0 );
	r_swapinterval = Cvar_Get( "r_swapinterval", "0", CVAR_ARCHIVE );

	gl_notestdriver = Cvar_Get( "gl_notestdriver", "0", 0 );
	gl_notestdriver->help = "disable the test of GL driver.";

	r_fullscreen = Cvar_Get( "r_fullscreen", "0", CVAR_ARCHIVE|CVAR_VID_LATCH );
	vid_hz = Cvar_Get( "vid_hz", "0", CVAR_ARCHIVE|CVAR_VID_LATCH );

// Kirk Barnes Anti-Aliasing support
	r_multiSamples = Cvar_Get("r_multiSamples", "1", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_multiSamples->help = "0,1 - disable Anti-Aliasing; 2,4,8,16... - enable AA, more samples better quality.";
	r_nvMultisampleFilterHint = Cvar_Get ("r_nvMultisampleFilterHint", "0", CVAR_ARCHIVE);		// 0 - fastest, 1 - nicest
	r_nvMultisampleFilterHint->help = "0 - fastest, 1 - nicest filtering.";

// добавляя команды, удаляйте в R_Shutdown !!!!!
	Cmd_AddCommand( "imagelist", GL_ImageList_f );
	Cmd_AddCommand( "materiallist", MaterialList_f );
	Cmd_AddCommand( "screenshot", GL_ScreenShot_f );
	Cmd_AddCommand( "envscreenshot", GL_EnvScreenShot_f );
	Cmd_AddCommand( "modellist", Mod_Modellist_f );
	Cmd_AddCommand( "gl_strings", GL_Strings_f );
	Cmd_AddCommand( "gl_progs", GL_GLProgs_f );
}


/*
** GLimp_Init
**
** This routine is responsible for initializing the OS specific portions
** of OpenGL.  Under Win32 this means dealing with the pixelformats and
** doing the wgl interface stuff.
*/
bool GLimp_Init( void *hinstance, void *wndproc )
{
#define OSR2_BUILD_NUMBER 1111

	OSVERSIONINFO	vinfo;

	vinfo.dwOSVersionInfoSize = sizeof(vinfo);

	glw_state.allowdisplaydepthchange = false;

	if ( GetVersionEx( &vinfo) )
	{
		if ( vinfo.dwMajorVersion > 4 )
		{
			glw_state.allowdisplaydepthchange = true;
		}
		else if ( vinfo.dwMajorVersion == 4 )
		{
			if ( vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT )
			{
				glw_state.allowdisplaydepthchange = true;
			}
			else if ( vinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS )
			{
				if ( LOWORD( vinfo.dwBuildNumber ) >= OSR2_BUILD_NUMBER )
				{
					glw_state.allowdisplaydepthchange = true;
				}
			}
		}
	}
	else
	{
		Com_Printf("^1GLimp_Init() - GetVersionEx failed\n" );
		return false;
	}

	glw_state.hInstance = ( HINSTANCE ) hinstance;
	glw_state.wndproc = wndproc;

	return true;
}


bool VerifyDriver()
{
	char buffer[4096];
	strcpy( &buffer[0], (char*)glGetString( GL_RENDERER ) );
	strlwr( buffer );
	return (bool)strcmp( buffer, "GDI Generic" );
}


/*
=============
Multisampling
from Kirk Barnes
adapted by Berserker
=============
*/
#define	OPENGL_CLASS		"OpenGLDummyPFDWindow"

// don't do anything
static LRESULT CALLBACK DummyOpenGLProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
  return DefWindowProc(hWnd,msg,wParam,lParam);
}

// Registers the window classes
bool RegisterOpenGLWindow(HINSTANCE hInst)
{
	WNDCLASSEX wcex;

	// Initialize our Window class
	wcex.cbSize = sizeof(wcex);
	if (GetClassInfoEx(hInst, OPENGL_CLASS, &wcex))
		return true;

	// register main one
	ZeroMemory(&wcex,sizeof(wcex));

	// now the dummy one
	wcex.cbSize			= sizeof(wcex);
	wcex.style			= CS_OWNDC;
	wcex.cbWndExtra		= 0; // space for our pointer 
	wcex.lpfnWndProc	= DummyOpenGLProc;
	wcex.hbrBackground	= NULL;
	wcex.hInstance		= hInst;
	wcex.hCursor		= LoadCursor(NULL,IDC_ARROW);
	wcex.lpszClassName	= OPENGL_CLASS;

	return RegisterClassEx(&wcex);
}

bool GLimp_InitGL()
{
	int		i, nSamples = max((int)r_multiSamples->value, 1);
	char	c, line[128], *string;

	// Get a DC for the specified window
	if ( glw_state.hDC != NULL )
		Com_Printf("^3GLimp_InitGL() - non-NULL DC exists\n" );

	RegisterOpenGLWindow (glw_state.hInstance);

	{
		int			iAttributes[30];
		float		fAttributes[] = {0, 0};
		int			iResults[30];
		int			pixelFormat;
		unsigned	numFormats;
		bool		status;

		PIXELFORMATDESCRIPTOR temppfd = { 
			sizeof(PIXELFORMATDESCRIPTOR),	// size of this pfd
			1,							// version number
			PFD_DRAW_TO_WINDOW |		// support window
///			PFD_GENERIC_ACCELERATED |	// accelerated			mini client driver ?
			PFD_SUPPORT_OPENGL |		// support OpenGL
			PFD_DOUBLEBUFFER,			// double buffered
			PFD_TYPE_RGBA,				// RGBA type
			32,							// desktop color depth
			0, 0, 0, 0, 0, 0,			// color bits ignored
			8,							// 8 bit destination alpha
			0,							// shift bit ignored
			0,							// no accumulation buffer
			0, 0, 0, 0,					// accum bits ignored
			24,							// 24-bit z-buffer	
			8,							// 8-bit stencil buffer
			0,							// no auxiliary buffer
			PFD_MAIN_PLANE,				// main layer
			0,							// reserved
			0, 0, 0						// layer masks ignored
		};
		HGLRC hGLRC;

		HWND temphwnd = CreateWindowEx(0L,OPENGL_CLASS,"Berserker@Quake2 OpenGL PFD Detection Window",WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,0,0,1,1,glw_state.hWnd,0,glw_state.hInstance,NULL);
		HDC hDC = GetDC (temphwnd);

		// Set up OpenGL
		pixelFormat = ChoosePixelFormat(hDC, &temppfd);
		if (!pixelFormat)
		{
			Com_Printf ("^1GLimp_InitGL() - ChoosePixelFormat (%dc/%da/%dd/%ds) failed. ErrorCode %d.\n", (int)temppfd.cColorBits, (int)temppfd.cAlphaBits, (int)temppfd.cColorBits, (int)temppfd.cStencilBits, GetLastError());
			goto fail;
		}

		if (SetPixelFormat(hDC, pixelFormat, &temppfd) == false)
		{
			Com_Printf ("^1GLimp_InitGL() - SetPixelFormat (%d) failed. ErrorCode %d.\n", pixelFormat, GetLastError());
			goto fail;
		}

		// Create a rendering context
		hGLRC = wglCreateContext(hDC);
		if (!hGLRC)
		{
			Com_Printf ("^1GLimp_InitGL() - wglCreateContext failed\n");
			goto fail;
		}

		// Make the rendering context current
		if (!(wglMakeCurrent(hDC, hGLRC)))
		{
			Com_Printf ("^1GLimp_InitGL() - wglMakeCurrent failed\n");
			goto fail;
		}

		if ( !VerifyDriver() )
		{
			Com_Printf("^1GLimp_InitGL() - no hardware accelerated pixelformats matching your system\n" );
			goto fail;
		}

		wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
		if (!wglGetExtensionsStringARB) 
		{
			Com_Printf ("^1X..failed to import wglGetExtensionsStringARB function\n");
			goto fail;
		}

		glw_state.wglExtsString = wglGetExtensionsStringARB (hDC);
		if (!glw_state.wglExtsString)
		{
			Com_Printf ("^1WGL_EXTENSION not found!\n");
			goto fail;
		}

		Com_Printf ("\nWGL_EXTENSIONS:\n---------------\n");
					 
		string = (char*)glw_state.wglExtsString;
		while (1)
		{
			i = 0;
			line[i] = 0;
			while (1)
			{
				c = *string++;
				if ((c == ' ') || (c == 0))
					break;
				line[i] = c;
				i++;
			}
			if(!c)
				break;
			line[i] = 0;
			Com_Printf("%s\n", line);
		}

		Com_Printf ("\n");

		if ( strstr( glw_state.wglExtsString, "WGL_ARB_pixel_format" ) )
		{
			wglGetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)wglGetProcAddress("wglGetPixelFormatAttribivARB");
		    wglGetPixelFormatAttribfvARB = (PFNWGLGETPIXELFORMATATTRIBFVARBPROC)wglGetProcAddress("wglGetPixelFormatAttribfvARB");
			wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
			if (!wglGetPixelFormatAttribivARB || !wglGetPixelFormatAttribfvARB || !wglChoosePixelFormatARB)
			{
				Com_Printf ("^1X..failed to import WGL_ARB_pixel_format functions\n");
				goto fail;
			}
			else
				Com_Printf("...using WGL_ARB_pixel_format\n");
		} 
		else
		{
			Com_Printf("^1X..WGL_ARB_pixel_format not found\n");
			goto fail;
		}

		if ( strstr( glw_state.wglExtsString, "WGL_ARB_multisample" ) )
		{
			if(nSamples < 2)
			{
				Com_Printf("^3X..ignoring WGL_ARB_multisample\n");
				gl_config.arb_multisample = false;
			}
			else
			{
				Com_Printf("...using WGL_ARB_multisample (trying to use %i samples)\n", nSamples);
				gl_config.arb_multisample = true;
			}
		}
		else
		{
			Com_Printf("^1X..WGL_ARB_multisample not found\n");
			gl_config.arb_multisample = false;
		}

		Com_Printf ("\n");

		// make no rendering context current
		wglMakeCurrent(NULL, NULL);

		// Destroy the rendering context...
		wglDeleteContext(hGLRC);
		hGLRC = NULL;

		// Get the number of pixel format available
		iAttributes[0] = WGL_NUMBER_PIXEL_FORMATS_ARB;

		if (!wglGetPixelFormatAttribivARB(hDC, 0, 0, 1, iAttributes, iResults))
		{
			Com_Printf ("^1GLimp_InitGL() - wglGetPixelFormatAttribivARB failed\n");
			goto fail;
		}

		// Choose a Pixel Format Descriptor (PFD) with multisampling support.
loop:
		iAttributes[0] = WGL_DOUBLE_BUFFER_ARB;
		iAttributes[1] = true;
						
		iAttributes[2] = WGL_COLOR_BITS_ARB;
		iAttributes[3] = 32;
		
		iAttributes[4] = WGL_DEPTH_BITS_ARB;
		iAttributes[5] = 24;

		iAttributes[6] = WGL_ALPHA_BITS_ARB;
		iAttributes[7] = 8;

		iAttributes[8] = WGL_STENCIL_BITS_ARB;
		iAttributes[9] = 8;
		
		iAttributes[10] = gl_config.arb_multisample ? WGL_SAMPLE_BUFFERS_ARB : 0;
		iAttributes[11] = gl_config.arb_multisample ? true : false;
				
		iAttributes[12] = gl_config.arb_multisample ? WGL_SAMPLES_ARB : 0;
		iAttributes[13] = gl_config.arb_multisample ? nSamples : 0;
		
		iAttributes[14] = 0;
		iAttributes[15] = 0;

		status = wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats);
		// Failure happens not only when the function fails, but also when no matching pixel format has been found
		if (!status || !numFormats)
		{
			if (nSamples < 2)
			{
				Com_Printf ("^1GLimp_InitGL() - wglChoosePixelFormatARB failed\n");
				goto fail;
			}
			else
			{
				Com_Printf ("^3WARNING: wglChoosePixelFormatARB with %i samples failed\nTrying to use %i samples...\n", nSamples, (nSamples >> 1));
				nSamples >>= 1;
				goto loop;	// next attempt
			}
		}
		else
		{
			// Fill the list of attributes we are interested in
			iAttributes[0 ] = WGL_PIXEL_TYPE_ARB;
			iAttributes[1 ] = WGL_COLOR_BITS_ARB;
			iAttributes[2 ] = WGL_RED_BITS_ARB;
			iAttributes[3 ] = WGL_GREEN_BITS_ARB;
			iAttributes[4 ] = WGL_BLUE_BITS_ARB;
			iAttributes[5 ] = WGL_ALPHA_BITS_ARB;
			iAttributes[6 ] = WGL_DEPTH_BITS_ARB;
			iAttributes[7 ] = WGL_STENCIL_BITS_ARB;

			// Since WGL_ARB_multisample and WGL_pbuffer are extensions, we must check if
			// those extensions are supported before passing the corresponding enums
			// to the driver. This could cause an error if they are not supported.
			
			iAttributes[8 ] = gl_config.arb_multisample ? WGL_SAMPLE_BUFFERS_ARB : WGL_PIXEL_TYPE_ARB;
			iAttributes[9 ] = gl_config.arb_multisample ? WGL_SAMPLES_ARB : WGL_PIXEL_TYPE_ARB;
			
			iAttributes[12] = WGL_PIXEL_TYPE_ARB;
			iAttributes[10] = WGL_DRAW_TO_WINDOW_ARB;
			iAttributes[11] = WGL_DRAW_TO_BITMAP_ARB;
			iAttributes[13] = WGL_DOUBLE_BUFFER_ARB;
			iAttributes[14] = WGL_STEREO_ARB;
			iAttributes[15] = WGL_ACCELERATION_ARB;
			iAttributes[16] = WGL_NEED_PALETTE_ARB;
			iAttributes[17] = WGL_NEED_SYSTEM_PALETTE_ARB;
			iAttributes[18] = WGL_SWAP_LAYER_BUFFERS_ARB;
			iAttributes[19] = WGL_SWAP_METHOD_ARB;
			iAttributes[20] = WGL_NUMBER_OVERLAYS_ARB;
			iAttributes[21] = WGL_NUMBER_UNDERLAYS_ARB;
			iAttributes[22] = WGL_TRANSPARENT_ARB;
			iAttributes[23] = WGL_SUPPORT_GDI_ARB;
			iAttributes[24] = WGL_SUPPORT_OPENGL_ARB;

			if (!wglGetPixelFormatAttribivARB(hDC, pixelFormat, 0, 25, iAttributes, iResults))
			{
				Com_Printf ("^1GLimp_InitGL() - wglGetPixelFormatAttribivARB failed\n");
				goto fail;
			}

			Com_Printf ("WGL_PFD: Color %d-bits, Alpha %d-bits, Depth %d-bits, Stencil %d-bits\n", iResults[1], iResults[5], iResults[6], iResults[7]);

			if (gl_config.arb_multisample)
			{
				if (iResults[8])
					Com_Printf ("Using multisampling (%i samples per pixel)\n", iResults[9]);
				else
					Com_Printf ("^1Multisampling setup FAILED.\n");
			}

			if (iResults[15] != WGL_FULL_ACCELERATION_ARB)
				Com_Printf ("^3********** WARNING **********\npixelformat %d is NOT hardware accelerated!\n*****************************\n", pixelFormat);

			ReleaseDC (temphwnd, hDC);
			DestroyWindow (temphwnd);
			temphwnd = NULL;

			//glw_state.hDC = GetDC (glw_state.hWnd);
			if ( ( glw_state.hDC = GetDC( glw_state.hWnd ) ) == NULL )
			{
				Com_Printf("^1GLimp_InitGL() - GetDC failed\n" );
				goto fail;
			}

			if ( SetPixelFormat( glw_state.hDC, pixelFormat, &temppfd) == false )
			{
				Com_Printf ("^1GLimp_InitGL() - SetPixelFormat (%d) failed. ErrorCode %d.\n", pixelFormat, GetLastError());
				goto fail;
			}

			if(!gl_notestdriver->value)
			{
				Com_Printf("...verifying OpenGL driver: ");
				DescribePixelFormat( glw_state.hDC, pixelFormat, sizeof( temppfd ), &temppfd );

				if ( !( temppfd.dwFlags & PFD_GENERIC_ACCELERATED )  && (temppfd.dwFlags & PFD_GENERIC_FORMAT))
				{
					Com_Printf("^1Software - OpenGL emulation\nThis program requires hardware ICD OpenGL acceleration\n\n");
					goto fail;
				}

				if ( ( temppfd.dwFlags & PFD_GENERIC_ACCELERATED )  && (temppfd.dwFlags & PFD_GENERIC_FORMAT))
				{
					Com_Printf("^1Hardware - MCD OpenGL (MiniClientDriver)\nThis program requires hardware ICD OpenGL acceleration\n\n");
					goto fail;
				}

				Com_Printf("Hardware ICD acceleration\n\n");
			}
			else
				Com_Printf("^3X..OpenGL driver verifying skipped\n\n");

			/*
			** startup the OpenGL subsystem by creating a context and making it current
			*/
			if ( ( glw_state.hGLRC = wglCreateContext( glw_state.hDC ) ) == 0 )
			{
				Com_Printf ("^1GLimp_InitGL() - wglCreateContext failed\n");
				goto fail;
			}

			if ( !wglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) )
			{
				Com_Printf ("^1GLimp_InitGL() - wglMakeCurrent failed\n");
				goto fail;
			}
		}
	}

	SetGamma(vid_gamma->value, vid_bright->value, vid_contrast->value);
	return true;

fail:
	if ( glw_state.hGLRC )
	{
		wglDeleteContext( glw_state.hGLRC );
		glw_state.hGLRC = NULL;
	}

	if ( glw_state.hDC )
	{
		ReleaseDC( glw_state.hWnd, glw_state.hDC );
		glw_state.hDC = NULL;
	}

	return false;
}


bool GLimp_InitGL_Old ()
{
    PIXELFORMATDESCRIPTOR pfd = 
	{
		sizeof(PIXELFORMATDESCRIPTOR),	// size of this pfd
		1,								// version number
		PFD_DRAW_TO_WINDOW |			// support window
		PFD_SUPPORT_OPENGL |			// support OpenGL
		PFD_DOUBLEBUFFER,				// double buffered
		PFD_TYPE_RGBA,					// RGBA type
		32,								// 24-bit color depth
		0, 0, 0, 0, 0, 0,				// color bits ignored
		8,								// 8 bit destination alpha
		0,								// shift bit ignored
		0,								// no accumulation buffer
		0, 0, 0, 0, 					// accum bits ignored
		24,								// 24-bit z-buffer	
		8,								// We need a stencil buffer for the shadows
		0,								// no auxiliary buffer
		PFD_MAIN_PLANE,					// main layer
		0,								// reserved
		0, 0, 0							// layer masks ignored
    };
    int  pixelFormat;
	
	gl_config.arb_multisample = false;

	// Get a DC for the specified window
	if ( glw_state.hDC != NULL )
		Com_Printf("^3GLimp_InitGL_Old() - non-NULL DC exists\n" );

	if ( ( glw_state.hDC = GetDC( glw_state.hWnd ) ) == NULL )
	{
		Com_Printf("^1GLimp_InitGL_Old() - GetDC failed\n" );
		return false;
	}

	if ( ( pixelFormat = ChoosePixelFormat( glw_state.hDC, &pfd)) == 0 )
	{
		Com_Printf("^1GLimp_InitGL_Old() - ChoosePixelFormat (%dc/%da/%dd/%ds) failed. ErrorCode %d.\n", (int)pfd.cColorBits, (int)pfd.cAlphaBits, (int)pfd.cColorBits, (int)pfd.cStencilBits, GetLastError());
		return false;
	}
	if ( SetPixelFormat( glw_state.hDC, pixelFormat, &pfd) == FALSE )
	{
		Com_Printf("^1GLimp_InitGL_Old() - SetPixelFormat (%d) failed. ErrorCode %d.\n", pixelFormat, GetLastError());
		return false;
	}

	if(!gl_notestdriver->value)
	{
		Com_Printf("...verifying OpenGL driver: ");
		DescribePixelFormat( glw_state.hDC, pixelFormat, sizeof( pfd ), &pfd );

		if ( !( pfd.dwFlags & PFD_GENERIC_ACCELERATED )  && (pfd.dwFlags & PFD_GENERIC_FORMAT))
		{
			Com_Printf("^1Software - OpenGL emulation\nThis program requires hardware ICD OpenGL acceleration\n\n");
			goto fail;
		}

		if ( ( pfd.dwFlags & PFD_GENERIC_ACCELERATED )  && (pfd.dwFlags & PFD_GENERIC_FORMAT))
		{
			Com_Printf("^1Hardware - MCD OpenGL (MiniClientDriver)\nThis program requires hardware ICD OpenGL acceleration\n\n");
			goto fail;
		}

		Com_Printf("Hardware ICD acceleration\n\n");
	}
	else
		Com_Printf("^3X..OpenGL driver verifying skipped\n\n");

	// startup the OpenGL subsystem by creating a context and making
	if ( ( glw_state.hGLRC = wglCreateContext( glw_state.hDC ) ) == 0 )
	{
		Com_Printf("^1GLimp_InitGL_Old() - wglCreateContext failed\n");
		goto fail;
	}

    if ( !wglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) )
	{
		Com_Printf("^1GLimp_InitGL_Old() - wglMakeCurrent failed\n");
		goto fail;
	}

	if ( !VerifyDriver() )
	{
		Com_Printf("^1GLimp_InitGL_Old() - no hardware acceleration detected\n" );
		goto fail;
	}

	// print out PFD specifics 
	Com_Printf("GL_PFD: Color(%d-bits),\nAlpha(%d-bits),\nDepth(%d-bits), Stencil(%d-bits)\n", ( int ) pfd.cColorBits, ( int ) pfd.cAlphaBits, ( int ) pfd.cDepthBits, ( int ) pfd.cStencilBits );

	SetGamma(vid_gamma->value, vid_bright->value, vid_contrast->value);
	return true;

fail:
	if ( glw_state.hGLRC )
	{
		wglDeleteContext( glw_state.hGLRC );
		glw_state.hGLRC = NULL;
	}

	if ( glw_state.hDC )
	{
		ReleaseDC( glw_state.hWnd, glw_state.hDC );
		glw_state.hDC = NULL;
	}
	return false;
}


bool VID_CreateWindow( int width, int height, bool fullscreen )
{
	WNDCLASS		wc;
	RECT			r;
	cvar_t			*vid_xpos, *vid_ypos;
	int				stylebits;
	int				x, y, w, h;
	int				exstyle;

	/* Register the frame class */
    wc.style         = 0;
    wc.lpfnWndProc   = (WNDPROC)glw_state.wndproc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = glw_state.hInstance;
    wc.hIcon         = 0;
    wc.hCursor       = LoadCursor (NULL,IDC_ARROW);
	wc.hbrBackground = (struct HBRUSH__ *)COLOR_GRAYTEXT;
    wc.lpszMenuName  = 0;
    wc.lpszClassName = WINDOW_CLASS_NAME;

    if (!RegisterClass (&wc) )
		Sys_Error ("Couldn't register window class");

	if (fullscreen)
	{
		exstyle = WS_EX_TOPMOST;
		stylebits = WS_POPUP|WS_VISIBLE;
	}
	else
	{
		exstyle = 0;
		stylebits = WINDOW_STYLE;
	}

	r.left = 0;
	r.top = 0;
	r.right  = width;
	r.bottom = height;

	AdjustWindowRect (&r, stylebits, FALSE);

	w = r.right - r.left;
	h = r.bottom - r.top;

	if (fullscreen)
	{
		x = 0;
		y = 0;
	}
	else
	{
		vid_xpos = Cvar_Get ("vid_xpos", "0", 0);
		vid_ypos = Cvar_Get ("vid_ypos", "0", 0);
		x = vid_xpos->value;
		y = vid_ypos->value;
	}

	glw_state.hWnd = CreateWindowEx (
		 exstyle, 
		 WINDOW_CLASS_NAME,
		 "Berserker",
		 stylebits,
		 x, y, w, h,
		 NULL,
		 NULL,
		 glw_state.hInstance,
		 NULL);

	if (!glw_state.hWnd)
		Sys_Error ("Couldn't create window");
	
	ShowWindow( glw_state.hWnd, SW_SHOW );
	UpdateWindow( glw_state.hWnd );

	// init all the gl stuff for the window
	if (!GLimp_InitGL ())
	{
		Com_Printf("^1GLimp_InitGL() failed\n^3Trying to call GLimp_InitGL_Old()\n");
		if (!GLimp_InitGL_Old ())
		{
			Com_Printf("^1VID_CreateWindow() - GLimp_InitGL() failed\n");
			return false;
		}
	}

	SetForegroundWindow( glw_state.hWnd );
	SetFocus( glw_state.hWnd );

	// let the sound and input subsystems know about the new window
	viddef.width  = width;
	viddef.height = height;

	return true;
}


bool Vid_GetModeInfo( int *width, int *height, int mode )
{
	if ( mode < 0 || mode >= _VID_NUM_MODES )
		return false;

	*width  = vid_modes[mode].width;
	*height = vid_modes[mode].height;
	return true;
}


rserr_t GLimp_SetMode( int *pwidth, int *pheight, int mode, bool fullscreen, int bitspixel, int hz )
{
	int width, height;
	char *win_fs[] = { "Win", "FS" };

	Com_Printf("Initializing OpenGL display\n");
	Com_Printf("...setting mode %d:", mode );

	if ( !Vid_GetModeInfo( &width, &height, mode ) )
	{
		Com_Printf("^1 invalid mode\n" );
		return rserr_invalid_mode;
	}

	Com_Printf(" %dx%d:%s\n", width, height, win_fs[fullscreen] );

	// destroy the existing window
	if (glw_state.hWnd)
		GLimp_Shutdown ();

	// do a CDS if needed
	if ( fullscreen )
	{
		DEVMODE dm;

		Com_Printf("...attempting fullscreen\n" );

		Memset( &dm, 0, sizeof( dm ) );

		dm.dmSize = sizeof( dm );

		dm.dmPelsWidth  = width;
		dm.dmPelsHeight = height;
		dm.dmFields     = DM_PELSWIDTH | DM_PELSHEIGHT;

		if ( bitspixel != 32 )
		{
			dm.dmBitsPerPel = 32;
			dm.dmFields |= DM_BITSPERPEL;
		}

///		if ( r_bitdepth->value != 0 )
///		{
///			if(r_bitdepth->value != 16 && r_bitdepth->value != 32)
///			{
///				Com_Printf("...Fixing r_bitdepth: %d -> 16\n", ( int ) r_bitdepth->value );
///				Cvar_SetValue( "r_bitdepth", 16 );
///				r_bitdepth->modified = false;
///			}
///
///			dm.dmBitsPerPel = r_bitdepth->value;
///			dm.dmFields |= DM_BITSPERPEL;
///			Com_Printf("...using r_bitdepth of %d\n", ( int ) r_bitdepth->value );
///		}
///		else
///		{
///			HDC hdc = GetDC( NULL );
///			int bitspixel = GetDeviceCaps( hdc, BITSPIXEL );
///			Com_Printf("...using desktop display depth of %d\n", bitspixel );
///			ReleaseDC( 0, hdc );
///		}

		if(hz)
		{
			dm.dmDisplayFrequency = hz;				// Screen Refresh (HZ)
			dm.dmFields |= DM_DISPLAYFREQUENCY;
			Com_Printf("...display refresh rate is %d Hz\n", hz);
		}

		Com_Printf("...calling CDS: " );
		if ( ChangeDisplaySettings( &dm, CDS_FULLSCREEN ) == DISP_CHANGE_SUCCESSFUL )
		{
			*pwidth = width;
			*pheight = height;

			gl_state.fullscreen = true;

			Com_Printf("ok\n" );

			if ( !VID_CreateWindow (width, height, true) )
				return rserr_invalid_mode;

			return rserr_ok;
		}
		else
		{
			*pwidth = width;
			*pheight = height;

			Com_Printf("^1failed\n" );
			Com_Printf("...calling CDS assuming dual monitors:" );

			dm.dmPelsWidth = width * 2;
			dm.dmPelsHeight = height;
			dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;

			if ( bitspixel != 32 )
			{
				dm.dmBitsPerPel = 32;
				dm.dmFields |= DM_BITSPERPEL;
			}

			/*
			** our first CDS failed, so maybe we're running on some weird dual monitor
			** system 
			*/
			if ( ChangeDisplaySettings( &dm, CDS_FULLSCREEN ) != DISP_CHANGE_SUCCESSFUL )
			{
				Com_Printf("^1 failed\n" );
				Com_Printf("...setting windowed mode\n" );

				ChangeDisplaySettings( 0, 0 );

				*pwidth = width;
				*pheight = height;
				gl_state.fullscreen = false;
				if ( !VID_CreateWindow (width, height, false) )
					return rserr_invalid_mode;
				return rserr_invalid_fullscreen;
			}
			else
			{
				Com_Printf(" ok\n" );
				if ( !VID_CreateWindow (width, height, true) )
					return rserr_invalid_mode;

				gl_state.fullscreen = true;
				return rserr_ok;
			}
		}
	}
	else
	{
		Com_Printf("...setting windowed mode\n" );

		ChangeDisplaySettings( 0, 0 );

		*pwidth = width;
		*pheight = height;
		gl_state.fullscreen = false;
		if ( !VID_CreateWindow (width, height, false) )
			return rserr_invalid_mode;
	}

	return rserr_ok;
}


bool R_SetMode ()
{
	rserr_t err;
	bool fullscreen;

//	if (r_fullscreen->modified)
//	{
//		Cvar_SetValue( "r_fullscreen", !r_fullscreen->value );
//	}

	r_fullscreen->modified = false;
	r_mode->modified = false;
	fullscreen = r_fullscreen->value;

	HDC hdc = GetDC( NULL );
	int bitspixel = GetDeviceCaps( hdc, BITSPIXEL );
	Com_Printf("...desktop color depth is %d\n", bitspixel );
	ReleaseDC( 0, hdc );

	if(bitspixel != 32 && !fullscreen)
	{
		Com_Printf("^3Windowed mode requires 32 bit desktop depth\n" );
		Cvar_SetValue( "r_fullscreen", 1 );
		fullscreen = r_fullscreen->value;
	}

	if ( ( err = GLimp_SetMode( &vid.width, &vid.height, r_mode->value, fullscreen, bitspixel, vid_hz->value ) ) == rserr_ok )
		gl_state.prev_mode = r_mode->value;
	else
	{
///		Cvar_SetValue( "r_bitdepth", 0);
///		r_bitdepth->modified = false;
		if ( err == rserr_invalid_fullscreen )
		{
			if (vid_hz->value)
			{
				Com_Printf("^3R_SetMode(): fullscreen.[%i Hz] unavailable in this mode\n    Trying to use default Hz setting.\n", (int)vid_hz->value );
				Cvar_SetValue( "vid_hz", 0);
				vid_hz->modified = false;
				if ( ( err = GLimp_SetMode( &vid.width, &vid.height, r_mode->value, true, bitspixel, 0 ) ) == rserr_ok )
					return true;
			}

			Cvar_SetValue( "r_fullscreen", 0);
			r_fullscreen->modified = false;
			Com_Printf("^1R_SetMode(): fullscreen unavailable in this mode\n" );
			if ( ( err = GLimp_SetMode( &vid.width, &vid.height, r_mode->value, false, bitspixel, vid_hz->value ) ) == rserr_ok )
				return true;
		}
		else if ( err == rserr_invalid_mode )
		{
			Cvar_SetValue( "r_mode", gl_state.prev_mode );
			r_mode->modified = false;
			Com_Printf("^1R_SetMode(): invalid mode\n" );
		}

		// try setting it back to something safe
		if ( ( err = GLimp_SetMode( &vid.width, &vid.height, gl_state.prev_mode, false, bitspixel, vid_hz->value ) ) != rserr_ok )
		{
			Com_Printf("^1R_SetMode(): could not revert to safe mode\n" );
			return false;
		}
	}
	return true;
}


void GL_SelectTexture( GLenum texture )
{
	int tmu = texture - GL_TEXTURE0;

	if ( tmu == gl_state.currenttmu )
		return;

	gl_state.currenttmu = tmu;

	glActiveTextureARB( texture );
	glClientActiveTextureARB( texture );
}


void GL_BindProgramVP (unsigned prg)
{
	if (gl_state.vp == prg)
		return;

	gl_state.vp = prg;
	glBindProgramARB(GL_VERTEX_PROGRAM_ARB, prg);
}


void GL_BindProgramFP (unsigned prg)
{
	if (gl_state.fp == prg)
		return;

	gl_state.fp = prg;
	glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, prg);
}


void GL_ClearCache(byte features)
{
	int i;

	if(features & GL_TEX)
		for(i=0; i<32/*gl_config.tmus*/; i++)
			gl_state.currenttextures[i] = -1;

	if(features & GL_TMU)
	{
		gl_state.currenttmu = -1;
		GL_SelectTexture(GL_TEXTURE0);	/// for gl_state.currenttmu
	}

	if(features & GL_TEXENV)
		for(i=0; i<32/*gl_config.tmus*/; i++)
			gl_state.texenv[i][0] = gl_state.texenv[i][1] = -1;

	if(features & GL_BLEND_)
		gl_state.lastblend_dst = gl_state.lastblend_src = -1;

	if(features & GL_COLOR_)
		gl_state.color[0] = gl_state.color[1] = gl_state.color[2] = gl_state.color[3] = -1;

	if(features & GL_VP)
		gl_state.vp = -1;

	if(features & GL_FP)
		gl_state.fp = -1;
}


void GL_Bind (GLenum target, int texnum)
{
	static image_t	*draw_chars;

	if (draw_chars)
		texnum = draw_chars->texnum;
	if ( gl_state.currenttextures[gl_state.currenttmu] == texnum)
		return;

	gl_state.currenttextures[gl_state.currenttmu] = texnum;
	glBindTexture (target, texnum);
}


void GL_Color4f(float r, float g, float b, float a)
{
	if (gl_state.color[0] != r ||
		gl_state.color[1] != g ||
		gl_state.color[2] != b ||
		gl_state.color[3] != a)
	{
		glColor4f(r,g,b,a);
		gl_state.color[0] = r;
		gl_state.color[1] = g;
		gl_state.color[2] = b;
		gl_state.color[3] = a;
	}
}


void GL_Color4fv(float *rgba)
{
	if (gl_state.color[0] != rgba[0] ||
		gl_state.color[1] != rgba[1] ||
		gl_state.color[2] != rgba[2] ||
		gl_state.color[3] != rgba[3])
	{
		glColor4fv(rgba);
		gl_state.color[0] = rgba[0];
		gl_state.color[1] = rgba[1];
		gl_state.color[2] = rgba[2];
		gl_state.color[3] = rgba[3];
	}
}


void GL_Color3f(float r, float g, float b)
{
	if (gl_state.color[0] != r ||
		gl_state.color[1] != g ||
		gl_state.color[2] != b ||
		gl_state.color[3] != 1)
	{
		glColor3f(r,g,b);
		gl_state.color[0] = r;
		gl_state.color[1] = g;
		gl_state.color[2] = b;
		gl_state.color[3] = 1;
	}
}


void GL_Color3fv(float *rgb)
{
	if (gl_state.color[0] != rgb[0] ||
		gl_state.color[1] != rgb[1] ||
		gl_state.color[2] != rgb[2] ||
		gl_state.color[3] != 1)
	{
		glColor3fv(rgb);
		gl_state.color[0] = rgb[0];
		gl_state.color[1] = rgb[1];
		gl_state.color[2] = rgb[2];
		gl_state.color[3] = 1;
	}
}


inline void GL_Bind2D (int texnum)
{
	GL_Bind(GL_TEXTURE_2D, texnum);
}


void GL_TexEnv( GLenum name, GLenum mode )
{
	if ( name != gl_state.texenv[gl_state.currenttmu][0] || mode != gl_state.texenv[gl_state.currenttmu][1] )
	{
		glTexEnvf( GL_TEXTURE_ENV, name, mode );
		gl_state.texenv[gl_state.currenttmu][0] = name;
		gl_state.texenv[gl_state.currenttmu][1] = mode;
	}
}


inline void gl_TexEnv( GLenum mode )
{
	GL_TexEnv (GL_TEXTURE_ENV_MODE, mode);
}


void GL_BlendFunc( GLenum src, GLenum dst )
{
	if ( (src != gl_state.lastblend_src) || (dst != gl_state.lastblend_dst) )
	{
		glBlendFunc( src, dst );
		gl_state.lastblend_src = src;
		gl_state.lastblend_dst = dst;
	}
}


inline void GL_MBind2D( GLenum target, int texnum )
{
	GL_SelectTexture( target );
	GL_Bind2D( texnum );
}


inline void GL_SetAnisotropy()
{
	if(gl_config.anisotropic)
	{
		if (r_anisotropy->value)
			Cvar_SetValue("r_anisotropy", min(gl_config.anisotropy_level, r_anisotropy->value));
		else
			Cvar_SetValue("r_anisotropy", 0);
		if (r_anisotropy->value)
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_anisotropy->value);
		else
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);
	}

/*	if(r_texture_lod->value)
	{
		if(r_texture_lod->value < R_TEXTURE_MAX_LOD)
			Cvar_SetValue( "r_texture_lod", R_TEXTURE_MAX_LOD );
		else if(r_texture_lod->value > 0)
			Cvar_SetValue( "r_texture_lod", 0 );

		if(gl_config.lod_bias)
			glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, r_texture_lod->value);
	}
*/
}


void GL_SetDefaultState ()
{
	if(fragment_shaders != 0xffffffff && glDeleteFragmentShaderATI)
	{
		glDeleteFragmentShaderATI(fragment_shaders);
		glDeleteFragmentShaderATI(fragment_shaders+1);
		fragment_shaders = 0xffffffff;
	}

	if(glDeleteProgramsARB)
	{
		if(fragment_program_nofilter != 0xffffffff)
		{
			glDeleteProgramsARB(1, &fragment_program_nofilter);
			fragment_program_nofilter = 0xffffffff;
			if(fragmentprogram_nofilter_ptr && fragmentprogram_nofilter_ptr != fragmentprogram_nofilter)
				Z_Free (fragmentprogram_nofilter_ptr);
		}

		if(fragment_program_filter != 0xffffffff)
		{
			glDeleteProgramsARB(1, &fragment_program_filter);
			fragment_program_filter = 0xffffffff;
			if(fragmentprogram_filter_ptr && fragmentprogram_filter_ptr != fragmentprogram_filter)
				Z_Free (fragmentprogram_filter_ptr);
		}

		if(fragment_program_filter4 != 0xffffffff)
		{
			glDeleteProgramsARB(1, &fragment_program_filter4);
			fragment_program_filter4 = 0xffffffff;
			if(fragmentprogram_filter4_ptr && fragmentprogram_filter4_ptr != fragmentprogram_filter4)
				Z_Free (fragmentprogram_filter4_ptr);
		}

		if(fragment_program_nofilter6_detail != 0xffffffff)
		{
			glDeleteProgramsARB(1, &fragment_program_nofilter6_detail);
			fragment_program_nofilter6_detail = 0xffffffff;
			if(fragmentprogram_nofilter6_detail_ptr && fragmentprogram_nofilter6_detail_ptr != fragmentprogram_nofilter6_detail)
				Z_Free (fragmentprogram_nofilter6_detail_ptr);
		}

		if(fragment_program_filter6_detail != 0xffffffff)
		{
			glDeleteProgramsARB(1, &fragment_program_filter6_detail);
			fragment_program_filter6_detail = 0xffffffff;
			if(fragmentprogram_filter6_detail_ptr && fragmentprogram_filter6_detail_ptr != fragmentprogram_filter6_detail)
				Z_Free (fragmentprogram_filter6_detail_ptr);
		}

		if(arbdistort_program != 0xffffffff)
		{
			glDeleteProgramsARB(1, &arbdistort_program);
			arbdistort_program = 0xffffffff;
			if(arbdistort_ptr && arbdistort_ptr != arbdistort)
				Z_Free (arbdistort_ptr);
		}

		if(fragment_program_ambient != 0xffffffff)
		{
			glDeleteProgramsARB(1, &fragment_program_ambient);
			fragment_program_ambient = 0xffffffff;
			if(fragmentprogram_ambient_ptr && fragmentprogram_ambient_ptr != fragmentprogram_ambient)
				Z_Free (fragmentprogram_ambient_ptr);
		}

		if(vertex_program_nolightfilter != 0xffffffff)
		{
			glDeleteProgramsARB(1, &vertex_program_nolightfilter);
			vertex_program_nolightfilter = 0xffffffff;
			if(vertexprogram_nolightfilter_ptr && vertexprogram_nolightfilter_ptr != vertexprogram_nolightfilter)
				Z_Free (vertexprogram_nolightfilter_ptr);
		}

		if(vertex_program_lightfilter6 != 0xffffffff)
		{
			glDeleteProgramsARB(1, &vertex_program_lightfilter6);
			vertex_program_lightfilter6 = 0xffffffff;
			if(vertexprogram_lightfilter6_ptr && vertexprogram_lightfilter6_ptr != vertexprogram_lightfilter6)
				Z_Free (vertexprogram_lightfilter6_ptr);
		}

		if(vertex_program_lightfilter4 != 0xffffffff)
		{
			glDeleteProgramsARB(1, &vertex_program_lightfilter4);
			vertex_program_lightfilter4 = 0xffffffff;
			if(vertexprogram_lightfilter4_ptr && vertexprogram_lightfilter4_ptr != vertexprogram_lightfilter4)
				Z_Free (vertexprogram_lightfilter4_ptr);
		}

		if(vertex_program_lightfilter6_parallax != 0xffffffff)
		{
			glDeleteProgramsARB(1, &vertex_program_lightfilter6_parallax);
			vertex_program_lightfilter6_parallax = 0xffffffff;
			if(vertexprogram_lightfilter6_parallax_ptr && vertexprogram_lightfilter6_parallax_ptr != vertexprogram_lightfilter6_parallax)
				Z_Free (vertexprogram_lightfilter6_parallax_ptr);
		}

		if(vertex_program_nolightfilter_parallax != 0xffffffff)
		{
			glDeleteProgramsARB(1, &vertex_program_nolightfilter_parallax);
			vertex_program_nolightfilter_parallax = 0xffffffff;
			if(vertexprogram_nolightfilter_parallax_ptr && vertexprogram_nolightfilter_parallax_ptr != vertexprogram_nolightfilter_parallax)
				Z_Free (vertexprogram_nolightfilter_parallax_ptr);
		}

		if(fragment_program_filter_parallax != 0xffffffff)
		{
			glDeleteProgramsARB(1, &fragment_program_filter_parallax);
			fragment_program_filter_parallax = 0xffffffff;
			if(fragmentprogram_filter_parallax_ptr && fragmentprogram_filter_parallax_ptr != fragmentprogram_filter_parallax)
				Z_Free (fragmentprogram_filter_parallax_ptr);
		}

		if(fragment_program_nofilter_parallax != 0xffffffff)
		{
			glDeleteProgramsARB(1, &fragment_program_nofilter_parallax);
			fragment_program_nofilter_parallax = 0xffffffff;
			if(fragmentprogram_nofilter_parallax_ptr && fragmentprogram_nofilter_parallax_ptr != fragmentprogram_nofilter_parallax)
				Z_Free (fragmentprogram_nofilter_parallax_ptr);
		}

		if(vertex_program_ambient_parallax != 0xffffffff)
		{
			glDeleteProgramsARB(1, &vertex_program_ambient_parallax);
			vertex_program_ambient_parallax = 0xffffffff;
			if(vertexprogram_ambient_parallax_ptr && vertexprogram_ambient_parallax_ptr != vertexprogram_ambient_parallax)
				Z_Free (vertexprogram_ambient_parallax_ptr);
		}

		if(fragment_program_ambient_parallax != 0xffffffff)
		{
			glDeleteProgramsARB(1, &fragment_program_ambient_parallax);
			fragment_program_ambient_parallax = 0xffffffff;
			if(fragmentprogram_ambient_parallax_ptr && fragmentprogram_ambient_parallax_ptr != fragmentprogram_ambient_parallax)
				Z_Free (fragmentprogram_ambient_parallax_ptr);
		}

		if(vertex_program_nobump_parallax != 0xffffffff)
		{
			glDeleteProgramsARB(1, &vertex_program_nobump_parallax);
			vertex_program_nobump_parallax = 0xffffffff;
			if(vertexprogram_nobump_parallax_ptr && vertexprogram_nobump_parallax_ptr != vertexprogram_nobump_parallax)
				Z_Free (vertexprogram_nobump_parallax_ptr);
		}

		if(fragment_program_nobump_parallax != 0xffffffff)
		{
			glDeleteProgramsARB(1, &fragment_program_nobump_parallax);
			fragment_program_nobump_parallax = 0xffffffff;
			if(fragmentprogram_nobump_parallax_ptr && fragmentprogram_nobump_parallax_ptr != fragmentprogram_nobump_parallax)
				Z_Free (fragmentprogram_nobump_parallax_ptr);
		}
	}

	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glHint (GL_FOG_HINT, GL_NICEST);

	glClearColor (0.35, 0.35, 0.35, 0.5);
	glCullFace(GL_FRONT);
	glEnable(GL_TEXTURE_2D);

	glDisable(GL_STENCIL_TEST);
	glDisable(GL_LIGHTING);
	glDisable(GL_AUTO_NORMAL);
	glDisable(GL_NORMALIZE);
	glDisable(GL_SCISSOR_TEST);
	glDisable(GL_CLIP_PLANE0);
	glDisable(GL_CLIP_PLANE1);
	glDisable(GL_CLIP_PLANE2);
	glDisable(GL_CLIP_PLANE3);
	glDisable(GL_CLIP_PLANE4);
	glDisable(GL_CLIP_PLANE5);
	glDisable(GL_FOG);

///	for (int i=0; i<6; i++)
///		gl_state.clipPlane[i][0] = gl_state.clipPlane[i][1] = gl_state.clipPlane[i][2] = gl_state.clipPlane[i][3] = -9999;

	if (gl_config.arb_multisample)
		glDisable(GL_MULTISAMPLE_ARB);

	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0.2);	/// чтоб решетки не пропадали

	glDisable (GL_DEPTH_TEST);
	glDisable (GL_CULL_FACE);
	glDisable (GL_BLEND);

	glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
	glPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
	glShadeModel (GL_SMOOTH);

	glLineWidth( 2 );

	GL_TextureMode( r_texturemode->string );
	GL_TextureAlphaMode( r_texturealphamode->string );
	GL_TextureSolidMode( r_texturesolidmode->string );

///	GL_SetAnisotropy();

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);

///	if(glPointParameterfEXT)
	if (r_ext_pointparameters->value && glPointParameterfEXT)	/// fixed by Berserker
	{
		float attenuations[3];
		attenuations[0] = r_particle_att_a->value;
		attenuations[1] = r_particle_att_b->value;
		attenuations[2] = r_particle_att_c->value;
		glEnable( GL_POINT_SMOOTH );
		glPointParameterfEXT( GL_POINT_SIZE_MIN_EXT, r_particle_min_size->value );
		glPointParameterfEXT( GL_POINT_SIZE_MAX_EXT, r_particle_max_size->value );
		glPointParameterfvEXT( GL_DISTANCE_ATTENUATION_EXT, attenuations );
	}

	GL_UpdateSwapInterval();
	GL_ClearCache(GL_TEX | GL_TMU | GL_TEXENV	| GL_BLEND_	| GL_COLOR_ | GL_VP | GL_FP);
	gl_TexEnv( GL_REPLACE );
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	GL_Color3f (1,1,1);

	if (gl_config.vbo)
	{
		gl_state.vbo_id = 0;
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
	}
}


byte	dottexture[8][8] =
{
	{0,0,0,0,0,0,0,0},
	{0,0,1,1,0,0,0,0},
	{0,1,1,1,1,0,0,0},
	{0,1,1,1,1,0,0,0},
	{0,0,1,1,0,0,0,0},
	{0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0},
};

void R_InitParticleTexture ()
{
	int		x,y;
	byte	data[8][8][4];

	//
	// also use this for bad textures, but without alpha
	//
	for (x=0 ; x<8 ; x++)
	{
		for (y=0 ; y<8 ; y++)
		{
			data[y][x][0] = 0;	// BGRA
			data[y][x][1] = 0;
			data[y][x][2] = dottexture[x&3][y&3]*255;
			data[y][x][3] = 255;
		}
	}
	r_notexture = GL_LoadPic ("***r_notexture***", NULL, (byte *)data, 8, 8, it_wall, 32, false, 0);

	//
	// particle texture
	//
	r_particletexture = GL_FindImage("textures/particles/particle.tga",it_sprite,true,0,false,0);
	if (!r_particletexture)
	{
		for (x=0 ; x<8 ; x++)
		{
			for (y=0 ; y<8 ; y++)
			{
				data[y][x][0] = 255;
				data[y][x][1] = 255;
				data[y][x][2] = 255;
				data[y][x][3] = dottexture[x][y]*255;
			}
		}
		r_particletexture = GL_LoadPic ("***particle***", NULL, (byte *)data, 8, 8, it_sprite, 32, false, 0);
	}

	env_texture_2d_r = GL_FindImage("textures/env/red_power.jpg",it_fx,true,0,false,0);
	env_texture_2d_g = GL_FindImage("textures/env/green_power.jpg",it_fx,true,0,false,0);
	env_texture_2d_b = GL_FindImage("textures/env/blue_power.jpg",it_fx,true,0,false,0);
	env_texture_2d_rg = GL_FindImage("textures/env/rg_power.jpg",it_fx,true,0,false,0);
	env_texture_2d_rb = GL_FindImage("textures/env/rb_power.jpg",it_fx,true,0,false,0);
	env_texture_2d_gb = GL_FindImage("textures/env/gb_power.jpg",it_fx,true,0,false,0);
	env_texture_2d_rgb = GL_FindImage("textures/env/envmap.jpg",it_fx,true,0,false,0);
	if(!env_texture_2d_r)
		env_texture_2d_r = r_notexture;
	if(!env_texture_2d_g)
		env_texture_2d_g = r_notexture;
	if(!env_texture_2d_b)
		env_texture_2d_b = r_notexture;
	if(!env_texture_2d_rg)
		env_texture_2d_rg = r_notexture;
	if(!env_texture_2d_rb)
		env_texture_2d_rb = r_notexture;
	if(!env_texture_2d_gb)
		env_texture_2d_gb = r_notexture;
	if(!env_texture_2d_rgb)
		env_texture_2d_rgb = r_notexture;

	chrome_texture_2d = GL_FindImage("textures/env/chrome.jpg",it_fx,true,0,false,0);
	if(!chrome_texture_2d)
		chrome_texture_2d = r_notexture;

	env_texture_2d_world = GL_FindImage("textures/env/envmap_world.jpg",it_fx,true,0,false,0);
	if(!env_texture_2d_world)
		env_texture_2d_world = r_notexture;

	chrome_texture_2d_world = GL_FindImage("textures/env/chrome_world.jpg",it_fx,true,0,false,0);
	if(!chrome_texture_2d_world)
		chrome_texture_2d_world = r_notexture;

	for (x=0; x<MAX_PARTICLES; x++)
	{
		texcoordArray_global[6*x+0] = 0.0625;
		texcoordArray_global[6*x+1] = 0.0625;
		texcoordArray_global[6*x+2] = 1.0625;
		texcoordArray_global[6*x+3] = 0.0625;
		texcoordArray_global[6*x+4] = 0.0625;
		texcoordArray_global[6*x+5] = 1.0625;

		texcoordArray_global2[6*x+0] = 0;
		texcoordArray_global2[6*x+1] = 0;
		texcoordArray_global2[6*x+2] = 2;
		texcoordArray_global2[6*x+3] = 0;
		texcoordArray_global2[6*x+4] = 0;
		texcoordArray_global2[6*x+5] = 2;
	}
}


void R_InitDefaultTextures ()
{
	byte tmp[1][1][4] = {0xff,0x80,0x80,0x0};	// BGRA
	byte tmp2[1][1][4] = {0x80,0x80,0x80,0xff};
	if (!r_simple->value)
	{
		r_defaultbump = GL_LoadPic ("***r_nobump***", NULL, (byte *)tmp, 1, 1, it_bump, 32, false, 0);
		r_nodetailbump = GL_LoadPic ("*r_nodetail*", NULL, (byte *)tmp2, 1, 1, it_bump, 32, false, 0);

		if (r_shader->value == SHADER_ARB6 && r_detailed_bump->value)
		{
			if (r_detailed_bump_default->string[0])
			{
				r_defaultbump_detail = GL_FindImage(r_detailed_bump_default->string, it_bump, false, 0, false, 0);
				if (!r_defaultbump_detail)
					r_defaultbump_detail = r_defaultbump;
			}
			else
				r_defaultbump_detail = r_defaultbump;
		}
	}
	else
		r_nodetailbump = r_defaultbump = r_defaultbump_detail = r_notexture;


	r_screenTexture = r_bloomTexture = r_notexture;	// Чисто для того, чтоб не вылетало на стандартных программах...
	if(gl_config.screentexture)
	{
		if(r_distort->value || r_fullscreen->value)		// r_screenTexture нужна также для bloom !!!
		{
			Memset(trans, 0, gl_config.screenTextureSize[0]*gl_config.screenTextureSize[1]*3);	/// т.к. экран-текстура не квадратная, то произведем её очистку, чтоб не было артефактов вне "экрана" (зеленые чёрточки).
			r_screenTexture = GL_LoadPic ("r_screenTexture", NULL, (byte*)trans, gl_config.screenTextureSize[0], gl_config.screenTextureSize[1], it_pic, 24, true, 0);	/// динамическую текстуру НЕ сжимаем!!! Иначе тормоза!
		}
		if(r_fullscreen->value)
		{
			unsigned temp = (min(vid.width, vid.height)>>4);	// bloom_size - итоговый размер bloom - в 16 раз меньше
			bloom_size = 1;
			while (1)
			{
				bloom_size *= 2;
				if(bloom_size >= temp)
					break;
			}
			temp = (min(vid.width, vid.height)>>1);	// max_bloom_size - размер bloomTexture - всего в 2 раза меньше экрана
			max_bloom_size = 1;
			while (1)
			{
				max_bloom_size *= 2;
				if(max_bloom_size >= temp)
					break;
			}
///			Memset(trans, 0, max_bloom_size*max_bloom_size*3);
			r_bloomTexture = GL_LoadPic ("r_bloomTexture", NULL, (byte*)trans, max_bloom_size, max_bloom_size, it_pic, 24, true, 0);	/// динамическую текстуру НЕ сжимаем!!! Иначе тормоза!
		}
	}

	tmp[0][0][0] = tmp[0][0][1] = tmp[0][0][2] = 0;
	tmp[0][0][3] = 0xff;
	r_dark = GL_LoadPic ("** r_dark **", NULL, (byte *)tmp, 1, 1, it_light, 32, false, 0);
	tmp[0][0][3] = 0;
	r_dark2 = GL_LoadPic ("** r_dark2 **", NULL, (byte *)tmp, 1, 1, it_light, 32, false, 0);
	tmp[0][0][0] = tmp[0][0][1] = tmp[0][0][2] = tmp[0][0][3] = 0xff;
	r_white = GL_LoadPic ("** r_white **", NULL, (byte *)tmp, 1, 1, it_light, 32, false, 0);
}


void BuildSqrtTable()	// Build the square root table
{
	unsigned i;
	FastSqrtUnion s;

	// Build the fast square root table
	for (i = 0; i <= 0x7FFF; i++)
	{
		// Build a float with the bit pattern i as mantissa
		// and an exponent of 0, stored as 127
		s.i = (i << 8) | (0x7F << 23);
		s.f = (float) sqrt(s.f);
    
		// Take the square root then strip the first 7 bits of
		// the mantissa into the table
		iFastSqrtTable[i + 0x8000] = (s.i & 0x7FFFFF);
    
		// Repeat the process, this time with an exponent of 1, 
		// stored as 128
		s.i = (i << 8) | (0x80 << 23);
		s.f = (float) sqrt(s.f);
    
		iFastSqrtTable[i] = (s.i & 0x7FFFFF);
	}
}


__forceinline float fastsqrt(float n)
{
	// Check for square root of 0
	if (FP_BITS(n) == 0)
		return 0.0;

	FP_BITS(n) = iFastSqrtTable[(FP_BITS(n) >> 8) & 0xFFFF] | ((((FP_BITS(n) - 0x3F800000) >> 1) + 0x3F800000) & 0x7F800000);
	return n;
}


// Fast reciprocal square root (Quake 3 game code)
__inline float RSqrt(float number)
{
	long i;
	float x2, y;
	const float threehalfs = 1.5f;

	x2 = number * 0.5f;
	y  = number;
	i  = * (long *) &y;						// evil floating point bit level hacking
	i  = 0x5f3759df - (i >> 1);             // what the fuck?
	y  = * (float *) &i;
	y  = y * (threehalfs - (x2 * y * y));   // 1st iteration

	return y;
}


void getCubeVector(int i, int cubesize, int x, int y, float *vector)
{
	float s, t, sc, tc, mag;

	s = ((float)x + 0.5f) / (float)cubesize;
	t = ((float)y + 0.5f) / (float)cubesize;
	sc = s*2.0f - 1.0f;
	tc = t*2.0f - 1.0f;

	switch (i)
	{
	case 0:
		vector[0] = 1.0f;
		vector[1] = -tc;
		vector[2] = -sc;
		break;
	case 1:
		vector[0] = -1.0;
	    vector[1] = -tc;
		vector[2] = sc;
	    break;
	case 2:
		vector[0] = sc;
	    vector[1] = 1.0;
		vector[2] = tc;
	    break;
	case 3:
		vector[0] = sc;
	    vector[1] = -1.0;
		vector[2] = -tc;
	    break;
	case 4:
		vector[0] = sc;
	    vector[1] = -tc;
		vector[2] = 1.0;
	    break;
	case 5:
		vector[0] = -sc;
	    vector[1] = -tc;
		vector[2] = -1.0;
	    break;
	}

	mag = RSqrt(vector[0]*vector[0] + vector[1]*vector[1] + vector[2]*vector[2]);
	vector[0] *= mag;
	vector[1] *= mag;
	vector[2] *= mag;
}


bool Com_BinClamp(float *val, float min, float max)
{
	int	i;

	if (*val<min)
	{
		*val = min;
		return true;
	}
	else if (*val>max)
	{
		*val = max;
		return true;
	}

	i=1;
	while (1)
	{
		i=i<<1;
		if(i>=*val)
			break;
	}

	if (i>*val)
	{
		*val = (i>>1);
		return true;
	}

	return false;
}


void R_InitNormalizationCubemap ()
{
	if (r_simple->value)
	{
		normcube_texture_object = r_notexture;
		return;
	}

	if (Com_BinClamp(&(r_normcube_size->value), 16,128))
	{
		Cvar_ForceSetValue("r_normcube_size", r_normcube_size->value);
		r_normcube_size->modified = false;
		Com_Printf("NormCube size: %i\n", (int)r_normcube_size->value);
		if (normcube_texture_object && normcube_texture_object->texnum)
		{
			glDeleteTextures(1, &normcube_texture_object->texnum);
			normcube_texture_object->texnum = NULL;
		}
	}

	vec3_t	vec;
	int		i, x, y, size = r_normcube_size->value;
	byte	*pix;
	image_t	*image;
	char	name[15] = "***NormalCM***";

	pix = (byte*)Z_Malloc(size*size*4, false);
	if (!pix)
		Com_Error(ERR_FATAL, "R_InitNormalizationCubemap: Error allocation memory");

	// find a free image_t
	for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (!image->texnum)
			break;
	}
	if (i == numgltextures)
	{
		if (numgltextures == MAX_GLTEXTURES)
			Com_Error (ERR_FATAL, "MAX_GLTEXTURES");
		numgltextures++;
	}
	image = &gltextures[i];

	strcpy (image->name, name);
	image->hash = Com_HashKey(name);
	image->registration_sequence = registration_sequence;

	image->width = size;
	image->height = size;
	image->upload_width = size;
	image->upload_height = size;
	image->type = it_pic;
	image->texnum = TEXNUM_IMAGES + (image - gltextures);

	normcube_texture_object = image;
	GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, normcube_texture_object->texnum);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, gl_edge_type);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, gl_edge_type);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, gl_edge_type);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	for (i = 0; i < 6; i++)
	{
		for (y = 0; y < size; y++)
		{
			for (x = 0; x < size; x++) 
			{
				getCubeVector(i, size, x, y, vec);
				pix[4*(y*size+x)+0] = (byte)(128+127*vec[2]);
				pix[4*(y*size+x)+1] = (byte)(128+127*vec[1]);
				pix[4*(y*size+x)+2] = (byte)(128+127*vec[0]);
				pix[4*(y*size+x)+3] = 255;
			}
		}
		glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+i, 0, GL_RGBA8, size, size, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pix);	// Berserker: using BGRA instead RGB
	}

	Z_Free(pix);
}


void R_FlipImage(int idx, img_t *pix, byte *dst)
{
	byte *from;
	byte *src = pix->pixels;
	int	width = pix->width;
	int	height = pix->height;
	int	x,y;

	if (idx==1)		// bk
	{
		for(y=height-1; y>=0; y--)
		{
			for(x=width-1; x>=0; x--)
			{	// copy rgb components
				from = src + (x*height + y)*3;
				dst[0] = from[2];				// swap for BGRA
				dst[1] = from[1];
				dst[2] = from[0];
				dst[3] = 255;
				dst+=4;
			}
		}
		return;
	}

	if (idx==2)		// lf
	{
		for(y=height-1; y>=0; y--)
		{
			for(x=0; x<width; x++)
			{	// copy rgb components
				from = src + (y*width + x)*3;
				dst[0] = from[2];				// swap for BGRA
				dst[1] = from[1];
				dst[2] = from[0];
				dst[3] = 255;
				dst+=4;
			}
		}
		return;
	}

	if (idx==3)		// rt
	{
		for(y=0; y<height; y++)
		{
			for(x=width-1; x>=0; x--)
			{	// copy rgb components
				from = src + (y*width + x)*3;
				dst[0] = from[2];				// swap for BGRA
				dst[1] = from[1];
				dst[2] = from[0];
				dst[3] = 255;
				dst+=4;
			}
		}
		return;
	}

	// ft, up, dn
	for(y=0; y<height; y++)
	{
		for(x=0; x<width; x++)
		{	// copy rgb components
			from = src + (x*height + y)*3;
			dst[0] = from[2];				// swap for BGRA
			dst[1] = from[1];
			dst[2] = from[0];
			dst[3] = 255;
			dst+=4;
		}
	}
}

image_t *R_LoadLightFilter (int id, char *map)
{
	int		i, minw, minh, maxw, maxh;
	image_t	*image;
	char	name[MAX_OSPATH];
	char	checkname[MAX_OSPATH];
	img_t	pix[6];
	byte	*nullpixels;
	bool	allNull = true;

	Com_sprintf (name, sizeof(name), "***Filter%2i***", id+1);

	// find a free image_t
	for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (!image->texnum)
			break;
	}
	if (i == numgltextures)
	{
		if (numgltextures == MAX_GLTEXTURES)
			Com_Error (ERR_FATAL, "MAX_GLTEXTURES");
		numgltextures++;
	}
	image = &gltextures[i];

	strcpy (image->name, name);
	image->hash = Com_HashKey(name);
	image->registration_sequence = registration_sequence;
	image->type = it_pic;
	image->texnum = TEXNUM_IMAGES + (image - gltextures);

	GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, image->texnum);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, gl_edge_type);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, gl_edge_type);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, gl_edge_type);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	minw = minh = 0;
	maxw = maxh = 9999999;
	for (i = 0; i < 6; i++)
	{
		pix[i].pixels = NULL;
		pix[i].width = pix[i].height = 0;
		if (map && map[0])
			Com_sprintf (checkname, sizeof(checkname), "textures/lights/%s/%i_%s.jpg", map, id+1, lsuf[i]);
		else
			Com_sprintf (checkname, sizeof(checkname), "textures/lights/%i_%s.jpg", id+1, lsuf[i]);

		// Berserker: stop spam
		if (FS_LoadFile(checkname, NULL) != -1)
		{
			LoadJPG (checkname, &pix[i].pixels, &pix[i].width, &pix[i].height);
			if(pix[i].width)
			{
				if (minw < pix[i].width)	minw = pix[i].width;
				if (maxw > pix[i].width)	maxw = pix[i].width;
			}

			if(pix[i].height)
			{
				if (minh < pix[i].height)	minh = pix[i].height;
				if (maxh > pix[i].height)	maxh = pix[i].height;
			}
		}
	}

	if ((minw == 0) || (minh == 0))
	{
///		Com_DPrintf("R_LoadLightFilter: filter %i does not exist\n", id+1);	// Berserker: stop spam
		minw = minh = maxw = maxh = 1;	// Для отсутствующего фильтра пусть будет фильтр 1х1 черный... (нет света)
	}

	if ((minw != maxw) || (minh != maxh) || (minw != minh))
		Com_Error(ERR_DROP, "R_LoadLightFilter: (%i) all images must be quadratic with equal sizes", id+1);

	for (i = 0; i < 6; i++)
	{
		if(pix[i].pixels)
		{
			allNull = false;
			R_FlipImage(i, &pix[i], (byte*)trans);
			Z_Free(pix[i].pixels);
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+i, 0, GL_RGBA8, minw, minh, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, trans);	// Berserker: using BGRA instead RGB
		}
		else
		{
			nullpixels = (byte*)Z_Malloc(minw*minh*4, true);
///			Memset(nullpixels, 0, minw*minh*3);
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+i, 0, GL_RGBA8, minw, minh, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullpixels);	// Berserker: using BGRA instead RGB
			Z_Free(nullpixels);
		}
	}

	image->width = minw;
	image->height = minh;
	image->upload_width = image->width * 6;
	image->upload_height = image->height * 6;
	image->filetype = file_jpg;

	if (allNull)
		image->registration_sequence = -1;	// free

	return image;
}


void R_LoadDecal (image_t **img, char *fname, filetype_t type)
{
	int		i, format;
	byte	*pixels;
	image_t	*image;

	// find a free image_t
	for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (!image->texnum)
			break;
	}
	if (i == numgltextures)
	{
		if (numgltextures == MAX_GLTEXTURES)
			Com_Error (ERR_FATAL, "MAX_GLTEXTURES");
		numgltextures++;
	}
	image = &gltextures[i];

	strcpy (image->name, fname);
	image->hash = Com_HashKey(fname);
	image->registration_sequence = registration_sequence;
	image->type = it_pic;
	image->texnum = TEXNUM_IMAGES + (image - gltextures);

	GL_Bind(GL_TEXTURE_2D, image->texnum);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gl_edge_type);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gl_edge_type);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	pixels = NULL;

	// Berserker: stop spam
	if (FS_LoadFile(fname, NULL) != -1)
	{
		switch (type)
		{
		case file_tga:
			LoadTGA (fname, &pixels, &image->width, &image->height);
			format = 32;
			break;
		case file_dds:
			LoadDDS (fname, &pixels, &image->width, &image->height);
			format = 32;
			break;
		case file_png:
			LoadPNG (fname, &pixels, &image->width, &image->height, &format);
			if(format == 32)
				RGBA2BGRA(image->width, image->height, pixels);		// RGBA -> BGRA
			break;
		case file_jpg:
			LoadJPG (fname, &pixels, &image->width, &image->height);
			format = 24;
			break;
		default:
			Com_Error (ERR_DROP, "Decal texture not DDS, PNG, TGA or JPG");
		}

		if (image->width != image->height)
			Com_Error (ERR_DROP, "Decal texture must be quadratic");

		image->upload_width = image->upload_height = image->width;
		image->filetype = type;
	}

	*img = image;
	if(pixels)
	{
		if (format == 32)
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image->width, image->width, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
		else	// 24
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image->width, image->width, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
		Z_Free(pixels);
	}
	else
		image->registration_sequence = -1;	// free
}


#define DST_SIZE 16
void R_InitDistortTextures ()
{
///	dst_texture = dst_texture_water = r_notexture;

	if(!r_simple->value && gl_config.arb_distort && arbdistort_program!=0xffffffff)
	{
		signed char dist[DST_SIZE][DST_SIZE][3];
		int x,y;

		for (x=0; x<DST_SIZE; x++)
		{
			for (y=0; y<DST_SIZE; y++)
			{
				dist[x][y][0] = rand()%48-24;
				dist[x][y][1] = rand()%48-24;
				dist[x][y][2] = 0;
			}
		}

		image_t		*image;
		char		name[18] = "**** Distort ****";
		int			i;

		// find a free image_t
		for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
			if (!image->texnum)
				break;

		if (i == numgltextures)
		{
			if (numgltextures == MAX_GLTEXTURES)
				Com_Error (ERR_FATAL, "MAX_GLTEXTURES");
			numgltextures++;
		}

		image = &gltextures[i];
		strcpy (image->name, name);
		image->hash = Com_HashKey(name);
		image->registration_sequence = registration_sequence;
		image->upload_height = image->upload_width = image->width = image->height = DST_SIZE;
		image->type = it_pic;
		image->texnum = TEXNUM_IMAGES + (image - gltextures);
		dst_texture = image;

		GL_Bind2D(image->texnum);
		glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB8, DST_SIZE, DST_SIZE, 0, GL_RGB, GL_BYTE, dist);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	}

	if(gl_config.texshaders/* && r_shader->value==SHADER_NVIDIA4*/)
	{
		signed char data[DST_SIZE][DST_SIZE][2];
		int x,y;

		for (x=0;x<DST_SIZE;x++)
			for (y=0;y<DST_SIZE;y++)
			{
				data[x][y][0]=rand()%255-128;
				data[x][y][1]=rand()%255-128;
			}

		image_t		*image;
		char		name[18] = "**** Water ****";
		int			i;

		// find a free image_t
		for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
			if (!image->texnum)
				break;

		if (i == numgltextures)
		{
			if (numgltextures == MAX_GLTEXTURES)
				Com_Error (ERR_FATAL, "MAX_GLTEXTURES");
			numgltextures++;
		}

		image = &gltextures[i];
		strcpy (image->name, name);
		image->hash = Com_HashKey(name);
		image->registration_sequence = registration_sequence;
		image->upload_height = image->upload_width = image->width = image->height = DST_SIZE;
		image->type = it_pic;
		image->texnum = TEXNUM_IMAGES + (image - gltextures);
		dst_texture_water = image;

		GL_Bind2D(image->texnum);
		glTexImage2D (GL_TEXTURE_2D, 0, GL_DSDT8_NV, DST_SIZE, DST_SIZE, 0, GL_DSDT_NV,	GL_BYTE, data);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	}
}


#define ATTEN_VOLUME_SIZE 64	// Size of 3d attenuation texture (requires of 64*64*64 = 256KB of VRAM)	FIXME: control via CFG!!!
void R_InitAttenuation ()
{
	if (r_simple->value)
	{
		atten3d_texture_object = r_notexture;
		return;
	}

	float	center, power;
	int		s, t, r, offs = 0;
	byte	data[ATTEN_VOLUME_SIZE*ATTEN_VOLUME_SIZE*ATTEN_VOLUME_SIZE];

	power = r_attenpower->value;
	if (power < 0.25)
		power = 0.25;
	else if (power > 2)
		power = 2;

	center = ATTEN_VOLUME_SIZE*0.5;

	for (s = 0; s < ATTEN_VOLUME_SIZE; s++)
	{
		for (t = 0; t < ATTEN_VOLUME_SIZE; t++)
		{
			for (r = 0; r < ATTEN_VOLUME_SIZE; r++)
			{
				float DistSq = Sqrt((s-center)*(s-center)+(t-center)*(t-center)+(r-center)*(r-center));
				if (DistSq < center)
				{
					byte value;
					float FallOff = pow((center - DistSq) / center, power);
					if(s==0 || s==ATTEN_VOLUME_SIZE-1 ||
					   t==0 || t==ATTEN_VOLUME_SIZE-1 ||
					   r==0 || r==ATTEN_VOLUME_SIZE-1)
						value = 0;
					else
						value = FallOff*255.0;

					data[offs++] = value;
				}
				else
					data[offs++] = 0;
			}
		}
	}

	image_t		*image;
	char		name[18] = "**attenuation3D**";
	int			i;

	// find a free image_t
	for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (!image->texnum)
			break;
	}
	if (i == numgltextures)
	{
		if (numgltextures == MAX_GLTEXTURES)
			Com_Error (ERR_FATAL, "MAX_GLTEXTURES");
		numgltextures++;
	}
	image = &gltextures[i];

	strcpy (image->name, &name[0]);
	image->registration_sequence = registration_sequence;
	image->hash = Com_HashKey(image->name);
	image->width = image->height = ATTEN_VOLUME_SIZE;
	image->upload_width = ATTEN_VOLUME_SIZE * ATTEN_VOLUME_SIZE * ATTEN_VOLUME_SIZE;
	image->upload_height = 1;
	image->type = it_pic;

	image->texnum = TEXNUM_IMAGES + (image - gltextures);

	atten3d_texture_object = image;
	GL_Bind(GL_TEXTURE_3D, atten3d_texture_object->texnum);
	glTexImage3DEXT(GL_TEXTURE_3D, 0, GL_INTENSITY, ATTEN_VOLUME_SIZE, ATTEN_VOLUME_SIZE, ATTEN_VOLUME_SIZE, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, gl_edge_type);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, gl_edge_type);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, gl_edge_type);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);	
}


bool Draw_InitFonts ()
{
	// load console characters (don't bilerp characters)
	draw_chars = GL_FindImage ("pics/conchars.tga", it_pic, true, 0, false, 0);				// Явно укажем - TGA, символы д/б с "дырками" в альфа-канале
	draw_conback = GL_FindImage ("pics/conback.tga", it_pic, true, 0, false, 0);
	draw_conback_bump = GL_FindImage ("pics/conback_bump.tga", it_pic, true, 0, false, 0);
	draw_default = GL_FindImage ("pics/levelshots/default", it_pic, false, 0, false, 0);

	if(draw_chars)
	{
		GL_Bind2D( draw_chars->texnum );
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		return true;
	}
	return false;
}


void ScreenSizeCallback( void *s )
{
	menuslider_s *slider = ( menuslider_s * ) s;

	Cvar_SetValue( "viewsize", slider->curvalue * 10 );
}


void SysGammaCallback( void *s )
{
	Cvar_SetValue( "vid_gamma", s_gamma_slider.curvalue * 0.1 );
	Cvar_SetValue( "vid_bright", s_bright_slider.curvalue * 0.1 );
	Cvar_SetValue( "vid_contrast", s_contrast_slider.curvalue * 0.1 );
	SetGamma(vid_gamma->value, vid_bright->value, vid_contrast->value);
}


void AnisotropyCallback( void *s )
{
///	s_anisotropy_slider_.curvalue = s_anisotropy_slider.curvalue;
}


void AntialiasCallback( void *s )
{
///	s_antialias_slider_.curvalue = s_antialias_slider.curvalue;
}


void Key_Event (int key, bool down, unsigned time);
void Key_ClearStates ()
{
	int		i;
	anykeydown = false;
	for (i=0 ; i<256 ; i++)
	{
		if ( keydown[i] || key_repeats[i] )
			Key_Event( i, false, 0 );
		keydown[i] = 0;
		key_repeats[i] = 0;
	}
}


void M_FreeCredits()
{
	if (creditsPlay)
	{
		creditsPlay = NULL;
		savedTrackName[0] = 0;
		Music_Stop();
	}
	if (creditsBuffer)
	{
		Z_Free (creditsBuffer);
		creditsBuffer = NULL;
	}
}

void M_ForceMenuOff()
{
	m_drawfunc = 0;
	m_keyfunc = 0;
	cls.key_dest = key_game;
	m_menudepth = 0;
	Key_ClearStates ();
	Cvar_Set ("paused", "0");
	M_FreeCredits();
}


void Check_VidRestart(bool restart)
{
	/// Berserker: "мягкий" рестарт чисто для r_ext_pointparameters
	if (r_ext_pointparameters && r_ext_pointparameters->modified)
	{
		if (!r_ext_pointparameters->value)
		{
			glDisable( GL_POINT_SMOOTH );
			r_ext_pointparameters->modified = false;
		}
		else
		{
			if (r_particle_min_size && r_particle_max_size && r_particle_att_a && r_particle_att_b && r_particle_att_c)
			{
				float attenuations[3];
				attenuations[0] = r_particle_att_a->value;
				attenuations[1] = r_particle_att_b->value;
				attenuations[2] = r_particle_att_c->value;
				glEnable( GL_POINT_SMOOTH );
				glPointParameterfEXT( GL_POINT_SIZE_MIN_EXT, r_particle_min_size->value );
				glPointParameterfEXT( GL_POINT_SIZE_MAX_EXT, r_particle_max_size->value );
				glPointParameterfvEXT( GL_DISTANCE_ATTENUATION_EXT, attenuations );
				r_ext_pointparameters->modified = false;
			}
		}
	}

	int		i;
	image_t	*glt;
	char	str[MAX_QPATH];
	/// Berserker: "мягкий" рестарт чисто для r_occlusion
	if (r_occlusion && r_occlusion->vid_latched_string)
	{
		strncpy(str, r_occlusion->vid_latched_string, sizeof(str)-1);
		Cvar_ForceSet("r_occlusion", str);
		if (!r_occlusion->value)
			gl_config.occlusion = 0;
		else
			GL_CheckOcclusion();
		r_occlusion->modified = false;
	}

	/// Berserker: "мягкий" рестарт чисто для r_normcube_size
	bool nc_changed = false;
	if (r_normcube_size && r_normcube_size->vid_latched_string)
	{
		strncpy(str, r_normcube_size->vid_latched_string, sizeof(str)-1);
		Cvar_ForceSet("r_normcube_size", str);
		r_normcube_size->modified = false;
		nc_changed = true;
	}

	if (nc_changed || (r_normcube_size && Com_BinClamp(&(r_normcube_size->value), 16,128)))
	{
		Cvar_ForceSetValue("r_normcube_size", r_normcube_size->value);
		r_normcube_size->modified = false;
		glDeleteTextures(1, &normcube_texture_object->texnum);
		normcube_texture_object->texnum = NULL;
		R_InitNormalizationCubemap();
	}

	bool	cha = false;
	/// Berserker: "мягкий" рестарт чисто для r_overbright
	if (r_overbright && r_overbright->vid_latched_string)
		if (r_shader && !r_shader->vid_latched_string &&
			(r_shader->value == SHADER_GENERIC4 ||
			r_shader->value == SHADER_GENERIC6 ||
			r_shader->value == SHADER_ARB4 ||
			r_shader->value == SHADER_ARB6))
			{
				strncpy(str, r_overbright->vid_latched_string, sizeof(str)-1);
				Cvar_ForceSet("r_overbright", str);
				if(r_overbright->value < 0)
					r_overbright->value = 0;
				if(r_overbright->value > 1)
					r_overbright->value = 1;
				cha = true;
				r_overbright->modified = false;
			}


	/// Berserker: "мягкий" рестарт чисто для r_parallax_scale
	if (r_parallax_scale && r_parallax_scale->vid_latched_string)
	{
		strncpy(str, r_parallax_scale->vid_latched_string, sizeof(str) - 1);
		Cvar_ForceSet("r_parallax_scale", str);
		cha = true;
		r_parallax_scale->modified = false;
	}

	/// Berserker: "мягкий" рестарт чисто для r_diffuse_modifier
	if (r_diffuse_modifier && r_diffuse_modifier->vid_latched_string)
	{
		strncpy(str, r_diffuse_modifier->vid_latched_string, sizeof(str)-1);
		Cvar_ForceSet("r_diffuse_modifier", str);
		cha = true;
		r_diffuse_modifier->modified = false;
	}

	/// Berserker: "мягкий" рестарт чисто для r_specular_modifier
	if (r_specular_modifier && r_specular_modifier->vid_latched_string)
	{
		strncpy(str, r_specular_modifier->vid_latched_string, sizeof(str)-1);
		Cvar_ForceSet("r_specular_modifier", str);
		cha = true;
		r_specular_modifier->modified = false;
	}

	if (cha)
		if (r_shader)
		{
			if (r_shader->value == SHADER_ARB4 || r_shader->value == SHADER_ARB6)
			{
				if (r_overbright->value)
				{
					glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 2 * r_diffuse_modifier->value, 2 * r_diffuse_modifier->value, 2 * r_diffuse_modifier->value, 2 * r_diffuse_modifier->value);
					glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, 2 * r_specular_modifier->value, 2 * r_specular_modifier->value, 2 * r_specular_modifier->value, 2 * r_specular_modifier->value);
				}
				else
				{
					glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, r_diffuse_modifier->value, r_diffuse_modifier->value, r_diffuse_modifier->value, r_diffuse_modifier->value);
					glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, r_specular_modifier->value, r_specular_modifier->value, r_specular_modifier->value, r_specular_modifier->value);
				}
			}
			if (r_shader->value == SHADER_ARB6 && r_parallax->value)
			{
				float prlxScale = r_parallax_scale->value;
				if (prlxScale < 0)	prlxScale = 0;
				else if (prlxScale > 3)	prlxScale = 3;
				prlxScale += 1;
				prlxScale *= 0.0104167;
				glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 4, prlxScale, prlxScale, -prlxScale*0.5, -prlxScale*0.5);
			}
		}

	/// Berserker: "мягкий" рестарт чисто для r_anisotropy
	if (r_anisotropy && r_anisotropy->vid_latched_string)
	{
		strncpy(str, r_anisotropy->vid_latched_string, sizeof(str)-1);
		Cvar_ForceSet("r_anisotropy", str);
		if(r_anisotropy->value < 2)
			r_anisotropy->value = 0;
		else
			r_anisotropy->value = min(gl_config.anisotropy_level, r_anisotropy->value);
		// change all the existing mipmap texture objects
		for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
		{
			if (glt->type != it_pic && glt->type != it_sky && glt->type != it_sprite && glt->type != it_light && glt->type != it_fx)
			{
				GL_Bind2D (glt->texnum);
				GL_SetAnisotropy();
			}
		}
		r_anisotropy->modified = false;
	}

	/// Berserker: "мягкий" рестарт чисто для r_mirror_size
	if (r_mirror_size && r_mirror_size->vid_latched_string)
	{
		strncpy(str, r_mirror_size->vid_latched_string, sizeof(str)-1);
		Cvar_ForceSet("r_mirror_size", str);
		GL_SelectMirrorSize(true);
		for (i=0; i<MAX_MIRRORS; i++)
		{
			glDeleteTextures(1, &mirrors[i].texture->texnum);
			mirrors[i].texture->texnum = NULL;
			mirrors[i].texture = GL_LoadPic (va("Mirror %i",i), NULL, (byte*)trans, gl_config.mirrorTextureSize, gl_config.mirrorTextureSize, it_pic, 32, true, 0);	/// динамическую текстуру НЕ сжимаем!!! Иначе тормоза!
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gl_edge_type );
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gl_edge_type );
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		}
		vid_restart = false;
		r_mirror_size->modified = false;
	}

	/// Berserker: "мягкий" рестарт чисто для r_attenpower
	if (r_attenpower && r_attenpower->vid_latched_string)
	{
		strncpy(str, r_attenpower->vid_latched_string, sizeof(str)-1);
		Cvar_ForceSet("r_attenpower", str);
		glDeleteTextures(1, &atten3d_texture_object->texnum);
		atten3d_texture_object->texnum = NULL;
		R_InitAttenuation();
		r_attenpower->modified = false;
	}

	/// Berserker: "мягкий" рестарт чисто для r_texturemode
	if (r_texturemode && r_texturemode->vid_latched_string)
	{
		strncpy(str, r_texturemode->vid_latched_string, sizeof(str)-1);
		Cvar_ForceSet("r_texturemode", str);
		GL_TextureMode( r_texturemode->string );
		r_texturemode->modified = false;
	}

	/// Berserker: "мягкий" рестарт чисто для r_offsetFactor
	if (r_offsetFactor && r_offsetFactor->vid_latched_string)
	{
		strncpy(str, r_offsetFactor->vid_latched_string, sizeof(str)-1);
		Cvar_ForceSet("r_offsetFactor", str);
		if (r_offsetUnits)	/// PVS-Studio
			glPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
		r_offsetFactor->modified = false;
	}

	/// Berserker: "мягкий" рестарт чисто для r_offsetUnits
	if (r_offsetUnits && r_offsetUnits->vid_latched_string)
	{
		strncpy(str, r_offsetUnits->vid_latched_string, sizeof(str)-1);
		Cvar_ForceSet("r_offsetUnits", str);
		if (r_offsetFactor)	/// PVS-Studio
			glPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
		r_offsetUnits->modified = false;
	}

	if (restart)
	{
		for (cvar_t	*var = cvar_vars ; var ; var = var->next)
			if (var->flags & CVAR_VID_LATCH)
				if (var->modified || var->vid_latched_string)
				{
					vid_restart = true;
					break;
				}
	}
}


void ApplyChanges( void *unused )
{
///	float gamma;

	/*
	** make values consistent
	*/
///	s_fs_box_.curvalue = s_fs_box.curvalue;
///	s_brightness_slider_.curvalue = s_brightness_slider.curvalue;
///	s_anisotropy_slider_.curvalue = s_anisotropy_slider.curvalue;
///	s_antialias_slider_.curvalue = s_antialias_slider.curvalue;

	/*
	** invert sense so greater = brighter, and scale to a range of 0.5 to 1.3
	*/
///	gamma = ( 0.8 - ( s_brightness_slider.curvalue/10.0 - 0.5 ) ) + 0.5;
///	Cvar_SetValue( "r_gamma", gamma );

	Cvar_SetValue( "r_picmip", 3 - s_tq_slider.curvalue );
	Cvar_SetValue( "r_picmip_bump", 3 - s_tq_slider.curvalue );
	Cvar_SetValue( "r_fullscreen", s_fs_box.curvalue );
	Cvar_SetValue( "r_swapinterval"/*"r_finish"*/, s_finish_box.curvalue );
	Cvar_SetValue( "r_nvMultisampleFilterHint", s_nvhint_box.curvalue );
	Cvar_SetValue( "r_mode", s_mode_list.curvalue );
	Cvar_SetValue( "r_modulate", s_lmscale_slider.curvalue * 0.1 );
	Cvar_SetValue( "r_overbright", s_bmscale_slider.curvalue );
	if(vid_ignorehwgamma->value)
	{
		Cvar_SetValue( "r_intensity", s_bright_slider.curvalue * 0.1 );
		Cvar_SetValue( "r_gamma", s_gamma_slider.curvalue * 0.1 );
	}
	Cvar_SetValue( "r_shader", s_shader_list.curvalue );
	Cvar_SetValue( "r_simple", s_simple.curvalue );
///	Cvar_Set( "r_texturemode", (char *)filters[s_filter_list.curvalue] );
	Cvar_Set( "r_texturesolidmode", (char *)solids[s_solid_list.curvalue] );
	Cvar_Set( "r_texturealphamode", (char *)alphas[s_alpha_list.curvalue] );
	Cvar_ForceSetValue( "r_diffuse_compression", s_compression_box.curvalue );
	Cvar_ForceSetValue( "r_bump_compression", s_bump_compression_box.curvalue );
	if(s_anisotropy_slider.curvalue >= 2)
		Cvar_SetValue( "r_anisotropy", pow(2.0, s_anisotropy_slider.curvalue-1.0) );
	else
		Cvar_SetValue( "r_anisotropy", 0 );
	if(s_antialias_slider.curvalue > 1)
		Cvar_ForceSetValue( "r_multiSamples", pow(2.0, s_antialias_slider.curvalue-1.0) );
	else
		Cvar_ForceSetValue( "r_multiSamples", 1 );


///	if(s_bits_list.curvalue == 1)
///		Cvar_SetValue( "r_bitdepth", 16 );
///	else
///		if(s_bits_list.curvalue == 2)
///			Cvar_SetValue( "r_bitdepth", 32 );
///		else
///			Cvar_SetValue( "r_bitdepth", 0 );

	/*
	** update appropriate stuff if we're running OpenGL has been modified
	*/
	Check_VidRestart(true);

	M_ForceMenuOff();
}


int	Vid_GetSafeMode(bool msg)
{
	int i, x, y;

	HDC hdc = GetDC(NULL);
	x = GetDeviceCaps(hdc, HORZRES);
	y = GetDeviceCaps(hdc, VERTRES);

	for (i=0; i<_VID_NUM_MODES; i++)
		if (vid_modes[i].width == x && vid_modes[i].height == y)
		{
stp:		if (msg)
				Com_Printf("^3Recommended safe mode %i: %ix%i\n", i, x, y);
			return i;
		}

	i = 3;	// safe mode
	x = vid_modes[i].width;
	y = vid_modes[i].height;
	goto stp;
}


void BloomFunc( void *unused )
{
	Cvar_SetValue( "r_bloom", s_r_bloom.curvalue );
}

void MirrorsFunc( void *unused )
{
	Cvar_SetValue( "r_mirrors", s_r_mirrors.curvalue );
}


void _3DHudFunc( void *unused )
{
	Cvar_SetValue( "scr_3dhud", s_3dhud.curvalue );
}

void BigHudFunc( void *unused )
{
	Cvar_SetValue( "scr_bighud", s_bighud.curvalue );
}

void fpsFunc( void *unused )
{
	Cvar_SetValue( "scr_fps", s_fps.curvalue );
}

void viewPosFunc( void *unused )
{
	Cvar_SetValue( "scr_viewpos", s_viewpos.curvalue );
}

void mapShotFunc( void *unused )
{
	Cvar_SetValue( "scr_mapshots", s_mapshots.curvalue );
}

void EFXzonesFunc(void *unused)
{
	Cvar_SetValue("r_showEFXzones", s_EFXzones.curvalue);
}

void gameTimeFunc( void *unused )
{
	Cvar_SetValue( "scr_gametime", s_gametime.curvalue );
}

void showTextureFunc( void *unused )
{
	Cvar_SetValue( "scr_texture", s_showtexture.curvalue );
}

void drawSpeedFunc( void *unused )
{
	Cvar_SetValue( "scr_drawspeed", s_drawspeed.curvalue );
}

void DrawClockFunc( void *unused )
{
	Cvar_SetValue( "scr_drawclock", s_drawclock.curvalue );
}

void MirrorsModelFunc( void *unused )
{
	Cvar_SetValue( "r_mirror_models", s_r_mirror_models.curvalue );
}

void StaticLightsFunc( void *unused )
{
	Cvar_SetValue( "r_nolights", 1 - s_r_nolights.curvalue );
}

void ShellsFunc( void *unused )
{
	Cvar_SetValue( "r_noshells", 1 - s_r_noshells.curvalue );
}

void DecalsFunc( void *unused )
{
	Cvar_SetValue( "r_decals", s_r_decals.curvalue );
}

void CausticsFunc( void *unused )
{
	Cvar_SetValue( "r_caustics", s_r_caustics.curvalue );
}

void FlaresFunc( void *unused )
{
	Cvar_SetValue( "r_flares", s_r_flares.curvalue * 4 );
}

void DrawSunFunc( void *unused )
{
	Cvar_SetValue( "r_drawsun", s_r_drawsun.curvalue );
}

void ShadowsFunc( void *unused )
{
	Cvar_SetValue( "r_shadows", s_r_shadows.curvalue );
	if (r_shadows->value)
		Cvar_SetValue("r_worldshadows", 1);
	else
		Cvar_SetValue("r_worldshadows", 0);
}

void MirrorShadowsFunc( void *unused )
{
	Cvar_SetValue( "r_mirror_shadows", s_r_mirror_shadows.curvalue );
}

void MirrorSizeFunc( void *unused )
{
	float mirror_scale;
	int vsize = min(viddef.width, viddef.height);

	if (s_r_mirror_size.curvalue == 0)
		mirror_scale = 1.0/4.0;
	else if (s_r_mirror_size.curvalue == 1)
		mirror_scale = 1.0/2.0;
	else
		mirror_scale = 1.0;

	Cvar_SetValue("r_mirror_size", mirror_scale * vsize);
}

void MirrorRefreshFunc( void *unused )
{
	if (s_r_mirror_frameskip.curvalue == 0)
		Cvar_SetValue( "r_mirror_frameskip", -60 );
	else if (s_r_mirror_frameskip.curvalue == 1)
		Cvar_SetValue( "r_mirror_frameskip", -55 );
	else if (s_r_mirror_frameskip.curvalue == 2)
		Cvar_SetValue( "r_mirror_frameskip", -50 );
	else if (s_r_mirror_frameskip.curvalue == 3)
		Cvar_SetValue( "r_mirror_frameskip", -45 );
	else if (s_r_mirror_frameskip.curvalue == 4)
		Cvar_SetValue( "r_mirror_frameskip", -40 );
	else if (s_r_mirror_frameskip.curvalue == 5)
		Cvar_SetValue( "r_mirror_frameskip", -35 );
	else if (s_r_mirror_frameskip.curvalue == 6)
		Cvar_SetValue( "r_mirror_frameskip", -30 );
	else if (s_r_mirror_frameskip.curvalue == 7)
		Cvar_SetValue( "r_mirror_frameskip", -25 );
	else if (s_r_mirror_frameskip.curvalue == 8)
		Cvar_SetValue( "r_mirror_frameskip", -20 );
	else if (s_r_mirror_frameskip.curvalue == 9)
		Cvar_SetValue( "r_mirror_frameskip", -15 );
	else if (s_r_mirror_frameskip.curvalue == 10)
		Cvar_SetValue( "r_mirror_frameskip", 0 );
	else if (s_r_mirror_frameskip.curvalue == 11)
		Cvar_SetValue( "r_mirror_frameskip", 1 );
	else if (s_r_mirror_frameskip.curvalue == 12)
		Cvar_SetValue( "r_mirror_frameskip", 2 );
	else
		Cvar_SetValue( "r_mirror_frameskip", 4 );
}

void PlayerShadowFunc( void *unused )
{
	Cvar_SetValue( "r_playershadow", s_playershadow.curvalue );
}

void PersonalGibsFunc( void *unused )
{
	Cvar_SetValue( "r_personal_gibs", s_r_personal_gibs.curvalue );
}

void DynamicShadowsFunc( void *unused )
{
	Cvar_SetValue( "dl_shadows", s_dl_shadows.curvalue );
}

void TinyShadowsFunc( void *unused )
{
	Cvar_SetValue( "r_teshadows", s_r_teshadows.curvalue );
}

void MirrorDistanceFunc( void *unused )
{
	Cvar_Set( "r_mirror_distance", s_r_mirror_distance_field.buffer );
	if (Cvar_VariableValue("r_mirror_distance") < 128)
		Cvar_SetValue("r_mirror_distance", 128);
	Menu_DrawStatusBar("Mirror disappearance distance, min 128");
}

void Mirrors_MenuInit()
{
	int mirror_refresh;
	if (r_mirror_frameskip->value <= -60)
		mirror_refresh = 0;		// 60 fps
	else if (r_mirror_frameskip->value <= -55)
		mirror_refresh = 1;		// 55 fps
	else if (r_mirror_frameskip->value <= -50)
		mirror_refresh = 2;		// 50 fps
	else if (r_mirror_frameskip->value <= -45)
		mirror_refresh = 3;		// 45 fps
	else if (r_mirror_frameskip->value <= -40)
		mirror_refresh = 4;		// 40 fps
	else if (r_mirror_frameskip->value <= -35)
		mirror_refresh = 5;		// 35 fps
	else if (r_mirror_frameskip->value <= -30)
		mirror_refresh = 6;		// 30 fps
	else if (r_mirror_frameskip->value <= -25)
		mirror_refresh = 7;		// 25 fps
	else if (r_mirror_frameskip->value <= -20)
		mirror_refresh = 8;		// 20 fps
	else if (r_mirror_frameskip->value <= -15)
		mirror_refresh = 9;		// 15 fps
	else if (r_mirror_frameskip->value == 0)
		mirror_refresh = 10;		// every frame
	else if (r_mirror_frameskip->value < 2)
		mirror_refresh = 11;		// skip 1 frame
	else if (r_mirror_frameskip->value < 3)
		mirror_refresh = 12;		// skip 2 frames
	else
		mirror_refresh = 13;		// skip 4 frames

	if (Cvar_VariableValue("r_mirrors") < 0)	Cvar_ForceSetValue("r_mirrors", 0);
	else if (Cvar_VariableValue("r_mirrors") > 1)	Cvar_ForceSetValue("r_mirrors", 1);

	int vsize = min(viddef.width, viddef.height);
	float mirror_scale = gl_config.mirrorTextureSize / (float)vsize;
	if ((mirror_scale <= 0) || (mirror_scale > 1))
		mirror_scale = 1;
	int mirror_size;
	if (mirror_scale <= 1.0/4.0)
		mirror_size = 0;
	else if (mirror_scale <= 1.0/2.0)
		mirror_size = 1;
	else
		mirror_size = 2;

	if ((Cvar_VariableValue("r_bloom") < 0) || (Cvar_VariableValue("r_bloom") > 3))
		Cvar_ForceSetValue("r_bloom", 0);

	if (Cvar_VariableValue("r_nolights") < 0)
		Cvar_SetValue("r_nolights", 0);
	if (Cvar_VariableValue("r_nolights") > 1)
		Cvar_SetValue("r_nolights", 1);

	if (Cvar_VariableValue("r_drawsun") < 0)
		Cvar_SetValue("r_drawsun", 0);
	if (Cvar_VariableValue("r_drawsun") > 2)
		Cvar_SetValue("r_drawsun", 2);

	if (Cvar_VariableValue("r_decals") < 0)
		Cvar_SetValue("r_decals", 0);
	if (Cvar_VariableValue("r_decals") > 1)
		Cvar_SetValue("r_decals", 1);

	if (Cvar_VariableValue("r_caustics") < 0)
		Cvar_SetValue("r_caustics", 0);
	if (Cvar_VariableValue("r_caustics") > 1)
		Cvar_SetValue("r_caustics", 1);

	if (Cvar_VariableValue("r_flares") < 0)
		Cvar_SetValue("r_flares", 0);
	if (Cvar_VariableValue("r_flares") > 0)
		Cvar_SetValue("r_flares", 4);

	if (Cvar_VariableValue("r_flares") < 0)
		Cvar_SetValue("r_flares", 0);
	if (Cvar_VariableValue("r_flares") > 0)
		Cvar_SetValue("r_flares", 4);

	if (Cvar_VariableValue("r_playershadow") < 0)
		Cvar_SetValue("r_playershadow", 0);
	if (Cvar_VariableValue("r_playershadow") > 0)
		Cvar_SetValue("r_playershadow", 1);

	if (Cvar_VariableValue("r_shadows") < 0)
		Cvar_SetValue("r_shadows", 0);
	if (Cvar_VariableValue("r_shadows") > 2)
		Cvar_SetValue("r_shadows", 2);
	if (r_shadows->value)
		Cvar_SetValue("r_worldshadows", 1);
	else
		Cvar_SetValue("r_worldshadows", 0);

	if (Cvar_VariableValue("dl_shadows") < 0)
		Cvar_SetValue("dl_shadows", 0);
	if (Cvar_VariableValue("dl_shadows") > 1)
		Cvar_SetValue("dl_shadows", 1);

	if (Cvar_VariableValue("r_mirror_shadows") < 0)
		Cvar_SetValue("r_mirror_shadows", 0);
	if (Cvar_VariableValue("r_mirror_shadows") > 2)
		Cvar_SetValue("r_mirror_shadows", 2);

	if (Cvar_VariableValue("r_teshadows") < 0)
		Cvar_SetValue("r_teshadows", 0);
	if (Cvar_VariableValue("r_teshadows") > 1)
		Cvar_SetValue("r_teshadows", 1);

	if (Cvar_VariableValue("r_mirror_distance") < 128)
		Cvar_SetValue("r_mirror_distance", 128);

	if (Cvar_VariableValue("r_mirror_models") < 0)
		Cvar_SetValue("r_mirror_models", 0);
	if (Cvar_VariableValue("r_mirror_models") > 1)
		Cvar_SetValue("r_mirror_models", 1);

	if (Cvar_VariableValue("r_personal_gibs") < 0)
		Cvar_SetValue("r_personal_gibs", 0);
	if (Cvar_VariableValue("r_personal_gibs") > 1)
		Cvar_SetValue("r_personal_gibs", 1);


	s_mirroroptions_menu.x = viddef.width * 0.50;
	s_mirroroptions_menu.nitems = 0;

	s_r_mirrors.generic.type	= MTYPE_SPINCONTROL;
	s_r_mirrors.generic.x = 0;
	s_r_mirrors.generic.y = 0;
	s_r_mirrors.generic.name = "Mirrors";
	s_r_mirrors.curvalue = r_mirrors->value;
	s_r_mirrors.itemnames = EnableDisable_names;
	s_r_mirrors.generic.callback = MirrorsFunc;

	s_r_mirror_frameskip.generic.type	= MTYPE_SPINCONTROL;
	s_r_mirror_frameskip.generic.x = 0;
	s_r_mirror_frameskip.generic.y = 10;
	s_r_mirror_frameskip.generic.name = "Mirror's refresh";
	s_r_mirror_frameskip.curvalue = mirror_refresh;
	s_r_mirror_frameskip.itemnames = MirrorRefresh_names;
	s_r_mirror_frameskip.generic.callback = MirrorRefreshFunc;

	s_r_mirror_size.generic.type	= MTYPE_SPINCONTROL;
	s_r_mirror_size.generic.x = 0;
	s_r_mirror_size.generic.y = 20;
	s_r_mirror_size.generic.name = "Mirror's quality";
	s_r_mirror_size.curvalue = mirror_size;
	s_r_mirror_size.itemnames = MirrorSize_names;
	s_r_mirror_size.generic.callback = MirrorSizeFunc;

	s_r_mirror_models.generic.type	= MTYPE_SPINCONTROL;
	s_r_mirror_models.generic.x = 0;
	s_r_mirror_models.generic.y = 30;
	s_r_mirror_models.generic.name = "Models in mirror";
	s_r_mirror_models.curvalue = r_mirror_models->value;
	s_r_mirror_models.itemnames = EnableDisable_names;
	s_r_mirror_models.generic.statusbar = "Draw models in mirror";
	s_r_mirror_models.generic.callback = MirrorsModelFunc;

	s_r_mirror_distance_title.generic.type	= MTYPE_SEPARATOR;
	s_r_mirror_distance_title.generic.name	= "Max mirror dist.";
	s_r_mirror_distance_title.generic.x		= -16;
	s_r_mirror_distance_title.generic.y		= 44;

	s_r_mirror_distance_field.generic.type = MTYPE_FIELD;
	s_r_mirror_distance_field.generic.name = 0;
	s_r_mirror_distance_field.generic.flags = QMF_NUMBERSONLY;
	s_r_mirror_distance_field.generic.x	= 0;
	s_r_mirror_distance_field.generic.y	= 44;
	s_r_mirror_distance_field.length = 5;
	s_r_mirror_distance_field.visible_length = 5;
	s_r_mirror_distance_field.generic.statusbarfunc = MirrorDistanceFunc;
	strcpy( s_r_mirror_distance_field.buffer, Cvar_VariableString("r_mirror_distance") );

	s_r_bloom.generic.type	= MTYPE_SPINCONTROL;
	s_r_bloom.generic.x = 0;
	s_r_bloom.generic.y = 58;
	s_r_bloom.generic.name = "Bloom";
	s_r_bloom.curvalue = r_bloom->value;
	s_r_bloom.itemnames = bloom_names;
	s_r_bloom.generic.callback = BloomFunc;

	s_r_drawsun.generic.type	= MTYPE_SPINCONTROL;
	s_r_drawsun.generic.x = 0;
	s_r_drawsun.generic.y = 68;
	s_r_drawsun.generic.name = "Sun";
	s_r_drawsun.curvalue = r_drawsun->value;
	s_r_drawsun.itemnames = sun_names;
	s_r_drawsun.generic.callback = DrawSunFunc;

	s_r_decals.generic.type	= MTYPE_SPINCONTROL;
	s_r_decals.generic.x = 0;
	s_r_decals.generic.y = 78;
	s_r_decals.generic.name = "Decals";
	s_r_decals.curvalue = r_decals->value;
	s_r_decals.itemnames = EnableDisable_names;
	s_r_decals.generic.callback = DecalsFunc;

	s_r_caustics.generic.type	= MTYPE_SPINCONTROL;
	s_r_caustics.generic.x = 0;
	s_r_caustics.generic.y = 88;
	s_r_caustics.generic.name = "Underwater caustics";
	s_r_caustics.curvalue = r_caustics->value;
	s_r_caustics.itemnames = EnableDisable_names;
	s_r_caustics.generic.callback = CausticsFunc;

	s_r_flares.generic.type	= MTYPE_SPINCONTROL;
	s_r_flares.generic.x = 0;
	s_r_flares.generic.y = 98;
	s_r_flares.generic.name = "Flares";
	s_r_flares.curvalue = ((bool)r_flares->value);
	s_r_flares.itemnames = EnableDisable_names;
	s_r_flares.generic.statusbar = "Light flares";
	s_r_flares.generic.callback = FlaresFunc;

	s_r_noshells.generic.type	= MTYPE_SPINCONTROL;
	s_r_noshells.generic.x = 0;
	s_r_noshells.generic.y = 108;
	s_r_noshells.generic.name = "Power shells";
	s_r_noshells.curvalue = 1 - r_noshells->value;
	s_r_noshells.itemnames = EnableDisable_names;
	s_r_noshells.generic.statusbar = "Shells for power effects and god mode";
	s_r_noshells.generic.callback = ShellsFunc;

	s_r_nolights.generic.type	= MTYPE_SPINCONTROL;
	s_r_nolights.generic.x = 0;
	s_r_nolights.generic.y = 118;
	s_r_nolights.generic.name = "Static lights";
	s_r_nolights.curvalue = 1 - r_nolights->value;
	s_r_nolights.itemnames = EnableDisable_names;
	s_r_nolights.generic.callback = StaticLightsFunc;

	s_r_shadows.generic.type	= MTYPE_SPINCONTROL;
	s_r_shadows.generic.x = 0;
	s_r_shadows.generic.y = 128;
	s_r_shadows.generic.name = "Shadows";
	s_r_shadows.curvalue = r_shadows->value;
	s_r_shadows.itemnames = Shadows_names;
	s_r_shadows.generic.callback = ShadowsFunc;

	s_r_mirror_shadows.generic.type	= MTYPE_SPINCONTROL;
	s_r_mirror_shadows.generic.x = 0;
	s_r_mirror_shadows.generic.y = 138;
	s_r_mirror_shadows.generic.name = "Shadows in mirrors";
	s_r_mirror_shadows.curvalue = r_mirror_shadows->value;
	s_r_mirror_shadows.itemnames = ShadowsMirror_names;
	s_r_mirror_shadows.generic.callback = MirrorShadowsFunc;

	s_dl_shadows.generic.type	= MTYPE_SPINCONTROL;
	s_dl_shadows.generic.x = 0;
	s_dl_shadows.generic.y = 148;
	s_dl_shadows.generic.name = "Dynamic shadows";
	s_dl_shadows.curvalue = dl_shadows->value;
	s_dl_shadows.itemnames = EnableDisable_names;
	s_dl_shadows.generic.statusbar = "Shadows from dynamic lights";
	s_dl_shadows.generic.callback = DynamicShadowsFunc;

	s_playershadow.generic.type	= MTYPE_SPINCONTROL;
	s_playershadow.generic.x = 0;
	s_playershadow.generic.y = 158;
	s_playershadow.generic.name = "Player shadow";
	s_playershadow.curvalue = r_playershadow->value;
	s_playershadow.itemnames = EnableDisable_names;
	s_playershadow.generic.statusbar = "Allow client to cast shadows";
	s_playershadow.generic.callback = PlayerShadowFunc;

	s_r_teshadows.generic.type	= MTYPE_SPINCONTROL;
	s_r_teshadows.generic.x = 0;
	s_r_teshadows.generic.y = 168;
	s_r_teshadows.generic.name = "Tiny shadows";
	s_r_teshadows.curvalue = r_teshadows->value;
	s_r_teshadows.itemnames = EnableDisable_names;
	s_r_teshadows.generic.statusbar = "Allow tiny models to cast shadows";
	s_r_teshadows.generic.callback = TinyShadowsFunc;

	s_r_personal_gibs.generic.type	= MTYPE_SPINCONTROL;
	s_r_personal_gibs.generic.x = 0;
	s_r_personal_gibs.generic.y = 178;
	s_r_personal_gibs.generic.name = "Personal gibs";
	s_r_personal_gibs.curvalue = r_personal_gibs->value;
	s_r_personal_gibs.itemnames = EnableDisable_names;
	s_r_personal_gibs.generic.statusbar = "Monsters/Players with own gibs";
	s_r_personal_gibs.generic.callback = PersonalGibsFunc;


	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_mirrors );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_mirror_frameskip );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_mirror_size );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_mirror_models );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_mirror_distance_title );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_mirror_distance_field );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_bloom );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_drawsun );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_decals );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_caustics );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_flares );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_noshells );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_nolights );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_shadows );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_mirror_shadows );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_dl_shadows );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_playershadow );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_teshadows );
	Menu_AddItem( &s_mirroroptions_menu, ( void * ) &s_r_personal_gibs );

	Menu_Center( &s_mirroroptions_menu );
	s_mirroroptions_menu.x += 32;
}

void Mirrors_MenuDraw ()
{
	Menu_AdjustCursor( &s_mirroroptions_menu, 1 );
	Menu_Draw( &s_mirroroptions_menu );
}

int Mirrors_MenuKey( int key )
{
	return Default_MenuKey( &s_mirroroptions_menu, key );
}

void M_Menu_Mirrors_f ()
{
	Mirrors_MenuInit();
	M_PushMenu( Mirrors_MenuDraw, Mirrors_MenuKey );
}

void CustomizeMirrorsFunc( void *unused )
{
	M_Menu_Mirrors_f();
}


void ScrInfo_MenuInit()
{
	if (Cvar_VariableValue("scr_3dhud") < 0)	Cvar_SetValue("scr_3dhud", 0);
	else if (Cvar_VariableValue("scr_3dhud") > 1)	Cvar_SetValue("scr_3dhud", 1);

	if (Cvar_VariableValue("scr_bighud") < 0)	Cvar_SetValue("scr_bighud", 0);
	else if (Cvar_VariableValue("scr_bighud") > 1)	Cvar_SetValue("scr_bighud", 1);

	if (Cvar_VariableValue("scr_drawclock") < 0)	Cvar_SetValue("scr_drawclock", 0);
	else if (Cvar_VariableValue("scr_drawclock") > 2)	Cvar_SetValue("scr_drawclock", 2);

	if (Cvar_VariableValue("scr_fps") < 0)	Cvar_SetValue("scr_fps", 0);
	else if (Cvar_VariableValue("scr_fps") > 2)	Cvar_SetValue("scr_fps", 2);

	if (Cvar_VariableValue("scr_viewpos") < 0)	Cvar_SetValue("scr_viewpos", 0);
	else if (Cvar_VariableValue("scr_viewpos") > 1)	Cvar_SetValue("scr_viewpos", 1);

	if (Cvar_VariableValue("scr_texture") < 0)	Cvar_SetValue("scr_texture", 0);
	else if (Cvar_VariableValue("scr_texture") > 1)	Cvar_SetValue("scr_texture", 1);

	if (Cvar_VariableValue("scr_gametime") < 0)	Cvar_SetValue("scr_gametime", 0);
	else if (Cvar_VariableValue("scr_gametime") > 1)	Cvar_SetValue("scr_gametime", 1);

	if (Cvar_VariableValue("scr_drawspeed") < 0)	Cvar_SetValue("scr_drawspeed", 0);
	else if (Cvar_VariableValue("scr_drawspeed") > 1)	Cvar_SetValue("scr_drawspeed", 1);

	if (Cvar_VariableValue("scr_mapshots") < 0)	Cvar_SetValue("scr_mapshots", 0);
	else if (Cvar_VariableValue("scr_mapshots") > 1)	Cvar_SetValue("scr_mapshots", 1);

	if (Cvar_VariableValue("r_showEFXzones") < 0)	Cvar_SetValue("r_showEFXzones", 0);
	else if (Cvar_VariableValue("r_showEFXzones") > 1)	Cvar_SetValue("r_showEFXzones", 1);

	s_scrinfo_menu.x = viddef.width * 0.50;
	s_scrinfo_menu.nitems = 0;

	s_3dhud.generic.type	= MTYPE_SPINCONTROL;
	s_3dhud.generic.x = 0;
	s_3dhud.generic.y = 0;
	s_3dhud.generic.name = "3d hud";
	s_3dhud.curvalue = scr_3dhud->value;
	s_3dhud.itemnames = EnableDisable_names;
	s_3dhud.generic.callback = _3DHudFunc;
	s_3dhud.generic.statusbar = "3D models in the hud instead 2D pics";

	s_bighud.generic.type	= MTYPE_SPINCONTROL;
	s_bighud.generic.x = 0;
	s_bighud.generic.y = 10;
	s_bighud.generic.name = "big hud";
	s_bighud.curvalue = scr_bighud->value;
	s_bighud.itemnames = yesno_names;
	s_bighud.generic.callback = BigHudFunc;
	s_bighud.generic.statusbar = "Big pics/models in the hud";

	s_drawclock.generic.type	= MTYPE_SPINCONTROL;
	s_drawclock.generic.x = 0;
	s_drawclock.generic.y = 20;
	s_drawclock.generic.name = "clock";
	s_drawclock.curvalue = scr_drawclock->value;
	s_drawclock.itemnames = Clock_names;
	s_drawclock.generic.callback = DrawClockFunc;
	s_drawclock.generic.statusbar = "Show current time";

	s_fps.generic.type	= MTYPE_SPINCONTROL;
	s_fps.generic.x = 0;
	s_fps.generic.y = 30;
	s_fps.generic.name = "FpS";
	s_fps.curvalue = scr_fps->value;
	s_fps.itemnames = fps_names;
	s_fps.generic.callback = fpsFunc;
	s_fps.generic.statusbar = "Show 'frames per seconds' counter";

	s_viewpos.generic.type	= MTYPE_SPINCONTROL;
	s_viewpos.generic.x = 0;
	s_viewpos.generic.y = 40;
	s_viewpos.generic.name = "view pos";
	s_viewpos.curvalue = scr_viewpos->value;
	s_viewpos.itemnames = yesno_names;
	s_viewpos.generic.callback = viewPosFunc;
	s_viewpos.generic.statusbar = "Show your current position in the map";

	s_gametime.generic.type	= MTYPE_SPINCONTROL;
	s_gametime.generic.x = 0;
	s_gametime.generic.y = 50;
	s_gametime.generic.name = "game time";
	s_gametime.curvalue = scr_gametime->value;
	s_gametime.itemnames = yesno_names;
	s_gametime.generic.callback = gameTimeFunc;
	s_gametime.generic.statusbar = "Show game time";

	s_showtexture.generic.type	= MTYPE_SPINCONTROL;
	s_showtexture.generic.x = 0;
	s_showtexture.generic.y = 60;
	s_showtexture.generic.name = "show texture";
	s_showtexture.curvalue = scr_texture->value;
	s_showtexture.itemnames = yesno_names;	///showtexture_names;
	s_showtexture.generic.callback = showTextureFunc;
	s_showtexture.generic.statusbar = "Show texture properties";

	s_drawspeed.generic.type	= MTYPE_SPINCONTROL;
	s_drawspeed.generic.x = 0;
	s_drawspeed.generic.y = 70;
	s_drawspeed.generic.name = "speed stats";
	s_drawspeed.curvalue = scr_drawspeed->value;
	s_drawspeed.itemnames = yesno_names;
	s_drawspeed.generic.callback = drawSpeedFunc;
	s_drawspeed.generic.statusbar = "Show speed statictics";

	s_mapshots.generic.type	= MTYPE_SPINCONTROL;
	s_mapshots.generic.x = 0;
	s_mapshots.generic.y = 80;
	s_mapshots.generic.name = "map shots";
	s_mapshots.curvalue = scr_mapshots->value;
	s_mapshots.itemnames = yesno_names;
	s_mapshots.generic.callback = mapShotFunc;
	s_mapshots.generic.statusbar = "Show mapshot during loading map";

	s_EFXzones.generic.type = MTYPE_SPINCONTROL;
	s_EFXzones.generic.x = 0;
	s_EFXzones.generic.y = 90;
	s_EFXzones.generic.name = "EFX zones";
	s_EFXzones.curvalue = r_showEFXzones->value;
	s_EFXzones.itemnames = yesno_names;
	s_EFXzones.generic.callback = EFXzonesFunc;
	s_EFXzones.generic.statusbar = "Show all zones with manual selected reverb.presets";



	Menu_AddItem( &s_scrinfo_menu, ( void * ) &s_3dhud );
	Menu_AddItem( &s_scrinfo_menu, ( void * ) &s_bighud );
	Menu_AddItem( &s_scrinfo_menu, ( void * ) &s_drawclock );
	Menu_AddItem( &s_scrinfo_menu, ( void * ) &s_fps );
	Menu_AddItem( &s_scrinfo_menu, ( void * ) &s_viewpos );
	Menu_AddItem( &s_scrinfo_menu, ( void * ) &s_gametime );
	Menu_AddItem( &s_scrinfo_menu, ( void * ) &s_showtexture );
	Menu_AddItem( &s_scrinfo_menu, ( void * ) &s_drawspeed );
	Menu_AddItem( &s_scrinfo_menu, ( void * ) &s_mapshots );
	Menu_AddItem( &s_scrinfo_menu, ( void * ) &s_EFXzones );


	Menu_Center( &s_scrinfo_menu );
	s_scrinfo_menu.x -= 52;
}

void ScrInfo_MenuDraw ()
{
	Menu_AdjustCursor( &s_scrinfo_menu, 1 );
	Menu_Draw( &s_scrinfo_menu );
}

int ScrInfo_MenuKey( int key )
{
	return Default_MenuKey( &s_scrinfo_menu, key );
}

void M_Menu_ScrInfo_f ()
{
	ScrInfo_MenuInit();
	M_PushMenu( ScrInfo_MenuDraw, ScrInfo_MenuKey );
}

void CustomizeScrInfoFunc( void *unused )
{
	M_Menu_ScrInfo_f();
}

void VID_MenuInit(bool restart)
{
	int	i;

	if (restart)
		for (cvar_t	*var = cvar_vars ; var ; var = var->next)
			if (var->flags & CVAR_VID_LATCH)
				if (var->modified || var->vid_latched_string)
					var->modified = false;

	if((r_mode->value < 0) || (r_mode->value > _VID_NUM_MODES))
	{
		s_mode_list.curvalue = Vid_GetSafeMode(1);
		Cvar_SetValue( "r_mode", s_mode_list.curvalue );
	}
	else
		s_mode_list.curvalue = r_mode->value;

	if(r_fullscreen->value < 0)
		Cvar_SetValue( "r_fullscreen", 0 );
	if(r_fullscreen->value > 1)
		Cvar_SetValue( "r_fullscreen", 1 );

///	if(!scr_fps)
///		scr_fps = Cvar_Get ("scr_fps", "0", CVAR_ARCHIVE);

///	if(scr_fps->value < 0)
///		Cvar_SetValue( "scr_fps", 0 );
///	if(scr_fps->value > 1)
///		Cvar_SetValue( "scr_fps", 1 );

///	if(r_finish->value < 0)
///		Cvar_SetValue( "r_finish", 0 );
///	if(r_finish->value > 1)
///		Cvar_SetValue( "r_finish", 1 );
	if(r_swapinterval->value < 0)
		Cvar_SetValue( "r_swapinterval", 0 );
	if (gl_config.wgl_swap_control_tear)
	{
		if(r_swapinterval->value > 2)
			Cvar_SetValue( "r_swapinterval", 2 );
	}
	else
	{
		if(r_swapinterval->value > 1)
			Cvar_SetValue( "r_swapinterval", 1 );
	}

	if(r_picmip->value < 0)
		Cvar_SetValue( "r_picmip", 0 );
	if(r_picmip->value > 3)
		Cvar_SetValue( "r_picmip", 3 );

	if(r_diffuse_compression->value < 0)
		Cvar_SetValue( "r_diffuse_compression", 0 );
	if(r_diffuse_compression->value > 4)
		Cvar_SetValue( "r_diffuse_compression", 4 );

	if(r_bump_compression->value < 0)
		Cvar_SetValue( "r_bump_compression", 0 );
	if(r_bump_compression->value > 4)
		Cvar_SetValue( "r_bump_compression", 4 );

	i = 0;
	if ( !r_texturemode )
		r_texturemode = Cvar_Get( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR", CVAR_ARCHIVE|CVAR_VID_LATCH );
///	while(1)
///	{
///		if(Q_strcasecmp((char *)filters[i], r_texturemode->string) == 0)
///		{
///			s_filter_list.curvalue = i;
///			goto mnu0;
///		}
///		i++;
///	}
///	s_filter_list.curvalue = 3;
///mnu0:

	s_parallax_slider.curvalue = r_parallax_scale->value;
	if (s_parallax_slider.curvalue < 0)
		s_parallax_slider.curvalue = 0;
	else if (s_parallax_slider.curvalue > 3)
		s_parallax_slider.curvalue = 3;
	Cvar_SetValue("r_parallax_scale", s_parallax_slider.curvalue);

	i = 0;
	while(1)
	{
		if(Q_strcasecmp((char *)solids[i], r_texturesolidmode->string) == 0)
		{
			s_solid_list.curvalue = i;
			goto mnu1;
		}
		i++;
	}
	s_solid_list.curvalue = 0;
mnu1:

	i = 0;
	while(1)
	{
		if(Q_strcasecmp((char *)alphas[i], r_texturealphamode->string) == 0)
		{
			s_alpha_list.curvalue = i;
			goto mnu2;
		}
		i++;
	}
	s_alpha_list.curvalue = 0;
mnu2:

	if (r_multiSamples->value < 1)
		Cvar_ForceSetValue( "r_multiSamples", 1 );

	if (r_multiSamples->value > 8)
		Cvar_ForceSetValue ("r_multiSamples", 8 );

	int anf_ = 1;
	int goodanf_ = 1;	// Легитимное значение антиалиазинга
	int amax_ = 1;		// Счетчик позиций в меню
	int goodpos_ = 1;	// Легитимное значение позиции в меню
	while(1)
	{
		if(anf_ <= r_multiSamples->value)
		{
			goodanf_ = anf_;
			goodpos_ = amax_;
		}

		if(anf_ >= 8)
			goto mnu3_;

		anf_ += anf_;
		amax_++;
	}
mnu3_:
	if(goodanf_ > 1 && r_multiSamples->value < 2)
		Cvar_ForceSetValue ("r_multiSamples", goodanf_ );

	if (r_multiSamples->value < 1)
		Cvar_ForceSetValue( "r_multiSamples", 1 );

	if(r_nvMultisampleFilterHint->value < 0)
		Cvar_SetValue( "r_nvMultisampleFilterHint", 0 );
	if(r_nvMultisampleFilterHint->value > 1)
		Cvar_SetValue( "r_nvMultisampleFilterHint", 1 );

	if (r_anisotropy->value < 2)
		Cvar_SetValue( "r_anisotropy", 0 );

	if (r_anisotropy->value > gl_config.max_anisotropy)
		Cvar_SetValue ("r_anisotropy", gl_config.max_anisotropy );

	int anf = 1;
	int goodanf = 1;	// Легитимное значение анизотропии
	int amax = 1;		// Счетчик позиций в меню
	int goodpos = 1;	// Легитимное значение позиции в меню
	while(1)
	{
		if(anf <= r_anisotropy->value)
		{
			goodanf = anf;
			goodpos = amax;
		}

		if(gl_config.max_anisotropy <= anf)
			goto mnu3;

		anf += anf;
		amax++;
	}
mnu3:
	if(goodanf > 1 && r_anisotropy->value < 2)
		Cvar_SetValue ("r_anisotropy", goodanf );

	if (r_anisotropy->value < 2)
		Cvar_SetValue( "r_anisotropy", 0 );

///	if ( !r_bitdepth )
///		r_bitdepth = Cvar_Get ("r_bitdepth", "0", CVAR_ARCHIVE);
///	if (r_bitdepth->value == 16)
///		s_bits_list.curvalue = 1;
///	else
///		if (r_bitdepth->value == 32)
///			s_bits_list.curvalue = 2;
///		else
///			s_bits_list.curvalue = 0;

///	if(!scr_viewsize)
///		scr_viewsize = Cvar_Get ("viewsize", "100", CVAR_ARCHIVE);

	if(r_shader->value >= MAX_SHADERS || r_shader->value < 0)
	{
		Cvar_SetValue( "r_shader", SHADER_GENERIC4 );
		Com_Printf("^3r_shader has incorrect value, fixed...\n");
	}

	if(r_modulate->value < 0)
		Cvar_ForceSetValue( "r_modulate", 0 );
	if(r_modulate->value > 2)
		Cvar_ForceSetValue( "r_modulate", 2 );
	if(r_overbright->value < 0)
		Cvar_ForceSetValue( "r_overbright", 0);
	if(r_overbright->value > 1)
		Cvar_ForceSetValue( "r_overbright", 1);
	if(vid_contrast->value < 0)
		Cvar_ForceSetValue( "vid_contrast", 0 );
	if(vid_contrast->value > 2)
		Cvar_ForceSetValue( "vid_contrast", 2 );
	if(r_gamma->value < 0.5)
		Cvar_ForceSetValue( "r_gamma", 0.5 );
	if(r_gamma->value > 1.3)
		Cvar_ForceSetValue( "r_gamma", 1.3 );
	if(vid_gamma->value < 0)
		Cvar_ForceSetValue( "vid_gamma", 0 );
	if(vid_gamma->value > 3)
		Cvar_ForceSetValue( "vid_gamma", 3 );
	if(r_intensity->value < 1)
		Cvar_ForceSetValue( "r_intensity", 1 );
	if(r_intensity->value > 3)
		Cvar_ForceSetValue( "r_intensity", 3 );
	if(vid_bright->value < 0)
		Cvar_ForceSetValue( "vid_bright", 0 );
	if(vid_bright->value > 4)
		Cvar_ForceSetValue( "vid_bright", 4 );
	if(r_simple->value<0)
		Cvar_ForceSetValue( "r_simple", 0 );
	if(r_simple->value>1)
		Cvar_ForceSetValue( "r_simple", 1 );

	s_opengl_menu.x = viddef.width * 0.50;
	s_opengl_menu.nitems = 0;

	s_mode_list.generic.type = MTYPE_SPINCONTROL;
	s_mode_list.generic.name = "video mode";
	s_mode_list.generic.x = 0;
	s_mode_list.generic.y = 9;
	s_mode_list.itemnames = resolutions;

///	s_screensize_slider.generic.type	= MTYPE_SLIDER;
///	s_screensize_slider.generic.x		= 0;
///	s_screensize_slider.generic.y		= 18;
///	s_screensize_slider.generic.name	= "screen size";
///	s_screensize_slider.minvalue = 4;///3;
///	s_screensize_slider.maxvalue = 10;///12;
///	s_screensize_slider.generic.callback = ScreenSizeCallback;
///	s_screensize_slider.curvalue = scr_viewsize->value/10;

	s_gamma_slider.generic.type	= MTYPE_SLIDER;
	s_gamma_slider.generic.x	= 0;
	s_gamma_slider.generic.y	= 18;
	if(!vid_ignorehwgamma->value)
	{
		s_gamma_slider.minvalue = 1;
		s_gamma_slider.maxvalue = 30;
		s_gamma_slider.generic.callback = SysGammaCallback;
		s_gamma_slider.generic.name	= "h/w.gamma";
		s_gamma_slider.curvalue = vid_gamma->value * 10;
		s_gamma_slider.generic.statusbar = "vid_gamma";
	}
	else
	{
		s_gamma_slider.minvalue = 5;
		s_gamma_slider.maxvalue = 13;
		s_gamma_slider.generic.name	= "tex.gamma";
		s_gamma_slider.curvalue = r_gamma->value * 10;
		s_gamma_slider.generic.statusbar = "r_gamma";
	}

	s_bright_slider.generic.type	= MTYPE_SLIDER;
	s_bright_slider.generic.x	= 0;
	s_bright_slider.generic.y	= 27;
	if(!vid_ignorehwgamma->value)
	{
		s_bright_slider.minvalue = 0;
		s_bright_slider.maxvalue = 40;
		s_bright_slider.generic.callback = SysGammaCallback;
		s_bright_slider.generic.name	= "h/w.brightness";
		s_bright_slider.curvalue = vid_bright->value * 10;
		s_bright_slider.generic.statusbar = "vid_bright";
	}
	else
	{
		s_bright_slider.minvalue = 10;
		s_bright_slider.maxvalue = 30;
		s_bright_slider.generic.name	= "tex.intensity";
		s_bright_slider.curvalue = r_intensity->value * 10;
		s_bright_slider.generic.statusbar = "r_intensity";
	}

	if(!vid_ignorehwgamma->value)
	{
		s_contrast_slider.generic.type	= MTYPE_SLIDER;
		s_contrast_slider.generic.x	= 0;
		s_contrast_slider.generic.y	= 36;
		s_contrast_slider.minvalue = 0;
		s_contrast_slider.maxvalue = 20;
		s_contrast_slider.generic.callback = SysGammaCallback;
		s_contrast_slider.generic.name	= "h/w.contrast";
		s_contrast_slider.curvalue = vid_contrast->value * 10;
		s_contrast_slider.generic.statusbar = "vid_contrast";
	}

	s_lmscale_slider.generic.type	= MTYPE_SLIDER;
	s_lmscale_slider.generic.x = 0;
	s_lmscale_slider.generic.y = 45;
	s_lmscale_slider.generic.name = "lightmap gamma";
	s_lmscale_slider.minvalue = 0;
	s_lmscale_slider.maxvalue = 20;
	s_lmscale_slider.curvalue = r_modulate->value * 10;
	s_lmscale_slider.generic.statusbar = "r_modulate";

	s_bmscale_slider.generic.type	= MTYPE_SPINCONTROL;
	s_bmscale_slider.generic.x = 0;
	s_bmscale_slider.generic.y = 54;
	s_bmscale_slider.generic.name = "light overbright";
	s_bmscale_slider.curvalue = r_overbright->value;
	s_bmscale_slider.itemnames = yesno_names;
	s_bmscale_slider.generic.statusbar = "r_overbright";

	s_fs_box.generic.type = MTYPE_SPINCONTROL;
	s_fs_box.generic.x	= 0;
	s_fs_box.generic.y	= 63;
	s_fs_box.generic.name	= "fullscreen";
	s_fs_box.itemnames = yesno_names;
	s_fs_box.curvalue = r_fullscreen->value;
	s_fs_box.generic.statusbar = "r_fullscreen";

///	s_bits_list.generic.type = MTYPE_SPINCONTROL;
///	s_bits_list.generic.name = "color depth";
///	s_bits_list.generic.x = 0;
///	s_bits_list.generic.y = 63;
///	s_bits_list.itemnames = colordepth;
///	s_bits_list.generic.statusbar = "video mode color depth";

	s_tq_slider.generic.type	= MTYPE_SLIDER;
	s_tq_slider.generic.x = 0;
	s_tq_slider.generic.y = 72;
	s_tq_slider.generic.name = "texture quality";
	s_tq_slider.minvalue = 0;
	s_tq_slider.maxvalue = 3;
	s_tq_slider.curvalue = 3-r_picmip->value;
	s_tq_slider.generic.statusbar = "sample down the color/bump textures for speed";

	s_solid_list.generic.type = MTYPE_SPINCONTROL;
	s_solid_list.generic.name = "tex.solid mode";
	s_solid_list.generic.x	= 0;
	s_solid_list.generic.y	= 81;
	s_solid_list.itemnames = solids;
	s_solid_list.generic.statusbar = "ignore if compression enabled";

	s_alpha_list.generic.type = MTYPE_SPINCONTROL;
	s_alpha_list.generic.name = "tex.alpha mode";
	s_alpha_list.generic.x	= 0;
	s_alpha_list.generic.y	= 90;
	s_alpha_list.itemnames = alphas;
	s_alpha_list.generic.statusbar = "ignore if compression enabled";

	s_compression_box.generic.type = MTYPE_SPINCONTROL;
	s_compression_box.generic.x	= 0;
	s_compression_box.generic.y	= 99;
	s_compression_box.generic.name	= "tex. compression";
	s_compression_box.curvalue = r_diffuse_compression->value;
	s_compression_box.itemnames = compr_types;
	s_compression_box.generic.statusbar = "r_diffuse_compression";

	s_bump_compression_box.generic.type = MTYPE_SPINCONTROL;
	s_bump_compression_box.generic.x	= 0;
	s_bump_compression_box.generic.y	= 108;
	s_bump_compression_box.generic.name	= "bump compression";
	s_bump_compression_box.curvalue = r_bump_compression->value;
	s_bump_compression_box.itemnames = compr_types;
	s_bump_compression_box.generic.statusbar = "r_bump_compression";

	s_parallax_slider.generic.type = MTYPE_SLIDER;
	s_parallax_slider.generic.x = 0;
	s_parallax_slider.generic.y = 117;
	s_parallax_slider.generic.name = "parallax scale";
	s_parallax_slider.minvalue = 0;
	s_parallax_slider.maxvalue = 3;
	s_parallax_slider.curvalue = r_parallax_scale->value;
	s_parallax_slider.generic.statusbarfunc = Menu_DrawParallaxStatusBar;
///s_filter_list.generic.type = MTYPE_SPINCONTROL;
///s_filter_list.generic.name = "tex.filter";
///s_filter_list.generic.x = 0;
///s_filter_list.generic.y = 117;
///s_filter_list.itemnames = filters;
///s_filter_list.generic.statusbarfunc = Menu_DrawTFilterStatusBar;

	s_shader_list.generic.type = MTYPE_SPINCONTROL;
	s_shader_list.generic.name = "Renderer";
	s_shader_list.generic.x	= 0;
	s_shader_list.generic.y	= 126;
	s_shader_list.curvalue = r_shader->value;
	s_shader_list.itemnames = shaders;
	s_shader_list.generic.statusbarfunc = Menu_DrawShaderStatusBar;

	s_simple.generic.type = MTYPE_SPINCONTROL;
	s_simple.generic.name = "Quake2 renderer";
	s_simple.generic.x	= 0;
	s_simple.generic.y	= 135;
	s_simple.curvalue = r_simple->value;
	s_simple.itemnames = yesno_names;
	s_simple.generic.statusbarfunc = Menu_DrawShaderStatusBar;

	s_anisotropy_slider.generic.type	= MTYPE_SLIDER;
	s_anisotropy_slider.generic.x = 0;
	s_anisotropy_slider.generic.y = 144;
	s_anisotropy_slider.generic.name = "anisotropy";
	s_anisotropy_slider.generic.callback = AnisotropyCallback;
	s_anisotropy_slider.minvalue = 1;
	s_anisotropy_slider.maxvalue = amax;
	s_anisotropy_slider.curvalue = goodpos;
	s_anisotropy_slider.generic.statusbarfunc = Menu_DrawAnisoStatusBar;

	s_antialias_slider.generic.type	= MTYPE_SLIDER;
	s_antialias_slider.generic.x = 0;
	s_antialias_slider.generic.y = 153;
	s_antialias_slider.generic.name = "multisampling";
	s_antialias_slider.generic.callback = AntialiasCallback;
	s_antialias_slider.minvalue = 1;
	s_antialias_slider.maxvalue = amax_;
	s_antialias_slider.curvalue = goodpos_;
	s_antialias_slider.generic.statusbarfunc = Menu_DrawAaStatusBar;

	s_nvhint_box.generic.type = MTYPE_SPINCONTROL;
	s_nvhint_box.generic.x	= 0;
	s_nvhint_box.generic.y	= 162;
	s_nvhint_box.generic.name	= "filter hint";
	s_nvhint_box.curvalue = r_nvMultisampleFilterHint->value;
	s_nvhint_box.itemnames = hint_names;

	s_finish_box.generic.type = MTYPE_SPINCONTROL;
	s_finish_box.generic.x	= 0;
	s_finish_box.generic.y	= 171;
	s_finish_box.generic.name	= "frame sync";
	s_finish_box.curvalue = r_swapinterval->value;
	if (gl_config.wgl_swap_control_tear)
		s_finish_box.itemnames = vsync_names;
	else
		s_finish_box.itemnames = yesno_names;

	s_screen_infos_action.generic.type	= MTYPE_ACTION;
	s_screen_infos_action.generic.x		= 0;
	s_screen_infos_action.generic.y		= 184;
	s_screen_infos_action.generic.name	= "On screen info";
	s_screen_infos_action.generic.callback = CustomizeScrInfoFunc;

	s_video_options_action.generic.type	= MTYPE_ACTION;
	s_video_options_action.generic.x		= 0;
	s_video_options_action.generic.y		= 193;
	s_video_options_action.generic.name	= "More options...";
	s_video_options_action.generic.callback = CustomizeMirrorsFunc;

///	s_cancel_action.generic.type = MTYPE_ACTION;
///	s_cancel_action.generic.name = "Apply changes";
///	s_cancel_action.generic.x    = 0;
///	s_cancel_action.generic.y    = 160;
///	s_cancel_action.generic.callback = ApplyChanges;


	Menu_AddItem( &s_opengl_menu, ( void * ) &s_mode_list);
///	Menu_AddItem( &s_opengl_menu, ( void * ) &s_screensize_slider);
///	Menu_AddItem( &s_opengl_menu, ( void * ) &s_brightness_slider);
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_gamma_slider);
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_bright_slider);
	if(!vid_ignorehwgamma->value)
		Menu_AddItem( &s_opengl_menu, ( void * ) &s_contrast_slider);
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_lmscale_slider );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_bmscale_slider );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_fs_box);
///	Menu_AddItem( &s_opengl_menu, ( void * ) &s_bits_list);
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_tq_slider );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_solid_list );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_alpha_list );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_compression_box );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_bump_compression_box );
///		Menu_AddItem( &s_opengl_menu, ( void * ) &s_filter_list);
		Menu_AddItem(&s_opengl_menu, (void *)&s_parallax_slider);
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_shader_list);
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_simple);
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_anisotropy_slider );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_antialias_slider );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_nvhint_box );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_finish_box );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_screen_infos_action );
	Menu_AddItem( &s_opengl_menu, ( void * ) &s_video_options_action );

///	Menu_AddItem( &s_opengl_menu, ( void * ) &s_cancel_action);

	Menu_Center( &s_opengl_menu );
	s_opengl_menu.x -= 24;
	s_opengl_menu.y += 16;
}


void Mod_Init ()
{
	Memset (mod_novis, 0xff, sizeof(mod_novis));
}


void GL_CreateShaderNVidia()
{
	vec3_t scaler = {0.5, 0.5, 0.5};
	glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 4);
	glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, &scaler[0]);

//	(normal map = A) dot (norm cubemap = B) save in Spare0 RGB (белый бамп)
//	(1.0 = C) mul (light filter = D) save in Spare1 RGB (светофильтр/затухание)
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE1, GL_EXPAND_NORMAL_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_TEXTURE0, GL_EXPAND_NORMAL_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_TEXTURE2, GL_UNSIGNED_IDENTITY_NV , GL_RGB);
	glCombinerOutputNV(GL_COMBINER0_NV, GL_RGB, GL_SPARE0_NV, GL_SPARE1_NV, GL_DISCARD_NV, GL_NONE, GL_NONE, GL_TRUE, GL_FALSE, GL_FALSE);
//	alpha out = nothing
	glCombinerOutputNV(GL_COMBINER0_NV, GL_ALPHA, GL_DISCARD_NV, GL_DISCARD_NV, GL_DISCARD_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);

//	combiner1 RGB: calculate (Spare0 RGB * Spare1 RGB * 4) -> store in Spare0 RGB (бамп * светофильтр/затухание * 4)
	glCombinerInputNV(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glCombinerOutputNV(GL_COMBINER1_NV, GL_RGB, GL_SPARE0_NV, GL_DISCARD_NV, GL_DISCARD_NV, GL_SCALE_BY_FOUR_NV, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);
//	combiner1 Alpha: calculate 2*((N'dotH)^2 - 0.5f) -> store in Spare0 Alpha (блик на основе бампа)
	glCombinerInputNV(GL_COMBINER1_NV, GL_ALPHA, GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_BLUE);
	glCombinerInputNV(GL_COMBINER1_NV, GL_ALPHA, GL_VARIABLE_B_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_BLUE);
	glCombinerInputNV(GL_COMBINER1_NV, GL_ALPHA, GL_VARIABLE_C_NV, GL_CONSTANT_COLOR0_NV, GL_SIGNED_NEGATE_NV, GL_BLUE);
	glCombinerInputNV(GL_COMBINER1_NV, GL_ALPHA, GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_ALPHA);
	glCombinerOutputNV(GL_COMBINER1_NV, GL_ALPHA, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE0_NV, GL_SCALE_BY_TWO_NV, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);

//	spare1 RGB: spare0.rgb * spare1.rgb (бамп * светофильтр/затухание * 4 * colormap)
	glCombinerInputNV(GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_B_NV, GL_TEXTURE3, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	if(r_overbright->value)
		glCombinerOutputNV(GL_COMBINER2_NV, GL_RGB, GL_SPARE1_NV, GL_DISCARD_NV, GL_DISCARD_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);
	else
		glCombinerOutputNV(GL_COMBINER2_NV, GL_RGB, GL_SPARE1_NV, GL_DISCARD_NV, GL_DISCARD_NV, GL_SCALE_BY_ONE_HALF_NV, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);
//	combiner2 Alpha: Raise specular further
	glCombinerInputNV(GL_COMBINER2_NV, GL_ALPHA, GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);
	glCombinerInputNV(GL_COMBINER2_NV, GL_ALPHA, GL_VARIABLE_B_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);
	glCombinerOutputNV(GL_COMBINER2_NV, GL_ALPHA, GL_SPARE0_NV, GL_DISCARD_NV, GL_DISCARD_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);

	glCombinerInputNV(GL_COMBINER3_NV, GL_RGB, GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);
	glCombinerInputNV(GL_COMBINER3_NV, GL_RGB, GL_VARIABLE_B_NV, GL_TEXTURE1, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);
	glCombinerInputNV(GL_COMBINER3_NV, GL_RGB, GL_VARIABLE_C_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER3_NV, GL_RGB, GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB);
	glCombinerOutputNV(GL_COMBINER3_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE1_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);

//	final combiner: final RGB = (Spare 0 RGB) * (Spare 1 RGB)
	glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glFinalCombinerInputNV(GL_VARIABLE_B_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glFinalCombinerInputNV(GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glFinalCombinerInputNV(GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glFinalCombinerInputNV(GL_VARIABLE_E_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glFinalCombinerInputNV(GL_VARIABLE_F_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
//	final cominer alpha = texture.alpha
	glFinalCombinerInputNV(GL_VARIABLE_G_NV, GL_TEXTURE3, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);
}



void GL_CreateShadersRadeon4()
{
	unsigned over;
	if(r_overbright->value)
		over = GL_4X_BIT_ATI;
	else
		over = GL_2X_BIT_ATI;

	glEnable(GL_FRAGMENT_SHADER_ATI);

	fragment_shaders = glGenFragmentShadersATI(2);

	// combined diffuse shader w/ vertex color
	glBindFragmentShaderATI(fragment_shaders);
	glBeginFragmentShaderATI();

	// texld r0, t0
	glSampleMapATI (GL_REG_0_ATI, GL_TEXTURE0, GL_SWIZZLE_STR_ATI);		// normal map
	// texld r1, t1
	glSampleMapATI (GL_REG_1_ATI, GL_TEXTURE1, GL_SWIZZLE_STR_ATI);		// normcube (for diffuse bump)
	// texld r2, t2
	glSampleMapATI (GL_REG_2_ATI, GL_TEXTURE2, GL_SWIZZLE_STR_ATI);		// color map
	// texld r3, t3
	glSampleMapATI (GL_REG_3_ATI, GL_TEXTURE3, GL_SWIZZLE_STR_ATI);		// attenuation


	// dp3_sat r1.rgb, r0_bx2.rgb, r1_bx2.rgb   // r1.rgb = diffuse bump
	glColorFragmentOp2ATI(GL_DOT3_ATI,
							GL_REG_1_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_SATURATE_BIT_ATI,
							GL_REG_0_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI,
							GL_REG_1_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI);

	// mul r1.rgb, r1.rgb, r2.rgb               // r1.rgb = color map * diffuse bump
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_1_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_2_ATI, GL_NONE, GL_NONE);

	// mul r3.rgb, r3.rgb, v0.rgb               // r3.rgb = atten * light color
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_3_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_3_ATI, GL_NONE, GL_NONE,
							GL_PRIMARY_COLOR_ARB, GL_NONE, GL_NONE);

	// mul_sat r0.rgb, r1.rgb, r3.rgb           // diffuse*atten*color
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_0_ATI, GL_NONE, GL_SATURATE_BIT_ATI|over,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_3_ATI, GL_NONE, GL_NONE);

	glEndFragmentShaderATI();


	// Second shader with cube filter
	glBindFragmentShaderATI(fragment_shaders+1);
	glBeginFragmentShaderATI();

	// texld r0, t0
	glSampleMapATI (GL_REG_0_ATI, GL_TEXTURE0, GL_SWIZZLE_STR_ATI);		// normal map
	// texld r1, t1
	glSampleMapATI (GL_REG_1_ATI, GL_TEXTURE1, GL_SWIZZLE_STR_ATI);		// normcube (for diffuse bump)
	// texld r2, t2
	glSampleMapATI (GL_REG_2_ATI, GL_TEXTURE2, GL_SWIZZLE_STR_ATI);		// color map
	// texld r3, t3
	glSampleMapATI (GL_REG_3_ATI, GL_TEXTURE3, GL_SWIZZLE_STR_ATI);		// attenuation
	// texld r4, t4
	glSampleMapATI (GL_REG_4_ATI, GL_TEXTURE4, GL_SWIZZLE_STR_ATI);		// light filter


	// dp3_sat r1.rgb, r0_bx2.rgb, r1_bx2.rgb   // r1.rgb = diffuse bump
	glColorFragmentOp2ATI(GL_DOT3_ATI,
							GL_REG_1_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_SATURATE_BIT_ATI,
							GL_REG_0_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI,
							GL_REG_1_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI);

	// mul r1.rgb, r1.rgb, r2.rgb               // r1.rgb = color map * diffuse bump
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_1_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_2_ATI, GL_NONE, GL_NONE);

	// mul r3.rgb, r3.rgb, v0.rgb               // r3.rgb = atten * light color
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_3_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_3_ATI, GL_NONE, GL_NONE,
							GL_PRIMARY_COLOR_ARB, GL_NONE, GL_NONE);

	// mul r0.rgb, r1.rgb, r4.rgb                 // r0.rgb = diffuse * light filter
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_0_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_4_ATI, GL_NONE, GL_NONE);

	// mul_sat r0.rgb, r0.rgb, r3.rgb           // diffuse * atten * color * light filter
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_0_ATI, GL_NONE, GL_SATURATE_BIT_ATI|over,
							GL_REG_0_ATI, GL_NONE, GL_NONE,
							GL_REG_3_ATI, GL_NONE, GL_NONE);

	glEndFragmentShaderATI();


	glDisable(GL_FRAGMENT_SHADER_ATI);
}


void GL_CreateShadersRadeon6()
{
	unsigned over;
	if(r_overbright->value)
		over = GL_4X_BIT_ATI;
	else
		over = GL_2X_BIT_ATI;

	float scaler[4] = {0.5, 0.5, 0.5, 0.5};

	glEnable(GL_FRAGMENT_SHADER_ATI);

	fragment_shaders = glGenFragmentShadersATI(2);

	// combined diffuse & specular shader w/ vertex color
	glBindFragmentShaderATI(fragment_shaders);
	glBeginFragmentShaderATI();

	glSetFragmentShaderConstantATI(GL_CON_0_ATI, &scaler[0]);

	// texld r0, t0
	glSampleMapATI (GL_REG_0_ATI, GL_TEXTURE0, GL_SWIZZLE_STR_ATI);		// normal map
	// texld r1, t1
	glSampleMapATI (GL_REG_1_ATI, GL_TEXTURE1, GL_SWIZZLE_STR_ATI);		// normcube (for diffuse bump)
	// texld r2, t2
	glSampleMapATI (GL_REG_2_ATI, GL_TEXTURE2, GL_SWIZZLE_STR_ATI);		// normcube (for specular bump)
	// texld r3, t3
	glSampleMapATI (GL_REG_3_ATI, GL_TEXTURE3, GL_SWIZZLE_STR_ATI);		// color map
	// texld r4, t4
	glSampleMapATI (GL_REG_4_ATI, GL_TEXTURE4, GL_SWIZZLE_STR_ATI);		// attenuation

	// gloss * atten * light color * specular +
	// dot * color * atten * light color * self shadow =
	// (gloss * specular + dot * color * self shadow ) * atten * light color
	// Alpha ops rule :-)

	// dp3_sat r2.rgb, r0_bx2.rgb, r2_bx2.rgb   // specular
	glColorFragmentOp2ATI(GL_DOT3_ATI,
							GL_REG_2_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_SATURATE_BIT_ATI,
							GL_REG_0_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI,
							GL_REG_2_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI);

	// +mov_x8_sat r2.a, r1_bx2.b               // self shadow term
	glAlphaFragmentOp1ATI(GL_MOV_ATI,
							GL_REG_2_ATI, GL_8X_BIT_ATI|GL_SATURATE_BIT_ATI,
							GL_REG_1_ATI, GL_BLUE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI);

	// dp3_sat r1.rgb, r0_bx2.rgb, r1_bx2.rgb   // diffuse
	glColorFragmentOp2ATI(GL_DOT3_ATI,
							GL_REG_1_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_SATURATE_BIT_ATI,
							GL_REG_0_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI,
							GL_REG_1_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI);

	// +mad_x2_sat r1.a, r2.b, r2.b, -c0.b      // specular exponent
	glAlphaFragmentOp3ATI(GL_MAD_ATI,
							GL_REG_1_ATI, GL_2X_BIT_ATI|GL_SATURATE_BIT_ATI,
							GL_REG_2_ATI, GL_BLUE, GL_NONE,
							GL_REG_2_ATI, GL_BLUE, GL_NONE,
							GL_CON_0_ATI, GL_BLUE, GL_NEGATE_BIT_ATI);

	// mul r1.rgb, r1.rgb, r3.rgb               // diffuse color * diffuse bump
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_1_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_3_ATI, GL_NONE, GL_NONE);

	// +mul r1.a, r1.a, r1.a                    // raise exponent
	glAlphaFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_1_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE);

	// mul r4.rgb, r4.rgb, v0.rgb               // atten * light color
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_4_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_4_ATI, GL_NONE, GL_NONE,
							GL_PRIMARY_COLOR_ARB, GL_NONE, GL_NONE);

	// +mul r1.a, r1.a, r1.a                    // raise exponent
	glAlphaFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_1_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE);

	// mul r1.rgb, r1.rgb, r2.a                 // self shadow * diffuse
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_1_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_2_ATI, GL_ALPHA, GL_NONE);

	// +mul r0.a, r1.a, r0.a                    // specular * gloss map
	glAlphaFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_0_ATI, GL_SATURATE_BIT_ATI,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_0_ATI, GL_NONE, GL_NONE);

	// add r0.rgb, r1.rgb, r0.a                 // diffuse + specular
	glColorFragmentOp2ATI(GL_ADD_ATI,
							GL_REG_0_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_0_ATI, GL_ALPHA, GL_NONE);

	// mul_sat r0.rgb, r0.rgb, r4.rgb           // (diffuse + specular)*atten*color
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_0_ATI, GL_NONE, GL_SATURATE_BIT_ATI|over,
							GL_REG_0_ATI, GL_NONE, GL_NONE,
							GL_REG_4_ATI, GL_NONE, GL_NONE);

	glEndFragmentShaderATI();

	// Second shader with cube filter
	glBindFragmentShaderATI(fragment_shaders+1);
	glBeginFragmentShaderATI();

	glSetFragmentShaderConstantATI(GL_CON_0_ATI, &scaler[0]);

	// texld r0, t0
	glSampleMapATI (GL_REG_0_ATI, GL_TEXTURE0, GL_SWIZZLE_STR_ATI);		// normal map
	// texld r1, t1
	glSampleMapATI (GL_REG_1_ATI, GL_TEXTURE1, GL_SWIZZLE_STR_ATI);		// normcube (for diffuse bump)
	// texld r2, t2
	glSampleMapATI (GL_REG_2_ATI, GL_TEXTURE2, GL_SWIZZLE_STR_ATI);		// normcube (for specular bump)
	// texld r3, t3
	glSampleMapATI (GL_REG_3_ATI, GL_TEXTURE3, GL_SWIZZLE_STR_ATI);		// color map
	// texld r4, t4
	glSampleMapATI (GL_REG_4_ATI, GL_TEXTURE4, GL_SWIZZLE_STR_ATI);		// attenuation
	// texld r5, t5
	glSampleMapATI (GL_REG_5_ATI, GL_TEXTURE5, GL_SWIZZLE_STR_ATI);		// light filter

	// gloss * atten * light color * specular +
	// dot * color * atten * light color * self shadow =
	// (gloss * specular + dot * color * self shadow ) * atten * light color * filter
	// Alpha ops rule :-)

	// dp3_sat r2.rgb, r0_bx2.rgb, r2_bx2.rgb      // specular
	glColorFragmentOp2ATI(GL_DOT3_ATI,
							GL_REG_2_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_SATURATE_BIT_ATI,
							GL_REG_0_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI,
							GL_REG_2_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI);

	// +mov_x8_sat r2.a, r1_bx2.b                  // self shadow term
	glAlphaFragmentOp1ATI(GL_MOV_ATI,
							GL_REG_2_ATI, GL_8X_BIT_ATI|GL_SATURATE_BIT_ATI,
							GL_REG_1_ATI, GL_BLUE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI);

	// dp3_sat r1.rgb, r0_bx2.rgb, r1_bx2.rgb  // diffuse
	glColorFragmentOp2ATI(GL_DOT3_ATI,
							GL_REG_1_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_SATURATE_BIT_ATI,
							GL_REG_0_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI,
							GL_REG_1_ATI, GL_NONE, GL_2X_BIT_ATI|GL_BIAS_BIT_ATI);

	// +mad_x2_sat r1.a, r2.b, r2.b, -c0.b     // specular exponent
	glAlphaFragmentOp3ATI(GL_MAD_ATI,
							GL_REG_1_ATI, GL_2X_BIT_ATI|GL_SATURATE_BIT_ATI,
							GL_REG_2_ATI, GL_BLUE, GL_NONE,
							GL_REG_2_ATI, GL_BLUE, GL_NONE,
							GL_CON_0_ATI, GL_BLUE, GL_NEGATE_BIT_ATI);

	// mul r1.rgb, r1.rgb, r3.rgb              // diffuse color * diffuse bump
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_1_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_3_ATI, GL_NONE, GL_NONE);

	// +mul r1.a, r1.a, r1.a                   // raise exponent
	glAlphaFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_1_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE);

	// mul r4.rgb, r4.rgb, v0.rgb              // atten * light color
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_4_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_4_ATI, GL_NONE, GL_NONE,
							GL_PRIMARY_COLOR_ARB, GL_NONE, GL_NONE);

	// +mul r1.a, r1.a, r1.a                   // raise exponent
	glAlphaFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_1_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE);

	// mul r1.rgb, r1.rgb, r2.a               // self shadow * diffuse
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_1_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_2_ATI, GL_ALPHA, GL_NONE);

	// +mul r0.a, r1.a, r0.a                 // specular * gloss map
	glAlphaFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_0_ATI, GL_SATURATE_BIT_ATI,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_0_ATI, GL_NONE, GL_NONE);

	// add r0.rgb, r0.rgb, r0.a              // diffuse + specular
	glColorFragmentOp2ATI(GL_ADD_ATI,
							GL_REG_0_ATI, GL_RED_BIT_ATI|GL_GREEN_BIT_ATI|GL_BLUE_BIT_ATI, GL_NONE,
							GL_REG_1_ATI, GL_NONE, GL_NONE,
							GL_REG_0_ATI, GL_ALPHA, GL_NONE);

	// mul r0.rgb, r0.rgb, r4.rgb            // (diffuse + specular)*atten*color
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_0_ATI, GL_NONE, over,
							GL_REG_0_ATI, GL_NONE, GL_NONE,
							GL_REG_4_ATI, GL_NONE, GL_NONE);

	// mul_sat r0.rgb, r0.rgb, r5.rgb    // (diffuse + specular)*atten*color*filter
	glColorFragmentOp2ATI(GL_MUL_ATI,
							GL_REG_0_ATI, GL_NONE, GL_SATURATE_BIT_ATI,
							GL_REG_0_ATI, GL_NONE, GL_NONE,
							GL_REG_5_ATI, GL_NONE, GL_NONE);

	glEndFragmentShaderATI();
	glDisable(GL_FRAGMENT_SHADER_ATI);
}


bool checkerr(char *file)
{
	int	error = -1;
	glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &error);

	if(error != -1)
	{
		Com_Printf("^1Program: \"%s\"\n%s\n", file, (char *) glGetString(GL_PROGRAM_ERROR_STRING_ARB) );
		return false;
	}
	return true;
}


bool GL_CreateShadersARB(int shader)
{
	if(shader==SHADER_ARB4)
		Com_Printf("\nBinding ARB4 programs...");
	else
		Com_Printf("\nBinding ARB6 programs...");

	if (fragment_program_ambient == 0xffffffff)
	{
		glGenProgramsARB(1, &fragment_program_ambient);
		GL_BindProgramFP(fragment_program_ambient);
		glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fragmentprogram_ambient_ptr), fragmentprogram_ambient_ptr);
		if(!checkerr("ARB ambient fragment program"))
			return false;
	}

	if(gl_config.arb_distort && (arbdistort_program == 0xffffffff))
	{
		glGenProgramsARB(1, &arbdistort_program);
		GL_BindProgramFP(arbdistort_program);
		glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(arbdistort_ptr), arbdistort_ptr);
		if(!checkerr("ARB water fragment program"))
			gl_config.arb_distort = false;
	}

	if(fragment_program_nofilter == 0xffffffff)
	{
		glGenProgramsARB(1, &fragment_program_nofilter);
		GL_BindProgramFP(fragment_program_nofilter);
		glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fragmentprogram_nofilter_ptr), fragmentprogram_nofilter_ptr);
		if(!checkerr("fragment program w/o light filter"))
			return false;
	}

	if(shader==SHADER_ARB4)
	{
		if(fragment_program_filter4 == 0xffffffff)
		{
			glGenProgramsARB(1, &fragment_program_filter4);
			GL_BindProgramFP(fragment_program_filter4);
			glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fragmentprogram_filter4), fragmentprogram_filter4);
			if(!checkerr("fragment program with light filter"))
				return false;
		}
	}
	else
	{
		if(fragment_program_filter == 0xffffffff)
		{
			glGenProgramsARB(1, &fragment_program_filter);
			GL_BindProgramFP(fragment_program_filter);
			glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fragmentprogram_filter_ptr), fragmentprogram_filter_ptr);
			if(!checkerr("fragment program with light filter"))
				return false;
		}
		if(fragment_program_nofilter6_detail == 0xffffffff)
		{
			glGenProgramsARB(1, &fragment_program_nofilter6_detail);
			GL_BindProgramFP(fragment_program_nofilter6_detail);
			glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fragmentprogram_nofilter6_detail_ptr), fragmentprogram_nofilter6_detail_ptr);
			if(!checkerr("fragment program w/o light filter and detailed bumpmap"))
				return false;
		}
		if(fragment_program_filter6_detail == 0xffffffff)
		{
			glGenProgramsARB(1, &fragment_program_filter6_detail);
			GL_BindProgramFP(fragment_program_filter6_detail);
			glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fragmentprogram_filter6_detail_ptr), fragmentprogram_filter6_detail_ptr);
			if(!checkerr("fragment program with light filter and detailed bumpmap"))
				return false;
		}
		if(fragment_program_filter_parallax == 0xffffffff)
		{
			glGenProgramsARB(1, &fragment_program_filter_parallax);
			GL_BindProgramFP(fragment_program_filter_parallax);
			glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fragmentprogram_filter_parallax_ptr), fragmentprogram_filter_parallax_ptr);
			if(!checkerr("fragment program with light filter and parallax"))
				return false;
		}
		if(fragment_program_nofilter_parallax == 0xffffffff)
		{
			glGenProgramsARB(1, &fragment_program_nofilter_parallax);
			GL_BindProgramFP(fragment_program_nofilter_parallax);
			glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fragmentprogram_nofilter_parallax_ptr), fragmentprogram_nofilter_parallax_ptr);
			if(!checkerr("fragment program w/o light filter and parallax"))
				return false;
		}
		if(fragment_program_ambient_parallax == 0xffffffff)
		{
			glGenProgramsARB(1, &fragment_program_ambient_parallax);
			GL_BindProgramFP(fragment_program_ambient_parallax);
			glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fragmentprogram_ambient_parallax_ptr), fragmentprogram_ambient_parallax_ptr);
			if(!checkerr("fragment program for ambient and parallax"))
				return false;
		}
		if(fragment_program_nobump_parallax == 0xffffffff)
		{
			glGenProgramsARB(1, &fragment_program_nobump_parallax);
			GL_BindProgramFP(fragment_program_nobump_parallax);
			glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fragmentprogram_nobump_parallax_ptr), fragmentprogram_nobump_parallax_ptr);
			if(!checkerr("fragment program for nobump and parallax"))
				return false;
		}
	}

	if(r_overbright->value)
	{
		glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 2*r_diffuse_modifier->value,2*r_diffuse_modifier->value,2*r_diffuse_modifier->value,2*r_diffuse_modifier->value);
		glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, 2*r_specular_modifier->value,2*r_specular_modifier->value,2*r_specular_modifier->value,2*r_specular_modifier->value);
	}
	else
	{
		glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, r_diffuse_modifier->value,r_diffuse_modifier->value,r_diffuse_modifier->value,r_diffuse_modifier->value);
		glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, r_specular_modifier->value,r_specular_modifier->value,r_specular_modifier->value,r_specular_modifier->value);
	}

	if (r_shader->value == SHADER_ARB6 && r_parallax->value)
	{
		float prlxScale = r_parallax_scale->value;
		if (prlxScale < 0)	prlxScale = 0;
		else if (prlxScale > 3)	prlxScale = 3;
		prlxScale += 1;
		prlxScale *= 0.0104167;
		glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 4, prlxScale, prlxScale, -prlxScale*0.5, -prlxScale*0.5);
	}

	glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 2, float(viddef.width) / gl_config.screenTextureSize[0] - 0.001, float(viddef.height) / gl_config.screenTextureSize[1] - 0.001, 0, 0);

///	glDisable(GL_FRAGMENT_PROGRAM_ARB);
	return true;
}


void GL_LoadVertexPrograms()
{
	gl_config.vp = false;
	if (r_vp->value)
	{
		if(strstr(gl_config.extensions_string, "GL_ARB_vertex_program"))
		{
			SAFE_GET_PROC(glProgramStringARB,PFNGLPROGRAMSTRINGARBPROC,"glProgramStringARB");
			SAFE_GET_PROC(glBindProgramARB,PFNGLBINDPROGRAMARBPROC,"glBindProgramARB");
			SAFE_GET_PROC(glDeleteProgramsARB,PFNGLDELETEPROGRAMSARBPROC,"glDeleteProgramsARB");
			SAFE_GET_PROC(glGenProgramsARB,PFNGLGENPROGRAMSARBPROC,"glGenProgramsARB");
			SAFE_GET_PROC(glProgramEnvParameter4dARB,PFNGLPROGRAMENVPARAMETER4DARBPROC,"glProgramEnvParameter4dARB");
			SAFE_GET_PROC(glProgramEnvParameter4dvARB,PFNGLPROGRAMENVPARAMETER4DVARBPROC,"glProgramEnvParameter4dvARB");
			SAFE_GET_PROC(glProgramEnvParameter4fARB,PFNGLPROGRAMENVPARAMETER4FARBPROC,"glProgramEnvParameter4fARB");
			SAFE_GET_PROC(glProgramEnvParameter4fvARB,PFNGLPROGRAMENVPARAMETER4FVARBPROC,"glProgramEnvParameter4fvARB");
			SAFE_GET_PROC(glProgramLocalParameter4dARB,PFNGLPROGRAMLOCALPARAMETER4DARBPROC,"glProgramLocalParameter4dARB");
			SAFE_GET_PROC(glProgramLocalParameter4dvARB,PFNGLPROGRAMLOCALPARAMETER4DVARBPROC,"glProgramLocalParameter4dvARB");
			SAFE_GET_PROC(glProgramLocalParameter4fARB,PFNGLPROGRAMLOCALPARAMETER4FARBPROC,"glProgramLocalParameter4fARB");
			SAFE_GET_PROC(glProgramLocalParameter4fvARB,PFNGLPROGRAMLOCALPARAMETER4FVARBPROC,"glProgramLocalParameter4fvARB");
			SAFE_GET_PROC(glGetProgramEnvParameterdvARB,PFNGLGETPROGRAMENVPARAMETERDVARBPROC,"glGetProgramEnvParameterdvARB");
			SAFE_GET_PROC(glGetProgramEnvParameterfvARB,PFNGLGETPROGRAMENVPARAMETERFVARBPROC,"glGetProgramEnvParameterfvARB");
			SAFE_GET_PROC(glGetProgramLocalParameterdvARB,PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC,"glGetProgramLocalParameterdvARB");
			SAFE_GET_PROC(glGetProgramLocalParameterfvARB,PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC,"glGetProgramLocalParameterfvARB");
			SAFE_GET_PROC(glGetProgramivARB,PFNGLGETPROGRAMIVARBPROC,"glGetProgramivARB");
			SAFE_GET_PROC(glGetProgramStringARB,PFNGLGETPROGRAMSTRINGARBPROC,"glGetProgramStringARB");
			SAFE_GET_PROC(glIsProgramARB,PFNGLISPROGRAMARBPROC,"glIsProgramARB");
			SAFE_GET_PROC(glVertexAttrib1dARB,PFNGLVERTEXATTRIB1DARBPROC,"glVertexAttrib1dARB");
			SAFE_GET_PROC(glVertexAttrib1dvARB,PFNGLVERTEXATTRIB1DVARBPROC,"glVertexAttrib1dvARB");
			SAFE_GET_PROC(glVertexAttrib1fARB,PFNGLVERTEXATTRIB1FARBPROC,"glVertexAttrib1fARB");
			SAFE_GET_PROC(glVertexAttrib1fvARB,PFNGLVERTEXATTRIB1FVARBPROC,"glVertexAttrib1fvARB");
			SAFE_GET_PROC(glVertexAttrib1sARB,PFNGLVERTEXATTRIB1SARBPROC,"glVertexAttrib1sARB");
			SAFE_GET_PROC(glVertexAttrib1svARB,PFNGLVERTEXATTRIB1SVARBPROC,"glVertexAttrib1svARB");
			SAFE_GET_PROC(glVertexAttrib2dARB,PFNGLVERTEXATTRIB2DARBPROC,"glVertexAttrib2dARB");
			SAFE_GET_PROC(glVertexAttrib2dvARB,PFNGLVERTEXATTRIB2DVARBPROC,"glVertexAttrib2dvARB");
			SAFE_GET_PROC(glVertexAttrib2fARB,PFNGLVERTEXATTRIB2FARBPROC,"glVertexAttrib2fARB");
			SAFE_GET_PROC(glVertexAttrib2fvARB,PFNGLVERTEXATTRIB2FVARBPROC,"glVertexAttrib2fvARB");
			SAFE_GET_PROC(glVertexAttrib2sARB,PFNGLVERTEXATTRIB2SARBPROC,"glVertexAttrib2sARB");
			SAFE_GET_PROC(glVertexAttrib2svARB,PFNGLVERTEXATTRIB2SVARBPROC,"glVertexAttrib2svARB");
			SAFE_GET_PROC(glVertexAttrib3dARB,PFNGLVERTEXATTRIB3DARBPROC,"glVertexAttrib3dARB");
			SAFE_GET_PROC(glVertexAttrib3dvARB,PFNGLVERTEXATTRIB3DVARBPROC,"glVertexAttrib3dvARB");
			SAFE_GET_PROC(glVertexAttrib3fARB,PFNGLVERTEXATTRIB3FARBPROC,"glVertexAttrib3fARB");
			SAFE_GET_PROC(glVertexAttrib3fvARB,PFNGLVERTEXATTRIB3FVARBPROC,"glVertexAttrib3fvARB");
			SAFE_GET_PROC(glVertexAttrib3sARB,PFNGLVERTEXATTRIB3SARBPROC,"glVertexAttrib3sARB");
			SAFE_GET_PROC(glVertexAttrib3svARB,PFNGLVERTEXATTRIB3SVARBPROC,"glVertexAttrib3svARB");
			SAFE_GET_PROC(glVertexAttrib4NbvARB,PFNGLVERTEXATTRIB4NBVARBPROC,"glVertexAttrib4NbvARB");
			SAFE_GET_PROC(glVertexAttrib4NivARB,PFNGLVERTEXATTRIB4NIVARBPROC,"glVertexAttrib4NivARB");
			SAFE_GET_PROC(glVertexAttrib4NsvARB,PFNGLVERTEXATTRIB4NSVARBPROC,"glVertexAttrib4NsvARB");
			SAFE_GET_PROC(glVertexAttrib4NubARB,PFNGLVERTEXATTRIB4NUBARBPROC,"glVertexAttrib4NubARB");
			SAFE_GET_PROC(glVertexAttrib4NubvARB,PFNGLVERTEXATTRIB4NUBVARBPROC,"glVertexAttrib4NubvARB");
			SAFE_GET_PROC(glVertexAttrib4NuivARB,PFNGLVERTEXATTRIB4NUIVARBPROC,"glVertexAttrib4NuivARB");
			SAFE_GET_PROC(glVertexAttrib4NusvARB,PFNGLVERTEXATTRIB4NUSVARBPROC,"glVertexAttrib4NusvARB");
			SAFE_GET_PROC(glVertexAttrib4bvARB,PFNGLVERTEXATTRIB4BVARBPROC,"glVertexAttrib4bvARB");
			SAFE_GET_PROC(glVertexAttrib4dARB,PFNGLVERTEXATTRIB4DARBPROC,"glVertexAttrib4dARB");
			SAFE_GET_PROC(glVertexAttrib4dvARB,PFNGLVERTEXATTRIB4DVARBPROC,"glVertexAttrib4dvARB");
			SAFE_GET_PROC(glVertexAttrib4fARB,PFNGLVERTEXATTRIB4FARBPROC,"glVertexAttrib4fARB");
			SAFE_GET_PROC(glVertexAttrib4fvARB,PFNGLVERTEXATTRIB4FVARBPROC,"glVertexAttrib4fvARB");
			SAFE_GET_PROC(glVertexAttrib4ivARB,PFNGLVERTEXATTRIB4IVARBPROC,"glVertexAttrib4ivARB");
			SAFE_GET_PROC(glVertexAttrib4sARB,PFNGLVERTEXATTRIB4SARBPROC,"glVertexAttrib4sARB");
			SAFE_GET_PROC(glVertexAttrib4svARB,PFNGLVERTEXATTRIB4SVARBPROC,"glVertexAttrib4svARB");
			SAFE_GET_PROC(glVertexAttrib4ubvARB,PFNGLVERTEXATTRIB4UBVARBPROC,"glVertexAttrib4ubvARB");
			SAFE_GET_PROC(glVertexAttrib4uivARB,PFNGLVERTEXATTRIB4UIVARBPROC,"glVertexAttrib4uivARB");
			SAFE_GET_PROC(glVertexAttrib4usvARB,PFNGLVERTEXATTRIB4USVARBPROC,"glVertexAttrib4usvARB");
			SAFE_GET_PROC(glVertexAttribPointerARB,PFNGLVERTEXATTRIBPOINTERARBPROC,"glVertexAttribPointerARB");
			SAFE_GET_PROC(glEnableVertexAttribArrayARB,PFNGLENABLEVERTEXATTRIBARRAYARBPROC,"glEnableVertexAttribArrayARB");
			SAFE_GET_PROC(glDisableVertexAttribArrayARB,PFNGLDISABLEVERTEXATTRIBARRAYARBPROC,"glDisableVertexAttribArrayARB");
			SAFE_GET_PROC(glGetVertexAttribdvARB,PFNGLGETVERTEXATTRIBDVARBPROC,"glGetVertexAttribdvARB");
			SAFE_GET_PROC(glGetVertexAttribfvARB,PFNGLGETVERTEXATTRIBFVARBPROC,"glGetVertexAttribfvARB");
			SAFE_GET_PROC(glGetVertexAttribivARB,PFNGLGETVERTEXATTRIBIVARBPROC,"glGetVertexAttribivARB");
			SAFE_GET_PROC(glGetVertexAttribPointervARB,PFNGLGETVERTEXATTRIBPOINTERVARBPROC,"glGetVertexAttribPointervARB");

			if( !glProgramStringARB ||
				!glBindProgramARB ||
				!glDeleteProgramsARB ||
				!glGenProgramsARB ||
				!glProgramEnvParameter4dARB ||
				!glProgramEnvParameter4dvARB ||
				!glProgramEnvParameter4fARB ||
				!glProgramEnvParameter4fvARB ||
				!glProgramLocalParameter4dARB ||
				!glProgramLocalParameter4dvARB ||
				!glProgramLocalParameter4fARB ||
				!glProgramLocalParameter4fvARB ||
				!glGetProgramEnvParameterdvARB ||
				!glGetProgramEnvParameterfvARB ||
				!glGetProgramLocalParameterdvARB ||
				!glGetProgramLocalParameterfvARB ||
				!glGetProgramivARB ||
				!glGetProgramStringARB ||
				!glIsProgramARB ||
				!glVertexAttrib1dARB ||
				!glVertexAttrib1dvARB ||
				!glVertexAttrib1fARB ||
				!glVertexAttrib1fvARB ||
				!glVertexAttrib1sARB ||
				!glVertexAttrib1svARB ||
				!glVertexAttrib2dARB ||
				!glVertexAttrib2dvARB ||
				!glVertexAttrib2fARB ||
				!glVertexAttrib2fvARB ||
				!glVertexAttrib2sARB ||
				!glVertexAttrib2svARB ||
				!glVertexAttrib3dARB ||
				!glVertexAttrib3dvARB ||
				!glVertexAttrib3fARB ||
				!glVertexAttrib3fvARB ||
				!glVertexAttrib3sARB ||
				!glVertexAttrib3svARB ||
				!glVertexAttrib4NbvARB ||
				!glVertexAttrib4NivARB ||
				!glVertexAttrib4NsvARB ||
				!glVertexAttrib4NubARB ||
				!glVertexAttrib4NubvARB ||
				!glVertexAttrib4NuivARB ||
				!glVertexAttrib4NusvARB ||
				!glVertexAttrib4bvARB ||
				!glVertexAttrib4dARB ||
				!glVertexAttrib4dvARB ||
				!glVertexAttrib4fARB ||
				!glVertexAttrib4fvARB ||
				!glVertexAttrib4ivARB ||
				!glVertexAttrib4sARB ||
				!glVertexAttrib4svARB ||
				!glVertexAttrib4ubvARB ||
				!glVertexAttrib4uivARB ||
				!glVertexAttrib4usvARB ||
				!glVertexAttribPointerARB ||
				!glEnableVertexAttribArrayARB ||
				!glDisableVertexAttribArrayARB ||
				!glGetVertexAttribdvARB ||
				!glGetVertexAttribfvARB ||
				!glGetVertexAttribivARB ||
				!glGetVertexAttribPointervARB )
				Com_Printf("^1X..failed to import ARBvp1.0 functions\n" );
			else
			{
				Com_Printf("...using GL_ARB_vertex_program\n" );
				gl_config.vp = true;

				int		length;
				char	name[MAX_QPATH];
				char	*buffer[4];
////////////////////////////////////////////////////////////////////////////////////
				Com_sprintf (name, sizeof(name), "glprogs/nolightfilter.vp");
				length = FS_LoadFile (name, (void **)&buffer[0]);
				if (!buffer[0] || (length <= 0))
				{
					if(buffer[0])
						Z_Free (buffer[0]);
intern2:			Com_Printf ("...using internal \"nolightfilter.vp\"\n");
					vertexprogram_nolightfilter_ptr = vertexprogram_nolightfilter;
				}
				else
				{
					Com_Printf ("...loading \"%s\"\n", name);
					vertexprogram_nolightfilter_ptr = buffer[0];

					if (strncmp(vertexprogram_nolightfilter_ptr,"!!ARBvp1.0",10))
					{
						Com_Printf("^1Corrupted! Using internal program.\n" );
intern:					Z_Free (vertexprogram_nolightfilter_ptr);
						vertexprogram_nolightfilter_ptr = vertexprogram_nolightfilter;
					}
				}

				if(vertex_program_nolightfilter == 0xffffffff)
				{
					glGenProgramsARB(1, &vertex_program_nolightfilter);
					GL_BindProgramVP(vertex_program_nolightfilter);
					glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(vertexprogram_nolightfilter_ptr), vertexprogram_nolightfilter_ptr);
					if(!checkerr("nolightfilter vertex program"))
					{
						if (vertexprogram_nolightfilter_ptr != vertexprogram_nolightfilter)
							goto intern;
						else
							goto intern2;
					}
				}
////////////////////////////////////////////////////////////////////////////////////
				Com_sprintf (name, sizeof(name), "glprogs/lightfilter6.vp");
				length = FS_LoadFile (name, (void **)&buffer[0]);
				if (!buffer[0] || (length <= 0))
				{
					if(buffer[0])
						Z_Free (buffer[0]);
intern2_:			Com_Printf ("...using internal \"lightfilter6.vp\"\n");
					vertexprogram_lightfilter6_ptr = vertexprogram_lightfilter6;
				}
				else
				{
					Com_Printf ("...loading \"%s\"\n", name);
					vertexprogram_lightfilter6_ptr = buffer[0];

					if (strncmp(vertexprogram_lightfilter6_ptr,"!!ARBvp1.0",10))
					{
						Com_Printf("^1Corrupted! Using internal program.\n" );
intern_:				Z_Free (vertexprogram_lightfilter6_ptr);
						vertexprogram_lightfilter6_ptr = vertexprogram_lightfilter6;
					}
				}

				if(vertex_program_lightfilter6 == 0xffffffff)
				{
					glGenProgramsARB(1, &vertex_program_lightfilter6);
					GL_BindProgramVP(vertex_program_lightfilter6);
					glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(vertexprogram_lightfilter6_ptr), vertexprogram_lightfilter6_ptr);
					if(!checkerr("lightfilter6 vertex program"))
					{
						if (vertexprogram_lightfilter6_ptr != vertexprogram_lightfilter6)
							goto intern_;
						else
							goto intern2_;
					}
				}
////////////////////////////////////////////////////////////////////////////////////
				Com_sprintf (name, sizeof(name), "glprogs/lightfilter4.vp");
				length = FS_LoadFile (name, (void **)&buffer[0]);
				if (!buffer[0] || (length <= 0))
				{
					if(buffer[0])
						Z_Free (buffer[0]);
intern2__:			Com_Printf ("...using internal \"lightfilter4.vp\"\n");
					vertexprogram_lightfilter4_ptr = vertexprogram_lightfilter4;
				}
				else
				{
					Com_Printf ("...loading \"%s\"\n", name);
					vertexprogram_lightfilter4_ptr = buffer[0];

					if (strncmp(vertexprogram_lightfilter4_ptr,"!!ARBvp1.0",10))
					{
						Com_Printf("^1Corrupted! Using internal program.\n" );
intern__:				Z_Free (vertexprogram_lightfilter4_ptr);
						vertexprogram_lightfilter4_ptr = vertexprogram_lightfilter4;
					}
				}

				if(vertex_program_lightfilter4 == 0xffffffff)
				{
					glGenProgramsARB(1, &vertex_program_lightfilter4);
					GL_BindProgramVP(vertex_program_lightfilter4);
					glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(vertexprogram_lightfilter4_ptr), vertexprogram_lightfilter4_ptr);
					if(!checkerr("lightfilter4 vertex program"))
					{
						if (vertexprogram_lightfilter4_ptr != vertexprogram_lightfilter4)
							goto intern__;
						else
							goto intern2__;
					}
				}
////////////////////////////////////////////////////////////////////////////////////
				Com_sprintf (name, sizeof(name), "glprogs/lightfilter6_parallax.vp");
				length = FS_LoadFile (name, (void **)&buffer[0]);
				if (!buffer[0] || (length <= 0))
				{
					if(buffer[0])
						Z_Free (buffer[0]);
intern2___:			Com_Printf ("...using internal \"lightfilter6_parallax.vp\"\n");
					vertexprogram_lightfilter6_parallax_ptr = vertexprogram_lightfilter6_parallax;
				}
				else
				{
					Com_Printf ("...loading \"%s\"\n", name);
					vertexprogram_lightfilter6_parallax_ptr = buffer[0];

					if (strncmp(vertexprogram_lightfilter6_parallax_ptr,"!!ARBvp1.0",10))
					{
						Com_Printf("^1Corrupted! Using internal program.\n" );
intern___:				Z_Free (vertexprogram_lightfilter6_parallax_ptr);
						vertexprogram_lightfilter6_parallax_ptr = vertexprogram_lightfilter6_parallax;
					}
				}

				if(vertex_program_lightfilter6_parallax == 0xffffffff)
				{
					glGenProgramsARB(1, &vertex_program_lightfilter6_parallax);
					GL_BindProgramVP(vertex_program_lightfilter6_parallax);
					glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(vertexprogram_lightfilter6_parallax_ptr), vertexprogram_lightfilter6_parallax_ptr);
					if(!checkerr("lightfilter6_parallax vertex program"))
					{
						if (vertexprogram_lightfilter6_parallax_ptr != vertexprogram_lightfilter6_parallax)
							goto intern___;
						else
							goto intern2___;
					}
				}
////////////////////////////////////////////////////////////////////////////////////
				Com_sprintf (name, sizeof(name), "glprogs/nolightfilter_parallax.vp");
				length = FS_LoadFile (name, (void **)&buffer[0]);
				if (!buffer[0] || (length <= 0))
				{
					if(buffer[0])
						Z_Free (buffer[0]);
intern2____:		Com_Printf ("...using internal \"nolightfilter_parallax.vp\"\n");
					vertexprogram_nolightfilter_parallax_ptr = vertexprogram_nolightfilter_parallax;
				}
				else
				{
					Com_Printf ("...loading \"%s\"\n", name);
					vertexprogram_nolightfilter_parallax_ptr = buffer[0];

					if (strncmp(vertexprogram_nolightfilter_parallax_ptr,"!!ARBvp1.0",10))
					{
						Com_Printf("^1Corrupted! Using internal program.\n" );
intern____:				Z_Free (vertexprogram_nolightfilter_parallax_ptr);
						vertexprogram_nolightfilter_parallax_ptr = vertexprogram_nolightfilter_parallax;
					}
				}

				if(vertex_program_nolightfilter_parallax == 0xffffffff)
				{
					glGenProgramsARB(1, &vertex_program_nolightfilter_parallax);
					GL_BindProgramVP(vertex_program_nolightfilter_parallax);
					glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(vertexprogram_nolightfilter_parallax_ptr), vertexprogram_nolightfilter_parallax_ptr);
					if(!checkerr("nolightfilter_parallax vertex program"))
					{
						if (vertexprogram_nolightfilter_parallax_ptr != vertexprogram_nolightfilter_parallax)
							goto intern____;
						else
							goto intern2____;
					}
				}
////////////////////////////////////////////////////////////////////////////////////
				Com_sprintf (name, sizeof(name), "glprogs/ambient_parallax.vp");
				length = FS_LoadFile (name, (void **)&buffer[0]);
				if (!buffer[0] || (length <= 0))
				{
					if(buffer[0])
						Z_Free (buffer[0]);
intern2___0:		Com_Printf ("...using internal \"ambient_parallax.vp\"\n");
					vertexprogram_ambient_parallax_ptr = vertexprogram_ambient_parallax;
				}
				else
				{
					Com_Printf ("...loading \"%s\"\n", name);
					vertexprogram_ambient_parallax_ptr = buffer[0];

					if (strncmp(vertexprogram_ambient_parallax_ptr,"!!ARBvp1.0",10))
					{
						Com_Printf("^1Corrupted! Using internal program.\n" );
intern___0:				Z_Free (vertexprogram_ambient_parallax_ptr);
						vertexprogram_ambient_parallax_ptr = vertexprogram_ambient_parallax;
					}
				}

				if(vertex_program_ambient_parallax == 0xffffffff)
				{
					glGenProgramsARB(1, &vertex_program_ambient_parallax);
					GL_BindProgramVP(vertex_program_ambient_parallax);
					glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(vertexprogram_ambient_parallax_ptr), vertexprogram_ambient_parallax_ptr);
					if(!checkerr("ambient_parallax vertex program"))
					{
						if (vertexprogram_ambient_parallax_ptr != vertexprogram_ambient_parallax)
							goto intern___0;
						else
							goto intern2___0;
					}
				}
////////////////////////////////////////////////////////////////////////////////////

				Com_sprintf (name, sizeof(name), "glprogs/nobump_parallax.vp");
				length = FS_LoadFile (name, (void **)&buffer[0]);
				if (!buffer[0] || (length <= 0))
				{
					if(buffer[0])
						Z_Free (buffer[0]);
intern2____0:		Com_Printf ("...using internal \"nobump_parallax.vp\"\n");
					vertexprogram_nobump_parallax_ptr = vertexprogram_nobump_parallax;
				}
				else
				{
					Com_Printf ("...loading \"%s\"\n", name);
					vertexprogram_nobump_parallax_ptr = buffer[0];

					if (strncmp(vertexprogram_nobump_parallax_ptr,"!!ARBvp1.0",10))
					{
						Com_Printf("^1Corrupted! Using internal program.\n" );
intern____0:			Z_Free (vertexprogram_nobump_parallax_ptr);
						vertexprogram_nobump_parallax_ptr = vertexprogram_nobump_parallax;
					}
				}

				if(vertex_program_nobump_parallax == 0xffffffff)
				{
					glGenProgramsARB(1, &vertex_program_nobump_parallax);
					GL_BindProgramVP(vertex_program_nobump_parallax);
					glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(vertexprogram_nobump_parallax_ptr), vertexprogram_nobump_parallax_ptr);
					if(!checkerr("nobump_parallax vertex program"))
					{
						if (vertexprogram_nobump_parallax_ptr != vertexprogram_nobump_parallax)
							goto intern____0;
						else
							goto intern2____0;
					}
				}
////////////////////////////////////////////////////////////////////////////////////

			}
		}
		else
			Com_Printf("^1X..GL_ARB_vertex_program not found\n" );
	}
	else
		Com_Printf("^3X..ignoring GL_ARB_vertex_program\n" );
}


void GL_CheckOcclusion()
{
	if (r_occlusion->value && !r_simple->value)	// окклюжн-тест имеет смысл только для PPL
	{
		if ( strstr( gl_config.extensions_string, "GL_ARB_occlusion_query" ) )
		{
			SAFE_GET_PROC(glGenQueriesARB, PFNGLGENQUERIESARBPROC, "glGenQueriesARB");
			SAFE_GET_PROC(glDeleteQueriesARB, PFNGLDELETEQUERIESARBPROC, "glDeleteQueriesARB");
			SAFE_GET_PROC(glIsQueryARB, PFNGLISQUERYARBPROC, "glIsQueryARB");
			SAFE_GET_PROC(glBeginQueryARB, PFNGLBEGINQUERYARBPROC, "glBeginQueryARB");
			SAFE_GET_PROC(glEndQueryARB, PFNGLENDQUERYARBPROC, "glEndQueryARB");
			SAFE_GET_PROC(glGetQueryivARB, PFNGLGETQUERYIVARBPROC, "glGetQueryivARB");
			SAFE_GET_PROC(glGetQueryObjectivARB, PFNGLGETQUERYOBJECTIVARBPROC, "glGetQueryObjectivARB");
			SAFE_GET_PROC(glGetQueryObjectuivARB, PFNGLGETQUERYOBJECTUIVARBPROC, "glGetQueryObjectuivARB");
			if(glGenQueriesARB && glDeleteQueriesARB && glIsQueryARB && glBeginQueryARB && glEndQueryARB && glGetQueryivARB && glGetQueryObjectivARB && glGetQueryObjectuivARB)
			{
				int GL_QueryBits;
				glGetQueryivARB (GL_SAMPLES_PASSED_ARB, GL_QUERY_COUNTER_BITS_ARB, &GL_QueryBits);
				if(GL_QueryBits)
				{
					if ( strstr( gl_config.extensions_string, "GL_ARB_occlusion_query2" ) )
					{
						Com_Printf("...using GL_ARB_occlusion_query2\n" );
						gl_config.occlusion = GL_ANY_SAMPLES_PASSED;
					}
					else
					{
						Com_Printf("...using GL_ARB_occlusion_query\n" );
						gl_config.occlusion = GL_SAMPLES_PASSED_ARB;
					}
				}
				else
				{
					Com_Printf("^1X..GL_QUERY_COUNTER_BITS_ARB = NULL\n" );
					goto eror;
				}
			}
			else
			{
				Com_Printf("^1X..failed to import GL_ARB_occlusion_query functions\n" );
				gl_config.occlusion = 0;
				glGenQueriesARB = NULL;
				glDeleteQueriesARB = NULL;
				glIsQueryARB = NULL;
				glBeginQueryARB = NULL;
				glEndQueryARB = NULL;
				glGetQueryivARB = NULL;
				glGetQueryObjectivARB = NULL;
				glGetQueryObjectuivARB = NULL;
			}
		}
		else
		{
			Com_Printf("^1X..GL_ARB_occlusion_query not found\n" );
			gl_config.occlusion = 0;
		}
	}
	else
	{
eror:	Com_Printf("^3X..ignoring GL_ARB_occlusion_query\n" );
		gl_config.occlusion = 0;
	}
}


void GL_SelectMirrorSize(bool silent)
{
	int max_mirror_size = r_mirror_size->value;
	int vsize = min(viddef.width, viddef.height);
	if ((max_mirror_size < MIRROR_SIZE) || (max_mirror_size > vsize))
		max_mirror_size = vsize;

	gl_config.mirrorTextureSize = 1;
	while (1)
	{
		gl_config.mirrorTextureSize += gl_config.mirrorTextureSize;
		if (gl_config.mirrorTextureSize == max_mirror_size)
			break;
		if (gl_config.mirrorTextureSize > max_mirror_size)
		{
			gl_config.mirrorTextureSize /= 2;
			break;
		}
	}

	if(gl_config.maxTextureSize < gl_config.mirrorTextureSize )
	{
		if (!silent)
			Com_Printf("^3X..mirrorTextureSize [%i] exceeds hardware limits [%i]\n", gl_config.mirrorTextureSize, gl_config.maxTextureSize );
		gl_config.mirrorTextureSize = gl_config.maxTextureSize;
	}

	if (!silent)
		Com_Printf("...mirrorTextureSize [%i]\n", gl_config.mirrorTextureSize );
	Cvar_SetValue("r_mirror_size", gl_config.mirrorTextureSize);
}


bool GL_CheckCommonExtensions()
{
	Com_Printf("Checking for common extensions:\n" );

	// GL_NVX_gpu_memory_info
	if ( strstr( gl_config.extensions_string, "GL_NVX_gpu_memory_info") )
	{
		Com_Printf("...using GL_NVX_gpu_memory_info\n");
		gl_config.nv_meminfo = true;
	}
	else
	{
		Com_Printf("^1X..GL_NVX_gpu_memory_info not found\n");
		gl_config.nv_meminfo = false;
	}

	// GL_ATI_meminfo
	if ( strstr( gl_config.extensions_string, "GL_ATI_meminfo") )
	{
		Com_Printf("...using GL_ATI_meminfo\n");
		gl_config.ati_meminfo = true;
	}
	else
	{
		Com_Printf("^1X..GL_ATI_meminfo not found\n");
		gl_config.ati_meminfo = false;
	}

	// Get max texture size supported
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_config.maxTextureSize);
	Com_Printf("...GL_MAX_TEXTURE_SIZE [%i]\n", gl_config.maxTextureSize );

	if (gl_config.maxTextureSize < LIGHTMAP_SIZE)
	{
		Com_Printf("^1X..GL_MAX_TEXTURE_SIZE(%i) less then LIGHTMAP_SIZE(%i)\n", gl_config.maxTextureSize, LIGHTMAP_SIZE );
		return false;
	}

	gl_config.screenTextureSize[0] = gl_config.screenTextureSize[1] = 1;
	while (1)
	{
		gl_config.screenTextureSize[0] *= 2;
		if(gl_config.screenTextureSize[0] >= viddef.width)
			break;
	}
	while (1)
	{
		gl_config.screenTextureSize[1] *= 2;
		if(gl_config.screenTextureSize[1] >= viddef.height)
			break;
	}

	if(gl_config.maxTextureSize < gl_config.screenTextureSize[0] || gl_config.maxTextureSize < gl_config.screenTextureSize[1])
	{
		Com_Printf("^1X..screenTextureSize [%i x %i] exceeds hardware limits [%i]\n", gl_config.screenTextureSize[0], gl_config.screenTextureSize[1], gl_config.maxTextureSize);
		gl_config.screentexture = false;
	}
	else
	{
		Com_Printf("...screenTextureSize [%i x %i]\n", gl_config.screenTextureSize[0], gl_config.screenTextureSize[1]);
		gl_config.screentexture = true;
	}

	GL_SelectMirrorSize(false);

	if ( gl_config.anisotropic )
		Com_Printf("...using GL_EXT_texture_filter_anisotropic [%i]\n", (int)gl_config.max_anisotropy);
	else
		Com_Printf("^1X..GL_EXT_texture_filter_anisotropic not found\n");

	SAFE_GET_PROC( wglSwapIntervalEXT, PFNWGLSWAPINTERVALEXTPROC, "wglSwapIntervalEXT");
	if(wglSwapIntervalEXT)
	{
		Com_Printf("...using WGL_EXT_swap_control\n" );
		if ( strstr( glw_state.wglExtsString, "WGL_EXT_swap_control_tear") )
		{
			Com_Printf("...using WGL_EXT_swap_control_tear\n");
			gl_config.wgl_swap_control_tear = true;
		}
		else
		{
			Com_Printf("^1X..WGL_EXT_swap_control_tear not found\n");
			gl_config.wgl_swap_control_tear = false;
		}
	}
	else
		Com_Printf("^1X..WGL_EXT_swap_control not found\n");


	if ( strstr( gl_config.extensions_string, "GL_ARB_texture_compression") )
	{
		Com_Printf("...using GL_ARB_texture_compression\n");
		gl_config.compression = true;
	}
	else
	{
		Com_Printf("^1X..GL_ARB_texture_compression not found\n");
		gl_config.compression = false;
	}

	if ( strstr( gl_config.extensions_string, "GL_EXT_texture_compression_s3tc") )
	{
		Com_Printf("...using GL_EXT_texture_compression_s3tc\n");
		gl_config.s3_compression = true;
	}
	else
	{
		Com_Printf("^1X..GL_EXT_texture_compression_s3tc not found\n");
		gl_config.s3_compression = false;
	}

	if(gl_config.compression && !gl_config.s3_compression && r_diffuse_compression->value>0 && r_diffuse_compression->value<4)
		Cvar_ForceSetValue("r_diffuse_compression", 4);

	if(gl_config.compression && !gl_config.s3_compression && r_bump_compression->value>0 && r_bump_compression->value<4)
		Cvar_ForceSetValue("r_bump_compression", 4);

	if(!gl_config.compression && gl_config.s3_compression && r_diffuse_compression->value==4)
		Cvar_ForceSetValue("r_diffuse_compression", 1);

	if(!gl_config.compression && gl_config.s3_compression && r_bump_compression->value==4)
		Cvar_ForceSetValue("r_bump_compression", 1);


	if (strstr( gl_config.vendor_string, "ATI") )
	{
		Com_Printf("^1X..GL_EXT_point_parameters functions disabled for ATI cards\n" );
		glPointParameterfEXT = NULL;
		glPointParameterfvEXT = NULL;
	}
	else if ( strstr( gl_config.extensions_string, "GL_EXT_point_parameters" ) )
	{
		SAFE_GET_PROC( glPointParameterfEXT, PFNGLPOINTPARAMETERFEXTPROC, "glPointParameterfEXT" );
		SAFE_GET_PROC( glPointParameterfvEXT, PFNGLPOINTPARAMETERFVEXTPROC, "glPointParameterfvEXT" );
		if(glPointParameterfEXT && glPointParameterfvEXT)
			Com_Printf("...using GL_EXT_point_parameters\n" );
		else
		{
			Com_Printf("^1X..failed to import GL_EXT_point_parameters functions\n" );
			glPointParameterfEXT = NULL;
			glPointParameterfvEXT = NULL;
		}
	}
	else
		Com_Printf("^1X..GL_EXT_point_parameters not found\n" );

	GL_CheckOcclusion();

	if ( strstr( gl_config.extensions_string, "GL_EXT_texture_lod_bias" ) )
	{
		Com_Printf("...using GL_EXT_texture_lod_bias\n" );
		gl_config.lod_bias = true;
	}
	else
	{
		Com_Printf("^1X..GL_EXT_texture_lod_bias not found\n" );
		gl_config.lod_bias = false;
	}

	///	UNSTABLE:  md3 in fog with vbo enabled => crash (jail1)
///	SAFE_GET_PROC( glDrawRangeElementsEXT, PFNGLDRAWRANGEELEMENTSEXTPROC, "glDrawRangeElementsEXT" );
///	if (glDrawRangeElementsEXT)
///		Com_Printf("...using GL_EXT_draw_range_elements\n" );
///	else
///		Com_Printf("^1X..GL_EXT_draw_range_elements not found\n" );

	if ( strstr( gl_config.extensions_string, "GL_EXT_texture_edge_clamp" ) )
	{
		Com_Printf("...using GL_EXT_texture_edge_clamp\n" );
		gl_edge_type = GL_CLAMP_TO_EDGE;
	}
	else
	{
		Com_Printf("^1X..GL_EXT_texture_edge_clamp not found\n" );
		gl_edge_type = GL_CLAMP;
	}

	if (r_vbo->value)
	{
		SAFE_GET_PROC (glGenBuffersARB, PFNGLGENBUFFERSARBPROC, "glGenBuffersARB");
		SAFE_GET_PROC (glBindBufferARB, PFNGLBINDBUFFERARBPROC, "glBindBufferARB");
		SAFE_GET_PROC (glBufferDataARB, PFNGLBUFFERDATAARBPROC, "glBufferDataARB");
		SAFE_GET_PROC (glDeleteBuffersARB, PFNGLDELETEBUFFERSARBPROC, "glDeleteBuffersARB");
///		SAFE_GET_PROC (glMapBufferARB, PFNGLMAPBUFFERARBPROC, "glMapBufferARB");
///		SAFE_GET_PROC (glUnmapBufferARB, PFNGLUNMAPBUFFERARBPROC, "glUnmapBufferARB");
		if (glGenBuffersARB && glBindBufferARB && glBufferDataARB && glDeleteBuffersARB/* && glMapBufferARB && glUnmapBufferARB*/)
		{
			gl_config.vbo = true;
			Com_Printf("...using GL_ARB_vertex_buffer_object\n" );
		}
		else
		{
			gl_config.vbo = false;
			Com_Printf("^1X..failed to import GL_ARB_vertex_buffer_object functions\n" );
		}
	}
	else
	{
		gl_config.vbo = false;
		Com_Printf ("^3X..ignoring GL_ARB_vertex_buffer_object\n");
	}

	if ( strstr( gl_config.extensions_string, "GL_NV_multisample_filter_hint" ) )
	{
		Com_Printf("...using GL_NV_multisample_filter_hint\n");
		gl_config.nv_multisample_hint = true;
	}
	else
	{
		Com_Printf("^1X..GL_NV_multisample_filter_hint not found\n");
		gl_config.nv_multisample_hint = false;		
	}

/*	SAFE_GET_PROC( glDepthBoundsEXT, PFNGLDEPTHBOUNDSEXTPROC, "glDepthBoundsEXT" );
	if(glDepthBoundsEXT)
		Com_Printf("...using GL_EXT_depth_bounds_test\n" );
	else
		Com_Printf("^1X..GL_EXT_depth_bounds_test not found\n" );
*/

	if ( strstr( gl_config.extensions_string, "GL_EXT_bgra") )
		Com_Printf("...using GL_EXT_bgra\n" );
	else
	{
		Com_Printf("^1X..GL_EXT_bgra not found\n" );
		return false;
	}

	SAFE_GET_PROC( glMultiTexCoord1fARB, PFNGLMULTITEXCOORD1FARBPROC, "glMultiTexCoord1fARB" );
	SAFE_GET_PROC( glMultiTexCoord2fARB, PFNGLMULTITEXCOORD2FARBPROC, "glMultiTexCoord2fARB" );
	SAFE_GET_PROC( glMultiTexCoord3fvARB, PFNGLMULTITEXCOORD3FVARBPROC, "glMultiTexCoord3fvARB" );
	SAFE_GET_PROC( glActiveTextureARB, PFNGLACTIVETEXTUREARBPROC, "glActiveTextureARB" );
	SAFE_GET_PROC( glClientActiveTextureARB, PFNGLCLIENTACTIVETEXTUREARBPROC, "glClientActiveTextureARB" );
	if(!glMultiTexCoord1fARB || !glMultiTexCoord2fARB || !glMultiTexCoord3fvARB || !glActiveTextureARB || !glClientActiveTextureARB)
	{
		Com_Printf("^1X..failed to import GL_ARB_multitexture functions\n" );
		return false;
	}

	glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &gl_config.tmus);
	Com_Printf("...using GL_ARB_multitexture [%d TMUs]\n", gl_config.tmus );

	if (r_simple->value)
		return true;

	if (gl_config.tmus < 4)
	{
		Com_Printf("^1This program requires at least 4 texture units\n");
		return false;
	}

	if ( strstr( gl_config.extensions_string, "GL_ARB_texture_cube_map") )
		Com_Printf("...using GL_ARB_texture_cube_map\n");
	else
	{
		Com_Printf("^1X..GL_ARB_texture_cube_map not found\n" );
		return false;
	}

	SAFE_GET_PROC( glTexImage3DEXT, PFNGLTEXIMAGE3DEXTPROC, "glTexImage3DEXT" );
	if (glTexImage3DEXT)
		Com_Printf("...using GL_EXT_texture3D\n");
	else
	{
		Com_Printf("^1X..GL_EXT_texture3D not found\n" );
		return false;
	}

	/// Berserker: перенёс из ChechGenericExtensions сюда, т.к. используется в рисовании консоли.
	/// Я не думаю что сегодня найдется видеокарта без поддержки DOT3  :)
	if(strstr(gl_config.extensions_string, "GL_ARB_texture_env_dot3") || strstr(gl_config.extensions_string, "GL_EXT_texture_env_dot3"))
		Com_Printf("...using GL_EXT_texture_env_dot3\n" );
	else
	{
		Com_Printf("^1X..GL_EXT_texture_env_dot3 not found\n" );
		return false;
	}

	return true;
}


bool GL_CheckGenericExtensions()
{
	Com_Printf("\nChecking for generic extensions:\n" );

	SAFE_GET_PROC( glBlendColor, PFNGLBLENDCOLORPROC, "glBlendColor");
	if (glBlendColor)
		Com_Printf("...using GL_ARB_imaging\n");
	else
	{
		Com_Printf("^1X..GL_ARB_imaging not found\n" );
		return false;
	}

	if ( strstr( gl_config.extensions_string, "GL_ARB_texture_env_combine") )
		Com_Printf("...using GL_ARB_texture_env_combine\n");
	else
	{
		Com_Printf("^1X..GL_ARB_texture_env_combine not found\n" );
		return false;
	}

	return true;
}


bool GL_CheckNvidiaExtensions()
{
	Com_Printf("\nChecking for nVidia extensions:\n" );

	if ( strstr( gl_config.extensions_string, "GL_NV_texture_shader") )
	{
		Com_Printf("...using GL_NV_texture_shader\n");
		gl_config.texshaders = true;
	}
	else
	{
		Com_Printf("^1X..GL_NV_texture_shader not found\n");
		gl_config.texshaders = false;
	}

	SAFE_GET_PROC( glBlendColor, PFNGLBLENDCOLORPROC, "glBlendColor");
	if (glBlendColor)
		Com_Printf("...using GL_ARB_imaging\n");
	else
	{
		Com_Printf("^1X..GL_ARB_imaging not found\n" );
		return false;
	}

	SAFE_GET_PROC( glCombinerParameteriNV, PFNGLCOMBINERPARAMETERINVPROC, "glCombinerParameteriNV" );
	SAFE_GET_PROC( glCombinerInputNV, PFNGLCOMBINERINPUTNVPROC, "glCombinerInputNV" );
	SAFE_GET_PROC( glCombinerOutputNV, PFNGLCOMBINEROUTPUTNVPROC, "glCombinerOutputNV" );
	SAFE_GET_PROC( glFinalCombinerInputNV, PFNGLFINALCOMBINERINPUTNVPROC, "glFinalCombinerInputNV" );
	SAFE_GET_PROC( glCombinerParameterfvNV, PFNGLCOMBINERPARAMETERFVNVPROC, "glCombinerParameterfvNV" );
	if(glCombinerParameteriNV && glCombinerInputNV && glCombinerOutputNV && glFinalCombinerInputNV && glCombinerParameterfvNV)
		Com_Printf("...using GL_NV_register_combiners\n");
	else
	{
		Com_Printf("^1X..failed to import GL_NV_register_combiners functions\n" );
		return false;
	}

	return true;
}


bool GL_CheckATIExtensions()
{
	Com_Printf("\nChecking for ATI extensions:\n" );

	if(gl_config.tmus < 4)
	{
		Com_Printf("^1 ATI shader requires at least 4 TMUs, skipped!\n");
		return false;
	}

	if(strstr(gl_config.extensions_string, "GL_ATI_fragment_shader"))
		Com_Printf("...using GL_ATI_fragment_shader\n" );
	else
	{
		Com_Printf("^1X..GL_ATI_fragment_shader not found\n" );
		return false;
	}

///	if(strstr(gl_config.extensions_string, "GL_EXT_vertex_shader"))
///		Com_Printf("GL_EXT_vertex_shader Ok\n");
///	else
///	{
///		Com_Printf("^1GL_EXT_vertex_shader failed\n" );
///		return false;
///	}

	SAFE_GET_PROC( glGenFragmentShadersATI, PFNGLGENFRAGMENTSHADERSATIPROC, "glGenFragmentShadersATI" );
	SAFE_GET_PROC( glDeleteFragmentShaderATI, PFNGLDELETEFRAGMENTSHADERATIPROC, "glDeleteFragmentShaderATI" );
	SAFE_GET_PROC( glBindFragmentShaderATI, PFNGLBINDFRAGMENTSHADERATIPROC, "glBindFragmentShaderATI" );
	SAFE_GET_PROC( glBeginFragmentShaderATI, PFNGLBEGINFRAGMENTSHADERATIPROC, "glBeginFragmentShaderATI" );
	SAFE_GET_PROC( glSetFragmentShaderConstantATI, PFNGLSETFRAGMENTSHADERCONSTANTATIPROC, "glSetFragmentShaderConstantATI" );
	SAFE_GET_PROC( glSampleMapATI, PFNGLSAMPLEMAPATIPROC, "glSampleMapATI" );
	SAFE_GET_PROC( glColorFragmentOp2ATI, PFNGLCOLORFRAGMENTOP2ATIPROC, "glColorFragmentOp2ATI" );
	SAFE_GET_PROC( glAlphaFragmentOp1ATI, PFNGLALPHAFRAGMENTOP1ATIPROC, "glAlphaFragmentOp1ATI" );
	SAFE_GET_PROC( glAlphaFragmentOp2ATI, PFNGLALPHAFRAGMENTOP2ATIPROC, "glAlphaFragmentOp2ATI" );
	SAFE_GET_PROC( glAlphaFragmentOp3ATI, PFNGLALPHAFRAGMENTOP3ATIPROC, "glAlphaFragmentOp3ATI" );
	SAFE_GET_PROC( glEndFragmentShaderATI, PFNGLENDFRAGMENTSHADERATIPROC, "glEndFragmentShaderATI" );
///	SAFE_GET_PROC( glGenVertexShadersEXT, PFNGLGENVERTEXSHADERSEXTPROC, "glGenVertexShadersEXT" );
///	SAFE_GET_PROC( glBeginVertexShaderEXT, PFNGLBEGINVERTEXSHADEREXTPROC, "glBeginVertexShaderEXT" );
///	SAFE_GET_PROC( glEndVertexShaderEXT, PFNGLENDVERTEXSHADEREXTPROC, "glEndVertexShaderEXT" );
///	SAFE_GET_PROC( glBindVertexShaderEXT, PFNGLBINDVERTEXSHADEREXTPROC, "glBindVertexShaderEXT" );
///	SAFE_GET_PROC( glBindParameterEXT, PFNGLBINDPARAMETEREXTPROC, "glBindParameterEXT" );
///	SAFE_GET_PROC( glBindTextureUnitParameterEXT, PFNGLBINDTEXTUREUNITPARAMETEREXTPROC, "glBindTextureUnitParameterEXT" );
///	SAFE_GET_PROC( glGenSymbolsEXT, PFNGLGENSYMBOLSEXTPROC, "glGenSymbolsEXT" );
///	SAFE_GET_PROC( glShaderOp1EXT, PFNGLSHADEROP1EXTPROC, "glShaderOp1EXT" );
///	SAFE_GET_PROC( glShaderOp2EXT, PFNGLSHADEROP2EXTPROC, "glShaderOp2EXT" );
///	SAFE_GET_PROC( glExtractComponentEXT, PFNGLEXTRACTCOMPONENTEXTPROC, "glExtractComponentEXT" );

	if( !glGenFragmentShadersATI ||
		!glDeleteFragmentShaderATI ||
		!glBindFragmentShaderATI ||
		!glBeginFragmentShaderATI ||
		!glSetFragmentShaderConstantATI ||
		!glSampleMapATI ||
		!glColorFragmentOp2ATI ||
		!glAlphaFragmentOp1ATI ||
		!glAlphaFragmentOp2ATI ||
		!glAlphaFragmentOp3ATI ||
		!glEndFragmentShaderATI/* ||
		!glGenVertexShadersEXT ||
		!glBeginVertexShaderEXT ||
		!glEndVertexShaderEXT ||
		!glBindVertexShaderEXT ||
		!glBindParameterEXT ||
		!glBindTextureUnitParameterEXT ||
		!glGenSymbolsEXT ||
		!glShaderOp1EXT ||
		!glShaderOp2EXT ||
		!glExtractComponentEXT*/)
	{
		Com_Printf("^1X..failed to import ATI-shader functions\n" );
		return false;
	}

	return true;
}


bool GL_CheckARBExtensions()
{
	int		length;
	char	name[MAX_QPATH];
	char	*buffer[4];

	Com_Printf("\nChecking for ARBfp1.0 extensions:\n" );

	if(strstr(gl_config.extensions_string, "GL_ARB_fragment_program"))
		Com_Printf("...using GL_ARB_fragment_program\n" );
	else
	{
		Com_Printf("^1X..GL_ARB_fragment_program not found\n" );
		return false;
	}

	glGetIntegerv(GL_MAX_TEXTURE_COORDS_ARB, &gl_config.maxTextureCoords);
	glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &gl_config.maxTextureImageUnits);

	Com_Printf("...GL_MAX_TEXTURE_COORDS_ARB [%i]\n", gl_config.maxTextureCoords);
	Com_Printf("...GL_MAX_TEXTURE_IMAGE_UNITS_ARB [%i]\n", gl_config.maxTextureImageUnits);

	if(gl_config.maxTextureCoords < 4 || gl_config.maxTextureImageUnits < 4)
	{
		Com_Printf("^1 ARB shader requires at least 4 TextureCoords and 4 ImageUnits, skipped!\n");
		return false;
	}

	SAFE_GET_PROC(glProgramStringARB,PFNGLPROGRAMSTRINGARBPROC,"glProgramStringARB");
	SAFE_GET_PROC(glBindProgramARB,PFNGLBINDPROGRAMARBPROC,"glBindProgramARB");
	SAFE_GET_PROC(glDeleteProgramsARB,PFNGLDELETEPROGRAMSARBPROC,"glDeleteProgramsARB");
	SAFE_GET_PROC(glGenProgramsARB,PFNGLGENPROGRAMSARBPROC,"glGenProgramsARB");
	SAFE_GET_PROC(glProgramEnvParameter4dARB,PFNGLPROGRAMENVPARAMETER4DARBPROC,"glProgramEnvParameter4dARB");
	SAFE_GET_PROC(glProgramEnvParameter4dvARB,PFNGLPROGRAMENVPARAMETER4DVARBPROC,"glProgramEnvParameter4dvARB");
	SAFE_GET_PROC(glProgramEnvParameter4fARB,PFNGLPROGRAMENVPARAMETER4FARBPROC,"glProgramEnvParameter4fARB");
	SAFE_GET_PROC(glProgramEnvParameter4fvARB,PFNGLPROGRAMENVPARAMETER4FVARBPROC,"glProgramEnvParameter4fvARB");
	SAFE_GET_PROC(glProgramLocalParameter4dARB,PFNGLPROGRAMLOCALPARAMETER4DARBPROC,"glProgramLocalParameter4dARB");
	SAFE_GET_PROC(glProgramLocalParameter4dvARB,PFNGLPROGRAMLOCALPARAMETER4DVARBPROC,"glProgramLocalParameter4dvARB");
	SAFE_GET_PROC(glProgramLocalParameter4fARB,PFNGLPROGRAMLOCALPARAMETER4FARBPROC,"glProgramLocalParameter4fARB");
	SAFE_GET_PROC(glProgramLocalParameter4fvARB,PFNGLPROGRAMLOCALPARAMETER4FVARBPROC,"glProgramLocalParameter4fvARB");
	SAFE_GET_PROC(glGetProgramEnvParameterdvARB,PFNGLGETPROGRAMENVPARAMETERDVARBPROC,"glGetProgramEnvParameterdvARB");
	SAFE_GET_PROC(glGetProgramEnvParameterfvARB,PFNGLGETPROGRAMENVPARAMETERFVARBPROC,"glGetProgramEnvParameterfvARB");
	SAFE_GET_PROC(glGetProgramLocalParameterdvARB,PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC,"glGetProgramLocalParameterdvARB");
	SAFE_GET_PROC(glGetProgramLocalParameterfvARB,PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC,"glGetProgramLocalParameterfvARB");
	SAFE_GET_PROC(glGetProgramivARB,PFNGLGETPROGRAMIVARBPROC,"glGetProgramivARB");
	SAFE_GET_PROC(glGetProgramStringARB,PFNGLGETPROGRAMSTRINGARBPROC,"glGetProgramStringARB");
	SAFE_GET_PROC(glIsProgramARB,PFNGLISPROGRAMARBPROC,"glIsProgramARB");

	if( !glProgramStringARB ||
		!glBindProgramARB ||
		!glDeleteProgramsARB ||
		!glGenProgramsARB ||
		!glProgramEnvParameter4dARB ||
		!glProgramEnvParameter4dvARB ||
		!glProgramEnvParameter4fARB ||
		!glProgramEnvParameter4fvARB ||
		!glProgramLocalParameter4dARB ||
		!glProgramLocalParameter4dvARB ||
		!glProgramLocalParameter4fARB ||
		!glProgramLocalParameter4fvARB ||
		!glGetProgramEnvParameterdvARB ||
		!glGetProgramEnvParameterfvARB ||
		!glGetProgramLocalParameterdvARB ||
		!glGetProgramLocalParameterfvARB ||
		!glGetProgramivARB ||
		!glGetProgramStringARB ||
		!glIsProgramARB )
	{
		Com_Printf("^1X..failed to import ARB-shader functions\n" );
		return false;
	}

	Com_sprintf (name, sizeof(name), "glprogs/nolightfilter.fp");
	length = FS_LoadFile (name, (void **)&buffer[0]);
	if (!buffer[0] || (length <= 0))
	{
		if(buffer[0])
			Z_Free (buffer[0]);
		Com_Printf ("...using internal \"nolightfilter.fp\"\n");
		fragmentprogram_nofilter_ptr = fragmentprogram_nofilter;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		fragmentprogram_nofilter_ptr = buffer[0];

		if (strncmp(fragmentprogram_nofilter_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (fragmentprogram_nofilter_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			fragmentprogram_nofilter_ptr = fragmentprogram_nofilter;
		}
	}

	Com_sprintf (name, sizeof(name), "glprogs/nolightfilter6_detail.fp");
	length = FS_LoadFile (name, (void **)&buffer[0]);
	if (!buffer[0] || (length <= 0))
	{
		if(buffer[0])
			Z_Free (buffer[0]);
		Com_Printf ("...using internal \"nolightfilter6_detail.fp\"\n");
		fragmentprogram_nofilter6_detail_ptr = fragmentprogram_nofilter6_detail;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		fragmentprogram_nofilter6_detail_ptr = buffer[0];

		if (strncmp(fragmentprogram_nofilter6_detail_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (fragmentprogram_nofilter6_detail_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			fragmentprogram_nofilter6_detail_ptr = fragmentprogram_nofilter6_detail;
		}
	}

	Com_sprintf (name, sizeof(name), "glprogs/lightfilter6_detail.fp");
	length = FS_LoadFile (name, (void **)&buffer[1]);
	if (!buffer[1] || (length <= 0))
	{
		if(buffer[1])
			Z_Free (buffer[1]);
		Com_Printf ("...using internal \"lightfilter6_detail.fp\"\n");
		fragmentprogram_filter6_detail_ptr = fragmentprogram_filter6_detail;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		fragmentprogram_filter6_detail_ptr = buffer[1];

		if (strncmp(fragmentprogram_filter6_detail_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (fragmentprogram_filter6_detail_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			fragmentprogram_filter6_detail_ptr = fragmentprogram_filter6_detail;
		}
	}

	Com_sprintf (name, sizeof(name), "glprogs/lightfilter6.fp");
	length = FS_LoadFile (name, (void **)&buffer[1]);
	if (!buffer[1] || (length <= 0))
	{
		if(buffer[1])
			Z_Free (buffer[1]);
		Com_Printf ("...using internal \"lightfilter6.fp\"\n");
		fragmentprogram_filter_ptr = fragmentprogram_filter;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		fragmentprogram_filter_ptr = buffer[1];

		if (strncmp(fragmentprogram_filter_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (fragmentprogram_filter_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			fragmentprogram_filter_ptr = fragmentprogram_filter;
		}
	}

	Com_sprintf (name, sizeof(name), "glprogs/lightfilter4.fp");
	length = FS_LoadFile (name, (void **)&buffer[2]);
	if (!buffer[2] || (length <= 0))
	{
		if(buffer[2])
			Z_Free (buffer[2]);
		Com_Printf ("...using internal \"lightfilter4.fp\"\n");
		fragmentprogram_filter4_ptr = fragmentprogram_filter4;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		fragmentprogram_filter4_ptr = buffer[2];

		if (strncmp(fragmentprogram_filter4_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (fragmentprogram_filter_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			fragmentprogram_filter4_ptr = fragmentprogram_filter4;
		}
	}

	Com_sprintf (name, sizeof(name), "glprogs/distort.fp");
	length = FS_LoadFile (name, (void **)&buffer[3]);
	if (!buffer[3] || (length <= 0))
	{
		if(buffer[3])
			Z_Free (buffer[3]);
		Com_Printf ("...using internal \"distort.fp\"\n");
		arbdistort_ptr = arbdistort;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		arbdistort_ptr = buffer[3];

		if (strncmp(arbdistort_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (arbdistort_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			arbdistort_ptr = arbdistort;
		}
	}
	gl_config.arb_distort = true;

	Com_sprintf (name, sizeof(name), "glprogs/ambient.fp");
	length = FS_LoadFile (name, (void **)&buffer[3]);
	if (!buffer[3] || (length <= 0))
	{
		if(buffer[3])
			Z_Free (buffer[3]);
		Com_Printf ("...using internal \"ambient.fp\"\n");
		fragmentprogram_ambient_ptr = fragmentprogram_ambient;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		fragmentprogram_ambient_ptr = buffer[3];

		if (strncmp(fragmentprogram_ambient_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (fragmentprogram_ambient_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			fragmentprogram_ambient_ptr = fragmentprogram_ambient;
		}
	}

	Com_sprintf (name, sizeof(name), "glprogs/lightfilter6_parallax.fp");
	length = FS_LoadFile (name, (void **)&buffer[1]);
	if (!buffer[1] || (length <= 0))
	{
		if(buffer[1])
			Z_Free (buffer[1]);
		Com_Printf ("...using internal \"lightfilter6_parallax.fp\"\n");
		fragmentprogram_filter_parallax_ptr = fragmentprogram_filter_parallax;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		fragmentprogram_filter_parallax_ptr = buffer[1];

		if (strncmp(fragmentprogram_filter_parallax_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (fragmentprogram_filter_parallax_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			fragmentprogram_filter_parallax_ptr = fragmentprogram_filter_parallax;
		}
	}

	Com_sprintf (name, sizeof(name), "glprogs/nolightfilter_parallax.fp");
	length = FS_LoadFile (name, (void **)&buffer[1]);
	if (!buffer[1] || (length <= 0))
	{
		if(buffer[1])
			Z_Free (buffer[1]);
		Com_Printf ("...using internal \"nolightfilter_parallax.fp\"\n");
		fragmentprogram_nofilter_parallax_ptr = fragmentprogram_nofilter_parallax;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		fragmentprogram_nofilter_parallax_ptr = buffer[1];

		if (strncmp(fragmentprogram_nofilter_parallax_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (fragmentprogram_nofilter_parallax_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			fragmentprogram_nofilter_parallax_ptr = fragmentprogram_nofilter_parallax;
		}
	}

	Com_sprintf (name, sizeof(name), "glprogs/ambient_parallax.fp");
	length = FS_LoadFile (name, (void **)&buffer[1]);
	if (!buffer[1] || (length <= 0))
	{
		if(buffer[1])
			Z_Free (buffer[1]);
		Com_Printf ("...using internal \"ambient_parallax.fp\"\n");
		fragmentprogram_ambient_parallax_ptr = fragmentprogram_ambient_parallax;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		fragmentprogram_ambient_parallax_ptr = buffer[1];

		if (strncmp(fragmentprogram_ambient_parallax_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (fragmentprogram_ambient_parallax_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			fragmentprogram_ambient_parallax_ptr = fragmentprogram_ambient_parallax;
		}
	}

	Com_sprintf (name, sizeof(name), "glprogs/nobump_parallax.fp");
	length = FS_LoadFile (name, (void **)&buffer[1]);
	if (!buffer[1] || (length <= 0))
	{
		if(buffer[1])
			Z_Free (buffer[1]);
		Com_Printf ("...using internal \"nobump_parallax.fp\"\n");
		fragmentprogram_nobump_parallax_ptr = fragmentprogram_nobump_parallax;
	}
	else
	{
		Com_Printf ("...loading \"%s\"\n", name);
		fragmentprogram_nobump_parallax_ptr = buffer[1];

		if (strncmp(fragmentprogram_nobump_parallax_ptr,"!!ARBfp1.0",10))
		{
			Z_Free (fragmentprogram_nobump_parallax_ptr);
			Com_Printf("^1Corrupted! Using internal program.\n" );
			fragmentprogram_nobump_parallax_ptr = fragmentprogram_nobump_parallax;
		}
	}

	return true;		// Мы всегда уверены в качестве встроенных программ...
}


bool GL_CheckEXTStencilWrap()
{
	Com_Printf("\nChecking for SeparateStencil:\n" );

	if(strstr(gl_config.extensions_string, "GL_EXT_stencil_wrap"))
	{
		Com_Printf("...using GL_EXT_stencil_wrap\n");
		return true;
	}
	else
	{
		Com_Printf("^1X..GL_EXT_stencil_wrap not found\n" );
		return false;
	}
}


bool GL_CheckNVStencilTwoSide()
{
	if(strstr(gl_config.extensions_string, "GL_NV_stencil_two_side"))
	{
		Com_Printf("...using GL_NV_stencil_two_side\n");

		SAFE_GET_PROC( glActiveStencilFaceEXT, PFNGLACTIVESTENCILFACEEXTPROC, "glActiveStencilFaceEXT" );

		if (!glActiveStencilFaceEXT)
		{
			Com_Printf("^1X..failed to import GL_NV_stencil_two_side function\n" );
			return false;
		}
		return true;
	}
	else
	{
		Com_Printf("^1X..GL_NV_stencil_two_side not found\n" );
		return false;
	}
}


bool GL_CheckEXTStencilTwoSide()
{
	if(strstr(gl_config.extensions_string, "GL_EXT_stencil_two_side"))
	{
		Com_Printf("...using GL_EXT_stencil_two_side\n");

		SAFE_GET_PROC( glActiveStencilFaceEXT, PFNGLACTIVESTENCILFACEEXTPROC, "glActiveStencilFaceEXT" );

		if (!glActiveStencilFaceEXT)
		{
			Com_Printf("^1X..failed to import GL_EXT_stencil_two_side function\n" );
			return false;
		}
		return true;
	}
	else
	{
		Com_Printf("^1X..GL_EXT_stencil_two_side not found\n" );
		return false;
	}
}


bool GL_CheckATISeparateStencil()
{
	if(strstr(gl_config.extensions_string, "GL_ATI_separate_stencil"))
	{
		Com_Printf("...using GL_ATI_separate_stencil\n");

		SAFE_GET_PROC( glStencilOpSeparateATI, PFNGLSTENCILOPSEPARATEATIPROC, "glStencilOpSeparateATI" );
		SAFE_GET_PROC( glStencilFuncSeparateATI, PFNGLSTENCILFUNCSEPARATEATIPROC, "glStencilFuncSeparateATI" );

		if (!glStencilOpSeparateATI || !glStencilFuncSeparateATI)
		{
			Com_Printf("^1X..failed to import GL_ATI_separate_stencil functions\n" );
			return false;
		}
		return true;
	}
	else
	{
		Com_Printf("^1X..GL_ATI_separate_stencil not found\n" );
		return false;
	}
}


bool GL_SelectShader()
{
	if(GL_CheckCommonExtensions())
	{
		GL_SetDefaultState();
		GL_LoadVertexPrograms();
		Com_Printf("Passed\n" );

		bool ext_gen = GL_CheckGenericExtensions();
		if(ext_gen)
			Com_Printf("Passed\n" );
		else
			Com_Printf("^1Failed\n" );

		bool ext_nv = GL_CheckNvidiaExtensions();
		if(ext_nv)
			Com_Printf("Passed\n" );
		else
			Com_Printf("^1Failed\n" );

		bool ext_ati = GL_CheckATIExtensions();
		if(ext_ati)
			Com_Printf("Passed\n" );
		else
			Com_Printf("^1Failed\n" );

		bool ext_arb = GL_CheckARBExtensions();
		if(ext_arb)
			Com_Printf("Passed\n" );
		else
			Com_Printf("^1Failed\n" );

		if(GL_CheckEXTStencilWrap())
		{
			gl_config.nv_stencil_two_side = GL_CheckNVStencilTwoSide() || GL_CheckEXTStencilTwoSide();
			gl_config.ati_separate_stencil = GL_CheckATISeparateStencil();

			if (gl_config.ati_separate_stencil)
				Com_Printf("Selected ATI SeparateStencil\n");
			else if (gl_config.nv_stencil_two_side)
				Com_Printf("Selected nVidia SeparateStencil\n");
			else
				Com_Printf("^1Failed SeparateStencil\n");
		}

		/// Berserker: исправлена ошибка, когда хотим simple-рендер и имеем возможность его запустить, но невозможность запуска всех полноценных рендеров запрещает запуск программы.
		if (r_simple->value)
			return true;

		if(r_shader->value >= MAX_SHADERS || r_shader->value < 0)
		{
			Com_Printf("\n^1r_shader = %i is incorrect and will be fixed!\n", (int)r_shader->value);
			goto forcechange;
		}

		/// Если желание не соответствует возможностям:
again:	if( ((r_shader->value == SHADER_GENERIC4 || r_shader->value == SHADER_GENERIC6) && !ext_gen) ||
			(r_shader->value == SHADER_GENERIC6 && gl_config.tmus < 6) ||
			(r_shader->value == SHADER_NVIDIA4 && !ext_nv) ||
			((r_shader->value == SHADER_ATI4 || r_shader->value == SHADER_ATI6) && !ext_ati) ||
			((r_shader->value == SHADER_ARB4 || r_shader->value == SHADER_ARB6) && !ext_arb) )
		{
			Com_Printf("\n^1\"%s\" not available for your videosystem!\n", shaders[(int)r_shader->value]);

forcechange:if (ext_arb)
			{
				if (gl_config.tmus/*maxTextureCoords*/ < 6)		/// Будем реалистами: если есть аппаратно только 4 TMU, не будет юзать ARB6 в режиме эмуляции
					Cvar_SetValue( "r_shader", SHADER_ARB4 );
				else
					Cvar_SetValue( "r_shader", SHADER_ARB6 );
			}
			else
			{
				if (ext_ati)
				{
					if (gl_config.tmus/*maxTextureCoords*/ < 6)		/// Будем реалистами: если есть аппаратно только 4 TMU, не будет юзать ATI6 в режиме эмуляции
						Cvar_SetValue( "r_shader", SHADER_ATI4 );
					else
						Cvar_SetValue( "r_shader", SHADER_ATI6 );
				}
				else
				{
					if (ext_nv)
						Cvar_SetValue( "r_shader", SHADER_NVIDIA4 );
					else
					{
						if (ext_gen)
						{
							if (gl_config.tmus < 6)
								Cvar_SetValue( "r_shader", SHADER_GENERIC4 );
							else
								Cvar_SetValue( "r_shader", SHADER_GENERIC6 );
						}
						else
							goto failed;
					}
				}
			}
			vid_restart = true;		/// форсируем рестарт видеосистемы (а когда будет видеорестарт, управление вернется сюда с валидным r_shader)
			return true;
		}

		/// Сюда попадаем, если r_shader соответствует возможностям видеосистемы
		switch ((int)r_shader->value)
		{
		case SHADER_GENERIC4:
			Com_Printf("\nSelected Generic4 path\n");
			return true;
		case SHADER_GENERIC6:
			Com_Printf("\nSelected Generic6 path\n");
			return true;
		case SHADER_NVIDIA4:
			GL_CreateShaderNVidia();
			Com_Printf("\nSelected nVidia4 path\n");
			return true;
		case SHADER_ATI4:
			GL_CreateShadersRadeon4();
			Com_Printf("\nSelected ATI4 path\n");
			return true;
		case SHADER_ATI6:
			GL_CreateShadersRadeon6();
			Com_Printf("\nSelected ATI6 path\n");
			return true;
		case SHADER_ARB4:
		case SHADER_ARB6:
			if (GL_CreateShadersARB((int)r_shader->value))
			{
				if(r_shader->value == SHADER_ARB4)
					Com_Printf("\nSelected ARB4 path\n");
				else
					Com_Printf("\nSelected ARB6 path\n");
				return true;
			}
			else
			{
				ext_arb = false;	/// недоступен ни ARB, ни Generic рендеры (редчайший случай, imho даже невозможный), заблокируем ARB рендер
				goto again;			/// и выберем другой наиболее подходящий рендер
			}
		}
	}

failed:
	Com_Printf("^1Failed\n" );		/// Ничего не доступно, выйдем нах...
	return false;
}


void R_LoadScreenImages()
{
	char	name[MAX_QPATH];

	glfx_mask = GL_FindImage ("pics/effects/Mask.tga", it_fx, true, 0, false, 0);
	if (!glfx_mask)
		Com_Printf("^1Can't find pic: pics/effects/Mask.tga\n");

	glfx_maskenv = GL_FindImage ("pics/effects/MaskEnv.tga", it_fx, true, 0, false, 0);
	if (!glfx_maskenv)
		Com_Printf("^1Can't find pic: pics/effects/MaskEnv.tga\n");

	glfx_drown = GL_FindImage ("pics/effects/drown.tga", it_fx, true, 0, false, 0);
	if (!glfx_drown)
		Com_Printf("^1Can't find pic: pics/effects/drown.tga\n");

	glfx_underwater = GL_FindImage ("pics/effects/underwater.tga", it_fx, true, 0, false, 0);
	if (!glfx_underwater)
		Com_Printf("^1Can't find pic: pics/effects/underwater.tga\n");

	for (num_pains=0; num_pains<MAX_RFX_IMAGES; num_pains++)
		glfx_pain[num_pains] = glfx_burn[num_pains] = NULL;

	for (num_pains=0; num_pains<MAX_RFX_IMAGES; num_pains++)
	{
		Com_sprintf (name, sizeof(name), "pics/effects/pain_%i.tga", num_pains);
		glfx_pain[num_pains] = GL_FindImage (name, it_fx, true, 0, false, 0);
		if (!glfx_pain[num_pains])
			break;
	}
	if (!num_pains)
		Com_Printf("^1Can't find any 'pain' pic\n");

	for (num_burns=0; num_burns<MAX_RFX_IMAGES; num_burns++)
	{
		Com_sprintf (name, sizeof(name), "pics/effects/burn_%i.tga", num_burns);
		glfx_burn[num_burns] = GL_FindImage (name, it_fx, true, 0, false, 0);
		if (!glfx_burn[num_burns])
			break;
	}
	if (!num_burns)
		Com_Printf("^1Can't find any 'burn' pic\n");
}


int R_Init( void *hinstance, void *hWnd )
{
	char renderer_buffer[1000];
	char vendor_buffer[1000];
	int	err;

	Init_Palette ();

	R_Register();

	// initialize OS-specific parts of OpenGL
	if ( !GLimp_Init( hinstance, hWnd ) )
		return -1;

	// set our "safe" mode
	gl_state.prev_mode = Vid_GetSafeMode(0);

	// create the window and set up the context
	if ( !R_SetMode () )
	{
		glw_state.hinstOpenGL = NULL;
		Com_Printf("^1R_Init(): could not R_SetMode()\n" );
		return -1;
	}

	// get our various GL strings
	gl_config.vendor_string = (char*)glGetString (GL_VENDOR);
	Com_Printf("GL_VENDOR: %s\n", gl_config.vendor_string );
	gl_config.renderer_string = (char*)glGetString (GL_RENDERER);
	Com_Printf("GL_RENDERER: %s\n", gl_config.renderer_string );
	gl_config.version_string = (char*)glGetString (GL_VERSION);
	Com_Printf("GL_VERSION: %s\n", gl_config.version_string );
	gl_config.extensions_string = (char*)glGetString (GL_EXTENSIONS);

	// Before VID_MenuInit!
	if ( strstr( gl_config.extensions_string, "GL_EXT_texture_filter_anisotropic") )
	{
		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl_config.max_anisotropy);
		gl_config.anisotropy_level = gl_config.max_anisotropy;
		gl_config.anisotropic = true;
	}
	else
		gl_config.anisotropic = false;

	VID_MenuInit(false);

	char *string = (char*)gl_config.extensions_string;
//	Com_Printf("GL_EXTENSIONS: %s\n", gl_config.extensions_string );	// From old engine...
	Com_Printf("\nGL_EXTENSIONS:\n--------------\n");
	char c, line[128];
	int i;
	while (1)
	{
		i = 0;
		line[i] = 0;
		while (1)
		{
			c = *string++;
			if ((c == ' ') || (c == 0))
				break;
			line[i] = c;
			i++;
		}
		if(!c)
			break;
		line[i] = 0;
		Com_Printf("%s\n", line);
	}
	Com_Printf("\n");

	strcpy( renderer_buffer, gl_config.renderer_string );
	strlwr( renderer_buffer );

	strcpy( vendor_buffer, gl_config.vendor_string );
	strlwr( vendor_buffer );

repeat:
	if(!GL_SelectShader())
	{
		Com_Printf("^1No shaders available!\n");
		if (!r_simple->value)
		{
			Cvar_ForceSetValue("r_simple", 1);
			Com_Printf("^3r_simple forced to 1\n");
			goto repeat;
		}
		else
			return -1;
	}

	if(gl_config.anisotropic && r_anisotropy->value)
		Com_Printf("...using anisotropy: %i\n", (int)r_anisotropy->value);
	else
		Com_Printf("^3X..ignoring anisotropy\n");

	if(r_diffuse_compression->value<0 || r_diffuse_compression->value>4)
		Cvar_ForceSetValue("r_diffuse_compression", 0);

	if(gl_config.compression && r_diffuse_compression->value)
		Com_Printf("...using %s diffuse compression\n", compr_types[(int)r_diffuse_compression->value]);
	else
		Com_Printf("^3X..ignoring diffuse compression\n");

	if(r_bump_compression->value<0 || r_bump_compression->value>4)
		Cvar_ForceSetValue("r_bump_compression", 0);

	if(gl_config.compression && r_bump_compression->value)
		Com_Printf("...using %s bump compression\n", compr_types[(int)r_bump_compression->value]);
	else
		Com_Printf("^3X..ignoring bump compression\n");

	R_VCInit();
	GL_InitImages ();
	Mod_Init ();
	R_InitParticleTexture ();
	R_InitDefaultTextures ();
	R_InitNormalizationCubemap ();
	R_InitAttenuation ();
	R_InitDistortTextures ();
	R_InitMirrorChains();	//Load mirror dummys
	R_LoadDecal(&bullet_mrk_object, "textures/decals/bullet_mrk.jpg", file_jpg);
	R_LoadDecal(&laser_mrk_object, "textures/decals/laser_mrk.jpg", file_jpg);
	// blood decals
	R_LoadDecal(&blood_mrk_object[0], "textures/decals/splat0.jpg", file_jpg);
	R_LoadDecal(&blood_mrk_object[1], "textures/decals/splat1.jpg", file_jpg);
	R_LoadDecal(&blood_mrk_object[2], "textures/decals/splat2.jpg", file_jpg);
	R_LoadDecal(&blood_mrk_object[3], "textures/decals/splat3.jpg", file_jpg);
	R_LoadDecal(&blood_mrk_object[4], "textures/decals/splat4.jpg", file_jpg);
	R_LoadDecal(&blood_mrk_object[5], "textures/decals/splat5.jpg", file_jpg);
	R_LoadDecal(&blood_mrk_object[6], "textures/decals/splat6.jpg", file_jpg);
	R_LoadDecal(&blood_mrk_object[7], "textures/decals/splat7.jpg", file_jpg);
	// green blood decals
	R_LoadDecal(&greenblood_mrk_object[0], "textures/decals/gsplat0.jpg", file_jpg);
	R_LoadDecal(&greenblood_mrk_object[1], "textures/decals/gsplat1.jpg", file_jpg);
	R_LoadDecal(&greenblood_mrk_object[2], "textures/decals/gsplat2.jpg", file_jpg);
	R_LoadDecal(&greenblood_mrk_object[3], "textures/decals/gsplat3.jpg", file_jpg);
	R_LoadDecal(&greenblood_mrk_object[4], "textures/decals/gsplat4.jpg", file_jpg);
	R_LoadDecal(&greenblood_mrk_object[5], "textures/decals/gsplat5.jpg", file_jpg);
	R_LoadDecal(&greenblood_mrk_object[6], "textures/decals/gsplat6.jpg", file_jpg);
	R_LoadDecal(&greenblood_mrk_object[7], "textures/decals/gsplat7.jpg", file_jpg);

	R_LoadDecal(&foot_mrk_object[0], "textures/decals/footprintl.tga", file_tga);
	R_LoadDecal(&foot_mrk_object[1], "textures/decals/footprintr.tga", file_tga);
	R_LoadDecal(&foot_mrk_object[2], "textures/decals/footprints.tga", file_tga);
	R_LoadDecal(&foot_mrk_object[3], "textures/decals/footprint_rot.tga", file_tga);
	R_LoadDecal(&foot_mrk_object[4], "textures/decals/footprints_rot.tga", file_tga);
	R_LoadDecal(&explosion_mrk_object, "textures/decals/explosion.jpg", file_jpg);
	R_LoadDecal(&bfg_explosion_mrk_object, "textures/decals/bfg_explosion.jpg", file_jpg);
	R_LoadDecal(&rail_mrk_object[0], "textures/decals/rail_mrk_rand.jpg", file_jpg);
	R_LoadDecal(&rail_mrk_object[1], "textures/decals/rail_mrk_b.jpg", file_jpg);
	R_LoadDecal(&rail_mrk_object[2], "textures/decals/rail_mrk_g.jpg", file_jpg);
	R_LoadDecal(&rail_mrk_object[3], "textures/decals/rail_mrk_gb.jpg", file_jpg);
	R_LoadDecal(&rail_mrk_object[4], "textures/decals/rail_mrk_r.jpg", file_jpg);
	R_LoadDecal(&rail_mrk_object[5], "textures/decals/rail_mrk_rb.jpg", file_jpg);
	R_LoadDecal(&rail_mrk_object[6], "textures/decals/rail_mrk_rg.jpg", file_jpg);
	R_LoadDecal(&rail_mrk_object[7], "textures/decals/rail_mrk_rgb.jpg", file_jpg);
	for(i=0; i<MAX_GLOBAL_FILTERS; i++)
		R_LoadDecal(&decal_texture_object[i], va("textures/decals/%i.jpg", i), file_jpg);	// JPG only

	for(i=0; i<MAX_CAUSTICS; i++)
	{
		char name[MAX_QPATH];
		Com_sprintf (name, sizeof(name), "textures/caustics/caust%02i", i);
		r_caustic[i] = GL_FindImage(name, it_fx, false, 0, false, 0);
		if(!r_caustic[i])
			r_caustic[i] = r_notexture;
	}

	sun_object = GL_FindImage("textures/flares/sun.jpg", it_pic, true, 0, false, 0);
	if(!sun_object)
		sun_object = r_notexture;

	sun1_object = GL_FindImage("textures/flares/sun1.jpg", it_pic, true, 0, false, 0);
	if(!sun1_object)
		sun1_object = r_notexture;

	sun2_object = GL_FindImage("textures/flares/sun2.jpg", it_pic, true, 0, false, 0);
	if(!sun2_object)
		sun2_object = r_notexture;

	smoke_object = GL_FindImage("textures/particles/smoke.tga", it_pic, true, 0, false, 0);
	if(!smoke_object)
		smoke_object = r_particletexture;

	blood_object = GL_FindImage("textures/particles/blood.tga", it_pic, true, 0, false, 0);
	if(!blood_object)
		blood_object = r_particletexture;

	fly_object = GL_FindImage("textures/particles/fly.tga", it_pic, true, 0, false, 0);
	if(!fly_object)
		fly_object = r_particletexture;

	bubble_object = GL_FindImage("textures/particles/bubble.tga", it_pic, true, 0, false, 0);
	if(!bubble_object)
		bubble_object = r_particletexture;

	splat_object = GL_FindImage("textures/particles/splat.tga", it_pic, true, 0, false, 0);
	if(!splat_object)
		splat_object = r_particletexture;

	waterwake_object = GL_FindImage("textures/particles/waterwake.tga", it_pic, true, 0, false, 0);
	if(!waterwake_object)
		waterwake_object = r_particletexture;

	waterplume_object = GL_FindImage("textures/particles/waterplume.tga", it_pic, true, 0, false, 0);
	if(!waterplume_object)
		waterplume_object = r_particletexture;

	tracer_object = GL_FindImage("textures/particles/tracer.tga", it_pic, true, 0, false, 0);
	if(!tracer_object)
		tracer_object = r_particletexture;

	if (!r_simple->value)
		for(i=0; i<MAX_GLOBAL_FILTERS; i++)
			filtercube_texture_object[i] = R_LoadLightFilter (i, NULL);	// JPG only

	R_LoadScreenImages();

	if(!Draw_InitFonts ())
	{
		Com_Printf("^1X..Error loading font texture\n");
		return -1;
	}

	if (gl_config.occlusion)
	{
		Memset(occResults, -9999, sizeof(occResult_t)*MAX_ENTITIES);
		Memset(r_entities_occUSE, 0, sizeof(int)*MAX_ENTITIES);
		glGenQueriesARB(MAX_ENTITIES, r_entities_occID);
	}

	if (r_simple->value)
	{
		if (gl_config.tmus < 2)
		{
			Com_Printf("^1Simple render path requires at least 2 texture units\n");
			return -1;
		}
		Com_Printf("^3...FORCE to use simple render path\n");
	}

	err = glGetError();
	if ( err != GL_NO_ERROR )
	{
		const char *Msg="Unknown";
		switch( err )
		{
			case GL_INVALID_ENUM:
				Msg = "GL_INVALID_ENUM";
				break;
			case GL_INVALID_VALUE:
				Msg = "GL_INVALID_VALUE";
				break;
			case GL_INVALID_OPERATION:
				Msg = "GL_INVALID_OPERATION";
				break;
			case GL_STACK_OVERFLOW:
				Msg = "GL_STACK_OVERFLOW";
				break;
			case GL_STACK_UNDERFLOW:
				Msg = "GL_STACK_UNDERFLOW";
				break;
			case GL_OUT_OF_MEMORY:
				Msg = "GL_OUT_OF_MEMORY";
				break;
		};
		Com_Printf("^1OpenGL error [0x%x]: %s\n", err, Msg);
	}

	return 0;
}


void Mod_Free (model_t *mod)
{
	if (mod->type == mod_alias)
	{
		if (mod->vbo_st)
		{
			R_VCFree(mod->vbo_st);
			mod->vbo_st = NULL;
		}
		else if (mod->st)
			Z_Free(mod->st);
	}
	else if (mod->type == mod_alias_md3)
	{
		int				i;
		maliasmodel_t	*poutmodel = (maliasmodel_t *)mod->extradata;
		maliasmesh_t	*poutmesh = poutmodel->meshes;

		for ( i = 0; i < poutmodel->num_meshes; i++, poutmesh++)
		{
			if (poutmesh->vbo_st)
			{
				R_VCFree(poutmesh->vbo_st);
				poutmesh->vbo_st = NULL;
			}
		}
	}

	Hunk_Free (mod->extradata, mod->extradatasize);
	Memset (mod, 0, sizeof(*mod));
}


void Mod_FreeAll ()
{
	int		i;

	for (i=0 ; i<mod_numknown ; i++)
	{
		if (mod_known[i].extradata)
			Mod_Free (&mod_known[i]);
	}

	if (net_compatibility->value)
	{
		for (i=0 ; i<MAX_IMAGES_Q2 ; i++)
			hud_model_test[i] = false;
	}
	else
	{
		for (i=0 ; i<MAX_IMAGES_BERS ; i++)
			hud_model_test[i] = false;
	}
}


void R_Shutdown ()
{
	Cmd_RemoveCommand ("imagelist");
	Cmd_RemoveCommand ("modellist");
	Cmd_RemoveCommand ("materiallist");
	Cmd_RemoveCommand ("screenshot");
	Cmd_RemoveCommand ("envscreenshot");
	Cmd_RemoveCommand ("gl_strings");
	Cmd_RemoveCommand ("gl_progs");

	R_VCShutdown();
	Mod_FreeAll ();		// Kill all models
	R_ClearSLights();

	if (gl_config.occlusion)
	{
		glDeleteQueriesARB(MAX_ENTITIES, r_entities_occID);
		for (int i=0; i<MAX_MIRRORS; i++)
		{
			GLuint	occid;
			if (mirrors[i].occID < 0)
				occid = -mirrors[i].occID;
			else
				occid = mirrors[i].occID;
			glDeleteQueriesARB(1, &occid);
		}
	}

	GL_ShutdownMaterials ();
	GL_ShutdownImages ();

	/*
	** shut down OS specific OpenGL stuff like contexts, etc.
	*/
	GLimp_Shutdown();

	glw_state.hinstOpenGL = NULL;
}


void VID_Shutdown ()
{
	if ( reflib_active )
		R_Shutdown ();
}


void FreePakNames()
{
	int i;
	if ( paknames )
	{
		for ( i = 0; i < numpaks; i++ )
			Z_Free( paknames[i] );
		Z_Free( paknames );
	}
	paknames = 0;
	numpaks = 0;
}


void FreePakNames_()
{
	int i, j;

	for (j=0; j<2; j++)
		if(numpaks_[j])
		{
			if ( paknames_[j] )
			{
				for ( i = 0; i < numpaks_[j]; i++ )
					Z_Free( paknames_[j][i] );
				Z_Free( paknames_[j] );
			}
			paknames_[j] = 0;
			numpaks_[j] = 0;
		}
}


/*
===============
CL_Shutdown

FIXME: this is a callback from Sys_Quit and Com_Error.  It would be better
to run quit through here before the final handoff to the sys code.
===============
*/
void CL_Shutdown()
{
	static bool isdown = false;
	char	name[MAX_OSPATH];

	if (isdown)
	{
		printf ("recursive shutdown\n");
		return;
	}
	isdown = true;

	CL_WriteConfiguration (); 

	Music_Shutdown();
///	CDAudio_Shutdown();

	S_Shutdown();
	IN_Shutdown();
	VID_Shutdown();

	FreeMapNames();
	FreeMapNames2();
	FreePakNames();

	if(net_compatibility->value)
		Com_sprintf (name, sizeof(name), "%s/cache/temp.dm2", FS_Gamedir());		// Berserker: удаляем мусор
	else
		Com_sprintf (name, sizeof(name), "%s/cache/temp.dem", FS_Gamedir());		// Berserker: удаляем мусор
	remove(name);
}


void CloseDedicatedServer()
{
	if (net_compatibility->value && r1q2_title[0])
	{
		HWND wnd = FindWindow(NULL, r1q2_title);
///		if (!wnd)
///			wnd = FindWindow(NULL, r1q2_down_title);	// try to find r1q2_down_title
		if (wnd)
		{
///			Com_Printf("Shutdown dedicated server\n");		// Почему-то портит лог-файл.
			PostMessage(wnd, WM_CLOSE, NULL, NULL);
			Sleep(1000);
		}
	}
}


void Sys_Error (char *error, ...)
{
	va_list		argptr;
	char		text[1024];

	CloseDedicatedServer();

	if(!dedicated->value)
		RestoreGamma();
	CL_Shutdown ();

	va_start (argptr, error);
	vsprintf (text, error, argptr);
	va_end (argptr);

	KillZipCache(true);

	MessageBox(/*NULL*/GetActiveWindow(), text, "Error", MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);	// fix by Barnes
	exit (1);
}


void Sys_Quit ()
{
	timeEndPeriod( 1 );

	CloseDedicatedServer();

	CL_Shutdown();
	if (dedicated && dedicated->value)
		FreeConsole ();

	KillZipCache(true);

	exit (0);
}


/*
================
Sys_Milliseconds
================
*/
int	curtime;
int Sys_Milliseconds ()
{
	static int	base;
	static bool	initialized = false;

	if (!initialized)
	{	// let base retain 16 bits of effectively random data
		base = timeGetTime() & 0xffff0000;
		initialized = true;
	}
	curtime = timeGetTime() - base;

	return curtime;
}


/*
============
Cbuf_AddText

Adds command text at the end of the buffer
============
*/
void Cbuf_AddText (char *text)
{
	int		l;
	
	l = strlen (text);

	if (cmd_text.cursize + l >= cmd_text.maxsize)
	{
		Com_Printf ("^3Cbuf_AddText: overflow\n");
		return;
	}
	SZ_Write (&cmd_text, text, strlen (text));
}


void Con_ClearNotify ()
{
	int	i;
	
	for (i=0 ; i<NUM_CON_TIMES ; i++)
		con.times[i] = 0;
}

void Key_Get (int key)
{
	/// Berserker's fix: если выполнить команду GET не из игры, а просто из консоли, то имеем мелкий глючок-с...
	if (cls.state != ca_active)
	{
		cls.key_dest = key_console;
		chat_bufferlen = 0;
		chat_buffer[0] = 0;
	}

	if ( key == K_ENTER || key == K_KP_ENTER )
	{
		cvar_t *getreceived = Cvar_Get("get_received", "", 0);
		getreceived->help = "special cvar for a reception of data for GET command.";
		Cvar_Set("get_received", chat_buffer);

		cls.key_dest = key_game;
		chat_bufferlen = 0;
		chat_buffer[0] = 0;
		return;
	}

	if (key == K_ESCAPE)
	{
		cls.key_dest = key_game;
		chat_bufferlen = 0;
		chat_buffer[0] = 0;
		return;
	}

	if (key < 32 || key > 127)
		return;	// non printable

	if (key == K_BACKSPACE)
	{
		if (chat_bufferlen)
		{
			chat_bufferlen--;
			chat_buffer[chat_bufferlen] = 0;
		}
		return;
	}

	if (chat_bufferlen == sizeof(chat_buffer)-1)
		return; // all full

	chat_buffer[chat_bufferlen++] = key;
	chat_buffer[chat_bufferlen] = 0;
}

void Key_Message (int key)
{
	/// Berserker's fix: если выполнить команду MESSAGEMODE* не из игры, а просто из консоли, то имеем мелкий глючок-с...
	if (cls.state != ca_active)
	{
		cls.key_dest = key_console;
		chat_bufferlen = 0;
		chat_buffer[0] = 0;
	}

	if ( key == K_ENTER || key == K_KP_ENTER )
	{
		if (chat_team)
			Cbuf_AddText ("say_team \"");
		else
			Cbuf_AddText ("say \"");
		Cbuf_AddText(chat_buffer);
		Cbuf_AddText("\"\n");

		cls.key_dest = key_game;
		chat_bufferlen = 0;
		chat_buffer[0] = 0;
		return;
	}

	if (key == K_ESCAPE)
	{
		cls.key_dest = key_game;
		chat_bufferlen = 0;
		chat_buffer[0] = 0;
		return;
	}

	if (key < 32 || key > 127)
		return;	// non printable

	if (key == K_BACKSPACE)
	{
		if (chat_bufferlen)
		{
			chat_bufferlen--;
			chat_buffer[chat_bufferlen] = 0;
		}
		return;
	}

	if (chat_bufferlen == sizeof(chat_buffer)-1)
		return; // all full

	chat_buffer[chat_bufferlen++] = key;
	chat_buffer[chat_bufferlen] = 0;
}


char *Sys_GetClipboardData()
{
	char *data = NULL;
	char *cliptext;

	if ( OpenClipboard( NULL ) != 0 )
	{
		HANDLE hClipboardData;

		if ( ( hClipboardData = GetClipboardData( CF_TEXT ) ) != 0 )
		{
			if ( ( cliptext = (char*) GlobalLock( hClipboardData ) ) != 0 ) 
			{
				data = (char*) Z_Malloc( GlobalSize( hClipboardData ) + 1, true );
				strcpy( data, cliptext );
				GlobalUnlock( hClipboardData );
			}
		}
		CloseClipboard();
	}
	return data;
}


/*
================
Com_DPrintf

A Com_Printf that only shows up if the "developer" cvar is set
================
*/
void Com_DPrintf (char *fmt, ...)
{
	va_list		argptr;
	char		msg[MAXPRINTMSG];
		
	if (!developer || !developer->value)
		return;			// don't confuse non-developers with techie stuff...

	va_start (argptr,fmt);
	vsprintf (msg,fmt,argptr);
	va_end (argptr);
	
	Com_Printf ("^4%s", msg);
}

/*
void Con_Printf (int print_level, char *fmt, ...)
{
	va_list		argptr;
	char		msg[MAXPRINTMSG];

	va_start (argptr,fmt);
	vsprintf (msg,fmt,argptr);
	va_end (argptr);

	if (print_level == PRINT_ALL)
	{
		Com_Printf ("%s", msg);
	}
	else if ( print_level == PRINT_DEVELOPER )
	{
		Com_DPrintf ("%s", msg);
	}
	else if ( print_level == PRINT_ALERT )
	{
		MessageBox( 0, msg, "PRINT_ALERT", MB_ICONWARNING );
		OutputDebugString( msg );
	}
}
*/

void GL_TextureMode( char *string )
{
	int		i;
	image_t	*glt;

	for (i=0 ; i< NUM_GL_MODES ; i++)
	{
		if ( !Q_strcasecmp( modes[i].name, string ) )
			break;
	}

	if (i == NUM_GL_MODES)
	{
		Com_Printf("^1bad filter name\n");
		return;
	}

	gl_filter_min = modes[i].minimize;
	gl_filter_max = modes[i].maximize;

	// change all the existing mipmap texture objects
	for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
	{
		if (glt->type != it_pic && glt->type != it_sky && glt->type != it_sprite)
		{
			GL_Bind2D (glt->texnum);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
			if (glt->type != it_light && glt->type != it_fx)
				GL_SetAnisotropy();
		}
	}
}


void GL_TextureAlphaMode( char *string )
{
	int		i;

	for (i=0 ; i< NUM_GL_ALPHA_MODES ; i++)
	{
		if ( !Q_strcasecmp( gl_alpha_modes[i].name, string ) )
			break;
	}

	if (i == NUM_GL_ALPHA_MODES)
	{
		Com_Printf("^1bad alpha texture mode name\n");
		return;
	}

	gl_tex_alpha_format = gl_alpha_modes[i].mode;
}


void GL_TextureSolidMode( char *string )
{
	int		i;

	for (i=0 ; i< NUM_GL_SOLID_MODES ; i++)
	{
		if ( !Q_strcasecmp( gl_solid_modes[i].name, string ) )
			break;
	}

	if (i == NUM_GL_SOLID_MODES)
	{
		Com_Printf("^1bad solid texture mode name\n");
		return;
	}

	gl_tex_solid_format = gl_solid_modes[i].mode;
}


void GL_UpdateSwapInterval ()
{
	if ( r_swapinterval->modified )
	{
		r_swapinterval->modified = false;
		if ( wglSwapIntervalEXT )
		{
			if (r_swapinterval->value == 2)
				wglSwapIntervalEXT( -1 );
			else
				wglSwapIntervalEXT( r_swapinterval->value );
		}
	}
}


void R_Clear ()
{
	int mask;
	if (r_worldmodel && r_viewcluster == -1)	/// Berserker: рисуем мир вне карты
		mask = GL_COLOR_BUFFER_BIT;
	else
		mask = 0;
	glClear (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | mask);
	glDepthFunc (GL_LEQUAL);
	glDepthRange (0, 1);
}


void BeginFrame()
{
///	if ( r_bitdepth->modified )
///	{
///		if ( r_bitdepth->value != 0 && !glw_state.allowdisplaydepthchange )
///		{
///			Cvar_SetValue( "r_bitdepth", 0 );
///			Com_Printf("r_bitdepth requires Win95 OSR2.x or WinNT 4.x\n" );
///		}
///		r_bitdepth->modified = false;
///	}

	if ( vid_hz->modified )
	{
		if ( vid_hz->value != 0 && !glw_state.allowdisplaydepthchange )
		{
			Cvar_SetValue( "vid_hz", 0 );
			Com_Printf("^3vid_hz requires Win95 OSR2.x, WinNT 4.x or better\n" );
		}
		vid_hz->modified = false;
	}

	glDrawBuffer( GL_BACK );

	/*
	** go into 2D mode
	*/
	glViewport (0,0, vid.width, vid.height);
	glMatrixMode(GL_PROJECTION);
    glLoadIdentity ();
	glOrtho  (0, vid.width, vid.height, 0, -99999, 99999);
	glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
	glDisable (GL_DEPTH_TEST);
	glDisable (GL_CULL_FACE);
	glDisable (GL_BLEND);
	glEnable (GL_ALPHA_TEST);

	/*
	** draw buffer stuff
	*/
	if ( r_drawbuffer->modified )
	{
		r_drawbuffer->modified = false;

		if ( Q_strcasecmp( r_drawbuffer->string, "GL_FRONT" ) == 0 )
			glDrawBuffer( GL_FRONT );
		else
			glDrawBuffer( GL_BACK );
	}

	/*
	** texturemode stuff
	*/
	if ( r_texturemode->modified )
	{
		GL_TextureMode( r_texturemode->string );
		r_texturemode->modified = false;
	}

	if ( r_texturealphamode->modified )
	{
		GL_TextureAlphaMode( r_texturealphamode->string );
		r_texturealphamode->modified = false;
	}

	if ( r_texturesolidmode->modified )
	{
		GL_TextureSolidMode( r_texturesolidmode->string );
		r_texturesolidmode->modified = false;
	}

	if (gl_config.nv_multisample_hint)
	{
		if (r_nvMultisampleFilterHint->modified)
		{
			r_nvMultisampleFilterHint->modified = false;
			if (r_nvMultisampleFilterHint->value)
			 	glHint (GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST);
			else
				glHint (GL_MULTISAMPLE_FILTER_HINT_NV, GL_FASTEST);
		}
	}	

	/*
	** swapinterval stuff
	*/
	GL_UpdateSwapInterval();

	//
	// clear screen if desired
	//
	R_Clear ();
	GL_ClearCache(GL_TEX | GL_TMU | GL_TEXENV	| GL_BLEND_	| GL_COLOR_ | GL_VP | GL_FP);
	gl_TexEnv( GL_REPLACE );
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	GL_Color3f (1,1,1);

	if (gl_config.vbo)
	{
		gl_state.vbo_id = 0;
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
	}
}


/*
=================
SCR_CalcVrect

Sets scr_vrect, the coordinates of the rendered window
=================
*/
void SCR_CalcVrect ()
{
	int		size;

	if (r_mirrors->value || (r_bloom->value>0 && r_bloom->value<=3 && r_fullscreen->value))
		Cvar_Set ("viewsize","100");
	else
	{
		// bound viewsize
		if (scr_viewsize->value < 40)
			Cvar_Set ("viewsize","40");
		if (scr_viewsize->value > 100)
			Cvar_Set ("viewsize","100");
	}

	size = scr_viewsize->value;

	scr_vrect.width = viddef.width*size/100;
	scr_vrect.width &= ~7;

	scr_vrect.height = viddef.height*size/100;
	scr_vrect.height &= ~1;

	scr_vrect.x = (viddef.width - scr_vrect.width)/2;
	scr_vrect.y = (viddef.height - scr_vrect.height)/2;
}


void SCR_AddDirtyPoint (int x, int y)
{
	if (x < scr_dirty.x1)
		scr_dirty.x1 = x;
	if (x > scr_dirty.x2)
		scr_dirty.x2 = x;
	if (y < scr_dirty.y1)
		scr_dirty.y1 = y;
	if (y > scr_dirty.y2)
		scr_dirty.y2 = y;
}


void SCR_DirtyScreen ()
{
	SCR_AddDirtyPoint (0, 0);
	SCR_AddDirtyPoint (viddef.width-1, viddef.height-1);
}


int FS_filelength (FILE *f)
{
	int		pos;
	int		end;

	pos = ftell (f);
	fseek (f, 0, SEEK_END);
	end = ftell (f);
	fseek (f, pos, SEEK_SET);

	return end;
}



/*
============
FS_CreatePath

Creates any directories needed to store the given filename
============
*/
void	FS_CreatePath (char *path)
{
	char	*ofs, bak;
	for (ofs = path+1 ; *ofs ; ofs++)
	{
		if (*ofs == '/' || *ofs == '\\')		// berserker: damn path names
		{	// create the directory
			bak = *ofs;
			*ofs = 0;
			_mkdir (path);
			*ofs = bak;
		}
	}
}


void KillZipCache(bool all)
{
	int i;
	for(i=0; i<MAX_PAKS; i++)
	{
		if(ZipCache[i].uf)
		{
			if (!all)
			{
				if (strstr(ZipCache[i].pak_name, BASEDIRNAME))
					continue;
			}
			PackFileClose (&ZipCache[i]);
		}
		else
			return;
	}
}


bool RepairPath(char *filename)
{
	int i = 0;
	int last = 0;
	int laster = 0;

	while (1)
	{
		if(filename[i]==0)
			return false;

		if(filename[i]=='/' || filename[i]=='\\')
		{
			laster = last;
			last = i;
		}

		if(filename[i]=='.' && filename[i+1]=='.')
		{
			if (laster == 0)
				return false;	// Ситуация типа '../monsters/skin.pcx', то есть две точки встретились в самом начале

			if (filename[i+2] != '/' && filename[i+2] != '\\')
				return false;	// Ситуация, когда не встретилась строка  ../

			int in, out;
			in = i + 3;
			out = laster+1;

			while (1)
			{
				filename[out] = filename[in];

				if (filename[in]==0)
					return true;

				in++;
				out++;
			}
		}

		i++;
	}
}


/*
===========
FS_FOpenFile

Finds the file in the search path.
returns filesize and an open FILE *
Used for streaming data out of either a pak file or
a seperate file.
===========
*/
int FS_FOpenFile (char *filename, FILE **file, bool test, int zip_start)
{
	char			netpath[MAX_OSPATH];
	pack_t			*pak;
///	filelink_t		*link;
	int				size;
	int				i;
	int				slot;
	searchpath_t	*search = NULL;
	file_from_pak = file_from_pk2 = 0;
	zipdata = NULL;

	while (RepairPath(filename));	/// Berserker: устранение безобразия типа в модели: "models\monsters\tank\tris.md2" - там есть строки типа "models/monsters/tank/../ctank/skin.pcx"
									/// FIXME: может убрать эту проверку и просто исправить ручками подобную строку на "models/monsters/ctank/skin.pcx"
									/// Признак ошибки: сообщения при загрузке "Refusing to download a path with .."

	unsigned hash_filename = Com_HashKey(filename);
	if (zip_start != -1)
	{
		i = zip_start;
		goto zip_ch;
	}

	last_zip_number = -1;

/*	// check for links first
	for (link = fs_links ; link ; link=link->next)
	{
		if (!strncmp (filename, link->from, link->fromlength))
		{
			Com_sprintf (netpath, sizeof(netpath), "%s%s",link->to, filename+link->fromlength);
			*file = FS_Fopen (netpath, "rb");
			if (*file)
			{
				if(!test)
					Com_DPrintf ("link file: %s\n",netpath);
				return FS_filelength (*file);
			}
			return -1;
		}
	}*/

//
// search through the path, one element at a time
//
	for (search = fs_searchpaths ; search ; search = search->next)
	{
		// is the element a pak file?
		if (search->pack)
		{
			if (search->disabled)
				continue;
			// look through all the pak file elements
			pak = search->pack;
			if (pak->isPK2)
			{
				unsigned	hash = Com_HashKey(pak->filename);
				slot = -1;
				for(i=0; i<MAX_PAKS; i++)
				{
					if(ZipCache[i].uf)
					{
						if (ZipCache[i].hash == hash)
						{
							if(!b_stricmp(ZipCache[i].pak_name, pak->filename))
							{
zip_ch:							Memcpy(&pf, &ZipCache[i], sizeof(zipfile_t));		// Нашли уже открытый ZIP и загрузили его описатель в pf
								last_zip_number = i;
								goto clc;
							}
						}
					}
					else
					{
						if(slot == -1)
						{
							slot = i;	// Ищем в slot первый свободный номер в кэше
							break;
						}
					}
				}
				strcpy(&ZipCache[slot].pak_name[0], pak->filename);	// Если не нашли zip в кэше, откроем его...
				if (!PackFileOpen (&ZipCache[slot]))
					Com_Error(ERR_FATAL, "Error opening pk2-file: %s", pak->filename);
				Memcpy(&pf, &ZipCache[slot], sizeof(zipfile_t));
				last_zip_number = slot;

clc:			if(test)
				{
					size = PackFileSize (&pf, filename, hash_filename);
					if(size<0)
					{
						if (search != NULL)
							continue;
						Com_Error(ERR_FATAL, "%s: size < 0", filename);
					}
				}
				else
				{
					size = PackFileGet (&pf, filename, &zipdata, hash_filename);
					if(size<0)
					{
						if (search != NULL)
							continue;
						Com_Error(ERR_FATAL, "%s[%s] corrupted, PackFileGet() returned %i", pf.pak_name, filename, size);
					}
				}

				file_from_pk2 = 1;
				if(!test)
					Com_DPrintf ("Pk2File: %s/%s\n", pf.pak_name, filename);
				return size;
			}
			else
			{
				// look through all the pak file elements
				for (i=0 ; i<pak->numfiles ; i++)
					if (pak->files[i].hash == hash_filename)
						if (!b_stricmp (pak->files[i].name, filename))
						{	// found it!
							file_from_pak = 1;
							if(!test)
								Com_DPrintf ("PakFile: %s : %s\n", pak->filename, filename);
							// open a new file on the pakfile
							*file = FS_Fopen (pak->filename, "rb");
							if (!*file)
								Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->filename);
							fseek (*file, pak->files[i].filepos, SEEK_SET);
							return pak->files[i].filelen;
						}
			}
		}
		else
		{		
			// check a file in the directory tree
			Com_sprintf (netpath, sizeof(netpath), "%s/%s",search->filename, filename);
			*file = FS_Fopen (netpath, "rb");
			if (!*file)
				continue;
			if(!test)
				Com_DPrintf ("FindFile: %s\n",netpath);
			return FS_filelength (*file);
		}
	}

	if(!test)
		Com_DPrintf ("can't find %s\n", filename);
	*file = NULL;
	return -1;
}


/*
=================
FS_Read

Properly handles partial reads
=================
*/
#define	MAX_READ	0x10000		// read in blocks of 64k
void FS_Read (void *buffer, int len, FILE *f)
{
	int		block, remaining;
	int		read;
	byte	*buf;
	int		tries;

	buf = (byte *)buffer;

	// read in chunks for progress bar
	remaining = len;
	tries = 0;
	while (remaining)
	{
		block = remaining;
		if (block > MAX_READ)
			block = MAX_READ;
		read = fread (buf, 1, block, f);
		if (read == 0)
		{
			// we might have been trying to read from a CD
			if (!tries)
			{
				tries = 1;
				CDAudio_Stop();
			}
			else
				Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
		}

		if (read == -1)
			Com_Error (ERR_FATAL, "FS_Read: -1 bytes read");

		// do some progress bar thing here...

		remaining -= read;
		buf += read;
	}
}

void FS_ClearFileCache(char *name)
{
	unsigned i, hash = Com_HashKey(name);

	// look for it (в кэш-списке файлов)
	for (i=0 ; i<fs_cache_number ; i++)
		if (fs_cache[i].hash == hash)
			if (!b_stricmp(fs_cache[i].name, name))
			{
				if (i < fs_cache_number-1)
					Memcpy(&fs_cache[i], &fs_cache[i+1], sizeof(fs_cache_t) * (fs_cache_number-1-i));
				fs_cache_number--;
				return;
			}
}

/*
============
FS_LoadFile

Filename are reletive to the berserker search path
a null buffer will just return the file length without loading
============
*/
int FS_LoadFile (char *path, void **buffer)
{
	FILE	*h;
	byte	*buf;
	int		len, i;
	bool	cached = false;
	bool	ovr = (fs_cache_number >= MAX_FILES);
	bool	tst = ((int)buffer == NULL);

	if (ovr)
		Com_DPrintf("FS_LoadFile: MAX_FILES = %i overflow\n", MAX_FILES);

	buf = NULL;	// quiet compiler warning
	h = NULL;
	unsigned	hash = Com_HashKey(path);

	// look for it (в кэш-списке файлов)
	for (i=0 ; i<fs_cache_number ; i++)
		if (fs_cache[i].hash == hash)
			if (!b_stricmp(fs_cache[i].name, path))
			{	// Нашли в кэше!
				if (tst)
					return fs_cache[i].len;
				else
				{
					len = FS_FOpenFile (path, &h, false, fs_cache[i].pak);
					cached = true;
					goto m1;
				}
			}

// look for it in the filesystem or pack files
	len = FS_FOpenFile (path, &h, tst, -1);

m1:	if (!h && !file_from_pk2 && !file_from_pak)
	{
		if (buffer)
			*buffer = NULL;

		if (!cached && !ovr)
		{
			strcpy(fs_cache[fs_cache_number].name, path);
			fs_cache[fs_cache_number].hash = hash;
			fs_cache[fs_cache_number].len = -1;
			fs_cache[fs_cache_number++].pak = -1;
		}
		return -1;
	}

	if (!buffer)
	{
		if(h)
			fclose (h);
		if (!cached && !ovr)
		{
			strcpy(fs_cache[fs_cache_number].name, path);
			fs_cache[fs_cache_number].hash = hash;
			if (file_from_pk2)
				fs_cache[fs_cache_number].pak = last_zip_number;
			else
				fs_cache[fs_cache_number].pak = -1;
			fs_cache[fs_cache_number++].len = len;
		}
		return len;
	}

	if(!cached && !ovr)
	{
		strcpy(fs_cache[fs_cache_number].name, path);
		fs_cache[fs_cache_number].hash = hash;
		fs_cache[fs_cache_number].len = len;
	}
	buf = (byte*) Z_Malloc(len+1, true);
	*buffer = buf;
	if(file_from_pk2)
	{
		if(!cached && !ovr)
			fs_cache[fs_cache_number++].pak = last_zip_number;
		Memcpy(buf, zipdata, len);
		Z_Free(zipdata);
	}
	else
	{
		if(!cached && !ovr)
			fs_cache[fs_cache_number++].pak = -1;
		FS_Read (buf, len, h);
		fclose (h);
	}

	buf[len] = 0;		/// fixed bug: при загрузке строк - завершаем нулем
	return len;
}


/*
DDSDecodePixelFormat()
determines which pixel format the dds texture is in
*/
void DDSDecodePixelFormat(ddsBuffer_t *dds, ddsPF_t *pf)
{
	unsigned	fourCC;

	/* dummy check */
	if(dds == NULL || pf == NULL)
		return;

	/* extract fourCC */
	fourCC = dds->pixelFormat.fourCC;

	/* test it */
	if(fourCC == 0)
		*pf = DDS_PF_ARGB8888;
	else if(fourCC == *((unsigned*)"DXT1"))
		*pf = DDS_PF_DXT1;
	else if(fourCC == *((unsigned*)"DXT2"))
		*pf = DDS_PF_DXT2;
	else if(fourCC == *((unsigned*)"DXT3"))
		*pf = DDS_PF_DXT3;
	else if(fourCC == *((unsigned*)"DXT4"))
		*pf = DDS_PF_DXT4;
	else if(fourCC == *((unsigned*)"DXT5"))
		*pf = DDS_PF_DXT5;
	else
		*pf = DDS_PF_UNKNOWN;
}

/*
DDSGetInfo()
extracts relevant info from a dds texture, returns 0 on success
*/
int DDSGetInfo(ddsBuffer_t *dds, int *width, int *height, ddsPF_t *pf)
{
	/* dummy test */
	if(dds == NULL)
		return -1;

	/* test dds header */
	if(*((int *)dds->magic) != *((int *)"DDS "))
		return -1;
	if(LittleLong(dds->size) != 124)
		return -1;

	/* extract width and height */
	if(width)
		*width = LittleLong(dds->width);
	if(height)
		*height = LittleLong(dds->height);

	/* get pixel format */
	DDSDecodePixelFormat(dds, pf);

	/* return ok */
	return 0;
}


/*
DDSGetColorBlockColors()
extracts colors from a dds color block
*/
void DDSGetColorBlockColors(ddsColorBlock_t *block, ddsColor_t colors[4])
{
	unsigned short	word;

	/* color 0 */
	word = LittleShort(block->colors[0]);
	colors[0].a = 0xff;
	/* extract rgb bits */
	colors[0].b = (unsigned char)word;
	colors[0].b <<= 3;
	colors[0].b |= (colors[0].b >> 5);
	word >>= 5;
	colors[0].g = (unsigned char)word;
	colors[0].g <<= 2;
	colors[0].g |= (colors[0].g >> 5);
	word >>= 6;
	colors[0].r = (unsigned char)word;
	colors[0].r <<= 3;
	colors[0].r |= (colors[0].r >> 5);

	/* same for color 1 */
	word = LittleShort(block->colors[1]);
	colors[1].a = 0xff;
	/* extract rgb bits */
	colors[1].b = (unsigned char)word;
	colors[1].b <<= 3;
	colors[1].b |= (colors[1].b >> 5);
	word >>= 5;
	colors[1].g = (unsigned char)word;
	colors[1].g <<= 2;
	colors[1].g |= (colors[1].g >> 5);
	word >>= 6;
	colors[1].r = (unsigned char)word;
	colors[1].r <<= 3;
	colors[1].r |= (colors[1].r >> 5);

	/* use this for all but the super-freak math method */
	if(block->colors[0] > block->colors[1])
	{
		/* four-color block: derive the other two colors.    
		   00 = color 0, 01 = color 1, 10 = color 2, 11 = color 3
		   these two bit codes correspond to the 2-bit fields 
		   stored in the 64-bit block. */
		word = ((unsigned short)colors[0].r * 2 + (unsigned short)colors[1].r) / 3;
		/* no +1 for rounding */
		/* as bits have been shifted to 888 */
		colors[2].r = (unsigned char)word;
		word = ((unsigned short)colors[0].g * 2 + (unsigned short)colors[1].g) / 3;
		colors[2].g = (unsigned char)word;
		word = ((unsigned short)colors[0].b * 2 + (unsigned short)colors[1].b) / 3;
		colors[2].b = (unsigned char)word;
		colors[2].a = 0xff;

		word = ((unsigned short)colors[0].r + (unsigned short)colors[1].r * 2) / 3;
		colors[3].r = (unsigned char)word;
		word = ((unsigned short)colors[0].g + (unsigned short)colors[1].g * 2) / 3;
		colors[3].g = (unsigned char)word;
		word = ((unsigned short)colors[0].b + (unsigned short)colors[1].b * 2) / 3;
		colors[3].b = (unsigned char)word;
		colors[3].a = 0xff;
	}
	else
	{
		/* three-color block: derive the other color.
		   00 = color 0, 01 = color 1, 10 = color 2,  
		   11 = transparent.
		   These two bit codes correspond to the 2-bit fields 
		   stored in the 64-bit block */
		word = ((unsigned short)colors[0].r + (unsigned short)colors[1].r) / 2;
		colors[2].r = (unsigned char)word;
		word = ((unsigned short)colors[0].g + (unsigned short)colors[1].g) / 2;
		colors[2].g = (unsigned char)word;
		word = ((unsigned short)colors[0].b + (unsigned short)colors[1].b) / 2;
		colors[2].b = (unsigned char)word;
		colors[2].a = 0xff;

		/* random color to indicate alpha */
		colors[3].r = 0x00;
		colors[3].g = 0xff;
		colors[3].b = 0xff;
		colors[3].a = 0x00;
	}

	// Berserker: swap R<->B for all colors[4]  (to BGRA)
	byte u, i;
	for (i=0; i<4; i++)
	{
		u = colors[i].r;
		colors[i].r = colors[i].b;
		colors[i].b = u;
	}
}


/*
DDSDecodeColorBlock()
decodes a dds color block
fixme: make endian-safe
*/
void DDSDecodeColorBlock(unsigned *pixel, ddsColorBlock_t *block, int width, unsigned colors[4])
{
	int			r, n;
	unsigned	bits;
	unsigned	masks[] = { 3, 12, 3 << 4, 3 << 6 };	/* bit masks = 00000011, 00001100, 00110000, 11000000 */
	int			shift[] = { 0, 2, 4, 6 };

	/* r steps through lines in y */
	for(r = 0; r < 4; r++, pixel += (width - 4))	/* no width * 4 as unsigned int ptr inc will * 4 */
	{
		/* width * 4 bytes per pixel per line, each j dxtc row is 4 lines of pixels */

		/* n steps through pixels */
		for(n = 0; n < 4; n++)
		{
			bits = block->row[r] & masks[n];
			bits >>= shift[n];

			switch (bits)
			{
				case 0:
					*pixel = colors[0];
					pixel++;
					break;

				case 1:
					*pixel = colors[1];
					pixel++;
					break;

				case 2:
					*pixel = colors[2];
					pixel++;
					break;

				case 3:
					*pixel = colors[3];
					pixel++;
					break;

				default:
					/* invalid */
					pixel++;
					break;
			}
		}
	}
}


/*
DDSDecodeAlphaExplicit()
decodes a dds explicit alpha block
*/
void DDSDecodeAlphaExplicit(unsigned *pixel, ddsAlphaBlockExplicit_t *alphaBlock, int width, unsigned alphaZero)
{
	int				row, pix;
	unsigned short	word;
	ddsColor_t		color;

	/* clear color */
	color.r = 0;
	color.g = 0;
	color.b = 0;

	/* walk rows */
	for(row = 0; row < 4; row++, pixel += (width - 4))
	{
		word = LittleShort(alphaBlock->row[row]);

		/* walk pixels */
		for(pix = 0; pix < 4; pix++)
		{
			/* zero the alpha bits of image pixel */
			*pixel &= alphaZero;
			color.a = word & 0x000F;
			color.a = color.a | (color.a << 4);
			*pixel |= *((unsigned*)&color);
			word >>= 4;			/* move next bits to lowest 4 */
			pixel++;			/* move to next pixel in the row */
		}
	}
}


/*
DDSDecodeAlpha3BitLinear()
decodes interpolated alpha block
*/
void DDSDecodeAlpha3BitLinear(unsigned *pixel, ddsAlphaBlock3BitLinear_t *alphaBlock, int width, unsigned alphaZero)
{
	int				row, pix;
	unsigned		stuff;
	unsigned char	bits[4][4];
	unsigned short	alphas[8];
	ddsColor_t		aColors[4][4];

	/* get initial alphas */
	alphas[0] = alphaBlock->alpha0;
	alphas[1] = alphaBlock->alpha1;

	/* 8-alpha block */
	if(alphas[0] > alphas[1])
	{
		/* 000 = alpha_0, 001 = alpha_1, others are interpolated */
		alphas[2] = (6 * alphas[0] + alphas[1]) / 7;	/* bit code 010 */
		alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7;	/* bit code 011 */
		alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7;	/* bit code 100 */
		alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7;	/* bit code 101 */
		alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7;	/* bit code 110 */
		alphas[7] = (alphas[0] + 6 * alphas[1]) / 7;	/* bit code 111 */
	}

	/* 6-alpha block */
	else
	{
		/* 000 = alpha_0, 001 = alpha_1, others are interpolated */
		alphas[2] = (4 * alphas[0] + alphas[1]) / 5;	/* bit code 010 */
		alphas[3] = (3 * alphas[0] + 2 * alphas[1]) / 5;	/* bit code 011 */
		alphas[4] = (2 * alphas[0] + 3 * alphas[1]) / 5;	/* bit code 100 */
		alphas[5] = (alphas[0] + 4 * alphas[1]) / 5;	/* bit code 101 */
		alphas[6] = 0;			/* bit code 110 */
		alphas[7] = 255;		/* bit code 111 */
	}

	/* decode 3-bit fields into array of 16 bytes with same value */

	/* first two rows of 4 pixels each */
	stuff = *((unsigned*)&(alphaBlock->stuff[0]));

	bits[0][0] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[0][1] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[0][2] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[0][3] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[1][0] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[1][1] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[1][2] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[1][3] = (unsigned char)(stuff & 0x00000007);

	/* last two rows */
	stuff = *((unsigned*)&(alphaBlock->stuff[3]));	/* last 3 bytes */

	bits[2][0] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[2][1] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[2][2] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[2][3] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[3][0] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[3][1] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[3][2] = (unsigned char)(stuff & 0x00000007);
	stuff >>= 3;
	bits[3][3] = (unsigned char)(stuff & 0x00000007);

	/* decode the codes into alpha values */
	for(row = 0; row < 4; row++)
	{
		for(pix = 0; pix < 4; pix++)
		{
			aColors[row][pix].r = 0;
			aColors[row][pix].g = 0;
			aColors[row][pix].b = 0;
			aColors[row][pix].a = (unsigned char)alphas[bits[row][pix]];
		}
	}

	/* write out alpha values to the image bits */
	for(row = 0; row < 4; row++, pixel += width - 4)
	{
		for(pix = 0; pix < 4; pix++)
		{
			/* zero the alpha bits of image pixel */
			*pixel &= alphaZero;

			/* or the bits into the prev. nulled alpha */
			*pixel |= *((unsigned*)&(aColors[row][pix]));
			pixel++;
		}
	}
}


/*
DDSDecompressDXT1()
decompresses a dxt1 format texture
*/
int DDSDecompressDXT1(ddsBuffer_t *dds, int width, int height, unsigned char *pixels)
{
	int				x, y, xBlocks, yBlocks;
	unsigned		*pixel;
	ddsColorBlock_t	*block;
	ddsColor_t		colors[4];

	/* setup */
	xBlocks = width / 4;
	yBlocks = height / 4;

	/* walk y */
	for(y = 0; y < yBlocks; y++)
	{
		/* 8 bytes per block */
		block = (ddsColorBlock_t *) ((unsigned)dds->data + y * xBlocks * 8);

		/* walk x */
		for(x = 0; x < xBlocks; x++, block++)
		{
			DDSGetColorBlockColors(block, colors);
			pixel = (unsigned*)(pixels + x * 16 + (y * 4) * width * 4);
			DDSDecodeColorBlock(pixel, block, width, (unsigned*)colors);
		}
	}

	/* return ok */
	return 0;
}


/*
DDSDecompressDXT3()
decompresses a dxt3 format texture
*/
int DDSDecompressDXT3(ddsBuffer_t *dds, int width, int height, unsigned char *pixels)
{
	int				x, y, xBlocks, yBlocks;
	unsigned		*pixel, alphaZero;
	ddsColorBlock_t	*block;
	ddsAlphaBlockExplicit_t *alphaBlock;
	ddsColor_t		colors[4];

	/* setup */
	xBlocks = width / 4;
	yBlocks = height / 4;

	/* create zero alpha */
	colors[0].a = 0;
	colors[0].r = 0xFF;
	colors[0].g = 0xFF;
	colors[0].b = 0xFF;
	alphaZero = *((unsigned*)&colors[0]);

	/* walk y */
	for(y = 0; y < yBlocks; y++)
	{
		/* 8 bytes per block, 1 block for alpha, 1 block for color */
		block = (ddsColorBlock_t *) ((unsigned)dds->data + y * xBlocks * 16);

		/* walk x */
		for(x = 0; x < xBlocks; x++, block++)
		{
			/* get alpha block */
			alphaBlock = (ddsAlphaBlockExplicit_t *) block;

			/* get color block */
			block++;
			DDSGetColorBlockColors(block, colors);

			/* decode color block */
			pixel = (unsigned*)(pixels + x * 16 + (y * 4) * width * 4);
			DDSDecodeColorBlock(pixel, block, width, (unsigned*)colors);

			/* overwrite alpha bits with alpha block */
			DDSDecodeAlphaExplicit(pixel, alphaBlock, width, alphaZero);
		}
	}

	/* return ok */
	return 0;
}


/*
DDSDecompressDXT5()
decompresses a dxt5 format texture
*/
int DDSDecompressDXT5(ddsBuffer_t *dds, int width, int height, unsigned char *pixels)
{
	int				x, y, xBlocks, yBlocks;
	unsigned		*pixel, alphaZero;
	ddsColorBlock_t	*block;
	ddsAlphaBlock3BitLinear_t *alphaBlock;
	ddsColor_t		colors[4];

	/* setup */
	xBlocks = width / 4;
	yBlocks = height / 4;

	/* create zero alpha */
	colors[0].a = 0;
	colors[0].r = 0xFF;
	colors[0].g = 0xFF;
	colors[0].b = 0xFF;
	alphaZero = *((unsigned*)&colors[0]);

	/* walk y */
	for(y = 0; y < yBlocks; y++)
	{
		/* 8 bytes per block, 1 block for alpha, 1 block for color */
		block = (ddsColorBlock_t *) ((unsigned)dds->data + y * xBlocks * 16);

		/* walk x */
		for(x = 0; x < xBlocks; x++, block++)
		{
			/* get alpha block */
			alphaBlock = (ddsAlphaBlock3BitLinear_t *) block;

			/* get color block */
			block++;
			DDSGetColorBlockColors(block, colors);

			/* decode color block */
			pixel = (unsigned*)(pixels + x * 16 + (y * 4) * width * 4);
			DDSDecodeColorBlock(pixel, block, width, (unsigned*)colors);

			/* overwrite alpha bits with alpha block */
			DDSDecodeAlpha3BitLinear(pixel, alphaBlock, width, alphaZero);
		}
	}

	/* return ok */
	return 0;
}


/*
DDSDecompressDXT2()
decompresses a dxt2 format texture (fixme: un-premultiply alpha)
*/
int DDSDecompressDXT2(ddsBuffer_t *dds, int width, int height, unsigned char *pixels)
{
	/* decompress dxt3 */
	return DDSDecompressDXT3(dds, width, height, pixels);
}


/*
DDSDecompressDXT4()
decompresses a dxt4 format texture (fixme: un-premultiply alpha)
*/
int DDSDecompressDXT4(ddsBuffer_t *dds, int width, int height, unsigned char *pixels)
{
	/* decompress dxt5 */
	return DDSDecompressDXT5(dds, width, height, pixels);
}


/*
DDSDecompressARGB8888()
decompresses an argb 8888 format texture
*/
int DDSDecompressARGB8888(ddsBuffer_t *dds, int width, int height, unsigned char *pixels)
{
	int				x, y;
	unsigned char	*in, *out;
	ddsColor_t		color;

	/* setup */
	in = dds->data;
	out = pixels;

	/* walk y */
	for(y = 0; y < height; y++)
	{
		/* walk x */
		for(x = 0; x < width; x++)
		{
#if 0
			*out++ = *in++;		// copy as RGBA
			*out++ = *in++;
			*out++ = *in++;
			*out++ = *in++;
#else
			color.r = *in++;		// copy as BGRA
			color.g = *in++;
			color.b = *in++;
			color.a = *in++;
			*out++ = color.b;
			*out++ = color.g;
			*out++ = color.r;
			*out++ = color.a;
#endif
		}
	}

	/* return ok */
	return 0;
}


/*
DDSDecompress()
decompresses a dds texture into an rgba image buffer, returns 0 on success
*/
int DDSDecompress(ddsBuffer_t *dds, unsigned char *pixels)
{
	int		width, height, r;
	ddsPF_t	pf;

	/* get dds info */
	r = DDSGetInfo(dds, &width, &height, &pf);
	if(r)
		return r;

	/* decompress */
	switch (pf)
	{
		case DDS_PF_ARGB8888:
			/* fixme: support other [a]rgb formats */
			r = DDSDecompressARGB8888(dds, width, height, pixels);
			break;

		case DDS_PF_DXT1:
			r = DDSDecompressDXT1(dds, width, height, pixels);
			break;

		case DDS_PF_DXT2:
			r = DDSDecompressDXT2(dds, width, height, pixels);
			break;

		case DDS_PF_DXT3:
			r = DDSDecompressDXT3(dds, width, height, pixels);
			break;

		case DDS_PF_DXT4:
			r = DDSDecompressDXT4(dds, width, height, pixels);
			break;

		case DDS_PF_DXT5:
			r = DDSDecompressDXT5(dds, width, height, pixels);
			break;

		default:
		case DDS_PF_UNKNOWN:
			Memset(pixels, 0xFF, width * height * 4);
			r = -1;
			break;
	}

	/* return to sender */
	return r;
}

/*
=============
LoadDDS
loads a dxtc (1, 3, 5) dds buffer into a valid rgba image
=============
*/
void LoadDDS (char *name, byte **pic, int *width, int *height)
{
	int			w, h;
	ddsPF_t		pf;
	byte		*buffer;

	*pic = NULL;

	// load the file
	FS_LoadFile (name, (void **)&buffer);
	if (!buffer)
	{
		Com_DPrintf ("LoadDDS: Bad dds file %s\n", name);
		return;
	}

	// null out
	*pic = 0;
	if (width)
		*width = 0;
	if (height)
		*height = 0;

	// get dds info
	if(DDSGetInfo((ddsBuffer_t *) buffer, &w, &h, &pf))
	{
		Com_DPrintf("LoadDDS: Invalid dds file '%s'\n", name);
		return;
	}

	// only certain types of dds textures are supported
	if(pf != DDS_PF_ARGB8888 && pf != DDS_PF_DXT1 && pf != DDS_PF_DXT3 && pf != DDS_PF_DXT5)
	{
		Com_DPrintf("LoadDDS: Only dds texture formats ARGB8888, DXT1, DXT3, and DXT5 are supported (%d) '%s'\n", pf, name);
		return;
	}

	if (w > MAX_TEXTURE_SIZE || h > MAX_TEXTURE_SIZE)
	{
		Com_DPrintf( "LoadDDS: image too large %s\n", name);
		return;
	}

	// create image pixel buffer
	if (width)
		*width = w;
	if (height)
		*height = h;
	*pic = (byte*)Z_Malloc(w * h * 4, true);

	// decompress the dds texture
	DDSDecompress((ddsBuffer_t *) buffer, *pic);

	Z_Free(buffer);
}



static void jpg_noop( j_decompress_ptr cinfo )
{
}

static boolean jpg_fill_input_buffer( j_decompress_ptr cinfo )
{
	Com_DPrintf( "LoadJPG: Premature end of jpeg file\n" );
    return true;
}

static void jpg_skip_input_data( j_decompress_ptr cinfo, long num_bytes )
{
    cinfo->src->next_input_byte += (size_t) num_bytes;
    cinfo->src->bytes_in_buffer -= (size_t) num_bytes;
}

static void jpeg_mem_src( j_decompress_ptr cinfo, byte *mem, int len )
{
    cinfo->src = (struct jpeg_source_mgr *)
	(*cinfo->mem->alloc_small)((j_common_ptr) cinfo,
				   JPOOL_PERMANENT,
				   sizeof(struct jpeg_source_mgr));
    cinfo->src->init_source = jpg_noop;
    cinfo->src->fill_input_buffer = jpg_fill_input_buffer;
    cinfo->src->skip_input_data = jpg_skip_input_data;
    cinfo->src->resync_to_restart = jpeg_resync_to_restart;
    cinfo->src->term_source = jpg_noop;
    cinfo->src->bytes_in_buffer = len;
    cinfo->src->next_input_byte = mem;
}

void LoadJPG (char *name, byte **pic, int *width, int *height)
{
	int		i, length, samples;
	byte	*img, *scan, *buffer, *line;
	struct	jpeg_error_mgr jerr;
	struct	jpeg_decompress_struct cinfo;

	*pic = NULL;

	// load the file
	length = FS_LoadFile (name, (void **)&buffer);
	if (!buffer || length <= 0)
	{
		if (buffer)
			Z_Free(buffer);
		Com_DPrintf ("LoadJPG: %s doesn't exist\n", name);
		return;
	}

	// idea by Knightmare: check for bad data
	if (strncmp((char*)buffer+6, "JFIF", 4))
	{
		Z_Free(buffer);
		Com_DPrintf ("LoadJPG: %s was malformed\n", name);
		return;
	}

	cinfo.err = jpeg_std_error( &jerr );
	jpeg_create_decompress( &cinfo );
	jpeg_mem_src( &cinfo, buffer, length );
	jpeg_read_header( &cinfo, true );
	jpeg_start_decompress( &cinfo );
	samples = cinfo.output_components;

	if( samples != 3 && samples != 1 )
	{
		Com_DPrintf( "LoadJPG: unsupported format %s\n", name );
		jpeg_destroy_decompress( &cinfo );
		Z_Free( buffer );
		return;
	}

	if (cinfo.output_width > MAX_TEXTURE_SIZE || cinfo.output_height > MAX_TEXTURE_SIZE)
	{
		Com_DPrintf( "LoadJPG: image too large %s\n", name);
		jpeg_destroy_decompress( &cinfo );
		Z_Free( buffer );
		return;
	}

	if (width)
		*width = cinfo.output_width;
	if (height)
		*height = cinfo.output_height;
	img = *pic = (byte*)Z_Malloc(cinfo.output_width * cinfo.output_height * 3, true);
	line = (byte*)Z_Malloc(cinfo.output_width * samples, true);

	while (cinfo.output_scanline < cinfo.output_height)
	{
		int	wb = cinfo.output_width*3;
		scan = line;
		if( !jpeg_read_scanlines( &cinfo, &scan, 1 ) )
		{
			Com_DPrintf( "LoadJPG: error reading %s\n", name );
			jpeg_destroy_decompress( &cinfo );
			Z_Free( line );
			Z_Free( img );
			*pic = NULL;
			Z_Free( buffer );
			return;
		}
		if( samples == 1 )
		{
			for( i = 0; i < cinfo.output_width; i++, img += 3 )
				img[0] = img[1] = img[2] = *scan++;
		}
		else
		{
///			for( i = 0; i < cinfo.output_width; i++, img += 3, scan += 3 )
///				img[0] = scan[0], img[1] = scan[1], img[2] = scan[2];
			// Berserker: speed up
			Memcpy(img, scan, wb);
			img += wb;
		}
	}

	jpeg_finish_decompress( &cinfo );
	jpeg_destroy_decompress( &cinfo );

	Z_Free(line);
	Z_Free (buffer);
}


void __cdecl PngReadFunc(png_struct *Png, png_bytep buf, png_size_t size)
{
	TPngFileBuffer *PngFileBuffer=(TPngFileBuffer*)png_get_io_ptr(Png);
	Memcpy(buf,PngFileBuffer->Buffer+PngFileBuffer->Pos,size);
	PngFileBuffer->Pos+=size;
}


void LoadPNG (char *name, byte **pic, int *width, int *height, int *bits)
{
	unsigned		i, rowbytes;
	png_structp		png_ptr;
	png_infop		info_ptr;
	png_infop		end_info;
	png_bytep		row_pointers[MAX_TEXTURE_SIZE];
	double			file_gamma;

	TPngFileBuffer	PngFileBuffer = {NULL,0};

	*pic = NULL;

	FS_LoadFile (name, (void **)&PngFileBuffer.Buffer);

    if (!PngFileBuffer.Buffer)
	{
		Com_DPrintf ("LoadPNG: Bad PNG file %s\n", name);
		return;
	}

	if ((png_check_sig(PngFileBuffer.Buffer, 8)) == 0)
	{
		Z_Free (PngFileBuffer.Buffer); 
		Com_DPrintf ("LoadPNG: Not a PNG file %s\n", name);
		return;
    }

	PngFileBuffer.Pos=0;

    png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL,  NULL, NULL);

    if (!png_ptr)
	{
		Z_Free (PngFileBuffer.Buffer);
		Com_DPrintf ("LoadPNG: Bad PNG file %s\n", name);
		return;
	}

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr)
	{
        png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
		Z_Free (PngFileBuffer.Buffer);
		Com_DPrintf ("LoadPNG: Bad PNG file %s\n", name);
		return;
    }

	end_info = png_create_info_struct(png_ptr);
    if (!end_info)
	{
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
		Z_Free (PngFileBuffer.Buffer);
		Com_DPrintf ("LoadPNG: Bad PNG file %s\n", name);
		return;
    }

	png_set_read_fn (png_ptr,(png_voidp)&PngFileBuffer,(png_rw_ptr)PngReadFunc);

	png_read_info(png_ptr, info_ptr);

	if (info_ptr->width > MAX_TEXTURE_SIZE || info_ptr->height > MAX_TEXTURE_SIZE)	/// PVS-Studio
	{
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
		Z_Free (PngFileBuffer.Buffer);
		Com_DPrintf ("LoadPNG: image too large %s\n", name);
		return;
	}

	if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
	{
		png_set_palette_to_rgb (png_ptr);
		png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
	}

	if (info_ptr->color_type == PNG_COLOR_TYPE_RGB)
		png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);

	if ((info_ptr->color_type == PNG_COLOR_TYPE_GRAY) && info_ptr->bit_depth < 8)
		png_set_gray_1_2_4_to_8(png_ptr);

	if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(png_ptr);

	if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY || info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(png_ptr);

	if (info_ptr->bit_depth == 16)
		png_set_strip_16(png_ptr);

	if (info_ptr->bit_depth < 8)
        png_set_packing(png_ptr);

	if (png_get_gAMA(png_ptr, info_ptr, &file_gamma))
		png_set_gamma (png_ptr, 2.0, file_gamma);

	png_read_update_info(png_ptr, info_ptr);

	rowbytes = png_get_rowbytes(png_ptr, info_ptr);

	*pic = (byte*) Z_Malloc (info_ptr->height * rowbytes, true);

	for (i = 0; i < info_ptr->height; i++)
		row_pointers[i] = *pic + i*rowbytes;

	png_read_image(png_ptr, row_pointers);

	if (width)
		*width = info_ptr->width;
	if (height)
		*height = info_ptr->height;
	*bits = info_ptr->pixel_depth;

	png_read_end(png_ptr, end_info);
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

	if (*bits != 24 && *bits != 32)
	{
		Z_Free (PngFileBuffer.Buffer);
		Com_Error(ERR_FATAL, "LoadPNG: bits-per-pixel must be 24 or 32");
	}

	Z_Free (PngFileBuffer.Buffer);
}


void LoadTGA (char *name, byte **pic, int *width, int *height)
{
	int				i, columns, rows, row_inc, row, col;
	byte			*buf_p, *buffer, *pixbuf, *targa_rgba;
	int				length, readpixelcount, pixelcount;
	byte			palette[256][4], red, green, blue, alpha;
	bool			compressed;
	WORD			wordrgb;
	WORD			*buf_w;
	TargaHeader		targa_header;

	*pic = NULL;

	//
	// load the file
	//
	length = FS_LoadFile (name, (void **)&buffer);
	if (!buffer || (length <= 0))
	{
		if(buffer)
			Z_Free (buffer);
		Com_DPrintf ("LoadTGA [%s]: can`t read file\n", name);
		return;
	}

	buf_p = buffer;
	targa_header.id_length = *buf_p++;
	targa_header.colormap_type = *buf_p++;
	targa_header.image_type = *buf_p++;

	targa_header.colormap_index = buf_p[0] + buf_p[1] * 256;
	buf_p+=2;
	targa_header.colormap_length = buf_p[0] + buf_p[1] * 256;
	buf_p+=2;
	targa_header.colormap_size = *buf_p++;
	targa_header.x_origin = LittleShort (*((short *)buf_p));
	buf_p+=2;
	targa_header.y_origin = LittleShort (*((short *)buf_p));
	buf_p+=2;
	targa_header.width = LittleShort (*((short *)buf_p));
	buf_p+=2;
	targa_header.height = LittleShort (*((short *)buf_p));
	buf_p+=2;
	targa_header.pixel_size = *buf_p++;
	targa_header.attributes = *buf_p++;
	if (targa_header.id_length != 0)
		buf_p += targa_header.id_length;  // skip TARGA image comment

	if (targa_header.width > MAX_TEXTURE_SIZE || targa_header.height > MAX_TEXTURE_SIZE)
	{
		Com_DPrintf("LoadTGA [%s]: image too large\n", name);
		Z_Free(buffer);
		return;
	}

	if ((targa_header.image_type == 1) || (targa_header.image_type == 9))
	{
		// uncompressed colormapped image
		if (targa_header.pixel_size != 8)
		{
			Com_DPrintf ("LoadTGA [%s]: Only 8 bit images supported for type 1 and 9\n", name);
			Z_Free (buffer);
			return;
		}
		if (targa_header.colormap_length != 256)
		{
			Com_DPrintf ("LoadTGA [%s]: Only 8 bit colormaps are supported for type 1 and 9\n", name);
			Z_Free (buffer);
			return;
		}
		if (targa_header.colormap_index)
		{
			Com_DPrintf ("LoadTGA [%s]: Colormap_index is not supported for type 1 and 9\n", name);
			Z_Free (buffer);
			return;
		}
		if (targa_header.colormap_size == 24)
		{
			for (i=0 ; i<targa_header.colormap_length ; i++)
			{
				palette[i][0] = *buf_p++;
				palette[i][1] = *buf_p++;
				palette[i][2] = *buf_p++;
				palette[i][3] = 255;
			}
		}
		else
			if (targa_header.colormap_size == 32)
			{
				for (i=0 ; i<targa_header.colormap_length ; i++)
				{
					palette[i][0] = *buf_p++;
					palette[i][1] = *buf_p++;
					palette[i][2] = *buf_p++;
					palette[i][3] = *buf_p++;
				}
			}
			else
			{
				Com_DPrintf ("LoadTGA [%s]: Only 24 and 32 bit colormaps are supported for type 1 and 9\n", name);
				Z_Free (buffer);
				return;
			}
	}
	else
		if ((targa_header.image_type == 2) || (targa_header.image_type == 10))
		{
			// uncompressed or RLE compressed RGB
			if ((targa_header.pixel_size != 32) && (targa_header.pixel_size != 24) && (targa_header.pixel_size != 16))
			{
				Com_DPrintf ("LoadTGA [%s]: Only 32 or 24 or 16 bit images supported for type 2 and 10\n", name);
				Z_Free (buffer);
				return;
			}
		}
		else
			if ((targa_header.image_type == 3) || (targa_header.image_type == 11))
			{
				// uncompressed greyscale
				if (targa_header.pixel_size != 8 )
				{
					Com_DPrintf ("LoadTGA [%s]: Only 8 bit images supported for type 3 and 11\n", name);
					Z_Free (buffer);
					return;
				}
			}

	columns = targa_header.width;
	if (width)
		*width = columns;

	rows = targa_header.height;
	if (height)
		*height = rows;

	*pic = targa_rgba = (byte*) Z_Malloc (columns * rows * 4, true);

	// if bit 5 of attributes isn't set, the image has been stored from bottom to top
	if (targa_header.attributes & 0x20)
	{
		pixbuf = targa_rgba;
		row_inc = 0;
	}
	else
	{
		pixbuf = targa_rgba + (rows - 1) * columns * 4;
		row_inc = -columns * 4 * 2;
	}

	compressed = ((targa_header.image_type == 9) || (targa_header.image_type == 10) || (targa_header.image_type == 11));
	for (row=col=0 ; row<rows ; )
	{
		readpixelcount = 0x10000;

		if (compressed)
		{
			pixelcount = *buf_p++;
			if (pixelcount & 0x80)	// run-length packet
				readpixelcount = 1;

			pixelcount = 1 + (pixelcount & 0x7f);
		}
		else
			pixelcount = 0x10000;

		while (pixelcount-- && (row < rows))
		{
			if (readpixelcount-- > 0)
			{
				switch (targa_header.image_type)
				{
					case 1:
					case 9:
						// colormapped image
						red = *buf_p++;
						blue = palette[red][0];
						green = palette[red][1];
						alpha = palette[red][3];
						red = palette[red][2];
						break;
					case 2:
					case 10:
						// 16 or 24 or 32 bit image
						{
							if (targa_header.pixel_size == 16)
							{	/// special case for tga16
								buf_w = (WORD*) buf_p;
								wordrgb = *buf_w;
								buf_p+=2;
								blue = (wordrgb & 0x1f)<<3;
								green = (wordrgb & 0x3e0)>>2;
								red = (wordrgb & 0x7c00)>>7;
								alpha = 255;
							}
							else
							{
								blue = *buf_p++;
								green = *buf_p++;
								red = *buf_p++;
								if (targa_header.pixel_size == 32)
									alpha = *buf_p++;
								else
									alpha = 255;
							}
						}
						break;
					case 3:
					case 11:
						// greyscale image
						blue = green = red = *buf_p++;
						alpha = 255;
						break;
				}
			}

			// save as BGRA
			*pixbuf++ = blue;
			*pixbuf++ = green;
			*pixbuf++ = red;
			*pixbuf++ = alpha;
			if (++col == columns)
			{ // run spans across rows
				row++;
				col = 0;
				pixbuf += row_inc;
			}
		}
	}

	Z_Free (buffer);
}


void LoadPCX (char *filename, byte **pic, byte **palette, int *width, int *height)
{
	byte	*raw;
	pcx_t	*pcx;
	int		x, y;
	int		len;
	int		dataByte, runLength;
	byte	*out, *pix;

	*pic = NULL;
	*palette = NULL;

	//
	// load the file
	//
	len = FS_LoadFile (filename, (void **)&raw);
	if (!raw || len <= 0)
	{
		if (raw)
			Z_Free(raw);
		Com_DPrintf ("LoadPCX: Bad pcx file %s\n", filename);
		return;
	}

	//
	// parse the PCX file
	//
	pcx = (pcx_t *)raw;

	pcx->xmin = LittleShort(pcx->xmin);
	pcx->ymin = LittleShort(pcx->ymin);
	pcx->xmax = LittleShort(pcx->xmax);
	pcx->ymax = LittleShort(pcx->ymax);
	pcx->hres = LittleShort(pcx->hres);
	pcx->vres = LittleShort(pcx->vres);
	pcx->bytes_per_line = LittleShort(pcx->bytes_per_line);
	pcx->palette_type = LittleShort(pcx->palette_type);

	raw = &pcx->data;

	if (pcx->manufacturer != 0x0a
		|| pcx->version != 5
		|| pcx->encoding != 1
		|| pcx->bits_per_pixel != 8
		|| pcx->xmax >= MAX_TEXTURE_SIZE
		|| pcx->ymax >= MAX_TEXTURE_SIZE)
	{
		Com_DPrintf ("LoadPCX: Unsupported pcx file %s\n", filename);
		return;
	}

	if (pcx->xmax+1 > MAX_TEXTURE_SIZE || pcx->ymax+1 > MAX_TEXTURE_SIZE)
	{
		Com_DPrintf( "LoadPCX: Image too large %s\n", filename);
		return;
	}

	pix = *pic = out = (byte*) Z_Malloc ( (pcx->ymax+1) * (pcx->xmax+1), true );

	if (palette)
	{
		*palette = (byte*) Z_Malloc(768, true);
		Memcpy (*palette, (byte *)pcx + len - 768, 768);
	}

	if (width)
		*width = pcx->xmax+1;
	if (height)
		*height = pcx->ymax+1;

	for (y=0 ; y<=pcx->ymax ; y++, pix += pcx->xmax+1)
	{
		for (x=0 ; x<=pcx->xmax ; )
		{
			dataByte = *raw++;

			if((dataByte & 0xC0) == 0xC0)
			{
				runLength = dataByte & 0x3F;
				dataByte = *raw++;
			}
			else
				runLength = 1;

			while(runLength-- > 0)
				pix[x++] = dataByte;
		}
	}

	if ( raw - (byte *)pcx > len)
	{
		Com_DPrintf ("LoadPCX: pcx file %s was malformed", filename);
		Z_Free (*pic);
		*pic = NULL;
	}

	Z_Free (pcx);
}


void LoadWal (char *filename, byte **pic, byte **palette, int *width, int *height)
{
	int			len, ofs;
	byte		*raw;
	miptex_t	*mt;
	byte		*out, *pix;

	*pic = NULL;
	*palette = NULL;

	//
	// load the file
	//
	len = FS_LoadFile (filename, (void **)&raw);
	if (!raw || len <= 0)
	{
		if (raw)
			Z_Free(raw);
		Com_DPrintf ("LoadWAL: Bad wal file %s\n", filename);
		return;
	}

	//
	// parse the WAL file
	//
	mt = (miptex_t *)raw;

	if (width)
		*width = LittleLong (mt->width);
	if (height)
		*height = LittleLong (mt->height);
	ofs = LittleLong (mt->offsets[0]);

	out = (byte*) Z_Malloc ( mt->width * mt->height, true );

	*pic = out;

	pix = out;

	if (palette)
	{
		*palette = (byte*) Z_Malloc(768, true);
///		Init_Palette();
		Memcpy (*palette, (byte *)d_8to24table, 768);
	}

	Memcpy (pix, (byte *)mt + ofs, mt->width * mt->height);

	Z_Free (mt);
}


/*
================
GL_MipMap

Operates in place, quartering the size of the texture
================
*/
/*
void GL_MipMap (byte *in, int width, int height)
{
	int		i, j;
	byte	*out;

	width <<=2;
	height >>= 1;
	out = in;
	for (i=0 ; i<height ; i++, in+=width)
	{
		for (j=0 ; j<width ; j+=8, out+=4, in+=8)
		{
			out[0] = (in[0] + in[4] + in[width+0] + in[width+4])>>2;
			out[1] = (in[1] + in[5] + in[width+1] + in[width+5])>>2;
			out[2] = (in[2] + in[6] + in[width+2] + in[width+6])>>2;
			out[3] = (in[3] + in[7] + in[width+3] + in[width+7])>>2;
		}
	}
}
*/


void R_MipMap (byte *in, int width, int height)
{
	int		i, j;
	byte	*out;
	int		row;

	if ( width == 1 && height == 1 )
		return;

	row = width * 4;
	out = in;
	width >>= 1;
	height >>= 1;

	if ( width == 0 || height == 0 )
	{
		width += height;	// get largest
		for (i=0 ; i<width ; i++, out+=4, in+=8 )
		{
			out[0] = ( in[0] + in[4] )>>1;
			out[1] = ( in[1] + in[5] )>>1;
			out[2] = ( in[2] + in[6] )>>1;
			out[3] = ( in[3] + in[7] )>>1;
		}
		return;
	}

	for (i=0 ; i<height ; i++, in+=row)
	{
		for (j=0 ; j<width ; j++, out+=4, in+=8)
		{
			out[0] = (in[0] + in[4] + in[row+0] + in[row+4])>>2;
			out[1] = (in[1] + in[5] + in[row+1] + in[row+5])>>2;
			out[2] = (in[2] + in[6] + in[row+2] + in[row+6])>>2;
			out[3] = (in[3] + in[7] + in[row+3] + in[row+7])>>2;
		}
	}
}


// from XReal
void R_MipNormalMap (byte *in, int width, int height)
{
	int		i, j;
	byte	*out;
	vec3_t	n;
	float	length, inv127 = 1.0f / 127.0f;

	if(width == 1 && height == 1)
		return;

	out = in;
	width <<= 2;
	height >>= 1;

	for(i = 0; i < height; i++, in += width)
	{
		for(j = 0; j < width; j += 8, out += 4, in += 8)
		{
			n[0] =	(inv127 * in[0] - 1.0) +
					(inv127 * in[4] - 1.0) +
					(inv127 * in[width + 0] - 1.0) +
					(inv127 * in[width + 4] - 1.0);

			n[1] =	(inv127 * in[1] - 1.0) +
					(inv127 * in[5] - 1.0) +
					(inv127 * in[width + 1] - 1.0) +
					(inv127 * in[width + 5] - 1.0);

			n[2] =	(inv127 * in[2] - 1.0) +
					(inv127 * in[6] - 1.0) +
					(inv127 * in[width + 2] - 1.0) +
					(inv127 * in[width + 6] - 1.0);

			length = VectorLength(n);

			if(length)
			{
				n[0] /= length;
				n[1] /= length;
				n[2] /= length;
			}
			else
				VectorSet(n, 0.0, 0.0, 1.0);

			out[0] = (byte) (128 + 127 * n[0]);
			out[1] = (byte) (128 + 127 * n[1]);
			out[2] = (byte) (128 + 127 * n[2]);
			out[3] = (in[3] + in[7] + in[width + 3] + in[width + 7]) >> 2;
		}
	}
}


// from XReal
void GL_ResampleTexture (unsigned *in, int inwidth, int inheight, unsigned *out,  int outwidth, int outheight, bool normalMap)
{
	int			i, j;
	unsigned	*inrow, *inrow2;
	unsigned	frac, fracstep;
	unsigned	p1[MAX_TEXTURE_SIZE], p2[MAX_TEXTURE_SIZE];
	byte		*pix1, *pix2, *pix3, *pix4;

	fracstep = inwidth*0x10000/outwidth;

	frac = fracstep>>2;
	for (i=0 ; i<outwidth ; i++)
	{
		p1[i] = 4*(frac>>16);
		frac += fracstep;
	}
	frac = 3*(fracstep>>2);
	for (i=0 ; i<outwidth ; i++)
	{
		p2[i] = 4*(frac>>16);
		frac += fracstep;
	}

	if(normalMap)
	{
		float	inv127 = 1.0 / 127.0;
		vec3_t	n, n2, n3, n4;

		for(i = 0; i < outheight; i++, out += outwidth)
		{
			inrow = in + inwidth * (int)((i + 0.25) * inheight / outheight);
			inrow2 = in + inwidth * (int)((i + 0.75) * inheight / outheight);

			for(j = 0; j < outwidth; j++)
			{
				pix1 = (byte *) inrow + p1[j];
				pix2 = (byte *) inrow + p2[j];
				pix3 = (byte *) inrow2 + p1[j];
				pix4 = (byte *) inrow2 + p2[j];

				n[0] = (pix1[0] * inv127 - 1.0);
				n[1] = (pix1[1] * inv127 - 1.0);
				n[2] = (pix1[2] * inv127 - 1.0);

				n2[0] = (pix2[0] * inv127 - 1.0);
				n2[1] = (pix2[1] * inv127 - 1.0);
				n2[2] = (pix2[2] * inv127 - 1.0);

				n3[0] = (pix3[0] * inv127 - 1.0);
				n3[1] = (pix3[1] * inv127 - 1.0);
				n3[2] = (pix3[2] * inv127 - 1.0);

				n4[0] = (pix4[0] * inv127 - 1.0);
				n4[1] = (pix4[1] * inv127 - 1.0);
				n4[2] = (pix4[2] * inv127 - 1.0);

				n[0] += n2[0] + n3[0] + n4[0];
				n[1] += n2[1] + n3[1] + n4[1];
				n[2] += n2[2] + n3[2] + n4[2];

				if(!VectorNormalize(n))
					VectorSet(n, 0, 0, 1);

				((byte *) (out + j))[0] = (byte)(128 + 127 * n[0]);
				((byte *) (out + j))[1] = (byte)(128 + 127 * n[1]);
				((byte *) (out + j))[2] = (byte)(128 + 127 * n[2]);
				((byte *) (out + j))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2;
			}
		}
	}
	else
	{
		for (i=0 ; i<outheight ; i++, out += outwidth)
		{
			inrow = in + inwidth*(int)((i+0.25)*inheight/outheight);
			inrow2 = in + inwidth*(int)((i+0.75)*inheight/outheight);
			for (j=0 ; j<outwidth ; j++)
			{
				pix1 = (byte *)inrow + p1[j];
				pix2 = (byte *)inrow + p2[j];
				pix3 = (byte *)inrow2 + p1[j];
				pix4 = (byte *)inrow2 + p2[j];
				((byte *)(out+j))[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0])>>2;
				((byte *)(out+j))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2;
				((byte *)(out+j))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2;
				((byte *)(out+j))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2;
			}
		}
	}
}


vec3_t	aver_color;
inline void GL_GetTextureColor (unsigned *in, int inwidth, int inheight)
{
	VectorClear(aver_color);

	int		i, c, d;
	byte	*p;
	float	tmp;

	p = (byte *)in;
	c = inwidth*inheight;
	d = 0;
	tmp = (1.0 / 255);

	for (i=0 ; i<c ; i++, p+=4)
	{
		if(p[3] && (p[0]>4 || p[1]>4 || p[2]>4))
		{
			aver_color[0] += p[2]*tmp;		// BGR
			aver_color[1] += p[1]*tmp;
			aver_color[2] += p[0]*tmp;
			d++;
		}
	}

	if (d)
		VectorScale(aver_color, 1.0/(float)d, aver_color);
	else
		VectorSet(aver_color, 1,1,1);
}


/*
================
GL_LightScaleTexture

Scale up the pixel values in a texture to increase the
lighting range
// from XReal
================
*/
inline void GL_LightScaleTexture (unsigned *in, int inwidth, int inheight, bool only_gamma)
{
	if(!vid_ignorehwgamma->value)
		return;

	if ( only_gamma )
	{
		int		i, c;
		byte	*p;

		p = (byte *)in;

		c = inwidth*inheight;
		for (i=0 ; i<c ; i++, p+=4)
		{
			if(p[3] && (p[0]>4 || p[1]>4 || p[2]>4))
			{
				p[0] = gammatable[p[0]];
				p[1] = gammatable[p[1]];
				p[2] = gammatable[p[2]];
			}
		}
	}
	else
	{
		int		i, c;
		byte	*p;

		p = (byte *)in;

		c = inwidth*inheight;
		for (i=0 ; i<c ; i++, p+=4)
		{
			p[0] = gammatable[intensitytable[p[0]]];
			p[1] = gammatable[intensitytable[p[1]]];
			p[2] = gammatable[intensitytable[p[2]]];
		}
	}
}


/*
===============
GL_Upload32

Returns has_alpha
===============
*/
int	upload_width, upload_height;
byte *palette;

bool GL_Upload32 (unsigned *data, int width, int height, imagetype_t type, int samp, bool no_compress)
{
	int			samples;
	int			scaled_width, scaled_height;
	int			comp;

	if(samp==3 || samp==4)
		samples = samp;
	else
	{
		int		i, c;
		byte	*scan;

		// scan the texture for any non-255 alpha
		c = width*height;
		scan = ((byte *)data) + 3;
		samples = 3;
		for (i=0 ; i<c ; i++, scan += 4)
		{
			if ( *scan != 255 )
			{
				samples = 4;
				break;
			}
		}
	}

	bool mipmap = (type != it_pic && type != it_sky && type != it_sprite);/// || (samples == 4 && type != it_bump);

	for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1);

	if (r_round_down->value && scaled_width > width && mipmap)
		scaled_width >>= 1;

	for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1);

	if (r_round_down->value && scaled_height > height && mipmap)
		scaled_height >>= 1;

	// let people sample down the world textures for speed
	if (mipmap)
	{
		if (type == it_bump)
		{
			scaled_width >>= (int)r_picmip_bump->value;
			scaled_height >>= (int)r_picmip_bump->value;
		}
		else
		{
			scaled_width >>= (int)r_picmip->value;
			scaled_height >>= (int)r_picmip->value;
		}
	}

	// don't ever bother with > MAX_TEXTURE_SIZE textures
#if 0
	if (scaled_width > MAX_TEXTURE_SIZE)
		scaled_width = MAX_TEXTURE_SIZE;
	if (scaled_height > MAX_TEXTURE_SIZE)
		scaled_height = MAX_TEXTURE_SIZE;
#else
	int maxTextureSize = min(MAX_TEXTURE_SIZE, gl_config.maxTextureSize);
	if (scaled_width > maxTextureSize)
		scaled_width = maxTextureSize;
	if (scaled_height > maxTextureSize)
		scaled_height = maxTextureSize;
#endif

	if (scaled_width < 1)
		scaled_width = 1;
	if (scaled_height < 1)
		scaled_height = 1;

	upload_width = scaled_width;
	upload_height = scaled_height;

///	if ((scaled_width > MAX_TEXTURE_SIZE) || (scaled_height > MAX_TEXTURE_SIZE))
///		Com_Error (ERR_DROP, "GL_Upload32: too large");

	int vc;
	if(type == it_bump)
		vc = r_bump_compression->value;
	else
		vc = r_diffuse_compression->value;

	if (samples == 3)
	{
		if(gl_config.compression && !no_compress)
		{
			switch (vc)
			{
			case 1:
				comp = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
				break;
			case 2:
				comp = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
				break;
			case 3:
				comp = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
				break;
			case 4:
				comp = GL_COMPRESSED_RGB_ARB;
				break;
			default:
				comp = gl_tex_solid_format;
			}
		}
		else
			comp = gl_tex_solid_format;
	}
	else if (samples == 4)
	{
		if(gl_config.compression && !no_compress)
		{
			switch (vc)
			{
			case 1:
				comp = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; //quality hack, ибо GL_COMPRESSED_RGBA_S3TC_DXT1_EXT слишком уродливо!!!
				break;
			case 2:
				comp = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
				break;
			case 3:
				comp = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
				break;
			case 4:
				comp = GL_COMPRESSED_RGBA_ARB;
				break;
			default:
				comp = gl_tex_alpha_format;
			}
		}
		else
			comp = gl_tex_alpha_format;
	}
	else
	{
		Com_Printf("^3Unknown number of texture components %i\n", samples);
		comp = samples;
	}

	if (type == it_wall)
		GL_GetTextureColor (data, width, height);

	if (type != it_bump)
		GL_LightScaleTexture (data, width, height, !mipmap);

	if (scaled_width == width && scaled_height == height)
	{
		if (!mipmap)
		{
			glTexImage2D (GL_TEXTURE_2D, 0, comp, scaled_width, scaled_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
			goto done;
		}
		Memcpy (scaled, data, width*height*4);
	}
	else
		GL_ResampleTexture (data, width, height, scaled, scaled_width, scaled_height, type == it_bump);

	glTexImage2D( GL_TEXTURE_2D, 0, comp, scaled_width, scaled_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, scaled );

	if (mipmap)
	{
		int		miplevel;

		miplevel = 0;
		while (scaled_width > 1 || scaled_height > 1)
		{
			if (type == it_bump)
				R_MipNormalMap ((byte *)scaled, scaled_width, scaled_height);
			else
				R_MipMap ((byte *)scaled, scaled_width, scaled_height);

			scaled_width >>= 1;
			scaled_height >>= 1;
			if (scaled_width < 1)
				scaled_width = 1;
			if (scaled_height < 1)
				scaled_height = 1;
			miplevel++;
			glTexImage2D (GL_TEXTURE_2D, miplevel, comp, scaled_width, scaled_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, scaled);
		}
	}
done:

	if(type!=it_pic && type!=it_sky && type!=it_sprite && type!=it_light && type!=it_fx)
		GL_SetAnisotropy();

	if (mipmap)
	{
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
	}
	else
	{
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
	}

	if ( (type == it_sprite) || (type == it_pic) || (type == it_sky) )
	{
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gl_edge_type);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gl_edge_type);
	}
	else
	{
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	}

	return (samples == 4);
}


bool GL_Upload8 (byte *data, int width, int height, imagetype_t type, bool no_compress)
{
	int	i, s, p, samp = 3;

	s = width*height;

	if ((width > MAX_TEXTURE_SIZE) || (height > MAX_TEXTURE_SIZE))
		Com_Error (ERR_DROP, "GL_Upload8: too large");

	for (i=0 ; i<s ; i++)
	{
		p = data[i] * 3;
		// copy rgb components to bgr
		((byte *)&trans[i])[0] = ((byte *)&palette[p])[2];
		((byte *)&trans[i])[1] = ((byte *)&palette[p])[1];
		((byte *)&trans[i])[2] = ((byte *)&palette[p])[0];
		if(p==255*3)
		{
			((byte *)&trans[i])[3] = 0;		// transparent
			samp = 4;
		}
		else
		{
			if (type == it_light)
			{
				((byte *)&trans[i])[3] = 0;
				samp = 4;
			}
			else
				((byte *)&trans[i])[3] = 255;
		}
	}

	return GL_Upload32 (trans, width, height, type, samp, no_compress);
}


bool GL_Upload24 (byte *data, int width, int height, imagetype_t type, bool no_compress)
{
	int	i, s, samp = 3;

	s = width*height;

	for (i=0 ; i<s ; i++)
	{
		// copy rgb components to bgr
		((byte *)&trans[i])[0] = ((byte *)&data[i*3])[2];
		((byte *)&trans[i])[1] = ((byte *)&data[i*3])[1];
		((byte *)&trans[i])[2] = ((byte *)&data[i*3])[0];
		if (type == it_light)
		{
			((byte *)&trans[i])[3] = 0;
			samp = 4;
		}
		else
			((byte *)&trans[i])[3] = 255;
	}

	return GL_Upload32 (trans, width, height, type, samp, no_compress);
}


void	ParseMaterial(material_t *material, char *s)
{
	char	*token;

	while (s)
	{
		token = COM_Parse (&s);
		if (!Q_strcasecmp(token, "decal") && mtr_decal->value)
		{
			/// Внимание! Если здесь вызывать загрузку картинки декаля, то словим качественный глюк!!! Не делать этого!
			Memcpy(material->decal_name, COM_Parse(&s), sizeof(material->decal_name));
			continue;
		}
		if (!Q_strcasecmp(token, "blend"))
		{
			token = COM_Parse(&s);
			material->add_blend = !Q_strcasecmp(token, "add");
			continue;
		}
		if (!Q_strcasecmp(token, "debris") && mtr_debris->value)
		{
			/// Внимание! Если здесь вызывать загрузку дебриса, то словим качественный глюк!!! Не делать этого!
			Memcpy(material->debris_name, COM_Parse(&s), sizeof(material->debris_name));
			continue;
		}
		if (!Q_strcasecmp(token, "footstep") && mtr_footstep->value)
		{
			Memcpy(material->footstep_name, COM_Parse(&s), sizeof(material->footstep_name));
			continue;
		}
		if (mtr_hitsound->value)
		{
			if (!Q_strcasecmp(token, "bullethit"))
			{
				Memcpy(material->hs_bullet_name, COM_Parse(&s), sizeof(material->hs_bullet_name));
				continue;
			}
			if (!Q_strcasecmp(token, "energyhit"))
			{
				Memcpy(material->hs_energy_name, COM_Parse(&s), sizeof(material->hs_energy_name));
				continue;
			}
		}
	}
}


material_t	*R_LoadMaterial(char *name, byte *data, unsigned hash)
{
	int			i;
	material_t	*material;

	// find a free material_t
	for (i=0, material=materials ; i<nummaterials ; i++,material++)
	{
		if (!material->registration_sequence)
			break;
	}
	if (i == nummaterials)
	{
		if (nummaterials == MAX_MATERIALS)
			Com_Error (ERR_FATAL, "MAX_MATERIALS");
		nummaterials++;
	}

	material = &materials[i];
	Memset(material, 0, sizeof(material_t));

	if (strlen(name) >= sizeof(material->name))
		Com_Error (ERR_DROP, "R_LoadMaterial: \"%s\" is too long", name);
	strcpy (material->name, name);
	material->hash = hash;
	material->registration_sequence = registration_sequence;

	ParseMaterial(material, (char*)data);
	S_RegisterMatSounds(material);

	return material;
}


material_t	*R_RegisterMaterial(char *name)
{
	int			i, len;
	material_t	*material;
	byte		*matdata;
	char		fname[MAX_OSPATH];		// Berserker: здесь реальное имя файла описателя материала

	if (!name)
		return NULL;
	if (name[0]==0)
		return NULL;

	unsigned hash = Com_HashKey(name);
	// look for it
	for (i=0, material=materials ; i<nummaterials ; i++,material++)
	{
		if (material->hash==hash)
		{
			if (!b_stricmp(material->name, name))
			{
				material->registration_sequence = registration_sequence;
				return material;
			}
		}
	}

	Com_sprintf (fname, sizeof(fname), "%s.mtr", name);

	// load the material from disk
	matdata = NULL;
	len = FS_LoadFile (fname, (void **)&matdata);
	if (!matdata || (len <= 0))
	{
		if(matdata)
			Z_Free (matdata);
		Com_DPrintf ("R_RegisterMaterial [%s]: can`t read file\n", name);
		return NULL;
	}

	material = R_LoadMaterial(name, matdata, hash);
	material->framecount = 1;
	Z_Free (matdata);
	return material;
}


void	ParseFX(image_t *image, char *s)
{
	char	*token;

	while (s)
	{
		token = COM_Parse (&s);
		if (!Q_strcasecmp(token, "fx"))
		{
			token = COM_Parse (&s);
			if (!Q_strcasecmp(token, "chrome"))
				image->fx = fx_chrome;
			else if (!Q_strcasecmp(token, "power"))
				image->fx = fx_power;
			else if (!Q_strcasecmp(token, "map"))
				image->fx = fx_map;
			else if ((!Q_strcasecmp(token, "distort")) && r_distort->value)
				image->fx = fx_distort;
			else if (!Q_strcasecmp(token, "style"))
				image->fx = fx_style;
			continue;
		}
		if (!Q_strcasecmp(token, "image"))
		{
			/// Внимание! Если здесь вызывать загрузку текстуры, то словим качественный глюк!!! Не делать этого!
			Memcpy(image->fx_image_name, COM_Parse (&s), sizeof(image->fx_image_name));
			continue;
		}
		if (!Q_strcasecmp(token, "detailbump"))
		{
			/// Внимание! Если здесь вызывать загрузку текстуры, то словим качественный глюк!!! Не делать этого!
			Memcpy(image->fx_detail_image_name, COM_Parse (&s), sizeof(image->fx_detail_image_name));
			image->NoDetailBump = false;
			continue;
		}
		if (!Q_strcasecmp(token, "detailscale"))
		{
			image->detail_scale = atof(COM_Parse (&s));
			continue;
		}
		if (!Q_strcasecmp(token, "nodetailbump"))
		{
			image->NoDetailBump = true;
			continue;
		}
		if (image->has_alpha)
		{
			if (!Q_strcasecmp(token, "parallax"))
			{
				image->Parallax = true;
				continue;
			}
			if (!Q_strcasecmp(token, "alphatest"))
			{
				image->AlphaTest = true;
				continue;
			}
			if (!Q_strcasecmp(token, "castshadow"))
			{
				image->CastShadow = true;
				continue;
			}
		}
		if (!Q_strcasecmp(token, "trans33"))
		{
			image->Translucent33 = true;
			continue;
		}
		if (!Q_strcasecmp(token, "trans66"))
		{
			image->Translucent66 = true;
			continue;
		}
		if (!Q_strcasecmp(token, "scroll"))
		{
			image->fx_s = atof(COM_Parse (&s));
			image->fx_t = atof(COM_Parse (&s));
			continue;
		}
		if (!Q_strcasecmp(token, "scale"))
		{
			image->fx_scale_s = atof(COM_Parse (&s));
			image->fx_scale_t = atof(COM_Parse (&s));
			continue;
		}
		if (!Q_strcasecmp(token, "original_size"))
		{
			image->fx_original_size_s = atoi(COM_Parse (&s));
			image->fx_original_size_t = atoi(COM_Parse (&s));
			continue;
		}
		if (!Q_strcasecmp(token, "style"))
		{
			unsigned	tmp;
			tmp = (atoi(COM_Parse (&s)) & 0xff);
///			if(tmp<0 || tmp>=MAX_LIGHTSTYLES)	// лишнее для unsighed и AND 0xFF
//				image->fx_s=0;
			if(image->fx_s == -1234)
				image->fx_s = tmp;		// fx_s не используется в fx_style, поэтому пусть хранит номер стиля
			else
				image->fx_s += tmp;		// fx_s не используется в fx_style, поэтому пусть хранит номер стиля
			continue;
		}
		if (!Q_strcasecmp(token, "cl_style"))
		{
			unsigned	tmp;
			tmp = ((atoi(COM_Parse (&s)) & 0xff) << 8);
///			if(tmp<0 || tmp>=MAX_LIGHTSTYLES)	// лишнее для unsighed и AND 0xFF
//				image->fx_s=0;
			if(image->fx_s == -1234)
				image->fx_s = tmp;		// fx_s не используется в fx_style, поэтому пусть хранит номер клиентского стиля
			else
				image->fx_s += tmp;		// fx_s не используется в fx_style, поэтому пусть хранит номер клиентского стиля
			continue;
		}
		if (!Q_strcasecmp(token, "rotate"))
		{
			image->rotate = atof(COM_Parse (&s));
			continue;
		}
		if (!Q_strcasecmp(token, "color"))
		{
			sscanf (s, "%f %f %f", &image->fx_t, &image->fx_scale_s, &image->fx_scale_t);
			if(image->fx_t<0 || image->fx_t>1)		// fx_t, fx_scale_s и fx_scale_t не используются в fx_style, поэтому пусть хранят RGB цвет
				image->fx_t=1;
			if(image->fx_scale_s<0 || image->fx_scale_s>1)
				image->fx_scale_s=1;
			if(image->fx_scale_t<0 || image->fx_scale_t>1)
				image->fx_scale_t=1;
			continue;
		}
		if (!Q_strcasecmp(token, "material"))
		{
			image->material = R_RegisterMaterial(COM_Parse (&s));
			continue;
		}
		if (!Q_strcasecmp(token, "mirror"))
		{
			image->mirror = true;
			continue;
		}
	}

	if (image->fx == fx_none)
		image->fx_image_name[0]= 0;
}


/*
================
GL_LoadPic

This is also used as an entry point for the generated r_notexture
================
*/
image_t *GL_LoadPic (char *name, char *name0, byte *pic, int width, int height, imagetype_t type, int bits, bool no_compress, unsigned i_hash)
{
	image_t		*image;
	int			i;

	// find a free image_t
	for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (!image->texnum)
			break;
	}
	if (i == numgltextures)
	{
		if (numgltextures == MAX_GLTEXTURES)
			Com_Error (ERR_FATAL, "MAX_GLTEXTURES");
		numgltextures++;
	}
	image = &gltextures[i];

	if (strlen(name) >= sizeof(image->name))
		Com_Error (ERR_DROP, "GL_LoadPic: \"%s\" is too long", name);
	strcpy (image->name, name);
	if (i_hash)
		image->hash = i_hash;
	else
		image->hash = Com_HashKey(name);
	image->registration_sequence = registration_sequence;

	image->width = width;
	image->height = height;
	image->type = type;

	if (type == it_pic && (name[0]|0x20)=='p' && (name[1]|0x20)=='i' && (name[2]|0x20)=='c' && (name[3]|0x20)=='s')
	{
		unsigned	s, t;
		if (FS_GetPCXSizes(name, &s, &t))
		{
			image->scale_s = s;
			image->scale_t = t;
		}
		else
		{
			image->scale_s = 0;
			image->scale_t = 0;
		}
	}

	image->texnum = TEXNUM_IMAGES + (image - gltextures);
	GL_Bind2D (image->texnum);
	if (bits == 8)
	{
		if ((width > MAX_TEXTURE_SIZE) || (height > MAX_TEXTURE_SIZE))
			Com_Error (ERR_DROP, "GL_LoadPic(8): \"%s\" too large", name);

		image->has_alpha = GL_Upload8 (pic, width, height, type, no_compress);
	}
	else if (bits == 24)
	{
		if ((width > MAX_TEXTURE_SIZE) || (height > MAX_TEXTURE_SIZE))
			Com_Error (ERR_DROP, "GL_LoadPic(24): \"%s\" too large", name);

		image->has_alpha = GL_Upload24 (pic, width, height, type, no_compress);
	}
	else
	{
		if (!r_simple->value)
		{
			if(r_bumpscale->value && (type==it_skin || type==it_wall))			// Сохраним картинку для возможного авто-бампа
			{
				if ((width > MAX_TEXTURE_SIZE) || (height > MAX_TEXTURE_SIZE))
					Com_Error (ERR_DROP, "GL_LoadPic(32): \"%s\" too large", name);

				Memcpy(trans, pic, width*height*4);
			}
		}

		image->has_alpha = GL_Upload32 ((unsigned*)pic, width, height, type, 0, no_compress);	// 0 - просканить альфа-канал имиджа
	}
	if (type==it_wall)
		VectorScale(aver_color, 0.75, image->color);

	image->upload_width = upload_width;		// after power of 2 and scales
	image->upload_height = upload_height;

	int len = strlen(name) - 4;
	if (!Q_strcasecmp(name+len, ".wal"))
		image->filetype = file_wal;
	else if (!Q_strcasecmp(name+len, ".pcx"))
		image->filetype = file_pcx;
	else if (!Q_strcasecmp(name+len, ".tga"))
		image->filetype = file_tga;
	else if (!Q_strcasecmp(name+len, ".jpg"))
		image->filetype = file_jpg;
	else if (!Q_strcasecmp(name+len, ".png"))
		image->filetype = file_png;
	else if (!Q_strcasecmp(name+len, ".dds"))
		image->filetype = file_dds;
	else
		image->filetype = file_nul;

	// set up defaults
	image->fx = fx_none;
	image->mirror = false;
	image->NoDetailBump = true;
	image->Parallax = image->AlphaTest = image->Translucent33 = image->Translucent66 = image->CastShadow = false;
	image->fx_image = image->fx_detail_image = NULL;
	image->detail_scale = 32;
	image->fx_image_name[0] = image->fx_detail_image_name[0] = 0;
	image->fx_s = -1234;
	image->fx_t = 8765;
	image->fx_scale_s = image->fx_scale_t = 1;
	image->fx_original_size_s = -1;		// default
	image->material = NULL;
	image->rotate = 0;

	if(name0 && (type==it_skin || type==it_wall || type==it_sprite))
	{
		char	sname[MAX_QPATH];

		int len = strlen(name0);
		if(len>4)		// если name0 с расширением, обрежем его...
		{
			if(name0[len-4]=='.')
			{
				Memcpy(sname, name0, MAX_QPATH);
				sname[len-4] = 0;
				len -= 4;
				name0 = sname;
			}
		}

		/// BIG HACK
		if(!strncmp(name0+len-7,"~chrome",7))		/// Textures like "texture~chrome" has envmap effect!
			image->fx = fx_chrome;
		else if(!strncmp(name0+len-6,"~power",6))	/// Textures like "texture~power" has power effect!
			image->fx = fx_power;
		else if(!strncmp(name0+len-4,"~map",4))		/// Textures like "texture~map" has map effect!
			image->fx = fx_map;
		else
		{
			char	nam[MAX_QPATH];
			char	*buffer;
			Com_sprintf(nam, sizeof(nam), "%s.fx", name0);
			// load the .fx
			len = FS_LoadFile (nam, (void **)&buffer);
			if (buffer)
			{
				if (len > 0)
				{
					char bak=buffer[len];
					buffer[len]=0;
					ParseFX(image, buffer);
					buffer[len]=bak;
				}
				Z_Free (buffer);
			}
		}
		if((image->fx == fx_distort))
		{
			if((image->fx_s == -1234) && (image->fx_t == 8765))
			{
				image->fx_s = 0;
				image->fx_t = 0;
			}
		}
		else if(image->fx == fx_style)
		{
			if(image->fx_s == -1234)
				image->fx_s = 0;
			if(image->fx_t == 8765)
				image->fx_t = 1;
		}
		else if((image->fx_s == -1234) && (image->fx_t == 8765))
		{
			image->fx_s = 1;
			image->fx_t = 0;
		}
	}

	if(r_texfx->value == 0)
	{
		image->fx = fx_none;
		image->fx_image_name[0] = 0;
		image->fx_detail_image_name[0] = 0;
		image->NoDetailBump = true;
		image->Parallax = false;
	}

	if (image->fx == fx_distort)
	{
		if (r_simple->value || (r_shader->value != SHADER_ARB4 && r_shader->value != SHADER_ARB6))
		{
			image->fx = fx_none;
			image->Translucent33 = true;
		}
	}

	if (r_simple->value)
	{
		image->Parallax = false;
		if (type==it_skin)
		{
			image->fx_detail_image = NULL;
			image->fx_detail_image_name[0] = 0;
		}
		else
		{
			image->fx = fx_none;
			image->fx_image = image->fx_detail_image = NULL;
			image->fx_image_name[0] = image->fx_detail_image_name[0] = 0;
			image->fx_s = -1234;
			image->fx_t = 8765;
			image->fx_scale_s = 1;
			image->fx_scale_t = 1;
		}
	}
	else
		if (type!=it_wall)
			image->Parallax = false;

	if (image->Parallax)
	{
		if (image->fx == fx_style)
		{
			Com_Printf("^3Parallax with FX_Style for image \"%s\"! Parallax disabled.\n", image->name);
			image->Parallax = false;
		}
		if (image->fx == fx_distort)
		{
			Com_Printf("^3Parallax with FX_Distort for image \"%s\"! Parallax disabled.\n", image->name);
			image->Parallax = false;
		}
	}

	return image;
}


void GenBumpMap(byte *pixels, int w, int h, byte *nmap)
{
	int i, j, cc;
	float sqlen, reciplen, nx, ny, nz;
	float oneOver255 = 1.0f/(255.0f*3.0f);
	float c, cx, cy, dcx, dcy;

	for (i=0; i<h; i++)
	{
		for (j=0; j<w; j++)
		{
			/* Expand the texel to its right. */
			int p = (i*w+(j+1)%w)*4;
			cx = (pixels[p] + pixels[p+1] + pixels[p+2]) * oneOver255;
			/* Expand the texel one up. */
			p = (((i+1)%h)*w + j)*4;
			cy = (pixels[p] + pixels[p+1] + pixels[p+2]) * oneOver255;
			/* Expand [0,255] texel values to the [0,1] range. */
			p = (i*w+j)*4;
			c = (pixels[p] + pixels[p+1] + pixels[p+2]) * oneOver255;
			dcx = (c - cx) * r_bumpscale->value;
			dcy = (c - cy) * r_bumpscale->value;

			/* Normalize the vector. */
			sqlen = dcx*dcx + dcy*dcy + 1;
			reciplen = RSqrt(sqlen);
			nx = dcx*reciplen;
			ny = dcy*reciplen;
			nz = reciplen;
			cc = c * r_specularscale->value;
			if (cc>1)	cc=1;
			if (cc<0)	cc=0;

			nmap[p+0] = (byte) (128 + 127*nz);		// BGRA
			nmap[p+1] = (byte) (128 + 127*ny);
			nmap[p+2] = (byte) (128 + 127*nx);
			nmap[p+3] = (byte) (255 * cc);
		}
	}
}


/// Копирует имя файла без суффиксов типа "~"
void __cdecl FS_GetName(char *to, const char *from)
{
	char	c;
	bool	skip = false;

	while (1)
	{
		c=*from++;

		if(c=='~' && !skip)
			skip = true;

		if((c=='.' || c=='_') && skip)
			skip = false;

		if(c==NULL)
		{
			*to=NULL;
			return;
		}

		if(!skip)
			*to++=c;
	}
}


char *FS_GetFileName(char *path)
{
	char *from = path;
	char *name = path;

	while (1)
	{
		if (path[0]==0)
			break;

		if (path[0]=='/')
			name = path+1;
		else if (path[0]=='\\')
			name = path+1;
		path++;
	}

	return name;
}


// from Q2XP2
unsigned Com_HashKey___ (const char *string, int len)
{
	unsigned	hashKey = 0;
	int			i;
	char		letter;

	for (i = 0; string[i] && i<len; i++)
	{
		letter = tolower(string[i]);
		if (letter =='\\')
			letter = '/';			// damn path names
		hashKey = (hashKey + i) * 37 + letter;
	}

	return hashKey;
}

/*
===============
GL_FindImage

Finds or loads the given image
===============
*/
int	overrided;
image_t	*GL_FindImage (char *name, imagetype_t type, bool exactly, image_t *tex, bool autobump, int override)
{
	image_t	*image;
	int		i, len;
	byte	*pic;
	int		width, height;
	char	*truename = NULL;
	char	fname[MAX_QPATH/*MAX_OSPATH*/];		// Berserker: здесь реальное имя графического файла
	char	fname2[MAX_QPATH/*MAX_OSPATH*/];		// Berserker: здесь реальное имя графического файла

	if (!name)
		return NULL;
	if (name[0]==0)
		return NULL;

	// Fixed: оверрайдим тока "родные текстуры"
	if (override)
	{
		unsigned hash___ = Com_HashKey___(name,14);
		for (i=0; i<MAX_SPEC_TEXTURES; i++)
		{
			if (hashes[i] == hash___)
				break;
		}
		if (i==MAX_SPEC_TEXTURES)
			override = 0;
	}

	if ((override&1) && r_override->value)
	{
		truename = FS_GetFileName(name);

		Com_sprintf (fname, sizeof(fname), "overrides/%s/%s", mapname, truename);
		image = GL_FindImage(fname, type, exactly, tex, autobump, 0);
		if (image)
		{
			overrided = 1;
			return image;
		}
	}

	if ((override&2) && r_override->value)
	{
		if (!truename)
			truename = FS_GetFileName(name);

		Com_sprintf (fname, sizeof(fname), "overrides/%s", truename);
		image = GL_FindImage(fname, type, exactly, tex, autobump, 0);
		if (image)
		{
			overrided = 2;
			return image;
		}
	}

	unsigned hash = Com_HashKey(name);
	// look for it
	for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (image->hash == hash)
		{
			if (!b_stricmp(image->name, name))
			{
				image->registration_sequence = registration_sequence;

				if(image->material)
					image->material->registration_sequence = registration_sequence;

				return image;
			}
		}
	}

	pic = palette = NULL;
	if(exactly)					// Bump всегда должен быть с точным именем (признак exactly)
	{
///		strcpy(fname, name);
FS_GetName(fname, name);
		if (FS_LoadFile (fname, NULL) == -1)
		{
			if(type != it_bump)	// И если это Bump не найден, то сгенерим его на основе основной текстуры, образ которой остался в Trans...
				return NULL;

			if(autobump && r_autobump->value)
			{
				GenBumpMap ((byte*)&trans[0], tex->width, tex->height, (byte*)&scaled[0]);
				Memcpy(trans, scaled, sizeof(scaled));
				image = GL_LoadPic (name, name, (byte*)&trans[0], tex->width, tex->height, it_bump, 32, false, Com_HashKey(name));
				strcpy(image->name, name);		// Регистрируем картинку под входящим именем
				return image;
			}
			else
				return r_defaultbump;
		}
	}
	else
	{
///		strcpy(fname, name);
FS_GetName(fname, name);
strcpy(fname2, fname);
		if (FS_LoadFile (fname, NULL) == -1)
		{	// if not exist, try again with PNG
			Com_sprintf (fname, sizeof(fname), "%s.png", fname2);
			if (FS_LoadFile (fname, NULL) == -1)
			{	// if not exist, try again with TGA
				Com_sprintf (fname, sizeof(fname), "%s.tga", fname2);
				if (FS_LoadFile (fname, NULL) == -1)
				{	// if not exist, try again with DDS
					Com_sprintf (fname, sizeof(fname), "%s.dds", fname2);
					if (FS_LoadFile (fname, NULL) == -1)
					{	// if not exist, try again with JPG
						Com_sprintf (fname, sizeof(fname), "%s.jpg", fname2);
						if (FS_LoadFile (fname, NULL) == -1)
						{	// if not exist, try again with WAL
							Com_sprintf (fname, sizeof(fname), "%s.wal", fname2);
							if (FS_LoadFile (fname, NULL) == -1)
							{	// if not exist, try again with PCX
								Com_sprintf (fname, sizeof(fname), "%s.pcx", fname2);
								if (FS_LoadFile (fname, NULL) == -1)
									return NULL;
								else
									goto pcx;
							}
							else
								goto wal;
						}
						else
							goto jpg;
					}
					else
						goto dds;
				}
				else
					goto tga;
			}
			else
				goto png;
		}
	}

	len = strlen(fname);
	if (!Q_strcasecmp(fname+len-4, ".png"))
	{
png:	int bits;
		LoadPNG (fname, &pic, &width, &height, &bits);		// грузит в формате RGB/RGBA
		if (!pic)
			return NULL;
		if (bits == 32)
			RGBA2BGRA(width, height, pic);		// RGBA -> BGRA
		image = GL_LoadPic (fname, name, pic, width, height, type, bits, false, Com_HashKey(name));
		strcpy(image->name, name);		// Регистрируем картинку под входящим именем
		goto cont;
	}
	else if (!Q_strcasecmp(fname+len-4, ".dds"))
	{
dds:	LoadDDS (fname, &pic, &width, &height);				// грузит в формате BGRA
		if (!pic)
			return NULL;
		image = GL_LoadPic (fname, name, pic, width, height, type, 32, false, Com_HashKey(name));
		strcpy(image->name, name);		// Регистрируем картинку под входящим именем
		goto cont;
	}
	else if (!Q_strcasecmp(fname+len-4, ".tga"))
	{
tga:	LoadTGA (fname, &pic, &width, &height);				// грузит в формате BGRA
		if (!pic)
			return NULL;
		image = GL_LoadPic (fname, name, pic, width, height, type, 32, false, Com_HashKey(name));
		strcpy(image->name, name);		// Регистрируем картинку под входящим именем
		goto cont;
	}
	else if (!Q_strcasecmp(fname+len-4, ".jpg"))
	{
jpg:	LoadJPG (fname, &pic, &width, &height);				// грузит в формате RGB, GL_Upload24 переделает в BGRA
		if (!pic)
			return NULL;
		image = GL_LoadPic (fname, name, pic, width, height, type, 24, false, Com_HashKey(name));
		strcpy(image->name, name);		// Регистрируем картинку под входящим именем
		goto cont;
	}
	else if (!Q_strcasecmp(fname+len-4, ".wal"))
	{
wal:	LoadWal (fname, &pic, &palette, &width, &height);	// грузит в формате RGB, GL_Upload8 переделает в BGRA
		if (!pic)
			return NULL;
		image = GL_LoadPic (fname, name, pic, width, height, type, 8, false, Com_HashKey(name));
		strcpy(image->name, name);		// Регистрируем картинку под входящим именем
		goto cont;
	}
	else if (!Q_strcasecmp(fname+len-4, ".pcx"))
	{
pcx:	LoadPCX (fname, &pic, &palette, &width, &height);	// грузит в формате RGB, GL_Upload8 переделает в BGRA
		if (!pic)
			return NULL;
		image = GL_LoadPic (fname, name, pic, width, height, type, 8, false, Com_HashKey(name));
		strcpy(image->name, name);		// Регистрируем картинку под входящим именем
		goto cont;
	}
	else
		return NULL;

cont:
	if (pic)
		Z_Free(pic);
	if (palette)
		Z_Free(palette);

	return image;
}


void Mod_LoadTexinfo (lump_t *l)
{
	texinfo_t *in;
	mtexinfo_t *out, *step;
	int 	i, j, count;
	char	name[MAX_QPATH];
	char	bumpname[MAX_QPATH];
	int		next;

	in = (texinfo_t *)(mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "Mod_LoadTexinfo: funny lump size in %s",loadmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mtexinfo_t *) Hunk_Alloc (count * sizeof(*out), true);

	loadmodel->texinfo = out;
	loadmodel->numtexinfo = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		for (j=0 ; j<8 ; j++)
			out->vecs[0][j] = LittleFloat (in->vecs[0][j]);

		out->flags = LittleLong (in->flags);
		if (in->value > 500)
			out->flags |= SURF_FLARE;

		next = LittleLong (in->nexttexinfo);
		if (next > 0)
			out->next = loadmodel->texinfo + next;
		else
		    out->next = NULL;

		if (caching__)		// при кэшировании пропустим этот шаг
		{
			out->image = r_notexture;
			out->bump = r_defaultbump;
			out->light = NULL;
		}
		else
		{
			overrided = 0;
			Com_sprintf (name, sizeof(name), "textures/%s", in->texture);		/// Berserker: грузим любую графику, а не только WAL
			out->image = GL_FindImage (name, it_wall, false, 0, false, 3);
			if (!out->image)
			{
				Com_Printf ("^1Couldn't load %s\n", name);
				out->image = r_notexture;		// Если текстура не найдена, то и не имеет смысла ни искать на диске, ни генерить Bump
				out->bump = r_defaultbump;
				out->light = NULL;
			}
			else
			{
				int over = overrided;

///				if (out->image->filetype == file_dds || out->image->filetype == file_png || out->image->filetype == file_tga || out->image->filetype == file_jpg)	// HACK: Если текстура PNG, TGA или JPG,
///					out->flags |= SURF_NEWTEX;																					// то увеличим детализацию
if (out->image->filetype != file_wal)
	out->flags |= SURF_NEWTEX;

				if (out->image->AlphaTest/* && out->image->has_alpha*/)	// HACK: Если текстура имеет альфа-канал (с альфа-тестом),
					out->flags |= SURF_FENCE;						// то будем считать её "дырявой". (не декалится и не отбрас.теней)
///				else if (out->image->Translucent33 && out->image->has_alpha)	// HACK: Если текстура имеет альфа-канал (с блендом 33),
///					out->flags |= SURF_TRANS33;							// то будем считать её "полупрозрачной 33". (не отбрас.теней)
///				else if (out->image->Translucent66 && out->image->has_alpha)	// HACK: Если текстура имеет альфа-канал (с блендом 66),
///					out->flags |= SURF_TRANS66;							// то будем считать её "полупрозрачной 66". (не отбрас.теней)

				if (!r_simple->value)
				{
					Com_sprintf (bumpname, sizeof(bumpname), "textures/%s_bump.tga", in->texture);		/// Berserker: BUMP - только TGA или DDS!
					out->bump = GL_FindImage (bumpname, it_bump, true, out->image, false, over);
					if (out->bump == r_defaultbump)
					{
						Com_sprintf (bumpname, sizeof(bumpname), "textures/%s_bump.dds", in->texture);		/// Berserker: BUMP - только TGA или DDS!
						out->bump = GL_FindImage (bumpname, it_bump, true, out->image, (r_bumpscale->value) ? true : false, over);	// Для стен - генерим автоматом бамп-текстуру, если таковая отсутствует (и если разрешено)...
					}

					Com_sprintf (bumpname, sizeof(bumpname), "textures/%s_light", in->texture);
					out->light = GL_FindImage (bumpname, it_light, false, 0, false, over);
				}
				else
				{
					out->bump = r_notexture;
					out->light = NULL;
				}
			}
		}
	}

	// count animation frames
	for (i=0 ; i<count ; i++)
	{
		out = &loadmodel->texinfo[i];
		out->numframes = 1;
		for (step = out->next ; step && step != out ; step=step->next)
			out->numframes++;
	}
}


image_t	*Draw_FindPic (char *name)
{
	image_t *gl;
	char	fullname[MAX_QPATH];

	if (name[0] != '/' && name[0] != '\\')
	{
		Com_sprintf (fullname, sizeof(fullname), "pics/%s", name);
		gl = GL_FindImage (fullname, it_pic, false, 0, false, 0);
	}
	else
		gl = GL_FindImage (name+1, it_pic, false, 0, false, 0);

	return gl;
}


/*
=============
Draw_TileClear

This repeats a 64*64 tile graphic to fill the screen around a sized down
refresh window.
=============
*/
void Draw_TileClear (int x, int y, int w, int h, char *pic)
{
	image_t	*image;

	image = Draw_FindPic (pic);
	if (!image)
	{
		Com_Printf("^1Can't find pic: %s\n", pic);
		return;
	}

	/// Hack (usualy it_pic - GL_CLAMP)
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

	GL_Bind2D (image->texnum);

	int x0 = x;
	int y0 = y;
	int x1 = x0 + w;
	int y1 = y0 + h;
///	Adjust320to640(&x0, &y0, &x1, &y1);

	glBegin (GL_QUADS);
	glTexCoord2f (x/64.0, y/64.0);
	glVertex2f (x0, y0);
	glTexCoord2f ( (x+w)/64.0, y/64.0);
	glVertex2f (x1, y0);
	glTexCoord2f ( (x+w)/64.0, (y+h)/64.0);
	glVertex2f (x1, y1);
	glTexCoord2f ( x/64.0, (y+h)/64.0 );
	glVertex2f (x0, y1);
	glEnd ();
}


/*
==============
SCR_TileClear

Clear any parts of the tiled background that were drawn on last frame
==============
*/
void SCR_TileClear ()
{
	int		i;
	int		top, bottom, left, right;
	dirty_t	clear;

	if (scr_con_current == 1.0)
		return;		// full screen console
	if (scr_viewsize->value == 100)
		return;		// full screen rendering
	if (cl.cinematictime > 0)
		return;		// full screen cinematic
	if (scr_mapshots->value && mapshot[0] && (cls.state != ca_active || !cl.refresh_prepped))
		return;		// full screen levelshot

	// erase rect will be the union of the past three frames
	// so tripple buffering works properly
	clear = scr_dirty;
	for (i=0 ; i<2 ; i++)
	{
		if (scr_old_dirty[i].x1 < clear.x1)
			clear.x1 = scr_old_dirty[i].x1;
		if (scr_old_dirty[i].x2 > clear.x2)
			clear.x2 = scr_old_dirty[i].x2;
		if (scr_old_dirty[i].y1 < clear.y1)
			clear.y1 = scr_old_dirty[i].y1;
		if (scr_old_dirty[i].y2 > clear.y2)
			clear.y2 = scr_old_dirty[i].y2;
	}

	scr_old_dirty[1] = scr_old_dirty[0];
	scr_old_dirty[0] = scr_dirty;

	scr_dirty.x1 = 9999;
	scr_dirty.x2 = -9999;
	scr_dirty.y1 = 9999;
	scr_dirty.y2 = -9999;

	// don't bother with anything convered by the console)
	top = scr_con_current*viddef.height;
	if (top >= clear.y1)
		clear.y1 = top;

	if (clear.y2 <= clear.y1)
		return;		// nothing disturbed

	top = scr_vrect.y;
	bottom = top + scr_vrect.height-1;
	left = scr_vrect.x;
	right = left + scr_vrect.width-1;

	if (clear.y1 < top)
	{	// clear above view screen
		i = clear.y2 < top-1 ? clear.y2 : top-1;
		Draw_TileClear (clear.x1 , clear.y1, clear.x2 - clear.x1 + 1, i - clear.y1+1, "backtile");
		clear.y1 = top;
	}
	if (clear.y2 > bottom)
	{	// clear below view screen
		i = clear.y1 > bottom+1 ? clear.y1 : bottom+1;
		Draw_TileClear (clear.x1, i, clear.x2-clear.x1+1, clear.y2-i+1, "backtile");
		clear.y2 = bottom;
	}
	if (clear.x1 < left)
	{	// clear left of view screen
		i = clear.x2 < left-1 ? clear.x2 : left-1;
		Draw_TileClear (clear.x1, clear.y1,	i-clear.x1+1, clear.y2 - clear.y1 + 1, "backtile");
		clear.x1 = left;
	}
	if (clear.x2 > right)
	{	// clear left of view screen
		i = clear.x1 > right+1 ? clear.x1 : right+1;
		Draw_TileClear (i, clear.y1, clear.x2-i+1, clear.y2 - clear.y1 + 1, "backtile");
		clear.x2 = right;
	}
}


/*
====================
V_ClearScene

Specifies the model that will be used as the world
====================
*/
void V_ClearScene ()
{
	r_numdlights = 0;
	r_numentities = 0;
	r_numparticles = 0;
}


float LerpAngle (float a2, float a1, float frac)
{
	if (a1 - a2 > 180)
		a1 -= 360;
	if (a1 - a2 < -180)
		a1 += 360;
	return a2 + frac * (a1 - a2);
}


void V_AddEntity (entity_t *ent, int key, alink_t *alink)
{
	if (r_numentities >= MAX_ENTITIES)
		return;

	if (r_simple->value)
		ent->flags &= ~(/*RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|*/RF_LIGHT|RF_NOCASTSHADOW);

	if (cl_thirdPerson->value)
		ent->flags &= ~RF_VIEWERMODEL;
	else if ((ent->flags & RF_VIEWERMODEL) && ent->model)
			if (ent->model->name[0] == 'p' || ent->model->name[0] == 'P')
				ent->model->flags |= RF_NOSELFSHADOW;	/// форсируем данный флаг чтобы тени модели игрока не падали на модель оружия

	ent->ownerkey = key;
	ent->alink = alink;
	r_entities[r_numentities] = *ent;

///	HACK: заменим модель спрайтом
	if (ent->model)
		if (ent->model->hash == hashLaserBolt[0] || ent->model->hash == hashLaserBolt[1] || ent->model->hash == hashLaserBolt[2])	// быстрая проверка
			if (!Q_strncasecmp(ent->model->name, "models/objects/laser/tris", 25))													// более точная проверка
				r_entities[r_numentities].model = cl_mod_laserbolt;

	r_numentities++;
}


void CL_AddViewWeapon (player_state_t *ps, player_state_t *ops)
{
	entity_t	gun;		// view model
	int			i;
	vec3_t		offset_down;
	vec3_t		offset_right;

	// allow the gun to be completely removed
	if (!cl_gun->value)
		return;

	// allow the gun to be completely removed
	if (hand->value == 2)
		return;

	// nudge gun down and right if in wide angle view
	if(cl.refdef.fov_x > 90)
	{
		if(cl_fovweap->value)
		{
			VectorScale(cl.v_up, 0.2 * (cl.refdef.fov_x - 90), offset_down);
			if (hand->value==1)
				VectorScale(cl.v_right, 0.15 * (cl.refdef.fov_x - 90), offset_right);
			else
				VectorScale(cl.v_right, -0.15 * (cl.refdef.fov_x - 90), offset_right);
		}
		else
			return;
	}
	else
		offset_down[0] = offset_down[1] = offset_down[2] = offset_right[0] = offset_right[1] = offset_right[2] = 0;

	Memset (&gun, 0, sizeof(gun));

	if (gun_model)
		gun.model = gun_model;	// development tool
	else
	{
		gun.model = cl.model_draw[ps->gunindex];
		if (hand->value == 1)
		{	/// change model to left
			if (gun.model == cl_mod_vbfg[0])
				gun.model = cl_mod_vbfg[1];
			else if (gun.model == cl_mod_vblast[0])
				gun.model = cl_mod_vblast[1];
			else if (gun.model == cl_mod_vchain[0])
				gun.model = cl_mod_vchain[1];
			else if (gun.model == cl_mod_vhandgr[0])
				gun.model = cl_mod_vhandgr[1];
			else if (gun.model == cl_mod_vhyperb[0])
				gun.model = cl_mod_vhyperb[1];
			else if (gun.model == cl_mod_vlaunch[0])
				gun.model = cl_mod_vlaunch[1];
			else if (gun.model == cl_mod_vmachn[0])
				gun.model = cl_mod_vmachn[1];
			else if (gun.model == cl_mod_vrail[0])
				gun.model = cl_mod_vrail[1];
			else if (gun.model == cl_mod_vrocket[0])
				gun.model = cl_mod_vrocket[1];
			else if (gun.model == cl_mod_vshotg[0])
				gun.model = cl_mod_vshotg[1];
			else if (gun.model == cl_mod_vshotg2[0])
				gun.model = cl_mod_vshotg2[1];
		}
	}
	if (!gun.model)
		return;

	// set up gun position
	for (i=0 ; i<3 ; i++)
	{
		gun.origin[i] = cl.refdef.vieworg[i] + ops->gunoffset[i] + cl.lerpfrac * (ps->gunoffset[i] - ops->gunoffset[i]);
		gun.angles[i] = cl.refdef.viewangles[i] + LerpAngle (ops->gunangles[i], ps->gunangles[i], cl.lerpfrac);
	}

	if (gun_frame)
	{
		gun.frame = gun_frame;	// development tool
		gun.oldframe = gun_frame;	// development tool
	}
	else
	{
		gun.frame = ps->gunframe;
		if (gun.frame == 0)
			gun.oldframe = 0;	// just changed weapons, don't lerp from old
		else
			gun.oldframe = ops->gunframe;
	}

	VectorSubtract(gun.origin, offset_down, gun.origin);
	VectorSubtract(gun.origin, offset_right, gun.origin);
	gun.flags = RF_MINLIGHT | RF_DEPTHHACK | RF_WEAPONMODEL;
gun.model->flags |= RF_NOSELFSHADOW;	/// форсируем данный флаг чтобы тени модели игрока не падали на модель оружия
	gun.backlerp = 1.0 - cl.lerpfrac;
	VectorCopy (gun.origin, gun.oldorigin);	// don't lerp at all

	centity_t *cent = &cl_entities[cl.playernum+1];						//////////////////////
	if (cent->current.effects & EF_INVIS)
	{
#ifndef DISTORT_WEAPON
		gun.flags |= RF_TRANSLUCENT;	// для руки с пушкой не будем применять RF_DISTORT
		gun.alpha = 0.3;
#else
		if (r_distort->value && gl_config.screentexture && gl_config.arb_distort && arbdistort_program!=0xffffffff)
			gun.flags |= RF_DISTORT;
		else
		{
			gun.flags |= RF_TRANSLUCENT;	// для руки с пушкой не будем применять RF_DISTORT, если он недоступен
			gun.alpha = 0.3;
		}
#endif
	}

	currententity = &gun;	// for R_AddModelLight / CheckEntityFrame...
	R_AddModelLight();

	V_AddEntity (&gun, NULL, NULL);

	if ((cent->current.effects & (EF_QUAD|EF_PENT|EF_COLOR_SHELL)) && !r_noshells->value)
	{
		VectorClear(shell_color);

		if (cent->current.effects & EF_QUAD)							// Тут мы добавляем синей SHELL если клиент взял QUAD
		{
			gun.flags |= RF_SHELL_BLUE|RF_TRANSLUCENT;
			shell_color[2] = 1;
		}

		if (cent->current.effects & EF_PENT)							// Тут мы добавляем красный SHELL если клиент взял PENTAGRAM
		{
			gun.flags |= RF_SHELL_RED|RF_TRANSLUCENT;
			shell_color[0] = 1;
		}

		// Тут мы добавляем белый SHELL если клиент in GOD mode...
		if ((cent->current.effects & EF_COLOR_SHELL) && (cent->current.renderfx & RF_SHELL_RED))
		{
			gun.flags |= RF_SHELL_RED|RF_TRANSLUCENT;
			shell_color[0] = 1;
		}
		if ((cent->current.effects & EF_COLOR_SHELL) && (cent->current.renderfx & RF_SHELL_GREEN))
		{
			gun.flags |= RF_SHELL_GREEN|RF_TRANSLUCENT;
			shell_color[1] = 1;
		}
		if ((cent->current.effects & EF_COLOR_SHELL) && (cent->current.renderfx & RF_SHELL_BLUE))
		{
			gun.flags |= RF_SHELL_BLUE|RF_TRANSLUCENT;
			shell_color[2] = 1;
		}

		V_AddEntity (&gun, NULL, NULL);
		weaponmodel_lighted = true;
	}																	//////////////////////
	else
		weaponmodel_lighted = false;
}


/*
======
vectoangles2 - this is duplicated in the game DLL, but I need it here.
======
*/
void vectoangles2 (vec3_t value1, vec3_t angles)
{
	float	forward;
	float	yaw, pitch;
	
	if (value1[1] == 0 && value1[0] == 0)
	{
		yaw = 0;
		if (value1[2] > 0)
			pitch = 90;
		else
			pitch = 270;
	}
	else
	{
	// PMM - fixed to correct for pitch of 0
		if (value1[0])
			yaw = (atan2(value1[1], value1[0]) * 180 / M_PI);
		else if (value1[1] > 0)
			yaw = 90;
		else
			yaw = 270;

		if (yaw < 0)
			yaw += 360;

		forward = Sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
		pitch = (atan2(value1[2], forward) * 180 / M_PI);
		if (pitch < 0)
			pitch += 360;
	}

	angles[PITCH] = -pitch;
	angles[YAW] = yaw;
	angles[ROLL] = 0;
}


/*
=================
anglemod

returns angle normalized to the range [0 <= angle < 360]
=================
*/
float anglemod(float a)
{
	return (360.0/65536) * ((int)(a*(65536/360.0)) & 65535);
}

/*
===============
CL_CalcViewValues

Sets cl.refdef view values
===============
*/
void CL_CalcViewValues ()
{
	int			i;
	float		lerp, backlerp;
	centity_t	*ent;
	frame_t		*oldframe;
	player_state_t	*ps, *ops;

	// find the previous frame to interpolate from
	ps = &cl.frame.playerstate;
	i = (cl.frame.serverframe - 1) & UPDATE_MASK;
	oldframe = &cl.frames[i];
	if (oldframe->serverframe != cl.frame.serverframe-1 || !oldframe->valid)
		oldframe = &cl.frame;		// previous frame was dropped or involid
	ops = &oldframe->playerstate;

	// see if the player entity was teleported this frame
	if ( fabs(float(ops->pmove.origin[0] - ps->pmove.origin[0])) > 256*8
		|| fabs(float(ops->pmove.origin[1] - ps->pmove.origin[1])) > 256*8
		|| fabs(float(ops->pmove.origin[2] - ps->pmove.origin[2])) > 256*8)
		ops = ps;		// don't interpolate

	ent = &cl_entities[cl.playernum+1];
	lerp = cl.lerpfrac;

	// calculate the origin
	if (!cl.attractloop && !ps->stats[STAT_CHASE] && (cl_predict->value) && !(cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
	{	// use predicted values
		unsigned	delta;

		backlerp = 1.0 - lerp;
		for (i=0 ; i<3 ; i++)
		{
			cl.refdef.vieworg[i] = cl.predicted_origin[i] + ops->viewoffset[i] + cl.lerpfrac * (ps->viewoffset[i] - ops->viewoffset[i]) - backlerp * cl.prediction_error[i];
		}

		// smooth out stair climbing
		delta = cls.realtime - cl.predicted_step_time;
		if (delta < 100)
			cl.refdef.vieworg[2] -= cl.predicted_step * (100 - delta) * 0.01;
	}
	else
	{	// just use interpolated values
		for (i=0 ; i<3 ; i++)
			cl.refdef.vieworg[i] = ops->pmove.origin[i]*0.125 + ops->viewoffset[i] + lerp * (ps->pmove.origin[i]*0.125 + ps->viewoffset[i] - (ops->pmove.origin[i]*0.125 + ops->viewoffset[i]) );
	}

	// if not running a demo or on a locked frame, add the local angle movement
	if ( cl.frame.playerstate.pmove.pm_type < PM_DEAD )
	{	// use predicted values
		for (i=0 ; i<3 ; i++)
			cl.refdef.viewangles[i] = cl.predicted_angles[i];
	}
	else
	{	// just use interpolated values
		if (cl.frame.playerstate.pmove.pm_type >= PM_DEAD && ops->pmove.pm_type < PM_DEAD)
		{	//r1: fix for server no longer sending viewangles every frame.
			for (i=0 ; i<3 ; i++)
				cl.refdef.viewangles[i] = LerpAngle (cl.predicted_angles[i], ps->viewangles[i], lerp);
		}
		else
		{
			for (i=0 ; i<3 ; i++)
				cl.refdef.viewangles[i] = LerpAngle (ops->viewangles[i], ps->viewangles[i], lerp);
		}
	}

	for (i=0 ; i<3 ; i++)
		cl.refdef.viewangles[i] += LerpAngle (ops->kick_angles[i], ps->kick_angles[i], lerp);

	AngleVectors (cl.refdef.viewangles, cl.v_forward, cl.v_right, cl.v_up);

	// interpolate field of view
	if (cl.attractloop && (demofov->value > 0))
		cl.refdef.fov_x = demofov->value;
	else
		cl.refdef.fov_x = ops->fov + lerp * (ps->fov - ops->fov);

	// Berserker: client-side zoom
	if ((cl.refdef.fov_x < 89.9 || cl.refdef.fov_x > 90.1) || cl.attractloop)
	{	// если FOV != 90 (не установлен флаг "Fixed FOV") или проигрывается демо
		float delta = (Sys_Milliseconds() - fov_time);	// Дельта FOV, основано на интервале времени от предыдущего до текущего клиентского кадра.
														// (Даже не пришлось умножать на scale, скорость смены FOV отличная. Сделать цвар?)
		if ( in_zoom.state & 3 )
		{	// +zoom
			float dfov = cl.refdef.fov_x - zoomfov->value;
			if (dfov > 0)
			{
				fov_delta += delta;
				if (fov_delta > dfov)
					fov_delta = dfov;
			}
		}
		else
		{	// -zoom
			fov_delta -= delta;
			if (fov_delta < 0)
				fov_delta = 0;
		}
		cl.refdef.fov_x -= fov_delta;
	}
	in_zoom.state &= ~2;
	fov_time = Sys_Milliseconds ();

	// clamp
	if (cl.refdef.fov_x < 1)
		cl.refdef.fov_x = 1;		/// 90;
	else if (cl.refdef.fov_x > MAX_FOV)
		cl.refdef.fov_x = MAX_FOV;

	// don't interpolate blend color
	for (i=0 ; i<4 ; i++)
		cl.refdef.blend[i] = ps->blend[i];

	if (cl_thirdPerson->value)
	{	/// Third person view from Quake3 cg-code
#define	FOCUS_DISTANCE	512
		vec3_t		forward, right, up;
		vec3_t		view, org;
		vec3_t		focusAngles;
		trace_t		trace;
		static vec3_t	mins = { -10, -10, -10 };	/// was -4		увеличил куб чтобы не подглядывать через стены
		static vec3_t	maxs = { 10, 10, 10 };	/// was 4
		vec3_t		focusPoint;
		float		focusDist;
		float		forwardScale, sideScale;

/// чтобы предсказание движений не мешало корректно двигаться камере!
		for (i=0 ; i<3 ; i++)
			org[i] = ops->pmove.origin[i]*0.125 + ops->viewoffset[i] + lerp * (ps->pmove.origin[i]*0.125 + ps->viewoffset[i] - (ops->pmove.origin[i]*0.125 + ops->viewoffset[i]) );

		if (cl.frame.playerstate.pmove.pm_type >= PM_DEAD && ops->pmove.pm_type < PM_DEAD)
		{	//r1: fix for server no longer sending viewangles every frame.
			for (i=0 ; i<3 ; i++)
				cl.refdef.viewangles[i] = LerpAngle (cl.predicted_angles[i], ps->viewangles[i], lerp);
		}
		else
		{
			for (i=0 ; i<3 ; i++)
				cl.refdef.viewangles[i] = LerpAngle (ops->viewangles[i], ps->viewangles[i], lerp);
		}


		if (cl_cameraOrbit->value)
			Cvar_SetValue("cl_thirdPersonAngle", anglemod(cl_cameraOrbit->value * cl.gameTime * 0.001 * 360/60));	/// при 1 вращаем камеру на 360 градусов за 1 минуту

		VectorCopy( cl.refdef.viewangles, focusAngles );

		if ( focusAngles[PITCH] > 75 )
			focusAngles[PITCH] = 75;		// don't go too far overhead

		AngleVectors( focusAngles, forward, NULL, NULL );
		VectorMA( org, FOCUS_DISTANCE, forward, focusPoint );
		VectorCopy( org, view );
		view[2] += 8;
		cl.refdef.viewangles[PITCH] *= 0.5;

		AngleVectors( cl.refdef.viewangles, forward, right, up );

		forwardScale = cos( cl_thirdPersonAngle->value / 180 * M_PI );
		sideScale = sin( cl_thirdPersonAngle->value / 180 * M_PI );
		VectorMA( view, -cl_thirdPersonRange->value * forwardScale, forward, view );
		VectorMA( view, -cl_thirdPersonRange->value * sideScale, right, view );

		// trace a ray from the origin to the viewpoint to make sure the view isn't
		// in a solid block.  Use an 8 by 8 block to prevent the view from near clipping anything

		trace = CL_PMTraceWorld( org, mins, maxs, view, MASK_SOLID );

		if ( trace.fraction != 1.0 )
		{
			VectorCopy( trace.endpos, view );
			view[2] += (1.0 - trace.fraction) * 32;
			// try another trace to this position, because a tunnel may have the ceiling
			// close enogh that this is poking out
			trace = CL_PMTraceWorld( org, mins, maxs, view, MASK_SOLID );
			VectorCopy( trace.endpos, view );
		}

		VectorCopy( view, cl.refdef.vieworg );

		// select pitch to look at focus point from vieword
		VectorSubtract( focusPoint, view, focusPoint );
		focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] );
		if ( focusDist < 1 )
			focusDist = 1;	// should never happen

		cl.refdef.viewangles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist );
		cl.refdef.viewangles[YAW] -= cl_thirdPersonAngle->value;
	}
	else
	{
		// add the weapon (обязательно тут!)
		CL_AddViewWeapon (ps, ops);
	}
}


float RandomMinMax(float min, float max)
{
	if (min == max)
		return min;

	return min+((rand()%10000)/10000.0)*(max-min);
}


int M_Credits_Key( int key )
{
	switch (key)
	{
	case K_ESCAPE:
		M_FreeCredits();
		M_PopMenu ();
		break;
	}
	return cl_sfx_menu_out_sound;
}


void M_Menu_Credits_f()
{
	int		n;
	int		count;
	char	*p;
	int		isdeveloper = 0;

	M_FreeCredits();

	count = FS_LoadFile ("credits.txt", (void**) &creditsBuffer);
	if (count > 0 && creditsBuffer)
	{
		p = creditsBuffer;
		for (n = 0; n < 255; n++)
		{
			creditsIndex[n] = p;
			while (*p != '\r' && *p != '\n')
			{
				p++;
				if (--count == 0)
					break;
			}
			if (*p == '\r')
			{
				*p++ = 0;
				if (--count == 0)
					break;
			}
			*p++ = 0;
			if (--count == 0)
				break;
		}
		creditsIndex[++n] = 0;
		credits = &creditsIndex[0];
	}
	else
		credits = berscredits;

	// start background music "credits"
	if (s_music->value)
		if (!streaming.enabled)
		{
			creditsPlay = true;
			Music_Play("music/credits");
		}

	credits_start_time = cls.realtime;
	M_PushMenu( M_Credits_MenuDraw, M_Credits_Key);
}


void CL_DiminishingTrail (vec3_t start, vec3_t end, centity_t *old, int type)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;
	float		dec;
	float		orgscale;
	float		velscale;
	float		p_scale, colors[3];
	byte		color[3], var[3];
	parttype_t	ptype;

	if (type == EF_BFG)
	{
		p_scale = 8;
		color[0] = 48;
		color[1] = 220;
		color[2] = 48;
		var[0] = 63;
		var[1] = 31;
		var[2] = 63;
		ptype = part_bigsmoke;
	}
	else if (type == EF_ROCKET)
	{
		if(!p_rocket->value)
			return;

		p_scale = 16 * (1 - ClampCvar(1.0/16.0, 15.0/16.0, p_rocket->value));
		color[0] = 220;
		color[1] = 150;
		color[2] = 100;
		var[0] = 31;
		var[1] = 63;
		var[2] = 0;
		ptype = part_smoke;
	}
	else if (type == EF_GRENADE)
	{
		if(!p_grenade->value)
			return;

		p_scale = 16 * (1 - ClampCvar(1.0/16.0, 15.0/16.0, p_grenade->value));
		color[0] = 200;
		color[1] = 200;
		color[2] = 200;
		var[0] = 15;
		var[1] = 15;
		var[2] = 15;
		ptype = part_smoke;
	}
	else if (type == EF_GREENGIB)
	{
		if(!p_gib->value)
			return;

		R_LightForPoint(start, colors);

		p_scale = 16 * (1 - ClampCvar(1.0/16.0, 15.0/16.0, p_gib->value));
		color[0] = p_greenblood_r->value * colors[0] * 200;
		color[1] = p_greenblood_g->value * colors[1] * 200;
		color[2] = p_greenblood_b->value * colors[2] * 200;
///		if (color[0] < 0)	color[0] = 0;	// color is byte
		if (color[1] < 8)	color[1] = 8;
///		if (color[2] < 0)	color[2] = 0;	// color is byte
		var[0] = 0;
		var[1] = 31;
		var[2] = 0;
		ptype = part_greenblood;
	}
	else		// EF_GIB
	{
		if(!p_gib->value)
			return;

		R_LightForPoint(start, colors);

		p_scale = 16 * (1 - ClampCvar(1.0/16.0, 15.0/16.0, p_gib->value));
		color[0] = p_blood_r->value * colors[0] * 200;
		color[1] = p_blood_g->value * colors[1] * 200;
		color[2] = p_blood_b->value * colors[2] * 200;
		if (color[0] < 8)	color[0] = 8;
///		if (color[1] < 0)	color[1] = 0;	PVS-Studio
///		if (color[2] < 0)	color[2] = 0;	PVS-Studio
		var[0] = 31;
		var[1] = 0;
		var[2] = 0;
		ptype = part_blood;
	}

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	dec = 0.5 * p_scale;
	VectorScale (vec, dec, vec);

	if (old->trailcount > 13)			/*50, 900*/
	{
		orgscale = 4;
		velscale = 15;
	}
	else if (old->trailcount > 10)		/*40, 800*/
	{
		orgscale = 2;
		velscale = 10;
	}
	else
	{
		orgscale = 1;
		velscale = 5;
	}

	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;

		// drop less particles as it flies
		if ((rand()&15/*63, 1023*/) < old->trailcount)
		{
			p = free_particles;
			free_particles = p->next;
			p->next = active_particles;
			active_particles = p;
			VectorClear (p->accel);

			p->time = cl.gameTime;
			p->flags = 0;
			p->alpha = 1.0;
			p->type = ptype;
			p->color_r0 = p->color_r1 = color[0] + (rand()&var[0]);
			p->color_g0 = p->color_g1 = color[1] + (rand()&var[1]);
			p->color_b0 = p->color_b1 = color[2] + (rand()&var[2]);

			if (ptype == part_blood || ptype == part_greenblood)
			{
				p->alphavel = -1.0 / (1+frand()*0.4);
				for (j=0 ; j<3 ; j++)
				{
					p->lastOrg[j] = p->org[j] = move[j] + crand()*orgscale;
					p->vel[j] = crand()*velscale;
					p->accel[j] = 0;
				}
				p->vel[2] -= PARTICLE_GRAVITY * 3;

				// кусок мяса марает стены/пол
				vec3_t	_mins = {-8, -8, -8};
				vec3_t	_maxs = {8, 8, 8};
				trace_t trace = CL_PMTraceWorld (start, _mins, _maxs, end, MASK_SOLID);
				if (trace.fraction > 0 && trace.fraction < 1)
				{
					float lt = r_bloodlifetime->value;
					if (lt <= 0.1 )	lt = 0.1;
					if (ptype == part_blood)
						R_SpawnDecal(trace.endpos, trace.plane.normal, blood_mrk_object[int(RandomMinMax(-0.4, 7.4)+0.5)], 16, 24, 16*lt, 24*lt, 0, 360, dt_mul, false, 0, 0, NULL, false);
					else		// part_greenblood
						R_SpawnDecal(trace.endpos, trace.plane.normal, greenblood_mrk_object[int(RandomMinMax(-0.4, 7.4)+0.5)], 16, 24, 16*lt, 24*lt, 0, 360, dt_mul, false, 0, 0, NULL, false);
				}
			}
			else
			{
				if (type == EF_BFG)
					p->alpha = 0.5;
				p->alphavel = -1.0 / (1+frand()*0.2);
				for (j=0 ; j<3 ; j++)
				{
					p->lastOrg[j] = p->org[j] = move[j] + crand()*orgscale;
					p->vel[j] = crand()*velscale;
				}
				p->accel[2] = 20;
			}
		}

///		old->trailcount -= 5;
		old->trailcount--;

		if (old->trailcount < 2/*10, 100*/)
			old->trailcount = 2/*10, 100*/;
		VectorAdd (move, vec, move);
	}
}

/*
void CL_RocketTrail (vec3_t start, vec3_t end, centity_t *old)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;
	float		dec;

	// smoke
	CL_DiminishingTrail (start, end, old, EF_ROCKET);

	// fire
	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	dec = 1;
	VectorScale (vec, dec, vec);

	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;

		if ( (rand()&7) == 0)
		{
			p = free_particles;
			free_particles = p->next;
			p->next = active_particles;
			active_particles = p;
			
			VectorClear (p->accel);
			p->flags = 0;
			p->time = cl.gameTime;

			p->alpha = 1.0;
			p->alphavel = -1.0 / (1+frand()*0.2);
			p->color_r = 220 + (rand()&15);
			p->color_g = 150 + (rand()&15);
			p->color_b = 0;
			for (j=0 ; j<3 ; j++)
			{
				p->org[j] = move[j] + crand()*5;
				p->vel[j] = crand()*20;
			}
			p->accel[2] = -PARTICLE_GRAVITY;
			p->type = part_simple;
		}
		VectorAdd (move, vec, move);
	}
}
*/

void CL_BlasterTrail (vec3_t start, vec3_t end, bool green)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);
VectorScale(vec, 0.5, vec);	/// сделаем погуще хвост в 2 раза

	// FIXME: this is a really silly way to have a loop
	while (len > 0)
	{
//		len --;
len -= 0.5;	/// сделаем погуще хвост в 2 раза

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		VectorClear (p->accel);

		p->time = cl.gameTime;
		p->flags = 0;

#if 1
		p->alpha = rand()%3;	//@tex рандомное время жизни,это позволяет некоторым частицам как бы зависать на некоторое время в воздухе,
								// хорошо бы выделять частицы с 0 аксел. по Z,и продлевать жизнь только для них.посмотрим,что получится 
		p->alphavel = -1.0 / (0.5 + frand() * 0.25);	//@tex добавил рандом,чтобы угасали с различной скоростью
#else
		p->alpha = 1.0;
		p->alphavel = -1.0 / (0.5 + frand()* 0.25);
#endif

		if(green)
		{
			p->color_r0 = 0;
			p->color_g0 = 200	+ (rand()&31);
			p->color_b0 = 0;
			p->color_r1 = 0;
			p->color_g1 = 0;
			p->color_b1 = 200	+ (rand()&31);
		}
		else
		{
			p->color_r0 = 220	+ (rand()&31);
			p->color_g0 = 150	+ (rand()&15);
			p->color_b0 = 0;
			p->color_r1 = 255;
			p->color_g1 = 0;
			p->color_b1 = 0;
		}
		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = move[j] + crand() - 2;
			p->vel[j] = crand()*8;
			p->accel[j] = 0;
		}

		p->accel[2] = -25 + (rand()%25);	//@tex рандомизируем "силу тяжести" 
		p->type = part_simple;				//@tex пробовал различные типы партиклей, если забыл - заменить на прежний

		VectorAdd (move, vec, move);
	}
}


#define	BEAMLENGTH			16
void CL_FlyParticles (vec3_t origin, int count)
{
	int			i;
	cparticle_t	*p;
	float		angle;
	float		sr, sp, sy, cr, cp, cy;
	vec3_t		forward;
	float		dist = 64;
	float		ltime;


	if (count > NUMVERTEXNORMALS)
		count = NUMVERTEXNORMALS;

	if (!avelocities[0][0])
	{
		for (i=0 ; i<NUMVERTEXNORMALS*3 ; i++)
			avelocities[0][i] = (rand()&255) * 0.01;
	}


	ltime = cl.leveltime;
	for (i=0 ; i<count ; i+=2)
	{
		angle = ltime * avelocities[i][0];
		SinCos(angle, &sy, &cy);
///		sy = sin(angle);
///		cy = cos(angle);
		angle = ltime * avelocities[i][1];
		SinCos(angle, &sp, &cp);
///		sp = sin(angle);
///		cp = cos(angle);
		angle = ltime * avelocities[i][2];
		SinCos(angle, &sr, &cr);
///		sr = sin(angle);
///		cr = cos(angle);
	
		forward[0] = cp*cy;
		forward[1] = cp*sy;
		forward[2] = -sp;

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cl.gameTime;
		p->flags = 0;

		dist = sin(ltime + i)*64;
		p->lastOrg[0] = p->org[0] = origin[0] + r_avertexnormals[i][0]*dist + forward[0]*BEAMLENGTH;
		p->lastOrg[1] = p->org[1] = origin[1] + r_avertexnormals[i][1]*dist + forward[1]*BEAMLENGTH;
		p->lastOrg[2] = p->org[2] = origin[2] + r_avertexnormals[i][2]*dist + forward[2]*BEAMLENGTH;

		VectorClear (p->vel);
		VectorClear (p->accel);

		p->color_r1 = p->color_r0 = 0;
		p->color_g1 = p->color_g0 = 0;
		p->color_b1 = p->color_b0 = 0;
///		p->colorvel = 0;

		p->alpha = 1;
		p->alphavel = -100;
		p->type = part_fly;
	}
}


void CL_FlyEffect (centity_t *ent, vec3_t origin)
{
	int		n;
	int		count;
	int		starttime;

	if (ent->fly_stoptime < cl.gameTime)
	{
		starttime = cl.gameTime;
		ent->fly_stoptime = cl.gameTime + 60000;
	}
	else
	{
		starttime = ent->fly_stoptime - 60000;
	}

	n = cl.gameTime - starttime;
	if (n < 20000)
		count = n * 162 / 20000.0;
	else
	{
		n = ent->fly_stoptime - cl.gameTime;
		if (n < 20000)
			count = n * 162 / 20000.0;
		else
			count = 162;
	}

	CL_FlyParticles (origin, count);
}


void CL_BfgParticles (entity_t *ent)
{
	int			i;
	cparticle_t	*p;
	float		angle;
	float		sr, sp, sy, cr, cp, cy;
	vec3_t		forward;
	float		dist = 64;
	vec3_t		v;
	float		ltime;
	
	if (!avelocities[0][0])
	{
		for (i=0 ; i<NUMVERTEXNORMALS*3 ; i++)
			avelocities[0][i] = (rand()&255) * 0.01;
	}

	ltime = cl.leveltime;
	for (i=0 ; i<NUMVERTEXNORMALS ; i++)
	{
		angle = ltime * avelocities[i][0];
		SinCos(angle, &sy, &cy);
///		sy = sin(angle);
///		cy = cos(angle);
		angle = ltime * avelocities[i][1];
		SinCos(angle, &sp, &cp);
///		sp = sin(angle);
///		cp = cos(angle);
		angle = ltime * avelocities[i][2];
		SinCos(angle, &sr, &cr);
///		sr = sin(angle);
///		cr = cos(angle);
	
		forward[0] = cp*cy;
		forward[1] = cp*sy;
		forward[2] = -sp;

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cl.gameTime;
		p->flags = 0;

		dist = sin(ltime + i)*64;
		p->lastOrg[0] = p->org[0] = ent->origin[0] + r_avertexnormals[i][0]*dist + forward[0]*BEAMLENGTH;
		p->lastOrg[1] = p->org[1] = ent->origin[1] + r_avertexnormals[i][1]*dist + forward[1]*BEAMLENGTH;
		p->lastOrg[2] = p->org[2] = ent->origin[2] + r_avertexnormals[i][2]*dist + forward[2]*BEAMLENGTH;

		VectorClear (p->vel);
		VectorClear (p->accel);

		VectorSubtract (p->org, ent->origin, v);
		dist = VectorLength(v) / 90.0;
		p->color_r0 = p->color_r1 = 0;
		p->color_g0 = p->color_g1 = floor (100 + dist * 7);
		p->color_b0 = p->color_b1 = 0;
///		p->colorvel = 0;

		p->alpha = 1.0 - dist;
		p->alphavel = -100;
		p->type = part_simple;
	}
}


void CL_FlagTrail (vec3_t start, vec3_t end, byte color_r, byte color_g, byte color_b)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;
	int			dec;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	dec = 5;
	VectorScale (vec, 5, vec);

	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		VectorClear (p->accel);
		
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = -1.0 / (0.8+frand()*0.2);
		p->color_r0 = p->color_r1 = color_r;
		p->color_g0 = p->color_g1 = color_g;
		p->color_b0 = p->color_b1 = color_b;
		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = move[j] + crand()*16;
			p->vel[j] = crand()*5;
			p->accel[j] = 0;
		}
		p->type = part_simple;

		VectorAdd (move, vec, move);
	}
}

void DMFlagPrint( void *self )
{
	menulist_s *f = ( menulist_s * ) self;
	char	*str = NULL;

	int flags = Cvar_VariableValue( "dmflags" );

	if ( f == &s_friendlyfire_box )
	{
		if ( f->curvalue )
			str = "You can't damage your friends";
		else
			str = "You can damage your friends";
	}
	else if ( f == &s_falls_box )
	{
		if ( f->curvalue )
			str = "Damages the player when they falling";
		else
			str = "Player don't get damage when falling";
	}
	else if ( f == &s_weapons_stay_box ) 
	{
		if ( f->curvalue )
			str = "Weapon stays when a player picks him up";
		else
			str = "Weapon removes when picked up";
	}
	else if ( f == &s_instant_powerups_box )
	{
		if ( f->curvalue )
			str = "Powerups will be used when picked it up";
		else
			str = "Able to use powerup at the right time";
	}
	else if ( f == &s_allow_exit_box )
	{
		if ( f->curvalue )
			str = "DM: Allow to exit from the map";
		else
			str = "DM: Kills player which tried to exit";
	}
	else if ( f == &s_powerups_box )
	{
		if ( f->curvalue )
			str = "Allows players to use powerups";
		else
			str = "Disable powerup items";
	}
	else if ( f == &s_health_box )
	{
		if ( f->curvalue )
			str = "Allows players to use healing items";
		else
			str = "Disable healing items";
	}
	else if ( f == &s_spawn_farthest_box )
	{
		if ( f->curvalue )
			str = "Spawn players far away from death place";
		else
			str = "Spawn players on the random place";
	}
	else if ( f == &s_teamplay_box )
	{
		if ( f->curvalue == 1 )
			str = "Team: players with equal skins";
		else if ( f->curvalue == 2 )
			str = "Team: players with equal models";
		else
			str = "No team play";
	}
	else if ( f == &s_samelevel_box )
	{
		if ( f->curvalue )
			str = "DM: Exit with return to same map";
		else
			str = "DM: Exit to the next map";
	}
	else if ( f == &s_force_respawn_box )
	{
		if ( f->curvalue )
			str = "Forces the player to respawn after death";
		else
			str = "Allow player to be dead";
	}
	else if ( f == &s_armor_box )
	{
		if ( f->curvalue )
			str = "Allows players to pick up armors";
		else
			str = "Disable armors";
	}
	else if ( f == &s_infinite_ammo_box )
	{
		if ( f->curvalue )
			str = "All players have infinite ammo";
		else
			str = "Players must pick up ammo for use";
	}
	else if ( f == &s_fixed_fov_box )
	{
		if ( f->curvalue )
			str = "Fixed FOV (Field of Vision)";
		else
			str = "Players are allowed to change their FOV";
	}
	else if ( f == &s_quad_drop_box )
	{
		if ( f->curvalue )
			str = "Drop the quad on death";
		else
			str = "No drop the quad on death";
	}
	else if ( f == &s_drop_weapon_box )
	{
		if ( f->curvalue )
			str = "Drop the weapon on death (Q2 style)";
		else
			str = "Drop the pack on death (Quake style)";
	}
	else if ( f == &s_flashlight_box )
	{
		if ( f->curvalue )
			str = "DM: Enable flashlight";
		else
			str = "DM: Disable flashlight";
	}
	else if ( f == &s_footprint_box )
	{
		if ( f->curvalue )
			str = "Enable wet footprints";
		else
			str = "Disable wet footprints";
	}
	else if ( f == &s_fastrocket_box )
	{
		if ( f->curvalue )
			str = "Fast rockets (Quake style)";
		else
			str = "Slow rockets (Quake2 style)";
	}
	else if ( f == &s_haste_box )
	{
		if ( f->curvalue )
			str = "Haste mode: speed-up shots";
		else
			str = "Normal speed of shots";
	}
	else if ( f == &s_shell_box )
	{
		if ( f->curvalue )
			str = "Red splash when getting damage";
		else
			str = "No red splash when getting damage";
	}
	else if ( f == &s_flashlightcells_box )
	{
		if ( f->curvalue )
			str = "A flashlight feeds from cells";
		else
			str = "Infinity flashlight";
	}
	else if ( f == &s_doublejump_box )
	{
		if ( f->curvalue )
			str = "Double jump enabled (Doom 2016 style)";
		else
			str = "No double jump";
	}
	else if (f == &s_godlight_box)
	{
		if (f->curvalue)
			str = "Draw light and shell for God";
		else
			str = "Gods as normal players";
	}
	else if (f == &s_instagib_box)
	{
		if (f->curvalue)
			str = "DM: Instant death from RL, GL and RailGun";
		else
			str = "DM: Normal damage...";
	}



	if (str)
		Menu_DrawStatusBar(str);
}

void DMFlagCallback( void *self )
{
	menulist_s *f = ( menulist_s * ) self;
	int		flags;
	int		bit = 0;

	flags = Cvar_VariableValue( "dmflags" );

	if ( f == &s_friendlyfire_box )
	{
		if ( f->curvalue )
			flags &= ~DF_NO_FRIENDLY_FIRE;
		else
			flags |= DF_NO_FRIENDLY_FIRE;
		goto setvalue;
	}
	else if ( f == &s_falls_box )
	{
		if ( f->curvalue )
			flags &= ~DF_NO_FALLING;
		else
			flags |= DF_NO_FALLING;
		goto setvalue;
	}
	else if ( f == &s_weapons_stay_box ) 
		bit = DF_WEAPONS_STAY;
	else if ( f == &s_instant_powerups_box )
		bit = DF_INSTANT_ITEMS;
	else if ( f == &s_allow_exit_box )
		bit = DF_ALLOW_EXIT;
	else if ( f == &s_powerups_box )
	{
		if ( f->curvalue )
			flags &= ~DF_NO_ITEMS;
		else
			flags |= DF_NO_ITEMS;
		goto setvalue;
	}
	else if ( f == &s_health_box )
	{
		if ( f->curvalue )
			flags &= ~DF_NO_HEALTH;
		else
			flags |= DF_NO_HEALTH;
		goto setvalue;
	}
	else if ( f == &s_spawn_farthest_box )
		bit = DF_SPAWN_FARTHEST;
	else if ( f == &s_teamplay_box )
	{
		if ( f->curvalue == 1 )
		{
			flags |=  DF_SKINTEAMS;
			flags &= ~DF_MODELTEAMS;
		}
		else if ( f->curvalue == 2 )
		{
			flags |=  DF_MODELTEAMS;
			flags &= ~DF_SKINTEAMS;
		}
		else
			flags &= ~( DF_MODELTEAMS | DF_SKINTEAMS );
		goto setvalue;
	}
	else if ( f == &s_samelevel_box )
		bit = DF_SAME_LEVEL;
	else if ( f == &s_force_respawn_box )
		bit = DF_FORCE_RESPAWN;
	else if ( f == &s_armor_box )
	{
		if ( f->curvalue )
			flags &= ~DF_NO_ARMOR;
		else
			flags |= DF_NO_ARMOR;
		goto setvalue;
	}
	else if ( f == &s_infinite_ammo_box )
		bit = DF_INFINITE_AMMO;
	else if ( f == &s_fixed_fov_box )
		bit = DF_FIXED_FOV;
	else if ( f == &s_quad_drop_box )
		bit = DF_QUAD_DROP;
	else if ( f == &s_drop_weapon_box )
		bit = DF_DROP_WEAPON;
	else if ( f == &s_flashlight_box )
		bit = DF_FLASHLIGHT;
	else if ( f == &s_footprint_box )
		bit = DF_FOOTPRINT;
	else if ( f == &s_fastrocket_box )
		bit = DF_FASTROCKET;
	else if ( f == &s_haste_box )
		bit = DF_HASTE;
	else if ( f == &s_shell_box )
		bit = DF_SHELL;
	else if ( f == &s_flashlightcells_box )
		bit = DF_FLASHLIGHTCELLS;
	else if ( f == &s_doublejump_box )
		bit = DF_UT_DOUBLE_JUMP;
	else if (f == &s_godlight_box)
	{
		if (f->curvalue)
			flags &= ~DF_NOGODLIGHT;
		else
			flags |= DF_NOGODLIGHT;
		goto setvalue;
	}
	else if (f == &s_instagib_box)
	{
		if (!f->curvalue)
			flags &= ~DF_INSTAGIB;
		else
			flags |= DF_INSTAGIB;
		goto setvalue;
	}

	if ( f )
	{
		if ( f->curvalue == 0 )
			flags &= ~bit;
		else
			flags |= bit;
	}

setvalue:
	Cvar_SetValue ("dmflags", flags);
}


void V_AddLight (vec3_t org, float radius, float r, float g, float b, int filtercube_start, int filtercube_end, float framerate, float _cone, float a0, float a1, float a2, int ownerkey, byte style, byte cl_style, rend_t rend)
{
	dlight_t	*dl;

	if (r_numdlights >= MAX_DLIGHTS)
		return;
	dl = &r_dlights[r_numdlights++];
	VectorCopy (org, dl->origin);
	dl->radius = radius;
	dl->color[0] = r;
	dl->color[1] = g;
	dl->color[2] = b;
	dl->ownerkey = ownerkey;
	dl->filtercube_start = filtercube_start;
	dl->filtercube_end = filtercube_end;
	dl->framerate = framerate;
	dl->_cone = _cone;
	VectorSet(dl->angles, a0,a1,a2);
	dl->style = style;
	dl->cl_style = cl_style;
	dl->render_to = rend;
}


int VectorCompare (vec3_t v1, vec3_t v2)
{
	if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2])
			return 0;
			
	return 1;
}


// RAFAEL
/*
===============
CL_IonripperTrail
===============
*/
void CL_IonripperTrail (vec3_t start, vec3_t ent)
{
	vec3_t	move;
	vec3_t	vec;
	float	len;
	int		j;
	cparticle_t *p;
	int		dec;
	int     left = 0;

	VectorCopy (start, move);
	VectorSubtract (ent, start, vec);
	len = VectorNormalize (vec);

	dec = 5;
	VectorScale (vec, 5, vec);

	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		VectorClear (p->accel);

		p->flags = 0;
		p->time = cl.gameTime;
		p->alpha = 0.5;
		p->alphavel = -1.0 / (0.3 + frand() * 0.2);
///	p->color = 0xe4 + (rand()&3);
		p->color_r0 = p->color_r1 = (0xe4 & 0xC0) + (rand()&3);
		p->color_g0 = p->color_g1 = ((0xe4 << 3) & 0xC0) + (rand()&3);
		p->color_b0 = p->color_b1 = ((0xe4 << 6) & 0xC0) + (rand()&3);

		for (j=0; j<3; j++)
		{
			p->lastOrg[j] = p->org[j] = move[j];
			p->accel[j] = 0;
		}
		if (left)
		{
			left = 0;
			p->vel[0] = 10;
		}
		else 
		{
			left = 1;
			p->vel[0] = -10;
		}

		p->vel[1] = 0;
		p->vel[2] = 0;
		p->type = part_simple;

		VectorAdd (move, vec, move);
	}
}


/*
===============
CL_TrapParticles
===============
*/
// RAFAEL
void CL_TrapParticles (entity_t *ent)
{
	vec3_t		move;
	vec3_t		vec;
	vec3_t		start, end;
	float		len;
	int			j;
	cparticle_t	*p;
	int			dec;

	ent->origin[2]-=14;
	VectorCopy (ent->origin, start);
	VectorCopy (ent->origin, end);
	end[2]+=64;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	dec = 5;
	VectorScale (vec, 5, vec);

	// FIXME: this is a really silly way to have a loop
	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		VectorClear (p->accel);
		
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = -1.0 / (0.3+frand()*0.2);
///	p->color = 0xe0;
		p->color_r0 = p->color_r1 = (0xe0 & 0xC0);
		p->color_g0 = p->color_g1 = ((0xe0 << 3) & 0xC0);
		p->color_b0 = p->color_b1 = ((0xe0 << 6) & 0xC0);
		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = move[j] + crand();
			p->vel[j] = crand()*15;
			p->accel[j] = 0;
		}
		p->accel[2] = PARTICLE_GRAVITY;
		p->type = part_simple;

		VectorAdd (move, vec, move);
	}

	{
	int			i, j, k;
	cparticle_t	*p;
	float		vel;
	vec3_t		dir;
	vec3_t		org;

	ent->origin[2]+=14;
	VectorCopy (ent->origin, org);

	for (i=-2 ; i<=2 ; i+=4)
		for (j=-2 ; j<=2 ; j+=4)
			for (k=-2 ; k<=4 ; k+=4)
			{
				if (!free_particles)
					return;
				p = free_particles;
				free_particles = p->next;
				p->next = active_particles;
				active_particles = p;

				p->flags = 0;
				p->time = cl.gameTime;
///			p->color = 0xe0 + (rand()&3);
				p->color_r0 = p->color_r1 = (0xe0 & 0xC0) + (rand()&3);
				p->color_g0 = p->color_g1 = ((0xe0 << 3) & 0xC0) + (rand()&3);
				p->color_b0 = p->color_b1 = ((0xe0 << 6) & 0xC0) + (rand()&3);

				p->alpha = 1.0;
				p->alphavel = -1.0 / (0.3 + (rand()&7) * 0.02);
				
				p->lastOrg[0] = p->org[0] = org[0] + i + ((rand()&23) * crand());
				p->lastOrg[1] = p->org[1] = org[1] + j + ((rand()&23) * crand());
				p->lastOrg[2] = p->org[2] = org[2] + k + ((rand()&23) * crand());
	
				dir[0] = j * 8;
				dir[1] = i * 8;
				dir[2] = k * 8;
	
				VectorNormalize (dir);						
				vel = 50 + rand()&63;
				VectorScale (dir, vel, p->vel);
				p->type = part_simple;

				p->accel[0] = p->accel[1] = 0;
				p->accel[2] = -PARTICLE_GRAVITY;
			}
	}
}


/*
===============
CL_TrackerTrail
===============
*/
void CL_TrackerTrail (vec3_t start, vec3_t end, int particleColor)
{
	vec3_t		move;
	vec3_t		vec;
	vec3_t		forward/*,right*/,up,angle_dir;
	float		len;
	int			j;
	cparticle_t	*p;
	int			dec;
	float		dist;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	VectorCopy(vec, forward);
	vectoangles2 (forward, angle_dir);
	AngleVectors (angle_dir, forward, NULL/*right*/, up);

	dec = 3;
	VectorScale (vec, 3, vec);

	// FIXME: this is a really silly way to have a loop
	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		VectorClear (p->accel);
		
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = -2.0;
///	p->color = particleColor;
		p->color_r0 = p->color_r1 = (particleColor & 0xC0);
		p->color_g0 = p->color_g1 = ((particleColor << 3) & 0xC0);
		p->color_b0 = p->color_b1 = ((particleColor << 6) & 0xC0);
		dist = DotProduct(move, forward);
		VectorMA(move, 8 * cos(dist), up, p->org);
		VectorCopy(p->org, p->lastOrg);
		for (j=0 ; j<3 ; j++)
		{
//			p->org[j] = move[j] + crand();
			p->vel[j] = 0;
			p->accel[j] = 0;
		}
		p->vel[2] = 5;
		p->type = part_simple;

		VectorAdd (move, vec, move);
	}
}


/*
===============
CL_TagTrail

===============
*/
void CL_TagTrail (vec3_t start, vec3_t end, int color)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;
	int			dec;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	dec = 5;
	VectorScale (vec, 5, vec);

	while (len >= 0)
	{
		len -= dec;

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		VectorClear (p->accel);
		
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = -1.0 / (0.8+frand()*0.2);
///	p->color = color;
		p->color_r0 = p->color_r1 = (color & 0xC0);
		p->color_g0 = p->color_g1 = ((color << 3) & 0xC0);
		p->color_b0 = p->color_b1 = ((color << 6) & 0xC0);
		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = move[j] + crand()*16;
			p->vel[j] = crand()*5;
			p->accel[j] = 0;
		}
		p->type = part_simple;

		VectorAdd (move, vec, move);
	}
}


void CL_Tracker_Shell(vec3_t origin)
{
	vec3_t			dir;
	int				i;
	cparticle_t		*p;

	for(i=0;i<300;i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		VectorClear (p->accel);
		
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = INSTANT_PARTICLE;
		p->color_r0 = p->color_r1 = 0;
		p->color_g0 = p->color_g1 = 0;
		p->color_b0 = p->color_b1 = 0;

		dir[0] = crand();
		dir[1] = crand();
		dir[2] = crand();
		VectorNormalize(dir);
		p->type = part_simple;

		VectorMA(origin, 40, dir, p->org);
		VectorCopy(p->org, p->lastOrg);
	}
}


void AddAliasModel (alink_t *alias, blink_t *brush, vec3_t neworg, vec3_t newang)
{
	int			i;
	entity_t	ent;
	vec3_t		org, temp, tempang;
	vec3_t		forward, right, up;

	Memset (&ent, 0, sizeof(ent));

	ent.model = alias->mdl;
	if(!ent.model)
		return;

	VectorSubtract(alias->origin, brush->origin, org);
	VectorNegate(newang, tempang);

	if(tempang[0] || tempang[1] || tempang[2])
	{
		AngleVectors (tempang, forward, right, up);
		VectorCopy (org, temp);
		org[0] = DotProduct (temp, forward);
		org[1] = -DotProduct (temp, right);
		org[2] = DotProduct (temp, up);
	}

	VectorAdd(alias->angles, newang, ent.angles);
	VectorAdd(org, neworg, ent.origin);

	/// Berserker: Проверяем видимость линкованной модели
	alias->nouse = true;
	vec3_t	bak_orgs;
	VectorCopy(alias->origin, bak_orgs);
	VectorCopy(ent.origin, alias->origin);
	bool result = R_MarkAliasLeaves(alias);
	VectorCopy(bak_orgs, alias->origin);
	if (result)
	{
		for (i=0; i<9; i++)
			if (cl.frame.areabits[alias->areas[i]>>3] & (1<<(alias->areas[i]&7)))
				break;

		if (i<9)
		{
			if (HasSharedLeafs (alias->vis, viewvis__))
			{
				if (alias->animframe)
				{
					if (ent.model->numframes <= 1)
					{
						ent.frame = 0;
						ent.oldframe = 0;
					}
					else
					{
						if (alias->frame < alias->frame_end)
						{
							ent.frame = cl.leveltime * 10;
							ent.frame = (alias->frame + ent.frame % (alias->frame_end - alias->frame + 1)) % ent.model->numframes;
							if (ent.frame==alias->frame)
								ent.oldframe = alias->frame_end % (ent.model->numframes-1);
							else
								ent.oldframe = ent.frame - 1;
						}
						else
						{
							if (alias->frame == alias->frame_end && alias->frame_end)
								goto noanim;
							ent.flags = RF_ANIMFRAME;		// auto animation
							ent.frame = cl.time*0.01;
							ent.frame = ent.frame % ent.model->numframes;
							if (ent.frame==0)
								ent.oldframe = ent.model->numframes - 1;
							else
								ent.oldframe = ent.frame - 1;
						}
					}
					ent.backlerp = 1 - cl.lerpfrac;
				}
				else
				{
noanim:				ent.frame = alias->frame;
					ent.backlerp = 0;
				}
				if (ent.frame >= ent.model->numframes)
					ent.frame = ent.model->numframes - 1;
				ent.skinnum = alias->skinnum;

				// Копируем некоторые флаги model во флаги entity
				if (ent.model->type == mod_alias || ent.model->type == mod_alias_md3)
				{
					VectorCopy(ent.origin, ent.oldorigin);
					// add model lights
					currententity = &ent;	// for R_AddModelLight / CheckEntityFrame...
					R_AddModelLight();
					// add entity
					if (ent.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE))
					{
						int bak = ent.flags;
						ent.flags = (ent.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE));
						V_AddEntity (&ent, NULL, alias);
						ent.flags = bak;
					}
					ent.flags |= (ent.model->flags & (RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|RF_LIGHT|RF_FULLBRIGHT|RF_DISTORT|RF_NOCASTSHADOW|RF_TRANSLUCENT));
					V_AddEntity (&ent, NULL, alias);
				}
				else
				{
					// Копируем некоторые флаги model во флаги entity
					ent.flags |= (ent.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE|RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|RF_LIGHT|RF_FULLBRIGHT|RF_DISTORT|RF_NOCASTSHADOW|RF_TRANSLUCENT));
					VectorCopy(ent.origin, ent.oldorigin);
					// add model lights
					currententity = &ent;	// for R_AddModelLight / CheckEntityFrame...
					R_AddModelLight();
					// add entity
					V_AddEntity (&ent, NULL, alias);
				}

				VectorCopy(ent.origin, alias->real_origin);
				alias->nouse = false;
			}
		}
	}
}


void AddLight (llink_t *light, blink_t *brush, vec3_t neworg, vec3_t newang)
{
	vec3_t	org, temp, tempang;
	vec3_t	forward, right, up;

///	if(!light->use)
///		return;

	VectorSubtract(light->origin, brush->origin, org);
	VectorNegate(newang, tempang);

	if(tempang[0] || tempang[1] || tempang[2])
	{
		AngleVectors (tempang, forward, right, up);
		VectorCopy (org, temp);
		org[0] = DotProduct (temp, forward);
		org[1] = -DotProduct (temp, right);
		org[2] = DotProduct (temp, up);
	}

	VectorAdd(light->angles, newang, tempang);
	VectorAdd(org, neworg, org);

	V_AddLight (org, light->radius, light->color[0], light->color[1], light->color[2], light->filtercube_start, light->filtercube_end, light->framerate, light->_cone, tempang[0], tempang[1], tempang[2], NULL, light->style, light->cl_style, rend_all);
}


void MoveEmit (emit_t *emit, blink_t *brush, vec3_t neworg, vec3_t newang)
{
	vec3_t	org, temp, tempang;
	vec3_t	forward, right, up;

	VectorSubtract(emit->origin0, brush->origin, org);
	VectorNegate(newang, tempang);

	if(tempang[0] || tempang[1] || tempang[2])
	{
		AngleVectors (tempang, forward, right, up);

		VectorCopy (org, temp);
		org[0] = DotProduct (temp, forward);
		org[1] = -DotProduct (temp, right);
		org[2] = DotProduct (temp, up);

		emit->dir[0] = DotProduct (emit->dir0, forward);
		emit->dir[1] = -DotProduct (emit->dir0, right);
		emit->dir[2] = DotProduct (emit->dir0, up);
	}
	VectorAdd(org, neworg, emit->origin);
	emit->area = CM_LeafArea (CM_PointLeafnum (emit->origin));
	emit->framecount = r_framecount;
}


void CreateNormal(vec3_t dst, vec3_t xyz0, vec3_t xyz1, vec3_t xyz2)
{
	float x10,y10,z10;
	float x20,y20,z20;

	x10=xyz1[0]-xyz0[0];
	x20=xyz2[0]-xyz0[0];
	y10=xyz1[1]-xyz0[1];
	y20=xyz2[1]-xyz0[1];
	z10=xyz1[2]-xyz0[2];
	z20=xyz2[2]-xyz0[2];
	dst[0]=y10*z20-y20*z10;
	dst[1]=z10*x20-z20*x10;
	dst[2]=x10*y20-x20*y10;
	VectorNormalize(dst);
}


// Вычислить центральную точку org и нормаль треугольника dir
void R_GetVertexOrigin(entity_t *ent, int i, vec3_t org, vec3_t dir, bool emits)
{
	vec3_t	vectors[3];
	int		j, index_xyz;
	model_t *mod = ent->model;

	if (mod->type == mod_alias)
	{
		vec3_t	move;
		vec3_t	frontv, backv;
		dtrivertx_t	*v;
		dtrivertx_t	*ov;
		dmdl_t	*paliashdr = (dmdl_t *)mod->extradata;
		CheckEntityFrame(paliashdr);
		daliasframe_t	*frame = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + ent->frame * paliashdr->framesize);
		daliasframe_t	*oldframe = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + ent->oldframe * paliashdr->framesize);
		dtriangle_t	*tris = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);
		float	backlerp = ent->backlerp;
		float	frontlerp = 1.0 - backlerp;
		// move should be the delta back to the previous frame * backlerp
		VectorSubtract (ent->oldorigin, ent->origin, move);
		if (ent->angles[0] || ent->angles[1] || ent->angles[2])
		{
			vec3_t	temp;
			VectorCopy(move, temp);
			AngleVectors (ent->angles, vectors[0], vectors[1], vectors[2]);
			move[0] = DotProduct (temp, vectors[0]);
			move[1] = -DotProduct (temp, vectors[1]);
			move[2] = DotProduct (temp, vectors[2]);
		}
		VectorAdd (move, oldframe->translate, move);
		for (j=0 ; j<3 ; j++)
		{
			move[j] = backlerp*move[j] + frontlerp*frame->translate[j];
			frontv[j] = frontlerp*frame->scale[j];
			backv[j] = backlerp*oldframe->scale[j];
		}

		for (j=0; j<3; j++)
		{
			if (emits)
				index_xyz = tris[mod->mod_emits[i].tri].index_xyz[j];
			else
				index_xyz = tris[mod->mod_lights[i].tri].index_xyz[j];
			v = frame->verts + index_xyz;
			ov = oldframe->verts + index_xyz;
			VectorSet (vectors[j], 
				move[0] + ov->v[0]*backv[0] + v->v[0]*frontv[0],
				move[1] + ov->v[1]*backv[1] + v->v[1]*frontv[1],
				move[2] + ov->v[2]*backv[2] + v->v[2]*frontv[2]);
		}
	}
	else
	{
		vec3_t	move;
		maliasvertex_t	*v;
		maliasvertex_t	*ov;
		maliasmesh_t	*mesh;
		maliasmodel_t	*paliashdr = (maliasmodel_t *)mod->extradata;
		CheckEntityFrameMD3(paliashdr);
		if (emits)
			mesh = &paliashdr->meshes[mod->mod_emits[i].mesh];
		else
			mesh = &paliashdr->meshes[mod->mod_lights[i].mesh];
		float	backlerp = ent->backlerp;
		float	frontlerp = 1.0 - backlerp;
		maliasframe_t	*frame = paliashdr->frames + ent->frame;
		maliasframe_t	*oldframe = paliashdr->frames + ent->oldframe;
		VectorSubtract (ent->oldorigin, ent->origin, move);
		if (ent->angles[0] || ent->angles[1] || ent->angles[2])
		{
			vec3_t	temp;
			VectorCopy(move, temp);
			AngleVectors (ent->angles, vectors[0], vectors[1], vectors[2]);
			move[0] = DotProduct (temp, vectors[0]);	// forward
			move[1] = -DotProduct (temp, vectors[1]);	// left
			move[2] = DotProduct (temp, vectors[2]);	// up
		}
		VectorAdd (move, oldframe->translate, move);
		for (j=0 ; j<3 ; j++)
			move[j] = backlerp*move[j] + frontlerp*frame->translate[j];

		for (j=0 ; j<3 ; j++)
		{
			if (emits)
				index_xyz = mesh->indexes[mod->mod_emits[i].tri*3+j];
			else
				index_xyz = mesh->indexes[mod->mod_lights[i].tri*3+j];
			v = mesh->vertexes + ent->frame*mesh->num_verts + index_xyz;
			ov = mesh->vertexes + ent->oldframe*mesh->num_verts + index_xyz;
			VectorSet (vectors[j], 
				move[0] + ov->point[0]*backlerp + v->point[0]*frontlerp,
				move[1] + ov->point[1]*backlerp + v->point[1]*frontlerp,
				move[2] + ov->point[2]*backlerp + v->point[2]*frontlerp);
		}
	}
	org[0] = (vectors[0][0] + vectors[1][0] + vectors[2][0])*0.3333333;
	org[1] = (vectors[0][1] + vectors[1][1] + vectors[2][1])*0.3333333;
	org[2] = (vectors[0][2] + vectors[1][2] + vectors[2][2])*0.3333333;

	CreateNormal(dir, vectors[2], vectors[1], vectors[0]);
}


void PointRotate(vec3_t angles, vec3_t org)
{
	float	a, b, c;
	float	sa, ca, sb, cb, sc, cc;
	float	xa, yc, zc;

	a = DEG2RAD(angles[0]);
	b = DEG2RAD(angles[1]);
	c = DEG2RAD(angles[2]);

	SinCos(a, &sa, &ca);
	SinCos(b, &sb, &cb);
	SinCos(c, &sc, &cc);
///	sa = sin(a);
///	ca = cos(a);
///	sb = sin(b);
///	cb = cos(b);
///	sc = sin(c);
///	cc = cos(c);

	yc = org[1]*cc-org[2]*sc;
	zc = org[1]*sc+org[2]*cc;
	xa = org[0]*ca+zc*sa;
	org[2] = -org[0]*sa+zc*ca;
	org[0] = xa*cb-yc*sb;
	org[1] = xa*sb+yc*cb;
}


void R_AddModelLight()
{
	int i;
	vec3_t	org, tempang, normal;
	vec3_t	temp;
	rend_t	rend;
	bool	add;

	if (currententity->model->type != mod_alias && currententity->model->type != mod_alias_md3)
		return;

	if (currententity->flags & (RF_DISTORT|RF_TRANSLUCENT))
		return;		// прозрачные модели не могут светиться

	unsigned flags = r_modlights->value;
	if (flags & 3)
		for (i=0; i<MAX_MODEL_LIGHTS; i++)
		{
			if (currententity->model->mod_lights[i].mesh != -1)
			{
				if (currententity->model->type == mod_alias)
				{
					if (currententity->model->mod_lights[i].skinbits == 0)
						add = true;
					else
					{
						if (currententity->skin)	// custom player skin
							add = true;
						else
						{
							if (currententity->skinnum >= MAX_MD2SKINS)
								add = true;
							else
								add = (currententity->model->mod_lights[i].skinbits & (unsigned)pow((double)2,currententity->skinnum));
						}
					}
				}
				else
					add = true;

				if (add)
					if (currententity->frame < currententity->model->mod_lights[i].frame_start || currententity->frame > currententity->model->mod_lights[i].frame_end)
						add = false;

				if (add)
				{
					if (flags!=3)
					{
						int b0, b1;
						b0 = Q_strncasecmp(currententity->model->name, "players", 7);
						b1 = Q_strncasecmp(currententity->model->name, "models/weapons", 14);
						if (flags==1 && b0 && b1)		// Player's models or player's weapon models
							continue;
						if (flags==2 && (!b0 || !b1))	// All other models
							continue;
					}

					R_GetVertexOrigin(currententity, i, org, normal, false);
					VectorMA(org, currententity->model->mod_lights[i].distance * currententity->model->scale, normal, org);
					if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
						PointRotate(currententity->angles, org);
					VectorAdd(org, currententity->origin, org);
					vectoangles2 (normal, temp);
					VectorAdd(temp, currententity->angles, tempang);

					VectorMA(currententity->model->mod_lights[i].angles, cl.leveltime, currententity->model->mod_lights[i].rspeed, temp);
					VectorAdd(tempang, temp, tempang);

					if (!Q_strncasecmp(currententity->model->name, "players", 7) && (strstr(currententity->model->name, "w_") || strstr(currententity->model->name, "a_")))
							rend = rend_mirror;
					else if (currententity->flags & RF_WEAPONMODEL)
							rend = rend_no_mirror;
					else
							rend = rend_all;

					V_AddLight(org, currententity->model->mod_lights[i].radius * currententity->model->scale, currententity->model->mod_lights[i].color[0], currententity->model->mod_lights[i].color[1], currententity->model->mod_lights[i].color[2], currententity->model->mod_lights[i].filtercube_start, currententity->model->mod_lights[i].filtercube_end, currententity->model->mod_lights[i].framerate, currententity->model->mod_lights[i]._cone, tempang[0], tempang[1], tempang[2], NULL, currententity->model->mod_lights[i].style, currententity->model->mod_lights[i].cl_style, rend);
				}
			}
		}

	if (!draw_emits)
		return;

	if (!r_modemits->value)
		return;

	// не добавляем дым для любого клиентского оружия (imho незачем...)
	if (!Q_strncasecmp(currententity->model->name, "players", 7) && (strstr(currententity->model->name, "w_") || strstr(currententity->model->name, "a_")))
		return;
	else if (currententity->flags & RF_WEAPONMODEL)
		return;

	for (i=0; i<MAX_MODEL_EMITS; i++)
	{
		if (currententity->model->mod_emits[i].mesh != -1)
		{
			if (currententity->model->type == mod_alias)
			{
				if (currententity->model->mod_emits[i].skinbits == 0)
					add = true;
				else
				{
					if (currententity->skin)	// custom player skin
						add = true;
					else
					{
						if (currententity->skinnum >= MAX_MD2SKINS)
							add = true;
						else
							add = (currententity->model->mod_emits[i].skinbits & (unsigned)pow((double)2,currententity->skinnum));
					}
				}
			}
			else
				add = true;

			if (add)
				if (currententity->frame < currententity->model->mod_emits[i].frame_start || currententity->frame > currententity->model->mod_emits[i].frame_end)
					add = false;

			if (!add)
				continue;

			int				n;
			clightstyle_t	*ls, *cl_ls;
			ls = &sv_lightstyle[currententity->model->mod_emits[i].style];
			cl_ls = &cl_lightstyle[currententity->model->mod_emits[i].cl_style];
			/// Кол-во партиклей
			n = currententity->model->mod_emits[i].numemits * ls->val * cl_ls->val;
			if (n)
			{
				R_GetVertexOrigin(currententity, i, org, normal, true);
				VectorMA(org, currententity->model->mod_emits[i].distance * currententity->model->scale, normal, org);
				VectorMA(org, 1, normal, temp);
				if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
				{
					PointRotate(currententity->angles, org);
					PointRotate(currententity->angles, temp);
				}
				VectorSubtract(temp, org, temp);
				VectorNormalize(temp);
				VectorAdd(org, currententity->origin, org);

				switch (currententity->model->mod_emits[i].emit)
				{
					case part_blood:
					case part_fast_blood:
						if (!VectorCompare(currententity->model->mod_emits[i].startcolor, vec3_origin))
							CL_EmitParticles(org, temp, currententity->model->mod_emits[i].startcolor[0]*255, currententity->model->mod_emits[i].startcolor[1]*255, currententity->model->mod_emits[i].startcolor[2]*255, currententity->model->mod_emits[i].endcolor[0]*255, currententity->model->mod_emits[i].endcolor[1]*255, currententity->model->mod_emits[i].endcolor[2]*255, n, currententity->model->mod_emits[i].emit, currententity->model->mod_emits[i].gravity ? emits[i].gravity*20 : PARTICLE_GRAVITY*4, currententity->model->mod_emits[i].vel, currententity->model->mod_emits[i].cone, 0, currententity->model->mod_emits[i].alphavel);
						else
							CL_EmitParticles(org, temp, 80,0,0, 80,0,0, n, currententity->model->mod_emits[i].emit, currententity->model->mod_emits[i].gravity ? currententity->model->mod_emits[i].gravity*20 : PARTICLE_GRAVITY*4, currententity->model->mod_emits[i].vel, currententity->model->mod_emits[i].cone, 0, currententity->model->mod_emits[i].alphavel);
						break;
					case part_smoke:
					case part_bigsmoke:
						if (!VectorCompare(currententity->model->mod_emits[i].startcolor, vec3_origin))
							CL_EmitParticles(org, temp, currententity->model->mod_emits[i].startcolor[0]*255,currententity->model->mod_emits[i].startcolor[1]*255,currententity->model->mod_emits[i].startcolor[2]*255, currententity->model->mod_emits[i].endcolor[0]*255,currententity->model->mod_emits[i].endcolor[1]*255,currententity->model->mod_emits[i].endcolor[2]*255, n, currententity->model->mod_emits[i].emit, currententity->model->mod_emits[i].gravity ? currententity->model->mod_emits[i].gravity*20 : -PARTICLE_GRAVITY*2, currententity->model->mod_emits[i].vel, currententity->model->mod_emits[i].cone, 0, currententity->model->mod_emits[i].alphavel);
						else
							CL_EmitParticles(org, temp, 200,200,200, 128,128,128, n, currententity->model->mod_emits[i].emit, currententity->model->mod_emits[i].gravity ? currententity->model->mod_emits[i].gravity*20 : -PARTICLE_GRAVITY*2, currententity->model->mod_emits[i].vel, currententity->model->mod_emits[i].cone, 0, currententity->model->mod_emits[i].alphavel);
						break;
					case part_splash:
						if (!VectorCompare(currententity->model->mod_emits[i].startcolor, vec3_origin))
							CL_EmitParticles(org, temp, currententity->model->mod_emits[i].startcolor[0]*255,currententity->model->mod_emits[i].startcolor[1]*255,currententity->model->mod_emits[i].startcolor[2]*255, currententity->model->mod_emits[i].endcolor[0]*255,currententity->model->mod_emits[i].endcolor[1]*255,currententity->model->mod_emits[i].endcolor[2]*255, n, currententity->model->mod_emits[i].emit, currententity->model->mod_emits[i].gravity ? currententity->model->mod_emits[i].gravity*20 : PARTICLE_GRAVITY*3, currententity->model->mod_emits[i].vel, currententity->model->mod_emits[i].cone, PARTICLE_BOUNCE | PARTICLE_FRICTION, currententity->model->mod_emits[i].alphavel);
						else
							CL_EmitParticles(org, temp, 200,200,200, 200,200,200, n, currententity->model->mod_emits[i].emit, currententity->model->mod_emits[i].gravity ? currententity->model->mod_emits[i].gravity*20 : PARTICLE_GRAVITY*3, currententity->model->mod_emits[i].vel, currententity->model->mod_emits[i].cone, PARTICLE_BOUNCE | PARTICLE_FRICTION, currententity->model->mod_emits[i].alphavel);
						break;
					case part_bubble:
						if (!VectorCompare(currententity->model->mod_emits[i].startcolor, vec3_origin))
							CL_EmitParticles(org, temp, currententity->model->mod_emits[i].startcolor[0]*255,currententity->model->mod_emits[i].startcolor[1]*255,currententity->model->mod_emits[i].startcolor[2]*255, currententity->model->mod_emits[i].endcolor[0]*255,currententity->model->mod_emits[i].endcolor[1]*255,currententity->model->mod_emits[i].endcolor[2]*255, n, currententity->model->mod_emits[i].emit, currententity->model->mod_emits[i].gravity ? currententity->model->mod_emits[i].gravity*20 : -PARTICLE_GRAVITY, currententity->model->mod_emits[i].vel, currententity->model->mod_emits[i].cone, 0, currententity->model->mod_emits[i].alphavel);
						else
							CL_EmitParticles(org, temp, 200,200,200, 200,200,200, n, currententity->model->mod_emits[i].emit, currententity->model->mod_emits[i].gravity ? currententity->model->mod_emits[i].gravity*20 : -PARTICLE_GRAVITY, currententity->model->mod_emits[i].vel, currententity->model->mod_emits[i].cone, 0, currententity->model->mod_emits[i].alphavel);
						break;
					case part_simple:
					default:
						if (!VectorCompare(currententity->model->mod_emits[i].startcolor, vec3_origin))
							CL_EmitParticles(org, temp, currententity->model->mod_emits[i].startcolor[0]*255,currententity->model->mod_emits[i].startcolor[1]*255,currententity->model->mod_emits[i].startcolor[2]*255, currententity->model->mod_emits[i].endcolor[0]*255,currententity->model->mod_emits[i].endcolor[1]*255,currententity->model->mod_emits[i].endcolor[2]*255, n, currententity->model->mod_emits[i].emit, currententity->model->mod_emits[i].gravity ? currententity->model->mod_emits[i].gravity*20 : PARTICLE_GRAVITY*2, currententity->model->mod_emits[i].vel, currententity->model->mod_emits[i].cone, PARTICLE_BOUNCE | PARTICLE_FRICTION, currententity->model->mod_emits[i].alphavel);
						else
							CL_EmitParticles(org, temp, 255,255,255, 255,255,255, n, currententity->model->mod_emits[i].emit, currententity->model->mod_emits[i].gravity ? currententity->model->mod_emits[i].gravity*20 : PARTICLE_GRAVITY*2, currententity->model->mod_emits[i].vel, emits[i].cone, PARTICLE_BOUNCE | PARTICLE_FRICTION, currententity->model->mod_emits[i].alphavel);
						break;
				}
			}
		}
	}
}


void CL_AddPacketEntities (frame_t *frame)
{
	entity_t			ent;
	entity_state_t		*s1;
	int					i;
	int					pnum;
	centity_t			*cent;
	int					autoanim;
	clientinfo_t		*ci;
	unsigned			effects, renderfx;
	bool				lighted;
	int					ownerkeycounter = 0;

	// brush models can auto animate their frames
	autoanim = 2 * cl.time/1000;

	Memset (&ent, 0, sizeof(ent));

	for (pnum = 0 ; pnum<frame->num_entities ; pnum++)
	{
		s1 = &cl_parse_entities[(frame->parse_entities+pnum)&(MAX_PARSE_ENTITIES-1)];

		// if set to invisible, skip
		if (!s1->modelindex)
			continue;

		ownerkeycounter++;
		VectorClear(ent.color_shell);

		cent = &cl_entities[s1->number];

		effects = s1->effects;
		renderfx = s1->renderfx;
		ent.ownerkey = NULL;
		lighted = false;

			// set frame
		if (effects & EF_ANIM01)
			ent.frame = autoanim & 1;
		else if (effects & EF_ANIM23)
			ent.frame = 2 + (autoanim & 1);
		else if (effects & EF_ANIM_ALL)
			ent.frame = autoanim;
		else if (effects & EF_ANIM_ALLFAST)
			ent.frame = cl.time / 100;
		else
			ent.frame = s1->frame;

		if (r_noshells->value)
			effects &= ~(EF_PENT|EF_QUAD|EF_COLOR_SHELL|EF_DOUBLE|EF_HALF_DAMAGE);

		// quad and pent can do different things on client
		if (effects & EF_PENT)
		{
			effects |= EF_COLOR_SHELL;
			renderfx |= RF_SHELL_RED | RF_LIGHT;
		}

		if (effects & EF_QUAD)
		{
			effects |= EF_COLOR_SHELL;
			renderfx |= RF_SHELL_BLUE | RF_LIGHT;
		}

		if (effects & EF_INVIS)
			renderfx |= RF_DISTORT;

		if (effects & EF_DOUBLE)
		{
			effects &= ~EF_DOUBLE;
			effects |= EF_COLOR_SHELL;
			renderfx |= /*RF_SHELL_DOUBLE*/RF_SHELL_RED|RF_SHELL_GREEN | RF_LIGHT;
		}

		if (effects & EF_HALF_DAMAGE)
		{
			effects &= ~EF_HALF_DAMAGE;
			effects |= EF_COLOR_SHELL;
			renderfx |= /*RF_SHELL_HALF_DAM*/RF_SHELL_RED|RF_SHELL_BLUE | RF_LIGHT;
		}
// pmm
//======
		ent.oldframe = cent->prev.frame;
		ent.backlerp = 1.0 - cl.lerpfrac;

		if (renderfx & (RF_FRAMELERP|RF_BEAM))
		{	// step origin discretely, because the frames
			// do the animation properly

			//r1: beam lerp fix
			if (renderfx & RF_BEAM)
			{
				ent.oldorigin[0] = cent->prev.old_origin[0] + cl.lerpfrac * (cent->current.old_origin[0] - cent->prev.old_origin[0]);
				ent.oldorigin[1] = cent->prev.old_origin[1] + cl.lerpfrac * (cent->current.old_origin[1] - cent->prev.old_origin[1]);
				ent.oldorigin[2] = cent->prev.old_origin[2] + cl.lerpfrac * (cent->current.old_origin[2] - cent->prev.old_origin[2]);

				ent.origin[0] = cent->prev.origin[0] + cl.lerpfrac * (cent->current.origin[0] - cent->prev.origin[0]);
				ent.origin[1] = cent->prev.origin[1] + cl.lerpfrac * (cent->current.origin[1] - cent->prev.origin[1]);
				ent.origin[2] = cent->prev.origin[2] + cl.lerpfrac * (cent->current.origin[2] - cent->prev.origin[2]);
			}
			else
			{
				VectorCopy (cent->current.origin, ent.origin);
				VectorCopy (cent->current.old_origin, ent.oldorigin);
			}
		}
		else	// interpolate origin
			for (i=0 ; i<3 ; i++)
				ent.origin[i] = ent.oldorigin[i] = cent->prev.origin[i] + cl.lerpfrac * (cent->current.origin[i] - cent->prev.origin[i]);

		// create a new entity
	
		// tweak the color of beams
		if ( renderfx & RF_BEAM )
		{	// the four beam colors are encoded in 32 bits of skinnum (hack)
			ent.alpha = 0.30;
			ent.skinnum = (s1->skinnum >> ((rand() % 4)*8)) & 0xff;
			ent.model = NULL;
		}
		else
		{
			// set skin
			if (s1->modelindex == 255)
			{	// use custom player skin
				ent.skinnum = 0;
				ci = &cl.clientinfo[s1->skinnum & 0xff];
				ent.skin = ci->skin;
				ent.bump = ci->bump;
				ent.light = ci->light;
				ent.model = ci->model;
				if (!ent.skin || !ent.bump || !ent.model)
				{
					if (s1->number == cl.playernum+1)
					{	/// Berserker: FIXED BUG: смена модели игрока при смене карты
						ent.skin = gender_skin;
						ent.bump = gender_bump;
						ent.light = gender_light;
						ent.model = gender_mdl;
						if (!ent.skin || !ent.bump || !ent.model)
							goto fail;
					}
					else
					{
fail:					ent.skin = cl.baseclientinfo.skin;
						ent.bump = cl.baseclientinfo.bump;
						ent.light = cl.baseclientinfo.light;
						ent.model = cl.baseclientinfo.model;
					}
				}

//============
//PGM
				if ((renderfx & RF_USE_DISGUISE) && cl_disguise_model)
				{
					ent.model = cl_disguise_model;
					ent.skin = cl_disguise_skin;
					ent.bump = cl_disguise_bump;
					ent.light = cl_disguise_light;
				}
//PGM
//============
			}
			else
			{
				ent.skinnum = s1->skinnum;
				ent.skin =
				ent.bump =
				ent.light = NULL;
				ent.model = cl.model_draw[s1->modelindex];
				if(ent.model)
				{
					if (ent.model->type == mod_brush)
					{
						int bm, am;

						for (bm=0; bm<brushmodel_counter; bm++)
							if (bmdl_list[bm].index == s1->modelindex - 1)
								break;

						if (bm < brushmodel_counter)
						{	// interpolate angles and origin
							float	a1, a2;
							for (i=0 ; i<3 ; i++)
							{
								a1 = cent->current.angles[i];
								a2 = cent->prev.angles[i];
								ent.angles[i] = LerpAngle (a2, a1, cl.lerpfrac);
								ent.origin[i] = ent.oldorigin[i] = cent->prev.origin[i] + cl.lerpfrac * (cent->current.origin[i] - cent->prev.origin[i]);
							}

							for (am=0; am<aliasmodel_counter; am++)
								if (strcmp(amdl_list[am].label, " ")&& strcmp(amdl_list[am].label, "SkyModel") && amdl_list[am].index == s1->modelindex - 1)
									AddAliasModel(&amdl_list[am], &bmdl_list[bm], ent.origin, ent.angles);

							for (am=0; am<lightmodel_counter; am++)
								if (lmdl_list[am].index == s1->modelindex - 1)
									AddLight(&lmdl_list[am], &bmdl_list[bm], ent.origin, ent.angles);

							for (am=0; am<numEmits; am++)
								if (emits[am].index == s1->modelindex - 1)
									MoveEmit(&emits[am], &bmdl_list[bm], ent.origin, ent.angles);
						}
					}
				}
			}
		}

		if (ent.model)
		{	// Копируем некоторые флаги model во флаги entity
			renderfx |= (ent.model->flags & (RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|RF_LIGHT|RF_FULLBRIGHT|RF_DISTORT|RF_NOCASTSHADOW|RF_TRANSLUCENT));

			if (ent.model->flags & RF_SHELL_RED)
			{
				effects |= EF_COLOR_SHELL;
				renderfx |= RF_SHELL_RED;
			}

			if (ent.model->flags & RF_SHELL_GREEN)
			{
				effects |= EF_COLOR_SHELL;
				renderfx |= RF_SHELL_GREEN;
			}

			if (ent.model->flags & RF_SHELL_BLUE)
			{
				effects |= EF_COLOR_SHELL;
				renderfx |= RF_SHELL_BLUE;
			}
		}

		// only used for black hole model right now, FIXME: do better
		if (renderfx == RF_TRANSLUCENT)
			ent.alpha = 0.70;

///		// render effects (fullbright, translucent, etc)
///		if ((effects & EF_COLOR_SHELL))
///			ent.flags = 0;	// renderfx go on color shell entity
///		else
			ent.flags = renderfx;

		// calculate angles
		if (effects & EF_ROTATE)
		{	// some bonus items auto-rotate at a fixed rate
			ent.angles[0] = 0;
			ent.angles[1] = anglemod(cl.leveltime * 100 /*cl.time / 10*/);
			ent.angles[2] = 0;

			// bobbing items
#if 0
			float bob_scale = (0.005f + s1->number * 0.00001f) * 0.5;
			float bob = cos( (cl.gameTime + 1000) * bob_scale ) * 5;
#else
			float bob_scale = 0.005f + s1->number * 0.00001f;
			float bob = 4 + cos((cl.gameTime + 1000) * bob_scale) * 4;
#endif
			ent.oldorigin[2] += bob;
			ent.origin[2] += bob;
		}
		// RAFAEL
		else if (effects & EF_SPINNINGLIGHTS)
		{
			ent.angles[0] = 0;
			ent.angles[1] = anglemod((cl.gameTime>>1)) + s1->angles[1];
			ent.angles[2] = 180;
			{
				vec3_t forward;
				vec3_t start;

				AngleVectors (ent.angles, forward, NULL, NULL);
				VectorMA (ent.origin, 64, forward, start);
				V_AddLight (start, 100, 1, 0, 0, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
			}
		}
		else
		{	// interpolate angles
			float	a1, a2;

			for (i=0 ; i<3 ; i++)
			{
				a1 = cent->current.angles[i];
				a2 = cent->prev.angles[i];
				ent.angles[i] = LerpAngle (a2, a1, cl.lerpfrac);
			}
		}

		if (renderfx & RF_LIGHT)
		{
			if (renderfx & (RF_SHELL_RED|RF_POWER_RED))
				ent.color_shell[0] = 1;

			if (renderfx & (RF_SHELL_GREEN|RF_POWER_GREEN))
				ent.color_shell[1] = 1;

			if (renderfx & (RF_SHELL_BLUE|RF_POWER_BLUE))
				ent.color_shell[2] = 1;

			if(VectorCompare(ent.color_shell, vec3_origin))
				VectorSet(ent.color_shell, 1,1,1);

			lighted = true;		// Данный entity светится

			if(dl_shell->value)
			{
				if (s1->number == cl.playernum+1 && !cl_thirdPerson->value)
					V_AddLight (cl.refdef.vieworg, 96 + (rand()&7), ent.color_shell[0], ent.color_shell[1], ent.color_shell[2], 0, 0, 0, 0,0,0,0, ownerkeycounter, 0, 0, rend_all);
				else
					V_AddLight (ent.origin, 96 + (rand()&7), ent.color_shell[0], ent.color_shell[1], ent.color_shell[2], 0, 0, 0, 0,0,0,0, ownerkeycounter, 0, 0, rend_all);
			}
		}
		else
		{
			if (ent.model)
				if (ent.model->type == mod_alias || ent.model->type == mod_alias_md3)
				{
					currententity = &ent;	// for R_AddModelLight / CheckEntityFrame...
					R_AddModelLight();
				}
		}

		if (!net_compatibility->value && (effects & (EF_FLASHLIGHT|EF_FLASHLIGHT_NOENT)))
		{
			vec3_t	orgs, angs;
			player_state_t	*ps, *ops;
			frame_t		*oldframe;

			if (s1->number == cl.playernum+1 && !cl_thirdPerson->value)
			{
				// find the previous frame to interpolate from
				ps = &cl.frame.playerstate;
				i = (cl.frame.serverframe - 1) & UPDATE_MASK;
				oldframe = &cl.frames[i];
				if (oldframe->serverframe != cl.frame.serverframe-1 || !oldframe->valid)
					oldframe = &cl.frame;		// previous frame was dropped or invalid
				ops = &oldframe->playerstate;

				// set up flashlight position
				for (i=0 ; i<3 ; i++)
				{
					if (hand->value==2)			// center
						orgs[i] = cl.refdef.vieworg[i] + ops->gunoffset[i] + cl.lerpfrac * (ps->gunoffset[i] - ops->gunoffset[i]) - vup[i]*3;
					else if (hand->value==1)	// left
						orgs[i] = cl.refdef.vieworg[i] + ops->gunoffset[i] + cl.lerpfrac * (ps->gunoffset[i] - ops->gunoffset[i]) - vright[i]*3 - vup[i]*3;
					else						// otherwise right
						orgs[i] = cl.refdef.vieworg[i] + ops->gunoffset[i] + cl.lerpfrac * (ps->gunoffset[i] - ops->gunoffset[i]) + vright[i]*3 - vup[i]*3;
					angs[i] = cl.refdef.viewangles[i] + LerpAngle (ops->gunangles[i], ps->gunangles[i], cl.lerpfrac);
				}

				V_AddLight (orgs, FLARELIGHT_RADIUS, 1,1,1, 1,1,0, 0.5, angs[0],angs[1],angs[2], ownerkeycounter, 0, 0, rend_all);	// 1 - FlashLight filter!!!
			}
			else
			{
				vec3_t	forward, right, up;
				VectorCopy(ent.angles, angs);
				AngleVectors(angs, forward, right, up);
				VectorMA(ent.origin, 16, forward, orgs);
				VectorMA(orgs, 4, right, orgs);
				VectorMA(orgs, 16, up, orgs);

				V_AddLight (orgs, FLARELIGHT_RADIUS, 1,1,1, 1,1,0, 0.5, angs[0],angs[1],angs[2], NULL/*ownerkeycounter*/, 0, 0, rend_all);	// 1 - FlashLight filter!!!
			}

			if (effects & EF_FLASHLIGHT_NOENT)	// Не создаем entity, так как он невидим!
				continue;

			lighted = true;		// Данный entity излучает свет
		}

		if (s1->number == cl.playernum+1)
		{
#if 0
			// fixed player origin from EGL
			/// Berserker: лаги тени игрока пропали, но при поездке на лифте тень дико скачет )))
			if ((cl_predict->value) && !(cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
			{
				VectorCopy(cl.predicted_origin, ent.origin);
				VectorCopy(cl.predicted_origin, ent.oldorigin);
			}
#else
			// fixed player origin from Berserker
			if ((cl_predict->value) && !(cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
				for (i = 0; i < 3; i++)
					ent.oldorigin[i] = ent.origin[i] = cl.predicted_origin[i] - (1.0 - cl.lerpfrac) * cl.prediction_error[i];
#endif

			if (effects & EF_FLAG1)
				V_AddLight (ent.origin, 225, 1.0, 0.1, 0.1, 0,0,0,0,0,0,0, ownerkeycounter, 0, 0, rend_all);
			else if (effects & EF_FLAG2)
				V_AddLight (ent.origin, 225, 0.1, 0.1, 1.0, 0,0,0,0,0,0,0, ownerkeycounter, 0, 0, rend_all);
			else if (effects & EF_TRACKERTRAIL)					//PGM
				V_AddLight (ent.origin, 225, 1.0, 1.0, 1.0, 0,0,0,0,0,0,0, ownerkeycounter, 0, 0, rend_all);
			else if (effects & EF_TAGTRAIL)						//PGM
				V_AddLight (ent.origin, 225, 1.0, 1.0, 0.0, 0,0,0,0,0,0,0, ownerkeycounter, 0, 0, rend_all);

///		if (r_playershadow->value && r_shadows->value > 1)
			if(r_mirrors->value)
			{
				// color shells generate a separate entity for the main model
				if (effects & EF_COLOR_SHELL)
				{
					ent.flags = renderfx | RF_TRANSLUCENT|RF_VIEWERMODEL;
					ent.flags &= ~(RF_CHROME|RF_DISTORT);
					ent.alpha = 0.30;
					V_AddEntity (&ent, NULL, NULL);
				}

				ent.flags = RF_VIEWERMODEL|/*RF_COLOR_SHELL|*/(renderfx&RF_DISTORT);	// only draw from mirrors
				ent.origin[2] += 1;			/// чтобы тень на земле нормально рисовалась...
				ent.oldorigin[2] += 1;		/// чтобы тень на земле нормально рисовалась...
				V_AddEntity (&ent, lighted ? ownerkeycounter : NULL, NULL);

/// найдём модель пушки для плеер-меню
				currentPlayerWeapon = NULL;
// add weapon for player
				// duplicate for linked models
				if (s1->modelindex2)
				{
					ent.skin =
					ent.bump =
					ent.light = NULL;	// never use a custom skin on others
					ent.skinnum =
					ent.alpha = 0;
///					ent.flags = RF_VIEWERMODEL;	// only draw from mirrors

					if (s1->modelindex2 == 255)
					{	// custom weapon
						ci = &cl.clientinfo[s1->skinnum & 0xff];
						i = (s1->skinnum >> 8); // 0 is default weapon model
						if (!cl_vwep->value || i > MAX_CLIENTWEAPONMODELS - 1)
							i = 0;
						ent.model = ci->weaponmodel[i];
						if (!ent.model)
						{
							if (i != 0)
								ent.model = ci->weaponmodel[0];
							if (!ent.model)
								ent.model = cl.baseclientinfo.weaponmodel[0];
						}
					}
					else
						ent.model = cl.model_draw[s1->modelindex2];
					currentPlayerWeapon = ent.model;

					currententity = &ent;	// for R_AddModelLight / CheckEntityFrame...
					R_AddModelLight();
					V_AddEntity (&ent, lighted ? ownerkeycounter : NULL, NULL);

					// color shells generate a seperate entity for the main model
					if (effects & EF_COLOR_SHELL)
					{
						ent.flags = renderfx | RF_TRANSLUCENT|RF_VIEWERMODEL;
						ent.flags &= ~(RF_CHROME|RF_DISTORT);
						ent.alpha = 0.30;
						V_AddEntity (&ent, NULL, NULL);
					}
				}
			}
			else
			{
/// найдём модель пушки для плеер-меню
				currentPlayerWeapon = NULL;
				// duplicate for linked models
				if (s1->modelindex2 == 255)
				{	// custom weapon
					ci = &cl.clientinfo[s1->skinnum & 0xff];
					i = (s1->skinnum >> 8); // 0 is default weapon model
					if (!cl_vwep->value || i > MAX_CLIENTWEAPONMODELS - 1)
						i = 0;
					currentPlayerWeapon = ci->weaponmodel[i];
					if (!currentPlayerWeapon)
					{
						if (i != 0)
							currentPlayerWeapon = ci->weaponmodel[0];
						if (!currentPlayerWeapon)
							currentPlayerWeapon = cl.baseclientinfo.weaponmodel[0];
					}
				}
				else
					currentPlayerWeapon = cl.model_draw[s1->modelindex2];
			}

			continue;
		}

		if (effects & EF_BFG)
		{
			ent.flags |= RF_TRANSLUCENT;
			ent.alpha = 0.3;
		}

		if (effects & EF_SPHERETRANS)
		{
			ent.flags |= RF_TRANSLUCENT;
			// PMM - *sigh*  yet more EF overloading
			if (effects & EF_TRACKERTRAIL)
				ent.alpha = 0.6;
			else
				ent.alpha = 0.3;
		}

		// RAFAEL
		if (effects & EF_PLASMA)
		{
			ent.flags |= RF_TRANSLUCENT;
			ent.alpha = 0.6;
		}

		if(!lighted && (effects & (EF_FLAG1|EF_FLAG2|EF_IONRIPPER|EF_TAGTRAIL|EF_TRACKERTRAIL)))
			lighted = true;

		// add to refresh list
		int flg = ent.flags;
		ent.flags &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
		V_AddEntity (&ent, lighted ? ownerkeycounter : NULL, NULL);
		ent.flags = flg;

		// color shells generate a seperate entity for the main model
		if (effects & EF_COLOR_SHELL)
		{
			ent.flags = renderfx | RF_TRANSLUCENT;
			ent.flags &= ~(RF_CHROME|RF_DISTORT);
			ent.alpha = 0.30;
			V_AddEntity (&ent, NULL, NULL);
		}

		ent.skin =
		ent.bump =
		ent.light = NULL;	// never use a custom skin on others
		ent.flags = ent.skinnum = 0;
		ent.alpha = 0;

		// duplicate for linked models
		if (s1->modelindex2)
		{
			if (s1->modelindex2 == 255)
			{	// custom weapon
				ci = &cl.clientinfo[s1->skinnum & 0xff];
				i = (s1->skinnum >> 8); // 0 is default weapon model
				if (!cl_vwep->value || i > MAX_CLIENTWEAPONMODELS - 1)
					i = 0;
				ent.model = ci->weaponmodel[i];
				if (!ent.model)
				{
					if (i != 0)
						ent.model = ci->weaponmodel[0];
					if (!ent.model)
						ent.model = cl.baseclientinfo.weaponmodel[0];
				}
			}
			else
				ent.model = cl.model_draw[s1->modelindex2];

			if (ent.model)	// Berserker: в rogue.demo3 есть какая то модель с индексом=219, а модели в cl.model_draw нет. Игнорируем ентити без модели!
			{
				// PMM - check for the defender sphere shell .. make it translucent
				// replaces the previous version which used the high bit on modelindex2 to determine transparency
				if (!Q_strcasecmp (cl.configstrings[CS_MODELS+(s1->modelindex2)], "models/items/shell/tris.md2"))
				{
					ent.alpha = 0.32;
					ent.flags = RF_TRANSLUCENT;
				}
				// pmm

				// Копируем некоторые флаги model во флаги entity
				ent.flags = (ent.model->flags & (RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|RF_LIGHT|RF_FULLBRIGHT|RF_DISTORT|RF_NOCASTSHADOW));
				if (renderfx & RF_DISTORT)	// добавим distort
					ent.flags |= RF_DISTORT;
				currententity = &ent;	// for R_AddModelLight / CheckEntityFrame...
				R_AddModelLight();
				V_AddEntity (&ent, lighted ? ownerkeycounter : NULL, NULL);

				// color shells generate a seperate entity for the main model
				if (effects & EF_COLOR_SHELL)
				{
					ent.flags = RF_TRANSLUCENT | (renderfx & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE));
					ent.alpha = 0.30;
					V_AddEntity (&ent, NULL, NULL);
				}

				//PGM - make sure these get reset.
				ent.flags = 0;
				ent.alpha = 0;
				//PGM
			}
		}
		if (s1->modelindex3)
		{
			ent.model = cl.model_draw[s1->modelindex3];
			currententity = &ent;	// for R_AddModelLight / CheckEntityFrame...
			R_AddModelLight();
			V_AddEntity (&ent, lighted ? ownerkeycounter : NULL, NULL);
		}
		if (s1->modelindex4)
		{
			ent.model = cl.model_draw[s1->modelindex4];
			currententity = &ent;	// for R_AddModelLight / CheckEntityFrame...
			R_AddModelLight();
			V_AddEntity (&ent, lighted ? ownerkeycounter : NULL, NULL);
		}

		if ( effects & EF_POWERSCREEN )
		{
			ent.model = cl_mod_powerscreen;
			ent.oldframe = 0;
			ent.frame = 0;
			ent.flags |= (RF_TRANSLUCENT | RF_SHELL_GREEN);
			ent.alpha = 0.30;
			V_AddEntity (&ent, NULL, NULL);
			V_AddLight (ent.origin, 96 + (rand()&7), 0, 1, 0, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
		}

		// add automatic particle trails
		if ( (effects&~EF_ROTATE) )
		{
			if (effects & EF_ROCKET)
			{
///				CL_RocketTrail (cent->lerp_origin, ent.origin, cent);
CL_DiminishingTrail (cent->lerp_origin, ent.origin, cent, EF_ROCKET);	// smoke
				if(dl_rocket->value)
					V_AddLight (ent.origin, 200, 1, 1, 0, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
			}
			// PGM - Do not reorder EF_BLASTER and EF_HYPERBLASTER. 
			// EF_BLASTER | EF_TRACKER is a special case for EF_BLASTER2... Cheese!
			else if (effects & EF_BLASTER)
			{
//PGM
				if (effects & EF_TRACKER)	// lame... problematic?
				{
					CL_BlasterTrail (cent->lerp_origin, ent.origin, true);
					if(dl_blaster->value)
						V_AddLight (ent.origin, 200, 0, 1, 0, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
				}
				else
				{
					CL_BlasterTrail (cent->lerp_origin, ent.origin, false);
					if(dl_blaster->value)
						V_AddLight (ent.origin, 200, 1, 1, 0, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
				}
			}
//PGM
			else if (effects & EF_HYPERBLASTER)
			{
				if (effects & EF_TRACKER)						// PGM	overloaded for blaster2.
				{
					if(dl_hyperblaster->value)
						V_AddLight (ent.origin, 200, 0, 1, 0, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
				}
				else
				{
					if(dl_hyperblaster->value)
						V_AddLight (ent.origin, 200, 1, 1, 0, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
				}
			}
			else if (effects & EF_GIB)
			{
				CL_DiminishingTrail (cent->lerp_origin, ent.origin, cent, EF_GIB);
			}
			else if (effects & EF_GRENADE)
			{
				CL_DiminishingTrail (cent->lerp_origin, ent.origin, cent, EF_GRENADE);
			}
			else if (effects & EF_FLIES)
			{
				CL_FlyEffect (cent, ent.origin);
			}
			else if (effects & EF_BFG)
			{
				CL_DiminishingTrail (cent->lerp_origin, ent.origin, cent, EF_BFG);
				if (effects & EF_ANIM_ALLFAST)
				{
					CL_BfgParticles (&ent);
					i = 200;
				}
				else
				{
					static int bfg_lightramp[6] = {300, 400, 600, 300, 150, 75};
					i = bfg_lightramp[s1->frame];
				}
				if(dl_bfg->value)
					V_AddLight (ent.origin, i, 0, 1, 0, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
			}
			else if (effects & EF_FLAG1)
			{
				CL_FlagTrail (cent->lerp_origin, ent.origin, 242, 110, 64);
				V_AddLight (ent.origin, 225, 1, 0.1, 0.1, 0,0,0,0,0,0,0, ownerkeycounter, 0, 0, rend_all);
			}
			else if (effects & EF_FLAG2)
			{
				CL_FlagTrail (cent->lerp_origin, ent.origin, 64, 64, 115);
				V_AddLight (ent.origin, 225, 0.1, 0.1, 1, 0,0,0,0,0,0,0, ownerkeycounter, 0, 0, rend_all);
			}
			// RAFAEL
			else if (net_compatibility->value && (effects & EF_IONRIPPER))
			{
				CL_IonripperTrail (cent->lerp_origin, ent.origin);
				V_AddLight (ent.origin, 100, 1, 0.5, 0.5, 0,0,0,0,0,0,0, ownerkeycounter, 0, 0, rend_all);
			}
			// RAFAEL
			else if (net_compatibility->value && (effects & EF_GREENGIB))
			{
				CL_DiminishingTrail (cent->lerp_origin, ent.origin, cent, effects);				
			}
			// RAFAEL
			else if (effects & EF_BLUEHYPERBLASTER)
			{
				if(dl_hyperblaster->value)
					V_AddLight (ent.origin, 200, 0, 0, 1, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
			}
			// RAFAEL
			else if (effects & EF_PLASMA)
			{
				if (effects & EF_ANIM_ALLFAST)
					CL_BlasterTrail (cent->lerp_origin, ent.origin, false);

				V_AddLight (ent.origin, 130, 1, 0.5, 0.5, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
			}
			// RAFAEL
			else if (effects & EF_TRAP)
			{
				ent.origin[2] += 32;
				CL_TrapParticles (&ent);
				i = (rand()%100) + 100;
				V_AddLight (ent.origin, i, 1, 0.8, 0.1, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
			}
			else if (effects & EF_TRACKER)
			{
				CL_TrackerTrail (cent->lerp_origin, ent.origin, 0);
				V_AddLight (ent.origin, 200, 1, 1, 1, 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
			}
			else if (effects & EF_TAGTRAIL)
			{
				CL_TagTrail (cent->lerp_origin, ent.origin, 220);
				V_AddLight (ent.origin, 225, 1.0, 1.0, 0.0, 0,0,0,0,0,0,0, ownerkeycounter, 0, 0, rend_all);
			}
			else if (effects & EF_TRACKERTRAIL)
			{
				if (effects & EF_TRACKER)
				{
					float intensity;

					intensity = 50 + (500 * (sin(cl.leveltime * 2) + 1.0));
					V_AddLight (ent.origin, intensity, 1.0, 1.0, 1.0, 0,0,0,0,0,0,0, ownerkeycounter, 0, 0, rend_all);
					}
				else
				{
					CL_Tracker_Shell (cent->lerp_origin);
					V_AddLight (ent.origin, 155, 1.0, 1.0, 1.0, 0,0,0,0,0,0,0, ownerkeycounter, 0, 0, rend_all);
				}
			}
		}

		VectorCopy (ent.origin, cent->lerp_origin);
	}
}

void PlayerConfig_MenuDraw()
{
	refdef_t refdef;

	char scratch[MAX_QPATH];

	Memset( &refdef, 0, sizeof( refdef ) );

	refdef.x = viddef.width / 2;
	refdef.y = viddef.height / 2 - 142;
	refdef.width = 288;
	refdef.height = 336;
	refdef.fov_x = 40;
	refdef.fov_y = CalcFov( refdef.fov_x, refdef.width, refdef.height );
	refdef.time = cls.realtime*0.001;

	if ( s_pmi[s_player_model_box.curvalue].skindisplaynames )
	{
/// set up player model
		refdef.num_entities = 1;

		entity_t entity[2];

		Memset( &entity[0], 0, sizeof( entity[0] ) );

		Com_sprintf( scratch, sizeof( scratch ), "players/%s/tris.md2", s_pmi[s_player_model_box.curvalue].directory );
		entity[0].model = R_RegisterModel( scratch, 1, false );
		Com_sprintf( scratch, sizeof( scratch ), "players/%s/%s", s_pmi[s_player_model_box.curvalue].directory, s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue] );
		entity[0].skin = R_RegisterSkin( scratch );
		entity[0].bump = R_RegisterBump( scratch, entity[0].skin, entity[0].model );
		entity[0].light = R_RegisterLight( scratch );
//		entity[0].flags = 0;
		entity[0].origin[0] = 70;
//		entity[0].origin[1] = 0;
		entity[0].origin[2] = -4;
		VectorCopy( entity[0].origin, entity[0].oldorigin );

		if (pose_rot_angle[pose_rot]==-1)
		{
			entity[0].angles[1] = anglemod(cls.realtime >> 4);
			if ( entity[0].angles[1] > 360 )
				entity[0].angles[1] -= 360;
		}
		else
			entity[0].angles[1] = pose_rot_angle[pose_rot];
		entity[0].frame = pose_start[pose]+((cls.realtime >> 8)%(pose_end[pose]-pose_start[pose]+1));
		entity[0].oldframe = pose_start[pose]+(((cls.realtime >> 8) - 1)%(pose_end[pose]-pose_start[pose]+1));
		entity[0].backlerp = 1.0-((float)(cls.realtime & 0xff))/255.0;

/// set up weapon model
		Memset( &entity[1], 0, sizeof( entity[1] ) );

		if (currentPlayerWeapon && cls.state == ca_active)
		{
			entity[1].model = currentPlayerWeapon;

			entity[1].skin = currentPlayerWeapon->skins[0];
			if (!entity[1].skin)	entity[1].skin = r_notexture;
			entity[1].bump = currentPlayerWeapon->bumps[0];
			if (!entity[1].bump)	entity[1].bump = r_defaultbump;
			entity[1].light = currentPlayerWeapon->lights[0];
			entity[1].origin[0] = 70;
			entity[1].origin[2] = -4;
			VectorCopy( entity[1].origin, entity[1].oldorigin );

			entity[1].angles[1] = entity[0].angles[1];
			entity[1].frame = entity[0].frame;
			entity[1].oldframe = entity[0].oldframe;
			entity[1].backlerp = entity[0].backlerp;

			refdef.num_entities++;
		}
		else
		{
			Com_sprintf( scratch, sizeof( scratch ), "players/%s/weapon.md2", s_pmi[s_player_model_box.curvalue].directory );
			entity[1].model = R_RegisterModel( scratch, 1, false );
			if (entity[1].model)
			{
				Com_sprintf( scratch, sizeof( scratch ), "players/%s/weapon", s_pmi[s_player_model_box.curvalue].directory );
				entity[1].skin = R_RegisterSkin( scratch );
				entity[1].bump = R_RegisterBump( scratch, entity[1].skin, entity[1].model );
				entity[1].light = R_RegisterLight( scratch );
//				entity[1].flags = 0;
				entity[1].origin[0] = 70;
//				entity[1].origin[1] = 0;
				entity[1].origin[2] = -4;
				VectorCopy( entity[1].origin, entity[1].oldorigin );

				entity[1].angles[1] = entity[0].angles[1];
				entity[1].frame = entity[0].frame;
				entity[1].oldframe = entity[0].oldframe;
				entity[1].backlerp = entity[0].backlerp;

				refdef.num_entities++;
			}
		}

		refdef.areabits = 0;
		refdef.entities = &entity[0];
		refdef.rdflags = RDF_NOWORLDMODEL;

		if(r_mode->value != 0)		// Berserker: in 640x480 not enought room in screen for banner
		{
			int w, h;
			Draw_GetPicSize (&w, &h, "m_banner_player_setup.tga" );
			Draw_Pic( viddef.width / 2 - w / 2, viddef.height / 2 - 140, "m_banner_player_setup.tga" );
		}
		Menu_Draw( &s_player_config_menu );

		M_DrawTextBox( ( refdef.x ) * ( 320.0F / viddef.width ) - 8, ( viddef.height >> 1 ) * ( 240.0F / viddef.height) - 77, refdef.width >> 4, refdef.height >> 4 );
		refdef.height += 8;

		/// Light setup
		currentshadowlight = &m_light;
		Memset(currentshadowlight, 0, sizeof(shadowlight_t));
		VectorSet(currentshadowlight->origin, -100, 100, 76);
///		VectorSet(currentshadowlight->color, 1,1,1);
		float v = scr_3dhud_intensity->value;
		VectorSet(currentshadowlight->styled_color, v,v,v);
		currentshadowlight->radiuses[0] = currentshadowlight->radiuses[1] = currentshadowlight->radiuses[2] = currentshadowlight->radius = 2000;
		currentshadowlight->sphere = true;
		VectorSet(currentshadowlight->mins, -256, -256, -256);
		VectorSet(currentshadowlight->maxs, 256, 256, 256);
		VectorSet(currentshadowlight->mins_cone, -256, -256, -256);
		VectorSet(currentshadowlight->maxs_cone, 256, 256, 256);
///		currentshadowlight->use = true;

		R_SaveRenderState();
		Render_Frame (&refdef, false);
		R_RestoreRenderState();

		Com_sprintf( scratch, sizeof( scratch ), "/players/%s/%s_i",
			s_pmi[s_player_model_box.curvalue].directory,
			s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue] );
		Draw_PicSized( s_player_config_menu.x - 40, refdef.y + 72, 32, 32, scratch );
	}
}

void CL_AddBeams ()
{
	int			i,j;
	beam_t		*b;
	vec3_t		dist, org;
	float		d;
	entity_t	ent;
	float		yaw, pitch;
	float		forward;
	float		len, steps;
	float		model_length;

// update beams
	for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++)
	{
		if (!b->model || b->endtime < cl.time)
			continue;

		// if coming from the player, update the start position
		if (b->entity == cl.playernum+1)	// entity 0 is the world
		{
			VectorCopy (cl.refdef.vieworg, b->start);
			b->start[2] -= 22;	// adjust for view height
		}
		VectorAdd (b->start, b->offset, org);

	// calculate pitch and yaw
		VectorSubtract (b->end, org, dist);

		if (dist[1] == 0 && dist[0] == 0)
		{
			yaw = 0;
			if (dist[2] > 0)
				pitch = 90;
			else
				pitch = 270;
		}
		else
		{
	// PMM - fixed to correct for pitch of 0
			if (dist[0])
				yaw = (atan2(dist[1], dist[0]) * 180 / M_PI);
			else if (dist[1] > 0)
				yaw = 90;
			else
				yaw = 270;
			if (yaw < 0)
				yaw += 360;
	
			forward = Sqrt (dist[0]*dist[0] + dist[1]*dist[1]);
			pitch = (atan2(dist[2], forward) * -180.0 / M_PI);
			if (pitch < 0)
				pitch += 360.0;
		}

	// add new entities for the beams
		d = VectorNormalize(dist);

		Memset (&ent, 0, sizeof(ent));
		if (b->model == cl_mod_lightning)
		{
			model_length = 35.0;
			d-= 20.0;  // correction so it doesn't end in middle of tesla
		}
		else
		{
			model_length = 30.0;
		}
		steps = ceil(d/model_length);
		len = (d-model_length)/(steps-1);

		// PMM - special case for lightning model .. if the real length is shorter than the model,
		// flip it around & draw it from the end to the start.  This prevents the model from going
		// through the tesla mine (instead it goes through the target)
		if ((b->model == cl_mod_lightning) && (d <= model_length))
		{
			VectorCopy (b->end, ent.origin);
			// offset to push beam outside of tesla model (negative because dist is from end to start
			// for this beam)
			ent.model = b->model;
			ent.flags = RF_FULLBRIGHT;
			ent.angles[0] = pitch;
			ent.angles[1] = yaw;
			ent.angles[2] = rand()%360;
			V_AddEntity (&ent, NULL, NULL);
			return;
		}
		while (d > 0)
		{
			VectorCopy (org, ent.origin);
			ent.model = b->model;
			if (b->model == cl_mod_lightning)
			{
				ent.flags = RF_FULLBRIGHT;
				ent.angles[0] = -pitch;
				ent.angles[1] = yaw + 180.0;
				ent.angles[2] = rand()%360;
			}
			else
			{
				ent.angles[0] = pitch;
				ent.angles[1] = yaw;
				ent.angles[2] = rand()%360;
			}

			V_AddEntity (&ent, NULL, NULL);

			for (j=0 ; j<3 ; j++)
				org[j] += dist[j]*len;
			d -= model_length;
		}
	}
}


void CL_AddExplosions ()
{
	entity_t	*ent;
	int			i;
	explosion_t	*ex;
	float		frac;
	int			f;

///	Memset (&ent, 0, sizeof(ent));

	for (i=0, ex=cl_explosions ; i< MAX_EXPLOSIONS ; i++, ex++)
	{
		if (ex->type == ex_free)
			continue;
		frac = (cl.time - ex->start)/100.0;
		f = floor(frac);

		ent = &ex->ent;

		switch (ex->type)
		{
		case ex_mflash:
			if (f >= ex->frames-1)
				ex->type = ex_free;
			break;
		case ex_misc:
			if (f >= ex->frames-1)
			{
				ex->type = ex_free;
				break;
			}
			ent->alpha = 1.0 - frac/(ex->frames-1);
			break;
		case ex_flash:
			if (f >= 1)
			{
				ex->type = ex_free;
				break;
			}
			ent->alpha = 1.0;
			break;
		case ex_poly:
			if (f >= ex->frames-1)
			{
				ex->type = ex_free;
				break;
			}

			ent->alpha = (16.0 - (float)f)/16.0;

			if (f < 10)
			{
				ent->skinnum = (f>>1);
				if (ent->skinnum < 0)
					ent->skinnum = 0;
			}
			else
			{
				ent->flags |= RF_TRANSLUCENT;
				if (f < 13)
					ent->skinnum = 5;
				else
					ent->skinnum = 6;
			}
			break;
		case ex_poly2:
			if (f >= ex->frames-1)
			{
				ex->type = ex_free;
				break;
			}

			ent->alpha = (5.0 - (float)f)/5.0;
			ent->skinnum = 0;
			ent->flags |= RF_TRANSLUCENT;
			break;
		}

		if (ex->type == ex_free)
			continue;

		if (ex->light && dl_explosion->value)
			V_AddLight (ex->light_origin, ex->light*ent->alpha, ex->lightcolor[0], ex->lightcolor[1], ex->lightcolor[2], 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);

		VectorCopy (ent->origin, ent->oldorigin);

		if (f < 0)
			f = 0;
		ent->frame = ex->baseframe + f + 1;
		ent->oldframe = ex->baseframe + f;
		ent->backlerp = 1.0 - cl.lerpfrac;
		V_AddEntity (ent, NULL, NULL);
	}
}


void CL_AddLasers ()
{
	laser_t		*l;
	int			i;

	for (i=0, l=cl_lasers ; i< MAX_LASERS ; i++, l++)
		if (l->endtime >= cl.gameTime)
			V_AddEntity (&l->ent, NULL, NULL);
}


void CL_Heatbeam (vec3_t start, vec3_t forward)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;
	vec3_t		right, up;
	int			i;
	float		c, s;
	vec3_t		dir;
	float		ltime;
	float		step = 32.0, rstep;
	float		start_pt;
	float		rot;
	float		variance;
	vec3_t		end;

	VectorMA (start, 4096, forward, end);

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	// FIXME - pmm - these might end up using old values?
//	MakeNormalVectors (vec, right, up);
	VectorCopy (cl.v_right, right);
	VectorCopy (cl.v_up, up);
	VectorMA (move, -0.5, right, move);
	VectorMA (move, -0.5, up, move);

	ltime = cl.leveltime;
	start_pt = fmod(float(ltime*96.0), step);
	VectorMA (move, start_pt, vec, move);

	VectorScale (vec, step, vec);

//	Com_Printf ("%f\n", ltime);
	rstep = M_PI/10.0;
	for (i=start_pt ; i<len ; i+=step)
	{
		if (i>step*5) // don't bother after the 5th ring
			break;

		for (rot = 0; rot < M_PI*2; rot += rstep)
		{

			if (!free_particles)
				return;

			p = free_particles;
			free_particles = p->next;
			p->next = active_particles;
			active_particles = p;
			
			p->flags = 0;
			p->time = cl.gameTime;
			VectorClear (p->accel);
//			rot+= fmod(ltime, 12.0)*M_PI;
//			c = cos(rot)/2.0;
//			s = sin(rot)/2.0;
//			variance = 0.4 + ((float)rand()/(float)RAND_MAX) *0.2;
			variance = 0.5;

			float sr, cr;
			SinCos(rot, &sr, &cr);
			c = cr*variance;
			s = sr*variance;
///			c = cos(rot)*variance;
///			s = sin(rot)*variance;
			
			// trim it so it looks like it's starting at the origin
			if (i < 10)
			{
				VectorScale (right, c*(i/10.0), dir);
				VectorMA (dir, s*(i/10.0), up, dir);
			}
			else
			{
				VectorScale (right, c, dir);
				VectorMA (dir, s, up, dir);
			}
		
			p->alpha = 0.5;
	//		p->alphavel = -1.0 / (1+frand()*0.2);
			p->alphavel = -1000.0;
	//		p->color = 0x74 + (rand()&7);
			p->color_r0 = p->color_r1 = (223 & 0xC0) - (rand()&7);
			p->color_g0 = p->color_g1 = ((223 << 3) & 0xC0) - (rand()&7);
			p->color_b0 = p->color_b1 = ((223 << 6) & 0xC0) - (rand()&7);
			for (j=0 ; j<3 ; j++)
			{
				p->lastOrg[j] = p->org[j] = move[j] + dir[j]*3;
	//			p->vel[j] = dir[j]*6;
				p->vel[j] = 0;
			}
			p->type = part_simple;
		}
		VectorAdd (move, vec, move);
	}
}


void CL_MonsterPlasma_Shell(vec3_t origin)
{
	vec3_t			dir;
	int				i;
	cparticle_t		*p;

	for(i=0;i<40;i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		VectorClear (p->accel);
		
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = INSTANT_PARTICLE;
///p->color = 0xe0;
		p->color_r0 = p->color_r1 = (0xe0 & 0xC0);
		p->color_g0 = p->color_g1 = ((0xe0 << 3) & 0xC0);
		p->color_b0 = p->color_b1 = ((0xe0 << 6) & 0xC0);

		dir[0] = crand();
		dir[1] = crand();
		dir[2] = crand();
		VectorNormalize(dir);
	
		VectorMA(origin, 10, dir, p->org);
		VectorCopy(p->org, p->lastOrg);
		p->type = part_simple;
//		VectorMA(origin, 10*(((rand () & 0x7fff) / ((float)0x7fff))), dir, p->org);
	}
}


/*
=================
ROGUE - draw player locked beams
CL_AddPlayerBeams
=================
*/
void CL_AddPlayerBeams ()
{
	int			i,j;
	beam_t		*b;
	vec3_t		dist, org;
	float		d;
	entity_t	ent;
	float		yaw, pitch;
	float		forward;
	float		len, steps;
	int			framenum;
	float		model_length;
	
	float		hand_multiplier;
	frame_t		*oldframe;
	player_state_t	*ps, *ops;

	if (hand)
	{
		if (hand->value == 2)
			hand_multiplier = 0;
		else if (hand->value == 1)
			hand_multiplier = -1;
		else
			hand_multiplier = 1;
	}
	else
		hand_multiplier = 1;

// update beams
	for (i=0, b=cl_playerbeams ; i< MAX_BEAMS ; i++, b++)
	{
		vec3_t		f,r,u;
		if (!b->model || b->endtime < cl.time)
			continue;

		if(cl_mod_heatbeam && (b->model == cl_mod_heatbeam))
		{

			// if coming from the player, update the start position
			if (b->entity == cl.playernum+1)	// entity 0 is the world
			{	
				// set up gun position
				// code straight out of CL_AddViewWeapon
				ps = &cl.frame.playerstate;
				j = (cl.frame.serverframe - 1) & UPDATE_MASK;
				oldframe = &cl.frames[j];
				if (oldframe->serverframe != cl.frame.serverframe-1 || !oldframe->valid)
					oldframe = &cl.frame;		// previous frame was dropped or invalid
				ops = &oldframe->playerstate;
				for (j=0 ; j<3 ; j++)
				{
					b->start[j] = cl.refdef.vieworg[j] + ops->gunoffset[j]
						+ cl.lerpfrac * (ps->gunoffset[j] - ops->gunoffset[j]);
				}
				VectorMA (b->start, (hand_multiplier * b->offset[0]), cl.v_right, org);
				VectorMA (     org, b->offset[1], cl.v_forward, org);
				VectorMA (     org, b->offset[2], cl.v_up, org);
				if ((hand) && (hand->value == 2))
					VectorMA (org, -1, cl.v_up, org);

				// FIXME - take these out when final
				VectorCopy (cl.v_right, r);
				VectorCopy (cl.v_forward, f);
				VectorCopy (cl.v_up, u);

			}
			else
				VectorCopy (b->start, org);
		}
		else
		{
			// if coming from the player, update the start position
			if (b->entity == cl.playernum+1)	// entity 0 is the world
			{
				VectorCopy (cl.refdef.vieworg, b->start);
				b->start[2] -= 22;	// adjust for view height
			}
			VectorAdd (b->start, b->offset, org);
		}

	// calculate pitch and yaw
		VectorSubtract (b->end, org, dist);

//PMM
		if(cl_mod_heatbeam && (b->model == cl_mod_heatbeam) && (b->entity == cl.playernum+1))
		{
			float len;

			len = VectorLength (dist);
			VectorScale (f, len, dist);
			VectorMA (dist, (hand_multiplier * b->offset[0]), r, dist);
			VectorMA (dist, b->offset[1], f, dist);
			VectorMA (dist, b->offset[2], u, dist);
			if ((hand) && (hand->value == 2))
				VectorMA (org, -1, cl.v_up, org);
		}
//PMM

		if (dist[1] == 0 && dist[0] == 0)
		{
			yaw = 0;
			if (dist[2] > 0)
				pitch = 90;
			else
				pitch = 270;
		}
		else
		{
	// PMM - fixed to correct for pitch of 0
			if (dist[0])
				yaw = (atan2(dist[1], dist[0]) * 180 / M_PI);
			else if (dist[1] > 0)
				yaw = 90;
			else
				yaw = 270;
			if (yaw < 0)
				yaw += 360;
	
			forward = Sqrt (dist[0]*dist[0] + dist[1]*dist[1]);
			pitch = (atan2(dist[2], forward) * -180.0 / M_PI);
			if (pitch < 0)
				pitch += 360.0;
		}
		
		if (cl_mod_heatbeam && (b->model == cl_mod_heatbeam))
		{
			if (b->entity != cl.playernum+1)
			{
				framenum = 2;
//				Com_Printf ("Third person\n");
				ent.angles[0] = -pitch;
				ent.angles[1] = yaw + 180.0;
				ent.angles[2] = 0;
//				Com_Printf ("%f %f - %f %f %f\n", -pitch, yaw+180.0, b->offset[0], b->offset[1], b->offset[2]);
				AngleVectors(ent.angles, f, r, u);
					
				// if it's a non-origin offset, it's a player, so use the hardcoded player offset
				if (!VectorCompare (b->offset, vec3_origin))
				{
					VectorMA (org, -(b->offset[0])+1, r, org);
					VectorMA (org, -(b->offset[1]), f, org);
					VectorMA (org, -(b->offset[2])-10, u, org);
				}
				else
				{
					// if it's a monster, do the particle effect
					CL_MonsterPlasma_Shell(b->start);
				}
			}
			else
			{
				framenum = 1;
			}
		}

		// if it's the heatbeam, draw the particle effect
		if ((cl_mod_heatbeam && (b->model == cl_mod_heatbeam) && (b->entity == cl.playernum+1)))
			CL_Heatbeam (org, dist);

	// add new entities for the beams
		d = VectorNormalize(dist);

		Memset (&ent, 0, sizeof(ent));
		if (b->model == cl_mod_heatbeam)
		{
			model_length = 32.0;
		}
		else if (b->model == cl_mod_lightning)
		{
			model_length = 35.0;
			d-= 20.0;  // correction so it doesn't end in middle of tesla
		}
		else
		{
			model_length = 30.0;
		}
		steps = ceil(d/model_length);
		len = (d-model_length)/(steps-1);

		// PMM - special case for lightning model .. if the real length is shorter than the model,
		// flip it around & draw it from the end to the start.  This prevents the model from going
		// through the tesla mine (instead it goes through the target)
		if ((b->model == cl_mod_lightning) && (d <= model_length))
		{
//			Com_Printf ("special case\n");
			VectorCopy (b->end, ent.origin);
			// offset to push beam outside of tesla model (negative because dist is from end to start
			// for this beam)
//			for (j=0 ; j<3 ; j++)
//				ent.origin[j] -= dist[j]*10.0;
			ent.model = b->model;
			ent.flags = RF_FULLBRIGHT;
			ent.angles[0] = pitch;
			ent.angles[1] = yaw;
			ent.angles[2] = rand()%360;
			V_AddEntity (&ent, NULL, NULL);
			return;
		}
		while (d > 0)
		{
			VectorCopy (org, ent.origin);
			ent.model = b->model;
			if(cl_mod_heatbeam && (b->model == cl_mod_heatbeam))
			{
//				ent.flags = RF_FULLBRIGHT|RF_TRANSLUCENT;
//				ent.alpha = 0.3;
				ent.flags = RF_FULLBRIGHT;
				ent.angles[0] = -pitch;
				ent.angles[1] = yaw + 180.0;
				ent.angles[2] = (cl.gameTime) % 360;
//				ent.angles[2] = rand()%360;
				ent.frame = framenum;
			}
			else if (b->model == cl_mod_lightning)
			{
				ent.flags = RF_FULLBRIGHT;
				ent.angles[0] = -pitch;
				ent.angles[1] = yaw + 180.0;
				ent.angles[2] = rand()%360;
			}
			else
			{
				ent.angles[0] = pitch;
				ent.angles[1] = yaw;
				ent.angles[2] = rand()%360;
			}
			
//			Com_Printf("B: %d -> %d\n", b->entity, b->dest_entity);
			V_AddEntity (&ent, NULL, NULL);

			for (j=0 ; j<3 ; j++)
				org[j] += dist[j]*len;
			d -= model_length;
		}
	}
}


void CL_Beams2Particles()
{
	int			i;
	cparticle_t	*p;

	for (i=0; i<r_numentities; i++)
	{
		currententity = &r_entities[i];
		if ( currententity->flags & RF_BEAM )
		{
			if (!free_particles)
				return;
			p = free_particles;
			free_particles = p->next;
			p->next = active_particles;
			active_particles = p;

			p->flags = 0;
			p->time = cl.gameTime;
			VectorClear (p->accel);

			int sk = (currententity->skinnum & 0xFF) * 3;
			p->color_r0 = p->color_r1 = d_8to24table[sk + 0];
			p->color_g0 = p->color_g1 = d_8to24table[sk + 1];
			p->color_b0 = p->color_b1 = d_8to24table[sk + 2];

			p->alpha = currententity->alpha;
			p->alphavel = INSTANT_PARTICLE;

			VectorCopy (currententity->origin, p->org);
			VectorCopy (currententity->origin, p->lastOrg);
			VectorSubtract(currententity->oldorigin, currententity->origin, p->length);
			VectorClear(p->vel);
			VectorClear(p->accel);

			p->type = part_beam;
			p->size = 4;
		}
	}
}

/* PMM - CL_Sustains */
void CL_ProcessSustain ()
{
	cl_sustain_t	*s;
	int				i;

	for (i=0, s=cl_sustains; i< MAX_SUSTAINS; i++, s++)
	{
		if (s->id)
			if ((s->endtime >= cl.gameTime) && (cl.gameTime >= s->nextthink))
			{
//				Com_Printf ("think %d %d %d\n", cl.time, s->nextthink, s->thinkinterval);
				s->think (s);
			}
			else if (s->endtime < cl.time)
				s->id = 0;
	}
}

void CL_AddTEnts ()
{
	CL_AddBeams ();
	// PMM - draw plasma beams
	CL_AddPlayerBeams ();
	CL_AddExplosions ();
	CL_AddLasers ();
	// PMM - set up sustain
	CL_ProcessSustain();
}


void V_AddParticle (vec3_t org, byte color_r, byte color_g, byte color_b, float alpha, parttype_t type, vec3_t length, int size)
{
	particle_t	*p;

	if (r_numparticles >= MAX_PARTICLES)
		return;
	p = &r_particles[r_numparticles++];
	VectorCopy (org, p->origin);
	VectorCopy (length, p->length);
	p->size = size;
	p->color_r = color_r;
	p->color_g = color_g;
	p->color_b = color_b;
	p->alpha = alpha;
	p->type = type;
}


/*
 =================
 VectorReflect
 =================
*/
void VectorReflect (const vec3_t v, const vec3_t normal, vec3_t out)
{
	float	d;

	d = 2.0 * (v[0]*normal[0] + v[1]*normal[1] + v[2]*normal[2]);

	out[0] = v[0] - normal[0] * d;
	out[1] = v[1] - normal[1] * d;
	out[2] = v[2] - normal[2] * d;
}


int VectorCompareEpsilon (vec3_t v1, vec3_t v2, float epsilon)
{
	if(fabs(v1[0]-v2[0])>epsilon)
		return 0;
	if(fabs(v1[1]-v2[1])>epsilon)
		return 0;
	if(fabs(v1[2]-v2[2])>epsilon)
		return 0;
	return 1;
}


/*
================
CL_ParseClientinfo

Load the skin, icon, and model for a client
================
*/
void CL_ParseClientinfo (int player)
{
	int				len, cs_playerskins;
	char			*s, *t;
	clientinfo_t	*ci;
	bool			hack = false;

	if (net_compatibility->value)
		cs_playerskins = CS_PLAYERSKINS_Q2;
	else
		cs_playerskins = CS_PLAYERSKINS_BERS;

	if (cl.attractloop)
	{
		if ((int)cl_forcemymodel->value & 2)
			hack = true;
	}
	else
	{
		if ((int)cl_forcemymodel->value & 1)
			hack = true;
	}

	if (hack)
	{
		char clname[MAX_QPATH];
		t = strstr(cl.configstrings[player+cs_playerskins], "\\");
		len = t - cl.configstrings[player+cs_playerskins];
		if (len>=sizeof(clname))
			len=sizeof(clname)-1;
		Memset(clname, 0, len+1);
		strncpy(clname, cl.configstrings[player+cs_playerskins], len);
		Com_sprintf (mymodel, sizeof(mymodel), "%s\\%s", clname, skin->string);
		s = mymodel;
	}
	else
		s = cl.configstrings[player+cs_playerskins];

	ci = &cl.clientinfo[player];

	CL_LoadClientinfo (ci, s, false);
}


void CL_AddParticles ()
{
	if (!cl_add_particles->value)
		return;

	cparticle_t	*p, *next;
	vec3_t		org;
	byte		color_r;
	byte		color_g;
	byte		color_b;
	cparticle_t	*active, *tail;
	float		alpha, bak;
	int			contents;
	float		time, time2, grav = Cvar_VariableValue("sv_gravity");
	if(!grav)
		grav = 1;
	else
		grav /= 800;

	active = NULL;
	tail = NULL;

	for (p=active_particles ; p ; p=next)
	{
		next = p->next;

		time = (cl.gameTime - p->time)*0.001;
		alpha = p->alpha + time*p->alphavel;
		if (alpha <= 0)
		{	// faded out
			p->next = free_particles;
			free_particles = p;
			continue;
		}

		if (p->type == part_simple)
		{
			if (alpha > 0.5)
				alpha = 1;
			else
				alpha *= 2;
		}

		time2 = time*time;
		org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2;
		org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2;
		bak = org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2*grav;

		org[2] += 1;
		contents = CL_PMpointcontents(org);
		if (!(p->type == part_tracer || p->type == part_plume || p->type == part_wake || p->type == part_beam || p->type == part_rail_beam || p->type == part_spiral || p->type == part_blood || p->type == part_fast_blood || p->type == part_greenblood))
		{
			if (contents & MASK_SOLID)
			{	// kill particle in solid
				p->next = free_particles;
				free_particles = p;
				continue;
			}
		}
		org[2] = bak;

		p->next = NULL;
		if (!tail)
			active = tail = p;
		else
		{
			tail->next = p;
			tail = p;
		}

		if (alpha > 1.0)
			alpha = 1;

		if(p->type == part_blood || p->type == part_fast_blood || p->type == part_greenblood)
		{
			trace_t trace = CL_PMTraceWorld (p->org, vec3_origin, vec3_origin, org, MASK_SOLID);

			if (trace.fraction == 0)
			{
				p->alpha = 0;	// kill the particle
				continue;		// don't add the particle
			}
			else
				if (trace.fraction > 0 && trace.fraction < 1)
				{
					bool dirty;

					if(p->type != part_fast_blood)
						dirty = ((rand()&255)<64);	/// SpeedUp: Пусть марают только 25 % партиклей blood
					else
						dirty = ((rand()&255)<16);	/// SpeedUp: Пусть марают только 6.25 % партиклей fast_blood

					if(dirty)
					{
						int mintime, maxtime;
						if(p->type != part_fast_blood)
						{
							mintime = 10;
							maxtime = 15;
						}
						else
						{
							mintime = 2;	// part_fast_blood
							maxtime = 3;
						}

						float lt = r_bloodlifetime->value;
						if (lt <= 0.1 )	lt = 0.1;
						mintime *= lt;
						maxtime *= lt;
						if (p->type == part_greenblood)
							R_SpawnDecal(trace.endpos, trace.plane.normal, greenblood_mrk_object[int(RandomMinMax(-0.4, 7.4)+0.5)], 8, 16, mintime, maxtime, 0, 360, dt_mul, false, 0, 0, NULL, false);
						else
							R_SpawnDecal(trace.endpos, trace.plane.normal, blood_mrk_object[int(RandomMinMax(-0.4, 7.4)+0.5)], 8, 16, mintime, maxtime, 0, 360, dt_mul, false, 0, 0, NULL, false);
					}
					p->alpha = 0;	// kill the particle
					continue;		// don't add the particle
				}
		}

		if (p->flags & PARTICLE_FRICTION)
		{
			if (contents & MASK_WATER)
			{
				if (contents & CONTENTS_WATER)
				{
					VectorScale(p->vel, 0.35, p->vel);
					VectorScale(p->accel, 0.35, p->accel);
				}
				else if (contents & CONTENTS_SLIME)
				{
					VectorScale(p->vel, 0.25, p->vel);
					VectorScale(p->accel, 0.25, p->accel);
				}
				else if (contents & CONTENTS_LAVA)
				{
					VectorScale(p->vel, 0.15, p->vel);
					VectorScale(p->accel, 0.15, p->accel);
				}
				else if(contents & MASK_SOLID)
				{
					p->alpha = 0;	// kill the particle
					continue;		// don't add the particle
				}

				p->flags &= ~PARTICLE_FRICTION;
				p->time = cl.gameTime;
				VectorCopy(org, p->org);
				p->flags &= ~PARTICLE_STRETCH;
			}
		}

		if (p->flags & PARTICLE_BOUNCE)
		{
			trace_t trace = CL_PMTraceWorld (p->lastOrg, vec3_origin, vec3_origin, org, MASK_SOLID);

			if (trace.fraction > 0 && trace.fraction < 1)
			{
				vec3_t	vel;
				float time = cl.gameTime - (cls.frametime + cls.frametime * trace.fraction) * 1000;
				time = (time - p->time) * 0.001;

				VectorSet(vel, p->vel[0], p->vel[1], p->vel[2] + p->accel[2] * time * grav);
				VectorReflect(vel, trace.plane.normal, p->vel);
				VectorScale(p->vel, 0.6, p->vel);
				if (trace.plane.normal[2] > 0 && p->vel[2] < 1)
				{
					if (trace.plane.normal[2] > 0.9)
					{
						VectorClear(p->vel);
						VectorClear(p->accel);
						p->flags &= ~PARTICLE_BOUNCE;
					}
					else
					{
						float dot = DotProduct(p->vel, trace.plane.normal);
						VectorMA(p->vel, -dot, trace.plane.normal, p->vel);
						dot = DotProduct(p->accel, trace.plane.normal);
						VectorMA(p->accel, -dot, trace.plane.normal, p->accel);
					}
				}

				VectorCopy(trace.endpos, org);
				p->time = cl.gameTime;
				VectorCopy(org, p->org);
				p->flags &= ~PARTICLE_STRETCH;
			}
		}

		float blend = 1 - alpha;
		color_r = p->color_r0 * alpha + p->color_r1 * blend;
		color_g = p->color_g0 * alpha + p->color_g1 * blend;
		color_b = p->color_b0 * alpha + p->color_b1 * blend;

		if (p->type == part_sparks)
		{
			VectorSubtract(p->lastOrg, p->org, p->length);
			VectorScale(p->length, 0.125, p->length);
			VectorCopy(org, p->lastOrg);
		}

		if (p->flags & (PARTICLE_BOUNCE | PARTICLE_STRETCH))
		{
			V_AddParticle (p->lastOrg, color_r, color_g, color_b, alpha, p->type, p->length, p->size);
			VectorCopy(org, p->lastOrg);
		}
		else
			V_AddParticle (org, color_r, color_g, color_b, alpha, p->type, p->length, p->size);
	}

	active_particles = active;
}


bool R_CullPoint (vec3_t org)
{
	int		i;

	for (i=0 ; i<4 ; i++)
		if (DotProduct(org, frustum[i].normal) < frustum[i].dist)
			return true;

	return false;
}


void CL_AddClEntities ()
{
	if (!r_drawentities->value)
		return;

	if (r_maxDistance->value <= 0)
		return;

	// Berserker: smart remover
	if (cls.state == ca_active && !cl_paused->value && !m_menudepth)
	{
		if (cl_maxfps->value >= r_fpsThreshold->value)
		{
			if (fps_refreshed && r_fpsThreshold->value > 0)
			{
				if (fps < r_fpsThreshold->value)
				{
					for (int q=0; q < (r_fpsThreshold->value - fps); q++)
					{	// грубая корреляция между разницей FPS и кол-вом удаляемых объектов
						R_FreeClEntities();
						if (R_FreeDecal())	// MAX_DECALS в 4 раза больше, чем MAX_CLENTITIES
							if (R_FreeDecal())
								if (R_FreeDecal())
									R_FreeDecal();
					}
					fps_refreshed = false;
///					Com_DPrintf("Smart remove: limit %i fps\n", (int)maxfps);
				}
			}
		}
	}

	entity_t		ent;
	clentity_t		*p, *next;
	clentity_t		*active, *tail;
	vec3_t			org, dir;
	float			alpha;
	int				contents, flag_mask;
	bool			onground;
	float			time, time2, dist, grav = Cvar_VariableValue("sv_gravity");

	if(!grav)
		grav = 1;
	else
		grav /= 800;
	grav *= 1.5;

	active = NULL;
	tail = NULL;

	Memset (&ent, 0, sizeof(ent));
	if(!r_teshadows->value)
		flag_mask = RF_NOCASTSHADOW;
	else
		flag_mask = 0;

	for (p=active_clentities ; p ; p=next)
	{
		next = p->next;

		time = cl.leveltime - p->time;
		alpha = p->alpha + time*p->alphavel;
		if (alpha <= 0 || !p->model)
		{	// faded out
			p->next = free_clentities;
			free_clentities = p;
			continue;
		}

		time2 = time*time;
		org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2;
		org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2;
		org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2*grav;

		org[2] -= 3;
		contents = CL_PMpointcontents(org);
		org[2] += 3;
		onground = contents & MASK_SOLID;
		if (onground)
		{
			p->flags &= ~PARTICLE_ROTATE;
			if (p->model == cl_mod_leg)
			{	// HACK: модель leg особая: при нулевых углах стоит.
				p->ang[0] = 90;
				p->ang[1] += time * p->avel[1];
				p->ang[2] += time * p->avel[2];
			}
			else
			{
				p->ang[0] = p->ang[2] = 0;
				p->ang[1] += time * p->avel[1];
			}
			VectorClear(p->avel);
		}
		else if (p->flags & PARTICLE_STOPED)
		{
			p->flags &= ~PARTICLE_STOPED;
			p->flags |= PARTICLE_BOUNCE;
			p->accel[2] = -4*PARTICLE_GRAVITY;
			// Reset
			p->alpha = 1;
			p->time = cl.leveltime;
			VectorCopy(org, p->org);
		}

		// установим лимит видимости осколков
		dist = r_maxDistance->value * p->model->radius / 8.0;
		dist *= dist;

		p->next = NULL;
		if (!tail)
			active = tail = p;
		else
		{
			tail->next = p;
			tail = p;
		}

		if (alpha > 1.0)
			alpha = 1;

		if (p->type && p->trailcount && !onground)
		{
			centity_t cent;
			cent.trailcount = p->trailcount;
			CL_DiminishingTrail(p->lastOrg, org, &cent, p->type);
			p->trailcount = cent.trailcount;
		}

		if (p->flags & PARTICLE_FRICTION)
		{
			if (contents & MASK_WATER)
			{
				if(contents & CONTENTS_LAVA)
				{	// kill entity in lava
					if (p->model->radius > 15)		// шумят только большие осколки
						S_StartSound (org, 0, CHAN_AUTO, cl_sfx_lava, 0.5, _ATTN_NORM, 0);
					VectorSet(dir, 0,0,1);
					CL_ParticleEffect3 (org, dir, p_smoke_r->value*16,p_smoke_g->value*16,p_smoke_b->value*16, 6, part_smoke);		// Дым
					p->alpha = 0;
					continue;
				}
				else if(contents & CONTENTS_SLIME)
				{	// kill entity in slime
					VectorScale(p->vel, 0.1, p->vel);
					VectorScale(p->accel, 0.3, p->accel);
					p->flags &= ~(PARTICLE_FRICTION|PARTICLE_ROTATE|PARTICLE_STRETCH);
					p->time = cl.leveltime;
					VectorCopy(org, p->org);
					if (p->model->radius > 15)		// шумят только большие осколки
						S_StartSound (org, 0, CHAN_AUTO, cl_sfx_lava, 0.5, _ATTN_NORM, 0);
					p->alpha = 1.0;		// пусть в кислоте джибс живёт максимум 2 секунды
					p->alphavel = -(1.0 + frand()*0.1) / min(2.0, Cvar_VariableValue("sv_gibs"));
					p->flags |= PARTICLE_BURNING;
					continue;
				}
				else
				{	// CONTENTS_WATER
					VectorScale(p->vel, 0.1, p->vel);
					VectorScale(p->accel, 0.3, p->accel);
					p->flags &= ~(PARTICLE_FRICTION|PARTICLE_ROTATE|PARTICLE_STRETCH);
					p->time = cl.leveltime;
					VectorCopy(org, p->org);
					if (p->model->radius > 15)		// шумят только большие осколки
						if(!(cl.frame.playerstate.rdflags & RDF_UNDERWATER))	// шум слышен только над водой
							S_StartSound (org, 0, CHAN_AUTO, cl_sfx_watershell, 1, _ATTN_NORM, 0);
				}
			}
		}

		if (p->flags & PARTICLE_BURNING)
		{
			VectorSet(dir, 0,0,1);
			CL_ParticleEffect3 (org, dir, p_smoke_r->value*128,p_smoke_g->value*128,p_smoke_b->value*128, 6, part_smoke);		// Дым
		}

		if (p->flags & PARTICLE_BOUNCE)
		{
			trace_t trace = CL_PMTraceWorld (p->lastOrg, vec3_origin, vec3_origin, org, MASK_SOLID);

			if (trace.fraction > 0 && trace.fraction < 1)
			{
				vec3_t	vel;
				float time = cl.leveltime - cls.frametime + cls.frametime * trace.fraction - p->time;

				VectorSet(vel, p->vel[0], p->vel[1], p->vel[2] + p->accel[2] * time * grav);
				VectorReflect(vel, trace.plane.normal, p->vel);
				if (p->model->radius > 15)
					VectorScale(p->vel, 0.5, p->vel);	// пусть мясо и осколки немного скачут
				else
					VectorScale(p->vel, 0.7, p->vel);
				if (trace.plane.normal[2] > 0 && p->vel[2] < 1)
				{
					if (trace.plane.normal[2] > 0.9)
					{
						// выровняем модель по горизонтали пола
						if (p->model == cl_mod_leg)
						{	// HACK: модель leg особая: при нулевых углах стоит.
							p->ang[0] = 90;
							p->ang[1] += time * p->avel[1];
							p->ang[2] += time * p->avel[2];
						}
						else
						{
							p->ang[0] = p->ang[2] = 0;
							p->ang[1] += time * p->avel[1];
						}
						VectorClear(p->vel);
						VectorClear(p->accel);
						VectorClear(p->avel);
						p->flags &= ~PARTICLE_BOUNCE;
						p->flags |= PARTICLE_STOPED;
					}
					else
					{
						float dot = DotProduct(p->vel, trace.plane.normal);
						VectorMA(p->vel, -dot, trace.plane.normal, p->vel);
						dot = DotProduct(p->accel, trace.plane.normal);
						VectorMA(p->accel, -dot, trace.plane.normal, p->accel);
					}
				}

				VectorCopy(trace.endpos, org);
				p->time = cl.leveltime;
				VectorCopy(org, p->org);
				p->flags &= ~PARTICLE_STRETCH;

				if (p->model->hit_sound_index == CLMOD_SFX_MSHELL)
					S_StartSound (org, 0, CHAN_AUTO, cl_sfx_mshell, 0.7, _ATTN_NORM, 0);
				else if (p->model->hit_sound_index == CLMOD_SFX_SSHELL)
					S_StartSound (org, 0, CHAN_AUTO, cl_sfx_sshell, 0.7, _ATTN_NORM, 0);
				else if (p->model->hit_sound_index == CLMOD_SFX_GLASS)
					S_StartSound (org, 0, CHAN_AUTO, cl_sfx_debris_glass, 1, _ATTN_NORM, 0);
				else if (p->model->hit_sound_index == CLMOD_SFX_FLESH)
				{
					int s, r = rand() & 7;
					float v;
					if (r < 4)	// 50%
					{
						s = cl_sfx_debris_flesh;
						v = 1;
					}
					else if (r < 6)	// 25%
					{
						s = cl_sfx_debris_flesh1;
						v = 0.6;
					}
					else	// 25%
					{
						s = cl_sfx_debris_flesh2;
						v = 0.75;
					}
					S_StartSound(org, 0, CHAN_AUTO, s, v, _ATTN_NORM, 0);
				}
				else if (p->model->hit_sound_index == CLMOD_SFX_METAL)
					S_StartSound (org, 0, CHAN_AUTO, cl_sfx_debris, 0.6, _ATTN_NORM, 0);
				else if (p->model->hit_sound_index == CLMOD_SFX_GEAR)
					S_StartSound (org, 0, CHAN_AUTO, cl_sfx_debris_metal, 0.6, _ATTN_NORM, 0);
			}
		}
		if (p->flags & (PARTICLE_BOUNCE | PARTICLE_STRETCH))
		{
			VectorCopy(p->lastOrg, ent.origin);
			VectorCopy(org, p->lastOrg);
		}
		else
			VectorCopy(org, ent.origin);

		if (CL_PMpointcontents(ent.origin) & MASK_SOLID)
		{
#if 0
			p->alpha = 0;	// kill if entity in solid
			continue;
#else
			/// Berserker: вместо вырезания, просто останавливаем! И тогда пропадат и падать не будут!
			p->ang[0] += time * p->avel[0];
			p->ang[1] += time * p->avel[1];
			p->ang[2] += time * p->avel[2];
			VectorClear(p->vel);
			VectorClear(p->accel);
			VectorClear(p->avel);
			p->flags &= ~PARTICLE_BOUNCE;
			p->flags |= PARTICLE_STOPED;
#endif
		}

		vec3_t	tmp;
		VectorSubtract(r_origin, ent.origin, tmp);
		if (DotProduct(tmp,tmp)<dist)
		{
			ent.flags = flag_mask;

			bool cull;
			if (p->model->radius > 10)
				cull = false;	// большие осколки добавляем в entity list, потом откуллим...
			else
				cull = R_CullPoint(ent.origin);		// мелочь отсеем сразу, она не так заметна
			if(cull)
			{
				if(r_mirrors->value)
					ent.flags = flag_mask | RF_VIEWERMODEL;		// рисуем только в зеркале!
				else
					continue;
			}

			ent.model = p->model;

			int leafnum = CM_PointLeafnum (p->org);
			int area = CM_LeafArea (leafnum);
			if (area)
			{	/// Berserker: Проверяем, не закрыто ли дверями?
				if (cl.frame.areabits[area>>3] & (1<<(area&7)))
				{
					byte	fatvis[MAX_MAP_LEAFS/8];
					if (R_MarkEntityLeaves (&ent, fatvis))
					{
						if (HasSharedLeafs (fatvis, viewvis__))
						{
							ent.angles[0] = p->ang[0] + ((p->flags & PARTICLE_ROTATE) ? (time * p->avel[0]) : 0);
							ent.angles[1] = p->ang[1] + ((p->flags & PARTICLE_ROTATE) ? (time * p->avel[1]) : 0);
							ent.angles[2] = p->ang[2] + ((p->flags & PARTICLE_ROTATE) ? (time * p->avel[2]) : 0);
							VectorCopy(ent.origin, ent.oldorigin);

							currententity = &ent;	// for R_AddModelLight / CheckEntityFrame...
							R_AddModelLight();

							if (ent.model->type == mod_alias || ent.model->type == mod_alias_md3)
							{
								if (ent.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE))
								{
									int bak = ent.flags;
									// Копируем некоторые флаги model во флаги entity
									ent.flags = (ent.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE));
									V_AddEntity (&ent, NULL, NULL);
									ent.flags = bak;
								}
								if (p->skin)
								{
									ent.skin = p->skin;
									ent.bump = p->bump;
									ent.light = p->light;
								}
								else
									ent.skin = ent.bump = ent.light = NULL;
								// Копируем некоторые флаги model во флаги entity
								ent.flags |= (ent.model->flags & (RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|RF_LIGHT|RF_FULLBRIGHT|RF_DISTORT|RF_TRANSLUCENT|RF_NOSELFSHADOW));
								V_AddEntity (&ent, NULL, NULL);
							}
							else
							{
								if (p->skin)
								{
									ent.skin = p->skin;
									ent.bump = p->bump;
									ent.light = p->light;
								}
								else
									ent.skin = ent.bump = ent.light = NULL;
								// Копируем некоторые флаги model во флаги entity
								ent.flags |= (ent.model->flags & (RF_CHROME|RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|RF_LIGHT|RF_FULLBRIGHT|RF_DISTORT|RF_TRANSLUCENT|RF_NOSELFSHADOW));
								V_AddEntity (&ent, NULL, NULL);
							}
						}
					}
				}
			}
		}
	}

	active_clentities = active;
}


void CL_AddDLights ()
{
	int			i;
	cdlight_t	*dl;
	dl = cl_dlights;

	for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
	{
		if (!dl->radius)
			continue;
		V_AddLight (dl->origin, dl->radius,	dl->color[0], dl->color[1], dl->color[2], 0,0,0,0,0,0,0, NULL, 0, 0, rend_all);
	}
}


void CL_AddLocalEntities ()
{
	int			i, am;
	entity_t	ent;

	for (am=0; am<aliasmodel_counter; am++)
	{
		if (!strcmp(amdl_list[am].label, "SkyModel"))
		{
			amdl_list[am].nouse = true;
			Memset (&ent, 0, sizeof(ent));
			ent.model = amdl_list[am].mdl;
			if (!ent.model)
				continue;
			ent.sky_model = true;
			VectorCopy(amdl_list[am].origin, ent.origin);
			VectorCopy(amdl_list[am].angles, ent.angles);
			if (amdl_list[am].animframe)
			{
				if (ent.model->numframes <= 1)
				{
					ent.frame = 0;
					ent.oldframe = 0;
				}
				else
				{
					if (amdl_list[am].frame < amdl_list[am].frame_end)
					{
						ent.frame = cl.time*0.01;
						ent.frame = (amdl_list[am].frame + ent.frame % (amdl_list[am].frame_end - amdl_list[am].frame + 1)) % ent.model->numframes;
						if (ent.frame==amdl_list[am].frame)
							ent.oldframe = amdl_list[am].frame_end % (ent.model->numframes-1);
						else
							ent.oldframe = ent.frame - 1;
					}
					else
					{
						if (amdl_list[am].frame == amdl_list[am].frame_end && amdl_list[am].frame_end)
							goto noanim;
						ent.flags = RF_ANIMFRAME;		// auto animation
						ent.frame = cl.time*0.01;
						ent.frame = ent.frame % ent.model->numframes;
						if (ent.frame==0)
							ent.oldframe = ent.model->numframes - 1;
						else
							ent.oldframe = ent.frame - 1;
					}
				}
				ent.backlerp = 1 - cl.lerpfrac;
			}
			else
			{
noanim:			ent.frame = amdl_list[am].frame;
				ent.backlerp = 0;
			}
			ent.skinnum = amdl_list[am].skinnum;
			goto drw;
		}
		if (!strcmp(amdl_list[am].label, " "))	// special case: такие модели создаём по-любому
		{
			amdl_list[am].nouse = true;
			if (!amdl_list[am].mdl)
				continue;
			/// Berserker: Проверяем видимость модели
			for (i=0; i<9; i++)
				if (cl.frame.areabits[amdl_list[am].areas[i]>>3] & (1<<(amdl_list[am].areas[i]&7)))
					break;
			if (i==9)
				continue;
			if (!HasSharedLeafs (amdl_list[am].vis, viewvis__))
				continue;

			Memset (&ent, 0, sizeof(ent));
			ent.model = amdl_list[am].mdl;
			VectorCopy(amdl_list[am].origin, ent.origin);
			VectorCopy(amdl_list[am].angles, ent.angles);
			if (amdl_list[am].animframe)
			{
				if (ent.model->numframes <= 1)
				{
					ent.frame = 0;
					ent.oldframe = 0;
				}
				else
				{
					if (amdl_list[am].frame < amdl_list[am].frame_end)
					{
						ent.frame = cl.time*0.01;
						ent.frame = (amdl_list[am].frame + ent.frame % (amdl_list[am].frame_end - amdl_list[am].frame + 1)) % ent.model->numframes;
						if (ent.frame==amdl_list[am].frame)
							ent.oldframe = amdl_list[am].frame_end % (ent.model->numframes-1);
						else
							ent.oldframe = ent.frame - 1;
					}
					else
					{
						if (amdl_list[am].frame == amdl_list[am].frame_end && amdl_list[am].frame_end)
							goto noanim2;
						ent.flags = RF_ANIMFRAME;		// auto animation
						ent.frame = cl.time*0.01;
						ent.frame = ent.frame % ent.model->numframes;
						if (ent.frame==0)
							ent.oldframe = ent.model->numframes - 1;
						else
							ent.oldframe = ent.frame - 1;
					}
				}
				ent.backlerp = 1 - cl.lerpfrac;
			}
			else
			{
noanim2:		ent.frame = amdl_list[am].frame;
				ent.backlerp = 0;
			}
			ent.skinnum = amdl_list[am].skinnum;
			if (ent.frame >= ent.model->numframes)
				ent.frame = ent.model->numframes - 1;
drw:		// Копируем некоторые флаги model во флаги entity
			if (ent.model->type == mod_alias || ent.model->type == mod_alias_md3)
			{
				VectorCopy(ent.origin, ent.oldorigin);
				// add model lights
				currententity = &ent;	// for R_AddModelLight / CheckEntityFrame...
				R_AddModelLight();
				// add entity
				if (ent.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE))
				{
					int bak = ent.flags;
					ent.flags = (ent.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE));
					V_AddEntity (&ent, NULL, &amdl_list[am]);
					ent.flags = bak;
				}
				ent.flags |= (ent.model->flags & (RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|RF_LIGHT|RF_FULLBRIGHT|RF_DISTORT|RF_NOCASTSHADOW|RF_TRANSLUCENT));
				V_AddEntity (&ent, NULL, &amdl_list[am]);
			}
			else
			{
				// Копируем некоторые флаги model во флаги entity
				ent.flags |= (ent.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE|RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|RF_LIGHT|RF_FULLBRIGHT|RF_DISTORT|RF_NOCASTSHADOW|RF_TRANSLUCENT));
				VectorCopy(ent.origin, ent.oldorigin);
				// add model lights
				currententity = &ent;	// for R_AddModelLight / CheckEntityFrame...
				R_AddModelLight();
				// add entity
				V_AddEntity (&ent, NULL, &amdl_list[am]);
			}
			amdl_list[am].nouse = false;
			VectorCopy(ent.origin, amdl_list[am].real_origin);
		}
	}
}


void CL_ComputeVis()
{
	byte	*vis;
	byte	fatvis[MAX_MAP_LEAFS/8];
	int		i, c;
	int		r_viewcluster__, r_viewcluster2__;
	mleaf_t	*leaf;

	leaf = Mod_PointInLeaf(cl.refdef.vieworg, r_worldmodel);
	r_viewcluster__ = r_viewcluster2__ = leaf->cluster;
	// check above and below so crossing solid water doesn't draw wrong
	if (!leaf->contents)
	{	// look down a bit
		vec3_t	temp;

		VectorCopy (cl.refdef.vieworg, temp);
		temp[2] -= 8;
		leaf = Mod_PointInLeaf (temp, r_worldmodel);
		if ( !(leaf->contents & CONTENTS_SOLID) && (leaf->cluster != r_viewcluster2__) )
			r_viewcluster2__ = leaf->cluster;
	}
	else
	{	// look up a bit
		vec3_t	temp;

		VectorCopy (cl.refdef.vieworg, temp);
		temp[2] += 8;
		leaf = Mod_PointInLeaf (temp, r_worldmodel);
		if ( !(leaf->contents & CONTENTS_SOLID) && (leaf->cluster != r_viewcluster2__) )
			r_viewcluster2__ = leaf->cluster;
	}

	if (/*r_novis->value ||*/ r_viewcluster__ == -1 || !r_worldmodel->vis)
		Memset(&viewvis, 0xff, (r_worldmodel->numleafs+7)>>3);	// all visible
	else
	{
		vis = Mod_ClusterPVS (r_viewcluster__, r_worldmodel);
		// may have to combine two clusters because of solid water boundaries
		if (r_viewcluster2__ != r_viewcluster__)
		{
			Memcpy (fatvis, vis, (r_worldmodel->numleafs+7)>>3);
			vis = Mod_ClusterPVS (r_viewcluster2__, r_worldmodel);
			c = (r_worldmodel->numleafs+31)/32;
			for (i=0 ; i<c ; i++)
				((int *)fatvis)[i] |= ((int *)vis)[i];
			vis = fatvis;
		}
		Memcpy(&viewvis__, vis, (r_worldmodel->numleafs+7)>>3);
	}
}


/*
===============
CL_AddEntities

Emits all entities, particles, and lights to the refresh
===============
*/
void CL_AddEntities ()
{
	if (cls.state != ca_active)
		return;

	if (cl.time > cl.frame.servertime)
	{
		if (cl_showclamp->value)
			Com_Printf ("high clamp %i\n", cl.time - cl.frame.servertime);
		cl.time = cl.frame.servertime;
		cl.lerpfrac = 1.0;
	}
	else if (cl.time < cl.frame.servertime - 100)
	{
		if (cl_showclamp->value)
			Com_Printf ("low clamp %i\n", cl.frame.servertime-100 - cl.time);
		cl.time = cl.frame.servertime - 100;
		cl.lerpfrac = 0;
	}
	else
		cl.lerpfrac = 1.0 - (cl.frame.servertime - cl.time) * 0.01;

	if (cl_timedemo->value)
		cl.lerpfrac = 1.0;

	cl.leveltime = cl.gameTime / 1000.0;		/// cl.time / 1000.0;

	if (needsToLoadClientEntities && effectsInited)
	{
		CL_ReadClientFile (needsToLoadClientEntities == 2);
		needsToLoadClientEntities = 0;
		effectsInited = false;
	}

	CL_CalcViewValues ();
	// PMM - moved this here so the heat beam has the right values for the vieworg, and can lock the beam to the gun
		CL_ComputeVis();	// перед CL_AddPacketEntities и CL_AddLocalEntities !!!
		CL_AddPacketEntities (&cl.frame);
		CL_AddLocalEntities();
	CL_AddTEnts ();
	CL_Beams2Particles();
	CL_AddParticles ();
	CL_AddClEntities ();
	CL_AddDLights ();
}


void CL_ParseFrame ()
{
	int			cmd;
	int			len;
	frame_t		*old;

	Memset (&cl.frame, 0, sizeof(cl.frame));

	cl.frame.serverframe = MSG_ReadLong (&net_message);
	cl.frame.deltaframe = MSG_ReadLong (&net_message);
#if 0
	cl.frame.servertime = cl.frame.serverframe*100;
#else
	if (cls.state != ca_active)
		cl.frame.servertime = 0;
	else
	{
		//r1: initial_server_frame is for fixing precision loss with high serverframes
		cl.frame.servertime = (cl.frame.serverframe - cl.initial_server_frame) * 100;
	}
#endif
	// BIG HACK to let old demos continue to work
	if (cls.serverProtocol != 26)
		cl.surpressCount = MSG_ReadByte (&net_message);

	// If the frame is delta compressed from data that we
	// no longer have available, we must suck up the rest of
	// the frame, but not use it, then ask for a non-compressed
	// message 
	if (cl.frame.deltaframe <= 0)
	{
		cl.frame.valid = true;		// uncompressed frame
		old = NULL;
		cls.demowaiting = false;	// we can start recording now
	}
	else
	{
		old = &cl.frames[cl.frame.deltaframe & UPDATE_MASK];
		if (!old->valid)
		{	// should never happen
			Com_Printf ("^3Delta from invalid frame (not supposed to happen!).\n");
		}
		if (old->serverframe != cl.frame.deltaframe)
		{	// The frame that the server did the delta from
			// is too old, so we can't reconstruct it properly.
			Com_Printf ("^3Delta frame too old.\n");
		}
		else if (cl.parse_entities - old->parse_entities > MAX_PARSE_ENTITIES-128)
		{
			Com_Printf ("^3Delta parse_entities too old.\n");
		}
		else
			cl.frame.valid = true;	// valid delta parse
	}

	// clamp time 
	if (cl.time > cl.frame.servertime)
		cl.time = cl.frame.servertime;
	else if (cl.time < cl.frame.servertime - 100)
		cl.time = cl.frame.servertime - 100;

	// read areabits
	len = MSG_ReadByte (&net_message);
	MSG_ReadData (&net_message, &cl.frame.areabits, len);

	// read playerinfo
	cmd = MSG_ReadByte (&net_message);
	if (cmd != svc_playerinfo)
		Com_Error (ERR_DROP, "CL_ParseFrame: not playerinfo");
	CL_ParsePlayerstate (old, &cl.frame);

	// read packet entities
	cmd = MSG_ReadByte (&net_message);
	if (cmd != svc_packetentities)
		Com_Error (ERR_DROP, "CL_ParseFrame: not packetentities");
	CL_ParsePacketEntities (old, &cl.frame);

	// save the frame off in the backup array for later delta comparisons
	cl.frames[cl.frame.serverframe & UPDATE_MASK] = cl.frame;

	if (cl.frame.valid)
	{
		// getting a valid frame message ends the connection process
		if (cls.state != ca_active)
		{
			cls.state = ca_active;

			//r1: fix for precision loss with high serverframes (when map runs for over several hours)
			cl.initial_server_frame = cl.frame.serverframe;
///			cl.frame.servertime = (cl.frame.serverframe - cl.initial_server_frame) * 100;
			cl.frame.servertime = 0;		/// Berserker:     ;D

			cl.force_refdef = true;
			cl.predicted_origin[0] = cl.frame.playerstate.pmove.origin[0]*0.125;
			cl.predicted_origin[1] = cl.frame.playerstate.pmove.origin[1]*0.125;
			cl.predicted_origin[2] = cl.frame.playerstate.pmove.origin[2]*0.125;
			VectorCopy (cl.frame.playerstate.viewangles, cl.predicted_angles);
			if (cls.disable_servercount != cl.servercount && cl.refresh_prepped)
				SCR_EndLoadingPlaque ();	// get rid of loading plaque
		}
		cl.sound_prepped = true;	// can start mixing ambient sounds

		// fire entity events
		CL_FireEntityEvents (&cl.frame);

		CL_CheckPredictionError ();
	}
}


int entitycmpfnc( const entity_t *a, const entity_t *b )
{
	// all other models are sorted by model then skin
	if ( a->model == b->model )
		return ( ( int ) a->skin - ( int ) b->skin );
	else
		return ( ( int ) a->model - ( int ) b->model );
}

void Draw_GetPicSize (int *w, int *h, char *pic)
{
	image_t *gl;

	gl = Draw_FindPic (pic);
	if (!gl)
	{
		*w = *h = -1;
		return;
	}
	*w = gl->width;
	*h = gl->height;
}


void SCR_DrawCrosshair ()
{
	float	scale;
	int		size_x, size_y;

	if (!scr_draw2d->value)
		return;

	if (!crosshair->value && !r_crosshair_image->string[0])
		return;

	if (crosshair->modified || r_crosshair_image->modified)
	{
		crosshair->modified = false;
		r_crosshair_image->modified = false;
		SCR_TouchPics ();
	}

	if (!crosshair_pic[0])
		return;

	scale = scr_crosshair_scale->value;
	size_x = scale * crosshair_width;
	if (size_x <= 1)
		return;
	size_y = scale * crosshair_height;
	if (size_y <= 1)
		return;

	glEnable(GL_BLEND);
	GL_BlendFunc (GL_ONE, GL_ONE);
	Draw_PicScaled (scr_vrect.x + ((scr_vrect.width - size_x)>>1), scr_vrect.y + ((scr_vrect.height - size_y)>>1), scale * 2, scale * 2, crosshair_pic);
	glDisable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


void CL_EmitParticles (vec3_t org, vec3_t dir, byte color_r0, byte color_g0, byte color_b0, byte color_r1, byte color_g1, byte color_b1, int count, parttype_t type, float gravity, float vel, float spread, int flags, float alphavel)
{
	int			i;
	cparticle_t	*p;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = flags;
		p->time = cl.gameTime;
		p->color_r0 = color_r0;
		p->color_g0 = color_g0;
		p->color_b0 = color_b0;
		p->color_r1 = color_r1;
		p->color_g1 = color_g1;
		p->color_b1 = color_b1;

		p->vel[0] = (spread*crand() + dir[0])*vel;
		p->vel[1] = (spread*crand() + dir[1])*vel;
		p->vel[2] = (spread*crand() + dir[2])*vel;

		VectorCopy(org, p->org);
		VectorCopy(org, p->lastOrg);

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -gravity;
		p->alpha = 1;

		if (alphavel)
			p->alphavel = -1.0 / alphavel;
		else
			p->alphavel = -1.0 / (0.5 + frand()*0.3);

		p->type = type;
	}
}

bool R_MarkEntityLeaves(entity_t *ent, byte *fatvis)
{
	int		leafs[MAX_MAP_LEAFS];
	int		i, j, count;
	int		longs;
	vec3_t	mins, maxs, org;
	byte	vis[MAX_MAP_LEAFS/8];
	byte	vis_[MAX_MAP_LEAFS/8];
	int		areas[9];
	model_t	*mod = ent->model;

	for (i=0 ; i<3 ; i++)
	{
		mins[i] = ent->origin[i] + mod->mins[i];
		maxs[i] = ent->origin[i] + mod->maxs[i];
	}

	areas[8] = CM_LeafArea (CM_PointLeafnum (ent->origin));

	Memset(fatvis, 0, (((r_worldmodel->numleafs+31)>>5)<<2)/*MAX_MAP_LEAFS/8*/);
	for (j=0; j<8; j++)
	{
		vec3_t	mins_, maxs_;

		if (j==0)
			VectorSet(org, mins[0], mins[1], mins[2]);
		else if (j==1)
			VectorSet(org, mins[0], mins[1], maxs[2]);
		else if (j==2)
			VectorSet(org, mins[0], maxs[1], mins[2]);
		else if (j==3)
			VectorSet(org, mins[0], maxs[1], maxs[2]);
		else if (j==4)
			VectorSet(org, maxs[0], mins[1], mins[2]);
		else if (j==5)
			VectorSet(org, maxs[0], mins[1], maxs[2]);
		else if (j==6)
			VectorSet(org, maxs[0], maxs[1], mins[2]);
		else
			VectorSet(org, maxs[0], maxs[1], maxs[2]);

		areas[j] = CM_LeafArea (CM_PointLeafnum (org));

		for (i=0 ; i<3 ; i++)
		{
			mins_[i] = org[i] - 8;
			maxs_[i] = org[i] + 8;
		}

		// строим vis-data
		Memcpy (vis_, CM_ClusterPVS(CM_LeafCluster (CM_PointLeafnum (org))), (((CM_NumClusters()+31)>>5)<<2));
		count = CM_BoxLeafnums (mins_, maxs_, leafs, r_worldmodel->numleafs/*MAX_MAP_LEAFS*/, NULL);
		if (count < 1)
			Com_Error (ERR_FATAL, "R_MarkEntityLeaves: count < 1");
		longs = (CM_NumClusters()+31)>>5;

		// convert leafs to clusters
		for (i=0 ; i<count ; i++)
			leafs[i] = CM_LeafCluster(leafs[i]);

		Memset(&vis, 0, (((r_worldmodel->numleafs+31)>>5)<<2)/*MAX_MAP_LEAFS/8*/);
		for (i=0 ; i<count ; i++)
			vis[leafs[i]>>3] |= (1<<(leafs[i]&7));
		// вырезаем кластеры, которых нет в списке leafs
		for (i=0 ; i<((r_worldmodel->numleafs+31)>>5)/*MAX_MAP_LEAFS/32*/ ; i++)
			((long *)vis_)[i] &= ((long *)vis)[i];
		// копируем результат в fatvis
		for (i=0 ; i<longs ; i++)
			((long *)fatvis)[i] |= ((long *)vis_)[i];
	}
	return (areas[0] || areas[1] || areas[2] || areas[3] || areas[4] || areas[5] || areas[6] || areas[7] || areas[8]);
}

bool R_MarkEmitLeaves(emit_t *emit, byte *fatvis)
{
	int		leafs[MAX_MAP_LEAFS];
	int		i, j, count;
	int		longs;
	vec3_t	mins, maxs, org;
	byte	vis[MAX_MAP_LEAFS/8];
	byte	vis_[MAX_MAP_LEAFS/8];
	int		areas[9];

	for (i=0 ; i<3 ; i++)
	{	/// для простоты пусть любой эмиттер занимает объём +/-64
		mins[i] = emit->origin[i] - 64;
		maxs[i] = emit->origin[i] + 64;
	}

	areas[8] = CM_LeafArea (CM_PointLeafnum (emit->origin));

	Memset(fatvis, 0, (((r_worldmodel->numleafs+31)>>5)<<2)/*MAX_MAP_LEAFS/8*/);
	for (j=0; j<8; j++)
	{
		vec3_t	mins_, maxs_;

		if (j==0)
			VectorSet(org, mins[0], mins[1], mins[2]);
		else if (j==1)
			VectorSet(org, mins[0], mins[1], maxs[2]);
		else if (j==2)
			VectorSet(org, mins[0], maxs[1], mins[2]);
		else if (j==3)
			VectorSet(org, mins[0], maxs[1], maxs[2]);
		else if (j==4)
			VectorSet(org, maxs[0], mins[1], mins[2]);
		else if (j==5)
			VectorSet(org, maxs[0], mins[1], maxs[2]);
		else if (j==6)
			VectorSet(org, maxs[0], maxs[1], mins[2]);
		else
			VectorSet(org, maxs[0], maxs[1], maxs[2]);

		areas[j] = CM_LeafArea (CM_PointLeafnum (org));

		for (i=0 ; i<3 ; i++)
		{
			mins_[i] = org[i] - 8;
			maxs_[i] = org[i] + 8;
		}

		// строим vis-data
		Memcpy (vis_, CM_ClusterPVS(CM_LeafCluster (CM_PointLeafnum (org))), (((CM_NumClusters()+31)>>5)<<2));
		count = CM_BoxLeafnums (mins_, maxs_, leafs, r_worldmodel->numleafs/*MAX_MAP_LEAFS*/, NULL);
		if (count < 1)
			Com_Error (ERR_FATAL, "R_MarkEmitLeaves: count < 1");
		longs = (CM_NumClusters()+31)>>5;

		// convert leafs to clusters
		for (i=0 ; i<count ; i++)
			leafs[i] = CM_LeafCluster(leafs[i]);

		Memset(&vis, 0, (((r_worldmodel->numleafs+31)>>5)<<2)/*MAX_MAP_LEAFS/8*/);
		for (i=0 ; i<count ; i++)
			vis[leafs[i]>>3] |= (1<<(leafs[i]&7));
		// вырезаем кластеры, которых нет в списке leafs
		for (i=0 ; i<((r_worldmodel->numleafs+31)>>5)/*MAX_MAP_LEAFS/32*/ ; i++)
			((long *)vis_)[i] &= ((long *)vis)[i];
		// копируем результат в fatvis
		for (i=0 ; i<longs ; i++)
			((long *)fatvis)[i] |= ((long *)vis_)[i];
	}
	return (areas[0] || areas[1] || areas[2] || areas[3] || areas[4] || areas[5] || areas[6] || areas[7] || areas[8]);
}


void CL_AddEmits()
{
	int				i, n;
	clightstyle_t	*ls, *cl_ls;

	if (!draw_emits)
		return;

	for (i=0; i<numEmits; i++)
	{
		{
			if(emits[i].label[0]==0)
				goto cont;

			if(emits[i].framecount==r_framecount-1)
			{
cont:			ls = &sv_lightstyle[emits[i].style];
				cl_ls = &cl_lightstyle[emits[i].cl_style];
				/// Кол-во партиклей
				n = emits[i].rspeed[2] * ls->val * cl_ls->val;
				if (n)
				{
					/// Berserker: Проверяем, не закрыто ли дверями?
					if (cl.frame.areabits[emits[i].area>>3] & (1<<(emits[i].area&7)))
					{
						byte	fatvis[MAX_MAP_LEAFS/8];
						if (R_MarkEmitLeaves (&emits[i], fatvis))
						{
							if (HasSharedLeafs (fatvis, viewvis__))
							{
								vec3_t	ang, dir;
								vec3_t	forward, right, up;

								if (emits[i].rspeed[0] || emits[i].rspeed[1])
								{
									ang[0] = emits[i].rspeed[0] * cl.leveltime;
									ang[1] = emits[i].rspeed[1] * cl.leveltime;
									ang[2] = 0;
									AngleVectors(ang, forward, right, up);
									dir[0] = DotProduct (emits[i].dir, forward);
									dir[1] = DotProduct (emits[i].dir, right);
									dir[2] = DotProduct (emits[i].dir, up);
								}
								else
									VectorCopy(emits[i].dir, dir);

								switch(emits[i].emit)
								{
								case part_blood:
								case part_fast_blood:
									if (!VectorCompare(emits[i].startcolor, vec3_origin))
										CL_EmitParticles(emits[i].origin, dir, emits[i].startcolor[0]*255,emits[i].startcolor[1]*255,emits[i].startcolor[2]*255, emits[i].endcolor[0]*255,emits[i].endcolor[1]*255,emits[i].endcolor[2]*255, n, emits[i].emit, emits[i].gravity ? emits[i].gravity*20 : PARTICLE_GRAVITY*4, emits[i].vel, emits[i].cone, 0, emits[i].alphavel);
									else
										CL_EmitParticles(emits[i].origin, dir, 80,0,0, 80,0,0, n, emits[i].emit, emits[i].gravity ? emits[i].gravity*20 : PARTICLE_GRAVITY*4, emits[i].vel, emits[i].cone, 0, emits[i].alphavel);
									break;
								case part_smoke:
								case part_bigsmoke:
									if (!VectorCompare(emits[i].startcolor, vec3_origin))
										CL_EmitParticles(emits[i].origin, dir, emits[i].startcolor[0]*255,emits[i].startcolor[1]*255,emits[i].startcolor[2]*255, emits[i].endcolor[0]*255,emits[i].endcolor[1]*255,emits[i].endcolor[2]*255, n, emits[i].emit, emits[i].gravity ? emits[i].gravity*20 : -PARTICLE_GRAVITY*2, emits[i].vel, emits[i].cone, 0, emits[i].alphavel);
									else
										CL_EmitParticles(emits[i].origin, dir, 200,200,200, 128,128,128, n, emits[i].emit, emits[i].gravity ? emits[i].gravity*20 : -PARTICLE_GRAVITY*2, emits[i].vel, emits[i].cone, 0, emits[i].alphavel);
									break;
								case part_splash:
									if (!VectorCompare(emits[i].startcolor, vec3_origin))
										CL_EmitParticles(emits[i].origin, dir, emits[i].startcolor[0]*255,emits[i].startcolor[1]*255,emits[i].startcolor[2]*255, emits[i].endcolor[0]*255,emits[i].endcolor[1]*255,emits[i].endcolor[2]*255, n, emits[i].emit, emits[i].gravity ? emits[i].gravity*20 : PARTICLE_GRAVITY*3, emits[i].vel, emits[i].cone, PARTICLE_BOUNCE | PARTICLE_FRICTION, emits[i].alphavel);
									else
										CL_EmitParticles(emits[i].origin, dir, 200,200,200, 200,200,200, n, emits[i].emit, emits[i].gravity ? emits[i].gravity*20 : PARTICLE_GRAVITY*3, emits[i].vel, emits[i].cone, PARTICLE_BOUNCE | PARTICLE_FRICTION, emits[i].alphavel);
									break;
								case part_bubble:
									if (!VectorCompare(emits[i].startcolor, vec3_origin))
										CL_EmitParticles(emits[i].origin, dir, emits[i].startcolor[0]*255,emits[i].startcolor[1]*255,emits[i].startcolor[2]*255, emits[i].endcolor[0]*255,emits[i].endcolor[1]*255,emits[i].endcolor[2]*255, n, emits[i].emit, emits[i].gravity ? emits[i].gravity*20 : -PARTICLE_GRAVITY, emits[i].vel, emits[i].cone, 0, emits[i].alphavel);
									else
										CL_EmitParticles(emits[i].origin, dir, 200,200,200, 200,200,200, n, emits[i].emit, emits[i].gravity ? emits[i].gravity*20 : -PARTICLE_GRAVITY, emits[i].vel, emits[i].cone, 0, emits[i].alphavel);
									break;
								case part_simple:
								default:
									if (!VectorCompare(emits[i].startcolor, vec3_origin))
										CL_EmitParticles(emits[i].origin, dir, emits[i].startcolor[0]*255,emits[i].startcolor[1]*255,emits[i].startcolor[2]*255, emits[i].endcolor[0]*255,emits[i].endcolor[1]*255,emits[i].endcolor[2]*255, n, emits[i].emit, emits[i].gravity ? emits[i].gravity*20 : PARTICLE_GRAVITY*2, emits[i].vel, emits[i].cone, PARTICLE_BOUNCE | PARTICLE_FRICTION, emits[i].alphavel);
									else
										CL_EmitParticles(emits[i].origin, dir, 255,255,255, 255,255,255, n, emits[i].emit, emits[i].gravity ? emits[i].gravity*20 : PARTICLE_GRAVITY*2, emits[i].vel, emits[i].cone, PARTICLE_BOUNCE | PARTICLE_FRICTION, emits[i].alphavel);
									break;
								}
							}
						}
					}
				}
			}
		}
	}
}


int entityDistcmpfnc( const entity_t *a, const entity_t *b )
{
	entity_t	*aa, *bb;
	aa = *(entity_t**)a;
	bb = *(entity_t**)b;

	// all other models are sorted by dist
	return (aa->dist > bb->dist);
}


int entityDistcmpfnc2( const entity_t *a, const entity_t *b )
{
	int	skna, sknb, dsta, dstb;

	// all other models are sorted by skin then distance
	skna = ( int ) a->skin;
	sknb = ( int ) b->skin;
	dsta = ( int ) a->dist;
	dstb = ( int ) b->dist;

	return (skna<16) + dsta - (sknb<<16) + dstb;
}


void V_RenderView()
{
	if (cls.state != ca_active)
		return;

	if (!cl.refresh_prepped)
		return;			// still loading

	if (cl_timedemo->value)
	{
		if (!cl.timedemo_start)
		{
			oc_light_brush_polys = oc_light_alias_polys = oc_lights = oc_no_light_ents = oc_no_lights = oc_no_mirrors = 0;
			cl.timedemo_start = Sys_Milliseconds ();
		}
		cl.timedemo_frames++;
	}

	// an invalid frame will just use the exact previous refdef
	// we can't use the old frame if the video mode has changed, though...
	if ( cl.frame.valid && (cl.force_refdef || !cl_paused->value) )
	{
		cl.force_refdef = false;

		V_ClearScene ();

		// build a refresh entity list and calc cl.sim*
		// this also calls CL_CalcViewValues which loads
		// v_forward, etc.
		CL_AddEmits ();
		CL_AddEntities ();

		// never let it sit exactly on a node line, because a water plane can
		// dissapear when viewed with the eye exactly on it.
		// the server protocol only specifies to 1/8 pixel, so add 1/16 in each axis
		cl.refdef.vieworg[0] += 1.0/16;
		cl.refdef.vieworg[1] += 1.0/16;
		cl.refdef.vieworg[2] += 1.0/16;

		cl.refdef.x = scr_vrect.x;
		cl.refdef.y = scr_vrect.y;
		cl.refdef.width = scr_vrect.width;
		cl.refdef.height = scr_vrect.height;
#if 1
// FOV correction for wide monitors (from KMQuake2): START
		float aspectRatio = (float)cl.refdef.width/(float)cl.refdef.height;
		// changed to improved algorithm by Dopefish
		if (aspectRatio > STANDARD_ASPECT_RATIO)
			cl.refdef.fov_x = RAD2DEG( 2 * atan( (aspectRatio/ STANDARD_ASPECT_RATIO) * tan(DEG2RAD(cl.refdef.fov_x) * 0.5) ) );
		cl.refdef.fov_x = min(cl.refdef.fov_x, MAX_FOV);
// FOV correction for wide monitors (from KMQuake2): END
#endif
		cl.refdef.fov_y = CalcFov (cl.refdef.fov_x, cl.refdef.width, cl.refdef.height);
		cl.refdef.time = cl.leveltime/*cl.time*0.001*/;

		cl.refdef.areabits = cl.frame.areabits;

		if (!cl_add_entities->value)
			r_numentities = 0;
		if (!cl_add_particles->value)
			r_numparticles = 0;
		if (!cl_add_lights->value)
			r_numdlights = 0;
		if (!cl_add_blend->value)
			cl.refdef.blend[0] = cl.refdef.blend[1] = cl.refdef.blend[2] = cl.refdef.blend[3] = 0;

		cl.refdef.num_entities = r_numentities;
		cl.refdef.entities = r_entities;
		cl.refdef.num_particles = r_numparticles;
		cl.refdef.particles = r_particles;
		cl.refdef.num_dlights = r_numdlights;
		cl.refdef.dlights = r_dlights;

		cl.refdef.rdflags = cl.frame.playerstate.rdflags;
/*
		if (TrackIsCombat_old != OLD_TRACK_DISABLED)
		{
			if (cl.refdef.rdflags & RDF_COMBATTRACK)
			{
				if (TrackIsCombat_old == OLD_TRACK_UNKNOWN || TrackIsCombat_old == OLD_TRACK_RELAX)
				{
					TrackIsCombat_old = OLD_TRACK_COMBAT;
///					S_StartBackgroundTrack(trackName_RelaxToCombat, trackName_Combat, trackCombat_offset);
				}
			}
			else
			{
				if (TrackIsCombat_old == OLD_TRACK_UNKNOWN || TrackIsCombat_old == OLD_TRACK_COMBAT)
				{
					TrackIsCombat_old = OLD_TRACK_RELAX;
///					S_StartBackgroundTrack(trackName_CombatToRelax, trackName_Relax, trackRelax_offset);
				}
			}
		}
*/
		if (gl_config.occlusion)
		{	// если работает окклюжн-тест, то...
			for (int i=0; i<cl.refdef.num_entities; i++)
			{
				float	x,y,z;
				x = cl.refdef.entities[i].origin[0] - cl.refdef.vieworg[0];
				y = cl.refdef.entities[i].origin[1] - cl.refdef.vieworg[1];
				z = cl.refdef.entities[i].origin[2] - cl.refdef.vieworg[2];
				cl.refdef.entities[i].dist = x*x+y*y+z*z;
			}	// отсортируем по дальности от камеры (для лучшей работы окклюжн-теста)
			qsort( cl.refdef.entities, cl.refdef.num_entities, sizeof( cl.refdef.entities[0] ), (int (*)(const void *, const void *))entityDistcmpfnc2 );
		}
		else
			// sort entities for better cache locality
			qsort( cl.refdef.entities, cl.refdef.num_entities, sizeof( cl.refdef.entities[0] ), (int (*)(const void *, const void *))entitycmpfnc );
	}

	R_VCFreeFrame();
	need_free_vbo = false;
	Render_Frame (&cl.refdef, true);

	SCR_AddDirtyPoint (scr_vrect.x, scr_vrect.y);
	SCR_AddDirtyPoint (scr_vrect.x+scr_vrect.width-1, scr_vrect.y+scr_vrect.height-1);
}


void Draw_Pic (int x, int y, char *pic)
{
	image_t *gl;

	gl = Draw_FindPic (pic);
	if (!gl)
	{
		Com_DPrintf("^1Can't find pic: %s\n", pic);		// berserker: don't flood!
		return;
	}

	GL_Bind2D (gl->texnum);

	int x0 = x;
	int y0 = y;
	int x1 = x0 + gl->width;
	int y1 = y0 + gl->height;
	Adjust320to640(&x0, &y0, &x1, &y1);

	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (x0, y0);
	glTexCoord2f (1, 0);
	glVertex2f (x1, y0);
	glTexCoord2f (1, 1);
	glVertex2f (x1, y1);
	glTexCoord2f (0, 1);
	glVertex2f (x0, y1);
	glEnd ();
}


void Draw_Pic16 (int x, int y, char *pic)
{
	image_t *gl;

	gl = Draw_FindPic (pic);
	if (!gl)
	{
		Com_DPrintf("^1Can't find pic: %s\n", pic);		// berserker: don't flood!
		return;
	}

	GL_Bind2D (gl->texnum);

	int x0 = x;
	int y0 = y;
	int x1 = x0 + gl->width;
	int y1 = y0 + gl->height;

	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (x0, y0);
	glTexCoord2f (1, 0);
	glVertex2f (x1, y0);
	glTexCoord2f (1, 1);
	glVertex2f (x1, y1);
	glTexCoord2f (0, 1);
	glVertex2f (x0, y1);
	glEnd ();
}


// sx, sy - sizes for virtual 640x480 mode
void Draw_PicSized(int x, int y, float sx, float sy, char *pic)
{
	image_t *gl;

	gl = Draw_FindPic (pic);
	if (!gl)
	{
		gl = draw_conback;
		if (!gl)
		{
			Com_DPrintf("^1Can't find pic: %s\n", pic);		// berserker: don't flood!
			return;
		}
	}

	GL_Bind2D (gl->texnum);

	int x0 = x;
	int y0 = y;
	int x1 = x0 + sx;
	int y1 = y0 + sy;
	Adjust320to640(&x0, &y0, &x1, &y1);

	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (x0, y0);
	glTexCoord2f (1, 0);
	glVertex2f (x1, y0);
	glTexCoord2f (1, 1);
	glVertex2f (x1, y1);
	glTexCoord2f (0, 1);
	glVertex2f (x0, y1);
	glEnd ();
}


void SCR_DrawPause ()
{
	int	w, h;

	if (!cl_paused->value)
		return;

	if (!scr_showpause->value)		// turn off for screenshots
		return;

	Draw_GetPicSize (&w, &h, "pause.tga");
	Draw_Pic ((viddef.width-w)/2, viddef.height/2 + 8, "pause.tga");
}


/*
================
Con_CheckResize

If the line width has changed, reformat the buffer.
================
*/
void Con_CheckResize ()
{
	int		i, j, width, oldwidth, oldtotallines, numlines, numchars;
	char	tbuf[CON_TEXTSIZE];
	char	cbuf[CON_TEXTSIZE];	// text color

	width = (viddef.width >> 4/*3*/) - 2;	// adjusted to 640x480

	if (width == con.linewidth)
		return;

	if (width < 1)			// video hasn't been initialized yet
	{
		width = 38;
		con.linewidth = width;
		con.totallines = CON_TEXTSIZE / con.linewidth;
		Memset (con.text, ' ', CON_TEXTSIZE);
		Memset (con.color, 7, CON_TEXTSIZE);
	}
	else
	{
		oldwidth = con.linewidth;
		con.linewidth = width;
		oldtotallines = con.totallines;
		con.totallines = CON_TEXTSIZE / con.linewidth;
		numlines = oldtotallines;

		if (con.totallines < numlines)
			numlines = con.totallines;

		numchars = oldwidth;

		if (con.linewidth < numchars)
			numchars = con.linewidth;

		Memcpy (tbuf, con.text, CON_TEXTSIZE);
		Memcpy (cbuf, con.color, CON_TEXTSIZE);
		Memset (con.text, ' ', CON_TEXTSIZE);
		Memset (con.color, 7, CON_TEXTSIZE);

		for (i=0 ; i<numlines ; i++)
		{
			for (j=0 ; j<numchars ; j++)
			{
				con.text[(con.totallines - 1 - i) * con.linewidth + j] = tbuf[((con.current - i + oldtotallines) % oldtotallines) * oldwidth + j];
				con.color[(con.totallines - 1 - i) * con.linewidth + j] = cbuf[((con.current - i + oldtotallines) % oldtotallines) * oldwidth + j];
			}
		}

		Con_ClearNotify ();
	}

	con.current = con.totallines - 1;
	con.display = con.current;
}


void Draw_LevelShot ()
{
	image_t	*gl;
	char	name[MAX_QPATH];

	gl = draw_mapshot;
	if (!gl)
	{
		gl = draw_default;
		if (!gl)
		{
			gl = draw_conback;
			if (!gl)
			{
				Com_DPrintf("^1Can't find pic: %s\n", mapshot);		// berserker: don't flood!
				return;
			}
		}
	}

	GL_Bind2D (gl->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (0, 0);
	glTexCoord2f (1, 0);
	glVertex2f (viddef.width, 0);
	glTexCoord2f (1, 1);
	glVertex2f (viddef.width, viddef.height);
	glTexCoord2f (0, 1);
	glVertex2f (0, viddef.height);
	glEnd ();

	if(loading_stage && loading_stage<=LSTAGE_STAGES)
	{
		float	x = viddef.width * (float)loading_stage / LSTAGE_STAGES;
		float	y = viddef.height*0.90;
		float	y2 = viddef.height*0.95;
		glDisable(GL_TEXTURE_2D);
		glEnable(GL_BLEND);
		glDisable (GL_ALPHA_TEST);

		glBegin (GL_QUADS);
		GL_Color4f(1,0,0,0.25);
		glVertex2f (x, y);
		glVertex2f (x, y2);
		GL_Color4f(1,1,0,0.5);
		glVertex2f (0, y2);
		glVertex2f (0, y);
		glEnd ();

		GL_Color3f(1,1,1);
		glEnable(GL_ALPHA_TEST);
		glDisable(GL_BLEND);
		glEnable(GL_TEXTURE_2D);

		Com_sprintf (name, sizeof(name), "Loading: %s", lstage_names[loading_stage-1]);
		DrawMixedString ((vid.width-(strlen(name)<<4))>>1, (y+y2-16)*0.5, name, 8);
	}
	else if (loading_string)
	{
		float	y = viddef.height*0.90;
		float	y2 = viddef.height*0.95;

		if (cls.download)
		{
			DrawAltString ((vid.width-(strlen(loading_string)<<4))>>1, (y+y2-28)*0.5, loading_string);

			float	x = viddef.width * cls.downloadpercent * 0.01;

			glDisable(GL_TEXTURE_2D);
			glEnable(GL_BLEND);
			glDisable (GL_ALPHA_TEST);

			glBegin (GL_QUADS);
			GL_Color4f(0,1,1,0.25);
			glVertex2f (x, y);
			glVertex2f (x, y2);
			GL_Color4f(0,0,1,0.5);
			glVertex2f (0, y2);
			glVertex2f (0, y);
			glEnd ();

			GL_Color3f(1,1,1);
			glEnable(GL_ALPHA_TEST);
			glDisable(GL_BLEND);
			glEnable(GL_TEXTURE_2D);

			Com_sprintf(name, sizeof(name), "%s: %i%%", cls.downloadname, cls.downloadpercent);
			DrawString ((vid.width-(strlen(name)<<4))>>1, (y+y2+4)*0.5, name);
		}
		else
			DrawAltString ((vid.width-(strlen(loading_string)<<4))>>1, (y+y2-16)*0.5, loading_string);
	}
}


bool Draw_Console (int y, int w, int h)
{
	float	l;
	vec4_t	ll;

	if (scr_mapshots->value)
	{
		if ((mapshot[0]) && (cls.state != ca_active || !cl.refresh_prepped))
		{
			Draw_LevelShot();
			return false;
		}
	}

	/// Berserker: убираем текст из консоли, если рисуем меню.
	bool	ret = (cls.key_dest != key_menu);	// false - in menu.

	if (!draw_conback || !draw_conback_bump)
	{
		Com_Printf("^1Can't find console images\n");
		return ret;
	}

	GL_MBind2D (GL_TEXTURE0, draw_conback_bump->texnum);
	gl_TexEnv( GL_COMBINE_ARB );
	GL_TexEnv( GL_COMBINE_RGB_ARB, GL_DOT3_RGB_ARB );
	GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
	GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
	GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB );
	GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );

	GL_MBind2D (GL_TEXTURE1, draw_conback->texnum);
	glEnable(GL_TEXTURE_2D);
	gl_TexEnv( GL_COMBINE_ARB );
	GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
	GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
	GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
	GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
	GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
	GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
	GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB );
	GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );

	glEnable(GL_BLEND);
	glDisable(GL_ALPHA_TEST);

	l = sys_time * 0.001;
	if (!con_transparency->value || cls.state != ca_active || !cl.refresh_prepped)
		// connected, but can't render
		ll[3]=1;				// Пока грузится карта (запрещена прорисовка), рисуем консоль с полной яркостью
	else
		ll[3] = (h+y)/float(h);

	SinCos(l, &ll[0], &ll[1]);
///	ll[0] = sin(l);
///	ll[1] = cos(l);
	ll[2] = 0.5;
	VectorNormalize(ll);
	ll[0] = ll[0] * 0.5 + 0.5;
	ll[1] = ll[1] * 0.5 + 0.5;
	ll[2] = ll[2] * 0.5 + 0.5;
	GL_Color4fv(ll);

	glBegin (GL_QUADS);
		glTexCoord2f (0, 0);
		glMultiTexCoord2fARB(GL_TEXTURE1, 0, 0);
		glVertex2f (0, y);

		glTexCoord2f (1, 0);
		glMultiTexCoord2fARB(GL_TEXTURE1, 1, 0);
		glVertex2f (w, y);

		glTexCoord2f (1, 1);
		glMultiTexCoord2fARB(GL_TEXTURE1, 1, 1);
		glVertex2f (w, y + h);

		glTexCoord2f (0, 1);
		glMultiTexCoord2fARB(GL_TEXTURE1, 0, 1);
		glVertex2f (0, y + h);
	glEnd ();

///	gl_TexEnv( GL_REPLACE );
gl_TexEnv( GL_COMBINE_ARB );
GL_TexEnv( GL_COMBINE_RGB_ARB, GL_REPLACE );
GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_TEXTURE );
GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );

	glDisable(GL_TEXTURE_2D);
	GL_SelectTexture(GL_TEXTURE0);

///	gl_TexEnv( GL_REPLACE );
gl_TexEnv( GL_COMBINE_ARB );
GL_TexEnv( GL_COMBINE_RGB_ARB, GL_REPLACE );
GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
//GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
//GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_TEXTURE );
//GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );

	glDisable(GL_BLEND);
	glEnable(GL_ALPHA_TEST);
	GL_Color3f(1,1,1);

	return ret;
}


/*
================
Draw_Char

Draws one 8*8 graphics character with 0 being transparent.
It can be clipped to the top of the screen to allow the console to be
smoothly scrolled off.
================
*/
void Draw_Char (int x, int y, int num)
{
	int				row, col;
	float			frow, fcol, size;

	num &= 255;

	if ( (num&127) == 32 )
		return;		// space

	if (y <= -8)
		return;			// totally off screen

	row = num>>4;
	col = num&15;

	frow = row*0.0625;
	fcol = col*0.0625;
	size = 0.0625;

	GL_Bind2D (draw_chars->texnum);

	int x0 = x;
	int y0 = y;
	int x1 = x0 + 8;
	int y1 = y0 + 8;
	Adjust320to640(&x0, &y0, &x1, &y1);

	glBegin (GL_QUADS);
	glTexCoord2f (fcol, frow);
	glVertex2f (x0, y0);
	glTexCoord2f (fcol + size, frow);
	glVertex2f (x1, y0);
	glTexCoord2f (fcol + size, frow + size);
	glVertex2f (x1, y1);
	glTexCoord2f (fcol, frow + size);
	glVertex2f (x0, y1);
	glEnd ();
}


void Draw_Char16 (int x, int y, int num)
{
	int				row, col;
	float			frow, fcol, size;

	num &= 255;

	if ( (num&127) == 32 )
		return;		// space

	if (y <= -16)
		return;			// totally off screen

	row = num>>4;
	col = num&15;

	frow = row*0.0625;
	fcol = col*0.0625;
	size = 0.0625;

	GL_Bind2D (draw_chars->texnum);

	int x0 = x;
	int y0 = y;
	int x1 = x0 + 16;
	int y1 = y0 + 16;

	glBegin (GL_QUADS);
	glTexCoord2f (fcol, frow);
	glVertex2f (x0, y0);
	glTexCoord2f (fcol + size, frow);
	glVertex2f (x1, y0);
	glTexCoord2f (fcol + size, frow + size);
	glVertex2f (x1, y1);
	glTexCoord2f (fcol, frow + size);
	glVertex2f (x0, y1);
	glEnd ();
}


void Draw_Char_Colored (int x, int y, int num, int cindex)
{
	int				row, col;
	float			frow, fcol, size;

///	num &= 255;
	num &= 127;

	if ( (num/*&127*/) == 32 )
		return;		// space

	if (y <= -16)
		return;			// totally off screen

	row = num>>4;
	col = num&15;

	frow = row*0.0625;
	fcol = col*0.0625;
	size = 0.0625;

	GL_Color3fv(g_color_table[cindex&7]);
	GL_Bind2D (draw_chars->texnum);

	int x0 = x;
	int y0 = y;
	int x1 = x0 + 16;
	int y1 = y0 + 16;

	glBegin (GL_QUADS);
	glTexCoord2f (fcol, frow);
	glVertex2f (x0, y0);
	glTexCoord2f (fcol + size, frow);
	glVertex2f (x1, y0);
	glTexCoord2f (fcol + size, frow + size);
	glVertex2f (x1, y1);
	glTexCoord2f (fcol, frow + size);
	glVertex2f (x0, y1);
	glEnd ();
}


void Draw_Char16_Colored (int x, int y, int num, int cindex)
{
	int				row, col;
	float			frow, fcol, size;

///	num &= 255;
	num &= 127;

	if ( (num/*&127*/) == 32 )
		return;		// space

	if (y <= -16)
		return;			// totally off screen

	row = num>>4;
	col = num&15;

	frow = row*0.0625;
	fcol = col*0.0625;
	size = 0.0625;

	GL_Color3fv(g_color_table[cindex&7]);
	GL_Bind2D (draw_chars->texnum);

	int x0 = x;
	int y0 = y;
	int x1 = x0 + 16;
	int y1 = y0 + 16;

	glBegin (GL_QUADS);
	glTexCoord2f (fcol, frow);
	glVertex2f (x0, y0);
	glTexCoord2f (fcol + size, frow);
	glVertex2f (x1, y0);
	glTexCoord2f (fcol + size, frow + size);
	glVertex2f (x1, y1);
	glTexCoord2f (fcol, frow + size);
	glVertex2f (x0, y1);
	glEnd ();
}


/*
================
Con_DrawInput

The input line scrolls horizontally if typing goes beyond the right edge
================
*/
void Con_DrawInput ()
{
	int		y, i;
	char	*text;

	if (cls.key_dest == key_menu)
		return;
	if (cls.key_dest != key_console && cls.state == ca_active)
		return;		// don't draw anything (always draw if not active)

	text = key_lines[edit_line];

// add the cursor frame
	text[key_linepos] = 10+((int)(cls.realtime>>8)&1);

// fill out remainder with spaces
	for (i=key_linepos+1 ; i< con.linewidth ; i++)
		text[i] = ' ';

//	prestep if horizontally scrolling
	if (key_linepos >= con.linewidth)
		text += 1 + key_linepos - con.linewidth;

	// draw it
	y = con.vislines - 44;
	for (i=0 ; i<con.linewidth ; i++)
		Draw_Char16 ( (i+1)<<4, y, text[i]);

// remove cursor
	key_lines[edit_line][key_linepos] = 0;
}


/*
================
Con_DrawConsole

Draws the console with the solid background
================
*/
bool Con_DrawConsole (float frac)
{
	int				i, x, y, n, j, sl;
	int				rows;
	char			*text, *colors;
	int				row;
	int				lines;
	char			version[64];
	char			dlbar[1024];

	lines = viddef.height * frac;
	if (lines <= 0)
		return true;

	if (lines > viddef.height)
		lines = viddef.height;

// draw the background
	if (!Draw_Console (-viddef.height+lines, viddef.width, viddef.height))
		return false;
	SCR_AddDirtyPoint (0,0);
	SCR_AddDirtyPoint (viddef.width-1,lines-1);

///	Com_sprintf (version, sizeof(version), "Version %4.2f", VERSION);
	Com_sprintf (version, sizeof(version), "Release 1.47 (%s)", __DATE__);
	int vlen = strlen(version);
	for (x=0 ; x<vlen ; x++)
		Draw_Char16 (viddef.width-8-(vlen-x)*16, 8, 128 + version[x] );

// draw the text
	con.vislines = lines;
// adjust to 640x480
	lines = lines>>1;

	rows = (lines-22)>>3;		// rows of text to draw
	y = lines - 30;

// draw from the bottom up
	if (con.display != con.current)
	{
	// draw arrows to show the buffer is backscrolled
		for (x=0 ; x<con.linewidth ; x+=4)
			Draw_Char16 ( (x+1)<<4, y<<1, '^');
		y -= 8;
		rows--;
	}

	row = con.display;
	gl_TexEnv( GL_MODULATE );
	for (i=0 ; i<rows ; i++, y-=8, row--)
	{
		if (row < 0)
			break;
		if (con.current - row >= con.totallines)
			break;		// past scrollback wrap point

		text = con.text + (row % con.totallines)*con.linewidth;
		colors = con.color + (row % con.totallines)*con.linewidth;

		for (x=0 ; x<con.linewidth ; x++)
			Draw_Char16_Colored ( (x+1)<<4, y<<1, text[x], colors[x]);
	}
	GL_Color3f(1,1,1);
	gl_TexEnv( GL_REPLACE );

//ZOID
	// draw the download bar
	// figure out width
	if (cls.download)
	{
		if ((text = strrchr(cls.downloadname, '/')) != NULL)
			text++;
		else if ((text = strrchr(cls.downloadname, '\\')) != NULL)		// berserker: damn path name
			text++;
		else
			text = cls.downloadname;

		x = con.linewidth - ((con.linewidth * 7) / 40);
		y = x - strlen(text) - 8;
		i = con.linewidth/3;
		if (strlen(text) > i)
		{
			y = x - i - 11;
			strncpy(dlbar, text, i);
			dlbar[i] = 0;
			strcat(dlbar, "...");
		} else
			strcpy(dlbar, text);
		strcat(dlbar, ": ");
		i = strlen(dlbar);
		dlbar[i++] = '\x80';
		// where's the dot go?
		if (cls.downloadpercent == 0)
			n = 0;
		else
			n = y * cls.downloadpercent / 100;

		for (j = 0; j < y; j++)
			if (j == n)
				dlbar[i++] = '\x83';
			else
				dlbar[i++] = '\x81';
		dlbar[i++] = '\x82';
		dlbar[i] = 0;

		sprintf(dlbar + strlen(dlbar), " %02d%%", cls.downloadpercent);

		// draw it
		y = con.vislines-24;
		sl = strlen(dlbar);			/// PVS-Studio
		for (i = 0; i < sl; i++)
			Draw_Char16 ( (i+1)<<4, y, dlbar[i]);
	}
//ZOID

// draw the input prompt, user text, and cursor if desired
	Con_DrawInput ();

	return true;
}


void DrawString (int x, int y, char *s)
{
	while (*s)
	{
		Draw_Char16 (x, y, *s);
		x+=16;
		s++;
	}
}


void DrawAltString (int x, int y, char *s)
{
	while (*s)
	{
		Draw_Char16 (x, y, *s ^ 0x80);
		x+=16;
		s++;
	}
}


void Draw_CharScaled (float x, float y, float scale_x, float scale_y, int num)
{
	int				row, col;
	float			frow, fcol, size;

	num &= 255;
	
	if ( (num&127) == 32 )
		return;		// space

	if (y <= -8*scale_y)
		return;			// totally off screen

	row = num>>4;
	col = num&15;

	frow = row*0.0625;
	fcol = col*0.0625;
	size = 0.0625;

	GL_Bind2D (draw_chars->texnum);

	int x0 = x;
	int y0 = y;
	int x1 = x0 + 8*scale_x;
	int y1 = y0 + 8*scale_y;
//	Adjust320to640(&x0, &y0, &x1, &y1);

	glBegin (GL_QUADS);
	glTexCoord2f (fcol, frow);
	glVertex2f (x0, y0);
	glTexCoord2f (fcol + size, frow);
	glVertex2f (x1, y0);
	glTexCoord2f (fcol + size, frow + size);
	glVertex2f (x1, y1);
	glTexCoord2f (fcol, frow + size);
	glVertex2f (x0, y1);
	glEnd ();
}


void DrawStringScaled (float x, float y, float scale_x, float scale_y, char *s)
{
	while (*s)
	{
		Draw_CharScaled (x, y, scale_x, scale_y, *s);
		x+=8*scale_x;
		s++;
	}
}


void DrawAltStringScaled (float x, float y, float scale_x, float scale_y, char *s)
{
	while (*s)
	{
		Draw_CharScaled (x, y, scale_x, scale_y, *s ^ 0x80);
		x+=8*scale_x;
		s++;
	}
}

/// Berserker: pos - кол-во первых символов зеленых, остальные белые.
void DrawMixedString (int x, int y, char *s, int pos)
{
	while (*s)
	{
		pos--;
		if (pos<0)
			Draw_Char16 (x, y, *s);
		else
			Draw_Char16 (x, y, *s ^ 0x80);
		x+=16;
		s++;
	}
}


/*
================
Con_DrawNotify

Draws the last few lines of output transparently over the game top
================
*/
void Con_DrawNotify ()
{
	int		x, v;
	char	*text, *colors;
	int		i;
	int		time;
	char	*s;
	int		skip;

	v = 0;
	gl_TexEnv( GL_MODULATE );
	for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++)
	{
		if (i < 0)
			continue;
		time = con.times[i % NUM_CON_TIMES];
		if (time == 0)
			continue;
		time = cls.realtime - time;
		if (time > con_notifytime->value*1000)
			continue;
		text = con.text + (i % con.totallines)*con.linewidth;
		colors = con.color + (i % con.totallines)*con.linewidth;

		for (x = 0 ; x < con.linewidth ; x++)
			Draw_Char_Colored ( (x+1)<<4, v<<1, text[x], colors[x]);
		v += 8;
	}
	GL_Color3f(1,1,1);
	gl_TexEnv( GL_REPLACE );

	if (cls.key_dest == key_get)
	{
		cvar_t *getmess = Cvar_Get("get_message", ">", 0);
		getmess->help = "special cvar, contains the message using in GET command.";
		int l = strlen(getmess->string);
		if (l)
			DrawString (16, v<<1, getmess->string);
		else
		{
			DrawString (16, v<<1, ">");
			l = 1;
		}
		skip = l + 1;
		goto nxt;
	}
	else if (cls.key_dest == key_message)
	{
		if (chat_team)
		{
			DrawString (16, v<<1, "say_team:");
			skip = 10;
		}
		else
		{
			DrawString (16, v<<1, "say:");
			skip = 5;
		}

nxt:	s = chat_buffer;
		if (chat_bufferlen > (viddef.width>>3)-(skip+1))
			s += chat_bufferlen - ((viddef.width>>3)-(skip+1));
		x = 0;
		while(s[x])
		{
			Draw_Char16 ( (x+skip)<<4, v<<1, s[x]);
			x++;
		}
		Draw_Char16 ( (x+skip)<<4, v<<1, 10+((cls.realtime>>8)&1));
		v += 8;
	}
	
	if (v)
	{
		SCR_AddDirtyPoint (0,0);
		SCR_AddDirtyPoint (viddef.width-1, v<<1);
	}
}


/*
=============
Draw_Fill

Fills a box of pixels with a single color
=============
*/
void Draw_Fill (int x, int y, int w, int h, byte r, byte g, byte b)
{
	glDisable (GL_TEXTURE_2D);

	GL_Color3f (float(r)/255.0, float(g)/255.0, float(b)/255.0);

	int x0 = x;
	int y0 = y;
	int x1 = x0 + w;
	int y1 = y0 + h;

	glBegin (GL_QUADS);
	glVertex2f (x0,y0);
	glVertex2f (x1, y0);
	glVertex2f (x1, y1);
	glVertex2f (x0, y1);
	glEnd ();

	GL_Color3f (1,1,1);
	glEnable (GL_TEXTURE_2D);
}


void SCR_DrawConsole ()
{
	Con_CheckResize ();

	if (cls.state == ca_disconnected || cls.state == ca_connecting)
	{	// forced full screen console
		Con_DrawConsole (1.0);
		return;
	}

	if (cls.state != ca_active || !cl.refresh_prepped)
	{	// connected, but can't render
		if(Con_DrawConsole (0.5))
			Draw_Fill (0, viddef.height>>1, viddef.width, viddef.height>>1, 0,0,0);
		return;
	}

	mapshot[0] = 0;
	if (scr_con_current)
		Con_DrawConsole (scr_con_current);
	else
		if (cls.key_dest == key_game || cls.key_dest == key_message || cls.key_dest == key_get)
			Con_DrawNotify ();	// only draw notify in game
}


/*
** End_Frame
** 
** Responsible for doing a swapbuffers and possibly for other stuff
** as yet to be determined.  Probably better not to make this a GLimp
** function and instead do a call to GLimp_SwapBuffers.
*/
void End_Frame ()
{
///	int		err;
///	err = glGetError();
///	assert( err == GL_NO_ERROR );

	if ( stricmp( r_drawbuffer->string, "GL_BACK" ) == 0 )
		if ( !SwapBuffers( glw_state.hDC ) )
			Com_Error( ERR_FATAL, "End_Frame():SwapBuffers failed!" );
}


void SCR_DrawLoading ()
{
	int	w, h;
		
	if (!scr_draw_loading)
		return;

	scr_draw_loading = false;
	Draw_GetPicSize (&w, &h, "loading");
	Draw_Pic ((viddef.width-w)/2, (viddef.height-h)/2, "loading");
}


void Draw_FadeScreen ()
{
	int x0, y0;
	int x1, y1;

	glEnable (GL_BLEND);
	glDisable (GL_TEXTURE_2D);
	GL_Color4f (0.1, 0.05, 0.0, 0.6);			// Цвет затенения...
	glBegin (GL_QUADS);

	if (!vid_ignorehwgamma->value && (m_drawfunc == VID_MenuDraw || m_drawfunc == Mirrors_MenuDraw))		// В видеоменю затеним только часть экрана (там где меню), чтобы можно было легко настроить гамму
	{
		x0 = (vid.width - 320) >> 1;
		y0 = (vid.height - 240) >> 1;
		x1 = x0 + 320;
		y1 = y0 + 240;
		Adjust320to640(&x0, &y0, &x1, &y1);
	}
	else
	{
		x0 = 0;
		y0 = 0;
		x1 = vid.width;
		y1 = vid.height;
	}

	glVertex2f (x0, y0);
	glVertex2f (x1, y0);
	glVertex2f (x1, y1);
	glVertex2f (x0, y1);

	glEnd ();
	GL_Color3f (1,1,1);
	glEnable (GL_TEXTURE_2D);
	glDisable (GL_BLEND);
}


void M_Draw ()
{
	if (cls.key_dest != key_menu)
		return;

	// repaint everything next frame
	SCR_DirtyScreen ();

	// dim everything behind it down
	if (cl.cinematictime > 0)
		Draw_Fill (0,0, viddef.width, viddef.height, 0,0,0);
	else
		Draw_FadeScreen ();

	m_drawfunc ();

	// delay playing the enter sound until after the
	// menu has been drawn, to avoid delay while
	// caching images
	if (m_entersound)
	{
		S_StartLocalSound(cl_sfx_menu_in_sound);
		m_entersound = false;
	}
}


void Draw_PicScaled (int x, int y, float scale_x, float scale_y, char *pic)
{
	float	w, h;
	image_t *gl;

	gl = Draw_FindPic (pic);
	if (!gl)
	{
		Com_DPrintf("^1Can't find pic: %s\n", pic);		// berserker: don't flood!
		return;
	}

	if (gl->scale_s)
	{
		w = gl->scale_s * scale_x;
		h = gl->scale_t * scale_y;
	}
	else
	{
		w = gl->width * scale_x * 0.5;
		h = gl->height * scale_y * 0.5;
	}

	int x0 = x;
	int y0 = y;
	int x1 = x0 + w;
	int y1 = y0 + h;
///	Adjust320to640(&x0, &y0, &x1, &y1);

	GL_Bind2D (gl->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (x0, y0);
	glTexCoord2f (1, 0);
	glVertex2f (x1, y0);
	glTexCoord2f (1, 1);
	glVertex2f (x1, y1);
	glTexCoord2f (0, 1);
	glVertex2f (x0, y1);
	glEnd ();
}


void SCR_DrawField (int x, int y, float scale_x, float scale_y, int color, int width, int value)
{
	char	num[16], *ptr;
	int		l;
	int		frame;

	if (width < 1)
		return;

	// draw number string
	if (width > 5)
		width = 5;

	SCR_AddDirtyPoint (x, y);
	SCR_AddDirtyPoint (x+(width*CHAR_WIDTH+2)*scale_x, y+23*scale_y);

	Com_sprintf (num, sizeof(num), "%i", value);
	l = strlen(num);
	if (l > width)
		l = width;
	x += (2 + CHAR_WIDTH*(width - l))*scale_x;

	ptr = num;
	while (*ptr && l)
	{
		if (*ptr == '-')
			frame = STAT_MINUS;
		else
			frame = *ptr -'0';

		Draw_PicScaled (x,y,scale_x,scale_y,sb_nums[color][frame]);
		x += CHAR_WIDTH*scale_x;
		ptr++;
		l--;
	}
}


void DrawHUDString (char *string, float x, float y, float scale_x, float scale_y, int centerwidth, int xor)
{
	float	margin;
	char	line[1024];
	int		width;
	int		i;

	margin = x;

	while (*string)
	{
		// scan out one line of text from the string
		width = 0;
		while (*string && *string != '\n')
			line[width++] = *string++;
		line[width] = 0;

		if (centerwidth)
			x = margin + (centerwidth - width*8)*scale_x/2;
		else
			x = margin;
		for (i=0 ; i<width ; i++)
		{
			Draw_CharScaled (x, y, scale_x, scale_y, line[i]^xor);
			x += 8*scale_x;
		}
		if (*string)
		{
			string++;	// skip the \n
			x = margin;
			y += 8*scale_y;
		}
	}
}


void SCR_ExecuteLayoutString (char *s, bool is_hud)
{
	int		x, y, max_configstrings, cs_playerskins;
	int		value;
	char	*token;
	int		width;
	int		index;
	clientinfo_t	*ci;
	model_t	*model;
	float	iScale = 1;

	if (cls.state != ca_active || !cl.refresh_prepped)
		return;

	if (!s[0])
		return;

	if (net_compatibility->value)
	{
		max_configstrings = MAX_CONFIGSTRINGS_Q2;
		cs_playerskins = CS_PLAYERSKINS_Q2;
	}
	else
	{
		max_configstrings = MAX_CONFIGSTRINGS_BERS;
		cs_playerskins = CS_PLAYERSKINS_BERS;
	}

	float scale = scr_bighud->value ? 1 : 0.5;
	float hud_sx = viddef.width / 320.0 * scale;
	float hud_sy = viddef.height / 240.0 * scale;

	if (is_hud)
	{
		if (net_compatibility->value)
		{
			for (x=0; x<MAX_IMAGES_Q2; x++)
				hud_model_counter[x] = 0;
		}
		else
		{
			for (x=0; x<MAX_IMAGES_BERS; x++)
				hud_model_counter[x] = 0;
		}
	}

	x = 0;
	y = 0;
	width = 3;

	while (s)
	{
		token = COM_Parse (&s);
		if (!strcmp(token, "sc"))
		{
			token = COM_Parse (&s);
			iScale = atof(token);
			continue;
		}
		if (!strcmp(token, "xl"))
		{
			token = COM_Parse (&s);
			x = atoi(token)*hud_sx;
			continue;
		}
		if (!strcmp(token, "xr"))
		{
			token = COM_Parse (&s);
			x = viddef.width + atoi(token)*hud_sx;
			continue;
		}
		if (!strcmp(token, "xv"))
		{
			token = COM_Parse (&s);
			x = viddef.width/2 - (160 - atoi(token))*hud_sx;
			continue;
		}

		if (!strcmp(token, "yt"))
		{
			token = COM_Parse (&s);
			y = atoi(token)*hud_sy;
			continue;
		}
		if (!strcmp(token, "yb"))
		{
			token = COM_Parse (&s);
			y = viddef.height + atoi(token)*hud_sy;
			continue;
		}
		if (!strcmp(token, "yv"))
		{
			token = COM_Parse (&s);
			y = viddef.height/2 - (120 - atoi(token))*hud_sy;
			continue;
		}

		if (!strcmp(token, "pic"))
		{	// draw a pic from a stat number
			token = COM_Parse (&s);
			value = cl.frame.playerstate.stats[atoi(token)];
			int	cs_images;
			if (net_compatibility->value)
			{
				if (value >= MAX_IMAGES_Q2)
					Com_Error (ERR_DROP, "Pic >= MAX_IMAGES_Q2");
				cs_images = CS_IMAGES_Q2;
			}
			else
			{
				if (value >= MAX_IMAGES_BERS)
					Com_Error (ERR_DROP, "Pic >= MAX_IMAGES_BERS");
				cs_images = CS_IMAGES_BERS;
			}
			if (cl.configstrings[cs_images+value])
			{
				if (!scr_3dhud->value)
					goto hud2d;

				if (hud_model_test[value])
				{
					if (hud_model_exist[value])
						model = hud_models[value];
					else
						model = NULL;
				}
				else
				{
					char name[MAX_QPATH];
					hud_model_test[value] = true;
					Com_sprintf (name, sizeof(name), "models/hud/%s.md2", cl.configstrings[cs_images+value]);
					model = R_RegisterModel(name, 1, false);
					if(model && (model->type == mod_alias || model->type == mod_alias_md3))
					{
						model->flags &= ~RF_DISTORT;
						hud_model_exist[value] = true;
						hud_models[value] = model;
						Com_sprintf(name, sizeof(name), "models/hud/%s", cl.configstrings[cs_images+value]);
						hud_skins[value] = R_RegisterSkin(name);
						hud_bumps[value] = R_RegisterBump(name, hud_skins[value], hud_models[value]);
						hud_lights[value] = R_RegisterLight(name);
					}
					else
						hud_model_exist[value] = false;
				}

				if (model)
				{
					hud_model_framecount[value] = r_framecount;		// метка для отложенного рисования 3D модели позже, в 3D перспективе...
					if(hud_model_counter[value]<16)
					{
						hud_model_origin[value][hud_model_counter[value]][0] = x;
						hud_model_origin[value][hud_model_counter[value]][1] = y;
						hud_model_scale[value][hud_model_counter[value]] = iScale;
						hud_model_counter[value]++;
					}
				}
				else
hud2d:				Draw_PicScaled (x, y, hud_sx*iScale, hud_sy*iScale, cl.configstrings[cs_images+value]);

				SCR_AddDirtyPoint (x, y);
				SCR_AddDirtyPoint (x+24*hud_sx*iScale, y+24*hud_sy*iScale);
			}
			continue;
		}

		if (!strcmp(token, "client"))
		{	// draw a deathmatch client block
			int		score, ping, time;

			token = COM_Parse (&s);
			x = viddef.width/2 - (160 - atoi(token))*hud_sx;
			token = COM_Parse (&s);
			y = viddef.height/2 - (120 - atoi(token))*hud_sy;
			SCR_AddDirtyPoint (x, y);
			SCR_AddDirtyPoint (x+159*hud_sx*iScale, y+31*hud_sy*iScale);

			token = COM_Parse (&s);
			value = atoi(token);
			if (value >= MAX_CLIENTS || value < 0)
				Com_Error (ERR_DROP, "client >= MAX_CLIENTS");
			ci = &cl.clientinfo[value];

			token = COM_Parse (&s);
			score = atoi(token);

			token = COM_Parse (&s);
			ping = atoi(token);

			token = COM_Parse (&s);
			time = atoi(token);

			char	c, out[MAXPRINTMSG];
			int		i = 0;
			char	*text = ci->name;
			while (1)
			{
povtor:			if (!(c = *text) || (i==MAXPRINTMSG-1))
				{
					out[i] = 0;
					break;
				}

				if (Q_IsColorString(text))
				{
					text+=2;		// Удаляем цветные метки
					goto povtor;
				}

				text++;
				out[i++] = c;
			}
			DrawAltStringScaled (x+32*hud_sx, y, hud_sx*iScale, hud_sy*iScale, out);
			DrawStringScaled (x+32*hud_sx, y+8*hud_sy, hud_sx*iScale, hud_sy*iScale, "Score: ");
			DrawAltStringScaled (x+(32+7*8)*hud_sx, y+8*hud_sy, hud_sx*iScale, hud_sy*iScale, va("%i", score));
			DrawStringScaled (x+32*hud_sx, y+16*hud_sy, hud_sx*iScale, hud_sy*iScale, va("Ping:  %i", ping));
			DrawStringScaled (x+32*hud_sx, y+24*hud_sy, hud_sx*iScale, hud_sy*iScale, va("Time:  %i", time));

			if (!ci->icon)
				ci = &cl.baseclientinfo;
			Draw_PicScaled (x, y, hud_sx*iScale, hud_sy*iScale, ci->iconname);
			continue;
		}

		/// New: berserker's token
		char cmodel[MAX_QPATH];		// model/skin
		char cmodel2[MAX_QPATH];	// model
		if (!strcmp(token, "pici"))
		{	// draw a pic from a player's skin (player icon)
			char *skinstring = NULL;
			char *modelstring = NULL;
			bool	hack = false;
			if (cl.attractloop)
			{
				if ((int)cl_forcemymodel->value & 2)
					hack = true;
			}
			else
			{
				if ((int)cl_forcemymodel->value & 1)
					hack = true;
			}
			if (hack)
			{
				skinstring = skin->string;
				modelstring = gender_model;///gender->string;
			}
			else
			{
				centity_t	*ent = &cl_entities[cl.playernum+1];
				int n = cs_playerskins + ent->current.number - 1;
				cmodel[0] = cmodel2[0] = 0;
				char *p = strchr(cl.configstrings[n], '\\');
				if (p)
				{
					p += 1;
					strcpy(cmodel, p);
					strcpy(cmodel2, p);
					p = strchr(cmodel2, '/');
					if (p)
						*p = 0;
				}
				if (cmodel[0] && cmodel2[0])
				{
					skinstring = cmodel;
					modelstring = cmodel2;
				}
			}
			if (!skinstring || !modelstring)
			{
				skinstring = skin->string;
				modelstring = gender_model;///gender->string;
			}
			{
				if (strncmp(old_skin, skinstring, MAX_QPATH))
				{
					hud_player_test = false;	// reset cache
					strcpy(old_skin, skinstring);
				}

				if (scr_3dhud->value)
				{
					hud_player_scale = iScale;

					if (hud_player_test)
					{
						if (hud_player_exist)
							model = hud_player_model;
						else
							model = NULL;
					}
					else
					{
						char name[MAX_QPATH];
						hud_player_test = true;
						Com_sprintf (name, sizeof(name), "models/hud/%s.md2", modelstring);
						model = R_RegisterModel(name, 1, false);
						if(model && (model->type == mod_alias || model->type == mod_alias_md3))
						{
							hud_player_exist = true;
							hud_player_model = model;
							Com_sprintf(name, sizeof(name), "players/%s", skinstring);
							hud_player_skin = R_RegisterSkin(name);
							if (hud_player_skin)
							{
								hud_player_bump = R_RegisterBump(name, hud_player_skin, hud_player_model);
								hud_player_light = R_RegisterLight(name);
							}
							else
							{
								Com_sprintf(name, sizeof(name), "models/hud/%s", modelstring);
								hud_player_skin = R_RegisterSkin(name);
								if (hud_player_skin)
								{
									hud_player_bump = R_RegisterBump(name, hud_player_skin, hud_player_model);
									hud_player_light = R_RegisterLight(name);
								}
								else
								{
									hud_player_skin = r_notexture;
									hud_player_bump = hud_player_light = NULL;
								}
							}
						}
						else
							hud_player_exist = false;
					}

					if (model)
					{
						hud_player_framecount = r_framecount;		// метка для отложенного рисования 3D модели позже, в 3D перспективе...
						hud_player_origin[0] = x;
						hud_player_origin[1] = y;
					}
					else
						goto oldplayer;
				}
				else
				{
oldplayer:			char scratch[1024];
					strcpy( scratch, "../players/" );
					strcat( scratch, skinstring );
					strcat( scratch, "_i.tga" );
					SCR_AddDirtyPoint (x, y);
					SCR_AddDirtyPoint (x+24*hud_sx*iScale, y+24*hud_sy*iScale);
					Draw_PicScaled (x, y, hud_sx*iScale, hud_sy*iScale, scratch);
				}
			}
			continue;
		}

		if (!strcmp(token, "ctf"))
		{	// draw a ctf client block
			int		score, ping;
			char	block[80];

			token = COM_Parse (&s);
			x = viddef.width/2 - (160 - atoi(token))*hud_sx;
			token = COM_Parse (&s);
			y = viddef.height/2 - (120 - atoi(token))*hud_sy;
			SCR_AddDirtyPoint (x, y);
			SCR_AddDirtyPoint (x+159, y+31);

			token = COM_Parse (&s);
			value = atoi(token);
			if (value >= MAX_CLIENTS || value < 0)
				Com_Error (ERR_DROP, "client >= MAX_CLIENTS");
			ci = &cl.clientinfo[value];

			token = COM_Parse (&s);
			score = atoi(token);

			token = COM_Parse (&s);
			ping = atoi(token);
			if (ping > 999)
				ping = 999;

			Com_sprintf(block, sizeof(block), "%3d %3d %-12.12s", score, ping, ci->name);

			if (value == cl.playernum)
				DrawAltStringScaled (x, y, hud_sx*iScale, hud_sy*iScale, block);
			else
				DrawStringScaled (x, y, hud_sx*iScale, hud_sy*iScale, block);
			continue;
		}

		if (!strcmp(token, "picn"))
		{	// draw a pic from a name
			token = COM_Parse (&s);
			SCR_AddDirtyPoint (x, y);
			SCR_AddDirtyPoint (x+23*hud_sx*iScale, y+23*hud_sy*iScale);
			Draw_PicScaled (x, y, hud_sx*iScale, hud_sy*iScale, token);
			continue;
		}

		if (!strcmp(token, "num"))
		{	// draw a number
			token = COM_Parse (&s);
			width = atoi(token);
			token = COM_Parse (&s);
			value = cl.frame.playerstate.stats[atoi(token)];
			SCR_DrawField (x, y, hud_sx*iScale, hud_sy*iScale, 0, width, value);
			continue;
		}

		if (!strcmp(token, "hnum"))
		{	// health number
			int		color;

			width = 3;
			value = cl.frame.playerstate.stats[STAT_HEALTH];
			if (value > 25)
				color = 0;	// green
			else if (value > 0)
				color = (cl.frame.serverframe>>2) & 1;		// flash
			else
				color = 1;

			if (cl.frame.playerstate.stats[STAT_FLASHES] & 1)
				Draw_PicScaled (x, y, hud_sx*iScale, hud_sy*iScale, "field_3");

			SCR_DrawField (x, y, hud_sx*iScale, hud_sy*iScale, color, width, value);
			continue;
		}

		if (!strcmp(token, "anum"))
		{	// ammo number
			int		color;

			width = 3;
			value = cl.frame.playerstate.stats[STAT_AMMO];
			if (value > 5)
				color = 0;	// green
			else if (value >= 0)
				color = (cl.frame.serverframe>>2) & 1;		// flash
			else
				continue;	// negative number = don't show

			if (cl.frame.playerstate.stats[STAT_FLASHES] & 4)
				Draw_PicScaled (x, y, hud_sx*iScale, hud_sy*iScale, "field_3");

			SCR_DrawField (x, y, hud_sx*iScale, hud_sy*iScale, color, width, value);
			continue;
		}

		if (!strcmp(token, "rnum"))
		{	// armor number
			int		color;

			width = 3;
			value = cl.frame.playerstate.stats[STAT_ARMOR];
			if (value < 1)
				continue;

			color = 0;	// green

			if (cl.frame.playerstate.stats[STAT_FLASHES] & 2)
				Draw_PicScaled (x, y, hud_sx*iScale, hud_sy*iScale, "field_3");

			SCR_DrawField (x, y, hud_sx*iScale, hud_sy*iScale, color, width, value);
			continue;
		}

		if (!strcmp(token, "stat_string"))
		{
			token = COM_Parse (&s);
			index = atoi(token);
			if (index < 0 || index >= max_configstrings)
				Com_Error (ERR_DROP, "Bad stat_string index");
			index = cl.frame.playerstate.stats[index];
			if (index < 0 || index >= max_configstrings)
				Com_Error (ERR_DROP, "Bad stat_string index");
			DrawStringScaled (x, y, hud_sx*iScale, hud_sy*iScale, cl.configstrings[index]);
			continue;
		}

		if (!strcmp(token, "cstring"))
		{
			token = COM_Parse (&s);
			DrawHUDString (token, x, y, hud_sx*iScale, hud_sy*iScale, 320, 0);
			continue;
		}

		if (!strcmp(token, "string"))
		{
			token = COM_Parse (&s);
			DrawStringScaled (x, y, hud_sx*iScale, hud_sy*iScale, token);
			continue;
		}

		if (!strcmp(token, "cstring2"))
		{
			token = COM_Parse (&s);
			DrawHUDString (token, x, y, hud_sx*iScale, hud_sy*iScale, 320, 0x80);
			continue;
		}

		if (!strcmp(token, "string2"))
		{
			token = COM_Parse (&s);
			DrawAltStringScaled (x, y, hud_sx*iScale, hud_sy*iScale, token);
			continue;
		}

		if (!strcmp(token, "cvar"))
		{
			char *c = Cvar_VariableString (COM_Parse (&s));
			DrawStringScaled (x, y, hud_sx*iScale, hud_sy*iScale, c);
			continue;
		}

		if (!strcmp(token, "cvar2"))
		{
			char *c = Cvar_VariableString (COM_Parse (&s));
			DrawAltStringScaled (x, y, hud_sx*iScale, hud_sy*iScale, c);
			continue;
		}

		if (!strcmp(token, "if"))
		{	// draw a number
			token = COM_Parse (&s);
			value = cl.frame.playerstate.stats[atoi(token)];
			if (!value)
			{	// skip to endif
				while (s && strcmp(token, "endif") )
				{
					token = COM_Parse (&s);
				}
			}
			continue;
		}
	}
}


#define	STAT_LAYOUTS		13
void SCR_DrawLayout ()
{
	if (!cl.frame.playerstate.stats[STAT_LAYOUTS])
		return;

	SCR_ExecuteLayoutString (cl.layout, false);			/// help screen
}


void Inv_DrawString (int x, int y, char *string)
{
	while (*string)
	{
		Draw_Char (x, y, *string);
		x+=8;
		string++;
	}
}


void SetStringHighBit (char *s)
{
	while (*s)
		*s++ |= 128;
}


#define	DISPLAY_ITEMS	17
void CL_DrawInventory ()
{
	int		i, j, cs_items;
	int		num, selected_num, item;
	int		index[MAX_ITEMS];
	char	string[1024];
	int		x, y;
	char	binding[1024];
	char	*bind;
	int		selected;
	int		top;

	if (net_compatibility->value)
		cs_items = CS_ITEMS_Q2;
	else
		cs_items = CS_ITEMS_BERS;

	selected = cl.frame.playerstate.stats[STAT_SELECTED_ITEM];

	num = 0;
	selected_num = 0;
	for (i=0 ; i<MAX_ITEMS ; i++)
	{
		if (i==selected)
			selected_num = num;
		if (cl.inventory[i])
		{
			index[num] = i;
			num++;
		}
	}

	// determine scroll point
	top = selected_num - DISPLAY_ITEMS/2;
	if (num - top < DISPLAY_ITEMS)
		top = num - DISPLAY_ITEMS;
	if (top < 0)
		top = 0;

	x = (viddef.width-256)/2;
	y = (viddef.height-240)/2;

	// repaint everything next frame
	SCR_DirtyScreen ();

	Draw_Pic (x, y+8, "inventory");

	y += 24;
	x += 24;
	Inv_DrawString (x, y, "hotkey ### item");
	Inv_DrawString (x, y+8, "------ --- ----");
	y += 16;
	for (i=top ; i<num && i < top+DISPLAY_ITEMS ; i++)
	{
		item = index[i];
		// search for a binding
		Com_sprintf (binding, sizeof(binding), "use %s", cl.configstrings[cs_items+item]);
		bind = "";
		for (j=0 ; j<256 ; j++)
			if (keybindings[j] && !Q_strcasecmp (keybindings[j], binding))
			{
				bind = Key_KeynumToString(j);
				break;
			}

		Com_sprintf (string, sizeof(string), "%6s %3i %s", bind, cl.inventory[item],
			cl.configstrings[cs_items+item] );
		if (item != selected)
			SetStringHighBit (string);
		else	// draw a blinky cursor by the selected item
		{
			if ( (int)(cls.realtime*10) & 1)
				Draw_Char (x-8, y, 15);
		}
		Inv_DrawString (x, y, string);
		y += 8;
	}
}


void SCR_DrawNet ()
{
	if (cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged < CMD_BACKUP-1)
		return;

	Draw_Pic16 (scr_vrect.x+64, scr_vrect.y, "net");
}


void SCR_DrawCenterString ()
{
	char	*start;
	int		l;
	int		j;
	int		x, y;
	int		remaining;

// the finale prints the characters one at a time
	remaining = 9999;

	scr_erase_center = 0;
	start = scr_centerstring;

	if (scr_center_lines <= 4)
		y = viddef.height*0.35;
	else
		y = 48;

	do	
	{
	// scan the width of the line
		for (l=0 ; l<40 ; l++)
			if (start[l] == '\n' || !start[l])
				break;
		x = (viddef.width - l*8)/2;
		int _x = x;
		int _y = y;
		int __x = x + 8;
		int __y = y + 8;
		Adjust320to640(&_x, &_y, &__x, &__y);
		SCR_AddDirtyPoint (_x, _y);
		for (j=0 ; j<l ; j++, x+=8)
		{
			Draw_Char (x, y, start[j]);	
			if (!remaining--)
				return;
		}
		SCR_AddDirtyPoint (__x, __y);
			
		y += 8;

		while (*start && *start != '\n')
			start++;

		if (!*start)
			break;
		start++;		// skip the \n
	} while (1);
}


void SCR_CheckDrawCenterString ()
{
	scr_centertime_off -= cls.frametime;
	
	if (scr_centertime_off <= 0)
		return;

	SCR_DrawCenterString ();
}


void SCR_ShowDemoSize()
{
	char st[80];

	if (cls.demorecording && !m_menudepth)
	{
		Com_sprintf(st, sizeof(st), "Demo recording: %i kbytes\n", cls.demolength/1024);
		DrawMixedString((vid.width-(strlen(st)<<3))>>1, 8, st, 15);
	}
}


int SCR_DrawGameTime(int pos)
{
	char string[32];

	if( !scr_gametime->value )
		return pos;

	if(m_menudepth)		// Не рисовать время когда находимся в меню
		return pos;

	int time = cl.gameTime / 100;
	int secs = (time % 600) / 10;
	int mins = (time % 36000) / 600;
	int hours = time / 36000;

	Com_sprintf (string, sizeof(string), "%i:%02i:%02i", hours, mins, secs);
	DrawAltString (4, vid.height-pos, string);
	SCR_AddDirtyPoint (4, vid.height-pos);
	SCR_AddDirtyPoint (4+16*32, vid.height-pos+16);
	return pos-16;
}

/*
==============
SCR_DrawClock
==============
*/
int SCR_DrawClock(int pos)		/// Q2Pro
{
	char string[32];
	time_t	clock;
	struct tm	*localTime;

	if( !scr_drawclock->value )
		return pos;

	if(m_menudepth)		// Не рисовать часы когда находимся в меню
		return pos;

	time( &clock );
	localTime = localtime( &clock );

	Com_sprintf( string, sizeof( string ), "%i:%02i:%02i%s", scr_drawclock->value > 1 ? localTime->tm_hour % 12 : localTime->tm_hour, localTime->tm_min, localTime->tm_sec,
					scr_drawclock->value <= 1 ?	"" : (localTime->tm_hour < 12 ? "am" : "pm"));
	DrawAltString (4, vid.height-pos, string);
	SCR_AddDirtyPoint (4, vid.height-pos);
	SCR_AddDirtyPoint (4+16*32, vid.height-pos+16);
	return pos-16;
}


int SCR_DrawEFXzones(int pos)
{
	if (r_showEFXzones->value && efx_preset_autoselected && !r_editor->value)
	{
		char str[256];
		if (efx_preset_autoselected > 0)
			Com_sprintf(str, sizeof(str), "^3You are in EFX zone with automatic selected preset:^7 %i - %s", efx_preset_autoselected, EFX_Names[efx_preset_autoselected]);
		else
			Com_sprintf(str, sizeof(str), "^2You are in EFX zone with manual selected preset:^7 %i - %s", -efx_preset_autoselected, EFX_Names[-efx_preset_autoselected]);
		Menu_DrawStringColored(4, vid.height - pos, str);

		SCR_AddDirtyPoint(4, vid.height - pos);
		SCR_AddDirtyPoint(4 + 16 * strlen(str), vid.height - pos + 16);
		return pos - 16;
	}

	return pos;
}



int SCR_DrawFPS (int pos)
{
	float	t, d;
	char	st[64];

	if (cls.state != ca_active || cl_paused->value || m_menudepth)
		fps_noshow = 2;		// Не вычислять FPS пока игра не активна
	else
	{
		t = float(Sys_Milliseconds()) * 0.001;
		d = t - lastframetime;
		if (d >= FPS_UPDATE_TIME || d < 0)
		{
			lastfps = fps_count;
			fps_count = 0;
			lastframetime = t;
			fps_refreshed = true;
			if (fps_noshow)
				fps_noshow--;
			if (fps_nocalc)
				fps_nocalc--;
		}
	}

	if (!fps_noshow)
		fps = lastfps * FPS_UPDATE_FREQ;	// save FPS for smart client entities remove

	if (!scr_fps->value)
		return pos;

	if (m_menudepth)
		return pos;		// не рисовать пока находимся в меню

	if (cls.state == ca_active && !cl_paused->value && !m_menudepth && ActiveApp)
	{
		if (!fps_nocalc)
		{
			/// fix startup value
			if (scr_fps_min->value == 0)
				Cvar_SetValue ("scr_fps_counter_min", 999999);

			if (fps < scr_fps_min->value)
				Cvar_SetValue ("scr_fps_counter_min", fps);

			if (fps > scr_fps_max->value)
				Cvar_SetValue ("scr_fps_counter_max", fps);
		}
	}
	else
		fps_nocalc = 2;		// Не вычислять FPS пока игра не активна

	if (scr_fps->value == 1)
		Com_sprintf(st, sizeof(st), "^3FpS: ^7%i", fps);
	else
		Com_sprintf(st, sizeof(st), "^3FpS: ^7%i [^1min: ^7%i][^2max: ^7%i]", fps, (int)scr_fps_min->value, (int)scr_fps_max->value);
	  Menu_DrawStringColored (4, vid.height-pos, st);
////  DrawString (4, vid.height-pos, st);
	SCR_AddDirtyPoint (4, vid.height-pos);
	SCR_AddDirtyPoint (4+16*strlen(st), vid.height-pos+16);
	return pos-16;
}


void TraceLight()
{
	if (!trace_light)
		return;

	if (le_mode)
	{
		VectorCopy(r_origin, curlink->origin);
		curlink->angles[0] = r_newrefdef.viewangles[0];
		curlink->angles[1] = r_newrefdef.viewangles[1];
	}
	else
	{
		VectorCopy(r_origin, curlight->origin);
		curlight->angles[0] = r_newrefdef.viewangles[0];
		curlight->angles[1] = r_newrefdef.viewangles[1];
// После изменения углов и координат требуется пересчитать некоторые параметры!
		currentshadowlight = curlight;
		VectorSet(currentshadowlight->frust[0].normal, 0,0,0);	// форсируем пересчет frust
		ComputeBSPLight();
	}
	Cl_UpdateEditorCvars (ED_LIGHT | ED_MODIFIED);
}

/*
Нестабилен, т.к. замещает данные старого лайта, а там и память могла быть выделена!
void CL_PasteCur_f ()
{
	vec3_t	bak;

	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if(!link_clipboard.radius)	// клипбоард пока пуст
			return;

		if (!curlink)
			return;

		VectorCopy(curlink->origin, bak);
		Memcpy(curlink, &link_clipboard, sizeof(llink_t));
		VectorCopy(bak, curlink->origin);
	}
	else
	{
		if(!light_clipboard.radius)	// клипбоард пока пуст
			return;

		if (!curlight)
			return;

		VectorCopy(curlight->origin, bak);
		Memcpy(curlight, &light_clipboard, sizeof(shadowlight_t));
		VectorCopy(bak, curlight->origin);

// После изменения параметров требуется пересчитать некоторые параметры!
		currentshadowlight = curlight;
VectorSet(currentshadowlight->frust[0].normal, 0,0,0);	// форсируем пересчет frust
		ComputeBSPLight();
	}
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}
*/


// Berserker: helper for style assignation
char	firstFreeStyleString[32];
char	noFreeStyle[] = "no free style";
char *GetFirstFreeStyle()
{
	int	i, j;
	// styles 32-62 are assigned by the light program for switchable lights
	for (j=32; j<63; j++)
	{
		for (i=0; i<numStaticShadowLights; i++)
			if (j == shadowlights[i].style)
				break;
		if (i<numStaticShadowLights)
			continue;

		for (i=0; i<lightmodel_counter; i++)
			if (j == lmdl_list[i].style)
				break;
		if (i<lightmodel_counter)
			continue;

		Com_sprintf(firstFreeStyleString, sizeof(firstFreeStyleString), "free style: %i", j);
		return firstFreeStyleString;
	}
	return noFreeStyle;
}

void TraceEmit()
{
	if (!trace_emit)
		return;

	if (!curemit)
		return;

	if (!LightEditor_Enabled(false))
	{
		trace_emit = false;
		return;
	}

	vec3_t	end;
	VectorMA (r_origin, 32768, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
	trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, MASK_SOLID);
///	if(trace.surface->name)
		if(trace.surface->name[0])
		{
			vectoangles2(trace.plane.normal, curemit->angles);
///			curemit->angles[0] += 90;
			VectorMA (r_origin, 32768 * trace.fraction, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
			VectorCopy(end, curemit->origin);
			VectorMA(curemit->origin, 2, trace.plane.normal, curemit->origin);
// После изменения углов требуется пересчитать некоторые параметры!
			vec3_t	ang;
			VectorSet(ang, curemit->angles[0], curemit->angles[1], 0);
			AngleVectors(ang, curemit->dir, NULL, NULL);
			VectorCopy(curemit->dir, curemit->dir0);
			int area = CM_LeafArea (CM_PointLeafnum (curemit->origin));
			if (!area)
				Com_Printf("^3Warning:^7 emitter out of BSP\n");
			Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
		}
}

void TraceModel()
{
	if (!trace_model)
		return;

	if (!curmodel)
		return;

	if (!LightEditor_Enabled(false))
	{
		trace_model = false;
		return;
	}

	vec3_t	end;
	VectorMA (r_origin, 32768, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
	trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, MASK_SOLID);
///	if(trace.surface->name)
		if(trace.surface->name[0])
		{
			vectoangles2(trace.plane.normal, curmodel->angles);
			curmodel->angles[0] += 90;
			VectorMA (r_origin, 32768 * trace.fraction, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
			VectorCopy(end, curmodel->origin);
			if (!R_MarkAliasLeaves(curmodel))
				Com_Printf("^3Warning:^7 model out of BSP\n");
			Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
		}
}


/// NetOptimized by Berserker (16-bits indexes by Knightmare)
void SV_WritePlayerstateToClient (client_frame_t *from, client_frame_t *to, sizebuf_t *msg, client_t *client)
{
	int				i;
	SHORT			pflags;
	BYTE			epflags;
	player_state_t	*ps, *ops;
	player_state_t	dummy;
	int				statbits;
	char			*val;
	int				integer;
	bool			old = net_compatibility->value;

	ps = &to->ps;
	if (!from)
	{
		Memset (&dummy, 0, sizeof(dummy));
		ops = &dummy;
	}
	else
		ops = &from->ps;

	//
	// determine what needs to be sent
	//
	pflags = 0;
	epflags = 0;

	if (ps->pmove.pm_type != ops->pmove.pm_type)
		pflags |= PS_M_TYPE;

	if (ps->pmove.origin[0] != ops->pmove.origin[0]
		|| ps->pmove.origin[1] != ops->pmove.origin[1]
		|| ps->pmove.origin[2] != ops->pmove.origin[2] )
		pflags |= PS_M_ORIGIN;

	// LARGE_MAP_SIZE support
	if (!old)
	{
		if (	ps->pmove.origin[0] >  4095 ||	ps->pmove.origin[0] < -4096
			||	ps->pmove.origin[1] >  4095 ||	ps->pmove.origin[1] < -4096
			||	ps->pmove.origin[2] >  4095 ||	ps->pmove.origin[2] < -4096 )
			epflags |= EPS_M_ORIGIN;
	}

	if (ps->pmove.velocity[0] != ops->pmove.velocity[0]
		|| ps->pmove.velocity[1] != ops->pmove.velocity[1]
		|| ps->pmove.velocity[2] != ops->pmove.velocity[2] )
	{
		if (old || ps->pmove.velocity[2] == 0)		/// часто скорость по вертикали = 0
			pflags |= PS_M_VELOCITY;
		else
			epflags |= EPS_M_VELOCITY;
	}

	if (ps->pmove.pm_time != ops->pmove.pm_time)
		pflags |= PS_M_TIME;

	if (ps->pmove.pm_flags != ops->pmove.pm_flags)
		pflags |= PS_M_FLAGS;

	if (ps->pmove.gravity != ops->pmove.gravity)
		pflags |= PS_M_GRAVITY;

	if (ps->pmove.delta_angles[0] != ops->pmove.delta_angles[0]
		|| ps->pmove.delta_angles[1] != ops->pmove.delta_angles[1]
		|| ps->pmove.delta_angles[2] != ops->pmove.delta_angles[2] )
		pflags |= PS_M_DELTA_ANGLES;

///	if(ps->viewoffset[0] != ops->viewoffset[0]
///	|| ps->viewoffset[1] != ops->viewoffset[1]
///	|| ps->viewoffset[2] != ops->viewoffset[2])
	Vec_RangeCap (ps->viewoffset, -32, 31.75);
	if ((int)(ps->viewoffset[0]*4) != (int)(ops->viewoffset[0]*4) ||
		(int)(ps->viewoffset[1]*4) != (int)(ops->viewoffset[1]*4) ||
		(int)(ps->viewoffset[2]*4) != (int)(ops->viewoffset[2]*4))
		pflags |= PS_VIEWOFFSET;

///	if (ps->viewangles[0] != ops->viewangles[0]
///		|| ps->viewangles[1] != ops->viewangles[1]
///		|| ps->viewangles[2] != ops->viewangles[2] )
	if (ANGLE2SHORT(ps->viewangles[0]) != ANGLE2SHORT(ops->viewangles[0]) ||
		ANGLE2SHORT(ps->viewangles[1]) != ANGLE2SHORT(ops->viewangles[1]) ||
		ANGLE2SHORT(ps->viewangles[2]) != ANGLE2SHORT(ops->viewangles[2]))
	{
		if (old || ANGLE2SHORT(ps->viewangles[2]) == 0)		/// Обычно крен равен нулю
			pflags |= PS_VIEWANGLES;
		else
			epflags |= EPS_VIEWANGLES;
	}

///	if (ps->kick_angles[0] != ops->kick_angles[0]
///		|| ps->kick_angles[1] != ops->kick_angles[1]
///		|| ps->kick_angles[2] != ops->kick_angles[2] )
	Vec_RangeCap (ps->kick_angles, -32, 31.75);
	if ((int)(ps->kick_angles[0]*4) != (int)(ops->kick_angles[0]*4) ||
		(int)(ps->kick_angles[1]*4) != (int)(ops->kick_angles[1]*4) ||
		(int)(ps->kick_angles[2]*4) != (int)(ops->kick_angles[2]*4))
	{
		if (old || ((int)(ps->kick_angles[1]*4) == 0 && (int)(ps->kick_angles[2]*4) == 0))
			pflags |= PS_KICKANGLES;
		else
			epflags |= EPS_KICKANGLES;
	}

	val = Info_ValueForKey(client->userinfo, "cl_blend");
	if (val[0])
		integer = atoi(val);
	else
		integer = 1;	/// default for cl_blend

	if (integer)
	{
		if (ps->blend[0] > 1)	ps->blend[0] = 1;
		if (ps->blend[1] > 1)	ps->blend[1] = 1;
		if (ps->blend[2] > 1)	ps->blend[2] = 1;
		if (ps->blend[3] > 1)	ps->blend[3] = 1;

		if ((int)(ps->blend[0]*255) != (int)(ops->blend[0]*255) ||
			(int)(ps->blend[1]*255) != (int)(ops->blend[1]*255) ||
			(int)(ps->blend[2]*255) != (int)(ops->blend[2]*255) ||
			(int)(ps->blend[3]*255) != (int)(ops->blend[3]*255))
			pflags |= PS_BLEND;
	}

	if (ps->fov != ops->fov)
		pflags |= PS_FOV;

	byte rdf = ps->rdflags;
	byte ordf = ops->rdflags;
	if (rdf != ordf)
		pflags |= PS_RDFLAGS;

	val = Info_ValueForKey(client->userinfo, "hand");
	if (val[0])
		integer = atoi(val);
	else
		integer = 0;	/// default for hand
	if(integer==2)
		goto skip;

	val = Info_ValueForKey(client->userinfo, "cl_gun");
	if (val[0])
		integer = atoi(val);
	else
		integer = 1;	/// default for cl_gun
	if(!integer)
		goto skip;

	if(ps->fov <= 90)
		goto good;

	val = Info_ValueForKey(client->userinfo, "cl_fovweap");
	if (val[0])
		integer = atoi(val);
	else
		integer = 1;	/// default for cl_fovweap
	if(!integer)
		goto skip;

good:
	if (ps->gunframe != ops->gunframe)
	{
		pflags |= PS_WEAPONFRAME;

		if (!old)
		{
			Vec_RangeCap (ps->gunoffset, -32, 31.75);
			if ((int)(ps->gunoffset[0]*4) != (int)(ops->gunoffset[0]*4) ||
				(int)(ps->gunoffset[1]*4) != (int)(ops->gunoffset[1]*4) ||
				(int)(ps->gunoffset[2]*4) != (int)(ops->gunoffset[2]*4))
				epflags |= EPS_GUNOFFSET;

			Vec_RangeCap (ps->gunangles, -32, 31.75);
			if ((int)(ps->gunangles[0]*4) != (int)(ops->gunangles[0]*4) ||
				(int)(ps->gunangles[1]*4) != (int)(ops->gunangles[1]*4) ||
				(int)(ps->gunangles[2]*4) != (int)(ops->gunangles[2]*4))
				epflags |= EPS_GUNANGLES;
		}
	}

	if (old || ps->gunindex != ops->gunindex)
	{
		pflags |= PS_WEAPONINDEX;
		if (!old && ps->gunindex > 255)
			epflags |= EPS_WEAPONINDEX16;
	}

skip:

	/// stats
	statbits = 0;
	for (i=0 ; i<MAX_STATS ; i++)
		if (ps->stats[i] != ops->stats[i])
			statbits |= 1<<i;
	if(!old && statbits)
		epflags |= EPS_STATS;


	//
	// write it
	//
	MSG_WriteByte (msg, svc_playerinfo);
	if (epflags && !old)
	{
		MSG_WriteShort (msg, (pflags | PS_EPS));
		MSG_WriteByte (msg, epflags);
	}
	else
		MSG_WriteShort (msg, pflags);

	//
	// write the pmove_state_t
	//
	if (pflags & PS_M_TYPE)
		MSG_WriteByte (msg, ps->pmove.pm_type);

	if (pflags & PS_M_ORIGIN)
	{
		if (epflags & EPS_M_ORIGIN)
		{
			MSG_WritePMCoordNew (msg, ps->pmove.origin[0]);
			MSG_WritePMCoordNew (msg, ps->pmove.origin[1]);
			MSG_WritePMCoordNew (msg, ps->pmove.origin[2]);
		}
		else
		{
			MSG_WriteShort (msg, ps->pmove.origin[0]);
			MSG_WriteShort (msg, ps->pmove.origin[1]);
			MSG_WriteShort (msg, ps->pmove.origin[2]);
		}
	}

	if (pflags & PS_M_VELOCITY)
	{
		MSG_WriteShort (msg, ps->pmove.velocity[0]);
		MSG_WriteShort (msg, ps->pmove.velocity[1]);
		if(old)
			MSG_WriteShort (msg, ps->pmove.velocity[2]);
	}
	else if (epflags & EPS_M_VELOCITY)
	{
		MSG_WriteShort (msg, ps->pmove.velocity[0]);
		MSG_WriteShort (msg, ps->pmove.velocity[1]);
		MSG_WriteShort (msg, ps->pmove.velocity[2]);
	}

	if (pflags & PS_M_TIME)
		MSG_WriteByte (msg, ps->pmove.pm_time);

	if (pflags & PS_M_FLAGS)
		MSG_WriteByte (msg, ps->pmove.pm_flags);

	if (pflags & PS_M_GRAVITY)
		MSG_WriteShort (msg, ps->pmove.gravity);

	if (pflags & PS_M_DELTA_ANGLES)
	{
		MSG_WriteShort (msg, ps->pmove.delta_angles[0]);
		MSG_WriteShort (msg, ps->pmove.delta_angles[1]);
		MSG_WriteShort (msg, ps->pmove.delta_angles[2]);
	}

	//
	// write the rest of the player_state_t
	//
	if (pflags & PS_VIEWOFFSET)
	{
		MSG_WriteChar (msg, ps->viewoffset[0]*4);
		MSG_WriteChar (msg, ps->viewoffset[1]*4);
		MSG_WriteChar (msg, ps->viewoffset[2]*4);
	}

	if (pflags & PS_VIEWANGLES)
	{
		MSG_WriteAngle16 (msg, ps->viewangles[0]);
		MSG_WriteAngle16 (msg, ps->viewangles[1]);
		if (old)
			MSG_WriteAngle16 (msg, ps->viewangles[2]);
	}
	else if (epflags & EPS_VIEWANGLES)
	{
		MSG_WriteAngle16 (msg, ps->viewangles[0]);
		MSG_WriteAngle16 (msg, ps->viewangles[1]);
		MSG_WriteAngle16 (msg, ps->viewangles[2]);
	}

	if (pflags & PS_KICKANGLES)
	{
		MSG_WriteChar (msg, ps->kick_angles[0]*4);
		if (old)
		{
			MSG_WriteChar (msg, ps->kick_angles[1]*4);
			MSG_WriteChar (msg, ps->kick_angles[2]*4);
		}
	}
	else if (epflags & EPS_KICKANGLES)
	{
		MSG_WriteChar (msg, ps->kick_angles[0]*4);
		MSG_WriteChar (msg, ps->kick_angles[1]*4);
		MSG_WriteChar (msg, ps->kick_angles[2]*4);
	}

	if (pflags & PS_WEAPONINDEX)
	{
		if (epflags & EPS_WEAPONINDEX16)
			MSG_WriteShort (msg, ps->gunindex);	// расширенные индексы (by Knightmare, optimized by Berserker)
		else
			MSG_WriteByte (msg, ps->gunindex);
	}

	if (pflags & PS_WEAPONFRAME)
	{
		MSG_WriteByte (msg, ps->gunframe);

		if ((epflags & EPS_GUNOFFSET) || old)
		{
			MSG_WriteChar (msg, ps->gunoffset[0]*4);
			MSG_WriteChar (msg, ps->gunoffset[1]*4);
			MSG_WriteChar (msg, ps->gunoffset[2]*4);
		}

		if ((epflags & EPS_GUNANGLES) || old)
		{
			MSG_WriteChar (msg, ps->gunangles[0]*4);
			MSG_WriteChar (msg, ps->gunangles[1]*4);
			MSG_WriteChar (msg, ps->gunangles[2]*4);
		}
	}

	if (pflags & PS_BLEND)
	{
		MSG_WriteByte (msg, ps->blend[0]*255);
		MSG_WriteByte (msg, ps->blend[1]*255);
		MSG_WriteByte (msg, ps->blend[2]*255);
		MSG_WriteByte (msg, ps->blend[3]*255);
	}

	if (pflags & PS_FOV)
		MSG_WriteByte (msg, ps->fov);

	if (pflags & PS_RDFLAGS)
		MSG_WriteByte (msg, rdf);

	// send stats
	if ((epflags & EPS_STATS) || old)
	{
		MSG_WriteLong (msg, statbits);
		for (i=0 ; i<MAX_STATS ; i++)
			if (statbits & (1<<i) )
				MSG_WriteShort (msg, ps->stats[i]);
	}
}


void CL_ParsePlayerstate (frame_t *oldframe, frame_t *newframe)
{
	SHORT			flags;
	BYTE			eflags;
	player_state_t	*state;
	int			i;
	int			statbits;
	bool		old = net_compatibility->value;

	state = &newframe->playerstate;

	// clear to old value before delta parsing
	if (oldframe)
		*state = oldframe->playerstate;
	else
		Memset (state, 0, sizeof(*state));

	flags = MSG_ReadShort (&net_message);
	if ((flags & PS_EPS) && !old)
		eflags = MSG_ReadByte (&net_message);
	else
		eflags = 0;

	//
	// parse the pmove_state_t
	//
	if (flags & PS_M_TYPE)
		state->pmove.pm_type = (pmtype_t) MSG_ReadByte (&net_message);

	if (flags & PS_M_ORIGIN)
	{
		if (eflags & EPS_M_ORIGIN)
		{
			state->pmove.origin[0] = MSG_ReadPMCoordNew (&net_message);
			state->pmove.origin[1] = MSG_ReadPMCoordNew (&net_message);
			state->pmove.origin[2] = MSG_ReadPMCoordNew (&net_message);
		}
		else
		{
			state->pmove.origin[0] = MSG_ReadShort (&net_message);
			state->pmove.origin[1] = MSG_ReadShort (&net_message);
			state->pmove.origin[2] = MSG_ReadShort (&net_message);
		}
	}

	if (flags & PS_M_VELOCITY)
	{
		state->pmove.velocity[0] = MSG_ReadShort (&net_message);
		state->pmove.velocity[1] = MSG_ReadShort (&net_message);
		if(old)
			state->pmove.velocity[2] = MSG_ReadShort (&net_message);
		else
			state->pmove.velocity[2] = 0;
	}
	else if (eflags & EPS_M_VELOCITY)
	{
		state->pmove.velocity[0] = MSG_ReadShort (&net_message);
		state->pmove.velocity[1] = MSG_ReadShort (&net_message);
		state->pmove.velocity[2] = MSG_ReadShort (&net_message);
	}

	if (flags & PS_M_TIME)
		state->pmove.pm_time = MSG_ReadByte (&net_message);

	if (flags & PS_M_FLAGS)
		state->pmove.pm_flags = MSG_ReadByte (&net_message);

	if (flags & PS_M_GRAVITY)
		state->pmove.gravity = MSG_ReadShort (&net_message);

	if (flags & PS_M_DELTA_ANGLES)
	{
		state->pmove.delta_angles[0] = MSG_ReadShort (&net_message);
		state->pmove.delta_angles[1] = MSG_ReadShort (&net_message);
		state->pmove.delta_angles[2] = MSG_ReadShort (&net_message);
	}

	if (cl.attractloop)
		state->pmove.pm_type = PM_FREEZE;		// demo playback

	//
	// parse the rest of the player_state_t
	//
	if (flags & PS_VIEWOFFSET)
	{
		state->viewoffset[0] = MSG_ReadChar (&net_message) * 0.25;
		state->viewoffset[1] = MSG_ReadChar (&net_message) * 0.25;
		state->viewoffset[2] = MSG_ReadChar (&net_message) * 0.25;
	}

	if (flags & PS_VIEWANGLES)
	{
		state->viewangles[0] = MSG_ReadAngle16 (&net_message);
		state->viewangles[1] = MSG_ReadAngle16 (&net_message);
		if(old)
			state->viewangles[2] = MSG_ReadAngle16 (&net_message);
		else
			state->viewangles[2] = 0;
	}
	else if (eflags & EPS_VIEWANGLES)
	{
		state->viewangles[0] = MSG_ReadAngle16 (&net_message);
		state->viewangles[1] = MSG_ReadAngle16 (&net_message);
		state->viewangles[2] = MSG_ReadAngle16 (&net_message);
	}

	if (flags & PS_KICKANGLES)
	{
		state->kick_angles[0] = MSG_ReadChar (&net_message) * 0.25;
		if(old)
		{
			state->kick_angles[1] = MSG_ReadChar (&net_message) * 0.25;
			state->kick_angles[2] = MSG_ReadChar (&net_message) * 0.25;
		}
		else
			state->kick_angles[1] = state->kick_angles[2] = 0;
	}
	else if (eflags & EPS_KICKANGLES)
	{
		state->kick_angles[0] = MSG_ReadChar (&net_message) * 0.25;
		state->kick_angles[1] = MSG_ReadChar (&net_message) * 0.25;
		state->kick_angles[2] = MSG_ReadChar (&net_message) * 0.25;
	}

	if (flags & PS_WEAPONINDEX)
	{
		if (eflags & EPS_WEAPONINDEX16)
			state->gunindex = MSG_ReadShort (&net_message);	// расширенные индексы (by Knightmare, optimized by Berserker)
		else
			state->gunindex = MSG_ReadByte (&net_message);
	}

	if (flags & PS_WEAPONFRAME)
	{
		state->gunframe = MSG_ReadByte (&net_message);

		if ((eflags & EPS_GUNOFFSET) || old)
		{
			state->gunoffset[0] = MSG_ReadChar (&net_message)*0.25;
			state->gunoffset[1] = MSG_ReadChar (&net_message)*0.25;
			state->gunoffset[2] = MSG_ReadChar (&net_message)*0.25;
		}

		if ((eflags & EPS_GUNANGLES) || old)
		{
			state->gunangles[0] = MSG_ReadChar (&net_message)*0.25;
			state->gunangles[1] = MSG_ReadChar (&net_message)*0.25;
			state->gunangles[2] = MSG_ReadChar (&net_message)*0.25;
		}
	}

	if (flags & PS_BLEND)
	{
		state->blend[0] = MSG_ReadByte (&net_message)/255.0;
		state->blend[1] = MSG_ReadByte (&net_message)/255.0;
		state->blend[2] = MSG_ReadByte (&net_message)/255.0;
		state->blend[3] = MSG_ReadByte (&net_message)/255.0;
	}
	else
		if(!cl_add_blend->value)
			state->blend[0] = state->blend[1] = state->blend[2] = state->blend[3] = 0;

	if (flags & PS_FOV)
		state->fov = MSG_ReadByte (&net_message);

	if (flags & PS_RDFLAGS)
		state->rdflags = MSG_ReadByte (&net_message);

	// parse stats
	if ((eflags & EPS_STATS) || old)
	{
		statbits = MSG_ReadLong (&net_message);
		for (i=0 ; i<MAX_STATS ; i++)
			if (statbits & (1<<i) )
				state->stats[i] = MSG_ReadShort(&net_message);
	}
}


int SCR_PrintReLight (int pos)
{
	if (!r_editor->value)
		return pos;

	int		i;
	char	str[256];

	pos+=16;

	if (le_mode)
	{	// linked lights mode
		if (curlink)
		{
			TraceLight ();

			pos-=16;

			Com_sprintf(str, sizeof(str), "Current linked light is %i [total %i]", curlink-lmdl_list, lightmodel_counter);
			DrawMixedString (4, vid.height-pos, str, 23);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Radius %i", (int)curlink->radius);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Origin %i %i %i", (int)curlink->origin[0], (int)curlink->origin[1], (int)curlink->origin[2]);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Color %1.2f %1.2f %1.2f", curlink->color[0], curlink->color[1], curlink->color[2]);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Angles %i %i %i", (int)curlink->angles[0], (int)curlink->angles[1], (int)curlink->angles[2]);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Cone %f", curlink->_cone);
			DrawMixedString (4, vid.height-pos, str, 4);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Style %i[%i] (%s)", curlink->style, curlink->cl_style, GetFirstFreeStyle());
			DrawMixedString (4, vid.height-pos, str, 5);
			pos-=16;

			if (curlink->filtercube_start == curlink->filtercube_end)
				Com_sprintf(str, sizeof(str), "Filter %i", curlink->filtercube_start);
			else
				Com_sprintf(str, sizeof(str), "Filter %i to %i (%f)", curlink->filtercube_start, curlink->filtercube_end, curlink->framerate);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Label %s", curlink->label);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;
		}
	}
	else
	{	// static lights mode
		if (curlight)
		{
			TraceLight ();

			pos-=16;

			if (curlight->flareLight)
			{
				Com_sprintf(str, sizeof(str), "Current static flareLight is %i [total %i]", curlight-shadowlights, numStaticShadowLights);
				DrawMixedString (4, vid.height-pos, str, 28);
			}
			else
			{
				Com_sprintf(str, sizeof(str), "Current static Light is %i [total %i]", curlight-shadowlights, numStaticShadowLights);
				DrawMixedString (4, vid.height-pos, str, 23);
			}
			pos-=16;

			if (curlight->sphere)
				Com_sprintf(str, sizeof(str), "Radius %i", (int)curlight->radius);
			else
				Com_sprintf(str, sizeof(str), "Radius %i %i %i", (int)curlight->radiuses[0], (int)curlight->radiuses[1], (int)curlight->radiuses[2]);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Origin %i %i %i", (int)curlight->origin[0], (int)curlight->origin[1], (int)curlight->origin[2]);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Color %1.2f %1.2f %1.2f", curlight->color[0], curlight->color[1], curlight->color[2]);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Angles %i %i %i", (int)curlight->angles[0], (int)curlight->angles[1], (int)curlight->angles[2]);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Rot.Speed %f %f %f", curlight->rspeed[0], curlight->rspeed[1], curlight->rspeed[2]);
			DrawMixedString (4, vid.height-pos, str, 9);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Cone %f", curlight->_cone);
			DrawMixedString (4, vid.height-pos, str, 4);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Style %i[%i] (%s)", curlight->style, curlight->cl_style, GetFirstFreeStyle());
			DrawMixedString (4, vid.height-pos, str, 5);
			pos-=16;

			if (curlight->filtercube_start == curlight->filtercube_end)
				Com_sprintf(str, sizeof(str), "Filter %i", curlight->filtercube_start);
			else
				Com_sprintf(str, sizeof(str), "Filter %i to %i (%f)", curlight->filtercube_start, curlight->filtercube_end, curlight->framerate);
			DrawMixedString (4, vid.height-pos, str, 6);
			pos-=16;

			Com_sprintf(str, sizeof(str), "Targetname %s", curlight->targetname);
			DrawMixedString (4, vid.height-pos, str, 10);
			pos-=16;

			if(curlight->start_off)
				Com_sprintf(str, sizeof(str), "Start off");
			else
				Com_sprintf(str, sizeof(str), "Start on");
			DrawMixedString (4, vid.height-pos, str, 5);
			pos-=16;

			if(curlight->noshadow)
			{
				DrawMixedString (4, vid.height-pos, "NoShadow.World", 8);
				pos-=16;
			}

			if(curlight->noshadow2)
			{
				DrawMixedString (4, vid.height-pos, "NoShadow.Models", 8);
				pos-=16;
			}

			if(curlight->nobump)
			{
				DrawMixedString (4, vid.height-pos, "NoBump", 6);
				pos-=16;
			}
		}
	}

	if (curemit)
	{
		TraceEmit();

		pos-=16;

		Com_sprintf(str, sizeof(str), "Current emitter is %i [total %i]", curemit-emits, numEmits);
		DrawMixedString (4, vid.height-pos, str, 18);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Origin %i %i %i", (int)curemit->origin[0], (int)curemit->origin[1], (int)curemit->origin[2]);
		DrawMixedString (4, vid.height-pos, str, 6);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Angles %i %i", (int)curemit->angles[0], (int)curemit->angles[1]);
		DrawMixedString (4, vid.height-pos, str, 6);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Velocity %i", (int)curemit->vel);
		DrawMixedString (4, vid.height-pos, str, 8);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Rot.Speed %i %i", (int)curemit->rspeed[0], (int)curemit->rspeed[1]);
		DrawMixedString (4, vid.height-pos, str, 9);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Num particles %i", (int)curemit->rspeed[2]);
		DrawMixedString (4, vid.height-pos, str, 13);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Cone %f", curemit->cone);
		DrawMixedString (4, vid.height-pos, str, 4);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Style %i[%i]", curemit->style, curemit->cl_style);
		DrawMixedString (4, vid.height-pos, str, 5);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Type %i", curemit->emit);
		DrawMixedString (4, vid.height-pos, str, 4);
		pos-=16;

		if (curemit->label[0])
		{
			Com_sprintf(str, sizeof(str), "Label %s", curemit->label);
			DrawMixedString (4, vid.height-pos, str, 5);
			pos-=16;
		}

		if (!VectorCompare(curemit->startcolor, vec3_origin))
		{
			Com_sprintf(str, sizeof(str), "Color from %1.2f %1.2f %1.2f to %1.2f %1.2f %1.2f", curemit->startcolor[0], curemit->startcolor[1], curemit->startcolor[2], curemit->endcolor[0], curemit->endcolor[1], curemit->endcolor[2]);
			DrawMixedString (4, vid.height-pos, str, 5);
			pos-=16;
		}

		if (curemit->alphavel)
		{
			Com_sprintf(str, sizeof(str), "LifeTime %f", curemit->alphavel);
			DrawMixedString (4, vid.height-pos, str, 8);
			pos-=16;
		}

		if (curemit->gravity)
		{
			Com_sprintf(str, sizeof(str), "Gravity %i", curemit->gravity);
			DrawMixedString (4, vid.height-pos, str, 7);
			pos-=16;
		}
	}

	if (curdecal)
	{
		pos-=16;

		i = curdecal->texture-decal_texture_object[0];
		if (i>=MAX_GLOBAL_FILTERS)
			i = curdecal->texture-decal_texture_object[MAX_GLOBAL_FILTERS]+MAX_GLOBAL_FILTERS;
		Com_sprintf(str, sizeof(str), "Decal's type: %i", i);
		DrawMixedString (4, vid.height-pos, str, 13);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Angle: %i", (int)curdecal->angle);
		DrawMixedString (4, vid.height-pos, str, 6);
		pos-=16;

		if (curdecal->dtype==dt_add)
			Com_sprintf(str, sizeof(str), "Blend: ADD");
		else
			Com_sprintf(str, sizeof(str), "Blend: MUL");
		DrawMixedString (4, vid.height-pos, str, 6);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Style %i[%i]", curdecal->style, curdecal->cl_style);
		DrawMixedString (4, vid.height-pos, str, 5);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Size: %i", (int)curdecal->size);
		DrawMixedString (4, vid.height-pos, str, 5);
		pos-=16;
	}

	if (curfog)
	{
		pos-=16;

		Com_sprintf(str, sizeof(str), "Fog bbox: from %i %i %i to %i %i %i", (int)curfog->origin[0], (int)curfog->origin[1], (int)curfog->origin[2], (int)curfog->origin2[0], (int)curfog->origin2[1], (int)curfog->origin2[2]);
		DrawMixedString (4, vid.height-pos, str, 9);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Fog color: %1.2f %1.2f %1.2f", curfog->color[0], curfog->color[1], curfog->color[2]);
		DrawMixedString (4, vid.height-pos, str, 10);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Fog density: %f", 1.0/curfog->density);
		DrawMixedString (4, vid.height-pos, str, 12);
		pos-=16;
	}

	if (curEFXzone)
	{
		pos -= 16;

		Com_sprintf(str, sizeof(str), "EFX zone bbox: from %i %i %i to %i %i %i", (int)curEFXzone->origin[0], (int)curEFXzone->origin[1], (int)curEFXzone->origin[2], (int)curEFXzone->origin2[0], (int)curEFXzone->origin2[1], (int)curEFXzone->origin2[2]);
		DrawMixedString(4, vid.height - pos, str, 14);
		pos -= 16;

		Com_sprintf(str, sizeof(str), "EFX zone preset: %i - %s", curEFXzone->preset, EFX_Names[curEFXzone->preset]);
		DrawMixedString(4, vid.height - pos, str, 16);
		pos -= 16;
	}

	if (efx_preset_autoselected)
	{
		if (efx_preset_autoselected > 0)
		{
			Com_sprintf(str, sizeof(str), "You are in EFX zone with automatic selected preset: %i - %s", efx_preset_autoselected, EFX_Names[efx_preset_autoselected]);
			DrawMixedString(4, vid.height - pos, str, 52);
		}
		else
		{
			Com_sprintf(str, sizeof(str), "You are in EFX zone with manual selected preset: %i - %s", -efx_preset_autoselected, EFX_Names[-efx_preset_autoselected]);
			DrawMixedString(4, vid.height - pos, str, 49);
		}
		pos -= 16;
	}

	if (curmodel)
	{
		TraceModel();

		pos-=16;

		if (curmodel->mdl->invert)
			Com_sprintf(str, sizeof(str), "Model's name: %s (x%4.2f Inverted)", curmodel->model, curmodel->mdl->scale);
		else
			Com_sprintf(str, sizeof(str), "Model's name: %s (x%4.2f)", curmodel->model, curmodel->mdl->scale);
		DrawMixedString (4, vid.height-pos, str, 13);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Origin %4.2f %4.2f %4.2f", curmodel->origin[0], curmodel->origin[1], curmodel->origin[2]);
		DrawMixedString (4, vid.height-pos, str, 6);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Angles %i %i %i", (int)curmodel->angles[0], (int)curmodel->angles[1], (int)curmodel->angles[2]);
		DrawMixedString (4, vid.height-pos, str, 6);
		pos-=16;

		if (curmodel->frame < curmodel->frame_end)
			Com_sprintf(str, sizeof(str), "Frames from %i to %i", curmodel->frame, curmodel->frame_end);
		else
			Com_sprintf(str, sizeof(str), "Frame %i", curmodel->frame);
		DrawMixedString (4, vid.height-pos, str, 6);
		pos-=16;

		Com_sprintf(str, sizeof(str), "Skin %i", curmodel->skinnum);
		DrawMixedString (4, vid.height-pos, str, 4);
		pos-=16;

		if (curmodel->label[0] && curmodel->label[0]!=' ')
		{
			Com_sprintf(str, sizeof(str), "Label %s", curmodel->label);
			DrawMixedString (4, vid.height-pos, str, 5);
			pos-=16;
		}

		if(curmodel->animframe)
		{
			DrawMixedString (4, vid.height-pos, "AnimFrame", 9);
			pos-=16;
		}

///		if(curmodel->sound[0])
///		{
///			if (curmodel->noise && curmodel->noise->cache)
///				Com_sprintf(str, sizeof(str), "Noise %s", curmodel->sound);
///			else
///				Com_sprintf(str, sizeof(str), "Noise %s (not found)", curmodel->sound);
///			DrawMixedString (4, vid.height-pos, str, 5);
///			pos-=16;
///		}

		if (curmodel->mdl->type == mod_alias)
		{
			Com_sprintf(str, sizeof(str), "Current model tri: %i (Max: %i)", curtri, curmodel->mdl->max_tris);
			DrawMixedString (4, vid.height-pos, str, 18);
		}
		else
		{
			Com_sprintf(str, sizeof(str), "Current model mesh/tri: %i / %i (Max: %i / %i)", curmesh, curtri, curmodel->mdl->max_meshes, curmodel->mdl->max_tris);
			DrawMixedString (4, vid.height-pos, str, 23);
		}
		pos-=16;
	}

	if (curbrush)
	{
		pos-=16;

		// ищем энтити
		for (i=0 ; i<cl.refdef.num_entities ; i++)
		{
			currententity = &cl.refdef.entities[i];
			currentmodel = currententity->model;

			if (!currentmodel)
				continue;

			if (currentmodel->type != mod_brush)
				continue;

			if (currentmodel->nummodelsurfaces == 0)
				continue;

			if (currentmodel->firstmodelsurface == curbrush->firstface)
				break;
		}

		if (i==cl.refdef.num_entities)
			return pos-16;

		for (i=0; i<brushmodel_counter; i++)
			if (bmdl_list[i].index == curbrushnum)
				break;

		VectorCopy(currententity->origin, curbrush_offset);
		if (bmdl_list[i].nodraw)
		{
			if (i==brushmodel_counter)
			{
				DrawMixedString (4, vid.height-pos, va("BrushModel_NODRAW \"*%i\" org(%i,%i,%i) ang(%i,%i,%i)\n", curbrushnum, (int)currententity->origin[0], (int)currententity->origin[1], (int)currententity->origin[2], (int)currententity->angles[0], (int)currententity->angles[1], (int)currententity->angles[2]), 17);
				pos-=16;
			}
			else
			{
				DrawMixedString (4, vid.height-pos, va("BrushModel_NODRAW \"*%i\" org(%i,%i,%i) ang(%i,%i,%i) label = \"%s\"\n", curbrushnum, (int)currententity->origin[0], (int)currententity->origin[1], (int)currententity->origin[2], (int)currententity->angles[0], (int)currententity->angles[1], (int)currententity->angles[2], bmdl_list[i].label), 17);
				pos-=16;
			}
		}
		else
		{
			if (i==brushmodel_counter)
			{
				DrawMixedString (4, vid.height-pos, va("BrushModel \"*%i\" org(%i,%i,%i) ang(%i,%i,%i)\n", curbrushnum, (int)currententity->origin[0], (int)currententity->origin[1], (int)currententity->origin[2], (int)currententity->angles[0], (int)currententity->angles[1], (int)currententity->angles[2]), 10);
				pos-=16;
			}
			else
			{
				DrawMixedString (4, vid.height-pos, va("BrushModel \"*%i\" org(%i,%i,%i) ang(%i,%i,%i) label = \"%s\"\n", curbrushnum, (int)currententity->origin[0], (int)currententity->origin[1], (int)currententity->origin[2], (int)currententity->angles[0], (int)currententity->angles[1], (int)currententity->angles[2], bmdl_list[i].label), 10);
				pos-=16;
			}
		}
	}

	return pos-16;
}


/// Berserker: эта версия игнорирует alias-модели
void CL_ClipMoveToEntitiesWorld ( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, trace_t *tr, int mask )
{
	int			i;
	trace_t		trace;
	int			headnode;
	float		*angles;
	entity_state_t	*ent;
	int			num;
	cmodel_t	*cmodel;

	for (i=0 ; i<cl.frame.num_entities ; i++)
	{
		num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
		ent = &cl_parse_entities[num];

		if (!ent->solid)
			continue;

		if (ent->solid != 31)	// special value for bmodel
			continue;

		cmodel = cl.model_clip[ent->modelindex];
		if (!cmodel)
			continue;
		headnode = cmodel->headnode;
		angles = ent->angles;

		if (tr->allsolid)
			return;

		trace = CM_TransformedBoxTrace (start, end, mins, maxs, headnode,  mask, ent->origin, angles);

		if (trace.allsolid || trace.startsolid || trace.fraction < tr->fraction)
		{
			trace.ent = (struct edict_s *)ent;
		 	if (tr->startsolid)
			{
				*tr = trace;
				tr->startsolid = true;
			}
			else
				*tr = trace;
		}
		else if (trace.startsolid)
			tr->startsolid = true;
	}
}


/// Berserker: эта версия игнорирует alias-модели
trace_t		CL_PMTraceWorld (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int mask)
{
	trace_t	t;

	// check against world
	t = CM_BoxTrace (start, end, mins, maxs, 0, mask);
	if (t.fraction < 1.0)
		t.ent = (struct edict_s *)1;

	// check all other solid models
	CL_ClipMoveToEntitiesWorld (start, mins, maxs, end, &t, mask);

	return t;
}


void GetSurfaceColor(float *or, float *og, float *ob, trace_t *otrace)
{
	vec3_t	end;

	VectorMA (r_origin, 32768, vpn, end);	// was 2048, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
	trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, (MASK_SOLID|MASK_WATER|CONTENTS_AUX));

///	if(trace.surface->name)
	{
		if(trace.surface->name[0])
		{
			float	r,g,b,t;
			r = float(trace.surface->color_r)/255.0;
			g = float(trace.surface->color_g)/255.0;
			b = float(trace.surface->color_b)/255.0;
			t = max(r, max(g, b));
			if (t>0.001)
				t = 1.0 / t;
			*or = r * t;
			*og = g * t;
			*ob = b * t;
		}
	}
	if (otrace)
		Memcpy(otrace, &trace, sizeof(trace_t));
}

void DrawQuickSavedShot(void *m)
{
	int		i;
	char	mapfile[64], *savdir;
	menuaction_s	*menu = (menuaction_s*)m;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	i = menu->generic.localdata[0];
	if (m_savevalid[i])
	{
		strcpy(mapfile, va("pics/../%s/quick%i/shot.tga", savdir, i/*m_saveshots[i]*/));
		R_FreePic(mapfile);
		FS_ClearFileCache(mapfile);
		strcpy(mapfile, va("../%s/quick%i/shot.tga", savdir, i/*m_saveshots[i]*/));
		Draw_PicSized( ((viddef.width-320)>>1) + 140, ((viddef.height-240)>>1) + 100, 160, 120, mapfile );
		Menu_DrawStatusBar(m_savestrings[i]);
	}
}


void DrawSavedShot(void *m)
{
	int		i;
	char	mapfile[64], *savdir;
	menuaction_s	*menu = (menuaction_s*)m;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	i = menu->generic.localdata[0];
	if (m_savevalid[i])
	{
		if (i)
		{
			strcpy(mapfile, va("pics/../%s/save%i/shot.tga", savdir, i/*m_saveshots[i]*/));
			R_FreePic(mapfile);
			FS_ClearFileCache(mapfile);
			strcpy(mapfile, va("../%s/save%i/shot.tga", savdir, i/*m_saveshots[i]*/));
		}
		else
			strcpy(mapfile, va("levelshots/%s.jpg", m_savemapnames[i]));
		Draw_PicSized( ((viddef.width-320)>>1) + 140, ((viddef.height-240)>>1) + 100, 160, 120, mapfile );
		if (i)
			Menu_DrawStatusBar(m_savestrings[i]);
		else
			Menu_DrawStatusBar(va("Start the map '%s'",m_savemapnames[i]));
	}
}


int	SCR_ShowTexture(int pos)
{
	if (!scr_texture->value)
		return pos;

	if(m_menudepth)		// Не рисовать имя текстуры когда находимся в меню
		return pos;

	float	r,g,b;
	trace_t	trace;
	GetSurfaceColor(&r, &g, &b, &trace);

///	if(trace.surface->name)
		if(trace.surface->name[0])
		{
			char	str[MAX_QPATH];
			Com_sprintf (str, sizeof(str),"%s: C(%1.2f %1.2f %1.2f) N(%1.2f %1.2f %1.2f)", trace.surface->name, r, g, b, trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2]);
			DrawAltString (4, vid.height-pos, str);
			SCR_AddDirtyPoint (4, vid.height-pos);
			SCR_AddDirtyPoint (4+16*sizeof(str), vid.height-pos+16);
/*	не нужно никому...
			if (scr_texture->value>1)
			{
				image_t *img = GL_FindImage(va("textures/%s",trace.surface->name), it_wall, false, NULL, false, 3);	// ищем существующую текстуру
				if (img && img!=r_notexture)
				{
					image_t	*image;
					char	ch[256];
					char	ch2[256];
					char	ch5[80];
					char	ch6[80];
					ch2[0] = 0;
					if (scr_texture->value==2)
					{
						image = img;
						Com_sprintf(ch, sizeof(ch), "ColorMap");
						if (image)
						{
							char *ch3 = "";
							char *ch4 = "";
							char *ch4_ = "";
							char *ch4__ = "";
							ch5[0] = 0;
							ch6[0] = 0;
							if (image->mirror)
								ch3 = "Mirror";
							if (image->AlphaTest)
								ch4 = "AlphaTest";
							if (image->CastShadow)
								ch4__ = "CastShadow";
							if (image->Parallax)
								ch4_ = "Parallax";
							if (image->material)
								Com_sprintf(ch5, sizeof(ch5), "Material \"%s\"", image->material->name);
							if (image->fx_detail_image_name && image->fx_detail_image_name[0])
								Com_sprintf(ch6, sizeof(ch6), "DetailBump \"%s\"", image->fx_detail_image_name);
							Com_sprintf(ch2, sizeof(ch2), "[%s %s %s %s %s]", ch3, ch4, ch4_, ch4__, ch5, ch6);
						}
					}
					else if (scr_texture->value==3)		// fx image
					{
						image = img->fx_image;
						Com_sprintf(ch, sizeof(ch), "FX");
						if (image)
						{
							if (img->fx == fx_chrome)
								Com_sprintf(ch2, sizeof(ch2), "[Chrome: Scale_S = %2.2f, Scale_T = %2.2f]", img->fx_scale_s, img->fx_scale_t);
							else if (img->fx == fx_power)
								Com_sprintf(ch2, sizeof(ch2), "[Power: Scale_S = %2.2f, Scale_T = %2.2f, Scroll_S = %2.1f, Scroll_T = %2.1f]", img->fx_scale_s, img->fx_scale_t, img->fx_s, img->fx_t);
							else if (img->fx == fx_map)
								Com_sprintf(ch2, sizeof(ch2), "[Map: Scale_S = %2.2f, Scale_T = %2.2f, Scroll_S = %2.1f, Scroll_T = %2.1f]", img->fx_scale_s, img->fx_scale_t, img->fx_s, img->fx_t);
							else if (img->fx == fx_distort)
								Com_sprintf(ch2, sizeof(ch2), "[Distort: Scale_S = %2.2f, Scale_T = %2.2f, Scroll_S = %2.1f, Scroll_T = %2.1f]", img->fx_scale_s, img->fx_scale_t, img->fx_s, img->fx_t);
							else if (img->fx == fx_style)
								Com_sprintf(ch2, sizeof(ch2), "[Style: R = %1.2f, G = %1.2f, B = %1.2f, Style = %i / CL_Style = %i]", img->fx_t, img->fx_scale_s, img->fx_scale_t, (((unsigned)(img->fx_s)) & 0xff), ((((unsigned)(img->fx_s)) >> 8 ) & 0xff));
							else
								Com_sprintf(ch2, sizeof(ch2), "[None]");
						}
					}
					else
					{
						image = img->fx_detail_image;	// detail bump
						Com_sprintf(ch, sizeof(ch), "DetailedBump");
						if (image)
							Com_sprintf(ch2, sizeof(ch2), "[Scale = %2.2f]", img->detail_scale);
					}

					if (image)
					{
						pos-=16;
						DrawAltString (4, vid.height-pos, ch2);
						pos-=16;
						DrawAltString (4, vid.height-pos, va("%s \"%s\"", ch, image->name));

						glDisable(GL_ALPHA_TEST);

						GL_Bind2D (image->texnum);
						glBegin (GL_QUADS);
						glTexCoord2f (0, 0);
						glVertex2f (0, 0);
						glTexCoord2f (1, 0);
						glVertex2f (256, 0);
						glTexCoord2f (1, 1);
						glVertex2f (256, 256);
						glTexCoord2f (0, 1);
						glVertex2f (0, 256);
						glEnd ();

						glEnable(GL_ALPHA_TEST);
					}
				}
			}
*/
		}

	return pos-16;
}

int SCR_ViewPos (int pos)
{
	char	st[128];

	if(m_menudepth)		// Не рисовать, когда находимся в меню
		return pos;

	if(!scr_viewpos->value)
		return pos;

	Com_sprintf (st, sizeof(st), "Orgs / Angs: %i %i %i / %i %i %i", int(cl.refdef.vieworg[0]), int(cl.refdef.vieworg[1]), int(cl.refdef.vieworg[2]), int(anglemod(cl.refdef.viewangles[0])), int(anglemod(cl.refdef.viewangles[1])), int(anglemod(cl.refdef.viewangles[2])));
	DrawMixedString (4, vid.height-pos, st, 12);
	SCR_AddDirtyPoint (4, vid.height-pos);
	SCR_AddDirtyPoint (4+16*128, vid.height-pos+16);
	return pos-16;
}

int SCR_DrawSpeed (int pos)
{
	char		st[32];
	vec3_t		vel;
	float		speed;

	if ((cls.state != ca_active) || cl.attractloop)
	{
		last_cvar_drawspeed = true;
		return pos;
	}

	if(m_menudepth)		// Не рисовать, когда находимся в меню
		return pos;

	if(!scr_drawspeed->value)
	{
		last_cvar_drawspeed = true;
		return pos;
	}

	if ((last_sys_time >= sys_time) || last_cvar_drawspeed)	// setup counters...
	{
		prev_sys_time = last_sys_time = sys_time;
		display_speed = -1;
		max_speed = extraspeed = 0;
		last_cvar_drawspeed = false;
		VectorCopy(cl.refdef.vieworg, cur_orgs);
		VectorCopy(cl.refdef.vieworg, old_orgs);
	}
	else
	{
		VectorCopy(cur_orgs, old_orgs);
		VectorCopy(cl.refdef.vieworg, cur_orgs);
		VectorSubtract(cur_orgs, old_orgs, vel);
		vel[2] = 0;
		speed = VectorLength (vel);
		if (speed - max_speed > 128)		/// Если скорость за 1 кадр поменялась более чем на 128 единиц - это или хороший пинок в зад :), или телепортация, или респаун - не будем считать такую скорость, ибо нет тут заслуги игрока!
			speed = 0;
		else
			speed *= 1000.0/(float)(sys_time - prev_sys_time);

		prev_sys_time = sys_time;

		if (speed > max_speed)
				max_speed = speed;

		if (max_speed > extraspeed)
			extraspeed = max_speed;

		if (display_speed >= 0)
		{
			Com_sprintf (st, sizeof(st), "%i / %i", (int)extraspeed, (int)display_speed);
			DrawAltString (4, vid.height-pos, st);
			SCR_AddDirtyPoint (4, vid.height-pos);
			SCR_AddDirtyPoint (4+16*32, vid.height-pos+16);
		}

		if (sys_time - last_sys_time >= 1000*VEL_UPDATE_TIME)
		{
			last_sys_time = sys_time;
			display_speed = max_speed;
			max_speed = 0;
		}
	}
	return pos-16;
}

inline void R_OverBright(int over)
{
	if(hud_overbright->value)
	{
		gl_TexEnv( GL_COMBINE_ARB );
		GL_TexEnv( GL_RGB_SCALE_ARB, over );
	}
}

void R_SaveRenderState()
{
	int	i;
	old_wmdl = r_worldmodel;
///	old_r_finish = r_finish->value;
	old_fps_count = fps_count;
	old_r_framecount = r_framecount;
	old_occ_framecount = occ_framecount;
	VectorCopy(vpn, old_vpn);
	VectorCopy(vright, old_vright);
	VectorCopy(vup, old_vup);
	VectorCopy(r_origin, old_r_origin);
	for(i=0; i<4; i++)
		old_frustum[i] = frustum[i];
}

void R_RestoreRenderState()
{
	int	i;
	r_worldmodel = old_wmdl;
///	r_finish->value = old_r_finish;
	fps_count = old_fps_count;
	r_framecount = old_r_framecount;
	occ_framecount = old_occ_framecount;
	VectorCopy(old_vpn, vpn);
	VectorCopy(old_vright, vright);
	VectorCopy(old_vup, vup);
	VectorCopy(old_r_origin, r_origin);
	for(i=0; i<4; i++)
		frustum[i] = old_frustum[i];
}

void R_Draw3DHud()
{
	int			i, value, drawed, old, old2;
	refdef_t	refdef;
	entity_t	entity;

	if (!scr_3dhud->value)
		return;

	if (cls.state != ca_active || !cl.refresh_prepped)
		return;

	float scale = scr_bighud->value ? 1 : 0.5;
	float hud_sx = viddef.width / 320.0 * scale;
	float hud_sy = viddef.height / 240.0 * scale;

	drawed = 0;
	currentshadowlight = &m_light;
	Memset( &refdef, 0, sizeof( refdef ) );
	Memset( &entity, 0, sizeof( entity ) );
	Memset(currentshadowlight, 0, sizeof(shadowlight_t));

	refdef.viewangles[0] = 23;
	refdef.fov_x = 45;
	refdef.time = cls.realtime*0.001;
	refdef.areabits = 0;
	refdef.num_entities = 1;
	refdef.entities = &entity;
	refdef.rdflags = RDF_NOWORLDMODEL | RDF_NOCLEAR;
	entity.angles[1] = anglemod(refdef.time*48);
	if ( entity.angles[1] > 360 )	entity.angles[1] -= 360;

	/// Light setup
	VectorSet(currentshadowlight->origin, -100, 0, 100);
	float v = scr_3dhud_intensity->value;
	VectorSet(currentshadowlight->styled_color, v,v,v);
	currentshadowlight->radiuses[0] = currentshadowlight->radiuses[1] = currentshadowlight->radiuses[2] = currentshadowlight->radius = 2000;
	currentshadowlight->sphere = true;
	VectorSet(currentshadowlight->mins, -256, -256, -256);
	VectorSet(currentshadowlight->maxs, 256, 256, 256);
	VectorSet(currentshadowlight->mins_cone, -256, -256, -256);
	VectorSet(currentshadowlight->maxs_cone, 256, 256, 256);

	R_SaveRenderState();
	r_worldmodel = NULL;
///	r_finish->value = 0;
	old = r_framecount;
	old2 = occ_framecount;

	int max_images;
	if (net_compatibility->value)
		max_images = MAX_IMAGES_Q2;
	else
		max_images = MAX_IMAGES_BERS;

	for (value=0; value<max_images; value++)
	{
		if (hud_model_framecount[value] == r_framecount)
		{
			entity.model = hud_models[value];
			entity.skin = hud_skins[value];
			entity.bump = hud_bumps[value];
			entity.light = hud_lights[value];
			VectorSet(refdef.vieworg, -entity.model->radius*1.5, 0, entity.model->radius*1.5*0.39);
			VectorNegate(entity.model->center, entity.origin);

			// Копируем некоторые флаги model во флаги entity
			entity.flags = entity.model->flags & (RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|RF_LIGHT|RF_FULLBRIGHT|RF_NOCASTSHADOW|RF_TRANSLUCENT);
			if (r_simple->value)
				entity.flags &= ~(/*RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|*/RF_LIGHT|RF_NOCASTSHADOW);

			for (i=0; i<hud_model_counter[value]; i++)
			{
				refdef.x = hud_model_origin[value][i][0];
				refdef.y = hud_model_origin[value][i][1];
				refdef.width = 24*hud_sx*hud_model_scale[value][i];
				refdef.height = 24*hud_sy*hud_model_scale[value][i];
				refdef.fov_y = CalcFov( refdef.fov_x, refdef.width, refdef.height );
				R_RenderView ( &refdef, false );
			}
			r_framecount = old;
			occ_framecount = old2;

			if (entity.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE))
			{
				entity.flags = (entity.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE));

				for (i=0; i<hud_model_counter[value]; i++)
				{
					refdef.x = hud_model_origin[value][i][0];
					refdef.y = hud_model_origin[value][i][1];
					refdef.width = 24*hud_sx*hud_model_scale[value][i];
					refdef.height = 24*hud_sy*hud_model_scale[value][i];
					refdef.fov_y = CalcFov( refdef.fov_x, refdef.width, refdef.height );
					R_RenderView ( &refdef, false );
				}
				r_framecount = old;
				occ_framecount = old2;
			}

			drawed = 1;
		}
	}

	// draw player's head model
	if (hud_player_framecount == r_framecount)
	{
		VectorSet(currentshadowlight->origin, -100, 0, 16);
		v = scr_3dhud_intensity->value;
		VectorSet(currentshadowlight->styled_color, v,v,v);

		refdef.viewangles[0] = 0;
		refdef.fov_x = 20;///15;	FIXED: вылечил нижнее обрезание головы мужика в хаде

		entity.model = hud_player_model;
		entity.skin = hud_player_skin;
		entity.bump = hud_player_bump;
		entity.light = hud_player_light;
		VectorNegate(entity.model->center, entity.origin);
///	VectorSet(refdef.vieworg, -entity.model->radius*4.3, 0, -entity.model->radius*0.05);
	VectorSet(refdef.vieworg, -entity.model->radius*3.3, 0, 0);

#if 0
		// old q2bers style
		entity.angles[1] = sin(anglemod(refdef.time)) * 30 + 180;
#else
		// q3 style (from cg_newdraw.c)
		float	frac;
		int		time = refdef.time * 1000;

		if ( time >= headEndTime )
		{	// select a new head angle
			headStartYaw = headEndYaw;
			headStartPitch = headEndPitch;
			headStartTime = headEndTime;
			headEndTime = time + 100 + random() * 2000;

			headEndYaw = 180 + 30 * cos( crandom()*M_PI );	// 30, was 20
			headEndPitch = 10 * cos( crandom()*M_PI );		// 10, was 5
		}

		// if the server was frozen for a while we may have a bad head start time
		if ( headStartTime > time )
			headStartTime = time;

		frac = ( time - headStartTime ) / (float)( headEndTime - headStartTime );
		frac = frac * frac * ( 3 - 2 * frac );
		entity.angles[YAW] = headStartYaw + ( headEndYaw - headStartYaw ) * frac;
		entity.angles[PITCH] = headStartPitch + ( headEndPitch - headStartPitch ) * frac;
#endif

		// Копируем некоторые флаги model во флаги entity
		entity.flags = entity.model->flags & (RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|RF_LIGHT|RF_FULLBRIGHT|RF_NOCASTSHADOW|RF_TRANSLUCENT);
		if (r_simple->value)
			entity.flags &= ~(/*RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|*/RF_LIGHT|RF_NOCASTSHADOW);

		refdef.x = hud_player_origin[0];
		refdef.y = hud_player_origin[1];
		refdef.width = 32*hud_sx*hud_player_scale;
		refdef.height = 32*hud_sy*hud_player_scale;
		refdef.fov_y = CalcFov( refdef.fov_x, refdef.width, refdef.height );

		centity_t *cent = &cl_entities[cl.playernum+1];						//////////////////////
		if (cent->current.effects & EF_INVIS)
		{
			entity.flags |= RF_TRANSLUCENT;	// для головы игрока не будем применять RF_DISTORT
			entity.alpha = 0.3;
		}

		R_RenderView ( &refdef, false );

		r_framecount = old;
		occ_framecount = old2;

		if (cent->current.effects & (EF_QUAD|EF_PENT|EF_COLOR_SHELL))
		{
			if (cent->current.effects & EF_QUAD)							// Тут мы добавляем синий SHELL если клиент взял QUAD
				entity.flags |= RF_SHELL_BLUE|RF_TRANSLUCENT;

			if (cent->current.effects & EF_PENT)							// Тут мы добавляем красный SHELL если клиент взял PENTAGRAM
				entity.flags |= RF_SHELL_RED|RF_TRANSLUCENT;

			// Тут мы добавляем белый SHELL если клиент in GOD mode...
			if ((cent->current.effects & EF_COLOR_SHELL) && (cent->current.renderfx & RF_SHELL_RED))
				entity.flags |= RF_SHELL_RED|RF_TRANSLUCENT;

			if ((cent->current.effects & EF_COLOR_SHELL) && (cent->current.renderfx & RF_SHELL_GREEN))
				entity.flags |= RF_SHELL_GREEN|RF_TRANSLUCENT;

			if ((cent->current.effects & EF_COLOR_SHELL) && (cent->current.renderfx & RF_SHELL_BLUE))
				entity.flags |= RF_SHELL_BLUE|RF_TRANSLUCENT;

			goto shell;
		}

		if (entity.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE))
		{
			entity.flags = (entity.model->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE));

shell:		refdef.x = hud_player_origin[0];
			refdef.y = hud_player_origin[1];
			refdef.width = 32*hud_sx*hud_player_scale;
			refdef.height = 32*hud_sy*hud_player_scale;
			refdef.fov_y = CalcFov( refdef.fov_x, refdef.width, refdef.height );
			R_RenderView ( &refdef, false );

			r_framecount = old;
			occ_framecount = old2;
		}
		SCR_AddDirtyPoint (refdef.x, refdef.y);
		SCR_AddDirtyPoint (refdef.x+32*hud_sx, refdef.y+32*hud_sy);

		drawed = 1;
	}
	R_RestoreRenderState();

//	вернуть 2D перспективу...
	if (drawed)
		R_SetGL2D();
}

void SCR_DrawPain()
{
	if (!num_pains)
		return;

	if (!r_using_pain)
		return;

	if (!rfx_pain->value)
		return;

	if (r_using_pain < cl.gameTime)
	{
		r_using_pain = 0;
		return;
	}

	image_t *gl = glfx_pain[r_using_pain_frame];

	glEnable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE);
	glDisable(GL_ALPHA_TEST);
	gl_TexEnv( GL_MODULATE );
	GL_Color4f(1,1,1,rfx_pain->value*((r_using_pain-cl.gameTime)/2001.0));

	GL_Bind2D (gl->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (scr_vrect.x, scr_vrect.y);
	glTexCoord2f (1, 0);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y);
	glTexCoord2f (1, 1);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y + scr_vrect.height);
	glTexCoord2f (0, 1);
	glVertex2f (scr_vrect.x, scr_vrect.y + scr_vrect.height);
	glEnd ();

	glDisable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_ALPHA_TEST);
	gl_TexEnv( GL_REPLACE );
	GL_Color3f(1,1,1);
}

void SCR_DrawUnderWater()
{
	if (!glfx_underwater)
		return;

	if(!(cl.frame.playerstate.rdflags & RDF_UNDERWATER))
		return;
////	if (!r_using_underwater)
////		return;

	if (!rfx_underwater->value)
		return;

	glEnable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE);
	glDisable(GL_ALPHA_TEST);
	gl_TexEnv( GL_MODULATE );
	GL_Color4f(1,1,1,rfx_underwater->value*(sin(cl.leveltime*4)*0.25+0.5));

	GL_Bind2D (glfx_underwater->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (scr_vrect.x, scr_vrect.y);
	glTexCoord2f (1, 0);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y);
	glTexCoord2f (1, 1);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y + scr_vrect.height);
	glTexCoord2f (0, 1);
	glVertex2f (scr_vrect.x, scr_vrect.y + scr_vrect.height);
	glEnd ();

	glDisable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_ALPHA_TEST);
	gl_TexEnv( GL_REPLACE );
	GL_Color3f(1,1,1);
}

void SCR_DrawDrowning()
{
	if (!glfx_drown)
		return;

	if (!r_using_drown)
		return;

	if (!rfx_drowning->value)
		return;

	float q = r_using_drown - cl.gameTime;
	if (q < 0)
	{
		r_using_drown = 0;
		return;
	}

	glEnable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE);
	glDisable(GL_ALPHA_TEST);
	gl_TexEnv( GL_MODULATE );
	GL_Color4f(1,1,1,rfx_drowning->value*(q/2000.0+0.25));

	CL_AddBubbles(q*0.008);

	GL_Bind2D (glfx_drown->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (scr_vrect.x, scr_vrect.y);
	glTexCoord2f (1, 0);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y);
	glTexCoord2f (1, 1);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y + scr_vrect.height);
	glTexCoord2f (0, 1);
	glVertex2f (scr_vrect.x, scr_vrect.y + scr_vrect.height);
	glEnd ();

	glDisable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_ALPHA_TEST);
	gl_TexEnv( GL_REPLACE );
	GL_Color3f(1,1,1);
}

void SCR_DrawBurning()
{
	if (!num_burns)
		return;

	if (!r_using_burning)
		return;

	if (!rfx_burning->value)
		return;

	if (r_using_burning < cl.gameTime)
	{
		r_using_burning = 0;
		return;
	}

	image_t *gl = glfx_burn[(cl.gameTime>>5)%num_burns];

	glEnable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE);
	glDisable(GL_ALPHA_TEST);
	gl_TexEnv( GL_MODULATE );
	GL_Color4f(0.5,0.5,0.5,rfx_burning->value*((r_using_burning-cl.gameTime)/2000.0+0.25));

	GL_Bind2D (gl->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (scr_vrect.x, scr_vrect.y);
	glTexCoord2f (1, 0);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y);
	glTexCoord2f (1, 1);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y + scr_vrect.height);
	glTexCoord2f (0, 1);
	glVertex2f (scr_vrect.x, scr_vrect.y + scr_vrect.height);
	glEnd ();

	glDisable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_ALPHA_TEST);
	gl_TexEnv( GL_REPLACE );
	GL_Color3f(1,1,1);
}

bool	invisibled_gun;
void SCR_DrawMask()
{
	if (!glfx_maskenv || !glfx_mask)
		return;

	if (invisibled_gun)
	{
		glEnable(GL_BLEND);
		GL_BlendFunc (GL_ONE, GL_ONE);
		GL_Color3f(0.3, 0.3, 0.3);
		gl_TexEnv( GL_MODULATE );
	}

	GL_Bind2D (glfx_mask->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (scr_vrect.x, scr_vrect.y);
	glTexCoord2f (1, 0);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y);
	glTexCoord2f (1, 1);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y + scr_vrect.height);
	glTexCoord2f (0, 1);
	glVertex2f (scr_vrect.x, scr_vrect.y + scr_vrect.height);
	glEnd ();

	GL_Bind2D (glfx_maskenv->texnum);
	GL_MBind2D (GL_TEXTURE1, glfx_maskenv->texnum);
	glEnable(GL_TEXTURE_2D);
	gl_TexEnv( GL_COMBINE_ARB );
	GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
	GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB );
	GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
	GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_TEXTURE );
	GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA );
///	GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
///	GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_TEXTURE );
///	GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
	if (!invisibled_gun)
	{
		glEnable(GL_BLEND);
		GL_BlendFunc (GL_ONE, GL_ONE);
	}
	glDisable(GL_ALPHA_TEST);

	float x = -cl.refdef.viewangles[1]/180.0;
	float y = cl.refdef.viewangles[0]/180.0;

	glBegin (GL_QUADS);
	glTexCoord2f (x, y);
	glMultiTexCoord2fARB(GL_TEXTURE1, 0,0);
	glVertex2f (scr_vrect.x, scr_vrect.y);
	glTexCoord2f (1+x, y);
	glMultiTexCoord2fARB(GL_TEXTURE1, 1,0);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y);
	glTexCoord2f (1+x, 1+y);
	glMultiTexCoord2fARB(GL_TEXTURE1, 1,1);
	glVertex2f (scr_vrect.x + scr_vrect.width, scr_vrect.y + scr_vrect.height);
	glTexCoord2f (x, 1+y);
	glMultiTexCoord2fARB(GL_TEXTURE1, 0,1);
	glVertex2f (scr_vrect.x, scr_vrect.y + scr_vrect.height);
	glEnd ();

	glDisable(GL_TEXTURE_2D);
	GL_SelectTexture(GL_TEXTURE0);
	gl_TexEnv( GL_REPLACE );
	glDisable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_ALPHA_TEST);
	if (invisibled_gun)
		GL_Color3f(1, 1, 1);

	// add the bubbles...
	float q = sin(cl.leveltime);
	if (q>0.7)
		CL_AddBubbles(q*5);
}

void SCR_DrawRFX()
{
///	if (scr_draw2d->value < 2)
///		return;

	if (cl_thirdPerson->value)
		return;

	SCR_DrawBurning();
	SCR_DrawPain();

	bool	use_mask = true;

	if (!(cl.frame.playerstate.rdflags & RDF_MASK))
		use_mask = false;
	else if (Cvar_VariableValue ("deathmatch"))
	{
		if (!rfx_mask_mp->value)
			use_mask = false;
	}
	else
	{
		if (!rfx_mask->value)
			use_mask = false;
	}

	if (!use_mask)
	{
		SCR_DrawDrowning();
		SCR_DrawUnderWater();
	}
	else
		SCR_DrawMask();
}

void SCR_Draw2D()
{
	if (scr_draw2d->value < 2)
		return;

	R_OverBright(2);

	if (LightEditor_Enabled(true))
	{
		if (r_editor_layoutstring->modified)
		{
			int		length;
			char	name[MAX_QPATH];
			char	*buffer;

			r_editor_layoutstring->modified = false;
			Com_sprintf (name, sizeof(name), "%s", r_editor_layoutstring->string);
			length = FS_LoadFile (name, (void **)&buffer);
			if (buffer && length < sizeof(editor_layoutstring) && length > 0)
			{
				Memcpy(editor_layoutstring, buffer, length);
				editor_layoutstring[length] = 0;
				Z_Free(buffer);
			}
			else
			{
				if (buffer)
					Z_Free(buffer);
				editor_layoutstring[0] = 0;
			}
		}
		if (editor_layoutstring[0])
			SCR_ExecuteLayoutString (editor_layoutstring, true);
	}
	else
		SCR_ExecuteLayoutString (cl.configstrings[CS_STATUSBAR], true);

	if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 1)
		SCR_DrawLayout ();
	if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 2)
		CL_DrawInventory ();

	SCR_DrawNet ();
	SCR_CheckDrawCenterString ();
	SCR_ShowDemoSize ();
	SCR_DrawFPS(SCR_DrawGameTime(SCR_DrawClock(SCR_DrawSpeed(SCR_ViewPos(SCR_ShowTexture(SCR_PrintReLight(SCR_DrawEFXzones(vid.height - 16*4))))))));

	R_OverBright(1);

//	Отложенное рисование 3D моделей в 3D перспективе
	R_Draw3DHud();

	SCR_DrawPause();
}

/*
==================
SCR_UpdateScreen

This is called every frame, and can also be called explicitly to flush
text to the screen.
==================
*/
void SCR_UpdateScreen ()
{
	// if the screen is disabled (loading plaque is up, or vid mode changing)
	// do nothing at all
	if (cls.disable_screen)
	{
		if (Sys_Milliseconds() - cls.disable_screen > 120000)	// Berserker: Wait connection-end packet from server; time out for 2 minutes. Fix it, if need more...
		{
			cls.disable_screen = 0;
			Com_Printf ("^3Loading plaque timed out.\n");
		}
		return;
	}

	if (!scr_initialized || !con.initialized)
		return;				// not initialized yet

	BeginFrame();

	if (scr_draw_loading == 2)
	{	//  loading plaque over black screen
		int		w, h;

		scr_draw_loading = false;
		Draw_GetPicSize (&w, &h, "loading");
		Draw_Pic ((viddef.width-w)/2, (viddef.height-h)/2, "loading");
	}
	// if a cinematic is supposed to be running, handle menus
	// and console specially
	else if (cl.cinematictime > 0)
	{
		if (cls.key_dest == key_menu)
		{
			R_VCFreeFrame();	// в M_Draw может вызываться рисование моделей
			M_Draw ();
		}
		else if (cls.key_dest == key_console)
		{
			SCR_DrawConsole ();
		}
		else
		{
			SCR_DrawCinematic();
		}
	}
	else 
	{
		// do 3D refresh drawing, and then update the screen
		SCR_CalcVrect();

		// clear any dirty part of the background
		SCR_TileClear ();

		need_free_vbo = true;
		if (!render_disable)
		{
			V_RenderView();

			SCR_DrawRFX ();

			// отложенный шот для сейва
			if (cls.state == ca_active && !m_menudepth && !scr_con_current)	// "в игре" и "не в меню" и "нет консоли"
				if (needsToDeferredShot[0])
				{
					R_GrabScreen(needsToDeferredShot);
					needsToDeferredShot[0] = 0;
				}
			SCR_DrawCrosshair ();
		}
		SCR_Draw2D();

/// Berserker: cl.gameTime равен cl.time, только непрерывен во время игры и останавливается с паузой
	oldClTime = clTime;
	clTime = cl.time;
	if (clTime < oldClTime)
		deltaTime += oldClTime - clTime;

	if (cl_paused->value)
		pausedTime = cl.gameTime;
	else
	{
		if (pausedTime)
		{
			deltaTime += pausedTime - cl.time - deltaTime;
			pausedTime = 0;
		}
		cl.gameTime = cl.time + deltaTime;
	}
/////
		if (cl.refdef.rdflags & RDF_BURN)
			r_using_burning = cl.gameTime + 1000;
		if (cl.refdef.rdflags & RDF_DROWN)
			r_using_drown = cl.gameTime + 1000;

		SCR_DrawConsole();

		if (need_free_vbo)		// Если требуется очистка - очистим
			R_VCFreeFrame();	// в M_Draw может вызываться рисование моделей
		M_Draw ();

		SCR_DrawLoading ();
	}

	End_Frame();
}


int Com_ServerState ()
{
	return server_state;
}


void Com_SetServerState (int state)
{
	server_state = state;
}


void CL_Pause_f ()
{
	if (!cl.attractloop)	/// Berserker: будем уверены, что пауза работает для дёмок
	{	// never pause in multiplayer
		if (Cvar_VariableValue ("maxclients") > 1 || !Com_ServerState ())
		{
			Cvar_SetValue ("paused", 0);
			return;
		}
	}

	Cvar_SetValue ("paused", !cl_paused->value);
}


void CL_KillClientEntities_f()
{
	int		i;
	
	free_clentities = &clentities[0];
	active_clentities = NULL;

	for (i=0 ;i<MAX_CLENTITIES ; i++)
		clentities[i].next = &clentities[i+1];
	clentities[MAX_CLENTITIES-1].next = NULL;
}

void CL_ResetEditor()
{
	le_mode = trace_decal = silent_decal = trace_light = trace_model = silent_model = trace_emit = false;
	curlight = NULL;
	curlink = NULL;
	curbrush = NULL;
	curemit = NULL;
	curdecal = NULL;
	curmodel = NULL;
	curfog = NULL;
	curEFXzone = NULL;
	curtexinfo = NULL;
	curtri = curmesh = curbrushnum = 0;
	Memset(&light_clipboard, 0, sizeof(light_clipboard));
	Memset(&link_clipboard, 0, sizeof(link_clipboard));
	Memset(&emit_clipboard, 0, sizeof(emit_clipboard));
	Memset(&decal_clipboard, 0, sizeof(decal_clipboard));
	Memset(&model_clipboard, 0, sizeof(model_clipboard));
	Cl_UpdateEditorCvars(ED_LIGHT | ED_EMIT | ED_DECAL | ED_FOG | ED_EFXZONE | ED_MODEL | ED_BRUSH | ED_AMBIENT);
}

void CL_ClearClEntities ()
{
	CL_KillClientEntities_f();

	// заодно почистим редактор
	CL_ResetEditor();
}


void SCR_BeginLoadingPlaque ()
{
	S_StopAllSounds ();
///		CL_ClearClEntities ();
		CL_ResetEditor();

	cl.sound_prepped = false;		// don't play ambients

	savedTrackName[0] = 0;
	Music_Stop();
//	CDAudio_Stop ();
///	S_StopBackgroundTrack();

	if (cls.disable_screen)
		return;
	if (developer->value)				// Пусть все видят консоль, нефига!
		return;
	if (cls.state == ca_disconnected)
		return;	// if at console, don't bring up the plaque
	if (cls.key_dest == key_console)
		return;
	if (cl.cinematictime > 0)
		scr_draw_loading = 2;	// clear to black first
	else
		scr_draw_loading = 1;

	SCR_UpdateScreen ();
	cls.disable_screen = Sys_Milliseconds ();
	cls.disable_servercount = cl.servercount;
}


/*
=================
SV_UserinfoChanged

Pull specific info from a newly changed userinfo string
into a more C freindly form.
=================
*/
void SV_UserinfoChanged (client_t *cl)
{
	char	*val;
	int		i;

	if (!cl->userinfo[0])
	{
		Com_Printf ("^3WARNING:^7 SV_UserinfoChanged for [%s] with empty userinfo!\n", NET_AdrToString (cl->netchan.remote_address));
		return;
	}

	//r1ch: kick anyone trying to use the end-of-message-in-string exploit
	if (strchr (cl->userinfo, '\xFF'))
	{
		Com_Printf ("^6EXPLOIT:^7 Client [%s] supplied userinfo string containing 0xFF\n", NET_AdrToString (cl->netchan.remote_address));
		SV_DropClient (cl);
		return;
	}

	//they tried to set name to something bad
	val = Info_ValueForKey (cl->userinfo, "name");
	if (!val[0] || StringIsWhitespace(val))
	{
		Com_Printf ("^3WARNING:^7 Invalid name change from [%s].\n", NET_AdrToString(cl->netchan.remote_address)); 
		SV_DropClient (cl);
		return;
	}

#if 0
	// name for C code (for cl->name)
	strncpy (cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name)-1);
	// mask off high bit
	for (i=0 ; i<sizeof(cl->name) ; i++)
		cl->name[i] &= 127;

	val = Info_ValueForKey (cl->userinfo, "ip");
	if (val[0])
	{
		Com_Printf ("^6EXPLOIT:^7 Dropping %s for attempted IP spoof (%s)\n", cl->name, val);
		SV_DropClient (cl);
		return;
	}
#else
	/// Berserker: вместо того, чтобы проверять на правильность IP адрес, тупо форсируем его установку реальным адресом клиента
	// force the IP key/value pair so the game can filter based on ip
	Info_SetValueForKey (cl->userinfo, "ip", NET_AdrToString(cl->netchan.remote_address));
#endif

	// call prog code to allow overrides
	ge->ClientUserinfoChanged (cl->edict, cl->userinfo);
	
	// name for C code
	strncpy (cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name)-1);
	// mask off high bit
	for (i=0 ; i<sizeof(cl->name) ; i++)
		cl->name[i] &= 127;

	// rate command
	val = Info_ValueForKey (cl->userinfo, "rate");
	if (val[0])
	{
		i = atoi(val);
		cl->rate = i;
		if(i!=SV_UNLIMITED_RATE)
		{
			if (cl->rate < 100)
				cl->rate = 100;
			if (cl->rate > 25000)	///15000
				cl->rate = 25000;	///15000
		}
	}
	else
		cl->rate = 25000;	///5000;

	// msg command
	val = Info_ValueForKey (cl->userinfo, "msg");
	if (val[0])
		cl->messagelevel = atoi(val);
}


/*
=========
Q_SortStricmp
=========
*/
int Q_SortStricmp (const void *a, const void *b)
{
	char	*aa, *bb;

	aa = *(char **)a;
	bb = *(char **)b;

	return Q_stricmp(aa, bb);
}

bool	one_match;
char	retval[256];
char *Cmd_CompleteCommand (char *partial)
{
	cmd_function_t	*cmd;
	cvar_t			*cvar;
	cmdalias_t		*a;
	int				len, i, o, p;
	bool			diff;
	unsigned		hash = Com_HashKey(partial);
	char			*pmatch[4096];

	len = strlen(partial);

	if (!len)
		return NULL;

// check exact match
	one_match = true;

	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
		if (hash == cmd->hash)
			if (!Q_strcasecmp(cmd->name, partial))
				return cmd->name;

	for (cvar=cvar_vars ; cvar ; cvar=cvar->next)
		if (hash == cvar->hash)
			if (!Q_strcasecmp(cvar->name, partial))
				return cvar->name;

	for (a=cmd_alias ; a ; a=a->next)
		if (hash == a->hash)
			if (!Q_strcasecmp(a->name, partial))
				return a->name;

// check partial match
	i=0;

	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
		if (!Q_strnicmp(partial, cmd->name, len))
		{
			if (i==4096)
				break;
			pmatch[i++] = cmd->name;
		}

	for (cvar=cvar_vars ; cvar ; cvar=cvar->next)
		if (!Q_strnicmp(partial, cvar->name, len))
		{
			if (i==4096)
				break;
			pmatch[i++] = cvar->name;
		}

	for (a=cmd_alias ; a ; a=a->next)
		if (!Q_strnicmp(partial, a->name, len))
		{
			if (i==4096)
				break;
			pmatch[i++] = a->name;
		}

	if (i)
	{
		char *cvstr;

		if (i == 1)
		{
			cvstr = Cvar_VariableString(pmatch[0]);
			if (cvstr && cvstr[0])
				Com_Printf("  %s = %s\n", pmatch[0], cvstr);
			else
				Com_Printf("  %s\n", pmatch[0]);

			Com_sprintf (retval, sizeof(retval), pmatch[0]);
			return retval;
		}

		// sort alphabetically
		qsort(pmatch, i, sizeof(char *), (cmp_t *)Q_SortStricmp);

		Com_Printf("Matches for '%s':\n", partial);
		for (o=0; o<i; o++)
		{
			cvstr = Cvar_VariableString(pmatch[o]);
			if (cvstr && cvstr[0])
				Com_Printf("  %s = %s\n", pmatch[o], cvstr);
			else
				Com_Printf("  %s\n", pmatch[o]);
		}

		retval[0] = 0;
		p = 0;
		diff = false;

		while (!diff && p < 256)
		{
			retval[p] = pmatch[0][p];

			for (o=0; o<i; o++)
			{
				if(p > strlen(pmatch[o]))
					continue;

				if((retval[p]|0x20) != (pmatch[o][p]|0x20))
				{
					retval[p] = 0;
					diff = false;
				}
			}
			p++;
		}
		one_match = false;
		Com_Printf("%i matches\n\n",i);
		return retval;
	}

	return NULL;
}


// Berserker's fix: позволим автозаполнять игнорируя ' ' и ';'
void CompleteCommand ()
{
	unsigned	cnt, stamp;
	char		*cmd, *s, *last;
	bool		first = true;
	bool		wasToken=false;
	bool		block=false;

	s = key_lines[edit_line]+1;
	if (*s == '\\' || *s == '/')
	{
		s++;
		stamp=cnt=2;
	}
	else
		stamp=cnt=1;

	last=s;
	while (1)
	{
		if (*s == 0)
			break;
		if (*s == ';')
		{
			block=wasToken=first=false;
			s++;
			last=s;
			stamp=cnt;
		}
		else if (*s == ' ')
		{
			s++;
			last=s;
			if (wasToken)
				block=true;
		}
		else
		{
			s++;
			wasToken=true;
		}
		cnt++;
	}

	if (!block)
	{
		cmd = Cmd_CompleteCommand (last);
		if (cmd)
		{
			if (first)
			{
				key_lines[edit_line][1] = '/';
				strcpy (key_lines[edit_line]+2, cmd);
				key_linepos = strlen(cmd)+2;
				if (one_match)
					key_lines[edit_line][key_linepos++] = ' ';
				key_lines[edit_line][key_linepos] = 0;
			}
			else
			{	// заполнение не первой команды
				if (strlen(key_lines[edit_line])+stamp+1+strlen(cmd)<MAXCMDLINE)
				{
					strcpy (key_lines[edit_line]+stamp+1, cmd);
					key_linepos = strlen(key_lines[edit_line]);
					if (one_match)
						key_lines[edit_line][key_linepos++] = ' ';
					key_lines[edit_line][key_linepos] = 0;
				}
			}
		}
	}
}


/*
====================
Key_Console

Interactive line editing and console scrollback
====================
*/
void Key_Console (int key)
{
	switch ( key )
	{
	case K_KP_SLASH:
		key = '/';
		break;
	case K_KP_MINUS:
		key = '-';
		break;
	case K_KP_PLUS:
		key = '+';
		break;
	case K_KP_HOME:
		key = '7';
		break;
	case K_KP_UPARROW:
		key = '8';
		break;
	case K_KP_PGUP:
		key = '9';
		break;
	case K_KP_LEFTARROW:
		key = '4';
		break;
	case K_KP_5:
		key = '5';
		break;
	case K_KP_RIGHTARROW:
		key = '6';
		break;
	case K_KP_END:
		key = '1';
		break;
	case K_KP_DOWNARROW:
		key = '2';
		break;
	case K_KP_PGDN:
		key = '3';
		break;
	case K_KP_INS:
		key = '0';
		break;
	case K_KP_DEL:
		key = '.';
		break;
	}

	if ( ( toupper( key ) == 'V' && keydown[K_CTRL] ) ||
		 ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keydown[K_SHIFT] ) )
	{
		char *cbd;
		
		if ( ( cbd = Sys_GetClipboardData() ) != 0 )
		{
			int i;

			strtok( cbd, "\n\r\b" );

			i = strlen( cbd );
			//r1: save byte for null terminator!!
			if ( i + key_linepos >= MAXCMDLINE - 1)
				i= MAXCMDLINE - key_linepos - 1;

			if ( i > 0 )
			{
				cbd[i]=0;
				strcat( key_lines[edit_line], cbd );
				key_linepos += i;
			}
			Z_Free( cbd );
		}

		return;
	}

	if ( key == 'l' ) 
	{
		if ( keydown[K_CTRL] )
		{
			Cbuf_AddText ("clear\n");
			return;
		}
	}

	if ( key == K_ENTER || key == K_KP_ENTER )
	{	// backslash text are commands, else chat
		if (key_lines[edit_line][1] == '\\' || key_lines[edit_line][1] == '/')
			Cbuf_AddText (key_lines[edit_line]+2);	// skip the >
		else
			Cbuf_AddText (key_lines[edit_line]+1);	// valid command

		Cbuf_AddText ("\n");
		Com_Printf ("%s\n",key_lines[edit_line]);
		edit_line = (edit_line + 1) & 31;
		history_line = edit_line;
		key_lines[edit_line][0] = ']';
		key_linepos = 1;
		if (cls.state == ca_disconnected)
			SCR_UpdateScreen ();	// force an update, because the command
									// may take some time
		return;
	}

	if (key == K_TAB)
	{	// command completion
		CompleteCommand ();
		return;
	}

	if ( ( key == K_BACKSPACE ) || ( key == K_LEFTARROW ) || ( key == K_KP_LEFTARROW ) || ( ( key == 'h' ) && ( keydown[K_CTRL] ) ) )
	{
		if (key_linepos > 1)
			key_linepos--;
		return;
	}

	if ( ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) ||
		 ( ( key == 'p' ) && keydown[K_CTRL] ) )
	{
		do
		{
			history_line = (history_line - 1) & 31;
		} while (history_line != edit_line
				&& !key_lines[history_line][1]);
		if (history_line == edit_line)
			history_line = (edit_line+1)&31;
		strcpy(key_lines[edit_line], key_lines[history_line]);
		key_linepos = strlen(key_lines[edit_line]);
		return;
	}

	if ( ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) ||
		 ( ( key == 'n' ) && keydown[K_CTRL] ) )
	{
		if (history_line == edit_line) return;
		do
		{
			history_line = (history_line + 1) & 31;
		}
		while (history_line != edit_line
			&& !key_lines[history_line][1]);
		if (history_line == edit_line)
		{
			key_lines[edit_line][0] = ']';
			key_linepos = 1;
		}
		else
		{
			strcpy(key_lines[edit_line], key_lines[history_line]);
			key_linepos = strlen(key_lines[edit_line]);
		}
		return;
	}

	if (key == K_PGUP || key == K_KP_PGUP )
	{
		con.display -= 2;
		return;
	}

	if (key == K_PGDN || key == K_KP_PGDN ) 
	{
		con.display += 2;
		if (con.display > con.current)
			con.display = con.current;
		return;
	}

	if (key == K_HOME || key == K_KP_HOME )
	{
		con.display = con.current - con.totallines + 10;
		return;
	}

	if (key == K_END || key == K_KP_END )
	{
		con.display = con.current;
		return;
	}

	if (key < 32 || key > 127)
		return;	// non printable

	if (key_linepos < MAXCMDLINE-1)
	{
		key_lines[edit_line][key_linepos] = key;
		key_linepos++;
		key_lines[edit_line][key_linepos] = 0;
	}
}


void M_Keydown(int key)
{
	if (m_keyfunc)
	{
		int s = m_keyfunc(key);
		if (s == cl_sfx_menu_in_sound)
			S_StartLocalSound(cl_sfx_menu_in_sound);
		if (s == cl_sfx_menu_move_sound)
			S_StartLocalSound(cl_sfx_menu_move_sound);
		if (s == cl_sfx_menu_out_sound)
			S_StartLocalSound(cl_sfx_menu_out_sound);
	}
}


/*
===================
Key_Event

Called by the system between frames for both key up and key down events
Should NOT be called during an interrupt!
===================
*/
void Key_Event (int key, bool down, unsigned time)
{
	char	*kb;
	char	cmd[1024];

	// hack for modal presses
	if (key_waiting == -1)
	{
		if (down)
			key_waiting = key;
		return;
	}

	// update auto-repeat status
	if (down)
	{
		key_repeats[key]++;
		if (m_menudepth)
		{
			if (key != K_BACKSPACE 
				&& key != K_PAUSE 
				&& key != K_PGUP 
				&& key != K_KP_PGUP 
				&& key != K_PGDN
				&& key != K_KP_PGDN
				// Berserker: если находимся в меню, добавим также автоповтор курсорных клавишь
			 	&& key != K_LEFTARROW
				&& key != K_RIGHTARROW
				&& key != K_UPARROW
				&& key != K_DOWNARROW
				&& key != K_KP_LEFTARROW
				&& key != K_KP_RIGHTARROW
				&& key != K_KP_UPARROW
				&& key != K_KP_DOWNARROW
				//
				&& key_repeats[key] > 1)
				return;	// ignore most autorepeats
		}
		else
		{
			if (key != K_BACKSPACE 
				&& key != K_PAUSE 
				&& key != K_PGUP 
				&& key != K_KP_PGUP 
				&& key != K_PGDN
				&& key != K_KP_PGDN
				&& key_repeats[key] > 1)
				return;	// ignore most autorepeats
		}

		if (key >= 200 && !keybindings[key])
			Com_Printf ("^3%s is unbound, hit F4 to set.\n", Key_KeynumToString (key) );
	}
	else
		key_repeats[key] = 0;

	if (key == K_SHIFT)
		shift_down = down;

	// console key is hardcoded, so the user can never unbind it
	if (key == '`' || key == '~')
	{
		if (!down)
			return;
		Con_ToggleConsole_f ();
		return;
	}

	// any key during the attract mode will bring up the menu
///	if (cl.attractloop && cls.key_dest != key_menu && !(key >= K_F1 && key <= K_F12))
///		key = K_ESCAPE;

	// menu key is hardcoded, so the user can never unbind it
	if (key == K_ESCAPE)
	{
		if (!down)
			return;

		switch (cls.key_dest)
		{
		case key_message:
			Key_Message (key);
			break;
		case key_menu:
			M_Keydown (key);
			break;
		case key_game:
		case key_console:
			M_Menu_Main_f ();
			break;
		case key_get:
			Key_Get (key);
			break;
		default:
			Com_Error (ERR_FATAL, "Bad cls.key_dest");
		}
		return;
	}

	// track if any key is down for BUTTON_ANY
	keydown[key] = down;
	if (down)
	{
		if (key_repeats[key] == 1)
			anykeydown++;
	}
	else
	{
		anykeydown--;
		if (anykeydown < 0)
			anykeydown = 0;
	}

	if (cls.key_dest != key_game)
		ClearKeys();

//
// key up events only generate commands if the game key binding is
// a button command (leading + sign).  These will occur even in console mode,
// to keep the character from continuing an action started before a console
// switch.  Button commands include the kenum as a parameter, so multiple
// downs can be matched with ups
//
	if (!down)
	{	//// Berserker: добавил такую же проверку, что чуть ниже, чтобы нажатия клавишь не вызывало срабатывания bind при нахождении в консоли
		if ( (cls.key_dest == key_menu && menubound[key])
		|| (cls.key_dest == key_console && !consolekeys[key])
		|| (cls.key_dest == key_game && ( cls.state == ca_active || !consolekeys[key] ) ) )
		////
		{
			kb = keybindings[key];
			if (kb && kb[0] == '+')
			{
				Com_sprintf (cmd, sizeof(cmd), "-%s %i %i\n", kb+1, key, time);
				Cbuf_AddText (cmd);
			}
			if (keyshift[key] != key)
			{
				kb = keybindings[keyshift[key]];
				if (kb && kb[0] == '+')
				{
					Com_sprintf (cmd, sizeof(cmd), "-%s %i %i\n", kb+1, key, time);
					Cbuf_AddText (cmd);
				}
			}
			return;
		}
	}

//
// if not a consolekey, send to the interpreter no matter what mode is
//
	if (!bind_grab)		/// Berserker: Fixed Q2 bug - невозможно было назначить клавишам типа F5 функцию...
		if ( (cls.key_dest == key_menu && menubound[key])
		|| (cls.key_dest == key_console && !consolekeys[key])
		|| (cls.key_dest == key_game && ( cls.state == ca_active || !consolekeys[key] ) ) )
		{
			kb = keybindings[key];
			if (kb)
			{
				if (kb[0] == '+')
				{	// button commands add keynum and time as a parm
					Com_sprintf (cmd, sizeof(cmd), "%s %i %i\n", kb, key, time);
					Cbuf_AddText (cmd);
				}
				else
				{
					Cbuf_AddText (kb);
					Cbuf_AddText ("\n");
				}
			}
			return;
		}

	if (!down)
		return;		// other systems only care about key down events

	if (shift_down)
		key = keyshift[key];

	switch (cls.key_dest)
	{
	case key_message:
		Key_Message (key);
		break;
	case key_menu:
		M_Keydown (key);
		break;
	case key_game:
	case key_console:
		Key_Console (key);
		break;
	case key_get:
		Key_Get (key);
		break;
	default:
		Com_Error (ERR_FATAL, "Bad cls.key_dest");
	}
}


cvar_t *Cvar_FindVar (char *var_name)
{
	unsigned	hash = Com_HashKey(var_name);
	cvar_t		*var;
	
	for (var=cvar_vars ; var ; var=var->next)
		if (hash == var->hash)
			if (!Q_strcasecmp(var->name, var_name))
				return var;

	return NULL;
}


void Cvar_SetHelp(char *cvar, const char *str)
{
	cvar_t *var = Cvar_FindVar(cvar);
	if (var)
		var->help = str;
}

float Cvar_VariableValue (char *var_name)
{
	cvar_t	*var;
	
	var = Cvar_FindVar (var_name);
	if (!var)
		return 0;
	return atof (var->string);
}


void Key_ClearTyping ()
{
	key_lines[edit_line][1] = 0;	// clear any typing
	key_linepos = 1;
}


void SCR_EndLoadingPlaque ()
{
	cls.disable_screen = 0;
	Con_ClearNotify ();
}


void Con_ToggleConsole_f ()
{
	SCR_EndLoadingPlaque ();	// get rid of loading plaque
	mapshot[0] = 0;

	if (cl.attractloop || (cl.cinematictime > 0))
	{
		char	*v;								/// Berserker: при обычном просмотре demo/pic - nextserver пуст, поэтому позволим при вызове консоли убить сервер (demo/pic)
		v = Cvar_VariableString ("nextserver");	/// Но если смотрим demo/pic с nextserver (это проигрывается игровая demo/pic с последующим вызовом карты) - то консоль недоступна! (Иначе можно убить игру вызвав консоль)
		if (!v[0])
		{
			Cbuf_AddText ("killserver\n");
			return;
		}
	}

	if (cls.state == ca_disconnected && !m_menudepth)
	{
		// start the demo loop again
		Cbuf_AddText ("d1\n");
		M_ForceMenuOff ();
		Cvar_Set ("paused", "0");
		return;
	}

	Key_ClearTyping();
	Con_ClearNotify();

	if (cls.key_dest == key_console)
	{
		M_ForceMenuOff ();
		Cvar_Set ("paused", "0");
	}
	else
	{
		M_ForceMenuOff ();
		cls.key_dest = key_console;

		if (Cvar_VariableValue ("maxclients") == 1 && Com_ServerState())
			Cvar_Set ("paused", "1");
	}
}


bool Mods_MenuInit()
{
	char	*path = NULL;
	char	**dirnames;
	char	findname[MAX_OSPATH];
	int		i, j, l, ndirs=0, t=0;

///	if (cls.state > ca_disconnected)
///		return false;

	path = FS_NextPath(NULL);
	if (path)
	{
		char *tmp = findname;
		Com_sprintf( findname, sizeof(findname), "%s/../*.*", path );
		while ( *tmp != 0 )
		{
			if ( *tmp == '\\' ) 
				*tmp = '/';
			tmp++;
		}
		l = strlen(findname) - 3;	// cut "*.*"
		if ( ( dirnames = FS_ListFiles( findname, &ndirs, SFF_SUBDIR, 0 ) ) != 0 )
		{
			ndirs--;
			if (ndirs > MAX_MODS)
				ndirs = MAX_MODS;
			for ( i = 0; i < ndirs; i++ )
			{
				strcpy(m_mod_names[i], &dirnames[i][l]);
				free( dirnames[i] );
			}
			free( dirnames );
		}
	}

	if (!ndirs)
		return false;

	s_mods_menu.x = viddef.width / 2 - 132;
	s_mods_menu.y = viddef.height / 2 - 114;
	s_mods_menu.nitems = 0;

	for (j=0; j<2; j++)
	{
		for ( i = 0; i < MAX_MODS/2; i++ )
		{
			if (t==ndirs)
				break;

			s_mods_actions[t].generic.name			= m_mod_names[t];
			s_mods_actions[t].generic.flags			= QMF_LEFT_JUSTIFY;
			s_mods_actions[t].generic.localdata[0]	= t;
			s_mods_actions[t].generic.callback		= ModCallback;

			s_mods_actions[t].generic.x = j * 154;
			s_mods_actions[t].generic.y = i * 10;

			s_mods_actions[t].generic.type = MTYPE_ACTION;

			Menu_AddItem( &s_mods_menu, &s_mods_actions[t] );

			t++;
		}
	}

	return true;
}

/*
====================
CL_Stop_f

stop recording a demo
====================
*/
void CL_Stop_f ()
{
	int		len;

	if (!cls.demorecording)
	{
		Com_Printf ("^3Not recording a demo.\n");
		return;
	}

// finish up
	len = -1;
	fwrite (&len, 4, 1, cls.demofile);
	fclose (cls.demofile);
	cls.demofile = NULL;
	cls.demorecording = false;
	Com_Printf ("Stopped demo.\n");
}


void NetadrToSockadr (netadr_t *a, struct sockaddr *s)
{
	Memset (s, 0, sizeof(*s));

	if (a->type == NA_BROADCAST)
	{
		((struct sockaddr_in *)s)->sin_family = AF_INET;
		((struct sockaddr_in *)s)->sin_port = a->port;
		((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST;
	}
	else if (a->type == NA_IP)
	{
		((struct sockaddr_in *)s)->sin_family = AF_INET;
		((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip;
		((struct sockaddr_in *)s)->sin_port = a->port;
	}
	else if (a->type == NA_IPX)
	{
		((struct sockaddr_ipx *)s)->sa_family = AF_IPX;
		Memcpy(((struct sockaddr_ipx *)s)->sa_netnum, &a->ipx[0], 4);
		Memcpy(((struct sockaddr_ipx *)s)->sa_nodenum, &a->ipx[4], 6);
		((struct sockaddr_ipx *)s)->sa_socket = a->port;
	}
	else if (a->type == NA_BROADCAST_IPX)
	{
		((struct sockaddr_ipx *)s)->sa_family = AF_IPX;
		Memset(((struct sockaddr_ipx *)s)->sa_netnum, 0, 4);
		Memset(((struct sockaddr_ipx *)s)->sa_nodenum, 0xff, 6);
		((struct sockaddr_ipx *)s)->sa_socket = a->port;
	}
}


void SockadrToNetadr (struct sockaddr *s, netadr_t *a)
{
	if (s->sa_family == AF_INET)
	{
		a->type = NA_IP;
		*(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr;
		a->port = ((struct sockaddr_in *)s)->sin_port;
	}
	else if (s->sa_family == AF_IPX)
	{
		a->type = NA_IPX;
		Memcpy(&a->ipx[0], ((struct sockaddr_ipx *)s)->sa_netnum, 4);
		Memcpy(&a->ipx[4], ((struct sockaddr_ipx *)s)->sa_nodenum, 6);
		a->port = ((struct sockaddr_ipx *)s)->sa_socket;
	}
}


bool	NET_CompareAdr (netadr_t a, netadr_t b)
{
	if (a.type != b.type)
		return false;

	if (a.type == NA_LOOPBACK)
		return TRUE;

	if (a.type == NA_IP)
	{
		if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port)
			return true;
		return false;
	}

	if (a.type == NA_IPX)
	{
		if ((Com_Memcmp(a.ipx, b.ipx, 10) == 0) && a.port == b.port)
			return true;
		return false;
	}
	return false;
}


/*
===================
NET_CompareBaseAdr

Compares without the port
===================
*/
bool	NET_CompareBaseAdr (netadr_t a, netadr_t b)
{
	if (a.type != b.type)
		return false;

	if (a.type == NA_LOOPBACK)
		return TRUE;

	if (a.type == NA_IP)
	{
		if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3])
			return true;
		return false;
	}

	if (a.type == NA_IPX)
	{
		if ((Com_Memcmp(a.ipx, b.ipx, 10) == 0))
			return true;
		return false;
	}
	return false;
}


char	*NET_AdrToString (netadr_t a)
{
	static	char	s[64];

	if (a.type == NA_LOOPBACK)
		Com_sprintf (s, sizeof(s), "loopback");
	else if (a.type == NA_IP)
		Com_sprintf (s, sizeof(s), "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs(a.port));
	else
		Com_sprintf (s, sizeof(s), "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%i", a.ipx[0], a.ipx[1], a.ipx[2], a.ipx[3], a.ipx[4], a.ipx[5], a.ipx[6], a.ipx[7], a.ipx[8], a.ipx[9], ntohs(a.port));

	return s;
}


/*
=============
NET_StringToAdr

localhost
idnewt
idnewt:28000
192.246.40.70
192.246.40.70:28000
=============
*/
#define DO(src,dest)	\
	copy[0] = s[src];	\
	copy[1] = s[src + 1];	\
	sscanf (copy, "%x", &val);	\
	((struct sockaddr_ipx *)sadr)->dest = val

bool	NET_StringToSockaddr (char *s, struct sockaddr *sadr)
{
	struct hostent	*h;
	char	*colon;
	int		val;
	char	copy[128];
	
	Memset (sadr, 0, sizeof(*sadr));

	if ((strlen(s) >= 23) && (s[8] == ':') && (s[21] == ':'))	// check for an IPX address
	{
		((struct sockaddr_ipx *)sadr)->sa_family = AF_IPX;
		copy[2] = 0;
		DO(0, sa_netnum[0]);
		DO(2, sa_netnum[1]);
		DO(4, sa_netnum[2]);
		DO(6, sa_netnum[3]);
		DO(9, sa_nodenum[0]);
		DO(11, sa_nodenum[1]);
		DO(13, sa_nodenum[2]);
		DO(15, sa_nodenum[3]);
		DO(17, sa_nodenum[4]);
		DO(19, sa_nodenum[5]);
		sscanf (&s[22], "%u", &val);
		((struct sockaddr_ipx *)sadr)->sa_socket = htons((WORD)val);
	}
	else
	{
		((struct sockaddr_in *)sadr)->sin_family = AF_INET;
		
		((struct sockaddr_in *)sadr)->sin_port = 0;

		strcpy (copy, s);
		// strip off a trailing :port if present
		for (colon = copy ; *colon ; colon++)
			if (*colon == ':')
			{
				*colon = 0;
				((struct sockaddr_in *)sadr)->sin_port = htons((short)atoi(colon+1));	
			}
		
		if (copy[0] >= '0' && copy[0] <= '9')
		{
			*(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(copy);
		}
		else
		{
			if (! (h = gethostbyname(copy)) )
				return 0;
			*(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0];
		}
	}
	
	return true;
}
#undef DO


/*
=============
NET_StringToAdr

localhost
idnewt
idnewt:28000
192.246.40.70
192.246.40.70:28000
=============
*/
bool	NET_StringToAdr (char *s, netadr_t *a)
{
	struct sockaddr sadr;
	
	if (!strcmp (s, "localhost"))
	{
		Memset (a, 0, sizeof(*a));
		a->type = NA_LOOPBACK;
		return true;
	}

	if (!NET_StringToSockaddr (s, &sadr))
		return false;

	SockadrToNetadr (&sadr, a);

	return true;
}


bool	NET_IsLocalAddress (netadr_t adr)
{
	return adr.type == NA_LOOPBACK;
}


bool Netchan_NeedReliable (netchan_t *chan)
{
	bool	send_reliable;

// if the remote side dropped the last reliable message, resend it
	send_reliable = false;

	if (chan->incoming_acknowledged > chan->last_reliable_sequence
	&& chan->incoming_reliable_acknowledged != chan->reliable_sequence)
		send_reliable = true;

// if the reliable transmit buffer is empty, copy the current message out
	if (!chan->reliable_length && chan->message.cursize)
	{
		send_reliable = true;
	}

	return send_reliable;
}


void MSG_WriteShort (sizebuf_t *sb, int c)
{
	char	*buf;
	buf = SZ_GetSpace (sb, 2);
	buf[0] = c&0xff;
	buf[1] = c>>8;
}

void MSG_WriteLong (sizebuf_t *sb, int c)
{
	char	*buf;
	buf = SZ_GetSpace (sb, 4);
	buf[0] = c&0xff;
	buf[1] = (c>>8)&0xff;
	buf[2] = (c>>16)&0xff;
	buf[3] = c>>24;
}

void MSG_WriteFloat (sizebuf_t *sb, float f)
{
	union
	{
		float	f;
		int	l;
	} dat;
	
	
	dat.f = f;
	dat.l = LittleLong (dat.l);
	
	SZ_Write (sb, &dat.l, 4);
}

void MSG_WriteString (sizebuf_t *sb, char *s)
{
	if (!s)
		SZ_Write (sb, "", 1);
	else
		SZ_Write (sb, s, strlen(s)+1);
}

// LARGE_MAP_SIZE support
void MSG_WriteCoordNew (sizebuf_t *sb, float f)
{
	int tmp;
	byte trans1;
	unsigned short trans2;

	tmp = f*8;			// 1/8 granulation, leaves bounds of +/-1M in signed 24-bit form
	trans1 = tmp >>16;	// bits 16-23
	trans2 = tmp;		// bits 0-15

	// Don't mess with sign bits on this end to allow overflow (map wrap-around).
	MSG_WriteByte (sb, trans1);
	MSG_WriteShort (sb, trans2);
}

// LARGE_MAP_SIZE support
void MSG_WriteCoord (sizebuf_t *sb, float f, bool large_map)
{
	if (large_map)
		MSG_WriteCoordNew (sb, f);
	else
		MSG_WriteShort (sb, (int)(f*8));
}

// LARGE_MAP_SIZE support
void MSG_WritePos (sizebuf_t *sb, vec3_t pos, bool large_map)
{
	if (large_map)
	{
		MSG_WriteCoordNew (sb, pos[0]);
		MSG_WriteCoordNew (sb, pos[1]);
		MSG_WriteCoordNew (sb, pos[2]);
	}
	else
	{
		MSG_WriteShort (sb, (int)(pos[0]*8));
		MSG_WriteShort (sb, (int)(pos[1]*8));
		MSG_WriteShort (sb, (int)(pos[2]*8));
	}
}

void MSG_WriteChar (sizebuf_t *sb, int c)
{
	char	*buf;
	buf = SZ_GetSpace (sb, 1);
	buf[0] = c;
}

void MSG_WriteByte (sizebuf_t *sb, unsigned c)
{
	byte	*buf;
	buf = (byte *)SZ_GetSpace (sb, 1);
	buf[0] = c;
}

void MSG_WriteAngle (sizebuf_t *sb, float f)
{
	MSG_WriteByte (sb, (int)(f*256/360) & 255);
}


void MSG_WriteAngle16 (sizebuf_t *sb, float f)
{
	MSG_WriteShort (sb, ANGLE2SHORT(f));
}


void MSG_WriteDeltaUsercmd (sizebuf_t *buf, usercmd_t *from, usercmd_t *cmd)
{
	int		bits;
//
// send the movement message
//
	bits = 0;
	if (cmd->angles[0] != from->angles[0])
		bits |= CM_ANGLE1;
	if (cmd->angles[1] != from->angles[1])
		bits |= CM_ANGLE2;
	if (cmd->angles[2] != from->angles[2])
		bits |= CM_ANGLE3;
	if (cmd->forwardmove != from->forwardmove)
		bits |= CM_FORWARD;
	if (cmd->sidemove != from->sidemove)
		bits |= CM_SIDE;
	if (cmd->upmove != from->upmove)
		bits |= CM_UP;
	if (cmd->buttons != from->buttons)
		bits |= CM_BUTTONS;
	if (cmd->impulse != from->impulse)
		bits |= CM_IMPULSE;

    MSG_WriteByte (buf, bits);

	if (bits & CM_ANGLE1)
		MSG_WriteShort (buf, cmd->angles[0]);
	if (bits & CM_ANGLE2)
		MSG_WriteShort (buf, cmd->angles[1]);
	if (bits & CM_ANGLE3)
		MSG_WriteShort (buf, cmd->angles[2]);
	
	if (bits & CM_FORWARD)
		MSG_WriteShort (buf, cmd->forwardmove);
	if (bits & CM_SIDE)
	  	MSG_WriteShort (buf, cmd->sidemove);
	if (bits & CM_UP)
		MSG_WriteShort (buf, cmd->upmove);

 	if (bits & CM_BUTTONS)
	  	MSG_WriteByte (buf, cmd->buttons);
 	if (bits & CM_IMPULSE)
	    MSG_WriteByte (buf, cmd->impulse);

	MSG_WriteByte (buf, cmd->msec);
	MSG_WriteByte (buf, cmd->lightlevel);
}


/*
==================
MSG_WriteDeltaEntity

Writes part of a packetentities message.
Can delta from either a baseline or a previous packet_entity
==================
	NetOptimized by Berserker
*/
void MSG_WriteDeltaEntity (entity_state_t *from, entity_state_t *to, sizebuf_t *msg, bool force, bool newentity)
{
	int		bits;

	if (!to->number)
		Com_Error (ERR_FATAL, "Unset entity number");
	if (to->number >= MAX_EDICTS)
		Com_Error (ERR_FATAL, "Entity number >= MAX_EDICTS");

// send an update
	bits = 0;

	if (to->number >= 256)
		bits |= U_NUMBER16;		// number8 is implicit otherwise

	if (((int)(to->origin[0]*8)&0xffff) != ((int)(from->origin[0]*8)&0xffff))
		bits |= U_ORIGIN1;
	if (((int)(to->origin[1]*8)&0xffff) != ((int)(from->origin[1]*8)&0xffff))
		bits |= U_ORIGIN2;
	if (((int)(to->origin[2]*8)&0xffff) != ((int)(from->origin[2]*8)&0xffff))
		bits |= U_ORIGIN3;

	if (((int)(to->angles[0]*256/360) & 255) != ((int)(from->angles[0]*256/360) & 255))
		bits |= U_ANGLE1;		
	if (((int)(to->angles[1]*256/360) & 255) != ((int)(from->angles[1]*256/360) & 255))
		bits |= U_ANGLE2;
	if (((int)(to->angles[2]*256/360) & 255) != ((int)(from->angles[2]*256/360) & 255))
		bits |= U_ANGLE3;

	if ( to->skinnum != from->skinnum )
	{
		if ((unsigned)to->skinnum < 256)
			bits |= U_SKIN8;
		else if ((unsigned)to->skinnum < 0x10000)
			bits |= U_SKIN16;
		else
			bits |= (U_SKIN8|U_SKIN16);
	}

	if ( to->frame != from->frame )
	{
		if (to->frame < 256)
			bits |= U_FRAME8;
		else
			bits |= U_FRAME16;
	}

	if ( to->effects != from->effects )
	{
		if (to->effects < 256)
			bits |= U_EFFECTS8;
		else if (to->effects < 0x8000)
			bits |= U_EFFECTS16;
		else
			bits |= U_EFFECTS8|U_EFFECTS16;
	}

	if ( to->renderfx != from->renderfx )
	{
		if (to->renderfx < 256)
			bits |= U_RENDERFX8;
		else if (to->renderfx < 0x8000)
			bits |= U_RENDERFX16;
		else
			bits |= U_RENDERFX8|U_RENDERFX16;
	}

	if ( to->solid != from->solid )
		bits |= U_SOLID;

	// event is not delta compressed, just 0 compressed
	if ( to->event  )
		bits |= U_EVENT;

	if ( to->modelindex != from->modelindex )
		bits |= U_MODEL;
	if ( to->modelindex2 != from->modelindex2 )
		bits |= U_MODEL2;
	if ( to->modelindex3 != from->modelindex3 )
		bits |= U_MODEL3;
	if ( to->modelindex4 != from->modelindex4 )
		bits |= U_MODEL4;

	if ( to->sound != from->sound )
		bits |= U_SOUND;

	if (!net_compatibility->value)
	{	// расширенные индексы (by Knightmare, optimized by Berserker)
		if (to->modelindex > 255 || to->modelindex2 > 255 || to->modelindex3 > 255 || to->modelindex4 > 255)
			bits |= U_MODEL16;
		if (to->sound > 255)
			bits |= U_SOUND16;
	}

	if (newentity || (to->renderfx & RF_BEAM))
		bits |= U_OLDORIGIN;

	// LARGE_MAP_SIZE support
	bool large_map1 = false;
	if (!net_compatibility->value)
	{
		if (bits & U_ORIGIN1)
			if (to->origin[0] > 4095 || to->origin[0] < -4096)
				large_map1 = true;
		if (bits & U_ORIGIN2)
			if (to->origin[1] > 4095 || to->origin[1] < -4096)
				large_map1 = true;
		if (bits & U_ORIGIN3)
			if (to->origin[2] > 4095 || to->origin[2] < -4096)
				large_map1 = true;
		if (large_map1)
			bits |= U_ORIGIN_LARGE;
	}

	// LARGE_MAP_SIZE support
	bool large_map2 = false;
	if (!net_compatibility->value)
	{
		if (bits & U_OLDORIGIN)
		{
			if (	to->old_origin[0] > 4095 || to->old_origin[0] < -4096
				||	to->old_origin[1] > 4095 || to->old_origin[1] < -4096
				||	to->old_origin[2] > 4095 || to->old_origin[2] < -4096 )
			{
				large_map2 = true;
				bits |= U_OLDORIGIN_LARGE;
			}
		}
	}

	//
	// write the message
	//
	if (!bits && !force)
		return;		// nothing to send!

	//----------

	if (bits & 0xff000000)
		bits |= U_MOREBITS3 | U_MOREBITS2 | U_MOREBITS1;
	else if (bits & 0x00ff0000)
		bits |= U_MOREBITS2 | U_MOREBITS1;
	else if (bits & 0x0000ff00)
		bits |= U_MOREBITS1;

	MSG_WriteByte (msg,	bits&255 );

	if (bits & 0xff000000)
	{
		MSG_WriteByte (msg,	(bits>>8)&255 );
		MSG_WriteByte (msg,	(bits>>16)&255 );
		MSG_WriteByte (msg,	(bits>>24)&255 );
	}
	else if (bits & 0x00ff0000)
	{
		MSG_WriteByte (msg,	(bits>>8)&255 );
		MSG_WriteByte (msg,	(bits>>16)&255 );
	}
	else if (bits & 0x0000ff00)
	{
		MSG_WriteByte (msg,	(bits>>8)&255 );
	}

	//----------

	if (bits & U_NUMBER16)
		MSG_WriteShort (msg, to->number);
	else
		MSG_WriteByte (msg,	to->number);

	if (net_compatibility->value || !(bits & U_MODEL16))
	{
		if (bits & U_MODEL)
			MSG_WriteByte (msg, to->modelindex);
		if (bits & U_MODEL2)
			MSG_WriteByte (msg, to->modelindex2);
		if (bits & U_MODEL3)
			MSG_WriteByte (msg, to->modelindex3);
		if (bits & U_MODEL4)
			MSG_WriteByte (msg, to->modelindex4);
	}
	else
	{	// расширенные индексы (by Knightmare, optimized by Berserker)
		if (bits & U_MODEL)
			MSG_WriteShort (msg, to->modelindex);
		if (bits & U_MODEL2)
			MSG_WriteShort (msg, to->modelindex2);
		if (bits & U_MODEL3)
			MSG_WriteShort (msg, to->modelindex3);
		if (bits & U_MODEL4)
			MSG_WriteShort (msg, to->modelindex4);
	}

	if (bits & U_FRAME8)
		MSG_WriteByte (msg, to->frame);
	if (bits & U_FRAME16)
		MSG_WriteShort (msg, to->frame);

	if ((bits & U_SKIN8) && (bits & U_SKIN16))		//used for laser colors
		MSG_WriteLong (msg, to->skinnum);
	else if (bits & U_SKIN8)
		MSG_WriteByte (msg, to->skinnum);
	else if (bits & U_SKIN16)
		MSG_WriteShort (msg, to->skinnum);

	if ( (bits & (U_EFFECTS8|U_EFFECTS16)) == (U_EFFECTS8|U_EFFECTS16) )
		MSG_WriteLong (msg, to->effects);
	else if (bits & U_EFFECTS8)
		MSG_WriteByte (msg, to->effects);
	else if (bits & U_EFFECTS16)
		MSG_WriteShort (msg, to->effects);

	if ( (bits & (U_RENDERFX8|U_RENDERFX16)) == (U_RENDERFX8|U_RENDERFX16) )
		MSG_WriteLong (msg, to->renderfx);
	else if (bits & U_RENDERFX8)
		MSG_WriteByte (msg, to->renderfx);
	else if (bits & U_RENDERFX16)
		MSG_WriteShort (msg, to->renderfx);

	if (bits & U_ORIGIN1)
		MSG_WriteCoord (msg, to->origin[0], large_map1);
	if (bits & U_ORIGIN2)
		MSG_WriteCoord (msg, to->origin[1], large_map1);
	if (bits & U_ORIGIN3)
		MSG_WriteCoord (msg, to->origin[2], large_map1);

	if (bits & U_ANGLE1)
		MSG_WriteAngle(msg, to->angles[0]);
	if (bits & U_ANGLE2)
		MSG_WriteAngle(msg, to->angles[1]);
	if (bits & U_ANGLE3)
		MSG_WriteAngle(msg, to->angles[2]);

	if (bits & U_OLDORIGIN)
	{
		MSG_WriteCoord (msg, to->old_origin[0], large_map2);
		MSG_WriteCoord (msg, to->old_origin[1], large_map2);
		MSG_WriteCoord (msg, to->old_origin[2], large_map2);
	}

	if (bits & U_SOUND)
	{
		if (net_compatibility->value)
sound8:		MSG_WriteByte (msg, to->sound);
		else
		{	// расширенные индексы (by Knightmare, optimized by Berserker)
			if (bits & U_SOUND16)
				MSG_WriteShort (msg, to->sound);
			else
				goto sound8;
		}
	}

	if (bits & U_EVENT)
		MSG_WriteByte (msg, to->event);
	if (bits & U_SOLID)
		MSG_WriteShort (msg, to->solid);
}


void MSG_BeginReading (sizebuf_t *msg)
{
	msg->readcount = 0;
}


byte Normal2Index(const vec3_t vec)
{
	int i, best;
	float d, bestd;

	bestd = best = 0;
	for (i=0 ; i<NUMVERTEXNORMALS ; i++)
	{
		d = DotProduct (vec, r_avertexnormals[i]);
		if (d > bestd)
		{
			bestd = d;
			best = i;
		}
	}

	return best;
}


void MSG_WriteDir (sizebuf_t *sb, vec3_t dir)
{
	if (dir)
		MSG_WriteByte (sb, Normal2Index(dir));
	else
		MSG_WriteByte (sb, 0);
}


void MSG_ReadDir (sizebuf_t *sb, vec3_t dir)
{
	unsigned	b;

	b = MSG_ReadByte (sb);
	if (b >= NUMVERTEXNORMALS)
		Com_Error (ERR_DROP, "MSF_ReadDir: out of range");
	VectorCopy (r_avertexnormals[b], dir);
}


unsigned MSG_ReadByte (sizebuf_t *msg_read)
{
	unsigned	c;

	if (msg_read->readcount+1 > msg_read->cursize)
		c = -1;
	else
		c = (byte)msg_read->data[msg_read->readcount];
	msg_read->readcount++;

	return c;
}

// returns -1 if no more characters are available
int MSG_ReadChar (sizebuf_t *msg_read)
{
	int	c;

	if (msg_read->readcount+1 > msg_read->cursize)
		c = -1;
	else
		c = (char)msg_read->data[msg_read->readcount];
	msg_read->readcount++;

	return c;
}

int MSG_ReadShort (sizebuf_t *msg_read)
{
	int	c;

	if (msg_read->readcount+2 > msg_read->cursize)
		c = -1;
	else		
		c = (short)(msg_read->data[msg_read->readcount]
		+ (msg_read->data[msg_read->readcount+1]<<8));

	msg_read->readcount += 2;

	return c;
}

int MSG_ReadLong (sizebuf_t *msg_read)
{
	int	c;

	if (msg_read->readcount+4 > msg_read->cursize)
		c = -1;
	else
		c = msg_read->data[msg_read->readcount]
		+ (msg_read->data[msg_read->readcount+1]<<8)
		+ (msg_read->data[msg_read->readcount+2]<<16)
		+ (msg_read->data[msg_read->readcount+3]<<24);
	
	msg_read->readcount += 4;

	return c;
}

float MSG_ReadFloat (sizebuf_t *msg_read)
{
	union
	{
		byte	b[4];
		float	f;
		int	l;
	} dat;

	if (msg_read->readcount+4 > msg_read->cursize)
		dat.f = -1;
	else
	{
		dat.b[0] =	msg_read->data[msg_read->readcount];
		dat.b[1] =	msg_read->data[msg_read->readcount+1];
		dat.b[2] =	msg_read->data[msg_read->readcount+2];
		dat.b[3] =	msg_read->data[msg_read->readcount+3];
	}
	msg_read->readcount += 4;

	dat.l = LittleLong(dat.l);

	return dat.f;	
}

char *MSG_ReadString (sizebuf_t *msg_read)
{
	static char	string[2048];
	int		l,c;

	l = 0;
	do
	{
		c = MSG_ReadChar (msg_read);
		if (c == -1 || c == 0)
			break;
		string[l] = c;
		l++;
	} while (l < sizeof(string)-1);

	string[l] = 0;

	return string;
}

char *MSG_ReadStringLine (sizebuf_t *msg_read)
{
	static char	string[2048];
	int		l,c;

	l = 0;
	do
	{
		c = MSG_ReadChar (msg_read);
		if (c == -1 || c == 0 || c == '\n')
			break;
		string[l] = c;
		l++;
	} while (l < sizeof(string)-1);

	string[l] = 0;

	return string;
}


// LARGE_MAP_SIZE support
float MSG_ReadCoordNew (sizebuf_t *msg_read)
{
	int tmp;
	byte trans1;
	unsigned short trans2;

	trans1 = MSG_ReadByte(msg_read);
	trans2 = MSG_ReadShort(msg_read);

	tmp = trans1 <<16;	// bits 16-23
	tmp += trans2;		// bits 0-15

	// Sign bit 23 means it's negative, so fill upper
	// 8 bits with 1s for 2's complement negative.
	if (tmp & BIT_23)	
		tmp |= UPRBITS;

	return tmp * (1.0/8);	// restore 1/8 granulation
}

// LARGE_MAP_SIZE support
// Player movement coords are already in 1/8 precision integer form
void MSG_WritePMCoordNew (sizebuf_t *sb, int in)
{
	byte trans1;
	unsigned short trans2;

	trans1 = in >>16;	// bits 16-23
	trans2 = in;		// bits 0-15

	MSG_WriteByte (sb, trans1);
	MSG_WriteShort (sb, trans2);
}

// LARGE_MAP_SIZE support
int MSG_ReadPMCoordNew (sizebuf_t *msg_read)
{
	int tmp;
	byte trans1;
	unsigned short trans2;

	trans1 = MSG_ReadByte(msg_read);
	trans2 = MSG_ReadShort(msg_read);

	tmp = trans1 <<16;	// bits 16-23
	tmp += trans2;		// bits 0-15

	// Sign bit 23 means it's negative, so fill upper
	// 8 bits with 1s for 2's complement negative.
	if (tmp & BIT_23)	
		tmp |= UPRBITS;

	return tmp;
}

// LARGE_MAP_SIZE support
float MSG_ReadCoord (sizebuf_t *msg_read, bool large_map)
{
	if (large_map)
		return MSG_ReadCoordNew(msg_read);
	else
		return MSG_ReadShort(msg_read) * (1.0/8);
}

void MSG_ReadPos (sizebuf_t *msg_read, vec3_t pos, bool large_map)
{
	if (large_map)
	{
		pos[0] = MSG_ReadCoordNew(msg_read);
		pos[1] = MSG_ReadCoordNew(msg_read);
		pos[2] = MSG_ReadCoordNew(msg_read);
	}
	else
	{
		pos[0] = MSG_ReadShort(msg_read) * (1.0/8);
		pos[1] = MSG_ReadShort(msg_read) * (1.0/8);
		pos[2] = MSG_ReadShort(msg_read) * (1.0/8);
	}
}


float MSG_ReadAngle (sizebuf_t *msg_read)
{
	return MSG_ReadChar(msg_read) * (360.0/256);
}


float MSG_ReadAngle16 (sizebuf_t *msg_read)
{
	return SHORT2ANGLE(MSG_ReadShort(msg_read));
}


void MSG_ReadData (sizebuf_t *msg_read, void *data, int len)
{
	int		i;

	for (i=0 ; i<len ; i++)
		((byte *)data)[i] = MSG_ReadByte (msg_read);
}


void MSG_ReadDeltaUsercmd (sizebuf_t *msg_read, usercmd_t *from, usercmd_t *move)
{
	int bits;

	Memcpy (move, from, sizeof(*move));

	bits = MSG_ReadByte (msg_read);

// read current angles
	if (bits & CM_ANGLE1)
		move->angles[0] = MSG_ReadShort (msg_read);
	if (bits & CM_ANGLE2)
		move->angles[1] = MSG_ReadShort (msg_read);
	if (bits & CM_ANGLE3)
		move->angles[2] = MSG_ReadShort (msg_read);

// read movement
	if (bits & CM_FORWARD)
		move->forwardmove = MSG_ReadShort (msg_read);
	if (bits & CM_SIDE)
		move->sidemove = MSG_ReadShort (msg_read);
	if (bits & CM_UP)
		move->upmove = MSG_ReadShort (msg_read);

// read buttons
	if (bits & CM_BUTTONS)
		move->buttons = MSG_ReadByte (msg_read);

	if (bits & CM_IMPULSE)
		move->impulse = MSG_ReadByte (msg_read);

// read time to run command
	move->msec = MSG_ReadByte (msg_read);

// read the light level
	move->lightlevel = MSG_ReadByte (msg_read);
}


void NET_SendLoopPacket (netsrc_t sock, int length, void *data, netadr_t to)
{
	int		i;
	loopback_t	*loop;

	loop = &loopbacks[sock^1];

	i = loop->send & (MAX_LOOPBACK-1);
	loop->send++;

	Memcpy (loop->msgs[i].data, data, length);
	loop->msgs[i].datalen = length;
}


bool	NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, sizebuf_t *net_message)
{
	int		i;
	loopback_t	*loop;

	loop = &loopbacks[sock];

	if (loop->send - loop->get > MAX_LOOPBACK)
		loop->get = loop->send - MAX_LOOPBACK;

	if (loop->get >= loop->send)
		return false;

	i = loop->get & (MAX_LOOPBACK-1);
	loop->get++;

	Memcpy (net_message->data, loop->msgs[i].data, loop->msgs[i].datalen);
	net_message->cursize = loop->msgs[i].datalen;
	Memset (net_from, 0, sizeof(*net_from));
	net_from->type = NA_LOOPBACK;
	return true;

}


char *NET_ErrorString ()
{
	int		code;

	code = WSAGetLastError ();
	switch (code)
	{
	case WSAEINTR: return "WSAEINTR";
	case WSAEBADF: return "WSAEBADF";
	case WSAEACCES: return "WSAEACCES";
	case WSAEDISCON: return "WSAEDISCON";
	case WSAEFAULT: return "WSAEFAULT";
	case WSAEINVAL: return "WSAEINVAL";
	case WSAEMFILE: return "WSAEMFILE";
	case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK";
	case WSAEINPROGRESS: return "WSAEINPROGRESS";
	case WSAEALREADY: return "WSAEALREADY";
	case WSAENOTSOCK: return "WSAENOTSOCK";
	case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ";
	case WSAEMSGSIZE: return "WSAEMSGSIZE";
	case WSAEPROTOTYPE: return "WSAEPROTOTYPE";
	case WSAENOPROTOOPT: return "WSAENOPROTOOPT";
	case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT";
	case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT";
	case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP";
	case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT";
	case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT";
	case WSAEADDRINUSE: return "WSAEADDRINUSE";
	case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL";
	case WSAENETDOWN: return "WSAENETDOWN";
	case WSAENETUNREACH: return "WSAENETUNREACH";
	case WSAEHOSTUNREACH: return "WSAEHOSTUNREACH";
	case WSAENETRESET: return "WSAENETRESET";
	case WSAECONNABORTED: return "WSWSAECONNABORTEDAEINTR";
	case WSAECONNRESET: return "WSAECONNRESET";
	case WSAENOBUFS: return "WSAENOBUFS";
	case WSAEISCONN: return "WSAEISCONN";
	case WSAENOTCONN: return "WSAENOTCONN";
	case WSAESHUTDOWN: return "WSAESHUTDOWN";
	case WSAETOOMANYREFS: return "WSAETOOMANYREFS";
	case WSAETIMEDOUT: return "WSAETIMEDOUT";
	case WSAECONNREFUSED: return "WSAECONNREFUSED";
	case WSAELOOP: return "WSAELOOP";
	case WSAENAMETOOLONG: return "WSAENAMETOOLONG";
	case WSAEHOSTDOWN: return "WSAEHOSTDOWN";
	case WSASYSNOTREADY: return "WSASYSNOTREADY";
	case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED";
	case WSANOTINITIALISED: return "WSANOTINITIALISED";
	case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND";
	case WSATRY_AGAIN: return "WSATRY_AGAIN";
	case WSANO_RECOVERY: return "WSANO_RECOVERY";
	case WSANO_DATA: return "WSANO_DATA";
	default: return "NO ERROR";
	}
}


bool	NET_GetPacket (netsrc_t sock, netadr_t *net_from, sizebuf_t *net_message)
{
	int 	ret;
	struct sockaddr from;
	int		fromlen;
	int		net_socket;
	int		protocol;
	int		err;

	if (NET_GetLoopPacket (sock, net_from, net_message))
		return true;

	for (protocol = 0 ; protocol < 2 ; protocol++)
	{
		if (protocol == 0)
			net_socket = ip_sockets[sock];
		else
			net_socket = ipx_sockets[sock];

		if (!net_socket)
			continue;

		fromlen = sizeof(from);

		if (net_compatibility->value)
		{
			ret = recvfrom (net_socket, (char*)net_message->data, net_message->maxsize, 0, (struct sockaddr *)&from, &fromlen);
			if (ret!=-1)
			{
				net_incoming += ret;
				znet_incoming += ret;
				if (net_traffic->value)
					Com_Printf("RECEIVED: %i\n", ret);
			}
		}
		else
		{
			ret = recvfrom (net_socket, (char*)compressed_frame, sizeof(compressed_frame), 0, (struct sockaddr *)&from, &fromlen);
			if (ret != -1)
			{
				int		saved___;
				byte	b0, b1;

				znet_incoming += ret;
				saved___ = ret;

				b0 = compressed_frame[0];
				b1 = compressed_frame[1];

				Com_XOR(&b0, 'P');
				Com_XOR(&b1, 'K');

				if (b0==(ret & 0xff) && b1==((ret>>8) & 0xff))
				{
					int unp = ZLibDecompress (&compressed_frame[ZHEADERLEN], ret - ZHEADERLEN, buff_out, ZBUFFLEN, -15);
					Memcpy(net_message->data, buff_out, unp);
					saved___ -= unp;
					if (net_traffic->value)
						Com_Printf("RECEIVED: %i <- %i (saved %i bytes %i%%)\n", unp, ret, -saved___, -100*saved___/unp);
					ret = unp;
				}
				else
				{
					Memcpy(net_message->data, compressed_frame, ret);
					if (net_traffic->value)
						Com_Printf("RECEIVED: %i\n", ret);
				}

				net_incoming += ret;
				net_saved_in = net_incoming - znet_incoming;
			}
		}

		SockadrToNetadr (&from, net_from);

		if (ret == -1)
		{
			err = WSAGetLastError();

			if (err == WSAEWOULDBLOCK || err == WSAECONNRESET)
				continue;
			if (err == WSAEMSGSIZE)
			{
				Com_Printf ("^3Warning:^7 Oversize packet from %s\n", NET_AdrToString(*net_from));
				continue;
			}

			if (dedicated->value)	// let dedicated servers continue after errors
				Com_Printf ("^1NET_GetPacket: %s from %s\n", NET_ErrorString(), NET_AdrToString(*net_from));
			else
				Com_Error (ERR_DROP, "NET_GetPacket ERROR: %s from %s", NET_ErrorString(), NET_AdrToString(*net_from));
			continue;
		}

		if (ret == net_message->maxsize)
		{
			Com_Printf ("^3Oversize packet from %s\n", NET_AdrToString (*net_from));
			continue;
		}

		net_message->cursize = ret;
		return true;
	}

	return false;
}


void NET_SendPacket (netsrc_t sock, int length, void *data, netadr_t to)
{
	int		ret;
	struct sockaddr	addr;
	int		net_socket;

	if ( to.type == NA_LOOPBACK )
	{
		NET_SendLoopPacket (sock, length, data, to);
		return;
	}

	if (to.type == NA_BROADCAST)
	{
		net_socket = ip_sockets[sock];
		if (!net_socket)
			return;
	}
	else if (to.type == NA_IP)
	{
		net_socket = ip_sockets[sock];
		if (!net_socket)
			return;
	}
	else if (to.type == NA_IPX)
	{
		net_socket = ipx_sockets[sock];
		if (!net_socket)
			return;
	}
	else if (to.type == NA_BROADCAST_IPX)
	{
		net_socket = ipx_sockets[sock];
		if (!net_socket)
			return;
	}
	else
		Com_Error (ERR_FATAL, "NET_SendPacket: bad address type");

	NetadrToSockadr (&to, &addr);

	net_outgoing += length;
	if ( net_compatibility->value || (net_zsize->value==0) || (net_zsize->value >= length) )
	{
		ret = sendto (net_socket, (char*) data, length, 0, &addr, sizeof(addr) );
		znet_outgoing += length;
		if (net_traffic->value)
			Com_Printf("SENDED: %i\n", length);
	}
	else
	{
		int compressed_frame_len = ZLibCompressChunk ((byte*)data, length, &compressed_frame[ZHEADERLEN], ZBUFFLEN, Z_DEFAULT_COMPRESSION, -15);
		int compressed_frame_len_ = compressed_frame_len + ZHEADERLEN;

		if (compressed_frame_len != -1 && compressed_frame_len_ < length)
		{
			compressed_frame[0] = compressed_frame_len_ & 0xff;
			compressed_frame[1] = (compressed_frame_len_>>8) & 0xff;
			Com_XOR(&compressed_frame[0], 'P');
			Com_XOR(&compressed_frame[1], 'K');
			ret = sendto (net_socket, (char*)compressed_frame, compressed_frame_len_, 0, &addr, sizeof(addr) );
			net_saved_out += length - compressed_frame_len_;
			znet_outgoing += compressed_frame_len_;
			if (net_traffic->value)
				Com_Printf("SENDED: %i -> %i (saved %i bytes %i%%)\n", length, compressed_frame_len_, length - compressed_frame_len_, 100*(length - compressed_frame_len_)/length);
		}
		else
		{
			byte	b0, b1;

			b0 = ((char*)data)[0];
			b1 = ((char*)data)[1];

			Com_XOR(&b0, 'P');
			Com_XOR(&b1, 'K');

			if (b0==(length & 0xff) && b1==((length>>8) & 0xff))
			{
/// В случае "фальшивого сжатого" пакета, который на самом деле не сжатый,
/// отошлем все-таки сжатый пакет, хоть это и не выгодно. Вероятность такого почти равна 0.
				compressed_frame[0] = compressed_frame_len_ & 0xff;
				compressed_frame[1] = (compressed_frame_len_>>8) & 0xff;
				Com_XOR(&compressed_frame[0], 'P');
				Com_XOR(&compressed_frame[1], 'K');
				ret = sendto (net_socket, (char*)compressed_frame, compressed_frame_len_, 0, &addr, sizeof(addr) );
				net_saved_out += length - compressed_frame_len_;
				znet_outgoing += compressed_frame_len_;
				if (net_traffic->value)
					Com_Printf("SENDED: %i -> %i (saved %i bytes %i%%) <fake compressed packet>\n", length, compressed_frame_len_, length - compressed_frame_len_, 100*(length - compressed_frame_len_)/length);
			}
			else
			{
				ret = sendto (net_socket, (char*) data, length, 0, &addr, sizeof(addr) );
				if (net_traffic->value)
					Com_Printf("SENDED: %i\n", length);
				znet_outgoing += length;
			}
		}
	}

	if (ret == -1)
	{
		int err = WSAGetLastError();

		// wouldblock is silent
		if (err == WSAEWOULDBLOCK)
			return;

		// some PPP links dont allow broadcasts
		if ((err == WSAEADDRNOTAVAIL || err == WSAEHOSTUNREACH || err == WSAENETUNREACH) && ((to.type == NA_BROADCAST) || (to.type == NA_BROADCAST_IPX)))
			return;

		if (dedicated->value)	// let dedicated servers continue after errors
		{
			Com_Printf ("^1NET_SendPacket ERROR: %s to %s\n", NET_ErrorString(), NET_AdrToString (to));
		}
		else
		{
			if (err == WSAEADDRNOTAVAIL)
			{
				Com_DPrintf ("NET_SendPacket Warning: %s : %s\n", NET_ErrorString(), NET_AdrToString (to));
			}
			else
			{
				Com_Error (ERR_DROP, "NET_SendPacket ERROR: %s to %s", NET_ErrorString(), NET_AdrToString (to));
			}
		}
	}
}


/*
===============
Netchan_Transmit

tries to send an unreliable message to a connection, and handles the
transmition / retransmition of the reliable messages.

A 0 length will still generate a packet and deal with the reliable messages.
================
*/
void Netchan_Transmit (netchan_t *chan, int length, byte *data)
{
	sizebuf_t	send;
	byte		send_buf[MAX_MSGLEN];
	bool		send_reliable;
	unsigned	w1, w2;

// check for message overflow
	if (chan->message.overflowed)
	{
		chan->fatal_error = true;
		Com_Printf ("^3%s: Outgoing message overflow\n", NET_AdrToString (chan->remote_address));
		return;
	}

	send_reliable = Netchan_NeedReliable (chan);

	if (!chan->reliable_length && chan->message.cursize)
	{
		Memcpy (chan->reliable_buf, chan->message_buf, chan->message.cursize);
		chan->reliable_length = chan->message.cursize;
		chan->message.cursize = 0;
		chan->reliable_sequence ^= 1;
	}


// write the packet header
	SZ_Init (&send, send_buf, sizeof(send_buf));

	w1 = ( chan->outgoing_sequence & ~(1<<31) ) | (send_reliable<<31);
	w2 = ( chan->incoming_sequence & ~(1<<31) ) | (chan->incoming_reliable_sequence<<31);

	chan->outgoing_sequence++;
	chan->last_sent = curtime;

	MSG_WriteLong (&send, w1);
	MSG_WriteLong (&send, w2);

	// send the qport if we are a client
	if (chan->sock == NS_CLIENT)
		MSG_WriteShort (&send, qport->value);

// copy the reliable message to the packet first
	if (send_reliable)
	{
		SZ_Write (&send, chan->reliable_buf, chan->reliable_length);
		chan->last_reliable_sequence = chan->outgoing_sequence;
	}
	
// add the unreliable part if space is available
	if (send.maxsize - send.cursize >= length)
		SZ_Write (&send, data, length);
	else
		Com_Printf ("^1Netchan_Transmit: dumped unreliable\n");

// send the datagram
	NET_SendPacket (chan->sock, send.cursize, send.data, chan->remote_address);
}


/// Decals //////////////////////////////////////////////////////////////////////

bool DecalAddPolygon(decal_t *dec, byte vertcount, vec3_t *vertices, image_t *image, msurface_t *surf)
{
///	byte	*triangle;
	byte	count, a, b;

	count = dec->vertexCount;
	if (count + vertcount >= MAX_DECAL_VERTICES)
		return false;

	if (dec->triangleCount + vertcount-2 >= MAX_DECAL_TRIANGLES)
		return false;

	// Add polygon as a triangle fan
///	triangle = &dec->triangleArray[dec->triangleCount][0];
	for (a = 2; a < vertcount; a++)
	{
		dec->triangleArray[dec->triangleCount][0] = count;
		dec->triangleArray[dec->triangleCount][1] = (count + a - 1);
		dec->triangleArray[dec->triangleCount][2] = (count + a );
		dec->images[dec->triangleCount] = image;
		dec->surfs[dec->triangleCount] = surf;
		dec->triangleCount++;
	}

	// Assign vertex colors
	for (b = 0; b < vertcount; b++)
	{
		VectorAdd(surf->polygonOffset, vertices[b], dec->vertexArray[count]);
		count++;
	}

	dec->vertexCount = count;
	return true;
}


byte DecalClipPolygonAgainstPlane(dplane_t *plane, byte vertexCount, vec3_t *vertex, vec3_t *newVertex)
{
	bool	negative[MAX_POLY_VERT + 1];
	byte	a, count, b, c;
	float	t;
	vec3_t	v1, v2;

	// Classify vertices
	int negativeCount = 0;
	for (a = 0; a < vertexCount; a++)
	{
		bool neg = ((DotProduct(plane->normal, vertex[a]) - plane->dist) < 0.0);
		negative[a] = neg;
		negativeCount += neg;
	}

	// Discard this polygon if it's completely culled
	if (negativeCount == vertexCount)
		return 0;

	count = 0;
	for (b = 0; b < vertexCount; b++)
	{
		// c is the index of the previous vertex
		c = (b != 0) ? b - 1 : vertexCount - 1;

		if (negative[b])
		{
			if (!negative[c])
			{
				// Current vertex is on negative side of plane,
				// but previous vertex is on positive side.
				VectorCopy(vertex[c],v1);
				VectorCopy(vertex[b],v2);

				t = (DotProduct(plane->normal, v1) - plane->dist) / (
					  plane->normal[0] * (v1[0] - v2[0])
					+ plane->normal[1] * (v1[1] - v2[1]) 
					+ plane->normal[2] * (v1[2] - v2[2]));

				VectorScale(v1,(1.0 - t),newVertex[count]);
				VectorMA(newVertex[count],t,v2,newVertex[count]);

				count++;
			}
		}
		else
		{
			if (negative[c])
			{
				// Current vertex is on positive side of plane,
				// but previous vertex is on negative side.
				VectorCopy(vertex[b],v1);
				VectorCopy(vertex[c],v2);

				t = (DotProduct(plane->normal, v1) - plane->dist) / (
					  plane->normal[0] * (v1[0] - v2[0])
					+ plane->normal[1] * (v1[1] - v2[1])
					+ plane->normal[2] * (v1[2] - v2[2]));

				VectorScale(v1,(1.0 - t),newVertex[count]);
				VectorMA(newVertex[count],t,v2,newVertex[count]);
				
				count++;
			}

			// Include current vertex
			VectorCopy(vertex[b],newVertex[count]);
			count++;
		}
	}

	// Return number of vertices in clipped polygon
	return count;
}


byte DecalClipPolygon(int vertexCount, vec3_t *vertices, vec3_t *newVertex)
{
	vec3_t		tempVertex[MAX_POLY_VERT];

	// Clip against all six planes
	byte count = DecalClipPolygonAgainstPlane(&leftPlane, vertexCount, vertices, tempVertex);
	if (count)
	{
		count = DecalClipPolygonAgainstPlane(&rightPlane, count, tempVertex, newVertex);
		if (count)
		{
			count = DecalClipPolygonAgainstPlane(&bottomPlane, count, newVertex, tempVertex);
			if (count)
			{
				count = DecalClipPolygonAgainstPlane(&topPlane, count, tempVertex, newVertex);
				if (count)
				{
					count = DecalClipPolygonAgainstPlane(&backPlane, count, newVertex, tempVertex);
					if (count)
						count = DecalClipPolygonAgainstPlane(&frontPlane, count, tempVertex, newVertex);
				}
			}
		}
	}

	return count;
}


#define	decalEpsilon	0.001

void DecalClipLeaf(decal_t *dec, mleaf_t *leaf)
{
	int		c;
	vec3_t	newVertex[MAX_POLY_VERT], t3;
	msurface_t **surf;

	c = leaf->nummarksurfaces;
	surf = leaf->firstmarksurface;

	//for all surfaces in the leaf
	for (c=0; c<leaf->nummarksurfaces; c++, surf++)
	{
		if (r_mirror)
			// Не декалим текущее зеркало
			if	(((*surf)->plane->dist == mirror_plane->plane.dist) &&
				VectorCompare((*surf)->plane->normal, mirror_plane->plane.normal))
				continue;

		if ((*surf)->texinfo->image->mirror && !((*surf)->flags & (SURF_DRAWSKY|SURF_DRAWTURB)))
			goto mirr;	// зеркала могут иметь альфу, поэтому вот такой хак ))

		if(!(((*surf)->texinfo->flags & (/*SURF_TRANS33|SURF_TRANS66|*/SURF_SKY|SURF_WARP|SURF_NODRAW|SURF_FLOWING|SURF_FENCE)) || ((*surf)->flags & (SURF_DRAWSKY|SURF_DRAWTURB))))
		{
mirr:		glpoly_t	*poly;
			int			i;
			byte		count;
			float		*v;

			poly = (*surf)->polys;

			if (poly->numverts > MAX_POLY_VERT)
				Com_Error(ERR_FATAL, "DecalClipLeaf: too many edges %i", poly->numverts);

			v = poly->verts[0];
			for (i=0 ; i<poly->numverts ; i++, v+= VERTEXSIZE)
				VectorCopy(v, newVertex[i]);

			VectorCopy((*surf)->plane->normal,t3);		

			if ((*surf)->flags & SURF_PLANEBACK)
				VectorNegate(t3, t3);

			//avoid backfacing and ortogonal facing faces to recieve decal parts
			if (DotProduct(dec_normal, t3) > decalEpsilon)
			{
				count = DecalClipPolygon(poly->numverts, newVertex, newVertex);
				if (count && (!DecalAddPolygon(dec, count, newVertex, (*surf)->texinfo->image, (*surf))))
					break;
			}
		}
	}
}


void DecalWalkBsp_R(decal_t *dec, mnode_t *node)
{
	cplane_t	*plane;
	float		dist;
	mleaf_t		*leaf;

	if (node->contents != -1)
	{
		//we are in a leaf
		leaf = (mleaf_t *)node;
		DecalClipLeaf(dec, leaf);
		return;
	}

	plane = node->plane;
	dist = DotProduct (dec_origin, plane->normal) - plane->dist;

	if (dist > dec_radius)
	{
		DecalWalkBsp_R (dec, node->children[0]);
		return;
	}

	if (dist < -dec_radius)
	{
		DecalWalkBsp_R (dec, node->children[1]);
		return;
	}

	DecalWalkBsp_R (dec, node->children[0]);
	DecalWalkBsp_R (dec, node->children[1]);
}


bool R_FreeDecal()
{
	decal_t	*p, *dec = NULL;
	float borntime, oldestTime = cl.leveltime + 999999;

	// ищем старейший декаль
	for (p=active_decals ; p ; p=p->next)
	{
		if (p->die - cl.leveltime > 4999999)	// Вечный декаль?
			continue;							// не трогаем его!
		borntime = p->die - p->lifetime;
		if (borntime < oldestTime)
		{
			oldestTime = borntime;
			dec = p;
		}
	}

	//deallocate decal
	if (dec)
	{
		dec->die = cl.leveltime;		// mark for delete

		// kill old decals
		for (p=active_decals ; p ; p=p->next)
		{
			for ( ;; )
			{
				dec = p->next;
				if (dec && (dec->die <= cl.leveltime))
				{
					p->next = dec->next;
					dec->next = free_decals;
					free_decals = dec;
					continue;
				}
				break;
			}
		}
		return true;
	}

	return false;
}

void R_SpawnDecalBSP (decal_t *decal, image_t *texture, dtype_t type, bool shot, byte style, byte cl_style, defDecal_t *defdec)
{
	int		i;
	float	d;
	decal_t	*dec;
	float	one_over_w, one_over_h;
	vec3_t	test = {0.57735, 0.57735, 0.57735};

	//allocate decal
	if (!free_decals)
	{
		if (!R_FreeDecal())
			return;
		if (!free_decals)
			return;
	}

	dec = free_decals;
	free_decals = dec->next;
	Memcpy(dec, decal, sizeof(decal_t));
	dec->next = active_decals;
	active_decals = dec;

	VectorCopy(dec_normal_bak, dec_normal);
	VectorCopy(dec_origin_bak, dec_origin);
	CrossProduct(dec_normal, test, dec_tangent);
	CrossProduct(dec_normal, dec_tangent, dec_binormal);

	// Calculate boundary planes
	d = DotProduct(dec_origin, dec_tangent);
	VectorCopy(dec_tangent, leftPlane.normal);
	leftPlane.dist = d - dec_half_radius;
	VectorNegate(dec_tangent, rightPlane.normal);
	rightPlane.dist = - d - dec_half_radius;

	d = DotProduct(dec_origin, dec_binormal);
	VectorCopy(dec_binormal, bottomPlane.normal);
	bottomPlane.dist = d - dec_half_radius;
	VectorNegate(dec_binormal, topPlane.normal);
	topPlane.dist = - d - dec_half_radius;

	d = DotProduct(dec_origin, dec_normal);
	VectorCopy(dec_normal, backPlane.normal);
	backPlane.dist = d - dec_half_radius;
	VectorNegate(dec_normal, frontPlane.normal);
	frontPlane.dist = - d - dec_half_radius;

	//Clip decal to bsp
	DecalWalkBsp_R(dec, r_worldmodel->nodes);

	//This happens when a decal is too far from any surface or the surface is too steeply sloped
	if (dec->triangleCount == 0)
	{
		//deallocate decal
		active_decals = dec->next;
		dec->next = free_decals;
		free_decals = dec;
		return;
	}

	//Assign texture mapping coordinates
	one_over_w  = 1.0F / dec_radius;
	one_over_h = 1.0F / dec_radius;

	for (i = 0; i < dec->vertexCount; i++)
	{
		vec3_t	v;
		VectorSubtract(dec->vertexArray[i], dec_origin, v);
		dec->texcoordArray[i][0] = -DotProduct(v, dec_tangent) * one_over_w;// + 0.5F;
		dec->texcoordArray[i][1] = DotProduct(v, dec_binormal) * one_over_h;// + 0.5F;
	}

	for (i = 0; i < dec->triangleCount; i++)
	{
		if(shot && dec->images[i]->material && dec->images[i]->material->decal)
		{
			dec->types[i] = dec->images[i]->material->add_blend ? dt_add : dt_mul;
			dec->images[i] = dec->images[i]->material->decal;
		}
		else
		{
			dec->types[i] = type;
			dec->images[i] = texture;
		}
	}

	dec->style = style;
	dec->cl_style = cl_style;
	dec->defDecal = defdec;
}


inline bool BBoxIntersectBBox(float *bbox0, float *bbox1)
{
	if ( bbox0[0] >= bbox1[3] )	return false;
	if ( bbox0[3] <= bbox1[0] )	return false;
	if ( bbox0[1] >= bbox1[4] )	return false;
	if ( bbox0[4] <= bbox1[1] )	return false;
	if ( bbox0[2] >= bbox1[5] )	return false;
	if ( bbox0[5] <= bbox1[2] )	return false;
	return true;
}


/*
===============
R_TextureAnimation

Returns the proper texture for a given time and base texture
===============
*/
mtexinfo_t *R_TextureAnimation (mtexinfo_t *tex)
{
	int		c;

	if (!tex->next)
		return tex;

	c = currententity->frame % tex->numframes;
	while (c)
	{
		tex = tex->next;
		c--;
	}

	return tex;
}


void DecalWalkBModel_R(decal_t *dec)
{
	int			i, j;
	byte		count;
	bool		rotated;
	float		mbbox[6];		// inline bmodel bbox...
	float		dbbox[6];		// decal bbox...
	msurface_t	*psurf;
	float		dot;
	vec3_t		newVertex[MAX_POLY_VERT];
	glpoly_t	*poly;
	float		*v;
	vec3_t		temp;
	vec3_t		forward, right, up;
	mtexinfo_t	*tx;
	image_t		*image;
	vec3_t test = {0.57735, 0.57735, 0.57735};

	if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
	{
		rotated = true;
		for (i=0 ; i<3 ; i++)
		{
			mbbox[i] = -currentmodel->radius;
			mbbox[i+3] = currentmodel->radius;
		}
	}
	else
	{
		rotated = false;
		for (i=0 ; i<3 ; i++)
		{
			mbbox[i] = currentmodel->mins[i];
			mbbox[i+3] = currentmodel->maxs[i];
		}
	}

	VectorSubtract (dec_origin, currententity->origin, dec_origin);
	if (rotated)
	{
		AngleVectors (currententity->angles, forward, right, up);

		VectorCopy (dec_origin, temp);
		dec_origin[0] = DotProduct (temp, forward);
		dec_origin[1] = -DotProduct (temp, right);
		dec_origin[2] = DotProduct (temp, up);
	}

	for(i=0; i<3; i++)
	{
		dbbox[i] = dec_origin[i] - dec_radius;
		dbbox[i+3] = dec_origin[i] + dec_radius;
	}

	if(!BBoxIntersectBBox(mbbox, dbbox))
		return;

	if (rotated)
	{
		VectorCopy(dec_normal, temp);
		dec_normal[0] = DotProduct (temp, forward);
		dec_normal[1] = -DotProduct (temp, right);
		dec_normal[2] = DotProduct (temp, up);

// фиксим вращение "мокрых следов"
		dec->angle -= currententity->angles[1];
	}

	CrossProduct(dec_normal, test, dec_tangent);
	CrossProduct(dec_normal, dec_tangent, dec_binormal);

	// Recalculate boundary planes (dec->origin will be changed)
	dot = DotProduct(dec_origin, dec_tangent);
	VectorCopy(dec_tangent, leftPlane.normal);
	leftPlane.dist = dot - dec_half_radius;
	VectorNegate(dec_tangent, rightPlane.normal);
	rightPlane.dist = - dot - dec_half_radius;

	dot = DotProduct(dec_origin, dec_binormal);
	VectorCopy(dec_binormal, bottomPlane.normal);
	bottomPlane.dist = dot - dec_half_radius;
	VectorNegate(dec_binormal, topPlane.normal);
	topPlane.dist = - dot - dec_half_radius;

	dot = DotProduct(dec_origin, dec_normal);
	VectorCopy(dec_normal, backPlane.normal);
	backPlane.dist = dot - dec_half_radius;
	VectorNegate(dec_normal, frontPlane.normal);
	frontPlane.dist = - dot - dec_half_radius;

	psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];
	//for all surfaces in the brush model
	for (i=0 ; i<currentmodel->nummodelsurfaces ; i++, psurf++)
	{
		if(!((psurf->texinfo->flags & (/*SURF_TRANS33|SURF_TRANS66|*/SURF_SKY|SURF_WARP|SURF_NODRAW|SURF_FLOWING|SURF_FENCE)) || (psurf->flags & (SURF_DRAWSKY|SURF_DRAWTURB))))
		{
			// find which side of the node we are on
			tx = R_TextureAnimation( psurf->texinfo );
			image = tx->image;
///			if(image->has_alpha)
///				continue;			// на "дырявых" стенах не ставим decal	TESTED on SURF_FENCE

			poly = psurf->polys;

			if (poly->numverts > MAX_POLY_VERT)
				Com_Error(ERR_FATAL, "DecalWalkBModel_R: too many edges %i", poly->numverts);

			VectorCopy(psurf->plane->normal, temp);
			if (psurf->flags & SURF_PLANEBACK)
				VectorNegate(temp, temp);

			//avoid backfacing and ortogonal facing faces to recieve decal parts
			if (DotProduct(dec_normal, temp) > decalEpsilon)
			{
				v = poly->verts[0];
				for (j=0 ; j<poly->numverts ; j++, v+= VERTEXSIZE)
					VectorCopy(v, newVertex[j]);

				count = DecalClipPolygon(poly->numverts, newVertex, newVertex);
				if (count && (!DecalAddPolygon(dec, count, newVertex, (*psurf).texinfo->image, psurf)))
					break;
			}
		}
	}
}


void R_SpawnDecalBModel (decal_t *decal, image_t *texture, dtype_t type, bool shot, byte style, byte cl_style, defDecal_t *defdec)
{
	int		i;
	decal_t	*dec;
	float	one_over_w, one_over_h;

	//allocate decal
	if (!free_decals)
	{
		if (!R_FreeDecal())
			return;
		if (!free_decals)
			return;
	}

	dec = free_decals;
	free_decals = dec->next;
	Memcpy(dec, decal, sizeof(decal_t));
	dec->next = active_decals;
	active_decals = dec;

	VectorCopy (dec_normal_bak, dec_normal);
	VectorCopy (dec_origin_bak, dec_origin);
	if (defdec)
		if (!VectorCompare(defdec->offset, vec3_origin))
			VectorSubtract(dec_origin, defdec->offset, dec_origin);

	//Clip decal to bmodel
	DecalWalkBModel_R(dec);

	//This happens when a decal is too far from any surface or the surface is to steeply sloped
	if (dec->triangleCount == 0)
	{
		//deallocate decal
		active_decals = dec->next;
		dec->next = free_decals;
		free_decals = dec;
		return;
	}

	dec->model_id = currentmodel->firstmodelsurface;

	//Assign texture mapping coordinates
	one_over_w  = 1.0F / dec_radius;
	one_over_h = 1.0F / dec_radius;
	for (i = 0; i < dec->vertexCount; i++)
	{
		vec3_t	v;
		VectorSubtract(dec->vertexArray[i], dec_origin, v);
		dec->texcoordArray[i][0] = -DotProduct(v, dec_tangent) * one_over_w;// + 0.5F;
		dec->texcoordArray[i][1] = DotProduct(v, dec_binormal) * one_over_h;// + 0.5F;
	}
	for (i = 0; i < dec->triangleCount; i++)
	{
		if(shot && dec->images[i]->material && dec->images[i]->material->decal)
		{
			dec->types[i] = dec->images[i]->material->add_blend ? dt_add : dt_mul;
			dec->images[i] = dec->images[i]->material->decal;
		}
		else
		{
			dec->types[i] = type;
			dec->images[i] = texture;
		}
	}
	dec->style = style;
	dec->cl_style = cl_style;
	dec->defDecal = defdec;
}


void R_StoreDecal(vec3_t center, vec3_t normal, image_t *texture, float size, float angle, dtype_t dtype, byte style, byte cl_style, vec3_t offset)
{
	if (num_deferred_decals < MAX_DECALS)
	{
		defDecal_t *dec = &deferred_decals[num_deferred_decals++];
		VectorCopy(center, dec->center);
		VectorCopy(normal, dec->normal);
		dec->texture = texture;
		dec->size = size;
		dec->angle = angle;
		dec->dtype = dtype;
		dec->style = style;
		dec->cl_style = cl_style;
		VectorCopy(offset, dec->offset);
		decals_needs_spawn = true;
	}
}


void R_SpawnDecal(vec3_t center, vec3_t normal, image_t *texture, float minsize, float maxsize, float minlifetime, float maxlifetime, float minangle, float maxangle, dtype_t dtype, bool shot, byte style, byte cl_style, defDecal_t *defdec, bool over)
{
	int		i;
	decal_t	dec;

	if(!r_decals->value)
		return;

	if(cls.key_dest == key_menu)		/// Berserker: убогое решение баги - если запустить демо и зайти в player_setup -> вылет в ось!
		return;

	if (!over)
	{	/// Berserker: если декалим стекла, то декалим с обоих сторон!
		if (normal)
		{
			vec3_t overCenter, overNormal;
			float sz = RandomMinMax(minsize, maxsize);
			VectorNegate(normal, overNormal);
			VectorMA(center, 48, overNormal, overCenter);
			trace_t trace = CL_PMTraceWorld(overCenter, vec3_origin, vec3_origin, center, CONTENTS_WINDOW);
			if (trace.fraction > 0 && trace.fraction < 1 && !trace.allsolid && (trace.contents & CONTENTS_WINDOW))
			{
				minsize = maxsize = sz;

				if (minlifetime != 9999999)		// special case: relight decal
				{
					minlifetime = RandomMinMax(minlifetime, maxlifetime);
					if (minlifetime > 99999)
						minlifetime = 99999;
					maxlifetime = minlifetime;
				}

				minangle = maxangle = RandomMinMax(minangle, maxangle);

				R_SpawnDecal(trace.endpos, trace.plane.normal, texture, minsize, maxsize, minlifetime, maxlifetime, minangle, maxangle, dtype, shot, style, cl_style, defdec, true);
			}
		}
		else
		{
			vec3_t	normal2;
			for (i = 0; i < 6; i++)
			{
				switch (i)
				{
				case 0:
					VectorSet(normal2, 0, 0, 1);
					break;
				case 1:
					VectorSet(normal2, 0, 0, -1);
					break;
				case 2:
					VectorSet(normal2, 0, 1, 0);
					break;
				case 3:
					VectorSet(normal2, 0, -1, 0);
					break;
				case 4:
					VectorSet(normal2, 1, 0, 0);
					break;
				default:
					VectorSet(normal2, -1, 0, 0);
					break;
				}

				vec3_t overCenter, overNormal;
				float sz = RandomMinMax(minsize, maxsize);
				VectorNegate(normal2, overNormal);
				VectorMA(center, 48, overNormal, overCenter);
				trace_t trace = CL_PMTraceWorld(overCenter, vec3_origin, vec3_origin, center, CONTENTS_WINDOW);
				if (trace.fraction > 0 && trace.fraction < 1 && !trace.allsolid && (trace.contents & CONTENTS_WINDOW))
				{
					minsize = maxsize = sz;

					if (minlifetime != 9999999)		// special case: relight decal
					{
						minlifetime = RandomMinMax(minlifetime, maxlifetime);
						if (minlifetime > 99999)
							minlifetime = 99999;
						maxlifetime = minlifetime;
					}

					minangle = maxangle = RandomMinMax(minangle, maxangle);

					R_SpawnDecal(trace.endpos, trace.plane.normal, texture, minsize, maxsize, minlifetime, maxlifetime, minangle, maxangle, dtype, shot, style, cl_style, defdec, true);
				}
			}
		}
	}

	//allocate decal
	if (!free_decals)
	{
		if (!R_FreeDecal())
			return;
		if (!free_decals)
			return;
	}

/// Если нормаль декали нулевая, то "декалим" в 6 направлениях от центра декаля
/// (такое случается в случае взрыва BFG)
	if (!normal)
	{
		vec3_t	normal2, end;
		trace_t	trace;
		for (i=0; i<6; i++)
		{
			switch(i)
			{
			case 0:
				VectorSet(normal2, 0,0,1);
				break;
			case 1:
				VectorSet(normal2, 0,0,-1);
				break;
			case 2:
				VectorSet(normal2, 0,1,0);
				break;
			case 3:
				VectorSet(normal2, 0,-1,0);
				break;
			case 4:
				VectorSet(normal2, 1,0,0);
				break;
			default:
				VectorSet(normal2, -1,0,0);
				break;
			}
			VectorMA(center, maxsize, normal2, end);
			trace = CL_PMTraceWorld(center, vec3_origin, vec3_origin, end, MASK_SOLID);
			if (trace.fraction < 1 && !trace.allsolid)
				R_SpawnDecal(center, trace.plane.normal, texture, minsize, maxsize, minlifetime, maxlifetime, minangle, maxangle, dtype, shot, style, cl_style, defdec, over);
		}
		return;
	}

	VectorCopy(center, dec_origin_bak);
	dec_radius = RandomMinMax(minsize, maxsize);
	dec_half_radius = 0.75 * dec_radius;/// was 0.5		чуток расширим границы декалирования
///	dec.texture = texture;
	if (minlifetime == 9999999)		// special case: relight decal
		dec.lifetime = minlifetime;
	else
	{
		dec.lifetime = RandomMinMax(minlifetime, maxlifetime);
		if (dec.lifetime > 99999)
			dec.lifetime = 99999;
	}
	dec.die = cl.leveltime + dec.lifetime;
///	dec.type = dtype;
	dec.angle = RandomMinMax(minangle, maxangle);
	dec.model_id = 0;
	Memcpy(dec.decalTextureName, texture->name, MAX_QPATH);
	dec.decalType = dtype;
	dec.shot = shot;
	dec.size = dec_radius;
	VectorCopy(center, dec.center);
	VectorCopy(normal, dec.normal);
	VectorCopy(normal, dec_normal_bak);
	// Begin with empty mesh
	dec.vertexCount = 0;
	dec.triangleCount = 0;

	R_SpawnDecalBSP (&dec, texture, dtype, shot, style, cl_style, defdec);

	if (!r_drawentities->value)
		return;

	if (minlifetime == 9999999)		// special case: relight decal
	{
		int			max_models;
		entity_t	ent;
		VectorClear(ent.angles);
		VectorClear(ent.origin);
		currententity = &ent;
		if (net_compatibility->value)
			max_models = MAX_MODELS_Q2;
		else
			max_models = MAX_MODELS_BERS;
		for ( i=0 ; i<max_models ; i++ )
		{
			currentmodel = cl.model_draw[i];
			if (currentmodel)
			{
				if (currentmodel->type == mod_brush && currentmodel->firstmodelsurface)
				{
					ent.model = currentmodel;
					R_SpawnDecalBModel(&dec, texture, dtype, shot, style, cl_style, defdec);
				}
			}
		}
	}
	else
	{
		for (i=0 ; i<cl.refdef.num_entities ; i++)
		{
			currententity = &cl.refdef.entities[i];
			currentmodel = currententity->model;

			if (!currentmodel)
				continue;

			if (currentmodel->type != mod_brush)
				continue;

			if (currentmodel->nummodelsurfaces == 0)
				continue;

			if (minangle == maxangle && (currententity->angles[0] || currententity->angles[2]))		/// только у footprint (minangle == maxangle) !!!
			{		// Смажем "мокрые следы" на моделях, вращающихся по осям X или Y
				if(texture == foot_mrk_object[0] || texture == foot_mrk_object[1])
					texture = foot_mrk_object[3];
				else
					texture = foot_mrk_object[4];
			}

			R_SpawnDecalBModel(&dec, texture, dtype, shot, style, cl_style, defdec);
		}
	}
}


void R_RotateForEntity (entity_t *e)
{
	glTranslatef (e->origin[0],  e->origin[1],  e->origin[2]);

	glRotatef (e->angles[1],  0, 0, 1);
	glRotatef (e->angles[0],  0, 1, 0);		/// fixed "stupid quake bug" ;)
	glRotatef (e->angles[2],  1, 0, 0);
}


void R_DrawDecals ()
{
	decal_t			*p, *kill;
	int				i;
	float			blend;
	clightstyle_t	*ls, *cl_ls;

	if(!r_decals->value)
		return;

	if (decals_needs_spawn)
	{
		decals_needs_spawn = false;
		for (int i=0; i<num_deferred_decals; i++)
			R_SpawnDecal(deferred_decals[i].center, deferred_decals[i].normal, deferred_decals[i].texture, deferred_decals[i].size, deferred_decals[i].size, 9999999, 9999999, deferred_decals[i].angle, deferred_decals[i].angle, deferred_decals[i].dtype, false, deferred_decals[i].style, deferred_decals[i].cl_style, &deferred_decals[i], false);
	}

	TraceDecal();

	glEnable (GL_BLEND);
	gl_TexEnv( GL_MODULATE );
	glMatrixMode(GL_TEXTURE);

	GL_BindVBO(NULL);
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	for ( ;; )
	{
		kill = active_decals;
		if (kill && (kill->die < cl.leveltime))
		{
			active_decals = kill->next;
			kill->next = free_decals;
			free_decals = kill;
			continue;
		}
		break;
	}

	for (p=active_decals ; p ; p=p->next)
	{
		for ( ;; )
		{
			kill = p->next;
			if (kill && (kill->die < cl.leveltime))
			{
				p->next = kill->next;
				kill->next = free_decals;
				free_decals = kill;
				continue;
			}
			break;
		}

		ls = &sv_lightstyle[p->style];
		cl_ls = &cl_lightstyle[p->cl_style];
		blend = ls->val * cl_ls->val;
		if (!blend)
			continue;

		if (p->die - cl.leveltime > 4999999)	// Вечный декаль?
			p->die = cl.leveltime + 9999999;	// обновим его время

		if (curdecal && p->defDecal == curdecal && r_editor->value)
		{
			if(!trace_decal && (cl.time & 512))
				continue;
			glDisable (GL_CULL_FACE);
			glDisable(GL_DEPTH_TEST);
		}

		glLoadIdentity();
		glTranslatef(0.5, 0.5, 0);
		glRotatef(p->angle, 0, 0, 1);

		if(p->model_id && r_drawentities->value)
		{
			for (i=0 ; i<r_newrefdef.num_entities ; i++)
			{
				currententity = &r_newrefdef.entities[i];
				if(currententity->model && currententity->model->firstmodelsurface == p->model_id)
					break;
			}

			if(i==r_newrefdef.num_entities)
				continue;
			else
			{
				glMatrixMode(GL_MODELVIEW);
				glPushMatrix ();
				R_RotateForEntity (currententity);
				glMatrixMode(GL_TEXTURE);
			}
		}

		//calculate color based on life ...
		blend *= (p->die-cl.leveltime)/p->lifetime;
		float	tempor = p->die - cl.leveltime;
		if (tempor < 0.5)
			blend *= 2*tempor;
		GL_Color3f(blend, blend, blend);

		glTexCoordPointer(2, GL_FLOAT, 0, p->texcoordArray);

		int	tri, counter;
		counter = 0;
		while (1)
		{
			int	tex = p->images[counter]->texnum;
			dtype_t typ = p->types[counter];

			bool vis = false;
			tri = 0;
			while (1)
			{
				tri++;
				counter++;
				if (counter==p->triangleCount)
					break;
				if (!vis && (p->surfs[counter]->visframe == r_framecount))
					vis = true;
				if (tex!=p->images[counter]->texnum)
					break;
			}

			if (vis)
			{
				GL_Bind2D(tex);
				switch(typ)
				{
				case dt_mul:
					GL_BlendFunc (GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
					break;
				default:
				case dt_add:
					GL_BlendFunc (GL_ONE, GL_ONE);
					break;
				}

				glVertexPointer(3, GL_FLOAT, 0, p->vertexArray);
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, 3*tri, 3*tri, GL_UNSIGNED_BYTE, &p->triangleArray[counter-tri]);
///				else
					glDrawElements(GL_TRIANGLES, 3*tri, GL_UNSIGNED_BYTE, &p->triangleArray[counter-tri]);
			}

			if (counter==p->triangleCount)
				break;
		}

		if(p->model_id && r_drawentities->value)
		{
			glMatrixMode(GL_MODELVIEW);
			glPopMatrix ();
			glMatrixMode(GL_TEXTURE);
		}

		if (curdecal && p->defDecal == curdecal && r_editor->value)
		{
			glEnable (GL_CULL_FACE);
			glEnable(GL_DEPTH_TEST);
		}
	}

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	gl_TexEnv( GL_REPLACE );
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
}

/////////////////////////////////////////////////////////////////////////////////
unsigned	rhs_checksum;
unsigned Mod_ReadHunkSize(void *data, int modfilelen)
{
	FILE		*f;
	int			len;
	unsigned	buf[2];
	char		name[MAX_OSPATH];

	rhs_checksum = Com_BlockChecksum(data, modfilelen);

	Com_sprintf (name, sizeof(name), "%s/cache/%s.mem", FS_Gamedir(), loadmodel->name);
	f = FS_Fopen(name, "rb");
	if (!f)
		return 0;
	len = fread(buf, 1, sizeof(buf), f);
	fclose(f);
	if (len != sizeof(buf))
		return 0;

	if (buf[0] != rhs_checksum)
		return 0;

	return buf[1];
}


void Mod_WriteHunkSize(int size)
{
	FILE		*f;
	unsigned	buf[2];
	char		name[MAX_OSPATH];

	buf[0] = rhs_checksum;
	buf[1] = size;

	Com_sprintf (name, sizeof(name), "%s/cache/%s.mem", FS_Gamedir(), loadmodel->name);
	FS_CreatePath(name);
	f = FS_Fopen(name, "wb");
	if (f)
	{
		fwrite(buf, 1, sizeof(buf), f);
		fclose(f);
	}
}

/*
==================
Mod_ForName

Loads in a model for the given name
==================
*/
int		hunk_alloc;
model_t *Mod_ForName(char *name, bool crash, float scale, bool invert, bool cache)		// scale and invert - for md2, md3, ase, iqm only
{
	model_t		*mod;
	unsigned	*buf;
	char		cachename[MAX_OSPATH];
	FILE		*file;
	int			i, hunk_size = 0;
	int			hunk_add = 1024;	// шаг прироста

	if (!name[0])
		Com_Error (ERR_DROP, "Mod_ForName: NULL name");

	if (cache)
		hunk_alloc = 64 * 1024 * 1024;		/// при кэшировании будем сразу выделять 64 мегабайта, чего должно хватить imho для всех моделей всех видов.
	else
		hunk_alloc = 0;

	//
	// inline models are grabbed only from worldmodel
	//
	if (name[0] == '*')
	{
		i = atoi(name+1);
		if (i < 1 || !r_worldmodel || i >= r_worldmodel->numsubmodels)
			Com_Error (ERR_DROP, "Mod_ForName: bad inline model number");
		return &mod_inline[i];
	}

	unsigned	hash = Com_HashKey(name);
	//
	// search the currently loaded models
	//
	for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
	{
		if (!mod->name[0])
			continue;
		if (mod->hash==hash)
		{
			if (!b_stricmp (mod->name, name) )
			{
				if ((mod->type == mod_alias || mod->type == mod_alias_md3))
				{
					if (mod->scale == scale && mod->invert == invert)
						return mod;
				}
				else
					return mod;
			}
		}
	}

	//
	// find a free model slot spot
	//
	for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
	{
		if (!mod->name[0])
			break;	// free spot
	}
	if (i == mod_numknown)
	{
		if (mod_numknown == MAX_MOD_KNOWN)
			Com_Error (ERR_DROP, "Mod_ForName: mod_numknown == MAX_MOD_KNOWN");
		mod_numknown++;
	}

again:
	strcpy(mod->name, name);
	mod->hash = hash;
	mod->scale = scale;
	mod->invert = invert;
	hunk_alloc += hunk_add;		// for md2 only

	//
	// load the file
	//
	modfilelen = FS_LoadFile (mod->name, (void **)&buf);
	if (!buf)
	{
er:		if (crash)
			Com_Error (ERR_DROP, "Mod_ForName: %s not found", mod->name);
		Memset (mod->name, 0, sizeof(mod->name));
		return NULL;
	}
	if (modfilelen <= 0)
	{
///		if (buf)	PVS-Studio
			Z_Free(buf);
		goto er;
	}

	loadmodel = mod;

	unsigned hs = Mod_ReadHunkSize(buf, modfilelen);
	if (hs)
	{
		hunk_size = hs & 0x7FFFFFFF;
		Com_DPrintf("Hunk size of '%s' = %i bytes, loaded from cache.\n", mod->name, hunk_size);
	}
	bool	smoothed = false;
	int		smoothType = 0;		// 0 - unknown, 1 - no smooth, 2 - smooth

	// call the apropriate loader
/*
	if (!Q_strcasecmp(name + strlen(name) - 4, ".iqm")) // look if we have an .iqm file (special case)
	{
		loadmodel->extradata = Hunk_Begin(hunk_size ? hunk_size : hunk_model_md3->value, name);
		Mod_LoadAliasIQMModel(mod, buf, scale, invert, cache);
		if (!hunk_size)	// не было кэша
		{	// выгрузить модель, для которой было выделено слишком много памяти (неточное выделение)
			hunk_size = loadmodel->extradatasize = Hunk_End();		// тут точный размер
			Mod_Free(loadmodel);
			Z_Free(buf);
			Com_DPrintf("reloading %s for optimal memory allocation...\n", loadmodel->name);
			goto again;
		}
	}
	else
*/
	if (!Q_strcasecmp(name+strlen(name)-4, ".ase")) // look if we have an .ase file (special case)
	{
		loadmodel->extradata = Hunk_Begin (hunk_size ? hunk_size : hunk_model_md3->value, name);
		Mod_LoadAliasASEModel(mod, buf, scale, invert, cache);
		if (!hunk_size)	// не было кэша
		{	// выгрузить модель, для которой было выделено слишком много памяти (неточное выделение)
			hunk_size = loadmodel->extradatasize = Hunk_End ();		// тут точный размер
			Mod_Free(loadmodel);
			Z_Free (buf);
			Com_DPrintf("reloading %s for optimal memory allocation...\n", loadmodel->name);
			goto again;
		}
	}
	else
	{
		switch (LittleLong(*(unsigned *)buf))
		{
		case IDALIASHEADER:
			// check the cache .md2
			if (invert)
				Com_sprintf (cachename, sizeof(cachename), "%s/cache/Inv_%s", FS_Gamedir(), loadmodel->name);
			else
				Com_sprintf (cachename, sizeof(cachename), "%s/cache/%s", FS_Gamedir(), loadmodel->name);
			file = FS_Fopen (cachename, "rb");
			if (file)
			{
				byte	smooth;
				i = fread(&smooth, 1, 1, file);
				fclose(file);
				if (i==1 && hs)
				{	// есть .mem-кэш и .md2-кэш, проверим "сглаженность"
					if ((bool)smooth != (bool)(hs & 0x80000000))
					{	// Информация о сглаженности модели из md2 кэша не соответствует информации из .mem! Сбросим кэши!
						Com_Printf("^3Warning:^7 MEM-cache mismatches to MD2-cache for %s! Cache will be reset.\n", loadmodel->name);
reset:					// kill cache .md2
						remove (cachename);
						if (hs)
						{	// kill cache .mem
							Com_sprintf (cachename, sizeof(cachename), "%s/cache/%s.mem", FS_Gamedir(), loadmodel->name);
							remove (cachename);
						}
						hs = 0;
						hunk_size = 0;
						smoothType = 0;
					}
					else
						smoothType = smooth ? 2 : 1;
				}
				else
					goto reset;		// нет .mem-кэша, но есть .md2-кэш (либо .md2-кэш не читается). Нехорошо! Сбросим их.
			}
			else
			{
				if (hs)
					goto reset;		// нет .md2-кэша, но есть .mem-кэш. Нехорошо! Сбросим их.
				// тут:  нет никаких кэшей, работаем на кэширование...
				smoothType = 0;
			}

			loadmodel->extradata = Hunk_Begin (hunk_size ? hunk_size : hunk_alloc/*hunk_model->value*/, name);
			if (!Mod_LoadAliasModel(mod, buf, scale, invert, smoothType, cache))
			{	// загрузка модели сброшена!
///				Com_Printf("^3Warning:^7 MEM-cache mismatches to FX2 for %s\n", loadmodel->name);
				Com_DPrintf("failed to allocate %i bytes for %s\n", hunk_alloc, loadmodel->name);
				// kill cache .mem
				Com_sprintf (cachename, sizeof(cachename), "%s/cache/%s.mem", FS_Gamedir(), loadmodel->name);
				remove (cachename);
				// kill cache .md2
				if (invert)
					Com_sprintf (cachename, sizeof(cachename), "%s/cache/Inv_%s", FS_Gamedir(), loadmodel->name);
				else
					Com_sprintf (cachename, sizeof(cachename), "%s/cache/%s", FS_Gamedir(), loadmodel->name);
				remove (cachename);
				// release partially loaded model
				loadmodel->extradatasize = Hunk_End ();
				Mod_Free(loadmodel);
				Z_Free (buf);
				hunk_size = 0;
				goto again;
			}
			smoothed = (loadmodel->flags & RF_SMOOTHVECS);
			break;

		case IDSPRITEHEADER:
			loadmodel->extradata = Hunk_Begin (hunk_size ? hunk_size : hunk_sprite->value, name);
			Mod_LoadSpriteModel(mod, buf, cache);
			break;

		case IDSPRITE2HEADER:
			loadmodel->extradata = Hunk_Begin (hunk_size ? hunk_size : hunk_sprite->value, name);
			Mod_LoadSprite2Model(mod, buf, cache);
			break;

		case IDMD3HEADER:
			loadmodel->extradata = Hunk_Begin (hunk_size ? hunk_size : hunk_model_md3->value, name);
			Mod_LoadAliasMD3Model(mod, buf, scale, invert, cache);
			if (!hunk_size)	// не было кэша
			{	// выгрузить модель, для которой было выделено слишком много памяти (неточное выделение)
				hunk_size = loadmodel->extradatasize = Hunk_End ();		// тут точный размер
				Mod_Free(loadmodel);
				Z_Free (buf);
				Com_DPrintf("reloading %s for optimal memory allocation...\n", loadmodel->name);
				goto again;
			}
			break;

		case IDBSPHEADER:
			loadmodel->extradata = Hunk_Begin (hunk_size ? hunk_size : hunk_map->value, name);
			Mod_LoadBrushModel(mod, buf);
			break;

		default:
			Com_Error (ERR_DROP,"Mod_ForName: unknown fileid for %s", mod->name);
			break;
		}
	}

	loadmodel->extradatasize = Hunk_End ();
	if (needFree)
	{
		int saved = loadmodel->extradatasize;
		loadmodel->extradata = needFree;
		loadmodel->extradatasize = 0;		// чтобы не сбивать статистику hunk_stats
		char	errname[MAX_QPATH];
		Com_sprintf (errname, sizeof(errname), loadmodel->name);
		Mod_Free(loadmodel);
		if (hunk_size)
			Sys_Error ("Mod_ForName: can't load %s", errname);
		hunk_size = saved;
		Z_Free (buf);
		goto again;
	}

	if (!hs)
	{
		Com_DPrintf("Hunk size of '%s' = %i bytes, writing to cache.\n", mod->name, loadmodel->extradatasize);
		Mod_WriteHunkSize(loadmodel->extradatasize + (smoothed ? 0x80000000 : 0));
	}

	Z_Free (buf);

	return mod;
}


void CL_ClearParticles ()
{
	int		i;
	
	free_particles = &particles[0];
	active_particles = NULL;

	for (i=0 ;i<MAX_PARTICLES ; i++)
		particles[i].next = &particles[i+1];
	particles[MAX_PARTICLES-1].next = NULL;
}


void CL_ClearDlights ()
{
	Memset (cl_dlights, 0, sizeof(cl_dlights));
}


void CL_ClearLightStyles ()
{
	Memset (sv_lightstyle, 0, sizeof(sv_lightstyle));
	Memset (cl_lightstyle, 0, sizeof(cl_lightstyle));
	lastofs = -1;
}


void CL_ClearEffects ()
{
///	CL_ClearParticles ();
///		CL_ClearClEntities ();
		CL_ResetEditor();
	CL_ClearDlights ();
	CL_ClearLightStyles ();
}


void CL_InitDecals()
{
	int i;

	Memset (decals, 0, sizeof(decals));
	free_decals = &decals[0];
	active_decals = NULL;

	for (i=0 ; i<MAX_DECALS ; i++)
		decals[i].next = &decals[i+1];

	decals[MAX_DECALS-1].next = NULL;
}


void CL_ClearTEnts ()
{
	Memset (cl_beams, 0, sizeof(cl_beams));
	Memset (cl_explosions, 0, sizeof(cl_explosions));
	Memset (cl_lasers, 0, sizeof(cl_lasers));
//ROGUE
	Memset (cl_playerbeams, 0, sizeof(cl_playerbeams));
	Memset (cl_sustains, 0, sizeof(cl_sustains));
//ROGUE
}

void CL_ClearState ()
{
	S_StopAllSounds ();
	CL_ClearEffects ();
	CL_ClearTEnts ();

// wipe the entire cl structure
	Memset (&cl, 0, sizeof(cl));
	Memset (&cl_entities, 0, sizeof(cl_entities));

	SZ_Clear (&cls.netchan.message);
}


/*
=====================
CL_Disconnect

Goes from a connected state to full screen console state
Sends a disconnect message to the server
This is also called on Com_Error, so it shouldn't cause any errors
=====================
*/
void CL_Disconnect ()
{
#define final_len	32
	byte	final[final_len];

	if (cls.state == ca_disconnected)
		return;

	if (cl_timedemo && cl_timedemo->value)
	{
		int	time;
		
		time = Sys_Milliseconds () - cl.timedemo_start;
		if (time > 0)
		{
			Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", cl.timedemo_frames, time/1000.0, cl.timedemo_frames*1000.0 / time);
			if (gl_config.occlusion)
				Com_Printf("%i lights: %i wpoly %i epoly\nOcc.Query dropped: %i ents %i lights %i mirrors\n", oc_lights, oc_light_brush_polys, oc_light_alias_polys, oc_no_light_ents, oc_no_lights, oc_no_mirrors);
			else
				Com_Printf("%i lights: %i wpoly %i epoly\n", oc_lights, oc_light_brush_polys, oc_light_alias_polys);
		}
	}

	cl.refdef.blend[0] = cl.refdef.blend[1] = cl.refdef.blend[2] = cl.refdef.blend[3] = 0;

	M_ForceMenuOff();

	cls.connect_time = 0;

	SCR_StopCinematic ();
	CDAudio_Stop ();
///	S_StopBackgroundTrack();

	if (cls.demorecording)
		CL_Stop_f ();

	// send a disconnect message to the server
	final[0] = clc_stringcmd;
	strcpy ((char *)final+1, "disconnect");
	Netchan_Transmit (&cls.netchan, final_len, final);
	Netchan_Transmit (&cls.netchan, final_len, final);
	Netchan_Transmit (&cls.netchan, final_len, final);

	CL_ClearState ();

	// stop download
	if (cls.download)
	{
		fclose(cls.download);
		cls.download = NULL;
	}

	cls.state = ca_disconnected;
}
#undef final_len


/*
================
CL_Drop

Called after an ERR_DROP was thrown
================
*/
void CL_Drop ()
{
	if (cls.state == ca_uninitialized)
		return;
	if (cls.state == ca_disconnected)
		return;

	CL_Disconnect ();

	// drop loading plaque unless this is the initial game start
	if (cls.disable_servercount != -1)
		SCR_EndLoadingPlaque ();	// get rid of loading plaque
}


/*
==================
SV_FinalMessage

Used by SV_Shutdown to send a final message to all
connected clients before the server goes down.  The messages are sent immediately,
not just stuck on the outgoing message list, because the server is going
to totally exit after returning from this function.
==================
*/
void SV_FinalMessage (char *message, bool reconnect)
{
	int			i;
	client_t	*cl;

	SZ_Clear (&net_message);
	MSG_WriteByte (&net_message, svc_print);
	MSG_WriteByte (&net_message, PRINT_HIGH);
	MSG_WriteString (&net_message, message);

	if (reconnect)
		MSG_WriteByte (&net_message, svc_reconnect);
	else
		MSG_WriteByte (&net_message, svc_disconnect);

	// send it twice
	// stagger the packets to crutch operating system limited buffers

	for (i=0, cl = svs.clients ; i<maxclients->value ; i++, cl++)
///		if (cl->state >= cs_connected)	// FUCK
		if (cl->state == cs_connected || cl->state == cs_spawned)
			Netchan_Transmit (&cl->netchan, net_message.cursize, net_message.data);

	for (i=0, cl = svs.clients ; i<maxclients->value ; i++, cl++)
///		if (cl->state >= cs_connected)	// FUCK
		if (cl->state == cs_connected || cl->state == cs_spawned)
			Netchan_Transmit (&cl->netchan, net_message.cursize, net_message.data);
}


/*
================
SV_Shutdown

Called when each game quits,
before Sys_Quit or Sys_Error
================
*/
void SV_Shutdown (char *finalmsg, bool reconnect)
{
	if (svs.clients)
		SV_FinalMessage (finalmsg, reconnect);

	SV_ShutdownGameProgs ();

	// free current level
	if (sv.demofile)
		fclose (sv.demofile);
	Memset (&sv, 0, sizeof(sv));
	Com_SetServerState (sv.state);

	// free server static data
	if (svs.clients)
		Z_Free (svs.clients);
	if (svs.client_entities)
		Z_Free (svs.client_entities);
	if (svs.demofile)
		fclose (svs.demofile);
	Memset (&svs, 0, sizeof(svs));
}


bool IconOfSkinExists( char *skin, char **names, int nfiles )
{
	int i;
	char scratch[1024];

	strcpy( scratch, skin );
	strcat( scratch, "_i" );

	for ( i = 0; i < nfiles; i++ )
		if ( strcmp( names[i], scratch ) == 0 )
			return true;

	return false;
}


void FreeFileList( char **list, int n )
{
	int i;
	for ( i = 0; i < n; i++ )
	{
		if ( list[i] )
		{
			free( list[i] );
			list[i] = 0;
		}
	}
	free( list );
}


void PlayerConfig_ScanDirectories()
{
	char findname[1024];
	char scratch[1024];
	int npms = 0;
	int ndirs;
	int ndirs0 = 0;
	int ndirs1 = 0;
	int ndirs2 = 0;
	char **dirnames;
	char **dirnames0;
	char **dirnames1;
	char **dirnames2;
	char *path = NULL;
	int i, j, l, m;

	s_numplayermodels = 0;

	/*
	** get a list of directories (mod's path, baseq3, cd)
	*/
	path = FS_NextPath( fs_gamedir );
	if (path)
	{
		Com_sprintf( findname, sizeof(findname), "%s/players/*.*", path );
		dirnames0 = FS_ListFiles( findname, &ndirs0, SFF_SUBDIR, 0 );

		path = FS_NextPath( path );
		if (path)
		{
			Com_sprintf( findname, sizeof(findname), "%s/players/*.*", path );
			dirnames1 = FS_ListFiles( findname, &ndirs1, SFF_SUBDIR, 0 );

			path = FS_NextPath( path );
			if (path)
			{
				Com_sprintf( findname, sizeof(findname), "%s/players/*.*", path );
				dirnames2 = FS_ListFiles( findname, &ndirs2, SFF_SUBDIR, 0 );
			}
		}
	}

	// create common list
	ndirs = ndirs0 + ndirs1 + ndirs2;
	if ( !ndirs )
		return;	/// false;
	dirnames = (char **) malloc( sizeof( char * ) * ( ndirs + 1 ) );
	Memset( dirnames, 0, sizeof( char * ) * ( ndirs + 1 ) );

	j = 0;
	// copy 1st list
	if (ndirs0)
	{
		for ( i = 0; i < ndirs0; i++ )
		{
			if ( dirnames0[i] )
			{
				dirnames[j++] = dirnames0[i];
			}
		}
	}
	// copy 2nd list
	if (ndirs1)
	{
		for ( i = 0; i < ndirs1; i++ )
		{
			if ( dirnames1[i] )
			{
				dirnames[j++] = dirnames1[i];
			}
		}
	}
	// copy 3rd list
	if (ndirs2)
	{
		for ( i = 0; i < ndirs2; i++ )
		{
			if ( dirnames2[i] )
			{
				dirnames[j++] = dirnames2[i];
			}
		}
	}

	npms = j;
	if ( npms > MAX_PLAYERMODELS )
		npms = MAX_PLAYERMODELS;

	for ( i = 0; i < npms; i++ )
	{
		int k, s;
		char *a, *b, *c;
		char **pngnames, **tganames, **ddsnames, **jpgnames, **pcxnames, **names;
		int npngfiles, ntgafiles, nddsfiles, njpgfiles, npcxfiles, nfiles;
		char **skinnames;
		int nskins = 0;

/*		// verify the existence of tris.md2
		strcpy( scratch, dirnames[i] );
		strcat( scratch, "/tris.md2" );
		if ( !Sys_FindFirst( scratch, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM ) )
		{
			free( dirnames[i] );
			dirnames[i] = 0;
			Sys_FindClose();
			continue;
		}
		Sys_FindClose();
*/
		// verify the existence of at least one skin
		strcpy( scratch, dirnames[i] );
		strcat( scratch, "/*.png" );
		pngnames = FS_ListFiles( scratch, &npngfiles, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
		if (!pngnames)	npngfiles = 0;

		strcpy( scratch, dirnames[i] );
		strcat( scratch, "/*.tga" );
		tganames = FS_ListFiles( scratch, &ntgafiles, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
		if (!tganames)	ntgafiles = 0;

		strcpy( scratch, dirnames[i] );
		strcat( scratch, "/*.dds" );
		ddsnames = FS_ListFiles( scratch, &nddsfiles, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
		if (!ddsnames)	nddsfiles = 0;

		strcpy( scratch, dirnames[i] );
		strcat( scratch, "/*.jpg" );
		jpgnames = FS_ListFiles( scratch, &njpgfiles, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
		if (!jpgnames)	njpgfiles = 0;

		strcpy( scratch, dirnames[i] );
		strcat( scratch, "/*.pcx" );
		pcxnames = FS_ListFiles( scratch, &npcxfiles, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
		if (!pcxnames)	npcxfiles = 0;

		// create common list
		nfiles = npngfiles + ntgafiles + nddsfiles + njpgfiles + npcxfiles;
		if ( !nfiles )
		{
			free( dirnames[i] );
			dirnames[i] = 0;
			continue;
		}
		names = (char **) malloc( sizeof( char * ) * ( nfiles + 1 ) );
		Memset( names, 0, sizeof( char * ) * ( nfiles + 1 ) );

		j = 0;
		char *dot;
		// copy PNG list
		if (npngfiles)
		{
			for ( l = 0; l < npngfiles; l++ )
			{
				if ( pngnames[l] )
				{
					dot = pngnames[l] + strlen(pngnames[l]) - 4;
					dot[0] = 0;	// cut .ext
					for (k=0; k<j; k++)
						if (!Q_strcasecmp(names[k], pngnames[l]))
							break;
					if (k==j)
						names[j++] = pngnames[l];
				}
			}
		}
		// copy TGA list
		if (ntgafiles)
		{
			for ( l = 0; l < ntgafiles; l++ )
			{
				if ( tganames[l] )
				{
					dot = tganames[l] + strlen(tganames[l]) - 4;
					dot[0] = 0;	// cut .ext
					for (k=0; k<j; k++)
						if (!Q_strcasecmp(names[k], tganames[l]))
							break;
					if (k==j)
						names[j++] = tganames[l];
				}
			}
		}
		// copy DDS list
		if (nddsfiles)
		{
			for ( l = 0; l < nddsfiles; l++ )
			{
				if ( ddsnames[l] )
				{
					dot = ddsnames[l] + strlen(ddsnames[l]) - 4;
					dot[0] = 0;	// cut .ext
					for (k=0; k<j; k++)
						if (!Q_strcasecmp(names[k], ddsnames[l]))
							break;
					if (k==j)
						names[j++] = ddsnames[l];
				}
			}
		}
		// copy JPG list
		if (njpgfiles)
		{
			for ( l = 0; l < njpgfiles; l++ )
			{
				if ( jpgnames[l] )
				{
					dot = jpgnames[l] + strlen(jpgnames[l]) - 4;
					dot[0] = 0;	// cut .ext
					for (k=0; k<j; k++)
						if (!Q_strcasecmp(names[k], jpgnames[l]))
							break;
					if (k==j)
						names[j++] = jpgnames[l];
				}
			}
		}
		// copy PCX list
		if (npcxfiles)
		{
			for ( l = 0; l < npcxfiles; l++ )
			{
				if ( pcxnames[l] )
				{
					dot = pcxnames[l] + strlen(pcxnames[l]) - 4;
					dot[0] = 0;	// cut .ext
					for (k=0; k<j; k++)
						if (!Q_strcasecmp(names[k], pcxnames[l]))
							break;
					if (k==j)
						names[j++] = pcxnames[l];
				}
			}
		}

		nfiles = j;

		// count valid skins, which consist of a skin with a matching "_i" icon
		for ( k = 0; k < nfiles; k++ )
		{
			if ( !strstr( names[k], "_i" ) )
				if ( IconOfSkinExists( names[k], names, nfiles ) )
					nskins++;
		}
		if ( !nskins )
		{	// Berserker: fixed memory leak
			free( dirnames[i] );
			dirnames[i] = 0;
			FreeFileList( names, nfiles );
			continue;
		}

		skinnames = (char **) malloc( sizeof( char * ) * ( nskins + 1 ) );
		Memset( skinnames, 0, sizeof( char * ) * ( nskins + 1 ) );

		// copy the valid skins
		for ( s = 0, k = 0; k < nfiles; k++ )
		{
			char *a, *b, *c;

			if ( !strstr( names[k], "_i" ) )
			{
				if ( IconOfSkinExists( names[k], names, nfiles ) )
				{
					a = strrchr( names[k], '/' );
					b = strrchr( names[k], '\\' );

					if ( a > b )
						c = a;
					else
						c = b;

					strcpy( scratch, c + 1 );
					skinnames[s] = strdup( scratch );
					s++;
				}
			}
		}

		// at this point we have a valid player model
		s_pmi[s_numplayermodels].nskins = nskins;
		s_pmi[s_numplayermodels].skindisplaynames = skinnames;

		// make short name for the model
		a = strrchr( dirnames[i], '/' );
		b = strrchr( dirnames[i], '\\' );

		if ( a > b )
			c = a;
		else
			c = b;

		strncpy( s_pmi[s_numplayermodels].displayname, c + 1, MAX_DISPLAYNAME-1 );
		strcpy( s_pmi[s_numplayermodels].directory, c + 1 );

		if (npngfiles)
			free (pngnames);
		if (ntgafiles)
			free (tganames);
		if (nddsfiles)
			free (ddsnames);
		if (njpgfiles)
			free (jpgnames);
		if (npcxfiles)
			free (pcxnames);

		FreeFileList( names, nfiles );

		s_numplayermodels++;
	}

	if (ndirs0)
		free (dirnames0);
	if (ndirs1)
		free (dirnames1);
	if (ndirs2)
		free (dirnames2);

	if ( dirnames )
		FreeFileList( dirnames, ndirs );

	// try to union s_pmi with equal .directory
	m = 0;
	for (i=s_numplayermodels-1; i>=0; i--)
	{
		for (j=i; j>=0; j--)
		{
			if (i != j)
			{
				if (!Q_strcasecmp(s_pmi[i].directory, s_pmi[j].directory))
				{
					int		nskins;
					char	**skinnames;
					nskins = s_pmi[j].nskins + s_pmi[i].nskins;
					skinnames = (char **) malloc( sizeof( char * ) * ( nskins + 1 ) );
					Memset( skinnames, 0, sizeof( char * ) * ( nskins + 1 ) );
					for (l=0; l<s_pmi[j].nskins; l++)
						skinnames[l] = s_pmi[j].skindisplaynames[l];
					for (l=0; l<s_pmi[i].nskins; l++)
						skinnames[l + s_pmi[j].nskins] = s_pmi[i].skindisplaynames[l];
					s_pmi[j].nskins = nskins;
					free(s_pmi[j].skindisplaynames);
					s_pmi[j].skindisplaynames = skinnames;
					s_pmi[i].nskins = 0;
					s_pmi[i].displayname[0] = 0;
					s_pmi[i].directory[0] = 0;
					m++;
				}
			}
		}
	}

	// pack
	if (m)
	{
		while (1)
		{
			playermodelinfo_s	*free_pmi;
			playermodelinfo_s	*pmi;
			for (i=0; i<s_numplayermodels; i++)
				if (!s_pmi[i].nskins)
				{
					free_pmi = &s_pmi[i];
					break;
				}
			if (i == s_numplayermodels)
				break;
			for (; i<s_numplayermodels; i++)
				if (s_pmi[i].nskins)
				{
					pmi = &s_pmi[i];
					break;
				}
			if (i == s_numplayermodels)
				break;
			Memcpy(free_pmi, pmi, sizeof(s_pmi[0]));
			pmi->nskins = 0;
		}
	}
	s_numplayermodels -= m;

	// verify the existence of tris.md2
	for (i=0; i<s_numplayermodels; i++)
	{
		if (s_pmi[i].directory[0])
		{
			strcpy( scratch, "players/" );
			strcat( scratch, s_pmi[i].directory );
			strcat( scratch, "/tris.md2" );
			l = FS_LoadFile( scratch, NULL );
			if (l <= 0)
			{	// model does not exist
				if (i < s_numplayermodels-1)
				{	// free current s_pmi
					free(s_pmi[i].skindisplaynames);
					// move down all above s_pmi
					for (j = i; j < s_numplayermodels-1; j++)
						Memcpy(&s_pmi[j], &s_pmi[j + 1], sizeof(s_pmi[0]));
				}
				s_numplayermodels--;
			}
		}
	}
}

int PlayerConfig_MenuKey (int key)
{
	int i;

	if ( key == K_ESCAPE )
	{
		char scratch[1024];

		Cvar_Set( "name", s_player_name_field.buffer );
		Cvar_Set( "rail_radius", s_player_radius_field.buffer );
		if(railtrailradius->value<0 || railtrailradius->value>127)
			Cvar_SetValue("rail_radius", 3);
		Cvar_SetValue( "rail_spiral", s_player_spiral_box.curvalue );
		if(railtrailspiral->value<0 || railtrailspiral->value>7)
			Cvar_SetValue("rail_spiral", 1);
		Cvar_SetValue( "rail_core", s_player_core_box.curvalue );

		Com_sprintf( scratch, sizeof( scratch ), "%s/%s", 
			s_pmi[s_player_model_box.curvalue].directory, 
			s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue] );

		Cvar_Set( "skin", scratch );

		for ( i = 0; i < s_numplayermodels; i++ )
		{
			int j;
			for ( j = 0; j < s_pmi[i].nskins; j++ )
			{
				if ( s_pmi[i].skindisplaynames[j] )
					free( s_pmi[i].skindisplaynames[j] );
				s_pmi[i].skindisplaynames[j] = 0;
			}
			free( s_pmi[i].skindisplaynames );
			s_pmi[i].skindisplaynames = 0;
			s_pmi[i].nskins = 0;
		}
	}
	return Default_MenuKey( &s_player_config_menu, key );
}


/*
=============
Com_Error

Both client and server can use this, and it will
do the apropriate things.
=============
*/
void Com_Error (int code, char *fmt, ...)
{
	va_list		argptr;
	static char	msg[MAXPRINTMSG];
	static bool	recursive;

	mapshot[0] = 0;
	ignore_cvars = false;
	loading_stage = 0;
	caching__ = false;

	if (recursive)
		Sys_Error ("recursive error after: %s", msg);
	recursive = true;

	va_start (argptr,fmt);
	vsprintf (msg,fmt,argptr);
	va_end (argptr);

	if (code == ERR_DISCONNECT && !no_jmp)
	{
		CL_Drop ();
		recursive = false;
		longjmp (abortframe, -1);
	}
	else if (code == ERR_DROP && !no_jmp)
	{
		Com_Printf ("^1********************\nERROR: %s\n********************\n", msg);
		SV_Shutdown (va("Server crashed: %s\n", msg), false);
		CL_Drop ();
		recursive = false;
		longjmp (abortframe, -1);
	}
	else
	{
		SV_Shutdown (va("Server fatal crashed: %s\n", msg), false);
		CL_Shutdown ();
	}

	if (logfile)
	{
		fclose (logfile);
		logfile = NULL;
	}

	Sys_Error ("%s", msg);
}


char *CopyString (char *in)
{
	char	*out;
	
	out = (char*) Z_Malloc (strlen(in)+1, true);
	strcpy (out, in);
	return out;
}


char *Cvar_VariableString (char *var_name)
{
	cvar_t *var;

	var = Cvar_FindVar (var_name);
	if (!var)
		return "";
	return var->string;
}


void	Cmd_AddCommand (char *cmd_name, xcommand_t function)
{
	unsigned		hash = Com_HashKey(cmd_name);
	cmd_function_t	*cmd;

// fail if the command is a variable name
	if (Cvar_VariableString(cmd_name)[0])
	{
		Com_Printf ("^3Cmd_AddCommand: %s already defined as a var\n", cmd_name);
		return;
	}
	
// fail if the command already exists
	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
	{
		if (hash == cmd->hash)
		{
			if (!Q_strcasecmp(cmd->name, cmd_name))
			{
				Com_Printf ("^3Cmd_AddCommand: %s already defined\n", cmd_name);
				return;
			}
		}
	}

	cmd = (cmd_function_s *) Z_Malloc (sizeof(cmd_function_t), true);
	cmd->name = cmd_name;
	cmd->hash = hash;
	cmd->function = function;
	cmd->next = cmd_functions;
	cmd_functions = cmd;
}


int		Cmd_Argc ()
{
	return cmd_argc;
}


char	*Cmd_Argv (int arg)
{
	if ( (unsigned)arg >= cmd_argc )
		return cmd_null_string;
	return cmd_argv[arg];	
}


char	*Cmd_Args ()
{
	return cmd_args;
}


/*
===================
Key_StringToKeynum

Returns a key number to be used to index keybindings[] by looking at
the given string.  Single ascii characters return themselves, while
the K_* names are matched up.
===================
*/
int Key_StringToKeynum (char *str)
{
	keyname_t	*kn;
	
	if (!str || !str[0])
		return -1;
	if (!str[1])
		return str[0];

	for (kn=keynames ; kn->name ; kn++)
	{
		if (!Q_strcasecmp(str,kn->name))
			return kn->keynum;
	}
	return -1;
}


void Key_SetBinding (int keynum, char *binding)
{
	char	*nw;
	int		l;
			
	if (keynum == -1)
		return;

// free old bindings
	if (keybindings[keynum])
	{
		Z_Free (keybindings[keynum]);
		keybindings[keynum] = NULL;
	}
			
// allocate memory for new binding
	l = strlen (binding);	
	nw = (char*) Z_Malloc (l+1, true);
	strcpy (nw, binding);
	nw[l] = 0;
	keybindings[keynum] = nw;
}


void Key_Unbind_f ()
{
	int	b;

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3USAGE: unbind <key> : remove commands from a key\n");
		return;
	}
	
	b = Key_StringToKeynum (Cmd_Argv(1));
	if (b==-1)
	{
		Com_Printf ("^3\"%s\" isn't a valid key\n", Cmd_Argv(1));
		return;
	}

	Key_SetBinding (b, "");
}


void Key_Unbindall_f ()
{
	int		i;
	
	for (i=0 ; i<256 ; i++)
		if (keybindings[i])
			Key_SetBinding (i, "");
}


void Key_Bind_f ()
{
	int		i, c, b;
	char	cmd[1024];
	
	c = Cmd_Argc();

	if (c < 2)
	{
		Com_Printf ("^3Usage: bind <key> [command] : attach a command to a key\n");
		return;
	}
	b = Key_StringToKeynum (Cmd_Argv(1));
	if (b==-1)
	{
		Com_Printf ("^3\"%s\" isn't a valid key\n", Cmd_Argv(1));
		return;
	}

	if (c == 2)
	{
		if (keybindings[b])
			Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keybindings[b] );
		else
			Com_Printf ("^3\"%s\" is not bound\n", Cmd_Argv(1) );
		return;
	}
	
// copy the rest of the command line
	cmd[0] = 0;		// start out with a null string
	for (i=2 ; i< c ; i++)
	{
		strcat (cmd, Cmd_Argv(i));
		if (i != (c-1))
			strcat (cmd, " ");
	}

	Key_SetBinding (b, cmd);
}


void Key_Bindlist_f ()
{
	int		i;

	for (i=0 ; i<256 ; i++)
		if (keybindings[i] && keybindings[i][0])
			Com_Printf ("%s \"%s\"\n", Key_KeynumToString(i), keybindings[i]);
}


void Key_Init ()
{
	int		i;

	for (i=0 ; i<32 ; i++)
	{
		key_lines[i][0] = ']';
		key_lines[i][1] = 0;
	}
	key_linepos = 1;
	
//
// init ascii characters in console mode
//
	for (i=32 ; i<128 ; i++)
		consolekeys[i] = true;
	consolekeys[K_ENTER] = true;
	consolekeys[K_KP_ENTER] = true;
	consolekeys[K_TAB] = true;
	consolekeys[K_LEFTARROW] = true;
	consolekeys[K_KP_LEFTARROW] = true;
	consolekeys[K_RIGHTARROW] = true;
	consolekeys[K_KP_RIGHTARROW] = true;
	consolekeys[K_UPARROW] = true;
	consolekeys[K_KP_UPARROW] = true;
	consolekeys[K_DOWNARROW] = true;
	consolekeys[K_KP_DOWNARROW] = true;
	consolekeys[K_BACKSPACE] = true;
	consolekeys[K_HOME] = true;
	consolekeys[K_KP_HOME] = true;
	consolekeys[K_END] = true;
	consolekeys[K_KP_END] = true;
	consolekeys[K_PGUP] = true;
	consolekeys[K_KP_PGUP] = true;
	consolekeys[K_PGDN] = true;
	consolekeys[K_KP_PGDN] = true;
	consolekeys[K_SHIFT] = true;
	consolekeys[K_INS] = true;
	consolekeys[K_KP_INS] = true;
	consolekeys[K_KP_DEL] = true;
	consolekeys[K_KP_SLASH] = true;
	consolekeys[K_KP_PLUS] = true;
	consolekeys[K_KP_MINUS] = true;
	consolekeys[K_KP_5] = true;

	consolekeys['`'] = false;
	consolekeys['~'] = false;

	for (i=0 ; i<256 ; i++)
		keyshift[i] = i;
	for (i='a' ; i<='z' ; i++)
		keyshift[i] = i - 'a' + 'A';
	keyshift['1'] = '!';
	keyshift['2'] = '@';
	keyshift['3'] = '#';
	keyshift['4'] = '$';
	keyshift['5'] = '%';
	keyshift['6'] = '^';
	keyshift['7'] = '&';
	keyshift['8'] = '*';
	keyshift['9'] = '(';
	keyshift['0'] = ')';
	keyshift['-'] = '_';
	keyshift['='] = '+';
	keyshift[','] = '<';
	keyshift['.'] = '>';
	keyshift['/'] = '?';
	keyshift[';'] = ':';
	keyshift['\''] = '"';
	keyshift['['] = '{';
	keyshift[']'] = '}';
	keyshift['`'] = '~';
	keyshift['\\'] = '|';

	menubound[K_ESCAPE] = true;
	for (i=0 ; i<12 ; i++)
		menubound[K_F1+i] = true;

//
// register our functions
//
	Cmd_AddCommand ("bind",Key_Bind_f);
	Cmd_AddCommand ("unbind",Key_Unbind_f);
	Cmd_AddCommand ("unbindall",Key_Unbindall_f);
	Cmd_AddCommand ("bindlist",Key_Bindlist_f);
}


/*
==============
COM_Parse
Parse a token out of a string
==============
*/
char *COM_Parse (char **data_p)
{
	int		c;
	char	*data;

	data = *data_p;
	com_token_len = 0;
	com_token[0] = 0;

	if (!data)
	{
		*data_p = NULL;
		return "";
	}

// skip whitespace
skipwhite:
	while ( (c = *data) <= ' ')
	{
		if (c == -96)
			break;
		if (c == 0)
		{
			*data_p = NULL;
			return "";
		}
		data++;
	}

// skip // comments
	if (c=='/' && data[1] == '/')
	{
		while (*data && *data != '\n')
			data++;
		goto skipwhite;
	}

// handle quoted strings specially
	if (c == '\"')
	{
		data++;
		while (1)
		{
			c = *data++;
			if (c=='\"' || !c)
			{
				com_token[com_token_len] = 0;
				*data_p = data;
				return com_token;
			}
			if (com_token_len < MAX_TOKEN_CHARS)
			{
				com_token[com_token_len] = c;
				com_token_len++;
			}
		}
	}

// parse a regular word
	do
	{
		if (com_token_len < MAX_TOKEN_CHARS)
		{
			com_token[com_token_len] = c;
			com_token_len++;
		}
		data++;
		c = *data;
	} while (c>32 || c==-96);

	if (com_token_len == MAX_TOKEN_CHARS)
	{
//		Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS);
		com_token_len = 0;
	}
	com_token[com_token_len] = 0;

	*data_p = data;
	return com_token;
}


char *Cmd_MacroExpandString (char *text)
{
	int		i, j, count, len, k;
	bool	inquote;
	char	*scan;
	static	char	expanded[MAX_STRING_CHARS];
	char	temporary[MAX_STRING_CHARS];
	char	*token, *start;

	inquote = false;
	scan = text;

	len = strlen (scan);
	if (len >= MAX_STRING_CHARS)
	{
		Com_Printf ("^3Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS);
		return NULL;
	}

	count = 0;

	for (i=0 ; i<len ; i++)
	{
		if (scan[i] == '"')
			inquote ^= 1;
		if (inquote)
			continue;	// don't expand inside quotes
		if (scan[i] != '$')
			continue;
		// scan out the complete macro
		start = scan+i+1;
		token = COM_Parse (&start);
		if (!start)
			continue;
	
		token = Cvar_VariableString (token);

		j = strlen(token);
		len += j;
		if (len >= MAX_STRING_CHARS)
		{
			Com_Printf ("^3Expanded line exceeded %i chars, discarded.\n", MAX_STRING_CHARS);
			return NULL;
		}

		strncpy (temporary, scan, i);
		strcpy (temporary+i, token);
		strcpy (temporary+i+j, start);

		/// Berserker: внутри токена заменяем все пробелы на 0xA0 (-96), чтобы считать токен единым словом
		for (k=i; k<i+j; k++)
			if (temporary[k]==0x20)		// if SPACE in token,
				temporary[k]=-96;		// replace SPACE with (SPACE+0x80 = -96)

		strcpy (expanded, temporary);
		scan = expanded;
		i--;

		if (++count == 100)
		{
			Com_Printf ("^3Macro expansion loop, discarded.\n");
			return NULL;
		}
	}

	if (inquote)
	{
		Com_Printf ("^3Line has unmatched quote, discarded.\n");
		return NULL;
	}

	return scan;
}


/*
============
Cmd_TokenizeString

Parses the given string into command line tokens.
$Cvars will be expanded unless they are in a quoted token
============
*/
void Cmd_TokenizeString (char *text, bool macroExpand)
{
	int		i, k, l;
	char	*com_token;

// clear the args from the last string
	for (i=0 ; i<cmd_argc ; i++)
		Z_Free (cmd_argv[i]);
		
	cmd_argc = 0;
	cmd_args[0] = 0;
	
	// macro expand the text
	if (macroExpand)
		text = Cmd_MacroExpandString (text);
	if (!text)
		return;

	while (1)
	{
// skip whitespace up to a /n
		while (*text && *text <= ' ' && *text != '\n' && *text != -96)
			text++;
		
		if (*text == '\n')
		{	// a newline separates commands in the buffer
			text++;
			break;
		}

		if (!*text)
			return;

		// set cmd_args to everything after the first arg
		if (cmd_argc == 1)
		{
			strcpy (cmd_args, text);
			/// Berserker: теперь восстановим токен, заменив все 0xA0 (-96) на пробелы
			if (macroExpand)
			{
				l = strlen(cmd_args);
				for (k=0; k<l; k++)
					if (cmd_args[k]==-96)
						cmd_args[k]=0x20;
			}

			// strip off any trailing whitespace
			l = strlen(cmd_args) - 1;
			for ( ; l >= 0 ; l--)
				if (cmd_args[l] <= ' ')
					cmd_args[l] = 0;
				else
					break;
		}

		com_token = COM_Parse (&text);
		if (!text)
			return;

		if (cmd_argc < MAX_STRING_TOKENS)
		{
			char *str;
			l = strlen(com_token);
			str = cmd_argv[cmd_argc] = (char*) Z_Malloc (l+1, true);
			strcpy (str, com_token);
			/// Berserker: теперь восстановим токен, заменив все 0xA0 (-96) на пробелы
			if (macroExpand)
			{
				for (k=0; k<l; k++)
					if (str[k]==-96)
						str[k]=0x20;
			}
			cmd_argc++;
		}
	}
}


/*
============
Cbuf_InsertText

Adds command text immediately after the current command
Adds a \n to the text
FIXME: actually change the command buffer to do less copying
============
*/
void Cbuf_InsertText (char *text)
{
	char	*temp;
	int		templen;

// copy off any commands still remaining in the exec buffer
	templen = cmd_text.cursize;
	if (templen)
	{
		temp = (char*) Z_Malloc (templen, true);
		Memcpy (temp, cmd_text.data, templen);
		SZ_Clear (&cmd_text);
	}
	else
		temp = NULL;	// shut up compiler
		
// add the entire text of the file
	Cbuf_AddText (text);
	
// add the copied off data
	if (templen)
	{
		SZ_Write (&cmd_text, temp, templen);
		Z_Free (temp);
	}
}


static bool Cvar_InfoValidate (char *s)
{
	if (strstr (s, "\\"))
		return false;
	if (strstr (s, "\""))
		return false;
	if (strstr (s, ";"))
		return false;
	return true;
}




/*
============
Cvar_Get

If the variable already exists, the value will not be set
The flags will be or'ed in if the variable exists.
============
*/
cvar_t *Cvar_Get (char *var_name, char *var_value, int flags)
{
	cvar_t	*var;
	
	if (flags & (CVAR_USERINFO | CVAR_SERVERINFO))
	{
		if (!Cvar_InfoValidate (var_name))
		{
			Com_Printf("^1invalid info cvar name\n");
			return NULL;
		}
	}

	var = Cvar_FindVar (var_name);
	if (var)
	{
		if(var->defaultString)
		{
			Z_Free(var->defaultString);
			var->defaultString = NULL;
		}
		if(var_value)
		{
			var->defaultString = (char*) Z_Malloc (strlen(var_value)+1, true);
			var->defaultString = CopyString(var_value);
		}
		var->flags |= flags;
		return var;
	}

	if (!var_value)
		return NULL;

	if (flags & (CVAR_USERINFO | CVAR_SERVERINFO))
	{
		if (!Cvar_InfoValidate (var_value))
		{
			Com_Printf("^1invalid info cvar value\n");
			return NULL;
		}
	}

	var = (struct cvar_s *) Z_Malloc (sizeof(*var), true);
	var->name = CopyString (var_name);
	var->string = CopyString (var_value);
	var->defaultString = (char*) Z_Malloc (strlen(var_value)+1, true);
	var->defaultString = CopyString(var_value);
	var->modified = true;
	var->value = atof (var->string);
	var->hash = Com_HashKey(var_name);

	// link the variable in
	var->next = cvar_vars;
	cvar_vars = var;

	var->flags = flags;
	var->help = NULL;

	return var;
}


cvar_t *Cvar_FullSet (char *var_name, char *value, int flags)
{
	cvar_t	*var;

	var = Cvar_FindVar (var_name);
	if (!var)
		return Cvar_Get (var_name, value, flags);	// create it

	var->modified = true;

	if (var->flags & CVAR_USERINFO)
		var->need_send = userinfo_modified = true;	// transmit at next oportunity

	Z_Free (var->string);	// free the old value string

	var->string = CopyString(value);
	var->value = atof (var->string);
	var->flags = flags;

	return var;
}


pack_t *FS_LoadPackFile (char *packfile, bool isPK2)
{
	pack_t			*pack;
	FILE			*packhandle;
	int				numpackfiles;
	packfile_t		*newfiles;

	packhandle = FS_Fopen(packfile, "rb");
	if (!packhandle)
		return NULL;

	if (!isPK2)
	{
		int				i;
		dpackheader_t	header;
		dpackfile_t		info[MAX_FILES_IN_PACK];

		fread (&header, 1, sizeof(header), packhandle);
		if (LittleLong(header.ident) != IDPAKHEADER)
			Com_Error (ERR_FATAL, "%s is not a packfile", packfile);
		header.dirofs = LittleLong (header.dirofs);
		header.dirlen = LittleLong (header.dirlen);

		numpackfiles = header.dirlen / sizeof(dpackfile_t);

		if (numpackfiles > MAX_FILES_IN_PACK)
			Com_Error (ERR_FATAL, "%s has %i files", packfile, numpackfiles);

		newfiles = (packfile_t*)Z_Malloc (numpackfiles * sizeof(packfile_t), true);

		fseek (packhandle, header.dirofs, SEEK_SET);
		fread (info, 1, header.dirlen, packhandle);

		// parse the directory
		for (i=0 ; i<numpackfiles ; i++)
		{
			strcpy (newfiles[i].name, info[i].name);
			newfiles[i].hash = Com_HashKey(newfiles[i].name);
			newfiles[i].filepos = LittleLong(info[i].filepos);
			newfiles[i].filelen = LittleLong(info[i].filelen);
		}
		Com_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
	}
	else
	{
		numpackfiles = 0;
		newfiles = NULL;
		Com_Printf ("Added packfile %s", packfile);

		int	i, j = -1;
		for(i=0; i<MAX_PAKS; i++)
		{
			if(ZipCache[i].uf)
			{
				if (!Q_strcasecmp(ZipCache[i].pak_name, packfile))
					break;
			}
			else
			{
				if (j == -1)
				{
					j = i;
					break;
				}
			}
		}
		if (j != -1)
		{
			Com_Printf (", caching... ");
			strcpy(&ZipCache[j].pak_name[0], packfile);
			if (!PackFileOpen (&ZipCache[j]))
				Com_Error(ERR_FATAL, "Error opening pk2-file: %s", packfile);
			Com_Printf ("Done (%i files)", PackFileGetFilesNumber(&ZipCache[j]));
		}
		Com_Printf ("\n");
	}

	pack = (pack_s *) Z_Malloc (sizeof (pack_t), true);
	pack->isPK2 = isPK2;
	strcpy (pack->filename, packfile);
	pack->handle = packhandle;
	pack->numfiles = numpackfiles;
	pack->files = newfiles;
	
	return pack;
}


/*
 =================
 Q_GlobMatchAfterStar

 Like Q_GlobMatch, but match pattern against any final segment of text
 =================
*/
bool Q_GlobMatch (const char *pattern, const char *text, bool caseSensitive);
static bool Q_GlobMatchAfterStar (const char *pattern, const char *text, bool caseSensitive)
{
	const char	*p = pattern;
	const char	*t = text;
	char		c1, c2;

	while ((c1 = *p++) == '?' || c1 == '*')
	{
		if (c1 == '?' && *t++ == '\0')
			return false;
	}

	if (c1 == '\0')
		return true;

	if (c1 == '\\')
		c2 = *p;
	else
		c2 = c1;

	while (1)
	{
		if (caseSensitive)
		{
			if (c1 == '[' || *t == c2)
			{
				if (Q_GlobMatch(p - 1, t, caseSensitive))
					return true;
			}
		}
		else
		{
			if (c1 == '[' || tolower(*t) == tolower(c2))
			{
				if (Q_GlobMatch(p - 1, t, caseSensitive))
					return true;
			}
		}

		if (*t++ == '\0')
			return false;
	}
}

/*
 =================
 Q_GlobMatch

 Matches the pattern against text.
 Returns true if matches, false otherwise.

 A match means the entire text is used up in matching.

 In the pattern string, '*' matches any sequence of characters, '?'
 matches any character, '[SET]' matches any character in the specified
 set, '[!SET]' matches any character not in the specified set.

 A set is composed of characters or ranges. A range looks like character
 hyphen character (as in 0-9 or A-Z).
 [0-9a-zA-Z_] is the set of characters allowed in C identifiers.
 Any other character in the pattern must be matched exactly.

 To suppress the special syntactic significance of any of '[]*?!-\', and
 match the character exactly, precede it with a '\'.
 =================
*/
bool Q_GlobMatch (const char *pattern, const char *text, bool caseSensitive)
{
	const char	*p = pattern;
	const char	*t = text;
	char		c1, c2, start, end;
	bool		invert;

	while ((c1 = *p++) != '\0')
	{
		switch (c1)
		{
		case '?':
			if (*t == '\0')
				return false;
			else
				++t;

			break;
		case '\\':
			if (caseSensitive)
			{
				if (*p++ != *t++)
					return false;
			}
			else
			{
				if (tolower(*p++) != tolower(*t++))
					return false;
			}

			break;
		case '*':
			return Q_GlobMatchAfterStar(p, t, caseSensitive);

		case '[':
			c2 = *t++;
			if (!c2)
				return false;

			invert = (*p == '!');
			if (invert)
				p++;

			c1 = *p++;
			while (1)
			{
				start = c1;
				end = c1;

				if (c1 == '\\')
				{
					start = *p++;
					end = start;
				}
				if (c1 == '\0')
					return false;

				c1 = *p++;
				if (c1 == '-' && *p != ']')
				{
					end = *p++;
					if (end == '\\')
						end = *p++;
					if (end == '\0')
						return false;

					c1 = *p++;
				}

				if (caseSensitive)
				{
					if (c2 >= start && c2 <= end)
						goto match;
				}
				else
				{
					if (tolower(c2) >= tolower(start) && tolower(c2) <= tolower(end))
						goto match;
				}

				if (c1 == ']')
					break;
			}

			if (!invert)
				return false;

			break;

match:
			while (c1 != ']')
			{
				if (c1 == '\0')
					return false;

				c1 = *p++;
				if (c1 == '\0')
					return false;
				else if (c1 == '\\')
					++p;
			}

			if (invert)
				return false;

			break;

		default:
			if (caseSensitive)
			{
				if (c1 != *t++)
					return false;
			}
			else
			{
				if (tolower(c1) != tolower(*t++))
					return false;
			}

			break;
		}
	}

	return (*t == '\0');
}


int Q_strcmp (const char *string1, const char *string2)
{
	return strncmp(string1, string2, 99999);
}

int Q_SortStrcmp (char **arg1, char **arg2)
{
	return Q_strcmp(*arg1, *arg2);
}

/*
 =================
 Sys_FindFiles

 Walks a directory adding every file and/or subdirectory whose name
 matches the specified pattern.
 The file list is sorted alphabetically.
 =================
*/
int Sys_FindFiles (const char *path, const char *pattern, char **fileList, int maxFiles, bool addFiles, bool addDirs)
{
	WIN32_FIND_DATA	findInfo;
	HANDLE			findHandle;
	bool			findRes = true;
	char			findPath[MAX_OSPATH], searchPath[MAX_OSPATH];
	int				fileCount = 0;

	Com_sprintf(searchPath, sizeof(searchPath), "%s/*", path);

	findHandle = FindFirstFile(searchPath, &findInfo);
	if (findHandle == INVALID_HANDLE_VALUE)
		return 0;

	while (findRes == true)
	{
		// Check for invalid file name
		if (findInfo.cFileName[strlen(findInfo.cFileName)-1] == '.')
		{
			findRes = FindNextFile(findHandle, &findInfo);
			continue;
		}

		// Match pattern
		if (!Q_GlobMatch(pattern, findInfo.cFileName, false))
		{
			findRes = FindNextFile(findHandle, &findInfo);
			continue;
		}

		Com_sprintf(findPath, sizeof(findPath), "%s/%s", path, findInfo.cFileName);

		int l = strlen(findPath);
		for (int j=0 ; j<l ; j++)
			findPath[j] = tolower(findPath[j]);

		if (findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			// Add a directory
			if (addDirs && (fileCount < maxFiles))
				fileList[fileCount++] = CopyString(findPath);
		}
		else
		{
			// Add a file
			if (addFiles && (fileCount < maxFiles))
				fileList[fileCount++] = CopyString(findPath);
		}

		findRes = FindNextFile(findHandle, &findInfo);
	}

	FindClose(findHandle);

	// Sort the list
	qsort(fileList, fileCount, sizeof(char *), (int (__cdecl *)(const void *,const void *))Q_SortStrcmp);

	return fileCount;
}


/*
================
FS_AddGameDirectory

Sets fs_gamedir, adds the directory to the head of the path,
then loads and adds pak00.pk2 pak01.pk2 ... pak99.pk2
================
*/
void FS_AddGameDirectory (char *dir)
{
	int				dirCount, i;
	searchpath_t	*search;
	pack_t			*pak;
	char			*dirFiles[MAX_PACK_FILES];

	strcpy (fs_gamedir, dir);

	//
	// add the directory to the search path
	//
	search = (searchpath_s *) Z_Malloc (sizeof(searchpath_t), true);
	strcpy (search->filename, dir);
	search->next = fs_searchpaths;
	fs_searchpaths = search;

	// Add any PAK files
	dirCount = Sys_FindFiles(dir, "*.pak", dirFiles, MAX_PACK_FILES, true, false);
	for (i = 0; i < dirCount; i++)
	{
		pak = FS_LoadPackFile(dirFiles[i], false);

		search = (searchpath_s *) Z_Malloc(sizeof(searchpath_t), true);
		search->pack = pak;
		search->next = fs_searchpaths;
		fs_searchpaths = search;

		Z_Free(dirFiles[i]);
	}
	// ... then add any PK2 files
	dirCount = Sys_FindFiles(dir, "*.pk2", dirFiles, MAX_PACK_FILES, true, false);
	for (i = 0; i < dirCount; i++)
	{
		pak = FS_LoadPackFile(dirFiles[i], true);

		search = (searchpath_s *) Z_Malloc(sizeof(searchpath_t), true);
		search->pack = pak;
		search->next = fs_searchpaths;
		fs_searchpaths = search;

		Z_Free(dirFiles[i]);
	}
}


/*
================
FS_SetGamedir

Sets the gamedir and path to a different directory.
================
*/
void FS_SetGamedir (char *dir)
{
	searchpath_t	*next;

	if (strstr(dir, "..") || strstr(dir, "/")
		|| strstr(dir, "\\") || strstr(dir, ":") )
	{
		Com_Printf ("^3Gamedir should be a single filename, not a path\n");
		return;
	}

	//
	// free up any current game dir info
	//
	while (fs_searchpaths != fs_base_searchpaths)
	{
		if (fs_searchpaths->pack)
		{
			fclose (fs_searchpaths->pack->handle);
			Z_Free (fs_searchpaths->pack);
		}
		next = fs_searchpaths->next;
		Z_Free (fs_searchpaths);
		fs_searchpaths = next;
	}

	//
	// flush all data, so it will be forced to reload
	//
	if (dedicated && !dedicated->value)
		Cbuf_AddText ("vid_restart\nsnd_restart\n");

	Com_sprintf (fs_gamedir, sizeof(fs_gamedir), "%s/%s", fs_basedir->string, dir);

	if (!strcmp(dir,BASEDIRNAME) || (*dir == 0))
	{
		Cvar_FullSet ("gamedir", "", CVAR_SERVERINFO|CVAR_NOSET);
		Cvar_FullSet ("game", "", CVAR_LATCH|CVAR_SERVERINFO);
	}
	else
	{
		Cvar_FullSet ("gamedir", dir, CVAR_SERVERINFO|CVAR_NOSET);
		if (fs_cddir->string[0])
			FS_AddGameDirectory (va("%s/%s", fs_cddir->string, dir) );
		FS_AddGameDirectory (va("%s/%s", fs_basedir->string, dir) );
	}
}


char	findbase[MAX_OSPATH];
char	findpath[MAX_OSPATH];
int		findhandle;

/*
============
COM_FilePath

Returns the path up to, but not including the last /
============
*/
void COM_FilePath (char *in, char *out)
{
	char *s;
	s = in + strlen(in) - 1;
	
	while (s != in && *s != '/' && *s != '\\')		// berserker: damn path names
		s--;

	strncpy (out,in, s-in);
	out[s-in] = 0;
}

/*
============
COM_SkipPath
============
*/
char *COM_SkipPath (char *pathname)
{
	char	*last;

	last = pathname;
	while (*pathname)
	{
		if (*pathname=='/' || *pathname=='\\')	// berserker: damn file name
			last = pathname+1;
		pathname++;
	}
	return last;
}


static bool CompareAttributes( unsigned found, unsigned musthave, unsigned canthave )
{
	if ( ( found & _A_RDONLY ) && ( canthave & SFF_RDONLY ) )
		return false;
	if ( ( found & _A_HIDDEN ) && ( canthave & SFF_HIDDEN ) )
		return false;
	if ( ( found & _A_SYSTEM ) && ( canthave & SFF_SYSTEM ) )
		return false;
	if ( ( found & _A_SUBDIR ) && ( canthave & SFF_SUBDIR ) )
		return false;
	if ( ( found & _A_ARCH ) && ( canthave & SFF_ARCH ) )
		return false;

	if ( ( musthave & SFF_RDONLY ) && !( found & _A_RDONLY ) )
		return false;
	if ( ( musthave & SFF_HIDDEN ) && !( found & _A_HIDDEN ) )
		return false;
	if ( ( musthave & SFF_SYSTEM ) && !( found & _A_SYSTEM ) )
		return false;
	if ( ( musthave & SFF_SUBDIR ) && !( found & _A_SUBDIR ) )
		return false;
	if ( ( musthave & SFF_ARCH ) && !( found & _A_ARCH ) )
		return false;

	return true;
}


char *Sys_FindFirst (char *path, unsigned musthave, unsigned canthave )
{
	struct _finddata_t findinfo;

	if (findhandle)
	{
		Com_Printf ("^3Sys_FindFirst without close\n");
		return NULL;
	}
///	findhandle = 0;

	COM_FilePath (path, findbase);
	findhandle = _findfirst (path, &findinfo);
	if (findhandle == -1)
		return NULL;
	if ( !CompareAttributes( findinfo.attrib, musthave, canthave ) )
		return ".";		/// was NULL   Berserker: исправим Ку2 баг - если в череде нахождений встретится неугодный элемент, то это не повод прекращать поиски...
	Com_sprintf (findpath, sizeof(findpath), "%s/%s", findbase, findinfo.name);
	return findpath;
}


void Sys_FindClose ()
{
	if (findhandle != -1)
		_findclose (findhandle);
	findhandle = 0;
}


void FS_ExecAutoexec ()
{
	char *dir;
	char name [MAX_QPATH];

	dir = Cvar_VariableString("gamedir");
	if (*dir)
		Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, dir); 
	else
		Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, BASEDIRNAME); 
	if (Sys_FindFirst(name, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM))
		Cbuf_AddText ("exec autoexec.cfg\n");
	Sys_FindClose();
}


// Berserker: purepaks.lst загружается после загрузки *.cfg.
// Поэтому, чтоб не загружать их из "левых" паков, сформируем дефолтный список.
void FS_CreateDefaultPureList()
{
	int		i, m;

	if(numpaks)
		FreePakNames();	// Если уже были определены структуры paknames, то освободим память...

	if (strcmp(fs_gamedir+2, BASEDIRNAME))	// если работает mod:
		m = 2;
	else
		m = 1;

	numpaks = 20;		// зарезервируем по 10 pak и по 10 pk2
	paknames = (char **) Z_Malloc( sizeof( char * ) * ( numpaks * m + 1 ), true );
	for ( i = 0; i < numpaks; i++ )
	{
		if (i < 10)
		{
			paknames[i] = (char *) Z_Malloc( 9, true );
			strcpy( paknames[i], va("%s/pak%1i.pak", BASEDIRNAME, i) );
		}
		else
		{
			paknames[i] = (char *) Z_Malloc( 14, true );
			strcpy( paknames[i], va("%s/_BersPak%1i.pk2", BASEDIRNAME, i-10) );
		}
	}
	if (m==2)
	{	// mod
		for ( i = 0; i < numpaks; i++ )
		{
			if (i < 10)
			{
				paknames[i+numpaks] = (char *) Z_Malloc( 9, true );
				strcpy( paknames[i+numpaks], va("%s/pak%1i.pak", FS_Gamedir(), i) );
			}
			else
			{
				paknames[i+numpaks] = (char *) Z_Malloc( 14, true );
				strcpy( paknames[i+numpaks], va("%s/_BersPak%1i.pk2", FS_Gamedir(), i-10) );
			}
		}
	}
	paknames[numpaks] = 0;
}


cvar_t *Cvar_Set2 (char *var_name, char *value, bool force)
{
	cvar_t	*var;

	var = Cvar_FindVar (var_name);
	if (!var)
	{	// create it
		return Cvar_Get (var_name, value, 0);
	}

	if (var->flags & (CVAR_USERINFO | CVAR_SERVERINFO))
	{
		if (!Cvar_InfoValidate (value))
		{
			Com_Printf("^1invalid info cvar value\n");
			return var;
		}
	}

	if (!force)
	{
		if ((var->flags & CVAR_NOSET) && !enable_to_change_readonly_cvars)
		{
			Com_Printf ("^3%s is write protected.\n", var_name);
			return var;
		}

		if (var->flags & CVAR_LATCH)
		{
			if (var->latched_string)
			{
				if (strcmp(value, var->latched_string) == 0)
					return var;
				Z_Free (var->latched_string);
			}
			else
			{
				if (strcmp(value, var->string) == 0)
					return var;
			}

			if (Com_ServerState())
			{
				if (!Q_strcasecmp(var->name, "game"))	/// Berserker: allow to instant 'game' change for dedicated server
					goto qqq;
				Com_Printf ("^3%s will be changed for next game.\n", var_name);
				var->latched_string = CopyString(value);
			}
			else
			{
				//memleak fix, thanks Maniac-
qqq:			Z_Free (var->string);
				var->string = CopyString(value);
				var->value = atof (var->string);
				if (!Q_strcasecmp(var->name, "game"))
				{
					CL_WriteConfiguration();
					s_demos_menu.cursor = 0;
					s_maps_list.curvalue = 0;
					s_startmap_list.curvalue = 0;
					KillZipCache(false);
					FS_SetGamedir (var->string);
					FS_CreateDefaultPureList();
					FS_CutNonPures();
					if (!fs_pure)
						fs_pure = Cvar_Get ("fs_pure", "1", CVAR_NOSET | CVAR_ARCHIVE);
					float old_fs_pure = fs_pure->value;
					fs_pure->value = 2;		// hack
					enable_to_change_readonly_cvars = true;
					Cbuf_AddText ("exec default.cfg\n");
					Cbuf_AddText ("exec q2b_config.cfg\n");
					FS_ExecAutoexec ();
					if (dedicated && !dedicated->value)
					{
						if (cl.attractloop)
							Cbuf_AddText ("vid_restart\nsnd_restart\n");
						else
							Cbuf_AddText ("killserver\nvid_restart\nsnd_restart\n");
					}
					Cbuf_Execute();
					fs_pure->value = old_fs_pure;	// hack
					enable_to_change_readonly_cvars = false;
					FS_LoadPureLists();
					FS_CutNonPures();
					if (Q_strcasecmp(fs_cached->string, FS_Gamedir()))
					{
						Cbuf_AddText ("cache\n");
						Cbuf_Execute ();
						Cvar_Set("fs_cached", FS_Gamedir());
					}
				}
			}
			return var;
		}

		if (var->flags & CVAR_VID_LATCH)
		{
			if (var->vid_latched_string)
			{
				if (strcmp(value, var->vid_latched_string) == 0)
					return var;
				Z_Free (var->vid_latched_string);
			}
			else
			{
				if (strcmp(value, var->string) == 0)
					return var;
			}

			if (var!=r_occlusion &&
				var!=r_diffuse_modifier &&
				var!=r_specular_modifier &&
				var!= r_parallax_scale &&
				var!=r_anisotropy &&
				var!=r_mirror_size &&
				var!=r_attenpower &&
				var!=r_texturemode &&
				var!=r_overbright &&
				var!=r_offsetFactor &&
				var!=r_offsetUnits &&
				var!=r_normcube_size)
				Com_Printf ("^3%s will be changed after vid_restart.\n", var_name);
			var->vid_latched_string = CopyString(value);
			return var;
		}
	}
	else
	{
		if (var->latched_string)
		{
			Z_Free (var->latched_string);
			var->latched_string = NULL;
		}

		if (var->vid_latched_string)
		{
			Z_Free (var->vid_latched_string);
			var->vid_latched_string = NULL;
		}
	}

	if (!strcmp(value, var->string))
		return var;		// not changed

	var->modified = true;

	if (var == cl_forcemymodel)
		cl_forcemymodel_modified = true;

	if (var->flags & CVAR_USERINFO)
		var->need_send = userinfo_modified = true;	// transmit at next oportunity

	Z_Free (var->string);	// free the old value string

	var->string = CopyString(value);
	var->value = atof (var->string);

	if (var == r_showtexture)
		hash_showtexture = Com_HashKey(r_showtexture->string);

	return var;
}


cvar_t *Cvar_ForceSet (char *var_name, char *value)
{
	return Cvar_Set2 (var_name, value, true);
}


cvar_t *Cvar_Set (char *var_name, char *value)
{
	return Cvar_Set2 (var_name, value, false);
}


void Cvar_ForceSetValue (char *var_name, float value)
{
	char	val[32];

	if (value == (int)value)
		Com_sprintf (val, sizeof(val), "%i",(int)value);
	else
		Com_sprintf (val, sizeof(val), "%f",value);
	Cvar_ForceSet (var_name, val);
}


void Cvar_SetValue (char *var_name, float value)
{
	char	val[32];

	if (value == (int)value)
		Com_sprintf (val, sizeof(val), "%i",(int)value);
	else
		Com_sprintf (val, sizeof(val), "%f",value);
	Cvar_Set (var_name, val);
}


/*
============
Cvar_Command

Handles variable inspection and changing from the console
============
*/
bool Cvar_Command ()
{
	cvar_t			*v;

// check variables
	v = Cvar_FindVar (Cmd_Argv(0));
	if (!v)
		return false;

// perform a variable print or set
	if (Cmd_Argc() == 1)
	{
		Com_Printf ("\"%s\" is \"%s\"", v->name, v->string);

		if(v->defaultString)
			Com_Printf (" default: \"%s\"", v->defaultString);

		if(v->latched_string)
			Com_Printf (" latched: \"%s\"", v->latched_string);

		if(v->vid_latched_string)
			Com_Printf (" vid_latched: \"%s\"", v->vid_latched_string);

		if(v->flags & CVAR_ARCHIVE)
			Com_Printf (" ARCH");

		if(v->flags & CVAR_USERINFO)
			Com_Printf (" USER");

		if(v->flags & CVAR_SERVERINFO)
			Com_Printf (" SERV");

		if(v->flags & CVAR_NOSET)
			Com_Printf (" NOSET");

		if(v->flags & CVAR_LATCH)
			Com_Printf (" LATCH");

		if(v->flags & CVAR_VID_LATCH)
			Com_Printf (" VIDLATCH");

		Com_Printf ("\n");
		return true;
	}

	Cvar_Set (v->name, Cmd_Argv(1));
	return true;
}


/*
============
Cvar_Toggle_f

Toggles a cvar for easy single key binding
============
*/
void Cvar_Toggle_f()
{
	char	*vs;
	int		v, i, vars = Cmd_Argc();

	if ( vars == 1 )
	{
		Com_Printf ("^3Usage: toggle <variable>\n");
		Com_Printf ("^3       toggle <variable> <str1> <str2> ... <strN>\n");
		return;
	}

	if ( vars == 2 )	// Quake3 style
	{
		v = Cvar_VariableValue(Cmd_Argv(1));
		Cvar_Set2(Cmd_Argv(1), va("%i", !v), false);
	}
	else				// Doom3 style
	{
		vs = Cvar_VariableString(Cmd_Argv(1));
		for ( i = 2; i < vars; i++ )
		{
			if (!Q_stricmp(Cmd_Argv(i), vs))
			{
				if (i==vars-1)	// Загрузим первое значение
					Cvar_Set2(Cmd_Argv(1), Cmd_Argv(2), false);
				else	// Загрузим следующее значение.
					Cvar_Set2(Cmd_Argv(1), Cmd_Argv(i+1), false);
				return;
			}
		}
		if (i == vars)	// Текущее значение не в предлагаемом списке: загрузим первое.
			Cvar_Set2(Cmd_Argv(1), Cmd_Argv(2), false);
	}
}


void Cvar_Modify_f()
{
	cvar_t	*var;

	if ( Cmd_Argc() != 2 )
	{
		Com_Printf ("^3Usage: modify <cvarName>\n");
		return;
	}

	var = Cvar_FindVar(Cmd_Argv(1));
	if (!var)
	{
		Com_Printf ("^3cvar not found\n");
		return;
	}

	var->modified = true;
}

void Cvar_Abs_f()
{
	if (Cmd_Argc() != 2 )
	{
		Com_Printf ("^3Usage: abs <variable>\n");
		return;
	}
	Cvar_SetValue(Cmd_Argv(1), fabs(Cvar_VariableValue(Cmd_Argv(1))));
}

void Cvar_Change_f()
{
	float	val;
	if (Cmd_Argc() != 3 )
	{
		Com_Printf ("^3Usage: %s <variable> <n>\n", Cmd_Argv(0));
		return;
	}
	val = atof(Cmd_Argv(2));
	if (!Q_strcasecmp(Cmd_Argv(0), "add"))
		Cvar_SetValue(Cmd_Argv(1), Cvar_VariableValue(Cmd_Argv(1)) + val);
	else if (!Q_strcasecmp(Cmd_Argv(0), "sub"))
		Cvar_SetValue(Cmd_Argv(1), Cvar_VariableValue(Cmd_Argv(1)) - val);
	else if (!Q_strcasecmp(Cmd_Argv(0), "mul"))
		Cvar_SetValue(Cmd_Argv(1), Cvar_VariableValue(Cmd_Argv(1)) * val);
	else/// if (!Q_strcasecmp(Cmd_Argv(0), "div"))
	{
		if (val)
			Cvar_SetValue(Cmd_Argv(1), Cvar_VariableValue(Cmd_Argv(1)) / val);
		else
			Com_Printf ("^1Division by zero\n");
	}
}


void Cvar_Reset_f()
{
	cvar_t	*var;

	if (Cmd_Argc () != 2)
	{
		Com_Printf ("^3Usage: reset <variable>\n");
		return;
	}

	var = Cvar_FindVar (Cmd_Argv (1));
	if (!var)
	{
		Com_Printf ("^3'%s' is not a registered cvar\n", Cmd_Argv (1));
		return;
	}

	if(var->defaultString)
		Cvar_Set (var->name, var->defaultString);
}


/*
===================
Cmd_ForwardToServer

adds the current command line as a clc_stringcmd to the client message.
things like godmode, noclip, etc, are commands directed to the server,
so when they are typed in at the console, they will need to be forwarded.
===================
*/
void Cmd_ForwardToServer ()
{
	char	*cmd;

	cmd = Cmd_Argv(0);
	if (cls.state <= ca_connected || *cmd == '-' || *cmd == '+')
	{
		Com_Printf ("^1Unknown command \"%s\"\n", cmd);
		return;
	}

	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
	SZ_Print (&cls.netchan.message, cmd);
	if (Cmd_Argc() > 1)
	{
		SZ_Print (&cls.netchan.message, " ");
		SZ_Print (&cls.netchan.message, Cmd_Args());
	}
}


/*
============
Cmd_ExecuteString

A complete command line has been parsed, so try to execute it
FIXME: lookupnoadd the token to speed search?
============
*/
void	Cmd_ExecuteString (char *text)
{	
	cmd_function_t	*cmd;
	cmdalias_t		*a;

	Cmd_TokenizeString (text, true);
			
	// execute the command line
	if (!Cmd_Argc())
		return;		// no tokens

	unsigned	hash = Com_HashKey(cmd_argv[0]);
	// check functions
	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
	{
		if (hash == cmd->hash)
		{
			if (!Q_strcasecmp(cmd->name, cmd_argv[0]))
			{
				if (!cmd->function)
					Cmd_ExecuteString (va("cmd %s", text));	// forward to server command
				else
					cmd->function ();
				return;
			}
		}
	}

	// check alias
	for (a=cmd_alias ; a ; a=a->next)
	{
		if (hash == a->hash)
		{
			if (!Q_strcasecmp(a->name, cmd_argv[0]))
			{
				if (++alias_count == ALIAS_LOOP_COUNT)
				{
					Com_Printf ("^3ALIAS_LOOP_COUNT\n");
					return;
				}
				Cbuf_InsertText (a->value);
				return;
			}
		}
	}

	// check cvars
	if (Cvar_Command ())
		return;

	// send it as a server command if we are connected
	Cmd_ForwardToServer ();
}


void Cbuf_Execute ()
{
	int		i;
	char	*text;
	char	line[1024];
	int		quotes;

	alias_count = 0;		// don't allow infinite alias loops

	while (cmd_text.cursize)
	{
		if( cmd_wait != -1)
			if( cmd_wait && (cmd_wait > sys_time) )
				break;

// find a \n or ; line break
		text = (char *)cmd_text.data;

		quotes = 0;
		for (i=0 ; i< cmd_text.cursize ; i++)
		{
			if (text[i] == '"')
				quotes++;
			if ( !(quotes&1) &&  text[i] == ';')
				break;	// don't break if inside a quoted string
			if (text[i] == '\n')
				break;
		}

		Memcpy (line, text, i);
		line[i] = 0;

// delete the text from the command buffer and move remaining commands down
// this is necessary because commands (exec, alias) can insert data at the
// beginning of the text buffer

		if (i == cmd_text.cursize)
			cmd_text.cursize = 0;
		else
		{
			i++;
			cmd_text.cursize -= i;
			memmove (text, text+i, cmd_text.cursize);
		}

// execute the command line
		Cmd_ExecuteString (line);

		if( cmd_wait == -1 )
		{
			// skip out while text still remains in buffer, leaving it
			// for next frame
			cmd_wait = 0;
			break;
		}
	}
}




void ParseCommandLine (LPSTR lpCmdLine)
{
	argc = 1;
	argv[0] = "exe";

	while (*lpCmdLine && (argc < MAX_NUM_ARGVS))
	{
		while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))
			lpCmdLine++;

		if (*lpCmdLine)
		{
			argv[argc] = lpCmdLine;
			argc++;

			while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))
				lpCmdLine++;

			if (*lpCmdLine)
			{
				*lpCmdLine = 0;
				lpCmdLine++;
			}
			
		}
	}

}


void COM_InitArgv (int argc, char **argv)
{
	int		i;

	if (argc > MAX_NUM_ARGVS)
		Com_Error (ERR_FATAL, "argc > MAX_NUM_ARGVS");
	com_argc = argc;
	for (i=0 ; i<argc ; i++)
	{
		if (!argv[i] || strlen(argv[i]) >= MAX_TOKEN_CHARS )
			com_argv[i] = "";
		else
			com_argv[i] = argv[i];
	}
}


void Cbuf_Init ()
{
	SZ_Init (&cmd_text, &cmd_text_buf[0], sizeof(cmd_text_buf));
}


/*
================
FS_NextPath

Allows enumerating all of the directories in the search path
================
*/
char *FS_NextPath (char *prevpath)
{
	searchpath_t	*s;
	char			*prev;

	if (!prevpath)
		return fs_gamedir;

	prev = fs_gamedir;
	for (s=fs_searchpaths ; s ; s=s->next)
	{
		if (s->pack)
			continue;
		if (prevpath == prev)
			return s->filename;
		prev = s->filename;
	}

	return NULL;
}


char *Sys_FindNext ( unsigned musthave, unsigned canthave )
{
	struct _finddata_t findinfo;

	if (findhandle == -1)
		return NULL;
	if (_findnext (findhandle, &findinfo) == -1)
		return NULL;
	if ( !CompareAttributes( findinfo.attrib, musthave, canthave ) )
		return ".";		/// was NULL   Berserker: исправим Ку2 баг - если в череде нахождений встретится неугодный элемент, то это не повод прекращать поиски...

	Com_sprintf (findpath, sizeof(findpath), "%s/%s", findbase, findinfo.name);
	return findpath;
}


/*
** FS_ListFiles
*/
char **FS_ListFiles( char *findname, int *numfiles, unsigned musthave, unsigned canthave )
{
	char *s;
	int nfiles = 0;
	char **list = 0;

	s = Sys_FindFirst( findname, musthave, canthave );
	while ( s )
	{
		if ( s[strlen(s)-1] != '.' )
			nfiles++;
		s = Sys_FindNext( musthave, canthave );
	}
	Sys_FindClose ();

	if ( !nfiles )
		return NULL;

	nfiles++; // add space for a guard
	*numfiles = nfiles;

	list = (char**) malloc( sizeof( char * ) * nfiles );
	Memset( list, 0, sizeof( char * ) * nfiles );

	s = Sys_FindFirst( findname, musthave, canthave );
	nfiles = 0;
	while ( s )
	{
		if ( s[strlen(s)-1] != '.' )
		{
			list[nfiles] = strdup( s );
			strlwr( list[nfiles] );
			nfiles++;
		}
		s = Sys_FindNext( musthave, canthave );
	}
	Sys_FindClose ();

	return list;
}


// Berserker: fixed and improved
void FS_Dir_f ()
{
	char	*path = NULL;
	char	findname[1024];
	char	wildcard[1024] = "*.*";
	char	**dirnames;
	int		i, ndirs;

	if ( Cmd_Argc() != 1 )
		strcpy( wildcard, Cmd_Argv( 1 ) );

	if ( ( path = FS_NextPath( NULL ) ) != NULL )
	{
		char *tmp = findname;
		Com_sprintf( findname, sizeof(findname), "%s/%s", path, wildcard );
		while ( *tmp != 0 )
		{
			if ( *tmp == '\\' )
				*tmp = '/';
			tmp++;
		}
		Com_Printf( "Directory of %s\n", findname );
		Com_Printf( "-----------------------------\n" );

		if ( ( dirnames = FS_ListFiles( findname, &ndirs, SFF_SUBDIR, 0 ) ) != 0 )
		{
			for ( i = 0; i < ndirs-1; i++ )
			{
				tmp = dirnames[i];
				while ( *tmp != 0 )
				{
					if ( *tmp == '\\' )
						*tmp = '/';
					tmp++;
				}
				if ( strrchr( dirnames[i], '/' ) )
					Com_Printf( "<DIR> %s\n", strrchr( dirnames[i], '/' ) + 1 );
				else
					Com_Printf( "<DIR> %s\n", dirnames[i] );

				free( dirnames[i] );
			}
			free( dirnames );
		}

		if ( ( dirnames = FS_ListFiles( findname, &ndirs, 0, SFF_SUBDIR ) ) != 0 )
		{
			for ( i = 0; i < ndirs-1; i++ )
			{
				tmp = dirnames[i];
				while ( *tmp != 0 )
				{
					if ( *tmp == '\\' )
						*tmp = '/';
					tmp++;
				}
				if ( strrchr( dirnames[i], '/' ) )
					Com_Printf( "      %s\n", strrchr( dirnames[i], '/' ) + 1 );
				else
					Com_Printf( "      %s\n", dirnames[i] );

				free( dirnames[i] );
			}
			free( dirnames );
		}

		Com_Printf( "\n" );
	}
}


void FS_Path_f ()
{
	searchpath_t	*s;
///	filelink_t		*l;

	if(fs_searchpaths)
	{
		Com_Printf ("Current search path:\n--------------------\n");
		for (s=fs_searchpaths ; s ; s=s->next)
		{
			if ((s == fs_base_searchpaths) && (s != fs_searchpaths))
				Com_Printf ("--------------------\n");
			if (s->pack)
			{
				if (!s->disabled)
				{
					if (s->pack->isPK2)
						Com_Printf ("%s \n", s->pack->filename);
					else
						Com_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
				}
			}
			else
				Com_Printf ("%s\n", s->filename);
		}
	}

/*	if(fs_links)
	{
		Com_Printf ("\nLinks:\n");
		for (l=fs_links ; l ; l=l->next)
			Com_Printf ("%s : %s\n", l->from, l->to);
	}*/

	Com_Printf ("\n");
}


/*
================
FS_Link_f

Creates a filelink_t
================
*/
/*
void FS_Link_f ()
{
	filelink_t	*l, **prev;

	if (Cmd_Argc() != 3)
	{
		Com_Printf ("USAGE: link <from> <to>\n");
		return;
	}

	// see if the link already exists
	prev = &fs_links;
	for (l=fs_links ; l ; l=l->next)
	{
		if (!strcmp (l->from, Cmd_Argv(1)))
		{
			Z_Free (l->to);
			if (!strlen(Cmd_Argv(2)))
			{	// delete it
				*prev = l->next;
				Z_Free (l->from);
				Z_Free (l);
				return;
			}
			l->to = CopyString (Cmd_Argv(2));
			return;
		}
		prev = &l->next;
	}

	// create a new link
	l = (filelink_s *) Z_Malloc(sizeof(*l), true);
	l->next = fs_links;
	fs_links = l;
	l->from = CopyString(Cmd_Argv(1));
	l->fromlength = strlen(l->from);
	l->to = CopyString(Cmd_Argv(2));
}
*/


void	FS_LoadPureList(char *dir, int idx)
{
	char	*buffer;
	char	paksname[MAX_QPATH];
	char	*s;
	int		length;
	int		i, cnt;
	FILE	*fp;

	numpaks_[idx] = 0;

	/*
	** load the list of pak names
	*/
	Com_sprintf( paksname, sizeof( paksname ), "%s/purepaks.lst", dir );
	if ( ( fp = FS_Fopen( paksname, "rb" ) ) == 0 )
	{
		Com_Printf( "^1Couldn't find %s\n", paksname );
		return;
	}
	else
	{
		length = filelength( fileno( fp  ) );
		buffer = (char *) Z_Malloc( length+3, true );
		fread( buffer, length, 1, fp );
		buffer[length] = 13;
		buffer[length+1] = 10;
		buffer[length+2] = 0;
		length+=3;
	}

	s = buffer;
	i = 0;
	while ( i < length )
	{
		if ( s[i] == '\r' )
			numpaks_[idx]++;
		i++;
	}

	if ( numpaks_[idx] == 0 )
	{
		Com_Printf( "^1No paks in %s\n", paksname );
		Z_Free( buffer );
		return;
	}

	paknames_[idx] = (char **) Z_Malloc( sizeof( char * ) * ( numpaks_[idx] + 1 ), true );
///	Memset( paknames_[idx], 0, sizeof( char * ) * ( numpaks_[idx] + 1 ) );

	s = buffer;

	for ( cnt = 0, i = 0; i < numpaks_[idx]; i++ )
	{
		char	shortname[MAX_QPATH];
		char	scratch[MAX_QPATH+MAX_QPATH];

		strcpy( shortname, COM_Parse( &s ) );

		if (strlen(shortname))
		{
			Com_sprintf( scratch, sizeof( scratch ), "%s/%s", dir, shortname );
			paknames_[idx][cnt] = (char *) Z_Malloc( strlen( scratch ) + 1, true );
			strcpy( paknames_[idx][cnt], scratch );
			cnt++;
		}
	}
	numpaks_[idx] = cnt;
	paknames_[idx][cnt] = 0;

	fp = 0;
	Z_Free( buffer );
}


void	FS_LoadPureLists()
{
	if (!fs_pure->value)
		return;

	if(numpaks)
		FreePakNames();	// Если уже были определены структуры paknames, то освободим память...

	FS_LoadPureList(BASEDIRNAME, 0);
	if (fs_pure->value == 2)
		if (strcmp(fs_gamedir+2, BASEDIRNAME))	// если работает mod:
			FS_LoadPureList(fs_gamedir, 1);

	// Объединим списки
	int i, j;
	paknames = (char **) Z_Malloc( sizeof( char * ) * ( numpaks_[0] + numpaks_[1] + 1 ), true );
///	Memset( paknames, 0, sizeof( char * ) * ( numpaks_[0] + numpaks_[1] + 1 ) );
	for (j=0; j<2; j++)
		for ( i = 0; i < numpaks_[j]; i++ )
		{
			paknames[numpaks] = (char *) Z_Malloc( strlen( paknames_[j][i] ) + 1, true );
			strcpy( paknames[numpaks], paknames_[j][i] );
			numpaks++;
		}

	paknames[numpaks] = 0;

	FreePakNames_();	// Если уже были определены структуры paknames, то освободим память...
}


void	FS_CutNonPures()
{
	if (!numpaks)
		return;

	int				i;
	char			*s1, *s2;
	searchpath_t	*search, *prev = NULL;

	for (search = fs_searchpaths ; search ; prev = search, search = search->next)
	{
		search->disabled = false;
		// is the element a pak file?
		if (search->pack)
		{
			if (fs_pure->value == 1)	// фильтруем только BaseQ2
				if (Q_strncasecmp(search->pack->filename+2, BASEDIRNAME, 6))	// from mod	(6 = length("baseq2"))
					continue;

			s1=search->pack->filename;
			if (s1[0]=='.' && (s1[1]=='/' || s1[1]=='\\'))		// berserker: damn path names
				s1+=2;

			for (i=0; i<numpaks; i++)
			{
				s2=paknames[i];
				if (s2[0]=='.' && (s2[1]=='/' || s2[1]=='\\'))	// berserker: damn path names
					s2+=2;

				if (!Q_strcasecmp(s1, s2))
					break;
			}

			if (i==numpaks)
				search->disabled = true;
		}
	}
}


void FS_ResetFilesystem_f ()
{
	fs_cache_number = 0;
	Memset(fs_cache, 0, sizeof(fs_cache));
}

void FS_InitFilesystem ()
{
	FS_ResetFilesystem_f();

	Cmd_AddCommand ("path", FS_Path_f);
///	Cmd_AddCommand ("link", FS_Link_f);
	Cmd_AddCommand ("dir", FS_Dir_f );
	Cmd_AddCommand ("filelist", FS_FileList_f );
	Cmd_AddCommand ("fs_reset", FS_ResetFilesystem_f );

	//
	// basedir <path>
	// allows the game to run from outside the data tree
	//
	fs_basedir = Cvar_Get ("basedir", ".", CVAR_NOSET);
	fs_pure = Cvar_Get ("fs_pure", "1", CVAR_NOSET | CVAR_ARCHIVE);
	fs_pure->help = "0 - load all paks, 1 - load the paks listed in purepaks.lst only (for BaseQ2) and all paks (for other folders), 2 - load the paks listed in purepaks.lst only (for any folders).";

	//
	// cddir <path>
	// Logically concatenates the cddir after the basedir for 
	// allows the game to run from outside the data tree
	//
	fs_cddir = Cvar_Get ("cddir", "", CVAR_NOSET);
	fs_cddir->help = "allow to use CD data.";
	if (fs_cddir->string[0])
		FS_AddGameDirectory (va("%s/%s", fs_cddir->string, BASEDIRNAME) );

	//
	// start up with BaseQ2 by default
	//
	FS_AddGameDirectory (va("%s/%s", fs_basedir->string, BASEDIRNAME) );

	// any set gamedirs will be freed up to here
	fs_base_searchpaths = fs_searchpaths;

	// check for game override
	fs_gamedirvar = Cvar_Get ("game", "", CVAR_LATCH|CVAR_SERVERINFO);
	if (fs_gamedirvar->string[0])
		FS_SetGamedir (fs_gamedirvar->string);
}


int COM_Argc ()
{
	return com_argc;
}

char *COM_Argv (int arg)
{
	if (arg < 0 || arg >= com_argc || !com_argv[arg])
		return "";
	return com_argv[arg];
}

void COM_ClearArgv (int arg)
{
	if (arg < 0 || arg >= com_argc || !com_argv[arg])
		return;
	com_argv[arg] = "";
}


/*
===============
Cbuf_AddEarlyCommands

Adds command line parameters as script statements
Commands lead with a +, and continue until another +

Set commands are added early, so they are guaranteed to be set before
the client and server initialize for the first time.

Other commands are added late, after all initialization is complete.
===============
*/
void Cbuf_AddEarlyCommands (bool clear)
{
	int		i;
	char	*s;

	for (i=0 ; i<COM_Argc() ; i++)
	{
		s = COM_Argv(i);
		//// Berserker: avoid the spaces in "+ set"
		if (!strncmp(s, "+", 1) && !Q_strncasecmp (COM_Argv(i+1), "set", 3))
		{
			COM_ClearArgv(i);
			i++;
			goto next;
		}
		////
		if (Q_strcasecmp (s, "+set"))
			continue;
next:	Cbuf_AddText (va("set %s %s\n", COM_Argv(i+1), COM_Argv(i+2)));
		if (clear)
		{
			COM_ClearArgv(i);
			COM_ClearArgv(i+1);
			COM_ClearArgv(i+2);
		}
		i+=2;
	}
}


/*
=============
Com_Quit

Both client and server can use this, and it will
do the apropriate things.
=============
*/
void Com_Quit ()
{
	SV_Shutdown ("Server quit\n", false);
	CL_Shutdown ();

	if (logfile)
	{
		fclose (logfile);
		logfile = NULL;
	}

	Sys_Quit ();
}


int		ccom_argc;
char	**ccom_argv;

/*
================
CCheckParm

Returns the position (1 to argc-1) in the program's argument list
where the given parameter apears, or 0 if not present
================
*/
int CCheckParm (char *parm)
{
	int             i;
	
	for (i=1 ; i<ccom_argc ; i++)
	{
		if (!ccom_argv[i])
			continue;
		if (!strcmp (parm,ccom_argv[i]))
			return i;
	}
		
	return 0;
}


/*
=================
Q_strncatz
Safe strncat that ensures a trailing zero
=================
*/
void Q_strncatz (char *dst, const char *src, int dstSize)
{
	if (!dst)
		Com_Error(ERR_FATAL, "Q_strncatz: NULL dst");

	if (!src)
		Com_Error(ERR_FATAL, "Q_strncatz: NULL src");

	if (dstSize < 1)
		Com_Error(ERR_FATAL, "Q_strncatz: dstSize < 1");

	while (--dstSize && *dst)
		dst++;

	if (dstSize > 0)
	{
		while (--dstSize && *src)
			*dst++ = *src++;

		*dst = 0;
	}
}


/*
 =================
 Sys_DetectCPU
 =================
*/
bool Sys_DetectCPU (char *cpuString, int maxSize)
{

#if defined _M_IX86

	char				vendor[16];
	int					stdBits, features, extFeatures;
	int					family, model;
	unsigned __int64	start, end, counter, stop, frequency;
	unsigned			speed;
	bool				hasMMX, hasMMXExt, has3DNow, has3DNowExt, hasSSE, hasSSE2;

	// Check if CPUID instruction is supported
	__try
	{
		__asm
		{
			xor eax, eax
			cpuid
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return false;
	}

	// Get CPU info
	__asm
	{
		; // Get vendor identifier
		sub eax, eax
		cpuid
		mov dword ptr[vendor+0], ebx
		mov dword ptr[vendor+4], edx
		mov dword ptr[vendor+8], ecx
		mov dword ptr[vendor+12], 0

		; // Get standard bits and features
		mov eax, 1
		cpuid
		mov stdBits, eax
		mov features, edx

		; // Check if extended functions are present
		mov extFeatures, 0
		mov eax, 80000000h
		cpuid
		cmp eax, 80000000h
		jbe NoExtFunction

		; // Get extended features
		mov eax, 80000001h
		cpuid
		mov extFeatures, edx

NoExtFunction:
	}

	// Get CPU name
	family = (stdBits >> 8) & 15;
	model = (stdBits >> 4) & 15;

	if (!Q_strcasecmp(vendor, "AuthenticAMD"))
	{
		Q_strncpyz(cpuString, "AMD", maxSize);

		switch (family)
		{
		case 5:
			switch (model)
			{
			case 0:
			case 1:
			case 2:
			case 3:
				Q_strncatz(cpuString, " K5", maxSize);
				break;
			case 6:
			case 7:
				Q_strncatz(cpuString, " K6", maxSize);
				break;
			case 8:
				Q_strncatz(cpuString, " K6-2", maxSize);
				break;
			case 9:
			case 10:
			case 11:
			case 12:
			case 13:
			case 14:
			case 15:
				Q_strncatz(cpuString, " K6-III", maxSize);
				break;
			}
			break;
		case 6:
			switch (model)
			{
			case 1:
				Q_strncatz(cpuString, " Athlon (0.25 core)", maxSize);
				break;
			case 2:
				Q_strncatz(cpuString, " Athlon (0.18 core)", maxSize);
				break;
			case 3:
				Q_strncatz(cpuString, " Duron Spitfire", maxSize);
				break;
			case 4:
				Q_strncatz(cpuString, " Athlon Thunderbird", maxSize);
				break;
			case 6:
				Q_strncatz(cpuString, " Athlon Palomino", maxSize);
				break;
			case 7:
				Q_strncatz(cpuString, " Duron Morgan", maxSize);
				break;
			case 8:
				Q_strncatz(cpuString, " Athlon Thoroughbred", maxSize);
				break;
			case 10:
				Q_strncatz(cpuString, " Athlon Barton", maxSize);
				break;
			}
			break;
		}
	}
	else if (!Q_strcasecmp(vendor, "GenuineIntel"))
	{
		Q_strncpyz(cpuString, "Intel", maxSize);

		switch (family)
		{
		case 5:
			switch (model)
			{
			case 0:
			case 1:
			case 2:
			case 3:
			case 4:
			case 7:
			case 8:
				Q_strncatz(cpuString, " Pentium", maxSize);
				break;
			}
			break;
		case 6:
			switch (model)
			{
			case 0:
			case 1:
				Q_strncatz(cpuString, " Pentium Pro", maxSize);
				break;
			case 3:
			case 5:		// Actual differentiation depends on cache settings
				Q_strncatz(cpuString, " Pentium II", maxSize);
				break;
			case 6:
				Q_strncatz(cpuString, " Celeron", maxSize);
				break;
			case 7:
			case 8:
			case 9:
			case 10:
			case 11:	// Actual differentiation depends on cache settings
				Q_strncatz(cpuString, " Pentium III", maxSize);
				break;
			}
			break;
		case 15:
			Q_strncatz(cpuString, " Pentium 4", maxSize);
			break;
		}
	}
	else
		return false;

	// Check if RDTSC instruction is supported
	if ((features >> 4) & 1)
	{
		// Measure CPU speed
		QueryPerformanceFrequency((LARGE_INTEGER *)&frequency);

		__asm {
			rdtsc
			mov dword ptr[start+0], eax
			mov dword ptr[start+4], edx
		}

		QueryPerformanceCounter((LARGE_INTEGER *)&stop);
		stop += frequency;

		do
		{
			QueryPerformanceCounter((LARGE_INTEGER *)&counter);
		}
		while (counter < stop);

		__asm
		{
			rdtsc
			mov dword ptr[end+0], eax
			mov dword ptr[end+4], edx
		}

		speed = (unsigned)((end - start) / 1000000);

		Q_strncatz(cpuString, va(" %u MHz", speed), maxSize);
	}

	// Get extended instruction sets supported
	hasMMX = (features >> 23) & 1;
	hasMMXExt = (extFeatures >> 22) & 1;
	has3DNow = (extFeatures >> 31) & 1;
	has3DNowExt = (extFeatures >> 30) & 1;
	hasSSE = (features >> 25) & 1;
	hasSSE2 = (features >> 26) & 1;

	cpu_mmx = cpu_mmxe = cpu_3dnow = cpu_3dnowe = cpu_sse = cpu_sse2 = false;

	if (hasMMX || has3DNow || hasSSE)
	{
		Q_strncatz(cpuString, " w/", maxSize);

		if (hasMMX)
		{
			Q_strncatz(cpuString, " MMX", maxSize);
			cpu_mmx = true;
			if (hasMMXExt)
			{
				Q_strncatz(cpuString, "+", maxSize);
				cpu_mmxe = true;
			}
		}
		if (has3DNow)
		{
			Q_strncatz(cpuString, " 3DNow!", maxSize);
			cpu_3dnow = true;
			if (has3DNowExt)
			{
				Q_strncatz(cpuString, "+", maxSize);
				cpu_3dnowe = true;
			}
		}
		if (hasSSE)
		{
			Q_strncatz(cpuString, " SSE", maxSize);
			cpu_sse = true;
			if (hasSSE2)
			{
				Q_strncatz(cpuString, "2", maxSize);
				cpu_sse2 = true;
			}
		}
	}

	return true;

#else

	Q_strncpyz(cpuString, "Alpha AXP", maxSize);

	return true;

#endif
}


/*
===============
Cmd_Alias_f

Creates a new command that executes a command string (possibly ; seperated)
===============
*/
void Cmd_Alias_f ()
{
	cmdalias_t	*a;
	char		cmd[1024];
	int			i, c;
	char		*s;

	if (Cmd_Argc() == 1)
	{
		Com_Printf ("Current alias commands:\n");
		for (a = cmd_alias ; a ; a=a->next)
			Com_Printf ("%s : %s\n", a->name, a->value);
		return;
	}

	s = Cmd_Argv(1);
	if (strlen(s) >= MAX_ALIAS_NAME)
	{
		Com_Printf ("^3Alias name is too long\n");
		return;
	}

	unsigned	hash = Com_HashKey(s);
	// if the alias already exists, reuse it
	for (a = cmd_alias ; a ; a=a->next)
	{
		if (hash == a->hash)
		{
			if (!Q_strcasecmp(s, a->name))
			{
				Z_Free (a->value);
				break;
			}
		}
	}

	if (!a)
	{
		a = (struct cmdalias_s *) Z_Malloc (sizeof(cmdalias_t), true);
		a->next = cmd_alias;
		cmd_alias = a;
	}
	strcpy (a->name, s);

// copy the rest of the command line
	cmd[0] = 0;		// start out with a null string
	c = Cmd_Argc();
	for (i=2 ; i< c ; i++)
	{
		strcat (cmd, Cmd_Argv(i));
		if (i != (c - 1))
			strcat (cmd, " ");
	}
	strcat (cmd, "\n");

	a->value = CopyString (cmd);
	a->hash = hash;
}


void Sys_Init ()
{
	OSVERSIONINFO	vinfo;
	char			*string;
	MEMORYSTATUS	memStatus;
	char			str[64];
	SYSTEM_INFO		sysInfo;

	timeBeginPeriod( 1 );

	Com_Printf("*** Berserker@Quake2 ***\nBuilded: %s %s\nBinary code: x86 (%s)\n\n", __DATE__, __TIME__, BUILDSTRING);

	old_mouse_support = false;

	vinfo.dwOSVersionInfoSize = sizeof(vinfo);

	if (!GetVersionEx (&vinfo))
		Sys_Error ("Couldn't get OS info");

	if (vinfo.dwMajorVersion < 4)
		Sys_Error ("Berserker requires Windows version 4 or greater");
	if (vinfo.dwPlatformId == VER_PLATFORM_WIN32s)
		Sys_Error ("Berserker doesn't run on Win32s");
	else if ( vinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS )
	{
		s_win95 = true;
		old_mouse_support = true;
	}

	if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
	{
		GetSystemInfo(&sysInfo);
		NumberOfProcessors = sysInfo.dwNumberOfProcessors;

		if (vinfo.dwMajorVersion == 4)
		{
			string = "Windows NT";
			old_mouse_support = true;
		}
		else if (vinfo.dwMajorVersion == 5 && vinfo.dwMinorVersion == 0)
		{
			string = "Windows 2000";
			old_mouse_support = true;
		}
		else if (vinfo.dwMajorVersion == 5 && vinfo.dwMinorVersion == 1)
			string = "Windows XP";
		else if (vinfo.dwMajorVersion == 5 && vinfo.dwMinorVersion == 2)
			string = "Windows XP x64 or Windows 2003 server";
		else if (vinfo.dwMajorVersion == 6)
			string = "Windows Vista";
		else
		{
			Com_Printf("OS: Windows %i.%i\n", vinfo.dwMajorVersion, vinfo.dwMinorVersion);
			goto cont;
		}
	}
	else if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
	{
		old_mouse_support = true;
		if (vinfo.dwMajorVersion == 4 && vinfo.dwMinorVersion == 0)
		{
			if (vinfo.szCSDVersion[1] == 'C' || vinfo.szCSDVersion[1] == 'B')
				string = "Windows 95 OSR2";
			else
				string = "Windows 95";
		}
		else if (vinfo.dwMajorVersion == 4 && vinfo.dwMinorVersion == 10)
		{
			if (vinfo.szCSDVersion[1] == 'A')
				string = "Windows 98 SE";
			else
				string = "Windows 98";
		}
		else if (vinfo.dwMajorVersion == 4 && vinfo.dwMinorVersion == 90)
			string = "Windows ME";
		else
		{
			Com_Printf("OS: Windows %i.%i\n", vinfo.dwMajorVersion, vinfo.dwMinorVersion);
			goto cont;
		}
	}
	else
	{
		old_mouse_support = true;
		Com_Printf("OS: Windows %i.%i\n", vinfo.dwMajorVersion, vinfo.dwMinorVersion);
		goto cont;
	}

	Com_Printf("OS: %s\n", string);

cont:
	if (m_oldmouse->value)
		old_mouse_support = true;

	// Get memory status
	GlobalMemoryStatus(&memStatus);
	Com_Printf("\nTotal physical RAM: %u MB\n", memStatus.dwTotalPhys >> 20);
	Com_Printf("Available physical RAM: %u MB\n", memStatus.dwAvailPhys >> 20);
	Com_Printf("Total PageFile: %u MB\n", memStatus.dwTotalPageFile >> 20);
	Com_Printf("Available PageFile: %u MB\n", memStatus.dwAvailPageFile >> 20);
	Com_Printf("Total Virtual Memory: %u MB\n", memStatus.dwTotalVirtual >> 20);
	Com_Printf("Available Virtual Memory: %u MB\n\n", memStatus.dwAvailVirtual >> 20);

	// Detect CPU
	Com_Printf("CPU: ");
	if (Sys_DetectCPU(str, sizeof(str)))
		sys_cpu = Cvar_Get("sys_cpu", str, CVAR_NOSET);
	else
		sys_cpu = Cvar_Get("sys_cpu", "Unknown", CVAR_NOSET);
	Com_Printf("%s\n", sys_cpu->string);
	if (NumberOfProcessors>1)
		Com_Printf("Found %i CPUs\n\n", NumberOfProcessors);

	// Get user name
	unsigned long len = sizeof(str);
	if (GetUserName(str, &len))
		sys_username = Cvar_Get("sys_username", str, CVAR_NOSET);
	else
		sys_username = Cvar_Get("sys_username", "Player", CVAR_NOSET);

	sys_affinity = Cvar_Get("sys_affinity", "0", CVAR_ARCHIVE);
	sys_affinity->help = "CPU mask, allow to select which cores for bers@q2 will be used.";
	sys_priority = Cvar_Get("sys_priority", "0", CVAR_ARCHIVE);
	sys_priority->help = "allow to set bers@q2 process priority: 0 - normal, 1 - high, 2 - realtime.";

	if (dedicated->value)
	{
		if (!AllocConsole ())
			Sys_Error ("Couldn't create dedicated server console");
		hinput = GetStdHandle (STD_INPUT_HANDLE);
		houtput = GetStdHandle (STD_OUTPUT_HANDLE);
	}
}


void NET_Init ()
{
///	WORD	wVersionRequested; 
	int		r;

///	wVersionRequested = MAKEWORD(1, 1); 

	r = WSAStartup (MAKEWORD(1, 1), &winsockdata);

	if (r)
		Com_Error (ERR_FATAL,"Winsock initialization failed.");

	Com_Printf("Winsock Initialized\n");

	noudp = Cvar_Get ("noudp", "0", CVAR_NOSET);
	noudp->help = "disable using UDP protocol.";
	noipx = Cvar_Get ("noipx", "0", CVAR_NOSET);
	noipx->help = "disable using IPX protocol.";

	if(net_compatibility->value)
		Com_Printf("...using Quake2 net protocol: %i\n\n", OLD_PROTOCOL_VERSION);
	else
		Com_Printf("...using Berserker's net protocol: %i\n\n", PROTOCOL_VERSION);
}


void Netchan_Init ()
{
	int		port;

	// pick a port value that should be nice and random
	port = Sys_Milliseconds() & 0xffff;
	qport = Cvar_Get ("qport", va("%i", port), CVAR_NOSET);
}


/*
==================
SV_SetPlayer

Sets sv_client and sv_player to the player with idnum Cmd_Argv(1)
==================
*/
bool SV_SetPlayer ()
{
	client_t	*cl;
	int			i;
	int			idnum;
	char		*s;

	if (Cmd_Argc() < 2)
		return false;

	s = Cmd_Argv(1);

	// numeric values are just slot numbers
	if (s[0] >= '0' && s[0] <= '9')
	{
		idnum = atoi(Cmd_Argv(1));
		if (idnum < 0 || idnum >= maxclients->value)
		{
			Com_Printf ("^1Bad client slot: %i\n", idnum);
			return false;
		}

		sv_client = &svs.clients[idnum];
		sv_player = sv_client->edict;
		if (!sv_client->state)
		{
			Com_Printf ("^1Client %i is not active\n", idnum);
			return false;
		}
		return true;
	}

	// check for a name match
	for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++)
	{
		if (!cl->state)
			continue;
		if (!strcmp(cl->name, s))
		{
			sv_client = cl;
			sv_player = sv_client->edict;
			return true;
		}
	}

	Com_Printf ("^1Userid %s is not on the server\n", s);
	return false;
}


/*
=================
SV_ClientPrintf

Sends text across to be displayed if the level passes
=================
*/
void SV_ClientPrintf (client_t *cl, int level, char *fmt, ...)
{
	va_list		argptr;
	char		string[1024];
	
	if (level < cl->messagelevel)
		return;
	
	va_start (argptr,fmt);
	vsprintf (string, fmt,argptr);
	va_end (argptr);
	
	MSG_WriteByte (&cl->netchan.message, svc_print);
	MSG_WriteByte (&cl->netchan.message, level);
	MSG_WriteString (&cl->netchan.message, string);
}


/*
=================
SV_BroadcastPrintf

Sends text to all active clients
=================
*/
void SV_BroadcastPrintf (int level, char *fmt, ...)
{
	va_list		argptr;
	char		string[2048];
	client_t	*cl;
	int			i;

	va_start (argptr,fmt);
	vsprintf (string, fmt,argptr);
	va_end (argptr);

	// echo to console
	if (dedicated->value)
	{
		char	copy[1024];
		int		i;

		// mask off high bits
		for (i=0 ; i<1023 && string[i] ; i++)
			copy[i] = string[i]&127;
		copy[i] = 0;
		Com_Printf ("%s", copy);
	}

	for (i=0, cl = svs.clients ; i<maxclients->value; i++, cl++)
	{
		if (level < cl->messagelevel)
			continue;
		if (cl->state != cs_spawned)
			continue;
		MSG_WriteByte (&cl->netchan.message, svc_print);
		MSG_WriteByte (&cl->netchan.message, level);
		MSG_WriteString (&cl->netchan.message, string);
	}
}


/*
=====================
SV_RemoveClient

Silently removes client from server
=====================
*/
void SV_RemoveClient( client_t *client )
{
	if( client->download )
	{
		Z_Free( client->download );
		client->download = NULL;
	}

	if( client->state == cs_spawned )
		Com_DPrintf( "Going from cs_spawned to cs_zombie for %s\n", client->name );
	else
		Com_DPrintf( "Going from cs_connected to cs_zombie for %s\n", client->name );

	client->state = cs_zombie;		// become free in a few seconds
	//client->name[0] = 0;
}


/*
=====================
SV_DropClient

Called when the player is totally leaving the server, either willingly
or unwillingly.  This is NOT called if the entire server is quiting
or crashing.
=====================
*/
void SV_DropClient (client_t *drop)
{
	if (drop->netchan.message.data)
		MSG_WriteByte (&drop->netchan.message, svc_disconnect);	// add the disconnect

	if (drop->state == cs_spawned)
	{
		// call the prog function for removing a client
		// this will remove the body, among other things
		ge->ClientDisconnect (drop->edict);
	}

	SV_RemoveClient( drop );
}


/*
==================
SV_Kick_f

Kick a user off of the server
==================
*/
void SV_Kick_f ()
{
	if (!svs.initialized)
	{
		Com_Printf ("^3No server running.\n");
		return;
	}

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3USAGE: kick <userid>\n");
		return;
	}

	if (!SV_SetPlayer ())
		return;

	SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked\n", sv_client->name);
	// print directly, because the dropped client won't get the
	// SV_BroadcastPrintf message
	SV_ClientPrintf (sv_client, PRINT_HIGH, "You were kicked from the game\n");
	SV_DropClient (sv_client);
	sv_client->lastmessage = svs.realtime;	// min case there is a funny zombie
}


void Info_Print (char *s)
{
	char	key[MAX_INFO_STRING];
	char	value[MAX_INFO_STRING];
	char	*o;
	int		l;

	if (*s == '\\')
		s++;
	while (*s)
	{
		o = key;
		while (*s && *s != '\\')
			*o++ = *s++;

		l = o - key;
		if (l < 20)
		{
			Memset (o, ' ', 20-l);
			key[20] = 0;
		}
		else
			*o = 0;
		Com_Printf ("%s", key);

		if (!*s)
		{
			Com_Printf ("^1MISSING VALUE\n");
			return;
		}

		o = value;
		s++;
		while (*s && *s != '\\')
			*o++ = *s++;
		*o = 0;

		if (*s)
			s++;
		Com_Printf ("%s\n", value);
	}
}


void Info_RemoveKey (char *s, char *key)
{
	char	*start;
	char	pkey[MAX_INFO_STRING];
	char	value[MAX_INFO_STRING];
	char	*o;

	if (strstr (key, "\\"))
		return;

	while (1)
	{
		start = s;
		if (*s == '\\')
			s++;
		o = pkey;
		while (*s != '\\')
		{
			if (!*s)
				return;
			*o++ = *s++;
		}
		*o = 0;
		s++;

		o = value;
		while (*s != '\\' && *s)
		{
			if (!*s)
				return;
			*o++ = *s++;
		}
		*o = 0;

		if (!strcmp (key, pkey) )
		{
			strcpy (start, s);	// remove this part
			return;
		}

		if (!*s)
			return;
	}

}


void Info_SetValueForKey (char *s, char *key, char *value)
{
	char	newi[MAX_INFO_STRING], *v;
	int		c;
	int		maxsize = MAX_INFO_STRING;

	if (strstr (key, "\\") || strstr (value, "\\") )
	{
		Com_Printf ("^1Can't use keys or values with a \\\n");
		return;
	}

	if (strstr (key, ";") )
	{
		Com_Printf ("^1Can't use keys or values with a semicolon\n");
		return;
	}

	if (strstr (key, "\"") || strstr (value, "\"") )
	{
		Com_Printf ("^1Can't use keys or values with a \"\n");
		return;
	}

	if (value)	/// PVS-Studio
	{
		if (strlen(key) > MAX_INFO_KEY - 1 || strlen(value) > MAX_INFO_KEY - 1)
		{
			Com_Printf("^1Keys and values must be < 64 characters.\n");
			return;
		}
	}
	Info_RemoveKey (s, key);
	if (!value || !strlen(value))
		return;

	Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);

	if (strlen(newi) + strlen(s) > maxsize)
	{
		Com_Printf ("^1Info string length exceeded\n");
		return;
	}

	// only copy ascii values
	bool	colored = false;
	s += strlen(s);
	v = newi;
	while (*v)
	{
		if (!colored && Q_IsColorString(v))
		{
			if (net_compatibility->value)
				v+=2;
			else
				colored = true;
		}
		c = *v++;
		c &= 127;		// strip high bits
		if (c >= 32 /*&& c < 127*/)
			*s++ = c;
	}
	if (colored)
	{
		*s++='^';
		*s++='7';
	}
	*s = 0;
}


char	*Cvar_BitInfo (int bit, bool all)
{
	static char	info[MAX_INFO_STRING];
	cvar_t	*var;

	info[0] = 0;

/*	if(net_compatibility->value)
	{	/// Berserker: VERY BIG HACK
		for (var = cvar_vars ; var ; var = var->next)
			if (var->flags & bit)
			{
				int old_hand, it_hand = 0;
				char *old_hand_string;
				if (!Q_strncasecmp(var->name, "hand", 4))
				{
					if(hand->value!=1)
					{
						it_hand = 1;
						old_hand_string = hand->string;
						old_hand = hand->value;
						hand->string = "0";
						hand->value = 0;	// если оружие не в центре (hand=1), то считаем что оно справа.
					}
				}

				if(all || var->need_send)
				{
					Info_SetValueForKey (info, var->name, var->string);
					var->need_send = false;
				}

				if(it_hand)
				{
					hand->string = old_hand_string;
					hand->value = old_hand;
				}
			}
	}
	else
*/	{
		for (var = cvar_vars ; var ; var = var->next)
			if (var->flags & bit)
				if(all || var->need_send)
				{
					Info_SetValueForKey (info, var->name, var->string);
					var->need_send = false;
				}
	}

	return info;
}


// returns an info string containing all the CVAR_USERINFO cvars
char	*Cvar_Userinfo (bool all)
{
	return Cvar_BitInfo (CVAR_USERINFO, all);
}


// returns an info string containing all the CVAR_SERVERINFO cvars
char	*Cvar_Serverinfo ()
{
	return Cvar_BitInfo (CVAR_SERVERINFO, true);
}


/*
===========
SV_Serverinfo_f

  Examine or change the serverinfo string
===========
*/
void SV_Serverinfo_f ()
{
	Com_Printf ("Server info settings:\n");
	Info_Print (Cvar_Serverinfo());
}


/*
===========
SV_DumpUser_f

Examine all a users info strings
===========
*/
void SV_DumpUser_f ()
{
	if (!svs.initialized)
	{
		Com_Printf ("^1No server running.\n");
		return;
	}

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3USAGE: dumpuser <userid>\n");
		return;
	}

	if (!SV_SetPlayer ())
		return;

	Com_Printf ("userinfo\n");
	Com_Printf ("--------\n");
	Info_Print (sv_client->userinfo);
}


/*
==================
SV_ConSay_f
==================
*/
void SV_ConSay_f()
{
	client_t *client;
	int		j;
	char	*p;
	char	text[1024];

	if (Cmd_Argc () < 2)
		return;

	strcpy (text, "console: ");
	p = Cmd_Args();

	if (*p == '"')
	{
		p++;
		p[strlen(p)-1] = 0;
	}

	strcat(text, p);

	for (j = 0, client = svs.clients; j < maxclients->value; j++, client++)
	{
		if (client->state != cs_spawned)
			continue;
		SV_ClientPrintf(client, PRINT_CHAT, "%s\n", text);
	}
}


/*
====================
NET_Socket
====================
*/
int NET_IPSocket (char *net_interface, int port)
{
	int					newsocket;
	struct sockaddr_in	address;
	unsigned long		_true = 1;
	int					i = 1;
	int					err;

	if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
	{
		err = WSAGetLastError();
		if (err != WSAEAFNOSUPPORT)
			Com_DPrintf ("WARNING: UDP_OpenSocket: socket: %s\n", NET_ErrorString());
		return 0;
	}

	// make it non-blocking
	if (ioctlsocket (newsocket, FIONBIO, &_true) == -1)
	{
		Com_DPrintf ("WARNING: UDP_OpenSocket: ioctl FIONBIO: %s\n", NET_ErrorString());
		return 0;
	}

	// make it broadcast capable
	if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) == -1)
	{
		Com_DPrintf ("WARNING: UDP_OpenSocket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString());
		return 0;
	}

	if (!net_interface || !net_interface[0] || !stricmp(net_interface, "localhost"))
		address.sin_addr.s_addr = INADDR_ANY;
	else
		NET_StringToSockaddr (net_interface, (struct sockaddr *)&address);

	if (port == PORT_ANY)
		address.sin_port = 0;
	else
		address.sin_port = htons((short)port);

	address.sin_family = AF_INET;

	if( bind (newsocket, (struct sockaddr *)&address, sizeof(address)) == -1)
	{
		Com_DPrintf ("WARNING: UDP_OpenSocket: bind: %s\n", NET_ErrorString());
		closesocket (newsocket);
		return 0;
	}

	return newsocket;
}


/*
====================
NET_OpenIP
====================
*/
void NET_OpenIP ()
{
	cvar_t	*ip;
	int		port;
	int		dedicated;

	ip = Cvar_Get ("ip", "localhost", CVAR_NOSET);

	dedicated = Cvar_VariableValue ("dedicated");

	if (!ip_sockets[NS_SERVER])
	{
		port = Cvar_Get("ip_hostport", "0", CVAR_NOSET)->value;
		if (!port)
		{
			port = Cvar_Get("hostport", "0", CVAR_NOSET)->value;
			if (!port)
			{
				if(net_compatibility->value)
					port = Cvar_Get("port", va("%i", OLD_PORT_SERVER), CVAR_NOSET)->value;
				else
					port = Cvar_Get("port", va("%i", PORT_SERVER), CVAR_NOSET)->value;
			}
		}
		ip_sockets[NS_SERVER] = NET_IPSocket (ip->string, port);
		if (!ip_sockets[NS_SERVER] && dedicated)
			Com_Error (ERR_FATAL, "Couldn't allocate dedicated server IP port");
	}


	// dedicated servers don't need client ports
	if (dedicated)
		return;

	if (!ip_sockets[NS_CLIENT])
	{
		port = Cvar_Get("ip_clientport", "0", CVAR_NOSET)->value;
		if (!port)
		{
			if(net_compatibility->value)
				port = Cvar_Get("clientport", va("%i", OLD_PORT_CLIENT), CVAR_NOSET)->value;
			else
				port = Cvar_Get("clientport", va("%i", PORT_CLIENT), CVAR_NOSET)->value;

			if (!port)
				port = PORT_ANY;
		}
		ip_sockets[NS_CLIENT] = NET_IPSocket (ip->string, port);
		if (!ip_sockets[NS_CLIENT])
			ip_sockets[NS_CLIENT] = NET_IPSocket (ip->string, PORT_ANY);
	}
}


/*
====================
IPX_Socket
====================
*/
int NET_IPXSocket (int port)
{
	int					newsocket;
	struct sockaddr_ipx	address;
	unsigned long		_true = 1;
	int					err;

	if ((newsocket = socket (PF_IPX, SOCK_DGRAM, NSPROTO_IPX)) == -1)
	{
		err = WSAGetLastError();
		if (err != WSAEAFNOSUPPORT)
			Com_DPrintf ("WARNING: IPX_Socket: socket: %s\n", NET_ErrorString());
		return 0;
	}

	// make it non-blocking
	if (ioctlsocket (newsocket, FIONBIO, &_true) == -1)
	{
		Com_DPrintf ("WARNING: IPX_Socket: ioctl FIONBIO: %s\n", NET_ErrorString());
		return 0;
	}

	// make it broadcast capable
	if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof(_true)) == -1)
	{
		Com_DPrintf ("WARNING: IPX_Socket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString());
		return 0;
	}

	address.sa_family = AF_IPX;
	Memset (address.sa_netnum, 0, 4);
	Memset (address.sa_nodenum, 0, 6);
	if (port == PORT_ANY)
		address.sa_socket = 0;
	else
		address.sa_socket = htons((short)port);

	if( bind (newsocket, (struct sockaddr*) &address, sizeof(address)) == -1)
	{
		Com_DPrintf ("WARNING: IPX_Socket: bind: %s\n", NET_ErrorString());
		closesocket (newsocket);
		return 0;
	}

	return newsocket;
}


/*
====================
NET_OpenIPX
====================
*/
void NET_OpenIPX ()
{
	int		port;
	int		dedicated;

	dedicated = Cvar_VariableValue ("dedicated");

	if (!ipx_sockets[NS_SERVER])
	{
		port = Cvar_Get("ipx_hostport", "0", CVAR_NOSET)->value;
		if (!port)
		{
			port = Cvar_Get("hostport", "0", CVAR_NOSET)->value;
			if (!port)
			{
				if(net_compatibility->value)
					port = Cvar_Get("port", va("%i", OLD_PORT_SERVER), CVAR_NOSET)->value;
				else
					port = Cvar_Get("port", va("%i", PORT_SERVER), CVAR_NOSET)->value;
			}
		}
		ipx_sockets[NS_SERVER] = NET_IPXSocket (port);
	}

	// dedicated servers don't need client ports
	if (dedicated)
		return;

	if (!ipx_sockets[NS_CLIENT])
	{
		port = Cvar_Get("ipx_clientport", "0", CVAR_NOSET)->value;
		if (!port)
		{
			if(net_compatibility->value)
				port = Cvar_Get("clientport", va("%i", OLD_PORT_CLIENT), CVAR_NOSET)->value;
			else
				port = Cvar_Get("clientport", va("%i", PORT_CLIENT), CVAR_NOSET)->value;

			if (!port)
				port = PORT_ANY;
		}
		ipx_sockets[NS_CLIENT] = NET_IPXSocket (port);
		if (!ipx_sockets[NS_CLIENT])
			ipx_sockets[NS_CLIENT] = NET_IPXSocket (PORT_ANY);
	}
}


/*
====================
NET_Config

A single player game will only use the loopback code
====================
*/
void	NET_Config (bool multiplayer)
{
	int		i;
	static	bool	old_config;

	if (old_config == multiplayer)
		return;

	old_config = multiplayer;

	if (!multiplayer)
	{	// shut down any existing sockets
		for (i=0 ; i<2 ; i++)
		{
			if (ip_sockets[i])
			{
				closesocket (ip_sockets[i]);
				ip_sockets[i] = 0;
			}
			if (ipx_sockets[i])
			{
				closesocket (ipx_sockets[i]);
				ipx_sockets[i] = 0;
			}
		}
	}
	else
	{	// open sockets
		if (! noudp->value)
			NET_OpenIP ();
		if (! noipx->value)
			NET_OpenIPX ();
	}
}


int Create_Mapsstrings (int mask)
{
	int				ii = 0;
	searchpath_t	*search;
	pack_t			*pak;
	int				slot;
	int				dirCount, i;
	char			*dirFiles[MAX_MAPS];
	char			*str_map, *str_map_;
	bool			stop = false;
	bool			exit = false;

	// init
	Memset(m_maps, 0, sizeof(char)*MAX_MAPS*(MAX_MAP_NAMELEN+1));
	Memset(m_mapsvalid, 0, sizeof(bool)*MAX_MAPS);

	str_map = ".bsp";
	str_map_ = "*.bsp";

	if (mask&1)
	{
		for (search = fs_searchpaths ; search ; search = search->next)
		{
			// is the element a pak file?
			if (search->pack && !search->disabled)
			{
				// look through all the pak file elements
				pak = search->pack;
				if (pak->isPK2)
				{
					unsigned	hash = Com_HashKey(pak->filename);
					slot = -1;
					for(i=0; i<MAX_PAKS; i++)
					{
						if(ZipCache[i].uf)
						{
							if (ZipCache[i].hash == hash)
							{
								if(!b_stricmp(ZipCache[i].pak_name, pak->filename))
								{
									Memcpy(&pf, &ZipCache[i], sizeof(zipfile_t));		// Нашли уже открытый ZIP и загрузили его описатель в pf
									goto clc;
								}
							}
						}
						else
						{
							if(slot == -1)
							{
								slot = i;	// Ищем в slot первый свободный номер в кэше
								break;
							}
						}
					}
					strcpy(&ZipCache[slot].pak_name[0], pak->filename);	// Если не нашли zip в кэше, откроем его...
					if (!PackFileOpen (&ZipCache[slot]))
						Com_Error(ERR_FATAL, "Error opening pk2-file: %s", pak->filename);
					Memcpy(&pf, &ZipCache[slot], sizeof(zipfile_t));
					goto clc2;
clc:				slot = i;
clc2:				for(i=0; i<pf.gi.number_entry; i++)
					{
						if(pf.fi[i].size)
						{
							char	nam[256];
							Memcpy(nam, pf.fi[i].name, 256);
							if(!Q_strncasecmp(nam, "maps/", 5))
							{
								if(!Q_strcasecmp(nam+strlen(nam)-4, str_map))
								{
									if (ii<MAX_MAPS && strlen(nam)-9<=MAX_MAP_NAMELEN)
									{
										strncpy (m_maps[ii], nam+5, strlen(nam)-9);
										m_mapsvalid[ii] = true;
										ii++;
									}
								}
							}
						}
					}
				}
				else
				{
					// look through all the pak file elements
					for (i=0 ; i<pak->numfiles ; i++)
					{
						char	nam[256];
						Memcpy(nam, pak->files[i].name, 256);
						if (!Q_strncasecmp(nam, "maps/", 5))
						{
							if(!Q_strcasecmp(nam+strlen(nam)-4, str_map))
							{
								if (ii<MAX_MAPS && strlen(nam)-9<=MAX_MAP_NAMELEN)
								{
									strncpy (m_maps[ii], nam+5, strlen(nam)-9);
									m_mapsvalid[ii] = true;
									ii++;
								}
							}
						}
					}
				}
			}
		}
	}

	if (mask&2)
	{
loop:	if (stop == true)
		{
base:		dirCount = Sys_FindFiles(va("./%s/maps", BASEDIRNAME), str_map_, dirFiles, MAX_MAPS, true, false);
			exit = true;
		}
		else
		{
			stop = true;
			if (strcmp(fs_gamedir+2, BASEDIRNAME))	// если работает mod:
				dirCount = Sys_FindFiles(va("%s/maps", fs_gamedir), str_map_, dirFiles, MAX_MAPS, true, false);
			else
				goto base;
		}

		for (i = 0; i < dirCount; i++)
		{
			char	*name = dirFiles[i];
			while (*name!=0)
			{
				if(*name=='/' || *name=='\\')
					goto good;
				name++;
			}
			goto stop;
good:		name++;
			while (*name!=0)
			{
				if(*name=='/' || *name=='\\')
					goto good2;
				name++;
			}
			goto stop;
good2:		name++;

			if (ii<MAX_MAPS && strlen(name)-9<=MAX_MAP_NAMELEN)
			{
				strncpy (m_maps[ii], name+5, strlen(name)-9);
				m_mapsvalid[ii] = true;
				ii++;
			}

stop:		Z_Free(dirFiles[i]);
		}

		if (!exit)
			goto loop;
	}

	i=ii;
	for (; ii<MAX_MAPS; ii++)
		m_mapsvalid[ii] = false;

	return i;
}


void FreeMapNames2()
{
	int i;
	if ( map_names )
	{
		for ( i = 0; i < num_maps; i++ )
			Z_Free( map_names[i] );
		Z_Free( map_names );
	}
	map_names = 0;
	num_maps = 0;
}


/*
===============
SV_KillServer_f

Kick everyone off, possibly in preparation for a new game
===============
*/
void SV_KillServer_f ()
{
	if (!svs.initialized)
		return;
	SV_Shutdown ("Server was killed.\n", false);
	NET_Config ( false );	// close network sockets
}


void SV_Status_f ()
{
	int			i, j, l;
	client_t	*cl;
	char		*s;
	int			ping;
	if (!svs.clients)
	{
		Com_Printf ("^1No server running.\n");
		return;
	}
	Com_Printf ("map              : %s\n", sv.name);

	Com_Printf ("num score ping name            lastmsg address               port \n");
	Com_Printf ("--- ----- ---- --------------- ------- --------------------- ------\n");
	for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++)
	{
		if (!cl->state)
			continue;
		Com_Printf ("%3i ", i);
		Com_Printf ("%5i ", cl->edict->client->ps.stats[STAT_FRAGS]);

		if (cl->state == cs_connected)
			Com_Printf ("CNCT ");
		else if (cl->state == cs_zombie)
			Com_Printf ("ZMBI ");
		else
		{
			ping = cl->ping < 9999 ? cl->ping : 9999;
			Com_Printf ("%4i ", ping);
		}

		Com_Printf ("%s", cl->name);
		l = 16 - strlen(cl->name);
		for (j=0 ; j<l ; j++)
			Com_Printf (" ");

		Com_Printf ("%7i ", svs.realtime - cl->lastmessage );

		s = NET_AdrToString ( cl->netchan.remote_address);
		Com_Printf ("%s", s);
		l = 22 - strlen(s);
		for (j=0 ; j<l ; j++)
			Com_Printf (" ");
		
		Com_Printf ("%5i", cl->netchan.qport);

		Com_Printf ("\n");
	}
	Com_Printf ("\n");
}


/*
=====================
SV_WipeSavegame

Delete save/<XXX>/
=====================
*/
void SV_WipeSavegame (char *savename)
{
	char	name[MAX_OSPATH];
	char	*s, *savdir;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_DPrintf("SV_WipeSaveGame(%s)\n", savename);

	Com_sprintf (name, sizeof(name), "%s/%s/%s/server.ssv", FS_Gamedir (), savdir, savename);
	remove (name);
	Com_sprintf (name, sizeof(name), "%s/%s/%s/game.ssv", FS_Gamedir (), savdir, savename);
	remove (name);
	// Knightmare: delete screenshot
	Com_sprintf (name, sizeof(name), "%s/%s/%s/shot.tga", FS_Gamedir (), savdir, savename);
	remove (name);
	// Berserker: delete client's saved data
	Com_sprintf (name, sizeof(name), "%s/%s/%s/*.csv", FS_Gamedir (), savdir, savename);
	s = Sys_FindFirst( name, 0, 0 );
	while (s)
	{
		remove (s);
		s = Sys_FindNext( 0, 0 );
	}
	Sys_FindClose ();

	Com_sprintf (name, sizeof(name), "%s/%s/%s/*.sav", FS_Gamedir (), savdir, savename);
	s = Sys_FindFirst( name, 0, 0 );
	while (s)
	{
		remove (s);
		s = Sys_FindNext( 0, 0 );
	}
	Sys_FindClose ();

	Com_sprintf (name, sizeof(name), "%s/%s/%s/*.sv2", FS_Gamedir (), savdir, savename);
	s = Sys_FindFirst(name, 0, 0 );
	while (s)
	{
		remove (s);
		s = Sys_FindNext( 0, 0 );
	}
	Sys_FindClose ();
}


/*
===================
CM_WritePortalState

Writes the portal state to a savegame file
===================
*/
void	CM_WritePortalState (FILE *f)
{
	fwrite (portalopen, sizeof(portalopen), 1, f);
}


/*
==============
SV_WriteLevelFile

==============
*/
void SV_WriteLevelFile ()
{
	char	name[MAX_OSPATH];
	FILE	*f;
	char	*savdir;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_DPrintf("SV_WriteLevelFile()\n");

	Com_sprintf (name, sizeof(name), "%s/%s/current/%s.sv2", FS_Gamedir(), savdir, sv.name);
	f = FS_Fopen(name, "wb");
	if (!f)
	{
		Com_Printf ("^1Failed to open %s\n", name);
		return;
	}
	fwrite (sv.configstrings, sizeof(sv.configstrings), 1, f);
	CM_WritePortalState (f);
	fclose (f);

	Com_sprintf (name, sizeof(name), "%s/%s/current/%s.sav", FS_Gamedir(), savdir, sv.name);
	ge->WriteLevel (name);
}

/*
==============
WriteClientFile
Berserker: saves all client data (decals, particles, client-entities)
Can be called from server and client
==============
*/
void WriteClientFile(bool temp)
{
	FILE		*f;
	unsigned	eod = 0x7F7F7F7F;
	clentity_t	*cent, *centnext;
	cparticle_t	*p, *next;
	decal_t		*dec;
	float		alpha, time;
	char		name[MAX_OSPATH], *id = "Q2Bc", dumm[MAX_QPATH];
	byte		version = 1;
	char		*savdir;

	dumm[0] = 0;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	if (temp)
	{
		Com_DPrintf("WriteClientFile(temp)\n");
		Com_sprintf (name, sizeof(name), "%s/%s/temp.csv", FS_Gamedir(), savdir);
	}
	else
	{
		Com_DPrintf("WriteClientFile()\n");
		Com_sprintf (name, sizeof(name), "%s/%s/current/%s.csv", FS_Gamedir(), savdir, sv.name);
	}

	f = FS_Fopen(name, "wb");
	if (!f)
	{
		Com_Printf ("^1Couldn't write %s\n", name);
		return;
	}

	// write ID and version
	fwrite (id, 1, 4, f);
	fwrite (&version, 1, 1, f);

	// save decals
	for (dec=active_decals ; dec ; dec=dec->next)
	{
		if (dec->defDecal)						// Вечный декаль?
			continue;								// не трогаем его!
		if (dec->die <= cl.leveltime)			// Декаль устарел?
			continue;								// не трогаем его!

		fwrite (&dec->size, 1, sizeof(dec->size), f);		// первым сохраняем всегда INT!
		fwrite (dec->center, 1, sizeof(dec->center), f);
		fwrite (dec->normal, 1, sizeof(dec->normal), f);
		fwrite (dec->decalTextureName, 1, sizeof(dec->decalTextureName), f);
		fwrite (&dec->angle, 1, sizeof(dec->angle), f);
		dec->die -= cl.leveltime;
		fwrite (&dec->die, 1, sizeof(dec->die), f);
		dec->die += cl.leveltime;
		fwrite (&dec->decalType, 1, sizeof(dec->decalType), f);
		fwrite (&dec->style, 1, sizeof(dec->style), f);
		fwrite (&dec->cl_style, 1, sizeof(dec->cl_style), f);
		fwrite (&dec->shot, 1, sizeof(dec->shot), f);
	}
	// end of data "decals"
	fwrite (&eod, 1, 4, f);

	// save particles
	for (p=active_particles ; p ; p=next)
	{
		next = p->next;

		time = (cl.gameTime - p->time)*0.001;
		alpha = p->alpha + time * p->alphavel;
		if (alpha <= 0)			// партикль устарел?
			continue;				// не трогаем его!

		fwrite (&p->size, 1, sizeof(p->size), f);		// первым сохраняем всегда INT!
		p->time -= cl.gameTime;
		fwrite (&p->time, 1, sizeof(p->time), f);
		p->time += cl.gameTime;
		fwrite (p->org, 1, sizeof(p->org), f);
		fwrite (p->lastOrg, 1, sizeof(p->lastOrg), f);
		fwrite (p->vel, 1, sizeof(p->vel), f);
		fwrite (p->accel, 1, sizeof(p->accel), f);
		fwrite (p->length, 1, sizeof(p->length), f);
		fwrite (&p->color_r0, 1, sizeof(p->color_r0), f);
		fwrite (&p->color_g0, 1, sizeof(p->color_g0), f);
		fwrite (&p->color_b0, 1, sizeof(p->color_b0), f);
		fwrite (&p->color_r1, 1, sizeof(p->color_r1), f);
		fwrite (&p->color_g1, 1, sizeof(p->color_g1), f);
		fwrite (&p->color_b1, 1, sizeof(p->color_b1), f);
		fwrite (&p->type, 1, sizeof(p->type), f);
		fwrite (&p->alpha, 1, sizeof(p->alpha), f);
		fwrite (&p->alphavel, 1, sizeof(p->alphavel), f);
		fwrite (&p->flags, 1, sizeof(p->flags), f);
	}
	// end of data "particles"
	fwrite (&eod, 1, 4, f);

	// save client entities
	for (cent=active_clentities ; cent ; cent=centnext)
	{
		centnext = cent->next;

		time = cl.leveltime - cent->time;
		alpha = cent->alpha + time*cent->alphavel;
		if (alpha <= 0)			// ентити устарел?
			continue;				// не трогаем его!

		fwrite (cent->model->name, 1, sizeof(cent->model->name), f);	// первым сохраняем modelname!
		cent->time -= cl.leveltime;
		fwrite (&cent->time, 1, sizeof(cent->time), f);
		cent->time += cl.leveltime;
		fwrite (cent->org, 1, sizeof(cent->org), f);
		fwrite (cent->lastOrg, 1, sizeof(cent->lastOrg), f);
		fwrite (cent->vel, 1, sizeof(cent->vel), f);
		fwrite (cent->accel, 1, sizeof(cent->accel), f);
		fwrite (cent->ang, 1, sizeof(cent->ang), f);
		fwrite (cent->avel, 1, sizeof(cent->avel), f);
		fwrite (&cent->alpha, 1, sizeof(cent->alpha), f);
		fwrite (&cent->alphavel, 1, sizeof(cent->alphavel), f);
		fwrite (&cent->flags, 1, sizeof(cent->flags), f);
		fwrite (&cent->trailcount, 1, sizeof(cent->trailcount), f);
		fwrite (&cent->type, 1, sizeof(cent->type), f);
		if (cent->skin)
			fwrite (cent->skin->name, 1, sizeof(cent->skin->name), f);
		else
			fwrite (dumm, 1, sizeof(cent->skin->name), f);
	}
	// end of data "client entities"
	fwrite (&eod, 1, 4, f);

	fclose (f);
}


void CL_DeleteTempClientFile()
{
	char	name[MAX_OSPATH];
	char	*savdir;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_DPrintf("CL_DeleteTempClientFile()\n");
	Com_sprintf (name, sizeof(name), "%s/%s/temp.csv", FS_Gamedir(), savdir);
	remove (name);
}


/*
==============
CL_ReadClientFile
Berserker: load all client data (decals, particles, client-entities)
==============
*/
void CL_ReadClientFile (bool temp)
{
	FILE		*f;
	clentity_t	cent, *ent;
	model_t		*mod;
	cparticle_t	p, *pp;
	decal_t		dec;
	char		name[MAX_OSPATH], skinname[MAX_OSPATH], id[5];
	byte		version;
	image_t		*img, *skin, *bump, *light;
	char		*savdir;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	if (temp)
	{
		Com_DPrintf("CL_ReadClientFile(temp)\n");
		Com_sprintf (name, sizeof(name), "%s/%s/temp.csv", FS_Gamedir(), savdir);
	}
	else
	{
		Com_DPrintf("CL_ReadClientFile()\n");
		Com_sprintf (name, sizeof(name), "%s/%s/current/%s.csv", FS_Gamedir(), savdir, mapname);
	}

	f = FS_Fopen(name, "rb");
	if (!f)
	{
		Com_Printf ("^1Couldn't read %s\n", name);
		return;
	}

	// check ID and version
	FS_Read (id, 4, f);
	id[4]=0;
	if (strcmp(id,"Q2Bc"))
	{
		Com_Printf ("^1ID = %s (must be 'Q2Bc')\n", id);
		fclose(f);
		if (temp)
			CL_DeleteTempClientFile();
		return;
	}
	FS_Read (&version, 1, f);
	if (version != 1)
	{
		Com_Printf ("^1Version = %i (must be 1)\n", version);
		fclose(f);
		if (temp)
			CL_DeleteTempClientFile();
		return;
	}

	// load decals
	while (1)
	{
		Memset(&dec, 0, sizeof(dec));
		FS_Read (&dec.size, sizeof(dec.size), f);
		if (dec.size == 0x7F7F7F7F)
			break;
		FS_Read (dec.center, sizeof(dec.center), f);
		FS_Read (dec.normal, sizeof(dec.normal), f);
		FS_Read (dec.decalTextureName, sizeof(dec.decalTextureName), f);
		FS_Read (&dec.angle, sizeof(dec.angle), f);
		FS_Read (&dec.die, sizeof(dec.die), f);
		FS_Read (&dec.decalType, sizeof(dec.decalType), f);
		FS_Read (&dec.style, sizeof(dec.style), f);
		FS_Read (&dec.cl_style, sizeof(dec.cl_style), f);
		FS_Read (&dec.shot, sizeof(dec.shot), f);
		img = GL_FindImage(dec.decalTextureName, it_pic, true, 0, false, 0);
		if (!img)
			Com_Printf ("^3Can't find decal's image '%s'\n", dec.decalTextureName);
		else
			R_SpawnDecal (	dec.center,
							dec.normal,
							img,
							dec.size,
							dec.size,
							dec.die + cl.leveltime,
							dec.die + cl.leveltime,
							dec.angle,
							dec.angle,
							dec.decalType,
							dec.shot,
							dec.style,
							dec.cl_style,
							NULL, false );
	}

	// load particles
	while (1)
	{
		Memset(&p, 0, sizeof(p));
		FS_Read (&p.size, sizeof(p.size), f);
		if (p.size == 0x7F7F7F7F)
			break;
		FS_Read (&p.time, sizeof(p.time), f);
		FS_Read (p.org, sizeof(p.org), f);
		FS_Read (p.lastOrg, sizeof(p.lastOrg), f);
		FS_Read (p.vel, sizeof(p.vel), f);
		FS_Read (p.accel, sizeof(p.accel), f);
		FS_Read (p.length, sizeof(p.length), f);
		FS_Read (&p.color_r0, sizeof(p.color_r0), f);
		FS_Read (&p.color_g0, sizeof(p.color_g0), f);
		FS_Read (&p.color_b0, sizeof(p.color_b0), f);
		FS_Read (&p.color_r1, sizeof(p.color_r1), f);
		FS_Read (&p.color_g1, sizeof(p.color_g1), f);
		FS_Read (&p.color_b1, sizeof(p.color_b1), f);
		FS_Read (&p.type, sizeof(p.type), f);
		FS_Read (&p.alpha, sizeof(p.alpha), f);
		FS_Read (&p.alphavel, sizeof(p.alphavel), f);
		FS_Read (&p.flags, sizeof(p.flags), f);

		if (cl_add_particles->value)
		{
			if (free_particles)
			{
				pp = free_particles;
				free_particles = pp->next;
				pp->next = active_particles;
				active_particles = pp;

				pp->size = p.size;
				pp->time = p.time + cl.gameTime;
				VectorCopy(p.org, pp->org);
				VectorCopy(p.lastOrg, pp->lastOrg);
				VectorCopy(p.vel, pp->vel);
				VectorCopy(p.accel, pp->accel);
				VectorCopy(p.length, pp->length);
				pp->color_r0 = p.color_r0;
				pp->color_g0 = p.color_g0;
				pp->color_b0 = p.color_b0;
				pp->color_r1 = p.color_r1;
				pp->color_g1 = p.color_g1;
				pp->color_b1 = p.color_b1;
				pp->type = p.type;
				pp->alpha = p.alpha;
				pp->alphavel = p.alphavel;
				pp->flags = p.flags;
			}
		}
	}

	// load client entities
	while (1)
	{
		Memset(&cent, 0, sizeof(cent));
		Memset(name, 0, sizeof(cent.model->name)+1);
		Memset(skinname, 0, sizeof(cent.skin->name)+1);
		FS_Read (name, 4, f);
		if (name[0] == 0x7F && name[1] == 0x7F && name[2] == 0x7F && name[3] == 0x7F)
			break;
		FS_Read (name+4, sizeof(cent.model->name)-4, f);
		FS_Read (&cent.time, sizeof(cent.time), f);
		FS_Read (cent.org, sizeof(cent.org), f);
		FS_Read (cent.lastOrg, sizeof(cent.lastOrg), f);
		FS_Read (cent.vel, sizeof(cent.vel), f);
		FS_Read (cent.accel, sizeof(cent.accel), f);
		FS_Read (cent.ang, sizeof(cent.ang), f);
		FS_Read (cent.avel, sizeof(cent.avel), f);
		FS_Read (&cent.alpha, sizeof(cent.alpha), f);
		FS_Read (&cent.alphavel, sizeof(cent.alphavel), f);
		FS_Read (&cent.flags, sizeof(cent.flags), f);
		FS_Read (&cent.trailcount, sizeof(cent.trailcount), f);
		FS_Read (&cent.type, sizeof(cent.type), f);
		FS_Read (skinname, sizeof(cent.skin->name), f);
		mod = R_RegisterModel(name, 1, false);
		if (!mod)
			Com_Printf ("^3Can't find model '%s'\n", name);
		else
		{
			skin = bump = light = NULL;
			if (skinname[0])
			{
				skin = R_RegisterSkin (skinname);
				if (!skin)
					Com_Printf ("^3Can't find skin '%s'\n", skinname);
				else
				{
					bump = R_RegisterBump (skinname, skin, mod);
					light = R_RegisterLight (skinname);
				}
			}
			if (free_clentities)
			{
				ent = free_clentities;
				free_clentities = ent->next;
				ent->next = active_clentities;
				active_clentities = ent;

				ent->model = mod;
				ent->time = cent.time + cl.leveltime;
				VectorCopy(cent.org, ent->org);
				VectorCopy(cent.lastOrg, ent->lastOrg);
				VectorCopy(cent.vel, ent->vel);
				VectorCopy(cent.accel, ent->accel);
				VectorCopy(cent.ang, ent->ang);
				VectorCopy(cent.avel, ent->avel);
				ent->alpha = cent.alpha;
				ent->alphavel = cent.alphavel;
				ent->flags = cent.flags;
				ent->trailcount = cent.trailcount;
				ent->type = cent.type;
				ent->skin = skin;
				ent->bump = bump;
				ent->light = light;
			}
		}
	}

	fclose(f);
	if (temp)
		CL_DeleteTempClientFile();
}


/*
==============
SV_WriteServerFile

==============
*/
void SV_WriteServerFile (bool autosave)
{
	FILE	*f;
	cvar_t	*var;
	char	name[MAX_OSPATH], string[128];
	char	comment[32];
	time_t	aclock;
	struct tm	*newtime;
	char	*savdir;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_DPrintf("SV_WriteServerFile(%s)\n", autosave ? "true" : "false");

	Com_sprintf (name, sizeof(name), "%s/%s/current/server.ssv", FS_Gamedir(), savdir);
	f = FS_Fopen(name, "wb");
	if (!f)
	{
		Com_Printf ("^1Couldn't write %s\n", name);
		return;
	}
	// write the comment field
	Memset (comment, 0, sizeof(comment));

	if (!autosave)
	{
		time (&aclock);
		newtime = localtime (&aclock);
		Com_sprintf (comment,sizeof(comment), "%2i:%i%i %2i/%2i  ", newtime->tm_hour
			, newtime->tm_min/10, newtime->tm_min%10,
			newtime->tm_mon+1, newtime->tm_mday);
///		strncat (comment, sv.configstrings[CS_NAME], sizeof(comment)-1-strlen(comment) );
	}
	else
	{	// autosaved
		Com_sprintf (comment, sizeof(comment), "ENTERING ");
	}
	// disable char's colors
	char *in = sv.configstrings[CS_NAME];
	char *out = comment + strlen(comment);
	for (int j=strlen(comment);j<sizeof(comment)-1;j++)
	{
		if (*in == '^')
		{
			in+=2;
			j++;
		}
		else
		{
			*out = *in;
			in++;
			out++;
		}
	}
	comment[31] = 0;

	fwrite (comment, 1, sizeof(comment), f);

	// write the mapcmd
	fwrite (svs.mapcmd, 1, sizeof(svs.mapcmd), f);

	// write all CVAR_LATCH cvars
	// these will be things like coop, skill, deathmatch, etc
	for (var = cvar_vars ; var ; var=var->next)
	{
		if (!(var->flags & CVAR_LATCH))
			continue;
		if (strlen(var->name) >= sizeof(name)-1
			|| strlen(var->string) >= sizeof(string)-1)
		{
			Com_Printf ("^1Cvar too long: %s = %s\n", var->name, var->string);
			continue;
		}
		Memset (name, 0, sizeof(name));
		Memset (string, 0, sizeof(string));
		strcpy (name, var->name);
		strcpy (string, var->string);
		fwrite (name, 1, sizeof(name), f);
		fwrite (string, 1, sizeof(string), f);
	}

	fclose (f);

	// write game state
	Com_sprintf (name, sizeof(name), "%s/%s/current/game.ssv", FS_Gamedir(), savdir);
	ge->WriteGame (name, autosave);
}


void CopyFile (char *src, char *dst)
{
	FILE	*f1, *f2;
	int		l;
	byte	buffer[65536];

	Com_DPrintf ("CopyFile (%s, %s)\n", src, dst);

	f1 = FS_Fopen (src, "rb");
	if (!f1)
		return;
	f2 = FS_Fopen (dst, "wb");
	if (!f2)
	{
		fclose (f1);
		return;
	}

	while (1)
	{
		l = fread (buffer, 1, sizeof(buffer), f1);
		if (!l)
			break;
		fwrite (buffer, 1, l, f2);
	}

	fclose (f2);
	fclose (f1);
}


void SV_CopySaveGame (char *src, char *dst)
{
	char	name[MAX_OSPATH], name2[MAX_OSPATH];
	int		l, len;
	char	*found;
	char	*savdir;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_DPrintf("SV_CopySaveGame(%s, %s)\n", src, dst);

	SV_WipeSavegame (dst);

	// copy the savegame over
	Com_sprintf (name, sizeof(name), "%s/%s/%s/server.ssv", FS_Gamedir(), savdir, src);
	Com_sprintf (name2, sizeof(name2), "%s/%s/%s/server.ssv", FS_Gamedir(), savdir, dst);
	FS_CreatePath (name2);
	CopyFile (name, name2);

	Com_sprintf (name, sizeof(name), "%s/%s/%s/game.ssv", FS_Gamedir(), savdir, src);
	Com_sprintf (name2, sizeof(name2), "%s/%s/%s/game.ssv", FS_Gamedir(), savdir, dst);
	CopyFile (name, name2);

	// copy screenshot
	if (strcmp(dst, "save0")) // no screenshot for start of level autosaves
	{
		Com_sprintf (name, sizeof(name), "%s/%s/%s/shot.tga", FS_Gamedir(), savdir, src);
		Com_sprintf (name2, sizeof(name2), "%s/%s/%s/shot.tga", FS_Gamedir(), savdir, dst);
		CopyFile (name, name2);
	}

	Com_sprintf (name, sizeof(name), "%s/%s/%s/", FS_Gamedir(), savdir, src);
	len = strlen(name);
	Com_sprintf (name, sizeof(name), "%s/%s/%s/*.sav", FS_Gamedir(), savdir, src);
	found = Sys_FindFirst(name, 0, 0 );
	while (found)
	{
		strcpy (name+len, found+len);

		Com_sprintf (name2, sizeof(name2), "%s/%s/%s/%s", FS_Gamedir(), savdir, dst, found+len);
		CopyFile (name, name2);

		// change sav to sv2
		l = strlen(name);
		strcpy (name+l-3, "sv2");
		l = strlen(name2);
		strcpy (name2+l-3, "sv2");
		CopyFile (name, name2);

		// change sv2 to csv
		l = strlen(name);
		strcpy (name+l-3, "csv");
		l = strlen(name2);
		strcpy (name2+l-3, "csv");
		CopyFile (name, name2);

		found = Sys_FindNext( 0, 0 );
	}
	Sys_FindClose ();
}


/*
============
Cvar_GetLatchedVars

Any variables with latched values will now be updated
============
*/
void Cvar_GetLatchedVars ()
{
	cvar_t		*var;
	unsigned	hash = Com_HashKey("game");

	for (var = cvar_vars ; var ; var = var->next)
	{
		if (var->latched_string)
		{
			Z_Free (var->string);
			var->string = var->latched_string;
			var->latched_string = NULL;
			var->value = atof(var->string);
			if (hash == var->hash)
			{
				if (!Q_strcasecmp(var->name, "game"))
				{
					CL_WriteConfiguration();
					s_demos_menu.cursor = 0;
					s_maps_list.curvalue = 0;
					s_startmap_list.curvalue = 0;
					KillZipCache(false);
					FS_SetGamedir (var->string);
					FS_CreateDefaultPureList();
					FS_CutNonPures();
					if (!fs_pure)
						fs_pure = Cvar_Get ("fs_pure", "1", CVAR_NOSET | CVAR_ARCHIVE);
					float old_fs_pure = fs_pure->value;
					fs_pure->value = 2;		// hack
					enable_to_change_readonly_cvars = true;
					Cbuf_AddText ("exec default.cfg\n");
					Cbuf_AddText ("exec q2b_config.cfg\n");
					FS_ExecAutoexec ();
					if (dedicated && !dedicated->value)
					{
						if (cl.attractloop)
							Cbuf_AddText ("vid_restart\nsnd_restart\n");
						else
							Cbuf_AddText ("killserver\nvid_restart\nsnd_restart\n");
					}
					Cbuf_Execute();
					fs_pure->value = old_fs_pure;	// hack
					enable_to_change_readonly_cvars = false;
					FS_LoadPureLists();
					FS_CutNonPures();
					if (Q_strcasecmp(fs_cached->string, FS_Gamedir()))
					{
						Cbuf_AddText ("cache\n");
						Cbuf_Execute ();
						Cvar_Set("fs_cached", FS_Gamedir());
					}
				}
			}
		}
	}
}


void Cvar_GetVidLatchedVars ()
{
	cvar_t	*var;

	for (var = cvar_vars ; var ; var = var->next)
	{
		if (var->vid_latched_string)
		{
			Z_Free (var->string);
			var->string = var->vid_latched_string;
			var->vid_latched_string = NULL;
			var->value = atof(var->string);
		}
	}
}


void Sys_UnloadGame ()
{
	if (!FreeLibrary (game_library))
		Com_Error (ERR_FATAL, "FreeLibrary failed for game library");
	game_library = NULL;
}


/*
===============
SV_ShutdownGameProgs

Called when either the entire server is being killed, or
it is changing to a different game directory.
===============
*/
void SV_ShutdownGameProgs ()
{
	if (!ge)
		return;
	ge->Shutdown ();
	Sys_UnloadGame ();
	ge = NULL;
}


int CM_PointLeafnum_r (vec3_t p, int num)
{
	float		d;
	cnode_t		*node;
	cplane_t	*plane;

	while (num >= 0)
	{
		node = map_nodes + num;
		plane = node->plane;
		
		if (plane->type < 3)
			d = p[plane->type] - plane->dist;
		else
			d = DotProduct (plane->normal, p) - plane->dist;
		if (d < 0)
			num = node->children[1];
		else
			num = node->children[0];
	}

	c_pointcontents++;		// optimize counter

	return -1 - num;
}

/*	Berserker: not used
int		CM_LeafContents (int leafnum)
{
	if (leafnum < 0 || leafnum >= numleafs)
		Com_Error (ERR_DROP, "CM_LeafContents: bad number");
	return map_leafs[leafnum].contents;
}
*/

int		CM_LeafCluster (int leafnum)
{
	if (leafnum < 0 || leafnum >= numleafs)
		Com_Error (ERR_DROP, "CM_LeafCluster: bad number");
	return map_leafs[leafnum].cluster;
}

int CM_PointLeafnum (vec3_t p)
{
	if (!numplanes)
		return 0;		// sound may call this without map loaded
	return CM_PointLeafnum_r (p, 0);
}


int		CM_LeafArea (int leafnum)
{
	if (leafnum < 0 || leafnum >= numleafs)
		Com_Error (ERR_DROP, "CM_LeafArea: bad number");
	return map_leafs[leafnum].area;
}


void CM_DecompressVis (byte *in, byte *out)
{
	int		c;
	byte	*out_p;
	int		row;

	row = (numclusters+7)>>3;	
	out_p = out;

	if (!in || !numvisibility)
	{	// no vis info, so make all visible
		while (row)
		{
			*out_p++ = 0xff;
			row--;
		}
		return;		
	}

	do
	{
		if (*in)
		{
			*out_p++ = *in++;
			continue;
		}
	
		c = in[1];
		in += 2;
		if ((out_p - out) + c > row)
		{
			c = row - (out_p - out);
			Com_DPrintf ("warning: Vis decompression overrun\n");
		}
		while (c)
		{
			*out_p++ = 0;
			c--;
		}
	} while (out_p - out < row);
}

byte	pvsrow[MAX_MAP_LEAFS/8];
byte	phsrow[MAX_MAP_LEAFS/8];

byte	*CM_ClusterPVS (int cluster)
{
	if (cluster == -1)
		Memset (pvsrow, 0, (numclusters+7)>>3);
	else
		CM_DecompressVis (map_visibility + map_vis->bitofs[cluster][DVIS_PVS], pvsrow);
	return pvsrow;
}

byte	*CM_ClusterPHS (int cluster)
{
	if (cluster == -1)
		Memset (phsrow, 0, (numclusters+7)>>3);
	else
		CM_DecompressVis (map_visibility + map_vis->bitofs[cluster][DVIS_PHS], phsrow);
	return phsrow;
}


bool	CM_AreasConnected (int area1, int area2)
{
	if (map_noareas->value)
		return true;

	if (area1 > numareas || area2 > numareas)
		Com_Error (ERR_DROP, "area > numareas");

	if (map_areas[area1].floodnum == map_areas[area2].floodnum)
		return true;
	return false;
}


/*
=================
SV_Multicast

Sends the contents of sv.multicast to a subset of the clients,
then clears sv.multicast.

MULTICAST_ALL	same as broadcast (origin can be NULL)
MULTICAST_PVS	send to clients potentially visible from org
MULTICAST_PHS	send to clients potentially hearable from org
=================
*/
void SV_Multicast (vec3_t origin, multicast_t to)
{
	client_t	*client;
	byte		*mask;
	int			leafnum, cluster;
	int			j;
	bool		reliable;
	int			area1, area2;

	reliable = false;

	if (to != MULTICAST_ALL_R && to != MULTICAST_ALL)
	{
		leafnum = CM_PointLeafnum (origin);
		area1 = CM_LeafArea (leafnum);
	}
	else
	{
		leafnum = 0;	// just to avoid compiler warnings
		area1 = 0;
	}

	// if doing a serverrecord, store everything
	if (svs.demofile)
		SZ_Write (&svs.demo_multicast, sv.multicast.data, sv.multicast.cursize);
	
	switch (to)
	{
	case MULTICAST_ALL_R:
		reliable = true;	// intentional fallthrough
	case MULTICAST_ALL:
		leafnum = 0;
		mask = NULL;
		break;

	case MULTICAST_PHS_R:
		reliable = true;	// intentional fallthrough
	case MULTICAST_PHS:
		leafnum = CM_PointLeafnum (origin);
		cluster = CM_LeafCluster (leafnum);
		mask = CM_ClusterPHS (cluster);
		break;

	case MULTICAST_PVS_R:
		reliable = true;	// intentional fallthrough
	case MULTICAST_PVS:
		leafnum = CM_PointLeafnum (origin);
		cluster = CM_LeafCluster (leafnum);
		mask = CM_ClusterPVS (cluster);
		break;

	default:
		mask = NULL;
		Com_Error (ERR_FATAL, "SV_Multicast: bad to: %i", to);
	}

	// send the data to all relevent clients
	for (j = 0, client = svs.clients; j < maxclients->value; j++, client++)
	{
		if (client->state == cs_free || client->state == cs_zombie)
			continue;
		if (client->state != cs_spawned && !reliable)
			continue;

		if (mask)
		{
			leafnum = CM_PointLeafnum (client->edict->s.origin);
			cluster = CM_LeafCluster (leafnum);
			area2 = CM_LeafArea (leafnum);
			if (!CM_AreasConnected (area1, area2))
				continue;
			if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) )
				continue;
		}

		if (reliable)
			SZ_Write (&client->netchan.message, sv.multicast.data, sv.multicast.cursize);
		else
			SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize);
	}

	SZ_Clear (&sv.multicast);
}


/*
===============
PF_Unicast

Sends the contents of the mutlicast buffer to a single client
===============
*/
void PF_Unicast (edict_t *ent, bool reliable)
{
	int		p;
	client_t	*client;

	if (!ent)
		return;

	p = NUM_FOR_EDICT(ent);
	if (p < 1 || p > maxclients->value)
		return;

	client = svs.clients + (p-1);

	/// From Q2Pro:
	// HACK: fixes anti-votekick exploit
	// This should work with OSP Tourney at least
	if( *sv.multicast.data == svc_disconnect )
		SV_RemoveClient( client );

	if (reliable)
		SZ_Write (&client->netchan.message, sv.multicast.data, sv.multicast.cursize);
	else
		SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize);

	SZ_Clear (&sv.multicast);
}


bool R_FreeClEntities()
{
	clentity_t	*p, *next, *oldest = NULL, *active = NULL, *tail = NULL;
	float		a, alpha = 999, time;
	bool		freed = false;

	for (p=active_clentities ; p ; p=next)
	{
		next = p->next;
		time = cl.leveltime - p->time;
		a = p->alpha + time*p->alphavel;
		if (a <= 0)
		{	// faded out
			freed = true;
			p->next = free_clentities;
			free_clentities = p;
			continue;
		}
		if (a < alpha)
		{
			alpha = a;
			oldest = p;
		}
		p->next = NULL;
		if (!tail)
			active = tail = p;
		else
		{
			tail->next = p;
			tail = p;
		}
	}
	active_clentities = active;

	if (freed)
		return true;

	if (!oldest)
		return false;

	oldest->alpha = oldest->alphavel = 0;

	active = tail = NULL;
	for (p=active_clentities ; p ; p=next)
	{
		next = p->next;
		time = cl.leveltime - p->time;
		a = p->alpha + time*p->alphavel;
		if (a <= 0)
		{	// faded out
			p->next = free_clentities;
			free_clentities = p;
			continue;
		}
		p->next = NULL;
		if (!tail)
			active = tail = p;
		else
		{
			tail->next = p;
			tail = p;
		}
	}
	active_clentities = active;

	return true;
}

void CL_ThrowGib(model_t *mod, vec3_t org, vec3_t velocity, bool spawner)
{	
///	vec3_t		tmp;
	clentity_t	*p;

	if (r_maxDistance->value <= 0)
		return;

	if (Cvar_VariableValue("sv_gibs") <= 0)
		return;

	if (!free_clentities)
	{
		if (!R_FreeClEntities())
			return;
		if (!free_clentities)
			return;
	}

///	VectorSubtract(r_origin, org, tmp);
///	if(DotProduct(tmp,tmp)>=(r_maxDistance->value*r_maxDistance->value*64))	/// Лимит видимости джибсов в 8 раз больше
///		return;

	p = free_clentities;
	free_clentities = p->next;
	p->next = active_clentities;
	active_clentities = p;

	p->time = cl.leveltime;

	p->lastOrg[0] = p->org[0] = org[0];
	p->lastOrg[1] = p->org[1] = org[1];
	p->lastOrg[2] = p->org[2] = org[2];

	p->vel[0] = velocity[0] + random()*64;
	p->vel[1] = velocity[1] + random()*64;
	p->vel[2] = velocity[2] + random()*64;

	// ограничение скорости:
	if (p->vel[0] < -300)
		p->vel[0] = -300;
	else if (p->vel[0] > 300)
		p->vel[0] = 300;
	if (p->vel[1] < -300)
		p->vel[1] = -300;
	else if (p->vel[1] > 300)
		p->vel[1] = 300;
	if (spawner)
	{	// special case for target_spawner's gibs
		if (p->vel[2] < 0)
			p->vel[2] = 0;
		else if (p->vel[2] > 300)
			p->vel[2] = 300;
	}
	else
	{
		if (p->vel[2] < 5)
			p->vel[2] = 5;	// always some upwards
		else if (p->vel[2] > 300)
			p->vel[2] = 300;
	}

	p->accel[0] = crand()*48;
	p->accel[1] = crand()*48;
	p->accel[2] = -6*PARTICLE_GRAVITY;

	p->alpha = 1.0;
	p->alphavel = -(1.0 + frand()*0.1) / max(1.0, Cvar_VariableValue("sv_gibs"));						// -0.1 - frand()*0.5;

	p->flags = PARTICLE_BOUNCE | PARTICLE_FRICTION | PARTICLE_ROTATE;

	p->ang[0] = crand()*360;
	p->ang[1] = crand()*360;
	p->ang[2] = crand()*360;

	p->avel[0] = crand()*256-128;
	p->avel[1] = crand() * 32 - 16;
	p->avel[2] = crand()*256-128;

	p->skin = NULL;
	p->model = mod;
	p->trailcount = 1;
	p->type = EF_GIB;
}


void CL_ThrowMonsterGib(model_t *mod, vec3_t org, vec3_t velocity, vec3_t angle, image_t *skin, image_t *bump, image_t *light)
{
///	vec3_t		tmp;
	int			type;
	float		lifetime;
	clentity_t	*p;

	if (r_maxDistance->value <= 0)
		return;

	if (mod->trail_index == CLMOD_EF_GIB)
		type = EF_GIB;
	else if (mod->trail_index == CLMOD_EF_GREENGIB)
		type = EF_GREENGIB;
	else if (mod->trail_index == CLMOD_EF_SMOKE)
		type = EF_GRENADE;
	else
		type = 0;

	if (type == EF_GIB || type == EF_GREENGIB)
	{
		if (Cvar_VariableValue("sv_gibs") <= 0)
			return;
		lifetime = -(1.0 + frand()*0.1) / max(1.0, Cvar_VariableValue("sv_gibs"));
	}
	else
	{
		if (r_debrisTimeScale->value <= 0)
			return;
		lifetime = (-0.1 - frand()*0.5) / max(1.0, r_debrisTimeScale->value);
	}

	if (!free_clentities)
	{
		if (!R_FreeClEntities())
			return;
		if (!free_clentities)
			return;
	}

	p = free_clentities;
	free_clentities = p->next;
	p->next = active_clentities;
	active_clentities = p;

	p->time = cl.leveltime;

	p->lastOrg[0] = p->org[0] = org[0];
	p->lastOrg[1] = p->org[1] = org[1];
	p->lastOrg[2] = p->org[2] = org[2];

	p->vel[0] = velocity[0];
	p->vel[1] = velocity[1];
	p->vel[2] = velocity[2];
	// ограничение скорости:
	if (p->vel[0] < -300)
		p->vel[0] = -300;
	else if (p->vel[0] > 300)
		p->vel[0] = 300;
	if (p->vel[1] < -300)
		p->vel[1] = -300;
	else if (p->vel[1] > 300)
		p->vel[1] = 300;
	// always some upwards
	if (p->vel[2] < 5)
		p->vel[2] = 5;
	else if (p->vel[2] > 300)
		p->vel[2] = 300;

	p->accel[0] = crand()*48;
	p->accel[1] = crand()*48;
	p->accel[2] = -6*PARTICLE_GRAVITY;

	p->alpha = 1.0;
	p->alphavel = lifetime;

	p->flags = PARTICLE_BOUNCE | PARTICLE_FRICTION | PARTICLE_ROTATE;

	p->ang[0] = angle[0];
	p->ang[1] = angle[1];
	p->ang[2] = angle[2];

	p->avel[0] = crand()*256-128;
	p->avel[1] = crand() * 32 - 16;
	p->avel[2] = crand()*256-128;

/// debug stuff
#if 0
VectorClear(p->vel);
VectorClear(p->avel);
VectorClear(p->accel);
#endif

	p->skin = skin;
	p->bump = bump;
	p->light = light;
	p->model = mod;
	p->trailcount = 1;
	p->type = type;
}


// используется для осколков из стен от попадания пуль
bool CL_BrassModels (vec3_t org, vec3_t dir, model_t *model, unsigned count, unsigned flags, float time, float vel, float avel)
{
	unsigned	i;
///	vec3_t		tmp;
	clentity_t	*p;

	if (!count)
		return false;

	if (r_maxDistance->value <= 0)
		return false;

	if (r_brassTimeScale->value <= 0)
		return false;

///	VectorSubtract(r_origin, org, tmp);
///	if(DotProduct(tmp,tmp)>=(r_maxDistance->value*r_maxDistance->value))
///		return false;

	for (i=0 ; i<count ; i++)
	{
		if (!free_clentities)
		{
			if (!R_FreeClEntities())
				return false;
			if (!free_clentities)
				return false;
		}

		p = free_clentities;
		free_clentities = p->next;
		p->next = active_clentities;
		active_clentities = p;

		p->time = cl.leveltime;

		p->lastOrg[0] = p->org[0] = org[0] + dir[0];
		p->lastOrg[1] = p->org[1] = org[1] + dir[1];
		p->lastOrg[2] = p->org[2] = org[2] + dir[2];

		p->vel[0] = fabs(crand()+crand()*0.5) * vel * dir[0];
		p->vel[1] = fabs(crand()+crand()*0.5) * vel * dir[1];
		p->vel[2] = fabs(crand()+crand()*0.5) * vel * dir[2];

		p->accel[0] = crand()*32;
		p->accel[1] = crand()*32;
		p->accel[2] = -8*PARTICLE_GRAVITY;

		p->ang[0] = crand()*360;
		p->ang[1] = crand()*360;
		p->ang[2] = crand()*360;

		p->avel[0] = (crand()-0.5)*avel;
		p->avel[1] = (crand()-0.5)*avel;
		p->avel[2] = (crand()-0.5)*avel;

		p->alpha = 1.0;
		p->alphavel = (-time - frand()*0.2) / max(1.0, r_brassTimeScale->value);

		p->flags = flags;
		p->model = model;
		p->skin = NULL;

		p->trailcount = 0;
		p->type = 0;
	}
	return true;
}


void CL_BrassDepris (vec3_t org, vec3_t size, unsigned big, unsigned corners, unsigned little, bool glass)
{
	unsigned	i;
///	vec3_t		tmp;
	clentity_t	*p;

	if (r_maxDistance->value <= 0)
		return;

///	VectorSubtract(r_origin, org, tmp);
///	if(DotProduct(tmp,tmp)>=(r_maxDistance->value*r_maxDistance->value*64))	/// Лимит видимости осколков в 8 раз больше
///		return;

	if (r_debrisTimeScale->value > 0)
	{
		if (big)
		{
			// a few big chunks
			for (i=0 ; i<big ; i++)
			{
				if (!free_clentities)
				{
					if (!R_FreeClEntities())
						return;
					if (!free_clentities)
						return;
				}
				p = free_clentities;
				free_clentities = p->next;
				p->next = active_clentities;
				active_clentities = p;

				p->time = cl.leveltime;

				p->lastOrg[0] = p->org[0] = org[0] + crand() * size[0];
				p->lastOrg[1] = p->org[1] = org[1] + crand() * size[1];
				p->lastOrg[2] = p->org[2] = org[2] + crand() * size[2];

				p->vel[0] = crand()*64;
				p->vel[1] = crand()*64;
				p->vel[2] = fabs(crand()*128) + 64;

				p->accel[0] = crand()*32;
				p->accel[1] = crand()*32;
				p->accel[2] = -6*PARTICLE_GRAVITY;

				p->alpha = 1.0;

				p->alphavel = (-0.1 - frand()*0.5) / max(1.0, r_debrisTimeScale->value);

				p->flags = PARTICLE_BOUNCE | PARTICLE_ROTATE;

				if (glass)
					p->model = cl_mod_debris1_glass;
				else
					p->model = cl_mod_debris1;

				p->skin = NULL;

				p->ang[0] = crand()*360;
				p->ang[1] = crand()*360;
				p->ang[2] = crand()*360;

				p->avel[0] = crand()*256-128;
				p->avel[1] = crand()*256-128;
				p->avel[2] = crand()*256-128;

				p->trailcount = 0;
				p->type = 0;
			}
		}

		if (corners)
		{
			// bottom corners
			for (i=0 ; i<corners ; i++)
			{
				if (!free_clentities)
				{
					if (!R_FreeClEntities())
						return;
					if (!free_clentities)
						return;
				}
				p = free_clentities;
				free_clentities = p->next;
				p->next = active_clentities;
				active_clentities = p;

				p->time = cl.leveltime;

				p->lastOrg[0] = p->org[0] = org[0] + crand() * size[0];
				p->lastOrg[1] = p->org[1] = org[1] + crand() * size[1];
				p->lastOrg[2] = p->org[2] = org[2] + crand() * size[2];

				p->vel[0] = crand()*96;
				p->vel[1] = crand()*96;
				p->vel[2] = fabs(crand()*192) + 96;

				p->accel[0] = p->accel[1] = 0;
				p->accel[2] = -6*PARTICLE_GRAVITY;

				p->alpha = 1.0;

				p->alphavel = (-0.1 - frand()*0.5) / max(1.0, r_debrisTimeScale->value);

				p->flags = PARTICLE_BOUNCE | PARTICLE_FRICTION | PARTICLE_ROTATE;

				if (glass)
					p->model = cl_mod_debris3_glass;
				else
					p->model = cl_mod_debris3;

				p->skin = NULL;

				p->ang[0] = crand()*360;
				p->ang[1] = crand()*360;
				p->ang[2] = crand()*360;

				p->avel[0] = crand()*384-192;
				p->avel[1] = crand()*384-192;
				p->avel[2] = crand()*384-192;

				p->trailcount = 0;
				p->type = 0;
			}
		}
	}

	if (r_brassTimeScale->value > 0 && little)
	{
		// a bunch of little chunks
		for (i=0 ; i<little ; i++)
		{
			if (!free_clentities)
			{
				if (!R_FreeClEntities())
					return;
				if (!free_clentities)
					return;
			}
			p = free_clentities;
			free_clentities = p->next;
			p->next = active_clentities;
			active_clentities = p;

			p->time = cl.leveltime;

			p->lastOrg[0] = p->org[0] = org[0] + crand() * size[0];
			p->lastOrg[1] = p->org[1] = org[1] + crand() * size[1];
			p->lastOrg[2] = p->org[2] = org[2] + crand() * size[2];

			p->vel[0] = crand()*128;
			p->vel[1] = crand()*128;
			p->vel[2] = fabs(crand()*256) + 128;

			p->accel[0] = crand()*64;
			p->accel[1] = crand()*64;
			p->accel[2] = -6*PARTICLE_GRAVITY;

			p->alpha = 1.0;

			p->alphavel = (-0.1 - frand()*0.5) / max(1.0, r_brassTimeScale->value);

			p->flags = PARTICLE_BOUNCE | PARTICLE_FRICTION | PARTICLE_ROTATE;

			if (glass)
				p->model = cl_mod_debris2_glass;
			else
				p->model = cl_mod_debris2;

			p->skin = NULL;

			p->ang[0] = crand()*360;
			p->ang[1] = crand()*360;
			p->ang[2] = crand()*360;

			p->avel[0] = crand()*512-256;
			p->avel[1] = crand()*512-256;
			p->avel[2] = crand()*512-256;

			p->trailcount = 0;
			p->type = 0;
		}
	}
}


void CL_BrassShells (vec3_t org, vec3_t dir, int count, bool mshell)
{
	int			i, j;
	clentity_t	*p;
	float		d;
///	vec3_t		tmp;

	if (r_maxDistance->value <= 0 || !count)
		return;

	if (r_brassTimeScale->value <= 0)
		return;

///	VectorSubtract(r_origin, org, tmp);
///	if(DotProduct(tmp,tmp)>=(r_maxDistance->value*r_maxDistance->value))
///		return;

	for (i=0 ; i<count ; i++)
	{
		if (!free_clentities)
		{
			if (!R_FreeClEntities())
				return;
			if (!free_clentities)
				return;
		}
		p = free_clentities;
		free_clentities = p->next;
		p->next = active_clentities;
		active_clentities = p;

		p->time = cl.leveltime;

		d = 192+rand()&63;
		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = org[j];
			p->vel[j] = crand()*24 + d*dir[j];
		}

		p->accel[0] = crand()*32;
		p->accel[1] = crand()*32;
		p->accel[2] = -6*PARTICLE_GRAVITY;
		p->alpha = 1.0;
		if (mshell)
			p->alphavel = (-1 - frand()*0.2) / max(1.0, r_brassTimeScale->value);
		else
			p->alphavel = (-0.2 - frand()*0.2) / max(1.0, r_brassTimeScale->value);

		p->flags = PARTICLE_BOUNCE | PARTICLE_FRICTION | PARTICLE_ROTATE;
		if(mshell)
			p->model = cl_mod_mshell;
		else
			p->model = cl_mod_sshell;

		p->skin = NULL;

		p->ang[0] = 0;
		p->ang[1] = crand()*360;
		p->ang[2] = 0;

		p->avel[0] = 0;
		p->avel[1] = crand()*500-250;
		p->avel[2] = 0;

		p->trailcount = 0;
		p->type = 0;
	}
}


void ReadScaledDir(vec3_t vec)
{
	MSG_ReadDir (&net_message, vec);
	float len = MSG_ReadFloat (&net_message);
	VectorScale(vec, len, vec);
}


void CL_ParticleSmoke(vec3_t org, vec3_t dir, unsigned r0, unsigned g0, unsigned b0, unsigned r1, unsigned g1, unsigned b1, int count)
{
	int i, j;
	cparticle_t *p;
	float d;

	for (i = 0; i < count; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cl.gameTime;
		p->flags = 0;
		p->type = part_smoke;
		p->size = 1;

		p->color_r0 = r0;
		p->color_g0 = g0;
		p->color_b0 = b0;

		p->color_r1 = r1;
		p->color_g1 = g1;
		p->color_b1 = b1;

		d = rand() & 15;
		for (j = 0; j < 3; j++)
		{
			p->org[j] = org[j] + d * dir[j];
			p->vel[j] = dir[j] * 20;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = PARTICLE_GRAVITY;

		p->alpha = 1.0;
		p->alphavel = -1.0 / (0.3 + frand() * 0.5);
	}
}


void CL_ParseTEnt (bool large_map)
{
	int			type;
	vec3_t		pos, pos2, dir;
	explosion_t	*ex;
	int			cnt;
	byte		color;
	byte		color_r;
	byte		color_g;
	byte		color_b;
	int			r;
	int			ent;
	int			magnitude;
	vec3_t		start, end;
	trace_t		trace;
	material_t	*material;
	vec3_t		vec, org;
	float		len;
	float		best;
	int			i;
	vec3_t		absmin, size, velocity, angles, forward, right, up;
	clientinfo_t	*ci;
	char			*slash;
	image_t			*skin;
	image_t			*bump;
	image_t			*light;

	type = MSG_ReadByte (&net_message);

	switch (type)
	{
	case TE_BLOOD:			// bullet hitting flesh
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		if(p_blood->value)
		{
			R_LightForPoint(pos, end);
			/// don't allow too dark!
			if (end[0] < 0.02)	end[0] = 0.02;
			if (end[1] < 0.02)	end[1] = 0.02;
			if (end[2] < 0.02)	end[2] = 0.02;

			color_r = p_blood_r->value * end[0] * 256.0;
			color_g = p_blood_g->value * end[1] * 256.0;
			color_b = p_blood_b->value * end[2] * 256.0;
			if (color_r < 1)	color_r = 1;
			if (color_g < 1)	color_g = 1;
			if (color_b < 1)	color_b = 1;

			cnt = 8*p_blood->value;
			if (cnt)
				CL_ParticleEffect (pos, dir, color_r,color_g,color_b, cnt, part_blood, PARTICLE_FRICTION);	// Berserker: Уменьшил кол-во пятен крови (так как speedup - мараем только 1/16 вероятностью, то пусть будет крови примерно на 2 пятна)
		}
		break;

	case TE_GUNSHOT:			// bullet hitting wall
	case TE_SPARKS:
	case TE_BULLET_SPARKS:
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		if (type == TE_GUNSHOT)
		{
			if(p_machine->value)
			{
#if 0
				CL_ParticleEffect3 (pos, dir, p_smoke_r->value*256,p_smoke_g->value*256,p_smoke_b->value*256, 2*p_machine->value, part_smoke);		// Дым
#else
				CL_ParticleSmoke(pos, dir, p_smoke_r->value*256,p_smoke_g->value*256,p_smoke_b->value*256,  p_smoke_r->value*200,p_smoke_g->value*200,p_smoke_b->value*200,  10 * p_machine->value);
#endif
				VectorAdd(pos, dir, start);
				VectorSubtract(pos, dir, end);
				trace = CL_PMTraceWorld (start, vec3_origin, vec3_origin, end, MASK_SOLID);
				if (trace.fraction > 0 && trace.fraction < 1)
				{
					material = R_FindMaterial(trace.surface->name);

					if (material && material->hs_bullet)
					{
						if (material->framecount != occ_framecount)
						{
							material->framecount = occ_framecount;
							material->count = 0;
						}
						if (material->count < MAX_MATERIAL_HITS)
						{
							S_StartSound (pos, 0, CHAN_AUTO, material->hs_bullet, 0.4, _ATTN_NORM, 0);
							material->count++;
						}
					}

					if (material && material->debris)
					{
						if (!CL_BrassModels(pos, dir, material->debris, max(0.25 * p_machine->value, 1), PARTICLE_BOUNCE | PARTICLE_FRICTION | PARTICLE_ROTATE, 1, 192, 192))	// Рикошет
							goto step0;
					}
					else
						goto step0;
				}
				else
				{
step0:				if (!(trace.surface->flags & SURF_NODRAW))
					{
						cnt = 4*p_machine->value;
						if (cnt)
							CL_ParticleEffect (pos, dir, trace.surface->color_r, trace.surface->color_g, trace.surface->color_b, cnt, part_simple, PARTICLE_BOUNCE | PARTICLE_FRICTION);	// Рикошет
					}
				}
			}
		}
		else
		{
			if (p_sparks->value)
			{
				cnt = 6 * p_sparks->value;
				if (cnt)
					CL_ParticleEffect (pos, dir, 60,40,10, cnt, part_simple, PARTICLE_BOUNCE | PARTICLE_FRICTION);
			}
		}

		if (type != TE_SPARKS)
		{
			float lt;
			lt = r_bulletMarkLifeTime->value;
			if (lt <= 0.1 )	lt = 0.1;
			R_SpawnDecal(pos, dir, bullet_mrk_object, 2,6, 16*lt,48*lt, 0, 360, dt_mul, true, 0, 0, NULL, false);
///			CL_SmokeAndFlash(pos);
			CL_Sparks(pos, dir);
			
			// impact sound
			cnt = rand()&15;
			if (cnt == 1)
				S_StartSound (pos, 0, CHAN_AUTO, cl_sfx_ric1, 1, _ATTN_NORM, 0);
			else if (cnt == 2)
				S_StartSound (pos, 0, CHAN_AUTO, cl_sfx_ric2, 1, _ATTN_NORM, 0);
			else if (cnt == 3)
				S_StartSound (pos, 0, CHAN_AUTO, cl_sfx_ric3, 1, _ATTN_NORM, 0);
		}

		break;

	case TE_SCREEN_SPARKS:
	case TE_SHIELD_SPARKS:
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		cnt = 15 * p_sparks->value;
		if (cnt)
		{
			if (type == TE_SCREEN_SPARKS)
///				CL_ParticleEffect (pos, dir, 0,100,0, cnt, part_simple, 0);
				CL_SplashSparks(pos, dir, 0,250,0, 0,50,0, cnt);
			else
///				CL_ParticleEffect (pos, dir, 0,100,100, cnt, part_simple, 0);
				CL_SplashSparks(pos, dir, 0,250,200, 0,80,50, cnt);
		}
		//FIXME : replace or remove this sound
		S_StartSound (pos, 0, CHAN_AUTO, cl_sfx_lashit, 1, _ATTN_NORM, 0);
		break;

	case TE_SHOTGUN:			// bullet hitting wall
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);

		if(p_shotgun->value)
		{
#if 0
			CL_ParticleEffect3 (pos, dir, p_smoke_r->value*256,p_smoke_g->value*256,p_smoke_b->value*256, 4*p_machine->value, part_smoke);		// Дым
#else
			CL_ParticleSmoke(pos, dir, p_smoke_r->value*256,p_smoke_g->value*256,p_smoke_b->value*256,  p_smoke_r->value*200,p_smoke_g->value*200,p_smoke_b->value*200,  10 * p_shotgun->value);
#endif

			VectorAdd(pos, dir, start);
			VectorSubtract(pos, dir, end);
			trace = CL_PMTraceWorld (start, vec3_origin, vec3_origin, end, MASK_SOLID);
			if (trace.fraction > 0 && trace.fraction < 1)
			{
				material = R_FindMaterial(trace.surface->name);

				if (material && material->hs_bullet)
				{
					if (material->framecount != occ_framecount)
					{
						material->framecount = occ_framecount;
						material->count = 0;
					}
					if (material->count < MAX_MATERIAL_HITS)
					{
						S_StartSound (pos, 0, CHAN_AUTO, material->hs_bullet, 0.4, _ATTN_NORM, 0);
						material->count++;
					}
				}

				if (material && material->debris)
				{
					if (!CL_BrassModels(pos, dir, material->debris, max(0.5 * p_shotgun->value, 1), PARTICLE_BOUNCE | PARTICLE_FRICTION | PARTICLE_ROTATE, 0.2, 192, 192))	// Рикошет
						goto step1;
				}
				else
					goto step1;
			}
			else
			{
step1:			if (!(trace.surface->flags & SURF_NODRAW))
				{
					cnt = 12*p_shotgun->value;
					if (cnt)
						CL_ParticleEffect (pos, dir, trace.surface->color_r, trace.surface->color_g, trace.surface->color_b, cnt, part_simple, PARTICLE_BOUNCE | PARTICLE_FRICTION);	// Рикошет
				}
			}
		}
		float lt;
		lt = r_bulletMarkLifeTime->value;
		if (lt <= 0.1 )	lt = 0.1;
		R_SpawnDecal(pos, dir, bullet_mrk_object, 2,6, 16*lt,48*lt, 0, 360, dt_mul, true, 0, 0, NULL, false);
		CL_Sparks(pos, dir);
		break;

	case TE_SPLASH:			// bullet hitting water
		cnt = MSG_ReadByte (&net_message) * ClampCvar(0, 1, p_splash->value);
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		r = MSG_ReadByte (&net_message);
		if (p_splash->value)
		{
			if (!cnt)	cnt=1;

			switch(r)
			{
			case SPLASH_SPARKS:
///				CL_ParticleEffect (pos, dir, 200, 200, 50, cnt, part_simple, PARTICLE_BOUNCE | PARTICLE_FRICTION);	// значения color_x не больше 238!!! (т.к. + rnd()&15)
				CL_SplashSparks(pos, dir, 250, 230, 100, 150, 20, 0, cnt);
				break;
			case SPLASH_BLUE_WATER:
				CL_WaterSplash (pos, dir, 155, 155, 200);
				CL_ParticleEffect (pos, dir, _r, _g, _b, cnt, part_splash, 0);
				break;
			case SPLASH_BROWN_WATER:
				CL_WaterSplash (pos, dir, 100, 50, 0);
				CL_ParticleEffect (pos, dir, _r, _g, _b, cnt, part_splash, 0);
				break;
			case SPLASH_SLIME:
				CL_WaterSplash (pos, dir, 50, 100, 50);
				CL_ParticleEffect (pos, dir, _r, _g, _b, cnt, part_splash, 0);
				break;
			case SPLASH_LAVA:
				CL_WaterSplash (pos, dir, 200, 50, 50);
				CL_ParticleEffect (pos, dir, _r, _g, _b, cnt, part_splash, 0);
				break;
			case SPLASH_BLOOD:
				{
					R_LightForPoint(pos, end);
					/// don't allow too dark!
					if (end[0] < 0.02)	end[0] = 0.02;
					if (end[1] < 0.02)	end[1] = 0.02;
					if (end[2] < 0.02)	end[2] = 0.02;

					color_r = p_blood_r->value * end[0] * 256.0;
					color_g = p_blood_g->value * end[1] * 256.0;
					color_b = p_blood_b->value * end[2] * 256.0;
					if (color_r < 1)	color_r = 1;
					if (color_g < 1)	color_g = 1;
					if (color_b < 1)	color_b = 1;

					CL_ParticleEffect (pos, dir, color_r, color_g, color_b, cnt, part_blood, PARTICLE_FRICTION);
				}
				break;
			}
		}

		if (r == SPLASH_SPARKS)
			S_StartSound (pos, 0, CHAN_AUTO, cl_sfx_spark5[rand() % 3], 1, _ATTN_STATIC, 0);
		break;

	case TE_LASER_SPARKS:
		cnt = MSG_ReadByte (&net_message);
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		color = MSG_ReadByte (&net_message);
		if (cnt*p_sparks->value)
		{
			color_r = d_8to24table[color*3+0];
			color_g = d_8to24table[color*3+1];
			color_b = d_8to24table[color*3+2];
			CL_ParticleEffect2 (pos, dir, color_r, color_g, color_b, cnt*p_sparks->value, part_simple);
		}
		R_SpawnDecal(pos, dir, laser_mrk_object, 6,10, 1.5,2.5, 0, 360, dt_add, true, 0, 0, NULL, false);
		VectorAdd(pos, dir, start);
		VectorSubtract(pos, dir, end);
		trace = CL_PMTraceWorld (start, vec3_origin, vec3_origin, end, MASK_SOLID);
		if (trace.fraction > 0 && trace.fraction < 1)
		{
			material = R_FindMaterial(trace.surface->name);
			if (material && material->hs_energy)
			{
				if (material->framecount != occ_framecount)
				{
					material->framecount = occ_framecount;
					material->count = 0;
				}
				if (material->count < MAX_MATERIAL_HITS)
				{
					S_StartSound (pos, 0, CHAN_AUTO, material->hs_energy, 0.05, _ATTN_NORM, 0);
					material->count++;
				}
			}
		}
		break;

	case TE_BLASTER:			// blaster hitting wall
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		cnt = 30 * p_sparks->value;
		if (cnt)
			CL_BlasterParticles (pos, dir, 220, 150, 64, cnt);

#if 1
		CL_ParticleSmoke(pos, dir, 0.8 * 255.0, 0.5 * 255.0, 0.2 * 255.0,  0.9 * 255.0, 0.7 * 255.0, 0.3 * 255.0,  8 * p_sparks->value);
#else
		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->ent.angles[0] = acos(dir[2])/M_PI*180;
	// PMM - fixed to correct for pitch of 0
		if (dir[0])
			ex->ent.angles[1] = atan2(dir[1], dir[0])/M_PI*180;
		else if (dir[1] > 0)
			ex->ent.angles[1] = 90;
		else if (dir[1] < 0)
			ex->ent.angles[1] = 270;
		else
			ex->ent.angles[1] = 0;
		ex->type = ex_misc;
		ex->ent.flags = RF_FULLBRIGHT|RF_TRANSLUCENT;
		ex->start = cl.frame.servertime - 100;
		ex->light = 100;
		VectorMA (pos, 10, dir, ex->light_origin);	// Berserker: fixed bug - немного отодвинем вспышку от заграждения, чтобы правильнее рисовалась тень (shifting = 10% from LightRadius)
		ex->lightcolor[0] = 1;
		ex->lightcolor[1] = 1;
		ex->ent.model = cl_mod_explode;
		ex->frames = 4;
#endif

		lt = r_laserMarkLifeTime->value;
		if (lt <= 0.1 )	lt = 0.1;
		R_SpawnDecal(pos, dir, laser_mrk_object, 4,8, 2.5*lt,4.5*lt, 0, 360, dt_add, true, 0, 0, NULL, false);
		VectorAdd(pos, dir, start);
		VectorSubtract(pos, dir, end);
		trace = CL_PMTraceWorld (start, vec3_origin, vec3_origin, end, MASK_SOLID);
		if (trace.fraction > 0 && trace.fraction < 1)
		{
			material = R_FindMaterial(trace.surface->name);
			if (material && material->hs_energy)
			{
				if (material->framecount != occ_framecount)
				{
					material->framecount = occ_framecount;
					material->count = 0;
				}
				if (material->count < MAX_MATERIAL_HITS)
				{
					S_StartSound (pos, 0, CHAN_AUTO, material->hs_energy, 0.2, _ATTN_NORM, 0);
					material->count++;
					break;		// чтоб отсечь cl_sfx_lashit, ибо он тут уже лишний
				}
			}
		}
		S_StartSound (pos,  0, CHAN_AUTO, cl_sfx_lashit, 1, _ATTN_NORM, 0);
		break;

	case TE_RAILTRAIL:				// railgun effect
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadPos (&net_message, pos2, large_map);
		VectorSubtract(pos2, pos, vec);
		len = VectorNormalize(vec);
		VectorMA(pos, len + 0.5, vec, org);
		trace = CL_PMTraceWorld(pos, vec3_origin, vec3_origin, org, MASK_SOLID);
		if (trace.fraction == 1 || trace.allsolid)
			VectorClear(dir);
		else
			VectorCopy(trace.plane.normal, dir);

		color_r = 1;
		color_g = 7;

		CL_RailTrail (pos, pos2, color_r, color_g, 3);
		S_StartSound (pos2, 0, CHAN_AUTO, cl_sfx_railg, 1, _ATTN_NORM, 0);
		lt = r_railMarkLifeTime->value;
		if (lt <= 0.1 )	lt = 0.1;
		R_SpawnDecal(pos2, dir, rail_mrk_object[color_g&7], 10,10, 4*lt,8*lt, 0, 360, dt_add, true, 0, 0, NULL, false);
		if (trace.fraction > 0 && trace.fraction < 1)
		{
			material = R_FindMaterial(trace.surface->name);
			if (material && material->hs_energy)
			{
				if (material->framecount != occ_framecount)
				{
					material->framecount = occ_framecount;
					material->count = 0;
				}
				if (material->count < MAX_MATERIAL_HITS)
				{
					S_StartSound (pos2, 0, CHAN_AUTO, material->hs_energy, 0.2, _ATTN_NORM, 0);
					material->count++;
				}
			}
		}
		break;

	case TE_RAILTRAIL_BERS_NEW:			// new railgun effect (new net-protocol) - w/ spiral radius control
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadPos (&net_message, pos2, large_map);
		MSG_ReadDir (&net_message, dir);		// new net-protocol
		color_g = MSG_ReadByte (&net_message);	/// railtrail core & spiral color
		color_b = MSG_ReadByte (&net_message);	/// railtrail spiral radius
		color_r = (color_g >> 4) & 0xf;
		color_g = color_g & 0xf;

		CL_RailTrail (pos, pos2, color_r, color_g, color_b);
		S_StartSound (pos2, 0, CHAN_AUTO, cl_sfx_railg, 1, _ATTN_NORM, 0);
		lt = r_railMarkLifeTime->value;
		if (lt <= 0.1 )	lt = 0.1;
		R_SpawnDecal(pos2, dir, rail_mrk_object[color_g&7], 10,10, 4*lt,8*lt, 0, 360, dt_add, true, 0, 0, NULL, false);
		VectorAdd(pos2, dir, start);
		VectorSubtract(pos2, dir, end);
		trace = CL_PMTraceWorld (start, vec3_origin, vec3_origin, end, MASK_SOLID);
		if (trace.fraction > 0 && trace.fraction < 1)
		{
			material = R_FindMaterial(trace.surface->name);
			if (material && material->hs_energy)
			{
				if (material->framecount != occ_framecount)
				{
					material->framecount = occ_framecount;
					material->count = 0;
				}
				if (material->count < MAX_MATERIAL_HITS)
				{
					S_StartSound (pos2, 0, CHAN_AUTO, material->hs_energy, 0.2, _ATTN_NORM, 0);
					material->count++;
				}
			}
		}
		break;

	case TE_MODEL_EXPLOSION:		/// Berserker: fixed bug: перегрузка сети при взрыве множества моделей
		MSG_ReadPos (&net_message, pos, large_map);
		pos2[0] = MSG_ReadByte (&net_message);
		pos2[1] = MSG_ReadByte (&net_message);
		pos2[2] = MSG_ReadByte (&net_message);
		CL_BrassDepris(pos, pos2, 2, 4, 8, false);
		break;

	case TE_EXPLOSION_NUM:		/// Berserker: fixed bug: перегрузка сети при взрыве множества объектов
	case TE_EXPLOSION_NUM_GLASS:
		MSG_ReadPos (&net_message, pos, large_map);
		pos2[0] = MSG_ReadByte (&net_message);
		pos2[1] = MSG_ReadByte (&net_message);
		pos2[2] = MSG_ReadByte (&net_message);
		color_r = MSG_ReadByte (&net_message);
		// big chunks
		if (color_r >= 4)
		{
			color_g = color_r >> 2;
			if (color_g > 8)
				color_g = 8;
		}
		else
			color_g = 0;
		// small chunks
		if (color_r > 16)
			color_r = 16;
		CL_BrassDepris(pos, pos2, color_g, 0, color_r, (type == TE_EXPLOSION_NUM_GLASS));
		break;

	case TE_EXPLOSION_ROCKET:
		MSG_ReadPos (&net_message, pos, large_map);
		CL_BrassDepris(pos, vec3_origin, 0, 0, rand()%5 + 3, false);
		break;

	case TE_GIBS_MAKRON:
		MSG_ReadPos (&net_message, absmin, large_map);
		size[0] = MSG_ReadByte (&net_message);
		size[1] = MSG_ReadByte (&net_message);
		size[2] = MSG_ReadByte (&net_message);
		ReadScaledDir(velocity);
		for (i=0; i<6; i++)
		{
			VectorAdd (absmin, size, pos2);
			pos[0] = pos2[0] + crandom() * size[0];
			pos[1] = pos2[1] + crandom() * size[1];
			pos[2] = pos2[2] + crandom() * size[2];
			if (!i)
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			else if (i == 1)
				CL_ThrowGib(cl_mod_gear, pos, velocity, false);
			else if (i == 5)
				CL_ThrowGib(cl_mod_metal, pos, velocity, false);
			else
			{
				VectorScale(velocity, 2, velocity);
				CL_ThrowGib(cl_mod_metal, pos, velocity, false);
			}
		}
		break;

	case TE_GIBS_ARM:
		MSG_ReadPos (&net_message, pos, large_map);
		ReadScaledDir(velocity);		// read the 5 bytes instead 12
		CL_ThrowGib(cl_mod_arm, pos, velocity, true);
		break;

	case TE_GIBS_LEG:
		MSG_ReadPos (&net_message, pos, large_map);
		ReadScaledDir(velocity);		// read the 5 bytes instead 12
		CL_ThrowGib(cl_mod_leg, pos, velocity, true);
		break;

	case TE_GIBS_HEAD:
		MSG_ReadPos (&net_message, pos, large_map);
		ReadScaledDir(velocity);		// read the 5 bytes instead 12
		CL_ThrowGib(cl_mod_head, pos, velocity, true);
		break;

	case TE_GIBS_GLADIATOR:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 0.5 + crandom()*0.15, absmin);
			VectorSet (dir, 0, 0, 9);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gladiator_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.25, absmin);
			VectorSet (dir, 0, 0, -8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gladiator_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.3 + crandom()*0.1, absmin);
			VectorSet (dir, 0, -18, -24);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gladiator_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.2 + crandom()*0.05, absmin);
			VectorSet (dir, 0, 14, -22);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gladiator_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.5 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 20, 4);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gladiator_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 2 + crandom()*0.5, absmin);
			VectorSet (dir, 0, 26, -8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gladiator_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.25, absmin);
			VectorSet (dir, 0, -20, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gladiator_g7, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.15 + crandom()*0.04, absmin);
			VectorSet (dir, 0, 0, 20);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_head2, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_INFANTRY:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 1.5 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 10, 10);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_infantry_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.1, absmin);
			VectorSet (dir, 0, 0, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_infantry_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 2 + crandom()*0.5, absmin);
			VectorSet (dir, 0, -12, 14);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_infantry_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.25, absmin);
			VectorSet (dir, 0, 0, 3);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_infantry_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.2 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -12, 5);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_infantry_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.3 + crandom()*0.1, absmin);
			VectorSet (dir, 0, 9, -14);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_infantry_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.2 + crandom()*0.06, absmin);
			VectorSet (dir, 0, -6, -12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_infantry_g7, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_FLIPPER:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
//			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов (а это для стоячих монстров)
			VectorSet(angles, 0, -90+best, 0);	// коррекция ориентации джибсов (рыба лежит и в живом, и в мертвом виде)

			VectorScale(velocity, 1 + crandom()*0.3, absmin);
			VectorSet (dir, 8, 0, -12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_flipper_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.8 + crandom()*0.25, absmin);
			VectorSet (dir, 0, 0, -12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_flipper_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.4 + crandom()*0.5, absmin);
			VectorSet (dir, -3, -8, -12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_flipper_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 2 + crandom()*0.75, absmin);
			VectorSet (dir, -36, -22, -6);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_flipper_g4, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<3; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
		}

		break;

	case TE_GIBS_MUTANT:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
//			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов (а это для стоячих монстров)
			VectorSet(angles, 0, 90+best, 0);	// коррекция ориентации джибсов (мутант горизонтален и в живом, и в мертвом виде)

			VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
			VectorSet (dir, 0, 0, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_mutant_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.9 + crandom()*0.4, absmin);
			VectorSet (dir, -8, 24, 10);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_mutant_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.5, absmin);
			VectorSet (dir, -8, -8, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_mutant_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, -16, -4, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_mutant_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.7 + crandom()*0.6, absmin);
			VectorSet (dir, -40, -8, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_mutant_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.7 + crandom()*0.6, absmin);
			VectorSet (dir, -40, 8, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_mutant_g6, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_HOVER:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 1.2 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 0, 13);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_hover_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.25, absmin);
			VectorSet (dir, 0, 0, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_hover_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.3 + crandom()*0.15, absmin);
			VectorSet (dir, 0, 6, -20);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_hover_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.3 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -12, 14);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_hover_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -12, 14);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_hover_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.3 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 12, 14);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_hover_g6, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<3; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
		}

		break;

	case TE_GIBS_GUNNER:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 1.1 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 0, 25);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gunner_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.6 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -16, 25);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gunner_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.9 + crandom()*0.4, absmin);
			VectorSet (dir, 0, -4, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gunner_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.3 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -6, -10);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gunner_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.9 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 4, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gunner_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.3 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 6, -20);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gunner_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.9 + crandom()*0.45, absmin);
			VectorSet (dir, 0, -16, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gunner_g7, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.6 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 16, 25);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gunner_g8, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.2 + crandom()*0.5, absmin);
			VectorSet (dir, 0, 19, 2);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_gunner_g9, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_FLOATER:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 0.7 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 0, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_floater_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.3, absmin);
			VectorSet (dir, 8, 20, 20);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_floater_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -26, -6);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_floater_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.3 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 0, 24);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_floater_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.5 + crandom()*0.4, absmin);
			VectorSet (dir, 8, 0, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_floater_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 24, -6);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_floater_g6, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
		}

		break;

	case TE_GIBS_FLYER:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
//			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов (а это для стоячих монстров)
			VectorSet(angles, 0, -90+best, 0);	// коррекция ориентации джибсов (flyer горизонтален в живом виде)

			VectorScale(velocity, 1 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 0, -16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_flyer_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.6 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 8, -16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_flyer_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.3 + crandom()*0.3, absmin);
			VectorSet (dir, 8, 8, -16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_flyer_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.6 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -8, -16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_flyer_g4, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
		}

		break;

	case TE_GIBS_SOLDIER:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);
		r = MSG_ReadByte(&net_message);

		if (r_personal_gibs->value && cl_mod_soldier_g1 && cl_mod_soldier_g2 && cl_mod_soldier_g3 && cl_mod_soldier_g4 && cl_mod_soldier_g5 && cl_mod_soldier_g6 && cl_mod_soldier_g7 && cl_mod_soldier_g8 && cl_mod_soldier_g9)
		{
			if (r == 0 || r == 1)
			{	// soldier_light
				skin = R_RegisterSkin ("models/monsters/soldier/skin_ltp");
				bump = R_RegisterBump ("models/monsters/soldier/skin_ltp", skin, cl_mod_soldier_g1);
				light = R_RegisterLight ("models/monsters/soldier/skin_ltp");
			}
			else if (r == 2 || r == 3)
			{	// soldier
				skin = R_RegisterSkin ("models/monsters/soldier/pain");
				bump = R_RegisterBump ("models/monsters/soldier/pain", skin, cl_mod_soldier_g1);
				light = R_RegisterLight ("models/monsters/soldier/pain");
			}
			else	// 4 || 5
			{	// soldier_ss
				skin = R_RegisterSkin ("models/monsters/soldier/skin_ssp");
				bump = R_RegisterBump ("models/monsters/soldier/skin_ssp", skin, cl_mod_soldier_g1);
				light = R_RegisterLight ("models/monsters/soldier/skin_ssp");
			}

			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 1.5 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 0, 10);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_soldier_g1, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 1 + crandom()*0.4, absmin);
			VectorSet (dir, 0, -16, 14);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_soldier_g2, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.6 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 0, 24);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_soldier_g3, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 1 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 8, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_soldier_g4, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 1 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 24, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_soldier_g5, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.8 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 0, 2);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_soldier_g6, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -6, -6);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_soldier_g7, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -6, -18);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_soldier_g8, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 2, -18);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_soldier_g9, pos, absmin, angles, skin, bump, light);
		}
		else
		{
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_TANK:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);
		r = MSG_ReadByte(&net_message);

		if (r_personal_gibs->value && cl_mod_tank_g1 && cl_mod_tank_g2 && cl_mod_tank_g3 && cl_mod_tank_g4 && cl_mod_tank_g5 && cl_mod_tank_g6 && cl_mod_tank_g7 && cl_mod_tank_g8 && cl_mod_tank_g9)
		{
			if (r == 0 || r == 1)
			{	// tank
				skin = R_RegisterSkin ("models/monsters/tank/pain");
				bump = R_RegisterBump ("models/monsters/tank/pain", skin, cl_mod_tank_g1);
				light = R_RegisterLight ("models/monsters/tank/pain");
			}
			else ///if (r == 2 || r == 3)
			{	// tank_commander
				skin = R_RegisterSkin ("models/monsters/ctank/pain");
				bump = R_RegisterBump ("models/monsters/ctank/pain", skin, cl_mod_tank_g1);
				light = R_RegisterLight ("models/monsters/ctank/pain");
			}

			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 1.5 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 0, 10);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_tank_g1, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.6 + crandom()*0.25, absmin);
			VectorSet (dir, 0, 8, -8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_tank_g2, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.9 + crandom()*0.3, absmin);
			VectorSet (dir, -8, -26, 12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_tank_g3, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.9 + crandom()*0.5, absmin);
			VectorSet (dir, 0, -22, 6);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_tank_g4, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.8 + crandom()*0.5, absmin);
			VectorSet (dir, 0, 16, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_tank_g5, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.3 + crandom()*0.15, absmin);
			VectorSet (dir, 0, -8, -20);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_tank_g6, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.2 + crandom()*0.15, absmin);
			VectorSet (dir, 0, -12, -38);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_tank_g7, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.3 + crandom()*0.15, absmin);
			VectorSet (dir, 0, 12, -35);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_tank_g8, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.15 + crandom()*0.1, absmin);
			VectorSet (dir, 0, 12, -37);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_tank_g9, pos, absmin, angles, skin, bump, light);
		}
		else
		{
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_metal, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_chest, pos, velocity, false);
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_gear, pos, velocity, false);
		}

		break;

	case TE_GIBS_INSANE:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);
		r = MSG_ReadByte(&net_message);

		if (r_personal_gibs->value && cl_mod_insane_g1 && cl_mod_insane_g2 && cl_mod_insane_g3 && cl_mod_insane_g4 && cl_mod_insane_g5 && cl_mod_insane_g6 && cl_mod_insane_g7 && cl_mod_insane_g8)
		{
			if (r == 1)
			{
				skin = R_RegisterSkin ("models/monsters/insane/i_skin02");
				bump = R_RegisterBump ("models/monsters/insane/i_skin02", skin, cl_mod_insane_g1);
				light = R_RegisterLight ("models/monsters/insane/i_skin02");
			}
			else if (r == 2)
			{
				skin = R_RegisterSkin ("models/monsters/insane/i_skin03");
				bump = R_RegisterBump ("models/monsters/insane/i_skin03", skin, cl_mod_insane_g1);
				light = R_RegisterLight ("models/monsters/insane/i_skin03");
			}
			else
			{
				skin = R_RegisterSkin ("models/monsters/insane/i_skin");
				bump = R_RegisterBump ("models/monsters/insane/i_skin", skin, cl_mod_insane_g1);
				light = R_RegisterLight ("models/monsters/insane/i_skin");
			}

			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 0.7 + crandom()*0.25, absmin);
			VectorSet (dir, 0, 0, 18);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g1, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.9 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 0, 12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g2, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
			VectorSet (dir, 0, 10, 12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g3, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
			VectorSet (dir, 0, -10, 12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g4, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.35 + crandom()*0.15, absmin);
			VectorSet (dir, 0, 2, -2);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g5, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.25 + crandom()*0.1, absmin);
			VectorSet (dir, 0, 2, -14);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g6, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.35 + crandom()*0.15, absmin);
			VectorSet (dir, 0, -2, -2);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g7, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.25 + crandom()*0.1, absmin);
			VectorSet (dir, 0, -2, -14);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g8, pos, absmin, angles, skin, bump, light);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_DEADSOLDIER:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value && cl_mod_insane_g2 && cl_mod_insane_g3 && cl_mod_insane_g4 && cl_mod_insane_g5 && cl_mod_insane_g6 && cl_mod_insane_g7 && cl_mod_insane_g8)
		{	// т.к. юзаем мультишкурные insane gibs, то укажем явно тут шкуры трупа
			skin = R_RegisterSkin ("models/deadbods/dude/dead1");
			bump = R_RegisterBump ("models/deadbods/dude/dead1", skin, cl_mod_insane_g1);
			light = R_RegisterLight ("models/deadbods/dude/dead1");

			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);

			VectorSet(angles, 0, 90+best, 0);	// коррекция ориентации джибсов

			VectorScale(velocity, 0.4 + crandom()*0.2, absmin);
			VectorSet (dir, 0, 0, 12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g2, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
			VectorSet (dir, 0, 10, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g3, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.8 + crandom()*0.4, absmin);
			VectorSet (dir, 0, -10, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g4, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.8 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 2, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g5, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.8 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 2, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g6, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.8 + crandom()*0.4, absmin);
			VectorSet (dir, 0, -2, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g7, pos, absmin, angles, skin, bump, light);

			VectorScale(velocity, 0.8 + crandom()*0.4, absmin);
			VectorSet (dir, 0, -2, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_insane_g8, pos, absmin, angles, skin, bump, light);
		}
		else
		{
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_BARREL_DEBRIS:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value && cl_mod_barrel_g1 && cl_mod_barrel_g2 && cl_mod_barrel_g3 && cl_mod_barrel_g4)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);

			VectorSet(angles, 0, 90+best, 0);	// коррекция ориентации джибсов

			VectorScale(velocity, 0.4 + crandom()*0.1, absmin);
			VectorSet (dir, 0, 0, -19);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_barrel_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.3 + crandom()*0.5, absmin);
			velocity[2] -= 0.3;			// пусть бок летит пониже
			VectorSet (dir, 0, -16, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_barrel_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.3 + crandom()*0.5, absmin);
			velocity[2] -= 0.3;			// пусть бок летит пониже
			VectorSet (dir, 0, 16, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_barrel_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.5 + crandom()*0.3, absmin);
			velocity[2] += 0.3;			// пусть верх летит повыше
			VectorSet (dir, 0, 0, 20);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_barrel_g4, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			VectorSet(pos, 16, 16, 20);
			CL_BrassDepris(pos2, pos, 2, 4, 8, false);
		}

		break;

	case TE_GIBS_PLAYER:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);
		r = MSG_ReadByte(&net_message);

		if (!r_personal_gibs->value)
			goto defgibs;
		ci = &cl.clientinfo[r];
		if (!ci)
			goto defgibs;
		if (!ci->model)
			goto defgibs;
		slash = strstr(ci->model->name, "\\");
		if (!slash)
			slash = strstr(ci->model->name, "/");
		if (slash)
		{
			if (!Q_strncasecmp(slash+1, "male", 4))
			{
				if (!cl_mod_male_g1 || !cl_mod_male_g2 || !cl_mod_male_g3 || !cl_mod_male_g4 || !cl_mod_male_g5 || !cl_mod_male_g6 || !cl_mod_male_g7 || !cl_mod_male_g8)
					goto defgibs;

				VectorSet(angles, 0, -best, 0);
				AngleVectors (angles, forward, right, up);

				VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

				VectorScale(velocity, 0.7 + crandom()*0.25, absmin);
				VectorSet (dir, 0, 0, 18);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_male_g1, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.9 + crandom()*0.3, absmin);
				VectorSet (dir, 0, 0, 12);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_male_g2, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
				VectorSet (dir, 0, 10, 12);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_male_g3, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
				VectorSet (dir, 0, -10, 12);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_male_g4, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.35 + crandom()*0.15, absmin);
				VectorSet (dir, 0, 2, -2);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_male_g5, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.25 + crandom()*0.1, absmin);
				VectorSet (dir, 0, 2, -14);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_male_g6, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.35 + crandom()*0.15, absmin);
				VectorSet (dir, 0, -2, -2);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_male_g7, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.25 + crandom()*0.1, absmin);
				VectorSet (dir, 0, -2, -14);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_male_g8, pos, absmin, angles, ci->skin, ci->bump, ci->light);
			}
			else if (!Q_strncasecmp(slash+1, "female", 6))
			{
				if (!cl_mod_female_g1 || !cl_mod_female_g2 || !cl_mod_female_g3 || !cl_mod_female_g4 || !cl_mod_female_g5 || !cl_mod_female_g6 || !cl_mod_female_g7 || !cl_mod_female_g8 || !cl_mod_female_g9)
					goto defgibs;

				VectorSet(angles, 0, -best, 0);
				AngleVectors (angles, forward, right, up);

				VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

				VectorScale(velocity, 0.9 + crandom()*0.3, absmin);
				VectorSet (dir, 0, 0, 12);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_female_g1, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.7 + crandom()*0.25, absmin);
				VectorSet (dir, 0, 0, 18);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_female_g2, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
				VectorSet (dir, 0, 10, 12);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_female_g3, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
				VectorSet (dir, 0, -10, 16);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_female_g4, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
				VectorSet (dir, 0, -10, 8);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_female_g5, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.35 + crandom()*0.15, absmin);
				VectorSet (dir, 0, -2, -2);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_female_g6, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.35 + crandom()*0.15, absmin);
				VectorSet (dir, 0, 2, -2);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_female_g7, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.25 + crandom()*0.1, absmin);
				VectorSet (dir, 0, -2, -14);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_female_g8, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.25 + crandom()*0.1, absmin);
				VectorSet (dir, 0, 2, -14);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_female_g9, pos, absmin, angles, ci->skin, ci->bump, ci->light);
			}
			else if (!Q_strncasecmp(slash+1, "cyborg", 6))
			{
				if (!cl_mod_cyborg_g1 || !cl_mod_cyborg_g2 || !cl_mod_cyborg_g3 || !cl_mod_cyborg_g4 || !cl_mod_cyborg_g5 || !cl_mod_cyborg_g6 || !cl_mod_cyborg_g7 || !cl_mod_cyborg_g8 || !cl_mod_cyborg_g9 || !cl_mod_cyborg_g10)
					goto defgibs;

				VectorSet(angles, 0, -best, 0);
				AngleVectors (angles, forward, right, up);

				VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

				VectorScale(velocity, 0.9 + crandom()*0.3, absmin);
				VectorSet (dir, 0, 0, 12);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_cyborg_g1, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.7 + crandom()*0.25, absmin);
				VectorSet (dir, 0, 0, -2);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_cyborg_g2, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.7 + crandom()*0.25, absmin);
				VectorSet (dir, 0, 0, 18);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_cyborg_g3, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.7 + crandom()*0.25, absmin);
				VectorSet (dir, 0, -8, 18);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_cyborg_g4, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 1 + crandom()*0.5, absmin);
				VectorSet (dir, 0, -10, 15);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_cyborg_g5, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
				VectorSet (dir, 0, -10, 12);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_cyborg_g6, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 1 + crandom()*0.4, absmin);
				VectorSet (dir, 0, 10, 15);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_cyborg_g7, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
				VectorSet (dir, 0, 10, 12);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_cyborg_g8, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.25 + crandom()*0.1, absmin);
				VectorSet (dir, 0, 2, -14);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_cyborg_g9, pos, absmin, angles, ci->skin, ci->bump, ci->light);

				VectorScale(velocity, 0.25 + crandom()*0.1, absmin);
				VectorSet (dir, 0, -2, -14);
				pos[0] = pos2[0] + DotProduct (dir, forward);
				pos[1] = pos2[1] - DotProduct (dir, right);
				pos[2] = pos2[2] + DotProduct (dir, up);
				CL_ThrowMonsterGib(cl_mod_cyborg_g10, pos, absmin, angles, ci->skin, ci->bump, ci->light);
			}
			else goto defgibs;
		}
		else
		{
defgibs:	for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}
		break;

	case TE_GIBS_BRAIN:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 0.3 + crandom()*0.2, absmin);
			VectorSet (dir, 0, 6, -16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_brain_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 2, -2);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_brain_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.4 + crandom()*0.2, absmin);
			VectorSet (dir, 0, 10, 4);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_brain_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.3 + crandom()*0.2, absmin);
			VectorSet (dir, 0, -6, -16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_brain_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.8 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -6, 4);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_brain_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -10, -2);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_brain_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 0, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_brain_g7, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 10, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_brain_g8, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.75 + crandom()*0.2, absmin);
			VectorSet (dir, 0, 0, 24);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_brain_g9, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -10, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_brain_g10, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_PARASITE:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
//			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов (а это для стоячих монстров)
			VectorSet(angles, 0, -90+best, 0);	// коррекция ориентации джибсов (паразит находится и в живом, и в мертвом виде)

			VectorScale(velocity, 0.9 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 0, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_parasite_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.3, absmin);
			VectorSet (dir, 2, 8, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_parasite_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.3, absmin);
			VectorSet (dir, 2, -16, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_parasite_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, 2, -8, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_parasite_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.4 + crandom()*0.3, absmin);
			VectorSet (dir, -8, -8, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_parasite_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.6 + crandom()*0.3, absmin);
			VectorSet (dir, -8, 4, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_parasite_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.8 + crandom()*0.3, absmin);
			VectorSet (dir, -12, 6, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_parasite_g7, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_MEDIC:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
//			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов (а это для стоячих монстров)
			VectorSet(angles, 0, -90+best, 0);	// коррекция ориентации джибсов (медик находится и в живом, и в мертвом виде)

			VectorScale(velocity, 1 + crandom()*0.3, absmin);
			VectorSet (dir, 16, 0, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_medic_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.75 + crandom()*0.3, absmin);
			VectorSet (dir, 8, 10, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_medic_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.6 + crandom()*0.3, absmin);
			VectorSet (dir, 16, 10, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_medic_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.3, absmin);
			VectorSet (dir, 14, 14, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_medic_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.3, absmin);
			VectorSet (dir, 14, -14, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_medic_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.8 + crandom()*0.3, absmin);
			VectorSet (dir, 14, -14, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_medic_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, -10, 10, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_medic_g7, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, -10, -10, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_medic_g8, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
			VectorSet (dir, -10, -10, -4);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_medic_g9, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.3 + crandom()*0.1, absmin);
			VectorSet (dir, -10, -10, -8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_medic_g10, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_BERSERK:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, 3, -18, 14);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_berserk_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 0, 22);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_berserk_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 18, 20);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_berserk_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.2, absmin);
			VectorSet (dir, 2, 25, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_berserk_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -3, 4);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_berserk_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.15, absmin);
			VectorSet (dir, 0, -3, -12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_berserk_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 3, 4);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_berserk_g7, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.15, absmin);
			VectorSet (dir, 0, 3, -12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_berserk_g8, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_CHICK:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 0.5 + crandom()*0.15, absmin);
			VectorSet (dir, 0, -8, -18);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_chick_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.3, absmin);
			VectorSet (dir, 0, -4, -2);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_chick_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.15, absmin);
			VectorSet (dir, 0, 16, -6);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_chick_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.9 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 2, 6);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_chick_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.6 + crandom()*0.2, absmin);
			VectorSet (dir, 0, -12, 6);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_chick_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.6 + crandom()*0.5, absmin);
			VectorSet (dir, 0, -6, 12);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_chick_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.9 + crandom()*0.2, absmin);
			VectorSet (dir, 0, 0, 20);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_chick_g7, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.6 + crandom()*0.5, absmin);
			VectorSet (dir, 0, 6, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_chick_g8, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 16, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_chick_g9, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<2; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_bone, pos, velocity, false);
			}
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_head2, pos, velocity, false);
		}

		break;

	case TE_GIBS_JORG:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 2.5 + crandom()*0.8, absmin);
			VectorSet (dir, 0, 0, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.4 + crandom()*0.6, absmin);
			VectorSet (dir, 0, 16, -8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 24, -16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.2 + crandom()*0.6, absmin);
			VectorSet (dir, 0, -24, -16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.6 + crandom()*0.2, absmin);
			VectorSet (dir, 0, -24, -16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.4 + crandom()*0.6, absmin);
			VectorSet (dir, 0, -24, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.4 + crandom()*0.6, absmin);
			VectorSet (dir, 0, 24, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g7, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.8 + crandom(), absmin);
			VectorSet (dir, 8, 8, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g8, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.8 + crandom(), absmin);
			VectorSet (dir, 8, -8, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g9, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.8 + crandom(), absmin);
			VectorSet (dir, -8, 8, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g10, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.8 + crandom(), absmin);
			VectorSet (dir, -8, -8, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_jorg_g11, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			for (i=0; i<8; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_metal, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_chest, pos, velocity, false);
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_gear, pos, velocity, false);
		}

		break;

	case TE_GIBS_BOSS2:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов

			VectorScale(velocity, 1.8 + crandom(), absmin);
			VectorSet (dir, -16, -24, 24);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.2 + crandom()*0.4, absmin);
			VectorSet (dir, -16, -24, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.4, absmin);
			VectorSet (dir, 0, -24, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.7 + crandom()*0.5, absmin);
			VectorSet (dir, 0, -16, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1 + crandom()*0.4, absmin);
			VectorSet (dir, 0, -16, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.2 + crandom()*0.4, absmin);
			VectorSet (dir, 0, -8, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.3 + crandom()*0.4, absmin);
			VectorSet (dir, 0, -2, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g7, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 2.5 + crandom()*0.5, absmin);
			VectorSet (dir, 0, 0, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g8, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.3 + crandom()*0.2, absmin);
			VectorSet (dir, 0, 16, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g9, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.4 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 8, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g10, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.9 + crandom()*0.5, absmin);
			VectorSet (dir, 0, 2, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g11, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.5 + crandom()*0.5, absmin);
			VectorSet (dir, 0, 16, 8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g12, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.5 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 0, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_boss2_g13, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			for (i=0; i<8; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_metal, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_chest, pos, velocity, false);
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_gear, pos, velocity, false);
		}

		break;

	case TE_GIBS_SUPERTANK:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		if (r_personal_gibs->value)
		{
			VectorSet(angles, 0, -best, 0);
			AngleVectors (angles, forward, right, up);
//			VectorSet(angles, 180, 90+best, 90);	// коррекция ориентации джибсов (а это для стоячих монстров)
			VectorSet(angles, 0, -90+best, 0);	// коррекция ориентации джибсов (supertank находится и в живом, и в мертвом виде)

			VectorScale(velocity, 2.3 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 64, -8);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_supertank_g1, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.5, absmin);
			VectorSet (dir, 32, -48, -56);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_supertank_g2, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.5 + crandom()*0.4, absmin);
			VectorSet (dir, 0, 16, 0);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_supertank_g3, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.8 + crandom()*0.5, absmin);
			VectorSet (dir, 0, 28, 30);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_supertank_g4, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 1.5 + crandom()*0.3, absmin);
			VectorSet (dir, 0, 48, 22);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_supertank_g5, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.7 + crandom()*0.2, absmin);
			VectorSet (dir, 0, -16, 16);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_supertank_g6, pos, absmin, angles, NULL, NULL, NULL);

			VectorScale(velocity, 0.2 + crandom()*0.15, absmin);
			VectorSet (dir, 0, 8, -48);
			pos[0] = pos2[0] + DotProduct (dir, forward);
			pos[1] = pos2[1] - DotProduct (dir, right);
			pos[2] = pos2[2] + DotProduct (dir, up);
			CL_ThrowMonsterGib(cl_mod_supertank_g7, pos, absmin, angles, NULL, NULL, NULL);
		}
		else
		{
			for (i=0; i<4; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_meat, pos, velocity, false);
			}
			for (i=0; i<8; i++)
			{
				pos[0] = pos2[0] + crandom() * 8;
				pos[1] = pos2[1] + crandom() * 8;
				pos[2] = pos2[2] + crandom() * 8 + 16;
				CL_ThrowGib(cl_mod_metal, pos, velocity, false);
			}
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_chest, pos, velocity, false);
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_gear, pos, velocity, false);
		}

		break;

	case TE_GIBS_2BONES_4MEAT_HEAD2:
		MSG_ReadPos (&net_message, pos2, large_map);
		ReadScaledDir(velocity);
		best = MSG_ReadAngle(&net_message);

		for (i=0; i<2; i++)
		{
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_bone, pos, velocity, false);
		}
		for (i=0; i<4; i++)
		{
			pos[0] = pos2[0] + crandom() * 8;
			pos[1] = pos2[1] + crandom() * 8;
			pos[2] = pos2[2] + crandom() * 8 + 16;
			CL_ThrowGib(cl_mod_meat, pos, velocity, false);
		}
		pos[0] = pos2[0] + crandom() * 8;
		pos[1] = pos2[1] + crandom() * 8;
		pos[2] = pos2[2] + crandom() * 8 + 16;
		CL_ThrowGib(cl_mod_head2, pos, velocity, false);

		break;

	case TE_EXPLOSION2:
	case TE_GRENADE_EXPLOSION:
	case TE_GRENADE_EXPLOSION_WATER:
		MSG_ReadPos (&net_message, pos, large_map);
		VectorSet(dir, 0,0,1);

		ex = CL_AllocExplosion ();
		ex->type = ex_poly;
		ex->start = cl.frame.servertime - 100;
		ex->light = 250;
		ex->lightcolor[0] = 1.0;
		ex->lightcolor[1] = 0.5;
		ex->lightcolor[2] = 0.5;
		if(r_sprite_expl->value)
		{
			pos[2] += 25;	// Berserker: fixed bug - когда взрывается граната на земле, земля не затеняет низшие слои (в данном движке подразумевается, что вектор гравитации фиксирован и направлен вниз) (shifting = 10% from LightRadius)
			VectorCopy (pos, ex->light_origin);
			pos[2] += 11;	// Чтобы спрайт взрыва не "пробивал" пол
			VectorCopy (pos, ex->ent.origin);
			ex->ent.flags = RF_FULLBRIGHT|RF_TRANSLUCENT;
			ex->ent.model = cl_mod_exp;
			ex->ent.alpha = 0.30;
			ex->frames = 13;
			if(p_explosion->value)
			{
				VectorSet(pos2, 0,0,9);
				CL_ParticleEffect3 (pos, pos2, 180,180,180, 8, part_bigsmoke);		// Дым
				if(p_explosion->value>1)
				{
					pos[0] -= 16;
					CL_ParticleEffect3 (pos, pos2, 180,180,180, 4, part_bigsmoke);		// Дым
					pos[1] -= 16;
					CL_ParticleEffect3 (pos, pos2, 180,180,180, 4, part_bigsmoke);		// Дым
					pos[0] += 32;
					CL_ParticleEffect3 (pos, pos2, 180,180,180, 4, part_bigsmoke);		// Дым
					pos[1] += 32;
					CL_ParticleEffect3 (pos, pos2, 180,180,180, 4, part_bigsmoke);		// Дым
					pos[0] -= 16;
					pos[1] -= 16;
				}
			}
			pos[2] -= 35;///36;	// (возврат на нач.значение)
		}
		else
		{
			ex->ent.angles[1] = rand() % 360;
			VectorCopy (pos, ex->ent.origin);
			pos[2] += 25;	// Berserker: fixed bug - когда взрывается граната на земле, земля не затеняет низшие слои (в данном движке подразумевается, что вектор гравитации фиксирован и направлен вниз) (shifting = 10% from LightRadius)
			VectorCopy (pos, ex->light_origin);
			ex->ent.flags = RF_FULLBRIGHT;
			ex->ent.model = cl_mod_explo4;
			ex->frames = 19;
			ex->baseframe = 30;
			CL_ExplosionParticles (pos);
			pos[2] -= 24;///25;	// (возврат на нач.значение)
		}
		if (type == TE_GRENADE_EXPLOSION_WATER)
			S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, 0.08, 0);
		else
			S_StartSound(pos, 0, 0, cl_sfx_grenexp, 1, 0.08, 0);

		lt = r_explosionMarkLifeTime->value;
		if (lt <= 0.1 )	lt = 0.1;
		R_SpawnDecal(pos, NULL/*dir*/, explosion_mrk_object, 80,100, 20*lt,30*lt, 0, 360, dt_mul, true, 0, 0, NULL, false);
		break;

	case TE_EXPLOSION1:
	case TE_EXPLOSION1_BIG:
	case TE_EXPLOSION1_NP:
	case TE_ROCKET_EXPLOSION:
	case TE_ROCKET_EXPLOSION_WATER:
		MSG_ReadPos (&net_message, pos, large_map);
		// взрыв в воздухе, dir не определен
		best = 999;
		VectorClear(dir);
		for (i = 0; i < 6; i++)
		{
			vec3_t	side;
			VectorClear(side);
			if (i<3)
				side[i] = 1;
			else
				side[i-3] = -1;
			VectorMA(pos, 50, side, org);	// radius=50
			trace = CL_PMTraceWorld(pos, vec3_origin, vec3_origin, org, MASK_SOLID);
			if (trace.fraction != 1.0 && !trace.allsolid)
			{
				if (trace.fraction < best)
				{
					best = trace.fraction;
					VectorCopy(trace.plane.normal, dir);
				}
			}
		}
		ex = CL_AllocExplosion ();
		ex->type = ex_poly;
		ex->start = cl.frame.servertime - 100;
		ex->light = 250;
		ex->lightcolor[0] = 1.0;
		ex->lightcolor[1] = 1.0;
		ex->lightcolor[2] = 0.5;
		if(r_sprite_expl->value)
		{
			VectorMA (pos, 25, dir, pos);	// Berserker: fixed bug - немного отодвинем вспышку от заграждения, чтобы правильнее рисовалась тень (shifting = 10% from LightRadius)
			VectorCopy (pos, ex->ent.origin);
			VectorCopy (pos, ex->light_origin);
			ex->ent.flags = RF_FULLBRIGHT|RF_TRANSLUCENT;
			ex->ent.model = cl_mod_rlexp;
			ex->ent.alpha = 0.3;
			ex->frames = 8;
			if(p_explosion->value)
			{
				VectorSet(pos2, 0,0,6);
				CL_ParticleEffect3 (pos, pos2, 180,180,180, 8, part_bigsmoke);		// Дым
				if(p_explosion->value>1)
				{
					pos[0] -= 16;
					CL_ParticleEffect3 (pos, pos2, 180,180,180, 4, part_bigsmoke);		// Дым
					pos[1] -= 16;
					CL_ParticleEffect3 (pos, pos2, 180,180,180, 4, part_bigsmoke);		// Дым
					pos[0] += 32;
					CL_ParticleEffect3 (pos, pos2, 180,180,180, 4, part_bigsmoke);		// Дым
					pos[1] += 32;
					CL_ParticleEffect3 (pos, pos2, 180,180,180, 4, part_bigsmoke);		// Дым
					pos[0] -= 16;
					pos[1] -= 16;
				}
			}
			VectorMA (pos, -24/*-25*/, dir, pos);	// (возврат на нач.значение)
		}
		else
		{
			ex->ent.angles[1] = rand() % 360;
			VectorCopy (pos, ex->ent.origin);
			VectorMA (pos, 25, dir, pos);	// Berserker: fixed bug - немного отодвинем вспышку от заграждения, чтобы правильнее рисовалась тень (shifting = 10% from LightRadius)
			VectorCopy (pos, ex->light_origin);
			ex->ent.flags = RF_FULLBRIGHT;
			if (type != TE_EXPLOSION1_BIG)				// PMM
				ex->ent.model = cl_mod_explo4;			// PMM
			else
				ex->ent.model = cl_mod_explo4_big;
			ex->frames = 15;
			if ((type != TE_EXPLOSION1_BIG) && (type != TE_EXPLOSION1_NP))		// PMM
				CL_ExplosionParticles (pos);									// PMM
			if (frand() < 0.5)
				ex->baseframe = 15;
			VectorMA (pos, -24/*-25*/, dir, pos);	// (возврат на нач.значение)
		}

		if (type == TE_ROCKET_EXPLOSION_WATER)
			S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, 0.08, 0);
		else
			S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, 0.08, 0);

		lt = r_explosionMarkLifeTime->value;
		if (lt <= 0.1 )	lt = 0.1;
		R_SpawnDecal(pos, NULL/*dir*/, explosion_mrk_object, 70,90, 20*lt,30*lt, 0, 360, dt_mul, true, 0, 0, NULL, false);
		break;

	case TE_BFG_EXPLOSION:
		MSG_ReadPos (&net_message, pos, large_map);
		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT;
		ex->start = cl.frame.servertime - 100;
		ex->light = 250;
		VectorCopy(pos, ex->light_origin);
		ex->lightcolor[0] = 0.0;
		ex->lightcolor[1] = 1.0;
		ex->lightcolor[2] = 0.0;
		ex->ent.model = cl_mod_bfg_explo;
		ex->ent.flags |= RF_TRANSLUCENT;
		ex->ent.alpha = 0.30;
		ex->frames = 4;
		break;

	case TE_BFG_BIGEXPLOSION:
		MSG_ReadPos (&net_message, pos, large_map);
		cnt = 384 * p_sparks->value;
		if (cnt)
			CL_BFGExplosionParticles (pos, cnt);
		lt = r_bfgMarkLifeTime->value;
		if (lt <= 0.1 )	lt = 0.1;
		R_SpawnDecal(pos, NULL, bfg_explosion_mrk_object, 80,90, 30*lt,40*lt, 0, 360, dt_add, true, 0, 0, NULL, false);
		break;

	case TE_BFG_LASER:
		CL_ParseLaser (0xd0d1d2d3, large_map);
		break;

	case TE_BUBBLETRAIL_WATER:
	case TE_BUBBLETRAIL_LAVA:
	case TE_BUBBLETRAIL_SLIME:
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadPos (&net_message, pos2, large_map);
		CL_BubbleTrail (pos, pos2, type);
		break;

	case TE_PARASITE_ATTACK:
	case TE_MEDIC_CABLE_ATTACK:
		/*ent = */CL_ParseBeam (cl_mod_parasite_segment, large_map);
		break;

	case TE_BOSSTPORT:			// boss teleporting to station
		MSG_ReadPos (&net_message, pos, large_map);
		CL_BigTeleportParticles (pos);
		S_StartSound(pos, 0, 0, S_RegisterSound("misc/bigtele.wav"), 1, _ATTN_NONE, 0);
		break;

	case TE_GRAPPLE_CABLE:
		/*ent = */CL_ParseBeam2 (cl_mod_grapple_cable, large_map);
		break;

	// RAFAEL
	case TE_WELDING_SPARKS:
		cnt = MSG_ReadByte (&net_message);
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		color = MSG_ReadByte (&net_message);
		if (cnt*p_sparks->value)
		{
			color_r = d_8to24table[color*3+0];
			color_g = d_8to24table[color*3+1];
			color_b = d_8to24table[color*3+2];
			CL_ParticleEffect2 (pos, dir, color_r, color_g, color_b, cnt*p_sparks->value, part_simple);
		}

		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->type = ex_flash;
		// note to self
		// we need a better no draw flag
		ex->ent.flags = RF_BEAM;
		ex->start = cl.frame.servertime - 0.1;
		ex->light = 100 + (rand()%75);
		ex->lightcolor[0] = 1.0;
		ex->lightcolor[1] = 1.0;
		ex->lightcolor[2] = 0.3;
		ex->ent.model = cl_mod_flash;
		ex->frames = 2;
		break;

	case TE_GREENBLOOD:
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		if(p_blood->value)
		{
			R_LightForPoint(pos, end);
			/// don't allow too dark!
			if (end[0] < 0.02)	end[0] = 0.02;
			if (end[1] < 0.02)	end[1] = 0.02;
			if (end[2] < 0.02)	end[2] = 0.02;

			color_r = p_greenblood_r->value * end[0] * 256.0;
			color_g = p_greenblood_g->value * end[1] * 256.0;
			color_b = p_greenblood_b->value * end[2] * 256.0;
			if (color_r < 1)	color_r = 1;
			if (color_g < 1)	color_g = 1;
			if (color_b < 1)	color_b = 1;

			cnt = 8*p_blood->value;
			if (cnt)
				CL_ParticleEffect (pos, dir, color_r,color_g,color_b, cnt, part_greenblood, PARTICLE_FRICTION);	// Berserker: Уменьшил кол-во пятен крови (так как speedup - мараем только 1/16 вероятностью, то пусть будет крови примерно на 2 пятна)
		}
		break;

	// RAFAEL
	case TE_TUNNEL_SPARKS:
		cnt = MSG_ReadByte (&net_message);
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		color = MSG_ReadByte (&net_message);
		if (cnt*p_sparks->value)
		{
			color_r = d_8to24table[color*3+0];
			color_g = d_8to24table[color*3+1];
			color_b = d_8to24table[color*3+2];
			CL_ParticleEffect3 (pos, dir, color_r, color_g, color_b, cnt*p_sparks->value, part_simple);
		}
		break;

	// RAFAEL
	case TE_BLUEHYPERBLASTER:
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadPos (&net_message, dir, large_map);
		cnt = 20 * p_sparks->value;
		if (cnt)
			CL_BlasterParticles (pos, dir, 220, 150, 64, cnt);
		break;

	// RAFAEL
	case TE_PLASMA_EXPLOSION:
		MSG_ReadPos (&net_message, pos, large_map);
		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT;
		ex->start = cl.frame.servertime - 100;
		ex->light = 350;
		ex->lightcolor[0] = 1.0; 
		ex->lightcolor[1] = 0.5;
		ex->lightcolor[2] = 0.5;
		ex->ent.angles[1] = rand() % 360;
		ex->ent.model = cl_mod_explo4;
		if (frand() < 0.5)
			ex->baseframe = 15;
		ex->frames = 15;
		CL_ExplosionParticles (pos);
		S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1.0, 0.08, 0);
		break;

	case TE_BLASTER2:			// green blaster hitting wall
	case TE_FLECHETTE:			// flechette
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);

		// PMM
		if (type == TE_BLASTER2)
		{
			color_r = d_8to24table[0xd0*3+0];
			color_g = d_8to24table[0xd0*3+1];
			color_b = d_8to24table[0xd0*3+2];
		}
		else
		{
			color_r = d_8to24table[0x6f*3+0];
			color_g = d_8to24table[0x6f*3+1];
			color_b = d_8to24table[0x6f*3+2];
		}
		cnt = 30 * p_sparks->value;
		if (cnt)
			CL_BlasterParticles (pos, dir, color_r, color_g, color_b, cnt);

		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->ent.angles[0] = acos(dir[2])/M_PI*180;
		// PMM - fixed to correct for pitch of 0
		if (dir[0])
			ex->ent.angles[1] = atan2(dir[1], dir[0])/M_PI*180;
		else if (dir[1] > 0)
			ex->ent.angles[1] = 90;
		else if (dir[1] < 0)
			ex->ent.angles[1] = 270;
		else
			ex->ent.angles[1] = 0;

		ex->type = ex_misc;
		ex->ent.flags = RF_FULLBRIGHT|RF_TRANSLUCENT;

		// PMM
		if (type == TE_BLASTER2)
			ex->ent.skinnum = 1;
		else // flechette
			ex->ent.skinnum = 2;

		ex->start = cl.frame.servertime - 100;
		ex->light = 150;
		// PMM
		if (type == TE_BLASTER2)
			ex->lightcolor[1] = 1;
		else // flechette
		{
			ex->lightcolor[0] = 0.19;
			ex->lightcolor[1] = 0.41;
			ex->lightcolor[2] = 0.75;
		}
		ex->ent.model = cl_mod_explode;
		ex->frames = 4;
		S_StartSound(pos, 0, CHAN_AUTO, cl_sfx_lashit, 1, _ATTN_NORM, 0);
		break;

	case TE_LIGHTNING:
		ent = CL_ParseLightning (cl_mod_lightning, large_map);
		S_StartSound(NULL, ent, CHAN_WEAPON, cl_sfx_lightning, 1, _ATTN_NORM, 0);
		break;

	case TE_DEBUGTRAIL:
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadPos (&net_message, pos2, large_map);
		CL_DebugTrail (pos, pos2);
		break;

	case TE_PLAIN_EXPLOSION:
		MSG_ReadPos (&net_message, pos, large_map);

		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT;
		ex->start = cl.frame.servertime - 100;
		ex->light = 350;
		ex->lightcolor[0] = 1.0;
		ex->lightcolor[1] = 0.5;
		ex->lightcolor[2] = 0.5;
		ex->ent.angles[1] = rand() % 360;
		ex->ent.model = cl_mod_explo4;
		if (frand() < 0.5)
			ex->baseframe = 15;
		ex->frames = 15;
		if (type == TE_ROCKET_EXPLOSION_WATER)
			S_StartSound(pos, 0, CHAN_AUTO, cl_sfx_watrexp, 1, 0.08, 0);
		else
			S_StartSound(pos, 0, CHAN_AUTO, cl_sfx_rockexp, 1, 0.08, 0);
		break;

	case TE_FLASHLIGHT:
		MSG_ReadPos(&net_message, pos, large_map);
		ent = MSG_ReadShort(&net_message);
		CL_Flashlight(ent, pos);
		break;

	case TE_FORCEWALL:
		MSG_ReadPos(&net_message, pos, large_map);
		MSG_ReadPos(&net_message, pos2, large_map);
		color = MSG_ReadByte (&net_message);
		CL_ForceWall(pos, pos2, color);
		break;

	case TE_HEATBEAM:
		/*ent = */CL_ParsePlayerBeam (cl_mod_heatbeam, large_map);
		break;

	case TE_MONSTER_HEATBEAM:
		/*ent = */CL_ParsePlayerBeam (cl_mod_monster_heatbeam, large_map);
		break;

	case TE_STEAM:
		CL_ParseSteam(large_map);
		break;

	case TE_BUBBLETRAIL2:
//		cnt = MSG_ReadByte (&net_message);
		cnt = 8;
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadPos (&net_message, pos2, large_map);
		CL_BubbleTrail2 (pos, pos2, cnt);
		S_StartSound(pos, 0, CHAN_AUTO, cl_sfx_lashit, 1, _ATTN_NORM, 0);
		break;

	case TE_MOREBLOOD:
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
///		CL_ParticleEffect (pos, dir, 0xe8, 250);
		if(p_blood->value)
		{
			cnt = 16*p_blood->value;
			if (cnt)
			{
				R_LightForPoint(pos, end);
				/// don't allow too dark!
				if (end[0] < 0.02)	end[0] = 0.02;
				if (end[1] < 0.02)	end[1] = 0.02;
				if (end[2] < 0.02)	end[2] = 0.02;

				color_r = p_blood_r->value * end[0] * 256.0;
				color_g = p_blood_g->value * end[1] * 256.0;
				color_b = p_blood_b->value * end[2] * 256.0;
				if (color_r < 1)	color_r = 1;
				if (color_g < 1)	color_g = 1;
				if (color_b < 1)	color_b = 1;

				CL_ParticleEffect (pos, dir, color_r,color_g,color_b, cnt, part_blood, PARTICLE_FRICTION);	// Berserker: Уменьшил кол-во пятен крови (так как speedup - мараем только 1/16 вероятностью, то пусть будет крови примерно на 2 пятна)
			}
		}
		break;

	case TE_HEATBEAM_SPARKS:
//		cnt = MSG_ReadByte (&net_message);
		cnt = 50;
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
//		r = MSG_ReadByte (&net_message);
//		magnitude = MSG_ReadShort (&net_message);
		r = 8;
		magnitude = 60;
		color = r & 0xff;
		CL_ParticleSteamEffect (pos, dir, color, cnt, magnitude);
		S_StartSound(pos, 0, CHAN_AUTO, cl_sfx_lashit, 1, _ATTN_NORM, 0);
		break;

	case TE_HEATBEAM_STEAM:
//		cnt = MSG_ReadByte (&net_message);
		cnt = 20;
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
//		r = MSG_ReadByte (&net_message);
//		magnitude = MSG_ReadShort (&net_message);
//		color = r & 0xff;
		color = 0xe0;
		magnitude = 60;
		CL_ParticleSteamEffect (pos, dir, color, cnt, magnitude);
		S_StartSound(pos, 0, CHAN_AUTO, cl_sfx_lashit, 1, _ATTN_NORM, 0);
		break;

	case TE_CHAINFIST_SMOKE:
		dir[0]=0; dir[1]=0; dir[2]=1;
		MSG_ReadPos(&net_message, pos, large_map);
///		CL_ParticleSmokeEffect (pos, dir, 0, 20, 20);
		CL_ParticleEffect3 (pos, dir, p_smoke_r->value*256,p_smoke_g->value*256,p_smoke_b->value*256, 4, part_bigsmoke);		// Дым
		break;

	case TE_ELECTRIC_SPARKS:
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		if (p_sparks->value)
		{
			cnt = 40 * p_sparks->value;
			if (cnt)
			{
				color_r = d_8to24table[0x75*3+0];
				color_g = d_8to24table[0x75*3+1];
				color_b = d_8to24table[0x75*3+2];
///				CL_ParticleEffect (pos, dir, color_r, color_g, color_b, cnt, part_simple, 0);
				CL_SplashSparks(pos, dir, color_r,color_g,color_b, 50,16,0, cnt);
			}
		}
		//FIXME : replace or remove this sound
		S_StartSound(pos, 0, CHAN_AUTO, cl_sfx_lashit, 1, _ATTN_NORM, 0);
		break;

	case TE_TRACKER_EXPLOSION:
		MSG_ReadPos (&net_message, pos, large_map);
		CL_ColorFlash (pos, 0, 150, -1, -1, -1);
		CL_ColorExplosionParticles (pos, 0, 1);
//		CL_Tracker_Explode (pos);
		S_StartSound(pos, 0, CHAN_AUTO, cl_sfx_disrexp, 1, 0.1, 0);
		break;

	case TE_TELEPORT_EFFECT:
	case TE_DBALL_GOAL:
		MSG_ReadPos (&net_message, pos, large_map);
		CL_TeleportParticles (pos);
		break;

	case TE_WIDOWBEAMOUT:
		CL_ParseWidow (large_map);
		break;

	case TE_WIDOWSPLASH:
		MSG_ReadPos (&net_message, pos, large_map);
		CL_WidowSplash (pos);
		break;

	case TE_NUKEBLAST:
		CL_ParseNuke (large_map);
		break;

	default:
		Com_Error (ERR_DROP, "CL_ParseTEnt: bad type");
	}
}


/*
===============
PF_dprintf

Debug print to server console
===============
*/
void PF_dprintf (char *fmt, ...)
{
	char		msg[1024];
	va_list		argptr;

	va_start (argptr,fmt);
	vsprintf (msg, fmt, argptr);
	va_end (argptr);

/// Berserker: было просто Com_Printf
	Com_DPrintf ("%s", msg);	/// пусть печатает только в developer режиме!
}


/*
===============
PF_cprintf

Print to a single client
===============
*/
void PF_cprintf (edict_t *ent, int level, char *fmt, ...)
{
	char		msg[1024];
	va_list		argptr;
	int			n;

	if (ent)
	{
		n = NUM_FOR_EDICT(ent);
		if (n < 1 || n > maxclients->value)
			Com_Error (ERR_DROP, "cprintf to a non-client");
	}

	va_start (argptr,fmt);
	vsprintf (msg, fmt, argptr);
	va_end (argptr);

	if (ent)
		SV_ClientPrintf (svs.clients+(n-1), level, "%s", msg);
	else
		Com_Printf ("%s", msg);
}


/*
===============
PF_centerprintf

centerprint to a single client
===============
*/
void PF_centerprintf (edict_t *ent, char *fmt, ...)
{
	char		msg[1024];
	va_list		argptr;
	int			n;
	
	n = NUM_FOR_EDICT(ent);
	if (n < 1 || n > maxclients->value)
		return;	// Com_Error (ERR_DROP, "centerprintf to a non-client");

	va_start (argptr,fmt);
	vsprintf (msg, fmt, argptr);
	va_end (argptr);

	MSG_WriteByte (&sv.multicast,svc_centerprint);
	MSG_WriteString (&sv.multicast,msg);
	PF_Unicast (ent, true);
}


/*
===============
PF_error

Abort the server with a game error
===============
*/
void PF_error (char *fmt, ...)
{
	char		msg[1024];
	va_list		argptr;
	
	va_start (argptr,fmt);
	vsprintf (msg, fmt, argptr);
	va_end (argptr);

	Com_Error (ERR_DROP, "Game Error: %s", msg);
}


// ClearLink is used for new headnodes
void ClearLink (link_t *l)
{
	l->prev = l->next = l;
}


void RemoveLink (link_t *l)
{
	l->next->prev = l->prev;
	l->prev->next = l->next;
}


void SV_UnlinkEdict (edict_t *ent)
{
	if (!ent->area.prev)
		return;		// not linked in anywhere
	RemoveLink (&ent->area);
	ent->area.prev = ent->area.next = NULL;
}


void InsertLinkBefore (link_t *l, link_t *before)
{
	l->next = before;
	l->prev = before->prev;
	l->prev->next = l;
	l->next->prev = l;
}


/*
==================
BoxOnPlaneSide

Returns 1, 2, or 1 + 2
==================
*/
#pragma warning( disable: 4035 )

__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p)
{
	static int bops_initialized;
	static int Ljmptab[8];

	__asm {

		push ebx
			
		cmp bops_initialized, 1
		je  initialized
		mov bops_initialized, 1
		
		mov Ljmptab[0*4], offset Lcase0
		mov Ljmptab[1*4], offset Lcase1
		mov Ljmptab[2*4], offset Lcase2
		mov Ljmptab[3*4], offset Lcase3
		mov Ljmptab[4*4], offset Lcase4
		mov Ljmptab[5*4], offset Lcase5
		mov Ljmptab[6*4], offset Lcase6
		mov Ljmptab[7*4], offset Lcase7
			
initialized:

		mov edx,ds:dword ptr[4+12+esp]
		mov ecx,ds:dword ptr[4+4+esp]
		xor eax,eax
		mov ebx,ds:dword ptr[4+8+esp]
		mov al,ds:byte ptr[17+edx]
		cmp al,8
		jge Lerror
		fld ds:dword ptr[0+edx]
		fld st(0)
		jmp dword ptr[Ljmptab+eax*4]
Lcase0:
		fmul ds:dword ptr[ebx]
		fld ds:dword ptr[0+4+edx]
		fxch st(2)
		fmul ds:dword ptr[ecx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[4+ebx]
		fld ds:dword ptr[0+8+edx]
		fxch st(2)
		fmul ds:dword ptr[4+ecx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[8+ebx]
		fxch st(5)
		faddp st(3),st(0)
		fmul ds:dword ptr[8+ecx]
		fxch st(1)
		faddp st(3),st(0)
		fxch st(3)
		faddp st(2),st(0)
		jmp LSetSides
Lcase1:
		fmul ds:dword ptr[ecx]
		fld ds:dword ptr[0+4+edx]
		fxch st(2)
		fmul ds:dword ptr[ebx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[4+ebx]
		fld ds:dword ptr[0+8+edx]
		fxch st(2)
		fmul ds:dword ptr[4+ecx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[8+ebx]
		fxch st(5)
		faddp st(3),st(0)
		fmul ds:dword ptr[8+ecx]
		fxch st(1)
		faddp st(3),st(0)
		fxch st(3)
		faddp st(2),st(0)
		jmp LSetSides
Lcase2:
		fmul ds:dword ptr[ebx]
		fld ds:dword ptr[0+4+edx]
		fxch st(2)
		fmul ds:dword ptr[ecx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[4+ecx]
		fld ds:dword ptr[0+8+edx]
		fxch st(2)
		fmul ds:dword ptr[4+ebx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[8+ebx]
		fxch st(5)
		faddp st(3),st(0)
		fmul ds:dword ptr[8+ecx]
		fxch st(1)
		faddp st(3),st(0)
		fxch st(3)
		faddp st(2),st(0)
		jmp LSetSides
Lcase3:
		fmul ds:dword ptr[ecx]
		fld ds:dword ptr[0+4+edx]
		fxch st(2)
		fmul ds:dword ptr[ebx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[4+ecx]
		fld ds:dword ptr[0+8+edx]
		fxch st(2)
		fmul ds:dword ptr[4+ebx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[8+ebx]
		fxch st(5)
		faddp st(3),st(0)
		fmul ds:dword ptr[8+ecx]
		fxch st(1)
		faddp st(3),st(0)
		fxch st(3)
		faddp st(2),st(0)
		jmp LSetSides
Lcase4:
		fmul ds:dword ptr[ebx]
		fld ds:dword ptr[0+4+edx]
		fxch st(2)
		fmul ds:dword ptr[ecx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[4+ebx]
		fld ds:dword ptr[0+8+edx]
		fxch st(2)
		fmul ds:dword ptr[4+ecx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[8+ecx]
		fxch st(5)
		faddp st(3),st(0)
		fmul ds:dword ptr[8+ebx]
		fxch st(1)
		faddp st(3),st(0)
		fxch st(3)
		faddp st(2),st(0)
		jmp LSetSides
Lcase5:
		fmul ds:dword ptr[ecx]
		fld ds:dword ptr[0+4+edx]
		fxch st(2)
		fmul ds:dword ptr[ebx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[4+ebx]
		fld ds:dword ptr[0+8+edx]
		fxch st(2)
		fmul ds:dword ptr[4+ecx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[8+ecx]
		fxch st(5)
		faddp st(3),st(0)
		fmul ds:dword ptr[8+ebx]
		fxch st(1)
		faddp st(3),st(0)
		fxch st(3)
		faddp st(2),st(0)
		jmp LSetSides
Lcase6:
		fmul ds:dword ptr[ebx]
		fld ds:dword ptr[0+4+edx]
		fxch st(2)
		fmul ds:dword ptr[ecx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[4+ecx]
		fld ds:dword ptr[0+8+edx]
		fxch st(2)
		fmul ds:dword ptr[4+ebx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[8+ecx]
		fxch st(5)
		faddp st(3),st(0)
		fmul ds:dword ptr[8+ebx]
		fxch st(1)
		faddp st(3),st(0)
		fxch st(3)
		faddp st(2),st(0)
		jmp LSetSides
Lcase7:
		fmul ds:dword ptr[ecx]
		fld ds:dword ptr[0+4+edx]
		fxch st(2)
		fmul ds:dword ptr[ebx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[4+ecx]
		fld ds:dword ptr[0+8+edx]
		fxch st(2)
		fmul ds:dword ptr[4+ebx]
		fxch st(2)
		fld st(0)
		fmul ds:dword ptr[8+ecx]
		fxch st(5)
		faddp st(3),st(0)
		fmul ds:dword ptr[8+ebx]
		fxch st(1)
		faddp st(3),st(0)
		fxch st(3)
		faddp st(2),st(0)
LSetSides:
		faddp st(2),st(0)
		fcomp ds:dword ptr[12+edx]
		xor ecx,ecx
		fnstsw ax
		fcomp ds:dword ptr[12+edx]
		and ah,1
		xor ah,1
		add cl,ah
		fnstsw ax
		and ah,1
		add ah,ah
		add cl,ah
		pop ebx
		mov eax,ecx
		ret
Lerror:
		int 3
	}
}
#pragma warning( default: 4035 )


/*
=============
CM_BoxLeafnums

Fills in a list of all the leafs touched
=============
*/
int		leaf_count, leaf_maxcount;
int		*leaf_list;
float	*leaf_mins, *leaf_maxs;
int		leaf_topnode;

void CM_BoxLeafnums_r (int nodenum)
{
	cplane_t	*plane;
	cnode_t		*node;
	int		s;

	while (1)
	{
		if (nodenum < 0)
		{
			if (leaf_count >= leaf_maxcount)
				return;
			leaf_list[leaf_count++] = -1 - nodenum;
			return;
		}
	
		node = &map_nodes[nodenum];
		plane = node->plane;
		s = BOX_ON_PLANE_SIDE(leaf_mins, leaf_maxs, plane);
		if (s == 1)
			nodenum = node->children[0];
		else if (s == 2)
			nodenum = node->children[1];
		else
		{	// go down both
			if (leaf_topnode == -1)
				leaf_topnode = nodenum;
			CM_BoxLeafnums_r (node->children[0]);
			nodenum = node->children[1];
		}

	}
}


int	CM_BoxLeafnums_headnode (vec3_t mins, vec3_t maxs, int *list, int listsize, int headnode, int *topnode)
{
	leaf_list = list;
	leaf_count = 0;
	leaf_maxcount = listsize;
	leaf_mins = mins;
	leaf_maxs = maxs;

	leaf_topnode = -1;

	CM_BoxLeafnums_r (headnode);

	if (topnode)
		*topnode = leaf_topnode;

	return leaf_count;
}


int	CM_BoxLeafnums (vec3_t mins, vec3_t maxs, int *list, int listsize, int *topnode)
{
	return CM_BoxLeafnums_headnode (mins, maxs, list,
		listsize, map_cmodels[0].headnode, topnode);
}


#define MAX_TOTAL_ENT_LEAFS		128
void SV_LinkEdict (edict_t *ent)
{
	areanode_t	*node;
	int			leafs[MAX_TOTAL_ENT_LEAFS];
	int			clusters[MAX_TOTAL_ENT_LEAFS];
	int			num_leafs;
	int			i, j, k;
	int			area;
	int			topnode;

	if (ent->area.prev)
		SV_UnlinkEdict (ent);	// unlink from old position
		
	if (ent == ge->edicts)
		return;		// don't add the world

	if (!ent->inuse)
		return;

	// set the size
	VectorSubtract (ent->maxs, ent->mins, ent->size);
	
	// encode the size into the entity_state for client prediction
	if (ent->solid == SOLID_BBOX && !(ent->svflags & SVF_DEADMONSTER))
	{	// assume that x/y are equal and symetric
		i = ent->maxs[0]/8;
		if (i<1)
			i = 1;
		if (i>31)
			i = 31;

		// z is not symetric
		j = (-ent->mins[2])/8;
		if (j<1)
			j = 1;
		if (j>31)
			j = 31;

		// and z maxs can be negative...
		k = (ent->maxs[2]+32)/8;
		if (k<1)
			k = 1;
		if (k>63)
			k = 63;

		ent->s.solid = (k<<10) | (j<<5) | i;
	}
	else if (ent->solid == SOLID_BSP)
	{
		ent->s.solid = 31;		// a solid_bbox will never create this value
	}
	else
		ent->s.solid = 0;

	// set the abs box
	if (ent->solid == SOLID_BSP && 
	(ent->s.angles[0] || ent->s.angles[1] || ent->s.angles[2]) )
	{	// expand for rotation
		float		max, v;
		int			i;

		max = 0;
		for (i=0 ; i<3 ; i++)
		{
			v =fabs( ent->mins[i]);
			if (v > max)
				max = v;
			v =fabs( ent->maxs[i]);
			if (v > max)
				max = v;
		}
		for (i=0 ; i<3 ; i++)
		{
			ent->absmin[i] = ent->s.origin[i] - max;
			ent->absmax[i] = ent->s.origin[i] + max;
		}
	}
	else
	{	// normal
		VectorAdd (ent->s.origin, ent->mins, ent->absmin);	
		VectorAdd (ent->s.origin, ent->maxs, ent->absmax);
	}

	// because movement is clipped an epsilon away from an actual edge,
	// we must fully check even when bounding boxes don't quite touch
	ent->absmin[0] -= 1;
	ent->absmin[1] -= 1;
	ent->absmin[2] -= 1;
	ent->absmax[0] += 1;
	ent->absmax[1] += 1;
	ent->absmax[2] += 1;

// link to PVS leafs
	ent->num_clusters = 0;
	ent->areanum = 0;
	ent->areanum2 = 0;

	//get all leafs, including solids
	num_leafs = CM_BoxLeafnums (ent->absmin, ent->absmax, leafs, MAX_TOTAL_ENT_LEAFS, &topnode);

	// set areas
	for (i=0 ; i<num_leafs ; i++)
	{
		clusters[i] = CM_LeafCluster (leafs[i]);
		area = CM_LeafArea (leafs[i]);
		if (area)
		{	// doors may legally straggle two areas,
			// but nothing should evern need more than that
			if (ent->areanum && ent->areanum != area)
			{
				if (ent->areanum2 && ent->areanum2 != area && sv.state == ss_loading)
					Com_DPrintf ("Object touching 3 areas at %f %f %f\n",
					ent->absmin[0], ent->absmin[1], ent->absmin[2]);
				ent->areanum2 = area;
			}
			else
				ent->areanum = area;
		}
	}

	if (num_leafs >= MAX_TOTAL_ENT_LEAFS)
	{	// assume we missed some leafs, and mark by headnode
		ent->num_clusters = -1;
		ent->headnode = topnode;
	}
	else
	{
		ent->num_clusters = 0;
		for (i=0 ; i<num_leafs ; i++)
		{
			if (clusters[i] == -1)
				continue;		// not a visible leaf
			for (j=0 ; j<i ; j++)
				if (clusters[j] == clusters[i])
					break;
			if (j == i)
			{
				if (ent->num_clusters == MAX_ENT_CLUSTERS)
				{	// assume we missed some leafs, and mark by headnode
					ent->num_clusters = -1;
					ent->headnode = topnode;
					break;
				}

				ent->clusternums[ent->num_clusters++] = clusters[i];
			}
		}
	}

	// if first time, make sure old_origin is valid
	if (!ent->linkcount)
	{
		VectorCopy (ent->s.origin, ent->s.old_origin);
	}
	ent->linkcount++;

	if (ent->solid == SOLID_NOT)
		return;

// find the first node that the ent's box crosses
	node = sv_areanodes;
	while (1)
	{
		if (node->axis == -1)
			break;
		if (ent->absmin[node->axis] > node->dist)
			node = node->children[0];
		else if (ent->absmax[node->axis] < node->dist)
			node = node->children[1];
		else
			break;		// crosses the node
	}
	
	// link it in	
	if (ent->solid == SOLID_TRIGGER)
		InsertLinkBefore (&ent->area, &node->trigger_edicts);
	else
		InsertLinkBefore (&ent->area, &node->solid_edicts);

}


void SV_AreaEdicts_r (areanode_t *node)
{
	link_t		*l, *next, *start;
	edict_t		*check;
	int			count;

	count = 0;

	// touch linked edicts
	if (area_type == AREA_SOLID)
		start = &node->solid_edicts;
	else
		start = &node->trigger_edicts;

	for (l=start->next  ; l != start ; l = next)
	{
		next = l->next;
		check = EDICT_FROM_AREA(l);

		if (check->solid == SOLID_NOT)
			continue;		// deactivated
		if (check->absmin[0] > area_maxs[0]
		|| check->absmin[1] > area_maxs[1]
		|| check->absmin[2] > area_maxs[2]
		|| check->absmax[0] < area_mins[0]
		|| check->absmax[1] < area_mins[1]
		|| check->absmax[2] < area_mins[2])
			continue;		// not touching

		if (area_count == area_maxcount)
		{
			Com_Printf ("^1SV_AreaEdicts: MAXCOUNT\n");
			return;
		}

		area_list[area_count] = check;
		area_count++;
	}
	
	if (node->axis == -1)
		return;		// terminal node

	// recurse down both sides
	if ( area_maxs[node->axis] > node->dist )
		SV_AreaEdicts_r ( node->children[0] );
	if ( area_mins[node->axis] < node->dist )
		SV_AreaEdicts_r ( node->children[1] );
}


int SV_AreaEdicts (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype)
{
	if (sv.state == ss_dead)	/// Berserker: добавил проверку, т.к.
		return 0;				/// R_SpawnDecal может вызвать SV_Trace в неудобное время (карта недозагружена?)

	area_mins = mins;
	area_maxs = maxs;
	area_list = list;
	area_count = 0;
	area_maxcount = maxcount;
	area_type = areatype;

	SV_AreaEdicts_r (sv_areanodes);
	return area_count;
}


void CM_TestBoxInBrush (vec3_t mins, vec3_t maxs, vec3_t p1, trace_t *trace, cbrush_t *brush)
{
	int			i, j;
	cplane_t	*plane;
	float		dist;
	vec3_t		ofs;
	float		d1;
	cbrushside_t	*side;

	if (!brush->numsides)
		return;

	for (i=0 ; i<brush->numsides ; i++)
	{
		side = &map_brushsides[brush->firstbrushside+i];
		plane = side->plane;

		// FIXME: special case for axial

		// general box case

		// push the plane out apropriately for mins/maxs

		// FIXME: use signbits into 8 way lookup for each mins/maxs
		for (j=0 ; j<3 ; j++)
		{
			if (plane->normal[j] < 0)
				ofs[j] = maxs[j];
			else
				ofs[j] = mins[j];
		}
		dist = DotProduct (ofs, plane->normal);
		dist = plane->dist - dist;

		d1 = DotProduct (p1, plane->normal) - dist;

		// if completely in front of face, no intersection
		if (d1 > 0)
			return;

	}

	// inside this brush
	trace->startsolid = trace->allsolid = true;
	trace->fraction = 0;
	trace->contents = brush->contents;
}


void CM_TestInLeaf (int leafnum)
{
	int			k;
	int			brushnum;
	cleaf_t		*leaf;
	cbrush_t	*b;

	leaf = &map_leafs[leafnum];
	if ( !(leaf->contents & trace_contents))
		return;
	// trace line against all brushes in the leaf
	for (k=0 ; k<leaf->numleafbrushes ; k++)
	{
		brushnum = map_leafbrushes[leaf->firstleafbrush+k];
		b = &map_brushes[brushnum];
		if (b->checkcount == checkcount)
			continue;	// already checked this brush in another leaf
		b->checkcount = checkcount;

		if ( !(b->contents & trace_contents))
			continue;
		CM_TestBoxInBrush (trace_mins, trace_maxs, trace_start, &trace_trace, b);
		if (!trace_trace.fraction)
			return;
	}

}


void CM_ClipBoxToBrush (vec3_t mins, vec3_t maxs, vec3_t p1, vec3_t p2, trace_t *trace, cbrush_t *brush)
{
	int			i, j;
	cplane_t	*plane, *clipplane;
	float		dist;
	float		enterfrac, leavefrac;
	vec3_t		ofs;
	float		d1, d2;
	bool		getout, startout;
	float		f;
	cbrushside_t	*side, *leadside;

	enterfrac = -1;
	leavefrac = 1;
	clipplane = NULL;

	if (!brush->numsides)
		return;

///	c_brush_traces++;

	getout = false;
	startout = false;
	leadside = NULL;

	for (i=0 ; i<brush->numsides ; i++)
	{
		side = &map_brushsides[brush->firstbrushside+i];
		plane = side->plane;

		// FIXME: special case for axial

		if (!trace_ispoint)
		{	// general box case

			// push the plane out apropriately for mins/maxs

			// FIXME: use signbits into 8 way lookup for each mins/maxs
			for (j=0 ; j<3 ; j++)
			{
				if (plane->normal[j] < 0)
					ofs[j] = maxs[j];
				else
					ofs[j] = mins[j];
			}
			dist = DotProduct (ofs, plane->normal);
			dist = plane->dist - dist;
		}
		else
		{	// special point case
			dist = plane->dist;
		}

		d1 = DotProduct (p1, plane->normal) - dist;
		d2 = DotProduct (p2, plane->normal) - dist;

		if (d2 > 0)
			getout = true;	// endpoint is not in solid
		if (d1 > 0)
			startout = true;

		// if completely in front of face, no intersection
		if (d1 > 0 && d2 >= d1)
			return;

		if (d1 <= 0 && d2 <= 0)
			continue;

		// crosses face
		if (d1 > d2)
		{	// enter
			f = (d1-DIST_EPSILON) / (d1-d2);
			if (f > enterfrac)
			{
				enterfrac = f;
				clipplane = plane;
				leadside = side;
			}
		}
		else
		{	// leave
			f = (d1+DIST_EPSILON) / (d1-d2);
			if (f < leavefrac)
				leavefrac = f;
		}
	}

	if (!startout)
	{	// original point was inside brush
		trace->startsolid = true;
		if (!getout)
			trace->allsolid = true;
		return;
	}
	if (enterfrac < leavefrac)
	{
		if (enterfrac > -1 && enterfrac < trace->fraction)
		{
			if (enterfrac < 0)
				enterfrac = 0;
			trace->fraction = enterfrac;
			trace->plane = *clipplane;
			trace->surface = &(leadside->surface->c);
			trace->contents = brush->contents;
		}
	}
}


void CM_TraceToLeaf (int leafnum)
{
	int			k;
	int			brushnum;
	cleaf_t		*leaf;
	cbrush_t	*b;

	leaf = &map_leafs[leafnum];
	if ( !(leaf->contents & trace_contents))
		return;
	// trace line against all brushes in the leaf
	for (k=0 ; k<leaf->numleafbrushes ; k++)
	{
		brushnum = map_leafbrushes[leaf->firstleafbrush+k];
		b = &map_brushes[brushnum];
		if (b->checkcount == checkcount)
			continue;	// already checked this brush in another leaf
		b->checkcount = checkcount;

		if ( !(b->contents & trace_contents))
			continue;
		CM_ClipBoxToBrush (trace_mins, trace_maxs, trace_start, trace_end, &trace_trace, b);
		if (!trace_trace.fraction)
			return;
	}

}


void CM_RecursiveHullCheck (int num, float p1f, float p2f, vec3_t p1, vec3_t p2)
{
	cnode_t		*node;
	cplane_t	*plane;
	float		t1, t2, offset;
	float		frac, frac2;
	float		idist;
	int			i;
	vec3_t		mid;
	int			side;
	float		midf;

	if (trace_trace.fraction <= p1f)
		return;		// already hit something nearer

	// if < 0, we are in a leaf node
	if (num < 0)
	{
		CM_TraceToLeaf (-1-num);
		return;
	}

	//
	// find the point distances to the seperating plane
	// and the offset for the size of the box
	//
	node = map_nodes + num;
	plane = node->plane;

	if (plane->type < 3)
	{
		t1 = p1[plane->type] - plane->dist;
		t2 = p2[plane->type] - plane->dist;
		offset = trace_extents[plane->type];
	}
	else
	{
		t1 = DotProduct (plane->normal, p1) - plane->dist;
		t2 = DotProduct (plane->normal, p2) - plane->dist;
		if (trace_ispoint)
			offset = 0;
		else
			offset = fabs(trace_extents[0]*plane->normal[0]) +
				fabs(trace_extents[1]*plane->normal[1]) +
				fabs(trace_extents[2]*plane->normal[2]);
	}

	// see which sides we need to consider
	if (t1 >= offset && t2 >= offset)
	{
		CM_RecursiveHullCheck (node->children[0], p1f, p2f, p1, p2);
		return;
	}
	if (t1 < -offset && t2 < -offset)
	{
		CM_RecursiveHullCheck (node->children[1], p1f, p2f, p1, p2);
		return;
	}

	// put the crosspoint DIST_EPSILON pixels on the near side
	if (t1 < t2)
	{
		idist = 1.0/(t1-t2);
		side = 1;
		frac2 = (t1 + offset + DIST_EPSILON)*idist;
		frac = (t1 - offset + DIST_EPSILON)*idist;
	}
	else if (t1 > t2)
	{
		idist = 1.0/(t1-t2);
		side = 0;
		frac2 = (t1 - offset - DIST_EPSILON)*idist;
		frac = (t1 + offset + DIST_EPSILON)*idist;
	}
	else
	{
		side = 0;
		frac = 1;
		frac2 = 0;
	}

	// move up to the node
	if (frac < 0)
		frac = 0;
	if (frac > 1)
		frac = 1;
		
	midf = p1f + (p2f - p1f)*frac;
	for (i=0 ; i<3 ; i++)
		mid[i] = p1[i] + frac*(p2[i] - p1[i]);

	CM_RecursiveHullCheck (node->children[side], p1f, midf, p1, mid);


	// go past the node
	if (frac2 < 0)
		frac2 = 0;
	if (frac2 > 1)
		frac2 = 1;
		
	midf = p1f + (p2f - p1f)*frac2;
	for (i=0 ; i<3 ; i++)
		mid[i] = p1[i] + frac2*(p2[i] - p1[i]);

	CM_RecursiveHullCheck (node->children[side^1], midf, p2f, mid, p2);
}


trace_t		CM_BoxTrace (vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, int headnode, int brushmask)
{
	int		i;

	checkcount++;		// for multi-check avoidance

///	c_traces++;			// for statistics, may be zeroed

	// fill in a default trace
	Memset (&trace_trace, 0, sizeof(trace_trace));
	trace_trace.fraction = 1;
	trace_trace.surface = &(nullsurface.c);

	if (!numnodes)	// map not loaded
		return trace_trace;

	trace_contents = brushmask;
	VectorCopy (start, trace_start);
	VectorCopy (end, trace_end);
	VectorCopy (mins, trace_mins);
	VectorCopy (maxs, trace_maxs);

	//
	// check for position test special case
	//
	if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2])
	{
		int		leafs[1024];
		int		i, numleafs;
		vec3_t	c1, c2;
		int		topnode;

		VectorAdd (start, mins, c1);
		VectorAdd (start, maxs, c2);
		for (i=0 ; i<3 ; i++)
		{
			c1[i] -= 1;
			c2[i] += 1;
		}

		numleafs = CM_BoxLeafnums_headnode (c1, c2, leafs, 1024, headnode, &topnode);
		for (i=0 ; i<numleafs ; i++)
		{
			CM_TestInLeaf (leafs[i]);
			if (trace_trace.allsolid)
				break;
		}
		VectorCopy (start, trace_trace.endpos);
		return trace_trace;
	}

	//
	// check for point special case
	//
	if (mins[0] == 0 && mins[1] == 0 && mins[2] == 0
		&& maxs[0] == 0 && maxs[1] == 0 && maxs[2] == 0)
	{
		trace_ispoint = true;
		VectorClear (trace_extents);
	}
	else
	{
		trace_ispoint = false;
		trace_extents[0] = -mins[0] > maxs[0] ? -mins[0] : maxs[0];
		trace_extents[1] = -mins[1] > maxs[1] ? -mins[1] : maxs[1];
		trace_extents[2] = -mins[2] > maxs[2] ? -mins[2] : maxs[2];
	}

	//
	// general sweeping through world
	//
	CM_RecursiveHullCheck (headnode, 0, 1, start, end);

	if (trace_trace.fraction == 1)
	{
		VectorCopy (end, trace_trace.endpos);
	}
	else
	{
		for (i=0 ; i<3 ; i++)
			trace_trace.endpos[i] = start[i] + trace_trace.fraction * (end[i] - start[i]);
	}
	return trace_trace;
}


void SV_TraceBounds (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, vec3_t boxmins, vec3_t boxmaxs)
{
	int		i;

	for (i=0 ; i<3 ; i++)
	{
		if (end[i] > start[i])
		{
			boxmins[i] = start[i] + mins[i] - 1;
			boxmaxs[i] = end[i] + maxs[i] + 1;
		}
		else
		{
			boxmins[i] = end[i] + mins[i] - 1;
			boxmaxs[i] = start[i] + maxs[i] + 1;
		}
	}
}


/*
===================
CM_HeadnodeForBox

To keep everything totally uniform, bounding boxes are turned into small
BSP trees instead of being compared directly.
===================
*/
int	CM_HeadnodeForBox (vec3_t mins, vec3_t maxs)
{
	box_planes[0].dist = maxs[0];
	box_planes[1].dist = -maxs[0];
	box_planes[2].dist = mins[0];
	box_planes[3].dist = -mins[0];
	box_planes[4].dist = maxs[1];
	box_planes[5].dist = -maxs[1];
	box_planes[6].dist = mins[1];
	box_planes[7].dist = -mins[1];
	box_planes[8].dist = maxs[2];
	box_planes[9].dist = -maxs[2];
	box_planes[10].dist = mins[2];
	box_planes[11].dist = -mins[2];
	return box_headnode;
}


/*
================
SV_HullForEntity

Returns a headnode that can be used for testing or clipping an
object of mins/maxs size.
Offset is filled in to contain the adjustment that must be added to the
testing object's origin to get a point to use with the returned hull.
================
*/
int SV_HullForEntity (edict_t *ent)
{
	cmodel_t	*model;

// decide which clipping hull to use, based on the size
	if (ent->solid == SOLID_BSP)
	{	// explicit hulls in the BSP model
		model = sv.models[ ent->s.modelindex ];

		if (!model)
			Com_Error (ERR_FATAL, "MOVETYPE_PUSH with a non bsp model");

		return model->headnode;
	}

	// create a temp hull from bounding box sizes
	return CM_HeadnodeForBox (ent->mins, ent->maxs);
}


void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up)
{
	float			angle;
	static float	sp, sy, cp, cy;
	// static to help MS compiler fp bugs

	if (angles[0] || angles[1] || angles[2])
	{
		angle = angles[YAW] * (M_PI*2 / 360);
		SinCos(angle, &sy, &cy);
///		sy = sin(angle);
///		cy = cos(angle);
		angle = angles[PITCH] * (M_PI*2 / 360);
		SinCos(angle, &sp, &cp);
///		sp = sin(angle);
///		cp = cos(angle);
		if (forward)
		{
			forward[0] = cp*cy;
			forward[1] = cp*sy;
			forward[2] = -sp;
		}
		if (right || up)
		{
			static float	sr, cr;
			// static to help MS compiler fp bugs

			angle = angles[ROLL] * (M_PI*2 / 360);
			SinCos(angle, &sr, &cr);
///			sr = sin(angle);
///			cr = cos(angle);

			if (right)
			{
				right[0] = (-sr*sp*cy+cr*sy);
				right[1] = (-sr*sp*sy-cr*cy);
				right[2] = -sr*cp;
			}
			if (up)
			{
				up[0] = (cr*sp*cy+sr*sy);
				up[1] = (cr*sp*sy-sr*cy);
				up[2] = cr*cp;
			}
		}
	}
	else
	{
		if (forward)
			VectorSet(forward, 1,0,0);
		if (right)
			VectorSet(right, 0,-1,0);
		if (up)
			VectorSet(up, 0,0,1);
	}
}


/*
==================
CM_TransformedBoxTrace

Handles offseting and rotation of the end points for moving and
rotating entities
==================
*/
#ifdef _WIN32
#pragma optimize( "", off )
#endif
trace_t		CM_TransformedBoxTrace (vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, int headnode, int brushmask, vec3_t origin, vec3_t angles)
{
	trace_t		trace;
	vec3_t		start_l, end_l;
	vec3_t		a;
	vec3_t		forward, right, up;
	vec3_t		temp;
	bool	rotated;

	// subtract origin offset
	VectorSubtract (start, origin, start_l);
	VectorSubtract (end, origin, end_l);

	// rotate start and end into the models frame of reference
	if (headnode != box_headnode && (angles[0] || angles[1] || angles[2]) )
		rotated = true;
	else
		rotated = false;

	if (rotated)
	{
		AngleVectors (angles, forward, right, up);

		VectorCopy (start_l, temp);
		start_l[0] = DotProduct (temp, forward);
		start_l[1] = -DotProduct (temp, right);
		start_l[2] = DotProduct (temp, up);

		VectorCopy (end_l, temp);
		end_l[0] = DotProduct (temp, forward);
		end_l[1] = -DotProduct (temp, right);
		end_l[2] = DotProduct (temp, up);
	}

	// sweep the box through the model
	trace = CM_BoxTrace (start_l, end_l, mins, maxs, headnode, brushmask);

	if (rotated && trace.fraction != 1.0)
	{
		// FIXME: figure out how to do this with existing angles
		VectorNegate (angles, a);
		AngleVectors (a, forward, right, up);

		VectorCopy (trace.plane.normal, temp);
		trace.plane.normal[0] = DotProduct (temp, forward);
		trace.plane.normal[1] = -DotProduct (temp, right);
		trace.plane.normal[2] = DotProduct (temp, up);
	}

	trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]);
	trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]);
	trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]);

	return trace;
}
#ifdef _WIN32
#pragma optimize( "", on )
#endif


void SV_ClipMoveToEntities ( moveclip_t *clip )
{
	int			i, num;
	edict_t		*touchlist[MAX_EDICTS], *touch;
	trace_t		trace;
	int			headnode;
	float		*angles;

	num = SV_AreaEdicts (clip->boxmins, clip->boxmaxs, touchlist, MAX_EDICTS, AREA_SOLID);

	// be careful, it is possible to have an entity in this
	// list removed before we get to it (killtriggered)
	for (i=0 ; i<num ; i++)
	{
		touch = touchlist[i];
		if (touch->solid == SOLID_NOT)
			continue;
		if (touch == clip->passedict)
			continue;
		if (clip->trace.allsolid)
			return;
		if (clip->passedict)
		{
		 	if (touch->owner == clip->passedict)
				continue;	// don't clip against own missiles
			if (clip->passedict->owner == touch)
				continue;	// don't clip against owner
		}

		if ( !(clip->contentmask & CONTENTS_DEADMONSTER) && (touch->svflags & SVF_DEADMONSTER) )
				continue;

		// might intersect, so do an exact clip
		headnode = SV_HullForEntity (touch);
		angles = touch->s.angles;
		if (touch->solid != SOLID_BSP)
			angles = vec3_origin;	// boxes don't rotate

		if (touch->svflags & SVF_MONSTER)
			trace = CM_TransformedBoxTrace (clip->start, clip->end, clip->mins2, clip->maxs2, headnode, clip->contentmask, touch->s.origin, angles);
		else
			trace = CM_TransformedBoxTrace (clip->start, clip->end, clip->mins, clip->maxs, headnode,  clip->contentmask, touch->s.origin, angles);

		if (trace.allsolid || trace.startsolid || trace.fraction < clip->trace.fraction)
		{
			trace.ent = touch;
		 	if (clip->trace.startsolid)
			{
				clip->trace = trace;
				clip->trace.startsolid = true;
			}
			else
				clip->trace = trace;
		}
		else if (trace.startsolid)
			clip->trace.startsolid = true;
	}
}


/*
==================
SV_Trace

Moves the given mins/maxs volume through the world from start to end.

Passedict and edicts owned by passedict are explicitly not checked.

==================
*/
trace_t SV_Trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passedict, int contentmask)
{
	moveclip_t	clip;

	if (!mins)
		mins = vec3_origin;
	if (!maxs)
		maxs = vec3_origin;

	Memset ( &clip, 0, sizeof ( moveclip_t ) );

	// clip to world
	clip.trace = CM_BoxTrace (start, end, mins, maxs, 0, contentmask);
	clip.trace.ent = ge->edicts;
	if (clip.trace.fraction == 0)
		return clip.trace;		// blocked by the world

	clip.contentmask = contentmask;
	clip.start = start;
	clip.end = end;
	clip.mins = mins;
	clip.maxs = maxs;
	clip.passedict = passedict;

	VectorCopy (mins, clip.mins2);
	VectorCopy (maxs, clip.maxs2);

	// create the bounding box of the entire move
	SV_TraceBounds ( start, clip.mins2, clip.maxs2, end, clip.boxmins, clip.boxmaxs );

	// clip to other solid entities
	SV_ClipMoveToEntities ( &clip );

	return clip.trace;
}


int CM_PointContents (vec3_t p, int headnode)
{
	int		l;

	if (!numnodes)	// map not loaded
		return 0;

	l = CM_PointLeafnum_r (p, headnode);

	return map_leafs[l].contents;
}


/*
==================
CM_TransformedPointContents

Handles offseting and rotation of the end points for moving and
rotating entities
==================
*/
int	CM_TransformedPointContents (vec3_t p, int headnode, vec3_t origin, vec3_t angles)
{
	vec3_t		p_l;
	vec3_t		temp;
	vec3_t		forward, right, up;
	int			l;

	// subtract origin offset
	VectorSubtract (p, origin, p_l);

	// rotate start and end into the models frame of reference
	if (headnode != box_headnode && (angles[0] || angles[1] || angles[2]) )
	{
		AngleVectors (angles, forward, right, up);

		VectorCopy (p_l, temp);
		p_l[0] = DotProduct (temp, forward);
		p_l[1] = -DotProduct (temp, right);
		p_l[2] = DotProduct (temp, up);
	}

	l = CM_PointLeafnum_r (p_l, headnode);

	return map_leafs[l].contents;
}


int SV_PointContents (vec3_t p)
{
	edict_t		*touch[MAX_EDICTS], *hit;
	int			i, num;
	int			contents, c2;
	int			headnode;
	float		*angles;

	// get base contents from world
	contents = CM_PointContents (p, sv.models[1]->headnode);

	// or in contents from all the other entities
	num = SV_AreaEdicts (p, p, touch, MAX_EDICTS, AREA_SOLID);

	for (i=0 ; i<num ; i++)
	{
		hit = touch[i];

		// might intersect, so do an exact clip
		headnode = SV_HullForEntity (hit);
		angles = hit->s.angles;
		if (hit->solid != SOLID_BSP)
			angles = vec3_origin;	// boxes don't rotate

		c2 = CM_TransformedPointContents (p, headnode, hit->s.origin, hit->s.angles);

		contents |= c2;
	}

	return contents;
}


int SV_FindIndex (char *name, int start, int max/*, bool create*/)
{
	int		i;
	
	if (!name || !name[0])
		return 0;

	for (i=1 ; i<max && sv.configstrings[start+i][0] ; i++)
		if (!strcmp(sv.configstrings[start+i], name))
			return i;

///	if (!create)
///		return 0;

	if (i == max)
	{
#if 0
		if (start == CS_MODELS)
			Com_Error (ERR_DROP, "*Index (models): overflow");
		else if (start == CS_SOUNDS)
			Com_Error (ERR_DROP, "*Index (sounds): overflow");
		else if (start == CS_IMAGES)
			Com_Error (ERR_DROP, "*Index (images): overflow");
		else
			Com_Error (ERR_DROP, "*Index: overflow");
#else
		if (net_compatibility->value)
		{
			if (start == CS_MODELS)
				Com_Printf ("^3*Index: overflow for models\n");
			else if (start == CS_SOUNDS_Q2)
				Com_Printf ("^3*Index: overflow for sounds\n");
			else if (start == CS_IMAGES_Q2)
				Com_Printf ("^3*Index: overflow for images\n");
			else
				Com_Printf ("^3*Index: overflow\n");
		}
		else
		{
			if (start == CS_MODELS)
				Com_Printf ("^3*Index: overflow for models\n");
			else if (start == CS_SOUNDS_BERS)
				Com_Printf ("^3*Index: overflow for sounds\n");
			else if (start == CS_IMAGES_BERS)
				Com_Printf ("^3*Index: overflow for images\n");
			else
				Com_Printf ("^3*Index: overflow\n");
		}
		return max-1;
#endif
	}

	strncpy (sv.configstrings[start+i], name, sizeof(sv.configstrings[i]));

	if (sv.state != ss_loading)
	{	// send the update to everyone
		SZ_Clear (&sv.multicast);
		MSG_WriteByte (&sv.multicast, svc_configstring);
		MSG_WriteShort (&sv.multicast, start+i);
		MSG_WriteString (&sv.multicast, name);
		SV_Multicast (NULL/*vec3_origin*/, MULTICAST_ALL_R);
	}

	return i;
}


int SV_ModelIndex (char *name)
{
	if (net_compatibility->value)
		return SV_FindIndex (name, CS_MODELS, MAX_MODELS_Q2/*, true*/);
	else
		return SV_FindIndex (name, CS_MODELS, MAX_MODELS_BERS/*, true*/);
}


int SV_SoundIndex (char *name)
{
	if (net_compatibility->value)
		return SV_FindIndex (name, CS_SOUNDS_Q2, MAX_SOUNDS_Q2/*, true*/);
	else
		return SV_FindIndex (name, CS_SOUNDS_BERS, MAX_SOUNDS_BERS/*, true*/);
}


int SV_ImageIndex (char *name)
{
	if (net_compatibility->value)
		return SV_FindIndex (name, CS_IMAGES_Q2, MAX_IMAGES_Q2/*, true*/);
	else
		return SV_FindIndex (name, CS_IMAGES_BERS, MAX_IMAGES_BERS/*, true*/);
}


cmodel_t	*CM_InlineModel (char *name)
{
	int		num;

	if (!name || name[0] != '*')
		Com_Error (ERR_DROP, "CM_InlineModel: bad name");
	num = atoi (name+1);
	if (num < 1 || num >= numcmodels)
		Com_Error (ERR_DROP, "CM_InlineModel: bad number");

	return &map_cmodels[num];
}


/*
=================
PF_setmodel

Also sets mins and maxs for inline bmodels
=================
*/
void PF_setmodel (edict_t *ent, char *name)
{
	int		i;
	cmodel_t	*mod;

	if (!name)
		Com_Error (ERR_DROP, "PF_setmodel: NULL");

	i = SV_ModelIndex (name);
		
//	ent->model = name;
	ent->s.modelindex = i;

// if it is an inline model, get the size information for it
	if (name[0] == '*')
	{
		mod = CM_InlineModel (name);
		VectorCopy (mod->mins, ent->mins);
		VectorCopy (mod->maxs, ent->maxs);
		SV_LinkEdict (ent);
	}

}


/*
=================
PF_inPVS

Also checks portalareas so that doors block sight
=================
*/
bool PF_inPVS (vec3_t p1, vec3_t p2)
{
	int		leafnum;
	int		cluster;
	int		area1, area2;
	byte	*mask;

	leafnum = CM_PointLeafnum (p1);
	cluster = CM_LeafCluster (leafnum);
	area1 = CM_LeafArea (leafnum);
	mask = CM_ClusterPVS (cluster);

	leafnum = CM_PointLeafnum (p2);
	cluster = CM_LeafCluster (leafnum);
	area2 = CM_LeafArea (leafnum);
	if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) )
		return false;
	if (!CM_AreasConnected (area1, area2))
		return false;		// a door blocks sight
	return true;
}


/*
=================
PF_inPHS

Also checks portalareas so that doors block sound
=================
*/
bool PF_inPHS (vec3_t p1, vec3_t p2)
{
	int		leafnum;
	int		cluster;
	int		area1, area2;
	byte	*mask;

	leafnum = CM_PointLeafnum (p1);
	cluster = CM_LeafCluster (leafnum);
	area1 = CM_LeafArea (leafnum);
	mask = CM_ClusterPHS (cluster);

	leafnum = CM_PointLeafnum (p2);
	cluster = CM_LeafCluster (leafnum);
	area2 = CM_LeafArea (leafnum);
	if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) )
		return false;		// more than one bounce away
	if (!CM_AreasConnected (area1, area2))
		return false;		// a door blocks hearing
	return true;
}


void PM_ClampAngles ()
{
	short	temp;
	int		i;

	if (pm->s.pm_flags & PMF_TIME_TELEPORT)
	{
		pm->viewangles[YAW] = SHORT2ANGLE(pm->cmd.angles[YAW] + pm->s.delta_angles[YAW]);
		pm->viewangles[PITCH] = 0;
		pm->viewangles[ROLL] = 0;
	}
	else
	{
		// circularly clamp the angles with deltas
		for (i=0 ; i<3 ; i++)
		{
			temp = pm->cmd.angles[i] + pm->s.delta_angles[i];
			pm->viewangles[i] = SHORT2ANGLE(temp);
		}

		// don't let the player look up or down more than 90 degrees
		if (pm->viewangles[PITCH] > 89 && pm->viewangles[PITCH] < 180)
			pm->viewangles[PITCH] = 89;
		else if (pm->viewangles[PITCH] < 271 && pm->viewangles[PITCH] >= 180)
			pm->viewangles[PITCH] = 271;
	}
	AngleVectors (pm->viewangles, pml.forward, pml.right, pml.up);
}


float VectorLength(vec3_t v)
{
	int		i;
	float	length;
	
	length = 0;
	for (i=0 ; i< 3 ; i++)
		length += v[i]*v[i];
	length = Sqrt (length);

	return length;
}


void VectorScale (vec3_t in, float scale, vec3_t out)
{
	out[0] = in[0] * scale;
	out[1] = in[1] * scale;
	out[2] = in[2] * scale;
}


float VectorNormalize (vec3_t v)
{
	float	length, ilength;
	length = Sqrt (DotProduct(v, v));

	if (length)
	{
		ilength = 1/length;
		v[0] *= ilength;
		v[1] *= ilength;
		v[2] *= ilength;
	}
		
	return length;
}


void PM_FlyMove (bool doclip)
{
	float	speed, drop, friction, control, newspeed;
	float	currentspeed, addspeed, accelspeed;
	int			i;
	vec3_t		wishvel;
	float		fmove, smove;
	vec3_t		wishdir;
	float		wishspeed;
	vec3_t		end;
	trace_t	trace;

	pm->viewheight = 22;

	// friction

	speed = VectorLength (pml.velocity);
	if (speed < 1)
		VectorClear (pml.velocity);
	else
	{
		drop = 0;

		friction = pm_friction*1.5;	// extra friction
		control = speed < pm_stopspeed ? pm_stopspeed : speed;
		drop += control*friction*pml.frametime;

		// scale the velocity
		newspeed = speed - drop;
		if (newspeed < 0)
			newspeed = 0;
		newspeed /= speed;

		VectorScale (pml.velocity, newspeed, pml.velocity);
	}

	// accelerate
	fmove = pm->cmd.forwardmove;
	smove = pm->cmd.sidemove;
	
	VectorNormalize (pml.forward);
	VectorNormalize (pml.right);

	for (i=0 ; i<3 ; i++)
		wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
	wishvel[2] += pm->cmd.upmove;

	VectorCopy (wishvel, wishdir);
	wishspeed = VectorNormalize(wishdir);

	//
	// clamp to server defined max speed
	//
	if (wishspeed > pm_maxspeed)
	{
		VectorScale (wishvel, pm_maxspeed/wishspeed, wishvel);
		wishspeed = pm_maxspeed;
	}


	currentspeed = DotProduct(pml.velocity, wishdir);
	addspeed = wishspeed - currentspeed;
	if (addspeed <= 0)
		return;
	accelspeed = pm_accelerate*pml.frametime*wishspeed;
	if (accelspeed > addspeed)
		accelspeed = addspeed;
	
	for (i=0 ; i<3 ; i++)
		pml.velocity[i] += accelspeed*wishdir[i];	

	if (doclip) {
		for (i=0 ; i<3 ; i++)
			end[i] = pml.origin[i] + pml.frametime * pml.velocity[i];

		trace = pm->trace (pml.origin, pm->mins, pm->maxs, end, MASK_PLAYERSOLID|CONTENTS_AUX);

		VectorCopy (trace.endpos, pml.origin);
	} else {
		// move
		VectorMA (pml.origin, pml.frametime, pml.velocity, pml.origin);
	}
}


/*
==============
PM_CheckDuck

Sets mins, maxs, and pm->viewheight
==============
*/
void PM_CheckDuck ()
{
	trace_t	trace;

	pm->mins[0] = -16;
	pm->mins[1] = -16;

	pm->maxs[0] = 16;
	pm->maxs[1] = 16;

	if (pm->s.pm_type == PM_GIB)
	{
		pm->mins[2] = 0;
		pm->maxs[2] = 16;
		pm->viewheight = 8;
		return;
	}

	pm->mins[2] = -24;

	if (pm->s.pm_type == PM_DEAD)
	{
		pm->s.pm_flags |= PMF_DUCKED;
	}
	else if (pm->cmd.upmove < 0 && (pm->s.pm_flags & PMF_ON_GROUND) )
	{	// duck
		pm->s.pm_flags |= PMF_DUCKED;
	}
	else
	{	// stand up if possible
		if (pm->s.pm_flags & PMF_DUCKED)
		{
			// try to stand up
			pm->maxs[2] = 32;
			trace = pm->trace (pml.origin, pm->mins, pm->maxs, pml.origin, MASK_PLAYERSOLID|CONTENTS_AUX);
			if (!trace.allsolid)
				pm->s.pm_flags &= ~PMF_DUCKED;
		}
	}

	if (pm->s.pm_flags & PMF_DUCKED)
	{
		pm->maxs[2] = 4;
		pm->viewheight = -2;
	}
	else
	{
		pm->maxs[2] = 32;
		pm->viewheight = 22;
	}
}


void PM_DeadMove ()
{
	float	forward;

	if (!pm->groundentity)
		return;

	// extra friction
	forward = VectorNormalize (pml.velocity);
	forward -= 20;
	if (forward <= 0)
		VectorClear (pml.velocity);
	else
		VectorScale (pml.velocity, forward, pml.velocity);
}


bool	PM_GoodPosition ()
{
	trace_t	trace;
	vec3_t	origin, end;
	int		i;

	if (pm->s.pm_type == PM_SPECTATOR)
		return true;

	for (i=0 ; i<3 ; i++)
		origin[i] = end[i] = pm->s.origin[i]*0.125;
	trace = pm->trace (origin, pm->mins, pm->maxs, end, MASK_PLAYERSOLID|CONTENTS_AUX);

	return !trace.allsolid;
}


/*
================
PM_SnapPosition

On exit, the origin will have a value that is pre-quantized to the 0.125
precision of the network channel and in a valid position.
================
*/
void PM_SnapPosition ()
{
	int		sign[3];
	int		i, j, bits;
///	short	base[3];
	int		base[3];	// LARGE_MAP_SIZE support
	// try all single bits first
	static int jitterbits[8] = {0,4,1,2,3,5,6,7};

	// snap velocity to eigths
	for (i=0 ; i<3 ; i++)
		pm->s.velocity[i] = (int)(pml.velocity[i]*8);

	for (i=0 ; i<3 ; i++)
	{
		if (pml.origin[i] >= 0)
			sign[i] = 1;
		else 
			sign[i] = -1;
		pm->s.origin[i] = (int)(pml.origin[i]*8);
		if (pm->s.origin[i]*0.125 == pml.origin[i])
			sign[i] = 0;
	}
	VectorCopy (pm->s.origin, base);

	// try all combinations
	for (j=0 ; j<8 ; j++)
	{
		bits = jitterbits[j];
		VectorCopy (base, pm->s.origin);
		for (i=0 ; i<3 ; i++)
			if (bits & (1<<i) )
				pm->s.origin[i] += sign[i];

		if (PM_GoodPosition ())
			return;
	}

	// go back to the last position
	VectorCopy (pml.previous_origin, pm->s.origin);
}


void PM_InitialSnapPosition()
{
	int			x, y, z;
///	short		base[3];
	int			base[3];	// LARGE_MAP_SIZE support
	static int	offset[3] = { 0, -1, 1 };

	VectorCopy (pm->s.origin, base);

	for ( z = 0; z < 3; z++ ) {
		pm->s.origin[2] = base[2] + offset[ z ];
		for ( y = 0; y < 3; y++ ) {
			pm->s.origin[1] = base[1] + offset[ y ];
			for ( x = 0; x < 3; x++ ) {
				pm->s.origin[0] = base[0] + offset[ x ];
				if (PM_GoodPosition ()) {
					pml.origin[0] = pm->s.origin[0]*0.125;
					pml.origin[1] = pm->s.origin[1]*0.125;
					pml.origin[2] = pm->s.origin[2]*0.125;
					VectorCopy (pm->s.origin, pml.previous_origin);
					return;
				}
			}
		}
	}

	Com_DPrintf ("Bad InitialSnapPosition\n");
}


void PM_CatagorizePosition ()
{
	vec3_t		point;
	int			cont;
	trace_t		trace;
	int			sample1;
	int			sample2;

// if the player hull point one unit down is solid, the player
// is on ground

// see if standing on something solid	
	point[0] = pml.origin[0];
	point[1] = pml.origin[1];
	point[2] = pml.origin[2] - 0.25;
	if (pml.velocity[2] > 180) //!!ZOID changed from 100 to 180 (ramp accel)
	{
		pm->s.pm_flags &= ~PMF_ON_GROUND;
		pm->groundentity = NULL;
	}
	else
	{
		trace = pm->trace (pml.origin, pm->mins, pm->maxs, point, MASK_PLAYERSOLID|CONTENTS_AUX);
		pml.groundplane = trace.plane;
		pml.groundsurface = trace.surface;
		pml.groundcontents = trace.contents;

/// Berserker: fixed jitclipbug (Thnx to Quake Standarts Group!)
#if 0
		if (!trace.ent || (trace.plane.normal[2] < 0.7 && !trace.startsolid) )
		{
			pm->groundentity = NULL;
			pm->s.pm_flags &= ~PMF_ON_GROUND;
		}
#else
		if (trace.plane.normal[2] < 0.7f && !trace.startsolid) // jitclipbug
		{
			trace_t      trace2;
			vec3_t      mins, maxs;

			// try a slightly smaller bounding box -- this is to fix getting stuck up
			// on angled walls and not being able to move (like you're stuck in the air)
			mins[0] = pm->mins[0] ? pm->mins[0] + 1 : 0;
			mins[1] = pm->mins[1] ? pm->mins[1] + 1 : 0;
			mins[2] = pm->mins[2];
			maxs[0] = pm->maxs[0] ? pm->maxs[0] - 1 : 0;
			maxs[1] = pm->maxs[1] ? pm->maxs[1] - 1 : 0;
			maxs[2] = pm->maxs[2];
			trace2 = pm->trace(pml.origin, mins, maxs, point, MASK_PLAYERSOLID|CONTENTS_AUX);

			if (!(trace2.plane.normal[2] < 0.7f && !trace2.startsolid))
			{
				Memcpy(&trace, &trace2, sizeof(trace));
				pml.groundplane = trace.plane;
				pml.groundsurface = trace.surface;
				pml.groundcontents = trace.contents;
				pm->groundentity = trace.ent;
			}
		}
		else if (!trace.ent)
		{
			pm->groundentity = NULL;
			pm->s.pm_flags &= ~PMF_ON_GROUND;
		}
#endif
		else
		{
			pm->groundentity = trace.ent;

			// hitting solid ground will end a waterjump
			if (pm->s.pm_flags & PMF_TIME_WATERJUMP)
			{
				pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT);
				pm->s.pm_time = 0;
			}

			if (! (pm->s.pm_flags & PMF_ON_GROUND) )
			{	// just hit the ground
				pm->s.pm_flags |= PMF_ON_GROUND;
				// don't do landing time if we were just going down a slope
				if (pml.velocity[2] < -200)
				{
					pm->s.pm_flags |= PMF_TIME_LAND;
					// don't allow another jump for a little while
					if (pml.velocity[2] < -400)
						pm->s.pm_time = 25;	
					else
						pm->s.pm_time = 18;
				}
			}
		}

		if (pm->numtouch < MAXTOUCH && trace.ent)
		{
			pm->touchents[pm->numtouch] = trace.ent;
			pm->numtouch++;
		}
	}

//
// get waterlevel, accounting for ducking
//
	pm->waterlevel = 0;
	pm->watertype = 0;

	sample2 = pm->viewheight - pm->mins[2];
	sample1 = sample2 / 2;

	point[2] = pml.origin[2] + pm->mins[2] + 1;	
	cont = pm->pointcontents (point);

	if (cont & MASK_WATER)
	{
		pm->watertype = cont;
		pm->waterlevel = 1;
		point[2] = pml.origin[2] + pm->mins[2] + sample1;
		cont = pm->pointcontents (point);
		if (cont & MASK_WATER)
		{
			pm->waterlevel = 2;
			point[2] = pml.origin[2] + pm->mins[2] + sample2;
			cont = pm->pointcontents (point);
			if (cont & MASK_WATER)
				pm->waterlevel = 3;
		}
	}

}


void PM_CheckSpecialMovement ()
{
	vec3_t	spot;
	int		cont;
	vec3_t	flatforward;
	trace_t	trace;

	if (pm->s.pm_time)
		return;

	pml.ladder = false;

	// check for ladder
	flatforward[0] = pml.forward[0];
	flatforward[1] = pml.forward[1];
	flatforward[2] = 0;
	VectorNormalize (flatforward);

	VectorMA (pml.origin, 1, flatforward, spot);
	trace = pm->trace (pml.origin, pm->mins, pm->maxs, spot, MASK_PLAYERSOLID|CONTENTS_AUX);
	if ((trace.fraction < 1) && (trace.contents & CONTENTS_LADDER))
		pml.ladder = true;

	// check for water jump
	if (pm->waterlevel != 2)
		return;

	VectorMA (pml.origin, 30, flatforward, spot);
	spot[2] += 4;
	cont = pm->pointcontents (spot);
	if (!(cont & CONTENTS_SOLID) && !(cont & CONTENTS_AUX))
		return;

	spot[2] += 16;
	cont = pm->pointcontents (spot);
	if (cont)
		return;
	// jump out of water
	VectorScale (flatforward, 50, pml.velocity);
	pml.velocity[2] = 350;

	pm->s.pm_flags |= PMF_TIME_WATERJUMP;
	pm->s.pm_time = 255;
}


/*
==================
PM_ClipVelocity

Slide off of the impacting object
returns the blocked flags (1 = floor, 2 = step / wall)
==================
*/
#define	STOP_EPSILON	0.1

void PM_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)
{
	float	backoff;
	float	change;
	int		i;
	
	backoff = DotProduct (in, normal) * overbounce;

	for (i=0 ; i<3 ; i++)
	{
		change = normal[i]*backoff;
		out[i] = in[i] - change;
		if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
			out[i] = 0;
	}
}


void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross)
{
	cross[0] = v1[1]*v2[2] - v1[2]*v2[1];
	cross[1] = v1[2]*v2[0] - v1[0]*v2[2];
	cross[2] = v1[0]*v2[1] - v1[1]*v2[0];
}


/*
==================
PM_StepSlideMove

Each intersection will try to step over the obstruction instead of
sliding along it.

Returns a new origin, velocity, and contact entity
Does not modify any world state?
==================
*/
#define	MIN_STEP_NORMAL	0.7		// can't step up onto very steep slopes
#define	MAX_CLIP_PLANES	5
void PM_StepSlideMove_ ()
{
	int			bumpcount, numbumps;
	vec3_t		dir;
	float		d;
	int			numplanes;
	vec3_t		planes[MAX_CLIP_PLANES];
	vec3_t		primal_velocity;
	int			i, j;
	trace_t	trace;
	vec3_t		end;
	float		time_left;
	
	numbumps = 4;
	
	VectorCopy (pml.velocity, primal_velocity);
	numplanes = 0;
	
	time_left = pml.frametime;

	for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
	{
		for (i=0 ; i<3 ; i++)
			end[i] = pml.origin[i] + time_left * pml.velocity[i];

		trace = pm->trace (pml.origin, pm->mins, pm->maxs, end, MASK_PLAYERSOLID|CONTENTS_AUX);

		if (trace.allsolid)
		{	// entity is trapped in another solid
			pml.velocity[2] = 0;	// don't build up falling damage
			return;
		}

		if (trace.fraction > 0)
		{	// actually covered some distance
			VectorCopy (trace.endpos, pml.origin);
			numplanes = 0;
		}

		if (trace.fraction == 1)
			 break;		// moved the entire distance

		// save entity for contact
		if (pm->numtouch < MAXTOUCH && trace.ent)
		{
			pm->touchents[pm->numtouch] = trace.ent;
			pm->numtouch++;
		}
		
		time_left -= time_left * trace.fraction;

		// slide along this plane
		if (numplanes >= MAX_CLIP_PLANES)
		{	// this shouldn't really happen
			VectorClear (pml.velocity);
			break;
		}

		VectorCopy (trace.plane.normal, planes[numplanes]);
		numplanes++;

//
// modify original_velocity so it parallels all of the clip planes
//
		for (i=0 ; i<numplanes ; i++)
		{
			PM_ClipVelocity (pml.velocity, planes[i], pml.velocity, 1.01);
			for (j=0 ; j<numplanes ; j++)
				if (j != i)
				{
					if (DotProduct (pml.velocity, planes[j]) < 0)
						break;	// not ok
				}
			if (j == numplanes)
				break;
		}
		
		if (i != numplanes)
		{	// go along this plane
		}
		else
		{	// go along the crease
			if (numplanes != 2)
			{
				VectorClear (pml.velocity);
				break;
			}
			CrossProduct (planes[0], planes[1], dir);
			d = DotProduct (dir, pml.velocity);
			VectorScale (dir, d, pml.velocity);
		}

		//
		// if velocity is against the original velocity, stop dead
		// to avoid tiny occilations in sloping corners
		//
		if (DotProduct (pml.velocity, primal_velocity) <= 0)
		{
			VectorClear (pml.velocity);
			break;
		}
	}

	if (pm->s.pm_time)
		VectorCopy (primal_velocity, pml.velocity);
}


void PM_StepSlideMove ()
{
	vec3_t		start_o, start_v;
	vec3_t		down_o, down_v;
	trace_t		trace;
	float		down_dist, up_dist;
	vec3_t		up, down;

	VectorCopy (pml.origin, start_o);
	VectorCopy (pml.velocity, start_v);

	PM_StepSlideMove_ ();

	VectorCopy (pml.origin, down_o);
	VectorCopy (pml.velocity, down_v);

	VectorCopy (start_o, up);
	up[2] += STEPSIZE;

	trace = pm->trace (up, pm->mins, pm->maxs, up, MASK_PLAYERSOLID|CONTENTS_AUX);
	if (trace.allsolid)
		return;		// can't step up

	// try sliding above
	VectorCopy (up, pml.origin);
	VectorCopy (start_v, pml.velocity);

	PM_StepSlideMove_ ();

	// push down the final amount
	VectorCopy (pml.origin, down);
	down[2] -= STEPSIZE;
	trace = pm->trace (pml.origin, pm->mins, pm->maxs, down, MASK_PLAYERSOLID|CONTENTS_AUX);
	if (!trace.allsolid)
		VectorCopy (trace.endpos, pml.origin);

	VectorCopy(pml.origin, up);

	// decide which one went farther
    down_dist = (down_o[0] - start_o[0])*(down_o[0] - start_o[0]) + (down_o[1] - start_o[1])*(down_o[1] - start_o[1]);
    up_dist = (up[0] - start_o[0])*(up[0] - start_o[0]) + (up[1] - start_o[1])*(up[1] - start_o[1]);

	if (down_dist > up_dist || trace.plane.normal[2] < MIN_STEP_NORMAL)
	{
		VectorCopy (down_o, pml.origin);
		VectorCopy (down_v, pml.velocity);
		return;
	}
	//!! Special case
	// if we were walking along a plane, then we need to copy the Z over
	pml.velocity[2] = down_v[2];
}


void PM_CheckJump ()
{
	int dm_flag = Cvar_VariableValue("dmflags");

	if (pm->s.pm_flags & PMF_TIME_LAND)
	{	// hasn't been long enough since landing to jump again
		return;
	}

	if (pm->cmd.upmove < 10)
	{	// not holding jump
		pm->s.pm_flags &= ~PMF_JUMP_HELD;
		return;
	}

	// must wait for jump to be released
	if (pm->s.pm_flags & PMF_JUMP_HELD)
		return;

	if (pm->s.pm_type == PM_DEAD)
		return;

	if (pm->waterlevel >= 2)
	{	// swimming, not jumping
		pm->groundentity = NULL;

		if (pml.velocity[2] <= -300)
			return;

		if (pm->watertype == CONTENTS_WATER)
			pml.velocity[2] = 100;
		else if (pm->watertype == CONTENTS_SLIME)
			pml.velocity[2] = 80;
		else
			pml.velocity[2] = 50;
		return;
	}

	// ArchAngel double jump from inside3d forum, thanx Kirk Barnes
	if(dm_flag & DF_UT_DOUBLE_JUMP)
	{
		if (pm->groundentity == NULL)
		{
			if(pm->s.pm_flags & PMF_DOUBLE_JUMP)
			{
				pm->s.pm_flags |= PMF_JUMP_HELD;
				pm->s.pm_flags  &= ~PMF_DOUBLE_JUMP;
///				if(pml.velocity[2] > 0)		//Still rising?		Berserker: Doom 2016 style double jump
				{
					if(pml.velocity[2] < 270)
						pml.velocity[2] = 270;
				}
			}
			return;		// in air, so no effect
		}

		pm->s.pm_flags |= PMF_JUMP_HELD;

		pm->groundentity = NULL;
		pml.velocity[2] += 270;
		if (pml.velocity[2] < 270)
			pml.velocity[2] = 270;
		pm->s.pm_flags |= PMF_DOUBLE_JUMP;
	}
	else
	{
		if (pm->groundentity == NULL)
			return;		// in air, so no effect

		pm->s.pm_flags |= PMF_JUMP_HELD;

		pm->groundentity = NULL;
		pml.velocity[2] += 270;
		if (pml.velocity[2] < 270)
			pml.velocity[2] = 270;
	}
}


/*
==================
PM_Friction

Handles both ground friction and water friction
==================
*/
void PM_Friction ()
{
	float	*vel;
	float	speed, newspeed, control;
	float	friction;
	float	drop;
	
	vel = pml.velocity;
	
	speed = Sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]);
	if (speed < 1)
	{
		vel[0] = 0;
		vel[1] = 0;
		return;
	}

	drop = 0;

// apply ground friction
	if ((pm->groundentity && pml.groundsurface && !(pml.groundsurface->flags & SURF_SLICK) ) || (pml.ladder) )
	{
		friction = pm_friction;
		control = speed < pm_stopspeed ? pm_stopspeed : speed;
		drop += control*friction*pml.frametime;
	}

// apply water friction
	if (pm->waterlevel && !pml.ladder)
		drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime;

// scale the velocity
	newspeed = speed - drop;
	if (newspeed < 0)
	{
		newspeed = 0;
	}
	newspeed /= speed;

	vel[0] = vel[0] * newspeed;
	vel[1] = vel[1] * newspeed;
	vel[2] = vel[2] * newspeed;
}


void PM_AddCurrents (vec3_t	wishvel)
{
	vec3_t	v;
	float	s;

	//
	// account for ladders
	//
	if (pml.ladder && fabs(pml.velocity[2]) <= 200)
	{
		if ((pm->viewangles[PITCH] <= -15) && (pm->cmd.forwardmove > 0))
			wishvel[2] = 200;
		else if ((pm->viewangles[PITCH] >= 15) && (pm->cmd.forwardmove > 0))
			wishvel[2] = -200;
		else if (pm->cmd.upmove > 0)
			wishvel[2] = 200;
		else if (pm->cmd.upmove < 0)
			wishvel[2] = -200;
		else
			wishvel[2] = 0;

		// limit horizontal speed when on a ladder
		if (wishvel[0] < -25)
			wishvel[0] = -25;
		else if (wishvel[0] > 25)
			wishvel[0] = 25;

		if (wishvel[1] < -25)
			wishvel[1] = -25;
		else if (wishvel[1] > 25)
			wishvel[1] = 25;
	}


	//
	// add water currents
	//
	if (pm->watertype & MASK_CURRENT)
	{
		VectorClear (v);

		if (pm->watertype & CONTENTS_CURRENT_0)
			v[0] += 1;
		if (pm->watertype & CONTENTS_CURRENT_90)
			v[1] += 1;
		if (pm->watertype & CONTENTS_CURRENT_180)
			v[0] -= 1;
		if (pm->watertype & CONTENTS_CURRENT_270)
			v[1] -= 1;
		if (pm->watertype & CONTENTS_CURRENT_UP)
			v[2] += 1;
		if (pm->watertype & CONTENTS_CURRENT_DOWN)
			v[2] -= 1;

		s = pm_waterspeed;
		if ((pm->waterlevel == 1) && (pm->groundentity))
			s /= 2;

		VectorMA (wishvel, s, v, wishvel);
	}

	//
	// add conveyor belt velocities
	//
	if (pm->groundentity)
	{
		VectorClear (v);

		if (pml.groundcontents & CONTENTS_CURRENT_0)
			v[0] += 1;
		if (pml.groundcontents & CONTENTS_CURRENT_90)
			v[1] += 1;
		if (pml.groundcontents & CONTENTS_CURRENT_180)
			v[0] -= 1;
		if (pml.groundcontents & CONTENTS_CURRENT_270)
			v[1] -= 1;
		if (pml.groundcontents & CONTENTS_CURRENT_UP)
			v[2] += 1;
		if (pml.groundcontents & CONTENTS_CURRENT_DOWN)
			v[2] -= 1;

		VectorMA (wishvel, 100 /* pm->groundentity->speed */, v, wishvel);
	}
}


/*
==============
PM_Accelerate

Handles user intended acceleration
==============
*/
void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel)
{
	int			i;
	float		addspeed, accelspeed, currentspeed;

	currentspeed = DotProduct (pml.velocity, wishdir);
	addspeed = wishspeed - currentspeed;
	if (addspeed <= 0)
		return;
	accelspeed = accel*pml.frametime*wishspeed;
	if (accelspeed > addspeed)
		accelspeed = addspeed;
	
	for (i=0 ; i<3 ; i++)
		pml.velocity[i] += accelspeed*wishdir[i];	
}


void PM_WaterMove ()
{
	int		i;
	vec3_t	wishvel;
	float	wishspeed;
	vec3_t	wishdir;

//
// user intentions
//
	for (i=0 ; i<3 ; i++)
		wishvel[i] = pml.forward[i]*pm->cmd.forwardmove + pml.right[i]*pm->cmd.sidemove;

	if (!pm->cmd.forwardmove && !pm->cmd.sidemove && !pm->cmd.upmove)
		wishvel[2] -= 60;		// drift towards bottom
	else
		wishvel[2] += pm->cmd.upmove;

	PM_AddCurrents (wishvel);

	VectorCopy (wishvel, wishdir);
	wishspeed = VectorNormalize(wishdir);

	if (wishspeed > pm_maxspeed)
	{
		VectorScale (wishvel, pm_maxspeed/wishspeed, wishvel);
		wishspeed = pm_maxspeed;
	}
	wishspeed *= 0.5;

	PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate);

	PM_StepSlideMove ();
}


void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel)
{
	int			i;
	float		addspeed, accelspeed, currentspeed, wishspd = wishspeed;
		
	if (wishspd > 30)
		wishspd = 30;
	currentspeed = DotProduct (pml.velocity, wishdir);
	addspeed = wishspd - currentspeed;
	if (addspeed <= 0)
		return;
	accelspeed = accel * wishspeed * pml.frametime;
	if (accelspeed > addspeed)
		accelspeed = addspeed;
	
	for (i=0 ; i<3 ; i++)
		pml.velocity[i] += accelspeed*wishdir[i];	
}


/*
===================
PM_AirMove

===================
*/
void PM_AirMove ()
{
	int			i;
	vec3_t		wishvel;
	float		fmove, smove;
	vec3_t		wishdir;
	float		wishspeed;
	float		maxspeed;

	fmove = pm->cmd.forwardmove;
	smove = pm->cmd.sidemove;
	
	for (i=0 ; i<2 ; i++)
		wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
	wishvel[2] = 0;

	PM_AddCurrents (wishvel);

	VectorCopy (wishvel, wishdir);
	wishspeed = VectorNormalize(wishdir);

//
// clamp to server defined max speed
//
	maxspeed = (pm->s.pm_flags & PMF_DUCKED) ? pm_duckspeed : pm_maxspeed;

	if (wishspeed > maxspeed)
	{
		VectorScale (wishvel, maxspeed/wishspeed, wishvel);
		wishspeed = maxspeed;
	}
	
	if ( pml.ladder )
	{
		PM_Accelerate (wishdir, wishspeed, pm_accelerate);
		if (!wishvel[2])
		{
			if (pml.velocity[2] > 0)
			{
				pml.velocity[2] -= pm->s.gravity * pml.frametime;
				if (pml.velocity[2] < 0)
					pml.velocity[2]  = 0;
			}
			else
			{
				pml.velocity[2] += pm->s.gravity * pml.frametime;
				if (pml.velocity[2] > 0)
					pml.velocity[2]  = 0;
			}
		}
		PM_StepSlideMove ();
	}
	else if ( pm->groundentity )
	{	// walking on ground
		pml.velocity[2] = 0; //!!! this is before the accel
		PM_Accelerate (wishdir, wishspeed, pm_accelerate);

		if(pm->s.gravity > 0)
			pml.velocity[2] = 0;
		else
			pml.velocity[2] -= pm->s.gravity * pml.frametime;

		if (!pml.velocity[0] && !pml.velocity[1])
			return;
		PM_StepSlideMove ();
	}
	else
	{	// not on ground, so little effect on velocity
		if (pm_airaccelerate)
			PM_AirAccelerate (wishdir, wishspeed, pm_accelerate);
		else
			PM_Accelerate (wishdir, wishspeed, 1);
		// add gravity
		pml.velocity[2] -= pm->s.gravity * pml.frametime;
		PM_StepSlideMove ();
	}
}


/*
================
Pmove

Can be called by either the server or the client
================
*/
void Pmove (pmove_t *pmove)
{
	pm = pmove;

	// clear results
	pm->numtouch = 0;
	VectorClear (pm->viewangles);
	pm->viewheight = 0;
	pm->groundentity = 0;
	pm->watertype = 0;
	pm->waterlevel = 0;

	// clear all pmove local vars
	Memset (&pml, 0, sizeof(pml));

	// convert origin and velocity to float values
	pml.origin[0] = pm->s.origin[0]*0.125;
	pml.origin[1] = pm->s.origin[1]*0.125;
	pml.origin[2] = pm->s.origin[2]*0.125;

	pml.velocity[0] = pm->s.velocity[0]*0.125;
	pml.velocity[1] = pm->s.velocity[1]*0.125;
	pml.velocity[2] = pm->s.velocity[2]*0.125;

	// save old org in case we get stuck
	VectorCopy (pm->s.origin, pml.previous_origin);

	pml.frametime = pm->cmd.msec * 0.001;

	PM_ClampAngles ();

	if (pm->s.pm_type == PM_SPECTATOR)
	{
		PM_FlyMove (false);
		PM_SnapPosition ();
		return;
	}

	if (pm->s.pm_type >= PM_DEAD)
	{
		pm->cmd.forwardmove = 0;
		pm->cmd.sidemove = 0;
		pm->cmd.upmove = 0;
	}

	if (pm->s.pm_type == PM_FREEZE)
		return;		// no movement at all

	// set mins, maxs, and viewheight
	PM_CheckDuck ();

	if (pm->snapinitial)
		PM_InitialSnapPosition ();

	// set groundentity, watertype, and waterlevel
	PM_CatagorizePosition ();

	if (pm->s.pm_type == PM_DEAD)
		PM_DeadMove ();

	PM_CheckSpecialMovement ();

	// drop timing counter
	if (pm->s.pm_time)
	{
		int		msec;

		msec = pm->cmd.msec >> 3;
		if (!msec)
			msec = 1;
		if ( msec >= pm->s.pm_time) 
		{
			pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT);
			pm->s.pm_time = 0;
		}
		else
			pm->s.pm_time -= msec;
	}

	if (pm->s.pm_flags & PMF_TIME_TELEPORT)
	{	// teleport pause stays exactly in place
	}
	else if (pm->s.pm_flags & PMF_TIME_WATERJUMP)
	{	// waterjump has no control, but falls
		pml.velocity[2] -= pm->s.gravity * pml.frametime;
		if (pml.velocity[2] < 0)
		{	// cancel as soon as we are falling down again
			pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT);
			pm->s.pm_time = 0;
		}

		PM_StepSlideMove ();
	}
	else
	{
		PM_CheckJump ();

		PM_Friction ();

		if (pm->waterlevel >= 2)
			PM_WaterMove ();
		else
		{
			vec3_t	angles;

			VectorCopy(pm->viewangles, angles);
			if (angles[PITCH] > 180)
				angles[PITCH] = angles[PITCH] - 360;
			angles[PITCH] /= 3;

			AngleVectors (angles, pml.forward, pml.right, pml.up);

			PM_AirMove ();
		}
	}

	// set groundentity, watertype, and waterlevel for final spot
	PM_CatagorizePosition ();

	PM_SnapPosition ();
}


char *PF_Lightstyle (int index)
{
	if (index < 1 || index >= MAX_LIGHTSTYLES_OVERRIDE)
		Com_Error (ERR_DROP, "lightstyle: bad index %i", index);

	return &lightstyles[index][0];
}


void PF_Configstring (int index, char *val)
{
	if (net_compatibility->value)
	{
		if (index < 0 || index >= MAX_CONFIGSTRINGS_Q2)
			Com_Error (ERR_DROP, "configstring: bad index %i", index);
	}
	else
	{
		if (index < 0 || index >= MAX_CONFIGSTRINGS_BERS)
			Com_Error (ERR_DROP, "configstring: bad index %i", index);
	}

	if (!val)
		val = "";

	// change the string in sv
	strcpy (sv.configstrings[index], val);

	if (sv.state != ss_loading)
	{	// send the update to everyone
		SZ_Clear (&sv.multicast);
		MSG_WriteByte (&sv.multicast, svc_configstring);
		MSG_WriteShort (&sv.multicast, index);
		MSG_WriteString (&sv.multicast, val);

		SV_Multicast (NULL/*vec3_origin*/, MULTICAST_ALL_R);
	}
}


void StartModGame( void *self )
{
	cvar_t	*gamedir;
	STARTUPINFO			sti;
	PROCESS_INFORMATION	pinf;
	char	cmd[1024];
	char	startmap[1024];
	int		timelimit;
	int		fraglimit;
	int		maxclients;
	int		port = Cvar_Get("port", va("%i", OLD_PORT_SERVER), CVAR_NOSET)->value;

	// disable updates and start the cinematic going
	cl.servercount = -1;
	M_ForceMenuOff ();

	// kill local server if running
	Cbuf_AddText ("killserver\n");
	Cbuf_Execute ();

	Com_sprintf(r1q2_title, sizeof(r1q2_title), "R1Q2 dedicated server");
///	Com_sprintf(r1q2_down_title, sizeof(r1q2_down_title), "%s (port %d, down)", s_hostname_field.buffer, port);
///	Com_Printf("Dedicated server window title: \"%s\"\n", r1q2_title);

	// close dedicated server if runned
	CloseDedicatedServer();

	Memset(&sti,0,sizeof(STARTUPINFO));
	sti.cb=sizeof(STARTUPINFO);

	if (s_mapname_field.buffer[0])
		strcpy( startmap, s_mapname_field.buffer );
	else
		strcpy( startmap, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 );
	maxclients  = atoi( s_maxclients_field.buffer );
	timelimit	= atoi( s_timelimit_field.buffer );
	fraglimit	= atoi( s_fraglimit_field.buffer );

	if (s_rules_box.curvalue)
	{	// coop
		// check for game override
		gamedir = Cvar_Get ("game", "", CVAR_LATCH|CVAR_SERVERINFO);
		if (gamedir->string[0])
			Com_sprintf(cmd, sizeof(cmd), " +set dedicated 1 +set net_port %i +set game %s +set coop 1 +set deathmatch 0 +set cheats 1 +set maxclients %i +set timelimit %i +set fraglimit %i +set hostname %s +set skill %i +set dmflags %i +set protocol 34 +map %s", port, gamedir->string, maxclients, timelimit, fraglimit, s_hostname_field.buffer, s_skill_box.curvalue, (int)Cvar_VariableValue("dmflags"), startmap);
		else
			Com_sprintf(cmd, sizeof(cmd), " +set dedicated 1 +set net_port %i +set coop 1 +set deathmatch 0 +set cheats 1 +set maxclients %i +set timelimit %i +set fraglimit %i +set hostname %s +set skill %i +set dmflags %i +set protocol 34 +map %s", port, maxclients, timelimit, fraglimit, s_hostname_field.buffer, s_skill_box.curvalue, (int)Cvar_VariableValue("dmflags"), startmap);
	}
	else
	{	// deathmatch
		// check for game override
		gamedir = Cvar_Get ("game", "", CVAR_LATCH|CVAR_SERVERINFO);
		if (gamedir->string[0])
			Com_sprintf(cmd, sizeof(cmd), " +set dedicated 1 +set net_port %i +set game %s +set coop 0 +set deathmatch 1 +set cheats 0 +set maxclients %i +set timelimit %i +set fraglimit %i +set hostname %s +set skill %i +set dmflags %i +set protocol 34 +map %s", port, gamedir->string, maxclients, timelimit, fraglimit, s_hostname_field.buffer, s_skill_box.curvalue, (int)Cvar_VariableValue("dmflags"), startmap);
		else
			Com_sprintf(cmd, sizeof(cmd), " +set dedicated 1 +set net_port %i +set coop 0 +set deathmatch 1 +set cheats 0 +set maxclients %i +set timelimit %i +set fraglimit %i +set hostname %s +set skill %i +set dmflags %i +set protocol 34 +map %s", port, maxclients, timelimit, fraglimit, s_hostname_field.buffer, s_skill_box.curvalue, (int)Cvar_VariableValue("dmflags"), startmap);
	}

	Com_Printf("Command line for dedicated server:%s\n", cmd);

	// start dedicated server
	if (!CreateProcess("q2dedicated.exe", cmd, NULL, NULL, true, CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS, NULL, NULL, &sti, &pinf))
		Com_Printf("^1Error starting dedicated server\n");
	else
	{	// connect to dedicated server
		Sleep(1000);
		Cbuf_AddText ("loading ; wait ; connect loopback\n");
		SetForegroundWindow( glw_state.hWnd );
		SetFocus( glw_state.hWnd );
	}
	cls.key_dest = key_game;
}


/*  
==================
SV_StartSound

Each entity can have eight independant sound sources, like voice,
weapon, feet, etc.

If cahnnel & 8, the sound will be sent to everyone, not just
things in the PHS.

FIXME: if entity isn't in PHS, they must be forced to be sent or
have the origin explicitly sent.

Channel 0 is an auto-allocate channel, the others override anything
already running on that entity/channel pair.

An attenuation of 0 will play full volume everywhere in the level.
Larger attenuations will drop off.  (max 4 attenuation)

Timeofs can range from 0.0 to 0.1 to cause sounds to be started
later in the frame than they normally would.

If origin is NULL, the origin is determined from the entity origin
or the midpoint of the entity box for bmodels.
==================
*/  
void SV_StartSound (vec3_t origin, edict_t *entity, int channel, int soundindex, float volume, float attenuation, float timeofs)
{       
	int			sendchan;
    int			flags;
    int			i;
	int			ent;
	vec3_t		origin_v;
	bool		use_phs;

	if (volume < 0 || volume > 1.0)
		Com_Error (ERR_FATAL, "SV_StartSound: volume = %f", volume);

	if (attenuation < 0 || attenuation > 3)	/// Berserker's FIX: was 4     For attenuation = 4 MSG_WriteByte will send the NULL!!!
		Com_Error (ERR_FATAL, "SV_StartSound: attenuation = %f", attenuation);

	if (timeofs < 0 || timeofs > 0.255)
		Com_Error (ERR_FATAL, "SV_StartSound: timeofs = %f", timeofs);

	ent = NUM_FOR_EDICT(entity);

	if (channel & 8)	// no PHS flag
	{
		use_phs = false;
		channel &= 7;
	}
	else
		use_phs = true;

	sendchan = (ent<<3) | (channel&7);

	flags = 0;
	if (volume != DEFAULT_SOUND_PACKET_VOLUME)
		flags |= SND_VOLUME;
	if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION)
		flags |= SND_ATTENUATION;

	// the client doesn't know that bmodels have weird origins
	// the origin can also be explicitly set
	if ( (entity->svflags & SVF_NOCLIENT)
		|| (entity->solid == SOLID_BSP) 
		|| origin )
		flags |= SND_POS;

	// always send the entity number for channel overrides
	flags |= SND_ENT;

	if (timeofs)
		flags |= SND_OFFSET;

	// use the entity origin unless it is a bmodel or explicitly specified
	if (!origin)
	{
		origin = origin_v;
		if (entity->solid == SOLID_BSP)
		{
			for (i=0 ; i<3 ; i++)
				origin_v[i] = entity->s.origin[i]+0.5*(entity->mins[i]+entity->maxs[i]);
		}
		else
			VectorCopy (entity->s.origin, origin_v);
	}

	// LARGE_MAP_SIZE support
	bool large_map = false;
	if (!net_compatibility->value)
	{
		if (flags & SND_POS)
		{
			if (	origin[0] >  4095 || origin[0] < -4096
				||	origin[1] >  4095 || origin[1] < -4096
				||	origin[2] >  4095 || origin[2] < -4096 )
			{
				large_map = true;
				flags |= SND_LARGE_POS;
			}
		}
	}

	MSG_WriteByte (&sv.multicast, svc_sound);
	if (net_compatibility->value)
	{
sound8:	MSG_WriteByte (&sv.multicast, flags);
		MSG_WriteByte (&sv.multicast, soundindex);
	}
	else
	{	// расширенные индексы (by Knightmare, optimized by Berserker)
		if (soundindex > 255)
		{
			flags |= SND_INDEX16;
			MSG_WriteByte (&sv.multicast, flags);
			MSG_WriteShort (&sv.multicast, soundindex);
		}
		else
			goto sound8;
	}

	if (flags & SND_VOLUME)
		MSG_WriteByte (&sv.multicast, volume*255);
	if (flags & SND_ATTENUATION)
		MSG_WriteByte (&sv.multicast, attenuation*64);
	if (flags & SND_OFFSET)
		MSG_WriteByte (&sv.multicast, timeofs*1000);

	if (flags & SND_ENT)
		MSG_WriteShort (&sv.multicast, sendchan);

	if (flags & SND_POS)
		MSG_WritePos (&sv.multicast, origin, large_map);

	// if the sound doesn't attenuate,send it to everyone
	// (global radio chatter, voiceovers, etc)
	if (attenuation == ATTN_NONE)
		use_phs = false;

	if (channel & CHAN_RELIABLE)
	{
		if (use_phs)
			SV_Multicast (origin, MULTICAST_PHS_R);
		else
			SV_Multicast (NULL/*origin*/, MULTICAST_ALL_R);
	}
	else
	{
		if (use_phs)
			SV_Multicast (origin, MULTICAST_PHS);
		else
			SV_Multicast (NULL/*origin*/, MULTICAST_ALL);
	}
}           


void PF_StartSound (edict_t *entity, int channel, int sound_num, float volume, float attenuation, float timeofs)
{
	if (!entity)
		return;
	SV_StartSound (NULL, entity, channel, sound_num, volume, attenuation, timeofs);
}


void PF_WriteChar (int c) {MSG_WriteChar (&sv.multicast, c);}
void PF_WriteByte (int c) {MSG_WriteByte (&sv.multicast, c);}
void PF_WriteShort (int c) {MSG_WriteShort (&sv.multicast, c);}
void PF_WriteLong (int c) {MSG_WriteLong (&sv.multicast, c);}
void PF_WriteFloat (float f) {MSG_WriteFloat (&sv.multicast, f);}
void PF_WriteString (char *s) {MSG_WriteString (&sv.multicast, s);}
void PF_WritePos (vec3_t pos, bool large_map) {MSG_WritePos (&sv.multicast, pos, large_map);}
void PF_WriteDir (vec3_t dir) {MSG_WriteDir (&sv.multicast, dir);}
void PF_WriteAngle (float f) {MSG_WriteAngle (&sv.multicast, f);}


/*
void DPrintfAreaPortalState()
{
	int i;

	if(developer->value)
	{
		Com_Printf("/------------------------\\\n");
		for(i=0; i<numareaportals; i++)
			Com_Printf("portalopen[%i] = %i\n", i, portalopen[i]);

		for(i=0; i<numareas; i++)
			Com_Printf("map_areas[%i] = %i\n", i, map_areas[i].floodnum);
		Com_Printf("\\------------------------/\n");
	}
}
*/

void FloodArea_r (carea_t *area, int floodnum)
{
	int		i;
	dareaportal_t	*p;

	if (area->floodvalid == floodvalid)
	{
		if (area->floodnum == floodnum)
			return;
		Com_Error (ERR_DROP, "FloodArea_r: reflooded");
	}

	area->floodnum = floodnum;
	area->floodvalid = floodvalid;
	p = &map_areaportals[area->firstareaportal];
	for (i=0 ; i<area->numareaportals ; i++, p++)
	{
		if (portalopen[p->portalnum])
			FloodArea_r (&map_areas[p->otherarea], floodnum);
	}
}


void	FloodAreaConnections ()
{
	int		i;
	carea_t	*area;
	int		floodnum;

	// all current floods are now invalid
	floodvalid++;
	floodnum = 0;

	// area 0 is not used
	for (i=1 ; i<numareas ; i++)
	{
		area = &map_areas[i];
		if (area->floodvalid == floodvalid)
			continue;		// already flooded into
		floodnum++;
		FloodArea_r (area, floodnum);
	}
}


void	CM_SetAreaPortalState (int portalnum, bool open)
{
	if (portalnum > numareaportals)
		Com_Error (ERR_DROP, "areaportal > numareaportals");

//	DPrintfAreaPortalState();

	portalopen[portalnum] = open;
	FloodAreaConnections ();

//	Com_DPrintf ("CM_SetAreaPortalState: portalopen[%i] changed to %i\n", portalnum, open);
//	DPrintfAreaPortalState();
}


/*
=================
Sys_GetGameAPI

Loads the game dll
=================
*/
void *Sys_GetGameAPI (void *parms)
{
	void	*(*GetGameAPI) (void *);
	char	name[MAX_OSPATH];
	char	*path;
	char	cwd[MAX_OSPATH];
	const char *gamename = "game.dll";

	if (game_library)
		Com_Error (ERR_FATAL, "Sys_GetGameAPI without Sys_UnloadGame");

	// check the current debug directory first for development purposes
	_getcwd (cwd, sizeof(cwd));
	Com_sprintf (name, sizeof(name), "%s/%s", cwd, gamename);
	game_library = LoadLibrary ( name );
	if (game_library)
		Com_DPrintf ("LoadLibrary (%s)\n", name);
	else
	{
		{
			// now run through the search paths
			path = NULL;
			while (1)
			{
				path = FS_NextPath (path);
				if (!path)
					return NULL;		// couldn't find one anywhere
				Com_sprintf (name, sizeof(name), "%s/%s", path, gamename);
				game_library = LoadLibrary (name);
				if (game_library)
				{
					Com_DPrintf ("LoadLibrary (%s)\n",name);
					break;
				}
			}
		}
	}

	GetGameAPI = (void *(__cdecl *)(void *)) GetProcAddress (game_library, "GetGameAPI");
	if (!GetGameAPI)
	{
		Sys_UnloadGame ();		
		return NULL;
	}
	return GetGameAPI (parms);
}


void R_ClearSLights()
{
	int i;
	for(i=0; i<numStaticShadowLights; i++)
	{
		currentshadowlight = &shadowlights[i];
		if(currentshadowlight->isStatic)
		{
			if (gl_config.occlusion)
				glDeleteQueriesARB(1, &currentshadowlight->occID);
			if(currentshadowlight->volumeCmds)
			{
				Z_Free(currentshadowlight->volumeCmds);
				currentshadowlight->volumeCmds = NULL;
			}
			if(currentshadowlight->lightCmds)
			{
				Z_Free(currentshadowlight->lightCmds);
				currentshadowlight->lightCmds = NULL;
			}
			if(currentshadowlight->sh_vbo_id)
			{
				R_VCFree(currentshadowlight->sh_vbo_id);
				currentshadowlight->sh_vbo_id = NULL;
			}
		}
	}
	numShadowLights = 0;
	numStaticShadowLights = 0;
	numEmits = 0;	// Заодно и очистим счетчик emits
}


shadowlight_t* AllocShadowLight ()
{
	if (numShadowLights >= MAXSHADOWLIGHTS)
		return NULL;

	numShadowLights++;
	return &shadowlights[numShadowLights-1];
}


llink_t* AllocLinkedLight ()
{
	if (lightmodel_counter >= MAX_ENTITIES * 16)
		return NULL;

	lightmodel_counter++;
	return &lmdl_list[lightmodel_counter-1];
}


void R_SpawnSLight(shadowlight_t *sl)
{
	int				i;
	shadowlight_t	*l;

	l = AllocShadowLight();
	if (!l)
		return;

	VectorCopy(sl->color, l->color);
	VectorCopy(sl->origin, l->origin);
	VectorCopy(sl->radiuses, l->radiuses);
	l->radius = max(max(l->radiuses[0], l->radiuses[1]), l->radiuses[2]);
	l->flareLight = sl->flareLight;
	l->style = sl->style;
	l->cl_style = sl->cl_style;
	l->_cone = sl->_cone;
	l->filtercube_start = sl->filtercube_start;
	l->filtercube_end = sl->filtercube_end;
	l->framerate = sl->ownerkey;
	VectorCopy(sl->angles, l->angles);
	VectorCopy(sl->rspeed, l->rspeed);
#ifdef FORCE_DYNAMICCONE
	if(l->_cone && (l->rspeed[0] || l->rspeed[1]))
		l->isStatic = false;
	else
#endif
	l->isStatic = true;
	l->lightCmds = NULL;	// обязательно обнулять!
	l->volumeCmds = NULL;	// обязательно обнулять!
	VectorClear(l->frust[0].normal);	// обязательно обнулять!
										// (денормализованный frust[0].normal - признак того, что надо рассчитать фрустум)
	l->sh_vbo_id = NULL;

	if (l->_cone==0/* || (l->angles[0]==0 && l->angles[1]==0)*/)
		goto sphere;	// нет конуса или направления - рисуем полный ббокс
	if (l->rspeed[0]==0 && l->rspeed[1]==0)
	{		// не вращается
		float	sa,cb,sb;
		sa = sin(DEG2RAD(l->angles[0]));
		SinCos(DEG2RAD(l->angles[1]), &sb, &cb);
///		cb = cos(DEG2RAD(l->angles[1]));
///		sb = sin(DEG2RAD(l->angles[1]));
		if (l->_cone<=1)
		{
			if (sa>=0.7193)
			{		// смотрит примерно вниз
				goto bottom;
			}
			else if (sa<=-0.7193)
			{		// смотрит примерно вверх
				goto up;
			}
			else if (sa>=-0.0175 && sa<=0.0175)
			{
				if (cb>=0.7193)
				{		// смотрит примерно вправо
					goto right;
				}
				else if (cb<=-0.7193)
				{		// смотрит примерно влево
					goto left;
				}
				else if (sb>=0.7193)
				{		// смотрит примерно прямо
					goto forward;
				}
				else if (sb<=-0.7193)
				{		// смотрит примерно назад
					goto backward;
				}
			}
		}
		else
		{
			if (sa>=0.9999)
			{		// смотрит строго вниз
bottom:			l->mins_cone[0] = l->origin[0] - l->radiuses[0];
				l->maxs_cone[0] = l->origin[0] + l->radiuses[0];
				l->mins_cone[1] = l->origin[1] - l->radiuses[1];
				l->maxs_cone[1] = l->origin[1] + l->radiuses[1];
				l->mins_cone[2] = l->origin[2] - l->radiuses[2];
				l->maxs_cone[2] = l->origin[2];
				goto draw;
			}
			else if (sa<=-0.9999)
			{		// смотрит строго вверх
up:
				l->mins_cone[0] = l->origin[0] - l->radiuses[0];
				l->maxs_cone[0] = l->origin[0] + l->radiuses[0];
				l->mins_cone[1] = l->origin[1] - l->radiuses[1];
				l->maxs_cone[1] = l->origin[1] + l->radiuses[1];
				l->mins_cone[2] = l->origin[2];
				l->maxs_cone[2] = l->origin[2] + l->radiuses[2];
				goto draw;
			}
			else if (sa>=-0.0175 && sa<=0.0175)
			{
				if (cb>=0.9999)
				{		// смотрит строго вправо
right:				l->mins_cone[0] = l->origin[0];
					l->maxs_cone[0] = l->origin[0] + l->radiuses[0];
					l->mins_cone[1] = l->origin[1] - l->radiuses[1];
					l->maxs_cone[1] = l->origin[1] + l->radiuses[1];
					l->mins_cone[2] = l->origin[2] - l->radiuses[2];
					l->maxs_cone[2] = l->origin[2] + l->radiuses[2];
					goto draw;
				}
				else if (cb<=-0.9999)
				{		// смотрит строго влево
left:				l->mins_cone[0] = l->origin[0] - l->radiuses[0];
					l->maxs_cone[0] = l->origin[0];
					l->mins_cone[1] = l->origin[1] - l->radiuses[1];
					l->maxs_cone[1] = l->origin[1] + l->radiuses[1];
					l->mins_cone[2] = l->origin[2] - l->radiuses[2];
					l->maxs_cone[2] = l->origin[2] + l->radiuses[2];
					goto draw;
				}
				else if (sb>=0.9999)
				{		// смотрит строго прямо
forward:			l->mins_cone[0] = l->origin[0] - l->radiuses[0];
					l->maxs_cone[0] = l->origin[0] + l->radiuses[0];
					l->mins_cone[1] = l->origin[1];
					l->maxs_cone[1] = l->origin[1] + l->radiuses[1];
					l->mins_cone[2] = l->origin[2] - l->radiuses[2];
					l->maxs_cone[2] = l->origin[2] + l->radiuses[2];
					goto draw;
				}
				else if (sb<=-0.9999)
				{		// смотрит строго назад
backward:			l->mins_cone[0] = l->origin[0] - l->radiuses[0];
					l->maxs_cone[0] = l->origin[0] + l->radiuses[0];
					l->mins_cone[1] = l->origin[1] - l->radiuses[1];
					l->maxs_cone[1] = l->origin[1];
					l->mins_cone[2] = l->origin[2] - l->radiuses[2];
					l->maxs_cone[2] = l->origin[2] + l->radiuses[2];
					goto draw;
				}
			}
		}
	}

sphere:
	for (i=0 ; i<3 ; i++)
	{
		l->mins_cone[i] = l->origin[i] - l->radiuses[i];
		l->maxs_cone[i] = l->origin[i] + l->radiuses[i];
	}

draw:
	for (i=0 ; i<3 ; i++)
	{
		l->mins[i] = l->origin[i] - l->radiuses[i];
		l->maxs[i] = l->origin[i] + l->radiuses[i];
	}

	l->sphere = (((int)l->radiuses[0] == (int)l->radiuses[1]) && ((int)l->radiuses[0] == (int)l->radiuses[2]));
	Memcpy(l->targetname, sl->targetname, MAX_QPATH);
	l->start_off = sl->start_off;
	l->noshadow = sl->noshadow;
	l->noshadow2 = sl->noshadow2;
	l->nobump = sl->nobump;
	l->occID = 0;
///	l->use = true;

	numStaticShadowLights++;
}


void R_SpawnLLight(shadowlight_t *sl)
{
	llink_t			*l;

	l = AllocLinkedLight();
	if (!l)
		return;

	VectorCopy(sl->color, l->color);
	VectorCopy(sl->origin, l->origin);
	l->radius = max(max(sl->radiuses[0], sl->radiuses[1]), sl->radiuses[2]);
	l->style = sl->style;
	l->cl_style = sl->cl_style;
	l->_cone = sl->_cone;
	l->filtercube_start = sl->filtercube_start;
	l->filtercube_end = sl->filtercube_end;
	l->framerate = sl->ownerkey;
	VectorCopy(sl->angles, l->angles);
	Memcpy(l->label, sl->label, MAX_QPATH);
///	l->use = true;
///	VectorCopy(sl->rspeed, l->rspeed);
///	Memcpy(l->targetname, sl->targetname, MAX_QPATH);
///	l->start_off = sl->start_off;
}


bool SpawnStaticLight (shadowlight_t *sl)
{
	if (numStaticShadowLights >= MAXSHADOWLIGHTS)
	{
		Com_DPrintf ("SpawnStaticLight: overflow\n");
		return false;
	}

	if (sl->radius == 1.0)
		return false;

	//Create a light and make it static
	R_SpawnSLight(sl);
///	numStaticShadowLights++;
	return true;
}


void SpawnLinkedLight (shadowlight_t *sl)
{
	if (lightmodel_counter >= MAX_ENTITIES * 16)
	{
		Com_DPrintf ("SpawnLinkedLight: overflow\n");
		return;
	}

	if (sl->radius == 1.0)
		return;

	//Create a light and make it dynamic linked light
	R_SpawnLLight(sl);
}


/*
===============
SV_InitGameProgs

Init the game subsystem for a new map
===============
*/
void SV_InitGameProgs ()
{
	game_import_t	import;

	// unload anything we have now
	if (ge)
		SV_ShutdownGameProgs ();

	// load a new game dll
	import.multicast = SV_Multicast;
	import.unicast = PF_Unicast;
	import.bprintf = SV_BroadcastPrintf;
	import.dprintf = PF_dprintf;
	import.cprintf = PF_cprintf;
	import.centerprintf = PF_centerprintf;
	import.error = PF_error;

	import.linkentity = SV_LinkEdict;
	import.unlinkentity = SV_UnlinkEdict;
	import.BoxEdicts = SV_AreaEdicts;
	import.trace = SV_Trace;
	import.pointcontents = SV_PointContents;
	import.setmodel = PF_setmodel;
	import.inPVS = PF_inPVS;
	import.inPHS = PF_inPHS;
	import.Pmove = Pmove;

	import.modelindex = SV_ModelIndex;
	import.soundindex = SV_SoundIndex;
	import.imageindex = SV_ImageIndex;

	import.configstring = PF_Configstring;
	import.lightstyle = PF_Lightstyle;		/// Berserker
	import.sound = PF_StartSound;
	import.positioned_sound = SV_StartSound;

	import.WriteChar = PF_WriteChar;
	import.WriteByte = PF_WriteByte;
	import.WriteShort = PF_WriteShort;
	import.WriteLong = PF_WriteLong;
	import.WriteFloat = PF_WriteFloat;
	import.WriteString = PF_WriteString;
	import.WritePosition = PF_WritePos;
	import.WriteDir = PF_WriteDir;
	import.WriteAngle = PF_WriteAngle;

	import.TagMalloc = G_TagMalloc;
	import.TagFree = Z_Free;
	import.FreeTags = Z_FreeTags;

	import.cvar = Cvar_Get;
	import.cvar_set = Cvar_Set;
	import.cvar_forceset = Cvar_ForceSet;

	import.argc = Cmd_Argc;
	import.argv = Cmd_Argv;
	import.args = Cmd_Args;
	import.AddCommandString = Cbuf_AddText;

///	import.DebugGraph = SCR_DebugGraph;
	import.SetAreaPortalState = CM_SetAreaPortalState;
	import.AreasConnected = CM_AreasConnected;

	ge = (game_export_t *)Sys_GetGameAPI (&import);

	if (!ge)
		Com_Error (ERR_DROP, "failed to load game DLL");
	if (ge->apiversion != GAME_API_VERSION)
		Com_Error (ERR_DROP, "game is version %i, not %i", ge->apiversion, GAME_API_VERSION);

	ge->Init ();
}


/*
==============
SV_InitGame

A brand new game has been started
==============
*/
void SV_InitGame ()
{
	int		i;
	edict_t	*ent;

	if (svs.initialized)
	{
		// cause any connected clients to reconnect
		SV_Shutdown ("Server restarted\n", true);
	}
	else
	{
		// make sure the client is down
		CL_Drop ();
		SCR_BeginLoadingPlaque ();
	}

	// get any latched variable changes (maxclients, etc)
	Cvar_GetLatchedVars ();

	svs.initialized = true;

	if (Cvar_VariableValue ("coop") && Cvar_VariableValue ("deathmatch"))
	{
		Com_Printf("^3Deathmatch and Coop both set, disabling Coop\n");
		Cvar_FullSet ("coop", "0",  CVAR_SERVERINFO | CVAR_LATCH);
	}

	// dedicated servers are can't be single player and are usually DM
	// so unless they explicity set coop, force it to deathmatch
	if (dedicated->value)
	{
		if (!Cvar_VariableValue ("coop"))
			Cvar_FullSet ("deathmatch", "1",  CVAR_SERVERINFO | CVAR_LATCH);
	}

	// init clients
	if (Cvar_VariableValue ("deathmatch"))
	{
		if (maxclients->value <= 1)
			Cvar_FullSet ("maxclients", "16", CVAR_SERVERINFO | CVAR_LATCH);
		else if (maxclients->value > MAX_CLIENTS)
			Cvar_FullSet ("maxclients", va("%i", MAX_CLIENTS), CVAR_SERVERINFO | CVAR_LATCH);
	}
	else if (Cvar_VariableValue ("coop"))
	{
		if (maxclients->value <= 1 || maxclients->value > 128)			// FIXED: max coop's maxclients is 128
			Cvar_FullSet ("maxclients", "16", CVAR_SERVERINFO | CVAR_LATCH);
	}
	else	// non-deathmatch, non-coop is one player
	{
		Cvar_FullSet ("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
	}

	svs.spawncount = rand();
	svs.clients = (client_s *) Z_Malloc (sizeof(client_t)*maxclients->value, true);
	svs.num_client_entities = maxclients->value*UPDATE_BACKUP*64;
	svs.client_entities = (entity_state_s *) Z_Malloc (sizeof(entity_state_t)*svs.num_client_entities, true);

	// init network stuff
	NET_Config ( (maxclients->value > 1) );

	// init game
	SV_InitGameProgs ();
	for (i=0 ; i<maxclients->value ; i++)
	{
		ent = EDICT_NUM(i+1);
		ent->s.number = i+1;
		svs.clients[i].edict = ent;
		Memset (&svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd));
	}
}


/*
=================
SV_BroadcastCommand

Sends text to all active clients
=================
*/
void SV_BroadcastCommand (char *fmt, ...)
{
	va_list		argptr;
	char		string[1024];
	
	if (!sv.state)
		return;
	va_start (argptr,fmt);
	vsprintf (string, fmt,argptr);
	va_end (argptr);

	MSG_WriteByte (&sv.multicast, svc_stufftext);
	MSG_WriteString (&sv.multicast, string);
	SV_Multicast (NULL, MULTICAST_ALL_R);
}


void SV_NextDownload_f ()
{
	int		r;
	int		percent;
	int		size;

	if (!sv_client->download)
		return;

	r = sv_client->downloadsize - sv_client->downloadcount;
	if (r > 1024)
		r = 1024;

	MSG_WriteByte (&sv_client->netchan.message, svc_download);
	MSG_WriteShort (&sv_client->netchan.message, r);

	sv_client->downloadcount += r;
	size = sv_client->downloadsize;
	if (!size)
		size = 1;
	percent = sv_client->downloadcount*100/size;
	MSG_WriteByte (&sv_client->netchan.message, percent);
	SZ_Write (&sv_client->netchan.message,
		sv_client->download + sv_client->downloadcount - r, r);

	if (sv_client->downloadcount != sv_client->downloadsize)
		return;

	Z_Free (sv_client->download);
	sv_client->download = NULL;
}


void SV_BeginDownload_f()
{
	char	*name, *p;
	int		length, offset = 0;

	name = Cmd_Argv(1);

	if (Cmd_Argc() > 2)
	{
		offset = atoi(Cmd_Argv(2)); // downloaded offset
		if (offset < 0)
		{
			Com_Printf ("^6EXPLOIT:^7 Refusing illegal download offset %d for %s to %s\n", offset, name, sv_client->name);
			goto deny;
		}
	}

	// Berserker: changes all '\' to '/'
	// заодно калечим строки типа '\\servername\share\file'
	p = name;
	while (*p)
	{
		if (*p == '\\')
			*p = '/';
		p++;
	}

	// поправив '\' -> '/', можно дальше проверять...
	//r1ch: fix some ./ references in maps, eg ./textures/map/file
	length = strlen(name);
	p = name;
	while (p = strstr (p, "./"))
	{
		memmove (p, p+2, length - (p - name) - 1);
		length -= 2;
	}

	// hacked by zoid to allow more conrol over download
	// first off, no .. or global allow check
	if (strstr (name, "..") || !allow_download->value
		|| !length || name[0] == 0		//r1ch: empty name, maybe as result of ./ normalize
		|| name[length-1] == '/'
		|| name[length-1] == '.'
///		// leading dot is no good
///		|| *name == '.' 
		// leading slash bad as well, must be in subdir
		|| *name == '/'
		// next up, skin check
		|| ((strncmp(name, "players/", 8/*6*/) == 0 && !allow_download_players->value))	/// Fixed by Berserker
		// now models
		|| ((strncmp(name, "models/", 7) == 0 && !allow_download_models->value))	/// Fixed by Berserker
		// now sounds
		|| ((strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value))	/// Fixed by Berserker
		// now maps (note special case for maps, must not be in pak)
		|| ((strncmp(name, "maps/", 5/*6*/) == 0 && !allow_download_maps->value))	/// Fixed by Berserker
		// MUST be in a subdirectory	
		|| !strstr (name, "/") )
	{	// don't allow anything with .. path
deny:	MSG_WriteByte (&sv_client->netchan.message, svc_download);
		MSG_WriteShort (&sv_client->netchan.message, -1);
		MSG_WriteByte (&sv_client->netchan.message, 0);
		return;
	}

	if (sv_client->download)
		Z_Free (sv_client->download);

	sv_client->downloadsize = FS_LoadFile (name, (void **)&sv_client->download);
	if (!sv_client->download)
	{
		Com_DPrintf ("Couldn't download %s to %s\n", name, sv_client->name);
		goto deny;
	}

	if (offset > sv_client->downloadsize)
		sv_client->downloadcount = sv_client->downloadsize;
	else
		sv_client->downloadcount = offset;

	SV_NextDownload_f ();
	Com_DPrintf ("Downloading %s to %s\n", name, sv_client->name);
}


void SV_Nextserver ()
{
	char	*v;

	//ZOID, ss_pic can be nextserver'd in coop mode
///	if (sv.state == ss_game || (sv.state == ss_pic && !Cvar_VariableValue("coop")))
///		return;		// can't nextserver while playing a normal game

	svs.spawncount++;	// make sure another doesn't sneak in
	v = Cvar_VariableString ("nextserver");
	if (!v[0])
		Cbuf_AddText ("killserver\n");
	else
	{
		Cbuf_AddText (v);
		Cbuf_AddText ("\n");
	}
	Cvar_Set ("nextserver","");
}


void SV_DemoCompleted ()
{
	if (sv.demofile)
	{
		fclose (sv.demofile);
		sv.demofile = NULL;
	}
	SV_Nextserver ();
}


void SV_SendClientMessages ()
{
	int			i;
	client_t	*c;
	int			msglen;
	byte		msgbuf[MAX_MSGLEN];
	int			r;

	msglen = 0;

	// read the next demo message if needed
	if (sv.state == ss_demo && sv.demofile)
	{
		if (sv_paused->value)
			msglen = 0;
		else
		{
			// get the next message
			r = fread (&msglen, 4, 1, sv.demofile);
			if (r != 1)
			{
				SV_DemoCompleted ();
				return;
			}
			msglen = LittleLong (msglen);
			if (msglen == -1)
			{
				SV_DemoCompleted ();
				return;
			}
			if (msglen > MAX_MSGLEN)
				Com_Error (ERR_DROP, "SV_SendClientMessages: msglen > MAX_MSGLEN");
			r = fread (msgbuf, msglen, 1, sv.demofile);
			if (r != 1)
			{
				SV_DemoCompleted ();
				return;
			}
		}
	}

	// send a message to each connected client
	for (i=0, c = svs.clients ; i<maxclients->value; i++, c++)
	{
		if (!c->state)
			continue;
		// if the reliable message overflowed,
		// drop the client
		if (c->netchan.message.overflowed)
		{
			SZ_Clear (&c->netchan.message);
			SZ_Clear (&c->datagram);
			SV_BroadcastPrintf (PRINT_HIGH, "%s overflowed\n", c->name);
			SV_DropClient (c);
		}

		if (sv.state == ss_cinematic || sv.state == ss_demo || sv.state == ss_pic)
			Netchan_Transmit (&c->netchan, msglen, msgbuf);
		else if (c->state == cs_spawned)
		{
			// don't overrun bandwidth
			if (SV_RateDrop (c))
				continue;

			SV_SendClientDatagram (c);
		}
		else
		{
	// just update reliable	if needed
			if (c->netchan.message.cursize	|| curtime - c->netchan.last_sent > 1000 )
				Netchan_Transmit (&c->netchan, 0, NULL);
		}
	}
}


void Cbuf_CopyToDefer ()
{
	Memcpy(defer_text_buf, cmd_text_buf, cmd_text.cursize);
	defer_text_buf[cmd_text.cursize] = 0;
	cmd_text.cursize = 0;
}


/* MD4 initialization. Begins an MD4 operation, writing a new context. */
void MD4Init (MD4_CTX *context)
{
	context->count[0] = context->count[1] = 0;

	/* Load magic initialization constants.*/
	context->state[0] = 0x67452301;
	context->state[1] = 0xefcdab89;
	context->state[2] = 0x98badcfe;
	context->state[3] = 0x10325476;
}


/* Decodes input (byte) into output (UINT4). Assumes len is a multiple of 4. */
static void Decode (UINT4 *output, byte *input, unsigned len)
{
	unsigned i, j;

	for (i = 0, j = 0; j < len; i++, j += 4)
 		output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
}


/* MD4 basic transformation. Transforms state based on block. */
static void MD4Transform (UINT4 state[4], byte block[64])
{
	UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];

	Decode (x, block, 64);

/* Round 1 */
FF (a, b, c, d, x[ 0], S11); 				/* 1 */
FF (d, a, b, c, x[ 1], S12); 				/* 2 */
FF (c, d, a, b, x[ 2], S13); 				/* 3 */
FF (b, c, d, a, x[ 3], S14); 				/* 4 */
FF (a, b, c, d, x[ 4], S11); 				/* 5 */
FF (d, a, b, c, x[ 5], S12); 				/* 6 */
FF (c, d, a, b, x[ 6], S13); 				/* 7 */
FF (b, c, d, a, x[ 7], S14); 				/* 8 */
FF (a, b, c, d, x[ 8], S11); 				/* 9 */
FF (d, a, b, c, x[ 9], S12); 				/* 10 */
FF (c, d, a, b, x[10], S13); 			/* 11 */
FF (b, c, d, a, x[11], S14); 			/* 12 */
FF (a, b, c, d, x[12], S11); 			/* 13 */
FF (d, a, b, c, x[13], S12); 			/* 14 */
FF (c, d, a, b, x[14], S13); 			/* 15 */
FF (b, c, d, a, x[15], S14); 			/* 16 */

/* Round 2 */
GG (a, b, c, d, x[ 0], S21); 			/* 17 */
GG (d, a, b, c, x[ 4], S22); 			/* 18 */
GG (c, d, a, b, x[ 8], S23); 			/* 19 */
GG (b, c, d, a, x[12], S24); 			/* 20 */
GG (a, b, c, d, x[ 1], S21); 			/* 21 */
GG (d, a, b, c, x[ 5], S22); 			/* 22 */
GG (c, d, a, b, x[ 9], S23); 			/* 23 */
GG (b, c, d, a, x[13], S24); 			/* 24 */
GG (a, b, c, d, x[ 2], S21); 			/* 25 */
GG (d, a, b, c, x[ 6], S22); 			/* 26 */
GG (c, d, a, b, x[10], S23); 			/* 27 */
GG (b, c, d, a, x[14], S24); 			/* 28 */
GG (a, b, c, d, x[ 3], S21); 			/* 29 */
GG (d, a, b, c, x[ 7], S22); 			/* 30 */
GG (c, d, a, b, x[11], S23); 			/* 31 */
GG (b, c, d, a, x[15], S24); 			/* 32 */

/* Round 3 */
HH (a, b, c, d, x[ 0], S31);				/* 33 */
HH (d, a, b, c, x[ 8], S32); 			/* 34 */
HH (c, d, a, b, x[ 4], S33); 			/* 35 */
HH (b, c, d, a, x[12], S34); 			/* 36 */
HH (a, b, c, d, x[ 2], S31); 			/* 37 */
HH (d, a, b, c, x[10], S32); 			/* 38 */
HH (c, d, a, b, x[ 6], S33); 			/* 39 */
HH (b, c, d, a, x[14], S34); 			/* 40 */
HH (a, b, c, d, x[ 1], S31); 			/* 41 */
HH (d, a, b, c, x[ 9], S32); 			/* 42 */
HH (c, d, a, b, x[ 5], S33); 			/* 43 */
HH (b, c, d, a, x[13], S34); 			/* 44 */
HH (a, b, c, d, x[ 3], S31); 			/* 45 */
HH (d, a, b, c, x[11], S32); 			/* 46 */
HH (c, d, a, b, x[ 7], S33); 			/* 47 */
HH (b, c, d, a, x[15], S34);			/* 48 */

state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;

	/* Zeroize sensitive information.*/
	Memset ((POINTER)x, 0, sizeof (x));
}


/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */
void MD4Update (MD4_CTX *context, byte *input, unsigned inputLen)
{
	unsigned i, index, partLen;

	/* Compute number of bytes mod 64 */
	index = (unsigned)((context->count[0] >> 3) & 0x3F);

	/* Update number of bits */
	if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3))
		context->count[1]++;

	context->count[1] += ((UINT4)inputLen >> 29);

	partLen = 64 - index;

	/* Transform as many times as possible.*/
	if (inputLen >= partLen)
	{
 		Memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen);
		MD4Transform (context->state, context->buffer);

 		for (i = partLen; i + 63 < inputLen; i += 64)
 			MD4Transform (context->state, &input[i]);

 		index = 0;
	}
	else
 		i = 0;

	/* Buffer remaining input */
	Memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i);
}


/* Encodes input (UINT4) into output (byte). Assumes len is a multiple of 4. */
static void Encode (byte *output, UINT4 *input, unsigned len)
{
	unsigned i, j;

	for (i = 0, j = 0; j < len; i++, j += 4) {
 		output[j] = (byte)(input[i] & 0xff);
 		output[j+1] = (byte)((input[i] >> 8) & 0xff);
 		output[j+2] = (byte)((input[i] >> 16) & 0xff);
 		output[j+3] = (byte)((input[i] >> 24) & 0xff);
	}
}


/* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */
void MD4Final (byte digest[16], MD4_CTX *context)
{
	byte		bits[8];
	unsigned	index, padLen;

	/* Save number of bits */
	Encode (bits, context->count, 8);

	/* Pad out to 56 mod 64.*/
	index = (unsigned)((context->count[0] >> 3) & 0x3f);
	padLen = (index < 56) ? (56 - index) : (120 - index);
	MD4Update (context, PADDING, padLen);

	/* Append length (before padding) */
	MD4Update (context, bits, 8);
	
	/* Store state in digest */
	Encode (digest, context->state, 16);

	/* Zeroize sensitive information.*/
	Memset ((POINTER)context, 0, sizeof (*context));
}


unsigned Com_BlockChecksum (void *buffer, int length)
{
	int			digest[4];
	unsigned	val;
	MD4_CTX		ctx;

	MD4Init (&ctx);
	MD4Update (&ctx, (byte*)buffer, length);
	MD4Final ((byte*)digest, &ctx);
	
	val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3];

	return val;
}


void CMod_LoadSurfaces (lump_t *l)
{
	texinfo_t	*in;
	mapsurface_t	*out;
	int			i, count;

	in = (texinfo_s *)(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "CMod_LoadSurfaces: funny lump size");
	count = l->filelen / sizeof(*in);
	if (count < 1)
		Com_Error (ERR_DROP, "CMod_LoadSurfaces: map with no surfaces");
	if (count > MAX_MAP_TEXINFO)
		Com_Error (ERR_DROP, "CMod_LoadSurfaces: map has too many surfaces");

	numtexinfo = count;
	out = map_surfaces;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		strncpy (out->c.name, in->texture, sizeof(out->c.name)-1);
		strncpy (out->rname, in->texture, sizeof(out->rname)-1);
		out->c.flags = LittleLong (in->flags);
		out->c.value = LittleLong (in->value);
	}
}


void CMod_LoadLeafs (lump_t *l)
{
	int			i;
	cleaf_t		*out;
	dleaf_t 	*in;
	int			count;
	
	in = (dleaf_t *)(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "CMod_LoadLeafs: funny lump size");
	count = l->filelen / sizeof(*in);

	if (count < 1)
		Com_Error (ERR_DROP, "CMod_LoadLeafs: map with no leafs");
	// need to save space for box planes
	if (count > MAX_MAP_PLANES)
		Com_Error (ERR_DROP, "CMod_LoadLeafs: map has too many planes");

	out = map_leafs;	
	numleafs = count;
	numclusters = 0;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		out->contents = LittleLong (in->contents);
		out->cluster = LittleShort (in->cluster);
		out->area = LittleShort (in->area);
		out->firstleafbrush = LittleShort (in->firstleafbrush);
		out->numleafbrushes = LittleShort (in->numleafbrushes);

		if (out->cluster >= numclusters)
			numclusters = out->cluster + 1;
	}

	if (map_leafs[0].contents != CONTENTS_SOLID)
		Com_Error (ERR_DROP, "CMod_LoadLeafs: map leaf 0 is not CONTENTS_SOLID");
	solidleaf = 0;
	emptyleaf = -1;
	for (i=1 ; i<numleafs ; i++)
	{
		if (!map_leafs[i].contents)
		{
			emptyleaf = i;
			break;
		}
	}
	if (emptyleaf == -1)
		Com_Error (ERR_DROP, "CMod_LoadLeafs: map does not have an empty leaf");
}


void CMod_LoadLeafBrushes (lump_t *l)
{
	int		i;
	WORD	*out;
	WORD 	*in;
	int		count;
	
	in = (WORD*)(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "CMod_LoadLeafBrushes: funny lump size");
	count = l->filelen / sizeof(*in);

	if (count < 1)
		Com_Error (ERR_DROP, "CMod_LoadLeafBrushes: map with no planes");
	// need to save space for box planes
	if (count > MAX_MAP_LEAFBRUSHES)
		Com_Error (ERR_DROP, "CMod_LoadLeafBrushes: map has too many leafbrushes");

	out = map_leafbrushes;
	numleafbrushes = count;

	for ( i=0 ; i<count ; i++, in++, out++)
		*out = LittleShort (*in);
}


void CMod_LoadPlanes (lump_t *l)
{
	int			i, j;
	cplane_t	*out;
	dplane_t 	*in;
	int			count;
	int			bits;
	
	in = (dplane_t *)(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "CMod_LoadPlanes: funny lump size");
	count = l->filelen / sizeof(*in);

	if (count < 1)
		Com_Error (ERR_DROP, "CMod_LoadPlanes: map with no planes");
	// need to save space for box planes
	if (count > MAX_MAP_PLANES)
		Com_Error (ERR_DROP, "CMod_LoadPlanes: map has too many planes");

	out = map_planes;	
	numplanes = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		bits = 0;
		for (j=0 ; j<3 ; j++)
		{
			out->normal[j] = LittleFloat (in->normal[j]);
			if (out->normal[j] < 0)
				bits |= 1<<j;
		}

		out->dist = LittleFloat (in->dist);
		out->type = LittleLong (in->type);
		out->signbits = bits;
	}
}


void CMod_LoadBrushSides (lump_t *l)
{
	int			i, j;
	cbrushside_t	*out;
	dbrushside_t 	*in;
	int			count;
	int			num;

	in = (dbrushside_t *)(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "CMod_LoadBrushSides: funny lump size");
	count = l->filelen / sizeof(*in);

	// need to save space for box planes
	if (count > MAX_MAP_BRUSHSIDES)
		Com_Error (ERR_DROP, "CMod_LoadBrushSides: map has too many planes");

	out = map_brushsides;	
	numbrushsides = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		num = LittleShort (in->planenum);
		out->plane = &map_planes[num];
		j = LittleShort (in->texinfo);
		if (j >= numtexinfo)
			Com_Error (ERR_DROP, "CMod_LoadBrushSides: bad brushside texinfo");
		out->surface = &map_surfaces[j];
	}
}


void CMod_LoadBrushes (lump_t *l)
{
	dbrush_t	*in;
	cbrush_t	*out;
	int			i, count;
	
	in = (dbrush_t *)(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "CMod_LoadBrushes: funny lump size");
	count = l->filelen / sizeof(*in);

	if (count > MAX_MAP_BRUSHES)
		Com_Error (ERR_DROP, "CMod_LoadBrushes: map has too many brushes");

	out = map_brushes;

	numbrushes = count;

	for (i=0 ; i<count ; i++, out++, in++)
	{
		out->firstbrushside = LittleLong(in->firstside);
		out->numsides = LittleLong(in->numsides);
		out->contents = LittleLong(in->contents);
	}

}


void CMod_LoadSubmodels (lump_t *l)
{
	dmodel_t	*in;
	cmodel_t	*out;
	int			i, j, count;

	in = (dmodel_t *)(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "CMod_LoadSubmodels: funny lump size");
	count = l->filelen / sizeof(*in);

	if (count < 1)
		Com_Error (ERR_DROP, "CMod_LoadSubmodels: map with no models");
	if (count > MAX_MAP_MODELS)
		Com_Error (ERR_DROP, "CMod_LoadSubmodels: map has too many models");

	numcmodels = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		out = &map_cmodels[i];

		for (j=0 ; j<3 ; j++)
		{	// spread the mins / maxs by a pixel
			out->mins[j] = LittleFloat (in->mins[j]) - 1;
			out->maxs[j] = LittleFloat (in->maxs[j]) + 1;
			out->origin[j] = LittleFloat (in->origin[j]);
		}
		out->headnode = LittleLong (in->headnode);
	}
}


void CMod_LoadNodes (lump_t *l)
{
	dnode_t		*in;
	int			child;
	cnode_t		*out;
	int			i, j, count;
	
	in = (dnode_t *)(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "CMod_LoadNodes: funny lump size");
	count = l->filelen / sizeof(*in);

	if (count < 1)
		Com_Error (ERR_DROP, "CMod_LoadNodes: map has no nodes");
	if (count > MAX_MAP_NODES)
		Com_Error (ERR_DROP, "CMod_LoadNodes: map has too many nodes");

	out = map_nodes;

	numnodes = count;

	for (i=0 ; i<count ; i++, out++, in++)
	{
		out->plane = map_planes + LittleLong(in->planenum);
		for (j=0 ; j<2 ; j++)
		{
			child = LittleLong (in->children[j]);
			out->children[j] = child;
		}
	}

}


void CMod_LoadAreas (lump_t *l)
{
	int			i;
	carea_t		*out;
	darea_t 	*in;
	int			count;

	in = (darea_t *)(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "CMod_LoadAreas: funny lump size");
	count = l->filelen / sizeof(*in);

	if (count > MAX_MAP_AREAS)
		Com_Error (ERR_DROP, "CMod_LoadAreas: map has too many areas");

	out = map_areas;
	numareas = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		out->numareaportals = LittleLong (in->numareaportals);
		out->firstareaportal = LittleLong (in->firstareaportal);
		out->floodvalid = 0;
		out->floodnum = 0;
	}
}


void CMod_LoadAreaPortals (lump_t *l)
{
	int			i;
	dareaportal_t	*out;
	dareaportal_t	*in;
	int			count;

	in = (dareaportal_t *)(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "CMod_LoadAreaPortals: funny lump size");
	count = l->filelen / sizeof(*in);

	if (count > MAX_MAP_AREAPORTALS)
		Com_Error (ERR_DROP, "CMod_LoadAreaPortals: map has too many areaportals");

	out = map_areaportals;
	numareaportals = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		out->portalnum = LittleLong (in->portalnum);
		out->otherarea = LittleLong (in->otherarea);
	}
}


void CMod_LoadVisibility (lump_t *l)
{
	int		i;

	numvisibility = l->filelen;
	if (l->filelen > MAX_MAP_VISIBILITY)
		Com_Error (ERR_DROP, "Map has too large visibility lump");

	Memcpy (map_visibility, cmod_base + l->fileofs, l->filelen);

	map_vis->numclusters = LittleLong (map_vis->numclusters);
	for (i=0 ; i<map_vis->numclusters ; i++)
	{
		map_vis->bitofs[i][0] = LittleLong (map_vis->bitofs[i][0]);
		map_vis->bitofs[i][1] = LittleLong (map_vis->bitofs[i][1]);
	}
}


cdlight_t *CL_AllocDlight (int key)
{
	int		i;
	cdlight_t	*dl;

// first look for an exact key match
	if (key)
	{
		dl = cl_dlights;
		for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
		{
			if (dl->key == key)
			{
				Memset (dl, 0, sizeof(*dl));
				dl->key = key;
				return dl;
			}
		}
	}

// then look for anything else
	dl = cl_dlights;
	for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
	{
		if (dl->die < cl.gameTime)
		{
			Memset (dl, 0, sizeof(*dl));
			dl->key = key;
			return dl;
		}
	}

	dl = &cl_dlights[0];
	Memset (dl, 0, sizeof(*dl));
	dl->key = key;
	return dl;
}


void CL_RunDLights ()
{
	int			i;
	cdlight_t	*dl;

	dl = cl_dlights;
	for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
	{
		if (!dl->radius)
			continue;
		
		if (dl->die < cl.gameTime)
		{
			dl->radius = 0;
			return;
		}
///		dl->radius -= cls.frametime*dl->decay;
		if (dl->radius < 0)
			dl->radius = 0;
	}
}


void CL_ParticleTracer(vec3_t start, vec3_t end)
{
	cparticle_t	*p;
	vec3_t		vec;
	float		rnd = frand() * 0.05;

	if (!free_particles)
		return;
	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;

	p->flags = 0;
	p->time = cl.gameTime;
	p->alpha = 1.0 - rnd * 5;
	p->alphavel = -1.0 / (0.1 + rnd);
	p->color_r0 = p->color_r1 = (0.6 + frand() * 0.35) * 255;
	p->color_g0 = p->color_g1 = (0.2 + frand() * 0.25) * 128;
	p->color_b0 = p->color_b1 = (0.0 + frand() * 0.15) * 16;

	VectorClear(p->accel);
	VectorClear(p->vel);
	VectorCopy(start, p->org);
	VectorCopy(start, p->lastOrg);

	VectorSubtract(end, start, vec);
	VectorCopy(vec, p->length);

	p->type = part_tracer;
	p->size = 1;
}

void CL_ParticleFire(vec3_t end, vec3_t start, float size)	/// специально перепутал конец с началом, чтобы перевернуть партикль )))
{
	cparticle_t	*p;
	vec3_t		vec;

	if (!free_particles)
		return;
	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;

	p->flags = 0;
	p->time = cl.gameTime;
	p->alpha = 0.2;
	p->alphavel = -1;
	p->color_r0 = p->color_r1 =
	p->color_g0 = p->color_g1 =
	p->color_b0 = p->color_b1 = 128;

	VectorClear(p->accel);
	VectorClear(p->vel);
	VectorCopy(start, p->org);
	VectorCopy(start, p->lastOrg);

	VectorSubtract(end, start, vec);
	VectorCopy(vec, p->length);

	p->type = part_tracer;
	p->size = size;
}

void CL_WeaponFire(vec3_t base, vec3_t vF, vec3_t vR, vec3_t vU, float dF, float dR, float dU, float forwSize, float rightSize, float upSize, float partSize)
{
	vec3_t		start, end, start2;

	if (!p_weapfire->value)
		return;

	forwSize += frand() * 0.2 * forwSize;
	rightSize += frand() * 0.2 * rightSize;
	upSize += frand() * 0.2 * upSize;

	VectorMA(base, dF, vF, start);
	if (dR > 0)
	{		/// for player
		if (hand->value == 0)		// right
			VectorMA(start, dR, vR, start);
		else if (hand->value == 1)	// left
			VectorMA(start, -dR, vR, start);
	}
	else if (dR < 0)	/// for monsters
		VectorMA(start, -dR, vR, start);
	if (dU != 0)
		VectorMA(start, dU, vU, start);
	VectorMA(start, forwSize, vF, end);
	VectorMA(start, -4, vF, start2);
	CL_ParticleFire(start2, end, partSize);


	VectorMA(start, -4, vR, start2);
	VectorMA(start, rightSize, vR, end);
	CL_ParticleFire(start2, end, partSize);

	VectorMA(start, 4, vR, start2);
	VectorMA(start, -rightSize, vR, end);
	CL_ParticleFire(start2, end, partSize);

	VectorMA(start, -4, vU, start2);
	VectorMA(start, upSize, vU, end);
	CL_ParticleFire(start2, end, partSize);

	VectorMA(start, 4, vU, start2);
	VectorMA(start, -upSize, vU, end);
	CL_ParticleFire(start2, end, partSize);
}

void CL_Tracers(vec3_t base, vec3_t vF, vec3_t vR, vec3_t vU, float dF, float dR, float dU, unsigned count, float rndSX, float rndSY, float rndEX, float rndEY)
{
	int			i;
	float		c0, c1, r, u;
	vec3_t		start, end, start2, end2, ndir;
	trace_t		trace;

	if (!p_tracer->value)
		return;

	VectorMA(base, dF, vF, start);
	if (dR > 0)
	{		/// for player
		if (hand->value == 0)		// right
			VectorMA(start, dR, vR, start);
		else if (hand->value == 1)	// left
			VectorMA(start, -dR, vR, start);
	}
	else if (dR < 0)	/// for monsters
		VectorMA(start, -dR, vR, start);
	if (dU != 0)
		VectorMA(start, dU, vU, start);
	VectorMA(start, 32768, vF, end);		/// 8192 for Q2 maps, 32768 for large maps support

	for (i = 0; i < count; i++)
	{
		c0 = crandom();
		c1 = crandom();

		r = c0 * rndSX;
		u = c1 * rndSY;
		VectorMA(start, r, vR, start2);
		VectorMA(start2, u, vU, start2);

		r = c0 * rndEX;
		u = c1 * rndEY;
		VectorMA(end, r, vR, end2);
		VectorMA(end2, u, vU, end2);

		trace = CL_PMTraceWorld(start2, vec3_origin, vec3_origin, end2, MASK_SOLID);
		if (trace.fraction > 0 && trace.fraction < 1 && !trace.startsolid)
		{
			float dirLen;
			VectorSubtract(trace.endpos, start2, ndir);
			dirLen = VectorNormalize(ndir) - 32;
			if (dirLen > 0)
			{
				VectorMA(start2, dirLen, ndir, end2);
				CL_ParticleTracer(start2, end2);
			}
		}
	}
}

void CL_ParseMuzzleFlash ()
{
	vec3_t		dir, org_brass, org_smoke;
	cdlight_t	*dl;
	int			i, j, weapon;
	centity_t	*pl;
	int			silenced;
	float		volume;
	int			p_scale;
	cdlight_t	zerolight;		/// заглушка
	vec3_t		fv, rv, up;

	i = MSG_ReadShort (&net_message);
	if (i < 1 || i >= MAX_EDICTS)
		Com_Error (ERR_DROP, "CL_ParseMuzzleFlash: bad entity");

	weapon = MSG_ReadByte (&net_message);
	silenced = weapon & MZ_SILENCED;
	weapon &= ~MZ_SILENCED;

	pl = &cl_entities[i];

	if (dl_muzzle->value)
		dl = CL_AllocDlight (i);
	else
		dl = &zerolight;

	if (cl.playernum == i-1 && !cl_thirdPerson->value)	// это локальный игрок?  и не включен вид от третьего лица?
	{
		VectorMA (pl->current.origin, 14, vpn, dl->origin);
		VectorMA (pl->current.origin, 17, vpn, org_smoke);
		VectorMA (org_smoke, 16, vup, org_smoke);
		VectorMA (pl->current.origin, 8, vpn, org_brass);
		if (hand->value == 1)		// left
		{
			VectorMA (org_smoke, -8, vright, org_smoke);
			VectorMA (org_brass, -8, vright, org_brass);
		}
		else if (hand->value == 2)	// center
		{
			VectorMA (org_smoke, 7, vright, org_smoke);		// всё же немного сдвинем дым вправо, чтоб не мешал обзору
		}
		else
		{							// right
			VectorMA (org_smoke, 8, vright, org_smoke);
			VectorMA (org_brass, 8, vright, org_brass);
		}
		for (j=0; j<3; j++)
			dir[j] = vpn[j] + (vright[j] + vup[j])*2.5;		// гильзы летят всегда в одну сторону, не бывает оружия левша-edition ))
		VectorMA (org_smoke, -4, vup, org_smoke);
		VectorMA (org_brass, -8, vup, org_brass);
	}
	else
	{
		AngleVectors (pl->current.angles, fv, rv, up);

		VectorMA (pl->current.origin, 32, fv, dl->origin);
		VectorMA (dl->origin, 9, rv, dl->origin);
		VectorMA (dl->origin, 10, up, dl->origin);

		VectorMA (pl->current.origin, 13, fv, org_smoke);
		VectorMA (org_smoke, 8, rv, org_smoke);
		VectorMA (org_smoke, 12, up, org_smoke);

		VectorMA (pl->current.origin, 8, fv, org_brass);
		VectorMA (org_brass, 5, rv, org_brass);
		VectorMA (org_brass, 8, up, org_brass);

		for (j=0; j<3; j++)
			dir[j] = fv[j] + (rv[j] + up[j])*2.5;
	}

	if (silenced)
	{
		dl->radius = 100 + (rand()&15);
		volume = 0.2;
	}
	else
	{
		dl->radius = 200 + (rand()&31);
		volume = 1;
	}

	dl->die = cl.gameTime;

	switch (weapon)
	{
	case MZ_BLASTER:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_blastf1a, volume * 0.75,
			_ATTN_WEAPON_LIGHT);
		break;
	case MZ_HYPERBLASTER:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_hyprbf1a, volume * 0.8,
			_ATTN_WEAPON_LIGHT);
		break;
	case MZ_MACHINEGUN:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		if(p_machine->value)
			CL_ParticleEffect3 (org_smoke, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 2*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (org_brass, dir, 1, true);
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_machgf1b[(rand() % 5)], volume * 0.8, _ATTN_WEAPON_LIGHT);

		/// из вида со стороны не совпадает направление пушки и место попадания пуль. Хуета! Не будем рисовать трейсеры.
		if (cl.playernum == i - 1 && !cl_thirdPerson->value)	// это локальный игрок?  и не включен вид от третьего лица?
		{
			CL_Tracers(r_origin, vpn, vright, vup, 48, 6, -10, 1, 1, 1, 2000, 2000);
///			CL_WeaponFire(r_origin, vpn, vright, vup, 24, 6, -10);		для быстро бегущего игрока имеется рассинхронизация вспышки огня с движением, убого выглядит!
		}
///		else
///			CL_WeaponFire(dl->origin, fv, rv, up, -30, 0, 0);
		break;
	case MZ_SHOTGUN:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		if(p_shotgun->value)
			CL_ParticleEffect3 (org_smoke, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 8*p_shotgun->value, part_smoke);		// Дым
		CL_BrassShells (org_brass, dir, 1, false);
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_shotgf1b, volume * 0.88, _ATTN_WEAPON_LIGHT);
		S_fastsound_queue(NULL, i, CHAN_AUTO, cl_sfx_weapons_shotgr1b, volume * 0.5, _ATTN_WEAPON_LIGHT, 100);

		/// из вида со стороны не совпадает направление пушки и место попадания пуль. Хуета! Не будем рисовать трейсеры.
		if (cl.playernum == i - 1 && !cl_thirdPerson->value)	// это локальный игрок?  и не включен вид от третьего лица?
		{
			CL_Tracers(r_origin, vpn, vright, vup, 32, 8, -8, 6, 2, 2, 2000, 2000);
///			CL_WeaponFire(r_origin, vpn, vright, vup, 8, 8, -8);
		}
////	else	PVS-Studio
///			CL_WeaponFire(dl->origin, fv, rv, up, -28, 0, 0);
		break;
	case MZ_SSHOTGUN:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		if(p_shotgun->value)
			CL_ParticleEffect3 (org_smoke, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 8*p_shotgun->value, part_smoke);		// Дым
		CL_BrassShells (org_brass, dir, 2, false);
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_sshotf1b, volume * 0.9, _ATTN_WEAPON_LIGHT);

		/// из вида со стороны не совпадает направление пушки и место попадания пуль. Хуета! Не будем рисовать трейсеры.
		if (cl.playernum == i - 1 && !cl_thirdPerson->value)	// это локальный игрок?  и не включен вид от третьего лица?
			CL_Tracers(r_origin, vpn, vright, vup, 32, 8, -8, 6, 3, 2, 3000, 2000);
		break;
	case MZ_CHAINGUN1:
		dl->radius = 200 + (rand()&31);
		dl->color[0] = 1;dl->color[1] = 0.25;dl->color[2] = 0;
		if(p_machine->value)
			CL_ParticleEffect3 (org_smoke, vec3_origin, p_shot_r->value*64,p_shot_g->value*64,p_shot_b->value*64, 2*p_machine->value, part_smoke);			// Дым
		CL_BrassShells (org_brass, dir, 1, true);
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_machgf1b[(rand() % 5)], volume * 0.8, _ATTN_WEAPON_LIGHT);

		/// из вида со стороны не совпадает направление пушки и место попадания пуль. Хуета! Не будем рисовать трейсеры.
		if (cl.playernum == i - 1 && !cl_thirdPerson->value)	// это локальный игрок?  и не включен вид от третьего лица?
			CL_Tracers(r_origin, vpn, vright, vup, 32, 6, -8, 1, 2, 2, 2000, 2000);
		break;
	case MZ_CHAINGUN2:
		dl->radius = 225 + (rand()&31);
		dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0;
		dl->die = cl.gameTime  + 0.1;	// long delay
		if(p_machine->value)
			CL_ParticleEffect3 (org_smoke, vec3_origin, p_shot_r->value*128,p_shot_g->value*128,p_shot_b->value*128, 4*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (org_brass, dir, 1, true);
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_machgf1b[(rand() % 5)], volume * 0.8, _ATTN_WEAPON_LIGHT);
		S_fastsound_queue(NULL, i, CHAN_WEAPON, cl_sfx_weapons_machgf1b[(rand() % 5)], volume * 0.8, _ATTN_WEAPON_LIGHT, 50);

		/// из вида со стороны не совпадает направление пушки и место попадания пуль. Хуета! Не будем рисовать трейсеры.
		if (cl.playernum == i - 1 && !cl_thirdPerson->value)	// это локальный игрок?  и не включен вид от третьего лица?
			CL_Tracers(r_origin, vpn, vright, vup, 32, 6, -8, 2, 2, 2, 2000, 2000);
		break;
	case MZ_CHAINGUN3:
		dl->radius = 250 + (rand()&31);
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		dl->die = cl.gameTime  + 0.1;	// long delay
		if(p_machine->value)
			CL_ParticleEffect3 (org_smoke, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 4*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (org_brass, dir, 1, true);
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_machgf1b[(rand() % 5)], volume * 0.8, _ATTN_WEAPON_LIGHT);
		S_fastsound_queue(NULL, i, CHAN_WEAPON, cl_sfx_weapons_machgf1b[(rand() % 5)], volume * 0.8, _ATTN_WEAPON_LIGHT, 33);
		S_fastsound_queue(NULL, i, CHAN_WEAPON, cl_sfx_weapons_machgf1b[(rand() % 5)], volume * 0.8, _ATTN_WEAPON_LIGHT, 66);

		/// из вида со стороны не совпадает направление пушки и место попадания пуль. Хуета! Не будем рисовать трейсеры.
		if (cl.playernum == i - 1 && !cl_thirdPerson->value)	// это локальный игрок?  и не включен вид от третьего лица?
			CL_Tracers(r_origin, vpn, vright, vup, 32, 6, -8, 3, 2, 2, 2000, 2000);
		break;
	case MZ_RAILGUN:
		dl->color[0] = 0.5;dl->color[1] = 0.5;dl->color[2] = 1.0;
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_railgf1a, volume * 0.8, _ATTN_WEAPON_LIGHT);
///		S_fastsound_queue(NULL, i, CHAN_WEAPON, cl_sfx_weapons_railgr1a, volume * 0.33, _ATTN_WEAPON_LIGHT, 400);		/// Berserker: reload
		break;
	case MZ_ROCKET:
		dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0.2;
		if(p_rocket->value)
		{
			p_scale = 4 * p_rocket->value;
			if(p_scale)
				CL_ParticleEffect3 (org_smoke, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, p_scale, part_bigsmoke);		// Дым
		}
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_rocklf1a, volume * 0.95, _ATTN_WEAPON_HEAVY);
		S_fastsound_queue(NULL, i, CHAN_AUTO, cl_sfx_weapons_rocklr1b, volume * 0.8, _ATTN_WEAPON_HEAVY, 150);
		break;
	case MZ_GRENADE:
		dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0;
		if(p_grenade->value)
		{
			p_scale = 4 * p_grenade->value;
			if(p_scale)
				CL_ParticleEffect3 (org_smoke, vec3_origin, p_smoke_r->value*256,p_smoke_g->value*256,p_smoke_b->value*256, p_scale, part_bigsmoke);		// Дым
		}
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_grenlf1a, volume * 0.95, _ATTN_WEAPON_LIGHT);
		S_fastsound_queue(NULL, i, CHAN_AUTO, cl_sfx_weapons_grenlr1b, volume * 0.8, _ATTN_WEAPON_LIGHT, 100);
		break;
	case MZ_BFG:
		dl->color[0] = 0;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_bfg__f1y, volume, _ATTN_WEAPON_HEAVY);
		break;

	case MZ_LOGIN:
		dl->color[0] = 0;dl->color[1] = 1; dl->color[2] = 0;
		dl->die = cl.gameTime + 1.0;
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_grenlf1a, 0.95, _ATTN_MEDIUM);
		CL_LogoutEffect (pl->current.origin, weapon);
		break;
	case MZ_LOGOUT:
		dl->color[0] = 1;dl->color[1] = 0; dl->color[2] = 0;
		dl->die = cl.gameTime + 1.0;
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_grenlf1a, 0.95, _ATTN_MEDIUM);
		CL_LogoutEffect (pl->current.origin, weapon);
		break;
	case MZ_RESPAWN:
		dl->color[0] = 1;dl->color[1] = 1; dl->color[2] = 0;
		dl->die = cl.gameTime + 1.0;
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_grenlf1a, 0.95, _ATTN_MEDIUM);
		CL_LogoutEffect (pl->current.origin, weapon);
		break;
	// RAFAEL
	case MZ_IONRIPPER:	
		dl->color[0] = 1;dl->color[1] = 0.5; dl->color[2] = 0.5;
		S_fastsound(NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/rippfire.wav"), volume, _ATTN_WEAPON_LIGHT);
		break;
	case MZ_BLUEHYPERBLASTER:
		dl->color[0] = 0;dl->color[1] = 0;dl->color[2] = 1;
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_hyprbf1a, volume, _ATTN_WEAPON_LIGHT);
		break;
	case MZ_PHALANX:
		dl->color[0] = 1;dl->color[1] = 0.5; dl->color[2] = 0.5;
		S_fastsound(NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/plasshot.wav"), volume, _ATTN_WEAPON_HEAVY);
		break;
	// PGM
	case MZ_ETF_RIFLE:
		dl->color[0] = 0.9;dl->color[1] = 0.7;dl->color[2] = 0;
		S_fastsound(NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/nail1.wav"), volume, _ATTN_WEAPON_LIGHT);
		break;
	case MZ_SHOTGUN2:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		CL_BrassShells (org_brass, dir, 1, false);	// fixme: скока там патронов, 1 или 2?
		S_fastsound(NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/shotg2.wav"), volume, _ATTN_WEAPON_LIGHT);

		/// из вида со стороны не совпадает направление пушки и место попадания пуль. Хуета! Не будем рисовать трейсеры.
		if (cl.playernum == i - 1 && !cl_thirdPerson->value)	// это локальный игрок?  и не включен вид от третьего лица?
			CL_Tracers(r_origin, vpn, vright, vup, 32, 8, -8, 6, 2, 2, 2000, 2000);
		break;
	case MZ_HEATBEAM:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		dl->die = cl.gameTime + 100;
//		S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/bfg__l1a.wav"), volume, _ATTN_NORM, 0);
		break;
	case MZ_BLASTER2:
		dl->color[0] = 0;dl->color[1] = 1;dl->color[2] = 0;
		// FIXME - different sound for blaster2 ??
		S_fastsound(NULL, i, CHAN_WEAPON, cl_sfx_weapons_blastf1a, volume * 0.75, _ATTN_WEAPON_LIGHT);
		break;
	case MZ_TRACKER:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 1;
		S_fastsound(NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/disint2.wav"), volume, _ATTN_MEDIUM);
		break;
	case MZ_NUKE1:
		dl->color[0] = 1;dl->color[1] = 0;dl->color[2] = 0;
		dl->die = cl.gameTime + 100;
		break;
	case MZ_NUKE2:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		dl->die = cl.gameTime + 100;
		break;
	case MZ_NUKE4:
		dl->color[0] = 0;dl->color[1] = 0;dl->color[2] = 1;
		dl->die = cl.gameTime + 100;
		break;
	case MZ_NUKE8:
		dl->color[0] = 0;dl->color[1] = 1;dl->color[2] = 1;
		dl->die = cl.gameTime + 100;
		break;
	// Berserker
	case MZ_MONSTER_RESPAWN:
		dl->color[0] = 0.1;dl->color[1] = 0.75;dl->color[2] = 0.3;
		dl->die = cl.gameTime + 800;
		S_StartSound (dl->origin, i, CHAN_AUTO, S_RegisterSound("world/blackhole.wav"), 1, _ATTN_NORM, 0);
		CL_LogoutEffect (pl->current.origin, weapon);
		break;
	}
}


bool	condition_true, condition_cheat;
char *ParseEntity (char *data, char **ents, bool it, bool *worldspawn_)
{
	char	keyname[256];
	char	*token;

	condition_true = !it;

	while (1)
	{	
		// parse key
		token = COM_Parse (&data);
		if (com_token[0] == '}' && com_token_len == 1)
			break;
		if (!data)
			Com_Error (ERR_DROP, "ParseEntity: EOF without closing brace");

		strncpy (keyname, com_token, sizeof(keyname));

		// parse value	
		token = COM_Parse (&data);
		if (!data)
			Com_Error (ERR_DROP, "ParseEntity: EOF without closing brace");
		if (com_token[0] == '}' && com_token_len == 1)
			Com_Error (ERR_DROP, "ParseEntity: closing brace without data");

		if (!strcmp(token, "worldspawn"))
			*worldspawn_ = true;

		if (!strcmp(keyname, "classname"))
		{
			condition_true = false;
			char **e = ents;
			while (e[0])
			{
				condition_true |= (!strcmp(token, *e));
				if (condition_true)
					break;
				e++;
			}

			if (!it)
				condition_true = !condition_true;
		}
	}

	return data;
}


char *ents0[]=
{
	"light",
	"_null",
	"smallsun",
	"sun",
	"bigsun",
	"emit",
	"decal",
	"opaque",
	"nodraw",
	"lightstyle",
	"ignored_lightsurfs",
	"cl_lightstyle",
	"info_fog",
	"info_EFXzone",
	0
};

char *ents1[]=
{
	"light",
	"_null",
	"smallsun",
	"sun",
	"bigsun",
	"emit",
	"decal",
	"misc_model",
	"item_invisibility",
	"opaque",
	"nodraw",
	"lightstyle",
	"ignored_lightsurfs",
	"cl_lightstyle",
	"info_fog",
	"info_EFXzone",
	0
};

char *CMod_FilterEnts (char *in, char *out, char **ents, bool it)
{
	char	*token;
	char	*bak;
	bool	worldspawn_ = false;

	condition_cheat = false;

	// parse ents
	while (1)
	{
		bak = in;
		// parse the opening brace
		token = COM_Parse (&in);
		if(!token[0])
			break;

		if (com_token[0] != '{')
			Com_Error (ERR_DROP, "CMod_FilterEnts: found %s when expecting {",com_token);

		in = ParseEntity (in, ents, it, &worldspawn_);

		if (!condition_true)
		{
			Memcpy (out, bak, in-bak);
			out += in-bak;

			if (it && worldspawn_ && !VectorCompare(amb, vec3_origin))
			{
				char stroka[MAX_QPATH];
				Com_sprintf (stroka, sizeof(stroka), "\"ambient\" \"%f %f %f\"}", amb[0], amb[1], amb[2]);
				Memcpy(out-1, stroka, strlen(stroka));
				out += strlen(stroka)-1;
				worldspawn_=false;
			}
		}
		else
			if (!worldspawn_)
				condition_cheat = true;
	}

	return out;
}


char *ParseWorldSpawnEntity (char *data, bool *stop_it)
{
	char	*token;

	while (1)
	{
		// parse key
		token = COM_Parse (&data);
		if (com_token[0] == '}' && com_token_len == 1)
			break;
		if (!data)
			Com_Error (ERR_DROP, "ParseWorldSpawnEntity: EOF without closing brace");

		// parse value
		token = COM_Parse (&data);
		if (!data)
			Com_Error (ERR_DROP, "ParseWorldSpawnEntity: EOF without closing brace");
		if (com_token[0] == '}' && com_token_len == 1)
			Com_Error (ERR_DROP, "ParseWorldSpawnEntity: closing brace without data");

		if (!strcmp(token, "worldspawn"))
		{
loop:		token = COM_Parse (&data);
			if (com_token[0] == '}' && com_token_len == 1)
				return data;
			if (!data)
				Com_Error (ERR_DROP, "ParseWorldSpawnEntity: EOF without closing brace");
			if (!strcmp(token, "ambient"))
			{
				sscanf (data, " \"%f %f %f", &amb[0], &amb[1], &amb[2]);
				*stop_it = true;
			}
			else if (!strcmp(token, "sky_viewcenter"))
			{
				sscanf (data, " \"%f %f %f", &sky_viewcenter[0], &sky_viewcenter[1], &sky_viewcenter[2]);
				*stop_it = true;
				sky_world = true;
			}
			else if (!strcmp(token, "sky_origin"))
			{
				sscanf (data, " \"%f %f %f", &sky_origin[0], &sky_origin[1], &sky_origin[2]);
				*stop_it = true;
				sky_world = true;
			}
			else if (!strcmp(token, "sky_angle"))
			{
				sscanf (data, " \"%f", &sky_angle);
				*stop_it = true;
				sky_world = true;
			}
			else if (!strcmp(token, "sky_rotate"))
			{
				sscanf (data, " \"%f", &sky_rotate);
				*stop_it = true;
				sky_world = true;
			}
			else if (!strcmp(token, "sky_scale"))
			{
				sscanf (data, " \"%f", &sky_scale);
				*stop_it = true;
				sky_world = true;
			}
			token = COM_Parse (&data);
			if (!data)
				Com_Error (ERR_DROP, "ParseWorldSpawnEntity: EOF without closing brace");
			if (com_token[0] == '}' && com_token_len == 1)
				Com_Error (ERR_DROP, "ParseWorldSpawnEntity: closing brace without data");
			goto loop;
		}
	}

	return data;
}


void 	CMod_ScanForAmbient(char *data)
{
	char	*token;
	bool	stop_it = false;

	// parse ents
	while (1)
	{
		// parse the opening brace
		token = COM_Parse (&data);
		if(!token[0])
			break;

		if (com_token[0] != '{')
			Com_Error (ERR_DROP, "CMod_ScanForAmbient: found %s when expecting {",com_token);

		data = ParseWorldSpawnEntity (data, &stop_it);
		if (stop_it)
			break;
	}

	if (sky_world)
		sky_area = CM_LeafArea (CM_PointLeafnum (sky_origin));
}


unsigned	CMod_LoadEntityString (lump_t *l, char *name)
{
	numentitychars = l->filelen;
	if (l->filelen > MAX_MAP_ENTSTRING)
		Com_Error (ERR_DROP, "Map has too large entity lump");

	Memset (map_entitystring, 0, MAX_MAP_ENTSTRING);					/// Fixed by Berserker: обнуляем map_entitystring, ибо возможно наложение предыдущей строки в случае отсутствия нуля в загружаемой строке. ("мистика"-баг)
	VectorClear(amb);
	VectorClear(sky_origin);
	VectorClear(sky_viewcenter);
	sky_angle = sky_rotate = sky_scale = 0;
	sky_area = -1;
	drawing_sky_world = sky_world = false;

// Поддержка релайта
	char	lit[MAX_QPATH];
	char	*end;
	char	*buf;
	char	*endbuf;
	char	*litdata = NULL;
	int		len = strlen(name);
	char	*in = (char*) cmod_base + l->fileofs;

	if(!r_relight->value)
		goto stop;

	Memcpy (lit, name, len);
	lit[len-3] = 'l';
	lit[len-2] = 'i';
	lit[len-1] = 't';
	lit[len] = 0;

	len = FS_LoadFile (lit, (void **)&litdata);
	if (!litdata || (len <= 0))
	{
stop:	if (litdata)
			Z_Free (litdata);
		Memcpy (map_entitystring, in, l->filelen);
		return 0;		// нет доп.чексуммы (для совместимости со старыми серверами)
	}

	buf = (char*)Z_Malloc(len+2, false);
	if(!buf)
		goto stop;
///	Memset (buf, 0, len+2);

	CMod_ScanForAmbient(litdata);
	end = CMod_FilterEnts (in, map_entitystring, ents0, true);		// Копируем строку из ент-стринга карты в Out, игнорируя classname, перечисленные в ents0
// это копирование делать вторым!!! чтоб позже воспользоваться результатом проверки condition_cheat !!!
	endbuf = CMod_FilterEnts (litdata, buf, ents1, false);			// Копируем строку из релайта в буфер, игнорируя все кроме classname, перечисленные в ents1
	end[0]=0x0a;
	Memcpy (end+1, buf, endbuf-buf);								// Дописываем энтитис релайта
	unsigned checksum = LittleLong (Com_BlockChecksum (end+1, endbuf-buf));
	if (condition_cheat)
		Com_Printf("^1*** cheater's relight detected ***\n");
	Z_Free(buf);
	Z_Free (litdata);
	return checksum;
}


/*
===================
CM_InitBoxHull

Set up the planes and nodes so that the six floats of a bounding box
can just be stored out and get a proper clipping hull structure.
===================
*/
void CM_InitBoxHull ()
{
	int			i;
	int			side;
	cnode_t		*c;
	cplane_t	*p;
	cbrushside_t	*s;

	box_headnode = numnodes;
	box_planes = &map_planes[numplanes];
	if (numnodes+6 > MAX_MAP_NODES
		|| numbrushes+1 > MAX_MAP_BRUSHES
		|| numleafbrushes+1 > MAX_MAP_LEAFBRUSHES
		|| numbrushsides+6 > MAX_MAP_BRUSHSIDES
		|| numplanes+12 > MAX_MAP_PLANES)
		Com_Error (ERR_DROP, "Not enough room for box tree");

	box_brush = &map_brushes[numbrushes];
	box_brush->numsides = 6;
	box_brush->firstbrushside = numbrushsides;
	box_brush->contents = CONTENTS_MONSTER;

	box_leaf = &map_leafs[numleafs];
	box_leaf->contents = CONTENTS_MONSTER;
	box_leaf->firstleafbrush = numleafbrushes;
	box_leaf->numleafbrushes = 1;

	map_leafbrushes[numleafbrushes] = numbrushes;

	for (i=0 ; i<6 ; i++)
	{
		side = i&1;

		// brush sides
		s = &map_brushsides[numbrushsides+i];
		s->plane = 	map_planes + (numplanes+i*2+side);
		s->surface = &nullsurface;

		// nodes
		c = &map_nodes[box_headnode+i];
		c->plane = map_planes + (numplanes+i*2);
		c->children[side] = -1 - emptyleaf;
		if (i != 5)
			c->children[side^1] = box_headnode+i + 1;
		else
			c->children[side^1] = -1 - numleafs;

		// planes
		p = &box_planes[i*2];
		p->type = i>>1;
		p->signbits = 0;
		VectorClear (p->normal);
		p->normal[i>>1] = 1;

		p = &box_planes[i*2+1];
		p->type = 3 + (i>>1);
		p->signbits = 0;
		VectorClear (p->normal);
		p->normal[i>>1] = -1;
	}	
}


void Remove_Flares(int num_texinfo, bool echo)
{
	int		i, j;
	int		rem = 0;
	bool	rep;

	void (*__print)(char *fmt, ...);
	__print = echo ? Com_Printf : Com_DPrintf;

	if (r_worldmodel->texinfo[num_texinfo].flags & SURF_FLARE)
	{	// удалить из списка flares
repeat:	rep = false;
		for (i=0; i<num_flares ; i++)
			if (flares[i].surf->texinfo == &r_worldmodel->texinfo[num_texinfo])
			{
				flares[i].surf->texinfo->flags |= SURF_IGNORE_LIGHT;
				flares[i].surf->texinfo->flags &= ~(SURF_FLARE|SURF_LIGHT);
				for (j=i; j<num_flares-1; j++)
				{
					flares[j].size = flares[j+1].size;
					flares[j].surf = flares[j+1].surf;
				}
				num_flares--;
				rem++;
				rep = true;
			}

		if (rep)
			goto repeat;
	}
	else
		__print("not a SURF_FLARE\n");

	if (rem)
		__print("%i flares removed\n", rem);
}


void CL_ParseFlareSurfaces(char *data)
{
	int		num_texinfo;

	while (1)
	{	
		if (!data)
			return;

		num_texinfo = atoi(data);
		if (num_texinfo<0 || num_texinfo>=numtexinfo)
			Com_DPrintf("CL_ParseFlareSurfaces: Bad surface number %i\n", num_texinfo);
		else
			Remove_Flares(num_texinfo, false);

// skip readed data
		while (*data != ' ')
		{
			if (*data == 0)
				return;
			data++;
		}
// skip whitespace
		while (*data == ' ')
		{
			if (*data == 0)
				return;
			data++;
		}
	}
}


void CL_ParseNoDrawSurfaces(char *data)
{
	int		num_texinfo;

	while (1)
	{	
		if (!data)
			return;

		num_texinfo = atoi(data);
		if (num_texinfo<0 || num_texinfo>=numtexinfo)
			Com_DPrintf("CL_ParseNoDrawSurfaces: Bad surface number %i\n", num_texinfo);
		else
		{
			if (r_worldmodel->texinfo[num_texinfo].flags & SURF_SKY)
				Com_DPrintf("CL_ParseNoDrawSurfaces: sky surface - %i\n", num_texinfo);
			else
				r_worldmodel->texinfo[num_texinfo].flags |= SURF_NODRAW;
		}
// skip readed data
		while (*data != ' ')
		{
			if (*data == 0)
				return;
			data++;
		}
// skip whitespace
		while (*data == ' ')
		{
			if (*data == 0)
				return;
			data++;
		}
	}
}


void CL_ParseOpaqueSurfaces(char *data)
{
	int		num_texinfo;

	while (1)
	{	
		if (!data)
			return;

		num_texinfo = atoi(data);
		if (num_texinfo<0 || num_texinfo>=numtexinfo)
			Com_DPrintf("CL_ParseOpaqueSurfaces: Bad surface number %i\n", num_texinfo);
		else
		{
			if (!(r_worldmodel->texinfo[num_texinfo].flags & SURF_SKY))
				Com_DPrintf("CL_ParseOpaqueSurfaces: not a sky - %i\n", num_texinfo);
			else
				r_worldmodel->texinfo[num_texinfo].flags |= SURF_SLICK;
		}
// skip readed data
		while (*data != ' ')
		{
			if (*data == 0)
				return;
			data++;
		}
// skip whitespace
		while (*data == ' ')
		{
			if (*data == 0)
				return;
			data++;
		}
	}
}


byte	ent_id;
bool	angle_hack;
char *ParseEnt (char *data, shadowlight_t *l, bool server)
{
	char	keyname[256];
	char	*token;

	ent_id = ENT_UNKNOWN;
	angle_hack = false;

	// go through all the dictionary pairs
	while (1)
	{	
		// parse key
		token = COM_Parse (&data);
		if (com_token[0] == '}' && com_token_len == 1)
			break;
		if (!data)
			Com_Error (ERR_DROP, "ParseEnt: EOF without closing brace");

		strncpy (keyname, com_token, sizeof(keyname));

		// parse value	
		token = COM_Parse (&data);
		if (!data)
			Com_Error (ERR_DROP, "ParseEnt: EOF without closing brace");

		if (com_token[0] == '}' && com_token_len == 1)
			Com_Error (ERR_DROP, "ParseEnt: closing brace without data");

		if (!strcmp(keyname, "origin"))
			sscanf (token, "%f %f %f", &l->origin[0], &l->origin[1], &l->origin[2]);
		if (!strcmp(keyname, "origin2"))
			sscanf (token, "%f %f %f", &l->angles[0], &l->angles[1], &l->angles[2]);
		else if (!strcmp(keyname, "_color") || !strcmp(keyname, "color") || !strcmp(keyname, "startcolor"))
		{
			sscanf (token, "%f %f %f", &l->color[0], &l->color[1], &l->color[2]);
			if(l->color[0]<0 || l->color[0]>1)
				l->color[0]=1;
			if(l->color[1]<0 || l->color[1]>1)
				l->color[1]=1;
			if(l->color[2]<0 || l->color[2]>1)
				l->color[2]=1;
		}
		else if (!strcmp(keyname, "endcolor"))
			sscanf (token, "%f %f %f", &l->radiuses[0], &l->radiuses[1], &l->radiuses[2]);
		else if (!strcmp(keyname, "light"))
			l->radiuses[0] = l->radiuses[1] = l->radiuses[2] = atof(token);
		else if (!strcmp(keyname, "radius"))
			sscanf (token, "%f %f %f", &l->radiuses[0], &l->radiuses[1], &l->radiuses[2]);
		else if (!strcmp(keyname, "density"))
			l->radius = atof(token);
		else if (!strcmp(keyname, "lifetime"))
			l->mins[0] = atof(token);
		else if (!strcmp(keyname, "_cone"))
			l->_cone = atof(token);
		else if (!strcmp(keyname, "_flare"))
			l->flareLight = atoi(token);
		else if (!strcmp(keyname, "frame"))
			l->ownerkey = atoi(token);
		else if (!strcmp(keyname, "frame_end"))
			l->framecount = atoi(token);
		else if (!strcmp(keyname, "skin"))
			l->filtercube_start = atoi(token);
		else if (!strcmp(keyname, "cone"))
			l->_cone = atof(token);
		else if (!strcmp(keyname, "style"))
			l->style = atoi(token);
		else if (!strcmp(keyname, "cl_style"))
			l->cl_style = atoi(token);
		else if (!strcmp(keyname, "gravity"))
			l->ownerkey = atoi(token);
		else if (!server && !strcmp(keyname, "surfaces"))	// by "opaque", "nodraw" or "ignored_lightsurfs" entity
		{
			if (ent_id == ENT_OPAQUE)
				CL_ParseOpaqueSurfaces(token);
			else if (ent_id == ENT_IGNORED_FLARE)
				CL_ParseFlareSurfaces(token);
			else if (ent_id == ENT_NODRAW)
				CL_ParseNoDrawSurfaces(token);
			else
				Com_Error (ERR_DROP, "ParseEnt: Token \"surfaces\" before \"classname\"");
		}
		else if (!strcmp(keyname, "filter"))
			l->filtercube_start = atoi(token);
		else if (!strcmp(keyname, "filter2"))
			l->filtercube_end = atoi(token);
		else if (!strcmp(keyname, "speed"))
			sscanf (token, "%f %f %f", &l->rspeed[0], &l->rspeed[1], &l->rspeed[2]);
		else if (!strcmp(keyname, "angles"))
			sscanf (token, "%f %f %f", &l->angles[0], &l->angles[1], &l->angles[2]);
		else if (!strcmp(keyname, "dir"))
			sscanf (token, "%f %f %f", &l->rspeed[0], &l->rspeed[1], &l->rspeed[2]);
		else if (!strcmp(keyname, "angle"))
		{
			l->angles[0] = atof(token);
			angle_hack = true;
		}
		else if (!strcmp(keyname, "volume"))
			l->radius = atof(token);
		else if (!strcmp(keyname, "wait"))
			l->frust[0].normal[0] = atof(token);
		else if (!strcmp(keyname, "spawnflags"))
		{
			l->area = atoi(token);
#define START_OFF	1
			l->start_off = (l->area & START_OFF);
		}
		else if (!strcmp(keyname, "nodraw"))
			l->nobump = (bool)atoi(token);
		else if (!strcmp(keyname, "nobump"))
			l->nobump = (bool)atoi(token);
		else if (!strcmp(keyname, "noshadow"))
			l->noshadow = (bool)atoi(token);
		else if (!strcmp(keyname, "noshadow2"))
			l->noshadow2 = (bool)atoi(token);
		else if (!strcmp(keyname, "scale"))
			l->framerate = atof(token);
		else if (!strcmp(keyname, "invert"))
			l->nobump = (bool)atoi(token);
		else if (!strcmp(keyname, "model"))
		{
			Memcpy(l->vis, token, MAX_QPATH);
			if(l->vis[0]=='*')
				ent_id = ENT_BRUSHMODEL;
		}
		else if (!strcmp(keyname, "map"))
			Memcpy(l->vis, token, MAX_QPATH-1);
		else if (!strcmp(keyname, "label"))
			Memcpy(l->label, token, MAX_QPATH);
		else if (!strcmp(keyname, "targetname"))
			Memcpy(l->targetname, token, MAX_QPATH);
		else if (!strcmp(keyname, "noise"))				// клиентские модели не используют targetname, заюзаем как буфер
			Memcpy(l->targetname, token, MAX_QPATH);
		else if (!strcmp(keyname, "classname"))
		{
			if(strcmp(token, "light"))
			{
				if(strcmp(token, "smallsun"))
				{
					if(strcmp(token, "sun"))
					{
						if(strcmp(token, "bigsun"))
						{
							if (strcmp(token, "emit"))
							{
								if (strcmp(token, "decal"))
								{
									if(strcmp(token, "opaque"))
									{
										if(strcmp(token, "ignored_lightsurfs"))
										{
											if(strcmp(token, "fog"))
											{
												if(strcmp(token, "misc_model"))
												{
													if(strcmp(token, "lightstyle"))
													{
														if(strcmp(token, "cl_lightstyle"))
														{
															if(strcmp(token, "nodraw"))
															{
																if (strcmp(token, "info_EFXzone"))
																{
																	if (!strcmp(token, "info_fog"))
																		ent_id = ENT_FOG_HACK;
																}
																else
																	ent_id = ENT_EFXZONE_HACK;
															}
															else
																ent_id = ENT_NODRAW;
														}
														else
															ent_id = ENT_CL_LIGHTSTYLE;
													}
													else
														ent_id = ENT_LIGHTSTYLE;
												}
												else
													ent_id = ENT_ALIASMODEL;
											}
											else
												ent_id = ENT_FOG;
										}
										else
											ent_id = ENT_IGNORED_FLARE;
									}
									else
										ent_id = ENT_OPAQUE;
								}
								else
									ent_id = ENT_DECAL;
							}
							else
								ent_id = ENT_EMIT;
						}
						else
							ent_id = ENT_BIG_SUN;
					}
					else
						ent_id = ENT_SUN;
				}
				else
					ent_id = ENT_SMALL_SUN;
			}
			else
				ent_id = ENT_LIGHT;
		}
	}

	if (ent_id == ENT_LIGHT)
	{
		if (l->filtercube_start > MAX_FILTERS)
			l->filtercube_start = 0;

		if (l->filtercube_end > MAX_FILTERS)
			l->filtercube_end = 0;

		if (!l->filtercube_start && l->filtercube_end)
			l->filtercube_start = l->filtercube_end;

		if (l->filtercube_start && !l->filtercube_end)
			l->filtercube_end = l->filtercube_start;

		if (l->ownerkey == 0)
			l->ownerkey = 2;	// framerate
	}

	return data;
}


emit_t* AllocEmit ()
{
	if (numEmits >= MAX_EMITS)
		return NULL;

	numEmits++;
	return &emits[numEmits-1];
}


void R_SpawnEmit(shadowlight_t *sl)
{
	emit_t *l;

	l = AllocEmit();
	if (!l)
		return;

	VectorCopy(sl->origin, l->origin);
	VectorCopy(sl->origin, l->origin0);
	l->angles[0] = sl->angles[0];
	l->angles[1] = sl->angles[1];
	l->vel = sl->angles[2];
	if (l->vel<=0)
		l->vel = 128;
	sl->angles[2] = 0;
	AngleVectors(sl->angles, l->dir, NULL, NULL);
	VectorCopy(l->dir, l->dir0);
	VectorCopy(sl->rspeed, l->rspeed);
	VectorCopy(sl->color, l->startcolor);
	VectorCopy(sl->radiuses, l->endcolor);
	l->style = sl->style;
	l->cl_style = sl->cl_style;
	l->emit = (parttype_t)sl->filtercube_start;
	l->cone = sl->_cone;
	l->alphavel = sl->mins[0];
	if (sl->ownerkey > 127)	l->gravity = 127;
	else if (sl->ownerkey < -128)	l->gravity = -128;
	else l->gravity = sl->ownerkey;
	Memcpy(l->label, sl->label, MAX_QPATH);
	l->area = CM_LeafArea (CM_PointLeafnum (l->origin));
	if (!l->area)
		Com_DPrintf("Emitter out of BSP at %f %f %f\n", l->origin[0], l->origin[1], l->origin[2]);
}


void SpawnEmit (shadowlight_t *sl)
{
	if (numEmits >= MAX_EMITS)
	{
		Com_DPrintf ("SpawnEmit: overflow\n");
		return;
	}

	//Create a light and make it static
	R_SpawnEmit(sl);
}


fog_t* AllocFog ()
{
	if (numFogs >= MAX_FOG_AREAS)
		return NULL;

	numFogs++;
	return &fog_infos[numFogs-1];
}


void R_SpawnFog(shadowlight_t *sl, bool hack)
{
	fog_t *l;

	l = AllocFog();
	if (!l)
		return;

	VectorCopy(sl->color, l->color);
	VectorCopy(sl->origin, l->origin);
	VectorCopy(sl->angles, l->origin2);
	l->density = 1.0 / sl->radius;
	l->ed = hack;
	if (l->origin[2] < l->origin2[2])
	{
		float temp;
		temp = l->origin[2];
		l->origin[2] = l->origin2[2];
		l->origin2[2] = temp;
	}
}

void SpawnFog (shadowlight_t *sl, bool hack)
{
	if (numFogs >= MAX_FOG_AREAS)
	{
		Com_DPrintf ("SpawnFog: overflow\n");
		return;
	}

	//Create a fog info
	R_SpawnFog(sl, hack);
}


EFXzone_t* AllocEFXzone()
{
	if (numEFXzones >= MAX_EFX_ZONES)
		return NULL;

	numEFXzones++;
	return &EFXzone_infos[numEFXzones - 1];
}


void R_SpawnEFXzone(shadowlight_t *sl, bool hack)
{
	EFXzone_t *l;

	l = AllocEFXzone();
	if (!l)
		return;

	VectorCopy(sl->origin, l->origin);
	VectorCopy(sl->angles, l->origin2);
	R_CalcEFXzoneBounds(l);
	l->preset = sl->style;
	l->ed = hack;
	if (l->origin[2] < l->origin2[2])
	{
		float temp;
		temp = l->origin[2];
		l->origin[2] = l->origin2[2];
		l->origin2[2] = temp;
	}
}

void SpawnEFXzone(shadowlight_t *sl, bool hack)
{
	if (numEFXzones >= MAX_EFX_ZONES)
	{
		Com_DPrintf("SpawnEFXzone: overflow\n");
		return;
	}

	//Create a fog info
	R_SpawnEFXzone(sl, hack);
}


alink_t* AllocAlias ()
{
	if (aliasmodel_counter >= MAX_ENTITIES * 16)
		return NULL;

	alink_t	*alias = &amdl_list[aliasmodel_counter++];
	return alias;
}


bool intersectsBoxPoint(vec3_t mins, vec3_t maxs, vec3_t p)
{
	if (p[0] > maxs[0]) return false;
	if (p[1] > maxs[1]) return false;
	if (p[2] > maxs[2]) return false;
 
	if (p[0] < mins[0]) return false;
	if (p[1] < mins[1]) return false;
	if (p[2] < mins[2]) return false;

	return true;
}


bool R_MarkAliasLeaves(alink_t *alias)
{
	vec3_t	org;
	int		leafs[MAX_MAP_LEAFS];
	int		i, j, count;
	int		longs;
	vec3_t	mins, maxs;
	byte	vis[MAX_MAP_LEAFS/8];
	byte	vis_[MAX_MAP_LEAFS/8];

	VectorAdd(alias->origin, alias->mdl->center, org);
	for (i=0 ; i<3 ; i++)
	{
		mins[i] = org[i] + alias->mdl->mins[i];
		maxs[i] = org[i] + alias->mdl->maxs[i];
	}

	alias->areas[8] = CM_LeafArea (CM_PointLeafnum (org));

	Memset(alias->vis, 0, (((r_worldmodel->numleafs+31)>>5)<<2)/*MAX_MAP_LEAFS/8*/);
	for (j=0; j<8; j++)
	{
		vec3_t	mins_, maxs_;

		if (j==0)
			VectorSet(org, mins[0], mins[1], mins[2]);
		else if (j==1)
			VectorSet(org, mins[0], mins[1], maxs[2]);
		else if (j==2)
			VectorSet(org, mins[0], maxs[1], mins[2]);
		else if (j==3)
			VectorSet(org, mins[0], maxs[1], maxs[2]);
		else if (j==4)
			VectorSet(org, maxs[0], mins[1], mins[2]);
		else if (j==5)
			VectorSet(org, maxs[0], mins[1], maxs[2]);
		else if (j==6)
			VectorSet(org, maxs[0], maxs[1], mins[2]);
		else
			VectorSet(org, maxs[0], maxs[1], maxs[2]);

		alias->areas[j] = CM_LeafArea (CM_PointLeafnum (org));

		for (i=0 ; i<3 ; i++)
		{
			mins_[i] = org[i] - 8;
			maxs_[i] = org[i] + 8;
		}

		// строим vis-data
		Memcpy (vis_, CM_ClusterPVS(CM_LeafCluster (CM_PointLeafnum (org))), (((CM_NumClusters()+31)>>5)<<2));
		count = CM_BoxLeafnums (mins_, maxs_, leafs, r_worldmodel->numleafs/*MAX_MAP_LEAFS*/, NULL);
		if (count < 1)
			Com_Error (ERR_FATAL, "R_MarkAliasLeaves: count < 1");
		longs = (CM_NumClusters()+31)>>5;

		// convert leafs to clusters
		for (i=0 ; i<count ; i++)
			leafs[i] = CM_LeafCluster(leafs[i]);

		Memset(&vis, 0, (((r_worldmodel->numleafs+31)>>5)<<2)/*MAX_MAP_LEAFS/8*/);
		for (i=0 ; i<count ; i++)
			vis[leafs[i]>>3] |= (1<<(leafs[i]&7));
		// вырезаем кластеры, которых нет в списке leafs
		for (i=0 ; i<((r_worldmodel->numleafs+31)>>5)/*MAX_MAP_LEAFS/32*/ ; i++)
			((long *)vis_)[i] &= ((long *)vis)[i];
		// копируем результат в alias->vis
		for (i=0 ; i<longs ; i++)
			((long *)alias->vis)[i] |= ((long *)vis_)[i];
	}
	return (alias->areas[0] || alias->areas[1] || alias->areas[2] || alias->areas[3] || alias->areas[4] || alias->areas[5] || alias->areas[6] || alias->areas[7] || alias->areas[8]);
}


void R_SpawnAlias(shadowlight_t *sl)
{
	alink_t	*l;
	char	buffer[MAX_QPATH+4];

	l = AllocAlias();
	if (!l)
		return;

	VectorCopy(sl->origin, l->origin);
	VectorCopy(sl->angles, l->angles);
	Memcpy(l->model, sl->vis, MAX_QPATH);
	Memcpy(l->label, sl->label, MAX_QPATH);
	Memcpy(l->sound, sl->targetname, MAX_QPATH);
	if (l->sound[0])
	{
		if (!strstr (l->sound, ".wav"))
			Com_sprintf (buffer, sizeof(buffer), "%s.wav", l->sound);
		else
			strncpy (buffer, l->sound, sizeof(buffer));
		l->noise = S_RegisterSound (buffer);
	}
	else
		l->noise = NULL;

#define	GFX_ANIMATION		2
	l->animframe = sl->area & GFX_ANIMATION;
	l->frame = sl->ownerkey;
	l->frame_end = sl->framecount;
	if (l->frame_end < l->frame)
		l->frame_end = l->frame;
	l->skinnum = (unsigned)sl->filtercube_start;
	l->mdl = R_RegisterModel(l->model, sl->framerate, sl->nobump);
	if (l->mdl)
	{
		if (!strcmp(l->label, "SkyModel"))
		{
			R_MarkAliasLeaves(l);
			return;
		}
		if (!strcmp(l->label, " "))
		{
			if (R_MarkAliasLeaves(l))
				return;
		}
		else
			return;
	}

	Com_DPrintf("Can't spawn %s\n", l->model);
	aliasmodel_counter--;
}


void SpawnAlias (shadowlight_t *sl)
{
	if (aliasmodel_counter >= MAX_ENTITIES * 16)
	{
		Com_DPrintf ("SpawnAlias: overflow\n");
		return;
	}

	//Create a aliasmodel info
	R_SpawnAlias(sl);
}


blink_t* AllocBrush (int idx)
{
	if (brushmodel_counter >= MAX_ENTITIES)
		return NULL;

	// find if it already exist
	int	i;
	for (i=0; i<brushmodel_counter; i++)
		if (bmdl_list[i].index == idx)
			return &bmdl_list[i];

	brushmodel_counter++;
	return &bmdl_list[brushmodel_counter-1];
}


void R_SpawnBrush(shadowlight_t *sl)
{
	blink_t *l;
	int		old = brushmodel_counter;
	int		idx = atoi((const char*)sl->vis+1);

	l = AllocBrush(idx);
	if (!l)
		return;

	Memcpy(l->label, sl->label, MAX_QPATH);
	l->nodraw = sl->nobump;

	if (old != brushmodel_counter)
	{
		VectorCopy(sl->origin, l->origin);
		l->index = idx;
	}

	if (idx >= r_worldmodel->numsubmodels || idx < 0)
		Com_Error(ERR_DROP, "R_SpawnBrush: bad index %i", idx);

	l->mdl = &r_worldmodel->submodels[idx];
}


void SpawnBrush (shadowlight_t *sl)
{
	if (brushmodel_counter >= MAX_ENTITIES)
	{
		Com_DPrintf ("SpawnBrush: overflow\n");
		return;
	}

	//Create a brushmodel info
	R_SpawnBrush(sl);
}


void CheckLightStyle(char *map)
{
	while (map[0])
	{
		if(map[0]<'a')	map[0]='a';
		if(map[0]>'z')	map[0]='z';
		map++;
	}
}

void CL_CL_SetLightstyle (int i, char *s)
{
	int		j, k;

	j = strlen (s);
	if (j >= MAX_QPATH)
		Com_Error (ERR_DROP, "cl_lightstyle length=%i", j);

	cl_lightstyle[i].length = j;

	for (k=0 ; k<j ; k++)
	{
		cl_lightstyles[i][k] = s[k];
		cl_lightstyle[i].map[k] = (float)(s[k]-'a')/(float)('m'-'a');
	}
}


void CM_SpawnEntities (char *entities, bool server)
{
	shadowlight_t sl;
	char *token;
	decals_needs_spawn = false;
	num_deferred_decals = 0;
	Memset(deferred_decals, 0, (MAX_DECALS+1)*sizeof(defDecal_t));

	if (!server)
	{
		aliasmodel_counter = brushmodel_counter = lightmodel_counter = 0;
		Memset(lightstyles, 0, sizeof(lightstyles));
		Memset(cl_lightstyles, 0, sizeof(cl_lightstyles));
	}

	// parse ents
	while (1)
	{
		Memset (&sl, 0, sizeof(sl));

		// parse the opening brace
		token = COM_Parse (&entities);
		if(!token[0])
			break;

		if (com_token[0] != '{')
			Com_Error (ERR_DROP, "CL_SpawnEntities: found %s when expecting {",com_token);

		entities = ParseEnt (entities, &sl, server);

		if (server && ent_id != ENT_LIGHTSTYLE)
			ent_id = ENT_UNKNOWN;

		if(Cvar_VariableValue ("deathmatch") && sl.area & SPAWNFLAG_NOT_DEATHMATCH)
			ent_id = ENT_UNKNOWN;

		if(Cvar_VariableValue ("coop") && sl.area & SPAWNFLAG_NOT_COOP)
			ent_id = ENT_UNKNOWN;

		if(Cvar_VariableValue ("skill")==0 && sl.area & SPAWNFLAG_NOT_EASY)
			ent_id = ENT_UNKNOWN;

		if(Cvar_VariableValue ("skill")==1 && sl.area & SPAWNFLAG_NOT_MEDIUM)
			ent_id = ENT_UNKNOWN;

		if(Cvar_VariableValue ("skill")==2 && sl.area & SPAWNFLAG_NOT_HARD)
			ent_id = ENT_UNKNOWN;

		if (ent_id != ENT_UNKNOWN)
		{
			if ((ent_id == ENT_LIGHT) && !r_simple->value)
			{
				if ((sl.color[0] == 0.0) && (sl.color[1] == 0.0) && (sl.color[2] == 0.0))
					VectorSet(sl.color, 1.0, 1.0, 1.0);		// Set default color

				if (sl.radiuses[0] <= 0.0)
					sl.radiuses[0] = 300.0;						// Set default radius

				if (sl.radiuses[1] <= 0.0)
					sl.radiuses[1] = 300.0;						// Set default radius

				if (sl.radiuses[2] <= 0.0)
					sl.radiuses[2] = 300.0;						// Set default radius

#ifndef FORCE_DYNAMICCONE
				if (sl.rspeed[0] || sl.rspeed[1])			// Если есть вращающиеся lights: _cone обнуляем.
					sl._cone = 0;							// PS: только если rspeed=={0,0,X}: _cone сохраним без изменений. (Это фильтры типа "вращающиеся вентиляторы")
#endif

				if (sl.filtercube_start<0 || sl.filtercube_start>MAX_FILTERS)
					sl.filtercube_start = 0;

				if (sl.filtercube_end<0 || sl.filtercube_end>MAX_FILTERS)
					sl.filtercube_end = 0;

				if (!sl.filtercube_start && sl.filtercube_end)
					sl.filtercube_start = sl.filtercube_end;

				if (sl.filtercube_start && !sl.filtercube_end)
					sl.filtercube_end = sl.filtercube_start;

				if (!sl.filtercube_start)
					sl._cone = 0;							// если нет фильтра, обнуляем _cone (на старых картах игнорируем cone!!!)

				if (sl.ownerkey == 0)
					sl.ownerkey = 2;	// framerate

				if (sl.label[0])
					SpawnLinkedLight (&sl);
				else
					SpawnStaticLight (&sl);
			}
			else if (ent_id == ENT_SMALL_SUN)
			{
				sun_size = 0.10;
				VectorCopy(sl.origin, sun_origin);
			}
			else if (ent_id == ENT_SUN)
			{
				sun_size = 0.20;
				VectorCopy(sl.origin, sun_origin);
			}
			else if (ent_id == ENT_BIG_SUN)
			{
				sun_size = 0.30;
				VectorCopy(sl.origin, sun_origin);
			}
			else if (ent_id == ENT_EMIT)
			{
				if(!sl.rspeed[2])
					sl.rspeed[2] = 3;

				SpawnEmit (&sl);
			}
			else if (ent_id == ENT_FOG)
			{
				if (sl.color[0] < 0.0)
					sl.color[0] = 0.0;
				else if (sl.color[0] > 1.0)
					sl.color[0] = 1.0;
				else if (sl.color[1] < 0.0)
					sl.color[1] = 0.0;
				else if (sl.color[1] > 1.0)
					sl.color[1] = 1.0;
				else if (sl.color[2] < 0.0)
					sl.color[2] = 0.0;
				else if (sl.color[2] > 1.0)
					sl.color[2] = 1.0;

				if ((sl.color[0] == 0.0) && (sl.color[1] == 0.0) && (sl.color[2] == 0.0))
					VectorSet(sl.color, 1.0, 1.0, 1.0);		// Set default color

				if (sl.radius <= 0)
					sl.radius = 1024;

				SpawnFog(&sl, false);
			}
			else if (ent_id == ENT_FOG_HACK)
			{
				if (sl.color[0] < 0.0)
					sl.color[0] = 0.0;
				else if (sl.color[0] > 1.0)
					sl.color[0] = 1.0;
				else if (sl.color[1] < 0.0)
					sl.color[1] = 0.0;
				else if (sl.color[1] > 1.0)
					sl.color[1] = 1.0;
				else if (sl.color[2] < 0.0)
					sl.color[2] = 0.0;
				else if (sl.color[2] > 1.0)
					sl.color[2] = 1.0;

				if ((sl.color[0] == 0.0) && (sl.color[1] == 0.0) && (sl.color[2] == 0.0))
					VectorSet(sl.color, 1.0, 1.0, 1.0);		// Set default color

				if (sl.radius <= 0)
					sl.radius = 1024;

				SpawnFog(&sl, true);
			}
			else if (ent_id == ENT_EFXZONE_HACK)
			{
				if (sl.style <= 0)
					sl.style = 0;
				else if (sl.style >= NUM_EFX_PRESETS)
					sl.style = NUM_EFX_PRESETS - 1;

				SpawnEFXzone(&sl, true);
			}
			else if (ent_id == ENT_LIGHTSTYLE)
			{
				if (sl.vis[0])
					if (sl.style > 0 && sl.style < MAX_LIGHTSTYLES_OVERRIDE)
					{
						CheckLightStyle((char*)sl.vis);
						Memset(lightstyles[sl.style], 0, MAX_QPATH);
						Memcpy(lightstyles[sl.style], sl.vis, MAX_QPATH-1);
						Com_DPrintf("LightStyle %i overrided with \"%s\"\n", sl.style, lightstyles[sl.style]);
					}
			}
			else if (ent_id == ENT_CL_LIGHTSTYLE)
			{
				if (sl.vis[0])
///					if (sl.style > 0 && sl.style < MAX_LIGHTSTYLES)		/// sl.style is byte
					{
						CheckLightStyle((char*)&sl.vis[0]);
						CL_CL_SetLightstyle(sl.style, (char*)&sl.vis[0]);
					}
			}
			else if (ent_id == ENT_ALIASMODEL)
			{
				if (angle_hack)
				{
					sl.angles[1] = sl.angles[0];
					sl.angles[0] = 0;
				}

				if (sl.framerate <= 0.001)
					sl.framerate = 1.0;

				if (sl.label[0])
					SpawnAlias(&sl);
			}
			else if (ent_id == ENT_BRUSHMODEL)
			{
				if (sl.label[0])
					SpawnBrush(&sl);
			}
			else if (ent_id == ENT_DECAL)
			{
				image_t *tex;
				dtype_t dtype;

				if (sl.filtercube_start<0 || sl.filtercube_start>=MAX_FILTERS)
					sl.filtercube_start = 0;

				tex = decal_texture_object[sl.filtercube_start];
				if (sl.style)
					dtype = dt_mul;
				else
					dtype = dt_add;

				if (sl.radius <= 0)
					sl.radius = 32;

///				if (sl.frust[0].normal[0] <= 0)
///					sl.frust[0].normal[0] = 9999999;

///				sl.angles[0] += 45;
///				sl.radius *= 2*0.57735;

	/// Berserker: ебучий случай :(
	/// поле style уже заюзано для определения blend add/mul
	/// поэтому чтобы не переделывать существующие релайты придется под style/cl_style заюзать токены "frame"/"cl_style"
				if (sl.ownerkey < 0 || sl.ownerkey >=MAX_LIGHTSTYLES)
					sl.ownerkey = 0;
///				if (sl.cl_style < 0 || sl.cl_style >=MAX_LIGHTSTYLES)	/// sl.cl_style is byte
///					sl.cl_style = 0;
				R_StoreDecal(sl.origin, sl.rspeed, tex, sl.radius, sl.angles[0], dtype, sl.ownerkey, sl.cl_style, sl.color);
			}
		}
	}
}


float RadiusFromBounds (vec3_t mins, vec3_t maxs)
{
	int		i;
	vec3_t	corner;

	for (i=0 ; i<3 ; i++)
		corner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]);

	return VectorLength (corner);
}


void Mod_LoadSubmodels (lump_t *l)
{
	dmodel_t	*in;
	mmodel_t	*out;
	int			i, j, count;

	in = (dmodel_t *)(mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "Mod_LoadSubmodels: funny lump size in %s",loadmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mmodel_t *) Hunk_Alloc (count*sizeof(*out), true);
	if (caching__)
		return;
	loadmodel->submodels = out;
	loadmodel->numsubmodels = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		for (j=0 ; j<3 ; j++)
		{	// spread the mins / maxs by a pixel
			out->mins[j] = LittleFloat (in->mins[j]) - 1;
			out->maxs[j] = LittleFloat (in->maxs[j]) + 1;
			out->origin[j] = LittleFloat (in->origin[j]);
		}
		out->radius = RadiusFromBounds (out->mins, out->maxs);
		out->headnode = LittleLong (in->headnode);
		out->firstface = LittleLong (in->firstface);
		out->numfaces = LittleLong (in->numfaces);
	}
}


char *Sys_ScanForCD ()
{
	static char	cddir[MAX_OSPATH];
	static bool	done;
	char		drive[4];
	FILE		*f;
	char		test[MAX_QPATH];

	if (done)		// don't re-check
		return cddir;

	// no abort/retry/fail errors
	SetErrorMode (SEM_FAILCRITICALERRORS);

	drive[0] = 'c';
	drive[1] = ':';
	drive[2] = '\\';
	drive[3] = 0;

	done = true;

	// scan the drives
	for (drive[0] = 'c' ; drive[0] <= 'z' ; drive[0]++)
	{
		// where activision put the stuff...
		sprintf (cddir, "%sinstall\\data", drive);
		sprintf (test, "%sinstall\\data\\quake2.exe", drive);
		f = FS_Fopen(test, "rb");
		if (f)
		{
			fclose (f);
			if (GetDriveType (drive) == DRIVE_CDROM)
				return cddir;
		}
	}

	cddir[0] = 0;
	return NULL;
}


/*
=========================================================================
JOYSTICK
=========================================================================
*/

/* 
=============== 
IN_StartupJoystick 
=============== 
*/  
void IN_StartupJoystick () 
{ 
	int			numdevs;
	JOYCAPS		jc;
	MMRESULT	mmr;
	cvar_t		*cv;

 	// assume no joystick
	joy_avail = false; 

	// abort startup if user requests no joystick
	cv = Cvar_Get ("in_initjoy", "1", CVAR_NOSET);
	if ( !cv->value ) 
		return; 
 
	// verify joystick driver is present
	if ((numdevs = joyGetNumDevs ()) == 0)
	{
//		Com_Printf ("joystick not found -- driver not present\n\n");
		return;
	}

	// cycle through the joystick ids for the first valid one
	for (joy_id=0 ; joy_id<numdevs ; joy_id++)
	{
		memset (&ji, 0, sizeof(ji));
		ji.dwSize = sizeof(ji);
		ji.dwFlags = JOY_RETURNCENTERED;

		if ((mmr = joyGetPosEx (joy_id, &ji)) == JOYERR_NOERROR)
			break;
	} 

	// abort startup if we didn't find a valid joystick
	if (mmr != JOYERR_NOERROR)
	{
		Com_Printf ("joystick not found -- no valid joysticks (%x)\n\n", mmr);
		return;
	}

	// get the capabilities of the selected joystick
	// abort startup if command fails
	memset (&jc, 0, sizeof(jc));
	if ((mmr = joyGetDevCaps (joy_id, &jc, sizeof(jc))) != JOYERR_NOERROR)
	{
		Com_Printf ("joystick not found -- invalid joystick capabilities (%x)\n\n", mmr); 
		return;
	}

	// save the joystick's number of buttons and POV status
	joy_numbuttons = jc.wNumButtons;
	joy_haspov = jc.wCaps & JOYCAPS_HASPOV;

	// old button and POV states default to no buttons pressed
	joy_oldbuttonstate = joy_oldpovstate = 0;

	// mark the joystick as available and advanced initialization not completed
	// this is needed as cvars are not available during initialization

	joy_avail = true; 
	joy_advancedinit = false;

	Com_Printf ("joystick detected\n\n"); 
}


/*
===========
RawValuePointer
===========
*/
PDWORD RawValuePointer (int axis)
{
	switch (axis)
	{
	case JOY_AXIS_X:
		return &ji.dwXpos;
	case JOY_AXIS_Y:
		return &ji.dwYpos;
	case JOY_AXIS_Z:
		return &ji.dwZpos;
	case JOY_AXIS_R:
		return &ji.dwRpos;
	case JOY_AXIS_U:
		return &ji.dwUpos;
	default:				// shut up compiler
	case JOY_AXIS_V:
		return &ji.dwVpos;
	}
}


/*
===========
Joy_AdvancedUpdate_f
===========
*/
void Joy_AdvancedUpdate_f ()
{
	// called once by IN_ReadJoystick and by user whenever an update is needed
	// cvars are now available
	int	i;
	DWORD dwTemp;

	// initialize all the maps
	for (i = 0; i < JOY_MAX_AXES; i++)
	{
		dwAxisMap[i] = AxisNada;
		dwControlMap[i] = JOY_ABSOLUTE_AXIS;
		pdwRawValue[i] = RawValuePointer(i);
	}

	if( joy_advanced->value == 0.0)
	{
		// default joystick initialization
		// 2 axes only with joystick control
		dwAxisMap[JOY_AXIS_X] = AxisTurn;
		// dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS;
		dwAxisMap[JOY_AXIS_Y] = AxisForward;
		// dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS;
	}
	else
	{
		if (strcmp (joy_name->string, "joystick") != 0)
		{
			// notify user of advanced controller
			Com_Printf ("\n%s configured\n\n", joy_name->string);
		}

		// advanced initialization here
		// data supplied by user via joy_axisn cvars
		dwTemp = (DWORD) joy_advaxisx->value;
		dwAxisMap[JOY_AXIS_X] = dwTemp & 0x0000000f;
		dwControlMap[JOY_AXIS_X] = dwTemp & JOY_RELATIVE_AXIS;
		dwTemp = (DWORD) joy_advaxisy->value;
		dwAxisMap[JOY_AXIS_Y] = dwTemp & 0x0000000f;
		dwControlMap[JOY_AXIS_Y] = dwTemp & JOY_RELATIVE_AXIS;
		dwTemp = (DWORD) joy_advaxisz->value;
		dwAxisMap[JOY_AXIS_Z] = dwTemp & 0x0000000f;
		dwControlMap[JOY_AXIS_Z] = dwTemp & JOY_RELATIVE_AXIS;
		dwTemp = (DWORD) joy_advaxisr->value;
		dwAxisMap[JOY_AXIS_R] = dwTemp & 0x0000000f;
		dwControlMap[JOY_AXIS_R] = dwTemp & JOY_RELATIVE_AXIS;
		dwTemp = (DWORD) joy_advaxisu->value;
		dwAxisMap[JOY_AXIS_U] = dwTemp & 0x0000000f;
		dwControlMap[JOY_AXIS_U] = dwTemp & JOY_RELATIVE_AXIS;
		dwTemp = (DWORD) joy_advaxisv->value;
		dwAxisMap[JOY_AXIS_V] = dwTemp & 0x0000000f;
		dwControlMap[JOY_AXIS_V] = dwTemp & JOY_RELATIVE_AXIS;
	}

	// compute the axes to collect from DirectInput
	joy_flags = JOY_RETURNCENTERED | JOY_RETURNBUTTONS | JOY_RETURNPOV;
	for (i = 0; i < JOY_MAX_AXES; i++)
		if (dwAxisMap[i] != AxisNada)
			joy_flags |= dwAxisFlags[i];
}


/*
===========
IN_Commands
===========
*/
void IN_Commands ()
{
	int		i, key_index;
	DWORD	buttonstate, povstate;

	if (!joy_avail)
		return;
	
	// loop through the joystick buttons
	// key a joystick event or auxillary event for higher number buttons for each state change
	buttonstate = ji.dwButtons;
	for (i=0 ; i < joy_numbuttons ; i++)
	{
		if ( (buttonstate & (1<<i)) && !(joy_oldbuttonstate & (1<<i)) )
		{
			key_index = (i < 4) ? K_JOY1 : K_AUX1;
			Key_Event (key_index + i, true, 0);
		}

		if ( !(buttonstate & (1<<i)) && (joy_oldbuttonstate & (1<<i)) )
		{
			key_index = (i < 4) ? K_JOY1 : K_AUX1;
			Key_Event (key_index + i, false, 0);
		}
	}
	joy_oldbuttonstate = buttonstate;

	if (joy_haspov)
	{
		// convert POV information into 4 bits of state information
		// this avoids any potential problems related to moving from one
		// direction to another without going through the center position
		povstate = 0;
		if(ji.dwPOV != JOY_POVCENTERED)
		{
			if (ji.dwPOV == JOY_POVFORWARD)
				povstate |= 0x01;
			if (ji.dwPOV == JOY_POVRIGHT)
				povstate |= 0x02;
			if (ji.dwPOV == JOY_POVBACKWARD)
				povstate |= 0x04;
			if (ji.dwPOV == JOY_POVLEFT)
				povstate |= 0x08;
		}
		// determine which bits have changed and key an auxillary event for each change
		for (i=0 ; i < 4 ; i++)
		{
			if ( (povstate & (1<<i)) && !(joy_oldpovstate & (1<<i)) )
			{
				Key_Event (K_AUX29 + i, true, 0);
			}

			if ( !(povstate & (1<<i)) && (joy_oldpovstate & (1<<i)) )
			{
				Key_Event (K_AUX29 + i, false, 0);
			}
		}
		joy_oldpovstate = povstate;
	}
}


/* 
=============== 
IN_ReadJoystick
=============== 
*/  
bool IN_ReadJoystick ()
{
	memset (&ji, 0, sizeof(ji));
	ji.dwSize = sizeof(ji);
	ji.dwFlags = joy_flags;

	if (joyGetPosEx (joy_id, &ji) == JOYERR_NOERROR)
		return true;
	else
	{
		// read error occurred
		// turning off the joystick seems too harsh for 1 read error,\
		// but what should be done?
		// Com_Printf ("IN_ReadJoystick: no response\n");
		// joy_avail = false;
		return false;
	}
}


/*
===========
IN_JoyMove
===========
*/
void IN_JoyMove (usercmd_t *cmd)
{
	float	speed, aspeed;
	float	fAxisValue;
	int		i;

	// complete initialization if first time in
	// this is needed as cvars are not available at initialization time
	if( joy_advancedinit != true )
	{
		Joy_AdvancedUpdate_f();
		joy_advancedinit = true;
	}

	// verify joystick is available and that the user wants to use it
	if (!joy_avail || !in_joystick->value)
		return; 
 
	// collect the joystick data, if possible
	if (IN_ReadJoystick () != true)
		return;

	if ( (in_speed.state & 1) ^ (int)cl_run->value)
		speed = 2;
	else
		speed = 1;
	aspeed = speed * cls.frametime;

	// loop through the axes
	for (i = 0; i < JOY_MAX_AXES; i++)
	{
		// get the floating point zero-centered, potentially-inverted data for the current axis
		fAxisValue = (float) *pdwRawValue[i];
		// move centerpoint to zero
		fAxisValue -= 32768.0;

		// convert range from -32768..32767 to -1..1 
		fAxisValue /= 32768.0;

		switch (dwAxisMap[i])
		{
		case AxisForward:
			if ((joy_advanced->value == 0.0) && mlooking)
			{
				// user wants forward control to become look control
				if (fabs(fAxisValue) > joy_pitchthreshold->value)
				{		
					// if mouse invert is on, invert the joystick pitch value
					// only absolute control support here (joy_advanced is false)
					if (m_pitch->value < 0.0)
						cl.viewangles[PITCH] -= (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value;
					else
						cl.viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value;
				}
			}
			else
			{
				// user wants forward control to be forward control
				if (fabs(fAxisValue) > joy_forwardthreshold->value)
					cmd->forwardmove += (fAxisValue * joy_forwardsensitivity->value) * speed * cl_forwardspeed->value;
			}
			break;

		case AxisSide:
			if (fabs(fAxisValue) > joy_sidethreshold->value)
				cmd->sidemove += (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value;
			break;

		case AxisUp:
			if (fabs(fAxisValue) > joy_upthreshold->value)
				cmd->upmove += (fAxisValue * joy_upsensitivity->value) * speed * cl_upspeed->value;
			break;

		case AxisTurn:
			if ((in_strafe.state & 1) || (lookstrafe->value && mlooking))
			{
				// user wants turn control to become side control
				if (fabs(fAxisValue) > joy_sidethreshold->value)
					cmd->sidemove -= (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value;
			}
			else
			{
				// user wants turn control to be turn control
				if (fabs(fAxisValue) > joy_yawthreshold->value)
				{
					if(dwControlMap[i] == JOY_ABSOLUTE_AXIS)
						cl.viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * aspeed * cl_yawspeed->value;
					else
						cl.viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * speed * 180.0;
				}
			}
			break;

		case AxisLook:
			if (mlooking)
			{
				if (fabs(fAxisValue) > joy_pitchthreshold->value)
				{
					// pitch movement detected and pitch movement desired by user
					if(dwControlMap[i] == JOY_ABSOLUTE_AXIS)
						cl.viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value;
					else
						cl.viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * speed * 180.0;
				}
			}
			break;

		default:
			break;
		}
	}
}

/*
=========================================================================
JOYSTICK		END
=========================================================================
*/

void ClearKeys()
{
	in_klook.down[0] = in_klook.down[1] = in_klook.state = 0;
	in_left.down[0] = in_left.down[1] = in_left.state = 0;
	in_right.down[0] = in_right.down[1] = in_right.state = 0;
	in_forward.down[0] = in_forward.down[1] = in_forward.state = 0;
	in_back.down[0] = in_back.down[1] = in_back.state = 0;
	in_lookup.down[0] = in_lookup.down[1] = in_lookup.state = 0;
	in_lookdown.down[0] = in_lookdown.down[1] = in_lookdown.state = 0;
	in_moveleft.down[0] = in_moveleft.down[1] = in_moveleft.state = 0;
	in_moveright.down[0] = in_moveright.down[1] = in_moveright.state = 0;
	in_strafe.down[0] = in_strafe.down[1] = in_strafe.state = 0;
	in_speed.down[0] = in_speed.down[1] = in_speed.state = 0;
	in_use.down[0] = in_use.down[1] = in_use.state = 0;
	in_attack.down[0] = in_attack.down[1] = in_attack.state = 0;
///	in_flashlight.down[0] = in_flashlight.down[1] = in_flashlight.state = 0;	allow flashlight work in the next map
	in_zoom.down[0] = in_zoom.down[1] = in_zoom.state = 0;
	in_up.down[0] = in_up.down[1] = in_up.state = 0;
	in_down.down[0] = in_down.down[1] = in_down.state = 0;
}


/*
==================
CM_LoadMap

Loads in the map and all submodels
==================
*/
cmodel_t *CM_LoadMap (char *name, bool clientload, unsigned *checksum)
{
	unsigned		*buf;
	int				i;
	dheader_t		header;
	int				length;
	static unsigned	last_checksum;

	map_noareas = Cvar_Get ("map_noareas", "0", 0);
	map_noareas->help = "disable using map areas (for developers).";
	map_nochecklit = Cvar_Get ("map_nochecklit", "0", 0);
	map_nochecklit->help = "disable checksums for .lit files.";

	if (clientload)
	{
		curlight = NULL;
		curlink = NULL;
		curfog = NULL;
		curEFXzone = NULL;
		curtexinfo = NULL;
		curdecal = NULL;
		curbrush = NULL;
		curemit = NULL;
		curmodel = NULL;
		render_disable = le_mode = trace_decal = trace_light = trace_model = trace_emit = false;
		Cl_UpdateEditorCvars(ED_LIGHT | ED_EMIT | ED_DECAL | ED_FOG | ED_EFXZONE | ED_MODEL | ED_BRUSH | ED_AMBIENT);

		Cvar_SetValue ("scr_fps_counter_min", 0);
		Cvar_SetValue ("scr_fps_counter_max", 0);

		ClearKeys();	//	Berserker (experimental): сбросим все клавиши чтобы не было бага "залипания" при заходе на следующую карту...
	}

	if (name && name[0])	/// PVS-Studio
	{
		if (!strcmp(map_name, name) && (clientload || !Cvar_VariableValue("flushmap")))
		{
			*checksum = last_checksum;
			if (!clientload)
			{
///				if(!areaportals_from_server)
				Memset(portalopen, 0, sizeof(portalopen));

				FloodAreaConnections();
			}
			return &map_cmodels[0];		// still have the right version
		}
	}

	// free old stuff
	numplanes = 0;
	numnodes = 0;
	numleafs = 0;
	numcmodels = 0;
	numvisibility = 0;
	numentitychars = 0;

	//r1: fix for missing terminators on some badly compiled maps
	Memset (map_entitystring, 0, sizeof(map_entitystring));
	Memset (map_name, 0, sizeof(map_name));

	if (!name || !name[0])
	{
		numleafs = 1;
		numclusters = 1;
		numareas = 1;
		*checksum = 0;
		render_disable = true;		// Berserker: little hack - запретим рендерить мир если CM_ структуры не определены
		return &map_cmodels[0];			// cinematic servers won't have anything at all
	}

	//
	// load the file
	//
	length = FS_LoadFile (name, (void **)&buf);
	if (!buf || length<=0)
	{
		if (buf)
			Z_Free(buf);
		Com_Error (ERR_DROP, "Couldn't load %s", name);
	}

	last_checksum = LittleLong (Com_BlockChecksum (buf, length));
///	*checksum = last_checksum;

	header = *(dheader_t *)buf;
	for (i=0 ; i<sizeof(dheader_t)/4 ; i++)
		((int *)&header)[i] = LittleLong ( ((int *)&header)[i]);

	if (header.version != BSPVERSION)
		Com_Error (ERR_DROP, "CM_LoadMap: %s has wrong version number (%i should be %i)", name, header.version, BSPVERSION);

	cmod_base = (byte *)buf;

	// load into heap
	CMod_LoadSurfaces (&header.lumps[LUMP_TEXINFO]);
	CMod_LoadLeafs (&header.lumps[LUMP_LEAFS]);
	CMod_LoadLeafBrushes (&header.lumps[LUMP_LEAFBRUSHES]);
	CMod_LoadPlanes (&header.lumps[LUMP_PLANES]);
	CMod_LoadBrushes (&header.lumps[LUMP_BRUSHES]);
	CMod_LoadBrushSides (&header.lumps[LUMP_BRUSHSIDES]);
	CMod_LoadSubmodels (&header.lumps[LUMP_MODELS]);
	CMod_LoadNodes (&header.lumps[LUMP_NODES]);
	CMod_LoadAreas (&header.lumps[LUMP_AREAS]);
	CMod_LoadAreaPortals (&header.lumps[LUMP_AREAPORTALS]);
	CMod_LoadVisibility (&header.lumps[LUMP_VISIBILITY]);
	unsigned chk = CMod_LoadEntityString (&header.lumps[LUMP_ENTITIES], name);
	if (!net_compatibility->value && !map_nochecklit->value)
		last_checksum += chk;
	*checksum = last_checksum;

	Z_Free (buf);

	CM_InitBoxHull ();

///	if(!areaportals_from_server)
		Memset (portalopen, 0, sizeof(portalopen));

///	areaportals_from_server = false;
	FloodAreaConnections ();

	strcpy (map_name, name);

	return &map_cmodels[0];
}


/*
===============
SV_CreateAreaNode

Builds a uniformly subdivided tree for the given world size
===============
*/
areanode_t *SV_CreateAreaNode (int depth, vec3_t mins, vec3_t maxs)
{
	areanode_t	*anode;
	vec3_t		size;
	vec3_t		mins1, maxs1, mins2, maxs2;

	anode = &sv_areanodes[sv_numareanodes];
	sv_numareanodes++;

	ClearLink (&anode->trigger_edicts);
	ClearLink (&anode->solid_edicts);
	
	if (depth == AREA_DEPTH)
	{
		anode->axis = -1;
		anode->children[0] = anode->children[1] = NULL;
		return anode;
	}
	
	VectorSubtract (maxs, mins, size);
	if (size[0] > size[1])
		anode->axis = 0;
	else
		anode->axis = 1;
	
	anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]);
	VectorCopy (mins, mins1);	
	VectorCopy (mins, mins2);	
	VectorCopy (maxs, maxs1);	
	VectorCopy (maxs, maxs2);	
	
	maxs1[anode->axis] = mins2[anode->axis] = anode->dist;
	
	anode->children[0] = SV_CreateAreaNode (depth+1, mins2, maxs2);
	anode->children[1] = SV_CreateAreaNode (depth+1, mins1, maxs1);

	return anode;
}


void SV_ClearWorld ()
{
	Memset (sv_areanodes, 0, sizeof(sv_areanodes));
	sv_numareanodes = 0;
	SV_CreateAreaNode (0, sv.models[1]->mins, sv.models[1]->maxs);
}


int		CM_NumInlineModels ()
{
	return numcmodels;
}


char	*CM_EntityString ()
{
	return map_entitystring;
}


/*
================
SV_CreateBaseline

Entity baselines are used to compress the update messages
to the clients -- only the fields that differ from the
baseline will be transmitted
================
*/
void SV_CreateBaseline ()
{
	edict_t			*svent;
	int				entnum;	

	for (entnum = 1; entnum < ge->num_edicts ; entnum++)
	{
		svent = EDICT_NUM(entnum);
		if (!svent->inuse)
			continue;
		if (!svent->s.modelindex && !svent->s.sound && !svent->s.effects)
			continue;
		svent->s.number = entnum;

		//
		// take current state as baseline
		//
		VectorCopy (svent->s.origin, svent->s.old_origin);
		sv.baselines[entnum] = svent->s;
	}
}


/*
===================
CM_ReadPortalState

Reads the portal state from a savegame file
and recalculates the area connections
===================
*/
void	CM_ReadPortalState (FILE *f)
{
	FS_Read (portalopen, sizeof(portalopen), f);
	FloodAreaConnections ();
}


void SV_ReadLevelFile ()
{
	char	name[MAX_OSPATH];
	FILE	*f;
	char	*savdir;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_DPrintf("SV_ReadLevelFile()\n");

	Com_sprintf (name, sizeof(name), "%s/%s/current/%s.sv2", FS_Gamedir(), savdir, sv.name);
	f = FS_Fopen(name, "rb");
	if (!f)
	{
		Com_Printf ("^1Failed to open %s\n", name);
		return;
	}
	FS_Read (sv.configstrings, sizeof(sv.configstrings), f);
	CM_ReadPortalState (f);
	fclose (f);

	Com_sprintf (name, sizeof(name), "%s/%s/current/%s.sav", FS_Gamedir(), savdir, sv.name);
	ge->ReadLevel (name);
}


void SV_CheckForSavegame ()
{
	char		name[MAX_OSPATH];
	FILE		*f;
	int			i;
	char		*savdir;

	if (sv_noreload->value)
		return;

	if (Cvar_VariableValue ("deathmatch"))
		return;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_sprintf (name, sizeof(name), "%s/%s/current/%s.sav", FS_Gamedir(), savdir, sv.name);
	f = FS_Fopen(name, "rb");
	if (!f)
		return;		// no savegame

	fclose (f);

	SV_ClearWorld ();

	needsToLoadClientEntities = 1;

	// get configstrings and areaportals
	SV_ReadLevelFile ();

	if (!sv.loadgame)
	{	// coming back to a level after being in a different
		// level, so run it for ten seconds

		// rlava2 was sending too many lightstyles, and overflowing the
		// reliable data. temporarily changing the server state to loading
		// prevents these from being passed down.
		server_state_t		previousState;		// PGM

		previousState = sv.state;				// PGM
		sv.state = ss_loading;					// PGM
		for (i=0 ; i<100 ; i++)
			ge->RunFrame (maxclients->value==1 ? sv_stopclock->value : 0);

		sv.state = previousState;				// PGM
	}
}


/*
================
SV_SpawnServer

Change the server to a new map, taking all connected
clients along with it.

================
*/
void SV_SpawnServer (char *server, char *spawnpoint, server_state_t serverstate, bool attractloop, bool loadgame)
{
	int			i;
	unsigned	checksum;

///	if (!attractloop && net_compatibility->value)
///		Com_Error(ERR_DROP, "You can't run the local server,\nwhile enabled the Quake2 net protocol");

	if (attractloop)
		Cvar_Set ("paused", "0");

	Com_Printf ("------- Server Initialization -------\n");

	if(dedicated->value)
		Com_Printf ("SpawnServer: %s\n",server);

	if (sv.demofile)
		fclose (sv.demofile);

	svs.spawncount++;		// any partially connected client will be
							// restarted
	sv.state = ss_dead;
	Com_SetServerState (sv.state);

	// wipe the entire per-level structure
	Memset (&sv, 0, sizeof(sv));
	svs.realtime = 0;
	sv.loadgame = loadgame;
	sv.attractloop = attractloop;

	// save name for levels that don't set message
	strcpy (sv.configstrings[CS_NAME], server);
	if (Cvar_VariableValue ("deathmatch"))
	{
		sprintf(sv.configstrings[CS_AIRACCEL], "%g", sv_airaccelerate->value);
		pm_airaccelerate = sv_airaccelerate->value;
	}
	else
	{
		strcpy(sv.configstrings[CS_AIRACCEL], "0");
		pm_airaccelerate = 0;
	}

	SZ_Init (&sv.multicast, sv.multicast_buf, sizeof(sv.multicast_buf));

///	strcpy (sv.name, server);	// berserker: продублировано ниже ))

	// leave slots at start for clients only
	for (i=0 ; i<maxclients->value ; i++)
	{
		// needs to reconnect
		if (svs.clients[i].state == cs_spawned/*> cs_connected*/)	// FUCK
			svs.clients[i].state = cs_connected;
		svs.clients[i].lastframe = -1;
	}

	sv.time = 1000;
	
	strcpy (sv.name, server);
	strcpy (sv.configstrings[CS_NAME], server);

	if (serverstate != ss_game)
		sv.models[1] = CM_LoadMap ("", false, &checksum);	// no real map
	else
	{
		Com_sprintf (sv.configstrings[CS_MODELS+1],sizeof(sv.configstrings[CS_MODELS+1]), "maps/%s.bsp", server);
		sv.models[1] = CM_LoadMap (sv.configstrings[CS_MODELS+1], false, &checksum);
		CM_SpawnEntities(CM_EntityString(), true);	/// Berserker: load the lightstyles
	}
	Com_sprintf (sv.configstrings[CS_MAPCHECKSUM],sizeof(sv.configstrings[CS_MAPCHECKSUM]), "%i", checksum);

	// clear physics interaction links
	SV_ClearWorld ();

	for (i=1 ; i<CM_NumInlineModels() ; i++)
	{
		Com_sprintf (sv.configstrings[CS_MODELS+1+i], sizeof(sv.configstrings[CS_MODELS+1+i]), "*%i", i);
		sv.models[i+1] = CM_InlineModel (sv.configstrings[CS_MODELS+1+i]);
	}

// spawn the rest of the entities on the map

	// precache and static commands can be issued during
	// map initialization
	sv.state = ss_loading;
	Com_SetServerState (sv.state);

	// load and spawn all entities
	ge->SpawnEntities ( sv.name, CM_EntityString(), spawnpoint );

	// run two frames to allow everything to settle
	ge->RunFrame (maxclients->value==1 ? sv_stopclock->value : 0);
	ge->RunFrame (maxclients->value==1 ? sv_stopclock->value : 0);

	// all precaches are complete
	sv.state = serverstate;
	Com_SetServerState (sv.state);

	// create a baseline for more efficient communications
	SV_CreateBaseline ();

	// check for a savegame
	SV_CheckForSavegame ();

	// set serverinfo variable
	Cvar_FullSet ("mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET);

/// Berserker
	if (Cvar_VariableValue ("deathmatch"))
	{
		if(Cvar_VariableValue ("dedicated"))
			Com_Printf ("Deathmatch.\n");
		else
			Com_Printf ("Deathmatch server: %s\n", sv.name);

		if (Cvar_VariableValue("cheats"))
			Com_Printf ("Cheats enabled.\n");
	}
	else if (Cvar_VariableValue ("coop"))
	{
		if(Cvar_VariableValue ("dedicated"))
			Com_Printf ("Cooperative.\n");
		else
			Com_Printf ("Coop server: %s\n", sv.name);

		Com_Printf ("Skill: %i\n", (int)Cvar_VariableValue("skill"));
	}
	else
	{
		Com_Printf ("Server: %s\n", sv.name);
		Com_Printf ("Skill: %i\n", (int)Cvar_VariableValue("skill"));
	}

	Com_Printf ("Max clients: %i\n", (int)maxclients->value);

	if(Cvar_VariableValue("net_compatibility"))
		Com_Printf ("Using Quake2 net protocol\n");
	else
		Com_Printf ("Using Berserker's net protocol\n");

	Com_Printf ("-------------------------------------\n");
}



/*
======================
SV_Map

  the full syntax is:

  map [*]<map>$<startspot>+<nextserver>

command from the console or progs.
Map can also be a .cin, .pcx, .tga, .jpg, .png, .dds or .dem file
Nextserver is used to allow a cinematic to play, then proceed to
another level:

	map tram.pcx+jail_e3
======================
*/
void SV_Map (bool attractloop, char *levelstring, bool loadgame)
{
	char	level[MAX_QPATH];
	char	*ch;
	int		l;
	char	spawnpoint[MAX_QPATH];

	effectsInited = false;
	sv.loadgame = loadgame;
	sv.attractloop = attractloop;

	if (sv.state == ss_dead && !sv.loadgame)
		SV_InitGame ();	// the game is just starting

	strcpy (level, levelstring);

	// if there is a + in the map, set nextserver to the remainder
	ch = strstr(level, "+");
	if (ch)
	{
		*ch = 0;
		Cvar_Set ("nextserver", va("gamemap \"%s\"", ch+1));
	}
	else
		Cvar_Set ("nextserver", "");

	//ZOID special hack for end game screen in coop mode
	if (Cvar_VariableValue ("coop") && !Q_strcasecmp(level, "victory.pcx"))
		Cvar_Set ("nextserver", "gamemap \"*base1\"");

	// if there is a $, use the remainder as a spawnpoint
	ch = strstr(level, "$");
	if (ch)
	{
		*ch = 0;
		strcpy (spawnpoint, ch+1);
	}
	else
		spawnpoint[0] = 0;

	// skip the end-of-unit flag if necessary
	if (level[0] == '*')
		strcpy (level, level+1);

	l = strlen(level);
	if (l<=4)
		goto go;
	if (!Q_strcasecmp (level+l-4, ".cin") )
	{
		SCR_BeginLoadingPlaque ();			// for local system
		SV_BroadcastCommand ("changing\n");
		SV_SpawnServer (level, spawnpoint, ss_cinematic, attractloop, loadgame);
	}
	else if (!net_compatibility->value && !Q_strcasecmp (level+l-4, ".dem"))
	{
		SCR_BeginLoadingPlaque ();			// for local system
		SV_BroadcastCommand ("changing\n");
		SV_SpawnServer (level, spawnpoint, ss_demo, attractloop, loadgame);
	}
	else if (net_compatibility->value && !Q_strcasecmp (level+l-4, ".dm2"))
	{
		SCR_BeginLoadingPlaque ();			// for local system
		SV_BroadcastCommand ("changing\n");
		SV_SpawnServer (level, spawnpoint, ss_demo, attractloop, loadgame);
	}
	else if ((!Q_strcasecmp (level+l-4, ".png") || !Q_strcasecmp (level+l-4, ".tga") || !Q_strcasecmp (level+l-4, ".pcx") || !Q_strcasecmp (level+l-4, ".jpg") || !Q_strcasecmp (level+l-4, ".dds")))
	{
		SCR_BeginLoadingPlaque ();			// for local system
		SV_BroadcastCommand ("changing\n");
		SV_SpawnServer (level, spawnpoint, ss_pic, attractloop, loadgame);
	}
	else
	{
go:		SCR_BeginLoadingPlaque ();			// for local system
		SV_BroadcastCommand ("changing\n");
		SV_SendClientMessages ();
		SV_SpawnServer (level, spawnpoint, ss_game, attractloop, loadgame);
		Cbuf_CopyToDefer ();
	}

	SV_BroadcastCommand ("reconnect\n");

	if (ge && ((int)sv_autoSavePeriod->value >= 10))
	{
		Cvar_ForceSet("autoSaveTimer", va("%i", Sys_Milliseconds()/1000 + (int)sv_autoSavePeriod->value));		// reset the timer
		Com_Printf("^2AutoSaving will be executed every %i seconds\n", (int)sv_autoSavePeriod->value);
	}
}


/*
==================
SV_GameMap_f

Saves the state of the map just being exited and goes to a new map.

If the initial character of the map string is '*', the next map is
in a new unit, so the current savegame directory is cleared of
map files.

Example:

*inter.pcx+jail

Clears the archived maps, plays the inter.cin cinematic, then
goes to map jail.bsp.
==================
*/
void SV_GameMap_f ()
{
	char		*map;
	int			i;
	client_t	*cl;
	bool		*savedInuse;
	char		*savdir;

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3USAGE: gamemap <map>\n");
		return;
	}

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_DPrintf("SV_GameMap(%s)\n", Cmd_Argv(1));

	FS_CreatePath (va("%s/%s/current/", FS_Gamedir(), savdir));

	// check for clearing the current savegame
	map = Cmd_Argv(1);
	if (map[0] == '*')
	{
		// wipe all the *.sav files
		SV_WipeSavegame ("current");
	}
	else
	{	// save the map just exited
		if (sv.state == ss_game)
		{
			if ( !dedicated->value )
				WriteClientFile(false);

			// clear all the client inuse flags before saving so that
			// when the level is re-entered, the clients will spawn
			// at spawn points instead of occupying body shells
			savedInuse = (bool*) Z_Malloc(maxclients->value * sizeof(bool), true);
			for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++)
			{
				savedInuse[i] = cl->edict->inuse;
				cl->edict->inuse = false;
			}

			SV_WriteLevelFile ();

			// we must restore these for clients to transfer over correctly
			for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++)
				cl->edict->inuse = savedInuse[i];
			Z_Free (savedInuse);
		}
	}

	saved_shot_dir[0] = 0;

	// start up the next map
	SV_Map (false, Cmd_Argv(1), false );

	// archive server state
	strncpy (svs.mapcmd, Cmd_Argv(1), sizeof(svs.mapcmd)-1);

	// copy off the level to the autosave slot
	if (!dedicated->value && !Cvar_VariableValue("deathmatch"))
	{
		SV_WriteServerFile (true);
		SV_CopySaveGame ("current", "save0");
		Cvar_ForceSetValue("com_quickSaveSlot", 0);
	}
}


/*
==================
SV_Map_f

Goes directly to a given map without any savegame archiving.
For development work
==================
*/
void SV_Map_f ()
{
	char	*map;
	char	expanded[MAX_QPATH];

	if (Cmd_Argc() < 2)
	{
		Com_Printf ("^3USAGE: map <map, demo or picture>\n");
		return;
	}

	// if not a pic, demo or cinematic, check to make sure the level exists
	map = Cmd_Argv(1);
	if (!strstr (map, "."))
	{
		Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map);
		if (FS_LoadFile (expanded, NULL) == -1)
		{
			Com_Printf ("^1Can't find %s\n", expanded);
			return;
		}
	}

	sv.state = ss_dead;		// don't save current level when changing
	SV_WipeSavegame("current");
	SV_GameMap_f ();
}


/*
==================
SV_DemoMap_f

Puts the server in demo mode on a specific map

Berserker:
Пусть команда "map" - для загрузки карт и демофайлов (только с расширением .dem),
а спец.команда "demo" - только для демофайлов (можно и без расширения .dem)
FIXED BUG: "demo q2dm1" - загружает карту и переводит сервер в деморежим. Прикольный глюк!
==================
*/
void SV_DemoMap_f ()
{
	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3USAGE: %s <filename>\n", Cmd_Argv(0));
		return;
	}

	char	demofile[MAX_QPATH];
	char	*ch;
	char	*demoname = Cmd_Argv(1);

	ch = strstr(demoname, ".cin");
	if (ch)
		strcpy(demofile, demoname);
	else
	{
		if(net_compatibility->value)
		{
			ch = strstr(demoname, ".dm2");
			if (ch)
				strcpy(demofile, demoname);
			else
				Com_sprintf (demofile, sizeof(demofile), "%s.dm2", demoname);
		}
		else
		{
			ch = strstr(demoname, ".dem");
			if (ch)
				strcpy(demofile, demoname);
			else
				Com_sprintf (demofile, sizeof(demofile), "%s.dem", demoname);
		}
	}

	saved_shot_dir[0] = 0;

	SV_Map (true, demofile, false );
}


/*
==============
SV_ServerRecord_f

Begins server demo recording.  Every entity and every message will be
recorded, but no playerinfo will be stored.  Primarily for demo merging.
==============
*/
void SV_ServerRecord_f ()
{
	char	name[MAX_OSPATH];
	byte	buf_data[32768];
	sizebuf_t	buf;
	int		len;
	int		i;

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3USAGE: serverrecord <demoname>\n");
		return;
	}

	if (svs.demofile)
	{
		Com_Printf ("^3Already recording.\n");
		return;
	}

	if (sv.state != ss_game)
	{
		Com_Printf ("^3You must be in a level to record.\n");
		return;
	}

	//
	// open the demo file
	//
	Com_sprintf (name, sizeof(name), "%s/demos/%s.srv", FS_Gamedir(), Cmd_Argv(1));

	Com_Printf ("recording to %s.\n", name);
	FS_CreatePath (name);
	svs.demofile = FS_Fopen (name, "wb");
	if (!svs.demofile)
	{
		Com_Printf ("^1ERROR: couldn't open.\n");
		return;
	}

	// setup a buffer to catch all multicasts
	SZ_Init (&svs.demo_multicast, svs.demo_multicast_buf, sizeof(svs.demo_multicast_buf));

	//
	// write a single giant fake message with all the startup info
	//
	SZ_Init (&buf, buf_data, sizeof(buf_data));

	//
	// serverdata needs to go over for all types of servers
	// to make sure the protocol is right, and to set the gamedir
	//
	// send the serverdata
	MSG_WriteByte (&buf, svc_serverdata);
	if(net_compatibility->value)
		MSG_WriteLong (&buf, OLD_PROTOCOL_VERSION);
	else
		MSG_WriteLong (&buf, PROTOCOL_VERSION);
	MSG_WriteLong (&buf, svs.spawncount);
	// 2 means server demo
	MSG_WriteByte (&buf, 2);	// demos are always attract loops
	MSG_WriteString (&buf, Cvar_VariableString ("gamedir"));
	MSG_WriteShort (&buf, -1);
	// send full levelname
	MSG_WriteString (&buf, sv.configstrings[CS_NAME]);

	int	max_configstrings;
	if (net_compatibility->value)
		max_configstrings = MAX_CONFIGSTRINGS_Q2;
	else
		max_configstrings = MAX_CONFIGSTRINGS_BERS;
	for (i=0 ; i<max_configstrings ; i++)
		if (sv.configstrings[i][0])
		{
			MSG_WriteByte (&buf, svc_configstring);
			MSG_WriteShort (&buf, i);
			MSG_WriteString (&buf, sv.configstrings[i]);
		}

	// write it to the demo file
	Com_DPrintf ("signon message length: %i\n", buf.cursize);
	len = LittleLong (buf.cursize);
	fwrite (&len, 4, 1, svs.demofile);
	fwrite (buf.data, buf.cursize, 1, svs.demofile);

	// the rest of the demo file will be individual frames
}

/*
==============
SV_ServerStop_f

Ends server demo recording
==============
*/
void SV_ServerStop_f ()
{
	if (!svs.demofile)
	{
		Com_Printf ("^3Not doing a serverrecord.\n");
		return;
	}
	fclose (svs.demofile);
	svs.demofile = NULL;
	Com_Printf ("Recording completed.\n");
}

/*
================
R_ResampleShotLerpLine
from DarkPlaces
================
*/
void R_ResampleShotLerpLine (byte *in, byte *out, int inwidth, int outwidth) 
{ 
	int j, xi, oldx = 0, f, fstep, l1, l2, endx;

	fstep = (int) (inwidth*65536.0f/outwidth); 
	endx = (inwidth-1); 
	for (j = 0,f = 0; j < outwidth; j++, f += fstep) 
	{ 
		xi = (int) f >> 16; 
		if (xi != oldx) 
		{ 
			in += (xi - oldx) * 3; 
			oldx = xi; 
		} 
		if (xi < endx) 
		{ 
			l2 = f & 0xFFFF; 
			l1 = 0x10000 - l2; 
			*out++ = (byte) ((in[0] * l1 + in[3] * l2) >> 16);
			*out++ = (byte) ((in[1] * l1 + in[4] * l2) >> 16); 
			*out++ = (byte) ((in[2] * l1 + in[5] * l2) >> 16); 
		} 
		else // last pixel of the line has no pixel to lerp to 
		{ 
			*out++ = in[0]; 
			*out++ = in[1]; 
			*out++ = in[2]; 
		} 
	} 
}


/*
================
R_ResampleShot
based on Knightmare's code
================
*/
void R_ResampleShot (byte *indata, int inwidth, int inheight, byte *outdata, int outwidth, int outheight) 
{ 
	int i, j, yi, oldy, f, fstep, l1, l2, endy = (inheight-1);

	byte *inrow, *out, *row1, *row2;
	out = outdata;
	fstep = (int) (inheight*65536.0f/outheight);

	row1 = (byte*)Z_Malloc(outwidth*3, false);
	if (!row1)
		return;

	row2 = (byte*)Z_Malloc(outwidth*3, false);
	if (!row2)
	{
		Z_Free(row1);
		return;
	}

	inrow = indata;
	oldy = 0;
	R_ResampleShotLerpLine (inrow, row1, inwidth, outwidth);
	R_ResampleShotLerpLine (inrow + inwidth*3, row2, inwidth, outwidth);
	for (i = 0, f = 0; i < outheight; i++,f += fstep)
	{
		yi = f >> 16;
		if (yi != oldy)
		{
			inrow = (byte *)indata + inwidth*3*yi;
			if (yi == oldy+1)
				Memcpy(row1, row2, outwidth*3);
			else
				R_ResampleShotLerpLine (inrow, row1, inwidth, outwidth);

			if (yi < endy)
				R_ResampleShotLerpLine (inrow + inwidth*3, row2, inwidth, outwidth);
			else
				Memcpy(row2, row1, outwidth*3);
			oldy = yi;
		}
		if (yi < endy)
		{
			l2 = f & 0xFFFF;
			l1 = 0x10000 - l2;
			for (j = 0;j < outwidth;j++)
			{
				*out++ = (byte) ((*row1++ * l1 + *row2++ * l2) >> 16);
				*out++ = (byte) ((*row1++ * l1 + *row2++ * l2) >> 16);
				*out++ = (byte) ((*row1++ * l1 + *row2++ * l2) >> 16);
			}
			row1 -= outwidth*3;
			row2 -= outwidth*3;
		}
		else // last line has no pixels to lerp to
		{
			for (j = 0;j < outwidth;j++)
			{
				*out++ = *row1++;
				*out++ = *row1++;
				*out++ = *row1++;
			}
			row1 -= outwidth*3;
		}
	}
	Z_Free(row2);
	Z_Free(row1);
} 

/*
================== 
R_ScaledScreenshot
based on Knightmare's code
================== 
*/
void R_ScaledScreenshot (char *name, byte *saveshotdata, int w, int h)
{
	FILE	*f;
	byte	*buffer;

	f = FS_Fopen(name, "wb");
	if (!f)
		Com_Printf ("^1R_ScaledScreenshot: Couldn't create %s\n", name); 

	int	saveshotsize_x = 640;
	int	saveshotsize_y = 480;
	int c = saveshotsize_x * saveshotsize_y * 3 + 18;

	buffer = (byte*) Z_Malloc(c, true);
	buffer[2] = 2;		// uncompressed type
	buffer[12] = saveshotsize_x & 255;
	buffer[13] = saveshotsize_x >> 8;
	buffer[14] = saveshotsize_y & 255;
	buffer[15] = saveshotsize_y >> 8;
	buffer[16] = 24;	// pixel size

	// Resize grabbed screen
	R_ResampleShot(saveshotdata, w, h, buffer + 18, saveshotsize_x, saveshotsize_y);

	fwrite (buffer, 1, c, f);
	fclose (f);

	Z_Free (buffer);
}

/*
================== 
R_GrabScreen
based on Knightmare's code
==================
*/
void R_GrabScreen(char *dir)
{
	int		w, h;
	char	name[MAX_OSPATH];
	char	*savdir;

	w = vid.width & 0xFFFFFFF0;		/// FIX for 1366x768
	h = vid.height;

	// Allocate room for a copy of the framebuffer
	byte *saveshotdata = (byte*)Z_Malloc(w * h * 3, false);
	if (!saveshotdata)
		return;

	// Read the framebuffer into our storage
	glReadPixels(0, 0, w, h, GL_BGR, GL_UNSIGNED_BYTE, saveshotdata);

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_sprintf (name, sizeof(name), "%s/%s/%s/shot.tga", FS_Gamedir(), savdir, dir);

	if (draw_mapshot)
	{
		R_FreePic(draw_mapshot->name);
		draw_mapshot = NULL;
	}

	Com_sprintf (name, sizeof(name), "%s/%s/%s/shot.tga", FS_Gamedir(), savdir, dir);
	R_ScaledScreenshot(name, saveshotdata, w, h);

	Z_Free(saveshotdata);
}

/*
===============
R_FreePic
by Knightmare
Frees a single pic
===============
*/
void R_FreePic (char *name)
{
	int		i;
	image_t	*image;
	unsigned hash = Com_HashKey(name);

	for (i=0, image=gltextures; i<numgltextures; i++, image++)
	{
		if (!image->registration_sequence)
			continue;		// free image_t slot
		if (image->type != it_pic)
			continue;		// only free pics
		if (image->hash != hash)
			continue;
		if (!b_stricmp(name, image->name))
		{
			// free it
			glDeleteTextures (1, &image->texnum);
			Memset (image, 0, sizeof(*image));
			return; //we're done here
		}
	}
}

int SV_NumClients()
{
	int	i, count;

	if (!svs.initialized)
		return 0;

	for (i=0, count = 0 ; i<maxclients->value ; i++)
		if (svs.clients[i].state == cs_spawned)
			count++;

	return count;
}

void SV_Savegame_f ()
{
	char	*dir;

	if (sv.state != ss_game)
	{
		Com_Printf ("^3You must be in a game to save.\n");
		return;
	}

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3USAGE: save <directory>\n");
		return;
	}

	if (!strcmp (Cmd_Argv(1), "current"))
	{
		Com_Printf ("^1Can't save to 'current'\n");
		return;
	}

	if (Cvar_VariableValue("deathmatch"))
	{
		Com_Printf ("^1Can't savegame in a deathmatch\n");
		return;
	}

	/// убрал проверку на кол-во клиентов, т.к. клиенты попросили ))
	/// нестабильно...
///	if (SV_NumClients() != 1)
///	{
///		Com_Printf ("^1Can't savegame in a multiplayer\n");
///		return;
///	}

	if (maxclients->value == 1 && svs.clients[0].edict->client->ps.stats[STAT_HEALTH] <= 0)
	{
		Com_Printf ("\n^1Can't savegame while dead!\n");
		return;
	}

	dir = Cmd_Argv(1);
	if (strstr (dir, "..") || strstr (dir, "/") || strstr (dir, "\\") )
	{
		Com_Printf ("^1Bad savedir.\n");
		return;
	}

	if (!strcmp (dir, "quick"))
	{	/// Berserker: hack
		cvar_t *cv = Cvar_Get ("com_quickSaveSlot", "-1", CVAR_NOSET | CVAR_ARCHIVE );
		if (cv->value == MAX_SAVEGAMES-1)	/// max quick save slots = MAX_SAVEGAMES
			Cvar_ForceSetValue("com_quickSaveSlot", 0);
		else
			Cvar_ForceSetValue("com_quickSaveSlot", cv->value + 1);
		dir = va("quick%i", (int)cv->value);
	}

	needsToDeferredShot[0] = 0;
	if ( !dedicated->value )
	{
#if 0
		if (!strcmp(Cmd_Argv(1), "quick") )
			R_GrabScreen("current");
		else
			Com_sprintf(needsToDeferredShot, sizeof(needsToDeferredShot), dir/*Cmd_Argv(1)*/);
#else
		Com_sprintf(needsToDeferredShot, sizeof(needsToDeferredShot), dir/*Cmd_Argv(1)*/);
#endif
	}

	if (strcmp (dir/*Cmd_Argv(1)*/, "server"))
		Com_Printf ("Saving game...\n");

	if ( !dedicated->value )
		WriteClientFile(false);

	// archive current level, including all client edicts.
	// when the level is reloaded, they will be shells awaiting
	// a connecting client
	SV_WriteLevelFile ();

	// save server state
	SV_WriteServerFile (false);

	// copy it off
	SV_CopySaveGame ("current", dir);

	Com_Printf ("Done.\n");
}

void SV_ReadServerFile ()
{
	FILE	*f;
	char	name[MAX_OSPATH], string[128];
	char	comment[32];
	char	mapcmd[MAX_TOKEN_CHARS];
	char	*savdir;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_DPrintf("SV_ReadServerFile()\n");

	Com_sprintf (name, sizeof(name), "%s/%s/current/server.ssv", FS_Gamedir(), savdir);
	f = FS_Fopen(name, "rb");
	if (!f)
	{
		Com_Printf ("^1Couldn't read %s\n", name);
		return;
	}
	// read the comment field
	FS_Read (comment, sizeof(comment), f);

	// read the mapcmd
	FS_Read (mapcmd, sizeof(mapcmd), f);

	// read all CVAR_LATCH cvars
	// these will be things like coop, skill, deathmatch, etc
	while (1)
	{
		if (!fread (name, 1, sizeof(name), f))
			break;
		FS_Read (string, sizeof(string), f);
		Com_DPrintf ("Set %s = %s\n", name, string);
		Cvar_ForceSet (name, string);
	}

	fclose (f);

	// start a new game fresh with new cvars
	SV_InitGame ();

	strcpy (svs.mapcmd, mapcmd);

	// read game state
	Com_sprintf (name, sizeof(name), "%s/%s/current/game.ssv", FS_Gamedir(), savdir);
	ge->ReadGame (name);
}


void SV_Loadgame_f ()
{
	char	name[MAX_OSPATH];
	FILE	*f;
	char	*dir;
	char	*savdir;

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3USAGE: load <directory>\n");
		return;
	}

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	Com_Printf ("Loading game...\n");

	dir = Cmd_Argv(1);
	if (strstr (dir, "..") || strstr (dir, "/") || strstr (dir, "\\") )
	{
		Com_Printf ("^1Bad savedir.\n");
		return;
	}

	cvar_t	*cv = Cvar_Get ("com_quickSaveSlot", "-1", CVAR_NOSET | CVAR_ARCHIVE );
	if (cv->value == -1)
	{
		Com_Printf ("^1You must to do 'save quick' before 'load quick'.\n");
		return;
	}

	int	slot = 99999;
	if (!strcmp(dir, "quick"))
		slot = cv->value;
	else if (!strcmp(dir, "quick-1"))
		slot = cv->value - 1;
	else if (!strcmp(dir, "quick-2"))
		slot = cv->value - 2;
	else if (!strcmp(dir, "quick-3"))
		slot = cv->value - 3;
	else if (!strcmp(dir, "quick-4"))
		slot = cv->value - 4;
	if (slot != 99999)
	{
		if (slot < 0)
			slot = 5 + slot;
		dir = va("quick%i", slot);
	}

	// make sure the server.ssv file exists
	Com_sprintf (name, sizeof(name), "%s/%s/%s/server.ssv", FS_Gamedir(), savdir, dir/*Cmd_Argv(1)*/);
	f = FS_Fopen(name, "rb");
	if (!f)
	{
		Com_Printf ("^1No such savegame: %s\n", name);
		return;
	}
	fclose (f);

	if (slot != 99999)
		Cvar_ForceSetValue("com_quickSaveSlot", (float)slot);

	if (!strncmp(dir/*Cmd_Argv(1)*/, "quick", 5) || !strncmp(dir/*Cmd_Argv(1)*/, "save", 4))
		Com_sprintf(saved_shot_dir, sizeof(saved_shot_dir), dir/*Cmd_Argv(1)*/);
	else
		saved_shot_dir[0] = 0;

	SV_CopySaveGame (dir/*Cmd_Argv(1)*/, "current");

	SV_ReadServerFile ();

	// go to the map
	sv.state = ss_dead;		// don't save current level when changing
	SV_Map (false, svs.mapcmd, true);
}

void CL_ParseConfigString ()
{
	int		i;
	char	*s;
	char	olds[MAX_QPATH];
	int		max_models, max_sounds, max_images, cs_lights, cs_sounds, cs_images, cs_playerskins;

	if (net_compatibility->value)
	{
		max_models = MAX_MODELS_Q2;
		max_sounds = MAX_SOUNDS_Q2;
		max_images = MAX_IMAGES_Q2;
		cs_lights = CS_LIGHTS_Q2;
		cs_sounds = CS_SOUNDS_Q2;
		cs_images = CS_IMAGES_Q2;
		cs_playerskins = CS_PLAYERSKINS_Q2;
	}
	else
	{
		max_models = MAX_MODELS_BERS;
		max_sounds = MAX_SOUNDS_BERS;
		max_images = MAX_IMAGES_BERS;
		cs_lights = CS_LIGHTS_BERS;
		cs_sounds = CS_SOUNDS_BERS;
		cs_images = CS_IMAGES_BERS;
		cs_playerskins = CS_PLAYERSKINS_BERS;
	}

	i = MSG_ReadShort (&net_message);
	if (net_compatibility->value)
	{
		if (i < 0 || i >= MAX_CONFIGSTRINGS_Q2)
			Com_Error (ERR_DROP, "configstring > MAX_CONFIGSTRINGS_Q2");
	}
	else
	{
		if (i < 0 || i >= MAX_CONFIGSTRINGS_BERS)
			Com_Error (ERR_DROP, "configstring > MAX_CONFIGSTRINGS_BERS");
	}
	s = MSG_ReadString(&net_message);

	strncpy (olds, cl.configstrings[i], sizeof(olds));
	olds[sizeof(olds) - 1] = 0;

	strcpy (cl.configstrings[i], s);

	// do something apropriate 
	if (i >= cs_lights && i < cs_lights+MAX_LIGHTSTYLES)
		CL_SetLightstyle (i - cs_lights);
	if (i == CS_TRACK_AMBIENT)
	{
		if (cl.refresh_prepped)
///			CDAudio_Play (atoi(cl.configstrings[CS_TRACK_AMBIENT]), true);
			Music_Play(NULL);
		///if (TrackIsCombat_old == OLD_TRACK_DISABLED)
			///CL_PlayBackgroundTrack ();
	}
	else if (i >= CS_MODELS && i < CS_MODELS+max_models)
	{
		if (cl.refresh_prepped)
		{
			cl.model_draw[i-CS_MODELS] = R_RegisterModel (cl.configstrings[i], 1, false);
			if (cl.configstrings[i][0] == '*')
				cl.model_clip[i-CS_MODELS] = CM_InlineModel (cl.configstrings[i]);
			else
				cl.model_clip[i-CS_MODELS] = NULL;
		}
	}
	else if (i >= cs_sounds && i < cs_sounds+max_sounds)
	{
		if (cl.refresh_prepped)
		{
			WILLOW_HACK_SOUND (i - cs_sounds, cs_sounds);
			cl.sound_precache[i-cs_sounds] = S_RegisterSound (cl.configstrings[i]);
		}
	}
	else if (i >= cs_images && i < cs_images+max_images)
	{
		if (cl.refresh_prepped)
			cl.image_precache[i-cs_images] = Draw_FindPic (cl.configstrings[i]);
	}
	else if (i >= cs_playerskins && i < cs_playerskins+MAX_CLIENTS)
	{
		if (cl.refresh_prepped && strcmp(olds, s))
			CL_ParseClientinfo (i-cs_playerskins);
	}

	if (i == CS_MODELS+1)
	{
		if (draw_mapshot)
		{
			R_FreePic(draw_mapshot->name);
			draw_mapshot = NULL;
		}

		mapshot[0] = 0;
		if (!developer->value && scr_mapshots->value)
		{
			char	mapfile[32];
			if (saved_shot_dir[0])
			{
				char	*savdir;
				if (net_compatibility->value)
					savdir = "save";
				else
					savdir = "save.q2b";
				strcpy(mapshot, va("pics/../%s/%s/shot.tga", savdir, saved_shot_dir));
				FS_ClearFileCache(mapshot);
				strcpy(mapshot, va("../%s/%s/shot.tga", savdir, saved_shot_dir));
				draw_mapshot = Draw_FindPic(mapshot);
				if (!draw_mapshot)
					goto nxt;
				scr_draw_loading = cls.disable_screen = 0;
			}
			else
			{
nxt:			strcpy (mapfile, cl.configstrings[i] + 5);	// skip "maps/"
				if(mapfile[0])
				{
					mapfile[strlen(mapfile)-4] = 0;		// cut off ".bsp"
					strcpy(mapshot, va("levelshots/%s.png", mapfile));
					draw_mapshot = Draw_FindPic(mapshot);
					scr_draw_loading = cls.disable_screen = 0;
				}
			}
		}
	}
}


void SV_Demos_f ()
{
	int		i, mask;
	char	cmd[128];

	if (Cmd_Argc() != 2)
	{
no:		Com_Printf ("^3Usage: demos all|paks|fldr\n");
		return;
	}

	Com_sprintf(cmd, sizeof(cmd), "%s", Cmd_Argv(1));
	if(!Q_strcasecmp(cmd, "all"))
		mask = 3;
	else if(!Q_strcasecmp(cmd, "paks"))
		mask = 1;
	else if(!Q_strcasecmp(cmd, "fldr"))
		mask = 2;
	else
		goto no;

	Create_Demosstrings(mask);

	if (!m_demosvalid[0])
		return;

	for ( i = 0; m_demosvalid[i]; i++ )
	{
		if (i==MAX_DEMOS-1 || !m_demosvalid[i+1])
			Com_sprintf (cmd, sizeof(cmd), "alias bers1975%i \"demo %s;set nextserver bers1975%i\"\n", i, m_demos[i], 0);
		else
			Com_sprintf (cmd, sizeof(cmd), "alias bers1975%i \"demo %s;set nextserver bers1975%i\"\n", i, m_demos[i], i+1);

		Cbuf_AddText (cmd);
		Cbuf_Execute ();
	}

	Cbuf_AddText ("bers19750\n");
	Cbuf_Execute ();
}


/*
===============
SV_ServerCommand_f

Let the game dll handle a command
===============
*/
void SV_ServerCommand_f ()
{
	if (!ge)
	{
		Com_Printf ("^1No game loaded.\n");
		return;
	}

	ge->ServerCommand();
}


void SV_InitOperatorCommands ()
{
	Cmd_AddCommand ("kick", SV_Kick_f);
	Cmd_AddCommand ("status", SV_Status_f);
	Cmd_AddCommand ("serverinfo", SV_Serverinfo_f);
	Cmd_AddCommand ("dumpuser", SV_DumpUser_f);

	Cmd_AddCommand ("map", SV_Map_f);
	Cmd_AddCommand ("demo", SV_DemoMap_f);
	Cmd_AddCommand ("demomap", SV_DemoMap_f);	// оставил для совместимости
	Cmd_AddCommand ("demos", SV_Demos_f);
	Cmd_AddCommand ("gamemap", SV_GameMap_f);

	if ( dedicated->value )
		Cmd_AddCommand ("say", SV_ConSay_f);

	Cmd_AddCommand ("serverrecord", SV_ServerRecord_f);
	Cmd_AddCommand ("serverstop", SV_ServerStop_f);

	Cmd_AddCommand ("save", SV_Savegame_f);
	Cmd_AddCommand ("load", SV_Loadgame_f);

	Cmd_AddCommand ("killserver", SV_KillServer_f);

	Cmd_AddCommand ("sv", SV_ServerCommand_f);
}


/*
===============
SV_Init

Only called at berserker.exe startup, not for each game
===============
*/
void SV_Init ()
{
	SV_InitOperatorCommands	();

	rcon_password = Cvar_Get ("rcon_password", "", 0);
	rcon_password->help = "password for use with RCON commands.";
	Cvar_Get ("skill", "1", 0);
	Cvar_Get ("sv_gibs", "10", CVAR_SERVERINFO|CVAR_ARCHIVE);
	Cvar_SetHelp("sv_gibs", "lifetime of gibs, number of seconds");
	Cvar_Get ("sv_predator", "0", CVAR_LATCH|CVAR_ARCHIVE);
	Cvar_SetHelp("sv_predator", "probability of invisible monsters arrival, 0..1");
	Cvar_Get ("sv_knockback", "1600", CVAR_ARCHIVE);
	Cvar_SetHelp("sv_knockback", "value of explosion knock back");
	Cvar_Get ("deathmatch", "0", CVAR_LATCH);
	Cvar_Get ("coop", "0", CVAR_LATCH);
	Cvar_Get ("dmflags", va("%i", DF_INSTANT_ITEMS | DF_DROP_WEAPON), CVAR_SERVERINFO | CVAR_ARCHIVE);
	Cvar_Get ("fraglimit", "0", CVAR_SERVERINFO);
	Cvar_Get ("timelimit", "0", CVAR_SERVERINFO);
	Cvar_Get ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
	if(net_compatibility->value)
		Cvar_Get ("protocol", va("%i", OLD_PROTOCOL_VERSION), CVAR_SERVERINFO|CVAR_NOSET);
	else
		Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO|CVAR_NOSET);
	maxclients = Cvar_Get ("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
	hostname = Cvar_Get ("hostname", "Bers@Q2 server", CVAR_SERVERINFO | CVAR_ARCHIVE);
	timeout = Cvar_Get ("timeout", "125", 0);
	timeout->help = "number of seconds after which a client is dropped if they have not sent any data.";
	zombietime = Cvar_Get ("zombietime", "2", 0);
	zombietime->help = "number of seconds during which packets from a recently disconnected client are ignored.";
	sv_allownodelta = Cvar_Get ("sv_allownodelta", "1", 0);		// r1ch
	sv_allownodelta->help = "Allow clients to use cl_nodelta (disables delta state). Not using delta state results in much greater bandwidth usage.";
	sv_locked = Cvar_Get ("sv_locked", "0", 0);	//r1: lock server (prevent new connections)
	sv_locked->help = "Prevent new players from connecting.";
	sv_showclamp = Cvar_Get ("showclamp", "0", 0);
	sv_paused = Cvar_Get ("paused", "0", 0);
	sv_timedemo = Cvar_Get ("timedemo", "0", 0);
	sv_enforcetime = Cvar_Get ("sv_enforcetime", "0", 0);
	sv_enforcetime->help = "enforce time movements to prevent speed hacking. 0 - disabled, 1 - enabled, prevent excess movement.";
	sv_stopclock = Cvar_Get ("sv_stopclock", "0", 0);
	sv_stopclock->help = "stop game clock (for developers).";

	sv_autoSavePeriod = Cvar_Get ("autoSavePeriod", "0", CVAR_ARCHIVE);
	sv_autoSavePeriod->help = "automatically saves game every # seconds. Values < 10 will be disables autosaving. Only for local games.";
	sv_autoSaveTimer = Cvar_Get ("autoSaveTimer", "0", CVAR_NOSET);

	allow_download = Cvar_Get ("allow_download", "1", CVAR_ARCHIVE);
	allow_download_players  = Cvar_Get ("allow_download_players", "0", CVAR_ARCHIVE);
	allow_download_models = Cvar_Get ("allow_download_models", "1", CVAR_ARCHIVE);
	allow_download_sounds = Cvar_Get ("allow_download_sounds", "1", CVAR_ARCHIVE);
	allow_download_maps	  = Cvar_Get ("allow_download_maps", "1", CVAR_ARCHIVE);

	sv_noreload = Cvar_Get ("sv_noreload", "0", 0);
	sv_airaccelerate = Cvar_Get("sv_airaccelerate", "0", CVAR_LATCH);
//	public_server = Cvar_Get ("public", "0", 0);

	sv_reconnect_limit = Cvar_Get ("sv_reconnect_limit", "3", CVAR_ARCHIVE);

	SZ_Init (&net_message, net_message_buffer, sizeof(net_message_buffer));
}


void Con_ToggleChat_f ()
{
	Key_ClearTyping ();

	if (cls.key_dest == key_console)
	{
		if (cls.state == ca_active)
		{
			M_ForceMenuOff ();
			cls.key_dest = key_game;
		}
	}
	else
		cls.key_dest = key_console;
	
	Con_ClearNotify ();
}


void Con_MessageMode_f ()
{
	chat_team = false;
	cls.key_dest = key_message;
}


void Con_MessageMode2_f ()
{
	chat_team = true;
	cls.key_dest = key_message;
}


void Con_Get_f ()
{
	cls.key_dest = key_get;
}

void Con_Clear_f ()
{
	Memset (con.text, ' ', CON_TEXTSIZE);
	Memset (con.color, 7, CON_TEXTSIZE);
}


/*
================
Con_Dump_f

Save the console contents out to a file
================
*/
void Con_Dump_f ()
{
	int		l, x;
	char	*line;
	FILE	*f;
	char	buffer[1024];
	char	name[MAX_OSPATH];

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3USAGE: condump <filename>\n");
		return;
	}

	Com_sprintf (name, sizeof(name), "%s/%s.txt", FS_Gamedir(), Cmd_Argv(1));

	Com_Printf ("Dumped console text to %s.\n", name);
	FS_CreatePath (name);
	f = FS_Fopen(name, "w");
	if (!f)
	{
		Com_Printf ("^1ERROR: couldn't open.\n");
		return;
	}

	// skip empty lines
	for (l = con.current - con.totallines + 1 ; l <= con.current ; l++)
	{
		line = con.text + (l%con.totallines)*con.linewidth;
		for (x=0 ; x<con.linewidth ; x++)
			if (line[x] != ' ')
				break;
		if (x != con.linewidth)
			break;
	}

	// write the remaining lines
	buffer[con.linewidth] = 0;
	for ( ; l <= con.current ; l++)
	{
		line = con.text + (l%con.totallines)*con.linewidth;
		strncpy (buffer, line, con.linewidth);
		for (x=con.linewidth-1 ; x>=0 ; x--)
		{
			if (buffer[x] == ' ')
				buffer[x] = 0;
			else
				break;
		}
		for (x=0; buffer[x]; x++)
			buffer[x] &= 0x7f;

		fprintf (f, "%s\n", buffer);
	}

	fclose (f);
}

						
void Con_Init ()
{
	con.linewidth = -1;

	Con_CheckResize ();
	
	Com_Printf ("Console initialized\n");

//
// register our commands
//
	con_maxfps = Cvar_Get ("con_maxfps", "25", CVAR_ARCHIVE);
	con_maxfps->help = "maximal rendering rate for actived menu or console.";
	con_notifytime = Cvar_Get ("con_notifytime", "3", 0);
	con_transparency = Cvar_Get ("con_transparency", "0", CVAR_ARCHIVE);
	con_transparency->help = "if set, draws the half-transparent console.";

	Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
	Cmd_AddCommand ("togglechat", Con_ToggleChat_f);
	Cmd_AddCommand ("messagemode", Con_MessageMode_f);
	Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
	Cmd_AddCommand ("get", Con_Get_f);
	Cmd_AddCommand ("clear", Con_Clear_f);
	Cmd_AddCommand ("condump", Con_Dump_f);
	con.initialized = true;
}


/*
============
VID_Restart_f

Console command to re-start the video mode and refresh DLL. We do this
simply by setting the modified flag for the vid_ref variable, which will
cause the entire video mode and refresh DLL to be reset on the next frame.
============
*/
void VID_Restart_f ()
{
	vid_restart = true;
}


void VID_Front_f()
{
	SetWindowLong( cl_hwnd, GWL_EXSTYLE, WS_EX_TOPMOST );
	SetForegroundWindow( cl_hwnd );
}


static void WIN_DisableAltTab()
{
	if ( s_alttab_disabled )
		return;

	if (dedicated->value)	/// Berserker: не блокируем ALT-TAB для выделенного сервера
		return;

	if ( s_win95 )
	{
		BOOL old;

		SystemParametersInfo( SPI_SCREENSAVERRUNNING, 1, &old, 0 );
	}
	else
	{
		RegisterHotKey( 0, 0, MOD_ALT, VK_TAB );
		RegisterHotKey( 0, 1, MOD_ALT, VK_RETURN );
		RegisterHotKey( 0, 2, MOD_ALT, VK_ESCAPE );
	}
	s_alttab_disabled = true;
}


static void WIN_EnableAltTab()
{
	if ( s_alttab_disabled )
	{
		if ( s_win95 )
		{
			BOOL old;

			SystemParametersInfo( SPI_SCREENSAVERRUNNING, 0, &old, 0 );
		}
		else
		{
			UnregisterHotKey( 0, 0 );
			UnregisterHotKey( 0, 1 );
			UnregisterHotKey( 0, 2 );
		}

		s_alttab_disabled = false;
	}
}


void M_Menu_Keys_f ()
{
	Keys_MenuInit();
	M_PushMenu( Keys_MenuDraw, Keys_MenuKey );
}


void CustomizeControlsFunc( void *unused )
{
	M_Menu_Keys_f();
}


void M_Menu_Keys2_f ()
{
	Keys2_MenuInit();
	M_PushMenu( Keys2_MenuDraw, Keys_MenuKey2 );
}


void CustomizeControlsFunc2( void *unused )
{
	M_Menu_Keys2_f();
}


float ClampCvar( float min, float max, float value )
{
	if ( value < min ) return min;
	if ( value > max ) return max;
	return value;
}


static void MusicFunc( void *unused )
{
	if (s_options_cdvolume_box.curvalue == 0)
	{	// disabled
		Cvar_SetValue("cd_nocd", 1);
		Cvar_SetValue("s_music", 0);
		s_options_cdvolume_box.generic.statusbar = "Music disabled";
	}
	else if (s_options_cdvolume_box.curvalue == 1)
	{	// CD only
		Cvar_SetValue("cd_nocd", 0);
		Cvar_SetValue("s_music", 0);
		s_options_cdvolume_box.generic.statusbar = "CD music enabled";
	}
	else if (s_options_cdvolume_box.curvalue == 2)
	{	// Tracks only
		Cvar_SetValue("cd_nocd", 1);
		Cvar_SetValue("s_music", 1);
		s_options_cdvolume_box.generic.statusbar = "Music from OGG/WAV files enabled";
	}
	else ///if (s_options_cdvolume_box.curvalue == 3)
	{	// Tracks or CD
		Cvar_SetValue("cd_nocd", 0);
		Cvar_SetValue("s_music", 1);
		s_options_cdvolume_box.generic.statusbar = "Music from both sources (CD/Files) enabled";
	}
///	else
///	{	// Situations tracks
///		Cvar_SetValue("cd_nocd", 1);
///		Cvar_SetValue("s_music", 2);
///	}
}

int SetMusicValue()
{
	if (Cvar_VariableValue("cd_nocd") && !s_music->value)
		return 0;	// disabled
	if (!Cvar_VariableValue("cd_nocd") && !s_music->value)
		return 1;	// CD only
	if (Cvar_VariableValue("cd_nocd") && s_music->value == 1)
		return 2;	// Tracks only
///	if (!Cvar_VariableValue("cd_nocd") && s_music->value == 1)
		return 3;	// Tracks or CD
///	// Situations tracks
///	Cvar_SetValue("cd_nocd", 1);
///	Cvar_SetValue("s_music", 2);
///	return 4;
}

static void UpdateCDVolumeFunc(void *unused)
{
	Cvar_SetValue(/*"cd_volume"*/"s_musicVolume", s_options_cdvolume_slider.curvalue / 10);
}

int QualFromKHZ(int hz)
{
	switch (hz)
	{
	case 0:
		return 0;
	case 22050:
		return 1;
	default:
	case 44100:
		return 2;
	case 48000:
		return 3;
	case 88200:
		return 4;
	case 96000:
		return 5;
	case 176400:
		return 6;
	case 192000:
		return 7;
	}
}

void ControlsSetMenuItemValues()
{
	s_options_sfxvolume_slider.curvalue		= Cvar_VariableValue( "s_volume" ) * 10;
	s_options_cdvolume_slider.curvalue		= Cvar_VariableValue(/*"cd_volume"*/"s_musicVolume") * 10;
	s_options_cdvolume_box.curvalue 		= SetMusicValue();
	s_options_quality_list.curvalue = QualFromKHZ(Cvar_VariableValue("s_quality"));
	s_options_sensitivity_slider.curvalue	= ( sensitivity->value ) * 5/*2*/;

	Cvar_SetValue( "cl_run", ClampCvar( 0, 1, cl_run->value ) );
	s_options_alwaysrun_box.curvalue		= cl_run->value;

	if (old_mouse_support)
		s_options_invertmouse_box.curvalue		= m_pitch->value < 0;
	else
		s_options_invertmouse_box.curvalue		= m_inversion->value > 0;

	Cvar_SetValue( "lookspring", ClampCvar( 0, 1, lookspring->value ) );
	s_options_lookspring_box.curvalue		= lookspring->value;

	Cvar_SetValue( "lookstrafe", ClampCvar( 0, 1, lookstrafe->value ) );
	s_options_lookstrafe_box.curvalue		= lookstrafe->value;

	Cvar_SetValue( "freelook", ClampCvar( 0, 1, freelook->value ) );
	s_options_freelook_box.curvalue			= freelook->value;

	Cvar_SetValue( "crosshair", ClampCvar( 0, MAX_CROSSHAIR, crosshair->value ) );
	s_options_crosshair_box.curvalue		= crosshair->value;

////*	Berserker
	Cvar_SetValue( "win_noalttab", ClampCvar( 0, 1, win_noalttab->value ) );
	s_options_noalttab_box.curvalue		= win_noalttab->value;
////*	Berserker

	Cvar_SetValue( "in_joystick", ClampCvar( 0, 1, in_joystick->value ) );
	s_options_joystick_box.curvalue		= in_joystick->value;
}


void ControlsResetDefaultsFunc( void *unused )
{
	Cbuf_AddText ("exec default.cfg\n");
///	Cbuf_AddText ("exec q2b_default.cfg\n");
	Cbuf_Execute();

	ControlsSetMenuItemValues();
}


void KillServerFunc( void *unused )
{
#if 0
	if (cl.attractloop || (cl.cinematictime > 0))
	{
		Cbuf_AddText ("killserver\n");
		return;
	}

	Key_ClearTyping ();
	Con_ClearNotify ();

	M_ForceMenuOff ();
	cls.key_dest = key_console;
#else
	// Berserker: возможность убивать не только demo/video, а и локальный сервер.
	Cbuf_AddText ("killserver\n");
#endif
}


int strlenColored( const char *string )
{
	int	i = 0;
	while (1)
	{
povtor:	if (*string==0)
			break;
		if (Q_IsColorString(string))
		{
			string+=2;
			goto povtor;
		}
		string++;
		i++;
	}
	return i;
}

void Menu_DrawStringColored( int x, int y, const char *string )
{
	unsigned	i, ii;
	char		cindex = 7;

	gl_TexEnv( GL_MODULATE );

	for ( i = 0, ii = 0; i < strlen( string ); i++, ii++ )
	{
povtor:	if (Q_IsColorString(&string[i]))
		{
			cindex = ColorIndex(string[i+1]);
			i+=2;
			if (i>=strlen( string ))
				break;
			goto povtor;
		}
		Draw_Char_Colored( ( x + (ii<<4) ), y, string[i], cindex );
	}

	GL_Color3f(1,1,1);
	gl_TexEnv( GL_REPLACE );
}

void Menu_DrawStatusBarColored( const char *string )
{
	if ( string )
	{
		int l = strlenColored( string );
		int maxrow = viddef.height >> 4;
		int maxcol = viddef.width >> 4;
		int col = (maxcol - l) >> 1;

		Draw_Fill( 0, viddef.height-16, viddef.width, 16, 40,40,40);
		Menu_DrawStringColored( col << 4, viddef.height-16, string );
	}
}

void Menu_DrawCrosshairStatusBar( void* unused )
{
	if (r_crosshair_image->string[0])
		Menu_DrawStatusBarColored("^1crosshair overrided by r_crosshair_image");
}

void JoystickFunc( void *unused )
{
	Cvar_SetValue( "in_joystick", s_options_joystick_box.curvalue );
}

static void AlDeviceFunc(void *unused)
{
	if (s_options_aldev_box.curvalue > 0)
		Cvar_Set("s_openal_device",	(char*)al_device[s_options_aldev_box.curvalue]);
	else
		Cvar_Set("s_openal_device", "");

	CL_Snd_Restart_f();
}

void ToggleWHAFunc(void *unused)
{
	Cvar_SetValue("weaponHitAccuracy", s_options_wphitacc.curvalue);
}

void Options_MenuInit()
{
	int i;

	win_noalttab = Cvar_Get( "win_noalttab", "0", CVAR_ARCHIVE );

	cvar_t *cv = Cvar_Get ("s_initsound", "1", 0);
	if(cv->value!=0 && cv->value!=1)
		Cvar_SetValue( "s_initsound", 1 );

	/*
	** configure controls menu and menu items
	*/
	s_options_menu.x = viddef.width / 2;
	s_options_menu.y = viddef.height / 2 - 58;
	s_options_menu.nitems = 0;

	s_options_cdvolume_box.generic.type	= MTYPE_SPINCONTROL;
	s_options_cdvolume_box.generic.x		= 0;
	s_options_cdvolume_box.generic.y		= -15;
	s_options_cdvolume_box.generic.name	= "music";
	s_options_cdvolume_box.generic.callback	= MusicFunc;
	s_options_cdvolume_box.itemnames		= MusicTrack_names;
	s_options_cdvolume_box.curvalue 		= SetMusicValue();

	s_options_cdvolume_slider.generic.type = MTYPE_SLIDER;
	s_options_cdvolume_slider.generic.x = 0;
	s_options_cdvolume_slider.generic.y = -5;
	s_options_cdvolume_slider.generic.name = "music volume";
	s_options_cdvolume_slider.generic.callback = UpdateCDVolumeFunc;
	s_options_cdvolume_slider.minvalue = 0;
	s_options_cdvolume_slider.maxvalue = 10;
	s_options_cdvolume_slider.curvalue = Cvar_VariableValue(/*"cd_volume"*/"s_musicVolume") * 10;

	s_options_onoff_list.generic.type		= MTYPE_SPINCONTROL;
	s_options_onoff_list.generic.x			= 0;
	s_options_onoff_list.generic.y			= 5;
	s_options_onoff_list.generic.name		= "sound";
	s_options_onoff_list.generic.callback	= ToggleSoundFunc;
	s_options_onoff_list.itemnames			= onoff_names;
	s_options_onoff_list.curvalue			= Cvar_VariableValue("s_initsound");

	s_options_sfxvolume_slider.generic.type	= MTYPE_SLIDER;
	s_options_sfxvolume_slider.generic.x	= 0;
	s_options_sfxvolume_slider.generic.y	= 15;
	s_options_sfxvolume_slider.generic.name	= "sound volume";
	s_options_sfxvolume_slider.generic.callback	= UpdateVolumeFunc;
	s_options_sfxvolume_slider.minvalue		= 0;
	s_options_sfxvolume_slider.maxvalue		= 10;
	s_options_sfxvolume_slider.curvalue		= Cvar_VariableValue( "s_volume" ) * 10;

	s_options_quality_list.generic.type = MTYPE_SPINCONTROL;
	s_options_quality_list.generic.x = 0;
	s_options_quality_list.generic.y = 25;
	s_options_quality_list.generic.name = "sound quality";
	s_options_quality_list.generic.callback = ToggleSoundFunc;
	s_options_quality_list.itemnames = quality_items;
	s_options_quality_list.curvalue = QualFromKHZ(Cvar_VariableValue("s_quality"));

	s_options_aldev_box.generic.type = MTYPE_SPINCONTROL;
	s_options_aldev_box.generic.x = 0;
	s_options_aldev_box.generic.y = 35;
	s_options_aldev_box.generic.name = "sound driver";
	s_options_aldev_box.generic.callback = AlDeviceFunc;
	s_options_aldev_box.itemnames = al_device;
	s_options_aldev_box.generic.statusbar = "Try to select other driver if sound disappear";

	s_options_aldev_box.curvalue = 0;
	for (i = 1; i <= alConfig.device_count; i++)
		if (!Q_strcasecmp(s_openal_device->string, (char*)al_device[i]))
		{
			s_options_aldev_box.curvalue = i;
			break;
		}

	s_options_sensitivity_slider.generic.type	= MTYPE_SLIDER;
	s_options_sensitivity_slider.generic.x		= 0;
	s_options_sensitivity_slider.generic.y		= 45;
	s_options_sensitivity_slider.generic.name	= "mouse speed";
	s_options_sensitivity_slider.generic.callback = MouseSpeedFunc;
	s_options_sensitivity_slider.minvalue		= 2;
	s_options_sensitivity_slider.maxvalue		= 50/*22*/;

	s_options_alwaysrun_box.generic.type = MTYPE_SPINCONTROL;
	s_options_alwaysrun_box.generic.x	= 0;
	s_options_alwaysrun_box.generic.y	= 55;
	s_options_alwaysrun_box.generic.name	= "always run";
	s_options_alwaysrun_box.generic.callback = AlwaysRunFunc;
	s_options_alwaysrun_box.itemnames = yesno_names;

	s_options_invertmouse_box.generic.type = MTYPE_SPINCONTROL;
	s_options_invertmouse_box.generic.x	= 0;
	s_options_invertmouse_box.generic.y	= 65;
	s_options_invertmouse_box.generic.name	= "invert mouse";
	s_options_invertmouse_box.generic.callback = InvertMouseFunc;
	s_options_invertmouse_box.itemnames = yesno_names;

	s_options_lookspring_box.generic.type = MTYPE_SPINCONTROL;
	s_options_lookspring_box.generic.x	= 0;
	s_options_lookspring_box.generic.y	= 75;
	s_options_lookspring_box.generic.name	= "lookspring";
	s_options_lookspring_box.generic.callback = LookspringFunc;
	s_options_lookspring_box.itemnames = yesno_names;

	s_options_lookstrafe_box.generic.type = MTYPE_SPINCONTROL;
	s_options_lookstrafe_box.generic.x	= 0;
	s_options_lookstrafe_box.generic.y	= 85;
	s_options_lookstrafe_box.generic.name	= "lookstrafe";
	s_options_lookstrafe_box.generic.callback = LookstrafeFunc;
	s_options_lookstrafe_box.itemnames = yesno_names;

	s_options_freelook_box.generic.type = MTYPE_SPINCONTROL;
	s_options_freelook_box.generic.x	= 0;
	s_options_freelook_box.generic.y	= 95;
	s_options_freelook_box.generic.name	= "free look";
	s_options_freelook_box.generic.callback = FreeLookFunc;
	s_options_freelook_box.itemnames = yesno_names;

	s_options_crosshair_box.generic.type = MTYPE_SPINCONTROL;
	s_options_crosshair_box.generic.x	= 0;
	s_options_crosshair_box.generic.y	= 105;
	s_options_crosshair_box.generic.name	= "crosshair";
	s_options_crosshair_box.generic.callback = CrosshairFunc;
	s_options_crosshair_box.itemnames = crosshair_names;
	s_options_crosshair_box.generic.statusbarfunc = Menu_DrawCrosshairStatusBar;

///	Berserker
	s_options_noalttab_box.generic.type = MTYPE_SPINCONTROL;
	s_options_noalttab_box.generic.x	= 0;
	s_options_noalttab_box.generic.y	= 115;
	s_options_noalttab_box.generic.name	= "disable alt-tab";
	s_options_noalttab_box.generic.callback = NoAltTabFunc;
	s_options_noalttab_box.itemnames = yesno_names;
///	Berserker

	s_options_joystick_box.generic.type = MTYPE_SPINCONTROL;
	s_options_joystick_box.generic.x	= 0;
	s_options_joystick_box.generic.y	= 125;
	s_options_joystick_box.generic.name	= "use joystick";
	s_options_joystick_box.generic.callback = JoystickFunc;
	s_options_joystick_box.itemnames = yesno_names;

	s_options_customize_options_action.generic.type	= MTYPE_ACTION;
	s_options_customize_options_action.generic.x		= 0;
	s_options_customize_options_action.generic.y		= 135;
	s_options_customize_options_action.generic.name	= "custom. controls 1";
	s_options_customize_options_action.generic.callback = CustomizeControlsFunc;

	s_options_customize2_options_action.generic.type	= MTYPE_ACTION;
	s_options_customize2_options_action.generic.x		= 0;
	s_options_customize2_options_action.generic.y		= 145;
	s_options_customize2_options_action.generic.name	= "custom. controls 2";
	s_options_customize2_options_action.generic.callback = CustomizeControlsFunc2;

	s_options_defaults_action.generic.type	= MTYPE_ACTION;
	s_options_defaults_action.generic.x		= 0;
	s_options_defaults_action.generic.y		= 155;
	s_options_defaults_action.generic.name	= "reset defaults";
	s_options_defaults_action.generic.callback = ControlsResetDefaultsFunc;

	ControlsSetMenuItemValues();

	Menu_AddItem( &s_options_menu, ( void * ) &s_options_cdvolume_box );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_cdvolume_slider );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_onoff_list );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_sfxvolume_slider );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_quality_list);
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_aldev_box);
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_sensitivity_slider );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_alwaysrun_box );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_invertmouse_box );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_lookspring_box );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_lookstrafe_box );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_freelook_box );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_crosshair_box );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_noalttab_box );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_joystick_box );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_customize_options_action );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_customize2_options_action );
	Menu_AddItem( &s_options_menu, ( void * ) &s_options_defaults_action );
}


void Options_MenuDraw ()
{
	M_Banner( "m_banner_options.tga" );
	Menu_AdjustCursor( &s_options_menu, 1 );
	Menu_Draw( &s_options_menu );
}


int Options_MenuKey( int key )
{
	return Default_MenuKey( &s_options_menu, key );
}


void M_Menu_Options_f ()
{
	Options_MenuInit();
	M_PushMenu ( Options_MenuDraw, Options_MenuKey );
}


/*
===========
IN_Activate

Called when the main window gains or loses focus.
The window may have been destroyed and recreated
between a deactivate and an activate.
===========
*/
void IN_Activate (bool active)
{
	in_appactive = active;
	mouseactive = !active;		// force a new window check or turn off
}


/*
============
Cvar_Set_f

Allows setting and defining of arbitrary cvars from console
============
*/
void Cvar_Set_f ()
{
	int		c;
	int		flags;

	c = Cmd_Argc();
	if (c != 3 && c != 4)
	{
		Com_Printf ("^3USAGE: set <variable> <value> [u / s/ a]\n");
		return;
	}

	if (c == 4)
	{
		if (!strcmp(Cmd_Argv(3), "u"))
			flags = CVAR_USERINFO;
		else if (!strcmp(Cmd_Argv(3), "s"))
			flags = CVAR_SERVERINFO;
		else if (!strcmp(Cmd_Argv(3), "a"))
			flags = CVAR_ARCHIVE;
		else if (!strcmp(Cmd_Argv(3), "-a"))
			flags = 0;
		else
		{
			Com_Printf ("^3flags can only be 'u' or 's' or 'a' or '-a'\n");
			return;
		}

		/// Berserker: Fixed Q2 bug:    "set <cvar_NOSET> value u"    will modify the <cvar_NOSET> !!!
		cvar_t *var = Cvar_FindVar(Cmd_Argv(1));
		if (var)
		{
			if (!(flags & CVAR_ARCHIVE))
				var->flags &= ~CVAR_ARCHIVE;		// allow to reset CVAR_ARCHIVE
			if ((flags & CVAR_USERINFO) && (var->flags & CVAR_SERVERINFO) ||
				(flags & CVAR_SERVERINFO) && (var->flags & CVAR_USERINFO))
				Com_Printf ("^3attempt to set incorrect flag\n");
			else if ((flags & CVAR_ARCHIVE) && !Q_strcasecmp(var->name, "game"))
				Com_Printf ("^3'game' can't be archived\n");
			else
				var->flags |= flags;
			Cvar_Set (Cmd_Argv(1), Cmd_Argv(2));
		}
		else
			Cvar_FullSet (Cmd_Argv(1), Cmd_Argv(2), flags);
	}
	else
		Cvar_Set (Cmd_Argv(1), Cmd_Argv(2));
}


void Cvar_Set2_f ()
{
	int		flags;

	if (Cmd_Argc() != 3)
	{
		Com_Printf ("^3USAGE: %s <variable> <value>\n", Cmd_Argv(0));
		return;
	}

	if (!Q_strcasecmp(Cmd_Argv(0), "seta"))
		flags = CVAR_ARCHIVE;
	else if (!Q_strcasecmp(Cmd_Argv(0), "setu"))
		flags = CVAR_USERINFO;
	else ///if (!Q_strcasecmp(Cmd_Argv(0), "sets"))
		flags = CVAR_SERVERINFO;

	/// Berserker: Fixed Q2 bug:    "setu <cvar_NOSET> value"    will modify the <cvar_NOSET> !!!
	cvar_t *var = Cvar_FindVar(Cmd_Argv(1));
	if (var)
	{
		if ((flags & CVAR_USERINFO) && (var->flags & CVAR_SERVERINFO) ||
			(flags & CVAR_SERVERINFO) && (var->flags & CVAR_USERINFO))
			Com_Printf ("^3attempt to set incorrect flag\n");
		else if ((flags & CVAR_ARCHIVE) && !Q_strcasecmp(var->name, "game"))
			Com_Printf ("^3'game' can't be archived\n");
		else
			var->flags |= flags;
		Cvar_Set (Cmd_Argv(1), Cmd_Argv(2));
	}
	else
		Cvar_FullSet (Cmd_Argv(1), Cmd_Argv(2), flags);
}


void VID_UpdateWindowPosAndSize( int x, int y )
{
	RECT r;
	int		style;
	int		w, h;

	r.left   = 0;
	r.top    = 0;
	r.right  = viddef.width;
	r.bottom = viddef.height;

	style = GetWindowLong( cl_hwnd, GWL_STYLE );
	AdjustWindowRect( &r, style, FALSE );

	w = r.right - r.left;
	h = r.bottom - r.top;

	MoveWindow( cl_hwnd, vid_xpos->value, vid_ypos->value, w, h, TRUE );
}


void AppActivate(BOOL fActive, BOOL minimize)
{
	Minimized = minimize;

	Key_ClearStates();

	// we don't want to act like we're active if we're minimized
	if (fActive && !Minimized)
		ActiveApp = true;
	else
		ActiveApp = false;

	// minimize/restore mouse-capture on demand
	if (!ActiveApp)
	{
		IN_Activate (false);
//Music_Pause();
//if (alConfig.hALC) alcSuspendContext(alConfig.hALC); //willow: Have no success??
		if ( win_noalttab->value )
			WIN_EnableAltTab();
	}
	else
	{
		IN_Activate (true);
//Music_Resume();
//if (alConfig.hALC) alcProcessContext(alConfig.hALC); //willow: Have no success??
		if ( win_noalttab->value )
			WIN_DisableAltTab();
	}
}


void IN_MouseEvent (int mstate)
{
	int		i;

	if (!mouseinitialized)
		return;

// perform button actions
	for (i=0 ; i<mouse_buttons ; i++)
	{
		if ( (mstate & (1<<i)) && !(mouse_oldbuttonstate & (1<<i)) )
			Key_Event (K_MOUSE1 + i, true, sys_msg_time);

		if ( !(mstate & (1<<i)) && (mouse_oldbuttonstate & (1<<i)) )
			Key_Event (K_MOUSE1 + i, false, sys_msg_time);
	}	
		
	mouse_oldbuttonstate = mstate;
}


void GLimp_AppActivate( bool active )
{
	if ( active )
	{
		SetForegroundWindow( glw_state.hWnd );
		ShowWindow( glw_state.hWnd, SW_RESTORE );
	}
	else
	{
		if ( r_fullscreen->value )
			ShowWindow( glw_state.hWnd, SW_MINIMIZE );
	}
}


/*
=======
MapKey

Map from windows to berserker keynums
=======
*/
int MapKey (int key)
{
	int result;
	int modified = ( key >> 16 ) & 255;
	bool is_extended = false;

	if ( modified > 127)
		return 0;

	if ( key & ( 1 << 24 ) )
		is_extended = true;

	result = scantokey[modified];

	if ( !is_extended )
	{
		switch ( result )
		{
		case K_HOME:
			return K_KP_HOME;
		case K_UPARROW:
			return K_KP_UPARROW;
		case K_PGUP:
			return K_KP_PGUP;
		case K_LEFTARROW:
			return K_KP_LEFTARROW;
		case K_RIGHTARROW:
			return K_KP_RIGHTARROW;
		case K_END:
			return K_KP_END;
		case K_DOWNARROW:
			return K_KP_DOWNARROW;
		case K_PGDN:
			return K_KP_PGDN;
		case K_INS:
			return K_KP_INS;
		case K_DEL:
			return K_KP_DEL;
		default:
			return result;
		}
	}
	else
	{
		switch ( result )
		{
		case 0x0D:
			return K_KP_ENTER;
		case 0x2F:
			return K_KP_SLASH;
		case 0xAF:
			return K_KP_PLUS;
		}
		return result;
	}
}


/*
====================
MainWndProc

main window procedure
====================
*/
LONG WINAPI MainWndProc (
    HWND    hWnd,
    UINT    uMsg,
    WPARAM  wParam,
    LPARAM  lParam)
{
	no_jmp = true;		// Если в ходе выполнения MainWndProc возникнет ERR_DROP/DISCONNECT, то заменим на ERR_FATAL.

	//willow:
	//ГЛАВНАЯ идея оптимизации обработки сообщений это упорядочить сообщения по интенсинсивности их появления
	//и критичности к времени обработки. Первым в данном списке идёт мышь, до 200 (PS/2) или 500 (USB) сообщений в секунду.
	//Профессиональные игровые мыши рапортуют до 1000 сообщений в секунду.
	if ( mouseactive )
	{
		if ( old_mouse_support )
		{
			// this is complicated because Win32 seems to pack multiple mouse events into
			// one update sometimes, so we always check all states and look for events
			switch (uMsg)
			{
				case WM_MOUSEMOVE:
				case WM_LBUTTONDOWN:
				case WM_LBUTTONUP:
				case WM_RBUTTONDOWN:
				case WM_RBUTTONUP:
				case WM_MBUTTONDOWN:
				case WM_MBUTTONUP:
					{
						int	temp = 0;
						if (wParam & MK_LBUTTON) temp |= 1;
						if (wParam & MK_RBUTTON) temp |= 2;
						if (wParam & MK_MBUTTON) temp |= 4;
						IN_MouseEvent (temp);
					}
					no_jmp = false;
					return DefWindowProc( hWnd, uMsg, wParam, lParam );
				case WM_MOUSEWHEEL:
					/*
					** this chunk of code theoretically only works under NT4 and Win98
					** since this message doesn't exist under Win95
					*/
					if ( ( short ) HIWORD( wParam ) > 0 )
					{
						Key_Event( K_MWHEELUP, true, sys_msg_time );
						Key_Event( K_MWHEELUP, false, sys_msg_time );
					}
					else
					{
						Key_Event( K_MWHEELDOWN, true, sys_msg_time );
						Key_Event( K_MWHEELDOWN, false, sys_msg_time );
					}
					no_jmp = false;
					return DefWindowProc( hWnd, uMsg, wParam, lParam );
				default:
					if (uMsg == MSH_MOUSEWHEEL)
					{
						if ( ( ( int ) wParam ) > 0 )
						{
							Key_Event( K_MWHEELUP, true, sys_msg_time );
							Key_Event( K_MWHEELUP, false, sys_msg_time );
						}
						else
						{
							Key_Event( K_MWHEELDOWN, true, sys_msg_time );
							Key_Event( K_MWHEELDOWN, false, sys_msg_time );
						}
						no_jmp = false;
						return DefWindowProc (hWnd, uMsg, wParam, lParam);
					}
			}
		}
		else
		{	//!old_mouse_support
			if ( uMsg == WM_INPUT )
			{
				UINT dwSize = 40;
				static BYTE lpb[40];

				if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != -1)
				{
					PRAWINPUT raw = (RAWINPUT*)lpb;

					if (raw->header.dwType == RIM_TYPEMOUSE)
					{
						if (raw->data.mouse.lLastX || raw->data.mouse.lLastY)
						{
							//willow:
							//Делать поддержку какого-либо искусственного ускорения баллистической траектории (по примеру m_accel)
							//мыши я не рекомендую. Подобные технологии признаны безперспективными всеми профессиональными игроками.
							///Berserker: коррекция чуствительности от FOV
							///уменьшим чуствительность в 10 раз (чтоб примерно соответствовало чуствительности старой мыши)
							///float sens = sensitivity->value * cl.refdef.fov_x / 90.0f * 0.1f;
							//willow: оптимизируем эту формулу. Корректирующий фактор поправлен c 0.1f на 0.111111f * 0.66f,
							//что в общем-то не имеет никакого принципиального значения. Иначе у меня слайдер настройки едва
							//за границу не вышел. Заодно получаем оптимизацию - гарантированная замена деления на умножение.
							float sens = sensitivity->value * cl.refdef.fov_x * 0.00066f;

							if (raw->data.mouse.lLastX)
								cl.viewangles_YAW -= sens * raw->data.mouse.lLastX;

							if (raw->data.mouse.lLastY)
							{
								if (m_inversion->value)
									cl.viewangles_PITCH -= sens * raw->data.mouse.lLastY;
								else
									cl.viewangles_PITCH += sens * raw->data.mouse.lLastY;
							}

							//willow, Berserker:
							//Хак курсор не убегал за пределы окна
							//willow: P.S. всё-же только для оконного режима
							if (!(r_fullscreen && r_fullscreen->value))
								SetCursorPos (window_center_x, window_center_y);
						}

						// perform button actions
						if (raw->data.mouse.usButtonFlags)
						{
							//RI_MOUSE_LEFT_BUTTON
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN) Key_Event (K_MOUSE1, true, sys_msg_time);
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP) Key_Event (K_MOUSE1, false, sys_msg_time);
							//RI_MOUSE_RIGHT_BUTTON
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN) Key_Event (K_MOUSE2, true, sys_msg_time);
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP) Key_Event (K_MOUSE2, false, sys_msg_time);
							//RI_MOUSE_MIDDLE_BUTTON
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN) Key_Event (K_MOUSE3, true, sys_msg_time);
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP) Key_Event (K_MOUSE3, false, sys_msg_time);
							//RI_MOUSE_BUTTON_4
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) Key_Event (K_MOUSE4, true, sys_msg_time);
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) Key_Event (K_MOUSE4, false, sys_msg_time);
							//RI_MOUSE_BUTTON_5
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) Key_Event (K_MOUSE5, true, sys_msg_time);
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) Key_Event (K_MOUSE5, false, sys_msg_time);
							//RI_MOUSE_WHEEL
							if (raw->data.mouse.usButtonFlags & RI_MOUSE_WHEEL)
							{
								int i = (short)raw->data.mouse.usButtonData; //wheel delta, signed
								if (i < 0)
								{
									Key_Event( K_MWHEELDOWN, true, sys_msg_time );
									Key_Event( K_MWHEELDOWN, false, sys_msg_time );
								}
								else if (i > 0)
								{
									Key_Event( K_MWHEELUP, true, sys_msg_time );
									Key_Event( K_MWHEELUP, false, sys_msg_time );
								}
							}
						}
						no_jmp = false;
						return 0;
						//willow: DefWindowProc? DefRawInputProc? то и другое?
					}
					else
					{
						//willow:
						//Мы обнаружили неизвестное устройство - сообщим в компетентные органы по вопросам RAW INPUT.
						//Ставить ли в известность DefWindowProc?
						no_jmp = false;
						return DefRawInputProc( &raw, 1, sizeof(RAWINPUTHEADER) ) ? 1 : 0;
					}
				}
				//willow:
				//Поправьте меня, но если мы не смогли прочитать WM_INPUT сообщение, то никто не сможет
				no_jmp = false;
				return 1;
			} //WM_INPUT
		}
	}

	//Willow: Относительно некритичные или редкие сообщения
	switch (uMsg)
	{
		case WM_SYSKEYDOWN:
			if ( wParam == 13 )		// Alt+Enter
			{
				if ( r_fullscreen )
				{
					Cvar_SetValue( "r_fullscreen", !r_fullscreen->value );
					vid_restart = true;		// Berserker fixed: без этого не работала комбинация :(
				}
				no_jmp = false;
				return 0;
			}
		// fall through
		case WM_KEYDOWN:
			Key_Event( MapKey( lParam ), true, sys_msg_time);
			break;
		case WM_SYSKEYUP:
		case WM_KEYUP:
			Key_Event( MapKey( lParam ), false, sys_msg_time);
			break;
		case WM_HOTKEY:
			no_jmp = false;
			return 0;
		case WM_PAINT:
			SCR_DirtyScreen ();	// force entire screen to update next frame
			break;
		case WM_DESTROY:
			// let sound and input know about this?
			cl_hwnd = NULL;
			break;
		case WM_ACTIVATE:
			{
				// KJB: Watch this for problems in fullscreen modes with Alt-tabbing.
				bool fActive = LOWORD(wParam);
				bool fMinimized = (BOOL) HIWORD(wParam);
				AppActivate( fActive != WA_INACTIVE, fMinimized);
				if ( reflib_active )
					GLimp_AppActivate( !( fActive == WA_INACTIVE ) );
			}
			break;
		case WM_MOVE:
			if (!r_fullscreen->value)
			{
				RECT r;
				int xPos = (short) LOWORD(lParam);    // horizontal position 
				int yPos = (short) HIWORD(lParam);    // vertical position 

				r.left   = 0;
				r.top    = 0;
				r.right  = 1;
				r.bottom = 1;

				int style = GetWindowLong( hWnd, GWL_STYLE );
				AdjustWindowRect( &r, style, FALSE );

				Cvar_SetValue( "vid_xpos", xPos + r.left);
				Cvar_SetValue( "vid_ypos", yPos + r.top);
				vid_xpos->modified = false;
				vid_ypos->modified = false;
				if (ActiveApp)
					IN_Activate (true);
			}
			break;
		case WM_SYSCOMMAND:
			no_jmp = false;
			if (wParam == SC_SCREENSAVE || wParam == SC_MONITORPOWER)
				return 0;
	        return DefWindowProc (hWnd, uMsg, wParam, lParam);
		case MM_MCINOTIFY:
			LONG CDAudio_MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
			CDAudio_MessageHandler (hWnd, uMsg, wParam, lParam);
			break;
		case WM_CREATE:
			cl_hwnd = hWnd;
			if (old_mouse_support)
				MSH_MOUSEWHEEL = RegisterWindowMessage("MSWHEEL_ROLLMSG");
			else
			{
				//Разрешить события WM_INPUT
				RAWINPUTDEVICE Rid[1];
				Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
				Rid[0].usUsage = HID_USAGE_GENERIC_MOUSE;
				Rid[0].dwFlags =
					(r_fullscreen && r_fullscreen->value) ?
					//If set, the mouse button click does not activate the other window
					RIDEV_CAPTUREMOUSE
					//If set, this enables the caller to receive the input even when the caller is not in the foreground.
					//Note that hwndTarget must be specified
					| RIDEV_INPUTSINK
					//If set, this prevents any devices specified by usUsagePage or usUsage from generating legacy messages.
					//This is only for the mouse and keyboard.
					| RIDEV_NOLEGACY
					:
					0;
				Rid[0].hwndTarget = cl_hwnd;
				RegisterRawInputDevices(Rid, 1, sizeof(RAWINPUTDEVICE));
			}
			break;

	case WM_CLOSE:
///		if(MessageBox (NULL, "Are you sure you want to quit?", "Confirm Exit", MB_YESNO | MB_ICONQUESTION) == IDYES)
///			PostQuitMessage( 0 );	// Exit program when the user will close program
		no_jmp = false;
		return 0;	// Внимание! Если вместо нуля возвращать DefWindowProc(), то программа остается висеть в памяти!
					// Так мы запрещаем Alt+F4, вместо этого срабатывает просто F4	;)

///	default:	// pass all unhandled messages to DefWindowProc
///		no_jmp = false;
///		return DefWindowProc (hWnd, uMsg, wParam, lParam);
    }

    /* return 0 if handled message, 1 if not */
	no_jmp = false;
    return DefWindowProc( hWnd, uMsg, wParam, lParam );
}


bool VID_LoadRefresh()
{
	Com_Printf( "------- Video System restart -------\n", name );

	if ( reflib_active )
		R_Shutdown();

	if ( R_Init( global_hInstance, MainWndProc ) == -1 )
	{
		R_Shutdown();
		return false;
	}

	Com_Printf( "------------------------------------\n\n");
	reflib_active = true;

	return true;
}


/*
============
VID_CheckChanges

This function gets called once just before drawing each frame, and it's sole purpose in life
is to check to see if any of the video mode parameters have changed, and if they have to 
update the rendering DLL and/or video mode to match.
============
*/
void VID_CheckChanges ()
{
	if ( win_noalttab->modified )
	{
		if ( win_noalttab->value )
			WIN_DisableAltTab();
		else
			WIN_EnableAltTab();

		win_noalttab->modified = false;
	}

	Check_VidRestart(false);

	if (vid_restart && !loading_stage)	// Berserker: fixed bug: порча mapshot если при загрузке карты нажать alt-enter
	{
		loading_stage = 0;
		mapshot[0] = 0;

		gamma_initialized_ = -16;	// Для надежности пофиксим гамму на 16 кадре.

		// Berserker: если в игре, сохраним клиентское состояние, т.к. видео рестарт похерит декали, партикли и модели...
		if (cls.state == ca_active)
		{
			WriteClientFile(true);
			needsToLoadClientEntities = 2;
		}

		S_StopAllSounds();
///		CL_ClearClEntities ();

		Cvar_GetVidLatchedVars();

		// refresh has changed
		vid_restart = false;
		cl.refresh_prepped = false;
		cls.disable_screen = true;
		cl.force_refdef = true;

		if ( !VID_LoadRefresh() )
			Com_Error (ERR_FATAL, "Error during initialization video");

		/// Berserker: фиксим баг - если отключена вертикальная синхронизация, после vid_restart она сама включается (драйвер?). Форсируем обновление wglSwapIntervalEXT.
		r_swapinterval->modified = true;

 		cls.disable_screen = false;
	}

	// update our window position
	if ( vid_xpos->modified || vid_ypos->modified )
	{
		if (!r_fullscreen->value)
			VID_UpdateWindowPosAndSize( vid_xpos->value, vid_ypos->value );

		vid_xpos->modified = false;
		vid_ypos->modified = false;
	}
}


mleaf_t *Mod_PointInLeaf (vec3_t p, model_t *model)
{
	mnode_t		*node;
	float		d;
	cplane_t	*plane;
	
	if (!model || !model->nodes)
		Com_Error (ERR_DROP, "Mod_PointInLeaf: bad model");

	node = model->nodes;
	while (1)
	{
		if (node->contents != -1)
			return (mleaf_t *)node;
		plane = node->plane;
		d = DotProduct (p,plane->normal) - plane->dist;
		if (d > 0)
			node = node->children[0];
		else
			node = node->children[1];
	}
	
	return NULL;	// never reached
}


void R_SetupFrame (bool mirrr)
{
	int i;
	mleaf_t	*leaf;

	r_framecount++;
	if (!mirrr)
		occ_framecount++;

// build the transformation matrix for the given view angles
	VectorCopy (r_newrefdef.vieworg, r_origin);
	AngleVectors (r_newrefdef.viewangles, vpn, vright, vup);

	if (!r_mirror)
		for (i=0 ; i<4 ; i++)
			v_blend[i] = r_newrefdef.blend[i];

// current viewcluster
	if ( !( r_newrefdef.rdflags & RDF_NOWORLDMODEL ) )
	{
		r_oldviewcluster = r_viewcluster;
		r_oldviewcluster2 = r_viewcluster2;
		leaf = Mod_PointInLeaf (r_origin, r_worldmodel);
		r_viewcluster = r_viewcluster2 = leaf->cluster;

		// check above and below so crossing solid water doesn't draw wrong
		if (!leaf->contents)
		{	// look down a bit
			vec3_t	temp;

			VectorCopy (r_origin, temp);
			temp[2] -= 8;
			leaf = Mod_PointInLeaf (temp, r_worldmodel);
			if ( !(leaf->contents & CONTENTS_SOLID) && (leaf->cluster != r_viewcluster2) )
				r_viewcluster2 = leaf->cluster;
		}
		else
		{	// look up a bit
			vec3_t	temp;

			VectorCopy (r_origin, temp);
			temp[2] += 8;
			leaf = Mod_PointInLeaf (temp, r_worldmodel);
			if ( !(leaf->contents & CONTENTS_SOLID) && (leaf->cluster != r_viewcluster2) )
				r_viewcluster2 = leaf->cluster;
		}
	}
else
	// clear out the portion of the screen that the NOWORLDMODEL defines
///	if ( r_newrefdef.rdflags & RDF_NOWORLDMODEL )
	{
		glEnable( GL_SCISSOR_TEST );
		glScissor( r_newrefdef.x, vid.height - r_newrefdef.height - r_newrefdef.y, r_newrefdef.width, r_newrefdef.height );
		if ( r_newrefdef.rdflags & RDF_NOCLEAR )
			glClear( GL_DEPTH_BUFFER_BIT );
		else
		{
			glClearColor( 0, 0, 0, 1 );
			glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
			glClearColor (0.35, 0.35, 0.35, 0.5);
		}
		glDisable( GL_SCISSOR_TEST );
	}
}


void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal )
{
	float d;
	vec3_t n;
	float inv_denom;

	inv_denom = 1.0F / DotProduct( normal, normal );

	d = DotProduct( normal, p ) * inv_denom;

	n[0] = normal[0] * inv_denom;
	n[1] = normal[1] * inv_denom;
	n[2] = normal[2] * inv_denom;

	dst[0] = p[0] - d * n[0];
	dst[1] = p[1] - d * n[1];
	dst[2] = p[2] - d * n[2];
}


void PerpendicularVector( vec3_t dst, const vec3_t src )
{
	int	pos;
	int i;
	float minelem = 1.0F;
	vec3_t tempvec;

	/*
	** find the smallest magnitude axially aligned vector
	*/
	for ( pos = 0, i = 0; i < 3; i++ )
	{
		if ( fabs( src[i] ) < minelem )
		{
			pos = i;
			minelem = fabs( src[i] );
		}
	}
	tempvec[0] = tempvec[1] = tempvec[2] = 0.0F;
	tempvec[pos] = 1.0F;

	/*
	** project the point onto the plane defined by src
	*/
	ProjectPointOnPlane( dst, tempvec, src );

	/*
	** normalize the result
	*/
	VectorNormalize( dst );
}


void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3])
{
	out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
				in1[0][2] * in2[2][0];
	out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
				in1[0][2] * in2[2][1];
	out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
				in1[0][2] * in2[2][2];
	out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
				in1[1][2] * in2[2][0];
	out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
				in1[1][2] * in2[2][1];
	out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
				in1[1][2] * in2[2][2];
	out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
				in1[2][2] * in2[2][0];
	out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
				in1[2][2] * in2[2][1];
	out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
				in1[2][2] * in2[2][2];
}


void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees )
{
	float	m[3][3];
	float	im[3][3];
	float	zrot[3][3];
	float	tmpmat[3][3];
	float	rot[3][3];
	int	i;
	vec3_t vr, vup, vf;

	vf[0] = dir[0];
	vf[1] = dir[1];
	vf[2] = dir[2];

	PerpendicularVector( vr, dir );
	CrossProduct( vr, vf, vup );

	m[0][0] = vr[0];
	m[1][0] = vr[1];
	m[2][0] = vr[2];

	m[0][1] = vup[0];
	m[1][1] = vup[1];
	m[2][1] = vup[2];

	m[0][2] = vf[0];
	m[1][2] = vf[1];
	m[2][2] = vf[2];

	Memcpy( im, m, sizeof( im ) );

	im[0][1] = m[1][0];
	im[0][2] = m[2][0];
	im[1][0] = m[0][1];
	im[1][2] = m[2][1];
	im[2][0] = m[0][2];
	im[2][1] = m[1][2];

	Memset( zrot, 0, sizeof( zrot ) );
	zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F;

#if 0
	zrot[0][0] = cos( DEG2RAD( degrees ) );
	zrot[0][1] = sin( DEG2RAD( degrees ) );
	zrot[1][0] = -sin( DEG2RAD( degrees ) );
	zrot[1][1] = cos( DEG2RAD( degrees ) );
#else
	float s, c;
	float rad = DEG2RAD( degrees );
	SinCos(rad, &s, &c);
	zrot[0][0] = zrot[1][1] = c;
	zrot[0][1] = s;
	zrot[1][0] = -s;
#endif

	R_ConcatRotations( m, zrot, tmpmat );
	R_ConcatRotations( tmpmat, im, rot );

	for ( i = 0; i < 3; i++ )
	{
		dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2];
	}
}


int SignbitsForPlane (cplane_t *out)
{
	int	bits, j;

	// for fast box on planeside test
	bits = 0;
	for (j=0 ; j<3 ; j++)
	{
		if (out->normal[j] < 0)
			bits |= 1<<j;
	}
	return bits;
}


void R_SetFrustum ()
{
	int		i;

/*	if (r_newrefdef.fov_x == 90) 
	{
		//Speedup Small Calculations
		VectorAdd (vpn, vright, frustum[0].normal);
		VectorSubtract (vpn, vright, frustum[1].normal);

		VectorAdd (vpn, vup, frustum[2].normal);
		VectorSubtract (vpn, vup, frustum[3].normal);
	}
	else
*/	{
		// rotate VPN right by FOV_X/2 degrees
		RotatePointAroundVector( frustum[0].normal, vup, vpn, -(90-r_newrefdef.fov_x / 2 ) );
		// rotate VPN left by FOV_X/2 degrees
		RotatePointAroundVector( frustum[1].normal, vup, vpn, 90-r_newrefdef.fov_x / 2 );
		// rotate VPN up by FOV_X/2 degrees
		RotatePointAroundVector( frustum[2].normal, vright, vpn, 90-r_newrefdef.fov_y / 2 );
		// rotate VPN down by FOV_X/2 degrees
		RotatePointAroundVector( frustum[3].normal, vright, vpn, -( 90 - r_newrefdef.fov_y / 2 ) );
	}

	for (i=0 ; i<4 ; i++)
	{
		frustum[i].type = PLANE_ANYZ;
		frustum[i].dist = DotProduct (r_origin, frustum[i].normal);
		frustum[i].signbits = SignbitsForPlane (&frustum[i]);
	}
}

/*	no more used!
void MYgluPerspective( double fovy, double aspect, double zNear, double zFar)
{
	double xmin, xmax, ymin, ymax;

	ymax = zNear * tan( fovy * M_PI / 360.0 );
	ymin = -ymax;

	xmin = ymin * aspect;
	xmax = ymax * aspect;

	glFrustum( xmin, xmax, ymin, ymax, zNear, zFar );
}
*/


// Matrix with infinite far clipping plane
//  - optimized with static matrix.
//	- "nudge" is now defined as static const.
// 	- p[2][3] was "-nudge" [causing heavy z-fighting on MacOS X], but has to be -1.0.
//	  This issue indicates less precision on the Windows side.
//	- replaced the custom cotan () with 1.0 / tan () [cotan () = cos () / sin ()].

GLdouble	nudge = 1.0 - 1.0 / ((GLdouble) (1<<23));

GLdouble	p[4][4]	=	{
						{ 0.0,    0.0,    0.0,    0.0 },
						{ 0.0,    0.0,    0.0,    0.0 },
						{ 0.0,    0.0,    0.0,   -1.0 },
						{ 0.0,    0.0,    0.0,    0.0 }
						};

void	MyGlPerspective (GLdouble fov, GLdouble aspectr, GLdouble zNear)
{
	GLdouble	fov1, fov2;

	if(r_worldmodel && CL_PMpointcontents(r_origin) & (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME))
	{
		float	s, c, time = r_newrefdef.time;
		SinCos(time, &s, &c);
		fov1 = (fov - 2 * (1 - s)) * M_PI / 360.0;
		fov2 = (fov - 2 * (1 - c)) * M_PI / 360.0;
	}
	else
		fov1 = fov2 = fov * M_PI / 360.0;

	p[0][0] = 1.0 / (aspectr * tan (fov1));
	p[1][1] = 1.0 / tan (fov2);
	p[2][2] = -nudge;
	p[3][2] = -2.0 * zNear * nudge;

	glLoadMatrixd (&p[0][0]);
}

/*
inline void Mat4_Transpose(const float in[16], float out[16])
{
	out[0] = in[0];
	out[1] = in[4];
	out[2] = in[8];
	out[3] = in[12];
	out[4] = in[1];
	out[5] = in[5];
	out[6] = in[9];
	out[7] = in[13];
	out[8] = in[2];
	out[9] = in[6];
	out[10] = in[10];
	out[11] = in[14];
	out[12] = in[3];
	out[13] = in[7];
	out[14] = in[11];
	out[15] = in[15];
}
*/

float Mat4_MultiplyVectorLite(const float m[16], const vec3_t in/*, vec3_t out*/)
{
	float	s;

	s = m[12] * in[0] + m[13] * in[1] + m[14] * in[2] + m[15];

	if (s == 0.0)
	{
///		VectorClear(out);
		return 0;
	}
	else if (s == 1.0)
	{
///		out[0] = m[0] * in[0] + m[1] * in[1] + m[2] * in[2] + m[3];
///		out[1] = m[4] * in[0] + m[5] * in[1] + m[6] * in[2] + m[7];
		return m[8] * in[0] + m[9] * in[1] + m[10] * in[2] + m[11];
	}
	else
	{
		s = 1.0 / s;
///		out[0] = (m[0] * in[0] + m[1] * in[1] + m[2] * in[2] + m[3]) * s;
///		out[1] = (m[4] * in[0] + m[5] * in[1] + m[6] * in[2] + m[7]) * s;
		return (m[8] * in[0] + m[9] * in[1] + m[10] * in[2] + m[11]) * s;
	}
}


void	ExtractFrustum()
{
	float   t;

	/* Combine the two matrices (multiply projection by modelview) */
	clip[ 0] = r_world_matrix[ 0] * r_project_matrix[ 0] + r_world_matrix[ 1] * r_project_matrix[ 4] + r_world_matrix[ 2] * r_project_matrix[ 8] + r_world_matrix[ 3] * r_project_matrix[12];
	clip[ 1] = r_world_matrix[ 0] * r_project_matrix[ 1] + r_world_matrix[ 1] * r_project_matrix[ 5] + r_world_matrix[ 2] * r_project_matrix[ 9] + r_world_matrix[ 3] * r_project_matrix[13];
	clip[ 2] = r_world_matrix[ 0] * r_project_matrix[ 2] + r_world_matrix[ 1] * r_project_matrix[ 6] + r_world_matrix[ 2] * r_project_matrix[10] + r_world_matrix[ 3] * r_project_matrix[14];
	clip[ 3] = r_world_matrix[ 0] * r_project_matrix[ 3] + r_world_matrix[ 1] * r_project_matrix[ 7] + r_world_matrix[ 2] * r_project_matrix[11] + r_world_matrix[ 3] * r_project_matrix[15];

	clip[ 4] = r_world_matrix[ 4] * r_project_matrix[ 0] + r_world_matrix[ 5] * r_project_matrix[ 4] + r_world_matrix[ 6] * r_project_matrix[ 8] + r_world_matrix[ 7] * r_project_matrix[12];
	clip[ 5] = r_world_matrix[ 4] * r_project_matrix[ 1] + r_world_matrix[ 5] * r_project_matrix[ 5] + r_world_matrix[ 6] * r_project_matrix[ 9] + r_world_matrix[ 7] * r_project_matrix[13];
	clip[ 6] = r_world_matrix[ 4] * r_project_matrix[ 2] + r_world_matrix[ 5] * r_project_matrix[ 6] + r_world_matrix[ 6] * r_project_matrix[10] + r_world_matrix[ 7] * r_project_matrix[14];
	clip[ 7] = r_world_matrix[ 4] * r_project_matrix[ 3] + r_world_matrix[ 5] * r_project_matrix[ 7] + r_world_matrix[ 6] * r_project_matrix[11] + r_world_matrix[ 7] * r_project_matrix[15];

	clip[ 8] = r_world_matrix[ 8] * r_project_matrix[ 0] + r_world_matrix[ 9] * r_project_matrix[ 4] + r_world_matrix[10] * r_project_matrix[ 8] + r_world_matrix[11] * r_project_matrix[12];
	clip[ 9] = r_world_matrix[ 8] * r_project_matrix[ 1] + r_world_matrix[ 9] * r_project_matrix[ 5] + r_world_matrix[10] * r_project_matrix[ 9] + r_world_matrix[11] * r_project_matrix[13];
	clip[10] = r_world_matrix[ 8] * r_project_matrix[ 2] + r_world_matrix[ 9] * r_project_matrix[ 6] + r_world_matrix[10] * r_project_matrix[10] + r_world_matrix[11] * r_project_matrix[14];
	clip[11] = r_world_matrix[ 8] * r_project_matrix[ 3] + r_world_matrix[ 9] * r_project_matrix[ 7] + r_world_matrix[10] * r_project_matrix[11] + r_world_matrix[11] * r_project_matrix[15];

	clip[12] = r_world_matrix[12] * r_project_matrix[ 0] + r_world_matrix[13] * r_project_matrix[ 4] + r_world_matrix[14] * r_project_matrix[ 8] + r_world_matrix[15] * r_project_matrix[12];
	clip[13] = r_world_matrix[12] * r_project_matrix[ 1] + r_world_matrix[13] * r_project_matrix[ 5] + r_world_matrix[14] * r_project_matrix[ 9] + r_world_matrix[15] * r_project_matrix[13];
	clip[14] = r_world_matrix[12] * r_project_matrix[ 2] + r_world_matrix[13] * r_project_matrix[ 6] + r_world_matrix[14] * r_project_matrix[10] + r_world_matrix[15] * r_project_matrix[14];
	clip[15] = r_world_matrix[12] * r_project_matrix[ 3] + r_world_matrix[13] * r_project_matrix[ 7] + r_world_matrix[14] * r_project_matrix[11] + r_world_matrix[15] * r_project_matrix[15];

	/* Extract the numbers for the RIGHT plane */
	frustumPlanes[0][0] = clip[ 3] - clip[ 0];
	frustumPlanes[0][1] = clip[ 7] - clip[ 4];
	frustumPlanes[0][2] = clip[11] - clip[ 8];
	frustumPlanes[0][3] = clip[15] - clip[12];

	/* Normalize the result */
	t = RSqrt( frustumPlanes[0][0] * frustumPlanes[0][0] + frustumPlanes[0][1] * frustumPlanes[0][1] + frustumPlanes[0][2] * frustumPlanes[0][2] );
	frustumPlanes[0][0] *= t;
	frustumPlanes[0][1] *= t;
	frustumPlanes[0][2] *= t;
	frustumPlanes[0][3] *= t;

	/* Extract the numbers for the LEFT plane */
	frustumPlanes[1][0] = clip[ 3] + clip[ 0];
	frustumPlanes[1][1] = clip[ 7] + clip[ 4];
	frustumPlanes[1][2] = clip[11] + clip[ 8];
	frustumPlanes[1][3] = clip[15] + clip[12];

	/* Normalize the result */
	t = RSqrt( frustumPlanes[1][0] * frustumPlanes[1][0] + frustumPlanes[1][1] * frustumPlanes[1][1] + frustumPlanes[1][2] * frustumPlanes[1][2] );
	frustumPlanes[1][0] *= t;
	frustumPlanes[1][1] *= t;
	frustumPlanes[1][2] *= t;
	frustumPlanes[1][3] *= t;

	/* Extract the BOTTOM plane */
	frustumPlanes[2][0] = clip[ 3] + clip[ 1];
	frustumPlanes[2][1] = clip[ 7] + clip[ 5];
	frustumPlanes[2][2] = clip[11] + clip[ 9];
	frustumPlanes[2][3] = clip[15] + clip[13];

	/* Normalize the result */
	t = RSqrt( frustumPlanes[2][0] * frustumPlanes[2][0] + frustumPlanes[2][1] * frustumPlanes[2][1] + frustumPlanes[2][2] * frustumPlanes[2][2] );
	frustumPlanes[2][0] *= t;
	frustumPlanes[2][1] *= t;
	frustumPlanes[2][2] *= t;
	frustumPlanes[2][3] *= t;

	/* Extract the TOP plane */
	frustumPlanes[3][0] = clip[ 3] - clip[ 1];
	frustumPlanes[3][1] = clip[ 7] - clip[ 5];
	frustumPlanes[3][2] = clip[11] - clip[ 9];
	frustumPlanes[3][3] = clip[15] - clip[13];

	/* Normalize the result */
	t = RSqrt( frustumPlanes[3][0] * frustumPlanes[3][0] + frustumPlanes[3][1] * frustumPlanes[3][1] + frustumPlanes[3][2] * frustumPlanes[3][2] );
	frustumPlanes[3][0] *= t;
	frustumPlanes[3][1] *= t;
	frustumPlanes[3][2] *= t;
	frustumPlanes[3][3] *= t;

	/* Extract the FAR plane */
	frustumPlanes[4][0] = clip[ 3] - clip[ 2];
	frustumPlanes[4][1] = clip[ 7] - clip[ 6];
	frustumPlanes[4][2] = clip[11] - clip[10];
	frustumPlanes[4][3] = clip[15] - clip[14];

	/* Normalize the result */
	t = RSqrt( frustumPlanes[4][0] * frustumPlanes[4][0] + frustumPlanes[4][1] * frustumPlanes[4][1] + frustumPlanes[4][2] * frustumPlanes[4][2] );
	frustumPlanes[4][0] *= t;
	frustumPlanes[4][1] *= t;
	frustumPlanes[4][2] *= t;
	frustumPlanes[4][3] *= t;

	/* Extract the NEAR plane */
	frustumPlanes[5][0] = clip[ 3] + clip[ 2];
	frustumPlanes[5][1] = clip[ 7] + clip[ 6];
	frustumPlanes[5][2] = clip[11] + clip[10];
	frustumPlanes[5][3] = clip[15] + clip[14];

	/* Normalize the result */
	t = RSqrt( frustumPlanes[5][0] * frustumPlanes[5][0] + frustumPlanes[5][1] * frustumPlanes[5][1] + frustumPlanes[5][2] * frustumPlanes[5][2] );
	frustumPlanes[5][0] *= t;
	frustumPlanes[5][1] *= t;
	frustumPlanes[5][2] *= t;
	frustumPlanes[5][3] *= t;
}


void R_SetupGL ()
{
	float	screenaspect;
	int		x, x2, y2, y, w, h;

	//
	// set up viewport
	//
	if (r_mirror)
	{
		x = y2 = 0;
		w = r_newrefdef.width;
		h = r_newrefdef.height;
	}
	else
	{
		x = floor(float(r_newrefdef.x * vid.width / vid.width));
		x2 = ceil(float((r_newrefdef.x + r_newrefdef.width) * vid.width / vid.width));
		y = floor(float(vid.height - r_newrefdef.y * vid.height / vid.height));
		y2 = ceil(float(vid.height - (r_newrefdef.y + r_newrefdef.height) * vid.height / vid.height));

		w = x2 - x;
		h = y - y2;
	}

	glViewport (x, y2, w, h);

	//
	// set up projection matrix
	//
	if (r_mirror)
		screenaspect = 1;
	else
		screenaspect = (float)r_newrefdef.width/r_newrefdef.height;
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity ();

///	MYgluPerspective (r_newrefdef.fov_y,  screenaspect,  4,  65536);//4096);		/// FIXED by BERSERKER: при малом Z-Far - глюки с ShadowVolumes при больших LightRadius
MyGlPerspective(r_newrefdef.fov_y, (float)r_newrefdef.width/r_newrefdef.height, 4);	/// Infinity Z-Far !!!

	if (r_mirror)
	{
		//If we are mirroring we want to use the screen projection matrix
		//not the texture (=> we just calculated this) one.
///	glLoadIdentity();
///	glMultMatrixf(r_project_matrix);
		glLoadMatrixf(r_project_matrix);

		if (mirror_plane->plane.normal[2])
			glScalef (1, -1, 1);
		else
			glScalef (-1, 1, 1);
		if(r_drawsun->value)
			glGetFloatv (GL_PROJECTION_MATRIX, mir_project_matrix);
	}
	else
	{
		glGetFloatv (GL_PROJECTION_MATRIX, r_project_matrix);		//Store it for later mirror use
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glTranslatef(0.5, 0.5, 0);
		glScalef(0.5, 0.5, 0);
		glMultMatrixf(r_project_matrix);
		glGetFloatv (GL_TEXTURE_MATRIX, r_texture_matrix);
		glLoadIdentity();
///		glMatrixMode(GL_MODELVIEW);
	}

	glCullFace(GL_FRONT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity ();

	glRotatef (-90,  1, 0, 0);	    // put Z going up
	glRotatef (90,  0, 0, 1);	    // put Z going up
	glRotatef (-r_newrefdef.viewangles[2],  1, 0, 0);
	glRotatef (-r_newrefdef.viewangles[0],  0, 1, 0);
	glRotatef (-r_newrefdef.viewangles[1],  0, 0, 1);
	glTranslatef (-r_origin[0],  -r_origin[1],  -r_origin[2]);

	glGetFloatv (GL_MODELVIEW_MATRIX, r_world_matrix);
	glGetIntegerv (GL_VIEWPORT, (int *) r_viewport);

	ExtractFrustum ();
//	Mat4_Transpose(clip, mvpMatrix);

	//
	// set drawing parms
	//
///	if (r_cull->value)
		glEnable(GL_CULL_FACE);
///	else
///		glDisable(GL_CULL_FACE);

	glDisable(GL_BLEND);
	glDisable(GL_ALPHA_TEST);
	glEnable(GL_DEPTH_TEST);
}


byte *Mod_DecompressVis (byte *in, model_t *model)
{
	int		c;
	byte	*out;
	int		row;

	row = (model->vis->numclusters+7)>>3;	
	out = decompressed;

	if (!in)
	{	// no vis info, so make all visible
		while (row)
		{
			*out++ = 0xff;
			row--;
		}
		return decompressed;		
	}

	do
	{
		if (*in)
		{
			*out++ = *in++;
			continue;
		}
	
		c = in[1];
		in += 2;
		while (c)
		{
			*out++ = 0;
			c--;
		}
	} while (out - decompressed < row);
	
	return decompressed;
}


byte *Mod_ClusterPVS (int cluster, model_t *model)
{
	if (cluster == -1 || !model->vis)
		return mod_novis;
	return Mod_DecompressVis ( (byte *)model->vis + model->vis->bitofs[cluster][DVIS_PVS], model);
}


/*
===============
R_MarkLeaves

Mark the leaves and nodes that are in the PVS for the current
cluster
===============
*/
void R_MarkLeaves ()
{
	byte	*vis;
	byte	fatvis[MAX_MAP_LEAFS/8];
	mnode_t	*node;
	int		i, c;
	mleaf_t	*leaf;
	int		cluster;

	if (r_oldviewcluster == r_viewcluster && r_oldviewcluster2 == r_viewcluster2 /*&& !r_novis->value*/ && r_viewcluster != -1)
		return;

	// development aid to let you run around and see exactly where
	// the pvs ends
	if (r_lockpvs->value)
		return;

	r_visframecount++;
	r_oldviewcluster = r_viewcluster;
	r_oldviewcluster2 = r_viewcluster2;

	if (/*r_novis->value ||*/ r_viewcluster == -1 || !r_worldmodel->vis)
	{
		// mark everything
		for (i=0 ; i<r_worldmodel->numleafs ; i++)
			r_worldmodel->leafs[i].visframe = r_visframecount;
		for (i=0 ; i<r_worldmodel->numnodes ; i++)
			r_worldmodel->nodes[i].visframe = r_visframecount;
		Memset(&viewvis, 0xff, (r_worldmodel->numleafs+7)>>3);	// all visible
		return;
	}

	vis = Mod_ClusterPVS (r_viewcluster, r_worldmodel);
	// may have to combine two clusters because of solid water boundaries
	if (r_viewcluster2 != r_viewcluster)
	{
		Memcpy (fatvis, vis, (r_worldmodel->numleafs+7)>>3);
		vis = Mod_ClusterPVS (r_viewcluster2, r_worldmodel);
		c = (r_worldmodel->numleafs+31)/32;
		for (i=0 ; i<c ; i++)
			((int *)fatvis)[i] |= ((int *)vis)[i];
		vis = fatvis;
	}

	Memcpy(&viewvis, vis, (r_worldmodel->numleafs+7)>>3);

	for (i=0,leaf=r_worldmodel->leafs ; i<r_worldmodel->numleafs ; i++, leaf++)
	{
		cluster = leaf->cluster;
		if (cluster == -1)
			continue;
		if (vis[cluster>>3] & (1<<(cluster&7)))
		{
			node = (mnode_t *)leaf;
			do
			{
				if (node->visframe == r_visframecount)
					break;
				node->visframe = r_visframecount;
				node = node->parent;
			} while (node);
		}
	}
}


void R_ClearSkyBox ()
{
	int		i;

	for (i=0 ; i<6 ; i++)
	{
		skymins[0][i] = skymins[1][i] = 9999;
		skymaxs[0][i] = skymaxs[1][i] = -9999;
	}
}


// this is the slow, general version
int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p)
{
	int		i;
	float	dist1, dist2;
	int		sides;
	vec3_t	corners[2];

	for (i=0 ; i<3 ; i++)
	{
		if (p->normal[i] < 0)
		{
			corners[0][i] = emins[i];
			corners[1][i] = emaxs[i];
		}
		else
		{
			corners[1][i] = emins[i];
			corners[0][i] = emaxs[i];
		}
	}
	dist1 = DotProduct (p->normal, corners[0]) - p->dist;
	dist2 = DotProduct (p->normal, corners[1]) - p->dist;
	sides = 0;
	if (dist1 >= 0)
		sides = 1;
	if (dist2 < 0)
		sides |= 2;

	return sides;
}


/*
=================
R_CullBox

Returns true if the box is completely outside the frustom
=================
*/
bool R_CullBox (vec3_t mins, vec3_t maxs)
{
	int		i;

///	if (r_nocull->value)
///		return false;

	for (i=0 ; i<4 ; i++)
		if ( BOX_ON_PLANE_SIDE(mins, maxs, &frustum[i]) == 2)
			return true;
	return false;
}

bool R_CullBox_ (vec3_t mins, vec3_t maxs, cplane_t *frust)
{
	int		i;

///	if (r_nocull->value)
///		return false;

	for (i=0 ; i<4 ; i++)
		if ( BoxOnPlaneSide2(mins, maxs, &frust[i]) == 2)
			return true;
	return false;
}


bool R_CullBox5 (vec3_t mins, vec3_t maxs, cplane_t *frust)
{
	int		i;

///	if (r_nocull->value)
///		return false;

	for (i=0 ; i<5 ; i++)
		if ( BoxOnPlaneSide2(mins, maxs, &frust[i]) == 2)
			return true;
	return false;
}


void DrawSkyPolygon (int nump, vec3_t vecs)
{
	int		i,j;
	vec3_t	v, av;
	float	s, t, dv;
	int		axis;
	float	*vp;

	c_sky++;
	// decide which face it maps to
	VectorClear (v);
	for (i=0, vp=vecs ; i<nump ; i++, vp+=3)
		VectorAdd (vp, v, v);

	av[0] = fabs(v[0]);
	av[1] = fabs(v[1]);
	av[2] = fabs(v[2]);
	if (av[0] > av[1] && av[0] > av[2])
	{
		if (v[0] < 0)
			axis = 1;
		else
			axis = 0;
	}
	else if (av[1] > av[2] && av[1] > av[0])
	{
		if (v[1] < 0)
			axis = 3;
		else
			axis = 2;
	}
	else
	{
		if (v[2] < 0)
			axis = 5;
		else
			axis = 4;
	}

	// project new texture coords
	for (i=0 ; i<nump ; i++, vecs+=3)
	{
		j = vec_to_st[axis][2];
		if (j > 0)
			dv = vecs[j - 1];
		else
			dv = -vecs[-j - 1];
		if (dv < 0.001)
			continue;	// don't divide by zero
		j = vec_to_st[axis][0];
		if (j < 0)
			s = -vecs[-j -1] / dv;
		else
			s = vecs[j-1] / dv;
		j = vec_to_st[axis][1];
		if (j < 0)
			t = -vecs[-j -1] / dv;
		else
			t = vecs[j-1] / dv;

		if (s < skymins[0][axis])
			skymins[0][axis] = s;
		if (t < skymins[1][axis])
			skymins[1][axis] = t;
		if (s > skymaxs[0][axis])
			skymaxs[0][axis] = s;
		if (t > skymaxs[1][axis])
			skymaxs[1][axis] = t;
	}
}


void ClipSkyPolygon (int nump, vec3_t vecs, int stage)
{
	float	*norm;
	float	*v;
	bool	front, back;
	float	d, e;
	float	dists[MAX_CLIP_VERTS];
	int		sides[MAX_CLIP_VERTS];
	vec3_t	newv[2][MAX_CLIP_VERTS];
	int		newc[2];
	int		i, j;

	if (nump > MAX_CLIP_VERTS-2)
		Com_Error (ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS");
	if (stage == 6)
	{	// fully clipped, so draw it
		DrawSkyPolygon (nump, vecs);
		return;
	}

	front = back = false;
	norm = skyclip[stage];
	for (i=0, v = vecs ; i<nump ; i++, v+=3)
	{
		d = DotProduct (v, norm);
		if (d > ON_EPSILON)
		{
			front = true;
			sides[i] = SIDE_FRONT;
		}
		else if (d < -ON_EPSILON)
		{
			back = true;
			sides[i] = SIDE_BACK;
		}
		else
			sides[i] = SIDE_ON;
		dists[i] = d;
	}

	if (!front || !back)
	{	// not clipped
		ClipSkyPolygon (nump, vecs, stage+1);
		return;
	}

	// clip it
	sides[i] = sides[0];
	dists[i] = dists[0];
	VectorCopy (vecs, (vecs+(i*3)) );
	newc[0] = newc[1] = 0;

	for (i=0, v = vecs ; i<nump ; i++, v+=3)
	{
		switch (sides[i])
		{
		case SIDE_FRONT:
			VectorCopy (v, newv[0][newc[0]]);
			newc[0]++;
			break;
		case SIDE_BACK:
			VectorCopy (v, newv[1][newc[1]]);
			newc[1]++;
			break;
		case SIDE_ON:
			VectorCopy (v, newv[0][newc[0]]);
			newc[0]++;
			VectorCopy (v, newv[1][newc[1]]);
			newc[1]++;
			break;
		}

		if (sides[i] == SIDE_ON || sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
			continue;

		d = dists[i] / (dists[i] - dists[i+1]);
		for (j=0 ; j<3 ; j++)
		{
			e = v[j] + d*(v[j+3] - v[j]);
			newv[0][newc[0]][j] = e;
			newv[1][newc[1]][j] = e;
		}
		newc[0]++;
		newc[1]++;
	}

	// continue
	ClipSkyPolygon (newc[0], newv[0][0], stage+1);
	ClipSkyPolygon (newc[1], newv[1][0], stage+1);
}


void R_AddSkySurface (msurface_t *fa)
{
	int			i;
	vec3_t		verts[MAX_CLIP_VERTS];
	glpoly_t	*p;

	// calculate vertex values for sky box
	for (p=fa->polys ; p ; p=p->next)
	{
		for (i=0 ; i<p->numverts ; i++)
		{
			VectorSubtract (p->verts[i], r_origin, verts[i]);
		}
		ClipSkyPolygon (p->numverts, verts[0], 0);
	}
}


void GL_RenderLightmappedPolyFog( msurface_t *surf, fog_t *fog )
{
	int		i, nv = surf->numedges;
	float	*v;
	mtexinfo_t	*tx = R_TextureAnimation( surf->texinfo );
	image_t *image = tx->image;
	glpoly_t *p;
	float	scroll, mmm;
	float	color[3];

	color[0] = fog->color[0];
	color[1] = fog->color[1];
	color[2] = fog->color[2];

	if(image->AlphaTest /*has_alpha*/)
	{
///		glEnable(GL_ALPHA_TEST);	// Поддержка "дырявых" текстур
		GL_MBind2D( GL_TEXTURE0, image->texnum );
		glEnable(GL_TEXTURE_2D);

		gl_TexEnv( GL_COMBINE_ARB );
		GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
		GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_PRIMARY_COLOR_ARB );
		GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
		GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_TEXTURE );
		GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA );
		GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_MODULATE );
		GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB );
		GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
		GL_TexEnv( GL_SOURCE1_ALPHA_ARB, GL_TEXTURE );
		GL_TexEnv( GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA );
///		GL_BlendFunc (GL_SRC_COLOR, GL_ONE_MINUS_SRC_ALPHA);

		if (surf->texinfo->flags & SURF_FLOWING)
		{
			scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
			if(scroll == 0.0)
				scroll = -64.0;
		}
		else
			scroll = 0;
	}

	p = surf->polys;
	v = p->verts[0];
	glBegin (GL_TRIANGLE_FAN);
	for (i=0 ; i < nv; i++, v+= VERTEXSIZE)
	{
		mmm = FogDist(fog, v);
		GL_Color3f(color[0]*mmm, color[1]*mmm, color[2]*mmm);

		if(image->AlphaTest /*has_alpha*/)
			glTexCoord2f (v[3]+scroll, v[4]);	///	glMultiTexCoord2fARB( GL_TEXTURE0, (v[3]+scroll), v[4]);

		glVertex3fv (v);
	}
	glEnd ();

	if(image->AlphaTest /*has_alpha*/)
	{
///		glDisable(GL_ALPHA_TEST);	// Поддержка "дырявых" текстур
		glDisable(GL_TEXTURE_2D);
		gl_TexEnv( GL_COMBINE_ARB );
		GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
		GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_PRIMARY_COLOR_ARB );
		GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
		GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_TEXTURE );
		GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
		GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_MODULATE );
		GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB );
		GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
		GL_TexEnv( GL_SOURCE1_ALPHA_ARB, GL_TEXTURE );
		GL_TexEnv( GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA );
///		GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	}
}


inline unsigned R_GetIMGFromName(image_t *image, image_t *img)
{
	if(!image->fx_image)
	{
		if(image->fx_image_name[0])
		{
			image->fx_image = GL_FindImage(image->fx_image_name, it_fx, false, 0, false, 0);
			if(!image->fx_image)
				image->fx_image = img;
		}
		else
			image->fx_image = img;
	}
	return image->fx_image->texnum;
}


inline unsigned R_GetIMGFromName2(image_t *image, image_t *img)
{
	if (image->NoDetailBump)
		return r_nodetailbump->texnum;

	if(!image->fx_detail_image)
	{
		if(image->fx_detail_image_name[0])
		{
			image->fx_detail_image = GL_FindImage(image->fx_detail_image_name, it_bump, false, 0, false, 0);
			if(!image->fx_detail_image)
				image->fx_detail_image = img;
		}
		else
			image->fx_detail_image = img;
	}
	return image->fx_detail_image->texnum;
}


/*
================
GL_FreeUnusedImages

Any image that was not touched on this registration sequence
will be freed.
================
*/
void GL_FreeUnusedImages ()
{
	int		i;
	image_t	*image;

	// never free attenuation, normcube_texture_object, etc...
	r_notexture->registration_sequence =
	env_texture_2d_r->registration_sequence =
	env_texture_2d_g->registration_sequence =
	env_texture_2d_b->registration_sequence =
	env_texture_2d_rg->registration_sequence =
	env_texture_2d_rb->registration_sequence =
	env_texture_2d_gb->registration_sequence =
	env_texture_2d_rgb->registration_sequence =
	chrome_texture_2d->registration_sequence =
	env_texture_2d_world->registration_sequence =
	chrome_texture_2d_world->registration_sequence =
	r_particletexture->registration_sequence =
	r_defaultbump->registration_sequence =
	r_nodetailbump->registration_sequence =
	r_dark->registration_sequence =
	r_dark2->registration_sequence =
	r_white->registration_sequence =
	r_screenTexture->registration_sequence =
	r_bloomTexture->registration_sequence =
	normcube_texture_object->registration_sequence =
	atten3d_texture_object->registration_sequence =
	bullet_mrk_object->registration_sequence =
	laser_mrk_object->registration_sequence =
	blood_mrk_object[0]->registration_sequence =
	blood_mrk_object[1]->registration_sequence =
	blood_mrk_object[2]->registration_sequence =
	blood_mrk_object[3]->registration_sequence =
	blood_mrk_object[4]->registration_sequence =
	blood_mrk_object[5]->registration_sequence =
	blood_mrk_object[6]->registration_sequence =
	blood_mrk_object[7]->registration_sequence =
	greenblood_mrk_object[0]->registration_sequence =
	greenblood_mrk_object[1]->registration_sequence =
	greenblood_mrk_object[2]->registration_sequence =
	greenblood_mrk_object[3]->registration_sequence =
	greenblood_mrk_object[4]->registration_sequence =
	greenblood_mrk_object[5]->registration_sequence =
	greenblood_mrk_object[6]->registration_sequence =
	greenblood_mrk_object[7]->registration_sequence =
	foot_mrk_object[0]->registration_sequence =
	foot_mrk_object[1]->registration_sequence =
	foot_mrk_object[2]->registration_sequence =
	foot_mrk_object[3]->registration_sequence =
	foot_mrk_object[4]->registration_sequence =
	explosion_mrk_object->registration_sequence =
	bfg_explosion_mrk_object->registration_sequence =
	sun_object->registration_sequence =
	sun1_object->registration_sequence =
	sun2_object->registration_sequence =
	smoke_object->registration_sequence =
	blood_object->registration_sequence =
	bubble_object->registration_sequence =
	fly_object->registration_sequence =
	waterwake_object->registration_sequence =
	waterplume_object->registration_sequence =
	tracer_object->registration_sequence =
	splat_object->registration_sequence = registration_sequence;

	if (r_shader->value == SHADER_ARB6 && r_detailed_bump->value)
		r_defaultbump_detail->registration_sequence = registration_sequence;

	for(i=0; i<MAX_GLOBAL_FILTERS; i++)
		decal_texture_object[i]->registration_sequence = registration_sequence;

	for(i=0; i<8; i++)
		rail_mrk_object[i]->registration_sequence = registration_sequence;

	if (!r_simple->value)
		for(i=0; i<MAX_GLOBAL_FILTERS; i++)
			filtercube_texture_object[i]->registration_sequence = registration_sequence;

	for(i=0; i<MAX_CAUSTICS; i++)
		r_caustic[i]->registration_sequence = registration_sequence;

	if(dst_texture)
		dst_texture->registration_sequence = registration_sequence;

	if(dst_texture_water)
		dst_texture_water->registration_sequence = registration_sequence;

	for (i=0; i<MAX_MIRRORS; i++)
		mirrors[i].texture->registration_sequence = registration_sequence;

	for (i=0; i<MAX_RFX_IMAGES; i++)
	{
		if (glfx_pain[i])
			glfx_pain[i]->registration_sequence = registration_sequence;
		if (glfx_burn[i])
			glfx_burn[i]->registration_sequence = registration_sequence;
	}
	if (glfx_mask)
		glfx_mask->registration_sequence = registration_sequence;
	if (glfx_maskenv)
		glfx_maskenv->registration_sequence = registration_sequence;
	if (glfx_underwater)
		glfx_underwater->registration_sequence = registration_sequence;
	if (glfx_drown)
		glfx_drown->registration_sequence = registration_sequence;

	for (i=0, image=gltextures ; i<numgltextures ; i++, image++)
	{
		if (image->registration_sequence == registration_sequence)
		{
			image->fx_image = NULL;
			image->fx_detail_image = NULL;

			if (image->fx_image_name[0])
			{
				if (image->fx == fx_chrome)
					R_GetIMGFromName(image, chrome_texture_2d);
				else if (image->fx == fx_power)
				{
					R_GetIMGFromName(image, env_texture_2d_r);
					R_GetIMGFromName(image, env_texture_2d_g);
					R_GetIMGFromName(image, env_texture_2d_b);
					R_GetIMGFromName(image, env_texture_2d_rg);
					R_GetIMGFromName(image, env_texture_2d_rb);
					R_GetIMGFromName(image, env_texture_2d_gb);
					R_GetIMGFromName(image, env_texture_2d_rgb);
				}
				else if (image->fx == fx_map)
					R_GetIMGFromName(image, chrome_texture_2d);
				else if ((image->fx == fx_distort) && gl_config.screentexture && gl_config.arb_distort)
					R_GetIMGFromName(image, dst_texture);
			}

			if (r_shader->value == SHADER_ARB6 && r_detailed_bump->value)
				if (image->fx_detail_image_name[0])
					R_GetIMGFromName2(image, r_defaultbump_detail);

			if(image->material)
				image->material->registration_sequence = registration_sequence;
			continue;		// used this sequence
		}
		if (!image->registration_sequence)
			continue;		// free image_t slot
		if (image->registration_sequence == -1)
			goto kill;		// force to free image_t slot
		if (image->type == it_pic)
			continue;		// don't free pics
		if (image->type == it_fx)
			continue;		// don't free fx

kill:	// free it
		glDeleteTextures (1, &image->texnum);
		Memset (image, 0, sizeof(*image));
	}
}


void GL_RenderLightmappedPoly( msurface_t *surf, bool tris, bool parallax )
{
	int		i, nv = surf->numedges;
	float	*v, adds, addt;
	mtexinfo_t	*tx = R_TextureAnimation( surf->texinfo );
	image_t *image = tx->image;
	image_t *light = tx->light;
	glpoly_t *p;
	float	scroll;
	vec3_t	t, n, vieworg;
	clightstyle_t	*ls, *cl_ls;
	vec4_t			ls_rgb;
	bool			paralax = parallax && image->Parallax;

	c_brush_polys++;

	if(/*image->has_alpha && */image->AlphaTest && !paralax)
		glEnable(GL_ALPHA_TEST);	// Поддержка "дырявых" текстур

	if (paralax)	// enable parallax shader
	{
		glEnable(GL_VERTEX_PROGRAM_ARB);
		GL_BindProgramVP(vertex_program_ambient_parallax);
		glEnable(GL_FRAGMENT_PROGRAM_ARB);
		GL_BindProgramFP(fragment_program_ambient_parallax);
	}

	GL_MBind2D( GL_TEXTURE0, image->texnum );
	if (!VectorCompare(world_ambient_light, vec3_origin))
		GL_MBind2D( GL_TEXTURE1, r_white->texnum );
	else
		GL_MBind2D( GL_TEXTURE1, /*gl_state.lightmap_textures*/TEXNUM_LIGHTMAPS + surf->lightmaptexturenum );

	if(light)
	{
		GL_MBind2D( GL_TEXTURE2, light->texnum );
		glEnable(GL_TEXTURE_2D);
	}
	else if (paralax)	// АРБ-программа все равно заюзает текстуру, поэтому биндим "темноту"
		GL_MBind2D( GL_TEXTURE2, r_dark2->texnum );

	bool env = image->fx == fx_chrome;
	bool power = image->fx == fx_power;
	bool map = image->fx == fx_map;
	bool style = image->fx == fx_style;
	if(env || power || map || style)
	{
		GL_SelectTexture(GL_TEXTURE3);
		glEnable( GL_TEXTURE_2D );
		gl_TexEnv( GL_COMBINE_ARB );
		if(light && light->has_alpha)
		{
			GL_TexEnv( GL_COMBINE_RGB_ARB, GL_INTERPOLATE_ARB );
			if (style)
			{
				GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_CONSTANT_ARB );
				unsigned tmp = image->fx_s;
				ls = &sv_lightstyle[tmp & 0xff];
				cl_ls = &cl_lightstyle[(tmp>>8) & 0xff];
				float	temp = ls->val * cl_ls->val;
				ls_rgb[0] = temp * image->fx_t;
				ls_rgb[1] = temp * image->fx_scale_s;
				ls_rgb[2] = temp * image->fx_scale_t;
				glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, ls_rgb);
			}
			else
				GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
			GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
			GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
			GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
			GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_TEXTURE2 );
			GL_TexEnv( GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA );
			GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
			GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_TEXTURE0 );
			GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
		}
		else
		{
			GL_TexEnv( GL_COMBINE_RGB_ARB, GL_ADD );
			if (style)
			{
				GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_CONSTANT_ARB );
				unsigned tmp = image->fx_s;
				ls = &sv_lightstyle[tmp & 0xff];
				cl_ls = &cl_lightstyle[(tmp>>8) & 0xff];
				float	temp = ls->val * cl_ls->val;
				ls_rgb[0] = temp * image->fx_t;
				ls_rgb[1] = temp * image->fx_scale_s;
				ls_rgb[2] = temp * image->fx_scale_t;
				glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, ls_rgb);
			}
			else
				GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
			GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
			GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
			GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
			GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
			GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_TEXTURE0 );
			GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
		}

		if(env)
		{
			GL_Bind2D( R_GetIMGFromName(image, chrome_texture_2d_world) );
			VectorCopy(modelorg, vieworg);
			adds = addt = 0;
		}
		else if(power)
		{
			GL_Bind2D( R_GetIMGFromName(image, env_texture_2d_world) );
			vieworg[0] = 64;
			vieworg[1] = vieworg[2] = 0;
			adds = image->fx_s * cl.leveltime;
			addt = image->fx_t * cl.leveltime;
		}
		else if(map)
		{
			GL_Bind2D( R_GetIMGFromName(image, chrome_texture_2d_world) );
			adds = image->fx_s * cl.leveltime;
			addt = image->fx_t * cl.leveltime;
		}
//		else	// style
//		{
//		}
	}
	else if (paralax)	// АРБ-программа все равно заюзает текстуру, поэтому биндим "темноту"
			GL_MBind2D( GL_TEXTURE3, r_dark->texnum );

	if (surf->texinfo->flags & SURF_FLOWING)
	{
		scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
		if(scroll == 0.0)
			scroll = -64.0;
	}
	else
		scroll = 0;

	p = surf->polys;
	v = p->verts[0];
	glBegin (GL_TRIANGLE_FAN);
	for (i=0 ; i < nv; i++, v+= VERTEXSIZE)
	{
		glTexCoord2f (v[3]+scroll, v[4]);	///	glMultiTexCoord2fARB( GL_TEXTURE0, (v[3]+scroll), v[4]);
		glMultiTexCoord2fARB( GL_TEXTURE1, v[5], v[6]);
		if(paralax)
		{
			glMultiTexCoord3fvARB( GL_TEXTURE4, &v[7]);
			glMultiTexCoord3fvARB( GL_TEXTURE5, &v[10]);
			glMultiTexCoord3fvARB( GL_TEXTURE2, &v[13]);
		}
		else if(light)
			glMultiTexCoord2fARB( GL_TEXTURE2, (v[3]+scroll), v[4]);
		if(env || power)
		{
			VectorSubtract (v, vieworg, t);
			// project vector
			n[0] = v[7];
			n[1] = v[8];
			n[2] = RSqrt(DotProduct(t,t));
			glMultiTexCoord2fARB( GL_TEXTURE3, (t[0]*n[2] - n[0] + adds)*image->fx_scale_s, (t[1]*n[2] - n[1] + addt)*image->fx_scale_t);
		} else if(map)
			glMultiTexCoord2fARB( GL_TEXTURE3, (v[3]+adds)*image->fx_scale_s, (v[4]+addt)*image->fx_scale_t);
///		else if(style)
///			glMultiTexCoord3fvARB( GL_TEXTURE3, ls_rgb);

		glVertex3fv (v);
	}
	glEnd ();

	if(env || power || map || style)
	{
///		GL_SelectTexture(GL_TEXTURE3);
		glDisable( GL_TEXTURE_2D );
		gl_TexEnv( GL_REPLACE );
	}

	if(light)
	{
		GL_SelectTexture(GL_TEXTURE2);
		glDisable(GL_TEXTURE_2D);
	}

	if (paralax)	// disable parallax shader
	{
		glDisable(GL_VERTEX_PROGRAM_ARB);
		glDisable(GL_FRAGMENT_PROGRAM_ARB);
	}

	if(/*image->has_alpha && */image->AlphaTest)
		glDisable(GL_ALPHA_TEST);	// Поддержка "дырявых" текстур

/// Berserker //////////////////////////
	if (tris || image->hash == hash_showtexture || surf->texinfo == curtexinfo)
		goto force;

	if (!r_showtris->value)
		return;

	if (r_showtris->value != 1)
force:	glDisable (GL_DEPTH_TEST);

//	glEnable(GL_LINE_SMOOTH);
	if (!r_simple->value)
	{
		GL_SelectTexture( GL_TEXTURE2 );
		glDisable (GL_TEXTURE_2D);
	}
	GL_SelectTexture( GL_TEXTURE1 );
	glDisable (GL_TEXTURE_2D);
	GL_SelectTexture( GL_TEXTURE0 );
	glDisable (GL_TEXTURE_2D);
	GL_Color3f (1,1,0);

	p = surf->polys;
	v = p->verts[0];
	glBegin (GL_LINE_STRIP);
	for (i=0 ; i < nv; i++, v+= VERTEXSIZE)
		glVertex3fv (v);
	glVertex3fv (p->verts[0]);
	glEnd ();

	v = p->verts[0];
	glBegin (GL_LINES);
	for (i=0 ; i < nv; i++, v+= VERTEXSIZE)
	{
		glVertex3fv (v);
		glVertex3f (v[0]+8*v[7],v[1]+8*v[8],v[2]+8*v[9]);
	}
	glVertex3fv (p->verts[0]);
	glEnd ();

//	glDisable(GL_LINE_SMOOTH);
	GL_SelectTexture( GL_TEXTURE0 );
	glEnable (GL_TEXTURE_2D);
	GL_SelectTexture( GL_TEXTURE1 );
	glEnable (GL_TEXTURE_2D);

	if (r_showtris->value != 1 || tris || image->hash == hash_showtexture || surf->texinfo == curtexinfo)
		glEnable (GL_DEPTH_TEST);

///	if (!r_worldmodel->lightdata)
	if (!VectorCompare(world_ambient_light, vec3_origin))	// Новое правило: если есть worldspawn.ambient - то он действует, и главнее чем r_worldmodel->lightdata.
		GL_Color3fv(world_ambient_light);
	else
		GL_Color3f(1,1,1);
/////////////////////////////////////////

}


#pragma warning (disable:4035)
__declspec( naked ) long Q_ftol( float f )
{
	static int tmp777;
	__asm fld dword ptr [esp+4]
	__asm fistp tmp777
	__asm mov eax, tmp777
	__asm ret
}
#pragma warning (default:4035)

float SphereInFrustum( vec3_t o, float radius )
{
	int		p;
	float	d;

	for( p = 0; p < 6; p++ )
	{
		d = frustumPlanes[p][0] * o[0] + frustumPlanes[p][1] * o[1] + frustumPlanes[p][2] * o[2] + frustumPlanes[p][3];
		if( d <= -radius )
			return 0;
	}
	return d + radius;
}

/// аналогичен R_CullBox, но проверяет все 6 плоскостей фрустума, это позволяет отсекать объекты за спиной игрока
bool BoxOutsideFrustum(vec3_t mins, vec3_t maxs)
{
	int		i, j;
	float	dist1, dist2;
	vec3_t	corners[2];

///	if (r_nocull->value)
///		return false;

	for (i=0 ; i<6 ; i++)
	{
		for (j=0 ; j<3 ; j++)
		{
			if (frustumPlanes[i][j] < 0)
			{
				corners[0][j] = mins[j];
				corners[1][j] = maxs[j];
			}
			else
			{
				corners[1][j] = mins[j];
				corners[0][j] = maxs[j];
			}
		}

		dist1 = DotProduct (frustumPlanes[i], corners[0]) + frustumPlanes[i][3];
		dist2 = DotProduct (frustumPlanes[i], corners[1]) + frustumPlanes[i][3];
		if (dist1 < 0 && dist2 < 0)
			return true;
	}

	return false;
}

inline bool SurfInFrustum(msurface_t *s)
{
	if (s->polys)
		return !R_CullBox(s->mins, s->maxs);
	return true;
}


void R_RecursiveWorldNode (mnode_t *node)
{
	int			c, side, sidebit;
	cplane_t	*plane;
	msurface_t	*surf, **mark;
	mleaf_t		*pleaf;
	float		dot;
	image_t		*image;

	if (node->contents == CONTENTS_SOLID)
		return;		// solid

	if (node->visframe != r_visframecount)
		return;

	if (R_CullBox (node->minmaxs, node->minmaxs+3))
///	if (BoxOutsideFrustum (node->minmaxs, node->minmaxs+3))		// почему-то тормознее...
		return;

	if (r_mirror)
	{
		if (BOX_ON_PLANE_SIDE(node->minmaxs, node->minmaxs+3, &(mirror_plane->plane)) == mirror_clipside)
			return;

///		Tenebrae bug: обрезается дальние зоны если стоим близко от зеркала. Это неверно, ибо в зеркале д.б. все видно!
///		if (BOX_ON_PLANE_SIDE(node->minmaxs, node->minmaxs+3, &mirror_far_plane) == 1)
///			return;
	}

// if a leaf node, draw stuff
	if (node->contents != -1)
	{
		pleaf = (mleaf_t *)node;

		// check for door connected areas
		if (r_newrefdef.areabits)
		{
			if (! (r_newrefdef.areabits[pleaf->area>>3] & (1<<(pleaf->area&7)) ) )
				return;		// not visible
		}

		mark = pleaf->firstmarksurface;
		c = pleaf->nummarksurfaces;

		if (c)
		{
			do
			{
				if (SurfInFrustum(*mark))
					(*mark)->visframe = r_framecount;
				(*mark)->ent = NULL;
				mark++;
			} while (--c);
		}

		return;
	}

// node is just a decision point, so go down the apropriate sides

// find which side of the node we are on
	plane = node->plane;

	switch (plane->type)
	{
	case PLANE_X:
		dot = modelorg[0] - plane->dist;
		break;
	case PLANE_Y:
		dot = modelorg[1] - plane->dist;
		break;
	case PLANE_Z:
		dot = modelorg[2] - plane->dist;
		break;
	default:
		dot = DotProduct (modelorg, plane->normal) - plane->dist;
		break;
	}

	if (dot >= 0)
	{
		side = 0;
		sidebit = 0;
	}
	else
	{
		side = 1;
		sidebit = SURF_PLANEBACK;
	}

// recurse down the children, front side first
	R_RecursiveWorldNode (node->children[side]);

	// draw stuff
	for ( c = node->numsurfaces, surf = r_worldmodel->surfaces + node->firstsurface; c ; c--, surf++)
	{
		if (surf->visframe != r_framecount)
			continue;

		if ( (surf->flags & SURF_PLANEBACK) != sidebit )
			continue;		// wrong side

		surf->drawframe = r_framecount;			// Berserker: draw it
		if (!r_mirror)
		{
			if (gl_config.screentexture && gl_config.arb_distort && r_distort->value && surf->texinfo->image->fx == fx_distort /*&& (surf->texinfo->flags & (SURF_TRANS33|SURF_TRANS66)) */ /*&& !(surf->flags & SURF_DRAWFOG)*/)
			{
				R_GetIMGFromName(surf->texinfo->image, dst_texture);
				surf->distortchain = r_distort_surfaces;
				r_distort_surfaces = surf;
				if (need_gun_test && !need_gun_optimisation)	// Если требуется проверка и еще не нашли ближний дисторт-объект
				{
					float	disdis = r_distort_distance->value;
					vec3_t	mins, maxs;
					VectorSet(mins, surf->mins[0]-disdis, surf->mins[1]-disdis, surf->mins[2]-disdis);
					VectorSet(maxs, surf->maxs[0]+disdis, surf->maxs[1]+disdis, surf->maxs[2]+disdis);
					if (PointInBBox(r_origin, mins, maxs))
						need_gun_optimisation = true;
				}
			}

			if (r_mirrors->value && surf->texinfo->image->mirror && !drawing_sky_world)
				R_AddMirror(surf);
		}
		else
		{	// Если рисуем зеркало, то само это зеркало не рисуем (такое бывает если зеркало прозрачное и камера вблизи от него)
			if	((surf->plane->dist == mirror_plane->plane.dist) &&
				VectorCompare(surf->plane->normal, mirror_plane->plane.normal))
				continue;
		}

		if (surf->texinfo->flags & SURF_SKY)
		{	// just adds to visible sky bounds
			R_AddSkySurface (surf);
		}
		else if (surf->texinfo->flags & (SURF_TRANS33|SURF_TRANS66))
		{	// add to the translucent chain
			if(surf->flags & SURF_DRAWFOG)
			{
				surf->fogchain = r_fog_surfaces;
				r_fog_surfaces = surf;
			}

			surf->texturechain = r_alpha_surfaces;
			r_alpha_surfaces = surf;
		}
		else
		{
			if (!(surf->texinfo->flags & SURF_NODRAW))		/// Berserker: вообще не рисуем полигоны NODRAW
			{
				if ( !( surf->flags & SURF_DRAWTURB ) )
				{
///					GL_RenderLightmappedPolyVA( surf );

///					surf->scenechain = r_scene_surfaces;
///					r_scene_surfaces = surf;
				scene_surfaces[num_scene_surfaces++] = surf;
///					surf->texinfo->image->framecount = r_framecount;

					if (surf->flags & SURF_DRAWFOG)
					{
						surf->fogchain = r_fog_surfaces;
						r_fog_surfaces = surf;
					}

					if((r_caustics->value) && (surf->flags & SURF_UNDERWATER))
					{
						surf->uwchain = r_uw_surfaces;
						r_uw_surfaces = surf;
					}
				}
				else
				{
					// the polygon is visible, so add it to the texture
					// sorted chain
					// FIXME: this is a hack for animation
					image = R_TextureAnimation (surf->texinfo)->image;
					surf->texturechain = image->texturechain;
					image->texturechain = surf;

					if(surf->flags & SURF_DRAWFOG)
					{
						surf->fogchain = r_fog_surfaces;
						r_fog_surfaces = surf;
					}
				}
			}
		}
	}

	// recurse down the back side
	R_RecursiveWorldNode (node->children[!side]);
}



void MakeSkyVec (float s, float t, int axis)
{
	vec3_t		v, b;
	int			j, k;

	if (net_compatibility->value)
	{
		b[0] = s*16384;//2300;
		b[1] = t*16384;//2300;
		b[2] = 16384;//2300;
	}
	else
	{	// LARGE_MAP_SIZE
		b[0] = s*32768;//2300;
		b[1] = t*32768;//2300;
		b[2] = 32768;//2300;
	}

	for (j=0 ; j<3 ; j++)
	{
		k = st_to_vec[axis][j];
		if (k < 0)
			v[j] = -b[-k - 1];
		else
			v[j] = b[k - 1];
	}

	// avoid bilerp seam
	s = (s+1)*0.5;
	t = (t+1)*0.5;

	if (s < SKY_MIN)
		s = SKY_MIN;
	else if (s > SKY_MAX)
		s = SKY_MAX;
	if (t < SKY_MIN)
		t = SKY_MIN;
	else if (t > SKY_MAX)
		t = SKY_MAX;

	t = 1.0 - t;
	glTexCoord2f (s, t);
	glVertex3fv (v);
}


void R_DrawSkyBox ()
{
	int		i;

	if (skyrotate)
	{	// check for no sky at all
		for (i=0 ; i<6 ; i++)
			if (skymins[0][i] < skymaxs[0][i] && skymins[1][i] < skymaxs[1][i])
				break;
		if (i == 6)
			return;		// nothing visible
	}

	glPushMatrix ();
	glTranslatef (r_origin[0], r_origin[1], r_origin[2]);
	glRotatef (r_newrefdef.time * skyrotate, skyaxis[0], skyaxis[1], skyaxis[2]);

	for (i=0 ; i<6 ; i++)
	{
		if (skyrotate)
		{	// hack, forces full sky to draw when rotating
			skymins[0][i] = -1;
			skymins[1][i] = -1;
			skymaxs[0][i] = 1;
			skymaxs[1][i] = 1;
		}

		if (skymins[0][i] >= skymaxs[0][i]
		|| skymins[1][i] >= skymaxs[1][i])
			continue;

		GL_Bind2D (sky_images[skytexorder[i]]->texnum);

		glBegin (GL_QUADS);
		MakeSkyVec (skymins[0][i], skymins[1][i], i);
		MakeSkyVec (skymins[0][i], skymaxs[1][i], i);
		MakeSkyVec (skymaxs[0][i], skymaxs[1][i], i);
		MakeSkyVec (skymaxs[0][i], skymins[1][i], i);
		glEnd ();
	}

	glPopMatrix ();
}


void DrawTextureChains ()
{
	int		i;
	msurface_t	*s;
	image_t		*image;

/// This already handled in R_RecursiveWorldNode !!!
/*	for ( i = 0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (!image->registration_sequence)
			continue;
		if (!image->texturechain)
			continue;
		s = image->texturechain;
		if (!s || (s->texinfo->flags & SURF_NODRAW))		/// Berserker: вообще не рисуем полигоны NODRAW
			continue;

		for ( ; s ; s=s->texturechain)
		{
			if ( !( s->flags & SURF_DRAWTURB ) )
				GL_RenderBrushPoly (s, r_origin, false);
		}
	}
*/
	for ( i = 0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (!image->registration_sequence)
			continue;
		if (!image->texturechain)
			continue;
		s = image->texturechain;
		if (!s /*|| (s->texinfo->flags & SURF_NODRAW)*/) /// лишняя проверка, ибо тут никогда не будет SURF_NODRAW
			continue;

		for ( ; s ; s=s->texturechain)
///		{
///			if ( s->flags & SURF_DRAWTURB )				/// лишняя проверка, ибо тут всегда будут SURF_DRAWTURB
				GL_RenderBrushPoly (s, r_origin, false);
///		}

		image->texturechain = NULL;
	}

	gl_TexEnv( GL_REPLACE );
}


/*
=================
R_CullOrigin

Returns true if the origin is completely outside the frustom
=================
*/
bool R_CullOrigin (vec3_t origin)
{
	int		i;

	for (i=0 ; i<4 ; i++)
		if ( BOX_ON_PLANE_SIDE(origin, origin, &frustum[i]) == 2)
			return true;
	return false;
}


void R_RenderSunFlare(image_t *tex, float offset, float size, float r, float g, float b)
{
	float	minx, miny, maxx, maxy;
	float	new_x, new_y, corr;

	GL_Color3f(r,g,b);
	GL_Bind2D(tex->texnum);

	if(offset)
	{
		new_x = offset * ((float)r_newrefdef.width/2 - sun_x) + sun_x;
		new_y = offset * ((float)r_newrefdef.height/2 - sun_y) + sun_y;
	}
	else
	{
		new_x = sun_x;
		new_y = sun_y;
	}

	if (r_mirror)
		corr = (float)vid.height / (float)vid.width;
	else
		corr = 1;

	minx = new_x - size * corr;
	miny = new_y - size;
	maxx = new_x + size * corr;
	maxy = new_y + size;

	glBegin (GL_QUADS);
		glTexCoord2f(0,0);	glVertex2f (minx, miny);
		glTexCoord2f(1,0);	glVertex2f (maxx, miny);
		glTexCoord2f(1,1);	glVertex2f (maxx, maxy);
		glTexCoord2f(0,1);	glVertex2f (minx, maxy);
	glEnd ();
}

bool R_CheckSun()
{
	float	l;

	if (!draw_sun)
		return false;

	int time = Sys_Milliseconds();

	if (r_mirror)
	{
		if (time - sun_time_mirror < 100)
			l = last_draw_sun_mirror;
		else
		{
			glReadPixels(sun_x, r_newrefdef.height-sun_y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &l);
			sun_time_mirror = time;
			last_draw_sun_mirror = l;
		}
	}
	else
	{
		if (time - sun_time < 100)
			l = last_draw_sun;
		else
		{
			glReadPixels(sun_x, r_newrefdef.height-sun_y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &l);
			sun_time = time;
			last_draw_sun = l;
		}
	}

	return (l==1.0);
}

void R_RenderSun()
{
	float	l, hx, hy;
	float	vec[2];

	hx = (float)r_newrefdef.width/2;
	hy = (float)r_newrefdef.height/2;
	vec[0] = 1 - fabs(sun_x - hx)/hx;
	vec[1] = 1 - fabs(sun_y - hy)/hy;
	l = 3*vec[0]*vec[1]+0.25;

	// set 2d
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity ();
	glOrtho (0, r_newrefdef.width, r_newrefdef.height, 0, -99999, 99999);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity ();
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	GL_BlendFunc (GL_ONE_MINUS_DST_COLOR, GL_ONE);
	gl_TexEnv( GL_MODULATE );
	glDepthRange (0, 0.3);

	if (r_mirror)
		glDisable(GL_CULL_FACE);

	float size = r_newrefdef.width * sun_size;
	R_RenderSunFlare(sun_object, 0, size, 1, 1, 1);
	if (!r_mirror && r_drawsun->value == 2)
	{
		if(l>1)
			l=1;

		R_RenderSunFlare(sun2_object, -0.9, size*0.07, l, l, 0);
		R_RenderSunFlare(sun2_object, -0.7, size*0.15, 0, 0, l);
		R_RenderSunFlare(sun2_object, -0.5, size*0.085, l, 0, 0);
		R_RenderSunFlare(sun1_object, 0.3, size*0.25, l, l, l);
		R_RenderSunFlare(sun2_object, 0.5, size*0.05, l, 0, 0);
		R_RenderSunFlare(sun2_object, 0.64, size*0.05, 0, l, 0);
		R_RenderSunFlare(sun2_object, 0.7, size*0.25, l, l, 0);
		R_RenderSunFlare(sun1_object, 0.85, size*0.5, l, l, l);
		R_RenderSunFlare(sun2_object, 1.1, size*0.125, l, 0, 0);
		R_RenderSunFlare(sun2_object, 1.25, size*0.08, l, l, 0);
	}

	if (r_mirror)
		glEnable(GL_CULL_FACE);

	glDepthRange (0, 1);
	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	// set 3d
	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}


void transform_point(float out[4], const float m[16], const float in[4])
{
#define M(row,col)  m[col*4+row]
   out[0] = M(0, 0) * in[0] + M(0, 1) * in[1] + M(0, 2) * in[2] + M(0, 3) * in[3];
   out[1] = M(1, 0) * in[0] + M(1, 1) * in[1] + M(1, 2) * in[2] + M(1, 3) * in[3];
   out[2] = M(2, 0) * in[0] + M(2, 1) * in[1] + M(2, 2) * in[2] + M(2, 3) * in[3];
   out[3] = M(3, 0) * in[0] + M(3, 1) * in[1] + M(3, 2) * in[2] + M(3, 3) * in[3];
#undef M
}


bool gluProject(float objx, float objy, float objz, const float model[16], const float proj[16], const int viewport[4], float *winx, float *winy)///, float *winz)
{
   /* matrice de transformation */
   float in[4], out[4], temp;

   /* initilise la matrice et le vecteur a transformer */
   in[0] = objx;
   in[1] = objy;
   in[2] = objz;
   in[3] = 1.0;
   transform_point(out, model, in);
   transform_point(in, proj, out);

   /* d'ou le resultat normalise entre -1 et 1 */
   if (in[3] == 0.0)
      return false;

   temp = 1.0 / in[3];
   in[0] *= temp;
   in[1] *= temp;
   in[2] *= temp;

   /* en coordonnees ecran */
   *winx = viewport[0] + (1 + in[0]) * (float)viewport[2] * 0.5;
   *winy = viewport[1] + (1 + in[1]) * (float)viewport[3] * 0.5;
///   /* entre 0 et 1 suivant z */
///   *winz = (1 + in[2]) * 0.5;
   return true;
}


void R_InitSun()
{
	draw_sun = false;

	if (sky_world)
		return;

	if (!sun_size)
		return;

	if (!r_drawsun->value)
		return;

	if (r_mirror && !mir_draw_sun)
		return;

	if (R_CullOrigin (sun_origin))
		return;

	draw_sun = true;

	if (r_mirror)
		gluProject(sun_origin[0], sun_origin[1], sun_origin[2], r_world_matrix, mir_project_matrix, (int *)r_viewport, &sun_x, &sun_y);///, &sun_z);
	else
		gluProject(sun_origin[0], sun_origin[1], sun_origin[2], r_world_matrix, r_project_matrix, (int *)r_viewport, &sun_x, &sun_y);///, &sun_z);
	sun_y = r_newrefdef.height - sun_y;
}


bool		_useParallax, ignore_lm;
bool R_AddLightmappedPolyVA( msurface_t *surf, bool parallax, bool chtex, unsigned *Base, unsigned *idxBase )
{
	unsigned	BaseVerts, BaseIdx;
	int		i, nv = surf->numedges;
	BaseVerts = *Base;
	if (BaseVerts + nv > MAX_BATCH_SURFS)
		return false;	// force the start new batch
	BaseIdx = *idxBase;
	float	*v, adds, addt;
	glpoly_t *p;
	float	scroll;
	vec3_t	t, n, vieworg;
	clightstyle_t	*ls, *cl_ls;
	vec4_t			ls_rgb;

	if (!_useParallax)
		parallax = false;

	c_brush_polys++;

	if (chtex)
	{
		mtexinfo_t	*tx = R_TextureAnimation( surf->texinfo );
		image_t *image = tx->image;
		image_t *light = tx->light;
		batch_fx_s = image->fx_s;
		batch_fx_t = image->fx_t;
		batch_fx_scale_s = image->fx_scale_s;
		batch_fx_scale_t = image->fx_scale_t;

		GL_MBind2D( GL_TEXTURE0, image->texnum );

		batch_env = image->fx == fx_chrome;
		batch_power = image->fx == fx_power;
		batch_map = image->fx == fx_map;
		batch_style = image->fx == fx_style;
		if(batch_env || batch_power || batch_map || batch_style)
		{
			GL_SelectTexture(GL_TEXTURE3);
			if (!parallax)
			{
				glEnable( GL_TEXTURE_2D );
				gl_TexEnv( GL_COMBINE_ARB );
				if(light && light->has_alpha)
				{
					GL_TexEnv( GL_COMBINE_RGB_ARB, GL_INTERPOLATE_ARB );
					if (batch_style)
					{
						GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_CONSTANT_ARB );
						unsigned tmp = batch_fx_s;
						ls = &sv_lightstyle[tmp & 0xff];
						cl_ls = &cl_lightstyle[(tmp>>8) & 0xff];
						float	temp = ls->val * cl_ls->val;
						ls_rgb[0] = temp * batch_fx_t;
						ls_rgb[1] = temp * batch_fx_scale_s;
						ls_rgb[2] = temp * batch_fx_scale_t;
						glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, ls_rgb);
					}
					else
						GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
					GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
					GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
					GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
					GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_TEXTURE2 );
					GL_TexEnv( GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA );
					GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
					GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_TEXTURE0 );
					GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
				}
				else
				{
					GL_TexEnv( GL_COMBINE_RGB_ARB, GL_ADD );
					if (batch_style)
					{
						GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_CONSTANT_ARB );
						unsigned tmp = batch_fx_s;
						ls = &sv_lightstyle[tmp & 0xff];
						cl_ls = &cl_lightstyle[(tmp>>8) & 0xff];
						float	temp = ls->val * cl_ls->val;
						ls_rgb[0] = temp * batch_fx_t;
						ls_rgb[1] = temp * batch_fx_scale_s;
						ls_rgb[2] = temp * batch_fx_scale_t;
						glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, ls_rgb);
					}
					else
						GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
					GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
					GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
					GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
					GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
					GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_TEXTURE0 );
					GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
				}
			}

			if(batch_env)
				GL_Bind2D( R_GetIMGFromName(image, chrome_texture_2d_world) );
			else if(batch_power)
				GL_Bind2D( R_GetIMGFromName(image, env_texture_2d_world) );
			else if(batch_map)
				GL_Bind2D( R_GetIMGFromName(image, chrome_texture_2d_world) );
//			else	// batch_style
///				GL_Bind( GL_TEXTURE_3D, r_rgb_3d->texnum );

			if(!batch_style)
			{
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);
				glTexCoordPointer(2, GL_FLOAT, 0, EnvArray);
			}
			batch_used_fx = true;
		}
		else
		{
			if (batch_used_fx)
			{
				GL_SelectTexture(GL_TEXTURE3);
				glDisable( GL_TEXTURE_2D );
				gl_TexEnv( GL_REPLACE );
				glDisableClientState(GL_TEXTURE_COORD_ARRAY);
				batch_used_fx = false;
			}
			if (parallax)
			{
				GL_MBind2D( GL_TEXTURE3, r_dark->texnum );
				glDisableClientState(GL_TEXTURE_COORD_ARRAY);
			}
		}

		if (!r_simple->value)
		{
			if(light)
				GL_MBind2D( GL_TEXTURE2, light->texnum );
			else
			{
				if (parallax)
					GL_MBind2D( GL_TEXTURE2, r_dark2->texnum );
				else
					GL_MBind2D( GL_TEXTURE2, r_dark->texnum );
			}
		}
	}

	if (batch_used_fx)
	{
		if(batch_env)
		{
			VectorCopy(r_origin, vieworg);
			adds = addt = 0;
		}
		else if(batch_power)
		{
			vieworg[0] = 64;
			vieworg[1] = vieworg[2] = 0;
			adds = batch_fx_s * cl.leveltime;
			addt = batch_fx_t * cl.leveltime;
		}
		else if(batch_map)
		{
			adds = batch_fx_s * cl.leveltime;
			addt = batch_fx_t * cl.leveltime;
		}
///		else	// batch_style
///		{
///		}
	}

	if (surf->texinfo->flags & SURF_FLOWING)
	{
		scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
		if(scroll == 0.0)
			scroll = -64.0;
	}
	else
		scroll = 0;

	// create indexes
	if (BaseIdx == 0xffffffff)	BaseIdx = 0;
	for (i=0; i<nv-2; i++)
	{
		indexArray[BaseIdx++] = BaseVerts;
		indexArray[BaseIdx++] = BaseVerts + i + 1;
		indexArray[BaseIdx++] = BaseVerts + i + 2;
	}

	p = surf->polys;
	v = p->verts[0];
	for (i=0 ; i < nv; i++, v+= VERTEXSIZE, BaseVerts++)
	{
		VectorCopy(v, wVertexArray[BaseVerts]);

		wTexArray[BaseVerts][0] = v[3]+scroll;
		wTexArray[BaseVerts][1] = v[4];

		if (!ignore_lm)
		{
			wLMArray[BaseVerts][0] = v[5];
			wLMArray[BaseVerts][1] = v[6];
		}

		if (parallax)
		{
			nTexArray[BaseVerts][0] = v[7];
			nTexArray[BaseVerts][1] = v[8];
			nTexArray[BaseVerts][2] = v[9];
			tTexArray[BaseVerts][0] = v[10];
			tTexArray[BaseVerts][1] = v[11];
			tTexArray[BaseVerts][2] = v[12];
			bTexArray[BaseVerts][0] = v[13];
			bTexArray[BaseVerts][1] = v[14];
			bTexArray[BaseVerts][2] = v[15];
		}

		if(batch_env || batch_power)
		{
			VectorSubtract (v, vieworg, t);
			// project vector
			n[0] = v[7];
			n[1] = v[8];
			n[2] = RSqrt(DotProduct(t,t));

			EnvArray[BaseVerts][0] = (t[0]*n[2] - n[0] + adds)*batch_fx_scale_s;
			EnvArray[BaseVerts][1] = (t[1]*n[2] - n[1] + addt)*batch_fx_scale_t;
		}
		else if(batch_map)
		{
			EnvArray[BaseVerts][0] = (v[3] + adds)*batch_fx_scale_s;
			EnvArray[BaseVerts][1] = (v[4] + addt)*batch_fx_scale_t;
		}
//		else if(batch_style)
///			VectorCopy(ls_rgb, EnvArray3[BaseVerts]);
	}

	*Base = BaseVerts;
	*idxBase = BaseIdx;
	return true;
}

int SurfCmpPLT( const msurface_t **a, const msurface_t **b )
{
	if (ignore_lm)
		return (((*a)->texinfo->image->Parallax<<24)/*+((*a)->lightmaptexturenum<<16)*/+(*a)->texinfo->image->texnum) - (((*b)->texinfo->image->Parallax<<24)/*+((*b)->lightmaptexturenum<<16)*/+(*b)->texinfo->image->texnum);
	return (((*a)->texinfo->image->Parallax<<24)+((*a)->lightmaptexturenum<<16)+(*a)->texinfo->image->texnum) - (((*b)->texinfo->image->Parallax<<24)+((*b)->lightmaptexturenum<<16)+(*b)->texinfo->image->texnum);
}

int SurfCmpLT( const msurface_t **a, const msurface_t **b )
{
	if (ignore_lm)
		return (/*((*a)->texinfo->image->Parallax<<24)+((*a)->lightmaptexturenum<<16)+*/(*a)->texinfo->image->texnum) - (/*((*b)->texinfo->image->Parallax<<24)+((*b)->lightmaptexturenum<<16)+*/(*b)->texinfo->image->texnum);
	return (/*((*a)->texinfo->image->Parallax<<24)+*/((*a)->lightmaptexturenum<<16)+(*a)->texinfo->image->texnum) - (/*((*b)->texinfo->image->Parallax<<24)+*/((*b)->lightmaptexturenum<<16)+(*b)->texinfo->image->texnum);
}

int SurfCmpPT( const msurface_t **a, const msurface_t **b )
{
	return (((*a)->texinfo->image->Parallax<<24)/*+((*a)->lightmaptexturenum<<16)*/+(*a)->texinfo->image->texnum) - (((*b)->texinfo->image->Parallax<<24)/*+((*b)->lightmaptexturenum<<16)*/+(*b)->texinfo->image->texnum);
}

int SurfCmpT( const msurface_t **a, const msurface_t **b )
{
	return (/*((*a)->texinfo->image->Parallax<<24)+((*a)->lightmaptexturenum<<16)*/+(*a)->texinfo->image->texnum) - (/*((*b)->texinfo->image->Parallax<<24)+((*b)->lightmaptexturenum<<16)*/+(*b)->texinfo->image->texnum);
}


bool R_AddTrisVA( msurface_t *surf, unsigned *Base, unsigned *idxBase )
{
	unsigned	BaseVerts, BaseIdx;
	int		i, nv = surf->numedges;
	BaseVerts = *Base;
	if (BaseVerts + 2*nv > MAX_BATCH_SURFS)
		return false;	// force the start new batch
	BaseIdx = *idxBase;
	float	*v;
	glpoly_t *p;

	// create indexes
	if (BaseIdx == 0xffffffff)	BaseIdx = 0;
	for (i=0; i<nv-1; i++)
	{
		indexArray[BaseIdx++] = BaseVerts + i;
		indexArray[BaseIdx++] = BaseVerts + i + 1;
	}
	indexArray[BaseIdx++] = BaseVerts + i;
	indexArray[BaseIdx++] = BaseVerts;
	for (i=0; i<nv; i++)
	{
		indexArray[BaseIdx++] = BaseVerts + i;
		indexArray[BaseIdx++] = BaseVerts + i + nv;
	}

	p = surf->polys;
	v = p->verts[0];
	for (i=0 ; i < nv; i++, v+= VERTEXSIZE, BaseVerts++)
		VectorCopy(v, wVertexArray[BaseVerts]);
	v = p->verts[0];
	for (i=0 ; i < nv; i++, v+= VERTEXSIZE, BaseVerts++)
	{
		wVertexArray[BaseVerts][0] = v[0]+8*v[7];
		wVertexArray[BaseVerts][1] = v[1]+8*v[8];
		wVertexArray[BaseVerts][2] = v[2]+8*v[9];
	}

	*Base = BaseVerts;
	*idxBase = BaseIdx;
	return true;
}

void R_DrawSceneSurfaces ()
{
	int			i;
	msurface_t	*s;

	c_world_batches = 0;

	_useParallax = (gl_config.vp && !r_simple->value && r_shader->value==SHADER_ARB6 && r_parallax->value);
	if (_useParallax)
		qsort(scene_surfaces, num_scene_surfaces, sizeof(msurface_t*), (int (*)(const void *, const void *))SurfCmpPLT);
	else
		qsort(scene_surfaces, num_scene_surfaces, sizeof(msurface_t*), (int (*)(const void *, const void *))SurfCmpLT);

	GL_SelectTexture(GL_TEXTURE0);
	gl_TexEnv( GL_MODULATE );

	GL_BindVBO(NULL);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, wTexArray);

	GL_SelectTexture(GL_TEXTURE1);
	glEnable( GL_TEXTURE_2D );
	gl_TexEnv( GL_MODULATE );
	if (!ignore_lm)
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_FLOAT, 0, wLMArray);
	}

	if (!r_simple->value)
	{
		GL_SelectTexture(GL_TEXTURE2);
		glEnable( GL_TEXTURE_2D );
		gl_TexEnv( GL_COMBINE_ARB );
		GL_TexEnv( GL_COMBINE_RGB_ARB, GL_ADD );
		GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
		GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
		GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
		GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_FLOAT, 0, wTexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, wVertexArray);

	bool		chtex;
	int			oldparal = 0;	// начинаем рисовать с non-Parallax surfs
	unsigned	oldtex = 0xffffffff;
	unsigned	vertCounter = 0, idxCounter = 0xffffffff;

	for (i=0; i<num_scene_surfaces; i++)
	{
		s=scene_surfaces[i];
		if (_useParallax)
			if ((int)s->texinfo->image->Parallax != oldparal)
			{	// пошла группа сурфов с параллаксом
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}

				glEnable(GL_VERTEX_PROGRAM_ARB);
				GL_BindProgramVP(vertex_program_ambient_parallax);
				glEnable(GL_FRAGMENT_PROGRAM_ARB);
				GL_BindProgramFP(fragment_program_ambient_parallax);

				GL_SelectTexture(GL_TEXTURE2);
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);
				glTexCoordPointer(3, GL_FLOAT, 0, bTexArray);

				GL_SelectTexture(GL_TEXTURE4);
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);
				glTexCoordPointer(3, GL_FLOAT, 0, nTexArray);

				GL_SelectTexture(GL_TEXTURE5);
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);
				glTexCoordPointer(3, GL_FLOAT, 0, tTexArray);

				oldparal = s->texinfo->image->Parallax;
			}

		if (ignore_lm)
			GL_MBind2D( GL_TEXTURE1, r_white->texnum );
		else
		{
			if (gl_state.currenttextures[1] != TEXNUM_LIGHTMAPS + s->lightmaptexturenum)
			{
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				GL_MBind2D( GL_TEXTURE1, /*gl_state.lightmap_textures*/TEXNUM_LIGHTMAPS + s->lightmaptexturenum );
			}
		}

		if (s->texinfo->image->texnum != oldtex)
		{	// смена текстуры
			if (idxCounter != 0xffffffff)
			{	// draw old batch
				c_world_batches++;
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///				else
					glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
				vertCounter = 0;
				idxCounter = 0xffffffff;
			}
			oldtex = s->texinfo->image->texnum;
			chtex = true;
		}
		else
			chtex = false;

repeat:	if (!R_AddLightmappedPolyVA(s, oldparal, chtex, &vertCounter, &idxCounter))
		{
			if (idxCounter != 0xffffffff)
			{	// draw old batch
				c_world_batches++;
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///				else
					glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
				vertCounter = 0;
				idxCounter = 0xffffffff;
			}
			goto repeat;
		}
	}
	if (idxCounter != 0xffffffff)
	{	// draw old batch
		c_world_batches++;
///		if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///			glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///		else
			glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
	}
	if (!_useParallax)
		oldparal = 0;
	if (batch_used_fx)
	{
		GL_SelectTexture(GL_TEXTURE3);
		glDisable( GL_TEXTURE_2D );
		gl_TexEnv( GL_REPLACE );
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}
	if (oldparal)
	{
		glDisable(GL_VERTEX_PROGRAM_ARB);
		glDisable(GL_FRAGMENT_PROGRAM_ARB);
		GL_SelectTexture(GL_TEXTURE3);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		GL_SelectTexture(GL_TEXTURE4);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		GL_SelectTexture(GL_TEXTURE5);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	if (!r_simple->value)
	{
		GL_SelectTexture(GL_TEXTURE2);
		glDisable( GL_TEXTURE_2D );
		gl_TexEnv( GL_REPLACE );
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE1);
	glDisable( GL_TEXTURE_2D );
	gl_TexEnv( GL_REPLACE );
	if (!ignore_lm)
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	gl_TexEnv( GL_REPLACE );
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

/// Berserker //////////////////////////
	if (hash_showtexture != null_hashkey || curtexinfo)
		goto fors;

	if (!r_showtris->value)
		return;

	if (r_showtris->value != 1)
fors:	glDisable (GL_DEPTH_TEST);
//	glEnable(GL_LINE_SMOOTH);
	glDisable (GL_TEXTURE_2D);
	GL_Color3f (1,1,1);
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, wVertexArray);
	vertCounter = 0;
	idxCounter = 0xffffffff;
	for (i=0; i<num_scene_surfaces; i++)
	{
		s=scene_surfaces[i];
		if (s->texinfo->image->hash == hash_showtexture || r_showtris->value ||  s->texinfo == curtexinfo)
rep:		if (!R_AddTrisVA(s, &vertCounter, &idxCounter))
			{	// draw old batch
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_LINES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///				else
					glDrawElements(GL_LINES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
				vertCounter = 0;
				idxCounter = 0xffffffff;
				goto rep;
			}
	}
	if (idxCounter != 0xffffffff)
	{	// draw old batch
///		if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///			glDrawRangeElementsEXT(GL_LINES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///		else
			glDrawElements(GL_LINES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
	}
	glDisableClientState(GL_VERTEX_ARRAY);
	glEnable (GL_TEXTURE_2D);
	if (r_showtris->value != 1 || hash_showtexture != null_hashkey || curtexinfo)
		glEnable (GL_DEPTH_TEST);
	if (!VectorCompare(world_ambient_light, vec3_origin))	// Новое правило: если есть worldspawn.ambient - то он действует, и главнее чем r_worldmodel->lightdata.
		GL_Color3fv(world_ambient_light);
}


void R_DrawAmbientWorld ()
{
	entity_t	ent;

///	if ( r_newrefdef.rdflags & RDF_NOWORLDMODEL )
///		return;

	if (!sky_world || (sky_world && drawing_sky_world))
		R_InitSun ();

	if(r_mirror_distance->value>0)
		inv_mirror_distance = 1.0 / r_mirror_distance->value;
	else
		inv_mirror_distance = 1.0 / 512.0;

	blend_mirror = r_mirror_blend->value;
	if (blend_mirror<0 || blend_mirror>0.9)
		blend_mirror = 0.75;
	blend_mirror1 = 1.0 / (1-blend_mirror);

	currentmodel = r_worldmodel;
	VectorCopy (r_origin, modelorg);

	// auto cycle the world frame for texture animation
	Memset (&ent, 0, sizeof(ent));
	ent.frame = (int)(r_newrefdef.time*2);
	currententity = &ent;

///	if (!r_worldmodel->lightdata)
	if (!VectorCompare(world_ambient_light, vec3_origin))	// Новое правило: если есть worldspawn.ambient - то он действует, и главнее чем r_worldmodel->lightdata.
	{
		GL_Color3fv(world_ambient_light);
		ignore_lm = true;
	}
	else
	{
		GL_Color3f(1,1,1);
		ignore_lm = false;
	}

	if (!sky_world || (sky_world && drawing_sky_world))
		R_ClearSkyBox ();

	num_scene_surfaces = 0;
	R_RecursiveWorldNode (r_worldmodel->nodes);
	R_DrawSceneSurfaces ();

	if (!sky_world || (sky_world && drawing_sky_world))
	{
		glDepthMask(0);
		R_DrawSkyBox ();
		glDepthMask(1);
	}
}


/*
void R_DrawBeam( entity_t *e )
{
#define NUM_BEAM_SEGS 6

	int	i;
	float r, g, b;

	vec3_t perpvec;
	vec3_t direction, normalized_direction;
	vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS];
	vec3_t oldorigin, origin;

	oldorigin[0] = e->oldorigin[0];
	oldorigin[1] = e->oldorigin[1];
	oldorigin[2] = e->oldorigin[2];

	origin[0] = e->origin[0];
	origin[1] = e->origin[1];
	origin[2] = e->origin[2];

	normalized_direction[0] = direction[0] = oldorigin[0] - origin[0];
	normalized_direction[1] = direction[1] = oldorigin[1] - origin[1];
	normalized_direction[2] = direction[2] = oldorigin[2] - origin[2];

	if ( VectorNormalize( normalized_direction ) == 0 )
		return;

	PerpendicularVector( perpvec, normalized_direction );
	VectorScale( perpvec, e->frame / 2, perpvec );

	for ( i = 0; i < 6; i++ )
	{
		RotatePointAroundVector( start_points[i], normalized_direction, perpvec, (360.0/NUM_BEAM_SEGS)*i );
		VectorAdd( start_points[i], origin, start_points[i] );
		VectorAdd( start_points[i], direction, end_points[i] );
	}

	glDisable( GL_TEXTURE_2D );
	glEnable( GL_BLEND );

///	if (!(currententity->flags & RF_TRANSLUCENT))
///		glDepthMask(0);

	r = e->skinnum & 0xC0;
	g = (e->skinnum & 0x30) << 2;
	b = (e->skinnum & 0x0C) << 4;

	r *= 1/255.0F;
	g *= 1/255.0F;
	b *= 1/255.0F;

	GL_Color4f( r, g, b, e->alpha );

	glBegin( GL_TRIANGLE_STRIP );
	for ( i = 0; i < NUM_BEAM_SEGS; i++ )
	{
		glVertex3fv( start_points[i] );
		glVertex3fv( end_points[i] );
		glVertex3fv( start_points[(i+1)%NUM_BEAM_SEGS] );
		glVertex3fv( end_points[(i+1)%NUM_BEAM_SEGS] );
	}
	glEnd();

	glEnable( GL_TEXTURE_2D );
	glDisable( GL_BLEND );

///	if (!(currententity->flags & RF_TRANSLUCENT))
///		glDepthMask(1);
}
*/


int RecursiveLightPoint (mnode_t *node, vec3_t start, vec3_t end)
{
	float		front, back, frac;
	int			side;
	cplane_t	*plane;
	vec3_t		mid;
	msurface_t	*surf;
	int			s, t, ds, dt;
	int			i;
	mtexinfo_t	*tex;
	byte		*lightmap;
	int			r;

	if (node->contents != -1)
		return -1;		// didn't hit anything

// calculate mid point

// FIXME: optimize for axial
	plane = node->plane;
	front = DotProduct (start, plane->normal) - plane->dist;
	back = DotProduct (end, plane->normal) - plane->dist;
	side = front < 0;

	if ( (back < 0) == side)
		return RecursiveLightPoint (node->children[side], start, end);

	frac = front / (front-back);
	mid[0] = start[0] + (end[0] - start[0])*frac;
	mid[1] = start[1] + (end[1] - start[1])*frac;
	mid[2] = start[2] + (end[2] - start[2])*frac;

// go down front side	
	r = RecursiveLightPoint (node->children[side], start, mid);
	if (r >= 0)
		return r;		// hit something

	if ( (back < 0) == side )
		return -1;		// didn't hit anything

// check for impact on this node
///	VectorCopy (mid, lightspot);
///	lightplane = plane;
	surf = r_worldmodel->surfaces + node->firstsurface;
	for (i=0 ; i<node->numsurfaces ; i++, surf++)
	{
		if (surf->flags&(SURF_DRAWTURB|SURF_DRAWSKY))
			continue;	// no lightmaps

		tex = surf->texinfo;

		s = DotProduct (mid, tex->vecs[0]) + tex->vecs[0][3];
		t = DotProduct (mid, tex->vecs[1]) + tex->vecs[1][3];

		if (s < surf->texturemins[0] || t < surf->texturemins[1])
			continue;

		ds = s - surf->texturemins[0];
		dt = t - surf->texturemins[1];

		if ( ds > surf->extents[0] || dt > surf->extents[1] )
			continue;

		if (!surf->samples)
			return 0;

		ds >>= 4;
		dt >>= 4;

		lightmap = surf->samples;
		if (lightmap)
		{
			lightmap += 3*(dt * ((surf->extents[0]>>4)+1) + ds);
			pointcolor[0] += lightmap[0] * (1.0/255);
			pointcolor[1] += lightmap[1] * (1.0/255);
			pointcolor[2] += lightmap[2] * (1.0/255);
		}

		return 1;
	}

// go down back side
	return RecursiveLightPoint (node->children[!side], mid, end);
}



void R_LightPoint (vec3_t p, vec3_t color)
{
	int		i;
	vec3_t	end;

	if(currententity->flags & RF_VIEWERMODEL)
		goto amba;

	if(currententity->ownerkey)
	{
		// find the light, owned by this entity
		for (i=0; i<numUsedShadowLights; i++)
		{
			if (currententity->ownerkey == usedshadowlights[i]->ownerkey)
			{
				VectorCopy (usedshadowlights[i]->color, color);
				return;
			}
		}
		goto amba;
	}
	else
	{
amba:
		if (!r_worldmodel || (r_newrefdef.rdflags & RDF_NOWORLDMODEL))
		{
			if (r_simple->value)
				color[0] = color[1] = color[2] = 1;
			else
				color[0] = color[1] = color[2] = 0;	///0.02;	//0.4;	/// Ambient color for drawing alias model in "Player setup" menu
			return;
		}

		if (!r_worldmodel->lightdata || !VectorCompare(world_ambient_light, vec3_origin))	// Новое правило: если есть worldspawn.ambient - то он действует, и главнее чем r_worldmodel->lightdata.
		{
			if (r_simple->value)
				VectorSet(color, 1,1,1);
			else
				VectorCopy(world_ambient_light, color);
			return;
		}

		end[0] = p[0];
		end[1] = p[1];
		end[2] = p[2] - 2048;

		VectorClear (pointcolor);
		if (RecursiveLightPoint (r_worldmodel->nodes, p, end) == -1)
		{
			if (r_simple->value)
				color[0] = color[1] = color[2] = 1;
			else
				color[0] = color[1] = color[2] = 0;
		}
		else
		{
			float mod;
			if (r_simple->value)
			{
				if (VectorCompare(pointcolor, vec3_origin))
				{
					color[0] = color[1] = color[2] = 1;
					return;
				}
				mod = r_modulate->value * 8;
			}
			else
				mod = r_modulate->value;
			VectorScale(pointcolor, mod, color);
			if(color[0]>1)	color[0]=1;
			if(color[1]>1)	color[1]=1;
			if(color[2]>1)	color[2]=1;
		}
	}
}


void R_DrawNullModel ()
{
	vec3_t	shadelight;
	int		i;

	currententity->flags |= RF_NOUSE;		// Не освещать модель (не обрабатывать bump-кодом, ибо модель не видна)

	if ( currententity->flags & RF_FULLBRIGHT )
		shadelight[0] = shadelight[1] = shadelight[2] = 1.0;
	else
	{
		R_LightPoint (currententity->origin, shadelight);
		if (!VectorCompare(currententity->color_shell, vec3_origin))
		{
			VectorAdd (shadelight, currententity->color_shell, shadelight);
			VectorNormalize(shadelight);
		}
	}

	glPushMatrix ();
	R_RotateForEntity (currententity);

	glDisable (GL_TEXTURE_2D);
	GL_Color3fv (shadelight);

	glBegin (GL_TRIANGLE_FAN);
	glVertex3f (0, 0, -16);
	for (i=0 ; i<=4 ; i++)
	{
		float s, c;
		SinCos(i*M_PI/2, &s, &c);
		glVertex3f (16*c, 16*s, 0);
///		glVertex3f (16*cos(i*M_PI/2), 16*sin(i*M_PI/2), 0);
	}
	glEnd ();

	glBegin (GL_TRIANGLE_FAN);
	glVertex3f (0, 0, 16);
	for (i=4 ; i>=0 ; i--)
	{
		float s, c;
		SinCos(i*M_PI/2, &s, &c);
		glVertex3f (16*c, 16*s, 0);
///		glVertex3f (16*cos(i*M_PI/2), 16*sin(i*M_PI/2), 0);
	}
	glEnd ();

	GL_Color3f (1,1,1);
	glPopMatrix ();
	glEnable (GL_TEXTURE_2D);
}


void CheckEntityFrame(dmdl_t *paliashdr)
{
	if(currententity->flags & RF_ANIMFRAME)		// Игнорируем фреймы с сервера, используем свои тайминги
	{
		currententity->backlerp = 1.0 - cl.lerpfrac;
		currententity->frame = cl.time*0.01;
		currententity->frame = currententity->frame%paliashdr->num_frames;
		if (currententity->frame==0)
			currententity->oldframe = paliashdr->num_frames-1;
		else
			currententity->oldframe = (currententity->frame-1)%paliashdr->num_frames;
	}
	else
	{
		if ( (currententity->frame >= paliashdr->num_frames) || (currententity->frame < 0) )
		{
			Com_Printf("^3CheckEntityFrame, %s: no such frame %d\n", currentmodel->name, currententity->frame);
			currententity->frame = 0;
			currententity->oldframe = 0;
		}

		if ( (currententity->oldframe >= paliashdr->num_frames)	|| (currententity->oldframe < 0))
		{
			Com_Printf("^3CheckEntityFrame, %s: no such oldframe %d\n", currentmodel->name, currententity->oldframe);
			currententity->frame = 0;
			currententity->oldframe = 0;
		}
	}
}


void CheckEntityFrameMD3(maliasmodel_t *paliashdr)
{
	if(currententity->flags & RF_ANIMFRAME)		// Игнорируем фреймы с сервера, используем свои тайминги
	{
		currententity->backlerp = 1.0 - cl.lerpfrac;
		currententity->frame = cl.time*0.01;
		currententity->frame = currententity->frame%paliashdr->num_frames;
		if (currententity->frame==0)
			currententity->oldframe = paliashdr->num_frames-1;
		else
			currententity->oldframe = (currententity->frame-1)%paliashdr->num_frames;
	}
	else
	{
		if ( ( currententity->frame >= paliashdr->num_frames ) || ( currententity->frame < 0 ) )
		{
			Com_Printf("^3CheckEntityFrameMD3, %s: no such frame %d\n", currentmodel->name, currententity->frame);
			currententity->frame = 0;
		}

		if ( ( currententity->oldframe >= paliashdr->num_frames ) || ( currententity->oldframe < 0 ) )
		{
			Com_Printf("^3CheckEntityFrameMD3, %s: no such oldframe %d\n", currentmodel->name, currententity->oldframe);
			currententity->oldframe = 0;
		}
	}
}


bool R_CullAliasModel( cplane_t *frust, bool check, bool it_frust )
{
	int i, j;
	vec3_t		mins, maxs;
	dmdl_t		*paliashdr;
	vec3_t		thismins, oldmins, thismaxs, oldmaxs;
	daliasframe_t *pframe, *poldframe;
	vec3_t		bbox[8];

	if (!it_frust || (it_frust && !currententity->minmax_ready))
	{
		paliashdr = (dmdl_t *)currentmodel->extradata;
		pframe = ( daliasframe_t * ) ( ( byte * ) paliashdr + paliashdr->ofs_frames + currententity->frame * paliashdr->framesize);
		poldframe = ( daliasframe_t * ) ( ( byte * ) paliashdr + paliashdr->ofs_frames + currententity->oldframe * paliashdr->framesize);

		/*
		** compute axially aligned mins and maxs
		*/
		if ( pframe == poldframe )
		{
			for ( i = 0; i < 3; i++ )
			{
				mins[i] = pframe->translate[i];
				maxs[i] = mins[i] + pframe->scale[i]*255;
			}
		}
		else
		{
			for ( i = 0; i < 3; i++ )
			{
				thismins[i] = pframe->translate[i];
				thismaxs[i] = thismins[i] + pframe->scale[i]*255;

				oldmins[i]  = poldframe->translate[i];
				oldmaxs[i]  = oldmins[i] + poldframe->scale[i]*255;

				if ( thismins[i] < oldmins[i] )
					mins[i] = thismins[i];
				else
					mins[i] = oldmins[i];

				if ( thismaxs[i] > oldmaxs[i] )
					maxs[i] = thismaxs[i];
				else
					maxs[i] = oldmaxs[i];
			}
		}

		/*
		** compute a full bounding box
		*/
		for ( i = 0; i < 8; i++ )
		{
			vec3_t   tmp;

			if ( i & 1 )
				tmp[0] = mins[0];
			else
				tmp[0] = maxs[0];

			if ( i & 2 )
				tmp[1] = mins[1];
			else
				tmp[1] = maxs[1];

			if ( i & 4 )
				tmp[2] = mins[2];
			else
				tmp[2] = maxs[2];

			VectorCopy( tmp, bbox[i] );
		}

		/*
		** rotate the bounding box  (fixed by Berserker: correctly point rotate)
		*/
		if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
		{
			for ( i = 0; i < 8; i++ )
			{
				PointRotate(currententity->angles, bbox[i]);
				VectorAdd( bbox[i], currententity->origin, bbox[i] );
			}
		}
		else
			for ( i = 0; i < 8; i++ )
				VectorAdd( bbox[i], currententity->origin, bbox[i] );


/*
		int p, f, aggregatemask = ~0;

		for ( p = 0; p < 8; p++ )
		{
			int mask = 0;

			for ( f = 0; f < 4; f++ )
				if ( DotProduct( frust[f].normal, bbox[p] ) < frust[f].dist )
					mask |= ( 1 << f );

			aggregatemask &= mask;
		}

		return (aggregatemask);
*/
		/// Berserker: faster!!!
		mins[0] = mins[1] = mins[2] = 999999;
		maxs[0] = maxs[1] = maxs[2] = -999999;

		for(j=0; j<8; j++)
		{
			for(i=0; i<3; i++)
			{
				if(mins[i] > bbox[j][i])
					mins[i] = bbox[j][i];

				if(maxs[i] < bbox[j][i])
					maxs[i] = bbox[j][i];
			}
		}

		// save the current bbox...
		currententity->minmax[0] = mins[0];
		currententity->minmax[1] = mins[1];
		currententity->minmax[2] = mins[2];
		currententity->minmax[3] = maxs[0];
		currententity->minmax[4] = maxs[1];
		currententity->minmax[5] = maxs[2];

		currententity->minmax_ready = it_frust;
	}
	else
	{
		mins[0] = currententity->minmax[0];
		mins[1] = currententity->minmax[1];
		mins[2] = currententity->minmax[2];
		maxs[0] = currententity->minmax[3];
		maxs[1] = currententity->minmax[4];
		maxs[2] = currententity->minmax[5];
	}

	if (!check)
		return false;		// not need to return any value...

	if (Ent_MirrorClip(mins, maxs))
		return true;

	return R_CullBox_(mins, maxs, &frust[0]);
}


bool R_CullAliasMD3Model( cplane_t *frust, bool check, bool it_frust )
{
	int i, j;
	vec3_t		mins, maxs;
	maliasmodel_t	*paliashdr;
	maliasframe_t	*pframe, *poldframe;
	vec3_t bbox[8];

	if (!it_frust || (it_frust && !currententity->minmax_ready))
	{
		paliashdr = (maliasmodel_t *)currentmodel->extradata;
		pframe = paliashdr->frames + currententity->frame;
		poldframe = paliashdr->frames + currententity->oldframe;

		/*
		** compute axially aligned mins and maxs
		*/
		if (pframe == poldframe)
		{
			for (i = 0; i < 3; i++)
			{
				mins[i] = pframe->mins[i] + pframe->translate[i];
				maxs[i] = pframe->maxs[i] + pframe->translate[i];
			}
		}
		else
		{
			for (i = 0; i < 3; i++)
			{
				if (pframe->mins[i] < poldframe->mins[i])
					mins[i] = pframe->mins[i] + pframe->translate[i];
				else
					mins[i] = poldframe->mins[i] + poldframe->translate[i];

				if (pframe->maxs[i] > poldframe->maxs[i])
					maxs[i] = pframe->maxs[i] + pframe->translate[i];
				else
					maxs[i] = poldframe->maxs[i] + poldframe->translate[i];
			}
		}

		/*
		** compute a full bounding box
		*/
		for ( i = 0; i < 8; i++ )
		{
			vec3_t   tmp;

			if ( i & 1 )
				tmp[0] = mins[0];
			else
				tmp[0] = maxs[0];

			if ( i & 2 )
				tmp[1] = mins[1];
			else
				tmp[1] = maxs[1];

			if ( i & 4 )
				tmp[2] = mins[2];
			else
				tmp[2] = maxs[2];

			VectorCopy( tmp, bbox[i] );
		}

		/*
		** rotate the bounding box  (fixed by Berserker: correctly point rotate)
		*/
		if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
		{
			for ( i = 0; i < 8; i++ )
			{
				PointRotate(currententity->angles, bbox[i]);
				VectorAdd( bbox[i], currententity->origin, bbox[i] );
			}
		}
		else
			for ( i = 0; i < 8; i++ )
				VectorAdd( bbox[i], currententity->origin, bbox[i] );


/*		int p, f, aggregatemask = ~0;

		for ( p = 0; p < 8; p++ )
		{
			int mask = 0;

			for ( f = 0; f < 4; f++ )
				if ( DotProduct( frust[f].normal, bbox[p] ) < frust[f].dist )
					mask |= ( 1 << f );

			aggregatemask &= mask;
		}

		return (aggregatemask);
*/
		/// Berserker: faster!!!
		mins[0] = mins[1] = mins[2] = 999999;
		maxs[0] = maxs[1] = maxs[2] = -999999;

		for(j=0; j<8; j++)
		{
			for(i=0; i<3; i++)
			{
				if(mins[i] > bbox[j][i])
					mins[i] = bbox[j][i];

				if(maxs[i] < bbox[j][i])
					maxs[i] = bbox[j][i];
			}
		}

		// save the current bbox...
		currententity->minmax[0] = mins[0];
		currententity->minmax[1] = mins[1];
		currententity->minmax[2] = mins[2];
		currententity->minmax[3] = maxs[0];
		currententity->minmax[4] = maxs[1];
		currententity->minmax[5] = maxs[2];

		currententity->minmax_ready = it_frust;
	}
	else
	{
		mins[0] = currententity->minmax[0];
		mins[1] = currententity->minmax[1];
		mins[2] = currententity->minmax[2];
		maxs[0] = currententity->minmax[3];
		maxs[1] = currententity->minmax[4];
		maxs[2] = currententity->minmax[5];
	}

	if (!check)
		return false;		// not need to return any value...

	if (Ent_MirrorClip(mins, maxs))
		return true;

	return R_CullBox_(mins, maxs, &frust[0]);
}


inline unsigned	Com_PosHash (vec3_t pos)	// pos must be in [-4096...+4095]
{
	return	((((int)(pos[0])+4096)<<17) & 0x3FF00C00) +
			((((int)(pos[1])+4096)<< 7) & 0x000FFC00) +
			((((int)(pos[2])+4096)>> 3) & 0x000003FF);
}


int Write_occHash()
{
	WORD		res;
	int			i, d;
	unsigned	och, ocn;
	float		x,y,z;
	occResult_t	*occ;

	if (r_mirror)
		return -1;

	if (currentmodel->type == mod_brush)
		ocn = currentmodel->firstmodelsurface;
	else
		ocn = Com_HashKey(currentmodel->name);

	x = currententity->origin[0] - currententity->oldorigin[0];
	y = currententity->origin[1] - currententity->oldorigin[1];
	z = currententity->origin[2] - currententity->oldorigin[2];
	if (x*x+y*y+z*z<=64)		// если разница меньше 8 единиц, то ищем в кэше результат
	{
		och = Com_PosHash(currententity->oldorigin);
		// ищем результаты видимости в предыдущем кадре
		for (i=0, occ = occResults; i<MAX_ENTITIES; i++, occ++)
		{
			if (occ->type == (WORD)currentmodel->type && 
				occ->framecount == occ_framecount-1 && 
				occ->hash_model_name == ocn && 
				occ->hash_model_origin == och)
			{	// нашли предыдущий результат!!!
				res = occ->occResult;
				goto resul;
			}
		}
		goto find;
	}
	else
	{
find:	for (d=8; d>0; d--)
		{
			// ищем первый свободный слот со "старостью" более d кадров
			for (i=0, occ = occResults; i<MAX_ENTITIES; i++, occ++)
			{
				if (occ_framecount - occ->framecount > d)
				{
					res = true;
					occ->type = (WORD)currentmodel->type;
					occ->hash_model_name = ocn;
resul:				occ->framecount = occ_framecount;
					occ->hash_model_origin = Com_PosHash(currententity->origin);
					if (!res)
					{
						currententity->flags |= RF_NOUSE;		// Не освещать модель (не обрабатывать bump-кодом)
						c_no_light_ents++;
					}
					return i;
				}
			}
		}
	}

	// Невозможно, но пусть будет...
	return -1;
}


void GL_RotateTexture(bool clear, float speed, const unsigned tmus)
{
	unsigned	i, mask, tmuz = tmus;
	if (clear)
	{
 		for (i=0, mask=1; i<32; i++, mask+=mask)
		{
			if (tmuz & mask)
			{
				GL_SelectTexture(GL_TEXTURE0 + i);
				glMatrixMode(GL_TEXTURE);
				glLoadIdentity();
				tmuz &= ~mask;
				if (!tmuz)
					break;
			}
		}
	}
	else
	{
		for (i=0, mask=1; i<32; i++, mask+=mask)
		{
			if (tmuz & mask)
			{
				GL_SelectTexture(GL_TEXTURE0 + i);
				glMatrixMode(GL_TEXTURE);
				glLoadIdentity();
				glTranslatef(0.5, 0.5, 0);
				if (speed)
					glRotatef(speed, 0, 0, 1);
				tmuz &= ~mask;
				if (!tmuz)
					break;
			}
		}
	}
	glMatrixMode(GL_MODELVIEW);
}


/// Внимание! Не допускать нелогичного случая: draw_solid = draw_trans = false
void R_DrawAliasMD3Model (bool hud, bool draw_solid, bool draw_trans)
{
	maliasmodel_t	*paliashdr;
	image_t			*skin, *light;
	int				i, j;
	float			frontlerp, backlerp;
	maliasframe_t	*frame, *oldframe;
	vec3_t			move, delta, vectors[3];
	maliasvertex_t	*v, *ov;
	float			alpha;
	float			tempArray[MD3_MAX_VERTS][2];
	vec3_t			vieworg;
	bool			occ = false;

	paliashdr = (maliasmodel_t *)currentmodel->extradata;
	CheckEntityFrameMD3(paliashdr);

	if (!hud)
	{
		if (!(draw_trans && currententity->flags & RF_TRANSLUCENT))		// override the md3_has_solid_mesh and md3_has_trans_mesh
		{
			if (draw_solid && !currentmodel->md3_has_solid_mesh)
				return;
			if (draw_trans && !currentmodel->md3_has_trans_mesh)
				return;
		}

		if ( currententity->flags & RF_DISTORT )
			return;

		if ( currententity->flags & RF_WEAPONMODEL )
		{
			if (r_mirror || drawing_sky_world || hand->value==2)	// не рисуем оружие в руках когда рендерим зеркало!
			{
				currententity->flags |= RF_NOUSE;
				return;
			}
		}
		else
		{
			if ( R_CullAliasMD3Model( &frustum[0], true, true ) )
			{
				currententity->flags |= RF_NOUSE;		// Не освещать модель (не обрабатывать bump-кодом, ибо модель не видна)
				return;
			}

			if(gl_config.occlusion)
			{
				if (draw_solid)	// окклюдер работает только для моделей непрозрачными мешами
				{
					if (!(currentmodel->flags & RF_DISTORT_SKIN))	// нет мешей с дистортом
					{
						int currententity_index = Write_occHash();
						if (currententity_index>=0)
						{
							// begin occlusion test
							glBeginQueryARB(gl_config.occlusion, r_entities_occID[currententity_index]);
							r_entities_occUSE[currententity_index] = occ_framecount;
							occ = true;
						}
					}
				}
			}
		}
	}

	if ( r_newrefdef.rdflags & RDF_IRGOGGLES && (currententity->flags & RF_IR_VISIBLE))
	{
		shadelight[0] = 1.0;
		shadelight[1] = 0.0;
		shadelight[2] = 0.0;
	}
	else if ( currententity->flags & ( RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE | RF_POWER_GREEN | RF_POWER_RED | RF_POWER_BLUE) )
	{
		VectorClear(shadelight);
		if ( currententity->flags & (RF_SHELL_RED | RF_POWER_RED))
			shadelight[0] = 0.5;
		if ( currententity->flags & (RF_SHELL_GREEN | RF_POWER_GREEN))
			shadelight[1] = 0.5;
		if ( currententity->flags & (RF_SHELL_BLUE | RF_POWER_BLUE))
			shadelight[2] = 0.5;
	}
	else if ( currententity->flags & RF_FULLBRIGHT )
	{
		shadelight[0] = shadelight[1] = shadelight[2] = 1.0;
	}
	else if (weaponmodel_lighted && (currententity->flags & RF_WEAPONMODEL))
	{
		R_LightPoint (currententity->origin, shadelight);
		VectorAdd(shadelight, shell_color, shadelight);
		if (!VectorCompare(currententity->color_shell, vec3_origin))
			VectorAdd (shadelight, currententity->color_shell, shadelight);
		VectorNormalize(shadelight);
	}
	else if (currententity->flags & RF_TRANSLUCENT)
		shadelight[0]=shadelight[1]=shadelight[2]=currententity->alpha;
	else
	{
		R_LightPoint (currententity->origin, shadelight);
		if (!VectorCompare(currententity->color_shell, vec3_origin))
		{
			VectorAdd (shadelight, currententity->color_shell, shadelight);
			VectorNormalize(shadelight);
		}
	}

	if ( currententity->flags & RF_MINLIGHT )
	{
		for (i=0 ; i<3 ; i++)
			if (shadelight[i] > 0.02)	/// 0.1
				break;
		if (i == 3)
			shadelight[0] = shadelight[1] = shadelight[2] = 0.02;	/// 0.1
	}

/*	if ( currententity->flags & RF_GLOW )
	{	// bonus items will pulse with time
		float	scale;
		float	min;

		scale = 0.05 * sin(r_newrefdef.time*7);
		for (i=0 ; i<3 ; i++)
		{
			min = shadelight[i] * 0.4;
			shadelight[i] += scale;
			if (shadelight[i] < min)
				shadelight[i] = min;
		}
	}
*/
// =================
// PGM	ir goggles color override

	if (currententity->flags & RF_DEPTHHACK) // hack the depth range to prevent view model from poking into walls
		glDepthRange (0, 0.3);

	glPushMatrix ();
///	e->angles[PITCH] = -currententity->angles[PITCH];	// sigh.
///	e->angles[YAW] = currententity->angles[YAW] - 90;
	R_RotateForEntity (currententity);
///	e->angles[PITCH] = -currententity->angles[PITCH];	// sigh.
///	e->angles[YAW] = currententity->angles[YAW] + 90;

///	glShadeModel (GL_SMOOTH);
	gl_TexEnv( GL_MODULATE );

	if ( currententity->flags & RF_TRANSLUCENT )
		glEnable (GL_BLEND);

///	if ( !r_lerpmodels->value )
///		currententity->backlerp = 0;

	backlerp = currententity->backlerp;
	frontlerp = 1.0 - backlerp;
	frame = paliashdr->frames + currententity->frame;
	oldframe = paliashdr->frames + currententity->oldframe;

	VectorSubtract (currententity->oldorigin, currententity->origin, delta);
	AngleVectors (currententity->angles, vectors[0], vectors[1], vectors[2]);
	move[0] = DotProduct (delta, vectors[0]);	// forward
	move[1] = -DotProduct (delta, vectors[1]);	// left
	move[2] = DotProduct (delta, vectors[2]);	// up

	VectorAdd (move, oldframe->translate, move);

	for (j=0 ; j<3 ; j++)
		move[j] = backlerp*move[j] + frontlerp*frame->translate[j];

	currentmodel->max_meshes = paliashdr->num_meshes;

	int  trans, dr_start, dr_end;
	bool rot = false;
	if (draw_solid)
		dr_start = 0;
	else
		dr_start = 1;
	if (draw_trans)
		dr_end = 2;
	else
		dr_end = 1;
	for (trans=dr_start; trans<dr_end; trans++)		// 0-непрозрачные, 1-прозрачные
	{
		for (i=0; i < paliashdr->num_meshes; i++)
		{
			bool	custom_skin = false;;
			if (rot)
			{
				rot = false;
				GL_RotateTexture(true, 0, 3);
				GL_SelectTexture(GL_TEXTURE0);
			}
			maliasmesh_t *mesh = &paliashdr->meshes[i];
			// select skin
			skin = mesh->img_skins[min(currententity->skinnum, MD3_MAX_SKINS-1)];
			if (!skin || skin == r_notexture)
			{
				if (currententity->skin)
				{
					skin = currententity->skin;	// custom player skin
					custom_skin = true;
				}
			}
			if (!skin)
				skin = mesh->img_skins[0];
			if(skin)
			{
				if (curmesh == i)
					currentmodel->max_tris = mesh->num_tris;

				if (skin->fx == fx_distort)
					continue;

				c_alias_polys += paliashdr->meshes[i].num_tris;

				GL_Bind2D(skin->texnum);

				if (custom_skin)
					light = currententity->light;	// custom player light
				else
				{
					light = mesh->img_lights[min(currententity->skinnum, MD3_MAX_SKINS-1)];
					if (!light)
						light = mesh->img_lights[0];
				}
				if (skin->rotate)
				{
					rot = true;
					if (light)
						GL_RotateTexture(false, anglemod(cl.leveltime * skin->rotate), 3);
					else
						GL_RotateTexture(false, anglemod(cl.leveltime * skin->rotate), 1);
					GL_SelectTexture(GL_TEXTURE0);
				}

				v = mesh->vertexes + currententity->frame*mesh->num_verts;
				ov = mesh->vertexes + currententity->oldframe*mesh->num_verts;

				if (currententity->flags & ( RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE ) )
				{
					if (!trans)
						continue;

					float	scale;

					if (currententity->flags & RF_WEAPONMODEL)
						scale = POWERSUIT_SCALE_WEAPON;
					else
						scale = POWERSUIT_SCALE;

					for ( j = 0; j < mesh->num_verts; j++, v++, ov++ )
					{
#if 0
						float *normal = r_avertexnormals[mesh->vertexes[j].normal];

						VectorSet(tempVertexArray[j],
							move[0] + ov->point[0] * backlerp + v->point[0] * frontlerp + normal[0] * scale,
							move[1] + ov->point[1] * backlerp + v->point[1] * frontlerp + normal[1] * scale,
							move[2] + ov->point[2] * backlerp + v->point[2] * frontlerp + normal[2] * scale);
#else
						VectorSet(tempVertexArray[j],
							move[0] + ov->point[0] * backlerp + v->point[0] * frontlerp + mesh->vertexes[j].normal[0] * scale,
							move[1] + ov->point[1] * backlerp + v->point[1] * frontlerp + mesh->vertexes[j].normal[1] * scale,
							move[2] + ov->point[2] * backlerp + v->point[2] * frontlerp + mesh->vertexes[j].normal[2] * scale);
#endif
					}

					glEnable (GL_BLEND);
					GL_BlendFunc(GL_SRC_COLOR, GL_ONE);

					glDepthMask(0);

					int envcolor = (currententity->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE)) >> 10;
					switch (envcolor)
					{
					case 1:
						GL_Bind2D(env_texture_2d_r->texnum);
					case 2:
						GL_Bind2D(env_texture_2d_g->texnum);
					case 3:
						GL_Bind2D(env_texture_2d_rg->texnum);
					case 4:
						GL_Bind2D(env_texture_2d_b->texnum);
					case 5:
						GL_Bind2D(env_texture_2d_rb->texnum);
					case 6:
						GL_Bind2D(env_texture_2d_gb->texnum);
					default:
						GL_Bind2D(env_texture_2d_rgb->texnum);
					}

					GL_Color3f(shadelight[0]+0.5, shadelight[1]+0.5, shadelight[2]+0.5);
					vieworg[0] = 64;
					vieworg[1] = vieworg[2] = 0;

					vec3_t	t, n;
///					float *normal;
					for ( j = 0; j < mesh->num_verts; j++ )
					{
#if 0
						normal = r_avertexnormals[mesh->vertexes[j].normal];
						VectorSubtract(tempVertexArray[j], vieworg, t);
						// project vector
						n[0] = normal[0];
						n[1] = normal[1];
#else
						VectorSubtract(tempVertexArray[j], vieworg, t);
						// project vector
						n[0] = mesh->vertexes[j].normal[0];
						n[1] = mesh->vertexes[j].normal[1];
#endif
						n[2] = RSqrt(DotProduct(t,t));

						tempArray[j][0] = t[0]*n[2] - n[0] + r_newrefdef.time;///cl.leveltime;
						tempArray[j][1] = t[1]*n[2] - n[1];
					}

					glEnableClientState( GL_TEXTURE_COORD_ARRAY );
///					if (gl_config.vbo)
///					{
///						currententity->vbo_fx[i] = R_VCLoadData(VBO_DYNAMIC, j*2*sizeof(float), &tempArray, VBO_STORE_ANY, NULL, NULL);
///						glTexCoordPointer(2, GL_FLOAT, 0, 0);
///					}
///					else
					{
						GL_BindVBO(NULL);
						glTexCoordPointer( 2, GL_FLOAT, 0, tempArray );
					}

					glEnableClientState( GL_VERTEX_ARRAY );
///					if (gl_config.vbo)
///					{
///						currententity->vbo_tsh[i] = R_VCLoadData(VBO_DYNAMIC, mesh->num_verts * sizeof(vec3_t), &tempVertexArray, VBO_STORE_ANY, NULL, NULL);
///						glVertexPointer(3, GL_FLOAT, 0, 0);
///					}
///					else
					{
						GL_BindVBO(NULL);
						glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );
					}

///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///					else
						glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

					glDisableClientState( GL_VERTEX_ARRAY );
					glDisable (GL_BLEND);
					GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

					glDepthMask(1);
				}
				else
				{
					if (skin->Translucent33)
						alpha = 0.33;
					else if (skin->Translucent66)
						alpha = 0.66;
					else if (currententity->flags & RF_TRANSLUCENT)
						alpha = currententity->alpha;
					else
						alpha = 1.0;

					if (alpha<1.0)
					{
						if (!trans)
							continue;

						glEnable (GL_BLEND);
						GL_BlendFunc(GL_ONE, GL_ONE);
						GL_Color3f (alpha,alpha,alpha);
						glDepthMask(0);
					}
					else
					{
						if (trans)
							continue;

						if(/*skin->has_alpha && */skin->AlphaTest)
						{
							glEnable(GL_ALPHA_TEST);	// Поддержка "дырявых" текстур
							glDisable(GL_CULL_FACE);
						}
						GL_Color3f (shadelight[0], shadelight[1], shadelight[2]);
					}

					bool	skip_load = false;
					if (gl_config.vbo)
					{
						currententity->vbo_xyz[i] = R_VCFindCache(VBO_STORE_XYZ, currententity, i);
						if (currententity->vbo_xyz[i])
						{
							GL_BindVBO(currententity->vbo_xyz[i]);
							if ((currententity->flags & RF_CHROME || skin->fx == fx_chrome) ||
								(currententity->flags & ( RF_POWER_GREEN | RF_POWER_RED | RF_POWER_BLUE ) || skin->fx == fx_power ))
							{
								skip_load = true;
								goto load_33;		// tempVertexArray нужен для хром и повер эффектов...
							}
							goto skipLoad;
						}
					}

load_33:			for ( j = 0; j < mesh->num_verts; j++, v++, ov++ )
						VectorSet ( tempVertexArray[j], 
							move[0] + ov->point[0]*backlerp + v->point[0]*frontlerp,
							move[1] + ov->point[1]*backlerp + v->point[1]*frontlerp,
							move[2] + ov->point[2]*backlerp + v->point[2]*frontlerp);

					if (!skip_load && gl_config.vbo)
						currententity->vbo_xyz[i] = R_VCLoadData(VBO_DYNAMIC, mesh->num_verts * sizeof(vec3_t), &tempVertexArray, VBO_STORE_XYZ, currententity, i);

skipLoad:			glEnableClientState( GL_TEXTURE_COORD_ARRAY );
					if (gl_config.vbo)
					{
						GL_BindVBO(mesh->vbo_st);
						glTexCoordPointer(2, GL_FLOAT, 0, 0);
					}
					else
					{
						GL_BindVBO(NULL);
						glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
					}

					if (light)
					{
						GL_SelectTexture(GL_TEXTURE1);

						gl_TexEnv( GL_COMBINE_ARB );
						GL_TexEnv( GL_COMBINE_RGB_ARB, GL_ADD );
						GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
						GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
						GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
						GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
						GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
						GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_TEXTURE0 );
						GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );

						GL_Bind2D( light->texnum );
						glEnable(GL_TEXTURE_2D);

						glEnableClientState( GL_TEXTURE_COORD_ARRAY );
						if (gl_config.vbo)
							glTexCoordPointer(2, GL_FLOAT, 0, 0);
						else
							glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
					}

					glEnableClientState( GL_VERTEX_ARRAY );
					if (gl_config.vbo)
					{
						GL_BindVBO(currententity->vbo_xyz[i]);
						glVertexPointer(3, GL_FLOAT, 0, 0);
					}
					else
					{
						GL_BindVBO(NULL);
						glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );
					}

///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///					else
						glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

					glDisableClientState( GL_VERTEX_ARRAY );
					if (light)
					{
						glDisable( GL_TEXTURE_2D );
						gl_TexEnv( GL_REPLACE );
						glDisableClientState( GL_TEXTURE_COORD_ARRAY );
						GL_SelectTexture(GL_TEXTURE0);
					}

					if (alpha<1.0)
					{
						glDisable (GL_BLEND);
						GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
						glDepthMask(1);
					}

					if(/*skin->has_alpha && */skin->AlphaTest)
					{
						glDisable(GL_ALPHA_TEST);	// Поддержка "дырявых" текстур
						glEnable(GL_CULL_FACE);
					}

					bool polyoffset = ((r_offset->value) && (( currententity->flags & ( RF_CHROME | RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE | RF_POWER_GREEN | RF_POWER_RED | RF_POWER_BLUE) ) || skin->fx == fx_chrome || skin->fx == fx_power || skin->fx == fx_map || skin->fx == fx_style ));
					if (polyoffset)
						glEnable(GL_POLYGON_OFFSET_FILL);

					if ( currententity->flags & ( RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE | RF_POWER_GREEN | RF_POWER_RED | RF_POWER_BLUE) )
						goto shell;

					if (currententity->flags & RF_CHROME || skin->fx == fx_chrome)
					{
						glEnable (GL_BLEND);
						GL_BlendFunc(GL_SRC_COLOR, GL_ONE);

						glDepthMask(0);

						if (rot)
							GL_RotateTexture(true, 0, 2);

						GL_SelectTexture(GL_TEXTURE1);
						glEnable( GL_TEXTURE_2D );
						gl_TexEnv( GL_COMBINE_ARB );
						GL_TexEnv( GL_COMBINE_RGB_ARB, GL_INTERPOLATE_ARB );
						GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
						GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
						if (light)
							GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
						else
							GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB );
						GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
						if(light)
							GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_TEXTURE0 );
						else
							GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_PRIMARY_COLOR_ARB);
						GL_TexEnv( GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA );
						GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
						GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB );
						GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );

						GL_Bind2D( R_GetIMGFromName(skin, chrome_texture_2d) );
						GL_Color4f(shadelight[0]+0.5, shadelight[1]+0.5, shadelight[2]+0.5, 0.75);

						//Put camera into model space
						if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
						{
							vec3_t	forward, right, up, temp;
							if ( currententity->flags & RF_WEAPONMODEL )
								VectorSet(temp, 16,16,8);
							else
								VectorSubtract(r_origin, currententity->origin, temp);
							AngleVectors (currententity->angles, forward, right, up);
							vieworg[0] = DotProduct (temp, forward);
							vieworg[1] = -DotProduct (temp, right);
							vieworg[2] = DotProduct (temp, up);
						}
						else
							VectorSubtract(r_origin, currententity->origin, vieworg);

						vec3_t	t, n;
///						float *normal;
						for ( j = 0; j < mesh->num_verts; j++ )
						{
#if 0
							normal = r_avertexnormals[mesh->vertexes[j].normal];
							VectorSubtract(tempVertexArray[j], vieworg, t);
							// project vector
							n[0] = normal[0];
							n[1] = normal[1];
#else
							VectorSubtract(tempVertexArray[j], vieworg, t);
							// project vector
							n[0] = mesh->vertexes[j].normal[0];
							n[1] = mesh->vertexes[j].normal[1];
#endif
							n[2] = RSqrt(DotProduct(t,t));

							tempArray[j][0] = (t[0]*n[2] - n[0])*skin->fx_scale_s;
							tempArray[j][1] = (t[1]*n[2] - n[1])*skin->fx_scale_t;
						}

						glEnableClientState( GL_TEXTURE_COORD_ARRAY );
///						if (gl_config.vbo)
///						{
///							currententity->vbo_fx[i] = R_VCLoadData(VBO_DYNAMIC, j * 2 * sizeof(float), &tempArray, VBO_STORE_ANY, NULL, NULL);
///							glTexCoordPointer(2, GL_FLOAT, 0, 0);
///						}
///						else
						{
							GL_BindVBO(NULL);
							glTexCoordPointer( 2, GL_FLOAT, 0, tempArray );
						}

						if (light)
						{
							GL_MBind2D(GL_TEXTURE0, light->texnum);
							if (gl_config.vbo)
							{
								GL_BindVBO(mesh->vbo_st);
								glTexCoordPointer(2, GL_FLOAT, 0, 0);
							}
							else
							{
								GL_BindVBO(NULL);
								glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
							}
						}

						glEnableClientState( GL_VERTEX_ARRAY );
						if (gl_config.vbo)
						{
							GL_BindVBO(currententity->vbo_xyz[i]);
							glVertexPointer(3, GL_FLOAT, 0, 0);
						}
						else
						{
							GL_BindVBO(NULL);
							glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );
						}

///						if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///							glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///						else
							glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

						glDisableClientState( GL_VERTEX_ARRAY );
						GL_SelectTexture(GL_TEXTURE1);
						glDisableClientState( GL_TEXTURE_COORD_ARRAY );
						glDisable( GL_TEXTURE_2D );
						gl_TexEnv( GL_REPLACE );
						GL_SelectTexture(GL_TEXTURE0);
						glDisable (GL_BLEND);
						GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

						glDepthMask(1);
					}
					else
shell:				if (currententity->flags & ( RF_POWER_GREEN | RF_POWER_RED | RF_POWER_BLUE ) || skin->fx == fx_power )
					{
						glEnable (GL_BLEND);
						GL_BlendFunc(GL_SRC_COLOR, GL_ONE);

						glDepthMask(0);

						GL_SelectTexture(GL_TEXTURE1);
						glEnable( GL_TEXTURE_2D );
						gl_TexEnv( GL_COMBINE_ARB );
						GL_TexEnv( GL_COMBINE_RGB_ARB, GL_INTERPOLATE_ARB );
						GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
						GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
						if (light)
							GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
						else
							GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB );
						GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
						if(light)
							GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_TEXTURE0 );
						else
							GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_PRIMARY_COLOR_ARB);
						GL_TexEnv( GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA );
						GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
						GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB );
						GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );

						int envcolor = (currententity->flags & (RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE)) >> 21;
						switch (envcolor)
						{
						case 1:
							GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_r) );
						case 2:
							GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_g) );
						case 3:
							GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_rg) );
						case 4:
							GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_b) );
						case 5:
							GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_rb) );
						case 6:
							GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_gb) );
						default:
							GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_rgb) );
						}

						GL_Color4f(shadelight[0]+0.5, shadelight[1]+0.5, shadelight[2]+0.5, 0.75);
						vieworg[0] = 64;
						vieworg[1] = vieworg[2] = 0;

						vec3_t	t, n;
///						float *normal;
						float adds = skin->fx_s * r_newrefdef.time;///cl.leveltime;
						float addt = skin->fx_t * r_newrefdef.time;///cl.leveltime;
						for ( j = 0; j < mesh->num_verts; j++ )
						{
#if 0
							normal = r_avertexnormals[mesh->vertexes[j].normal];
							VectorSubtract(tempVertexArray[j], vieworg, t);
							// project vector
							n[0] = normal[0];
							n[1] = normal[1];
#else
							VectorSubtract(tempVertexArray[j], vieworg, t);
							// project vector
							n[0] = mesh->vertexes[j].normal[0];
							n[1] = mesh->vertexes[j].normal[1];
#endif
							n[2] = RSqrt(DotProduct(t,t));

							tempArray[j][0] = (t[0]*n[2] - n[0] + adds)*skin->fx_scale_s;
							tempArray[j][1] = (t[1]*n[2] - n[1] + addt)*skin->fx_scale_t;
						}

						glEnableClientState( GL_TEXTURE_COORD_ARRAY );
///						if (gl_config.vbo)
///						{
///							currententity->vbo_fx[i] = R_VCLoadData(VBO_DYNAMIC, j * 2 * sizeof(float), &tempArray, VBO_STORE_ANY, NULL, NULL);
///							glTexCoordPointer(2, GL_FLOAT, 0, 0);
///						}
///						else
						{
							GL_BindVBO(NULL);
							glTexCoordPointer( 2, GL_FLOAT, 0, tempArray );
						}

						if (light)
						{
							GL_MBind2D(GL_TEXTURE0, light->texnum);
							if (gl_config.vbo)
							{
								GL_BindVBO(mesh->vbo_st);
								glTexCoordPointer(2, GL_FLOAT, 0, 0);
							}
							else
							{
								GL_BindVBO(NULL);
								glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
							}
						}

						glEnableClientState( GL_VERTEX_ARRAY );
						if (gl_config.vbo)
						{
							GL_BindVBO(currententity->vbo_xyz[i]);
							glVertexPointer(3, GL_FLOAT, 0, 0);
						}
						else
						{
							GL_BindVBO(NULL);
							glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );
						}

///						if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///							glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///						else
							glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

						glDisableClientState( GL_VERTEX_ARRAY );
						GL_SelectTexture(GL_TEXTURE1);
						glDisableClientState( GL_TEXTURE_COORD_ARRAY );
						glDisable( GL_TEXTURE_2D );
						gl_TexEnv( GL_REPLACE );
						GL_SelectTexture(GL_TEXTURE0);
						glDisable (GL_BLEND);
						GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

						glDepthMask(1);
					}
					else if (skin->fx == fx_map )
					{
						glEnable (GL_BLEND);
						GL_BlendFunc(GL_SRC_COLOR, GL_ONE);

						glDepthMask(0);

						GL_SelectTexture(GL_TEXTURE1);
						glEnable( GL_TEXTURE_2D );
						gl_TexEnv( GL_COMBINE_ARB );
						GL_TexEnv( GL_COMBINE_RGB_ARB, GL_INTERPOLATE_ARB );
						GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
						GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
						if (light)
							GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
						else
							GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB );
						GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
						if(light)
							GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_TEXTURE0 );
						else
							GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_PRIMARY_COLOR_ARB);
						GL_TexEnv( GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA );
						GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
						GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB );
						GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );

						GL_Bind2D( R_GetIMGFromName(skin, chrome_texture_2d) );
						GL_Color4f(shadelight[0]+0.5, shadelight[1]+0.5, shadelight[2]+0.5, 0.75);

						float adds = skin->fx_s * r_newrefdef.time;///cl.leveltime;
						float addt = skin->fx_t * r_newrefdef.time;///cl.leveltime;
						for ( j = 0; j < mesh->num_verts; j++ )
						{
							tempArray[j][0] = (mesh->stcoords[j].st[0] + adds)*skin->fx_scale_s;
							tempArray[j][1] = (mesh->stcoords[j].st[1] + addt)*skin->fx_scale_t;
						}

						glEnableClientState( GL_TEXTURE_COORD_ARRAY );
///						if (gl_config.vbo)
///						{
///							currententity->vbo_fx[i] = R_VCLoadData(VBO_DYNAMIC, j * 2 * sizeof(float), &tempArray, VBO_STORE_ANY, NULL, NULL);
///							glTexCoordPointer(2, GL_FLOAT, 0, 0);
///						}
///						else
						{
							GL_BindVBO(NULL);
							glTexCoordPointer( 2, GL_FLOAT, 0, tempArray );
						}

						if (light)
						{
							GL_MBind2D(GL_TEXTURE0, light->texnum);
							if (gl_config.vbo)
							{
								GL_BindVBO(mesh->vbo_st);
								glTexCoordPointer(2, GL_FLOAT, 0, 0);
							}
							else
							{
								GL_BindVBO(NULL);
								glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
							}
						}

						glEnableClientState( GL_VERTEX_ARRAY );
						if (gl_config.vbo)
						{
							GL_BindVBO(currententity->vbo_xyz[i]);
							glVertexPointer(3, GL_FLOAT, 0, 0);
						}
						else
						{
							GL_BindVBO(NULL);
							glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );
						}

///						if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///							glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///						else
							glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

						glDisableClientState( GL_VERTEX_ARRAY );
						GL_SelectTexture(GL_TEXTURE1);
						glDisableClientState( GL_TEXTURE_COORD_ARRAY );
						glDisable( GL_TEXTURE_2D );
						gl_TexEnv( GL_REPLACE );
						GL_SelectTexture(GL_TEXTURE0);
						glDisable (GL_BLEND);
						GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

						glDepthMask(1);
					}
					else if (skin->fx == fx_style )
					{
						clightstyle_t	*ls, *cl_ls;
						vec4_t			ls_rgb;

						glEnable (GL_BLEND);
						GL_BlendFunc(GL_SRC_COLOR, GL_ONE);

						glDepthMask(0);

						GL_SelectTexture(GL_TEXTURE1);
						glEnable( GL_TEXTURE_2D );
						gl_TexEnv( GL_COMBINE_ARB );
						GL_TexEnv( GL_COMBINE_RGB_ARB, GL_INTERPOLATE_ARB );
						GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_CONSTANT_ARB );
						GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
						if (light)
							GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
						else
							GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB );
						GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
						if(light)
							GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_TEXTURE0 );
						else
							GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_PRIMARY_COLOR_ARB);
						GL_TexEnv( GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA );
						GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
						GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB );
						GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );

						unsigned tmp = skin->fx_s;
						ls = &sv_lightstyle[tmp & 0xff];
						cl_ls = &cl_lightstyle[(tmp>>8) & 0xff];
						float	temp = ls->val * cl_ls->val;
						ls_rgb[0] = temp * skin->fx_t;
						ls_rgb[1] = temp * skin->fx_scale_s;
						ls_rgb[2] = temp * skin->fx_scale_t;
						glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, ls_rgb);

						if (light)
						{
							GL_MBind2D(GL_TEXTURE0, light->texnum);
							if (gl_config.vbo)
							{
								GL_BindVBO(mesh->vbo_st);
								glTexCoordPointer(2, GL_FLOAT, 0, 0);
							}
							else
							{
								GL_BindVBO(NULL);
								glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
							}
						}

						glEnableClientState( GL_VERTEX_ARRAY );
						if (gl_config.vbo)
						{
							GL_BindVBO(currententity->vbo_xyz[i]);
							glVertexPointer(3, GL_FLOAT, 0, 0);
						}
						else
						{
							GL_BindVBO(NULL);
							glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );
						}

///						if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///							glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///						else
							glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

						glDisableClientState( GL_VERTEX_ARRAY );
						GL_SelectTexture(GL_TEXTURE1);
						glDisable( GL_TEXTURE_2D );
						gl_TexEnv( GL_REPLACE );
						GL_SelectTexture(GL_TEXTURE0);
						glDisable (GL_BLEND);
						GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

						glDepthMask(1);
					}
					if (polyoffset)
						glDisable(GL_POLYGON_OFFSET_FILL);
				}

				glDisableClientState( GL_TEXTURE_COORD_ARRAY );

/// Berserker //////////////////////////
				if ((r_showtris->value || (curmodel && currententity->alink == curmodel && r_editor->value)) && !(currententity->flags & (RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE)))
				{
//					glEnable(GL_LINE_SMOOTH);
					if(light)
					{
						GL_SelectTexture(GL_TEXTURE1);
						glDisable (GL_TEXTURE_2D);
					}
					GL_SelectTexture(GL_TEXTURE0);
					glDisable (GL_TEXTURE_2D);

					if (r_editor->value && curtri>=0 && curmesh==i && curmodel && currententity->alink == curmodel)
					{
						float	*vr;
						if (cl.time & 128)
							GL_Color3f (1,1,1);
						else
							GL_Color3f (0,0,0);
						glDisable (GL_DEPTH_TEST);
						glDisable (GL_CULL_FACE);

						glBegin(GL_TRIANGLES);
						for (j=0 ; j<3 ; j++)
						{
							vr = tempVertexArray[mesh->indexes[curtri*3+j]];
							glVertex3fv (vr);
						}
						glEnd();

						glEnable (GL_DEPTH_TEST);
						glEnable (GL_CULL_FACE);
					}

					if (r_showtris->value > 1 || (r_editor->value && (curmodel && currententity->alink == curmodel) && (cl.time & 512)))
						glDisable (GL_DEPTH_TEST);

					GL_Color3f (1,0,0.5);

					glEnableClientState( GL_VERTEX_ARRAY );
					if (gl_config.vbo)
					{
						GL_BindVBO(currententity->vbo_xyz[i]);
						glVertexPointer(3, GL_FLOAT, 0, 0);
					}
					else
					{
						GL_BindVBO(NULL);
						glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );
					}

					for(j=0; j<mesh->num_tris; j++)
						glDrawElements(GL_LINE_STRIP, 3, GL_UNSIGNED_SHORT, mesh->indexes + j*3);

					glDisableClientState( GL_VERTEX_ARRAY );

					if (!hud)
					{
						glBegin (GL_LINES);
						for(j=0; j<mesh->num_verts; j++)
						{
							vec3_t	vv;
///							float	*n;
///							n = r_avertexnormals[mesh->vertexes[j].normal];
							vv[0] = tempVertexArray[j][0];// + move[0];
							vv[1] = tempVertexArray[j][1];// + move[1];
							vv[2] = tempVertexArray[j][2];// + move[2];
							glVertex3fv (vv);
///							glVertex3f (vv[0]+n[0],vv[1]+n[1],vv[2]+n[2]);
								glVertex3f(vv[0] + mesh->vertexes[j].normal[0], vv[1] + mesh->vertexes[j].normal[1], vv[2] + mesh->vertexes[j].normal[2]);
						}
						glEnd ();
					}

//					glDisable(GL_LINE_SMOOTH);
					glEnable (GL_TEXTURE_2D);

					if (r_showtris->value > 1 || (r_editor->value && (curmodel && currententity->alink == curmodel) && (cl.time & 512)))
						glEnable (GL_DEPTH_TEST);
				}
/////////////////////////////////////////
			}
		}
	}

	if (rot)
	{
		GL_RotateTexture(true, 0, 3);
		GL_SelectTexture(GL_TEXTURE0);
	}

	if (gl_config.vbo)
		GL_BindVBO(NULL);

	if ( currententity->flags & RF_TRANSLUCENT )
		glDisable (GL_BLEND);

	if (currententity->flags & RF_DEPTHHACK)
		glDepthRange (0, 1);

	gl_TexEnv( GL_REPLACE );
///	glShadeModel (GL_FLAT);
	glPopMatrix ();
	GL_Color3f (1,1,1);

	// end occlusion test
	if (occ)
		glEndQueryARB(gl_config.occlusion);
}


/*
=============
GL_DrawAliasFrameLerp

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
bool GL_DrawAliasFrameLerp (dmdl_t *paliashdr, bool light, bool hud)
{
	vec3_t		vertexArray[3*MAX_TRIANGLES];
	int			index_xyz;
	int			i, j, jj;
	bool		showtris = (r_showtris->value || (curmodel && currententity->alink == curmodel && r_editor->value));
	bool		calced = false;
	dtriangle_t	*tris = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);

	GL_Color3fv(shadelight);

	if (gl_config.vbo)
	{
		currententity->vbo_xyz[0] = R_VCFindCache(VBO_STORE_XYZ, currententity, 0);
		if (currententity->vbo_xyz[0])
		{
			jj = paliashdr->num_tris * 3;
			if (showtris)		// рассчитать tempVertexArray для ShowTris
			{
				R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...
				calced = true;
			}
			goto skipLoad;
		}
	}

	R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...
	calced = true;

	for (jj=0, i=0; i<paliashdr->num_tris; i++)
	{
		for (j=0; j<3; j++, jj++)
		{
			index_xyz = tris[i].index_xyz[j];
			VectorCopy(tempVertexArray[index_xyz], vertexArray[jj]);
		}
	}
	if (gl_config.vbo)
		currententity->vbo_xyz[0] = R_VCLoadData(VBO_DYNAMIC, jj*sizeof(vec3_t), &vertexArray, VBO_STORE_XYZ, currententity, 0);

skipLoad:
	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currentmodel->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);
	}

	if (light)
	{
		GL_SelectTexture(GL_TEXTURE1);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (gl_config.vbo)
			glTexCoordPointer(2, GL_FLOAT, 0, 0);
		else
			glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);
	}

	glEnableClientState( GL_VERTEX_ARRAY );
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[0]);
		glVertexPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glVertexPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glDrawArrays (GL_TRIANGLES, 0, jj);

	if (light)
	{
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		GL_SelectTexture(GL_TEXTURE0);
	}
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	if (showtris)
	{
		GL_SelectTexture(GL_TEXTURE1);
		glDisable (GL_TEXTURE_2D);
		GL_SelectTexture(GL_TEXTURE0);
		glDisable (GL_TEXTURE_2D);
		if (r_showtris->value > 1 || (r_editor->value && (curmodel && currententity->alink == curmodel) && (cl.time & 512)))
			glDisable (GL_DEPTH_TEST);
		GL_Color3f (1,0,0);
		glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
		glDrawArrays (GL_TRIANGLES, 0, jj);
		glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
	}

	glDisableClientState( GL_VERTEX_ARRAY );
	if (gl_config.vbo)
		GL_BindVBO(NULL);

/// Berserker //////////////////////////
	if (showtris)
	{
		float	*v;
		if (r_editor->value && curtri>=0 && curmodel && currententity->alink == curmodel)
		{
			if (cl.time & 128)
				GL_Color3f (1,1,1);
			else
				GL_Color3f (0,0,0);
			glDisable (GL_DEPTH_TEST);
			glDisable (GL_CULL_FACE);

			glBegin(GL_TRIANGLES);
			for (j=0; j<3; j++)
			{
				v = tempVertexArray[tris[curtri].index_xyz[j]];
				glVertex3fv (v);
			}
			glEnd();

			glEnable (GL_CULL_FACE);
			glEnable (GL_DEPTH_TEST);
		}

		if (!hud)
		{
			GL_Color3f (1,0,0);
			if (r_showtris->value > 1 || (r_editor->value && (curmodel && currententity->alink == curmodel) && (cl.time & 512)))
				glDisable (GL_DEPTH_TEST);

			unsigned offs;
			if (currentmodel->flags & RF_SMOOTHVECS)
				offs = paliashdr->num_xyz;
			else
				offs = paliashdr->num_tris;

			daliasframe_t *oldframe = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->oldframe * paliashdr->framesize);
			dtrivertx_t	*oldverts = oldframe->verts;
			unsigned offs2 = offs*currententity->oldframe;
			byte *oldnorms = currentmodel->normals + offs2;

			daliasframe_t *frame = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->frame * paliashdr->framesize);
			dtrivertx_t	*verts = frame->verts;
			offs2 = offs*currententity->frame;
			byte *norms = currentmodel->normals + offs2;

			float	backlerp, frontlerp;
///			if ( r_lerpmodels->value )
///			{
				backlerp = currententity->backlerp;
				frontlerp = 1 - backlerp;
///			}
///			else
///			{
///				backlerp = 0;
///				frontlerp = 1;
///			}

			glBegin(GL_LINES);
			for (i=0; i<paliashdr->num_tris; i++)
			{
				for (j=0; j<3; j++)
				{
					vec3_t	n;
					index_xyz = tris[i].index_xyz[j];
					if (currentmodel->flags & RF_SMOOTHVECS)
					{
						int index2 = verts[index_xyz].lightnormalindex;
						int oldindex2 = oldverts[index_xyz].lightnormalindex;
						n[0] = r_avertexnormals[oldindex2][0]*backlerp + r_avertexnormals[index2][0]*frontlerp;
						n[1] = r_avertexnormals[oldindex2][1]*backlerp + r_avertexnormals[index2][1]*frontlerp;
						n[2] = r_avertexnormals[oldindex2][2]*backlerp + r_avertexnormals[index2][2]*frontlerp;
					}
					else
					{
						n[0] = r_avertexnormals[oldnorms[i]][0]*backlerp + r_avertexnormals[norms[i]][0]*frontlerp;
						n[1] = r_avertexnormals[oldnorms[i]][1]*backlerp + r_avertexnormals[norms[i]][1]*frontlerp;
						n[2] = r_avertexnormals[oldnorms[i]][2]*backlerp + r_avertexnormals[norms[i]][2]*frontlerp;
					}
					index_xyz = tris[i].index_xyz[j];
					glVertex3fv (tempVertexArray[index_xyz]);
					glVertex3f (tempVertexArray[index_xyz][0]+n[0],tempVertexArray[index_xyz][1]+n[1],tempVertexArray[index_xyz][2]+n[2]);
				}
			}
			glEnd ();
		}

		glEnable (GL_TEXTURE_2D);
		if (r_showtris->value > 1 || (r_editor->value && (curmodel && currententity->alink == curmodel) && (cl.time & 512)))
			glEnable (GL_DEPTH_TEST);
	}
/////////////////////////////////////////

	return calced;
}


void GL_DrawAliasFrameLerpFog (dmdl_t *paliashdr, fog_t *fog)
{
	vec3_t			oldorg;
	float			oldfog, mmm;
	vec3_t			vertexArray[3*MAX_TRIANGLES];
	byte			colorArray[3*MAX_TRIANGLES*3];
	dtriangle_t		*tris;
	int				i, j, jj=0;

///	if (!gl_config.vbo)	// tempvertexarray нужен для вычисления тумана!
		R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...
	tris = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);

	oldfog = fog->origin[2];
	VectorCopy(r_origin, oldorg);
	fog->origin[2] -= currententity->origin[2];
	VectorSubtract(r_origin, currententity->origin, r_origin);

	if (currententity->angles[1])
	{
		vec3_t	temp, vectors[3], ang;

		VectorSet(ang, 0, currententity->angles[1], 0);
		AngleVectors (ang, vectors[0], vectors[1], vectors[2]);

		VectorCopy(r_origin, temp);
		r_origin[0] = DotProduct (temp, vectors[0]);
		r_origin[1] = -DotProduct (temp, vectors[1]);
		r_origin[2] = DotProduct (temp, vectors[2]);
	}

	byte color[3];
	color[0] = fog->color[0] * 255;
	color[1] = fog->color[1] * 255;
	color[2] = fog->color[2] * 255;

	for (i=0; i<paliashdr->num_tris; i++)
	{
		for (j=0; j<3; j++, jj++)
		{
			VectorCopy(tempVertexArray[tris[i].index_xyz[j]], vertexArray[jj]);
			mmm = FogDist(fog, vertexArray[jj]);
			colorArray[jj*3+0] = color[0] * mmm;
			colorArray[jj*3+1] = color[1] * mmm;
			colorArray[jj*3+2] = color[2] * mmm;
		}
	}

	glEnableClientState( GL_COLOR_ARRAY );
///	if (gl_config.vbo)
///	{
///		currententity->vbo_lightp[0] = R_VCLoadData(VBO_DYNAMIC, jj*3, &colorArray, VBO_STORE_ANY, NULL, NULL);
///		glColorPointer( 3, GL_UNSIGNED_BYTE, 0, 0 );
///	}
///	else
	{
		GL_BindVBO(NULL);
		glColorPointer( 3, GL_UNSIGNED_BYTE, 0, colorArray );
	}

	glEnableClientState( GL_VERTEX_ARRAY );
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[0]);
		glVertexPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glVertexPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glDrawArrays (GL_TRIANGLES, 0, jj);

	fog->origin[2] = oldfog;
	VectorCopy(oldorg, r_origin);

	glDisableClientState( GL_COLOR_ARRAY );
	glDisableClientState( GL_VERTEX_ARRAY );
	if (gl_config.vbo)
		GL_BindVBO(NULL);
}


void FS_UnionPath(char *path, char *file, char *name, int size)
{
	int		i, nul;
	char	*ofs, bak;
	for (ofs = path, i = 0 ; *ofs ; ofs++, i++)
		if (*ofs == '/' || *ofs == '\\')		// berserker: damn path names
			nul = i;

	bak = path[nul];
	path[nul] = 0;
	Com_sprintf(name, size, "%s/%s", path, file);
	path[nul] = bak;
}

/*
=================
Mod_LoadAliasMD3Model
=================
*/
//
// And here some Vic and some my (Harven)...  and modified by Berserker  ;)
//
void Mod_LoadAliasMD3Model ( model_t *mod, void *buffer, float scale, bool invert, bool cache )
{
	int					version, i, j, l, k, ll;
	dmd3_t				*pinmodel;
	dmd3frame_t			*pinframe;
///	dmd3tag_t			*pintag;
	dmd3mesh_t			*pinmesh;
	dmd3skin_t			*pinskin;
	dmd3coord_t			*pincoord;
	dmd3vertex_t		*pinvert;
	unsigned			*pinindex;
	WORD				*poutindex;
	maliasvertex_t		*poutvert;
	maliascoord_t		*poutcoord;
///	maliasskin_t		*poutskin;
	maliasmesh_t		*poutmesh;
///	maliastag_t			*pouttag;
	maliasframe_t		*poutframe;
	maliasmodel_t		*poutmodel;
	char				name[MAX_OSPATH+8];
	float				lat, lng;
	vec3_t				invscale;
	unsigned			checksum, chsum;

	if (scale <= 0.001)
		Com_Error (ERR_DROP, "%s with invalid scale", mod->name);

	if (invert)
		VectorSet(invscale, MD3_XYZ_SCALE * scale, -MD3_XYZ_SCALE * scale, MD3_XYZ_SCALE * scale);
	else
		VectorSet(invscale, MD3_XYZ_SCALE * scale, MD3_XYZ_SCALE * scale, MD3_XYZ_SCALE * scale);

	pinmodel = ( dmd3_t * )buffer;
	version = LittleLong( pinmodel->version );

	if ( version != MD3_ALIAS_VERSION )
		Com_Error (ERR_DROP, "%s has wrong version number (%i should be %i)", mod->name, version, MD3_ALIAS_VERSION);

	chsum = LittleLong (Com_BlockChecksum (buffer, modfilelen));

	poutmodel = (maliasmodel_s*) Hunk_Alloc (sizeof(maliasmodel_t), true);

	// byte swap the header fields and sanity check
	poutmodel->num_frames = LittleLong ( pinmodel->num_frames );
///	poutmodel->num_tags = LittleLong ( pinmodel->num_tags );
	poutmodel->num_meshes = LittleLong ( pinmodel->num_meshes );
///	poutmodel->num_skins = 0;

	if ( poutmodel->num_frames <= 0 )
	{
		Com_Error ( ERR_DROP, "model %s has no frames", mod->name );
	} 
	else if ( poutmodel->num_frames > MD3_MAX_FRAMES )
	{
		Com_Error ( ERR_DROP, "model %s has too many frames", mod->name );
	}

///	if ( poutmodel->num_tags > MD3_MAX_TAGS )
///	{
///		Com_Error ( ERR_DROP, "model %s has too many tags", mod->name );
///	}
///	else if ( poutmodel->num_tags < 0 ) 
///	{
///		Com_Error ( ERR_DROP, "model %s has invalid number of tags", mod->name );
///	}

	if ( poutmodel->num_meshes <= 0 )
	{
		Com_Error ( ERR_DROP, "model %s has no meshes", mod->name );
	}
	else if ( poutmodel->num_meshes > MD3_MAX_MESHES )
	{
		Com_Error ( ERR_DROP, "model %s has too many meshes", mod->name );
	}

//
// load the frames
//

	pinframe = (dmd3frame_t *)((byte *)pinmodel + LittleLong (pinmodel->ofs_frames));
	poutframe = poutmodel->frames = (maliasframe_t*) Hunk_Alloc ( sizeof(maliasframe_t) * poutmodel->num_frames, true);

///	ClearBounds ( mod->mins, mod->maxs );
	for ( i = 0; i < poutmodel->num_frames; i++, pinframe++, poutframe++ )
	{
		for ( j = 0; j < 3; j++ )
		{
			poutframe->translate[j] = LittleFloat ( pinframe->translate[j] ) * scale;
			poutframe->mins[j] = LittleFloat ( pinframe->mins[j] ) * scale + poutframe->translate[j];
			poutframe->maxs[j] = LittleFloat ( pinframe->maxs[j] ) * scale + poutframe->translate[j];
		}
		if (invert)
		{
			poutframe->translate[1] = -poutframe->translate[1];
			poutframe->mins[1] = LittleFloat ( pinframe->maxs[1] ) * scale + poutframe->translate[1];
			poutframe->maxs[1] = LittleFloat ( pinframe->mins[1] ) * scale + poutframe->translate[1];
		}

		poutframe->radius = LittleFloat ( pinframe->radius ) * scale;
///		AddPointToBounds ( poutframe->mins, mod->mins, mod->maxs );
///		AddPointToBounds ( poutframe->maxs, mod->mins, mod->maxs );
	}

/*
//
// load the tags
//
	pintag = (dmd3tag_t *)((byte *)pinmodel + LittleLong (pinmodel->ofs_tags));
	pouttag = poutmodel->tags = (maliastag_t*) Hunk_Alloc( sizeof(maliastag_t) * poutmodel->num_frames * poutmodel->num_tags, true);

	for ( i = 0; i < poutmodel->num_frames; i++ )
	{
		for ( l = 0; l < poutmodel->num_tags; l++, pintag++, pouttag++ )
		{
			Memcpy ( pouttag->name, pintag->name, MD3_MAX_PATH );
			for ( j = 0; j < 3; j++ )
			{
				pouttag->orient.origin[j] = LittleFloat ( pintag->orient.origin[j] );
				pouttag->orient.axis[0][j] = LittleFloat ( pintag->orient.axis[0][j] );
				pouttag->orient.axis[1][j] = LittleFloat ( pintag->orient.axis[1][j] );
				pouttag->orient.axis[2][j] = LittleFloat ( pintag->orient.axis[2][j] );
			}
///			Com_Printf("X: (%f %f %f) Y: (%f %f %f) Z: (%f %f %f)\n", pouttag->orient.axis[0][0], pouttag->orient.axis[0][1], pouttag->orient.axis[0][2], pouttag->orient.axis[1][0], pouttag->orient.axis[1][1], pouttag->orient.axis[1][2], pouttag->orient.axis[2][0], pouttag->orient.axis[2][1], pouttag->orient.axis[2][2]);
		}
	}
*/

	ClearBounds(mod->mins, mod->maxs);
//
// load the meshes
//
	pinmesh = (dmd3mesh_t *)((byte *)pinmodel + LittleLong (pinmodel->ofs_meshes));
	poutmesh = poutmodel->meshes = (maliasmesh_t*) Hunk_Alloc ( sizeof(maliasmesh_t)*poutmodel->num_meshes, true);
	mod->flags = 0;
	for ( i = 0; i < poutmodel->num_meshes; i++, poutmesh++)
	{
		Memcpy (poutmesh->name, pinmesh->name, MD3_MAX_PATH);

		if ( strncmp ((const char *)pinmesh->id, "IDP3", 4))
			Com_Error ( ERR_DROP, "mesh %s in model %s has wrong id (%i should be %i)", poutmesh->name, mod->name, pinmesh->id, IDMD3HEADER );

		poutmesh->num_tris = LittleLong ( pinmesh->num_tris );
		poutmesh->num_skins = LittleLong ( pinmesh->num_skins );
		poutmesh->num_verts = LittleLong ( pinmesh->num_verts );

		if ( poutmesh->num_skins <= 0 )
		{
			Com_Error ( ERR_DROP, "mesh %i in model %s has no skins", i, mod->name );
		}
		else if ( poutmesh->num_skins > MD3_MAX_SKINS )
		{
			Com_Error ( ERR_DROP, "mesh %i in model %s has too many skins", i, mod->name );
		}

		if ( poutmesh->num_tris <= 0 )
		{
			Com_Error ( ERR_DROP, "mesh %i in model %s has no triangles", i, mod->name );
		}
		else if ( poutmesh->num_tris > MD3_MAX_TRIANGLES )
		{
			Com_Error ( ERR_DROP, "mesh %i in model %s has too many triangles", i, mod->name );
		}

		if ( poutmesh->num_verts <= 0 )
		{
			Com_Error ( ERR_DROP, "mesh %i in model %s has no vertices", i, mod->name );
		}
		else if ( poutmesh->num_verts > MD3_MAX_VERTS )
		{
			Com_Error ( ERR_DROP, "mesh %i in model %s has too many vertices: %i > %i", i, mod->name, poutmesh->num_verts, MD3_MAX_VERTS );
		}

		// register all skins
		pinskin = (dmd3skin_t *)((byte *)pinmesh + LittleLong (pinmesh->ofs_skins));
///		poutskin = poutmesh->skins = (maliasskin_t*) Hunk_Alloc( sizeof(maliasskin_t) * poutmesh->num_skins, true);

		for ( j = 0; j < poutmesh->num_skins; j++, pinskin++/*, poutskin++*/ )
		{
			if (cache || /*!pinskin->name || */!pinskin->name[0])
			{
				poutmesh->img_skins[j] = r_notexture;
				poutmesh->img_bumps[j] = poutmesh->img_lights[j] = NULL;
				continue;
			}
///			Memcpy (poutskin->name, pinskin->name, MD3_MAX_PATH);
			if (pinskin->name[0]=='.' && pinskin->name[1]=='.')	// ".."
				FS_UnionPath(mod->name, pinskin->name, name, sizeof(name));
			else
				Memcpy (name, pinskin->name, MD3_MAX_PATH);
			while (RepairPath(name));	/// Berserker: устранение безобразия типа в модели: "models\monsters\tank\tris.md2" - там есть строки типа "models/monsters/tank/../ctank/skin.pcx"
			int len = strlen(name);
			char *dot = strstr (name, ".");
			if (dot)
				k = strlen(dot);
			else
				k = 0;
			if(len < k+1)		// Если встретился меш с коротким именем, то это фигня, игнорируем!
			{
				poutmesh->img_skins[j] = r_notexture;
				poutmesh->img_bumps[j] = poutmesh->img_lights[j] = NULL;
				continue;
			}
			name[len-k] = 0;	// cut off file-ext
			poutmesh->img_skins[j] = GL_FindImage(name, it_skin, false, 0, false, 0);

			// не нашли, попробуем взять шкуру используя путь до модели
			if (!poutmesh->img_skins[j])
			{
				char path[MAX_QPATH], *file;
				COM_FilePath(mod->name, path);
				file = COM_SkipPath(name);
				Com_sprintf(name, sizeof(name), "%s/%s", path, file);
				poutmesh->img_skins[j] = GL_FindImage(name, it_skin, false, 0, false, 0);
				len = strlen(name);
				k = 0;
			}

			if(poutmesh->img_skins[j] && !r_simple->value)
			{
				if (poutmesh->img_skins[j]->fx == fx_distort)
					mod->flags |= RF_DISTORT_SKIN;	/// признак того, что хоть один скин с дисторт-эффектом
				name[len-k+0] = '_';
				name[len-k+1] = 'b';
				name[len-k+2] = 'u';
				name[len-k+3] = 'm';
				name[len-k+4] = 'p';
				name[len-k+5] = '.';
				name[len-k+6] = 't';
				name[len-k+7] = 'g';
				name[len-k+8] = 'a';
				name[len-k+9] = 0;
				poutmesh->img_bumps[j] = GL_FindImage (name, it_bump, true, poutmesh->img_skins[j], false, 0);
				if (poutmesh->img_bumps[j] == r_defaultbump)
				{	// если нет bump-скина TGA, то попробуем DDS
					name[len-k+6] = 'd';
					name[len-k+7] = 'd';
					name[len-k+8] = 's';
					poutmesh->img_bumps[j] = GL_FindImage (name, it_bump, true, poutmesh->img_skins[j], (r_bumpscale->value && !(mod->flags & RF_NOAUTOBUMP)) ? true : false, 0);
				}
			}
			else
			{
				if (!poutmesh->img_skins[j])
				{
					Com_DPrintf("Warning: %s not found for model %s\n", name, mod->name);
					poutmesh->img_skins[j] = r_notexture;
				}
				poutmesh->img_bumps[j] = NULL;
			}

			name[len-k+0] = '_';
			name[len-k+1] = 'l';
			name[len-k+2] = 'i';
			name[len-k+3] = 'g';
			name[len-k+4] = 'h';
			name[len-k+5] = 't';
			name[len-k+6] = 0;
			poutmesh->img_lights[j] = GL_FindImage (name, it_light, false, 0, false, 0);
		}

		//
		// load the indexes
		//

		pinindex = (unsigned *)((byte *)pinmesh + LittleLong (pinmesh->ofs_tris));
		poutindex = poutmesh->indexes = (WORD*) Hunk_Alloc( sizeof(WORD) * poutmesh->num_tris * 3, true);

		for ( j = 0; j < poutmesh->num_tris; j++, pinindex += 3, poutindex += 3 )
		{
			if (invert)
			{	/// invert indices sequence
				poutindex[0] = (WORD)LittleLong ( pinindex[2] );
				poutindex[1] = (WORD)LittleLong ( pinindex[1] );
				poutindex[2] = (WORD)LittleLong ( pinindex[0] );
			}
			else
			{
				poutindex[0] = (WORD)LittleLong ( pinindex[0] );
				poutindex[1] = (WORD)LittleLong ( pinindex[1] );
				poutindex[2] = (WORD)LittleLong ( pinindex[2] );
			}
		}

		//
		// load the texture coordinates
		//

		pincoord = (dmd3coord_t *)((byte *)pinmesh + LittleLong (pinmesh->ofs_tcs));
		poutcoord = poutmesh->stcoords = (maliascoord_t*) Hunk_Alloc( sizeof(maliascoord_t) * poutmesh->num_verts, true);

		for ( j = 0; j < poutmesh->num_verts; j++, pincoord++, poutcoord++ )
		{
			poutcoord->st[0] = LittleFloat ( pincoord->st[0] );
			poutcoord->st[1] = LittleFloat ( pincoord->st[1] );
			if (poutmesh->img_skins[0] && poutmesh->img_skins[0]->rotate)		// просто проверяем первый скин меша, поэтому не надо смешивать в одном меше шкуры с разными rotate
			{	// отцентрируем вращающуюся текстуру
				poutcoord->st[0] -= 0.5;
				poutcoord->st[1] -= 0.5;
			}
		}

		//
		// load the vertexes and normals
		//

		pinvert = (dmd3vertex_t *)((byte *)pinmesh + LittleLong (pinmesh->ofs_verts));
		poutvert = poutmesh->vertexes = (maliasvertex_t*) Hunk_Alloc(poutmodel->num_frames * poutmesh->num_verts * sizeof(maliasvertex_t), true);

		for (l = 0; l < poutmodel->num_frames; l++)
		{
			for (j = 0; j < poutmesh->num_verts; j++, pinvert++, poutvert++)
			{
				vec3_t	vertex;
				int		y;
				for (y = 0; y < 3; y++)
				{
					poutvert->point[y] = (float)LittleShort(pinvert->point[y]) * invscale[y];
					vertex[y] = poutvert->point[y] + poutmodel->frames[l].translate[y];
				}
				AddPointToBounds(vertex, mod->mins, mod->maxs);
			}
		}

		pinvert = (dmd3vertex_t *)((byte *)pinmesh + LittleLong(pinmesh->ofs_verts));
		poutvert = poutmesh->vertexes;

		vec3_t		tangents[MD3_MAX_VERTS], binormals[MD3_MAX_VERTS];
		char		cachename[MAX_OSPATH];
		FILE		*f;
		// create the cache directory
		if (invert)
			Com_sprintf (cachename, sizeof(cachename), "%s/cache/Inv_%s_%i", FS_Gamedir(), mod->name, i);
		else
			Com_sprintf (cachename, sizeof(cachename), "%s/cache/%s_%i", FS_Gamedir(), mod->name, i);

		if (strcmp(fs_gamedir+2, BASEDIRNAME))		// именно через сравнение строк: чтобы не прокатило +set game baseq2
		{	// если работает мод
			char	basecachename[MAX_QPATH];

			// проверим, имеется ли уже кэш для мода?
			f = FS_Fopen(cachename, "rb");
			if (f)			// если есть, то выходим
				goto good444;

			// если нет, попробуем скопировать кэш из baseq2
			if (invert)
				Com_sprintf (basecachename, sizeof(basecachename), "%s/cache/Inv_%s_%i", BASEDIRNAME, mod->name, i);
			else
				Com_sprintf (basecachename, sizeof(basecachename), "%s/cache/%s_%i", BASEDIRNAME, mod->name, i);
			CopyFile(basecachename, cachename);
		}
		f = FS_Fopen(cachename, "rb");
		if (f)
		{	/// read from cache
good444:	fread(&checksum, 1, sizeof(unsigned), f);
#define MAGIC_CACHE_MD3 2	// Здесь это магическое число меняем чтоб следующая версия движка сбрасывала кэш!
			chsum += MAGIC_CACHE_MD3;
			checksum -= chsum;
			if (checksum)
				goto eggog;
			poutvert = poutmesh->vertexes;
			for ( l = 0; l < poutmodel->num_frames; l++ )
			{
				for (j=0; j<poutmesh->num_verts; j++, poutvert++)
				{
					byte ivec[3];
					if (fread(ivec, sizeof(ivec), 1, f) != 1)
						goto egg;
					poutvert->normal[0] = ivec[0] * (1.0 / 127.0) - 1.0;
					poutvert->normal[1] = ivec[1] * (1.0 / 127.0) - 1.0;
					poutvert->normal[2] = ivec[2] * (1.0 / 127.0) - 1.0;
					if (fread(ivec, sizeof(ivec), 1, f) != 1)
						goto egg;
					poutvert->tangent[0] = ivec[0] * (1.0 / 127.0) - 1.0;
					poutvert->tangent[1] = ivec[1] * (1.0 / 127.0) - 1.0;
					poutvert->tangent[2] = ivec[2] * (1.0 / 127.0) - 1.0;
					if (fread(ivec, sizeof(ivec), 1, f) != 1)
					{
egg:					checksum = 1;	// любое ненулевое число
						goto eggog;
					}
					poutvert->binormal[0] = ivec[0] * (1.0 / 127.0) - 1.0;
					poutvert->binormal[1] = ivec[1] * (1.0 / 127.0) - 1.0;
					poutvert->binormal[2] = ivec[2] * (1.0 / 127.0) - 1.0;
				}
			}
eggog:		fclose(f);
			if (checksum)
			{
				Com_DPrintf("%s_%i: invalid cache\n", mod->name, i);
				goto err;
			}
			else
				Com_DPrintf("%s_%i: loaded from cache\n", mod->name, i);
		}
		else
		{
err:		if (!cache)
				Com_DPrintf("%s_%i: calculating extra data\n", mod->name, i);
			else
				caching_calc = true;

			// for all frames
			for (l=0; l<poutmodel->num_frames; l++)
			{
				Memset(tangents, 0, poutmesh->num_verts*sizeof(vec3_t));
				Memset(binormals, 0, poutmesh->num_verts*sizeof(vec3_t));
				poutvert = poutmesh->vertexes + l * poutmesh->num_verts;
				for ( j = 0; j < poutmesh->num_verts; j++, pinvert++, poutvert++ )
				{
					vec3_t	vertex;
					int		y;
					for(y=0; y<3; y++)
					{
						poutvert->point[y] = (float)LittleShort ( pinvert->point[y] ) * invscale[y];
						vertex[y] = poutvert->point[y] + poutmodel->frames[l].translate[y];
					}

					lat = (pinvert->norm >> 8) & 0xff;
					lng = (pinvert->norm & 0xff);

					lat *= M_PI/128;
					lng *= M_PI/128;
#if 0
					vec3_t	norma;
					float	slat, clat, slng, clng;
					SinCos(lat, &slat, &clat);
					SinCos(lng, &slng, &clng);

					norma[0] = clat * slng;
					if (invert)
						norma[1] = -slat * slng;
					else
						norma[1] = slat * slng;
					norma[2] = clng;
					poutvert->normal = Normal2Index(norma);
#else
					float	slat, clat, slng, clng;
					SinCos(lat, &slat, &clat);
					SinCos(lng, &slng, &clng);

					poutvert->normal[0] = clat * slng;
					if (invert)
						poutvert->normal[1] = -slat * slng;
					else
						poutvert->normal[1] = slat * slng;
					poutvert->normal[2] = clng;
#endif
				}
				//for all tris
				poutvert = poutmesh->vertexes + l * poutmesh->num_verts;
				for (j=0; j<poutmesh->num_tris; j++ )
				{
					vec3_t tangent;
					vec3_t binormal;

					TangentForTrimd3(&poutmesh->indexes[j*3], poutvert, poutmesh->stcoords, tangent, binormal);
					// for all vertices in the tri
					for (k=0; k<3; k++)
					{
						ll = poutmesh->indexes[j*3+k];
						VectorAdd(tangents[ll], tangent, tangents[ll]);
						VectorAdd(binormals[ll], binormal, binormals[ll]);
					}
				}
				// normalize averages
				for (j=0; j<poutmesh->num_verts; j++ )
				{
					VectorNormalize(tangents[j]);
					VectorNormalize(binormals[j]);
///					poutvert[j].tangent = Normal2Index(tangents[j]);
///					poutvert[j].binormal = Normal2Index(binormals[j]);
						VectorCopy(tangents[j], poutvert[j].tangent);
						VectorCopy(binormals[j], poutvert[j].binormal);
				}

				/// Berserker: сравниваем координаты всех точек меша. Если равны, то складываем tangent/binormal точек.
				for (j = 0; j < poutmesh->num_verts - 1; j++)
				{
					for (int b = j + 1; b < poutmesh->num_verts; b++)
					{
						if (VectorCompareEpsilon(poutvert[j].point, poutvert[b].point, 0.001f))
						{
							if (DotProduct(poutvert[j].normal, poutvert[b].normal) >= 0.9848)	/// cos 10. Если угол меньше 10 градусов, то сглаживаем.
							{
								VectorAdd(poutvert[j].normal, poutvert[b].normal, poutvert[j].normal);
								VectorCopy(poutvert[j].normal, poutvert[b].normal);
								VectorNormalize(poutvert[j].normal);
								VectorNormalize(poutvert[b].normal);

								VectorAdd(poutvert[j].tangent, poutvert[b].tangent, poutvert[j].tangent);
								VectorCopy(poutvert[j].tangent, poutvert[b].tangent);

								VectorAdd(poutvert[j].binormal, poutvert[b].binormal, poutvert[j].binormal);
								VectorCopy(poutvert[j].binormal, poutvert[b].binormal);
							}
						}
					}
				}
				/// normalize it
				for (j = 0; j < poutmesh->num_verts - 1; j++)
				{
					VectorNormalize(poutvert[j].tangent);
					VectorNormalize(poutvert[j].binormal);
				}
			}

			// save the cache
			FS_CreatePath(cachename);
			f = FS_Fopen(cachename, "wb");
			if(f)
			{
				poutvert = poutmesh->vertexes;
				fwrite(&chsum, 1, sizeof(unsigned), f);
				for ( l = 0; l < poutmodel->num_frames; l++ )
				{
					for (j=0; j<poutmesh->num_verts; j++, poutvert++)
					{
						byte ivec[3];
						ivec[0] = (poutvert->normal[0] + 1.0) * 127.0;
						ivec[1] = (poutvert->normal[1] + 1.0) * 127.0;
						ivec[2] = (poutvert->normal[2] + 1.0) * 127.0;
						fwrite(ivec, sizeof(ivec), 1, f);
						ivec[0] = (poutvert->tangent[0] + 1.0) * 127.0;
						ivec[1] = (poutvert->tangent[1] + 1.0) * 127.0;
						ivec[2] = (poutvert->tangent[2] + 1.0) * 127.0;
						fwrite(ivec, sizeof(ivec), 1, f);
						ivec[0] = (poutvert->binormal[0] + 1.0) * 127.0;
						ivec[1] = (poutvert->binormal[1] + 1.0) * 127.0;
						ivec[2] = (poutvert->binormal[2] + 1.0) * 127.0;
						fwrite(ivec, sizeof(ivec), 1, f);
					}
				}
				fclose(f);
			}
		}

		pinmesh = (dmd3mesh_t *)((byte *)pinmesh + LittleLong (pinmesh->meshsize));
		poutmesh->triangles = (neighbours_t*) Hunk_Alloc ( sizeof(neighbours_t) * poutmesh->num_tris, true);
		if (!r_simple->value)
			R_BuildTriangleNeighbors ( poutmesh->triangles, poutmesh->indexes, poutmesh->num_tris );
	}
	mod->type = mod_alias_md3;

	char	nam[MAX_OSPATH];
	char	*buff;
	i = strlen(mod->name);
	Memcpy(nam, mod->name, i);
	nam[i-3]='f';
	nam[i-2]='x';
	nam[i]=0;
	// load the .fx3
	i = FS_LoadFile (nam, (void **)&buff);
	InitModLightsEmits(mod);
	if (buff)
	{
		if (i > 0)
		{
			char bak=buff[i];
			buff[i]=0;
			ParseMFX(mod, buff);
			buff[i]=bak;
		}
		Z_Free (buff);
	}

	mod->md3_has_solid_mesh = mod->md3_has_trans_mesh = false;
	poutmesh = poutmodel->meshes;
	for ( i = 0; i < poutmodel->num_meshes; i++, poutmesh++)
	{
		for (int j=0; j<poutmesh->num_skins; j++)
		{
			image_t *skin = poutmesh->img_skins[j];
			if(skin)
			{
				if (skin->Translucent33 || skin->Translucent66)
					mod->md3_has_trans_mesh = true;
				else
					mod->md3_has_solid_mesh = true;
			}
		}
	}

	// если все меши с дистортом, назначим модели флаг RF_DISTORT (ибо так практичнее для оклюжн-теста... нет бага)
	poutmesh = poutmodel->meshes;
	for ( i = 0; i < poutmodel->num_meshes; i++, poutmesh++)
	{
		int	j;
		for (j=0; j<poutmesh->num_skins; j++)
		{
			image_t *skin = poutmesh->img_skins[j];
			if(skin)
			{
				if (skin->fx != fx_distort)
					break;
			}
		}
		if (j < poutmesh->num_skins)
			break;
	}
	if (i == poutmodel->num_meshes)	// все меши с дистортом
		mod->flags |= RF_DISTORT;

/// Calc md3 bounds and radius...
	vec3_t	tempr, tempv;
	tempr[0] = mod->maxs[0] - mod->mins[0];
	tempr[1] = mod->maxs[1] - mod->mins[1];
	tempr[2] = 0;
	tempv[0] = 0;
	tempv[1] = 0;
	tempv[2] = mod->maxs[2] - mod->mins[2];
	mod->radius = max(VectorLength(tempr), VectorLength(tempv));

	for(i=0; i<3; i++)
		mod->center[i] = (mod->maxs[i] + mod->mins[i]) * 0.5;

	poutmesh = poutmodel->meshes;
	for ( i = 0; i < poutmodel->num_meshes; i++, poutmesh++)
	{
		poutmesh->vbo_st = NULL;
		if (gl_config.vbo)
		{
			float *map = (float*) vbo_shadow;	// hack: этот буфер очень большой, поэтому он должен вместить в себя любую модель
			maliascoord_t *coord = poutmesh->stcoords;
			for (k=0, l=0; l<poutmesh->num_verts; l++)
			{
				map[k++] = coord[l].st[0];
				map[k++] = coord[l].st[1];
			}

			if (k>3*MAX_VBO_XYZs)
				Com_Error(ERR_FATAL, "Temporary buffer overflow");

			poutmesh->vbo_st = R_VCLoadData(VBO_STATIC, k*sizeof(float), &vbo_shadow, VBO_STORE_ANY, NULL, NULL);
			GL_BindVBO(NULL);
		}
	}
}

//////////////////////////////////////////////////////


void Mod_LoadSpriteModel(model_t *mod, void *buffer, bool cache)
{
	dsprite_t	*sprin, *sprout;
	int			i;

	sprin = (dsprite_t *)buffer;
	sprout = (dsprite_t *) Hunk_Alloc (modfilelen, true);

	if (cache)
	{
		caching_calc = true;
		return;
	}

	sprout->ident = LittleLong (sprin->ident);
	sprout->version = LittleLong (sprin->version);
	sprout->numframes = LittleLong (sprin->numframes);

	if (sprout->version != SPRITE_VERSION)
		Com_Error (ERR_DROP, "%s has wrong version number (%i should be %i)", mod->name, sprout->version, SPRITE_VERSION);

	if (sprout->numframes > MAX_MD2SKINS)
		Com_Error (ERR_DROP, "%s has too many frames (%i > %i)", mod->name, sprout->numframes, MAX_MD2SKINS);

	mod->flags = 0;
	// byte swap everything
	for (i=0 ; i<sprout->numframes ; i++)
	{
		sprout->frames[i].width = LittleLong (sprin->frames[i].width);
		sprout->frames[i].height = LittleLong (sprin->frames[i].height);
		sprout->frames[i].origin_x = LittleLong (sprin->frames[i].origin_x);
		sprout->frames[i].origin_y = LittleLong (sprin->frames[i].origin_y);
		Memcpy (sprout->frames[i].name, sprin->frames[i].name, MAX_SKINNAME);
		mod->skins[i] = GL_FindImage (sprout->frames[i].name, it_sprite, false, 0, false, 0);	/// !!! Для спрайтов не бывает бампа!!!
		// не нашли, попробуем взять шкуру используя путь до спрайта
		if (!mod->skins[i])
		{
			char path[MAX_QPATH], *file, name[MAX_OSPATH];
			COM_FilePath(mod->name, path);
			file = COM_SkipPath(sprout->frames[i].name);
			Com_sprintf(name, sizeof(name), "%s/%s", path, file);
			mod->skins[i] = GL_FindImage (name, it_sprite, false, 0, false, 0);	/// !!! Для спрайтов не бывает бампа!!!
		}
		if (!mod->skins[i])
			Com_Error (ERR_DROP, "%s has not image", mod->name);
		if (mod->skins[i]->fx == fx_distort)
			mod->flags |= RF_DISTORT_SKIN;	/// признак того, что хоть один скин с дисторт-эффектом
	}

	mod->numframes = sprout->numframes;
	mod->type = mod_sprite;
}


bool Mod_LoadAliasModel (model_t *mod, void *buffer, float scale, bool invert, int smoothType, bool cache)
{
	int					i, j, index_st, *pincmd, *poutcmd, version;
	dmdl_t				*pinmodel, *pheader;
	dstvert_t			*pinst;
	fstvert_t			*poutst;
	dtriangle_t			*pintri, *pouttri;
	daliasframe_t		*pinframe, *poutframe;
	float				s, t, iw, ih;

	if (scale <= 0.001)
		Com_Error (ERR_DROP, "%s with invalid scale", mod->name);

	pinmodel = (dmdl_t *)buffer;

	version = LittleLong (pinmodel->version);
	if (version != ALIAS_VERSION)
		Com_Error (ERR_DROP, "%s has wrong version number (%i should be %i)", mod->name, version, ALIAS_VERSION);

	pheader = (dmdl_t*) Hunk_Alloc (LittleLong(pinmodel->ofs_end), false);
	if (!pheader)
		return false;

	// byte swap the header fields and sanity check
	for (i=0 ; i<sizeof(dmdl_t)/4 ; i++)
		((int *)pheader)[i] = LittleLong (((int *)buffer)[i]);

///	Berserker: stupid limit
///	if (pheader->skinheight > MAX_LBM_HEIGHT)
///		Com_Error (ERR_DROP, "model %s has a skin taller than %d", mod->name, MAX_LBM_HEIGHT);

	if (pheader->num_xyz <= 0)
		Com_Error (ERR_DROP, "model %s has no vertices", mod->name);

	if (pheader->num_xyz > MAX_VERTS)
		Com_Error (ERR_DROP, "model %s has too many vertices", mod->name);

	if (pheader->num_st <= 0)
		Com_Error (ERR_DROP, "model %s has no st vertices", mod->name);

	if (pheader->num_tris <= 0)
		Com_Error (ERR_DROP, "model %s has no triangles", mod->name);

	/// added by Berserker
	if (pheader->num_tris > MAX_TRIANGLES)
		Com_Error (ERR_DROP, "model %s has too many triangles", mod->name);

	if (pheader->num_frames <= 0)
		Com_Error (ERR_DROP, "model %s has no frames", mod->name);

//
// load triangle lists
//
	pintri = (dtriangle_t *) ((byte *)pinmodel + pheader->ofs_tris);
	pouttri = (dtriangle_t *) ((byte *)pheader + pheader->ofs_tris);
	for (i=0 ; i<pheader->num_tris ; i++)
	{
		if (invert)
		{	/// invert indices sequence
			for (j=0 ; j<3 ; j++)
			{
				pouttri[i].index_xyz[j] = LittleShort (pintri[i].index_xyz[2-j]);
				pouttri[i].index_st[j] = LittleShort (pintri[i].index_st[2-j]);
			}
		}
		else
		{
			for (j=0 ; j<3 ; j++)
			{
				pouttri[i].index_xyz[j] = LittleShort (pintri[i].index_xyz[j]);
				pouttri[i].index_st[j] = LittleShort (pintri[i].index_st[j]);
			}
		}
	}

//
// load the frames
//
	for (i=0 ; i<pheader->num_frames ; i++)
	{
		pinframe = (daliasframe_t *) ((byte *)pinmodel + pheader->ofs_frames + i * pheader->framesize);
		poutframe = (daliasframe_t *) ((byte *)pheader + pheader->ofs_frames + i * pheader->framesize);

///		Memcpy (poutframe->name, pinframe->name, sizeof(poutframe->name));		// poutframe->name NOT USED ???
		for (j=0 ; j<3 ; j++)
		{
			poutframe->scale[j] = LittleFloat (pinframe->scale[j]) * scale;
			poutframe->translate[j] = LittleFloat (pinframe->translate[j]) * scale;
		}
		if (invert)
		{	/// invert scale and translate
			poutframe->scale[1] = -poutframe->scale[1];
			poutframe->translate[1] = -poutframe->translate[1];
		}
		// verts are all 8 bit, so no swapping needed
		Memcpy (poutframe->verts, pinframe->verts, pheader->num_xyz*sizeof(dtrivertx_t));
	}

//
// load base s and t vertices
//
	pinst = (dstvert_t *) ((byte *)pinmodel + pheader->ofs_st);
	poutst = (fstvert_t*)Hunk_Alloc (pheader->num_st * sizeof(fstvert_t), false);
	if (!poutst)
		return false;

///	// Fix ofs_st
///	pheader->ofs_st = (byte *)poutst - (byte *)pheader;
	// Calculate texcoords for triangles (for general use and compute tangents and binormals)
	iw = 1.0 / pheader->skinwidth;
	ih = 1.0 / pheader->skinheight;
	for (i=0; i<pheader->num_st ; i++)
	{
		s = LittleShort (pinst[i].s);
		t = LittleShort (pinst[i].t);
		poutst[i].s = (s - 0.5) * iw;
		poutst[i].t = (t - 0.5) * ih;
	}

	//
	// load the glcmds
	//
	if (!cache)
	{
		pincmd = (int *)((byte *)pinmodel + pheader->ofs_glcmds);
		poutcmd = (int *)((byte *)pheader + pheader->ofs_glcmds);
		for (i = 0; i<pheader->num_glcmds; i++)
			poutcmd[i] = LittleLong(pincmd[i]);
	}

	/// reserve some space for ST cache
	if (!gl_config.vbo)
		mod->st = (float*) Z_Malloc(pheader->num_tris * 3 * sizeof(float) * 2, true);

	mod->type = mod_alias;

	mod->flags = 0;
	char	nam[MAX_OSPATH];
	char	*buff;
	i = strlen(mod->name);
	Memcpy(nam, mod->name, i);
	nam[i-3]='f';
	nam[i-2]='x';
	nam[i]=0;
	// Berserker: грязный хак: по умолчанию пусть модели сглаживаются.
	mod->flags |= RF_SMOOTHVECS;
	// load the .fx2
	i = FS_LoadFile (nam, (void **)&buff);
	InitModLightsEmits(mod);
	if (buff)
	{
		if (i>0)
		{
			char bak=buff[i];
			buff[i]=0;
			ParseMFX(mod, buff);
			buff[i]=bak;
		}
		Z_Free (buff);
	}

	if (smoothType)
		if ((bool)(smoothType - 1) != (bool)(mod->flags & RF_SMOOTHVECS))
			return false;		// зарезервировано памяти по данным из кэша, а модель имеет другой тип сглаживания! Сброс!

	// register all skins
	Memcpy ((char *)pheader + pheader->ofs_skins, (char *)pinmodel + pheader->ofs_skins, pheader->num_skins*MAX_SKINNAME);

	if (cache)	/// хак: если кэшируем, то нет нужды грузить шкуры.
		pheader->num_skins = 0;

	for (i=0 ; i<pheader->num_skins ; i++)
	{
		char name[MAX_OSPATH+6];
		Com_sprintf (name, sizeof(name),(char *)pheader + pheader->ofs_skins + i*MAX_SKINNAME);
		if (name[0]=='.' && name[1]=='.')	// ".."
			FS_UnionPath(mod->name, (char *)pheader + pheader->ofs_skins + i*MAX_SKINNAME, name, sizeof(name));
		while (RepairPath(name));	/// Berserker: устранение безобразия типа в модели: "models\monsters\tank\tris.md2" - там есть строки типа "models/monsters/tank/../ctank/skin.pcx"
		int len = strlen(name);
		char *dot = strstr (name, ".");
		if (dot)
			j = strlen(dot);
		else
			j = 0;
		if(len < j+1)		// Если встретился меш с коротким именем, то это фигня, игнорируем!
			goto badM;
		name[len-j] = 0;	// cut off file-ext
		mod->skins[i] = GL_FindImage (name, it_skin, false, 0, false, 0);
		// не нашли, попробуем взять шкуру используя путь до модели
		if (!mod->skins[i])
		{
			char path[MAX_QPATH], *file;
			COM_FilePath(mod->name, path);
			file = COM_SkipPath(name);
			Com_sprintf(name, sizeof(name), "%s/%s", path, file);
			mod->skins[i] = GL_FindImage(name, it_skin, false, 0, false, 0);
			len = strlen(name);
			j = 0;
		}
		if (!mod->skins[i])
		{
badM:		Com_DPrintf("Warning: %s has no valid skin\n", mod->name);
			pheader->num_skins = 0;
			break;
		}

		if (!r_simple->value)
		{
			name[len-j+0] = '_';
			name[len-j+1] = 'b';
			name[len-j+2] = 'u';
			name[len-j+3] = 'm';
			name[len-j+4] = 'p';
			name[len-j+5] = '.';
			name[len-j+6] = 't';
			name[len-j+7] = 'g';
			name[len-j+8] = 'a';
			name[len-j+9] = 0;
			mod->bumps[i] = GL_FindImage (name, it_bump, true, mod->skins[i], false, 0);
			if (mod->bumps[i] == r_defaultbump)
			{	// если нет bump-скина TGA, то попробуем DDS
				name[len-j+6] = 'd';
				name[len-j+7] = 'd';
				name[len-j+8] = 's';
				mod->bumps[i] = GL_FindImage (name, it_bump, true, mod->skins[i], (r_bumpscale->value && !(mod->flags & RF_NOAUTOBUMP)) ? true : false, 0);
			}
		}
		else
			mod->bumps[i] = r_notexture;

		name[len-j+0] = '_';
		name[len-j+1] = 'l';
		name[len-j+2] = 'i';
		name[len-j+3] = 'g';
		name[len-j+4] = 'h';
		name[len-j+5] = 't';
		name[len-j+6] = 0;
		mod->lights[i] = GL_FindImage (name, it_light, false, 0, false, 0);
	}

///	mod->mins[0] = mod->mins[1] = mod->mins[2] = -32;
///	mod->maxs[0] = mod->maxs[1] = mod->maxs[2] = 32;


	vec3_t			normal, triangle[3], v1, v2;
	daliasframe_t	*frame;
	dtrivertx_t		*verts, *v;
	neighbours_t	*triangles = NULL;
	byte			*norms = NULL, *tangents = NULL, *binormals = NULL;
	int		k, l;
	bool	success = false, cache_ = false, err = true;
	int		ax, cx;
	byte	smooth;
	char	cachename[MAX_OSPATH];
	FILE	*f;
	unsigned	checksum, cs_tris, cs_binormals, cs_tangents, cs_norms;

	dtriangle_t *tris = (dtriangle_t *) ((byte *)pheader + pheader->ofs_tris);

	// create the cache directory
	if (invert)
		Com_sprintf (cachename, sizeof(cachename), "%s/cache/Inv_%s", FS_Gamedir(), mod->name);
	else
		Com_sprintf (cachename, sizeof(cachename), "%s/cache/%s", FS_Gamedir(), mod->name);

	if (strcmp(fs_gamedir+2, BASEDIRNAME))		// именно через сравнение строк: чтобы не прокатило +set game baseq2
	{	// если работает мод
		char	basecachename[MAX_QPATH];

		// проверим, имеется ли уже кэш для мода?
		f = FS_Fopen(cachename, "rb");
		if (f)			// если есть, то выходим
			goto good444;

		// если нет, попробуем скопировать кэш из baseq2
		if (invert)
			Com_sprintf (basecachename, sizeof(basecachename), "%s/cache/Inv_%s", BASEDIRNAME, mod->name);
		else
			Com_sprintf (basecachename, sizeof(basecachename), "%s/cache/%s", BASEDIRNAME, mod->name);
		CopyFile(basecachename, cachename);
	}

	f = FS_Fopen(cachename, "rb");
	if (f)
	{	/// read from cache
good444:ax = fread(&smooth, 1, sizeof(smooth), f);
		if(ax==sizeof(smooth))
		{
			if ((bool)smooth != (bool)(mod->flags & RF_SMOOTHVECS))
				goto erc;
			unsigned	ang;
			if(fread(&ang, 1, sizeof(unsigned), f)!=sizeof(unsigned))
				goto badd;
			if (ang != (unsigned)(smooth_cosine*0x7fffffff))
			{
badd:			fclose(f);
				goto bad;
			}

			cache_ = true;
			cx = pheader->num_tris * sizeof(neighbours_t);
			mod->triangles = triangles = (neighbours_t *)Hunk_Alloc (cx, false);
			if (!triangles)
				return false;

			if(fread(&cs_tris, 1, sizeof(int), f)!=sizeof(int))
				goto badd;
			cx = pheader->num_tris * sizeof(neighbours_t);
			ax = fread(mod->triangles, 1, cx, f);
			if(ax==cx)
				err = false;

			if(smooth)
			{
				cx = pheader->num_xyz * pheader->num_frames * sizeof(byte);
				mod->binormals = binormals =(byte*)Hunk_Alloc (cx, false);
				if (!binormals)
					return false;
				mod->tangents = tangents = (byte*)Hunk_Alloc (cx, false);
				if (!tangents)
					return false;
				if(fread(&cs_binormals, 1, sizeof(int), f)!=sizeof(int))
					goto badd;
				ax = fread(mod->binormals, 1, cx, f);
				if(ax==cx)
				{
					if(fread(&cs_tangents, 1, sizeof(int), f)!=sizeof(int))
						goto badd;
					ax = fread(mod->tangents, 1, cx, f);
					success = (ax==cx);
				}
			}
			else
			{
				cx = pheader->num_tris * pheader->num_frames * sizeof(byte);
				mod->binormals = binormals = (byte*)Hunk_Alloc (cx, false);
				if (!binormals)
					return false;
				mod->tangents = tangents = (byte*)Hunk_Alloc (cx, false);
				if (!tangents)
					return false;
				mod->normals = norms = (byte *)Hunk_Alloc (cx, false);
				if (!norms)
					return false;
				if(fread(&cs_binormals, 1, sizeof(int), f)!=sizeof(int))
					goto badd;
				ax = fread(mod->binormals, 1, cx, f);
				if(ax==cx)
				{
					if(fread(&cs_tangents, 1, sizeof(int), f)!=sizeof(int))
						goto badd;
					ax = fread(mod->tangents, 1, cx, f);
					if(ax==cx)
					{
						if(fread(&cs_norms, 1, sizeof(int), f)!=sizeof(int))
							goto badd;
						ax = fread(mod->normals, 1, cx, f);
						success = (ax==cx);
					}
				}
			}
		}
erc:	fclose(f);
	}

	if(!err)
	{
		ax = pheader->num_tris * sizeof(neighbours_t);
		checksum = LittleLong (Com_BlockChecksum (mod->triangles, ax));
		if(checksum == cs_tris)
			goto ok;
	}
bad:Com_DPrintf("%s: invalid cache\n", mod->name);
	success = false;

ok:	if(success)
	{
		checksum = LittleLong (Com_BlockChecksum (mod->binormals, cx));
		if(checksum == cs_binormals)
		{
			checksum = LittleLong (Com_BlockChecksum (mod->tangents, cx));
			if(checksum == cs_tangents)
			{
				if(smooth)
					goto okey;

				checksum = LittleLong (Com_BlockChecksum (mod->normals, cx));
				if(checksum == cs_norms)
					goto okey;
			}
		}
		Com_Printf("^1%s: invalid checksum!\n", mod->name);
		success = false;
	}

	if(success)
	{
okey:	Com_DPrintf("%s: loaded from cache\n", mod->name);
		if (mod->flags & RF_CALC_NORMALS)
			HACK_RecalcVertsLightNormalIdx(pheader, invert);
		else if (invert)
			Mod_InvertNormal(pheader);
		mod->flags &= ~RF_SMOOTHVECS;
		if(smooth)
			mod->flags |= RF_SMOOTHVECS;
		goto exit;
	}

	if (cache_)
		Com_DPrintf("error loading /%s from cache...\n", mod->name);

	if (!cache)
		Com_DPrintf("%s: calculating extra data\n", mod->name);
	else
		caching_calc = true;

	//Alloc triangles (for shadow volumes)
	if(!triangles)
	{
		mod->triangles = triangles = (neighbours_t *)Hunk_Alloc (pheader->num_tris * sizeof(neighbours_t), false);
		if (!triangles)
			return false;
	}

	if (!r_simple->value)
	{
		//Set neighbours to NULL
		for (i=0 ; i<pheader->num_tris ; i++)
			for (j=0 ; j<3 ; j++)
				triangles[i].neighbours[j] = -1;

		//Generate edges information (for shadow volumes)
		//Note: We do this with the original vertices not the reordered onces since reordening them
		//duplicates vertices and we only compare indices
		for (i=0 ; i<pheader->num_tris ; i++)
			for (j=0 ; j<3 ; j++)
				if (triangles[i].neighbours[j] == -1)	//none found yet 
					triangles[i].neighbours[j] = findneighbour(i, j, pheader->num_tris, triangles, tris);
	}

	if (mod->flags & RF_CALC_NORMALS)
		HACK_RecalcVertsLightNormalIdx(pheader, invert);
	else if (invert)
		Mod_InvertNormal(pheader);

	if(mod->flags & RF_SMOOTHVECS)
	{
		smooth = 1;
		cx = pheader->num_xyz * pheader->num_frames * sizeof(byte);
		vec3_t	binormals_[MAX_VERTS];
		vec3_t	tangents_[MAX_VERTS];
		// Calculate tangents for vertices (bump mapping)
		if(!binormals || !tangents)
		{
			mod->binormals = binormals = (byte*)Hunk_Alloc (cx, false);
			if (!binormals)
				return false;
			mod->tangents = tangents = (byte*)Hunk_Alloc (cx, false);
			if (!tangents)
				return false;
		}

		//for all frames
		for (i=0; i<pheader->num_frames; i++)
		{
			//set temp to zero
			Memset(tangents_, 0, pheader->num_xyz*sizeof(vec3_t));
			Memset(binormals_, 0, pheader->num_xyz*sizeof(vec3_t));

			frame = (daliasframe_t *)((byte *)pheader + pheader->ofs_frames + i * pheader->framesize);
			verts = frame->verts;

			//for all tris
			for (j=0; j<pheader->num_tris; j++)
			{
				vec3_t	vv0,vv1,vv2;
				vec3_t tangent, binormal;

				vv0[0] = (float)verts[tris[j].index_xyz[0]].v[0];
				vv0[1] = (float)verts[tris[j].index_xyz[0]].v[1];
				vv0[2] = (float)verts[tris[j].index_xyz[0]].v[2];
				vv1[0] = (float)verts[tris[j].index_xyz[1]].v[0];
				vv1[1] = (float)verts[tris[j].index_xyz[1]].v[1];
				vv1[2] = (float)verts[tris[j].index_xyz[1]].v[2];
				vv2[0] = (float)verts[tris[j].index_xyz[2]].v[0];
				vv2[1] = (float)verts[tris[j].index_xyz[2]].v[1];
				vv2[2] = (float)verts[tris[j].index_xyz[2]].v[2];

				VecsForTris(vv0, vv1, vv2,
							&poutst[tris[j].index_st[0]].s,
							&poutst[tris[j].index_st[1]].s,
							&poutst[tris[j].index_st[2]].s,
							tangent, binormal);			//for all vertices in the tri

				for (k=0; k<3; k++)
				{
					l = tris[j].index_xyz[k];
					VectorAdd(tangents_[l], tangent, tangents_[l]);
					VectorAdd(binormals_[l], binormal, binormals_[l]);
				}
			}

			/// Berserker:
			// В некоторых моделях изредка попадаются несколько точек, имеющие одинаковые координаты.
			// Для таких случаев объединяем их вектора (если угол между нормалями невелик (до 15 градусов)).
			for (j=0; j<pheader->num_xyz; j++)
				for (k=j+1; k<pheader->num_xyz; k++)
					if(verts[j].v[0] == verts[k].v[0] && verts[j].v[1] == verts[k].v[1] && verts[j].v[2] == verts[k].v[2])
					{
						float *jnormal = r_avertexnormals[verts[j].lightnormalindex];
						float *knormal = r_avertexnormals[verts[k].lightnormalindex];
						if(DotProduct(jnormal, knormal)>=smooth_cosine)		/// smooth_cosine = cos(N), при величине угла между нормалями менее чем N градусов, считаем, что это одна точка
						{
							VectorAdd(tangents_[j], tangents_[k], tangents_[j]);
							VectorCopy(tangents_[j], tangents_[k]);
							VectorAdd(binormals_[j], binormals_[k], binormals_[j]);
							VectorCopy(binormals_[j], binormals_[k]);
						}
					}

			//normalize averages
			for (j=0; j<pheader->num_xyz; j++)
			{
				VectorNormalize(tangents_[j]);
				VectorNormalize(binormals_[j]);

				tangents[i * pheader->num_xyz + j] = Normal2Index(tangents_[j]);
				binormals[i * pheader->num_xyz + j] = Normal2Index(binormals_[j]);
			}
		}
	}
	else
	{
		smooth = 0;
		cx = pheader->num_tris * pheader->num_frames * sizeof(byte);
		if(!binormals || !tangents)
		{
			mod->binormals = binormals = (byte*)Hunk_Alloc (cx, false);
			if (!binormals)
				return false;
			mod->tangents = tangents = (byte*)Hunk_Alloc (cx, false);
			if (!tangents)
				return false;
		}
		if (!norms)
		{
			mod->normals = norms = (byte *)Hunk_Alloc (cx, false);
			if (!norms)
				return false;
		}

		//for all frames
		for (i=0; i<pheader->num_frames; i++)
		{
			frame = (daliasframe_t *)((byte *)pheader + pheader->ofs_frames + i * pheader->framesize);
			verts = frame->verts;

			//for all tris
			for (j=0; j<pheader->num_tris; j++)
			{
				//make 3 vec3_t's of this triangle's vertices
				for (k=0; k<3; k++)
				{
					l = tris[j].index_xyz[k];
					v = &verts[l];
					for (l=0; l<3; l++)
						triangle[k][l] = v->v[l];
				}

				//calculate normal
				VectorSubtract(triangle[0], triangle[1], v1);
				VectorSubtract(triangle[2], triangle[1], v2);
				CrossProduct(v2,v1, normal);
				VectorScale(normal, -1.0/VectorLength(normal), normal);
				norms[i*pheader->num_tris+j] = Normal2Index(normal);

				// calculate tangent and binormal
				vec3_t	bin,tan;
				VecsForTris(triangle[0], triangle[1], triangle[2],
							&poutst[tris[j].index_st[0]].s,
							&poutst[tris[j].index_st[1]].s,
							&poutst[tris[j].index_st[2]].s,
							tan,
							bin);			//for all vertices in the tri
				tangents[i * pheader->num_tris + j] = Normal2Index(tan);
				binormals[i * pheader->num_tris + j] = Normal2Index(bin);
			}
		}
	}

	if (invert)
		Com_sprintf (cachename, sizeof(cachename), "%s/cache/Inv_%s", FS_Gamedir(), mod->name);
	else
		Com_sprintf (cachename, sizeof(cachename), "%s/cache/%s", FS_Gamedir(), mod->name);
	FS_CreatePath(cachename);
	f = FS_Fopen(cachename, "wb");
	if(f)
	{
		fwrite(&smooth, 1, sizeof(smooth), f);
		unsigned	sc = (unsigned)(smooth_cosine*0x7fffffff);
		fwrite(&sc, 1, sizeof(unsigned), f);

		ax = pheader->num_tris * sizeof(neighbours_t);
		checksum = LittleLong (Com_BlockChecksum (triangles, ax));
		fwrite(&checksum, 1, sizeof(int), f);
		fwrite(triangles, 1, ax, f);

		checksum = LittleLong (Com_BlockChecksum (binormals, cx));
		fwrite(&checksum, 1, sizeof(int), f);
		fwrite(binormals, 1, cx, f);

		checksum = LittleLong (Com_BlockChecksum (tangents, cx));
		fwrite(&checksum, 1, sizeof(int), f);
		fwrite(tangents, 1, cx, f);

		if(!smooth)
		{
			checksum = LittleLong (Com_BlockChecksum (norms, cx));
			fwrite(&checksum, 1, sizeof(int), f);
			fwrite(norms, 1, cx, f);
		}

		fclose(f);
	}

exit:
	ClearBounds(mod->mins, mod->maxs);
	VectorClear(mod->center);
	frame = (daliasframe_t *)((byte *)pheader + pheader->ofs_frames);/// + i * pheader->framesize);		Берем только нулевой кадр!
	verts = frame->verts;
	for (k=0; k<pheader->num_xyz; k++)
		for (l=0; l<3; l++)
		{
			if (mod->mins[l] > verts[k].v[l])	mod->mins[l] = verts[k].v[l];
			if (mod->maxs[l] < verts[k].v[l])	mod->maxs[l] = verts[k].v[l];
		}

	for (l=0; l<3; l++)
	{
		mod->mins[l] = mod->mins[l] * frame->scale[l] + frame->translate[l];
		mod->maxs[l] = mod->maxs[l] * frame->scale[l] + frame->translate[l];
		mod->center[l] = (mod->mins[l] + mod->maxs[l]) * 0.5;
	}

	vec3_t	tempr, tempv;
	tempr[0] = mod->maxs[0] - mod->mins[0];
	tempr[1] = mod->maxs[1] - mod->mins[1];
	tempr[2] = 0;
	tempv[0] = 0;
	tempv[1] = 0;
	tempv[2] = mod->maxs[2] - mod->mins[2];
	mod->radius = max(VectorLength(tempr), VectorLength(tempv));

	tris = (dtriangle_t *) ((byte *)pheader + pheader->ofs_tris);
	mod->vbo_st = NULL;
	if (gl_config.vbo)
	{
		float	*map = (float*) vbo_shadow;	// hack: этот буфер очень большой, поэтому он должен вместить в себя любую модель
		for (l=0, i=0; i<pheader->num_tris; i++)
		{
			for (j=0; j<3; j++)
			{
				index_st = tris[i].index_st[j];
				map[l++] = poutst[index_st].s;
				map[l++] = poutst[index_st].t;
			}
		}

		if (l>3*MAX_VBO_XYZs)
			Com_Error(ERR_FATAL, "Temporary buffer overflow");

		mod->vbo_st = R_VCLoadData(VBO_STATIC, l*sizeof(float), &vbo_shadow, VBO_STORE_ANY, NULL, NULL);
		GL_BindVBO(NULL);
	}
	else
	{
		for (l=0, i=0; i<pheader->num_tris; i++)
		{
			for (j=0; j<3; j++)
			{
				index_st = tris[i].index_st[j];
				mod->st[l++] = poutst[index_st].s;
				mod->st[l++] = poutst[index_st].t;
			}
		}
	}

	return true;
}


/*
=============
EmitWaterPolys

Does a water warp on the pre-fragmented glpoly_t chain
=============
*/
void EmitWaterPolys (msurface_t *fa, unsigned texnum, bool tris)
{
	glpoly_t	*p, *bp;
	float		*v;
	int			i;
	float		s, t, os, ot;
	float		scroll, dstscroll;
	float		rdt = r_newrefdef.time;

	if(gl_config.texshaders)
	{
		GL_Bind2D(dst_texture_water->texnum);
		gl_TexEnv( GL_REPLACE );
		glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_TEXTURE_2D);
		GL_MBind2D(GL_TEXTURE1, texnum);
		glEnable(GL_TEXTURE_2D);
		gl_TexEnv( GL_REPLACE );
		glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_TEXTURE_2D);
		glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_OFFSET_TEXTURE_2D_NV);
		glTexEnvi(GL_TEXTURE_SHADER_NV, GL_PREVIOUS_TEXTURE_INPUT_NV, GL_TEXTURE0);
		glTexEnvfv(GL_TEXTURE_SHADER_NV, GL_OFFSET_TEXTURE_MATRIX_NV, &args_distort[0]);
		glEnable(GL_TEXTURE_SHADER_NV);
		dstscroll = 64 * ( (r_newrefdef.time*0.15f) - (int)(r_newrefdef.time*0.15f) );
	}
	else
	{
		GL_Bind2D(texnum);
		gl_TexEnv( GL_MODULATE );
	}

	if (fa->texinfo->flags & SURF_FLOWING)
		scroll = -64 * ( (r_newrefdef.time*0.5) - (int)(r_newrefdef.time*0.5) );
	else
		scroll = 0;
	for (bp=fa->polys ; bp ; bp=bp->next)
	{
		p = bp;

		glBegin (GL_TRIANGLE_FAN);
		for (i=0,v=p->verts[0] ; i<p->numverts ; i++, v+=VERTEXSIZE)
		{
			os = v[3];
			ot = v[4];
			s = (os + r_turbsin[Q_ftol( ((ot*0.125+rdt) * TURBSCALE) ) & 255] + scroll) * 0.015625f;
			t = (ot + r_turbsin[Q_ftol( ((os*0.125+rdt) * TURBSCALE) ) & 255]) * 0.015625f;

			if (gl_config.texshaders)
			{
				glTexCoord2f ((v[3]+dstscroll)*0.015625f, (v[4]+dstscroll)*0.015625f);
				glMultiTexCoord2fARB(GL_TEXTURE1, s, t);
			}
			else
				glTexCoord2f (s, t);

			glVertex3fv (v);
		}
		glEnd ();
	}

	if(gl_config.texshaders)
	{
		glDisable(GL_TEXTURE_2D);
		GL_SelectTexture(GL_TEXTURE0);
		glDisable(GL_TEXTURE_SHADER_NV);
	}
	else
		gl_TexEnv( GL_REPLACE );

/// Berserker //////////////////////////
	if (tris || fa->texinfo->image->hash == hash_showtexture || fa->texinfo == curtexinfo)
		goto force;

	if (!r_showtris->value)
		return;

	if (r_showtris->value != 1)
force:	glDisable (GL_DEPTH_TEST);

	if(fa->texinfo->flags & (SURF_TRANS33 | SURF_TRANS66))
		glDisable(GL_BLEND);
//	glEnable(GL_LINE_SMOOTH);
	glDisable (GL_TEXTURE_2D);
	GL_Color3f (0,0,1);
	for (bp=fa->polys ; bp ; bp=bp->next)
	{
		p = bp;
		for (i=0,v=p->verts[1] ; i<p->numverts-1 ; i++, v+=VERTEXSIZE)
		{
			glBegin (GL_LINE_STRIP);
			glVertex3fv (p->verts[0]);
			glVertex3fv (v);
			glVertex3fv (i == p->numverts-2 ? p->verts[1] : v+VERTEXSIZE);
			glEnd ();
		}
	}
//	glDisable(GL_LINE_SMOOTH);
	glEnable (GL_TEXTURE_2D);
	if (fa->texinfo->flags & SURF_TRANS33)
		GL_Color4f (1,1,1,0.33);
	else if (fa->texinfo->flags & SURF_TRANS66)
		GL_Color4f (1,1,1,0.66);
	else
		GL_Color3f (1,1,1);
	if(fa->texinfo->flags & (SURF_TRANS33 | SURF_TRANS66))
		glEnable(GL_BLEND);

	if (r_showtris->value != 1 || tris || fa->texinfo->image->hash == hash_showtexture || fa->texinfo == curtexinfo)
		glEnable (GL_DEPTH_TEST);
/////////////////////////////////////////
}


void DrawGLPoly (msurface_t *s, vec3_t org, bool tris)
{
	int		i;
	float	*v, adds, addt;
	float	scroll;
	vec3_t	t, n, vieworg;
	glpoly_t	*p = s->polys;
	image_t		*image = s->texinfo->image;
	clightstyle_t	*ls, *cl_ls;
	vec4_t			ls_rgb;

	gl_TexEnv( GL_MODULATE );

	if(s->texinfo->flags & SURF_FLOWING)
	{
		scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
		if(scroll == 0.0)
			scroll = -64.0;
	}
	else
		scroll = 0.0;

	bool env = image->fx == fx_chrome;
	bool power = image->fx == fx_power;
	bool map = image->fx == fx_map;
	bool style = image->fx == fx_style;
	if(env || power || map || style)
	{
		GL_SelectTexture(GL_TEXTURE1);
		glEnable( GL_TEXTURE_2D );
		gl_TexEnv( GL_COMBINE_ARB );
		GL_TexEnv( GL_COMBINE_RGB_ARB, GL_ADD );
		if (style)
		{
			GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_CONSTANT_ARB );
			unsigned tmp = image->fx_s;
			ls = &sv_lightstyle[tmp & 0xff];
			cl_ls = &cl_lightstyle[(tmp>>8) & 0xff];
			float	temp = ls->val * cl_ls->val;
			ls_rgb[0] = temp * image->fx_t;
			ls_rgb[1] = temp * image->fx_scale_s;
			ls_rgb[2] = temp * image->fx_scale_t;
			glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, ls_rgb);
		}
		else
			GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
		GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
		GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
		GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
		if(env)
		{
			GL_Bind2D( R_GetIMGFromName(image, chrome_texture_2d_world) );
			VectorCopy(org, vieworg);
			adds = addt = 0;
		}
		else if(power)
		{
			GL_Bind2D( R_GetIMGFromName(image, env_texture_2d_world) );
			vieworg[0] = 64;
			vieworg[1] = vieworg[2] = 0;
			adds = image->fx_s * cl.leveltime;
			addt = image->fx_t * cl.leveltime;
		}
		else if(map)
		{
			GL_Bind2D( R_GetIMGFromName(image, chrome_texture_2d_world) );
			adds = image->fx_s * cl.leveltime;
			addt = image->fx_t * cl.leveltime;
		}
///		else	// style
///		{
///		}
	}

	glBegin (GL_TRIANGLE_FAN);
	for (i=0, v = p->verts[0] ; i<p->numverts ; i++, v+= VERTEXSIZE)
	{
		glTexCoord2f ((v[3] + scroll), v[4]);
		if(env || power)
		{
			VectorSubtract (v, vieworg, t);
			// project vector
			n[0] = v[7];
			n[1] = v[8];
			n[2] = RSqrt(DotProduct(t,t));

			glMultiTexCoord2fARB( GL_TEXTURE1, (t[0]*n[2] - n[0] + adds)*image->fx_scale_s, (t[1]*n[2] - n[1] + addt)*image->fx_scale_t);
		} else if(map)
			glMultiTexCoord2fARB( GL_TEXTURE1, (v[3]+adds)*image->fx_scale_s, (v[4]+addt)*image->fx_scale_t);
///		else if(style)
///			glMultiTexCoord3fvARB( GL_TEXTURE1, ls_rgb);
		glVertex3fv (v);
	}
	glEnd ();

	if(env || power || map || style)
	{
///		GL_SelectTexture(GL_TEXTURE1);
		glDisable( GL_TEXTURE_2D );
		gl_TexEnv( GL_REPLACE );
		GL_SelectTexture(GL_TEXTURE0);
	}

/// Berserker //////////////////////////
	if (tris || image->hash == hash_showtexture || s->texinfo == curtexinfo)
		goto force;

	if (!r_showtris->value)
		return;

	if (r_showtris->value != 1)
force:	glDisable (GL_DEPTH_TEST);

//	glEnable(GL_LINE_SMOOTH);
	glDisable (GL_TEXTURE_2D);
	GL_Color3f (0,1,0);
	v = p->verts[0];
	glBegin (GL_LINE_STRIP);
	for (i=0 ; i<p->numverts ; i++, v+= VERTEXSIZE)
		glVertex3fv (v);
	glVertex3fv (p->verts[0]);
	glEnd();
//	glDisable(GL_LINE_SMOOTH);
	glEnable (GL_TEXTURE_2D);
	GL_Color3f (1,1,1);

	if (r_showtris->value != 1 || tris || image->hash == hash_showtexture || s->texinfo == curtexinfo)
		glEnable (GL_DEPTH_TEST);
/////////////////////////////////////////
}



void GL_RenderBrushPoly (msurface_t *fa, vec3_t org, bool tris)
{
	image_t		*image;

	c_brush_polys++;

	image = R_TextureAnimation (fa->texinfo)->image;

	if (fa->flags & SURF_DRAWTURB)
	{
		// warp texture, no lightmaps
///		gl_TexEnv( GL_MODULATE );
		if(vid_ignorehwgamma->value)
			GL_Color3f( gl_state.inverse_intensity, gl_state.inverse_intensity, gl_state.inverse_intensity );
		else
			GL_Color3f(0.5, 0.5, 0.5);
		EmitWaterPolys (fa, image->texnum, tris);
///		gl_TexEnv( GL_REPLACE );
	}
	else
	{
		GL_Bind2D( image->texnum );
///		gl_TexEnv( GL_REPLACE );
		DrawGLPoly (fa, org, tris);
	}
}


void DrawFogPolys (msurface_t *fa, fog_t *fog)
{
	glpoly_t	*bp;
	float		*v, mmm;
	int			i;
	vec3_t		color;

	color[0] = fog->color[0];
	color[1] = fog->color[1];
	color[2] = fog->color[2];

	for (bp=fa->polys ; bp ; bp=bp->next)
	{
		glBegin (GL_TRIANGLE_FAN);
		for (i=0,v=bp->verts[0] ; i<bp->numverts ; i++, v+=VERTEXSIZE)
		{
			mmm = FogDist(fog, v);
			GL_Color3f(color[0]*mmm, color[1]*mmm, color[2]*mmm);
			glVertex3fv (v);
		}
		glEnd ();
	}
}


void R_DrawInlineBModelFog (fog_t *fog)
{
	int			i;
	cplane_t	*pplane;
	float		dot;
	msurface_t	*psurf;

	psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];

	// draw texture
	for (i=0 ; i<currentmodel->nummodelsurfaces ; i++, psurf++)
	{
		if (psurf->visframe != r_framecount)
			continue;

		// find which side of the node we are on
		pplane = psurf->plane;

		dot = DotProduct (r_origin, pplane->normal) - pplane->dist;

		// draw the polygon
		if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
			(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
		{
			if (psurf->texinfo->flags & (SURF_TRANS33|SURF_TRANS66|SURF_NODRAW) )
				continue;
			else
			{
				if ( psurf->flags & SURF_DRAWTURB )
					DrawFogPolys (psurf, fog);
				else
					GL_RenderLightmappedPolyFog( psurf, fog );
			}
		}
	}
}


bool R_DrawInlineBModel (bool in_water)
{
	int			i;
	cplane_t	*pplane;
	float		dot;
	msurface_t	*psurf;
	bool		force = false;
	bool		parallax = (gl_config.vp && !r_simple->value && r_shader->value==SHADER_ARB6 && r_parallax->value);
	bool		distort = false;

	psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];

	if (curbrush && r_editor->value)
		force = (currentmodel->firstmodelsurface == curbrush->firstface);

	if ( currententity->flags & RF_TRANSLUCENT )
	{
		glEnable (GL_BLEND);
///	GL_Color4f (1,1,1,0.25);
	GL_Color4f (gl_state.color[0],gl_state.color[1],gl_state.color[2],0.25);
		gl_TexEnv( GL_MODULATE );
	}

	// draw texture
	for (i=0 ; i<currentmodel->nummodelsurfaces ; i++, psurf++)
	{
		if (!r_mirror)
			if (gl_config.screentexture && gl_config.arb_distort && r_distort->value && psurf->texinfo->image->fx == fx_distort /*&& (psurf->texinfo->flags & (SURF_TRANS33|SURF_TRANS66))*/ /*&& !(psurf->flags & SURF_DRAWFOG)*/)
			{
				if(distortbrush == MAX_MODELVIEW_CACHE)
					Com_DPrintf("MAX_MODELVIEW_CACHE overflow\n");
				else
				{
					R_GetIMGFromName(psurf->texinfo->image, dst_texture);
					psurf->distortchain = r_distort_surfaces;
					r_distort_surfaces = psurf;
					psurf->flags |= SURF_DISTORTBRUSH;
					psurf->mvm_cache_idx2 = (short)distortbrush;
					glGetFloatv (GL_MODELVIEW_MATRIX, &r_mvm_cache2[distortbrush][0]);
					distortbrush++;
					distort = true;
				}
			}

		if (force)
			goto draw;

		if (!(psurf->texinfo->flags & SURF_NODRAW))		/// Berserker: вообще не рисуем полигоны NODRAW
		{
			// find which side of the node we are on
draw:		pplane = psurf->plane;

			dot = DotProduct (modelorg, pplane->normal) - pplane->dist;

			// draw the polygon
			if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
				(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
			{
				psurf->visframe = r_framecount;
				psurf->ent = currententity;
				if (psurf->texinfo->flags & (SURF_TRANS33|SURF_TRANS66) )
				{	// add to the translucent chain
					if(transbrush == MAX_MODELVIEW_CACHE)
						Com_DPrintf("MAX_MODELVIEW_CACHE overflow\n");
					else
					{
						psurf->texturechain = r_alpha_surfaces;
						r_alpha_surfaces = psurf;
						psurf->flags |= SURF_TRANSBRUSH;
						psurf->mvm_cache_idx = (short)transbrush;
						glGetFloatv (GL_MODELVIEW_MATRIX, &r_mvm_cache[transbrush][0]);
						r_mvm_cache[transbrush][16] = modelorg[0];
						r_mvm_cache[transbrush][17] = modelorg[1];
						r_mvm_cache[transbrush][18] = modelorg[2];
						transbrush++;
					}
				}
				else if ( !( psurf->flags & SURF_DRAWTURB ) )
				{
					GL_RenderLightmappedPoly( psurf, force, parallax );
					if(in_water)
					{
						if(causticbrush == MAX_MODELVIEW_CACHE)
							Com_DPrintf("MAX_MODELVIEW_CACHE overflow\n");
						else
						{
							psurf->uwchain = r_uw_surfaces;
							r_uw_surfaces = psurf;
							psurf->flags |= SURF_CAUSTICBRUSH;
							psurf->mvm_cache_idx3 = (short)causticbrush;
							glGetFloatv (GL_MODELVIEW_MATRIX, &r_mvm_cache3[causticbrush][0]);
							r_mvm_cache3[causticbrush][16] = modelorg[0];
							r_mvm_cache3[causticbrush][17] = modelorg[1];
							r_mvm_cache3[causticbrush][18] = modelorg[2];
							causticbrush++;
						}
					}
				}
				else
				{
					GL_SelectTexture( GL_TEXTURE1 );
					glDisable( GL_TEXTURE_2D );
					GL_SelectTexture( GL_TEXTURE0 );

					GL_RenderBrushPoly( psurf, modelorg, force );

					GL_SelectTexture( GL_TEXTURE1 );
					glEnable( GL_TEXTURE_2D );
					GL_SelectTexture( GL_TEXTURE0 );
				}
			}
		}
	}

	if ( currententity->flags & RF_TRANSLUCENT)
	{
		glDisable (GL_BLEND);
///		if (!r_worldmodel->lightdata)
		if (!VectorCompare(world_ambient_light, vec3_origin))	// Новое правило: если есть worldspawn.ambient - то он действует, и главнее чем r_worldmodel->lightdata.
			GL_Color3fv(world_ambient_light);
		else
			GL_Color3f(1,1,1);
		gl_TexEnv( GL_REPLACE );
	}

	return distort;
}


bool R_DrawBrushModel ()
{
	int			i;
	bool		rotated;
	bool		occ = false;
	vec3_t		e_mins, e_maxs;		// inline bmodel bbox...

	// проверим, не является ли клиентская модель невидимой?
	for(i=0; i<brushmodel_counter; i++)
		if (bmdl_list[i].mdl->firstface == currentmodel->firstmodelsurface)
			if (bmdl_list[i].nodraw)
			{
				currententity->flags |= RF_NOUSE;
				return false;
			}

	if (currentmodel->nummodelsurfaces == 0)
	{
		currententity->flags |= RF_NOUSE;		// Не освещать модель (не обрабатывать bump-кодом, ибо модель не видна)
		return false;
	}

	if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
	{
		rotated = true;
		for (i=0 ; i<3 ; i++)
		{
			e_mins[i] = currententity->origin[i] - currentmodel->radius;
			e_maxs[i] = currententity->origin[i] + currentmodel->radius;
		}
	}
	else
	{
		rotated = false;
		VectorAdd (currententity->origin, currentmodel->mins, e_mins);
		VectorAdd (currententity->origin, currentmodel->maxs, e_maxs);
	}

	// save the current bbox...
	currententity->minmax[0] = e_mins[0];
	currententity->minmax[1] = e_mins[1];
	currententity->minmax[2] = e_mins[2];
	currententity->minmax[3] = e_maxs[0];
	currententity->minmax[4] = e_maxs[1];
	currententity->minmax[5] = e_maxs[2];

	if(Ent_MirrorClip(e_mins, e_maxs))
	{
		currententity->flags |= RF_NOUSE;		// Не освещать модель (не обрабатывать bump-кодом, ибо модель не видна в зеркале)
		return false;
	}

///	if (R_CullBox (e_mins, e_maxs))
	if (BoxOutsideFrustum (e_mins, e_maxs))
	{
		currententity->flags |= RF_NOUSE;		// Не освещать модель (не обрабатывать bump-кодом, ибо модель не видна)
		return false;
	}

	if(gl_config.occlusion)
	{
		int currententity_index = Write_occHash();
		if (currententity_index>=0)
		{
			// begin occlusion test
			glBeginQueryARB(gl_config.occlusion, r_entities_occID[currententity_index]);
			r_entities_occUSE[currententity_index] = occ_framecount;
			occ = true;
		}
	}

	VectorSubtract (r_origin, currententity->origin, modelorg);
	if (rotated)
	{
		vec3_t	temp;
		vec3_t	forward, right, up;

		VectorCopy (modelorg, temp);
		AngleVectors (currententity->angles, forward, right, up);
		modelorg[0] = DotProduct (temp, forward);
		modelorg[1] = -DotProduct (temp, right);
		modelorg[2] = DotProduct (temp, up);
	}

	glPushMatrix ();
	R_RotateForEntity (currententity);

///	if (!r_worldmodel->lightdata)
	if (!VectorCompare(world_ambient_light, vec3_origin))	// Новое правило: если есть worldspawn.ambient - то он действует, и главнее чем r_worldmodel->lightdata.
		GL_Color3fv(world_ambient_light);
	else
		GL_Color3f(1,1,1);

	GL_SelectTexture( GL_TEXTURE0);
///	gl_TexEnv( GL_MODULATE );
gl_TexEnv( GL_COMBINE_ARB );
GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );

GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_TEXTURE );
GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );

	GL_SelectTexture( GL_TEXTURE1);
	glEnable( GL_TEXTURE_2D );
///	gl_TexEnv( GL_MODULATE );
gl_TexEnv( GL_COMBINE_ARB );
GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );

GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB );
GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );

	if (!r_simple->value)
	{
		GL_SelectTexture(GL_TEXTURE2);
		gl_TexEnv( GL_COMBINE_ARB );
		GL_TexEnv( GL_COMBINE_RGB_ARB, GL_ADD );
		GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
		GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
		GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
		GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
	}

	bool in_water = false;
	if (r_caustics->value)
	{
#if 0
		int		cont[4];	// was 5
		vec3_t	org;
		VectorSet(org, currententity->minmax[0], currententity->minmax[1], currententity->minmax[5]);
		cont[0] = CL_PMpointcontents2(org, currentmodel);
//		if (!cont[0])
//			goto nono;
		VectorSet(org, currententity->minmax[3], currententity->minmax[1], currententity->minmax[5]);
		cont[1] = CL_PMpointcontents2(org, currentmodel);
//		if (!cont[1])
//			goto nono;
		VectorSet(org, currententity->minmax[0], currententity->minmax[4], currententity->minmax[5]);
		cont[2] = CL_PMpointcontents2(org, currentmodel);
//		if (!cont[2])
//			goto nono;
		VectorSet(org, currententity->minmax[3], currententity->minmax[4], currententity->minmax[5]);
		cont[3] = CL_PMpointcontents2(org, currentmodel);
//		if (!cont[3])
//			goto nono;
//		org[0] = (currententity->minmax[0] + currententity->minmax[3]) * 0.5;
//		org[1] = (currententity->minmax[1] + currententity->minmax[4]) * 0.5;
//		org[2] = (currententity->minmax[2] + currententity->minmax[5]) * 0.5;
//		cont[4] = CL_PMpointcontents2(org, currentmodel);
//		if (!cont[4])
//			goto nono;
		if ((cont[0] & MASK_WATER) || (cont[1] & MASK_WATER) || (cont[2] & MASK_WATER) || (cont[3] & MASK_WATER)/* || (cont[4] & MASK_WATER)*/)
			in_water = true;
#else
		vec3_t	org;
		VectorSet(org, currententity->minmax[0], currententity->minmax[1], currententity->minmax[5]);
		if (CL_PMpointcontents2(org, currentmodel) & MASK_WATER)
			in_water = true;
		else
		{
			VectorSet(org, currententity->minmax[3], currententity->minmax[1], currententity->minmax[5]);
			if (CL_PMpointcontents2(org, currentmodel) & MASK_WATER)
				in_water = true;
			else
			{
				VectorSet(org, currententity->minmax[0], currententity->minmax[4], currententity->minmax[5]);
				if (CL_PMpointcontents2(org, currentmodel) & MASK_WATER)
					in_water = true;
				else
				{
					VectorSet(org, currententity->minmax[3], currententity->minmax[4], currententity->minmax[5]);
					if (CL_PMpointcontents2(org, currentmodel) & MASK_WATER)
						in_water = true;
				}
			}
		}

#endif
	}
//nono:
	bool distort = R_DrawInlineBModel (in_water);

	if (!r_simple->value)
	{
		GL_SelectTexture(GL_TEXTURE2);
		gl_TexEnv( GL_REPLACE );
	}

	GL_SelectTexture( GL_TEXTURE1 );
	glDisable( GL_TEXTURE_2D );
	gl_TexEnv( GL_REPLACE );

	GL_SelectTexture( GL_TEXTURE0 );

	glPopMatrix ();

	GL_Color3f(1,1,1);

	// end occlusion test
	if (occ)
		glEndQueryARB(gl_config.occlusion);

	return distort;
}


void R_DrawBrushModelFog ()
{
	int			i;
	vec3_t		oldorg;
	float		oldfog;
	fog_t		*fog = NULL;

	if (currentmodel->nummodelsurfaces == 0)
		return;

	for (i=0; i<numFogs; i++)
		if (BBoxIntersectBBox(fog_infos[i].minmax, currententity->minmax))
			fog = &fog_infos[i];

	if (fog)
	{
		oldfog = fog->origin[2];
		VectorCopy(r_origin, oldorg);
		fog->origin[2] -= currententity->origin[2];
		VectorSubtract (r_origin, currententity->origin, r_origin);

		if (currententity->angles[1])
		{
			vec3_t	temp, ang;
			vec3_t	forward, right, up;

			VectorSet(ang, 0, currententity->angles[1], 0);
			AngleVectors (ang, forward, right, up);

			VectorCopy (r_origin, temp);
			r_origin[0] = DotProduct (temp, forward);
			r_origin[1] = -DotProduct (temp, right);
			r_origin[2] = DotProduct (temp, up);
		}

		glPushMatrix ();
		R_RotateForEntity (currententity);

		R_DrawInlineBModelFog (fog);

		glPopMatrix ();

		fog->origin[2] = oldfog;
		VectorCopy(oldorg, r_origin);
	}
}


/*
=============
GL_DrawAliasFrameLerpFX

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void GL_DrawAliasFrameLerpFX (dmdl_t *paliashdr, bool chrome, bool map, bool style, image_t *skin, image_t *light, bool calced)
{
	int		i;
	int		*order;
	int		count;
	daliasframe_t	*frame, *oldframe;
	dtrivertx_t	*verts, *oldverts;
	vec3_t	vieworg;
	vec3_t	temp;
	float	tempArray[MAX_VERTS][2];
	vec3_t	t, n;
	float	*normal, *oldnormal, adds, addt;
	clightstyle_t	*ls, *cl_ls;
	vec4_t			ls_rgb;
	float	backlerp, frontlerp;

	if (r_offset->value)
		glEnable(GL_POLYGON_OFFSET_FILL);

	if (!calced)
		R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}

	glEnable (GL_BLEND);
	GL_BlendFunc(GL_SRC_COLOR, GL_ONE);
	GL_BindVBO(NULL);

///	if (!(currententity->flags & RF_TRANSLUCENT))	// При рисовании непрозрачных моделей идет запись в Z...
///		glDepthMask(0);								// отключим это!

	if (light)
		GL_Bind2D(light->texnum);

	GL_SelectTexture(GL_TEXTURE1);
	glEnable( GL_TEXTURE_2D );
	gl_TexEnv( GL_COMBINE_ARB );
	GL_TexEnv( GL_COMBINE_RGB_ARB, GL_INTERPOLATE_ARB );
	if (style)
	{
		GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_CONSTANT_ARB );
		unsigned tmp = skin->fx_s;
		ls = &sv_lightstyle[tmp & 0xff];
		cl_ls = &cl_lightstyle[(tmp>>8) & 0xff];
		float	temp = ls->val * cl_ls->val;
		ls_rgb[0] = temp * skin->fx_t;
		ls_rgb[1] = temp * skin->fx_scale_s;
		ls_rgb[2] = temp * skin->fx_scale_t;
		glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, ls_rgb);
	}
	else
		GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
	GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
	if (light)
		GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
	else
		GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB );
	GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
	if(light)
		GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_TEXTURE0 );
	else
		GL_TexEnv( GL_SOURCE2_RGB_ARB, GL_PRIMARY_COLOR_ARB);
	GL_TexEnv( GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA );
	GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
	GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB );
	GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
	if (chrome)
	{
		GL_Bind2D( R_GetIMGFromName(skin, chrome_texture_2d) );
		GL_Color4f(shadelight[0]+0.5, shadelight[1]+0.5, shadelight[2]+0.5, 0.75);

		//Put camera into model space
		if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
		{
			vec3_t	forward, right, up;
			if ( currententity->flags & RF_WEAPONMODEL )
				VectorSet(temp, 16,16,8);
			else
				VectorSubtract(r_origin, currententity->origin, temp);
			AngleVectors (currententity->angles, forward, right, up);
			vieworg[0] = DotProduct (temp, forward);
			vieworg[1] = -DotProduct (temp, right);
			vieworg[2] = DotProduct (temp, up);
		}
		else
			VectorSubtract(r_origin, currententity->origin, vieworg);
		adds = addt = 0;
	}
	else if(map)
	{
		GL_Bind2D( R_GetIMGFromName(skin, chrome_texture_2d) );
		GL_Color4f(shadelight[0]+0.5, shadelight[1]+0.5, shadelight[2]+0.5, 0.75);

		adds = skin->fx_s * r_newrefdef.time;///cl.leveltime;
		addt = skin->fx_t * r_newrefdef.time;///cl.leveltime;
	}
	else if(style)
	{
	}
	else
	{	// power
		int envcolor = (currententity->flags & (RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE)) >> 21;
		switch (envcolor)
		{
		case 1:
			GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_r) );
		case 2:
			GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_g) );
		case 3:
			GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_rg) );
		case 4:
			GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_b) );
		case 5:
			GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_rb) );
		case 6:
			GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_gb) );
		default:
			GL_Bind2D( R_GetIMGFromName(skin, env_texture_2d_rgb) );
		}

		GL_Color4f(shadelight[0]+0.5, shadelight[1]+0.5, shadelight[2]+0.5, 0.75);
		vieworg[0] = 64;
		vieworg[1] = vieworg[2] = 0;
		adds = skin->fx_s * r_newrefdef.time;///cl.leveltime;
		addt = skin->fx_t * r_newrefdef.time;///cl.leveltime;
	}

	frame = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->frame * paliashdr->framesize);
	oldframe = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->oldframe * paliashdr->framesize);
	verts = frame->verts;
	oldverts = oldframe->verts;
	order = (int *)((byte *)paliashdr + paliashdr->ofs_glcmds);

	if(!map)
	{
		if(!style)
		{
			for (i=0; i<paliashdr->num_xyz; i++)
			{
				normal = r_avertexnormals[verts[i].lightnormalindex];
				oldnormal = r_avertexnormals[oldverts[i].lightnormalindex];
				VectorSubtract (tempVertexArray[i], vieworg, t);
				// project vector
				n[0] = oldnormal[0]*backlerp + normal[0]*frontlerp;
				n[1] = oldnormal[1]*backlerp + normal[1]*frontlerp;
				n[2] = RSqrt(DotProduct(t,t));
				tempArray[i][0] = (t[0]*n[2] - n[0] + adds)*skin->fx_scale_s;
				tempArray[i][1] = (t[1]*n[2] - n[1] + addt)*skin->fx_scale_t;
			}
			glEnableClientState( GL_TEXTURE_COORD_ARRAY );
			glTexCoordPointer( 2, GL_FLOAT, 0, tempArray );
		}
	}

///	if ( glLockArraysEXT != 0 )
///		glLockArraysEXT( 0, paliashdr->num_xyz );

	glEnableClientState( GL_VERTEX_ARRAY );
	glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );

	if (currentmodel->invert)
		glCullFace(GL_BACK);

	while (1)
	{
		// get the vertex count and primitive type
		count = *order++;
		if (!count)
			break;		// done
		if (count < 0)
		{
			count = -count;
			glBegin (GL_TRIANGLE_FAN);
		}
		else
			glBegin (GL_TRIANGLE_STRIP);

		do
		{
			// texture coordinates come from the draw list
			float s = ((float *)order)[0];
			float t = ((float *)order)[1];
			glTexCoord2f(s, t);		///	glMultiTexCoord2fARB( GL_TEXTURE0, s, t);
			if(map)
				glMultiTexCoord2fARB( GL_TEXTURE1, (s+adds)*skin->fx_scale_s, (t+addt)*skin->fx_scale_t);
			glArrayElement( order[2] );
			order += 3;
		} while (--count);
		glEnd ();
	}

	if (currentmodel->invert)
		glCullFace(GL_FRONT);

	glDisableClientState( GL_VERTEX_ARRAY );

///	if ( glUnlockArraysEXT != 0 )
///		glUnlockArraysEXT();

	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

///	if (!(currententity->flags & RF_TRANSLUCENT))	// При рисовании непрозрачных моделей должна быть запись в Z...
///		glDepthMask(1);								// включим это!

	if(!map)
		glDisableClientState( GL_TEXTURE_COORD_ARRAY );

	glDisable( GL_TEXTURE_2D );
	gl_TexEnv( GL_REPLACE );

	GL_SelectTexture(GL_TEXTURE0);

	if (r_offset->value)
		glDisable(GL_POLYGON_OFFSET_FILL);
}


void GL_DrawAliasFrameLerpShell (dmdl_t *paliashdr)
{
	int		i;
	int		*order;
	int		count;
	daliasframe_t	*frame, *oldframe;
	dtrivertx_t	*verts, *oldverts;
	vec3_t	vieworg;
	float	tempArray[MAX_VERTS][2];
	vec3_t	t, n;
	float	*normal, *oldnormal;
	float	backlerp, frontlerp;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}

	glEnable (GL_BLEND);
	GL_BlendFunc(GL_SRC_COLOR, GL_ONE);
	gl_TexEnv( GL_MODULATE );
	GL_BindVBO(NULL);

	int envcolor = (currententity->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE)) >> 10;
	switch (envcolor)
	{
	case 1:
		GL_Bind2D(env_texture_2d_r->texnum);
	case 2:
		GL_Bind2D(env_texture_2d_g->texnum);
	case 3:
		GL_Bind2D(env_texture_2d_rg->texnum);
	case 4:
		GL_Bind2D(env_texture_2d_b->texnum);
	case 5:
		GL_Bind2D(env_texture_2d_rb->texnum);
	case 6:
		GL_Bind2D(env_texture_2d_gb->texnum);
	default:
		GL_Bind2D(env_texture_2d_rgb->texnum);
	}

	GL_Color3f(shadelight[0]+0.5, shadelight[1]+0.5, shadelight[2]+0.5);
	vieworg[0] = 64;
	vieworg[1] = vieworg[2] = 0;

	frame = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->frame * paliashdr->framesize);
	verts = frame->verts;
	oldframe = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->oldframe * paliashdr->framesize);
	oldverts = oldframe->verts;
	order = (int *)((byte *)paliashdr + paliashdr->ofs_glcmds);

	R_CalcAliasFrameLerp(paliashdr);

	for (i=0; i<paliashdr->num_xyz; i++)
	{
		normal = r_avertexnormals[verts[i].lightnormalindex];
		oldnormal = r_avertexnormals[oldverts[i].lightnormalindex];

		VectorSubtract (tempVertexArray[i], vieworg, t);
		// project vector
		n[0] = oldnormal[0]*backlerp + normal[0]*frontlerp;
		n[1] = oldnormal[1]*backlerp + normal[1]*frontlerp;
		n[2] = RSqrt(DotProduct(t,t));

		tempArray[i][0] = t[0]*n[2] - n[0] + r_newrefdef.time;///cl.leveltime;
		tempArray[i][1] = t[1]*n[2] - n[1];
	}

	glEnableClientState( GL_TEXTURE_COORD_ARRAY );
	glTexCoordPointer( 2, GL_FLOAT, 0, tempArray );

///	if ( glLockArraysEXT != 0 )
///		glLockArraysEXT( 0, paliashdr->num_xyz );

	glEnableClientState( GL_VERTEX_ARRAY );
	glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );

	if (currentmodel->invert)
		glCullFace(GL_BACK);

	while (1)
	{
		// get the vertex count and primitive type
		count = *order++;
		if (!count)
			break;		// done
		if (count < 0)
		{
			count = -count;
			glBegin (GL_TRIANGLE_FAN);
		}
		else
			glBegin (GL_TRIANGLE_STRIP);

		do
		{
			// texture coordinates come from the draw list
			float s = ((float *)order)[0];
			float t = ((float *)order)[1];
			glTexCoord2f(s, t);		///	glMultiTexCoord2fARB( GL_TEXTURE0, s, t);
			glArrayElement( order[2] );
			order += 3;
		} while (--count);
		glEnd ();
	}

	if (currentmodel->invert)
		glCullFace(GL_FRONT);

///	if ( glUnlockArraysEXT != 0 )
///		glUnlockArraysEXT();

	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	gl_TexEnv( GL_REPLACE );

	glDisableClientState( GL_VERTEX_ARRAY );
	glDisableClientState( GL_TEXTURE_COORD_ARRAY );
}


void R_DrawAliasModel (bool hud)
{
	int			i;
	float		alpha;
	dmdl_t		*paliashdr;
	image_t		*skin;
	image_t		*light;
	bool		calced, occ = false;

	paliashdr = (dmdl_t *)currentmodel->extradata;
	CheckEntityFrame(paliashdr);
	currentmodel->max_tris = paliashdr->num_tris;

	if (!hud)
	{
		if ( currententity->flags & RF_DISTORT )
			return;

		if ( currententity->flags & RF_WEAPONMODEL )
		{
			if (r_mirror || drawing_sky_world || hand->value==2)	// не рисуем оружие в руках когда рендерим зеркало!
			{
				currententity->flags |= RF_NOUSE;
				return;
			}
		}
		else
		{
			if ( R_CullAliasModel( &frustum[0], true, true ) )
			{
				currententity->flags |= RF_NOUSE;		// Не освещать модель (не обрабатывать bump-кодом, ибо модель не видна)
				return;
			}

			if(gl_config.occlusion)
			{
				int currententity_index = Write_occHash();
				if (currententity_index>=0)
				{
					// begin occlusion test
					glBeginQueryARB(gl_config.occlusion, r_entities_occID[currententity_index]);
					r_entities_occUSE[currententity_index] = occ_framecount;
					occ = true;
				}
			}
		}
	}

	c_alias_polys += paliashdr->num_tris;

	// draw all the triangles
	if (currententity->flags & RF_DEPTHHACK) // hack the depth range to prevent view model from poking into walls
		glDepthRange (0, 0.3);

	glPushMatrix ();
	R_RotateForEntity (currententity);

	// select skin
	bool custom_skin = false;
	if (currententity->skin)
	{
		skin = currententity->skin;	// custom player skin
		custom_skin = true;
	}
	else
	{
		if (currententity->skinnum >= MAX_MD2SKINS)
			skin = currentmodel->skins[0];
		else
		{
			skin = currentmodel->skins[currententity->skinnum];
			if (!skin)
				skin = currentmodel->skins[0];
		}
	}
	if (!skin)
		skin = r_notexture;	// fallback...
	GL_Bind2D(skin->texnum);
	gl_TexEnv( GL_MODULATE );

	if (skin->Translucent33)
		alpha = 0.33;
	else if (skin->Translucent66)
		alpha = 0.66;
	else if (currententity->flags & RF_TRANSLUCENT)
		alpha = currententity->alpha;
	else
		alpha = 1.0;

	//
	// get lighting information
	//
	// PMM - rewrote, reordered to handle new shells & mixing
	// PMM - 3.20 code .. replaced with original way of doing it to keep mod authors happy
	//
	if ( r_newrefdef.rdflags & RDF_IRGOGGLES && (currententity->flags & RF_IR_VISIBLE))
	{
		shadelight[0] = 1.0;
		shadelight[1] = 0.0;
		shadelight[2] = 0.0;
	}
	else if ( currententity->flags & ( RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE | RF_POWER_GREEN | RF_POWER_RED | RF_POWER_BLUE) )
	{
		VectorClear(shadelight);
		if ( currententity->flags & (RF_SHELL_RED | RF_POWER_RED))
			shadelight[0] = 0.5;
		if ( currententity->flags & (RF_SHELL_GREEN | RF_POWER_GREEN))
			shadelight[1] = 0.5;
		if ( currententity->flags & (RF_SHELL_BLUE | RF_POWER_BLUE))
			shadelight[2] = 0.5;
	}
	else if ( currententity->flags & RF_FULLBRIGHT )
	{
		shadelight[0] = shadelight[1] = shadelight[2] = 1.0;
	}
	else if (weaponmodel_lighted && (currententity->flags & RF_WEAPONMODEL))
	{
		R_LightPoint (currententity->origin, shadelight);
		VectorAdd(shadelight, shell_color, shadelight);
		if (!VectorCompare(currententity->color_shell, vec3_origin))
			VectorAdd (shadelight, currententity->color_shell, shadelight);
		VectorNormalize(shadelight);
	}
	else if (alpha<1)
		shadelight[0]=shadelight[1]=shadelight[2]=alpha;
	else
	{
		R_LightPoint (currententity->origin, shadelight);
		if (!VectorCompare(currententity->color_shell, vec3_origin))
		{
			VectorAdd (shadelight, currententity->color_shell, shadelight);
			VectorNormalize(shadelight);
		}
	}

	if ( currententity->flags & RF_MINLIGHT )
	{
		for (i=0 ; i<3 ; i++)
			if (shadelight[i] > 0.02)	/// 0.1
				break;
		if (i == 3)
			shadelight[0] = shadelight[1] = shadelight[2] = 0.02;	/// 0.1
	}

/*	if ( currententity->flags & RF_GLOW )
	{	// bonus items will pulse with time
		float	scale;
		float	min;

		scale = 0.05 * sin(r_newrefdef.time*7);
		for (i=0 ; i<3 ; i++)
		{
			min = shadelight[i] * 0.4;
			shadelight[i] += scale;
			if (shadelight[i] < min)
				shadelight[i] = min;
		}
	}
*/
// =================
// PGM	ir goggles color override

	if ( alpha<1 )
	{
		glEnable (GL_BLEND);
		GL_BlendFunc(GL_ONE, GL_ONE);
	}

	if ( currententity->flags & ( RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE | RF_POWER_GREEN | RF_POWER_RED | RF_POWER_BLUE) )
		goto shell;
	if ( alpha<1 )
	{
		GL_DrawAliasFrameLerp (paliashdr, false, hud);
		goto stop;
	}
	if (currententity->flags & RF_CHROME || skin->fx == fx_chrome)
	{
		// select light-skin
		if (custom_skin)
			light = currententity->light;	// custom player light-skin
		else
		{
			if (currententity->skinnum >= MAX_MD2SKINS)
				light = currentmodel->lights[0];
			else
			{
				light = currentmodel->lights[currententity->skinnum];
				if (!light)
					light = currentmodel->lights[0];
			}
		}
		if (light)
		{
			GL_SelectTexture(GL_TEXTURE1);

			gl_TexEnv( GL_COMBINE_ARB );
			GL_TexEnv( GL_COMBINE_RGB_ARB, GL_ADD );
			GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
			GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
			GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
			GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );

			GL_Bind2D( light->texnum );
			glEnable(GL_TEXTURE_2D);
			calced = GL_DrawAliasFrameLerp (paliashdr, true, hud);

			GL_SelectTexture( GL_TEXTURE1 );
			glDisable( GL_TEXTURE_2D );
			gl_TexEnv( GL_REPLACE );
			GL_SelectTexture( GL_TEXTURE0 );
		}
		else
			calced = GL_DrawAliasFrameLerp (paliashdr, false, hud);

		GL_DrawAliasFrameLerpFX (paliashdr, true, false, false, skin, light, calced);
	}
	else
	{
shell:	if (currententity->flags & ( RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE ) )
			GL_DrawAliasFrameLerpShell (paliashdr);
		else
		{
			// select light-skin
			if (custom_skin)
				light = currententity->light;	// custom player light-skin
			else
			{
				if (currententity->skinnum >= MAX_MD2SKINS)
					light = currentmodel->lights[0];
				else
				{
					light = currentmodel->lights[currententity->skinnum];
					if (!light)
						light = currentmodel->lights[0];
				}
			}
			if (light)
			{
				GL_SelectTexture(GL_TEXTURE1);
				gl_TexEnv( GL_COMBINE_ARB );
				GL_TexEnv( GL_COMBINE_RGB_ARB, GL_ADD );
				GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
				GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
				GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
				GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );

				GL_Bind2D( light->texnum );
				glEnable(GL_TEXTURE_2D);
				calced = GL_DrawAliasFrameLerp (paliashdr, true, hud);

				GL_SelectTexture( GL_TEXTURE1 );
				glDisable( GL_TEXTURE_2D );
				gl_TexEnv( GL_REPLACE );
				GL_SelectTexture( GL_TEXTURE0 );
			}
			else
				calced = GL_DrawAliasFrameLerp (paliashdr, false, hud);

			if (currententity->flags & ( RF_POWER_GREEN | RF_POWER_RED | RF_POWER_BLUE ) || skin->fx == fx_power )
				GL_DrawAliasFrameLerpFX (paliashdr, false, false, false, skin, light, calced);
			else if (skin->fx == fx_map)
				GL_DrawAliasFrameLerpFX (paliashdr, false, true, false, skin, light, calced);
			else if (skin->fx == fx_style)
				GL_DrawAliasFrameLerpFX (paliashdr, false, false, true, skin, light, calced);
		}
	}

stop:
	if ( alpha<1 )
	{
		glDisable (GL_BLEND);
		GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	}

	gl_TexEnv( GL_REPLACE );
	glPopMatrix ();

	if ( currententity->flags & RF_DEPTHHACK )
		glDepthRange (0, 1);

	GL_Color3f (1,1,1);

	// end occlusion test
	if (occ)
		glEndQueryARB(gl_config.occlusion);
}


void Vid_InitModeList()
{
	char	*old, *modeBuffer;
	int		count, x, y;

	Com_Printf("Loading \"modelist.txt\":\n");
	count = FS_LoadFile ("../modelist.txt", (void**) &modeBuffer);
	if ((count > 0) && modeBuffer)
	{
		old = modeBuffer;
		_VID_NUM_MODES = 0;
		while (modeBuffer && (_VID_NUM_MODES < MAX_VID_NUM_MODES))
		{
			if (modeBuffer-old > count)
				break;
			x = atoi(COM_Parse (&modeBuffer));
			if (modeBuffer-old > count)
				break;
			y = atoi(COM_Parse (&modeBuffer));

			if (x>=640 && y>=480)
			{
				vid_modes[_VID_NUM_MODES].width = x;
				vid_modes[_VID_NUM_MODES].height = y;
				Com_sprintf (_resolutions[_VID_NUM_MODES], sizeof(_resolutions[_VID_NUM_MODES]), "[%4i %4i]", x, y);
				resolutions[_VID_NUM_MODES] = &_resolutions[_VID_NUM_MODES][0];
				_VID_NUM_MODES++;
			}
			else	// не позволяем использовать видеорежимы меньше 640 x 480, т.к. меню не влезет!
				if (x || y)
					Com_Printf("^3WARNING:^7 mode (%i x %i) ignored\n", x, y);
		}
		Z_Free (old);

		if (!_VID_NUM_MODES)
			goto nomodes;
	}
	else
	{
		if (modeBuffer)
			Z_Free (modeBuffer);
nomodes:		/// при отсутствии modelist.txt формируем дефолтную таблицу 640х480
		Com_Printf("^1Invalid \"modelist.txt\",\n^1Using internal videomodes list.\n");

		_VID_NUM_MODES = 0;

/*		vid_modes[0].width = 320;
		vid_modes[0].height = 240;
		Com_sprintf (_resolutions[0], sizeof(_resolutions[0]), "[320 240  ]");
		resolutions[0] = &_resolutions[0][0];

		vid_modes[1].width = 400;
		vid_modes[1].height = 300;
		Com_sprintf (_resolutions[1], sizeof(_resolutions[1]), "[400 300  ]");
		resolutions[1] = &_resolutions[1][0];

		vid_modes[2].width = 512;
		vid_modes[2].height = 384;
		Com_sprintf (_resolutions[2], sizeof(_resolutions[2]), "[512 384  ]");
		resolutions[2] = &_resolutions[2][0];
*/
		vid_modes[_VID_NUM_MODES].width = 640;
		vid_modes[_VID_NUM_MODES].height = 480;
		Com_sprintf (_resolutions[_VID_NUM_MODES], sizeof(_resolutions[_VID_NUM_MODES]), "[640 480  ]");
		resolutions[_VID_NUM_MODES] = &_resolutions[_VID_NUM_MODES][0];
		_VID_NUM_MODES++;

		vid_modes[_VID_NUM_MODES].width = 800;
		vid_modes[_VID_NUM_MODES].height = 600;
		Com_sprintf (_resolutions[_VID_NUM_MODES], sizeof(_resolutions[_VID_NUM_MODES]), "[800 600  ]");
		resolutions[_VID_NUM_MODES] = &_resolutions[_VID_NUM_MODES][0];
		_VID_NUM_MODES++;

		vid_modes[_VID_NUM_MODES].width = 960;
		vid_modes[_VID_NUM_MODES].height = 720;
		Com_sprintf (_resolutions[_VID_NUM_MODES], sizeof(_resolutions[_VID_NUM_MODES]), "[960 720  ]");
		resolutions[_VID_NUM_MODES] = &_resolutions[_VID_NUM_MODES][0];
		_VID_NUM_MODES++;

		vid_modes[_VID_NUM_MODES].width = 1024;
		vid_modes[_VID_NUM_MODES].height = 768;
		Com_sprintf (_resolutions[_VID_NUM_MODES], sizeof(_resolutions[_VID_NUM_MODES]), "[1024 768 ]");
		resolutions[_VID_NUM_MODES] = &_resolutions[_VID_NUM_MODES][0];
		_VID_NUM_MODES++;

		vid_modes[_VID_NUM_MODES].width = 1152;
		vid_modes[_VID_NUM_MODES].height = 864;
		Com_sprintf (_resolutions[_VID_NUM_MODES], sizeof(_resolutions[_VID_NUM_MODES]), "[1152 864 ]");
		resolutions[_VID_NUM_MODES] = &_resolutions[_VID_NUM_MODES][0];
		_VID_NUM_MODES++;

		vid_modes[_VID_NUM_MODES].width = 1280;
		vid_modes[_VID_NUM_MODES].height = 960;
		Com_sprintf (_resolutions[_VID_NUM_MODES], sizeof(_resolutions[_VID_NUM_MODES]), "[1280 960 ]");
		resolutions[_VID_NUM_MODES] = &_resolutions[_VID_NUM_MODES][0];
		_VID_NUM_MODES++;

		vid_modes[_VID_NUM_MODES].width = 1280;
		vid_modes[_VID_NUM_MODES].height = 1024;
		Com_sprintf (_resolutions[_VID_NUM_MODES], sizeof(_resolutions[_VID_NUM_MODES]), "[1280 1024]");
		resolutions[_VID_NUM_MODES] = &_resolutions[_VID_NUM_MODES][0];
		_VID_NUM_MODES++;

		vid_modes[_VID_NUM_MODES].width = 1600;
		vid_modes[_VID_NUM_MODES].height = 1200;
		Com_sprintf (_resolutions[_VID_NUM_MODES], sizeof(_resolutions[_VID_NUM_MODES]), "[1600 1200]");
		resolutions[_VID_NUM_MODES] = &_resolutions[_VID_NUM_MODES][0];
		_VID_NUM_MODES++;

		vid_modes[_VID_NUM_MODES].width = 2048;
		vid_modes[_VID_NUM_MODES].height = 1536;
		Com_sprintf (_resolutions[_VID_NUM_MODES], sizeof(_resolutions[_VID_NUM_MODES]), "[2048 1536]");
		resolutions[_VID_NUM_MODES] = &_resolutions[_VID_NUM_MODES][0];
		_VID_NUM_MODES++;
	}

	resolutions[_VID_NUM_MODES] = 0;
	for (x=0; x<_VID_NUM_MODES; x++)
		Com_Printf("%2i: %s\n", x, resolutions[x]);
}


void R_DrawSpriteModel ()
{
	float		alpha;
	vec3_t		point;
	dsprframe_t	*frame;
	float		*up, *right;
	dsprite_t	*psprite;

	// don't even bother culling, because it's just a single
	// polygon without a surface cache
	psprite = (dsprite_t *)currentmodel->extradata;
	if (psprite->numframes <= 1)
		currententity->frame = 0;
	else
	{
		if(currententity->flags & RF_ANIMFRAME)
			currententity->frame = (int)(cl.leveltime*20) % psprite->numframes;
		else
			currententity->frame %= psprite->numframes;
	}

	frame = &psprite->frames[currententity->frame];
	up = vup;
	right = vright;

	if ( currententity->flags & RF_TRANSLUCENT )
		alpha = currententity->alpha;
	else
		alpha = 1;

	glEnable(GL_BLEND);
	GL_BlendFunc(GL_ONE, GL_ONE);
	glDepthMask(0);
	GL_Color3f( alpha, alpha, alpha );

	image_t *tex = currentmodel->skins[currententity->frame];
	if(tex)
		GL_Bind2D(tex->texnum);
	else
		GL_Bind2D(r_notexture->texnum);		// fixed by berserker

	gl_TexEnv( GL_MODULATE );

	glBegin (GL_QUADS);

	glTexCoord2f (0, 1);
	VectorMA (currententity->origin, -frame->origin_y, up, point);
	VectorMA (point, -frame->origin_x, right, point);
	glVertex3fv (point);

	glTexCoord2f (0, 0);
	VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
	VectorMA (point, -frame->origin_x, right, point);
	glVertex3fv (point);

	glTexCoord2f (1, 0);
	VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
	VectorMA (point, frame->width - frame->origin_x, right, point);
	glVertex3fv (point);

	glTexCoord2f (1, 1);
	VectorMA (currententity->origin, -frame->origin_y, up, point);
	VectorMA (point, frame->width - frame->origin_x, right, point);
	glVertex3fv (point);
	
	glEnd ();

	gl_TexEnv( GL_REPLACE );
	glDisable( GL_BLEND );
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	GL_Color3f( 1, 1, 1 );
	glDepthMask(1);

/// Berserker //////////////////////////
	if (r_showtris->value || (curmodel && currententity->alink == curmodel))
	{
		if (r_showtris->value > 1 || (r_editor->value && (curmodel && currententity->alink == curmodel) && (cl.time & 512)))
			glDisable (GL_DEPTH_TEST);
		glDisable (GL_TEXTURE_2D);
		GL_Color3f (0,1,1);

		glBegin (GL_LINE_STRIP);

		VectorMA (currententity->origin, -frame->origin_y, up, point);
		VectorMA (point, -frame->origin_x, right, point);
		glVertex3fv (point);

		VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
		VectorMA (point, -frame->origin_x, right, point);
		glVertex3fv (point);

		VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
		VectorMA (point, frame->width - frame->origin_x, right, point);
		glVertex3fv (point);

		VectorMA (currententity->origin, -frame->origin_y, up, point);
		VectorMA (point, frame->width - frame->origin_x, right, point);
		glVertex3fv (point);
	
		VectorMA (currententity->origin, -frame->origin_y, up, point);
		VectorMA (point, -frame->origin_x, right, point);
		glVertex3fv (point);

		glEnd ();

		glEnable (GL_TEXTURE_2D);
		if (r_showtris->value > 1 || (r_editor->value && (curmodel && currententity->alink == curmodel) && (cl.time & 512)))
			glEnable (GL_DEPTH_TEST);
		GL_Color3f (1,1,1);
	}
/////////////////////////////////////////
}


void R_DrawSprite2Model ()
{
	float			alpha;
	vec3_t			point;
	dspr2frame_t	*frame;
	float			*up, *right;
	dsprite2_t		*psprite;
	int				nframe;
	vec3_t			length, width;

	psprite = (dsprite2_t *)currentmodel->extradata;

	if(currententity->flags & RF_ANIMFRAME)		// Спрайты игнорируют фреймы с сервера, используем свои тайминги
	{
		if (psprite->timescale == 1)	// default
			nframe = cl.leveltime*20;
		else
			nframe = cl.leveltime*psprite->timescale/FRAMETIME;
	}
	else if(currententity->flags & RF_ANIM_ONE_CYCLE)	// Рисовать только первый цикл!
	{
		nframe = ((float)(currententity->oldframe) + cl.lerpfrac * ((float)(currententity->frame) - (float)(currententity->oldframe))) * psprite->timescale;
		if (nframe<0)
			nframe = -currententity->frame;
		if (nframe >= psprite->numframes)
		{
			currententity->flags |= RF_NOUSE;
			return;
		}
	}
	else
		nframe = currententity->frame;

	nframe %= psprite->numframes;
	frame = &psprite->frames[nframe];
	up = vup;
	right = vright;

	if (psprite->skipDiffuse)
		goto skip;

	// don't even bother culling, because it's just a single
	// polygon without a surface cache
	image_t *tex = frame->skin;

	if ( currententity->flags & RF_TRANSLUCENT )
		alpha = currententity->alpha;
	else
		alpha = 1;

	glDepthMask(0);
	glEnable(GL_BLEND);
	GL_BlendFunc(GL_ONE, GL_ONE);
	GL_Color3f( alpha, alpha, alpha );
///	if(tex)
		GL_Bind2D(tex->texnum);
///	else
///		GL_Bind2D(r_notexture->texnum);		// fixed by berserker
	gl_TexEnv( GL_MODULATE );

	glBegin (GL_QUADS);

	if (psprite->directed)
	{
		if (psprite->directedScale > 0.0)
		{
			AngleVectors (currententity->angles, length, NULL, NULL);
			VectorScale(length, frame->width * psprite->directedScale, length);
		}
		else
			VectorSubtract(currententity->oldorigin, currententity->origin, length);

		VectorSubtract(currententity->origin, r_origin, point);
		CrossProduct(point, length, width);
		if (!VectorNormalize(width))
			VectorCopy(vup, width);
		VectorScale(width, frame->width, width);

		glTexCoord2f( 0, 0 );
		glVertex3f( currententity->origin[0] + width[0], currententity->origin[1] + width[1], currententity->origin[2] + width[2]);

		glTexCoord2f( 0, 1 );
		glVertex3f( currententity->origin[0] - width[0], currententity->origin[1] - width[1], currententity->origin[2] - width[2]);

		VectorSubtract(currententity->origin, r_origin, point);
		VectorAdd(point, length, point);
		CrossProduct(point, length, width);
		if (!VectorNormalize(width))
			VectorCopy(vup, width);
		VectorScale(width, frame->width, width);

		glTexCoord2f( 1, 1 );
		glVertex3f( currententity->origin[0] + length[0] - width[0], currententity->origin[1] + length[1] - width[1], currententity->origin[2] + length[2] - width[2]);

		glTexCoord2f( 1, 0 );
		glVertex3f( currententity->origin[0] + length[0] + width[0], currententity->origin[1] + length[1] + width[1], currententity->origin[2] + length[2] + width[2]);
	}
	else
	{
		glTexCoord2f (0, 1);
		VectorMA (currententity->origin, -frame->origin_y, up, point);
		VectorMA (point, -frame->origin_x, right, point);
		glVertex3fv (point);

		glTexCoord2f (0, 0);
		VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
		VectorMA (point, -frame->origin_x, right, point);
		glVertex3fv (point);

		glTexCoord2f (1, 0);
		VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
		VectorMA (point, frame->width - frame->origin_x, right, point);
		glVertex3fv (point);

		glTexCoord2f (1, 1);
		VectorMA (currententity->origin, -frame->origin_y, up, point);
		VectorMA (point, frame->width - frame->origin_x, right, point);
		glVertex3fv (point);
	}

	glEnd ();

	gl_TexEnv( GL_REPLACE );
	glDisable( GL_BLEND );
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	GL_Color3f( 1, 1, 1 );
	glDepthMask(1);

skip:
/// Berserker //////////////////////////
	if ((!psprite->directed) && (r_showtris->value || (curmodel && currententity->alink == curmodel)))
	{
		if (r_showtris->value > 1 || (r_editor->value && (curmodel && currententity->alink == curmodel) && (cl.time & 512)))
			glDisable (GL_DEPTH_TEST);
		glDisable (GL_TEXTURE_2D);
		GL_Color3f (0,1,1);

		glBegin (GL_LINE_STRIP);

		VectorMA (currententity->origin, -frame->origin_y, up, point);
		VectorMA (point, -frame->origin_x, right, point);
		glVertex3fv (point);

		VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
		VectorMA (point, -frame->origin_x, right, point);
		glVertex3fv (point);

		VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
		VectorMA (point, frame->width - frame->origin_x, right, point);
		glVertex3fv (point);

		VectorMA (currententity->origin, -frame->origin_y, up, point);
		VectorMA (point, frame->width - frame->origin_x, right, point);
		glVertex3fv (point);
	
		VectorMA (currententity->origin, -frame->origin_y, up, point);
		VectorMA (point, -frame->origin_x, right, point);
		glVertex3fv (point);

		glEnd ();

		glEnable (GL_TEXTURE_2D);
		if (r_showtris->value > 1 || (r_editor->value && (curmodel && currententity->alink == curmodel) && (cl.time & 512)))
			glEnable (GL_DEPTH_TEST);
		GL_Color3f (1,1,1);
	}
/////////////////////////////////////////
}


void R_DrawAliasMD3ModelFog ()
{
	maliasmodel_t	*paliashdr;
	int				i, j;
	float			frontlerp, backlerp;
	maliasframe_t	*frame, *oldframe;
	vec3_t			move, vectors[3];
	maliasvertex_t	*v, *ov;
	fog_t			*fog = NULL;
	image_t			*skin;
	vec3_t			oldorg;
	float			oldfog, mmm;
	byte			colorArray[MD3_MAX_VERTS*3];

	if (( currententity->flags & RF_WEAPONMODEL ) || (currentmodel->flags & RF_DISTORT) || (currententity->flags & RF_DISTORT))
		return;

///	if ( !( currententity->flags & RF_WEAPONMODEL ) )
///		if ( R_CullAliasMD3Model( &frustum[0], true, true ) )
///			return;

	for (j=0; j<numFogs; j++)
		if (BBoxIntersectBBox(fog_infos[j].minmax, currententity->minmax))
			fog = &fog_infos[j];

	if (fog)
	{
		paliashdr = (maliasmodel_t *)currentmodel->extradata;

		glPushMatrix ();
		R_RotateForEntity (currententity);

///		if ( !r_lerpmodels->value )
///			currententity->backlerp = 0;

		backlerp = currententity->backlerp;
		frontlerp = 1.0 - backlerp;
		frame = paliashdr->frames + currententity->frame;
		oldframe = paliashdr->frames + currententity->oldframe;

		VectorSubtract (currententity->oldorigin, currententity->origin, move);
		oldfog = fog->origin[2];
		VectorCopy(r_origin, oldorg);
		fog->origin[2] -= currententity->origin[2];
		VectorSubtract(r_origin, currententity->origin, r_origin);

		if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
		{
			vec3_t	temp;
			AngleVectors (currententity->angles, vectors[0], vectors[1], vectors[2]);

			VectorCopy(move, temp);
			move[0] = DotProduct (temp, vectors[0]);
			move[1] = -DotProduct (temp, vectors[1]);
			move[2] = DotProduct (temp, vectors[2]);
		}

		if (currententity->angles[1])
		{
			vec3_t	temp, ang;

			VectorSet(ang, 0, currententity->angles[1], 0);
			AngleVectors (ang, vectors[0], vectors[1], vectors[2]);

			VectorCopy(r_origin, temp);
			r_origin[0] = DotProduct (temp, vectors[0]);
			r_origin[1] = -DotProduct (temp, vectors[1]);
			r_origin[2] = DotProduct (temp, vectors[2]);
		}

		VectorAdd (move, oldframe->translate, move);

		for (j=0 ; j<3 ; j++)
			move[j] = backlerp*move[j] + frontlerp*frame->translate[j];

		glEnableClientState( GL_COLOR_ARRAY );
		glEnableClientState( GL_VERTEX_ARRAY );

		byte	rrr,ggg,bbb;
		rrr = fog->color[0] * 255;
		ggg = fog->color[1] * 255;
		bbb = fog->color[2] * 255;

		bool	rot = false;
		for (i=0; i < paliashdr->num_meshes; i++)
		{
			if (rot)
			{
				rot = false;
				GL_RotateTexture(true, 0, 1);
			}
			maliasmesh_t *mesh = &paliashdr->meshes[i];
			// select skin
			skin = mesh->img_skins[min(currententity->skinnum, MD3_MAX_SKINS-1)];
			if (!skin || skin == r_notexture)
			{
				if (currententity->skin)
					skin = currententity->skin;	// custom player skin
			}
			if (!skin)
				skin = mesh->img_skins[0];
			if(skin)
			{
				if (skin->Translucent33 || skin->Translucent66 || skin->fx == fx_distort)
					continue;

				if (skin->rotate)
				{
					rot = true;
					GL_RotateTexture(false, anglemod(cl.leveltime * skin->rotate), 1);
				}

				if(/*skin->has_alpha && */skin->AlphaTest)	// Поддержка "дырявых" текстур
				{
					GL_MBind2D( GL_TEXTURE0, skin->texnum );
					glEnable(GL_TEXTURE_2D);

					gl_TexEnv( GL_COMBINE_ARB );
					GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
					GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_PRIMARY_COLOR_ARB );
					GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
					GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_TEXTURE );
					GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA );
					GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_MODULATE );
					GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB );
					GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
					GL_TexEnv( GL_SOURCE1_ALPHA_ARB, GL_TEXTURE );
					GL_TexEnv( GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA );
				}

				v = mesh->vertexes + currententity->frame*mesh->num_verts;
				ov = mesh->vertexes + currententity->oldframe*mesh->num_verts;
				for ( j = 0; j < mesh->num_verts; j++, v++, ov++ )
				{
					VectorSet ( tempVertexArray[j], 
						move[0] + ov->point[0]*backlerp + v->point[0]*frontlerp,
						move[1] + ov->point[1]*backlerp + v->point[1]*frontlerp,
						move[2] + ov->point[2]*backlerp + v->point[2]*frontlerp);

					mmm = FogDist(fog, tempVertexArray[j]);
					colorArray[j*3+0] = rrr * mmm;
					colorArray[j*3+1] = ggg * mmm;
					colorArray[j*3+2] = bbb * mmm;
				}

///				if (gl_config.vbo)
///				{
///					currententity->vbo_lightp[i] = R_VCLoadData(VBO_DYNAMIC, j * 3, &colorArray, VBO_STORE_ANY, NULL, NULL);
///					glColorPointer( 3, GL_UNSIGNED_BYTE, 0, 0 );
///				}
///				else
				{
					GL_BindVBO(NULL);
					glColorPointer( 3, GL_UNSIGNED_BYTE, 0, colorArray );
				}

				if(skin->AlphaTest/* && skin->has_alpha*/)
				{
					glEnableClientState( GL_TEXTURE_COORD_ARRAY );
					if (gl_config.vbo)
					{
						GL_BindVBO(mesh->vbo_st);
						glTexCoordPointer(2, GL_FLOAT, 0, 0);
					}
					else
					{
						GL_BindVBO(NULL);
						glTexCoordPointer( 2, GL_FLOAT, 0, mesh->stcoords );
					}
				}

				if (gl_config.vbo)
				{
					GL_BindVBO(currententity->vbo_xyz[i]);
					glVertexPointer(3, GL_FLOAT, 0, 0);
				}
				else
				{
					GL_BindVBO(NULL);
					glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );
				}

///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///				else
					glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

				if(skin->AlphaTest/* && skin->has_alpha*/)
				{
					glDisable(GL_TEXTURE_2D);
					gl_TexEnv( GL_COMBINE_ARB );
					GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
					GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_PRIMARY_COLOR_ARB );
					GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
					GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_TEXTURE );
					GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
					GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_MODULATE );
					GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB );
					GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
					GL_TexEnv( GL_SOURCE1_ALPHA_ARB, GL_TEXTURE );
					GL_TexEnv( GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA );
					glDisableClientState( GL_TEXTURE_COORD_ARRAY );
				}
			}
		}

		if (rot)
			GL_RotateTexture(true, 0, 1);

		glDisableClientState( GL_VERTEX_ARRAY );
		glDisableClientState( GL_COLOR_ARRAY );
		if (gl_config.vbo)
			GL_BindVBO(NULL);

		fog->origin[2] = oldfog;
		VectorCopy(oldorg, r_origin);

		glPopMatrix ();
	}
}


void R_DrawAliasModelFog ()
{
	int			j;
	dmdl_t		*paliashdr;
	fog_t		*fog = NULL;

	if (( currententity->flags & RF_WEAPONMODEL ) || (currentmodel->flags & RF_DISTORT) || (currententity->flags & RF_DISTORT))
		return;

///	if ( !( currententity->flags & RF_WEAPONMODEL ) )
///		if ( R_CullAliasModel( &frustum[0], true, true ) )
///			return;

	for (j=0; j<numFogs; j++)
		if (BBoxIntersectBBox(fog_infos[j].minmax, currententity->minmax))
			fog = &fog_infos[j];

	if (fog)
	{
		paliashdr = (dmdl_t *)currentmodel->extradata;

		glPushMatrix ();
		R_RotateForEntity (currententity);
		GL_DrawAliasFrameLerpFog(paliashdr, fog);
		glPopMatrix ();
	}
}


void R_DrawFogModels ()
{
	int		i;

	if (!r_drawentities->value)
		return;

	bool	nomdl = (r_mirror && !r_mirror_models->value);

	// draw non-transparent only
	for (i=0 ; i<r_newrefdef.num_entities ; i++)
	{
		currententity = &r_newrefdef.entities[i];

		if (currententity->flags & (RF_TRANSLUCENT | RF_BEAM | RF_NOUSE | RF_DISTORT))
			continue;

		if (!r_mirror && (currententity->flags & RF_VIEWERMODEL))
			continue;

		if (sky_world)
			if (currententity->sky_model != drawing_sky_world)
				continue;

		currentmodel = currententity->model;
		if (currentmodel->type == mod_brush)
			R_DrawBrushModelFog ();
		else if (!nomdl && currentmodel->type == mod_alias)
		{
			R_DrawAliasModelFog ();
			GL_ClearCache(GL_COLOR_);	// reset cache (glColorPointer used...)
		}
		else if (!nomdl && currentmodel->type == mod_alias_md3)
		{
			R_DrawAliasMD3ModelFog ();
			GL_ClearCache(GL_COLOR_);	// reset cache (glColorPointer used...)
		}
	}
}


void CL_Switch_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	le_mode = !le_mode;

	if(le_mode)
	{
///		if(!lightmodel_counter)
///		{
///			Com_Printf("^3No linked lights exist!\n");
///			le_mode = false;
///		}
///		else
			Com_Printf("Switched to linked lights mode.\n");
	}
	else
	{
///		if(!numStaticShadowLights)
///		{
///			Com_Printf("^3No static lights exist!\n");
///			le_mode = true;
///		}
///		else
			Com_Printf("Switched to static lights mode.\n");
	}

	Cl_UpdateEditorCvars(ED_LIGHT);
}


void CL_PickEmit_f()
{
	int			i, j, best;
	cplane_t	frust[5];
	float		best_dist;
	emit_t		*p;

	if (!LightEditor_Enabled(false))
		return;

// set slim frustum (FOV = 0 degrees)
	// rotate VPN right by FOV_X/2 degrees
	RotatePointAroundVector( frust[0].normal, vup, vpn, -90 );
	// rotate VPN left by FOV_X/2 degrees
	RotatePointAroundVector( frust[1].normal, vup, vpn, 90 );
	// rotate VPN up by FOV_X/2 degrees
	RotatePointAroundVector( frust[2].normal, vright, vpn, 90 );
	// rotate VPN down by FOV_X/2 degrees
	RotatePointAroundVector( frust[3].normal, vright, vpn, -90 );
	// near plane
	VectorCopy(vpn, frust[4].normal);
	for (i=0 ; i<5 ; i++)
	{
		frust[i].type = PLANE_ANYZ;
		frust[i].dist = DotProduct (r_origin, frust[i].normal);
		frust[i].signbits = SignbitsForPlane (&frust[i]);
	}

	best = -1;
	best_dist = 99999999;

	for (i=0; i<numEmits; i++)
	{
		float	x, y, z, dist;
		vec3_t	mins, maxs;

		p = &emits[i];

		/*
		** compute axially aligned mins and maxs
		*/
		for (j = 0; j < 3; j++)
		{
			mins[j] = p->origin0[j] - 16;
			maxs[j] = p->origin0[j] + 16;
		}

		if (!R_CullBox5(mins, maxs, &frust[0]))
		{
			if (DotProduct(p->origin0, frust[4].normal) - frust[4].dist > 0)
			{
				x = r_origin[0] - p->origin0[0];
				y = r_origin[1] - p->origin0[1];
				z = r_origin[2] - p->origin0[2];
				dist = x*x+y*y+z*z;
				if(dist<best_dist)
				{
					best_dist=dist;
					best=i;
				}
			}
		}
	}

	if(best==-1)
	{
		Com_Printf("^3No emitter selected!\n");
		return;
	}

	curemit = &emits[best];
	Cl_UpdateEditorCvars(ED_EMIT);
}

void CL_PickDecal_f()
{
	int			i, j, best;
	cplane_t	frust[5];
	float		best_dist;
	defDecal_t	*p;

	if (!LightEditor_Enabled(false))
		return;

// set slim frustum (FOV = 0 degrees)
	// rotate VPN right by FOV_X/2 degrees
	RotatePointAroundVector( frust[0].normal, vup, vpn, -90 );
	// rotate VPN left by FOV_X/2 degrees
	RotatePointAroundVector( frust[1].normal, vup, vpn, 90 );
	// rotate VPN up by FOV_X/2 degrees
	RotatePointAroundVector( frust[2].normal, vright, vpn, 90 );
	// rotate VPN down by FOV_X/2 degrees
	RotatePointAroundVector( frust[3].normal, vright, vpn, -90 );
	// near plane
	VectorCopy(vpn, frust[4].normal);
	for (i=0 ; i<5 ; i++)
	{
		frust[i].type = PLANE_ANYZ;
		frust[i].dist = DotProduct (r_origin, frust[i].normal);
		frust[i].signbits = SignbitsForPlane (&frust[i]);
	}

	best = -1;
	best_dist = 99999999;

	for (i=0; i<num_deferred_decals; i++)
	{
		float	x, y, z, dist;
		vec3_t	mins, maxs;

		p = &deferred_decals[i];

		/*
		** compute axially aligned mins and maxs
		*/
		for (j = 0; j < 3; j++)
		{
			mins[j] = p->center[j] - p->size;
			maxs[j] = p->center[j] + p->size;
		}

		if (!R_CullBox5(mins, maxs, &frust[0]))
		{
			if (DotProduct(p->center, frust[4].normal) - frust[4].dist > 0)
			{
				x = r_origin[0] - p->center[0];
				y = r_origin[1] - p->center[1];
				z = r_origin[2] - p->center[2];
				dist = x*x+y*y+z*z;
				if(dist<best_dist)
				{
					best_dist=dist;
					best=i;
				}
			}
		}
	}

	if(best==-1)
	{
		Com_Printf("^3No decal selected!\n");
		return;
	}

	curdecal = &deferred_decals[best];
	Cl_UpdateEditorCvars(ED_DECAL);
}

void CL_Pick_f()
{
	int			i, j, best;
	cplane_t	frust[5];
	float		best_dist;

	if (!LightEditor_Enabled(false))
		return;

	if (numStaticShadowLights==0)
	{
		Com_Printf("^3No static lights exist!\n");
		return;
	}

	if (le_mode)
		CL_Switch_f();

// set slim frustum (FOV = 0 degrees)
	// rotate VPN right by FOV_X/2 degrees
	RotatePointAroundVector( frust[0].normal, vup, vpn, -90 );
	// rotate VPN left by FOV_X/2 degrees
	RotatePointAroundVector( frust[1].normal, vup, vpn, 90 );
	// rotate VPN up by FOV_X/2 degrees
	RotatePointAroundVector( frust[2].normal, vright, vpn, 90 );
	// rotate VPN down by FOV_X/2 degrees
	RotatePointAroundVector( frust[3].normal, vright, vpn, -90 );
	// near plane
	VectorCopy(vpn, frust[4].normal);
	for (i=0 ; i<5 ; i++)
	{
		frust[i].type = PLANE_ANYZ;
		frust[i].dist = DotProduct (r_origin, frust[i].normal);
		frust[i].signbits = SignbitsForPlane (&frust[i]);
	}

	best = -1;
	best_dist = 99999999;
	for(i=0; i<numStaticShadowLights; i++)
	{
		float	x, y, z, dist;
		vec3_t	mins, maxs;

		/*
		** compute axially aligned mins and maxs
		*/
		for (j = 0; j < 3; j++)
		{
			mins[j] = shadowlights[i].origin[j] - 4;
			maxs[j] = shadowlights[i].origin[j] + 4;
		}

		if (!R_CullBox5(mins, maxs, &frust[0]))
		{
			if (DotProduct(shadowlights[i].origin, frust[4].normal) - frust[4].dist > 0)
			{
				x = r_origin[0] - shadowlights[i].origin[0];
				y = r_origin[1] - shadowlights[i].origin[1];
				z = r_origin[2] - shadowlights[i].origin[2];
				dist = x*x+y*y+z*z;
				if(dist<best_dist)
				{
					best_dist=dist;
					best=i;
				}
			}
		}
	}

	if(best==-1)
		return;

	curlight = &shadowlights[best];
	Com_Printf("Selected static light %i\n", best);
	Cl_UpdateEditorCvars(ED_LIGHT);
}

void R_DrawAmbientWeaponSolid ()
{
	int			i;

///	if (!r_drawentities->value)
///		return;

	// draw non-transparent first
	for (i=0 ; i<r_newrefdef.num_entities ; i++)
	{
		currententity = &r_newrefdef.entities[i];
		if (!(currententity->flags & RF_WEAPONMODEL))
			continue;

		if (currententity->flags & RF_TRANSLUCENT)
			continue;	// solid

		if (currententity->flags & RF_BEAM)
			continue;

		currentmodel = currententity->model;
		if (!currentmodel)
			continue;

		if (currentmodel->type != mod_alias && currentmodel->type != mod_alias_md3)
			continue;

		if (currentmodel->type == mod_alias)
		{
			R_DrawAliasModel (false);
			break;
		}
		else
		{
			R_DrawAliasMD3Model (false, true, false);
			break;
		}
	}
}

void R_DrawAmbientEntitiesOnList_Solid ()
{
	int			i;

	if (!r_drawentities->value)
		return;

	entity_t	*weap = NULL;
	bool		distort, nomdl = (r_mirror && !r_mirror_models->value);
	float		disdis = r_distort_distance->value;

	// draw non-transparent first
	for (i=0 ; i<r_newrefdef.num_entities ; i++)
	{
		currententity = &r_newrefdef.entities[i];
		currententity->minmax_ready = false;

		// grab the weapon model
		if (currententity->flags & RF_WEAPONMODEL)
		{
			if (currententity->flags & RF_TRANSLUCENT)
			{
				if (currententity->flags & (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE))
					continue;	// shell weapon model, skip
				invisibled_gun = true;
			}
			else
				weap = currententity;
			continue;
		}

		if (currententity->flags & RF_BEAM)
			continue;

		if (currententity->model)
		{
			currentmodel = currententity->model;
			if ((currententity->flags & (RF_DISTORT_SKIN|RF_DISTORT)) || (currentmodel->flags & (RF_DISTORT_SKIN|RF_DISTORT)))
			{
				if (need_gun_test && !need_gun_optimisation)
				{
					bool visible = true;
					if (currentmodel->type == mod_alias)
					{
						if ( R_CullAliasModel( &frustum[0], true, true ) )
							visible = false;
					}
					else if (currentmodel->type == mod_alias_md3)
					{
						if ( R_CullAliasMD3Model( &frustum[0], true, true ) )
							visible = false;
					}
			/// FIXME: куллить спрайты!!!

					if (visible)	// Если требуется проверка и еще не нашли ближний дисторт-объект
					{
						vec3_t	mins, maxs;
						VectorSet(mins, currententity->origin[0]+currentmodel->mins[0]-disdis, currententity->origin[1]+currentmodel->mins[1]-disdis, currententity->origin[2]+currentmodel->mins[2]-disdis);
						VectorSet(maxs, currententity->origin[0]+currentmodel->maxs[0]+disdis, currententity->origin[1]+currentmodel->maxs[1]+disdis, currententity->origin[2]+currentmodel->maxs[2]+disdis);
						if (PointInBBox(r_origin, mins, maxs))
							need_gun_optimisation = true;
					}
				}

// т.к. solid модели рисуются первыми, то только тут и будем фиксить:
				if ( currententity->flags & RF_DISTORT )
				{
					if(!gl_config.screentexture || !gl_config.arb_distort || arbdistort_program==0xffffffff || !r_distort->value || r_simple->value)
					{
						currententity->flags &= ~RF_DISTORT;	// убрать distort флаг
						currententity->flags |= RF_TRANSLUCENT;
						currententity->alpha = 0.1;
					}
					continue;	// solid
				}
// конец фикса
			}

			if (currentmodel->type == mod_sprite || currentmodel->type == mod_sprite2)
				continue;
		}
		else
		{
			if(!nomdl)
				R_DrawNullModel ();
			continue;
		}

		if (currententity->flags & RF_TRANSLUCENT)
			continue;	// translucent

		if (!r_mirror && (currententity->flags & RF_VIEWERMODEL))
			continue;

		if (sky_world)
			if (currententity->sky_model != drawing_sky_world)
				continue;

		switch (currentmodel->type)
		{
		case mod_alias:
			if(!nomdl)
				R_DrawAliasModel (false);
			break;
		case mod_alias_md3:
			if(!nomdl)
				R_DrawAliasMD3Model (false, true, false);
			break;
		case mod_brush:
			distort = R_DrawBrushModel ();
			if (distort && need_gun_test && !need_gun_optimisation)	// Если требуется проверка и еще не нашли ближний дисторт-объект)
			{
				vec3_t	mins, maxs;
				VectorSet(mins, currententity->origin[0]+currentmodel->mins[0]-disdis, currententity->origin[1]+currentmodel->mins[1]-disdis, currententity->origin[2]+currentmodel->mins[2]-disdis);
				VectorSet(maxs, currententity->origin[0]+currentmodel->maxs[0]+disdis, currententity->origin[1]+currentmodel->maxs[1]+disdis, currententity->origin[2]+currentmodel->maxs[2]+disdis);
				if (PointInBBox(r_origin, mins, maxs))
					need_gun_optimisation = true;
			}
			break;
		case mod_sprite:
		case mod_sprite2:
///			R_DrawSpriteModel ();
			break;
		default:
			Com_Error (ERR_DROP, "R_DrawAmbientEntitiesOnList_Solid: Bad modeltype");
			break;
		}
	}

	if (weap && !need_gun_optimisation)
	{	// нарисуем ружьё, т.к. оптимизация не требуется
		currententity = weap;
		currentmodel = currententity->model;
		if (currentmodel)
		{
			switch (currentmodel->type)
			{
			case mod_alias:
				R_DrawAliasModel (false);
				break;
			case mod_alias_md3:
				R_DrawAliasMD3Model (false, true, false);
			break;
			default:
				Com_Error (ERR_DROP, "R_DrawAmbientEntitiesOnList_Solid: invalid weaponmodel");
				break;
			}
		}
	}
}


inline void R_EnableClipMirror(int n)
{
	double	plane[4];

	if(mirror_plane->planeback)
	{
		plane[0] = -mirror_plane->plane.normal[0];
		plane[1] = -mirror_plane->plane.normal[1];
		plane[2] = -mirror_plane->plane.normal[2];
		plane[3] = mirror_plane->plane.dist;
	}
	else
	{
		plane[0] = mirror_plane->plane.normal[0];
		plane[1] = mirror_plane->plane.normal[1];
		plane[2] = mirror_plane->plane.normal[2];
		plane[3] = -mirror_plane->plane.dist;
	}

///	if (gl_state.clipPlane[n][0] != plane[0] || gl_state.clipPlane[n][1] != plane[1] || gl_state.clipPlane[n][2] != plane[2] || gl_state.clipPlane[n][3] != plane[3])
///	{
///		gl_state.clipPlane[n][0] = plane[0];
///		gl_state.clipPlane[n][1] = plane[1];
///		gl_state.clipPlane[n][2] = plane[2];
///		gl_state.clipPlane[n][3] = plane[3];
		glClipPlane (GL_CLIP_PLANE0+n, plane);
///	}
	glEnable (GL_CLIP_PLANE0+n);
}


inline void R_DisableClipMirror(int n)
{
	glDisable (GL_CLIP_PLANE0+n);
}


void R_DrawAmbientEntitiesOnList_Transparent ()
{
	int		i;
	bool	flag_saved;
	int		oldflags;
	float	oldalpha;

	if (!r_drawentities->value)
		return;

	bool	nomdl = (r_mirror && !r_mirror_models->value);

	// draw transparent entities
	// we could sort these if it ever becomes a problem...
	for (i=0 ; i<r_newrefdef.num_entities ; i++)
	{
		currententity = &r_newrefdef.entities[i];
		flag_saved = false;

		if (currententity->model && (currententity->model->type == mod_sprite || currententity->model->type == mod_sprite2))
			goto yes;

		if (r_mirror && (currententity->flags & RF_DISTORT))
		{	// HACK: в зеркалах заменим дисторт-модели на прозрачные
			flag_saved = true;
			oldflags = currententity->flags;
			oldalpha = currententity->alpha;
			currententity->flags &= ~RF_DISTORT;
			currententity->flags |= RF_TRANSLUCENT;
			currententity->alpha = 0.1;
			goto next;
		}

		if (currententity->model)
		{
			if (currententity->model->type == mod_alias_md3)
			{
				if (!((currententity->flags & RF_TRANSLUCENT) || currententity->model->md3_has_trans_mesh))
					continue;	// solid
			}
			else
			{
				if (!(currententity->flags & RF_TRANSLUCENT))
					continue;	// solid
			}
		}

next:
		if (!r_mirror && (currententity->flags & RF_VIEWERMODEL))	/// imho не бывают прозрачных моделей игрока!
		{
			if (flag_saved)
			{
				currententity->flags = oldflags;
				currententity->alpha = oldalpha;
			}
			continue;
		}

yes:	if (sky_world)
			if (currententity->sky_model != drawing_sky_world)
			{
				if (flag_saved)
				{
					currententity->flags = oldflags;
					currententity->alpha = oldalpha;
				}
				continue;
			}

//		if ( currententity->flags & RF_BEAM )
//			R_DrawBeam( currententity );
//		else
if (!(currententity->flags & RF_BEAM))
		{
			currentmodel = currententity->model;

			if (!currentmodel)
			{
				if(!nomdl)
					R_DrawNullModel ();
				if (flag_saved)
				{
					currententity->flags = oldflags;
					currententity->alpha = oldalpha;
				}
				continue;
			}
			switch (currentmodel->type)
			{
			case mod_alias:
				if(!nomdl)
				{
					if (currententity->flags & RF_WEAPONMODEL)
					{
						if (!need_gun_optimisation || (need_gun_optimisation && gun_pass))
							R_DrawAliasModel (false);
					}
					else
					{
						if (!need_gun_optimisation || (need_gun_optimisation && !gun_pass))
							R_DrawAliasModel (false);
					}
				}
				break;
			case mod_alias_md3:
				if(!nomdl)
				{
					if (currententity->flags & RF_WEAPONMODEL)
					{
						if (!need_gun_optimisation || (need_gun_optimisation && gun_pass))
						{
							R_DrawAliasMD3Model (false, false, true);
							glDepthMask(0);		// восстановим, ибо иногда в подпрограмме включаем DepthMask
						}
					}
					else
					{
						if (!need_gun_optimisation || (need_gun_optimisation && !gun_pass))
						{
							R_DrawAliasMD3Model (false, false, true);
							glDepthMask(0);		// восстановим, ибо иногда в подпрограмме включаем DepthMask
						}
					}
				}
				break;
			case mod_brush:
				if (!need_gun_optimisation || (need_gun_optimisation && !gun_pass))
					R_DrawBrushModel ();
				break;
			case mod_sprite:
				if(!nomdl)
					if (!need_gun_optimisation || (need_gun_optimisation && !gun_pass))
					{
						R_DrawSpriteModel ();
						glDepthMask(0);		// восстановим, ибо иногда в подпрограмме включаем DepthMask
					}
				break;
			case mod_sprite2:
				if(!nomdl)
					if (!need_gun_optimisation || (need_gun_optimisation && !gun_pass))
					{
						R_DrawSprite2Model ();
						glDepthMask(0);		// восстановим, ибо иногда в подпрограмме включаем DepthMask
					}
				break;
			default:
				Com_Error (ERR_DROP, "R_DrawAmbientEntitiesOnList_Transparent: Bad modeltype");
				break;
			}
		}
		if (flag_saved)
		{
			currententity->flags = oldflags;
			currententity->alpha = oldalpha;
		}
	}
}


void R_DrawParticles ()
{
	int			i, j = 0;
	particle_t	*p;
	float		vertexArray[9 * MAX_PARTICLES];
	byte		colorArray[12 * MAX_PARTICLES];

	glEnable( GL_BLEND );
	gl_TexEnv( GL_MODULATE );

	GL_BindVBO(NULL);
	glEnableClientState(GL_COLOR_ARRAY);
	glColorPointer(4, GL_UNSIGNED_BYTE, 0, colorArray);
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	if (r_ext_pointparameters->value && glPointParameterfEXT)
	{
		glDisable( GL_TEXTURE_2D );
		glPointSize( r_particle_size->value);

		for ( i = 0, p = r_newrefdef.particles; i < r_newrefdef.num_particles; i++, p++ )
		{
			if(p->type == part_simple)
			{
				colorArray[j*4+0] = p->color_r;
				colorArray[j*4+1] = p->color_g;
				colorArray[j*4+2] = p->color_b;
				colorArray[j*4+3] = p->alpha*255;

				vertexArray[j*3+0] = p->origin[0];
				vertexArray[j*3+1] = p->origin[1];
				vertexArray[j*3+2] = p->origin[2];

				j++;
			}
		}

		glDrawArrays (GL_POINTS, 0, j);

		glEnable( GL_TEXTURE_2D );
	}
	else
	{
		float		scale;
		vec3_t		up, right;

		VectorScale (vup, 1.5, up);
		VectorScale (vright, 1.5, right);

		glEnable( GL_TEXTURE_2D );
		GL_Bind2D(r_particletexture->texnum);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_FLOAT, 0, texcoordArray_global);

		for ( i = 0, p = r_newrefdef.particles; i < r_newrefdef.num_particles; i++, p++ )
		{
			if(p->type == part_simple)
			{
				// hack a scale up to keep particles from disapearing
				scale = ( p->origin[0] - r_origin[0] ) * vpn[0] + 
						( p->origin[1] - r_origin[1] ) * vpn[1] +
						( p->origin[2] - r_origin[2] ) * vpn[2];

				if (scale < 30)
					scale = 3;
				else
					scale = 3 + scale * 0.006;

				colorArray[j*12+0] = colorArray[j*12+4] = colorArray[j*12+8] = p->color_r;
				colorArray[j*12+1] = colorArray[j*12+5] = colorArray[j*12+9] = p->color_g;
				colorArray[j*12+2] = colorArray[j*12+6] = colorArray[j*12+10] = p->color_b;
				colorArray[j*12+3] = colorArray[j*12+7] = colorArray[j*12+11] = p->alpha*255;

				vertexArray[j*9+0] = p->origin[0] - (up[0] + right[0]) * 0.3333 * scale;
				vertexArray[j*9+1] = p->origin[1] - (up[1] + right[1]) * 0.3333 * scale;
				vertexArray[j*9+2] = p->origin[2];// - (up[2] + right[2]) * 0.3333 * scale;		// приподымем партикль, чтобы он не пропадал в полу

				vertexArray[j*9+3] = vertexArray[j*9+0] + up[0]*scale;
				vertexArray[j*9+4] = vertexArray[j*9+1] + up[1]*scale;
				vertexArray[j*9+5] = vertexArray[j*9+2] + up[2]*scale;

				vertexArray[j*9+6] = vertexArray[j*9+0] + right[0]*scale;
				vertexArray[j*9+7] = vertexArray[j*9+1] + right[1]*scale;
				vertexArray[j*9+8] = vertexArray[j*9+2] + right[2]*scale;

				j++;
			}
		}

		glDrawArrays (GL_TRIANGLES, 0, 3*j);

		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);

	glDisable( GL_BLEND );
	gl_TexEnv( GL_REPLACE );
}


void MakeNormalVectors (vec3_t forward, vec3_t right, vec3_t up)
{
	float		d;

	// this rotate and negat guarantees a vector
	// not colinear with the original
	right[1] = -forward[0];
	right[2] = forward[1];
	right[0] = forward[2];

	d = DotProduct (right, forward);
	VectorMA (right, -d, forward, right);
	VectorNormalize (right);
	CrossProduct (right, forward, up);
}


void AnglesFromVecs(vec3_t start, vec3_t end, vec3_t ang)
{
	vec3_t	vec;
	VectorSubtract (end, start, vec);
	vectoangles2 (vec, ang);
}

void R_DrawSpecParticles ()
{
	int			i, loc;
	particle_t	*p;
	float		len;
	float		c, d, s, w, v;
	vec3_t		point, width, depth;
	vec3_t		move, vec, right, up, dir1, dir2, dir3, spdir;
	vec3_t		length;

	glEnable( GL_TEXTURE_2D );
	glEnable( GL_BLEND );
	gl_TexEnv( GL_MODULATE );

	for ( i = 0, p = r_newrefdef.particles; i < r_newrefdef.num_particles; i++, p++ )
	{
		if(p->type == part_sparks)
		{
			if (p->alpha<=0.1)
				continue;

			GL_Bind2D (r_particletexture->texnum);
			glColor4ub(p->color_r, p->color_g, p->color_b, p->alpha*255);

			glBegin (GL_QUADS);

				VectorCopy(p->length, depth);
				if (!VectorNormalize(depth))
					VectorCopy(vright, depth);
				VectorScale(depth, 2*p->size, depth);

				VectorSubtract(p->origin, r_origin, point);
				CrossProduct(point, p->length, width);
				if (!VectorNormalize(width))
					VectorCopy(vup, width);
				VectorScale(width, p->size, width);

				glTexCoord2f( 0, 0 );
				glVertex3f( p->origin[0] + width[0] - depth[0], p->origin[1] + width[1] - depth[1], p->origin[2] + width[2] - depth[2]);

				glTexCoord2f( 0, 0.5 );
				glVertex3f( p->origin[0] - width[0] - depth[0], p->origin[1] - width[1] - depth[1], p->origin[2] - width[2] - depth[2]);

				glTexCoord2f( 0.25, 0.5 );
				glVertex3f( p->origin[0] - width[0], p->origin[1] - width[1], p->origin[2] - width[2]);

				glTexCoord2f( 0.25, 0 );
				glVertex3f( p->origin[0] + width[0], p->origin[1] + width[1], p->origin[2] + width[2]);

				glTexCoord2f( 0.25, 0 );
				glVertex3f( p->origin[0] + width[0], p->origin[1] + width[1], p->origin[2] + width[2]);

				glTexCoord2f( 0.25, 0.5 );
				glVertex3f( p->origin[0] - width[0], p->origin[1] - width[1], p->origin[2] - width[2]);

				VectorSubtract(p->origin, r_origin, point);
				VectorAdd(point, p->length, point);
				CrossProduct(point, p->length, width);

				if (!VectorNormalize(width))
					VectorCopy(vup, width);
				VectorScale(width, p->size, width);

				glTexCoord2f(0.25, 0.5);
				glVertex3f( p->origin[0] + p->length[0] - width[0], p->origin[1] + p->length[1] - width[1], p->origin[2] + p->length[2] - width[2]);

				glTexCoord2f(0.25, 0);
				glVertex3f( p->origin[0] + p->length[0] + width[0], p->origin[1] + p->length[1] + width[1], p->origin[2] + p->length[2] + width[2]);

				glTexCoord2f(0.25, 0);
				glVertex3f( p->origin[0] + p->length[0] + width[0], p->origin[1] + p->length[1] + width[1], p->origin[2] + p->length[2] + width[2]);

				glTexCoord2f(0.25, 0.5);
				glVertex3f( p->origin[0] + p->length[0] - width[0], p->origin[1] + p->length[1] - width[1], p->origin[2] + p->length[2] - width[2]);

				glTexCoord2f(0.5, 0.5);
				glVertex3f( p->origin[0] + p->length[0] - width[0] + depth[0], p->origin[1] + p->length[1] - width[1] + depth[1], p->origin[2] + p->length[2] - width[2] + depth[2]);

				glTexCoord2f(0.5, 0);
				glVertex3f( p->origin[0] + p->length[0] + width[0] + depth[0], p->origin[1] + p->length[1] + width[1] + depth[1], p->origin[2] + p->length[2] + width[2] + depth[2]);

			glEnd ();
		}
		else if(p->type == part_tracer)
		{
			if (p->alpha<=0.1)
				continue;

			GL_Bind2D (tracer_object->texnum);
			glColor3ub(p->color_r, p->color_g, p->color_b);

			goto p1;
		}
		else if(p->type == part_plume)
		{
			if (p->alpha<=0.1)
				continue;

			GL_Bind2D (waterplume_object->texnum);
			glColor4ub(p->color_r, p->color_g, p->color_b, p->alpha*255);

p1:			VectorScale(p->length, 1.1-p->alpha, length);
			glBegin (GL_QUADS);

				VectorSubtract(p->origin, r_origin, point);
				CrossProduct(point, length, width);
				if (!VectorNormalize(width))
					VectorCopy(vup, width);
				VectorScale(width, p->size, width);

				glTexCoord2f( 0, 0 );
				glVertex3f( p->origin[0] + width[0], p->origin[1] + width[1], p->origin[2] + width[2]);

				glTexCoord2f( 0, 1 );
				glVertex3f( p->origin[0] - width[0], p->origin[1] - width[1], p->origin[2] - width[2]);

				VectorSubtract(p->origin, r_origin, point);
				VectorAdd(point, length, point);
				CrossProduct(point, length, width);
				if (!VectorNormalize(width))
					VectorCopy(vup, width);
				VectorScale(width, p->size, width);

				glTexCoord2f( 1, 1 );
				glVertex3f( p->origin[0] + length[0] - width[0], p->origin[1] + length[1] - width[1], p->origin[2] + length[2] - width[2]);

				glTexCoord2f( 1, 0 );
				glVertex3f( p->origin[0] + length[0] + width[0], p->origin[1] + length[1] + width[1], p->origin[2] + length[2] + width[2]);

			glEnd ();
		}
		else if (p->type == part_beam || p->type == part_rail_beam)
		{
/// hack: добавляем модель-спрайт для луча рельсы
if (cl_mod_beam && p->type == part_rail_beam)
{
	int			ii;
	vec3_t		angles, v_right, v_up;
	entity_t	beam;
	dsprite2_t	*psprite;

	Memset (&beam, 0, sizeof(beam));
	psprite = (dsprite2_t *)cl_mod_beam->extradata;
	beam.model = cl_mod_beam;
	VectorCopy(p->origin, beam.origin);
	VectorAdd(beam.origin, p->length, beam.oldorigin);
	beam.flags = RF_ANIM_ONE_CYCLE;
	beam.oldframe = beam.frame = (1 - p->alpha) * psprite->numframes;
	AnglesFromVecs(beam.origin, beam.oldorigin, angles);
	AngleVectors (angles, NULL, v_right, v_up);
	for (ii=0 ; ii<3 ; ii++)
		beam.origin[ii] = beam.origin[ii] - (v_right[ii] + v_up[ii])*2;
	V_AddEntity(&beam, NULL, NULL);
	r_newrefdef.num_entities++;
}

			GL_Bind2D (r_particletexture->texnum);
			glColor4ub(p->color_r, p->color_g, p->color_b, p->alpha*255);

			glBegin (GL_QUADS);

				VectorCopy(p->length, depth);
				if (!VectorNormalize(depth))
					VectorCopy(vright, depth);
				VectorScale(depth, 2*p->size, depth);

				VectorSubtract(p->origin, r_origin, point);
				CrossProduct(point, p->length, width);
				if (!VectorNormalize(width))
					VectorCopy(vup, width);
				VectorScale(width, p->size, width);

				glTexCoord2f( 0, 0 );
				glVertex3f( p->origin[0] + width[0] - depth[0], p->origin[1] + width[1] - depth[1], p->origin[2] + width[2] - depth[2]);

				glTexCoord2f( 0, 0.5 );
				glVertex3f( p->origin[0] - width[0] - depth[0], p->origin[1] - width[1] - depth[1], p->origin[2] - width[2] - depth[2]);

				glTexCoord2f( 0.25, 0.5 );
				glVertex3f( p->origin[0] - width[0], p->origin[1] - width[1], p->origin[2] - width[2]);

				glTexCoord2f( 0.25, 0 );
				glVertex3f( p->origin[0] + width[0], p->origin[1] + width[1], p->origin[2] + width[2]);

				glTexCoord2f( 0.25, 0 );
				glVertex3f( p->origin[0] + width[0], p->origin[1] + width[1], p->origin[2] + width[2]);

				glTexCoord2f( 0.25, 0.5 );
				glVertex3f( p->origin[0] - width[0], p->origin[1] - width[1], p->origin[2] - width[2]);

				VectorSubtract(p->origin, r_origin, point);
				VectorAdd(point, p->length, point);
				CrossProduct(point, p->length, width);

				if (!VectorNormalize(width))
					VectorCopy(vup, width);
				VectorScale(width, p->size, width);

				glTexCoord2f(0.25, 0.5);
				glVertex3f( p->origin[0] + p->length[0] - width[0], p->origin[1] + p->length[1] - width[1], p->origin[2] + p->length[2] - width[2]);

				glTexCoord2f(0.25, 0);
				glVertex3f( p->origin[0] + p->length[0] + width[0], p->origin[1] + p->length[1] + width[1], p->origin[2] + p->length[2] + width[2]);

				glTexCoord2f(0.25, 0);
				glVertex3f( p->origin[0] + p->length[0] + width[0], p->origin[1] + p->length[1] + width[1], p->origin[2] + p->length[2] + width[2]);

				glTexCoord2f(0.25, 0.5);
				glVertex3f( p->origin[0] + p->length[0] - width[0], p->origin[1] + p->length[1] - width[1], p->origin[2] + p->length[2] - width[2]);

				glTexCoord2f(0.5, 0.5);
				glVertex3f( p->origin[0] + p->length[0] - width[0] + depth[0], p->origin[1] + p->length[1] - width[1] + depth[1], p->origin[2] + p->length[2] - width[2] + depth[2]);

				glTexCoord2f(0.5, 0);
				glVertex3f( p->origin[0] + p->length[0] + width[0] + depth[0], p->origin[1] + p->length[1] + width[1] + depth[1], p->origin[2] + p->length[2] + width[2] + depth[2]);

			glEnd ();
		}
		else if (p->type == part_spiral)
		{
			GL_Bind2D (r_particletexture->texnum);
			glColor4ub(p->color_r, p->color_g, p->color_b, p->alpha*255);
			w = 1 + (1 - p->alpha);
			v = 2 + p->size*(1 - p->alpha);

			glBegin (GL_QUADS);

				VectorCopy(p->origin, move);
				VectorCopy(p->length, vec);
				len = VectorNormalize (vec);
				MakeNormalVectors (vec, right, up);

				for (loc = 0; loc < len; loc++)
				{
					d = loc * 0.1;
					SinCos(d, &s, &c);
///					c = cos(d);
///					s = sin(d);

					VectorScale(right, c * v, dir1);
					VectorMA (dir1, s * v, up, dir1);

					d = (loc + 1) * 0.1;
					SinCos(d, &s, &c);
///					c = cos(d);
///					s = sin(d);

					VectorScale(right, c *v, dir2);
					VectorMA (dir2, s * v, up, dir2);
					VectorAdd(dir2, vec, dir2);

					d = (loc + 2) * 0.1;
					SinCos(d, &s, &c);
///					c = cos(d);
///					s = sin(d);

					VectorScale(right, c * v, dir3);
					VectorMA (dir3, s * v, up, dir3);
					VectorMA (dir3, 2, vec, dir3);

					VectorAdd (move, dir1, point);

					VectorSubtract(dir2, dir1, spdir);

					VectorSubtract(point, r_origin, point);
					CrossProduct(point, spdir, width);
					if (!VectorNormalize(width))
						VectorCopy(vup, width);
					VectorScale(width, w, width);

					glTexCoord2f( 0.25, 0.5 );
					glVertex3f (point[0] + width[0] + r_origin[0],
								point[1] + width[1] + r_origin[1],
								point[2] + width[2] + r_origin[2]);

					glTexCoord2f( 0.25, 0 );
					glVertex3f (point[0] - width[0] + r_origin[0],
								point[1] - width[1] + r_origin[1],
								point[2] - width[2] + r_origin[2]);

					VectorAdd (move, dir2, point);
					VectorSubtract(dir3, dir2, spdir);

					VectorSubtract(point, r_origin, point);
					CrossProduct(point, spdir, width);
					if (!VectorNormalize(width))
						VectorCopy(vup, width);
					VectorScale(width, w, width);

					glTexCoord2f( 0.25, 0 );
					glVertex3f (point[0] - width[0] + r_origin[0],
								point[1] - width[1] + r_origin[1],
								point[2] - width[2] + r_origin[2]);

					glTexCoord2f( 0.25, 0.5 );
					glVertex3f (point[0] + width[0] + r_origin[0],
								point[1] + width[1] + r_origin[1],
								point[2] + width[2] + r_origin[2]);

					VectorAdd (move, vec, move);
				}

			glEnd ();
		}
		else if (p->type == part_wake)
		{
			vec3_t	axis[3];

			GL_Bind2D (waterwake_object->texnum);
			glColor4ub(p->color_r, p->color_g, p->color_b, p->alpha*255);

			// Calculate radius
			float radius = (1.1-p->alpha) * p->size;

			// Find axes
			VectorCopy(p->length, axis[0]);
			MakeNormalVectors(axis[0], axis[1], axis[2]);

			// Scale the axes by radius
			VectorScale(axis[1], radius, axis[1]);
			VectorScale(axis[2], radius, axis[2]);

			glBegin (GL_QUADS);

					glTexCoord2f( 0, 0 );
					glVertex3f (p->origin[0] + axis[1][0] + axis[2][0],
								p->origin[1] + axis[1][1] + axis[2][1],
								p->origin[2] + axis[1][2] + axis[2][2]);

					glTexCoord2f( 1, 0 );
					glVertex3f (p->origin[0] - axis[1][0] + axis[2][0],
								p->origin[1] - axis[1][1] + axis[2][1],
								p->origin[2] - axis[1][2] + axis[2][2]);

					glTexCoord2f( 1, 1 );
					glVertex3f (p->origin[0] - axis[1][0] - axis[2][0],
								p->origin[1] - axis[1][1] - axis[2][1],
								p->origin[2] - axis[1][2] - axis[2][2]);

					glTexCoord2f( 0, 1 );
					glVertex3f (p->origin[0] + axis[1][0] - axis[2][0],
								p->origin[1] + axis[1][1] - axis[2][1],
								p->origin[2] + axis[1][2] - axis[2][2]);

			glEnd ();
		}
	}

	glDisable( GL_BLEND );
///	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	gl_TexEnv( GL_REPLACE );
}



#if 1
// Berserker: для текстурных партиклей, для батч-сортинга
typedef struct
{
	particle_t	*part;
	float		scale;
	unsigned	tex;
	byte		color[4];
} tparticle_t;

unsigned	num_tparticles;
tparticle_t	tparticles[MAX_PARTICLES];
tparticle_t	*taparticles[MAX_PARTICLES];

int SurfPart( const tparticle_t **a, const tparticle_t **b )
{
	return (*a)->tex - (*b)->tex;
}

void R_DrawTexParticles ()
{
	if (!r_newrefdef.num_particles)
		return;

	int			i, cont;
	byte		color[4];
	particle_t	*p;
	tparticle_t	*tp;
	float		scale, sscale;
	unsigned	tex, oldtex = 0, vpointer = 0;
	float		vertexArray[9 * MAX_PARTICLES];
	byte		colorArray[12 * MAX_PARTICLES];

	num_tparticles = 0;
	for ( i = 0, p = r_newrefdef.particles; i < r_newrefdef.num_particles; i++, p++ )
	{
		if(p->type == part_simple)
			continue;

		color[0] = p->color_r;
		color[1] = p->color_g;
		color[2] = p->color_b;
		color[3] = p->alpha*255;

		switch(p->type)
		{
		case part_bubble:
			cont = (CL_PMpointcontents(p->origin) & MASK_WATER);
			if (cont)	// не в воздухе
			{
				scale = 4;
				tex = bubble_object->texnum;	// Пузыри
			}
			else
				continue;	// в воздухе - не рисуем пузыри!
			break;
		case part_splash:
			cont = (CL_PMpointcontents(p->origin) & MASK_WATER);
			if (!cont)	// В воздухе
			{
				scale = (2 - p->alpha) * 8;
				tex = splat_object->texnum;		// Брызги
			}
			else
				continue;	// в жидкости - не рисуем брызги!
			break;
		case part_smoke:
		case part_bigsmoke:
			cont = (CL_PMpointcontents(p->origin) & MASK_WATER);
			if (cont)
			{
				if (cont & CONTENTS_WATER)
				{
					color[0] = 170 + (rand()&31);
					color[1] = 170 + (rand()&31);
					color[2] = 200 + (rand()&31);
				}
				else if (cont & CONTENTS_LAVA)
				{
					color[0] = 200 + (rand()&31);
					color[1] = 120 + (rand()&31);
					color[2] = 120 + (rand()&31);
				}
				else if (cont & CONTENTS_SLIME)
				{
					color[0] = 120 + (rand()&31);
					color[1] = 200 + (rand()&31);
					color[2] = 120 + (rand()&31);
				}
				if(p->type == part_bigsmoke)
					scale = 18;
				else
					scale = 6;
				tex = bubble_object->texnum;		// Пузыри
			}
			else
			{
				if(p->type == part_bigsmoke)
					scale = (2 - p->alpha) * 96;
				else
					scale = (2 - p->alpha) * 32;

				tex = smoke_object->texnum;			// Дымок
			}
			break;
		case part_blood:
		case part_fast_blood:
		case part_greenblood:
			scale = (2 - p->alpha) * 16;
			tex = blood_object->texnum;				// Кровь
			break;
		case part_fly:
			cont = (CL_PMpointcontents(p->origin) & MASK_WATER);
			if (!cont)	// В воздухе
			{
				scale = 6;
				tex = fly_object->texnum;			// Мухи
			}
			else
				continue;	// в жидкости - не рисуем мух!
			break;
		case part_beam:
		case part_rail_beam:
		case part_spiral:
		case part_wake:
		case part_plume:
		case part_tracer:
		case part_sparks:
			/// special case...
			continue;		// пока не рисуем лазеры и спирали!
		default:
			scale = 4;
			tex = r_particletexture->texnum;
		}

		tparticles[num_tparticles].part = p;
		tparticles[num_tparticles].scale = scale;
		tparticles[num_tparticles].tex = tex;
		tparticles[num_tparticles].color[0] = color[0];
		tparticles[num_tparticles].color[1] = color[1];
		tparticles[num_tparticles].color[2] = color[2];
		tparticles[num_tparticles].color[3] = color[3];
		taparticles[num_tparticles] = &tparticles[num_tparticles];
		num_tparticles++;
	}

	if (!num_tparticles)
		return;

	qsort(taparticles, num_tparticles, sizeof(tparticle_t*), (int (*)(const void *, const void *))SurfPart);

	glEnable( GL_TEXTURE_2D );
	glEnable( GL_BLEND );
	gl_TexEnv( GL_MODULATE );

	GL_BindVBO(NULL);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texcoordArray_global2);
	glEnableClientState(GL_COLOR_ARRAY);
	glColorPointer(4, GL_UNSIGNED_BYTE, 0, colorArray);
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	for (i=0; i<num_tparticles; tp++, i++)
	{
		tp = taparticles[i];
		if (oldtex != tp->tex)
		{
			oldtex = tp->tex;
			if (vpointer)
			{
				glDrawArrays (GL_TRIANGLES, 0, 3*vpointer);
				vpointer = 0;
			}
			GL_Bind2D (oldtex);
		}

		colorArray[vpointer*12+0] = colorArray[vpointer*12+4] = colorArray[vpointer*12+8] = tp->color[0];
		colorArray[vpointer*12+1] = colorArray[vpointer*12+5] = colorArray[vpointer*12+9] = tp->color[1];
		colorArray[vpointer*12+2] = colorArray[vpointer*12+6] = colorArray[vpointer*12+10] = tp->color[2];
		colorArray[vpointer*12+3] = colorArray[vpointer*12+7] = colorArray[vpointer*12+11] = tp->color[3];

		sscale = -tp->scale*0.25;
		VectorMA(tp->part->origin, sscale, vup, &vertexArray[vpointer*9+0]);
		VectorMA(&vertexArray[vpointer*9+0], sscale, vright, &vertexArray[vpointer*9+0]);

		vertexArray[vpointer*9+3] = vertexArray[vpointer*9+0] + vup[0]*tp->scale;
		vertexArray[vpointer*9+4] = vertexArray[vpointer*9+1] + vup[1]*tp->scale;
		vertexArray[vpointer*9+5] = vertexArray[vpointer*9+2] + vup[2]*tp->scale;

		vertexArray[vpointer*9+6] = vertexArray[vpointer*9+0] + vright[0]*tp->scale;
		vertexArray[vpointer*9+7] = vertexArray[vpointer*9+1] + vright[1]*tp->scale;
		vertexArray[vpointer*9+8] = vertexArray[vpointer*9+2] + vright[2]*tp->scale;

		vpointer++;
	}

	if (vpointer)
		glDrawArrays (GL_TRIANGLES, 0, 3*vpointer);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);

	glDisable( GL_BLEND );
	gl_TexEnv( GL_REPLACE );
}
#else
void R_DrawTexParticles ()
{
	if (!r_newrefdef.num_particles)
		return;

	particle_t	*p;
	int			i, cont, j = 0;
	float		scale, sscale;
	byte		color[4];
	float		vertexArray[9 * MAX_PARTICLES];
	byte		colorArray[12 * MAX_PARTICLES];
	unsigned	tex, texture = -1;

	glEnable( GL_TEXTURE_2D );
	glEnable( GL_BLEND );
	gl_TexEnv( GL_MODULATE );

	GL_BindVBO(NULL);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texcoordArray_global2);
	glEnableClientState(GL_COLOR_ARRAY);
	glColorPointer(4, GL_UNSIGNED_BYTE, 0, colorArray);
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	for ( i = 0, p = r_newrefdef.particles; i < r_newrefdef.num_particles; i++, p++ )
	{
		if(p->type != part_simple)
		{
			color[0] = p->color_r;
			color[1] = p->color_g;
			color[2] = p->color_b;
			color[3] = p->alpha*255;

			switch(p->type)
			{
			case part_bubble:
				cont = (CL_PMpointcontents(p->origin) & MASK_WATER);
				if (cont)	// не в воздухе
				{
					scale = 4;
					tex = bubble_object->texnum;	// Пузыри
				}
				else
					continue;	// в воздухе - не рисуем пузыри!
				break;
			case part_splash:
				cont = (CL_PMpointcontents(p->origin) & MASK_WATER);
				if (!cont)	// В воздухе
				{
					scale = (2 - p->alpha) * 8;
					tex = splat_object->texnum;		// Брызги
				}
				else
					continue;	// в жидкости - не рисуем брызги!
				break;
			case part_smoke:
			case part_bigsmoke:
				cont = (CL_PMpointcontents(p->origin) & MASK_WATER);
				if (cont)
				{
					if (cont & CONTENTS_WATER)
					{
						color[0] = 170 + (rand()&31);
						color[1] = 170 + (rand()&31);
						color[2] = 200 + (rand()&31);
					}
					else if (cont & CONTENTS_LAVA)
					{
						color[0] = 200 + (rand()&31);
						color[1] = 120 + (rand()&31);
						color[2] = 120 + (rand()&31);
					}
					else if (cont & CONTENTS_SLIME)
					{
						color[0] = 120 + (rand()&31);
						color[1] = 200 + (rand()&31);
						color[2] = 120 + (rand()&31);
					}
					if(p->type == part_bigsmoke)
						scale = 18;
					else
						scale = 6;
					tex = bubble_object->texnum;		// Пузыри
				}
				else
				{
					if(p->type == part_bigsmoke)
						scale = (2 - p->alpha) * 96;
					else
						scale = (2 - p->alpha) * 32;

					tex = smoke_object->texnum;			// Дымок
				}
				break;
			case part_blood:
			case part_fast_blood:
			case part_greenblood:
				scale = (2 - p->alpha) * 16;
				tex = blood_object->texnum;				// Кровь
				break;
			case part_fly:
				cont = (CL_PMpointcontents(p->origin) & MASK_WATER);
				if (!cont)	// В воздухе
				{
					scale = 6;
					tex = fly_object->texnum;			// Мухи
				}
				else
					continue;	// в жидкости - не рисуем мух!
				break;
			case part_beam:
			case part_rail_beam:
			case part_spiral:
			case part_wake:
			case part_plume:
			case part_tracer:
			case part_sparks:
				/// special case...
				continue;		// пока не рисуем лазеры и спирали!
			default:
				scale = 4;
				tex = r_particletexture->texnum;
			}

			if (texture != tex)
			{
				if (j)
					glDrawArrays (GL_TRIANGLES, 0, 3*j);

				texture = tex;
				j = 0;
				GL_Bind2D (tex);
			}

			colorArray[j*12+0] = colorArray[j*12+4] = colorArray[j*12+8] = color[0];
			colorArray[j*12+1] = colorArray[j*12+5] = colorArray[j*12+9] = color[1];
			colorArray[j*12+2] = colorArray[j*12+6] = colorArray[j*12+10] = color[2];
			colorArray[j*12+3] = colorArray[j*12+7] = colorArray[j*12+11] = color[3];

			sscale = -scale/4;
			VectorMA(p->origin, sscale, vup, &vertexArray[j*9+0]);
			VectorMA(&vertexArray[j*9+0], sscale, vright, &vertexArray[j*9+0]);

			vertexArray[j*9+3] = vertexArray[j*9+0] + vup[0]*scale;
			vertexArray[j*9+4] = vertexArray[j*9+1] + vup[1]*scale;
			vertexArray[j*9+5] = vertexArray[j*9+2] + vup[2]*scale;

			vertexArray[j*9+6] = vertexArray[j*9+0] + vright[0]*scale;
			vertexArray[j*9+7] = vertexArray[j*9+1] + vright[1]*scale;
			vertexArray[j*9+8] = vertexArray[j*9+2] + vright[2]*scale;

			j++;
		}
	}

	if (j)
		glDrawArrays (GL_TRIANGLES, 0, 3*j);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);

	glDisable( GL_BLEND );
	gl_TexEnv( GL_REPLACE );
}
#endif



/*
================
R_DrawAlphaSurfaces

Draw water surfaces and windows.
The BSP tree is waled front to back, so unwinding the chain
of alpha_surfaces will draw back to front, giving proper ordering.
================
*/
void R_DrawAlphaSurfaces ()
{
	msurface_t	*s;
	float		intens;

	//
	// go back to the world matrix
	//
	glLoadMatrixf (r_world_matrix);

	glEnable (GL_BLEND);

	if(vid_ignorehwgamma->value)
		intens = gl_state.inverse_intensity;
	else
		intens = 0.5;

	for (s=r_alpha_surfaces ; s ; s=s->texturechain)
	{
		if (((s->texinfo->flags & SURF_TRANS33) && (s->texinfo->flags & SURF_TRANS66)) || (s->texinfo->flags & SURF_NODRAW))	/// nodraw	/// Berserker: вообще не рисуем полигоны NODRAW
			continue;	/// Не рисуем внешней границы тумана

		c_brush_polys++;

		if (s->texinfo->flags & SURF_TRANS33)
			GL_Color4f (intens, intens, intens, 0.33);
		else if (s->texinfo->flags & SURF_TRANS66)
			GL_Color4f (intens, intens, intens, 0.66);
		else
			GL_Color3f (intens, intens, intens);

		if(s->flags & SURF_TRANSBRUSH)
		{
			glLoadMatrixf (&r_mvm_cache[s->mvm_cache_idx][0]);
			modelorg[0] = r_mvm_cache[s->mvm_cache_idx][16];
			modelorg[1] = r_mvm_cache[s->mvm_cache_idx][17];
			modelorg[2] = r_mvm_cache[s->mvm_cache_idx][18];
		}
		else
			VectorCopy(r_origin, modelorg);

		if (s->flags & SURF_DRAWTURB)
			EmitWaterPolys (s, s->texinfo->image->texnum, false);	/// Рисуем воду
		else
		{
			GL_Bind2D(s->texinfo->image->texnum);
			DrawGLPoly (s, modelorg, false);						/// Рисуем окно
		}

		if(s->flags & SURF_TRANSBRUSH)
		{
			glLoadMatrixf (r_world_matrix);
			s->flags &= ~SURF_TRANSBRUSH;
		}

	}

	gl_TexEnv( GL_REPLACE );
	GL_Color3f (1,1,1);
	glDisable (GL_BLEND);

	r_alpha_surfaces = NULL;
	transbrush = 0;
}


// Возвращает степень затуманивания, использует r_origin
inline float FogDist (fog_t *fog, float *v)
{
	float	dist_all, z_fog, z_all;
	vec3_t	temp;

	if (fog->ed)
	{
		if (fog->origin[2] - v[2] <= 1.0)
			return 0;					// вертекс вне тумана, непорядок - туман = NULL
	}
	else
	{
		if (v[2] > fog->origin[2])
			return 0;					// вертекс вне тумана, непорядок - туман = NULL
	}

	VectorSubtract(r_origin, v, temp);
	dist_all = Sqrt(temp[0]*temp[0]+temp[1]*temp[1]+temp[2]*temp[2]) * fog->density;
	if (dist_all>1)
		dist_all = 1;

	if (r_origin[2] < fog->origin[2])
		return dist_all;			// камера внутри тумана...

	z_all = r_origin[2] - v[2];
	z_fog = fog->origin[2] - v[2];
	return dist_all * z_fog / z_all;// камера вне тумана...
}


void R_DrawCausticsSurfaces ()
{
	msurface_t	*s;
	int			i;
	float		*v;
	glpoly_t	*bp;

	if(r_caustics->value)
	{
		//
		// go back to the world matrix
		//
		glLoadMatrixf (r_world_matrix);

		glEnable(GL_POLYGON_OFFSET_FILL);
		glEnable (GL_BLEND);
		GL_BlendFunc (GL_DST_COLOR, GL_ONE);
		GL_MBind2D( GL_TEXTURE0, r_caustic[((int)(r_newrefdef.time*20)) & (MAX_CAUSTICS-1)]->texnum );

		GLfloat sPlane[4] = {0.008f, 0.004f, 0.0f, 0.1f*r_newrefdef.time };
		GLfloat tPlane[4] = {0.0f, 0.008f, 0.004f, 0.0f };
		glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
		glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
		glTexGenfv(GL_S, GL_OBJECT_PLANE, sPlane);
		glTexGenfv(GL_T, GL_OBJECT_PLANE, tPlane);
		glEnable(GL_TEXTURE_GEN_S);
		glEnable(GL_TEXTURE_GEN_T);

		for (s=r_uw_surfaces ; s ; s=s->uwchain)
		{
			if(s->flags & SURF_CAUSTICBRUSH)
				glLoadMatrixf (&r_mvm_cache3[s->mvm_cache_idx3][0]);

			for (bp=s->polys ; bp ; bp=bp->next)
			{
				glBegin (GL_TRIANGLE_FAN);
				for (i=0,v=bp->verts[0] ; i<bp->numverts ; i++, v+=VERTEXSIZE)
					glVertex3fv (v);
				glEnd ();
			}

			if(s->flags & SURF_CAUSTICBRUSH)
				glLoadMatrixf (r_world_matrix);
		}

		glDisable(GL_TEXTURE_GEN_S);
		glDisable(GL_TEXTURE_GEN_T);

		r_uw_surfaces = NULL;
		causticbrush = 0;
		glDisable (GL_BLEND);
		GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDisable(GL_POLYGON_OFFSET_FILL);
	}
}


bool R_AddFogPolyVA( glpoly_t *p, unsigned *Base, unsigned *idxBase, fog_t *fog)
{
	unsigned	BaseVerts, BaseIdx;
	int		i, nv = p->numverts;
	BaseVerts = *Base;
	if (BaseVerts + nv > MAX_BATCH_SURFS)
		return false;	// force the start new batch
	BaseIdx = *idxBase;
	float	*v, mmm;
	vec3_t	color;

	color[0] = fog->color[0];
	color[1] = fog->color[1];
	color[2] = fog->color[2];

	// create indexes
	if (BaseIdx == 0xffffffff)	BaseIdx = 0;
	for (i=0; i<nv-2; i++)
	{
		indexArray[BaseIdx++] = BaseVerts;
		indexArray[BaseIdx++] = BaseVerts + i + 1;
		indexArray[BaseIdx++] = BaseVerts + i + 2;
	}

	for (i=0, v=p->verts[0] ; i < nv; i++, v+= VERTEXSIZE, BaseVerts++)
	{
		mmm = FogDist(fog, v);

		VectorCopy(v, wVertexArray[BaseVerts]);

		nTexArray[BaseVerts][0] = color[0]*mmm;
		nTexArray[BaseVerts][1] = color[1]*mmm;
		nTexArray[BaseVerts][2] = color[2]*mmm;
	}

	*Base = BaseVerts;
	*idxBase = BaseIdx;
	return true;
}


void R_DrawFogSurfaces ()
{
	msurface_t	*s;
	glpoly_t	*bp;

	GL_BindVBO(NULL);
	glEnableClientState(GL_COLOR_ARRAY);
	glColorPointer(3, GL_FLOAT, 0, nTexArray);
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, wVertexArray);

	unsigned	vertCounter = 0, idxCounter = 0xffffffff;

	for (s=r_fog_surfaces ; s ; s=s->fogchain)
	{
		for (bp=s->polys ; bp ; bp=bp->next)
		{
repeat:		if (!R_AddFogPolyVA(bp, &vertCounter, &idxCounter, s->fog))
			{	// draw old batch
				c_world_batches_lighted++;
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///				else
					glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);

				vertCounter = 0;
				idxCounter = 0xffffffff;
				goto repeat;
			}
		}
	}

	if (idxCounter != 0xffffffff)
	{
		c_world_batches_lighted++;
///		if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///			glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///		else
			glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
	}

	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
	GL_ClearCache(GL_COLOR_);	// reset cache (glColorPointer used...)

	r_fog_surfaces = NULL;
}


// gun frame debugging functions
void V_Gun_Next_f ()
{
	gun_frame++;
	Com_Printf ("frame %i\n", gun_frame);
}


void V_Gun_Prev_f ()
{
	gun_frame--;
	if (gun_frame < 0)
		gun_frame = 0;
	Com_Printf ("frame %i\n", gun_frame);
}


void V_Gun_Model_f ()
{
	char	name[MAX_QPATH];

	if (Cmd_Argc() != 2)
	{
		gun_model = NULL;
		return;
	}
	Com_sprintf (name, sizeof(name), "models/%s/tris.md2", Cmd_Argv(1));
	gun_model = R_RegisterModel (name, 1, false);
}


void R_PolyBlend ()
{
///	if (!r_polyblend->value)
///		return;
	if (!cl_add_blend->value)
		return;
	if (!v_blend[3])
		return;
	if (cl_thirdPerson->value)
		return;

	glDisable (GL_ALPHA_TEST);
	glEnable (GL_BLEND);
	glDisable (GL_DEPTH_TEST);
	glDisable (GL_TEXTURE_2D);

	glLoadIdentity ();

	// FIXME: get rid of these
	glRotatef (-90,  1, 0, 0);	    // put Z going up
	glRotatef (90,  0, 0, 1);	    // put Z going up

	GL_Color4fv (v_blend);

	glBegin (GL_QUADS);
		glVertex3f (10, 100, 100);
		glVertex3f (10, -100, 100);
		glVertex3f (10, -100, -100);
		glVertex3f (10, 100, -100);
	glEnd ();

	glDisable (GL_BLEND);
	glEnable (GL_TEXTURE_2D);
	glEnable (GL_ALPHA_TEST);

	GL_Color3f(1,1,1);
}


void R_RenderDlight (dlight_t *light)
{
	int		i, j;
	float	a;
	vec3_t	v;

	float	rad = light->radius * 0.35;
	GL_Color3f (light->color[0]*0.1, light->color[1]*0.1, light->color[2]*0.1);
	VectorSubtract (light->origin, r_origin, v);

	glBegin (GL_TRIANGLE_FAN);
	for (i=0 ; i<3 ; i++)
		v[i] = light->origin[i] - vpn[i]*rad;
	glVertex3fv (v);
	GL_Color3f (0,0,0);
	for (i=16 ; i>=0 ; i--)
	{
		float s, c;
		a = i/16.0 * M_PI*2;
		SinCos(a, &s, &c);
		for (j=0 ; j<3 ; j++)
			v[j] = light->origin[j] + (vright[j]*c + vup[j]*s)*rad;
///			v[j] = light->origin[j] + (vright[j]*cos(a) + vup[j]*sin(a))*rad;
		glVertex3fv (v);
	}
	glEnd ();
}


void R_RenderDlights ()			/// Рисуем все dynamic light flares...
{
	int		i;
	dlight_t	*l;

	if (!r_flashblend->value)
		return;

///	r_dlightframecount = r_framecount + 1;	// because the count hasn't
											//  advanced yet for this frame
	glDisable (GL_TEXTURE_2D);
	glEnable (GL_BLEND);
	GL_BlendFunc (GL_ONE, GL_ONE);

	l = r_newrefdef.dlights;
	for (i=0 ; i<r_newrefdef.num_dlights ; i++, l++)
		R_RenderDlight (l);

	GL_Color3f (1,1,1);
	glDisable (GL_BLEND);
	glEnable (GL_TEXTURE_2D);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


void R_RenderSlight()
{
	int		i, j;
	float	a;
	vec3_t	v;

	if(shadow_used && !light_used && (cl.time & 64))
		GL_Color3f (currentshadowlight->color[0]*0.25, currentshadowlight->color[1]*0.25, currentshadowlight->color[2]*0.25);
	else
		GL_Color3f (currentshadowlight->color[0]*0.5, currentshadowlight->color[1]*0.5, currentshadowlight->color[2]*0.5);

	VectorSubtract (currentshadowlight->origin, r_origin, v);

	glBegin (GL_TRIANGLE_FAN);
	for (i=0 ; i<3 ; i++)
		v[i] = currentshadowlight->origin[i] - vpn[i]*currentshadowlight->radiuses[i];
	glVertex3fv (v);
	GL_Color3f (0,0,0);
	for (i=16 ; i>=0 ; i--)
	{
		float s, c;
		a = i/16.0 * M_PI*2;
		SinCos(a, &s, &c);
		for (j=0 ; j<3 ; j++)
			v[j] = currentshadowlight->origin[j] + (vright[j]*c + vup[j]*s)*currentshadowlight->radiuses[j];
///			v[j] = currentshadowlight->origin[j] + (vright[j]*cos(a) + vup[j]*sin(a))*currentshadowlight->radiuses[j];
		glVertex3fv (v);
	}
	glEnd ();
}

void R_RenderCurrentLight ()
{
	if (r_showtris->value < 3)
		return;

	glDisable (GL_TEXTURE_2D);
	glDisable (GL_DEPTH_TEST);
	glEnable (GL_BLEND);
	GL_BlendFunc (GL_ONE, GL_ONE);
	glDepthMask(0);

	R_RenderSlight();

	// draw light center
	vec3_t	v[8];
	VectorSet(v[0], currentshadowlight->origin[0]-2, currentshadowlight->origin[1]-2, currentshadowlight->origin[2]-2);
	VectorSet(v[1], currentshadowlight->origin[0]-2, currentshadowlight->origin[1]-2, currentshadowlight->origin[2]+2);
	VectorSet(v[2], currentshadowlight->origin[0]-2, currentshadowlight->origin[1]+2, currentshadowlight->origin[2]-2);
	VectorSet(v[3], currentshadowlight->origin[0]-2, currentshadowlight->origin[1]+2, currentshadowlight->origin[2]+2);
	VectorSet(v[4], currentshadowlight->origin[0]+2, currentshadowlight->origin[1]-2, currentshadowlight->origin[2]-2);
	VectorSet(v[5], currentshadowlight->origin[0]+2, currentshadowlight->origin[1]-2, currentshadowlight->origin[2]+2);
	VectorSet(v[6], currentshadowlight->origin[0]+2, currentshadowlight->origin[1]+2, currentshadowlight->origin[2]-2);
	VectorSet(v[7], currentshadowlight->origin[0]+2, currentshadowlight->origin[1]+2, currentshadowlight->origin[2]+2);

	if(shadow_used && !light_used && (cl.time & 64))
		GL_Color3f (currentshadowlight->color[0]*0.25, currentshadowlight->color[1]*0.25, currentshadowlight->color[2]*0.25);
	else
		GL_Color3f (currentshadowlight->color[0]*0.5, currentshadowlight->color[1]*0.5, currentshadowlight->color[2]*0.5);

	glBegin(GL_LINE_LOOP);
	glVertex3fv(v[0]);
	glVertex3fv(v[2]);
	glVertex3fv(v[3]);
	glVertex3fv(v[1]);
	glEnd();
	glBegin(GL_LINE_LOOP);
	glVertex3fv(v[4]);
	glVertex3fv(v[6]);
	glVertex3fv(v[7]);
	glVertex3fv(v[5]);
	glEnd();
	glBegin(GL_LINES);
	glVertex3fv(v[0]);
	glVertex3fv(v[4]);
	glVertex3fv(v[1]);
	glVertex3fv(v[5]);
	glVertex3fv(v[2]);
	glVertex3fv(v[6]);
	glVertex3fv(v[3]);
	glVertex3fv(v[7]);
	glVertex3fv(v[3]);
	glVertex3fv(v[4]);
	glVertex3fv(v[1]);
	glVertex3fv(v[6]);
	glVertex3fv(v[0]);
	glVertex3fv(v[7]);
	glVertex3fv(v[2]);
	glVertex3fv(v[5]);
	glEnd();

	glDepthMask(1);
	GL_Color3f(1,1,1);
	glDisable (GL_BLEND);
	glEnable (GL_TEXTURE_2D);
	glEnable (GL_DEPTH_TEST);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
/*
void R_MarkLights (dlight_t *light, int bit, mnode_t *node)
{
	cplane_t	*splitplane;
	float		dist;
	msurface_t	*surf;
	int			i;

	if (node->contents != -1)
		return;

	splitplane = node->plane;
	dist = DotProduct (light->origin, splitplane->normal) - splitplane->dist;

	if (dist > light->radius - DLIGHT_CUTOFF)
	{
		R_MarkLights (light, bit, node->children[0]);
		return;
	}
	if (dist < -light->radius + DLIGHT_CUTOFF)
	{
		R_MarkLights (light, bit, node->children[1]);
		return;
	}

// mark the polygons
	surf = r_worldmodel->surfaces + node->firstsurface;
	for (i=0 ; i<node->numsurfaces ; i++, surf++)
	{
		if (surf->dlightframe != r_dlightframecount)
		{
			surf->dlightbits = 0;
			surf->dlightframe = r_dlightframecount;
		}
		surf->dlightbits |= bit;
	}

	R_MarkLights (light, bit, node->children[0]);
	R_MarkLights (light, bit, node->children[1]);
}


void R_PushDlights ()
{
	int			i;
	dlight_t	*l;

	r_dlightframecount = r_framecount + 1;	// because the count hasn't
											//  advanced yet for this frame
	l = r_newrefdef.dlights;
	for (i=0 ; i<r_newrefdef.num_dlights ; i++, l++)
		R_MarkLights ( l, 1<<i, r_worldmodel->nodes );
}
*/

void R_SpawnDlight(dlight_t *dl)
{
	int				i;
	shadowlight_t	*l;

	l = AllocShadowLight();
	if (!l)
		return;

	VectorCopy(dl->color, l->color);
	VectorCopy(dl->origin, l->origin);
	l->radius = l->radiuses[0] = l->radiuses[1] = l->radiuses[2] = dl->radius;	// dlight всегда шаровидный
	l->_cone = dl->_cone;
	l->style = dl->style;
	l->cl_style = dl->cl_style;
	l->flareLight = false;
	l->filtercube_start = dl->filtercube_start;
	l->filtercube_end = dl->filtercube_end;
	l->framerate = dl->framerate;
	VectorCopy(dl->angles, l->angles);
	VectorClear(l->rspeed);	/// fixme: VectorCopy(dl->rspeed, l->rspeed);
	l->isStatic = false;
	l->ownerkey = dl->ownerkey;
	l->sh_vbo_id = NULL;

	for (i=0 ; i<3 ; i++)
	{
		l->mins[i] = l->mins_cone[i] = l->origin[i] - l->radius;
		l->maxs[i] = l->maxs_cone[i] = l->origin[i] + l->radius;
	}

	l->sphere = true;
	l->nobump = l->noshadow = l->noshadow2 = false;
///	l->use = true;
}


/*
=============
R_MarkDLights

Adds dynamic lights to the shadow light list
=============
*/
void R_AddDlights ()
{
	int		i;
	dlight_t	*l;

	l = r_newrefdef.dlights;
	for (i=0 ; i<r_newrefdef.num_dlights ; i++, l++)
	{
		if (r_mirror && l->render_to == rend_no_mirror)
			continue;
		if (!r_mirror && l->render_to == rend_mirror)
			continue;
		R_SpawnDlight(l);
	}
}


/*
=============
R_ProjectSphere

Returns the rectangle the sphere will be in when it is drawn.
FIXME: This is crappy code we draw a "sprite" and project those points
it should be possible to analytically derive a eq.
=============
*/
void R_ProjectSphere (shadowlight_t *light, int *rect)
{
	int		i, j;
	float	a;
	vec3_t	v;
	vec3_t	mrad;
	float minx, maxx, miny, maxy;
	float px, py;///, pz;

	for (j=0 ; j<3 ; j++)
		mrad[j] = 2*vpn[j]*light->radius;

	minx = 9999999;
	miny = 9999999;
	maxx = -9999999;
	maxy = -9999999;

	for (i=0 ; i<4 ; i++)
	{
		float s, c;
		a = (float)i * M_PI * 0.5;
		SinCos(a, &s, &c);
		for (j=0 ; j<3 ; j++)
			v[j] = light->origin[j] + (vright[j]*c + vup[j]*s + vpn[j])*light->radius;
///			v[j] = light->origin[j] + (vright[j]*cos(a) + vup[j]*sin(a) + vpn[j])*light->radius;

		gluProject(v[0], v[1], v[2], r_world_matrix, r_project_matrix, (int *) r_viewport, &px, &py);///, &pz);

		if (px > maxx) maxx = px;
		if (px < minx) minx = px;
		if (py > maxy) maxy = py;
		if (py < miny) miny = py;

		for (j=0 ; j<3 ; j++)
			v[j] -= mrad[j];

		gluProject(v[0], v[1], v[2], r_world_matrix, r_project_matrix, (int *) r_viewport, &px, &py);///, &pz);

		if (px > maxx) maxx = px;
		if (px < minx) minx = px;
		if (py > maxy) maxy = py;
		if (py < miny) miny = py;
	}

	rect[0] = (int)minx;
	rect[1] = (int)miny;
	rect[2] = (int)maxx;
	rect[3] = (int)maxy;	
}


/*
=============
HasSharedLeaves

Returns true if both vis arrays have shared leafs visible.
FIXME: compare bytes at a time (what does quake fill the unused bits with??)
=============
*/
/*
bool HasSharedLeafs(byte *v1, byte *v2)
{
#if 0
	int i, j, k;
	for (i=0 ; i<r_worldmodel->numleafs ; i++)
	{
		k = i>>3;
		j = 1<<(i&7);
		if (v1[k] & j)
			if (v2[k] & j)
				return true;
	}

	return false;
#else
	int numleafs__ = r_worldmodel->numleafs;
	_asm
	{
		mov esi, v1
		mov edi, v2
		sub edx, edx
l1:		mov eax, edx
		mov ecx, edx
		mov ebx, 1
		and ecx, 7
		shr eax, 3
		shl ebx, cl
		test byte ptr [esi+eax], bl
		jz short l2
		test byte ptr [edi+eax], bl
		jnz short l3
l2:		inc edx
		cmp edx, numleafs__
		jc short l1
	}
	return false;
	_asm
	{
l3:
	}
	return true;
#endif
}
*/

/// from Tenebrae, asm by Berserker
bool HasSharedLeafs(byte *v1, byte *v2)
{
#if 0
	int i, j, k, l;
	l = r_worldmodel->numleafs>>5;
	for (i=0 ; i<l ; i++)
		if (((long *)v1)[i] & ((long *)v2)[i])
			return true;

	l = (r_worldmodel->numleafs & ~0x1f);
	for (i=l ; i<r_worldmodel->numleafs ; i++)
	{
		k = i>>3;
		j = 1<<(i&7);
		if (v1[k] & j)
			if (v2[k] & j)
				return true;
	}

	return false;
#else
	int numleafs__ = r_worldmodel->numleafs;
	_asm
	{
		mov edx, numleafs__
		mov esi, v1
		mov edi, v2
		shr edx, 5
		jz short l4
l0:		mov eax, [esi]
		add esi, 4
		test eax, [edi]
		jnz short l3
		add edi, 4
		dec edx
		jnz short l0
l4:		mov edx, numleafs__
		mov esi, v1
		and edx, ~0x1f
		mov edi, v2
		cmp edx, numleafs__
		jz short l5
l1:		mov eax, edx
		mov ecx, edx
		mov ebx, 1
		and ecx, 7
		shr eax, 3
		shl ebx, cl
		test byte ptr [esi+eax], bl
		jz short l2
		test byte ptr [edi+eax], bl
		jnz short l3
l2:		inc edx
		cmp edx, numleafs__
		jc short l1
	}
l5:	return false;
	_asm
	{
l3:
	}
	return true;
#endif
}


void IntersectRayPlane(vec3_t v1, vec3_t v2, cplane_t *plane, vec3_t res)
{
	vec3_t	v;
	float	sect;

	VectorSubtract (v1, v2, v);
	sect = -(DotProduct (plane->normal, v1)-plane->dist) / DotProduct (plane->normal, v);
	VectorScale (v,sect,v);
	VectorAdd (v1, v, res);
}


void Mat_Mul_1x4_4x4(vec4_t a, vec4_t result)
{
	int	index_j,	// column index
		index_k;	// row index
	float sum;		// temp used to hold sum of products

	// loop thru columns of b
	for (index_j=0; index_j<4; index_j++)
    {
	    // multiply ith row of a by jth column of b and store the sum
		// of products in the position i,j of result
	    sum = 0;
	    for (index_k=0; index_k<4; index_k++)
		    sum += a[index_k]*clip[index_k*4+index_j];
	    // store result
	    result[index_j] = sum;
    }
}


void addPoint(vec3_t v1, int *rect)
{
	vec4_t	point, res;
	float	px, py, tmp;

	point[0] = v1[0];
	point[1] = v1[1];
	point[2] = v1[2];
	point[3] = 1;
	Mat_Mul_1x4_4x4(point, res);

	tmp = 1.0/res[3];
	px = (res[0]*tmp+1.0) * 0.5;
	py = (res[1]*tmp+1.0) * 0.5;

	px = px * r_viewport[2] + r_viewport[0];
	py = py * r_viewport[3] + r_viewport[1];

	if (px > rect[2]) rect[2] = (int)px;
	if (px < rect[0]) rect[0] = (int)px;
	if (py > rect[3]) rect[3] = (int)py;
	if (py < rect[1]) rect[1] = (int)py;
}


void addEdge(vec3_t v1, vec3_t v2, int *rect)
{
	vec3_t		intersect, v1_, v2_;
	cplane_t	plane;
	bool		side1, side2;

	VectorCopy(v1,v1_);
	VectorCopy(v2,v2_);
	VectorCopy(vpn, plane.normal);
	plane.dist = DotProduct(r_origin, vpn) + 5.0;

	//Check edge to frustrum near plane
	side1 = ((DotProduct(plane.normal, v1_) - plane.dist) >= 0);
	side2 = ((DotProduct(plane.normal, v2_) - plane.dist) >= 0);

	if (!side1 && !side2)
		return; //edge behind near plane

	if (!side1 || !side2)
		IntersectRayPlane(v1_,v2_,&plane,intersect);

	if (!side1)
		VectorCopy(intersect,v1_);
	else if (!side2)
		VectorCopy(intersect,v2_);

	addPoint(v1_, rect);
	addPoint(v2_, rect);
}


/*
=============
boxScreenSpaceRect

Returns the screen space rectangle taken by the box.
(Clips the box to the near plane to have correct results even if the box intersects the near plane)
=============
*/
/*
///Оригинальный код из Tenebrae...
void boxScreenSpaceRect(shadowlight_t *light, int *rect)
{
	vec3_t v1, v2, mins, maxs;

	VectorCopy(light->mins, mins);
	VectorCopy(light->maxs, maxs);

	rect[0] = 9999999;
	rect[1] = 9999999;
	rect[2] = -9999999;
	rect[3] = -9999999;

	VectorSet(v1, maxs[0],maxs[1],maxs[2]);
	VectorSet(v2, mins[0],maxs[1],maxs[2]);
	addEdge(v1, v2, rect);

	VectorSet(v1, maxs[0],maxs[1],maxs[2]);
	VectorSet(v2, maxs[0],mins[1],maxs[2]);
	addEdge(v1, v2, rect);

	VectorSet(v1, mins[0],mins[1],maxs[2]);
	VectorSet(v2, mins[0],maxs[1],maxs[2]);
	addEdge(v1, v2, rect);

	VectorSet(v1, mins[0],mins[1],maxs[2]);
	VectorSet(v2, maxs[0],mins[1],maxs[2]);
	addEdge(v1, v2, rect);

	//Bottom plane

	VectorSet(v1, maxs[0],maxs[1],mins[2]);
	VectorSet(v2, mins[0],maxs[1],mins[2]);
	addEdge(v1, v2, rect);

	VectorSet(v1, maxs[0],maxs[1],mins[2]);
	VectorSet(v2, maxs[0],mins[1],mins[2]);
	addEdge(v1, v2, rect);

	VectorSet(v1, mins[0],mins[1],mins[2]);
	VectorSet(v2, mins[0],maxs[1],mins[2]);
	addEdge(v1, v2, rect);

	VectorSet(v1, mins[0],mins[1],mins[2]);
	VectorSet(v2, maxs[0],mins[1],mins[2]);
	addEdge(v1, v2, rect);

	//Sides

	VectorSet(v1, mins[0],maxs[1],mins[2]);
	VectorSet(v2, mins[0],maxs[1],maxs[2]);
	addEdge(v1, v2, rect);

	VectorSet(v1, maxs[0],maxs[1],mins[2]);
	VectorSet(v2, maxs[0],maxs[1],maxs[2]);
	addEdge(v1, v2, rect);

	VectorSet(v1, mins[0],mins[1],mins[2]);
	VectorSet(v2, mins[0],mins[1],maxs[2]);
	addEdge(v1, v2, rect);

	VectorSet(v1, maxs[0],mins[1],mins[2]);
	VectorSet(v2, maxs[0],mins[1],maxs[2]);
	addEdge(v1, v2, rect);
}
*/
/// Оптимизировано...
void boxScreenSpaceRect(shadowlight_t *light, int *rect)
{
	int		i;
	vec3_t	aaa,iaa,aia,iia,aai,iai,aii,iii;
	float	mins[3], maxs[3];

	if (!light->sphere)
	{
		for (i=0; i<3; i++)
		{
			mins[i] = light->origin[i] - light->radius;
			maxs[i] = light->origin[i] + light->radius;
		}
		VectorSet(aaa, maxs[0], maxs[1], maxs[2]);
		VectorSet(iaa, mins[0], maxs[1], maxs[2]);
		VectorSet(aia, maxs[0], mins[1], maxs[2]);
		VectorSet(iia, mins[0], mins[1], maxs[2]);
		VectorSet(aai, maxs[0], maxs[1], mins[2]);
		VectorSet(iai, mins[0], maxs[1], mins[2]);
		VectorSet(aii, maxs[0], mins[1], mins[2]);
		VectorSet(iii, mins[0], mins[1], mins[2]);
	}
	else	// этот код дает артефакты для не-сфер, поэтому scissor строим по полному объему сферы
	{
		VectorSet(aaa, light->maxs[0], light->maxs[1], light->maxs[2]);
		VectorSet(iaa, light->mins[0], light->maxs[1], light->maxs[2]);
		VectorSet(aia, light->maxs[0], light->mins[1], light->maxs[2]);
		VectorSet(iia, light->mins[0], light->mins[1], light->maxs[2]);
		VectorSet(aai, light->maxs[0], light->maxs[1], light->mins[2]);
		VectorSet(iai, light->mins[0], light->maxs[1], light->mins[2]);
		VectorSet(aii, light->maxs[0], light->mins[1], light->mins[2]);
		VectorSet(iii, light->mins[0], light->mins[1], light->mins[2]);
	}

	rect[0] = 9999999;
	rect[1] = 9999999;
	rect[2] = -9999999;
	rect[3] = -9999999;

	addEdge(aaa, iaa, rect);
	addEdge(aaa, aia, rect);
	addEdge(iia, iaa, rect);
	addEdge(iia, aia, rect);
	addEdge(aai, iai, rect);
	addEdge(aai, aii, rect);
	addEdge(iii, iai, rect);
	addEdge(iii, aii, rect);
	addEdge(iai, iaa, rect);
	addEdge(aai, aaa, rect);
	addEdge(iii, iia, rect);
	addEdge(aii, aia, rect);
}

inline int	CM_NumClusters ()
{
	return numclusters;
}


/*
===============
R_MarkLightLeaves

Marks nodes from the light, this is used for
gross culling during svbsp creation.
===============
*/
bool R_MarkLightLeaves ()
{
	int contents, leafnum, cluster;

	contents = CL_PMpointcontents(currentshadowlight->origin);
	if (contents & CONTENTS_SOLID)
		goto skip;

	leafnum = CM_PointLeafnum (currentshadowlight->origin);
	cluster = CM_LeafCluster (leafnum);
	currentshadowlight->area = CM_LeafArea (leafnum);

	if(!currentshadowlight->area)
	{
skip:	Com_DPrintf("Out of BSP, rejected light at %f %f %f\n", currentshadowlight->origin[0], currentshadowlight->origin[1], currentshadowlight->origin[2]);
		return false;
	}

	// строим vis-data
	Memcpy (&currentshadowlight->vis, CM_ClusterPVS(cluster), (((CM_NumClusters()+31)>>5)<<2));

	int		leafs[MAX_MAP_LEAFS];
	int		i, count;
///	int		longs;
	vec3_t	mins, maxs;
	byte	vis[MAX_MAP_LEAFS/8];

	for (i=0 ; i<3 ; i++)
	{
		mins[i] = currentshadowlight->origin[i] - currentshadowlight->radiuses[i];
		maxs[i] = currentshadowlight->origin[i] + currentshadowlight->radiuses[i];
	}

	count = CM_BoxLeafnums (mins, maxs, leafs, r_worldmodel->numleafs/*MAX_MAP_LEAFS*/, NULL);
	if (count < 1)
		Com_Error (ERR_FATAL, "R_MarkLightLeaves: count < 1");
///	longs = (CM_NumClusters()+31)>>5;

	// convert leafs to clusters
	for (i=0 ; i<count ; i++)
		leafs[i] = CM_LeafCluster(leafs[i]);

	Memset(&vis, 0, (((r_worldmodel->numleafs+31)>>5)<<2)/*MAX_MAP_LEAFS/8*/);
	for (i=0 ; i<count ; i++)
		vis[leafs[i]>>3] |= (1<<(leafs[i]&7));
	// вырезаем кластеры, которых нет в списке leafs
	for (i=0 ; i<((r_worldmodel->numleafs+31)>>5)/*MAX_MAP_LEAFS/32*/ ; i++)
		((long *)currentshadowlight->vis)[i] &= ((long *)vis)[i];

	return true;
}


void CheckWorldInteraction()
{
	msurface_t *surf;
	int lightPos = 0;

	if (!currentshadowlight->lightCmds)
		goto no;

	bool skipp = (!(gl_config.vp && (r_shader->value == SHADER_ARB4 || r_shader->value == SHADER_ARB6)));
	lightcmd_u *lightCmds = currentshadowlight->lightCmds;

	while (1)
	{
		surf = (msurface_t *)lightCmds[lightPos++].asVoid;
		if(!surf)
			break; //end of list

///		Berserker's hack: пусть (SURF_SKY+SURF_SLICK) участвует в затенении, но не рисуется в бампмапе!
		if((surf->drawframe == r_framecount) && !((surf->texinfo->flags & SURF_SKY) && (surf->texinfo->flags & SURF_SLICK)))		/// Если рисуемо-освещаемая поверхность не видна, то не рисуем
		{
			currentshadowlight->has_lightedsurfs = true;
			return;
		}

		if (skipp)
			lightPos += surf->numedges * 3;
	}

no:	currentshadowlight->has_lightedsurfs = false;
}


/*
=============
R_ContributeFrame

Returns true if the light should be rendered.
=============
*/
bool R_ContributeFrame ()
{
#if 0
	if (r_mirror)
	{
		if (currentshadowlight->styled_color[0] < 0.2 && currentshadowlight->styled_color[1] < 0.2 && currentshadowlight->styled_color[2] < 0.2)
			return false;	//very soft light, don't bother.
	}
	else
	{
		if (currentshadowlight->styled_color[0] < 0.1 && currentshadowlight->styled_color[1] < 0.1 && currentshadowlight->styled_color[2] < 0.1)
			return false;	//very soft light, don't bother.
	}
#else
	if (currentshadowlight->styled_color[0] / currentshadowlight->color[0] < 0.05 &&
		currentshadowlight->styled_color[1] / currentshadowlight->color[1] < 0.05 &&
		currentshadowlight->styled_color[2] / currentshadowlight->color[2] < 0.05)
			return false;	//very soft light, don't bother.
#endif

	if (currentshadowlight->sphere)
	{
		//frustum scissor testing
		float d = SphereInFrustum(currentshadowlight->origin, currentshadowlight->radius);
		if (d == 0)
			return false;	//whole sphere is out ouf frustum so cut it.
	}
	else
	{	// ранее тут был R_CullBox, но из-за него источники света "сзади" камеры не отбрасывались (на q1dm6 в одном месте был косяк с освещением)
		if (BoxOutsideFrustum (currentshadowlight->mins_cone, currentshadowlight->maxs_cone))
			return false;
	}

	if(!currentshadowlight->isStatic)
		if(!R_MarkLightLeaves())
			return false;

	//not in a visible area => skip it
	if (!(r_newrefdef.areabits[currentshadowlight->area>>3] & (1<<(currentshadowlight->area&7))))
		return false;

	/// Berserker: Проверяем, не закрыто ли дверями? (for not mirrors only!!!)
//	if (!r_mirror)
//		if (!CM_AreasConnected (currentshadowlight->area, viewarea))
//			return false;	/// Это вызывает баг при присоединении к удаленному серверу!

	if (!HasSharedLeafs (currentshadowlight->vis, viewvis))
		return false;

	//fully/partially in frustum
	if(!r_mirror && !intersectsBoxPoint(currentshadowlight->mins_cone, currentshadowlight->maxs_cone, r_origin))
	{
		if(r_newrefdef.fov_x <= 90)
			/// Berserker: этот способ быстрее, но при углах обзора больше 90, дает артефакты куллинга;
			/// поэтому юзаем его тока при углах меньших или равных 90.
			R_ProjectSphere(currentshadowlight, currentshadowlight->scizz.coords);
		else
			boxScreenSpaceRect(currentshadowlight, currentshadowlight->scizz.coords);
	}
	else
	{	//viewport is ofs/width based
		currentshadowlight->scizz.coords[0] = r_viewport[0];
		currentshadowlight->scizz.coords[1] = r_viewport[1];
		currentshadowlight->scizz.coords[2] = r_viewport[0] + r_viewport[2];
		currentshadowlight->scizz.coords[3] = r_viewport[1] + r_viewport[3];
	}

	return true;
}


/*
=============
R_InitShadowsForFrame

Do per frame intitialization for the shadows
=============
*/
void R_InitShadowsForFrame ()
{
	int		i;
	vec3_t	temp;

	numShadowLights = numStaticShadowLights;
	R_AddDlights (); //add dynamic lights to the list
	numUsedShadowLights = 0;

///	int len = (r_worldmodel->vis->numclusters+7)>>3;
///	Memcpy(&viewvis, Mod_ClusterPVS (r_viewcluster, r_worldmodel), len);
///	Memset(&viewvis[len], 0, ((r_worldmodel->numleafs+7)>>3) - len);	// all other invisible
///	if (!r_mirror)
///		viewarea = CM_LeafArea (CM_PointLeafnum (r_origin));	/// not used...

	for (i=0; i<numShadowLights; i++)
	{
		currentshadowlight = &shadowlights[i];
		currentshadowlight->visible = false;

///		if(!currentshadowlight->use)
///			continue;

		if(currentshadowlight->isStatic && r_nolights->value)
			continue;

		if(!currentshadowlight->style && !currentshadowlight->cl_style)		/// special case
			VectorCopy(currentshadowlight->color, currentshadowlight->styled_color);
		else
		{
			float			bright;
			clightstyle_t	*ls, *cl_ls;
			ls = &sv_lightstyle[currentshadowlight->style];
			cl_ls = &cl_lightstyle[currentshadowlight->cl_style];
			bright = ls->val * cl_ls->val;
			VectorSet( currentshadowlight->styled_color, bright*currentshadowlight->color[0], bright*currentshadowlight->color[1], bright*currentshadowlight->color[2] );
		}

		if(currentshadowlight->isStatic && (/*currentshadowlight->volumeCmds == NULL || */currentshadowlight->lightCmds == NULL))
			currentshadowlight->visible = false;
		else
		{
			if (R_ContributeFrame())
			{
				if (numUsedShadowLights < MAXUSEDSHADOWLIGHS)
				{
					currentshadowlight->visible = true;
					usedshadowlights[numUsedShadowLights] = currentshadowlight;
					numUsedShadowLights++;
				}
				else
				{
					Com_DPrintf("R_InitShadowsForFrame: More than MAXUSEDSHADOWLIGHS=%i lights for frame\n",MAXUSEDSHADOWLIGHS);

					/// Поиск самого дальнего/слабого статичного источника света по системе рейтингов
					int		j, idx = -1;
					float	current_rating, other_rating, min_rating;

					VectorSubtract(r_origin, currentshadowlight->origin, temp);
					min_rating = current_rating = currentshadowlight->radius * ((currentshadowlight->styled_color[0]+currentshadowlight->styled_color[1]+currentshadowlight->styled_color[2])*0.333333f) / Sqrt(DotProduct(temp, temp));

					for (j=0; j<numUsedShadowLights; j++)
					{
						if(usedshadowlights[j]->isStatic)
						{
							VectorSubtract(r_origin, usedshadowlights[j]->origin, temp);
							other_rating = usedshadowlights[j]->radius * ((usedshadowlights[j]->styled_color[0]+usedshadowlights[j]->styled_color[1]+usedshadowlights[j]->styled_color[2])*0.333333f) / Sqrt(DotProduct(temp, temp));

							if(other_rating < min_rating)
							{
								min_rating = other_rating;
								idx = j;
							}
						}
					}

					if(idx != -1)
					{				// нашли источник с самым малым рейтингом, вот его и заменим на текущий...
						usedshadowlights[idx]->visible = false;
						usedshadowlights[idx] = currentshadowlight;
						usedshadowlights[idx]->visible = true;
					}
				}
			}
		}
	}
}


/*
=============
getVertexIndexFromSurf

Gets index of the i'th vertex of the surface in the models vertex array
=============
*/
int getVertexIndexFromSurf(msurface_t *surf, int index, model_t *model)
{
	int lindex = model->surfedges[surf->firstedge + index];
	medge_t	*r_pedge;

	if (lindex > 0)
	{
		r_pedge = &model->edges[lindex];
		return r_pedge->v[0];
	}
	else
	{
		r_pedge = &model->edges[-lindex];
		return r_pedge->v[1];
	}
}

/*
bool R_SetDepthBounds()
{
	float	dist;

	if (!glDepthBoundsEXT || !r_DepthBoundsTest->value)
		return false;

	// distance from light origin to near plane
	dist =	frustumPlanes[5][0] * currentshadowlight->origin[0] +
			frustumPlanes[5][1] * currentshadowlight->origin[1] +
			frustumPlanes[5][2] * currentshadowlight->origin[2] +
			frustumPlanes[5][3];

	if((dist < -currentshadowlight->radius) || (dist <= currentshadowlight->radius))
		return false;							// light behind near plane or clipped
	else
	{
		glEnable(GL_DEPTH_BOUNDS_TEST_EXT);
////// Berserker: не ясно как привести границы к 1, если zFar = infinity (((
		glDepthBoundsEXT(dist - currentshadowlight->radius, dist + currentshadowlight->radius);
		return true;
	}
}
*/

/*
=============
PrecalcVolumesForLight

This will create arrays with gl-commands that define the shadow volumes
(something similar to the mesh arrays that are created.)
They are stored for static lights and recalculated every frame for dynamic ones.
Non calculated vertices are not saved in the list but the index in the vertex array
of the model is saved.

We store them in volumeCmdsBuff and lightCmdsBuff
=============
*/
void PrecalcVolumesForLight(model_t *model)
{
	msurface_t *surf;
	int startVerts, numPos, stripLen, i, j, srf;
	glpoly_t *poly;
	vec3_t v1;
	bool shadow, lastshadow;
	bool skipp = (!(gl_config.vp && (r_shader->value == SHADER_ARB4 || r_shader->value == SHADER_ARB6)));
	float	*v;

	lightcmd_t *volumeCmds = &volumeCmdsBuff[0];
	lightcmd_t *lightCmds = &lightCmdsBuff[0];
	int volumePos = 0;
	int lightPos = 0;

	float scale = 32 * currentshadowlight->radius;	/// было 10, при 32 вырос FPS!

	if (gl_config.vp && r_shader->value==SHADER_ARB6 && r_parallax->value)
		qsort(scene_surfaces, num_scene_surfaces, sizeof(msurface_t*), (int (*)(const void *, const void *))SurfCmpPT);
	else
		qsort(scene_surfaces, num_scene_surfaces, sizeof(msurface_t*), (int (*)(const void *, const void *))SurfCmpT);
///	surf = shadowchain;

///	while (surf)
	for (srf=0; srf<num_scene_surfaces; srf++)
	{
		surf = scene_surfaces[srf];
		poly = surf->polys;
		if (!currentshadowlight->noshadow)
		{
			//1. Calculate shadow volumes
///			Old Berserker's hack: пусть (SURF_SKY+SURF_SLICK) участвует в затенении!
			if ((surf->texinfo->flags & SURF_SKY) && (surf->texinfo->flags & SURF_SLICK))
				goto yes;
///			if (surf->texinfo->image->CastShadow)		// Форсируем отброс тени
///				goto yes;
			if ((surf->texinfo->flags & (SURF_TRANS33|SURF_TRANS66|SURF_SKY|SURF_WARP|SURF_NODRAW)) || (surf->flags & SURF_DRAWTURB))
			{
///				surf = surf->shadowchain;
				Com_DPrintf ("Water/Sky in shadow chain!\n");
				continue;
			}

yes:		//a. far cap
			volumeCmds[volumePos++].asInt = GL_TRIANGLE_FAN;
			volumeCmds[volumePos++].asInt = surf->numedges;

			startVerts = volumePos;
			for (i=0 ; i<surf->numedges ; i++)
			{
				VectorSubtract (poly->verts[i], currentshadowlight->origin, v1);
				float sca = scale/VectorLength(v1);
				volumeCmds[volumePos++].asFloat = v1[0] * sca + poly->verts[i][0];
				volumeCmds[volumePos++].asFloat = v1[1] * sca + poly->verts[i][1];
				volumeCmds[volumePos++].asFloat = v1[2] * sca + poly->verts[i][2];
			}

			if (volumePos > MAX_VOLUME_COMMANDS)
			{
				Com_DPrintf ("More than MAX_VOLUME_COMMANDS commands! (%i)\n", volumePos);
				break;
			}

			//b. borders of volume
			//we make quad strips if we have continuous borders
			lastshadow = false;

			for (i=0 ; i<surf->numedges ; i++)
			{
				shadow = false;
				if (poly->neighbours[i] != NULL)
				{
					if ( poly->neighbours[i]->lightTimestamp != poly->lightTimestamp)
						shadow = true;
				}
				else
					shadow = true;

				if (shadow)
				{
					if (!lastshadow)
					{
						//begin new strip
						volumeCmds[volumePos++].asInt = GL_QUAD_STRIP;
						numPos = volumePos;
						volumeCmds[volumePos++].asInt = 4;
						stripLen = 2;

						//copy vertices
						volumeCmds[volumePos++].asInt = -getVertexIndexFromSurf(surf, i, model);
						volumeCmds[volumePos++].asInt = startVerts+i*3;
					}

					volumeCmds[volumePos++].asInt = -getVertexIndexFromSurf(surf, (i+1)%poly->numverts, model);
					volumeCmds[volumePos++].asInt = startVerts+((i+1)%poly->numverts)*3;

					stripLen+=2;
				}
				else
				{
					if (lastshadow)
					{
						//close list up
						volumeCmds[numPos].asInt = stripLen;
					}
				}
				lastshadow = shadow;
			}

			if (lastshadow)
			{
				//close list up
				volumeCmds[numPos].asInt = stripLen;
			}

			if (volumePos > MAX_VOLUME_COMMANDS)
			{
				Com_DPrintf ("More than MAX_VOLUME_COMMANDS commands! (%i)\n", volumePos);
				break;
			}
		}

		lightCmds[lightPos++].asVoid = (void*)surf;
		if (surf->texinfo && surf->texinfo->image->Parallax && gl_config.vp)
			currentshadowlight->has_parallax_surf = true;

		if (skipp)		//// BERSERKER: ARB4/6 with VP - not needs for lightcmds !!!
		{
			for (j=0, v = poly->verts[0] ; j<poly->numverts ; j++, v+= VERTEXSIZE)
			{
				//calculate local light vector and put it into tangent space
				vec3_t lightDir, tsLightDir;
				VectorSubtract( currentshadowlight->origin,v,lightDir);
///				VectorNormalize(lightDir);

				tsLightDir[2] = lightDir[0]*v[7]+lightDir[1]*v[8]+lightDir[2]*v[9];
				tsLightDir[0] = lightDir[0]*v[10]+lightDir[1]*v[11]+lightDir[2]*v[12];
				tsLightDir[1] = lightDir[0]*v[13]+lightDir[1]*v[14]+lightDir[2]*v[15];

				lightCmds[lightPos++].asFloat = tsLightDir[0];
				lightCmds[lightPos++].asFloat = tsLightDir[1];
				lightCmds[lightPos++].asFloat = tsLightDir[2];
			}
		}

		if (lightPos > MAX_LIGHT_COMMANDS)
		{
			Com_DPrintf ("More than MAX_LIGHT_COMMANDS commands (%i)\n", lightPos);
			break;
		}
///		surf = surf->shadowchain;
	}

	//finish them off with 0
	lightCmds[lightPos++].asInt = NULL;
	volumeCmds[volumePos++].asInt = NULL;

	numLightCmds = lightPos;
	numVolumeCmds = volumePos;
}


dplane_t *AllocPlane()
{
	if (planesusedpool >= MAX_MAP_PLANES*3)
	{
		Com_DPrintf("Too many planes...\n");
		return NULL;
	}
	planesusedpool++;
	return &PlanePool[planesusedpool-1];
}


svnode_t *AllocNode()
{
	if (nodesusedpool >= MAX_MAP_NODES*3)
	{
		Com_DPrintf("Too many nodes...\n");
		return NULL;
	}
	nodesusedpool++;
	return &NodePool[nodesusedpool-1];
}


int Epsilon_Sign(float value)
{
	if (value < -ON_EPSILON) return 1;
	else if (value > ON_EPSILON) return 2;
	else return 0; 
}


/*
=============
R_CreateEmptyTree

Do some tricks to make sure we have a 360 deg. field of view.
This routine destroys the last svbsp the program has generated.

         plane
         /   \ 
      -plane  open
       /  \
   solid   open

=============
*/
svnode_t *R_CreateEmptyTree()
{
	dplane_t *plane1;
	dplane_t *plane2;
	svnode_t *node1;
	svnode_t *node2;

	planesusedpool = 0;
	nodesusedpool = 0;
	plane1 = AllocPlane ();
	plane2 = AllocPlane ();
	node1 = AllocNode ();
	node2 = AllocNode ();

	//all planes go trough the light origin
	plane1->normal[0] = 1;
	plane1->normal[1] = 0;
	plane1->normal[2] = 0;
	plane1->dist = DotProduct (currentshadowlight->origin, plane1->normal);

	plane2->normal[0] = -1;
	plane2->normal[1] = 0;
	plane2->normal[2] = 0;
	plane2->dist = DotProduct (currentshadowlight->origin, plane2->normal);


	node1->splitplane = plane1;
	node1->children[0] = node2;
	node1->children[1] = NULL;

	node2->splitplane = plane2;
	node2->children[0] = NULL;
	node2->children[1] = NULL;

	return node1;
}


svnode_t *NodeFromEdge(vec3_t *v, int vnum, int edgeindex)
{
	vec3_t		*v1,*v2;
	vec3_t		vert1,vert2,normal;
	float		dist;
	dplane_t	*plane;
	svnode_t	*res;

	//Extract one vertex so we can calultate the normal of this shadow plane
	v1 = &v[edgeindex];
	v2 = &v[(edgeindex+1)% vnum];
	VectorSubtract ((*v1), currentshadowlight->origin, vert1);
	VectorSubtract ((*v1), (*v2), vert2);
	CrossProduct (vert1, vert2, normal);
	VectorNormalize (normal);

	//VectorInverse(normal);
	dist = DotProduct(normal, currentshadowlight->origin);

	//make a node with it
	plane = AllocPlane ();
	if (!plane)
		return NULL;

	VectorCopy (normal, plane->normal);
	plane->dist = dist;

	res = AllocNode ();
	if (!res)
		return res;

	res->splitplane = plane;
	res->children[0] = NULL;
	res->children[1] = NULL;
	return res;
}


svnode_t *ExpandVolume(vec3_t *v,int *sign, int vnum, msurface_t *surf)
{
	svnode_t	*res, *currnode;
	int			i;

	if (vnum == 0)
		return NULL;

	res = NodeFromEdge (v, vnum, 0);
	if (!res)
		return NULL;

	currnode = res;
	for (i=1; i<vnum; i++)
	{
		currnode->children[0] = NodeFromEdge (v, vnum, i);
		if (currnode->children[0] == NULL)
			break;
		currnode = currnode->children[0];
	}

	return res;
}


void SplitPolygon(vec3_t *polygon,int *signs, int vnum, dplane_t *plane, vec3_t *inpts, int *innum, vec3_t *outpts, int *outnum)
{
	int out_c = 0;
	int in_c = 0;

	vec3_t *ptA = &polygon[vnum-1];
	vec3_t *ptB, v, newVert;
	int sideA = signs[vnum-1];
	int sideB;
	int i;
	float sect;

	for (i=0;  i<vnum; i++)
	{
		ptB = &polygon[i];
		sideB = signs[i];

		//is b on "right side"
		if (sideB == 2)
		{
			if (sideA == 1)
			{
				// compute the intersection point of the line
				// from point A to point B with the partition
				// plane. This is a simple ray-plane intersection.
				VectorSubtract ((*ptB), (*ptA), v);
				sect = - (DotProduct (plane->normal, (*ptA) )-plane->dist) / DotProduct (plane->normal, v);
				VectorScale (v,sect,v);
            
				//add a new vertex
				VectorAdd ((*ptA), v, newVert);
				VectorCopy (newVert, inpts[in_c]);
				VectorCopy (newVert, outpts[out_c]);

				out_c++;
				in_c++;
			}
			VectorCopy (polygon[i], outpts[out_c]);
			out_c++;
		}
		//b is on "left" side
		else
			if (sideB ==1)
			{
				if (sideA == 2)
				{
					// compute the intersection point of the line
					// from point A to point B with the partition
					// plane. This is a simple ray-plane intersection.
					VectorSubtract ((*ptB), (*ptA), v);
					sect = - (DotProduct (plane->normal, (*ptA) )-plane->dist) / DotProduct (plane->normal, v);
					VectorScale (v,sect,v);

					//add a new vertex
					VectorAdd ((*ptA), v, newVert);
					VectorCopy (newVert, inpts[in_c]);
					VectorCopy (newVert, outpts[out_c]);

					out_c++;
					in_c++;
				}
				VectorCopy (polygon[i], inpts[in_c]);
				in_c++;
			}
			//b is almost on plane
			else
			{
				VectorCopy (polygon[i], inpts[in_c]);
				VectorCopy (inpts[in_c], outpts[out_c]);
				in_c++;
				out_c++;
			}

			ptA = ptB;
			sideA = sideB;

			if ((out_c > MAX_POLY_VERT) || (in_c > MAX_POLY_VERT))
			{
				Com_DPrintf ("MAX_POLY_VERT exceeded: %i %i\n", in_c, out_c);
				//just return what we've got
				(*innum) = in_c;
				(*outnum) = out_c;
				return;
			}
	}

	(*innum) = in_c;
	(*outnum) = out_c;
}


/*
=================
CL_PrepRefresh

Call before entering a new level, or after changing dlls
=================
*/
void CL_PrepRefresh ()
{
	int			i;
	char		name[MAX_QPATH];
	float		rotate;
	vec3_t		axis;

	if (!cl.configstrings[CS_MODELS+1][0])
		return;		// no map loaded

	SCR_AddDirtyPoint (0, 0);
	SCR_AddDirtyPoint (viddef.width-1, viddef.height-1);

	// let the render dll load the map
	strcpy (mapname, cl.configstrings[CS_MODELS+1] + 5);	// skip "maps/"
	mapname[strlen(mapname)-4] = 0;		// cut off ".bsp"

	old_skin[0] = 0;
	R_ClearMirrorChains(true);
	CL_Clear3DHud();	// kill 3dhud models

	// register models, pics, and skins
	loading_stage = 1;
	Com_Printf ("Map: %s\r", mapname);
	SCR_UpdateScreen ();
	R_BeginRegistration (mapname);
	Com_Printf ("                                     \r");

	// precache status bar pics
	loading_stage = 2;
	Com_Printf ("Pics\r"); 
	SCR_UpdateScreen ();
	SCR_TouchPics ();
	Com_Printf ("                                     \r");

	loading_stage = 3;
	Com_Printf ("Models\r");
	CL_RegisterTEntModels ();

	num_cl_weaponmodels = 1;
	strcpy(cl_weaponmodels[0], "weapon.md2");

	int	max_models;
	if (net_compatibility->value)
		max_models = MAX_MODELS_Q2;
	else
		max_models = MAX_MODELS_BERS;
	for (i=1 ; i<max_models && cl.configstrings[CS_MODELS+i][0] ; i++)
	{
		strcpy (name, cl.configstrings[CS_MODELS+i]);
		name[37] = 0;	// never go beyond one line
		if (name[0] != '*')
			Com_Printf ("%s\r", name); 
		SCR_UpdateScreen ();
		Sys_SendKeyEvents ();	// pump message loop
		if (name[0] == '#')
		{
			// special player weapon model
			if (num_cl_weaponmodels < MAX_CLIENTWEAPONMODELS)
			{
				strncpy(cl_weaponmodels[num_cl_weaponmodels], cl.configstrings[CS_MODELS+i]+1, sizeof(cl_weaponmodels[num_cl_weaponmodels]) - 1);
				num_cl_weaponmodels++;
			}
		} 
		else
		{
			cl.model_draw[i] = R_RegisterModel (cl.configstrings[CS_MODELS+i], 1, false);
			if (name[0] == '*')
				cl.model_clip[i] = CM_InlineModel (cl.configstrings[CS_MODELS+i]);
			else
				cl.model_clip[i] = NULL;
		}
		if (name[0] != '*')
			Com_Printf ("                                     \r");
	}

	loading_stage = 4;
	Com_Printf ("Images\r");
	SCR_UpdateScreen ();

	int max_images, cs_images, cs_playerskins;
	if (net_compatibility->value)
	{
		max_images = MAX_IMAGES_Q2;
		cs_images = CS_IMAGES_Q2;
		cs_playerskins = CS_PLAYERSKINS_Q2;
	}
	else
	{
		max_images = MAX_IMAGES_BERS;
		cs_images = CS_IMAGES_BERS;
		cs_playerskins = CS_PLAYERSKINS_BERS;
	}
	for (i=1 ; i<max_images && cl.configstrings[cs_images+i][0] ; i++)
	{
		cl.image_precache[i] = Draw_FindPic (cl.configstrings[cs_images+i]);
		Sys_SendKeyEvents ();	// pump message loop
	}
// load user's decals
	for(i=MAX_GLOBAL_FILTERS; i<MAX_FILTERS; i++)
	{
		if (decal_texture_object[i])
			if (decal_texture_object[i]->registration_sequence != registration_sequence)
				decal_texture_object[i]->registration_sequence = -1;	// kill old object
		R_LoadDecal(&decal_texture_object[i], va("textures/decals/%s/%i.jpg", mapname, i), file_jpg);	// JPG only
	}

// load user's light-filters
	if (!r_simple->value)
		for(i=MAX_GLOBAL_FILTERS; i<MAX_FILTERS; i++)
		{
			if (filtercube_texture_object[i])
				if (filtercube_texture_object[i]->registration_sequence != registration_sequence)
					filtercube_texture_object[i]->registration_sequence = -1;	// kill old object
			filtercube_texture_object[i] = R_LoadLightFilter (i, mapname);	// JPG only
		}

	loading_stage = 5;
	Com_Printf ("                                     \r");
	for (i=0 ; i<MAX_CLIENTS ; i++)
	{
		if (!cl.configstrings[cs_playerskins+i][0])
			continue;
		Com_Printf ("client %i\r", i);
		SCR_UpdateScreen ();
		Sys_SendKeyEvents ();	// pump message loop
		CL_ParseClientinfo (i);
		Com_Printf ("                                     \r");
	}

	CL_LoadClientinfo (&cl.baseclientinfo, "unnamed\\male/grunt", true);
	CL_FixUpGender();

	// set sky textures and speed (and world ambient light)
	loading_stage = 6;
	Com_Printf ("Sky\r");
	SCR_UpdateScreen ();
	rotate = atof (cl.configstrings[CS_SKYROTATE]);
	sscanf (cl.configstrings[CS_SKYAXIS], "%f %f %f", &axis[0], &axis[1], &axis[2]);
	SetSky (cl.configstrings[CS_SKY], rotate, axis);
	if (r_simple->value)
		VectorClear(world_ambient_light);			// в упрощённом рендере игнорируем "ambient" и используем lightmaps
	else
	{
		if (net_compatibility->value || r_simple->value)
			VectorCopy(amb, world_ambient_light);
		else
		{
			if (cl.attractloop)							// Если дёмка, то пусть ambient будет то, что в entity string.
				VectorCopy(amb, world_ambient_light);
			else
			{
				int	rr, gg, bb;
				sscanf (cl.configstrings[CS_TRACK_AMBIENT], "%i %i %i %i", &i, &rr, &gg, &bb);
				VectorSet(world_ambient_light, rr/256.0F, gg/256.0F, bb/256.0F);
			}
		}
	}
	Com_Printf ("                                     \r");
	Com_DPrintf ("World ambient light: %f %f %f\n", world_ambient_light[0], world_ambient_light[1], world_ambient_light[2]);

	// the renderer can now free unneeded stuff
///	R_EndRegistration ();		// нельзя тут чистить текстуры, т.к. загрузка еще не завершена!

	// clear any lines of console text
	Con_ClearNotify ();

	loading_stage = 7;
	SCR_UpdateScreen ();
	cl.refresh_prepped = true;
	cl.force_refdef = true;	// make sure we have a valid refdef

	numFogs = sun_size = numEFXzones = 0;
////	r_using_underwater = r_using_breather = r_using_enviro = false;
	bubble_think = r_using_pain = r_using_burning = r_using_drown = 0;

	R_ClearSLights();
	CL_InitDecals();
	CL_ClearClEntities ();
	CL_ClearParticles();
	effectsInited = true;

	CM_SpawnEntities (CM_EntityString(), false);
	CL_ABIndex();
	if(sun_size)
		Com_Printf ("Sun initialised\r");

	// calc static lights
	Com_Printf ("Spawned %i static lights\r", numStaticShadowLights);
	for(i=0; i<numStaticShadowLights; i++)
	{
		currentshadowlight = &shadowlights[i];
#ifdef FORCE_DYNAMICCONE
		if(currentshadowlight->isStatic)
#endif
		R_CalcSLight();
	}
	Com_Printf ("                               \r");

	// defining fog_areas to fog_entities
	loading_stage = 8;
	Com_Printf ("Fogs\r");
	SCR_UpdateScreen ();
	if (numFogs)
	{
		Com_Printf ("Spawned %i fog areas\r", numFogs);
		R_MarkFogSurfs();
		Com_Printf ("                       \r");
	}
	else
		Com_Printf ("    \r");

	loading_stage = 0;

	// the renderer can now free unneeded stuff
	R_EndRegistration ();

	// start the cd track
///	CDAudio_Play (atoi(cl.configstrings[CS_TRACK_AMBIENT]), true);
	Music_Play(NULL);
/*
	TrackIsCombat_old = OLD_TRACK_DISABLED;	// set default

	if (atoi(cl.configstrings[CS_TRACK_AMBIENT]) >= 0)		// if <0, force to play old tracks, disable "situations tracks" for loading map
	{
		// check for music override
		if (s_music->value == 2)
		{
			char	*cfg;
			Com_sprintf (name, sizeof(name), "maps/%s.cfg", mapname);
			FS_LoadFile(name, (void **)&cfg);
			if (cfg)
			{
				char	*token, *s = cfg;

				trackName_Relax[0] = 0;
				trackName_RelaxToCombat[0] = 0;
				trackName_Combat[0] = 0;
				trackName_CombatToRelax[0] = 0;
				trackRelax_offset = trackCombat_offset = 0;

				while (s)
				{
					token = COM_Parse (&s);
					if (!Q_strcasecmp(token, "relaxTrackName"))
					{
						Memcpy(trackName_Relax, COM_Parse(&s), sizeof(trackName_Relax));
						continue;
					}
					if (!Q_strcasecmp(token, "tensionTrackName"))
					{
						Memcpy(trackName_RelaxToCombat, COM_Parse(&s), sizeof(trackName_RelaxToCombat));
						continue;
					}
					if (!Q_strcasecmp(token, "combatTrackName"))
					{
						Memcpy(trackName_Combat, COM_Parse(&s), sizeof(trackName_Combat));
						continue;
					}
					if (!Q_strcasecmp(token, "calmTrackName"))
					{
						Memcpy(trackName_CombatToRelax, COM_Parse(&s), sizeof(trackName_CombatToRelax));
						continue;
					}
				}

				Z_Free(cfg);

				// check for files exist
				if (trackName_Relax[0])
				{
					Com_sprintf (name, sizeof(name), "%s.wav", trackName_Relax);
					if (FS_LoadFile(name, NULL) == -1)
					{
						Com_sprintf (name, sizeof(name), "%s.ogg", trackName_Relax);
						if (FS_LoadFile(name, NULL) == -1)
							trackName_Relax[0] = 0;
					}
				}

				if (trackName_RelaxToCombat[0])
				{
					Com_sprintf (name, sizeof(name), "%s.wav", trackName_RelaxToCombat);
					if (FS_LoadFile(name, NULL) == -1)
					{
						Com_sprintf (name, sizeof(name), "%s.ogg", trackName_RelaxToCombat);
						if (FS_LoadFile(name, NULL) == -1)
							trackName_RelaxToCombat[0] = 0;
					}
				}

				if (trackName_Combat[0])
				{
					Com_sprintf (name, sizeof(name), "%s.wav", trackName_Combat);
					if (FS_LoadFile(name, NULL) == -1)
					{
						Com_sprintf (name, sizeof(name), "%s.ogg", trackName_Combat);
						if (FS_LoadFile(name, NULL) == -1)
							trackName_Combat[0] = 0;
					}
				}

				if (trackName_CombatToRelax[0])
				{
					Com_sprintf (name, sizeof(name), "%s.wav", trackName_CombatToRelax);
					if (FS_LoadFile(name, NULL) == -1)
					{
						Com_sprintf (name, sizeof(name), "%s.ogg", trackName_CombatToRelax);
						if (FS_LoadFile(name, NULL) == -1)
							trackName_CombatToRelax[0] = 0;
					}
				}

				if (!trackName_RelaxToCombat[0] && trackName_Combat[0])
					Memcpy(trackName_RelaxToCombat, trackName_Combat, sizeof(trackName_CombatToRelax));

				if (!trackName_CombatToRelax[0] && trackName_Relax[0])
					Memcpy(trackName_CombatToRelax, trackName_Relax, sizeof(trackName_CombatToRelax));

				if (trackName_Relax[0] && trackName_RelaxToCombat[0] && trackName_Combat[0] && trackName_CombatToRelax[0])
					TrackIsCombat_old = OLD_TRACK_UNKNOWN;
			}
		}
	}

	///if (TrackIsCombat_old == OLD_TRACK_DISABLED)
		///CL_PlayBackgroundTrack ();
*/
}

void CL_Create_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		llink_t	tlink;
		Memcpy(&tlink, &link_clipboard, sizeof(llink_t));
		link_clipboard.radius = 300;
		VectorCopy(r_origin, link_clipboard.origin);
		VectorSet(link_clipboard.color, 1,1,1);
		VectorSet(link_clipboard.angles, 0,0,0);
		link_clipboard._cone = 0;
		link_clipboard.style = 0;
		link_clipboard.cl_style = 0;
		link_clipboard.filtercube_start = link_clipboard.filtercube_end = 0;
		link_clipboard.framerate = 2;
		CL_Paste_f();
		Memcpy(&link_clipboard, &tlink, sizeof(llink_t));
	}
	else
	{
		shadowlight_t	tlight;

		Memcpy(&tlight, &light_clipboard, sizeof(shadowlight_t));
		light_clipboard.radius = light_clipboard.radiuses[0] = light_clipboard.radiuses[1] = light_clipboard.radiuses[2] = 300;
		VectorCopy(r_origin, light_clipboard.origin);
		VectorSet(light_clipboard.color, 1,1,1);
		VectorSet(light_clipboard.angles, 0,0,0);
		VectorSet(light_clipboard.rspeed, 0,0,0);
		light_clipboard.flareLight = false;
		light_clipboard._cone = 0;
		light_clipboard.style = 0;
		light_clipboard.cl_style = 0;
		light_clipboard.filtercube_start = light_clipboard.filtercube_end = 0;
		light_clipboard.framerate = 2;
		light_clipboard.targetname[0] = 0;
		light_clipboard.start_off = false;
		light_clipboard.noshadow = false;
		light_clipboard.noshadow2 = false;
		light_clipboard.nobump = false;
		CL_Paste_f();
		Memcpy(&light_clipboard, &tlight, sizeof(shadowlight_t));
	}

	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


bool CheckBrushLabel(char *label)
{
	int	i;

	for (i=0; i<brushmodel_counter; i++)
		if(!strcmp(bmdl_list[i].label, label))
		{
			Com_Printf("^3\"%s\" is the label for brush model \"*%i\"\n", label, bmdl_list[i].index);
			return false;
		}

	return true;
}

void CL_LabelBrush_f ()
{
	int				i;
	char			*arg;
	shadowlight_t	sl;
	char			buf[16];

	if (!LightEditor_Enabled(false))
		return;

	if (!curbrush)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: labelbrush <string>\n");
		return;
	}

	arg = Cmd_Argv(1);

	// ищем энтити
	for (i=0 ; i<cl.refdef.num_entities ; i++)
	{
		currententity = &cl.refdef.entities[i];
		currentmodel = currententity->model;

		if (!currentmodel)
			continue;

		if (currentmodel->type != mod_brush)
			continue;

		if (currentmodel->nummodelsurfaces == 0)
			continue;

		if (currentmodel->firstmodelsurface == curbrush->firstface)
			break;
	}

	if (i==cl.refdef.num_entities)
		return;

	for (i=0; i<brushmodel_counter; i++)
		if (bmdl_list[i].index == curbrushnum)
			break;

	if (i==brushmodel_counter)
	{	// no labeled brush
		if (arg[0] == 0)
			return;				// Не помеченный браш и сброс лейбла - ничего не делаем.
		if (CheckBrushLabel(arg))
		{
			Memcpy(sl.label, arg, MAX_QPATH);
			Com_sprintf(buf, sizeof(buf), "*%i", curbrushnum);
			Memcpy(sl.vis, buf, sizeof(buf));
			sl.nobump = false;
			VectorCopy(curbrush->origin, sl.origin);
			R_SpawnBrush(&sl);
			Com_Printf("Brush model labeled.\n");
			Cl_UpdateEditorCvars(ED_BRUSH | ED_MODIFIED);
		}
	}
	else
	{	// labeled brush
		if (arg[0] == 0)
		{						// Помеченный браш и сброс лейбла - удаляем из списка.
			if (i<brushmodel_counter-1)
			{	// если не последний в списке, то придётся смещать список вниз
				for (; i<brushmodel_counter-1; i++)
					Memcpy(&bmdl_list[i], &bmdl_list[i+1], sizeof(blink_t));
			}
			brushmodel_counter--;
			Com_Printf("Label deleted.\n");
			Cl_UpdateEditorCvars(ED_BRUSH | ED_MODIFIED);
		}
		else
		{
			if (CheckBrushLabel(arg))
			{
				Memcpy(bmdl_list[i].label, arg, MAX_QPATH);	// просто сменим label
				Com_Printf("Label changed.\n");
				Cl_UpdateEditorCvars(ED_BRUSH | ED_MODIFIED);
			}
		}
	}
}


/*
=============
R_AddShadowCaster

Polygons must be added in front to back order!
=============
*/
svnode_t *R_AddShadowCaster(svnode_t *node, vec3_t *v, int vnum, msurface_t *surf,int depth)
{
	int sign;
	int	signs[MAX_POLY_VERT],signs2[MAX_POLY_VERT];
	vec3_t v1[MAX_POLY_VERT],v2[MAX_POLY_VERT];
	int vnum1,vnum2;
	int i;

	if (depth > 1500)
	{
		Com_DPrintf("too deep\n");
		return NULL;
	}

	if (vnum == 0)
		return NULL;

	sign = 0;
	for (i=0; i<vnum; i++)
		sign |= signs[i] = Epsilon_Sign (DotProduct (v[i], node->splitplane->normal)- node->splitplane->dist);

	if (sign == 1)
	{
		if (node->children[0] != NULL)
			R_AddShadowCaster (node->children[0], v, vnum, surf, depth+1); 

	}
	else
		if (sign == 2)
		{
			if (node->children[1] != NULL)
				R_AddShadowCaster (node->children[1], v, vnum, surf, depth+1); 
			else
			{
				node->children[1] = ExpandVolume(v, signs, vnum, surf);

				if (surf->visframe != r_lightTimestamp)
				{
					//Store it out as visible
///					surf->shadowchain = shadowchain;
					surf->visframe = r_lightTimestamp;
					surf->polys->lightTimestamp = r_lightTimestamp;
///					shadowchain = surf;
///					svBsp_NumKeptPolys++;
						scene_surfaces[num_scene_surfaces++] = surf;
				}
			}
		}
		else
			if (sign == 3)
			{
				SplitPolygon(&v[0], &signs[0], vnum, node->splitplane, &v1[0], &vnum1, &v2[0], &vnum2);

				if (node->children[0] != NULL)
					R_AddShadowCaster (node->children[0], v1, vnum1, surf, depth+1);

				if (vnum2 == 0)
					return NULL;

				if (node->children[1] != NULL)
					R_AddShadowCaster (node->children[1], v2, vnum2, surf, depth+1);
				else
				{
					node->children[1] = ExpandVolume (v2, signs2, vnum2, surf);

					if (surf->visframe != r_lightTimestamp)
					{
						//Store it out as visible
///						surf->shadowchain = shadowchain;
						surf->visframe = r_lightTimestamp;
						surf->polys->lightTimestamp = r_lightTimestamp;
///						shadowchain = surf;
///						svBsp_NumKeptPolys++;
							scene_surfaces[num_scene_surfaces++] = surf;
					}
				}
			}

	return NULL;
}


/*
================
AddToShadowBsp

Add a surface as potential shadow caster to the svbsp, it can be
cut when it is occluded by other surfaces.
================
*/
void AddToShadowBsp(msurface_t *surf)
{
	vec3_t surfvects[MAX_POLY_VERT];
	int numsurfvects;
	int i;
	float *v;

	//FIXME: use constant instead of MAX_POLY_VERT
	if (surf->numedges > MAX_POLY_VERT)
	{
		Com_DPrintf("AddToShadowBsp: too many edges\n");
		return;
	}

	surf->visframe = 0;
	//Make temp copy of surface polygon
	numsurfvects = surf->numedges;
	for (i=0, v=surf->polys->verts[0]; i<numsurfvects; i++, v+=VERTEXSIZE)
		VectorCopy(v,surfvects[i]);

	R_AddShadowCaster(currentlightroot,surfvects,numsurfvects,surf,0);
}


/*
================
R_RecursiveShadowAdd

Add surfaces front to back to the shadow bsp.
================
*/
void R_RecursiveShadowAdd(mnode_t *node)
{
	int			c, side;
	cplane_t	*plane;
	msurface_t	*surf;
	double		dot;

	if (node->contents == CONTENTS_SOLID)
		return;		// solid

	if (node->contents != -1)
		return;		// leaf

	//find which side of the node we are on
	plane = node->plane;
	switch (plane->type)
	{
	case PLANE_X:
		dot = currentshadowlight->origin[0] - plane->dist;
		break;
	case PLANE_Y:
		dot = currentshadowlight->origin[1] - plane->dist;
		break;
	case PLANE_Z:
		dot = currentshadowlight->origin[2] - plane->dist;
		break;
	default:
		dot = DotProduct (currentshadowlight->origin, plane->normal) - plane->dist;
		break;
	}

	if (dot >= 0)
		side = 0;
	else
		side = 1;

	//recurse down the children, front side first
	R_RecursiveShadowAdd (node->children[side]);

	//draw stuff
	c = node->numsurfaces;
	if (c)
	{
		surf = r_worldmodel->surfaces + node->firstsurface;
		do
		{
			if (surf->polys)
				if ((surf->polys->lightTimestamp == r_lightTimestamp))
					AddToShadowBsp (surf);
			surf++;
		} while (--c);
	}

	//recurse down the back side
	R_RecursiveShadowAdd (node->children[!side]);
}


/*
================
ShadowVolumeBsp

Create the shadow volume bsp for currentshadowlight
================
*/
bool ShadowVolumeBsp()
{
	MakeFrustum4Light(false);
	currentlightroot = R_CreateEmptyTree();
	//we use the same timestamp as for rendering (may cause errors maybe)
	r_visframecount++;
	if(R_MarkLightLeaves())
	{
		R_MarkShadowCasting (currentshadowlight, r_worldmodel->nodes);
///		shadowchain = NULL;
		num_scene_surfaces = 0;
///		svBsp_NumKeptPolys = 0;
		R_RecursiveShadowAdd(r_worldmodel->nodes);

/*		// пересортируем список shadowchain по номерам текстур (для этого временно поюзаем texturechain)
		int			i;
		image_t		*image;
		msurface_t	*s;

		r_temp_surfaces = NULL;
		for (i=0, image=gltextures ; i<numgltextures ; i++, image++)
		{
			if (!image->texnum)
				continue;

			for (s=shadowchain ; s ; s=s->shadowchain)
				if (s->texinfo->image == image)
				{
					s->texturechain = r_temp_surfaces;
					r_temp_surfaces = s;
				}
		}
		// перенесем цепочку из r_temp_surfaces в shadowchain (на законное место)
		shadowchain = NULL;
		for (s=r_temp_surfaces ; s ; s=s->texturechain)
		{
			s->shadowchain = shadowchain;
			shadowchain = s;
		}
		*/
		return true;
	}
	return false;
}


/*
================
R_CalcSLight

Called once for every static light
================
*/
bool R_CalcSLight()
{
	int i;
///	msurface_t *surf;

///	shadowchain = NULL;
	num_scene_surfaces = 0;
	r_lightTimestamp++;
	r_framecount++;

	if (gl_config.occlusion)
		glGenQueriesARB(1, &currentshadowlight->occID);

	//Calculate visible polygons
	if(!ShadowVolumeBsp())
	{
		currentshadowlight->volumeCmds = NULL;
		currentshadowlight->lightCmds = NULL;
		return false;
	}

	//Remark polys since polygons may have been removed since the last time stamp
	r_lightTimestamp++;
///	surf = shadowchain;
///	for (i=0; i<svBsp_NumKeptPolys; i++,surf = surf->shadowchain)
///		surf->polys->lightTimestamp = r_lightTimestamp;
	for (i=0; i<num_scene_surfaces; i++)
		scene_surfaces[i]->polys->lightTimestamp = r_lightTimestamp;

	//Precalculate the shadow volume
	PrecalcVolumesForLight(r_worldmodel);

	if(numLightCmds >= 1)
	{
		currentshadowlight->lightCmds = (lightcmd_t *)Z_Malloc(numLightCmds * 4, true);
		Memcpy(currentshadowlight->lightCmds, &lightCmdsBuff, numLightCmds * 4);
	}
	else
		currentshadowlight->lightCmds = NULL;

	if(numVolumeCmds >= 2)
	{
		currentshadowlight->volumeCmds = (lightcmd_t *)Z_Malloc(numVolumeCmds * 4, true);
		Memcpy(currentshadowlight->volumeCmds, &volumeCmdsBuff, numVolumeCmds * 4);
		CreateShadowVBO();
	}
	else
		currentshadowlight->volumeCmds = NULL;

	return (currentshadowlight->lightCmds != NULL || currentshadowlight->volumeCmds != NULL);
}


/*
Copy one rectangle to another.
*/
void R_RectPosCopy(screenrect_t *src, screenrect_t *dst)
{
	int i;
	for (i=0; i<4; i++)
		dst->coords[i] = src->coords[i]+50000;
}


/*
Check if rectangles overlap
returns true if they overlap
*/
bool R_RectOverlap(screenrect_t *r1, screenrect_t *r2)
{
	//r1 is rightof r2
	if (r1->coords[0] > r2->coords[0])
	{
		//overlap in x dir?
		if ((r1->coords[0] - r2->coords[2]) < 0)
		{
			//check y dir

			//r1 below r2
			if (r1->coords[1] > r2->coords[1])
			{
				if ((r1->coords[1]-r2->coords[3]) < 0)
					return true;
				else
					return false;
			//r1 above r2
			}
			else
			{
				if ((r2->coords[1]-r1->coords[3]) < 0) 
					return true;
				else 
					return false;
			}
		//no overlap in x- they don't overlap at all
		}
		else
			return false;

		//r2 is rightof r1
	}
	else
	{
		//overlap in x dir
		if ((r2->coords[0] - r1->coords[2]) < 0)
		{
			//check y dir

			//r1 below r2
			if (r1->coords[1] > r2->coords[1])
			{
				if ((r1->coords[1]-r2->coords[3]) < 0)
					return true;
				else
					return false;
			//r1 above r2
			}
			else
			{
				if ((r2->coords[1]-r1->coords[3]) < 0) 
					return true;
				else 
					return false;
			}
		//no overlap in x- they don't overlap at all
		}
		else
			return false;
	}
}


bool R_CheckRectList(screenrect_t *rec)
{
	screenrect_t *r;
	r = recList;
	while (r)
	{
		if (R_RectOverlap(rec,r))
			return false;
		r = r->next;
	}
	return true;
}


//Copy one rectangle to another.
void R_RectCopy(screenrect_t *src, screenrect_t *dst)
{
	int i;
	for (i=0; i<4; i++)
		dst->coords[i] = src->coords[i];
}


void R_RectsAdd(screenrect_t *add, screenrect_t *res)
{
	res->coords[0] = min (res->coords[0],add->coords[0]);
	res->coords[1] = min (res->coords[1],add->coords[1]);
	res->coords[2] = max (res->coords[2],add->coords[2]);
	res->coords[3] = max (res->coords[3],add->coords[3]);
}


void R_AddRectList(screenrect_t *rec)
{
	//Extend bounding rectangle
	if (!recList)
		R_RectCopy(rec,&totalRect);
	else
		R_RectsAdd(rec,&totalRect);

	/// Berserker: FIX - новые ATI драйверы стали по-другому работать с glScissor
	if (totalRect.coords[0] < r_viewport[0])	totalRect.coords[0] = r_viewport[0];
	if (totalRect.coords[1] < r_viewport[1])	totalRect.coords[1] = r_viewport[1];
	if (totalRect.coords[2] > r_viewport[2])	totalRect.coords[2] = r_viewport[2];
	if (totalRect.coords[3] > r_viewport[3])	totalRect.coords[3] = r_viewport[3];

	//Add it to the list
	rec->next = recList;
	recList = rec;
}


bool R_MarkShadowSurf(msurface_t *surf, shadowlight_t *light, bool world)
{
	cplane_t	*plane;
	float		dist;
	glpoly_t	*poly;

/// Berserker's hack: пусть (SURF_SKY+SURF_SLICK) участвует в затенении!
	if ((surf->texinfo->flags & SURF_SKY) && (surf->texinfo->flags & SURF_SLICK))
		goto yes;
///	if (surf->texinfo->image->CastShadow)		// Форсируем отброс тени
///		goto yes;
	//we don't cast shadows with water
	if ((surf->texinfo->flags & (SURF_TRANS33|SURF_TRANS66|SURF_SKY|SURF_WARP|SURF_NODRAW)) || (surf->flags & SURF_DRAWTURB))
		return false;

yes:plane = surf->plane;
	poly = surf->polys;

	if (poly->lightTimestamp == r_lightTimestamp)
		return false;

	switch (plane->type)
	{
	case PLANE_X:
		dist = light->origin[0] - plane->dist;
		break;
	case PLANE_Y:
		dist = light->origin[1] - plane->dist;
		break;
	case PLANE_Z:
		dist = light->origin[2] - plane->dist;
		break;
	default:
		dist = DotProduct (light->origin, plane->normal) - plane->dist;
		break;
	}

	//the normals are flipped when surf_planeback is 1
	if (((surf->flags & SURF_PLANEBACK) && (dist > 0)) ||
		(!(surf->flags & SURF_PLANEBACK) && (dist < 0)))
		return false;

	//the normals are flipped when surf_planeback is 1
	if (fabs(dist) > light->radius)
		return false;

	if(world)
	{
		float	lbbox[6], pbbox[6];

		lbbox[0] = light->mins_cone[0];
		lbbox[1] = light->mins_cone[1];
		lbbox[2] = light->mins_cone[2];
		lbbox[3] = light->maxs_cone[0];
		lbbox[4] = light->maxs_cone[1];
		lbbox[5] = light->maxs_cone[2];

		// surface bounding box
		pbbox[0] = surf->mins[0];
		pbbox[1] = surf->mins[1];
		pbbox[2] = surf->mins[2];
		pbbox[3] = surf->maxs[0];
		pbbox[4] = surf->maxs[1];
		pbbox[5] = surf->maxs[2];

		if(!BBoxIntersectBBox(lbbox, pbbox))
			return false;

		if(light->_cone && R_CullBox_(&pbbox[0], &pbbox[3], light->frust))
			return false;
	}

	// very soft (static only!) light, don't bother...
///	if (r_mirror)
///	{
///		if (light->isStatic && (light->radius - abs(dist))/light->radius < 0.2)
///			return false;
///	}
///	else
	{
		if (light->isStatic && (light->radius - fabs(dist))/light->radius < 0.1)
			return false;
	}

	poly->lightTimestamp = r_lightTimestamp;
	return true;
}


/*
=============
R_MarkShadowCasting

Fills the shadow chain with polygons we should consider.

Polygons that will be added are:
1. In the light volume. (sphere)
2. "Visible" to the light.

Visible is:
a. facing the light (dotprod > 0)
b. in a leaf that is visible from the light's leaf. (based on vis data)

This is crude for static lights we use extra tricks (svbsp / revis) to
reduce the number of polyons.
=============
*/
void R_MarkShadowCasting (shadowlight_t *light, mnode_t *node)
{
	cplane_t	*plane;
	float		dist;
	msurface_t	**surf;
	mleaf_t		*leaf;
	int			c;

	if (node->contents != -1)
	{
		//we are in a leaf
		leaf = (mleaf_t *)node;

		//is this leaf visible from the light
		int cluster = leaf->cluster;
		if (!(light->vis[cluster>>3] & (1<<(cluster&7))))
			return;

		surf = leaf->firstmarksurface;

		for (c=0; c<leaf->nummarksurfaces; c++, surf++)
		{
			if (R_MarkShadowSurf ((*surf), light, true))
			{
///				(*surf)->shadowchain = shadowchain;
///				shadowchain = (*surf);
				scene_surfaces[num_scene_surfaces++] = (*surf);
			}
		}
		return;
	}

	plane = node->plane;
	dist = DotProduct (light->origin, plane->normal) - plane->dist;

	if (dist > light->radius)
	{
		R_MarkShadowCasting (light, node->children[0]);
		return;
	}
	if (dist < -light->radius)
	{
		R_MarkShadowCasting (light, node->children[1]);
		return;
	}

	R_MarkShadowCasting (light, node->children[0]);
	R_MarkShadowCasting (light, node->children[1]);
}


bool R_FillShadowChain (shadowlight_t *light)
{
	R_MarkShadowCasting (light, r_worldmodel->nodes);
	return num_scene_surfaces;
}


/*
=============
GL_SetupCubeMapMatrix

Loads the current matrix with a tranformation used for light filters
=============
*/
void GL_SetupCubeMapMatrix(bool world)
{
	float	a, b, c;

	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();

	a = currentshadowlight->angles[2] + currentshadowlight->rspeed[2] * cl.leveltime;
	b = currentshadowlight->angles[0] + currentshadowlight->rspeed[0] * cl.leveltime;
	c = currentshadowlight->angles[1] + currentshadowlight->rspeed[1] * cl.leveltime;

	if (a)
		glRotatef ( -a,  1, 0, 0);
	if (b)
		glRotatef ( -b,  0, 1, 0);
	if (c)
		glRotatef ( -c,  0, 0, 1);

	if (!world)
	{
		if (currententity->angles[1])
			glRotatef (currententity->angles[1],  0, 0, 1);
		if (currententity->angles[0])
			glRotatef (currententity->angles[0],  0, 1, 0);		/// fixed "stupid quake bug" ;)
		if (currententity->angles[2])
			glRotatef (currententity->angles[2],  1, 0, 0);
	}

	glTranslatef(-currentshadowlight->origin[0], -currentshadowlight->origin[1], -currentshadowlight->origin[2]);
	glMatrixMode(GL_MODELVIEW);
}


bool	arb_vp_work = false;
void GL_EnableShaderARB (bool world, int shader)
{
//	no light filter:
//fragment.texcoord[0]	- texture coordinates
//fragment.texcoord[1]	- light vector (for diffuse bump)
//fragment.texcoord[2]	- light falloff coordinates
//fragment.texcoord[3]	- halfangle light vector (for specular bump)

//	with light filter:
//fragment.texcoord[0]	- texture coordinates
//fragment.texcoord[1]	- light vector (for diffuse bump)
//fragment.texcoord[2]	- light falloff coordinates
//fragment.texcoord[3]	- cubemap light filter coordinates
//fragment.texcoord[4]	- halfangle light vector (for specular bump)

//tex0	2D		normal map
//tex1	2D		color map
//tex2	3D		light falloff
//[tex3	CUBE	light filter]

	glEnable (GL_BLEND);
	GL_BlendFunc (GL_ONE, GL_ONE);

	GL_SelectTexture(GL_TEXTURE2);
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glTranslatef(0.5,0.5,0.5);
	glScalef(0.5/currentshadowlight->radiuses[0], 0.5/currentshadowlight->radiuses[1], 0.5/currentshadowlight->radiuses[2]);
	if (!world && !currentshadowlight->sphere)
	{
		glRotatef ( currententity->angles[1],  0, 0, 1);
		glRotatef ( currententity->angles[0],  0, 1, 0);
		glRotatef ( currententity->angles[2],  1, 0, 0);
	}
	glTranslatef(-currentshadowlight->origin[0], -currentshadowlight->origin[1], -currentshadowlight->origin[2]);
	glMatrixMode(GL_MODELVIEW);
	GL_Bind(GL_TEXTURE_3D, atten3d_texture_object->texnum);

	glEnable(GL_FRAGMENT_PROGRAM_ARB);
	bool	binded = false;
	if (currentshadowlight->nobump)
	{
		GL_BindProgramFP(fragment_program_ambient);
		binded = true;
	}
	else if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE3);
		if(currentshadowlight->filtercube_start>0)
		{
			int mul, tempo = currentshadowlight->filtercube_end - currentshadowlight->filtercube_start;
			if (tempo)
			{
				if (tempo > 0)
					mul = 1;
				else
					mul = -1;
				tempo = abs(tempo)+1;
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start+mul*(int(r_newrefdef.time*currentshadowlight->framerate)%tempo)-1]->texnum);
			}
			else
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start-1]->texnum);
		}
		else
			GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, -currentshadowlight->filtercube_start);
		GL_SetupCubeMapMatrix(world);

		if(shader==SHADER_ARB6)
		{
			if (gl_config.vp)
			{
				arb_vp_work = true;
				glEnable(GL_VERTEX_PROGRAM_ARB);
			}
		}
		else
		{
			if (gl_config.vp)
			{
				arb_vp_work = true;
				glEnable(GL_VERTEX_PROGRAM_ARB);
				GL_BindProgramVP(vertex_program_lightfilter4);
				glProgramLocalParameter4fvARB(GL_VERTEX_PROGRAM_ARB, 0, currentshadowlight->origin);
			}

			GL_BindProgramFP(fragment_program_filter4);
			binded = true;
		}
	}
	else
	{
		if (gl_config.vp)
		{
			arb_vp_work = true;
			glEnable(GL_VERTEX_PROGRAM_ARB);
			if (shader!=SHADER_ARB6)
			{
				GL_BindProgramVP(vertex_program_nolightfilter);
				glProgramLocalParameter4fvARB(GL_VERTEX_PROGRAM_ARB, 0, currentshadowlight->origin);
			}
		}

		if(shader!=SHADER_ARB6)
		{
			GL_BindProgramFP(fragment_program_nofilter);
			binded = true;
		}
	}

	if (binded)
		glProgramLocalParameter4fvARB(GL_FRAGMENT_PROGRAM_ARB, 1, currentshadowlight->styled_color);

	GL_Color3f(1,1,1);
}


void GL_DisableShaderARB ()
{
	if (arb_vp_work)
	{
		arb_vp_work = false;
		glDisable(GL_VERTEX_PROGRAM_ARB);
	}

	glDisable(GL_FRAGMENT_PROGRAM_ARB);

	GL_SelectTexture(GL_TEXTURE2);
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE3);
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
	}

	GL_SelectTexture(GL_TEXTURE0);

	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


void GL_EnableShaderATI4 (bool world)
{
//tex 0 = normal map
//tex 1 = normalization cube map (tangent space light vector)
//tex 2 = color map
//tex 3/4 = (attenuation or light filter, depends on light settings)

///	vec3_t scaler = {0.5f, 0.5f, 0.5f};
	glEnable (GL_BLEND);
	GL_BlendFunc (GL_ONE, GL_ONE);
///	glShadeModel (GL_SMOOTH);

	GL_SelectTexture(GL_TEXTURE1);
	glEnable(GL_TEXTURE_CUBE_MAP_ARB);
	GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, normcube_texture_object->texnum);

	GL_SelectTexture(GL_TEXTURE2);
	glEnable(GL_TEXTURE_2D);

	GL_SelectTexture(GL_TEXTURE3);
	GL_Color3fv( currentshadowlight->styled_color );
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glTranslatef(0.5,0.5,0.5);
	glScalef(0.5/currentshadowlight->radiuses[0], 0.5/currentshadowlight->radiuses[1], 0.5/currentshadowlight->radiuses[2]);
	if (!world && !currentshadowlight->sphere)
	{
		glRotatef ( currententity->angles[1],  0, 0, 1);
		glRotatef ( currententity->angles[0],  0, 1, 0);
		glRotatef ( currententity->angles[2],  1, 0, 0);
	}
	glTranslatef(-currentshadowlight->origin[0], -currentshadowlight->origin[1], -currentshadowlight->origin[2]);
	glMatrixMode(GL_MODELVIEW);
	glEnable(GL_TEXTURE_3D);
	GL_Bind(GL_TEXTURE_3D, atten3d_texture_object->texnum);

	glEnable(GL_FRAGMENT_SHADER_ATI);
	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glEnable(GL_TEXTURE_CUBE_MAP_ARB);
		if(currentshadowlight->filtercube_start>0)
		{
			int mul, tempo = currentshadowlight->filtercube_end - currentshadowlight->filtercube_start;
			if (tempo)
			{
				if (tempo > 0)
					mul = 1;
				else
					mul = -1;
				tempo = abs(tempo)+1;
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start+mul*(int(r_newrefdef.time*currentshadowlight->framerate)%tempo)-1]->texnum);
			}
			else
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start-1]->texnum);
		}
		else
			GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, -currentshadowlight->filtercube_start);
		GL_SetupCubeMapMatrix(world);

		glBindFragmentShaderATI( fragment_shaders + 1 );
	}
	else
		glBindFragmentShaderATI( fragment_shaders );

	GL_SelectTexture(GL_TEXTURE0);
	glEnable(GL_TEXTURE_2D);
}


void GL_DisableShaderATI4 ()
{
	glDisable(GL_FRAGMENT_SHADER_ATI);

///	glShadeModel (GL_FLAT);

	GL_SelectTexture(GL_TEXTURE1);
	glDisable(GL_TEXTURE_CUBE_MAP_ARB);

	GL_SelectTexture(GL_TEXTURE2);
	glDisable(GL_TEXTURE_2D);

	GL_SelectTexture(GL_TEXTURE3);
	glDisable(GL_TEXTURE_3D);
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	GL_Color3f(1,1,1);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glDisable(GL_TEXTURE_CUBE_MAP_ARB);
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
	}

	GL_SelectTexture(GL_TEXTURE0);

	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


void GL_EnableShaderATI6 (bool world)
{
//tex 0 = normal map
//tex 1 = normalization cube map (tangent space light vector)
//tex 2 = normalization cube map (tangent space half vector)
//tex 3 = color map
//tex 4/5 = (attenuation or light filter, depends on light settings)

///	vec3_t scaler = {0.5f, 0.5f, 0.5f};
	glEnable (GL_BLEND);
	GL_BlendFunc (GL_ONE, GL_ONE);
///	glShadeModel (GL_SMOOTH);

	GL_SelectTexture(GL_TEXTURE1);
	glEnable(GL_TEXTURE_CUBE_MAP_ARB);
	GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, normcube_texture_object->texnum);

	GL_SelectTexture(GL_TEXTURE2);
	glEnable(GL_TEXTURE_CUBE_MAP_ARB);
	GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, normcube_texture_object->texnum);

	GL_SelectTexture(GL_TEXTURE3);
	glEnable(GL_TEXTURE_2D);

	GL_SelectTexture(GL_TEXTURE4);
	GL_Color3fv( currentshadowlight->styled_color );
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glTranslatef(0.5,0.5,0.5);
	glScalef(0.5/currentshadowlight->radiuses[0], 0.5/currentshadowlight->radiuses[1], 0.5/currentshadowlight->radiuses[2]);
	if (!world && !currentshadowlight->sphere)
	{
		glRotatef ( currententity->angles[1],  0, 0, 1);
		glRotatef ( currententity->angles[0],  0, 1, 0);
		glRotatef ( currententity->angles[2],  1, 0, 0);
	}
	glTranslatef(-currentshadowlight->origin[0], -currentshadowlight->origin[1], -currentshadowlight->origin[2]);
	glMatrixMode(GL_MODELVIEW);
	glEnable(GL_TEXTURE_3D);
	GL_Bind(GL_TEXTURE_3D, atten3d_texture_object->texnum);

	glEnable(GL_FRAGMENT_SHADER_ATI);
	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE5);
		glEnable(GL_TEXTURE_CUBE_MAP_ARB);
		if(currentshadowlight->filtercube_start>0)
		{
			int mul, tempo = currentshadowlight->filtercube_end - currentshadowlight->filtercube_start;
			if (tempo)
			{
				if (tempo > 0)
					mul = 1;
				else
					mul = -1;
				tempo = abs(tempo)+1;
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start+mul*(int(r_newrefdef.time*currentshadowlight->framerate)%tempo)-1]->texnum);
			}
			else
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start-1]->texnum);
		}
		else
			GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, -currentshadowlight->filtercube_start);
		GL_SetupCubeMapMatrix(world);

		glBindFragmentShaderATI( fragment_shaders + 1 );
	}
	else
		glBindFragmentShaderATI( fragment_shaders );

	GL_SelectTexture(GL_TEXTURE0);
	glEnable(GL_TEXTURE_2D);
}


void GL_DisableShaderATI6 ()
{
	glDisable(GL_FRAGMENT_SHADER_ATI);

///	glShadeModel (GL_FLAT);

	GL_SelectTexture(GL_TEXTURE1);
	glDisable(GL_TEXTURE_CUBE_MAP_ARB);

	GL_SelectTexture(GL_TEXTURE2);
	glDisable(GL_TEXTURE_CUBE_MAP_ARB);

	GL_SelectTexture(GL_TEXTURE3);
	glDisable(GL_TEXTURE_2D);

	GL_SelectTexture(GL_TEXTURE4);
	glDisable(GL_TEXTURE_3D);
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	GL_Color3f(1,1,1);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE5);
		glDisable(GL_TEXTURE_CUBE_MAP_ARB);
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
	}

	GL_SelectTexture(GL_TEXTURE0);

	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


void GL_EnableShaderNVIDIA4 (bool world, bool onepass)
{	/// Ограничение данного шейдера: Если есть светофильтр, то свет берется белый
//	tex 0 = normalization cube map (tangent space half angle) for bumpmap
//	tex 1 = bump map + Alpha (gloss map)
//	tex 2 = attenuation of light / light filter cubemap
//	tex 3 = texture RGB

	glEnable (GL_BLEND);
	GL_Color3f(1,1,1);

	GL_SelectTexture( GL_TEXTURE3 );
	glEnable(GL_TEXTURE_2D);

	GL_SelectTexture( GL_TEXTURE2 );
	if (onepass)
	{
		glBlendColor( currentshadowlight->styled_color[0], currentshadowlight->styled_color[1], currentshadowlight->styled_color[2], 1 );
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glTranslatef(0.5,0.5,0.5);
		glScalef(0.5/currentshadowlight->radiuses[0], 0.5/currentshadowlight->radiuses[1], 0.5/currentshadowlight->radiuses[2]);
		if (!world && !currentshadowlight->sphere)
		{
			glRotatef ( currententity->angles[1],  0, 0, 1);
			glRotatef ( currententity->angles[0],  0, 1, 0);
			glRotatef ( currententity->angles[2],  1, 0, 0);
		}
		glTranslatef(-currentshadowlight->origin[0], -currentshadowlight->origin[1], -currentshadowlight->origin[2]);
		glMatrixMode(GL_MODELVIEW);
		glEnable(GL_TEXTURE_3D);
		GL_Bind(GL_TEXTURE_3D, atten3d_texture_object->texnum);
		GL_BlendFunc( GL_CONSTANT_COLOR, GL_ONE );
	}
	else
	{
		glEnable(GL_TEXTURE_CUBE_MAP_ARB);
		if(currentshadowlight->filtercube_start>0)
		{
			int mul, tempo = currentshadowlight->filtercube_end - currentshadowlight->filtercube_start;
			if (tempo)
			{
				if (tempo > 0)
					mul = 1;
				else
					mul = -1;
				tempo = abs(tempo)+1;
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start+mul*(int(r_newrefdef.time*currentshadowlight->framerate)%tempo)-1]->texnum);
			}
			else
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start-1]->texnum);
		}
		else
			GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, -currentshadowlight->filtercube_start);
		GL_SetupCubeMapMatrix(world);
		GL_BlendFunc (GL_DST_ALPHA, GL_ONE);
	}

	GL_SelectTexture( GL_TEXTURE1 );
	glEnable(GL_TEXTURE_2D);

	GL_SelectTexture( GL_TEXTURE0 );
	glEnable(GL_TEXTURE_CUBE_MAP_ARB);
	GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, normcube_texture_object->texnum);

	glEnable(GL_REGISTER_COMBINERS_NV);
}


void GL_DisableShaderNVIDIA4 (bool onepass)
{
	GL_SelectTexture( GL_TEXTURE3 );
	glDisable(GL_TEXTURE_2D);

	glDisable(GL_REGISTER_COMBINERS_NV);
	GL_SelectTexture( GL_TEXTURE2 );
	if (onepass)
	{
		glDisable(GL_TEXTURE_3D);
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
	}
	else
	{
		glDisable(GL_TEXTURE_CUBE_MAP_ARB);
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
	}

	GL_SelectTexture( GL_TEXTURE1 );
	glDisable(GL_TEXTURE_2D);

	GL_SelectTexture( GL_TEXTURE0 );
	glDisable(GL_TEXTURE_CUBE_MAP_ARB);
	gl_TexEnv(GL_MODULATE);

	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


void GL_EnableShaderGENERIC (bool world, bool onepass)
{	/// Ограничение шейдера SHADER_GENERIC4: Если есть светофильтр, то свет берется белый
// For 4 TMUs:
//	tex 0 = normalization cube map (tangent space half angle) for bumpmap
//	tex 1 = bump map + Alpha (gloss map)
//	tex 2 = attenuation of light / Light Filter cubemap
//	tex 3 = texture RGB

// For 6 TMUs:
//	tex 0..3 = ---//---
//	tex 4 = Light Filter cubemap

///	vec3_t scaler = {0.5f, 0.5f, 0.5f};
	glEnable (GL_BLEND);

	GL_SelectTexture( GL_TEXTURE0 );
	glEnable(GL_TEXTURE_CUBE_MAP_ARB);
	GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, normcube_texture_object->texnum);
	gl_TexEnv(GL_REPLACE);

	GL_SelectTexture( GL_TEXTURE1 );
	glEnable(GL_TEXTURE_2D);
	gl_TexEnv( GL_COMBINE_ARB );
	GL_TexEnv( GL_COMBINE_RGB_ARB, GL_DOT3_RGB_ARB );
	GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB );
	GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
	GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_TEXTURE );
	GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );

	GL_SelectTexture( GL_TEXTURE2 );
	if(onepass)
	{
		glBlendColor( currentshadowlight->styled_color[0], currentshadowlight->styled_color[1], currentshadowlight->styled_color[2], 1 );
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glTranslatef(0.5,0.5,0.5);
		glScalef(0.5/currentshadowlight->radiuses[0], 0.5/currentshadowlight->radiuses[1], 0.5/currentshadowlight->radiuses[2]);
		if (!world && !currentshadowlight->sphere)
		{
			glRotatef ( currententity->angles[1],  0, 0, 1);
			glRotatef ( currententity->angles[0],  0, 1, 0);
			glRotatef ( currententity->angles[2],  1, 0, 0);
		}
		glTranslatef(-currentshadowlight->origin[0], -currentshadowlight->origin[1], -currentshadowlight->origin[2]);
		glMatrixMode(GL_MODELVIEW);
		glEnable(GL_TEXTURE_3D);
		GL_Bind(GL_TEXTURE_3D, atten3d_texture_object->texnum);
		GL_BlendFunc( GL_CONSTANT_COLOR, GL_ONE );
	}
	else
	{
		GL_Color3fv( currentshadowlight->styled_color );
		glEnable(GL_TEXTURE_CUBE_MAP_ARB);
		if(currentshadowlight->filtercube_start>0)
		{
			int mul, tempo = currentshadowlight->filtercube_end - currentshadowlight->filtercube_start;
			if (tempo)
			{
				if (tempo > 0)
					mul = 1;
				else
					mul = -1;
				tempo = abs(tempo)+1;
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start+mul*(int(r_newrefdef.time*currentshadowlight->framerate)%tempo)-1]->texnum);
			}
			else
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start-1]->texnum);
		}
		else
			GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, -currentshadowlight->filtercube_start);
		GL_SetupCubeMapMatrix(world);
		GL_BlendFunc (GL_DST_ALPHA, GL_ONE);
	}

	gl_TexEnv( GL_COMBINE_ARB );
	GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
	GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
	GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
	GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
	GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );

	GL_SelectTexture( GL_TEXTURE3 );
	glEnable(GL_TEXTURE_2D);
	gl_TexEnv( GL_COMBINE_ARB );
	GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
	GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
	GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
	GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
	GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
	GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
	GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_TEXTURE );
	GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
	if(r_overbright->value)
		GL_TexEnv( GL_RGB_SCALE_ARB, 4 );						// gain RGB up to 4
	else
		GL_TexEnv( GL_RGB_SCALE_ARB, 2 );						// gain RGB up to 2

	if(r_shader->value == SHADER_GENERIC6 && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture( GL_TEXTURE4 );
		glEnable(GL_TEXTURE_CUBE_MAP_ARB);
		if(currentshadowlight->filtercube_start>0)
		{
			int mul, tempo = currentshadowlight->filtercube_end - currentshadowlight->filtercube_start;
			if (tempo)
			{
				if (tempo > 0)
					mul = 1;
				else
					mul = -1;
				tempo = abs(tempo)+1;
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start+mul*(int(r_newrefdef.time*currentshadowlight->framerate)%tempo)-1]->texnum);
			}
			else
				GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, filtercube_texture_object[currentshadowlight->filtercube_start-1]->texnum);
		}
		else
			GL_Bind(GL_TEXTURE_CUBE_MAP_ARB, -currentshadowlight->filtercube_start);
		GL_SetupCubeMapMatrix(world);
		gl_TexEnv( GL_COMBINE_ARB );
		GL_TexEnv( GL_COMBINE_RGB_ARB, GL_MODULATE );
		GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
		GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
		GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
		GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );
		GL_TexEnv( GL_COMBINE_ALPHA_ARB, GL_REPLACE );
		GL_TexEnv( GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB );
		GL_TexEnv( GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA );
	}
}


void GL_DisableShaderGENERIC (bool onepass)
{
	if(r_shader->value == SHADER_GENERIC6 && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture( GL_TEXTURE4 );
		glDisable(GL_TEXTURE_CUBE_MAP_ARB);
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
	}

	GL_SelectTexture( GL_TEXTURE3 );
	GL_TexEnv( GL_RGB_SCALE_ARB, 1 );
	glDisable(GL_TEXTURE_2D);

	GL_SelectTexture( GL_TEXTURE2 );
	if(onepass)
	{
		glDisable(GL_TEXTURE_3D);
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
		glBlendColor(1,1,1,1);
	}
	else
	{
		glDisable(GL_TEXTURE_CUBE_MAP_ARB);
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
		GL_Color3f(1,1,1);
	}

	GL_SelectTexture( GL_TEXTURE1 );
	glDisable(GL_TEXTURE_2D);

	GL_SelectTexture( GL_TEXTURE0 );
	glDisable(GL_TEXTURE_CUBE_MAP_ARB);
	gl_TexEnv(GL_MODULATE);

	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


void GL_EnableAttenuationShader (bool world)
{
	glDisable(GL_BLEND);
	GL_Color4f(0,0,0,(currentshadowlight->styled_color[0] + currentshadowlight->styled_color[1] + currentshadowlight->styled_color[2]) * 0.333333f);
	GL_SelectTexture( GL_TEXTURE0 );
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glTranslatef(0.5,0.5,0.5);
	glScalef(0.5/currentshadowlight->radiuses[0], 0.5/currentshadowlight->radiuses[1], 0.5/currentshadowlight->radiuses[2]);
	if (!world && !currentshadowlight->sphere)
	{
		glRotatef ( currententity->angles[1],  0, 0, 1);
		glRotatef ( currententity->angles[0],  0, 1, 0);
		glRotatef ( currententity->angles[2],  1, 0, 0);
	}
	glTranslatef(-currentshadowlight->origin[0], -currentshadowlight->origin[1], -currentshadowlight->origin[2]);
	glMatrixMode(GL_MODELVIEW);
	glEnable(GL_TEXTURE_3D);
	GL_Bind(GL_TEXTURE_3D, atten3d_texture_object->texnum);
	gl_TexEnv(GL_MODULATE);
}


void GL_DisableAttenuationShader()
{
	GL_SelectTexture( GL_TEXTURE0 );
	glDisable(GL_TEXTURE_3D);
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	GL_Color3f(1,1,1);
}


bool R_AddLightedPolyATI4( msurface_t *surf, bool chtex, unsigned *Base, unsigned *idxBase, lightcmd_u *lightCmds, int *lightPos )
{
	image_t		*tex, *bump;
	unsigned	BaseVerts, BaseIdx, BaseLightPos;
	int		i, num = surf->numedges;
	BaseVerts = *Base;
	if (BaseVerts + num > MAX_BATCH_SURFS)
		return false;	// force the start new batch
	BaseLightPos = *lightPos;
	BaseIdx = *idxBase;
	mtexinfo_t	*tx, *t = surf->texinfo;
	float	*v;
	glpoly_t *p;
	float	scroll;

	c_light_brush_polys++;

	if (chtex)
	{
		tx = R_TextureAnimation( surf->texinfo );
		tex = tx->image;
		if(surf->texinfo->bump->texnum && !currentshadowlight->nobump)
			bump = tx->bump;
		else
			bump = r_defaultbump;

		GL_MBind2D(GL_TEXTURE0, bump->texnum);
		GL_MBind2D(GL_TEXTURE2, tex->texnum);
	}

	if (surf->texinfo->flags & SURF_FLOWING)
	{
		scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
		if(scroll == 0.0)
			scroll = -64.0;
	}
	else
		scroll = 0;

	// create indexes
	if (BaseIdx == 0xffffffff)	BaseIdx = 0;
	for (i=0; i<num-2; i++)
	{
		indexArray[BaseIdx++] = BaseVerts;
		indexArray[BaseIdx++] = BaseVerts + i + 1;
		indexArray[BaseIdx++] = BaseVerts + i + 2;
	}

	p = surf->polys;
	v = p->verts[0];
	for (i=0 ; i < num; i++, v+= VERTEXSIZE, BaseVerts++)
	{
		nTexArray[BaseVerts][0] = lightCmds[BaseLightPos].asFloat;
		nTexArray[BaseVerts][1] = lightCmds[BaseLightPos+1].asFloat;
		nTexArray[BaseVerts][2] = lightCmds[BaseLightPos+2].asFloat;
		wTexArray[BaseVerts][0] = v[3] + scroll;
		wTexArray[BaseVerts][1] = v[4];
		VectorCopy(v, wVertexArray[BaseVerts]);
		BaseLightPos+=3;
	}

	*Base = BaseVerts;
	*idxBase = BaseIdx;
	*lightPos = BaseLightPos;
	return true;
}

void R_DrawWorldATI4(lightcmd_u *lightCmds)
{
	mtexinfo_t	*t;
	int num, lightPos = 0;
	msurface_t *surf;
///	vec3_t	LightP[MAX_POLY_VERT], vertexArray[MAX_POLY_VERT];
///	float	stArray[MAX_POLY_VERT][2];

	GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, wTexArray/*stArray*/);

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, nTexArray/*LightP*/);

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, wTexArray/*stArray*/);

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);

	bool		chtex;
	unsigned	oldtex = 0xffffffff;
	unsigned	vertCounter = 0, idxCounter = 0xffffffff;

	while (1)
	{
		surf = (msurface_t *)lightCmds[lightPos++].asVoid;
		if(!surf)
		{
			if (idxCounter != 0xffffffff)
			{
				c_world_batches_lighted++;
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///				else
					glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
			}
			break; //end of list
		}

		num = surf->numedges;
		t = surf->texinfo;
///		Berserker's hack: пусть (SURF_SKY+SURF_SLICK) участвует в затенении, но не рисуется в бампмапе!
		if((surf->drawframe != r_framecount) || ((surf->texinfo->flags & SURF_SKY) && (surf->texinfo->flags & SURF_SLICK)))		/// Если рисуемо-освещаемая поверхность не видна, то не рисуем
			lightPos += num * 3;
		else
		{
			if (t->image->texnum != oldtex)
			{	// смена текстуры
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				oldtex = t->image->texnum;
				chtex = true;
			}
			else
				chtex = false;

repeat:		if (!R_AddLightedPolyATI4(surf, chtex, &vertCounter, &idxCounter, lightCmds, &lightPos))
			{
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				goto repeat;
			}
		}
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


void CL_TeleportParticles (vec3_t org)
{
	int			i, j, k;
	cparticle_t	*p;
	float		vel;
	vec3_t		dir;

	for (i=-16 ; i<=16 ; i+=4)
		for (j=-16 ; j<=16 ; j+=4)
			for (k=-16 ; k<=32 ; k+=4)
			{
				if (!free_particles)
					return;
				p = free_particles;
				free_particles = p->next;
				p->next = active_particles;
				active_particles = p;

				p->flags = 0;
				p->time = cl.gameTime;
				p->color_r0 = 128 + (rand()&127);
				p->color_g0 = 128 + (rand()&127);
				p->color_b0 = 128 + (rand()&127);
				p->color_r1 = p->color_g1 = p->color_b1 = 0;

				p->alpha = 1.0;
				p->alphavel = -1.0 / (0.3 + (rand()&7) * 0.02);

				p->lastOrg[0] = p->org[0] = org[0] + i + (rand()&3);
				p->lastOrg[1] = p->org[1] = org[1] + j + (rand()&3);
				p->lastOrg[2] = p->org[2] = org[2] + k + (rand()&3);

				dir[0] = j*8;
				dir[1] = i*8;
				dir[2] = k*8;

				VectorNormalize (dir);						
				vel = 50 + (rand()&63);
				VectorScale (dir, vel, p->vel);

				p->accel[0] = p->accel[1] = 0;
				p->accel[2] = -PARTICLE_GRAVITY;
				p->type = part_simple;
			}
}


bool R_AddLightedPolyATI6( msurface_t *surf, bool chtex, unsigned *Base, unsigned *idxBase, lightcmd_u *lightCmds, int *lightPos )
{
	vec3_t	H;
	image_t		*tex, *bump;
	unsigned	BaseVerts, BaseIdx, BaseLightPos;
	int		i, num = surf->numedges;
	BaseVerts = *Base;
	if (BaseVerts + num > MAX_BATCH_SURFS)
		return false;	// force the start new batch
	BaseLightPos = *lightPos;
	BaseIdx = *idxBase;
	mtexinfo_t	*tx, *t = surf->texinfo;
	float	*v;
	glpoly_t *p;
	float	scroll;

	c_light_brush_polys++;

	if (chtex)
	{
		tx = R_TextureAnimation( surf->texinfo );
		tex = tx->image;
		if(surf->texinfo->bump->texnum && !currentshadowlight->nobump)
			bump = tx->bump;
		else
			bump = r_defaultbump;

		GL_MBind2D(GL_TEXTURE0, bump->texnum);
		GL_MBind2D(GL_TEXTURE3, tex->texnum);
	}

	if (surf->texinfo->flags & SURF_FLOWING)
	{
		scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
		if(scroll == 0.0)
			scroll = -64.0;
	}
	else
		scroll = 0;

	// create indexes
	if (BaseIdx == 0xffffffff)	BaseIdx = 0;
	for (i=0; i<num-2; i++)
	{
		indexArray[BaseIdx++] = BaseVerts;
		indexArray[BaseIdx++] = BaseVerts + i + 1;
		indexArray[BaseIdx++] = BaseVerts + i + 2;
	}

	p = surf->polys;
	v = p->verts[0];
	for (i=0 ; i < num; i++, v+= VERTEXSIZE, BaseVerts++)
	{
		tTexArray[BaseVerts][0] = lightCmds[BaseLightPos].asFloat;
		tTexArray[BaseVerts][1] = lightCmds[BaseLightPos+1].asFloat;
		tTexArray[BaseVerts][2] = lightCmds[BaseLightPos+2].asFloat;
///		VectorNormalize(LightP[i]);		// not needs, already normalized!
		VectorSubtract(r_origin, v, H);
///		VectorNormalize(H);		// дает тупые артефакты в спекуларе

		bTexArray[BaseVerts][2] = H[0]*v[7]+H[1]*v[8]+H[2]*v[9];
		bTexArray[BaseVerts][0] = H[0]*v[10]+H[1]*v[11]+H[2]*v[12];
		bTexArray[BaseVerts][1] = H[0]*v[13]+H[1]*v[14]+H[2]*v[15];

		VectorAdd(tTexArray[BaseVerts], bTexArray[BaseVerts], bTexArray[BaseVerts]);

		wTexArray[BaseVerts][0] = v[3] + scroll;
		wTexArray[BaseVerts][1] = v[4];
		VectorCopy(v, wVertexArray[BaseVerts]);

		BaseLightPos+=3;
	}

	*Base = BaseVerts;
	*idxBase = BaseIdx;
	*lightPos = BaseLightPos;
	return true;
}

void R_DrawWorldATI6(lightcmd_u *lightCmds)
{
	mtexinfo_t	*t;
	int num, lightPos = 0;
	msurface_t *surf;
///	vec3_t	LightP[MAX_POLY_VERT], tsH[MAX_POLY_VERT], vertexArray[MAX_POLY_VERT];
///	float	stArray[MAX_POLY_VERT][2];

	GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, wTexArray/*stArray*/);

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, tTexArray/*LightP*/);

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, bTexArray/*tsH*/);

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, wTexArray/*stArray*/);

	GL_SelectTexture(GL_TEXTURE4);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE5);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);

	bool		chtex;
	unsigned	oldtex = 0xffffffff;
	unsigned	vertCounter = 0, idxCounter = 0xffffffff;

	while (1)
	{
		surf = (msurface_t *)lightCmds[lightPos++].asVoid;
		if(!surf)
		{
			if (idxCounter != 0xffffffff)
			{
				c_world_batches_lighted++;
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///				else
					glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
			}
			break; //end of list
		}

		num = surf->numedges;
		t = surf->texinfo;
///		Berserker's hack: пусть (SURF_SKY+SURF_SLICK) участвует в затенении, но не рисуется в бампмапе!
		if((surf->drawframe != r_framecount) || ((surf->texinfo->flags & SURF_SKY) && (surf->texinfo->flags & SURF_SLICK)))		/// Если рисуемо-освещаемая поверхность не видна, то не рисуем
			lightPos += num * 3;
		else
		{
			if (t->image->texnum != oldtex)
			{	// смена текстуры
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				oldtex = t->image->texnum;
				chtex = true;
			}
			else
				chtex = false;

repeat:		if (!R_AddLightedPolyATI6(surf, chtex, &vertCounter, &idxCounter, lightCmds, &lightPos))
			{
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				goto repeat;
			}
		}
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE5);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE4);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


bool GL_BindARB(bool parallax)
{
	parallax = parallax && (r_parallax->value);
	if (currentshadowlight->filtercube_start)
	{
		if (gl_config.vp)
		{
			if (parallax)
				GL_BindProgramVP(vertex_program_lightfilter6_parallax);
			else
				GL_BindProgramVP(vertex_program_lightfilter6);
			glProgramLocalParameter4fvARB(GL_VERTEX_PROGRAM_ARB, 0, currentshadowlight->origin);
		}

		if (parallax && gl_config.vp)
			GL_BindProgramFP(fragment_program_filter_parallax);
		else
		{
			if (r_detailed_bump->value)
				GL_BindProgramFP(fragment_program_filter6_detail);
			else
				GL_BindProgramFP(fragment_program_filter);
		}

	}
	else
	{
		if (gl_config.vp)
		{
			if (parallax)
			{
				if (currentshadowlight->nobump)
					GL_BindProgramVP(vertex_program_nobump_parallax);
				else
				{
					GL_BindProgramVP(vertex_program_nolightfilter_parallax);
					glProgramLocalParameter4fvARB(GL_VERTEX_PROGRAM_ARB, 0, currentshadowlight->origin);
				}
			}
			else
			{
				GL_BindProgramVP(vertex_program_nolightfilter);
				glProgramLocalParameter4fvARB(GL_VERTEX_PROGRAM_ARB, 0, currentshadowlight->origin);
			}
		}

		if (parallax && gl_config.vp)
		{
			if (currentshadowlight->nobump)
				GL_BindProgramFP(fragment_program_nobump_parallax);
			else
				GL_BindProgramFP(fragment_program_nofilter_parallax);
		}
		else
		{
			if (r_detailed_bump->value)
				GL_BindProgramFP(fragment_program_nofilter6_detail);
			else
				GL_BindProgramFP(fragment_program_nofilter);
		}
	}
	glProgramLocalParameter4fvARB(GL_FRAGMENT_PROGRAM_ARB, 1, currentshadowlight->styled_color);
	return parallax;
}


bool R_AddLightedPolyARB( msurface_t *surf, int shader, bool chtex, unsigned *Base, unsigned *idxBase, lightcmd_u *lightCmds, int *lightPos, bool need_tsH, bool skipp )
{
	vec3_t	H;
	image_t		*tex, *bump;
	unsigned	BaseVerts, BaseIdx, BaseLightPos;
	int		i, num = surf->numedges;
	BaseVerts = *Base;
	if (BaseVerts + num > MAX_BATCH_SURFS)
		return false;	// force the start new batch
	BaseLightPos = *lightPos;
	BaseIdx = *idxBase;
	mtexinfo_t	*tx, *t = surf->texinfo;
	float	*v;
	glpoly_t *p;
	float	scroll;

	c_light_brush_polys++;

	if (chtex)
	{
		tx = R_TextureAnimation( surf->texinfo );
		tex = tx->image;
		if(surf->texinfo->bump->texnum && !currentshadowlight->nobump)
			bump = tx->bump;
		else
			bump = r_defaultbump;

		if (!currentshadowlight->nobump)
		{
			GL_MBind2D(GL_TEXTURE0, bump->texnum);
			if (shader == SHADER_ARB6)
			{
				GL_BindARB(false);
				if (r_detailed_bump->value)
				{
					GL_MBind2D(GL_TEXTURE5, R_GetIMGFromName2(tex, r_defaultbump_detail));
					glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, tex->detail_scale, tex->detail_scale, tex->detail_scale, tex->detail_scale);
				}
			}
		}
		GL_MBind2D(GL_TEXTURE1, tex->texnum);
	}

	if (t->flags & SURF_FLOWING)
	{
		scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
		if(scroll == 0.0)
			scroll = -64.0;
	}
	else
		scroll = 0;

	// create indexes
	if (BaseIdx == 0xffffffff)	BaseIdx = 0;
	for (i=0; i<num-2; i++)
	{
		indexArray[BaseIdx++] = BaseVerts;
		indexArray[BaseIdx++] = BaseVerts + i + 1;
		indexArray[BaseIdx++] = BaseVerts + i + 2;
	}

	p = surf->polys;
	v = p->verts[0];
	for (i=0 ; i < num; i++, v+= VERTEXSIZE, BaseVerts++)
	{
		if (!currentshadowlight->nobump)
		{
			tTexArray[BaseVerts][0] = lightCmds[BaseLightPos].asFloat;
			tTexArray[BaseVerts][1] = lightCmds[BaseLightPos+1].asFloat;
			tTexArray[BaseVerts][2] = lightCmds[BaseLightPos+2].asFloat;
///			VectorNormalize(tTexArray[BaseVerts]);		// not needs, already normalized!

			if (need_tsH)
			{
				VectorSubtract(r_origin, v, H);
///				VectorNormalize(H);		// нормализация дает артефакт в спекуларе!!!

				bTexArray[BaseVerts][2] = H[0]*v[7]+H[1]*v[8]+H[2]*v[9];
				bTexArray[BaseVerts][0] = H[0]*v[10]+H[1]*v[11]+H[2]*v[12];
				bTexArray[BaseVerts][1] = H[0]*v[13]+H[1]*v[14]+H[2]*v[15];

				VectorAdd(tTexArray[BaseVerts], bTexArray[BaseVerts], bTexArray[BaseVerts]);
			}
		}

		wTexArray[BaseVerts][0] = v[3] + scroll;
		wTexArray[BaseVerts][1] = v[4];
		VectorCopy(v, wVertexArray[BaseVerts]);

		if (skipp)
			BaseLightPos+=3;
	}

	*Base = BaseVerts;
	*idxBase = BaseIdx;
	*lightPos = BaseLightPos;
	return true;
}


void R_DrawWorldARB(lightcmd_u *lightCmds, int shader)
{
	mtexinfo_t	*t;
	int num, lightPos = 0;
	msurface_t *surf;
///	vec3_t	LightP[MAX_POLY_VERT], tsH[MAX_POLY_VERT], vertexArray[MAX_POLY_VERT];
///	float	stArray[MAX_POLY_VERT][2];
	bool	need_tsH;
	bool	skipp = (!(gl_config.vp && (r_shader->value == SHADER_ARB4 || r_shader->value == SHADER_ARB6)));

	GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, wTexArray/*stArray*/);

	if (!currentshadowlight->nobump)
	{
		GL_SelectTexture(GL_TEXTURE1);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(3, GL_FLOAT, 0, tTexArray/*LightP*/);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);

	if (!currentshadowlight->nobump)
	{
		GL_SelectTexture(GL_TEXTURE3);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (currentshadowlight->filtercube_start)
		{
			glTexCoordPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);
			need_tsH = false;
		}
		else
		{
			glTexCoordPointer(3, GL_FLOAT, 0, bTexArray/*tsH*/);
			need_tsH = true;
		}

		if (shader==SHADER_ARB6 && currentshadowlight->filtercube_start)
		{
			GL_SelectTexture(GL_TEXTURE4);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			glTexCoordPointer(3, GL_FLOAT, 0, bTexArray/*tsH*/);
			need_tsH = true;
		}
	}
	else
		need_tsH = false;

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);

	bool		chtex;
	unsigned	oldtex = 0xffffffff;
	unsigned	vertCounter = 0, idxCounter = 0xffffffff;

	while (1)
	{
		surf = (msurface_t *)lightCmds[lightPos++].asVoid;
		if(!surf)
		{
			if (idxCounter != 0xffffffff)
			{
				c_world_batches_lighted++;
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///				else
					glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
			}
			break; //end of list
		}

		num = surf->numedges;
		t = surf->texinfo;
///		Berserker's hack: пусть (SURF_SKY+SURF_SLICK) участвует в затенении, но не рисуется в бампмапе!
		if((surf->drawframe != r_framecount) || ((surf->texinfo->flags & SURF_SKY) && (surf->texinfo->flags & SURF_SLICK)))		/// Если рисуемо-освещаемая поверхность не видна, то не рисуем
		{
			if (skipp)
				lightPos += num * 3;
		}
		else
		{
			if (t->image->texnum != oldtex)
			{	// смена текстуры
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				oldtex = t->image->texnum;
				chtex = true;
			}
			else
				chtex = false;

repeat:		if (!R_AddLightedPolyARB(surf, shader, chtex, &vertCounter, &idxCounter, lightCmds, &lightPos, need_tsH, skipp))
			{
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				goto repeat;
			}
		}
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	if (!currentshadowlight->nobump)
	{
		if (shader==SHADER_ARB6 && currentshadowlight->filtercube_start)
		{
			GL_SelectTexture(GL_TEXTURE4);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		}

		GL_SelectTexture(GL_TEXTURE3);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	if (!currentshadowlight->nobump)
	{
		GL_SelectTexture(GL_TEXTURE1);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


bool R_AddLightedPolyARB_vp( msurface_t *surf, int shader, bool chtex, unsigned *Base, unsigned *idxBase )
{
	mtexinfo_t	*tx, *t = surf->texinfo;
	image_t		*tex, *bump;
	unsigned	BaseVerts, BaseIdx;
	int		i, num = surf->numedges;
	BaseVerts = *Base;
	if (BaseVerts + num > MAX_BATCH_SURFS)
		return false;	// force the start new batch
	BaseIdx = *idxBase;
	float	*v;
	glpoly_t *p;
	float	scroll;

	c_light_brush_polys++;

	if (chtex)
	{
		tx = R_TextureAnimation( t );
		tex = tx->image;
		if(tx->bump->texnum && !currentshadowlight->nobump)
			bump = tx->bump;
		else
			bump = r_defaultbump;

		GL_MBind2D(GL_TEXTURE0, bump->texnum);
		if (shader == SHADER_ARB6)
		{
			if (!GL_BindARB(tex->Parallax))
			{
				if (r_detailed_bump->value)
				{
					GL_MBind2D(GL_TEXTURE5, R_GetIMGFromName2(tex, r_defaultbump_detail));
					glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, tex->detail_scale, tex->detail_scale, tex->detail_scale, tex->detail_scale);
				}
			}
		}
		GL_MBind2D(GL_TEXTURE1, tex->texnum);
	}

	if (t->flags & SURF_FLOWING)
	{
		scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
		if(scroll == 0.0)
			scroll = -64.0;
	}
	else
		scroll = 0;

	// create indexes
	if (BaseIdx == 0xffffffff)	BaseIdx = 0;
	for (i=0; i<num-2; i++)
	{
		indexArray[BaseIdx++] = BaseVerts;
		indexArray[BaseIdx++] = BaseVerts + i + 1;
		indexArray[BaseIdx++] = BaseVerts + i + 2;
	}

	p = surf->polys;
	v = p->verts[0];
	for (i=0 ; i < num; i++, v+= VERTEXSIZE, BaseVerts++)
	{
		VectorCopy(v, wVertexArray[BaseVerts]);

		wTexArray[BaseVerts][0] = v[3]+scroll;
		wTexArray[BaseVerts][1] = v[4];

		nTexArray[BaseVerts][0] = v[7];
		nTexArray[BaseVerts][1] = v[8];
		nTexArray[BaseVerts][2] = v[9];
		tTexArray[BaseVerts][0] = v[10];
		tTexArray[BaseVerts][1] = v[11];
		tTexArray[BaseVerts][2] = v[12];
		bTexArray[BaseVerts][0] = v[13];
		bTexArray[BaseVerts][1] = v[14];
		bTexArray[BaseVerts][2] = v[15];
	}

	*Base = BaseVerts;
	*idxBase = BaseIdx;
	return true;
}

void R_DrawWorldARB_vp(lightcmd_u *lightCmds, int shader)
{
	mtexinfo_t	*t;
	int			lightPos = 0;
	msurface_t	*surf;
///	vec3_t		normalArray[MAX_POLY_VERT+2], tangentArray[MAX_POLY_VERT+2], binormalArray[MAX_POLY_VERT+2], vertexArray[MAX_POLY_VERT+2];
///	float		stArray[MAX_POLY_VERT+2][2];

	GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, wTexArray/*stArray*/);

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, bTexArray/*binormalArray*/);

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, nTexArray/*normalArray*/);

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, tTexArray/*tangentArray*/);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);

	bool		chtex;
	unsigned	oldtex = 0xffffffff;
	unsigned	vertCounter = 0, idxCounter = 0xffffffff;

	while (1)
	{
		surf = (msurface_t *)lightCmds[lightPos++].asVoid;
		if(!surf)
		{
			if (idxCounter != 0xffffffff)
			{
				c_world_batches_lighted++;
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///				else
					glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
			}
			break; //end of list
		}

///		Berserker's hack: пусть (SURF_SKY+SURF_SLICK) участвует в затенении, но не рисуется в бампмапе!
		t = surf->texinfo;
		if(!((surf->drawframe != r_framecount) || ((t->flags & SURF_SKY) && (t->flags & SURF_SLICK))))		/// Если рисуемо-освещаемая поверхность не видна, то не рисуем
		{
			if (t->image->texnum != oldtex)
			{	// смена текстуры
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				oldtex = t->image->texnum;
				chtex = true;
			}
			else
				chtex = false;

repeat:		if (!R_AddLightedPolyARB_vp(surf, shader, chtex, &vertCounter, &idxCounter))
			{
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				goto repeat;
			}
		}
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


bool R_AddLightedPoly( msurface_t *surf, bool chtex, unsigned *Base, unsigned *idxBase, lightcmd_u *lightCmds, int *lightPos )
{
	image_t		*tex, *bump;
	unsigned	BaseVerts, BaseIdx, BaseLightPos;
	int		i, num = surf->numedges;
	BaseVerts = *Base;
	if (BaseVerts + num > MAX_BATCH_SURFS)
		return false;	// force the start new batch
	BaseLightPos = *lightPos;
	BaseIdx = *idxBase;
	mtexinfo_t	*tx, *t = surf->texinfo;
	float	*v;
	glpoly_t *p;
	float	scroll;

	c_light_brush_polys++;

	if (chtex)
	{
		tx = R_TextureAnimation( surf->texinfo );
		tex = tx->image;
		if(surf->texinfo->bump->texnum && !currentshadowlight->nobump)
			bump = tx->bump;
		else
			bump = r_defaultbump;

		GL_MBind2D(GL_TEXTURE1, bump->texnum);
		GL_MBind2D(GL_TEXTURE3, tex->texnum);
	}

	if (surf->texinfo->flags & SURF_FLOWING)
	{
		scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
		if(scroll == 0.0)
			scroll = -64.0;
	}
	else
		scroll = 0;

	// create indexes
	if (BaseIdx == 0xffffffff)	BaseIdx = 0;
	for (i=0; i<num-2; i++)
	{
		indexArray[BaseIdx++] = BaseVerts;
		indexArray[BaseIdx++] = BaseVerts + i + 1;
		indexArray[BaseIdx++] = BaseVerts + i + 2;
	}

	p = surf->polys;
	v = p->verts[0];
	for (i=0 ; i < num; i++, v+= VERTEXSIZE, BaseVerts++)
	{
		nTexArray[BaseVerts][0] = lightCmds[BaseLightPos].asFloat;
		nTexArray[BaseVerts][1] = lightCmds[BaseLightPos+1].asFloat;
		nTexArray[BaseVerts][2] = lightCmds[BaseLightPos+2].asFloat;
		wTexArray[BaseVerts][0] = v[3] + scroll;
		wTexArray[BaseVerts][1] = v[4];
		VectorCopy(v, wVertexArray[BaseVerts]);
		BaseLightPos+=3;
	}

	*Base = BaseVerts;
	*idxBase = BaseIdx;
	*lightPos = BaseLightPos;
	return true;
}

void R_DrawWorld(lightcmd_u *lightCmds)
{
	int num, lightPos = 0;
	msurface_t *surf;
	mtexinfo_t	*t;
///	vec3_t	tsLightDir[MAX_POLY_VERT], vertexArray[MAX_POLY_VERT];
///	float	stArray[MAX_POLY_VERT][2];

	GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, nTexArray/*tsLightDir*/);

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, wTexArray/*stArray*/);

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, wTexArray/*stArray*/);

	if(r_shader->value == SHADER_GENERIC6 && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);

	bool		chtex;
	unsigned	oldtex = 0xffffffff;
	unsigned	vertCounter = 0, idxCounter = 0xffffffff;

	while (1)
	{
		surf = (msurface_t *)lightCmds[lightPos++].asVoid;
		if(!surf)
		{
			if (idxCounter != 0xffffffff)
			{
				c_world_batches_lighted++;
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///				else
					glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
			}
			break; //end of list
		}

///		Berserker's hack: пусть (SURF_SKY+SURF_SLICK) участвует в затенении, но не рисуется в бампмапе!
		num = surf->numedges;
		t = surf->texinfo;
		if((surf->drawframe != r_framecount) || ((t->flags & SURF_SKY) && (t->flags & SURF_SLICK)))		/// Если рисуемо-освещаемая поверхность не видна, то не рисуем
			lightPos += num * 3;
		else
		{
			if (t->image->texnum != oldtex)
			{	// смена текстуры
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				oldtex = t->image->texnum;
				chtex = true;
			}
			else
				chtex = false;

repeat:		if (!R_AddLightedPoly(surf, chtex, &vertCounter, &idxCounter, lightCmds, &lightPos))
			{
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				goto repeat;
			}
		}
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	if(r_shader->value == SHADER_GENERIC6 && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


bool R_AddLightedPolyATT( msurface_t *surf, unsigned *Base, unsigned *idxBase, int *lightPos )
{
	unsigned	BaseVerts, BaseIdx, BaseLightPos;
	int		i, num = surf->numedges;
	BaseVerts = *Base;
	if (BaseVerts + num > MAX_BATCH_SURFS)
		return false;	// force the start new batch
	BaseLightPos = *lightPos;
	BaseIdx = *idxBase;
	float	*v;
	glpoly_t *p;

	c_light_brush_polys++;

	// create indexes
	if (BaseIdx == 0xffffffff)	BaseIdx = 0;
	for (i=0; i<num-2; i++)
	{
		indexArray[BaseIdx++] = BaseVerts;
		indexArray[BaseIdx++] = BaseVerts + i + 1;
		indexArray[BaseIdx++] = BaseVerts + i + 2;
	}

	p = surf->polys;
	v = p->verts[0];
	for (i=0 ; i < num; i++, v+= VERTEXSIZE, BaseVerts++)
		VectorCopy(v, wVertexArray[BaseVerts]);
	BaseLightPos+=3*num;

	*Base = BaseVerts;
	*idxBase = BaseIdx;
	*lightPos = BaseLightPos;
	return true;
}


void CL_ItemRespawnParticles (vec3_t org)
{
	if(!p_spawn->value)
		return;

	int			i, j, n = 64 * p_spawn->value;
	cparticle_t	*p;

	for (i=0 ; i<n ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = 0;
		p->time = cl.gameTime;

		p->color_r0 = 0;
		p->color_g0 = 220 + (rand()&7);	// green
		p->color_b0 = 0;
		p->color_r1 = 220 + (rand()&7);	// yellow
		p->color_g1 = 220 + (rand()&7);
		p->color_b1 = 0;

		p->lastOrg[0] = p->org[0] = org[0] + crand()*8;
		p->lastOrg[1] = p->org[1] = org[1] + crand()*8;
		p->lastOrg[2] = p->org[2] = org[2] + crand()*8;

		for (j=0 ; j<3 ; j++)
			p->vel[j] = crand()*8;

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY*0.2;
		p->alpha = 1.0;

		p->alphavel = -1.0 / (1.0 + frand()*0.3);
		p->type = part_simple;
	}
}


void CL_FootPrint(entity_state_t *ent, int idx)
{
	float	angle, size;
	vec3_t	pos, dir;

	size = (idx==2) ? 32 : 16;
	pos[0] = ent->origin[0];
	pos[1] = ent->origin[1];
	pos[2] = ent->origin[2] - 24;
	VectorSet (dir, 0,0,1);
	angle = ent->angles[1] - 45;
	R_SpawnDecal(pos, dir, foot_mrk_object[idx], size,size, 45, 60, angle, angle, dt_mul, false, 0, 0, NULL, true);	// Следы сохнут от 45 до 60 секунд		Следы не будем рисовать с обоих сторон стекла...
}


void CL_FootPrint2(entity_state_t *ent, bool right, bool left)
{
	float	offsx, offsy, ang;

	ang = -ent->angles[1] * (M_PI*2 / 360);
	SinCos(ang, &offsx, &offsy);
	offsx *= 10;
	offsy *= 10;
///	offsx = 10 * sin(ang);
///	offsy = 10 * cos(ang);

	if (right && left)
		CL_FootPrint(ent, 2);
	else
	{
		if (right)
		{
			ent->origin[0] += offsx;
			ent->origin[1] += offsy;
			CL_FootPrint(ent, 0);
			ent->origin[0] -= offsx;
			ent->origin[1] -= offsy;
		}

		if (left)
		{
			ent->origin[0] -= offsx;
			ent->origin[1] -= offsy;
			CL_FootPrint(ent, 1);
			ent->origin[0] += offsx;
			ent->origin[1] += offsy;
		}
	}
}


void CL_BloodPrint(entity_state_t *ent, float dmg)
{
	vec3_t	pos, dir;
	float	ang, lt, s, c;

	lt = r_bloodlifetime->value;
	if (lt <= 0.1 )	lt = 0.1;
	ang = RandomMinMax(0, M_PI*2);
	SinCos(ang, &s, &c);
	pos[0] = ent->origin[0] + 24 * s;
	pos[1] = ent->origin[1] + 24 * c;
	pos[2] = ent->origin[2] - 24;
	VectorSet (dir, 0,0,1);

	R_SpawnDecal(pos, dir, blood_mrk_object[int(RandomMinMax(-0.4, 7.4)+0.5)], 16*dmg,24*dmg, 15*dmg*lt, 30*dmg*lt, 0, 360, dt_mul, false, 0, 0, NULL, false);
}


/*
==============
CL_EntityEvent

An entity has just been parsed that has an event value
the female events are there for backwards compatability
==============
*/
void CL_EntityEvent (entity_state_t *ent)
{
	vec3_t		end, start;
	trace_t		trace;
	material_t	*material;

	switch (ent->event)
	{
	case EV_ITEM_RESPAWN:
		S_StartSound(NULL, ent->number, CHAN_WEAPON, S_RegisterSound("items/respawn1.wav"), 1, _ATTN_IDLE, 0);
		CL_ItemRespawnParticles (ent->origin);
		break;
	case EV_PLAYER_TELEPORT:
		S_StartSound(NULL, ent->number, CHAN_WEAPON, S_RegisterSound("misc/tele1.wav"), 1, _ATTN_IDLE, 0);
		CL_TeleportParticles (ent->origin);
		break;
	case EV_FOOTSTEP:
	case EV_FOOTSTEP_LEFT_PRINT:
	case EV_FOOTSTEP_RIGHT_PRINT:
	case EV_FOOTSTEP2:
	case EV_FOOTSTEP2_LEFT_PRINT:
	case EV_FOOTSTEP2_RIGHT_PRINT:
		if (cl_footsteps->value)
		{
			VectorSet(start, ent->origin[0], ent->origin[1], ent->origin[2] + 16);
			if (CL_PMpointcontents(start) & MASK_WATER)	// если глубоко в воде
				goto step00;							// то ничего не делаем (нет топота и нет следов)
			VectorSet(end, ent->origin[0], ent->origin[1], ent->origin[2] - 96);		// 96 - на столько прощупываем вниз
			trace = CL_PMTraceWorld (start, vec3_origin, vec3_origin, end, MASK_SOLID | MASK_WATER);
			if (trace.fraction > 0 && trace.fraction < 1)
			{
				if (trace.contents & MASK_WATER)
				{
					S_fastsound(NULL, ent->number, CHAN_BODY, cl_sfx_footsteps_splash_0[rand() % 4], ((ent->event == EV_FOOTSTEP_RIGHT_PRINT || ent->event == EV_FOOTSTEP_LEFT_PRINT || ent->event == EV_FOOTSTEP) ? 0.5 : 0.2), _ATTN_NORM);
					goto step0;
				}

				material = R_FindMaterial(trace.surface->name);
				if (material && material->footstep)
				{
					S_fastsound(NULL, ent->number, CHAN_BODY, material->footstep, ((ent->event==EV_FOOTSTEP_RIGHT_PRINT || ent->event==EV_FOOTSTEP_LEFT_PRINT || ent->event==EV_FOOTSTEP) ? 0.5 : 0.2), _ATTN_NORM);
					goto step0;
				}
			}
			S_fastsound(NULL, ent->number, CHAN_BODY, cl_sfx_footsteps_0[rand() % 4], ((ent->event==EV_FOOTSTEP_RIGHT_PRINT || ent->event==EV_FOOTSTEP_LEFT_PRINT || ent->event==EV_FOOTSTEP) ? 0.5 : 0.2), _ATTN_NORM);
		}
step0:	if (ent->event==EV_FOOTSTEP_RIGHT_PRINT || ent->event==EV_FOOTSTEP2_RIGHT_PRINT)
			CL_FootPrint2(ent, true, false);
		else if (ent->event==EV_FOOTSTEP_LEFT_PRINT || ent->event==EV_FOOTSTEP2_LEFT_PRINT)
			CL_FootPrint2(ent, false, true);
step00:	break;
	case EV_FOOTSTEP_DOUBLE_PRINT:
	case EV_FOOTSTEP_DOUBLE:
		if (cl_footsteps->value)
		{
			VectorSet(start, ent->origin[0], ent->origin[1], ent->origin[2] + 16);
			if (CL_PMpointcontents(start) & MASK_WATER)	// если глубоко в воде
				goto step11;							// то ничего не делаем (нет топота и нет следов)
			VectorSet(end, ent->origin[0], ent->origin[1], ent->origin[2] - 96);		// 96 - на столько прощупываем вниз
			trace = CL_PMTraceWorld(start, vec3_origin, vec3_origin, end, MASK_SOLID | MASK_WATER);
			if (trace.fraction > 0 && trace.fraction < 1)
			{
				if (trace.contents & MASK_WATER)
				{
					S_fastsound(NULL, ent->number, CHAN_BODY, cl_sfx_footsteps_splash_0[rand() % 4], 0.5, _ATTN_NORM);
					goto step1;
				}

				material = R_FindMaterial(trace.surface->name);
				if (material && material->footstep)
				{
					S_fastsound(NULL, ent->number, CHAN_BODY, material->footstep, 0.5, _ATTN_NORM);
					goto step1;
				}
			}
			S_fastsound(NULL, ent->number, CHAN_BODY, cl_sfx_footsteps_0[rand() % 4], 0.5, _ATTN_NORM);
		}
step1:	if (ent->event==EV_FOOTSTEP_DOUBLE_PRINT)
			CL_FootPrint2(ent, true, true);
step11:	break;
	case EV_FALLSHORT_PRINT:
	case EV_FALLSHORT:
		S_StartSound(NULL, ent->number, CHAN_AUTO, S_RegisterSound("player/land1.wav"), 1, _ATTN_NORM, 0);
		if (ent->event==EV_FALLSHORT_PRINT)
			CL_FootPrint2(ent, true, true);
		break;
	case EV_FALL_PRINT:
	case EV_FALL:
		S_StartSound(NULL, ent->number, CHAN_AUTO, S_RegisterSexedSound(&cl_entities[ent->number].current, "*fall2.wav"), 1, _ATTN_NORM, 0);
		CL_BloodPrint(ent, 1);
		if (ent->event==EV_FALL_PRINT)
			CL_FootPrint2(ent, true, true);
		break;
	case EV_FALLFAR_PRINT:
	case EV_FALLFAR:
		S_StartSound(NULL, ent->number, CHAN_AUTO, S_RegisterSexedSound(&cl_entities[ent->number].current, "*fall1.wav"), 1, _ATTN_NORM, 0);
		CL_BloodPrint(ent, 2);
		if (ent->event==EV_FALLFAR_PRINT)
			CL_FootPrint2(ent, true, true);
		break;
	}
}


void R_DrawAttenuationWorld(lightcmd_u *lightCmds, bool att)
{
	int num;
	int lightPos = 0;
	msurface_t *surf;
///	vec3_t	vertexArray[MAX_POLY_VERT];

	GL_BindVBO(NULL);

	glColorMask(0,0,0,1);

	GL_SelectTexture(GL_TEXTURE0);
	if(att)
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);
	}
	else
		glDisable(GL_TEXTURE_2D);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, wVertexArray/*vertexArray*/);

	unsigned	vertCounter = 0, idxCounter = 0xffffffff;

	while (1)
	{
		surf = (msurface_t *)lightCmds[lightPos++].asVoid;
		if(!surf)
		{
			if (idxCounter != 0xffffffff)
			{
				c_world_batches_lighted++;
///				if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///					glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///				else
					glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
			}
			break; //end of list
		}

///		Berserker's hack: пусть (SURF_SKY+SURF_SLICK) участвует в затенении, но не рисуется в бампмапе!
		num = surf->numedges;
		if((surf->drawframe != r_framecount) || ((surf->texinfo->flags & SURF_SKY) && (surf->texinfo->flags & SURF_SLICK)))		/// Если рисуемо-освещаемая поверхность не видна, то не рисуем
			lightPos += num * 3;
		else
		{
repeat:		if (!R_AddLightedPolyATT(surf, &vertCounter, &idxCounter, &lightPos))
			{
				if (idxCounter != 0xffffffff)
				{	// draw old batch
					c_world_batches_lighted++;
///					if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///						glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertCounter, idxCounter, GL_UNSIGNED_SHORT, indexArray);
///					else
						glDrawElements(GL_TRIANGLES, idxCounter, GL_UNSIGNED_SHORT, indexArray);
					vertCounter = 0;
					idxCounter = 0xffffffff;
				}
				goto repeat;
			}
		}
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	if(att)
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	else
		glEnable(GL_TEXTURE_2D);

	glColorMask(1,1,1,1);
}


void R_DrawBrushATI4()
{
	vec3_t	LightDir, LightP[MAX_POLY_VERT], vertexArray[MAX_POLY_VERT];
	float	stArray[MAX_POLY_VERT][2], scroll;
	glpoly_t *poly;
	int		i,j;
	float	*v;
	msurface_t *psurf;
	mtexinfo_t *tx;
	image_t	*tex, *bump;

	GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, stArray);

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, LightP);

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, stArray);

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];

	for (i=0 ; i<currentmodel->nummodelsurfaces ; i++, psurf++)
	{
		if (psurf->visframe != r_framecount)
			continue;

		poly = psurf->polys;

		if (poly->lightTimestamp != r_lightTimestamp)
			continue;

		if (poly->numverts > MAX_POLY_VERT)
			Com_Error(ERR_FATAL, "R_DrawBrushATI4: too many edges %i", poly->numverts);

		if (psurf->texinfo->flags & SURF_FLOWING)
		{
			scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
			if(scroll == 0.0)
				scroll = -64.0;
		}
		else
			scroll = 0;

		tx = R_TextureAnimation( psurf->texinfo );
		tex = tx->image;
		if(psurf->texinfo->bump->texnum && !currentshadowlight->nobump)
			bump = tx->bump;
		else
			bump = r_defaultbump;

		GL_MBind2D(GL_TEXTURE0, bump->texnum);
		GL_MBind2D(GL_TEXTURE2, tex->texnum);

		c_light_brush_polys++;

		v = poly->verts[0];
		for (j=0 ; j<poly->numverts ; j++, v+= VERTEXSIZE)
		{
			//calculate local light vector and put it into tangent space
			VectorSubtract(currentshadowlight->origin,v,LightDir);
///			VectorNormalize(LightDir);

			LightP[j][2] = LightDir[0]*v[7]+LightDir[1]*v[8]+LightDir[2]*v[9];
			LightP[j][0] = LightDir[0]*v[10]+LightDir[1]*v[11]+LightDir[2]*v[12];
			LightP[j][1] = LightDir[0]*v[13]+LightDir[1]*v[14]+LightDir[2]*v[15];

			stArray[j][0] = v[3] + scroll;
			stArray[j][1] = v[4];
			VectorCopy(v, vertexArray[j]);
		}
		glDrawArrays (GL_TRIANGLE_FAN, 0, poly->numverts);
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


void R_DrawBrushATI6()
{
	vec3_t	LightDir, H, LightP[MAX_POLY_VERT], tsH[MAX_POLY_VERT], vertexArray[MAX_POLY_VERT];
	float	stArray[MAX_POLY_VERT][2], scroll;
	glpoly_t *poly;
	int		i,j;
	float	*v;
	msurface_t *psurf;
	mtexinfo_t *tx;
	image_t	*tex, *bump;

	GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, stArray);

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, LightP);

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, tsH);

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, stArray);

	GL_SelectTexture(GL_TEXTURE4);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE5);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];

	for (i=0 ; i<currentmodel->nummodelsurfaces ; i++, psurf++)
	{
		if (psurf->visframe != r_framecount)
			continue;

		poly = psurf->polys;

		if (poly->lightTimestamp != r_lightTimestamp)
			continue;

		if (poly->numverts > MAX_POLY_VERT)
			Com_Error(ERR_FATAL, "R_DrawBrushATI6: too many edges %i", poly->numverts);

		if (psurf->texinfo->flags & SURF_FLOWING)
		{
			scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
			if(scroll == 0.0)
				scroll = -64.0;
		}
		else
			scroll = 0;

		tx = R_TextureAnimation( psurf->texinfo );
		tex = tx->image;
		if(psurf->texinfo->bump->texnum && !currentshadowlight->nobump)
			bump = tx->bump;
		else
			bump = r_defaultbump;

		GL_MBind2D(GL_TEXTURE0, bump->texnum);
		GL_MBind2D(GL_TEXTURE3, tex->texnum);

		c_light_brush_polys++;

		v = poly->verts[0];
		for (j=0 ; j<poly->numverts ; j++, v+= VERTEXSIZE)
		{
			//calculate local light vector and put it into tangent space
			VectorSubtract(currentshadowlight->origin,v,LightDir);
///			VectorNormalize(LightDir);

			LightP[j][2] = LightDir[0]*v[7]+LightDir[1]*v[8]+LightDir[2]*v[9];
			LightP[j][0] = LightDir[0]*v[10]+LightDir[1]*v[11]+LightDir[2]*v[12];
			LightP[j][1] = LightDir[0]*v[13]+LightDir[1]*v[14]+LightDir[2]*v[15];

			VectorSubtract(r_origin, v, H);
///			VectorNormalize(H);

			tsH[j][2] = H[0]*v[7]+H[1]*v[8]+H[2]*v[9];
			tsH[j][0] = H[0]*v[10]+H[1]*v[11]+H[2]*v[12];
			tsH[j][1] = H[0]*v[13]+H[1]*v[14]+H[2]*v[15];

			VectorAdd(LightP[j], tsH[j], tsH[j]);

			stArray[j][0] = v[3] + scroll;
			stArray[j][1] = v[4];
			VectorCopy(v, vertexArray[j]);
		}
		glDrawArrays (GL_TRIANGLE_FAN, 0, poly->numverts);
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE5);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE4);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


void R_DrawBrushARB(int shader)
{
	vec3_t	LightDir, H, LightP[MAX_POLY_VERT], tsH[MAX_POLY_VERT], vertexArray[MAX_POLY_VERT];
	float	stArray[MAX_POLY_VERT][2], scroll;
	glpoly_t *poly;
	int		i,j;
	float	*v;
	msurface_t *psurf;
	mtexinfo_t *tx;
	image_t	*tex, *bump;
	bool	need_tsH;

	GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, stArray);

	if (!currentshadowlight->nobump)
	{
		GL_SelectTexture(GL_TEXTURE1);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(3, GL_FLOAT, 0, LightP);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);

	if (!currentshadowlight->nobump)
	{
		GL_SelectTexture(GL_TEXTURE3);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (currentshadowlight->filtercube_start)
		{
			glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
			need_tsH = false;
		}
		else
		{
			glTexCoordPointer(3, GL_FLOAT, 0, tsH);
			need_tsH = true;
		}

		if (shader==SHADER_ARB6 && currentshadowlight->filtercube_start)
		{
			GL_SelectTexture(GL_TEXTURE4);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			glTexCoordPointer(3, GL_FLOAT, 0, tsH);
			need_tsH = true;
		}
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];

	for (i=0 ; i<currentmodel->nummodelsurfaces ; i++, psurf++)
	{
		if (psurf->visframe != r_framecount)
			continue;

		poly = psurf->polys;

		if (poly->lightTimestamp != r_lightTimestamp)
			continue;

		if (poly->numverts > MAX_POLY_VERT)
			Com_Error(ERR_FATAL, "R_DrawBrushARB: too many edges %i", poly->numverts);

		if (psurf->texinfo->flags & SURF_FLOWING)
		{
			scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
			if(scroll == 0.0)
				scroll = -64.0;
		}
		else
			scroll = 0;

		tx = R_TextureAnimation( psurf->texinfo );
		tex = tx->image;
		if(psurf->texinfo->bump->texnum && !currentshadowlight->nobump)
			bump = tx->bump;
		else
			bump = r_defaultbump;

		if (!currentshadowlight->nobump)
		{
			GL_MBind2D(GL_TEXTURE0, bump->texnum);
			if (shader == SHADER_ARB6)
			{
				GL_BindARB(false);
				if (r_detailed_bump->value)
				{
					GL_MBind2D(GL_TEXTURE5, R_GetIMGFromName2(tex, r_defaultbump_detail));
					glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, tex->detail_scale, tex->detail_scale, tex->detail_scale, tex->detail_scale);
				}
			}
		}
		GL_MBind2D(GL_TEXTURE1, tex->texnum);

		c_light_brush_polys++;

		v = poly->verts[0];
		for (j=0 ; j<poly->numverts ; j++, v+= VERTEXSIZE)
		{
			if (!currentshadowlight->nobump)
			{
				//calculate local light vector and put it into tangent space
				VectorSubtract(currentshadowlight->origin,v,LightDir);
///				VectorNormalize(LightDir);

				LightP[j][2] = LightDir[0]*v[7]+LightDir[1]*v[8]+LightDir[2]*v[9];
				LightP[j][0] = LightDir[0]*v[10]+LightDir[1]*v[11]+LightDir[2]*v[12];
				LightP[j][1] = LightDir[0]*v[13]+LightDir[1]*v[14]+LightDir[2]*v[15];

				if (need_tsH)
				{
					VectorSubtract(r_origin, v, H);
///					VectorNormalize(H);

					tsH[j][2] = H[0]*v[7]+H[1]*v[8]+H[2]*v[9];
					tsH[j][0] = H[0]*v[10]+H[1]*v[11]+H[2]*v[12];
					tsH[j][1] = H[0]*v[13]+H[1]*v[14]+H[2]*v[15];

					VectorAdd(LightP[j], tsH[j], tsH[j]);
				}
			}

			stArray[j][0] = v[3] + scroll;
			stArray[j][1] = v[4];
			VectorCopy(v, vertexArray[j]);
		}
		glDrawArrays (GL_TRIANGLE_FAN, 0, poly->numverts);
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	if (!currentshadowlight->nobump)
	{
		if (shader==SHADER_ARB6 && currentshadowlight->filtercube_start)
		{
			GL_SelectTexture(GL_TEXTURE4);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		}

		GL_SelectTexture(GL_TEXTURE3);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	if (!currentshadowlight->nobump)
	{
		GL_SelectTexture(GL_TEXTURE1);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


void R_DrawBrushARB_vp(int shader)
{
	vec3_t		normalArray[MAX_POLY_VERT], tangentArray[MAX_POLY_VERT], binormalArray[MAX_POLY_VERT], vertexArray[MAX_POLY_VERT];
	float		stArray[MAX_POLY_VERT][2], scroll;
	glpoly_t	*poly;
	int			i,j, num;
	float		*v;
	msurface_t	*psurf;
	mtexinfo_t	*tx, *t;
	image_t		*tex, *bump;

	GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, stArray);

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, binormalArray);

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, normalArray);

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, tangentArray);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];

	for (i=0 ; i<currentmodel->nummodelsurfaces ; i++, psurf++)
	{
		if (psurf->visframe != r_framecount)
			continue;

		poly = psurf->polys;
		if (poly->lightTimestamp != r_lightTimestamp)
			continue;

		num = poly->numverts;
		if (num > MAX_POLY_VERT)
			Com_Error(ERR_FATAL, "R_DrawBrushARB: too many edges %i", num);

		if (psurf->texinfo->flags & SURF_FLOWING)
		{
			scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
			if(scroll == 0.0)
				scroll = -64.0;
		}
		else
			scroll = 0;

		t = psurf->texinfo;
		tx = R_TextureAnimation( t );
		tex = tx->image;
		if(tx->bump->texnum && !currentshadowlight->nobump)
			bump = tx->bump;
		else
			bump = r_defaultbump;

		GL_MBind2D(GL_TEXTURE0, bump->texnum);
		if (shader == SHADER_ARB6)
		{
			if (!GL_BindARB(tex->Parallax))
			{
				if (r_detailed_bump->value)
				{
					GL_MBind2D(GL_TEXTURE5, R_GetIMGFromName2(tex, r_defaultbump_detail));
					glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, tex->detail_scale, tex->detail_scale, tex->detail_scale, tex->detail_scale);
				}
			}
		}
		GL_MBind2D(GL_TEXTURE1, tex->texnum);

		c_light_brush_polys++;

		v = poly->verts[0];
		for (j=0 ; j<num ; j++, v+= VERTEXSIZE)
		{
			vertexArray[j][0] = v[0];
			vertexArray[j][1] = v[1];
			vertexArray[j][2] = v[2];

			stArray[j][0] = v[3]+scroll;
			stArray[j][1] = v[4];

			normalArray[j][0] = v[7];
			normalArray[j][1] = v[8];
			normalArray[j][2] = v[9];

			tangentArray[j][0] = v[10];
			tangentArray[j][1] = v[11];
			tangentArray[j][2] = v[12];

			binormalArray[j][0] = v[13];
			binormalArray[j][1] = v[14];
			binormalArray[j][2] = v[15];
		}
		glDrawArrays (GL_TRIANGLE_FAN, 0, num);
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


void R_DrawAttenuationBrush(bool att)
{
	vec3_t	vertexArray3[MAX_POLY_VERT];
	float	vertexArray2[MAX_POLY_VERT][2];
	glpoly_t *poly;
	int		i,j;
	float	*v, scroll;
	msurface_t *psurf;

	GL_BindVBO(NULL);

	glColorMask(0,0,0,1);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if(att)
		glTexCoordPointer(3, GL_FLOAT, 0, vertexArray3);
	else
		glTexCoordPointer(2, GL_FLOAT, 0, vertexArray2);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertexArray3);

	psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];

	for (i=0 ; i<currentmodel->nummodelsurfaces ; i++, psurf++)
	{
		if (psurf->visframe != r_framecount)
			continue;

		poly = psurf->polys;

		if (poly->lightTimestamp != r_lightTimestamp)
			continue;

		if(!att)
		{
			if (psurf->texinfo->flags & SURF_FLOWING)
			{
				scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
				if(scroll == 0.0)
					scroll = -64.0;
			}
			else
				scroll = 0;

			GL_Bind2D(R_TextureAnimation( psurf->texinfo )->image->texnum);
		}

		v = poly->verts[0];
		for (j=0 ; j<poly->numverts ; j++, v+= VERTEXSIZE)
		{
				VectorCopy(v, vertexArray3[j]);
				if(!att)
				{
					vertexArray2[j][0] = v[3] + scroll;
					vertexArray2[j][1] = v[4];
				}
		}
		glDrawArrays (GL_TRIANGLE_FAN, 0, poly->numverts);
	}

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glColorMask(1,1,1,1);
}


void R_DrawBrush()
{
	vec3_t	lightDir, tsLightDir[MAX_POLY_VERT], vertexArray[MAX_POLY_VERT];
	float	stArray[MAX_POLY_VERT][2], scroll;
	glpoly_t *poly;
	int		i,j;
	float	*v;
	msurface_t *psurf;
	mtexinfo_t *tx;
	image_t	*tex, *bump;

	GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, tsLightDir);

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, stArray);

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, stArray);

	if(r_shader->value == SHADER_GENERIC6 && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];

	for (i=0 ; i<currentmodel->nummodelsurfaces ; i++, psurf++)
	{
		if (psurf->visframe != r_framecount)
			continue;

		poly = psurf->polys;

		if (poly->lightTimestamp != r_lightTimestamp)
			continue;

		if (poly->numverts > MAX_POLY_VERT)
			Com_Error(ERR_FATAL, "R_DrawBrush: too many edges %i", poly->numverts);

		if (psurf->texinfo->flags & SURF_FLOWING)
		{
			scroll = -64 * ( (r_newrefdef.time / 40.0) - (int)(r_newrefdef.time / 40.0) );
			if(scroll == 0.0)
				scroll = -64.0;
		}
		else
			scroll = 0;

		tx = R_TextureAnimation( psurf->texinfo );
		tex = tx->image;
		if(psurf->texinfo->bump->texnum && !currentshadowlight->nobump)
			bump = tx->bump;
		else
			bump = r_defaultbump;

		GL_MBind2D(GL_TEXTURE1, bump->texnum);
		GL_MBind2D(GL_TEXTURE3, tex->texnum);

		c_light_brush_polys++;

		v = poly->verts[0];
		for (j=0 ; j<poly->numverts ; j++, v+= VERTEXSIZE)
		{
			//calculate local light vector and put it into tangent space
			VectorSubtract(currentshadowlight->origin,v,lightDir);
///			VectorNormalize(lightDir);

			tsLightDir[j][2] = lightDir[0]*v[7]+lightDir[1]*v[8]+lightDir[2]*v[9];
			tsLightDir[j][0] = lightDir[0]*v[10]+lightDir[1]*v[11]+lightDir[2]*v[12];
			tsLightDir[j][1] = lightDir[0]*v[13]+lightDir[1]*v[14]+lightDir[2]*v[15];

			VectorCopy(v, vertexArray[j]);

			stArray[j][0] = v[3] + scroll;
			stArray[j][1] = v[4];
		}
		glDrawArrays (GL_TRIANGLE_FAN, 0, poly->numverts);
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	if(r_shader->value == SHADER_GENERIC6 && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


inline void R_DrawWorldBumpedGENERIC4()
{
	bool onepass;

	if (currentshadowlight->filtercube_start)
	{
		GL_EnableAttenuationShader(true);
		R_DrawAttenuationWorld(currentshadowlight->lightCmds, true);
		GL_DisableAttenuationShader();
		onepass = false;
	}
	else
		onepass = true;

	GL_EnableShaderGENERIC(true, onepass);
	R_DrawWorld(currentshadowlight->lightCmds);
	GL_DisableShaderGENERIC(onepass);

	if(!onepass)
		R_DrawAttenuationWorld(currentshadowlight->lightCmds, false);
}


inline void R_DrawWorldBumpedGENERIC6()
{
	GL_EnableShaderGENERIC(true, true);
	R_DrawWorld(currentshadowlight->lightCmds);
	GL_DisableShaderGENERIC(true);
}


inline void R_DrawWorldBumpedNVIDIA4()
{
	bool onepass;

	if (currentshadowlight->filtercube_start)
	{
		GL_EnableAttenuationShader(true);
		R_DrawAttenuationWorld(currentshadowlight->lightCmds, true);
		GL_DisableAttenuationShader();
		onepass = false;
	}
	else
		onepass = true;

	GL_EnableShaderNVIDIA4(true, onepass);
	R_DrawWorld(currentshadowlight->lightCmds);
	GL_DisableShaderNVIDIA4(onepass);

	if(!onepass)
		R_DrawAttenuationWorld(currentshadowlight->lightCmds, false);
}


inline void R_DrawWorldBumpedATI4()
{
	GL_EnableShaderATI4(true);
	R_DrawWorldATI4(currentshadowlight->lightCmds);
	GL_DisableShaderATI4();
}


inline void R_DrawWorldBumpedATI6()
{
	GL_EnableShaderATI6(true);
	R_DrawWorldATI6(currentshadowlight->lightCmds);
	GL_DisableShaderATI6();
}


inline void R_DrawWorldBumpedARB(int shader)
{
	GL_EnableShaderARB(true, shader);

	if (!arb_vp_work && currentshadowlight->has_parallax_surf)	// FIX: форсируем VP для NoBump+Parallax
	{
		if (gl_config.vp)
		{
			arb_vp_work = true;
			glEnable(GL_VERTEX_PROGRAM_ARB);
		}
	}

	if (arb_vp_work)
		R_DrawWorldARB_vp(currentshadowlight->lightCmds, shader);
	else
		R_DrawWorldARB(currentshadowlight->lightCmds, shader);
	GL_DisableShaderARB();
}


void R_DrawWorldBumped()
{
	entity_t	ent;
	// auto cycle the world frame for texture animation
	Memset (&ent, 0, sizeof(ent));
	ent.frame = (int)(r_newrefdef.time*2);
	currententity = &ent;

	switch ((int)r_shader->value)
	{
	case SHADER_GENERIC4:
		R_DrawWorldBumpedGENERIC4();
		break;
	case SHADER_GENERIC6:
		R_DrawWorldBumpedGENERIC6();
		break;
	case SHADER_NVIDIA4:
		R_DrawWorldBumpedNVIDIA4();
		break;
	case SHADER_ATI4:
		R_DrawWorldBumpedATI4();
		break;
	case SHADER_ATI6:
		R_DrawWorldBumpedATI6();
		break;
	case SHADER_ARB4:
	case SHADER_ARB6:
		R_DrawWorldBumpedARB((int)r_shader->value);
		break;
	}
	light_used = true;
}


inline int CreateTwoTrisFromOneQuad(lightcmd_t *volumeCmds, int volumePos, int out)
{
	int ind;

/// First tri
// at0
	ind = volumeCmds[volumePos].asInt;
	if (ind > 0)
	{
		vbo_shadow[out][0] = volumeCmds[ind].asFloat;
		vbo_shadow[out][1] = volumeCmds[ind+1].asFloat;
		vbo_shadow[out][2] = volumeCmds[ind+2].asFloat;
	}
	else
		VectorCopy(r_worldmodel->vertexes[-ind].position, vbo_shadow[out]);
	out++;
	if (out == MAX_VBO_XYZs)
		goto fatal;

// at1
	ind = volumeCmds[volumePos + 1].asInt;
	if (ind > 0)
	{
		vbo_shadow[out][0] = volumeCmds[ind].asFloat;
		vbo_shadow[out][1] = volumeCmds[ind+1].asFloat;
		vbo_shadow[out][2] = volumeCmds[ind+2].asFloat;
	}
	else
		VectorCopy(r_worldmodel->vertexes[-ind].position, vbo_shadow[out]);
	out++;
	if (out == MAX_VBO_XYZs)
		goto fatal;

// at2
	ind = volumeCmds[volumePos + 2].asInt;
	if (ind > 0)
	{
		vbo_shadow[out][0] = volumeCmds[ind].asFloat;
		vbo_shadow[out][1] = volumeCmds[ind+1].asFloat;
		vbo_shadow[out][2] = volumeCmds[ind+2].asFloat;
	}
	else
		VectorCopy(r_worldmodel->vertexes[-ind].position, vbo_shadow[out]);
	out++;
	if (out == MAX_VBO_XYZs)
		goto fatal;

/// Second tri
// at2
	ind = volumeCmds[volumePos + 2].asInt;
	if (ind > 0)
	{
		vbo_shadow[out][0] = volumeCmds[ind].asFloat;
		vbo_shadow[out][1] = volumeCmds[ind+1].asFloat;
		vbo_shadow[out][2] = volumeCmds[ind+2].asFloat;
	}
	else
		VectorCopy(r_worldmodel->vertexes[-ind].position, vbo_shadow[out]);
	out++;
	if (out == MAX_VBO_XYZs)
		goto fatal;

// at1
	ind = volumeCmds[volumePos + 1].asInt;
	if (ind > 0)
	{
		vbo_shadow[out][0] = volumeCmds[ind].asFloat;
		vbo_shadow[out][1] = volumeCmds[ind+1].asFloat;
		vbo_shadow[out][2] = volumeCmds[ind+2].asFloat;
	}
	else
		VectorCopy(r_worldmodel->vertexes[-ind].position, vbo_shadow[out]);
	out++;
	if (out == MAX_VBO_XYZs)
		goto fatal;

// at3
	ind = volumeCmds[volumePos + 3].asInt;
	if (ind > 0)
	{
		vbo_shadow[out][0] = volumeCmds[ind].asFloat;
		vbo_shadow[out][1] = volumeCmds[ind+1].asFloat;
		vbo_shadow[out][2] = volumeCmds[ind+2].asFloat;
	}
	else
		VectorCopy(r_worldmodel->vertexes[-ind].position, vbo_shadow[out]);
	out++;
	if (out == MAX_VBO_XYZs)
fatal:	Com_Error(ERR_FATAL, "CreateTwoTrisFromOneQuad: overflowed");

	return out;
}


inline int CreateTri(lightcmd_t *volumeCmds, int volumePos, int out, int end)
{
// at0 - base vertex
	vbo_shadow[out][0] = volumeCmds[volumePos-3].asFloat;
	vbo_shadow[out][1] = volumeCmds[volumePos-2].asFloat;
	vbo_shadow[out][2] = volumeCmds[volumePos-1].asFloat;
	out++;
	if (out == MAX_VBO_XYZs)
		goto fatal;
// at1
	vbo_shadow[out][0] = volumeCmds[volumePos-end*3].asFloat;
	vbo_shadow[out][1] = volumeCmds[volumePos-end*3+1].asFloat;
	vbo_shadow[out][2] = volumeCmds[volumePos-end*3+2].asFloat;
	out++;
	if (out == MAX_VBO_XYZs)
		goto fatal;
// at2
	vbo_shadow[out][0] = volumeCmds[volumePos-(end+1)*3].asFloat;
	vbo_shadow[out][1] = volumeCmds[volumePos-(end+1)*3+1].asFloat;
	vbo_shadow[out][2] = volumeCmds[volumePos-(end+1)*3+2].asFloat;
	out++;
	if (out == MAX_VBO_XYZs)
fatal:	Com_Error(ERR_FATAL, "CreateTri: overflowed");

	return out;
}


inline int CreateTri2(float *base, float *v, int out)
{
// at0 - base vertex
	vbo_shadow[out][0] = base[0];
	vbo_shadow[out][1] = base[1];
	vbo_shadow[out][2] = base[2];
	out++;
	if (out == MAX_VBO_XYZs)
		goto fatal;
// at1
	vbo_shadow[out][0] = v[VERTEXSIZE];
	vbo_shadow[out][1] = v[VERTEXSIZE+1];
	vbo_shadow[out][2] = v[VERTEXSIZE+2];
	out++;
	if (out == MAX_VBO_XYZs)
		goto fatal;
// at2
	vbo_shadow[out][0] = v[2*VERTEXSIZE];
	vbo_shadow[out][1] = v[2*VERTEXSIZE+1];
	vbo_shadow[out][2] = v[2*VERTEXSIZE+2];
	out++;
	if (out == MAX_VBO_XYZs)
fatal:	Com_Error(ERR_FATAL, "CreateTri2: overflowed");

	return out;
}


int GenerateShadowVolumeTriangles()
{
	int command, num, i, volumePos, lightPos;
	msurface_t	*surf;
	float		*v;
	lightcmd_t	*volumeCmds, *lightCmds;

	bool skipp = (!(gl_config.vp && (r_shader->value == SHADER_ARB4 || r_shader->value == SHADER_ARB6)));
	int out = 0;
	volumeCmds = currentshadowlight->volumeCmds;
	lightCmds = currentshadowlight->lightCmds;
	volumePos = lightPos = 0;

	while (1)
	{
		command = volumeCmds[volumePos++].asInt;
		if (command == 0)
			break; //end of list

		num = volumeCmds[volumePos++].asInt;
		if (command == GL_QUAD_STRIP)
		{
			if (num < 4)
				Com_Error(ERR_FATAL, "GenerateShadowVolumeTriangles: QUAD_STRIP with %i vertexes", num);
			out = CreateTwoTrisFromOneQuad(volumeCmds, volumePos, out);
			volumePos += 2;
			for (i=4; i<num; i+=2)
			{
				out = CreateTwoTrisFromOneQuad(volumeCmds, volumePos, out);
				volumePos += 2;
			}
			volumePos += 2;
		}
		else	/// GL_TRIANGLE_FAN
		{
			if (num < 3)
				goto fatal;
			//caps point inwards
			volumePos += num*3;
			out = CreateTri(volumeCmds, volumePos, out, 2);
			for (i=3; i<num; i++)
				out = CreateTri(volumeCmds, volumePos, out, i);
		}
	}

	while (1)
	{
		surf = (msurface_t *)lightCmds[lightPos++].asVoid;
		if(!surf)
			break; //end of list

		num = surf->numedges;
		if (num < 3)
fatal:		Com_Error(ERR_FATAL, "GenerateShadowVolumeTriangles: GL_TRIANGLE_FAN with %i vertexes", num);

		float *v0;
		v0 = v = surf->polys->verts[0];
		out = CreateTri2(v0, v, out);
		v+= VERTEXSIZE;
		for (i=3; i<num; i++, v+= VERTEXSIZE)
			out = CreateTri2(v0, v, out);
		//skip all tangent space light vectors
		if (skipp)
			lightPos += num*3;
	}

	return out;
}


void CreateShadowVBO()
{
	if (gl_config.vbo && !currentshadowlight->sh_vbo_id)
	{
		int out = GenerateShadowVolumeTriangles();
		currentshadowlight->sh_vbo_id = R_VCLoadData(VBO_STATIC, out * sizeof(vec3_t), &vbo_shadow, VBO_STORE_ANY, NULL, NULL);
		currentshadowlight->sh_vbo_xyz = out;
		GL_BindVBO(NULL);
	}
}


/*
=============
DrawVolumeFromCmds

Draws the generated commands as shadow volumes
=============
*/
inline void DrawVolumeFromCmds()
{
	if (currentshadowlight->sh_vbo_id)
	{
		GL_BindVBO(currentshadowlight->sh_vbo_id);
		glVertexPointer( 3, GL_FLOAT, 0, 0 );
		glDrawArrays (GL_TRIANGLES, 0, currentshadowlight->sh_vbo_xyz);
		GL_BindVBO(NULL);
	}
	else
	{	// vbo не имеют динамичные источники, либо vbo недоступно или запрещено
		int out = GenerateShadowVolumeTriangles();
		GL_BindVBO(NULL);
		glVertexPointer( 3, GL_FLOAT, 0, vbo_shadow );
		glDrawArrays (GL_TRIANGLES, 0, out);
	}
}


/*
=============
R_MarkBrushModelSurfaces

Set the light timestamps of the brush model.
=============
*/
void R_MarkBrushModelSurfaces()
{
	int			i;
	msurface_t	*psurf;
	model_t		*clmodel;
	vec3_t		oldlightorigin;

	//backup light origin since we will have to translate
	//light into model space
	VectorCopy (currentshadowlight->origin, oldlightorigin);

	clmodel = currententity->model;

	VectorSubtract (currentshadowlight->origin, currententity->origin, currentshadowlight->origin);
	if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
	{
		vec3_t	temp;
		vec3_t	forward, right, up;

		AngleVectors (currententity->angles, forward, right, up);

		VectorCopy (currentshadowlight->origin, temp);
		currentshadowlight->origin[0] = DotProduct (temp, forward);
		currentshadowlight->origin[1] = -DotProduct (temp, right);
		currentshadowlight->origin[2] = DotProduct (temp, up);
	}

	psurf = &clmodel->surfaces[clmodel->firstmodelsurface];
	for (i=0 ; i<clmodel->nummodelsurfaces ; i++, psurf++)
		R_MarkShadowSurf(psurf, currentshadowlight, false);

	VectorCopy(oldlightorigin, currentshadowlight->origin);
}


void R_MarkBrushSurfaces()
{
	int		i;

	if (!r_drawentities->value)
		return;

	for (i=0 ; i<cl_numlightvisedicts ; i++)
	{
		currententity = cl_lightvisedicts[i];
		if(currententity->model->type == mod_brush)
			R_MarkBrushModelSurfaces();
	}
}


bool InShadowEntity()
{
	vec3_t	dst;
	float	lbox[6], ebox[6];

	if(currentmodel->type == mod_brush)
	{
		lbox[0] = currentshadowlight->mins_cone[0];
		lbox[1] = currentshadowlight->mins_cone[1];
		lbox[2] = currentshadowlight->mins_cone[2];
		lbox[3] = currentshadowlight->maxs_cone[0];
		lbox[4] = currentshadowlight->maxs_cone[1];
		lbox[5] = currentshadowlight->maxs_cone[2];
		return BBoxIntersectBBox(lbox, currententity->minmax);
	}

	if(currentshadowlight->sphere)
	{	// для идеальных сфер - это быстрый и простой способ
		VectorSubtract (currentshadowlight->origin, currententity->origin, dst);
		return (VectorLength (dst) < (currentshadowlight->radius + currentmodel->radius));
	}

	// Несферичные источники будем проверять по bounding box - так мы точнее отсечем затененные entities
	lbox[0] = currentshadowlight->mins_cone[0];
	lbox[1] = currentshadowlight->mins_cone[1];
	lbox[2] = currentshadowlight->mins_cone[2];
	lbox[3] = currentshadowlight->maxs_cone[0];
	lbox[4] = currentshadowlight->maxs_cone[1];
	lbox[5] = currentshadowlight->maxs_cone[2];

	ebox[0] = currententity->origin[0] - currentmodel->radius;
	ebox[1] = currententity->origin[1] - currentmodel->radius;
	ebox[2] = currententity->origin[2] - currentmodel->radius;
	ebox[3] = currententity->origin[0] + currentmodel->radius;
	ebox[4] = currententity->origin[1] + currentmodel->radius;
	ebox[5] = currententity->origin[2] + currentmodel->radius;
	return BBoxIntersectBBox(lbox, ebox);
}


bool InConeEntity()
{
	vec3_t	e_mins, e_maxs;		// for inline bmodel bbox...

///	if (r_nocull->value)
///		return false;

	if(currentmodel->type == mod_alias)
		return !R_CullAliasModel( currentshadowlight->frust, true, false );
	else if(currentmodel->type == mod_alias_md3)
		return !R_CullAliasMD3Model( currentshadowlight->frust, true, false );
	else if(currentmodel->type == mod_brush)
	{
		e_mins[0] = currententity->minmax[0];
		e_mins[1] = currententity->minmax[1];
		e_mins[2] = currententity->minmax[2];
		e_maxs[0] = currententity->minmax[3];
		e_maxs[1] = currententity->minmax[4];
		e_maxs[2] = currententity->minmax[5];
		return !R_CullBox_(e_mins, e_maxs, currentshadowlight->frust);
	}

	return false;	/// for entity not mod_alias, mod_brush
}


///	*_BK	==		NegX
///	*_DN	==		NegY
///	*_FT	==		PosX
///	*_LF	==		NegZ
///	*_RT	==		PosZ
///	*_UP	==		PosY

void SetSky (char *name, float rotate, vec3_t axis)
{
	int		i;
	char	pathname[MAX_QPATH];

	strncpy (skyname, name, sizeof(skyname)-1);
	skyrotate = rotate;
	VectorCopy (axis, skyaxis);

	for (i=0 ; i<6 ; i++)
	{
		Com_sprintf (pathname, sizeof(pathname), "env/%s%s", skyname, suf[i]);
		sky_images[i] = GL_FindImage (pathname, it_sky, false, 0, false, 0);

		if (!sky_images[i])
			sky_images[i] = r_notexture;
	}
}


void Mod_LoadVertexes (lump_t *l)
{
	dvertex_t	*in;
	mvertex_t	*out;
	int			i, count;

	in = (dvertex_t *)(mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "Mod_LoadVertexes: funny lump size in %s",loadmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mvertex_t *) Hunk_Alloc (count*sizeof(*out), true);

	loadmodel->vertexes = out;
	loadmodel->numvertexes = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		out->position[0] = LittleFloat (in->point[0]);
		out->position[1] = LittleFloat (in->point[1]);
		out->position[2] = LittleFloat (in->point[2]);
	}
}


//see: http://members.rogers.com/deseric/tangentspace.htm
void VecsForTris(float *v0, float *v1, float *v2, float *st0, float *st1, float *st2, vec3_t Tangent, vec3_t Binormal)
{
	vec3_t	vec1, vec2;
	vec3_t	planes[3];
	float	tmp;
	int		i;

	for (i=0; i<3; i++)
	{
		vec1[0] = v1[i]-v0[i];
		vec1[1] = st1[0]-st0[0];
		vec1[2] = st1[1]-st0[1];
		vec2[0] = v2[i]-v0[i];
		vec2[1] = st2[0]-st0[0];
		vec2[2] = st2[1]-st0[1];
		VectorNormalize(vec1);
		VectorNormalize(vec2);
		CrossProduct(vec1,vec2,planes[i]);
	}

	//Tangent = (-planes[B][x]/plane[A][x], -planes[B][y]/planes[A][y], - planes[B][z]/planes[A][z] )
	//Binormal = (-planes[C][x]/planes[A][x], -planes[C][y]/planes[A][y], -planes[C][z]/planes[A][z] )
	for (i=0; i<3; i++)
	{
		tmp = 1.0 / planes[i][0];
		Tangent[i] = -planes[i][1]*tmp;
		Binormal[i] = -planes[i][2]*tmp;
	}
	VectorNormalize(Tangent);
	VectorNormalize(Binormal);
}


void ClosestPointOnLine(vec3_t a, vec3_t b, vec3_t p, vec3_t res)
{
	vec3_t c,V;
	float d,t ;

	// a-b is the line, p the point in question
	VectorSubtract(p, a, c);
	VectorSubtract(b, a, V);
	d = VectorNormalize(V); // normalize V
	t = DotProduct(V,c);

	// Check to see if t is beyond the extents of the line segment
	if (t < 0.0f)
		VectorCopy(a, res);

	if (t > d)
		VectorCopy(b, res);

	// Return the point between a and b
	VectorScale(V, t, V); //set length of V to t.
	VectorAdd(a, V, res);
}


/*
Yet another hack.  Some models seem to have edges shared between three triangles, this is obviously
a strange thing to have, we resolve it simply by throwing away that shared egde and giving all
triangles a "-1" neighbour for that edge.  This will give some unneeded fins for some edges of some models
but this number is generally very low (< 3 edges per model) and only on a few models.
*/
int findneighbour(int index, int edgei, int numtris, neighbours_t *triangles, dtriangle_t *tris)
{
	int i, j, found, foundj = 0;
	dtriangle_t *current = &tris[index];
	bool		dup;

	found = -1;
	dup = false;
	for (i=0; i<numtris; i++)
	{
		if (i == index)
			continue;

		for (j=0; j<3; j++)
		{
			if (((current->index_xyz[edgei] == tris[i].index_xyz[j])
			    && (current->index_xyz[(edgei+1)%3] == tris[i].index_xyz[(j+1)%3]))
				||
			   ((current->index_xyz[edgei] == tris[i].index_xyz[(j+1)%3])
			    && (current->index_xyz[(edgei+1)%3] == tris[i].index_xyz[j])))
			{
				//no edge for this model found yet?
				if (found == -1)
				{
					found = i;
					foundj = j;
				}
				//the three edges story
				else
					dup = true;
			}
		}
	}

	//normal edge, setup neighbour pointers
	if (!dup && (found != -1))							/// FIXED by BERSERKER: в Tenebrae не проверяется случай, когда found==-1  -> ошибка защиты памяти!
	{
		triangles[found].neighbours[foundj] = index;
		return found;
	}
	//naughty egde let no-one have the neighbour
	return -1;
}


void ClearBounds (vec3_t mins, vec3_t maxs)
{
	mins[0] = mins[1] = mins[2] = 99999999;
	maxs[0] = maxs[1] = maxs[2] = -99999999;
}


void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs)
{
	int		i;
	float	val;

	for (i=0 ; i<3 ; i++)
	{
		val = v[i];
		if (val < mins[i])
			mins[i] = val;
		if (val > maxs[i])
			maxs[i] = val;
	}
}


void Mod_InvertNormal(dmdl_t *pheader)
{
	int				i, j;
	daliasframe_t	*frame;
	dtrivertx_t		*verts;

	//for all frames
	for (i=0; i<pheader->num_frames; i++)
	{
		frame = (daliasframe_t *)((byte *)pheader + pheader->ofs_frames + i * pheader->framesize);
		verts = frame->verts;
		for (j=0; j<pheader->num_xyz; j++)
		{	//invert normals
			vec3_t	normal;
			byte	idx = verts[j].lightnormalindex;
			normal[0] = r_avertexnormals[idx][0];
			normal[1] = -r_avertexnormals[idx][1];
			normal[2] = r_avertexnormals[idx][2];
			verts[j].lightnormalindex = Normal2Index(normal);
		}
	}
}

void HACK_RecalcVertsLightNormalIdx (dmdl_t *pheader, bool invert)
{
	int				i, j, k, l;
	daliasframe_t	*frame;
	dtrivertx_t		*verts, *v;
	vec3_t			normal, triangle[3], v1, v2;
	dtriangle_t		*tris = (dtriangle_t *) ((byte *)pheader + pheader->ofs_tris);
//////////////////////////////////////////////////////////////////////////
// Ввиду того, что некоторые модели содержат дефективные lightnormalindex //
// (всё оружие cl_vwep), то лучше самим расчитать их...                 //
//////////////////////////////////////////////////////////////////////////
/*	/// Little hack: lightnormalindexes моделей оружия (cl_vwep) сделаны плохо! Форсируем их перерасчет...
	bool force;
	if (calc_lni->value ||
		strstr(mod->name, "a_grenades.md2") ||
		strstr(mod->name, "w_bfg.md2") ||
		strstr(mod->name, "w_blaster.md2") ||
		strstr(mod->name, "w_chaingun.md2") ||
		strstr(mod->name, "w_glauncher.md2") ||
		strstr(mod->name, "w_hyperblaster.md2") ||
		strstr(mod->name, "w_machinegun.md2") ||
		strstr(mod->name, "w_railgun.md2") ||
		strstr(mod->name, "w_rlauncher.md2") ||
		strstr(mod->name, "w_shotgun.md2") ||
		strstr(mod->name, "w_sshotgun.md2"))
		force = true;
	else
		force = false;
*/
	/// Berserker: проверим на равенство всех нормалей модели...
	//for all frames
	for (i=0; i<pheader->num_frames; i++)
	{
		frame = (daliasframe_t *)((byte *)pheader + pheader->ofs_frames + i * pheader->framesize);
		verts = frame->verts;

/*		int baseidx;
		if (!force)
		{
			for (j=0; j<pheader->num_xyz; j++)
			{
				int idx = verts[j].lightnormalindex;

				if(j==0)
					baseidx = idx;
				else
					if(baseidx != idx)
						goto frame_ok;
			}

			/// Berserker: все нормали модели - одинаковы! Пересчитаем их!
			Com_DPrintf("Rebuilding lightnormalindex for %s frame %i\n", mod->name, i);
		}
*/
		vec3_t	normals_[MAX_VERTS];
		Memset(normals_, 0, pheader->num_xyz*sizeof(vec3_t));

		//for all tris
		for (j=0; j<pheader->num_tris; j++)
		{
			//make 3 vec3_t's of this triangle's vertices
			for (k=0; k<3; k++)
			{
				l = tris[j].index_xyz[k];
				v = &verts[l];
				for (l=0; l<3; l++)
					triangle[k][l] = v->v[l];
			}

			//calculate normal
			VectorSubtract(triangle[0], triangle[1], v1);
			VectorSubtract(triangle[2], triangle[1], v2);
			CrossProduct(v2,v1, normal);
			VectorScale(normal, -1.0/VectorLength(normal), normal);

			for (k=0; k<3; k++)
			{
				l = tris[j].index_xyz[k];
				VectorAdd(normals_[l], normal, normals_[l]);
			}
		}

		/// Berserker
		// В некоторых моделях изредка попадаются несколько точек, имеющие одинаковые координаты.
		// Для таких случаев объединяем их вектора (если угол между нормалями невелик (до 15 градусов)).
		for (j=0; j<pheader->num_xyz; j++)
			for (k=j+1; k<pheader->num_xyz; k++)
				if(verts[j].v[0] == verts[k].v[0] && verts[j].v[1] == verts[k].v[1] && verts[j].v[2] == verts[k].v[2])
				{
					float *jnormal = r_avertexnormals[verts[j].lightnormalindex];
					float *knormal = r_avertexnormals[verts[k].lightnormalindex];
					if(DotProduct(jnormal, knormal)>=smooth_cosine)		/// smooth_cosine = cos(N), при величине угла между нормалями менее чем N градусов, считаем, что это одна точка
					{
						VectorAdd(normals_[j], normals_[k], normals_[j]);
						VectorCopy(normals_[j], normals_[k]);
					}
				}

		//normalize average
		for (j=0; j<pheader->num_xyz; j++)
		{
			VectorNormalize(normals_[j]);
			if (invert)
			{
				normals_[j][0] = -normals_[j][0];
				normals_[j][2] = -normals_[j][2];
			}
			verts[j].lightnormalindex = Normal2Index(normals_[j]);
		}
/*
frame_ok:;*/
	}
//////////////////////////////////////////////////////////////////////////
}


void Mod_LoadEdges (lump_t *l)
{
	dedge_t *in;
	medge_t *out;
	int 	i, count;

	in = (dedge_t *)(mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "Mod_LoadEdges: funny lump size in %s",loadmodel->name);
	count = l->filelen / sizeof(*in);
	out = (medge_t *) Hunk_Alloc ((count + 1) * sizeof(*out), true);

	loadmodel->edges = out;
	loadmodel->numedges = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		out->v[0] = (WORD)LittleShort(in->v[0]);
		out->v[1] = (WORD)LittleShort(in->v[1]);
	}
}


void Mod_LoadSurfedges (lump_t *l)
{	
	int		i, count;
	int		*in, *out;
	
	in = (int *)(mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "Mod_LoadSurfedges: funny lump size in %s",loadmodel->name);
	count = l->filelen / sizeof(*in);
	if (count < 1 || count >= MAX_MAP_SURFEDGES)
		Com_Error (ERR_DROP, "Mod_LoadSurfedges: bad surfedges count in %s: %i", loadmodel->name, count);

	out = (int *) Hunk_Alloc ( count*sizeof(*out), true);

	loadmodel->surfedges = out;
	loadmodel->numsurfedges = count;

	for ( i=0 ; i<count ; i++)
		out[i] = LittleLong (in[i]);
}


void Mod_LoadLighting (lump_t *l)
{
	if (!l->filelen)
	{
		loadmodel->lightdata = NULL;
		return;
	}
	loadmodel->lightdata = (byte *) Hunk_Alloc ( l->filelen, true );		// выделять память обязательно даже при кэшировании, чтоб получить верный *.mem
	if (!caching__)		// при кэшировании пропустим этот шаг
		Memcpy (loadmodel->lightdata, mod_base + l->fileofs, l->filelen);
}


void Mod_LoadPlanes (lump_t *l)
{
	int			i, j;
	cplane_t	*out;
	dplane_t 	*in;
	int			count;
	int			bits;
	
	in = (dplane_t *)(mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "Mod_LoadPlanes: funny lump size in %s",loadmodel->name);
	count = l->filelen / sizeof(*in);
	out = (cplane_t *) Hunk_Alloc ( count*2*sizeof(*out), true);
	
	loadmodel->planes = out;
	loadmodel->numplanes = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		bits = 0;
		for (j=0 ; j<3 ; j++)
		{
			out->normal[j] = LittleFloat (in->normal[j]);
			if (out->normal[j] < 0)
				bits |= 1<<j;
		}

		out->dist = LittleFloat (in->dist);
		out->type = LittleLong (in->type);
		out->signbits = bits;
	}
}


/*
================
CalcSurfaceExtents

Fills in s->texturemins[] and s->extents[]
================
*/
void CalcSurfaceExtents (msurface_t *s)
{
	float		mins[2], maxs[2], val;
	int			i, j, e;
	mvertex_t	*v;
	mtexinfo_t	*tex;
	int			bmins[2], bmaxs[2];

	mins[0] = mins[1] = 999999;
	maxs[0] = maxs[1] = -999999;

	tex = s->texinfo;
	
	for (i=0 ; i<s->numedges ; i++)
	{
		e = loadmodel->surfedges[s->firstedge+i];
		if (e >= 0)
			v = &loadmodel->vertexes[loadmodel->edges[e].v[0]];
		else
			v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]];
		
		for (j=0 ; j<2 ; j++)
		{
			val = v->position[0] * tex->vecs[j][0] + 
				v->position[1] * tex->vecs[j][1] +
				v->position[2] * tex->vecs[j][2] +
				tex->vecs[j][3];
			if (val < mins[j])
				mins[j] = val;
			if (val > maxs[j])
				maxs[j] = val;
		}
	}

	for (i=0 ; i<2 ; i++)
	{	
		bmins[i] = floor(mins[i]/16);
		bmaxs[i] = ceil(maxs[i]/16);

		s->texturemins[i] = bmins[i] * 16;
		s->extents[i] = (bmaxs[i] - bmins[i]) * 16;
	}
}


void BoundPoly (int numverts, float *verts, vec3_t mins, vec3_t maxs)
{
	int		i, j;
	float	*v;

	mins[0] = mins[1] = mins[2] = 9999;
	maxs[0] = maxs[1] = maxs[2] = -9999;
	v = verts;
	for (i=0 ; i<numverts ; i++)
		for (j=0 ; j<3 ; j++, v++)
		{
			if (*v < mins[j])
				mins[j] = *v;
			if (*v > maxs[j])
				maxs[j] = *v;
		}
}


void SubdividePolygon (int numverts, float *verts)
{
	int		i, j, k;
	vec3_t	mins, maxs;
	float	m;
	float	*v;
	vec3_t	front[64], back[64];
	int		f, b;
	float	dist[64];
	float	frac;
	glpoly_t	*poly;
	float	s, t;
	vec3_t	total;
	float	total_s, total_t;

	if (numverts > 60)
		Com_Error (ERR_DROP, "numverts = %i", numverts);

	BoundPoly (numverts, verts, mins, maxs);

	for (i=0 ; i<3 ; i++)
	{
		m = (mins[i] + maxs[i]) * 0.5;
		m = SUBDIVIDE_SIZE * floor (m/SUBDIVIDE_SIZE + 0.5);
		if (maxs[i] - m < 8)
			continue;
		if (m - mins[i] < 8)
			continue;

		// cut it
		v = verts + i;
		for (j=0 ; j<numverts ; j++, v+= 3)
			dist[j] = *v - m;

		// wrap cases
		dist[j] = dist[0];
		v-=i;
		VectorCopy (verts, v);

		f = b = 0;
		v = verts;
		for (j=0 ; j<numverts ; j++, v+= 3)
		{
			if (dist[j] >= 0)
			{
				VectorCopy (v, front[f]);
				f++;
			}
			if (dist[j] <= 0)
			{
				VectorCopy (v, back[b]);
				b++;
			}
			if (dist[j] == 0 || dist[j+1] == 0)
				continue;
			if ( (dist[j] > 0) != (dist[j+1] > 0) )
			{
				// clip point
				frac = dist[j] / (dist[j] - dist[j+1]);
				for (k=0 ; k<3 ; k++)
					front[f][k] = back[b][k] = v[k] + frac*(v[3+k] - v[k]);
				f++;
				b++;
			}
		}

		SubdividePolygon (f, front[0]);
		SubdividePolygon (b, back[0]);
		return;
	}

	// add a point in the center to help keep warp valid
	poly = (glpoly_s *) Hunk_Alloc (sizeof(glpoly_t) + ((numverts-4)+2) * VERTEXSIZE*sizeof(float), true);
	poly->next = warpface->polys;
	warpface->polys = poly;
	poly->numverts = numverts+2;
	VectorClear (total);
	total_s = 0;
	total_t = 0;
	for (i=0 ; i<numverts ; i++, verts+= 3)
	{
		VectorCopy (verts, poly->verts[i+1]);
		s = DotProduct (verts, warpface->texinfo->vecs[0]);
		t = DotProduct (verts, warpface->texinfo->vecs[1]);

		total_s += s;
		total_t += t;
		VectorAdd (total, verts, total);

		poly->verts[i+1][3] = s;
		poly->verts[i+1][4] = t;
	}

	float	sca = 1.0/numverts;
	VectorScale (total, sca, poly->verts[0]);
	poly->verts[0][3] = total_s*sca;
	poly->verts[0][4] = total_t*sca;

	// copy first vertex to last
	Memcpy (poly->verts[i+1], poly->verts[1], sizeof(poly->verts[0]));
}


/*
================
GL_SubdivideSurface

Breaks a polygon up along axial SUBDIVIDE_SIZE unit
boundaries so that turbulent and sky warps
can be done reasonably.
================
*/
void GL_SubdivideSurface (msurface_t *fa)
{
	vec3_t		verts[64];
	int			numverts;
	int			i;
	int			lindex;
	float		*vec;

	warpface = fa;

	//
	// convert edges back to a normal polygon
	//
	numverts = 0;
	for (i=0 ; i<fa->numedges ; i++)
	{
		lindex = loadmodel->surfedges[fa->firstedge + i];

		if (lindex > 0)
			vec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position;
		else
			vec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position;
		VectorCopy (vec, verts[numverts]);
		numverts++;
	}

	SubdividePolygon (numverts, verts[0]);
}


void LM_InitBlock ()
{
	Memset( gl_lms.allocated, 0, sizeof( gl_lms.allocated ) );
}


void GL_BeginBuildingLightmaps ()
{
	LM_InitBlock ();
	occ_framecount = r_framecount = 1;		// no dlightcache

	GL_ClearCache(GL_TEX | GL_TMU | GL_TEXENV	| GL_BLEND_	| GL_COLOR_ | GL_VP | GL_FP);
///	GL_SelectTexture( GL_TEXTURE0 );
///	gl_TexEnv( GL_REPLACE );

	GL_SelectTexture( GL_TEXTURE1 );
	glEnable( GL_TEXTURE_2D );
	gl_TexEnv( GL_REPLACE );

///	if (!gl_state.lightmap_textures)
///		gl_state.lightmap_textures	= TEXNUM_LIGHTMAPS;

	gl_lms.current_lightmap_texture = 0;
}


void LM_UploadBlock()
{
	int texture;
	int height = 0;

	texture = gl_lms.current_lightmap_texture;
	if ( texture == MAX_LIGHTMAPS )
		Com_Error( ERR_DROP, "LM_UploadBlock() - MAX_LIGHTMAPS(%i) exceeded", MAX_LIGHTMAPS );

	GL_Bind2D( /*gl_state.lightmap_textures*/TEXNUM_LIGHTMAPS + texture );
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D( GL_TEXTURE_2D,
				   0,
				   GL_RGBA8,	/// gl_tex_solid_format,
				   LIGHTMAP_SIZE, LIGHTMAP_SIZE,
				   0,
				   GL_BGRA,
				   GL_UNSIGNED_INT_8_8_8_8_REV,
				   trans );
	gl_lms.current_lightmap_texture++;
}


void GL_EndBuildingLightmaps ()
{
	LM_UploadBlock();
	Com_DPrintf("Builded %i lightmap(s)\n", gl_lms.current_lightmap_texture);

	GL_SelectTexture( GL_TEXTURE1 );
	glDisable( GL_TEXTURE_2D );
	gl_TexEnv( GL_REPLACE );

	GL_SelectTexture( GL_TEXTURE0 );
	gl_TexEnv( GL_REPLACE );
}


bool LM_AllocBlock (int w, int h, int *x, int *y)
{
	int		i, j;
	int		best, best2;

	best = LIGHTMAP_SIZE;

	for (i=0 ; i<LIGHTMAP_SIZE-w ; i++)
	{
		best2 = 0;

		for (j=0 ; j<w ; j++)
		{
			if (gl_lms.allocated[i+j] >= best)
				break;
			if (gl_lms.allocated[i+j] > best2)
				best2 = gl_lms.allocated[i+j];
		}
		if (j == w)
		{	// this is a valid spot
			*x = i;
			*y = best = best2;
		}
	}

	if (best + h > LIGHTMAP_SIZE)
		return false;

	for (i=0 ; i<w ; i++)
		gl_lms.allocated[*x + i] = best + h;

	return true;
}


void R_BuildLightMap (msurface_t *surf, byte *dest, int stride)
{
	int			smax, tmax;
	int			r, g, b, max;
	int			i, j, size;
	byte		*lightmap;
	float		*bl;

	if ( surf->texinfo->flags & (SURF_SKY|SURF_TRANS33|SURF_TRANS66|SURF_WARP) )
		Com_Error (ERR_DROP, "R_BuildLightMap called for non-lit surface");

	smax = (surf->extents[0]>>4)+1;
	tmax = (surf->extents[1]>>4)+1;
	size = smax*tmax;
	if (size > (sizeof(s_blocklights)>>4) )
		Com_Error (ERR_DROP, "Bad s_blocklights size");

// set to full darkness if no light data
	if (!surf->samples)
	{
		byte dum;
		if (r_simple->value)
			dum = 255;		// Berserker: при отсутствии LM делаем полный свет для simple-рендера!
		else
			dum = 0;		// Berserker: при отсутствии LM делаем полную тьму для PPL-рендеров!
		for (i=0 ; i<size*3 ; i++)
			s_blocklights[i] = dum;
	}
	else
	{
		lightmap = surf->samples;
		bl = s_blocklights;

		float mod;
		if (r_simple->value)
			mod=r_modulate->value*8;
		else
			mod=r_modulate->value;
		for (i=0 ; i<size ; i++, bl+=3)
		{
			bl[0] = lightmap[i*3+0] * mod;	/// Berserker: изменим яркость LM; ambient lightning...
			bl[1] = lightmap[i*3+1] * mod;
			bl[2] = lightmap[i*3+2] * mod;
		}
	}

	stride -= (smax<<2);
	bl = s_blocklights;

	for (i=0 ; i<tmax ; i++, dest += stride)
	{
		for (j=0 ; j<smax ; j++)
		{
			r = Q_ftol( bl[0] );
			g = Q_ftol( bl[1] );
			b = Q_ftol( bl[2] );

			// catch negative lights
			if (r < 0)
				r = 0;
			if (g < 0)
				g = 0;
			if (b < 0)
				b = 0;

			// determine the brightest of the three color components
			if (r > g)
				max = r;
			else
				max = g;
			if (b > max)
				max = b;

			/*
			** rescale all the color components if the intensity of the greatest
			** channel exceeds 1.0
			*/
			if (max > 255)
			{
				float t = 255.0F / max;

				r = r*t;
				g = g*t;
				b = b*t;
			}

			dest[0] = b;
			dest[1] = g;
			dest[2] = r;
			dest[3] = 1;	/// Berserker: we are not using mono lightmaps, so we must wet alpha to 1 for correct rendering

			bl += 3;
			dest += 4;
		}
	}
}


void GL_CreateSurfaceLightmap (msurface_t *surf)
{
	int		smax, tmax;
	byte	*base;

	if (surf->flags & (SURF_DRAWSKY|SURF_DRAWTURB))
		return;

	smax = (surf->extents[0]>>4)+1;
	tmax = (surf->extents[1]>>4)+1;

	if ( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ) )
	{
		LM_UploadBlock();
		LM_InitBlock();
		if ( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ) )
			Com_Error( ERR_FATAL, "Consecutive calls to LM_AllocBlock(%d,%d) failed", smax, tmax );
	}

	surf->lightmaptexturenum = gl_lms.current_lightmap_texture;

	base = (byte*)trans + (surf->light_t * LIGHTMAP_SIZE + surf->light_s) * LIGHTMAP_BYTES;

	R_BuildLightMap (surf, base, LIGHTMAP_SIZE*LIGHTMAP_BYTES);
}


/*
================
SetupSurfaceConnectivity

Setup the neighour pointers of this surface's polygon.
================
*/
void SetupSurfaceConnectivity(msurface_t *surf)
{
	int				i, j, lindex;
	temp_connect_t	*tempEdge;

	if (surf->numedges > MAX_POLY_VERT)
		Com_DPrintf ("SetupSurfaceConnectivity: too many edges %i\n", surf->numedges);

	for (i=0 ; i<surf->numedges ; i++)
	{
		lindex = currentmodel->surfedges[surf->firstedge + i];
		tempEdge = tempEdges+abs(lindex);

		surf->polys->neighbours[i] = NULL;
		for (j=0; j<tempEdge->used; j++)
			if (tempEdge->poly[j] != surf->polys)
				surf->polys->neighbours[i] = tempEdge->poly[j];
	}
}


void Mod_LoadMarksurfaces (lump_t *l)
{	
	int		i, j, count;
	short		*in;
	msurface_t **out;
	
	in = (short *)(mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "Mod_LoadMarksurfaces: funny lump size in %s",loadmodel->name);
	count = l->filelen / sizeof(*in);
	out = (msurface_s **) Hunk_Alloc (count*sizeof(*out), true);
	if (caching__)
		return;
	loadmodel->marksurfaces = out;
	loadmodel->nummarksurfaces = count;

	for ( i=0 ; i<count ; i++)
	{
		j = LittleShort(in[i]);
		if (j < 0 ||  j >= loadmodel->numsurfaces)
			Com_Error (ERR_DROP, "Mod_LoadMarksurfaces: bad surface number");
		out[i] = loadmodel->surfaces + j;
	}
}


void Mod_LoadVisibility (lump_t *l)
{
	int		i;

	if (!l->filelen)
	{
		loadmodel->vis = NULL;
		return;
	}
	loadmodel->vis = (dvis_t *) Hunk_Alloc ( l->filelen, true );
	if (caching__)
		return;
	Memcpy (loadmodel->vis, mod_base + l->fileofs, l->filelen);

	loadmodel->vis->numclusters = LittleLong (loadmodel->vis->numclusters);
	for (i=0 ; i<loadmodel->vis->numclusters ; i++)
	{
		loadmodel->vis->bitofs[i][0] = LittleLong (loadmodel->vis->bitofs[i][0]);
		loadmodel->vis->bitofs[i][1] = LittleLong (loadmodel->vis->bitofs[i][1]);
	}
}


void Mod_LoadLeafs (lump_t *l)
{
	dleaf_t 	*in;
	mleaf_t 	*out;
	int			i, j, count, p;

	in = (dleaf_t *)(mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "Mod_LoadLeafs: funny lump size in %s",loadmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mleaf_t *) Hunk_Alloc (count*sizeof(*out), true);
	if (caching__)
		return;
	loadmodel->leafs = out;
	loadmodel->numleafs = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		for (j=0 ; j<3 ; j++)
		{
			out->minmaxs[j] = LittleShort (in->mins[j]);
			out->minmaxs[3+j] = LittleShort (in->maxs[j]);
		}

		p = LittleLong(in->contents);
		out->contents = p;

		out->cluster = LittleShort(in->cluster);
		out->area = LittleShort(in->area);

///		out->firstmarksurface = loadmodel->marksurfaces + LittleShort(in->firstleafface);
///		out->nummarksurfaces = LittleShort(in->numleaffaces);
			// make unsigned long from signed short
			WORD firstleafface = LittleShort(in->firstleafface) & 0xFFFF;
			out->nummarksurfaces = LittleShort(in->numleaffaces) & 0xFFFF;
			out->firstmarksurface = loadmodel->marksurfaces + firstleafface;

		if (p & (MASK_WATER))
			for (j=0 ; j<out->nummarksurfaces ; j++)
				out->firstmarksurface[j]->flags |= SURF_UNDERWATER;
	}
}


void Mod_SetParent (mnode_t *node, mnode_t *parent)
{
	node->parent = parent;
	if (node->contents != -1)
		return;
	Mod_SetParent (node->children[0], node);
	Mod_SetParent (node->children[1], node);
}


void Mod_LoadNodes (lump_t *l)
{
	int			i, j, count, p;
	dnode_t		*in;
	mnode_t 	*out;

	in = (dnode_t *)(mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "Mod_LoadNodes: funny lump size in %s",loadmodel->name);
	count = l->filelen / sizeof(*in);
	out = (mnode_t *) Hunk_Alloc (count*sizeof(*out), true);
	if (caching__)
		return;
	loadmodel->nodes = out;
	loadmodel->numnodes = count;

	for ( i=0 ; i<count ; i++, in++, out++)
	{
		for (j=0 ; j<3 ; j++)
		{
			out->minmaxs[j] = LittleShort (in->mins[j]);
			out->minmaxs[3+j] = LittleShort (in->maxs[j]);
		}
	
		p = LittleLong(in->planenum);
		out->plane = loadmodel->planes + p;

		out->firstsurface = LittleShort (in->firstface);
		out->numsurfaces = LittleShort (in->numfaces);
		out->contents = -1;	// differentiate from leafs

		for (j=0 ; j<2 ; j++)
		{
			p = LittleLong (in->children[j]);
			if (p >= 0)
				out->children[j] = loadmodel->nodes + p;
			else
				out->children[j] = (mnode_t *)(loadmodel->leafs + (-1 - p));
		}
	}
	
	Mod_SetParent (loadmodel->nodes, NULL);	// sets nodes and leafs
}


void Mod_LoadBrushModel (model_t *mod, void *buffer)
{
	int			i;
	dheader_t	*header;
	mmodel_t 	*bm;

	loadmodel->type = mod_brush;
	if (loadmodel != mod_known)
		Com_Error (ERR_DROP, "Mod_LoadBrushModel: loaded a brush model after the world");

	header = (dheader_t *)buffer;

	i = LittleLong (header->version);
	if (i != BSPVERSION)
		Com_Error (ERR_DROP, "Mod_LoadBrushModel: %s has wrong version number (%i should be %i)", mod->name, i, BSPVERSION);

// swap all the lumps
	mod_base = (byte *)header;

	for (i=0 ; i<sizeof(dheader_t)/4 ; i++)
		((int *)header)[i] = LittleLong ( ((int *)header)[i]);

// load into heap
	Mod_LoadVertexes (&header->lumps[LUMP_VERTEXES]);
	Mod_LoadEdges (&header->lumps[LUMP_EDGES]);
	Mod_LoadSurfedges (&header->lumps[LUMP_SURFEDGES]);
	Mod_LoadLighting (&header->lumps[LUMP_LIGHTING]);
	Mod_LoadPlanes (&header->lumps[LUMP_PLANES]);
	Mod_LoadTexinfo (&header->lumps[LUMP_TEXINFO]);
	Mod_LoadFaces (&header->lumps[LUMP_FACES]);
///	if (caching__)
///		return;
	Mod_LoadMarksurfaces (&header->lumps[LUMP_LEAFFACES]);
	Mod_LoadVisibility (&header->lumps[LUMP_VISIBILITY]);
	Mod_LoadLeafs (&header->lumps[LUMP_LEAFS]);
	Mod_LoadNodes (&header->lumps[LUMP_NODES]);
	Mod_LoadSubmodels (&header->lumps[LUMP_MODELS]);
	if (caching__)
		return;
	Mod_CalcSurfColors();
	mod->numframes = 2;		// regular and alternate animation

//
// set up the submodels
//
	for (i=0 ; i<mod->numsubmodels ; i++)
	{
		model_t	*starmod;

		bm = &mod->submodels[i];
		starmod = &mod_inline[i];

		*starmod = *loadmodel;

		starmod->firstmodelsurface = bm->firstface;
		starmod->nummodelsurfaces = bm->numfaces;
		starmod->firstnode = bm->headnode;
		if (starmod->firstnode >= loadmodel->numnodes)
			Com_Error (ERR_DROP, "Mod_LoadBrushModel: inline model %i has bad firstnode", i);

		VectorCopy (bm->maxs, starmod->maxs);
		VectorCopy (bm->mins, starmod->mins);
		starmod->radius = bm->radius;

		if (i == 0)
			*loadmodel = *starmod;

		starmod->numleafs = bm->visleafs;
	}
}


/// Berserker: like as Create_Demosstrings
/// sample:
/// ext  = ".bsp"
/// ext2 = "*.bsp"
/// path = "maps/"
/// path2 = "maps"
void Create_Cache (char *ext, char *ext2, char *path, char *path2, void (*CreateCache)(char *name))
{
	searchpath_t	*search;
	pack_t			*pak;
	int				slot;
	int				dirCount, i;
	char			*dirFiles[MAX_FILES];
	bool			stop = false;
	bool			exit = false;

	unsigned fsgl;
	if (*fs_gamedir)
		fsgl = strlen(fs_gamedir);
	else
		fsgl = 0;

	// ищем сначала в паках
	for (search = fs_searchpaths ; search ; search = search->next)
	{
		// игнорируем неродные ресурсы
		if (fsgl)
		{
			if (search->pack && !search->disabled)
			{
				if (Q_strncasecmp(search->pack->filename, fs_gamedir, fsgl))
					continue;
			}
		}

		// is the element a pak file?
		if (search->pack && !search->disabled)
		{
			// look through all the pak file elements
			pak = search->pack;
			if (pak->isPK2)
			{
				unsigned	hash = Com_HashKey(pak->filename);
				slot = -1;
				for(i=0; i<MAX_PAKS; i++)
				{
					if(ZipCache[i].uf)
					{
						if (ZipCache[i].hash == hash)
						{
							if(!b_stricmp(ZipCache[i].pak_name, pak->filename))
							{
								Memcpy(&pf, &ZipCache[i], sizeof(zipfile_t));		// Нашли уже открытый ZIP и загрузили его описатель в pf
								goto clc;
							}
						}
					}
					else
					{
						if(slot == -1)
						{
							slot = i;	// Ищем в slot первый свободный номер в кэше
							break;
						}
					}
				}
				strcpy(&ZipCache[slot].pak_name[0], pak->filename);	// Если не нашли zip в кэше, откроем его...
				if (!PackFileOpen (&ZipCache[slot]))
					Com_Error(ERR_FATAL, "Error opening pk2-file: %s", pak->filename);
				Memcpy(&pf, &ZipCache[slot], sizeof(zipfile_t));
				goto clc2;
clc:			slot = i;
clc2:			for(i=0; i<pf.gi.number_entry; i++)
				{
					if(pf.fi[i].size)
					{
						char	nam[256];
						Memcpy(nam, pf.fi[i].name, 256);
						if(!Q_strncasecmp(nam, path, strlen(path)))
						{
							if(!Q_strcasecmp(nam+strlen(nam)-4, ext))
							{
								/// save ZIP state
								zipfile_t	pf_bak = pf;
								CreateCache(nam);
								SCR_UpdateScreen ();
								/// restore ZIP state
								pf = pf_bak;
							}
						}
					}
				}
			}
			else
			{
				// look through all the pak file elements
				for (i=0 ; i<pak->numfiles ; i++)
				{
					char	nam[256];
					Memcpy(nam, pak->files[i].name, 256);
					if(!Q_strncasecmp(nam, path, strlen(path)))
						if(!Q_strcasecmp(nam+strlen(nam)-4, ext))
						{
							CreateCache(nam);
							SCR_UpdateScreen ();
						}
				}
			}
		}
	}

	// ищем в файлах
loop:if (stop == true)
	{
base:	dirCount = Sys_FindFiles(va("./%s/%s", BASEDIRNAME, path2), ext2, dirFiles, MAX_FILES, true, false);
		exit = true;
	}
	else
	{
		stop = true;
		if (strcmp(fs_gamedir+2, BASEDIRNAME))	// если работает mod:
			dirCount = Sys_FindFiles(va("%s/%s", fs_gamedir, path2), ext2, dirFiles, MAX_FILES, true, false);
		else
			goto base;
	}

	for (i = 0; i < dirCount; i++)
	{
		char	*name = dirFiles[i];

		// игнорируем неродные ресурсы
		if (fsgl)
			if (Q_strncasecmp(name, fs_gamedir, fsgl))
				goto stop;

		while (*name!=0)
		{
			if(*name=='/' || *name=='\\')
				goto good;
			name++;
		}
		goto stop;
good:	name++;
		while (*name!=0)
		{
			if(*name=='/' || *name=='\\')
				goto good2;
			name++;
		}
		goto stop;
good2:	name++;

		CreateCache(name);
		SCR_UpdateScreen ();

stop:	Z_Free(dirFiles[i]);
	}

	if (!exit)
		goto loop;
}


void CreateBSPCache(char *name)
{
	caching__ = true;
	caching_calc = false;
	int start_clock_ = Sys_Milliseconds ();

	Com_Printf("Caching '%s': ", name);
	SCR_UpdateScreen ();

	if (r_worldmodel)
		Mod_Free (r_worldmodel);
	map_name[0]=0;

	// explicitly free the old map if different
	// this guarantees that mod_known[0] is the world map
	if ( mod_known[0].name[0] )
		Mod_Free (&mod_known[0]);
	r_worldmodel = Mod_ForName(name, true, 1, false, true);
	Mod_Free (r_worldmodel);
	r_worldmodel = NULL;

	if (caching_calc)
		Com_Printf ("%3.3f seconds\n", (Sys_Milliseconds() - start_clock_)/1000.0);
	else
		Com_Printf ("skipped.\n");
	SCR_UpdateScreen ();
	caching__ = false;
}

void CreateMDLCache(char *name)
{
	bool caching_calc_;
	int start_clock_ = Sys_Milliseconds();

	Com_Printf("Caching '%s': ", name);
	SCR_UpdateScreen();

	caching_calc = false;
	model_t *model = Mod_ForName(name, false, 1, false, true);
	if (model)
	{
		Mod_Free (model);
		caching_calc_ = caching_calc;
		caching_calc = false;
		model = Mod_ForName(name, false, 1, true, true);
		if (!model)
			goto err;
		Mod_Free(model);
		if (caching_calc || caching_calc_)
			Com_Printf("%3.3f seconds\n", (Sys_Milliseconds() - start_clock_) / 1000.0);
		else
			Com_Printf("skipped.\n");
	}
	else
err:	Com_Printf("^1Error loading.\n");

	SCR_UpdateScreen ();
}

float	time_cachebsp;
void CL_CacheBSP_f()
{
	time_cachebsp = 0;

	if (cls.state != ca_disconnected)
	{
		Com_Printf("^3Map loaded, command not available.\nUse \"killserver\" or \"disconnect\".\n");
		return;
	}

	int start_clock_ = Sys_Milliseconds ();
	if( Cmd_Argc() == 2 )
	{
		char name[MAX_QPATH];
		Com_sprintf (name, sizeof(name), "maps/%s.bsp",Cmd_Argv(1));
		CreateBSPCache(name);
	}
	else
		Create_Cache (".bsp", "*.bsp", "maps/", "maps", CreateBSPCache);

	time_cachebsp = (Sys_Milliseconds() - start_clock_)/1000.0;
	Com_Printf ("^2BSP caching time: %4.3f seconds\n", time_cachebsp);
}

float	time_cachemdl;
void CL_CacheMDL_f()
{
	time_cachemdl = 0;

	if (cls.state != ca_disconnected)
	{
		Com_Printf("^3Map loaded, command not available.\nUse \"killserver\" or \"disconnect\".\n");
		return;
	}

	int start_clock_ = Sys_Milliseconds ();

	if( Cmd_Argc() == 2 )
		CreateMDLCache(Cmd_Argv(1));
	else
	{	// для незапакованных файлов будет искать в папке без учёта подпапок.
		// поэтому тут укажем самые основные папки...
		Create_Cache (".md2", "*.md2", "players/Cyborg/", "players/Cyborg", CreateMDLCache);
		Create_Cache (".md3", "*.md3", "players/Cyborg/", "players/Cyborg", CreateMDLCache);
		Create_Cache (".ase", "*.ase", "players/Cyborg/", "players/Cyborg", CreateMDLCache);

		Create_Cache (".md2", "*.md2", "players/Female/", "players/Female", CreateMDLCache);
		Create_Cache (".md3", "*.md3", "players/Female/", "players/Female", CreateMDLCache);
		Create_Cache (".ase", "*.ase", "players/Female/", "players/Female", CreateMDLCache);

		Create_Cache (".md2", "*.md2", "players/Male/", "players/Male", CreateMDLCache);
		Create_Cache (".md3", "*.md3", "players/Male/", "players/Male", CreateMDLCache);
		Create_Cache (".ase", "*.ase", "players/Male/", "players/Male", CreateMDLCache);

		Create_Cache (".md2", "*.md2", "players/QuakeGuy/", "players/QuakeGuy", CreateMDLCache);	// поддержка QuakeGuy
		Create_Cache (".md3", "*.md3", "players/QuakeGuy/", "players/QuakeGuy", CreateMDLCache);	// поддержка QuakeGuy
		Create_Cache (".ase", "*.ase", "players/QuakeGuy/", "players/QuakeGuy", CreateMDLCache);	// поддержка QuakeGuy

		Create_Cache (".md2", "*.md2", "models/", "models", CreateMDLCache);
		Create_Cache (".md3", "*.md3", "models/", "models", CreateMDLCache);
		Create_Cache (".ase", "*.ase", "models/", "models", CreateMDLCache);

		Create_Cache (".sp2", "*.sp2", "sprites/", "sprites", CreateMDLCache);
		Create_Cache (".sp3", "*.sp3", "sprites/", "sprites", CreateMDLCache);
	}

	time_cachemdl = (Sys_Milliseconds() - start_clock_)/1000.0;
	Com_Printf ("^2MDL caching time: %4.3f seconds\n", time_cachemdl);
}

void CL_Cache_f()
{
	if (cls.state != ca_disconnected)
	{
		Com_Printf("^3Map loaded, command not available.\nUse \"killserver\" or \"disconnect\".\n");
		return;
	}

///	if( Cmd_Argc() != 1 )
///		Com_Printf("^3Command must be run without parameters.\n");
///	else
	{
		Com_Printf("^2Maps caching, please wait...\n");
		CL_CacheBSP_f();
		Com_Printf("^2Models caching, please wait...\n");
		CL_CacheMDL_f();
		Com_Printf ("^2Total caching time: %5.3f seconds\n", time_cachebsp + time_cachemdl);
	}
}

bool Ent_MirrorClip(float *mins, float *maxs)
{
	if (!r_mirror)
		return false;

	if (BOX_ON_PLANE_SIDE(mins, maxs, &(mirror_plane->plane)) == mirror_clipside)
		return true;

///		Tenebrae bug: обрезается дальние зоны если стоим близко от зеркала. Это неверно, ибо в зеркале д.б. все видно!
///	if ( BOX_ON_PLANE_SIDE(mins, maxs, &mirror_far_plane) == 1)
///		return true;

	return false;
}

/*
/// толку меньше, вреда (тормозов) больше... накуй!
float min__max[6];
void R_RecursiveMask(mnode_t *node)
{
	mleaf_t	*l;
	int		c;

	if (node->contents == CONTENTS_SOLID)
		return;		// solid

	if (node->contents != -1)
	{
		if (!BBoxIntersectBBox(node->minmaxs, min__max))
		{
			l = (mleaf_t *)node;
			c = l->cluster;
			currententity->vis[c>>3] &= ~(1<<(c&7));
		}
		return;
	}

	R_RecursiveMask (node->children[0]);
	R_RecursiveMask (node->children[1]);
}
*/

bool InLightVISEntity()
{
	int		leafs[MAX_MAP_LEAFS];
	int		i, count;
	int		longs;
	vec3_t	mins, maxs;

	if (currententity->framecount != r_framecount)
	{
		currententity->framecount = r_framecount;
		if ( currententity->flags & RF_WEAPONMODEL )
		{
			for (i=0 ; i<3 ; i++)
			{
				mins[i] = currententity->origin[i] - currentmodel->radius;
				maxs[i] = currententity->origin[i] + currentmodel->radius;
			}
		}
		else
		{
			if (currententity->minmax[0]==0 &&
				currententity->minmax[1]==0 &&
				currententity->minmax[2]==0 &&
				currententity->minmax[3]==0 &&
				currententity->minmax[4]==0 &&
				currententity->minmax[5]==0)
			{	// это модели RF_VIEWERMODEL (r_playershadow=1)
				if(currentmodel->type == mod_alias)
					R_CullAliasModel( &frustum[0], false, true );			// чисто только для расчета currententity->minmax
				else if(currentmodel->type == mod_alias_md3)
					R_CullAliasMD3Model( &frustum[0], false, true );			// чисто только для расчета currententity->minmax
			}

			mins[0] = currententity->minmax[0];		// brushes - same as...
			mins[1] = currententity->minmax[1];
			mins[2] = currententity->minmax[2];
			maxs[0] = currententity->minmax[3];
			maxs[1] = currententity->minmax[4];
			maxs[2] = currententity->minmax[5];
		}

		count = CM_BoxLeafnums (mins, maxs, leafs, r_worldmodel->numleafs/*MAX_MAP_LEAFS*/, NULL);
		if (count < 1)
			Com_Error (ERR_FATAL, "InLightVISEntity: count < 1");
		longs = (CM_NumClusters()+31)>>5;

		// convert leafs to clusters
		for (i=0 ; i<count ; i++)
			leafs[i] = CM_LeafCluster(leafs[i]);

		Memset(&currententity->vis, 0, (((r_worldmodel->numleafs+31)>>5)<<2)/*MAX_MAP_LEAFS/8*/);
		for (i=0 ; i<count ; i++)
			currententity->vis[leafs[i]>>3] |= (1<<(leafs[i]&7));

///		min__max[0] = mins[0];
///		min__max[1] = mins[1];
///		min__max[2] = mins[2];
///		min__max[3] = maxs[0];
///		min__max[4] = maxs[1];
///		min__max[5] = maxs[2];
///		R_RecursiveMask(r_worldmodel->nodes);	/// Для currententity->vis
	}

	return HasSharedLeafs (currentshadowlight->vis, currententity->vis);
}


void GetOcclusionResults()
{
	int		i;
	GLuint	result;

	if (r_mirror)
		return;

	if (!gl_config.occlusion)
		return;

	if (r_drawentities->value)
	{
		for (i=0 ; i<MAX_ENTITIES ; i++)
		{
			if (r_entities_occUSE[i] == occ_framecount-1)
			{
			// форсируем получение результата (возможен "простой" GPU)
	// FIXME: проверить, требуется ли этот GL-вызов???
///	glGetQueryObjectuivARB(r_entities_occID[i], GL_QUERY_RESULT_AVAILABLE_ARB, &result);

				// get occlusion test result
				glGetQueryObjectuivARB(r_entities_occID[i], GL_QUERY_RESULT_ARB, &result);
				occResults[i].occResult = result ? 1 : 0;
			}
		}
	}

	if (!r_nolights->value)
	{
		for(i=0; i<numStaticShadowLights; i++)
		{
			currentshadowlight = &shadowlights[i];
			if(currentshadowlight->isStatic)
			{
				if (currentshadowlight->framecount == occ_framecount-1)
				{
					if (currentshadowlight->directVisibled)
						continue;
					if (!intersectsBoxPoint(currentshadowlight->mins_cone, currentshadowlight->maxs_cone, r_origin))
					{
			// форсируем получение результата (возможен "простой" GPU)
	// FIXME: проверить, требуется ли этот GL-вызов???
///	glGetQueryObjectuivARB(currentshadowlight->occID, GL_QUERY_RESULT_AVAILABLE_ARB, &result);

						// get occlusion test result
						glGetQueryObjectuivARB(currentshadowlight->occID, GL_QUERY_RESULT_ARB, &result);
						if (!result)
						{
							currentshadowlight->visible = false;
							c_no_lights++;
						}
					}
				}
			}
		}
	}

///	glGetError();
}


void MarkShadowEntities()
{
	int		i;
	float	mins[3], maxs[3];

	if (!r_drawentities->value)
		return;

	cl_numlightvisedicts = 0;
	for (i=0 ; i<r_newrefdef.num_entities ; i++)
	{
		currententity = &r_newrefdef.entities[i];

		if (currententity->flags & (RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE | RF_BEAM | RF_TRANSLUCENT | RF_DISTORT))
			continue;

		if ((currententity->flags & RF_NOCASTSHADOW) && (currententity->flags & RF_FULLBRIGHT))
			continue;

		if (currententity->ownerkey && (currentshadowlight->ownerkey == currententity->ownerkey))
			continue;

		if (sky_world)
			if (currententity->sky_model != drawing_sky_world)
				continue;

		currentmodel = currententity->model;

		if (!currentmodel || (currentmodel->flags & RF_DISTORT))
			continue;

		if ((currentmodel->flags & RF_NOCASTSHADOW) && (currentmodel->flags & RF_FULLBRIGHT))
			continue;

		if (currentmodel->type != mod_alias && currentmodel->type != mod_alias_md3 && currentmodel->type != mod_brush)
			continue;

		if (r_mirror && !r_mirror_models->value && currentmodel->type != mod_brush)
			continue;

		if (r_mirror)
		{
			mins[0] = currententity->minmax[0];
			mins[1] = currententity->minmax[1];
			mins[2] = currententity->minmax[2];
			maxs[0] = currententity->minmax[3];
			maxs[1] = currententity->minmax[4];
			maxs[2] = currententity->minmax[5];
		}
		else
			goto yesyes;

		if (!Ent_MirrorClip(mins, maxs))
			//Dont cast shadows with the ent this light is attached to because
			//when the light is partially in the model shadows will look weird.
yesyes:		if (InShadowEntity())
			{
				if (currentshadowlight->_cone)
					if (!InConeEntity())
						continue;

				if (!InLightVISEntity())
					continue;

///				currententity->lightTimestamp = r_lightTimestamp;
				cl_lightvisedicts[cl_numlightvisedicts] = currententity;
				cl_numlightvisedicts++;
			}
	}
}


/*
=============
R_DrawBrushModelVolumes

Draw the shadow volumes of the brush model.
They are dynamically calculated.
=============
*/
void R_DrawBrushModelVolumes()
{
	int			i, j, k;
	msurface_t	*surf, *baksurf;
	model_t		*clmodel;
	glpoly_t	*poly;
	vec3_t		v1;
	bool		shadow;
	vec3_t		oldlightorigin;

	clmodel = currententity->model;
	baksurf = surf = &clmodel->surfaces[clmodel->firstmodelsurface];

	//backup light origin since we will have to translate
	//light into model space
	VectorCopy (currentshadowlight->origin, oldlightorigin);

	VectorSubtract (currentshadowlight->origin, currententity->origin, currentshadowlight->origin);
	if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
	{
		vec3_t	temp;
		vec3_t	forward, right, up;

		VectorCopy (modelorg, temp);
		AngleVectors (currententity->angles, forward, right, up);

		VectorCopy (currentshadowlight->origin, temp);
		currentshadowlight->origin[0] = DotProduct (temp, forward);
		currentshadowlight->origin[1] = -DotProduct (temp, right);
		currentshadowlight->origin[2] = DotProduct (temp, up);

	}

	glPushMatrix ();
	R_RotateForEntity (currententity);

	float scale = 32 * currentshadowlight->radius;	/// было 10

	int pass;
	if(r_faststencil->value && (gl_config.ati_separate_stencil || gl_config.nv_stencil_two_side))
	{
		if(gl_config.ati_separate_stencil)
		{
			glStencilOpSeparateATI(GL_BACK, GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);
			glStencilOpSeparateATI(GL_FRONT, GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
		}
		else
		{
			glActiveStencilFaceEXT (GL_BACK);
			glStencilOp (GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);
			glActiveStencilFaceEXT (GL_FRONT);
			glStencilOp (GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
		}
		pass = 1;
	}
	else
		pass = 2;

	// generate vertex buffer
	int vb = 0;
	for (i=0 ; i<clmodel->nummodelsurfaces ; i++, surf++)
	{
		if (surf->polys->lightTimestamp != r_lightTimestamp)
			continue;

		if (!surf->texinfo->image->CastShadow)		// Форсируем отброс тени
			if (surf->texinfo->flags & SURF_FENCE)
				continue;

		poly = surf->polys;
		for (j=0 ; j<surf->numedges ; j++)
		{
			VectorCopy(poly->verts[j], vcache[vb*2+0]);
			VectorSubtract (poly->verts[j], currentshadowlight->origin, v1);
			float sca = scale/VectorLength(v1);
			vcache[vb*2+1][0] = v1[0] * sca + poly->verts[j][0];
			vcache[vb*2+1][1] = v1[1] * sca + poly->verts[j][1];
			vcache[vb*2+1][2] = v1[2] * sca + poly->verts[j][2];
			vb++;
		}
	}

	// generate index buffer
	int ib = 0;
	int surfBase = 0;
	surf = baksurf;
	for (i=0 ; i<clmodel->nummodelsurfaces ; i++, surf++)
	{
		if (surf->polys->lightTimestamp != r_lightTimestamp)
			continue;

		if (!surf->texinfo->image->CastShadow)		// Форсируем отброс тени
			if (surf->texinfo->flags & SURF_FENCE)
				continue;

		poly = surf->polys;
		//check if neighbouring polygons are shadowed
		for (j=0 ; j<surf->numedges ; j++)
		{
			shadow = false;
			if (poly->neighbours[j] != NULL)
			{
				if ( poly->neighbours[j]->lightTimestamp != poly->lightTimestamp)
					shadow = true;
			}
			else
				shadow = true;

			if (shadow)
			{
				int jj = (j+1)%poly->numverts;

				icache[ib++] = j*2+0	+surfBase;
				icache[ib++] = j*2+1	+surfBase;
				icache[ib++] = jj*2+1	+surfBase;

				icache[ib++] = j*2+0	+surfBase;
				icache[ib++] = jj*2+1	+surfBase;
				icache[ib++] = jj*2+0	+surfBase;
			}
		}

		//Draw near light cap
		for (j=0; j<surf->numedges-2 ; j++)
		{
			icache[ib++] = 0			+surfBase;
			icache[ib++] = (j+1)*2+0	+surfBase;
			icache[ib++] = (j+2)*2+0	+surfBase;
		}

		//Draw extruded cap
		for (j=0; j<surf->numedges-2 ; j++)
		{
			icache[ib++] = 1			+surfBase;
			icache[ib++] = (j+2)*2+1	+surfBase;
			icache[ib++] = (j+1)*2+1	+surfBase;
		}

		surfBase += surf->numedges*2;
	}

	if (ib)
	{
		GL_BindVBO(NULL);
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, vcache);

		for (k=0; k<pass; k++)
		{
			if(pass==2)
			{
				if(k==0)
				{
					//Pass 1 increase
					glCullFace(GL_BACK);
					glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
				}
				else
				{
					// Second Pass. Decrease Stencil Value In The Shadow
					glCullFace(GL_FRONT);
					glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
				}
			}

			glDrawElements(GL_TRIANGLES, ib, GL_UNSIGNED_INT, icache);
		}
		glDisableClientState(GL_VERTEX_ARRAY);
	}

	VectorCopy(oldlightorigin,currentshadowlight->origin);
	glPopMatrix ();
	shadow_used = true;
}


/*
=============
R_CalcAliasFrameLerp

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void R_CalcAliasFrameLerp (dmdl_t *paliashdr)
{
	daliasframe_t	*frame, *oldframe;
	dtrivertx_t	*v, *ov, *verts;
	float	frontlerp;
	vec3_t	move, vectors[3];
	vec3_t	frontv, backv;
	int		i;
	float	*lerp;
	float	backlerp;

///	if ( r_lerpmodels->value )
		backlerp = currententity->backlerp;
///	else
///		backlerp = 0;

	frame = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->frame * paliashdr->framesize);
	verts = v = frame->verts;
	oldframe = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->oldframe * paliashdr->framesize);
	ov = oldframe->verts;

	frontlerp = 1.0 - backlerp;

	// move should be the delta back to the previous frame * backlerp
	VectorSubtract (currententity->oldorigin, currententity->origin, move);

	if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
	{
		vec3_t	temp;
		VectorCopy(move, temp);
		AngleVectors (currententity->angles, vectors[0], vectors[1], vectors[2]);
		move[0] = DotProduct (temp, vectors[0]);
		move[1] = -DotProduct (temp, vectors[1]);
		move[2] = DotProduct (temp, vectors[2]);
	}

	VectorAdd (move, oldframe->translate, move);

	for (i=0 ; i<3 ; i++)
	{
		move[i] = backlerp*move[i] + frontlerp*frame->translate[i];
		frontv[i] = frontlerp*frame->scale[i];
		backv[i] = backlerp*oldframe->scale[i];
	}

	lerp = tempVertexArray[0];

	if (currententity->flags & ( RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE ) )
	{
		float	scale;
		if (currententity->flags & RF_WEAPONMODEL)
			scale = POWERSUIT_SCALE_WEAPON;
		else
			scale = POWERSUIT_SCALE;

		for (i=0 ; i < paliashdr->num_xyz; i++, v++, ov++, lerp+=3 )
		{
			float *normal = r_avertexnormals[verts[i].lightnormalindex];

			lerp[0] = move[0] + ov->v[0]*backv[0] + v->v[0]*frontv[0] + normal[0] * scale;
			lerp[1] = move[1] + ov->v[1]*backv[1] + v->v[1]*frontv[1] + normal[1] * scale;
			lerp[2] = move[2] + ov->v[2]*backv[2] + v->v[2]*frontv[2] + normal[2] * scale;
		}
	}
	else
	{
		for (i=0 ; i < paliashdr->num_xyz; i++, v++, ov++, lerp+=3)
		{
			lerp[0] = move[0] + ov->v[0]*backv[0] + v->v[0]*frontv[0];
			lerp[1] = move[1] + ov->v[1]*backv[1] + v->v[1]*frontv[1];
			lerp[2] = move[2] + ov->v[2]*backv[2] + v->v[2]*frontv[2];
		}
	}
}


void R_CalcAliasFrameLerpShadow (dmdl_t *paliashdr)
{
	int		i, j;
	int		index_xyz;
	dtriangle_t *tri;
	vec3_t	triangle[3];
	vec3_t	v1, v2, normal;

	R_CalcAliasFrameLerp (paliashdr);

	tri = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);
	float scale = 32 * currentshadowlight->radius;	/// было 10

	for (i=0 ; i<paliashdr->num_tris ; i++)
	{
		for (j=0 ; j<3 ; j++)
		{
			index_xyz = tri[i].index_xyz[j];
			VectorCopy(tempVertexArray[index_xyz], triangle[j]);
			/// calculate extruded verts
			VectorSubtract (tempVertexArray[index_xyz], currentshadowlight->origin, v1);
			float sca = scale/VectorLength(v1);
			extrudedVerts[index_xyz][0] = v1[0] * sca + tempVertexArray[index_xyz][0];
			extrudedVerts[index_xyz][1] = v1[1] * sca + tempVertexArray[index_xyz][1];
			extrudedVerts[index_xyz][2] = v1[2] * sca + tempVertexArray[index_xyz][2];
		}

		if(j==3)
		{	/// Все точки данного треугольника попали в прорисовку, вычислим нормаль...

			//calculate their normal
			VectorSubtract(triangle[0], triangle[1], v1);
			VectorSubtract(triangle[2], triangle[1], v2);
			CrossProduct(v2,v1, normal);		
			VectorScale(normal, 1/VectorLength(normal), trinormals[i]);

			//calculate visibility
			if (DotProduct(trinormals[i], currentshadowlight->origin) <= DotProduct(triangle[0], trinormals[i]))
				viss[i] = 0;	// Невидимая грань
			else
				viss[i] = 1;	// Видимая грань
		}
		else
			viss[i] = 0;		// Невидимая грань
	}
}


void R_CalcAliasFrameShadowVolume (dmdl_t *paliashdr)
{
	int		i, j, pos;
	int		index0, index1;

	neighbours_t *tri = currentmodel->triangles;
	dtriangle_t *triangle = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);

	TrisCounter = pos = 0;
	for (i=0; i<paliashdr->num_tris; i++)
	{
		if (viss[i])
		{
			for (j=0; j<3; j++)
			{
				bool shadow = false;
				if (tri[i].neighbours[j] == -1)
					shadow = true;
				else
					if (!viss[tri[i].neighbours[j]])
						shadow = true;

				if (shadow)
				{
					index0 = triangle[i].index_xyz[j];
					index1 = triangle[i].index_xyz[(j+1)%3];

					TrisVerts[pos++] = tempVertexArray[index0][0];
					TrisVerts[pos++] = tempVertexArray[index0][1];
					TrisVerts[pos++] = tempVertexArray[index0][2];
					TrisVerts[pos++] = extrudedVerts[index0][0];
					TrisVerts[pos++] = extrudedVerts[index0][1];
					TrisVerts[pos++] = extrudedVerts[index0][2];
					TrisVerts[pos++] = extrudedVerts[index1][0];
					TrisVerts[pos++] = extrudedVerts[index1][1];
					TrisVerts[pos++] = extrudedVerts[index1][2];

					TrisVerts[pos++] = extrudedVerts[index1][0];
					TrisVerts[pos++] = extrudedVerts[index1][1];
					TrisVerts[pos++] = extrudedVerts[index1][2];
					TrisVerts[pos++] = tempVertexArray[index1][0];
					TrisVerts[pos++] = tempVertexArray[index1][1];
					TrisVerts[pos++] = tempVertexArray[index1][2];
					TrisVerts[pos++] = tempVertexArray[index0][0];
					TrisVerts[pos++] = tempVertexArray[index0][1];
					TrisVerts[pos++] = tempVertexArray[index0][2];

					TrisCounter += 6;
				}
			}
		}
	}

	for (i=0; i<paliashdr->num_tris; i++)
	{
		if (viss[i])
		{
			for (j=0; j<3; j++)
			{
				index0 = triangle[i].index_xyz[j];
				TrisVerts[pos++] = tempVertexArray[index0][0];
				TrisVerts[pos++] = tempVertexArray[index0][1];
				TrisVerts[pos++] = tempVertexArray[index0][2];
			}

			for (j=2; j>=0; j--)
			{
				index0 = triangle[i].index_xyz[j];
				TrisVerts[pos++] = extrudedVerts[index0][0];
				TrisVerts[pos++] = extrudedVerts[index0][1];
				TrisVerts[pos++] = extrudedVerts[index0][2];
			}

			TrisCounter += 6;
		}
	}
}


void R_DrawAliasShadowVolume ()
{
	dmdl_t *	paliashdr;
	vec3_t		oldlightpos;

///	if (currententity->lightTimestamp != r_lightTimestamp)
///		return;

	// Если (рисуем не-зазеркалье и ентити "модель игрока") и (если запрещены тени от "модели игрока") то игнор.
	if (!r_mirror && (currententity->flags & RF_VIEWERMODEL))
		if (!(r_playershadow->value && r_shadows->value > 1))
			return;

	if (currententity->flags & (RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE | RF_FULLBRIGHT | RF_BEAM | RF_TRANSLUCENT | RF_WEAPONMODEL))
		return;

	currentmodel = currententity->model;
	paliashdr = (dmdl_t *)currentmodel->extradata;

	//Put light into model space
	VectorCopy(currentshadowlight->origin, oldlightpos);
	VectorSubtract(currentshadowlight->origin, currententity->origin, currentshadowlight->origin);
	if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
	{
		vec3_t	temp;
		vec3_t	forward, right, up;

		AngleVectors (currententity->angles, forward, right, up);
		VectorCopy (currentshadowlight->origin, temp);
		currentshadowlight->origin[0] = DotProduct (temp, forward);
		currentshadowlight->origin[1] = -DotProduct (temp, right);
		currentshadowlight->origin[2] = DotProduct (temp, up);
	}

	glPushMatrix ();
	R_RotateForEntity (currententity);

	R_CalcAliasFrameLerpShadow (paliashdr);
	R_CalcAliasFrameShadowVolume(paliashdr);

	GL_BindVBO(NULL);
	glEnableClientState(GL_VERTEX_ARRAY);

	bool	en = false;
	if(r_faststencil->value && (gl_config.ati_separate_stencil || gl_config.nv_stencil_two_side))
	{
		if(gl_config.ati_separate_stencil)
		{
			glStencilOpSeparateATI(GL_BACK, GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
			glStencilOpSeparateATI(GL_FRONT, GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);
		}
		else
		{
			glActiveStencilFaceEXT (GL_BACK);
			glStencilOp (GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
			glActiveStencilFaceEXT (GL_FRONT);
			glStencilOp (GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);
		}
		en = true;
	}
	else
	{
		//Pass 1 increase
		glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
		glCullFace(GL_FRONT);
		glVertexPointer(3, GL_FLOAT, 0, TrisVerts);
		glDrawArrays(GL_TRIANGLES, 0, TrisCounter);

		// Second Pass. Decrease Stencil Value In The Shadow
		glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
		glCullFace(GL_BACK);
	}

	if (en)
		glVertexPointer(3, GL_FLOAT, 0, TrisVerts);
	glDrawArrays(GL_TRIANGLES, 0, TrisCounter);

	glDisableClientState(GL_VERTEX_ARRAY);

	glCullFace(GL_FRONT);

	glPopMatrix();
	VectorCopy(oldlightpos,currentshadowlight->origin);
	shadow_used = true;
}


void R_DrawMD3AliasShadowVolume ()
{
	int				i, j, pos, k;
	maliasmodel_t	*paliashdr;
	float			frontlerp, backlerp;
	vec3_t			oldlightpos, move, v1, v2, normal, trinormal;
	maliasframe_t	*frame, *oldframe;
	maliasmesh_t	*mesh;
	maliasvertex_t	*v, *ov;
	WORD			*idx, *index0, *index1;

	// Если (рисуем не-зазеркалье и ентити "модель игрока") и (если запрещены тени от "модели игрока") то игнор.
	if (!r_mirror && (currententity->flags & RF_VIEWERMODEL))
		if (!(r_playershadow->value && r_shadows->value > 1))
			return;

	if (currententity->flags & (RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE | RF_FULLBRIGHT | RF_BEAM | RF_TRANSLUCENT | RF_WEAPONMODEL))
		return;

	currentmodel = currententity->model;
	paliashdr = (maliasmodel_t *)currentmodel->extradata;

	//Put light into model space
	VectorCopy(currentshadowlight->origin, oldlightpos);
	VectorSubtract(currentshadowlight->origin, currententity->origin, currentshadowlight->origin);
	VectorSubtract (currententity->oldorigin, currententity->origin, move);
	if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
	{
		vec3_t	temp;
		vec3_t	forward, right, up;
		AngleVectors (currententity->angles, forward, right, up);

		VectorCopy (currentshadowlight->origin, temp);
		currentshadowlight->origin[0] = DotProduct (temp, forward);
		currentshadowlight->origin[1] = -DotProduct (temp, right);
		currentshadowlight->origin[2] = DotProduct (temp, up);

		VectorCopy (move, temp);
		move[0] = DotProduct (temp, forward);
		move[1] = -DotProduct (temp, right);
		move[2] = DotProduct (temp, up);
	}

	glPushMatrix ();
	R_RotateForEntity (currententity);

///	if ( !r_lerpmodels->value )
///		currententity->backlerp = 0;

	backlerp = currententity->backlerp;
	frontlerp = 1.0 - backlerp;
	frame = paliashdr->frames + currententity->frame;
	oldframe = paliashdr->frames + currententity->oldframe;

	VectorAdd (move, oldframe->translate, move);
	for (j=0 ; j<3 ; j++)
		move[j] = backlerp*move[j] + frontlerp*frame->translate[j];

	for (i=0; i < paliashdr->num_meshes; i++)
	{
		mesh = &paliashdr->meshes[i];
		// select skin
		image_t	*skin = mesh->img_skins[min(currententity->skinnum, MD3_MAX_SKINS-1)];
		if (!skin || skin == r_notexture)
		{
			if (currententity->skin)
				skin = currententity->skin;	// custom player skin
		}
		if(!skin)
		{
			skin = mesh->img_skins[0];
			if(!skin)
				continue;
		}

		if (skin->fx == fx_distort)
			continue;

		if(!skin->CastShadow)		// Форсируем отброс тени
			if(skin->AlphaTest || skin->Translucent33 || skin->Translucent66)
				continue;

		v = mesh->vertexes + currententity->frame*mesh->num_verts;
		ov = mesh->vertexes + currententity->oldframe*mesh->num_verts;
		float scale = 32 * currentshadowlight->radius;	/// было 10
		for ( j = 0; j < mesh->num_verts; j++, v++, ov++ )
		{
			VectorSet (tempVertexArray[j], 
					move[0] + ov->point[0]*backlerp + v->point[0]*frontlerp,
					move[1] + ov->point[1]*backlerp + v->point[1]*frontlerp,
					move[2] + ov->point[2]*backlerp + v->point[2]*frontlerp);

			/// calculate extruded verts
			VectorSubtract (tempVertexArray[j], currentshadowlight->origin, v1);
			float sca = scale/VectorLength(v1);
			extrudedVerts[j][0] = v1[0] * sca + tempVertexArray[j][0];
			extrudedVerts[j][1] = v1[1] * sca + tempVertexArray[j][1];
			extrudedVerts[j][2] = v1[2] * sca + tempVertexArray[j][2];
		}

		idx = mesh->indexes;
		for ( j = 0; j < mesh->num_tris; j++)
		{
			//calculate their normal
			VectorSubtract(tempVertexArray[*idx], tempVertexArray[*(idx+1)], v1);
			VectorSubtract(tempVertexArray[*(idx+2)], tempVertexArray[*(idx+1)], v2);
			CrossProduct(v2,v1, normal);
			VectorScale(normal, 1/VectorLength(normal), trinormal);

			//calculate visibility
			if (DotProduct(trinormal, currentshadowlight->origin) <= DotProduct(tempVertexArray[*idx], trinormal))
				viss[j] = 0;	// Невидимая грань
			else
				viss[j] = 1;	// Видимая грань

			idx += 3;
		}

		idx = mesh->indexes;
		TrisCounter = pos = 0;
		for (j = 0; j < mesh->num_tris; j++)
		{
			if (viss[j])
			{
				for (k=0; k<3; k++)
				{
					bool shadow = false;
					if(mesh->triangles[j].neighbours[k] == -1)
						shadow = true;
					else
						if (!viss[mesh->triangles[j].neighbours[k]])
							shadow = true;

					if (shadow)
					{
						index0 = idx+k;
						index1 = idx+((k+1)%3);

						TrisVerts[pos++] = tempVertexArray[*index0][0];
						TrisVerts[pos++] = tempVertexArray[*index0][1];
						TrisVerts[pos++] = tempVertexArray[*index0][2];
						TrisVerts[pos++] = extrudedVerts[*index0][0];
						TrisVerts[pos++] = extrudedVerts[*index0][1];
						TrisVerts[pos++] = extrudedVerts[*index0][2];
						TrisVerts[pos++] = extrudedVerts[*index1][0];
						TrisVerts[pos++] = extrudedVerts[*index1][1];
						TrisVerts[pos++] = extrudedVerts[*index1][2];

						TrisVerts[pos++] = extrudedVerts[*index1][0];
						TrisVerts[pos++] = extrudedVerts[*index1][1];
						TrisVerts[pos++] = extrudedVerts[*index1][2];
						TrisVerts[pos++] = tempVertexArray[*index1][0];
						TrisVerts[pos++] = tempVertexArray[*index1][1];
						TrisVerts[pos++] = tempVertexArray[*index1][2];
						TrisVerts[pos++] = tempVertexArray[*index0][0];
						TrisVerts[pos++] = tempVertexArray[*index0][1];
						TrisVerts[pos++] = tempVertexArray[*index0][2];

						TrisCounter += 6;
					}
				}
			}

			idx += 3;
		}

		idx = mesh->indexes;
		for (j = 0; j < mesh->num_tris; j++)
		{
			if (viss[j])
			{
				for (k=0; k<3; k++)
				{
					index0 = idx+k;
					TrisVerts[pos++] = tempVertexArray[*index0][0];
					TrisVerts[pos++] = tempVertexArray[*index0][1];
					TrisVerts[pos++] = tempVertexArray[*index0][2];
				}

				for (k=2; k>=0; k--)
				{
					index0 = idx+k;
					TrisVerts[pos++] = extrudedVerts[*index0][0];
					TrisVerts[pos++] = extrudedVerts[*index0][1];
					TrisVerts[pos++] = extrudedVerts[*index0][2];
				}

				TrisCounter += 6;
			}

			idx += 3;
		}

		GL_BindVBO(NULL);
		glEnableClientState(GL_VERTEX_ARRAY);

		bool	en = false;
		if(r_faststencil->value && (gl_config.ati_separate_stencil || gl_config.nv_stencil_two_side))
		{
			if(gl_config.ati_separate_stencil)
			{
				glStencilOpSeparateATI(GL_BACK, GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
				glStencilOpSeparateATI(GL_FRONT, GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);
			}
			else
			{
				glActiveStencilFaceEXT (GL_BACK);
				glStencilOp (GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
				glActiveStencilFaceEXT (GL_FRONT);
				glStencilOp (GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);
			}
			en = true;
		}
		else
		{
			//Pass 1 increase
			glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
			glCullFace(GL_FRONT);
			glVertexPointer(3, GL_FLOAT, 0, TrisVerts);
			glDrawArrays(GL_TRIANGLES, 0, TrisCounter);

			// Second Pass. Decrease Stencil Value In The Shadow
			glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
			glCullFace(GL_BACK);
		}

		if (en)
			glVertexPointer(3, GL_FLOAT, 0, TrisVerts);
		glDrawArrays(GL_TRIANGLES, 0, TrisCounter);

		glDisableClientState(GL_VERTEX_ARRAY);

		glCullFace(GL_FRONT);
	}

	glPopMatrix();
	VectorCopy(oldlightpos,currentshadowlight->origin);
	shadow_used = true;
}


void R_DrawEntsShadowVolumes(bool SelfShadow, bool all)
{
	int		i;
	bool	nomdl;

	if (!r_drawentities->value)
		return;

	if (r_mirror)
	{
		if (!r_mirror_shadows->value)
			return;
		nomdl = (!r_mirror_models->value);
	}
	else
	{
		if(!r_shadows->value)
			return;
		nomdl = false;
	}

	for (i=0 ; i<cl_numlightvisedicts ; i++)
	{
		currententity = cl_lightvisedicts[i];

		if (currententity->model->type == mod_brush)
		{
			if (!all && !SelfShadow)
				continue;
			R_DrawBrushModelVolumes();
		}
		else if(!r_mirror && r_shadows->value <= 1)	// тени для алиасов рисуем только если r_shadows > 1
				continue;
		else if (r_mirror && r_mirror_shadows->value <= 1)
				continue;
		else if (currententity->model->type == mod_alias)
		{
			if(nomdl)
				continue;

			if (!all)
			{
				if (SelfShadow && (currententity->model->flags & RF_NOSELFSHADOW))
					continue;
				if (!SelfShadow && !(currententity->model->flags & RF_NOSELFSHADOW))
					continue;
			}

			if ((currententity->flags & (RF_NOCASTSHADOW|RF_DISTORT)) || (currententity->model->flags & (RF_NOCASTSHADOW|RF_DISTORT)))
				continue;

//			if (r_mirror)
//			{
//				vec3_t dist;
//				VectorSubtract (currententity->origin, currentshadowlight->origin, dist);
//				float colorscale = 1 - (VectorLength(dist) / currentshadowlight->radius);
//				//it will be a very faint shadow
//				if (colorscale < 0.1)
//					continue;
//			}

			R_DrawAliasShadowVolume();
		}
		else if (currententity->model->type == mod_alias_md3)
		{
			if(nomdl)
				continue;

			if (!all)
			{
				if (SelfShadow && (currententity->model->flags & RF_NOSELFSHADOW))
					continue;
				if (!SelfShadow && !(currententity->model->flags & RF_NOSELFSHADOW))
					continue;
			}

			if ((currententity->flags & (RF_NOCASTSHADOW|RF_DISTORT)) || (currententity->model->flags & (RF_NOCASTSHADOW|RF_DISTORT)))
				continue;

//			if (r_mirror)
//			{
//				vec3_t dist;
//				VectorSubtract (currententity->origin, currentshadowlight->origin, dist);
//				float colorscale = 1 - (VectorLength(dist) / currentshadowlight->radius);
//				//it will be a very faint shadow
//				if (colorscale < 0.1)
//					continue;
//			}

			R_DrawMD3AliasShadowVolume();
		}
	}
}


inline void R_DrawBrushObjectLightGENERIC4()
{
	bool onepass;

	if (currentshadowlight->filtercube_start)
	{
		GL_EnableAttenuationShader(false);
		R_DrawAttenuationBrush(true);
		GL_DisableAttenuationShader();
		onepass = false;
	}
	else
		onepass = true;

	GL_EnableShaderGENERIC(false, onepass);
	R_DrawBrush();
	GL_DisableShaderGENERIC(onepass);

	if(!onepass)
		R_DrawAttenuationBrush(false);
}


inline void R_DrawBrushObjectLightGENERIC6()
{
	GL_EnableShaderGENERIC(false, true);
	R_DrawBrush();
	GL_DisableShaderGENERIC(true);
}


inline void R_DrawBrushObjectLightNVIDIA4()
{
	bool onepass;

	if (currentshadowlight->filtercube_start)
	{
		GL_EnableAttenuationShader(false);
		R_DrawAttenuationBrush(true);
		GL_DisableAttenuationShader();
		onepass = false;
	}
	else
		onepass = true;

	GL_EnableShaderNVIDIA4(false, onepass);
	R_DrawBrush();
	GL_DisableShaderNVIDIA4(onepass);

	if(!onepass)
		R_DrawAttenuationBrush(false);
}


inline void R_DrawBrushObjectLightATI4()
{
	GL_EnableShaderATI4(false);
	R_DrawBrushATI4();
	GL_DisableShaderATI4();
}


inline void R_DrawBrushObjectLightATI6()
{
	GL_EnableShaderATI6(false);
	R_DrawBrushATI6();
	GL_DisableShaderATI6();
}


inline void R_DrawBrushObjectLightARB(int shader)
{
	GL_EnableShaderARB(false, shader);
	if (!arb_vp_work && currentshadowlight->has_parallax_surf)	// FIX: форсируем VP для NoBump+Parallax
	{
		if (gl_config.vp)
		{
			arb_vp_work = true;
			glEnable(GL_VERTEX_PROGRAM_ARB);
		}
	}
	if (arb_vp_work)
		R_DrawBrushARB_vp(shader);
	else
		R_DrawBrushARB(shader);
	GL_DisableShaderARB();
}


/*
=============
R_DrawBrushObjectLight

Idea: Creepy object oriented programming by using function pointers.
Function: Puts the light into object space, adapts the world->eye matrix
and calls BrushGeoSender if all that has been done.
Cleans up afterwards so nothing has changed.
=============
*/
void R_DrawBrushObjectLight()
{
	vec3_t	oldlightorigin, oldvieworg;
	bool	extra = ((r_shader->value == SHADER_ATI6) || (r_shader->value == SHADER_ARB4) || (r_shader->value == SHADER_ARB6));

	VectorCopy (currentshadowlight->origin, oldlightorigin);
	VectorSubtract (currentshadowlight->origin, currententity->origin, currentshadowlight->origin);
	if(extra)
	{
		VectorCopy (r_origin, oldvieworg);
		VectorSubtract (r_origin, currententity->origin, r_origin);
	}

	if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
	{
		vec3_t	temp;
		vec3_t	forward, right, up;

		AngleVectors (currententity->angles, forward, right, up);

		VectorCopy (currentshadowlight->origin, temp);
		currentshadowlight->origin[0] = DotProduct (temp, forward);
		currentshadowlight->origin[1] = -DotProduct (temp, right);
		currentshadowlight->origin[2] = DotProduct (temp, up);

		if(extra)
		{
			VectorCopy (r_origin, temp);
			r_origin[0] = DotProduct (temp, forward);
			r_origin[1] = -DotProduct (temp, right);
			r_origin[2] = DotProduct (temp, up);
		}
	}

	glPushMatrix ();
	R_RotateForEntity (currententity);

	switch ((int)r_shader->value)
	{
	case SHADER_GENERIC4:
		R_DrawBrushObjectLightGENERIC4();
		break;
	case SHADER_GENERIC6:
		R_DrawBrushObjectLightGENERIC6();
		break;
	case SHADER_NVIDIA4:
		R_DrawBrushObjectLightNVIDIA4();
		break;
	case SHADER_ATI4:
		R_DrawBrushObjectLightATI4();
		break;
	case SHADER_ATI6:
		R_DrawBrushObjectLightATI6();
		break;
	case SHADER_ARB4:
	case SHADER_ARB6:
		R_DrawBrushObjectLightARB((int)r_shader->value);
		break;
	}

	VectorCopy(oldlightorigin, currentshadowlight->origin);
	if(extra)
		VectorCopy(oldvieworg, r_origin);
	glPopMatrix ();
	light_used = true;
}


/*
=============
R_DrawAttenuationAlias

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void R_DrawAttenuationAlias (dmdl_t *paliashdr, bool att)
{
	vec3_t	vertexArray[3*MAX_TRIANGLES];
	int		i, j, jj;
	dtriangle_t *tris;

	tris = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);
	if (!gl_config.vbo)
		R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...

	glColorMask(0,0,0,1);

	if (gl_config.vbo)
		jj = paliashdr->num_tris * 3;
	else
	{
		for (jj=0, i=0; i<paliashdr->num_tris; i++)
			for (j=0; j<3; j++, jj++)
				VectorCopy(tempVertexArray[tris[i].index_xyz[j]], vertexArray[jj]);
	}

	GL_SelectTexture(GL_TEXTURE0);
	if(att)
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (gl_config.vbo)
		{
			GL_BindVBO(currententity->vbo_xyz[0]);
			glTexCoordPointer(3, GL_FLOAT, 0, 0);
		}
		else
		{
			GL_BindVBO(NULL);
			glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
		}
	}
	else
		glDisable(GL_TEXTURE_2D);

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[0]);
		glVertexPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glVertexPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glDrawArrays (GL_TRIANGLES, 0, jj);

	glDisableClientState(GL_VERTEX_ARRAY);
	if(att)
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	else
		glEnable(GL_TEXTURE_2D);
	if (gl_config.vbo)
		GL_BindVBO(NULL);
	glColorMask(1,1,1,1);
}


/*
=============
R_DrawAttenuationMD3Alias

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void R_DrawAttenuationMD3Alias (maliasmesh_t *mesh, bool att, int i)
{
	glColorMask(0,0,0,1);

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if(att)
	{
		if (gl_config.vbo)
		{
			GL_BindVBO(currententity->vbo_xyz[i]);
			glTexCoordPointer(3, GL_FLOAT, 0, 0);
		}
		else
		{
			GL_BindVBO(NULL);
			glTexCoordPointer(3, GL_FLOAT, 0, tempVertexArray);
		}
	}
	else
	{
		if (gl_config.vbo)
		{
			GL_BindVBO(mesh->vbo_st);
			glTexCoordPointer(2, GL_FLOAT, 0, 0);
		}
		else
		{
			GL_BindVBO(NULL);
			glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
		}
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
	{
		if (!att)
			GL_BindVBO(currententity->vbo_xyz[i]);
		glVertexPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glVertexPointer(3, GL_FLOAT, 0, tempVertexArray);
	}

///	if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///		glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///	else
		glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
	glColorMask(1,1,1,1);
}


/*
=============
GL_DrawAliasFrameLerpLightATI4

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void GL_DrawAliasFrameLerpLightATI4 (dmdl_t *paliashdr)
{
	vec3_t	vertexArray[3*MAX_TRIANGLES];
	vec3_t	LightP[3*MAX_TRIANGLES];
	int		i, j, jj = 0;
	int		index_xyz;
	byte	*norms, *oldnorms;
	byte	*binormals, *oldbinormals;
	byte	*tangents, *oldtangents;
	vec3_t	lightDir;
	dtriangle_t *tris;
	daliasframe_t	*frame, *oldframe;
	dtrivertx_t	*verts, *oldverts;
	WORD		cache[MAX_VERTS];
	Memset(cache, 0xff, 2*MAX_VERTS);
	float	backlerp, frontlerp;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}

	unsigned offs, offs2;
	if (currentmodel->flags & RF_SMOOTHVECS)
		offs = paliashdr->num_xyz;
	else
		offs = paliashdr->num_tris;

	oldframe = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->oldframe * paliashdr->framesize);
	oldverts = oldframe->verts;
	offs2 = offs*currententity->oldframe;
	oldnorms = currentmodel->normals + offs2;
	oldbinormals = currentmodel->binormals + offs2;
	oldtangents = currentmodel->tangents + offs2;

	frame = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->frame * paliashdr->framesize);
	verts = frame->verts;
	offs2 = offs*currententity->frame;
	norms = currentmodel->normals + offs2;
	binormals = currentmodel->binormals + offs2;
	tangents = currentmodel->tangents + offs2;

	tris = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);

	if (!gl_config.vbo)
		R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...

	for (i=0; i<paliashdr->num_tris; i++)
	{
		for (j=0; j<3; j++, jj++)
		{
			index_xyz = tris[i].index_xyz[j];
			if(cache[index_xyz] == 0xffff)
			{
				VectorSubtract(currentshadowlight->origin, tempVertexArray[index_xyz], lightDir);

				int index2, oldindex2;
				vec3_t	tempnormal;
				if (currentmodel->flags & RF_SMOOTHVECS)
				{
					cache[index_xyz] = jj;
					index2 = verts[index_xyz].lightnormalindex;
					oldindex2 = oldverts[index_xyz].lightnormalindex;

					tempnormal[0] = r_avertexnormals[oldtangents[index_xyz]][0]*backlerp + r_avertexnormals[tangents[index_xyz]][0]*frontlerp;
					tempnormal[1] = r_avertexnormals[oldtangents[index_xyz]][1]*backlerp + r_avertexnormals[tangents[index_xyz]][1]*frontlerp;
					tempnormal[2] = r_avertexnormals[oldtangents[index_xyz]][2]*backlerp + r_avertexnormals[tangents[index_xyz]][2]*frontlerp;
					LightP[jj][0] = DotProduct(lightDir, tempnormal);

					tempnormal[0] = r_avertexnormals[oldbinormals[index_xyz]][0]*backlerp + r_avertexnormals[binormals[index_xyz]][0]*frontlerp;
					tempnormal[1] = r_avertexnormals[oldbinormals[index_xyz]][1]*backlerp + r_avertexnormals[binormals[index_xyz]][1]*frontlerp;
					tempnormal[2] = r_avertexnormals[oldbinormals[index_xyz]][2]*backlerp + r_avertexnormals[binormals[index_xyz]][2]*frontlerp;
					LightP[jj][1] = DotProduct(lightDir, tempnormal);

					tempnormal[0] = r_avertexnormals[oldindex2][0]*backlerp + r_avertexnormals[index2][0]*frontlerp;
					tempnormal[1] = r_avertexnormals[oldindex2][1]*backlerp + r_avertexnormals[index2][1]*frontlerp;
					tempnormal[2] = r_avertexnormals[oldindex2][2]*backlerp + r_avertexnormals[index2][2]*frontlerp;
					LightP[jj][2] = DotProduct(lightDir, tempnormal);
				}
				else
				{
					tempnormal[0] = r_avertexnormals[oldtangents[i]][0]*backlerp + r_avertexnormals[tangents[i]][0]*frontlerp;
					tempnormal[1] = r_avertexnormals[oldtangents[i]][1]*backlerp + r_avertexnormals[tangents[i]][1]*frontlerp;
					tempnormal[2] = r_avertexnormals[oldtangents[i]][2]*backlerp + r_avertexnormals[tangents[i]][2]*frontlerp;
					LightP[jj][0] = DotProduct(lightDir, tempnormal);

					tempnormal[0] = r_avertexnormals[oldbinormals[i]][0]*backlerp + r_avertexnormals[binormals[i]][0]*frontlerp;
					tempnormal[1] = r_avertexnormals[oldbinormals[i]][1]*backlerp + r_avertexnormals[binormals[i]][1]*frontlerp;
					tempnormal[2] = r_avertexnormals[oldbinormals[i]][2]*backlerp + r_avertexnormals[binormals[i]][2]*frontlerp;
					LightP[jj][1] = DotProduct(lightDir, tempnormal);

					tempnormal[0] = r_avertexnormals[oldnorms[i]][0]*backlerp + r_avertexnormals[norms[i]][0]*frontlerp;
					tempnormal[1] = r_avertexnormals[oldnorms[i]][1]*backlerp + r_avertexnormals[norms[i]][1]*frontlerp;
					tempnormal[2] = r_avertexnormals[oldnorms[i]][2]*backlerp + r_avertexnormals[norms[i]][2]*frontlerp;
					LightP[jj][2] = DotProduct(lightDir, tempnormal);
				}
			}
			else
				VectorCopy(LightP[cache[index_xyz]], LightP[jj]);

			if (!gl_config.vbo)
				VectorCopy(tempVertexArray[index_xyz], vertexArray[jj]);
		}
	}

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///	if (gl_config.vbo)
///	{
///		currententity->vbo_lightp[0] = R_VCLoadData(VBO_DYNAMIC, jj * sizeof(vec3_t), &LightP, VBO_STORE_ANY, NULL, NULL);
///		glTexCoordPointer(3, GL_FLOAT, 0, 0);
///	}
///	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, LightP);
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currentmodel->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	else
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[0]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (gl_config.vbo)
			glTexCoordPointer(3, GL_FLOAT, 0, 0);
		else
			glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		glVertexPointer(3, GL_FLOAT, 0, 0);
	else
		glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	glDrawArrays (GL_TRIANGLES, 0, jj);

	glDisableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		GL_BindVBO(NULL);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


/*
=============
GL_DrawAliasFrameLerpLightATI6

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void GL_DrawAliasFrameLerpLightATI6 (dmdl_t *paliashdr)
{
	vec3_t	vertexArray[3*MAX_TRIANGLES];
	vec3_t	H, LightP[3*MAX_TRIANGLES], tsH[3*MAX_TRIANGLES];
	int		i, j, jj = 0;
	int		index_xyz;
	byte	*norms, *oldnorms;
	byte	*binormals, *oldbinormals;
	byte	*tangents, *oldtangents;
	vec3_t	lightDir;
	dtriangle_t *tris;
	daliasframe_t	*frame, *oldframe;
	dtrivertx_t	*verts, *oldverts;
	WORD		cache[MAX_VERTS];
	Memset(cache, 0xff, 2*MAX_VERTS);
	float	backlerp, frontlerp;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}

	unsigned offs, offs2;
	if (currentmodel->flags & RF_SMOOTHVECS)
		offs = paliashdr->num_xyz;
	else
		offs = paliashdr->num_tris;

	oldframe = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->oldframe * paliashdr->framesize);
	oldverts = oldframe->verts;
	offs2 = offs*currententity->oldframe;
	oldnorms = currentmodel->normals + offs2;
	oldbinormals = currentmodel->binormals + offs2;
	oldtangents = currentmodel->tangents + offs2;

	frame = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->frame * paliashdr->framesize);
	verts = frame->verts;
	offs2 = offs*currententity->frame;
	norms = currentmodel->normals + offs2;
	binormals = currentmodel->binormals + offs2;
	tangents = currentmodel->tangents + offs2;

	tris = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);

	if (!gl_config.vbo)
		R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...

	for (i=0; i<paliashdr->num_tris; i++)
	{
		for (j=0; j<3; j++, jj++)
		{
			index_xyz = tris[i].index_xyz[j];
			if(cache[index_xyz] == 0xffff)
			{
				VectorSubtract(currentshadowlight->origin, tempVertexArray[index_xyz], lightDir);

				int index2, oldindex2;
				vec3_t	tempnormal[3];
				if (currentmodel->flags & RF_SMOOTHVECS)
				{
					cache[index_xyz] = jj;
					index2 = verts[index_xyz].lightnormalindex;
					oldindex2 = oldverts[index_xyz].lightnormalindex;

					tempnormal[0][0] = r_avertexnormals[oldtangents[index_xyz]][0]*backlerp + r_avertexnormals[tangents[index_xyz]][0]*frontlerp;
					tempnormal[0][1] = r_avertexnormals[oldtangents[index_xyz]][1]*backlerp + r_avertexnormals[tangents[index_xyz]][1]*frontlerp;
					tempnormal[0][2] = r_avertexnormals[oldtangents[index_xyz]][2]*backlerp + r_avertexnormals[tangents[index_xyz]][2]*frontlerp;

					tempnormal[1][0] = r_avertexnormals[oldbinormals[index_xyz]][0]*backlerp + r_avertexnormals[binormals[index_xyz]][0]*frontlerp;
					tempnormal[1][1] = r_avertexnormals[oldbinormals[index_xyz]][1]*backlerp + r_avertexnormals[binormals[index_xyz]][1]*frontlerp;
					tempnormal[1][2] = r_avertexnormals[oldbinormals[index_xyz]][2]*backlerp + r_avertexnormals[binormals[index_xyz]][2]*frontlerp;

					tempnormal[2][0] = r_avertexnormals[oldindex2][0]*backlerp + r_avertexnormals[index2][0]*frontlerp;
					tempnormal[2][1] = r_avertexnormals[oldindex2][1]*backlerp + r_avertexnormals[index2][1]*frontlerp;
					tempnormal[2][2] = r_avertexnormals[oldindex2][2]*backlerp + r_avertexnormals[index2][2]*frontlerp;
				}
				else
				{
					tempnormal[0][0] = r_avertexnormals[oldtangents[i]][0]*backlerp + r_avertexnormals[tangents[i]][0]*frontlerp;
					tempnormal[0][1] = r_avertexnormals[oldtangents[i]][1]*backlerp + r_avertexnormals[tangents[i]][1]*frontlerp;
					tempnormal[0][2] = r_avertexnormals[oldtangents[i]][2]*backlerp + r_avertexnormals[tangents[i]][2]*frontlerp;

					tempnormal[1][0] = r_avertexnormals[oldbinormals[i]][0]*backlerp + r_avertexnormals[binormals[i]][0]*frontlerp;
					tempnormal[1][1] = r_avertexnormals[oldbinormals[i]][1]*backlerp + r_avertexnormals[binormals[i]][1]*frontlerp;
					tempnormal[1][2] = r_avertexnormals[oldbinormals[i]][2]*backlerp + r_avertexnormals[binormals[i]][2]*frontlerp;

					tempnormal[2][0] = r_avertexnormals[oldnorms[i]][0]*backlerp + r_avertexnormals[norms[i]][0]*frontlerp;
					tempnormal[2][1] = r_avertexnormals[oldnorms[i]][1]*backlerp + r_avertexnormals[norms[i]][1]*frontlerp;
					tempnormal[2][2] = r_avertexnormals[oldnorms[i]][2]*backlerp + r_avertexnormals[norms[i]][2]*frontlerp;
				}

				LightP[jj][0] = DotProduct(lightDir, tempnormal[0]);
				LightP[jj][1] = DotProduct(lightDir, tempnormal[1]);
				LightP[jj][2] = DotProduct(lightDir, tempnormal[2]);

///				VectorNormalize(LightP[jj]);
				VectorSubtract(r_origin, tempVertexArray[index_xyz], H);
///				VectorNormalize(H);

				tsH[jj][0] = DotProduct(H, tempnormal[0]);
				tsH[jj][1] = DotProduct(H, tempnormal[1]);
				tsH[jj][2] = DotProduct(H, tempnormal[2]);

				VectorAdd(LightP[jj], tsH[jj], tsH[jj]);
			}
			else
			{
				VectorCopy(LightP[cache[index_xyz]], LightP[jj]);
				VectorCopy(tsH[cache[index_xyz]], tsH[jj]);
			}

			if (!gl_config.vbo)
				VectorCopy(tempVertexArray[index_xyz], vertexArray[jj]);
		}
	}

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///	if (gl_config.vbo)
///	{
///		currententity->vbo_lightp[0] = R_VCLoadData(VBO_DYNAMIC, jj * sizeof(vec3_t), &LightP, VBO_STORE_ANY, NULL, NULL);
///		glTexCoordPointer(3, GL_FLOAT, 0, 0);
///	}
///	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, LightP);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///	if (gl_config.vbo)
///	{
///		currententity->vbo_tsh[0] = R_VCLoadData(VBO_DYNAMIC, jj * sizeof(vec3_t), &tsH, VBO_STORE_ANY, NULL, NULL);
///		glTexCoordPointer(3, GL_FLOAT, 0, 0);
///	}
///	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, tsH);
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currentmodel->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	else
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);

	GL_SelectTexture(GL_TEXTURE4);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[0]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE5);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (gl_config.vbo)
			glTexCoordPointer(3, GL_FLOAT, 0, 0);
		else
			glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		glVertexPointer(3, GL_FLOAT, 0, 0);
	else
		glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	glDrawArrays (GL_TRIANGLES, 0, jj);

	glDisableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		GL_BindVBO(NULL);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE5);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE4);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


/*
=============
GL_DrawAliasFrameLerpLightARB

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void GL_DrawAliasFrameLerpLightARB (dmdl_t *paliashdr, int shader)
{
	vec3_t	vertexArray[3*MAX_TRIANGLES];
	vec3_t	H, LightP[3*MAX_TRIANGLES], tsH[3*MAX_TRIANGLES];
	int		i, j, jj = 0;
	int		index_xyz;
	byte	*norms, *oldnorms;
	byte	*binormals, *oldbinormals;
	byte	*tangents, *oldtangents;
	vec3_t	lightDir;
	dtriangle_t *tris;
	daliasframe_t	*frame, *oldframe;
	dtrivertx_t	*verts, *oldverts;
	WORD		cache[MAX_VERTS];
	bool		need_tsH = (!currentshadowlight->nobump && !(shader==SHADER_ARB4 && currentshadowlight->filtercube_start));
	Memset(cache, 0xff, 2*MAX_VERTS);
	float	backlerp, frontlerp;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}

	unsigned offs, offs2;
	if (currentmodel->flags & RF_SMOOTHVECS)
		offs = paliashdr->num_xyz;
	else
		offs = paliashdr->num_tris;

	oldframe = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->oldframe * paliashdr->framesize);
	oldverts = oldframe->verts;
	offs2 = offs*currententity->oldframe;
	oldnorms = currentmodel->normals + offs2;
	oldbinormals = currentmodel->binormals + offs2;
	oldtangents = currentmodel->tangents + offs2;

	frame = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->frame * paliashdr->framesize);
	verts = frame->verts;
	offs2 = offs*currententity->frame;
	norms = currentmodel->normals + offs2;
	binormals = currentmodel->binormals + offs2;
	tangents = currentmodel->tangents + offs2;

	tris = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);

	if (!gl_config.vbo)
		R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...

	for (i=0; i<paliashdr->num_tris; i++)
	{
		for (j=0; j<3; j++, jj++)
		{
			index_xyz = tris[i].index_xyz[j];
			if (!currentshadowlight->nobump)
			{
				if(cache[index_xyz] == 0xffff)
				{
					VectorSubtract(currentshadowlight->origin, tempVertexArray[index_xyz], lightDir);
///					VectorNormalize(lightDir);

					int index2, oldindex2;
					vec3_t	tempnormal[3];
					if (currentmodel->flags & RF_SMOOTHVECS)
					{
						cache[index_xyz] = jj;
						index2 = verts[index_xyz].lightnormalindex;
						oldindex2 = oldverts[index_xyz].lightnormalindex;

						tempnormal[0][0] = r_avertexnormals[oldtangents[index_xyz]][0]*backlerp + r_avertexnormals[tangents[index_xyz]][0]*frontlerp;
						tempnormal[0][1] = r_avertexnormals[oldtangents[index_xyz]][1]*backlerp + r_avertexnormals[tangents[index_xyz]][1]*frontlerp;
						tempnormal[0][2] = r_avertexnormals[oldtangents[index_xyz]][2]*backlerp + r_avertexnormals[tangents[index_xyz]][2]*frontlerp;

						tempnormal[1][0] = r_avertexnormals[oldbinormals[index_xyz]][0]*backlerp + r_avertexnormals[binormals[index_xyz]][0]*frontlerp;
						tempnormal[1][1] = r_avertexnormals[oldbinormals[index_xyz]][1]*backlerp + r_avertexnormals[binormals[index_xyz]][1]*frontlerp;
						tempnormal[1][2] = r_avertexnormals[oldbinormals[index_xyz]][2]*backlerp + r_avertexnormals[binormals[index_xyz]][2]*frontlerp;

						tempnormal[2][0] = r_avertexnormals[oldindex2][0]*backlerp + r_avertexnormals[index2][0]*frontlerp;
						tempnormal[2][1] = r_avertexnormals[oldindex2][1]*backlerp + r_avertexnormals[index2][1]*frontlerp;
						tempnormal[2][2] = r_avertexnormals[oldindex2][2]*backlerp + r_avertexnormals[index2][2]*frontlerp;
					}
					else
					{
						tempnormal[0][0] = r_avertexnormals[oldtangents[i]][0]*backlerp + r_avertexnormals[tangents[i]][0]*frontlerp;
						tempnormal[0][1] = r_avertexnormals[oldtangents[i]][1]*backlerp + r_avertexnormals[tangents[i]][1]*frontlerp;
						tempnormal[0][2] = r_avertexnormals[oldtangents[i]][2]*backlerp + r_avertexnormals[tangents[i]][2]*frontlerp;

						tempnormal[1][0] = r_avertexnormals[oldbinormals[i]][0]*backlerp + r_avertexnormals[binormals[i]][0]*frontlerp;
						tempnormal[1][1] = r_avertexnormals[oldbinormals[i]][1]*backlerp + r_avertexnormals[binormals[i]][1]*frontlerp;
						tempnormal[1][2] = r_avertexnormals[oldbinormals[i]][2]*backlerp + r_avertexnormals[binormals[i]][2]*frontlerp;

						tempnormal[2][0] = r_avertexnormals[oldnorms[i]][0]*backlerp + r_avertexnormals[norms[i]][0]*frontlerp;
						tempnormal[2][1] = r_avertexnormals[oldnorms[i]][1]*backlerp + r_avertexnormals[norms[i]][1]*frontlerp;
						tempnormal[2][2] = r_avertexnormals[oldnorms[i]][2]*backlerp + r_avertexnormals[norms[i]][2]*frontlerp;
					}

					LightP[jj][0] = DotProduct(lightDir, tempnormal[0]);
					LightP[jj][1] = DotProduct(lightDir, tempnormal[1]);
					LightP[jj][2] = DotProduct(lightDir, tempnormal[2]);

					if (need_tsH)
					{
						VectorSubtract(r_origin, tempVertexArray[index_xyz], H);
///						VectorNormalize(H);

						tsH[jj][0] = DotProduct(H, tempnormal[0]);
						tsH[jj][1] = DotProduct(H, tempnormal[1]);
						tsH[jj][2] = DotProduct(H, tempnormal[2]);

						VectorAdd(LightP[jj], tsH[jj], tsH[jj]);
					}
				}
				else
				{
					VectorCopy(LightP[cache[index_xyz]], LightP[jj]);
					if (need_tsH)
						VectorCopy(tsH[cache[index_xyz]], tsH[jj]);
				}
			}

			if (!gl_config.vbo)
				VectorCopy(tempVertexArray[index_xyz], vertexArray[jj]);
		}
	}

	if (!currentshadowlight->nobump)
	{
		GL_SelectTexture(GL_TEXTURE1);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///		if (gl_config.vbo)
///		{
///			currententity->vbo_lightp[0] = R_VCLoadData(VBO_DYNAMIC, jj * sizeof(vec3_t), &LightP, VBO_STORE_ANY, NULL, NULL);
///			glTexCoordPointer(3, GL_FLOAT, 0, 0);
///		}
///		else
		{
			GL_BindVBO(NULL);
			glTexCoordPointer(3, GL_FLOAT, 0, LightP);
		}

		if (!currentshadowlight->filtercube_start)
		{
			GL_SelectTexture(GL_TEXTURE3);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///			if (gl_config.vbo)
///			{
///				currententity->vbo_tsh[0] = R_VCLoadData(VBO_DYNAMIC, jj * sizeof(vec3_t), &tsH, VBO_STORE_ANY, NULL, NULL);
///				glTexCoordPointer(3, GL_FLOAT, 0, 0);
///			}
///			else
			{
				GL_BindVBO(NULL);
				glTexCoordPointer(3, GL_FLOAT, 0, tsH);
			}
		}
		else if (shader==SHADER_ARB6)
		{
			GL_SelectTexture(GL_TEXTURE4);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///			if (gl_config.vbo)
///			{
///				currententity->vbo_tsh[0] = R_VCLoadData(VBO_DYNAMIC, jj * sizeof(vec3_t), &tsH, VBO_STORE_ANY, NULL, NULL);
///				glTexCoordPointer(3, GL_FLOAT, 0, 0);
///			}
///			else
			{
				GL_BindVBO(NULL);
				glTexCoordPointer(3, GL_FLOAT, 0, tsH);
			}
		}
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currentmodel->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[0]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	if (!currentshadowlight->nobump && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE3);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (gl_config.vbo)
			glTexCoordPointer(3, GL_FLOAT, 0, 0);
		else
			glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		glVertexPointer(3, GL_FLOAT, 0, 0);
	else
		glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	glDrawArrays (GL_TRIANGLES, 0, jj);

	glDisableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		GL_BindVBO(NULL);

	if (!currentshadowlight->nobump)
	{
		if (shader==SHADER_ARB6 && currentshadowlight->filtercube_start)
		{
			GL_SelectTexture(GL_TEXTURE4);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		}

		GL_SelectTexture(GL_TEXTURE3);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	if (!currentshadowlight->nobump)
	{
		GL_SelectTexture(GL_TEXTURE1);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


void GL_DrawAliasFrameLerpLightARB_vp (dmdl_t *paliashdr, int shader)
{
	vec3_t	normalArray[3*MAX_TRIANGLES], tangentArray[3*MAX_TRIANGLES], binormalArray[3*MAX_TRIANGLES], vertexArray[3*MAX_TRIANGLES];
	int		i, j, jj = 0;
	int		index_xyz;
	byte	*norms, *oldnorms;
	byte	*binormals, *oldbinormals;
	byte	*tangents, *oldtangents;
	dtriangle_t *tris;
	daliasframe_t	*frame, *oldframe;
	dtrivertx_t	*verts, *oldverts;
	float	backlerp, frontlerp;
	bool	calc0, calc1, calc2;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}

	unsigned offs, offs2;
	if (currentmodel->flags & RF_SMOOTHVECS)
		offs = paliashdr->num_xyz;
	else
		offs = paliashdr->num_tris;

	oldframe = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->oldframe * paliashdr->framesize);
	oldverts = oldframe->verts;
	offs2 = offs*currententity->oldframe;
	oldnorms = currentmodel->normals + offs2;
	oldbinormals = currentmodel->binormals + offs2;
	oldtangents = currentmodel->tangents + offs2;

	frame = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->frame * paliashdr->framesize);
	verts = frame->verts;
	offs2 = offs*currententity->frame;
	norms = currentmodel->normals + offs2;
	binormals = currentmodel->binormals + offs2;
	tangents = currentmodel->tangents + offs2;

	tris = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);

	calc0=calc1=calc2=true;
	if (!gl_config.vbo)
		R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...
	else
	{
		currententity->vbo_normals[0] = R_VCFindCache(VBO_STORE_NORMAL, currententity, 0);
		if (currententity->vbo_normals[0])
			calc0 = false;
		currententity->vbo_binormals[0] = R_VCFindCache(VBO_STORE_BINORMAL, currententity, 0);
		if (currententity->vbo_binormals[0])
			calc1 = false;
		currententity->vbo_tangents[0] = R_VCFindCache(VBO_STORE_TANGENT, currententity, 0);
		if (currententity->vbo_tangents[0])
			calc2 = false;
	}

	if (!calc0 && !calc1 && !calc2)
		jj = paliashdr->num_tris*3;
	else
	{
		for (i=0; i<paliashdr->num_tris; i++)
		{
			for (j=0; j<3; j++, jj++)
			{
				index_xyz = tris[i].index_xyz[j];
				int index2, oldindex2;
				if (currentmodel->flags & RF_SMOOTHVECS)
				{
					index2 = verts[index_xyz].lightnormalindex;
					oldindex2 = oldverts[index_xyz].lightnormalindex;

					if (calc0)
					{
						normalArray[jj][0] = r_avertexnormals[oldindex2][0]*backlerp + r_avertexnormals[index2][0]*frontlerp;
						normalArray[jj][1] = r_avertexnormals[oldindex2][1]*backlerp + r_avertexnormals[index2][1]*frontlerp;
						normalArray[jj][2] = r_avertexnormals[oldindex2][2]*backlerp + r_avertexnormals[index2][2]*frontlerp;
					}

					if (calc2)
					{
						tangentArray[jj][0] = r_avertexnormals[oldtangents[index_xyz]][0]*backlerp + r_avertexnormals[tangents[index_xyz]][0]*frontlerp;
						tangentArray[jj][1] = r_avertexnormals[oldtangents[index_xyz]][1]*backlerp + r_avertexnormals[tangents[index_xyz]][1]*frontlerp;
						tangentArray[jj][2] = r_avertexnormals[oldtangents[index_xyz]][2]*backlerp + r_avertexnormals[tangents[index_xyz]][2]*frontlerp;
					}

					if (calc1)
					{
						binormalArray[jj][0] = r_avertexnormals[oldbinormals[index_xyz]][0]*backlerp + r_avertexnormals[binormals[index_xyz]][0]*frontlerp;
						binormalArray[jj][1] = r_avertexnormals[oldbinormals[index_xyz]][1]*backlerp + r_avertexnormals[binormals[index_xyz]][1]*frontlerp;
						binormalArray[jj][2] = r_avertexnormals[oldbinormals[index_xyz]][2]*backlerp + r_avertexnormals[binormals[index_xyz]][2]*frontlerp;
					}
				}
				else
				{
					if (calc0)
					{
						normalArray[jj][0] = r_avertexnormals[oldnorms[i]][0]*backlerp + r_avertexnormals[norms[i]][0]*frontlerp;
						normalArray[jj][1] = r_avertexnormals[oldnorms[i]][1]*backlerp + r_avertexnormals[norms[i]][1]*frontlerp;
						normalArray[jj][2] = r_avertexnormals[oldnorms[i]][2]*backlerp + r_avertexnormals[norms[i]][2]*frontlerp;
					}

					if (calc2)
					{
						tangentArray[jj][0] = r_avertexnormals[oldtangents[i]][0]*backlerp + r_avertexnormals[tangents[i]][0]*frontlerp;
						tangentArray[jj][1] = r_avertexnormals[oldtangents[i]][1]*backlerp + r_avertexnormals[tangents[i]][1]*frontlerp;
						tangentArray[jj][2] = r_avertexnormals[oldtangents[i]][2]*backlerp + r_avertexnormals[tangents[i]][2]*frontlerp;
					}

					if (calc1)
					{
						binormalArray[jj][0] = r_avertexnormals[oldbinormals[i]][0]*backlerp + r_avertexnormals[binormals[i]][0]*frontlerp;
						binormalArray[jj][1] = r_avertexnormals[oldbinormals[i]][1]*backlerp + r_avertexnormals[binormals[i]][1]*frontlerp;
						binormalArray[jj][2] = r_avertexnormals[oldbinormals[i]][2]*backlerp + r_avertexnormals[binormals[i]][2]*frontlerp;
					}
				}

				if (!gl_config.vbo)
					VectorCopy(tempVertexArray[index_xyz], vertexArray[jj]);
			}
		}
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currentmodel->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);
	}

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		if (calc1)
			currententity->vbo_binormals[0] = R_VCLoadData(VBO_DYNAMIC, jj * sizeof(vec3_t), &binormalArray, VBO_STORE_BINORMAL, currententity, 0);
		else
			GL_BindVBO(currententity->vbo_binormals[0]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, binormalArray);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		if (calc0)
			currententity->vbo_normals[0] = R_VCLoadData(VBO_DYNAMIC, jj * sizeof(vec3_t), &normalArray, VBO_STORE_NORMAL, currententity, 0);
		else
			GL_BindVBO(currententity->vbo_normals[0]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, normalArray);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		if (calc2)
			currententity->vbo_tangents[0] = R_VCLoadData(VBO_DYNAMIC, jj * sizeof(vec3_t), &tangentArray, VBO_STORE_TANGENT, currententity, 0);
		else
			GL_BindVBO(currententity->vbo_tangents[0]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, tangentArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[0]);
		glVertexPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glVertexPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glDrawArrays (GL_TRIANGLES, 0, jj);

	glDisableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


/*
=============
GL_DrawMD3AliasFrameLerpLightATI4

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void GL_DrawMD3AliasFrameLerpLightATI4 (maliasmesh_t *mesh, int nmesh)
{
	int		i;
	vec3_t	lightDir;
	vec3_t	LightP[MD3_MAX_VERTS];
	float	backlerp, frontlerp;
	maliasvertex_t	*verts, *oldverts;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}
	verts = mesh->vertexes + currententity->frame * mesh->num_verts;
	oldverts = mesh->vertexes + currententity->oldframe * mesh->num_verts;

	for(i=0; i<mesh->num_verts; i++)
	{
		vec3_t	t, b, n;
#if 0
		n[0] = r_avertexnormals[verts[i].normal][0] * frontlerp + r_avertexnormals[oldverts[i].normal][0] * backlerp;
		n[1] = r_avertexnormals[verts[i].normal][1] * frontlerp + r_avertexnormals[oldverts[i].normal][1] * backlerp;
		n[2] = r_avertexnormals[verts[i].normal][2] * frontlerp + r_avertexnormals[oldverts[i].normal][2] * backlerp;

		t[0] = r_avertexnormals[verts[i].tangent][0] * frontlerp + r_avertexnormals[oldverts[i].tangent][0] * backlerp;
		t[1] = r_avertexnormals[verts[i].tangent][1] * frontlerp + r_avertexnormals[oldverts[i].tangent][1] * backlerp;
		t[2] = r_avertexnormals[verts[i].tangent][2] * frontlerp + r_avertexnormals[oldverts[i].tangent][2] * backlerp;

		b[0] = r_avertexnormals[verts[i].binormal][0] * frontlerp + r_avertexnormals[oldverts[i].binormal][0] * backlerp;
		b[1] = r_avertexnormals[verts[i].binormal][1] * frontlerp + r_avertexnormals[oldverts[i].binormal][1] * backlerp;
		b[2] = r_avertexnormals[verts[i].binormal][2] * frontlerp + r_avertexnormals[oldverts[i].binormal][2] * backlerp;
#else
		n[0] = verts[i].normal[0] * frontlerp + oldverts[i].normal[0] * backlerp;
		n[1] = verts[i].normal[1] * frontlerp + oldverts[i].normal[1] * backlerp;
		n[2] = verts[i].normal[2] * frontlerp + oldverts[i].normal[2] * backlerp;

		t[0] = verts[i].tangent[0] * frontlerp + oldverts[i].tangent[0] * backlerp;
		t[1] = verts[i].tangent[1] * frontlerp + oldverts[i].tangent[1] * backlerp;
		t[2] = verts[i].tangent[2] * frontlerp + oldverts[i].tangent[2] * backlerp;

		b[0] = verts[i].binormal[0] * frontlerp + oldverts[i].binormal[0] * backlerp;
		b[1] = verts[i].binormal[1] * frontlerp + oldverts[i].binormal[1] * backlerp;
		b[2] = verts[i].binormal[2] * frontlerp + oldverts[i].binormal[2] * backlerp;
#endif
		VectorSubtract(currentshadowlight->origin, tempVertexArray[i], lightDir);

		LightP[i][0] = DotProduct(lightDir, t);
		LightP[i][1] = DotProduct(lightDir, b);
		LightP[i][2] = DotProduct(lightDir, n);
	}

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///	if (gl_config.vbo)
///	{
///		currententity->vbo_lightp[nmesh] = R_VCLoadData(VBO_DYNAMIC, i * sizeof(vec3_t), &LightP, VBO_STORE_ANY, NULL, NULL);
///		glTexCoordPointer(3, GL_FLOAT, 0, 0);
///	}
///	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, LightP);
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(mesh->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	else
		glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[nmesh]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, tempVertexArray);
	}

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (gl_config.vbo)
			glTexCoordPointer(3, GL_FLOAT, 0, 0);
		else
			glTexCoordPointer(3, GL_FLOAT, 0, tempVertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		glVertexPointer(3, GL_FLOAT, 0, 0);
	else
		glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );

///	if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///		glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///	else
		glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glDisableClientState(GL_VERTEX_ARRAY);
}


/*
=============
GL_DrawMD3AliasFrameLerpLightATI6

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void GL_DrawMD3AliasFrameLerpLightATI6 (maliasmesh_t *mesh, int nmesh)
{
	int		i;
	vec3_t	lightDir, H;
	vec3_t	LightP[MD3_MAX_VERTS];
	vec3_t	tsH[MD3_MAX_VERTS];
	float	backlerp, frontlerp;
	maliasvertex_t	*verts, *oldverts;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}
	verts = mesh->vertexes + currententity->frame * mesh->num_verts;
	oldverts = mesh->vertexes + currententity->oldframe * mesh->num_verts;

	for(i=0; i<mesh->num_verts; i++)
	{
		vec3_t	t, b, n;
#if 0
		n[0] = r_avertexnormals[verts[i].normal][0] * frontlerp + r_avertexnormals[oldverts[i].normal][0] * backlerp;
		n[1] = r_avertexnormals[verts[i].normal][1] * frontlerp + r_avertexnormals[oldverts[i].normal][1] * backlerp;
		n[2] = r_avertexnormals[verts[i].normal][2] * frontlerp + r_avertexnormals[oldverts[i].normal][2] * backlerp;

		t[0] = r_avertexnormals[verts[i].tangent][0] * frontlerp + r_avertexnormals[oldverts[i].tangent][0] * backlerp;
		t[1] = r_avertexnormals[verts[i].tangent][1] * frontlerp + r_avertexnormals[oldverts[i].tangent][1] * backlerp;
		t[2] = r_avertexnormals[verts[i].tangent][2] * frontlerp + r_avertexnormals[oldverts[i].tangent][2] * backlerp;

		b[0] = r_avertexnormals[verts[i].binormal][0] * frontlerp + r_avertexnormals[oldverts[i].binormal][0] * backlerp;
		b[1] = r_avertexnormals[verts[i].binormal][1] * frontlerp + r_avertexnormals[oldverts[i].binormal][1] * backlerp;
		b[2] = r_avertexnormals[verts[i].binormal][2] * frontlerp + r_avertexnormals[oldverts[i].binormal][2] * backlerp;
#else
		n[0] = verts[i].normal[0] * frontlerp + oldverts[i].normal[0] * backlerp;
		n[1] = verts[i].normal[1] * frontlerp + oldverts[i].normal[1] * backlerp;
		n[2] = verts[i].normal[2] * frontlerp + oldverts[i].normal[2] * backlerp;

		t[0] = verts[i].tangent[0] * frontlerp + oldverts[i].tangent[0] * backlerp;
		t[1] = verts[i].tangent[1] * frontlerp + oldverts[i].tangent[1] * backlerp;
		t[2] = verts[i].tangent[2] * frontlerp + oldverts[i].tangent[2] * backlerp;

		b[0] = verts[i].binormal[0] * frontlerp + oldverts[i].binormal[0] * backlerp;
		b[1] = verts[i].binormal[1] * frontlerp + oldverts[i].binormal[1] * backlerp;
		b[2] = verts[i].binormal[2] * frontlerp + oldverts[i].binormal[2] * backlerp;
#endif
		VectorSubtract(currentshadowlight->origin, tempVertexArray[i], lightDir);

		LightP[i][0] = DotProduct(lightDir, t);
		LightP[i][1] = DotProduct(lightDir, b);
		LightP[i][2] = DotProduct(lightDir, n);

///		VectorNormalize(LightP[i]);
		VectorSubtract(r_origin, tempVertexArray[i], H);
///		VectorNormalize(H);

		tsH[i][0] = DotProduct(H, t);
		tsH[i][1] = DotProduct(H, b);
		tsH[i][2] = DotProduct(H, n);

		VectorAdd(LightP[i], tsH[i], tsH[i]);
	}

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///	if (gl_config.vbo)
///	{
///		currententity->vbo_lightp[nmesh] = R_VCLoadData(VBO_DYNAMIC, i * sizeof(vec3_t), &LightP, VBO_STORE_ANY, NULL, NULL);
///		glTexCoordPointer(3, GL_FLOAT, 0, 0);
///	}
///	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, LightP);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///	if (gl_config.vbo)
///	{
///		currententity->vbo_tsh[nmesh] = R_VCLoadData(VBO_DYNAMIC, i * sizeof(vec3_t), &tsH, VBO_STORE_ANY, NULL, NULL);
///		glTexCoordPointer(3, GL_FLOAT, 0, 0);
///	}
///	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, tsH);
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(mesh->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	else
		glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);

	GL_SelectTexture(GL_TEXTURE4);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[nmesh]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, tempVertexArray);
	}

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE5);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (gl_config.vbo)
			glTexCoordPointer(3, GL_FLOAT, 0, 0);
		else
			glTexCoordPointer(3, GL_FLOAT, 0, tempVertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		glVertexPointer(3, GL_FLOAT, 0, 0);
	else
		glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );

///	if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///		glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///	else
		glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

	if (currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE5);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE4);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glDisableClientState(GL_VERTEX_ARRAY);
}


void GL_DrawMD3AliasFrameLerpLightARB_vp (maliasmesh_t *mesh, int shader, maliasmodel_t *paliashdr, int nmesh)
{
	int		i;
	vec3_t	normalArray[MD3_MAX_VERTS], tangentArray[MD3_MAX_VERTS], binormalArray[MD3_MAX_VERTS];
	bool	calc0, calc1, calc2;
	float	backlerp, frontlerp;
	maliasvertex_t	*verts, *oldverts;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}
	verts = mesh->vertexes + currententity->frame * mesh->num_verts;
	oldverts = mesh->vertexes + currententity->oldframe * mesh->num_verts;

	calc0=calc1=calc2=true;
	if (gl_config.vbo)
	{
		currententity->vbo_normals[nmesh] = R_VCFindCache(VBO_STORE_NORMAL, currententity, nmesh);
		if (currententity->vbo_normals[nmesh])
			calc0 = false;
		currententity->vbo_binormals[nmesh] = R_VCFindCache(VBO_STORE_BINORMAL, currententity, nmesh);
		if (currententity->vbo_binormals[nmesh])
			calc1 = false;
		currententity->vbo_tangents[nmesh] = R_VCFindCache(VBO_STORE_TANGENT, currententity, nmesh);
		if (currententity->vbo_tangents[nmesh])
			calc2 = false;
	}

	if (!calc0 && !calc1 && !calc2)
		i = mesh->num_verts;
	else
	{
		for(i=0; i<mesh->num_verts; i++)
		{
			if (calc0)
			{
				///				normalArray[i][0] = r_avertexnormals[verts[i].normal][0]*frontlerp + r_avertexnormals[oldverts[i].normal][0]*backlerp;
				///				normalArray[i][1] = r_avertexnormals[verts[i].normal][1]*frontlerp + r_avertexnormals[oldverts[i].normal][1]*backlerp;
				///				normalArray[i][2] = r_avertexnormals[verts[i].normal][2]*frontlerp + r_avertexnormals[oldverts[i].normal][2]*backlerp;
				normalArray[i][0] = verts[i].normal[0] * frontlerp + oldverts[i].normal[0] * backlerp;
				normalArray[i][1] = verts[i].normal[1] * frontlerp + oldverts[i].normal[1] * backlerp;
				normalArray[i][2] = verts[i].normal[2] * frontlerp + oldverts[i].normal[2] * backlerp;
			}

			if (calc2)
			{
				///				tangentArray[i][0] = r_avertexnormals[verts[i].tangent][0]*frontlerp + r_avertexnormals[oldverts[i].tangent][0]*backlerp;
				///				tangentArray[i][1] = r_avertexnormals[verts[i].tangent][1]*frontlerp + r_avertexnormals[oldverts[i].tangent][1]*backlerp;
				///				tangentArray[i][2] = r_avertexnormals[verts[i].tangent][2]*frontlerp + r_avertexnormals[oldverts[i].tangent][2]*backlerp;
				tangentArray[i][0] = verts[i].tangent[0] * frontlerp + oldverts[i].tangent[0] * backlerp;
				tangentArray[i][1] = verts[i].tangent[1] * frontlerp + oldverts[i].tangent[1] * backlerp;
				tangentArray[i][2] = verts[i].tangent[2] * frontlerp + oldverts[i].tangent[2] * backlerp;
			}

			if (calc1)
			{
				///				binormalArray[i][0] = r_avertexnormals[verts[i].binormal][0]*frontlerp + r_avertexnormals[oldverts[i].binormal][0]*backlerp;
				///				binormalArray[i][1] = r_avertexnormals[verts[i].binormal][1]*frontlerp + r_avertexnormals[oldverts[i].binormal][1]*backlerp;
				///				binormalArray[i][2] = r_avertexnormals[verts[i].binormal][2]*frontlerp + r_avertexnormals[oldverts[i].binormal][2]*backlerp;
				binormalArray[i][0] = verts[i].binormal[0] * frontlerp + oldverts[i].binormal[0] * backlerp;
				binormalArray[i][1] = verts[i].binormal[1] * frontlerp + oldverts[i].binormal[1] * backlerp;
				binormalArray[i][2] = verts[i].binormal[2] * frontlerp + oldverts[i].binormal[2] * backlerp;
			}
		}
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(mesh->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
	}

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		if (calc1)
			currententity->vbo_binormals[nmesh] = R_VCLoadData(VBO_DYNAMIC, i * sizeof(vec3_t), &binormalArray, VBO_STORE_BINORMAL, currententity, nmesh);
		else
			GL_BindVBO(currententity->vbo_binormals[nmesh]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, binormalArray);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		if (calc0)
			currententity->vbo_normals[nmesh] = R_VCLoadData(VBO_DYNAMIC, i * sizeof(vec3_t), &normalArray, VBO_STORE_NORMAL, currententity, nmesh);
		else
			GL_BindVBO(currententity->vbo_normals[nmesh]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, normalArray);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		if (calc2)
			currententity->vbo_tangents[nmesh] = R_VCLoadData(VBO_DYNAMIC, i * sizeof(vec3_t), &tangentArray, VBO_STORE_TANGENT, currententity, nmesh);
		else
			GL_BindVBO(currententity->vbo_tangents[nmesh]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, tangentArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[nmesh]);
		glVertexPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );
	}

///	if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///		glDrawRangeElementsEXT(GL_TRIANGLES, 0, i, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///	else
		glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

	glDisableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		GL_BindVBO(NULL);

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


/*
=============
GL_DrawMD3AliasFrameLerpLightARB

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void GL_DrawMD3AliasFrameLerpLightARB (maliasmesh_t *mesh, int shader, int nmesh)
{
	int		i;
	vec3_t	lightDir, H;
	vec3_t	LightP[MD3_MAX_VERTS];
	vec3_t	tsH[MD3_MAX_VERTS];
	float	backlerp, frontlerp;
	maliasvertex_t	*verts, *oldverts;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}
	verts = mesh->vertexes + currententity->frame * mesh->num_verts;
	oldverts = mesh->vertexes + currententity->oldframe * mesh->num_verts;

	bool	need_tsH = (!currentshadowlight->nobump && !(shader==SHADER_ARB4 && currentshadowlight->filtercube_start));

	if (!currentshadowlight->nobump)
	{
		for(i=0; i<mesh->num_verts; i++)
		{
			vec3_t	t, b, n;
#if 0
			n[0] = r_avertexnormals[verts[i].normal][0] * frontlerp + r_avertexnormals[oldverts[i].normal][0] * backlerp;
			n[1] = r_avertexnormals[verts[i].normal][1] * frontlerp + r_avertexnormals[oldverts[i].normal][1] * backlerp;
			n[2] = r_avertexnormals[verts[i].normal][2] * frontlerp + r_avertexnormals[oldverts[i].normal][2] * backlerp;

			t[0] = r_avertexnormals[verts[i].tangent][0] * frontlerp + r_avertexnormals[oldverts[i].tangent][0] * backlerp;
			t[1] = r_avertexnormals[verts[i].tangent][1] * frontlerp + r_avertexnormals[oldverts[i].tangent][1] * backlerp;
			t[2] = r_avertexnormals[verts[i].tangent][2] * frontlerp + r_avertexnormals[oldverts[i].tangent][2] * backlerp;

			b[0] = r_avertexnormals[verts[i].binormal][0] * frontlerp + r_avertexnormals[oldverts[i].binormal][0] * backlerp;
			b[1] = r_avertexnormals[verts[i].binormal][1] * frontlerp + r_avertexnormals[oldverts[i].binormal][1] * backlerp;
			b[2] = r_avertexnormals[verts[i].binormal][2] * frontlerp + r_avertexnormals[oldverts[i].binormal][2] * backlerp;
#else
			n[0] = verts[i].normal[0] * frontlerp + oldverts[i].normal[0] * backlerp;
			n[1] = verts[i].normal[1] * frontlerp + oldverts[i].normal[1] * backlerp;
			n[2] = verts[i].normal[2] * frontlerp + oldverts[i].normal[2] * backlerp;

			t[0] = verts[i].tangent[0] * frontlerp + oldverts[i].tangent[0] * backlerp;
			t[1] = verts[i].tangent[1] * frontlerp + oldverts[i].tangent[1] * backlerp;
			t[2] = verts[i].tangent[2] * frontlerp + oldverts[i].tangent[2] * backlerp;

			b[0] = verts[i].binormal[0] * frontlerp + oldverts[i].binormal[0] * backlerp;
			b[1] = verts[i].binormal[1] * frontlerp + oldverts[i].binormal[1] * backlerp;
			b[2] = verts[i].binormal[2] * frontlerp + oldverts[i].binormal[2] * backlerp;
#endif
			VectorSubtract(currentshadowlight->origin, tempVertexArray[i], lightDir);
///			VectorNormalize(lightDir);
			LightP[i][0] = DotProduct(lightDir, t);
			LightP[i][1] = DotProduct(lightDir, b);
			LightP[i][2] = DotProduct(lightDir, n);

			if (need_tsH)
			{
				VectorSubtract(r_origin, tempVertexArray[i], H);
///				VectorNormalize(H);

				tsH[i][0] = DotProduct(H, t);
				tsH[i][1] = DotProduct(H, b);
				tsH[i][2] = DotProduct(H, n);

				VectorAdd(LightP[i], tsH[i], tsH[i]);
			}
		}

		GL_SelectTexture(GL_TEXTURE1);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///		if (gl_config.vbo)
///		{
///			currententity->vbo_lightp[nmesh] = R_VCLoadData(VBO_DYNAMIC, i * sizeof(vec3_t), &LightP, VBO_STORE_ANY, NULL, NULL);
///			glTexCoordPointer(3, GL_FLOAT, 0, 0);
///		}
///		else
		{
			GL_BindVBO(NULL);
			glTexCoordPointer(3, GL_FLOAT, 0, LightP);
		}

		GL_SelectTexture(GL_TEXTURE3);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (!currentshadowlight->filtercube_start)
		{
///			if (gl_config.vbo)
///			{
///				currententity->vbo_tsh[nmesh] = R_VCLoadData(VBO_DYNAMIC, i * sizeof(vec3_t), &tsH, VBO_STORE_ANY, NULL, NULL);
///				glTexCoordPointer(3, GL_FLOAT, 0, 0);
///			}
///			else
			{
				GL_BindVBO(NULL);
				glTexCoordPointer(3, GL_FLOAT, 0, tsH);
			}
		}
		else if (shader==SHADER_ARB6)
		{
			GL_SelectTexture(GL_TEXTURE4);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///			if (gl_config.vbo)
///			{
///				currententity->vbo_tsh[nmesh] = R_VCLoadData(VBO_DYNAMIC, i * sizeof(vec3_t), &tsH, VBO_STORE_ANY, NULL, NULL);
///				glTexCoordPointer(3, GL_FLOAT, 0, 0);
///			}
///			else
			{
				GL_BindVBO(NULL);
				glTexCoordPointer(3, GL_FLOAT, 0, tsH);
			}
		}
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(mesh->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[nmesh]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, tempVertexArray);
	}

	if (!currentshadowlight->nobump && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE3);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (gl_config.vbo)
			glTexCoordPointer(3, GL_FLOAT, 0, 0);
		else
			glTexCoordPointer(3, GL_FLOAT, 0, tempVertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		glVertexPointer(3, GL_FLOAT, 0, 0);
	else
		glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );

///	if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///		glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///	else
		glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

	if (!currentshadowlight->nobump)
	{
		if (shader==SHADER_ARB6 && currentshadowlight->filtercube_start)
		{
			GL_SelectTexture(GL_TEXTURE4);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		}

		GL_SelectTexture(GL_TEXTURE3);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	if (!currentshadowlight->nobump)
	{
		GL_SelectTexture(GL_TEXTURE1);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glDisableClientState(GL_VERTEX_ARRAY);
}


/*
=============
GL_DrawAliasFrameLerpLight

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void GL_DrawAliasFrameLerpLight (dmdl_t *paliashdr, bool onepass)
{
	vec3_t	vertexArray[3*MAX_TRIANGLES];
	vec3_t	tsLightDir[3*MAX_TRIANGLES];
	int		i, j, jj = 0;
	int		index_xyz;
	byte	*norms, *oldnorms;
	byte	*binormals, *oldbinormals;
	byte	*tangents, *oldtangents;
	vec3_t	lightDir;
	dtriangle_t *tris;
	daliasframe_t	*frame, *oldframe;
	dtrivertx_t	*verts, *oldverts;
	WORD		cache[MAX_VERTS];
	Memset(cache, 0xff, 2*MAX_VERTS);
	float	backlerp, frontlerp;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}

	unsigned offs, offs2;
	if (currentmodel->flags & RF_SMOOTHVECS)
		offs = paliashdr->num_xyz;
	else
		offs = paliashdr->num_tris;

	oldframe = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->oldframe * paliashdr->framesize);
	oldverts = oldframe->verts;
	offs2 = offs*currententity->oldframe;
	oldnorms = currentmodel->normals + offs2;
	oldbinormals = currentmodel->binormals + offs2;
	oldtangents = currentmodel->tangents + offs2;

	frame = (daliasframe_t *)((byte *)paliashdr + paliashdr->ofs_frames + currententity->frame * paliashdr->framesize);
	verts = frame->verts;
	offs2 = offs*currententity->frame;
	norms = currentmodel->normals + offs2;
	binormals = currentmodel->binormals + offs2;
	tangents = currentmodel->tangents + offs2;

	tris = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);

	if (onepass)
		if (!gl_config.vbo)
			R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...

	for (i=0; i<paliashdr->num_tris; i++)
	{
		for (j=0; j<3; j++, jj++)
		{
			index_xyz = tris[i].index_xyz[j];
			if(cache[index_xyz] == 0xffff)
			{
				VectorSubtract(currentshadowlight->origin, tempVertexArray[index_xyz], lightDir);

				int index2, oldindex2;
				vec3_t	tempnormal[3];
				if (currentmodel->flags & RF_SMOOTHVECS)
				{
					cache[index_xyz] = jj;
					index2 = verts[index_xyz].lightnormalindex;
					oldindex2 = oldverts[index_xyz].lightnormalindex;

					tempnormal[0][0] = r_avertexnormals[oldtangents[index_xyz]][0]*backlerp + r_avertexnormals[tangents[index_xyz]][0]*frontlerp;
					tempnormal[0][1] = r_avertexnormals[oldtangents[index_xyz]][1]*backlerp + r_avertexnormals[tangents[index_xyz]][1]*frontlerp;
					tempnormal[0][2] = r_avertexnormals[oldtangents[index_xyz]][2]*backlerp + r_avertexnormals[tangents[index_xyz]][2]*frontlerp;

					tempnormal[1][0] = r_avertexnormals[oldbinormals[index_xyz]][0]*backlerp + r_avertexnormals[binormals[index_xyz]][0]*frontlerp;
					tempnormal[1][1] = r_avertexnormals[oldbinormals[index_xyz]][1]*backlerp + r_avertexnormals[binormals[index_xyz]][1]*frontlerp;
					tempnormal[1][2] = r_avertexnormals[oldbinormals[index_xyz]][2]*backlerp + r_avertexnormals[binormals[index_xyz]][2]*frontlerp;

					tempnormal[2][0] = r_avertexnormals[oldindex2][0]*backlerp + r_avertexnormals[index2][0]*frontlerp;
					tempnormal[2][1] = r_avertexnormals[oldindex2][1]*backlerp + r_avertexnormals[index2][1]*frontlerp;
					tempnormal[2][2] = r_avertexnormals[oldindex2][2]*backlerp + r_avertexnormals[index2][2]*frontlerp;
				}
				else
				{
					tempnormal[0][0] = r_avertexnormals[oldtangents[i]][0]*backlerp + r_avertexnormals[tangents[i]][0]*frontlerp;
					tempnormal[0][1] = r_avertexnormals[oldtangents[i]][1]*backlerp + r_avertexnormals[tangents[i]][1]*frontlerp;
					tempnormal[0][2] = r_avertexnormals[oldtangents[i]][2]*backlerp + r_avertexnormals[tangents[i]][2]*frontlerp;

					tempnormal[1][0] = r_avertexnormals[oldbinormals[i]][0]*backlerp + r_avertexnormals[binormals[i]][0]*frontlerp;
					tempnormal[1][1] = r_avertexnormals[oldbinormals[i]][1]*backlerp + r_avertexnormals[binormals[i]][1]*frontlerp;
					tempnormal[1][2] = r_avertexnormals[oldbinormals[i]][2]*backlerp + r_avertexnormals[binormals[i]][2]*frontlerp;

					tempnormal[2][0] = r_avertexnormals[oldnorms[i]][0]*backlerp + r_avertexnormals[norms[i]][0]*frontlerp;
					tempnormal[2][1] = r_avertexnormals[oldnorms[i]][1]*backlerp + r_avertexnormals[norms[i]][1]*frontlerp;
					tempnormal[2][2] = r_avertexnormals[oldnorms[i]][2]*backlerp + r_avertexnormals[norms[i]][2]*frontlerp;
				}

				tsLightDir[jj][0] = DotProduct(lightDir, tempnormal[0]);
				tsLightDir[jj][1] = DotProduct(lightDir, tempnormal[1]);
				tsLightDir[jj][2] = DotProduct(lightDir, tempnormal[2]);
			}
			else
				VectorCopy(tsLightDir[cache[index_xyz]], tsLightDir[jj]);

			if (!gl_config.vbo)
				VectorCopy(tempVertexArray[index_xyz], vertexArray[jj]);
		}
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///	if (gl_config.vbo)
///	{
///		currententity->vbo_lightp[0] = R_VCLoadData(VBO_DYNAMIC, jj * sizeof(vec3_t), &tsLightDir, VBO_STORE_ANY, NULL, NULL);
///		glTexCoordPointer(3, GL_FLOAT, 0, 0);
///	}
///	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, tsLightDir);
	}

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currentmodel->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	else
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[0]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	if(r_shader->value == SHADER_GENERIC6 && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (gl_config.vbo)
			glTexCoordPointer(3, GL_FLOAT, 0, 0);
		else
			glTexCoordPointer(3, GL_FLOAT, 0, vertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		glVertexPointer(3, GL_FLOAT, 0, 0);
	else
		glVertexPointer(3, GL_FLOAT, 0, vertexArray);

	glDrawArrays (GL_TRIANGLES, 0, jj);

	glDisableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		GL_BindVBO(NULL);

	if(r_shader->value == SHADER_GENERIC6 && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


/*
=============
GL_DrawMD3AliasFrameLerpLight

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void GL_DrawMD3AliasFrameLerpLight (maliasmesh_t *mesh, int nmesh)
{
	int		i;
	vec3_t	lightDir;
	vec3_t	LightP[MD3_MAX_VERTS];
	float	backlerp, frontlerp;
	maliasvertex_t	*verts, *oldverts;

///	if ( r_lerpmodels->value )
///	{
		backlerp = currententity->backlerp;
		frontlerp = 1 - backlerp;
///	}
///	else
///	{
///		backlerp = 0;
///		frontlerp = 1;
///	}
	verts = mesh->vertexes + currententity->frame * mesh->num_verts;
	oldverts = mesh->vertexes + currententity->oldframe * mesh->num_verts;

	for(i=0; i<mesh->num_verts; i++)
	{
		vec3_t	t, b, n;
#if 0
		n[0] = r_avertexnormals[verts[i].normal][0] * frontlerp + r_avertexnormals[oldverts[i].normal][0] * backlerp;
		n[1] = r_avertexnormals[verts[i].normal][1] * frontlerp + r_avertexnormals[oldverts[i].normal][1] * backlerp;
		n[2] = r_avertexnormals[verts[i].normal][2] * frontlerp + r_avertexnormals[oldverts[i].normal][2] * backlerp;

		t[0] = r_avertexnormals[verts[i].tangent][0] * frontlerp + r_avertexnormals[oldverts[i].tangent][0] * backlerp;
		t[1] = r_avertexnormals[verts[i].tangent][1] * frontlerp + r_avertexnormals[oldverts[i].tangent][1] * backlerp;
		t[2] = r_avertexnormals[verts[i].tangent][2] * frontlerp + r_avertexnormals[oldverts[i].tangent][2] * backlerp;

		b[0] = r_avertexnormals[verts[i].binormal][0] * frontlerp + r_avertexnormals[oldverts[i].binormal][0] * backlerp;
		b[1] = r_avertexnormals[verts[i].binormal][1] * frontlerp + r_avertexnormals[oldverts[i].binormal][1] * backlerp;
		b[2] = r_avertexnormals[verts[i].binormal][2] * frontlerp + r_avertexnormals[oldverts[i].binormal][2] * backlerp;
#else
		n[0] = verts[i].normal[0] * frontlerp + oldverts[i].normal[0] * backlerp;
		n[1] = verts[i].normal[1] * frontlerp + oldverts[i].normal[1] * backlerp;
		n[2] = verts[i].normal[2] * frontlerp + oldverts[i].normal[2] * backlerp;

		t[0] = verts[i].tangent[0] * frontlerp + oldverts[i].tangent[0] * backlerp;
		t[1] = verts[i].tangent[1] * frontlerp + oldverts[i].tangent[1] * backlerp;
		t[2] = verts[i].tangent[2] * frontlerp + oldverts[i].tangent[2] * backlerp;

		b[0] = verts[i].binormal[0] * frontlerp + oldverts[i].binormal[0] * backlerp;
		b[1] = verts[i].binormal[1] * frontlerp + oldverts[i].binormal[1] * backlerp;
		b[2] = verts[i].binormal[2] * frontlerp + oldverts[i].binormal[2] * backlerp;
#endif
		VectorSubtract(currentshadowlight->origin, tempVertexArray[i], lightDir);

		LightP[i][0] = DotProduct(lightDir, t);
		LightP[i][1] = DotProduct(lightDir, b);
		LightP[i][2] = DotProduct(lightDir, n);
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
///	if (gl_config.vbo)
///	{
///		currententity->vbo_lightp[nmesh] = R_VCLoadData(VBO_DYNAMIC, i * sizeof(vec3_t), &LightP, VBO_STORE_ANY, NULL, NULL);
///		glTexCoordPointer(3, GL_FLOAT, 0, 0);
///	}
///	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, LightP);
	}

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(mesh->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	else
		glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currententity->vbo_xyz[nmesh]);
		glTexCoordPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(3, GL_FLOAT, 0, tempVertexArray);
	}

	if(r_shader->value == SHADER_GENERIC6 && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if (gl_config.vbo)
			glTexCoordPointer(3, GL_FLOAT, 0, 0);
		else
			glTexCoordPointer(3, GL_FLOAT, 0, tempVertexArray);
	}

	glEnableClientState(GL_VERTEX_ARRAY);
	if (gl_config.vbo)
		glVertexPointer(3, GL_FLOAT, 0, 0);
	else
		glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );

///	if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///		glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///	else
		glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

	if(r_shader->value == SHADER_GENERIC6 && currentshadowlight->filtercube_start)
	{
		GL_SelectTexture(GL_TEXTURE4);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	GL_SelectTexture(GL_TEXTURE3);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glDisableClientState(GL_VERTEX_ARRAY);
}


inline void R_DrawAliasObjectLightGENERIC4(dmdl_t *paliashdr, image_t *skin, image_t *bump)
{
	bool onepass;

	if (currentshadowlight->filtercube_start)
	{
		GL_EnableAttenuationShader(false);
		R_DrawAttenuationAlias(paliashdr, true);
		GL_DisableAttenuationShader();
		onepass = false;
	}
	else
		onepass = true;

	GL_EnableShaderGENERIC(false, onepass);
	GL_MBind2D(GL_TEXTURE1, bump->texnum);
	GL_MBind2D(GL_TEXTURE3, skin->texnum);
	GL_DrawAliasFrameLerpLight (paliashdr, onepass);
	GL_DisableShaderGENERIC(onepass);

	if(!onepass)
		R_DrawAttenuationAlias(paliashdr, false);
}


void R_DrawMD3AliasObjectLightGENERIC4(maliasmesh_t *mesh, image_t *skin, image_t *bump, int i)
{
	bool onepass;

	if (currentshadowlight->filtercube_start)
	{
		GL_EnableAttenuationShader(false);
		R_DrawAttenuationMD3Alias(mesh, true, i);
		GL_DisableAttenuationShader();
		onepass = false;
	}
	else
		onepass = true;

	if (skin->rotate)
		GL_RotateTexture(false, anglemod(cl.leveltime * skin->rotate), 11);
	GL_MBind2D(GL_TEXTURE3, skin->texnum);
	GL_MBind2D(GL_TEXTURE1, bump->texnum);
	GL_EnableShaderGENERIC(false, onepass);
	GL_DrawMD3AliasFrameLerpLight (mesh, i);
	GL_DisableShaderGENERIC(onepass);
	if (skin->rotate)
		GL_RotateTexture(true, 0, 11);

	if(!onepass)
		R_DrawAttenuationMD3Alias(mesh, false, i);

	return;
}


void R_DrawMD3AliasObjectLightNVIDIA4(maliasmesh_t *mesh, image_t *skin, image_t *bump, int i)
{
	bool onepass;

	if (currentshadowlight->filtercube_start)
	{
		GL_EnableAttenuationShader(false);
		R_DrawAttenuationMD3Alias(mesh, true, i);
		GL_DisableAttenuationShader();
		onepass = false;
	}
	else
		onepass = true;

	if (skin->rotate)
		GL_RotateTexture(false, anglemod(cl.leveltime * skin->rotate), 11);
	GL_MBind2D(GL_TEXTURE3, skin->texnum);
	GL_MBind2D(GL_TEXTURE1, bump->texnum);
	GL_EnableShaderNVIDIA4(false, onepass);
	GL_DrawMD3AliasFrameLerpLight (mesh, i);
	GL_DisableShaderNVIDIA4(onepass);
	if (skin->rotate)
		GL_RotateTexture(true, 0, 11);

	if(!onepass)
		R_DrawAttenuationMD3Alias(mesh, false, i);

	return;
}


inline void R_DrawAliasObjectLightGENERIC6(dmdl_t *paliashdr, image_t *skin, image_t *bump)
{
	GL_EnableShaderGENERIC(false, true);
	GL_MBind2D(GL_TEXTURE1, bump->texnum);
	GL_MBind2D(GL_TEXTURE3, skin->texnum);
	GL_DrawAliasFrameLerpLight (paliashdr, true);
	GL_DisableShaderGENERIC(true);
}


void R_DrawMD3AliasObjectLightGENERIC6(maliasmesh_t *mesh, image_t *skin, image_t *bump, int i)
{
	if (skin->rotate)
		GL_RotateTexture(false, anglemod(cl.leveltime * skin->rotate), 11);
	GL_MBind2D(GL_TEXTURE3, skin->texnum);
	GL_MBind2D(GL_TEXTURE1, bump->texnum);
	GL_DrawMD3AliasFrameLerpLight (mesh, i);
	if (skin->rotate)
		GL_RotateTexture(true, 0, 11);
	return;
}


inline void R_DrawAliasObjectLightNVIDIA4(dmdl_t *paliashdr, image_t *skin, image_t *bump)
{
	bool onepass;

	if (currentshadowlight->filtercube_start)
	{
		GL_EnableAttenuationShader(false);
		R_DrawAttenuationAlias(paliashdr, true);
		GL_DisableAttenuationShader();
		onepass = false;
	}
	else
		onepass = true;

	GL_EnableShaderNVIDIA4(false, onepass);
	GL_MBind2D(GL_TEXTURE1, bump->texnum);
	GL_MBind2D(GL_TEXTURE3, skin->texnum);
	GL_DrawAliasFrameLerpLight (paliashdr, onepass);
	GL_DisableShaderNVIDIA4(onepass);

	if(!onepass)
		R_DrawAttenuationAlias(paliashdr, false);
}


inline void R_DrawAliasObjectLightATI4(dmdl_t *paliashdr, image_t *skin, image_t *bump)
{
	GL_EnableShaderATI4(false);
	GL_MBind2D(GL_TEXTURE0, bump->texnum);
	GL_MBind2D(GL_TEXTURE2, skin->texnum);
	GL_DrawAliasFrameLerpLightATI4 (paliashdr);
	GL_DisableShaderATI4();
}


inline void R_DrawAliasObjectLightATI6(dmdl_t *paliashdr, image_t *skin, image_t *bump)
{
	GL_EnableShaderATI6(false);
	GL_MBind2D(GL_TEXTURE0, bump->texnum);
	GL_MBind2D(GL_TEXTURE3, skin->texnum);
	GL_DrawAliasFrameLerpLightATI6 (paliashdr);
	GL_DisableShaderATI6();
}


inline void R_DrawAliasObjectLightARB(dmdl_t *paliashdr, image_t *skin, image_t *bump, int shader)
{
	GL_EnableShaderARB(false, shader);
	if (!currentshadowlight->nobump)
	{
		GL_MBind2D(GL_TEXTURE0, bump->texnum);
		if (shader == SHADER_ARB6)
		{
			GL_BindARB(false);
			if (r_detailed_bump->value)
			{
				GL_MBind2D(GL_TEXTURE5, R_GetIMGFromName2(skin, r_defaultbump_detail));
				glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, skin->detail_scale, skin->detail_scale, skin->detail_scale, skin->detail_scale);
			}
		}
	}
	GL_MBind2D(GL_TEXTURE1, skin->texnum);
	if (arb_vp_work)
		GL_DrawAliasFrameLerpLightARB_vp (paliashdr, shader);
	else
		GL_DrawAliasFrameLerpLightARB (paliashdr, shader);
	GL_DisableShaderARB();
}


void R_DrawMD3AliasObjectLightARB(maliasmesh_t *mesh, image_t *skin, image_t *bump, int shader, maliasmodel_t *paliashdr, int nmesh)
{
	unsigned	tmus;

	if (!currentshadowlight->nobump)
	{
		GL_MBind2D(GL_TEXTURE0, bump->texnum);
		if (shader == SHADER_ARB6)
		{
			GL_BindARB(false);
			if (r_detailed_bump->value)
			{
				GL_MBind2D(GL_TEXTURE5, R_GetIMGFromName2(skin, r_defaultbump_detail));
				glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, skin->detail_scale, skin->detail_scale, skin->detail_scale, skin->detail_scale);
			}
		}
	}
	GL_MBind2D(GL_TEXTURE1, skin->texnum);
	if (skin->rotate)
	{
		if (arb_vp_work)
			tmus = 1;
		else
		{
			if (currentshadowlight->filtercube_start)
				tmus = 19;
			else
				tmus = 11;
		}
		GL_RotateTexture(false, anglemod(cl.leveltime * skin->rotate), tmus);
	}
	if (arb_vp_work)
		GL_DrawMD3AliasFrameLerpLightARB_vp (mesh, shader, paliashdr, nmesh);
	else
		GL_DrawMD3AliasFrameLerpLightARB (mesh, shader, nmesh);
	if (skin->rotate)
		GL_RotateTexture(true, 0, tmus);
	return;
}


void R_DrawMD3AliasObjectLightATI4(maliasmesh_t *mesh, image_t *skin, image_t *bump, int nmesh)
{
	if (skin->rotate)
		GL_RotateTexture(false, anglemod(cl.leveltime * skin->rotate), 7);
	GL_MBind2D(GL_TEXTURE2, skin->texnum);
	GL_MBind2D(GL_TEXTURE0, bump->texnum);
	GL_DrawMD3AliasFrameLerpLightATI4 (mesh, nmesh);
	if (skin->rotate)
		GL_RotateTexture(true, 0, 7);
	return;
}


void R_DrawMD3AliasObjectLightATI6(maliasmesh_t *mesh, image_t *skin, image_t *bump, int nmesh)
{
	if (skin->rotate)
		GL_RotateTexture(false, anglemod(cl.leveltime * skin->rotate), 15);
	GL_MBind2D(GL_TEXTURE0, bump->texnum);
	GL_MBind2D(GL_TEXTURE3, skin->texnum);
	GL_DrawMD3AliasFrameLerpLightATI6 (mesh, nmesh);
	if (skin->rotate)
		GL_RotateTexture(true, 0, 15);
	return;
}


void R_DrawAliasObjectLight ()
{
	dmdl_t		*paliashdr;
	image_t		*skin, *bump;
	vec3_t		oldlightorigin, oldvieworg;

///	if ( !( currententity->flags & RF_WEAPONMODEL ) )
///		if ( R_CullAliasModel( &frustum[0], true, true ) )
///			return;

	if ( currententity->flags & RF_WEAPONMODEL )
	{
		if (need_gun_optimisation && !gun_pass)
			return;
		if ( hand->value==2 )
			return;
	}
	else
	{
		if (need_gun_optimisation && gun_pass)
			return;
	}

	bool extra = ((r_shader->value == SHADER_ATI6) || (r_shader->value == SHADER_ARB4) || (r_shader->value == SHADER_ARB6));
	paliashdr = (dmdl_t *)currentmodel->extradata;
	c_light_alias_polys += paliashdr->num_tris;

	// draw all the triangles
	if (currententity->flags & RF_DEPTHHACK) // hack the depth range to prevent view model from poking into walls
		glDepthRange (0, 0.3);

	// select skin
	bool custom_skin = false;
	if (currententity->skin)
	{
		skin = currententity->skin;	// custom player skin
		custom_skin = true;
	}
	else
	{
		if (currententity->skinnum >= MAX_MD2SKINS)
			skin = currentmodel->skins[0];
		else
		{
			skin = currentmodel->skins[currententity->skinnum];
			if (!skin)
				skin = currentmodel->skins[0];
		}
	}

	if (!currentshadowlight->nobump)
	{
		// select bump
		if (custom_skin)
			bump = currententity->bump;	// custom player bump
		else
		{
			if (currententity->skinnum >= MAX_MD2SKINS)
				bump = currentmodel->bumps[0];
			else
			{
				bump = currentmodel->bumps[currententity->skinnum];
				if (!bump)
					bump = currentmodel->bumps[0];
			}
		}
	}
	else
		bump = r_defaultbump;

	if (!skin)
		skin = r_notexture;		// fallback...
	if (!bump)
		bump = r_defaultbump;	// fallback...

	VectorCopy (currentshadowlight->origin, oldlightorigin);
	VectorSubtract (currentshadowlight->origin, currententity->origin, currentshadowlight->origin);
	if(extra)
	{
		VectorCopy (r_origin, oldvieworg);
		VectorSubtract (r_origin, currententity->origin, r_origin);
	}

	if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
	{
		vec3_t	temp, forward, right, up;

		AngleVectors (currententity->angles, forward, right, up);

		VectorCopy (currentshadowlight->origin, temp);
		currentshadowlight->origin[0] = DotProduct (temp, forward);
		currentshadowlight->origin[1] = -DotProduct (temp, right);
		currentshadowlight->origin[2] = DotProduct (temp, up);

		if(extra)
		{
			VectorCopy (r_origin, temp);
			r_origin[0] = DotProduct (temp, forward);
			r_origin[1] = -DotProduct (temp, right);
			r_origin[2] = DotProduct (temp, up);
		}
	}

	glPushMatrix ();
	R_RotateForEntity (currententity);

	switch ((int)r_shader->value)
	{
	case SHADER_GENERIC4:
		R_DrawAliasObjectLightGENERIC4(paliashdr, skin, bump);
		break;
	case SHADER_GENERIC6:
		R_DrawAliasObjectLightGENERIC6(paliashdr, skin, bump);
		break;
	case SHADER_NVIDIA4:
		R_DrawAliasObjectLightNVIDIA4(paliashdr, skin, bump);
		break;
	case SHADER_ATI4:
		R_DrawAliasObjectLightATI4(paliashdr, skin, bump);
		break;
	case SHADER_ATI6:
		R_DrawAliasObjectLightATI6(paliashdr, skin, bump);
		break;
	case SHADER_ARB4:
	case SHADER_ARB6:
		R_DrawAliasObjectLightARB(paliashdr, skin, bump, (int)r_shader->value);
		break;
	}

	VectorCopy(oldlightorigin, currentshadowlight->origin);
	if(extra)
		VectorCopy(oldvieworg, r_origin);

	glPopMatrix ();

	if (currententity->flags & RF_DEPTHHACK)
		glDepthRange (0, 1);

	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	light_used = true;
}


void R_DrawMD3AliasObjectLight ()
{
	maliasmodel_t	*paliashdr;
	image_t			*skin, *bump;
	int				i, j;
	float			frontlerp, backlerp;
	maliasframe_t	*frame, *oldframe;
	vec3_t			move;
	maliasvertex_t	*v, *ov;
	vec3_t			oldlightorigin, oldvieworg;

///	if ( !( currententity->flags & RF_WEAPONMODEL ) )
///		if ( R_CullAliasMD3Model( &frustum[0], true, true ) )
///			return;

	if ( currententity->flags & RF_WEAPONMODEL )
	{
		if (need_gun_optimisation && !gun_pass)
			return;
		if ( hand->value==2 )
			return;
	}
	else
	{
		if (need_gun_optimisation && gun_pass)
			return;
	}

	paliashdr = (maliasmodel_t *)currentmodel->extradata;

	// draw all the triangles
	if (currententity->flags & RF_DEPTHHACK) // hack the depth range to prevent view model from poking into walls
		glDepthRange (0, 0.3);

///	if ( !r_lerpmodels->value )
///		currententity->backlerp = 0;

	backlerp = currententity->backlerp;
	frontlerp = 1.0 - backlerp;
	frame = paliashdr->frames + currententity->frame;
	oldframe = paliashdr->frames + currententity->oldframe;

	bool extra = ((r_shader->value == SHADER_ATI6) || (r_shader->value == SHADER_ARB4) || (r_shader->value == SHADER_ARB6));

	VectorCopy (currentshadowlight->origin, oldlightorigin);
	VectorSubtract (currentshadowlight->origin, currententity->origin, currentshadowlight->origin);
	if(extra)
	{
		VectorCopy (r_origin, oldvieworg);
		VectorSubtract (r_origin, currententity->origin, r_origin);
	}

	VectorSubtract (currententity->oldorigin, currententity->origin, move);
	if (currententity->angles[0] || currententity->angles[1] || currententity->angles[2])
	{
		vec3_t	temp, forward, right, up;

		AngleVectors (currententity->angles, forward, right, up);

		VectorCopy (move, temp);
		move[0] = DotProduct (temp, forward);
		move[1] = -DotProduct (temp, right);
		move[2] = DotProduct (temp, up);

		VectorCopy (currentshadowlight->origin, temp);
		currentshadowlight->origin[0] = DotProduct (temp, forward);
		currentshadowlight->origin[1] = -DotProduct (temp, right);
		currentshadowlight->origin[2] = DotProduct (temp, up);

		if(extra)
		{
			VectorCopy (r_origin, temp);
			r_origin[0] = DotProduct (temp, forward);
			r_origin[1] = -DotProduct (temp, right);
			r_origin[2] = DotProduct (temp, up);
		}
	}

	VectorAdd (move, oldframe->translate, move);

	for (j=0 ; j<3 ; j++)
		move[j] = backlerp*move[j] + frontlerp*frame->translate[j];

	glPushMatrix ();
	R_RotateForEntity (currententity);

	switch ((int)r_shader->value)
	{
	case SHADER_GENERIC6:
		GL_EnableShaderGENERIC(false, true);
		break;
	case SHADER_ATI4:
		GL_EnableShaderATI4(false);
		break;
	case SHADER_ATI6:
		GL_EnableShaderATI6(false);
		break;
	case SHADER_ARB4:
	case SHADER_ARB6:
		GL_EnableShaderARB(false, (int)r_shader->value);
		break;
	}

	for (i=0; i < paliashdr->num_meshes; i++)
	{
		maliasmesh_t *mesh = &paliashdr->meshes[i];
		// select skin
		bool custom_skin = false;
		skin = mesh->img_skins[min(currententity->skinnum, MD3_MAX_SKINS-1)];
		if (!skin || skin == r_notexture)
		{
			if (currententity->skin)
			{
				skin = currententity->skin;	// custom player skin
				custom_skin = true;
			}
		}
		else
			skin = mesh->img_skins[0];
		if (custom_skin)
			bump = currententity->bump;	// custom player bump
		else
		{
			bump = mesh->img_bumps[min(currententity->skinnum, MD3_MAX_SKINS-1)];
			if (!bump)
				bump = mesh->img_bumps[0];
		}
		if(skin && bump)
		{
			if (skin->Translucent33 || skin->Translucent66)
				continue;

			v = mesh->vertexes + currententity->frame*mesh->num_verts;
			ov = mesh->vertexes + currententity->oldframe*mesh->num_verts;
			for ( j = 0; j < mesh->num_verts; j++, v++, ov++ )
				VectorSet ( tempVertexArray[j], 
					move[0] + ov->point[0]*backlerp + v->point[0]*frontlerp,
					move[1] + ov->point[1]*backlerp + v->point[1]*frontlerp,
					move[2] + ov->point[2]*backlerp + v->point[2]*frontlerp);

			c_light_alias_polys += mesh->num_tris;

			switch ((int)r_shader->value)
			{
			case SHADER_GENERIC4:
				R_DrawMD3AliasObjectLightGENERIC4(mesh, skin, bump, i);
				break;
			case SHADER_GENERIC6:
				R_DrawMD3AliasObjectLightGENERIC6(mesh, skin, bump, i);
				break;
			case SHADER_NVIDIA4:
				R_DrawMD3AliasObjectLightNVIDIA4(mesh, skin, bump, i);
				break;
			case SHADER_ARB4:
			case SHADER_ARB6:
				if (skin->fx != fx_distort)
					R_DrawMD3AliasObjectLightARB(mesh, skin, bump, (int)r_shader->value, paliashdr, i);
				break;
			case SHADER_ATI4:
				R_DrawMD3AliasObjectLightATI4(mesh, skin, bump, i);
				break;
			case SHADER_ATI6:
				R_DrawMD3AliasObjectLightATI6(mesh, skin, bump, i);
				break;
			}
		}
	}

	if (gl_config.vbo)
		GL_BindVBO(NULL);

	switch ((int)r_shader->value)
	{
	case SHADER_GENERIC6:
		GL_DisableShaderGENERIC(true);
		break;
	case SHADER_ATI4:
		GL_DisableShaderATI4();
		break;
	case SHADER_ATI6:
		GL_DisableShaderATI6();
		break;
	case SHADER_ARB4:
	case SHADER_ARB6:
		GL_DisableShaderARB();
		break;
	}

	VectorCopy(oldlightorigin, currentshadowlight->origin);
	if(extra)
		VectorCopy(oldvieworg, r_origin);

	glPopMatrix ();

	if (currententity->flags & RF_DEPTHHACK)
		glDepthRange (0, 1);

	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	light_used = true;
}


void R_DrawLightAliases(bool SelfShadow, bool all)
{
	int		i;

	if (!r_drawentities->value)
		return;

	if (r_mirror && !r_mirror_models->value)
		return;

	if (!cl_numlightvisedicts)
		return;

///	if(r_mirror)
///		R_EnableClipMirror(0);

	for (i=0 ; i<cl_numlightvisedicts ; i++)
	{
		currententity = cl_lightvisedicts[i];

		if(currententity->flags & (RF_NOUSE | RF_DISTORT))
			continue;

		if (!r_mirror && (currententity->flags & RF_VIEWERMODEL))
			continue;

		currentmodel = currententity->model;

		if(currentmodel->type == mod_alias)
		{
			if (!all)
			{
				if (SelfShadow && (currentmodel->flags & RF_NOSELFSHADOW))
					continue;
				if (!SelfShadow && !(currentmodel->flags & RF_NOSELFSHADOW))
					continue;
			}

			R_DrawAliasObjectLight();
		}
		else if(currentmodel->type == mod_alias_md3)
		{
			if (!all)
			{
				if (SelfShadow && (currentmodel->flags & RF_NOSELFSHADOW))
					continue;
				if (!SelfShadow && !(currentmodel->flags & RF_NOSELFSHADOW))
					continue;
			}

			R_DrawMD3AliasObjectLight();
		}
	}

///	if(r_mirror)
///		R_DisableClipMirror(0);
}


void R_DrawLightBrushes()
{
	int		i;
	if (!r_drawentities->value)
		return;

///	if (r_mirror)
///		R_EnableClipMirror(0);

	for (i=0 ; i<cl_numlightvisedicts ; i++)
	{
		currententity = cl_lightvisedicts[i];

		if (currententity->flags & RF_NOUSE)
			continue;

		currentmodel = currententity->model;

		if(currentmodel->type == mod_brush)
			R_DrawBrushObjectLight();
	}

///	if(r_mirror)
///		R_DisableClipMirror(0);
}


void R_DrawWorldShadowVolumes()
{
	if (!r_worldshadows->value)
		return;

	glEnableClientState(GL_VERTEX_ARRAY);
	if (r_faststencil->value && (gl_config.ati_separate_stencil || gl_config.nv_stencil_two_side))
	{
		if(gl_config.ati_separate_stencil)
		{
			glStencilOpSeparateATI(GL_BACK, GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);
			glStencilOpSeparateATI(GL_FRONT, GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
		}
		else
		{
			glActiveStencilFaceEXT (GL_BACK);
			glStencilOp (GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);
			glActiveStencilFaceEXT (GL_FRONT);
			glStencilOp (GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
		}
	}
	else
	{
		//Pass 1 increase
		glCullFace(GL_BACK);
		glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
		DrawVolumeFromCmds();

		// Second Pass. Decrease Stencil Value In The Shadow
		glCullFace(GL_FRONT);
		glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
	}

	DrawVolumeFromCmds();
	glDisableClientState(GL_VERTEX_ARRAY);
	shadow_used = true;
}


void MakeFrustum4Light(bool ingame)
{
	vec3_t		v0, v1, v2, v3, v4;
	vec3_t		forward, right, up;
	vec3_t		angles, rspeed;

	if (!currentshadowlight->_cone)
		return;	// Уйдем, если фрустум не надо рассчитывать (не задан параметр _cone)

	// Если фрустум уже был рассчитан...
	if(!VectorCompare(currentshadowlight->frust[0].normal, vec3_origin))
		// и если источник - статичный...
		if(currentshadowlight->isStatic)
			// и если источник никак не вращается в пространстве (вращение в своей плоскости не учитываем)
			if(!currentshadowlight->rspeed[0] && !currentshadowlight->rspeed[1])
				return;		// то фрустум больше не считаем!

	if(ingame)
		VectorCopy(currentshadowlight->rspeed, rspeed);
	else
		VectorClear(rspeed);

	angles[0] = currentshadowlight->angles[0] + rspeed[0] * cl.leveltime;
	angles[1] = currentshadowlight->angles[1] + rspeed[1] * cl.leveltime;
	angles[2] = currentshadowlight->angles[2] + rspeed[2] * cl.leveltime;

	AngleVectors (angles, forward, right, up);

	VectorScale(right, currentshadowlight->_cone, right);
	VectorScale(up, currentshadowlight->_cone, up);
	VectorCopy(currentshadowlight->origin, v0);

	v1[0] = v0[0] + (forward[0] - right[0] - up[0]);
	v1[1] = v0[1] + (forward[1] - right[1] - up[1]);
	v1[2] = v0[2] + (forward[2] - right[2] - up[2]);

	v2[0] = v0[0] + (forward[0] - right[0] + up[0]);
	v2[1] = v0[1] + (forward[1] - right[1] + up[1]);
	v2[2] = v0[2] + (forward[2] - right[2] + up[2]);

	v3[0] = v0[0] + (forward[0] + right[0] + up[0]);
	v3[1] = v0[1] + (forward[1] + right[1] + up[1]);
	v3[2] = v0[2] + (forward[2] + right[2] + up[2]);

	v4[0] = v0[0] + (forward[0] + right[0] - up[0]);
	v4[1] = v0[1] + (forward[1] + right[1] - up[1]);
	v4[2] = v0[2] + (forward[2] + right[2] - up[2]);

	CreateNormal(currentshadowlight->frust[0].normal, v1, v2, v0);
	currentshadowlight->frust[0].dist = DotProduct(currentshadowlight->frust[0].normal, v0);

	CreateNormal(currentshadowlight->frust[1].normal, v2, v3, v0);
	currentshadowlight->frust[1].dist = DotProduct(currentshadowlight->frust[1].normal, v0);

	CreateNormal(currentshadowlight->frust[2].normal, v3, v4, v0);
	currentshadowlight->frust[2].dist = DotProduct(currentshadowlight->frust[2].normal, v0);

	CreateNormal(currentshadowlight->frust[3].normal, v4, v1, v0);
	currentshadowlight->frust[3].dist = DotProduct(currentshadowlight->frust[3].normal, v0);
/*
	if(ingame)
	{
		GL_SelectTexture( GL_TEXTURE0 );
		glDisable (GL_TEXTURE_2D);
		glDisable (GL_DEPTH_TEST);
		GL_Color3f(1,1,1);

		glBegin (GL_LINE_STRIP);
			glVertex3fv (v0);	glVertex3fv (v1);
			glVertex3fv (v0);	glVertex3fv (v2);
			glVertex3fv (v0);	glVertex3fv (v3);
			glVertex3fv (v0);	glVertex3fv (v4);

			glVertex3fv (v1);	glVertex3fv (v2);
			glVertex3fv (v2);	glVertex3fv (v3);
			glVertex3fv (v3);	glVertex3fv (v4);
			glVertex3fv (v4);	glVertex3fv (v1);
		glEnd ();

		glEnable (GL_DEPTH_TEST);
		glEnable (GL_TEXTURE_2D);
	}
*/
}


void COM_StripExtension (char *in, char *out)
{
	while (*in && *in != '.')
		*out++ = *in++;
	*out = 0;
}


bool FS_GetWalSizes(char *walname, unsigned *wal_s, unsigned *wal_t)
{
	unsigned	*buffer;
	int			length;
	char		name[MAX_OSPATH+6];

	Com_sprintf (name, sizeof(name), "cache/%s.st", walname);
	length = FS_LoadFile (name, (void **)&buffer);
	if (!buffer || (length <= 0))
	{
		if(buffer)
			Z_Free (buffer);

		Com_sprintf (name, sizeof(name), "%s.wal", walname);
		length = FS_LoadFile (name, (void **)&buffer);
		if (!buffer || (length <= 0))
		{
			if(buffer)
				Z_Free (buffer);
			return false;
		}

		*wal_s = buffer[8];
		*wal_t = buffer[9];

		Com_sprintf (name, sizeof(name), "%s/cache/%s.st", FS_Gamedir(), walname);
		FS_CreatePath(name);
		FILE	*f;
		f = FS_Fopen(name, "wb");
		if (f)
		{
			fwrite (&buffer[8], 1, 8, f);
			fclose (f);
		}

		Z_Free (buffer);
		return true;
	}
	else
	{
		*wal_s = buffer[0];
		*wal_t = buffer[1];

		Z_Free (buffer);
		return true;
	}
}


bool FS_GetPCXSizes(char *picname, unsigned *pic_s, unsigned *pic_t)
{
	unsigned	*buffer;
	int			length;
	char		name[MAX_OSPATH+6];
	char		strip[MAX_OSPATH+6];
	pcx_t		*pcx;
	unsigned	s, t;

	COM_StripExtension (picname, strip);
	Com_sprintf (name, sizeof(name), "cache/%s.st", strip);
	length = FS_LoadFile (name, (void **)&buffer);
	if (!buffer || (length <= 0))
	{
		if(buffer)
			Z_Free (buffer);

		Com_sprintf (name, sizeof(name), "%s.pcx", strip);
		length = FS_LoadFile (name, (void **)&buffer);
		if (!buffer || (length <= 0))
		{
			if(buffer)
				Z_Free (buffer);
			return false;
		}

		pcx = (pcx_t *)buffer;

		s = *pic_s = LittleShort(pcx->xmax) + 1;
		t = *pic_t = LittleShort(pcx->ymax) + 1;

		Com_sprintf (name, sizeof(name), "%s/cache/%s.st", FS_Gamedir(), strip);
		FS_CreatePath(name);
		FILE	*f;
		f = FS_Fopen(name, "wb");
		if (f)
		{
			fwrite (&s, 1, 4, f);
			fwrite (&t, 1, 4, f);
			fclose (f);
		}

		Z_Free (buffer);
		return true;
	}
	else
	{
		*pic_s = buffer[0];
		*pic_t = buffer[1];

		Z_Free (buffer);
		return true;
	}
}


void R_GrabScreenToTexture()
{
	GL_Bind2D(r_screenTexture->texnum);
	glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cl.refdef.x, viddef.height - cl.refdef.height - cl.refdef.y, cl.refdef.width, cl.refdef.height);
}


void Distort_Initialise()
{
	float	sms, smt;

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_POLYGON_OFFSET_FILL);
	GL_Color3f(1,1,1);

	GL_SelectTexture(GL_TEXTURE1);
	R_GrabScreenToTexture();

	glPushMatrix();
	glLoadIdentity();
	glTexGenfv(GL_S, GL_EYE_PLANE, s___);
	glTexGenfv(GL_T, GL_EYE_PLANE, t___);
	glTexGenfv(GL_R, GL_EYE_PLANE, r___);
	glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
	glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
	glTexGenf(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
	glEnable(GL_TEXTURE_GEN_S);
	glEnable(GL_TEXTURE_GEN_T);
	glEnable(GL_TEXTURE_GEN_R);
	glPopMatrix();

	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	sms = 0.5/gl_config.screenTextureSize[0]*cl.refdef.width;
	smt = 0.5/gl_config.screenTextureSize[1]*cl.refdef.height;
	glTranslatef(sms, smt, 0);
	glScalef(sms, smt, 0);
	glMultMatrixf(r_project_matrix);
	glMatrixMode(GL_MODELVIEW);

	GL_BindProgramFP(arbdistort_program);
	glEnable(GL_FRAGMENT_PROGRAM_ARB);

	GL_SelectTexture(GL_TEXTURE0);

	distort_initialised = true;
}


void Distort_Shutdown()
{
	GL_SelectTexture(GL_TEXTURE1);

	glDisable(GL_FRAGMENT_PROGRAM_ARB);

	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);

	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);

	GL_SelectTexture(GL_TEXTURE0);

	glDisable(GL_POLYGON_OFFSET_FILL);
}


void R_DrawDistortAliasModelMD3()
{
	image_t		*skin, *bump;
	float	adds, addt, scs, sct;
	float	tempSTArray[MD3_MAX_VERTS][2];
	maliasmodel_t	*paliashdr;
	float			frontlerp, backlerp;
	maliasframe_t	*frame, *oldframe;
	vec3_t			move, delta, vectors[3];
	maliasvertex_t	*v, *ov;
	int				i, j;
	maliasmesh_t	*mesh;

	if (currententity->flags & (RF_VIEWERMODEL | RF_TRANSLUCENT | RF_BEAM | RF_NOUSE))
		return;

	if ( currententity->flags & RF_WEAPONMODEL )
		if ( hand->value==2 )
			return;

	if ((currentmodel->flags & RF_DISTORT_SKIN) || (currententity->flags & RF_DISTORT))
	{
		if (!distort_initialised)
			Distort_Initialise();
	}
	else
		return;

#ifndef DISTORT_WEAPON
	if ( currententity->flags & RF_WEAPONMODEL )
		Com_Error(ERR_FATAL, "WeaponModel with 'distort' flag");
#endif

	if (currententity->flags & RF_DEPTHHACK) // hack the depth range to prevent view model from poking into walls
		glDepthRange (0, 0.3);

	glPushMatrix ();
	R_RotateForEntity (currententity);

	paliashdr = (maliasmodel_t *)currentmodel->extradata;

///	if ( !r_lerpmodels->value )
///		currententity->backlerp = 0;

	backlerp = currententity->backlerp;
	frontlerp = 1.0 - backlerp;
	frame = paliashdr->frames + currententity->frame;
	oldframe = paliashdr->frames + currententity->oldframe;

	VectorSubtract (currententity->oldorigin, currententity->origin, delta);
	AngleVectors (currententity->angles, vectors[0], vectors[1], vectors[2]);
	move[0] = DotProduct (delta, vectors[0]);	// forward
	move[1] = -DotProduct (delta, vectors[1]);	// left
	move[2] = DotProduct (delta, vectors[2]);	// up

	VectorAdd (move, oldframe->translate, move);

	for (j=0 ; j<3 ; j++)
		move[j] = backlerp*move[j] + frontlerp*frame->translate[j];

	GL_Color3f(r_predator_intensity, r_predator_intensity, r_predator_intensity);

	for (i=0; i < paliashdr->num_meshes; i++)
	{
		mesh = &paliashdr->meshes[i];
		// select skin
		bool custom_skin = false;
		skin = mesh->img_skins[min(currententity->skinnum, MD3_MAX_SKINS-1)];
		if (!skin || skin == r_notexture)
		{
			if (currententity->skin)
			{
				skin = currententity->skin;	// custom player skin
				custom_skin = true;
			}
		}
		else
			skin = mesh->img_skins[0];
		if(skin)
		{
			c_alias_polys += paliashdr->meshes[i].num_tris;

			if (currententity->flags & RF_DISTORT)
			{
				// select bump
				if (custom_skin)
					bump = currententity->bump;	// custom player skin
				else
				{
					bump = mesh->img_bumps[min(currententity->skinnum, MD3_MAX_SKINS-1)];
					if (!bump || bump == r_defaultbump)
						bump = mesh->img_bumps[0];
				}
				if (!bump)
					bump = dst_texture;

				GL_MBind2D(GL_TEXTURE0, bump->texnum);
			}
			else
			{
				if (skin->fx != fx_distort)
					continue;

				adds = skin->fx_s * cl.leveltime;
				addt = skin->fx_t * cl.leveltime;
				scs = skin->fx_scale_s;
				sct = skin->fx_scale_t;
				R_GetIMGFromName(skin, dst_texture);
				GL_MBind2D(GL_TEXTURE0, skin->fx_image->texnum);
			}
			glEnableClientState( GL_TEXTURE_COORD_ARRAY );
			if (gl_config.vbo)
			{
				GL_BindVBO(mesh->vbo_st);
				glTexCoordPointer(2, GL_FLOAT, 0, 0);
			}
			else
			{
				GL_BindVBO(NULL);
				glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
			}

			GL_MBind2D(GL_TEXTURE2, skin->texnum);
			glEnableClientState( GL_TEXTURE_COORD_ARRAY );
			if (currententity->flags & RF_DISTORT)
			{
				if (gl_config.vbo)
					glTexCoordPointer(2, GL_FLOAT, 0, 0);
				else
					glTexCoordPointer(2, GL_FLOAT, 0, mesh->stcoords);
			}
			else
			{
				for ( j = 0; j < mesh->num_verts; j++ )
				{
					tempSTArray[j][0] = (mesh->stcoords[j].st[0] + adds) * scs;
					tempSTArray[j][1] = (mesh->stcoords[j].st[1] + addt) * sct;
				}
///				if (gl_config.vbo)
///				{
///					currententity->vbo_fx[i] = R_VCLoadData(VBO_DYNAMIC, j * 2 * sizeof(float), &tempSTArray, VBO_STORE_ANY, NULL, NULL);
///					glTexCoordPointer(2, GL_FLOAT, 0, 0);
///				}
///				else
				{
					GL_BindVBO(NULL);
					glTexCoordPointer( 2, GL_FLOAT, 0, tempSTArray );
				}
			}

			v = mesh->vertexes + currententity->frame*mesh->num_verts;
			ov = mesh->vertexes + currententity->oldframe*mesh->num_verts;

			glEnableClientState( GL_VERTEX_ARRAY );
			if (gl_config.vbo)
			{
				currententity->vbo_xyz[i] = R_VCFindCache(VBO_STORE_XYZ, currententity, i);
				if (currententity->vbo_xyz[i])
					goto skipLoad;
			}

			for ( j = 0; j < mesh->num_verts; j++, v++, ov++ )
				VectorSet ( tempVertexArray[j], 
					move[0] + ov->point[0]*backlerp + v->point[0]*frontlerp,
					move[1] + ov->point[1]*backlerp + v->point[1]*frontlerp,
					move[2] + ov->point[2]*backlerp + v->point[2]*frontlerp);

			if (gl_config.vbo)
			{
				currententity->vbo_xyz[i] = R_VCLoadData(VBO_DYNAMIC, mesh->num_verts * sizeof(vec3_t), &tempVertexArray, VBO_STORE_XYZ, currententity, i);
				goto next;
skipLoad:		GL_BindVBO(currententity->vbo_xyz[i]);
next:			glVertexPointer(3, GL_FLOAT, 0, 0);
			}
			else
			{
				GL_BindVBO(NULL);
				glVertexPointer( 3, GL_FLOAT, 0, tempVertexArray );
			}

///			if(r_ext_draw_range_elements->value && glDrawRangeElementsEXT)
///				glDrawRangeElementsEXT(GL_TRIANGLES, 0, mesh->num_verts, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);
///			else
				glDrawElements(GL_TRIANGLES, mesh->num_tris*3, GL_UNSIGNED_SHORT, mesh->indexes);

			glDisableClientState( GL_TEXTURE_COORD_ARRAY );
			GL_SelectTexture(GL_TEXTURE0);
			glDisableClientState( GL_TEXTURE_COORD_ARRAY );
			glDisableClientState( GL_VERTEX_ARRAY );
		}
	}

	if (gl_config.vbo)
		GL_BindVBO(NULL);

	GL_Color3f(1,1,1);
	glPopMatrix ();

	if (currententity->flags & RF_DEPTHHACK)
		glDepthRange (0, 1);
}


void R_DrawDistortSprite()
{
	dsprite_t	*psprite;
	dsprframe_t	*frame;
	float		*up, *right;
	vec3_t		point;

	psprite = (dsprite_t *)currentmodel->extradata;
	currententity->frame %= psprite->numframes;
	image_t *tex = currentmodel->skins[currententity->frame];
	if(tex->fx != fx_distort)
		return;

	if (!distort_initialised)
		Distort_Initialise();

	frame = &psprite->frames[currententity->frame];
	up = vup;
	right = vright;

	R_GetIMGFromName(tex, dst_texture);
	GL_MBind2D(GL_TEXTURE0, tex->fx_image->texnum);
	GL_MBind2D(GL_TEXTURE2, tex->texnum);
	float	adds = tex->fx_s * cl.leveltime;
	float	addt = tex->fx_t * cl.leveltime;

	glBegin (GL_QUADS);

	glTexCoord2f (adds*tex->fx_scale_s, (1+addt)*tex->fx_scale_t);
	glMultiTexCoord2fARB (GL_TEXTURE2, 0, 1);
	VectorMA (currententity->origin, -frame->origin_y, up, point);
	VectorMA (point, -frame->origin_x, right, point);
	glVertex3fv (point);

	glTexCoord2f (adds*tex->fx_scale_s, addt*tex->fx_scale_t);
	glMultiTexCoord2fARB (GL_TEXTURE2, 0, 0);
	VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
	VectorMA (point, -frame->origin_x, right, point);
	glVertex3fv (point);

	glTexCoord2f ((1+adds)*tex->fx_scale_s, addt*tex->fx_scale_t);
	glMultiTexCoord2fARB (GL_TEXTURE2, 1, 0);
	VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
	VectorMA (point, frame->width - frame->origin_x, right, point);
	glVertex3fv (point);

	glTexCoord2f ((1+adds)*tex->fx_scale_s, (1+addt)*tex->fx_scale_t);
	glMultiTexCoord2fARB (GL_TEXTURE2, 1, 1);
	VectorMA (currententity->origin, -frame->origin_y, up, point);
	VectorMA (point, frame->width - frame->origin_x, right, point);
	glVertex3fv (point);

	glEnd ();
}


void R_DrawDistortSprite2()
{
	dsprite2_t		*psprite;
	dspr2frame_t	*frame;
	float			*up, *right;
	vec3_t			point;
	int				nframe;
	vec3_t			length, width;

	if (currententity->flags & RF_NOUSE)
		return;

	psprite = (dsprite2_t *)currentmodel->extradata;

	if(currententity->flags & RF_ANIMFRAME)		// Спрайты игнорируют фреймы с сервера, используем свои тайминги
	{
		if (psprite->timescale == 1)	// default
			nframe = cl.leveltime*20;
		else
			nframe = cl.leveltime*psprite->timescale/FRAMETIME;
	}
	else if(currententity->flags & RF_ANIM_ONE_CYCLE)	// Рисовать только первый цикл!
	{
		nframe = ((float)(currententity->oldframe) + cl.lerpfrac * ((float)(currententity->frame) - (float)(currententity->oldframe))) * psprite->timescale;
		if (nframe<0)
			nframe = -currententity->frame;
///		if (nframe >= psprite->numframes)		/// уже отсечено в R_DrawSprite2Model
///		{
///			currententity->flags |= RF_NOUSE;
///			return;
///		}
	}
	else
		nframe = currententity->frame;

	nframe %= psprite->numframes;
	frame = &psprite->frames[nframe];

	image_t *tex = frame->skin;
	if(tex->fx != fx_distort)
		return;

	if (!distort_initialised)
		Distort_Initialise();

	up = vup;
	right = vright;

	R_GetIMGFromName(tex, dst_texture);
	GL_MBind2D(GL_TEXTURE0, tex->fx_image->texnum);
	GL_MBind2D(GL_TEXTURE2, tex->texnum);
	float	adds = tex->fx_s * cl.leveltime;
	float	addt = tex->fx_t * cl.leveltime;

	glBegin (GL_QUADS);

	if (psprite->directed)
	{
		if (psprite->directedScale > 0.0)
		{
			AngleVectors (currententity->angles, length, NULL, NULL);
			VectorScale(length, frame->width * psprite->directedScale, length);
		}
		else
			VectorSubtract(currententity->oldorigin, currententity->origin, length);

		VectorSubtract(currententity->origin, r_origin, point);
		CrossProduct(point, length, width);
		if (!VectorNormalize(width))
			VectorCopy(vup, width);
		VectorScale(width, frame->width, width);

		glTexCoord2f (adds*tex->fx_scale_s, addt*tex->fx_scale_t);
		glMultiTexCoord2fARB (GL_TEXTURE2, 0, 0 );
		glVertex3f( currententity->origin[0] + width[0], currententity->origin[1] + width[1], currententity->origin[2] + width[2]);

		glTexCoord2f (adds*tex->fx_scale_s, (1+addt)*tex->fx_scale_t);
		glMultiTexCoord2fARB (GL_TEXTURE2, 0, 1 );
		glVertex3f( currententity->origin[0] - width[0], currententity->origin[1] - width[1], currententity->origin[2] - width[2]);

		VectorSubtract(currententity->origin, r_origin, point);
		VectorAdd(point, length, point);
		CrossProduct(point, length, width);
		if (!VectorNormalize(width))
			VectorCopy(vup, width);
		VectorScale(width, frame->width, width);

		glTexCoord2f ((1+adds)*tex->fx_scale_s, (1+addt)*tex->fx_scale_t);
		glMultiTexCoord2fARB (GL_TEXTURE2, 1, 1 );
		glVertex3f( currententity->origin[0] + length[0] - width[0], currententity->origin[1] + length[1] - width[1], currententity->origin[2] + length[2] - width[2]);

		glTexCoord2f ((1+adds)*tex->fx_scale_s, addt*tex->fx_scale_t);
		glMultiTexCoord2fARB (GL_TEXTURE2, 1, 0 );
		glVertex3f( currententity->origin[0] + length[0] + width[0], currententity->origin[1] + length[1] + width[1], currententity->origin[2] + length[2] + width[2]);
	}
	else
	{
		glTexCoord2f (adds*tex->fx_scale_s, (1+addt)*tex->fx_scale_t);
		glMultiTexCoord2fARB (GL_TEXTURE2, 0, 1);
		VectorMA (currententity->origin, -frame->origin_y, up, point);
		VectorMA (point, -frame->origin_x, right, point);
		glVertex3fv (point);

		glTexCoord2f (adds*tex->fx_scale_s, addt*tex->fx_scale_t);
		glMultiTexCoord2fARB (GL_TEXTURE2, 0, 0);
		VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
		VectorMA (point, -frame->origin_x, right, point);
		glVertex3fv (point);

		glTexCoord2f ((1+adds)*tex->fx_scale_s, addt*tex->fx_scale_t);
		glMultiTexCoord2fARB (GL_TEXTURE2, 1, 0);
		VectorMA (currententity->origin, frame->height - frame->origin_y, up, point);
		VectorMA (point, frame->width - frame->origin_x, right, point);
		glVertex3fv (point);

		glTexCoord2f ((1+adds)*tex->fx_scale_s, (1+addt)*tex->fx_scale_t);
		glMultiTexCoord2fARB (GL_TEXTURE2, 1, 1);
		VectorMA (currententity->origin, -frame->origin_y, up, point);
		VectorMA (point, frame->width - frame->origin_x, right, point);
		glVertex3fv (point);
	}

	glEnd ();
}


/*
=============
GL_DrawDistortAliasFrameLerp

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
void GL_DrawDistortAliasFrameLerp (dmdl_t *paliashdr, float adds, float addt, float scs, float sct)
{
	float	stArrayM[3*MAX_TRIANGLES][2];
	vec3_t	vertexArray[3*MAX_TRIANGLES];
	int			index_xyz;
	int			i, j, jj, jjj;
	dtriangle_t	*tris;
	bool		skipL = false;
	bool		simpl = false;

	c_alias_polys += paliashdr->num_tris;

	tris = (dtriangle_t *) ((byte *)paliashdr + paliashdr->ofs_tris);

	if (gl_config.vbo)
	{
		simpl = (adds==0 && addt==0 && scs==1 && sct==1);
		currententity->vbo_xyz[0] = R_VCFindCache(VBO_STORE_XYZ, currententity, 0);
		if (currententity->vbo_xyz[0])
		{
			skipL = true;
			goto skipLoad;
		}
	}

	R_CalcAliasFrameLerp(paliashdr);			/// Просто сюда переместили вычисления Lerp...

skipLoad:
	for (jj=0, jjj=0, i=0; i<paliashdr->num_tris; i++)
	{
		for (j=0; j<3; j++, jj++)
		{
			index_xyz = tris[i].index_xyz[j];
			if (!skipL)
				VectorCopy(tempVertexArray[index_xyz], vertexArray[jj]);
			if (!gl_config.vbo || (gl_config.vbo && !simpl))
			{
				stArrayM[jj][0] = (currentmodel->st[jjj++] + adds) * scs;
				stArrayM[jj][1] = (currentmodel->st[jjj++] + addt) * sct;
			}
		}
	}

	GL_SelectTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
	{
		GL_BindVBO(currentmodel->vbo_st);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);
	}

	GL_SelectTexture(GL_TEXTURE2);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	else
		glTexCoordPointer(2, GL_FLOAT, 0, currentmodel->st);

	GL_SelectTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo		&& simpl)
	{
///		if (simpl)
			GL_BindVBO(currentmodel->vbo_st);
///		else
///			currententity->vbo_lightp[0] = R_VCCreate(VBO_DYNAMIC, jj * 2 * sizeof(float), &stArrayM, VBO_STORE_ANY, NULL, NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glTexCoordPointer(2, GL_FLOAT, 0, stArrayM);
	}

	glEnableClientState( GL_VERTEX_ARRAY );
	if (gl_config.vbo)
	{
		if (!skipL)
			currententity->vbo_xyz[0] = R_VCLoadData(VBO_DYNAMIC, jj*sizeof(vec3_t), &vertexArray, VBO_STORE_XYZ, currententity, 0);
		else
			GL_BindVBO(currententity->vbo_xyz[0]);
		glVertexPointer(3, GL_FLOAT, 0, 0);
	}
	else
	{
		GL_BindVBO(NULL);
		glVertexPointer( 3, GL_FLOAT, 0, vertexArray );
	}

	glDrawArrays (GL_TRIANGLES, 0, jj);

	glDisableClientState( GL_VERTEX_ARRAY );
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	GL_SelectTexture(GL_TEXTURE2);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	GL_SelectTexture(GL_TEXTURE0);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	if (gl_config.vbo)
		GL_BindVBO(NULL);
}


void R_DrawDistortAliasModel()
{
	image_t		*skin, *bump;
	float	adds, addt, scs, sct;

	if (currententity->flags & (RF_VIEWERMODEL | RF_TRANSLUCENT | RF_BEAM | RF_NOUSE))
		return;

	if ( currententity->flags & RF_WEAPONMODEL )
		if ( hand->value==2 )
			return;

	// select skin
	bool custom_skin = false;
	if (currententity->skin)
	{
		skin = currententity->skin;	// custom player skin
		custom_skin = true;
	}
	else
	{
		if (currententity->skinnum >= MAX_MD2SKINS)
			skin = currentmodel->skins[0];
		else
		{
			skin = currentmodel->skins[currententity->skinnum];
			if (!skin)
				skin = currentmodel->skins[0];
		}
	}

	if (!skin)
		return;

	if (currententity->flags & RF_DISTORT)
	{
		if (!distort_initialised)
			Distort_Initialise();

		// select bump
		if (custom_skin)
			bump = currententity->bump;	// custom player bump
		else
		{
			if (currententity->skinnum >= MAX_MD2SKINS)
				bump = currentmodel->bumps[0];
			else
			{
				bump = currentmodel->bumps[currententity->skinnum];
				if (!bump)
					bump = currentmodel->bumps[0];
			}
		}
		if (!bump)
			bump = dst_texture;

		adds = addt = 0;
		scs = sct = 1;
		GL_MBind2D(GL_TEXTURE0, bump->texnum);
	}
	else
	{
		if (skin->fx != fx_distort)
			return;

		if (!distort_initialised)
			Distort_Initialise();

		adds = skin->fx_s * cl.leveltime;
		addt = skin->fx_t * cl.leveltime;
		scs = skin->fx_scale_s;
		sct = skin->fx_scale_t;
		R_GetIMGFromName(skin, dst_texture);
		GL_MBind2D(GL_TEXTURE0, skin->fx_image->texnum);
	}

#ifndef DISTORT_WEAPON
	if ( currententity->flags & RF_WEAPONMODEL )
		Com_Error(ERR_FATAL, "WeaponModel with 'distort' flag");
#endif

	GL_MBind2D(GL_TEXTURE2, skin->texnum);

	if (currententity->flags & RF_DEPTHHACK) // hack the depth range to prevent view model from poking into walls
		glDepthRange (0, 0.3);

	glPushMatrix ();
	R_RotateForEntity (currententity);

	GL_Color3f(r_predator_intensity, r_predator_intensity, r_predator_intensity);
	GL_DrawDistortAliasFrameLerp((dmdl_t *)currentmodel->extradata, adds, addt, scs, sct);
	GL_Color3f(1,1,1);

	glPopMatrix ();

	if (currententity->flags & RF_DEPTHHACK)
		glDepthRange (0, 1);
}


void InitModLightsEmits(model_t *mod)
{
	int		i;
	for (i=0; i<MAX_MODEL_LIGHTS; i++)
	{	/// Set defaults
		mod->mod_lights[i].mesh = -1;
		mod->mod_lights[i].tri = 0;
		mod->mod_lights[i]._cone = 0;
		mod->mod_lights[i].distance = 0;
		VectorClear(mod->mod_lights[i].angles);
		VectorClear(mod->mod_lights[i].rspeed);
		VectorSet(mod->mod_lights[i].color,1,1,1);
		mod->mod_lights[i].style = mod->mod_lights[i].cl_style = 0;
		mod->mod_lights[i].filtercube_end = mod->mod_lights[i].filtercube_start = 0;
		mod->mod_lights[i].framerate = 0;
		mod->mod_lights[i].radius = 64;
		mod->mod_lights[i].skinbits = 0;
		mod->mod_lights[i].frame_start = 0;
		mod->mod_lights[i].frame_end = 0x7FFFFFFF;
	}
	for (i=0; i<MAX_MODEL_EMITS; i++)
	{	/// Set defaults
		mod->mod_emits[i].mesh = -1;
		mod->mod_emits[i].tri = 0;
		mod->mod_emits[i].cone = 0;
		mod->mod_emits[i].distance = 0;
		mod->mod_emits[i].skinbits = 0;
		VectorClear(mod->mod_emits[i].startcolor);
		VectorClear(mod->mod_emits[i].endcolor);
		mod->mod_emits[i].style = mod->mod_lights[i].cl_style = 0;
		mod->mod_emits[i].vel = 8;
		mod->mod_emits[i].gravity = 0;
		mod->mod_emits[i].emit = part_simple;
		mod->mod_emits[i].alphavel = 1;
		mod->mod_emits[i].numemits = 1;
		mod->mod_emits[i].frame_start = 0;
		mod->mod_emits[i].frame_end = 0x7FFFFFFF;
	}
}


inline void SetFX (bool r, bool g, bool b, model_t *mod)
{
	if(r_itemfx->value == 1)
		mod->flags |= RF_CHROME;
	else if(r_itemfx->value == 2)
	{
		if(r)	mod->flags |= RF_POWER_RED;
		if(g)	mod->flags |= RF_POWER_GREEN;
		if(b)	mod->flags |= RF_POWER_BLUE;
	}
	else if(r_itemfx->value == 3)
	{
		if(r)	mod->flags |= RF_SHELL_RED;
		if(g)	mod->flags |= RF_SHELL_GREEN;
		if(b)	mod->flags |= RF_SHELL_BLUE;
	}
}


void	ParseMFX(model_t *mod, char *s)
{
	int		t;
	int		cur_mod_light = -1;
	int		cur_mod_emit = -1;
	char	*token, *os;

	mod->trail_index = CLMOD_EF_NONE;
	mod->hit_sound_index = CLMOD_SFX_NONE;

	while (s)
	{
		token = COM_Parse (&s);
		if (!Q_strcasecmp(token, "trail_blood"))
		{
			mod->trail_index = CLMOD_EF_GIB;
			continue;
		}
		if (!Q_strcasecmp(token, "trail_greenblood"))
		{
			mod->trail_index = CLMOD_EF_GREENGIB;
			continue;
		}
		if (!Q_strcasecmp(token, "trail_smoke"))
		{
			mod->trail_index = CLMOD_EF_SMOKE;
			continue;
		}
		if (!Q_strcasecmp(token, "sfx_mgun_shell"))
		{
			mod->hit_sound_index = CLMOD_SFX_MSHELL;
			continue;
		}
		if (!Q_strcasecmp(token, "sfx_sgun_shell"))
		{
			mod->hit_sound_index = CLMOD_SFX_SSHELL;
			continue;
		}
		if (!Q_strcasecmp(token, "sfx_glass"))
		{
			mod->hit_sound_index = CLMOD_SFX_GLASS;
			continue;
		}
		if (!Q_strcasecmp(token, "sfx_flesh"))
		{
			mod->hit_sound_index = CLMOD_SFX_FLESH;
			continue;
		}
		if (!Q_strcasecmp(token, "sfx_metal"))
		{
			mod->hit_sound_index = CLMOD_SFX_METAL;
			continue;
		}
		if (!Q_strcasecmp(token, "sfx_gear"))
		{
			mod->hit_sound_index = CLMOD_SFX_GEAR;
			continue;
		}
		if (!Q_strcasecmp(token, "nocastshadow"))
		{
			mod->flags |= RF_NOCASTSHADOW;
			continue;
		}
		if (!Q_strcasecmp(token, "fullbright"))
		{
			mod->flags |= RF_FULLBRIGHT;
			continue;
		}
		if (!Q_strcasecmp(token, "force_trans"))
		{
			mod->flags |= RF_TRANSLUCENT;
			continue;
		}
		if (!Q_strcasecmp(token, "shell_blue"))
		{
			mod->flags |= RF_SHELL_BLUE;
			continue;
		}
		if (!Q_strcasecmp(token, "shell_green"))
		{
			mod->flags |= RF_SHELL_GREEN;
			continue;
		}
		if (!Q_strcasecmp(token, "shell_red"))
		{
			mod->flags |= RF_SHELL_RED;
			continue;
		}
		if (!Q_strcasecmp(token, "power_blue"))
		{
			mod->flags |= RF_POWER_BLUE;
			continue;
		}
		if (!Q_strcasecmp(token, "power_green"))
		{
			mod->flags |= RF_POWER_GREEN;
			continue;
		}
		if (!Q_strcasecmp(token, "power_red"))
		{
			mod->flags |= RF_POWER_RED;
			continue;
		}
		if (!Q_strcasecmp(token, "fx_blue"))
		{
			SetFX(0,0,1,mod);
			continue;
		}
		if (!Q_strcasecmp(token, "fx_green"))
		{
			SetFX(0,1,0,mod);
			continue;
		}
		if (!Q_strcasecmp(token, "fx_red"))
		{
			SetFX(1,0,0,mod);
			continue;
		}
		if (!Q_strcasecmp(token, "light"))
		{
			mod->flags |= RF_LIGHT;
			continue;
		}
///		if ((!Q_strcasecmp(token, "distort")) || (r_predator->value && !Q_strncasecmp(mod->name, "models/monsters/",16)))
		if (!Q_strcasecmp(token, "distort"))
		{
			mod->flags |= RF_DISTORT;
			continue;
		}
		if (!Q_strcasecmp(token, "noautobump"))
		{
			mod->flags |= RF_NOAUTOBUMP;
			continue;
		}
		if (!Q_strcasecmp(token, "smoothvecs"))
		{
			mod->flags |= RF_SMOOTHVECS;
			continue;
		}
		if (!Q_strcasecmp(token, "unsmoothvecs"))
		{
			mod->flags &= ~RF_SMOOTHVECS;
			continue;
		}
		if (!Q_strcasecmp(token, "calcnormals"))
		{
			mod->flags |= RF_CALC_NORMALS;
			continue;
		}
		if (!Q_strcasecmp(token, "noselfshadow"))
		{
			mod->flags |= RF_NOSELFSHADOW;
			continue;
		}
		if (!Q_strcasecmp(token, "chrome"))
		{
			mod->flags |= RF_CHROME;
			continue;
		}

// ModEmit and ModLight parsing...
		if (!Q_strcasecmp(token, "modemit"))
		{
			cur_mod_light = -1;
			cur_mod_emit = atoi(COM_Parse(&s));
			if (cur_mod_emit<0 || cur_mod_emit>=MAX_MODEL_EMITS)
			{
				Com_Printf("^3Warning:^7 %s has invalid modemit index\n", mod->name);
				cur_mod_emit = -1;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "modlight"))
		{
			cur_mod_emit = -1;
			cur_mod_light = atoi(COM_Parse(&s));
			if (cur_mod_light<0 || cur_mod_light>=MAX_MODEL_LIGHTS)
			{
				Com_Printf("^3Warning:^7 %s has invalid modlight index\n", mod->name);
				cur_mod_light = -1;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "tri"))
		{
			if (cur_mod_emit != -1)
			{
				if (mod->type == mod_alias)
				{
					mod->mod_emits[cur_mod_emit].mesh = 0;
					mod->mod_emits[cur_mod_emit].tri = atoi(COM_Parse(&s));

					dmdl_t	*paliashdr = (dmdl_t *)mod->extradata;
					if (mod->mod_emits[cur_mod_emit].tri >= paliashdr->num_tris)
					{
						Com_Printf("^3Warning:^7 %s has invalid tri index (%i >= %i)\n", mod->name, mod->mod_emits[cur_mod_emit].tri, paliashdr->num_tris);
						mod->mod_emits[cur_mod_emit].tri = 0;
					}
				}
				else if (mod->type == mod_alias_md3)
				{
					mod->mod_emits[cur_mod_emit].mesh = atoi(COM_Parse(&s));
					mod->mod_emits[cur_mod_emit].tri = atoi(COM_Parse(&s));

					maliasmodel_t	*paliashdr = (maliasmodel_t *)mod->extradata;
					if (mod->mod_emits[cur_mod_emit].mesh >= paliashdr->num_meshes)
					{
						Com_Printf("^3Warning:^7 %s has invalid mesh index (%i >= %i)\n", mod->name, mod->mod_emits[cur_mod_emit].mesh, paliashdr->num_meshes);
						mod->mod_emits[cur_mod_emit].mesh = 0;
					}
					maliasmesh_t *mesh = &paliashdr->meshes[mod->mod_emits[cur_mod_emit].mesh];
					if (mod->mod_emits[cur_mod_emit].tri >= mesh->num_tris)
					{
						Com_Printf("^3Warning:^7 %s has invalid tri index (%i >= %i) in mesh %i\n", mod->name, mod->mod_emits[cur_mod_emit].tri, mesh->num_tris, mod->mod_emits[cur_mod_emit].mesh);
						mod->mod_emits[cur_mod_emit].tri = 0;
					}
				}
			}
			else
			{
				if (cur_mod_light != -1)
				{
					if (mod->type == mod_alias)
					{
						mod->mod_lights[cur_mod_light].mesh = 0;
						mod->mod_lights[cur_mod_light].tri = atoi(COM_Parse(&s));

						dmdl_t	*paliashdr = (dmdl_t *)mod->extradata;
						if (mod->mod_lights[cur_mod_light].tri >= paliashdr->num_tris)
						{
							Com_Printf("^3Warning:^7 %s has invalid tri index (%i >= %i)\n", mod->name, mod->mod_lights[cur_mod_light].tri, paliashdr->num_tris);
							mod->mod_lights[cur_mod_light].tri = 0;
						}
					}
					else if (mod->type == mod_alias_md3)
					{
						mod->mod_lights[cur_mod_light].mesh = atoi(COM_Parse(&s));
						mod->mod_lights[cur_mod_light].tri = atoi(COM_Parse(&s));

						maliasmodel_t	*paliashdr = (maliasmodel_t *)mod->extradata;
						if (mod->mod_lights[cur_mod_light].mesh >= paliashdr->num_meshes)
						{
							Com_Printf("^3Warning:^7 %s has invalid mesh index (%i >= %i)\n", mod->name, mod->mod_lights[cur_mod_light].mesh, paliashdr->num_meshes);
							mod->mod_lights[cur_mod_light].mesh = 0;
						}
						maliasmesh_t *mesh = &paliashdr->meshes[mod->mod_lights[cur_mod_light].mesh];
						if (mod->mod_lights[cur_mod_light].tri >= mesh->num_tris)
						{
							Com_Printf("^3Warning:^7 %s has invalid tri index (%i >= %i) in mesh %i\n", mod->name, mod->mod_lights[cur_mod_light].tri, mesh->num_tris, mod->mod_lights[cur_mod_light].mesh);
							mod->mod_lights[cur_mod_light].tri = 0;
						}
					}
				}
				else
				{	// skip
					COM_Parse(&s);
					if (mod->type != mod_alias)
						COM_Parse(&s);
				}
				continue;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "distance"))
		{
			if (cur_mod_emit != -1)
				mod->mod_emits[cur_mod_emit].distance = atof(COM_Parse(&s));
			else
			{
				if (cur_mod_light != -1)
					mod->mod_lights[cur_mod_light].distance = atof(COM_Parse(&s));
				else
					COM_Parse(&s);	// skip
				continue;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "angles"))
		{
			if (cur_mod_emit != -1)
			{
				COM_Parse(&s);	// skip
				COM_Parse(&s);	// skip
				COM_Parse(&s);	// skip
			}
			else
			{
				if (cur_mod_light != -1)
				{
					mod->mod_lights[cur_mod_light].angles[0] = atof(COM_Parse(&s));
					mod->mod_lights[cur_mod_light].angles[1] = atof(COM_Parse(&s));
					mod->mod_lights[cur_mod_light].angles[2] = atof(COM_Parse(&s));
				}
				else
				{
					COM_Parse(&s);	// skip
					COM_Parse(&s);	// skip
					COM_Parse(&s);	// skip
				}
				continue;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "speed"))
		{
			if (cur_mod_emit != -1)
			{
				COM_Parse(&s);	// skip
				COM_Parse(&s);	// skip
				COM_Parse(&s);	// skip
			}
			else
			{
				if (cur_mod_light != -1)
				{
					mod->mod_lights[cur_mod_light].rspeed[0] = atof(COM_Parse(&s));
					mod->mod_lights[cur_mod_light].rspeed[1] = atof(COM_Parse(&s));
					mod->mod_lights[cur_mod_light].rspeed[2] = atof(COM_Parse(&s));
				}
				else
				{
					COM_Parse(&s);	// skip
					COM_Parse(&s);	// skip
					COM_Parse(&s);	// skip
				}
				continue;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "skins"))
		{
			while (1)
			{
				os = s;
				token = COM_Parse(&s);
				if (!token[0] || token[0]<'0' || token[0]>'9')
				{
					s = os;
					break;
				}
				t = atoi(token);
				if (t<0 || t>=32)
					break;
				if (cur_mod_emit != -1)
					mod->mod_emits[cur_mod_emit].skinbits |= (unsigned)pow((double)2,t);
				else
				{
					if (cur_mod_light != -1)
						mod->mod_lights[cur_mod_light].skinbits |= (unsigned)pow((double)2,t);
					else	// skip
						COM_Parse(&s);
				}
			}
			continue;
		}

		if (!Q_strcasecmp(token, "frame_start"))
		{
			if (cur_mod_emit != -1)
				mod->mod_emits[cur_mod_emit].frame_start = atoi(COM_Parse(&s));
			else
			{
				if (cur_mod_light != -1)
					mod->mod_lights[cur_mod_light].frame_start = atoi(COM_Parse(&s));
				else	// skip
					COM_Parse(&s);
				continue;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "frame_end"))
		{
			if (cur_mod_emit != -1)
				mod->mod_emits[cur_mod_emit].frame_end = atoi(COM_Parse(&s));
			else
			{
				if (cur_mod_light != -1)
					mod->mod_lights[cur_mod_light].frame_end = atoi(COM_Parse(&s));
				else	// skip
					COM_Parse(&s);
				continue;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "style"))
		{
			if (cur_mod_emit != -1)
				mod->mod_emits[cur_mod_emit].style = atoi(COM_Parse(&s));
			else
			{
				if (cur_mod_light != -1)
					mod->mod_lights[cur_mod_light].style = atoi(COM_Parse(&s));
				else	// skip
					COM_Parse(&s);
				continue;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "cl_style"))
		{
			if (cur_mod_emit != -1)
				mod->mod_emits[cur_mod_emit].cl_style = atoi(COM_Parse(&s));
			else
			{
				if (cur_mod_light != -1)
					mod->mod_lights[cur_mod_light].cl_style = atoi(COM_Parse(&s));
				else	// skip
					COM_Parse(&s);
				continue;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "cone"))
		{
			if (cur_mod_emit != -1)
				mod->mod_emits[cur_mod_emit].cone = atof(COM_Parse(&s));
			else
			{
				if (cur_mod_light != -1)
					mod->mod_lights[cur_mod_light]._cone = atof(COM_Parse(&s));
				else	// skip
					COM_Parse(&s);
				continue;
			}
			continue;
		}

		if (!Q_strcasecmp(token, "type"))
		{
			if (cur_mod_emit != -1)
			{
				mod->mod_emits[cur_mod_emit].emit = (parttype_t)atoi(COM_Parse(&s));
				if (mod->mod_emits[cur_mod_emit].emit<part_simple || mod->mod_emits[cur_mod_emit].emit>=part_end_of_list_for_emitters)
					mod->mod_emits[cur_mod_emit].emit = part_simple;
			}
			else
				COM_Parse(&s);	// skip
			continue;
		}

		if (!Q_strcasecmp(token, "numemits"))
		{
			if (cur_mod_emit != -1)
			{
				mod->mod_emits[cur_mod_emit].numemits = atoi(COM_Parse(&s));
				if (mod->mod_emits[cur_mod_emit].numemits <= 0)
					mod->mod_emits[cur_mod_emit].numemits = 1;
			}
			else
				COM_Parse(&s);	// skip
			continue;
		}

		if (!Q_strcasecmp(token, "lifetime"))
		{
			if (cur_mod_emit != -1)
			{
				mod->mod_emits[cur_mod_emit].alphavel = atof(COM_Parse(&s));
				if (mod->mod_emits[cur_mod_emit].alphavel <= 0)
					mod->mod_emits[cur_mod_emit].alphavel = 1;
			}
			else
				COM_Parse(&s);	// skip
			continue;
		}

		if (!Q_strcasecmp(token, "vel"))
		{
			if (cur_mod_emit != -1)
				mod->mod_emits[cur_mod_emit].vel = atof(COM_Parse(&s));
			else
				COM_Parse(&s);	// skip
			continue;
		}

		if (!Q_strcasecmp(token, "gravity"))
		{
			if (cur_mod_emit != -1)
				mod->mod_emits[cur_mod_emit].gravity = atoi(COM_Parse(&s));
			else
				COM_Parse(&s);	// skip
			continue;
		}

		if (!Q_strcasecmp(token, "startcolor"))
		{
			if (cur_mod_emit != -1)
			{
				mod->mod_emits[cur_mod_emit].startcolor[0] = atof(COM_Parse(&s));
				mod->mod_emits[cur_mod_emit].startcolor[1] = atof(COM_Parse(&s));
				mod->mod_emits[cur_mod_emit].startcolor[2] = atof(COM_Parse(&s));
				if (mod->mod_emits[cur_mod_emit].startcolor[0]<0 || mod->mod_emits[cur_mod_emit].startcolor[0]>1)
					mod->mod_emits[cur_mod_emit].startcolor[0] = 0;
				if (mod->mod_emits[cur_mod_emit].startcolor[1]<0 || mod->mod_emits[cur_mod_emit].startcolor[1]>1)
					mod->mod_emits[cur_mod_emit].startcolor[1] = 0;
				if (mod->mod_emits[cur_mod_emit].startcolor[2]<0 || mod->mod_emits[cur_mod_emit].startcolor[2]>1)
					mod->mod_emits[cur_mod_emit].startcolor[2] = 0;
			}
			else
			{	// skip
				COM_Parse(&s);
				COM_Parse(&s);
				COM_Parse(&s);
			}
			continue;
		}

		if (!Q_strcasecmp(token, "endcolor"))
		{
			if (cur_mod_emit != -1)
			{
				mod->mod_emits[cur_mod_emit].endcolor[0] = atof(COM_Parse(&s));
				mod->mod_emits[cur_mod_emit].endcolor[1] = atof(COM_Parse(&s));
				mod->mod_emits[cur_mod_emit].endcolor[2] = atof(COM_Parse(&s));
				if (mod->mod_emits[cur_mod_emit].endcolor[0]<0 || mod->mod_emits[cur_mod_emit].endcolor[0]>1)
					mod->mod_emits[cur_mod_emit].endcolor[0] = 0;
				if (mod->mod_emits[cur_mod_emit].endcolor[1]<0 || mod->mod_emits[cur_mod_emit].endcolor[1]>1)
					mod->mod_emits[cur_mod_emit].endcolor[1] = 0;
				if (mod->mod_emits[cur_mod_emit].endcolor[2]<0 || mod->mod_emits[cur_mod_emit].endcolor[2]>1)
					mod->mod_emits[cur_mod_emit].endcolor[2] = 0;
			}
			else
			{	// skip
				COM_Parse(&s);
				COM_Parse(&s);
				COM_Parse(&s);
			}
			continue;
		}

		if (!Q_strcasecmp(token, "color"))
		{
			if (cur_mod_light != -1)
			{
				mod->mod_lights[cur_mod_light].color[0] = atof(COM_Parse(&s));
				mod->mod_lights[cur_mod_light].color[1] = atof(COM_Parse(&s));
				mod->mod_lights[cur_mod_light].color[2] = atof(COM_Parse(&s));
				if (mod->mod_lights[cur_mod_light].color[0]<0 || mod->mod_lights[cur_mod_light].color[0]>1)
					mod->mod_lights[cur_mod_light].color[0] = 1;
				if (mod->mod_lights[cur_mod_light].color[1]<0 || mod->mod_lights[cur_mod_light].color[1]>1)
					mod->mod_lights[cur_mod_light].color[1] = 1;
				if (mod->mod_lights[cur_mod_light].color[2]<0 || mod->mod_lights[cur_mod_light].color[2]>1)
					mod->mod_lights[cur_mod_light].color[2] = 1;
			}
			else
			{	// skip
				COM_Parse(&s);
				COM_Parse(&s);
				COM_Parse(&s);
			}
			continue;
		}

		if (!Q_strcasecmp(token, "radius"))
		{
			if (cur_mod_light != -1)
			{
				mod->mod_lights[cur_mod_light].radius = atof(COM_Parse(&s));
				if (mod->mod_lights[cur_mod_light].radius < 2)
					mod->mod_lights[cur_mod_light].radius = 64;
			}
			else	// skip
				COM_Parse(&s);
			continue;
		}

		if (!Q_strcasecmp(token, "filter"))
		{
			if (cur_mod_light != -1)
			{
				mod->mod_lights[cur_mod_light].filtercube_start = atoi(COM_Parse(&s));
				mod->mod_lights[cur_mod_light].filtercube_end = atoi(COM_Parse(&s));

				if (mod->mod_lights[cur_mod_light].filtercube_start<0 || mod->mod_lights[cur_mod_light].filtercube_start>MAX_FILTERS)
					mod->mod_lights[cur_mod_light].filtercube_start = 0;

				if (mod->mod_lights[cur_mod_light].filtercube_end<0 || mod->mod_lights[cur_mod_light].filtercube_end>MAX_FILTERS)
					mod->mod_lights[cur_mod_light].filtercube_end = 0;

				if (!mod->mod_lights[cur_mod_light].filtercube_start && mod->mod_lights[cur_mod_light].filtercube_end)
					mod->mod_lights[cur_mod_light].filtercube_start = mod->mod_lights[cur_mod_light].filtercube_end;

				if (mod->mod_lights[cur_mod_light].filtercube_start && !mod->mod_lights[cur_mod_light].filtercube_end)
					mod->mod_lights[cur_mod_light].filtercube_end = mod->mod_lights[cur_mod_light].filtercube_start;
			}
			else
			{	// skip
				COM_Parse(&s);
				COM_Parse(&s);
			}
			continue;
		}

		if (!Q_strcasecmp(token, "framerate"))
		{
			if (cur_mod_light != -1)
				mod->mod_lights[cur_mod_light].framerate = atof(COM_Parse(&s));
			else	// skip
				COM_Parse(&s);
			continue;
		}
	}

	if (r_simple->value)
	{
/*		if (mod->flags & RF_POWER_RED)
			mod->flags |= RF_SHELL_RED;

		if (mod->flags & RF_POWER_GREEN)
			mod->flags |= RF_SHELL_GREEN;

		if (mod->flags & RF_POWER_BLUE)
			mod->flags |= RF_SHELL_BLUE;
*/
		if (mod->flags & RF_LIGHT)
			mod->flags |= /*RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE|*/RF_FULLBRIGHT;

		mod->flags &= ~(/*RF_CHROME|RF_POWER_RED|RF_POWER_GREEN|RF_POWER_BLUE|*/RF_LIGHT|RF_NOCASTSHADOW);
	}
}


int R_FindTriangleWithEdge ( WORD *indexes, int numtris, WORD start, WORD end, int ignore)
{
	int i;
	int match, count;
	
	count = 0;
	match = -1;
	
	for (i = 0; i < numtris; i++, indexes += 3)
	{
		if ( (indexes[0] == start && indexes[1] == end)
			|| (indexes[1] == start && indexes[2] == end)
			|| (indexes[2] == start && indexes[0] == end) )
		{
			if (i != ignore)
				match = i;
			count++;
		} else if ( (indexes[1] == start && indexes[0] == end)
			|| (indexes[2] == start && indexes[1] == end)
			|| (indexes[0] == start && indexes[2] == end) )
		{
			count++;
		}
	}

	// detect edges shared by three triangles and make them seams
	if (count > 2)
		match = -1;

	return match;
}


/*
===============
R_BuildTriangleNeighbors
===============
*/
void R_BuildTriangleNeighbors ( neighbours_t *neighbors, WORD *indexes, int numtris )
{
	int				i;
	neighbours_t	*n;
	WORD			*index;

	for (i = 0, index = indexes, n = neighbors; i < numtris; i++, index += 3, n++)
	{
		n->neighbours[0] = R_FindTriangleWithEdge (indexes, numtris, index[1], index[0], i);
		n->neighbours[1] = R_FindTriangleWithEdge (indexes, numtris, index[2], index[1], i);
		n->neighbours[2] = R_FindTriangleWithEdge (indexes, numtris, index[0], index[2], i);
	}
}


int R_FindTriangleWithEdgeASE ( WORD *indexes, int numtris, WORD start, WORD end, int ignore, maliasvertex_t *poutvert )
{
	int		i;
	int		match, count;
	vec3_t	v_start, v_end, v0, v1, v2;

	VectorCopy(poutvert[start].point, v_start);
	VectorCopy(poutvert[end].point, v_end);

	count = 0;
	match = -1;
	
	for (i = 0; i < numtris; i++, indexes += 3)
	{
		VectorCopy(poutvert[indexes[0]].point, v0);
		VectorCopy(poutvert[indexes[1]].point, v1);
		VectorCopy(poutvert[indexes[2]].point, v2);

		if ( (VectorCompare(v0,v_start) && VectorCompare(v1,v_end))
			|| (VectorCompare(v1,v_start) && VectorCompare(v2,v_end))
			|| (VectorCompare(v2,v_start) && VectorCompare(v0,v_end)) )
		{
			if (i != ignore)
				match = i;
			count++;
		} else if ( (VectorCompare(v1,v_start) && VectorCompare(v0,v_end))
			|| (VectorCompare(v2,v_start) && VectorCompare(v1,v_end))
			|| (VectorCompare(v0,v_start) && VectorCompare(v2,v_end)) )
		{
			count++;
		}
	}

	// detect edges shared by three triangles and make them seams
	if (count > 2)
		match = -1;

	return match;
}


void R_BuildTriangleNeighborsASE ( neighbours_t *neighbors, WORD *indexes, int numtris, maliasvertex_t *poutvert )
{
	int				i;
	neighbours_t	*n;
	WORD			*index;

	for (i = 0, index = indexes, n = neighbors; i < numtris; i++, index += 3, n++)
	{
		n->neighbours[0] = R_FindTriangleWithEdgeASE (indexes, numtris, index[1], index[0], i, poutvert);
		n->neighbours[1] = R_FindTriangleWithEdgeASE (indexes, numtris, index[2], index[1], i, poutvert);
		n->neighbours[2] = R_FindTriangleWithEdgeASE (indexes, numtris, index[0], index[2], i, poutvert);
	}
}


void TangentForTrimd3(WORD *index, maliasvertex_s *vertices, maliascoord_s *texcos, vec3_t Tangent, vec3_t Binormal)
{
	float *v0, *v1, *v2;
	float *st0, *st1, *st2;
	vec3_t vec1, vec2;
	vec3_t planes[3];
	int i;

	v0 = vertices[index[0]].point;
	v1 = vertices[index[1]].point;
	v2 = vertices[index[2]].point;
	st0 = texcos[index[0]].st;
	st1 = texcos[index[1]].st;
	st2 = texcos[index[2]].st;

	for (i=0; i<3; i++)
	{
		vec1[0] = v1[i]-v0[i];
		vec1[1] = st1[0]-st0[0];
		vec1[2] = st1[1]-st0[1];
		vec2[0] = v2[i]-v0[i];
		vec2[1] = st2[0]-st0[0];
		vec2[2] = st2[1]-st0[1];
		VectorNormalize(vec1);
		VectorNormalize(vec2);
		CrossProduct(vec1,vec2,planes[i]);
	}

	// Berserker's fix: some models could have degenerate triangles (or strongly elongated)
	// corrected the divisor so that it was not very close to zero, otherwise we get division by zero or +/- INF
	#define DIV_EPSILON 0.00001
	if (fabs(planes[0][0]) <= DIV_EPSILON) planes[0][0] = DIV_EPSILON * sign(planes[0][0]);
	if (fabs(planes[1][0]) <= DIV_EPSILON) planes[1][0] = DIV_EPSILON * sign(planes[1][0]);
	if (fabs(planes[2][0]) <= DIV_EPSILON) planes[2][0] = DIV_EPSILON * sign(planes[2][0]);

	//Tangent = (-planes[B][x]/plane[A][x], -planes[B][y]/planes[A][y], - planes[B][z]/planes[A][z] )
	//Binormal = (-planes[C][x]/planes[A][x], -planes[C][y]/planes[A][y], -planes[C][z]/planes[A][z] )
	Tangent[0] = -planes[0][1]/planes[0][0];
	Tangent[1] = -planes[1][1]/planes[1][0];
	Tangent[2] = -planes[2][1]/planes[2][0];
	Binormal[0] = -planes[0][2]/planes[0][0];
	Binormal[1] = -planes[1][2]/planes[1][0];
	Binormal[2] = -planes[2][2]/planes[2][0];
	VectorNormalize(Tangent); //is this needed?
	VectorNormalize(Binormal);
}

/*
bool IQM_CheckRange(iqmHeader_t *header, int offset, int count, int size)
{	// return true if the range specified by offset, count and size
	// doesn't fit into the file
	return (count <= 0 ||
		offset < 0 ||
		offset > header->filesize ||
		offset + count * size < 0 ||
		offset + count * size > header->filesize);
}

void Mod_LoadAliasIQMModel(model_t *mod, void *buffer, float scale, bool invert, bool cache)
{
	iqmHeader_t		*header;
	iqmVertexArray_t	*vertexarray;
	iqmTriangle_t	*triangle;
	iqmMesh_t		*mesh;
	iqmJoint_t		*joint;
	iqmPose_t		*pose;
	iqmBounds_t		*bounds;
	unsigned short	*framedata;
	char			*str;
	int				i, j;
	float			jointInvMats[IQM_MAX_JOINTS * 12] = { 0.0f };
	float			*mat, *matInv;
	size_t			size, joint_names;
	iqmData_t		*iqmData;
	srfIQModel_t	*surface;
	char			meshName[MAX_QPATH];
	byte			blendIndexesType, blendWeightsType;
	vec3_t			invscale;

	if (scale <= 0.001)
		Com_Error(ERR_DROP, "%s with invalid scale", mod->name);

	header = (iqmHeader_t *)buffer;
	if (strncmp(header->magic, IQM_MAGIC, sizeof(header->magic)))
		Com_Error(ERR_DROP, "%s is not IQM", mod->name);

	header->version = LittleLong(header->version);
	if (header->version != IQM_VERSION)
		Com_Error(ERR_DROP, "%s is an unsupported IQM version %d, need version %d", mod->name, header->version, IQM_VERSION);

	header->filesize = LittleLong(header->filesize);
	header->flags = LittleLong(header->flags);
	header->num_text = LittleLong(header->num_text);
	header->ofs_text = LittleLong(header->ofs_text);
	header->num_meshes = LittleLong(header->num_meshes);
	header->ofs_meshes = LittleLong(header->ofs_meshes);
	header->num_vertexarrays = LittleLong(header->num_vertexarrays);
	header->num_vertexes = LittleLong(header->num_vertexes);
	header->ofs_vertexarrays = LittleLong(header->ofs_vertexarrays);
	header->num_triangles = LittleLong(header->num_triangles);
	header->ofs_triangles = LittleLong(header->ofs_triangles);
	header->ofs_adjacency = LittleLong(header->ofs_adjacency);
	header->num_joints = LittleLong(header->num_joints);
	header->ofs_joints = LittleLong(header->ofs_joints);
	header->num_poses = LittleLong(header->num_poses);
	header->ofs_poses = LittleLong(header->ofs_poses);
	header->num_anims = LittleLong(header->num_anims);
	header->ofs_anims = LittleLong(header->ofs_anims);
	header->num_frames = LittleLong(header->num_frames);
	header->num_framechannels = LittleLong(header->num_framechannels);
	header->ofs_frames = LittleLong(header->ofs_frames);
	header->ofs_bounds = LittleLong(header->ofs_bounds);
	header->num_comment = LittleLong(header->num_comment);
	header->ofs_comment = LittleLong(header->ofs_comment);
	header->num_extensions = LittleLong(header->num_extensions);
	header->ofs_extensions = LittleLong(header->ofs_extensions);

	// check ioq3 joint limit
	if (header->num_joints > IQM_MAX_JOINTS)
		Com_Error(ERR_DROP, "%s has more than %d joints: %d", mod->name, IQM_MAX_JOINTS, header->num_joints);

	blendIndexesType = blendWeightsType = IQM_UBYTE;

	// check and swap vertex arrays
	if (IQM_CheckRange(header, header->ofs_vertexarrays, header->num_vertexarrays, sizeof(iqmVertexArray_t)))
		Com_Error(ERR_DROP, "IQM_CheckRange(vertexarrays, %i) failed for %s", header->num_vertexarrays, mod->name);

	if (invert)
		VectorSet(invscale, scale, -scale, scale);
	else
		VectorSet(invscale, scale, scale, scale);

	vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays);
	for (i = 0; i < header->num_vertexarrays; i++, vertexarray++)
	{
		int	n, *intPtr;
		if (vertexarray->size <= 0 || vertexarray->size > 4)
			Com_Error(ERR_DROP, "vertexarray size check failed for %s", mod->name);

		// total number of values
		n = header->num_vertexes * vertexarray->size;

		switch (vertexarray->format)
		{
		case IQM_BYTE:
		case IQM_UBYTE:
			// 1 byte, no swapping necessary
			if (IQM_CheckRange(header, vertexarray->offset, n, sizeof(byte)))
				Com_Error(ERR_DROP, "IQM_CheckRange(vertexarray, %i) failed for %s", n, mod->name);
			break;
		case IQM_INT:
		case IQM_UINT:
		case IQM_FLOAT:
			// 4-byte swap
			if (IQM_CheckRange(header, vertexarray->offset, n, sizeof(float)))
				Com_Error(ERR_DROP, "IQM_CheckRange(vertexarray, %i) failed for %s", n, mod->name);
			intPtr = (int *)((byte *)header + vertexarray->offset);
			for (j = 0; j < n; j++, intPtr++)
				*intPtr = LittleLong(*intPtr);
			break;
		default:
			// not supported
			Com_Error(ERR_DROP, "unsupported vertexarray format in %s", mod->name);
			break;
		}

		switch (vertexarray->type)
		{
		case IQM_POSITION:
		case IQM_NORMAL:
			if (vertexarray->format != IQM_FLOAT || vertexarray->size != 3)
				Com_Error(ERR_DROP, "vertexarray POSITION / NORMAL not FLOAT(3) in %s", mod->name);
			break;
		case IQM_TANGENT:
			if (vertexarray->format != IQM_FLOAT || vertexarray->size != 4)
				Com_Error(ERR_DROP, "vertexarray TANGENT not FLOAT(4) in %s", mod->name);
			break;
		case IQM_TEXCOORD:
			if (vertexarray->format != IQM_FLOAT || vertexarray->size != 2)
				Com_Error(ERR_DROP, "vertexarray TEXCOORD not FLOAT(2) in %s", mod->name);
			break;
		case IQM_BLENDINDEXES:
			if ((vertexarray->format != IQM_INT && vertexarray->format != IQM_UBYTE) || vertexarray->size != 4)
				Com_Error(ERR_DROP, "vertexarray BLENDINDEXES not INT(2) or UBYTE(2) in %s", mod->name);
			blendIndexesType = vertexarray->format;
			break;
		case IQM_BLENDWEIGHTS:
			if ((vertexarray->format != IQM_FLOAT && vertexarray->format != IQM_UBYTE) || vertexarray->size != 4)
				Com_Error(ERR_DROP, "vertexarray BLENDWEIGHTS not FLOAT(4) or UBYTE(4) in %s", mod->name);
			blendWeightsType = vertexarray->format;
			break;
		case IQM_COLOR:
			if (vertexarray->format != IQM_UBYTE || vertexarray->size != 4)
				Com_Error(ERR_DROP, "vertexarray COLOR not UBYTE(4) in %s", mod->name);
			break;
		}
	}

	// check and swap triangles
	if (IQM_CheckRange(header, header->ofs_triangles, header->num_triangles, sizeof(iqmTriangle_t)))
		Com_Error(ERR_DROP, "IQM_CheckRange(triangles, %i) failed for %s", header->num_triangles, mod->name);

	triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles);
	for (i = 0; i < header->num_triangles; i++, triangle++)
	{
		triangle->vertex[0] = LittleLong(triangle->vertex[0]);
		triangle->vertex[1] = LittleLong(triangle->vertex[1]);
		triangle->vertex[2] = LittleLong(triangle->vertex[2]);

		if (triangle->vertex[0] > header->num_vertexes ||
			triangle->vertex[1] > header->num_vertexes ||
			triangle->vertex[2] > header->num_vertexes)
			Com_Error(ERR_DROP, "vertex index check failed for triangle % of %s", i, mod->name);
	}

	// check and swap meshes
	if (IQM_CheckRange(header, header->ofs_meshes, header->num_meshes, sizeof(iqmMesh_t)))
		Com_Error(ERR_DROP, "IQM_CheckRange(meshes, %i) failed for %s", header->num_meshes, mod->name);

	mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes);
	for (i = 0; i < header->num_meshes; i++, mesh++)
	{
		mesh->name = LittleLong(mesh->name);
		mesh->material = LittleLong(mesh->material);
		mesh->first_vertex = LittleLong(mesh->first_vertex);
		mesh->num_vertexes = LittleLong(mesh->num_vertexes);
		mesh->first_triangle = LittleLong(mesh->first_triangle);
		mesh->num_triangles = LittleLong(mesh->num_triangles);

		if (mesh->name < header->num_text)
			Q_strncpyz(meshName, (char*)header + header->ofs_text + mesh->name, sizeof(meshName));
		else
			meshName[0] = '\0';

		if (mesh->first_vertex >= header->num_vertexes ||
			mesh->first_vertex + mesh->num_vertexes > header->num_vertexes ||
			mesh->first_triangle >= header->num_triangles ||
			mesh->first_triangle + mesh->num_triangles > header->num_triangles ||
			mesh->name >= header->num_text ||
			mesh->material >= header->num_text)
			Com_Error(ERR_DROP, "mesh %i check failed for %s", i, mod->name);
	}

	if (header->num_poses != header->num_joints && header->num_poses != 0)
		Com_Error(ERR_DROP, "%s has %d poses and %d joints, must have the same number or 0 poses", mod->name, header->num_poses, header->num_joints);

	joint_names = 0;

	if (header->num_joints)
	{
		// check and swap joints
		if (IQM_CheckRange(header, header->ofs_joints, header->num_joints, sizeof(iqmJoint_t)))
			Com_Error(ERR_DROP, "IQM_CheckRange(joints, %i) failed for %s", header->num_joints, mod->name);

		joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
		for (i = 0; i < header->num_joints; i++, joint++)
		{
			joint->name = LittleLong(joint->name);
			joint->parent = LittleLong(joint->parent);
			joint->translate[0] = LittleFloat(joint->translate[0]);
			joint->translate[1] = LittleFloat(joint->translate[1]);
			joint->translate[2] = LittleFloat(joint->translate[2]);
			joint->rotate[0] = LittleFloat(joint->rotate[0]);
			joint->rotate[1] = LittleFloat(joint->rotate[1]);
			joint->rotate[2] = LittleFloat(joint->rotate[2]);
			joint->rotate[3] = LittleFloat(joint->rotate[3]);
			joint->scale[0] = LittleFloat(joint->scale[0]);
			joint->scale[1] = LittleFloat(joint->scale[1]);
			joint->scale[2] = LittleFloat(joint->scale[2]);

			if (joint->parent < -1 ||
				joint->parent >= (int)header->num_joints ||
				joint->name >= (int)header->num_text)
				Com_Error(ERR_DROP, "joint %i check failed for %s", i, mod->name);
			joint_names += strlen((char *)header + header->ofs_text + joint->name) + 1;
		}
	}

	if (header->num_poses)
	{	// check and swap poses
		if (IQM_CheckRange(header, header->ofs_poses, header->num_poses, sizeof(iqmPose_t)))
			Com_Error(ERR_DROP, "IQM_CheckRange(poses, %i) failed for %s", header->num_poses, mod->name);
		pose = (iqmPose_t *)((byte *)header + header->ofs_poses);
		for (i = 0; i < header->num_poses; i++, pose++)
		{
			pose->parent = LittleLong(pose->parent);
			pose->mask = LittleLong(pose->mask);
			pose->channeloffset[0] = LittleFloat(pose->channeloffset[0]);
			pose->channeloffset[1] = LittleFloat(pose->channeloffset[1]);
			pose->channeloffset[2] = LittleFloat(pose->channeloffset[2]);
			pose->channeloffset[3] = LittleFloat(pose->channeloffset[3]);
			pose->channeloffset[4] = LittleFloat(pose->channeloffset[4]);
			pose->channeloffset[5] = LittleFloat(pose->channeloffset[5]);
			pose->channeloffset[6] = LittleFloat(pose->channeloffset[6]);
			pose->channeloffset[7] = LittleFloat(pose->channeloffset[7]);
			pose->channeloffset[8] = LittleFloat(pose->channeloffset[8]);
			pose->channeloffset[9] = LittleFloat(pose->channeloffset[9]);
			pose->channelscale[0] = LittleFloat(pose->channelscale[0]);
			pose->channelscale[1] = LittleFloat(pose->channelscale[1]);
			pose->channelscale[2] = LittleFloat(pose->channelscale[2]);
			pose->channelscale[3] = LittleFloat(pose->channelscale[3]);
			pose->channelscale[4] = LittleFloat(pose->channelscale[4]);
			pose->channelscale[5] = LittleFloat(pose->channelscale[5]);
			pose->channelscale[6] = LittleFloat(pose->channelscale[6]);
			pose->channelscale[7] = LittleFloat(pose->channelscale[7]);
			pose->channelscale[8] = LittleFloat(pose->channelscale[8]);
			pose->channelscale[9] = LittleFloat(pose->channelscale[9]);
		}
	}

	if (header->ofs_bounds)
	{
		// check and swap model bounds
		if (IQM_CheckRange(header, header->ofs_bounds, header->num_frames, sizeof(*bounds)))
			Com_Error(ERR_DROP, "IQM_CheckRange(bounds, %i) failed for %s", header->num_frames, mod->name);
		bounds = (iqmBounds_t *)((byte *)header + header->ofs_bounds);
		for (i = 0; i < header->num_frames; i++)
		{
			bounds->bbmin[0] = LittleFloat(bounds->bbmin[0]);
			bounds->bbmin[1] = LittleFloat(bounds->bbmin[1]);
			bounds->bbmin[2] = LittleFloat(bounds->bbmin[2]);
			bounds->bbmax[0] = LittleFloat(bounds->bbmax[0]);
			bounds->bbmax[1] = LittleFloat(bounds->bbmax[1]);
			bounds->bbmax[2] = LittleFloat(bounds->bbmax[2]);
			bounds++;
		}
	}

	// allocate the model and copy the data
	size = sizeof(iqmData_t);
	size += header->num_meshes * sizeof(srfIQModel_t);
	size += header->num_joints * 12 * sizeof(float); // joint mats
	size += header->num_poses * header->num_frames * 12 * sizeof(float); // pose mats
	if (header->ofs_bounds)
		size += header->num_frames * 6 * sizeof(float);	// model bounds
	size += header->num_vertexes * 3 * sizeof(float);	// positions
	size += header->num_vertexes * 2 * sizeof(float);	// texcoords
	size += header->num_vertexes * 3 * sizeof(float);	// normals
	size += header->num_vertexes * 4 * sizeof(float);	// tangents
	size += header->num_vertexes * 4 * sizeof(byte);	// blendIndexes
	size += header->num_vertexes * 4 * sizeof(byte);	// colors
	size += header->num_joints * sizeof(int);		// parents
	size += header->num_triangles * 3 * sizeof(int);	// triangles
	size += joint_names;					// joint names

											// blendWeights
	if (blendWeightsType == IQM_FLOAT)
		size += header->num_vertexes * 4 * sizeof(float);
	else
		size += header->num_vertexes * 4 * sizeof(byte);

	mod->type = mod_alias_iqm;
	mod->iqm = iqmData = (iqmData_t *)Hunk_Alloc(size);

	// fill header
	iqmData->num_vertexes = header->num_vertexes;
	iqmData->num_triangles = header->num_triangles;
	iqmData->num_frames = header->num_frames;
	iqmData->num_surfaces = header->num_meshes;
	iqmData->num_joints = header->num_joints;
	iqmData->num_poses = header->num_poses;
	iqmData->blendWeightsType = blendWeightsType;
	iqmData->surfaces = (srfIQModel_t *)(iqmData + 1);
	iqmData->jointMats = (float *)(iqmData->surfaces + iqmData->num_surfaces);
	iqmData->poseMats = iqmData->jointMats + 12 * header->num_joints;
	if (header->ofs_bounds)
	{
		iqmData->bounds = iqmData->poseMats + 12 * header->num_poses * header->num_frames;
		iqmData->positions = iqmData->bounds + 6 * header->num_frames;
	}
	else
		iqmData->positions = iqmData->poseMats + 12 * header->num_poses * header->num_frames;
	iqmData->texcoords = iqmData->positions + 3 * header->num_vertexes;
	iqmData->normals = iqmData->texcoords + 2 * header->num_vertexes;
	iqmData->tangents = iqmData->normals + 3 * header->num_vertexes;
	iqmData->blendIndexes = (byte *)(iqmData->tangents + 4 * header->num_vertexes);

	if (blendWeightsType == IQM_FLOAT)
	{
		iqmData->blendWeights.f = (float *)(iqmData->blendIndexes + 4 * header->num_vertexes);
		iqmData->colors = (byte *)(iqmData->blendWeights.f + 4 * header->num_vertexes);
	}
	else
	{
		iqmData->blendWeights.b = iqmData->blendIndexes + 4 * header->num_vertexes;
		iqmData->colors = iqmData->blendWeights.b + 4 * header->num_vertexes;
	}

	iqmData->jointParents = (int *)(iqmData->colors + 4 * header->num_vertexes);
	iqmData->triangles = iqmData->jointParents + header->num_joints;
	iqmData->names = (char *)(iqmData->triangles + 3 * header->num_triangles);

	if (header->num_joints == 0)
		iqmData->jointMats = NULL;

	if (header->num_poses == 0)
		iqmData->poseMats = NULL;

	// calculate joint matrices and their inverses
	// joint inverses are needed only until the pose matrices are calculated
	mat = iqmData->jointMats;
	matInv = jointInvMats;
	joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
	for (i = 0; i < header->num_joints; i++, joint++)
	{
		float baseFrame[12], invBaseFrame[12];

		JointToMatrix(joint->rotate, joint->scale, joint->translate, baseFrame);
		Matrix34Invert(baseFrame, invBaseFrame);

		if (joint->parent >= 0)
		{
			Matrix34Multiply(iqmData->jointMats + 12 * joint->parent, baseFrame, mat);
			mat += 12;
			Matrix34Multiply(invBaseFrame, jointInvMats + 12 * joint->parent, matInv);
			matInv += 12;
		}
		else
		{
			Memcpy(mat, baseFrame, sizeof(baseFrame));
			mat += 12;
			Memcpy(matInv, invBaseFrame, sizeof(invBaseFrame));
			matInv += 12;
		}
	}

	// calculate pose matrices
	framedata = (unsigned short *)((byte *)header + header->ofs_frames);
	mat = iqmData->poseMats;
	for (i = 0; i < header->num_frames; i++)
	{
		pose = (iqmPose_t *)((byte *)header + header->ofs_poses);
		for (j = 0; j < header->num_poses; j++, pose++)
		{
			vec3_t	translate;
			vec4_t	rotate;
			vec3_t	scale;
			float	mat1[12], mat2[12];

			translate[0] = pose->channeloffset[0];
			if (pose->mask & 0x001)
				translate[0] += *framedata++ * pose->channelscale[0];
			translate[1] = pose->channeloffset[1];
			if (pose->mask & 0x002)
				translate[1] += *framedata++ * pose->channelscale[1];
			translate[2] = pose->channeloffset[2];
			if (pose->mask & 0x004)
				translate[2] += *framedata++ * pose->channelscale[2];

			rotate[0] = pose->channeloffset[3];
			if (pose->mask & 0x008)
				rotate[0] += *framedata++ * pose->channelscale[3];
			rotate[1] = pose->channeloffset[4];
			if (pose->mask & 0x010)
				rotate[1] += *framedata++ * pose->channelscale[4];
			rotate[2] = pose->channeloffset[5];
			if (pose->mask & 0x020)
				rotate[2] += *framedata++ * pose->channelscale[5];
			rotate[3] = pose->channeloffset[6];
			if (pose->mask & 0x040)
				rotate[3] += *framedata++ * pose->channelscale[6];

			scale[0] = pose->channeloffset[7];
			if (pose->mask & 0x080)
				scale[0] += *framedata++ * pose->channelscale[7];
			scale[1] = pose->channeloffset[8];
			if (pose->mask & 0x100)
				scale[1] += *framedata++ * pose->channelscale[8];
			scale[2] = pose->channeloffset[9];
			if (pose->mask & 0x200)
				scale[2] += *framedata++ * pose->channelscale[9];

			// construct transformation matrix
			JointToMatrix(rotate, scale, translate, mat1);

			if (pose->parent >= 0)
				Matrix34Multiply(iqmData->jointMats + 12 * pose->parent, mat1, mat2);
			else
				Memcpy(mat2, mat1, sizeof(mat1));

			Matrix34Multiply(mat2, jointInvMats + 12 * j, mat);
			mat += 12;
		}
	}

	// register shaders
	// overwrite the material offset with the shader index
	mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes);
	surface = iqmData->surfaces;
	str = (char *)header + header->ofs_text;
	for (i = 0; i < header->num_meshes; i++, mesh++, surface++)
	{
		surface->surfaceType = SF_IQM;
		Q_strncpyz(surface->name, str + mesh->name, sizeof(surface->name));
		Q_strlwr(surface->name); // lowercase the surface name so skin compares are faster
		surface->shader = R_FindShader(str + mesh->material, LIGHTMAP_NONE, true);
		if (surface->shader->flags & SHADERFLAG_defaultShader)
			surface->shader = tr.defaultShader;
		surface->data = iqmData;
		surface->first_vertex = mesh->first_vertex;
		surface->num_vertexes = mesh->num_vertexes;
		surface->first_triangle = mesh->first_triangle;
		surface->num_triangles = mesh->num_triangles;
	}

	// copy vertexarrays and indexes
	vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays);
	for (i = 0; i < header->num_vertexarrays; i++, vertexarray++)
	{	// total number of values
		int	n = header->num_vertexes * vertexarray->size;

		switch (vertexarray->type)
		{
		case IQM_POSITION:
			Memcpy(iqmData->positions, (byte *)header + vertexarray->offset, n * sizeof(float));
			break;
		case IQM_NORMAL:
			Memcpy(iqmData->normals, (byte *)header + vertexarray->offset, n * sizeof(float));
			break;
		case IQM_TANGENT:
			Memcpy(iqmData->tangents, (byte *)header + vertexarray->offset, n * sizeof(float));
			break;
		case IQM_TEXCOORD:
			Memcpy(iqmData->texcoords, (byte *)header + vertexarray->offset, n * sizeof(float));
			break;
		case IQM_BLENDINDEXES:
			if (blendIndexesType == IQM_INT)
			{
				int *data = (int*)((byte*)header + vertexarray->offset);
				for (j = 0; j < n; j++)
					iqmData->blendIndexes[j] = (byte)data[j];
			}
			else
				Memcpy(iqmData->blendIndexes, (byte *)header + vertexarray->offset, n * sizeof(byte));
			break;
		case IQM_BLENDWEIGHTS:
			if (blendWeightsType == IQM_FLOAT)
				Memcpy(iqmData->blendWeights.f, (byte *)header + vertexarray->offset, n * sizeof(float));
			else
				Memcpy(iqmData->blendWeights.b, (byte *)header + vertexarray->offset, n * sizeof(byte));
			break;
		case IQM_COLOR:
			Memcpy(iqmData->colors, (byte *)header + vertexarray->offset, n * sizeof(byte));
			break;
		}
	}

	// copy joint parents
	joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
	for (i = 0; i < header->num_joints; i++, joint++)
		iqmData->jointParents[i] = joint->parent;

	// copy triangles
	triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles);
	for (i = 0; i < header->num_triangles; i++, triangle++)
	{
		iqmData->triangles[3 * i + 0] = triangle->vertex[0];
		iqmData->triangles[3 * i + 1] = triangle->vertex[1];
		iqmData->triangles[3 * i + 2] = triangle->vertex[2];
	}

	// copy joint names
	str = iqmData->names;
	joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
	for (i = 0; i < header->num_joints; i++, joint++)
	{
		char *name = (char *)header + header->ofs_text + joint->name;
		int len = strlen(name) + 1;
		Memcpy(str, name, len);
		str += len;
	}

	// copy model bounds
	if (header->ofs_bounds)
	{
		mat = iqmData->bounds;
		bounds = (iqmBounds_t *)((byte *)header + header->ofs_bounds);
		for (i = 0; i < header->num_frames; i++)
		{
			mat[0] = bounds->bbmin[0];
			mat[1] = bounds->bbmin[1];
			mat[2] = bounds->bbmin[2];
			mat[3] = bounds->bbmax[0];
			mat[4] = bounds->bbmax[1];
			mat[5] = bounds->bbmax[2];

			mat += 6;
			bounds++;
		}
	}





}
*/

// Load ASE into MD3 container
void Mod_LoadAliasASEModel(model_t *mod, void *buffer, float scale, bool invert, bool cache)
{
	neighbours_t		*triangles;
	WORD				*poutindex;
	maliasvertex_t		*poutvert;
	maliascoord_t		*poutcoord;
	maliasmesh_t		*poutmesh;
///	maliasskin_t		*poutskin;
	maliasframe_t		*poutframe;
	maliasmodel_t		*poutmodel;
	char				name[MAX_OSPATH+8];
	char				*buf;
	char				*token;
	int					num_materials, mesh, num_meshVerts;
	image_t				*skins[MD3_MAX_MESHES][MD3_MAX_SKINS];
	image_t				*bumps[MD3_MAX_MESHES][MD3_MAX_SKINS];
	image_t				*lights[MD3_MAX_MESHES][MD3_MAX_SKINS];
	float				mat_tiles[MD3_MAX_MESHES][2];
	float				mat_offset[MD3_MAX_MESHES][2];
	int					num_skins[MD3_MAX_MESHES];
	vec3_t				meshVerts[MD3_MAX_VERTS];
	vec3_t				frame_mins[MD3_MAX_FRAMES];
	vec3_t				frame_maxs[MD3_MAX_FRAMES];
///	bool				null_mesh[MD3_MAX_MESHES];
	vec3_t				invscale;
	int					i;

	if (scale <= 0.001)
		Com_Error (ERR_DROP, "%s with invalid scale", mod->name);

	if (strncmp((char*)buffer, "*3DSMAX_ASCIIEXPORT", 19))
		Com_Error (ERR_DROP, "%s is not ASE", mod->name);

	if (invert)
		VectorSet(invscale, scale, -scale, scale);
	else
		VectorSet(invscale, scale, scale, scale);

	poutmodel = (maliasmodel_s*) Hunk_Alloc (sizeof(maliasmodel_t), true);

///	poutmodel->num_tags = 0;
	poutmodel->num_meshes = 0;
///	poutmodel->num_skins = 1;

	// считаем количество кадров (по первому найденному *MESH_ANIMATION, ибо есть требование: все меши имеют одинаковое количество кадров)
	poutmodel->num_frames = 1;
	buf = (char*)buffer;
	while (buf)
	{
		token = COM_Parse (&buf);
		if (!strcmp(token, "*MESH_ANIMATION"))
		{	// если найден анимированный мешь
			poutmodel->num_frames = 0;	// обнулим счётчик кадров
			while (buf)
			{
				token = COM_Parse (&buf);
				if (!strcmp(token, "*GEOMOBJECT"))	// если наткнулись на следующий мешь, то прерываем сканирование
					goto stop;
				if (!strcmp(token, "*MESH"))		// считаем меши внутри анимированного меша (считаем кадры)
					poutmodel->num_frames++;
			}
			goto stop;
		}
	}
stop:
	if (poutmodel->num_frames <= 0)
	{
		Com_Printf("^3WARNING:^7 animated model '%s' with no frames\n", mod->name);
		poutmodel->num_frames = 1;
	}
	else if (poutmodel->num_frames > MD3_MAX_FRAMES)
	{
		Com_Printf("^3WARNING:^7 animated model '%s' has too many frames\n", mod->name);
		poutmodel->num_frames = MD3_MAX_FRAMES;
	}

	for (i=0; i<poutmodel->num_frames; i++)
		ClearBounds(frame_mins[i], frame_maxs[i]);

	// Считаем количество текстур
	buf = (char*)buffer;
	num_materials = 0;
	while (buf)
	{
		token = COM_Parse (&buf);
		if (!strcmp(token, "*MATERIAL_COUNT"))
		{
			num_materials = atoi(COM_Parse(&buf));
			break;
		}
	}
	if (!num_materials)
		Com_Error (ERR_DROP, "%s has not materials", mod->name);
	if (num_materials>=MD3_MAX_MESHES)
		Com_Error (ERR_DROP, "%s has too many materials", mod->name);

	// Кэшируем имена текстур в индексированный массив
	buf = (char*)buffer;
	while (buf)
	{
		token = COM_Parse (&buf);
		if (!strcmp(token, "*MATERIAL"))
		{
			int i;
			int idx = atoi(COM_Parse(&buf));
			if (idx<0 || idx>=num_materials)
				Com_Error (ERR_DROP, "Bad materialIndex in %s", mod->name);
			while (buf)
			{
				token = COM_Parse (&buf);
				if (!strcmp(token, "*MATERIAL_CLASS"))
				{
					token = COM_Parse (&buf);
					if (!strcmp(token, "Standard"))
					{
						num_skins[idx] = 1;
						goto stp;
					}
					else
					{
						while (buf)
						{
							token = COM_Parse (&buf);
							if (!strcmp(token, "*NUMSUBMTLS"))
							{
								num_skins[idx] = atoi(COM_Parse(&buf));
								if (num_skins[idx] > MD3_MAX_SKINS)
								{
									Com_Printf("^3WARNING:^7 model '%s' mesh %i has too many skins: %i\n", mod->name, idx, num_skins[idx]);
									num_skins[idx] = MD3_MAX_SKINS;
								}
								goto stp;
							}
							if (*token=='}' && com_token_len == 1)
								goto stp;
						}
					}
				}
				if (*token=='}' && com_token_len == 1)
					goto stp;
			}
stp:		int sk = -1;		// skin counter
			int getParms = 0;	// для подсчёта параметров bitmap, прежде чем выходить
			while (buf)
			{
				token = COM_Parse (&buf);
				if (sk+1==num_skins[idx] && getParms==4)
					break;
				if (!strcmp(token, "*BITMAP"))
				{
					sk++;
					token = COM_Parse (&buf);
					if (cache)
						goto badM;
					char *temps = strstr(token, "models");
					if (temps)
					{
						if (temps[0]=='.' && temps[1]=='.')	// ".."
							FS_UnionPath(mod->name, temps, name, sizeof(name));
						else
							Memcpy(name, temps, MD3_MAX_PATH);		// копируем относительный путь
					}
					else
					{
						if (token[0]=='.' && token[1]=='.')	// ".."
							FS_UnionPath(mod->name, token, name, sizeof(name));
						else
							Memcpy(name, token, MD3_MAX_PATH);		// иначе попробуем то что было прописано
					}
					while (RepairPath(name));	/// Berserker: устранение безобразия типа в модели: "models\monsters\tank\tris.md2" - там есть строки типа "models/monsters/tank/../ctank/skin.pcx"
					i=strlen(name);
					char *dot = strstr (name, ".");
					int kl;
					if (dot)
						kl = strlen(dot);
					else
						kl = 0;
					if (i < kl+1)
					{
						Com_Printf("^3WARNING:^7 short materialName '%s' in model %s", name, mod->name);
						goto badM;
					}
					if (i>=MD3_MAX_PATH-1)
					{
						Com_Printf("^3WARNING:^7 long materialName '%s' in model %s", name, mod->name);
						goto badM;
					}
					name[i-kl] = 0;	// cut off file-ext
					skins[idx][sk] = GL_FindImage(name, it_skin, false, 0, false, 0);
					// не нашли, попробуем взять шкуру используя путь до модели
					if (!skins[idx][sk])
					{
						char path[MAX_QPATH], *file;
						COM_FilePath(mod->name, path);
						file = COM_SkipPath(name);
						Com_sprintf(name, sizeof(name), "%s/%s", path, file);
						skins[idx][sk] = GL_FindImage(name, it_skin, false, 0, false, 0);
						i = strlen(name);
						kl = 0;
					}
					if(skins[idx][sk])
					{
						if (!r_simple->value)
						{
///							if (skins[idx][sk]->fx == fx_distort)
///								mod->flags |= RF_DISTORT_SKIN;	/// признак того, что хоть один скин с дисторт-эффектом
							name[i-kl+0] = '_';
							name[i-kl+1] = 'b';
							name[i-kl+2] = 'u';
							name[i-kl+3] = 'm';
							name[i-kl+4] = 'p';
							name[i-kl+5] = '.';
							name[i-kl+6] = 't';
							name[i-kl+7] = 'g';
							name[i-kl+8] = 'a';
							name[i-kl+9] = 0;
							bumps[idx][sk] = GL_FindImage (name, it_bump, true, skins[idx][sk], false, 0);
							if (bumps[idx][sk] == r_defaultbump)
							{	// если нет bump-скина TGA, то попробуем DDS
								name[i-kl+6] = 'd';
								name[i-kl+7] = 'd';
								name[i-kl+8] = 's';
								bumps[idx][sk] = GL_FindImage (name, it_bump, true, skins[idx][sk], /*(r_bumpscale->value && !(mod->flags & RF_NOAUTOBUMP)) ? true :*/ false, 0);
							}
						}
						else
							bumps[idx][sk] = NULL;

						name[i-kl+0] = '_';
						name[i-kl+1] = 'l';
						name[i-kl+2] = 'i';
						name[i-kl+3] = 'g';
						name[i-kl+4] = 'h';
						name[i-kl+5] = 't';
						name[i-kl+6] = 0;
						lights[idx][sk] = GL_FindImage (name, it_light, false, 0, false, 0);
					}
					else
					{
						Com_DPrintf("Warning: '%s' not found for model %s\n", name, mod->name);
badM:					skins[idx][sk] = r_notexture;
						bumps[idx][sk] = lights[idx][sk] = NULL;
					}
				}
				else if (!strcmp(token, "*UVW_U_TILING") && !sk)		// for skin #0 only!
				{
					mat_tiles[idx][0] = atof(COM_Parse (&buf));
					getParms++;
				}
				else if (!strcmp(token, "*UVW_V_TILING") && !sk)
				{
					mat_tiles[idx][1] = atof(COM_Parse (&buf));
					getParms++;
				}
				else if (!strcmp(token, "*UVW_U_OFFSET") && !sk)
				{
					mat_offset[idx][0] = atof(COM_Parse (&buf));
					getParms++;
				}
				else if (!strcmp(token, "*UVW_V_OFFSET") && !sk)
				{
					mat_offset[idx][1] = atof(COM_Parse (&buf));
					getParms++;
				}
			}
		}
	}

	// Считаем количество мешей (в том числе и меши кадров) и проверяем наличие необходимых секций
	int num_MESH_NORMALS = 0;
	int num_MESH_TVERTLIST = 0;
	int tp = -1;
		bool	nul_mesh;
	buf = (char*)buffer;
	while (buf)
	{
		token = COM_Parse (&buf);
		if (!strcmp(token, "*MESH"))
		{
			tp++;
///			null_mesh[tp] = false;
			nul_mesh = false;
			poutmodel->num_meshes++;
			continue;
		}
		if (!strcmp(token, "*MESH_NUMVERTEX"))	// empty mesh
		{
			token = COM_Parse (&buf);
			if (!strcmp(token, "0"))
			{
///				Com_Printf("^3%s has empty mesh %i\n", mod->name, tp);
///				null_mesh[tp] = true;
///				poutmodel->num_meshes--;
				Com_Error(ERR_DROP, "Model '%s' has the empty mesh", mod->name);
			}
			continue;
		}
		if (!strcmp(token, "*MESH_NORMALS"))
		{
///			if (!null_mesh[tp])
			if (!nul_mesh)
				num_MESH_NORMALS++;
			continue;
		}
		if (!strcmp(token, "*MESH_TVERTLIST"))
		{
///			if (!null_mesh[tp])
			if (!nul_mesh)
				num_MESH_TVERTLIST++;
			continue;
		}
	}
	if (poutmodel->num_meshes<=0)
		Com_Error (ERR_DROP, "%s has not meshes", mod->name);
	if (poutmodel->num_meshes!=num_MESH_NORMALS)
		Com_Error (ERR_DROP, "%s: one or more meshes has not MESH_NORMALS", mod->name);
	if (poutmodel->num_meshes!=num_MESH_TVERTLIST)
		Com_Error (ERR_DROP, "%s: one or more meshes has not MESH_TVERTLIST", mod->name);

	// считаем количество реальных мешей
	if (poutmodel->num_frames > 1)
	{
		poutmodel->num_meshes = 0;
		buf = (char*)buffer;
		while (buf)
		{
			token = COM_Parse (&buf);
			if (!strcmp(token, "*GEOMOBJECT"))
				poutmodel->num_meshes++;
		}
	}

	// загружаем меши
	int	frm;
	mod->flags = 0;
	vec3_t	tangents[MD3_MAX_VERTS], binormals[MD3_MAX_VERTS];
	float	mat[3][3];
	poutmesh = poutmodel->meshes = (maliasmesh_t*) Hunk_Alloc ( sizeof(maliasmesh_t)*poutmodel->num_meshes, true);
	poutmodel->frames = (maliasframe_t*) Hunk_Alloc ( sizeof(maliasframe_t) * poutmodel->num_frames, true);
	ClearBounds(mod->mins, mod->maxs);
	mesh = -1;
	buf = (char*)buffer;
	tp = 0;
	int	node_skipped = -1;
	while (buf)
	{
		token = COM_Parse(&buf);
		if (!strcmp(token, "*GEOMOBJECT"))		// метка начала меша
		{
///next:	if (null_mesh[tp++])	// empty mesh?
///			{
///				while (buf)			// skip empty mesh
///				{
///					token = COM_Parse(&buf);
///					if (!strcmp(token, "*GEOMOBJECT"))		// метка начала меша
///						goto next;
///				}
///				break;
///			}
			node_skipped = -1;
			mesh++;
			mod->skins[mesh] = NULL;
			Memset(mat, 0, 3*3*sizeof(float));
			continue;
		}
		if (!strcmp(token, "*NODE_TM"))
		{
			node_skipped = 5;	// Считаем что *NODE пройден, когда считан TM_ROW0, TM_ROW1, TM_ROW2, TM_SCALE, NODE_NAME (5 токенов)
			continue;
		}
		if (!strcmp(token, "*TM_ROW0"))		// считываем матрицу поворота
		{
			mat[0][0] = atof(COM_Parse(&buf));
			mat[0][1] = atof(COM_Parse(&buf));
			mat[0][2] = atof(COM_Parse(&buf));
			node_skipped--;
			continue;
		}
		if (!strcmp(token, "*TM_ROW1"))		// считываем матрицу поворота
		{
			mat[1][0] = atof(COM_Parse(&buf));
			mat[1][1] = atof(COM_Parse(&buf));
			mat[1][2] = atof(COM_Parse(&buf));
			node_skipped--;
			continue;
		}
		if (!strcmp(token, "*TM_ROW2"))		// считываем матрицу поворота
		{
			mat[2][0] = atof(COM_Parse(&buf));
			mat[2][1] = atof(COM_Parse(&buf));
			mat[2][2] = atof(COM_Parse(&buf));
			node_skipped--;
			continue;
		}
		if (!strcmp(token, "*TM_SCALE"))		// считываем масштаб матрицы поворота
		{
			vec3_t	mat_scale;
			mat_scale[0] = 1.0 / atof(COM_Parse(&buf));
			mat_scale[1] = 1.0 / atof(COM_Parse(&buf));
			mat_scale[2] = 1.0 / atof(COM_Parse(&buf));
			if (!cache)
			{
				mat[0][0] *= mat_scale[0];
				mat[1][0] *= mat_scale[0];
				mat[2][0] *= mat_scale[0];
				mat[0][1] *= mat_scale[1];
				mat[1][1] *= mat_scale[1];
				mat[2][1] *= mat_scale[1];
				mat[0][2] *= mat_scale[2];
				mat[1][2] *= mat_scale[2];
				mat[2][2] *= mat_scale[2];
			}
			node_skipped--;
			continue;
		}
		if (!strcmp(token, "*NODE_NAME"))		// тут берем имя меша
		{
			node_skipped--;
			if (mesh>=0)
			{
				Memcpy(poutmesh[mesh].name, COM_Parse(&buf), MD3_MAX_PATH);
				continue;
			}
		}
		if (node_skipped == 0)
		{	// прошли *NODE
			node_skipped = -1;
			frm = -1;
			poutvert = NULL;
			poutindex = NULL;
			poutcoord = NULL;
			triangles = NULL;
			if (poutmodel->num_frames > 1)
			{
				while (buf)
				{
					token = COM_Parse(&buf);
					/// *MATERIAL_REF идёт перед *MESH_ANIMATION, поэтому продублируем тут код
					if (!strcmp(token, "*MATERIAL_REF"))	// загружаем текстуру
					{
						int i, j;
						int idx = atoi(COM_Parse(&buf));
						if (idx<0 || idx>=num_materials)
							Com_Error (ERR_DROP, "Bad materialIndex in mesh %s, model %s", poutmesh[mesh].name, mod->name);
						poutmesh[mesh].num_skins = num_skins[idx];
///						poutskin = poutmesh[mesh].skins = (maliasskin_t*) Hunk_Alloc( sizeof(maliasskin_t) * poutmesh[mesh].num_skins, true);
						for (j=0; j<poutmesh[mesh].num_skins; j++/*, poutskin++*/)
						{
							poutmesh[mesh].img_skins[j] = skins[idx][j];
							poutmesh[mesh].img_bumps[j] = bumps[idx][j];
							poutmesh[mesh].img_lights[j] = lights[idx][j];

							// Учтем масштаб и сдвиг текстур
							if (!j)		// for skin #0 only!
							{
								poutcoord = poutmesh[mesh].stcoords;
								for (i=0; i<poutmesh[mesh].num_verts; i++)
								{
									// масштаб
									poutcoord[i].st[0] *= mat_tiles[idx][0];
									poutcoord[i].st[1] *= mat_tiles[idx][1];
									// сдвиг
									poutcoord[i].st[0] += mat_offset[idx][0];
									poutcoord[i].st[1] -= mat_offset[idx][1];
									// отцентрируем вращающуюся текстуру
									if (poutmesh[mesh].img_skins[j]->rotate)
									{
										poutcoord[i].st[0] -= 0.5;
										poutcoord[i].st[1] += 0.5;
									}
								}
							}
						}
						continue;
					}
					if (!strcmp(token, "*MESH_ANIMATION"))		// метка начала анимированного меша
						break;
				}
				continue;
			}
		}
		if (!strcmp(token, "*MESH"))
		{
			frm++;
			poutframe = poutmodel->frames + frm;
			continue;
		}
		if (!strcmp(token, "*MESH_NUMVERTEX"))	// берем количество геометрических вершин
		{
			num_meshVerts = atoi(COM_Parse(&buf));
///			if (num_meshVerts<=0)		// такое устраняется проверкой на empty mesh
///				Com_Error ( ERR_DROP, "mesh %s in model %s has no vertices", poutmesh[mesh].name, mod->name );
///			else
			if (num_meshVerts>MD3_MAX_VERTS)
				Com_Error ( ERR_DROP, "mesh %s in model %s has too many vertices", poutmesh[mesh].name, mod->name );
			continue;
		}
		if (!strcmp(token, "*MESH_NUMFACES"))	// берем количество треугольников
		{
			poutmesh[mesh].num_tris = atoi(COM_Parse(&buf));
			if (poutmesh[mesh].num_tris<=0)
				Com_Error ( ERR_DROP, "mesh %s in model %s has no triangles", poutmesh[mesh].name, mod->name );
			else if (poutmesh[mesh].num_tris>MD3_MAX_TRIANGLES)
				Com_Error ( ERR_DROP, "mesh %s in model %s has too many triangles", poutmesh[mesh].name, mod->name );
			continue;
		}
		if (!strcmp(token, "*MESH_VERTEX_LIST"))	// загружаем координаты геометрических вершин во временный буфер
		{
			while (buf)
			{
				token = COM_Parse(&buf);
				if (*token=='}' && com_token_len == 1)
					break;
				if (!strcmp(token, "*MESH_VERTEX"))
				{
					int idx = atoi(COM_Parse(&buf));
					if (idx<0 || idx>=num_meshVerts)
						Com_Error ( ERR_DROP, "Bad vertexIndex %i in mesh %s, model %s", idx, poutmesh[mesh].name, mod->name );
					meshVerts[idx][0] = atof(COM_Parse(&buf)) * invscale[0];
					meshVerts[idx][1] = atof(COM_Parse(&buf)) * invscale[1];
					meshVerts[idx][2] = atof(COM_Parse(&buf)) * invscale[2];
				}
				else if (*token!='{')
					Com_Error ( ERR_DROP, "unexpected token %s in section *MESH_VERTEX_LIST, mesh %s, model %s", token, poutmesh[mesh].name, mod->name );
			}
			continue;
		}
		if (!strcmp(token, "*MESH_FACE_LIST"))		// загружаем список треугольников:
		{
			if (!poutindex)
				poutmesh[mesh].indexes = (WORD*) Hunk_Alloc( sizeof(WORD) * poutmesh[mesh].num_tris * 3, true);
			poutindex = poutmesh[mesh].indexes;
			while (buf)
			{
				token = COM_Parse(&buf);
				if (*token=='}' && com_token_len == 1)
					break;
				if (!strcmp(token, "*MESH_FACE"))	// загружаем индексы геометрических вершин
				{
					token = COM_Parse(&buf);
					token[strlen(token)-1]=0;		// kill the ':'
					int idx = atoi(token);
					int i;
					if (idx<0 || idx>=poutmesh[mesh].num_tris)
						Com_Error ( ERR_DROP, "Bad triangleIndex %i in MESH_FACE, mesh %s, model %s", idx, poutmesh[mesh].name, mod->name );
					idx*=3;
					for (i=0; i<3; i++)
					{
						COM_Parse(&buf);
						poutindex[idx+i] = atoi(COM_Parse(&buf));
					}
				}
			}
			continue;
		}
		if (!strcmp(token, "*MATERIAL_REF"))	// загружаем текстуру
		{
			int i, j;
			int idx = atoi(COM_Parse(&buf));
			if (idx<0 || idx>=num_materials)
				Com_Error (ERR_DROP, "Bad materialIndex in mesh %s, model %s", poutmesh[mesh].name, mod->name);
			poutmesh[mesh].num_skins = num_skins[idx];
///			poutskin = poutmesh[mesh].skins = (maliasskin_t*) Hunk_Alloc( sizeof(maliasskin_t) * poutmesh[mesh].num_skins, true);
			for (j=0; j<poutmesh[mesh].num_skins; j++/*, poutskin++*/)
			{
				poutmesh[mesh].img_skins[j] = skins[idx][j];
				poutmesh[mesh].img_bumps[j] = bumps[idx][j];
				poutmesh[mesh].img_lights[j] = lights[idx][j];

				// Учтем масштаб и сдвиг текстур
				if (!j)		// for skin #0 only!
				{
					poutcoord = poutmesh[mesh].stcoords;
					for (i=0; i<poutmesh[mesh].num_verts; i++)
					{
						// масштаб
						poutcoord[i].st[0] *= mat_tiles[idx][0];
						poutcoord[i].st[1] *= mat_tiles[idx][1];
						// сдвиг
						poutcoord[i].st[0] += mat_offset[idx][0];
						poutcoord[i].st[1] -= mat_offset[idx][1];
						// отцентрируем вращающуюся текстуру
						if (poutmesh[mesh].img_skins[j]->rotate)
						{
							poutcoord[i].st[0] -= 0.5;
							poutcoord[i].st[1] += 0.5;
						}
					}
				}
			}
			continue;
		}
		if (!strcmp(token, "*MESH_NUMTVERTEX"))		// загружаем кол-во вершин
		{
			poutmesh[mesh].num_verts = atoi(COM_Parse(&buf));
			if (poutmesh[mesh].num_verts<=0)
				Com_Error ( ERR_DROP, "mesh %s in model %s has no vertices", poutmesh[mesh].name, mod->name );
			else if (poutmesh[mesh].num_verts>MD3_MAX_VERTS)
				Com_Error ( ERR_DROP, "mesh %s in model %s has too many vertices", poutmesh[mesh].name, mod->name );
			Memset(tangents, 0, poutmesh[mesh].num_verts*sizeof(vec3_t));
			Memset(binormals, 0, poutmesh[mesh].num_verts*sizeof(vec3_t));
			if (!poutvert)
				poutmesh[mesh].vertexes = (maliasvertex_t*) Hunk_Alloc(poutmodel->num_frames * poutmesh[mesh].num_verts * sizeof(maliasvertex_t), true);
			poutvert = &poutmesh[mesh].vertexes[frm * poutmesh[mesh].num_verts];
			continue;
		}
		if (!strcmp(token, "*MESH_TVERTLIST"))		// загружаем список вершин (S/T координаты текстур)
		{
			if (!poutcoord)
				poutmesh[mesh].stcoords = (maliascoord_t*) Hunk_Alloc( sizeof(maliascoord_t) * poutmesh[mesh].num_verts, true);
			poutcoord = poutmesh[mesh].stcoords;
			while (buf)
			{
				token = COM_Parse(&buf);
				if (*token=='}' && com_token_len == 1)
					break;
				if (!strcmp(token, "*MESH_TVERT"))
				{
					int idx = atoi(COM_Parse(&buf));
					if (idx<0 || idx>=poutmesh[mesh].num_verts)
						Com_Error ( ERR_DROP, "Bad vertexIndex %i in mesh %s, model %s", idx, poutmesh[mesh].name, mod->name );
					poutcoord[idx].st[0] = atof(COM_Parse(&buf));	// S
					poutcoord[idx].st[1] = -atof(COM_Parse(&buf));	// T
					COM_Parse(&buf);								// ignore
				}
				else if (*token!='{')
					Com_Error ( ERR_DROP, "unexpected token %s in section *MESH_TVERTLIST, mesh %s, model %s", token, poutmesh[mesh].name, mod->name );
			}
			continue;
		}
		if (!strcmp(token, "*MESH_TFACELIST"))		// загружаем список индексов вершин для каждого треугольника
		{
			while (buf)
			{
				token = COM_Parse(&buf);
				if (*token=='}' && com_token_len == 1)
					break;
				if (!strcmp(token, "*MESH_TFACE"))
				{
					int idx = atoi(COM_Parse(&buf));
					if (idx<0 || idx>=poutmesh[mesh].num_tris)
						Com_Error ( ERR_DROP, "Bad triangleIndex %i in MESH_TFACE, mesh %s, model %s", idx, poutmesh[mesh].name, mod->name );
					int ix = atoi(COM_Parse(&buf));
					int iy = atoi(COM_Parse(&buf));
					int iz = atoi(COM_Parse(&buf));
					if (ix<0 || ix>=poutmesh[mesh].num_verts || iy<0 || iy>=poutmesh[mesh].num_verts || iz<0 || iz>=poutmesh[mesh].num_verts)
						Com_Error ( ERR_DROP, "Bad vertexIndex %i in mesh %s, model %s", idx, poutmesh[mesh].name, mod->name );
					int ixx = poutindex[idx*3+0];
					int iyy = poutindex[idx*3+1];
					int izz = poutindex[idx*3+2];
					if (invert)									// переиндексируем
					{
						poutindex[idx*3+0] = iz;
						poutindex[idx*3+1] = iy;
						poutindex[idx*3+2] = ix;
					}
					else
					{
						poutindex[idx*3+0] = ix;
						poutindex[idx*3+1] = iy;
						poutindex[idx*3+2] = iz;
					}
					poutvert[ix].point[0] = meshVerts[ixx][0];	// получим XYZ координаты вершин
					poutvert[ix].point[1] = meshVerts[ixx][1];
					poutvert[ix].point[2] = meshVerts[ixx][2];
					poutvert[iy].point[0] = meshVerts[iyy][0];
					poutvert[iy].point[1] = meshVerts[iyy][1];
					poutvert[iy].point[2] = meshVerts[iyy][2];
					poutvert[iz].point[0] = meshVerts[izz][0];
					poutvert[iz].point[1] = meshVerts[izz][1];
					poutvert[iz].point[2] = meshVerts[izz][2];
				}
				else if (*token!='{')
					Com_Error ( ERR_DROP, "unexpected token %s in section *MESH_TFACELIST, mesh %s, model %s", token, poutmesh[mesh].name, mod->name );
			}
			continue;
		}
		if (!strcmp(token, "*MESH_NORMALS"))		// загружаем нормали вершин для каждого треугольника
		{
			int i, j, idx;
			WORD ll;
			while (buf)
			{
				token = COM_Parse(&buf);
				if (*token=='}' && com_token_len == 1)
					break;
				if (!strcmp(token, "*MESH_FACENORMAL"))
				{
					idx = atoi(COM_Parse(&buf));
					if (idx<0 || idx>=poutmesh[mesh].num_tris)
						Com_Error ( ERR_DROP, "Bad triangleIndex %i in MESH_FACENORMAL, mesh %s, model %s", idx, poutmesh[mesh].name, mod->name );
					i = 0;
					continue;
				}
				if (!strcmp(token, "*MESH_VERTEXNORMAL"))
				{
					vec3_t	tmp, vec;
					if (i>=3)
						Com_Error ( ERR_DROP, "Too many vertexes in MESH_NORMALS, mesh %s, model %s", poutmesh[mesh].name, mod->name );
					COM_Parse(&buf);
					tmp[0] = atof(COM_Parse(&buf));
					tmp[1] = atof(COM_Parse(&buf));
					tmp[2] = atof(COM_Parse(&buf));
					vec[0] = mat[0][0]*tmp[0]-mat[0][1]*tmp[1]-mat[0][2]*tmp[2];
					vec[1] = -mat[1][0]*tmp[0]+mat[1][1]*tmp[1]-mat[1][2]*tmp[2];
					vec[2] = -mat[2][0]*tmp[0]-mat[2][1]*tmp[1]+mat[2][2]*tmp[2];
					VectorNormalize(vec);
					int id = poutindex[idx*3+i];
					if (invert)
						vec[1] = -vec[1];
					///					poutvert[id].normal = Normal2Index(vec);
					VectorCopy(vec, poutvert[id].normal);
					i++;
					continue;
				}
			}
			for (i=0; i<poutmesh[mesh].num_tris; i++)
			{
				// переиндексируем
				WORD	tmp;
				tmp = poutindex[i*3+2];
				poutindex[i*3+2] = poutindex[i*3+1];
				poutindex[i*3+1] = tmp;
			}
			for (i=0; i<poutmesh[mesh].num_tris; i++)
			{
				vec3_t tangent;
				vec3_t binormal;
				TangentForTrimd3(&poutmesh[mesh].indexes[i*3], poutmesh[mesh].vertexes, poutmesh[mesh].stcoords, tangent, binormal);
				for (j=0; j<3; j++)
				{
					ll = poutmesh[mesh].indexes[i*3+j];
					VectorAdd(tangents[ll], tangent, tangents[ll]);
					VectorAdd(binormals[ll], binormal, binormals[ll]);
				}
			}
			for (i=0; i<poutmesh[mesh].num_verts; i++)
			{
				VectorNormalize(tangents[i]);
				VectorNormalize(binormals[i]);
				///				poutmesh[mesh].vertexes[i].tangent = Normal2Index(tangents[i]);
				VectorCopy(tangents[i], poutmesh[mesh].vertexes[i].tangent);
				///				poutmesh[mesh].vertexes[i].binormal = Normal2Index(binormals[i]);
				VectorCopy(binormals[i], poutmesh[mesh].vertexes[i].binormal);
				AddPointToBounds(poutmesh[mesh].vertexes[i].point, mod->mins, mod->maxs);
				AddPointToBounds ( poutmesh[mesh].vertexes[i].point, frame_mins[frm], frame_maxs[frm] );
			}

			/// Berserker: сравниваем координаты всех точек меша. Если равны, то складываем tangent/binormal точек.
			for (j = 0; j < poutmesh->num_verts - 1; j++)
			{
				for (int b = j + 1; b < poutmesh->num_verts; b++)
				{
					if (VectorCompareEpsilon(poutvert[j].point, poutvert[b].point, 0.001f))
					{
						if (DotProduct(poutvert[j].normal, poutvert[b].normal) >= 0.9848)	/// cos 10. Если угол меньше 10 градусов, то сглаживаем.
						{
							VectorAdd(poutvert[j].normal, poutvert[b].normal, poutvert[j].normal);
							VectorCopy(poutvert[j].normal, poutvert[b].normal);
							VectorNormalize(poutvert[j].normal);
							VectorNormalize(poutvert[b].normal);

							VectorAdd(poutvert[j].tangent, poutvert[b].tangent, poutvert[j].tangent);
							VectorCopy(poutvert[j].tangent, poutvert[b].tangent);

							VectorAdd(poutvert[j].binormal, poutvert[b].binormal, poutvert[j].binormal);
							VectorCopy(poutvert[j].binormal, poutvert[b].binormal);
						}
					}
				}
			}

			if (!triangles)
				poutmesh[mesh].triangles = (neighbours_t*) Hunk_Alloc ( sizeof(neighbours_t) * poutmesh[mesh].num_tris, true);
			triangles = poutmesh[mesh].triangles;
			if (!r_simple->value)
				R_BuildTriangleNeighborsASE ( triangles, poutindex, poutmesh[mesh].num_tris, poutvert );
			continue;
		}
	}

	if (cache)
	{
		caching_calc = true;
		return;		/// кэширование ASE есть только замер выделения памяти. Далее нет Hunk_Alloc, потому можно выйти.
	}

	for (i=0; i<poutmodel->num_frames; i++)
	{
		poutframe = poutmodel->frames + i;
		VectorCopy(frame_mins[i], poutframe->mins);
		VectorCopy(frame_maxs[i], poutframe->maxs);
		VectorClear(poutframe->translate);
	}

	mod->type = mod_alias_md3;

	char	nam[MAX_OSPATH];
	char	*buff;
	i = strlen(mod->name);
	Memcpy(nam, mod->name, i);
	nam[i-3]='f';
	nam[i-2]='x';
	nam[i-1]='a';
	nam[i]=0;
	// load the .fxa
	i = FS_LoadFile (nam, (void **)&buff);
	InitModLightsEmits(mod);
	if (buff)
	{
		if (i > 0)
		{
			char bak=buff[i];
			buff[i]=0;
			ParseMFX(mod, buff);
			buff[i]=bak;
		}
		Z_Free (buff);
	}

	mod->md3_has_solid_mesh = mod->md3_has_trans_mesh = false;
	poutmesh = poutmodel->meshes;
	for ( i = 0; i < poutmodel->num_meshes; i++, poutmesh++)
	{
		for (int j=0; j<poutmesh->num_skins; j++)
		{
			image_t *skin = poutmesh->img_skins[j];
			if(skin)
			{
				if (skin->Translucent33 || skin->Translucent66)
					mod->md3_has_trans_mesh = true;
				else
					mod->md3_has_solid_mesh = true;
			}
		}
	}

	// если все меши с дистортом, назначим модели флаг RF_DISTORT (ибо так практичнее для оклюжн-теста... нет бага)
	poutmesh = poutmodel->meshes;
	for ( i = 0; i < poutmodel->num_meshes; i++, poutmesh++)
	{
		int	j;
		for (j=0; j<poutmesh->num_skins; j++)
		{
			image_t *skin = poutmesh->img_skins[j];
			if(skin)
			{
				if (skin->fx != fx_distort)
					break;
			}
		}
		if (j < poutmesh->num_skins)
			break;
	}
	if (i == poutmodel->num_meshes)	// все меши с дистортом
		mod->flags |= RF_DISTORT;

/// Calc ASE bounds and radius...
	vec3_t	tempr, tempv;
	tempr[0] = mod->maxs[0] - mod->mins[0];
	tempr[1] = mod->maxs[1] - mod->mins[1];
	tempr[2] = 0;
	tempv[0] = 0;
	tempv[1] = 0;
	tempv[2] = mod->maxs[2] - mod->mins[2];
	poutframe->radius = mod->radius = max(VectorLength(tempr), VectorLength(tempv));

	for(i=0; i<3; i++)
		mod->center[i] = (mod->maxs[i] + mod->mins[i]) * 0.5;

	poutmesh = poutmodel->meshes;
	for ( i = 0; i < poutmodel->num_meshes; i++, poutmesh++)
	{
		poutmesh->vbo_st = NULL;
		if (gl_config.vbo)
		{
			int k, l;
			float *map = (float*) vbo_shadow;	// hack: этот буфер очень большой, поэтому он должен вместить в себя любую модель
			maliascoord_t *coord = poutmesh->stcoords;
			for (k=0, l=0; l<poutmesh->num_verts; l++)
			{
				map[k++] = coord[l].st[0];
				map[k++] = coord[l].st[1];
			}

			if (k>3*MAX_VBO_XYZs)
				Com_Error(ERR_FATAL, "Temporary buffer overflow");

			poutmesh->vbo_st = R_VCLoadData(VBO_STATIC, k*sizeof(float), &vbo_shadow, VBO_STORE_ANY, NULL, NULL);
			GL_BindVBO(NULL);
		}
	}
}


void R_DrawDistortModels()
{
	int	i;

	if (!r_drawentities->value)
		return;
/*
	for (i=0; i<r_newrefdef.num_entities; i++)
	{
		float	x,y,z;
		x = r_newrefdef.entities[i].origin[0] - r_origin[0];
		y = r_newrefdef.entities[i].origin[1] - r_origin[1];
		z = r_newrefdef.entities[i].origin[2] - r_origin[2];
		r_newrefdef.entities[i].dist = x*x+y*y+z*z;
	}	// отсортируем по дальности от камеры
	qsort( r_newrefdef.entities, r_newrefdef.num_entities, sizeof( r_newrefdef.entities[0] ), (int (*)(const void *, const void *))entityDistcmpfnc );
*/
	for (i=0 ; i<r_newrefdef.num_entities ; i++)
	{
		currententity = &r_newrefdef.entities[i];
		currentmodel = currententity->model;

		if (sky_world)
			if (currententity->sky_model != drawing_sky_world)
				continue;

		if (currentmodel)
		{
			switch (currentmodel->type)
			{
				case mod_alias:
					glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 3, 0,0,0,-1);	// ignore alpha from diffuse
					R_DrawDistortAliasModel();
					break;
				case mod_alias_md3:
					glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 3, 0,0,0,-1);	// ignore alpha from diffuse
					R_DrawDistortAliasModelMD3();
					break;
				case mod_sprite:
					glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 3, 0,0,0,0);	// do not ignore alpha from diffuse
					R_DrawDistortSprite();
					break;
				case mod_sprite2:
					glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 3, 0,0,0,0);	// do not ignore alpha from diffuse
					R_DrawDistortSprite2();
					break;
			}
		}
	}
	glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 3, 0,0,0,0);	// do not ignore alpha from diffuse
}


void R_DrawDistorts()
{
	float	scroll, mul;
	float	s, t, rdt = r_newrefdef.time;

	if (r_distort->value)
	{
		if(gl_config.screentexture && gl_config.arb_distort && arbdistort_program!=0xffffffff)
		{
			distort_initialised = false;

			if(r_distort_surfaces)
			{
				msurface_t	*surf;

				if (!distort_initialised)
					Distort_Initialise();

				for (surf=r_distort_surfaces ; surf ; surf=surf->distortchain)
				{
					int			i;
					float		*v;
					glpoly_t	*bp;
					image_t		*image = surf->texinfo->image;

					GL_MBind2D(GL_TEXTURE0, image->fx_image->texnum);
					GL_MBind2D(GL_TEXTURE2, image->texnum);

					float	adds = image->fx_s * cl.leveltime;
					float	addt = image->fx_t * cl.leveltime;

					if(surf->flags & SURF_DISTORTBRUSH)
						glLoadMatrixf (&r_mvm_cache2[surf->mvm_cache_idx2][0]);

					if (surf->texinfo->flags & SURF_FLOWING)
						scroll = -64 * ( (r_newrefdef.time*0.5) - (int)(r_newrefdef.time*0.5) );
					else
						scroll = 0;

					if(surf->flags & SURF_DRAWTURB)
						mul = 0.015625f;
					else
						mul = 1;

					for (bp=surf->polys ; bp ; bp=bp->next)
					{
						glBegin (GL_TRIANGLE_FAN);
						for (i=0, v=bp->verts[0] ; i<bp->numverts ; i++, v+=VERTEXSIZE)
						{
							if (surf->flags & SURF_DRAWTURB)
							{
								s = (v[3] + r_turbsin[Q_ftol( ((v[4]*0.125+rdt) * TURBSCALE) ) & 255] + scroll) * 0.015625f;
								t = (v[4] + r_turbsin[Q_ftol( ((v[3]*0.125+rdt) * TURBSCALE) ) & 255]) * 0.015625f;
							}
							else
							{
								s = v[3];
								t = v[4];
							}

							glTexCoord2f ((v[3]+adds)*image->fx_scale_s, (v[4]+addt)*image->fx_scale_t);
							glMultiTexCoord2fARB (GL_TEXTURE2, (s+scroll)*mul, (t+scroll)*mul);
							glVertex3fv (v);
						}
						glEnd ();
					}

					if(surf->flags & SURF_DISTORTBRUSH)
					{
						glLoadMatrixf (r_world_matrix);
						surf->flags &= ~SURF_DISTORTBRUSH;
					}
				}
			}

			R_DrawDistortModels();

			if (distort_initialised)
				Distort_Shutdown();
		}
	}

	r_distort_surfaces = NULL;
	distortbrush = 0;
}


void	R_DrawCurrentLight(shadowlight_t *light, llink_t *link, float radius)
{
	vec3_t			v[8], rad;
	float			r, g, b;
	clightstyle_t	*ls, *cl_ls;
	vec3_t			start, forward, right, end, tmp;
	bool			rot;

	rot = false;
	if (le_mode)
	{
		ls = &sv_lightstyle[link->style];
		cl_ls = &cl_lightstyle[link->cl_style];
		r = 0.15 + (link->color[0] * ls->val * cl_ls->val) * 0.85;
		g = 0.15 + (link->color[1] * ls->val * cl_ls->val) * 0.85;
		b = 0.15 + (link->color[2] * ls->val * cl_ls->val) * 0.85;

		int			bm, j;
		blink_t		*brush;
		entity_t	*ent;
		vec3_t		org, tempang, up, temp;
		for (bm=0; bm<brushmodel_counter; bm++)
			if (bmdl_list[bm].index == link->index)
					break;
		if (bm < brushmodel_counter)
		{
			brush = &bmdl_list[bm];
			for (j=0; j<cl.refdef.num_entities; j++)
			{
				ent = &cl.refdef.entities[j];
				if (ent->model == cl.model_draw[link->index+1])
					break;
			}
			if (j<cl.refdef.num_entities)
			{
				VectorSubtract(link->origin, brush->origin, org);
				VectorNegate(ent->angles, tempang);
				if(tempang[0] || tempang[1] || tempang[2])
				{
					AngleVectors (tempang, forward, right, up);
					VectorCopy (org, temp);
					org[0] = DotProduct (temp, forward);
					org[1] = -DotProduct (temp, right);
					org[2] = DotProduct (temp, up);
				}
				VectorAdd(link->angles, ent->angles, tempang);
				VectorAdd(org, ent->origin, org);

				if (!VectorCompare(tempang, vec3_origin))
				{
					AngleVectors (tempang, forward, right, NULL);
					rot = true;
				}
				VectorCopy(org, start);

				if (radius)
					VectorSet(rad, radius, radius, radius);
				else
					VectorSet(rad, link->radius, link->radius, link->radius);

				VectorSet(v[0], org[0]-rad[0], org[1]-rad[1], org[2]-rad[2]);
				VectorSet(v[1], org[0]-rad[0], org[1]-rad[1], org[2]+rad[2]);
				VectorSet(v[2], org[0]-rad[0], org[1]+rad[1], org[2]-rad[2]);
				VectorSet(v[3], org[0]-rad[0], org[1]+rad[1], org[2]+rad[2]);
				VectorSet(v[4], org[0]+rad[0], org[1]-rad[1], org[2]-rad[2]);
				VectorSet(v[5], org[0]+rad[0], org[1]-rad[1], org[2]+rad[2]);
				VectorSet(v[6], org[0]+rad[0], org[1]+rad[1], org[2]-rad[2]);
				VectorSet(v[7], org[0]+rad[0], org[1]+rad[1], org[2]+rad[2]);
			}
			else
				return;
		}
		else
			return;
	}
	else
	{
		if (!VectorCompare(light->angles, vec3_origin))
		{
			AngleVectors (light->angles, forward, right, NULL);
			rot = true;
		}
		VectorCopy(light->origin, start);

		ls = &sv_lightstyle[light->style];
		cl_ls = &cl_lightstyle[light->cl_style];
		r = 0.15 + (light->color[0] * ls->val * cl_ls->val) * 0.85;
		g = 0.15 + (light->color[1] * ls->val * cl_ls->val) * 0.85;
		b = 0.15 + (light->color[2] * ls->val * cl_ls->val) * 0.85;

		if (radius)
			VectorSet(rad, radius, radius, radius);
		else
			VectorCopy(light->radiuses, rad);

		VectorSet(v[0], light->origin[0]-rad[0], light->origin[1]-rad[1], light->origin[2]-rad[2]);
		VectorSet(v[1], light->origin[0]-rad[0], light->origin[1]-rad[1], light->origin[2]+rad[2]);
		VectorSet(v[2], light->origin[0]-rad[0], light->origin[1]+rad[1], light->origin[2]-rad[2]);
		VectorSet(v[3], light->origin[0]-rad[0], light->origin[1]+rad[1], light->origin[2]+rad[2]);
		VectorSet(v[4], light->origin[0]+rad[0], light->origin[1]-rad[1], light->origin[2]-rad[2]);
		VectorSet(v[5], light->origin[0]+rad[0], light->origin[1]-rad[1], light->origin[2]+rad[2]);
		VectorSet(v[6], light->origin[0]+rad[0], light->origin[1]+rad[1], light->origin[2]-rad[2]);
		VectorSet(v[7], light->origin[0]+rad[0], light->origin[1]+rad[1], light->origin[2]+rad[2]);
	}

	glDisable (GL_TEXTURE_2D);
	if (radius == 0)		// при рисовании curlight или curlink
	{
		GL_Color4f (r, g, b, 0.25);
		glEnable(GL_BLEND);
		glDisable (GL_CULL_FACE);

		glBegin(GL_TRIANGLE_FAN);
		glVertex3fv(v[4]);
		glVertex3fv(v[0]);
		glVertex3fv(v[1]);
		glVertex3fv(v[5]);
		glVertex3fv(v[7]);
		glVertex3fv(v[6]);
		glVertex3fv(v[2]);
		glVertex3fv(v[0]);
		glEnd();

		glBegin(GL_TRIANGLE_FAN);
		glVertex3fv(v[3]);
		glVertex3fv(v[0]);
		glVertex3fv(v[1]);
		glVertex3fv(v[5]);
		glVertex3fv(v[7]);
		glVertex3fv(v[6]);
		glVertex3fv(v[2]);
		glVertex3fv(v[0]);
		glEnd();
	}

	if(cl.time & 64)
		glDisable(GL_DEPTH_TEST);

	GL_Color3f(1,1,1);

	glBegin(GL_LINE_LOOP);
	glVertex3fv(v[0]);
	glVertex3fv(v[2]);
	glVertex3fv(v[3]);
	glVertex3fv(v[1]);
	glEnd();

	glBegin(GL_LINE_LOOP);
	glVertex3fv(v[4]);
	glVertex3fv(v[6]);
	glVertex3fv(v[7]);
	glVertex3fv(v[5]);
	glEnd();

	glBegin(GL_LINES);
	glVertex3fv(v[0]);
	glVertex3fv(v[4]);
	glVertex3fv(v[1]);
	glVertex3fv(v[5]);
	glVertex3fv(v[2]);
	glVertex3fv(v[6]);
	glVertex3fv(v[3]);
	glVertex3fv(v[7]);
	glVertex3fv(v[3]);
	glVertex3fv(v[4]);
	glVertex3fv(v[1]);
	glVertex3fv(v[6]);
	glVertex3fv(v[0]);
	glVertex3fv(v[7]);
	glVertex3fv(v[2]);
	glVertex3fv(v[5]);
	glEnd();

	if (radius == 0)		// при рисовании curlight или curlink
	{
		if (rot && !trace_light)
		{			// если светильник имеет направление, рисуем стрелочку
			glBegin(GL_LINES);

			VectorMA(start, 32, forward, end);
			glVertex3fv(start);
			glVertex3fv(end);

			VectorMA(end, -5, right, tmp);
			VectorMA(tmp, -8, forward, tmp);
			glVertex3fv(end);
			glVertex3fv(tmp);

			VectorMA(end, 5, right, tmp);
			VectorMA(tmp, -8, forward, tmp);
			glVertex3fv(end);
			glVertex3fv(tmp);

			glEnd();
		}

// рисуем мировую ИСО
		// X axis
		GL_Color3f(1,0,0);
		glBegin(GL_LINES);
		VectorSet(end, start[0] + 16, start[1], start[2]);
		glVertex3fv(start);
		glVertex3fv(end);
		glEnd();
		// Y axis
		GL_Color3f(0,1,0);
		glBegin(GL_LINES);
		VectorSet(end, start[0], start[1] + 16, start[2]);
		glVertex3fv(start);
		glVertex3fv(end);
		glEnd();
		// Z axis
		GL_Color3f(0,0,1);
		glBegin(GL_LINES);
		VectorSet(end, start[0], start[1], start[2] + 16);
		glVertex3fv(start);
		glVertex3fv(end);
		glEnd();
		GL_Color3f(1,1,1);

		glEnable(GL_CULL_FACE);
		glDisable(GL_BLEND);
	}

	if(cl.time & 64)
		glEnable(GL_DEPTH_TEST);

	glEnable (GL_TEXTURE_2D);
}

void R_DrawCurrentRelight()
{
	vec3_t			v[8];
	vec3_t			start, forward, right, end, tmp;

	/// не в тему, но пусть будет тут :))
	if (r_showEFXzones->value)
	{
		int i;
		EFXzone_t	*curZone;
		for (i = 0; i<numEFXzones; i++)
		{
			curZone = &EFXzone_infos[i];

			VectorSet(v[0], curZone->origin[0], curZone->origin[1], curZone->origin[2]);
			VectorSet(v[1], curZone->origin[0], curZone->origin[1], curZone->origin2[2]);
			VectorSet(v[2], curZone->origin[0], curZone->origin2[1], curZone->origin[2]);
			VectorSet(v[3], curZone->origin[0], curZone->origin2[1], curZone->origin2[2]);
			VectorSet(v[4], curZone->origin2[0], curZone->origin[1], curZone->origin[2]);
			VectorSet(v[5], curZone->origin2[0], curZone->origin[1], curZone->origin2[2]);
			VectorSet(v[6], curZone->origin2[0], curZone->origin2[1], curZone->origin[2]);
			VectorSet(v[7], curZone->origin2[0], curZone->origin2[1], curZone->origin2[2]);

			glDisable(GL_TEXTURE_2D);
			GL_Color3f(1, 1, 1);
			if (cl.time & 64)
				glDisable(GL_DEPTH_TEST);

			glBegin(GL_LINE_LOOP);
			glVertex3fv(v[0]);
			glVertex3fv(v[2]);
			glVertex3fv(v[3]);
			glVertex3fv(v[1]);
			glEnd();

			glBegin(GL_LINE_LOOP);
			glVertex3fv(v[4]);
			glVertex3fv(v[6]);
			glVertex3fv(v[7]);
			glVertex3fv(v[5]);
			glEnd();

			glBegin(GL_LINES);
			glVertex3fv(v[0]);
			glVertex3fv(v[4]);
			glVertex3fv(v[1]);
			glVertex3fv(v[5]);
			glVertex3fv(v[2]);
			glVertex3fv(v[6]);
			glVertex3fv(v[3]);
			glVertex3fv(v[7]);
			glVertex3fv(v[3]);
			glVertex3fv(v[4]);
			glVertex3fv(v[1]);
			glVertex3fv(v[6]);
			glVertex3fv(v[0]);
			glVertex3fv(v[7]);
			glVertex3fv(v[2]);
			glVertex3fv(v[5]);
			glEnd();

			if (cl.time & 64)
				glEnable(GL_DEPTH_TEST);
			glEnable(GL_TEXTURE_2D);
		}
	}


	if (!r_editor->value)
		return;

	if ((curlight && !le_mode) || (curlink && le_mode))
		R_DrawCurrentLight(curlight, curlink, 0);

/*
/// draw current decal
	if (curdecal)
	{						/// Эта функция делегирована R_DrawDecals  %)
	}
*/

/// draw current fog
	if (curfog)
	{
		VectorSet(v[0], curfog->origin[0], curfog->origin[1], curfog->origin[2]);
		VectorSet(v[1], curfog->origin[0], curfog->origin[1], curfog->origin2[2]);
		VectorSet(v[2], curfog->origin[0], curfog->origin2[1], curfog->origin[2]);
		VectorSet(v[3], curfog->origin[0], curfog->origin2[1], curfog->origin2[2]);
		VectorSet(v[4], curfog->origin2[0], curfog->origin[1], curfog->origin[2]);
		VectorSet(v[5], curfog->origin2[0], curfog->origin[1], curfog->origin2[2]);
		VectorSet(v[6], curfog->origin2[0], curfog->origin2[1], curfog->origin[2]);
		VectorSet(v[7], curfog->origin2[0], curfog->origin2[1], curfog->origin2[2]);

		glDisable (GL_TEXTURE_2D);
		GL_Color3fv(curfog->color);
		if(cl.time & 64)
			glDisable(GL_DEPTH_TEST);

		glBegin(GL_LINE_LOOP);
		glVertex3fv(v[0]);
		glVertex3fv(v[2]);
		glVertex3fv(v[3]);
		glVertex3fv(v[1]);
		glEnd();

		glBegin(GL_LINE_LOOP);
		glVertex3fv(v[4]);
		glVertex3fv(v[6]);
		glVertex3fv(v[7]);
		glVertex3fv(v[5]);
		glEnd();

		glBegin(GL_LINES);
		glVertex3fv(v[0]);
		glVertex3fv(v[4]);
		glVertex3fv(v[1]);
		glVertex3fv(v[5]);
		glVertex3fv(v[2]);
		glVertex3fv(v[6]);
		glVertex3fv(v[3]);
		glVertex3fv(v[7]);
		glVertex3fv(v[3]);
		glVertex3fv(v[4]);
		glVertex3fv(v[1]);
		glVertex3fv(v[6]);
		glVertex3fv(v[0]);
		glVertex3fv(v[7]);
		glVertex3fv(v[2]);
		glVertex3fv(v[5]);
		glEnd();

		if(cl.time & 64)
			glEnable(GL_DEPTH_TEST);
		glEnable (GL_TEXTURE_2D);
	}

	/// draw current EFXzone
	if (curEFXzone)
	{
		VectorSet(v[0], curEFXzone->origin[0], curEFXzone->origin[1], curEFXzone->origin[2]);
		VectorSet(v[1], curEFXzone->origin[0], curEFXzone->origin[1], curEFXzone->origin2[2]);
		VectorSet(v[2], curEFXzone->origin[0], curEFXzone->origin2[1], curEFXzone->origin[2]);
		VectorSet(v[3], curEFXzone->origin[0], curEFXzone->origin2[1], curEFXzone->origin2[2]);
		VectorSet(v[4], curEFXzone->origin2[0], curEFXzone->origin[1], curEFXzone->origin[2]);
		VectorSet(v[5], curEFXzone->origin2[0], curEFXzone->origin[1], curEFXzone->origin2[2]);
		VectorSet(v[6], curEFXzone->origin2[0], curEFXzone->origin2[1], curEFXzone->origin[2]);
		VectorSet(v[7], curEFXzone->origin2[0], curEFXzone->origin2[1], curEFXzone->origin2[2]);

		glDisable(GL_TEXTURE_2D);
		GL_Color3f(1,1,1);
		if (cl.time & 64)
			glDisable(GL_DEPTH_TEST);

		glBegin(GL_LINE_LOOP);
		glVertex3fv(v[0]);
		glVertex3fv(v[2]);
		glVertex3fv(v[3]);
		glVertex3fv(v[1]);
		glEnd();

		glBegin(GL_LINE_LOOP);
		glVertex3fv(v[4]);
		glVertex3fv(v[6]);
		glVertex3fv(v[7]);
		glVertex3fv(v[5]);
		glEnd();

		glBegin(GL_LINES);
		glVertex3fv(v[0]);
		glVertex3fv(v[4]);
		glVertex3fv(v[1]);
		glVertex3fv(v[5]);
		glVertex3fv(v[2]);
		glVertex3fv(v[6]);
		glVertex3fv(v[3]);
		glVertex3fv(v[7]);
		glVertex3fv(v[3]);
		glVertex3fv(v[4]);
		glVertex3fv(v[1]);
		glVertex3fv(v[6]);
		glVertex3fv(v[0]);
		glVertex3fv(v[7]);
		glVertex3fv(v[2]);
		glVertex3fv(v[5]);
		glEnd();

		if (cl.time & 64)
			glEnable(GL_DEPTH_TEST);
		glEnable(GL_TEXTURE_2D);
	}

	/// draw current emit
	if (curemit)
	{
		VectorSet(v[0], curemit->origin[0]-16, curemit->origin[1]-16, curemit->origin[2]-16);
		VectorSet(v[1], curemit->origin[0]-16, curemit->origin[1]-16, curemit->origin[2]+16);
		VectorSet(v[2], curemit->origin[0]-16, curemit->origin[1]+16, curemit->origin[2]-16);
		VectorSet(v[3], curemit->origin[0]-16, curemit->origin[1]+16, curemit->origin[2]+16);
		VectorSet(v[4], curemit->origin[0]+16, curemit->origin[1]-16, curemit->origin[2]-16);
		VectorSet(v[5], curemit->origin[0]+16, curemit->origin[1]-16, curemit->origin[2]+16);
		VectorSet(v[6], curemit->origin[0]+16, curemit->origin[1]+16, curemit->origin[2]-16);
		VectorSet(v[7], curemit->origin[0]+16, curemit->origin[1]+16, curemit->origin[2]+16);

		glDisable (GL_TEXTURE_2D);
		GL_Color3f(0,1,1);
		if(cl.time & 64)
			glDisable(GL_DEPTH_TEST);

		glBegin(GL_LINE_LOOP);
		glVertex3fv(v[0]);
		glVertex3fv(v[2]);
		glVertex3fv(v[3]);
		glVertex3fv(v[1]);
		glEnd();

		glBegin(GL_LINE_LOOP);
		glVertex3fv(v[4]);
		glVertex3fv(v[6]);
		glVertex3fv(v[7]);
		glVertex3fv(v[5]);
		glEnd();

		glBegin(GL_LINES);
		glVertex3fv(v[0]);
		glVertex3fv(v[4]);
		glVertex3fv(v[1]);
		glVertex3fv(v[5]);
		glVertex3fv(v[2]);
		glVertex3fv(v[6]);
		glVertex3fv(v[3]);
		glVertex3fv(v[7]);
		glVertex3fv(v[3]);
		glVertex3fv(v[4]);
		glVertex3fv(v[1]);
		glVertex3fv(v[6]);
		glVertex3fv(v[0]);
		glVertex3fv(v[7]);
		glVertex3fv(v[2]);
		glVertex3fv(v[5]);

		if (!VectorCompare(curemit->angles, vec3_origin))
		{
			if (curemit->label[0] != 0)
			{
				int			bm, j;
				blink_t		*brush;
				entity_t	*ent;
				vec3_t		tempang;
				for (bm=0; bm<brushmodel_counter; bm++)
					if (bmdl_list[bm].index == curemit->index)
							break;
				if (bm < brushmodel_counter)
				{
					brush = &bmdl_list[bm];
					for (j=0; j<cl.refdef.num_entities; j++)
					{
						ent = &cl.refdef.entities[j];
						if (ent->model == cl.model_draw[curemit->index+1])
							break;
					}
					if (j<cl.refdef.num_entities)
					{
////						VectorAdd(curemit->angles, ent->angles, tempang);
							tempang[0] = ent->angles[0] + curemit->angles[0];
							tempang[1] = ent->angles[1] + curemit->angles[1];
							tempang[2] = ent->angles[2];
						AngleVectors (tempang, forward, right, NULL);
					}
				}
			}
			else
				AngleVectors (curemit->angles, forward, right, NULL);

			VectorCopy(curemit->origin, start);

			VectorMA(start, 32, forward, end);
			glVertex3fv(start);
			glVertex3fv(end);

			VectorMA(end, -5, right, tmp);
			VectorMA(tmp, -8, forward, tmp);
			glVertex3fv(end);
			glVertex3fv(tmp);

			VectorMA(end, 5, right, tmp);
			VectorMA(tmp, -8, forward, tmp);
			glVertex3fv(end);
			glVertex3fv(tmp);
		}

		glEnd();

// рисуем мировую ИСО
		// X axis
		GL_Color3f(1,0,0);
		glBegin(GL_LINES);
		VectorSet(end, curemit->origin[0] + 16, curemit->origin[1], curemit->origin[2]);
		glVertex3fv(curemit->origin);
		glVertex3fv(end);
		glEnd();
		// Y axis
		GL_Color3f(0,1,0);
		glBegin(GL_LINES);
		VectorSet(end, curemit->origin[0], curemit->origin[1] + 16, curemit->origin[2]);
		glVertex3fv(curemit->origin);
		glVertex3fv(end);
		glEnd();
		// Z axis
		GL_Color3f(0,0,1);
		glBegin(GL_LINES);
		VectorSet(end, curemit->origin[0], curemit->origin[1], curemit->origin[2] + 16);
		glVertex3fv(curemit->origin);
		glVertex3fv(end);
		glEnd();
		GL_Color3f(1,1,1);

		if(cl.time & 64)
			glEnable(GL_DEPTH_TEST);
		glEnable (GL_TEXTURE_2D);
	}

	if (curmodel)
	{
// рисуем мировую ИСО
		glDisable (GL_TEXTURE_2D);
		VectorCopy(curmodel->origin, start);
		if(cl.time & 64)
			glDisable(GL_DEPTH_TEST);
		// X axis
		GL_Color3f(1,0,0);
		glBegin(GL_LINES);
		VectorSet(end, start[0] + 16, start[1], start[2]);
		glVertex3fv(start);
		glVertex3fv(end);
		glEnd();
		// Y axis
		GL_Color3f(0,1,0);
		glBegin(GL_LINES);
		VectorSet(end, start[0], start[1] + 16, start[2]);
		glVertex3fv(start);
		glVertex3fv(end);
		glEnd();
		// Z axis
		GL_Color3f(0,0,1);
		glBegin(GL_LINES);
		VectorSet(end, start[0], start[1], start[2] + 16);
		glVertex3fv(start);
		glVertex3fv(end);
		glEnd();

		GL_Color3f(1,1,1);
		glEnable (GL_TEXTURE_2D);
		if(cl.time & 64)
			glEnable(GL_DEPTH_TEST);
	}
}


char *ParseSexedMFX(char *s)
{
	char	*token;

	while (s)
	{
		token = COM_Parse (&s);
		if (!Q_strcasecmp(token, "gender"))
			return COM_Parse(&s);
	}

	return NULL;
}


void CL_FixUpGender()
{
///	if (gender_auto->value)
	{
		if (cl_forcemymodel->value)
			cl_forcemymodel_modified = true;

///		if (gender->modified)
///		{
///			// was set directly, don't override the user
///			gender->modified = false;
///			return;
///		}

		char	nam[MAX_OSPATH];
		char	*buff, *sex = NULL;
		Memcpy(gender_model, skin->string, MAX_QPATH-1);
		gender_model[MAX_QPATH-1]=0;
		char *p = strchr(gender_model, '/');
		if (p)
			*p = 0;
		/// Berserker: читаем пол игрока из players/<model>/tris.fx2 из токена gender
		/// (даже если и сделают модели игроков в md3 формате, продолжаем читать из fx2)
		Com_sprintf (nam, sizeof(nam), "players/%s/tris.fx2", gender_model);
		// load the .fx2
		int i = FS_LoadFile (nam, (void **)&buff);
		if (buff)
		{
			if (i>0)
			{
				char bak=buff[i];
				buff[i]=0;
				sex = ParseSexedMFX(buff);
				buff[i]=bak;
			}
			Z_Free (buff);
		}

		if (sex)
			Cvar_ForceSet ("gender", sex);
		else
		{	// если не определили пол, назначаем старым способом...
			strncpy(nam, skin->string, sizeof(nam) - 1);
			if ((p = strchr(nam, '/')) != NULL)
				*p = 0;
			if (Q_strcasecmp(nam, "male") == 0)
				Cvar_ForceSet ("gender", "male");
			else if (Q_strcasecmp(nam, "female") == 0)
				Cvar_ForceSet ("gender", "female");
/*			else if (Q_strcasecmp(nam, "cyborg") == 0)	// Fixed by Berserker: не было поддержки Cyborg
				Cvar_ForceSet ("gender", "cyborg");
			else if (Q_strcasecmp(nam, "quakeguy") == 0)	// Added by Berserker: поддержка QuakeGuy
				Cvar_ForceSet ("gender", "quakeguy");
*/			else
				Cvar_ForceSet ("gender", "none");
		}
///		gender->modified = false;

		Com_sprintf (nam, sizeof(nam), "players/%s/tris.md2", gender_model);
		gender_mdl = R_RegisterModel(nam, 1, false);
		Com_sprintf (nam, sizeof(nam), "players/%s", skin->string);
		gender_skin = R_RegisterSkin(nam);
		if (gender_skin)
		{
			gender_bump = R_RegisterBump(nam, gender_skin, gender_mdl);
			gender_light = R_RegisterLight(nam);
		}
	}
}


void R_DrawFlare(flare_t *l, int num_flares_)
{
	int			i, j, k;
	float		a, d, dist, intensity;
	vec3_t		v, n;
	msurface_t	*s = l->surf;

	if (s->ent)
	{
		if (!VectorCompare(s->ent->angles, vec3_origin))
			return;		// Не будем рисовать фларезы для вращающихся браш-моделей
		glPushMatrix ();
		glTranslatef (s->ent->origin[0],  s->ent->origin[1],  s->ent->origin[2]);
	}

	int	segs = r_flares_segs->value;
	if (segs<8)
		segs = 8;
	else if (segs>16)
		segs = 16;
	float		inv_segs = M_PI * 2 / float(segs);

	if (num_flares_<3)
		num_flares_=3;
	else if (num_flares_>8)
		num_flares_=8;
	intensity = r_flares_intensity->value;
	if (intensity>1)
		intensity=1;
	else if (intensity<0.1)
		intensity=0.1;
	float		inv_num_flares = 1.0 / float(num_flares_) * intensity;

	if (s->flags & SURF_PLANEBACK)
	{
		VectorNegate(s->plane->normal, n);
		d = -s->plane->dist;
	}
	else
	{
		VectorCopy(s->plane->normal, n);
		d = s->plane->dist;
	}

	bool chet = false;
	for (k=0; k<num_flares_; k++)
	{
		float	size;
		vec3_t	center, color;

		VectorMA(s->center, k*8, n, center);
		size = l->size + k*4;
		VectorScale(s->texinfo->image->color, float(num_flares_-k)*inv_num_flares, color);
		chet = !chet;

		glBegin (GL_TRIANGLE_FAN);

			GL_Color3fv(color);
			glVertex3fv (s->center);

			GL_Color3fv(vec3_origin);
			for (i=segs ; i>=0 ; i--)
			{
				if (chet)
					a = (cl.leveltime*0.3 + i) * inv_segs;
				else
					a = (-cl.leveltime*0.3 + i) * inv_segs;
				float s, c;
				SinCos(a, &s, &c);
				for (j=0 ; j<3 ; j++)
					v[j] = center[j] + (vright[j]*c + vup[j]*s)*size;
				dist = DotProduct(v, n) - d;
				if (dist<0)
					VectorMA(v, -dist, n, v);
				glVertex3fv (v);
			}

		glEnd ();
	}

	if (s->ent)
		glPopMatrix ();
}


void R_DrawFlares()
{
	int	i;

	if (r_flares->value<=0)
		return;

	if (!num_flares)
		return;

	glDisable (GL_TEXTURE_2D);
	glEnable (GL_BLEND);
	GL_BlendFunc (GL_ONE, GL_ONE);
///	glEnable(GL_POLYGON_OFFSET_FILL);
	glDepthMask( 0 );

	flare_t	*l = flares;
	for (i=0; i<num_flares ; i++, l++)
		if (l->surf->visframe == r_framecount)
			R_DrawFlare (l, r_flares->value);

	GL_Color3f (1,1,1);
	glEnable (GL_TEXTURE_2D);
	glDisable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
///	glDisable(GL_POLYGON_OFFSET_FILL);
}


void R_InitMirrorChains()
{
	int		i;
	for (i=0; i<MAX_MIRRORS; i++)
	{
		mirrors[i].texture = GL_LoadPic (va("Mirror %i",i), NULL, (byte*)trans, gl_config.mirrorTextureSize, gl_config.mirrorTextureSize, it_pic, 32, true, 0);	/// динамическую текстуру НЕ сжимаем!!! Иначе тормоза!
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gl_edge_type );
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gl_edge_type );
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		if (gl_config.occlusion)
		{
			GLuint	occid;
			glGenQueriesARB(1, &occid);
			mirrors[i].occID = -occid;		// метим, что данный тест еще не может получить результат
		}
	}
}


void R_ClearMirrorChains(bool full_clear)
{
	int i;
	for (i=0; i<MAX_MIRRORS; i++)
	{
		mirrors[i].chain = NULL;
		mirrors[i].lockframe = 0;
		if (full_clear)
		{
			mirrors[i].updateframe = 0;
			if (mirrors[i].occID > 0)
				mirrors[i].occID = -mirrors[i].occID;
		}
	}
}


void R_AddMirror(msurface_t *surf)
{
	float t = fabs(surf->plane->normal[2]);
	if (t>0.01 && t<0.99)		// Не добавляем в зеркала наклонные поверхности
		return;

	int			i, oldest, oindex;
///	vec3_t		center = {0,0,0};
	mirror_t	*mir = NULL;

	//see if surface is part of an existing mirror
	for (i=0; i<MAX_MIRRORS; i++)
	{
		if	((surf->plane->dist == mirrors[i].plane.dist) &&
			VectorCompare(surf->plane->normal, mirrors[i].plane.normal) &&
			(bool(surf->flags & SURF_PLANEBACK) == mirrors[i].planeback))
		{
			mir = &mirrors[i];
			break;
		}
	}

	//add new mirror
	if (!mir)
	{
		oldest = occ_framecount;
		oindex = -1;
		for (i=0; i<MAX_MIRRORS; i++)
			if (mirrors[i].lockframe < oldest)
			{
				oldest = mirrors[i].lockframe;
				oindex = i;
			}

		if (oindex == -1)
		{
			Com_DPrintf("R_AddMirror: more than %i mirrors!\n", MAX_MIRRORS);
			return;
		}

		mir = &mirrors[oindex];
		VectorCopy(surf->plane->normal, mir->plane.normal);
		mir->plane.dist = surf->plane->dist;
		mir->plane.type = surf->plane->type;
		mir->plane.signbits = surf->plane->signbits;
		mir->planeback = (surf->flags & SURF_PLANEBACK);
	}

	float ad = fabs(DotProduct (r_origin, mir->plane.normal) - mir->plane.dist);
	//camera is too far away from mirror don't update it...
	if (ad > r_mirror_distance->value)
	{
qqq:	mir->chain = NULL;		// kill the mirror
		return;
	}

	if (ad * inv_mirror_distance < blend_mirror)
		mir->alpha = 1.0;
	else
		mir->alpha = (r_mirror_distance->value - ad) * inv_mirror_distance * blend_mirror1;

	if (mir->alpha < 0.1)	// very faint mirror
		goto qqq;

	mir->lockframe = r_framecount;
	surf->mirrorchain = mir->chain;
	mir->chain = surf;
}


/*
=============
R_Mirror
=============
*/
void R_Mirror (mirror_t *mir)
{
	refdef_t	refdef, oldrefdef;
	vec3_t		real_vieworg, mir_sun_origin;

	Memcpy(&oldrefdef, &r_newrefdef, sizeof(refdef_t));
	Memcpy(&refdef, &r_newrefdef, sizeof(refdef_t));
	R_SaveRenderState();
///	r_finish->value = 0;

	mirror_plane = mir;

	VectorCopy(r_origin, real_vieworg);
	//setup mirrored view origin & direction
	float d = DotProduct (r_origin, mir->plane.normal) - mir->plane.dist;
	VectorMA (r_origin, -2*d, mir->plane.normal, r_origin);

	if (d < 0)
		mirror_clipside = 1;
	else
		mirror_clipside = 2;

	float ds = DotProduct (vpn, mir->plane.normal);
	VectorMA (vpn, -2*ds, mir->plane.normal, vpn);

	float	tmp = 180.0 / M_PI;
	refdef.viewangles[0] = -asin (vpn[2])*tmp;
	refdef.viewangles[1] = atan2 (vpn[1], vpn[0])*tmp;
	refdef.viewangles[2] = -refdef.viewangles[2];

	//Render to a small view rectangle
	refdef.x = 0;
	refdef.y = 0;
	refdef.width = gl_config.mirrorTextureSize;
	refdef.height = gl_config.mirrorTextureSize;
	VectorCopy(r_origin, refdef.vieworg);

	ds = DotProduct(sun_origin, mir->plane.normal) - mir->plane.dist;
	if (ds*d <= 0)	// солнце и камера по разные стороны зеркала.
		mir_draw_sun = false;
	else
	{
		VectorMA (sun_origin, -2*ds, mir->plane.normal, mir_sun_origin);
		trace_t trace = CL_PMTraceWorld (real_vieworg, vec3_origin, vec3_origin, mir_sun_origin, MASK_SOLID);
		if (trace.fraction > 0 && trace.fraction < 1)
		{
			ds = DotProduct(trace.endpos, mir->plane.normal) - mir->plane.dist;
			if (ds*d < 0)	// точка пересечения с SOLID и камера по разные стороны зеркала.
				mir_draw_sun = true;
			else
				mir_draw_sun = false;
		}
		else
			mir_draw_sun = true;
	}

	glFrontFace (GL_CW);
	R_RenderView (&refdef, true);
	glFrontFace (GL_CCW);

	GL_Bind2D (mir->texture->texnum);
	glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, 0, 0, gl_config.mirrorTextureSize, gl_config.mirrorTextureSize);
	glEnable( GL_SCISSOR_TEST );
	glScissor( 0, 0, gl_config.mirrorTextureSize, gl_config.mirrorTextureSize );
	glClear (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	glDisable( GL_SCISSOR_TEST );

	R_RestoreRenderState();
	Memcpy(&r_newrefdef, &oldrefdef, sizeof(refdef_t));
}


bool R_RenderMirrors()
{
	int		i, skip;

	bool	mirr = false;
	if (r_mirror_frameskip->value < 0)
		skip = -lastfps / r_mirror_frameskip->value;
	else
		skip = r_mirror_frameskip->value;

	for (i=0; i<MAX_MIRRORS; i++)
		if (mirrors[i].chain && ((occ_framecount - mirrors[i].updateframe) >= skip))
		{
			if (!gl_config.occlusion)
				mirrors[i].occResult = true;
			else
			{
				if (mirrors[i].occID < 0)
				{
					mirrors[i].occID = -mirrors[i].occID;
					mirrors[i].occResult = true;
				}
				else
				{	// get occlusion test result
					GLuint	result;
					glGetQueryObjectuivARB(mirrors[i].occID, GL_QUERY_RESULT_ARB, &result);
					mirrors[i].occResult = result ? true : false;
				}
			}
			if (mirrors[i].occResult)
			{
				R_Mirror(&mirrors[i]);
				mirrors[i].lockframe = mirrors[i].updateframe = occ_framecount;
				mirr = true;
			}
			else
				c_no_mirrors++;
		}

	return mirr;
}


void EmitMirrorWaterPolys (msurface_t *fa, float intens)
{
	glpoly_t	*p;
	float		*v, os, ot, s, t, rdt;
	int			i;
	vec3_t		ray;

	rdt = r_newrefdef.time;
	intens *= 0.9;		// убавим максимальную интенсивность зеркала

	for (p=fa->polys ; p ; p=p->next)
	{
		glBegin (GL_TRIANGLE_FAN);
		for (i=0, v=p->verts[0] ; i<p->numverts ; i++, v+= VERTEXSIZE)
		{
			// вычисляем затухание зеркала в зависимости от угла зрения (Френель?)
			ray[0] = v[0] - r_origin[0];
			ray[1] = v[1] - r_origin[1];
			ray[2] = v[2] - r_origin[2];
			VectorNormalize(ray);
			GL_Color4f (1, 1, 1, (1 - fabs(DotProduct(ray, fa->plane->normal))) * intens);

			os = v[3];
			ot = v[4];

			s = (os + r_turbsin[Q_ftol( ((ot*0.125+rdt) * TURBSCALE) ) & 255]) * 0.015625f;
			t = (ot + r_turbsin[Q_ftol( ((os*0.125+rdt) * TURBSCALE) ) & 255]) * 0.015625f;

			glMultiTexCoord2fARB(GL_TEXTURE1, s, t);
			glVertex3fv(v);
		}
		glEnd ();
	}
}


void EmitMirrorPolys (msurface_t *fa)
{
	glpoly_t	*p;
	float		*v;
	int			i;

	for (p=fa->polys ; p ; p=p->next)
	{
		glBegin (GL_TRIANGLE_FAN);
		for (i=0, v=p->verts[0] ; i<p->numverts ; i++, v+= VERTEXSIZE)
		{
			glMultiTexCoord2fARB(GL_TEXTURE1, v[3], v[4]);
			glVertex3fv(v);
		}
		glEnd ();
	}
}


void R_DrawMirrorSurfaces(bool trans)
{
	int			i;
	msurface_t	*s;
	float		/*refl,*/ intens;

///	if (trans)
///	{
///		refl = r_mirror_trans->value;
///		if (refl<=0 || refl>1)
///			refl = 0.5;
///	}
///	else
///		refl = 1;

	glEnable(GL_BLEND);

	GL_SelectTexture(GL_TEXTURE0);
	gl_TexEnv (GL_MODULATE);
	glPushMatrix();
	glLoadIdentity();
	glTexGenfv(GL_S, GL_EYE_PLANE, s___);
	glTexGenfv(GL_T, GL_EYE_PLANE, t___);
	glTexGenfv(GL_R, GL_EYE_PLANE, r___);
	glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
	glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
	glTexGenf(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
	glEnable(GL_TEXTURE_GEN_S);
	glEnable(GL_TEXTURE_GEN_T);
	glEnable(GL_TEXTURE_GEN_R);
	glPopMatrix();
	glMatrixMode(GL_TEXTURE);
	glLoadMatrixf(r_texture_matrix);
	glMatrixMode(GL_MODELVIEW);

	GL_SelectTexture(GL_TEXTURE1);
	glEnable(GL_TEXTURE_2D);
/*
	gl_TexEnv (GL_COMBINE_ARB);
	GL_TexEnv (GL_COMBINE_RGB_ARB, GL_INTERPOLATE_ARB);
	GL_TexEnv (GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
	GL_TexEnv (GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
	GL_TexEnv (GL_SOURCE1_RGB_ARB, GL_CONSTANT_ARB);
	GL_TexEnv (GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
	GL_TexEnv (GL_SOURCE2_RGB_ARB, GL_TEXTURE);
	GL_TexEnv (GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA);
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, vec3_origin);
*/
	gl_TexEnv (GL_COMBINE_ARB);
	GL_TexEnv (GL_COMBINE_RGB_ARB, GL_INTERPOLATE_ARB);
	GL_TexEnv (GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
	GL_TexEnv (GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
	GL_TexEnv (GL_SOURCE1_RGB_ARB, GL_CONSTANT_ARB);
	GL_TexEnv (GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
	GL_TexEnv (GL_SOURCE2_RGB_ARB, GL_TEXTURE);
	GL_TexEnv (GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA);
	GL_TexEnv (GL_COMBINE_ALPHA_ARB, GL_REPLACE);
	GL_TexEnv (GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB);
	GL_TexEnv (GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, vec3_origin);

	for (i=0; i<MAX_MIRRORS; i++)
	{
		s = mirrors[i].chain;
		if(s)
		{
			bool res = (s->texinfo->flags & (SURF_TRANS33|SURF_TRANS66));
			if (trans == res)
			{
				// begin occlusion test
				if (gl_config.occlusion)
					glBeginQueryARB(gl_config.occlusion, mirrors[i].occID);

				intens = /*refl**/mirrors[i].alpha;
				GL_MBind2D (GL_TEXTURE0, mirrors[i].texture->texnum);
				for ( ; s ; s=s->mirrorchain)
				{
					GL_MBind2D (GL_TEXTURE1, s->texinfo->image->texnum);
					if (s->flags & SURF_DRAWTURB)
					{
						GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
						EmitMirrorWaterPolys (s, intens);
					}
					else
					{
						GL_Color3f (intens, intens, intens);
						GL_BlendFunc (GL_ONE, GL_ONE);
						EmitMirrorPolys (s);
					}
				}

				// end occlusion test
				if (gl_config.occlusion)
					glEndQueryARB(gl_config.occlusion);
			}
		}
	}

	GL_SelectTexture(GL_TEXTURE1);
	glDisable(GL_TEXTURE_2D);
	GL_SelectTexture(GL_TEXTURE0);
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);

	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);

	GL_Color3f (1,1,1);
	glDisable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


void Clear_NoUses()
{
	int		i;

	if (!r_drawentities->value)
		return;

	for (i=0 ; i<r_newrefdef.num_entities ; i++)
		r_newrefdef.entities[i].flags &= ~RF_NOUSE;
}


void R_SetupMirrorFrame(byte *vis)
{
	msurface_t	*s;
	mnode_t			*node;
	mleaf_t			*leaf;
	int				i, j, cluster;

	int longs = (r_worldmodel->numleafs+31)>>5;
	AngleVectors (r_newrefdef.viewangles, vpn, vright, vup);

	Memcpy(vis, &viewvis, longs<<2);
	Memcpy(&viewvis, CM_ClusterPVS(CM_LeafCluster(CM_PointLeafnum(mirror_plane->chain->center))), longs<<2);

	for (s = mirror_plane->chain->mirrorchain ; s ; s=s->mirrorchain)
	{
		byte *tempvis = CM_ClusterPVS(CM_LeafCluster(CM_PointLeafnum(s->center)));
		for (j=0 ; j<longs ; j++)
			((long *)viewvis)[j] |= ((long *)tempvis)[j];
	}

	r_visframecount++;
	for (i=0,leaf=r_worldmodel->leafs ; i<r_worldmodel->numleafs ; i++, leaf++)
	{
		cluster = leaf->cluster;
		if (cluster == -1)
			continue;
		if (viewvis[cluster>>3] & (1<<(cluster&7)))
		{
			node = (mnode_t *)leaf;
			do
			{
				if (node->visframe == r_visframecount)
					break;
				node->visframe = r_visframecount;
				node = node->parent;
			} while (node);
		}
	}
}


void R_RestoreFrame(byte *vis)
{
	mnode_t			*node;
	mleaf_t			*leaf;
	int				i, cluster;

	int longs = (r_worldmodel->numleafs+31)>>5;
	Memcpy(&viewvis, vis, longs<<2);

	r_visframecount++;
	for (i=0,leaf=r_worldmodel->leafs ; i<r_worldmodel->numleafs ; i++, leaf++)
	{
		cluster = leaf->cluster;
		if (cluster == -1)
			continue;
		if (viewvis[cluster>>3] & (1<<(cluster&7)))
		{
			node = (mnode_t *)leaf;
			do
			{
				if (node->visframe == r_visframecount)
					break;
				node->visframe = r_visframecount;
				node = node->parent;
			} while (node);
		}
	}
}


void R_RenderShadowVolumes(bool SelfShadow, bool all)
{
	if (!currentshadowlight->isStatic && !dl_shadows->value)
		return;

//	if (gl_config.arb_multisample)				 НЕ ВКЛЮЧАТЬ!  ВЫЗОВЕТ АРТЕФАКТЫ ТИПА Z-FIGHT
//		glDisable(GL_MULTISAMPLE_ARB);

	glDepthFunc (GL_LESS);
	glDisable(GL_TEXTURE_2D);	// Turn off texturing...
	glColorMask(0, 0, 0, 0);

	if(r_faststencil->value && (gl_config.ati_separate_stencil || gl_config.nv_stencil_two_side))
	{
		glDisable(GL_CULL_FACE);
		if(gl_config.ati_separate_stencil)
			glStencilFuncSeparateATI(GL_ALWAYS, GL_ALWAYS, 1, 0xffffffff);
		else
		{ 
			glEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);
			glActiveStencilFaceEXT (GL_BACK);
			glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
			glActiveStencilFaceEXT (GL_FRONT);
			glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
		}
	}
	else
		glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

	if (all)
	{
		if (currentshadowlight->volumeCmds && currentshadowlight->lightCmds/* && !currentshadowlight->noshadow*/)	//При noshadow и так volumeCmds == NULL
			R_DrawWorldShadowVolumes();
		if (!currentshadowlight->noshadow2)
			R_DrawEntsShadowVolumes(NULL, true);
	}
	else
	{
		if (SelfShadow)
		{
			if (currentshadowlight->volumeCmds && currentshadowlight->lightCmds/* && !currentshadowlight->noshadow*/)	//При noshadow и так volumeCmds == NULL
				R_DrawWorldShadowVolumes();
			if (!currentshadowlight->noshadow2)
				R_DrawEntsShadowVolumes(true, false);
		}
		else
		{
			if (!currentshadowlight->noshadow2)
				R_DrawEntsShadowVolumes(false, false);
		}
	}

	if(r_faststencil->value && (gl_config.ati_separate_stencil || gl_config.nv_stencil_two_side))
	{
		glEnable(GL_CULL_FACE);
		if(gl_config.nv_stencil_two_side)
			glDisable(GL_STENCIL_TEST_TWO_SIDE_EXT);
	}

	glColorMask(1, 1, 1, 1);
	glDepthFunc (GL_LEQUAL);

//	if (gl_config.arb_multisample)
//		glEnable(GL_MULTISAMPLE_ARB);
}


void R_RenderLightingAndShadows(bool shadow)
{
	int		i;

	if (!r_drawentities->value)
		return;

	if (r_mirror && !r_mirror_models->value)
		return;

	if (!cl_numlightvisedicts)
		return;

	// отсортируем по дальности...
	entity_t	*gun = NULL, *player[8];	/// до 8 player model
	int			ne = 0;
	for (i=0 ; i<cl_numlightvisedicts ; i++)
	{
		currententity = cl_lightvisedicts[i];
		currentmodel = currententity->model;
		if (currentmodel)
		{
			if (currentmodel->flags & RF_NOSELFSHADOW)
			{
				float x, y, z;

				if (currententity->flags & RF_DEPTHHACK)	/// v_gun model
					gun = currententity;
				else if (currententity->flags & RF_VIEWERMODEL && (currentmodel->name[0] == 'p' || currentmodel->name[0] == 'P'))	/// player model
				{
					if (ne<8)
					{
						player[ne] = currententity;
						ne++;
						continue;	// не будем тратить время на подсчёт dist, т.к. он будет заменён позже
					}
				}

				x = currentshadowlight->origin[0] - currententity->origin[0];
				y = currentshadowlight->origin[1] - currententity->origin[1];
				z = currentshadowlight->origin[2] - currententity->origin[2];
				currententity->dist = x * x + y * y + z * z;
			}
			else
				currententity->dist = 0;
		}
		else
			currententity->dist = 0;
	}

	if (gun)
		for (i=0; i<ne; i++)
			player[i]->dist = gun->dist + 1;

	qsort(cl_lightvisedicts, cl_numlightvisedicts, sizeof(entity_t*), (int (*)(const void *, const void *))entityDistcmpfnc );

	for (i=0 ; i<cl_numlightvisedicts ; i++)
	{
		currententity = cl_lightvisedicts[i];
		currentmodel = currententity->model;
		if (!(currentmodel->flags & RF_NOSELFSHADOW))
			continue;

///		if((currentmodel->type != mod_alias) && (currentmodel->type != mod_alias_md3))
///			continue;	// лишнее, ибо RF_NOSELFSHADOW имеют только модели!

		if(currententity->flags & (RF_NOUSE | RF_DISTORT))
			goto next;

		if (!r_mirror && (currententity->flags & RF_VIEWERMODEL))
			goto next;

		// Light...
		glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
		glStencilFunc(GL_EQUAL, 0, 0xffffffff);
		if (r_offset->value)
			glEnable(GL_POLYGON_OFFSET_FILL);
///		if(r_mirror)
///			R_EnableClipMirror(0);
		if(currentmodel->type == mod_alias)
			R_DrawAliasObjectLight();
		else/// if(currentmodel->type == mod_alias_md3)
			R_DrawMD3AliasObjectLight();
		if (r_offset->value)
			glDisable(GL_POLYGON_OFFSET_FILL);
///		if(r_mirror)
///			R_DisableClipMirror(0);

next:	// and Shadow...
		if (currentshadowlight->noshadow2)
			continue;
		if (!currentshadowlight->isStatic && !dl_shadows->value)
			continue;
		if(!r_mirror && r_shadows->value <= 1)	// тени для алиасов рисуем только если r_shadows > 1
			continue;
		if (r_mirror && r_mirror_shadows->value <= 1)
			continue;
		if ((currententity->flags & (RF_NOCASTSHADOW|RF_DISTORT)) || (currententity->model->flags & (RF_NOCASTSHADOW|RF_DISTORT)))
			continue;
//		if (r_mirror)
//		{
//			vec3_t dist;
//			VectorSubtract (currententity->origin, currentshadowlight->origin, dist);
//			float colorscale = 1 - (VectorLength(dist) / currentshadowlight->radius);
//			//it will be a very faint shadow
//			if (colorscale < 0.1)
//				continue;
//		}
		glDepthFunc (GL_LESS);
		glDisable(GL_TEXTURE_2D);	// Turn off texturing...
		glColorMask(0, 0, 0, 0);
		if(r_faststencil->value && (gl_config.ati_separate_stencil || gl_config.nv_stencil_two_side))
		{
			glDisable(GL_CULL_FACE);
			if(gl_config.ati_separate_stencil)
				glStencilFuncSeparateATI(GL_ALWAYS, GL_ALWAYS, 1, 0xffffffff);
			else
			{ 
				glEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);
				glActiveStencilFaceEXT (GL_BACK);
				glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
				glActiveStencilFaceEXT (GL_FRONT);
				glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
			}
		}
		else
			glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

		if (currententity->model->type == mod_alias)
			R_DrawAliasShadowVolume();
		else if (currententity->model->type == mod_alias_md3)
			R_DrawMD3AliasShadowVolume();
		else
			continue;

		if(r_faststencil->value && (gl_config.ati_separate_stencil || gl_config.nv_stencil_two_side))
		{
			glEnable(GL_CULL_FACE);
			if(gl_config.nv_stencil_two_side)
				glDisable(GL_STENCIL_TEST_TWO_SIDE_EXT);
		}
		glColorMask(1, 1, 1, 1);
		glDepthFunc (GL_LEQUAL);
	}
}


void CalcSurfaceCenter (msurface_t *s, vec3_t normal)
{
	glpoly_t	*p;
	float		*v;
	int			i, j;
	vec3_t		mins, maxs;

	if (s->polys)
	{
		mins[0] = mins[1] = mins[2] = 999999;
		maxs[0] = maxs[1] = maxs[2] = -999999;

		for (p=s->polys ; p ; p=p->next)
			for (i=0, v=p->verts[0] ; i<p->numverts ; i++, v+=VERTEXSIZE)
				for(j=0; j<3; j++)
				{
					if(mins[j] > v[j])
						mins[j] = v[j];
					if(maxs[j] < v[j])
						maxs[j] = v[j];
				}

		VectorCopy(mins, s->mins);
		VectorCopy(maxs, s->maxs);

		for(j=0; j<3; j++)
			s->center[j] = (mins[j] + maxs[j]) * 0.5;

		VectorSubtract(s->center, normal, s->center);
	}
}


void Mod_LoadFaces (lump_t *l)
{
	dface_t		*in;
	msurface_t 	*out;
	int			i, j, count, surfnum;
	int			planenum, side;
	int			ti;
	unsigned	chk = LittleLong (Com_BlockChecksum (mod_base, modfilelen));

	num_flares = 0;

	in = (dface_t *)(mod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
		Com_Error (ERR_DROP, "Mod_LoadFaces: funny lump size in %s",loadmodel->name);
	count = l->filelen / sizeof(*in);
	if (count>MAX_MAP_FACES)
		Com_Error (ERR_DROP, "Mod_LoadFaces: map has too many surfaces");
	out = (msurface_t *) Hunk_Alloc ( count*sizeof(*out), true);

	loadmodel->surfaces = out;
	loadmodel->numsurfaces = count;
	currentmodel = loadmodel;

	if (!r_simple->value)
	{
		// Allocate storage for our edge table
		tempEdges = (temp_connect_t *)Z_Malloc(currentmodel->numedges * sizeof(temp_connect_t), true);
///		// clear tempedges
///		Memset(tempEdges, 0, currentmodel->numedges * sizeof(temp_connect_t));
	}

	GL_BeginBuildingLightmaps ();

	int c_non_flat = 0;

	for ( surfnum=0 ; surfnum<count ; surfnum++, in++, out++)
	{
		out->firstedge = LittleLong(in->firstedge);
		out->numedges = LittleShort(in->numedges);		
		out->flags = 0;
		out->polys = NULL;

		planenum = LittleShort(in->planenum);
		side = LittleShort(in->side);
		if (side)
			out->flags |= SURF_PLANEBACK;

		out->plane = loadmodel->planes + planenum;

		ti = LittleShort (in->texinfo);
		if (ti < 0 || ti >= loadmodel->numtexinfo)
			Com_Error (ERR_DROP, "Mod_LoadFaces: bad texinfo number");
		out->texinfo = loadmodel->texinfo + ti;

		CalcSurfaceExtents (out);

		if (caching__)		// при кэшировании пропустим этот шаг
			goto no;
		i = LittleLong(in->lightofs);
		if (i == -1)
no:			out->samples = NULL;
		else
			out->samples = loadmodel->lightdata + i;

///		if (!caching__)		// при кэшировании пропустим этот шаг	FIXED: нельзя пропускать, т.к. GL_SubdivideSurface выделяет Hunk_Memory, а это нужно для верного расчёта *.mem-кеша
		{
			// set the drawing flags
			if (out->texinfo->flags & SURF_WARP)
			{
				out->flags |= SURF_DRAWTURB;
				for (i=0 ; i<2 ; i++)
				{
					out->extents[i] = 16384;
					out->texturemins[i] = -8192;
				}
				GL_SubdivideSurface (out);	// cut up polygon for warps
			}
		}

		// create lightmaps and polygons
		if (!(out->texinfo->flags & (SURF_SKY|SURF_TRANS33|SURF_TRANS66|SURF_WARP|SURF_NODRAW)))
			GL_CreateSurfaceLightmap (out);

		float	dist;
		vec3_t	norma;
		VectorClear(out->polygonOffset);

		if (out->flags & SURF_PLANEBACK)
			VectorCopy(out->plane->normal, norma);
		else
			VectorNegate(out->plane->normal, norma);

		if (!(out->texinfo->flags & SURF_WARP))
		{
			GL_BuildPolygonFromSurface(out);

			glpoly_t	*p = out->polys;
			for (i=0; i<p->numverts; i++)
			{
				float *v0 = p->verts[i];
				// Check point on surface
				dist = fabs(DotProduct(out->plane->normal, v0)-out->plane->dist);
				if (dist>0.05)
				{
					VectorScale(norma, -dist, out->polygonOffset);
					c_non_flat++;
					break;
				}
			}
			/// Berserker: add some offset for avoid decal's z-fighting
			if (i==p->numverts)
				VectorScale(norma, -0.05, out->polygonOffset);
		}

		CalcSurfaceCenter (out, norma);

		if (!caching__)		// при кэшировании пропустим этот шаг
		{
			if (!(out->texinfo->flags & (SURF_SKY|SURF_TRANS33|SURF_TRANS66|SURF_WARP|SURF_NODRAW)))
				if (/*(out->texinfo->flags & SURF_LIGHT) && */(out->texinfo->flags & SURF_FLARE))
					if (num_flares < MAX_FLARES)
					{
						glpoly_t	*p;
						vec3_t		mins = {999999, 999999, 999999};
						vec3_t		maxs = {-999999, -999999, -999999};

///						for (p=out->polys ; p ; p=p->next)
p=out->polys;
						{
///							p->light = true;
							for (i=0 ; i<p->numverts ; i++)
							{
								if (mins[0] > p->verts[i][0])	mins[0] = p->verts[i][0];
								if (mins[1] > p->verts[i][1])	mins[1] = p->verts[i][1];
								if (mins[2] > p->verts[i][2])	mins[2] = p->verts[i][2];
								if (maxs[0] < p->verts[i][0])	maxs[0] = p->verts[i][0];
								if (maxs[1] < p->verts[i][1])	maxs[1] = p->verts[i][1];
								if (maxs[2] < p->verts[i][2])	maxs[2] = p->verts[i][2];
							}
						}

						float	r0, r1, r2;
						r0 = maxs[0]-mins[0];
						r1 = maxs[1]-mins[1];
						r2 = maxs[2]-mins[2];
						flares[num_flares].surf = out;
						if (r0>=r1 && r0>=r2)
							flares[num_flares++].size = r0;
						else if (r1>=r0 && r1>=r2)
							flares[num_flares++].size = r1;
						else
							flares[num_flares++].size = r2;
					}
		}
	}

	if (c_non_flat)
		Com_DPrintf("%i non-flat surfaces found\n", c_non_flat);

	// we now have the connectivity in tempEdges now store it in the polygons
	/// berserker: ...and compute checksum
	unsigned	check_from_cache;
	if (!caching__ && !r_simple->value)		// при кэшировании пропустим этот шаг
	{
		msurface_t	*surf = currentmodel->surfaces;
		for (surfnum=0 ; surfnum<count ; surfnum++, surf++)
		{
			if ( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY) )
				continue;

			SetupSurfaceConnectivity (surf);
		}
	}
	/// строим нормали и касательные вектора вершин для гладкого бамп-мапа
	byte		*cache = NULL;
	char		cachename[MAX_QPATH];
	int			ci, cj, flp;
	float		*vi, *vj;
	msurface_t	*si, *sj;
	vec3_t		ni, nj, ttt, tttt, ttttt, vector;
	unsigned	checksum;
	FILE *f = NULL;
// create the cache directory
	Com_sprintf (cachename, sizeof(cachename), "%s/cache/%s", FS_Gamedir(), loadmodel->name);

	if (strcmp(fs_gamedir+2, BASEDIRNAME))		// именно через сравнение строк: чтобы не прокатило +set game baseq2
	{	// если работает мод
		char	basecachename[MAX_QPATH];

		// проверим, имеется ли уже кэш для мода?
		f = FS_Fopen(cachename, "rb");
		if (f)			// если есть, то выходим
			goto good444;

		// если нет, попробуем скопировать кэш из baseq2
		Com_sprintf (basecachename, sizeof(basecachename), "%s/cache/%s", BASEDIRNAME, loadmodel->name);
		CopyFile(basecachename, cachename);
	}

	f = FS_Fopen(cachename, "rb");
	if (f)
	{	/// read from cache
good444:unsigned	ang;
		unsigned	check;
		flp = FS_filelength(f)-sizeof(float)-sizeof(unsigned)-sizeof(float);
		if (flp<0)
			goto bad;
		if(fread(&ang, 1, sizeof(unsigned), f)!=sizeof(unsigned))
			goto bad;
		if (ang != (unsigned)(smooth_cosine*0x7fffffff))
			goto bad;
		if(fread(&check_from_cache, 1, sizeof(unsigned), f)!=sizeof(unsigned))
			goto bad;
		if (check_from_cache != chk)
			goto bad;
		cache = (byte*)Z_Malloc(flp, true);
		fread(&check, 1, sizeof(unsigned), f);
#define MAGIC_CACHE 1236	// Здесь это магическое число меняем чтоб следующая версия движка сбрасывала кэш!
check += MAGIC_CACHE;
		fread(cache, 1, flp, f);
		checksum = LittleLong (Com_BlockChecksum (cache, flp));
		if (checksum == check)
		{
			flp = 0;
			for (i=0 ; i<count ; i++)
			{
				si = &currentmodel->surfaces[i];
				if (!(si->texinfo->flags & (SURF_SKY|SURF_TRANS33|SURF_TRANS66|SURF_WARP|SURF_NODRAW|SURF_WARP)))
					for (ci=0, vi = si->polys->verts[0]; ci<si->numedges; ci++, vi+=VERTEXSIZE)
					{
						vi[7] = r_avertexnormals[cache[flp]][0];
						vi[8] = r_avertexnormals[cache[flp]][1];
						vi[9] = r_avertexnormals[cache[flp++]][2];
						vi[10] = r_avertexnormals[cache[flp]][0];
						vi[11] = r_avertexnormals[cache[flp]][1];
						vi[12] = r_avertexnormals[cache[flp++]][2];
						vi[13] = r_avertexnormals[cache[flp]][0];
						vi[14] = r_avertexnormals[cache[flp]][1];
						vi[15] = r_avertexnormals[cache[flp++]][2];
					}
			}
			Z_Free (cache);
			fclose (f);
		}
		else
		{
			Z_Free (cache);
bad:		fclose (f);
			Com_Printf("^3 Invalid cache\n");
			goto crea;
		}
	}
	else
	{
crea:	if (!caching__)
			Com_Printf("^3Calculating extra data\n");
		else
			caching_calc = true;

		flp = 0;
		for (i=0 ; i<count ; i++)
		{
			si = &currentmodel->surfaces[i];
			if (!(si->texinfo->flags & (SURF_SKY|SURF_TRANS33|SURF_TRANS66|SURF_WARP|SURF_NODRAW|SURF_WARP)))
			{
				vi = si->polys->verts[0];
				for (ci=0; ci<si->numedges; ci++, vi+=VERTEXSIZE)
					vi[7] = vi[8] = vi[9] = vi[10] = vi[11] = vi[12] = vi[13] = vi[14] = vi[15] = 0;
				if (si->flags & SURF_PLANEBACK)
					VectorNegate(si->plane->normal, ni);
				else
					VectorCopy(si->plane->normal, ni);
				for (j=0 ; j<count ; j++)
				{
					sj = &currentmodel->surfaces[j];
					if (!(sj->texinfo->flags & (SURF_SKY|SURF_TRANS33|SURF_TRANS66|SURF_WARP|SURF_NODRAW|SURF_WARP)))
					{
						if (sj->flags & SURF_PLANEBACK)
							VectorNegate(sj->plane->normal, nj);
						else
							VectorCopy(sj->plane->normal, nj);
						if(DotProduct(ni, nj)>=smooth_cosine)
						{
							vi = si->polys->verts[0];
							for (ci=0; ci<si->numedges; ci++, vi+=VERTEXSIZE)
							{
								vj = sj->polys->verts[0];
								for (cj=0; cj<sj->numedges; cj++, vj+=VERTEXSIZE)
								{
									if (VectorCompare(vi, vj))
									{
										vi[7] += nj[0];
										vi[8] += nj[1];
										vi[9] += nj[2];
									}
								}
							}
						}
					}
				}

				vi = si->polys->verts[0];
				for (ci=0; ci<si->numedges; ci++, vi+=VERTEXSIZE)
				{
					VectorSet(ttt, vi[7], vi[8], vi[9]);
					VectorNormalize(ttt);

					if(DotProduct(ttt, ni)<smooth_cosine)
					{
						vi[7] = ttt[0] = ni[0];
						vi[8] = ttt[1] = ni[1];
						vi[9] = ttt[2] = ni[2];
					}
					else
					{
						vi[7] = ttt[0];
						vi[8] = ttt[1];
						vi[9] = ttt[2];
					}

					CrossProduct(ttt, si->texinfo->vecs[0], tttt);
					CrossProduct(ttt, tttt, ttttt);
					VectorNormalize(ttttt);
					if (DotProduct(ttttt, si->texinfo->vecs[0])<0)
					{
						vi[10] = -ttttt[0];
						vi[11] = -ttttt[1];
						vi[12] = -ttttt[2];
					}
					else
					{
						vi[10] = ttttt[0];
						vi[11] = ttttt[1];
						vi[12] = ttttt[2];
					}

					CrossProduct(ttt, si->texinfo->vecs[1], tttt);
					CrossProduct(ttt, tttt, ttttt);
					VectorNormalize(ttttt);
					if (DotProduct(ttttt, si->texinfo->vecs[1])<0)
					{
						vi[13] = -ttttt[0];
						vi[14] = -ttttt[1];
						vi[15] = -ttttt[2];
					}
					else
					{
						vi[13] = ttttt[0];
						vi[14] = ttttt[1];
						vi[15] = ttttt[2];
					}

					flp += 3;
				}
			}
		}

		FS_CreatePath(cachename);
		f = FS_Fopen(cachename, "wb");
		if (f)
		{
			// write to cache
			int	offs = 0;
			cache = (byte *)Z_Malloc(flp, true);
			for (i=0 ; i<count ; i++)
			{
				si = &currentmodel->surfaces[i];
				if (!(si->texinfo->flags & (SURF_SKY|SURF_TRANS33|SURF_TRANS66|SURF_WARP|SURF_NODRAW|SURF_WARP)))
				{
					for (ci=0, vi = si->polys->verts[0]; ci<si->numedges; ci++, vi+=VERTEXSIZE)
					{
						vector[0] = vi[7];
						vector[1] = vi[8];
						vector[2] = vi[9];
						cache[offs++] = Normal2Index(vector);
						vector[0] = vi[10];
						vector[1] = vi[11];
						vector[2] = vi[12];
						cache[offs++] = Normal2Index(vector);
						vector[0] = vi[13];
						vector[1] = vi[14];
						vector[2] = vi[15];
						cache[offs++] = Normal2Index(vector);
					}
				}
			}

			checksum = LittleLong (Com_BlockChecksum (cache, flp));
			unsigned	sc = (unsigned)(smooth_cosine*0x7fffffff);
			fwrite(&sc, 1, sizeof(unsigned), f);
			fwrite(&chk, 1, sizeof(unsigned), f);
checksum -= MAGIC_CACHE;
			fwrite(&checksum, 1, sizeof(unsigned), f);
			fwrite (cache, 1, flp, f);
			Z_Free(cache);
			fclose (f);
		}
	}

	if (!r_simple->value)
		Z_Free (tempEdges);

	GL_EndBuildingLightmaps ();
}


void R_RenderLighting(bool SelfShadow, bool all)
{
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	glStencilFunc(GL_EQUAL, 0, 0xffffffff);
	if (r_offset->value)
		glEnable(GL_POLYGON_OFFSET_FILL);

///	if(r_mirror)
///		R_EnableClipMirror(0);

	if (all)
	{
		if (!gun_pass)
		{
			if (/*currentshadowlight->volumeCmds && currentshadowlight->lightCmds && */currentshadowlight->has_lightedsurfs)
				R_DrawWorldBumped();
			R_DrawLightBrushes();
		}
		R_DrawLightAliases(NULL, true);
	}
	else
	{
		if (SelfShadow)
		{
			if (!gun_pass)
			{
				if (/*currentshadowlight->volumeCmds && currentshadowlight->lightCmds && */currentshadowlight->has_lightedsurfs)
					R_DrawWorldBumped();
				R_DrawLightBrushes();
			}
			R_DrawLightAliases(true, false);
		}
		else
			R_DrawLightAliases(false, false);
	}

	if (r_offset->value)
		glDisable(GL_POLYGON_OFFSET_FILL);

///	if(r_mirror)
///		R_DisableClipMirror(0);
}


void L_OccTests()
{
	int		i;
	vec3_t	v[8];

	if (r_mirror)
		return;

	if (!gl_config.occlusion)
		return;

	if (r_nolights->value)
		return;

	glDisable (GL_CULL_FACE);
	glDepthMask(0);
	glStencilMask(0);
	glColorMask(0,0,0,0);
	for (i=0; i<numUsedShadowLights; i++)
	{
		currentshadowlight = usedshadowlights[i];
		if (currentshadowlight->isStatic)
		{
			currentshadowlight->framecount = occ_framecount;

			trace_t	trace = CL_PMTraceWorld(currentshadowlight->origin, vec3_origin, vec3_origin, r_origin, MASK_SOLID);
			if (trace.fraction == 1)
				currentshadowlight->directVisibled = true;
			else
			{
				currentshadowlight->directVisibled = false;

				// begin occlusion test
				glBeginQueryARB(gl_config.occlusion, currentshadowlight->occID);

				// draw light's bbox
				vec3_t	org, rad;
				VectorCopy(currentshadowlight->origin, org);
				if (currentshadowlight->_cone && (currentshadowlight->rspeed[0]==0 && currentshadowlight->rspeed[1]==0))
				{
					VectorSet(v[0], currentshadowlight->mins_cone[0], currentshadowlight->mins_cone[1], currentshadowlight->mins_cone[2]);
					VectorSet(v[1], currentshadowlight->mins_cone[0], currentshadowlight->mins_cone[1], currentshadowlight->maxs_cone[2]);
					VectorSet(v[2], currentshadowlight->mins_cone[0], currentshadowlight->maxs_cone[1], currentshadowlight->mins_cone[2]);
					VectorSet(v[3], currentshadowlight->mins_cone[0], currentshadowlight->maxs_cone[1], currentshadowlight->maxs_cone[2]);
					VectorSet(v[4], currentshadowlight->maxs_cone[0], currentshadowlight->mins_cone[1], currentshadowlight->mins_cone[2]);
					VectorSet(v[5], currentshadowlight->maxs_cone[0], currentshadowlight->mins_cone[1], currentshadowlight->maxs_cone[2]);
					VectorSet(v[6], currentshadowlight->maxs_cone[0], currentshadowlight->maxs_cone[1], currentshadowlight->mins_cone[2]);
					VectorSet(v[7], currentshadowlight->maxs_cone[0], currentshadowlight->maxs_cone[1], currentshadowlight->maxs_cone[2]);
				}
				else
				{
					VectorScale(currentshadowlight->radiuses, ClampCvar(0.7, 1.0, r_occlusion_scale->value), rad);
					VectorSet(v[0], org[0]-rad[0], org[1]-rad[1], org[2]-rad[2]);
					VectorSet(v[1], org[0]-rad[0], org[1]-rad[1], org[2]+rad[2]);
					VectorSet(v[2], org[0]-rad[0], org[1]+rad[1], org[2]-rad[2]);
					VectorSet(v[3], org[0]-rad[0], org[1]+rad[1], org[2]+rad[2]);
					VectorSet(v[4], org[0]+rad[0], org[1]-rad[1], org[2]-rad[2]);
					VectorSet(v[5], org[0]+rad[0], org[1]-rad[1], org[2]+rad[2]);
					VectorSet(v[6], org[0]+rad[0], org[1]+rad[1], org[2]-rad[2]);
					VectorSet(v[7], org[0]+rad[0], org[1]+rad[1], org[2]+rad[2]);
				}

				glBegin(GL_TRIANGLE_FAN);
				glVertex3fv(v[4]);
				glVertex3fv(v[0]);
				glVertex3fv(v[1]);
				glVertex3fv(v[5]);
				glVertex3fv(v[7]);
				glVertex3fv(v[6]);
				glVertex3fv(v[2]);
				glVertex3fv(v[0]);
				glEnd();
				glBegin(GL_TRIANGLE_FAN);
				glVertex3fv(v[3]);
				glVertex3fv(v[0]);
				glVertex3fv(v[1]);
				glVertex3fv(v[5]);
				glVertex3fv(v[7]);
				glVertex3fv(v[6]);
				glVertex3fv(v[2]);
				glVertex3fv(v[0]);
				glEnd();

				// end occlusion test
				glEndQueryARB(gl_config.occlusion);
			}
		}
	}
	glEnable (GL_CULL_FACE);
	glStencilMask(-1);
	glDepthMask(1);
	glColorMask(1,1,1,1);
}


void R_DrawSkyWorld()
{
	float		angle;
	vec3_t		ang, delta, forward, right, up;
	refdef_t	refdef, oldrefdef;
	int			old_framecount;

	drawing_sky_world = true;

	Memcpy(&oldrefdef, &r_newrefdef, sizeof(refdef_t));
	Memcpy(&refdef, &r_newrefdef, sizeof(refdef_t));
	old_framecount = r_framecount;
	old_occ_framecount = occ_framecount;
///	old_r_finish = r_finish->value;
///	r_finish->value = 0;
	old_fps_count = fps_count;

	angle = sky_angle + (cl.time * 0.1) * sky_rotate;
	refdef.viewangles[1] += angle;
	VectorSubtract(refdef.vieworg, sky_viewcenter, delta);
	VectorSet(ang, 0,-angle,0);
	AngleVectors (ang, forward, right, up);
	refdef.vieworg[0] = sky_origin[0] + sky_scale * DotProduct(delta, forward);
	refdef.vieworg[1] = sky_origin[1] - sky_scale * DotProduct(delta, right);
	refdef.vieworg[2] = sky_origin[2] + sky_scale * DotProduct(delta, up);

	// find the sky's PVS
	int		cameraarea;
	byte	buffer[MAX_MAP_AREAS/8];		// portalarea visibility bits
	byte	*areabits = buffer;
	cameraarea = CM_LeafArea (CM_PointLeafnum (refdef.vieworg));
	CM_WriteAreaBits(areabits, cameraarea);
	refdef.areabits = areabits;

	refdef.num_dlights = 0;
	refdef.num_particles = 0;

	R_RenderView(&refdef,false);
	glClear (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	fps_count = old_fps_count;
///	r_finish->value = old_r_finish;
	Memcpy(&r_newrefdef, &oldrefdef, sizeof(refdef_t));
	r_framecount = old_framecount;
	occ_framecount = old_occ_framecount;

	drawing_sky_world = false;
}


/*
================
R_RenderView

r_newrefdef must be set before the first call
================
*/
void R_RenderView (refdef_t *fd, bool mirror)
{
	int				i, j;
	r_newrefdef =	*fd;
	byte			old_viewvis[MAX_MAP_LEAFS/8];

	bool			hud = (!r_worldmodel || (r_newrefdef.rdflags & RDF_NOWORLDMODEL));

	if (sky_world && !drawing_sky_world && !hud && !mirror)
		R_DrawSkyWorld();

	bool	old_mirror = r_mirror;
	r_mirror = mirror;

	if (!r_worldmodel && !( r_newrefdef.rdflags & RDF_NOWORLDMODEL ) )
		Com_Error (ERR_DROP, "R_RenderView: NULL worldmodel");

	if (!r_mirror)
		c_brush_polys = c_alias_polys = c_light_brush_polys = c_light_alias_polys = c_mirrors = c_lights = c_no_light_ents = c_no_lights = c_no_mirrors = 0;
	else
		c_mirrors++;

///	if (r_finish->value)
///		glFinish ();

	if (!r_mirror)
	{
		R_SetupFrame (false);
		fps_count++;
	}
	else
		R_SetupMirrorFrame(old_viewvis);

	R_SetFrustum ();
	R_SetupGL();

	need_gun_optimisation = false;		// тут определим заранее для случая hud-моделей
	if(hud)
	{	// ВНИМАНИЕ! Для хад моделей RF_DISTORT сбрасывается автоматически.
		if (gl_config.arb_multisample)
			glEnable(GL_MULTISAMPLE_ARB);

		for (i=0; i<r_newrefdef.num_entities; i++)
		{
			currententity = r_newrefdef.entities+i;
			currentmodel = currententity->model;

			if ((currententity->flags & RF_DISTORT) || (currentmodel->flags & RF_DISTORT))
			{
				Com_Printf("^3Warning:^7 distort hud model\n");
				break;
			}

			switch (currentmodel->type)
			{
			case mod_alias:
				R_DrawAliasModel (true);
				if (!r_simple->value)
				{
					if (!(currententity->flags & (RF_DISTORT|RF_TRANSLUCENT)))
					{
						if (r_offset->value)
							glEnable(GL_POLYGON_OFFSET_FILL);
						R_DrawAliasObjectLight ();
					}
				}
				break;
			case mod_alias_md3:
				R_DrawAliasMD3Model (true, true, true);
				if (!r_simple->value)
				{
					if (!(currententity->flags & (RF_DISTORT|RF_TRANSLUCENT)))
					{
						if (r_offset->value)
							glEnable(GL_POLYGON_OFFSET_FILL);
						R_DrawMD3AliasObjectLight ();
					}
				}
				break;
			default:
				Com_Error (ERR_DROP, "R_RenderView: Bad modeltype");
				break;
			}

			if (!r_simple->value)
				if (r_offset->value)
					glDisable(GL_POLYGON_OFFSET_FILL);
		}

		if (gl_config.arb_multisample)
			glDisable(GL_MULTISAMPLE_ARB);
		r_mirror = old_mirror;
		return;
	}

	if (!numplanes)		/// Berserker: если не загружена карта, то и нечего показывать...
	{
		r_mirror = old_mirror;
		return;
	}

	Clear_NoUses();

	if (drawing_sky_world)
		R_MarkLeaves ();	// done here so we know if we're in water

	if(!r_mirror && !drawing_sky_world)
	{
		if (host_speeds->value)
			time_before_ref_ambient = Sys_Milliseconds ();

		invisibled_gun = false;		// true - признак того, что действует item_invisible

		R_MarkLeaves ();	// done here so we know if we're in water
		bool	mirr;
		mirr = R_RenderMirrors();	// Рендерим все зеркала в текстуры.
		R_ClearMirrorChains(false);
		if (mirr)						// Восстановим состояние OpenGL после отрисовки зеркал
		{
			R_SetupFrame (true);
			R_SetFrustum ();
			R_SetupGL();
			Clear_NoUses();
		}
	}

	need_gun_test = !r_mirror && !sky_world && gl_config.screentexture && gl_config.arb_distort && arbdistort_program!=0xffffffff && r_distort->value && r_distort_distance->value && cl_gun->value && !r_simple->value && hand->value!=2 && r_drawentities->value;

	if (gl_config.arb_multisample)
		glEnable(GL_MULTISAMPLE_ARB);

	if (r_mirror)
		R_EnableClipMirror(0);
	R_DrawAmbientWorld ();
	GetOcclusionResults();		// Ставим получение результата окклюдера ДО тестов, чтоб не было "простоя" CPU/GPU
	R_DrawAmbientEntitiesOnList_Solid ();	// Вначале рисуем solid entities
	DrawTextureChains ();		// Рисование texture-chains: цепочек плоскостей.
	if(r_mirror)
		R_DisableClipMirror(0);
	L_OccTests();

	bool chs = R_CheckSun();

	if(!r_mirror && !drawing_sky_world)
	{
		c_world_batches_lighted = 0;
		if(host_speeds->value)
			time_before_ref_light = Sys_Milliseconds ();
	}

	if (LightEditor_Enabled(true))
		for (i=0; i<numUsedShadowLights; i++)
		{
			bool baklm = le_mode;
			le_mode = false;	// all lights (static, dynamic and linked) will be draw as shadowlight_t
			R_DrawCurrentLight(usedshadowlights[i], NULL, 4);
			le_mode = baklm;
		}

	int	gun_limit = (r_simple->value) ? 1 : 2;
	for (gun_pass=0; gun_pass<gun_limit; gun_pass++)
	{
		if (r_simple->value)
			goto skip;

		// если need_gun_optimisation==true, то
		//	если gun_pass==0 - рисуем освещенные модели без оружия
		//	если gun_pass==1 - рисуем освещенную модель оружия
		// если need_gun_optimisation==false, то
		//	рисуем как обычно в один проход.
		bool	stencil_cleared = false;
		if (gun_pass)
		{
			if (!need_gun_optimisation)
				break;			// рисуем как обычно в один проход.
			// восстановим stencil-buffer перед вторым проходом
			glDepthMask(1);
			glClear(GL_STENCIL_BUFFER_BIT);
			stencil_cleared = true;	// предотвратить возможную повторную очистку буфера
			// нарисуем ambient-weapon-model
			R_DrawAmbientWeaponSolid ();
		}

		if(!r_mirror)
		{
			glEnable(GL_SCISSOR_TEST);
			glScissor(r_viewport[0], r_viewport[1], r_viewport[2], r_viewport[3]);
		}
		else if (!gun_pass)
			R_EnableClipMirror(0);

		if (!r_simple->value)
			R_InitShadowsForFrame ();

		recList = NULL;

		for (i=0; i<numUsedShadowLights; i++)
		{
			//find a lights that still fits on our current screen plane
			//using the first fit method.
			//Other methods may give better results (exhaustive for example)
			//but i'm convinced you can't save more clears than those that you
			//save with this.
			bool foundone = false;
			for (j=0; j<numUsedShadowLights; j++)
			{
				if (!usedshadowlights[j]->visible)
					continue;

				currentshadowlight = usedshadowlights[j];
				if (R_CheckRectList(&currentshadowlight->scizz))
				{
						foundone = true;
						break;
				}
			}

			if (!foundone)
			{
				//Only clear dirty part
				glScissor(totalRect.coords[0], totalRect.coords[1], totalRect.coords[2] - totalRect.coords[0], totalRect.coords[3] - totalRect.coords[1]);
				if (!stencil_cleared)
				{
					stencil_cleared = true;
					glClear(GL_STENCIL_BUFFER_BIT);
				}
				recList = NULL;
				for (j=0; j<numUsedShadowLights; j++)
				{
					currentshadowlight = usedshadowlights[j];
					if (currentshadowlight->visible)
						break;
				}
			}

			if (!currentshadowlight->visible)			// FIXME: тест эту ситуацию, ИМХО такого не возникнет никогда!
				continue;

			stencil_cleared = false;
			MakeFrustum4Light(true);

			//mark shadow casting ents
			r_lightTimestamp++;
///			shadowchain = NULL;
			num_scene_surfaces = 0;
			MarkShadowEntities();

			//Find the polygons that cast shadows for dynamic light
			if(!currentshadowlight->isStatic)
			{
				if (!R_FillShadowChain(currentshadowlight))
				{
					if (cl_numlightvisedicts)
					{
						currentshadowlight->volumeCmds = NULL;
						currentshadowlight->lightCmds = NULL;
						goto la;
					}
					currentshadowlight->visible = false;
					continue;
				}

				PrecalcVolumesForLight(r_worldmodel);
				if (numVolumeCmds < 2 || numLightCmds < 2)
				{
					if (cl_numlightvisedicts)
					{
						currentshadowlight->volumeCmds = NULL;
						currentshadowlight->lightCmds = NULL;
						goto la;
					}
					currentshadowlight->visible = false;
					continue;
				}

				currentshadowlight->volumeCmds = &volumeCmdsBuff[0];
				currentshadowlight->lightCmds = &lightCmdsBuff[0];
			}

la:			// проверим, есть ли поверхности, которые в данном кадре д.б. освещены?
			CheckWorldInteraction();
			if (!cl_numlightvisedicts && !currentshadowlight->has_lightedsurfs && !currentshadowlight->volumeCmds)
			{
				currentshadowlight->visible = false;
				continue;
			}

			glEnable(GL_STENCIL_TEST);
			glDepthMask(0);
///			bool dbt = R_SetDepthBounds();

			if (R_CheckRectList(&currentshadowlight->scizz))
				//we can have another go without clearing
				R_AddRectList(&currentshadowlight->scizz);
			else
			{
				//Only clear dirty part
				glScissor(totalRect.coords[0], totalRect.coords[1], totalRect.coords[2]-totalRect.coords[0], totalRect.coords[3]-totalRect.coords[1]);
				glClear(GL_STENCIL_BUFFER_BIT);
				recList = NULL;
				R_AddRectList(&currentshadowlight->scizz);
			}

			R_MarkBrushSurfaces();
			glScissor(currentshadowlight->scizz.coords[0], currentshadowlight->scizz.coords[1], currentshadowlight->scizz.coords[2]-currentshadowlight->scizz.coords[0], currentshadowlight->scizz.coords[3]-currentshadowlight->scizz.coords[1]);
			light_used = shadow_used = false;
			bool	shad = (!(currentshadowlight->noshadow && currentshadowlight->noshadow2) && !VectorCompareEpsilon(currentshadowlight->origin, r_origin, 0.1f));			// Не рисуем shadow volume, если источник света совпадает с камерой
			switch ((int)r_noselfshadows->value)
			{
			case 0:
				// Рисуем все тени
				if(shad)
					R_RenderShadowVolumes(NULL, true);

				// Рисуем все освещенные плоскости
				R_RenderLighting(NULL, true);
				break;
			case 1:
				// Рисуем тени от обычных сурфов
				if(shad)
					R_RenderShadowVolumes(true, false);

				// Рисуем освещенные плоскости noSelfShadow
				R_RenderLighting(false, false);

				// Рисуем тени от сурфов noSelfShadow
				if(shad)
					R_RenderShadowVolumes(false, false);

				// Рисуем освещенные обычные плоскости
				R_RenderLighting(true, false);
				break;
			default:
				// Рисуем тени от обычных сурфов
				if(shad)
					R_RenderShadowVolumes(true, false);

				// Рисуем освещенные плоскости noSelfShadow, затем тени от сурфов noSelfShadow
				R_RenderLightingAndShadows(shad);

				// Рисуем освещенные обычные плоскости
				R_RenderLighting(true, false);
				break;
			}

			glDisable (GL_BLEND);
			GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			glDisable(GL_STENCIL_TEST);
			glDepthMask(1);
///			if (dbt)
///				glDisable(GL_DEPTH_BOUNDS_TEST_EXT);
			currentshadowlight->visible = false;
			if(!r_mirror)
			{
				if (light_used || shadow_used)
				{
					if (!gun_pass)
					{
						R_RenderCurrentLight();
						c_lights++;
					}
				}
			}
		}

		if(!r_mirror)
			glDisable(GL_SCISSOR_TEST);
		else if (!gun_pass)
			R_DisableClipMirror(0);

skip:	glEnable( GL_TEXTURE_2D );

		glDepthMask( 0 );
		if(!r_mirror && !gun_pass && !drawing_sky_world)
			R_DrawMirrorSurfaces (false);
		if (!gun_pass)
		{
			if(r_mirror)
				R_EnableClipMirror(0);
			R_DrawCausticsSurfaces ();
			R_DrawDecals();
			R_RenderDlights ();
			R_DrawAlphaSurfaces ();
		}
		R_DrawAmbientEntitiesOnList_Transparent ();		// Рисуем transparent entities
		if (!gun_pass)
		{
			R_DrawFlares ();

			if(numFogs)
			{
				glEnable(GL_POLYGON_OFFSET_FILL);
				glEnable (GL_BLEND);
				GL_BlendFunc (GL_ONE, GL_ONE);
				gl_TexEnv (GL_MODULATE);
				glDisable (GL_TEXTURE_2D);

				R_DrawFogSurfaces ();
				R_DrawFogModels ();

				gl_TexEnv (GL_REPLACE);
				GL_Color3f(1,1,1);
				glDisable (GL_BLEND);
				GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				glEnable (GL_TEXTURE_2D);
				glDisable(GL_POLYGON_OFFSET_FILL);
			}

			if(!r_mirror && !drawing_sky_world)
				R_DrawMirrorSurfaces (true);

			R_DrawParticles ();			// Рисуем нетекстурированные партикли (old style)
			R_DrawTexParticles ();		// Рисуем текстурированные партикли (new style)
			R_DrawSpecParticles ();
			GL_ClearCache(GL_COLOR_);	// reset cache (некэшированный glColor и glColorArray used...)
			GL_Color3f(1,1,1);

			if(r_mirror)
				R_DisableClipMirror(0);
			else if(!r_simple->value)
			{
				glDepthMask(1);
				R_DrawDistorts();
				glDepthMask(0);
			}
		}
	}

	if (gl_config.arb_multisample)
		glDisable(GL_MULTISAMPLE_ARB);

///	if (!sky_world || (sky_world && drawing_sky_world))	// ибо проверка уже не имеет смысла
	if (chs)
		R_RenderSun();

	if(!r_mirror && !drawing_sky_world)
		R_DrawCurrentRelight();
	glDepthMask(1);

	if(!r_mirror)
	{
		if (!drawing_sky_world)
		{
			R_PolyBlend();
			// После последнего прорисованного объекта в основном RenderView (не mirror и не SkyWorld) грабим экран для bloom
			if (!do_bloom && gl_config.screentexture && (r_bloom->value>0 && r_bloom->value<=3 && r_fullscreen->value))
			{
				GL_SelectTexture(GL_TEXTURE0);
				R_GrabScreenToTexture();
				do_bloom = 1;
			}
			if (r_speeds->value)
			{
				if (gl_config.occlusion)
					Com_Printf("%4i wpoly %4i epoly %3i wbatches\n%3i lights: %4i wpoly %4i epoly %3i wbatches\nOcc.Query dropped: %3i ents %3i lights %3i mirrors\n%1i mirrors\n", c_brush_polys, c_alias_polys, c_world_batches, c_lights, c_light_brush_polys, c_light_alias_polys, c_world_batches_lighted, c_no_light_ents, c_no_lights, c_no_mirrors, c_mirrors);
				else
					Com_Printf("%4i wpoly %4i epoly %3i wbatches\n%3i lights: %4i wpoly %4i epoly %3i wbatches\n%1i mirrors\n\n", c_brush_polys, c_alias_polys, c_world_batches, c_lights, c_light_brush_polys, c_light_alias_polys, c_world_batches_lighted, c_mirrors);
			}
		}
		oc_light_brush_polys += c_light_brush_polys;
		oc_light_alias_polys += c_light_alias_polys;
		oc_lights += c_lights;
		oc_no_light_ents += c_no_light_ents;
		oc_no_lights += c_no_lights;
		oc_no_mirrors += c_no_mirrors;
	}

	if (r_mirror)
		R_RestoreFrame(old_viewvis);
	else if (host_speeds->value && !drawing_sky_world)
		time_after_ref_light = Sys_Milliseconds ();

	r_mirror = old_mirror;
}


void R_SetGL2D ()
{
	// set 2D virtual screen size
	glViewport (0,0, vid.width, vid.height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity ();
	glOrtho  (0, vid.width, vid.height, 0, -99999, 99999);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity ();
	glDisable (GL_DEPTH_TEST);
	glDisable (GL_CULL_FACE);
	glDisable (GL_BLEND);
	glEnable (GL_ALPHA_TEST);
	GL_Color3f(1,1,1);
}


/// Общепринятый алгоритм Bloom, модифицированный Berserker'ом для достижения качественного downsample
void R_RenderBloom()
{
	float intensity;
	int		i, j, k, l;
	float	scs = (float)cl.refdef.width/(float)gl_config.screenTextureSize[0];
	float	sct = (float)cl.refdef.height/(float)gl_config.screenTextureSize[1];

	GL_MBind2D (GL_TEXTURE0, r_screenTexture->texnum);
	gl_TexEnv( GL_REPLACE );
// do downsample (качественный постепенный downsample)
	float bs, bt;
	i = max_bloom_size;
	while (1)
	{
		if (i == max_bloom_size)
		{
			bs = scs;
			bt = sct;
		}
		else
			bs = bt = (float)(i<<1) / (float)max_bloom_size;

		glBegin (GL_QUADS);
		glTexCoord2f (0, bt);
		glVertex2f (cl.refdef.x, cl.refdef.y);
		glTexCoord2f (bs, bt);
		glVertex2f (cl.refdef.x+i, cl.refdef.y);
		glTexCoord2f (bs, 0);
		glVertex2f (cl.refdef.x+i, cl.refdef.y+i);
		glTexCoord2f (0, 0);
		glVertex2f (cl.refdef.x, cl.refdef.y+i);
		glEnd ();
		GL_Bind2D(r_bloomTexture->texnum);
		glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cl.refdef.x, viddef.height - i - cl.refdef.y, i, i);
		i = i >> 1;
		if (i<bloom_size)
		{
			bs = bt = (float)bloom_size / (float)max_bloom_size;
			break;
		}
	}

// do contrast
	glEnable(GL_BLEND);
	GL_BlendFunc (GL_DST_COLOR, GL_ZERO);
	for (i=0; i<(4-r_bloom->value); i++)
	{
		glBegin (GL_QUADS);
		glTexCoord2f (0, 0);
		glVertex2f (cl.refdef.x, cl.refdef.y+bloom_size);
		glTexCoord2f (0, bt);
		glVertex2f (cl.refdef.x, cl.refdef.y);
		glTexCoord2f (bs, bt);
		glVertex2f (cl.refdef.x+bloom_size, cl.refdef.y);
		glTexCoord2f (bs, 0);
		glVertex2f (cl.refdef.x+bloom_size, cl.refdef.y+bloom_size);
		glEnd ();
	}
	glDisable(GL_BLEND);

// Грабим получившуюся low-res contrast картинку
	glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cl.refdef.x, viddef.height - bloom_size - cl.refdef.y, bloom_size, bloom_size);

// do blur
	gl_TexEnv( GL_MODULATE );
	// first time - w/o blend
	intensity = 0.3 * Diamond8x[4][4];
	GL_Color3f(intensity, intensity, intensity);
	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (cl.refdef.x, cl.refdef.y+bloom_size);
	glTexCoord2f (0, bt);
	glVertex2f (cl.refdef.x, cl.refdef.y);
	glTexCoord2f (bs, bt);
	glVertex2f (cl.refdef.x+bloom_size, cl.refdef.y);
	glTexCoord2f (bs, 0);
	glVertex2f (cl.refdef.x+bloom_size, cl.refdef.y+bloom_size);
	glEnd ();
	// with blend
	glEnable(GL_BLEND);
	GL_BlendFunc (GL_ONE, GL_ONE_MINUS_SRC_COLOR);
	for (i = 0; i < 8; i++)
	{
		k = i - 4;
		for (j = 0; j < 8; j++)
		{
			l = j - 4;
			if (!k && !l)
				continue;	// skip passed first
			intensity = 0.1 * Diamond8x[i][j];
			GL_Color3f(intensity, intensity, intensity);
			glBegin (GL_QUADS);
			glTexCoord2f (0, 0);
			glVertex2f (cl.refdef.x+k, cl.refdef.y+bloom_size+l);
			glTexCoord2f (0, bt);
			glVertex2f (cl.refdef.x+k, cl.refdef.y+l);
			glTexCoord2f (bs, bt);
			glVertex2f (cl.refdef.x+bloom_size+k, cl.refdef.y+l);
			glTexCoord2f (bs, 0);
			glVertex2f (cl.refdef.x+bloom_size+k, cl.refdef.y+bloom_size+l);
			glEnd ();
		}
	}
	glDisable(GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	GL_Color3f(1, 1, 1);

// Грабим получившуюся low-res blured картинку
	glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cl.refdef.x, viddef.height - bloom_size - cl.refdef.y, bloom_size, bloom_size);

// Объединяем с экранной текстурой
	GL_MBind2D (GL_TEXTURE1, r_screenTexture->texnum);
	glEnable(GL_TEXTURE_2D);
	gl_TexEnv( GL_COMBINE_ARB );
	if (r_showbloom->value)
	{	// отладка: просмотр полученной текстуры bloom
		GL_TexEnv( GL_COMBINE_RGB_ARB, GL_REPLACE );
		GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB );
	}
	else
	{
		GL_TexEnv( GL_COMBINE_RGB_ARB, GL_ADD );
		GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
	}
	GL_TexEnv( GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
	GL_TexEnv( GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
	GL_TexEnv( GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );

/// Berserker's fix: eliminate light edges on screen
bs = bt = ((float)bloom_size - 1) / (float)max_bloom_size;

	glBegin (GL_QUADS);
	glTexCoord2f (0, bt);
	glMultiTexCoord2fARB(GL_TEXTURE1, 0, sct);
	glVertex2f (cl.refdef.x, cl.refdef.y);
	glTexCoord2f (bs, bt);
	glMultiTexCoord2fARB(GL_TEXTURE1, scs, sct);
	glVertex2f (cl.refdef.x+cl.refdef.width, cl.refdef.y);
	glTexCoord2f (bs, 0);
	glMultiTexCoord2fARB(GL_TEXTURE1, scs, 0);
	glVertex2f (cl.refdef.x+cl.refdef.width, cl.refdef.y+cl.refdef.height);
	glTexCoord2f (0, 0);
	glMultiTexCoord2fARB(GL_TEXTURE1, 0, 0);
	glVertex2f (cl.refdef.x, cl.refdef.y+cl.refdef.height);
	glEnd ();

	glDisable(GL_TEXTURE_2D);
	gl_TexEnv( GL_COMBINE_ARB );
	GL_TexEnv( GL_COMBINE_RGB_ARB, GL_REPLACE );
	if (r_showbloom->value)
		GL_TexEnv( GL_SOURCE0_RGB_ARB, GL_TEXTURE );
	gl_TexEnv( GL_REPLACE );
	GL_SelectTexture(GL_TEXTURE0);
}


void R_FreeUnusedMaterials()
{
	int			i;
	image_t		*image;
	material_t	*material;
	char		fullname[MAX_QPATH];

	// refresh all materials of all actual images
	for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (!image->registration_sequence)
			continue;

		if (image->registration_sequence != registration_sequence)
			continue;

		if (!image->material)
			continue;

		image->material->registration_sequence = registration_sequence;
	}

	// kill unused materials
	for (i=0, material=materials ; i<nummaterials ; i++, material++)
	{
		if (!material->registration_sequence)
			continue;		// free material_t slot

		if (material->registration_sequence == registration_sequence)
		{
			if (material->decal_name[0])
			{
				Com_sprintf (fullname, sizeof(fullname), "textures/%s", material->decal_name);
				material->decal = GL_FindImage(fullname, it_pic, false, NULL, false, 0);
			}
			if (material->debris_name[0])
			{
				Com_sprintf (fullname, sizeof(fullname), "models/%s.ase", material->debris_name);
				material->debris = R_RegisterModel(fullname, 1, false);
				if (!material->debris)
				{
					Com_sprintf (fullname, sizeof(fullname), "models/%s.md3", material->debris_name);
					material->debris = R_RegisterModel(fullname, 1, false);
					if (!material->debris)
					{
						Com_sprintf (fullname, sizeof(fullname), "models/%s.md2", material->debris_name);
						material->debris = R_RegisterModel(fullname, 1, false);
					}
				}
			}
		}
		else
			Memset (material, 0, sizeof(*material));	// free it
	}
}


void Render_Frame (refdef_t *fd, bool bloom)
{
	do_bloom = bloom ? 0 : -1;
	R_RenderView(fd, false);
	R_SetGL2D();
	if (do_bloom == 1)
		R_RenderBloom();
}

// Спасибо Кириллу (Kirk Barnes, Q2XP) за инфу.
void R_MemInfo_f ()
{
	if (gl_config.nv_meminfo)
	{
		int mem;
		Com_Printf("\nnVidia specific memory info:\n");

		glGetIntegerv (GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX , &mem);
		Com_Printf("dedicated video memory %i MB\n", mem >>10);

		glGetIntegerv (GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX , &mem);
		Com_Printf("total available memory %i MB\n", mem >>10);

		glGetIntegerv (GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX , &mem);
		Com_Printf("currently unused GPU memory %i MB\n", mem >>10);

		glGetIntegerv (GPU_MEMORY_INFO_EVICTION_COUNT_NVX , &mem);
		Com_Printf("count of total evictions seen by system %i MB\n", mem >>10);

		glGetIntegerv (GPU_MEMORY_INFO_EVICTED_MEMORY_NVX , &mem);
		Com_Printf("total video memory evicted %i MB\n", mem >>10);
	}

	if (gl_config.ati_meminfo)
	{
		int mem[4];
		Com_Printf("\nATI/AMD specific memory info:\n");

		glGetIntegerv (VBO_FREE_MEMORY_ATI, mem);
		Com_Printf("VBO: total memory free in the pool %i MB\n", mem[0] >> 10);
		Com_Printf("VBO: largest available free block in the pool %i MB\n", mem[1] >> 10);
		Com_Printf("VBO: total auxiliary memory free %i MB\n", mem[2] >> 10);
		Com_Printf("VBO: largest auxiliary free block %i MB\n", mem[3] >> 10);

		glGetIntegerv (TEXTURE_FREE_MEMORY_ATI, mem);
		Com_Printf("Texture: total memory free in the pool %i MB\n", mem[0] >> 10);
		Com_Printf("Texture: largest available free block in the pool %i MB\n", mem[1] >> 10);
		Com_Printf("Texture: total auxiliary memory free %i MB\n", mem[2] >> 10);
		Com_Printf("Texture: largest auxiliary free block %i MB\n", mem[3] >> 10);

		glGetIntegerv (RENDERBUFFER_FREE_MEMORY_ATI, mem);
		Com_Printf("RenderBuffer: total memory free in the pool %i MB\n", mem[0] >> 10);
		Com_Printf("RenderBuffer: largest available free block in the pool %i MB\n", mem[1] >> 10);
		Com_Printf("RenderBuffer: total auxiliary memory free %i MB\n", mem[2] >> 10);
		Com_Printf("RenderBuffer: largest auxiliary free block %i MB\n", mem[3] >> 10);
	}

	if (!gl_config.nv_meminfo && !gl_config.ati_meminfo)
		Com_Printf("^1MemInfo not availabled for your video card or driver!\n");
}

void VID_Init ()
{
	/* Create the video variables so we know how to start the graphics drivers */
	vid_xpos = Cvar_Get ("vid_xpos", "3", CVAR_ARCHIVE);
	vid_xpos->help = "X coordinate of Bers@Q2 window position.";
	vid_ypos = Cvar_Get ("vid_ypos", "22", CVAR_ARCHIVE);
	vid_ypos->help = "Y coordinate of Bers@Q2 window position.";
	r_fullscreen = Cvar_Get ("r_fullscreen", "0", CVAR_ARCHIVE|CVAR_VID_LATCH);
	vid_hz = Cvar_Get ("vid_hz", "0", CVAR_ARCHIVE|CVAR_VID_LATCH);
	vid_hz->help = "allow to set the screen refresh rate (for fullscreen mode).";
///	r_bitdepth = Cvar_Get ("r_bitdepth", "0", CVAR_ARCHIVE);
	win_noalttab = Cvar_Get( "win_noalttab", "0", CVAR_ARCHIVE );
	win_noalttab->help = "disables the ALT-TAB. Doesn't affect the dedicated server.";
	vid_ignorehwgamma = Cvar_Get( "vid_ignorehwgamma", "0", CVAR_ARCHIVE|CVAR_NOSET );		// Запретим менять во время игры чтобы не было глюков с восстановлением Gamma
	vid_ignorehwgamma->help = "disables the hardware gamma control.";
	vid_gamma = Cvar_Get ("vid_gamma", "1", CVAR_ARCHIVE);
	vid_bright = Cvar_Get ("vid_bright", "2", CVAR_ARCHIVE);
	vid_contrast = Cvar_Get ("vid_contrast", "1", CVAR_ARCHIVE);
	r_gamma = Cvar_Get( "r_gamma", "1", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_intensity = Cvar_Get ("r_intensity", "2", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_modulate = Cvar_Get ("r_modulate", "0.3", CVAR_ARCHIVE|CVAR_VID_LATCH);
	r_overbright = Cvar_Get ("r_overbright", "0", CVAR_ARCHIVE|CVAR_VID_LATCH);
	hud_overbright = Cvar_Get ("hud_overbright", "0", CVAR_ARCHIVE);

	/* Add some console commands that we want to handle */
	Cmd_AddCommand ("vid_restart", VID_Restart_f);
	Cmd_AddCommand ("vid_front", VID_Front_f);

	/// Загружаем внешний video mode list
	Vid_InitModeList();

	/* Start the graphics mode*/
	vid_restart = true;
	VID_CheckChanges();

	Cmd_AddCommand ("r_meminfo", R_MemInfo_f);		// после VID_CheckChanges() !!!
}


void SCR_TimeRefresh_f ()
{
	int		i;
	int		start, stop;
	float	time;

	if ( cls.state != ca_active )
		return;

	start = Sys_Milliseconds ();

	if (Cmd_Argc() == 2)
	{	// run without page flipping
		BeginFrame();
		for (i=0 ; i<128 ; i++)
		{
			cl.refdef.viewangles[1] = i/128.0*360.0;
			R_RenderView (&cl.refdef, false);
		}
		End_Frame();
	}
	else
	{
		for (i=0 ; i<128 ; i++)
		{
			BeginFrame();
			cl.refdef.viewangles[1] = i/128.0*360.0;
			R_RenderView (&cl.refdef, false);
			End_Frame();
		}
	}

	stop = Sys_Milliseconds ();
	R_SetGL2D();
	time = (stop-start)/1000.0;
	Com_Printf ("%f seconds (%f fps)\n", time, 128/time);
}


/*
=================
SCR_SizeUp_f

Keybinding command
=================
*/
void SCR_SizeUp_f ()
{
	Cvar_SetValue ("viewsize",scr_viewsize->value+10);
}

/*
=================
SCR_SizeDown_f

Keybinding command
=================
*/
void SCR_SizeDown_f ()
{
	Cvar_SetValue ("viewsize",scr_viewsize->value-10);
}

/*
void SCR_Loading_f ()
{
	SCR_BeginLoadingPlaque ();
}
*/

/*
=================
SCR_Sky_f

Set a specific sky and rotation speed
=================
*/
void SCR_Sky_f ()
{
	float	rotate;
	vec3_t	axis;

	if (Cmd_Argc() < 2)
	{
		Com_Printf ("^3USAGE: sky <basename> <rotate> <axis x y z>\n");
		return;
	}

	if (Cmd_Argc() > 2)
		rotate = atof(Cmd_Argv(2));
	else
		rotate = 0;

	if (Cmd_Argc() == 6)
	{
		axis[0] = atof(Cmd_Argv(3));
		axis[1] = atof(Cmd_Argv(4));
		axis[2] = atof(Cmd_Argv(5));
	}
	else
	{
		axis[0] = 0;
		axis[1] = 0;
		axis[2] = 1;
	}

	SetSky (Cmd_Argv(1), rotate, axis);
}


void SCR_StartClock_f()
{
	if(clock_started)
		Com_Printf("^3Warning:^7 clock already started\n");

	start_clock = Sys_Milliseconds ();
	clock_started = true;
}


void SCR_StopClock_f()
{
	if(!clock_started)
	{
		Com_Printf("^3Clock not started\n");
		return;
	}

	clock_started = false;
	Com_Printf ("%f seconds\n", (Sys_Milliseconds() - start_clock)/1000.0);
}


void SCR_Init ()
{
	scr_mapshots = Cvar_Get ("scr_mapshots", "1", 0);
	scr_mapshots->help = "show screenshot while map loading.";
	scr_drawclock = Cvar_Get( "scr_drawclock", "0", CVAR_ARCHIVE );
	scr_drawclock->help = "print current time: 0 - disable, 1 - 24 hours, 2 - 12 hours.";
	scr_draw2d = Cvar_Get("scr_draw2d", "2", 0);
	scr_draw2d->help = "show 2d info: 0 - disable, 1 - all except a crosshair, 2 - show all.";
	scr_viewsize = Cvar_Get ("viewsize", "100", 0);
	scr_viewsize->help = "size of game window, ignored if mirrors or fullscreen bloom present.";
	scr_fps = Cvar_Get ("scr_fps", "0", CVAR_ARCHIVE);
	scr_fps->help = "show FPS counter.";
	scr_fps_min = Cvar_Get ("scr_fps_counter_min", "0", 0);
	scr_fps_min->help = "minimal value of FPS counter.";
	scr_fps_max = Cvar_Get ("scr_fps_counter_max", "0", 0);
	scr_fps_max->help = "maximal value of FPS counter.";
	scr_gametime = Cvar_Get ("scr_gametime", "0", CVAR_ARCHIVE);
	scr_texture = Cvar_Get ("scr_texture", "0", 0);
	scr_drawspeed = Cvar_Get ("scr_drawspeed", "0", 0);
	scr_drawspeed->help = "print your current and maximal speed.";
	scr_viewpos = Cvar_Get ("scr_viewpos", "0", 0);
	scr_viewpos->help = "print your current position and angles on map (for developers).";
	scr_conspeed = Cvar_Get ("scr_conspeed", "3", 0);
	scr_showpause = Cvar_Get ("scr_showpause", "1", 0);
	scr_centertime = Cvar_Get ("scr_centertime", "2.5", 0);
	r_screenshot_png_quality = Cvar_Get ("r_screenshot_png_quality", "6", CVAR_ARCHIVE);
	if(r_screenshot_png_quality->value<0)	Cvar_SetValue("r_screenshot_png_quality", 0);
	if(r_screenshot_png_quality->value>9)	Cvar_SetValue("r_screenshot_png_quality", 9);
	r_screenshot_jpg_quality = Cvar_Get ("r_screenshot_jpg_quality", "85", CVAR_ARCHIVE);
	if(r_screenshot_jpg_quality->value<1)	Cvar_SetValue("r_screenshot_jpg_quality", 1);
	if(r_screenshot_jpg_quality->value>100)	Cvar_SetValue("r_screenshot_jpg_quality", 100);
//
// register our commands
//
	Cmd_AddCommand ("timerefresh",SCR_TimeRefresh_f);
	Cmd_AddCommand ("loading",/*SCR_Loading_f*/SCR_BeginLoadingPlaque);
	Cmd_AddCommand ("sizeup",SCR_SizeUp_f);
	Cmd_AddCommand ("sizedown",SCR_SizeDown_f);
	Cmd_AddCommand ("sky",SCR_Sky_f);
	Cmd_AddCommand ("startclock",SCR_StartClock_f);
	Cmd_AddCommand ("stopclock",SCR_StopClock_f);

	scr_initialized = true;
}


void IN_CenterView ()
{
	if (old_mouse_support)
		cl.viewangles[PITCH] = -SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]);
	else
		cl.viewangles_PITCH = -SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]);
}


void GL_BuildPolygonFromSurface(msurface_t *fa)
{
	int			i, lindex, lnumverts;
	medge_t		*pedges, *r_pedge;
	int			vertpage;
	float		*vec;
	float		s, t;
	glpoly_t	*poly;
	vec3_t		total;
	temp_connect_t *tempEdge;

// reconstruct the polygon
	pedges = currentmodel->edges;
	lnumverts = fa->numedges;
	vertpage = 0;

	VectorClear (total);
	//
	// draw texture
	//
	poly = (glpoly_t*) Hunk_Alloc (sizeof(glpoly_t) + (lnumverts-4) * VERTEXSIZE*sizeof(float), true);
	poly->next = fa->polys;
///	poly->flags = fa->flags;	/// Berserker: not used???
	fa->polys = poly;
	poly->numverts = lnumverts;
	// reserve space for neighbour pointers
	// FIXME: pointers don't need to be 4 bytes
	poly->neighbours = (glpoly_s **)Hunk_Alloc (lnumverts*4, true);

/// Berserker's QUAKE2 RETEXTURING !!!
	if (fa->texinfo->image->fx_original_size_s != -1)
	{	// пересчитаем масштаб
		fa->texinfo->image->scale_s = 1.0 / fa->texinfo->image->fx_original_size_s;
		fa->texinfo->image->scale_t = 1.0 / fa->texinfo->image->fx_original_size_t;
		fa->scale_s = fa->texinfo->image->width * fa->texinfo->image->scale_s;
		fa->scale_t = fa->texinfo->image->height * fa->texinfo->image->scale_t;
	}
	else
	{
		if(fa->texinfo->flags & SURF_NEWTEX)	/// Berserker: HACK!
		{
			char		*nam;
			unsigned	wal_s, wal_t;
			if (!strncmp(fa->texinfo->image->name,"overrides",9))
			{
				if (fa->texinfo->image->scale_s)
					goto cached;

				nam = FS_GetFileName(fa->texinfo->image->name);
				// Berserker: это хак, но надеюсь что одноименные текстуры в разных папках имеют одинаковый размер...
				if (FS_GetWalSizes(va("textures/e1u1/%s", nam), &wal_s, &wal_t) ||
					FS_GetWalSizes(va("textures/e1u2/%s", nam), &wal_s, &wal_t) ||
					FS_GetWalSizes(va("textures/e1u3/%s", nam), &wal_s, &wal_t) ||
					FS_GetWalSizes(va("textures/e1u4/%s", nam), &wal_s, &wal_t) ||
					FS_GetWalSizes(va("textures/e2u1/%s", nam), &wal_s, &wal_t) ||
					FS_GetWalSizes(va("textures/e2u2/%s", nam), &wal_s, &wal_t) ||
					FS_GetWalSizes(va("textures/e2u3/%s", nam), &wal_s, &wal_t) ||
					FS_GetWalSizes(va("textures/e3u1/%s", nam), &wal_s, &wal_t) ||
					FS_GetWalSizes(va("textures/e3u2/%s", nam), &wal_s, &wal_t) ||
					FS_GetWalSizes(va("textures/e3u3/%s", nam), &wal_s, &wal_t) ||
					FS_GetWalSizes(va("textures/test/%s", nam), &wal_s, &wal_t))
					goto ok;
				else
					goto new_tex;
			}

#if 1
			unsigned hash___ = Com_HashKey___(fa->texinfo->image->name,14);
			for (i=0; i<MAX_SPEC_TEXTURES; i++)
			{
				if (hashes[i] == hash___)
					break;
			}
			if (i<MAX_SPEC_TEXTURES)
#else
			nam = fa->texinfo->image->name+9;		/// skip the "textures/"
			if (!Q_strncasecmp(nam,"e1u1",4) ||
				!Q_strncasecmp(nam,"e1u2",4) ||
				!Q_strncasecmp(nam,"e1u3",4) ||
				!Q_strncasecmp(nam,"e1u4",4) ||
				!Q_strncasecmp(nam,"e2u1",4) ||
				!Q_strncasecmp(nam,"e2u2",4) ||
				!Q_strncasecmp(nam,"e2u3",4) ||
				!Q_strncasecmp(nam,"e3u1",4) ||
				!Q_strncasecmp(nam,"e3u2",4) ||
				!Q_strncasecmp(nam,"e3u3",4) ||
				!Q_strncasecmp(nam,"test",4))
#endif
			{
				if (fa->texinfo->image->scale_s)
					goto cached;
				if(FS_GetWalSizes(fa->texinfo->image->name, &wal_s, &wal_t))
				{
ok:					fa->texinfo->image->scale_s = 1.0 / wal_s;
					fa->texinfo->image->scale_t = 1.0 / wal_t;
cached:				fa->scale_s = fa->texinfo->image->width * fa->texinfo->image->scale_s;
					fa->scale_t = fa->texinfo->image->height * fa->texinfo->image->scale_t;
				}
				else
					fa->scale_s = fa->scale_t = Q2toBERS_Detalization;		/// Если новые текстуры (PNG, TARGA or JPEG) - то увеличиваем детализацию
			}
			else
new_tex:		fa->scale_s = fa->scale_t = Q2toBERS_Detalization;			/// Если новые текстуры (PNG, TARGA or JPEG) - то увеличиваем детализацию
		}
		else
			fa->scale_s = fa->scale_t = 1;									/// Если исходные текстуры Q2 - то не меняем детализацию...
	}

	for (i=0 ; i<lnumverts ; i++)
	{
		lindex = currentmodel->surfedges[fa->firstedge + i];

		if (lindex > 0)
		{
			r_pedge = &pedges[lindex];
			vec = currentmodel->vertexes[r_pedge->v[0]].position;
		}
		else
		{
			r_pedge = &pedges[-lindex];
			vec = currentmodel->vertexes[r_pedge->v[1]].position;
		}
		s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];
		s /= fa->texinfo->image->width;

		t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];
		t /= fa->texinfo->image->height;

		VectorAdd (total, vec, total);
		VectorCopy (vec, poly->verts[i]);

		poly->verts[i][3] = s * fa->scale_s;
		poly->verts[i][4] = t * fa->scale_t;

		// lightmap texture coordinates
		s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];
		s -= fa->texturemins[0];
		s += fa->light_s*16;
		s += 8;
		s /= LIGHTMAP_SIZE*16;

		t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];
		t -= fa->texturemins[1];
		t += fa->light_t*16;
		t += 8;
		t /= LIGHTMAP_SIZE*16;

		poly->verts[i][5] = s;
		poly->verts[i][6] = t;

		if (!r_simple->value)
		{
			// Store in the tempedges table that this polygon uses the edge
			tempEdge = tempEdges+abs(lindex);
			if (tempEdge->used < 2)
			{
				tempEdge->poly[tempEdge->used]  = poly;
				tempEdge->used++;
			}
			else
				Com_DPrintf ("GL_BuildPolygonFromSurface: Edge used by more than 2 surfaces\n");
		}
	}

///	poly->numverts = lnumverts;
}


void KeyDown (kbutton_t *b)
{
	int		k;
	char	*c;
	
	c = Cmd_Argv(1);
	if (c[0])
		k = atoi(c);
	else
		k = -1;		// typed manually at the console for continuous down

	if (k == b->down[0] || k == b->down[1])
		return;		// repeating key
	
	if (!b->down[0])
		b->down[0] = k;
	else if (!b->down[1])
		b->down[1] = k;
	else
	{
		Com_Printf ("^3Three keys down for a button!\n");
		return;
	}
	
	if (b->state & 1)
		return;		// still down

	// save timestamp
	c = Cmd_Argv(2);
	b->downtime = atoi(c);
	if (!b->downtime)
		b->downtime = sys_frame_time - 100;

	b->state |= 1 + 2;	// down + impulse down
}

void KeyUp (kbutton_t *b)
{
	int		k;
	char	*c;
	unsigned	uptime;

	c = Cmd_Argv(1);
	if (c[0])
		k = atoi(c);
	else
	{ // typed manually at the console, assume for unsticking, so clear all
		b->down[0] = b->down[1] = 0;
		b->state = 4;	// impulse up
		return;
	}

	if (b->down[0] == k)
		b->down[0] = 0;
	else if (b->down[1] == k)
		b->down[1] = 0;
	else
		return;		// key up without coresponding down (menu pass through)
	if (b->down[0] || b->down[1])
		return;		// some other key is still holding it down

	if (!(b->state & 1))
		return;		// still up (this should not happen)

	// save timestamp
	c = Cmd_Argv(2);
	uptime = atoi(c);
	if (uptime)
		b->msec += uptime - b->downtime;
	else
		b->msec += 10;

	b->state &= ~1;		// now up
	b->state |= 4; 		// impulse up
}


void IN_KLookDown (void) {KeyDown(&in_klook);}
void IN_KLookUp (void) {KeyUp(&in_klook);}
void IN_UpDown(void) {KeyDown(&in_up);}
void IN_UpUp(void) {KeyUp(&in_up);}
void IN_DownDown(void) {KeyDown(&in_down);}
void IN_DownUp(void) {KeyUp(&in_down);}
void IN_LeftDown(void) {KeyDown(&in_left);}
void IN_LeftUp(void) {KeyUp(&in_left);}
void IN_RightDown(void) {KeyDown(&in_right);}
void IN_RightUp(void) {KeyUp(&in_right);}
void IN_ForwardDown(void) {KeyDown(&in_forward);}
void IN_ForwardUp(void) {KeyUp(&in_forward);}
void IN_BackDown(void) {KeyDown(&in_back);}
void IN_BackUp(void) {KeyUp(&in_back);}
void IN_LookupDown(void) {KeyDown(&in_lookup);}
void IN_LookupUp(void) {KeyUp(&in_lookup);}
void IN_LookdownDown(void) {KeyDown(&in_lookdown);}
void IN_LookdownUp(void) {KeyUp(&in_lookdown);}
void IN_MoveleftDown(void) {KeyDown(&in_moveleft);}
void IN_MoveleftUp(void) {KeyUp(&in_moveleft);}
void IN_MoverightDown(void) {KeyDown(&in_moveright);}
void IN_MoverightUp(void) {KeyUp(&in_moveright);}

void IN_SpeedDown(void) {KeyDown(&in_speed);}
void IN_SpeedUp(void) {KeyUp(&in_speed);}
void IN_StrafeDown(void) {KeyDown(&in_strafe);}
void IN_StrafeUp(void) {KeyUp(&in_strafe);}

void IN_AttackDown(void) {KeyDown(&in_attack);}
void IN_AttackUp(void) {KeyUp(&in_attack);}

void IN_UseDown (void) {KeyDown(&in_use);}
void IN_UseUp (void) {KeyUp(&in_use);}

void IN_Impulse (void) {in_impulse=atoi(Cmd_Argv(1));}

void IN_FlashLightDown (void)
{
	if (r_simple->value)		// Hack: запретить фонарик для simple-рендера
	{
		in_flashlight.down[0] = in_flashlight.down[1] = 0;
		in_flashlight.state = 0;
	}
	else
		KeyDown(&in_flashlight);
}
void IN_FlashLightUp (void)
{
	if (r_simple->value)		// Hack: Hack: запретить фонарик для simple-рендера
	{
		in_flashlight.down[0] = in_flashlight.down[1] = 0;
		in_flashlight.state = 0;
	}
	else
		KeyUp(&in_flashlight);
}

void IN_ZoomDown (void) {KeyDown(&in_zoom);}
void IN_ZoomUp (void) {KeyUp(&in_zoom);}



void CL_InitInput ()
{
	Cmd_AddCommand ("centerview",IN_CenterView);

	Cmd_AddCommand ("+moveup",IN_UpDown);
	Cmd_AddCommand ("-moveup",IN_UpUp);
	Cmd_AddCommand ("+movedown",IN_DownDown);
	Cmd_AddCommand ("-movedown",IN_DownUp);
	Cmd_AddCommand ("+left",IN_LeftDown);
	Cmd_AddCommand ("-left",IN_LeftUp);
	Cmd_AddCommand ("+right",IN_RightDown);
	Cmd_AddCommand ("-right",IN_RightUp);
	Cmd_AddCommand ("+forward",IN_ForwardDown);
	Cmd_AddCommand ("-forward",IN_ForwardUp);
	Cmd_AddCommand ("+back",IN_BackDown);
	Cmd_AddCommand ("-back",IN_BackUp);
	Cmd_AddCommand ("+lookup", IN_LookupDown);
	Cmd_AddCommand ("-lookup", IN_LookupUp);
	Cmd_AddCommand ("+lookdown", IN_LookdownDown);
	Cmd_AddCommand ("-lookdown", IN_LookdownUp);
	Cmd_AddCommand ("+strafe", IN_StrafeDown);
	Cmd_AddCommand ("-strafe", IN_StrafeUp);
	Cmd_AddCommand ("+moveleft", IN_MoveleftDown);
	Cmd_AddCommand ("-moveleft", IN_MoveleftUp);
	Cmd_AddCommand ("+moveright", IN_MoverightDown);
	Cmd_AddCommand ("-moveright", IN_MoverightUp);
	Cmd_AddCommand ("+speed", IN_SpeedDown);
	Cmd_AddCommand ("-speed", IN_SpeedUp);
	Cmd_AddCommand ("+attack", IN_AttackDown);
	Cmd_AddCommand ("-attack", IN_AttackUp);
	Cmd_AddCommand ("+use", IN_UseDown);
	Cmd_AddCommand ("-use", IN_UseUp);
	Cmd_AddCommand ("impulse", IN_Impulse);
	Cmd_AddCommand ("+klook", IN_KLookDown);
	Cmd_AddCommand ("-klook", IN_KLookUp);
// Berserker's flashlight
	Cmd_AddCommand ("+flashlight", IN_FlashLightDown);
	Cmd_AddCommand ("-flashlight", IN_FlashLightUp);
// Berserker's zoom
	Cmd_AddCommand ("+zoom", IN_ZoomDown);
	Cmd_AddCommand ("-zoom", IN_ZoomUp);

	cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0);
}


/*
==================
IN_Frame

Called every frame, even if not generating commands
==================
*/
void IN_Frame ()
{
	if (bind_grab)	/// Berserker's FIX: в оконном режиме не биндились мышкины кнопки и колесо.
	{
		IN_ActivateMouse();
		return;
	}

	if (old_mouse_support)
	{
		if (!mouseinitialized)
			return;

		if (!in_mouse || !in_appactive)
		{
			IN_DeactivateMouse ();
			return;
		}
	}
	else
	{
		if (!in_appactive)
		{
			IN_DeactivateMouse ();
			return;
		}
	}

	if ( !cl.refresh_prepped
		|| cls.key_dest == key_console
		|| cls.key_dest == key_menu)
	{
		// temporarily deactivate if in fullscreen
		if (Cvar_VariableValue ("r_fullscreen") == 0)
		{
			IN_DeactivateMouse ();
			return;
		}
	}

	IN_ActivateMouse ();
}


void CL_ForwardToServer_f ()
{
	if (cls.state != ca_connected && cls.state != ca_active)
	{
		Com_Printf ("^1Can't \"%s\", not connected\n", Cmd_Argv(0));
		return;
	}
	
	// don't forward the first argument
	if (Cmd_Argc() > 1)
	{
		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
		SZ_Print (&cls.netchan.message, Cmd_Args());
	}
}


/*
===============
Netchan_OutOfBand

Sends an out-of-band datagram
================
*/
void Netchan_OutOfBand (int net_socket, netadr_t adr, int length, byte *data)
{
	sizebuf_t	send;
	byte		send_buf[MAX_MSGLEN];

// write the packet header
	SZ_Init (&send, send_buf, sizeof(send_buf));
	
	MSG_WriteLong (&send, -1);	// -1 sequence means out of band
	SZ_Write (&send, data, length);

// send the datagram
	NET_SendPacket ((netsrc_t)net_socket, send.cursize, send.data, adr);
}

/*
===============
Netchan_OutOfBandPrint

Sends a text message in an out-of-band datagram
================
*/
void Netchan_OutOfBandPrint (int net_socket, netadr_t adr, char *format, ...)
{
	va_list		argptr;
	static char		string[MAX_MSGLEN - 4];
	
	va_start (argptr, format);
	vsprintf (string, format,argptr);
	va_end (argptr);

	Netchan_OutOfBand (net_socket, adr, strlen(string), (byte *)string);
}


void CL_PingServers_f ()
{
	int			i;
	netadr_t	adr;
	char		name[32];
	char		*adrstring;
	cvar_t		*noudp;
	cvar_t		*noipx;

	NET_Config (true);		// allow remote

	// send a broadcast packet
	Com_Printf ("pinging broadcast...\n");

	noudp = Cvar_Get ("noudp", "0", CVAR_NOSET);
	if (!noudp->value)
	{
		adr.type = NA_BROADCAST;
		if(net_compatibility->value)
		{
			adr.port = BigShort(OLD_PORT_SERVER);
			Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", OLD_PROTOCOL_VERSION));
		}
		else
		{
			adr.port = BigShort(PORT_SERVER);
			Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION));
		}
	}

	noipx = Cvar_Get ("noipx", "0", CVAR_NOSET);
	if (!noipx->value)
	{
		adr.type = NA_BROADCAST_IPX;
		if(net_compatibility->value)
		{
			adr.port = BigShort(OLD_PORT_SERVER);
			Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", OLD_PROTOCOL_VERSION));
		}
		else
		{
			adr.port = BigShort(PORT_SERVER);
			Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION));
		}
	}

	// send a packet to each address book entry
	for (i=0 ; i<16 ; i++)
	{
		Com_sprintf (name, sizeof(name), "adr%i", i);
		adrstring = Cvar_VariableString (name);
		if (!adrstring || !adrstring[0])
			continue;

		Com_Printf ("pinging %s...\n", adrstring);
		if (!NET_StringToAdr (adrstring, &adr))
		{
			Com_Printf ("^1Bad address: %s\n", adrstring);
			continue;
		}
		if (!adr.port)
		{
			if(net_compatibility->value)
				adr.port = BigShort(OLD_PORT_SERVER);
			else
				adr.port = BigShort(PORT_SERVER);
		}
		if(net_compatibility->value)
			Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", OLD_PROTOCOL_VERSION));
		else
			Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION));
	}
}


void CL_Userinfo_f ()
{
	Com_Printf ("User info settings:\n");
	Info_Print (Cvar_Userinfo(true));
}


///void CL_Disconnect_f ()
///{
///	Com_Error (ERR_DROP, "Disconnected from server");
///}


void CL_Quit_f ()
{
	CL_Disconnect ();
	Com_Quit ();
}


void CL_Connect_f ()
{
	char	*server;

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3USAGE: connect <server>\n");
		return;	
	}
	
	saved_shot_dir[0] = 0;
	if (Com_ServerState ())
	{	// if running a local server, kill it and reissue
		SV_Shutdown (va("Server quit\n", msg), false);
	}
	else
	{
		CL_Disconnect ();
	}

	server = Cmd_Argv (1);

	NET_Config (true);		// allow remote

	CL_Disconnect ();

	cls.state = ca_connecting;
	strncpy (cls.servername, server, sizeof(cls.servername)-1);
	cls.connect_time = -99999;	// CL_CheckForResend() will fire immediately
}


/*
=================
CL_Reconnect_f

The server is changing levels
=================
*/
void CL_Reconnect_f ()
{
	//ZOID
	//if we are downloading, we don't change!  This so we don't suddenly stop downloading a map
	if (cls.download)
		return;

	S_StopAllSounds ();
///		CL_ClearClEntities ();
		CL_ResetEditor();
	saved_shot_dir[0] = 0;

	if (cls.state == ca_connected)
	{
		Com_Printf ("reconnecting...\n");
///		cls.state = ca_connected;
		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
		MSG_WriteString (&cls.netchan.message, "new");		
		return;
	}

	if (*cls.servername)
	{
		if (cls.state >= ca_connected)
		{
			CL_Disconnect();
			cls.connect_time = cls.realtime - 1500;
		}
		else
			cls.connect_time = -99999; // fire immediately

		cls.state = ca_connecting;
		Com_Printf ("reconnecting...\n");
	}
}


/*
=====================
CL_Rcon_f

  Send the rest of the command line over as
  an unconnected command.
=====================
*/
void CL_Rcon_f ()
{
	char	message[1024];
	int		i;
	netadr_t	to;

	if (!rcon_client_password->string)
	{
		Com_Printf ("^3You must set 'rcon_password' before\n"
					"issuing an rcon command.\n");
		return;
	}

	message[0] = (char)255;
	message[1] = (char)255;
	message[2] = (char)255;
	message[3] = (char)255;
	message[4] = 0;

	NET_Config (true);		// allow remote

	strcat (message, "rcon ");

	strcat (message, rcon_client_password->string);
	strcat (message, " ");

	for (i=1 ; i<Cmd_Argc() ; i++)
	{
		strcat (message, Cmd_Argv(i));
		strcat (message, " ");
	}

	if (cls.state >= ca_connected)
		to = cls.netchan.remote_address;
	else
	{
		if (!strlen(rcon_address->string))
		{
			Com_Printf ("^3You must either be connected,\n"
						"or set the 'rcon_address' cvar\n"
						"to issue rcon commands\n");

			return;
		}
		NET_StringToAdr (rcon_address->string, &to);
		if (!to.port)
		{
			if(net_compatibility->value)
				to.port = BigShort(OLD_PORT_SERVER);
			else
				to.port = BigShort(PORT_SERVER);
		}
	}
	
	NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to);
}


int	paged_total;

void Com_PageInMemory (byte *buffer, int size)
{
	int		i;

	for (i=size-1 ; i>0 ; i-=4096)
		paged_total += buffer[i];
}


/*
void CL_RegisterTEntSounds ()
{
	cl_sfx_lava = S_RegisterSound ("player/lava_in.wav");
	cl_sfx_watershell = S_RegisterSound ("misc/water_shell.wav");
	cl_sfx_mbrass = S_RegisterSound ("misc/mbrass_shell.wav");			// CLMOD_SFX_MSHELL
	cl_sfx_sbrass = S_RegisterSound ("misc/sbrass_shell.wav");			// CLMOD_SFX_SSHELL
	cl_sfx_debris = S_RegisterSound ("misc/debris.wav");				// CLMOD_SFX_METAL
	cl_sfx_debris_glass = S_RegisterSound ("misc/glass.wav");			// CLMOD_SFX_GLASS
	cl_sfx_debris_metal = S_RegisterSound ("misc/metal.wav");			// CLMOD_SFX_GEAR
	cl_sfx_debris_flesh = S_RegisterSound ("misc/flesh.wav");			// CLMOD_SFX_FLESH
	cl_sfx_ric1 = S_RegisterSound ("world/ric1.wav");
	cl_sfx_ric2 = S_RegisterSound ("world/ric2.wav");
	cl_sfx_ric3 = S_RegisterSound ("world/ric3.wav");
	cl_sfx_lashit = S_RegisterSound("weapons/lashit.wav");
	cl_sfx_spark5 = S_RegisterSound ("world/spark5.wav");
	cl_sfx_spark6 = S_RegisterSound ("world/spark6.wav");
	cl_sfx_spark7 = S_RegisterSound ("world/spark7.wav");
	cl_sfx_railg = S_RegisterSound ("weapons/railgf1a.wav");
	cl_sfx_rockexp = S_RegisterSound ("weapons/rocklx1a.wav");
	cl_sfx_grenexp = S_RegisterSound ("weapons/grenlx1a.wav");
	cl_sfx_watrexp = S_RegisterSound ("weapons/xpld_wat.wav");

	S_RegisterSound ("player/land1.wav");
	S_RegisterSound ("player/fall2.wav");
	S_RegisterSound ("player/fall1.wav");

//PGM
	cl_sfx_lightning = S_RegisterSound ("weapons/tesla.wav");
}	
*/

void S_RegisterMatSounds(material_t *material)
{
	char		fullname[MAX_QPATH];

	if (material->footstep_name[0])
	{
		Com_sprintf (fullname, sizeof(fullname), "%s.wav", material->footstep_name);
		material->footstep = S_RegisterSound (fullname);
	}

	if (material->hs_bullet_name[0])
	{
		Com_sprintf (fullname, sizeof(fullname), "%s.wav", material->hs_bullet_name);
		material->hs_bullet = S_RegisterSound (fullname);
	}

	if (material->hs_energy_name[0])
	{
		Com_sprintf (fullname, sizeof(fullname), "%s.wav", material->hs_energy_name);
		material->hs_energy = S_RegisterSound (fullname);
	}
}

void S_RegisterMaterialSounds ()
{
	int			i;
	material_t	*material;

	for (i=0, material=materials ; i<nummaterials ; i++, material++)
	{
		if (!material->registration_sequence)
			continue;		// free material_t slot

		S_RegisterMatSounds(material);
	}
}

/*
=================
CL_Changing_f

Just sent as a hint to the client that they should
drop to full console
=================
*/
void CL_Changing_f ()
{
	//ZOID
	//if we are downloading, we don't change!  This so we don't suddenly stop downloading a map
	if (cls.download)
		return;

	SCR_BeginLoadingPlaque ();
	cls.state = ca_connected;	// not active anymore, but not disconnected
	Com_Printf ("\nChanging map...\n");
}


/*
=================
CL_Snd_Restart_f

Restart the sound subsystem so it can pick up
new parameters and flush all sounds
=================
*/
void CL_Snd_Restart_f ()
{
	S_Shutdown ();
	S_Init ();
	CL_RegisterSounds ();
	Music_Play(NULL);
}


model_s *R_RegisterModel (char *name, float scale, bool invert)		// scale - for md2, md3, ase only
{
	model_t			*mod;
	int				i, len;
	dmdl_t			*pheader;
	maliasmodel_t	*pheader3;
	dsprite_t		*sprout;
	dsprite2_t		*sprout2;

	len = strlen(name);
	if (Q_strcasecmp(name+len-4, ".ase")) // .ase модели ничем не подменяем!
	{
///		if(r_iqm->value)
///		{
///			// IQM autoreplace code
///			if (!Q_strcasecmp(name+len-4, ".md2")) // look if we have a .iqm file
///			{
///				char s[MAX_QPATH];
///				strcpy(s,name);
///				s[len - 3] = 'i';
///				s[len - 2] = 'q';
///				s[len - 1] = 'm';
///				mod = R_RegisterModel (s, scale, invert);
///				if (mod)
///					return mod;
///			}
///		}

		if (r_md3->value)
		{
			// MD3 autoreplace code
			if (!Q_strcasecmp(name + len - 4, ".md2")) // look if we have a .md3 file
			{
				char s[MAX_QPATH];
				strcpy(s, name);
				s[len - 1] = '3';
				mod = R_RegisterModel(s, scale, invert);
				if (mod)
					return mod;
			}
		}

		if(r_sp3->value)
		{
			// Berserker's SP3 autoreplace code
			if (!Q_strcasecmp(name+len-4, ".sp2")) // look if we have a .sp3 file
			{
				char s[MAX_QPATH];
				strcpy(s,name);
				s[len-1]='3';
				mod = R_RegisterModel (s, scale, invert);
				if (mod)
					return mod;
			}
		}
	}

	mod = Mod_ForName(name, false, scale, invert, false);
	if (mod)
	{
		mod->registration_sequence = registration_sequence;

		// register any images used by the models
		if (mod->type == mod_sprite)
		{
			sprout = (dsprite_t *)mod->extradata;
			for (i=0 ; i<sprout->numframes ; i++)
				mod->skins[i] = GL_FindImage (sprout->frames[i].name, it_sprite, false, 0, false, 0);	/// !!! Для спрайтов не бывает бампа!!!
		}
		else if (mod->type == mod_sprite2)
		{
			sprout2 = (dsprite2_t *)mod->extradata;
			for (i=0 ; i<sprout2->numframes ; i++)
				mod->skins[i] = GL_FindImage (sprout2->frames[i].name, it_sprite, false, 0, false, 0);	/// !!! Для спрайтов не бывает бампа!!!
		}
		else
		if (mod->type == mod_alias)
		{
			pheader = (dmdl_t *)mod->extradata;
			for (i=0 ; i<pheader->num_skins ; i++)
			{
#if 1
				if (mod->skins[i] && /*mod->skins[i]->name &&*/ mod->skins[i]->name[0])
					mod->skins[i]->registration_sequence = registration_sequence;
				if (mod->bumps[i] && /*mod->bumps[i]->name &&*/ mod->bumps[i]->name[0])
					mod->bumps[i]->registration_sequence = registration_sequence;
				if (mod->lights[i] && /*mod->lights[i]->name &&*/ mod->lights[i]->name[0])
					mod->lights[i]->registration_sequence = registration_sequence;
#else
				char nam[MAX_QPATH+8];
				len = strlen((char *)pheader + pheader->ofs_skins + i*MAX_SKINNAME);
				if(len<5)		// Если встретился меш с коротким именем, то это фигня, игнорируем!
				{
					mod->bumps[i] = mod->lights[i] = NULL;
					continue;
				}
				Memcpy(nam, (char *)pheader + pheader->ofs_skins + i*MAX_SKINNAME, len+1);
				nam[len-4] = 0;
				mod->skins[i] = GL_FindImage (nam, it_skin, false, 0, false, 0);
				if(!r_simple->value)
				{
					nam[len-4] = '_';
					nam[len-3] = 'b';
					nam[len-2] = 'u';
					nam[len-1] = 'm';
					nam[len  ] = 'p';
					nam[len+1] = '.';
					nam[len+2] = 't';
					nam[len+3] = 'g';
					nam[len+4] = 'a';
					nam[len+5] = 0;
					mod->bumps[i] = GL_FindImage (nam, it_bump, true, mod->skins[i], false, 0);
					if (mod->bumps[i] == r_defaultbump)
					{	// если нет bump-скина TGA, то попробуем DDS
						nam[len+2] = 'd';
						nam[len+3] = 'd';
						nam[len+4] = 's';
						mod->bumps[i] = GL_FindImage (nam, it_bump, true, mod->skins[i], (r_bumpscale->value && !(mod->flags & RF_NOAUTOBUMP)) ? true : false, 0);
					}
				}
				else
					mod->bumps[i] = NULL;

				nam[len-4] = '_';
				nam[len-3] = 'l';
				nam[len-2] = 'i';
				nam[len-1] = 'g';
				nam[len  ] = 'h';
				nam[len+1] = 't';
				nam[len+2] = 0;
				mod->lights[i] = GL_FindImage (nam, it_light, false, 0, false, 0);
#endif
			}
			mod->numframes = pheader->num_frames;
		}
		// Harven++ MD3
		else if (mod->type == mod_alias_md3)
		{
			pheader3 = (maliasmodel_t *)mod->extradata;
			int	k;
			maliasmesh_t *mesh = pheader3->meshes;
			for (i=0; i<pheader3->num_meshes; i++, mesh++)
			{
				for (k=0; k<mesh->num_skins; k++)
				{
#if 1
					if (mesh->img_skins[k] && /*mesh->img_skins[k]->name &&*/ mesh->img_skins[k]->name[0])
						mesh->img_skins[k]->registration_sequence = registration_sequence;
					if (mesh->img_bumps[k] && /*mesh->img_bumps[k]->name &&*/ mesh->img_bumps[k]->name[0])
						mesh->img_bumps[k]->registration_sequence = registration_sequence;
					if (mesh->img_lights[k] && /*mesh->img_lights[k]->name &&*/ mesh->img_lights[k]->name[0])
						mesh->img_lights[k]->registration_sequence = registration_sequence;
#else
					if (!mesh->img_skins[k]->name || !mesh->img_skins[k]->name[0])
					{
						mesh->img_bumps[k] = mesh->img_lights[k] = NULL;
						continue;
					}
					char nam[MAX_QPATH+16];
					len = strlen(mesh->img_skins[k]->name);
					Memcpy(nam, mesh->img_skins[k]->name, len+1);
					mesh->img_skins[k] = GL_FindImage(nam, it_skin, false, 0, false, 0);
					if(mesh->img_skins[k] && !r_simple->value)
					{
						nam[len  ] = '_';
						nam[len+1] = 'b';
						nam[len+2] = 'u';
						nam[len+3] = 'm';
						nam[len+4] = 'p';
						nam[len+5] = '.';
						nam[len+6] = 't';
						nam[len+7] = 'g';
						nam[len+8] = 'a';
						nam[len+9] = 0;
						mesh->img_bumps[k] = GL_FindImage (nam, it_bump, true, mesh->img_skins[k], false, 0);
						if (mesh->img_bumps[k] == r_defaultbump)
						{	// если нет bump-скина TGA, то попробуем DDS
							nam[len+6] = 'd';
							nam[len+7] = 'd';
							nam[len+8] = 's';
							mesh->img_bumps[k] = GL_FindImage (nam, it_bump, true, mesh->img_skins[k], (r_bumpscale->value && !(mod->flags & RF_NOAUTOBUMP)) ? true : false, 0);
						}
					}
					else
						mesh->img_bumps[k] = NULL;

					nam[len  ] = '_';
					nam[len+1] = 'l';
					nam[len+2] = 'i';
					nam[len+3] = 'g';
					nam[len+4] = 'h';
					nam[len+5] = 't';
					nam[len+6] = 0;
					mesh->img_lights[k] = GL_FindImage (nam, it_light, false, 0, false, 0);
#endif
				}
			}
			mod->numframes = pheader3->num_frames;
		}
		// Harven-- MD3
		else if (mod->type == mod_brush)
		{
			for (i=0 ; i<mod->numtexinfo ; i++)
			{
				mod->texinfo[i].image->registration_sequence = mod->texinfo[i].bump->registration_sequence = registration_sequence;
				if(mod->texinfo[i].light)
					mod->texinfo[i].light->registration_sequence = registration_sequence;
			}
		}
		return mod;
	}
	else
	{
		// Если мы запросили md2 файл, причем реально имеется только md3,
		// и в то же время r_md3=0, то все-таки загрузим md3 версию.
		if (!Q_strcasecmp(name+len-4, ".md2")) // look if we have a .md3 file
		{
			char s[MAX_QPATH];
			strcpy(s,name);
			s[len-1]='3';
			return R_RegisterModel (s, scale, invert);
		}

		// Если мы запросили ase файл и его нет, попытаемся загрузить md3
		if (!Q_strcasecmp(name+len-4, ".ase")) // look if we have a .md3 file
		{
			char s[MAX_QPATH];
			strcpy(s,name);
			s[len-3]='m';
			s[len-2]='d';
			s[len-1]='3';
			return R_RegisterModel (s, scale, invert);
		}
	}
	return NULL;	// Ничего не нашли...
}


/*
====================
CL_Record_f

record <demoname>

Begins recording a demo from the current position
====================
*/
void CL_Record_f ()
{
	char	name[MAX_QPATH+MAX_QPATH+16];
	char	name2[MAX_QPATH+MAX_QPATH+16];
	byte	buf_data[MAX_MSGLEN];
	sizebuf_t	buf;
	int		i;
	int		len;
	entity_state_t	*ent;
	entity_state_t	nullstate;
	char	*c;

	if (cl.attractloop)	// Fixed by berserker: если в демо-записи будет команда записи демо, то не будем писать.
	{					// Такая фигня была в дёмке enforcer'а
		Com_Printf ("^3Ignored ^1record^3 in demo.\n");
		return;
	}

	if (cls.demorecording)
	{
		Com_Printf ("^3Already recording.\n");
		return;
	}

	if (cls.state != ca_active)
	{
		Com_Printf ("^3You must be in a level to record.\n");
		return;
	}

	if (Cmd_Argc() != 2)
	{
		/// Berserker: auto generate demofile name
		time_t		clock;
		struct tm	*localTime;
		char		cc, out[64];
		int			ii = 0;

		time( &clock );
		localTime = localtime( &clock );

		char *text = cl.clientinfo->name;
		while (1)
		{
povtor:		if (!(cc = *text))
			{
				out[ii] = 0;
				break;
			}

			if (Q_IsColorString(text))
			{
				text+=2;		// Удаляем цветные метки
				goto povtor;
			}

			text++;
			out[ii++] = cc;
		}

		Com_sprintf (name2, sizeof(name2), "%s_%s_(%i-%02i-%02i)", out, cl.configstrings[0], localTime->tm_hour, localTime->tm_min, localTime->tm_sec);

		// проверяем чтоб не было в имени файла пробелов и спец-символов '*' и '?'. Заменяем их соответственно на '_', '#' и '!'.
		c = name2;
		while (*c)
		{
			if(*c==' ')
				*c='_';
			else if(*c=='*')
				*c='#';
			else if(*c=='?')
				*c='!';
			c++;
		}

		c = name2;
	}
	else
		c = Cmd_Argv(1);

	//
	// open the demo file
	//
	if(net_compatibility->value)
		Com_sprintf (name, sizeof(name), "%s/demos/%s.dm2", FS_Gamedir(), c);
	else
		Com_sprintf (name, sizeof(name), "%s/demos/%s.dem", FS_Gamedir(), c);

	Com_Printf ("recording to %s.\n", name);
	FS_CreatePath (name);
	cls.demofile = FS_Fopen(name, "wb");
	if (!cls.demofile)
	{
		Com_Printf ("^1ERROR: couldn't open.\n");
		return;
	}
	cls.demorecording = true;
	cls.demolength = 0;

	// don't start saving messages until a non-delta compressed message is received
	cls.demowaiting = true;

	//
	// write out messages to hold the startup information
	//
	SZ_Init (&buf, buf_data, sizeof(buf_data));

	// send the serverdata
	MSG_WriteByte (&buf, svc_serverdata);
	if(net_compatibility->value)
		MSG_WriteLong (&buf, OLD_PROTOCOL_VERSION);
	else
		MSG_WriteLong (&buf, PROTOCOL_VERSION);
	MSG_WriteLong (&buf, 0x10000 + cl.servercount);
	MSG_WriteByte (&buf, 1);	// demos are always attract loops
	MSG_WriteString (&buf, cl.gamedir);
	MSG_WriteShort (&buf, cl.playernum);

	MSG_WriteString (&buf, cl.configstrings[CS_NAME]);

	int	max_configstrings;
	if (net_compatibility->value)
		max_configstrings = MAX_CONFIGSTRINGS_Q2;
	else
		max_configstrings = MAX_CONFIGSTRINGS_BERS;
	// configstrings
	for (i=0 ; i<max_configstrings ; i++)
	{
		if (cl.configstrings[i][0])
		{
			if (buf.cursize + strlen (cl.configstrings[i]) + 32 > buf.maxsize)
			{	// write it out
				len = LittleLong (buf.cursize);
				fwrite (&len, 4, 1, cls.demofile);
				fwrite (buf.data, buf.cursize, 1, cls.demofile);
				cls.demolength += buf.cursize + 4;
				buf.cursize = 0;
			}

			MSG_WriteByte (&buf, svc_configstring);
			MSG_WriteShort (&buf, i);
			MSG_WriteString (&buf, cl.configstrings[i]);
		}

	}

	// baselines
	Memset (&nullstate, 0, sizeof(nullstate));
	for (i=0; i<MAX_EDICTS ; i++)
	{
		ent = &cl_entities[i].baseline;
		if (!ent->modelindex)
			continue;

		if (buf.cursize + 64 > buf.maxsize)
		{	// write it out
			len = LittleLong (buf.cursize);
			fwrite (&len, 4, 1, cls.demofile);
			fwrite (buf.data, buf.cursize, 1, cls.demofile);
			cls.demolength += buf.cursize + 4;
			buf.cursize = 0;
		}

		MSG_WriteByte (&buf, svc_spawnbaseline);
		MSG_WriteDeltaEntity (&nullstate, &cl_entities[i].baseline, &buf, true, true);
	}

	MSG_WriteByte (&buf, svc_stufftext);
	MSG_WriteString (&buf, "precache\n");

	// write it to the demo file
	len = LittleLong (buf.cursize);
	fwrite (&len, 4, 1, cls.demofile);
	fwrite (buf.data, buf.cursize, 1, cls.demofile);
	cls.demolength += buf.cursize + 4;

	// the rest of the demo file will be individual frames
}


/*
===============
SCR_TouchPics

Allows rendering code to cache all needed sbar graphics
===============
*/
void SCR_TouchPics ()
{
	int		i, j;

	for (i=0 ; i<2 ; i++)
		for (j=0 ; j<11 ; j++)
			Draw_FindPic (sb_nums[i][j]);

	if (r_crosshair_image->string[0])
		Com_sprintf (crosshair_pic, sizeof(crosshair_pic), "%s", r_crosshair_image->string);
	else if (crosshair->value)
	{
		if (crosshair->value > MAX_CROSSHAIR || crosshair->value < 0)
			crosshair->value = MAX_CROSSHAIR;
		Com_sprintf (crosshair_pic, sizeof(crosshair_pic), "crosshair%i", (int)(crosshair->value));
	}

	Draw_GetPicSize (&crosshair_width, &crosshair_height, crosshair_pic);
	if (!crosshair_width)
		crosshair_pic[0] = 0;
}


void CL_DownloadFileName(char *dest, int destlen, char *fn)
{
	if (strncmp(fn, "players", 7) == 0)
		Com_sprintf (dest, destlen, "%s/%s", BASEDIRNAME, fn);
	else
		Com_sprintf (dest, destlen, "%s/%s", FS_Gamedir(), fn);
}


/*
===============
CL_CheckOrDownloadFile

Returns true if the file exists, otherwise it attempts
to start a download from the server.
===============
*/
bool	CL_CheckOrDownloadFile (char *filename, bool picture)
{
	FILE *fp;
	char	name[MAX_OSPATH];
	char	fname[MAX_OSPATH];		// Berserker: здесь реальное имя графического файла
	char	fname2[MAX_OSPATH];		// Berserker: здесь реальное имя графического файла

	if (strstr (filename, ".."))
	{
		Com_DPrintf ("Quake2 error: Refusing to download a path with ..\n");
		Com_DPrintf ("Berserker: '%s' fixed to ",filename);
		while (RepairPath(filename));	/// Berserker: устранение безобразия типа в модели: "models\monsters\tank\tris.md2" - там есть строки типа "models/monsters/tank/../ctank/skin.pcx"
										/// FIXME: может убрать эту проверку и просто исправить ручками подобную строку на "models/monsters/ctank/skin.pcx"
										/// Признак ошибки: сообщения при загрузке "Refusing to download a path with .."

		Com_DPrintf ("'%s'\n",filename);
///		return true;
	}

///	strcpy(fname, filename);
FS_GetName(fname, filename);
strcpy(fname2, fname);
	if (FS_LoadFile (fname, NULL) == -1)
	{
		if (picture)
		{
			// if picture not exist, try again with PNG
			Com_sprintf (fname, sizeof(fname), "%s.png", fname2);
			if (FS_LoadFile (fname, NULL) == -1)
			{	// if not exist, try again with TGA
				Com_sprintf (fname, sizeof(fname), "%s.tga", fname2);
				if (FS_LoadFile (fname, NULL) == -1)
				{	// if not exist, try again with DDS
					Com_sprintf (fname, sizeof(fname), "%s.dds", fname2);
					if (FS_LoadFile (fname, NULL) == -1)
					{	// if not exist, try again with JPG
						Com_sprintf (fname, sizeof(fname), "%s.jpg", fname2);
						if (FS_LoadFile (fname, NULL) == -1)
						{	// if not exist, try again with WAL
							Com_sprintf (fname, sizeof(fname), "%s.wal", fname2);
							if (FS_LoadFile (fname, NULL) == -1)
							{	// if not exist, try again with PCX
								Com_sprintf (fname, sizeof(fname), "%s.pcx", fname2);
								if (FS_LoadFile (fname, NULL) == -1)
									goto dwnld;
								else
									return true;	// pcx
							}
							else
								return true;	// wal
						}
						else
							return true;	// jpg
					}
					else
						return true;	// dds
				}
				else
					return true;	// tga
			}
			else
				return true;	// png
		}
	}
	else
		return true;	// not picture

dwnld:
	strcpy (cls.downloadname, fname2);	// Berserker: server грузит файлы только по точным именам!

	// download to a temp name, and only rename
	// to the real name when done, so if interrupted
	// a runt file wont be left
	COM_StripExtension (cls.downloadname, cls.downloadtempname);
	strcat (cls.downloadtempname, ".tmp");

//ZOID
	// check to see if we already have a tmp for this file, if so, try to resume
	// open the file if not opened yet
	CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);

	fp = FS_Fopen (name, "r+b");
	if (fp)
	{ // it exists
		int len;
		fseek(fp, 0, SEEK_END);
		len = ftell(fp);

		cls.download = fp;

		// give the server an offset to start the download
		Com_Printf ("Resuming %s\n", cls.downloadname);
		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
		MSG_WriteString (&cls.netchan.message, va("download %s %i", cls.downloadname, len));
	} else {
		Com_Printf ("Downloading %s\n", cls.downloadname);
		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
		MSG_WriteString (&cls.netchan.message, va("download %s", cls.downloadname));
	}

///	cls.downloadnumber++;
	return false;
}


void CL_RequestNextDownload ()
{
	unsigned	map_checksum;		// for detecting cheater maps
	char fn[MAX_OSPATH];
	dmdl_t *pheader;

	if (cls.state != ca_connected)
		return;

	int env_cnt, texture_cnt, max_models, max_sounds, max_images, cs_sounds, cs_images, cs_playerskins;
	if (net_compatibility->value)
	{
		env_cnt = ENV_CNT_Q2;
		texture_cnt = TEXTURE_CNT_Q2;
		max_models = MAX_MODELS_Q2;
		max_sounds = MAX_SOUNDS_Q2;
		max_images = MAX_IMAGES_Q2;
		cs_sounds = CS_SOUNDS_Q2;
		cs_images = CS_IMAGES_Q2;
		cs_playerskins = CS_PLAYERSKINS_Q2;
	}
	else
	{
		env_cnt = ENV_CNT_BERS;
		texture_cnt = TEXTURE_CNT_BERS;
		max_models = MAX_MODELS_BERS;
		max_sounds = MAX_SOUNDS_BERS;
		max_images = MAX_IMAGES_BERS;
		cs_sounds = CS_SOUNDS_BERS;
		cs_images = CS_IMAGES_BERS;
		cs_playerskins = CS_PLAYERSKINS_BERS;
	}

	if (!allow_download->value && precache_check < env_cnt)
		precache_check = env_cnt;

//ZOID
	if (precache_check == CS_MODELS)
	{ // confirm map
		precache_check = CS_MODELS+2; // 0 isn't used
		if (allow_download_maps->value)
			if (!CL_CheckOrDownloadFile(cl.configstrings[CS_MODELS+1], false))
				return; // started a download
	}
	if (precache_check >= CS_MODELS && precache_check < CS_MODELS+max_models)
	{
		if (allow_download_models->value)
		{
			while (precache_check < CS_MODELS+max_models && cl.configstrings[precache_check][0])
			{
				if (cl.configstrings[precache_check][0] == '*' ||
					cl.configstrings[precache_check][0] == '#')
				{
					precache_check++;
					continue;
				}
				if (precache_model_skin == 0)
				{
					if (!CL_CheckOrDownloadFile(cl.configstrings[precache_check], true))
					{
						precache_model_skin = 1;
						return; // started a download
					}
					precache_model_skin = 1;
				}

				// checking for skins in the model
				if (!precache_model)
				{
					FS_LoadFile (cl.configstrings[precache_check], (void **)&precache_model);
					if (!precache_model)
					{
						precache_model_skin = 0;
						precache_check++;
						continue; // couldn't load it
					}
					if (LittleLong(*(unsigned *)precache_model) != IDALIASHEADER)
					{
						// not an alias model
						Z_Free(precache_model);
						precache_model = 0;
						precache_model_skin = 0;
						precache_check++;
						continue;
					}
					pheader = (dmdl_t *)precache_model;
					if (LittleLong (pheader->version) != ALIAS_VERSION)
					{
						// wrong version of model
						Z_Free(precache_model);
						precache_model = 0;
						precache_model_skin = 0;
						precache_check++;
						continue; // couldn't load it
					}
				}

				pheader = (dmdl_t *)precache_model;
				while (precache_model_skin - 1 < LittleLong(pheader->num_skins))
				{
					if (!CL_CheckOrDownloadFile((char *)precache_model + LittleLong(pheader->ofs_skins) + (precache_model_skin - 1)*MAX_SKINNAME, true))
					{
						precache_model_skin++;
						return; // started a download
					}
					precache_model_skin++;
				}
				if (precache_model)
				{
					Z_Free(precache_model);
					precache_model = 0;
				}
				precache_model_skin = 0;
				precache_check++;
			}
		}
		precache_check = cs_sounds;
	}
	if (precache_check >= cs_sounds && precache_check < cs_sounds+max_sounds)
	{
		if (allow_download_sounds->value)
		{
			if (precache_check == cs_sounds)
				precache_check++; // zero is blank
			while (precache_check < cs_sounds+max_sounds && cl.configstrings[precache_check][0])
			{
				if (cl.configstrings[precache_check][0] == '*')
				{
					precache_check++;
					continue;
				}
				Com_sprintf(fn, sizeof(fn), "sound/%s", cl.configstrings[precache_check++]);
				if (!CL_CheckOrDownloadFile(fn, false))
					return; // started a download
			}
		}
		precache_check = cs_images;
	}
	if (precache_check >= cs_images && precache_check < cs_images+max_images)
	{
		if (precache_check == cs_images)
			precache_check++; // zero is blank
		while (precache_check < cs_images+max_images && cl.configstrings[precache_check][0])
		{
			Com_sprintf(fn, sizeof(fn), "pics/%s", cl.configstrings[precache_check++]);
			if (!CL_CheckOrDownloadFile(fn, true))
				return; // started a download
		}
		precache_check = cs_playerskins;
	}
	// skins are special, since a player has three things to download:
	// model, weapon model and skin
	// so precache_check is now *3
	if (precache_check >= cs_playerskins && precache_check < cs_playerskins + MAX_CLIENTS * PLAYER_MULT)
	{
		if (allow_download_players->value)
		{
			while (precache_check < cs_playerskins + MAX_CLIENTS * PLAYER_MULT)
			{
				int i, n;
				char model[MAX_QPATH], skin[MAX_QPATH], *p;

				i = (precache_check - cs_playerskins)/PLAYER_MULT;
				n = (precache_check - cs_playerskins)%PLAYER_MULT;

				if (!cl.configstrings[cs_playerskins+i][0])
				{
					precache_check = cs_playerskins + (i + 1) * PLAYER_MULT;
					continue;
				}

				if ((p = strchr(cl.configstrings[cs_playerskins+i], '\\')) != NULL)
					p++;
				else
					p = cl.configstrings[cs_playerskins+i];
				strcpy(model, p);
				p = strchr(model, '/');
				if (!p)
					p = strchr(model, '\\');
				if (p)
				{
					*p++ = 0;
					strcpy(skin, p);
				}
				else
					*skin = 0;

				switch (n)
				{
				case 0: // model
					Com_sprintf(fn, sizeof(fn), "players/%s/tris.md2", model);
					if (!CL_CheckOrDownloadFile(fn, false))
					{
						precache_check = cs_playerskins + i * PLAYER_MULT + 1;
						return; // started a download
					}
					n++;
					/*FALL THROUGH*/

				case 1: // weapon model
					Com_sprintf(fn, sizeof(fn), "players/%s/weapon.md2", model);
					if (!CL_CheckOrDownloadFile(fn, false))
					{
					precache_check = cs_playerskins + i * PLAYER_MULT + 2;
						return; // started a download
					}
					n++;
					/*FALL THROUGH*/

				case 2: // weapon skin
					Com_sprintf(fn, sizeof(fn), "players/%s/weapon", model);
					if (!CL_CheckOrDownloadFile(fn, true))
					{
						precache_check = cs_playerskins + i * PLAYER_MULT + 3;
						return; // started a download
					}
					n++;
					/*FALL THROUGH*/

				case 3: // skin
					Com_sprintf(fn, sizeof(fn), "players/%s/%s", model, skin);
					if (!CL_CheckOrDownloadFile(fn, true))
					{
						precache_check = cs_playerskins + i * PLAYER_MULT + 4;
						return; // started a download
					}
					n++;
					/*FALL THROUGH*/

				case 4: // skin_i
					Com_sprintf(fn, sizeof(fn), "players/%s/%s_i", model, skin);
					if (!CL_CheckOrDownloadFile(fn, true))
					{
						precache_check = cs_playerskins + i * PLAYER_MULT + 5;
						return; // started a download
					}
					// move on to next model
					precache_check = cs_playerskins + (i + 1) * PLAYER_MULT;
				}
			}
		}
		// precache phase completed
		precache_check = env_cnt;
	}

	if (precache_check == env_cnt)
	{
///		precache_check = env_cnt + 1;
		precache_check++;

		CM_LoadMap (cl.configstrings[CS_MODELS+1], true, &map_checksum);

		if (map_checksum != atoi(cl.configstrings[CS_MAPCHECKSUM]))
		{
			Com_Error (ERR_DROP, "Local map(+lit) checksum differs from server: %i != '%s'",
				map_checksum, cl.configstrings[CS_MAPCHECKSUM]);
			return;
		}
	}

	if (precache_check > env_cnt && precache_check < texture_cnt)
	{
		if (allow_download->value && allow_download_maps->value)
		{
			int nn = texture_cnt;
			while (precache_check < texture_cnt)
			{
				int n = precache_check++ - env_cnt - 1;
				Com_sprintf(fn, sizeof(fn), "env/%s%s",	cl.configstrings[CS_SKY], suf[n]);
				if (!CL_CheckOrDownloadFile(fn, true))
					return; // started a download
			}
		}
		precache_check = texture_cnt;
	}

	if (precache_check == texture_cnt)
	{
///		precache_check = texture_cnt+1;
		precache_check++;
		precache_tex = 0;
	}

	// confirm existance of textures, download any that don't exist
	if (precache_check == texture_cnt+1)
	{
		// from qcommon/cmodel.c
		extern int			numtexinfo;
		extern mapsurface_t	map_surfaces[];

		if (allow_download->value && allow_download_maps->value)
		{
			while (precache_tex < numtexinfo)
			{
				char fn[MAX_OSPATH];

				sprintf(fn, "textures/%s", map_surfaces[precache_tex++].rname);		/// Berserker: грузим любую графику, а не только WAL
				if (!CL_CheckOrDownloadFile(fn, true))
					return; // started a download
			}
		}
		precache_check = texture_cnt+999;
	}

//ZOID
	CL_RegisterSounds ();
	CL_PrepRefresh ();

	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
	SCR_StopCinematic ();
	MSG_WriteString (&cls.netchan.message, va("begin %i\n", precache_spawncount) );		// Команда захода игрока в игру (когда ВСЕ загружено)
}


/*
=================
CL_Precache_f

The server will send this command right
before allowing the client into the server
=================
*/
void CL_Precache_f ()
{
	//Yet another hack to let old demos work
	//the old precache sequence
	if (Cmd_Argc() < 2)
	{
		unsigned	map_checksum;		// for detecting cheater maps

		CM_LoadMap (cl.configstrings[CS_MODELS+1], true, &map_checksum);
		CL_RegisterSounds ();
		CL_PrepRefresh ();
		return;
	}

	precache_check = CS_MODELS;
	precache_spawncount = atoi(Cmd_Argv(1));
	precache_model = 0;
	precache_model_skin = 0;

	CL_RequestNextDownload();
}


/*
===============
CL_Download_f

Request a download from the server
===============
*/
void	CL_Download_f ()
{
	char filename[MAX_OSPATH];

	if (Cmd_Argc() != 2)
	{
		Com_Printf("^3USAGE: download <filename>\n");
		return;
	}

	if (cls.state != ca_connected && cls.state != ca_active)
	{
		Com_Printf ("^1Can't download \"%s\", not connected\n", Cmd_Argv(1));
		return;
	}
	
	Com_sprintf(filename, sizeof(filename), "%s", Cmd_Argv(1));

	if (strstr (filename, ".."))
	{
		Com_DPrintf ("Quake2 error: Refusing to download a path with ..\n");
		Com_DPrintf ("Berserker: '%s' fixed to ",filename);
		while (RepairPath(filename));	/// Berserker: устранение безобразия типа в модели: "models\monsters\tank\tris.md2" - там есть строки типа "models/monsters/tank/../ctank/skin.pcx"
										/// FIXME: может убрать эту проверку и просто исправить ручками подобную строку на "models/monsters/ctank/skin.pcx"
										/// Признак ошибки: сообщения при загрузке "Refusing to download a path with .."

		Com_DPrintf ("'%s'\n",filename);
///		return;
	}

	if (FS_LoadFile (filename, NULL) != -1)
	{	// it exists, no need to download
		Com_Printf("^1File already exists.\n");
		return;
	}

	strcpy (cls.downloadname, filename);
	Com_Printf ("Downloading %s\n", cls.downloadname);

	// download to a temp name, and only rename
	// to the real name when done, so if interrupted
	// a runt file wont be left
	COM_StripExtension (cls.downloadname, cls.downloadtempname);
	strcat (cls.downloadtempname, ".tmp");

	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
	MSG_WriteString (&cls.netchan.message, va("download %s", cls.downloadname));

///	cls.downloadnumber++;
}


/*
=================
CL_Skins_f

Load or download any custom player skins and models
=================
*/
void CL_Skins_f ()
{
	int		i, cs_playerskins;

	if (net_compatibility->value)
		cs_playerskins = CS_PLAYERSKINS_Q2;
	else
		cs_playerskins = CS_PLAYERSKINS_BERS;

	for (i=0 ; i<MAX_CLIENTS ; i++)
	{
		if (!cl.configstrings[cs_playerskins+i][0])
			continue;
		Com_Printf ("client %i: %s\n", i, cl.configstrings[cs_playerskins+i]); 
		SCR_UpdateScreen ();
		Sys_SendKeyEvents ();	// pump message loop
		CL_ParseClientinfo (i);
	}
}


void DisconnectFunc( void *unused )
{
	CL_Drop ();
	Com_Printf("Server disconnected\n");
}

bool LightEditor_Enabled(bool quiet)
{
	if (r_simple->value)
	{
		if (!quiet)
			Com_Printf ("^1LightEditor unavailable in simple render mode.\n");
		return false;
	}

	if (net_compatibility->value)
	{
		if (!quiet)
			Com_Printf ("^1LightEditor unavailable for net compatibility mode.\n");
		return false;
	}

	if (cls.state != ca_active)
	{
		if (!quiet)
			Com_Printf ("^1You must be in a level to use LightEditor's commands.\n");
		return false;
	}

	if (Cvar_VariableValue ("deathmatch") || Cvar_VariableValue ("coop"))
	{
		if (!quiet)
			Com_Printf ("^1LightEditor unavailable for multiplayer.\n");
		return false;
	}

	if (!ge)
	{
		if (!quiet)
			Com_Printf ("^1LightEditor unavailable for remote server.\n");
		return false;
	}

	if (!r_editor->value)
	{
		if (!quiet)
			Com_Printf ("^1You must set 'r_editor 1' to enable LightEditor.\n");
		return false;
	}

	return true;
}


void CL_Sky_Move_Origin_f ()
{
	int		pos;
	float	add;

	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() == 1 )
		add = 1;
	else
		add = atof(Cmd_Argv(1));

	if (!Q_strcasecmp(Cmd_Argv(0), "sky_move_origin_x"))
		pos = 0;
	else if (!Q_strcasecmp(Cmd_Argv(0), "sky_move_origin_y"))
		pos = 1;
	else if (!Q_strcasecmp(Cmd_Argv(0), "sky_move_origin_z"))
		pos = 2;
	else
	{
		Com_Printf("^3Usage: sky_move_origin_x/y/z <delta>\n");
		return;
	}

	sky_origin[pos] += add;
	Com_Printf("sky_origin changed.\n");
	sky_world = true;
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Sky_Move_ViewCenter_f ()
{
	int		pos;
	float	add;

	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() == 1 )
		add = 1;
	else
		add = atof(Cmd_Argv(1));

	if (!Q_strcasecmp(Cmd_Argv(0), "sky_move_viewcenter_x"))
		pos = 0;
	else if (!Q_strcasecmp(Cmd_Argv(0), "sky_move_viewcenter_y"))
		pos = 1;
	else if (!Q_strcasecmp(Cmd_Argv(0), "sky_move_viewcenter_z"))
		pos = 2;
	else
	{
		Com_Printf("^3Usage: sky_move_viewcenter_x/y/z <delta>\n");
		return;
	}

	sky_viewcenter[pos] += add;
	Com_Printf("sky_viewcenter changed.\n");
	sky_world = true;
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Sky_Clear_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	sky_world = false;
	Com_Printf("SkyWorld cleared.\n");
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Sky_Rotate_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2)
	{
		if(sky_world && (sky_rotate!=0.0))
			Com_Printf("Current sky_rotate: %f\n", sky_rotate);
		Com_Printf("^3Usage: <sky_rotate r>\n");
		return;
	}

	sky_world = true;
	sky_rotate = atof(Cmd_Argv(1));
	Com_Printf("sky_rotate changed.\n");
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Sky_Scale_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2)
	{
		if(sky_world && (sky_scale!=0.0))
			Com_Printf("Current sky_scale: %f\n", sky_scale);
		Com_Printf("^3Usage: <sky_scale a>\n");
		return;
	}

	sky_world = true;
	sky_scale = atof(Cmd_Argv(1));
	Com_Printf("sky_scale changed.\n");
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Sky_Angle_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2)
	{
		if(sky_world && (sky_angle!=0.0))
			Com_Printf("Current sky_angle: %f\n", sky_angle);
		Com_Printf("^3Usage: <sky_angle a>\n");
		return;
	}

	sky_world = true;
	sky_angle = atof(Cmd_Argv(1));
	Com_Printf("sky_angle changed.\n");
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Sky_Origin_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( (Cmd_Argc() != 4) && (Cmd_Argc() != 2) )
	{
		if(sky_world && !VectorCompare(sky_origin, vec3_origin))
			Com_Printf("Current sky_origin: %f %f %f\n", sky_origin[0], sky_origin[1], sky_origin[2]);
		Com_Printf("^3Usage: <sky_origin x y z | camera>\n");
		return;
	}

	if ((Cmd_Argc() == 4))
		VectorSet(sky_origin, atof(Cmd_Argv(1)),atof(Cmd_Argv(2)),atof(Cmd_Argv(3)));
	else
		VectorCopy(r_origin, sky_origin);

	sky_world = true;
	Com_Printf("sky_origin changed.\n");
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Sky_ViewCenter_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( (Cmd_Argc() != 4) && (Cmd_Argc() != 2) )
	{
		if(sky_world && !VectorCompare(sky_viewcenter, vec3_origin))
			Com_Printf("Current sky_viewcenter: %f %f %f\n", sky_viewcenter[0], sky_viewcenter[1], sky_viewcenter[2]);
		Com_Printf("^3Usage: <sky_viewcenter x y z | camera>\n");
		return;
	}

	if ((Cmd_Argc() == 4))
		VectorSet(sky_viewcenter, atof(Cmd_Argv(1)),atof(Cmd_Argv(2)),atof(Cmd_Argv(3)));
	else
		VectorCopy(r_origin, sky_viewcenter);

	sky_world = true;
	Com_Printf("sky_viewcenter changed.\n");
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Ambient_f ()
{
	float	r, g, b;

	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 4 )
	{
		if(!VectorCompare(world_ambient_light, vec3_origin))
			Com_Printf("Current world ambient light: %1.2f %1.2f %1.2f\n", world_ambient_light[0], world_ambient_light[1], world_ambient_light[2]);
		Com_Printf("^3Usage: <ambient r g b>\n");
		return;
	}

	r = atof(Cmd_Argv(1));
	g = atof(Cmd_Argv(2));
	b = atof(Cmd_Argv(3));

	if(r<0 || r>1)	return;
	if(g<0 || g>1)	return;
	if(b<0 || b>1)	return;

	VectorSet(world_ambient_light, r,g,b);
	Com_Printf("world ambient light changed.\n");
	Cl_UpdateEditorCvars(ED_AMBIENT | ED_MODIFIED);
}

FILE *CreateFile(FILE *file, char *name)
{
	if(!file)
	{
		FS_CreatePath(name);
		file = FS_Fopen (name, "w");
		if(!file)
			Com_Error(ERR_DROP, "Error creating %s", name);
	}

	return file;
}

defDecal_t	*AllocDefDecal()
{
	if (num_deferred_decals >= MAX_DECALS)
	{
		Com_Printf("Too many decals.\n");
		return NULL;
	}
	return &deferred_decals[num_deferred_decals++];
}

void CL_DeleteDecal_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curdecal)
		return;

	for (decal_t *p=active_decals ; p ; p=p->next)
		if (p->defDecal == curdecal)
			p->die = cl.leveltime;
	defDecal_t	*p;
	for (p = curdecal; p < &deferred_decals[MAX_DECALS]; p++)
		Memcpy(p, p+1, sizeof(defDecal_t));
	Memset(&deferred_decals[MAX_DECALS], 0, sizeof(defDecal_t));
	num_deferred_decals--;

	curdecal = NULL;
	if (!silent_decal)
		Com_Printf("Decal deleted.\n");
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}

void CL_CreateDecal_f()
{
	vec3_t	end;

	if (!LightEditor_Enabled(false))
		return;

	curdecal = AllocDefDecal();

	if (curdecal)
	{
		VectorMA (r_origin, 32768, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
		trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, MASK_SOLID);
///		if(trace.surface->name)
		{
			if(trace.surface->name[0])
			{
				VectorMA (r_origin, 32768*trace.fraction-1, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
				if (curbrush)
					VectorCopy(curbrush_offset, curdecal->offset);
				else
					VectorClear(curdecal->offset);
				R_SpawnDecal(end, trace.plane.normal, decal_texture_object[0], 32, 32, 9999999, 9999999, 0, 0, dt_mul, false, 0, 0, curdecal, false);
				curdecal->angle = 0;
				VectorCopy(end, curdecal->center);
				curdecal->cl_style = 0;
				curdecal->dtype = dt_mul;
				VectorCopy(trace.plane.normal, curdecal->normal);
				curdecal->size = 32;
				curdecal->style = 0;
				curdecal->texture = decal_texture_object[0];
				if (sv_stopclock->value)
					Com_Printf("Decal spawned.\n");
				else
					Com_Printf("^3WARNING:^7 Decal spawned, but sv_stopclock = 0\n");
				Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
				return;
			}
		}
		silent_decal = true;
		CL_DeleteDecal_f();
		silent_decal = false;
		return;
	}
	curdecal = NULL;
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_CopyDecal_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curdecal)
		return;

	Memcpy(&decal_clipboard, curdecal, sizeof(defDecal_t));
	if (!silent_decal)
		Com_Printf("Decal copied to clipboard.\n");
}


void CL_CutDecal_f ()
{
	silent_decal = true;
	CL_CopyDecal_f();
	CL_DeleteDecal_f();
	silent_decal = false;
}


void TraceDecal()
{
	if (!trace_decal)
		return;

	if (!curdecal)
		return;

	if (!LightEditor_Enabled(false))
	{
		trace_decal = false;
		return;
	}

	vec3_t	end;
	VectorMA (r_origin, 32768, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
	trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, MASK_SOLID);
///	if(trace.surface->name)
		if(trace.surface->name[0])
		{
			// Скопируем в буфер обмена и удалим декаль
			CL_CutDecal_f();
			curdecal = AllocDefDecal();
			if (curdecal)
			{
				// Пересчитаем геометрию декаля и создадим его
				VectorMA (r_origin, 32768*trace.fraction-(decal_clipboard.size*0.5), vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
				if (curbrush)
					VectorCopy(curbrush_offset, curdecal->offset);
				else
					VectorClear(curdecal->offset);
				R_SpawnDecal(end, trace.plane.normal, decal_clipboard.texture, decal_clipboard.size, decal_clipboard.size, 9999999, 9999999, decal_clipboard.angle, decal_clipboard.angle, decal_clipboard.dtype, false, decal_clipboard.style, decal_clipboard.cl_style, curdecal, false);
				curdecal->angle = decal_clipboard.angle;
				VectorCopy(end, curdecal->center);
				curdecal->cl_style = decal_clipboard.cl_style;
				curdecal->dtype = decal_clipboard.dtype;
				VectorCopy(trace.plane.normal, curdecal->normal);
				curdecal->size = decal_clipboard.size;
				curdecal->style = decal_clipboard.style;
				curdecal->texture = decal_clipboard.texture;
///				if (!sv_stopclock->value)
///					Com_Printf("^3WARNING:^7 sv_stopclock = 0\n");
			}
			Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
		}
}


void CL_TraceDecal_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curdecal)
		return;

	if (!trace_decal)
	{
		Com_Printf("Trace decal ON\n");
		trace_decal = true;
	}
	else
	{
		Com_Printf("Trace decal OFF\n");
		trace_decal = false;
	}

	if (curbrush)
		VectorCopy(curbrush_offset, curdecal->offset);
	else
		VectorClear(curdecal->offset);

	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_TraceEmit_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

	if (!trace_emit)
	{
		Com_Printf("Trace emitter ON\n");
		trace_emit = true;
	}
	else
	{
		Com_Printf("Trace emitter OFF\n");
		trace_emit = false;
	}
}

void CL_TraceModel_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if (!trace_model)
	{
		Com_Printf("Trace model ON\n");
		trace_model = true;
	}
	else
	{
		Com_Printf("Trace model OFF\n");
		trace_model = false;
	}
}


void CL_SaveLights_f ()
{
	int		i;
	char	name[MAX_OSPATH], cname[MAX_OSPATH];
	FILE	*file = NULL;

	if (!LightEditor_Enabled(false))
		return;

	if (!Cvar_VariableValue("ed_modified"))
	{
		Com_Printf("^3Data did not changed.\n");
		return;
	}

	if (Cmd_Argc() != 2)
	{
		Com_sprintf(name, sizeof(name), "%s/%s", FS_Gamedir(), r_worldmodel->name);
		int	len = strlen(name);
		name[len-3] = 'l';
		name[len-2] = 'i';
		name[len-1] = 't';

		Com_sprintf(cname, sizeof(cname), "%s", r_worldmodel->name);
		len = strlen(cname);
		cname[len-3] = 'l';
		cname[len-2] = 'i';
		cname[len-1] = 't';
	}
	else
	{
		Com_sprintf(name, sizeof(name), "%s/maps/%s.lit", FS_Gamedir(), Cmd_Argv(1));
		Com_sprintf(cname, sizeof(cname), "maps/%s.lit", Cmd_Argv(1));
	}

	FS_ClearFileCache(cname);

	bool vp0 = (!VectorCompare(world_ambient_light, vec3_origin));
	bool vp1 = (!VectorCompare(sky_viewcenter, vec3_origin));
	bool vp2 = (!VectorCompare(sky_origin, vec3_origin));
	bool vp3 = (sky_angle!=0.0);
	bool vp4 = (sky_rotate!=0.0);
	bool vp5 = (sky_scale!=0.0);

	if (vp0 || vp1 || vp2 || vp3 || vp4 || vp5)
	{
		file = CreateFile(file, name);
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"worldspawn\"\n");
		if (vp0)
			fprintf(file, "\"ambient\" \"%1.2f %1.2f %1.2f\"\n", world_ambient_light[0], world_ambient_light[1], world_ambient_light[2]);
		if (sky_world)
		{
			if (vp1)
				fprintf(file, "\"sky_viewcenter\" \"%1.2f %1.2f %1.2f\"\n", sky_viewcenter[0], sky_viewcenter[1], sky_viewcenter[2]);
			if (vp2)
				fprintf(file, "\"sky_origin\" \"%1.2f %1.2f %1.2f\"\n", sky_origin[0], sky_origin[1], sky_origin[2]);
			if (vp3)
				fprintf(file, "\"sky_angle\" \"%1.2f\"\n", sky_angle);
			if (vp4)
				fprintf(file, "\"sky_rotate\" \"%1.2f\"\n", sky_rotate);
			if (vp5)
				fprintf(file, "\"sky_scale\" \"%1.2f\"\n", sky_scale);
		}
		fprintf(file, "}\n");
	}

	int ssize = sun_size*10;
	if (ssize==1)
	{
		file = CreateFile(file, name);
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"smallsun\"\n");
		fprintf(file, "\"origin\" \"%i %i %i\"\n", (int)sun_origin[0], (int)sun_origin[1], (int)sun_origin[2]);
		fprintf(file, "}\n");
	}
	else if (ssize==2)
	{
		file = CreateFile(file, name);
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"sun\"\n");
		fprintf(file, "\"origin\" \"%i %i %i\"\n", (int)sun_origin[0], (int)sun_origin[1], (int)sun_origin[2]);
		fprintf(file, "}\n");
	}
	else if (ssize==3)
	{
		file = CreateFile(file, name);
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"bigsun\"\n");
		fprintf(file, "\"origin\" \"%i %i %i\"\n", (int)sun_origin[0], (int)sun_origin[1], (int)sun_origin[2]);
		fprintf(file, "}\n");
	}

	for (i=0; i<numStaticShadowLights; i++)
	{
		currentshadowlight = &shadowlights[i];

///		if(!currentshadowlight->use)
///			continue;

		if(currentshadowlight->isStatic && (/*currentshadowlight->volumeCmds == NULL || */currentshadowlight->lightCmds == NULL))
			continue;

		file = CreateFile(file, name);
		fprintf(file, "//Light %i\n", i);
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"light\"\n");
		if (currentshadowlight->flareLight)
			fprintf(file, "\"_flare\" \"1\"\n");
		if (currentshadowlight->sphere)
		{
			if (currentshadowlight->radius != 300)
				fprintf(file, "\"light\" \"%i\"\n", (int)currentshadowlight->radius);
		}
		else
			fprintf(file, "\"radius\" \"%i %i %i\"\n", (int)currentshadowlight->radiuses[0], (int)currentshadowlight->radiuses[1], (int)currentshadowlight->radiuses[2]);

		fprintf(file, "\"origin\" \"%i %i %i\"\n", (int)currentshadowlight->origin[0], (int)currentshadowlight->origin[1], (int)currentshadowlight->origin[2]);

		if(currentshadowlight->color[0]!=1 || currentshadowlight->color[1]!=1 || currentshadowlight->color[2]!=1)
			fprintf(file, "\"color\" \"%1.2f %1.2f %1.2f\"\n", currentshadowlight->color[0], currentshadowlight->color[1], currentshadowlight->color[2]);

		if(!VectorCompare(currentshadowlight->angles, vec3_origin))
			fprintf(file, "\"angles\" \"%i %i %i\"\n", (int)currentshadowlight->angles[0], (int)currentshadowlight->angles[1], (int)currentshadowlight->angles[2]);

		if(!VectorCompare(currentshadowlight->rspeed, vec3_origin))
			fprintf(file, "\"speed\" \"%f %f %f\"\n", currentshadowlight->rspeed[0], currentshadowlight->rspeed[1], currentshadowlight->rspeed[2]);

		if(currentshadowlight->_cone!=0)
			fprintf(file, "\"cone\" \"%f\"\n", currentshadowlight->_cone);

		if(currentshadowlight->style)
			fprintf(file, "\"style\" \"%i\"\n", currentshadowlight->style);

		if(currentshadowlight->cl_style)
			fprintf(file, "\"cl_style\" \"%i\"\n", currentshadowlight->cl_style);

		if(currentshadowlight->filtercube_start)
			fprintf(file, "\"filter\" \"%i\"\n", currentshadowlight->filtercube_start);

		if(currentshadowlight->filtercube_end && (currentshadowlight->filtercube_start!=currentshadowlight->filtercube_end))
			fprintf(file, "\"filter2\" \"%i\"\n", currentshadowlight->filtercube_end);

		if (currentshadowlight->framerate && currentshadowlight->framerate!=2)
			fprintf(file, "\"frame\" \"%f\"\n", currentshadowlight->framerate);

		if(currentshadowlight->targetname[0])
			fprintf(file, "\"targetname\" \"%s\"\n", currentshadowlight->targetname);

		if(currentshadowlight->start_off)
			fprintf(file, "\"spawnflags\" \"%i\"\n", START_OFF);

		if(currentshadowlight->noshadow)
			fprintf(file, "\"noshadow\" \"1\"\n");

		if(currentshadowlight->noshadow2)
			fprintf(file, "\"noshadow2\" \"1\"\n");

		if(currentshadowlight->nobump)
			fprintf(file, "\"nobump\" \"1\"\n");

		fprintf(file, "}\n");
	}

	llink_t	*llight;
	for (i=0; i<lightmodel_counter; i++)
	{
		llight = &lmdl_list[i];

///		if(!llight->use)
///			continue;

		file = CreateFile(file, name);
		fprintf(file, "//Linked light %i\n", i);
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"light\"\n");

		if (llight->radius != 300)
			fprintf(file, "\"light\" \"%i\"\n", (int)llight->radius);

		fprintf(file, "\"origin\" \"%i %i %i\"\n", (int)llight->origin[0], (int)llight->origin[1], (int)llight->origin[2]);

		if(llight->color[0]!=1 || llight->color[1]!=1 || llight->color[2]!=1)
			fprintf(file, "\"color\" \"%1.2f %1.2f %1.2f\"\n", llight->color[0], llight->color[1], llight->color[2]);

		if(!VectorCompare(llight->angles, vec3_origin))
			fprintf(file, "\"angles\" \"%i %i %i\"\n", (int)llight->angles[0], (int)llight->angles[1], (int)llight->angles[2]);

		if(llight->_cone!=0)
			fprintf(file, "\"cone\" \"%f\"\n", llight->_cone);

		if(llight->style)
			fprintf(file, "\"style\" \"%i\"\n", llight->style);

		if(llight->cl_style)
			fprintf(file, "\"cl_style\" \"%i\"\n", llight->cl_style);

		if(llight->filtercube_start)
			fprintf(file, "\"filter\" \"%i\"\n", llight->filtercube_start);

		if(llight->filtercube_end && (llight->filtercube_start!=llight->filtercube_end))
			fprintf(file, "\"filter2\" \"%i\"\n", llight->filtercube_end);

		if (llight->framerate && llight->framerate!=2)
			fprintf(file, "\"frame\" \"%f\"\n", llight->framerate);

		fprintf(file, "\"label\" \"%s\"\n", llight->label);

///		if(!VectorCompare(llight->rspeed, vec3_origin))
///			fprintf(file, "\"speed\" \"%f %f %f\"\n", llight->rspeed[0], llight->rspeed[1], llight->rspeed[2]);

///		if(currentshadowlight->targetname[0])
///			fprintf(file, "\"targetname\" \"%s\"\n", currentshadowlight->targetname);

///		if(currentshadowlight->start_off)
///			fprintf(file, "\"spawnflags\" \"%i\"\n", START_OFF);

		fprintf(file, "}\n");
	}

	for (i=0; i<brushmodel_counter; i++)
	{
		file = CreateFile(file, name);
		fprintf(file, "//Linked brush %i\n", i);
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"_null\"\n");
		fprintf(file, "\"model\" \"*%i\"\n", bmdl_list[i].index);
		fprintf(file, "\"label\" \"%s\"\n", bmdl_list[i].label);
		if (bmdl_list[i].nodraw)
			fprintf(file, "\"nodraw\" \"1\"\n");
		fprintf(file, "}\n");
	}

	for (i=0; i<numEmits; i++)
	{
		file = CreateFile(file, name);
		fprintf(file, "//Emitter %i\n", i);
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"emit\"\n");
		fprintf(file, "\"origin\" \"%i %i %i\"\n", (int)emits[i].origin0[0], (int)emits[i].origin0[1], (int)emits[i].origin0[2]);
		fprintf(file, "\"angles\" \"%i %i %i\"\n", (int)emits[i].angles[0], (int)emits[i].angles[1], (int)emits[i].vel);
		fprintf(file, "\"speed\" \"%i %i %i\"\n", (int)emits[i].rspeed[0], (int)emits[i].rspeed[1], (int)emits[i].rspeed[2]);
		fprintf(file, "\"cone\" \"%f\"\n", emits[i].cone);
		if(emits[i].style)
			fprintf(file, "\"style\" \"%i\"\n", emits[i].style);
		if(emits[i].cl_style)
			fprintf(file, "\"cl_style\" \"%i\"\n", emits[i].cl_style);
		if(emits[i].emit)
			fprintf(file, "\"filter\" \"%i\"\n", emits[i].emit);
		if(!VectorCompare(emits[i].startcolor, vec3_origin))
		{
			fprintf(file, "\"startcolor\" \"%1.2f %1.2f %1.2f\"\n", emits[i].startcolor[0], emits[i].startcolor[1], emits[i].startcolor[2]);
			fprintf(file, "\"endcolor\" \"%1.2f %1.2f %1.2f\"\n", emits[i].endcolor[0], emits[i].endcolor[1], emits[i].endcolor[2]);
		}
		if(emits[i].alphavel)
			fprintf(file, "\"lifetime\" \"%f\"\n", emits[i].alphavel);
		if(emits[i].gravity)
			fprintf(file, "\"gravity\" \"%i\"\n", emits[i].gravity);
		if(emits[i].label[0])
			fprintf(file, "\"label\" \"%s\"\n", emits[i].label);
		fprintf(file, "}\n");
	}

	defDecal_t	*p;
	for (int j=0; j<num_deferred_decals; j++)
	{
		p = &deferred_decals[j];
		file = CreateFile(file, name);
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"decal\"\n");
		fprintf(file, "\"origin\" \"%f %f %f\"\n", p->center[0], p->center[1], p->center[2]);
		if (p->angle)
			fprintf(file, "\"angle\" \"%f\"\n", p->angle);
		i = p->texture-decal_texture_object[0];
		if (i>=MAX_GLOBAL_FILTERS)
			i = p->texture-decal_texture_object[MAX_GLOBAL_FILTERS]+MAX_GLOBAL_FILTERS;
		fprintf(file, "\"filter\" \"%i\"\n", i);
		if(p->style)
			fprintf(file, "\"frame\" \"%i\"\n", p->style);
		if(p->cl_style)
			fprintf(file, "\"cl_style\" \"%i\"\n", p->cl_style);
		if (p->dtype==dt_mul)
			fprintf(file, "\"style\" \"1\"\n");
///		else
///			fprintf(file, "\"style\" \"0\"\n");
		fprintf(file, "\"volume\" \"%i\"\n", (int)p->size);
		fprintf(file, "\"dir\" \"%1.2f %1.2f %1.2f\"\n", p->normal[0], p->normal[1], p->normal[2]);
		if (!VectorCompare(p->offset, vec3_origin))
			fprintf(file, "\"_color\" \"%1.2f %1.2f %1.2f\"\n", p->offset[0], p->offset[1], p->offset[2]);
		fprintf(file, "}\n");
	}

	for (i=0; i<aliasmodel_counter; i++)
	{
		file = CreateFile(file, name);
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"misc_model\"\n");
		fprintf(file, "\"origin\" \"%f %f %f\"\n", amdl_list[i].origin[0], amdl_list[i].origin[1], amdl_list[i].origin[2]);
		if (!VectorCompare(amdl_list[i].angles, vec3_origin))
			fprintf(file, "\"angles\" \"%f %f %f\"\n", amdl_list[i].angles[0], amdl_list[i].angles[1], amdl_list[i].angles[2]);
		fprintf(file, "\"model\" \"%s\"\n", amdl_list[i].model);
		if(amdl_list[i].label[0])
			fprintf(file, "\"label\" \"%s\"\n", amdl_list[i].label);
		if(amdl_list[i].frame)
			fprintf(file, "\"frame\" \"%i\"\n", amdl_list[i].frame);
		if(amdl_list[i].frame < amdl_list[i].frame_end)
			fprintf(file, "\"frame_end\" \"%i\"\n", amdl_list[i].frame_end);
		if(amdl_list[i].skinnum)
			fprintf(file, "\"skin\" \"%i\"\n", amdl_list[i].skinnum);
		if(amdl_list[i].animframe)
			fprintf(file, "\"spawnflags\" \"%i\"\n", GFX_ANIMATION);
		if(amdl_list[i].sound[0])
			fprintf(file, "\"noise\" \"%s\"\n", amdl_list[i].sound);
		if(amdl_list[i].mdl->scale != 1.0)
			fprintf(file, "\"scale\" \"%f\"\n", amdl_list[i].mdl->scale);
		if(amdl_list[i].mdl->invert)
			fprintf(file, "\"invert\" \"1\"\n");
		fprintf(file, "}\n");
	}

	/// nodraw surfs
	for (i=0; i<numtexinfo; i++)
		if (!(r_worldmodel->texinfo[i].flags & SURF_SKY) && r_worldmodel->texinfo[i].flags & SURF_NODRAW)
			break;
	if (i<numtexinfo)
	{
		bool	first = true;
		file = CreateFile(file, name);
		fprintf(file, "//NoDraw surfaces\n");
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"nodraw\"\n");
		fprintf(file, "\"surfaces\" \"");
		for (; i<numtexinfo; i++)
			if (!(r_worldmodel->texinfo[i].flags & SURF_SKY) && r_worldmodel->texinfo[i].flags & SURF_NODRAW)
			{
				if (first)
				{
					first = false;
					fprintf(file, "%i", i);
				}
				else
					fprintf(file, " %i", i);
			}
		fprintf(file, "\"\n");
		fprintf(file, "}\n");
	}

	/// opaque skyes
	for (i=0; i<numtexinfo; i++)
		if (r_worldmodel->texinfo[i].flags & SURF_SKY && r_worldmodel->texinfo[i].flags & SURF_SLICK)
			break;
	if (i<numtexinfo)
	{
		bool	first = true;
		file = CreateFile(file, name);
		fprintf(file, "//Opaque sky surfaces\n");
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"opaque\"\n");
		fprintf(file, "\"surfaces\" \"");
		for (; i<numtexinfo; i++)
			if (r_worldmodel->texinfo[i].flags & SURF_SKY && r_worldmodel->texinfo[i].flags & SURF_SLICK)
			{
				if (first)
				{
					first = false;
					fprintf(file, "%i", i);
				}
				else
					fprintf(file, " %i", i);
			}
		fprintf(file, "\"\n");
		fprintf(file, "}\n");
	}

	/// light surfs
	for (i=0; i<numtexinfo; i++)
		if (r_worldmodel->texinfo[i].flags & SURF_IGNORE_LIGHT)
			break;
	if (i<numtexinfo)
	{
		bool	first = true;
		file = CreateFile(file, name);
		fprintf(file, "//Ignored light surfaces\n");
		fprintf(file, "{\n");
		fprintf(file, "\"classname\" \"ignored_lightsurfs\"\n");
		fprintf(file, "\"surfaces\" \"");
		for (; i<numtexinfo; i++)
			if (r_worldmodel->texinfo[i].flags & SURF_IGNORE_LIGHT)
			{
				if (first)
				{
					first = false;
					fprintf(file, "%i", i);
				}
				else
					fprintf(file, " %i", i);
			}
		fprintf(file, "\"\n");
		fprintf(file, "}\n");
	}

	for (i=1; i<MAX_LIGHTSTYLES_OVERRIDE; i++)
	{
		if (lightstyles[i][0])
		{
			file = CreateFile(file, name);
			fprintf(file, "{\n");
			fprintf(file, "\"classname\" \"lightstyle\"\n");
			fprintf(file, "\"style\" \"%i\"\n", i);
			fprintf(file, "\"map\" \"%s\"\n", lightstyles[i]);
			fprintf(file, "}\n");
		}
	}

	for (i=1; i<MAX_LIGHTSTYLES; i++)
	{
		if (cl_lightstyle[i].length>0)
		{
			int	j;
			file = CreateFile(file, name);
			fprintf(file, "{\n");
			fprintf(file, "\"classname\" \"cl_lightstyle\"\n");
			fprintf(file, "\"style\" \"%i\"\n", i);
			fprintf(file, "\"map\" \"");
			for (j=0; j<cl_lightstyle[i].length; j++)
				fprintf(file, "%c", cl_lightstyles[i][j]);
			fprintf(file, "\"\n");
			fprintf(file, "}\n");
		}
	}

	for (i=0; i<numFogs; i++)
	{
		if (fog_infos[i].ed)
		{
			file = CreateFile(file, name);
			fprintf(file, "{\n");
			fprintf(file, "\"classname\" \"info_fog\"\n");
			if(fog_infos[i].color[0]!=1 || fog_infos[i].color[1]!=1 || fog_infos[i].color[2]!=1)
				fprintf(file, "\"color\" \"%1.2f %1.2f %1.2f\"\n", fog_infos[i].color[0], fog_infos[i].color[1], fog_infos[i].color[2]);
			fprintf(file, "\"origin\" \"%i %i %i\"\n", (int)fog_infos[i].origin[0], (int)fog_infos[i].origin[1], (int)fog_infos[i].origin[2]);
			fprintf(file, "\"origin2\" \"%i %i %i\"\n", (int)fog_infos[i].origin2[0], (int)fog_infos[i].origin2[1], (int)fog_infos[i].origin2[2]);
			fprintf(file, "\"density\" \"%i\"\n", (int)(1.0/fog_infos[i].density));
			fprintf(file, "}\n");
		}
	}

	for (i = 0; i<numEFXzones; i++)
	{
		if (EFXzone_infos[i].ed)
		{
			file = CreateFile(file, name);
			fprintf(file, "{\n");
			fprintf(file, "\"classname\" \"info_EFXzone\"\n");
			fprintf(file, "\"origin\" \"%i %i %i\"\n", (int)EFXzone_infos[i].origin[0], (int)EFXzone_infos[i].origin[1], (int)EFXzone_infos[i].origin[2]);
			fprintf(file, "\"origin2\" \"%i %i %i\"\n", (int)EFXzone_infos[i].origin2[0], (int)EFXzone_infos[i].origin2[1], (int)EFXzone_infos[i].origin2[2]);
			fprintf(file, "\"style\" \"%i\"\n", EFXzone_infos[i].preset);
			fprintf(file, "}\n");
		}
	}

	if(file)
	{
		fclose(file);
		Com_Printf("Light entities saved to %s\n", name);
		Cvar_ForceSet("ed_modified","0");
	}
}


void CL_SelectBrush_f ()
{
	vec3_t	end;

	if (!LightEditor_Enabled(false))
		return;

	if (!r_worldmodel->numsubmodels)
	{
		Com_Printf("^3No brush models exist!\n");
		return;
	}

	VectorMA (r_origin, 32768, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
	trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, MASK_SOLID);
	if(/*trace.surface->name &&*/ trace.surface->name[0])
	{
///		if ((int)trace.ent>1)	PVS-Studio
		if (trace.ent)
		{
			if (cl.model_draw[trace.ent->s.modelindex]->type==mod_brush)
			{
				int fms = cl.model_draw[trace.ent->s.modelindex]->firstmodelsurface;
				if (fms)	// bmodel
				{
					for (curbrushnum=1; curbrushnum<r_worldmodel->numsubmodels; curbrushnum++)
					{
						if (r_worldmodel->submodels[curbrushnum].firstface == fms)
							break;
					}
					curbrush = &r_worldmodel->submodels[curbrushnum];
					Cl_UpdateEditorCvars(ED_BRUSH);
				}
			}
		}
	}
}


void CL_UnSelectBrush_f ()
{
	if (!LightEditor_Enabled(false))
		return;
	curbrush = NULL;
	Cl_UpdateEditorCvars(ED_BRUSH);
}


void CL_ShowBrush_f ()
{
	int	num;

	if (!LightEditor_Enabled(false))
		return;

	if (Cmd_Argc() != 2)
	{
		Com_Printf("^3Usage: showbrush <number>\n");
		return;
	}

	num = atoi(Cmd_Argv(1));

	if (num==0)
	{
		curbrush = NULL;
		Cl_UpdateEditorCvars(ED_BRUSH);
		return;
	}

	if (!r_worldmodel->numsubmodels)
	{
		Com_Printf("^3No brush models exist!\n");
		return;
	}

	if (num < 1 || num >= r_worldmodel->numsubmodels)
	{
		Com_Printf("^3Wrong number, not from 1 to %i\n", r_worldmodel->numsubmodels-1);
		return;
	}

	curbrush = &r_worldmodel->submodels[num];
	curbrushnum = num;
	Cl_UpdateEditorCvars(ED_BRUSH);
}


void CL_NextBrush_f ()
{
	int	num;

	if (!LightEditor_Enabled(false))
		return;

	if (!r_worldmodel->numsubmodels)
	{
		Com_Printf("^3No brush models exist!\n");
		return;
	}

	num = curbrushnum + 1;

	if (num >= r_worldmodel->numsubmodels)
		num = 1;

	curbrush = &r_worldmodel->submodels[num];
	curbrushnum = num;
	Cl_UpdateEditorCvars(ED_BRUSH);
}


void CL_PrevBrush_f ()
{
	int	num;

	if (!LightEditor_Enabled(false))
		return;

	if (!r_worldmodel->numsubmodels)
	{
		Com_Printf("^3No brush models exist!\n");
		return;
	}

	num = curbrushnum - 1;

	if (num < 1)
		num = r_worldmodel->numsubmodels-1;

	curbrush = &r_worldmodel->submodels[num];
	curbrushnum = num;
	Cl_UpdateEditorCvars(ED_BRUSH);
}


void CL_Paste_f ()
{
	shadowlight_t	temp;

	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		int	i;

		if(!link_clipboard.radius)	// клипбоард пока пуст
			return;

		if (!curbrush)
		{
			Com_Printf("No brush model selected.\n");
			return;
		}

		// ищем энтити
		for (i=0 ; i<cl.refdef.num_entities ; i++)
		{
			currententity = &cl.refdef.entities[i];
			currentmodel = currententity->model;

			if (!currentmodel)
				continue;

			if (currentmodel->type != mod_brush)
				continue;

			if (currentmodel->nummodelsurfaces == 0)
				continue;

			if (currentmodel->firstmodelsurface == curbrush->firstface)
				break;
		}

		if (i==cl.refdef.num_entities)
			return;		// невозможно, но пусть будет...

		for (i=0; i<brushmodel_counter; i++)
			if (bmdl_list[i].index == curbrushnum)
				break;

		if (i==brushmodel_counter)
		{
			Com_Printf("No labeled brush model found.\n");
			return;
		}

		if (!bmdl_list[i].label[0])
		{
			Com_Printf("No labeled brush model selected.\n");
			return;
		}
		Memcpy(link_clipboard.label, bmdl_list[i].label, sizeof(curlink->label));
		link_clipboard.index = curbrushnum;

		VectorCopy(link_clipboard.color, temp.color);
		VectorSubtract(link_clipboard.angles, currententity->angles, temp.angles);
		if (!Q_strcasecmp(Cmd_Argv(0), "paste2camera"))
		{
			VectorSubtract(r_origin, currententity->origin, temp.origin);
			Com_Printf("^3WARNING:^7 \"paste2camera\" not recommended for linked lights!\n");
		}
		else
			VectorSubtract(link_clipboard.origin, currententity->origin, temp.origin);
		vec3_t	forward, right, up, tmp, atmp;
		if(temp.angles[0] || temp.angles[1] || temp.angles[2])
		{
			VectorNegate(temp.angles, atmp);
			AngleVectors (atmp, forward, right, up);
			VectorCopy (temp.origin, tmp);
			temp.origin[0] = DotProduct (tmp, forward);
			temp.origin[1] = -DotProduct (tmp, right);
			temp.origin[2] = DotProduct (tmp, up);
		}

		temp.radius = temp.radiuses[0] = temp.radiuses[1] = temp.radiuses[2] = link_clipboard.radius;
		temp.style = link_clipboard.style;
		temp.cl_style = link_clipboard.cl_style;
		temp._cone = link_clipboard._cone;
		temp.filtercube_start = link_clipboard.filtercube_start;
		temp.filtercube_end = link_clipboard.filtercube_end;
		temp.ownerkey = link_clipboard.framerate;
		Memcpy(temp.label, link_clipboard.label, MAX_QPATH);

		SpawnLinkedLight(&temp);
		curlink = &lmdl_list[lightmodel_counter-1];
		curlink->index = link_clipboard.index;
		Com_Printf("Light spawned and linked to brush labeled \"%s\".\n", curlink->label);
	}
	else
	{
		if(!light_clipboard.radius)	// клипбоард пока пуст
			return;

		Memcpy(&temp, &light_clipboard, sizeof(shadowlight_t));
		temp.ownerkey = link_clipboard.framerate;

		if (!Q_strcasecmp(Cmd_Argv(0), "paste2camera"))
			VectorCopy(r_origin, temp.origin);

		numShadowLights = numStaticShadowLights;
		if (SpawnStaticLight(&temp))
		{
			curlight = &shadowlights[numStaticShadowLights-1];

// После изменения параметров требуется пересчитать некоторые параметры!
			currentshadowlight = curlight;
			if (ComputeBSPLight())
				Com_Printf("Static light spawned.\n");
			else
				Com_Printf("^3Static light rejected.\n");
		}
	}

	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_Select_f ()
{
	int	num;

	if (!LightEditor_Enabled(false))
		return;

	if (Cmd_Argc() != 2)
	{
		Com_Printf("^3Usage: select <light's number>\n");
		return;
	}

	num = atoi(Cmd_Argv(1));

	if (num==-1)
	{
		if (le_mode)
		{
			Com_Printf("Switched to static lights mode.\n");
			le_mode = false;
		}
		curlight = NULL;
		curlink = NULL;
		trace_light = false;
		Cl_UpdateEditorCvars(ED_LIGHT);
		return;
	}

	if (le_mode)
	{
		if (lightmodel_counter==0)
		{
			Com_Printf("^3No linked lights exist!\n");
			return;
		}

		if ((num >= lightmodel_counter) || (num<0))
		{
			Com_Printf("^3Bad light's number: %i (range is 0..%i; -1 is special case).\n", num, lightmodel_counter-1);
			return;
		}

		curlink = &lmdl_list[num];
	}
	else
	{
		if (numStaticShadowLights==0)
		{
			Com_Printf("^3No static lights exist!\n");
			return;
		}

		if ((num >= numStaticShadowLights) || (num<0))
		{
			Com_Printf("^3Bad light's number: %i (range is 0..%i; -1 is special case).\n", num, numStaticShadowLights-1);
			return;
		}

		curlight = &shadowlights[num];
	}
	Cl_UpdateEditorCvars(ED_LIGHT);
}


void CL_SelectEmit_f ()
{
	int	num;

	if (!LightEditor_Enabled(false))
		return;

	if (Cmd_Argc() != 2)
	{
		Com_Printf("^3Usage: selectemit <emit's number>\n");
		return;
	}

	num = atoi(Cmd_Argv(1));

	if (num==-1)
	{
		curemit = NULL;
		if (trace_emit)
		{
			Com_Printf("Trace emitter OFF\n");
			trace_emit = false;
		}
		Cl_UpdateEditorCvars(ED_EMIT);
		return;
	}

	if (numEmits==0)
	{
		Com_Printf("^3No emitters exist!\n");
		return;
	}

	if ((num >= numEmits) || (num<0))
	{
		Com_Printf("^3Bad emitter's number: %i (range is 0..%i; -1 is special case).\n", num, numEmits-1);
		return;
	}

	curemit = &emits[num];
	Cl_UpdateEditorCvars(ED_EMIT);
}


void CL_SelectNearest_f ()
{
	int		i, best = -1;
	float	dist, best_dist = 99999999;

	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (lightmodel_counter==0)
		{
			Com_Printf("^3No linked lights exist!\n");
			return;
		}

		for(i=0; i<lightmodel_counter; i++)
		{
			int		bm, j;
			float	x, y, z;
			blink_t *brush;
			llink_t *light;
			vec3_t	org, temp, tempang;
			vec3_t	forward, right, up;
			entity_t	*ent;

			light = &lmdl_list[i];
			for (bm=0; bm<brushmodel_counter; bm++)
				if (bmdl_list[bm].index == light->index)
						break;
			if (bm < brushmodel_counter)
			{
				brush = &bmdl_list[bm];

				for (j=0; j<cl.refdef.num_entities; j++)
				{
					ent = &cl.refdef.entities[j];
					if (ent->model == cl.model_draw[light->index+1])
						break;
				}
				if (j<cl.refdef.num_entities)
				{
					VectorSubtract(light->origin, brush->origin, org);
					VectorNegate(ent->angles, tempang);

					if(tempang[0] || tempang[1] || tempang[2])
					{
						AngleVectors (tempang, forward, right, up);
						VectorCopy (org, temp);
						org[0] = DotProduct (temp, forward);
						org[1] = -DotProduct (temp, right);
						org[2] = DotProduct (temp, up);
					}

					VectorAdd(org, ent->origin, org);

					x = r_origin[0] - org[0];
					y = r_origin[1] - org[1];
					z = r_origin[2] - org[2];
					dist = x*x+y*y+z*z;
					if(dist<best_dist)
					{
						best_dist=dist;
						best=i;
					}
				}
			}
			else
			{
				float	x, y, z;
				x = r_origin[0] - light->origin[0];
				y = r_origin[1] - light->origin[1];
				z = r_origin[2] - light->origin[2];
				dist = x*x+y*y+z*z;
				if(dist<best_dist)
				{
					best_dist=dist;
					best=i;
				}
			}
		}

		if(best==-1)
		{
			Com_Printf("^3Nearest linked light not found!\n");
			return;
		}

		curlink = &lmdl_list[best];
		Com_Printf("Selected linked light %i\n", best);
	}
	else
	{
		if (numStaticShadowLights==0)
		{
			Com_Printf("^3No static lights exist!\n");
			return;
		}

		for(i=0; i<numStaticShadowLights; i++)
		{
			float	x, y, z;
			x = r_origin[0] - shadowlights[i].origin[0];
			y = r_origin[1] - shadowlights[i].origin[1];
			z = r_origin[2] - shadowlights[i].origin[2];
			dist = x*x+y*y+z*z;
			if(dist<best_dist)
			{
				best_dist=dist;
				best=i;
			}
		}

		if(best==-1)
		{	// невозможный случай, но пусть будет
			Com_Printf("^3Nearest static light not found!\n");
			return;
		}

		curlight = &shadowlights[best];
		Com_Printf("Selected static light %i\n", best);
	}
	Cl_UpdateEditorCvars(ED_LIGHT);
}


void CL_SelectNearestEmit_f ()
{
	int		i, best = -1;
	float	dist, best_dist = 99999999;

	if (!LightEditor_Enabled(false))
		return;

	if (numEmits==0)
	{
		Com_Printf("^3No emitters exist!\n");
		return;
	}

	for(i=0; i<numEmits; i++)
	{
		float	x, y, z;
		x = r_origin[0] - emits[i].origin[0];
		y = r_origin[1] - emits[i].origin[1];
		z = r_origin[2] - emits[i].origin[2];
		dist = x*x+y*y+z*z;
		if(dist<best_dist)
		{
			best_dist=dist;
			best=i;
		}
	}

	if(best==-1)
	{
		Com_Printf("^3Nearest emitter not found!\n");
		return;
	}

	curemit = &emits[best];
	Com_Printf("Selected emitter %i\n", best);
	Cl_UpdateEditorCvars(ED_EMIT);
}


void CL_Delete_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!ignore_cvars)
	{
		if(( Cmd_Argc() != 1 ) && ( Cmd_Argc() != 2 ))
		{
sintax:		Com_Printf("^3Usage: delete <all> | <notargeted>\n");
			return;
		}

		if ( Cmd_Argc() == 2 )
		{
			if (!Q_strcasecmp(Cmd_Argv(1), "notargeted"))
			{
				int i, j=0;
				bool old_le_mode = le_mode;
				le_mode = false;
				ignore_cvars = true;
				for(i=0; i<numStaticShadowLights; i++)
				{
					curlight = &shadowlights[i];
					if(curlight->isStatic && !(/*curlight->label && */curlight->label[0]) && !(/*curlight->targetname && */curlight->targetname[0]))
					{
						int old_numStaticShadowLights = numStaticShadowLights;
						CL_Delete_f();
						if (old_numStaticShadowLights!=numStaticShadowLights)
						{
							j++;
							i--;
						}
					}
				}
				le_mode = old_le_mode;
				curlight = NULL;
				ignore_cvars = false;
				if (j)
					Com_Printf("Deleted %i notargeted static lights\n", j);
			}
			else if (!Q_strcasecmp(Cmd_Argv(1), "all"))
			{
				int i, j=0;
				bool old_le_mode = le_mode;
				le_mode = false;
				ignore_cvars = true;
				for(i=0; i<numStaticShadowLights; i++)
				{
					curlight = &shadowlights[i];
					if(curlight->isStatic && !(/*curlight->label && */curlight->label[0]))
					{
						int old_numStaticShadowLights = numStaticShadowLights;
						CL_Delete_f();
						if (old_numStaticShadowLights!=numStaticShadowLights)
						{
							j++;
							i--;
						}
					}
				}
				le_mode = old_le_mode;
				curlight = NULL;
				ignore_cvars = false;
				if (j)
					Com_Printf("Deleted %i static lights\n", j);
			}
			else
				goto sintax;

			Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
			return;
		}
	}

	if (le_mode)
	{
		if(!curlink)
			return;

		llink_t	*light;

		if (lightmodel_counter==0)
		{
			Com_Printf("^3No linked lights exist!\n");
			return;
		}

		for(light=curlink; light<&lmdl_list[lightmodel_counter-1]; light++)
			Memcpy(light, light+1, sizeof(llink_t));

		Memset(&lmdl_list[lightmodel_counter-1], 0, sizeof(llink_t));
		lightmodel_counter--;
		if(curlink == &lmdl_list[lightmodel_counter])
		{
			if(lightmodel_counter)
				curlink--;
			else
				curlink = NULL;
		}
		Com_Printf("Linked light deleted.\n");
	}
	else
	{
		if(!curlight)
			return;

		shadowlight_t	*light;

		if (numStaticShadowLights==0)
		{
			Com_Printf("^3No static lights exist!\n");
			return;
		}

		if(curlight->volumeCmds)
		{
			Z_Free(curlight->volumeCmds);
			curlight->volumeCmds = NULL;
		}
		if(curlight->lightCmds)
		{
			Z_Free(curlight->lightCmds);
			curlight->lightCmds = NULL;
		}
		if(curlight->sh_vbo_id)
		{
			R_VCFree(curlight->sh_vbo_id);
			curlight->sh_vbo_id = NULL;
		}

		for(light=curlight; light<&shadowlights[numStaticShadowLights-1]; light++)
			Memcpy(light, light+1, sizeof(shadowlight_t));

		Memset(&shadowlights[numStaticShadowLights-1], 0, sizeof(shadowlight_t));
		numShadowLights--;
		numStaticShadowLights--;
		if(curlight == &shadowlights[numStaticShadowLights])
		{
			if(numStaticShadowLights)
				curlight--;
			else
				curlight = NULL;
		}
		Com_Printf("Static light deleted.\n");
	}
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_DeleteEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if(!curemit)
		return;

	emit_t	*emit;

	for(emit=curemit; emit<&emits[numEmits-1]; emit++)
		Memcpy(emit, emit+1, sizeof(emit_t));

	Memset(&emits[numEmits-1], 0, sizeof(emit_t));
	numEmits--;

	if(curemit == &emits[numEmits])
	{
		if(numEmits)
			curemit--;
		else
			curemit = NULL;
	}
	Com_Printf("Emitter deleted.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_Copy_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if(!curlink)
			return;

		Memcpy(&link_clipboard, curlink, sizeof(llink_t));
		Com_Printf("Linked light copied to clipboard.\n");
	}
	else
	{
		if(!curlight)
			return;

		Memcpy(&light_clipboard, curlight, sizeof(shadowlight_t));
		Com_Printf("Static light copied to clipboard.\n");
	}
}


void CL_CopyEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if(!curemit)
		return;

	Memcpy(&emit_clipboard, curemit, sizeof(emit_t));
	Com_Printf("Emitter copied to clipboard.\n");
}


void CL_Cut_f ()
{
	CL_Copy_f();
	ignore_cvars = true;
	CL_Delete_f();
	ignore_cvars = false;
}


void CL_CutEmit_f ()
{
	CL_CopyEmit_f();
	CL_DeleteEmit_f();
}


void Menu_DrawString( int x, int y, const char *string )
{
	unsigned i, sl;

	sl = strlen(string);		/// PVS-Studio
	for ( i = 0; i < sl; i++ )
		Draw_Char( ( x + (i<<3) ), y, string[i] );
}

void Menu_DrawString16( int x, int y, const char *string )
{
	unsigned i, sl;

	sl = strlen(string);		/// PVS-Studio
	for ( i = 0; i < sl; i++ )
		Draw_Char16( ( x + (i<<4) ), y, string[i] );
}

void Menu_DrawStatusBar( const char *string )
{
	if ( string )
	{
		int l = strlen( string );
		int maxrow = viddef.height >> 4;
		int maxcol = viddef.width >> 4;
		int col = (maxcol - l) >> 1;

		Draw_Fill( 0, viddef.height-16, viddef.width, 16, 40,40,40);
		Menu_DrawString16( col << 4, viddef.height-16, string );
	}
}


bool ComputeBSPLight()
{
	bool	tmp;
	int		i;

	if (currentshadowlight->_cone==0/* || (currentshadowlight->angles[0]==0 && currentshadowlight->angles[1]==0)*/)
		goto sphere;	// нет конуса или направления - рисуем полный ббокс
	if (currentshadowlight->rspeed[0]==0 && currentshadowlight->rspeed[1]==0)
	{		// не вращается
		float	sa,cb,sb;
		sa = sin(DEG2RAD(currentshadowlight->angles[0]));
		SinCos(DEG2RAD(currentshadowlight->angles[1]), &sb, &cb);
///		cb = cos(DEG2RAD(currentshadowlight->angles[1]));
///		sb = sin(DEG2RAD(currentshadowlight->angles[1]));
		if (currentshadowlight->_cone<=1)
		{
			if (sa>=0.7193)
			{		// смотрит примерно вниз
				goto bottom;
			}
			else if (sa<=-0.7193)
			{		// смотрит примерно вверх
				goto up;
			}
			else if (sa>=-0.0175 && sa<=0.0175)
			{
				if (cb>=0.7193)
				{		// смотрит примерно вправо
					goto right;
				}
				else if (cb<=-0.7193)
				{		// смотрит примерно влево
					goto left;
				}
				else if (sb>=0.7193)
				{		// смотрит примерно прямо
					goto forward;
				}
				else if (sb<=-0.7193)
				{		// смотрит примерно назад
					goto backward;
				}
			}
		}
		else
		{
			if (sa>=0.9999)
			{		// смотрит строго вниз
bottom:			currentshadowlight->mins_cone[0] = currentshadowlight->origin[0] - currentshadowlight->radiuses[0];
				currentshadowlight->maxs_cone[0] = currentshadowlight->origin[0] + currentshadowlight->radiuses[0];
				currentshadowlight->mins_cone[1] = currentshadowlight->origin[1] - currentshadowlight->radiuses[1];
				currentshadowlight->maxs_cone[1] = currentshadowlight->origin[1] + currentshadowlight->radiuses[1];
				currentshadowlight->mins_cone[2] = currentshadowlight->origin[2] - currentshadowlight->radiuses[2];
				currentshadowlight->maxs_cone[2] = currentshadowlight->origin[2];
				goto draw;
			}
			else if (sa<=-0.9999)
			{		// смотрит строго вверх
up:
				currentshadowlight->mins_cone[0] = currentshadowlight->origin[0] - currentshadowlight->radiuses[0];
				currentshadowlight->maxs_cone[0] = currentshadowlight->origin[0] + currentshadowlight->radiuses[0];
				currentshadowlight->mins_cone[1] = currentshadowlight->origin[1] - currentshadowlight->radiuses[1];
				currentshadowlight->maxs_cone[1] = currentshadowlight->origin[1] + currentshadowlight->radiuses[1];
				currentshadowlight->mins_cone[2] = currentshadowlight->origin[2];
				currentshadowlight->maxs_cone[2] = currentshadowlight->origin[2] + currentshadowlight->radiuses[2];
				goto draw;
			}
			else if (sa>=-0.0175 && sa<=0.0175)
			{
				if (cb>=0.9999)
				{		// смотрит строго вправо
right:				currentshadowlight->mins_cone[0] = currentshadowlight->origin[0];
					currentshadowlight->maxs_cone[0] = currentshadowlight->origin[0] + currentshadowlight->radiuses[0];
					currentshadowlight->mins_cone[1] = currentshadowlight->origin[1] - currentshadowlight->radiuses[1];
					currentshadowlight->maxs_cone[1] = currentshadowlight->origin[1] + currentshadowlight->radiuses[1];
					currentshadowlight->mins_cone[2] = currentshadowlight->origin[2] - currentshadowlight->radiuses[2];
					currentshadowlight->maxs_cone[2] = currentshadowlight->origin[2] + currentshadowlight->radiuses[2];
					goto draw;
				}
				else if (cb<=-0.9999)
				{		// смотрит строго влево
left:				currentshadowlight->mins_cone[0] = currentshadowlight->origin[0] - currentshadowlight->radiuses[0];
					currentshadowlight->maxs_cone[0] = currentshadowlight->origin[0];
					currentshadowlight->mins_cone[1] = currentshadowlight->origin[1] - currentshadowlight->radiuses[1];
					currentshadowlight->maxs_cone[1] = currentshadowlight->origin[1] + currentshadowlight->radiuses[1];
					currentshadowlight->mins_cone[2] = currentshadowlight->origin[2] - currentshadowlight->radiuses[2];
					currentshadowlight->maxs_cone[2] = currentshadowlight->origin[2] + currentshadowlight->radiuses[2];
					goto draw;
				}
				else if (sb>=0.9999)
				{		// смотрит строго прямо
forward:			currentshadowlight->mins_cone[0] = currentshadowlight->origin[0] - currentshadowlight->radiuses[0];
					currentshadowlight->maxs_cone[0] = currentshadowlight->origin[0] + currentshadowlight->radiuses[0];
					currentshadowlight->mins_cone[1] = currentshadowlight->origin[1];
					currentshadowlight->maxs_cone[1] = currentshadowlight->origin[1] + currentshadowlight->radiuses[1];
					currentshadowlight->mins_cone[2] = currentshadowlight->origin[2] - currentshadowlight->radiuses[2];
					currentshadowlight->maxs_cone[2] = currentshadowlight->origin[2] + currentshadowlight->radiuses[2];
					goto draw;
				}
				else if (sb<=-0.9999)
				{		// смотрит строго назад
backward:			currentshadowlight->mins_cone[0] = currentshadowlight->origin[0] - currentshadowlight->radiuses[0];
					currentshadowlight->maxs_cone[0] = currentshadowlight->origin[0] + currentshadowlight->radiuses[0];
					currentshadowlight->mins_cone[1] = currentshadowlight->origin[1] - currentshadowlight->radiuses[1];
					currentshadowlight->maxs_cone[1] = currentshadowlight->origin[1];
					currentshadowlight->mins_cone[2] = currentshadowlight->origin[2] - currentshadowlight->radiuses[2];
					currentshadowlight->maxs_cone[2] = currentshadowlight->origin[2] + currentshadowlight->radiuses[2];
					goto draw;
				}
			}
		}
	}

sphere:
	for (i=0 ; i<3 ; i++)
	{
		currentshadowlight->mins_cone[i] = currentshadowlight->origin[i] - currentshadowlight->radiuses[i];
		currentshadowlight->maxs_cone[i] = currentshadowlight->origin[i] + currentshadowlight->radiuses[i];
	}

draw:
	for (i=0 ; i<3 ; i++)
	{
		currentshadowlight->mins[i] = currentshadowlight->origin[i] - currentshadowlight->radiuses[i];
		currentshadowlight->maxs[i] = currentshadowlight->origin[i] + currentshadowlight->radiuses[i];
	}

	if (gl_config.occlusion)
		glDeleteQueriesARB(1, &currentshadowlight->occID);

	// clear old light datas
	if(currentshadowlight->volumeCmds)
	{
		Z_Free(currentshadowlight->volumeCmds);
		currentshadowlight->volumeCmds = NULL;
	}
	if(currentshadowlight->lightCmds)
	{
		Z_Free(currentshadowlight->lightCmds);
		currentshadowlight->lightCmds = NULL;
	}
	if(currentshadowlight->sh_vbo_id)
	{
		R_VCFree(currentshadowlight->sh_vbo_id);
		currentshadowlight->sh_vbo_id = NULL;
	}

	int old_visframecount = r_visframecount;
///	if(currentshadowlight->isStatic)
	tmp = R_CalcSLight();	// precalculate static light
	r_visframecount = old_visframecount;
	return tmp;
}


void CL_LabelEmit_f ()
{
	int	i;

	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

	if (!curbrush)
	{
		Com_Printf("No brush model selected.\n");
		return;
	}

	// ищем энтити
	for (i=0 ; i<cl.refdef.num_entities ; i++)
	{
		currententity = &cl.refdef.entities[i];
		currentmodel = currententity->model;

		if (!currentmodel)
			continue;

		if (currentmodel->type != mod_brush)
			continue;

		if (currentmodel->nummodelsurfaces == 0)
			continue;

		if (currentmodel->firstmodelsurface == curbrush->firstface)
			break;
	}

	if (i==cl.refdef.num_entities)
		return;		// невозможно, но пусть будет...

	for (i=0; i<brushmodel_counter; i++)
		if (bmdl_list[i].index == curbrushnum)
			break;

	if (i==brushmodel_counter)
	{
		Com_Printf("No labeled brush model found.\n");
		return;
	}

	if (!bmdl_list[i].label[0])
	{
		Com_Printf("No labeled brush model selected.\n");
		return;
	}

	Memcpy(curemit->label, bmdl_list[i].label, MAX_QPATH);
	curemit->index = curbrushnum;

	VectorSubtract(curemit->origin, currententity->origin, curemit->origin);
	VectorSubtract(curemit->origin0, currententity->origin, curemit->origin0);
	curemit->angles[0] -= currententity->angles[0];
	curemit->angles[1] -= currententity->angles[1];

	Com_Printf("Emitter linked to brush labeled \"%s\".\n", curemit->label);
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_LabelModel_f ()
{
	int	i;

	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if (!curbrush)
	{
		Com_Printf("No brush model selected.\n");
		return;
	}

	// ищем энтити
	for (i=0 ; i<cl.refdef.num_entities ; i++)
	{
		currententity = &cl.refdef.entities[i];
		currentmodel = currententity->model;

		if (!currentmodel)
			continue;

		if (currentmodel->type != mod_brush)
			continue;

		if (currentmodel->nummodelsurfaces == 0)
			continue;

		if (currentmodel->firstmodelsurface == curbrush->firstface)
			break;
	}

	if (i==cl.refdef.num_entities)
		return;		// невозможно, но пусть будет...

	for (i=0; i<brushmodel_counter; i++)
		if (bmdl_list[i].index == curbrushnum)
			break;

	if (i==brushmodel_counter)
	{
		Com_Printf("No labeled brush model found.\n");
		return;
	}

	if (!bmdl_list[i].label[0])
	{
		Com_Printf("No labeled brush model selected.\n");
		return;
	}

	Memcpy(curmodel->label, bmdl_list[i].label, MAX_QPATH);
	curmodel->index = curbrushnum;

	VectorSubtract(curmodel->origin, currententity->origin, curmodel->origin);
	VectorSubtract(curmodel->angles, currententity->angles, curmodel->angles);

	Com_Printf("Model linked to brush labeled \"%s\".\n", curmodel->label);
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_PasteEmit_f ()
{
	emit_t	*l;

	if (!LightEditor_Enabled(false))
		return;

	if(!emit_clipboard.vel)	// клипбоард пока пуст
		return;

	l = AllocEmit();
	if (!l)
	{
		Com_Printf("^3No enought room for emitter.\n");
		Cl_UpdateEditorCvars(ED_EMIT);
		return;
	}

	Memcpy(l, &emit_clipboard, sizeof(emit_t));

	if (!Q_strcasecmp(Cmd_Argv(0), "paste2cameraemit"))
	{
		VectorCopy(r_origin, l->origin);
		VectorCopy(r_origin, l->origin0);
		l->area = CM_LeafArea (CM_PointLeafnum (l->origin));
	}

	if (!l->area)
		Com_Printf("^3Emitter is out of BSP!\n");

	curemit = l;
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_PasteCurEmit_f ()
{
	vec3_t	bak;

	if (!LightEditor_Enabled(false))
		return;

	if(!emit_clipboard.vel)		// клипбоард пока пуст
		return;

	if (!curemit)
		return;

	VectorCopy(curemit->origin, bak);
	Memcpy(curemit, &emit_clipboard, sizeof(emit_t));
	VectorCopy(bak, curemit->origin);
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_MoveEmit_f ()
{
	int		pos;
	float	add;

	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

	if( Cmd_Argc() == 1 )
		add = 1;
	else
		add = atof(Cmd_Argv(1));

	if (!Q_strcasecmp(Cmd_Argv(0), "moveemit_x"))
		pos = 0;
	else if (!Q_strcasecmp(Cmd_Argv(0), "moveemit_y"))
		pos = 1;
	else
		pos = 2;

	curemit->origin[pos] += add;
	curemit->origin0[pos] += add;

// После изменения параметров требуется пересчитать некоторые параметры!
	curemit->area = CM_LeafArea (CM_PointLeafnum (curemit->origin));
	if (!curemit->area)
		Com_Printf("^3Emitter is out of BSP!\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_MoveModel_f ()
{
	int		pos;
	float	add;

	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if( Cmd_Argc() == 1 )
		add = 1;
	else
		add = atof(Cmd_Argv(1));

	if (!Q_strcasecmp(Cmd_Argv(0), "movemodel_x"))
		pos = 0;
	else if (!Q_strcasecmp(Cmd_Argv(0), "movemodel_y"))
		pos = 1;
	else	// movemodel_z"
		pos = 2;

	curmodel->origin[pos] += add;

	if (!R_MarkAliasLeaves(curmodel))
		Com_Printf("^3Warning:^7 model out of BSP\n");
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_Move_f ()
{
	int		pos;
	float	add;

	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() == 1 )
		add = 1;
	else
		add = atof(Cmd_Argv(1));

	if (!Q_strcasecmp(Cmd_Argv(0), "move_x"))
		pos = 0;
	else if (!Q_strcasecmp(Cmd_Argv(0), "move_y"))
		pos = 1;
	else if (!Q_strcasecmp(Cmd_Argv(0), "move_z"))
		pos = 2;
	else			// add_r:
		pos = -1;	// special case

	if (le_mode)
	{
		if (!curlink)
			return;

		if (pos == -1)
		{
			curlink->radius += add;
			if(curlink->radius<=1)
				curlink->radius=2;
		}
		else
			curlink->origin[pos] += add;
	}
	else
	{
		if (!curlight)
			return;

		if (pos == -1)
		{
			if (!Q_strcasecmp(Cmd_Argv(0), "add_r_x"))
			{
				curlight->radiuses[0] += add;
				curlight->radius = max(max(curlight->radiuses[0], curlight->radiuses[1]), curlight->radiuses[2]);
			}
			else if (!Q_strcasecmp(Cmd_Argv(0), "add_r_y"))
			{
				curlight->radiuses[1] += add;
				curlight->radius = max(max(curlight->radiuses[0], curlight->radiuses[1]), curlight->radiuses[2]);
			}
			else if (!Q_strcasecmp(Cmd_Argv(0), "add_r_z"))
			{
				curlight->radiuses[2] += add;
				curlight->radius = max(max(curlight->radiuses[0], curlight->radiuses[1]), curlight->radiuses[2]);
			}
			else	// add_r
			{
				curlight->radius += add;
				curlight->radiuses[0] += add;
				curlight->radiuses[1] += add;
				curlight->radiuses[2] += add;
			}

			if(curlight->radius<=1)			curlight->radius=2;
			if(curlight->radiuses[0]<=1)	curlight->radiuses[0]=2;
			if(curlight->radiuses[1]<=1)	curlight->radiuses[1]=2;
			if(curlight->radiuses[2]<=1)	curlight->radiuses[2]=2;

			curlight->sphere = (((int)curlight->radiuses[0] == (int)curlight->radiuses[1]) && ((int)curlight->radiuses[0] == (int)curlight->radiuses[2]));
		}
		else
			curlight->origin[pos] += add;

// После изменения параметров требуется пересчитать некоторые параметры!
		currentshadowlight = curlight;
VectorSet(currentshadowlight->frust[0].normal, 0,0,0);	// форсируем пересчет frust
		ComputeBSPLight();
	}
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_SunAdd_f ()
{
	int		pos;
	float	add;

	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() == 1 )
	{
		Com_Printf("^3Usage: %s <delta>\n", Cmd_Argv(0));
		return;
	}
	else
		add = atof(Cmd_Argv(1));

	if (!Q_strcasecmp(Cmd_Argv(0), "sun_addx"))
		pos = 0;
	else if (!Q_strcasecmp(Cmd_Argv(0), "sun_addy"))
		pos = 1;
	else
		pos = 2;

	sun_origin[pos] += add;
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Rot_f ()
{
	int		pos;
	float	add;

	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() == 1 )
		add = 1;
	else
		add = atof(Cmd_Argv(1));

	if (!Q_strcasecmp(Cmd_Argv(0), "rot_a"))
		pos = 0;
	else if (!Q_strcasecmp(Cmd_Argv(0), "rot_b"))
		pos = 1;
	else			// rot_c
		pos = 2;

	if (le_mode)
	{
		if (!curlink)
			return;

		curlink->angles[pos] += add;
	}
	else
	{
		if (!curlight)
			return;

		curlight->angles[pos] += add;

// После изменения углов требуется пересчитать некоторые параметры!
		currentshadowlight = curlight;
		VectorSet(currentshadowlight->frust[0].normal, 0,0,0);	// форсируем пересчет frust
		ComputeBSPLight();
	}
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_Direct_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (!curlink)
			return;

		AnglesFromVecs(curlink->origin, r_origin, curlink->angles);
		Com_Printf("^3WARNING:^7 \"direct\" not recommended for linked lights!\n");
	}
	else
	{
		if (!curlight)
			return;

		AnglesFromVecs(curlight->origin, r_origin, curlight->angles);
// После изменения углов требуется пересчитать некоторые параметры!
		currentshadowlight = curlight;
		VectorSet(currentshadowlight->frust[0].normal, 0,0,0);	// форсируем пересчет frust
		ComputeBSPLight();
	}
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_StartColorEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

	if( Cmd_Argc() != 4 )
	{
		Com_Printf("^3Usage: StartColorEmit <r g b>\n");
		return;
	}

	curemit->startcolor[0] = atof(Cmd_Argv(1));
	curemit->startcolor[1] = atof(Cmd_Argv(2));
	curemit->startcolor[2] = atof(Cmd_Argv(3));
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_EndColorEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

	if( Cmd_Argc() != 4 )
	{
		Com_Printf("^3Usage: EndColorEmit <r g b>\n");
		return;
	}

	curemit->endcolor[0] = atof(Cmd_Argv(1));
	curemit->endcolor[1] = atof(Cmd_Argv(2));
	curemit->endcolor[2] = atof(Cmd_Argv(3));
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_LifeTimeEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: LifeTimeEmit <seconds>\n");
		return;
	}

	curemit->alphavel = atof(Cmd_Argv(1));
	Com_Printf("Emitter's LifeTimeEmit changed.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_GravityEmit_f ()
{
	int grav;

	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: GravityEmit <n>\n");
		return;
	}

	grav = atoi(Cmd_Argv(1));

	if (grav > 127)	curemit->gravity = 127;
	else if (grav < -128)	curemit->gravity = -128;
	else curemit->gravity = grav;

	Com_Printf("Emitter's gravity changed.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_DirectEmit_f ()
{
	vec3_t	ang;

	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

////	float bak = curemit->angles[2];
////	AnglesFromVecs(curemit->origin, r_origin, curemit->angles);
////	curemit->angles[2] = bak;
		AnglesFromVecs(curemit->origin, r_origin, ang);
		curemit->angles[0] = ang[0];
		curemit->angles[1] = ang[1];

// После изменения углов требуется пересчитать некоторые параметры!
	VectorSet(ang, curemit->angles[0], curemit->angles[1], 0);
	AngleVectors(ang, curemit->dir, NULL, NULL);
	VectorCopy(curemit->dir, curemit->dir0);
	Com_Printf("Emitter's angles changed.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_RotEmit_f ()
{
	int		pos;
	float	add;

	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

	if( Cmd_Argc() == 1 )
		add = 1;
	else
		add = atof(Cmd_Argv(1));

	if (!Q_strcasecmp(Cmd_Argv(0), "rotemit_a"))
		pos = 0;
	else			// rotemit_b
		pos = 1;

	curemit->angles[pos] += add;
// После изменения углов требуется пересчитать некоторые параметры!
	vec3_t	ang;
	VectorSet(ang, curemit->angles[0], curemit->angles[1], 0);
	AngleVectors(ang, curemit->dir, NULL, NULL);
	VectorCopy(curemit->dir, curemit->dir0);
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_CreateEmit_f()
{
	vec3_t	v;
	emit_t	*l;

	if (!LightEditor_Enabled(false))
		return;

	l = AllocEmit();
	if (!l)
	{
		Com_Printf("^3No enought room for emitter.\n");
		Cl_UpdateEditorCvars(ED_EMIT);
		return;
	}

	VectorCopy(r_origin, l->origin);
	VectorCopy(r_origin, l->origin0);
	VectorSet(v, -90,0,0);
	l->angles[0] = v[0];
	l->angles[1] = v[1];
	l->vel = 128;
	AngleVectors(v, l->dir, NULL, NULL);
	VectorCopy(l->dir, l->dir0);
	VectorSet(l->rspeed, 0,0,3);
	l->style = 0;
	l->cl_style = 0;
	l->emit = part_simple;
	l->cone = 0.2;
	l->alphavel = 0;							// no lifetime override
	l->gravity = 0;								// no gravity override
	VectorClear(l->startcolor);					// no color override
	Memset(l->label, 0, MAX_QPATH);
	l->area = CM_LeafArea (CM_PointLeafnum (l->origin));
	if (!l->area)
		Com_Printf("^3Emitter is out of BSP!\n");
	curemit = l;
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_NoDrawBrush_f()
{
	int	i;

	if (curbrush)
		for (i=0; i<brushmodel_counter; i++)
			if (bmdl_list[i].index == curbrushnum)
			{
				bmdl_list[i].nodraw = !bmdl_list[i].nodraw;
				Com_Printf("NoDraw changed\n");
			}
}


void CL_FilterDecal_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: typedecal <n>\n");
		return;
	}

	if (!curdecal)
		return;

	int	num = atoi(Cmd_Argv(1));
	if (num<0 || num>MAX_FILTERS)
		num = 0;

	curdecal->texture = decal_texture_object[num];

	for (decal_t *p=active_decals ; p ; p=p->next)
		if (p->defDecal == curdecal)
			for (num = 0; num < p->triangleCount; num++)
				p->images[num] = curdecal->texture;

	if (curbrush)
		VectorCopy(curbrush_offset, curdecal->offset);
	else
		VectorClear(curdecal->offset);

	Com_Printf("Decal's texture changed.\n");
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_BlendDecal_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curdecal)
		return;

	if( Cmd_Argc() != 2)
	{
err:	Com_Printf("^3Usage: blenddecal <add | mul>\n");
		return;
	}

	bool mul = !Q_strcasecmp(Cmd_Argv(1), "mul");
	bool add = !Q_strcasecmp(Cmd_Argv(1), "add");

	if (!mul && !add)
		goto err;

	curdecal->dtype = mul ? dt_mul : dt_add;

	for (decal_t *p=active_decals ; p ; p=p->next)
		if (p->defDecal == curdecal)
			for (int num = 0; num < p->triangleCount; num++)
				p->types[num] = curdecal->dtype;

	if (curbrush)
		VectorCopy(curbrush_offset, curdecal->offset);
	else
		VectorClear(curdecal->offset);

	Com_Printf("Decal's blend changed.\n");
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_AngleDecal_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2)
	{
		Com_Printf("^3Usage: angledecal <ang>\n");
		return;
	}

	if (!curdecal)
		return;

	curdecal->angle = atof(Cmd_Argv(1));

	for (decal_t *p=active_decals ; p ; p=p->next)
		if (p->defDecal == curdecal)
			p->angle = curdecal->angle;

	if (curbrush)
		VectorCopy(curbrush_offset, curdecal->offset);
	else
		VectorClear(curdecal->offset);

	Com_Printf("Decal's angle changed.\n");
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_RotDecal_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2)
	{
		Com_Printf("^3Usage: rot_decal <ang>\n");
		return;
	}

	if (!curdecal)
		return;

	curdecal->angle += atof(Cmd_Argv(1));

	for (decal_t *p=active_decals ; p ; p=p->next)
		if (p->defDecal == curdecal)
			p->angle = curdecal->angle;

	if (curbrush)
		VectorCopy(curbrush_offset, curdecal->offset);
	else
		VectorClear(curdecal->offset);
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_PasteDecal_f ()
{
	vec3_t	end;

	if (!LightEditor_Enabled(false))
		return;

	if (!decal_clipboard.size)	// клипбоард пока пуст!
		return;

	curdecal = AllocDefDecal();

	if (curdecal)
	{
		VectorMA (r_origin, 32768, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
		trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, MASK_SOLID);
///		if(trace.surface->name)
			if(trace.surface->name[0])
			{
				VectorMA (r_origin, 32768*trace.fraction-1, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
				if (curbrush)
					VectorCopy(curbrush_offset, curdecal->offset);
				else
					VectorClear(curdecal->offset);
				R_SpawnDecal(end, trace.plane.normal, decal_clipboard.texture, decal_clipboard.size, decal_clipboard.size, 9999999, 9999999, decal_clipboard.angle, decal_clipboard.angle, decal_clipboard.dtype, false, decal_clipboard.style, decal_clipboard.cl_style, curdecal, false);
				curdecal->angle = decal_clipboard.angle;
				VectorCopy(end, curdecal->center);
				curdecal->cl_style = decal_clipboard.cl_style;
				curdecal->dtype = decal_clipboard.dtype;
				VectorCopy(trace.plane.normal, curdecal->normal);
				curdecal->size = decal_clipboard.size;
				curdecal->style = decal_clipboard.style;
				curdecal->texture = decal_clipboard.texture;
				if (sv_stopclock->value)
					Com_Printf("Decal spawned.\n");
				else
					Com_Printf("^3WARNING:^7 Decal spawned, but sv_stopclock = 0\n");
			}
		if (curbrush)
			VectorCopy(curbrush_offset, curdecal->offset);
		else
			VectorClear(curdecal->offset);
	}
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_RotModel_f ()
{
	int		pos;
	float	add;

	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if( Cmd_Argc() == 1 )
		add = 1;
	else
		add = atof(Cmd_Argv(1));

	if (!Q_strcasecmp(Cmd_Argv(0), "rotmodel_a"))
		pos = 0;
	else if (!Q_strcasecmp(Cmd_Argv(0), "rotmodel_b"))
		pos = 1;
	else			// rotmodel_c
		pos = 2;

	curmodel->angles[pos] += add;
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_AnglesModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 4 )
	{
		Com_Printf("^3Usage: anglesmodel <a b c>\n");
		return;
	}

	if (!curmodel)
		return;

	curmodel->angles[0] = atof(Cmd_Argv(1));
	curmodel->angles[1] = atof(Cmd_Argv(2));
	curmodel->angles[2] = atof(Cmd_Argv(3));
	Com_Printf("Angles changed.\n");
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_UnSelectDecal_f ()
{
	curdecal = NULL;
	if (trace_decal)
	{
		Com_Printf("Trace decal OFF\n");
		trace_decal = false;
	}
	Cl_UpdateEditorCvars(ED_DECAL);
}

void CL_UnSelectModel_f ()
{
	curmodel = NULL;
	if (trace_model)
	{
		Com_Printf("Trace model OFF\n");
		trace_model = false;
	}
	Cl_UpdateEditorCvars(ED_MODEL);
}

void CL_SelectNearestDecal_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	int		i;
	float	dist, best_dist = 99999999;

	curdecal = NULL;
	for (i = 0; i < num_deferred_decals; i++)
	{
		float	x, y, z;
		defDecal_t	*p = &deferred_decals[i];
		x = r_origin[0] - p->center[0];
		y = r_origin[1] - p->center[1];
		z = r_origin[2] - p->center[2];
		dist = x*x+y*y+z*z;
		if(dist<best_dist)
		{
			best_dist=dist;
			curdecal=p;
		}
	}

	if(!curdecal)
		Com_Printf("^3Decal not found.\n");
	Cl_UpdateEditorCvars(ED_DECAL);
}


void CL_SelectModelMesh_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: select_model_mesh <+n/-n/n>\n");
		return;
	}

	if (!curmodel)
		return;

	if (curmodel->mdl->type == mod_alias_md3)
	{
		char *s = Cmd_Argv(1);
		if (s[0]=='-' || s[0]=='+')
			curmesh += atoi(s);
		else
			curmesh = atoi(s);

		maliasmodel_t	*paliashdr = (maliasmodel_t *)curmodel->mdl->extradata;
		if (curmesh<0 || curmesh>=paliashdr->num_meshes)
		{
			Com_Printf("Mesh index must be in (0 - %i)\n", paliashdr->num_meshes-1);
			if (curmesh<0)
				curmesh = paliashdr->num_meshes-1;
			else if (curmesh>=paliashdr->num_meshes)
				curmesh = 0;
		}
		curtri = 0;
	}
	Cl_UpdateEditorCvars(ED_MODEL);
}


void CL_SelectModelTri_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: select_model_tri <+n/-n/n>\n");
		return;
	}

	if (!curmodel)
		return;

	char *s = Cmd_Argv(1);
	if (s[0]=='-' || s[0]=='+')
		curtri += atoi(s);
	else
		curtri = atoi(s);

	if (curmodel->mdl->type == mod_alias)
	{
		dmdl_t	*paliashdr = (dmdl_t *)curmodel->mdl->extradata;
		if (curtri<0 || curtri>=paliashdr->num_tris)
		{
			Com_Printf("Tri index must be in (0 - %i)\n", paliashdr->num_tris-1);
			if (curtri<0)
				curtri = paliashdr->num_tris-1;
			else if (curtri>=paliashdr->num_tris)
				curtri = 0;
		}
	}
	else
	{	/// mod_alias_md3
		maliasmodel_t	*paliashdr = (maliasmodel_t *)curmodel->mdl->extradata;
		if (curmesh<0 || curmesh>=paliashdr->num_meshes)
		{
			Com_Printf("Mesh index must be in (0 - %i)\n", paliashdr->num_meshes-1);
			if (curmesh<0)
				curmesh = paliashdr->num_meshes-1;
			else if (curmesh>=paliashdr->num_meshes)
				curmesh = 0;
		}
		maliasmesh_t	*mesh = &paliashdr->meshes[curmesh];
		if (curtri<0 || curtri>=mesh->num_tris)
		{
			Com_Printf("Tri index must be in (0 - %i) for mesh %i\n", mesh->num_tris-1, curmesh);
			if (curtri<0)
				curtri = mesh->num_tris-1;
			else if (curtri>=mesh->num_tris)
				curtri = 0;
		}
	}
	Cl_UpdateEditorCvars(ED_MODEL);
}

void CL_SelectNearestModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	float	dist, best_dist = 99999999;
	alink_t	*p;

	curmodel = NULL;
	for (int am=0; am<aliasmodel_counter; am++)
	{
		p = &amdl_list[am];
		if (p->label[0]==0 || (p->label[0]==' ' && p->label[1]==0))
		{
m1:			float	x, y, z;
			x = r_origin[0] - p->origin[0];
			y = r_origin[1] - p->origin[1];
			z = r_origin[2] - p->origin[2];
			dist = x*x+y*y+z*z;
			if(dist<best_dist)
			{
				best_dist=dist;
				curmodel=p;
			}
		}
		else
		{
			int		bm, j;
			float	x, y, z;
			blink_t *brush;
			vec3_t	org, temp, tempang;
			vec3_t	forward, right, up;
			entity_t	*ent;

			for (bm=0; bm<brushmodel_counter; bm++)
				if (bmdl_list[bm].index == p->index)
						break;
			if (bm < brushmodel_counter)
			{
				brush = &bmdl_list[bm];

				for (j=0; j<cl.refdef.num_entities; j++)
				{
					ent = &cl.refdef.entities[j];
					if (ent->model == cl.model_draw[p->index+1])
						break;
				}
				if (j<cl.refdef.num_entities)
				{
					VectorSubtract(p->origin, brush->origin, org);
					VectorNegate(ent->angles, tempang);

					if(tempang[0] || tempang[1] || tempang[2])
					{
						AngleVectors (tempang, forward, right, up);
						VectorCopy (org, temp);
						org[0] = DotProduct (temp, forward);
						org[1] = -DotProduct (temp, right);
						org[2] = DotProduct (temp, up);
					}

					VectorAdd(org, ent->origin, org);
					x = r_origin[0] - org[0];
					y = r_origin[1] - org[1];
					z = r_origin[2] - org[2];
					dist = x*x+y*y+z*z;
					if(dist<best_dist)
					{
						best_dist=dist;
						curmodel=p;
					}
				}
			}
			else
				goto m1;
		}
	}

	if(!curmodel)
		Com_Printf("^3Model not found.\n");
	Cl_UpdateEditorCvars(ED_MODEL);
}


void CL_SizeDecal_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2)
	{
		Com_Printf("^3Usage: sizedecal <n>\n");
		return;
	}

	if (!curdecal)
		return;

	curdecal->size = atof(Cmd_Argv(1));
	if (curdecal->size<1)
		curdecal->size = 1;
	// Скопируем в буфер обмена и удалим декаль
	CL_CutDecal_f();

	curdecal = AllocDefDecal();
	if (curdecal)
	{	// Пересчитаем геометрию декаля и создадим его
		if (curbrush)
			VectorCopy(curbrush_offset, curdecal->offset);
		else
			VectorClear(curdecal->offset);
		R_SpawnDecal(decal_clipboard.center, decal_clipboard.normal, decal_clipboard.texture, decal_clipboard.size, decal_clipboard.size, 9999999, 9999999, decal_clipboard.angle, decal_clipboard.angle, decal_clipboard.dtype, false, decal_clipboard.style, decal_clipboard.cl_style, curdecal, false);
		curdecal->angle = decal_clipboard.angle;
		VectorCopy(decal_clipboard.center, curdecal->center);
		curdecal->cl_style = decal_clipboard.cl_style;
		curdecal->dtype = decal_clipboard.dtype;
		VectorCopy(decal_clipboard.normal, curdecal->normal);
		curdecal->size = decal_clipboard.size;
		curdecal->style = decal_clipboard.style;
		curdecal->texture = decal_clipboard.texture;
///		if (!sv_stopclock->value)
///			Com_Printf("^3WARNING:^7 sv_stopclock = 0\n");
	}
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_TraceLight_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (!curlink)
			return;
		if (!trace_light)
			Com_Printf("^3WARNING:^7 \"trace\" not recommended for linked lights!\n");
	}
	else
	{
		if (!curlight)
			return;
	}

	if (!trace_light)
	{
		Com_Printf("Trace light ON\n");
		trace_light = true;
	}
	else
	{
		Com_Printf("Trace light OFF\n");
		trace_light = false;
	}
}


void CL_ReSizeDecal_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2)
	{
		Com_Printf("^3Usage: resizedecal <n>\n");
		return;
	}

	if (!curdecal)
		return;

	curdecal->size += atof(Cmd_Argv(1));
	if (curdecal->size<1)
		curdecal->size = 1;
	// Скопируем в буфер обмена и удалим декаль
	CL_CutDecal_f();

	curdecal = AllocDefDecal();
	if (curdecal)
	{	// Пересчитаем геометрию декаля и создадим его
		if (curbrush)
			VectorCopy(curbrush_offset, curdecal->offset);
		else
			VectorClear(curdecal->offset);
		R_SpawnDecal(decal_clipboard.center, decal_clipboard.normal, decal_clipboard.texture, decal_clipboard.size, decal_clipboard.size, 9999999, 9999999, decal_clipboard.angle, decal_clipboard.angle, decal_clipboard.dtype, false, decal_clipboard.style, decal_clipboard.cl_style, curdecal, false);
		curdecal->angle = decal_clipboard.angle;
		VectorCopy(decal_clipboard.center, curdecal->center);
		curdecal->cl_style = decal_clipboard.cl_style;
		curdecal->dtype = decal_clipboard.dtype;
		VectorCopy(decal_clipboard.normal, curdecal->normal);
		curdecal->size = decal_clipboard.size;
		curdecal->style = decal_clipboard.style;
		curdecal->texture = decal_clipboard.texture;
///		if (!sv_stopclock->value)
///			Com_Printf("^3WARNING:^7 sv_stopclock = 0\n");
	}
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_IgnoreLightSurf_f ()
{
	vec3_t	end;

	if (!LightEditor_Enabled(false))
		return;

	VectorMA (r_origin, 32768, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
	trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, CONTENTS_SOLID);

	if(trace.surface && trace.surface!=&(nullsurface.c))
		Remove_Flares((mapsurface_t*)trace.surface - &map_surfaces[0], true);
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_OpaqueSky_f ()
{
	vec3_t	end;

	if (!LightEditor_Enabled(false))
		return;

	VectorMA (r_origin, 32768, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
	trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, CONTENTS_SOLID);

	if(trace.surface && trace.surface!=&(nullsurface.c))
	{
		if (!(trace.surface->flags & SURF_SKY))
		{
bad:		Com_Printf("^3Surface is not SKY\n");
			return;
		}

		int		num_texinfo = (mapsurface_t*)trace.surface - &map_surfaces[0];
		if (!(r_worldmodel->texinfo[num_texinfo].flags & SURF_SKY))
			goto bad;

		if (r_worldmodel->texinfo[num_texinfo].flags & SURF_SLICK)
		{
			r_worldmodel->texinfo[num_texinfo].flags &= ~SURF_SLICK;
			Com_Printf("Surface %i is TRANSLUCENT\n", num_texinfo);
		}
		else
		{
			r_worldmodel->texinfo[num_texinfo].flags |= SURF_SLICK;
			Com_Printf("Surface %i is OPAQUE\n", num_texinfo);
		}

		// Пересчитаем все shadow volumes
		for(int i=0; i<numStaticShadowLights; i++)
		{
			currentshadowlight = &shadowlights[i];
			ComputeBSPLight();
		}
		Cl_UpdateEditorCvars(ED_MODIFIED);
	}
}


void CL_DeleteFlareLights_f()
{
	if (!LightEditor_Enabled(false))
		return;

	int i, j=0;
	bool old_le_mode = le_mode;
	le_mode = false;
	ignore_cvars = true;
	for(i=0; i<numStaticShadowLights; i++)
	{
		curlight = &shadowlights[i];
		if(curlight->isStatic && curlight->flareLight)
		{
			int old_numStaticShadowLights = numStaticShadowLights;
			CL_Delete_f();
			if (old_numStaticShadowLights!=numStaticShadowLights)
			{
				j++;
				i--;
			}
		}
	}
	le_mode = old_le_mode;
	curlight = NULL;
	ignore_cvars = false;
	if (j)
		Com_Printf("Deleted %i flareLights\n", j);
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_CreateFlareLights_f()
{
	int				i, j, jj;
	shadowlight_t	light;
	flare_t			*l;

	if (!LightEditor_Enabled(false))
		return;

	if (!num_flares)
	{
		if (!r_flares->value)
			Com_Printf("^3You must set r_flares to 1\n");
		else
			Com_Printf("^3No flares found...\n");
	}

	float	flareScale;
	if( Cmd_Argc() == 1 )
		flareScale = 1;
	else
	{
		flareScale = atof(Cmd_Argv(1));
		if (flareScale < 0.05)
			flareScale = 0.05;
	}

	CL_DeleteFlareLights_f();

	for (j=0, jj=0, i=0, l = flares; i<num_flares; i++, l++)
	{
		if (l->surf->ent)
			continue;

		Memset(&light, 0, sizeof(shadowlight_t));
		float	r,g,b,t;
		r = l->surf->texinfo->image->color[0];
		g = l->surf->texinfo->image->color[1];
		b = l->surf->texinfo->image->color[2];
		t = max(r, max(g, b));
		if (t>0.001)
			t = 1.0 / t;
		t *= flareScale;
		VectorSet(light.color, r*t,g*t,b*t);
		if (l->surf->flags & SURF_PLANEBACK)
			VectorMA(l->surf->center, -max(2, l->size*0.5), l->surf->plane->normal, light.origin);
		else
			VectorMA(l->surf->center, max(2, l->size*0.5), l->surf->plane->normal, light.origin);
		light.radiuses[0] = light.radiuses[1] = light.radiuses[2] = light.radius = l->size*2;
		light.sphere = true;
		light.flareLight = true;
		if (SpawnStaticLight(&light))
		{
			currentshadowlight = &shadowlights[numStaticShadowLights-1];
			if(ComputeBSPLight())
				j++;
			else
			{
				ignore_cvars = true;
				bool old_le_mode = le_mode;
				le_mode = false;
				curlight = currentshadowlight;
				CL_Delete_f();		// reject
				ignore_cvars = false;
				le_mode = old_le_mode;
				curlight = NULL;
			}
			jj++;
		}
	}
	if (jj)
		Com_Printf("Spawned %i flareLights, %i rejected\n", j, jj-j);
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_ShowSurf_f()
{
	int		num_texinfo;
	vec3_t	end;

	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() == 2)
	{
		num_texinfo = atoi(Cmd_Argv(1));
		if (num_texinfo>=0 && num_texinfo<r_worldmodel->numsurfaces)
		{
			if (r_worldmodel->texinfo[num_texinfo].flags & SURF_SKY)
				return;
			curtexinfo = &r_worldmodel->texinfo[num_texinfo];
		}
		else
		{
			Com_Printf("Unselected texinfo\n");
			curtexinfo = NULL;
			return;
		}
	}
	else
	{
		VectorMA (r_origin, 32768, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
		trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);

		if(trace.surface && trace.surface!=&(nullsurface.c))
		{
			if (trace.surface->flags & SURF_SKY)
				return;
			num_texinfo = (mapsurface_t*)trace.surface - &map_surfaces[0];
			if (r_worldmodel->texinfo[num_texinfo].flags & SURF_SKY)
				return;
			curtexinfo = &r_worldmodel->texinfo[num_texinfo];
		}
	}
	Com_Printf("Selected texinfo %i\n", num_texinfo);
}


void CL_NoDrawSurf_f()
{
	int		num_texinfo;
	vec3_t	end;

	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() == 2)
	{
		num_texinfo = atoi(Cmd_Argv(1));
		if (num_texinfo<0 || num_texinfo>=r_worldmodel->numsurfaces)
			return;
	}
	else
	{
		VectorMA (r_origin, 32768, vpn, end);	// was 8192, увеличил чтоб доставало до любого места на карте (LARGE_MAP_SIZE)
		trace_t trace = CL_PMTraceWorld (r_origin, vec3_origin, vec3_origin, end, CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);

		if(trace.surface && trace.surface!=&(nullsurface.c))
		{
			if (trace.surface->flags & SURF_SKY)
				return;
			num_texinfo = (mapsurface_t*)trace.surface - &map_surfaces[0];
		}
	}

	if (r_worldmodel->texinfo[num_texinfo].flags & SURF_SKY)
		return;

	int i, ns = 0;
	for (i=0; i<r_worldmodel->numsurfaces; i++)
		if (r_worldmodel->surfaces[i].texinfo == &(r_worldmodel->texinfo[num_texinfo]))
			ns++;

	if (r_worldmodel->texinfo[num_texinfo].flags & SURF_NODRAW)
	{
		r_worldmodel->texinfo[num_texinfo].flags &= ~SURF_NODRAW;
		Com_Printf("%i surface(s) #%i is VISIBLE\n", ns, num_texinfo);
	}
	else
	{
		r_worldmodel->texinfo[num_texinfo].flags |= SURF_NODRAW;
		Com_Printf("%i surface(s) #%i is INVISIBLE\n", ns, num_texinfo);
	}

	// Пересчитаем все shadow volumes
	for(int i=0; i<numStaticShadowLights; i++)
	{
		currentshadowlight = &shadowlights[i];
		ComputeBSPLight();
	}
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


/*
==================
CL_DeltaEntity

Parses deltas from the given base and adds the resulting entity
to the current frame
==================
*/
void CL_DeltaEntity (frame_t *frame, int newnum, entity_state_t *old, int bits)
{
	centity_t	*ent;
	entity_state_t	*state;

	ent = &cl_entities[newnum];

	state = &cl_parse_entities[cl.parse_entities & (MAX_PARSE_ENTITIES-1)];
	cl.parse_entities++;
	frame->num_entities++;

	CL_ParseDelta (old, state, newnum, bits);

	// some data changes will force no lerping
	if (state->modelindex != ent->current.modelindex
		|| state->modelindex2 != ent->current.modelindex2
		|| state->modelindex3 != ent->current.modelindex3
		|| state->modelindex4 != ent->current.modelindex4
		|| fabs(state->origin[0] - ent->current.origin[0]) > 512
		|| fabs(state->origin[1] - ent->current.origin[1]) > 512
		|| fabs(state->origin[2] - ent->current.origin[2]) > 512
		|| state->event == EV_PLAYER_TELEPORT
		|| state->event == EV_OTHER_TELEPORT
		)
	{
		ent->serverframe = -99;
	}

	if (ent->serverframe != cl.frame.serverframe - 1)
	{	// wasn't in last update, so initialize some things
		ent->trailcount = 16;//64;//1024;		// for diminishing rocket / grenade trails	/// BERSERKER: уменьшил кол-во партиклей, так как теперь они текстурированные! (если меняешь, поменяй в CL_DiminishingTrail!!!)
		// duplicate the current state so lerping doesn't hurt anything
		ent->prev = *state;
		if (state->event == EV_OTHER_TELEPORT)
		{
			VectorCopy (state->origin, ent->prev.origin);
			VectorCopy (state->origin, ent->lerp_origin);
		}
		else
		{
			VectorCopy (state->old_origin, ent->prev.origin);
			VectorCopy (state->old_origin, ent->lerp_origin);
		}
	}
	else
	{	// shuffle the last state to previous
		ent->prev = ent->current;
	}

	ent->serverframe = cl.frame.serverframe;
	ent->current = *state;
}

void DrawLevelShot(void *notused)
{
	char	map[MAX_QPATH], file[MAX_QPATH];

	if (s_mapname_field.buffer[0])
	{
		Com_sprintf(file, sizeof(file), "levelshots/%s.jpg", s_mapname_field.buffer);
		Draw_PicSized( ((viddef.width-320)>>1) + 140, ((viddef.height-240)>>1) + 64, 160, 120, file );
		Menu_DrawStatusBar(s_mapname_field.buffer);
	}
	else
	{
		strcpy( map, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 );
		Com_sprintf(file, sizeof(file), "levelshots/%s.jpg", map);
		Draw_PicSized( ((viddef.width-320)>>1) + 140, ((viddef.height-240)>>1) + 64, 160, 120, file );
		Menu_DrawStatusBar(map);
	}
}


void CL_Radius_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (!curlink)
			return;

		float	r;

		if( Cmd_Argc() != 2 )
		{
			Com_Printf("^3Usage: radius <r>\n");
			return;
		}

		r = atof(Cmd_Argv(1));
		if(r<=1)	return;

		curlink->radius = r;

		Com_Printf("Radius changed.\n");
	}
	else
	{
		if (!curlight)
			return;

		float	x, y, z, r;

		if(( Cmd_Argc() != 2 ) && ( Cmd_Argc() != 4 ))
		{
			Com_Printf("^3Usage: radius <x y z | r>\n");
			return;
		}

		if(Cmd_Argc()==4)
		{
			x = atof(Cmd_Argv(1));
			y = atof(Cmd_Argv(2));
			z = atof(Cmd_Argv(3));

			if(x<=1)	return;
			if(y<=1)	return;
			if(z<=1)	return;

			r = max(max(x, y), z);
			curlight->sphere = (((int)x == (int)y) && ((int)x == (int)z));
		}
		else
		{
			r = x = y = z = atof(Cmd_Argv(1));
			if(r<=1)	return;
			curlight->sphere = true;
		}

		curlight->radius = r;
		VectorSet(curlight->radiuses, x,y,z);

// После изменения параметров требуется пересчитать некоторые параметры!
		currentshadowlight = curlight;
		if (ComputeBSPLight())
			Com_Printf("Radius changed.\n");
		else
			Com_Printf("Static light rejected.\n");
	}
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_OriginEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if(( Cmd_Argc() != 2 ) && ( Cmd_Argc() != 4 ))
	{
		Com_Printf("^3Usage: originemit <x y z | camera>\n");
		return;
	}

	if (!curemit)
		return;

	if (( Cmd_Argc() == 2 ) && (!Q_strcasecmp(Cmd_Argv(1), "camera")))
	{
		curemit->origin0[0] = curemit->origin[0] = r_origin[0];
		curemit->origin0[1] = curemit->origin[1] = r_origin[1];
		curemit->origin0[2] = curemit->origin[2] = r_origin[2];
	}
	else
	{
		curemit->origin0[0] = curemit->origin[0] = atof(Cmd_Argv(1));
		curemit->origin0[1] = curemit->origin[1] = atof(Cmd_Argv(2));
		curemit->origin0[2] = curemit->origin[2] = atof(Cmd_Argv(3));
	}

// После изменения параметров требуется пересчитать некоторые параметры!
	curemit->area = CM_LeafArea (CM_PointLeafnum (curemit->origin));
	Com_Printf("Emitter's origin changed.\n");
	if (!curemit->area)
		Com_Printf("^3Emitter is out of BSP!\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_OriginModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if(( Cmd_Argc() != 2 ) && ( Cmd_Argc() != 4 ))
	{
		Com_Printf("^3Usage: originmodel <x y z | camera>\n");
		return;
	}

	if (!curmodel)
		return;

	if (( Cmd_Argc() == 2 ) && (!Q_strcasecmp(Cmd_Argv(1), "camera")))
	{
		curmodel->origin[0] = r_origin[0];
		curmodel->origin[1] = r_origin[1];
		curmodel->origin[2] = r_origin[2];
	}
	else
	{
		curmodel->origin[0] = atof(Cmd_Argv(1));
		curmodel->origin[1] = atof(Cmd_Argv(2));
		curmodel->origin[2] = atof(Cmd_Argv(3));
	}

	if (!R_MarkAliasLeaves(curmodel))
		Com_Printf("^3Warning:^7 model out of BSP\n");
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_Origin_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if(( Cmd_Argc() != 2 ) && ( Cmd_Argc() != 4 ))
	{
		Com_Printf("^3Usage: origin <x y z | camera>\n");
		return;
	}

	if (le_mode)
	{
		if (!curlink)
			return;

		if (( Cmd_Argc() == 2 ) && (!Q_strcasecmp(Cmd_Argv(1), "camera")))
			VectorCopy(r_origin, curlink->origin);
		else
		{
			curlink->origin[0] = atof(Cmd_Argv(1));
			curlink->origin[1] = atof(Cmd_Argv(2));
			curlink->origin[2] = atof(Cmd_Argv(3));
		}

		Com_Printf("^3WARNING:^7 \"origin\" not recommended for linked lights!\n");
		Com_Printf("Origin changed.\n");
	}
	else
	{
		if (!curlight)
			return;

		if (( Cmd_Argc() == 2 ) && (!Q_strcasecmp(Cmd_Argv(1), "camera")))
			VectorCopy(r_origin, curlight->origin);
		else
		{
			curlight->origin[0] = atof(Cmd_Argv(1));
			curlight->origin[1] = atof(Cmd_Argv(2));
			curlight->origin[2] = atof(Cmd_Argv(3));
		}

// После изменения параметров требуется пересчитать некоторые параметры!
		currentshadowlight = curlight;
		if (ComputeBSPLight())
			Com_Printf("Origin changed.\n");
		else
			Com_Printf("^3Static light rejected.\n");
	}
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_Color_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 4 )
	{
		Com_Printf("^3Usage: color <r g b>\n");
		return;
	}

	if (le_mode)
	{
		if (!curlink)
			return;

		curlink->color[0] = atof(Cmd_Argv(1));
		curlink->color[1] = atof(Cmd_Argv(2));
		curlink->color[2] = atof(Cmd_Argv(3));

		if(curlink->color[0]<0 || curlink->color[0]>1)	curlink->color[0]=1;
		if(curlink->color[1]<0 || curlink->color[1]>1)	curlink->color[1]=1;
		if(curlink->color[2]<0 || curlink->color[2]>1)	curlink->color[2]=1;
	}
	else
	{
		if (!curlight)
			return;

		curlight->color[0] = atof(Cmd_Argv(1));
		curlight->color[1] = atof(Cmd_Argv(2));
		curlight->color[2] = atof(Cmd_Argv(3));

		if(curlight->color[0]<0 || curlight->color[0]>1)	curlight->color[0]=1;
		if(curlight->color[1]<0 || curlight->color[1]>1)	curlight->color[1]=1;
		if(curlight->color[2]<0 || curlight->color[2]>1)	curlight->color[2]=1;
	}

	Com_Printf("Color changed.\n");
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_Color2_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (!curlink)
			return;

		VectorSet(curlink->color, 1,1,1);
		GetSurfaceColor(&curlink->color[0], &curlink->color[1], &curlink->color[2], NULL);

		if(curlink->color[0]<0 || curlink->color[0]>1)	curlink->color[0]=1;
		if(curlink->color[1]<0 || curlink->color[1]>1)	curlink->color[1]=1;
		if(curlink->color[2]<0 || curlink->color[2]>1)	curlink->color[2]=1;
	}
	else
	{
		if (!curlight)
			return;

		VectorSet(curlight->color, 1,1,1);
		GetSurfaceColor(&curlight->color[0], &curlight->color[1], &curlight->color[2], NULL);

		if(curlight->color[0]<0 || curlight->color[0]>1)	curlight->color[0]=1;
		if(curlight->color[1]<0 || curlight->color[1]>1)	curlight->color[1]=1;
		if(curlight->color[2]<0 || curlight->color[2]>1)	curlight->color[2]=1;
	}

	Com_Printf("Color changed.\n");
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_Angles_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 4 )
	{
		Com_Printf("^3Usage: angles <a b c>\n");
		return;
	}

	if (le_mode)
	{
		if (!curlink)
			return;

		curlink->angles[0] = atof(Cmd_Argv(1));
		curlink->angles[1] = atof(Cmd_Argv(2));
		curlink->angles[2] = atof(Cmd_Argv(3));

		Com_Printf("Angles changed.\n");
	}
	else
	{
		if (!curlight)
			return;

		curlight->angles[0] = atof(Cmd_Argv(1));
		curlight->angles[1] = atof(Cmd_Argv(2));
		curlight->angles[2] = atof(Cmd_Argv(3));

// После изменения углов требуется пересчитать некоторые параметры!
		currentshadowlight = curlight;
		VectorSet(currentshadowlight->frust[0].normal, 0,0,0);	// форсируем пересчет frust
		if (ComputeBSPLight())
			Com_Printf("Angles changed.\n");
		else
			Com_Printf("^3Static light rejected.\n");
	}
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_AnglesEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 3 )
	{
		Com_Printf("^3Usage: anglesemit <a b>\n");
		return;
	}

	if (!curemit)
		return;

	curemit->angles[0] = atof(Cmd_Argv(1));
	curemit->angles[1] = atof(Cmd_Argv(2));
// После изменения углов требуется пересчитать некоторые параметры!
	vec3_t	ang;
	VectorSet(ang, curemit->angles[0], curemit->angles[1], 0);
	AngleVectors(ang, curemit->dir, NULL, NULL);
	VectorCopy(curemit->dir, curemit->dir0);
	Com_Printf("Emitter's angles changed.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_VelEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: velemit <velocity>\n");
		return;
	}

	if (!curemit)
		return;

	curemit->vel = atof(Cmd_Argv(1));
	Com_Printf("Emitter's velocity changed.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_RSpeed_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (!curlink)
			return;

		Com_Printf("^3Linked lights can't auto-rotate.\n");
	}
	else
	{
		if (!curlight)
			return;

		if( Cmd_Argc() != 4 )
		{
			Com_Printf("^3Usage: speed <a b c>\n");
			return;
		}

		curlight->rspeed[0] = atof(Cmd_Argv(1));
		curlight->rspeed[1] = atof(Cmd_Argv(2));
		curlight->rspeed[2] = atof(Cmd_Argv(3));

		Com_Printf("Rotation speed changed.\n");
		Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
	}
}


void CL_RSpeedEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

	if( Cmd_Argc() != 3 )
	{
		Com_Printf("^3Usage: speedemit <a b>\n");
		return;
	}

	curemit->rspeed[0] = atof(Cmd_Argv(1));
	curemit->rspeed[1] = atof(Cmd_Argv(2));

	Com_Printf("Emitter's rotation speed changed.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_NumEmits_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curemit)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: numemits <num>\n");
		return;
	}

	float num = atof(Cmd_Argv(1));
	if (int(num)>0)
	{
		curemit->rspeed[2] = num;
		Com_Printf("Number of emitter particles changed.\n");
		Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
	}
}


void CL_Cone_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: cone <n>\n");
		return;
	}

	if (le_mode)
	{
		if (!curlink)
			return;

		curlink->_cone = atof(Cmd_Argv(1));

		Com_Printf("Cone changed.\n");
	}
	else
	{
		if (!curlight)
			return;

		curlight->_cone = atof(Cmd_Argv(1));

// После изменения cone требуется пересчитать некоторые параметры!
		currentshadowlight = curlight;
		VectorSet(currentshadowlight->frust[0].normal, 0,0,0);	// форсируем пересчет frust
		if (ComputeBSPLight())
			Com_Printf("Cone changed.\n");
		else
			Com_Printf("^3Static light rejected.\n");
	}
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_ConeEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: coneemit <n>\n");
		return;
	}

	if (!curemit)
		return;

	curemit->cone = atof(Cmd_Argv(1));
	Com_Printf("Emitter's cone changed.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_Style_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: style <n>\n");
		return;
	}

	if (le_mode)
	{
		if (!curlink)
			return;

		curlink->style = atoi(Cmd_Argv(1));

///		if(curlink->style<0 || curlink->style>=MAX_LIGHTSTYLES)	curlink->style=0;	/// is byte
	}
	else
	{
		if (!curlight)
			return;

		curlight->style = atoi(Cmd_Argv(1));

///		if(curlight->style<0 || curlight->style>=MAX_LIGHTSTYLES)	curlight->style=0;	/// is byte
	}

	Com_Printf("Style changed.\n");
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_CL_Style_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: cl_style <n>\n");
		return;
	}

	if (le_mode)
	{
		if (!curlink)
			return;

		curlink->cl_style = atoi(Cmd_Argv(1));

///		if(curlink->cl_style<0 || curlink->cl_style>=MAX_LIGHTSTYLES)	curlink->cl_style=0;	/// is byte
	}
	else
	{
		if (!curlight)
			return;

		curlight->cl_style = atoi(Cmd_Argv(1));

///		if(curlight->cl_style<0 || curlight->cl_style>=MAX_LIGHTSTYLES)	curlight->cl_style=0;	/// is byte
	}

	Com_Printf("cl_style changed.\n");
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_StyleDecal_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: styledecal <n>\n");
		return;
	}

	if (!curdecal)
		return;

	curdecal->style = atoi(Cmd_Argv(1));

///	if(curdecal->style<0 || curdecal->style>=MAX_LIGHTSTYLES)	curdecal->style=0;	/// is byte

	for (decal_t *p=active_decals ; p ; p=p->next)
		if (p->defDecal == curdecal)
			p->style = curdecal->style;

	if (curbrush)
		VectorCopy(curbrush_offset, curdecal->offset);
	else
		VectorClear(curdecal->offset);
	Com_Printf("Decal's style changed.\n");
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_CL_StyleDecal_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: cl_styledecal <n>\n");
		return;
	}

	if (!curdecal)
		return;

	curdecal->cl_style = atoi(Cmd_Argv(1));

///	if(curdecal->cl_style<0 || curdecal->cl_style>=MAX_LIGHTSTYLES)	curdecal->cl_style=0;	/// is byte

	for (decal_t *p=active_decals ; p ; p=p->next)
		if (p->defDecal == curdecal)
			p->cl_style = curdecal->cl_style;

	if (curbrush)
		VectorCopy(curbrush_offset, curdecal->offset);
	else
		VectorClear(curdecal->offset);
	Com_Printf("Decal's cl_style changed.\n");
	Cl_UpdateEditorCvars(ED_DECAL | ED_MODIFIED);
}


void CL_StyleEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: styleemit <n>\n");
		return;
	}

	if (!curemit)
		return;

	curemit->style = atoi(Cmd_Argv(1));

///	if(curemit->style<0 || curemit->style>=MAX_LIGHTSTYLES)	curemit->style=0;	/// is byte

	Com_Printf("Emitter's style changed.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_CL_StyleEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: cl_styleemit <n>\n");
		return;
	}

	if (!curemit)
		return;

	curemit->cl_style = atoi(Cmd_Argv(1));

///	if(curemit->cl_style<0 || curemit->cl_style>=MAX_LIGHTSTYLES)	curemit->cl_style=0;	/// is byte

	Com_Printf("Emitter's cl_style changed.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_FrameRate_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	int num = Cmd_Argc();
	if( num != 2)
	{
		Com_Printf("^3Usage: framerate <n>\n");
		return;
	}

	if (le_mode)
	{
		if (!curlink)
			return;
		curlink->framerate = atof(Cmd_Argv(1));
		if (curlink->framerate == 0)
			curlink->framerate = 2;
	}
	else
	{
		if (!curlight)
			return;
		curlight->framerate = atof(Cmd_Argv(1));
		if (curlight->framerate == 0)
			curlight->framerate = 2;
	}

	Com_Printf("Framerate changed.\n");
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_Filter_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	int num = Cmd_Argc();
	if( num != 2 && num != 3 )
	{
		Com_Printf("^3Usage: filter <n> | <n_start n_end>\n");
		return;
	}

	if (le_mode)
	{
		if (!curlink)
			return;

		if (num==2)
		{
			curlink->filtercube_start = atoi(Cmd_Argv(1));
			if(curlink->filtercube_start<0 || curlink->filtercube_start>MAX_FILTERS)
				curlink->filtercube_start=0;
			curlink->filtercube_end = curlink->filtercube_start;
		}
		else
		{
			curlink->filtercube_start = atoi(Cmd_Argv(1));
			curlink->filtercube_end = atoi(Cmd_Argv(2));
			if(curlink->filtercube_start<0 || curlink->filtercube_start>MAX_FILTERS)
				curlink->filtercube_start=0;
			if(curlink->filtercube_end<0 || curlink->filtercube_end>MAX_FILTERS)
				curlink->filtercube_end=0;
			if (curlink->filtercube_start > curlink->filtercube_end)
			{
				int tempo = curlink->filtercube_start;
				curlink->filtercube_start = curlink->filtercube_end;
				curlink->filtercube_end = tempo;
			}
			if (!curlink->filtercube_start && curlink->filtercube_end)
				curlink->filtercube_start = curlink->filtercube_end;
		}
	}
	else
	{
		if (!curlight)
			return;

		if (num==2)
		{
			curlight->filtercube_start = atoi(Cmd_Argv(1));
			if(curlight->filtercube_start<0 || curlight->filtercube_start>MAX_FILTERS)
				curlight->filtercube_start=0;
			curlight->filtercube_end = curlight->filtercube_start;
		}
		else
		{
			curlight->filtercube_start = atoi(Cmd_Argv(1));
			curlight->filtercube_end = atoi(Cmd_Argv(2));
			if(curlight->filtercube_start<0 || curlight->filtercube_start>MAX_FILTERS)
				curlight->filtercube_start=0;
			if(curlight->filtercube_end<0 || curlight->filtercube_end>MAX_FILTERS)
				curlight->filtercube_end=0;
			if (!curlight->filtercube_start && curlight->filtercube_end)
				curlight->filtercube_start = curlight->filtercube_end;
			if (curlight->filtercube_start && !curlight->filtercube_end)
				curlight->filtercube_end = curlight->filtercube_start;
		}
	}

	Com_Printf("Filter changed.\n");
	Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
}


void CL_FilterEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: typeemit <n>\n");
		return;
	}

	if (!curemit)
		return;

	curemit->emit = (parttype_t)atoi(Cmd_Argv(1));

	if(curemit->emit<part_simple || curemit->emit>=part_end_of_list_for_emitters)
		curemit->emit=part_simple;

	Com_Printf("Emitter's type changed.\n");
	Cl_UpdateEditorCvars(ED_EMIT | ED_MODIFIED);
}


void CL_Sun_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		if (sun_size==0)
		{
			Com_Printf("^3Sun not spawned\n");
			return;
		} else if (sun_size==0.10)
		{
			Com_Printf("Small sun\n");
			return;
		} else if (sun_size==0.20)
		{
			Com_Printf("Middle sun\n");
			return;
		} else
		{
			Com_Printf("Big sun\n");
			return;
		}
	}

	int type = atoi(Cmd_Argv(1));
	if (type==0)
	{
		sun_size = 0;
		Com_Printf("Sun removed\n");
		Cl_UpdateEditorCvars(ED_MODIFIED);
		return;
	} else if (type==1)
	{
		sun_size = 0.10;
		VectorCopy(r_origin, sun_origin);
		Com_Printf("Small sun spawned\n");
		Cl_UpdateEditorCvars(ED_MODIFIED);
		return;
	} else if (type==2)
	{
		sun_size = 0.20;
		VectorCopy(r_origin, sun_origin);
		Com_Printf("Middle sun spawned\n");
		Cl_UpdateEditorCvars(ED_MODIFIED);
		return;
	} else if (type==3)
	{
		sun_size = 0.30;
		VectorCopy(r_origin, sun_origin);
		Com_Printf("Big sun spawned\n");
		Cl_UpdateEditorCvars(ED_MODIFIED);
		return;
	} else 
		Com_Printf("^3Usage: sun <0-remove;1;2;3>\n");
}


void CL_SunPos_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 4 )
	{
		Com_Printf("%i %i %i\n", (int)sun_origin[0], (int)sun_origin[1], (int)sun_origin[2]);
		return;
	}

	sun_origin[0] = atof(Cmd_Argv(1));
	sun_origin[1] = atof(Cmd_Argv(2));
	sun_origin[2] = atof(Cmd_Argv(3));
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Start_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (!curlink)
			return;

		Com_Printf("^3Linked lights can't be stopped.\n");
	}
	else
	{
		if (!curlight)
			return;

		curlight->start_off = !curlight->start_off;

		Com_Printf("Start changed.\n");
		Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);

		if ((curlight->style < 32) && curlight->start_off)
			Com_Printf("^3Warning:^7 stopped with style < 32\n");
	}
}


void CL_NoBump_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (!curlink)
			return;

		Com_Printf("^3Linked lights can't have \"nobump\"\n");	/// fixme: временное ограничение... лениво пока.
	}
	else
	{
		if (!curlight)
			return;

		curlight->nobump = !curlight->nobump;
		Com_Printf("\"nobump\" changed.\n");
		Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
	}
}


void CL_NoShadow_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (!curlink)
			return;

		Com_Printf("^3Linked lights can't have \"noshadow\"\n");	/// fixme: временное ограничение... лениво пока.
	}
	else
	{
		if (!curlight)
			return;

		curlight->noshadow = !curlight->noshadow;

// После изменения параметров требуется пересчитать некоторые параметры!
		currentshadowlight = curlight;
		if (ComputeBSPLight())
			Com_Printf("\"noshadow\" changed.\n");
		else
			Com_Printf("^3Static light rejected.\n");
		Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
	}
}


void CL_NoShadow2_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (!curlink)
			return;

		Com_Printf("^3Linked lights can't have \"noshadow2\"\n");	/// fixme: временное ограничение... лениво пока.
	}
	else
	{
		if (!curlight)
			return;

		curlight->noshadow2 = !curlight->noshadow2;
		Com_Printf("\"noshadow2\" changed.\n");
		Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
	}
}


void CL_LightStyle_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 3 )
	{
		Com_Printf("^3Usage: lightstyle <num> <string>\n");
		return;
	}

	int num = atoi(Cmd_Argv(1));
	if (num<=0 || num>=MAX_LIGHTSTYLES_OVERRIDE)
	{
		Com_Printf("^3<num> must be in [1 ... %i]\n", MAX_LIGHTSTYLES_OVERRIDE-1);
		return;
	}

	CheckLightStyle(Cmd_Argv(2));

	Memset(lightstyles[num], 0, MAX_QPATH);
	Memcpy(lightstyles[num], Cmd_Argv(2), MAX_QPATH-1);

	if (net_compatibility->value)
	{
		Memset(cl.configstrings[num+CS_LIGHTS_Q2], 0, MAX_QPATH);
		Memcpy(cl.configstrings[num+CS_LIGHTS_Q2], Cmd_Argv(2), MAX_QPATH-1);
	}
	else
	{
		Memset(cl.configstrings[num+CS_LIGHTS_BERS], 0, MAX_QPATH);
		Memcpy(cl.configstrings[num+CS_LIGHTS_BERS], Cmd_Argv(2), MAX_QPATH-1);
	}
	CL_SetLightstyle(num);
	Com_Printf("LightStyle %i overrided with \"%s\"\n", num, lightstyles[num]);
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_CL_LightStyle_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 3 )
	{
		Com_Printf("^3Usage: cl_lightstyle <num> <string>\n");
		return;
	}

	int num = atoi(Cmd_Argv(1));
	if (num<=0 || num>=MAX_LIGHTSTYLES)
	{
		Com_Printf("^3<num> must be in [1 ... %i]\n", MAX_LIGHTSTYLES-1);
		return;
	}

	CheckLightStyle(Cmd_Argv(2));
	CL_CL_SetLightstyle(num, Cmd_Argv(2));
	Com_Printf("cl_lightstyle %i changed with \"%s\"\n", num, Cmd_Argv(2));
	Cl_UpdateEditorCvars(ED_MODIFIED);
}


void CL_Targetname_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (!curlink)
			return;

		Com_Printf("^3Linked lights can't be targeted.\n");
	}
	else
	{
		if (!curlight)
			return;

		if( Cmd_Argc() != 2 )
		{
			Com_Printf("^3Usage: targetname <string>\n");
			return;
		}

		Memcpy(curlight->targetname, Cmd_Argv(1), sizeof(curlight->targetname));
		Com_Printf("Targetname changed.\n");
		Cl_UpdateEditorCvars(ED_LIGHT | ED_MODIFIED);
	}
}


void CL_NoiseModel_f ()
{
	char	buffer[MAX_QPATH+4];

	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: noisemodel <string>\n");
		return;
	}

	Memcpy(curmodel->sound, Cmd_Argv(1), MAX_QPATH);
	if (curmodel->sound[0])
	{
		if (!strstr (curmodel->sound, ".wav"))
			Com_sprintf (buffer, sizeof(buffer), "%s.wav", curmodel->sound);
		else
			strncpy (buffer, curmodel->sound, sizeof(buffer));
		curmodel->noise = S_RegisterSound (buffer);
		Com_Printf("Noise changed.\n");
	}
	else
	{
		curmodel->noise = NULL;
		Com_Printf("Noise cleared.\n");
	}
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}

void CL_SkinModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: skinmodel <n>\n");
		return;
	}

	curmodel->skinnum = (atoi(Cmd_Argv(1)) & (MAX_MD2SKINS-1));
	Com_Printf("Skin changed.\n");
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_NextSkinModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	curmodel->skinnum = (curmodel->skinnum + 1) & (MAX_MD2SKINS - 1);
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_PrevSkinModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	curmodel->skinnum = (curmodel->skinnum - 1) & (MAX_MD2SKINS - 1);
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_FrameModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: framemodel <n>\n");
		return;
	}

	if (curmodel->mdl->numframes <= 1)
	{
		curmodel->frame = 0;
		Com_Printf("^3Model have one frame only.\n");
	}
	else
	{
		curmodel->frame = atoi(Cmd_Argv(1));
		if (curmodel->frame < 0)
			curmodel->frame = 0;
		if (curmodel->frame >= curmodel->mdl->numframes)
			curmodel->frame = curmodel->mdl->numframes - 1;
		if (curmodel->frame_end < curmodel->frame)
			curmodel->frame_end = curmodel->frame;
		Com_Printf("Frame changed.\n");
	}
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_FrameEndModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: frameendmodel <n>\n");
		return;
	}

	curmodel->frame_end = atoi(Cmd_Argv(1));

	if (curmodel->mdl->numframes <= 1)
	{
		curmodel->frame_end = curmodel->frame = 0;
		Com_Printf("^3Model have one frame only.\n");
	}
	else
	{
		if (curmodel->frame < 0)
			curmodel->frame = 0;
		if (curmodel->frame >= curmodel->mdl->numframes)
			curmodel->frame = curmodel->mdl->numframes - 1;
		if (curmodel->frame_end < curmodel->frame)
			curmodel->frame_end = curmodel->frame;
		Com_Printf("Frame changed.\n");
	}
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_NextFrameModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if (curmodel->mdl->numframes <= 1)
		curmodel->frame = 0;
	else
	{
		curmodel->frame++;
		if (curmodel->frame >= curmodel->mdl->numframes)
			curmodel->frame = curmodel->mdl->numframes - 1;
		if (curmodel->frame_end < curmodel->frame)
			curmodel->frame_end = curmodel->frame;
	}
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_PrevFrameModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if (curmodel->mdl->numframes <= 1)
		curmodel->frame = 0;
	else
	{
		curmodel->frame--;
		if (curmodel->frame < 0)
			curmodel->frame = 0;
	}
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_AnimFrameModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	curmodel->animframe = !curmodel->animframe;
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_Next_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (lightmodel_counter==0)
		{
			Com_Printf("^3No linked lights exist!\n");
			return;
		}

		if ((curlink == NULL) || (curlink == &lmdl_list[lightmodel_counter-1]))
			curlink = &lmdl_list[0];
		else
			curlink++;
	}
	else
	{
		if (numStaticShadowLights==0)
		{
			Com_Printf("^3No static lights exist!\n");
			return;
		}

		if ((curlight == NULL) || (curlight == &shadowlights[numStaticShadowLights-1]))
			curlight = &shadowlights[0];
		else
			curlight++;
	}
	Cl_UpdateEditorCvars(ED_LIGHT);
}


void CL_Prev_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (le_mode)
	{
		if (lightmodel_counter==0)
		{
			Com_Printf("^3No linked lights exist!\n");
			return;
		}

		if ((curlink == NULL) || (curlink == &lmdl_list[0]))
			curlink = &lmdl_list[lightmodel_counter-1];
		else
			curlink--;
	}
	else
	{
		if (numStaticShadowLights==0)
		{
			Com_Printf("^3No static lights exist!\n");
			return;
		}

		if ((curlight == NULL) || (curlight == &shadowlights[0]))
			curlight = &shadowlights[numStaticShadowLights-1];
		else
			curlight--;
	}
	Cl_UpdateEditorCvars(ED_LIGHT);
}


void CL_NextEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (numEmits==0)
	{
		Com_Printf("^3No emitters exist!\n");
		return;
	}

	if ((curemit == NULL) || (curemit == &emits[numEmits-1]))
		curemit = &emits[0];
	else
		curemit++;
	Cl_UpdateEditorCvars(ED_EMIT);
}


void CL_NextModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (aliasmodel_counter==0)
	{
		Com_Printf("^3No models exist!\n");
		return;
	}

	if ((curmodel == NULL) || (curmodel == &amdl_list[aliasmodel_counter-1]))
		curmodel = &amdl_list[0];
	else
		curmodel++;
	Cl_UpdateEditorCvars(ED_MODEL);
}


void CL_PrevEmit_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (numEmits==0)
	{
		Com_Printf("^3No emitters exist!\n");
		return;
	}

	if ((curemit == NULL) || (curemit == &emits[0]))
		curemit = &emits[numEmits-1];
	else
		curemit--;
	Cl_UpdateEditorCvars(ED_EMIT);
}


void CL_PrevModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if (aliasmodel_counter==0)
	{
		Com_Printf("^3No models exist!\n");
		return;
	}

	if ((curmodel == NULL) || (curmodel == &amdl_list[0]))
		curmodel = &amdl_list[aliasmodel_counter-1];
	else
		curmodel--;
	Cl_UpdateEditorCvars(ED_MODEL);
}


void M_SelectMath(int it, int size, bool def, bool silent)
{
	int		i, start, min;
	int		result[4];		// до 4 версий
	float	a;
	void	*buf0;
	void	*buf1;

	if (def)
	{	// ставим заведомо работающие версии подпрограмм (на любых CPU)
		Sqrt = sqrtf;
		Memcpy = MS_memcpy;
		Memset = MS_memset;
		return;
	}

	if (size>0)
	{
		buf0 = malloc(size);
		buf1 = malloc(size);
	}

	void (*Printf)(char *fmt, ...);
	if (silent)
		Printf = Com_DPrintf;
	else
		Printf = Com_Printf;

// sqrt
	start = Sys_Milliseconds ();
	for(i=1; i<1000000*it; i++)
		a = sqrtf(i);
	a *= 0.00000001;
	result[int(a)] = Sys_Milliseconds()-start;
	Printf("sqrt %i ms\n", result[0]);

	start = Sys_Milliseconds ();
	for(i=1; i<1000000*it; i++)
		a = fastsqrt(i);
	a *= 0.00000001;
	result[int(a)+1] = Sys_Milliseconds()-start;
	Printf("fastsqrt %i ms\n", result[1]);

	min = min(result[0], result[1]);
	if (min == result[0])
	{
		Printf("    Selected sqrt\n");
		Sqrt = sqrtf;
	}
	else
	{
		Printf("    Selected fastsqrt\n");
		Sqrt = fastsqrt;
	}

// memcpy
	start = Sys_Milliseconds ();
	for (i=0; i<it * 100; i++)
		MS_memcpy(buf0, buf1, size);
	result[0] = Sys_Milliseconds()-start;
	Printf("memcpy %i ms\n", result[0]);

	start = Sys_Milliseconds ();
	for (i=0; i<it * 100; i++)
		Memcpy(buf0, buf1, size);
	result[1] = Sys_Milliseconds()-start;
	Printf("Com_Memcpy %i ms\n", result[1]);

	if (cpu_mmx)
	{
		start = Sys_Milliseconds ();
		for (i=0; i<it * 100; i++)
			MMX_Memcpy(buf0, buf1, size);
		result[2] = Sys_Milliseconds()-start;
		Printf("MMX_Memcpy %i ms\n", result[2]);
	}
	else
	{
		result[2] = 0x7fffffff;
		Printf("MMX_Memcpy ignored\n");
	}

	if (cpu_3dnow)
	{
		start = Sys_Milliseconds ();
		for (i=0; i<it * 100; i++)
			AMD_Memcpy(buf0, buf1, size);
		result[3] = Sys_Milliseconds()-start;
		Printf("AMD_Memcpy %i ms\n", result[3]);
	}
	else
	{
		result[3] = 0x7fffffff;
		Printf("AMD_Memcpy ignored\n");
	}

	min = min(result[0], min(result[1], min(result[2], result[3])));
	if (min == result[0])
	{
		Printf("    Selected memcpy\n");
		Memcpy = MS_memcpy;
	}
	else if (min == result[1])
	{
		Printf("    Selected Com_Memcpy\n");
		Memcpy = Com_Memcpy;
	}
	else if (min == result[2])
	{
		Printf("    Selected MMX_Memcpy\n");
		Memcpy = MMX_Memcpy;
	}
	else
	{
		Printf("    Selected AMD_Memcpy\n");
		Memcpy = AMD_Memcpy;
	}

// memset
	start = Sys_Milliseconds ();
	for (i=0; i<it * 100; i++)
		MS_memset(buf0, 0, size);
	result[0] = Sys_Milliseconds()-start;
	Printf("memset %i ms\n", result[0]);

	start = Sys_Milliseconds ();
	for (i=0; i<it * 100; i++)
		Memset(buf0, 0, size);
	result[1] = Sys_Milliseconds()-start;
	Printf("Com_Memset %i ms\n", result[1]);

	if (cpu_mmx)
	{
		start = Sys_Milliseconds ();
		for (i=0; i<it * 100; i++)
			MMX_Memset(buf0, 0, size);
		result[2] = Sys_Milliseconds()-start;
		Printf("MMX_Memset %i ms\n", result[2]);
	}
	else
	{
		result[2] = 0x7fffffff;
		Printf("MMX_Memset ignored\n");
	}

	min = min(result[0], min(result[1], result[2]));
	if (min == result[0])
	{
		Printf("    Selected memset\n");
		Memset = MS_memset;
	}
	else if (min == result[1])
	{
		Printf("    Selected Com_Memset\n");
		Memset = Com_Memset;
	}
	else
	{
		Printf("    Selected MMX_Memset\n");
		Memset = MMX_Memset;
	}

	if (size>0)
	{
		free (buf0);
		free (buf1);
	}
}

void Com_MathTest_f ()
{
	M_SelectMath(128, 1024*1024, false, false);		// benchmark and set progs
}


void CL_CreateFog_f()
{
	if (!LightEditor_Enabled(false))
		return;

	curfog = AllocFog();
	if (!curfog)
	{
		Com_Printf("^3Too many fog areas\n");
		Cl_UpdateEditorCvars(ED_FOG);
		return;
	}

	VectorSet(curfog->color, 1,1,1);
	VectorSet(curfog->origin, r_origin[0]+1, r_origin[1]+1, r_origin[2]+1);
	VectorSet(curfog->origin2, r_origin[0]-1, r_origin[1]-1, r_origin[2]-1);
	curfog->density = 1.0/1024.0;
	curfog->ed = true;
	Com_Printf("Fog spawned\n");
	Cl_UpdateEditorCvars(ED_FOG | ED_MODIFIED);
}


void R_MarkFogSurfs ()
{
	int		i, c, j, k;
	float	minmax2[6];
	mleaf_t	*pleaf;

	for (i=0; i<numFogs; i++)
	{
		fog_infos[i].minmax[0]=fog_infos[i].minmax[1]=fog_infos[i].minmax[2]=999999;
		fog_infos[i].minmax[3]=fog_infos[i].minmax[4]=fog_infos[i].minmax[5]=-999999;

		// create bounding box for "fog" entity
		if(fog_infos[i].minmax[0]>fog_infos[i].origin[0])
			fog_infos[i].minmax[0]=fog_infos[i].origin[0];
		if(fog_infos[i].minmax[1]>fog_infos[i].origin[1])
			fog_infos[i].minmax[1]=fog_infos[i].origin[1];
		if(fog_infos[i].minmax[2]>fog_infos[i].origin[2])
			fog_infos[i].minmax[2]=fog_infos[i].origin[2];
		if(fog_infos[i].minmax[0]>fog_infos[i].origin2[0])
			fog_infos[i].minmax[0]=fog_infos[i].origin2[0];
		if(fog_infos[i].minmax[1]>fog_infos[i].origin2[1])
			fog_infos[i].minmax[1]=fog_infos[i].origin2[1];
		if(fog_infos[i].minmax[2]>fog_infos[i].origin2[2])
			fog_infos[i].minmax[2]=fog_infos[i].origin2[2];
		if(fog_infos[i].minmax[3]<fog_infos[i].origin[0])
			fog_infos[i].minmax[3]=fog_infos[i].origin[0];
		if(fog_infos[i].minmax[4]<fog_infos[i].origin[1])
			fog_infos[i].minmax[4]=fog_infos[i].origin[1];
		if(fog_infos[i].minmax[5]<fog_infos[i].origin[2])
			fog_infos[i].minmax[5]=fog_infos[i].origin[2];
		if(fog_infos[i].minmax[3]<fog_infos[i].origin2[0])
			fog_infos[i].minmax[3]=fog_infos[i].origin2[0];
		if(fog_infos[i].minmax[4]<fog_infos[i].origin2[1])
			fog_infos[i].minmax[4]=fog_infos[i].origin2[1];
		if(fog_infos[i].minmax[5]<fog_infos[i].origin2[2])
			fog_infos[i].minmax[5]=fog_infos[i].origin2[2];

		pleaf = r_worldmodel->leafs;
		for (j=0 ; j<r_worldmodel->numleafs ; j++, pleaf++)
		{
			if (fog_infos[i].ed)
				goto skipCont;
			if (pleaf->contents & CONTENTS_FOG)
			{
				// create properly bounding box for leaf
skipCont:		minmax2[0]=minmax2[1]=minmax2[2]=999999;
				minmax2[3]=minmax2[4]=minmax2[5]=-999999;

				if(minmax2[0]>pleaf->minmaxs[0])
					minmax2[0]=pleaf->minmaxs[0]-1;
				if(minmax2[1]>pleaf->minmaxs[1])
					minmax2[1]=pleaf->minmaxs[1]-1;
				if(minmax2[2]>pleaf->minmaxs[2])
					minmax2[2]=pleaf->minmaxs[2]-1;
				if(minmax2[0]>pleaf->minmaxs[3])
					minmax2[0]=pleaf->minmaxs[3]-1;
				if(minmax2[1]>pleaf->minmaxs[4])
					minmax2[1]=pleaf->minmaxs[4]-1;
				if(minmax2[2]>pleaf->minmaxs[5])
					minmax2[2]=pleaf->minmaxs[5]-1;
				if(minmax2[3]<pleaf->minmaxs[0])
					minmax2[3]=pleaf->minmaxs[0]+1;
				if(minmax2[4]<pleaf->minmaxs[1])
					minmax2[4]=pleaf->minmaxs[1]+1;
				if(minmax2[5]<pleaf->minmaxs[2])
					minmax2[5]=pleaf->minmaxs[2]+1;
				if(minmax2[3]<pleaf->minmaxs[3])
					minmax2[3]=pleaf->minmaxs[3]+1;
				if(minmax2[4]<pleaf->minmaxs[4])
					minmax2[4]=pleaf->minmaxs[4]+1;
				if(minmax2[5]<pleaf->minmaxs[5])
					minmax2[5]=pleaf->minmaxs[5]+1;

				// Если pleaf->minmax целиком внутри minmax, определенном парой "fog":
				if (BBoxIntersectBBox(fog_infos[i].minmax, minmax2))
				{
					c = pleaf->nummarksurfaces;
					for (k=0 ; k<c ; k++)
					{
						if (!fog_infos[i].ed)
							goto mark;
						if (!(pleaf->firstmarksurface[k]->texinfo->flags & (SURF_TRANS33|SURF_TRANS66|SURF_NODRAW)))
						{
mark:						pleaf->firstmarksurface[k]->flags |= SURF_DRAWFOG;
							pleaf->firstmarksurface[k]->fog = &fog_infos[i];
						}
					}
				}
			}
		}
	}
}


void R_ReMarkFogSurfs()
{
	int		c, j, k;
	mleaf_t	*pleaf;

	if (curfog)
		if (curfog->origin[2] < curfog->origin2[2])
		{
			float temp;
			temp = curfog->origin[2];
			curfog->origin[2] = curfog->origin2[2];
			curfog->origin2[2] = temp;
		}

	for (j=0, pleaf = r_worldmodel->leafs ; j<r_worldmodel->numleafs ; j++, pleaf++)
	{
		c = pleaf->nummarksurfaces;
		for (k=0 ; k<c ; k++)
		{
			pleaf->firstmarksurface[k]->flags &= ~SURF_DRAWFOG;
			pleaf->firstmarksurface[k]->fog = NULL;
		}
	}

	R_MarkFogSurfs ();
}


void CL_DeleteFog_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curfog)
		return;

	fog_t	*fog;

	for(fog=curfog; fog<&fog_infos[numFogs-1]; fog++)
		Memcpy(fog, fog+1, sizeof(fog_t));

	Memset(&fog_infos[numFogs-1], 0, sizeof(fog_t));
	numFogs--;

	if(curfog == &fog_infos[numFogs])
	{
		if(numFogs)
			curfog--;
		else
			curfog = NULL;
	}

	// Пересчитаем туманы
	R_ReMarkFogSurfs ();

	Com_Printf("Fog area deleted.\n");
	Cl_UpdateEditorCvars(ED_FOG | ED_MODIFIED);
}


void CL_OriginFog0_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curfog)
		return;

	VectorSet(curfog->origin, r_origin[0]+1, r_origin[1]+1, r_origin[2]+1);

	// Пересчитаем туманы
	R_ReMarkFogSurfs ();
	Cl_UpdateEditorCvars(ED_FOG | ED_MODIFIED);
}


void CL_OriginFog1_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curfog)
		return;

	VectorSet(curfog->origin2, r_origin[0]-1, r_origin[1]-1, r_origin[2]-1);

	// Пересчитаем туманы
	R_ReMarkFogSurfs ();
	Cl_UpdateEditorCvars(ED_FOG | ED_MODIFIED);
}


void CL_FogDensity_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curfog)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: fog_density <d>\n");
		return;
	}

	float den = atof(Cmd_Argv(1));
	if (den<1)
		den = 1;
	curfog->density = 1.0/den;
	Com_Printf("Fog density changed.\n");
	Cl_UpdateEditorCvars(ED_FOG | ED_MODIFIED);
}


void CL_UnSelectFog_f()
{
	if (!LightEditor_Enabled(false))
		return;

	curfog = NULL;
	Cl_UpdateEditorCvars(ED_FOG);
}


void CL_SelectFog_f()
{
	if (!LightEditor_Enabled(false))
		return;

	int	i;
	for (i=0; i<numFogs; i++)
	{
		if (r_origin[0]<fog_infos[i].minmax[0])
			continue;
		if (r_origin[1]<fog_infos[i].minmax[1])
			continue;
		if (r_origin[2]<fog_infos[i].minmax[2])
			continue;
		if (r_origin[0]>fog_infos[i].minmax[3])
			continue;
		if (r_origin[1]>fog_infos[i].minmax[4])
			continue;
		if (r_origin[2]>fog_infos[i].minmax[5])
			continue;
		// fog area found!
		break;
	}

	if (i<numFogs)
	{
		curfog = &fog_infos[i];
		Com_Printf("Fog area selected\n");
		Cl_UpdateEditorCvars(ED_FOG);
	}
}


void CL_AddFogOrigin0_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curfog)
		return;

	if( Cmd_Argc() != 4 )
	{
		Com_Printf("^3Usage: add_fog_origin0 <dx dy dz>\n");
		return;
	}

	curfog->origin[0] += atof(Cmd_Argv(1));
	curfog->origin[1] += atof(Cmd_Argv(2));
	curfog->origin[2] += atof(Cmd_Argv(3));

	// Пересчитаем туманы
	R_ReMarkFogSurfs ();
	Cl_UpdateEditorCvars(ED_FOG | ED_MODIFIED);
}


void CL_AddFogOrigin1_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curfog)
		return;

	if( Cmd_Argc() != 4 )
	{
		Com_Printf("^3Usage: add_fog_origin1 <dx dy dz>\n");
		return;
	}

	curfog->origin2[0] += atof(Cmd_Argv(1));
	curfog->origin2[1] += atof(Cmd_Argv(2));
	curfog->origin2[2] += atof(Cmd_Argv(3));

	// Пересчитаем туманы
	R_ReMarkFogSurfs ();
	Cl_UpdateEditorCvars(ED_FOG | ED_MODIFIED);
}


void CL_FogColor_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!curfog)
		return;

	if( Cmd_Argc() != 4 )
	{
		Com_Printf("^3Usage: fog_color <r g b>\n");
		return;
	}

	curfog->color[0] = atof(Cmd_Argv(1));
	curfog->color[1] = atof(Cmd_Argv(2));
	curfog->color[2] = atof(Cmd_Argv(3));

	if(curfog->color[0]<0 || curfog->color[0]>1)	curfog->color[0]=1;
	if(curfog->color[1]<0 || curfog->color[1]>1)	curfog->color[1]=1;
	if(curfog->color[2]<0 || curfog->color[2]>1)	curfog->color[2]=1;

	Com_Printf("Fog color changed.\n");
	Cl_UpdateEditorCvars(ED_FOG | ED_MODIFIED);
}


void CL_PickModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	float	dist, best_dist = 99999999;
	alink_t	*p;
	vec3_t	mins, maxs;
	int		i, j;
	vec3_t	bbox[8];
	cplane_t	frust[5];

// set slim frustum (FOV = 0 degrees)
	// rotate VPN right by FOV_X/2 degrees
	RotatePointAroundVector( frust[0].normal, vup, vpn, -90 );
	// rotate VPN left by FOV_X/2 degrees
	RotatePointAroundVector( frust[1].normal, vup, vpn, 90 );
	// rotate VPN up by FOV_X/2 degrees
	RotatePointAroundVector( frust[2].normal, vright, vpn, 90 );
	// rotate VPN down by FOV_X/2 degrees
	RotatePointAroundVector( frust[3].normal, vright, vpn, -90 );
	// near plane
	VectorCopy(vpn, frust[4].normal);
	for (i=0 ; i<5 ; i++)
	{
		frust[i].type = PLANE_ANYZ;
		frust[i].dist = DotProduct (r_origin, frust[i].normal);
		frust[i].signbits = SignbitsForPlane (&frust[i]);
	}

	curmodel = NULL;
	for (int am=0; am<aliasmodel_counter; am++)
	{
		p = &amdl_list[am];
		if (p->label[0]==0 || (p->label[0]==' ' && p->label[1]==0))
		{
			if (p->mdl->type == mod_alias)
			{
				dmdl_t		*paliashdr;
				daliasframe_t *pframe;

				paliashdr = (dmdl_t *)p->mdl->extradata;
				pframe = ( daliasframe_t * ) ( ( byte * ) paliashdr + paliashdr->ofs_frames + p->frame * paliashdr->framesize);

				/*
				** compute axially aligned mins and maxs
				*/
				for ( i = 0; i < 3; i++ )
				{
					mins[i] = pframe->translate[i];
					maxs[i] = mins[i] + pframe->scale[i]*255;
				}
			}
			else if (p->mdl->type == mod_alias_md3)
			{
				maliasmodel_t	*paliashdr;
				maliasframe_t	*pframe;

				paliashdr = (maliasmodel_t *)p->mdl->extradata;
				pframe = paliashdr->frames + p->frame;

				/*
				** compute axially aligned mins and maxs
				*/
				for (i = 0; i < 3; i++)
				{
					mins[i] = pframe->mins[i] + pframe->translate[i];
					maxs[i] = pframe->maxs[i] + pframe->translate[i];
				}
			}

			/*
			** compute a full bounding box
			*/
			for ( i = 0; i < 8; i++ )
			{
				vec3_t   tmp;

				if ( i & 1 )
					tmp[0] = mins[0];
				else
					tmp[0] = maxs[0];

				if ( i & 2 )
					tmp[1] = mins[1];
				else
					tmp[1] = maxs[1];

				if ( i & 4 )
					tmp[2] = mins[2];
				else
					tmp[2] = maxs[2];

				VectorCopy( tmp, bbox[i] );
			}

			/*
			** rotate the bounding box  (fixed by Berserker: correctly point rotate)
			*/
			if (p->angles[0] || p->angles[1] || p->angles[2])
			{
				for ( i = 0; i < 8; i++ )
				{
					PointRotate(p->angles, bbox[i]);
					VectorAdd( bbox[i], p->origin, bbox[i] );
				}
			}
			else
				for ( i = 0; i < 8; i++ )
					VectorAdd( bbox[i], p->origin, bbox[i] );

			mins[0] = mins[1] = mins[2] = 999999;
			maxs[0] = maxs[1] = maxs[2] = -999999;

			for(j=0; j<8; j++)
			{
				for(i=0; i<3; i++)
				{
					if(mins[i] > bbox[j][i])
						mins[i] = bbox[j][i];

					if(maxs[i] < bbox[j][i])
						maxs[i] = bbox[j][i];
				}
			}

			if (!R_CullBox5(mins, maxs, &frust[0]))
			{
				if (DotProduct(p->origin, frust[4].normal) - frust[4].dist > 0)
				{
					float	x, y, z;
					x = r_origin[0] - p->origin[0];
					y = r_origin[1] - p->origin[1];
					z = r_origin[2] - p->origin[2];
					dist = x*x+y*y+z*z;
					if(dist<best_dist)
					{
						best_dist=dist;
						curmodel=p;
					}
				}
			}
		}
	}

	if(!curmodel)
		Com_Printf("^3Model not found.\n");
	Cl_UpdateEditorCvars(ED_MODEL);
}


void CL_CopyModel_f ()
{
	if (!LightEditor_Enabled(false))
		return;

	if(!curmodel)
		return;

	Memcpy(&model_clipboard, curmodel, sizeof(alink_t));
	if (!silent_model)
		Com_Printf("Model copied to clipboard.\n");
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}

	
void CL_DeleteModel_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if(!curmodel)
		return;

	for (int am=0; am<aliasmodel_counter; am++)
		if (&amdl_list[am] == curmodel)
		{
			if (aliasmodel_counter-am-1)
				Memcpy(curmodel, &amdl_list[am+1], sizeof(alink_t)*(aliasmodel_counter-am-1));
			aliasmodel_counter--;
			Memset(&amdl_list[aliasmodel_counter], 0, sizeof(alink_t));
			if (!silent_model)
				Com_Printf("Model deleted.\n");
			if (aliasmodel_counter)
			{
				curmodel = &amdl_list[aliasmodel_counter-1];
				Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
				return;
			}
		}

	curmodel = NULL;
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_CutModel_f ()
{
	CL_CopyModel_f();
	CL_DeleteModel_f();
}


void CL_PasteModel_f ()
{
	alink_t	*m;

	if (!LightEditor_Enabled(false))
		return;

	if(!model_clipboard.model[0])	// клипбоард пока пуст
		return;

	m = AllocAlias();
	if (!m)
	{
		Com_Printf("^3No enought room for model.\n");
		Cl_UpdateEditorCvars(ED_MODEL);
		return;
	}

	Memcpy(m, &model_clipboard, sizeof(alink_t));

	curmodel = m;
	if (!Q_strcasecmp(Cmd_Argv(0), "paste2cameramodel"))
	{
		VectorCopy(r_origin, curmodel->origin);
		if (!R_MarkAliasLeaves(curmodel))
			Com_Printf("^3Warning:^7 model out of BSP\n");
	}
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_InvertModel_f()
{
	model_t	*mdl;
	bool	inv;
	float	scale;

	if (!LightEditor_Enabled(false))
		return;

	if(!curmodel)
		return;

	inv = curmodel->mdl->invert;
	scale = curmodel->mdl->scale;
	silent_model = true;
	CL_CopyModel_f();
	mdl = R_RegisterModel(model_clipboard.model, scale, !inv);
	if (!mdl)
		return;

	CL_DeleteModel_f();
	model_clipboard.mdl = mdl;
	CL_PasteModel_f();
	silent_model = false;
	if (!R_MarkAliasLeaves(curmodel))
		Com_Printf("^3Warning:^7 model out of BSP\n");
	Com_Printf("Model inverted.\n");
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_ScaleModel_f()
{
	model_t	*mdl;
	bool	inv;
	float	scale;

	if (!LightEditor_Enabled(false))
		return;

	if(!curmodel)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: scalemodel <scale>\n");
		return;
	}

	scale = atof(Cmd_Argv(1));
	if (scale <= 0.001)
	{
		Com_Printf("^3Bad scale\n");
		return;
	}

	inv = curmodel->mdl->invert;
	silent_model = true;
	CL_CopyModel_f();
	mdl = R_RegisterModel(model_clipboard.model, scale, inv);
	if (!mdl)
		return;

	CL_DeleteModel_f();
	model_clipboard.mdl = mdl;
	CL_PasteModel_f();
	silent_model = false;
	if (!R_MarkAliasLeaves(curmodel))
		Com_Printf("^3Warning:^7 model out of BSP\n");
	Com_Printf("Model scaled.\n");
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}

void CL_CreateModel_f()
{
	alink_t	*m;
	model_t	*mdl;

	if (!LightEditor_Enabled(false))
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("^3Usage: createmodel <filename>\n");
		return;
	}

	mdl = R_RegisterModel(Cmd_Argv(1), 1, false);
	if (!mdl)
		return;

	m = AllocAlias();
	if (!m)
	{
		Com_Printf("^3No enought room for model.\n");
		Cl_UpdateEditorCvars(ED_MODEL);
		return;
	}

	VectorCopy(cl.refdef.vieworg, m->origin);
	VectorCopy(cl.refdef.viewangles, m->angles);
	Memcpy(m->model, Cmd_Argv(1), MAX_QPATH);
	m->label[0] = ' ';	// чисто "пустой" линк, чтоб сервер игнорировал модель
	m->label[1] = 0;
	m->mdl = mdl;
	m->animframe = false;
	m->frame = m->frame_end = 0;
	m->skinnum = 0;
	m->sound[0] = 0;
	m->noise = NULL;
	curmodel = m;
	if (R_MarkAliasLeaves(m))
		Com_Printf("Model spawned.\n");
	else
		Com_Printf("^3Warning:^7 model out of BSP\n");
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_ChangeModel_f()
{
	model_t	*mdl;
	bool	inv;
	float	scale;

	if (!LightEditor_Enabled(false))
		return;

	if (!curmodel)
		return;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf("Usage: changemodel <filename>\n");
		return;
	}

	inv = curmodel->mdl->invert;
	scale = curmodel->mdl->scale;
	mdl = R_RegisterModel(Cmd_Argv(1), scale, inv);
	if (!mdl)
		return;

	Memcpy(curmodel->model, Cmd_Argv(1), MAX_QPATH);
	curmodel->mdl = mdl;
	curmodel->frame = curmodel->skinnum = curmesh = curtri = 0;

	if (!R_MarkAliasLeaves(curmodel))
		Com_Printf("^3Warning:^7 model out of BSP\n");
	Com_Printf("Model changed.\n");
	Cl_UpdateEditorCvars(ED_MODEL | ED_MODIFIED);
}


void CL_InitLocal ()
{
	int	i;
	cls.state = ca_disconnected;
	cls.realtime = Sys_Milliseconds ();

	CL_InitInput ();

	for( i=0 ; i<MAX_LOCAL_SERVERS ; i++ )
		Cvar_Get( va( "adr%i", i ), "", CVAR_ARCHIVE );

//
// register our variables
//
	cl_add_lights = Cvar_Get ("cl_lights", "1", 0);
	cl_add_particles = Cvar_Get ("cl_particles", "1", 0);
	cl_add_entities = Cvar_Get ("cl_entities", "1", 0);
	cl_footsteps = Cvar_Get ("cl_footsteps", "1", 0);
	cl_noskins = Cvar_Get ("cl_noskins", "0", 0);
	cl_noskins->help = "assign for all players skin 'players/male/grunt'.";
///	cl_autoskins = Cvar_Get ("cl_autoskins", "0", 0);
	cl_predict = Cvar_Get ("cl_predict", "1", 0);
	cl_sleep = Cvar_Get ("cl_sleep", "0", CVAR_ARCHIVE);
	cl_sleep->help = "can decrease CPU loading when cl_maxfps limits render speed.";
	cl_forcemymodel = Cvar_Get ("cl_forcemymodel", "0", CVAR_ARCHIVE);
	cl_forcemymodel->help = "assign for all players your skin. 1 - in game, 2 - in demos.";
	cl_maxfps = Cvar_Get ("cl_maxfps", "60", CVAR_ARCHIVE);

	p_splash =  Cvar_Get ("p_splash", "1", CVAR_ARCHIVE);		p_splash->value = ClampCvar(0, 1, p_splash->value);
	p_blood =  Cvar_Get ("p_blood", "1", CVAR_ARCHIVE);			p_blood->value = ClampCvar(0, 1, p_blood->value);
	p_spawn =  Cvar_Get ("p_spawn", "1", CVAR_ARCHIVE);			p_spawn->value = ClampCvar(0, 1, p_spawn->value);
	p_rocket =  Cvar_Get ("p_rocket", "1", CVAR_ARCHIVE);		p_rocket->value = ClampCvar(0, 1, p_rocket->value);
	p_grenade =  Cvar_Get ("p_grenade", "1", CVAR_ARCHIVE);		p_grenade->value = ClampCvar(0, 1, p_grenade->value);
	p_gib =  Cvar_Get ("p_gib", "1", CVAR_ARCHIVE);				p_gib->value = ClampCvar(0, 1, p_gib->value);
	p_shotgun =  Cvar_Get ("p_shotgun", "0.75", CVAR_ARCHIVE);	p_shotgun->value = ClampCvar(0, 1, p_shotgun->value);
	p_machine =  Cvar_Get ("p_machine", "0.5", CVAR_ARCHIVE);	p_machine->value = ClampCvar(0, 1, p_machine->value);
	p_explosion =  Cvar_Get ("p_explosion", "2", CVAR_ARCHIVE);	p_explosion->value = ClampCvar(0, 2, p_explosion->value);
	p_sparks =  Cvar_Get ("p_sparks", "1", CVAR_ARCHIVE);	p_sparks->value = ClampCvar(0, 2, p_sparks->value);
	p_tracer = Cvar_Get("p_tracer", "1", CVAR_ARCHIVE);	p_tracer->value = ClampCvar(0, 1, p_tracer->value);
	p_weapfire = Cvar_Get("p_weapfire", "1", CVAR_ARCHIVE);	p_weapfire->value = ClampCvar(0, 1, p_weapfire->value);

// цвет крови
	p_blood_r =  Cvar_Get ("p_blood_r", "0.5", CVAR_ARCHIVE);	p_blood_r->value = ClampCvar(0, 1, p_blood_r->value);
	p_blood_g =  Cvar_Get ("p_blood_g", "0", CVAR_ARCHIVE);	p_blood_g->value = ClampCvar(0, 1, p_blood_g->value);
	p_blood_b =  Cvar_Get ("p_blood_b", "0", CVAR_ARCHIVE);	p_blood_b->value = ClampCvar(0, 1, p_blood_b->value);
// цвет зелёной крови
	p_greenblood_r =  Cvar_Get ("p_greenblood_r", "0", CVAR_ARCHIVE);	p_greenblood_r->value = ClampCvar(0, 1, p_greenblood_r->value);
	p_greenblood_g =  Cvar_Get ("p_greenblood_g", "0.4", CVAR_ARCHIVE);	p_greenblood_g->value = ClampCvar(0, 1, p_greenblood_g->value);
	p_greenblood_b =  Cvar_Get ("p_greenblood_b", "0", CVAR_ARCHIVE);	p_greenblood_b->value = ClampCvar(0, 1, p_greenblood_b->value);
// цвет дыма от попадания в стену или взрыва
	p_smoke_r =  Cvar_Get ("p_smoke_r", "0.7", CVAR_ARCHIVE);	p_smoke_r->value = ClampCvar(0, 1, p_smoke_r->value);
	p_smoke_g =  Cvar_Get ("p_smoke_g", "0.7", CVAR_ARCHIVE);	p_smoke_g->value = ClampCvar(0, 1, p_smoke_g->value);
	p_smoke_b =  Cvar_Get ("p_smoke_b", "0.7", CVAR_ARCHIVE);	p_smoke_b->value = ClampCvar(0, 1, p_smoke_b->value);
// цвет дыма от выстрела или полёта ракеты
	p_shot_r =  Cvar_Get ("p_shot_r", "0.86", CVAR_ARCHIVE);	p_shot_r->value = ClampCvar(0, 1, p_shot_r->value);
	p_shot_g =  Cvar_Get ("p_shot_g", "0.58", CVAR_ARCHIVE);	p_shot_g->value = ClampCvar(0, 1, p_shot_g->value);
	p_shot_b =  Cvar_Get ("p_shot_b", "0.39", CVAR_ARCHIVE);	p_shot_b->value = ClampCvar(0, 1, p_shot_b->value);

	cl_upspeed = Cvar_Get ("cl_upspeed", "200", 0);
	cl_forwardspeed = Cvar_Get ("cl_forwardspeed", "200", 0);
	cl_sidespeed = Cvar_Get ("cl_sidespeed", "200", 0);
	cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", 0);
	cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "150", 0);
	cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0);

	cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE);
	freelook = Cvar_Get( "freelook", "1", CVAR_ARCHIVE );
	lookspring = Cvar_Get ("lookspring", "0", CVAR_ARCHIVE);
	lookstrafe = Cvar_Get ("lookstrafe", "0", CVAR_ARCHIVE);
	sensitivity = Cvar_Get ("sensitivity", "2", CVAR_ARCHIVE);
	m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE);
	m_yaw = Cvar_Get ("m_yaw", "0.022", 0);
	m_forward = Cvar_Get ("m_forward", "1", 0);
	m_side = Cvar_Get ("m_side", "1", 0);

	cl_showmiss = Cvar_Get ("cl_showmiss", "0", 0);
	cl_showclamp = Cvar_Get ("showclamp", "0", 0);
	cl_timeout = Cvar_Get ("cl_timeout", "120", 0);
	cl_paused = Cvar_Get ("paused", "0", 0);
	cl_timedemo = Cvar_Get ("timedemo", "0", 0);

	rcon_client_password = Cvar_Get ("rcon_password", "", 0);
	rcon_client_password->help = "password for use with RCON commands.";
	rcon_address = Cvar_Get ("rcon_address", "", 0);

	dl_shell = Cvar_Get ("dl_shell", "1", CVAR_ARCHIVE);
	dl_rocket = Cvar_Get ("dl_rocket", "1", CVAR_ARCHIVE);
	dl_blaster = Cvar_Get ("dl_blaster", "1", CVAR_ARCHIVE);
	dl_hyperblaster = Cvar_Get ("dl_hyperblaster", "1", CVAR_ARCHIVE);
	dl_bfg = Cvar_Get ("dl_bfg", "1", CVAR_ARCHIVE);
	dl_explosion = Cvar_Get ("dl_explosion", "1", CVAR_ARCHIVE);
	dl_muzzle = Cvar_Get ("dl_muzzle", "1", 0);

	//
	// userinfo
	//
	cl_add_blend = Cvar_Get("cl_blend", "1", CVAR_USERINFO | CVAR_ARCHIVE);
	cl_gun = Cvar_Get("cl_gun", "1", CVAR_USERINFO | CVAR_ARCHIVE);
	cl_handicap = Cvar_Get("cl_handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE);
	cl_handicap->help = "allow to set your health limit.";
	cl_fovweap = Cvar_Get("cl_fovweap", "1", CVAR_USERINFO | CVAR_ARCHIVE);
	cl_fovweap->help = "if set, your weapon does not disappear for FOV > 90.";
	info_password = Cvar_Get ("password", "", CVAR_USERINFO);
	info_spectator = Cvar_Get ("spectator", "0", CVAR_USERINFO);
	name = Cvar_Get ("name", sys_username->string, CVAR_USERINFO | CVAR_ARCHIVE);
	skin = Cvar_Get ("skin", "male/grunt", CVAR_USERINFO | CVAR_ARCHIVE);
	rate = Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE);	// FIXME
	rate->help = "limits the net bandwidth.";
	msg = Cvar_Get ("msg", "1", CVAR_USERINFO | CVAR_ARCHIVE);
	hand = Cvar_Get ("hand", "0", CVAR_USERINFO | CVAR_ARCHIVE);
	hand->help = "0 - right, 1 - left, 2 - center (no weapon).";
	if ( hand->value < 0 || hand->value > 2 )
		Cvar_SetValue( "hand", 0 );

	railtrailcore = Cvar_Get ("rail_core", "7", CVAR_USERINFO | CVAR_ARCHIVE);
	railtrailcore->help = "allow you change the railtrail core color. 0 - no core.";
///	if(railtrailcore->value<0 || railtrailcore->value>7)
///		Cvar_SetValue("rail_core", 7);
	railtrailspiral = Cvar_Get ("rail_spiral", "1", CVAR_USERINFO | CVAR_ARCHIVE);
	railtrailspiral->help = "allow you change the railtrail spiral color. 0 - random color.";
	if(railtrailspiral->value<0 || railtrailspiral->value>7)
		Cvar_SetValue("rail_spiral", 1);
	railtrailradius = Cvar_Get ("rail_radius", "3", CVAR_USERINFO | CVAR_ARCHIVE);
	railtrailradius->help = "railtrail spiral radius. 0 - no spiral.";
	if(railtrailradius->value<0 || railtrailradius->value>127)
		Cvar_SetValue("rail_radius", 3);

	fov = Cvar_Get ("fov", "91", CVAR_USERINFO|CVAR_ARCHIVE);
	fov->help = "Field Of Vision (degrees). '90' will block '+zoom'.";
	zoomfov = Cvar_Get ("zoomfov", "22.5", CVAR_ARCHIVE);
	zoomfov->help = "lower FOV limit for '+zoom'";
	gender = Cvar_Get ("gender", "male", CVAR_USERINFO | /*CVAR_ARCHIVE*/CVAR_NOSET);
///	gender_auto = Cvar_Get ("gender_auto", "1", CVAR_ARCHIVE);
///	gender->modified = false; // clear this so we know when user sets it manually
CL_FixUpGender();	// Berserker: на старте вычислим gender и модель.

	cl_vwep = Cvar_Get ("cl_vwep", "1", CVAR_ARCHIVE);

	/// код "вида от третьего лица" взят из Quake3
	cl_thirdPerson			=	Cvar_Get("cl_thirdPerson", "0", CVAR_ARCHIVE);
	cl_thirdPerson->help	=	"Enable the third person view.";
	cl_thirdPersonRange		=	Cvar_Get("cl_thirdPersonRange", "40", CVAR_ARCHIVE);
	cl_thirdPersonAngle		=	Cvar_Get("cl_thirdPersonAngle", "0", 0);
	cl_cameraOrbit			=	Cvar_Get("cl_cameraOrbit", "0", 0);
	cl_cameraOrbit->help	=	"Enable fly the camera around the player.";

	//
	// register our commands
	//
	Cmd_AddCommand ("cmd", CL_ForwardToServer_f);
	Cmd_AddCommand ("pause", CL_Pause_f);
	Cmd_AddCommand ("pingservers", CL_PingServers_f);
	Cmd_AddCommand ("skins", CL_Skins_f);
	Cmd_AddCommand ("userinfo", CL_Userinfo_f);
	Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f);
	Cmd_AddCommand ("changing", CL_Changing_f);
///	Cmd_AddCommand ("disconnect", CL_Disconnect_f);
	Cmd_AddCommand ("record", CL_Record_f);
	Cmd_AddCommand ("stop", CL_Stop_f);
	Cmd_AddCommand ("quit", CL_Quit_f);
	Cmd_AddCommand ("connect", CL_Connect_f);
	Cmd_AddCommand ("reconnect", CL_Reconnect_f);
	Cmd_AddCommand ("rcon", CL_Rcon_f);
	Cmd_AddCommand ("precache", CL_Precache_f);
	Cmd_AddCommand ("download", CL_Download_f);
	Cmd_AddCommand ("killClientEntities", CL_KillClientEntities_f);

	Cmd_AddCommand ("cache", CL_Cache_f);
	Cmd_AddCommand ("cache_bsp", CL_CacheBSP_f);
	Cmd_AddCommand ("cache_mdl", CL_CacheMDL_f);

/// berserker's relight
	Cmd_AddCommand ("savelights", CL_SaveLights_f);
	Cmd_AddCommand ("ambient", CL_Ambient_f);
	Cmd_AddCommand ("sky_clear", CL_Sky_Clear_f);
	Cmd_AddCommand ("sky_viewcenter", CL_Sky_ViewCenter_f);
	Cmd_AddCommand ("sky_move_viewcenter_x", CL_Sky_Move_ViewCenter_f);
	Cmd_AddCommand ("sky_move_viewcenter_y", CL_Sky_Move_ViewCenter_f);
	Cmd_AddCommand ("sky_move_viewcenter_z", CL_Sky_Move_ViewCenter_f);
	Cmd_AddCommand ("sky_origin", CL_Sky_Origin_f);
	Cmd_AddCommand ("sky_move_origin_x", CL_Sky_Move_Origin_f);
	Cmd_AddCommand ("sky_move_origin_y", CL_Sky_Move_Origin_f);
	Cmd_AddCommand ("sky_move_origin_z", CL_Sky_Move_Origin_f);
	Cmd_AddCommand ("sky_angle", CL_Sky_Angle_f);
	Cmd_AddCommand ("sky_rotate", CL_Sky_Rotate_f);
	Cmd_AddCommand ("sky_scale", CL_Sky_Scale_f);
	Cmd_AddCommand ("switch", CL_Switch_f);
	Cmd_AddCommand ("select", CL_Select_f);
	Cmd_AddCommand ("select_nearest", CL_SelectNearest_f);
	Cmd_AddCommand ("next", CL_Next_f);
	Cmd_AddCommand ("prev", CL_Prev_f);
	Cmd_AddCommand ("delete", CL_Delete_f);
	Cmd_AddCommand ("copy", CL_Copy_f);
	Cmd_AddCommand ("cut", CL_Cut_f);
	Cmd_AddCommand ("paste", CL_Paste_f);
	Cmd_AddCommand ("paste2camera", CL_Paste_f);
///	Cmd_AddCommand ("paste2current", CL_PasteCur_f);
	Cmd_AddCommand ("create", CL_Create_f);
	Cmd_AddCommand ("pick", CL_Pick_f);
	Cmd_AddCommand ("createflarelights", CL_CreateFlareLights_f);
	Cmd_AddCommand ("deleteflarelights", CL_DeleteFlareLights_f);
	Cmd_AddCommand ("radius", CL_Radius_f);
	Cmd_AddCommand ("origin", CL_Origin_f);
	Cmd_AddCommand ("color", CL_Color_f);
	Cmd_AddCommand ("pipette", CL_Color2_f);
	Cmd_AddCommand ("angles", CL_Angles_f);
	Cmd_AddCommand ("speed", CL_RSpeed_f);
	Cmd_AddCommand ("cone", CL_Cone_f);
	Cmd_AddCommand ("style", CL_Style_f);
	Cmd_AddCommand ("cl_style", CL_CL_Style_f);
	Cmd_AddCommand ("filter", CL_Filter_f);
	Cmd_AddCommand ("framerate", CL_FrameRate_f);
	Cmd_AddCommand ("start", CL_Start_f);
	Cmd_AddCommand ("noshadow", CL_NoShadow_f);
	Cmd_AddCommand ("noshadow2", CL_NoShadow2_f);
	Cmd_AddCommand ("nobump", CL_NoBump_f);
	Cmd_AddCommand ("targetname", CL_Targetname_f);
	Cmd_AddCommand ("move_x", CL_Move_f);
	Cmd_AddCommand ("move_y", CL_Move_f);
	Cmd_AddCommand ("move_z", CL_Move_f);
	Cmd_AddCommand ("add_r", CL_Move_f);
	Cmd_AddCommand ("add_r_x", CL_Move_f);
	Cmd_AddCommand ("add_r_y", CL_Move_f);
	Cmd_AddCommand ("add_r_z", CL_Move_f);
	Cmd_AddCommand ("rot_a", CL_Rot_f);
	Cmd_AddCommand ("rot_b", CL_Rot_f);
	Cmd_AddCommand ("rot_c", CL_Rot_f);
	Cmd_AddCommand ("direct", CL_Direct_f);
	Cmd_AddCommand ("selectbrush", CL_SelectBrush_f);
	Cmd_AddCommand ("unselectbrush", CL_UnSelectBrush_f);
	Cmd_AddCommand ("showbrush", CL_ShowBrush_f);
	Cmd_AddCommand ("nextbrush", CL_NextBrush_f);
	Cmd_AddCommand ("prevbrush", CL_PrevBrush_f);
	Cmd_AddCommand ("labelbrush", CL_LabelBrush_f);
	Cmd_AddCommand ("sun", CL_Sun_f);
	Cmd_AddCommand ("sun_addx", CL_SunAdd_f);
	Cmd_AddCommand ("sun_addy", CL_SunAdd_f);
	Cmd_AddCommand ("sun_addz", CL_SunAdd_f);
	Cmd_AddCommand ("sunpos", CL_SunPos_f);
	Cmd_AddCommand ("createemit", CL_CreateEmit_f);
	Cmd_AddCommand ("originemit", CL_OriginEmit_f);
	Cmd_AddCommand ("moveemit_x", CL_MoveEmit_f);
	Cmd_AddCommand ("moveemit_y", CL_MoveEmit_f);
	Cmd_AddCommand ("moveemit_z", CL_MoveEmit_f);
	Cmd_AddCommand ("traceemit", CL_TraceEmit_f);
	Cmd_AddCommand ("labelemit", CL_LabelEmit_f);
	Cmd_AddCommand ("typeemit", CL_FilterEmit_f);
	Cmd_AddCommand ("styleemit", CL_StyleEmit_f);
	Cmd_AddCommand ("cl_styleemit", CL_CL_StyleEmit_f);
	Cmd_AddCommand ("coneemit", CL_ConeEmit_f);
	Cmd_AddCommand ("speedemit", CL_RSpeedEmit_f);
	Cmd_AddCommand ("numemits", CL_NumEmits_f);
	Cmd_AddCommand ("anglesemit", CL_AnglesEmit_f);
	Cmd_AddCommand ("velemit", CL_VelEmit_f);
	Cmd_AddCommand ("rotemit_a", CL_RotEmit_f);
	Cmd_AddCommand ("rotemit_b", CL_RotEmit_f);
	Cmd_AddCommand ("deleteemit", CL_DeleteEmit_f);
	Cmd_AddCommand ("nextemit", CL_NextEmit_f);
	Cmd_AddCommand ("prevemit", CL_PrevEmit_f);
	Cmd_AddCommand ("select_nearest_emit", CL_SelectNearestEmit_f);
	Cmd_AddCommand ("selectemit", CL_SelectEmit_f);
	Cmd_AddCommand ("copyemit", CL_CopyEmit_f);
	Cmd_AddCommand ("cutemit", CL_CutEmit_f);
	Cmd_AddCommand ("pasteemit", CL_PasteEmit_f);
	Cmd_AddCommand ("paste2cameraemit", CL_PasteEmit_f);
	Cmd_AddCommand ("paste2currentemit", CL_PasteCurEmit_f);
	Cmd_AddCommand ("directemit", CL_DirectEmit_f);
	Cmd_AddCommand ("startcoloremit", CL_StartColorEmit_f);
	Cmd_AddCommand ("endcoloremit", CL_EndColorEmit_f);
	Cmd_AddCommand ("lifetimeemit", CL_LifeTimeEmit_f);
	Cmd_AddCommand ("gravityemit", CL_GravityEmit_f);
	Cmd_AddCommand ("createdecal", CL_CreateDecal_f);
	Cmd_AddCommand ("deletedecal", CL_DeleteDecal_f);
	Cmd_AddCommand ("select_nearest_decal", CL_SelectNearestDecal_f);
	Cmd_AddCommand ("unselectdecal", CL_UnSelectDecal_f);
	Cmd_AddCommand ("typedecal", CL_FilterDecal_f);
	Cmd_AddCommand ("blenddecal", CL_BlendDecal_f);
	Cmd_AddCommand ("angledecal", CL_AngleDecal_f);
	Cmd_AddCommand ("rot_decal", CL_RotDecal_f);
	Cmd_AddCommand ("sizedecal", CL_SizeDecal_f);
	Cmd_AddCommand ("resizedecal", CL_ReSizeDecal_f);
	Cmd_AddCommand ("copydecal", CL_CopyDecal_f);
	Cmd_AddCommand ("pastedecal", CL_PasteDecal_f);
	Cmd_AddCommand ("cutdecal", CL_CutDecal_f);
	Cmd_AddCommand ("tracedecal", CL_TraceDecal_f);
	Cmd_AddCommand ("styledecal", CL_StyleDecal_f);
	Cmd_AddCommand ("cl_styledecal", CL_CL_StyleDecal_f);
	Cmd_AddCommand ("pickdecal", CL_PickDecal_f);
	Cmd_AddCommand ("pickemit", CL_PickEmit_f);
	Cmd_AddCommand ("trace", CL_TraceLight_f);
	Cmd_AddCommand ("createmodel", CL_CreateModel_f);
	Cmd_AddCommand ("scalemodel", CL_ScaleModel_f);
	Cmd_AddCommand ("invertmodel", CL_InvertModel_f);
	Cmd_AddCommand ("deletemodel", CL_DeleteModel_f);
	Cmd_AddCommand ("pickmodel", CL_PickModel_f);
	Cmd_AddCommand ("select_nearest_model", CL_SelectNearestModel_f);
	Cmd_AddCommand ("select_model_mesh", CL_SelectModelMesh_f);
	Cmd_AddCommand ("select_model_tri", CL_SelectModelTri_f);
	Cmd_AddCommand ("unselectmodel", CL_UnSelectModel_f);
	Cmd_AddCommand ("changemodel", CL_ChangeModel_f);
	Cmd_AddCommand ("anglesmodel", CL_AnglesModel_f);
	Cmd_AddCommand ("rotmodel_a", CL_RotModel_f);
	Cmd_AddCommand ("rotmodel_b", CL_RotModel_f);
	Cmd_AddCommand ("rotmodel_c", CL_RotModel_f);
	Cmd_AddCommand ("movemodel_x", CL_MoveModel_f);
	Cmd_AddCommand ("movemodel_y", CL_MoveModel_f);
	Cmd_AddCommand ("movemodel_z", CL_MoveModel_f);
	Cmd_AddCommand ("originmodel", CL_OriginModel_f);
	Cmd_AddCommand ("nextmodel", CL_NextModel_f);
	Cmd_AddCommand ("prevmodel", CL_PrevModel_f);
	Cmd_AddCommand ("labelmodel", CL_LabelModel_f);
	Cmd_AddCommand ("framemodel", CL_FrameModel_f);
	Cmd_AddCommand ("frameendmodel", CL_FrameEndModel_f);
	Cmd_AddCommand ("nextframemodel", CL_NextFrameModel_f);
	Cmd_AddCommand ("prevframemodel", CL_PrevFrameModel_f);
	Cmd_AddCommand ("skinmodel", CL_SkinModel_f);
	Cmd_AddCommand ("nextskinmodel", CL_NextSkinModel_f);
	Cmd_AddCommand ("prevskinmodel", CL_PrevSkinModel_f);
	Cmd_AddCommand ("animframemodel", CL_AnimFrameModel_f);
	Cmd_AddCommand ("tracemodel", CL_TraceModel_f);
	Cmd_AddCommand ("copymodel", CL_CopyModel_f);
	Cmd_AddCommand ("pastemodel", CL_PasteModel_f);
	Cmd_AddCommand ("paste2cameramodel", CL_PasteModel_f);
	Cmd_AddCommand ("cutmodel", CL_CutModel_f);
	Cmd_AddCommand ("noisemodel", CL_NoiseModel_f);
	Cmd_AddCommand ("nodrawbrush", CL_NoDrawBrush_f);
	Cmd_AddCommand ("nodrawsurf", CL_NoDrawSurf_f);
	Cmd_AddCommand ("showsurf", CL_ShowSurf_f);
	Cmd_AddCommand ("opaquesky", CL_OpaqueSky_f);
	Cmd_AddCommand ("lightstyle", CL_LightStyle_f);
	Cmd_AddCommand ("ignore_lightsurf", CL_IgnoreLightSurf_f);
	Cmd_AddCommand ("cl_lightstyle", CL_CL_LightStyle_f);
	Cmd_AddCommand ("createfog", CL_CreateFog_f);
	Cmd_AddCommand ("deletefog", CL_DeleteFog_f);
	Cmd_AddCommand ("fog_color", CL_FogColor_f);
	Cmd_AddCommand ("fog_density", CL_FogDensity_f);
	Cmd_AddCommand ("fog_origin0", CL_OriginFog0_f);
	Cmd_AddCommand ("fog_origin1", CL_OriginFog1_f);
	Cmd_AddCommand ("add_fog_origin0", CL_AddFogOrigin0_f);
	Cmd_AddCommand ("add_fog_origin1", CL_AddFogOrigin1_f);
	Cmd_AddCommand ("selectfog", CL_SelectFog_f);
	Cmd_AddCommand ("unselectfog", CL_UnSelectFog_f);
	Cmd_AddCommand("createEFXzone", CL_CreateEFXzone_f);
	Cmd_AddCommand("deleteEFXzone", CL_DeleteEFXzone_f);
	Cmd_AddCommand("deleteEFXzone_all", CL_DeleteEFXzoneAll_f);
	Cmd_AddCommand("selectEFXzone", CL_SelectEFXzone_f);
	Cmd_AddCommand("selectEFXzone_next", CL_SelectEFXzoneNext_f);
	Cmd_AddCommand("selectEFXzone_prev", CL_SelectEFXzonePrev_f);
	Cmd_AddCommand("unselectEFXzone", CL_UnSelectEFXzone_f);
	Cmd_AddCommand("EFXzone_preset", CL_EFXzonePreset_f);
	Cmd_AddCommand("EFXzone_preset_next", CL_EFXzonePresetNext_f);
	Cmd_AddCommand("EFXzone_preset_prev", CL_EFXzonePresetPrev_f);
	Cmd_AddCommand("EFXzone_origin0", CL_OriginEFXzone0_f);
	Cmd_AddCommand("EFXzone_origin1", CL_OriginEFXzone1_f);
	Cmd_AddCommand("add_EFXzone_origin0", CL_AddEFXzoneOrigin0_f);
	Cmd_AddCommand("add_EFXzone_origin1", CL_AddEFXzoneOrigin1_f);
	Cmd_AddCommand("EFX_list", CL_ListEFX_f);

	// relight's info cvars (for scripts):
	Cvar_Get ("ed_modified", "0", CVAR_NOSET);			// 0 - no need to save, 1 - modified but not saved
	// light's properties
	Cvar_Get ("ed_light", "0", CVAR_NOSET);				// 0 - no current light, 1 - static, 2 - linked lights mode
	Cvar_Get ("ed_light_origin_x", "", CVAR_NOSET);
	Cvar_Get ("ed_light_origin_y", "", CVAR_NOSET);
	Cvar_Get ("ed_light_origin_z", "", CVAR_NOSET);
	Cvar_Get ("ed_light_radius", "", CVAR_NOSET);
	Cvar_Get ("ed_light_radius_x", "", CVAR_NOSET);
	Cvar_Get ("ed_light_radius_y", "", CVAR_NOSET);
	Cvar_Get ("ed_light_radius_z", "", CVAR_NOSET);
	Cvar_Get ("ed_light_color_r", "", CVAR_NOSET);
	Cvar_Get ("ed_light_color_g", "", CVAR_NOSET);
	Cvar_Get ("ed_light_color_b", "", CVAR_NOSET);
	Cvar_Get ("ed_light_angle_a", "", CVAR_NOSET);
	Cvar_Get ("ed_light_angle_b", "", CVAR_NOSET);
	Cvar_Get ("ed_light_angle_c", "", CVAR_NOSET);
	Cvar_Get ("ed_light_rspeed_a", "", CVAR_NOSET);
	Cvar_Get ("ed_light_rspeed_b", "", CVAR_NOSET);
	Cvar_Get ("ed_light_rspeed_c", "", CVAR_NOSET);
	Cvar_Get ("ed_light_cone", "", CVAR_NOSET);
	Cvar_Get ("ed_light_style", "", CVAR_NOSET);
	Cvar_Get ("ed_light_cl_style", "", CVAR_NOSET);
	Cvar_Get ("ed_light_filter_start", "", CVAR_NOSET);
	Cvar_Get ("ed_light_filter_end", "", CVAR_NOSET);
	Cvar_Get ("ed_light_filter_framerate", "", CVAR_NOSET);
	Cvar_Get ("ed_light_targetname", "", CVAR_NOSET);
	Cvar_Get ("ed_light_noshadow", "", CVAR_NOSET);
	Cvar_Get ("ed_light_noshadow2", "", CVAR_NOSET);
	Cvar_Get ("ed_light_nobump", "", CVAR_NOSET);
	Cvar_Get ("ed_light_nostart", "", CVAR_NOSET);
	Cvar_Get ("ed_light_label", "", CVAR_NOSET);
	// emitter's properties
	Cvar_Get ("ed_emit", "0", CVAR_NOSET);		// 0 - no current emitter
	Cvar_Get ("ed_emit_origin_x", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_origin_y", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_origin_z", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_angle_a", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_angle_b", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_rspeed_a", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_rspeed_b", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_velocity", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_num_particles", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_cone", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_style", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_cl_style", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_type", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_label", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_color_start_r", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_color_start_g", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_color_start_b", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_color_end_r", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_color_end_g", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_color_end_b", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_lifetime", "", CVAR_NOSET);
	Cvar_Get ("ed_emit_gravity", "", CVAR_NOSET);
	// decal's properties
	Cvar_Get ("ed_decal", "0", CVAR_NOSET);		// 0 - no current decal
	Cvar_Get ("ed_decal_origin_x", "", CVAR_NOSET);
	Cvar_Get ("ed_decal_origin_y", "", CVAR_NOSET);
	Cvar_Get ("ed_decal_origin_z", "", CVAR_NOSET);
	Cvar_Get ("ed_decal_type", "", CVAR_NOSET);
	Cvar_Get ("ed_decal_angle", "", CVAR_NOSET);
	Cvar_Get ("ed_decal_blend", "", CVAR_NOSET);
	Cvar_Get ("ed_decal_style", "", CVAR_NOSET);
	Cvar_Get ("ed_decal_cl_style", "", CVAR_NOSET);
	Cvar_Get ("ed_decal_size", "", CVAR_NOSET);
	// fog's properties
	Cvar_Get ("ed_fog", "0", CVAR_NOSET);		// 0 - no current fog
	Cvar_Get ("ed_fog_origin0_x", "", CVAR_NOSET);
	Cvar_Get ("ed_fog_origin0_y", "", CVAR_NOSET);
	Cvar_Get ("ed_fog_origin0_z", "", CVAR_NOSET);
	Cvar_Get ("ed_fog_origin1_x", "", CVAR_NOSET);
	Cvar_Get ("ed_fog_origin1_y", "", CVAR_NOSET);
	Cvar_Get ("ed_fog_origin1_z", "", CVAR_NOSET);
	Cvar_Get ("ed_fog_color_r", "", CVAR_NOSET);
	Cvar_Get ("ed_fog_color_g", "", CVAR_NOSET);
	Cvar_Get ("ed_fog_color_b", "", CVAR_NOSET);
	Cvar_Get ("ed_fog_density", "", CVAR_NOSET);
	// EFXzone's properties
	Cvar_Get("ed_EFXzone", "0", CVAR_NOSET);		// 0 - no current EFXzone
	Cvar_Get("ed_EFXzone_origin0_x", "", CVAR_NOSET);
	Cvar_Get("ed_EFXzone_origin0_y", "", CVAR_NOSET);
	Cvar_Get("ed_EFXzone_origin0_z", "", CVAR_NOSET);
	Cvar_Get("ed_EFXzone_origin1_x", "", CVAR_NOSET);
	Cvar_Get("ed_EFXzone_origin1_y", "", CVAR_NOSET);
	Cvar_Get("ed_EFXzone_origin1_z", "", CVAR_NOSET);
	Cvar_Get("ed_EFXzone_preset", "", CVAR_NOSET);
	// model's properties
	Cvar_Get ("ed_model", "0", CVAR_NOSET);		// 0 - no current model
	Cvar_Get ("ed_model_name", "", CVAR_NOSET);
	Cvar_Get ("ed_model_skinnum", "", CVAR_NOSET);
	Cvar_Get ("ed_model_num_skins", "", CVAR_NOSET);
	Cvar_Get ("ed_model_origin_x", "", CVAR_NOSET);
	Cvar_Get ("ed_model_origin_y", "", CVAR_NOSET);
	Cvar_Get ("ed_model_origin_z", "", CVAR_NOSET);
	Cvar_Get ("ed_model_angle_a", "", CVAR_NOSET);
	Cvar_Get ("ed_model_angle_b", "", CVAR_NOSET);
	Cvar_Get ("ed_model_angle_c", "", CVAR_NOSET);
	Cvar_Get ("ed_model_num_frames", "", CVAR_NOSET);
	Cvar_Get ("ed_model_frame_start", "", CVAR_NOSET);
	Cvar_Get ("ed_model_frame_end", "", CVAR_NOSET);
	Cvar_Get ("ed_model_label", "", CVAR_NOSET);
	Cvar_Get ("ed_model_animate", "", CVAR_NOSET);
	Cvar_Get ("ed_model_noise", "", CVAR_NOSET);
	Cvar_Get ("ed_model_tri", "", CVAR_NOSET);
	Cvar_Get ("ed_model_mesh", "", CVAR_NOSET);
	Cvar_Get ("ed_model_numtris", "", CVAR_NOSET);
	Cvar_Get ("ed_model_nummeshes", "", CVAR_NOSET);
	Cvar_Get ("ed_model_invert", "", CVAR_NOSET);
	Cvar_Get ("ed_model_scale", "", CVAR_NOSET);
	// brush's properties
	Cvar_Get ("ed_brush", "0", CVAR_NOSET);		// 0 - no current brush
	Cvar_Get ("ed_brush_label", "", CVAR_NOSET);
	Cvar_Get ("ed_brush_nodraw", "", CVAR_NOSET);
	// ambient's properties
	Cvar_Get ("ed_ambient_r", "", CVAR_NOSET);
	Cvar_Get ("ed_ambient_g", "", CVAR_NOSET);
	Cvar_Get ("ed_ambient_b", "", CVAR_NOSET);

	//
	// forward to server commands
	//
	// the only thing this does is allow command completion
	// to work -- all unknown commands are automatically
	// forwarded to the server
	Cmd_AddCommand ("wave", NULL);
	Cmd_AddCommand ("inven", NULL);
	Cmd_AddCommand ("killmonsters", NULL);
	Cmd_AddCommand ("kill", NULL);
	Cmd_AddCommand ("use", NULL);
	Cmd_AddCommand ("drop", NULL);
	Cmd_AddCommand ("say", NULL);
	Cmd_AddCommand ("say_team", NULL);
	Cmd_AddCommand ("info", NULL);
	Cmd_AddCommand ("prog", NULL);	/// ?
	Cmd_AddCommand ("give", NULL);
	Cmd_AddCommand ("spawn", NULL);
	Cmd_AddCommand ("god", NULL);
	Cmd_AddCommand ("notarget", NULL);
	Cmd_AddCommand ("noclip", NULL);
	Cmd_AddCommand ("invuse", NULL);
	Cmd_AddCommand ("invprev", NULL);
	Cmd_AddCommand ("invnext", NULL);
	Cmd_AddCommand ("invdrop", NULL);
	Cmd_AddCommand ("weapnext", NULL);
	Cmd_AddCommand ("weapprev", NULL);

	Cmd_AddCommand ("players", NULL);
	Cmd_AddCommand ("playerlist", NULL);
	Cmd_AddCommand ("score", NULL);
	Cmd_AddCommand ("help", NULL);
	Cmd_AddCommand ("putaway", NULL);
	Cmd_AddCommand ("invnextw", NULL);
	Cmd_AddCommand ("invprevw", NULL);
	Cmd_AddCommand ("invnextp", NULL);
	Cmd_AddCommand ("invprevp", NULL);
	Cmd_AddCommand ("weaplast", NULL);
}


void Cl_UpdateEditorCvars (int mask)
{
	int	i;

	if (mask & ED_MODIFIED)
		Cvar_ForceSet("ed_modified","1");

	if (mask & ED_AMBIENT)
	{
		Cvar_ForceSet("ed_ambient_r", va("%1.2f", world_ambient_light[0]));
		Cvar_ForceSet("ed_ambient_g", va("%1.2f", world_ambient_light[1]));
		Cvar_ForceSet("ed_ambient_b", va("%1.2f", world_ambient_light[2]));
	}

	if (mask & ED_LIGHT)
	{
		if (le_mode)
		{
			if (curlink)
			{
				Cvar_ForceSet("ed_light","2");

				Cvar_ForceSet("ed_light_radius", va("%i", (int)curlink->radius));
				Cvar_ForceSet("ed_light_radius_x", va("%i", (int)curlink->radius));
				Cvar_ForceSet("ed_light_radius_y", va("%i", (int)curlink->radius));
				Cvar_ForceSet("ed_light_radius_z", va("%i", (int)curlink->radius));

				Cvar_ForceSet("ed_light_origin_x", va("%8.2f", curlink->origin[0]));
				Cvar_ForceSet("ed_light_origin_y", va("%8.2f", curlink->origin[1]));
				Cvar_ForceSet("ed_light_origin_z", va("%8.2f", curlink->origin[2]));

				Cvar_ForceSet("ed_light_color_r", va("%1.2f", curlink->color[0]));
				Cvar_ForceSet("ed_light_color_g", va("%1.2f", curlink->color[1]));
				Cvar_ForceSet("ed_light_color_b", va("%1.2f", curlink->color[2]));

				Cvar_ForceSet("ed_light_angle_a", va("%7.2f", curlink->angles[0]));
				Cvar_ForceSet("ed_light_angle_b", va("%7.2f", curlink->angles[1]));
				Cvar_ForceSet("ed_light_angle_c", va("%7.2f", curlink->angles[2]));

				Cvar_ForceSet("ed_light_cone", va("%f", curlink->_cone));

				Cvar_ForceSet("ed_light_style", va("%i", curlink->style));
				Cvar_ForceSet("ed_light_cl_style", va("%i", curlink->cl_style));

				Cvar_ForceSet("ed_light_filter_start", va("%i", curlink->filtercube_start));
				Cvar_ForceSet("ed_light_filter_end", va("%i", curlink->filtercube_end));
				Cvar_ForceSet("ed_light_filter_framerate", va("%f", curlink->framerate));

				Cvar_ForceSet("ed_light_label", curlink->label);
			}
			else
				Cvar_ForceSet("ed_light","0");
		}
		else
		{
			if (curlight)
			{
				Cvar_ForceSet("ed_light","1");

				Cvar_ForceSet("ed_light_radius", va("%i", (int)curlight->radius));
				Cvar_ForceSet("ed_light_radius_x", va("%i", (int)curlight->radiuses[0]));
				Cvar_ForceSet("ed_light_radius_y", va("%i", (int)curlight->radiuses[1]));
				Cvar_ForceSet("ed_light_radius_z", va("%i", (int)curlight->radiuses[2]));

				Cvar_ForceSet("ed_light_origin_x", va("%8.2f", curlight->origin[0]));
				Cvar_ForceSet("ed_light_origin_y", va("%8.2f", curlight->origin[1]));
				Cvar_ForceSet("ed_light_origin_z", va("%8.2f", curlight->origin[2]));

				Cvar_ForceSet("ed_light_color_r", va("%1.2f", curlight->color[0]));
				Cvar_ForceSet("ed_light_color_g", va("%1.2f", curlight->color[1]));
				Cvar_ForceSet("ed_light_color_b", va("%1.2f", curlight->color[2]));

				Cvar_ForceSet("ed_light_angle_a", va("%7.2f", curlight->angles[0]));
				Cvar_ForceSet("ed_light_angle_b", va("%7.2f", curlight->angles[1]));
				Cvar_ForceSet("ed_light_angle_c", va("%7.2f", curlight->angles[2]));

				Cvar_ForceSet("ed_light_rspeed_a", va("%s", curlight->rspeed[0]));
				Cvar_ForceSet("ed_light_rspeed_b", va("%s", curlight->rspeed[1]));
				Cvar_ForceSet("ed_light_rspeed_c", va("%s", curlight->rspeed[2]));

				Cvar_ForceSet("ed_light_cone", va("%f", curlight->_cone));

				Cvar_ForceSet("ed_light_style", va("%i", curlight->style));
				Cvar_ForceSet("ed_light_cl_style", va("%i", curlight->cl_style));

				Cvar_ForceSet("ed_light_filter_start", va("%i", curlight->filtercube_start));
				Cvar_ForceSet("ed_light_filter_end", va("%i", curlight->filtercube_end));
				Cvar_ForceSet("ed_light_filter_framerate", va("%f", curlight->framerate));

				Cvar_ForceSet("ed_light_targetname", curlight->targetname);

				Cvar_ForceSet("ed_light_nostart", (curlight->start_off ? "1" : "0"));

				Cvar_ForceSet("ed_light_noshadow", (curlight->noshadow ? "1" : "0"));
				Cvar_ForceSet("ed_light_noshadow2", (curlight->noshadow2 ? "1" : "0"));

				Cvar_ForceSet("ed_light_nobump", (curlight->nobump ? "1" : "0"));
			}
			else
				Cvar_ForceSet("ed_light","0");
		}
	}

	if (mask & ED_EMIT)
	{
		if (curemit)
		{
			Cvar_ForceSet("ed_emit","1");

			Cvar_ForceSet("ed_emit_origin_x", va("%8.2f", curemit->origin[0]));
			Cvar_ForceSet("ed_emit_origin_y", va("%8.2f", curemit->origin[1]));
			Cvar_ForceSet("ed_emit_origin_z", va("%8.2f", curemit->origin[2]));

			Cvar_ForceSet("ed_emit_angle_a", va("%7.2f", curemit->angles[0]));
			Cvar_ForceSet("ed_emit_angle_b", va("%7.2f", curemit->angles[1]));

			Cvar_ForceSet("ed_emit_rspeed_a", va("%7.2f", curemit->rspeed[0]));
			Cvar_ForceSet("ed_emit_rspeed_b", va("%7.2f", curemit->rspeed[1]));

			Cvar_ForceSet("ed_emit_num_particles", va("%i", (int)curemit->rspeed[2]));

			Cvar_ForceSet("ed_emit_velocity", va("%f", curemit->vel));

			Cvar_ForceSet("ed_emit_cone", va("%f", curemit->cone));

			Cvar_ForceSet("ed_emit_style", va("%i", curemit->style));
			Cvar_ForceSet("ed_emit_cl_style", va("%i", curemit->cl_style));

			Cvar_ForceSet("ed_emit_type", va("%i", curemit->emit));

			Cvar_ForceSet("ed_emit_label", curemit->label);

			Cvar_ForceSet("ed_emit_color_start_r", va("%1.2f", curemit->startcolor[0]));
			Cvar_ForceSet("ed_emit_color_start_g", va("%1.2f", curemit->startcolor[1]));
			Cvar_ForceSet("ed_emit_color_start_b", va("%1.2f", curemit->startcolor[2]));

			Cvar_ForceSet("ed_emit_color_end_r", va("%1.2f", curemit->endcolor[0]));
			Cvar_ForceSet("ed_emit_color_end_g", va("%1.2f", curemit->endcolor[1]));
			Cvar_ForceSet("ed_emit_color_end_b", va("%1.2f", curemit->endcolor[2]));

			Cvar_ForceSet("ed_emit_lifetime", va("%f", curemit->alphavel));

			Cvar_ForceSet("ed_emit_gravity", va("%i", curemit->gravity));
		}
		else
			Cvar_ForceSet("ed_emit","0");
	}

	if (mask & ED_DECAL)
	{
		if (curdecal)
		{
			Cvar_ForceSet("ed_decal","1");

			Cvar_ForceSet("ed_decal_origin_x", va("%8.2f", curdecal->center[0]));
			Cvar_ForceSet("ed_decal_origin_y", va("%8.2f", curdecal->center[1]));
			Cvar_ForceSet("ed_decal_origin_z", va("%8.2f", curdecal->center[2]));

			i = curdecal->texture-decal_texture_object[0];
			if (i>=MAX_GLOBAL_FILTERS)
				i = curdecal->texture-decal_texture_object[MAX_GLOBAL_FILTERS]+MAX_GLOBAL_FILTERS;
			Cvar_ForceSet("ed_decal_type", va("%i", i));

			Cvar_ForceSet("ed_decal_angle", va("%7.2f", curdecal->angle));

			if (curdecal->dtype==dt_add)
				Cvar_ForceSet("ed_decal_blend", "add");
			else
				Cvar_ForceSet("ed_decal_blend", "mul");

			Cvar_ForceSet("ed_decal_style", va("%i", curdecal->style));
			Cvar_ForceSet("ed_decal_cl_style", va("%i", curdecal->cl_style));

			Cvar_ForceSet("ed_decal_size", va("%i", (int)curdecal->size));
		}
		else
			Cvar_ForceSet("ed_decal","0");
	}

	if (mask & ED_FOG)
	{
		if (curfog)
		{
			Cvar_ForceSet("ed_fog","1");

			Cvar_ForceSet("ed_fog_origin0_x", va("%8.2f", curfog->origin[0]));
			Cvar_ForceSet("ed_fog_origin0_y", va("%8.2f", curfog->origin[1]));
			Cvar_ForceSet("ed_fog_origin0_z", va("%8.2f", curfog->origin[2]));

			Cvar_ForceSet("ed_fog_origin1_x", va("%8.2f", curfog->origin2[0]));
			Cvar_ForceSet("ed_fog_origin1_y", va("%8.2f", curfog->origin2[1]));
			Cvar_ForceSet("ed_fog_origin1_z", va("%8.2f", curfog->origin2[2]));

			Cvar_ForceSet("ed_fog_color_r", va("%1.2f", curfog->color[0]));
			Cvar_ForceSet("ed_fog_color_g", va("%1.2f", curfog->color[1]));
			Cvar_ForceSet("ed_fog_color_b", va("%1.2f", curfog->color[2]));

			Cvar_ForceSet("ed_fog_density", va("%f", 1.0/curfog->density));
		}
		else
			Cvar_ForceSet("ed_fog","0");
	}

	if (mask & ED_EFXZONE)
	{
		if (curEFXzone)
		{
			Cvar_ForceSet("ed_EFXzone", "1");

			Cvar_ForceSet("ed_EFXzone_origin0_x", va("%8.2f", curEFXzone->origin[0]));
			Cvar_ForceSet("ed_EFXzone_origin0_y", va("%8.2f", curEFXzone->origin[1]));
			Cvar_ForceSet("ed_EFXzone_origin0_z", va("%8.2f", curEFXzone->origin[2]));

			Cvar_ForceSet("ed_EFXzone_origin1_x", va("%8.2f", curEFXzone->origin2[0]));
			Cvar_ForceSet("ed_EFXzone_origin1_y", va("%8.2f", curEFXzone->origin2[1]));
			Cvar_ForceSet("ed_EFXzone_origin1_z", va("%8.2f", curEFXzone->origin2[2]));

			Cvar_ForceSet("ed_EFXzone_preset", va("%i", curEFXzone->preset));
		}
		else
			Cvar_ForceSet("ed_EFXzone", "0");
	}

	if (mask & ED_BRUSH)
	{
		if (curbrush)
		{
			// ищем энтити
			for (i=0 ; i<cl.refdef.num_entities ; i++)
			{
				currententity = &cl.refdef.entities[i];
				currentmodel = currententity->model;

				if (!currentmodel)
					continue;

				if (currentmodel->type != mod_brush)
					continue;

				if (currentmodel->nummodelsurfaces == 0)
					continue;

				if (currentmodel->firstmodelsurface == curbrush->firstface)
					break;
			}

			if (i==cl.refdef.num_entities)
				goto nxt;		// невозможный случай, но пусть будет

			for (i=0; i<brushmodel_counter; i++)
				if (bmdl_list[i].index == curbrushnum)
					break;

			Cvar_ForceSet("ed_brush","1");

			Cvar_ForceSet("ed_brush_label", bmdl_list[i].label);

			if (bmdl_list[i].nodraw)
				Cvar_ForceSet("ed_brush_nodraw", "1");
			else
				Cvar_ForceSet("ed_brush_nodraw", "0");
		}
		else
nxt:		Cvar_ForceSet("ed_brush","0");
	}

	if (mask & ED_MODEL)
	{
		if (curmodel)
		{
			int num_skins, num_tris, num_meshes;

			Cvar_ForceSet("ed_model","1");

			Cvar_ForceSet("ed_model_name", curmodel->model);

			Cvar_ForceSet("ed_model_skinnum", va("%i", curmodel->skinnum));

			if (curmodel->mdl->type == mod_alias)
			{
				dmdl_t	*paliashdr = (dmdl_t *)curmodel->mdl->extradata;
				num_skins = paliashdr->num_skins;
				num_tris = paliashdr->num_tris;
				num_meshes = 1;
			}
			else if (curmodel->mdl->type == mod_alias_md3)
			{
				maliasmodel_t	*paliashdr = (maliasmodel_t *)curmodel->mdl->extradata;
				maliasmesh_t	*mesh = &paliashdr->meshes[curmesh];
				num_skins = mesh->num_skins;
				num_tris = mesh->num_tris;
				num_meshes = paliashdr->num_meshes;
			}
			Cvar_ForceSet("ed_model_num_skins", va("%i", num_skins));

			Cvar_ForceSet("ed_model_num_frames", va("%i", curmodel->mdl->numframes));

			Cvar_ForceSet("ed_model_frame_start", va("%i", curmodel->frame));
			Cvar_ForceSet("ed_model_frame_end", va("%i", curmodel->frame_end));

			Cvar_ForceSet("ed_model_origin_x", va("%8.2f", curmodel->origin[0]));
			Cvar_ForceSet("ed_model_origin_y", va("%8.2f", curmodel->origin[1]));
			Cvar_ForceSet("ed_model_origin_z", va("%8.2f", curmodel->origin[2]));

			Cvar_ForceSet("ed_model_angle_a", va("%7.2f", curmodel->angles[0]));
			Cvar_ForceSet("ed_model_angle_b", va("%7.2f", curmodel->angles[1]));
			Cvar_ForceSet("ed_model_angle_c", va("%7.2f", curmodel->angles[2]));

			Cvar_ForceSet("ed_model_label", curmodel->label);

			if(curmodel->animframe)
				Cvar_ForceSet("ed_model_animate", "1");
			else
				Cvar_ForceSet("ed_model_animate", "0");

			Cvar_ForceSet("ed_model_noise", curmodel->sound);

			Cvar_ForceSet("ed_model_tri", va("%i", curtri));
			Cvar_ForceSet("ed_model_mesh", va("%i", curmesh));

			Cvar_ForceSet("ed_model_numtris", va("%i", curmodel->mdl->max_tris));
			Cvar_ForceSet("ed_model_nummeshes", va("%i", curmodel->mdl->max_meshes));

			if (curmodel->mdl->invert)
				Cvar_ForceSet("ed_model_invert", "1");
			else
				Cvar_ForceSet("ed_model_invert", "0");

			Cvar_ForceSet("ed_model_scale", va("%f", curmodel->mdl->scale));
		}
		else
			Cvar_ForceSet("ed_model","0");
	}
}


void IN_MLookDown () { mlooking = true; }


void IN_MLookUp ()
{
	mlooking = false;
	if (!freelook->value && lookspring->value)
		IN_CenterView ();
}


void IN_StartupMouse ()
{
	cvar_t		*cv;

	cv = Cvar_Get ("in_initmouse", "1", CVAR_NOSET);
	if ( !cv->value ) 
		return; 

	mouseinitialized = true;
	mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0);
	mouse_buttons = 3;
}


/* willow:
	Задействовать функцию для выбора мышек.
*/
/*
bool InitRawDevices()
{
	PRAWINPUTDEVICELIST g_pRawInputDeviceList;
	// Find the Raw Devices
	UINT nDevices;

	Com_Printf("Init Raw Devices:\n");

	// Get Number of devices attached
	if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0)
	{
		Com_Printf("^1No RawInput devices attached\n");
		return false;
	} else
		Com_Printf("System reporting about %i RAW input devices\n",nDevices);

	// Create list large enough to hold all RAWINPUTDEVICE structs
	if ((g_pRawInputDeviceList = (PRAWINPUTDEVICELIST)Z_Malloc(sizeof(RAWINPUTDEVICELIST) * nDevices), false) == NULL)
	{
		Com_Printf("^1Error mallocing RAWINPUTDEVICELIST\n");
		return false;
	}
	// Now get the data on the attached devices
	if (GetRawInputDeviceList(g_pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST)) == -1)
	{
		Com_Printf("^1Error from GetRawInputDeviceList\n");
		Z_Free(g_pRawInputDeviceList);
		return false;
	}

	PRAWINPUTDEVICE g_pRawInputDevices = (PRAWINPUTDEVICE)Z_Malloc( nDevices * sizeof(RAWINPUTDEVICE, true) );
//	g_nUsagePage1Usage8Devices = 0;

	// Look through device list for RIM_TYPEHID devices with UsagePage == 1, Usage == 8
	for(UINT i=0; i<nDevices; i++)
	{
		if (g_pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE)
		{
			UINT nchars = 300;
			TCHAR deviceName[300];
			if (GetRawInputDeviceInfo(g_pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, deviceName, &nchars) >= 0)
				Com_DPrintf("Device[%d]:\n handle=0x%x\n name = %s\n\n", i, g_pRawInputDeviceList[i].hDevice, deviceName);
			RID_DEVICE_INFO dinfo;
			UINT sizeofdinfo = sizeof(dinfo);
			dinfo.cbSize = sizeofdinfo;
			if (GetRawInputDeviceInfo(g_pRawInputDeviceList[i].hDevice, RIDI_DEVICEINFO, &dinfo, &sizeofdinfo ) >= 0)
			{
				if (dinfo.dwType == RIM_TYPEMOUSE)
				{
					RID_DEVICE_INFO_MOUSE *pMouseInfo = &dinfo.mouse;
					Com_DPrintf("ID = 0x%x\n", pMouseInfo->dwId);
					Com_DPrintf("Number of buttons = %i\n", pMouseInfo->dwNumberOfButtons);
					Com_DPrintf("Sample Rate = %i\n", pMouseInfo->dwSampleRate);
					Com_DPrintf("Has Horizontal Wheel: %s\n", pMouseInfo->fHasHorizontalWheel ? "Yes" : "No");

///					g_pRawInputDevices[g_nUsagePage1Usage8Devices].usUsagePage = phidInfo->usUsagePage;
///					g_pRawInputDevices[g_nUsagePage1Usage8Devices].usUsage     = phidInfo->usUsage;
///					g_pRawInputDevices[g_nUsagePage1Usage8Devices].dwFlags     = 0;
///					g_pRawInputDevices[g_nUsagePage1Usage8Devices].hwndTarget  = NULL;
///					g_nUsagePage1Usage8Devices++;
				}
			}
		}
	}
	// Register for input from the devices in the list
///	if (RegisterRawInputDevices( g_pRawInputDevices, g_nUsagePage1Usage8Devices, sizeof(RAWINPUTDEVICE) ) == FALSE )
///	{
///		Com_Printf("Error calling RegisterRawInputDevices\n");
///		Z_Free(g_pRawInputDevices);
///		Z_Free(g_pRawInputDeviceList);
///		return false;
///	}

	Z_Free(g_pRawInputDevices);
	Z_Free(g_pRawInputDeviceList);
	return true;
}
*/

void IN_Init ()
{
	// joystick variables
	in_joystick				= Cvar_Get ("in_joystick",				"0",		CVAR_ARCHIVE);
	joy_name				= Cvar_Get ("joy_name",					"joystick",	0);
	joy_advanced			= Cvar_Get ("joy_advanced",				"0",		0);
	joy_advaxisx			= Cvar_Get ("joy_advaxisx",				"0",		0);
	joy_advaxisy			= Cvar_Get ("joy_advaxisy",				"0",		0);
	joy_advaxisz			= Cvar_Get ("joy_advaxisz",				"0",		0);
	joy_advaxisr			= Cvar_Get ("joy_advaxisr",				"0",		0);
	joy_advaxisu			= Cvar_Get ("joy_advaxisu",				"0",		0);
	joy_advaxisv			= Cvar_Get ("joy_advaxisv",				"0",		0);
	joy_forwardthreshold	= Cvar_Get ("joy_forwardthreshold",		"0.15",		0);
	joy_sidethreshold		= Cvar_Get ("joy_sidethreshold",		"0.15",		0);
	joy_upthreshold  		= Cvar_Get ("joy_upthreshold",			"0.15",		0);
	joy_pitchthreshold		= Cvar_Get ("joy_pitchthreshold",		"0.15",		0);
	joy_yawthreshold		= Cvar_Get ("joy_yawthreshold",			"0.15",		0);
	joy_forwardsensitivity	= Cvar_Get ("joy_forwardsensitivity",	"-1",		0);
	joy_sidesensitivity		= Cvar_Get ("joy_sidesensitivity",		"-1",		0);
	joy_upsensitivity		= Cvar_Get ("joy_upsensitivity",		"-1",		0);
	joy_pitchsensitivity	= Cvar_Get ("joy_pitchsensitivity",		"1",		0);
	joy_yawsensitivity		= Cvar_Get ("joy_yawsensitivity",		"-1",		0);

	// centering
	v_centermove			= Cvar_Get ("v_centermove",				"0.15",		0);
	v_centerspeed			= Cvar_Get ("v_centerspeed",			"500",		0);

	Cmd_AddCommand ("+mlook", IN_MLookDown);
	Cmd_AddCommand ("-mlook", IN_MLookUp);

	Cmd_AddCommand ("joy_advancedupdate", Joy_AdvancedUpdate_f);

	if (old_mouse_support)
	{
		Com_Printf ("Selected OLD mouse code path\n");

		// mouse variables
		in_mouse				= Cvar_Get ("in_mouse",					"1",		CVAR_ARCHIVE);
		m_filter				= Cvar_Get ("m_filter",					"0",		CVAR_ARCHIVE);
		m_accel					= Cvar_Get( "m_accel",					"0",		CVAR_ARCHIVE);
		m_xpfix					= Cvar_Get( "m_xpfix",					"0",		CVAR_ARCHIVE);

		IN_StartupMouse ();
	}
	else
	{
		Com_Printf ("Selected NEW mouse code path\n");

		// mouse variables
		m_inversion				= Cvar_Get ("m_inversion",				"0",		CVAR_ARCHIVE);

///		InitRawDevices();
	}

	IN_StartupJoystick ();
}


void V_Viewpos_f ()
{
	Com_Printf ("Origin: %i %i %i\n", int(cl.refdef.vieworg[0]), int(cl.refdef.vieworg[1]), int(cl.refdef.vieworg[2]));
	Com_Printf ("Angles: %i %i %i\n", int(cl.refdef.viewangles[0]), int(cl.refdef.viewangles[1]), int(cl.refdef.viewangles[2]));
}


void V_Init ()
{
	Cmd_AddCommand ("gun_next", V_Gun_Next_f);
	Cmd_AddCommand ("gun_prev", V_Gun_Prev_f);
	Cmd_AddCommand ("gun_model", V_Gun_Model_f);
	Cmd_AddCommand ("viewpos", V_Viewpos_f);
	crosshair = Cvar_Get ("crosshair", "0", CVAR_ARCHIVE);
	scr_crosshair_scale = Cvar_Get ("scr_crosshair_scale", "1", CVAR_ARCHIVE);
}


void M_PushMenu ( void (*draw) (void), int(*key) (int k) )
{
	int		i;

	if (Cvar_VariableValue ("maxclients") == 1 && Com_ServerState ())
		Cvar_Set ("paused", "1");

	// if this menu is already present, drop back to that level
	// to avoid stacking menus by hotkeys
	for (i=0 ; i<m_menudepth ; i++)
		if (m_layers[i+1].draw == draw && m_layers[i+1].key == key)
		{
			m_menudepth = i;
			break;
		}

	m_drawfunc = draw;
	m_keyfunc = key;

	if (i == m_menudepth)
	{
		m_menudepth++;
		if (m_menudepth >= MAX_MENU_DEPTH)
			Com_Error (ERR_FATAL, "M_PushMenu: MAX_MENU_DEPTH");
		m_layers[m_menudepth].draw = m_drawfunc;
		m_layers[m_menudepth].key = m_keyfunc;
	}

	m_entersound = true;

	cls.key_dest = key_menu;
}


/*
=============
M_DrawCursor

Draws an animating cursor with the point at
x,y.  The pic will extend to the left of x,
and both above and below y.
=============
*/
void M_DrawCursor( int x, int y )
{
	static		bool cached;
	char		cursorname[80];
	refdef_t	refdef;
	entity_t	entity;
	char		scratch[MAX_QPATH];
	int			old, old2;

	Memset(&entity, 0, sizeof(entity));

	Com_sprintf (scratch, sizeof(scratch), "models/hud/p_quad.md2");
	entity.model = R_RegisterModel(scratch, 1, false);
	if (entity.model)
	{
		Com_sprintf(scratch, sizeof(scratch), "models/hud/p_quad");
		entity.skin = R_RegisterSkin(scratch);
		entity.bump = R_RegisterBump(scratch, entity.skin, entity.model);
		entity.light = R_RegisterLight(scratch);

		Memset(&refdef, 0, sizeof(refdef));
		currentshadowlight = &m_light;
		Memset(currentshadowlight, 0, sizeof(shadowlight_t));

		int x0 = x-11;
		int y0 = y-3;
		int x1 = x0 + 42;
		int y1 = y0 + 42;
		Adjust320to640(&x0, &y0, &x1, &y1);
		refdef.x = x0;
		refdef.y = y0;
		refdef.width = x1 - x0;
		refdef.height = y1 - y0;

		refdef.viewangles[0] = 23;
		refdef.fov_x = 45;
		refdef.fov_y = CalcFov(refdef.fov_x, refdef.width, refdef.height);
		refdef.time = cls.realtime * 0.004;
		refdef.areabits = 0;
		refdef.num_entities = 1;
		refdef.entities = &entity;
		refdef.rdflags = RDF_NOWORLDMODEL | RDF_NOCLEAR;

		entity.flags = RF_NOCASTSHADOW;
		entity.angles[1] = anglemod(-refdef.time*48);
		if ( entity.angles[1] > 360 )
			entity.angles[1] -= 360;

		/// Light setup
		VectorSet(currentshadowlight->origin, -100, 0, 100);
		VectorSet(currentshadowlight->styled_color, 1,1,1);
		currentshadowlight->radiuses[0] = currentshadowlight->radiuses[1] = currentshadowlight->radiuses[2] = currentshadowlight->radius = 2000;
		currentshadowlight->sphere = true;
		VectorSet(currentshadowlight->mins, -256, -256, -256);
		VectorSet(currentshadowlight->maxs, 256, 256, 256);
		VectorSet(currentshadowlight->mins_cone, -256, -256, -256);
		VectorSet(currentshadowlight->maxs_cone, 256, 256, 256);

		R_SaveRenderState();
		r_worldmodel = NULL;
		old = r_framecount;
		old2 = occ_framecount;

		VectorSet(refdef.vieworg, -entity.model->radius*1.5, 0, entity.model->radius*1.5*0.39);
		VectorNegate(entity.model->center, entity.origin);

		R_RenderView ( &refdef, false );

		r_framecount = old;
		occ_framecount = old2;

		R_RestoreRenderState();

//		вернуть 2D перспективу...
		R_SetGL2D();
	}
	else
	{
		if ( !cached )
		{
			int i;
			for ( i = 0; i < NUM_CURSOR_FRAMES; i++ )
			{
				Com_sprintf( cursorname, sizeof( cursorname ), "m_cursor%d.tga", i );
				Draw_FindPic( cursorname );
			}
			cached = true;
		}
		Com_sprintf( cursorname, sizeof(cursorname), "m_cursor%d.tga", (int)(cls.realtime / 100)%NUM_CURSOR_FRAMES );
		Draw_Pic( x, y, cursorname );
	}
}


void M_Main_Draw ()
{
	int i;
	int w, h;
	int ystart;
	int	xoffset;
	int widest = -1;
	int totalheight = 0;
	char litname[80];
	char *names[] =
	{
		"m_main_game",
		"m_main_multiplayer",
		"m_main_demos",
		"m_main_options",
		"m_main_video",
		"m_main_quit",
		0
	};

	for ( i = 0; names[i] != 0; i++ )
	{
		strcpy( litname, names[i] );
		strcat( litname, ".tga" );
		Draw_GetPicSize( &w, &h, litname );

		if ( w > widest )
			widest = w;
		totalheight += ( h + 12 );
	}

	ystart = ( viddef.height / 2 - 110 );
	xoffset = ( viddef.width - widest + 70 ) / 2;

	for ( i = 0; names[i] != 0; i++ )
	{
		if ( i != m_main_cursor )
		{
			strcpy( litname, names[i] );
			strcat( litname, ".tga" );
			Draw_Pic( xoffset, ystart + i * 34 + 5, litname );
		}
	}
	strcpy( litname, names[m_main_cursor] );
	strcat( litname, "_sel.tga" );
	Draw_Pic( xoffset, ystart + m_main_cursor * 34 + 5, litname );

	M_DrawCursor( xoffset - 25, ystart + m_main_cursor * 34 + 8 );

	Draw_GetPicSize( &w, &h, "m_main_plaque.tga" );
	Draw_Pic( xoffset - 30 - w, ystart, "m_main_plaque.tga" );

	Draw_Pic( xoffset - 30 - w, ystart + h + 5, "m_main_logo.tga" );
}


void M_PopMenu ()
{
	S_StartLocalSound(cl_sfx_menu_out_sound);
	if (m_menudepth < 1)
		Com_Error (ERR_FATAL, "M_PopMenu: depth < 1");
	m_menudepth--;

	if (m_menudepth)
	{
		m_drawfunc = m_layers[m_menudepth].draw;
		m_keyfunc = m_layers[m_menudepth].key;
	}
	else
		M_ForceMenuOff ();
}


void StartGame()
{
	// disable updates and start the cinematic going
	cl.servercount = -1;
	M_ForceMenuOff ();
	Cvar_SetValue( "deathmatch", 0 );
	Cvar_SetValue( "coop", 0 );

	Cbuf_AddText ("loading ; killserver ; wait ; newgame\n");
	cls.key_dest = key_game;
}


void EasyGameFunc( void *data )
{
	Cvar_ForceSet( "skill", "0" );
	StartGame();
}


void MediumGameFunc( void *data )
{
	Cvar_ForceSet( "skill", "1" );
	StartGame();
}


void HardGameFunc( void *data )
{
	Cvar_ForceSet( "skill", "2" );
	StartGame();
}


void NMGameFunc( void *data )
{
	Cvar_ForceSet( "skill", "3" );
	StartGame();
}


void QuickLoadGameCallback( void *self )
{
	menuaction_s *a = ( menuaction_s * ) self;

	if ( m_savevalid[ a->generic.localdata[0] ] )
		Cbuf_AddText (va("load quick%i\n",  a->generic.localdata[0] ) );
	M_ForceMenuOff ();
}


void LoadGameCallback( void *self )
{
	menuaction_s *a = ( menuaction_s * ) self;

	if ( m_savevalid[ a->generic.localdata[0] ] )
		Cbuf_AddText (va("load save%i\n",  a->generic.localdata[0] ) );
	M_ForceMenuOff ();
}


void Create_QuickSavestrings ()
{
	int		i, j;
	FILE	*f;
	char	name[MAX_OSPATH];
	char	*savdir;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	cvar_t *com_quickSaveSlot = Cvar_Get ("com_quickSaveSlot", "-1", CVAR_NOSET | CVAR_ARCHIVE );

	for (i=0 ; i<MAX_SAVEGAMES ; i++)
	{
		Com_sprintf (name, sizeof(name), "%s/%s/quick%i/server.ssv", FS_Gamedir(), savdir, i);
		f = FS_Fopen(name, "rb");
		if (!f)
		{
			strcpy (m_savestrings[i], "<EMPTY>");
			strcpy (m_savemapnames[i], "<EMPTY>");
			m_savevalid[i] = false;
		}
		else
		{
			FS_Read (m_savestrings[i], sizeof(m_savestrings[i]), f);
			FS_Read (m_savemapnames[i], sizeof(m_savemapnames[i]), f);
			for (j=0; j<sizeof(m_savemapnames[i]); j++)
			{
				if (m_savemapnames[i][j]=='$' || !m_savemapnames[i][j])
				{
					m_savemapnames[i][j] = 0;
					break;
				}
			}
			fclose (f);
			m_savevalid[i] = true;
///			m_saveshots[i] = i;
		}
	}
}


void Create_Savestrings ()
{
	int		i, j;
	FILE	*f;
	char	name[MAX_OSPATH];
	char	*savdir;

	if (net_compatibility->value)
		savdir = "save";
	else
		savdir = "save.q2b";

	for (i=0 ; i<MAX_SAVEGAMES ; i++)
	{
		Com_sprintf (name, sizeof(name), "%s/%s/save%i/server.ssv", FS_Gamedir(), savdir, i);
		f = FS_Fopen(name, "rb");
		if (!f)
		{
			strcpy (m_savestrings[i], "<EMPTY>");
			strcpy (m_savemapnames[i], "<EMPTY>");
			m_savevalid[i] = false;
		}
		else
		{
			FS_Read (m_savestrings[i], sizeof(m_savestrings[i]), f);
			FS_Read (m_savemapnames[i], sizeof(m_savemapnames[i]), f);
			for (j=0; j<sizeof(m_savemapnames[i]); j++)
			{
				if (m_savemapnames[i][j]=='$' || !m_savemapnames[i][j])
				{
					m_savemapnames[i][j] = 0;
					break;
				}
			}
			fclose (f);
			m_savevalid[i] = true;
///			m_saveshots[i] = i;
		}
	}
}


void LoadQuickGame_MenuInit()
{
	int i, ii;

	s_loadquickgame_menu.x = viddef.width / 2 - 120;
	s_loadquickgame_menu.y = viddef.height / 2 - 58;
	s_loadquickgame_menu.nitems = 0;

	Create_QuickSavestrings();

	cvar_t *com_quickSaveSlot = Cvar_Get ("com_quickSaveSlot", "-1", CVAR_NOSET | CVAR_ARCHIVE );

	for ( ii = 0; ii < MAX_SAVEGAMES; ii++ )
		if (m_savevalid[ii])
			break;

	if (ii < MAX_SAVEGAMES)
	{
		if (com_quickSaveSlot->value == -1)				// такое возможно если стёрли .cfg (com_quickSaveSlot стал -1), но папки есть
			Cvar_ForceSet("com_quickSaveSlot", "0");	// тогда ставим "0" чтоб можно было хоть что то загрузить
	}
	else
		Cvar_ForceSet("com_quickSaveSlot", "-1");		// такое возможно если папок нет.

	for ( ii = 0; ii < MAX_SAVEGAMES; ii++ )
	{
		// remap
		if (com_quickSaveSlot->value == -1)
			i = -ii;
		else
			i = com_quickSaveSlot->value - ii;
		if (i < 0)
			i = MAX_SAVEGAMES + i;

		s_load_quickgame_actions[ii].generic.name			= m_savemapnames[i];
		s_load_quickgame_actions[ii].generic.flags			= QMF_LEFT_JUSTIFY;
		s_load_quickgame_actions[ii].generic.localdata[0]	= i;
		s_load_quickgame_actions[ii].generic.callback		= QuickLoadGameCallback;
		s_load_quickgame_actions[ii].generic.statusbarfunc = DrawQuickSavedShot;

		s_load_quickgame_actions[ii].generic.x = 0;
		s_load_quickgame_actions[ii].generic.y = ( ii ) * 10;

		s_load_quickgame_actions[ii].generic.type = MTYPE_ACTION;

		Menu_AddItem( &s_loadquickgame_menu, &s_load_quickgame_actions[ii] );
	}
}

void LoadGame_MenuInit()
{
	int i;

	s_loadgame_menu.x = viddef.width / 2 - 120;
	s_loadgame_menu.y = viddef.height / 2 - 58;
	s_loadgame_menu.nitems = 0;

	Create_Savestrings();

	for ( i = 0; i < MAX_SAVEGAMES; i++ )
	{
		if (!i)
			s_loadgame_actions[i].generic.name			= m_savestrings[i];
		else
			s_loadgame_actions[i].generic.name			= m_savemapnames[i];
		s_loadgame_actions[i].generic.flags			= QMF_LEFT_JUSTIFY;
		s_loadgame_actions[i].generic.localdata[0]	= i;
		s_loadgame_actions[i].generic.callback		= LoadGameCallback;
		s_loadgame_actions[i].generic.statusbarfunc = DrawSavedShot;

		s_loadgame_actions[i].generic.x = 0;
		s_loadgame_actions[i].generic.y = ( i ) * 10;
		if (i>0)	// separate from autosave
			s_loadgame_actions[i].generic.y += 10;

		s_loadgame_actions[i].generic.type = MTYPE_ACTION;

		Menu_AddItem( &s_loadgame_menu, &s_loadgame_actions[i] );
	}
}


void M_Banner( char *name )
{
	int w, h;

	Draw_GetPicSize (&w, &h, name );
	Draw_Pic( viddef.width / 2 - w / 2, viddef.height / 2 - 110, name );
}


void LoadQuickGame_MenuDraw()
{
	M_Banner( "m_banner_load_game.tga" );
	Menu_Draw( &s_loadquickgame_menu );
}

void LoadGame_MenuDraw()
{
	M_Banner( "m_banner_load_game.tga" );
	Menu_Draw( &s_loadgame_menu );
}


int LoadQuickGame_MenuKey( int key )
{
	return Default_MenuKey( &s_loadquickgame_menu, key );
}


int LoadGame_MenuKey( int key )
{
	if ( key == K_ESCAPE || key == K_ENTER )
	{
		s_savegame_menu.cursor = s_loadgame_menu.cursor - 1;
		if ( s_savegame_menu.cursor < 0 )
			s_savegame_menu.cursor = 0;
	}
	return Default_MenuKey( &s_loadgame_menu, key );
}


void M_Menu_LoadQuickGame_f ()
{
	LoadQuickGame_MenuInit();
	M_PushMenu( LoadQuickGame_MenuDraw, LoadQuickGame_MenuKey );
}

void M_Menu_LoadGame_f ()
{
	LoadGame_MenuInit();
	M_PushMenu( LoadGame_MenuDraw, LoadGame_MenuKey );
}


int Demos_MenuKey( int key )
{
	return Default_MenuKey( &s_demos_menu, key );
}


void Demos_MenuDraw()
{
	M_Banner( "m_banner_demos.tga" );
	Menu_Draw( &s_demos_menu );
}


void DemosCallback( void *self )
{
	menuaction_s *a = ( menuaction_s * ) self;

	if ( m_demosvalid[ a->generic.localdata[0] ] && s_demos_menu.cursor < MAX_DEMOS )
		Cbuf_AddText (va("demo %s\n", m_demos[s_demos_menu.cursor] ) );
	M_ForceMenuOff ();
}


void Create_Demosstrings (int mask)
{
	int				ii = 0;
	searchpath_t	*search;
	pack_t			*pak;
	int				slot;
	int				dirCount, i;
	char			*dirFiles[MAX_DEMOS];
	char			*str_dm, *str_dm_;
	bool			stop = false;
	bool			exit = false;

	// init
	Memset(m_demos, 0, sizeof(char)*MAX_DEMOS*(MAX_QPATH+1));
	Memset(m_demosvalid, 0, sizeof(bool)*MAX_DEMOS);
	Memset(m_demos_short, 0, sizeof(char)*MAX_DEMOS*(MAX_DEMO_NAMELEN+1));

	if (net_compatibility->value)
	{
		str_dm = ".dm2";
		str_dm_ = "*.dm2";
	}
	else
	{
		str_dm = ".dem";
		str_dm_ = "*.dem";
	}

	if (mask&1)
	{
		for (search = fs_searchpaths ; search ; search = search->next)
		{
			// is the element a pak file?
			if (search->pack && !search->disabled)
			{
				// look through all the pak file elements
				pak = search->pack;
				if (pak->isPK2)
				{
					unsigned	hash = Com_HashKey(pak->filename);
					slot = -1;
					for(i=0; i<MAX_PAKS; i++)
					{
						if(ZipCache[i].uf)
						{
							if (ZipCache[i].hash == hash)
							{
								if(!b_stricmp(ZipCache[i].pak_name, pak->filename))
								{
									Memcpy(&pf, &ZipCache[i], sizeof(zipfile_t));		// Нашли уже открытый ZIP и загрузили его описатель в pf
									goto clc;
								}
							}
						}
						else
						{
							if(slot == -1)
							{
								slot = i;	// Ищем в slot первый свободный номер в кэше
								break;
							}
						}
					}
					strcpy(&ZipCache[slot].pak_name[0], pak->filename);	// Если не нашли zip в кэше, откроем его...
					if (!PackFileOpen (&ZipCache[slot]))
						Com_Error(ERR_FATAL, "Error opening pk2-file: %s", pak->filename);
					Memcpy(&pf, &ZipCache[slot], sizeof(zipfile_t));
					goto clc2;
clc:				slot = i;
clc2:				for(i=0; i<pf.gi.number_entry; i++)
					{
						if(pf.fi[i].size)
						{
							char	nam[256];
							Memcpy(nam, pf.fi[i].name, 256);
							if(!Q_strncasecmp(nam, "demos/", 6))
							{
								if(!Q_strcasecmp(nam+strlen(nam)-4, str_dm))
								{
									if (ii<MAX_DEMOS && strlen(nam)-10<=MAX_QPATH)
									{
										strncpy (m_demos[ii], nam+6, strlen(nam)-10);
										if (strlen(nam)-10<=MAX_DEMO_NAMELEN)
											strncpy (m_demos_short[ii], nam+6, strlen(nam)-10);
										else
										{
											strncpy (m_demos_short[ii], nam+6, MAX_DEMO_NAMELEN-1);
											m_demos_short[ii][MAX_DEMO_NAMELEN-1]='*';
										}
										m_demosvalid[ii] = pak->filename;
										ii++;
									}
								}
							}
						}
					}
				}
				else
				{
					// look through all the pak file elements
					for (i=0 ; i<pak->numfiles ; i++)
					{
						char	nam[256];
						Memcpy(nam, pak->files[i].name, 256);
						if(!Q_strncasecmp(nam, "demos/", 6))
						{
							if(!Q_strcasecmp(nam+strlen(nam)-4, str_dm))
							{
								if (ii<MAX_DEMOS && strlen(nam)-10<=MAX_QPATH)
								{
									strncpy (m_demos[ii], nam+6, strlen(nam)-10);
									if (strlen(nam)-10<=MAX_DEMO_NAMELEN)
										strncpy (m_demos_short[ii], nam+6, strlen(nam)-10);
									else
									{
										strncpy (m_demos_short[ii], nam+6, MAX_DEMO_NAMELEN-1);
										m_demos_short[ii][MAX_DEMO_NAMELEN-1]='*';
									}
									m_demosvalid[ii] = pak->filename;
									ii++;
								}
							}
						}
					}
				}
			}
		}
	}

	if (mask&2)
	{
loop:	if (stop == true)
		{
base:		dirCount = Sys_FindFiles(va("./%s/demos", BASEDIRNAME), str_dm_, dirFiles, MAX_DEMOS, true, false);
			exit = true;
		}
		else
		{
			stop = true;
			if (strcmp(fs_gamedir+2, BASEDIRNAME))	// если работает mod:
				dirCount = Sys_FindFiles(va("%s/demos", fs_gamedir), str_dm_, dirFiles, MAX_DEMOS, true, false);
			else
				goto base;
		}

		for (i = 0; i < dirCount; i++)
		{
			char	*name = dirFiles[i];
			while (*name!=0)
			{
				if(*name=='/' || *name=='\\')
					goto good;
				name++;
			}
			goto stop;
good:		name++;
			while (*name!=0)
			{
				if(*name=='/' || *name=='\\')
					goto good2;
				name++;
			}
			goto stop;
good2:		name++;

			if (ii<MAX_DEMOS && strlen(name)-10<=MAX_QPATH)
			{
				strncpy (m_demos[ii], name+6, strlen(name)-10);
				if (strlen(name)-10<=MAX_DEMO_NAMELEN)
					strncpy (m_demos_short[ii], name+6, strlen(name)-10);
				else
				{
					strncpy (m_demos_short[ii], name+6, MAX_DEMO_NAMELEN-1);
					m_demos_short[ii][MAX_DEMO_NAMELEN-1]='*';
				}
				m_demosvalid[ii] = "DEMOS folder";
				ii++;
			}
stop:		Z_Free(dirFiles[i]);
		}

		if (!exit)
			goto loop;
	}

	for (; ii<MAX_DEMOS; ii++)
		m_demosvalid[ii] = NULL;
}


void Demos_MenuInit()
{
	int i, j, t = 0;

	s_demos_menu.x = viddef.width / 2 - 132;
	s_demos_menu.y = viddef.height / 2 - 73;
	s_demos_menu.nitems = 0;

	Create_Demosstrings(3);

	for (j=0; j<2; j++)
	{
		for ( i = 0; i < MAX_DEMOS/2; i++ )
		{
			if (!m_demosvalid[t])
				return;

			s_demos_actions[t].generic.name			= m_demos_short[t];
			s_demos_actions[t].generic.flags		= QMF_LEFT_JUSTIFY;
			s_demos_actions[t].generic.localdata[0]	= t;
			s_demos_actions[t].generic.callback		= DemosCallback;
			s_demos_actions[t].generic.statusbar	= m_demosvalid[t];

			s_demos_actions[t].generic.x = j * 154;
			s_demos_actions[t].generic.y = i * 10;

			s_demos_actions[t].generic.type = MTYPE_ACTION;

			Menu_AddItem( &s_demos_menu, &s_demos_actions[t] );

			t++;
		}
	}
}


void M_Menu_Demos_f ()
{
	Demos_MenuInit();
	M_PushMenu( Demos_MenuDraw, Demos_MenuKey );
}


void SaveGame_MenuDraw()
{
	M_Banner( "m_banner_save_game.tga" );
	Menu_AdjustCursor( &s_savegame_menu, 1 );
	Menu_Draw( &s_savegame_menu );
}


void SaveGameCallback( void *self )
{
	menuaction_s *a = ( menuaction_s * ) self;

	Cbuf_AddText (va("save save%i\n", a->generic.localdata[0] ));
	M_ForceMenuOff ();
}

void SaveGame_MenuInit()
{
	int i;

	s_savegame_menu.x = viddef.width / 2 - 120;
	s_savegame_menu.y = viddef.height / 2 - 58;
	s_savegame_menu.nitems = 0;

	Create_Savestrings();

	// don't include the autosave slot
	for ( i = 0; i < MAX_SAVEGAMES-1; i++ )
	{
		s_savegame_actions[i].generic.name = m_savemapnames[i+1];
		s_savegame_actions[i].generic.localdata[0] = i+1;
		s_savegame_actions[i].generic.flags = QMF_LEFT_JUSTIFY;
		s_savegame_actions[i].generic.callback = SaveGameCallback;
		s_savegame_actions[i].generic.statusbarfunc = DrawSavedShot;

		s_savegame_actions[i].generic.x = 0;
		s_savegame_actions[i].generic.y = ( i ) * 10;

		s_savegame_actions[i].generic.type = MTYPE_ACTION;

		Menu_AddItem( &s_savegame_menu, &s_savegame_actions[i] );
	}
}


int SaveGame_MenuKey( int key )
{
	if ( key == K_ENTER || key == K_ESCAPE )
	{
		s_loadgame_menu.cursor = s_savegame_menu.cursor - 1;
		if ( s_loadgame_menu.cursor < 0 )
			s_loadgame_menu.cursor = 0;
	}
	return Default_MenuKey( &s_savegame_menu, key );
}


void M_Menu_SaveGame_f ()
{
	if (!Com_ServerState())
		return;		// not playing a game

	SaveGame_MenuInit();
	M_PushMenu( SaveGame_MenuDraw, SaveGame_MenuKey );
	Create_Savestrings ();
}



void LoadQuickGameFunc( void *unused )
{
	M_Menu_LoadQuickGame_f ();
}


void LoadGameFunc( void *unused )
{
	M_Menu_LoadGame_f ();
}


void SaveGameFunc( void *unused )
{
	M_Menu_SaveGame_f();
}


void M_Credits_MenuDraw()
{
	int i, y, x, sl;

	glEnable (GL_BLEND);
//	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	gl_TexEnv( GL_MODULATE );
	glDisable( GL_ALPHA_TEST );

	int centr = (viddef.height>>1);
	float one_over_centre = 1/(float)centr;

	// draw the credits
	for ( i = 0, y = viddef.height - ( ( cls.realtime - credits_start_time ) / 40.0F ); credits[i] && y < viddef.height; y += 18, i++ )
	{
		int bold, j, stringoffset = 0;

		if ( y <= -8 )
			continue;

		if ( credits[i][0] == '+' )
		{
			bold = 128;
			stringoffset = 1;
		}
		else
			bold = stringoffset = 0;

		GL_Color4f(1,1,1,(centr - abs(y - centr))*one_over_centre);

		sl = strlen(credits[i]);	/// PVS-Studio
		for ( j = 0; credits[i][j+stringoffset]; j++ )
		{
			x = ( viddef.width - (sl + stringoffset) * 16 ) / 2 + ( j + stringoffset ) * 16;
			Draw_Char16( x, y, credits[i][j+stringoffset] + bold );
		}
	}

	int w, h;					// Berserker: рисуем лого к "кредитам".
	#define LOGOOFFSET	32		// На сколько пикселов отстает Лого от "кредитов".
	Draw_GetPicSize (&w, &h, "m_main_logo.tga");
	x = (viddef.width - w) / 2;
	GL_Color4f(1,1,1,(centr - abs(y+LOGOOFFSET - centr))*one_over_centre);
	Draw_Pic16 (x, y+LOGOOFFSET, "m_main_logo.tga");

	if ( y < -h-LOGOOFFSET )
		credits_start_time = cls.realtime;

	glDisable (GL_BLEND);
	GL_Color3f(1,1,1);
	gl_TexEnv( GL_REPLACE );
	glEnable( GL_ALPHA_TEST );
}


void CreditsFunc( void *unused )
{
	M_Menu_Credits_f();
}


int Menu_TallySlots( menuframework_s *menu )
{
	return menu->nitems;
/*
	int i;
	int total = 0;

	for ( i = 0; i < menu->nitems; i++ )
	{
		if ( ( ( menucommon_s * ) menu->items[i] )->type == MTYPE_LIST )
		{
			int nitems = 0;
			const char **n = ( ( menulist_s * ) menu->items[i] )->itemnames;

			while (*n)
				nitems++, n++;

			total += nitems;
		}
		else
		{
			total++;
		}
	}

	return total;
*/
}


void Menu_AddItem( menuframework_s *menu, void *item )
{
	if ( menu->nitems == 0 )
		menu->nslots = 0;

	if ( menu->nitems < MAXMENUITEMS )
	{
		menu->items[menu->nitems] = item;
		( ( menucommon_s * ) menu->items[menu->nitems] )->parent = menu;
		menu->nitems++;
	}

	menu->nslots = Menu_TallySlots( menu );
}


void Menu_Center( menuframework_s *menu )
{
	int height;

	height = ( ( menucommon_s * ) menu->items[menu->nitems-1])->y;
	height += 10;

	menu->y = ( viddef.height - height ) / 2;
}

void Menu_FlyQBEStatusBar( void* unused )
{
	char string[96];

	if(s_flyqbe_slider.curvalue>0)
		Com_sprintf (string, sizeof(string), "probability of FlyQBE arrival %i%%", (int)s_flyqbe_slider.curvalue * 10);
	else
		Com_sprintf (string, sizeof(string), "no FlyQBE");

	Menu_DrawStatusBar(string);
}

void Menu_PredatorStatusBar( void* unused )
{
	char string[96];

	if(s_predator_slider.curvalue>0)
		Com_sprintf (string, sizeof(string), "probability of predator arrival %i%%", (int)s_predator_slider.curvalue * 10);
	else
		Com_sprintf (string, sizeof(string), "no invisible monsters");

	Menu_DrawStatusBar(string);
}

void Menu_MonsterRespawnStatusBar( void* unused )
{
	char string[96];

	if(s_monsterRespawn.curvalue>0)
		Com_sprintf (string, sizeof(string), "probability of monster respawn %i%%", (int)s_monsterRespawn.curvalue * 10);
	else
		Com_sprintf (string, sizeof(string), "no monster respawn");

	Menu_DrawStatusBar(string);
}

static void UpdateFlyQBEFunc(void *unused)
{
	Cvar_ForceSetValue("sv_flyqbe", s_flyqbe_slider.curvalue / 10);
}

static void UpdatePredatorFunc(void *unused)
{
	Cvar_ForceSetValue("sv_predator", s_predator_slider.curvalue / 10);
}

static void UpdateMonsterRespawnFunc(void *unused)
{
	Cvar_ForceSetValue("g_monsterRespawn", s_monsterRespawn.curvalue / 10);
}

void FootStepsFunc( void *unused )
{
	Cvar_SetValue( "cl_footsteps", s_footsteps.curvalue );
}

void KnockBackCallback( void* unused )
{
	Cvar_ForceSet( "sv_knockback", s_knockback_field.buffer );
	if (Cvar_VariableValue("sv_knockback") < 0)	Cvar_ForceSetValue("sv_knockback", 0);
	Menu_DrawStatusBar("explosion knock back");
}

void DistanceCallback( void* unused )
{
	char	str[MAX_OSPATH];

	Cvar_Set( "r_maxDistance", s_distance_field.buffer );

	if (r_maxDistance->value < 0)	Cvar_SetValue("r_maxDistance", 0);
	else if (r_maxDistance->value > 99999)	Cvar_SetValue("r_maxDistance", 99999);

	if (Cvar_VariableValue("r_maxDistance")<=0)
		Menu_DrawStatusBar("no fragments");
	else
	{
		Com_sprintf (str, sizeof(str), "Max visible distance for fragments");
		Menu_DrawStatusBar(str);
	}
}

void GibsCallback( void* unused )
{
	char	str[MAX_OSPATH];

	Cvar_ForceSet( "sv_gibs", s_gibs_field.buffer );
	if (Cvar_VariableValue("sv_gibs") < 0)	Cvar_ForceSetValue("sv_gibs", 0);
	if (!Cvar_VariableValue("sv_gibs"))
		Menu_DrawStatusBar("no gibs");
	else
	{
		Com_sprintf (str, sizeof(str), "Time of gibs life = %i seconds", (int)Cvar_VariableValue("sv_gibs"));
		Menu_DrawStatusBar(str);
	}
}

int Value2LifeTime(float val)
{
	if (!val)
		return 0;	// Very Quickly
	if (val <= 0.5)
		return 1;	// Quickly
	if (val <= 2)
		return 2;	// Normally
	if (val <= 5)
		return 3;	// Long
	if (val <= 20)
		return 4;	// Very long
	return 5;		// Eternally
}

float LifeTime2Value(int val)
{
	if (!val)
		return 0;	// Very Quickly
	if (val == 1)
		return 0.5;// Quickly
	if (val == 2)
		return 2;	// Normally
	if (val == 3)
		return 5;	// Long
	if (val == 4)
		return 20;	// Very long
	return 999999;	// Eternally
}

void BrassTimeScaleFunc( void* unused )
{
	Cvar_ForceSetValue("r_brassTimeScale", LifeTime2Value(s_r_brassTimeScale.curvalue));
}

void DebrisTimeScaleFunc( void* unused )
{
	Cvar_ForceSetValue("r_debrisTimeScale", LifeTime2Value(s_r_debrisTimeScale.curvalue));
}

void BloodLifeTimeFunc( void* unused )
{
	Cvar_ForceSetValue("r_bloodLifeTime", LifeTime2Value(s_bloodLifeTime.curvalue));
}

void BulletLifeTimeFunc( void* unused )
{
	Cvar_ForceSetValue("r_bulletMarkLifeTime", LifeTime2Value(s_bulletMarkLifeTime.curvalue));
}

void LaserLifeTimeFunc( void* unused )
{
	Cvar_ForceSetValue("r_laserMarkLifeTime", LifeTime2Value(s_laserMarkLifeTime.curvalue));
}

void ExplosionLifeTimeFunc( void* unused )
{
	Cvar_ForceSetValue("r_explosionMarkLifeTime", LifeTime2Value(s_explosionMarkLifeTime.curvalue));
}

void RailLifeTimeFunc( void* unused )
{
	Cvar_ForceSetValue("r_railMarkLifeTime", LifeTime2Value(s_railMarkLifeTime.curvalue));
}

void bfgLifeTimeFunc( void* unused )
{
	Cvar_ForceSetValue("r_bfgMarkLifeTime", LifeTime2Value(s_bfgMarkLifeTime.curvalue));
}

void FixOverflowFunc( void *unused )
{
	Cvar_SetValue( "net_fixoverflow", s_fixoverflow.curvalue );
}

void GameOptions_MenuInit()
{
	s_options_wphitacc.curvalue = Cvar_VariableValue("weaponHitAccuracy");
	if (s_options_wphitacc.curvalue < 0) s_options_wphitacc.curvalue = 0;
	else if (s_options_wphitacc.curvalue > 1) s_options_wphitacc.curvalue = 1;

	if (Cvar_VariableValue("sv_flyqbe") < 0)	Cvar_ForceSetValue("sv_flyqbe", 0);
	else if (Cvar_VariableValue("sv_flyqbe") > 1)	Cvar_ForceSetValue("sv_flyqbe", 1);

	if (Cvar_VariableValue("sv_predator") < 0)	Cvar_ForceSetValue("sv_predator", 0);
	else if (Cvar_VariableValue("sv_predator") > 1)	Cvar_ForceSetValue("sv_predator", 1);

	Cvar_Get ("g_monsterRespawn", "0", CVAR_ARCHIVE);
	if (Cvar_VariableValue("g_monsterRespawn") < 0)	Cvar_ForceSetValue("g_monsterRespawn", 0);
	else if (Cvar_VariableValue("g_monsterRespawn") > 1)	Cvar_ForceSetValue("g_monsterRespawn", 1);

	if (Cvar_VariableValue("cl_footsteps") < 0)
		Cvar_SetValue("cl_footsteps", 0);
	if (Cvar_VariableValue("cl_footsteps") > 0)
		Cvar_SetValue("cl_footsteps", 1);

	if (Cvar_VariableValue("net_fixoverflow") < 0)
		Cvar_SetValue("net_fixoverflow", 0);
	if (Cvar_VariableValue("net_fixoverflow") > 0)
		Cvar_SetValue("net_fixoverflow", 1);

	if (Cvar_VariableValue("sv_knockback") < 0)	Cvar_ForceSetValue("sv_knockback", 0);

	Cvar_Get ("sv_gibs", "10", 0);
	if (Cvar_VariableValue("sv_gibs") < 0)	Cvar_ForceSetValue("sv_gibs", 0);

	if (r_maxDistance->value < 0)	Cvar_SetValue("r_maxDistance", 0);
	else if (r_maxDistance->value > 99999)	Cvar_SetValue("r_maxDistance", 99999);

	s_gameoptions_menu.x = viddef.width * 0.50;
	s_gameoptions_menu.nitems = 0;

	s_flyqbe_slider.generic.type	= MTYPE_SLIDER;
	s_flyqbe_slider.generic.x = 0;
	s_flyqbe_slider.generic.y = 0;
	s_flyqbe_slider.generic.name = "FlyQBE";
	s_flyqbe_slider.generic.callback = UpdateFlyQBEFunc;
	s_flyqbe_slider.minvalue = 0;
	s_flyqbe_slider.maxvalue = 10;
	s_flyqbe_slider.curvalue = Cvar_VariableValue("sv_flyqbe") * 10;
	s_flyqbe_slider.generic.statusbarfunc = Menu_FlyQBEStatusBar;

	s_predator_slider.generic.type	= MTYPE_SLIDER;
	s_predator_slider.generic.x = 0;
	s_predator_slider.generic.y = 10;
	s_predator_slider.generic.name = "Predators";
	s_predator_slider.generic.callback = UpdatePredatorFunc;
	s_predator_slider.minvalue = 0;
	s_predator_slider.maxvalue = 10;
	s_predator_slider.curvalue = Cvar_VariableValue("sv_predator") * 10;
	s_predator_slider.generic.statusbarfunc = Menu_PredatorStatusBar;

	s_monsterRespawn.generic.type	= MTYPE_SLIDER;
	s_monsterRespawn.generic.x = 0;
	s_monsterRespawn.generic.y = 20;
	s_monsterRespawn.generic.name = "Monster respawn";
	s_monsterRespawn.generic.callback = UpdateMonsterRespawnFunc;
	s_monsterRespawn.minvalue = 0;
	s_monsterRespawn.maxvalue = 10;
	s_monsterRespawn.curvalue = Cvar_VariableValue("g_monsterRespawn") * 10;
	s_monsterRespawn.generic.statusbarfunc = Menu_MonsterRespawnStatusBar;

	s_fixoverflow.generic.type	= MTYPE_SPINCONTROL;
	s_fixoverflow.generic.x = 0;
	s_fixoverflow.generic.y = 30;
	s_fixoverflow.generic.name = "Net overflow fix";
	s_fixoverflow.curvalue = net_fixoverflow->value;
	s_fixoverflow.itemnames = EnableDisable_names;
	s_fixoverflow.generic.statusbar = "Avoid 'freezing' by using client models";
	s_fixoverflow.generic.callback = FixOverflowFunc;

	s_footsteps.generic.type	= MTYPE_SPINCONTROL;
	s_footsteps.generic.x = 0;
	s_footsteps.generic.y = 40;
	s_footsteps.generic.name = "foot steps";
	s_footsteps.curvalue = cl_footsteps->value;
	s_footsteps.itemnames = yesno_names;
	s_footsteps.generic.statusbar = "allow player to hear foot steps";
	s_footsteps.generic.callback = FootStepsFunc;

	s_knockback_title.generic.type	= MTYPE_SEPARATOR;
	s_knockback_title.generic.name	= "knock back";
	s_knockback_title.generic.x		= -16;
	s_knockback_title.generic.y		= 54;

	s_knockback_field.generic.type = MTYPE_FIELD;
	s_knockback_field.generic.name = 0;
	s_knockback_field.generic.flags = QMF_NUMBERSONLY;
	s_knockback_field.generic.x	= 0;
	s_knockback_field.generic.y	= 54;
	s_knockback_field.length = 5;
	s_knockback_field.visible_length = 5;
	s_knockback_field.generic.statusbarfunc = KnockBackCallback;
	strcpy( s_knockback_field.buffer, Cvar_VariableString("sv_knockback") );

	s_distance_title.generic.type	= MTYPE_SEPARATOR;
	s_distance_title.generic.name	= "Max distance";
	s_distance_title.generic.x		= -16;
	s_distance_title.generic.y		= 68;

	s_distance_field.generic.type = MTYPE_FIELD;
	s_distance_field.generic.name = 0;
	s_distance_field.generic.flags = QMF_NUMBERSONLY;
	s_distance_field.generic.x	= 0;
	s_distance_field.generic.y	= 68;
	s_distance_field.length = 5;
	s_distance_field.visible_length = 5;
	s_distance_field.generic.statusbarfunc = DistanceCallback;
	strcpy( s_distance_field.buffer, Cvar_VariableString("r_maxDistance") );

	s_gibs_title.generic.type	= MTYPE_SEPARATOR;
	s_gibs_title.generic.name	= "time of gibs life";
	s_gibs_title.generic.x		= -16;
	s_gibs_title.generic.y		= 68+14;

	s_gibs_field.generic.type = MTYPE_FIELD;
	s_gibs_field.generic.name = 0;
	s_gibs_field.generic.flags = QMF_NUMBERSONLY;
	s_gibs_field.generic.x	= 0;
	s_gibs_field.generic.y	= 68+14;
	s_gibs_field.length = 5;
	s_gibs_field.visible_length = 5;
	s_gibs_field.generic.statusbarfunc = GibsCallback;
	strcpy( s_gibs_field.buffer, Cvar_VariableString("sv_gibs") );

	s_r_brassTimeScale.generic.type	= MTYPE_SPINCONTROL;
	s_r_brassTimeScale.generic.x = 0;
	s_r_brassTimeScale.generic.y = 82+14;
	s_r_brassTimeScale.generic.name = "tiny fragments";
	s_r_brassTimeScale.curvalue = Value2LifeTime(r_brassTimeScale->value);
	s_r_brassTimeScale.itemnames = lifetime_names2;
	s_r_brassTimeScale.generic.callback = BrassTimeScaleFunc;
	s_r_brassTimeScale.generic.statusbar = "Time of life of brass shells, etc.";

	s_r_debrisTimeScale.generic.type	= MTYPE_SPINCONTROL;
	s_r_debrisTimeScale.generic.x = 0;
	s_r_debrisTimeScale.generic.y = 82+14+8;
	s_r_debrisTimeScale.generic.name = "little fragments";
	s_r_debrisTimeScale.curvalue = Value2LifeTime(r_debrisTimeScale->value);
	s_r_debrisTimeScale.itemnames = lifetime_names2;
	s_r_debrisTimeScale.generic.callback = DebrisTimeScaleFunc;
	s_r_debrisTimeScale.generic.statusbar = "Time of life of debrises, etc.";

	s_bloodLifeTime.generic.type	= MTYPE_SPINCONTROL;
	s_bloodLifeTime.generic.x = 0;
	s_bloodLifeTime.generic.y = 82+14+16;
	s_bloodLifeTime.generic.name = "blood marks";
	s_bloodLifeTime.curvalue = Value2LifeTime(r_bloodlifetime->value);
	s_bloodLifeTime.itemnames = lifetime_names;
	s_bloodLifeTime.generic.callback = BloodLifeTimeFunc;
	s_bloodLifeTime.generic.statusbar = "Time of life of blood marks";

	s_bulletMarkLifeTime.generic.type	= MTYPE_SPINCONTROL;
	s_bulletMarkLifeTime.generic.x = 0;
	s_bulletMarkLifeTime.generic.y = 90+14+16;
	s_bulletMarkLifeTime.generic.name = "bullet marks";
	s_bulletMarkLifeTime.curvalue = Value2LifeTime(r_bulletMarkLifeTime->value);
	s_bulletMarkLifeTime.itemnames = lifetime_names;
	s_bulletMarkLifeTime.generic.callback = BulletLifeTimeFunc;
	s_bulletMarkLifeTime.generic.statusbar = "Time of life of bullet marks";

	s_laserMarkLifeTime.generic.type	= MTYPE_SPINCONTROL;
	s_laserMarkLifeTime.generic.x = 0;
	s_laserMarkLifeTime.generic.y = 98+14+16;
	s_laserMarkLifeTime.generic.name = "laser marks";
	s_laserMarkLifeTime.curvalue = Value2LifeTime(r_laserMarkLifeTime->value);
	s_laserMarkLifeTime.itemnames = lifetime_names;
	s_laserMarkLifeTime.generic.callback = LaserLifeTimeFunc;
	s_laserMarkLifeTime.generic.statusbar = "Time of life of laser marks";

	s_explosionMarkLifeTime.generic.type	= MTYPE_SPINCONTROL;
	s_explosionMarkLifeTime.generic.x = 0;
	s_explosionMarkLifeTime.generic.y = 106+14+16;
	s_explosionMarkLifeTime.generic.name = "explosion marks";
	s_explosionMarkLifeTime.curvalue = Value2LifeTime(r_explosionMarkLifeTime->value);
	s_explosionMarkLifeTime.itemnames = lifetime_names;
	s_explosionMarkLifeTime.generic.callback = ExplosionLifeTimeFunc;
	s_explosionMarkLifeTime.generic.statusbar = "Time of life of explosion marks";

	s_railMarkLifeTime.generic.type	= MTYPE_SPINCONTROL;
	s_railMarkLifeTime.generic.x = 0;
	s_railMarkLifeTime.generic.y = 114+14+16;
	s_railMarkLifeTime.generic.name = "rail marks";
	s_railMarkLifeTime.curvalue = Value2LifeTime(r_railMarkLifeTime->value);
	s_railMarkLifeTime.itemnames = lifetime_names;
	s_railMarkLifeTime.generic.callback = RailLifeTimeFunc;
	s_railMarkLifeTime.generic.statusbar = "Time of life of rail marks";

	s_bfgMarkLifeTime.generic.type	= MTYPE_SPINCONTROL;
	s_bfgMarkLifeTime.generic.x = 0;
	s_bfgMarkLifeTime.generic.y = 122+14+16;
	s_bfgMarkLifeTime.generic.name = "BFG marks";
	s_bfgMarkLifeTime.curvalue = Value2LifeTime(r_bfgMarkLifeTime->value);
	s_bfgMarkLifeTime.itemnames = lifetime_names;
	s_bfgMarkLifeTime.generic.callback = bfgLifeTimeFunc;
	s_bfgMarkLifeTime.generic.statusbar = "Time of life of BFG marks";

	s_options_wphitacc.generic.type = MTYPE_SPINCONTROL;
	s_options_wphitacc.generic.x = 0;
	s_options_wphitacc.generic.y = 130 + 14 + 16;
	s_options_wphitacc.generic.name = "weapon hits style";
	s_options_wphitacc.generic.callback = ToggleWHAFunc;
	s_options_wphitacc.itemnames = wha_names;
	s_options_wphitacc.generic.statusbar = "Weapon hits accuracely";

	s_startserver_dmoptions_action.generic.type = MTYPE_ACTION;
	s_startserver_dmoptions_action.generic.name	= "Game flags";
	s_startserver_dmoptions_action.generic.flags= 0;
	s_startserver_dmoptions_action.generic.x	= 0;
	s_startserver_dmoptions_action.generic.y	= 140+14+16;
///	s_startserver_dmoptions_action.generic.statusbar = NULL;
	s_startserver_dmoptions_action.generic.callback = DMOptionsFunc;
	s_startserver_dmoptions_action.generic.statusbarfunc = NULL;

	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_flyqbe_slider );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_predator_slider );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_monsterRespawn );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_fixoverflow );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_footsteps );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_knockback_title );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_knockback_field );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_distance_title );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_distance_field );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_gibs_title );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_gibs_field );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_r_brassTimeScale );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_r_debrisTimeScale );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_bloodLifeTime );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_bulletMarkLifeTime );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_laserMarkLifeTime );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_explosionMarkLifeTime );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_railMarkLifeTime );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_bfgMarkLifeTime );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_options_wphitacc );
	Menu_AddItem( &s_gameoptions_menu, ( void * ) &s_startserver_dmoptions_action );

	Menu_Center( &s_gameoptions_menu );
	s_gameoptions_menu.y += 16;
}

void GameOptions_MenuDraw ()
{
	M_Banner( "m_banner_options.tga" );
	/*
	** move cursor to a reasonable starting position
	*/
	Menu_AdjustCursor( &s_gameoptions_menu, 1 );

	/*
	** draw the menu
	*/
	Menu_Draw( &s_gameoptions_menu );
}

int GameOptions_MenuKey( int key )
{
	return Default_MenuKey( &s_gameoptions_menu, key );
}

void M_GameOptions_f ()
{
	GameOptions_MenuInit();
	M_PushMenu( GameOptions_MenuDraw, GameOptions_MenuKey );
}


void GameOptionsFunc( void *unused )
{
	M_GameOptions_f();
}

void Game_MenuInit()
{
	s_game_menu.x = viddef.width * 0.50;
	s_game_menu.nitems = 0;

	s_easy_game_action.generic.type	= MTYPE_ACTION;
	s_easy_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_easy_game_action.generic.x		= 0;
	s_easy_game_action.generic.y		= 0;
	s_easy_game_action.generic.name	= "easy";
	s_easy_game_action.generic.callback = EasyGameFunc;

	s_medium_game_action.generic.type	= MTYPE_ACTION;
	s_medium_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_medium_game_action.generic.x		= 0;
	s_medium_game_action.generic.y		= 10;
	s_medium_game_action.generic.name	= "medium";
	s_medium_game_action.generic.callback = MediumGameFunc;

	s_hard_game_action.generic.type	= MTYPE_ACTION;
	s_hard_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_hard_game_action.generic.x		= 0;
	s_hard_game_action.generic.y		= 20;
	s_hard_game_action.generic.name	= "hard";
	s_hard_game_action.generic.callback = HardGameFunc;

	s_nm_game_action.generic.type	= MTYPE_ACTION;
	s_nm_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_nm_game_action.generic.x		= 0;
	s_nm_game_action.generic.y		= 30;
	s_nm_game_action.generic.name	= "nightmare";
	s_nm_game_action.generic.callback = NMGameFunc;

	s_blankline.generic.type = MTYPE_SEPARATOR;

	s_sm_game_action.generic.type	= MTYPE_ACTION;
	s_sm_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_sm_game_action.generic.x		= 0;
	s_sm_game_action.generic.y		= 50;
	s_sm_game_action.generic.name	= "select map";
	s_sm_game_action.generic.callback = SelectMapFunc;

	s_smod_game_action.generic.type	= MTYPE_ACTION;
	s_smod_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_smod_game_action.generic.x		= 0;
	s_smod_game_action.generic.y		= 60;
	s_smod_game_action.generic.name	= "select mod";
	s_smod_game_action.generic.callback = SelectModFunc;

	s_game_options_action.generic.type	= MTYPE_ACTION;
	s_game_options_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_game_options_action.generic.x		= 0;
	s_game_options_action.generic.y		= 70;
	s_game_options_action.generic.name	= "options";
	s_game_options_action.generic.callback = GameOptionsFunc;

	s_load_quickgame_action.generic.type	= MTYPE_ACTION;
	s_load_quickgame_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_load_quickgame_action.generic.x		= 0;
	s_load_quickgame_action.generic.y		= 90;
	s_load_quickgame_action.generic.name	= "quick save queue";
	s_load_quickgame_action.generic.callback = LoadQuickGameFunc;

	s_load_game_action.generic.type	= MTYPE_ACTION;
	s_load_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_load_game_action.generic.x		= 0;
	s_load_game_action.generic.y		= 100;
	s_load_game_action.generic.name	= "load game";
	s_load_game_action.generic.callback = LoadGameFunc;

	s_save_game_action.generic.type	= MTYPE_ACTION;
	s_save_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_save_game_action.generic.x		= 0;
	s_save_game_action.generic.y		= 110;
	s_save_game_action.generic.name	= "save game";
	s_save_game_action.generic.callback = SaveGameFunc;

	s_credits_action.generic.type	= MTYPE_ACTION;
	s_credits_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_credits_action.generic.x		= 0;
	s_credits_action.generic.y		= 130;
	s_credits_action.generic.name	= "credits";
	s_credits_action.generic.callback = CreditsFunc;

	Menu_AddItem( &s_game_menu, ( void * ) &s_easy_game_action );
	Menu_AddItem( &s_game_menu, ( void * ) &s_medium_game_action );
	Menu_AddItem( &s_game_menu, ( void * ) &s_hard_game_action );
	Menu_AddItem( &s_game_menu, ( void * ) &s_nm_game_action );
	Menu_AddItem( &s_game_menu, ( void * ) &s_blankline );
	Menu_AddItem( &s_game_menu, ( void * ) &s_sm_game_action );
	Menu_AddItem( &s_game_menu, ( void * ) &s_smod_game_action );
	Menu_AddItem( &s_game_menu, ( void * ) &s_game_options_action );
	Menu_AddItem( &s_game_menu, ( void * ) &s_load_quickgame_action );
	Menu_AddItem( &s_game_menu, ( void * ) &s_load_game_action );
	Menu_AddItem( &s_game_menu, ( void * ) &s_save_game_action );
	Menu_AddItem( &s_game_menu, ( void * ) &s_blankline );
	Menu_AddItem( &s_game_menu, ( void * ) &s_credits_action );

	Menu_Center( &s_game_menu );
}


void *Menu_ItemAtCursor( menuframework_s *m )
{
	if ( m->cursor < 0 || m->cursor >= m->nitems )
		return 0;

	return m->items[m->cursor];
}


/*
** Menu_AdjustCursor
**
** This function takes the given menu, the direction, and attempts
** to adjust the menu's cursor so that it's at the next available
** slot.
*/
void Menu_AdjustCursor( menuframework_s *m, int dir )
{
	menucommon_s *citem;

	/*
	** see if it's in a valid spot
	*/
	if ( m->cursor >= 0 && m->cursor < m->nitems )
	{
		if ( ( citem = (menucommon_s *) Menu_ItemAtCursor( m ) ) != 0 )
		{
			if ( citem->type != MTYPE_SEPARATOR )
				return;
		}
	}

	/*
	** it's not in a valid spot, so crawl in the direction indicated until we
	** find a valid spot
	*/
	if ( dir == 1 )
	{
		while ( 1 )
		{
			citem = (menucommon_s *) Menu_ItemAtCursor( m );
			if ( citem )
				if ( citem->type != MTYPE_SEPARATOR )
					break;
			m->cursor += dir;
			if ( m->cursor >= m->nitems )
				m->cursor = 0;
		}
	}
	else
	{
		while ( 1 )
		{
			citem = (menucommon_s *) Menu_ItemAtCursor( m );
			if ( citem )
				if ( citem->type != MTYPE_SEPARATOR )
					break;
			m->cursor += dir;
			if ( m->cursor < 0 )
				m->cursor = m->nitems - 1;
		}
	}
}


void Menu_DrawStringDark( int x, int y, const char *string )
{
	unsigned i, sl;

	sl = strlen(string);		/// PVS-Studio
	for ( i = 0; i < sl; i++ )
	{
		Draw_Char( ( x + i * 8 ), y, string[i] + 128 );
	}
}


void Menu_DrawStringR2L( int x, int y, const char *string )
{
	unsigned i, sl;

	sl = strlen(string);		/// PVS-Studio
	for ( i = 0; i < sl; i++ )
	{
		Draw_Char( ( x - i * 8 ), y, string[sl - i - 1] );
	}
}


void Menu_DrawStringR2LDark( int x, int y, const char *string )
{
	unsigned i, sl;

	sl = strlen(string);		/// PVS-Studio
	for ( i = 0; i < sl; i++ )
	{
		Draw_Char( ( x - i * 8 ), y, string[sl - i - 1] + 128 );
	}
}


void Field_Draw( menufield_s *f )
{
	int i;
	char tempbuffer[128]="";

	if ( f->generic.name )
		Menu_DrawStringR2LDark( f->generic.x + f->generic.parent->x + LCOLUMN_OFFSET, f->generic.y + f->generic.parent->y, f->generic.name );

	strncpy( tempbuffer, f->buffer + f->visible_offset, f->visible_length );

	Draw_Char( f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y - 4, 18 );
	Draw_Char( f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y + 4, 24 );

	Draw_Char( f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y - 4, 20 );
	Draw_Char( f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y + 4, 26 );

	for ( i = 0; i < f->visible_length; i++ )
	{
		Draw_Char( f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y - 4, 19 );
		Draw_Char( f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y + 4, 25 );
	}

	Menu_DrawString( f->generic.x + f->generic.parent->x + 24, f->generic.y + f->generic.parent->y, tempbuffer );

	if ( Menu_ItemAtCursor( f->generic.parent ) == f )
	{
		int offset;

		if ( f->visible_offset )
			offset = f->visible_length;
		else
			offset = f->cursor;

		if ( ( ( int ) ( Sys_Milliseconds() / 250 ) ) & 1 )
		{
			Draw_Char( f->generic.x + f->generic.parent->x + ( offset + 2 ) * 8 + 8,
					   f->generic.y + f->generic.parent->y,
					   11 );
		}
		else
		{
			Draw_Char( f->generic.x + f->generic.parent->x + ( offset + 2 ) * 8 + 8,
					   f->generic.y + f->generic.parent->y,
					   ' ' );
		}
	}
}


void Slider_Draw( menuslider_s *s )
{
	int	i;

	Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET,
		                s->generic.y + s->generic.parent->y, 
						s->generic.name );

	s->range = ( s->curvalue - s->minvalue ) / ( float ) ( s->maxvalue - s->minvalue );

	if ( s->range < 0)
		s->range = 0;
	if ( s->range > 1)
		s->range = 1;
	Draw_Char( s->generic.x + s->generic.parent->x + RCOLUMN_OFFSET, s->generic.y + s->generic.parent->y, 128);
	for ( i = 0; i < SLIDER_RANGE; i++ )
		Draw_Char( RCOLUMN_OFFSET + s->generic.x + i*8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 129);
	Draw_Char( RCOLUMN_OFFSET + s->generic.x + i*8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 130);
	Draw_Char( ( int ) ( 8 + RCOLUMN_OFFSET + s->generic.parent->x + s->generic.x + (SLIDER_RANGE-1)*8 * s->range ), s->generic.y + s->generic.parent->y, 131);
}

/*
void MenuList_Draw( menulist_s *l )
{
	const char **n;
	int y = 0;

	Menu_DrawStringR2LDark( l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y, l->generic.name );

	n = l->itemnames;

	Draw_Fill( l->generic.x - 112 + l->generic.parent->x, l->generic.parent->y + l->generic.y + l->curvalue*10 + 10, 128, 10, 222,222,222 );
	while ( *n )
	{
		Menu_DrawStringR2LDark( l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y + y + 10, *n );

		n++;
		y += 10;
	}
}
*/

void SpinControl_Draw( menulist_s *s )
{
	char buffer[100];

	if ( s->generic.name )
	{
		Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET, 
							s->generic.y + s->generic.parent->y, 
							s->generic.name );
	}
	if ( !strchr( s->itemnames[s->curvalue], '\n' ) )
	{
		Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->itemnames[s->curvalue] );
	}
	else
	{
		strcpy( buffer, s->itemnames[s->curvalue] );
		*strchr( buffer, '\n' ) = 0;
		Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, buffer );
		strcpy( buffer, strchr( s->itemnames[s->curvalue], '\n' ) + 1 );
		Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y + 10, buffer );
	}
}


void Action_Draw( menuaction_s *a )
{
	if ( a->generic.flags & QMF_LEFT_JUSTIFY )
	{
		if ( a->generic.flags & QMF_GRAYED )
			Menu_DrawStringDark( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
		else
			Menu_DrawString( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
	}
	else
	{
		if ( a->generic.flags & QMF_GRAYED )
			Menu_DrawStringR2LDark( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
		else
			Menu_DrawStringR2L( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
	}
	if ( a->generic.ownerdraw )
		a->generic.ownerdraw( a );
}


void Separator_Draw( menuseparator_s *s )
{
	if ( s->generic.name )
		Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->generic.name );
}

/*
void Menu_DrawTFilterStatusBar( void* unused )
{
	char *string;

	if(s_filter_list.curvalue == 0)
		string = "No mipmaps, no tex.smooth, ugly!";
	else if(s_filter_list.curvalue == 1)
		string = "No mipmaps, tex.smooth";
	else if(s_filter_list.curvalue == 2)
		string = "Mipmaps, no tex.smooth, ugly!";
	else if(s_filter_list.curvalue == 3)
		string = "Bilinear filtering";
	else if(s_filter_list.curvalue == 4)
		string = "Mipmaps filtering, no tex.smooth";
	else if(s_filter_list.curvalue == 5)
		string = "Trilinear filtering";

	Menu_DrawStatusBar(string);
}
*/
void Menu_DrawParallaxStatusBar(void* unused)
{
	char *string;

	if (s_parallax_slider.curvalue < 0)
		s_parallax_slider.curvalue = 0;
	else if (s_parallax_slider.curvalue > 3)
		s_parallax_slider.curvalue = 3;
	Cvar_SetValue("r_parallax_scale", s_parallax_slider.curvalue);

	if (s_parallax_slider.curvalue == 0)
		string = "Low scaled parallax";
	else if (s_parallax_slider.curvalue == 1)
		string = "Middle scaled parallax";
	else if (s_parallax_slider.curvalue == 2)
		string = "High scaled parallax (optimal)";
	else ///if (s_parallax_slider.curvalue == 3)
		string = "Extra scaled parallax (may be glitches)";

	Menu_DrawStatusBar(string);
}

void Menu_DrawShaderStatusBar( void* unused )
{
	char *string;

	if(s_simple.curvalue)
		string = "Simple renderer for weak cards: GF2MX, Intel...";
	else
	{
		if(s_shader_list.curvalue == SHADER_GENERIC4)
			string = "Generic OpenGL card, 4 texture units";
		else if(s_shader_list.curvalue == SHADER_GENERIC6)
			string = "Generic OpenGL card, 6 texture units";
		else if(s_shader_list.curvalue == SHADER_NVIDIA4)
			string = "nVidia GeForce3 or better (no MX!)";
		else if(s_shader_list.curvalue == SHADER_ATI4)
			string = "ATI Radeon 8500 or better";
		else if(s_shader_list.curvalue == SHADER_ATI6)
			string = "ATI Radeon 9700 or better";
		else if(s_shader_list.curvalue == SHADER_ARB4)
			string = "GF FX5200 or better, Radeon 9200 or better";
		else if(s_shader_list.curvalue == SHADER_ARB6)
			string = "GF FX5900 or better, Radeon 9700 or better";
	}

	Menu_DrawStatusBar(string);
}

void Menu_DrawAnisoStatusBar( void* unused )
{
	char string[24];

	if(s_anisotropy_slider.curvalue>1)
		Com_sprintf (string, sizeof(string), "anisotropy %i level", (int)pow(2,s_anisotropy_slider.curvalue-1));
	else
		Com_sprintf (string, sizeof(string), "no anisotropy");

	Menu_DrawStatusBar(string);
}

void Menu_DrawAaStatusBar( void* unused )
{
	char string[24];

	if(s_antialias_slider.curvalue>1)
		Com_sprintf (string, sizeof(string), "%i samples per pixel", (int)pow(2,s_antialias_slider.curvalue-1));
	else
		Com_sprintf (string, sizeof(string), "no anti-aliasing");

	Menu_DrawStatusBar(string);
}


void Menu_Draw( menuframework_s *menu )
{
	int i;
	menucommon_s *item;

	for ( i = 0; i < menu->nitems; i++ )
	{
		switch ( ( ( menucommon_s * ) menu->items[i] )->type )
		{
		case MTYPE_FIELD:
			Field_Draw( ( menufield_s * ) menu->items[i] );
			break;
		case MTYPE_SLIDER:
			Slider_Draw( ( menuslider_s * ) menu->items[i] );
			break;
///		case MTYPE_LIST:
///			MenuList_Draw( ( menulist_s * ) menu->items[i] );
///			break;
		case MTYPE_SPINCONTROL:
			SpinControl_Draw( ( menulist_s * ) menu->items[i] );
			break;
		case MTYPE_ACTION:
			Action_Draw( ( menuaction_s * ) menu->items[i] );
			break;
		case MTYPE_SEPARATOR:
			Separator_Draw( ( menuseparator_s * ) menu->items[i] );
			break;
		}
	}

	item = (menucommon_s *) Menu_ItemAtCursor( menu );

	if ( item && item->cursordraw )
	{
		item->cursordraw( item );
	}
	else if ( menu->cursordraw )
	{
		menu->cursordraw( menu );
	}
	else if ( item && item->type != MTYPE_FIELD )
	{
		if ( item->flags & QMF_LEFT_JUSTIFY )
		{
			Draw_Char( menu->x + item->x - 24 + item->cursor_offset, menu->y + item->y, 12 + ( ( int ) ( Sys_Milliseconds()/250 ) & 1 ) );
		}
		else
		{
			Draw_Char( menu->x + item->cursor_offset, menu->y + item->y, 12 + ( ( int ) ( Sys_Milliseconds()/250 ) & 1 ) );
		}
	}

	if ( item )
	{
		if ( item->statusbarfunc )
			item->statusbarfunc( (void*) item );
		else if ( item->statusbar )
			Menu_DrawStatusBar( item->statusbar );
		else
			Menu_DrawStatusBar( menu->statusbar );

	}
	else
		Menu_DrawStatusBar( menu->statusbar );
}


void Game_MenuDraw()
{
	M_Banner( "m_banner_game.tga" );
	Menu_AdjustCursor( &s_game_menu, 1 );
	Menu_Draw( &s_game_menu );
}


bool Field_Key( menufield_s *f, int key )
{
	switch ( key )
	{
	case K_KP_SLASH:
		key = '/';
		break;
	case K_KP_MINUS:
		key = '-';
		break;
	case K_KP_PLUS:
		key = '+';
		break;
	case K_KP_HOME:
		key = '7';
		break;
	case K_KP_UPARROW:
		key = '8';
		break;
	case K_KP_PGUP:
		key = '9';
		break;
	case K_KP_LEFTARROW:
		key = '4';
		break;
	case K_KP_5:
		key = '5';
		break;
	case K_KP_RIGHTARROW:
		key = '6';
		break;
	case K_KP_END:
		key = '1';
		break;
	case K_KP_DOWNARROW:
		key = '2';
		break;
	case K_KP_PGDN:
		key = '3';
		break;
	case K_KP_INS:
		key = '0';
		break;
	case K_KP_DEL:
		key = '.';
		break;
	}

	if ( key > 127 )
	{
		switch ( key )
		{
		case K_DEL:
		default:
			return false;
		}
	}

	/*
	** support pasting from the clipboard
	*/
	if ( ( toupper( key ) == 'V' && keydown[K_CTRL] ) ||
		 ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keydown[K_SHIFT] ) )
	{
		char *cbd;
		
		if ( ( cbd = Sys_GetClipboardData() ) != 0 )
		{
			strtok( cbd, "\n\r\b" );

			strncpy( f->buffer, cbd, f->length - 1 );
			f->cursor = strlen( f->buffer );
			f->visible_offset = f->cursor - f->visible_length;
			if ( f->visible_offset < 0 )
				f->visible_offset = 0;

			Z_Free( cbd );
		}
		return true;
	}

	switch ( key )
	{
	case K_KP_LEFTARROW:
	case K_LEFTARROW:
	case K_BACKSPACE:
		if ( f->cursor > 0 )
		{
			memmove( &f->buffer[f->cursor-1], &f->buffer[f->cursor], strlen( &f->buffer[f->cursor] ) + 1 );
			f->cursor--;

			if ( f->visible_offset )
			{
				f->visible_offset--;
			}
		}
		break;

	case K_KP_DEL:
	case K_DEL:
		memmove( &f->buffer[f->cursor], &f->buffer[f->cursor+1], strlen( &f->buffer[f->cursor+1] ) + 1 );
		break;

	case K_KP_ENTER:
	case K_ENTER:
	case K_ESCAPE:
	case K_TAB:
		return false;

	case K_SPACE:
	default:
		if ( !isdigit( key ) && ( f->generic.flags & QMF_NUMBERSONLY ) )
			return false;

		if ( f->cursor < f->length )
		{
			f->buffer[f->cursor++] = key;
			f->buffer[f->cursor] = 0;

			if ( f->cursor > f->visible_length )
			{
				f->visible_offset++;
			}
		}
	}
	return true;
}


void Slider_DoSlide( menuslider_s *s, int dir )
{
	s->curvalue += dir;

	if ( s->curvalue > s->maxvalue )
		s->curvalue = s->maxvalue;
	else if ( s->curvalue < s->minvalue )
		s->curvalue = s->minvalue;

	if ( s->generic.callback )
		s->generic.callback( s );
}


void SpinControl_DoSlide( menulist_s *s, int dir )
{
	s->curvalue += dir;

///	if ( s->curvalue < 0 )
///		s->curvalue = 0;
///	else if ( s->itemnames[s->curvalue] == 0 )
///		s->curvalue--;
if ( s->curvalue < 0 )
{
	s->curvalue = 0;
	while (1)
	{
		if (s->itemnames[s->curvalue])
			s->curvalue++;
		else
		{
			s->curvalue--;
			break;
		}
	}
}
else if ( s->itemnames[s->curvalue] == 0 )
	s->curvalue = 0;

	if ( s->generic.callback )
		s->generic.callback( s );
}


void Menu_SlideItem( menuframework_s *s, int dir )
{
	menucommon_s *item = ( menucommon_s * ) Menu_ItemAtCursor( s );

	if ( item )
	{
		switch ( item->type )
		{
		case MTYPE_SLIDER:
			Slider_DoSlide( ( menuslider_s * ) item, dir );
			break;
		case MTYPE_SPINCONTROL:
			SpinControl_DoSlide( ( menulist_s * ) item, dir );
			break;
		}
	}
}


bool Field_DoEnter( menufield_s *f )
{
	if ( f->generic.callback )
	{
		f->generic.callback( f );
		return true;
	}
	return false;
}


void Action_DoEnter( menuaction_s *a )
{
	if ( a->generic.callback )
		a->generic.callback( a );
}


bool Menu_SelectItem( menuframework_s *s )
{
	menucommon_s *item = ( menucommon_s * ) Menu_ItemAtCursor( s );

	if ( item )
	{
		switch ( item->type )
		{
		case MTYPE_FIELD:
			return Field_DoEnter( ( menufield_s * ) item ) ;
		case MTYPE_ACTION:
			Action_DoEnter( ( menuaction_s * ) item );
			return true;
///		case MTYPE_LIST:
///			return false;
		case MTYPE_SPINCONTROL:
			return false;
		}
	}
	return false;
}


int Default_MenuKey( menuframework_s *m, int key )
{
	int sound = 0;
	menucommon_s *item;

	if ( m )
	{
		if ( ( item = (menucommon_s *) Menu_ItemAtCursor( m ) ) != 0 )
		{
			if ( item->type == MTYPE_FIELD )
			{
				if ( Field_Key( ( menufield_s * ) item, key ) )
					return NULL;
			}
		}
	}

	switch ( key )
	{
	case K_ESCAPE:
		M_PopMenu();
		return cl_sfx_menu_out_sound;
	case K_KP_UPARROW:
	case K_UPARROW:
		if ( m )
		{
			m->cursor--;
			Menu_AdjustCursor( m, -1 );
			sound = cl_sfx_menu_move_sound;
		}
		break;
	case K_TAB:
		if ( m )
		{
			m->cursor++;
			Menu_AdjustCursor( m, 1 );
			sound = cl_sfx_menu_move_sound;
		}
		break;
	case K_KP_DOWNARROW:
	case K_DOWNARROW:
		if ( m )
		{
			m->cursor++;
			Menu_AdjustCursor( m, 1 );
			sound = cl_sfx_menu_move_sound;
		}
		break;
	case K_KP_LEFTARROW:
	case K_LEFTARROW:
		if ( m )
		{
			Menu_SlideItem( m, -1 );
			sound = cl_sfx_menu_move_sound;
		}
		break;
	case K_KP_RIGHTARROW:
	case K_RIGHTARROW:
		if ( m )
		{
			Menu_SlideItem( m, 1 );
			sound = cl_sfx_menu_move_sound;
		}
		break;

	case K_MOUSE1:
	case K_MOUSE2:
	case K_MOUSE3:
	case K_JOY1:
	case K_JOY2:
	case K_JOY3:
	case K_JOY4:
	case K_AUX1:
	case K_AUX2:
	case K_AUX3:
	case K_AUX4:
	case K_AUX5:
	case K_AUX6:
	case K_AUX7:
	case K_AUX8:
	case K_AUX9:
	case K_AUX10:
	case K_AUX11:
	case K_AUX12:
	case K_AUX13:
	case K_AUX14:
	case K_AUX15:
	case K_AUX16:
	case K_AUX17:
	case K_AUX18:
	case K_AUX19:
	case K_AUX20:
	case K_AUX21:
	case K_AUX22:
	case K_AUX23:
	case K_AUX24:
	case K_AUX25:
	case K_AUX26:
	case K_AUX27:
	case K_AUX28:
	case K_AUX29:
	case K_AUX30:
	case K_AUX31:
	case K_AUX32:

	case K_KP_ENTER:
	case K_ENTER:
		if ( m )
			Menu_SelectItem( m );
		sound = cl_sfx_menu_move_sound;
		break;
	}
	return sound;
}


int Game_MenuKey( int key )
{
	return Default_MenuKey( &s_game_menu, key );
}


void M_Menu_Game_f ()
{
	Game_MenuInit();
	M_PushMenu( Game_MenuDraw, Game_MenuKey );
	m_game_cursor = 1;
}


void AddressBook_MenuInit()
{
	int i;

	s_addressbook_menu.x = viddef.width / 2 - 142;
	s_addressbook_menu.y = viddef.height / 2 - 58;
	s_addressbook_menu.nitems = 0;

	for ( i = 0; i < NUM_ADDRESSBOOK_ENTRIES; i++ )
	{
		cvar_t *adr;
		char buffer[20];

		Com_sprintf( buffer, sizeof( buffer ), "adr%d", i );

		adr = Cvar_Get( buffer, "", CVAR_ARCHIVE );

		s_addressbook_fields[i].generic.type = MTYPE_FIELD;
		s_addressbook_fields[i].generic.name = 0;
		s_addressbook_fields[i].generic.callback = 0;
		s_addressbook_fields[i].generic.x		= 0;
		s_addressbook_fields[i].generic.y		= i * 18 + 0;
		s_addressbook_fields[i].generic.localdata[0] = i;
		s_addressbook_fields[i].cursor			= 0;
		s_addressbook_fields[i].length			= 60;
		s_addressbook_fields[i].visible_length	= 30;

		strcpy( s_addressbook_fields[i].buffer, adr->string );

		Menu_AddItem( &s_addressbook_menu, &s_addressbook_fields[i] );
	}
}


int AddressBook_MenuKey( int key )
{
	if ( key == K_ESCAPE )
	{
		int index;
		char buffer[20];

		for ( index = 0; index < NUM_ADDRESSBOOK_ENTRIES; index++ )
		{
			Com_sprintf( buffer, sizeof( buffer ), "adr%d", index );
			Cvar_Set( buffer, s_addressbook_fields[index].buffer );
		}
	}
	return Default_MenuKey( &s_addressbook_menu, key );
}


void AddressBook_MenuDraw()
{
	M_Banner( "m_banner_addressbook.tga" );
	Menu_Draw( &s_addressbook_menu );
}


void M_Menu_AddressBook_f()
{
	AddressBook_MenuInit();
	M_PushMenu( AddressBook_MenuDraw, AddressBook_MenuKey );
}


void AddressBookFunc( void *self )
{
	M_Menu_AddressBook_f();
}


/*
================
M_DrawCharacter

Draws one solid graphics character
cx and cy are in 320*240 coordinates, and will be centered on
higher res screens.
================
*/
void M_DrawCharacter (int cx, int cy, int num)
{
	Draw_Char ( cx + ((viddef.width - 320)>>1), cy + ((viddef.height - 240)>>1), num);
}


void M_DrawTextBox (int x, int y, int width, int lines)
{
	int		cx, cy;
	int		n;

	// draw left side
	cx = x;
	cy = y;
	M_DrawCharacter (cx, cy, 1);
	for (n = 0; n < lines; n++)
	{
		cy += 8;
		M_DrawCharacter (cx, cy, 4);
	}
	M_DrawCharacter (cx, cy+8, 7);

	// draw middle
	cx += 8;
	while (width > 0)
	{
		cy = y;
		M_DrawCharacter (cx, cy, 2);
		for (n = 0; n < lines; n++)
		{
			cy += 8;
			M_DrawCharacter (cx, cy, 5);
		}
		M_DrawCharacter (cx, cy+8, 8);
		width -= 1;
		cx += 8;
	}

	// draw right side
	cy = y;
	M_DrawCharacter (cx, cy, 3);
	for (n = 0; n < lines; n++)
	{
		cy += 8;
		M_DrawCharacter (cx, cy, 6);
	}
	M_DrawCharacter (cx, cy+8, 9);
}

		
void M_Print (int cx, int cy, char *str)
{
	while (*str)
	{
		M_DrawCharacter (cx, cy, (*str)+128);
		str++;
		cx += 8;
	}
}


void SearchLocalGames()
{
	int		i;

	m_num_servers = 0;
	for (i=0 ; i<MAX_LOCAL_SERVERS ; i++)
		strcpy (local_server_names[i], NO_SERVER_STRING);

	M_DrawTextBox( 8, 120 - 48, 36, 3 );
	M_Print( 16 + 16, 120 - 48 + 8,  "Searching for local servers, this" );
	M_Print( 16 + 16, 120 - 48 + 16, "could take up to a minute." );
	M_Print( 16 + 16, 120 - 48 + 24, "Please wait..." );

	// the text box won't show up unless we do a buffer swap
	End_Frame();

	// send out info packets
	CL_PingServers_f();
}


void SearchLocalGamesFunc( void *self )
{
	SearchLocalGames();
}


void JoinServerFunc( void *self )
{
	char	buffer[128];
	int		index;

	index = ( menuaction_s * ) self - s_joinserver_server_actions;

	if ( Q_strcasecmp( local_server_names[index], NO_SERVER_STRING ) == 0 )
		return;

	if (index >= m_num_servers)
		return;

	Com_sprintf (buffer, sizeof(buffer), "connect %s\n", NET_AdrToString (local_server_netadr[index]));
	Cbuf_AddText (buffer);
	M_ForceMenuOff ();
}


void JoinServer_MenuInit()
{
	int i;

	s_joinserver_menu.x = viddef.width * 0.50 - 120;
	s_joinserver_menu.nitems = 0;

	for ( i = 0; i < MAX_LOCAL_SERVERS; i++ )
	{
		s_joinserver_server_actions[i].generic.type	= MTYPE_ACTION;
		strcpy (local_server_names[i], NO_SERVER_STRING);
		s_joinserver_server_actions[i].generic.name	= local_server_names[i];
		s_joinserver_server_actions[i].generic.flags	= QMF_LEFT_JUSTIFY;
		s_joinserver_server_actions[i].generic.x		= 0;
		s_joinserver_server_actions[i].generic.y		= i*10;
		s_joinserver_server_actions[i].generic.callback = JoinServerFunc;
		s_joinserver_server_actions[i].generic.statusbar = "press ENTER to connect";
	}

	s_joinserver_server_title.generic.type = MTYPE_SEPARATOR;
	s_joinserver_server_title.generic.name = "\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37";
	s_joinserver_server_title.generic.x    = 126;
	s_joinserver_server_title.generic.y	   = (MAX_LOCAL_SERVERS + 1) * 10;

	s_joinserver_address_book_action.generic.type	= MTYPE_ACTION;
	s_joinserver_address_book_action.generic.name	= "address book";
	s_joinserver_address_book_action.generic.flags	= QMF_LEFT_JUSTIFY;
	s_joinserver_address_book_action.generic.x		= 0;
	s_joinserver_address_book_action.generic.y		= (MAX_LOCAL_SERVERS + 2) * 10;
	s_joinserver_address_book_action.generic.callback = AddressBookFunc;

	s_joinserver_search_action.generic.type = MTYPE_ACTION;
	s_joinserver_search_action.generic.name	= "refresh server list";
	s_joinserver_search_action.generic.flags	= QMF_LEFT_JUSTIFY;
	s_joinserver_search_action.generic.x	= 0;
	s_joinserver_search_action.generic.y	= (MAX_LOCAL_SERVERS + 3) * 10;
	s_joinserver_search_action.generic.callback = SearchLocalGamesFunc;
	s_joinserver_search_action.generic.statusbar = "search for servers";

	for ( i = 0; i < 8; i++ )
		Menu_AddItem( &s_joinserver_menu, &s_joinserver_server_actions[i] );

	Menu_AddItem( &s_joinserver_menu, &s_joinserver_server_title );
	Menu_AddItem( &s_joinserver_menu, &s_joinserver_address_book_action );
	Menu_AddItem( &s_joinserver_menu, &s_joinserver_search_action );

	Menu_Center( &s_joinserver_menu );

	SearchLocalGames();
}


void JoinServer_MenuDraw()
{
	M_Banner( "m_banner_join_server.tga" );
	Menu_Draw( &s_joinserver_menu );
}


int JoinServer_MenuKey( int key )
{
	return Default_MenuKey( &s_joinserver_menu, key );
}


void M_Menu_JoinServer_f ()
{
	JoinServer_MenuInit();
	M_PushMenu( JoinServer_MenuDraw, JoinServer_MenuKey );
}


void JoinNetworkServerFunc( void *unused )
{
	M_Menu_JoinServer_f();
}


void RulesChangeFunc ( void *self )
{
	if (s_rules_box.curvalue == 0)	// DM
	{
		s_maxclients_field.generic.statusbar = NULL;
		s_startserver_dmoptions_action.generic.statusbar = NULL;
	}
	else if(s_rules_box.curvalue == 1)		// coop
	{
		s_maxclients_field.generic.statusbar = "4 maximum for cooperative";
		if (atoi(s_maxclients_field.buffer) > 4)
			strcpy( s_maxclients_field.buffer, "4" );
		s_startserver_dmoptions_action.generic.statusbar = "N/A for cooperative";
	}
}


void Menu_SetStatusBar( menuframework_s *m, const char *string )
{
	m->statusbar = string;
}


void DMOptions_MenuInit()
{
	static const char *yes_no_names[] =
	{
		"no", "yes", 0
	};
	static const char *teamplay_names[] = 
	{
		"disabled", "by skin", "by model", 0
	};
	int dmflags = Cvar_VariableValue( "dmflags" );
	int y = 0;

	s_dmoptions_menu.x = viddef.width * 0.50;
	s_dmoptions_menu.nitems = 0;

	s_falls_box.generic.type = MTYPE_SPINCONTROL;
	s_falls_box.generic.x	= 0;
	s_falls_box.generic.y	= y;
	s_falls_box.generic.name	= "falling damage";
	s_falls_box.generic.callback = DMFlagCallback;
	s_falls_box.itemnames = yes_no_names;
	s_falls_box.curvalue = ( dmflags & DF_NO_FALLING ) == 0;
	s_falls_box.generic.statusbarfunc = DMFlagPrint;

	s_weapons_stay_box.generic.type = MTYPE_SPINCONTROL;
	s_weapons_stay_box.generic.x	= 0;
	s_weapons_stay_box.generic.y	= y += 8;
	s_weapons_stay_box.generic.name	= "weapons stay";
	s_weapons_stay_box.generic.callback = DMFlagCallback;
	s_weapons_stay_box.itemnames = yes_no_names;
	s_weapons_stay_box.curvalue = ( dmflags & DF_WEAPONS_STAY ) != 0;
	s_weapons_stay_box.generic.statusbarfunc = DMFlagPrint;

	s_instant_powerups_box.generic.type = MTYPE_SPINCONTROL;
	s_instant_powerups_box.generic.x	= 0;
	s_instant_powerups_box.generic.y	= y += 8;
	s_instant_powerups_box.generic.name	= "instant powerups";
	s_instant_powerups_box.generic.callback = DMFlagCallback;
	s_instant_powerups_box.itemnames = yes_no_names;
	s_instant_powerups_box.curvalue = ( dmflags & DF_INSTANT_ITEMS ) != 0;
	s_instant_powerups_box.generic.statusbarfunc = DMFlagPrint;

	s_powerups_box.generic.type = MTYPE_SPINCONTROL;
	s_powerups_box.generic.x	= 0;
	s_powerups_box.generic.y	= y += 8;
	s_powerups_box.generic.name	= "allow powerups";
	s_powerups_box.generic.callback = DMFlagCallback;
	s_powerups_box.itemnames = yes_no_names;
	s_powerups_box.curvalue = ( dmflags & DF_NO_ITEMS ) == 0;
	s_powerups_box.generic.statusbarfunc = DMFlagPrint;

	s_health_box.generic.type = MTYPE_SPINCONTROL;
	s_health_box.generic.x	= 0;
	s_health_box.generic.y	= y += 8;
	s_health_box.generic.callback = DMFlagCallback;
	s_health_box.generic.name	= "allow health";
	s_health_box.itemnames = yes_no_names;
	s_health_box.curvalue = ( dmflags & DF_NO_HEALTH ) == 0;
	s_health_box.generic.statusbarfunc = DMFlagPrint;

	s_armor_box.generic.type = MTYPE_SPINCONTROL;
	s_armor_box.generic.x	= 0;
	s_armor_box.generic.y	= y += 8;
	s_armor_box.generic.name	= "allow armor";
	s_armor_box.generic.callback = DMFlagCallback;
	s_armor_box.itemnames = yes_no_names;
	s_armor_box.curvalue = ( dmflags & DF_NO_ARMOR ) == 0;
	s_armor_box.generic.statusbarfunc = DMFlagPrint;

	s_spawn_farthest_box.generic.type = MTYPE_SPINCONTROL;
	s_spawn_farthest_box.generic.x	= 0;
	s_spawn_farthest_box.generic.y	= y += 8;
	s_spawn_farthest_box.generic.name	= "spawn farthest";
	s_spawn_farthest_box.generic.callback = DMFlagCallback;
	s_spawn_farthest_box.itemnames = yes_no_names;
	s_spawn_farthest_box.curvalue = ( dmflags & DF_SPAWN_FARTHEST ) != 0;
	s_spawn_farthest_box.generic.statusbarfunc = DMFlagPrint;

	s_samelevel_box.generic.type = MTYPE_SPINCONTROL;
	s_samelevel_box.generic.x	= 0;
	s_samelevel_box.generic.y	= y += 8;
	s_samelevel_box.generic.name	= "same map";
	s_samelevel_box.generic.callback = DMFlagCallback;
	s_samelevel_box.itemnames = yes_no_names;
	s_samelevel_box.curvalue = ( dmflags & DF_SAME_LEVEL ) != 0;
	s_samelevel_box.generic.statusbarfunc = DMFlagPrint;

	s_force_respawn_box.generic.type = MTYPE_SPINCONTROL;
	s_force_respawn_box.generic.x	= 0;
	s_force_respawn_box.generic.y	= y += 8;
	s_force_respawn_box.generic.name	= "force respawn";
	s_force_respawn_box.generic.callback = DMFlagCallback;
	s_force_respawn_box.itemnames = yes_no_names;
	s_force_respawn_box.curvalue = ( dmflags & DF_FORCE_RESPAWN ) != 0;
	s_force_respawn_box.generic.statusbarfunc = DMFlagPrint;

	s_teamplay_box.generic.type = MTYPE_SPINCONTROL;
	s_teamplay_box.generic.x	= 0;
	s_teamplay_box.generic.y	= y += 8;
	s_teamplay_box.generic.name	= "teamplay";
	s_teamplay_box.generic.callback = DMFlagCallback;
	s_teamplay_box.itemnames = teamplay_names;
	s_teamplay_box.generic.statusbarfunc = DMFlagPrint;

	s_allow_exit_box.generic.type = MTYPE_SPINCONTROL;
	s_allow_exit_box.generic.x	= 0;
	s_allow_exit_box.generic.y	= y += 8;
	s_allow_exit_box.generic.name	= "allow exit";
	s_allow_exit_box.generic.callback = DMFlagCallback;
	s_allow_exit_box.itemnames = yes_no_names;
	s_allow_exit_box.curvalue = ( dmflags & DF_ALLOW_EXIT ) != 0;
	s_allow_exit_box.generic.statusbarfunc = DMFlagPrint;

	s_infinite_ammo_box.generic.type = MTYPE_SPINCONTROL;
	s_infinite_ammo_box.generic.x	= 0;
	s_infinite_ammo_box.generic.y	= y += 8;
	s_infinite_ammo_box.generic.name	= "infinite ammo";
	s_infinite_ammo_box.generic.callback = DMFlagCallback;
	s_infinite_ammo_box.itemnames = yes_no_names;
	s_infinite_ammo_box.curvalue = ( dmflags & DF_INFINITE_AMMO ) != 0;
	s_infinite_ammo_box.generic.statusbarfunc = DMFlagPrint;

	s_fixed_fov_box.generic.type = MTYPE_SPINCONTROL;
	s_fixed_fov_box.generic.x	= 0;
	s_fixed_fov_box.generic.y	= y += 8;
	s_fixed_fov_box.generic.name	= "fixed FOV";
	s_fixed_fov_box.generic.callback = DMFlagCallback;
	s_fixed_fov_box.itemnames = yes_no_names;
	s_fixed_fov_box.curvalue = ( dmflags & DF_FIXED_FOV ) != 0;
	s_fixed_fov_box.generic.statusbarfunc = DMFlagPrint;

	s_quad_drop_box.generic.type = MTYPE_SPINCONTROL;
	s_quad_drop_box.generic.x	= 0;
	s_quad_drop_box.generic.y	= y += 8;
	s_quad_drop_box.generic.name	= "quad drop";
	s_quad_drop_box.generic.callback = DMFlagCallback;
	s_quad_drop_box.itemnames = yes_no_names;
	s_quad_drop_box.curvalue = ( dmflags & DF_QUAD_DROP ) != 0;
	s_quad_drop_box.generic.statusbarfunc = DMFlagPrint;

	s_friendlyfire_box.generic.type = MTYPE_SPINCONTROL;
	s_friendlyfire_box.generic.x	= 0;
	s_friendlyfire_box.generic.y	= y += 8;
	s_friendlyfire_box.generic.name	= "friendly fire";
	s_friendlyfire_box.generic.callback = DMFlagCallback;
	s_friendlyfire_box.itemnames = yes_no_names;
	s_friendlyfire_box.curvalue = ( dmflags & DF_NO_FRIENDLY_FIRE ) == 0;
	s_friendlyfire_box.generic.statusbarfunc = DMFlagPrint;

	s_drop_weapon_box.generic.type = MTYPE_SPINCONTROL;
	s_drop_weapon_box.generic.x	= 0;
	s_drop_weapon_box.generic.y	= y += 8;
	s_drop_weapon_box.generic.name	= "death: drop weapon";
	s_drop_weapon_box.generic.callback = DMFlagCallback;
	s_drop_weapon_box.itemnames = yes_no_names;
	s_drop_weapon_box.curvalue = ( dmflags & DF_DROP_WEAPON ) != 0;
	s_drop_weapon_box.generic.statusbarfunc = DMFlagPrint;

	s_flashlight_box.generic.type = MTYPE_SPINCONTROL;
	s_flashlight_box.generic.x	= 0;
	s_flashlight_box.generic.y	= y += 8;
	s_flashlight_box.generic.name	= "flashlight";
	s_flashlight_box.generic.callback = DMFlagCallback;
	s_flashlight_box.itemnames = yes_no_names;
	s_flashlight_box.curvalue = ( dmflags & DF_FLASHLIGHT ) != 0;
	s_flashlight_box.generic.statusbarfunc = DMFlagPrint;

	s_flashlightcells_box.generic.type = MTYPE_SPINCONTROL;
	s_flashlightcells_box.generic.x	= 0;
	s_flashlightcells_box.generic.y	= y += 8;
	s_flashlightcells_box.generic.name	= "flashlight cells";
	s_flashlightcells_box.generic.callback = DMFlagCallback;
	s_flashlightcells_box.itemnames = yes_no_names;
	s_flashlightcells_box.curvalue = ( dmflags & DF_FLASHLIGHTCELLS ) != 0;
	s_flashlightcells_box.generic.statusbarfunc = DMFlagPrint;

	s_footprint_box.generic.type = MTYPE_SPINCONTROL;
	s_footprint_box.generic.x	= 0;
	s_footprint_box.generic.y	= y += 8;
	s_footprint_box.generic.name	= "foot print";
	s_footprint_box.generic.callback = DMFlagCallback;
	s_footprint_box.itemnames = yes_no_names;
	s_footprint_box.curvalue = ( dmflags & DF_FOOTPRINT ) != 0;
	s_footprint_box.generic.statusbarfunc = DMFlagPrint;

	s_fastrocket_box.generic.type = MTYPE_SPINCONTROL;
	s_fastrocket_box.generic.x	= 0;
	s_fastrocket_box.generic.y	= y += 8;
	s_fastrocket_box.generic.name	= "fast rockets";
	s_fastrocket_box.generic.callback = DMFlagCallback;
	s_fastrocket_box.itemnames = yes_no_names;
	s_fastrocket_box.curvalue = ( dmflags & DF_FASTROCKET ) != 0;
	s_fastrocket_box.generic.statusbarfunc = DMFlagPrint;

	s_haste_box.generic.type = MTYPE_SPINCONTROL;
	s_haste_box.generic.x	= 0;
	s_haste_box.generic.y	= y += 8;
	s_haste_box.generic.name	= "haste mode";
	s_haste_box.generic.callback = DMFlagCallback;
	s_haste_box.itemnames = yes_no_names;
	s_haste_box.curvalue = ( dmflags & DF_HASTE ) != 0;
	s_haste_box.generic.statusbarfunc = DMFlagPrint;

	s_shell_box.generic.type = MTYPE_SPINCONTROL;
	s_shell_box.generic.x	= 0;
	s_shell_box.generic.y = y += 8;
	s_shell_box.generic.name	= "damage flash";
	s_shell_box.generic.callback = DMFlagCallback;
	s_shell_box.itemnames = yes_no_names;
	s_shell_box.curvalue = ( dmflags & DF_SHELL ) != 0;
	s_shell_box.generic.statusbarfunc = DMFlagPrint;

	s_doublejump_box.generic.type = MTYPE_SPINCONTROL;
	s_doublejump_box.generic.x	= 0;
	s_doublejump_box.generic.y	= y += 8;
	s_doublejump_box.generic.name	= "double jump";
	s_doublejump_box.generic.callback = DMFlagCallback;
	s_doublejump_box.itemnames = yes_no_names;
	s_doublejump_box.curvalue = ( dmflags & DF_UT_DOUBLE_JUMP ) != 0;
	s_doublejump_box.generic.statusbarfunc = DMFlagPrint;

	s_godlight_box.generic.type = MTYPE_SPINCONTROL;
	s_godlight_box.generic.x = 0;
	s_godlight_box.generic.y = y += 8;
	s_godlight_box.generic.name = "Show God-players";
	s_godlight_box.generic.callback = DMFlagCallback;
	s_godlight_box.itemnames = yes_no_names;
	s_godlight_box.curvalue = (dmflags & DF_NOGODLIGHT) == 0;
	s_godlight_box.generic.statusbarfunc = DMFlagPrint;

	s_instagib_box.generic.type = MTYPE_SPINCONTROL;
	s_instagib_box.generic.x = 0;
	s_instagib_box.generic.y = y += 8;
	s_instagib_box.generic.name = "insta-gib mod";
	s_instagib_box.generic.callback = DMFlagCallback;
	s_instagib_box.itemnames = yes_no_names;
	s_instagib_box.curvalue = (dmflags & DF_INSTAGIB) != 0;
	s_instagib_box.generic.statusbarfunc = DMFlagPrint;

	Menu_AddItem( &s_dmoptions_menu, &s_falls_box );
	Menu_AddItem( &s_dmoptions_menu, &s_weapons_stay_box );
	Menu_AddItem( &s_dmoptions_menu, &s_instant_powerups_box );
	Menu_AddItem( &s_dmoptions_menu, &s_powerups_box );
	Menu_AddItem( &s_dmoptions_menu, &s_health_box );
	Menu_AddItem( &s_dmoptions_menu, &s_armor_box );
	Menu_AddItem( &s_dmoptions_menu, &s_spawn_farthest_box );
	Menu_AddItem( &s_dmoptions_menu, &s_samelevel_box );
	Menu_AddItem( &s_dmoptions_menu, &s_force_respawn_box );
	Menu_AddItem( &s_dmoptions_menu, &s_teamplay_box );
	Menu_AddItem( &s_dmoptions_menu, &s_allow_exit_box );
	Menu_AddItem( &s_dmoptions_menu, &s_infinite_ammo_box );
	Menu_AddItem( &s_dmoptions_menu, &s_fixed_fov_box );
	Menu_AddItem( &s_dmoptions_menu, &s_quad_drop_box );
	Menu_AddItem( &s_dmoptions_menu, &s_friendlyfire_box );
	Menu_AddItem( &s_dmoptions_menu, &s_drop_weapon_box );
	Menu_AddItem( &s_dmoptions_menu, &s_flashlight_box );
	Menu_AddItem( &s_dmoptions_menu, &s_flashlightcells_box );
	Menu_AddItem( &s_dmoptions_menu, &s_footprint_box );
	Menu_AddItem( &s_dmoptions_menu, &s_fastrocket_box );
	Menu_AddItem( &s_dmoptions_menu, &s_haste_box );
	Menu_AddItem( &s_dmoptions_menu, &s_shell_box );
	Menu_AddItem( &s_dmoptions_menu, &s_doublejump_box );
	Menu_AddItem( &s_dmoptions_menu, &s_godlight_box );
	Menu_AddItem( &s_dmoptions_menu, &s_instagib_box );

	Menu_Center( &s_dmoptions_menu );

	// set the original dmflags statusbar
	DMFlagCallback( 0 );
///	Menu_SetStatusBar( &s_dmoptions_menu, dmoptions_statusbar );
}


void DMOptions_MenuDraw()
{
	Menu_Draw( &s_dmoptions_menu );
}


int DMOptions_MenuKey( int key )
{
	return Default_MenuKey( &s_dmoptions_menu, key );
}


void M_Menu_DMOptions_f ()
{
	DMOptions_MenuInit();
	M_PushMenu( DMOptions_MenuDraw, DMOptions_MenuKey );
}


void DMOptionsFunc( void *self )
{
///	if (s_rules_box.curvalue == 1)	// тупое ограничение, которое всё-равно легко обойти
///		return;
	M_Menu_DMOptions_f();
}


void StartServerActionFunc( void *self )
{
	char	startmap[1024];
	int		timelimit;
	int		fraglimit;
	int		maxclients;

	if (s_mapname_field.buffer[0])
		strcpy( startmap, s_mapname_field.buffer );
	else
		strcpy( startmap, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 );

	maxclients  = atoi( s_maxclients_field.buffer );
	timelimit	= atoi( s_timelimit_field.buffer );
	fraglimit	= atoi( s_fraglimit_field.buffer );

	Cvar_SetValue( "maxclients", ClampCvar( 0, maxclients, maxclients ) );
	Cvar_SetValue ("timelimit", ClampCvar( 0, timelimit, timelimit ) );
	Cvar_SetValue ("fraglimit", ClampCvar( 0, fraglimit, fraglimit ) );
	Cvar_Set("hostname", s_hostname_field.buffer );
	Cvar_SetValue ("deathmatch", !s_rules_box.curvalue );
	Cvar_SetValue ("coop", s_rules_box.curvalue );

	Cbuf_AddText (va("map %s\n", startmap));

	M_ForceMenuOff ();
}


void FreeMapNames()
{
	int i;
	if ( mapnames )
	{
		for ( i = 0; i < nummaps; i++ )
			Z_Free( mapnames[i] );
		Z_Free( mapnames );
	}
	mapnames = 0;
	nummaps = 0;
}

void SkillChangeFunc ( void *self )
{
	if (s_skill_box.curvalue < 0)
		s_skill_box.curvalue = 0;

	if (s_skill_box.curvalue > 3)
		s_skill_box.curvalue = 3;

	Cvar_ForceSetValue( "skill", s_skill_box.curvalue );
}


void StartServer_MenuInit(bool dedicated)
{
	char *buffer;
	char  mapsname[1024];
	char *s;
	int length;
	int i;
	FILE *fp;

	/*
	** load the list of map names
	*/
	Com_sprintf( mapsname, sizeof( mapsname ), "%s/maps.lst", FS_Gamedir() );
	if ( ( fp = FS_Fopen( mapsname, "rb" ) ) == 0 )
	{
		if ( ( length = FS_LoadFile( "maps.lst", ( void ** ) &buffer ) ) == -1 )
			Com_Error( ERR_DROP, "couldn't find maps.lst" );
	}
	else
	{
		length = filelength( fileno( fp  ) );
		buffer = (char *) Z_Malloc( length, true );
		fread( buffer, length, 1, fp );
	}

	s = buffer;

// Berserker: Исправление глюка Q2 - выбрать карту и запустить сервер. Memory leaks...
	if(nummaps)
		FreeMapNames();	// Если уже были определены структуры mapnames, то освободим память...
////////////////////////////////////////////////////////////////////////////////////////

	i = 0;
	while ( i < length )
	{
		if ( s[i] == '\r' )
		{
again:		if (i + 2 < length)			/// Berserker: игнорируем пустые строки в maps.lst
			{
				if (s[i + 2] == '\r')
				{
					i += 2;
					goto again;
				}
			}
			nummaps++;
		}
		i++;
	}

	if ( nummaps == 0 )
		Com_Error( ERR_DROP, "no maps in maps.lst" );

	mapnames = (char **) Z_Malloc( sizeof( char * ) * ( nummaps + 1 ), true );
///	Memset( mapnames, 0, sizeof( char * ) * ( nummaps + 1 ) );

	s = buffer;

	for ( i = 0; i < nummaps; i++ )
	{
		char  shortname[MAX_TOKEN_CHARS];
		char  longname[MAX_TOKEN_CHARS];
		char  scratch[200];
		int		j, l;

		strcpy( shortname, COM_Parse( &s ) );
		l = strlen(shortname);
		for (j=0 ; j<l ; j++)
			shortname[j] = toupper(shortname[j]);
		strcpy( longname, COM_Parse( &s ) );
		Com_sprintf( scratch, sizeof( scratch ), "%s\n%s", longname, shortname );

		mapnames[i] = (char *) Z_Malloc( strlen( scratch ) + 1, true );
		strcpy( mapnames[i], scratch );
	}
	mapnames[nummaps] = 0;

	if ( fp != 0 )
	{
#if 1
		fclose(fp);		/// Berserker: bug fix
#else
		fp = 0;
#endif
///		Z_Free( buffer );
	}
///	else
	Z_Free( buffer );

	/*
	** initialize the menu stuff
	*/
	s_startserver_menu.x = viddef.width * 0.50;
	s_startserver_menu.nitems = 0;

	s_startmap_list.generic.type = MTYPE_SPINCONTROL;
	s_startmap_list.generic.x	= -40;
	s_startmap_list.generic.y	= 0;
	s_startmap_list.generic.name	= "initial map";
	s_startmap_list.itemnames = (const char **) mapnames;
	s_startmap_list.generic.statusbarfunc = DrawLevelShot;
	s_startmap_list.generic.cursor_offset = -40;

	s_mapname_title.generic.type = MTYPE_SEPARATOR;
	s_mapname_title.generic.name = "custom map";
	s_mapname_title.generic.x = -64;
	s_mapname_title.generic.y = 10;

	s_mapname_field.generic.type = MTYPE_FIELD;
	s_mapname_field.generic.name = 0;
	s_mapname_field.generic.flags = 0;
	s_mapname_field.generic.x	= -152;
	s_mapname_field.generic.y	= 24;
	s_mapname_field.length = 64;
	s_mapname_field.visible_length = 12;
	s_mapname_field.generic.statusbarfunc = DrawLevelShot;
///	strcpy( s_mapname_field.buffer, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 );

	s_rules_title.generic.type = MTYPE_SEPARATOR;
	s_rules_title.generic.name = "rules";
	s_rules_title.generic.x = -104;
	s_rules_title.generic.y = 38;

	s_rules_box.generic.type = MTYPE_SPINCONTROL;
	s_rules_box.generic.x	= -100;
	s_rules_box.generic.y	= 38;
	s_rules_box.generic.name	= 0;
	s_rules_box.generic.cursor_offset = -144;
	s_rules_box.itemnames = dm_coop_names;
	s_rules_box.generic.statusbarfunc = DrawLevelShot;
	if (dedicated)
	{
		Cvar_SetValue("coop", 1);
		Cvar_SetValue("deathmatch", 0);
		s_rules_box.curvalue = 1;
	}
	else
	{
		Cvar_SetValue("coop", 0);
		Cvar_SetValue("deathmatch", 1);
		s_rules_box.curvalue = 0;
	}
	s_rules_box.generic.callback = RulesChangeFunc;

	s_timelimit_title.generic.type = MTYPE_SEPARATOR;
	s_timelimit_title.generic.name = "time limit";
	s_timelimit_title.generic.x = -64;
	s_timelimit_title.generic.y = 48;

	s_timelimit_field.generic.type = MTYPE_FIELD;
	s_timelimit_field.generic.name = 0;
	s_timelimit_field.generic.flags = QMF_NUMBERSONLY;
	s_timelimit_field.generic.x	= -104;
	s_timelimit_field.generic.y	= 62;
///	s_timelimit_field.generic.statusbar = "0 = no limit";
	s_timelimit_field.length = 3;
	s_timelimit_field.visible_length = 3;
	s_timelimit_field.generic.statusbarfunc = DrawLevelShot;
	strcpy( s_timelimit_field.buffer, Cvar_VariableString("timelimit") );

	s_fraglimit_title.generic.type = MTYPE_SEPARATOR;
	s_fraglimit_title.generic.name = "frag limit";
	s_fraglimit_title.generic.x = -64;
	s_fraglimit_title.generic.y = 76;

	s_fraglimit_field.generic.type = MTYPE_FIELD;
	s_fraglimit_field.generic.name = 0;
	s_fraglimit_field.generic.flags = QMF_NUMBERSONLY;
	s_fraglimit_field.generic.x	= -104;
	s_fraglimit_field.generic.y	= 90;
///	s_fraglimit_field.generic.statusbar = "0 = no limit";
	s_fraglimit_field.length = 3;
	s_fraglimit_field.visible_length = 3;
	s_fraglimit_field.generic.statusbarfunc = DrawLevelShot;
	strcpy( s_fraglimit_field.buffer, Cvar_VariableString("fraglimit") );

	/*
	** maxclients determines the maximum number of players that can join
	** the game.  If maxclients is only "1" then we should default the menu
	** option to 8 players, otherwise use whatever its current value is. 
	** Clamping will be done when the server is actually started.
	*/
	s_maxclients_title.generic.type = MTYPE_SEPARATOR;
	s_maxclients_title.generic.name = "max players";
	s_maxclients_title.generic.x = -56;
	s_maxclients_title.generic.y = 104;

	s_maxclients_field.generic.type = MTYPE_FIELD;
	s_maxclients_field.generic.name = 0;
	s_maxclients_field.generic.flags = QMF_NUMBERSONLY;
	s_maxclients_field.generic.x	= -104;
	s_maxclients_field.generic.y	= 118;
///	s_maxclients_field.generic.statusbar = NULL;
	s_maxclients_field.length = 3;
	s_maxclients_field.visible_length = 3;
	s_maxclients_field.generic.statusbarfunc = DrawLevelShot;
	if ( Cvar_VariableValue( "maxclients" ) == 1 )
		strcpy( s_maxclients_field.buffer, "8" );
	else
		strcpy( s_maxclients_field.buffer, Cvar_VariableString("maxclients") );

	s_skill_title.generic.type = MTYPE_SEPARATOR;
	s_skill_title.generic.name = "skill";
	s_skill_title.generic.x = -104;
	s_skill_title.generic.y = 132;

	s_skill_box.generic.type = MTYPE_SPINCONTROL;
	s_skill_box.generic.x	= -110;
	s_skill_box.generic.y	= 132;
	s_skill_box.generic.name	= 0;
	s_skill_box.generic.cursor_offset = -144;
	s_skill_box.itemnames = skill_names;
	s_skill_box.generic.statusbarfunc = DrawLevelShot;

	s_skill_box.curvalue = Cvar_VariableValue("skill");
	if (s_skill_box.curvalue<0)	s_skill_box.curvalue=0;
	if (s_skill_box.curvalue>3)	s_skill_box.curvalue=3;
	s_skill_box.generic.callback = SkillChangeFunc;

	s_hostname_title.generic.type = MTYPE_SEPARATOR;
	s_hostname_title.generic.name = "hostname";
	s_hostname_title.generic.x = -80;
	s_hostname_title.generic.y = 144;

	s_hostname_field.generic.type = MTYPE_FIELD;
	s_hostname_field.generic.name = 0;
	s_hostname_field.generic.flags = 0;
	s_hostname_field.generic.x	= -152;
	s_hostname_field.generic.y	= 158;
///	s_hostname_field.generic.statusbar = NULL;
	s_hostname_field.length = 12;
	s_hostname_field.visible_length = 12;
	s_hostname_field.generic.statusbarfunc = DrawLevelShot;
	strcpy( s_hostname_field.buffer, Cvar_VariableString("hostname") );

	s_startserver_dmoptions_action.generic.type = MTYPE_ACTION;
	s_startserver_dmoptions_action.generic.name	= "Game flags";
	s_startserver_dmoptions_action.generic.flags= QMF_LEFT_JUSTIFY;
	s_startserver_dmoptions_action.generic.x	= 8;
	s_startserver_dmoptions_action.generic.y	= 142;
///	s_startserver_dmoptions_action.generic.statusbar = NULL;
	s_startserver_dmoptions_action.generic.callback = DMOptionsFunc;
	s_startserver_dmoptions_action.generic.statusbarfunc = DrawLevelShot;

	s_startserver_start_action.generic.type = MTYPE_ACTION;
	s_startserver_start_action.generic.name	= "Begin";
	s_startserver_start_action.generic.flags= QMF_LEFT_JUSTIFY;
	s_startserver_start_action.generic.x	= 104;
	s_startserver_start_action.generic.y	= 142;
	if (dedicated)
		s_startserver_start_action.generic.callback = StartModGame;
	else
		s_startserver_start_action.generic.callback = StartServerActionFunc;
	s_startserver_start_action.generic.statusbarfunc = DrawLevelShot;

	Menu_AddItem( &s_startserver_menu, &s_startmap_list );
	Menu_AddItem( &s_startserver_menu, &s_mapname_title );
	Menu_AddItem( &s_startserver_menu, &s_mapname_field );
	Menu_AddItem( &s_startserver_menu, &s_rules_title );
	Menu_AddItem( &s_startserver_menu, &s_rules_box );
	Menu_AddItem( &s_startserver_menu, &s_timelimit_title );
	Menu_AddItem( &s_startserver_menu, &s_timelimit_field );
	Menu_AddItem( &s_startserver_menu, &s_fraglimit_title );
	Menu_AddItem( &s_startserver_menu, &s_fraglimit_field );
	Menu_AddItem( &s_startserver_menu, &s_maxclients_title );
	Menu_AddItem( &s_startserver_menu, &s_maxclients_field );
	Menu_AddItem( &s_startserver_menu, &s_skill_title );
	Menu_AddItem( &s_startserver_menu, &s_skill_box );
	Menu_AddItem( &s_startserver_menu, &s_hostname_title );
	Menu_AddItem( &s_startserver_menu, &s_hostname_field );
	Menu_AddItem( &s_startserver_menu, &s_startserver_dmoptions_action );
	Menu_AddItem( &s_startserver_menu, &s_startserver_start_action );

	Menu_Center( &s_startserver_menu );

	// call this now to set proper inital state
	RulesChangeFunc ( NULL );
}


void StartServer_MenuDraw()
{
	M_Banner( "m_banner_start_server.tga" );
	Menu_Draw( &s_startserver_menu );
}


int StartServer_MenuKey( int key )
{
	if ( key == K_ESCAPE )
		FreeMapNames();
	return Default_MenuKey( &s_startserver_menu, key );
}


void M_Menu_StartServer_f ()
{
	StartServer_MenuInit(false);
	M_PushMenu( StartServer_MenuDraw, StartServer_MenuKey );
}

void StartNetworkServerFunc( void *unused )
{
	M_Menu_StartServer_f ();
}

void M_Menu_ModServer_f ()
{
	StartServer_MenuInit(true);
	M_PushMenu( StartServer_MenuDraw, StartServer_MenuKey );
}

void StartModServerFunc( void *unused )
{
	M_Menu_ModServer_f ();
}

void HandednessCallback( void *unused )
{
	Cvar_SetValue( "hand", s_player_handedness_box.curvalue );
}


static void RateCallback( void *unused )
{
	if (s_player_rate_box.curvalue != sizeof(rate_tbl) / sizeof(*rate_tbl))
		Cvar_SetValue( "rate", rate_tbl[s_player_rate_box.curvalue] );
}


static void HandicapCallback( void *unused )
{
	if (s_player_handicap_box.curvalue != sizeof(handicap_tbl) / sizeof(*handicap_tbl))
		Cvar_SetValue( "cl_handicap", handicap_tbl[s_player_handicap_box.curvalue] );
}


static void PoseCallback( void *unused )
{
	int j = sizeof(pose_names) / sizeof(*pose_names)-1;
	pose = s_player_pose_box.curvalue;
	if (pose<0)		pose = 0;
	if (pose>=j)	pose = j-1;
}


static void RotCallback( void *unused )
{
	int j = sizeof(pose_rot_names) / sizeof(*pose_rot_names)-1;
	pose_rot = s_player_rot_box.curvalue;
	if (pose_rot<0)		pose_rot = 0;
	if (pose_rot>=j)	pose_rot = j-1;
}


void DrawMapShot(void *notused)
{
	image_t	*gl;
	char	file[MAX_QPATH+16];

	Menu_DrawStatusBar("use LEFT/RIGHT arrows and ENTER");

	Com_sprintf(file, sizeof(file), "levelshots/%s.jpg", map_names[s_maps_list.curvalue]);

	gl = Draw_FindPic (file);
	if (!gl)
	{
		gl = draw_conback;
		if (!gl)
		{
			Com_DPrintf("^1Can't find pic: %s\n", file);		// berserker: don't flood!
			return;
		}
	}

	int	x0 = ((viddef.width-320)>>1) + 140;
	int	y0 = ((viddef.height-240)>>1) + 64;
	int x1 = x0 + 160;
	int y1 = y0 + 120;

	Adjust320to640(&x0, &y0, &x1, &y1);

	GL_Bind2D (gl->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (x0, y0);
	glTexCoord2f (1, 0);
	glVertex2f (x1, y0);
	glTexCoord2f (1, 1);
	glVertex2f (x1, y1);
	glTexCoord2f (0, 1);
	glVertex2f (x0, y1);
	glEnd ();
}

void Adjust320to640(int *x0, int *y0, int *x1, int *y1)
{
	int hw = viddef.width>>1;		// FIXME: make viddef.halfWidth and viddef.halfHeight ???
	int hh = viddef.height>>1;
	*x0 = ((*x0 - hw)<<1) + hw;
	*y0 = ((*y0 - hh)<<1) + hh;
	*x1 = ((*x1 - hw)<<1) + hw;
	*y1 = ((*y1 - hh)<<1) + hh;
}

void StartMapActionFunc( void *self )
{
	char	startmap[1024];

	strcpy( startmap, map_names[s_maps_list.curvalue] );

	Cvar_SetValue ("deathmatch", 0 );
	Cvar_SetValue ("coop", 0 );

	Cbuf_AddText (va("map %s\n", startmap));

	M_ForceMenuOff ();
}

bool Maps_MenuInit()
{
	M_LoadMapNames();
	if (!num_maps)
		return false;

	// initialize the menu stuff
	s_maps_menu.x = viddef.width * 0.50;
	s_maps_menu.nitems = 0;

	s_maps_title.generic.type = MTYPE_SEPARATOR;
	s_maps_title.generic.name = "initial map";
	s_maps_title.generic.x = -56;
	s_maps_title.generic.y = 0;

	s_maps_list.generic.type = MTYPE_SPINCONTROL;
	s_maps_list.generic.x	= -150;
	s_maps_list.generic.y	= 10;
	s_maps_list.generic.name	= 0;
	s_maps_list.itemnames = (const char **) map_names;
	s_maps_list.generic.statusbarfunc = DrawMapShot;
	s_maps_list.generic.cursor_offset = -150;

	s_skill_title.generic.type = MTYPE_SEPARATOR;
	s_skill_title.generic.name = "skill";
	s_skill_title.generic.x = -104;
	s_skill_title.generic.y = 30;

	s_skill_box.generic.type = MTYPE_SPINCONTROL;
	s_skill_box.generic.x	= -152;
	s_skill_box.generic.y	= 40;
	s_skill_box.generic.name	= 0;
	s_skill_box.generic.cursor_offset = -152;
	s_skill_box.itemnames = skill_names;
	s_skill_box.generic.statusbarfunc = DrawMapShot;

	s_skill_box.curvalue = Cvar_VariableValue("skill");
	if (s_skill_box.curvalue<0)	s_skill_box.curvalue=0;
	if (s_skill_box.curvalue>3)	s_skill_box.curvalue=3;
	s_skill_box.generic.callback = SkillChangeFunc;

	Menu_AddItem( &s_maps_menu, &s_maps_list );
	Menu_AddItem( &s_maps_menu, &s_maps_title );
	Menu_AddItem( &s_maps_menu, &s_skill_title );
	Menu_AddItem( &s_maps_menu, &s_skill_box );

	Menu_Center( &s_maps_menu );

	// call this now to set proper inital state
	SkillChangeFunc ( NULL );

	return true;
}


void Maps_MenuDraw()
{
	M_Banner( "m_banner_select_map.tga" );
	Menu_Draw( &s_maps_menu );
}


int Maps_MenuKey( int key )
{
	if ( key == K_KP_ENTER || key == K_ENTER)
		StartMapActionFunc(NULL);
	return Default_MenuKey( &s_maps_menu, key );
}


void SelectMapFunc( void *data )
{
	if (Maps_MenuInit())
		M_PushMenu( Maps_MenuDraw, Maps_MenuKey );
}


void ModCallback( void *self )
{
	menuaction_s *a = ( menuaction_s * ) self;

	if ( s_mods_menu.cursor < MAX_MODS )
		Cbuf_AddText (va("game %s\n", m_mod_names[s_mods_menu.cursor] ) );
	M_ForceMenuOff ();
}


void Mods_MenuDraw()
{
	Menu_Draw( &s_mods_menu );
}

int Mods_MenuKey( int key )
{
	return Default_MenuKey( &s_mods_menu, key );
}

void SelectModFunc( void *data )
{
	if (Mods_MenuInit())
		M_PushMenu( Mods_MenuDraw, Mods_MenuKey );
}


int pmicmpfnc( const void *_a, const void *_b )
{
	const playermodelinfo_s *a = ( const playermodelinfo_s * ) _a;
	const playermodelinfo_s *b = ( const playermodelinfo_s * ) _b;

	/*
	** sort by male, female, then alphabetical
	*/
	if ( strcmp( a->directory, "male" ) == 0 )
		return -1;
	else if ( strcmp( b->directory, "male" ) == 0 )
		return 1;

	if ( strcmp( a->directory, "female" ) == 0 )
		return -1;
	else if ( strcmp( b->directory, "female" ) == 0 )
		return 1;

	return strcmp( a->directory, b->directory );
}


static void ModelCallback( void *unused )
{
	s_player_skin_box.itemnames = (const char **) s_pmi[s_player_model_box.curvalue].skindisplaynames;
	s_player_skin_box.curvalue = 0;
	currentPlayerWeapon = NULL;		// сбросить модель оружия, если сменили плеер-модель
}


static void DownloadCallback( void *self )
{
	menulist_s *f = ( menulist_s * ) self;

	if (f == &s_allow_download_box)
	{
		Cvar_SetValue("allow_download", f->curvalue);
	}

	else if (f == &s_allow_download_maps_box)
	{
		Cvar_SetValue("allow_download_maps", f->curvalue);
	}

	else if (f == &s_allow_download_models_box)
	{
		Cvar_SetValue("allow_download_models", f->curvalue);
	}

	else if (f == &s_allow_download_players_box)
	{
		Cvar_SetValue("allow_download_players", f->curvalue);
	}

	else if (f == &s_allow_download_sounds_box)
	{
		Cvar_SetValue("allow_download_sounds", f->curvalue);
	}
}


void DownloadOptions_MenuInit()
{
	static const char *yes_no_names[] =
	{
		"no", "yes", 0
	};
	int y = 0;

	s_downloadoptions_menu.x = viddef.width * 0.50;
	s_downloadoptions_menu.nitems = 0;

	s_download_title.generic.type = MTYPE_SEPARATOR;
	s_download_title.generic.name = "Download Options";
	s_download_title.generic.x    = 48;
	s_download_title.generic.y	 = y;

	s_allow_download_box.generic.type = MTYPE_SPINCONTROL;
	s_allow_download_box.generic.x	= 0;
	s_allow_download_box.generic.y	= y += 20;
	s_allow_download_box.generic.name	= "allow downloading";
	s_allow_download_box.generic.callback = DownloadCallback;
	s_allow_download_box.itemnames = yes_no_names;
	s_allow_download_box.curvalue = (Cvar_VariableValue("allow_download") != 0);

	s_allow_download_maps_box.generic.type = MTYPE_SPINCONTROL;
	s_allow_download_maps_box.generic.x	= 0;
	s_allow_download_maps_box.generic.y	= y += 20;
	s_allow_download_maps_box.generic.name	= "maps";
	s_allow_download_maps_box.generic.callback = DownloadCallback;
	s_allow_download_maps_box.itemnames = yes_no_names;
	s_allow_download_maps_box.curvalue = (Cvar_VariableValue("allow_download_maps") != 0);

	s_allow_download_players_box.generic.type = MTYPE_SPINCONTROL;
	s_allow_download_players_box.generic.x	= 0;
	s_allow_download_players_box.generic.y	= y += 10;
	s_allow_download_players_box.generic.name	= "player models/skins";
	s_allow_download_players_box.generic.callback = DownloadCallback;
	s_allow_download_players_box.itemnames = yes_no_names;
	s_allow_download_players_box.curvalue = (Cvar_VariableValue("allow_download_players") != 0);

	s_allow_download_models_box.generic.type = MTYPE_SPINCONTROL;
	s_allow_download_models_box.generic.x	= 0;
	s_allow_download_models_box.generic.y	= y += 10;
	s_allow_download_models_box.generic.name	= "models";
	s_allow_download_models_box.generic.callback = DownloadCallback;
	s_allow_download_models_box.itemnames = yes_no_names;
	s_allow_download_models_box.curvalue = (Cvar_VariableValue("allow_download_models") != 0);

	s_allow_download_sounds_box.generic.type = MTYPE_SPINCONTROL;
	s_allow_download_sounds_box.generic.x	= 0;
	s_allow_download_sounds_box.generic.y	= y += 10;
	s_allow_download_sounds_box.generic.name	= "sounds";
	s_allow_download_sounds_box.generic.callback = DownloadCallback;
	s_allow_download_sounds_box.itemnames = yes_no_names;
	s_allow_download_sounds_box.curvalue = (Cvar_VariableValue("allow_download_sounds") != 0);

	Menu_AddItem( &s_downloadoptions_menu, &s_download_title );
	Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_box );
	Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_maps_box );
	Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_players_box );
	Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_models_box );
	Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_sounds_box );

	Menu_Center( &s_downloadoptions_menu );

	// skip over title
	if (s_downloadoptions_menu.cursor == 0)
		s_downloadoptions_menu.cursor = 1;
}

void DownloadOptions_MenuDraw()
{
	Menu_Draw( &s_downloadoptions_menu );
}

int DownloadOptions_MenuKey( int key )
{
	return Default_MenuKey( &s_downloadoptions_menu, key );
}

void M_Menu_DownloadOptions_f ()
{
	DownloadOptions_MenuInit();
	M_PushMenu( DownloadOptions_MenuDraw, DownloadOptions_MenuKey );
}

void DownloadOptionsFunc( void *self )
{
	M_Menu_DownloadOptions_f();
}


void Menu_DrawNameStatusBar( void* unused )
{
	Menu_DrawStatusBarColored(s_player_name_field.buffer);
}

void railCallback( void* unused )
{
	Cvar_Set( "rail_radius", s_player_radius_field.buffer );
	if(railtrailradius->value<0 || railtrailradius->value>127)
		Cvar_SetValue("rail_radius", 3);
	Cvar_SetValue( "rail_spiral", s_player_spiral_box.curvalue );
	if(railtrailspiral->value<0 || railtrailspiral->value>7)
		Cvar_SetValue("rail_spiral", 1);
	Cvar_SetValue( "rail_core", s_player_core_box.curvalue );

	if (railtrailcore->value == 0 && railtrailradius->value == 0)
		Menu_DrawStatusBar("WARNING: rail trail will be Quake2 style");
	else
	{
		char *core, *spiral;
		core = spiral = "";
		if (railtrailcore->value)
		{
			if (railtrailcore->value == 1)
				core = "^4Core";
			else if (railtrailcore->value == 2)
				core = "^2Core";
			else if (railtrailcore->value == 3)
				core = "^5Core";
			else if (railtrailcore->value == 4)
				core = "^1Core";
			else if (railtrailcore->value == 5)
				core = "^6Core";
			else if (railtrailcore->value == 6)
				core = "^3Core";
			else if (railtrailcore->value == 7)
				core = "^7Core";
		}
		if (railtrailradius->value)
		{
			if (railtrailspiral->value == 0)
				spiral = "^1S^2p^3i^4r^5a^6l";
			else if (railtrailspiral->value == 1)
				spiral = "^4Spiral";
			else if (railtrailspiral->value == 2)
				spiral = "^2Spiral";
			else if (railtrailspiral->value == 3)
				spiral = "^5Spiral";
			else if (railtrailspiral->value == 4)
				spiral = "^1Spiral";
			else if (railtrailspiral->value == 5)
				spiral = "^6Spiral";
			else if (railtrailspiral->value == 6)
				spiral = "^3Spiral";
			else if (railtrailspiral->value == 7)
				spiral = "^7Spiral";
		}
		char	str[MAX_QPATH];
		Com_sprintf (str, sizeof(str), "Rail trail: %s %s ^7radius %i", core, spiral, (int)railtrailradius->value);
		Menu_DrawStatusBarColored(str);
	}
}

bool PlayerConfig_MenuInit()
{
	char currentdirectory[1024];
	char currentskin[1024];
	int i = 0, j;

	int currentdirectoryindex = 0;
	int currentskinindex = 0;

///	cvar_t *hand = Cvar_Get( "hand", "0", CVAR_USERINFO | CVAR_ARCHIVE );

	PlayerConfig_ScanDirectories();

	if (s_numplayermodels == 0)
		return false;

	if ( hand->value < 0 || hand->value > 2 )
		Cvar_SetValue( "hand", 0 );

#if 0
	strcpy( currentdirectory, skin->string );
#else
	strncpy( currentdirectory, skin->string, sizeof(currentdirectory)-1 );
#endif

	if ( strchr( currentdirectory, '/' ) )
	{
		strcpy( currentskin, strchr( currentdirectory, '/' ) + 1 );
		*strchr( currentdirectory, '/' ) = 0;
	}
	else if ( strchr( currentdirectory, '\\' ) )
	{
		strcpy( currentskin, strchr( currentdirectory, '\\' ) + 1 );
		*strchr( currentdirectory, '\\' ) = 0;
	}
	else
	{
		strcpy( currentdirectory, "male" );
		strcpy( currentskin, "grunt" );
	}

	qsort( s_pmi, s_numplayermodels, sizeof( s_pmi[0] ), pmicmpfnc );

	Memset( s_pmnames, 0, sizeof( s_pmnames ) );
	for ( i = 0; i < s_numplayermodels; i++ )
	{
		s_pmnames[i] = s_pmi[i].displayname;
		if ( Q_strcasecmp( s_pmi[i].directory, currentdirectory ) == 0 )
		{
			int j;

			currentdirectoryindex = i;

			for ( j = 0; j < s_pmi[i].nskins; j++ )
			{
				if ( Q_strcasecmp( s_pmi[i].skindisplaynames[j], currentskin ) == 0 )
				{
					currentskinindex = j;
					break;
				}
			}
		}
	}

	s_player_config_menu.x = viddef.width / 2 - 95; 
	s_player_config_menu.y = viddef.height / 2 - 97;
	s_player_config_menu.nitems = 0;

	s_player_name_field.generic.type = MTYPE_FIELD;
	s_player_name_field.generic.name = "name";
	s_player_name_field.generic.callback = 0;
	s_player_name_field.generic.x		= 0;
	s_player_name_field.generic.y		= 0;
	s_player_name_field.length	= 27;
	s_player_name_field.visible_length = 27;
	strcpy( s_player_name_field.buffer, name->string );
	s_player_name_field.cursor = strlen( name->string );
	s_player_name_field.generic.statusbarfunc = Menu_DrawNameStatusBar;

	s_player_model_title.generic.type	= MTYPE_SEPARATOR;
	s_player_model_title.generic.name	= "model";
	s_player_model_title.generic.x		= -8;	///-8	+44;
	s_player_model_title.generic.y		= 16;	///60	-32;

	s_player_model_box.generic.type = MTYPE_SPINCONTROL;
	s_player_model_box.generic.x	= -40	+44;	///-56	+44;
	s_player_model_box.generic.y	= 16;	/// 70	-32;
	s_player_model_box.generic.callback = ModelCallback;
	s_player_model_box.generic.cursor_offset = -32	+44;	///-48	+44;
	s_player_model_box.curvalue = currentdirectoryindex;
	s_player_model_box.itemnames = (const char **) s_pmnames;

	s_player_core_title.generic.type	= MTYPE_SEPARATOR;
	s_player_core_title.generic.name	= "core";
	s_player_core_title.generic.x		= -24	+44;
	s_player_core_title.generic.y		= 60	-32 + 4;

	s_player_core_box.generic.type = MTYPE_SPINCONTROL;
	s_player_core_box.generic.x	= -24	+44 +8;
	s_player_core_box.generic.y	= 60	-32 + 4;
	s_player_core_box.generic.name	= 0;
	s_player_core_box.generic.callback = 0;
	s_player_core_box.generic.statusbarfunc = railCallback;
	s_player_core_box.generic.cursor_offset = -24	+44+16;
	s_player_core_box.curvalue = Cvar_VariableValue("rail_core");
	s_player_core_box.itemnames = coreColorNames;

	s_player_spiral_title.generic.type	= MTYPE_SEPARATOR;
	s_player_spiral_title.generic.name	= "spir.";
	s_player_spiral_title.generic.x		= -24	+44 +8;
	s_player_spiral_title.generic.y		= 60	-32 + 12;

	s_player_spiral_box.generic.type = MTYPE_SPINCONTROL;
	s_player_spiral_box.generic.x	= -24	+44 +8;
	s_player_spiral_box.generic.y	= 60	-32 + 12;
	s_player_spiral_box.generic.name	= 0;
	s_player_spiral_box.generic.callback = 0;
	s_player_spiral_box.generic.statusbarfunc = railCallback;
	s_player_spiral_box.generic.cursor_offset = -24	+44+16;
	s_player_spiral_box.curvalue = Cvar_VariableValue("rail_spiral");
	s_player_spiral_box.itemnames = spiralColorNames;

	s_player_radius_title.generic.type	= MTYPE_SEPARATOR;
	s_player_radius_title.generic.name	= "radius";
	s_player_radius_title.generic.x		= -24	+44+16;
	s_player_radius_title.generic.y		= 60	-32 + 20+4;

	s_player_radius_field.generic.type = MTYPE_FIELD;
	s_player_radius_field.generic.name = 0;
	s_player_radius_field.generic.flags = QMF_NUMBERSONLY;
	s_player_radius_field.generic.x	= -24	+44 +8;
	s_player_radius_field.generic.y	= 60	-32 + 20+4;
	s_player_radius_field.length = 3;
	s_player_radius_field.visible_length = 3;
	s_player_radius_field.generic.statusbarfunc = railCallback;
	strcpy( s_player_radius_field.buffer, Cvar_VariableString("rail_radius") );

	s_player_skin_title.generic.type = MTYPE_SEPARATOR;
	s_player_skin_title.generic.name = "skin";
	s_player_skin_title.generic.x    = -16;
	s_player_skin_title.generic.y	 = 84	-24;

	s_player_skin_box.generic.type = MTYPE_SPINCONTROL;
	s_player_skin_box.generic.x	= -56;
	s_player_skin_box.generic.y	= 94	-24;
	s_player_skin_box.generic.name	= 0;
	s_player_skin_box.generic.callback = 0;
	s_player_skin_box.generic.cursor_offset = -48;
	s_player_skin_box.curvalue = currentskinindex;
	s_player_skin_box.itemnames = (const char **) s_pmi[currentdirectoryindex].skindisplaynames;

	s_player_hand_title.generic.type = MTYPE_SEPARATOR;
	s_player_hand_title.generic.name = "handedness";
	s_player_hand_title.generic.x    = 32;
	s_player_hand_title.generic.y	 = 108	-24;

	s_player_handedness_box.generic.type = MTYPE_SPINCONTROL;
	s_player_handedness_box.generic.x	= -56;
	s_player_handedness_box.generic.y	= 118	-24;
	s_player_handedness_box.generic.name	= 0;
	s_player_handedness_box.generic.cursor_offset = -48;
	s_player_handedness_box.generic.callback = HandednessCallback;
	s_player_handedness_box.curvalue = Cvar_VariableValue( "hand" );
	s_player_handedness_box.itemnames = handedness;

	j = sizeof(handicap_tbl) / sizeof(*handicap_tbl);
	for (i = 0; i < j; i++)
		if (Cvar_VariableValue("cl_handicap") == handicap_tbl[i])
			break;
	if (i==j)
	{
		Cvar_SetValue("cl_handicap", 100);		/// Сброс в default value если handicap не "вписывается" в стандартные значения
		for (i = 0; i < j; i++)
			if (Cvar_VariableValue("cl_handicap") == handicap_tbl[i])
				break;
	}

	s_player_handicap_title.generic.type = MTYPE_SEPARATOR;
	s_player_handicap_title.generic.name = "handicap";
	s_player_handicap_title.generic.x    = 16;
	s_player_handicap_title.generic.y	 = 132	-24;

	s_player_handicap_box.generic.type = MTYPE_SPINCONTROL;
	s_player_handicap_box.generic.x	= -56;
	s_player_handicap_box.generic.y	= 142	-24;
	s_player_handicap_box.generic.name	= 0;
	s_player_handicap_box.generic.cursor_offset = -48;
	s_player_handicap_box.generic.callback = HandicapCallback;
	s_player_handicap_box.curvalue = i;
	s_player_handicap_box.itemnames = handicap_names;

	j = sizeof(rate_tbl) / sizeof(*rate_tbl);
	for (i = 0; i < j; i++)
		if (Cvar_VariableValue("rate") == rate_tbl[i])
			break;
	if (i==j)
	{
		Cvar_SetValue("rate", 25000);		/// Сброс в default value если rate не "вписывается" в стандартные значения
		for (i = 0; i < j; i++)
			if (Cvar_VariableValue("rate") == rate_tbl[i])
				break;
	}

	s_player_rate_title.generic.type = MTYPE_SEPARATOR;
	s_player_rate_title.generic.name = "connect speed";
	s_player_rate_title.generic.x    = 56;
	s_player_rate_title.generic.y	 = 156 - 24;

	s_player_rate_box.generic.type = MTYPE_SPINCONTROL;
	s_player_rate_box.generic.x	= -56;
	s_player_rate_box.generic.y	= 166 - 24;
	s_player_rate_box.generic.name	= 0;
	s_player_rate_box.generic.cursor_offset = -48;
	s_player_rate_box.generic.callback = RateCallback;
	s_player_rate_box.curvalue = i;
	s_player_rate_box.itemnames = rate_names;

	s_player_download_action.generic.type = MTYPE_ACTION;
	s_player_download_action.generic.name	= "download options";
	s_player_download_action.generic.flags= QMF_LEFT_JUSTIFY;
	s_player_download_action.generic.x	= -24;
	s_player_download_action.generic.y	= 180 - 24;
	s_player_download_action.generic.statusbar = NULL;
	s_player_download_action.generic.callback = DownloadOptionsFunc;

	j = sizeof(pose_names) / sizeof(*pose_names)-1;
	if (pose<0)		pose = 0;
	if (pose>=j)	pose = j-1;

	s_player_pose_title.generic.type = MTYPE_SEPARATOR;
	s_player_pose_title.generic.name = "Demonstration";
	s_player_pose_title.generic.x    = 56;
	s_player_pose_title.generic.y	 = 194	-24;

	s_player_pose_box.generic.type = MTYPE_SPINCONTROL;
	s_player_pose_box.generic.x	= -56;
	s_player_pose_box.generic.y	= 204	-24;
	s_player_pose_box.generic.name	= 0;
	s_player_pose_box.generic.cursor_offset = -48;
	s_player_pose_box.generic.callback = PoseCallback;
	s_player_pose_box.curvalue = pose;
	s_player_pose_box.itemnames = pose_names;

	j = sizeof(pose_rot_names) / sizeof(*pose_rot_names)-1;
	if (pose_rot<0)		pose_rot = 0;
	if (pose_rot>=j)	pose_rot = j-1;

	s_player_rot_box.generic.type = MTYPE_SPINCONTROL;
	s_player_rot_box.generic.x	= -56;
	s_player_rot_box.generic.y	= 214	-24;
	s_player_rot_box.generic.name	= 0;
	s_player_rot_box.generic.cursor_offset = -48;
	s_player_rot_box.generic.callback = RotCallback;
	s_player_rot_box.curvalue = pose_rot;
	s_player_rot_box.itemnames = pose_rot_names;

	Menu_AddItem( &s_player_config_menu, &s_player_name_field );
	Menu_AddItem( &s_player_config_menu, &s_player_model_title );
	Menu_AddItem( &s_player_config_menu, &s_player_model_box );
	Menu_AddItem( &s_player_config_menu, &s_player_core_title );
	Menu_AddItem( &s_player_config_menu, &s_player_core_box );
	Menu_AddItem( &s_player_config_menu, &s_player_spiral_title );
	Menu_AddItem( &s_player_config_menu, &s_player_spiral_box );
	Menu_AddItem( &s_player_config_menu, &s_player_radius_title );
	Menu_AddItem( &s_player_config_menu, &s_player_radius_field );
	if ( s_player_skin_box.itemnames )
	{
		Menu_AddItem( &s_player_config_menu, &s_player_skin_title );
		Menu_AddItem( &s_player_config_menu, &s_player_skin_box );
	}
	Menu_AddItem( &s_player_config_menu, &s_player_hand_title );
	Menu_AddItem( &s_player_config_menu, &s_player_handedness_box );
	Menu_AddItem( &s_player_config_menu, &s_player_handicap_title );
	Menu_AddItem( &s_player_config_menu, &s_player_handicap_box );
	Menu_AddItem( &s_player_config_menu, &s_player_rate_title );
	Menu_AddItem( &s_player_config_menu, &s_player_rate_box );
	Menu_AddItem( &s_player_config_menu, &s_player_download_action );
	Menu_AddItem( &s_player_config_menu, &s_player_pose_title );
	Menu_AddItem( &s_player_config_menu, &s_player_pose_box );
	Menu_AddItem( &s_player_config_menu, &s_player_rot_box );

	return true;
}


float CalcFov (float fov_x, float width, float height)
{
	float	a;
	float	x;

	if (fov_x < 1 || fov_x > 179)
		Com_Error (ERR_DROP, "Bad fov: %f", fov_x);

// Micro$oft's VS2008 compiler lame!  >:E
#if 0
	x = width/tan(fov_x/360*M_PI);		/* This caused: fatal error C1001: An internal error has occurred in the compiler. */
#else
	float half_fov_deg = fov_x / 2.0;
	float half_fov_rad = DEG2RAD(half_fov_deg);
	float tan_half_fov_rad = tan(half_fov_rad);
	x = width / tan_half_fov_rad;
#endif

	a = atan (height/x);

	a = a*360/M_PI;

	return a;
}


void M_Menu_PlayerConfig_f ()
{
	if (!PlayerConfig_MenuInit())
	{
		Menu_SetStatusBar( &s_multiplayer_menu, "No valid player models found" );
		return;
	}
	Menu_SetStatusBar( &s_multiplayer_menu, NULL );
	M_PushMenu( PlayerConfig_MenuDraw, PlayerConfig_MenuKey );
}


void PlayerSetupFunc( void *unused )
{
	M_Menu_PlayerConfig_f();
}


void Multiplayer_MenuInit()
{
	int	add;
	s_multiplayer_menu.x = viddef.width * 0.50 - 64;
	s_multiplayer_menu.nitems = 0;

	s_join_network_server_action.generic.type	= MTYPE_ACTION;
	s_join_network_server_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_join_network_server_action.generic.x		= 0;
	s_join_network_server_action.generic.y		= 0;
	s_join_network_server_action.generic.name	= " join network server";
	s_join_network_server_action.generic.callback = JoinNetworkServerFunc;
	s_join_network_server_action.generic.statusbar = "Find and join remote server";

	s_start_network_server_action.generic.type	= MTYPE_ACTION;
	s_start_network_server_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_start_network_server_action.generic.x		= 0;
	s_start_network_server_action.generic.y		= 16;
	s_start_network_server_action.generic.name	= " start network server";
	s_start_network_server_action.generic.callback = StartNetworkServerFunc;
	s_start_network_server_action.generic.statusbar = "Local server using Game.dll";

	if (net_compatibility->value)
	{
		static char	gamename[64];
		cvar_t *gamedir = Cvar_Get ("game", "", CVAR_LATCH|CVAR_SERVERINFO);
		if (gamedir->string[0])
			Com_sprintf(gamename, sizeof(gamename), " start %s server", gamedir->string);
		else
			Com_sprintf(gamename, sizeof(gamename), " start Quake2 server");
		s_start_mod_server_action.generic.type	= MTYPE_ACTION;
		s_start_mod_server_action.generic.flags  = QMF_LEFT_JUSTIFY;
		s_start_mod_server_action.generic.x		= 0;
		s_start_mod_server_action.generic.y		= 26;
		s_start_mod_server_action.generic.name	= gamename;
		s_start_mod_server_action.generic.callback = StartModServerFunc;
		s_start_mod_server_action.generic.statusbar = "Dedicated server using GameX86.dll";
		add = 10;
	}
	else
		add = 0;

	s_disconnect_action.generic.type	= MTYPE_ACTION;
	s_disconnect_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_disconnect_action.generic.x		= 0;
	s_disconnect_action.generic.y		= 32 + add;
	s_disconnect_action.generic.name	= " disconnect";
	s_disconnect_action.generic.callback = DisconnectFunc;
	s_disconnect_action.generic.statusbar = "Disconnect remote server";

	s_killserver_action.generic.type	= MTYPE_ACTION;
	s_killserver_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_killserver_action.generic.x		= 0;
	s_killserver_action.generic.y		= 42 + add;
	s_killserver_action.generic.name	= " kill local server";
	s_killserver_action.generic.callback = KillServerFunc;
	s_killserver_action.generic.statusbar = "Stop video, demo or game";

	s_player_setup_action.generic.type	= MTYPE_ACTION;
	s_player_setup_action.generic.flags  = QMF_LEFT_JUSTIFY;
	s_player_setup_action.generic.x		= 0;
	s_player_setup_action.generic.y		= 58 + add;
	s_player_setup_action.generic.name	= " player setup";
	s_player_setup_action.generic.callback = PlayerSetupFunc;

	Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_join_network_server_action );
	Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_start_network_server_action );
	if (net_compatibility->value)
		Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_start_mod_server_action );
	Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_disconnect_action );
	Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_killserver_action );
	Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_player_setup_action );

	Menu_SetStatusBar( &s_multiplayer_menu, NULL );

	Menu_Center( &s_multiplayer_menu );
}


int Multiplayer_MenuKey( int key )
{
	return Default_MenuKey( &s_multiplayer_menu, key );
}


void Multiplayer_MenuDraw ()
{
	M_Banner( "m_banner_multiplayer.tga" );

	Menu_AdjustCursor( &s_multiplayer_menu, 1 );
	Menu_Draw( &s_multiplayer_menu );
}


void M_Menu_Multiplayer_f()
{
	Multiplayer_MenuInit();
	M_PushMenu( Multiplayer_MenuDraw, Multiplayer_MenuKey );
}


void UpdateVolumeFunc( void *unused )
{
	Cvar_SetValue( "s_volume", s_options_sfxvolume_slider.curvalue / 10 );
}


void ToggleSoundFunc( void *unused )
{
	Cvar_SetValue( "s_initsound", s_options_onoff_list.curvalue );

	if (s_options_onoff_list.curvalue)
		s_options_onoff_list.generic.statusbar = "Sound enabled";
	else
		s_options_onoff_list.generic.statusbar = "No sound";

	switch (s_options_quality_list.curvalue)
	{
		case 0:
			Cvar_SetValue("s_quality", 0);
			s_options_quality_list.generic.statusbar = "Let the system decide";
			break;
		case 1:
			Cvar_SetValue("s_quality", 22050);
			s_options_quality_list.generic.statusbar = "22050 Hz";
			break;
		default:
		case 2:
			Cvar_SetValue("s_quality", 44100);
			s_options_quality_list.generic.statusbar = "44100 Hz";
			break;
		case 3:
			Cvar_SetValue("s_quality", 48000);
			s_options_quality_list.generic.statusbar = "48000 Hz";
			break;
		case 4:
			Cvar_SetValue("s_quality", 88200);
			s_options_quality_list.generic.statusbar = "88200 Hz";
			break;
		case 5:
			Cvar_SetValue("s_quality", 96000);
			s_options_quality_list.generic.statusbar = "96000 Hz";
			break;
		case 6:
			Cvar_SetValue("s_quality", 176400);
			s_options_quality_list.generic.statusbar = "176400 Hz";
			break;
		case 7:
			Cvar_SetValue("s_quality", 192000);
			s_options_quality_list.generic.statusbar = "192000 Hz";
			break;
	}

	CL_Snd_Restart_f();
}


void MouseSpeedFunc( void *unused )
{
	Cvar_SetValue( "sensitivity", s_options_sensitivity_slider.curvalue / 5.0F/*2.0F*/ );
}


void AlwaysRunFunc( void *unused )
{
	Cvar_SetValue( "cl_run", s_options_alwaysrun_box.curvalue );
}


void InvertMouseFunc( void *unused )
{
	if (old_mouse_support)
		Cvar_SetValue( "m_pitch", -m_pitch->value );
	else
		Cvar_SetValue( "m_inversion", !(m_inversion->value) );
}


void LookspringFunc( void *unused )
{
	Cvar_SetValue( "lookspring", !lookspring->value );
}


void LookstrafeFunc( void *unused )
{
	Cvar_SetValue( "lookstrafe", !lookstrafe->value );
}


void FreeLookFunc( void *unused )
{
	Cvar_SetValue( "freelook", s_options_freelook_box.curvalue );
}


void CrosshairFunc( void *unused )
{
	Cvar_SetValue( "crosshair", s_options_crosshair_box.curvalue );
}


void NoAltTabFunc( void *unused )
{
	Cvar_SetValue( "win_noalttab", s_options_noalttab_box.curvalue );
}

void KeyCursorDrawFunc( menuframework_s *menu )
{
	if ( bind_grab )
		Draw_Char( menu->x, menu->y + menu->cursor * 9, '=' );
	else
		Draw_Char( menu->x, menu->y + menu->cursor * 9, 12 + ( ( int ) ( Sys_Milliseconds() / 250 ) & 1 ) );
}


void M_FindKeysForCommand (char *command, int *twokeys)
{
	int		count;
	int		j;
	int		l;
	char	*b;

	twokeys[0] = twokeys[1] = -1;
	l = strlen(command);
	count = 0;

	for (j=0 ; j<256 ; j++)
	{
		b = keybindings[j];
		if (!b)
			continue;
		if (!strncmp (b, command, l) )
		{
			twokeys[count] = j;
			count++;
			if (count == 2)
				break;
		}
	}
}


void DrawKeyBindingFunc( void *self )
{
	int keys[2];
	menuaction_s *a = ( menuaction_s * ) self;

	M_FindKeysForCommand( bindnames[a->generic.localdata[0]][0], keys);
		
	if (keys[0] == -1)
	{
		Menu_DrawString( a->generic.x + a->generic.parent->x + 16, a->generic.y + a->generic.parent->y, "???" );
	}
	else
	{
		int x;
		const char *name;

		name = Key_KeynumToString (keys[0]);

		Menu_DrawString( a->generic.x + a->generic.parent->x + 16, a->generic.y + a->generic.parent->y, name );

		x = strlen(name) * 8;

		if (keys[1] != -1)
		{
			Menu_DrawString( a->generic.x + a->generic.parent->x + 24 + x, a->generic.y + a->generic.parent->y, "or" );
			Menu_DrawString( a->generic.x + a->generic.parent->x + 48 + x, a->generic.y + a->generic.parent->y, Key_KeynumToString (keys[1]) );
		}
	}
}


void DrawKey2BindingFunc( void *self )
{
	int keys[2];
	menuaction_s *a = ( menuaction_s * ) self;

	M_FindKeysForCommand( bindnames2[a->generic.localdata[0]][0], keys);
		
	if (keys[0] == -1)
	{
		Menu_DrawString( a->generic.x + a->generic.parent->x + 16, a->generic.y + a->generic.parent->y, "???" );
	}
	else
	{
		int x;
		const char *name;

		name = Key_KeynumToString (keys[0]);

		Menu_DrawString( a->generic.x + a->generic.parent->x + 16, a->generic.y + a->generic.parent->y, name );

		x = strlen(name) * 8;

		if (keys[1] != -1)
		{
			Menu_DrawString( a->generic.x + a->generic.parent->x + 24 + x, a->generic.y + a->generic.parent->y, "or" );
			Menu_DrawString( a->generic.x + a->generic.parent->x + 48 + x, a->generic.y + a->generic.parent->y, Key_KeynumToString (keys[1]) );
		}
	}
}


void Keys_MenuInit()
{
	int y = 0;
	int i = 0;

	s_keys_menu.x = viddef.width * 0.50;
	s_keys_menu.nitems = 0;
	s_keys_menu.cursordraw = KeyCursorDrawFunc;

	s_keys_attack_action.generic.type	= MTYPE_ACTION;
	s_keys_attack_action.generic.flags  = QMF_GRAYED;
	s_keys_attack_action.generic.x		= 0;
	s_keys_attack_action.generic.y		= y;
	s_keys_attack_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_attack_action.generic.localdata[0] = i;
	s_keys_attack_action.generic.name	= bindnames[i][1];

	s_keys_walk_forward_action.generic.type	= MTYPE_ACTION;
	s_keys_walk_forward_action.generic.flags  = QMF_GRAYED;
	s_keys_walk_forward_action.generic.x		= 0;
	s_keys_walk_forward_action.generic.y		= y += 9;
	s_keys_walk_forward_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_walk_forward_action.generic.localdata[0] = ++i;
	s_keys_walk_forward_action.generic.name	= bindnames[i][1];

	s_keys_backpedal_action.generic.type	= MTYPE_ACTION;
	s_keys_backpedal_action.generic.flags  = QMF_GRAYED;
	s_keys_backpedal_action.generic.x		= 0;
	s_keys_backpedal_action.generic.y		= y += 9;
	s_keys_backpedal_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_backpedal_action.generic.localdata[0] = ++i;
	s_keys_backpedal_action.generic.name	= bindnames[i][1];

	s_keys_turn_left_action.generic.type	= MTYPE_ACTION;
	s_keys_turn_left_action.generic.flags  = QMF_GRAYED;
	s_keys_turn_left_action.generic.x		= 0;
	s_keys_turn_left_action.generic.y		= y += 9;
	s_keys_turn_left_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_turn_left_action.generic.localdata[0] = ++i;
	s_keys_turn_left_action.generic.name	= bindnames[i][1];

	s_keys_turn_right_action.generic.type	= MTYPE_ACTION;
	s_keys_turn_right_action.generic.flags  = QMF_GRAYED;
	s_keys_turn_right_action.generic.x		= 0;
	s_keys_turn_right_action.generic.y		= y += 9;
	s_keys_turn_right_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_turn_right_action.generic.localdata[0] = ++i;
	s_keys_turn_right_action.generic.name	= bindnames[i][1];

	s_keys_run_action.generic.type	= MTYPE_ACTION;
	s_keys_run_action.generic.flags  = QMF_GRAYED;
	s_keys_run_action.generic.x		= 0;
	s_keys_run_action.generic.y		= y += 9;
	s_keys_run_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_run_action.generic.localdata[0] = ++i;
	s_keys_run_action.generic.name	= bindnames[i][1];

	s_keys_step_left_action.generic.type	= MTYPE_ACTION;
	s_keys_step_left_action.generic.flags  = QMF_GRAYED;
	s_keys_step_left_action.generic.x		= 0;
	s_keys_step_left_action.generic.y		= y += 9;
	s_keys_step_left_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_step_left_action.generic.localdata[0] = ++i;
	s_keys_step_left_action.generic.name	= bindnames[i][1];

	s_keys_step_right_action.generic.type	= MTYPE_ACTION;
	s_keys_step_right_action.generic.flags  = QMF_GRAYED;
	s_keys_step_right_action.generic.x		= 0;
	s_keys_step_right_action.generic.y		= y += 9;
	s_keys_step_right_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_step_right_action.generic.localdata[0] = ++i;
	s_keys_step_right_action.generic.name	= bindnames[i][1];

	s_keys_sidestep_action.generic.type	= MTYPE_ACTION;
	s_keys_sidestep_action.generic.flags  = QMF_GRAYED;
	s_keys_sidestep_action.generic.x		= 0;
	s_keys_sidestep_action.generic.y		= y += 9;
	s_keys_sidestep_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_sidestep_action.generic.localdata[0] = ++i;
	s_keys_sidestep_action.generic.name	= bindnames[i][1];

	s_keys_look_up_action.generic.type	= MTYPE_ACTION;
	s_keys_look_up_action.generic.flags  = QMF_GRAYED;
	s_keys_look_up_action.generic.x		= 0;
	s_keys_look_up_action.generic.y		= y += 9;
	s_keys_look_up_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_look_up_action.generic.localdata[0] = ++i;
	s_keys_look_up_action.generic.name	= bindnames[i][1];

	s_keys_look_down_action.generic.type	= MTYPE_ACTION;
	s_keys_look_down_action.generic.flags  = QMF_GRAYED;
	s_keys_look_down_action.generic.x		= 0;
	s_keys_look_down_action.generic.y		= y += 9;
	s_keys_look_down_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_look_down_action.generic.localdata[0] = ++i;
	s_keys_look_down_action.generic.name	= bindnames[i][1];

	s_keys_center_view_action.generic.type	= MTYPE_ACTION;
	s_keys_center_view_action.generic.flags  = QMF_GRAYED;
	s_keys_center_view_action.generic.x		= 0;
	s_keys_center_view_action.generic.y		= y += 9;
	s_keys_center_view_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_center_view_action.generic.localdata[0] = ++i;
	s_keys_center_view_action.generic.name	= bindnames[i][1];

	s_keys_mouse_look_action.generic.type	= MTYPE_ACTION;
	s_keys_mouse_look_action.generic.flags  = QMF_GRAYED;
	s_keys_mouse_look_action.generic.x		= 0;
	s_keys_mouse_look_action.generic.y		= y += 9;
	s_keys_mouse_look_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_mouse_look_action.generic.localdata[0] = ++i;
	s_keys_mouse_look_action.generic.name	= bindnames[i][1];

	s_keys_keyboard_look_action.generic.type	= MTYPE_ACTION;
	s_keys_keyboard_look_action.generic.flags  = QMF_GRAYED;
	s_keys_keyboard_look_action.generic.x		= 0;
	s_keys_keyboard_look_action.generic.y		= y += 9;
	s_keys_keyboard_look_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_keyboard_look_action.generic.localdata[0] = ++i;
	s_keys_keyboard_look_action.generic.name	= bindnames[i][1];

	s_keys_move_up_action.generic.type	= MTYPE_ACTION;
	s_keys_move_up_action.generic.flags  = QMF_GRAYED;
	s_keys_move_up_action.generic.x		= 0;
	s_keys_move_up_action.generic.y		= y += 9;
	s_keys_move_up_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_move_up_action.generic.localdata[0] = ++i;
	s_keys_move_up_action.generic.name	= bindnames[i][1];

	s_keys_move_down_action.generic.type	= MTYPE_ACTION;
	s_keys_move_down_action.generic.flags  = QMF_GRAYED;
	s_keys_move_down_action.generic.x		= 0;
	s_keys_move_down_action.generic.y		= y += 9;
	s_keys_move_down_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_move_down_action.generic.localdata[0] = ++i;
	s_keys_move_down_action.generic.name	= bindnames[i][1];

	s_keys_flashlight_action.generic.type	= MTYPE_ACTION;
	s_keys_flashlight_action.generic.flags  = QMF_GRAYED;
	s_keys_flashlight_action.generic.x		= 0;
	s_keys_flashlight_action.generic.y		= y += 9;
	s_keys_flashlight_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_flashlight_action.generic.localdata[0] = ++i;
	s_keys_flashlight_action.generic.name	= bindnames[i][1];

	s_keys_zoom_action.generic.type	= MTYPE_ACTION;
	s_keys_zoom_action.generic.flags  = QMF_GRAYED;
	s_keys_zoom_action.generic.x		= 0;
	s_keys_zoom_action.generic.y		= y += 9;
	s_keys_zoom_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_zoom_action.generic.localdata[0] = ++i;
	s_keys_zoom_action.generic.name	= bindnames[i][1];

	s_keys_inventory_action.generic.type	= MTYPE_ACTION;
	s_keys_inventory_action.generic.flags  = QMF_GRAYED;
	s_keys_inventory_action.generic.x		= 0;
	s_keys_inventory_action.generic.y		= y += 9;
	s_keys_inventory_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_inventory_action.generic.localdata[0] = ++i;
	s_keys_inventory_action.generic.name	= bindnames[i][1];

	s_keys_inv_use_action.generic.type	= MTYPE_ACTION;
	s_keys_inv_use_action.generic.flags  = QMF_GRAYED;
	s_keys_inv_use_action.generic.x		= 0;
	s_keys_inv_use_action.generic.y		= y += 9;
	s_keys_inv_use_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_inv_use_action.generic.localdata[0] = ++i;
	s_keys_inv_use_action.generic.name	= bindnames[i][1];

	s_keys_inv_drop_action.generic.type	= MTYPE_ACTION;
	s_keys_inv_drop_action.generic.flags  = QMF_GRAYED;
	s_keys_inv_drop_action.generic.x		= 0;
	s_keys_inv_drop_action.generic.y		= y += 9;
	s_keys_inv_drop_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_inv_drop_action.generic.localdata[0] = ++i;
	s_keys_inv_drop_action.generic.name	= bindnames[i][1];

	s_keys_inv_prev_action.generic.type	= MTYPE_ACTION;
	s_keys_inv_prev_action.generic.flags  = QMF_GRAYED;
	s_keys_inv_prev_action.generic.x		= 0;
	s_keys_inv_prev_action.generic.y		= y += 9;
	s_keys_inv_prev_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_inv_prev_action.generic.localdata[0] = ++i;
	s_keys_inv_prev_action.generic.name	= bindnames[i][1];

	s_keys_inv_next_action.generic.type	= MTYPE_ACTION;
	s_keys_inv_next_action.generic.flags  = QMF_GRAYED;
	s_keys_inv_next_action.generic.x		= 0;
	s_keys_inv_next_action.generic.y		= y += 9;
	s_keys_inv_next_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_inv_next_action.generic.localdata[0] = ++i;
	s_keys_inv_next_action.generic.name	= bindnames[i][1];

	s_keys_help_computer_action.generic.type	= MTYPE_ACTION;
	s_keys_help_computer_action.generic.flags  = QMF_GRAYED;
	s_keys_help_computer_action.generic.x		= 0;
	s_keys_help_computer_action.generic.y		= y += 9;
	s_keys_help_computer_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_help_computer_action.generic.localdata[0] = ++i;
	s_keys_help_computer_action.generic.name	= bindnames[i][1];

	s_keys_screenshot_action.generic.type	= MTYPE_ACTION;
	s_keys_screenshot_action.generic.flags  = QMF_GRAYED;
	s_keys_screenshot_action.generic.x		= 0;
	s_keys_screenshot_action.generic.y		= y += 9;
	s_keys_screenshot_action.generic.ownerdraw = DrawKeyBindingFunc;
	s_keys_screenshot_action.generic.localdata[0] = ++i;
	s_keys_screenshot_action.generic.name	= bindnames[i][1];

	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_attack_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_walk_forward_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_backpedal_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_turn_left_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_turn_right_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_run_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_step_left_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_step_right_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_sidestep_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_look_up_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_look_down_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_center_view_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_mouse_look_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_keyboard_look_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_move_up_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_move_down_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_flashlight_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_zoom_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inventory_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_use_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_drop_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_prev_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_next_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_help_computer_action );
	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_screenshot_action );

	Menu_SetStatusBar( &s_keys_menu, "enter to change, del/backspace to clear" );
	Menu_Center( &s_keys_menu );

	if(r_mode->value != 0)
		s_keys_menu.y += 12;
}


void Keys2_MenuInit()
{
	int y = 0;
	int i = 0;

	s_keys2_menu.x = viddef.width * 0.50;
	s_keys2_menu.nitems = 0;
	s_keys2_menu.cursordraw = KeyCursorDrawFunc;

	s_keys2_blaster_action.generic.type	= MTYPE_ACTION;
	s_keys2_blaster_action.generic.flags  = QMF_GRAYED;
	s_keys2_blaster_action.generic.x		= 0;
	s_keys2_blaster_action.generic.y		= y;
	s_keys2_blaster_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_blaster_action.generic.localdata[0] = i;
	s_keys2_blaster_action.generic.name	= bindnames2[i][1];

	s_keys2_shotgun_action.generic.type	= MTYPE_ACTION;
	s_keys2_shotgun_action.generic.flags  = QMF_GRAYED;
	s_keys2_shotgun_action.generic.x		= 0;
	s_keys2_shotgun_action.generic.y		= y += 9;
	s_keys2_shotgun_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_shotgun_action.generic.localdata[0] = ++i;
	s_keys2_shotgun_action.generic.name	= bindnames2[i][1];

	s_keys2_sshotgun_action.generic.type	= MTYPE_ACTION;
	s_keys2_sshotgun_action.generic.flags  = QMF_GRAYED;
	s_keys2_sshotgun_action.generic.x		= 0;
	s_keys2_sshotgun_action.generic.y		= y += 9;
	s_keys2_sshotgun_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_sshotgun_action.generic.localdata[0] = ++i;
	s_keys2_sshotgun_action.generic.name	= bindnames2[i][1];

	s_keys2_machinegun_action.generic.type	= MTYPE_ACTION;
	s_keys2_machinegun_action.generic.flags  = QMF_GRAYED;
	s_keys2_machinegun_action.generic.x		= 0;
	s_keys2_machinegun_action.generic.y		= y += 9;
	s_keys2_machinegun_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_machinegun_action.generic.localdata[0] = ++i;
	s_keys2_machinegun_action.generic.name	= bindnames2[i][1];

	s_keys2_chaingun_action.generic.type	= MTYPE_ACTION;
	s_keys2_chaingun_action.generic.flags  = QMF_GRAYED;
	s_keys2_chaingun_action.generic.x		= 0;
	s_keys2_chaingun_action.generic.y		= y += 9;
	s_keys2_chaingun_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_chaingun_action.generic.localdata[0] = ++i;
	s_keys2_chaingun_action.generic.name	= bindnames2[i][1];

	s_keys2_grenades_action.generic.type	= MTYPE_ACTION;
	s_keys2_grenades_action.generic.flags  = QMF_GRAYED;
	s_keys2_grenades_action.generic.x		= 0;
	s_keys2_grenades_action.generic.y		= y += 9;
	s_keys2_grenades_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_grenades_action.generic.localdata[0] = ++i;
	s_keys2_grenades_action.generic.name	= bindnames2[i][1];

	s_keys2_glauncher_action.generic.type	= MTYPE_ACTION;
	s_keys2_glauncher_action.generic.flags  = QMF_GRAYED;
	s_keys2_glauncher_action.generic.x		= 0;
	s_keys2_glauncher_action.generic.y		= y += 9;
	s_keys2_glauncher_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_glauncher_action.generic.localdata[0] = ++i;
	s_keys2_glauncher_action.generic.name	= bindnames2[i][1];

	s_keys2_rlauncher_action.generic.type	= MTYPE_ACTION;
	s_keys2_rlauncher_action.generic.flags  = QMF_GRAYED;
	s_keys2_rlauncher_action.generic.x		= 0;
	s_keys2_rlauncher_action.generic.y		= y += 9;
	s_keys2_rlauncher_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_rlauncher_action.generic.localdata[0] = ++i;
	s_keys2_rlauncher_action.generic.name	= bindnames2[i][1];

	s_keys2_hyperblaster_action.generic.type	= MTYPE_ACTION;
	s_keys2_hyperblaster_action.generic.flags  = QMF_GRAYED;
	s_keys2_hyperblaster_action.generic.x		= 0;
	s_keys2_hyperblaster_action.generic.y		= y += 9;
	s_keys2_hyperblaster_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_hyperblaster_action.generic.localdata[0] = ++i;
	s_keys2_hyperblaster_action.generic.name	= bindnames2[i][1];

	s_keys2_railgun_action.generic.type	= MTYPE_ACTION;
	s_keys2_railgun_action.generic.flags  = QMF_GRAYED;
	s_keys2_railgun_action.generic.x		= 0;
	s_keys2_railgun_action.generic.y		= y += 9;
	s_keys2_railgun_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_railgun_action.generic.localdata[0] = ++i;
	s_keys2_railgun_action.generic.name	= bindnames2[i][1];

	s_keys2_bfg10k_action.generic.type	= MTYPE_ACTION;
	s_keys2_bfg10k_action.generic.flags  = QMF_GRAYED;
	s_keys2_bfg10k_action.generic.x		= 0;
	s_keys2_bfg10k_action.generic.y		= y += 9;
	s_keys2_bfg10k_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_bfg10k_action.generic.localdata[0] = ++i;
	s_keys2_bfg10k_action.generic.name	= bindnames2[i][1];

	s_keys2_weapprev_action.generic.type	= MTYPE_ACTION;
	s_keys2_weapprev_action.generic.flags  = QMF_GRAYED;
	s_keys2_weapprev_action.generic.x		= 0;
	s_keys2_weapprev_action.generic.y		= y += 9;
	s_keys2_weapprev_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_weapprev_action.generic.localdata[0] = ++i;
	s_keys2_weapprev_action.generic.name	= bindnames2[i][1];

	s_keys2_weapnext_action.generic.type	= MTYPE_ACTION;
	s_keys2_weapnext_action.generic.flags  = QMF_GRAYED;
	s_keys2_weapnext_action.generic.x		= 0;
	s_keys2_weapnext_action.generic.y		= y += 9;
	s_keys2_weapnext_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_weapnext_action.generic.localdata[0] = ++i;
	s_keys2_weapnext_action.generic.name	= bindnames2[i][1];

	s_keys2_quaddamage_action.generic.type	= MTYPE_ACTION;
	s_keys2_quaddamage_action.generic.flags  = QMF_GRAYED;
	s_keys2_quaddamage_action.generic.x		= 0;
	s_keys2_quaddamage_action.generic.y		= y += 9;
	s_keys2_quaddamage_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_quaddamage_action.generic.localdata[0] = ++i;
	s_keys2_quaddamage_action.generic.name	= bindnames2[i][1];

	s_keys2_invulnerability_action.generic.type	= MTYPE_ACTION;
	s_keys2_invulnerability_action.generic.flags  = QMF_GRAYED;
	s_keys2_invulnerability_action.generic.x		= 0;
	s_keys2_invulnerability_action.generic.y		= y += 9;
	s_keys2_invulnerability_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_invulnerability_action.generic.localdata[0] = ++i;
	s_keys2_invulnerability_action.generic.name	= bindnames2[i][1];

	s_keys2_rebreather_action.generic.type	= MTYPE_ACTION;
	s_keys2_rebreather_action.generic.flags  = QMF_GRAYED;
	s_keys2_rebreather_action.generic.x		= 0;
	s_keys2_rebreather_action.generic.y		= y += 9;
	s_keys2_rebreather_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_rebreather_action.generic.localdata[0] = ++i;
	s_keys2_rebreather_action.generic.name	= bindnames2[i][1];

	s_keys2_envsuit_action.generic.type	= MTYPE_ACTION;
	s_keys2_envsuit_action.generic.flags  = QMF_GRAYED;
	s_keys2_envsuit_action.generic.x		= 0;
	s_keys2_envsuit_action.generic.y		= y += 9;
	s_keys2_envsuit_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_envsuit_action.generic.localdata[0] = ++i;
	s_keys2_envsuit_action.generic.name	= bindnames2[i][1];

	s_keys2_silencer_action.generic.type	= MTYPE_ACTION;
	s_keys2_silencer_action.generic.flags  = QMF_GRAYED;
	s_keys2_silencer_action.generic.x		= 0;
	s_keys2_silencer_action.generic.y		= y += 9;
	s_keys2_silencer_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_silencer_action.generic.localdata[0] = ++i;
	s_keys2_silencer_action.generic.name	= bindnames2[i][1];

	s_keys2_powershield_action.generic.type	= MTYPE_ACTION;
	s_keys2_powershield_action.generic.flags  = QMF_GRAYED;
	s_keys2_powershield_action.generic.x		= 0;
	s_keys2_powershield_action.generic.y		= y += 9;
	s_keys2_powershield_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_powershield_action.generic.localdata[0] = ++i;
	s_keys2_powershield_action.generic.name	= bindnames2[i][1];

	s_keys2_invisibility_action.generic.type	= MTYPE_ACTION;
	s_keys2_invisibility_action.generic.flags  = QMF_GRAYED;
	s_keys2_invisibility_action.generic.x		= 0;
	s_keys2_invisibility_action.generic.y		= y += 9;
	s_keys2_invisibility_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_invisibility_action.generic.localdata[0] = ++i;
	s_keys2_invisibility_action.generic.name	= bindnames2[i][1];

	s_keys2_messagemode_action.generic.type	= MTYPE_ACTION;
	s_keys2_messagemode_action.generic.flags  = QMF_GRAYED;
	s_keys2_messagemode_action.generic.x		= 0;
	s_keys2_messagemode_action.generic.y		= y += 9;
	s_keys2_messagemode_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_messagemode_action.generic.localdata[0] = ++i;
	s_keys2_messagemode_action.generic.name	= bindnames2[i][1];

	s_keys2_qsaving_action.generic.type	= MTYPE_ACTION;
	s_keys2_qsaving_action.generic.flags  = QMF_GRAYED;
	s_keys2_qsaving_action.generic.x		= 0;
	s_keys2_qsaving_action.generic.y		= y += 9;
	s_keys2_qsaving_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_qsaving_action.generic.localdata[0] = ++i;
	s_keys2_qsaving_action.generic.name	= bindnames2[i][1];

	s_keys2_qloading_action.generic.type	= MTYPE_ACTION;
	s_keys2_qloading_action.generic.flags  = QMF_GRAYED;
	s_keys2_qloading_action.generic.x		= 0;
	s_keys2_qloading_action.generic.y		= y += 9;
	s_keys2_qloading_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_qloading_action.generic.localdata[0] = ++i;
	s_keys2_qloading_action.generic.name	= bindnames2[i][1];

	s_keys2_quit_action.generic.type	= MTYPE_ACTION;
	s_keys2_quit_action.generic.flags  = QMF_GRAYED;
	s_keys2_quit_action.generic.x		= 0;
	s_keys2_quit_action.generic.y		= y += 9;
	s_keys2_quit_action.generic.ownerdraw = DrawKey2BindingFunc;
	s_keys2_quit_action.generic.localdata[0] = ++i;
	s_keys2_quit_action.generic.name	= bindnames2[i][1];

	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_blaster_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_shotgun_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_sshotgun_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_machinegun_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_chaingun_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_grenades_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_glauncher_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_rlauncher_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_hyperblaster_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_railgun_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_bfg10k_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_weapprev_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_weapnext_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_quaddamage_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_invulnerability_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_rebreather_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_envsuit_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_silencer_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_powershield_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_invisibility_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_messagemode_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_qsaving_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_qloading_action );
	Menu_AddItem( &s_keys2_menu, ( void * ) &s_keys2_quit_action );

	Menu_SetStatusBar( &s_keys2_menu, "enter to change, del/backspace to clear" );
	Menu_Center( &s_keys2_menu );

	if(r_mode->value != 0)
		s_keys2_menu.y += 12;
}


void Keys_MenuDraw ()
{
	if(r_mode->value != 0)		// Berserker: in 320x240 not enought room in screen for banner
	{
		int w, h;
		Draw_GetPicSize (&w, &h, "m_banner_customize.tga" );
		Draw_Pic( viddef.width / 2 - w / 2, viddef.height / 2 - 140, "m_banner_customize.tga" );
	}
	Menu_AdjustCursor( &s_keys_menu, 1 );
	Menu_Draw( &s_keys_menu );
}


void Keys2_MenuDraw ()
{
	if(r_mode->value != 0)		// Berserker: in 320x240 not enought room in screen for banner
	{
		int w, h;
		Draw_GetPicSize (&w, &h, "m_banner_customize.tga" );
		Draw_Pic( viddef.width / 2 - w / 2, viddef.height / 2 - 140, "m_banner_customize.tga" );
	}
	Menu_AdjustCursor( &s_keys2_menu, 1 );
	Menu_Draw( &s_keys2_menu );
}


void M_UnbindCommand (char *command)
{
	int		j;
	int		l;
	char	*b;

	l = strlen(command);

	for (j=0 ; j<256 ; j++)
	{
		b = keybindings[j];
		if (!b)
			continue;
		if (!strncmp (b, command, l) )
			Key_SetBinding (j, "");
	}
}


void KeyBindingFunc( void *self )
{
	menuaction_s *a = ( menuaction_s * ) self;
	int keys[2];

	M_FindKeysForCommand( bindnames[a->generic.localdata[0]][0], keys );

	if (keys[1] != -1)
		M_UnbindCommand( bindnames[a->generic.localdata[0]][0]);

	bind_grab = true;

	Menu_SetStatusBar( &s_keys_menu, "press a key or button for this action" );
}


void Key2BindingFunc( void *self )
{
	menuaction_s *a = ( menuaction_s * ) self;
	int keys[2];

	M_FindKeysForCommand( bindnames2[a->generic.localdata[0]][0], keys );

	if (keys[1] != -1)
		M_UnbindCommand( bindnames2[a->generic.localdata[0]][0]);

	bind_grab = true;

	Menu_SetStatusBar( &s_keys2_menu, "press a key or button for this action" );
}


int Keys_MenuKey( int key )
{
	menuaction_s *item = ( menuaction_s * ) Menu_ItemAtCursor( &s_keys_menu );

	if ( bind_grab )
	{	
		if ( key != K_ESCAPE && key != '`' )
		{
			char cmd[1024];

			Com_sprintf (cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", Key_KeynumToString(key), bindnames[item->generic.localdata[0]][0]);
			Cbuf_InsertText (cmd);
		}
		
		Menu_SetStatusBar( &s_keys_menu, "enter to change, del/backspace to clear" );
		bind_grab = false;
		return cl_sfx_menu_out_sound;
	}

	switch ( key )
	{
	case K_KP_ENTER:
	case K_ENTER:
		KeyBindingFunc( item );
		return cl_sfx_menu_in_sound;
	case K_BACKSPACE:		// delete bindings
	case K_DEL:				// delete bindings
	case K_KP_DEL:
		M_UnbindCommand( bindnames[item->generic.localdata[0]][0] );
		return cl_sfx_menu_out_sound;
	default:
		return Default_MenuKey( &s_keys_menu, key );
	}
}


int Keys_MenuKey2( int key )
{
	menuaction_s *item = ( menuaction_s * ) Menu_ItemAtCursor( &s_keys2_menu );

	if ( bind_grab )
	{	
		if ( key != K_ESCAPE && key != '`' )
		{
			char cmd[1024];

			Com_sprintf (cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", Key_KeynumToString(key), bindnames2[item->generic.localdata[0]][0]);
			Cbuf_InsertText (cmd);
		}

		Menu_SetStatusBar( &s_keys2_menu, "enter to change, del/backspace to clear" );
		bind_grab = false;
		return cl_sfx_menu_out_sound;
	}

	switch ( key )
	{
	case K_KP_ENTER:
	case K_ENTER:
		Key2BindingFunc( item );
		return cl_sfx_menu_in_sound;
	case K_BACKSPACE:		// delete bindings
	case K_DEL:				// delete bindings
	case K_KP_DEL:
		M_UnbindCommand( bindnames2[item->generic.localdata[0]][0] );
		return cl_sfx_menu_out_sound;
	default:
		return Default_MenuKey( &s_keys2_menu, key );
	}
}


int M_Quit_Key (int key)
{
	switch (key)
	{
	case K_ESCAPE:
	case 'n':
	case 'N':
		M_PopMenu ();
		break;

	case K_ENTER:
	case 'Y':
	case 'y':
		cls.key_dest = key_console;
		CL_Quit_f ();
		break;

	default:
		break;
	}

	return 0;
}


void M_Quit_Draw ()
{
	int		w, h;

	Draw_GetPicSize (&w, &h, "quit.tga");
	Draw_Pic ( (viddef.width-w)/2, (viddef.height-h)/2, "quit.tga");
}


void M_Menu_Quit_f ()
{
	M_PushMenu (M_Quit_Draw, M_Quit_Key);
}


void VID_MenuDraw ()
{
	int w, h;

	/*
	** draw the banner
	*/
	Draw_GetPicSize( &w, &h, "m_banner_video.tga" );
	Draw_Pic( viddef.width / 2 - w / 2, viddef.height /2 - 110, "m_banner_video.tga" );

	/*
	** move cursor to a reasonable starting position
	*/
	Menu_AdjustCursor( &s_opengl_menu, 1 );

	/*
	** draw the menu
	*/
	Menu_Draw( &s_opengl_menu );
}


int VID_MenuKey( int key )
{
	menuframework_s *m = &s_opengl_menu;

	switch ( key )
	{
	case K_ESCAPE:
		M_PopMenu ();	//CancelChanges
		return NULL;
	case K_KP_UPARROW:
	case K_UPARROW:
		m->cursor--;
		Menu_AdjustCursor( m, -1 );
		break;
	case K_KP_DOWNARROW:
	case K_DOWNARROW:
		m->cursor++;
		Menu_AdjustCursor( m, 1 );
		break;
	case K_KP_LEFTARROW:
	case K_LEFTARROW:
		Menu_SlideItem( m, -1 );
		break;
	case K_KP_RIGHTARROW:
	case K_RIGHTARROW:
		Menu_SlideItem( m, 1 );
		break;
	case K_KP_ENTER:
	case K_ENTER:
		if ( !Menu_SelectItem( m ) )
			ApplyChanges( 0 );
		break;
	}

	return cl_sfx_menu_in_sound;
}


void M_Menu_Video_f ()
{
	VID_MenuInit(true);
	M_PushMenu( VID_MenuDraw, VID_MenuKey );
}


int M_Main_Key (int key)
{
	int sound = cl_sfx_menu_move_sound;

	switch (key)
	{
	case K_ESCAPE:
		M_PopMenu ();
		break;

	case K_KP_DOWNARROW:
	case K_DOWNARROW:
		if (++m_main_cursor >= MAIN_ITEMS)
			m_main_cursor = 0;
		return sound;

	case K_KP_UPARROW:
	case K_UPARROW:
		if (--m_main_cursor < 0)
			m_main_cursor = MAIN_ITEMS - 1;
		return sound;

	case K_KP_ENTER:
	case K_ENTER:
		m_entersound = true;

		switch (m_main_cursor)
		{
		case 0:
			M_Menu_Game_f ();
			break;

		case 1:
			M_Menu_Multiplayer_f();
			break;

		case 2:
			M_Menu_Demos_f ();
			break;

		case 3:
			M_Menu_Options_f ();
			break;

		case 4:
			M_Menu_Video_f ();
			break;

		case 5:
			M_Menu_Quit_f ();
			break;
		}
	}

	return 0;
}


void M_Menu_Main_f ()
{
	M_PushMenu (M_Main_Draw, M_Main_Key);
}

void M_LoadMapNames()
{
	char	shortname[MAX_MAP_NAMELEN];
	char	scratch[200];
	int		i, j, l;

	FreeMapNames2();

	// load the list of map names
	num_maps = Create_Mapsstrings(3);

	map_names = (char **) Z_Malloc( sizeof( char * ) * ( num_maps + 1 ), true );
///	Memset( map_names, 0, sizeof( char * ) * ( num_maps + 1 ) );

	for ( i = 0; i < num_maps; i++ )
	{
		l = strlen(m_maps[i]);
		for (j=0 ; j<l ; j++)
			shortname[j] = toupper(m_maps[i][j]);
		shortname[j] = 0;
		Com_sprintf( scratch, sizeof( scratch ), "%s", shortname );
		map_names[i] = (char *) Z_Malloc( strlen( scratch ) + 1, true );
		strcpy( map_names[i], scratch );
	}
	map_names[num_maps] = 0;

	qsort(map_names, num_maps, sizeof(char *), (int (__cdecl *)(const void *,const void *))Q_SortStrcmp);
}


void M_Init ()
{
	Cmd_AddCommand ("menu_main", M_Menu_Main_f);
	Cmd_AddCommand ("menu_game", M_Menu_Game_f);
		Cmd_AddCommand ("menu_gameoptions", M_GameOptions_f);
		Cmd_AddCommand ("menu_quickloadgame", M_Menu_LoadQuickGame_f);
		Cmd_AddCommand ("menu_loadgame", M_Menu_LoadGame_f);
		Cmd_AddCommand ("menu_savegame", M_Menu_SaveGame_f);
		Cmd_AddCommand ("menu_joinserver", M_Menu_JoinServer_f);
			Cmd_AddCommand ("menu_addressbook", M_Menu_AddressBook_f);
		Cmd_AddCommand ("menu_startserver", M_Menu_StartServer_f);
			Cmd_AddCommand ("menu_dmoptions", M_Menu_DMOptions_f);
		Cmd_AddCommand ("menu_playerconfig", M_Menu_PlayerConfig_f);
			Cmd_AddCommand ("menu_downloadoptions", M_Menu_DownloadOptions_f);
		Cmd_AddCommand ("menu_credits", M_Menu_Credits_f );
	Cmd_AddCommand ("menu_multiplayer", M_Menu_Multiplayer_f );
	Cmd_AddCommand ("menu_demos", M_Menu_Demos_f);
	Cmd_AddCommand ("menu_video", M_Menu_Video_f);
		Cmd_AddCommand ("menu_screeninfos", M_Menu_ScrInfo_f);
		Cmd_AddCommand ("menu_video_more", M_Menu_Mirrors_f);
	Cmd_AddCommand ("menu_options", M_Menu_Options_f);
		Cmd_AddCommand ("menu_keys", M_Menu_Keys_f);
		Cmd_AddCommand ("menu_keys2", M_Menu_Keys2_f);
	Cmd_AddCommand ("menu_quit", M_Menu_Quit_f);
}


void CL_Init ()
{
	if (dedicated->value)
		return;		// nothing running on the client

	net_message.data = net_message_buffer;
	net_message.maxsize = sizeof(net_message_buffer);

	// all archived variables will now be loaded
	Con_Init ();
	VID_Init ();
	S_Init ();
	V_Init ();
	M_Init ();
	SCR_Init ();

///	CDAudio_Init ();
	Music_Init();

	CL_InitLocal ();
	IN_Init ();

	cls.disable_screen = false;

	FS_ExecAutoexec ();
	Cbuf_Execute ();

	if (Q_strcasecmp(fs_cached->string, FS_Gamedir()))
	{
		Cbuf_AddText ("cache\n");
		Cbuf_Execute ();
		Cvar_Set("fs_cached", FS_Gamedir());
	}
}


/*
=================
Cbuf_AddLateCommands

Adds command line parameters as script statements
Commands lead with a + and continue until another + or -
berserker +game ctf +map amlev1

Returns true if any late commands were added, which
will keep the demoloop from immediately starting
=================
*/
bool Cbuf_AddLateCommands ()
{
	int		i, j;
	int		s;
	char	*text, *build, c;
	int		argc;
	bool	ret;

// build the combined string to parse from
	s = 0;
	argc = COM_Argc();
	for (i=1 ; i<argc ; i++)
	{
		s += strlen (COM_Argv(i)) + 1;
	}
	if (!s)
		return false;
		
	text = (char*) Z_Malloc (s+1, true);
	text[0] = 0;
	for (i=1 ; i<argc ; i++)
	{
		strcat (text,COM_Argv(i));
		if (i != argc-1)
			strcat (text, " ");
	}
	
// pull out the commands
	build = (char*) Z_Malloc (s+1, true);
	build[0] = 0;
	
	for (i=0 ; i<s-1 ; i++)
	{
		if (text[i] == '+')
		{
			i++;

			for (j=i ; (text[j] != '+') && (text[j] != '-') && (text[j] != 0) ; j++)
				;

			c = text[j];
			text[j] = 0;
			
			strcat (build, text+i);
			strcat (build, "\n");
			text[j] = c;
			i = j-1;
		}
	}

	ret = (build[0] != 0);
	if (ret)
		Cbuf_AddText (build);
	
	Z_Free (text);
	Z_Free (build);

	return ret;
}


// alias sorting from r1q2
static int aliassort( const void *_a, const void *_b )
{
	const cmdalias_t	*a = (const cmdalias_t *)_a;
	const cmdalias_t	*b = (const cmdalias_t *)_b;
	return strcmp (a->name, b->name);
}

void Cmd_Alias_List_f ()
{
	cmdalias_t		*a, *aa, *sortedList;
	int				i, num;

	for (a = cmd_alias, num = 0; a ; a = a->next, num++);
	sortedList = (cmdalias_t *) Z_Malloc (num * sizeof(cmdalias_t), false);
	if (sortedList)
	{
		for (a = cmd_alias, i = 0; a ; a = a->next, i++)
			sortedList[i] = *a;
		qsort (sortedList, num, sizeof(sortedList[0]), aliassort);
	}

	for (aa=cmd_alias, i = 0; aa ; aa=aa->next, i++)
	{
		if (sortedList)
			a = &sortedList[i];
		else
			a = aa;
		Com_Printf ("%s = %s", a->name, a->value);
	}

	Com_Printf ("%i aliases\n", num);

	if (sortedList)
		Z_Free(sortedList);
}

// cmd sorting from r1q2
static int cmdsort( const void *_a, const void *_b )
{
	const cmd_function_t	*a = (const cmd_function_t *)_a;
	const cmd_function_t	*b = (const cmd_function_t *)_b;
	return strcmp (a->name, b->name);
}

void Cmd_List_f ()
{
	cmd_function_t	*cmd, *command, *sortedList;
	int				i, num;

	for (cmd = cmd_functions, num = 0; cmd ; cmd = cmd->next, num++);
	sortedList = (cmd_function_t *) Z_Malloc (num * sizeof(cmd_function_t), false);
	if (sortedList)
	{
		for (cmd = cmd_functions, i = 0; cmd ; cmd = cmd->next, i++)
			sortedList[i] = *cmd;
		qsort (sortedList, num, sizeof(sortedList[0]), cmdsort);
	}

	for (command=cmd_functions , i = 0; command ; command=command->next, i++)
	{
		if (sortedList)
			cmd = &sortedList[i];
		else
			cmd = command;
		Com_Printf ("%s\n", cmd->name);
	}

	Com_Printf ("%i commands\n", num);

	if (sortedList)
		Z_Free(sortedList);
}


/*
===============
Cmd_Echo_f

Just prints the rest of the line to the console
===============
*/
void Cmd_Echo_f ()
{
	int		i;
	
	for (i=1 ; i<Cmd_Argc() ; i++)
		Com_Printf ("%s ",Cmd_Argv(i));
	Com_Printf ("\n");
}

/*
============
Cmd_Wait_f

Causes execution of the remainder of the command buffer to be delayed until
next frame.  This allows commands like:
bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"

Moded: you may set time delay...
============
*/
void Cmd_Wait_f ()
{
	if (Cmd_Argc() == 1)
		cmd_wait = -1;				/// спец.случай - применяем wait как обычно: пропуск 1 кадра... (совместимость панимашь...)
	else if (Cmd_Argc() == 2)
	{
		cmd_wait = sys_time + atoi(Cmd_Argv(1));	/// пропускаем № миллисекунд...
		if (cmd_wait <= sys_time)
			cmd_wait = 0;				/// спец.случай - команды wait не было...
	}
	else
	{
		Com_Printf("^3Usage: wait <msec>\n");
		cmd_wait = 0;
	}
}


void Cmd_NetStat_f ()
{
	if (net_compatibility->value || !net_incoming || !net_outgoing)
		Com_Printf("   Received: %i bytes\nSended data: %i bytes\n", net_incoming, net_outgoing);
	else
		Com_Printf("Received compressed data: %i bytes\n       Uncompressed data: %i bytes\n                   Saved: %i bytes %i%%\nSended compressed data: %i bytes\n     Uncompressed data: %i bytes\n                 Saved: %i bytes %i%%\n", znet_incoming, net_incoming, net_saved_in, 100*net_saved_in/net_incoming, znet_outgoing, net_outgoing, net_saved_out, 100*net_saved_out/net_outgoing);
}


void Cmd_ClearNetStat_f ()
{
	net_saved_in =
	net_saved_out =
	net_incoming =
	net_outgoing =
	znet_incoming =
	znet_outgoing = 0;
}


void Cmd_Init ()
{
//
// register our commands
//
	Cmd_AddCommand ("cmdlist",Cmd_List_f);
	Cmd_AddCommand ("exec",Cmd_Exec_f);
	Cmd_AddCommand ("echo",Cmd_Echo_f);
	Cmd_AddCommand ("alias",Cmd_Alias_f);
	Cmd_AddCommand ("wait", Cmd_Wait_f);
	Cmd_AddCommand ("aliaslist",Cmd_Alias_List_f);		/// Berserker
	Cmd_AddCommand ("condition",Cmd_Condition_f);		/// Berserker
}


void Cvar_Clamp_f()
{
	char	*cvarName;
	float	min, max;

	if (Cmd_Argc() != 4)
	{
		Com_Printf ("^3USAGE: clamp <cvarName> <minValue> <maxValue>\n");
		return;
	}

	cvarName = Cmd_Argv(1);
	min = atof(Cmd_Argv(2));
	max = atof(Cmd_Argv(3));
	Cvar_SetValue(cvarName, ClampCvar(min, max, Cvar_VariableValue(cvarName)));
}


void Cvar_Rotate_f()
{
	char	*cvarName;
	float	val, min, max;

	if (Cmd_Argc() != 4)
	{
		Com_Printf ("^3USAGE: rotate <cvarName> <minValue> <maxValue>\n");
		return;
	}

	cvarName = Cmd_Argv(1);
	min = atof(Cmd_Argv(2));
	max = atof(Cmd_Argv(3));
	val = Cvar_VariableValue(cvarName);
	if (val < min)	val = max;
	else if (val > max)	val = min;
	Cvar_SetValue(cvarName, val);
}


/*
============
Cvar_List_f
============
*/
// cvar sorting from r1q2
static int cvarsort( const void *_a, const void *_b )
{
	const cvar_t	*a = (const cvar_t *)_a;
	const cvar_t	*b = (const cvar_t *)_b;
	return strcmp (a->name, b->name);
}

void Cvar_List_f ()
{
	cvar_t	*var, *cvar, *sortedList;
	int		i, num;
	char	*hlp;
	bool	help = false;

	for (var = cvar_vars, num = 0; var ; var = var->next, num++);
	sortedList = (cvar_t *) Z_Malloc (num * sizeof(cvar_t), false);
	if (sortedList)
	{
		for (var = cvar_vars, i = 0; var ; var = var->next, i++)
			sortedList[i] = *var;
		qsort (sortedList, num, sizeof(sortedList[0]), cvarsort);
	}

	if (Cmd_Argc() == 2)
	{
		hlp = Cmd_Argv(1);
		if (!Q_strcasecmp(hlp, "?") || !Q_strcasecmp(hlp, "h") || !Q_strcasecmp(hlp, "help"))
			help = true;
	}

	for (cvar = cvar_vars , i = 0; cvar ; cvar = cvar->next, i++)
	{
		if (sortedList)
			var = &sortedList[i];
		else
			var = cvar;

		if (var->flags & CVAR_ARCHIVE)
			Com_Printf ("*");
		else
			Com_Printf (" ");
		if (var->flags & CVAR_USERINFO)
			Com_Printf ("U");
		else
			Com_Printf (" ");
		if (var->flags & CVAR_SERVERINFO)
			Com_Printf ("S");
		else
			Com_Printf (" ");
		if (var->flags & CVAR_NOSET)
			Com_Printf ("-");
		else if (var->flags & CVAR_LATCH)
			Com_Printf ("L");
		else if (var->flags & CVAR_VID_LATCH)
			Com_Printf ("V");
		else
			Com_Printf (" ");
		Com_Printf (" %s \"%s\"", var->name, var->string);

		if(var->defaultString)
			Com_Printf (" default: \"%s\"", var->defaultString);

		if(help && var->help)
			Com_Printf (" [%s]", var->help);

		Com_Printf ("\n");
	}

	Com_Printf ("%i cvars\n", num);

	if (sortedList)
		Z_Free(sortedList);
}

// r1ch cvar help system
void Cvar_Help_f()
{
	cvar_t	*var;
	char	str[MAXPRINTMSG];

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("^3Usage: cvarhelp <cvarName>\n");
		return;
	}

	var = Cvar_FindVar (Cmd_Argv(1));
	if (!var)
	{
		Com_Printf ("^1Cvar %s not found.\n", Cmd_Argv(1));
		return;
	}

	if (!var->help)
	{
		Com_Printf ("^3No help available for %s.\n", Cmd_Argv(1));
		return;
	}

	Com_sprintf (str, sizeof(str), "%s: %s\n", var->name, var->help);
	Com_Printf (str, var->string);
}

/*
============
Cvar_Init

Reads in all archived cvars
============
*/
void Cvar_Init ()
{
	Cmd_AddCommand ("set", Cvar_Set_f);
	Cmd_AddCommand ("seta", Cvar_Set2_f);
	Cmd_AddCommand ("sets", Cvar_Set2_f);
	Cmd_AddCommand ("setu", Cvar_Set2_f);
	Cmd_AddCommand ("cvarlist", Cvar_List_f);
	Cmd_AddCommand ("cvarhelp", Cvar_Help_f);	// r1ch
	Cmd_AddCommand ("reset", Cvar_Reset_f);
	Cmd_AddCommand ("toggle", Cvar_Toggle_f);
	Cmd_AddCommand ("modify", Cvar_Modify_f);
	Cmd_AddCommand ("abs", Cvar_Abs_f);
	Cmd_AddCommand ("add", Cvar_Change_f);
	Cmd_AddCommand ("sub", Cvar_Change_f);
	Cmd_AddCommand ("mul", Cvar_Change_f);
	Cmd_AddCommand ("div", Cvar_Change_f);
	Cmd_AddCommand ("clamp", Cvar_Clamp_f);
	Cmd_AddCommand ("rotate", Cvar_Rotate_f);
	Cmd_AddCommand ("concat", Cvar_Concat_f);
}


/*
=============
Com_Error_f

Just throw a fatal error to
test error shutdown procedures
=============
*/
void Com_Error_f ()
{
	Com_Error (ERR_FATAL, "%s", Cmd_Argv(1));
}


void Spec_Hash_Init()
{
	hashes[0] = Com_HashKey("textures/e1u1/");
	hashes[1] = Com_HashKey("textures/e1u2/");
	hashes[2] = Com_HashKey("textures/e1u3/");
	hashes[3] = Com_HashKey("textures/e1u4/");
	hashes[4] = Com_HashKey("textures/e2u1/");
	hashes[5] = Com_HashKey("textures/e2u2/");
	hashes[6] = Com_HashKey("textures/e2u3/");
	hashes[7] = Com_HashKey("textures/e3u1/");
	hashes[8] = Com_HashKey("textures/e3u2/");
	hashes[9] = Com_HashKey("textures/e3u3/");
	hashes[10] = Com_HashKey("textures/test/");

	hashLaserBolt[0] = Com_HashKey("models/objects/laser/tris.md2");
	hashLaserBolt[1] = Com_HashKey("models/objects/laser/tris.md3");
	hashLaserBolt[2] = Com_HashKey("models/objects/laser/tris.ase");
}


void Common_Init (int argc, char **argv)
{
	char	*s;

	if (setjmp (abortframe) )
		Sys_Error ("Error during initialization");

	// reset rand() generator
	srand(time(NULL));

	z_chain.next = z_chain.prev = &z_chain;

	// prepare enough of the subsystems to handle
	// cvar and command buffer management
	COM_InitArgv (argc, argv);

	Swap_Init ();
	Cbuf_Init ();
	Cmd_Init ();
	Cvar_Init ();
	Key_Init ();
	Spec_Hash_Init();

	// we need to add the early commands twice, because
	// a basedir or cddir needs to be set before execing
	// config files, but we want other parms to override
	// the settings of the config files
	Cbuf_AddEarlyCommands (false);
	Cbuf_Execute ();

	FS_InitFilesystem ();

	FS_CreateDefaultPureList();
	FS_CutNonPures();
	if (!fs_pure)
		fs_pure = Cvar_Get ("fs_pure", "1", CVAR_NOSET | CVAR_ARCHIVE);
	float old_fs_pure = fs_pure->value;
	fs_pure->value = 2;		// hack
	enable_to_change_readonly_cvars = true;
	Cbuf_AddText ("exec default.cfg\n");
///	Cbuf_AddText ("exec q2b_default.cfg\n");
	Cbuf_AddText ("exec q2b_config.cfg\n");

	Cbuf_AddEarlyCommands (true);
	Cbuf_Execute ();
	fs_pure->value = old_fs_pure;	// hack
	enable_to_change_readonly_cvars = false;
	FS_LoadPureLists();
	FS_CutNonPures();
	Cmd_AddCommand ("purelist", FS_PureList_f );

	//
	// init commands and vars
	//
	Cmd_AddCommand ("z_stats", Z_Stats_f);
	Cmd_AddCommand ("hunk_stats", H_Stats_f);
	Cmd_AddCommand ("mem_stats", M_Stats_f);
	Cmd_AddCommand ("clearnetstat",Cmd_ClearNetStat_f);
	Cmd_AddCommand ("net_stats",Cmd_NetStat_f);
	net_traffic = Cvar_Get ("net_traffic", "0", 0);
	net_traffic->help = "Prints traffic info over network.";
	Cmd_AddCommand ("error", Com_Error_f);
	// for math library
	Cmd_AddCommand( "bench", Com_MathTest_f );

	/// force caching for the first start
	fs_cached = Cvar_Get ("fs_cached", "", CVAR_ARCHIVE);

	developer = Cvar_Get ("developer", "0", 0);
	developer->help = "print to console all diagnostic messages.";
	host_speeds = Cvar_Get ("host_speeds", "0", 0);
	timescale = Cvar_Get ("timescale", "1", 0);
	fixedtime = Cvar_Get ("fixedtime", "0", 0);
	logfile_active = Cvar_Get ("logfile", "0", 0);
	dedicated = Cvar_Get ("dedicated", "0", CVAR_NOSET);
	r_relight = Cvar_Get ("r_relight", "1", CVAR_NOSET);
	r_relight->help = "enables the using .lit files.";
	net_compatibility = Cvar_Get ("net_compatibility", "0", CVAR_SERVERINFO|CVAR_NOSET);
	net_compatibility->help = "if set, bers@q2 uses Quake2 native net protocol, otherwise using the Berserker protocol.";
	net_fixoverflow = Cvar_Get ("net_fixoverflow", "1", CVAR_SERVERINFO);
	net_fixoverflow->help = "Help to avoid SZ_Getspace: overflow and 'freezing' effects on the client by only sending partial amounts of packetentities. This will break delta state and may cause odd effects on the client. Also, gibs and debrises fully processes at client side.";
	hunk_model = Cvar_Get ("hunk_model", "10485760", CVAR_ARCHIVE);
	hunk_model->help = "maximal reserved memory for loading MD2 models.";
	hunk_model_md3 = Cvar_Get("hunk_model_md3", "20971520", CVAR_ARCHIVE);
	hunk_model_md3->help = "maximal reserved memory for loading MD3 models.";
	hunk_sprite = Cvar_Get ("hunk_sprite", "8192", CVAR_ARCHIVE);
	hunk_sprite->help = "maximal reserved memory for loading sprites.";
	hunk_map = Cvar_Get ("hunk_map", "16777216", CVAR_ARCHIVE);
	hunk_map->help = "maximal reserved memory for loading maps.";

	/// Berserker: читаем до Sys_Init
	m_oldmouse = Cvar_Get ("m_oldmouse", "0", CVAR_ARCHIVE|CVAR_NOSET);
	m_oldmouse->help = "if set, old mouse code will be used.";

	if(!net_compatibility->value)
	{
		net_zsize = Cvar_Get ("net_zsize", "1", CVAR_ARCHIVE);	/// экономим даже 1 байт
		net_zsize->help = "minimal net packet size which will be compressed before sending.";
		sv_flyqbe = Cvar_Get ("sv_flyqbe", "0.5", CVAR_ARCHIVE);
		sv_flyqbe->help = "probability of FlyQBE arrival, 0..1.";
	}

///	s = va("%4.2f %s x86 %s", VERSION, __DATE__, BUILDSTRING);
	s = va("Berserker@Quake2 %s %s x86 (%s)", __DATE__, __TIME__,  BUILDSTRING);
	Cvar_Get ("version", s, CVAR_SERVERINFO|CVAR_NOSET);

	if (dedicated->value)
		Cmd_AddCommand ("quit", Com_Quit);

	Sys_Init();

	NET_Init();
	Netchan_Init();

	SV_Init();
	CL_Init();

	// remove startup logo
	if (hwnd_dialog)
		DestroyWindow (hwnd_dialog); 

	// add + commands from command line
	if (!Cbuf_AddLateCommands ())
	{	// if the user didn't give any commands, run default action
		if (dedicated->value)
		{
			Cbuf_AddText ("dedicated_start\n");
		}
		else
		{
			if (sv.state == ss_dead)	// Berserker: fixed q2 bug: запускаем видео/дёмки только если сервер мёртв (не было команд на запуск чего-либо в autoexec.cfg)
			{
				FILE *tmp;
				if (FS_FOpenFile("video/idlog.cin", &tmp, true, -1)>0)
					Cbuf_AddText ("demo idlog.cin\n");
				else
					Cbuf_AddText ("menu_main\n");
				cls.key_dest = key_game;
			}
		}
		Cbuf_Execute ();
	}
	else
	{	// the user asked for something explicit
		// so drop the loading plaque
		SCR_EndLoadingPlaque ();
	}

	/// Berserker: видим какие ресурсы юзаем
///	Cbuf_AddText ("path\n");
///	Cbuf_Execute ();

	// Berserker: механизм запуска нужной одноразовой последовательности действий сразу после обновления.
#define FIRST_TIME_EXECUTION_VER	1		// для новой работы - сменить версию и написать новую подпрограмму.
	cv_reset = Cvar_Get ("cv_reset", "", CVAR_ARCHIVE|CVAR_NOSET);
	if (cv_reset->value != FIRST_TIME_EXECUTION_VER)
	{
		// Если fov = 90, то заменим на 91
		if (fov->value == 90)
			Cbuf_AddText ("reset fov\n");
		// сбросим цвары hunk_*
		Cbuf_AddText("reset hunk_model_md3\n");
		Cbuf_AddText ("reset hunk_model\n");
		Cbuf_AddText ("reset hunk_sprite\n");
		Cbuf_AddText ("reset hunk_map\n");
		Cbuf_Execute();

		enable_to_change_readonly_cvars = true;
		Cvar_SetValue("cv_reset", FIRST_TIME_EXECUTION_VER);
		enable_to_change_readonly_cvars = false;
	}

	Com_Printf ("====== Engine Initialized ======\n\n");	
}

/*
bool WeAreAlone (LPSTR szName)
{
	HANDLE hMutex = CreateMutex (NULL, TRUE, szName);
	if (GetLastError() == ERROR_ALREADY_EXISTS)
	{
		CloseHandle(hMutex);
		return false;
	}
	return true;
}
*/

char *Sys_ConsoleInput ()
{
	INPUT_RECORD	recs[1024];
	int		ch;
	unsigned long dummy, numread, numevents;

	if (!dedicated || !dedicated->value)
		return NULL;


	for ( ;; )
	{
		if (!GetNumberOfConsoleInputEvents (hinput, &numevents))
			Sys_Error ("Error getting # of console events");

		if (numevents <= 0)
			break;

		if (!ReadConsoleInput(hinput, recs, 1, &numread))
			Sys_Error ("Error reading console input");

		if (numread != 1)
			Sys_Error ("Couldn't read console input");

		if (recs[0].EventType == KEY_EVENT)
		{
			if (!recs[0].Event.KeyEvent.bKeyDown)
			{
				ch = recs[0].Event.KeyEvent.uChar.AsciiChar;

				switch (ch)
				{
					case '\r':
						WriteFile(houtput, "\r\n", 2, &dummy, NULL);

						if (console_textlen)
						{
							console_text[console_textlen] = 0;
							console_textlen = 0;
							return console_text;
						}
						break;

					case '\b':
						if (console_textlen)
						{
							console_textlen--;
							WriteFile(houtput, "\b \b", 3, &dummy, NULL);	
						}
						break;

					default:
						if (ch >= ' ')
						{
							if (console_textlen < sizeof(console_text)-2)
							{
								WriteFile(houtput, &ch, 1, &dummy, NULL);	
								console_text[console_textlen] = ch;
								console_textlen++;
							}
						}

						break;

				}
			}
		}
	}

	return NULL;
}


/*
==================
SV_CheckTimeouts

If a packet has not been received from a client for timeout->value
seconds, drop the conneciton.  Server frames are used instead of
realtime to avoid dropping the local client while debugging.

When a client is normally dropped, the client_t goes into a zombie state
for a few seconds to make sure any final reliable message gets resent
if necessary
==================
*/
void SV_CheckTimeouts ()
{
	int		i;
	client_t	*cl;
	int			droppoint;
	int			zombiepoint;

	droppoint = svs.realtime - 1000*timeout->value;
	zombiepoint = svs.realtime - 1000*zombietime->value;

	for (i=0,cl=svs.clients ; i<maxclients->value ; i++,cl++)
	{
		// message times may be wrong across a changelevel
		if (cl->lastmessage > svs.realtime)
			cl->lastmessage = svs.realtime;

		if (cl->state == cs_zombie
		&& cl->lastmessage < zombiepoint)
		{
			cl->state = cs_free;	// can now be reused
			continue;
		}
		if ( (cl->state == cs_connected || cl->state == cs_spawned) && cl->lastmessage < droppoint)
		{
			SV_BroadcastPrintf (PRINT_HIGH, "%s timed out\n", cl->name);
			SV_DropClient (cl); 
			cl->state = cs_free;	// don't bother with zombie state
		}
	}
}


/*
================
SVC_Ping

Just responds with an acknowledgement
================
*/
void SVC_Ping ()
{
	Netchan_OutOfBandPrint (NS_SERVER, net_from, "ack");
}


/*
================
SVC_Ack

================
*/
void SVC_Ack ()
{
	Com_Printf ("Ping acknowledge from %s\n", NET_AdrToString(net_from));
}


/*
================
SVC_Info

Responds with short info for broadcast scans
The second parameter should be the current protocol version number.
================
*/
void SVC_Info ()
{
	char	string[64];
	int		version, vers;

	if (maxclients->value == 1)
		return;		// ignore in single player

	version = atoi (Cmd_Argv(1));

	if(net_compatibility->value)
		vers = OLD_PROTOCOL_VERSION;
	else
		vers = PROTOCOL_VERSION;

	if (version != vers)
		Com_sprintf (string, sizeof(string), "%s: wrong version\n", hostname->string, sizeof(string));
	else
	{
		if(net_compatibility->value)
			Com_sprintf (string, sizeof(string), "%16s %8s %2i/%2i\n", hostname->string, sv.name, SV_NumClients(), (int)maxclients->value);
		else
		{
			char *cd, *ch = "", *lck;
			if (Cvar_VariableValue("sv_locked"))
				lck = "# ";
			else
				lck = "";
			if (Cvar_VariableValue ("coop") && !Cvar_VariableValue ("deathmatch"))
				cd = "Coop";
			else if (!Cvar_VariableValue ("coop") && Cvar_VariableValue ("deathmatch"))
			{
				cd = "DM";
				if (Cvar_VariableValue("cheats"))
					ch = "/cheats";
			}
			else
				cd = "??";
			Com_sprintf (string, sizeof(string), "%16s %8s %s%2i/%2i %s%s\n", hostname->string, sv.name, lck, SV_NumClients(), (int)maxclients->value, cd, ch);
		}
	}

	Netchan_OutOfBandPrint (NS_SERVER, net_from, "info\n%s", string);
}


/*
=================
SVC_GetChallenge

Returns a challenge number that can be used
in a subsequent client_connect command.
We do this to prevent denial of service attacks that
flood the server with invalid connection IPs.  With a
challenge, they must give a valid IP address.
=================
*/
void SVC_GetChallenge ()
{
	int		i;
	int		oldest;
	int		oldestTime;

	oldest = 0;
	oldestTime = 0x7fffffff;

	// see if we already have a challenge for this ip
	for (i = 0 ; i < MAX_CHALLENGES ; i++)
	{
		if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))
			break;
		if (svs.challenges[i].time < oldestTime)
		{
			oldestTime = svs.challenges[i].time;
			oldest = i;
		}
	}

	if (i == MAX_CHALLENGES)
	{
		// overwrite the oldest
		svs.challenges[oldest].challenge = rand() & 0x7fff;
		svs.challenges[oldest].adr = net_from;
		svs.challenges[oldest].time = curtime;
		i = oldest;
	}

	// send it back
	Netchan_OutOfBandPrint (NS_SERVER, net_from, "challenge %i", svs.challenges[i].challenge);
}

/*
===============
Info_ValueForKey

Searches the string for the given
key and returns the associated value, or an empty string.
===============
*/
char *Info_ValueForKey (char *s, char *key)
{
	char	pkey[MAX_INFO_STRING];
	static	char value[2][MAX_INFO_STRING];	// use two buffers so compares
								// work without stomping on each other
	static	int	valueindex;
	char	*o;
	
	valueindex ^= 1;
	if (*s == '\\')
		s++;
	while (1)
	{
		o = pkey;
		while (*s != '\\')
		{
			if (!*s)
				return "";
			*o++ = *s++;
		}
		*o = 0;
		s++;

		o = value[valueindex];

		while (*s != '\\' && *s)
		{
			if (!*s)
				return "";
			*o++ = *s++;
		}
		*o = 0;

		if (!strcmp (key, pkey) )
			return value[valueindex];

		if (!*s)
			return "";
		s++;
	}
}


int StringIsWhitespace (char *name)
{
	char *p = name;
	while (p[0])
	{
		if (!isspace (p[0]))
			return 0;
		p++;
	}
	return 1;
}

/*
==============
Netchan_Setup

called to open a channel to a remote system
==============
*/
void Netchan_Setup (netsrc_t sock, netchan_t *chan, netadr_t adr, int qport)
{
	Memset (chan, 0, sizeof(*chan));
	
	chan->sock = sock;
	chan->remote_address = adr;
	chan->qport = qport;
	chan->last_received = curtime;
	chan->incoming_sequence = 0;
	chan->outgoing_sequence = 1;

	SZ_Init (&chan->message, chan->message_buf, sizeof(chan->message_buf));
	chan->message.allowoverflow = true;
}


int Bin(int num, char *bufr)
{
	char	cnt = '0';
	int		cnt2 = 0;
	int		i;
	int		mask = 1;
	int		offs = 0;
	bool	first = true;

	for (i=0; i<32; i++)		// лимит до 32 ядер
	{
		if (num & mask)
		{
			if (!first)
				bufr[offs++]=',';
			bufr[offs++]=cnt;
			cnt2++;
			first = false;
		}
		cnt++;
		mask += mask;
	}

	return cnt2;
}

void Common_Frame (int msec)
{
	char	*s;
	int		time_before, time_between, time_after;

	if (setjmp (abortframe))			// an ERR_DROP was thrown
		return;

	if (NumberOfProcessors>1 && sys_priority->modified)	// нельзя повышать приоритет если всего 1 CPU, иначе огребем залипания и прочие тормоза
	{
		if (sys_priority->value<0)
		   Cvar_SetValue( "sys_priority", 0);
		else if (sys_priority->value>2)
		   Cvar_SetValue( "sys_priority", 2);
		sys_priority->modified = false;
		Com_Printf("Task priority changed to ");
		if (sys_priority->value==0)
		{
			SetPriorityClass (GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
			Com_Printf("NORMAL\n");
		}
		else if (sys_priority->value==1)
		{
			SetPriorityClass (GetCurrentProcess(), HIGH_PRIORITY_CLASS);
			Com_Printf("HIGH\n");
		}
		else
		{
			SetPriorityClass (GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
			Com_Printf("REALTIME\n");
		}
	}

	if (sys_affinity->modified)
	{
		int		cnt;
		char	bufr[128];
		Memset(bufr, 0, 128);
		if (sys_affinity->value<0)
			Cvar_SetValue( "sys_affinity", -sys_affinity->value);
		if (NumberOfProcessors>1)
		{
			int	mask = pow(2.0, NumberOfProcessors) - 1;
			Cvar_SetValue( "sys_affinity", (int)sys_affinity->value & mask);
			cnt = Bin((int)sys_affinity->value, bufr);
			if (cnt)
			{
				Com_Printf("AffinityMask = %i (%s core",(int)sys_affinity->value, bufr);
				if (cnt==1)
					Com_Printf(")\n");
				else
					Com_Printf("s)\n");
				SetThreadAffinityMask(GetCurrentThread(),(int)sys_affinity->value);
			}
		}
		sys_affinity->modified = false;
	}

	if (fixedtime->value)
		msec = fixedtime->value;
	else if (timescale->value)
		msec *= timescale->value;

	if (msec < 1)			/// Berserker: fixed bug: зависон при fixedtime < 1.0
		msec = 1;

	do
	{
		s = Sys_ConsoleInput ();
		if (s)
			Cbuf_AddText (va("%s\n",s));
	} while (s);

	Cbuf_Execute ();

	if (host_speeds->value)
		time_before = Sys_Milliseconds ();

	SV_Frame(msec);

	if (host_speeds->value)
		time_between = Sys_Milliseconds ();		

	CL_Frame(msec);

	if (host_speeds->value)
	{
		int	all, sv, gm, cl, rf, amb, lit;

		time_after = Sys_Milliseconds ();
		all = time_after - time_before;
		sv = time_between - time_before;
		cl = time_after - time_between;
		gm = time_after_game - time_before_game;
		rf = time_after_ref - time_before_ref;
		sv -= gm;
		cl -= rf;
		amb = time_before_ref_light - time_before_ref_ambient;
		lit = time_after_ref_light - time_before_ref_light;
		Com_Printf ("all:%3i sv:%3i gm:%3i cl:%3i rf:%3i rf_amb:%3i rf_lit:%3i\n", all, sv, gm, cl, rf, amb, lit);
	}
}


/*
==================
SVC_DirectConnect

A connection request that did not come from the master
==================
*/
void SVC_DirectConnect ()
{
	char		userinfo[MAX_INFO_STRING], *pass;
	netadr_t	adr;
	int			i;
	client_t	*cl, *newcl;
	client_t	temp;
	edict_t		*ent;
	int			edictnum;
	int			version, vers;
	int			qport;
	int			challenge;

	adr = net_from;

	Com_DPrintf ("SVC_DirectConnect (%s)\n", NET_AdrToString (adr));

	version = atoi(Cmd_Argv(1));
	if(net_compatibility->value)
		vers = OLD_PROTOCOL_VERSION;
	else
		vers = PROTOCOL_VERSION;

	if (version != vers)
	{
		Com_DPrintf ("    rejected connect from version %i\n", version);
		Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nServer's protocol version is %4.2f.\n", /*VERSION*/vers);
		return;
	}

	// attractloop servers are ONLY for local clients
	if (!NET_IsLocalAddress(adr) && sv.attractloop)
	{
		Com_Printf ("^3Remote connect in attract loop. Ignored.\n");
		Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nConnection refused.\n");
		return;
	}

	//r1: deny if server is locked
	if (sv_locked->value && !sv.attractloop)
	{
		Com_DPrintf ("    server locked\n");
		Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nServer is locked.\n");
		return;
	}

	qport = atoi(Cmd_Argv(2));

	challenge = atoi(Cmd_Argv(3));

	strncpy (userinfo, Cmd_Argv(4), sizeof(userinfo)-1);
	userinfo[sizeof(userinfo) - 1] = 0;

	// r1: check for null string
	if (!userinfo[0])
	{
		Com_DPrintf ("    empty userinfo string\n");
		Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nNull userinfo string.\n");
		return;
	}
	//r1ch: kick anyone trying to use the end-of-message-in-string exploit
	if (strchr(userinfo, '\xFF'))
	{
		Com_Printf ("^6EXPLOIT:^7 Client %s supplied userinfo string containing 0xFF\n", NET_AdrToString(adr));
		Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nConnection refused.\n");
		return;
	}
	if (!sv.attractloop)
	{
		pass = Info_ValueForKey (userinfo, "name");
		if (!pass[0] || StringIsWhitespace (pass))
		{
			Com_Printf ("^3Client %s with no name\n", NET_AdrToString(adr));
			Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nPlease set your name before connecting.\n");
			return;
		}
#if 0
		pass = Info_ValueForKey (userinfo, "ip");
		if (pass[0])
		{
			Com_Printf ("^6EXPLOIT:^7 Client %s[%s] attempted to spoof IP address: %s\n", Info_ValueForKey (userinfo, "name"), NET_AdrToString(adr), pass);
			return;
		}
#endif
	}

	//r1: check it is not overflowed, save enough bytes for /ip/111.222.333.444:55555
	if (strlen(userinfo) + 25 >= sizeof(userinfo)-1)
	{
		Com_DPrintf ("    userinfo length exceeded\n");
		Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nUserinfo string length exceeded.\n");
		return;
	}
	// force the IP key/value pair so the game can filter based on ip
	Info_SetValueForKey (userinfo, "ip", NET_AdrToString(net_from));

	// see if the challenge is valid
	if (!NET_IsLocalAddress (adr))
	{
		for (i=0 ; i<MAX_CHALLENGES ; i++)
		{
			if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))
			{
				if (challenge == svs.challenges[i].challenge)
					break;		// good
				Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nBad challenge.\n");
				return;
			}
		}
		if (i == MAX_CHALLENGES)
		{
			Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nNo challenge for address.\n");
			return;
		}
	}

	newcl = &temp;
	Memset (newcl, 0, sizeof(client_t));

	// if there is already a slot for this ip, reuse it
	for (i=0,cl=svs.clients ; i<maxclients->value ; i++,cl++)
	{
		if (cl->state == cs_free)
			continue;
		if (NET_CompareBaseAdr (adr, cl->netchan.remote_address)
			&& ( cl->netchan.qport == qport 
			|| adr.port == cl->netchan.remote_address.port ) )
		{
			if (!NET_IsLocalAddress (adr) && (svs.realtime - cl->lastconnect) < ((int)sv_reconnect_limit->value * 1000))
			{
				Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (adr));
				return;
			}
			Com_Printf ("%s:reconnect\n", NET_AdrToString (adr));
			newcl = cl;
			goto gotnewcl;
		}
	}

	// find a client slot
	newcl = NULL;
	for (i=0,cl=svs.clients ; i<maxclients->value ; i++,cl++)
	{
		if (cl->state == cs_free)
		{
			newcl = cl;
			break;
		}
	}
	if (!newcl)
	{
		Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nServer is full.\n");
		Com_DPrintf ("Rejected a connection.\n");
		return;
	}

gotnewcl:	
	// build a new connection
	// accept the new client
	// this is the only place a client_t is ever initialized
	*newcl = temp;
	sv_client = newcl;
	edictnum = (newcl-svs.clients)+1;
	ent = EDICT_NUM(edictnum);
	newcl->edict = ent;
	newcl->challenge = challenge; // save challenge for checksumming

	// get the game a chance to reject this connection or modify the userinfo
	if (!(ge->ClientConnect (ent, userinfo)))
	{
		if (*Info_ValueForKey (userinfo, "rejmsg")) 
			Netchan_OutOfBandPrint (NS_SERVER, adr, "print\n%s\nConnection refused.\n",  
				Info_ValueForKey (userinfo, "rejmsg"));
		else
			Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nConnection refused.\n" );
		Com_DPrintf ("Game rejected a connection.\n");
		return;
	}

	// parse some info from the info strings
	strncpy (newcl->userinfo, userinfo, sizeof(newcl->userinfo)-1);
	SV_UserinfoChanged (newcl);

	// send the connect packet to the client
	Netchan_OutOfBandPrint (NS_SERVER, adr, "client_connect");

	Netchan_Setup (NS_SERVER, &newcl->netchan , adr, qport);

	newcl->state = cs_connected;
	
	SZ_Init (&newcl->datagram, newcl->datagram_buf, sizeof(newcl->datagram_buf) );
	newcl->datagram.allowoverflow = true;
	newcl->lastmessage = svs.realtime;	// don't timeout
	newcl->lastconnect = svs.realtime;
}


/*
===============
SV_StatusString

Builds the string that is sent as heartbeats and status replies
===============
*/
char	*SV_StatusString ()
{
	char	player[1024];
	static char	status[MAX_MSGLEN - 16];
	int		i;
	client_t	*cl;
	int		statusLength;
	int		playerLength;

	strcpy (status, Cvar_Serverinfo());
	strcat (status, "\n");
	statusLength = strlen(status);

	for (i=0 ; i<maxclients->value ; i++)
	{
		cl = &svs.clients[i];
		if (cl->state == cs_connected || cl->state == cs_spawned )
		{
			Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n", 
				cl->edict->client->ps.stats[STAT_FRAGS], cl->ping, cl->name);
			playerLength = strlen(player);
			if (statusLength + playerLength >= sizeof(status) )
				break;		// can't hold any more
			strcpy (status + statusLength, player);
			statusLength += playerLength;
		}
	}

	return status;
}


/*
================
SVC_Status

Responds with all the info that qplug or qspy can see
================
*/
void SVC_Status ()
{
	Netchan_OutOfBandPrint (NS_SERVER, net_from, "print\n%s", SV_StatusString());
}


int Rcon_Validate ()
{
	if (!strlen (rcon_password->string))
		return 0;

	if (strcmp (Cmd_Argv(1), rcon_password->string) )
		return 0;

	return 1;
}


void Com_BeginRedirect (int target, char *buffer, int buffersize, void (*flush_))
{
	if (!target || !buffer || !buffersize || !flush_)
		return;
	rd_target = target;
	rd_buffer = buffer;
	rd_buffersize = buffersize;
	rd_flush = (void (__cdecl *)(int,char *)) flush_;

	*rd_buffer = 0;
}


void Com_EndRedirect ()
{
	rd_flush(rd_target, rd_buffer);

	rd_target = 0;
	rd_buffer = NULL;
	rd_buffersize = 0;
	rd_flush = NULL;
}


void SV_FlushRedirect (int sv_redirected, char *outputbuf)
{
	if (sv_redirected == RD_PACKET)
	{
		Netchan_OutOfBandPrint (NS_SERVER, net_from, "print\n%s", outputbuf);
	}
	else if (sv_redirected == RD_CLIENT)
	{
		MSG_WriteByte (&sv_client->netchan.message, svc_print);
		MSG_WriteByte (&sv_client->netchan.message, PRINT_HIGH);
		MSG_WriteString (&sv_client->netchan.message, outputbuf);
	}
}


/*
===============
SVC_RemoteCommand

A client issued an rcon command.
Shift down the remaining args
Redirect all printfs
===============
*/
void SVC_RemoteCommand ()
{
	int		i;
	char	remaining[1024];

	i = Rcon_Validate ();

	if (i == 0)
		Com_Printf ("^1Bad rcon from %s:\n%s\n", NET_AdrToString (net_from), net_message.data+4);
	else
		Com_Printf ("Rcon from %s:\n%s\n", NET_AdrToString (net_from), net_message.data+4);

	Com_BeginRedirect (RD_PACKET, sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);

	if (!Rcon_Validate ())
	{
		Com_Printf ("^1Bad rcon_password.\n");
	}
	else
	{
		remaining[0] = 0;

		for (i=2 ; i<Cmd_Argc() ; i++)
		{
			strcat (remaining, Cmd_Argv(i) );
			strcat (remaining, " ");
		}

		Cmd_ExecuteString (remaining);
	}

	Com_EndRedirect ();
}


/*
=================
SV_ConnectionlessPacket

A connectionless packet has four leading 0xff
characters to distinguish it from a game channel.
Clients that are in the game can still send
connectionless packets.
=================
*/
void SV_ConnectionlessPacket ()
{
	char	*s;
	char	*c;

	MSG_BeginReading (&net_message);
	MSG_ReadLong (&net_message);		// skip the -1 marker

	s = MSG_ReadStringLine (&net_message);

	Cmd_TokenizeString (s, false);

	c = Cmd_Argv(0);
	Com_DPrintf ("Packet %s : %s\n", NET_AdrToString(net_from), c);

	if (!strcmp(c, "ping"))
		SVC_Ping ();
	else if (!strcmp(c, "ack"))
		SVC_Ack ();
	else if (!strcmp(c,"status"))
		SVC_Status ();
	else if (!strcmp(c,"info"))
		SVC_Info ();
	else if (!strcmp(c,"getchallenge"))
		SVC_GetChallenge ();
	else if (!strcmp(c,"connect"))
		SVC_DirectConnect ();
	else if (!strcmp(c, "rcon"))
		SVC_RemoteCommand ();
	else
		Com_Printf ("^1bad connectionless packet from %s:\n%s\n", NET_AdrToString (net_from), s);
}


/*
=================
Netchan_Process

called when the current net_message is from remote_address
modifies net_message so that it points to the packet payload
=================
*/
bool	Netchan_Process (netchan_t *chan, sizebuf_t *msg)
{
	unsigned	sequence, sequence_ack;
	unsigned	reliable_ack, reliable_message;
	int			qport;

// get sequence numbers		
	MSG_BeginReading (msg);
	sequence = MSG_ReadLong (msg);
	sequence_ack = MSG_ReadLong (msg);

	// read the qport if we are a server
	if (chan->sock == NS_SERVER)
		qport = MSG_ReadShort (msg);

	reliable_message = sequence >> 31;
	reliable_ack = sequence_ack >> 31;

	sequence &= ~(1<<31);
	sequence_ack &= ~(1<<31);	

//
// discard stale or duplicated packets
//
	if (sequence <= chan->incoming_sequence)
		return false;

//
// dropped packets don't keep the message from being used
//
	chan->dropped = sequence - (chan->incoming_sequence+1);

//
// if the current outgoing reliable message has been acknowledged
// clear the buffer to make way for the next
//
	if (reliable_ack == chan->reliable_sequence)
		chan->reliable_length = 0;	// it has been received
	
//
// if this message contains a reliable message, bump incoming_reliable_sequence 
//
	chan->incoming_sequence = sequence;
	chan->incoming_acknowledged = sequence_ack;
	chan->incoming_reliable_acknowledged = reliable_ack;
	if (reliable_message)
	{
		chan->incoming_reliable_sequence ^= 1;
	}

//
// the message can now be read from the current message pointer
//
	chan->last_received = curtime;

	return true;
}


void CRC_Init(WORD *crcvalue)
{
	*crcvalue = CRC_INIT_VALUE;
}


WORD CRC_Block (byte *start, int count)
{
	WORD	crc;

	CRC_Init (&crc);
	while (count--)
		crc = (crc << 8) ^ crctable[(crc >> 8) ^ *start++];

	return crc;
}


/*
====================
COM_BlockSequenceCRCByte

For proxy protecting
====================
*/
byte	COM_BlockSequenceCRCByte (byte *base, int length, int sequence)
{
	int		n;
	byte	*p;
	int		x;
	byte	chkb[60 + 4];
	WORD	crc;

	if (sequence < 0)
		Sys_Error("sequence < 0, this shouldn't happen\n");

	p = chktbl + (sequence % (sizeof(chktbl) - 4));

	if (length > 60)
		length = 60;
	Memcpy (chkb, base, length);

	chkb[length] = p[0];
	chkb[length+1] = p[1];
	chkb[length+2] = p[2];
	chkb[length+3] = p[3];

	length += 4;

	crc = CRC_Block(chkb, length);

	for (x=0, n=0; n<length; n++)
		x += chkb[n];

	crc = (crc ^ x) & 0xff;

	return crc;
}


void SV_ClientThink (client_t *cl, usercmd_t *cmd)

{
	cl->commandMsec -= cmd->msec;

	if (cl->commandMsec < 0 && sv_enforcetime->value )
	{
		Com_DPrintf ("commandMsec underflow from %s\n", cl->name);
		return;
	}

	ge->ClientThink (cl->edict, cmd);
}


void SV_BeginDemoserver ()
{
	int		len;
	char	name[MAX_OSPATH];
	char	checkname[MAX_OSPATH];

	Com_sprintf (name, sizeof(name), "demos/%s", sv.name);
	len = FS_FOpenFile (name, &sv.demofile, false, -1);

	if (!sv.demofile && !file_from_pk2)
		Com_Error (ERR_DROP, "Couldn't open %s", name);

	if(file_from_pk2)
	{
		if(net_compatibility->value)
			Com_sprintf (checkname, sizeof(checkname), "%s/cache/temp.dm2", FS_Gamedir());
		else
			Com_sprintf (checkname, sizeof(checkname), "%s/cache/temp.dem", FS_Gamedir());
		FS_CreatePath(checkname);
		sv.demofile = FS_Fopen(checkname, "wb");
		if (!sv.demofile)
			Com_Error (ERR_DROP, "Couldn't create temporary demo file %s", checkname);
		fwrite (zipdata, 1, len, sv.demofile);
		Z_Free(zipdata);
		fclose (sv.demofile);
		sv.demofile = FS_Fopen(checkname, "rb");
		if (!sv.demofile)
			Com_Error (ERR_DROP, "Couldn't read temporary demo file %s", checkname);
	}
}


/*
================
SV_New_f

Sends the first message from the server to a connected client.
This will be sent on the initial connection and upon each server load.
================
*/
void SV_New_f ()
{
	char		*gamedir;
	int			playernum;
	edict_t		*ent;

	Com_DPrintf ("New() from %s\n", sv_client->name);

	if (sv_client->state != cs_connected)
	{
		Com_Printf ("^3New not valid -- already spawned\n");
		return;
	}

	// demo servers just dump the file message
	if (sv.state == ss_demo)
	{
		SV_BeginDemoserver ();
		return;
	}

	//
	// serverdata needs to go over for all types of servers
	// to make sure the protocol is right, and to set the gamedir
	//
	gamedir = Cvar_VariableString ("gamedir");

	// send the serverdata
	MSG_WriteByte (&sv_client->netchan.message, svc_serverdata);
	if(net_compatibility->value)
		MSG_WriteLong (&sv_client->netchan.message, OLD_PROTOCOL_VERSION);
	else
		MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION);
	MSG_WriteLong (&sv_client->netchan.message, svs.spawncount);
	MSG_WriteByte (&sv_client->netchan.message, sv.attractloop);
	MSG_WriteString (&sv_client->netchan.message, gamedir);

	if (sv.state == ss_cinematic || sv.state == ss_pic)
		playernum = -1;
	else
		playernum = sv_client - svs.clients;
	MSG_WriteShort (&sv_client->netchan.message, playernum);

	// send full levelname
	MSG_WriteString (&sv_client->netchan.message, sv.configstrings[CS_NAME]);

	//
	// game server
	// 
	if (sv.state == ss_game)
	{
		// set up the entity for the client
		ent = EDICT_NUM(playernum+1);
		ent->s.number = playernum+1;
		sv_client->edict = ent;
		Memset (&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd));

		// begin fetching configstrings
		MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
		MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i 0\n",svs.spawncount) );
	}
}


void Cbuf_InsertFromDefer ()
{
	Cbuf_InsertText (defer_text_buf);
	defer_text_buf[0] = 0;
}


void SV_Begin_f ()
{
	Com_DPrintf ("Begin() from %s\n", sv_client->name);

	// handle the case of a level changing while a client was connecting
	if ( atoi(Cmd_Argv(1)) != svs.spawncount )
	{
		Com_Printf ("^3SV_Begin_f from different level\n");
		SV_New_f ();
		return;
	}

	sv_client->state = cs_spawned;

	// call the game begin function
	ge->ClientBegin (sv_player);

	Cbuf_InsertFromDefer ();
}


/*
=================
SV_Disconnect_f

The client is going to disconnect, so remove the connection immediately
=================
*/
void SV_Disconnect_f ()
{
///	Com_Printf("%s disconnected\n", sv_client->name);
	SV_DropClient (sv_client);
}


/*
==================
SV_ShowServerinfo_f

Dumps the serverinfo info string
==================
*/
void SV_ShowServerinfo_f ()
{
	Info_Print (Cvar_Serverinfo());
}


void SV_Configstrings_f ()
{
	int			start;

	Com_DPrintf ("Configstrings() from %s\n", sv_client->name);

	if (sv_client->state != cs_connected)
	{
		Com_Printf ("^3configstrings not valid -- already spawned\n");
		return;
	}

	// handle the case of a level changing while a client was connecting
	if ( atoi(Cmd_Argv(1)) != svs.spawncount )
	{
		Com_Printf ("^3SV_Configstrings_f from different level\n");
		SV_New_f ();
		return;
	}

	start = atoi(Cmd_Argv(2));

	int	max_configstrings;
	if (net_compatibility->value)
		max_configstrings = MAX_CONFIGSTRINGS_Q2;
	else
		max_configstrings = MAX_CONFIGSTRINGS_BERS;
	// write a packet full of data
	while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2 && start < max_configstrings)
	{
		if (sv.configstrings[start][0])
		{
			MSG_WriteByte (&sv_client->netchan.message, svc_configstring);
			MSG_WriteShort (&sv_client->netchan.message, start);
			MSG_WriteString (&sv_client->netchan.message, sv.configstrings[start]);
		}
		start++;
	}

	// send next command
	if (start == max_configstrings)
	{
		MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
		MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) );
	}
	else
	{
		MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
		MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i %i\n",svs.spawncount, start) );
	}
}


void SV_Baselines_f ()
{
	int		start;
	entity_state_t	nullstate;
	entity_state_t	*base;

	Com_DPrintf ("Baselines() from %s\n", sv_client->name);

	if (sv_client->state != cs_connected)
	{
		Com_Printf ("^3baselines not valid -- already spawned\n");
		return;
	}

	// handle the case of a level changing while a client was connecting
	if ( atoi(Cmd_Argv(1)) != svs.spawncount )
	{
		Com_Printf ("^3SV_Baselines_f from different level\n");
		SV_New_f ();
		return;
	}

	start = atoi(Cmd_Argv(2));
	if (start < 0 || start >= MAX_EDICTS)
	{	//r1: huge security fix !! remote DoS by negative here.
		Com_Printf ("Illegal baseline offset from %s[%s]\n", sv_client->name, NET_AdrToString(sv_client->netchan.remote_address));
		SV_DropClient (sv_client);
		return;
	}

	Memset (&nullstate, 0, sizeof(nullstate));

	// write a packet full of data
	while ( sv_client->netchan.message.cursize <  MAX_MSGLEN/2 && start < MAX_EDICTS)
	{
		base = &sv.baselines[start];
		if (base->modelindex || base->sound || base->effects)
		{
			MSG_WriteByte (&sv_client->netchan.message, svc_spawnbaseline);
			MSG_WriteDeltaEntity (&nullstate, base, &sv_client->netchan.message, true, true);
		}
		start++;
	}

	// send next command
	if (start == MAX_EDICTS)
	{
		MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
		MSG_WriteString (&sv_client->netchan.message, va("precache %i\n", svs.spawncount) );
	}
	else
	{
		MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
		MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) );
	}
}


/*
==================
SV_Nextserver_f

A cinematic has completed or been aborted by a client, so move
to the next server,
==================
*/
void SV_Nextserver_f ()
{
	if ( atoi(Cmd_Argv(1)) != svs.spawncount )
	{
		Com_DPrintf ("Nextserver() from wrong level, from %s\n", sv_client->name);
		return;		// leftover from last server
	}

	Com_DPrintf ("Nextserver() from %s\n", sv_client->name);
	SV_Nextserver ();
}

void SV_Save_f()
{
#if 0
	if (SV_NumClients() == 1)
	{
		if (Cvar_VariableValue("deathmatch"))
			PF_cprintf (sv_player, PRINT_HIGH, "Can't savegame in a deathmatch\n");
		else
		{
			PF_cprintf (sv_player, PRINT_HIGH, "Saving game...\n");
			Cbuf_AddText("save server\n");
		}
	}
	else
		PF_cprintf (sv_player, PRINT_HIGH, "Can't savegame in a multiplayer\n");
#else	/// Разблокируем возможность сохранения игры в мультиплеере
	if (Cvar_VariableValue("deathmatch"))
		PF_cprintf (sv_player, PRINT_HIGH, "Can't savegame in a deathmatch\n");
	else
	{
		if (SV_NumClients() != 1)
			PF_cprintf (sv_player, PRINT_HIGH, "WARNING: loadgame in a multiplayer may be unstable!\n");
		PF_cprintf (sv_player, PRINT_HIGH, "Saving game...\n");
		Cbuf_AddText("save server\n");
	}
#endif
}

void SV_Load_f()
{
#if 0
	if (SV_NumClients() == 1)
		Cbuf_AddText("load server\n");
	else
		PF_cprintf (sv_player, PRINT_HIGH, "Can't loadgame in a multiplayer\n");
#else	/// Разблокируем возможность восстановления игры в мультиплеере
	Cbuf_AddText("load server\n");
#endif
}

ucmd_t ucmds[] =
{
	// auto issued
	{"new", SV_New_f},
	{"configstrings", SV_Configstrings_f},
	{"baselines", SV_Baselines_f},
	{"begin", SV_Begin_f},
	{"nextserver", SV_Nextserver_f},
	{"disconnect", SV_Disconnect_f},
	// issued by hand at client consoles	
	{"info", SV_ShowServerinfo_f},
	{"download", SV_BeginDownload_f},
	{"nextdl", SV_NextDownload_f},
	// Berserker: availables the save/load the game for remoted dedicated servers
	{"sv_save", SV_Save_f},
	{"sv_load", SV_Load_f},
	{NULL, NULL}
};


// anti-exploit by R1CH
void SV_ExecuteUserCommand (char *s)
{
	char	*teststring;
	ucmd_t	*u;

	//r1: catch attempted command expansions
	if (strchr(s, '$'))
	{
		teststring = Cmd_MacroExpandString(s);
		if (!teststring)
			return;
		if (strcmp (teststring, s))
		{
			Com_Printf ("^6EXPLOIT:^7 Client %s[%s] attempted macro-expansion\n", sv_client->name, NET_AdrToString(sv_client->netchan.remote_address));
			return;
		}
	}

	//r1: catch end-of-message exploit
	if (strchr (s, '\xFF'))
	{
		Com_Printf ("^6EXPLOIT:^7 Client %s[%s] tried to use a command containing 0xFF\n", sv_client->name, NET_AdrToString(sv_client->netchan.remote_address));
		return;
	}

	Cmd_TokenizeString (s, false);	// было true (закрываем "rcon_password" exploit)
	sv_player = sv_client->edict;

	for (u=ucmds ; u->name ; u++)
///		if (!strcmp (Cmd_Argv(0), u->name) )
		if (!Q_strcasecmp (Cmd_Argv(0), u->name) )		// Berserker: уберем case-чувствительность серверных команд
		{
			u->func ();
			//r1ch: why break?
			return;
		}

	if (!u->name && sv.state == ss_game)
		ge->ClientCommand (sv_player);
}


/// Berserker: В старом Q2 при изменении даже одного userinfo параметра (обычно fov), передается по сети вся строка!
/// Поэтому, для экономии сетевого трафика, следует передавать только изменившиеся параметры.
/// Функция Info_Union объединяет старый userinfo (бывший на сервере) и пришедшую изменившуюся userinfo строку.
void	Info_Union (char *oldinfo, char *newinfo)
{
	char	*o;
	char	pkey[MAX_INFO_STRING];
	char	val[MAX_INFO_STRING];

	if (*newinfo == '\\')
		newinfo++;

	while(1)
	{
		o = pkey;
		while (*newinfo != '\\')
		{
			if (!*newinfo)
				return;
			*o++ = *newinfo++;
		}
		*o = 0;
		newinfo++;

		o = val;
		while (*newinfo != '\\' && *newinfo)
		{
			if (!*newinfo)
				return;
			*o++ = *newinfo++;
		}
		*o = 0;
		newinfo++;

		Info_SetValueForKey(oldinfo, pkey, val);

		if (!*newinfo)
			return;
	}
}


/*
===================
SV_ExecuteClientMessage

The current net_message is parsed for the given client
===================
*/
void SV_ExecuteClientMessage (client_t *cl)
{
	int		c;
	char	*s;

	usercmd_t	nullcmd;
	usercmd_t	oldest, oldcmd, newcmd;
	int		net_drop;
	int		stringCmdCount;
	int		checksum, calculatedChecksum;
	int		checksumIndex;
	bool	move_issued;
	int		lastframe;

	sv_client = cl;
	sv_player = sv_client->edict;

	// only allow one move command
	move_issued = false;
	stringCmdCount = 0;

	while (1)
	{
		if (net_message.readcount > net_message.cursize)
		{
			Com_Printf ("^1SV_ReadClientMessage: badread\n");
			SV_DropClient (cl);
			return;
		}	

		c = MSG_ReadByte (&net_message);
		if (c == -1)
			break;
				
		switch (c)
		{
		default:
			Com_Printf ("^1SV_ReadClientMessage: unknown command char\n");
			SV_DropClient (cl);
			return;

		case clc_nop:
			break;

		case clc_userinfo:
			if(net_compatibility->value)
				strncpy (cl->userinfo, MSG_ReadString (&net_message), sizeof(cl->userinfo)-1);
			else
				Info_Union (cl->userinfo, MSG_ReadString (&net_message));
			SV_UserinfoChanged (cl);
			break;

		case clc_move:
			if (move_issued)
				return;		// someone is trying to cheat...

			move_issued = true;
			checksumIndex = net_message.readcount;
			checksum = MSG_ReadByte (&net_message);
			lastframe = MSG_ReadLong (&net_message);

			//r1ch: allow server admins to stop clients from using nodelta
			//note, this doesn't affect server->client if the clients frame
			//was too old (ie client lagged out) so should be safe to enable
			//nodelta clients typically consume 4-5x bandwidth than normal.
			if (lastframe == -1 && cl->lastframe == -1)
			{
				if (++cl->nodeltaframes >= 100 && !sv_allownodelta->value)
				{
					SV_DropClient (cl);
					return;
				}
			}
			else
				cl->nodeltaframes = 0;

			if (lastframe != cl->lastframe)
			{
				cl->lastframe = lastframe;
				if (cl->lastframe > 0)
				{
					cl->frame_latency[cl->lastframe&(LATENCY_COUNTS-1)] = 
						svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime;
				}
			}

			Memset (&nullcmd, 0, sizeof(nullcmd));
			MSG_ReadDeltaUsercmd (&net_message, &nullcmd, &oldest);
			if (net_message.readcount > net_message.cursize)		// r1ch
			{
				SV_DropClient (cl);
				return;
			}
			MSG_ReadDeltaUsercmd (&net_message, &oldest, &oldcmd);
			if (net_message.readcount > net_message.cursize)		// r1ch
			{
				SV_DropClient (cl);
				return;
			}
			MSG_ReadDeltaUsercmd (&net_message, &oldcmd, &newcmd);
			if (net_message.readcount > net_message.cursize)		// r1ch
			{
				SV_DropClient (cl);
				return;
			}

			if ( cl->state != cs_spawned )
			{
				cl->lastframe = -1;
				break;
			}

			// if the checksum fails, ignore the rest of the packet
			calculatedChecksum = COM_BlockSequenceCRCByte (
				net_message.data + checksumIndex + 1,
				net_message.readcount - checksumIndex - 1,
				cl->netchan.incoming_sequence);

			if (calculatedChecksum != checksum)
			{
				Com_DPrintf ("Failed command checksum for %s (%d != %d)/%d\n", 
					cl->name, calculatedChecksum, checksum, 
					cl->netchan.incoming_sequence);
				return;
			}

			if (!sv_paused->value)
			{
				net_drop = cl->netchan.dropped;
				if (net_drop < 20)
				{
					while (net_drop > 2)
					{
						SV_ClientThink (cl, &cl->lastcmd);

						net_drop--;
					}
					if (net_drop > 1)
						SV_ClientThink (cl, &oldest);

					if (net_drop > 0)
						SV_ClientThink (cl, &oldcmd);

				}
				SV_ClientThink (cl, &newcmd);
			}

			cl->lastcmd = newcmd;
			break;

		case clc_stringcmd:	
			s = MSG_ReadString (&net_message);

			// malicious users may try using too many string commands
			if (++stringCmdCount < MAX_STRINGCMDS)
				SV_ExecuteUserCommand (s);

			if (cl->state == cs_zombie)
				return;	// disconnect command
			break;
		}
	}
}


void SV_ReadPackets ()
{
	int			i;
	client_t	*cl;
	int			qport;

	while (NET_GetPacket (NS_SERVER, &net_from, &net_message))
	{
		// check for connectionless packet (0xffffffff) first
		if (*(int *)net_message.data == -1)
		{
			SV_ConnectionlessPacket ();
			continue;
		}

		// read the qport out of the message so we can fix up
		// stupid address translating routers
		MSG_BeginReading (&net_message);
		MSG_ReadLong (&net_message);		// sequence number
		MSG_ReadLong (&net_message);		// sequence number
		qport = MSG_ReadShort (&net_message) & 0xffff;

		// check for packets from connected clients
		for (i=0, cl=svs.clients ; i<maxclients->value ; i++,cl++)
		{
			if (cl->state == cs_free)
				continue;
			if (!NET_CompareBaseAdr (net_from, cl->netchan.remote_address))
				continue;
			if (cl->netchan.qport != qport)
				continue;
			if (cl->netchan.remote_address.port != net_from.port)
			{
				Com_Printf ("^3SV_ReadPackets: fixing up a translated port\n");
				cl->netchan.remote_address.port = net_from.port;
			}

			if (Netchan_Process(&cl->netchan, &net_message))
			{	// this is a valid, sequenced packet, so process it
				if (cl->state != cs_zombie)
				{
					cl->lastmessage = svs.realtime;	// don't timeout
					SV_ExecuteClientMessage (cl);
				}
			}
			break;
		}
		
		if (i != maxclients->value)
			continue;
	}
}


// sleeps msec or until net socket is ready
void NET_Sleep(int msec)
{
    struct timeval timeout;
	fd_set	fdset;
	extern cvar_t *dedicated;
	int i;

	if (!dedicated || !dedicated->value)
		return; // we're not a server, just run full speed

	FD_ZERO(&fdset);
	i = 0;
	if (ip_sockets[NS_SERVER]) {
		FD_SET(ip_sockets[NS_SERVER], &fdset); // network socket
		i = ip_sockets[NS_SERVER];
	}
	if (ipx_sockets[NS_SERVER]) {
		FD_SET(ipx_sockets[NS_SERVER], &fdset); // network socket
		if (ipx_sockets[NS_SERVER] > i)
			i = ipx_sockets[NS_SERVER];
	}
	timeout.tv_sec = msec/1000;
	timeout.tv_usec = (msec%1000)*1000;
	select(i+1, &fdset, NULL, NULL, &timeout);
}


/*
===================
SV_CalcPings

Updates the cl->ping variables
===================
*/
void SV_CalcPings ()
{
	int			i, j;
	client_t	*cl;
	int			total, count;

	for (i=0 ; i<maxclients->value ; i++)
	{
		cl = &svs.clients[i];
		if (cl->state != cs_spawned )
			continue;

		total = 0;
		count = 0;
		for (j=0 ; j<LATENCY_COUNTS ; j++)
		{
			if (cl->frame_latency[j] > 0)
			{
				count++;
				total += cl->frame_latency[j];
			}
		}
		if (!count)
			cl->ping = 0;
		else
			cl->ping = total / count;

		// let the game dll know about the ping
		cl->edict->client->ping = cl->ping;
	}
}


/*
===================
SV_GiveMsec

Every few frames, gives all clients an allotment of milliseconds
for their command moves.  If they exceed it, assume cheating.
===================
*/
void SV_GiveMsec ()
{
	int			i;
	client_t	*cl;

	if (sv.framenum & 15)
		return;

	for (i=0 ; i<maxclients->value ; i++)
	{
		cl = &svs.clients[i];
		if (cl->state == cs_free )
			continue;
		
		cl->commandMsec = 1800;		// 1600 + some slop
	}
}


void SV_RunGameFrame ()
{
	if (host_speeds->value)
		time_before_game = Sys_Milliseconds ();

	// we always need to bump framenum, even if we
	// don't run the world, otherwise the delta
	// compression can get confused when a client
	// has the "current" frame
	sv.framenum++;
	sv.time = sv.framenum*100;

	// don't run if paused
	if (!sv_paused->value || maxclients->value > 1)
	{
		ge->RunFrame (maxclients->value==1 ? sv_stopclock->value : 0);

		// never get more than one tic behind
		if (sv.time < svs.realtime)
		{
			if (sv_showclamp->value)
				Com_Printf ("sv highclamp\n");
			svs.realtime = sv.time;
		}
	}

	if (host_speeds->value)
		time_after_game = Sys_Milliseconds ();

}


/*
=======================
SV_RateDrop

Returns true if the client is over its current
bandwidth estimation and should not be sent another packet
=======================
*/
bool SV_RateDrop (client_t *c)
{
	int		total;
	int		i;

	// never drop over the loopback
	if (c->netchan.remote_address.type == NA_LOOPBACK)
		return false;

	total = 0;

	for (i = 0 ; i < RATE_MESSAGES ; i++)
		total += c->message_size[i];

	if (total > c->rate)
	{
		c->surpressCount++;
		c->message_size[sv.framenum % RATE_MESSAGES] = 0;
		return true;
	}

	return false;
}


/*
=================
CM_WriteAreaBits

Writes a length byte followed by a bit vector of all the areas
that area in the same flood as the area parameter

This is used by the client refreshes to cull visibility
=================
*/
int CM_WriteAreaBits (byte *buffer, int area)
{
	int		i;
	int		floodnum;
	int		bytes;

	bytes = (numareas+7)>>3;

	if (map_noareas->value)
	{	// for debugging, send everything
		Memset (buffer, 255, bytes);
	}
	else
	{
		Memset (buffer, 0, bytes);

		floodnum = map_areas[area].floodnum;
		for (i=0 ; i<numareas ; i++)
		{
			if (map_areas[i].floodnum == floodnum || !area)
				buffer[i>>3] |= 1<<(i&7);
		}
	}

	return bytes;
}


byte		fatpvs[MAX_MAP_LEAFS/8];
/*
============
SV_FatPVS

The client will interpolate the view position,
so we can't use a single PVS point
===========
*/
void SV_FatPVS (vec3_t org)
{
	int		leafs[64];
	int		i, j, count;
	int		longs;
	byte	*src;
	vec3_t	mins, maxs;

	for (i=0 ; i<3 ; i++)
	{
		mins[i] = org[i] - 8;
		maxs[i] = org[i] + 8;
	}

	count = CM_BoxLeafnums (mins, maxs, leafs, 64, NULL);
	if (count < 1)
		Com_Error (ERR_FATAL, "SV_FatPVS: count < 1");
	longs = (CM_NumClusters()+31)>>5;

	// convert leafs to clusters
	for (i=0 ; i<count ; i++)
		leafs[i] = CM_LeafCluster(leafs[i]);

	Memcpy (fatpvs, CM_ClusterPVS(leafs[0]), longs<<2);
	// or in all the other leaf bits
	for (i=1 ; i<count ; i++)
	{
		for (j=0 ; j<i ; j++)
			if (leafs[i] == leafs[j])
				break;
		if (j != i)
			continue;		// already have the cluster we want
		src = CM_ClusterPVS(leafs[i]);
		for (j=0 ; j<longs ; j++)
			((long *)fatpvs)[j] |= ((long *)src)[j];
	}
}


/*
=============
CM_HeadnodeVisible

Returns true if any leaf under headnode has a cluster that
is potentially visible
=============
*/
bool CM_HeadnodeVisible (int nodenum, byte *visbits)
{
	int		leafnum;
	int		cluster;
	cnode_t	*node;

	if (nodenum < 0)
	{
		leafnum = -1-nodenum;
		cluster = map_leafs[leafnum].cluster;
		if (cluster == -1)
			return false;
		if (visbits[cluster>>3] & (1<<(cluster&7)))
			return true;
		return false;
	}

	node = &map_nodes[nodenum];
	if (CM_HeadnodeVisible(node->children[0], visbits))
		return true;
	return CM_HeadnodeVisible(node->children[1], visbits);
}


/*
=============
SV_BuildClientFrame

Decides which entities are going to be visible to the client, and
copies off the playerstat and areabits.
=============
*/
void SV_BuildClientFrame (client_t *client)
{
	int		e, i;
	vec3_t	org;
	edict_t	*ent;
	edict_t	*clent;
	client_frame_t	*frame;
	entity_state_t	*state;
	int		l;
	int		clientarea, clientcluster;
	int		leafnum;
///	int		c_fullsend;
	byte	*clientphs;
	byte	*bitvector;

	clent = client->edict;
	if (!clent->client)
		return;		// not in game yet

	// this is the frame we are creating
	frame = &client->frames[sv.framenum & UPDATE_MASK];

	frame->senttime = svs.realtime; // save it for ping calc later

	// find the client's PVS
	for (i=0 ; i<3 ; i++)
		org[i] = clent->client->ps.pmove.origin[i]*0.125 + clent->client->ps.viewoffset[i];

	leafnum = CM_PointLeafnum (org);
	clientarea = CM_LeafArea (leafnum);
	clientcluster = CM_LeafCluster (leafnum);

	// calculate the visible areas
	frame->areabytes = CM_WriteAreaBits (frame->areabits, clientarea);

	// grab the current player_state_t
	frame->ps = clent->client->ps;

	SV_FatPVS (org);
	clientphs = CM_ClusterPHS (clientcluster);

	// build up the list of visible entities
	frame->num_entities = 0;
	frame->first_entity = svs.next_client_entities;

///	c_fullsend = 0;

	for (e=1 ; e<ge->num_edicts ; e++)
	{
		ent = EDICT_NUM(e);

		// ignore ents without visible models
		if (ent->svflags & SVF_NOCLIENT)
			continue;

		// ignore ents without visible models unless they have an effect
		if (!ent->s.modelindex && !ent->s.effects && !ent->s.sound && !ent->s.event)
			continue;

		// ignore if not touching a PV leaf
		if (ent != clent)
		{
			// check area
			if (!CM_AreasConnected (clientarea, ent->areanum))
			{	// doors can legally straddle two areas, so
				// we may need to check another one
				if (!ent->areanum2 || !CM_AreasConnected (clientarea, ent->areanum2))
					continue;		// blocked by a door
			}

			ent->s.effects &= ~EF_FLASHLIGHT_NOENT;
			bool flash = (ent->s.effects & EF_FLASHLIGHT);	/// Berserker: если оружие-"фонарик" не отделено дверями,
															/// то форсируем отправку такого entity текущему клиенту.
			// beams just check one point for PHS
			if (ent->s.renderfx & RF_BEAM)
			{
				l = ent->clusternums[0];
				if ( !(clientphs[l >> 3] & (1 << (l&7) )) )
					continue;
			}
			else
			{
				// FIXME: if an ent has a model and a sound, but isn't
				// in the PVS, only the PHS, clear the model
///				if (ent->s.sound)
///				{
///					bitvector = fatpvs;	//clientphs;
///				}
///				else
					bitvector = fatpvs;

				if (ent->num_clusters == -1)
				{	// too many leafs for individual check, go by headnode
					if (!CM_HeadnodeVisible (ent->headnode, bitvector))
					{
///						continue;
						if (flash)
							ent->s.effects |= EF_FLASHLIGHT_NOENT;
						else
							continue;
///					c_fullsend++;
					}
				}
				else
				{	// check individual leafs
					for (i=0 ; i < ent->num_clusters ; i++)
					{
						l = ent->clusternums[i];
						if (bitvector[l >> 3] & (1 << (l&7) ))
							break;
					}
					if (i == ent->num_clusters)
					{
///						continue;		// not visible
						if (flash)
							ent->s.effects |= EF_FLASHLIGHT_NOENT;
						else
							continue;
					}
				}

				if (!ent->s.modelindex)
				{	// don't send sounds if they will be attenuated away
					vec3_t	delta;
					float	len;

					VectorSubtract (org, ent->s.origin, delta);
					len = VectorLength (delta);
					if (len > 400)
					{
///						continue;
						if (flash)
							ent->s.effects |= EF_FLASHLIGHT_NOENT;
						else
							continue;
					}
				}
			}
		}

		// add it to the circular client_entities array
		state = &svs.client_entities[svs.next_client_entities%svs.num_client_entities];
		if (ent->s.number != e)
		{
			Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n");
			ent->s.number = e;
		}
		*state = ent->s;

		// don't mark players missiles as solid
		if (ent->owner == client->edict)
			state->solid = 0;

		svs.next_client_entities++;
		frame->num_entities++;
	}
}


/*
=============
SV_EmitPacketEntities

Writes a delta update of an entity_state_t list to the message.
=============
*/
void SV_EmitPacketEntities (client_frame_t *from, client_frame_t *to, sizebuf_t *msg)
{
	entity_state_t	*oldent, *newent;
	int		oldindex, newindex;
	int		oldnum, newnum;
	int		from_num_entities;
	int		bits;

	MSG_WriteByte (msg, svc_packetentities);

	if (!from)
		from_num_entities = 0;
	else
		from_num_entities = from->num_entities;

	newindex = 0;
	oldindex = 0;
	while (newindex < to->num_entities || oldindex < from_num_entities)
	{
		//r1: anti-packet overflow
		//note, worst case delta will generate 47 bytes of output. This should be extremely rare so we use 40.
		if (net_fixoverflow->value)
			if (msg->maxsize - msg->cursize < 40)
				break;

		if (newindex >= to->num_entities)
			newnum = 9999;
		else
		{
			newent = &svs.client_entities[(to->first_entity+newindex)%svs.num_client_entities];
			newnum = newent->number;
		}

		if (oldindex >= from_num_entities)
			oldnum = 9999;
		else
		{
			oldent = &svs.client_entities[(from->first_entity+oldindex)%svs.num_client_entities];
			oldnum = oldent->number;
		}

		if (newnum == oldnum)
		{	// delta update from old position
			// because the force parm is false, this will not result
			// in any bytes being emited if the entity has not changed at all
			// note that players are always 'newentities', this updates their oldorigin always
			// and prevents warping
			MSG_WriteDeltaEntity (oldent, newent, msg, false, newent->number <= maxclients->value);
			oldindex++;
			newindex++;
			continue;
		}

		if (newnum < oldnum)
		{	// this is a new entity, send it from the baseline
			MSG_WriteDeltaEntity (&sv.baselines[newnum], newent, msg, true, true);
			newindex++;
			continue;
		}

		if (newnum > oldnum)
		{	// the old entity isn't present in the new message
			bits = U_REMOVE;
			if (oldnum >= 256)
				bits |= U_NUMBER16 | U_MOREBITS1;

			MSG_WriteByte (msg,	bits&255 );
			if (bits & 0x0000ff00)
				MSG_WriteByte (msg,	(bits>>8)&255 );

			if (bits & U_NUMBER16)
				MSG_WriteShort (msg, oldnum);
			else
				MSG_WriteByte (msg, oldnum);

			oldindex++;
			continue;
		}
	}

	MSG_WriteShort (msg, 0);	// end of packetentities
}


void SV_WriteFrameToClient (client_t *client, sizebuf_t *msg)
{
	client_frame_t		*frame, *oldframe;
	int					lastframe;

	// this is the frame we are creating
	frame = &client->frames[sv.framenum & UPDATE_MASK];

	if (client->lastframe <= 0)
	{	// client is asking for a retransmit
		oldframe = NULL;
		lastframe = -1;
	}
	else if (sv.framenum - client->lastframe >= (UPDATE_BACKUP - 3) )
	{	// client hasn't gotten a good message through in a long time
		oldframe = NULL;
		lastframe = -1;
	}
	else
	{	// we have a valid message to delta from
		oldframe = &client->frames[client->lastframe & UPDATE_MASK];
		lastframe = client->lastframe;
	}

	MSG_WriteByte (msg, svc_frame);
	MSG_WriteLong (msg, sv.framenum);
	MSG_WriteLong (msg, lastframe);	// what we are delta'ing from
	MSG_WriteByte (msg, client->surpressCount);	// rate dropped packets
	client->surpressCount = 0;

	// send over the areabits
	MSG_WriteByte (msg, frame->areabytes);
	SZ_Write (msg, frame->areabits, frame->areabytes);

	// delta encode the playerstate
	SV_WritePlayerstateToClient (oldframe, frame, msg, client);

	// delta encode the entities
	SV_EmitPacketEntities (oldframe, frame, msg);
}


bool SV_SendClientDatagram (client_t *client)
{
	byte		msg_buf[MAX_MSGLEN];
	sizebuf_t	msg;

	SV_BuildClientFrame (client);

	SZ_Init (&msg, msg_buf, sizeof(msg_buf));
	msg.allowoverflow = true;

	// send over all the relevant entity_state_t
	// and the player_state_t
	SV_WriteFrameToClient (client, &msg);

	// copy the accumulated multicast datagram
	// for this client out to the message
	// it is necessary for this to be after the WriteEntities
	// so that entity references will be current
	if (client->datagram.overflowed)
		Com_Printf ("^3WARNING:^7 datagram overflowed for %s\n", client->name);
	else
		SZ_Write (&msg, client->datagram.data, client->datagram.cursize);
	SZ_Clear (&client->datagram);

	if (msg.overflowed)
	{	// must have room left for the packet header
		Com_Printf ("^3WARNING:^7 msg overflowed for %s\n", client->name);
		SZ_Clear (&msg);
	}

	// send the datagram
	Netchan_Transmit (&client->netchan, msg.cursize, msg.data);

	// record the size for rate estimation
	client->message_size[sv.framenum % RATE_MESSAGES] = msg.cursize;

	return true;
}


/*
================
SV_PrepWorldFrame

This has to be done before the world logic, because
player processing happens outside RunWorldFrame
================
*/
void SV_PrepWorldFrame ()
{
	edict_t	*ent;
	int		i;

	for (i=0 ; i<ge->num_edicts ; i++, ent++)
	{
		ent = EDICT_NUM(i);
		// events only last for a single message
		ent->s.event = 0;
	}

}


/*
==================
SV_RecordDemoMessage

Save everything in the world out without deltas.
Used for recording footage for merged or assembled demos
==================
*/
void SV_RecordDemoMessage ()
{
	int			e;
	edict_t		*ent;
	entity_state_t	nostate;
	sizebuf_t	buf;
	byte		buf_data[32768];
	int			len;

	if (!svs.demofile)
		return;

	Memset (&nostate, 0, sizeof(nostate));
	SZ_Init (&buf, buf_data, sizeof(buf_data));

	// write a frame message that doesn't contain a player_state_t
	MSG_WriteByte (&buf, svc_frame);
	MSG_WriteLong (&buf, sv.framenum);

	MSG_WriteByte (&buf, svc_packetentities);

	e = 1;
	ent = EDICT_NUM(e);
	while (e < ge->num_edicts) 
	{
		// ignore ents without visible models unless they have an effect
		if (ent->inuse &&
			ent->s.number && 
			(ent->s.modelindex || ent->s.effects || ent->s.sound || ent->s.event) && 
			!(ent->svflags & SVF_NOCLIENT))
			MSG_WriteDeltaEntity (&nostate, &ent->s, &buf, false, true);

		e++;
		ent = EDICT_NUM(e);
	}

	MSG_WriteShort (&buf, 0);		// end of packetentities

	// now add the accumulated multicast information
	SZ_Write (&buf, svs.demo_multicast.data, svs.demo_multicast.cursize);
	SZ_Clear (&svs.demo_multicast);

	// now write the entire message to the file, prefixed by the length
	len = LittleLong (buf.cursize);
	fwrite (&len, 4, 1, svs.demofile);
	fwrite (buf.data, buf.cursize, 1, svs.demofile);
}


void SV_Frame (int msec)
{
	time_before_game = time_after_game = 0;

	// if server is not active, do nothing
	if (!svs.initialized)
		return;

    svs.realtime += msec;

	// keep the random time dependent
	rand ();

	// check timeouts
	SV_CheckTimeouts ();

	// get packets from clients
	SV_ReadPackets ();

	// move autonomous things around if enough time has passed
	if (!sv_timedemo->value && svs.realtime < sv.time)
	{
		// never let the time get too far off
		if (sv.time - svs.realtime > 100)
		{
			if (sv_showclamp->value)
				Com_Printf ("sv lowclamp\n");
			svs.realtime = sv.time - 100;
		}
		NET_Sleep(sv.time - svs.realtime);
		return;
	}

	// update ping based on the last known frame from all clients
	SV_CalcPings ();

	// give the clients some timeslices
	SV_GiveMsec ();

	// let everything in the world think and move
	SV_RunGameFrame ();

	// send messages back to the clients that had packets read this frame
	SV_SendClientMessages ();

	// save the entire world state if recording a serverdemo
	SV_RecordDemoMessage ();

	// clear teleport flags, etc for next frame
	SV_PrepWorldFrame ();

	if (ge && ((int)sv_autoSavePeriod->value >= 10) && (sv.state == ss_game) && !cl_paused->value)
	{
		if (sv_autoSaveTimer->value <= (float)Sys_Milliseconds()/1000.0)
		{
			Cvar_ForceSet("autoSaveTimer", va("%i", (float)Sys_Milliseconds()/1000.0 + (int)sv_autoSavePeriod->value));		// reset the timer
			Cbuf_InsertText("echo quick autosaving...; wait; save quick\n");
		}
	}
}


void M_AddToServerList (netadr_t adr, char *info)
{
	int		i;

	if (m_num_servers == MAX_LOCAL_SERVERS)
		return;
	while ( *info == ' ' )
		info++;

	// ignore if duplicated
	for (i=0 ; i<m_num_servers ; i++)
		if (!strcmp(info, local_server_names[i]))
			return;

	local_server_netadr[m_num_servers] = adr;
	strncpy (local_server_names[m_num_servers], info, sizeof(local_server_names[0])-1);
	m_num_servers++;
}


/*
=================
CL_ParseStatusMessage

Handle a reply from a ping
=================
*/
void CL_ParseStatusMessage ()
{
	char	*s;

	s = MSG_ReadString(&net_message);

	Com_Printf ("%s\n", s);
	M_AddToServerList (net_from, s);
}


/*
=======================
CL_SendConnectPacket

We have gotten a challenge from the server, so try and
connect.
======================
*/
void CL_SendConnectPacket ()
{
	netadr_t	adr;
	int		port;

	if (!NET_StringToAdr (cls.servername, &adr))
	{
		Com_Printf ("^1Bad server address\n");
		cls.connect_time = 0;
		return;
	}
	if (!adr.port)
	{
		if(net_compatibility->value)
			adr.port = BigShort(OLD_PORT_SERVER);
		else
			adr.port = BigShort(PORT_SERVER);
	}

	port = Cvar_VariableValue ("qport");
	userinfo_modified = false;

	if(net_compatibility->value)
		Netchan_OutOfBandPrint (NS_CLIENT, adr, "connect %i %i %i \"%s\"\n", OLD_PROTOCOL_VERSION, port, cls.challenge, Cvar_Userinfo(true));
	else
		Netchan_OutOfBandPrint (NS_CLIENT, adr, "connect %i %i %i \"%s\"\n", PROTOCOL_VERSION, port, cls.challenge, Cvar_Userinfo(true));
}


void Sys_AppActivate ()
{
	ShowWindow ( cl_hwnd, SW_RESTORE);
	SetForegroundWindow ( cl_hwnd );
}


#define	ANTICHEAT									/// Experimental Anticheat support
#ifdef ANTICHEAT
/* from R1Q2/Q2Pro AntiCheat code */
typedef PVOID (*FNINIT)( VOID );
static PVOID	anticheatApi;
static FNINIT	anticheatInit;
static HMODULE	anticheatHandle;

bool Sys_GetAntiCheatAPI ()
{
	bool			updated = false;

    //already loaded, just reinit
    if( anticheatInit )
	{
        anticheatApi = anticheatInit();
        if( !anticheatApi )
		{
            Com_Printf( "^1Anticheat failed to reinitialize!\n" );
            FreeLibrary( anticheatHandle );
            anticheatHandle = NULL;
            anticheatInit = NULL;
            return false;
        }
        return true;
    }

    //windows version check
    if( s_win95 )
	{
        Com_Printf("^1Anticheat requires Windows 2000/XP/2003/Vista.\n" );
        return false;
    }

reInit:
    anticheatHandle = LoadLibrary( "anticheat" );
    if( !anticheatHandle )
	{
        Com_Printf( "^1Anticheat failed to load.\n" );
        return false;
    }

    //this should never fail unless the anticheat.dll is bad
    anticheatInit = ( FNINIT )GetProcAddress(anticheatHandle, "Initialize" );
    if( !anticheatInit )
	{
        Com_Printf( "^1Couldn't get API of anticheat.dll!\nPlease check you are using a valid anticheat.dll from http://antiche.at/\n" );
        FreeLibrary( anticheatHandle );
        anticheatHandle = NULL;
        return false;
    }

    anticheatApi = anticheatInit();
    if( anticheatApi )
        return true; // succeeded

    FreeLibrary( anticheatHandle );
    anticheatHandle = NULL;
    anticheatInit = NULL;
    if( !updated )
	{
        updated = true;
        goto reInit;
    }

    Com_Printf( "^1Anticheat failed to initialize.\n" );
    return false;
}
#endif


/*
=================
CL_ConnectionlessPacket

Responses to broadcasts, etc
=================
*/
void CL_ConnectionlessPacket ()
{
	char	*s;
	char	*c;
	
	MSG_BeginReading (&net_message);
	MSG_ReadLong (&net_message);	// skip the -1

	s = MSG_ReadStringLine (&net_message);

	Cmd_TokenizeString (s, false);

	c = Cmd_Argv(0);

	Com_Printf ("%s: %s\n", NET_AdrToString (net_from), c);

	// server connection
	if (!strcmp(c, "client_connect"))
	{
		if (cls.state == ca_connected)
		{
			Com_Printf ("^3Dup connect received. Ignored.\n");
			return;
		}
		Netchan_Setup (NS_CLIENT, &cls.netchan, net_from, cls.port);

#ifdef ANTICHEAT
		int		i, ia;
		bool	try_to_use_anticheat;
		char	*p;

		try_to_use_anticheat = false;
		ia = Cmd_Argc();
		for (i = 1; i < ia; i++)
		{
			p = Cmd_Argv(i);
			if (!strncmp (p, "ac=", 3))
			{
				p+= 3;
				if (!p[0])
					continue;
				if (atoi(p))
					try_to_use_anticheat = true;
			}
		}

		if (try_to_use_anticheat)
		{
			MSG_WriteByte (&cls.netchan.message, clc_nop);
			Netchan_Transmit (&cls.netchan, 0, NULL);
			S_StopAllSounds ();
			Com_Printf ("^2Loading anticheat, this may take a few moments...\n");
			SCR_UpdateScreen ();
			if( !Sys_GetAntiCheatAPI() )
				Com_Printf ("^1ERROR: anticheat.dll failed to load\n"
							"Either the file is missing, or something is preventing\n"
							"it from loading or connecting to the anticheat server.\n"
							"This is commonly caused by over-aggressive anti-virus\n"
							"software. Try disabling any anti-virus or other security\n"
							"software or add an exception for anticheat.dll.\n"
							"\n"
							"Trying to connect without anticheat support.\n");
			else
                Com_Printf( "^2Anticheat loaded successfully.\n" );
		}
#endif
		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
		MSG_WriteString (&cls.netchan.message, "new");	
		cls.state = ca_connected;
		return;
	}

	// server responding to a status broadcast
	if (!strcmp(c, "info"))
	{
		CL_ParseStatusMessage ();
		return;
	}

	// remote command from gui front end
	if (!strcmp(c, "cmd"))
	{
		if (!NET_IsLocalAddress(net_from))
		{
			Com_Printf ("^3Command packet from remote host. Ignored.\n");
			return;
		}
		Sys_AppActivate ();
		s = MSG_ReadString (&net_message);
		Cbuf_AddText (s);
		Cbuf_AddText ("\n");
		return;
	}
	// print command from somewhere
	if (!strcmp(c, "print"))
	{
		s = MSG_ReadString (&net_message);
		Com_Printf ("%s", s);
		return;
	}

	// ping from somewhere
	if (!strcmp(c, "ping"))
	{
		Netchan_OutOfBandPrint (NS_CLIENT, net_from, "ack");
		return;
	}

	// challenge from the server we are connecting to
	if (!strcmp(c, "challenge"))
	{
		cls.challenge = atoi(Cmd_Argv(1));
		CL_SendConnectPacket ();
		return;
	}

	// echo request from server
	if (!strcmp(c, "echo"))
	{
		Netchan_OutOfBandPrint (NS_CLIENT, net_from, "%s", Cmd_Argv(1) );
		return;
	}

	Com_Printf ("^3Unknown command.\n");
}


/*
==============
SCR_CenterPrint

Called for important messages that should stay in the center of the screen
for a few moments
==============
*/
void SCR_CenterPrint (char *str)
{
	char	*s;
	char	line[64];
	int		i, j, l;

	strncpy (scr_centerstring, str, sizeof(scr_centerstring)-1);
	scr_centertime_off = scr_centertime->value;
///	scr_centertime_start = cl.time;

	// count the number of lines for centering
	scr_center_lines = 1;
	s = str;
	while (*s)
	{
		if (*s == '\n')
			scr_center_lines++;
		s++;
	}

	// echo it to the console
	Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");

	s = str;
	do	
	{
	// scan the width of the line
		for (l=0 ; l<40 ; l++)
			if (s[l] == '\n' || !s[l])
				break;
		for (i=0 ; i<(40-l)/2 ; i++)
			line[i] = ' ';

		for (j=0 ; j<l ; j++)
		{
			line[i++] = s[j];
		}

		line[i] = '\n';
		line[i+1] = 0;

		Com_Printf ("%s", line);

		while (*s && *s != '\n')
			s++;

		if (!*s)
			break;
		s++;		// skip the \n
	} while (1);
	Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
	Con_ClearNotify ();
}

void CL_ParseServerData ()
{
	char	*str;
	int		i;
	
	Com_DPrintf ("Serverdata packet received.\n");
//
// wipe the client_state_t struct
//
	CL_ClearState ();
	cls.state = ca_connected;

// parse protocol version number
	i = MSG_ReadLong (&net_message);
	cls.serverProtocol = i;

	int	prot;
	if (net_compatibility->value)
		prot = OLD_PROTOCOL_VERSION;
	else
		prot = PROTOCOL_VERSION;

	// BIG HACK to let demos from release work with the 3.0x patch!!!
	if (Com_ServerState() && prot == 34)
	{
	}
	else if (i != prot)
		Com_Error (ERR_DROP,"Server returned version %i, not %i", i, prot);

	cl.servercount = MSG_ReadLong (&net_message);
	cl.attractloop = MSG_ReadByte (&net_message);

	// game directory
	str = MSG_ReadString (&net_message);
	strncpy (cl.gamedir, str, sizeof(cl.gamedir)-1);

	// set gamedir
	if (strlen(str))	// Berserker: fixed bug: if empty(str) -> CRASH!!!
		if ((*str && (!fs_gamedirvar->string || !*fs_gamedirvar->string || strcmp(fs_gamedirvar->string, str))) || (!*str && (fs_gamedirvar->string || *fs_gamedirvar->string)))
			Cvar_Set("game", str);

	// parse player entity number
	cl.playernum = MSG_ReadShort (&net_message);

	// get the full level name
	str = MSG_ReadString (&net_message);

	if (cl.playernum == -1)
	{	// playing a cinematic or showing a pic, not a level
		SCR_PlayCinematic (str);
	}
	else
	{
		// seperate the printfs so the server message can have a color
		Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
		Com_Printf ("%c%s\n", 2, str);

		// need to prep refresh at next oportunity
		cl.refresh_prepped = false;
	}
}


void CL_RunLightStyles ()
{
	int		ofs;
	int		i;
	clightstyle_t	*ls, *cl_ls;

///	ofs = cl.time / 100;
	ofs = cl.leveltime * 10;
	if (ofs == lastofs)
		return;
	lastofs = ofs;

	for (i=0, ls=sv_lightstyle, cl_ls=cl_lightstyle ; i<MAX_LIGHTSTYLES ; i++, ls++, cl_ls++)
	{
		if (!ls->length)
		{
			ls->val = 1.0;
			goto cls;
		}
		if (ls->length == 1)
			ls->val = ls->map[0];
		else
			ls->val = ls->map[ofs%ls->length];

cls:	if (!cl_ls->length)
		{
			cl_ls->val = 1.0;
			continue;
		}
		if (cl_ls->length == 1)
			cl_ls->val = cl_ls->map[0];
		else
			cl_ls->val = cl_ls->map[ofs%cl_ls->length];
	}
}


void CL_SetLightstyle (int i)
{
	char	*s;
	int		j, k;

	if (net_compatibility->value)
		s = cl.configstrings[i+CS_LIGHTS_Q2];
	else
		s = cl.configstrings[i+CS_LIGHTS_BERS];

	j = strlen (s);
	if (j >= MAX_QPATH)
		Com_Error (ERR_DROP, "svc_lightstyle length=%i", j);

	sv_lightstyle[i].length = j;

	for (k=0 ; k<j ; k++)
		sv_lightstyle[i].map[k] = (float)(s[k]-'a')/(float)('m'-'a');
}


/*
void CL_ParseAreaPortalsState ()
{
	int		i;

	numareaportals = MSG_ReadShort (&net_message);

	if (numareaportals > MAX_MAP_AREAPORTALS)
		Com_Error (ERR_DROP, "number of areaportals > MAX_MAP_AREAPORTALS");

//	DPrintfAreaPortalState();

	for (i=0; i<numareaportals; i++)
		portalopen[i] = MSG_ReadByte (&net_message);

	areaportals_from_server = true;
	FloodAreaConnections ();

//	Com_DPrintf ("CL_ParseAreaPortalsState: array \"portalopen[%i]\" received\n", numareaportals);
//	DPrintfAreaPortalState();
}


void CL_ParseAreaPortalChange ()			/// Berserker: выполняется у клиентов (как CM_SetAreaPortalState на сервере)
{
	int portalnum;
	bool open;

	portalnum = MSG_ReadShort (&net_message);
	open = MSG_ReadByte (&net_message);

	if (portalnum > numareaportals)
		Com_Error (ERR_DROP, "areaportal > numareaportals");

//	DPrintfAreaPortalState();

	portalopen[portalnum] = open;
	FloodAreaConnections ();

//	Com_DPrintf ("CL_ParseAreaPortalChange: portalopen[%i] changed to %i\n", portalnum, open);
//	DPrintfAreaPortalState();
}
*/

/*
=================
CL_ParseEntityBits

Returns the entity number and the header bits
=================
*/
///int	bitcounts[32];	// just for protocol profiling
int CL_ParseEntityBits (unsigned *bits)
{
	unsigned	b, total;
///	int			i;
	int			number;

	total = MSG_ReadByte (&net_message);
	if (total & U_MOREBITS1)
	{
		b = MSG_ReadByte (&net_message);
		total |= b<<8;
	}
	if (total & U_MOREBITS2)
	{
		b = MSG_ReadByte (&net_message);
		total |= b<<16;
	}
	if (total & U_MOREBITS3)
	{
		b = MSG_ReadByte (&net_message);
		total |= b<<24;
	}

///	// count the bits for net profiling
///	for (i=0 ; i<32 ; i++)
///		if (total&(1<<i))
///			bitcounts[i]++;

	if (total & U_NUMBER16)
		number = MSG_ReadShort (&net_message);
	else
		number = MSG_ReadByte (&net_message);

	*bits = total;

	return number;
}


/*
=====================
CL_ParseDownload

A download message has been received from the server
=====================
*/
void CL_ParseDownload ()
{
	int		size, percent;
	char	name[MAX_OSPATH];
	int		r;

	// read the data
	size = MSG_ReadShort (&net_message);
	percent = MSG_ReadByte (&net_message);
	if (size == -1)
	{
		Com_Printf ("^1Server does not have this file.\n");
		if (cls.download)
		{
			// if here, we tried to resume a file but the server said no
			fclose (cls.download);
			cls.download = NULL;
		}
		CL_RequestNextDownload ();
		return;
	}

	// open the file if not opened yet
	if (!cls.download)
	{
		CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);

		FS_CreatePath (name);

		cls.download = FS_Fopen(name, "wb");
		if (!cls.download)
		{
			net_message.readcount += size;
			Com_Printf ("^1Failed to open %s\n", cls.downloadtempname);
			CL_RequestNextDownload ();
			return;
		}
	}

	fwrite (net_message.data + net_message.readcount, 1, size, cls.download);
	net_message.readcount += size;

	if (percent != 100)
	{
		// request next block
// change display routines by zoid
#if 0
		Com_Printf (".");
		if (10*(percent/10) != cls.downloadpercent)
		{
			cls.downloadpercent = 10*(percent/10);
			Com_Printf ("%i%%", cls.downloadpercent);
		}
#endif
		cls.downloadpercent = percent;

		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
		SZ_Print (&cls.netchan.message, "nextdl");
	}
	else
	{
		char	oldn[MAX_OSPATH];
		char	newn[MAX_OSPATH];

//		Com_Printf ("100%%\n");

		fclose (cls.download);

		// rename the temp file to it's final name
		CL_DownloadFileName(oldn, sizeof(oldn), cls.downloadtempname);
		CL_DownloadFileName(newn, sizeof(newn), cls.downloadname);
		r = rename (oldn, newn);
		if (r)
			Com_Printf ("^1failed to rename.\n");

		cls.download = NULL;
		cls.downloadpercent = 0;

		// get another file if needed

		CL_RequestNextDownload ();
	}
}


/*
==================
CL_ParseDelta

Can go from either a baseline or a previous packet_entity
==================
*/
void CL_ParseDelta (entity_state_t *from, entity_state_t *to, int number, int bits)
{
	// set everything to the state we are delta'ing from
	*to = *from;

	VectorCopy (from->origin, to->old_origin);
	to->number = number;

	if (!bits)
	{
		to->event = 0;
		return;
	}

	if (bits & U_MODEL16)
	{	// расширенные индексы (by Knightmare, optimized by Berserker)
		if (bits & U_MODEL)
			to->modelindex = MSG_ReadShort (&net_message);
		if (bits & U_MODEL2)
			to->modelindex2 = MSG_ReadShort (&net_message);
		if (bits & U_MODEL3)
			to->modelindex3 = MSG_ReadShort (&net_message);
		if (bits & U_MODEL4)
			to->modelindex4 = MSG_ReadShort (&net_message);
	}
	else
	{
		if (bits & U_MODEL)
			to->modelindex = MSG_ReadByte (&net_message);
		if (bits & U_MODEL2)
			to->modelindex2 = MSG_ReadByte (&net_message);
		if (bits & U_MODEL3)
			to->modelindex3 = MSG_ReadByte (&net_message);
		if (bits & U_MODEL4)
			to->modelindex4 = MSG_ReadByte (&net_message);
	}

	if (bits & U_FRAME8)
		to->frame = MSG_ReadByte (&net_message);
	if (bits & U_FRAME16)
		to->frame = MSG_ReadShort (&net_message);

	if ((bits & U_SKIN8) && (bits & U_SKIN16))		//used for laser colors
		to->skinnum = MSG_ReadLong(&net_message);
	else if (bits & U_SKIN8)
		to->skinnum = MSG_ReadByte(&net_message);
	else if (bits & U_SKIN16)
		to->skinnum = MSG_ReadShort(&net_message);

	if ( (bits & (U_EFFECTS8|U_EFFECTS16)) == (U_EFFECTS8|U_EFFECTS16) )
		to->effects = MSG_ReadLong(&net_message);
	else if (bits & U_EFFECTS8)
		to->effects = MSG_ReadByte(&net_message);
	else if (bits & U_EFFECTS16)
		to->effects = MSG_ReadShort(&net_message);

	if ( (bits & (U_RENDERFX8|U_RENDERFX16)) == (U_RENDERFX8|U_RENDERFX16) )
		to->renderfx = MSG_ReadLong(&net_message);
	else if (bits & U_RENDERFX8)
		to->renderfx = MSG_ReadByte(&net_message);
	else if (bits & U_RENDERFX16)
		to->renderfx = MSG_ReadShort(&net_message);

	// LARGE_MAP_SIZE support
	bool large_map1 = (bits & U_ORIGIN_LARGE);
	bool large_map2 = (bits & U_OLDORIGIN_LARGE);

	if (bits & U_ORIGIN1)
		to->origin[0] = MSG_ReadCoord (&net_message, large_map1);
	if (bits & U_ORIGIN2)
		to->origin[1] = MSG_ReadCoord (&net_message, large_map1);
	if (bits & U_ORIGIN3)
		to->origin[2] = MSG_ReadCoord (&net_message, large_map1);

	if (bits & U_ANGLE1)
		to->angles[0] = MSG_ReadAngle(&net_message);
	if (bits & U_ANGLE2)
		to->angles[1] = MSG_ReadAngle(&net_message);
	if (bits & U_ANGLE3)
		to->angles[2] = MSG_ReadAngle(&net_message);

	if (bits & U_OLDORIGIN)
		MSG_ReadPos (&net_message, to->old_origin, large_map2);

	if (bits & U_SOUND)
	{
		if (bits & U_SOUND16)
			to->sound = MSG_ReadShort (&net_message);	// расширенные индексы (by Knightmare, optimized by Berserker)
		else
			to->sound = MSG_ReadByte (&net_message);
	}

	if (bits & U_EVENT)
		to->event = MSG_ReadByte (&net_message);
	else
		to->event = 0;

	if (bits & U_SOLID)
		to->solid = MSG_ReadShort (&net_message);
}


/*
==================
CL_ParsePacketEntities

An svc_packetentities has just been parsed, deal with the
rest of the data stream.
==================
*/
void CL_ParsePacketEntities (frame_t *oldframe, frame_t *newframe)
{
	int				newnum;
	unsigned		bits;
	entity_state_t	*oldstate;
	int				oldindex, oldnum;

	newframe->parse_entities = cl.parse_entities;
	newframe->num_entities = 0;

	// delta from the entities present in oldframe
	oldindex = 0;
	if (!oldframe)
		oldnum = 99999;
	else
	{
		if (oldindex >= oldframe->num_entities)
			oldnum = 99999;
		else
		{
			oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
			oldnum = oldstate->number;
		}
	}

	while (1)
	{
		newnum = CL_ParseEntityBits (&bits);
		if (newnum >= MAX_EDICTS)
			Com_Error (ERR_DROP,"CL_ParsePacketEntities: bad number:%i", newnum);

		if (net_message.readcount > net_message.cursize)
			Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message");

		if (!newnum)
			break;

		while (oldnum < newnum)
		{	// one or more entities from the old packet are unchanged
			CL_DeltaEntity (newframe, oldnum, oldstate, 0);
			
			oldindex++;

			if (oldindex >= oldframe->num_entities)
				oldnum = 99999;
			else
			{
				oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
				oldnum = oldstate->number;
			}
		}

		if (bits & U_REMOVE)
		{	// the entity present in oldframe is not in the current frame
			if (oldnum != newnum)
				Com_Printf ("^3U_REMOVE: oldnum != newnum\n");

			oldindex++;

			if (oldindex >= oldframe->num_entities)
				oldnum = 99999;
			else
			{
				oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
				oldnum = oldstate->number;
			}
			continue;
		}

		if (oldnum == newnum)
		{	// delta from previous state
			CL_DeltaEntity (newframe, newnum, oldstate, bits);

			oldindex++;

			if (oldindex >= oldframe->num_entities)
				oldnum = 99999;
			else
			{
				oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
				oldnum = oldstate->number;
			}
			continue;
		}

		if (oldnum > newnum)
		{	// delta from baseline
			CL_DeltaEntity (newframe, newnum, &cl_entities[newnum].baseline, bits);
			continue;
		}

	}

	// any remaining entities in the old frame are copied over
	while (oldnum != 99999)
	{	// one or more entities from the old packet are unchanged
		CL_DeltaEntity (newframe, oldnum, oldstate, 0);
		
		oldindex++;

		if (oldindex >= oldframe->num_entities)
			oldnum = 99999;
		else
		{
			oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
			oldnum = oldstate->number;
		}
	}
}


void CL_CheckPredictionError ()
{
	int		frame;
	int		delta[3];
	int		i;
	int		len;

	if (!cl_predict->value || (cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
		return;

	// calculate the last usercmd_t we sent that the server has processed
	frame = cls.netchan.incoming_acknowledged;
	frame &= (CMD_BACKUP-1);

	// compare what the server returned with what we had predicted it to be
	VectorSubtract (cl.frame.playerstate.pmove.origin, cl.predicted_origins[frame], delta);

	// save the prediction error for interpolation
	len = abs(delta[0]) + abs(delta[1]) + abs(delta[2]);
	if (len > 640)	// 80 world units
	{	// a teleport or something
		VectorClear (cl.prediction_error);
	}
	else
	{
		if (cl_showmiss->value && (delta[0] || delta[1] || delta[2]) )
			Com_Printf ("^3prediction miss on %i: %i\n", cl.frame.serverframe, delta[0] + delta[1] + delta[2]);

		VectorCopy (cl.frame.playerstate.pmove.origin, cl.predicted_origins[frame]);

		// save for error itnerpolation
		for (i=0 ; i<3 ; i++)
			cl.prediction_error[i] = delta[i]*0.125;
	}
}


/*
===============
CL_ParticleEffect

Wall impact puffs
===============
*/
void CL_ParticleEffect (vec3_t org, vec3_t dir, byte color_r, byte color_g, byte color_b, int count, parttype_t type, int flag)
{
	int			i, j;
	cparticle_t	*p;
	float		d;

	if(!count)
		return;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = flag;
		p->time = cl.gameTime;
		p->color_r0 = p->color_r1 = color_r;
		p->color_g0 = p->color_g1 = color_g;
		p->color_b0 = p->color_b1 = color_b;

		d = rand()&31;
		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j];
			p->vel[j] = crand()*20;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY*4;
		p->alpha = 1.0;

		p->alphavel = -1.0 / (0.8 + frand() * 0.3);
		p->type = type;
	}
}


explosion_t *CL_AllocExplosion ()
{
	int		i;
	int		time;
	int		index;

	for (i=0 ; i<MAX_EXPLOSIONS ; i++)
	{
		if (cl_explosions[i].type == ex_free)
		{
			Memset (&cl_explosions[i], 0, sizeof (cl_explosions[i]));
			return &cl_explosions[i];
		}
	}
// find the oldest explosion
	time = cl.time;
	index = 0;

	for (i=0 ; i<MAX_EXPLOSIONS ; i++)
		if (cl_explosions[i].start < time)
		{
			time = cl_explosions[i].start;
			index = i;
		}
	Memset (&cl_explosions[index], 0, sizeof (cl_explosions[index]));
	return &cl_explosions[index];
}


/*
///Berserker: not used more...
void CL_SmokeAndFlash(vec3_t origin)
{
	explosion_t	*ex;

	ex = CL_AllocExplosion ();
	VectorCopy (origin, ex->ent.origin);
	ex->type = ex_misc;
	ex->frames = 4;
	ex->ent.flags = RF_TRANSLUCENT;
	ex->start = cl.frame.servertime - 100;
	ex->ent.model = cl_mod_smoke;

	ex = CL_AllocExplosion ();
	VectorCopy (origin, ex->ent.origin);
	ex->type = ex_flash;
	ex->ent.flags = RF_FULLBRIGHT;
	ex->frames = 2;
	ex->start = cl.frame.servertime - 100;
	ex->ent.model = cl_mod_flash;
}
*/

material_t	*R_FindMaterial(char *name)
{
	int		i;
	image_t	*image;
	char	fname[MAX_QPATH];
	char	oname1[MAX_QPATH];
	char	oname2[MAX_QPATH];
	char	*truename = NULL;

	truename = FS_GetFileName(name);
	Com_sprintf (fname, sizeof(fname), "textures/%s", name);
	Com_sprintf (oname1, sizeof(oname1), "overrides/%s/%s", mapname, truename);
	Com_sprintf (oname2, sizeof(oname2), "overrides/%s", truename);
	unsigned hash0 = Com_HashKey(fname);
	unsigned hash1 = Com_HashKey(oname1);
	unsigned hash2 = Com_HashKey(oname2);
	for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (image->hash == hash1)
			if (!b_stricmp(image->name, oname1))
				return image->material;
		if (image->hash == hash2)
			if (!b_stricmp(image->name, oname2))
				return image->material;
		if (image->hash == hash0)
			if (!b_stricmp(image->name, fname))
				return image->material;
	}
	return NULL;
}


void CL_TeleporterParticles (entity_state_t *ent)
{
	int			i, j;
	cparticle_t	*p;

	for (i=0 ; i<8 ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = 0;
		p->time = cl.gameTime;
		p->color_r0 = p->color_g0 = p->color_b0 = 200;
		p->color_r1 = p->color_g1 = p->color_b1 = 0;

		for (j=0 ; j<2 ; j++)
		{
			p->lastOrg[j] = p->org[j] = ent->origin[j] - 16 + (rand()&31);
			p->vel[j] = crand()*14;
		}

		p->lastOrg[2] = p->org[2] = ent->origin[2] - 8 + (rand()&7);
		p->vel[2] = 80 + (rand()&7);

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;
		p->alpha = 1.0;

		p->alphavel = -0.5;
		p->type = part_simple;
	}
}


void CL_FireEntityEvents (frame_t *frame)
{
	entity_state_t		*s1;
	int					pnum, num;

	for (pnum = 0 ; pnum<frame->num_entities ; pnum++)
	{
		num = (frame->parse_entities + pnum)&(MAX_PARSE_ENTITIES-1);
		s1 = &cl_parse_entities[num];
		if (s1->event)
			CL_EntityEvent (s1);

		// EF_TELEPORTER acts like an event, but is not cleared each frame
		if (s1->effects & EF_TELEPORTER)
			CL_TeleporterParticles (s1);
	}
}


void CL_ParseRenderFX ()
{
	byte rfx = MSG_ReadByte (&net_message);
	if (rfx==RFX_DISABLE_ALL)
	{
////	r_using_underwater = r_using_breather = r_using_enviro = false;
		r_using_pain /*= r_using_burning = r_using_drown */= 0;
	}
////	else if (rfx==RFX_BREATHER)
////		r_using_breather = true;
////	else if (rfx==RFX_BREATHER+0x80)
////		r_using_breather = false;
////	else if (rfx==RFX_ENVIRO)
////		r_using_enviro = true;
////	else if (rfx==RFX_ENVIRO+0x80)
////		r_using_enviro = false;
////	else if (rfx==RFX_UNDERWATER)
////		r_using_underwater = true;
////	else if (rfx==RFX_UNDERWATER+0x80)
////		r_using_underwater = false;
////	else if (rfx==RFX_BURN_1_SECOND)
////		r_using_burning = cl.gameTime + 1000;
	else if (rfx==RFX_PAIN_07_SECOND)
	{
		r_using_pain = cl.gameTime + 700;
		r_using_pain_frame = cl.gameTime%num_pains;
	}
	else if (rfx==RFX_PAIN_2_SECOND)
	{
		r_using_pain = cl.gameTime + 2000;
		r_using_pain_frame = cl.gameTime%num_pains;
	}
////	else if (rfx==RFX_DROWN_1_SECOND)
////		r_using_drown = cl.gameTime + 1000;
	else
		Com_Printf("^3CL_ParseRenderFX: wrong render FX code: %i\n", rfx);
}


void CL_ParseInventory ()
{
	int		i;

	for (i=0 ; i<MAX_ITEMS ; i++)
		cl.inventory[i] = MSG_ReadShort (&net_message);
}

/*
=================
S_RegisterSexedSound
=================
*/
ALuint S_RegisterSexedSound(entity_state_t * ent, const char *base)
{
	int n, cs_playerskins;
	char *p;
	char model[MAX_QPATH];
	char sexedFilename[MAX_QPATH];

	if (net_compatibility->value)
		cs_playerskins = CS_PLAYERSKINS_Q2;
	else
		cs_playerskins = CS_PLAYERSKINS_BERS;

	// determine what model the client is using
	model[0] = 0;
	n = cs_playerskins + ent->number - 1;
	if (cl.configstrings[n][0]) {
		p = strchr(cl.configstrings[n], '\\');
		if (p) {
			p += 1;
			strcpy(model, p);
			p = strchr(model, '/');
			if (p)
				*p = 0;
		}
	}

	// if we can't figure it out, they're male
	if (!model[0]) {
		if (!strcmp(cl.clientinfo->sex, "cyborg"))
			strcpy(model, "cyborg");
		else if (!strcmp(cl.clientinfo->sex, "female"))
			strcpy(model, "female");
		else if (!strcmp(cl.clientinfo->sex, "male"))
			strcpy(model, "male");
		else
			strcpy(model, gender->string);

	}

	// see if we already know of the model specific sound
	Com_sprintf(sexedFilename, sizeof(sexedFilename), "#players/%s/%s", model, base + 1);

	return S_FindName(sexedFilename, true);

}

void CL_ParseStartSoundPacket()
{
    vec3_t  pos_v;
	float	*pos;
    int 	channel, ent;
    int 	sound_num;
    float 	volume;
    float 	attenuation;  
	int		flags;
	float	ofs;
	int id;

	flags = MSG_ReadByte (&net_message);

	if (flags & SND_INDEX16)
		sound_num = MSG_ReadShort (&net_message);	// расширенные индексы (by Knightmare, optimized by Berserker)
	else
		sound_num = MSG_ReadByte (&net_message);

    if (flags & SND_VOLUME)
		volume = MSG_ReadByte (&net_message) / 255.0;
	else
		volume = DEFAULT_SOUND_PACKET_VOLUME;
	
    if (flags & SND_ATTENUATION)
		attenuation = MSG_ReadByte (&net_message) / 64.0;
	else
		attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;	

    if (flags & SND_OFFSET)
		ofs = MSG_ReadByte (&net_message) / 1000.0;
	else
		ofs = 0;

	if (flags & SND_ENT)
	{	// entity reletive
		channel = MSG_ReadShort(&net_message); 
		ent = channel>>3;
		if (ent > MAX_EDICTS)
			Com_Error (ERR_DROP,"CL_ParseStartSoundPacket: ent = %i", ent);

		channel &= 7;
	}
	else
	{
		ent = 0;
		channel = 0;
	}

	if (flags & SND_POS)
	{	// positioned in space
		if (flags & SND_LARGE_POS)		// LARGE_MAP_SIZE
			MSG_ReadPos (&net_message, pos_v, true);
		else
			MSG_ReadPos (&net_message, pos_v, false);
 		pos = pos_v;
	}
	else	// use entity number
		pos = NULL;

	if (cl.sound_sexedname[sound_num])
		id = S_RegisterSexedSound(&cl_entities[ent].current, cl.sound_sexedname[sound_num]);
	else
		id = cl.sound_precache[sound_num];

	// willow: F**K THE HACK'S!!! trying to cheat ugly id's map design and stuff
	if (!attenuation && (cl.sound_precache_hacks[sound_num] & sound_precache_hacks_FLAT2D))
	{
		S_StartLocalSound(id);
		return;
	}

	if (!id)
		return;

	if (cl.sound_precache_hacks[sound_num] & sound_precache_hacks_AUTOFIX_gain)
		volume = cl.sound_precache_gain[sound_num];

	if (cl.sound_precache_hacks[sound_num] & sound_precache_hacks_AUTOFIX_rolloff_factor)
		attenuation = cl.sound_precache_rolloff_factor[sound_num];

	// willow: simple as it should be ^_^
	S_StartSound(pos, ent, channel, id, volume, attenuation, ofs);
}       


void CL_ParseBaseline ()
{
	centity_t		*cent;
	entity_state_t	*es;
	unsigned		bits;
	int				newnum;
	entity_state_t	nullstate;

	Memset (&nullstate, 0, sizeof(nullstate));

	newnum = CL_ParseEntityBits (&bits);
	cent = &cl_entities[newnum];
	es = &cent->baseline;
	CL_ParseDelta (&nullstate, es, newnum, bits);
}


void CL_ParticleEffect2 (vec3_t org, vec3_t dir, byte color_r, byte color_g, byte color_b, int count, parttype_t type)
{
	int			i, j;
	cparticle_t	*p;
	float		d;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = 0;

		p->time = cl.gameTime;
		p->color_r0 = p->color_r1 = color_r;
		p->color_g0 = p->color_g1 = color_g;
		p->color_b0 = p->color_b1 = color_b;

		d = rand()&7;
		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j];
			p->vel[j] = crand()*20;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY*2;
		p->alpha = 1.0;

		p->alphavel = -1.0 / (0.5 + frand()*0.3);
		p->type = type;
	}
}


/*
===============
CL_BlasterParticles

Wall impact puffs
===============
*/
void CL_BlasterParticles (vec3_t org, vec3_t dir, byte color_r, byte color_g, byte color_b, int count)
{
	int			i, j;
	cparticle_t	*p;
	float		d;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = PARTICLE_BOUNCE | PARTICLE_FRICTION;
		p->time = cl.gameTime;
		p->color_r0 = color_r + (rand()&15);
		p->color_g0 = color_g + (rand()&15);
		p->color_b0 = color_b + (rand()&15);
		p->color_r1 = p->color_g1 = p->color_b1 = 255;

		d = rand()&15;
		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j];
			p->vel[j] = dir[j] * 30 + crand()*40;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY*2;
		p->alpha = 1.0;

		p->alphavel = -0.5 / (1 + frand()*0.5);
		p->type = part_simple;
	}
}


void SetColor(byte idx, byte *r, byte *g, byte *b, byte *r_, byte *g_, byte *b_)
{
	switch(idx)
	{
	case 1:		/// синий
		*r = 32;
		*g = 32;
		*b = 220;
		*r_ = 63;
		*g_ = 63;
		*b_ = 15;
		break;
	case 2:		/// зеленый
		*r = 32;
		*g = 220;
		*b = 32;
		*r_ = 63;
		*g_ = 15;
		*b_ = 63;
		break;
	case 3:		/// сине-зеленый
		*r = 32;
		*g = 220;
		*b = 220;
		*r_ = 63;
		*g_ = 15;
		*b_ = 15;
		break;
	case 4:		/// красный
		*r = 220;
		*g = 32;
		*b = 32;
		*r_ = 15;
		*g_ = 63;
		*b_ = 63;
		break;
	case 5:		/// пурпурный
		*r = 220;
		*g = 32;
		*b = 220;
		*r_ = 15;
		*g_ = 63;
		*b_ = 15;
		break;
	case 6:		/// желтый
		*r = 220;
		*g = 220;
		*b = 32;
		*r_ = 15;
		*g_ = 15;
		*b_ = 63;
		break;
	case 7:		/// белый
		*r = 220;
		*g = 220;
		*b = 220;
		*r_ = 15;
		*g_ = 15;
		*b_ = 15;
		break;
	default:	/// разноцветный
		*r = 10;
		*g = 10;
		*b = 10;
		*r_ = 255;
		*g_ = 255;
		*b_ = 255;
		break;
	}
}

/// modified by Berserker
void CL_RailTrail (vec3_t start, vec3_t end, byte core, byte spiral, byte radius)
{
	vec3_t		vec;
	int			j;
	cparticle_t	*p;
	byte		core_r;
	byte		core_g;
	byte		core_b;
	byte		spiral_r;
	byte		spiral_g;
	byte		spiral_b;
	byte		core_r_;
	byte		core_g_;
	byte		core_b_;
	byte		spiral_r_;
	byte		spiral_g_;
	byte		spiral_b_;

	VectorSubtract (end, start, vec);

	if(radius)
	{
		SetColor(spiral, &spiral_r, &spiral_g, &spiral_b, &spiral_r_, &spiral_g_, &spiral_b_);

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		
		p->flags = 0;
		p->time = cl.gameTime;
		VectorClear (p->accel);

		p->alpha = 1.0;
		p->alphavel = -1.0 / (1+frand()*0.2);
		p->color_r0 = p->color_r1 = spiral_r+(rand()&spiral_r_);
		p->color_g0 = p->color_g1 = spiral_g+(rand()&spiral_g_);
		p->color_b0 = p->color_b1 = spiral_b+(rand()&spiral_b_);

		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = start[j];
			p->length[j] = vec[j];
			p->vel[j] = 0;
			p->accel[j] = 0;
		}
		p->type = part_spiral;
		p->size = radius;
	}

	if (core)
	{
		SetColor(core, &core_r, &core_g, &core_b, &core_r_, &core_g_, &core_b_);

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = 0;
		p->time = cl.gameTime;
		VectorClear (p->accel);

		p->alpha = 1.0;
		p->alphavel = -1.0 / (0.8+frand()*0.2);
		p->color_r0 = p->color_r1 = core_r+(rand()&core_r_);
		p->color_g0 = p->color_g1 = core_g+(rand()&core_g_);
		p->color_b0 = p->color_b1 = core_b+(rand()&core_b_);

		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = start[j];
			p->length[j] = vec[j];
			p->vel[j] = 0;
			p->accel[j] = 0;
		}
		p->type = part_rail_beam;
		p->size = 1;
	}
}


void CL_ExplosionParticles (vec3_t org)
{
	int			i, j;
	cparticle_t	*p;

	for (i=0 ; i<256 ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = PARTICLE_BOUNCE | PARTICLE_FRICTION;

		p->time = cl.gameTime;
		p->color_r0 = 220 + (rand()&15);
		p->color_g0 = 100 + (rand()&31);
		p->color_b0 = p->color_r1 = p->color_g1 = p->color_b1 = 0;

		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = org[j] + ((rand()%32)-16);
			p->vel[j] = (rand()%384)-192;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -2*PARTICLE_GRAVITY;
		p->alpha = 1.0;

		p->alphavel = -0.5 / (0.5 + frand()*0.3);
		p->type = part_simple;
	}
}


//FIXME combined with CL_ExplosionParticles
void CL_BFGExplosionParticles (vec3_t org, int cnt)
{
	int			i, j;
	cparticle_t	*p;

	for (i=0 ; i<cnt ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = PARTICLE_BOUNCE | PARTICLE_FRICTION;
		p->time = cl.gameTime;
		p->color_r0 = p->color_r1 = 0;
		p->color_g0 = p->color_g1 = 100 + (rand()&127);
		p->color_b0 = p->color_b1 = 0;

		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = org[j] + ((rand()%32)-16);
			p->vel[j] = (rand()%384)-192;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -2*PARTICLE_GRAVITY;
		p->alpha = 1.0;

		p->alphavel = -0.3 / (0.3 + frand()*0.3);
		p->type = part_simple;
	}
}



void CL_ParseLaser (int colors, bool large_map)
{
	vec3_t	start;
	vec3_t	end;
	laser_t	*l;
	int		i;

	MSG_ReadPos (&net_message, start, large_map);
	MSG_ReadPos (&net_message, end, large_map);

	for (i=0, l=cl_lasers ; i< MAX_LASERS ; i++, l++)
	{
		if (l->endtime < cl.gameTime)
		{
			l->ent.flags = RF_TRANSLUCENT | RF_BEAM;
			VectorCopy (start, l->ent.origin);
			VectorCopy (end, l->ent.oldorigin);
			l->ent.alpha = 0.30;
			l->ent.skinnum = (colors >> ((rand() % 4)*8)) & 0xff;
			l->ent.model = NULL;
			l->ent.frame = 4;
			l->endtime = cl.gameTime + 100;
			return;
		}
	}
}


void CL_AddBubbles (float num)
{
	int i, j;
	cparticle_t	*p;

	if (!num)
		return;

	if (bubble_think >= cl.gameTime)
		return;

	/// Think time равен 0.25 сек.
	bubble_think = cl.gameTime + 250;

	for (i=0; i<num; i++)
	{
		if (!free_particles)
			return;

		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		VectorClear (p->accel);
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = -1.0 / (1+frand()*0.2);

		p->color_r0 = p->color_r1 = 170 + (rand()&31);
		p->color_g0 = p->color_g1 = 170 + (rand()&31);
		p->color_b0 = p->color_b1 = 200 + (rand()&31);

		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = r_origin[j] + vpn[j]*4 - vup[j]*5 + crand()*2;
			p->vel[j] = crand()*4;
		}
		p->vel[2] += 8;
		p->type = part_bubble;
	}
}

void CL_BubbleTrail (vec3_t start, vec3_t end, int type)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			i, j;
	cparticle_t	*p;
	float		dec;
	byte		r, g, b;

	if(type==TE_BUBBLETRAIL_WATER)
	{
		r = 170;
		g = 170;
		b = 200;
	}
	else if(type==TE_BUBBLETRAIL_LAVA)
	{
		r = 200;
		g = 120;
		b = 120;
	}
	else
	{
		r = 120;
		g = 200;
		b = 120;
	}

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	dec = 32;
	VectorScale (vec, dec, vec);

	for (i=0 ; i<len ; i+=dec)
	{
		if (!free_particles)
			return;

		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		VectorClear (p->accel);
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = -1.0 / (1+frand()*0.2);

		p->color_r0 = p->color_r1 = r + (rand()&31);
		p->color_g0 = p->color_g1 = g + (rand()&31);
		p->color_b0 = p->color_b1 = b + (rand()&31);

		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = move[j] + crand()*2;
			p->vel[j] = crand()*5;
		}
		p->vel[2] += 6;
		p->type = part_bubble;

		VectorAdd (move, vec, move);
	}
}


void CL_ParseBeam (struct model_s *model, bool large_map)
{
	int		ent;
	vec3_t	start, end;
	beam_t	*b;
	int		i;
	
	ent = MSG_ReadShort (&net_message);
	
	MSG_ReadPos (&net_message, start, large_map);
	MSG_ReadPos (&net_message, end, large_map);

// override any beam with the same entity
	for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++)
		if (b->entity == ent)
		{
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy (start, b->start);
			VectorCopy (end, b->end);
			VectorClear (b->offset);
			return;/// ent;
		}

// find a free beam
	for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++)
	{
		if (!b->model || b->endtime < cl.time)
		{
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy (start, b->start);
			VectorCopy (end, b->end);
			VectorClear (b->offset);
			return;/// ent;
		}
	}
	Com_Printf ("^3beam list overflow!\n");	
	return;/// ent;
}


/*
=================
CL_ParseBeam2
=================
*/
void CL_ParseBeam2 (struct model_s *model, bool large_map)
{
	int		ent;
	vec3_t	start, end, offset;
	beam_t	*b;
	int		i;
	
	ent = MSG_ReadShort (&net_message);
	
	MSG_ReadPos (&net_message, start, large_map);
	MSG_ReadPos (&net_message, end, large_map);
	MSG_ReadPos (&net_message, offset, large_map);

//	Com_Printf ("end- %f %f %f\n", end[0], end[1], end[2]);

// override any beam with the same entity

	for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++)
		if (b->entity == ent)
		{
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy (start, b->start);
			VectorCopy (end, b->end);
			VectorCopy (offset, b->offset);
			return;/// ent;
		}

// find a free beam
	for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++)
	{
		if (!b->model || b->endtime < cl.time)
		{
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 200;	
			VectorCopy (start, b->start);
			VectorCopy (end, b->end);
			VectorCopy (offset, b->offset);
			return;/// ent;
		}
	}
	Com_Printf ("^3beam list overflow!\n");	
	return;/// ent;
}


void CL_BigTeleportParticles (vec3_t org)
{
	int			i;
	cparticle_t	*p;
	float		angle, dist, s, c;

	for (i=0 ; i<4096 ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = 0;
		p->time = cl.gameTime;

		p->color_r0 = p->color_g0 = p->color_b0 = p->color_r1 = p->color_g1 = p->color_b1 = colortable[rand()&3];

		angle = M_PI*2*(rand()&1023)/1023.0;
		dist = rand()&31;
		SinCos(angle, &s, &c);

		p->lastOrg[0] = p->org[0] = org[0] + c*dist;
		p->vel[0] = c*(70+(rand()&63));
		p->accel[0] = -c*100;

		p->lastOrg[1] = p->org[1] = org[1] + s*dist;
		p->vel[1] = s*(70+(rand()&63));
		p->accel[1] = -s*100;

		p->lastOrg[2] = p->org[2] = org[2] + 8 + (rand()%90);
		p->vel[2] = -100 + (rand()&31);
		p->accel[2] = PARTICLE_GRAVITY*4;
		p->alpha = 1.0;

		p->alphavel = -0.3 / (0.5 + frand()*0.3);
		p->type = part_simple;
	}
}


void CL_ParticleEffect3 (vec3_t org, vec3_t dir, byte color_r, byte color_g, byte color_b, int count, parttype_t type)
{
	int			i, j;
	cparticle_t	*p;
	float		d;

	if(!count)
		return;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = 0;
		p->time = cl.gameTime;
		p->color_r0 = p->color_r1 = color_r;
		p->color_g0 = p->color_g1 = color_g;
		p->color_b0 = p->color_b1 = color_b;

		d = rand()&7;
		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j];
			p->vel[j] = crand()*20;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = PARTICLE_GRAVITY;
		p->alpha = 1.0;

		p->alphavel = -1.0 / (0.6 + frand()*0.3);
		p->type = type;
	}
}


/*
=================
CL_ParseLightning
=================
*/
int CL_ParseLightning (struct model_s *model, bool large_map)
{
	int		srcEnt, destEnt;
	vec3_t	start, end;
	beam_t	*b;
	int		i;
	
	srcEnt = MSG_ReadShort (&net_message);
	destEnt = MSG_ReadShort (&net_message);

	MSG_ReadPos (&net_message, start, large_map);
	MSG_ReadPos (&net_message, end, large_map);

// override any beam with the same source AND destination entities
	for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++)
		if (b->entity == srcEnt && b->dest_entity == destEnt)
		{
//			Com_Printf("%d: OVERRIDE  %d -> %d\n", cl.time, srcEnt, destEnt);
			b->entity = srcEnt;
			b->dest_entity = destEnt;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy (start, b->start);
			VectorCopy (end, b->end);
			VectorClear (b->offset);
			return srcEnt;
		}

// find a free beam
	for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++)
	{
		if (!b->model || b->endtime < cl.time)
		{
//			Com_Printf("%d: NORMAL  %d -> %d\n", cl.time, srcEnt, destEnt);
			b->entity = srcEnt;
			b->dest_entity = destEnt;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy (start, b->start);
			VectorCopy (end, b->end);
			VectorClear (b->offset);
			return srcEnt;
		}
	}
	Com_Printf ("^3beam list overflow!\n");	
	return srcEnt;
}


/*
======
CL_DebugTrail
======
*/
void CL_DebugTrail (vec3_t start, vec3_t end)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;
	float		dec;
	vec3_t		right, up;
//	int			i;
//	float		d, c, s;
//	vec3_t		dir;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	MakeNormalVectors (vec, right, up);

//	VectorScale(vec, RT2_SKIP, vec);

//	dec = 1.0;
//	dec = 0.75;
	dec = 3;
	VectorScale (vec, dec, vec);
	VectorCopy (start, move);

	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = 0;
		p->time = cl.gameTime;
		VectorClear (p->accel);
		VectorClear (p->vel);
		p->alpha = 1.0;
		p->alphavel = -0.1;
//		p->alphavel = 0;

		int colr = 0x74 + (rand()&7);
		p->color_r0 = p->color_r1 = d_8to24table[colr*3+0];
		p->color_g0 = p->color_g1 = d_8to24table[colr*3+1];
		p->color_b0 = p->color_b1 = d_8to24table[colr*3+2];

		for (j=0 ; j<3 ; j++)
			p->lastOrg[j] = p->org[j] = move[j];
/*
		for (j=0 ; j<3 ; j++)
		{
			p->org[j] = move[j] + crand()*2;
			p->vel[j] = crand()*3;
			p->accel[j] = 0;
		}
*/
		VectorAdd (move, vec, move);
		p->type = part_simple;
	}

}


void CL_ForceWall (vec3_t start, vec3_t end, int color)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	VectorScale (vec, 4, vec);

	// FIXME: this is a really silly way to have a loop
	while (len > 0)
	{
		len -= 4;

		if (!free_particles)
			return;

		if (frand() > 0.3)
		{
			p = free_particles;
			free_particles = p->next;
			p->next = active_particles;
			active_particles = p;
			VectorClear (p->accel);

			p->flags = 0;
			p->time = cl.gameTime;

			p->alpha = 1.0;
			p->alphavel =  -1.0 / (3.0+frand()*0.5);

			int colr = color + rand()&7;
			p->color_r0 = p->color_r1 = d_8to24table[colr*3+0];
			p->color_g0 = p->color_g1 = d_8to24table[colr*3+1];
			p->color_b0 = p->color_b1 = d_8to24table[colr*3+2];

			for (j=0 ; j<3 ; j++)
			{
				p->lastOrg[j] = p->org[j] = move[j] + crand()*3;
				p->accel[j] = 0;
			}
			p->vel[0] = 0;
			p->vel[1] = 0;
			p->vel[2] = -40 - (crand()*10);
			p->type = part_simple;
		}

		VectorAdd (move, vec, move);
	}
}


/// FIXME: (BERSERKER) - может просто мой фонарик сделать?
void CL_Flashlight (int ent, vec3_t pos)
{
	cdlight_t	*dl;

	dl = CL_AllocDlight (ent);
	VectorCopy (pos,  dl->origin);
	dl->radius = 400;
///	dl->minlight = 250;
	dl->die = cl.gameTime + 100;
	dl->color[0] = 1;
	dl->color[1] = 1;
	dl->color[2] = 1;
}


// ROGUE
/*
=================
CL_ParsePlayerBeam
  - adds to the cl_playerbeam array instead of the cl_beams array
=================
*/
void CL_ParsePlayerBeam (model_s *model, bool large_map)
{
	int		ent;
	vec3_t	start, end, offset;
	beam_t	*b;
	int		i;
	
	ent = MSG_ReadShort (&net_message);
	
	MSG_ReadPos (&net_message, start, large_map);
	MSG_ReadPos (&net_message, end, large_map);
	// PMM - network optimization
	if (model == cl_mod_heatbeam)
		VectorSet(offset, 2, 7, -3);
	else if (model == cl_mod_monster_heatbeam)
	{
		model = cl_mod_heatbeam;
		VectorSet(offset, 0, 0, 0);
	}
	else
		MSG_ReadPos (&net_message, offset, large_map);

//	Com_Printf ("end- %f %f %f\n", end[0], end[1], end[2]);

// override any beam with the same entity
// PMM - For player beams, we only want one per player (entity) so..
	for (i=0, b=cl_playerbeams ; i< MAX_BEAMS ; i++, b++)
	{
		if (b->entity == ent)
		{
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy (start, b->start);
			VectorCopy (end, b->end);
			VectorCopy (offset, b->offset);
			return;/// ent;
		}
	}

// find a free beam
	for (i=0, b=cl_playerbeams ; i< MAX_BEAMS ; i++, b++)
	{
		if (!b->model || b->endtime < cl.time)
		{
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 100;		// PMM - this needs to be 100 to prevent multiple heatbeams
			VectorCopy (start, b->start);
			VectorCopy (end, b->end);
			VectorCopy (offset, b->offset);
			return;/// ent;
		}
	}
	Com_Printf ("^3beam list overflow!\n");	
	return;/// ent;
}
//rogue


void CL_ParticleSteamEffect2 (cl_sustain_t *self)
//vec3_t org, vec3_t dir, int color, int count, int magnitude)
{
	int			i, j;
	cparticle_t	*p;
	float		d;
	vec3_t		r, u;
	vec3_t		dir;

//	vectoangles2 (dir, angle_dir);
//	AngleVectors (angle_dir, f, r, u);

	VectorCopy (self->dir, dir);
	MakeNormalVectors (dir, r, u);

	for (i=0 ; i<self->count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->type = part_simple;
		p->flags = 0;
		p->time = cl.gameTime;

		int colr = self->color + (rand()&7);
		p->color_r0 = p->color_r1 = d_8to24table[colr*3+0];
		p->color_g0 = p->color_g1 = d_8to24table[colr*3+1];
		p->color_b0 = p->color_b1 = d_8to24table[colr*3+2];

		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = self->org[j] + self->magnitude*0.1*crand();
//			p->vel[j] = dir[j]*magnitude;
		}
		VectorScale (dir, self->magnitude, p->vel);
		d = crand()*self->magnitude/3;
		VectorMA (p->vel, d, r, p->vel);
		d = crand()*self->magnitude/3;
		VectorMA (p->vel, d, u, p->vel);

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY/2;
		p->alpha = 1.0;

		p->alphavel = -1.0 / (0.5 + frand()*0.3);
	}
	self->nextthink += self->thinkinterval;
}


/*
===============
CL_ParticleSteamEffect

Puffs with velocity along direction, with some randomness thrown in
===============
*/
void CL_ParticleSteamEffect (vec3_t org, vec3_t dir, int color, int count, int magnitude)
{
	int			i, j;
	cparticle_t	*p;
	float		d;
	vec3_t		r, u;

//	vectoangles2 (dir, angle_dir);
//	AngleVectors (angle_dir, f, r, u);

	MakeNormalVectors (dir, r, u);

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->type = part_simple;
		p->flags = 0;
		p->time = cl.gameTime;

		int colr = color + (rand()&7);
		p->color_r0 = p->color_r1 = d_8to24table[colr*3+0];
		p->color_g0 = p->color_g1 = d_8to24table[colr*3+1];
		p->color_b0 = p->color_b1 = d_8to24table[colr*3+2];

		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = org[j] + magnitude*0.1*crand();
//			p->vel[j] = dir[j]*magnitude;
		}
		VectorScale (dir, magnitude, p->vel);
		d = crand()*magnitude/3;
		VectorMA (p->vel, d, r, p->vel);
		d = crand()*magnitude/3;
		VectorMA (p->vel, d, u, p->vel);

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY/2;
		p->alpha = 1.0;

		p->alphavel = -1.0 / (0.5 + frand()*0.3);
	}
}


//=============
//ROGUE
void CL_ParseSteam (bool large_map)
{
	vec3_t	pos, dir;
	int		id, i;
	int		r;
	int		cnt;
	int		color;
	int		magnitude;
	cl_sustain_t	*s, *free_sustain;

	id = MSG_ReadShort (&net_message);		// an id of -1 is an instant effect
	if (id != -1) // sustains
	{
//			Com_Printf ("Sustain effect id %d\n", id);
		free_sustain = NULL;
		for (i=0, s=cl_sustains; i<MAX_SUSTAINS; i++, s++)
		{
			if (s->id == 0)
			{
				free_sustain = s;
				break;
			}
		}
		if (free_sustain)
		{
			s->id = id;
			s->count = MSG_ReadByte (&net_message);
			MSG_ReadPos (&net_message, s->org, large_map);
			MSG_ReadDir (&net_message, s->dir);
			r = MSG_ReadByte (&net_message);
			s->color = r & 0xff;
			s->magnitude = MSG_ReadShort (&net_message);
			s->endtime = cl.gameTime + MSG_ReadLong (&net_message);
			s->think = CL_ParticleSteamEffect2;
			s->thinkinterval = 100;
			s->nextthink = cl.gameTime;
		}
		else
		{
//				Com_Printf ("No free sustains!\n");
			// FIXME - read the stuff anyway
			cnt = MSG_ReadByte (&net_message);
			MSG_ReadPos (&net_message, pos, large_map);
			MSG_ReadDir (&net_message, dir);
			r = MSG_ReadByte (&net_message);
			magnitude = MSG_ReadShort (&net_message);
			magnitude = MSG_ReadLong (&net_message); // really interval
		}
	}
	else // instant
	{
		cnt = MSG_ReadByte (&net_message);
		MSG_ReadPos (&net_message, pos, large_map);
		MSG_ReadDir (&net_message, dir);
		r = MSG_ReadByte (&net_message);
		magnitude = MSG_ReadShort (&net_message);
		color = r & 0xff;
		CL_ParticleSteamEffect (pos, dir, color, cnt, magnitude);
//		S_StartSound (pos,  0, 0, cl_sfx_lashit, 1, _ATTN_NORM, 0);
	}
}


/*
===============
CL_BubbleTrail2 (lets you control the # of bubbles by setting the distance between the spawns)
===============
*/
void CL_BubbleTrail2 (vec3_t start, vec3_t end, int dist)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			i, j;
	cparticle_t	*p;
	float		dec;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	dec = dist;
	VectorScale (vec, dec, vec);

	for (i=0 ; i<len ; i+=dec)
	{
		if (!free_particles)
			return;

		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->type = part_bubble;
		VectorClear (p->accel);
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = -1.0 / (1+frand()*0.1);

		int colr = 4 + (rand()&7);
		p->color_r0 = p->color_r1 = d_8to24table[colr*3+0];
		p->color_g0 = p->color_g1 = d_8to24table[colr*3+1];
		p->color_b0 = p->color_b1 = d_8to24table[colr*3+2];

		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = move[j] + crand()*2;
			p->vel[j] = crand()*10;
		}
		p->lastOrg[2] = p->org[2] -= 4;
//		p->vel[2] += 6;
		p->vel[2] += 20;

		VectorAdd (move, vec, move);
	}
}


/*
======
CL_ColorFlash - flash of light
======
*/
void CL_ColorFlash (vec3_t pos, int ent, int intensity, float r, float g, float b)
{
	cdlight_t	*dl;

	dl = CL_AllocDlight (ent);
	VectorCopy (pos,  dl->origin);
	dl->radius = intensity;
///	dl->minlight = 250;
	dl->die = cl.gameTime + 100;
	dl->color[0] = r;
	dl->color[1] = g;
	dl->color[2] = b;
}


/*
===============
CL_ColorExplosionParticles
===============
*/
void CL_ColorExplosionParticles (vec3_t org, int color, int run)
{
	int			i, j;
	cparticle_t	*p;

	for (i=0 ; i<128 ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->type = part_simple;
		p->flags = PARTICLE_BOUNCE | PARTICLE_FRICTION;
		p->time = cl.gameTime;
///	p->color = color + (rand() % run);
		p->color_r0 = p->color_r1 = color & 0xC0 + (rand() % run);
		p->color_g0 = p->color_g1 = (color << 3) & 0xC0 + (rand() % run);
		p->color_b0 = p->color_b1 = (color << 6) & 0xC0 + (rand() % run);

		for (j=0 ; j<3 ; j++)
		{
			p->lastOrg[j] = p->org[j] = org[j] + ((rand()%32)-16);
			p->vel[j] = (rand()%256)-128;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -2*PARTICLE_GRAVITY;
		p->alpha = 1.0;

		p->alphavel = -0.4 / (0.6 + frand()*0.2);
	}
}


void CL_Widowbeamout (cl_sustain_t *self)
{
	vec3_t			dir;
	int				i;
	cparticle_t		*p;
	float			ratio;

	ratio = 1.0 - (((float)self->endtime - (float)cl.gameTime)/2100.0);

	for(i=0;i<300;i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		VectorClear (p->accel);
		
		p->type = part_simple;
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = INSTANT_PARTICLE;
///	p->color = colortable[rand()&3];
		p->color_r0 = p->color_r1 = colortable[rand()&3] & 0xC0;
		p->color_g0 = p->color_g1 = (colortable[rand()&3] << 3) & 0xC0;
		p->color_b0 = p->color_b1 = (colortable[rand()&3] << 6) & 0xC0;

		dir[0] = crand();
		dir[1] = crand();
		dir[2] = crand();
		VectorNormalize(dir);
	
		VectorMA(self->org, (45.0 * ratio), dir, p->org);
		VectorCopy(p->org, p->lastOrg);
//		VectorMA(origin, 10*(((rand () & 0x7fff) / ((float)0x7fff))), dir, p->org);
	}
}

void CL_Nukeblast (cl_sustain_t *self)
{
	vec3_t			dir;
	int				i;
	cparticle_t		*p;
	int colortable_[4] = {110, 112, 114, 116};
	float			ratio;

	ratio = 1.0 - (((float)self->endtime - (float)cl.gameTime)/1000.0);

	for(i=0;i<700;i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		VectorClear (p->accel);
		
		p->type = part_simple;
		p->flags = 0;
		p->time = cl.gameTime;

		p->alpha = 1.0;
		p->alphavel = INSTANT_PARTICLE;
///	p->color = colortable_[rand()&3];
		p->color_r0 = p->color_r1 = colortable_[rand()&3] & 0xC0;
		p->color_g0 = p->color_g1 = (colortable_[rand()&3] << 3);
		p->color_b0 = p->color_b1 = (colortable_[rand()&3] << 6);

		dir[0] = crand();
		dir[1] = crand();
		dir[2] = crand();
		VectorNormalize(dir);
	
		VectorMA(self->org, (200.0 * ratio), dir, p->org);
		VectorCopy(p->org, p->lastOrg);
//		VectorMA(origin, 10*(((rand () & 0x7fff) / ((float)0x7fff))), dir, p->org);
	}
}


void CL_ParseWidow (bool large_map)
{
	vec3_t	pos;
	int		id, i;
	cl_sustain_t	*s, *free_sustain;

	id = MSG_ReadShort (&net_message);

	free_sustain = NULL;
	for (i=0, s=cl_sustains; i<MAX_SUSTAINS; i++, s++)
	{
		if (s->id == 0)
		{
			free_sustain = s;
			break;
		}
	}
	if (free_sustain)
	{
		s->id = id;
		MSG_ReadPos (&net_message, s->org, large_map);
		s->endtime = cl.gameTime + 2100;
		s->think = CL_Widowbeamout;
		s->thinkinterval = 1;
		s->nextthink = cl.gameTime;
	}
	else // no free sustains
	{
		// FIXME - read the stuff anyway
		MSG_ReadPos (&net_message, pos, large_map);
	}
}

void CL_ParseNuke (bool large_map)
{
	vec3_t	pos;
	int		i;
	cl_sustain_t	*s, *free_sustain;

	free_sustain = NULL;
	for (i=0, s=cl_sustains; i<MAX_SUSTAINS; i++, s++)
	{
		if (s->id == 0)
		{
			free_sustain = s;
			break;
		}
	}
	if (free_sustain)
	{
		s->id = 21000;
		MSG_ReadPos (&net_message, s->org, large_map);
		s->endtime = cl.gameTime + 1000;
		s->think = CL_Nukeblast;
		s->thinkinterval = 1;
		s->nextthink = cl.gameTime;
	}
	else // no free sustains
	{
		// FIXME - read the stuff anyway
		MSG_ReadPos (&net_message, pos, large_map);
	}
}


void CL_WidowSplash (vec3_t org)
{
	int			i;
	cparticle_t	*p;
	vec3_t		dir;

	for (i=0 ; i<256 ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->type = part_simple;
		p->flags = 0;
		p->time = cl.gameTime;
///	p->color = colortable[rand()&3];
		p->color_r0 = p->color_r1 = colortable[rand()&3] & 0xC0;
		p->color_g0 = p->color_g1 = (colortable[rand()&3] << 3) & 0xC0;
		p->color_b0 = p->color_b1 = (colortable[rand()&3] << 6) & 0xC0;

		dir[0] = crand();
		dir[1] = crand();
		dir[2] = crand();
		VectorNormalize(dir);
		VectorMA(org, 45.0, dir, p->org);
		VectorCopy(p->org, p->lastOrg);
		VectorMA(vec3_origin, 40.0, dir, p->vel);

		p->accel[0] = p->accel[1] = 0;
		p->alpha = 1.0;

		p->alphavel = -0.8 / (0.5 + frand()*0.3);
	}

}


void CL_WaterSplash (vec3_t org, vec3_t dir, byte color_r, byte color_g, byte color_b)
{
	vec3_t		start, end;
	cparticle_t	*p;

	// Чуток приподымем для трассировки
	VectorMA(org, 1, dir, start);
	// Чуток приопустим для трассировки
	VectorMA(org, -1, dir, end);
	// Чуток приподымем чтоб не было z-aliasing
	VectorMA(org, 0.1, dir, org);

	trace_t trace = CL_PMTraceWorld (start, vec3_origin, vec3_origin, end, MASK_WATER);
	if (trace.fraction > 0 && trace.fraction < 1)
	{
		_r = trace.surface->color_r1;
		_g = trace.surface->color_g1;
		_b = trace.surface->color_b1;
	}
	else
	{
		_r = color_r;
		_g = color_g;
		_b = color_b;
	}

	// Брызги на воде
	if (!free_particles)
		return;
	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;

	p->flags = 0;
	p->time = cl.gameTime;
	p->color_r0 = p->color_r1 = _r;
	p->color_g0 = p->color_g1 = _g;
	p->color_b0 = p->color_b1 = _b;
	p->alpha = 1;
	p->alphavel = -1.0 / (0.075 + frand()*0.2);
	VectorClear (p->accel);
	VectorClear (p->vel);
	VectorCopy (org, p->org);
	VectorCopy (org, p->lastOrg);
	VectorScale(dir, 40+frand()*10, p->length);		// финальный размер
	p->size = 14;
	p->type = part_plume;

	// Разводы на воде
	if (!free_particles)
		return;
	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;

	p->flags = 0;
	p->time = cl.gameTime;
	p->color_r0 = p->color_r1 = _r;
	p->color_g0 = p->color_g1 = _g;
	p->color_b0 = p->color_b1 = _b;
	p->alpha = 1;
	p->alphavel = -1.0 / (0.75 + frand()*0.3);
	VectorClear (p->accel);
	VectorClear (p->vel);
	VectorCopy (org, p->org);
	VectorCopy (org, p->lastOrg);
	VectorCopy (dir, p->length);
	p->size = 24;		// финальный радиус
	p->type = part_wake;
}


void CL_SplashSparks (vec3_t org, vec3_t dir, byte r0, byte g0, byte b0, byte r1, byte g1, byte b1, int count)
{
	int			i, j;
	cparticle_t	*p;
	float		d;

	if(!count)
		return;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;

		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = 0;
		p->time = cl.gameTime;
		p->color_r0 = r0;
		p->color_g0 = g0;
		p->color_b0 = b0;
		p->color_r1 = r1;
		p->color_g1 = g1;
		p->color_b1 = b1;

		d = 16 + crand()*16;
		for (j=0 ; j<3 ; j++)
		{
			p->vel[j] = crand()*16 + d*dir[j];
			p->org[j] = org[j];
			p->lastOrg[j] = org[j] + p->vel[j];
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY*3;
		p->alpha = 1.0;
		p->alphavel = -1.0 / (1 + frand() * 0.4);
		p->size = 1;
		p->type = part_sparks;
	}
}


void CL_Sparks (vec3_t org, vec3_t dir)
{
	vec3_t		norg;
	cparticle_t	*p;

	// Чуток приподымем чтоб нормально протестить контент
	VectorMA(org, 1, dir, org);

	if (CL_PMpointcontents(org) & MASK_WATER)	// в воде
		return;

	// небольшие отклонения от первоначального dir
	VectorMA(org, 8, dir, norg);
	norg[0] += crand();
	norg[1] += crand();
	norg[2] += crand();
	VectorSubtract(norg, org, dir);
	VectorNormalize(dir);

	if (!free_particles)
		return;
	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;

	p->flags = 0;
	p->time = cl.gameTime;
	p->color_r0 = p->color_g0 = p->color_b0 = p->color_r1 = p->color_g1 = p->color_b1 = 255;
	p->alpha = 1;
	p->alphavel = -1.0 / 0.1;
	VectorClear (p->accel);
	VectorClear (p->vel);
	VectorCopy (org, p->org);
	VectorCopy (org, p->lastOrg);
	VectorScale(dir, 48+frand()*16, p->length);		// финальный размер
	p->size = 2;
	p->type = part_tracer;
}


/*
====================
CL_WriteDemoMessage

Dumps the current net message, prefixed by the length
====================
*/
void CL_WriteDemoMessage ()
{
	int		len, swlen;

	// the first eight bytes are just packet sequencing stuff
	len = net_message.cursize-8;
	swlen = LittleLong(len);
	fwrite (&swlen, 4, 1, cls.demofile);
	fwrite (net_message.data+8,	len, 1, cls.demofile);
	cls.demolength += len + 4;
}


void CL_LogoutEffect (vec3_t org, int type)
{
	if(!p_spawn->value)
		return;

	int			i, j, n = 500 * p_spawn->value;
	cparticle_t	*p;

	for (i=0 ; i<n ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->flags = 0;
		p->time = cl.gameTime;

		if (type == MZ_LOGIN)
		{
			p->color_r0 = 0;
			p->color_g0 = 128 + (rand()&127);	// green
			p->color_b0 = 0;
			p->color_r1 = 0;
			p->color_g1 = 128 + (rand()&127);
			p->color_b1 = 128 + (rand()&127);
		}
		else if (type == MZ_LOGOUT)
		{
			p->color_r0 = 128 + (rand()&127);	// red
			p->color_g0 = 0;
			p->color_b0 = 0;
			p->color_r1 = 128 + (rand()&127);
			p->color_g1 = rand()&127;
			p->color_b1 = 0;
		}
		else
		{
			p->color_r0 = 200 + (rand()&15);	// from yellow
			p->color_g0 = 150 + (rand()&7);
			p->color_b0 = 0;
			p->color_r1 = 0;
			p->color_g1 = 200 + (rand()&7);		// to green
			p->color_b1 = 0;
		}

		p->lastOrg[0] = p->org[0] = org[0] - 16 + frand()*32;
		p->lastOrg[1] = p->org[1] = org[1] - 16 + frand()*32;
		p->lastOrg[2] = p->org[2] = org[2] - 24 + frand()*56;

		for (j=0 ; j<3 ; j++)
			p->vel[j] = crand()*20;

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;
		p->alpha = 1.0;

		p->alphavel = -1.0 / (1.0 + frand()*0.3);
		p->type = part_simple;
	}
}


void CL_ParseMuzzleFlash2 () 
{
	int			ent;
	vec3_t		origin;
	int			flash_number;
	cdlight_t	*dl;
	vec3_t		forward, right, up;
	char		soundname[64];

	ent = MSG_ReadShort (&net_message);
	if (ent < 1 || ent >= MAX_EDICTS)
		Com_Error (ERR_DROP, "CL_ParseMuzzleFlash2: bad entity");

	flash_number = MSG_ReadByte (&net_message);

	int size = sizeof(monster_flash_offset) / (3*sizeof(float));		/// Berserker: проверка на переполнение массива monster_flash_offset
	if (flash_number > size)
	{
		Com_DPrintf("CL_ParseMuzzleFlash2: flash_number [%i] too large\n", flash_number);
		flash_number = MZ2_SOLDIER_BLASTER_1;
	}

	// locate the origin
	AngleVectors (cl_entities[ent].current.angles, forward, right, up);
	origin[0] = cl_entities[ent].current.origin[0] + forward[0] * monster_flash_offset[flash_number*3] + right[0] * monster_flash_offset[flash_number*3+1];
	origin[1] = cl_entities[ent].current.origin[1] + forward[1] * monster_flash_offset[flash_number*3] + right[1] * monster_flash_offset[flash_number*3+1];
	origin[2] = cl_entities[ent].current.origin[2] + forward[2] * monster_flash_offset[flash_number*3] + right[2] * monster_flash_offset[flash_number*3+1] + monster_flash_offset[flash_number*3+2];

	dl = CL_AllocDlight (ent);
	VectorCopy (origin,  dl->origin);
	dl->radius = 200 + (rand()&31);
///	dl->minlight = 32;
	dl->die = cl.gameTime;	// + 0.1;

	switch (flash_number)
	{
	case MZ2_INFANTRY_MACHINEGUN_1:
	case MZ2_INFANTRY_MACHINEGUN_2:
	case MZ2_INFANTRY_MACHINEGUN_3:
	case MZ2_INFANTRY_MACHINEGUN_4:
	case MZ2_INFANTRY_MACHINEGUN_5:
	case MZ2_INFANTRY_MACHINEGUN_6:
	case MZ2_INFANTRY_MACHINEGUN_7:
	case MZ2_INFANTRY_MACHINEGUN_8:
	case MZ2_INFANTRY_MACHINEGUN_9:
	case MZ2_INFANTRY_MACHINEGUN_10:
	case MZ2_INFANTRY_MACHINEGUN_11:
	case MZ2_INFANTRY_MACHINEGUN_12:
	case MZ2_INFANTRY_MACHINEGUN_13:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
//CL_ParticleEffect (origin, vec3_origin, 30,30,30, 40, part_simple,0);
//CL_SmokeAndFlash(origin);
		if(p_machine->value)
			CL_ParticleEffect3 (dl->origin, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 4*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (dl->origin, vec3_origin, 1, true);
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("infantry/infatck1.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		CL_Tracers(dl->origin, forward, right, up, 32, 0, 0, 1, 5, 5, 2000, 2000);
		CL_WeaponFire(dl->origin, forward, right, up, 2, 0, 0, 24, 12, 12, 10);
		break;

	case MZ2_SOLDIER_MACHINEGUN_1:
	case MZ2_SOLDIER_MACHINEGUN_2:
	case MZ2_SOLDIER_MACHINEGUN_3:
	case MZ2_SOLDIER_MACHINEGUN_4:
	case MZ2_SOLDIER_MACHINEGUN_5:
	case MZ2_SOLDIER_MACHINEGUN_6:
	case MZ2_SOLDIER_MACHINEGUN_7:
	case MZ2_SOLDIER_MACHINEGUN_8:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
//CL_ParticleEffect (origin, vec3_origin, 30,30,30, 40, part_simple,0);
//CL_SmokeAndFlash(origin);
		if(p_machine->value)
			CL_ParticleEffect3 (dl->origin, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 4*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (dl->origin, vec3_origin, 1, true);
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("soldier/solatck3.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		CL_Tracers(dl->origin, forward, right, up, 32, 0, 8, 1, 2, 2, 1000, 1000);
		CL_WeaponFire(dl->origin, forward, right, up, 0, 0, 8, 24, 12, 10, 8);
		break;

	case MZ2_GUNNER_MACHINEGUN_1:
	case MZ2_GUNNER_MACHINEGUN_2:
	case MZ2_GUNNER_MACHINEGUN_3:
	case MZ2_GUNNER_MACHINEGUN_4:
	case MZ2_GUNNER_MACHINEGUN_5:
	case MZ2_GUNNER_MACHINEGUN_6:
	case MZ2_GUNNER_MACHINEGUN_7:
	case MZ2_GUNNER_MACHINEGUN_8:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
//CL_ParticleEffect (origin, vec3_origin, 30,30,30, 40, part_simple,0);
//CL_SmokeAndFlash(origin);
		if(p_machine->value)
			CL_ParticleEffect3 (dl->origin, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 6*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (dl->origin, vec3_origin, 1, true);
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("gunner/gunatck2.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		CL_Tracers(dl->origin, forward, right, up, 32, 0, 2, 1, 10, 10, 2000, 2000);
		CL_WeaponFire(dl->origin, forward, right, up, 0, 0, 2, 30, 10, 10, 16);
		break;

	case MZ2_ACTOR_MACHINEGUN_1:
	case MZ2_SUPERTANK_MACHINEGUN_1:
	case MZ2_SUPERTANK_MACHINEGUN_2:
	case MZ2_SUPERTANK_MACHINEGUN_3:
	case MZ2_SUPERTANK_MACHINEGUN_4:
	case MZ2_SUPERTANK_MACHINEGUN_5:
	case MZ2_SUPERTANK_MACHINEGUN_6:
	case MZ2_TURRET_MACHINEGUN:			// PGM
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
//CL_ParticleEffect (origin, vec3_origin, 30,30,30, 40, part_simple,0);
//CL_SmokeAndFlash(origin);
		if(p_machine->value)
			CL_ParticleEffect3 (dl->origin, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 6*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (dl->origin, vec3_origin, 1, true);
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("infantry/infatck1.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		CL_Tracers(dl->origin, forward, right, up, 32, 12, -6, 1, 10, 10, 3000, 3000);
		CL_WeaponFire(dl->origin, forward, right, up, 0, 12, -6, 48, 12, 12, 24);
		break;

	case MZ2_BOSS2_MACHINEGUN_L1:
	case MZ2_BOSS2_MACHINEGUN_L2:
	case MZ2_BOSS2_MACHINEGUN_L3:
	case MZ2_BOSS2_MACHINEGUN_L4:
	case MZ2_BOSS2_MACHINEGUN_L5:
	case MZ2_CARRIER_MACHINEGUN_L1:		// PMM
	case MZ2_CARRIER_MACHINEGUN_L2:		// PMM
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
//CL_ParticleEffect (origin, vec3_origin, 30,30,30, 40, part_simple,0);
//CL_SmokeAndFlash(origin);
		if(p_machine->value)
			CL_ParticleEffect3 (dl->origin, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 6*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (dl->origin, vec3_origin, 1, true);
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("infantry/infatck1.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		CL_Tracers(dl->origin, forward, right, up, 32, 0, 0, 1, 8, 8, 3000, 3000);
		CL_WeaponFire(dl->origin, forward, right, up, 3, 0, 0, 48, 12, 12, 24);
		break;

	case MZ2_SOLDIER_BLASTER_1:
	case MZ2_SOLDIER_BLASTER_2:
	case MZ2_SOLDIER_BLASTER_3:
	case MZ2_SOLDIER_BLASTER_4:
	case MZ2_SOLDIER_BLASTER_5:
	case MZ2_SOLDIER_BLASTER_6:
	case MZ2_SOLDIER_BLASTER_7:
	case MZ2_SOLDIER_BLASTER_8:
	case MZ2_TURRET_BLASTER:			// PGM
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("soldier/solatck2.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_FLYER_BLASTER_1:
	case MZ2_FLYER_BLASTER_2:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("flyer/flyatck3.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_MEDIC_BLASTER_1:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("medic/medatck1.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_HOVER_BLASTER_1:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("hover/hovatck1.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_FLOAT_BLASTER_1:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("floater/fltatck1.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_SOLDIER_SHOTGUN_1:
	case MZ2_SOLDIER_SHOTGUN_2:
	case MZ2_SOLDIER_SHOTGUN_3:
	case MZ2_SOLDIER_SHOTGUN_4:
	case MZ2_SOLDIER_SHOTGUN_5:
	case MZ2_SOLDIER_SHOTGUN_6:
	case MZ2_SOLDIER_SHOTGUN_7:
	case MZ2_SOLDIER_SHOTGUN_8:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
//CL_SmokeAndFlash(origin);
		if(p_shotgun->value)
			CL_ParticleEffect3 (dl->origin, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 8*p_shotgun->value, part_smoke);		// Дым
		CL_BrassShells (dl->origin, vec3_origin, 1, false);
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("soldier/solatck1.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		CL_Tracers(dl->origin, forward, right, up, 32, 0, 0, 4, 2, 2, 2000, 2000);
		CL_WeaponFire(dl->origin, forward, right, up, 4, 0, 0, 24, 8, 4, 8);
		break;

	case MZ2_TANK_BLASTER_1:
	case MZ2_TANK_BLASTER_2:
	case MZ2_TANK_BLASTER_3:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("tank/tnkatck3.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_TANK_MACHINEGUN_1:
	case MZ2_TANK_MACHINEGUN_2:
	case MZ2_TANK_MACHINEGUN_3:
	case MZ2_TANK_MACHINEGUN_4:
	case MZ2_TANK_MACHINEGUN_5:
	case MZ2_TANK_MACHINEGUN_6:
	case MZ2_TANK_MACHINEGUN_7:
	case MZ2_TANK_MACHINEGUN_8:
	case MZ2_TANK_MACHINEGUN_9:
	case MZ2_TANK_MACHINEGUN_10:
	case MZ2_TANK_MACHINEGUN_11:
	case MZ2_TANK_MACHINEGUN_12:
	case MZ2_TANK_MACHINEGUN_13:
	case MZ2_TANK_MACHINEGUN_14:
	case MZ2_TANK_MACHINEGUN_15:
	case MZ2_TANK_MACHINEGUN_16:
	case MZ2_TANK_MACHINEGUN_17:
	case MZ2_TANK_MACHINEGUN_18:
	case MZ2_TANK_MACHINEGUN_19:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
//CL_ParticleEffect (origin, vec3_origin, 30,30,30, 40, part_simple,0);
//CL_SmokeAndFlash(origin);
		if(p_machine->value)
			CL_ParticleEffect3 (dl->origin, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 6*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (dl->origin, vec3_origin, 1, true);
		Com_sprintf(soundname, sizeof(soundname), "tank/tnkatk2%c.wav", 'a' + rand() % 5);
		S_fastsound(NULL, ent, CHAN_WEAPON, S_RegisterSound(soundname), 1,
			MONSTERS_ATTACK_ATTENUATION);
		CL_Tracers(dl->origin, forward, right, up, 32, 0, 0, 1, 4, 4, 2000, 2000);
		CL_WeaponFire(dl->origin, forward, right, up, 0, 0, 0, 48, 16, 16, 24);
		break;

	case MZ2_CHICK_ROCKET_1:
	case MZ2_TURRET_ROCKET:			// PGM
		dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0.2;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("chick/chkatck2.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_TANK_ROCKET_1:
	case MZ2_TANK_ROCKET_2:
	case MZ2_TANK_ROCKET_3:
		dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0.2;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("tank/tnkatck1.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_SUPERTANK_ROCKET_1:
	case MZ2_SUPERTANK_ROCKET_2:
	case MZ2_SUPERTANK_ROCKET_3:
	case MZ2_BOSS2_ROCKET_1:
	case MZ2_BOSS2_ROCKET_2:
	case MZ2_BOSS2_ROCKET_3:
	case MZ2_BOSS2_ROCKET_4:
	case MZ2_CARRIER_ROCKET_1:
//	case MZ2_CARRIER_ROCKET_2:
//	case MZ2_CARRIER_ROCKET_3:
//	case MZ2_CARRIER_ROCKET_4:
		dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0.2;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("tank/rocket.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_GUNNER_GRENADE_1:
	case MZ2_GUNNER_GRENADE_2:
	case MZ2_GUNNER_GRENADE_3:
	case MZ2_GUNNER_GRENADE_4:
		dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("gunner/gunatck3.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_GLADIATOR_RAILGUN_1:
	// PMM
	case MZ2_CARRIER_RAILGUN:
	case MZ2_WIDOW_RAIL:
	// pmm
		dl->color[0] = 0.5;dl->color[1] = 0.5;dl->color[2] = 1.0;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("weapons/railgf1a.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

// --- Xian's shit starts ---
	case MZ2_MAKRON_BFG:
		dl->color[0] = 0.5;dl->color[1] = 1 ;dl->color[2] = 0.5;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("makron/bfg_fire.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_MAKRON_BLASTER_1:
	case MZ2_MAKRON_BLASTER_2:
	case MZ2_MAKRON_BLASTER_3:
	case MZ2_MAKRON_BLASTER_4:
	case MZ2_MAKRON_BLASTER_5:
	case MZ2_MAKRON_BLASTER_6:
	case MZ2_MAKRON_BLASTER_7:
	case MZ2_MAKRON_BLASTER_8:
	case MZ2_MAKRON_BLASTER_9:
	case MZ2_MAKRON_BLASTER_10:
	case MZ2_MAKRON_BLASTER_11:
	case MZ2_MAKRON_BLASTER_12:
	case MZ2_MAKRON_BLASTER_13:
	case MZ2_MAKRON_BLASTER_14:
	case MZ2_MAKRON_BLASTER_15:
	case MZ2_MAKRON_BLASTER_16:
	case MZ2_MAKRON_BLASTER_17:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("makron/blaster.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;
	
	case MZ2_JORG_MACHINEGUN_L1:
	case MZ2_JORG_MACHINEGUN_L2:
	case MZ2_JORG_MACHINEGUN_L3:
	case MZ2_JORG_MACHINEGUN_L4:
	case MZ2_JORG_MACHINEGUN_L5:
	case MZ2_JORG_MACHINEGUN_L6:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
//CL_ParticleEffect (origin, vec3_origin, 30,30,30, 40, part_simple,0);
//CL_SmokeAndFlash(origin);
		if(p_machine->value)
			CL_ParticleEffect3 (dl->origin, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 6*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (dl->origin, vec3_origin, 1, true);
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("boss3/xfire.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		CL_Tracers(dl->origin, forward, right, up, 32, 0, 0, 1, 8, 8, 2000, 2000);
		CL_WeaponFire(dl->origin, forward, right, up, 0, 0, -12, 48, 24, 24, 24);
		break;

	case MZ2_JORG_MACHINEGUN_R1:
	case MZ2_JORG_MACHINEGUN_R2:
	case MZ2_JORG_MACHINEGUN_R3:
	case MZ2_JORG_MACHINEGUN_R4:
	case MZ2_JORG_MACHINEGUN_R5:
	case MZ2_JORG_MACHINEGUN_R6:
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
//CL_ParticleEffect (origin, vec3_origin, 30,30,30, 40, part_simple,0);
//CL_SmokeAndFlash(origin);
		if(p_machine->value)
			CL_ParticleEffect3 (dl->origin, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 6*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (dl->origin, vec3_origin, 1, true);
		CL_Tracers(dl->origin, forward, right, up, 32, 0, 0, 1, 8, 8, 2000, 2000);
		CL_WeaponFire(dl->origin, forward, right, up, 0, 0, -12, 48, 24, 24, 24);
		break;

	case MZ2_JORG_BFG_1:
		dl->color[0] = 0.5;dl->color[1] = 1 ;dl->color[2] = 0.5;
		break;

	case MZ2_BOSS2_MACHINEGUN_R1:
	case MZ2_BOSS2_MACHINEGUN_R2:
	case MZ2_BOSS2_MACHINEGUN_R3:
	case MZ2_BOSS2_MACHINEGUN_R4:
	case MZ2_BOSS2_MACHINEGUN_R5:
	case MZ2_CARRIER_MACHINEGUN_R1:			// PMM
	case MZ2_CARRIER_MACHINEGUN_R2:			// PMM
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
//CL_ParticleEffect (origin, vec3_origin, 30,30,30, 40, part_simple,0);
//CL_SmokeAndFlash(origin);
		if(p_machine->value)
			CL_ParticleEffect3 (dl->origin, vec3_origin, p_shot_r->value*256,p_shot_g->value*256,p_shot_b->value*256, 6*p_machine->value, part_smoke);		// Дым
		CL_BrassShells (dl->origin, vec3_origin, 1, true);
		CL_Tracers(dl->origin, forward, right, up, 32, 0, 0, 1, 8, 8, 3000, 3000);
		CL_WeaponFire(dl->origin, forward, right, up, 3, 0, 0, 48, 12, 12, 24);
		break;


// ROGUE
	case MZ2_STALKER_BLASTER:
	case MZ2_DAEDALUS_BLASTER:
	case MZ2_MEDIC_BLASTER_2:
	case MZ2_WIDOW_BLASTER:
	case MZ2_WIDOW_BLASTER_SWEEP1:
	case MZ2_WIDOW_BLASTER_SWEEP2:
	case MZ2_WIDOW_BLASTER_SWEEP3:
	case MZ2_WIDOW_BLASTER_SWEEP4:
	case MZ2_WIDOW_BLASTER_SWEEP5:
	case MZ2_WIDOW_BLASTER_SWEEP6:
	case MZ2_WIDOW_BLASTER_SWEEP7:
	case MZ2_WIDOW_BLASTER_SWEEP8:
	case MZ2_WIDOW_BLASTER_SWEEP9:
	case MZ2_WIDOW_BLASTER_100:
	case MZ2_WIDOW_BLASTER_90:
	case MZ2_WIDOW_BLASTER_80:
	case MZ2_WIDOW_BLASTER_70:
	case MZ2_WIDOW_BLASTER_60:
	case MZ2_WIDOW_BLASTER_50:
	case MZ2_WIDOW_BLASTER_40:
	case MZ2_WIDOW_BLASTER_30:
	case MZ2_WIDOW_BLASTER_20:
	case MZ2_WIDOW_BLASTER_10:
	case MZ2_WIDOW_BLASTER_0:
	case MZ2_WIDOW_BLASTER_10L:
	case MZ2_WIDOW_BLASTER_20L:
	case MZ2_WIDOW_BLASTER_30L:
	case MZ2_WIDOW_BLASTER_40L:
	case MZ2_WIDOW_BLASTER_50L:
	case MZ2_WIDOW_BLASTER_60L:
	case MZ2_WIDOW_BLASTER_70L:
	case MZ2_WIDOW_RUN_1:
	case MZ2_WIDOW_RUN_2:
	case MZ2_WIDOW_RUN_3:
	case MZ2_WIDOW_RUN_4:
	case MZ2_WIDOW_RUN_5:
	case MZ2_WIDOW_RUN_6:
	case MZ2_WIDOW_RUN_7:
	case MZ2_WIDOW_RUN_8:
		dl->color[0] = 0;dl->color[1] = 1;dl->color[2] = 0;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("tank/tnkatck3.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_WIDOW_DISRUPTOR:
		dl->color[0] = -1;dl->color[1] = -1;dl->color[2] = -1;
		S_fastsound(NULL, ent, CHAN_WEAPON,
			S_RegisterSound("weapons/disint2.wav"), 1,
			MONSTERS_ATTACK_ATTENUATION);
		break;

	case MZ2_WIDOW_PLASMABEAM:
	case MZ2_WIDOW2_BEAMER_1:
	case MZ2_WIDOW2_BEAMER_2:
	case MZ2_WIDOW2_BEAMER_3:
	case MZ2_WIDOW2_BEAMER_4:
	case MZ2_WIDOW2_BEAMER_5:
	case MZ2_WIDOW2_BEAM_SWEEP_1:
	case MZ2_WIDOW2_BEAM_SWEEP_2:
	case MZ2_WIDOW2_BEAM_SWEEP_3:
	case MZ2_WIDOW2_BEAM_SWEEP_4:
	case MZ2_WIDOW2_BEAM_SWEEP_5:
	case MZ2_WIDOW2_BEAM_SWEEP_6:
	case MZ2_WIDOW2_BEAM_SWEEP_7:
	case MZ2_WIDOW2_BEAM_SWEEP_8:
	case MZ2_WIDOW2_BEAM_SWEEP_9:
	case MZ2_WIDOW2_BEAM_SWEEP_10:
	case MZ2_WIDOW2_BEAM_SWEEP_11:
		dl->radius = 300 + (rand()&100);
		dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0;
		dl->die = cl.gameTime + 200;
		break;
	}
}


void CL_ParseServerMessage ()
{
	int			cmd;
	char		*s;
	int			i;

	while (1)
	{
		if (net_message.readcount > net_message.cursize)
		{
			Com_Error (ERR_DROP,"CL_ParseServerMessage: Bad server message");
			break;
		}

		cmd = MSG_ReadByte (&net_message);

		if (cmd == -1)
		{
			break;
		}

		// other commands
		switch (cmd)
		{
		default:
			Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message");
			break;

		case svc_nop:
			break;

		case svc_disconnect:
			Com_Error (ERR_DISCONNECT,"Server disconnected\n");
			CDAudio_Stop ();
///			S_StopBackgroundTrack();
			break;

		case svc_reconnect:
			Com_Printf ("Server disconnected, reconnecting\n");
			if (cls.download)
			{	//ZOID, close download
				fclose (cls.download);
				cls.download = NULL;
			}
			cls.state = ca_connecting;
			cls.connect_time = -99999;	// CL_CheckForResend() will fire immediately
			break;

		case svc_print:
			i = MSG_ReadByte (&net_message);
/*			if (i == PRINT_CHAT)
			{
				S_StartLocalSound (cl_sfx_misc_talk);
				con.ormask = 128;
			}
			Com_Printf ("%s", MSG_ReadString (&net_message));
			con.ormask = 0;
*/
			if (i == PRINT_CHAT)
			{
				S_StartLocalSound (cl_sfx_misc_talk);
				Com_Printf_Mono ("^2", MSG_ReadString (&net_message));
			}
			else
				Com_Printf ("%s", MSG_ReadString (&net_message));

			break;

		case svc_centerprint:
			SCR_CenterPrint (MSG_ReadString (&net_message));
			break;

		case svc_stufftext:
			loading_string = "Getting a stufftext";
			s = MSG_ReadString (&net_message);
			Com_DPrintf ("stufftext: %s\n", s);
			Cbuf_AddText (s);
			break;

		case svc_serverdata:
			Cbuf_Execute ();		// make sure any stuffed commands are done
			CL_ParseServerData ();
			break;

		case svc_configstring:
			loading_string = "Getting a configstrings";
			CL_ParseConfigString ();
			break;

		case svc_sound:
			CL_ParseStartSoundPacket();
			break;

		case svc_spawnbaseline:
			loading_string = "Spawn a baseline";
			CL_ParseBaseline ();
			break;

		case svc_temp_entity:
			CL_ParseTEnt (false);
			break;

		case svc_temp_entity_ext:
			CL_ParseTEnt (true);
			break;

		case svc_muzzleflash:
			CL_ParseMuzzleFlash ();
			break;

		case svc_muzzleflash2:
			CL_ParseMuzzleFlash2 ();
			break;

		case svc_download:
			loading_string = "Downloading:";
			CL_ParseDownload ();
			break;

		case svc_frame:
			CL_ParseFrame ();
			break;

		case svc_inventory:
			CL_ParseInventory ();
			break;

		case svc_layout:
			s = MSG_ReadString (&net_message);
			strncpy (cl.layout, s, sizeof(cl.layout)-1);
			break;

////		case svc_areaportals_state:
////			CL_ParseAreaPortalsState ();	/// Berserker: using only for demos
////			break;

////		case svc_areaportal:
////			CL_ParseAreaPortalChange ();	/// Berserker
////			break;

		case svc_rfx:
			CL_ParseRenderFX ();
			break;

		case svc_playerinfo:
		case svc_packetentities:
		case svc_deltapacketentities:
			Com_Error (ERR_DROP, "Out of place frame data");
			break;
		}
	}

	//
	// we don't know if it is ok to save a demo message until
	// after we have parsed the frame
	//
	if (cls.demorecording && !cls.demowaiting)
		CL_WriteDemoMessage ();
}


void CL_ReadPackets ()
{
	while (NET_GetPacket (NS_CLIENT, &net_from, &net_message))
	{
		//
		// remote command packet
		//
		if (*(int *)net_message.data == -1)
		{
			CL_ConnectionlessPacket ();
			continue;
		}

		if (cls.state == ca_disconnected || cls.state == ca_connecting)
			continue;		// dump it if not connected

		if (net_message.cursize < 8)
		{
			Com_Printf ("^3%s: Runt packet\n", NET_AdrToString(net_from));
			continue;
		}

		//
		// packet from server
		//
		if (!NET_CompareAdr (net_from, cls.netchan.remote_address))
		{
			Com_DPrintf ("%s:sequenced packet without connection\n" ,NET_AdrToString(net_from));
			continue;
		}
		if (!Netchan_Process(&cls.netchan, &net_message))
			continue;		// wasn't accepted for some reason
		CL_ParseServerMessage ();
	}

	//
	// check timeout
	//
	if (cls.state >= ca_connected
	 && cls.realtime - cls.netchan.last_received > cl_timeout->value*1000)
	{
		if (++cl.timeoutcount > 5)	// timeoutcount saves debugger
		{
			Com_Printf ("\n^1Server connection timed out.\n");
			CL_Disconnect ();
			return;
		}
	}
	else
		cl.timeoutcount = 0;
	
}


/*
================
Sys_SendKeyEvents

Send Key_Event calls
================
*/
void Sys_SendKeyEvents ()
{
    MSG        msg;

	while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
	{
		if (!GetMessage (&msg, NULL, 0, 0))
			Sys_Quit ();
		sys_msg_time = msg.time;
      	TranslateMessage (&msg);
      	DispatchMessage (&msg);
	}

	// grab frame time 
	sys_frame_time = timeGetTime();	// FIXME: should this be at start?
}


/*
===============
CL_KeyState

Returns the fraction of the frame that the key was down
===============
*/
float CL_KeyState (kbutton_t *key)
{
	float		val;
	int			msec;

	key->state &= 1;		// clear impulses

	msec = key->msec;
	key->msec = 0;

	if (key->state)
	{	// still down
		msec += sys_frame_time - key->downtime;
		key->downtime = sys_frame_time;
	}

	val = (float)msec / frame_msec;
	if (val < 0)
		val = 0;
	if (val > 1)
		val = 1;

	return val;
}


/*
================
CL_AdjustAngles

Moves the local angle positions
================
*/
void CL_AdjustAngles ()
{
	float	speed;
	float	up, down;
	
	if (in_speed.state & 1)
		speed = cls.frametime * cl_anglespeedkey->value;
	else
		speed = cls.frametime;

	if (!(in_strafe.state & 1))
	{
		if (old_mouse_support)
		{
			cl.viewangles[YAW] -= speed*cl_yawspeed->value * CL_KeyState (&in_right);
			cl.viewangles[YAW] += speed*cl_yawspeed->value * CL_KeyState (&in_left);
		}
		else
		{
			cl.viewangles_YAW -= speed*cl_yawspeed->value * CL_KeyState (&in_right);
			cl.viewangles_YAW += speed*cl_yawspeed->value * CL_KeyState (&in_left);
		}
	}
	if (in_klook.state & 1)
	{
		if (old_mouse_support)
		{
			cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward);
			cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back);
		}
		else
		{
			cl.viewangles_PITCH -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward);
			cl.viewangles_PITCH += speed*cl_pitchspeed->value * CL_KeyState (&in_back);
		}
	}
	
	up = CL_KeyState (&in_lookup);
	down = CL_KeyState(&in_lookdown);
	
	if (old_mouse_support)
	{
		cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * up;
		cl.viewangles[PITCH] += speed*cl_pitchspeed->value * down;
	}
	else
	{
		cl.viewangles_PITCH -= speed*cl_pitchspeed->value * up;
		cl.viewangles_PITCH += speed*cl_pitchspeed->value * down;
	}
}


/*
================
CL_BaseMove

Send the intended movement message to the server
================
*/
void CL_BaseMove (usercmd_t *cmd)
{	
	CL_AdjustAngles ();

	Memset (cmd, 0, sizeof(*cmd));

	if (old_mouse_support)
		VectorCopy (cl.viewangles, cmd->angles);
	else
	{
		cmd->angles[0] = cl.viewangles_PITCH;
		cmd->angles[1] = cl.viewangles_YAW;
		cmd->angles[2] = 0;
	}

	if (in_strafe.state & 1)
	{
		cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right);
		cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left);
	}

	cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright);
	cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft);

	cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up);
	cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down);

	if (! (in_klook.state & 1) )
	{	
		cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward);
		cmd->forwardmove -= cl_forwardspeed->value * CL_KeyState (&in_back);
	}	

//
// adjust for speed key / running
//
	if ( (in_speed.state & 1) ^ (int)(cl_run->value) )
	{
		cmd->forwardmove *= 2;
		cmd->sidemove *= 2;
		cmd->upmove *= 2;
	}	
}


void IN_MouseMove (usercmd_t *cmd)
{
	int		mx, my;

	if (!old_mouse_support)
		return;

	if (!mouseactive)
		return;

	// find mouse movement
	if (!GetCursorPos (&current_pos))
		return;

	mx = current_pos.x - window_center_x;
	my = current_pos.y - window_center_y;

	if (m_filter->value)
	{
		mouse_x = (mx + old_mouse_x) * 0.5;
		mouse_y = (my + old_mouse_y) * 0.5;
	}
	else
	{
		mouse_x = mx;
		mouse_y = my;
	}

	old_mouse_x = mx;
	old_mouse_y = my;

	if( m_accel->value < 0 )
		Cvar_Set( "m_accel", "0" );

	/// Berserker: коррекция чуствительности от FOV
	float sens = (sensitivity->value + sqrt(float(mouse_x * mouse_x + mouse_y * mouse_y)) * m_accel->value) * cl.refdef.fov_x / 90.0f;
	mouse_x *= sens;
	mouse_y *= sens;

// add mouse X/Y movement to cmd
	if ( (in_strafe.state & 1) || (lookstrafe->value && mlooking ))
		cmd->sidemove += m_side->value * mouse_x;
	else
		cl.viewangles[YAW] -= m_yaw->value * mouse_x;

	if ( (mlooking || freelook->value) && !(in_strafe.state & 1))
	{
		cl.viewangles[PITCH] += m_pitch->value * mouse_y;
	}
	else
	{
		cmd->forwardmove -= m_forward->value * mouse_y;
	}

	// force the mouse to the center, so there's room to move
	if (mx || my)
		SetCursorPos (window_center_x, window_center_y);
}



void CL_ClampPitch ()
{
	float	pitch;

	pitch = SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]);
	if (pitch > 180)
		pitch -= 360;

	if (old_mouse_support)
	{
		if (cl.viewangles[PITCH] + pitch < -360)
			cl.viewangles[PITCH] += 360; // wrapped
		if (cl.viewangles[PITCH] + pitch > 360)
			cl.viewangles[PITCH] -= 360; // wrapped

		if (cl.viewangles[PITCH] + pitch > 89)
			cl.viewangles[PITCH] = 89 - pitch;
		if (cl.viewangles[PITCH] + pitch < -89)
			cl.viewangles[PITCH] = -89 - pitch;
	}
	else
	{
		if (cl.viewangles_PITCH + pitch < -360)
			cl.viewangles_PITCH += 360; // wrapped
		if (cl.viewangles_PITCH + pitch > 360)
			cl.viewangles_PITCH -= 360; // wrapped

		if (cl.viewangles_PITCH + pitch > 89)
			cl.viewangles_PITCH = 89 - pitch;
		if (cl.viewangles_PITCH + pitch < -89)
			cl.viewangles_PITCH = -89 - pitch;
	}
}


void R_LightForPoint (vec3_t point, vec3_t color)
{
	vec3_t		end;

	if (!VectorCompare(world_ambient_light, vec3_origin))
	{
		VectorCopy(world_ambient_light, color);
		return;
	}

	if (!r_worldmodel || !r_worldmodel->lightdata)
	{
		if (r_simple->value)
			color[0] = color[1] = color[2] = 0.8;
		else
			color[0] = color[1] = color[2] = 0.5;
		return;
	}
	end[0] = point[0];
	end[1] = point[1];
	end[2] = point[2] - 2048;
	VectorClear (pointcolor);
	if (RecursiveLightPoint (r_worldmodel->nodes, point, end) == -1)
	{
		if (r_simple->value)
			color[0] = color[1] = color[2] = 1;
		else
			color[0] = color[1] = color[2] = 0.02;
	}
	else
	{
		if (r_simple->value)
		{
			if (r_modulate->value)
			{
				if (VectorCompare(pointcolor, vec3_origin))
				{
					color[0] = color[1] = color[2] = 1;
					return;
				}
				VectorScale(pointcolor, r_modulate->value * 8, color);
			}
			else
				color[0] = color[1] = color[2] = 0.8;
		}
		else
		{
			if (r_modulate->value)
				VectorScale(pointcolor, r_modulate->value, color);
			else
				color[0] = color[1] = color[2] = 0.5;
		}
	}
}


void Mod_CalcSurfColors()
{
	mtexinfo_t	*texinfo;
	image_t		*image;
	int			i;
	float		d, r,g,b;

	for (i=0; i<numtexinfo; i++)
	{
		texinfo = (loadmodel->texinfo+i);
		image = texinfo->image;

		// для уменьшения контрастности
		r = Sqrt(image->color[0]);
		g = Sqrt(image->color[1]);
		b = Sqrt(image->color[2]*0.9);	// хак для уменьшения синевы (для воды)
		d = 255.0 / max(r, max(g, b));

		map_surfaces[i].c.color_r = image->color[0]*255;
		map_surfaces[i].c.color_g = image->color[1]*255;
		map_surfaces[i].c.color_b = image->color[2]*255;

		map_surfaces[i].c.color_r1 = r*d;
		map_surfaces[i].c.color_g1 = g*d;
		map_surfaces[i].c.color_b1 = b*d;
	}
}


void CL_FinishMove (usercmd_t *cmd)
{
	int		ms;
	int		i;

//
// figure button bits
//
	if ( in_flashlight.state & 3 )
		cmd->buttons |= BUTTON_FLASHLIGHT;
	in_flashlight.state &= ~2;

	if ( in_attack.state & 3 )
		cmd->buttons |= BUTTON_ATTACK;
	in_attack.state &= ~2;

	if (in_use.state & 3)
		cmd->buttons |= BUTTON_USE;
	in_use.state &= ~2;

	if (anykeydown && cls.key_dest == key_game)
		cmd->buttons |= BUTTON_ANY;

	// send milliseconds of time to apply the move
	ms = cls.frametime * 1000;
	if (ms > 250)
		ms = 100;		// time was unreasonable
	cmd->msec = ms;

	CL_ClampPitch ();
	if (old_mouse_support)
	{
		for (i=0 ; i<3 ; i++)
			cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]);
	}
	else
	{
		cmd->angles[0] = ANGLE2SHORT(cl.viewangles_PITCH);
		cmd->angles[1] = ANGLE2SHORT(cl.viewangles_YAW);
		cmd->angles[2] = 0;
	}

	cmd->impulse = in_impulse;
	in_impulse = 0;

// send the ambient light level at the player's current position
	if (cls.state != ca_active)
		cmd->lightlevel = 0;
	else
	{
		vec3_t	ambi;
		R_LightForPoint(cl.refdef.vieworg, ambi);
		float max = max(max(ambi[0],ambi[1]),ambi[2]);
		cmd->lightlevel = (byte)150 * max;
///		if(cmd->lightlevel>255)		/// is byte
///			cmd->lightlevel=255;
	}
}


usercmd_t CL_CreateCmd ()
{
	usercmd_t	cmd;

	frame_msec = sys_frame_time - old_sys_frame_time;
	if (frame_msec < 1)
		frame_msec = 1;
	if (frame_msec > 200)
		frame_msec = 200;
	
	// get basic movement from keyboard
	CL_BaseMove (&cmd);

	// allow OLD mice to add to the move
	IN_MouseMove (&cmd);

	if (ActiveApp)
		IN_JoyMove (&cmd);

	CL_FinishMove (&cmd);

	old_sys_frame_time = sys_frame_time;

	return cmd;
}


void CL_SendCmd ()
{
	sizebuf_t	buf;
	byte		data[128];
	int			i;
	usercmd_t	*cmd, *oldcmd;
	usercmd_t	nullcmd;
	int			checksumIndex;

buf.data = NULL;	// quiet compiler warning

	// build a command even if not connected

	// save this command off for prediction
	i = cls.netchan.outgoing_sequence & (CMD_BACKUP-1);
	cmd = &cl.cmds[i];
	cl.cmd_time[i] = cls.realtime;	// for netgraph ping calculation

	*cmd = CL_CreateCmd ();

	cl.cmd = *cmd;

	if (cls.state == ca_disconnected || cls.state == ca_connecting)
		return;

	if ( cls.state == ca_connected)
	{
		if (cls.netchan.message.cursize	|| curtime - cls.netchan.last_sent > 1000 )
			Netchan_Transmit (&cls.netchan, 0, buf.data);	
		return;
	}

	// send a userinfo update if needed
	if (userinfo_modified)
	{
		CL_FixUpGender();
		userinfo_modified = false;
		MSG_WriteByte (&cls.netchan.message, clc_userinfo);
		if(net_compatibility->value)
			MSG_WriteString (&cls.netchan.message, Cvar_Userinfo(true) );	/// Шлём полную строку для совместимости
		else
			MSG_WriteString (&cls.netchan.message, Cvar_Userinfo(false) );	/// или только изменения (Berserker: оптимизация сетевого протокола)
	}

	SZ_Init (&buf, data, sizeof(data));

	if (cmd->buttons && cl.cinematictime > 0 && !cl.attractloop && cls.realtime - cl.cinematictime > 1000)
	{	// skip the rest of the cinematic
		SCR_StopCinematic ();		/// Berserker: НЕ УБИРАТЬ! Иначе будут глюки с cinema!!!
		SCR_FinishCinematic ();
	}

	// begin a client move command
	MSG_WriteByte (&buf, clc_move);

	// save the position for a checksum byte
	checksumIndex = buf.cursize;
	MSG_WriteByte (&buf, 0);

	// let the server know what the last frame we
	// got was, so the next message can be delta compressed
	if (cl_nodelta->value || !cl.frame.valid || cls.demowaiting)
		MSG_WriteLong (&buf, -1);	// no compression
	else
		MSG_WriteLong (&buf, cl.frame.serverframe);

	// send this and the previous cmds in the message, so
	// if the last packet was dropped, it can be recovered
	i = (cls.netchan.outgoing_sequence-2) & (CMD_BACKUP-1);
	cmd = &cl.cmds[i];
	Memset (&nullcmd, 0, sizeof(nullcmd));
	MSG_WriteDeltaUsercmd (&buf, &nullcmd, cmd);
	oldcmd = cmd;

	i = (cls.netchan.outgoing_sequence-1) & (CMD_BACKUP-1);
	cmd = &cl.cmds[i];
	MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd);
	oldcmd = cmd;

	i = (cls.netchan.outgoing_sequence) & (CMD_BACKUP-1);
	cmd = &cl.cmds[i];
	MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd);

	// calculate a checksum over the move commands
	buf.data[checksumIndex] = COM_BlockSequenceCRCByte(
		buf.data + checksumIndex + 1, buf.cursize - checksumIndex - 1,
		cls.netchan.outgoing_sequence);

	//
	// deliver the message
	//
	Netchan_Transmit (&cls.netchan, buf.cursize, buf.data);	
}


/*
=================
CL_CheckForResend

Resend a connect message if the last one has timed out
=================
*/
void CL_CheckForResend ()
{
	netadr_t	adr;

	// if the local server is running and we aren't then connect
	if (cls.state == ca_disconnected && Com_ServerState() )
	{
		cls.state = ca_connecting;
		strncpy (cls.servername, "localhost", sizeof(cls.servername)-1);
		// we don't need a challenge on the localhost
		CL_SendConnectPacket ();
		return;
	}

	// resend if we haven't gotten a reply yet
	if (cls.state != ca_connecting)
		return;

	if (cls.realtime - cls.connect_time < 3000)
		return;

	if (!NET_StringToAdr (cls.servername, &adr))
	{
		Com_Printf ("^1Bad server address\n");
		cls.state = ca_disconnected;
		return;
	}
	if (!adr.port)
	{
		if(net_compatibility->value)
			adr.port = BigShort(OLD_PORT_SERVER);
		else
			adr.port = BigShort(PORT_SERVER);
	}

	cls.connect_time = cls.realtime;	// for retransmit requests

	Com_Printf ("Connecting to %s...\n", cls.servername);

	Netchan_OutOfBandPrint (NS_CLIENT, adr, "getchallenge\n");
}


void CL_ClipMoveToEntities ( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, trace_t *tr )
{
	int			i, x, zd, zu;
	trace_t		trace;
	int			headnode;
	float		*angles;
	entity_state_t	*ent;
	int			num;
	cmodel_t		*cmodel;
	vec3_t		bmins, bmaxs;

	for (i=0 ; i<cl.frame.num_entities ; i++)
	{
		num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
		ent = &cl_parse_entities[num];

		if (!ent->solid)
			continue;

		if (ent->number == cl.playernum+1)
			continue;

		if (ent->solid == 31)
		{	// special value for bmodel
			cmodel = cl.model_clip[ent->modelindex];
			if (!cmodel)
				continue;
			headnode = cmodel->headnode;
			angles = ent->angles;
		}
		else
		{	// encoded bbox
			x = 8*(ent->solid & 31);
			zd = 8*((ent->solid>>5) & 31);
			zu = 8*((ent->solid>>10) & 63) - 32;

			bmins[0] = bmins[1] = -x;
			bmaxs[0] = bmaxs[1] = x;
			bmins[2] = -zd;
			bmaxs[2] = zu;

			headnode = CM_HeadnodeForBox (bmins, bmaxs);
			angles = vec3_origin;	// boxes don't rotate
		}

		if (tr->allsolid)
			return;

		trace = CM_TransformedBoxTrace (start, end,
			mins, maxs, headnode,  MASK_PLAYERSOLID|CONTENTS_AUX,
			ent->origin, angles);

		if (trace.allsolid || trace.startsolid ||
		trace.fraction < tr->fraction)
		{
			trace.ent = (struct edict_s *)ent;
		 	if (tr->startsolid)
			{
				*tr = trace;
				tr->startsolid = true;
			}
			else
				*tr = trace;
		}
		else if (trace.startsolid)
			tr->startsolid = true;
	}
}


trace_t		CL_PMTrace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int mask)
{
	trace_t	t;

	// check against world
	t = CM_BoxTrace (start, end, mins, maxs, 0, mask);
	if (t.fraction < 1.0)
		t.ent = (struct edict_s *)1;

	// check all other solid models
	CL_ClipMoveToEntities (start, mins, maxs, end, &t);

	return t;
}


int	CL_PMpointcontents2 (vec3_t point, model_t *ignore)
{
	int			i;
	entity_state_t	*ent;
	int			num;
	cmodel_t		*cmodel;
	int			contents;

	contents = CM_PointContents (point, 0);

	for (i=0 ; i<cl.frame.num_entities ; i++)
	{
		num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
		ent = &cl_parse_entities[num];

		if (ent->solid != 31) // special value for bmodel
			continue;

		if (cl.model_draw[ent->modelindex] == ignore)
			continue;

		cmodel = cl.model_clip[ent->modelindex];
		if (!cmodel)
			continue;

		contents |= CM_TransformedPointContents (point, cmodel->headnode, ent->origin, ent->angles);
	}

	return contents;
}


int		CL_PMpointcontents (vec3_t point)
{
	int			i;
	entity_state_t	*ent;
	int			num;
	cmodel_t		*cmodel;
	int			contents;

	contents = CM_PointContents (point, 0);

	for (i=0 ; i<cl.frame.num_entities ; i++)
	{
		num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
		ent = &cl_parse_entities[num];

		if (ent->solid != 31) // special value for bmodel
			continue;

		cmodel = cl.model_clip[ent->modelindex];
		if (!cmodel)
			continue;

		contents |= CM_TransformedPointContents (point, cmodel->headnode, ent->origin, ent->angles);
	}

	return contents;
}


void CL_FixCvarCheats ()
{
	int			i;
	cheatvar_t	*var;

	if ( !strcmp(cl.configstrings[CS_MAXCLIENTS], "1") || !cl.configstrings[CS_MAXCLIENTS][0] )
		return;		// single player can cheat

	// find all the cvars if we haven't done it yet
	if (!numcheatvars)
	{
		while (cheatvars[numcheatvars].name)
		{
			cheatvars[numcheatvars].var = Cvar_Get (cheatvars[numcheatvars].name, cheatvars[numcheatvars].value, 0);
			numcheatvars++;
		}
	}

	// make sure they are all set to the proper values
	for (i=0, var = cheatvars ; i<numcheatvars ; i++, var++)
		if ( strcmp (var->var->string, var->value) )
			Cvar_Set (var->name, var->value);
}


void CL_SendCommand ()
{
	// get new key events
	Sys_SendKeyEvents ();

	// allow mice or other external controllers to add commands
	IN_Commands ();

	// process console commands
	Cbuf_Execute ();

	if(!cl.attractloop)			/// Berserker: не отменять читы для дёмок
		CL_FixCvarCheats ();	// fix any cheating cvars

	// send intentions now
	CL_SendCmd ();

	// resend a connection request if necessary
	CL_CheckForResend ();
}


/*
=================
CL_PredictMovement

Sets cl.predicted_origin and cl.predicted_angles
=================
*/
void CL_PredictMovement ()
{
	int			ack, current;
	int			frame;
	int			oldframe;
	usercmd_t	*cmd;
	pmove_t		pm;
	int			i;
	int			step;
	int			oldz;

	if (cls.state != ca_active)
		return;

	if (cl_paused->value)
		return;

	if (!cl_predict->value || (cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION))
	{	// just set angles
		if (old_mouse_support)
		{
			for (i=0 ; i<3 ; i++)
			{
				cl.predicted_angles[i] = cl.viewangles[i] + SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[i]);
			}
		}
		else
		{
			cl.predicted_angles[0] = cl.viewangles_PITCH + SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[0]);
			cl.predicted_angles[1] = cl.viewangles_YAW + SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[1]);
			cl.predicted_angles[2] = SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[2]);
		}
		return;
	}

	ack = cls.netchan.incoming_acknowledged;
	current = cls.netchan.outgoing_sequence;

	// if we are too far out of date, just freeze
	if (current - ack >= CMD_BACKUP)
	{
		if (cl_showmiss->value)
			Com_Printf ("exceeded CMD_BACKUP\n");
		return;	
	}

	// copy current state to pmove
	Memset (&pm, 0, sizeof(pm));
	pm.trace = CL_PMTrace;
	pm.pointcontents = CL_PMpointcontents;

	pm_airaccelerate = atof(cl.configstrings[CS_AIRACCEL]);

	pm.s = cl.frame.playerstate.pmove;

	frame = 0;

	// run frames
	while (++ack < current)
	{
		frame = ack & (CMD_BACKUP-1);
		cmd = &cl.cmds[frame];

		pm.cmd = *cmd;
		Pmove (&pm);

		// save for debug checking
		VectorCopy (pm.s.origin, cl.predicted_origins[frame]);
	}

	oldframe = (ack-2) & (CMD_BACKUP-1);
	oldz = cl.predicted_origins[oldframe][2];
	step = pm.s.origin[2] - oldz;
	if (step > 63 && step < 160 && (pm.s.pm_flags & PMF_ON_GROUND) )
	{
		cl.predicted_step = step * 0.125;
		cl.predicted_step_time = cls.realtime - cls.frametime * 500;
	}


	// copy results out for rendering
	cl.predicted_origin[0] = pm.s.origin[0]*0.125;
	cl.predicted_origin[1] = pm.s.origin[1]*0.125;
	cl.predicted_origin[2] = pm.s.origin[2]*0.125;

	VectorCopy (pm.viewangles, cl.predicted_angles);
}


int	ParseSP3header(model_t *mod, char *s)
{
	char	*token;
	int		numframes = 0;
	bool	it_spr3 = false;
	byte	cond = 0;

	while (s)
	{
		if (cond==3)
			break;
		token = COM_Parse (&s);
		if (!Q_strcasecmp(token, "frames"))
		{
			numframes = atoi(COM_Parse (&s));
			cond |= 1;
			continue;
		}
		if (!strcmp(token, "IDS3"))
		{
			it_spr3 = true;
			cond |= 2;
			continue;
		}
	}

	if(!it_spr3)
		Com_Error (ERR_DROP, "%s is not SP3 sprite", mod->name);

	return numframes;
}


float ParseSP3frames(model_t *mod, dsprite2_t *sprout, char *s, float *sc, int *flags)
{
	char	*token;
	int		i = 0;
	float	scale = 1;

	while (s)
	{
		token = COM_Parse (&s);

		if (!strcmp(token, "IDS3"))
			continue;

		if (!Q_strcasecmp(token, "frames"))
		{
			COM_Parse (&s);
			continue;
		}

		if (!Q_strcasecmp(token, "timescale"))
		{
			scale = atof(COM_Parse (&s));
			continue;
		}

		if (!Q_strcasecmp(token, "skipDiffuse"))
		{
			sprout->skipDiffuse = true;
			continue;
		}

		if (!Q_strcasecmp(token, "directed"))
		{
			sprout->directed = true;
			sprout->directedScale = atof(COM_Parse (&s));
			continue;
		}

		if (!Q_strcasecmp(token, "imagescale"))
		{
			*sc = atof(COM_Parse (&s));
			continue;
		}

		Memcpy(sprout->frames[i].name, token, strlen(token));
		sprout->frames[i].skin = GL_FindImage (sprout->frames[i].name, it_sprite, false, 0, false, 0);	/// !!! Для спрайтов не бывает бампа!!!
		// не нашли, попробуем взять шкуру используя путь до спрайта
		if (!sprout->frames[i].skin)
		{
			char path[MAX_QPATH], *file, name[MAX_OSPATH];
			COM_FilePath(mod->name, path);
			file = COM_SkipPath(sprout->frames[i].name);
			Com_sprintf(name, sizeof(name), "%s/%s", path, file);
			sprout->frames[i].skin = GL_FindImage (name, it_sprite, false, 0, false, 0);	/// !!! Для спрайтов не бывает бампа!!!
		}
		if(sprout->frames[i].skin)
		{
			if (sprout->frames[i].skin->fx == fx_distort)
				*flags |= RF_DISTORT_SKIN;	/// признак того, что хоть один скин с дисторт-эффектом
			sprout->frames[i].width = sprout->frames[i].skin->width;
			sprout->frames[i].height = sprout->frames[i].skin->height;
			sprout->frames[i].origin_x = sprout->frames[i].width/2;
			sprout->frames[i].origin_y = sprout->frames[i].height/2;
			i++;
		}
	}

	if(i!=sprout->numframes)
		Com_Error (ERR_DROP, "%s has incorrect number or frames (%i)", mod->name, sprout->numframes);

	return scale;
}


void Mod_LoadSprite2Model (model_t *mod, void *buffer, bool cache)
{
	dsprite2_t	*sprout;
	int			i, numframes;
	float		imagescale = 1;

	numframes = ParseSP3header(mod, (char *)buffer);
	if(numframes<=0)
		Com_Error (ERR_DROP, "%s has wrong number of frames (%i)", mod->name, numframes);

	sprout = (dsprite2_t *) Hunk_Alloc (sizeof(dspr2frame_t)*numframes + sizeof(dsprite2_t), true);

	if (cache)
	{
		caching_calc = true;
		return;
	}

	sprout->skipDiffuse = sprout->directed = false;
	sprout->numframes = numframes;
	int flags = 0;
	sprout->timescale = ParseSP3frames(mod, sprout, (char *)buffer, &imagescale, &flags);
	mod->flags = flags;

	for(i=0; i<numframes; i++)
	{
		sprout->frames[i].width *= imagescale;
		sprout->frames[i].height *= imagescale;
		sprout->frames[i].origin_x *= imagescale;
		sprout->frames[i].origin_y *= imagescale;
	}

	mod->numframes = numframes;
	mod->type = mod_sprite2;
}


void R_BeginRegistration (char *model)
{
	char	fullname[MAX_QPATH];
	cvar_t	*flushmap;

	registration_sequence++;
	r_oldviewcluster = -1;		// force markleafs

	Com_sprintf (fullname, sizeof(fullname), "maps/%s.bsp", model);

	// explicitly free the old map if different
	// this guarantees that mod_known[0] is the world map
	flushmap = Cvar_Get ("flushmap", "0", 0);
	if ( strcmp(mod_known[0].name, fullname) || flushmap->value)
		Mod_Free (&mod_known[0]);
	r_worldmodel = Mod_ForName(fullname, true, 1, false, false);

	int	max_images;
	if (net_compatibility->value)
		max_images = MAX_IMAGES_Q2;
	else
		max_images = MAX_IMAGES_BERS;
	for (int i=0 ; i<max_images ; i++)
		hud_model_test[i] = false;

	r_viewcluster = -1;
}


void CL_RegisterTEntModels ()
{
	cl_mod_explode = R_RegisterModel ("models/objects/explode/tris.md2", 1, false);
	cl_mod_smoke = R_RegisterModel ("models/objects/smoke/tris.md2", 1, false);
	cl_mod_parasite_segment = R_RegisterModel ("models/monsters/parasite/segment/tris.md2", 1, false);
	cl_mod_grapple_cable = R_RegisterModel ("models/ctf/segment/tris.md2", 1, false);
	cl_mod_flash = R_RegisterModel ("models/objects/flash/tris.md2", 1, false);
	cl_mod_parasite_tip = R_RegisterModel ("models/monsters/parasite/tip/tris.md2", 1, false);
	cl_mod_explo4 = R_RegisterModel ("models/objects/r_explode/tris.md2", 1, false);
	cl_mod_bfg_explo = R_RegisterModel ("sprites/s_bfg2.sp2", 1, false);
	cl_mod_lightning = R_RegisterModel ("models/proj/lightning/tris.md2", 1, false);
	cl_mod_powerscreen = R_RegisterModel ("models/items/armor/effect/tris.md2", 1, false);

//ROGUE
	cl_mod_explo4_big = R_RegisterModel ("models/objects/r_explode2/tris.md2", 1, false);
	cl_mod_lightning = R_RegisterModel ("models/proj/lightning/tris.md2", 1, false);
	cl_mod_heatbeam = R_RegisterModel ("models/proj/beam/tris.md2", 1, false);
	cl_mod_monster_heatbeam = R_RegisterModel ("models/proj/widowbeam/tris.md2", 1, false);
//ROGUE

/// Berserker: заранее кэшируем модели во избежание тормоза на новой карте при изменении FOV
	char scratch[MAX_QPATH];
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/tris.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/a_grenades.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/w_bfg.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/w_blaster.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/w_chaingun.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/w_glauncher.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/w_hyperblaster.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/w_machinegun.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/w_railgun.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/w_rlauncher.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/w_shotgun.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/w_sshotgun.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );
	Com_sprintf( scratch, sizeof( scratch ), "players/%s/weapon.md2", gender_model/*gender->string*/ );
	R_RegisterModel( scratch, 1, false );

	cl_mod_exp = R_RegisterModel ("sprites/s_exp.sp2", 1, false);
	cl_mod_rlexp = R_RegisterModel ("sprites/s_rlboom.sp2", 1, false);
	R_RegisterModel ("sprites/s_bfg1.sp2", 1, false);
	R_RegisterModel ("sprites/s_bfg2.sp2", 1, false);
	R_RegisterModel ("sprites/s_bfg3.sp2", 1, false);

	R_RegisterModel ("models/objects/rocket/tris.md2", 1, false);
	R_RegisterModel ("models/objects/grenade/tris.md2", 1, false);
	R_RegisterModel ("models/objects/grenade2/tris.md2", 1, false);

///		R_RegisterModel ("models/objects/laser/tris.md2", 1, false);
		cl_mod_laserbolt = R_RegisterModel ("sprites/laser/bolt.sp3", 1, false);
		cl_mod_beam = R_RegisterModel ("sprites/beam/beam.sp3", 1, false);

//	R_RegisterModel ("models/objects/gibs/bone/tris.md2", 1, false);
//	R_RegisterModel ("models/objects/gibs/sm_meat/tris.md2", 1, false);
///	R_RegisterModel ("models/objects/gibs/bone2/tris.md2", 1, false);	// not used?

	cl_mod_mshell = R_RegisterModel ("models/shells/m_shell.md2", 1, false);
	cl_mod_sshell = R_RegisterModel ("models/shells/s_shell.md2", 1, false);
	cl_mod_debris1 = R_RegisterModel ("models/objects/debris1/tris.md2", 1, false);
	cl_mod_debris2 = R_RegisterModel ("models/objects/debris2/tris.md2", 1, false);
	cl_mod_debris3 = R_RegisterModel ("models/objects/debris3/tris.md2", 1, false);
	cl_mod_debris1_glass = R_RegisterModel ("models/objects/debris1/glass.md2", 1, false);
	cl_mod_debris2_glass = R_RegisterModel ("models/objects/debris2/glass.md2", 1, false);
	cl_mod_debris3_glass = R_RegisterModel ("models/objects/debris3/glass.md2", 1, false);

	cl_mod_head2 = R_RegisterModel ("models/objects/gibs/head2/tris.md2", 1, false);
	cl_mod_meat = R_RegisterModel ("models/objects/gibs/sm_meat/tris.md2", 1, false);
	cl_mod_chest = R_RegisterModel ("models/objects/gibs/chest/tris.md2", 1, false);
	cl_mod_bone = R_RegisterModel ("models/objects/gibs/bone/tris.md2", 1, false);
	cl_mod_gear = R_RegisterModel ("models/objects/gibs/gear/tris.md2", 1, false);
	cl_mod_metal = R_RegisterModel ("models/objects/gibs/sm_metal/tris.md2", 1, false);
	cl_mod_arm = R_RegisterModel ("models/objects/gibs/arm/tris.md2", 1, false);
	cl_mod_leg = R_RegisterModel ("models/objects/gibs/leg/tris.md2", 1, false);
	cl_mod_head = R_RegisterModel ("models/objects/gibs/head/tris.md2", 1, false);

	if (r_personal_gibs->value)
	{
		// Gladiator's gibs
		cl_mod_gladiator_g1 = R_RegisterModel ("models/monsters/gladiatr/gibs/g1.md3", 1, false);
		if (!cl_mod_gladiator_g1)
			cl_mod_gladiator_g1 = cl_mod_chest;
		cl_mod_gladiator_g2 = R_RegisterModel ("models/monsters/gladiatr/gibs/g2.md3", 1, false);
		if (!cl_mod_gladiator_g2)
			cl_mod_gladiator_g2 = cl_mod_gear;
		cl_mod_gladiator_g3 = R_RegisterModel ("models/monsters/gladiatr/gibs/g3.md3", 1, false);
		if (!cl_mod_gladiator_g3)
			cl_mod_gladiator_g3 = cl_mod_leg;
		cl_mod_gladiator_g4 = R_RegisterModel ("models/monsters/gladiatr/gibs/g4.md3", 1, false);
		if (!cl_mod_gladiator_g4)
			cl_mod_gladiator_g4 = cl_mod_leg;
		cl_mod_gladiator_g5 = R_RegisterModel ("models/monsters/gladiatr/gibs/g5.md3", 1, false);
		if (!cl_mod_gladiator_g5)
			cl_mod_gladiator_g5 = cl_mod_arm;
		cl_mod_gladiator_g6 = R_RegisterModel ("models/monsters/gladiatr/gibs/g6.md3", 1, false);
		if (!cl_mod_gladiator_g6)
			cl_mod_gladiator_g6 = cl_mod_metal;
		cl_mod_gladiator_g7 = R_RegisterModel ("models/monsters/gladiatr/gibs/g7.md3", 1, false);
		if (!cl_mod_gladiator_g7)
			cl_mod_gladiator_g7 = cl_mod_metal;

		// Infantry's gibs
		cl_mod_infantry_g1 = R_RegisterModel ("models/monsters/infantry/gibs/g1.md3", 1, false);
		if (!cl_mod_infantry_g1)
			cl_mod_infantry_g1 = cl_mod_arm;
		cl_mod_infantry_g2 = R_RegisterModel ("models/monsters/infantry/gibs/g2.md3", 1, false);
		if (!cl_mod_infantry_g2)
			cl_mod_infantry_g2 = cl_mod_chest;
		cl_mod_infantry_g3 = R_RegisterModel ("models/monsters/infantry/gibs/g3.md3", 1, false);
		if (!cl_mod_infantry_g3)
			cl_mod_infantry_g3 = cl_mod_meat;
		cl_mod_infantry_g4 = R_RegisterModel ("models/monsters/infantry/gibs/g4.md3", 1, false);
		if (!cl_mod_infantry_g4)
			cl_mod_infantry_g4 = cl_mod_metal;
		cl_mod_infantry_g5 = R_RegisterModel ("models/monsters/infantry/gibs/g5.md3", 1, false);
		if (!cl_mod_infantry_g5)
			cl_mod_infantry_g5 = cl_mod_gear;
		cl_mod_infantry_g6 = R_RegisterModel ("models/monsters/infantry/gibs/g6.md3", 1, false);
		if (!cl_mod_infantry_g6)
			cl_mod_infantry_g6 = cl_mod_leg;
		cl_mod_infantry_g7 = R_RegisterModel ("models/monsters/infantry/gibs/g7.md3", 1, false);
		if (!cl_mod_infantry_g7)
			cl_mod_infantry_g7 = cl_mod_leg;

		// Flipper's gibs
		cl_mod_flipper_g1 = R_RegisterModel ("models/monsters/flipper/gibs/g1.md3", 1, false);
		if (!cl_mod_flipper_g1)
			cl_mod_flipper_g1 = cl_mod_meat;
		cl_mod_flipper_g2 = R_RegisterModel ("models/monsters/flipper/gibs/g2.md3", 1, false);
		if (!cl_mod_flipper_g2)
			cl_mod_flipper_g2 = cl_mod_meat;
		cl_mod_flipper_g3 = R_RegisterModel ("models/monsters/flipper/gibs/g3.md3", 1, false);
		if (!cl_mod_flipper_g3)
			cl_mod_flipper_g3 = cl_mod_bone;
		cl_mod_flipper_g4 = R_RegisterModel ("models/monsters/flipper/gibs/g4.md3", 1, false);
		if (!cl_mod_flipper_g4)
			cl_mod_flipper_g4 = cl_mod_bone;

		// Mutant's gibs
		cl_mod_mutant_g1 = R_RegisterModel ("models/monsters/mutant/gibs/g1.md3", 1, false);
		if (!cl_mod_mutant_g1)
			cl_mod_mutant_g1 = cl_mod_chest;
		cl_mod_mutant_g2 = R_RegisterModel ("models/monsters/mutant/gibs/g2.md3", 1, false);
		if (!cl_mod_mutant_g2)
			cl_mod_mutant_g2 = cl_mod_meat;
		cl_mod_mutant_g3 = R_RegisterModel ("models/monsters/mutant/gibs/g3.md3", 1, false);
		if (!cl_mod_mutant_g3)
			cl_mod_mutant_g3 = cl_mod_meat;
		cl_mod_mutant_g4 = R_RegisterModel ("models/monsters/mutant/gibs/g4.md3", 1, false);
		if (!cl_mod_mutant_g4)
			cl_mod_mutant_g4 = cl_mod_meat;
		cl_mod_mutant_g5 = R_RegisterModel ("models/monsters/mutant/gibs/g5.md3", 1, false);
		if (!cl_mod_mutant_g5)
			cl_mod_mutant_g5 = cl_mod_meat;
		cl_mod_mutant_g6 = R_RegisterModel ("models/monsters/mutant/gibs/g6.md3", 1, false);
		if (!cl_mod_mutant_g6)
			cl_mod_mutant_g6 = cl_mod_meat;

		// Hover's gibs
		cl_mod_hover_g1 = R_RegisterModel ("models/monsters/hover/gibs/g1.md3", 1, false);
		if (!cl_mod_hover_g1)
			cl_mod_hover_g1 = cl_mod_chest;
		cl_mod_hover_g2 = R_RegisterModel ("models/monsters/hover/gibs/g2.md3", 1, false);
		if (!cl_mod_hover_g2)
			cl_mod_hover_g2 = cl_mod_leg;
		cl_mod_hover_g3 = R_RegisterModel ("models/monsters/hover/gibs/g3.md3", 1, false);
		if (!cl_mod_hover_g3)
			cl_mod_hover_g3 = cl_mod_leg;
		cl_mod_hover_g4 = R_RegisterModel ("models/monsters/hover/gibs/g4.md3", 1, false);
		if (!cl_mod_hover_g4)
			cl_mod_hover_g4 = cl_mod_metal;
		cl_mod_hover_g5 = R_RegisterModel ("models/monsters/hover/gibs/g5.md3", 1, false);
		if (!cl_mod_hover_g5)
			cl_mod_hover_g5 = cl_mod_metal;
		cl_mod_hover_g6 = R_RegisterModel ("models/monsters/hover/gibs/g6.md3", 1, false);
		if (!cl_mod_hover_g6)
			cl_mod_hover_g6 = cl_mod_gear;

		// Gunner's gibs
		cl_mod_gunner_g1 = R_RegisterModel ("models/monsters/gunner/gibs/g1.md3", 1, false);
		if (!cl_mod_gunner_g1)
			cl_mod_gunner_g1 = cl_mod_chest;
		cl_mod_gunner_g2 = R_RegisterModel ("models/monsters/gunner/gibs/g2.md3", 1, false);
		if (!cl_mod_gunner_g2)
			cl_mod_gunner_g2 = cl_mod_bone;
		cl_mod_gunner_g3 = R_RegisterModel ("models/monsters/gunner/gibs/g3.md3", 1, false);
		if (!cl_mod_gunner_g3)
			cl_mod_gunner_g3 = cl_mod_leg;
		cl_mod_gunner_g4 = R_RegisterModel ("models/monsters/gunner/gibs/g4.md3", 1, false);
		if (!cl_mod_gunner_g4)
			cl_mod_gunner_g4 = cl_mod_leg;
		cl_mod_gunner_g5 = R_RegisterModel ("models/monsters/gunner/gibs/g5.md3", 1, false);
		if (!cl_mod_gunner_g5)
			cl_mod_gunner_g5 = cl_mod_leg;
		cl_mod_gunner_g6 = R_RegisterModel ("models/monsters/gunner/gibs/g6.md3", 1, false);
		if (!cl_mod_gunner_g6)
			cl_mod_gunner_g6 = cl_mod_leg;
		cl_mod_gunner_g7 = R_RegisterModel ("models/monsters/gunner/gibs/g7.md3", 1, false);
		if (!cl_mod_gunner_g7)
			cl_mod_gunner_g7 = cl_mod_arm;
		cl_mod_gunner_g8 = R_RegisterModel ("models/monsters/gunner/gibs/g8.md3", 1, false);
		if (!cl_mod_gunner_g8)
			cl_mod_gunner_g8 = cl_mod_gear;
		cl_mod_gunner_g9 = R_RegisterModel ("models/monsters/gunner/gibs/g9.md3", 1, false);
		if (!cl_mod_gunner_g9)
			cl_mod_gunner_g9 = cl_mod_metal;

		// Floater's gibs
		cl_mod_floater_g1 = R_RegisterModel ("models/monsters/float/gibs/g1.md3", 1, false);
		if (!cl_mod_floater_g1)
			cl_mod_floater_g1 = cl_mod_chest;
		cl_mod_floater_g2 = R_RegisterModel ("models/monsters/float/gibs/g2.md3", 1, false);
		if (!cl_mod_floater_g2)
			cl_mod_floater_g2 = cl_mod_metal;
		cl_mod_floater_g3 = R_RegisterModel ("models/monsters/float/gibs/g3.md3", 1, false);
		if (!cl_mod_floater_g3)
			cl_mod_floater_g3 = cl_mod_metal;
		cl_mod_floater_g4 = R_RegisterModel ("models/monsters/float/gibs/g4.md3", 1, false);
		if (!cl_mod_floater_g4)
			cl_mod_floater_g4 = cl_mod_gear;
		cl_mod_floater_g5 = R_RegisterModel ("models/monsters/float/gibs/g5.md3", 1, false);
		if (!cl_mod_floater_g5)
			cl_mod_floater_g5 = cl_mod_meat;
		cl_mod_floater_g6 = R_RegisterModel ("models/monsters/float/gibs/g6.md3", 1, false);
		if (!cl_mod_floater_g6)
			cl_mod_floater_g6 = cl_mod_metal;

		// Flyer's gibs
		cl_mod_flyer_g1 = R_RegisterModel ("models/monsters/flyer/gibs/g1.md3", 1, false);
		if (!cl_mod_flyer_g1)
			cl_mod_flyer_g1 = cl_mod_chest;
		cl_mod_flyer_g2 = R_RegisterModel ("models/monsters/flyer/gibs/g2.md3", 1, false);
		if (!cl_mod_flyer_g2)
			cl_mod_flyer_g2 = cl_mod_metal;
		cl_mod_flyer_g3 = R_RegisterModel ("models/monsters/flyer/gibs/g3.md3", 1, false);
		if (!cl_mod_flyer_g3)
			cl_mod_flyer_g3 = cl_mod_bone;
		cl_mod_flyer_g4 = R_RegisterModel ("models/monsters/flyer/gibs/g4.md3", 1, false);
		if (!cl_mod_flyer_g4)
			cl_mod_flyer_g4 = cl_mod_meat;

		// Soldier's gibs  (нельзя подставлять ку2 модель если основная модель не найдена - это даст косяк: ку2 модель + шкура для основной модели)
		cl_mod_soldier_g1 = R_RegisterModel ("models/monsters/soldier/gibs/g1.md3", 1, false);
		cl_mod_soldier_g2 = R_RegisterModel ("models/monsters/soldier/gibs/g2.md3", 1, false);
		cl_mod_soldier_g3 = R_RegisterModel ("models/monsters/soldier/gibs/g3.md3", 1, false);
		cl_mod_soldier_g4 = R_RegisterModel ("models/monsters/soldier/gibs/g4.md3", 1, false);
		cl_mod_soldier_g5 = R_RegisterModel ("models/monsters/soldier/gibs/g5.md3", 1, false);
		cl_mod_soldier_g6 = R_RegisterModel ("models/monsters/soldier/gibs/g6.md3", 1, false);
		cl_mod_soldier_g7 = R_RegisterModel ("models/monsters/soldier/gibs/g7.md3", 1, false);
		cl_mod_soldier_g8 = R_RegisterModel ("models/monsters/soldier/gibs/g8.md3", 1, false);
		cl_mod_soldier_g9 = R_RegisterModel ("models/monsters/soldier/gibs/g9.md3", 1, false);

		// Tank's gibs
		cl_mod_tank_g1 = R_RegisterModel ("models/monsters/tank/gibs/g1.md3", 1, false);
		cl_mod_tank_g2 = R_RegisterModel ("models/monsters/tank/gibs/g2.md3", 1, false);
		cl_mod_tank_g3 = R_RegisterModel ("models/monsters/tank/gibs/g3.md3", 1, false);
		cl_mod_tank_g4 = R_RegisterModel ("models/monsters/tank/gibs/g4.md3", 1, false);
		cl_mod_tank_g5 = R_RegisterModel ("models/monsters/tank/gibs/g5.md3", 1, false);
		cl_mod_tank_g6 = R_RegisterModel ("models/monsters/tank/gibs/g6.md3", 1, false);
		cl_mod_tank_g7 = R_RegisterModel ("models/monsters/tank/gibs/g7.md3", 1, false);
		cl_mod_tank_g8 = R_RegisterModel ("models/monsters/tank/gibs/g8.md3", 1, false);
		cl_mod_tank_g9 = R_RegisterModel ("models/monsters/tank/gibs/g9.md3", 1, false);

		// Insane's gibs
		cl_mod_insane_g1 = R_RegisterModel ("models/monsters/insane/gibs/g1.md3", 1, false);
		cl_mod_insane_g2 = R_RegisterModel ("models/monsters/insane/gibs/g2.md3", 1, false);
		cl_mod_insane_g3 = R_RegisterModel ("models/monsters/insane/gibs/g3.md3", 1, false);
		cl_mod_insane_g4 = R_RegisterModel ("models/monsters/insane/gibs/g4.md3", 1, false);
		cl_mod_insane_g5 = R_RegisterModel ("models/monsters/insane/gibs/g5.md3", 1, false);
		cl_mod_insane_g6 = R_RegisterModel ("models/monsters/insane/gibs/g6.md3", 1, false);
		cl_mod_insane_g7 = R_RegisterModel ("models/monsters/insane/gibs/g7.md3", 1, false);
		cl_mod_insane_g8 = R_RegisterModel ("models/monsters/insane/gibs/g8.md3", 1, false);

		// Barrel's debris
		cl_mod_barrel_g1 = R_RegisterModel ("models/objects/barrels/debris/g1.md3", 1, false);
		cl_mod_barrel_g2 = R_RegisterModel ("models/objects/barrels/debris/g2.md3", 1, false);
		cl_mod_barrel_g3 = R_RegisterModel ("models/objects/barrels/debris/g3.md3", 1, false);
		cl_mod_barrel_g4 = R_RegisterModel ("models/objects/barrels/debris/g4.md3", 1, false);

		// Male's gibs
		cl_mod_male_g1 = R_RegisterModel ("players/male/gibs/g1.md3", 1, false);
		cl_mod_male_g2 = R_RegisterModel ("players/male/gibs/g2.md3", 1, false);
		cl_mod_male_g3 = R_RegisterModel ("players/male/gibs/g3.md3", 1, false);
		cl_mod_male_g4 = R_RegisterModel ("players/male/gibs/g4.md3", 1, false);
		cl_mod_male_g5 = R_RegisterModel ("players/male/gibs/g5.md3", 1, false);
		cl_mod_male_g6 = R_RegisterModel ("players/male/gibs/g6.md3", 1, false);
		cl_mod_male_g7 = R_RegisterModel ("players/male/gibs/g7.md3", 1, false);
		cl_mod_male_g8 = R_RegisterModel ("players/male/gibs/g8.md3", 1, false);

		// Female's gibs
		cl_mod_female_g1 = R_RegisterModel ("players/female/gibs/g1.md3", 1, false);
		cl_mod_female_g2 = R_RegisterModel ("players/female/gibs/g2.md3", 1, false);
		cl_mod_female_g3 = R_RegisterModel ("players/female/gibs/g3.md3", 1, false);
		cl_mod_female_g4 = R_RegisterModel ("players/female/gibs/g4.md3", 1, false);
		cl_mod_female_g5 = R_RegisterModel ("players/female/gibs/g5.md3", 1, false);
		cl_mod_female_g6 = R_RegisterModel ("players/female/gibs/g6.md3", 1, false);
		cl_mod_female_g7 = R_RegisterModel ("players/female/gibs/g7.md3", 1, false);
		cl_mod_female_g8 = R_RegisterModel ("players/female/gibs/g8.md3", 1, false);
		cl_mod_female_g9 = R_RegisterModel ("players/female/gibs/g9.md3", 1, false);

		// Cyborg's gibs
		cl_mod_cyborg_g1 = R_RegisterModel ("players/cyborg/gibs/g1.md3", 1, false);
		cl_mod_cyborg_g2 = R_RegisterModel ("players/cyborg/gibs/g2.md3", 1, false);
		cl_mod_cyborg_g3 = R_RegisterModel ("players/cyborg/gibs/g3.md3", 1, false);
		cl_mod_cyborg_g4 = R_RegisterModel ("players/cyborg/gibs/g4.md3", 1, false);
		cl_mod_cyborg_g5 = R_RegisterModel ("players/cyborg/gibs/g5.md3", 1, false);
		cl_mod_cyborg_g6 = R_RegisterModel ("players/cyborg/gibs/g6.md3", 1, false);
		cl_mod_cyborg_g7 = R_RegisterModel ("players/cyborg/gibs/g7.md3", 1, false);
		cl_mod_cyborg_g8 = R_RegisterModel ("players/cyborg/gibs/g8.md3", 1, false);
		cl_mod_cyborg_g9 = R_RegisterModel ("players/cyborg/gibs/g9.md3", 1, false);
		cl_mod_cyborg_g10 = R_RegisterModel ("players/cyborg/gibs/g10.md3", 1, false);

		// Brain's gibs
		cl_mod_brain_g1 = R_RegisterModel ("models/monsters/brain/gibs/g1.md3", 1, false);
		if (!cl_mod_brain_g1)
			cl_mod_brain_g1 = cl_mod_leg;
		cl_mod_brain_g2 = R_RegisterModel ("models/monsters/brain/gibs/g2.md3", 1, false);
		if (!cl_mod_brain_g2)
			cl_mod_brain_g2 = cl_mod_bone;
		cl_mod_brain_g3 = R_RegisterModel ("models/monsters/brain/gibs/g3.md3", 1, false);
		if (!cl_mod_brain_g3)
			cl_mod_brain_g3 = cl_mod_meat;
		cl_mod_brain_g4 = R_RegisterModel ("models/monsters/brain/gibs/g4.md3", 1, false);
		if (!cl_mod_brain_g4)
			cl_mod_brain_g4 = cl_mod_leg;
		cl_mod_brain_g5 = R_RegisterModel ("models/monsters/brain/gibs/g5.md3", 1, false);
		if (!cl_mod_brain_g5)
			cl_mod_brain_g5 = cl_mod_gear;
		cl_mod_brain_g6 = R_RegisterModel ("models/monsters/brain/gibs/g6.md3", 1, false);
		if (!cl_mod_brain_g6)
			cl_mod_brain_g6 = cl_mod_metal;
		cl_mod_brain_g7 = R_RegisterModel ("models/monsters/brain/gibs/g7.md3", 1, false);
		if (!cl_mod_brain_g7)
			cl_mod_brain_g7 = cl_mod_chest;
		cl_mod_brain_g8 = R_RegisterModel ("models/monsters/brain/gibs/g8.md3", 1, false);
		if (!cl_mod_brain_g8)
			cl_mod_brain_g8 = cl_mod_arm;
		cl_mod_brain_g9 = R_RegisterModel ("models/monsters/brain/gibs/g9.md3", 1, false);
		if (!cl_mod_brain_g9)
			cl_mod_brain_g9 = cl_mod_metal;
		cl_mod_brain_g10 = R_RegisterModel ("models/monsters/brain/gibs/g10.md3", 1, false);
		if (!cl_mod_brain_g10)
			cl_mod_brain_g10 = cl_mod_arm;

		// Parasite's gibs
		cl_mod_parasite_g1 = R_RegisterModel ("models/monsters/parasite/gibs/g1.md3", 1, false);
		if (!cl_mod_parasite_g1)
			cl_mod_parasite_g1 = cl_mod_chest;
		cl_mod_parasite_g2 = R_RegisterModel ("models/monsters/parasite/gibs/g2.md3", 1, false);
		if (!cl_mod_parasite_g2)
			cl_mod_parasite_g2 = cl_mod_arm;
		cl_mod_parasite_g3 = R_RegisterModel ("models/monsters/parasite/gibs/g3.md3", 1, false);
		if (!cl_mod_parasite_g3)
			cl_mod_parasite_g3 = cl_mod_arm;
		cl_mod_parasite_g4 = R_RegisterModel ("models/monsters/parasite/gibs/g4.md3", 1, false);
		if (!cl_mod_parasite_g4)
			cl_mod_parasite_g4 = cl_mod_bone;
		cl_mod_parasite_g5 = R_RegisterModel ("models/monsters/parasite/gibs/g5.md3", 1, false);
		if (!cl_mod_parasite_g5)
			cl_mod_parasite_g5 = cl_mod_metal;
		cl_mod_parasite_g6 = R_RegisterModel ("models/monsters/parasite/gibs/g6.md3", 1, false);
		if (!cl_mod_parasite_g6)
			cl_mod_parasite_g6 = cl_mod_gear;
		cl_mod_parasite_g7 = R_RegisterModel ("models/monsters/parasite/gibs/g7.md3", 1, false);
		if (!cl_mod_parasite_g7)
			cl_mod_parasite_g7 = cl_mod_metal;

		// Medic's gibs
		cl_mod_medic_g1 = R_RegisterModel ("models/monsters/medic/gibs/g1.md3", 1, false);
		if (!cl_mod_medic_g1)
			cl_mod_medic_g1 = cl_mod_chest;
		cl_mod_medic_g2 = R_RegisterModel ("models/monsters/medic/gibs/g2.md3", 1, false);
		if (!cl_mod_medic_g2)
			cl_mod_medic_g2 = cl_mod_bone;
		cl_mod_medic_g3 = R_RegisterModel ("models/monsters/medic/gibs/g3.md3", 1, false);
		if (!cl_mod_medic_g3)
			cl_mod_medic_g3 = cl_mod_bone;
		cl_mod_medic_g4 = R_RegisterModel ("models/monsters/medic/gibs/g4.md3", 1, false);
		if (!cl_mod_medic_g4)
			cl_mod_medic_g4 = cl_mod_gear;
		cl_mod_medic_g5 = R_RegisterModel ("models/monsters/medic/gibs/g5.md3", 1, false);
		if (!cl_mod_medic_g5)
			cl_mod_medic_g5 = cl_mod_meat;
		cl_mod_medic_g6 = R_RegisterModel ("models/monsters/medic/gibs/g6.md3", 1, false);
		if (!cl_mod_medic_g6)
			cl_mod_medic_g6 = cl_mod_gear;
		cl_mod_medic_g7 = R_RegisterModel ("models/monsters/medic/gibs/g7.md3", 1, false);
		if (!cl_mod_medic_g7)
			cl_mod_medic_g7 = cl_mod_gear;
		cl_mod_medic_g8 = R_RegisterModel ("models/monsters/medic/gibs/g8.md3", 1, false);
		if (!cl_mod_medic_g8)
			cl_mod_medic_g8 = cl_mod_metal;
		cl_mod_medic_g9 = R_RegisterModel ("models/monsters/medic/gibs/g9.md3", 1, false);
		if (!cl_mod_medic_g9)
			cl_mod_medic_g9 = cl_mod_metal;
		cl_mod_medic_g10 = R_RegisterModel ("models/monsters/medic/gibs/g10.md3", 1, false);
		if (!cl_mod_medic_g10)
			cl_mod_medic_g10 = cl_mod_gear;

		// Berserk's gibs
		cl_mod_berserk_g1 = R_RegisterModel ("models/monsters/berserk/gibs/g1.md3", 1, false);
		if (!cl_mod_berserk_g1)
			cl_mod_berserk_g1 = cl_mod_arm;
		cl_mod_berserk_g2 = R_RegisterModel ("models/monsters/berserk/gibs/g2.md3", 1, false);
		if (!cl_mod_berserk_g2)
			cl_mod_berserk_g2 = cl_mod_chest;
		cl_mod_berserk_g3 = R_RegisterModel ("models/monsters/berserk/gibs/g3.md3", 1, false);
		if (!cl_mod_berserk_g3)
			cl_mod_berserk_g3 = cl_mod_bone;
		cl_mod_berserk_g4 = R_RegisterModel ("models/monsters/berserk/gibs/g4.md3", 1, false);
		if (!cl_mod_berserk_g4)
			cl_mod_berserk_g4 = cl_mod_arm;
		cl_mod_berserk_g5 = R_RegisterModel ("models/monsters/berserk/gibs/g5.md3", 1, false);
		if (!cl_mod_berserk_g5)
			cl_mod_berserk_g5 = cl_mod_bone;
		cl_mod_berserk_g6 = R_RegisterModel ("models/monsters/berserk/gibs/g6.md3", 1, false);
		if (!cl_mod_berserk_g6)
			cl_mod_berserk_g6 = cl_mod_leg;
		cl_mod_berserk_g7 = R_RegisterModel ("models/monsters/berserk/gibs/g7.md3", 1, false);
		if (!cl_mod_berserk_g7)
			cl_mod_berserk_g7 = cl_mod_bone;
		cl_mod_berserk_g8 = R_RegisterModel ("models/monsters/berserk/gibs/g8.md3", 1, false);
		if (!cl_mod_berserk_g8)
			cl_mod_berserk_g8 = cl_mod_leg;

		// Chick's gibs
		cl_mod_chick_g1 = R_RegisterModel ("models/monsters/bitch/gibs/g1.md3", 1, false);
		if (!cl_mod_chick_g1)
			cl_mod_chick_g1 = cl_mod_leg;
		cl_mod_chick_g2 = R_RegisterModel ("models/monsters/bitch/gibs/g2.md3", 1, false);
		if (!cl_mod_chick_g2)
			cl_mod_chick_g2 = cl_mod_bone;
		cl_mod_chick_g3 = R_RegisterModel ("models/monsters/bitch/gibs/g3.md3", 1, false);
		if (!cl_mod_chick_g3)
			cl_mod_chick_g3 = cl_mod_leg;
		cl_mod_chick_g4 = R_RegisterModel ("models/monsters/bitch/gibs/g4.md3", 1, false);
		if (!cl_mod_chick_g4)
			cl_mod_chick_g4 = cl_mod_bone;
		cl_mod_chick_g5 = R_RegisterModel ("models/monsters/bitch/gibs/g5.md3", 1, false);
		if (!cl_mod_chick_g5)
			cl_mod_chick_g5 = cl_mod_arm;
		cl_mod_chick_g6 = R_RegisterModel ("models/monsters/bitch/gibs/g6.md3", 1, false);
		if (!cl_mod_chick_g6)
			cl_mod_chick_g6 = cl_mod_metal;
		cl_mod_chick_g7 = R_RegisterModel ("models/monsters/bitch/gibs/g7.md3", 1, false);
		if (!cl_mod_chick_g7)
			cl_mod_chick_g7 = cl_mod_chest;
		cl_mod_chick_g8 = R_RegisterModel ("models/monsters/bitch/gibs/g8.md3", 1, false);
		if (!cl_mod_chick_g8)
			cl_mod_chick_g8 = cl_mod_metal;
		cl_mod_chick_g9 = R_RegisterModel ("models/monsters/bitch/gibs/g9.md3", 1, false);
		if (!cl_mod_chick_g9)
			cl_mod_chick_g9 = cl_mod_gear;

		// Jorg's gibs
		cl_mod_jorg_g1 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g1.md3", 1, false);
		if (!cl_mod_jorg_g1)
			cl_mod_jorg_g1 = cl_mod_chest;
		cl_mod_jorg_g2 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g2.md3", 1, false);
		if (!cl_mod_jorg_g2)
			cl_mod_jorg_g2 = cl_mod_gear;
		cl_mod_jorg_g3 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g3.md3", 1, false);
		if (!cl_mod_jorg_g3)
			cl_mod_jorg_g3 = cl_mod_debris1;
		cl_mod_jorg_g4 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g4.md3", 1, false);
		if (!cl_mod_jorg_g4)
			cl_mod_jorg_g4 = cl_mod_gear;
		cl_mod_jorg_g5 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g5.md3", 1, false);
		if (!cl_mod_jorg_g5)
			cl_mod_jorg_g5 = cl_mod_debris3;
		cl_mod_jorg_g6 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g6.md3", 1, false);
		if (!cl_mod_jorg_g6)
			cl_mod_jorg_g6 = cl_mod_gear;
		cl_mod_jorg_g7 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g7.md3", 1, false);
		if (!cl_mod_jorg_g7)
			cl_mod_jorg_g7 = cl_mod_gear;
		cl_mod_jorg_g8 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g8.md3", 1, false);
		if (!cl_mod_jorg_g8)
			cl_mod_jorg_g8 = cl_mod_metal;
		cl_mod_jorg_g9 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g9.md3", 1, false);
		if (!cl_mod_jorg_g9)
			cl_mod_jorg_g9 = cl_mod_metal;
		cl_mod_jorg_g10 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g10.md3", 1, false);
		if (!cl_mod_jorg_g10)
			cl_mod_jorg_g10 = cl_mod_metal;
		cl_mod_jorg_g11 = R_RegisterModel ("models/monsters/boss3/jorg/gibs/g11.md3", 1, false);
		if (!cl_mod_jorg_g11)
			cl_mod_jorg_g11 = cl_mod_metal;

		// Boss2's gibs
		cl_mod_boss2_g1 = R_RegisterModel ("models/monsters/boss2/gibs/g1.md3", 1, false);
		if (!cl_mod_boss2_g1)
			cl_mod_boss2_g1 = cl_mod_gear;
		cl_mod_boss2_g2 = R_RegisterModel ("models/monsters/boss2/gibs/g2.md3", 1, false);
		if (!cl_mod_boss2_g2)
			cl_mod_boss2_g2 = cl_mod_metal;
		cl_mod_boss2_g3 = R_RegisterModel ("models/monsters/boss2/gibs/g3.md3", 1, false);
		if (!cl_mod_boss2_g3)
			cl_mod_boss2_g3 = cl_mod_gear;
		cl_mod_boss2_g4 = R_RegisterModel ("models/monsters/boss2/gibs/g4.md3", 1, false);
		if (!cl_mod_boss2_g4)
			cl_mod_boss2_g4 = cl_mod_debris1;
		cl_mod_boss2_g5 = R_RegisterModel ("models/monsters/boss2/gibs/g5.md3", 1, false);
		if (!cl_mod_boss2_g5)
			cl_mod_boss2_g5 = cl_mod_metal;
		cl_mod_boss2_g6 = R_RegisterModel ("models/monsters/boss2/gibs/g6.md3", 1, false);
		if (!cl_mod_boss2_g6)
			cl_mod_boss2_g6 = cl_mod_metal;
		cl_mod_boss2_g7 = R_RegisterModel ("models/monsters/boss2/gibs/g7.md3", 1, false);
		if (!cl_mod_boss2_g7)
			cl_mod_boss2_g7 = cl_mod_metal;
		cl_mod_boss2_g8 = R_RegisterModel ("models/monsters/boss2/gibs/g8.md3", 1, false);
		if (!cl_mod_boss2_g8)
			cl_mod_boss2_g8 = cl_mod_chest;
		cl_mod_boss2_g9 = R_RegisterModel ("models/monsters/boss2/gibs/g9.md3", 1, false);
		if (!cl_mod_boss2_g9)
			cl_mod_boss2_g9 = cl_mod_metal;
		cl_mod_boss2_g10 = R_RegisterModel ("models/monsters/boss2/gibs/g10.md3", 1, false);
		if (!cl_mod_boss2_g10)
			cl_mod_boss2_g10 = cl_mod_metal;
		cl_mod_boss2_g11 = R_RegisterModel ("models/monsters/boss2/gibs/g11.md3", 1, false);
		if (!cl_mod_boss2_g11)
			cl_mod_boss2_g11 = cl_mod_gear;
		cl_mod_boss2_g12 = R_RegisterModel ("models/monsters/boss2/gibs/g12.md3", 1, false);
		if (!cl_mod_boss2_g12)
			cl_mod_boss2_g12 = cl_mod_debris1;
		cl_mod_boss2_g13 = R_RegisterModel ("models/monsters/boss2/gibs/g13.md3", 1, false);
		if (!cl_mod_boss2_g13)
			cl_mod_boss2_g13 = cl_mod_debris3;

		// SuperTank's gibs
		cl_mod_supertank_g1 = R_RegisterModel ("models/monsters/boss1/gibs/g1.md3", 1, false);
		if (!cl_mod_supertank_g1)
			cl_mod_supertank_g1 = cl_mod_gear;
		cl_mod_supertank_g2 = R_RegisterModel ("models/monsters/boss1/gibs/g2.md3", 1, false);
		if (!cl_mod_supertank_g2)
			cl_mod_supertank_g2 = cl_mod_gear;
		cl_mod_supertank_g3 = R_RegisterModel ("models/monsters/boss1/gibs/g3.md3", 1, false);
		if (!cl_mod_supertank_g3)
			cl_mod_supertank_g3 = cl_mod_gear;
		cl_mod_supertank_g4 = R_RegisterModel ("models/monsters/boss1/gibs/g4.md3", 1, false);
		if (!cl_mod_supertank_g4)
		{
			cl_mod_supertank_g4 = R_RegisterModel ("models/objects/gibs/skull/tris.md2", 1, false);
			if (!cl_mod_supertank_g4)
				cl_mod_supertank_g4 = cl_mod_head2;
		}
		cl_mod_supertank_g5 = R_RegisterModel ("models/monsters/boss1/gibs/g5.md3", 1, false);
		if (!cl_mod_supertank_g5)
			cl_mod_supertank_g5 = cl_mod_metal;
		cl_mod_supertank_g6 = R_RegisterModel ("models/monsters/boss1/gibs/g6.md3", 1, false);
		if (!cl_mod_supertank_g6)
			cl_mod_supertank_g6 = cl_mod_debris3;
		cl_mod_supertank_g7 = R_RegisterModel ("models/monsters/boss1/gibs/g7.md3", 1, false);
		if (!cl_mod_supertank_g7)
			cl_mod_supertank_g7 = cl_mod_debris1;
	}

	for (int i=0; i<2; i++)
	{
		cl_mod_vbfg[i] = R_RegisterModel ("models/weapons/v_bfg/tris.md2", 1, (bool)i);
		cl_mod_vblast[i] = R_RegisterModel ("models/weapons/v_blast/tris.md2", 1, (bool)i);
		cl_mod_vchain[i] = R_RegisterModel ("models/weapons/v_chain/tris.md2", 1, (bool)i);
		cl_mod_vhandgr[i] = R_RegisterModel ("models/weapons/v_handgr/tris.md2", 1, (bool)i);
		cl_mod_vhyperb[i] = R_RegisterModel ("models/weapons/v_hyperb/tris.md2", 1, (bool)i);
		cl_mod_vlaunch[i] = R_RegisterModel ("models/weapons/v_launch/tris.md2", 1, (bool)i);
		cl_mod_vmachn[i] = R_RegisterModel ("models/weapons/v_machn/tris.md2", 1, (bool)i);
		cl_mod_vrail[i] = R_RegisterModel ("models/weapons/v_rail/tris.md2", 1, (bool)i);
		cl_mod_vrocket[i] = R_RegisterModel ("models/weapons/v_rocket/tris.md2", 1, (bool)i);
		cl_mod_vshotg[i] = R_RegisterModel ("models/weapons/v_shotg/tris.md2", 1, (bool)i);
		cl_mod_vshotg2[i] = R_RegisterModel ("models/weapons/v_shotg2/tris.md2", 1, (bool)i);
	}

	cl_disguise_model = R_RegisterModel ("players/cyborg/tris.md2", 1, false);
	if (cl_disguise_model)
	{
		cl_disguise_skin = R_RegisterSkin ("players/cyborg/disguise");
		cl_disguise_bump = R_RegisterBump ("players/cyborg/disguise", cl_disguise_skin, cl_disguise_model);
		cl_disguise_light = R_RegisterLight ("players/cyborg/disguise");
	}

/// кэшируем все 3D hud models сначала из паков, затем из папки models/hud (кэшируем без шкурок)
	searchpath_t	*search;
	pack_t			*pak;
	int				slot;
	int				dirCount, i;
	char			*dirFiles[MAX_HUDMODEL_FILES];

	for (search = fs_searchpaths ; search ; search = search->next)
	{
		// is the element a pak file?
		if (search->pack && !search->disabled)
		{
			// look through all the pak file elements
			pak = search->pack;
			if (pak->isPK2)
			{
				unsigned	hash = Com_HashKey(pak->filename);
				slot = -1;
				for(i=0; i<MAX_PAKS; i++)
				{
					if(ZipCache[i].uf)
					{
						if (ZipCache[i].hash == hash)
						{
							if(!b_stricmp(ZipCache[i].pak_name, pak->filename))
							{
								Memcpy(&pf, &ZipCache[i], sizeof(zipfile_t));		// Нашли уже открытый ZIP и загрузили его описатель в pf
								goto clc;
							}
						}
					}
					else
					{
						if(slot == -1)
						{
							slot = i;	// Ищем в slot первый свободный номер в кэше
							break;
						}
					}
				}
				strcpy(&ZipCache[slot].pak_name[0], pak->filename);	// Если не нашли zip в кэше, откроем его...
				if (!PackFileOpen (&ZipCache[slot]))
					Com_Error(ERR_FATAL, "Error opening pk2-file: %s", pak->filename);
				Memcpy(&pf, &ZipCache[slot], sizeof(zipfile_t));
				goto clc2;
clc:			slot = i;
clc2:			for(i=0; i<pf.gi.number_entry; i++)
				{
					if(pf.fi[i].size)
					{
						char	nam[256];
						Memcpy(nam, pf.fi[i].name, 256);
						if(!Q_strncasecmp(nam, "models/hud/", 11))
						{
							if(!Q_strcasecmp(nam+strlen(nam)-4, ".md2") || !Q_strcasecmp(nam+strlen(nam)-4, ".md3"))
							{
								model_t *model = R_RegisterModel(nam, 1, false);
								if (model)
									model->flags &= ~RF_DISTORT;
								Memcpy(&pf, &ZipCache[slot], sizeof(zipfile_t));	// восстановим PF, ибо он портится при регистрации модели (вызов ZIP)
								Com_DPrintf("3D hud model: %s/%s\n", pf.pak_name, pf.fi[i].name);
							}
						}
					}
				}
			}
			else
			{
				// look through all the pak file elements
				for (i=0 ; i<pak->numfiles ; i++)
				{
					char	nam[256];
					Memcpy(nam, pak->files[i].name, 256);
					if(!Q_strncasecmp(nam, "models/hud/", 11))
					{
						if(!Q_strcasecmp(nam+strlen(nam)-4, ".md2") || !Q_strcasecmp(nam+strlen(nam)-4, ".md3"))
						{
							model_t *model = R_RegisterModel(nam, 1, false);
							if (model)
								model->flags &= ~RF_DISTORT;
							Com_DPrintf("3D hud model: %s/%s\n", pak->filename, pak->files[i].name);
						}
					}
				}
			}
		}
	}

	dirCount = Sys_FindFiles(va("%s/models/hud", FS_Gamedir()), "*.md?", dirFiles, MAX_HUDMODEL_FILES, true, false);
	for (i = 0; i < dirCount; i++)
	{
		char	*name = dirFiles[i];
		while (*name!=0)
		{
			if(*name=='/' || *name=='\\')
				goto good;
			name++;
		}
		goto stop;
good:	name++;
		while (*name!=0)
		{
			if(*name=='/' || *name=='\\')
				goto good2;
			name++;
		}
		goto stop;
good2:	name++;
		R_RegisterModel(name, 1, false);
		Com_DPrintf("3D hud model: %s\n", dirFiles[i]);
stop:	Z_Free(dirFiles[i]);
	}
}	


image_s *R_RegisterSkin (char *name)
{
	return GL_FindImage (name, it_skin, false, 0, false, 0);
}


image_s *R_RegisterBump (char *name, image_s *tex, model_t *mod)
{
	image_t	*img;
	char	nam[MAX_QPATH+16];

	if(!tex)
		return NULL;

	if(r_simple->value)
		return r_notexture;

	Com_sprintf (nam, sizeof(nam), "%s_bump.tga", name);
	img = GL_FindImage (nam, it_bump, true, tex, false, 0);
	if (img == r_defaultbump)
	{
		Com_sprintf (nam, sizeof(nam), "textures/%s_bump.dds", name);
		img = GL_FindImage (nam, it_bump, true, tex, (r_bumpscale->value && !(mod->flags & RF_NOAUTOBUMP)) ? true : false, 0);
	}
	return img;
}


image_s *R_RegisterLight (char *name)
{
	char nam[MAX_QPATH+8];
	Com_sprintf (nam, sizeof(nam), "%s_light", name);
	return GL_FindImage (nam, it_light, false, 0, false, 0);
}


void FreeSound(char *name)
{
	int i;

	if (!s_initsound->value || openalStop || !num_sfx)
		return;

	// see if already loaded
	for (i = 0; i < num_sfx; i++)
		if (!strcmp(known_sfx_name[i], name))
			break;
	if (i == num_sfx)
		return;

	// Stop all sounds
	S_StopAllSounds();

	// Free sound
	alDeleteBuffers(1, &known_sfx_bufferNum[i]);

	// Clean up
	Memset(&known_sfx_name[i], 0, MAX_QPATH);
	Memset(&known_sfx_bufferNum[i], 0, sizeof(known_sfx_bufferNum[0]));
}

static char oldSex[MAX_QPATH];
void CL_LoadClientinfo (clientinfo_t *ci, char *s, bool noLoadTauntSounds)
{
	int i;
	char		*t;
	char		model_name[MAX_QPATH];
	char		skin_name[MAX_QPATH];
	char		model_filename[MAX_QPATH];
	char		skin_filename[MAX_QPATH];
	char		weapon_filename[MAX_QPATH];

	strncpy(ci->cinfo, s, sizeof(ci->cinfo));
	ci->cinfo[sizeof(ci->cinfo)-1] = 0;

	// isolate the player's name
	strncpy(ci->name, s, sizeof(ci->name));
	ci->name[sizeof(ci->name)-1] = 0;
	t = strstr (s, "\\");
	if (t)
	{
		ci->name[t-s] = 0;
		s = t+1;
	}

	if (cl_noskins->value || *s == 0)
	{
		Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
		Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/weapon.md2");
		Com_sprintf (skin_filename, sizeof(skin_filename), "players/male/grunt");
		Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/male/grunt_i");
		ci->model = R_RegisterModel (model_filename, 1, false);
		Memset(ci->weaponmodel, 0, sizeof(ci->weaponmodel));
		ci->weaponmodel[0] = R_RegisterModel (weapon_filename, 1, false);
		ci->skin = R_RegisterSkin (skin_filename);
		ci->bump = R_RegisterBump (skin_filename, ci->skin, ci->model);
		ci->light = R_RegisterLight (skin_filename);
		ci->icon = Draw_FindPic (ci->iconname);
	}
	else
	{
		// isolate the model name
		strcpy (model_name, s);
		t = strstr(model_name, "/");
		if (!t)
			t = strstr(model_name, "\\");
		if (!t)
			t = model_name;
		*t = 0;

		// isolate the skin name
		strcpy (skin_name, s + strlen(model_name) + 1);

		// model file
		Com_sprintf (model_filename, sizeof(model_filename), "players/%s/tris.md2", model_name);
		ci->model = R_RegisterModel (model_filename, 1, false);
		if (!ci->model)
		{
			strcpy(model_name, "male");
			Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
			ci->model = R_RegisterModel (model_filename, 1, false);
		}

		// skin file
		Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s", model_name, skin_name);
		ci->skin = R_RegisterSkin (skin_filename);
		ci->bump = R_RegisterBump (skin_filename, ci->skin, ci->model);
		ci->light = R_RegisterLight (skin_filename);

		// if we don't have the skin and the model wasn't male,
		// see if the male has it (this is for CTF's skins)
 		if ((!ci->skin || !ci->bump) && Q_strcasecmp(model_name, "male"))
		{
			// change model to male
			strcpy(model_name, "male");
			Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
			ci->model = R_RegisterModel (model_filename, 1, false);

			// see if the skin exists for the male model
			Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s", model_name, skin_name);
			ci->skin = R_RegisterSkin (skin_filename);
			ci->bump = R_RegisterBump (skin_filename, ci->skin, ci->model);
			ci->light = R_RegisterLight (skin_filename);
		}

		// if we still don't have a skin, it means that the male model didn't have
		// it, so default to grunt
		if (!ci->skin || !ci->bump)
		{
			// see if the skin exists for the male model
			Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/grunt", model_name, skin_name);
			ci->skin = R_RegisterSkin (skin_filename);
			ci->bump = R_RegisterBump (skin_filename, ci->skin, ci->model);
			ci->light = R_RegisterLight (skin_filename);
		}

		// weapon file
		for (i = 0; i < num_cl_weaponmodels; i++)
		{
			Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/%s/%s", model_name, cl_weaponmodels[i]);
			ci->weaponmodel[i] = R_RegisterModel(weapon_filename, 1, false);
			if (!ci->weaponmodel[i] && strcmp(model_name, "cyborg") == 0)
			{
				// try male
				Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/%s", cl_weaponmodels[i]);
				ci->weaponmodel[i] = R_RegisterModel(weapon_filename, 1, false);
			}
			if (!cl_vwep->value)
				break; // only one when vwep is off
		}

		// icon file
		Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/%s/%s_i", model_name, skin_name);
		ci->icon = Draw_FindPic (ci->iconname);
	}

	if (!noLoadTauntSounds)
	{
		Memset(oldSex, 0, sizeof(oldSex));
		strncpy(oldSex, ci->sex, sizeof(oldSex));
		///	if (!strcmp(model_name, "cyborg") || !strcmp(model_name, "male") || !strcmp(model_name, "female"))
		{
			strncpy(ci->sex, model_name, sizeof(ci->sex));
			Cvar_ForceSet("gender", model_name);
		}
		///	else
		///		strncpy(ci->sex, "null", sizeof(ci->sex));
		// перерегаем sexed звуки, которые используются в game-коде
		{
			char sexedFilename[MAX_QPATH];
			// освободим старый звук, и на его место впишем новый (костыль и хак...) - зато не надо обновлять soundindex в Game-коде
			if (oldSex[0])
			{
				Com_sprintf(sexedFilename, sizeof(sexedFilename), "#players/%s/taunt1.wav", oldSex);
				FreeSound(sexedFilename);
			}
			S_RegisterSexedSound(&cl_entities[cl.playernum + 1].current, "*taunt1.wav");
			if (oldSex[0])
			{
				Com_sprintf(sexedFilename, sizeof(sexedFilename), "#players/%s/taunt2.wav", oldSex);
				FreeSound(sexedFilename);
			}
			S_RegisterSexedSound(&cl_entities[cl.playernum + 1].current, "*taunt2.wav");
		}
	}

	// must have loaded all data types to be valud
	if (!ci->skin || !ci->bump || !ci->icon || !ci->model || !ci->weaponmodel[0])
	{
		ci->skin =
		ci->bump =
		ci->light =
		ci->icon = NULL;
		ci->model = NULL;
		ci->weaponmodel[0] = 0;
		return;
	}
}


void R_EndRegistration ()
{
	int		i;
	model_t	*mod;

	R_FreeUnusedMaterials ();

	for (i=0, mod=mod_known ; i<mod_numknown ; i++, mod++)
		if (mod->name[0])
			if (mod->registration_sequence != registration_sequence)
				Mod_Free (mod);	// don't need this model

	GL_FreeUnusedImages ();
}


void CL_ABIndex()
{
	int i,j;

	for(i=0; i<brushmodel_counter; i++)
	{
		for(j=0; j<aliasmodel_counter; j++)
			if(!strcmp(bmdl_list[i].label, amdl_list[j].label))
			{
				amdl_list[j].index = bmdl_list[i].index;
				Com_DPrintf("Link \"%s\": \"%s\" linked with brush \"*%i\"\n", bmdl_list[i].label, amdl_list[j].model, bmdl_list[i].index);
			}

		for(j=0; j<lightmodel_counter; j++)
			if(!strcmp(bmdl_list[i].label, lmdl_list[j].label))
			{
				lmdl_list[j].index = bmdl_list[i].index;
				Com_DPrintf("Link \"%s\": light linked with brush \"*%i\"\n", bmdl_list[i].label, bmdl_list[i].index);
			}

		for (j=0; j<numEmits; j++)
			if(!strcmp(bmdl_list[i].label, emits[j].label))
			{
				emits[j].index = bmdl_list[i].index;
				Com_DPrintf("Link \"%s\": emitter linked with brush \"*%i\"\n", bmdl_list[i].label, bmdl_list[i].index);
			}
	}
}


void CL_Clear3DHud()
{
	int	i, max_images;

	if (net_compatibility->value)
		max_images = MAX_IMAGES_Q2;
	else
		max_images = MAX_IMAGES_BERS;

	for (i=0; i<max_images; i++)
	{
		hud_model_test[i] = false;
		hud_model_exist[i] = false;
		hud_model_framecount[i] = 0;
		hud_model_counter[i] = 0;
		if (hud_models[i])
			Mod_Free(hud_models[i]);
	}

	hud_player_test = false;
	hud_player_exist = false;
	hud_player_framecount = 0;
	if (hud_player_model)
		Mod_Free(hud_player_model);
}


/*
==================
SCR_RunConsole

Scroll it up or down
==================
*/
void SCR_RunConsole ()
{
// decide on the height of the console
	if (cls.key_dest == key_console)
		scr_conlines = 0.5;		// half screen
	else
		scr_conlines = 0;				// none visible

////	if (cl.attractloop || cin.pic[0])			/// Berserker: во время демо или показа картинки
////		scr_con_current = scr_conlines = 0;		/// консоль НЕ ВИДНА!!!

	if (scr_conlines < scr_con_current)
	{
		scr_con_current -= scr_conspeed->value*cls.frametime;
		if (scr_conlines > scr_con_current)
			scr_con_current = scr_conlines;

	}
	else if (scr_conlines > scr_con_current)
	{
		scr_con_current += scr_conspeed->value*cls.frametime;
		if (scr_conlines < scr_con_current)
			scr_con_current = scr_conlines;
	}

}


/*
=================
S_SpatializeOrigin

Used for spatializing channels and autosounds
=================
*/
/*
void S_SpatializeOrigin (vec3_t origin, float master_vol, float dist_mult, int *left_vol, int *right_vol)
{
    float		dot;
    float		dist;
    float		lscale, rscale, scale;
    vec3_t		source_vec;

	if (cls.state != ca_active)
	{
		*left_vol = *right_vol = 255;
		return;
	}

// calculate stereo seperation and distance attenuation
	VectorSubtract(origin, listener_origin, source_vec);

	dist = VectorNormalize(source_vec);
	dist -= SOUND_FULLVOLUME;
	if (dist < 0)
		dist = 0;			// close enough to be at full volume
	dist *= dist_mult;		// different attenuation levels
	
	dot = DotProduct(listener_right, source_vec);

	if (dma.channels == 1 || !dist_mult)
	{ // no attenuation = no spatialization
		rscale = 1.0;
		lscale = 1.0;
	}
	else
	{
		rscale = 0.5 * (1.0 + dot);
		lscale = 0.5*(1.0 - dot);
	}

	// add in distance effect
	scale = (1.0 - dist) * rscale;
	*right_vol = (int) (master_vol * scale);
	if (*right_vol < 0)
		*right_vol = 0;

	scale = (1.0 - dist) * lscale;
	*left_vol = (int) (master_vol * scale);
	if (*left_vol < 0)
		*left_vol = 0;
}
*/

void CL_Frame (int msec)
{
	static int	extratime;

	if (dedicated->value)
		return;

	if (emit_time <= sys_time)
	{
		emit_time = sys_time + 100;	/// Think time равен 0.1 сек.
		draw_emits = true;
	}
	else
		draw_emits = false;

	extratime += msec;

	if (!cl_timedemo->value)
	{
		if (cls.state == ca_connected && extratime < 100)
			return;			// don't flood packets out while connecting
		float	maxfps;
		if (cls.state == ca_active && !cl_paused->value && !m_menudepth && ActiveApp)
			maxfps = cl_maxfps->value;
		else
			maxfps = con_maxfps->value;
		if (maxfps < 5) maxfps = 5;				// защита от нулевого значения, иначе зависание!
		int ms = 1000/maxfps - extratime;
		if (ms>0)
		{
			if (cl_sleep->value)	// снижение загруженности CPU
				Sleep(ms);
			return;			// framerate is too high
		}
	}

	// let the mouse activate or deactivate
	IN_Frame ();

	// decide the simulation time
	cls.frametime = extratime/1000.0;
	cl.time += extratime;
	cls.realtime = curtime;

	extratime = 0;
	if (cls.frametime > (1.0 / 5))
		cls.frametime = (1.0 / 5);

	// if in the debugger last frame, don't timeout
	if (msec > 5000)
		cls.netchan.last_received = Sys_Milliseconds ();

	// fetch results from server
	CL_ReadPackets ();

	// send a new command message to the server
	CL_SendCommand ();

	// predict all unacknowledged movements
	CL_PredictMovement ();

	int	cs_playerskins;
	if (net_compatibility->value)
		cs_playerskins = CS_PLAYERSKINS_Q2;
	else
		cs_playerskins = CS_PLAYERSKINS_BERS;
	// Berserker: обновим клиентинфо всех клиентов при изменении цвара
	if (cl_forcemymodel_modified)
	{
		cl_forcemymodel_modified = false;
		for (int i=0 ; i<MAX_CLIENTS ; i++)
			if (cl.configstrings[cs_playerskins+i][0])
				CL_ParseClientinfo (i);
	}

	// allow rendering DLL change
	VID_CheckChanges ();
	if (!cl.refresh_prepped && cls.state == ca_active)
		CL_PrepRefresh ();

	// update the screen
	if (host_speeds->value)
		time_before_ref = Sys_Milliseconds ();
	SCR_UpdateScreen ();
	if (host_speeds->value)
		time_after_ref = Sys_Milliseconds ();

	// Berserker: если системная гамма изменена при запуске игры
	// то установки своей гаммы до некоторого времени не работают. Пофиксим. Почему так, хз, особо не разбирался...
	if(!cls.disable_screen)
	{
		gamma_initialized_++;
		if(gamma_initialized_ == 0)
			SetGamma(vid_gamma->value, vid_bright->value, vid_contrast->value);
	}

	// update audio
	Music_Update();
	{
		float orientation[6];

		Memcpy(orientation, cl.v_forward, sizeof(vec3_t));
		Memcpy(&orientation[3], cl.v_up, sizeof(vec3_t));
		S_Update(cl.refdef.vieworg, cl.v_forward, orientation);
	}

	// advance local effects for next frame
	CL_RunDLights ();
	CL_RunLightStyles ();
	SCR_RunCinematic ();
	SCR_RunConsole ();

///	cls.framecount++;	// not used?
}


inline void MS_memcpy( void *dest, const void *src, size_t count )
{
	memcpy(dest, src, count);
}

inline void MS_memset( void *dest, const int b, size_t count )
{
	memset(dest, b, count);
}

void MMX_Memcpy8B( void *dest, const void *src, size_t count )
{
	_asm
	{
		mov esi, src
		mov edi, dest
		mov ecx, count
		shr ecx, 3			// 8 bytes per iteration
loop1:
		movq mm1, [ESI]		// Read in source data
		movntq [EDI], mm1	// Non-temporal stores

		add esi, 8
		add edi, 8
		dec ecx
		jnz loop1
		emms
	}
}

void MMX_Memcpy64B( void *dest, const void *src, size_t count )
{
	_asm
	{
		mov esi, src
		mov edi, dest
		mov ecx, count
		shr ecx, 6			// 64 bytes per iteration 
loop1:
		prefetchnta 64[ESI]	// Prefetch next loop, non-temporal 
		prefetchnta 96[ESI]

		movq mm1, [ESI]		// Read in source data 
		movq mm2, 8[ESI]
		movq mm3, 16[ESI]
		movq mm4, 24[ESI]
		movq mm5, 32[ESI]
		movq mm6, 40[ESI]
		movq mm7, 48[ESI]
		movq mm0, 56[ESI]

		movntq [EDI], mm1	// Non-temporal stores 
		movntq 8[EDI], mm2
		movntq 16[EDI], mm3
		movntq 24[EDI], mm4
		movntq 32[EDI], mm5
		movntq 40[EDI], mm6
		movntq 48[EDI], mm7
		movntq 56[EDI], mm0

		add esi, 64
		add edi, 64
		dec ecx
		jnz loop1
		emms
	}
}

void MMX_Memcpy2kB( void *dest, const void *src, size_t count )
{
	byte buf[2048];
	byte *tbuf = &buf[0];
	__asm
	{
		push ebx
		mov esi, src
		mov ebx, count
		shr ebx, 11				// 2048 bytes at a time 
		mov edi, dest
loop2k:
		push edi				// copy 2k into temporary buffer 
		mov edi, tbuf
		mov ecx, 32
loopMemToL1:
		prefetchnta 64[ESI]		// Prefetch next loop, non-temporal 
		prefetchnta 96[ESI]

		movq mm1, [ESI]			// Read in source data 
		movq mm2, 8[ESI]
		movq mm3, 16[ESI]
		movq mm4, 24[ESI]
		movq mm5, 32[ESI]
		movq mm6, 40[ESI]
		movq mm7, 48[ESI]
		movq mm0, 56[ESI]

		movq [EDI], mm1			// Store into L1 
		movq 8[EDI], mm2
		movq 16[EDI], mm3
		movq 24[EDI], mm4
		movq 32[EDI], mm5
		movq 40[EDI], mm6
		movq 48[EDI], mm7
		movq 56[EDI], mm0
		add esi, 64
		add edi, 64
		dec ecx
		jnz loopMemToL1

		pop edi					// Now copy from L1 to system memory 
		push esi
		mov esi, tbuf
		mov ecx, 32
loopL1ToMem:
		movq mm1, [ESI]			// Read in source data from L1 
		movq mm2, 8[ESI]
		movq mm3, 16[ESI]
		movq mm4, 24[ESI]
		movq mm5, 32[ESI]
		movq mm6, 40[ESI]
		movq mm7, 48[ESI]
		movq mm0, 56[ESI]

		movntq [EDI], mm1		// Non-temporal stores 
		movntq 8[EDI], mm2
		movntq 16[EDI], mm3
		movntq 24[EDI], mm4
		movntq 32[EDI], mm5
		movntq 40[EDI], mm6
		movntq 48[EDI], mm7
		movntq 56[EDI], mm0

		add esi, 64
		add edi, 64
		dec ecx
		jnz loopL1ToMem

		pop esi					// Do next 2k block 
		dec ebx
		jnz loop2k
		pop ebx
		emms
	}
}

void MMX_Memcpy( void *dest0, const void *src0, size_t count0 )
{
	// if copying more than 16 bytes and we can copy 8 byte aligned
	if ( count0 > 16 && !( ( (int)dest0 ^ (int)src0 ) & 7 ) )
	{
		byte *dest = (byte *)dest0;
		byte *src = (byte *)src0;
		// copy up to the first 8 byte aligned boundary
		int count = ((int)dest) & 7;
		memcpy( dest, src, count );
		dest += count;
		src += count;
		count = count0 - count;
		// if there are multiple blocks of 2kB
		if ( count & ~4095 )
		{
			MMX_Memcpy2kB( dest, src, count );
			src += (count & ~2047);
			dest += (count & ~2047);
			count &= 2047;
		}
		// if there are blocks of 64 bytes
		if ( count & ~63 )
		{
			MMX_Memcpy64B( dest, src, count );
			src += (count & ~63);
			dest += (count & ~63);
			count &= 63;
		}

		// if there are blocks of 8 bytes
		if ( count & ~7 )
		{
			MMX_Memcpy8B( dest, src, count );
			src += (count & ~7);
			dest += (count & ~7);
			count &= 7;
		}
		// copy any remaining bytes
		memcpy( dest, src, count );
	}
	else
	{
		// use the regular one if we cannot copy 8 byte aligned
		memcpy( dest0, src0, count0 );
	}
}


// Very optimized memcpy() routine for all AMD Athlon and Duron family.
// This code uses any of FOUR different basic copy methods, depending
// on the transfer size.
// NOTE:  Since this code uses MOVNTQ (also known as "Non-Temporal MOV" or
// "Streaming Store"), and also uses the software prefetchnta instructions,
// be sure you're running on Athlon/Duron or other recent CPU before calling!

#define TINY_BLOCK_COPY 64       // upper limit for movsd type copy
// The smallest copy uses the X86 "movsd" instruction, in an optimized
// form which is an "unrolled loop".

#define IN_CACHE_COPY 64 * 1024  // upper limit for movq/movq copy w/SW prefetch
// Next is a copy that uses the MMX registers to copy 8 bytes at a time,
// also using the "unrolled loop" optimization.   This code uses
// the software prefetch instruction to get the data into the cache.

#define UNCACHED_COPY 197 * 1024 // upper limit for movq/movntq w/SW prefetch
// For larger blocks, which will spill beyond the cache, it's faster to
// use the Streaming Store instruction MOVNTQ.   This write instruction
// bypasses the cache and writes straight to main memory.  This code also
// uses the software prefetch instruction to pre-read the data.
// USE 64 * 1024 FOR THIS VALUE IF YOU'RE ALWAYS FILLING A "CLEAN CACHE"

#define BLOCK_PREFETCH_COPY  infinity // no limit for movq/movntq w/block prefetch 
#define CACHEBLOCK 80h // number of 64-byte blocks (cache lines) for block prefetch
// For the largest size blocks, a special technique called Block Prefetch
// can be used to accelerate the read operations.   Block Prefetch reads
// one address per cache line, for a series of cache lines, in a short loop.
// This is faster than using software prefetch.  The technique is great for
// getting maximum read bandwidth, especially in DDR memory systems.

void AMD_Memcpy( void *dest, const void *src, size_t n )
{
	__asm
	{
		mov		ecx, [n]					// number of bytes to copy
		mov		edi, [dest]					// destination
		mov		esi, [src]					// source
		mov		ebx, ecx					// keep a copy of count

		cld
		cmp		ecx, TINY_BLOCK_COPY
		jb		$memcpy_ic_3				// tiny? skip mmx copy

		cmp		ecx, 32*1024				// don't align between 32k-64k because
		jbe		$memcpy_do_align			//  it appears to be slower
		cmp		ecx, 64*1024
		jbe		$memcpy_align_done
$memcpy_do_align:
		mov		ecx, 8						// a trick that's faster than rep movsb...
		sub		ecx, edi					// align destination to qword
		and		ecx, 111b					// get the low bits
		sub		ebx, ecx					// update copy count
		neg		ecx							// set up to jump into the array
		add		ecx, offset $memcpy_align_done
		jmp		ecx							// jump to array of movsb's

align 4
		movsb
		movsb
		movsb
		movsb
		movsb
		movsb
		movsb
		movsb

$memcpy_align_done:						// destination is dword aligned
		mov		ecx, ebx					// number of bytes left to copy
		shr		ecx, 6						// get 64-byte block count
		jz		$memcpy_ic_2				// finish the last few bytes

		cmp		ecx, IN_CACHE_COPY/64		// too big 4 cache? use uncached copy
		jae		$memcpy_uc_test

// This is small block copy that uses the MMX registers to copy 8 bytes
// at a time.  It uses the "unrolled loop" optimization, and also uses
// the software prefetch instruction to get the data into the cache.
align 16
$memcpy_ic_1:							// 64-byte block copies, in-cache copy

		prefetchnta [esi + (200*64/34+192)]	// start reading ahead

		movq	mm0, [esi+0]				// read 64 bits
		movq	mm1, [esi+8]
		movq	[edi+0], mm0				// write 64 bits
		movq	[edi+8], mm1				//    note:  the normal movq writes the
		movq	mm2, [esi+16]				//    data to cache; a cache line will be
		movq	mm3, [esi+24]				//    allocated as needed, to store the data
		movq	[edi+16], mm2
		movq	[edi+24], mm3
		movq	mm0, [esi+32]
		movq	mm1, [esi+40]
		movq	[edi+32], mm0
		movq	[edi+40], mm1
		movq	mm2, [esi+48]
		movq	mm3, [esi+56]
		movq	[edi+48], mm2
		movq	[edi+56], mm3

		add		esi, 64						// update source pointer
		add		edi, 64						// update destination pointer
		dec		ecx							// count down
		jnz		$memcpy_ic_1				// last 64-byte block?

$memcpy_ic_2:
		mov		ecx, ebx					// has valid low 6 bits of the byte count
$memcpy_ic_3:
		shr		ecx, 2						// dword count
		and		ecx, 1111b					// only look at the "remainder" bits
		neg		ecx							// set up to jump into the array
		add		ecx, offset $memcpy_last_few
		jmp		ecx							// jump to array of movsd's

$memcpy_uc_test:
		cmp		ecx, UNCACHED_COPY/64		// big enough? use block prefetch copy
		jae		$memcpy_bp_1

$memcpy_64_test:
		or		ecx, ecx					// tail end of block prefetch will jump here
		jz		$memcpy_ic_2				// no more 64-byte blocks left

// For larger blocks, which will spill beyond the cache, it's faster to
// use the Streaming Store instruction MOVNTQ.   This write instruction
// bypasses the cache and writes straight to main memory.  This code also
// uses the software prefetch instruction to pre-read the data.
align 16
$memcpy_uc_1:							// 64-byte blocks, uncached copy

		prefetchnta [esi + (200*64/34+192)]	// start reading ahead

		movq	mm0,[esi+0]					// read 64 bits
		add		edi,64						// update destination pointer
		movq	mm1,[esi+8]
		add		esi,64						// update source pointer
		movq	mm2,[esi-48]
		movntq	[edi-64], mm0				// write 64 bits, bypassing the cache
		movq	mm0,[esi-40]				//    note: movntq also prevents the CPU
		movntq	[edi-56], mm1				//    from READING the destination address
		movq	mm1,[esi-32]				//    into the cache, only to be over-written
		movntq	[edi-48], mm2				//    so that also helps performance
		movq	mm2,[esi-24]
		movntq	[edi-40], mm0
		movq	mm0,[esi-16]
		movntq	[edi-32], mm1
		movq	mm1,[esi-8]
		movntq	[edi-24], mm2
		movntq	[edi-16], mm0
		dec		ecx
		movntq	[edi-8], mm1
		jnz		$memcpy_uc_1				// last 64-byte block?

		jmp		$memcpy_ic_2				// almost done

// For the largest size blocks, a special technique called Block Prefetch
// can be used to accelerate the read operations.   Block Prefetch reads
// one address per cache line, for a series of cache lines, in a short loop.
// This is faster than using software prefetch, in this case.
// The technique is great for getting maximum read bandwidth,
// especially in DDR memory systems.
$memcpy_bp_1:							// large blocks, block prefetch copy

		cmp		ecx, CACHEBLOCK				// big enough to run another prefetch loop?
		jl		$memcpy_64_test				// no, back to regular uncached copy

		mov		eax, CACHEBLOCK / 2			// block prefetch loop, unrolled 2X
		add		esi, CACHEBLOCK * 64		// move to the top of the block
align 16
$memcpy_bp_2:
		mov		edx, [esi-64]				// grab one address per cache line
		mov		edx, [esi-128]				// grab one address per cache line
		sub		esi, 128					// go reverse order
		dec		eax							// count down the cache lines
		jnz		$memcpy_bp_2				// keep grabbing more lines into cache

		mov		eax, CACHEBLOCK				// now that it's in cache, do the copy
align 16
$memcpy_bp_3:
		movq	mm0, [esi   ]				// read 64 bits
		movq	mm1, [esi+ 8]
		movq	mm2, [esi+16]
		movq	mm3, [esi+24]
		movq	mm4, [esi+32]
		movq	mm5, [esi+40]
		movq	mm6, [esi+48]
		movq	mm7, [esi+56]
		add		esi, 64						// update source pointer
		movntq	[edi   ], mm0				// write 64 bits, bypassing cache
		movntq	[edi+ 8], mm1				//    note: movntq also prevents the CPU
		movntq	[edi+16], mm2				//    from READING the destination address 
		movntq	[edi+24], mm3				//    into the cache, only to be over-written,
		movntq	[edi+32], mm4				//    so that also helps performance
		movntq	[edi+40], mm5
		movntq	[edi+48], mm6
		movntq	[edi+56], mm7
		add		edi, 64						// update dest pointer

		dec		eax							// count down

		jnz		$memcpy_bp_3				// keep copying
		sub		ecx, CACHEBLOCK				// update the 64-byte block count
		jmp		$memcpy_bp_1				// keep processing chunks

// The smallest copy uses the X86 "movsd" instruction, in an optimized
// form which is an "unrolled loop".   Then it handles the last few bytes.
align 4
		movsd
		movsd								// perform last 1-15 dword copies
		movsd
		movsd
		movsd
		movsd
		movsd
		movsd
		movsd
		movsd								// perform last 1-7 dword copies
		movsd
		movsd
		movsd
		movsd
		movsd
		movsd

$memcpy_last_few:						// dword aligned from before movsd's
		mov		ecx, ebx					// has valid low 2 bits of the byte count
		and		ecx, 11b					// the last few cows must come home
		jz		$memcpy_final				// no more, let's leave
		rep		movsb						// the last 1, 2, or 3 bytes

$memcpy_final: 
		emms								// clean up the MMX state
		sfence								// flush the write buffer
		mov		eax, [dest]					// ret value = destination pointer
	}
}


void MMX_Memset( void* dest0, const int val, const size_t count0 )
{
	union
	{
		byte	bytes[8];
		WORD	words[4];
		DWORD	dwords[2];
	} dat;

	byte *dest = (byte *)dest0;
	int count = count0;

	while( count > 0 && (((int)dest) & 7) )
	{
		*dest = val;
		dest++;
		count--;
	}
	if ( !count )
		return;

	dat.bytes[0] = val;
	dat.bytes[1] = val;
	dat.words[1] = dat.words[0];
	dat.dwords[1] = dat.dwords[0];

	if ( count >= 64 )
	{
		__asm
		{
			mov edi, dest
			mov ecx, count
			shr ecx, 6		// 64 bytes per iteration
			movq mm1, dat	// Read in source data
			movq mm2, mm1
			movq mm3, mm1
			movq mm4, mm1
			movq mm5, mm1
			movq mm6, mm1
			movq mm7, mm1
			movq mm0, mm1
loop1:
			movntq 0[EDI], mm1	// Non-temporal stores
			movntq 8[EDI], mm2
			movntq 16[EDI], mm3
			movntq 24[EDI], mm4
			movntq 32[EDI], mm5
			movntq 40[EDI], mm6
			movntq 48[EDI], mm7
			movntq 56[EDI], mm0

			add edi, 64
			dec ecx
			jnz loop1
		}
		dest += ( count & ~63 );
		count &= 63;
	}

	if ( count >= 8 )
	{
		__asm
		{
			mov edi, dest
			mov ecx, count
			shr ecx, 3		// 8 bytes per iteration
			movq mm1, dat	// Read in source data
			loop2:
			movntq 0[EDI], mm1	// Non-temporal stores

			add edi, 8
			dec ecx
			jnz loop2
		}
		dest += (count & ~7);
		count &= 7;
	}

	while( count > 0 )
	{
		*dest = val;
		dest++;
		count--;
	}

	__asm
	{
		emms
	}
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int iCmdShow)
{
    MSG		msg;
	int		time, oldtime;
	RECT	rect;

	/* previous instances do not exist in Win32 */
	if (hPrevInstance)
		return 0;

	// Cgg - max number of open files at the crt level
	_setmaxstdio(2048);
	// !Cgg

	BuildSqrtTable();				// Ставим в самом начале, т.к. может понадобиться fastsqrt...
	M_SelectMath(0, 0, true, true);	// set default progs

	r1q2_title[0] = /*r1q2_down_title[0] = */0;

	global_hInstance = hInstance;

	ParseCommandLine (lpCmdLine);

	// startup logo
	hwnd_dialog = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, NULL);

	if (hwnd_dialog)
	{
		if (GetWindowRect (hwnd_dialog, &rect))
			if (rect.left > (rect.top * 2))
				SetWindowPos (hwnd_dialog, 0, (rect.left/2) - ((rect.right - rect.left)/2), rect.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE);

		ShowWindow (hwnd_dialog, SW_SHOWDEFAULT);
		UpdateWindow (hwnd_dialog);
		SetForegroundWindow (hwnd_dialog);
	}

	// if we find the CD, add a +set cddir xxx command line
	char *cddir = Sys_ScanForCD ();
	if (cddir)
		q2cd_letter[0]=cddir[0];
	else
		q2cd_letter[0]=0;
	if (cddir && argc < MAX_NUM_ARGVS - 3)
	{
		int		i;

		// don't override a cddir on the command line
		for (i=0 ; i<argc ; i++)
			if (!Q_strcasecmp(argv[i], "cddir"))
				break;
		if (i == argc)
		{
			argv[argc++] = "+set";
			argv[argc++] = "cddir";
			argv[argc++] = cddir;
		}
	}

	Common_Init (argc, argv);
	M_SelectMath(16, 1024*1024, false, true);	// fast select progs

/*
	if (dedicated && dedicated->value)
		if (!WeAreAlone ("Berserker@Quake2"))
			Com_Error(ERR_FATAL, "Berserker's server already running!");
*/

	oldtime = Sys_Milliseconds ();

	/* main window message loop */
	while (1)
	{
		// if at a full screen console, don't update unless needed
		if (Minimized || (dedicated && dedicated->value) )
		{
			Sleep (1);
		}

		while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if (!GetMessage (&msg, NULL, 0, 0))
				Com_Quit ();
			sys_msg_time = msg.time;
			TranslateMessage (&msg);
   			DispatchMessage (&msg);
		}

		do
		{
			sys_time = Sys_Milliseconds ();
			time = sys_time - oldtime;
		} while (time < 1);

		_controlfp( _PC_24, _MCW_PC );
		Common_Frame (time);

		oldtime = sys_time;
	}

	// never gets here
    return 0;
}









/**************** OpenAL ******************/

char *al_error(int err)
{
	if (err == AL_INVALID_NAME)
		return "AL_INVALID_NAME";
	else if (err == AL_INVALID_ENUM)
		return "AL_INVALID_ENUM";
	else if (err == AL_INVALID_VALUE)
		return "AL_INVALID_VALUE";
	else if (err == AL_INVALID_OPERATION)
		return "AL_INVALID_OPERATION";
	else if (err == AL_OUT_OF_MEMORY)
		return "AL_OUT_OF_MEMORY";
	else if (err == AL_NO_ERROR)
		return "AL_NO_ERROR";
	return va("0x%X", err);
}





ALuint EFX_RvbCreate(EFXEAXREVERBPROPERTIES *rvb, unsigned index)
{
	unsigned err;
	bool	totalErr = false;
	ALuint effect;
	EFXEAXREVERBPROPERTIES *rvbGeneric = &rvb_GENERIC;

	alGenEffects(1, &effect);

	alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		Com_Printf("^1EFX preset %i (%s) failed on AL_EFFECT_TYPE, error %s\n", index, EFX_Names[index], al_error(err));
		totalErr = true;
	}

	alEffectf(effect, AL_EAXREVERB_DENSITY, rvb->flDensity);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_DENSITY, rvbGeneric->flDensity);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_DENSITY, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_DENSITY, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flDensity);
	}

	alEffectf(effect, AL_EAXREVERB_DIFFUSION, rvb->flDiffusion);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_DIFFUSION, rvbGeneric->flDiffusion);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_DIFFUSION, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_DIFFUSION, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flDiffusion);
	}

	alEffectf(effect, AL_EAXREVERB_GAIN, rvb->flGain);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_GAIN, rvbGeneric->flGain);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_GAIN, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_GAIN, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flGain);
	}

	alEffectf(effect, AL_EAXREVERB_GAINHF, rvb->flGainHF);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_GAINHF, rvbGeneric->flGainHF);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_GAINHF, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_GAINHF, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flGainHF);
	}

	alEffectf(effect, AL_EAXREVERB_GAINLF, rvb->flGainLF);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_GAINLF, rvbGeneric->flGainLF);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_GAINLF, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_GAINLF, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flGainLF);
	}

	alEffectf(effect, AL_EAXREVERB_DECAY_TIME, rvb->flDecayTime);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_DECAY_TIME, rvbGeneric->flDecayTime);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_DECAY_TIME, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_DECAY_TIME, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flDecayTime);
	}

	alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, rvb->flDecayHFRatio);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, rvbGeneric->flDecayHFRatio);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_DECAY_HFRATIO, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_DECAY_HFRATIO, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flDecayHFRatio);
	}

	alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, rvb->flDecayLFRatio);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, rvbGeneric->flDecayLFRatio);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_DECAY_LFRATIO, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_DECAY_LFRATIO, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flDecayLFRatio);
	}

	alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, rvb->flReflectionsGain);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, rvbGeneric->flReflectionsGain);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_REFLECTIONS_GAIN, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_REFLECTIONS_GAIN, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flReflectionsGain);
	}

	alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, rvb->flReflectionsDelay);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, rvbGeneric->flReflectionsDelay);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_REFLECTIONS_DELAY, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_REFLECTIONS_DELAY, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flReflectionsDelay);
	}

	alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, rvb->flReflectionsPan);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, rvbGeneric->flReflectionsPan);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_REFLECTIONS_PAN, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_REFLECTIONS_PAN, but set to fail safe value: %2.4f %2.4f %2.4f\n", index, EFX_Names[index], rvbGeneric->flReflectionsPan[0], rvbGeneric->flReflectionsPan[1], rvbGeneric->flReflectionsPan[2]);
	}

	alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, rvb->flLateReverbGain);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, rvbGeneric->flLateReverbGain);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_LATE_REVERB_GAIN, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_LATE_REVERB_GAIN, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flLateReverbGain);
	}

	alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, rvb->flLateReverbDelay);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, rvbGeneric->flLateReverbDelay);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_LATE_REVERB_DELAY, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_LATE_REVERB_DELAY, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flLateReverbDelay);
	}

	alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, rvb->flLateReverbPan);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, rvbGeneric->flLateReverbPan);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_LATE_REVERB_PAN, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_LATE_REVERB_PAN, but set to fail safe value: %2.4f %2.4f %2.4f\n", index, EFX_Names[index], rvbGeneric->flLateReverbPan[0], rvbGeneric->flLateReverbPan[1], rvbGeneric->flLateReverbPan[2]);
	}

	alEffectf(effect, AL_EAXREVERB_ECHO_TIME, rvb->flEchoTime);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_ECHO_TIME, rvbGeneric->flEchoTime);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_ECHO_TIME, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_ECHO_TIME, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flEchoTime);
	}

	alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, rvb->flEchoDepth);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, rvbGeneric->flEchoDepth);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_ECHO_DEPTH, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_ECHO_DEPTH, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flEchoDepth);
	}

	alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, rvb->flModulationTime);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, rvbGeneric->flModulationTime);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_MODULATION_TIME, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_MODULATION_TIME, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flModulationTime);
	}

	alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, rvb->flModulationDepth);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, rvbGeneric->flModulationDepth);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_MODULATION_DEPTH, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_MODULATION_DEPTH, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flModulationDepth);
	}

	alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, rvb->flAirAbsorptionGainHF);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, rvbGeneric->flAirAbsorptionGainHF);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_AIR_ABSORPTION_GAINHF, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_AIR_ABSORPTION_GAINHF, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flAirAbsorptionGainHF);
	}

	alEffectf(effect, AL_EAXREVERB_HFREFERENCE, rvb->flHFReference);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_HFREFERENCE, rvbGeneric->flHFReference);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_HFREFERENCE, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_HFREFERENCE, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flHFReference);
	}

	alEffectf(effect, AL_EAXREVERB_LFREFERENCE, rvb->flLFReference);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_LFREFERENCE, rvbGeneric->flLFReference);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_LFREFERENCE, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_LFREFERENCE, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flLFReference);
	}

	alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, rvb->flRoomRolloffFactor);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, rvbGeneric->flRoomRolloffFactor);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, but set to fail safe value: %2.4f\n", index, EFX_Names[index], rvbGeneric->flRoomRolloffFactor);
	}

	alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, rvb->iDecayHFLimit);
	err = alGetError();
	if (err != AL_NO_ERROR)
	{
		// try to set to fail safe value
		alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, rvbGeneric->iDecayHFLimit);
		err = alGetError();
		if (err != AL_NO_ERROR)
		{
			Com_Printf("^1EFX preset %i (%s) failed on AL_EAXREVERB_DECAY_HFLIMIT, error %s\n", index, EFX_Names[index], al_error(err));
			totalErr = true;
		}
		else
			Com_Printf("^3EFX preset %i (%s) failed on AL_EAXREVERB_DECAY_HFLIMIT, but set to fail safe value: %i\n", index, EFX_Names[index], rvbGeneric->iDecayHFLimit);
	}

	if (totalErr)
	{
		alDeleteEffects(1, &effect);
		alGetError();
		effect = AL_EFFECT_NULL;
	}
	else
		numEFXeffectsLoaded++;

	return effect;
}


/*
=================
QAL_Shutdown

Unloads the specified DLL then nulls out all the proc pointers
=================
*/
void QAL_Shutdown()
{
	Com_Printf("...shutting down QAL\n");

	if (alConfig.hInstOpenAL)
	{
		Com_Printf("...unloading 'OpenAL32.dll'\n");
		FreeLibrary(alConfig.hInstOpenAL);
	}
	Memset(&alConfig, 0, sizeof(alConfig_t));

	alcOpenDevice = NULL;
	alcCloseDevice = NULL;
	alcCreateContext = NULL;
	alcDestroyContext = NULL;
	alcMakeContextCurrent = NULL;
	alcProcessContext = NULL;
	alcSuspendContext = NULL;
	alcGetCurrentContext = NULL;
	alcGetContextsDevice = NULL;
	alcGetString = NULL;
	alcGetIntegerv = NULL;
	alcGetError = NULL;
	alcIsExtensionPresent = NULL;
	alcGetProcAddress = NULL;
	alcGetEnumValue = NULL;
	alSource3i = NULL;
	alBufferData = NULL;
	alDeleteBuffers = NULL;
	alDeleteSources = NULL;
	alDistanceModel = NULL;
	alDisable = NULL;
	alDopplerFactor = NULL;
	alDopplerVelocity = NULL;
	alSpeedOfSound = NULL;
	alEnable = NULL;
	alGenBuffers = NULL;
	alGenSources = NULL;
	alGetBoolean = NULL;
	alGetBooleanv = NULL;
	alGetBufferf = NULL;
	alGetBufferi = NULL;
	alGetDouble = NULL;
	alGetDoublev = NULL;
	alGetEnumValue = NULL;
	alGetError = NULL;
	alGetFloat = NULL;
	alGetFloatv = NULL;
	alGetInteger = NULL;
	alGetIntegerv = NULL;
	alGetListener3f = NULL;
	alGetListenerf = NULL;
	alGetListenerfv = NULL;
	alGetListeneri = NULL;
	alGetProcAddress = NULL;
	alGetSource3f = NULL;
	alGetSourcef = NULL;
	alGetSourcefv = NULL;
	alGetSourcei = NULL;
	alGetString = NULL;
	alIsBuffer = NULL;
	alIsEnabled = NULL;
	alIsExtensionPresent = NULL;
	alIsSource = NULL;
	alListener3f = NULL;
	alListenerf = NULL;
	alListenerfv = NULL;
	alListeneri = NULL;
	alSource3f = NULL;
	alSourcef = NULL;
	alSourcefv = NULL;
	alSourcei = NULL;
	alSourcePause = NULL;
	alSourcePausev = NULL;
	alSourcePlay = NULL;
	alSourcePlayv = NULL;
	alSourceQueueBuffers = NULL;
	alSourceRewind = NULL;
	alSourceRewindv = NULL;
	alSourceStop = NULL;
	alSourceStopv = NULL;
	alSourceUnqueueBuffers = NULL;

	// Effect objects
	alGenEffects = NULL;
	alDeleteEffects = NULL;
	alIsEffect = NULL;
	alEffecti = NULL;
	alEffectiv = NULL;
	alEffectf = NULL;
	alEffectfv = NULL;
	alGetEffecti = NULL;
	alGetEffectiv = NULL;
	alGetEffectf = NULL;
	alGetEffectfv = NULL;

	//Filter objects
	alGenFilters = NULL;
	alDeleteFilters = NULL;
	alIsFilter = NULL;
	alFilteri = NULL;
	alFilteriv = NULL;
	alFilterf = NULL;
	alFilterfv = NULL;
	alGetFilteri = NULL;
	alGetFilteriv = NULL;
	alGetFilterf = NULL;
	alGetFilterfv = NULL;

	// Auxiliary slot object
	alGenAuxiliaryEffectSlots = NULL;
	alDeleteAuxiliaryEffectSlots = NULL;
	alIsAuxiliaryEffectSlot = NULL;
	alAuxiliaryEffectSloti = NULL;
	alAuxiliaryEffectSlotiv = NULL;
	alAuxiliaryEffectSlotf = NULL;
	alAuxiliaryEffectSlotfv = NULL;
	alGetAuxiliaryEffectSloti = NULL;
	alGetAuxiliaryEffectSlotiv = NULL;
	alGetAuxiliaryEffectSlotf = NULL;
	alGetAuxiliaryEffectSlotfv = NULL;
}

/*
=================
AL_InitDriver
=================
*/
static bool AL_InitDriver()
{
	char *deviceName = s_openal_device->string;

	Com_Printf("Initializing OpenAL driver\n");

	if (!deviceName[0])
		deviceName = NULL;

	if (deviceName)
		Com_Printf("...opening device (%s): ", deviceName);
	else
		Com_Printf("...opening default device: ");

	// Open the device
	if ((alConfig.hDevice = alcOpenDevice(deviceName)) == NULL)
	{
		Com_Printf("^1failed\n");
		return false;
	}

	if (deviceName)
		Com_Printf("^2succeeded\n");
	else
		Com_Printf("^2succeeded ^7(%s)\n", alcGetString(alConfig.hDevice, ALC_DEVICE_SPECIFIER));

	// Create the AL context and make it current
	Com_Printf("...creating AL context: ");
	{
		soft_hrtf = false;
		if (alcIsExtensionPresent(alConfig.hDevice, "ALC_SOFT_HRTF") == AL_TRUE) soft_hrtf = true;

#ifndef ALC_SOFT_HRTF
#define ALC_SOFT_HRTF	1
#define ALC_HRTF_SOFT	0x1992
#endif

		// Setup context attributes at context creation time:
		// ALC_FREQUENCY, ALC_REFRESH, ALC_SYNC, ALC_MONO_SOURCES, ALC_STEREO_SOURCES
		ALCint attrlist[6] = { ALC_FREQUENCY, 44100, ALC_HRTF_SOFT, (s_useHRTF->value && soft_hrtf) ? ALC_TRUE : AL_FALSE, 0 };
		ALCint *attrlist_p;

		if (!(s_quality->value))
			attrlist_p = NULL;
		else {
			switch ((int)s_quality->value)
			{
			case 1:
				attrlist[1] = 22050;
				break;
			case 2:
			default:
				attrlist[1] = 44100;
				break;
			case 3:
				attrlist[1] = 48000;
				break;
			case 4:
				attrlist[1] = 88200;
				break;
			case 5:
				attrlist[1] = 96000;
				break;
			case 6:
				attrlist[1] = 176400;
				break;
			case 7:
				attrlist[1] = 192000;
				break;
			}
			attrlist_p = attrlist;
		}

		if ((alConfig.hALC = alcCreateContext(alConfig.hDevice, attrlist_p)) == NULL)
		{
			Com_Printf("^1failed\n");
			goto failed;
		}
	}
	Com_Printf("^2succeeded\n");

	Com_Printf("...making context current: ");
	if (!alcMakeContextCurrent(alConfig.hALC))
	{
		Com_Printf("^1failed\n");
		goto failed;
	}
	Com_Printf("^2succeeded\n");
	return true;

failed:
	Com_Printf("^1X..failed hard\n");
	openalStop = true;

	if (alConfig.hALC)
	{
		alcDestroyContext(alConfig.hALC);
		alConfig.hALC = NULL;
	}

	if (alConfig.hDevice)
	{
		alcCloseDevice(alConfig.hDevice);
		alConfig.hDevice = NULL;
	}

	return false;
}

/*
=================
AL_StartOpenAL
=================
*/
bool AL_StartOpenAL()
{
	alConfig.device_count = 0;
	// Get device list
	if (alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
	{
		unsigned i = 0;
		const char *a = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
		if (!a)
		{
			// We have no audio output devices. No hope.
			QAL_Shutdown();
			return false;
		}
		while (*a)
		{
			al_device[++i] = (char*)a;
			Com_Printf("Device species %i: ^2%s\n", i, a);
			while (*a)
				a++;
			a++;
		}
		alConfig.device_count = i;
	}
	else
	{
		// OS Specific devices. We are here, because device enumeration failed!
#ifdef _WIN32
		al_device[1] = "DirectSound3D";
		al_device[2] = "DirectSound";
		al_device[3] = "MMSYSTEM";
		alConfig.device_count = 3;
#else
		QAL_Shutdown();
		return false;
#endif
	}

	// Initialize the device, context, etc...
	if (AL_InitDriver())
		return true;
	else
	{
		QAL_Shutdown();
		return false;
	}
}

/*
=================
AL_Init
=================
*/
bool AL_Init()
{
	char	path[MAX_OSPATH];
	Com_Printf("Starting OpenAL audio subsystem...\n");

	if (!SearchPath(NULL, "OpenAL32.dll", NULL, sizeof(path), path, NULL))
	{
		Com_Printf("^1X..couldn't find OpenAL driver 'OpenAL32.dll'\n");
		openalStop = true;
		return false;
	}

	Com_Printf("...LoadLibrary( '%s' ) \n", path);
	if ((alConfig.hInstOpenAL = LoadLibrary(path)) == NULL)
	{
		Com_Printf("^1X..couldn't load OpenAL driver 'OpenAL32.dll'\n");
		openalStop = true;
		return false;
	}

	// Binds our AL function pointers to the appropriate AL stuff
	alcOpenDevice = (LPALCOPENDEVICE)GPA("alcOpenDevice");
	alcCloseDevice = (LPALCCLOSEDEVICE)GPA("alcCloseDevice");
	alcCreateContext = (LPALCCREATECONTEXT)GPA("alcCreateContext");
	alcDestroyContext = (LPALCDESTROYCONTEXT)GPA("alcDestroyContext");
	alcMakeContextCurrent = (LPALCMAKECONTEXTCURRENT)GPA("alcMakeContextCurrent");
	alcProcessContext = (LPALCPROCESSCONTEXT)GPA("alcProcessContext");
	alcSuspendContext = (LPALCSUSPENDCONTEXT)GPA("alcSuspendContext");
	alcGetCurrentContext = (LPALCGETCURRENTCONTEXT)GPA("alcGetCurrentContext");
	alcGetContextsDevice = (LPALCGETCONTEXTSDEVICE)GPA("alcGetContextsDevice");
	alcGetString = (LPALCGETSTRING)GPA("alcGetString");
	alcGetIntegerv = (LPALCGETINTEGERV)GPA("alcGetIntegerv");
	alcGetError = (LPALCGETERROR)GPA("alcGetError");
	alcIsExtensionPresent = (LPALCISEXTENSIONPRESENT)GPA("alcIsExtensionPresent");
	alcGetProcAddress = (LPALCGETPROCADDRESS)GPA("alcGetProcAddress");
	alcGetEnumValue = (LPALCGETENUMVALUE)GPA("alcGetEnumValue");

	alBufferData = (LPALBUFFERDATA)GPA("alBufferData");
	alDeleteBuffers = (LPALDELETEBUFFERS)GPA("alDeleteBuffers");
	alDeleteSources = (LPALDELETESOURCES)GPA("alDeleteSources");

	alDistanceModel = (LPALDISTANCEMODEL)GPA("alDistanceModel");
	alDisable = (LPALDISABLE)GPA("alDisable");
	alDopplerFactor = (LPALDOPPLERFACTOR)GPA("alDopplerFactor");
	alDopplerVelocity = (LPALDOPPLERVELOCITY)GPA("alDopplerVelocity");
	alSpeedOfSound = (LPALSPEEDOFSOUND)GPA("alSpeedOfSound");
	alEnable = (LPALENABLE)GPA("alEnable");
	alGenBuffers = (LPALGENBUFFERS)GPA("alGenBuffers");
	alGenSources = (LPALGENSOURCES)GPA("alGenSources");
	alGetBoolean = (LPALGETBOOLEAN)GPA("alGetBoolean");
	alGetBooleanv = (LPALGETBOOLEANV)GPA("alGetBooleanv");
	alGetBufferf = (LPALGETBUFFERF)GPA("alGetBufferf");
	alGetBufferi = (LPALGETBUFFERI)GPA("alGetBufferi");
	alGetDouble = (LPALGETDOUBLE)GPA("alGetDouble");
	alGetDoublev = (LPALGETDOUBLEV)GPA("alGetDoublev");
	alGetEnumValue = (LPALGETENUMVALUE)GPA("alGetEnumValue");
	alGetError = (LPALGETERROR)GPA("alGetError");
	alGetFloat = (LPALGETFLOAT)GPA("alGetFloat");
	alGetFloatv = (LPALGETFLOATV)GPA("alGetFloatv");
	alGetInteger = (LPALGETINTEGER)GPA("alGetInteger");
	alGetIntegerv = (LPALGETINTEGERV)GPA("alGetIntegerv");
	alGetListener3f = (LPALGETLISTENER3F)GPA("alGetListener3f");
	alGetListenerf = (LPALGETLISTENERF)GPA("alGetListenerf");
	alGetListenerfv = (LPALGETLISTENERFV)GPA("alGetListenerfv");
	alGetListeneri = (LPALGETLISTENERI)GPA("alGetListeneri");
	alGetProcAddress = (LPALGETPROCADDRESS)GPA("alGetProcAddress");
	alGetSource3f = (LPALGETSOURCE3F)GPA("alGetSource3f");
	alGetSourcef = (LPALGETSOURCEF)GPA("alGetSourcef");
	alGetSourcefv = (LPALGETSOURCEFV)GPA("alGetSourcefv");
	alGetSourcei = (LPALGETSOURCEI)GPA("alGetSourcei");
	alGetString = (LPALGETSTRING)GPA("alGetString");
	alIsBuffer = (LPALISBUFFER)GPA("alIsBuffer");
	alIsEnabled = (LPALISENABLED)GPA("alIsEnabled");
	alIsExtensionPresent = (LPALISEXTENSIONPRESENT)GPA("alIsExtensionPresent");
	alIsSource = (LPALISSOURCE)GPA("alIsSource");
	alListener3f = (LPALLISTENER3F)GPA("alListener3f");
	alListenerf = (LPALLISTENERF)GPA("alListenerf");
	alListenerfv = (LPALLISTENERFV)GPA("alListenerfv");
	alListeneri = (LPALLISTENERI)GPA("alListeneri");
	alSource3f = (LPALSOURCE3F)GPA("alSource3f");
	alSourcef = (LPALSOURCEF)GPA("alSourcef");
	alSourcefv = (LPALSOURCEFV)GPA("alSourcefv");
	alSourcei = (LPALSOURCEI)GPA("alSourcei");
	alSourcePause = (LPALSOURCEPAUSE)GPA("alSourcePause");
	alSourcePausev = (LPALSOURCEPAUSEV)GPA("alSourcePausev");
	alSourcePlay = (LPALSOURCEPLAY)GPA("alSourcePlay");
	alSourcePlayv = (LPALSOURCEPLAYV)GPA("alSourcePlayv");
	alSourceQueueBuffers = (LPALSOURCEQUEUEBUFFERS)GPA("alSourceQueueBuffers");
	alSourceRewind = (LPALSOURCEREWIND)GPA("alSourceRewind");
	alSourceRewindv = (LPALSOURCEREWINDV)GPA("alSourceRewindv");
	alSourceStop = (LPALSOURCESTOP)GPA("alSourceStop");
	alSourceStopv = (LPALSOURCESTOPV)GPA("alSourceStopv");
	alSourceUnqueueBuffers = (LPALSOURCEUNQUEUEBUFFERS)GPA("alSourceUnqueueBuffers");
	alSource3i = (LPALSOURCE3I)alGetProcAddress("alSource3i");

	// Initialize OpenAL subsystem
	if (!AL_StartOpenAL())
	{	// Let the user continue without sound
		Com_Printf("^1X..OpenAL initialization failed\n");
		openalStop = true;
		return false;
	}

	// Initialize extensions
	alConfig.efx = false;

	// Check for ALC Extensions
	if (alcIsExtensionPresent(alConfig.hDevice, "ALC_EXT_CAPTURE") == AL_TRUE)
		Com_Printf("...capture capabilities.\n");

	// If EFX is enabled, determine if it's available and use it
	if (s_openal_efx->value)
	{
		if (alcIsExtensionPresent(alConfig.hDevice, "ALC_EXT_EFX") == AL_TRUE)
		{
			ALuint		uiEffectSlots[128] = { 0 };
			ALuint		uiEffects[1] = { 0 };
			ALuint		uiFilters[1] = { 0 };
			ALint		iEffectSlotsGenerated;
			ALint		iSends;

			alConfig.efx = true;

			// Imported EFX functions; Get function pointers
			// Effect objects
			alGenEffects = (LPALGENEFFECTS)alGetProcAddress("alGenEffects");
			alDeleteEffects = (LPALDELETEEFFECTS)alGetProcAddress("alDeleteEffects");
			alIsEffect = (LPALISEFFECT)alGetProcAddress("alIsEffect");
			alEffecti = (LPALEFFECTI)alGetProcAddress("alEffecti");
			alEffectiv = (LPALEFFECTIV)alGetProcAddress("alEffectiv");
			alEffectf = (LPALEFFECTF)alGetProcAddress("alEffectf");
			alEffectfv = (LPALEFFECTFV)alGetProcAddress("alEffectfv");
			alGetEffecti = (LPALGETEFFECTI)alGetProcAddress("alGetEffecti");
			alGetEffectiv = (LPALGETEFFECTIV)alGetProcAddress("alGetEffectiv");
			alGetEffectf = (LPALGETEFFECTF)alGetProcAddress("alGetEffectf");
			alGetEffectfv = (LPALGETEFFECTFV)alGetProcAddress("alGetEffectfv");
			//Filter objects
			alGenFilters = (LPALGENFILTERS)alGetProcAddress("alGenFilters");
			alDeleteFilters = (LPALDELETEFILTERS)alGetProcAddress("alDeleteFilters");
			alIsFilter = (LPALISFILTER)alGetProcAddress("alIsFilter");
			alFilteri = (LPALFILTERI)alGetProcAddress("alFilteri");
			alFilteriv = (LPALFILTERIV)alGetProcAddress("alFilteriv");
			alFilterf = (LPALFILTERF)alGetProcAddress("alFilterf");
			alFilterfv = (LPALFILTERFV)alGetProcAddress("alFilterfv");
			alGetFilteri = (LPALGETFILTERI)alGetProcAddress("alGetFilteri");
			alGetFilteriv = (LPALGETFILTERIV)alGetProcAddress("alGetFilteriv");
			alGetFilterf = (LPALGETFILTERF)alGetProcAddress("alGetFilterf");
			alGetFilterfv = (LPALGETFILTERFV)alGetProcAddress("alGetFilterfv");
			// Auxiliary slot object
			alGenAuxiliaryEffectSlots = (LPALGENAUXILIARYEFFECTSLOTS)alGetProcAddress("alGenAuxiliaryEffectSlots");
			alDeleteAuxiliaryEffectSlots = (LPALDELETEAUXILIARYEFFECTSLOTS)alGetProcAddress("alDeleteAuxiliaryEffectSlots");
			alIsAuxiliaryEffectSlot = (LPALISAUXILIARYEFFECTSLOT)alGetProcAddress("alIsAuxiliaryEffectSlot");
			alAuxiliaryEffectSloti = (LPALAUXILIARYEFFECTSLOTI)alGetProcAddress("alAuxiliaryEffectSloti");
			alAuxiliaryEffectSlotiv = (LPALAUXILIARYEFFECTSLOTIV)alGetProcAddress("alAuxiliaryEffectSlotiv");
			alAuxiliaryEffectSlotf = (LPALAUXILIARYEFFECTSLOTF)alGetProcAddress("alAuxiliaryEffectSlotf");
			alAuxiliaryEffectSlotfv = (LPALAUXILIARYEFFECTSLOTFV)alGetProcAddress("alAuxiliaryEffectSlotfv");
			alGetAuxiliaryEffectSloti = (LPALGETAUXILIARYEFFECTSLOTI)alGetProcAddress("alGetAuxiliaryEffectSloti");
			alGetAuxiliaryEffectSlotiv = (LPALGETAUXILIARYEFFECTSLOTIV)alGetProcAddress("alGetAuxiliaryEffectSlotiv");
			alGetAuxiliaryEffectSlotf = (LPALGETAUXILIARYEFFECTSLOTF)alGetProcAddress("alGetAuxiliaryEffectSlotf");
			alGetAuxiliaryEffectSlotfv = (LPALGETAUXILIARYEFFECTSLOTFV)alGetProcAddress("alGetAuxiliaryEffectSlotfv");

			// To determine how many Auxiliary Effects Slots are available, create as many as possible (up to 128)
			// until the call fails.
			for (iEffectSlotsGenerated = 0; iEffectSlotsGenerated < 128; iEffectSlotsGenerated++)
			{
				alGenAuxiliaryEffectSlots(1, &uiEffectSlots[iEffectSlotsGenerated]);
				if (alGetError() != AL_NO_ERROR)
					break;
			}

			Com_Printf("\n%d Auxiliary Effect Slot%s\n", iEffectSlotsGenerated, (iEffectSlotsGenerated == 1) ? "" : "s");

			// Retrieve the number of Auxiliary Effect Slots Sends available on each Source
			alcGetIntegerv(alConfig.hDevice, ALC_MAX_AUXILIARY_SENDS, 1, &iSends);
			Com_Printf("%d Auxiliary Send%s per Source\n", iSends, (iSends == 1) ? "" : "s");

			// To determine which Effects are supported, generate an Effect Object, and try to set its type to
			// the various Effect enum values
			Com_Printf("\nEffects Supported:\n------------------\n");
			alGenEffects(1, &uiEffects[0]);
			if (alGetError() == AL_NO_ERROR)
			{
				// Try setting Effect Type to known Effects
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_REVERB);
				Com_Printf("'Reverb' Support            %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
				Com_Printf("'EAX Reverb' Support        %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_CHORUS);
				Com_Printf("'Chorus' Support            %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_DISTORTION);
				Com_Printf("'Distortion' Support        %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_ECHO);
				Com_Printf("'Echo' Support              %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_FLANGER);
				Com_Printf("'Flanger' Support           %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_FREQUENCY_SHIFTER);
				Com_Printf("'Frequency Shifter' Support %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_VOCAL_MORPHER);
				Com_Printf("'Vocal Morpher' Support     %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_PITCH_SHIFTER);
				Com_Printf("'Pitch Shifter' Support     %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_RING_MODULATOR);
				Com_Printf("'Ring Modulator' Support    %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_AUTOWAH);
				Com_Printf("'Autowah' Support           %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_COMPRESSOR);
				Com_Printf("'Compressor' Support        %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_EQUALIZER);
				Com_Printf("'Equalizer' Support         %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
			}
			// To determine which Filters are supported, generate a Filter Object, and try to set its type to
			// the various Filter enum values
			Com_Printf("\nFilters Supported:\n------------------\n");
			// Generate a Filter to use to determine what Filter Types are supported
			alGenFilters(1, &uiFilters[0]);
			if (alGetError() == AL_NO_ERROR)
			{
				// Try setting the Filter type to known Filters
				alFilteri(uiFilters[0], AL_FILTER_TYPE, AL_FILTER_LOWPASS);
				Com_Printf("'Low Pass'  Support         %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alFilteri(uiFilters[0], AL_FILTER_TYPE, AL_FILTER_HIGHPASS);
				Com_Printf("'High Pass' Support         %s\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
				alFilteri(uiFilters[0], AL_FILTER_TYPE, AL_FILTER_BANDPASS);
				Com_Printf("'Band Pass' Support         %s\n\n", (alGetError() == AL_NO_ERROR) ? "^2YES" : " ^1NO");
			}
			// Clean-up ...
			// Delete Filter
			alDeleteFilters(1, &uiFilters[0]);
			// Delete Effect
			alDeleteEffects(1, &uiEffects[0]);
			// Delete Auxiliary Effect Slots
			alDeleteAuxiliaryEffectSlots(iEffectSlotsGenerated, uiEffectSlots);
		}
	}//<---(!s_openal_efx->value)

	 // Check for AL Extensions
	if (alIsExtensionPresent("AL_EXT_OFFSET") == AL_TRUE)
		Com_Printf("...sample offset capabilities.\n");
	if (alIsExtensionPresent("AL_EXT_LINEAR_DISTANCE") == AL_TRUE)
		Com_Printf("...unlocked linear distance preset.\n");
	if (alIsExtensionPresent("AL_EXT_EXPONENT_DISTANCE") == AL_TRUE)
		Com_Printf("...unlocked exponent distance preset.\n");

	return true;
}

void S_SoundInfo_f(void)
{
	const char *exts;

	if (!alConfig.hALC)
	{
		Com_Printf("^1Cannot provide OpenAL information\n");
		return;
	}

	Com_Printf("\nAL_VENDOR:     ^2%s\n", alGetString(AL_VENDOR));
	Com_Printf("AL_RENDERER:   ^2%s\n", alGetString(AL_RENDERER));
	Com_Printf("AL_VERSION:    ^2%s\n", alGetString(AL_VERSION));
	Com_Printf("AL_DEVICE:     ^2%s\n", alcGetString(alConfig.hDevice, ALC_DEVICE_SPECIFIER));
	Com_Printf("EFX reverberation: ");
	if (efx.on)
		Com_Printf("^2enabled\n");
	else
		Com_Printf("^1disabled\n");
	Com_Printf("Soft HRTF:         ");
	if (soft_hrtf && s_useHRTF->value)
		Com_Printf("^2enabled\n");
	else
		Com_Printf("^1disabled\n");
	Com_Printf("\nAL_EXTENSIONS:\n--------------\n");
	exts = alGetString(AL_EXTENSIONS);
	if (exts)
	{
		unsigned l = strlen(exts), ll;
		if (l > 0)
		{
			char buf[128], c;
			char *ptr = (char *)exts;
			char *p;
			while (1)
			{
				ll = 0;
				p = (char*)ptr;
				while (1)
				{
					c = *p;
					if (!c || c == ' ')
						break;
					ll++;
					p++;
				}
				if (ll >= sizeof(buf))
				{
					Com_Printf("^1*** extension too long: %i bytes ***\n", ll);
					break;
				}
				if (ll)
				{
					Memcpy(buf, ptr, ll);
					buf[ll] = 0;
					Com_Printf("^2%s\n", buf);
				}
				if (!c)
					break;
				ptr = p + 1;
			}
		}
		else
			Com_Printf("^1*** strlen(exts) = 0 ***\n");
	}
	else
		Com_Printf("^1*** exts = NULL ***\n");
}

static void AllocChannels()
{
	s_openal_numChannels = MAX_CHANNELS + 1;	// +1 streaming channel

	while (s_openal_numChannels > MIN_CHANNELS)
	{
		alGenSources(s_openal_numChannels, source_name);
		--s_openal_numChannels;
		if (alGetError() == AL_NO_ERROR)
		{
			Com_Printf("...%i mix channels allocated.\n", s_openal_numChannels);
			Com_Printf("...streaming channel allocated.\n\n");
			return;
		}
	}

	Com_Printf("^1X..not enough mix channels\n");
	s_openal_numChannels = 0;
}


void S_fastsound_get_descriptors_pool(unsigned count, ALuint * descriptors_pool)
{
	alGenBuffers(count, descriptors_pool);
}

byte *data_p;
byte *iff_end;
byte *last_chunk;
byte *iff_data;
int iff_chunk_len;


short GetLittleShort(void)
{
#if 0
	short val = *data_p;
	val = val + (*(data_p + 1) << 8);
#else
	short val = *((short *)data_p);
#endif
	data_p += 2;
	return val;
}

long GetLittleLong(void)
{
#if 0
	long val = *data_p;
	val = val + (*(data_p + 1) << 8);
	val = val + (*(data_p + 2) << 16);
	val = val + (*(data_p + 3) << 24);
#else
	long val = *((long *)data_p);
#endif
	data_p += 4;
	return val;
}

void FindNextChunk(char *name)
{
	while (1)
	{
		data_p = last_chunk;

		if (data_p >= iff_end)
		{	// didn't find the chunk
			data_p = NULL;
			return;
		}

		data_p += 4;
		iff_chunk_len = GetLittleLong();
		if (iff_chunk_len < 0)
		{
			data_p = NULL;
			return;
		}
//      if (iff_chunk_len > 1024*1024)
//          Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len);
		data_p -= 8;
		last_chunk = data_p + 8 + ((iff_chunk_len + 1) & ~1);
		if (!strncmp((const char*)data_p, name, 4))
			return;
	}
}

void FindChunk(char *name)
{
	last_chunk = iff_data;
	FindNextChunk(name);
}

static bool LoadWAV(char *name, byte ** wav, ALenum * format, ALsizei * rate, ALsizei * size)
{
	byte *buffer, *out;
	short channels, width;
	int length = FS_LoadFile(name, (void **)&buffer);

	if (!buffer)
		return false;

	iff_data = buffer;
	iff_end = buffer + length;

	// Find "RIFF" chunk
	FindChunk("RIFF");
	if (!(data_p && !memcmp((void *)(data_p + 8), "WAVE", 4)))
	{
		Com_Printf("^1LoadWAV: missing 'RIFF/WAVE' chunks (%s)\n", name);
		Z_Free(buffer);
		return false;
	}
	// Get "fmt " chunk
	iff_data = data_p + 12;

	FindChunk("fmt ");
	if (!data_p)
	{
		Com_Printf("^1LoadWAV: missing 'fmt ' chunk (%s)\n", name);
		Z_Free(buffer);
		return false;
	}

	data_p += 8;

	if (GetLittleShort() != 1)
	{
		Com_Printf("^1LoadWAV: Microsoft PCM format only (%s)\n", name);
		Z_Free(buffer);
		return false;
	}

	*format = AL_FORMAT_MONO8;
	channels = GetLittleShort();
	if (channels < 1 || channels > 2)
	{
		Com_Printf("^1LoadWAV: only mono and stereo WAV files supported (%s)\n", name);
		Z_Free(buffer);
		return false;
	}
	if (channels == 2)
		*format |= PCM_STEREO;

	*rate = GetLittleLong();

	data_p += 4 + 2;

	width = GetLittleShort() >> 3;
	if (width != 1 && width != 2)
	{
		Com_Printf("^1LoadWAV: only 8 and 16 bit WAV files supported (%s)\n", name);
		Z_Free(buffer);
		return false;
	}
	if (width == 2)
		*format |= PCM_16BIT;

	// Find data chunk
	FindChunk("data");
	if (!data_p)
	{
		Com_Printf("^1LoadWAV: missing 'data' chunk (%s)\n", name);
		Z_Free(buffer);
		return false;
	}

	data_p += 4;
	*size = GetLittleLong();

	if (*size == 0)
	{
		Com_Printf("^1LoadWAV: file with 0 samples (%s)\n", name);
		Z_Free(buffer);
		return false;
	}
	// Load the data
	*wav = out = (byte*)Z_Malloc(*size, true);
	Memcpy(out, buffer + (data_p - buffer), *size);

	Z_Free(buffer);

	return true;
}

void S_fastsound_load(char *input_name, ALuint bufferNum)
{
	unsigned err;
	char name[MAX_QPATH];
	byte *data;
	ALsizei rate;				// the frequency of the audio data
	ALenum format;
	ALsizei size;

	// Load it from disk
	if (input_name[0] == '#')
		Com_sprintf(name, sizeof(name), "%s", &input_name[1]);
	else
		Com_sprintf(name, sizeof(name), "sound/%s", input_name);

	if (!LoadWAV(name, &data, &format, &rate, &size))
	{
		Com_Printf("^1S_fastsound_load: BAD WAVE '%s'\n", name);
		return;
	}

	// Upload the sound
	alBufferData(bufferNum, format, data, size, rate);
	err = alGetError();
	if (err != AL_NO_ERROR)
		Com_Printf("^1S_fastsound_load: bad sample, error %s\n",al_error(err));

	Z_Free(data);
}

void CL_fast_sound_init(void)
{
	int i = FAST_SOUNDS_TOTAL;
	// Register waves
	S_fastsound_get_descriptors_pool(FAST_SOUNDS_TOTAL, fastsound_descriptor);
	while (i--)
	{
		// Actually load waves them from disk
		S_fastsound_load(fastsound_name[i], fastsound_descriptor[i]);
	}

	cl_sfx_weapons_blastf1a = fastsound_descriptor[id_cl_sfx_weapons_blastf1a];
	cl_sfx_weapons_hyprbf1a = fastsound_descriptor[id_cl_sfx_weapons_hyprbf1a];
	cl_sfx_weapons_machgf1b[0] = fastsound_descriptor[id_cl_sfx_weapons_machgf1b];
	cl_sfx_weapons_machgf1b[1] = fastsound_descriptor[id_cl_sfx_weapons_machgf2b];
	cl_sfx_weapons_machgf1b[2] = fastsound_descriptor[id_cl_sfx_weapons_machgf3b];
	cl_sfx_weapons_machgf1b[3] = fastsound_descriptor[id_cl_sfx_weapons_machgf4b];
	cl_sfx_weapons_machgf1b[4] = fastsound_descriptor[id_cl_sfx_weapons_machgf5b];
	cl_sfx_weapons_shotgf1b = fastsound_descriptor[id_cl_sfx_weapons_shotgf1b];
	cl_sfx_weapons_shotgr1b = fastsound_descriptor[id_cl_sfx_weapons_shotgr1b];
	cl_sfx_weapons_sshotf1b = fastsound_descriptor[id_cl_sfx_weapons_sshotf1b];
	cl_sfx_weapons_railgf1a = fastsound_descriptor[id_cl_sfx_weapons_railgf1a];
	cl_sfx_weapons_rocklf1a = fastsound_descriptor[id_cl_sfx_weapons_rocklf1a];
	cl_sfx_weapons_rocklr1b = fastsound_descriptor[id_cl_sfx_weapons_rocklr1b];
	cl_sfx_weapons_grenlf1a = fastsound_descriptor[id_cl_sfx_weapons_grenlf1a];
	cl_sfx_weapons_grenlr1b = fastsound_descriptor[id_cl_sfx_weapons_grenlr1b];
	cl_sfx_weapons_bfg__f1y = fastsound_descriptor[id_cl_sfx_weapons_bfg__f1y];
	cl_sfx_misc_talk = fastsound_descriptor[id_cl_sfx_misc_talk];
	cl_sfx_menu_in_sound = fastsound_descriptor[id_cl_sfx_menu_in_sound];
	cl_sfx_menu_move_sound = fastsound_descriptor[id_cl_sfx_menu_move_sound];
	cl_sfx_menu_out_sound = fastsound_descriptor[id_cl_sfx_menu_out_sound];
	cl_sfx_ric1 = fastsound_descriptor[id_cl_sfx_ric1];
	cl_sfx_ric2 = fastsound_descriptor[id_cl_sfx_ric2];
	cl_sfx_ric3 = fastsound_descriptor[id_cl_sfx_ric3];
	cl_sfx_lashit = fastsound_descriptor[id_cl_sfx_lashit];
	cl_sfx_spark5[0] = fastsound_descriptor[id_cl_sfx_spark5];
	cl_sfx_spark5[1] = fastsound_descriptor[id_cl_sfx_spark6];
	cl_sfx_spark5[2] = fastsound_descriptor[id_cl_sfx_spark7];
	cl_sfx_railg = fastsound_descriptor[id_cl_sfx_railg];
	cl_sfx_rockexp = fastsound_descriptor[id_cl_sfx_rockexp];
	cl_sfx_grenexp = fastsound_descriptor[id_cl_sfx_grenexp];
	cl_sfx_watrexp = fastsound_descriptor[id_cl_sfx_watrexp];
	cl_sfx_lava = fastsound_descriptor[id_cl_sfx_lava];
	cl_sfx_mshell = fastsound_descriptor[id_cl_sfx_mshell];
	cl_sfx_debris = fastsound_descriptor[id_cl_sfx_debris];
	cl_sfx_footsteps_0[0] = fastsound_descriptor[id_cl_sfx_footsteps_0];
	cl_sfx_footsteps_0[1] = fastsound_descriptor[id_cl_sfx_footsteps_1];
	cl_sfx_footsteps_0[2] = fastsound_descriptor[id_cl_sfx_footsteps_2];
	cl_sfx_footsteps_0[3] = fastsound_descriptor[id_cl_sfx_footsteps_3];
	cl_sfx_debris_glass = fastsound_descriptor[id_cl_sfx_debris_glass];
	cl_sfx_debris_metal = fastsound_descriptor[id_cl_sfx_debris_metal];
	cl_sfx_debris_flesh = fastsound_descriptor[id_cl_sfx_debris_flesh];
	cl_sfx_sshell = fastsound_descriptor[id_cl_sfx_sshell];
	cl_sfx_watershell = fastsound_descriptor[id_cl_sfx_watershell];
					///	cl_sfx_weapons_railgr1a = fastsound_descriptor[id_cl_sfx_weapons_railgr1a];
	cl_sfx_debris_flesh1 = fastsound_descriptor[id_cl_sfx_debris_flesh1];
	cl_sfx_debris_flesh2 = fastsound_descriptor[id_cl_sfx_debris_flesh2];

	cl_sfx_footsteps_splash_0[0] = fastsound_descriptor[id_cl_sfx_footsteps_splash_0];
	cl_sfx_footsteps_splash_0[1] = fastsound_descriptor[id_cl_sfx_footsteps_splash_1];
	cl_sfx_footsteps_splash_0[2] = fastsound_descriptor[id_cl_sfx_footsteps_splash_2];
	cl_sfx_footsteps_splash_0[3] = fastsound_descriptor[id_cl_sfx_footsteps_splash_3];
}


void EFX_RvbDeleteEffects()
{
	int i;
	for (i = 0; i < NUM_EFX_PRESETS; i++)
	{
		if (efx.rvbEffects[i] != AL_EFFECT_NULL)
			alDeleteEffects(1, &efx.rvbEffects[i]);
	}
}

void EFX_RvbInit()
{
	unsigned err;

	numEFXeffectsLoaded = 0;

	if (efx.on)
		return;

	efx.rvbEffects[EFX_PRESET_GENERIC] = EFX_RvbCreate(&rvb_GENERIC, EFX_PRESET_GENERIC);
	efx.rvbEffects[EFX_PRESET_PADDEDCELL] = EFX_RvbCreate(&rvb_PADDEDCELL, EFX_PRESET_PADDEDCELL);
	efx.rvbEffects[EFX_PRESET_ROOM] = EFX_RvbCreate(&rvb_ROOM, EFX_PRESET_ROOM);
	efx.rvbEffects[EFX_PRESET_BATHROOM] = EFX_RvbCreate(&rvb_BATHROOM, EFX_PRESET_BATHROOM);
	efx.rvbEffects[EFX_PRESET_LIVINGROOM] = EFX_RvbCreate(&rvb_LIVINGROOM, EFX_PRESET_LIVINGROOM);
	efx.rvbEffects[EFX_PRESET_STONEROOM] = EFX_RvbCreate(&rvb_STONEROOM, EFX_PRESET_STONEROOM);
	efx.rvbEffects[EFX_PRESET_AUDITORIUM] = EFX_RvbCreate(&rvb_AUDITORIUM, EFX_PRESET_AUDITORIUM);
	efx.rvbEffects[EFX_PRESET_CONCERTHALL] = EFX_RvbCreate(&rvb_CONCERTHALL, EFX_PRESET_CONCERTHALL);
	efx.rvbEffects[EFX_PRESET_CAVE] = EFX_RvbCreate(&rvb_CAVE, EFX_PRESET_CAVE);
	efx.rvbEffects[EFX_PRESET_ARENA] = EFX_RvbCreate(&rvb_ARENA, EFX_PRESET_ARENA);
	efx.rvbEffects[EFX_PRESET_HANGAR] = EFX_RvbCreate(&rvb_HANGAR, EFX_PRESET_HANGAR);
	efx.rvbEffects[EFX_PRESET_CARPETEDHALLWAY] = EFX_RvbCreate(&rvb_CARPETEDHALLWAY, EFX_PRESET_CARPETEDHALLWAY);
	efx.rvbEffects[EFX_PRESET_HALLWAY] = EFX_RvbCreate(&rvb_HALLWAY, EFX_PRESET_HALLWAY);
	efx.rvbEffects[EFX_PRESET_STONECORRIDOR] = EFX_RvbCreate(&rvb_STONECORRIDOR, EFX_PRESET_STONECORRIDOR);
	efx.rvbEffects[EFX_PRESET_ALLEY] = EFX_RvbCreate(&rvb_ALLEY, EFX_PRESET_ALLEY);
	efx.rvbEffects[EFX_PRESET_FOREST] = EFX_RvbCreate(&rvb_FOREST, EFX_PRESET_FOREST);
	efx.rvbEffects[EFX_PRESET_CITY] = EFX_RvbCreate(&rvb_CITY, EFX_PRESET_CITY);
	efx.rvbEffects[EFX_PRESET_MOUNTAINS] = EFX_RvbCreate(&rvb_MOUNTAINS, EFX_PRESET_MOUNTAINS);
	efx.rvbEffects[EFX_PRESET_QUARRY] = EFX_RvbCreate(&rvb_QUARRY, EFX_PRESET_QUARRY);
	efx.rvbEffects[EFX_PRESET_PLAIN] = EFX_RvbCreate(&rvb_PLAIN, EFX_PRESET_PLAIN);
	efx.rvbEffects[EFX_PRESET_PARKINGLOT] = EFX_RvbCreate(&rvb_PARKINGLOT, EFX_PRESET_PARKINGLOT);
	efx.rvbEffects[EFX_PRESET_SEWERPIPE] = EFX_RvbCreate(&rvb_SEWERPIPE, EFX_PRESET_SEWERPIPE);
	efx.rvbEffects[EFX_PRESET_UNDERWATER] = EFX_RvbCreate(&rvb_UNDERWATER, EFX_PRESET_UNDERWATER);
	efx.rvbEffects[EFX_PRESET_DRUGGED] = EFX_RvbCreate(&rvb_DRUGGED, EFX_PRESET_DRUGGED);
	efx.rvbEffects[EFX_PRESET_DIZZY] = EFX_RvbCreate(&rvb_DIZZY, EFX_PRESET_DIZZY);
	efx.rvbEffects[EFX_PRESET_PSYCHOTIC] = EFX_RvbCreate(&rvb_PSYCHOTIC, EFX_PRESET_PSYCHOTIC);
	efx.rvbEffects[EFX_PRESET_CASTLE_SMALLROOM] = EFX_RvbCreate(&rvb_CASTLE_SMALLROOM, EFX_PRESET_CASTLE_SMALLROOM);
	efx.rvbEffects[EFX_PRESET_CASTLE_SHORTPASSAGE] = EFX_RvbCreate(&rvb_CASTLE_SHORTPASSAGE, EFX_PRESET_CASTLE_SHORTPASSAGE);
	efx.rvbEffects[EFX_PRESET_CASTLE_MEDIUMROOM] = EFX_RvbCreate(&rvb_CASTLE_MEDIUMROOM, EFX_PRESET_CASTLE_MEDIUMROOM);
	efx.rvbEffects[EFX_PRESET_CASTLE_LARGEROOM] = EFX_RvbCreate(&rvb_CASTLE_LARGEROOM, EFX_PRESET_CASTLE_LARGEROOM);
	efx.rvbEffects[EFX_PRESET_CASTLE_LONGPASSAGE] = EFX_RvbCreate(&rvb_CASTLE_LONGPASSAGE, EFX_PRESET_CASTLE_LONGPASSAGE);
	efx.rvbEffects[EFX_PRESET_CASTLE_HALL] = EFX_RvbCreate(&rvb_CASTLE_HALL, EFX_PRESET_CASTLE_HALL);
	efx.rvbEffects[EFX_PRESET_CASTLE_CUPBOARD] = EFX_RvbCreate(&rvb_CASTLE_CUPBOARD, EFX_PRESET_CASTLE_CUPBOARD);
	efx.rvbEffects[EFX_PRESET_CASTLE_COURTYARD] = EFX_RvbCreate(&rvb_CASTLE_COURTYARD, EFX_PRESET_CASTLE_COURTYARD);
	efx.rvbEffects[EFX_PRESET_CASTLE_ALCOVE] = EFX_RvbCreate(&rvb_CASTLE_ALCOVE, EFX_PRESET_CASTLE_ALCOVE);
	efx.rvbEffects[EFX_PRESET_FACTORY_SMALLROOM] = EFX_RvbCreate(&rvb_FACTORY_SMALLROOM, EFX_PRESET_FACTORY_SMALLROOM);
	efx.rvbEffects[EFX_PRESET_FACTORY_SHORTPASSAGE] = EFX_RvbCreate(&rvb_FACTORY_SHORTPASSAGE, EFX_PRESET_FACTORY_SHORTPASSAGE);
	efx.rvbEffects[EFX_PRESET_FACTORY_MEDIUMROOM] = EFX_RvbCreate(&rvb_FACTORY_MEDIUMROOM, EFX_PRESET_FACTORY_MEDIUMROOM);
	efx.rvbEffects[EFX_PRESET_FACTORY_LARGEROOM] = EFX_RvbCreate(&rvb_FACTORY_LARGEROOM, EFX_PRESET_FACTORY_LARGEROOM);
	efx.rvbEffects[EFX_PRESET_FACTORY_LONGPASSAGE] = EFX_RvbCreate(&rvb_FACTORY_LONGPASSAGE, EFX_PRESET_FACTORY_LONGPASSAGE);
	efx.rvbEffects[EFX_PRESET_FACTORY_HALL] = EFX_RvbCreate(&rvb_FACTORY_HALL, EFX_PRESET_FACTORY_HALL);
	efx.rvbEffects[EFX_PRESET_FACTORY_CUPBOARD] = EFX_RvbCreate(&rvb_FACTORY_CUPBOARD, EFX_PRESET_FACTORY_CUPBOARD);
	efx.rvbEffects[EFX_PRESET_FACTORY_COURTYARD] = EFX_RvbCreate(&rvb_FACTORY_COURTYARD, EFX_PRESET_FACTORY_COURTYARD);
	efx.rvbEffects[EFX_PRESET_FACTORY_ALCOVE] = EFX_RvbCreate(&rvb_FACTORY_ALCOVE, EFX_PRESET_FACTORY_ALCOVE);
	efx.rvbEffects[EFX_PRESET_ICEPALACE_SMALLROOM] = EFX_RvbCreate(&rvb_ICEPALACE_SMALLROOM, EFX_PRESET_ICEPALACE_SMALLROOM);
	efx.rvbEffects[EFX_PRESET_ICEPALACE_SHORTPASSAGE] = EFX_RvbCreate(&rvb_ICEPALACE_SHORTPASSAGE, EFX_PRESET_ICEPALACE_SHORTPASSAGE);
	efx.rvbEffects[EFX_PRESET_ICEPALACE_MEDIUMROOM] = EFX_RvbCreate(&rvb_ICEPALACE_MEDIUMROOM, EFX_PRESET_ICEPALACE_MEDIUMROOM);
	efx.rvbEffects[EFX_PRESET_ICEPALACE_LARGEROOM] = EFX_RvbCreate(&rvb_ICEPALACE_LARGEROOM, EFX_PRESET_ICEPALACE_LARGEROOM);
	efx.rvbEffects[EFX_PRESET_ICEPALACE_LONGPASSAGE] = EFX_RvbCreate(&rvb_ICEPALACE_LONGPASSAGE, EFX_PRESET_ICEPALACE_LONGPASSAGE);
	efx.rvbEffects[EFX_PRESET_ICEPALACE_HALL] = EFX_RvbCreate(&rvb_ICEPALACE_HALL, EFX_PRESET_ICEPALACE_HALL);
	efx.rvbEffects[EFX_PRESET_ICEPALACE_CUPBOARD] = EFX_RvbCreate(&rvb_ICEPALACE_CUPBOARD, EFX_PRESET_ICEPALACE_CUPBOARD);
	efx.rvbEffects[EFX_PRESET_ICEPALACE_COURTYARD] = EFX_RvbCreate(&rvb_ICEPALACE_COURTYARD, EFX_PRESET_ICEPALACE_COURTYARD);
	efx.rvbEffects[EFX_PRESET_ICEPALACE_ALCOVE] = EFX_RvbCreate(&rvb_ICEPALACE_ALCOVE, EFX_PRESET_ICEPALACE_ALCOVE);
	efx.rvbEffects[EFX_PRESET_SPACESTATION_SMALLROOM] = EFX_RvbCreate(&rvb_SPACESTATION_SMALLROOM, EFX_PRESET_SPACESTATION_SMALLROOM);
	efx.rvbEffects[EFX_PRESET_SPACESTATION_SHORTPASSAGE] = EFX_RvbCreate(&rvb_SPACESTATION_SHORTPASSAGE, EFX_PRESET_SPACESTATION_SHORTPASSAGE);
	efx.rvbEffects[EFX_PRESET_SPACESTATION_MEDIUMROOM] = EFX_RvbCreate(&rvb_SPACESTATION_MEDIUMROOM, EFX_PRESET_SPACESTATION_MEDIUMROOM);
	efx.rvbEffects[EFX_PRESET_SPACESTATION_LARGEROOM] = EFX_RvbCreate(&rvb_SPACESTATION_LARGEROOM, EFX_PRESET_SPACESTATION_LARGEROOM);
	efx.rvbEffects[EFX_PRESET_SPACESTATION_LONGPASSAGE] = EFX_RvbCreate(&rvb_SPACESTATION_LONGPASSAGE, EFX_PRESET_SPACESTATION_LONGPASSAGE);
	efx.rvbEffects[EFX_PRESET_SPACESTATION_HALL] = EFX_RvbCreate(&rvb_SPACESTATION_HALL, EFX_PRESET_SPACESTATION_HALL);
	efx.rvbEffects[EFX_PRESET_SPACESTATION_CUPBOARD] = EFX_RvbCreate(&rvb_SPACESTATION_CUPBOARD, EFX_PRESET_SPACESTATION_CUPBOARD);
	efx.rvbEffects[EFX_PRESET_SPACESTATION_ALCOVE] = EFX_RvbCreate(&rvb_SPACESTATION_ALCOVE, EFX_PRESET_SPACESTATION_ALCOVE);
	efx.rvbEffects[EFX_PRESET_WOODEN_SMALLROOM] = EFX_RvbCreate(&rvb_WOODEN_SMALLROOM, EFX_PRESET_WOODEN_SMALLROOM);
	efx.rvbEffects[EFX_PRESET_WOODEN_SHORTPASSAGE] = EFX_RvbCreate(&rvb_WOODEN_SHORTPASSAGE, EFX_PRESET_WOODEN_SHORTPASSAGE);
	efx.rvbEffects[EFX_PRESET_WOODEN_MEDIUMROOM] = EFX_RvbCreate(&rvb_WOODEN_MEDIUMROOM, EFX_PRESET_WOODEN_MEDIUMROOM);
	efx.rvbEffects[EFX_PRESET_WOODEN_LARGEROOM] = EFX_RvbCreate(&rvb_WOODEN_LARGEROOM, EFX_PRESET_WOODEN_LARGEROOM);
	efx.rvbEffects[EFX_PRESET_WOODEN_LONGPASSAGE] = EFX_RvbCreate(&rvb_WOODEN_LONGPASSAGE, EFX_PRESET_WOODEN_LONGPASSAGE);
	efx.rvbEffects[EFX_PRESET_WOODEN_HALL] = EFX_RvbCreate(&rvb_WOODEN_HALL, EFX_PRESET_WOODEN_HALL);
	efx.rvbEffects[EFX_PRESET_WOODEN_CUPBOARD] = EFX_RvbCreate(&rvb_WOODEN_CUPBOARD, EFX_PRESET_WOODEN_CUPBOARD);
	efx.rvbEffects[EFX_PRESET_WOODEN_COURTYARD] = EFX_RvbCreate(&rvb_WOODEN_COURTYARD, EFX_PRESET_WOODEN_COURTYARD);
	efx.rvbEffects[EFX_PRESET_WOODEN_ALCOVE] = EFX_RvbCreate(&rvb_WOODEN_ALCOVE, EFX_PRESET_WOODEN_ALCOVE);
	efx.rvbEffects[EFX_PRESET_SPORT_EMPTYSTADIUM] = EFX_RvbCreate(&rvb_SPORT_EMPTYSTADIUM, EFX_PRESET_SPORT_EMPTYSTADIUM);
	efx.rvbEffects[EFX_PRESET_SPORT_SQUASHCOURT] = EFX_RvbCreate(&rvb_SPORT_SQUASHCOURT, EFX_PRESET_SPORT_SQUASHCOURT);
	efx.rvbEffects[EFX_PRESET_SPORT_SMALLSWIMMINGPOOL] = EFX_RvbCreate(&rvb_SPORT_SMALLSWIMMINGPOOL, EFX_PRESET_SPORT_SMALLSWIMMINGPOOL);
	efx.rvbEffects[EFX_PRESET_SPORT_LARGESWIMMINGPOOL] = EFX_RvbCreate(&rvb_SPORT_LARGESWIMMINGPOOL, EFX_PRESET_SPORT_LARGESWIMMINGPOOL);
	efx.rvbEffects[EFX_PRESET_SPORT_GYMNASIUM] = EFX_RvbCreate(&rvb_SPORT_GYMNASIUM, EFX_PRESET_SPORT_GYMNASIUM);
	efx.rvbEffects[EFX_PRESET_SPORT_FULLSTADIUM] = EFX_RvbCreate(&rvb_SPORT_FULLSTADIUM, EFX_PRESET_SPORT_FULLSTADIUM);
	efx.rvbEffects[EFX_PRESET_SPORT_STADIUMTANNOY] = EFX_RvbCreate(&rvb_SPORT_STADIUMTANNOY, EFX_PRESET_SPORT_STADIUMTANNOY);
	efx.rvbEffects[EFX_PRESET_PREFAB_WORKSHOP] = EFX_RvbCreate(&rvb_PREFAB_WORKSHOP, EFX_PRESET_PREFAB_WORKSHOP);
	efx.rvbEffects[EFX_PRESET_PREFAB_SCHOOLROOM] = EFX_RvbCreate(&rvb_PREFAB_SCHOOLROOM, EFX_PRESET_PREFAB_SCHOOLROOM);
	efx.rvbEffects[EFX_PRESET_PREFAB_PRACTISEROOM] = EFX_RvbCreate(&rvb_PREFAB_PRACTISEROOM, EFX_PRESET_PREFAB_PRACTISEROOM);
	efx.rvbEffects[EFX_PRESET_PREFAB_OUTHOUSE] = EFX_RvbCreate(&rvb_PREFAB_OUTHOUSE, EFX_PRESET_PREFAB_OUTHOUSE);
	efx.rvbEffects[EFX_PRESET_PREFAB_CARAVAN] = EFX_RvbCreate(&rvb_PREFAB_CARAVAN, EFX_PRESET_PREFAB_CARAVAN);
	efx.rvbEffects[EFX_PRESET_DOME_TOMB] = EFX_RvbCreate(&rvb_DOME_TOMB, EFX_PRESET_DOME_TOMB);
	efx.rvbEffects[EFX_PRESET_PIPE_SMALL] = EFX_RvbCreate(&rvb_PIPE_SMALL, EFX_PRESET_PIPE_SMALL);
	efx.rvbEffects[EFX_PRESET_DOME_SAINTPAULS] = EFX_RvbCreate(&rvb_DOME_SAINTPAULS, EFX_PRESET_DOME_SAINTPAULS);
	efx.rvbEffects[EFX_PRESET_PIPE_LONGTHIN] = EFX_RvbCreate(&rvb_PIPE_LONGTHIN, EFX_PRESET_PIPE_LONGTHIN);
	efx.rvbEffects[EFX_PRESET_PIPE_LARGE] = EFX_RvbCreate(&rvb_PIPE_LARGE, EFX_PRESET_PIPE_LARGE);
	efx.rvbEffects[EFX_PRESET_PIPE_RESONANT] = EFX_RvbCreate(&rvb_PIPE_RESONANT, EFX_PRESET_PIPE_RESONANT);
	efx.rvbEffects[EFX_PRESET_OUTDOORS_BACKYARD] = EFX_RvbCreate(&rvb_OUTDOORS_BACKYARD, EFX_PRESET_OUTDOORS_BACKYARD);
	efx.rvbEffects[EFX_PRESET_OUTDOORS_ROLLINGPLAINS] = EFX_RvbCreate(&rvb_OUTDOORS_ROLLINGPLAINS, EFX_PRESET_OUTDOORS_ROLLINGPLAINS);
	efx.rvbEffects[EFX_PRESET_OUTDOORS_DEEPCANYON] = EFX_RvbCreate(&rvb_OUTDOORS_DEEPCANYON, EFX_PRESET_OUTDOORS_DEEPCANYON);
	efx.rvbEffects[EFX_PRESET_OUTDOORS_CREEK] = EFX_RvbCreate(&rvb_OUTDOORS_CREEK, EFX_PRESET_OUTDOORS_CREEK);
	efx.rvbEffects[EFX_PRESET_OUTDOORS_VALLEY] = EFX_RvbCreate(&rvb_OUTDOORS_VALLEY, EFX_PRESET_OUTDOORS_VALLEY);
	efx.rvbEffects[EFX_PRESET_MOOD_HEAVEN] = EFX_RvbCreate(&rvb_MOOD_HEAVEN, EFX_PRESET_MOOD_HEAVEN);
	efx.rvbEffects[EFX_PRESET_MOOD_HELL] = EFX_RvbCreate(&rvb_MOOD_HELL, EFX_PRESET_MOOD_HELL);
	efx.rvbEffects[EFX_PRESET_MOOD_MEMORY] = EFX_RvbCreate(&rvb_MOOD_MEMORY, EFX_PRESET_MOOD_MEMORY);
	efx.rvbEffects[EFX_PRESET_DRIVING_COMMENTATOR] = EFX_RvbCreate(&rvb_DRIVING_COMMENTATOR, EFX_PRESET_DRIVING_COMMENTATOR);
	efx.rvbEffects[EFX_PRESET_DRIVING_PITGARAGE] = EFX_RvbCreate(&rvb_DRIVING_PITGARAGE, EFX_PRESET_DRIVING_PITGARAGE);
	efx.rvbEffects[EFX_PRESET_DRIVING_INCAR_RACER] = EFX_RvbCreate(&rvb_DRIVING_INCAR_RACER, EFX_PRESET_DRIVING_INCAR_RACER);
	efx.rvbEffects[EFX_PRESET_DRIVING_INCAR_SPORTS] = EFX_RvbCreate(&rvb_DRIVING_INCAR_SPORTS, EFX_PRESET_DRIVING_INCAR_SPORTS);
	efx.rvbEffects[EFX_PRESET_DRIVING_INCAR_LUXURY] = EFX_RvbCreate(&rvb_DRIVING_INCAR_LUXURY, EFX_PRESET_DRIVING_INCAR_LUXURY);
	efx.rvbEffects[EFX_PRESET_DRIVING_FULLGRANDSTAND] = EFX_RvbCreate(&rvb_DRIVING_FULLGRANDSTAND, EFX_PRESET_DRIVING_FULLGRANDSTAND);
	efx.rvbEffects[EFX_PRESET_DRIVING_EMPTYGRANDSTAND] = EFX_RvbCreate(&rvb_DRIVING_EMPTYGRANDSTAND, EFX_PRESET_DRIVING_EMPTYGRANDSTAND);
	efx.rvbEffects[EFX_PRESET_DRIVING_TUNNEL] = EFX_RvbCreate(&rvb_DRIVING_TUNNEL, EFX_PRESET_DRIVING_TUNNEL);
	efx.rvbEffects[EFX_PRESET_CITY_STREETS] = EFX_RvbCreate(&rvb_CITY_STREETS, EFX_PRESET_CITY_STREETS);
	efx.rvbEffects[EFX_PRESET_CITY_SUBWAY] = EFX_RvbCreate(&rvb_CITY_SUBWAY, EFX_PRESET_CITY_SUBWAY);
	efx.rvbEffects[EFX_PRESET_CITY_MUSEUM] = EFX_RvbCreate(&rvb_CITY_MUSEUM, EFX_PRESET_CITY_MUSEUM);
	efx.rvbEffects[EFX_PRESET_CITY_LIBRARY] = EFX_RvbCreate(&rvb_CITY_LIBRARY, EFX_PRESET_CITY_LIBRARY);
	efx.rvbEffects[EFX_PRESET_CITY_UNDERPASS] = EFX_RvbCreate(&rvb_CITY_UNDERPASS, EFX_PRESET_CITY_UNDERPASS);
	efx.rvbEffects[EFX_PRESET_CITY_ABANDONED] = EFX_RvbCreate(&rvb_CITY_ABANDONED, EFX_PRESET_CITY_ABANDONED);
	efx.rvbEffects[EFX_PRESET_DUSTYROOM] = EFX_RvbCreate(&rvb_DUSTYROOM, EFX_PRESET_DUSTYROOM);
	efx.rvbEffects[EFX_PRESET_CHAPEL] = EFX_RvbCreate(&rvb_CHAPEL, EFX_PRESET_CHAPEL);
	efx.rvbEffects[EFX_PRESET_SMALLWATERROOM] = EFX_RvbCreate(&rvb_SMALLWATERROOM, EFX_PRESET_SMALLWATERROOM);

	alGenAuxiliaryEffectSlots(1, &efx.rvbAuxSlot);
	alAuxiliaryEffectSloti(efx.rvbAuxSlot, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, AL_TRUE);

	err = alGetError();
	if (err == AL_NO_ERROR)
	{
		Com_Printf("...EFX initialized\n");
		if (numEFXeffectsLoaded == NUM_EFX_PRESETS)
			Com_Printf("...^2%i presets loaded\n", NUM_EFX_PRESETS);
		else
			Com_Printf("...^3%i of %i presets loaded\n", numEFXeffectsLoaded, NUM_EFX_PRESETS);
		efx.on = true;
	}
	else
	{
		Com_Printf("^1X..EFX init failed, error %s\n", al_error(err));
		EFX_RvbDeleteEffects();
		alGetError();
		efx.on = false;
	}
}

/*
==================
S_StopAllSounds
==================
*/
void S_StopAllSounds(void)
{
	unsigned i;
	openal_channel_t *ch;

	if (!s_openal_numChannels)
		return;

	// clear all the playsounds
	Memset(s_playsounds, 0, sizeof(s_playsounds));
	s_freeplays.next = s_freeplays.prev = &s_freeplays;
	s_pendingplays.next = s_pendingplays.prev = &s_pendingplays;

	for (i = 0; i < MAX_PLAYSOUNDS; i++)
	{
		s_playsounds[i].prev = &s_freeplays;
		s_playsounds[i].next = s_freeplays.next;
		s_playsounds[i].prev->next = &s_playsounds[i];
		s_playsounds[i].next->prev = &s_playsounds[i];
	}

	// Stop all the channels
	alSourceStopv(s_openal_numChannels, source_name);

	// Mark all the channels free
	for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++)
	{
		ch->bufferNum = AL_NONE;
		alSourcei(source_name[i], AL_BUFFER, AL_NONE);
	}


	// clear all the channels
	// Memset(s_openal_channels, 0, sizeof(s_openal_channels));

	S_Streaming_Stop();
}

/*
=================
AL_Shutdown
=================
*/
void AL_Shutdown()
{
	Com_Printf("Shutting down OpenAL subsystem\n");

	if (alConfig.hALC)
	{
		if (alcMakeContextCurrent)
		{
			Com_Printf("...alcMakeContextCurrent( NULL ): ");
			if (!alcMakeContextCurrent(NULL))
				Com_Printf("^1failed\n");
			else
				Com_Printf("^2succeeded\n");
		}

		if (alcDestroyContext)
		{
			Com_Printf("...destroying AL context\n");
			alcDestroyContext(alConfig.hALC);
		}

		alConfig.hALC = NULL;
	}

	if (alConfig.hDevice)
	{
		if (alcCloseDevice)
		{
			Com_Printf("...closing device\n");
			alcCloseDevice(alConfig.hDevice);
		}

		alConfig.hDevice = NULL;
	}

	QAL_Shutdown();
}

void S_Play()
{
	int 	i;
	char name[256];

	i = 1;
	while (i<Cmd_Argc())
	{
		if (!strrchr(Cmd_Argv(i), '.'))
		{
			strcpy(name, Cmd_Argv(i));
			strcat(name, ".wav");
		}
		else
			strcpy(name, Cmd_Argv(i));
		S_StartSound(NULL, cl.playernum + 1, CHAN_AUTO, S_RegisterSound(name), 1.0, 1.0, 0);
		i++;
	}
}

/*
================
S_Init
================
*/
void S_Init()
{
	Com_Printf("\n------- sound initialization -------\n");

	cd_nocd = Cvar_Get("cd_nocd", "1", CVAR_ARCHIVE);
	cd_nocd->help = "disable CD music.";
	s_musicVolume = Cvar_Get("s_musicvolume", "0.7", CVAR_ARCHIVE);
	s_music = Cvar_Get("s_music", "1", CVAR_ARCHIVE);
	s_volume = Cvar_Get("s_volume", "1", CVAR_ARCHIVE);
///	s_musicrandom = Cvar_Get("s_musicrandom", "0", CVAR_ARCHIVE);
	s_openal_device = Cvar_Get("s_openal_device", "", CVAR_ARCHIVE);
	s_openal_efx = Cvar_Get("s_openal_efx", "1", CVAR_ARCHIVE);
	s_openal_efx->help = "enable EFX extension for sound reverberation";
	s_quality = Cvar_Get("s_quality", "0", CVAR_ARCHIVE);
	s_quality->help = "Sound frequency (Hz)\nAutomatic select for zero value.";
///	s_distance_model = Cvar_Get("s_distance_model", "0", CVAR_ARCHIVE);
	s_initsound = Cvar_Get("s_initsound", "1", 0);
	s_initsound->help = "Sound On/Off";
	s_dynamicReverberation = Cvar_Get("s_dynamicReverberation", "1", CVAR_ARCHIVE);
	s_dynamicReverberation->help = "dynamic reverberation effect depends on room size\nEFX must be enabled.";
	s_useHRTF = Cvar_Get("s_useHRTF", "1", CVAR_ARCHIVE);

	if (!s_initsound->value || openalStop)
	{
		Com_Printf("^1X..sound not initialized\n");
		return;
	}
	else
	{
		if (AL_Init())
		{
			AllocChannels();
			if (s_openal_numChannels)
			{
/*
				if (s_distance_model->value)	// OpenAL using the best selection by default
				{
					// AL_NONE bypasses all distance
					// attenuation calculation for all sources. The
					// implementation is expected to optimize this
					// situation. AL_INVERSE_DISTANCE_CLAMPED is the IASIG
					// I3DL2 model, with
					// AL_REFERENCE_DISTANCE indicating both the reference
					// distance and the distance
					// below which gain will be clamped.
					// AL_INVERSE_DISTANCE is equivalent to the
					// IASIG I3DL2 model with the exception that
					// AL_REFERENCE_DISTANCE does not
					// imply any clamping. The linear models are not
					// physically realistic, but do allow full
					// attenuation of a source beyond a specified
					// distance. The OpenAL implementation is still
					// free to apply any range clamping as necessary.
					ALenum modelName[7] = {
						AL_NONE,
						AL_INVERSE_DISTANCE,
						AL_INVERSE_DISTANCE_CLAMPED,
						AL_LINEAR_DISTANCE,
						AL_LINEAR_DISTANCE_CLAMPED,
						AL_EXPONENT_DISTANCE,
						AL_EXPONENT_DISTANCE_CLAMPED
					};
					int dstModel = (int)s_distance_model->value - 1;
					if (dstModel < 0)	dstModel = 0;
					else if (dstModel > 6)	dstModel = 6;
					alDistanceModel(modelName[dstModel]);
				}
*/
				alListenerf(AL_GAIN, 1.0);							/// Berserker: master sound volume. always 1

				Cmd_AddCommand("play", S_Play);
				Cmd_AddCommand("stopsound", S_StopAllSounds);
				Cmd_AddCommand("music", S_Music_f);
				Cmd_AddCommand("s_info", S_SoundInfo_f);
				Cmd_AddCommand("track", S_Track_f);

				CL_fast_sound_init();

				// Generate some AL Buffers for streaming
				alGenBuffers(NUM_STRBUF, streaming.buffers);

				if (alConfig.efx)
					EFX_RvbInit();

				S_StopAllSounds();	// inits freeplays

				S_SoundInfo_f();
			}
			else
				AL_Shutdown();
		}

		if (!s_openal_numChannels)
		{
			Com_Printf("^1X..OpenAL failed to initialize, no sound available\n");
			Cvar_ForceSet("s_initsound", "0");	/// Berserker's FIX: устранён крэш если OpenAL не может запуститься
			AL_Shutdown();
		}
	}

	Com_Printf("-------------------------------------\n");
}

void WILLOW_HACK_SOUND(int i, int k)
{
	// At first, make sure we set default values to hacking database
	cl.sound_precache_hacks[i] = 0;
	cl.sound_precache_rolloff_factor[i] = 1.0f;
	cl.sound_precache_gain[i] = 1.0f;

	// willow: World mentor hack! TO DO: find a solution to call mentor
	// proper way
	if (!strncmp(cl.configstrings[k + i], "world/voice", 11)
		|| !strncmp(cl.configstrings[k + i], "world/v_", 8)) {
		cl.sound_precache_hacks[i] = sound_precache_hacks_FLAT2D;
	}
	// willow: TO DO: "misc/secret.wav" hide this sound from other
	// players!
	else if (!strncmp(cl.configstrings[k + i], "misc/secret", 11)) {
		cl.sound_precache_hacks[i] = sound_precache_hacks_FLAT2D;
	}
	// willow: Battle backgrounds hacks! TO DO: find a proper solution
	else if (!strncmp(cl.configstrings[k + i], "world/battle", 12)) {
		cl.sound_precache_hacks[i] =
			sound_precache_hacks_AUTOFIX_gain |
			sound_precache_hacks_AUTOFIX_rolloff_factor;
		cl.sound_precache_gain[i] = 0.56;
		cl.sound_precache_rolloff_factor[i] = 0.08;
	}
	else
		if (!strncmp(cl.configstrings[k + i], "world/flyby", 11)) {
			cl.sound_precache_hacks[i] =
				sound_precache_hacks_AUTOFIX_gain |
				sound_precache_hacks_AUTOFIX_rolloff_factor;
			cl.sound_precache_gain[i] = 1;
			cl.sound_precache_rolloff_factor[i] = 0.05;
		}
		else if (!strncmp(cl.configstrings[k + i], "misc/ar", 7)) {
			cl.sound_precache_hacks[i] =
				sound_precache_hacks_AUTOFIX_gain |
				sound_precache_hacks_AUTOFIX_rolloff_factor;
			cl.sound_precache_gain[i] = 0.8;
			cl.sound_precache_rolloff_factor[i] = 1;
		}
		// Hack railgun background noise
		else if (!strcmp
		(cl.configstrings[k + i], "weapons/rg_hum.wav")) {
			cl.sound_precache_hacks[i] = sound_precache_hacks_AUTOFIX_gain;
			cl.sound_precache_gain[i] = 0.2f;
		}
		// Hack hyperblaster drum rotation noise
		else if (!strcmp
		(cl.configstrings[k + i], "weapons/hyprbl1a.wav")) {
			cl.sound_precache_hacks[i] = sound_precache_hacks_AUTOFIX_gain;
			cl.sound_precache_gain[i] = 0.7f;
		}
		else
			if (!strcmp
			(cl.configstrings[k + i], "weapons/hyprbu1a.wav")) {
				cl.sound_precache_hacks[i] = sound_precache_hacks_AUTOFIX_gain;
				cl.sound_precache_gain[i] = 0.7f;
			}
			else
				if (!strcmp
				(cl.configstrings[k + i], "infantry/inflies1.wav")) {
					cl.sound_precache_hacks[i] =
						sound_precache_hacks_AUTOFIX_gain |
						sound_precache_hacks_AUTOFIX_rolloff_factor;
					cl.sound_precache_gain[i] = 0.7f;
					cl.sound_precache_rolloff_factor[i] = 0.5f;
				}
	// grenades timer countdown
				else if (!strcmp
				(cl.configstrings[k + i], "weapons/hgrenc1b.wav")) {
					cl.sound_precache_hacks[i] = sound_precache_hacks_AUTOFIX_gain;
					cl.sound_precache_gain[i] = 0.2f;
				}
				else if (!strcmp(cl.configstrings[k + i], "world/wind2.wav")) {
					cl.sound_precache_hacks[i] =
						sound_precache_hacks_AUTOFIX_gain |
						sound_precache_hacks_AUTOFIX_rolloff_factor;
					cl.sound_precache_gain[i] = 0.55f;
					cl.sound_precache_rolloff_factor[i] = 0.888f;
				}
				else
					if (!strncmp(cl.configstrings[k + i], "world/radio", 11)) {
						cl.sound_precache_hacks[i] =
							sound_precache_hacks_AUTOFIX_gain |
							sound_precache_hacks_AUTOFIX_rolloff_factor;
						cl.sound_precache_gain[i] = 0.33;
						cl.sound_precache_rolloff_factor[i] = 1.1f;
					}
}

/*
======================
CL_RegisterSounds
======================
*/
void CL_RegisterSounds(void)
{
	int i = 1;
	int j, k;
	if (net_compatibility->value)
	{
		j = MAX_SOUNDS_Q2;
		k = CS_SOUNDS_Q2;
	}
	else
	{
		j = MAX_SOUNDS_BERS;
		k = CS_SOUNDS_BERS;
	}

	while (i < j && cl.configstrings[k + i][0])
	{
		WILLOW_HACK_SOUND(i, k);

		cl.sound_precache[i] = S_RegisterSound(cl.configstrings[k + i]);

		if (cl.configstrings[k + i][0] == '*')
			cl.sound_sexedname[i] = cl.configstrings[k + i];
		else
			cl.sound_sexedname[i] = NULL;

		// XXX: this causes audio menu sliders to start counting to infinity,
		//      as if the user keeps pressing left/right arrow keys, when a map is loaded
		//Sys_SendKeyEvents();	// pump message loop
		++i;
	}

	S_RegisterMaterialSounds();
}

static void FreeChannels(void)
{
	alDeleteSources(s_openal_numChannels + 1, source_name);
	Memset(s_openal_channels, 0, sizeof(openal_channel_t)* MAX_CHANNELS);

	s_openal_numChannels = 0;
}


void FreeSounds(void)
{
	// Stop all sounds
	S_StopAllSounds();

	// Free all sounds
	alDeleteBuffers(num_sfx, known_sfx_bufferNum);

	// Clean up
	Memset(known_sfx_name, 0, sizeof(known_sfx_name));
	Memset(known_sfx_bufferNum, 0, sizeof(known_sfx_bufferNum));
	num_sfx = 0;
}

void S_fastsound_kill_descriptors_pool(unsigned count, ALuint * descriptors_pool)
{
	alDeleteBuffers(count, descriptors_pool);
	Memset(descriptors_pool, 0, count * sizeof(ALuint));
}

void CL_fast_sound_close(void)
{
	// Kill and free the waves
	S_fastsound_kill_descriptors_pool(FAST_SOUNDS_TOTAL, fastsound_descriptor);
}

void EFX_RvbShutdown()
{
	if (!efx.on)
		return;

	Com_Printf("EFX shutdown\n");
	alDeleteAuxiliaryEffectSlots(1, &efx.rvbAuxSlot);
	EFX_RvbDeleteEffects();

	efx.on = false;
}

void S_Shutdown()
{
	if (s_openal_numChannels)
	{
		CL_fast_sound_close();
		FreeSounds();

		Cmd_RemoveCommand("play");
		Cmd_RemoveCommand("stopsound");
		Cmd_RemoveCommand("music");
		Cmd_RemoveCommand("track");
		Cmd_RemoveCommand("s_info");

		if (alConfig.efx)
			EFX_RvbShutdown();

		FreeChannels();
		AL_Shutdown();
	}
}

void Flag_set(channel_task_t * task, unsigned long flags_collection)
{
	task->flags |= flags_collection;
}

void Flag_clear(channel_task_t * task, unsigned long flags_collection)
{
	task->flags &= ~flags_collection;
}

bool Flag_check(channel_task_t * task, unsigned long flags_collection)
{
	return task->flags & flags_collection;
}

ALuint Flag_checkAL(channel_task_t * task, unsigned long flags_collection)
{
	return task->flags & flags_collection ? AL_TRUE : AL_FALSE;
}

void FlagAL_set(openal_channel_t * ch, unsigned long flags_collection)
{
	ch->flags |= flags_collection;
}

void FlagAL_clear(openal_channel_t * ch, unsigned long flags_collection)
{
	ch->flags &= ~flags_collection;
}

bool FlagAL_check(openal_channel_t * ch, unsigned long flags_collection)
{
	return ch->flags & flags_collection;
}

ALuint FlagAL_checkAL(openal_channel_t * ch, unsigned long flags_collection)
{
	return ch->flags & flags_collection ? AL_TRUE : AL_FALSE;
}

void TASK_TerminateChannel(channel_task_t * task, openal_channel_t * ch)
{
	Flag_set(task, AL_TASK_MANAGER__TERMINATE);
	ch->bufferNum = 0;			// ch->sfx = NULL;

//  alSourceStop(ch->sourceNum);
//  alSourcei(ch->sourceNum, AL_BUFFER, 0);
}

// picks a channel based on priorities, empty slots, number of channels
openal_channel_t *PickChannel_NEW(unsigned int entNum, unsigned int entChannel, vec3_t new_vec, ALuint * return_index)
{
	openal_channel_t *ch;
	int i;
	int firstToDie = -1;
//  int                 oldestTime = cl.time;
	bool is_terminate = true;

	for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++)
	{
		// Don't let game sounds override streaming sounds
		/* if (!i && streaming) continue; */

		// Check if this channel is active
		/*		if (!ch->sfx)							// found free channel
		{
		firstToDie = i;
		is_terminate = qfalse;
		break;
		} else {*/
		ALint SourceState;

		alGetSourcei(source_name[i], AL_SOURCE_STATE, &SourceState);

		if (SourceState == AL_STOPPED || SourceState == AL_INITIAL)	// The
																	// source
																	// already
																	// out
																	// of
																	// processing.
		{
			ch->bufferNum = 0;	// ch->sfx = NULL;
			firstToDie = i;
			is_terminate = false;
			break;
		}
	}

	// Emergency channel re-caption.
	// Override sound from same entity willow???????
	/* if (firstToDie == -1) { for (i = 0, ch = s_openal_channels; i <
	s_openal_numChannels; i++, ch++) if (ch->entNum == entNum &&
	ch->entChannel == entChannel) { firstToDie = i; break; } } */

	// Emergency channel re-caption. Issue 2
	// Terminate the most distant entity
	if (firstToDie == -1)
	{
		float len, max;
		vec3_t delta;

		VectorSubtract(cl.refdef.vieworg, new_vec, delta);
		max = VectorLength_Squared(delta);

		for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++)
			if (!FlagAL_check(ch, AL_FLAGS_FLAT2D)	// Don't touch FLAT 2D
													// samples!
				&& FlagAL_check(ch, AL_FLAGS_AL_LOOPING)
				)
			{
				// ch->_AL_POSITION must have same data:
				// alGetSourcefv(ch->sourceNum, AL_POSITION, (ALfloat
				// *)&al_position);
				// willow: i assume cl.refdef.vieworg always have the last
				// listener position
				VectorSubtract(cl.refdef.vieworg, ch->_AL_POSITION, delta);
				// willow:
				// actually we need vector length here, but squared length
				// can do the same job faster.
				len = VectorLength_Squared(delta);
				if (len > max)
				{
					firstToDie = i;
					max = len;
				}
			}

		if (firstToDie == -1)
			return NULL;
	}

	ch = &s_openal_channels[firstToDie];

	Memcpy(ch->_AL_POSITION, new_vec, sizeof(vec3_t));
	FlagAL_clear(ch, AL_FLAGS_FLAT2D);
	// Flag_clear
	// (&Channels_TODO[firstToDie],AL_TASK_MANAGER__IS_SOURCE_RELATIVE);

	ch->entNum = entNum;
	ch->entChannel = entChannel;
	// ch->startTime = cl.time;

	*return_index = source_name[firstToDie];

	// TERMINATE CHANNEL (stop working channel)
	// TASK_TerminateChannel (&Channels_TODO[firstToDie], ch);
	if (is_terminate)
	{
		alSourceStop(source_name[firstToDie]);
		// alSourceStop(ch->sourceNum);
		// alSourcei(ch->sourceNum, AL_BUFFER, 0);
	}

	return ch;
}

void EFX_RvbProcSrc(openal_channel_t *ch, ALuint source, bool enabled)
{
	if (!enabled)
		alSource3i(source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL);
	else
		alSource3i(source, AL_AUXILIARY_SEND_FILTER, efx.rvbAuxSlot, 0, AL_FILTER_NULL);
}

/*
===============
CL_GetEntityOrigin

Called to get the sound spatialization origin
===============
*/
void CL_GetEntityOrigin(int ent, vec3_t origin)
{
	// FIXME: bmodel issues...*/
	centity_t *cent;
	cmodel_t *cmodel;
	vec3_t midPoint;

	if (ent < 0 || ent >= MAX_EDICTS)
		Com_Error(ERR_DROP, "CL_GetEntityOrigin: ent = %i", ent);

	// Player entity
	if (ent == cl.playernum + 1)
	{
		VectorCopy(cl.refdef.vieworg, origin);
		return;
	}

	cent = &cl_entities[ent];

	// Calculate origin
	if (cent->current.renderfx & (RF_FRAMELERP | RF_BEAM))
	{
		origin[0] = cent->current.old_origin[0] + (cent->current.origin[0] - cent->current.old_origin[0]) * cl.lerpfrac;
		origin[1] = cent->current.old_origin[1] + (cent->current.origin[1] - cent->current.old_origin[1]) * cl.lerpfrac;
		origin[2] = cent->current.old_origin[2] + (cent->current.origin[2] - cent->current.old_origin[2]) * cl.lerpfrac;
	}
	else
	{
		origin[0] = cent->prev.origin[0] + (cent->current.origin[0] - cent->prev.origin[0]) * cl.lerpfrac;
		origin[1] = cent->prev.origin[1] + (cent->current.origin[1] - cent->prev.origin[1]) * cl.lerpfrac;
		origin[2] = cent->prev.origin[2] + (cent->current.origin[2] - cent->prev.origin[2]) * cl.lerpfrac;
	}

	// If a brush model, offset the origin
	if (cent->current.solid == 31)
	{
		cmodel = cl.model_clip[cent->current.modelindex];
		if (!cmodel)
			return;

		VectorAverage(cmodel->mins, cmodel->maxs, midPoint);
		VectorAdd(origin, midPoint, origin);
	}
}

void S_fastsound(vec3_t origin, int entnum, int entchannel, ALuint bufferNum, ALfloat gain, ALfloat rolloff_factor)
{
	openal_channel_t *ch;
	ALuint sourceNum;

	if (!s_openal_numChannels || !bufferNum)
		return;					// safety check.

	// Pick a channel and start the sound effect
	if (origin)
		ch = PickChannel_NEW(entnum, entchannel, origin, &sourceNum);
	else
	{
		vec3_t position;
		CL_GetEntityOrigin(entnum, position);
		ch = PickChannel_NEW(entnum, entchannel, position, &sourceNum);
	}

	if (ch)
	{
//      VectorCopy(ps->origin, ch->position);
		ch->bufferNum = bufferNum;	// ch->sfx = sfx;

		// Update min/max distance
		alSourcef(sourceNum, AL_REFERENCE_DISTANCE, 28);
		// Set max distance really far away (default)
		// alSourcef(ch->sourceNum, AL_MAX_DISTANCE, 65536);

		alSourcefv(sourceNum, AL_POSITION, ch->_AL_POSITION);

		if (origin)
		{
			FlagAL_clear(ch, AL_FLAGS_AL_LOOPING);
			FlagAL_set(ch, AL_FLAGS_FIXED_POSITION);
			alSource3f(sourceNum, AL_VELOCITY, 0, 0, 0);
		}
		else
		{
			FlagAL_clear(ch, AL_FLAGS_FIXED_POSITION | AL_FLAGS_AL_LOOPING);
			// willow: TO DO.
			// alSourcefv(sourceNum, AL_VELOCITY,
			// current_task->TASK_AL_VELOCITY);
			alSource3f(sourceNum, AL_VELOCITY, 0, 0, 0);
		}

		alSourcei(sourceNum, AL_BUFFER, bufferNum);
		alSourcei(sourceNum, AL_SOURCE_RELATIVE, AL_FALSE);
		alSourcei(sourceNum, AL_LOOPING, FlagAL_checkAL(ch, AL_FLAGS_AL_LOOPING));
		alSourcef(sourceNum, AL_ROLLOFF_FACTOR, rolloff_factor);
		alSourcef(sourceNum, AL_GAIN, gain * s_volume->value);

		if (alConfig.efx)
			EFX_RvbProcSrc(ch, sourceNum, true);

		alSourcePlay(sourceNum);
	}
}

playsound_t *S_AllocPlaysound(void)
{
	playsound_t *ps = s_freeplays.next;

	if (ps == &s_freeplays)
		return NULL;			// no free playsounds

	// unlink from freelist
	ps->prev->next = ps->next;
	ps->next->prev = ps->prev;

	return ps;
}

void S_fastsound_queue(vec3_t origin, int entnum, int entchannel, ALuint bufferNum, float fvol, float attenuation, unsigned timeofs)
{
	playsound_t *ps, *sort;

	if (!s_openal_numChannels || !bufferNum)
		return;

	// willow: Immediate sound effect start in case zero time delay
	// provided.
	// This is shortcut, just call "S_fastsound" call instead
	if (!timeofs)
	{
		S_fastsound(origin, entnum, entchannel, bufferNum, fvol, attenuation);
		return;
	}
	// Allocate a playSound
	ps = S_AllocPlaysound();
	if (!ps)
	{
		Com_Printf("^1S_StartSound: active-task queue fault\n");
		return;
	}

	ps->bufferNum = bufferNum;
	ps->entnum = entnum;
	ps->entchannel = entchannel;

//  ps->flat = is_flat;

	if (origin)
	{
		ps->fixed_origin = true;
		VectorCopy(origin, ps->origin);
	}
	else
		ps->fixed_origin = false;

	ps->volume = fvol;
	ps->attenuation = attenuation;
	ps->begin = cl.time + timeofs;

	// Sort into the pending playSounds list
	for (sort = s_pendingplays.next;
		sort != &s_pendingplays && sort->begin < ps->begin;
		sort = sort->next);

	ps->next = sort;
	ps->prev = sort->prev;

	ps->next->prev = ps;
	ps->prev->next = ps;
}

/*
==================
S_FindName
==================
*/
ALuint S_FindName(char *name, bool create)
{
	int i;
	unsigned err;
	char real_name[MAX_QPATH];
	byte *data;
	ALsizei rate;
	ALenum format;
	ALsizei size;

	if (!s_initsound->value || openalStop)
		return 0;

	if (!name)
	{
		Com_Printf("^1S_FindName: NULL name");
		return 0;
	}
	if (!name[0])
	{
		Com_Printf("^1S_FindName: empty name");
		return 0;
	}
	if (strlen(name) >= MAX_QPATH)
	{
		Com_Printf("^1S_FindName: name too long: %s", name);
		return 0;
	}

	// check internal fastsound effects first
	for (i = 0; i < FAST_SOUNDS_TOTAL; i++)
		if (!strcmp(fastsound_name[i], name))
		{
			Com_DPrintf("Legacy code requested fastsound sfx caching!\n");
			Com_DPrintf("cache request ignored: %s\n", name);
			return fastsound_descriptor[i];
		}
	// see if already loaded
	for (i = 0; i < num_sfx; i++)
		if (!strcmp(known_sfx_name[i], name))
			return known_sfx_bufferNum[i];

	if (!create)
		return 0;

	// find a free sfx
	for (i = 0; i < num_sfx; i++)
		if (!known_sfx_name[i][0])
			break;

	// willow: TO DO smthg, it's really dumb solution to terminate game
	// just because out of descriptors pool.
	// Just to adjust this pool is not good solution too.
	if (num_sfx >= MAX_SFX)
	{
		Com_Printf("^1S_FindName: out of sfx slots");
		return 0;
	}

	Memset(known_sfx_name[i], 0, MAX_QPATH);
	strcpy(known_sfx_name[i], name);

	// Load it from disk
	if (name[0] == '#')
		Com_sprintf(real_name, sizeof(real_name), "%s", &name[1]);
	else
		Com_sprintf(real_name, sizeof(real_name), "sound/%s", name);

	if (!LoadWAV(real_name, &data, &format, &rate, &size))
	{
		Com_Printf("^1S_FindName: BAD WAVE '%s'\n", name);
		return 0;
	}
	else
	{
		// Upload the sound
		alGenBuffers(1, &known_sfx_bufferNum[i]);
		alBufferData(known_sfx_bufferNum[i], format, data, size, rate);
		err = alGetError();
		if (err != AL_NO_ERROR)
			Com_Printf("^1S_FindName: bad sample, error %s\n", al_error(err));

		Z_Free(data);

		num_sfx++;
		return known_sfx_bufferNum[i];
	}
}

ALuint S_RegisterSound(const char *name)
{
	if (name[0] == '*')
		return S_RegisterSexedSound(&cl_entities[cl.playernum + 1].current, name);
	else
		return S_FindName((char*)name, s_openal_numChannels);
}

// picks a channel based on priorities, empty slots, number of channels
openal_channel_t *PickChannel_lite(ALuint * sourceNum)
{
	openal_channel_t *ch;
	int i;
	int firstToDie = -1;
	bool terminate = true;

	for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++)
	{
		// Check if this channel is active
/*		if (!ch->sfx)							// found free channel
		{
		firstToDie = i;
		terminate = qfalse;
		break;
		} else {*/
		ALint SourceState;

		alGetSourcei(source_name[i], AL_SOURCE_STATE, &SourceState);

		// The source already out of processing.
		if (SourceState == AL_STOPPED || SourceState == AL_INITIAL)
		{
			firstToDie = i;
			terminate = false;
			break;
		}
/*		}*/
	}

	// Emergency channel re-caption. Issue 2
	// Terminate the most distant entity
	if (firstToDie == -1)
	{
		// vec3_t listener;
		float len, max = 0;
		vec3_t delta;
		// alGetListenerfv(AL_POSITION, (ALfloat *)&listener);

		for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++)
			if (!FlagAL_check(ch, AL_FLAGS_FLAT2D)	// Don't touch FLAT 2D
													// samples!
				&& FlagAL_check(ch, AL_FLAGS_AL_LOOPING)
				)
			{
				// ch->_AL_POSITION must have same data:
				// alGetSourcefv(ch->sourceNum, AL_POSITION, (ALfloat
				// *)&al_position);
				// willow: i am assume cl.refdef.vieworg always have the
				// last listener position
				VectorSubtract(cl.refdef.vieworg, ch->_AL_POSITION, delta);
				// willow:
				// actually we need vector length here, but squared length
				// can do the same job faster.
				len = VectorLength_Squared(delta);
				if (len > max)
				{
					firstToDie = i;
					max = len;
				}
			}

		if (firstToDie == -1)
			return NULL;
	}

	ch = &s_openal_channels[firstToDie];

	Memset(ch->_AL_POSITION, 0, sizeof(vec3_t));

	ch->entNum = -1;
	ch->entChannel = 0;
// ch->startTime = cl.time;

	*sourceNum = source_name[firstToDie];

	// TERMINATE CHANNEL (stop working channel)
	if (terminate)
		alSourceStop(*sourceNum);

	return ch;
}

/*
==================
S_StartLocalSound
==================
*/
void S_StartLocalSound(ALuint bufferNum)
{
	if (s_openal_numChannels && bufferNum)
	{
		ALuint sourceNum;
		// Pick a channel and start the sound effect
		openal_channel_t *ch = PickChannel_lite(&sourceNum);
		if (ch)
		{
			FlagAL_clear(ch, AL_FLAGS_AL_LOOPING);
			ch->bufferNum = bufferNum;
			FlagAL_set(ch, AL_FLAGS_FLAT2D | AL_FLAGS_FIXED_POSITION);
			alSourcef(sourceNum, AL_PITCH, 1);
			alSource3f(sourceNum, AL_DIRECTION, 0, 0, 0);
			alSourcef(sourceNum, AL_REFERENCE_DISTANCE, 0);
			alSourcef(sourceNum, AL_ROLLOFF_FACTOR, 0);
			alSource3f(sourceNum, AL_POSITION, 0, 0, 0);
			alSource3f(sourceNum, AL_VELOCITY, 0, 0, 0);
			alSourcei(sourceNum, AL_BUFFER, bufferNum);
			alSourcei(sourceNum, AL_SOURCE_RELATIVE, AL_TRUE);
			alSourcei(sourceNum, AL_LOOPING, AL_FALSE);
			alSourcef(sourceNum, AL_GAIN, 0.47 * s_volume->value);

			if (alConfig.efx)
				EFX_RvbProcSrc(ch, sourceNum, false);

			alSourcePlay(sourceNum);
		}
		else
			Com_Printf("^1S_StartLocalSound: Dropped sound\n");
	}
}

void EFX_GetRoomSize(vec3_t listener_position)
{
	vec3_t forward, right, up;
	vec3_t end, tmp;
	trace_t trace;
	float sum, frontL, backL, leftL, rightL, upL, downL;

	int	i;
	for (i = 0; i<numEFXzones; i++)
	{
		if (listener_position[0]<EFXzone_infos[i].minmax[0])
			continue;
		if (listener_position[1]<EFXzone_infos[i].minmax[1])
			continue;
		if (listener_position[2]<EFXzone_infos[i].minmax[2])
			continue;
		if (listener_position[0]>EFXzone_infos[i].minmax[3])
			continue;
		if (listener_position[1]>EFXzone_infos[i].minmax[4])
			continue;
		if (listener_position[2]>EFXzone_infos[i].minmax[5])
			continue;
		// EFX zone found!
		break;
	}

	if (i < numEFXzones)
	{
		EFXzone_t *EFXzone = &EFXzone_infos[i];
		if (EFXzone->preset >= 0 && EFXzone->preset < NUM_EFX_PRESETS)
		{
			if (efx.rvbEffects[EFXzone->preset] != AL_EFFECT_NULL)
			{
				alAuxiliaryEffectSloti(efx.rvbAuxSlot, AL_EFFECTSLOT_EFFECT, efx.rvbEffects[EFXzone->preset]);
				efx_preset_autoselected = -EFXzone->preset;
			}
			return;
		}
	}

	if (!s_dynamicReverberation->value)
	{
		if (efx.rvbEffects[EFX_PRESET_CITY] != AL_EFFECT_NULL)
		{
			alAuxiliaryEffectSloti(efx.rvbAuxSlot, AL_EFFECTSLOT_EFFECT, efx.rvbEffects[EFX_PRESET_CITY]);
			efx_preset_autoselected = EFX_PRESET_CITY;
		}
		return;
	}

	VectorSet(forward, 1, 0, 0);
	VectorSet(right, 0, 1, 0);
	VectorSet(up, 0, 0, 1);

	// trace to forward
	VectorMA(listener_position, 2048.0, forward, end);
	trace = CL_PMTraceWorld(listener_position, vec3_origin, vec3_origin, end, MASK_SOLID);
	if (trace.fraction > 0 && trace.fraction < 1)
	{
		VectorSubtract(trace.endpos, listener_position, tmp);
		frontL = VectorLength(tmp);
	}

	// trace to back
	VectorMA(listener_position, -2048.0, forward, end);
	trace = CL_PMTraceWorld(listener_position, vec3_origin, vec3_origin, end, MASK_SOLID);
	if (trace.fraction > 0 && trace.fraction < 1)
	{
		VectorSubtract(trace.endpos, listener_position, tmp);
		backL = VectorLength(tmp);
	}

	// trace to right
	VectorMA(listener_position, 2048.0, right, end);
	trace = CL_PMTraceWorld(listener_position, vec3_origin, vec3_origin, end, MASK_SOLID);
	if (trace.fraction > 0 && trace.fraction < 1)
	{
		VectorSubtract(trace.endpos, listener_position, tmp);
		rightL = VectorLength(tmp);
	}

	// trace to left
	VectorMA(listener_position, -2048.0, right, end);
	trace = CL_PMTraceWorld(listener_position, vec3_origin, vec3_origin, end, MASK_SOLID);
	if (trace.fraction > 0 && trace.fraction < 1)
	{
		VectorSubtract(trace.endpos, listener_position, tmp);
		leftL = VectorLength(tmp);
	}

	// trace to up
	VectorMA(listener_position, 2048.0, up, end);
	trace = CL_PMTraceWorld(listener_position, vec3_origin, vec3_origin, end, MASK_SOLID);
	if (trace.fraction > 0 && trace.fraction < 1)
	{
		VectorSubtract(trace.endpos, listener_position, tmp);
		upL = VectorLength(tmp);
	}

	// trace to down
	VectorMA(listener_position, -2048.0, up, end);
	trace = CL_PMTraceWorld(listener_position, vec3_origin, vec3_origin, end, MASK_SOLID);
	if (trace.fraction > 0 && trace.fraction < 1)
	{
		VectorSubtract(trace.endpos, listener_position, tmp);
		downL = VectorLength(tmp);
	}

	sum = (frontL + backL + leftL + rightL + upL + downL) / 6.0;

	if (sum <= 128.0)
	{
		if (efx.rvbEffects[EFX_PRESET_FACTORY_ALCOVE] != AL_EFFECT_NULL)
		{
			alAuxiliaryEffectSloti(efx.rvbAuxSlot, AL_EFFECTSLOT_EFFECT, efx.rvbEffects[EFX_PRESET_FACTORY_ALCOVE]);
			efx_preset_autoselected = EFX_PRESET_FACTORY_ALCOVE;
		}
	}
	else if (sum <= 256.0)
	{
		if (efx.rvbEffects[EFX_PRESET_FACTORY_SMALLROOM] != AL_EFFECT_NULL)
		{
			alAuxiliaryEffectSloti(efx.rvbAuxSlot, AL_EFFECTSLOT_EFFECT, efx.rvbEffects[EFX_PRESET_FACTORY_SMALLROOM]);
			efx_preset_autoselected = EFX_PRESET_FACTORY_SMALLROOM;
		}
	}
	else if (sum <= 512.0)
	{
		if (efx.rvbEffects[EFX_PRESET_FACTORY_MEDIUMROOM] != AL_EFFECT_NULL)
		{
			alAuxiliaryEffectSloti(efx.rvbAuxSlot, AL_EFFECTSLOT_EFFECT, efx.rvbEffects[EFX_PRESET_FACTORY_MEDIUMROOM]);
			efx_preset_autoselected = EFX_PRESET_FACTORY_MEDIUMROOM;
		}
	}
	else
	{
		if (efx.rvbEffects[EFX_PRESET_FACTORY_LARGEROOM] != AL_EFFECT_NULL)
		{
			alAuxiliaryEffectSloti(efx.rvbAuxSlot, AL_EFFECTSLOT_EFFECT, efx.rvbEffects[EFX_PRESET_FACTORY_LARGEROOM]);
			efx_preset_autoselected = EFX_PRESET_FACTORY_LARGEROOM;
		}
	}
}

void EFX_RvbUpdate(vec3_t listener_position, int cont)
{
	unsigned err;

	if (!efx.on)
		return;

	// If we are not playing, use default preset
	if (cls.state != ca_active)
	{
		if (efx.rvbEffects[EFX_PRESET_GENERIC] != AL_EFFECT_NULL)
		{
			alAuxiliaryEffectSloti(efx.rvbAuxSlot, AL_EFFECTSLOT_EFFECT, efx.rvbEffects[EFX_PRESET_GENERIC]);
			efx_preset_autoselected = EFX_PRESET_GENERIC;
		}
	}
	else if (cont & MASK_WATER)
	{	// Check if we are underwater and update data
		if (efx.rvbEffects[EFX_PRESET_UNDERWATER] != AL_EFFECT_NULL)
		{
			alAuxiliaryEffectSloti(efx.rvbAuxSlot, AL_EFFECTSLOT_EFFECT, efx.rvbEffects[EFX_PRESET_UNDERWATER]);
			efx_preset_autoselected = EFX_PRESET_UNDERWATER;
		}
	}
	else
		EFX_GetRoomSize(listener_position);

	err = alGetError();
	if (err != AL_NO_ERROR)
		Com_Printf("^1EFX update failed, error %s\n", al_error(err));
}

openal_channel_t *PickChannel(channel_task_t * Channels_TODO, unsigned int entNum, unsigned int entChannel, vec3_t new_vec, int *return_index, vec3_t listener)
{
	openal_channel_t *ch;
	int i;
	int firstToDie = -1;
	bool terminate = false;

	for (i = 0, ch = s_openal_channels; i < s_openal_numChannels;i++, ch++)
	{
		// Don't let game sounds override streaming sounds
		/* if (!i && streaming) continue; */

		// Check if this channel is active
		if (!ch->bufferNum)		// (!ch->sfx)
		{
			// Free channel
			firstToDie = i;
			break;
		}
		/*		if (Flag_checkAL (&Channels_TODO[i], AL_TASK_MANAGER__TERMINATE))
		{
		firstToDie = i;
		terminate = qfalse;
		break;
		}*/
		else
		{
			ALint SourceState;

			alGetSourcei(source_name[i], AL_SOURCE_STATE, &SourceState);

			if (SourceState == AL_STOPPED || SourceState == AL_INITIAL)	// The
																		// source
																		// already
																		// out
																		// of
																		// processing.
			{
				firstToDie = i;
				break;
			}
		}

	}

	// Emergency channel re-caption.
	// Override sound from same entity willow???????
	/* if (firstToDie == -1) { for (i = 0, ch = s_openal_channels; i <
	s_openal_numChannels; i++, ch++) if (ch->entNum == entNum &&
	ch->entChannel == entChannel) { firstToDie = i; break; } } */

	// Emergency channel re-caption. Issue 2
	// Terminate the most distant entity
	if (firstToDie == -1)
	{
		float len, max;
		vec3_t delta;

		terminate = true;		// we need to eat weakest now :)
		if (new_vec) {
			VectorSubtract(listener, new_vec, delta);
			max = VectorLength_Squared(delta);
		}
		else
			max = 0;

		for (i = 0, ch = s_openal_channels; i < s_openal_numChannels;
			i++, ch++)
			if (!FlagAL_check(ch, AL_FLAGS_FLAT2D)	// Don't touch FLAT 2D
													// samples!
				&& FlagAL_check(ch, AL_FLAGS_AL_LOOPING)
				) {
				// ch->_AL_POSITION must have same data:
				// alGetSourcefv(ch->sourceNum, AL_POSITION, (ALfloat
				// *)&al_position);
				VectorSubtract(listener, ch->_AL_POSITION, delta);
				// willow:
				// actually we need vector length here, but squared length
				// can do the same job faster.
				len = VectorLength_Squared(delta);
				if (len > max) {
					firstToDie = i;
					max = len;
				}
			}

		if (firstToDie == -1) {
			return NULL;
		}
	}

	ch = &s_openal_channels[firstToDie];

	if (new_vec) {
		Memcpy(ch->_AL_POSITION, new_vec, sizeof(vec3_t));
		FlagAL_clear(ch, AL_FLAGS_FLAT2D);
//      Flag_clear (&Channels_TODO[firstToDie],AL_TASK_MANAGER__IS_SOURCE_RELATIVE);
	}
	else {
		Memset(ch->_AL_POSITION, 0, sizeof(vec3_t));
		FlagAL_set(ch, AL_FLAGS_FLAT2D);
//      Flag_set (&Channels_TODO[firstToDie],AL_TASK_MANAGER__IS_SOURCE_RELATIVE);
	}

	ch->entNum = entNum;
	ch->entChannel = entChannel;
	// ch->startTime = cl.time;
	if (return_index)
		*return_index = firstToDie;

	// TERMINATE CHANNEL (stop working channel)
	if (terminate)
		TASK_TerminateChannel(&Channels_TODO[firstToDie], ch);
	// alSourceStop(ch->sourceNum);
	// alSourcei(ch->sourceNum, AL_BUFFER, 0);

	return ch;
}

/*
=================
S_FreePlaysound
=================
*/
void S_FreePlaysound(playsound_t * ps)
{
	// unlink from channel
	ps->prev->next = ps->next;
	ps->next->prev = ps->prev;

	// add to free list
	ps->next = s_freeplays.next;
	s_freeplays.next->prev = ps;
	ps->prev = &s_freeplays;
	s_freeplays.next = ps;
}


/*
============
S_Update

Called once each time through the main loop
============
*/
void S_Update(vec3_t listener_position, vec3_t velocity, float orientation[6])
{
	entity_state_t *ent;
	int i, j, cont;
	byte cl_parse_entities_goodjob[MAX_PARSE_ENTITIES];
	playsound_t *ps;
	openal_channel_t *ch;
	channel_task_t Channels_TODO[MAX_CHANNELS];
	int logical_channel_index;

	efx_preset_autoselected = 0;

	if (!s_openal_numChannels)
		return;

	// Set up listener
	alListenerfv(AL_POSITION, listener_position);
	alListenerfv(AL_VELOCITY, velocity);
	alListenerfv(AL_ORIENTATION, orientation);

	cont = CL_PMpointcontents(listener_position);
	if (cont & MASK_WATER)
		alSpeedOfSound(59000);
	else
		alSpeedOfSound(13515);


	if (alConfig.efx)
		EFX_RvbUpdate(listener_position, cont);

	Memset(Channels_TODO, 0, sizeof(Channels_TODO));

	// Add looping sounds
	if (!(cls.state != ca_active || cl_paused->value))
	{
		Memset(cl_parse_entities_goodjob, 0, MAX_PARSE_ENTITIES);

		for (i = cl.frame.parse_entities; i < cl.frame.num_entities + cl.frame.parse_entities; i++)
		{
			int idx = i & (MAX_PARSE_ENTITIES - 1);
			ALuint sfx_numm;
			ent = &cl_parse_entities[idx];
			if (!ent->sound)
			{
				cl_parse_entities_goodjob[idx] = 1;
				continue;		// No sound effect
			}

			sfx_numm = cl.sound_precache[ent->sound];
			if (!sfx_numm)
			{
				cl_parse_entities_goodjob[idx] = 1;
				continue;		// Bad sound effect
			}
			// If this entity is already playing the same sound effect on
			// an
			// active channel, then simply update it
			for (j = 0, ch = s_openal_channels; j < s_openal_numChannels; j++, ch++)
			{
//              if (ch->sfx != sfx) continue;
				if (ch->bufferNum != sfx_numm)
					continue;

				if (ch->entNum != ent->number)
					continue;

				if (!FlagAL_check(ch, AL_FLAGS_AL_LOOPING))
					continue;

				Flag_set(&Channels_TODO[j], AL_TASK_MANAGER__IS_LOOP_ACTIVE);

				{
					vec3_t position;
					vec3_t delta;
					CL_GetEntityOrigin(ch->entNum, position);

					VectorSubtract(position, ch->_AL_POSITION, delta);
					VectorScale(delta, 1 / 30, delta);

					alSourcefv(source_name[j], AL_VELOCITY, delta);
					alSourcefv(source_name[j], AL_POSITION, position);
					// Memcpy(Channels_TODO[j].TASK_AL_VELOCITY, delta,
					// sizeof(vec3_t));
					Memcpy(ch->_AL_POSITION, position, sizeof(vec3_t));
				}

				cl_parse_entities_goodjob[idx] = 1;

				break;			// keep channel alive.
			}
		}
	}
	// Check for stop, no-loops spatialization
	for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++)
	{
		if (ch->bufferNum)		// (ch->sfx)
		{
			ALint SourceState;
			alGetSourcei(source_name[i], AL_SOURCE_STATE, &SourceState);
			if (SourceState == AL_STOPPED || SourceState == AL_INITIAL)	// The
																		// source
																		// already
																		// out
																		// of
																		// processing.
			{
				ch->bufferNum = 0;
				// alSourcei(ch->sourceNum, AL_BUFFER, 0);
			}
			else
			{
				// Kill some loops
				if (FlagAL_check(ch, AL_FLAGS_AL_LOOPING))
				{
					if (!Flag_check(&Channels_TODO[i], AL_TASK_MANAGER__IS_LOOP_ACTIVE))
						TASK_TerminateChannel(&Channels_TODO[i], ch);
				}
				else if (!FlagAL_check(ch, AL_FLAGS_FIXED_POSITION))
				{
					vec3_t velocity, position;

					CL_GetEntityOrigin(ch->entNum, position);
					VectorSubtract(position, ch->_AL_POSITION, velocity);
					VectorScale(velocity, 1 / 30, velocity);

					alSourcefv(source_name[i], AL_VELOCITY, velocity);
					alSourcefv(source_name[i], AL_POSITION, position);
					Memcpy(ch->_AL_POSITION, position, sizeof(vec3_t));
					// Memcpy(Channels_TODO[i].TASK_AL_VELOCITY, velocity,
					// sizeof(vec3_t));
					// alSourcefv(ch->sourceNum, AL_DIRECTION, direction);
				}
			}
		}
	}

	if (!(cls.state != ca_active || cl_paused->value))
		for (i = cl.frame.parse_entities; i < cl.frame.num_entities + cl.frame.parse_entities; i++)
		{
		if (!cl_parse_entities_goodjob[i & (MAX_PARSE_ENTITIES - 1)])
		{
			vec3_t position;

			ent = &cl_parse_entities[i & (MAX_PARSE_ENTITIES - 1)];

			// Pick a channel and start the sound effect
			CL_GetEntityOrigin(ent->number, position);


			//          ????? ps->entnum, ps->entchannel ?????
			if ((ch = PickChannel(Channels_TODO, ent->number, 0, position, &logical_channel_index, listener_position)))
			{
				channel_task_t *current_task = &Channels_TODO[logical_channel_index];

				ch->bufferNum = cl.sound_precache[ent->sound];
				ch->entNum = ent->number;	// loopNum

											// ch->loopSound = AL_TRUE;
				FlagAL_set(ch, AL_FLAGS_AL_LOOPING);
				FlagAL_clear(ch, AL_FLAGS_FIXED_POSITION);

				// ch->distanceMult = 0.3f;
				// Update min/max distance
				current_task->TASK_AL_REFERENCE_DISTANCE = 27;	// willow:
																// Player
																// unit
																// width
																// * 3
																// /
																// 4;
																// Set max distance really far away (default)
																// alSourcef(ch->sourceNum, AL_MAX_DISTANCE, 65536);

																// Update volume and rolloff factor from hacking
																// database, the only decent source :(
				current_task->TASK_AL_ROLLOFF_FACTOR = cl.sound_precache_rolloff_factor[ent->sound];
				current_task->TASK_AL_GAIN = cl.sound_precache_gain[ent->sound];

//              alSourcefv(ch->sourceNum, AL_DIRECTION, ent->angles);
				{
					vec3_t delta, delta2;
					VectorSubtract(position, ent->old_origin, delta2);
					VectorScale(delta2, 1 / 30, delta);
					Memcpy(current_task->TASK_AL_VELOCITY, delta, sizeof(vec3_t));
					// Memcpy(current_task->TASK_AL_VELOCITY,
					// ent->delta, sizeof(vec3_t));
				}

//              current_task->TASK_AL_SOURCE_RELATIVE = AL_FALSE;
				Flag_set(current_task, AL_TASK_MANAGER__EXECUTE);
			}
		}
	}
	// Issue playSounds
	for (;;)
	{
		ps = s_pendingplays.next;

		if (ps == &s_pendingplays)
			break;				// No more pending playSounds

		if (ps->begin > cl.time)
			break;				// No more pending playSounds this frame

		if (ps->fixed_origin)
			ch = PickChannel(Channels_TODO, ps->entnum, ps->entchannel,
				ps->origin, &logical_channel_index,
				listener_position);
		else
		{
			vec3_t position;
			CL_GetEntityOrigin(ps->entnum, position);
			ch = PickChannel(Channels_TODO, ps->entnum, ps->entchannel, position, &logical_channel_index, listener_position);
		}


		if (ch)
		{
			channel_task_t *current_task = &Channels_TODO[logical_channel_index];

			// VectorCopy(ps->origin, ch->position);
			ch->bufferNum = ps->bufferNum;	// ch->sfx = ps->sfx;


			{
				// Update min/max distance
				current_task->TASK_AL_REFERENCE_DISTANCE = 28;	// 34;
																// Set max distance really far away (default)
																// alSourcef(ch->sourceNum, AL_MAX_DISTANCE, 65536);

				current_task->TASK_AL_ROLLOFF_FACTOR = ps->attenuation;
				current_task->TASK_AL_GAIN = ps->volume;

				// SpatializeChannel(ch, ps->origin);
				// Memcpy(ch->_AL_POSITION, ps->origin, sizeof(vec3_t));
				// //alSourcefv(ch->sourceNum, AL_POSITION, ps->origin);
				// //ch->position);
				// alSource3f(ch->sourceNum, AL_VELOCITY, 0, 0, 0);

				if (ps->fixed_origin)
				{
					FlagAL_clear(ch, AL_FLAGS_AL_LOOPING);
					FlagAL_set(ch, AL_FLAGS_FIXED_POSITION);
					Memset(current_task->TASK_AL_VELOCITY, 0, sizeof(vec3_t));
				}
				else
				{
					FlagAL_clear(ch, AL_FLAGS_FIXED_POSITION | AL_FLAGS_AL_LOOPING);
					// willow: TO DO.
					Memset(current_task->TASK_AL_VELOCITY, 0, sizeof(vec3_t));
				}

//              current_task->TASK_AL_SOURCE_RELATIVE = AL_FALSE;
			}
			Flag_set(current_task, AL_TASK_MANAGER__EXECUTE);
		}
		// Free the playSound
		S_FreePlaysound(ps);
	}

	// Direct access to OpenAL layer
	for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++)
	{
		channel_task_t *current_task = &Channels_TODO[i];

		if (Flag_check(current_task, AL_TASK_MANAGER__TERMINATE))
			alSourceStop(source_name[i]);

		if (Flag_check(current_task, AL_TASK_MANAGER__EXECUTE)) 
		{
			ALuint sourceNum = source_name[i];
			alSourcef(sourceNum, AL_PITCH, 1);
			alSource3f(sourceNum, AL_DIRECTION, 0, 0, 0);
			alSourcef(sourceNum, AL_REFERENCE_DISTANCE, current_task->TASK_AL_REFERENCE_DISTANCE);
			alSourcef(sourceNum, AL_ROLLOFF_FACTOR, current_task->TASK_AL_ROLLOFF_FACTOR);
			alSourcefv(sourceNum, AL_POSITION, ch->_AL_POSITION);
			alSourcefv(sourceNum, AL_VELOCITY, current_task->TASK_AL_VELOCITY);
			alSourcei(sourceNum, AL_BUFFER, ch->bufferNum);
			alSourcei(sourceNum, AL_SOURCE_RELATIVE, FlagAL_checkAL(ch, AL_FLAGS_FLAT2D));	// Flag_checkAL
																							// (current_task,
																							// AL_TASK_MANAGER__IS_SOURCE_RELATIVE));
			alSourcei(sourceNum, AL_LOOPING, FlagAL_checkAL(ch, AL_FLAGS_AL_LOOPING));	// ch->loopSound);
			alSourcef(sourceNum, AL_GAIN, current_task->TASK_AL_GAIN * s_volume->value);

			if (alConfig.efx)
				EFX_RvbProcSrc(ch, sourceNum, true);

			alSourcePlay(sourceNum);
		}
	}

	S_Streaming_RecycleBuffers();
}



///////// Sound Tracks support //////////

/*
================
CL_MissionPackCDTrack
Returns correct WAV track number for mission packs.
This assumes that the standard Q2 CD was ripped
as track02-track11, and the Rogue CD as track12-track21.
based on KMQuake2 code
================
*/
int CL_MissionPackCDTrack(int tracknum)
{
	if (modType("rogue"))
	{
		if (tracknum >= 2 && tracknum <= 11)
			return tracknum + 10;
		return tracknum;
	}

	// an out-of-order mix from Q2 and Rogue CDs
	if (modType("xatrix"))
	{
		switch (tracknum)
		{
		case 2: return 9;
		case 3: return 13;
		case 4: return 14;
		case 5: return 7;
		case 6: return 16;
		case 7: return 2;
		case 8: return 15;
		case 9: return 3;
		case 10: return 4;
		case 11: return 18;
		default: return tracknum;
		}
	}

	return tracknum;
}

static int MC_ReadWAV(MC_WAV_t *f, void *buffer, int n)
{
	const int r = min(n, f->size - f->pos);

	if (r > 0)
	{
		Memcpy(buffer, f->start + f->pos, r);
		f->pos += r;
	}

	return r;
}

///static void MC_RewindWAV(MC_WAV_t *f)
///{
///	f->pos = 0;
///}

static void MC_CloseWAV(MC_WAV_t *f)
{
	Z_Free(f->data);
}

// Loads WAV file in memory returned as "wav", but also returns start of audio data in "start"
bool S_LoadWAV(char *name, byte **oWav, byte **oStart, int *oBits, int *oChans, int *oRate, int *oSize)
{
	byte *buffer;
	short channels, width;
	int length = FS_LoadFile(name, (void **)&buffer);

	if (!buffer)
		return false;

	iff_data = buffer;
	iff_end = buffer + length;

	// Find "RIFF" chunk
	FindChunk("RIFF");
	if (!(data_p && !memcmp((void *)(data_p + 8), "WAVE", 4)))
	{
		Com_Printf("^1S_LoadWAV: missing 'RIFF/WAVE' chunks (%s)\n", name);
		Z_Free(buffer);
		return false;
	}
	// Get "fmt " chunk
	iff_data = data_p + 12;

	FindChunk("fmt ");
	if (!data_p)
	{
		Com_Printf("^1S_LoadWAV: missing 'fmt ' chunk (%s)\n", name);
		Z_Free(buffer);
		return false;
	}

	data_p += 8;

	if (GetLittleShort() != 1)
	{
		Com_Printf("^1S_LoadWAV: Microsoft PCM format only (%s)\n", name);
		Z_Free(buffer);
		return false;
	}

	channels = GetLittleShort();
	if (channels < 1 || channels > 2)
	{
		Com_Printf("^1S_LoadWAV: only mono and stereo WAV files supported (%s)\n", name);
		Z_Free(buffer);
		return false;
	}
	*oChans = channels;

	*oRate = GetLittleLong();

	data_p += 4 + 2;

	width = GetLittleShort() >> 3;
	if (width != 1 && width != 2)
	{
		Com_Printf("^1S_LoadWAV: only 8 and 16 bit WAV files supported (%s)\n", name);
		Z_Free(buffer);
		return false;
	}
	*oBits = width * 8;

	// Find data chunk
	FindChunk("data");
	if (!data_p)
	{
		Com_Printf("^1S_LoadWAV: missing 'data' chunk (%s)\n", name);
		Z_Free(buffer);
		return false;
	}

	data_p += 4;
	*oSize = GetLittleLong();

	if (*oSize == 0)
	{
		Com_Printf("^1S_LoadWAV: file with 0 samples (%s)\n", name);
		Z_Free(buffer);
		return false;
	}

	// Load the data
	*oWav = buffer;
	*oStart = data_p;

	return true;
}

static Gen_Interface_t *MC_OpenWAV(char *name, soundparams_t *sp)
{
	static MC_WAV_t f;
	static Gen_Interface_t res;

	if (S_LoadWAV(name, &f.data, &f.start, &sp->bits, &sp->channels, &sp->rate, &f.size))
	{
		f.pos = 0;

		res.read = (readFunc_t)MC_ReadWAV;
///		res.rewind = (rewindFunc_t)MC_RewindWAV;
		res.close = (closeFunc_t)MC_CloseWAV;
		res.f = &f;
		res.ext = "wav";

		return &res;
	}
	return NULL;
}

char *ogg_error(int err)
{
	if (err == -1)
		return "OV_FALSE";
	else if (err == -2)
		return "OV_EOF";
	else if (err == -3)
		return "OV_HOLE";
	else if (err == -128)
		return "OV_EREAD";
	else if (err == -129)
		return "OV_EFAULT";
	else if (err == -130)
		return "OV_EIMPL";
	else if (err == -131)
		return "OV_EINVAL";
	else if (err == -132)
		return "OV_ENOTVORBIS";
	else if (err == -133)
		return "OV_EBADHEADER";
	else if (err == -134)
		return "OV_EVERSION";
	else if (err == -135)
		return "OV_ENOTAUDIO";
	else if (err == -136)
		return "OV_EBADPACKET";
	else if (err == -137)
		return "OV_EBADLINK";
	else if (err == -138)
		return "OV_ENOSEEK";
	return va("0x%X", err);
}

#if 0
static int MC_ReadVorbis(MC_Vorbis_t *f, char *buffer, int n)
{
	int total = 0;
	const int step = 1024 * 64;

///	assert(step < n);
	while (total + step < n)
	{
		// FIXME: check endianess
		int cur = ov_read(&f->ovFile, buffer + total, step, 0, 2, 1, &f->pos);
		if (cur < 0)
			return 0;
		if (cur == 0)
			return total;
		total += cur;
	}
	return total;
}
#else
/// Berserker's FIX: читаем порциями по MUSIC_BUFFER_READ_SIZE байт,
/// и прекращаем чтение когда превысили n.
/// Потому буфер music_buffer размером MAX_STRBUF_SIZE с добавкой MUSIC_BUFFER_READ_SIZE
static int MC_ReadVorbis(MC_Vorbis_t *f, char *buffer, int n)
{
	int total = 0;

	///	assert(step < n);
	while(1)
	{
		// FIXME: check endianess
		int cur = ov_read(&f->ovFile, buffer + total, MUSIC_BUFFER_READ_SIZE, 0, 2, 1, &f->pos);
		if (cur < 0)		/// Error
		{
			Com_Printf("^3Couldn't read OGG stream (%s), error %s\n", savedTrackName, ogg_error(cur));
			return 0;
		}
		if (cur == 0)		/// EOF
			return total;
		if (total + cur > n)
			return total + cur;
		total += cur;
	}
}
#endif

///static void MC_RewindVorbis(MC_Vorbis_t *f)
///{
///	f->pos = 0;
///}

static void MC_CloseVorbis(MC_Vorbis_t *f)
{
	ov_clear(&f->ovFile);
	Z_Free(f->ovRawFile);
}

static Gen_Interface_t *MC_OpenVorbis(char *name, soundparams_t *sp)
{
	static MC_Vorbis_t f;
	static Gen_Interface_t res;

	f.size = FS_LoadFile(name, &f.ovRawFile);
	if (f.size < 0)
		return NULL;

	if (ov_open(NULL, &f.ovFile, (char*)f.ovRawFile, f.size) == 0)
	{
		f.info = ov_info(&f.ovFile, 0);
		f.pos = 0;
		sp->bits = 16;
		sp->channels = f.info->channels;
		sp->rate = f.info->rate;

		res.read = (readFunc_t)MC_ReadVorbis;
///		res.rewind = (rewindFunc_t)MC_RewindVorbis;
		res.close = (closeFunc_t)MC_CloseVorbis;
		res.f = &f;
		res.ext = "ogg";

		return &res;
	}
	else
	{
		Z_Free(f.ovRawFile);
		return NULL;
	}
}

// Generic interface and type specific implementations
supported_exts_t supported_exts[] = {
	{ "wav", (openFunc_t)MC_OpenWAV },
	{ "ogg", (openFunc_t)MC_OpenVorbis }
};

// Gen_OpenAny: try all possible extensions of filename in sequence
static Gen_Interface_t *Gen_OpenAny(char *name, soundparams_t *sp)
{
	// check all supported extensions, or better try in a loop somewhere else?
	int i;

	for (i = 0; i < sizeof(supported_exts) / sizeof(supported_exts[0]); i++)
	{
		char path[MAX_QPATH];
		Gen_Interface_t *res;

		Com_sprintf(path, sizeof(path), "%s.%s", name, supported_exts[i].name);
		res = supported_exts[i].openFunc(path, sp);
		if (res != NULL)
			return res;
	}
	return NULL;
}

// only to be called inside Music_Play
bool Music_PlayFile(char *name)
{
	soundparams_t sp;

	music_handle = Gen_OpenAny(name, &sp);
	if (music_handle)
	{
		Com_DPrintf("Music_PlayFile: playing \"%s.%s\"\n", name, music_handle->ext);
///		if (creditsPlay)
///			S_Streaming_Start(sp.bits, sp.channels, sp.rate, 1.0);
///		else
			S_Streaming_Start(sp.bits, sp.channels, sp.rate, s_musicVolume->value);
		mstat = MSTAT_PLAYING;
		return true;
	}
	else
	{
		Com_Printf("^1Music_PlayFile: unable to load \"%s\"\n", name);
		return false;
	}
}

void Music_Play(char *tr)
{
	int track;
	char name[MAX_QPATH], trck[MAX_QPATH];

	if (music_type != MUSIC_FILES && tr)
		return;

	if (!tr)
		Com_sprintf(trck, sizeof(trck), "%s", savedTrackName);
	else
		Com_sprintf(trck, sizeof(trck), "%s", tr);

	Music_Stop();

	if (!trck[0])
	{
///		if (s_musicrandom->value)
///			track = 2 + rand() % 10;	// original soundtrack has tracks 2 to 11
///		else
		{
			track = atoi(cl.configstrings[CS_TRACK_AMBIENT]);
			if (!track)
				return;
		}
	}

	switch (music_type)
	{
	case MUSIC_CD:
		CDAudio_Play(track, true);
		mstat = MSTAT_PLAYING;
		break;

	case MUSIC_FILES:
		if (trck[0])
			Com_sprintf(name, sizeof(name), "%s", trck);
		else
			Com_sprintf(name, sizeof(name), "music/track%02i", CL_MissionPackCDTrack(track));
		Com_sprintf(savedTrackName, sizeof(savedTrackName), "%s", name);
		Music_PlayFile(name);
		break;
	}
}

void Music_Stop(void)
{
	if (mstat == MSTAT_STOPPED)
		return;

	Com_DPrintf("Stopped playing music\n");

	switch (music_type)
	{
	case MUSIC_CD:
		CDAudio_Stop();
		break;
	case MUSIC_FILES:
		music_handle->close(music_handle->f);
		S_Streaming_Stop();
		break;
	}

	savedTrackName[0] = 0;
	mstat = MSTAT_STOPPED;
}

void Music_Pause(void)
{
	if (mstat != MSTAT_PLAYING)
		return;

	switch (music_type)
	{
	case MUSIC_CD:
		CDAudio_Activate(false);
		break;
	case MUSIC_FILES:
		alSourcePause(source_name[CH_STREAMING]);
		break;
	}

	mstat = MSTAT_PAUSED;
}

void Music_Resume(void)
{
	if (mstat != MSTAT_PAUSED)
		return;

	switch (music_type)
	{
	case MUSIC_CD:
		CDAudio_Activate(true);
		break;
	case MUSIC_FILES:
		alSourcePlay(source_name[CH_STREAMING]);
		break;
	}

	mstat = MSTAT_PLAYING;
}

void Music_Init(void)
{
	if (s_music->value == 0)
	{
		if (cd_nocd->value == 0)
			music_type = MUSIC_NONE;
		else
			music_type = MUSIC_CD;
	}
	else
		music_type = MUSIC_FILES;

	mstat = MSTAT_STOPPED;

	switch (music_type)
	{
	case MUSIC_CD:
		CDAudio_Init();
		break;
	case MUSIC_FILES:
		break;
	default:
	case MUSIC_NONE:
		return;
	}
}

void Music_Shutdown(void)
{
	if (music_type == MUSIC_NONE)
		return;

	Music_Stop();

	switch (music_type)
	{
	case MUSIC_CD:
		CDAudio_Shutdown();
		break;
	case MUSIC_FILES:
		break;
	}
	music_type = MUSIC_NONE;
}

void Music_Update(void)
{
	int n;

	// if we are in the configuration menu, or paused we don't do anything
	if (mstat == MSTAT_PAUSED)
		return;

	// Check for configuration changes
	if (cd_nocd->modified || s_music->modified)/// || s_distance_model->modified)
	{
		Music_Shutdown();
		Music_Init();
		Music_Play(NULL);
		cd_nocd->modified = false;
		s_music->modified = false;
///		s_distance_model->modified = false;
		s_musicVolume->modified = false;
///		s_musicrandom->modified = false;
		return;
	}

	if (music_type == MUSIC_NONE)
		return;

///	if (s_musicrandom->modified)
///	{
///		s_musicrandom->modified = false;
///		savedTrackName[0] = 0;
///		Music_Play(NULL);
///		return;
///	}

	if (s_musicVolume->modified)
	{
		switch (music_type)
		{
		case MUSIC_CD:
			Cvar_SetValue("cd_volume", s_musicVolume->value);
			break;
		case MUSIC_FILES:
			if (creditsPlay)
				alSourcef(source_name[CH_STREAMING], AL_GAIN, 1.0);
			else
				alSourcef(source_name[CH_STREAMING], AL_GAIN, s_musicVolume->value);
			break;
		}
		s_musicVolume->modified = false;
		return;
	}

	// Do the actual update
	switch (music_type)
	{
	case MUSIC_CD:
		CDAudio_Update();
		break;

	case MUSIC_FILES:
		if (mstat != MSTAT_PLAYING || S_Streaming_NumFreeBufs() == 0)
			return;

		// Play a portion of the current file
		n = music_handle->read(music_handle->f, music_buffer, MAX_STRBUF_SIZE);
		if (n == 0)
		{	/// Berserker's FIX: когда уже нечего читать из файла, все-равно ждём окончания проигрывания всей очереди буферов:
			if (streaming.bNumAvail == NUM_STRBUF)	// если число свободных буферов стало максимальным,
				Music_Play(NULL);					// то можно запускать трек заново!
		}
		else
			// don't check return value as the buffer is guaranteed to fit
			S_Streaming_Add(music_buffer, n);
		break;
	}
}

void S_Music_f()
{
	if (Cmd_Argc() == 2)
		Music_Play(Cmd_Argv(1));
	else
		Com_Printf ("music <musicFile>\n");
}

void S_Track_f()
{
	if (Cmd_Argc() == 2)
	{
		char trackName[MAX_QPATH];
		Com_sprintf(trackName, sizeof(trackName), "music/track%02i", atoi(Cmd_Argv(1)));
		Music_Play(trackName);
	}
	else
		Com_Printf("track <trackNumber>\n");
}


////////// SOUND RE-LIGHT EDITOR ///////////////
void R_CalcEFXzoneBounds(EFXzone_t *EFXzone)
{
	EFXzone->minmax[0] = EFXzone->minmax[1] = EFXzone->minmax[2] = 999999;
	EFXzone->minmax[3] = EFXzone->minmax[4] = EFXzone->minmax[5] = -999999;

	// create bounding box for "fog" entity
	if (EFXzone->minmax[0] > EFXzone->origin[0])
		EFXzone->minmax[0] = EFXzone->origin[0];
	if (EFXzone->minmax[1] > EFXzone->origin[1])
		EFXzone->minmax[1] = EFXzone->origin[1];
	if (EFXzone->minmax[2] > EFXzone->origin[2])
		EFXzone->minmax[2] = EFXzone->origin[2];
	if (EFXzone->minmax[0] > EFXzone->origin2[0])
		EFXzone->minmax[0] = EFXzone->origin2[0];
	if (EFXzone->minmax[1] > EFXzone->origin2[1])
		EFXzone->minmax[1] = EFXzone->origin2[1];
	if (EFXzone->minmax[2] > EFXzone->origin2[2])
		EFXzone->minmax[2] = EFXzone->origin2[2];
	if (EFXzone->minmax[3] < EFXzone->origin[0])
		EFXzone->minmax[3] = EFXzone->origin[0];
	if (EFXzone->minmax[4] < EFXzone->origin[1])
		EFXzone->minmax[4] = EFXzone->origin[1];
	if (EFXzone->minmax[5] < EFXzone->origin[2])
		EFXzone->minmax[5] = EFXzone->origin[2];
	if (EFXzone->minmax[3] < EFXzone->origin2[0])
		EFXzone->minmax[3] = EFXzone->origin2[0];
	if (EFXzone->minmax[4] < EFXzone->origin2[1])
		EFXzone->minmax[4] = EFXzone->origin2[1];
	if (EFXzone->minmax[5] < EFXzone->origin2[2])
		EFXzone->minmax[5] = EFXzone->origin2[2];
}


void CL_CreateEFXzone_f()
{
	int preset = 0;

	if (!LightEditor_Enabled(false))
		return;

	if (!efx.on)
	{
		Com_Printf("^1EFX disabled!\n");
		return;
	}

	curEFXzone = AllocEFXzone();
	if (!curEFXzone)
	{
		Com_Printf("^3Too many EFX zones\n");
		Cl_UpdateEditorCvars(ED_EFXZONE);
		return;
	}

	if (Cmd_Argc() == 2)
	{
		preset = atoi(Cmd_Argv(1));
		if (preset < 0)
			preset = 0;
		else if (preset >= NUM_EFX_PRESETS)
			preset = NUM_EFX_PRESETS - 1;
	}

	VectorSet(curEFXzone->origin, r_origin[0] + 1, r_origin[1] + 1, r_origin[2] + 1);
	VectorSet(curEFXzone->origin2, r_origin[0] - 1, r_origin[1] - 1, r_origin[2] - 1);
	curEFXzone->preset = preset;
	curEFXzone->ed = true;
	Com_Printf("EFX zone spawned\n");
	Cl_UpdateEditorCvars(ED_EFXZONE | ED_MODIFIED);
}

void CL_DeleteEFXzone_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!efx.on)
	{
		Com_Printf("^1EFX disabled!\n");
		return;
	}

	if (!curEFXzone)
		return;

	EFXzone_t	*EFXzone;

	for (EFXzone = curEFXzone; EFXzone<&EFXzone_infos[numEFXzones - 1]; EFXzone++)
		Memcpy(EFXzone, EFXzone + 1, sizeof(EFXzone_t));

	Memset(&EFXzone_infos[numEFXzones - 1], 0, sizeof(EFXzone_t));
	numEFXzones--;

	if (curEFXzone == &EFXzone_infos[numEFXzones])
	{
		if (numEFXzones)
			curEFXzone--;
		else
			curEFXzone = NULL;
	}

	Com_Printf("EFX zone deleted.\n");
	Cl_UpdateEditorCvars(ED_EFXZONE | ED_MODIFIED);
}

void CL_DeleteEFXzoneAll_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!efx.on)
	{
		Com_Printf("^1EFX disabled!\n");
		return;
	}

	if (!curEFXzone)
		return;

	Memset(EFXzone_infos, 0, sizeof(EFXzone_infos));
	curEFXzone = NULL;

	Com_Printf("All EFX zones deleted.\n");
	Cl_UpdateEditorCvars(ED_EFXZONE | ED_MODIFIED);
}

void CL_UnSelectEFXzone_f()
{
	if (!LightEditor_Enabled(false))
		return;

	curEFXzone = NULL;
	Cl_UpdateEditorCvars(ED_EFXZONE);
}


void CL_SelectEFXzone_f()
{
	if (!LightEditor_Enabled(false))
		return;

	int	i;
	for (i = 0; i<numEFXzones; i++)
	{
		if (r_origin[0]<EFXzone_infos[i].minmax[0])
			continue;
		if (r_origin[1]<EFXzone_infos[i].minmax[1])
			continue;
		if (r_origin[2]<EFXzone_infos[i].minmax[2])
			continue;
		if (r_origin[0]>EFXzone_infos[i].minmax[3])
			continue;
		if (r_origin[1]>EFXzone_infos[i].minmax[4])
			continue;
		if (r_origin[2]>EFXzone_infos[i].minmax[5])
			continue;
		// EFX zone found!
		break;
	}

	if (i<numEFXzones)
	{
		curEFXzone = &EFXzone_infos[i];
		Com_Printf("EFX zone selected\n");
	}
	else
		curEFXzone = NULL;
	Cl_UpdateEditorCvars(ED_EFXZONE);
}

void CL_SelectEFXzoneNext_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!numEFXzones)
		return;

	int	i;
	if (curEFXzone)
	{
		i = curEFXzone - EFXzone_infos + 1;
		if (i >= numEFXzones)
			i = 0;
	}
	else
		i = 0;

	curEFXzone = &EFXzone_infos[i];
	Cl_UpdateEditorCvars(ED_EFXZONE);
}

void CL_SelectEFXzonePrev_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!numEFXzones)
		return;

	int	i;
	if (curEFXzone)
	{
		i = curEFXzone - EFXzone_infos - 1;
		if (i < 0)
			i = numEFXzones - 1;
	}
	else
		i = numEFXzones - 1;

	curEFXzone = &EFXzone_infos[i];
	Cl_UpdateEditorCvars(ED_EFXZONE);
}

void CL_EFXzonePreset_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!efx.on)
	{
		Com_Printf("^1EFX disabled!\n");
		return;
	}

	if (!curEFXzone)
		return;

	if (Cmd_Argc() != 2)
	{
		Com_Printf("^3Usage: EFXzone_preset <d>\n");
		return;
	}

	int preset = atoi(Cmd_Argv(1));
	if (preset < 0)
		preset = 0;
	else if (preset >= NUM_EFX_PRESETS)
		preset = NUM_EFX_PRESETS - 1;
	curEFXzone->preset = preset;
	Com_Printf("EFX zone preset changed.\n");
	Cl_UpdateEditorCvars(ED_EFXZONE | ED_MODIFIED);
}

void CL_OriginEFXzone0_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!efx.on)
	{
		Com_Printf("^1EFX disabled!\n");
		return;
	}

	if (!curEFXzone)
		return;

	VectorSet(curEFXzone->origin, r_origin[0] + 1, r_origin[1] + 1, r_origin[2] + 1);

	R_CalcEFXzoneBounds(curEFXzone);
	Cl_UpdateEditorCvars(ED_EFXZONE | ED_MODIFIED);
}


void CL_OriginEFXzone1_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!efx.on)
	{
		Com_Printf("^1EFX disabled!\n");
		return;
	}

	if (!curEFXzone)
		return;

	VectorSet(curEFXzone->origin2, r_origin[0] - 1, r_origin[1] - 1, r_origin[2] - 1);

	R_CalcEFXzoneBounds(curEFXzone);
	Cl_UpdateEditorCvars(ED_EFXZONE | ED_MODIFIED);
}


void CL_AddEFXzoneOrigin0_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!efx.on)
	{
		Com_Printf("^1EFX disabled!\n");
		return;
	}

	if (!curEFXzone)
		return;

	if (Cmd_Argc() != 4)
	{
		Com_Printf("^3Usage: add_EFXzone_origin0 <dx dy dz>\n");
		return;
	}

	curEFXzone->origin[0] += atof(Cmd_Argv(1));
	curEFXzone->origin[1] += atof(Cmd_Argv(2));
	curEFXzone->origin[2] += atof(Cmd_Argv(3));

	R_CalcEFXzoneBounds(curEFXzone);
	Cl_UpdateEditorCvars(ED_EFXZONE | ED_MODIFIED);
}


void CL_AddEFXzoneOrigin1_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!efx.on)
	{
		Com_Printf("^1EFX disabled!\n");
		return;
	}

	if (!curEFXzone)
		return;

	if (Cmd_Argc() != 4)
	{
		Com_Printf("^3Usage: add_EFXzone_origin1 <dx dy dz>\n");
		return;
	}

	curEFXzone->origin2[0] += atof(Cmd_Argv(1));
	curEFXzone->origin2[1] += atof(Cmd_Argv(2));
	curEFXzone->origin2[2] += atof(Cmd_Argv(3));

	R_CalcEFXzoneBounds(curEFXzone);
	Cl_UpdateEditorCvars(ED_EFXZONE | ED_MODIFIED);
}

void CL_ListEFX_f()
{
	numEFXeffectsLoaded = 0;
	for (int i = 0; i < NUM_EFX_PRESETS; i++)
	{
		if (efx.rvbEffects[i] != AL_EFFECT_NULL)
		{
			Com_Printf("^2  %i: %s\n", i, EFX_Names[i]);
			numEFXeffectsLoaded++;
		}
		else
			Com_Printf("^1X %i: %s\n", i, EFX_Names[i]);
	}
	Com_Printf("--------------------------------\n");
	if (numEFXeffectsLoaded == NUM_EFX_PRESETS)
		Com_Printf("^2%i presets loaded\n", NUM_EFX_PRESETS);
	else
		Com_Printf("^3%i of %i presets loaded\n", numEFXeffectsLoaded, NUM_EFX_PRESETS);
}

void CL_EFXzonePresetNext_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!efx.on)
	{
		Com_Printf("^1EFX disabled!\n");
		return;
	}

	if (!curEFXzone)
		return;

	curEFXzone->preset++;
	if (curEFXzone->preset >= NUM_EFX_PRESETS)
		curEFXzone->preset = 0;
	Cl_UpdateEditorCvars(ED_EFXZONE | ED_MODIFIED);
}

void CL_EFXzonePresetPrev_f()
{
	if (!LightEditor_Enabled(false))
		return;

	if (!efx.on)
	{
		Com_Printf("^1EFX disabled!\n");
		return;
	}

	if (!curEFXzone)
		return;

	curEFXzone->preset--;
	if (curEFXzone->preset < 0)
		curEFXzone->preset = NUM_EFX_PRESETS - 1;
	Cl_UpdateEditorCvars(ED_EFXZONE | ED_MODIFIED);
}
