shithub: qk1

Download patch

ref: de503b3834162b9de155772f18983c8c83907eb4
parent: be559d26383e23407fe568d178cef7273c411dbf
author: Travis Bradshaw <travis.bradshaw@idsoftware.com>
date: Tue Jan 31 11:03:17 EST 2012

Just kind of shoving the QuakeWorld QuakeC source in here.

--- a/WinQuake/gl_test.c
+++ b/WinQuake/gl_test.c
@@ -1,0 +1,182 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+
+#include "quakedef.h"
+
+#ifdef GLTEST
+
+typedef struct
+{
+	plane_t	*plane;
+	vec3_t	origin;
+	vec3_t	normal;
+	vec3_t	up;
+	vec3_t	right;
+	vec3_t	reflect;
+	float	length;
+} puff_t;
+
+#define	MAX_PUFFS	64
+
+puff_t	puffs[MAX_PUFFS];
+
+
+void Test_Init (void)
+{
+}
+
+
+
+plane_t	junk;
+plane_t	*HitPlane (vec3_t start, vec3_t end)
+{
+	trace_t		trace;
+
+// fill in a default trace
+	memset (&trace, 0, sizeof(trace_t));
+	trace.fraction = 1;
+	trace.allsolid = true;
+	VectorCopy (end, trace.endpos);
+
+	SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, start, end, &trace);
+
+	junk = trace.plane;
+	return &junk;
+}
+
+void Test_Spawn (vec3_t origin)
+{
+	int		i;
+	puff_t	*p;
+	vec3_t	temp;
+	vec3_t	normal;
+	vec3_t	incoming;
+	plane_t	*plane;
+	float	d;
+
+	for (i=0,p=puffs ; i<MAX_PUFFS ; i++,p++)
+	{
+		if (p->length <= 0)
+			break;
+	}
+	if (i == MAX_PUFFS)
+		return;
+
+	VectorSubtract (r_refdef.vieworg, origin, incoming);
+	VectorSubtract (origin, incoming, temp);
+	plane = HitPlane (r_refdef.vieworg, temp);
+
+	VectorNormalize (incoming);
+	d = DotProduct (incoming, plane->normal);
+	VectorSubtract (vec3_origin, incoming, p->reflect);
+	VectorMA (p->reflect, d*2, plane->normal, p->reflect);
+
+	VectorCopy (origin, p->origin);
+	VectorCopy (plane->normal, p->normal);
+
+	CrossProduct (incoming, p->normal, p->up);
+
+	CrossProduct (p->up, p->normal, p->right);
+
+	p->length = 8;
+}
+
+void DrawPuff (puff_t *p)
+{
+	vec3_t	pts[2][3];
+	int		i, j;
+	float	s, d;
+
+	for (i=0 ; i<2 ; i++)
+	{
+		if (i == 1)
+		{
+			s = 6;
+			d = p->length;
+		}
+		else
+		{
+			s = 2;
+			d = 0;
+		}
+
+		for (j=0 ; j<3 ; j++)
+		{
+			pts[i][0][j] = p->origin[j] + p->up[j]*s + p->reflect[j]*d;
+			pts[i][1][j] = p->origin[j] + p->right[j]*s + p->reflect[j]*d;
+			pts[i][2][j] = p->origin[j] + -p->right[j]*s + p->reflect[j]*d;
+		}
+	}
+
+	glColor3f (1, 0, 0);
+
+#if 0
+	glBegin (GL_LINES);
+	glVertex3fv (p->origin);
+	glVertex3f (p->origin[0] + p->length*p->reflect[0],
+		p->origin[1] + p->length*p->reflect[1],
+		p->origin[2] + p->length*p->reflect[2]);
+
+	glVertex3fv (pts[0][0]);
+	glVertex3fv (pts[1][0]);
+
+	glVertex3fv (pts[0][1]);
+	glVertex3fv (pts[1][1]);
+
+	glVertex3fv (pts[0][2]);
+	glVertex3fv (pts[1][2]);
+
+	glEnd ();
+#endif
+
+	glBegin (GL_QUADS);
+	for (i=0 ; i<3 ; i++)
+	{
+		j = (i+1)%3;
+		glVertex3fv (pts[0][j]);
+		glVertex3fv (pts[1][j]);
+		glVertex3fv (pts[1][i]);
+		glVertex3fv (pts[0][i]);
+	}
+	glEnd ();
+
+	glBegin (GL_TRIANGLES);
+	glVertex3fv (pts[1][0]);
+	glVertex3fv (pts[1][1]);
+	glVertex3fv (pts[1][2]);
+	glEnd ();
+
+	p->length -= host_frametime*2;
+}
+
+
+void Test_Draw (void)
+{
+	int		i;
+	puff_t	*p;
+
+	for (i=0, p=puffs ; i<MAX_PUFFS ; i++,p++)
+	{
+		if (p->length > 0)
+			DrawPuff (p);
+	}
+}
+
+#endif
--- /dev/null
+++ b/WinQuake/gl_warp.c
@@ -1,0 +1,1092 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+// gl_warp.c -- sky and water polygons
+
+#include "quakedef.h"
+
+extern	model_t	*loadmodel;
+
+int		skytexturenum;
+
+int		solidskytexture;
+int		alphaskytexture;
+float	speedscale;		// for top sky and bottom sky
+
+msurface_t	*warpface;
+
+extern cvar_t gl_subdivide_size;
+
+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;
+
+	if (numverts > 60)
+		Sys_Error ("numverts = %i", numverts);
+
+	BoundPoly (numverts, verts, mins, maxs);
+
+	for (i=0 ; i<3 ; i++)
+	{
+		m = (mins[i] + maxs[i]) * 0.5;
+		m = gl_subdivide_size.value * floor (m/gl_subdivide_size.value + 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;
+	}
+
+	poly = Hunk_Alloc (sizeof(glpoly_t) + (numverts-4) * VERTEXSIZE*sizeof(float));
+	poly->next = warpface->polys;
+	warpface->polys = poly;
+	poly->numverts = numverts;
+	for (i=0 ; i<numverts ; i++, verts+= 3)
+	{
+		VectorCopy (verts, poly->verts[i]);
+		s = DotProduct (verts, warpface->texinfo->vecs[0]);
+		t = DotProduct (verts, warpface->texinfo->vecs[1]);
+		poly->verts[i][3] = s;
+		poly->verts[i][4] = t;
+	}
+}
+
+/*
+================
+GL_SubdivideSurface
+
+Breaks a polygon up along axial 64 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;
+	texture_t	*t;
+
+	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]);
+}
+
+//=========================================================
+
+
+
+// speed up sin calculations - Ed
+float	turbsin[] =
+{
+	#include "gl_warp_sin.h"
+};
+#define TURBSCALE (256.0 / (2 * M_PI))
+
+/*
+=============
+EmitWaterPolys
+
+Does a water warp on the pre-fragmented glpoly_t chain
+=============
+*/
+void EmitWaterPolys (msurface_t *fa)
+{
+	glpoly_t	*p;
+	float		*v;
+	int			i;
+	float		s, t, os, ot;
+
+
+	for (p=fa->polys ; p ; p=p->next)
+	{
+		glBegin (GL_POLYGON);
+		for (i=0,v=p->verts[0] ; i<p->numverts ; i++, v+=VERTEXSIZE)
+		{
+			os = v[3];
+			ot = v[4];
+
+			s = os + turbsin[(int)((ot*0.125+realtime) * TURBSCALE) & 255];
+			s *= (1.0/64);
+
+			t = ot + turbsin[(int)((os*0.125+realtime) * TURBSCALE) & 255];
+			t *= (1.0/64);
+
+			glTexCoord2f (s, t);
+			glVertex3fv (v);
+		}
+		glEnd ();
+	}
+}
+
+
+
+
+/*
+=============
+EmitSkyPolys
+=============
+*/
+void EmitSkyPolys (msurface_t *fa)
+{
+	glpoly_t	*p;
+	float		*v;
+	int			i;
+	float	s, t;
+	vec3_t	dir;
+	float	length;
+
+	for (p=fa->polys ; p ; p=p->next)
+	{
+		glBegin (GL_POLYGON);
+		for (i=0,v=p->verts[0] ; i<p->numverts ; i++, v+=VERTEXSIZE)
+		{
+			VectorSubtract (v, r_origin, dir);
+			dir[2] *= 3;	// flatten the sphere
+
+			length = dir[0]*dir[0] + dir[1]*dir[1] + dir[2]*dir[2];
+			length = sqrt (length);
+			length = 6*63/length;
+
+			dir[0] *= length;
+			dir[1] *= length;
+
+			s = (speedscale + dir[0]) * (1.0/128);
+			t = (speedscale + dir[1]) * (1.0/128);
+
+			glTexCoord2f (s, t);
+			glVertex3fv (v);
+		}
+		glEnd ();
+	}
+}
+
+/*
+===============
+EmitBothSkyLayers
+
+Does a sky warp on the pre-fragmented glpoly_t chain
+This will be called for brushmodels, the world
+will have them chained together.
+===============
+*/
+void EmitBothSkyLayers (msurface_t *fa)
+{
+	int			i;
+	int			lindex;
+	float		*vec;
+
+	GL_DisableMultitexture();
+
+	GL_Bind (solidskytexture);
+	speedscale = realtime*8;
+	speedscale -= (int)speedscale & ~127 ;
+
+	EmitSkyPolys (fa);
+
+	glEnable (GL_BLEND);
+	GL_Bind (alphaskytexture);
+	speedscale = realtime*16;
+	speedscale -= (int)speedscale & ~127 ;
+
+	EmitSkyPolys (fa);
+
+	glDisable (GL_BLEND);
+}
+
+#ifndef QUAKE2
+/*
+=================
+R_DrawSkyChain
+=================
+*/
+void R_DrawSkyChain (msurface_t *s)
+{
+	msurface_t	*fa;
+
+	GL_DisableMultitexture();
+
+	// used when gl_texsort is on
+	GL_Bind(solidskytexture);
+	speedscale = realtime*8;
+	speedscale -= (int)speedscale & ~127 ;
+
+	for (fa=s ; fa ; fa=fa->texturechain)
+		EmitSkyPolys (fa);
+
+	glEnable (GL_BLEND);
+	GL_Bind (alphaskytexture);
+	speedscale = realtime*16;
+	speedscale -= (int)speedscale & ~127 ;
+
+	for (fa=s ; fa ; fa=fa->texturechain)
+		EmitSkyPolys (fa);
+
+	glDisable (GL_BLEND);
+}
+
+#endif
+
+/*
+=================================================================
+
+  Quake 2 environment sky
+
+=================================================================
+*/
+
+#ifdef QUAKE2
+
+
+#define	SKY_TEX		2000
+
+/*
+=================================================================
+
+  PCX Loading
+
+=================================================================
+*/
+
+typedef struct
+{
+    char	manufacturer;
+    char	version;
+    char	encoding;
+    char	bits_per_pixel;
+    unsigned short	xmin,ymin,xmax,ymax;
+    unsigned short	hres,vres;
+    unsigned char	palette[48];
+    char	reserved;
+    char	color_planes;
+    unsigned short	bytes_per_line;
+    unsigned short	palette_type;
+    char	filler[58];
+    unsigned 	data;			// unbounded
+} pcx_t;
+
+byte	*pcx_rgb;
+
+/*
+============
+LoadPCX
+============
+*/
+void LoadPCX (FILE *f)
+{
+	pcx_t	*pcx, pcxbuf;
+	byte	palette[768];
+	byte	*pix;
+	int		x, y;
+	int		dataByte, runLength;
+	int		count;
+
+//
+// parse the PCX file
+//
+	fread (&pcxbuf, 1, sizeof(pcxbuf), f);
+
+	pcx = &pcxbuf;
+
+	if (pcx->manufacturer != 0x0a
+		|| pcx->version != 5
+		|| pcx->encoding != 1
+		|| pcx->bits_per_pixel != 8
+		|| pcx->xmax >= 320
+		|| pcx->ymax >= 256)
+	{
+		Con_Printf ("Bad pcx file\n");
+		return;
+	}
+
+	// seek to palette
+	fseek (f, -768, SEEK_END);
+	fread (palette, 1, 768, f);
+
+	fseek (f, sizeof(pcxbuf) - 4, SEEK_SET);
+
+	count = (pcx->xmax+1) * (pcx->ymax+1);
+	pcx_rgb = malloc( count * 4);
+
+	for (y=0 ; y<=pcx->ymax ; y++)
+	{
+		pix = pcx_rgb + 4*y*(pcx->xmax+1);
+		for (x=0 ; x<=pcx->ymax ; )
+		{
+			dataByte = fgetc(f);
+
+			if((dataByte & 0xC0) == 0xC0)
+			{
+				runLength = dataByte & 0x3F;
+				dataByte = fgetc(f);
+			}
+			else
+				runLength = 1;
+
+			while(runLength-- > 0)
+			{
+				pix[0] = palette[dataByte*3];
+				pix[1] = palette[dataByte*3+1];
+				pix[2] = palette[dataByte*3+2];
+				pix[3] = 255;
+				pix += 4;
+				x++;
+			}
+		}
+	}
+}
+
+/*
+=========================================================
+
+TARGA LOADING
+
+=========================================================
+*/
+
+typedef struct _TargaHeader {
+	unsigned char 	id_length, colormap_type, image_type;
+	unsigned short	colormap_index, colormap_length;
+	unsigned char	colormap_size;
+	unsigned short	x_origin, y_origin, width, height;
+	unsigned char	pixel_size, attributes;
+} TargaHeader;
+
+
+TargaHeader		targa_header;
+byte			*targa_rgba;
+
+int fgetLittleShort (FILE *f)
+{
+	byte	b1, b2;
+
+	b1 = fgetc(f);
+	b2 = fgetc(f);
+
+	return (short)(b1 + b2*256);
+}
+
+int fgetLittleLong (FILE *f)
+{
+	byte	b1, b2, b3, b4;
+
+	b1 = fgetc(f);
+	b2 = fgetc(f);
+	b3 = fgetc(f);
+	b4 = fgetc(f);
+
+	return b1 + (b2<<8) + (b3<<16) + (b4<<24);
+}
+
+
+/*
+=============
+LoadTGA
+=============
+*/
+void LoadTGA (FILE *fin)
+{
+	int				columns, rows, numPixels;
+	byte			*pixbuf;
+	int				row, column;
+
+	targa_header.id_length = fgetc(fin);
+	targa_header.colormap_type = fgetc(fin);
+	targa_header.image_type = fgetc(fin);
+	
+	targa_header.colormap_index = fgetLittleShort(fin);
+	targa_header.colormap_length = fgetLittleShort(fin);
+	targa_header.colormap_size = fgetc(fin);
+	targa_header.x_origin = fgetLittleShort(fin);
+	targa_header.y_origin = fgetLittleShort(fin);
+	targa_header.width = fgetLittleShort(fin);
+	targa_header.height = fgetLittleShort(fin);
+	targa_header.pixel_size = fgetc(fin);
+	targa_header.attributes = fgetc(fin);
+
+	if (targa_header.image_type!=2 
+		&& targa_header.image_type!=10) 
+		Sys_Error ("LoadTGA: Only type 2 and 10 targa RGB images supported\n");
+
+	if (targa_header.colormap_type !=0 
+		|| (targa_header.pixel_size!=32 && targa_header.pixel_size!=24))
+		Sys_Error ("Texture_LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n");
+
+	columns = targa_header.width;
+	rows = targa_header.height;
+	numPixels = columns * rows;
+
+	targa_rgba = malloc (numPixels*4);
+	
+	if (targa_header.id_length != 0)
+		fseek(fin, targa_header.id_length, SEEK_CUR);  // skip TARGA image comment
+	
+	if (targa_header.image_type==2) {  // Uncompressed, RGB images
+		for(row=rows-1; row>=0; row--) {
+			pixbuf = targa_rgba + row*columns*4;
+			for(column=0; column<columns; column++) {
+				unsigned char red,green,blue,alphabyte;
+				switch (targa_header.pixel_size) {
+					case 24:
+							
+							blue = getc(fin);
+							green = getc(fin);
+							red = getc(fin);
+							*pixbuf++ = red;
+							*pixbuf++ = green;
+							*pixbuf++ = blue;
+							*pixbuf++ = 255;
+							break;
+					case 32:
+							blue = getc(fin);
+							green = getc(fin);
+							red = getc(fin);
+							alphabyte = getc(fin);
+							*pixbuf++ = red;
+							*pixbuf++ = green;
+							*pixbuf++ = blue;
+							*pixbuf++ = alphabyte;
+							break;
+				}
+			}
+		}
+	}
+	else if (targa_header.image_type==10) {   // Runlength encoded RGB images
+		unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j;
+		for(row=rows-1; row>=0; row--) {
+			pixbuf = targa_rgba + row*columns*4;
+			for(column=0; column<columns; ) {
+				packetHeader=getc(fin);
+				packetSize = 1 + (packetHeader & 0x7f);
+				if (packetHeader & 0x80) {        // run-length packet
+					switch (targa_header.pixel_size) {
+						case 24:
+								blue = getc(fin);
+								green = getc(fin);
+								red = getc(fin);
+								alphabyte = 255;
+								break;
+						case 32:
+								blue = getc(fin);
+								green = getc(fin);
+								red = getc(fin);
+								alphabyte = getc(fin);
+								break;
+					}
+	
+					for(j=0;j<packetSize;j++) {
+						*pixbuf++=red;
+						*pixbuf++=green;
+						*pixbuf++=blue;
+						*pixbuf++=alphabyte;
+						column++;
+						if (column==columns) { // run spans across rows
+							column=0;
+							if (row>0)
+								row--;
+							else
+								goto breakOut;
+							pixbuf = targa_rgba + row*columns*4;
+						}
+					}
+				}
+				else {                            // non run-length packet
+					for(j=0;j<packetSize;j++) {
+						switch (targa_header.pixel_size) {
+							case 24:
+									blue = getc(fin);
+									green = getc(fin);
+									red = getc(fin);
+									*pixbuf++ = red;
+									*pixbuf++ = green;
+									*pixbuf++ = blue;
+									*pixbuf++ = 255;
+									break;
+							case 32:
+									blue = getc(fin);
+									green = getc(fin);
+									red = getc(fin);
+									alphabyte = getc(fin);
+									*pixbuf++ = red;
+									*pixbuf++ = green;
+									*pixbuf++ = blue;
+									*pixbuf++ = alphabyte;
+									break;
+						}
+						column++;
+						if (column==columns) { // pixel packet run spans across rows
+							column=0;
+							if (row>0)
+								row--;
+							else
+								goto breakOut;
+							pixbuf = targa_rgba + row*columns*4;
+						}						
+					}
+				}
+			}
+			breakOut:;
+		}
+	}
+	
+	fclose(fin);
+}
+
+/*
+==================
+R_LoadSkys
+==================
+*/
+char	*suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
+void R_LoadSkys (void)
+{
+	int		i;
+	FILE	*f;
+	char	name[64];
+
+	for (i=0 ; i<6 ; i++)
+	{
+		GL_Bind (SKY_TEX + i);
+		sprintf (name, "gfx/env/bkgtst%s.tga", suf[i]);
+		COM_FOpenFile (name, &f);
+		if (!f)
+		{
+			Con_Printf ("Couldn't load %s\n", name);
+			continue;
+		}
+		LoadTGA (f);
+//		LoadPCX (f);
+
+		glTexImage2D (GL_TEXTURE_2D, 0, gl_solid_format, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, targa_rgba);
+//		glTexImage2D (GL_TEXTURE_2D, 0, gl_solid_format, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, pcx_rgb);
+
+		free (targa_rgba);
+//		free (pcx_rgb);
+
+		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+	}
+}
+
+
+vec3_t	skyclip[6] = {
+	{1,1,0},
+	{1,-1,0},
+	{0,-1,1},
+	{0,1,1},
+	{1,0,1},
+	{-1,0,1} 
+};
+int	c_sky;
+
+// 1 = s, 2 = t, 3 = 2048
+int	st_to_vec[6][3] =
+{
+	{3,-1,2},
+	{-3,1,2},
+
+	{1,3,2},
+	{-1,-3,2},
+
+	{-2,-1,3},		// 0 degrees yaw, look straight up
+	{2,-1,-3}		// look straight down
+
+//	{-1,2,3},
+//	{1,2,-3}
+};
+
+// s = [0]/[2], t = [1]/[2]
+int	vec_to_st[6][3] =
+{
+	{-2,3,1},
+	{2,3,-1},
+
+	{1,3,2},
+	{-1,3,-2},
+
+	{-2,-1,3},
+	{-2,1,-3}
+
+//	{-1,2,3},
+//	{1,2,-3}
+};
+
+float	skymins[2][6], skymaxs[2][6];
+
+void DrawSkyPolygon (int nump, vec3_t vecs)
+{
+	int		i,j;
+	vec3_t	v, av;
+	float	s, t, dv;
+	int		axis;
+	float	*vp;
+
+	c_sky++;
+#if 0
+glBegin (GL_POLYGON);
+for (i=0 ; i<nump ; i++, vecs+=3)
+{
+	VectorAdd(vecs, r_origin, v);
+	glVertex3fv (v);
+}
+glEnd();
+return;
+#endif
+	// decide which face it maps to
+	VectorCopy (vec3_origin, 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];
+
+		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;
+	}
+}
+
+#define	MAX_CLIP_VERTS	64
+void ClipSkyPolygon (int nump, vec3_t vecs, int stage)
+{
+	float	*norm;
+	float	*v;
+	qboolean	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)
+		Sys_Error ("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);
+}
+
+/*
+=================
+R_DrawSkyChain
+=================
+*/
+void R_DrawSkyChain (msurface_t *s)
+{
+	msurface_t	*fa;
+
+	int		i;
+	vec3_t	verts[MAX_CLIP_VERTS];
+	glpoly_t	*p;
+
+	c_sky = 0;
+	GL_Bind(solidskytexture);
+
+	// calculate vertex values for sky box
+
+	for (fa=s ; fa ; fa=fa->texturechain)
+	{
+		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);
+		}
+	}
+}
+
+
+/*
+==============
+R_ClearSkyBox
+==============
+*/
+void R_ClearSkyBox (void)
+{
+	int		i;
+
+	for (i=0 ; i<6 ; i++)
+	{
+		skymins[0][i] = skymins[1][i] = 9999;
+		skymaxs[0][i] = skymaxs[1][i] = -9999;
+	}
+}
+
+
+void MakeSkyVec (float s, float t, int axis)
+{
+	vec3_t		v, b;
+	int			j, k;
+
+	b[0] = s*2048;
+	b[1] = t*2048;
+	b[2] = 2048;
+
+	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];
+		v[j] += r_origin[j];
+	}
+
+	// avoid bilerp seam
+	s = (s+1)*0.5;
+	t = (t+1)*0.5;
+
+	if (s < 1.0/512)
+		s = 1.0/512;
+	else if (s > 511.0/512)
+		s = 511.0/512;
+	if (t < 1.0/512)
+		t = 1.0/512;
+	else if (t > 511.0/512)
+		t = 511.0/512;
+
+	t = 1.0 - t;
+	glTexCoord2f (s, t);
+	glVertex3fv (v);
+}
+
+/*
+==============
+R_DrawSkyBox
+==============
+*/
+int	skytexorder[6] = {0,2,1,3,4,5};
+void R_DrawSkyBox (void)
+{
+	int		i, j, k;
+	vec3_t	v;
+	float	s, t;
+
+#if 0
+glEnable (GL_BLEND);
+glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+glColor4f (1,1,1,0.5);
+glDisable (GL_DEPTH_TEST);
+#endif
+	for (i=0 ; i<6 ; i++)
+	{
+		if (skymins[0][i] >= skymaxs[0][i]
+		|| skymins[1][i] >= skymaxs[1][i])
+			continue;
+
+		GL_Bind (SKY_TEX+skytexorder[i]);
+#if 0
+skymins[0][i] = -1;
+skymins[1][i] = -1;
+skymaxs[0][i] = 1;
+skymaxs[1][i] = 1;
+#endif
+		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 ();
+	}
+#if 0
+glDisable (GL_BLEND);
+glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+glColor4f (1,1,1,0.5);
+glEnable (GL_DEPTH_TEST);
+#endif
+}
+
+
+#endif
+
+//===============================================================
+
+/*
+=============
+R_InitSky
+
+A sky texture is 256*128, with the right side being a masked overlay
+==============
+*/
+void R_InitSky (texture_t *mt)
+{
+	int			i, j, p;
+	byte		*src;
+	unsigned	trans[128*128];
+	unsigned	transpix;
+	int			r, g, b;
+	unsigned	*rgba;
+	extern	int			skytexturenum;
+
+	src = (byte *)mt + mt->offsets[0];
+
+	// make an average value for the back to avoid
+	// a fringe on the top level
+
+	r = g = b = 0;
+	for (i=0 ; i<128 ; i++)
+		for (j=0 ; j<128 ; j++)
+		{
+			p = src[i*256 + j + 128];
+			rgba = &d_8to24table[p];
+			trans[(i*128) + j] = *rgba;
+			r += ((byte *)rgba)[0];
+			g += ((byte *)rgba)[1];
+			b += ((byte *)rgba)[2];
+		}
+
+	((byte *)&transpix)[0] = r/(128*128);
+	((byte *)&transpix)[1] = g/(128*128);
+	((byte *)&transpix)[2] = b/(128*128);
+	((byte *)&transpix)[3] = 0;
+
+
+	if (!solidskytexture)
+		solidskytexture = texture_extension_number++;
+	GL_Bind (solidskytexture );
+	glTexImage2D (GL_TEXTURE_2D, 0, gl_solid_format, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, trans);
+	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+
+	for (i=0 ; i<128 ; i++)
+		for (j=0 ; j<128 ; j++)
+		{
+			p = src[i*256 + j];
+			if (p == 0)
+				trans[(i*128) + j] = transpix;
+			else
+				trans[(i*128) + j] = d_8to24table[p];
+		}
+
+	if (!alphaskytexture)
+		alphaskytexture = texture_extension_number++;
+	GL_Bind(alphaskytexture);
+	glTexImage2D (GL_TEXTURE_2D, 0, gl_alpha_format, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, trans);
+	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+}
+
--- /dev/null
+++ b/WinQuake/glquake2.h
@@ -1,0 +1,209 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+// disable data conversion warnings
+
+#pragma warning(disable : 4244)     // MIPS
+#pragma warning(disable : 4136)     // X86
+#pragma warning(disable : 4051)     // ALPHA
+  
+#include <windows.h>
+
+#include <gl\gl.h>
+#include <gl\glu.h>
+
+void GL_BeginRendering (int *x, int *y, int *width, int *height);
+void GL_EndRendering (void);
+
+
+// Function prototypes for the Texture Object Extension routines
+typedef GLboolean (APIENTRY *ARETEXRESFUNCPTR)(GLsizei, const GLuint *,
+                    const GLboolean *);
+typedef void (APIENTRY *BINDTEXFUNCPTR)(GLenum, GLuint);
+typedef void (APIENTRY *DELTEXFUNCPTR)(GLsizei, const GLuint *);
+typedef void (APIENTRY *GENTEXFUNCPTR)(GLsizei, GLuint *);
+typedef GLboolean (APIENTRY *ISTEXFUNCPTR)(GLuint);
+typedef void (APIENTRY *PRIORTEXFUNCPTR)(GLsizei, const GLuint *,
+                    const GLclampf *);
+typedef void (APIENTRY *TEXSUBIMAGEPTR)(int, int, int, int, int, int, int, int, void *);
+
+extern	BINDTEXFUNCPTR bindTexFunc;
+extern	DELTEXFUNCPTR delTexFunc;
+extern	TEXSUBIMAGEPTR TexSubImage2DFunc;
+
+extern	int texture_extension_number;
+extern	int		texture_mode;
+
+extern	float	gldepthmin, gldepthmax;
+
+void GL_Upload32 (unsigned *data, int width, int height,  qboolean mipmap, qboolean alpha, qboolean modulate);
+void GL_Upload8 (byte *data, int width, int height,  qboolean mipmap, qboolean alpha, qboolean modulate);
+int GL_LoadTexture (char *identifier, int width, int height, byte *data, int mipmap, int alpha, int modulate);
+int GL_FindTexture (char *identifier);
+
+typedef struct
+{
+	float	x, y, z;
+	float	s, t;
+	float	r, g, b;
+} glvert_t;
+
+extern glvert_t glv;
+
+extern	int glx, gly, glwidth, glheight;
+
+extern	PROC glArrayElementEXT;
+extern	PROC glColorPointerEXT;
+extern	PROC glTexturePointerEXT;
+extern	PROC glVertexPointerEXT;
+
+
+// r_local.h -- private refresh defs
+
+#define MAXALIASVERTS		2000	// TODO: tune this
+
+#define ALIAS_BASE_SIZE_RATIO		(1.0 / 11.0)
+					// normalizing factor so player model works out to about
+					//  1 pixel per triangle
+#define	MAX_LBM_HEIGHT		480
+
+#define TILE_SIZE		128		// size of textures generated by R_GenTiledSurf
+
+#define SKYSHIFT		7
+#define	SKYSIZE			(1 << SKYSHIFT)
+#define SKYMASK			(SKYSIZE - 1)
+
+#define BACKFACE_EPSILON	0.01
+
+
+void R_TimeRefresh_f (void);
+void R_ReadPointFile_f (void);
+texture_t *R_TextureAnimation (texture_t *base);
+
+typedef struct surfcache_s
+{
+	struct surfcache_s	*next;
+	struct surfcache_s 	**owner;		// NULL is an empty chunk of memory
+	int					lightadj[MAXLIGHTMAPS]; // checked for strobe flush
+	int					dlight;
+	int					size;		// including header
+	unsigned			width;
+	unsigned			height;		// DEBUG only needed for debug
+	float				mipscale;
+	struct texture_s	*texture;	// checked for animating textures
+	byte				data[4];	// width*height elements
+} surfcache_t;
+
+
+typedef struct
+{
+	pixel_t		*surfdat;	// destination for generated surface
+	int			rowbytes;	// destination logical width in bytes
+	msurface_t	*surf;		// description for surface to generate
+	fixed8_t	lightadj[MAXLIGHTMAPS];
+							// adjust for lightmap levels for dynamic lighting
+	texture_t	*texture;	// corrected for animating textures
+	int			surfmip;	// mipmapped ratio of surface texels / world pixels
+	int			surfwidth;	// in mipmapped texels
+	int			surfheight;	// in mipmapped texels
+} drawsurf_t;
+
+
+typedef enum {
+	pt_static, pt_grav, pt_slowgrav, pt_fire, pt_explode, pt_explode2, pt_blob, pt_blob2
+} ptype_t;
+
+// !!! if this is changed, it must be changed in d_ifacea.h too !!!
+typedef struct particle_s
+{
+// driver-usable fields
+	vec3_t		org;
+	float		color;
+// drivers never touch the following fields
+	struct particle_s	*next;
+	vec3_t		vel;
+	float		ramp;
+	float		die;
+	ptype_t		type;
+} particle_t;
+
+
+//====================================================
+
+
+extern	entity_t	r_worldentity;
+extern	qboolean	r_cache_thrash;		// compatability
+extern	vec3_t		modelorg, r_entorigin;
+extern	entity_t	*currententity;
+extern	int			r_visframecount;	// ??? what difs?
+extern	int			r_framecount;
+extern	mplane_t	frustum[4];
+extern	int		c_brush_polys, c_alias_polys;
+
+
+//
+// view origin
+//
+extern	vec3_t	vup;
+extern	vec3_t	vpn;
+extern	vec3_t	vright;
+extern	vec3_t	r_origin;
+
+//
+// screen size info
+//
+extern	refdef_t	r_refdef;
+extern	mleaf_t		*r_viewleaf, *r_oldviewleaf;
+extern	texture_t	*r_notexture_mip;
+extern	int		d_lightstylevalue[256];	// 8.8 fraction of base light value
+
+extern	qboolean	envmap;
+extern	int	currenttexture;
+extern	int	particletexture;
+extern	int	playertextures;
+
+extern	int	skytexturenum;		// index in cl.loadmodel, not gl texture object
+
+extern	cvar_t	r_drawentities;
+extern	cvar_t	r_drawworld;
+extern	cvar_t	r_drawviewmodel;
+extern	cvar_t	r_speeds;
+extern	cvar_t	r_waterwarp;
+extern	cvar_t	r_fullbright;
+extern	cvar_t	r_lightmap;
+extern	cvar_t	r_shadows;
+extern	cvar_t	r_dynamic;
+
+extern	cvar_t	gl_clear;
+extern	cvar_t	gl_cull;
+extern	cvar_t	gl_poly;
+extern	cvar_t	gl_texsort;
+extern	cvar_t	gl_smoothmodels;
+extern	cvar_t	gl_affinemodels;
+extern	cvar_t	gl_fogblend;
+extern	cvar_t	gl_polyblend;
+extern	cvar_t	gl_keeptjunctions;
+extern	cvar_t	gl_reporttjunctions;
+
+extern	int		gl_lightmap_format;
+extern	int		gl_solid_format;
+extern	int		gl_alpha_format;
+
+void R_TranslatePlayerSkin (int playernum);
+void GL_Bind (int texnum);
--- /dev/null
+++ b/WinQuake/host_cmd.c
@@ -1,0 +1,1925 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+
+#include "quakedef.h"
+
+extern cvar_t	pausable;
+
+int	current_skill;
+
+void Mod_Print (void);
+
+/*
+==================
+Host_Quit_f
+==================
+*/
+
+extern void M_Menu_Quit_f (void);
+
+void Host_Quit_f (void)
+{
+	if (key_dest != key_console && cls.state != ca_dedicated)
+	{
+		M_Menu_Quit_f ();
+		return;
+	}
+	CL_Disconnect ();
+	Host_ShutdownServer(false);		
+
+	Sys_Quit ();
+}
+
+
+/*
+==================
+Host_Status_f
+==================
+*/
+void Host_Status_f (void)
+{
+	client_t	*client;
+	int			seconds;
+	int			minutes;
+	int			hours = 0;
+	int			j;
+	void		(*print) (char *fmt, ...);
+	
+	if (cmd_source == src_command)
+	{
+		if (!sv.active)
+		{
+			Cmd_ForwardToServer ();
+			return;
+		}
+		print = Con_Printf;
+	}
+	else
+		print = SV_ClientPrintf;
+
+	print ("host:    %s\n", Cvar_VariableString ("hostname"));
+	print ("version: %4.2f\n", VERSION);
+	if (tcpipAvailable)
+		print ("tcp/ip:  %s\n", my_tcpip_address);
+	if (ipxAvailable)
+		print ("ipx:     %s\n", my_ipx_address);
+	print ("map:     %s\n", sv.name);
+	print ("players: %i active (%i max)\n\n", net_activeconnections, svs.maxclients);
+	for (j=0, client = svs.clients ; j<svs.maxclients ; j++, client++)
+	{
+		if (!client->active)
+			continue;
+		seconds = (int)(net_time - client->netconnection->connecttime);
+		minutes = seconds / 60;
+		if (minutes)
+		{
+			seconds -= (minutes * 60);
+			hours = minutes / 60;
+			if (hours)
+				minutes -= (hours * 60);
+		}
+		else
+			hours = 0;
+		print ("#%-2u %-16.16s  %3i  %2i:%02i:%02i\n", j+1, client->name, (int)client->edict->v.frags, hours, minutes, seconds);
+		print ("   %s\n", client->netconnection->address);
+	}
+}
+
+
+/*
+==================
+Host_God_f
+
+Sets client to godmode
+==================
+*/
+void Host_God_f (void)
+{
+	if (cmd_source == src_command)
+	{
+		Cmd_ForwardToServer ();
+		return;
+	}
+
+	if (pr_global_struct->deathmatch && !host_client->privileged)
+		return;
+
+	sv_player->v.flags = (int)sv_player->v.flags ^ FL_GODMODE;
+	if (!((int)sv_player->v.flags & FL_GODMODE) )
+		SV_ClientPrintf ("godmode OFF\n");
+	else
+		SV_ClientPrintf ("godmode ON\n");
+}
+
+void Host_Notarget_f (void)
+{
+	if (cmd_source == src_command)
+	{
+		Cmd_ForwardToServer ();
+		return;
+	}
+
+	if (pr_global_struct->deathmatch && !host_client->privileged)
+		return;
+
+	sv_player->v.flags = (int)sv_player->v.flags ^ FL_NOTARGET;
+	if (!((int)sv_player->v.flags & FL_NOTARGET) )
+		SV_ClientPrintf ("notarget OFF\n");
+	else
+		SV_ClientPrintf ("notarget ON\n");
+}
+
+qboolean noclip_anglehack;
+
+void Host_Noclip_f (void)
+{
+	if (cmd_source == src_command)
+	{
+		Cmd_ForwardToServer ();
+		return;
+	}
+
+	if (pr_global_struct->deathmatch && !host_client->privileged)
+		return;
+
+	if (sv_player->v.movetype != MOVETYPE_NOCLIP)
+	{
+		noclip_anglehack = true;
+		sv_player->v.movetype = MOVETYPE_NOCLIP;
+		SV_ClientPrintf ("noclip ON\n");
+	}
+	else
+	{
+		noclip_anglehack = false;
+		sv_player->v.movetype = MOVETYPE_WALK;
+		SV_ClientPrintf ("noclip OFF\n");
+	}
+}
+
+/*
+==================
+Host_Fly_f
+
+Sets client to flymode
+==================
+*/
+void Host_Fly_f (void)
+{
+	if (cmd_source == src_command)
+	{
+		Cmd_ForwardToServer ();
+		return;
+	}
+
+	if (pr_global_struct->deathmatch && !host_client->privileged)
+		return;
+
+	if (sv_player->v.movetype != MOVETYPE_FLY)
+	{
+		sv_player->v.movetype = MOVETYPE_FLY;
+		SV_ClientPrintf ("flymode ON\n");
+	}
+	else
+	{
+		sv_player->v.movetype = MOVETYPE_WALK;
+		SV_ClientPrintf ("flymode OFF\n");
+	}
+}
+
+
+/*
+==================
+Host_Ping_f
+
+==================
+*/
+void Host_Ping_f (void)
+{
+	int		i, j;
+	float	total;
+	client_t	*client;
+	
+	if (cmd_source == src_command)
+	{
+		Cmd_ForwardToServer ();
+		return;
+	}
+
+	SV_ClientPrintf ("Client ping times:\n");
+	for (i=0, client = svs.clients ; i<svs.maxclients ; i++, client++)
+	{
+		if (!client->active)
+			continue;
+		total = 0;
+		for (j=0 ; j<NUM_PING_TIMES ; j++)
+			total+=client->ping_times[j];
+		total /= NUM_PING_TIMES;
+		SV_ClientPrintf ("%4i %s\n", (int)(total*1000), client->name);
+	}
+}
+
+/*
+===============================================================================
+
+SERVER TRANSITIONS
+
+===============================================================================
+*/
+
+
+/*
+======================
+Host_Map_f
+
+handle a 
+map <servername>
+command from the console.  Active clients are kicked off.
+======================
+*/
+void Host_Map_f (void)
+{
+	int		i;
+	char	name[MAX_QPATH];
+
+	if (cmd_source != src_command)
+		return;
+
+	cls.demonum = -1;		// stop demo loop in case this fails
+
+	CL_Disconnect ();
+	Host_ShutdownServer(false);		
+
+	key_dest = key_game;			// remove console or menu
+	SCR_BeginLoadingPlaque ();
+
+	cls.mapstring[0] = 0;
+	for (i=0 ; i<Cmd_Argc() ; i++)
+	{
+		strcat (cls.mapstring, Cmd_Argv(i));
+		strcat (cls.mapstring, " ");
+	}
+	strcat (cls.mapstring, "\n");
+
+	svs.serverflags = 0;			// haven't completed an episode yet
+	strcpy (name, Cmd_Argv(1));
+#ifdef QUAKE2
+	SV_SpawnServer (name, NULL);
+#else
+	SV_SpawnServer (name);
+#endif
+	if (!sv.active)
+		return;
+	
+	if (cls.state != ca_dedicated)
+	{
+		strcpy (cls.spawnparms, "");
+
+		for (i=2 ; i<Cmd_Argc() ; i++)
+		{
+			strcat (cls.spawnparms, Cmd_Argv(i));
+			strcat (cls.spawnparms, " ");
+		}
+		
+		Cmd_ExecuteString ("connect local", src_command);
+	}	
+}
+
+/*
+==================
+Host_Changelevel_f
+
+Goes to a new map, taking all clients along
+==================
+*/
+void Host_Changelevel_f (void)
+{
+#ifdef QUAKE2
+	char	level[MAX_QPATH];
+	char	_startspot[MAX_QPATH];
+	char	*startspot;
+
+	if (Cmd_Argc() < 2)
+	{
+		Con_Printf ("changelevel <levelname> : continue game on a new level\n");
+		return;
+	}
+	if (!sv.active || cls.demoplayback)
+	{
+		Con_Printf ("Only the server may changelevel\n");
+		return;
+	}
+
+	strcpy (level, Cmd_Argv(1));
+	if (Cmd_Argc() == 2)
+		startspot = NULL;
+	else
+	{
+		strcpy (_startspot, Cmd_Argv(2));
+		startspot = _startspot;
+	}
+
+	SV_SaveSpawnparms ();
+	SV_SpawnServer (level, startspot);
+#else
+	char	level[MAX_QPATH];
+
+	if (Cmd_Argc() != 2)
+	{
+		Con_Printf ("changelevel <levelname> : continue game on a new level\n");
+		return;
+	}
+	if (!sv.active || cls.demoplayback)
+	{
+		Con_Printf ("Only the server may changelevel\n");
+		return;
+	}
+	SV_SaveSpawnparms ();
+	strcpy (level, Cmd_Argv(1));
+	SV_SpawnServer (level);
+#endif
+}
+
+/*
+==================
+Host_Restart_f
+
+Restarts the current server for a dead player
+==================
+*/
+void Host_Restart_f (void)
+{
+	char	mapname[MAX_QPATH];
+#ifdef QUAKE2
+	char	startspot[MAX_QPATH];
+#endif
+
+	if (cls.demoplayback || !sv.active)
+		return;
+
+	if (cmd_source != src_command)
+		return;
+	strcpy (mapname, sv.name);	// must copy out, because it gets cleared
+								// in sv_spawnserver
+#ifdef QUAKE2
+	strcpy(startspot, sv.startspot);
+	SV_SpawnServer (mapname, startspot);
+#else
+	SV_SpawnServer (mapname);
+#endif
+}
+
+/*
+==================
+Host_Reconnect_f
+
+This command causes the client to wait for the signon messages again.
+This is sent just before a server changes levels
+==================
+*/
+void Host_Reconnect_f (void)
+{
+	SCR_BeginLoadingPlaque ();
+	cls.signon = 0;		// need new connection messages
+}
+
+/*
+=====================
+Host_Connect_f
+
+User command to connect to server
+=====================
+*/
+void Host_Connect_f (void)
+{
+	char	name[MAX_QPATH];
+	
+	cls.demonum = -1;		// stop demo loop in case this fails
+	if (cls.demoplayback)
+	{
+		CL_StopPlayback ();
+		CL_Disconnect ();
+	}
+	strcpy (name, Cmd_Argv(1));
+	CL_EstablishConnection (name);
+	Host_Reconnect_f ();
+}
+
+
+/*
+===============================================================================
+
+LOAD / SAVE GAME
+
+===============================================================================
+*/
+
+#define	SAVEGAME_VERSION	5
+
+/*
+===============
+Host_SavegameComment
+
+Writes a SAVEGAME_COMMENT_LENGTH character comment describing the current 
+===============
+*/
+void Host_SavegameComment (char *text)
+{
+	int		i;
+	char	kills[20];
+
+	for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
+		text[i] = ' ';
+	memcpy (text, cl.levelname, strlen(cl.levelname));
+	sprintf (kills,"kills:%3i/%3i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]);
+	memcpy (text+22, kills, strlen(kills));
+// convert space to _ to make stdio happy
+	for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
+		if (text[i] == ' ')
+			text[i] = '_';
+	text[SAVEGAME_COMMENT_LENGTH] = '\0';
+}
+
+
+/*
+===============
+Host_Savegame_f
+===============
+*/
+void Host_Savegame_f (void)
+{
+	char	name[256];
+	FILE	*f;
+	int		i;
+	char	comment[SAVEGAME_COMMENT_LENGTH+1];
+
+	if (cmd_source != src_command)
+		return;
+
+	if (!sv.active)
+	{
+		Con_Printf ("Not playing a local game.\n");
+		return;
+	}
+
+	if (cl.intermission)
+	{
+		Con_Printf ("Can't save in intermission.\n");
+		return;
+	}
+
+	if (svs.maxclients != 1)
+	{
+		Con_Printf ("Can't save multiplayer games.\n");
+		return;
+	}
+
+	if (Cmd_Argc() != 2)
+	{
+		Con_Printf ("save <savename> : save a game\n");
+		return;
+	}
+
+	if (strstr(Cmd_Argv(1), ".."))
+	{
+		Con_Printf ("Relative pathnames are not allowed.\n");
+		return;
+	}
+		
+	for (i=0 ; i<svs.maxclients ; i++)
+	{
+		if (svs.clients[i].active && (svs.clients[i].edict->v.health <= 0) )
+		{
+			Con_Printf ("Can't savegame with a dead player\n");
+			return;
+		}
+	}
+
+	sprintf (name, "%s/%s", com_gamedir, Cmd_Argv(1));
+	COM_DefaultExtension (name, ".sav");
+	
+	Con_Printf ("Saving game to %s...\n", name);
+	f = fopen (name, "w");
+	if (!f)
+	{
+		Con_Printf ("ERROR: couldn't open.\n");
+		return;
+	}
+	
+	fprintf (f, "%i\n", SAVEGAME_VERSION);
+	Host_SavegameComment (comment);
+	fprintf (f, "%s\n", comment);
+	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+		fprintf (f, "%f\n", svs.clients->spawn_parms[i]);
+	fprintf (f, "%d\n", current_skill);
+	fprintf (f, "%s\n", sv.name);
+	fprintf (f, "%f\n",sv.time);
+
+// write the light styles
+
+	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
+	{
+		if (sv.lightstyles[i])
+			fprintf (f, "%s\n", sv.lightstyles[i]);
+		else
+			fprintf (f,"m\n");
+	}
+
+
+	ED_WriteGlobals (f);
+	for (i=0 ; i<sv.num_edicts ; i++)
+	{
+		ED_Write (f, EDICT_NUM(i));
+		fflush (f);
+	}
+	fclose (f);
+	Con_Printf ("done.\n");
+}
+
+
+/*
+===============
+Host_Loadgame_f
+===============
+*/
+void Host_Loadgame_f (void)
+{
+	char	name[MAX_OSPATH];
+	FILE	*f;
+	char	mapname[MAX_QPATH];
+	float	time, tfloat;
+	char	str[32768], *start;
+	int		i, r;
+	edict_t	*ent;
+	int		entnum;
+	int		version;
+	float			spawn_parms[NUM_SPAWN_PARMS];
+
+	if (cmd_source != src_command)
+		return;
+
+	if (Cmd_Argc() != 2)
+	{
+		Con_Printf ("load <savename> : load a game\n");
+		return;
+	}
+
+	cls.demonum = -1;		// stop demo loop in case this fails
+
+	sprintf (name, "%s/%s", com_gamedir, Cmd_Argv(1));
+	COM_DefaultExtension (name, ".sav");
+	
+// we can't call SCR_BeginLoadingPlaque, because too much stack space has
+// been used.  The menu calls it before stuffing loadgame command
+//	SCR_BeginLoadingPlaque ();
+
+	Con_Printf ("Loading game from %s...\n", name);
+	f = fopen (name, "r");
+	if (!f)
+	{
+		Con_Printf ("ERROR: couldn't open.\n");
+		return;
+	}
+
+	fscanf (f, "%i\n", &version);
+	if (version != SAVEGAME_VERSION)
+	{
+		fclose (f);
+		Con_Printf ("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
+		return;
+	}
+	fscanf (f, "%s\n", str);
+	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+		fscanf (f, "%f\n", &spawn_parms[i]);
+// this silliness is so we can load 1.06 save files, which have float skill values
+	fscanf (f, "%f\n", &tfloat);
+	current_skill = (int)(tfloat + 0.1);
+	Cvar_SetValue ("skill", (float)current_skill);
+
+#ifdef QUAKE2
+	Cvar_SetValue ("deathmatch", 0);
+	Cvar_SetValue ("coop", 0);
+	Cvar_SetValue ("teamplay", 0);
+#endif
+
+	fscanf (f, "%s\n",mapname);
+	fscanf (f, "%f\n",&time);
+
+	CL_Disconnect_f ();
+	
+#ifdef QUAKE2
+	SV_SpawnServer (mapname, NULL);
+#else
+	SV_SpawnServer (mapname);
+#endif
+	if (!sv.active)
+	{
+		Con_Printf ("Couldn't load map\n");
+		return;
+	}
+	sv.paused = true;		// pause until all clients connect
+	sv.loadgame = true;
+
+// load the light styles
+
+	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
+	{
+		fscanf (f, "%s\n", str);
+		sv.lightstyles[i] = Hunk_Alloc (strlen(str)+1);
+		strcpy (sv.lightstyles[i], str);
+	}
+
+// load the edicts out of the savegame file
+	entnum = -1;		// -1 is the globals
+	while (!feof(f))
+	{
+		for (i=0 ; i<sizeof(str)-1 ; i++)
+		{
+			r = fgetc (f);
+			if (r == EOF || !r)
+				break;
+			str[i] = r;
+			if (r == '}')
+			{
+				i++;
+				break;
+			}
+		}
+		if (i == sizeof(str)-1)
+			Sys_Error ("Loadgame buffer overflow");
+		str[i] = 0;
+		start = str;
+		start = COM_Parse(str);
+		if (!com_token[0])
+			break;		// end of file
+		if (strcmp(com_token,"{"))
+			Sys_Error ("First token isn't a brace");
+			
+		if (entnum == -1)
+		{	// parse the global vars
+			ED_ParseGlobals (start);
+		}
+		else
+		{	// parse an edict
+
+			ent = EDICT_NUM(entnum);
+			memset (&ent->v, 0, progs->entityfields * 4);
+			ent->free = false;
+			ED_ParseEdict (start, ent);
+	
+		// link it into the bsp tree
+			if (!ent->free)
+				SV_LinkEdict (ent, false);
+		}
+
+		entnum++;
+	}
+	
+	sv.num_edicts = entnum;
+	sv.time = time;
+
+	fclose (f);
+
+	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+		svs.clients->spawn_parms[i] = spawn_parms[i];
+
+	if (cls.state != ca_dedicated)
+	{
+		CL_EstablishConnection ("local");
+		Host_Reconnect_f ();
+	}
+}
+
+#ifdef QUAKE2
+void SaveGamestate()
+{
+	char	name[256];
+	FILE	*f;
+	int		i;
+	char	comment[SAVEGAME_COMMENT_LENGTH+1];
+	edict_t	*ent;
+
+	sprintf (name, "%s/%s.gip", com_gamedir, sv.name);
+	
+	Con_Printf ("Saving game to %s...\n", name);
+	f = fopen (name, "w");
+	if (!f)
+	{
+		Con_Printf ("ERROR: couldn't open.\n");
+		return;
+	}
+	
+	fprintf (f, "%i\n", SAVEGAME_VERSION);
+	Host_SavegameComment (comment);
+	fprintf (f, "%s\n", comment);
+//	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+//		fprintf (f, "%f\n", svs.clients->spawn_parms[i]);
+	fprintf (f, "%f\n", skill.value);
+	fprintf (f, "%s\n", sv.name);
+	fprintf (f, "%f\n", sv.time);
+
+// write the light styles
+
+	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
+	{
+		if (sv.lightstyles[i])
+			fprintf (f, "%s\n", sv.lightstyles[i]);
+		else
+			fprintf (f,"m\n");
+	}
+
+
+	for (i=svs.maxclients+1 ; i<sv.num_edicts ; i++)
+	{
+		ent = EDICT_NUM(i);
+		if ((int)ent->v.flags & FL_ARCHIVE_OVERRIDE)
+			continue;
+		fprintf (f, "%i\n",i);
+		ED_Write (f, ent);
+		fflush (f);
+	}
+	fclose (f);
+	Con_Printf ("done.\n");
+}
+
+int LoadGamestate(char *level, char *startspot)
+{
+	char	name[MAX_OSPATH];
+	FILE	*f;
+	char	mapname[MAX_QPATH];
+	float	time, sk;
+	char	str[32768], *start;
+	int		i, r;
+	edict_t	*ent;
+	int		entnum;
+	int		version;
+//	float	spawn_parms[NUM_SPAWN_PARMS];
+
+	sprintf (name, "%s/%s.gip", com_gamedir, level);
+	
+	Con_Printf ("Loading game from %s...\n", name);
+	f = fopen (name, "r");
+	if (!f)
+	{
+		Con_Printf ("ERROR: couldn't open.\n");
+		return -1;
+	}
+
+	fscanf (f, "%i\n", &version);
+	if (version != SAVEGAME_VERSION)
+	{
+		fclose (f);
+		Con_Printf ("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
+		return -1;
+	}
+	fscanf (f, "%s\n", str);
+//	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+//		fscanf (f, "%f\n", &spawn_parms[i]);
+	fscanf (f, "%f\n", &sk);
+	Cvar_SetValue ("skill", sk);
+
+	fscanf (f, "%s\n",mapname);
+	fscanf (f, "%f\n",&time);
+
+	SV_SpawnServer (mapname, startspot);
+
+	if (!sv.active)
+	{
+		Con_Printf ("Couldn't load map\n");
+		return -1;
+	}
+
+// load the light styles
+	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
+	{
+		fscanf (f, "%s\n", str);
+		sv.lightstyles[i] = Hunk_Alloc (strlen(str)+1);
+		strcpy (sv.lightstyles[i], str);
+	}
+
+// load the edicts out of the savegame file
+	while (!feof(f))
+	{
+		fscanf (f, "%i\n",&entnum);
+		for (i=0 ; i<sizeof(str)-1 ; i++)
+		{
+			r = fgetc (f);
+			if (r == EOF || !r)
+				break;
+			str[i] = r;
+			if (r == '}')
+			{
+				i++;
+				break;
+			}
+		}
+		if (i == sizeof(str)-1)
+			Sys_Error ("Loadgame buffer overflow");
+		str[i] = 0;
+		start = str;
+		start = COM_Parse(str);
+		if (!com_token[0])
+			break;		// end of file
+		if (strcmp(com_token,"{"))
+			Sys_Error ("First token isn't a brace");
+			
+		// parse an edict
+
+		ent = EDICT_NUM(entnum);
+		memset (&ent->v, 0, progs->entityfields * 4);
+		ent->free = false;
+		ED_ParseEdict (start, ent);
+	
+		// link it into the bsp tree
+		if (!ent->free)
+			SV_LinkEdict (ent, false);
+	}
+	
+//	sv.num_edicts = entnum;
+	sv.time = time;
+	fclose (f);
+
+//	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+//		svs.clients->spawn_parms[i] = spawn_parms[i];
+
+	return 0;
+}
+
+// changing levels within a unit
+void Host_Changelevel2_f (void)
+{
+	char	level[MAX_QPATH];
+	char	_startspot[MAX_QPATH];
+	char	*startspot;
+
+	if (Cmd_Argc() < 2)
+	{
+		Con_Printf ("changelevel2 <levelname> : continue game on a new level in the unit\n");
+		return;
+	}
+	if (!sv.active || cls.demoplayback)
+	{
+		Con_Printf ("Only the server may changelevel\n");
+		return;
+	}
+
+	strcpy (level, Cmd_Argv(1));
+	if (Cmd_Argc() == 2)
+		startspot = NULL;
+	else
+	{
+		strcpy (_startspot, Cmd_Argv(2));
+		startspot = _startspot;
+	}
+
+	SV_SaveSpawnparms ();
+
+	// save the current level's state
+	SaveGamestate ();
+
+	// try to restore the new level
+	if (LoadGamestate (level, startspot))
+		SV_SpawnServer (level, startspot);
+}
+#endif
+
+
+//============================================================================
+
+/*
+======================
+Host_Name_f
+======================
+*/
+void Host_Name_f (void)
+{
+	char	*newName;
+
+	if (Cmd_Argc () == 1)
+	{
+		Con_Printf ("\"name\" is \"%s\"\n", cl_name.string);
+		return;
+	}
+	if (Cmd_Argc () == 2)
+		newName = Cmd_Argv(1);	
+	else
+		newName = Cmd_Args();
+	newName[15] = 0;
+
+	if (cmd_source == src_command)
+	{
+		if (Q_strcmp(cl_name.string, newName) == 0)
+			return;
+		Cvar_Set ("_cl_name", newName);
+		if (cls.state == ca_connected)
+			Cmd_ForwardToServer ();
+		return;
+	}
+
+	if (host_client->name[0] && strcmp(host_client->name, "unconnected") )
+		if (Q_strcmp(host_client->name, newName) != 0)
+			Con_Printf ("%s renamed to %s\n", host_client->name, newName);
+	Q_strcpy (host_client->name, newName);
+	host_client->edict->v.netname = host_client->name - pr_strings;
+	
+// send notification to all clients
+	
+	MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
+	MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
+	MSG_WriteString (&sv.reliable_datagram, host_client->name);
+}
+
+	
+void Host_Version_f (void)
+{
+	Con_Printf ("Version %4.2f\n", VERSION);
+	Con_Printf ("Exe: "__TIME__" "__DATE__"\n");
+}
+
+#ifdef IDGODS
+void Host_Please_f (void)
+{
+	client_t *cl;
+	int			j;
+	
+	if (cmd_source != src_command)
+		return;
+
+	if ((Cmd_Argc () == 3) && Q_strcmp(Cmd_Argv(1), "#") == 0)
+	{
+		j = Q_atof(Cmd_Argv(2)) - 1;
+		if (j < 0 || j >= svs.maxclients)
+			return;
+		if (!svs.clients[j].active)
+			return;
+		cl = &svs.clients[j];
+		if (cl->privileged)
+		{
+			cl->privileged = false;
+			cl->edict->v.flags = (int)cl->edict->v.flags & ~(FL_GODMODE|FL_NOTARGET);
+			cl->edict->v.movetype = MOVETYPE_WALK;
+			noclip_anglehack = false;
+		}
+		else
+			cl->privileged = true;
+	}
+
+	if (Cmd_Argc () != 2)
+		return;
+
+	for (j=0, cl = svs.clients ; j<svs.maxclients ; j++, cl++)
+	{
+		if (!cl->active)
+			continue;
+		if (Q_strcasecmp(cl->name, Cmd_Argv(1)) == 0)
+		{
+			if (cl->privileged)
+			{
+				cl->privileged = false;
+				cl->edict->v.flags = (int)cl->edict->v.flags & ~(FL_GODMODE|FL_NOTARGET);
+				cl->edict->v.movetype = MOVETYPE_WALK;
+				noclip_anglehack = false;
+			}
+			else
+				cl->privileged = true;
+			break;
+		}
+	}
+}
+#endif
+
+
+void Host_Say(qboolean teamonly)
+{
+	client_t *client;
+	client_t *save;
+	int		j;
+	char	*p;
+	unsigned char	text[64];
+	qboolean	fromServer = false;
+
+	if (cmd_source == src_command)
+	{
+		if (cls.state == ca_dedicated)
+		{
+			fromServer = true;
+			teamonly = false;
+		}
+		else
+		{
+			Cmd_ForwardToServer ();
+			return;
+		}
+	}
+
+	if (Cmd_Argc () < 2)
+		return;
+
+	save = host_client;
+
+	p = Cmd_Args();
+// remove quotes if present
+	if (*p == '"')
+	{
+		p++;
+		p[Q_strlen(p)-1] = 0;
+	}
+
+// turn on color set 1
+	if (!fromServer)
+		sprintf (text, "%c%s: ", 1, save->name);
+	else
+		sprintf (text, "%c<%s> ", 1, hostname.string);
+
+	j = sizeof(text) - 2 - Q_strlen(text);  // -2 for /n and null terminator
+	if (Q_strlen(p) > j)
+		p[j] = 0;
+
+	strcat (text, p);
+	strcat (text, "\n");
+
+	for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++)
+	{
+		if (!client || !client->active || !client->spawned)
+			continue;
+		if (teamplay.value && teamonly && client->edict->v.team != save->edict->v.team)
+			continue;
+		host_client = client;
+		SV_ClientPrintf("%s", text);
+	}
+	host_client = save;
+
+	Sys_Printf("%s", &text[1]);
+}
+
+
+void Host_Say_f(void)
+{
+	Host_Say(false);
+}
+
+
+void Host_Say_Team_f(void)
+{
+	Host_Say(true);
+}
+
+
+void Host_Tell_f(void)
+{
+	client_t *client;
+	client_t *save;
+	int		j;
+	char	*p;
+	char	text[64];
+
+	if (cmd_source == src_command)
+	{
+		Cmd_ForwardToServer ();
+		return;
+	}
+
+	if (Cmd_Argc () < 3)
+		return;
+
+	Q_strcpy(text, host_client->name);
+	Q_strcat(text, ": ");
+
+	p = Cmd_Args();
+
+// remove quotes if present
+	if (*p == '"')
+	{
+		p++;
+		p[Q_strlen(p)-1] = 0;
+	}
+
+// check length & truncate if necessary
+	j = sizeof(text) - 2 - Q_strlen(text);  // -2 for /n and null terminator
+	if (Q_strlen(p) > j)
+		p[j] = 0;
+
+	strcat (text, p);
+	strcat (text, "\n");
+
+	save = host_client;
+	for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++)
+	{
+		if (!client->active || !client->spawned)
+			continue;
+		if (Q_strcasecmp(client->name, Cmd_Argv(1)))
+			continue;
+		host_client = client;
+		SV_ClientPrintf("%s", text);
+		break;
+	}
+	host_client = save;
+}
+
+
+/*
+==================
+Host_Color_f
+==================
+*/
+void Host_Color_f(void)
+{
+	int		top, bottom;
+	int		playercolor;
+	
+	if (Cmd_Argc() == 1)
+	{
+		Con_Printf ("\"color\" is \"%i %i\"\n", ((int)cl_color.value) >> 4, ((int)cl_color.value) & 0x0f);
+		Con_Printf ("color <0-13> [0-13]\n");
+		return;
+	}
+
+	if (Cmd_Argc() == 2)
+		top = bottom = atoi(Cmd_Argv(1));
+	else
+	{
+		top = atoi(Cmd_Argv(1));
+		bottom = atoi(Cmd_Argv(2));
+	}
+	
+	top &= 15;
+	if (top > 13)
+		top = 13;
+	bottom &= 15;
+	if (bottom > 13)
+		bottom = 13;
+	
+	playercolor = top*16 + bottom;
+
+	if (cmd_source == src_command)
+	{
+		Cvar_SetValue ("_cl_color", playercolor);
+		if (cls.state == ca_connected)
+			Cmd_ForwardToServer ();
+		return;
+	}
+
+	host_client->colors = playercolor;
+	host_client->edict->v.team = bottom + 1;
+
+// send notification to all clients
+	MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
+	MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
+	MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
+}
+
+/*
+==================
+Host_Kill_f
+==================
+*/
+void Host_Kill_f (void)
+{
+	if (cmd_source == src_command)
+	{
+		Cmd_ForwardToServer ();
+		return;
+	}
+
+	if (sv_player->v.health <= 0)
+	{
+		SV_ClientPrintf ("Can't suicide -- allready dead!\n");
+		return;
+	}
+	
+	pr_global_struct->time = sv.time;
+	pr_global_struct->self = EDICT_TO_PROG(sv_player);
+	PR_ExecuteProgram (pr_global_struct->ClientKill);
+}
+
+
+/*
+==================
+Host_Pause_f
+==================
+*/
+void Host_Pause_f (void)
+{
+	
+	if (cmd_source == src_command)
+	{
+		Cmd_ForwardToServer ();
+		return;
+	}
+	if (!pausable.value)
+		SV_ClientPrintf ("Pause not allowed.\n");
+	else
+	{
+		sv.paused ^= 1;
+
+		if (sv.paused)
+		{
+			SV_BroadcastPrintf ("%s paused the game\n", pr_strings + sv_player->v.netname);
+		}
+		else
+		{
+			SV_BroadcastPrintf ("%s unpaused the game\n",pr_strings + sv_player->v.netname);
+		}
+
+	// send notification to all clients
+		MSG_WriteByte (&sv.reliable_datagram, svc_setpause);
+		MSG_WriteByte (&sv.reliable_datagram, sv.paused);
+	}
+}
+
+//===========================================================================
+
+
+/*
+==================
+Host_PreSpawn_f
+==================
+*/
+void Host_PreSpawn_f (void)
+{
+	if (cmd_source == src_command)
+	{
+		Con_Printf ("prespawn is not valid from the console\n");
+		return;
+	}
+
+	if (host_client->spawned)
+	{
+		Con_Printf ("prespawn not valid -- allready spawned\n");
+		return;
+	}
+	
+	SZ_Write (&host_client->message, sv.signon.data, sv.signon.cursize);
+	MSG_WriteByte (&host_client->message, svc_signonnum);
+	MSG_WriteByte (&host_client->message, 2);
+	host_client->sendsignon = true;
+}
+
+/*
+==================
+Host_Spawn_f
+==================
+*/
+void Host_Spawn_f (void)
+{
+	int		i;
+	client_t	*client;
+	edict_t	*ent;
+
+	if (cmd_source == src_command)
+	{
+		Con_Printf ("spawn is not valid from the console\n");
+		return;
+	}
+
+	if (host_client->spawned)
+	{
+		Con_Printf ("Spawn not valid -- allready spawned\n");
+		return;
+	}
+
+// run the entrance script
+	if (sv.loadgame)
+	{	// loaded games are fully inited allready
+		// if this is the last client to be connected, unpause
+		sv.paused = false;
+	}
+	else
+	{
+		// set up the edict
+		ent = host_client->edict;
+
+		memset (&ent->v, 0, progs->entityfields * 4);
+		ent->v.colormap = NUM_FOR_EDICT(ent);
+		ent->v.team = (host_client->colors & 15) + 1;
+		ent->v.netname = host_client->name - pr_strings;
+
+		// copy spawn parms out of the client_t
+
+		for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
+			(&pr_global_struct->parm1)[i] = host_client->spawn_parms[i];
+
+		// call the spawn function
+
+		pr_global_struct->time = sv.time;
+		pr_global_struct->self = EDICT_TO_PROG(sv_player);
+		PR_ExecuteProgram (pr_global_struct->ClientConnect);
+
+		if ((Sys_FloatTime() - host_client->netconnection->connecttime) <= sv.time)
+			Sys_Printf ("%s entered the game\n", host_client->name);
+
+		PR_ExecuteProgram (pr_global_struct->PutClientInServer);	
+	}
+
+
+// send all current names, colors, and frag counts
+	SZ_Clear (&host_client->message);
+
+// send time of update
+	MSG_WriteByte (&host_client->message, svc_time);
+	MSG_WriteFloat (&host_client->message, sv.time);
+
+	for (i=0, client = svs.clients ; i<svs.maxclients ; i++, client++)
+	{
+		MSG_WriteByte (&host_client->message, svc_updatename);
+		MSG_WriteByte (&host_client->message, i);
+		MSG_WriteString (&host_client->message, client->name);
+		MSG_WriteByte (&host_client->message, svc_updatefrags);
+		MSG_WriteByte (&host_client->message, i);
+		MSG_WriteShort (&host_client->message, client->old_frags);
+		MSG_WriteByte (&host_client->message, svc_updatecolors);
+		MSG_WriteByte (&host_client->message, i);
+		MSG_WriteByte (&host_client->message, client->colors);
+	}
+	
+// send all current light styles
+	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
+	{
+		MSG_WriteByte (&host_client->message, svc_lightstyle);
+		MSG_WriteByte (&host_client->message, (char)i);
+		MSG_WriteString (&host_client->message, sv.lightstyles[i]);
+	}
+
+//
+// send some stats
+//
+	MSG_WriteByte (&host_client->message, svc_updatestat);
+	MSG_WriteByte (&host_client->message, STAT_TOTALSECRETS);
+	MSG_WriteLong (&host_client->message, pr_global_struct->total_secrets);
+
+	MSG_WriteByte (&host_client->message, svc_updatestat);
+	MSG_WriteByte (&host_client->message, STAT_TOTALMONSTERS);
+	MSG_WriteLong (&host_client->message, pr_global_struct->total_monsters);
+
+	MSG_WriteByte (&host_client->message, svc_updatestat);
+	MSG_WriteByte (&host_client->message, STAT_SECRETS);
+	MSG_WriteLong (&host_client->message, pr_global_struct->found_secrets);
+
+	MSG_WriteByte (&host_client->message, svc_updatestat);
+	MSG_WriteByte (&host_client->message, STAT_MONSTERS);
+	MSG_WriteLong (&host_client->message, pr_global_struct->killed_monsters);
+
+	
+//
+// send a fixangle
+// Never send a roll angle, because savegames can catch the server
+// in a state where it is expecting the client to correct the angle
+// and it won't happen if the game was just loaded, so you wind up
+// with a permanent head tilt
+	ent = EDICT_NUM( 1 + (host_client - svs.clients) );
+	MSG_WriteByte (&host_client->message, svc_setangle);
+	for (i=0 ; i < 2 ; i++)
+		MSG_WriteAngle (&host_client->message, ent->v.angles[i] );
+	MSG_WriteAngle (&host_client->message, 0 );
+
+	SV_WriteClientdataToMessage (sv_player, &host_client->message);
+
+	MSG_WriteByte (&host_client->message, svc_signonnum);
+	MSG_WriteByte (&host_client->message, 3);
+	host_client->sendsignon = true;
+}
+
+/*
+==================
+Host_Begin_f
+==================
+*/
+void Host_Begin_f (void)
+{
+	if (cmd_source == src_command)
+	{
+		Con_Printf ("begin is not valid from the console\n");
+		return;
+	}
+
+	host_client->spawned = true;
+}
+
+//===========================================================================
+
+
+/*
+==================
+Host_Kick_f
+
+Kicks a user off of the server
+==================
+*/
+void Host_Kick_f (void)
+{
+	char		*who;
+	char		*message = NULL;
+	client_t	*save;
+	int			i;
+	qboolean	byNumber = false;
+
+	if (cmd_source == src_command)
+	{
+		if (!sv.active)
+		{
+			Cmd_ForwardToServer ();
+			return;
+		}
+	}
+	else if (pr_global_struct->deathmatch && !host_client->privileged)
+		return;
+
+	save = host_client;
+
+	if (Cmd_Argc() > 2 && Q_strcmp(Cmd_Argv(1), "#") == 0)
+	{
+		i = Q_atof(Cmd_Argv(2)) - 1;
+		if (i < 0 || i >= svs.maxclients)
+			return;
+		if (!svs.clients[i].active)
+			return;
+		host_client = &svs.clients[i];
+		byNumber = true;
+	}
+	else
+	{
+		for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++)
+		{
+			if (!host_client->active)
+				continue;
+			if (Q_strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
+				break;
+		}
+	}
+
+	if (i < svs.maxclients)
+	{
+		if (cmd_source == src_command)
+			if (cls.state == ca_dedicated)
+				who = "Console";
+			else
+				who = cl_name.string;
+		else
+			who = save->name;
+
+		// can't kick yourself!
+		if (host_client == save)
+			return;
+
+		if (Cmd_Argc() > 2)
+		{
+			message = COM_Parse(Cmd_Args());
+			if (byNumber)
+			{
+				message++;							// skip the #
+				while (*message == ' ')				// skip white space
+					message++;
+				message += Q_strlen(Cmd_Argv(2));	// skip the number
+			}
+			while (*message && *message == ' ')
+				message++;
+		}
+		if (message)
+			SV_ClientPrintf ("Kicked by %s: %s\n", who, message);
+		else
+			SV_ClientPrintf ("Kicked by %s\n", who);
+		SV_DropClient (false);
+	}
+
+	host_client = save;
+}
+
+/*
+===============================================================================
+
+DEBUGGING TOOLS
+
+===============================================================================
+*/
+
+/*
+==================
+Host_Give_f
+==================
+*/
+void Host_Give_f (void)
+{
+	char	*t;
+	int		v, w;
+	eval_t	*val;
+
+	if (cmd_source == src_command)
+	{
+		Cmd_ForwardToServer ();
+		return;
+	}
+
+	if (pr_global_struct->deathmatch && !host_client->privileged)
+		return;
+
+	t = Cmd_Argv(1);
+	v = atoi (Cmd_Argv(2));
+	
+	switch (t[0])
+	{
+   case '0':
+   case '1':
+   case '2':
+   case '3':
+   case '4':
+   case '5':
+   case '6':
+   case '7':
+   case '8':
+   case '9':
+      // MED 01/04/97 added hipnotic give stuff
+      if (hipnotic)
+      {
+         if (t[0] == '6')
+         {
+            if (t[1] == 'a')
+               sv_player->v.items = (int)sv_player->v.items | HIT_PROXIMITY_GUN;
+            else
+               sv_player->v.items = (int)sv_player->v.items | IT_GRENADE_LAUNCHER;
+         }
+         else if (t[0] == '9')
+            sv_player->v.items = (int)sv_player->v.items | HIT_LASER_CANNON;
+         else if (t[0] == '0')
+            sv_player->v.items = (int)sv_player->v.items | HIT_MJOLNIR;
+         else if (t[0] >= '2')
+            sv_player->v.items = (int)sv_player->v.items | (IT_SHOTGUN << (t[0] - '2'));
+      }
+      else
+      {
+         if (t[0] >= '2')
+            sv_player->v.items = (int)sv_player->v.items | (IT_SHOTGUN << (t[0] - '2'));
+      }
+		break;
+	
+    case 's':
+		if (rogue)
+		{
+	        val = GetEdictFieldValue(sv_player, "ammo_shells1");
+		    if (val)
+			    val->_float = v;
+		}
+
+        sv_player->v.ammo_shells = v;
+        break;		
+    case 'n':
+		if (rogue)
+		{
+			val = GetEdictFieldValue(sv_player, "ammo_nails1");
+			if (val)
+			{
+				val->_float = v;
+				if (sv_player->v.weapon <= IT_LIGHTNING)
+					sv_player->v.ammo_nails = v;
+			}
+		}
+		else
+		{
+			sv_player->v.ammo_nails = v;
+		}
+        break;		
+    case 'l':
+		if (rogue)
+		{
+			val = GetEdictFieldValue(sv_player, "ammo_lava_nails");
+			if (val)
+			{
+				val->_float = v;
+				if (sv_player->v.weapon > IT_LIGHTNING)
+					sv_player->v.ammo_nails = v;
+			}
+		}
+        break;
+    case 'r':
+		if (rogue)
+		{
+			val = GetEdictFieldValue(sv_player, "ammo_rockets1");
+			if (val)
+			{
+				val->_float = v;
+				if (sv_player->v.weapon <= IT_LIGHTNING)
+					sv_player->v.ammo_rockets = v;
+			}
+		}
+		else
+		{
+			sv_player->v.ammo_rockets = v;
+		}
+        break;		
+    case 'm':
+		if (rogue)
+		{
+			val = GetEdictFieldValue(sv_player, "ammo_multi_rockets");
+			if (val)
+			{
+				val->_float = v;
+				if (sv_player->v.weapon > IT_LIGHTNING)
+					sv_player->v.ammo_rockets = v;
+			}
+		}
+        break;		
+    case 'h':
+        sv_player->v.health = v;
+        break;		
+    case 'c':
+		if (rogue)
+		{
+			val = GetEdictFieldValue(sv_player, "ammo_cells1");
+			if (val)
+			{
+				val->_float = v;
+				if (sv_player->v.weapon <= IT_LIGHTNING)
+					sv_player->v.ammo_cells = v;
+			}
+		}
+		else
+		{
+			sv_player->v.ammo_cells = v;
+		}
+        break;		
+    case 'p':
+		if (rogue)
+		{
+			val = GetEdictFieldValue(sv_player, "ammo_plasma");
+			if (val)
+			{
+				val->_float = v;
+				if (sv_player->v.weapon > IT_LIGHTNING)
+					sv_player->v.ammo_cells = v;
+			}
+		}
+        break;		
+    }
+}
+
+edict_t	*FindViewthing (void)
+{
+	int		i;
+	edict_t	*e;
+	
+	for (i=0 ; i<sv.num_edicts ; i++)
+	{
+		e = EDICT_NUM(i);
+		if ( !strcmp (pr_strings + e->v.classname, "viewthing") )
+			return e;
+	}
+	Con_Printf ("No viewthing on map\n");
+	return NULL;
+}
+
+/*
+==================
+Host_Viewmodel_f
+==================
+*/
+void Host_Viewmodel_f (void)
+{
+	edict_t	*e;
+	model_t	*m;
+
+	e = FindViewthing ();
+	if (!e)
+		return;
+
+	m = Mod_ForName (Cmd_Argv(1), false);
+	if (!m)
+	{
+		Con_Printf ("Can't load %s\n", Cmd_Argv(1));
+		return;
+	}
+	
+	e->v.frame = 0;
+	cl.model_precache[(int)e->v.modelindex] = m;
+}
+
+/*
+==================
+Host_Viewframe_f
+==================
+*/
+void Host_Viewframe_f (void)
+{
+	edict_t	*e;
+	int		f;
+	model_t	*m;
+
+	e = FindViewthing ();
+	if (!e)
+		return;
+	m = cl.model_precache[(int)e->v.modelindex];
+
+	f = atoi(Cmd_Argv(1));
+	if (f >= m->numframes)
+		f = m->numframes-1;
+
+	e->v.frame = f;		
+}
+
+
+void PrintFrameName (model_t *m, int frame)
+{
+	aliashdr_t 			*hdr;
+	maliasframedesc_t	*pframedesc;
+
+	hdr = (aliashdr_t *)Mod_Extradata (m);
+	if (!hdr)
+		return;
+	pframedesc = &hdr->frames[frame];
+	
+	Con_Printf ("frame %i: %s\n", frame, pframedesc->name);
+}
+
+/*
+==================
+Host_Viewnext_f
+==================
+*/
+void Host_Viewnext_f (void)
+{
+	edict_t	*e;
+	model_t	*m;
+	
+	e = FindViewthing ();
+	if (!e)
+		return;
+	m = cl.model_precache[(int)e->v.modelindex];
+
+	e->v.frame = e->v.frame + 1;
+	if (e->v.frame >= m->numframes)
+		e->v.frame = m->numframes - 1;
+
+	PrintFrameName (m, e->v.frame);		
+}
+
+/*
+==================
+Host_Viewprev_f
+==================
+*/
+void Host_Viewprev_f (void)
+{
+	edict_t	*e;
+	model_t	*m;
+
+	e = FindViewthing ();
+	if (!e)
+		return;
+
+	m = cl.model_precache[(int)e->v.modelindex];
+
+	e->v.frame = e->v.frame - 1;
+	if (e->v.frame < 0)
+		e->v.frame = 0;
+
+	PrintFrameName (m, e->v.frame);		
+}
+
+/*
+===============================================================================
+
+DEMO LOOP CONTROL
+
+===============================================================================
+*/
+
+
+/*
+==================
+Host_Startdemos_f
+==================
+*/
+void Host_Startdemos_f (void)
+{
+	int		i, c;
+
+	if (cls.state == ca_dedicated)
+	{
+		if (!sv.active)
+			Cbuf_AddText ("map start\n");
+		return;
+	}
+
+	c = Cmd_Argc() - 1;
+	if (c > MAX_DEMOS)
+	{
+		Con_Printf ("Max %i demos in demoloop\n", MAX_DEMOS);
+		c = MAX_DEMOS;
+	}
+	Con_Printf ("%i demo(s) in loop\n", c);
+
+	for (i=1 ; i<c+1 ; i++)
+		strncpy (cls.demos[i-1], Cmd_Argv(i), sizeof(cls.demos[0])-1);
+
+	if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
+	{
+		cls.demonum = 0;
+		CL_NextDemo ();
+	}
+	else
+		cls.demonum = -1;
+}
+
+
+/*
+==================
+Host_Demos_f
+
+Return to looping demos
+==================
+*/
+void Host_Demos_f (void)
+{
+	if (cls.state == ca_dedicated)
+		return;
+	if (cls.demonum == -1)
+		cls.demonum = 1;
+	CL_Disconnect_f ();
+	CL_NextDemo ();
+}
+
+/*
+==================
+Host_Stopdemo_f
+
+Return to looping demos
+==================
+*/
+void Host_Stopdemo_f (void)
+{
+	if (cls.state == ca_dedicated)
+		return;
+	if (!cls.demoplayback)
+		return;
+	CL_StopPlayback ();
+	CL_Disconnect ();
+}
+
+//=============================================================================
+
+/*
+==================
+Host_InitCommands
+==================
+*/
+void Host_InitCommands (void)
+{
+	Cmd_AddCommand ("status", Host_Status_f);
+	Cmd_AddCommand ("quit", Host_Quit_f);
+	Cmd_AddCommand ("god", Host_God_f);
+	Cmd_AddCommand ("notarget", Host_Notarget_f);
+	Cmd_AddCommand ("fly", Host_Fly_f);
+	Cmd_AddCommand ("map", Host_Map_f);
+	Cmd_AddCommand ("restart", Host_Restart_f);
+	Cmd_AddCommand ("changelevel", Host_Changelevel_f);
+#ifdef QUAKE2
+	Cmd_AddCommand ("changelevel2", Host_Changelevel2_f);
+#endif
+	Cmd_AddCommand ("connect", Host_Connect_f);
+	Cmd_AddCommand ("reconnect", Host_Reconnect_f);
+	Cmd_AddCommand ("name", Host_Name_f);
+	Cmd_AddCommand ("noclip", Host_Noclip_f);
+	Cmd_AddCommand ("version", Host_Version_f);
+#ifdef IDGODS
+	Cmd_AddCommand ("please", Host_Please_f);
+#endif
+	Cmd_AddCommand ("say", Host_Say_f);
+	Cmd_AddCommand ("say_team", Host_Say_Team_f);
+	Cmd_AddCommand ("tell", Host_Tell_f);
+	Cmd_AddCommand ("color", Host_Color_f);
+	Cmd_AddCommand ("kill", Host_Kill_f);
+	Cmd_AddCommand ("pause", Host_Pause_f);
+	Cmd_AddCommand ("spawn", Host_Spawn_f);
+	Cmd_AddCommand ("begin", Host_Begin_f);
+	Cmd_AddCommand ("prespawn", Host_PreSpawn_f);
+	Cmd_AddCommand ("kick", Host_Kick_f);
+	Cmd_AddCommand ("ping", Host_Ping_f);
+	Cmd_AddCommand ("load", Host_Loadgame_f);
+	Cmd_AddCommand ("save", Host_Savegame_f);
+	Cmd_AddCommand ("give", Host_Give_f);
+
+	Cmd_AddCommand ("startdemos", Host_Startdemos_f);
+	Cmd_AddCommand ("demos", Host_Demos_f);
+	Cmd_AddCommand ("stopdemo", Host_Stopdemo_f);
+
+	Cmd_AddCommand ("viewmodel", Host_Viewmodel_f);
+	Cmd_AddCommand ("viewframe", Host_Viewframe_f);
+	Cmd_AddCommand ("viewnext", Host_Viewnext_f);
+	Cmd_AddCommand ("viewprev", Host_Viewprev_f);
+
+	Cmd_AddCommand ("mcache", Mod_Print);
+}
--- /dev/null
+++ b/WinQuake/in_null.c
@@ -1,0 +1,39 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+// in_null.c -- for systems without a mouse
+
+#include "quakedef.h"
+
+void IN_Init (void)
+{
+}
+
+void IN_Shutdown (void)
+{
+}
+
+void IN_Commands (void)
+{
+}
+
+void IN_Move (usercmd_t *cmd)
+{
+}
+
--- /dev/null
+++ b/WinQuake/in_win.c
@@ -1,0 +1,1239 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+// in_win.c -- windows 95 mouse and joystick code
+// 02/21/97 JCB Added extended DirectInput code to support external controllers.
+
+#include <dinput.h>
+#include "quakedef.h"
+#include "winquake.h"
+#include "dosisms.h"
+
+#define DINPUT_BUFFERSIZE           16
+#define iDirectInputCreate(a,b,c,d)	pDirectInputCreate(a,b,c,d)
+
+HRESULT (WINAPI *pDirectInputCreate)(HINSTANCE hinst, DWORD dwVersion,
+	LPDIRECTINPUT * lplpDirectInput, LPUNKNOWN punkOuter);
+
+// mouse variables
+cvar_t	m_filter = {"m_filter","0"};
+
+int			mouse_buttons;
+int			mouse_oldbuttonstate;
+POINT		current_pos;
+int			mouse_x, mouse_y, old_mouse_x, old_mouse_y, mx_accum, my_accum;
+
+static qboolean	restore_spi;
+static int		originalmouseparms[3], newmouseparms[3] = {0, 0, 1};
+
+unsigned int uiWheelMessage;
+qboolean	mouseactive;
+qboolean		mouseinitialized;
+static qboolean	mouseparmsvalid, mouseactivatetoggle;
+static qboolean	mouseshowtoggle = 1;
+static qboolean	dinput_acquired;
+
+static unsigned int		mstate_di;
+
+// joystick defines and variables
+// where should defines be moved?
+#define JOY_ABSOLUTE_AXIS	0x00000000		// control like a joystick
+#define JOY_RELATIVE_AXIS	0x00000010		// control like a mouse, spinner, trackball
+#define	JOY_MAX_AXES		6				// X, Y, Z, R, U, V
+#define JOY_AXIS_X			0
+#define JOY_AXIS_Y			1
+#define JOY_AXIS_Z			2
+#define JOY_AXIS_R			3
+#define JOY_AXIS_U			4
+#define JOY_AXIS_V			5
+
+enum _ControlList
+{
+	AxisNada = 0, AxisForward, AxisLook, AxisSide, AxisTurn
+};
+
+DWORD	dwAxisFlags[JOY_MAX_AXES] =
+{
+	JOY_RETURNX, JOY_RETURNY, JOY_RETURNZ, JOY_RETURNR, JOY_RETURNU, JOY_RETURNV
+};
+
+DWORD	dwAxisMap[JOY_MAX_AXES];
+DWORD	dwControlMap[JOY_MAX_AXES];
+PDWORD	pdwRawValue[JOY_MAX_AXES];
+
+// none of these cvars are saved over a session
+// this means that advanced controller configuration needs to be executed
+// each time.  this avoids any problems with getting back to a default usage
+// or when changing from one controller to another.  this way at least something
+// works.
+cvar_t	in_joystick = {"joystick","0", true};
+cvar_t	joy_name = {"joyname", "joystick"};
+cvar_t	joy_advanced = {"joyadvanced", "0"};
+cvar_t	joy_advaxisx = {"joyadvaxisx", "0"};
+cvar_t	joy_advaxisy = {"joyadvaxisy", "0"};
+cvar_t	joy_advaxisz = {"joyadvaxisz", "0"};
+cvar_t	joy_advaxisr = {"joyadvaxisr", "0"};
+cvar_t	joy_advaxisu = {"joyadvaxisu", "0"};
+cvar_t	joy_advaxisv = {"joyadvaxisv", "0"};
+cvar_t	joy_forwardthreshold = {"joyforwardthreshold", "0.15"};
+cvar_t	joy_sidethreshold = {"joysidethreshold", "0.15"};
+cvar_t	joy_pitchthreshold = {"joypitchthreshold", "0.15"};
+cvar_t	joy_yawthreshold = {"joyyawthreshold", "0.15"};
+cvar_t	joy_forwardsensitivity = {"joyforwardsensitivity", "-1.0"};
+cvar_t	joy_sidesensitivity = {"joysidesensitivity", "-1.0"};
+cvar_t	joy_pitchsensitivity = {"joypitchsensitivity", "1.0"};
+cvar_t	joy_yawsensitivity = {"joyyawsensitivity", "-1.0"};
+cvar_t	joy_wwhack1 = {"joywwhack1", "0.0"};
+cvar_t	joy_wwhack2 = {"joywwhack2", "0.0"};
+
+qboolean	joy_avail, joy_advancedinit, joy_haspov;
+DWORD		joy_oldbuttonstate, joy_oldpovstate;
+
+int			joy_id;
+DWORD		joy_flags;
+DWORD		joy_numbuttons;
+
+static LPDIRECTINPUT		g_pdi;
+static LPDIRECTINPUTDEVICE	g_pMouse;
+
+static JOYINFOEX	ji;
+
+static HINSTANCE hInstDI;
+
+static qboolean	dinput;
+
+typedef struct MYDATA {
+	LONG  lX;                   // X axis goes here
+	LONG  lY;                   // Y axis goes here
+	LONG  lZ;                   // Z axis goes here
+	BYTE  bButtonA;             // One button goes here
+	BYTE  bButtonB;             // Another button goes here
+	BYTE  bButtonC;             // Another button goes here
+	BYTE  bButtonD;             // Another button goes here
+} MYDATA;
+
+static DIOBJECTDATAFORMAT rgodf[] = {
+  { &GUID_XAxis,    FIELD_OFFSET(MYDATA, lX),       DIDFT_AXIS | DIDFT_ANYINSTANCE,   0,},
+  { &GUID_YAxis,    FIELD_OFFSET(MYDATA, lY),       DIDFT_AXIS | DIDFT_ANYINSTANCE,   0,},
+  { &GUID_ZAxis,    FIELD_OFFSET(MYDATA, lZ),       0x80000000 | DIDFT_AXIS | DIDFT_ANYINSTANCE,   0,},
+  { 0,              FIELD_OFFSET(MYDATA, bButtonA), DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,},
+  { 0,              FIELD_OFFSET(MYDATA, bButtonB), DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,},
+  { 0,              FIELD_OFFSET(MYDATA, bButtonC), 0x80000000 | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,},
+  { 0,              FIELD_OFFSET(MYDATA, bButtonD), 0x80000000 | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,},
+};
+
+#define NUM_OBJECTS (sizeof(rgodf) / sizeof(rgodf[0]))
+
+static DIDATAFORMAT	df = {
+	sizeof(DIDATAFORMAT),       // this structure
+	sizeof(DIOBJECTDATAFORMAT), // size of object data format
+	DIDF_RELAXIS,               // absolute axis coordinates
+	sizeof(MYDATA),             // device data size
+	NUM_OBJECTS,                // number of objects
+	rgodf,                      // and here they are
+};
+
+// forward-referenced functions
+void IN_StartupJoystick (void);
+void Joy_AdvancedUpdate_f (void);
+void IN_JoyMove (usercmd_t *cmd);
+
+
+/*
+===========
+Force_CenterView_f
+===========
+*/
+void Force_CenterView_f (void)
+{
+	cl.viewangles[PITCH] = 0;
+}
+
+
+/*
+===========
+IN_UpdateClipCursor
+===========
+*/
+void IN_UpdateClipCursor (void)
+{
+
+	if (mouseinitialized && mouseactive && !dinput)
+	{
+		ClipCursor (&window_rect);
+	}
+}
+
+
+/*
+===========
+IN_ShowMouse
+===========
+*/
+void IN_ShowMouse (void)
+{
+
+	if (!mouseshowtoggle)
+	{
+		ShowCursor (TRUE);
+		mouseshowtoggle = 1;
+	}
+}
+
+
+/*
+===========
+IN_HideMouse
+===========
+*/
+void IN_HideMouse (void)
+{
+
+	if (mouseshowtoggle)
+	{
+		ShowCursor (FALSE);
+		mouseshowtoggle = 0;
+	}
+}
+
+
+/*
+===========
+IN_ActivateMouse
+===========
+*/
+void IN_ActivateMouse (void)
+{
+
+	mouseactivatetoggle = true;
+
+	if (mouseinitialized)
+	{
+		if (dinput)
+		{
+			if (g_pMouse)
+			{
+				if (!dinput_acquired)
+				{
+					IDirectInputDevice_Acquire(g_pMouse);
+					dinput_acquired = true;
+				}
+			}
+			else
+			{
+				return;
+			}
+		}
+		else
+		{
+			if (mouseparmsvalid)
+				restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0);
+
+			SetCursorPos (window_center_x, window_center_y);
+			SetCapture (mainwindow);
+			ClipCursor (&window_rect);
+		}
+
+		mouseactive = true;
+	}
+}
+
+
+/*
+===========
+IN_SetQuakeMouseState
+===========
+*/
+void IN_SetQuakeMouseState (void)
+{
+	if (mouseactivatetoggle)
+		IN_ActivateMouse ();
+}
+
+
+/*
+===========
+IN_DeactivateMouse
+===========
+*/
+void IN_DeactivateMouse (void)
+{
+
+	mouseactivatetoggle = false;
+
+	if (mouseinitialized)
+	{
+		if (dinput)
+		{
+			if (g_pMouse)
+			{
+				if (dinput_acquired)
+				{
+					IDirectInputDevice_Unacquire(g_pMouse);
+					dinput_acquired = false;
+				}
+			}
+		}
+		else
+		{
+			if (restore_spi)
+				SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0);
+
+			ClipCursor (NULL);
+			ReleaseCapture ();
+		}
+
+		mouseactive = false;
+	}
+}
+
+
+/*
+===========
+IN_RestoreOriginalMouseState
+===========
+*/
+void IN_RestoreOriginalMouseState (void)
+{
+	if (mouseactivatetoggle)
+	{
+		IN_DeactivateMouse ();
+		mouseactivatetoggle = true;
+	}
+
+// try to redraw the cursor so it gets reinitialized, because sometimes it
+// has garbage after the mode switch
+	ShowCursor (TRUE);
+	ShowCursor (FALSE);
+}
+
+
+/*
+===========
+IN_InitDInput
+===========
+*/
+qboolean IN_InitDInput (void)
+{
+    HRESULT		hr;
+	DIPROPDWORD	dipdw = {
+		{
+			sizeof(DIPROPDWORD),        // diph.dwSize
+			sizeof(DIPROPHEADER),       // diph.dwHeaderSize
+			0,                          // diph.dwObj
+			DIPH_DEVICE,                // diph.dwHow
+		},
+		DINPUT_BUFFERSIZE,              // dwData
+	};
+
+	if (!hInstDI)
+	{
+		hInstDI = LoadLibrary("dinput.dll");
+		
+		if (hInstDI == NULL)
+		{
+			Con_SafePrintf ("Couldn't load dinput.dll\n");
+			return false;
+		}
+	}
+
+	if (!pDirectInputCreate)
+	{
+		pDirectInputCreate = (void *)GetProcAddress(hInstDI,"DirectInputCreateA");
+
+		if (!pDirectInputCreate)
+		{
+			Con_SafePrintf ("Couldn't get DI proc addr\n");
+			return false;
+		}
+	}
+
+// register with DirectInput and get an IDirectInput to play with.
+	hr = iDirectInputCreate(global_hInstance, DIRECTINPUT_VERSION, &g_pdi, NULL);
+
+	if (FAILED(hr))
+	{
+		return false;
+	}
+
+// obtain an interface to the system mouse device.
+	hr = IDirectInput_CreateDevice(g_pdi, &GUID_SysMouse, &g_pMouse, NULL);
+
+	if (FAILED(hr))
+	{
+		Con_SafePrintf ("Couldn't open DI mouse device\n");
+		return false;
+	}
+
+// set the data format to "mouse format".
+	hr = IDirectInputDevice_SetDataFormat(g_pMouse, &df);
+
+	if (FAILED(hr))
+	{
+		Con_SafePrintf ("Couldn't set DI mouse format\n");
+		return false;
+	}
+
+// set the cooperativity level.
+	hr = IDirectInputDevice_SetCooperativeLevel(g_pMouse, mainwindow,
+			DISCL_EXCLUSIVE | DISCL_FOREGROUND);
+
+	if (FAILED(hr))
+	{
+		Con_SafePrintf ("Couldn't set DI coop level\n");
+		return false;
+	}
+
+
+// set the buffer size to DINPUT_BUFFERSIZE elements.
+// the buffer size is a DWORD property associated with the device
+	hr = IDirectInputDevice_SetProperty(g_pMouse, DIPROP_BUFFERSIZE, &dipdw.diph);
+
+	if (FAILED(hr))
+	{
+		Con_SafePrintf ("Couldn't set DI buffersize\n");
+		return false;
+	}
+
+	return true;
+}
+
+
+/*
+===========
+IN_StartupMouse
+===========
+*/
+void IN_StartupMouse (void)
+{
+	HDC			hdc;
+
+	if ( COM_CheckParm ("-nomouse") ) 
+		return; 
+
+	mouseinitialized = true;
+
+	if (COM_CheckParm ("-dinput"))
+	{
+		dinput = IN_InitDInput ();
+
+		if (dinput)
+		{
+			Con_SafePrintf ("DirectInput initialized\n");
+		}
+		else
+		{
+			Con_SafePrintf ("DirectInput not initialized\n");
+		}
+	}
+
+	if (!dinput)
+	{
+		mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0);
+
+		if (mouseparmsvalid)
+		{
+			if ( COM_CheckParm ("-noforcemspd") ) 
+				newmouseparms[2] = originalmouseparms[2];
+
+			if ( COM_CheckParm ("-noforcemaccel") ) 
+			{
+				newmouseparms[0] = originalmouseparms[0];
+				newmouseparms[1] = originalmouseparms[1];
+			}
+
+			if ( COM_CheckParm ("-noforcemparms") ) 
+			{
+				newmouseparms[0] = originalmouseparms[0];
+				newmouseparms[1] = originalmouseparms[1];
+				newmouseparms[2] = originalmouseparms[2];
+			}
+		}
+	}
+
+	mouse_buttons = 3;
+
+// if a fullscreen video mode was set before the mouse was initialized,
+// set the mouse state appropriately
+	if (mouseactivatetoggle)
+		IN_ActivateMouse ();
+}
+
+
+/*
+===========
+IN_Init
+===========
+*/
+void IN_Init (void)
+{
+	// mouse variables
+	Cvar_RegisterVariable (&m_filter);
+
+	// joystick variables
+	Cvar_RegisterVariable (&in_joystick);
+	Cvar_RegisterVariable (&joy_name);
+	Cvar_RegisterVariable (&joy_advanced);
+	Cvar_RegisterVariable (&joy_advaxisx);
+	Cvar_RegisterVariable (&joy_advaxisy);
+	Cvar_RegisterVariable (&joy_advaxisz);
+	Cvar_RegisterVariable (&joy_advaxisr);
+	Cvar_RegisterVariable (&joy_advaxisu);
+	Cvar_RegisterVariable (&joy_advaxisv);
+	Cvar_RegisterVariable (&joy_forwardthreshold);
+	Cvar_RegisterVariable (&joy_sidethreshold);
+	Cvar_RegisterVariable (&joy_pitchthreshold);
+	Cvar_RegisterVariable (&joy_yawthreshold);
+	Cvar_RegisterVariable (&joy_forwardsensitivity);
+	Cvar_RegisterVariable (&joy_sidesensitivity);
+	Cvar_RegisterVariable (&joy_pitchsensitivity);
+	Cvar_RegisterVariable (&joy_yawsensitivity);
+	Cvar_RegisterVariable (&joy_wwhack1);
+	Cvar_RegisterVariable (&joy_wwhack2);
+
+	Cmd_AddCommand ("force_centerview", Force_CenterView_f);
+	Cmd_AddCommand ("joyadvancedupdate", Joy_AdvancedUpdate_f);
+
+	uiWheelMessage = RegisterWindowMessage ( "MSWHEEL_ROLLMSG" );
+
+	IN_StartupMouse ();
+	IN_StartupJoystick ();
+}
+
+/*
+===========
+IN_Shutdown
+===========
+*/
+void IN_Shutdown (void)
+{
+
+	IN_DeactivateMouse ();
+	IN_ShowMouse ();
+
+    if (g_pMouse)
+	{
+		IDirectInputDevice_Release(g_pMouse);
+		g_pMouse = NULL;
+	}
+
+    if (g_pdi)
+	{
+		IDirectInput_Release(g_pdi);
+		g_pdi = NULL;
+	}
+}
+
+
+/*
+===========
+IN_MouseEvent
+===========
+*/
+void IN_MouseEvent (int mstate)
+{
+	int	i;
+
+	if (mouseactive && !dinput)
+	{
+	// perform button actions
+		for (i=0 ; i<mouse_buttons ; i++)
+		{
+			if ( (mstate & (1<<i)) &&
+				!(mouse_oldbuttonstate & (1<<i)) )
+			{
+				Key_Event (K_MOUSE1 + i, true);
+			}
+
+			if ( !(mstate & (1<<i)) &&
+				(mouse_oldbuttonstate & (1<<i)) )
+			{
+				Key_Event (K_MOUSE1 + i, false);
+			}
+		}	
+			
+		mouse_oldbuttonstate = mstate;
+	}
+}
+
+
+/*
+===========
+IN_MouseMove
+===========
+*/
+void IN_MouseMove (usercmd_t *cmd)
+{
+	int					mx, my;
+	HDC					hdc;
+	int					i;
+	DIDEVICEOBJECTDATA	od;
+	DWORD				dwElements;
+	HRESULT				hr;
+
+	if (!mouseactive)
+		return;
+
+	if (dinput)
+	{
+		mx = 0;
+		my = 0;
+
+		for (;;)
+		{
+			dwElements = 1;
+
+			hr = IDirectInputDevice_GetDeviceData(g_pMouse,
+					sizeof(DIDEVICEOBJECTDATA), &od, &dwElements, 0);
+
+			if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
+			{
+				dinput_acquired = true;
+				IDirectInputDevice_Acquire(g_pMouse);
+				break;
+			}
+
+			/* Unable to read data or no data available */
+			if (FAILED(hr) || dwElements == 0)
+			{
+				break;
+			}
+
+			/* Look at the element to see what happened */
+
+			switch (od.dwOfs)
+			{
+				case DIMOFS_X:
+					mx += od.dwData;
+					break;
+
+				case DIMOFS_Y:
+					my += od.dwData;
+					break;
+
+				case DIMOFS_BUTTON0:
+					if (od.dwData & 0x80)
+						mstate_di |= 1;
+					else
+						mstate_di &= ~1;
+					break;
+
+				case DIMOFS_BUTTON1:
+					if (od.dwData & 0x80)
+						mstate_di |= (1<<1);
+					else
+						mstate_di &= ~(1<<1);
+					break;
+					
+				case DIMOFS_BUTTON2:
+					if (od.dwData & 0x80)
+						mstate_di |= (1<<2);
+					else
+						mstate_di &= ~(1<<2);
+					break;
+			}
+		}
+
+	// perform button actions
+		for (i=0 ; i<mouse_buttons ; i++)
+		{
+			if ( (mstate_di & (1<<i)) &&
+				!(mouse_oldbuttonstate & (1<<i)) )
+			{
+				Key_Event (K_MOUSE1 + i, true);
+			}
+
+			if ( !(mstate_di & (1<<i)) &&
+				(mouse_oldbuttonstate & (1<<i)) )
+			{
+				Key_Event (K_MOUSE1 + i, false);
+			}
+		}	
+			
+		mouse_oldbuttonstate = mstate_di;
+	}
+	else
+	{
+		GetCursorPos (&current_pos);
+		mx = current_pos.x - window_center_x + mx_accum;
+		my = current_pos.y - window_center_y + my_accum;
+		mx_accum = 0;
+		my_accum = 0;
+	}
+
+//if (mx ||  my)
+//	Con_DPrintf("mx=%d, my=%d\n", mx, my);
+
+	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;
+
+	mouse_x *= sensitivity.value;
+	mouse_y *= sensitivity.value;
+
+// add mouse X/Y movement to cmd
+	if ( (in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1) ))
+		cmd->sidemove += m_side.value * mouse_x;
+	else
+		cl.viewangles[YAW] -= m_yaw.value * mouse_x;
+
+	if (in_mlook.state & 1)
+		V_StopPitchDrift ();
+		
+	if ( (in_mlook.state & 1) && !(in_strafe.state & 1))
+	{
+		cl.viewangles[PITCH] += m_pitch.value * mouse_y;
+		if (cl.viewangles[PITCH] > 80)
+			cl.viewangles[PITCH] = 80;
+		if (cl.viewangles[PITCH] < -70)
+			cl.viewangles[PITCH] = -70;
+	}
+	else
+	{
+		if ((in_strafe.state & 1) && noclip_anglehack)
+			cmd->upmove -= m_forward.value * mouse_y;
+		else
+			cmd->forwardmove -= m_forward.value * mouse_y;
+	}
+
+// if the mouse has moved, force it to the center, so there's room to move
+	if (mx || my)
+	{
+		SetCursorPos (window_center_x, window_center_y);
+	}
+}
+
+
+/*
+===========
+IN_Move
+===========
+*/
+void IN_Move (usercmd_t *cmd)
+{
+
+	if (ActiveApp && !Minimized)
+	{
+		IN_MouseMove (cmd);
+		IN_JoyMove (cmd);
+	}
+}
+
+
+/*
+===========
+IN_Accumulate
+===========
+*/
+void IN_Accumulate (void)
+{
+	int		mx, my;
+	HDC	hdc;
+
+	if (mouseactive)
+	{
+		if (!dinput)
+		{
+			GetCursorPos (&current_pos);
+
+			mx_accum += current_pos.x - window_center_x;
+			my_accum += current_pos.y - window_center_y;
+
+		// force the mouse to the center, so there's room to move
+			SetCursorPos (window_center_x, window_center_y);
+		}
+	}
+}
+
+
+/*
+===================
+IN_ClearStates
+===================
+*/
+void IN_ClearStates (void)
+{
+
+	if (mouseactive)
+	{
+		mx_accum = 0;
+		my_accum = 0;
+		mouse_oldbuttonstate = 0;
+	}
+}
+
+
+/* 
+=============== 
+IN_StartupJoystick 
+=============== 
+*/  
+void IN_StartupJoystick (void) 
+{ 
+	int			i, numdevs;
+	JOYCAPS		jc;
+	MMRESULT	mmr;
+ 
+ 	// assume no joystick
+	joy_avail = false; 
+
+	// abort startup if user requests no joystick
+	if ( COM_CheckParm ("-nojoy") ) 
+		return; 
+ 
+	// verify joystick driver is present
+	if ((numdevs = joyGetNumDevs ()) == 0)
+	{
+		Con_Printf ("\njoystick 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)
+	{
+		Con_Printf ("\njoystick 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)
+	{
+		Con_Printf ("\njoystick 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;
+
+	Con_Printf ("\njoystick 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;
+	case JOY_AXIS_V:
+		return &ji.dwVpos;
+	}
+}
+
+
+/*
+===========
+Joy_AdvancedUpdate_f
+===========
+*/
+void Joy_AdvancedUpdate_f (void)
+{
+
+	// 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 (Q_strcmp (joy_name.string, "joystick") != 0)
+		{
+			// notify user of advanced controller
+			Con_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 (void)
+{
+	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);
+		}
+
+		if ( !(buttonstate & (1<<i)) && (joy_oldbuttonstate & (1<<i)) )
+		{
+			key_index = (i < 4) ? K_JOY1 : K_AUX1;
+			Key_Event (key_index + i, false);
+		}
+	}
+	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);
+			}
+
+			if ( !(povstate & (1<<i)) && (joy_oldpovstate & (1<<i)) )
+			{
+				Key_Event (K_AUX29 + i, false);
+			}
+		}
+		joy_oldpovstate = povstate;
+	}
+}
+
+
+/* 
+=============== 
+IN_ReadJoystick
+=============== 
+*/  
+qboolean IN_ReadJoystick (void)
+{
+
+	memset (&ji, 0, sizeof(ji));
+	ji.dwSize = sizeof(ji);
+	ji.dwFlags = joy_flags;
+
+	if (joyGetPosEx (joy_id, &ji) == JOYERR_NOERROR)
+	{
+		// this is a hack -- there is a bug in the Logitech WingMan Warrior DirectInput Driver
+		// rather than having 32768 be the zero point, they have the zero point at 32668
+		// go figure -- anyway, now we get the full resolution out of the device
+		if (joy_wwhack1.value != 0.0)
+		{
+			ji.dwUpos += 100;
+		}
+		return true;
+	}
+	else
+	{
+		// read error occurred
+		// turning off the joystick seems too harsh for 1 read error,\
+		// but what should be done?
+		// Con_Printf ("IN_ReadJoystick: no response\n");
+		// joy_avail = false;
+		return false;
+	}
+}
+
+
+/*
+===========
+IN_JoyMove
+===========
+*/
+void IN_JoyMove (usercmd_t *cmd)
+{
+	float	speed, aspeed;
+	float	fAxisValue, fTemp;
+	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)
+		speed = cl_movespeedkey.value;
+	else
+		speed = 1;
+	aspeed = speed * host_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;
+
+		if (joy_wwhack2.value != 0.0)
+		{
+			if (dwAxisMap[i] == AxisTurn)
+			{
+				// this is a special formula for the Logitech WingMan Warrior
+				// y=ax^b; where a = 300 and b = 1.3
+				// also x values are in increments of 800 (so this is factored out)
+				// then bounds check result to level out excessively high spin rates
+				fTemp = 300.0 * pow(abs(fAxisValue) / 800.0, 1.3);
+				if (fTemp > 14000.0)
+					fTemp = 14000.0;
+				// restore direction information
+				fAxisValue = (fAxisValue > 0.0) ? fTemp : -fTemp;
+			}
+		}
+
+		// convert range from -32768..32767 to -1..1 
+		fAxisValue /= 32768.0;
+
+		switch (dwAxisMap[i])
+		{
+		case AxisForward:
+			if ((joy_advanced.value == 0.0) && (in_mlook.state & 1))
+			{
+				// 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;
+					}
+					V_StopPitchDrift();
+				}
+				else
+				{
+					// no pitch movement
+					// disable pitch return-to-center unless requested by user
+					// *** this code can be removed when the lookspring bug is fixed
+					// *** the bug always has the lookspring feature on
+					if(lookspring.value == 0.0)
+						V_StopPitchDrift();
+				}
+			}
+			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 AxisTurn:
+			if ((in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1)))
+			{
+				// 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 (in_mlook.state & 1)
+			{
+				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;
+					}
+					V_StopPitchDrift();
+				}
+				else
+				{
+					// no pitch movement
+					// disable pitch return-to-center unless requested by user
+					// *** this code can be removed when the lookspring bug is fixed
+					// *** the bug always has the lookspring feature on
+					if(lookspring.value == 0.0)
+						V_StopPitchDrift();
+				}
+			}
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	// bounds check pitch
+	if (cl.viewangles[PITCH] > 80.0)
+		cl.viewangles[PITCH] = 80.0;
+	if (cl.viewangles[PITCH] < -70.0)
+		cl.viewangles[PITCH] = -70.0;
+}
--- /dev/null
+++ b/WinQuake/keys.c
@@ -1,0 +1,759 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+#include "quakedef.h"
+
+/*
+
+key up events are sent even if in console mode
+
+*/
+
+
+#define		MAXCMDLINE	256
+char	key_lines[32][MAXCMDLINE];
+int		key_linepos;
+int		shift_down=false;
+int		key_lastpress;
+
+int		edit_line=0;
+int		history_line=0;
+
+keydest_t	key_dest;
+
+int		key_count;			// incremented every key event
+
+char	*keybindings[256];
+qboolean	consolekeys[256];	// if true, can't be rebound while in console
+qboolean	menubound[256];	// if true, can't be rebound while in menu
+int		keyshift[256];		// key to map to if shift held down in console
+int		key_repeats[256];	// if > 1, it is autorepeating
+qboolean	keydown[256];
+
+typedef struct
+{
+	char	*name;
+	int		keynum;
+} keyname_t;
+
+keyname_t keynames[] =
+{
+	{"TAB", K_TAB},
+	{"ENTER", K_ENTER},
+	{"ESCAPE", K_ESCAPE},
+	{"SPACE", K_SPACE},
+	{"BACKSPACE", K_BACKSPACE},
+	{"UPARROW", K_UPARROW},
+	{"DOWNARROW", K_DOWNARROW},
+	{"LEFTARROW", K_LEFTARROW},
+	{"RIGHTARROW", K_RIGHTARROW},
+
+	{"ALT", K_ALT},
+	{"CTRL", K_CTRL},
+	{"SHIFT", K_SHIFT},
+	
+	{"F1", K_F1},
+	{"F2", K_F2},
+	{"F3", K_F3},
+	{"F4", K_F4},
+	{"F5", K_F5},
+	{"F6", K_F6},
+	{"F7", K_F7},
+	{"F8", K_F8},
+	{"F9", K_F9},
+	{"F10", K_F10},
+	{"F11", K_F11},
+	{"F12", K_F12},
+
+	{"INS", K_INS},
+	{"DEL", K_DEL},
+	{"PGDN", K_PGDN},
+	{"PGUP", K_PGUP},
+	{"HOME", K_HOME},
+	{"END", K_END},
+
+	{"MOUSE1", K_MOUSE1},
+	{"MOUSE2", K_MOUSE2},
+	{"MOUSE3", K_MOUSE3},
+
+	{"JOY1", K_JOY1},
+	{"JOY2", K_JOY2},
+	{"JOY3", K_JOY3},
+	{"JOY4", K_JOY4},
+
+	{"AUX1", K_AUX1},
+	{"AUX2", K_AUX2},
+	{"AUX3", K_AUX3},
+	{"AUX4", K_AUX4},
+	{"AUX5", K_AUX5},
+	{"AUX6", K_AUX6},
+	{"AUX7", K_AUX7},
+	{"AUX8", K_AUX8},
+	{"AUX9", K_AUX9},
+	{"AUX10", K_AUX10},
+	{"AUX11", K_AUX11},
+	{"AUX12", K_AUX12},
+	{"AUX13", K_AUX13},
+	{"AUX14", K_AUX14},
+	{"AUX15", K_AUX15},
+	{"AUX16", K_AUX16},
+	{"AUX17", K_AUX17},
+	{"AUX18", K_AUX18},
+	{"AUX19", K_AUX19},
+	{"AUX20", K_AUX20},
+	{"AUX21", K_AUX21},
+	{"AUX22", K_AUX22},
+	{"AUX23", K_AUX23},
+	{"AUX24", K_AUX24},
+	{"AUX25", K_AUX25},
+	{"AUX26", K_AUX26},
+	{"AUX27", K_AUX27},
+	{"AUX28", K_AUX28},
+	{"AUX29", K_AUX29},
+	{"AUX30", K_AUX30},
+	{"AUX31", K_AUX31},
+	{"AUX32", K_AUX32},
+
+	{"PAUSE", K_PAUSE},
+
+	{"MWHEELUP", K_MWHEELUP},
+	{"MWHEELDOWN", K_MWHEELDOWN},
+
+	{"SEMICOLON", ';'},	// because a raw semicolon seperates commands
+
+	{NULL,0}
+};
+
+/*
+==============================================================================
+
+			LINE TYPING INTO THE CONSOLE
+
+==============================================================================
+*/
+
+
+/*
+====================
+Key_Console
+
+Interactive line editing and console scrollback
+====================
+*/
+void Key_Console (int key)
+{
+	char	*cmd;
+	
+	if (key == K_ENTER)
+	{
+		Cbuf_AddText (key_lines[edit_line]+1);	// skip the >
+		Cbuf_AddText ("\n");
+		Con_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
+		cmd = Cmd_CompleteCommand (key_lines[edit_line]+1);
+		if (!cmd)
+			cmd = Cvar_CompleteVariable (key_lines[edit_line]+1);
+		if (cmd)
+		{
+			Q_strcpy (key_lines[edit_line]+1, cmd);
+			key_linepos = Q_strlen(cmd)+1;
+			key_lines[edit_line][key_linepos] = ' ';
+			key_linepos++;
+			key_lines[edit_line][key_linepos] = 0;
+			return;
+		}
+	}
+	
+	if (key == K_BACKSPACE || key == K_LEFTARROW)
+	{
+		if (key_linepos > 1)
+			key_linepos--;
+		return;
+	}
+
+	if (key == K_UPARROW)
+	{
+		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;
+		Q_strcpy(key_lines[edit_line], key_lines[history_line]);
+		key_linepos = Q_strlen(key_lines[edit_line]);
+		return;
+	}
+
+	if (key == K_DOWNARROW)
+	{
+		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
+		{
+			Q_strcpy(key_lines[edit_line], key_lines[history_line]);
+			key_linepos = Q_strlen(key_lines[edit_line]);
+		}
+		return;
+	}
+
+	if (key == K_PGUP || key==K_MWHEELUP)
+	{
+		con_backscroll += 2;
+		if (con_backscroll > con_totallines - (vid.height>>3) - 1)
+			con_backscroll = con_totallines - (vid.height>>3) - 1;
+		return;
+	}
+
+	if (key == K_PGDN || key==K_MWHEELDOWN)
+	{
+		con_backscroll -= 2;
+		if (con_backscroll < 0)
+			con_backscroll = 0;
+		return;
+	}
+
+	if (key == K_HOME)
+	{
+		con_backscroll = con_totallines - (vid.height>>3) - 1;
+		return;
+	}
+
+	if (key == K_END)
+	{
+		con_backscroll = 0;
+		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;
+	}
+
+}
+
+//============================================================================
+
+char chat_buffer[32];
+qboolean team_message = false;
+
+void Key_Message (int key)
+{
+	static int chat_bufferlen = 0;
+
+	if (key == K_ENTER)
+	{
+		if (team_message)
+			Cbuf_AddText ("say_team \"");
+		else
+			Cbuf_AddText ("say \"");
+		Cbuf_AddText(chat_buffer);
+		Cbuf_AddText("\"\n");
+
+		key_dest = key_game;
+		chat_bufferlen = 0;
+		chat_buffer[0] = 0;
+		return;
+	}
+
+	if (key == K_ESCAPE)
+	{
+		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 == 31)
+		return; // all full
+
+	chat_buffer[chat_bufferlen++] = key;
+	chat_buffer[chat_bufferlen] = 0;
+}
+
+//============================================================================
+
+
+/*
+===================
+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;
+}
+
+/*
+===================
+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_SetBinding
+===================
+*/
+void Key_SetBinding (int keynum, char *binding)
+{
+	char	*new;
+	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 = Q_strlen (binding);	
+	new = Z_Malloc (l+1);
+	Q_strcpy (new, binding);
+	new[l] = 0;
+	keybindings[keynum] = new;	
+}
+
+/*
+===================
+Key_Unbind_f
+===================
+*/
+void Key_Unbind_f (void)
+{
+	int		b;
+
+	if (Cmd_Argc() != 2)
+	{
+		Con_Printf ("unbind <key> : remove commands from a key\n");
+		return;
+	}
+	
+	b = Key_StringToKeynum (Cmd_Argv(1));
+	if (b==-1)
+	{
+		Con_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
+		return;
+	}
+
+	Key_SetBinding (b, "");
+}
+
+void Key_Unbindall_f (void)
+{
+	int		i;
+	
+	for (i=0 ; i<256 ; i++)
+		if (keybindings[i])
+			Key_SetBinding (i, "");
+}
+
+
+/*
+===================
+Key_Bind_f
+===================
+*/
+void Key_Bind_f (void)
+{
+	int			i, c, b;
+	char		cmd[1024];
+	
+	c = Cmd_Argc();
+
+	if (c != 2 && c != 3)
+	{
+		Con_Printf ("bind <key> [command] : attach a command to a key\n");
+		return;
+	}
+	b = Key_StringToKeynum (Cmd_Argv(1));
+	if (b==-1)
+	{
+		Con_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
+		return;
+	}
+
+	if (c == 2)
+	{
+		if (keybindings[b])
+			Con_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keybindings[b] );
+		else
+			Con_Printf ("\"%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++)
+	{
+		if (i > 2)
+			strcat (cmd, " ");
+		strcat (cmd, Cmd_Argv(i));
+	}
+
+	Key_SetBinding (b, cmd);
+}
+
+/*
+============
+Key_WriteBindings
+
+Writes lines containing "bind key value"
+============
+*/
+void Key_WriteBindings (FILE *f)
+{
+	int		i;
+
+	for (i=0 ; i<256 ; i++)
+		if (keybindings[i])
+			if (*keybindings[i])
+				fprintf (f, "bind \"%s\" \"%s\"\n", Key_KeynumToString(i), keybindings[i]);
+}
+
+
+/*
+===================
+Key_Init
+===================
+*/
+void Key_Init (void)
+{
+	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_TAB] = true;
+	consolekeys[K_LEFTARROW] = true;
+	consolekeys[K_RIGHTARROW] = true;
+	consolekeys[K_UPARROW] = true;
+	consolekeys[K_DOWNARROW] = true;
+	consolekeys[K_BACKSPACE] = true;
+	consolekeys[K_PGUP] = true;
+	consolekeys[K_PGDN] = true;
+	consolekeys[K_SHIFT] = true;
+	consolekeys[K_MWHEELUP] = true;
+	consolekeys[K_MWHEELDOWN] = 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);
+
+
+}
+
+/*
+===================
+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, qboolean down)
+{
+	char	*kb;
+	char	cmd[1024];
+
+	keydown[key] = down;
+
+	if (!down)
+		key_repeats[key] = 0;
+
+	key_lastpress = key;
+	key_count++;
+	if (key_count <= 0)
+	{
+		return;		// just catching keys for Con_NotifyBox
+	}
+
+// update auto-repeat status
+	if (down)
+	{
+		key_repeats[key]++;
+		if (key != K_BACKSPACE && key != K_PAUSE && key_repeats[key] > 1)
+		{
+			return;	// ignore most autorepeats
+		}
+			
+		if (key >= 200 && !keybindings[key])
+			Con_Printf ("%s is unbound, hit F4 to set.\n", Key_KeynumToString (key) );
+	}
+
+	if (key == K_SHIFT)
+		shift_down = down;
+
+//
+// handle escape specialy, so the user can never unbind it
+//
+	if (key == K_ESCAPE)
+	{
+		if (!down)
+			return;
+		switch (key_dest)
+		{
+		case key_message:
+			Key_Message (key);
+			break;
+		case key_menu:
+			M_Keydown (key);
+			break;
+		case key_game:
+		case key_console:
+			M_ToggleMenu_f ();
+			break;
+		default:
+			Sys_Error ("Bad key_dest");
+		}
+		return;
+	}
+
+//
+// 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)
+	{
+		kb = keybindings[key];
+		if (kb && kb[0] == '+')
+		{
+			sprintf (cmd, "-%s %i\n", kb+1, key);
+			Cbuf_AddText (cmd);
+		}
+		if (keyshift[key] != key)
+		{
+			kb = keybindings[keyshift[key]];
+			if (kb && kb[0] == '+')
+			{
+				sprintf (cmd, "-%s %i\n", kb+1, key);
+				Cbuf_AddText (cmd);
+			}
+		}
+		return;
+	}
+
+//
+// during demo playback, most keys bring up the main menu
+//
+	if (cls.demoplayback && down && consolekeys[key] && key_dest == key_game)
+	{
+		M_ToggleMenu_f ();
+		return;
+	}
+
+//
+// if not a consolekey, send to the interpreter no matter what mode is
+//
+	if ( (key_dest == key_menu && menubound[key])
+	|| (key_dest == key_console && !consolekeys[key])
+	|| (key_dest == key_game && ( !con_forcedup || !consolekeys[key] ) ) )
+	{
+		kb = keybindings[key];
+		if (kb)
+		{
+			if (kb[0] == '+')
+			{	// button commands add keynum as a parm
+				sprintf (cmd, "%s %i\n", kb, key);
+				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 (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;
+	default:
+		Sys_Error ("Bad key_dest");
+	}
+}
+
+
+/*
+===================
+Key_ClearStates
+===================
+*/
+void Key_ClearStates (void)
+{
+	int		i;
+
+	for (i=0 ; i<256 ; i++)
+	{
+		keydown[i] = false;
+		key_repeats[i] = 0;
+	}
+}
+
--- /dev/null
+++ b/WinQuake/kit/3DFX.TXT
@@ -1,0 +1,279 @@
+GLQuake Drivers for Voodoo Graphics Based 3D Accelerators
+Preliminary Release 2
+
+Copyright ( 1997 3Dfx Interactive, Inc. )
+All Rights Reserved
+
+3Dfx Interactive, Inc.
+www: www.3dfx.com
+news: news.3dfx.com  
+
+-----------------------------------------------------------------------
+NOTE: GLQuake requires DirectX 2.0 or DirectX 3.0
+(Needed for DirectSound support)
+
+DirectX 2.0 or DirectX 3.0 can be installed from the media provided
+with your Voodoo Based 3D Accelerator.
+-----------------------------------------------------------------------
+
+
+
+Release Notes for GLQuake Preliminary Release 2 (Quake Version 1.07)
+
+-----------------------------------------------------------------------
+What's in the distribution?
+-----------------------------------------------------------------------
+This distribution contains GLQuake Drivers for Voodoo Graphics Based 3D
+Accelerators.  These drivers were tested on the following boards: 
+
+- Diamond Monster 3D
+- Orchid Righteous 3D
+- 3Dfx Interactive reference boards
+
+NOTE:  The enclosed drivers are not meant to replace any Direct3D or 
+Glide drivers provided by your Voodoo Graphics card manufacturer. 
+Please obtain supported drivers from:
+
+- Diamond supported drivers can be obtained from www.diamondmm.com
+- Orchid supported drivers can be obtained from www.orchid.com
+
+
+Included Files
+--------------
+
+OPENGL32.DLL - QuakeGL Interface to Voodoo Graphics
+FXMEMMAP.VXD - Voodoo Graphics VXD
+GLQUAKE.EXE  - ID Software provided GLQUAKE.EXE
+README.TXT   - ID Software provided README.TXT
+READ3DFX.TXT - This File
+
+All enclosed files MUST reside in your Quake directory and not in the
+Windows\SYSTEM directory.  
+
+OEMSR2 users: Do NOT replace OPENGL32.DLL located in your
+Windows\SYSTEM directory.
+
+
+
+-----------------------------------------------------------------------
+Installation 
+-----------------------------------------------------------------------
+
+Requirements
+------------
+
+- Voodoo Graphics Based 3D Accelerator
+- Windows 95
+- A PC with a Pentium 90 or higher CPU
+- 16MB of RAM
+- 2D Video card set at 16 bit or higher color
+
+Installation
+------------
+
+Adding GLQuake Driver Support
+-----------------------------
+1) Install the FULL Version (Not the Shareware version!) of Quake.
+
+2) Copy the GLQUAKE.EXE and other associated files from the GLQuake
+   ZIP file to your Quake Directory.  (Use Windows Explorer)
+
+3) Copy the enclosed OPENGL32.DLL file to your Quake Directory.
+   (Use Windows Explorer)   NOTE: DO NOT COPY OPENGL32.DLL to your
+   Windows\SYSTEM directory
+
+4) Create a Desktop Shortcut for GLQuake:  Using the right mouse button
+   drag the GLQUAKE.EXE file from Windows Explorer to the Desktop.
+         When prompted choose "Create Shortcut"
+
+5) Create an autoexec.cfg file in the ID1\ directory of your quake
+   installation if you do not already have one.  Add the line
+
+   gl_playermip "2"
+
+   to the file.
+
+6) Start GLQuake by running the shortcut. 
+
+
+Troubleshooting and Frequently Asked Questions
+----------------------------------------------
+
+1. Will GLQuake work with shareware Quake?
+
+No, the full registered version of Quake is required.  
+
+
+
+2. Do I need any other drivers to run GLQuake on Voodoo Graphics?
+
+Just make sure that the FXMEMMAP.VXD file is in your Windows\SYSTEM 
+directory and that DirectX 2.0, DirectX 3.0 or DirectX 3.0a are
+installed as GLQuake uses DirectSound. The latest version of DirectX
+can be obtained from:
+	http://www.microsoft.com/mediadev/download/isdk.htm
+
+
+
+3. I installed GLQuake and try to run the GLQUAKE.EXE file but I get a 
+"no RGB fullscreen modes available"  How do I get GLQuake to run?
+
+Make sure that your 2D video card is set to 16bit color (65K colors,
+not 16 colors). In addition, do not start GLQuake from a full screen
+MS-DOS prompt.
+
+
+
+4. GLQuake comes up for a little while then drops me back to the
+Windows 95 desktop, what's wrong?
+
+Your Virtual Memory settings on your system should be increased.  Open
+Control Panel, System - click on the Performance tab and then click on
+Virtual Memory.  Adjust the settings so that the minimum swap file size
+is 80MB.   You may also want to delete all the mesh files - do this by
+deleting the quake\ID1\glquake directory.
+
+
+
+5. Why does GLQuake try to connect to my Internet connection whenever
+it starts?
+
+GLQuake uses Windows networking. Auto-Dial is likely enabled in your
+Internet Control Panel or in Internet Explorer Options.  Single
+Player users: To disable Network use in GLQuake and prevent the
+network connection screen from coming up, add "-nolan" to the
+GLQUAKE.EXE command line, example:
+	GLQUAKE.EXE -nolan
+
+
+
+
+6. I have a three button mouse, but I can't use or set the middle 
+button in GLQuake, what's wrong?
+
+To use a three button mouse with GLQuake, your Windows mouse driver
+must support three buttons.  Use the Logitech PS/2, Serial or Bus
+driver instead of the Microsoft or Standard PS/2, Serial or Bus driver.
+Also, make certain that your Mouse control panel sets the middle button
+to "middle" and not "double click".
+
+
+
+7. Mouse input seems jumpy, how do I fix that?
+
+From the console (hit the ~ tilde key), enter m_filter 1 <enter>
+This option can be added to the AUTOEXEC.CFG file (in the \ID1
+directory).  You may also add this option to the GLQUAKE.EXE command
+line, example:
+	GLQUAKE.EXE +m_filter 1
+
+
+
+8. While playing GLQuake the sound stutters, how do I fix it?
+
+If your sound card does not support DirectSound, you may encounter
+stuttering sound during game play.  Try adding the following value to
+the CONFIG.CFG file (in the quake\ID1 directory):
+		 _snd_mixahead ".14"
+
+
+
+9. When I hit ALT-TAB or the Windows start button to switch to another
+application why do I return to a 640x480 display?
+
+GLQuake by default needs to keep the 2D display at 640x480 while it is
+running. To return the display to your normal setting you must exit
+GLQuake.  To prevent this, add the following to the GLQUAKE.EXE command
+line options "+_windowed_mouse 1" and "-window"   example:
+	GLQUAKE.EXE +_windowed_mouse 1 -window
+
+
+
+10. GLQuake multiplayer can't find other games or won't connect.
+
+GLQuake uses Windows 95 Networking.  Verify that the correct networking
+components are installed and that you can connect to the other machine
+using File and print sharing or TCP/IP ping.  If you are using IPX also
+make certain that the frame type is the same on all the systems.
+
+
+
+11. GLQuake multiplayer slows down alot, how do I fix it?
+
+Add gl_playermip 2 to the AUTOEXEC.CFG file (in the \ID1 directory)
+You may however add "+gl_playermip 2" to the GLQUAKE.EXE command line,
+example:
+	GLQUAKE.EXE +gl_playermip 2
+
+
+
+12. Does the Activision(r) Scourge of Armagon add-on (Mission Pack 1)
+work with GLQuake?
+
+Yes, start GLQUAKE.EXE with a "-hipnotic" switch.   Example:
+	GLQUAKE.EXE -hipnotic
+
+
+
+13. Do other 3rd party quake levels work with GLQuake?
+
+Not all 3rd party levels have been tested, some may not work properly
+or optimally.
+
+
+
+14. Will GLQuake use a Voodoo Graphics accelerator under Windows NT?
+
+The 3Dfx GLQuake drivers currently only work under Windows 95.  
+
+
+
+15. After installing GLQuake the OpenGL screen savers in Windows 95
+(OEMSR2) don't work. What's wrong?
+
+The OpenGL Windows 95 screen savers in OEMSR2 will fail if you copied
+the OPENGL32.DLL file that comes with GLQuake to your Windows\SYSTEM
+directory.  The 3Dfx OPENGL32.DLL file only works with Quake. It will
+not run with other OpenGL applications.  If you copied the 3Dfx 
+OPENGL32.DLL to your Windows\SYSTEM directory and need to restore the
+Microsoft provided OPENGL32.DLL, follow these steps:
+
+	OEMSR2 Users
+	------------
+	1) Insert your Windows 95 CD into your CD-ROM drive
+	2) Open a MS-DOS prompt, (Click Start, Programs, MS-DOS Prompt)
+	3) Switch to the Windows\SYSTEM directory, ie:
+		C: <enter>
+		CD\Windows\system <enter>
+	4) At the command prompt, enter:
+		EXTRACT /A E:\WIN95 opengl32.dll <enter>
+		(Substitute E:\ for your CD-ROM drive letter)
+	
+	Standard Windows 95 Users
+	-------------------------
+	1) Download and reinstall OpenGL for Windows 95 from the source
+	you previously used.
+		
+
+
+16. How do I get support for GLQuake
+
+GLQuake is currently unsupported.  You may however find answers to 
+questions on various Quake dedicated websites.  3Dfx provides a GLQuake
+newsgroup on news.3dfx.com (Newsgroup name is 3dfx.games.glquake ) to
+discuss GLQuake with other users.  3Dfx also provides a regularly
+updated GLQuake FAQ at: http://www.3dfx.com/game_dev/quake_faq.html
+
+
+
+16. How do I send a bug report?
+
+If your problem is not resolved in this document or our updated FAQ
+(please see #15) and your bug is related to visual quality, performance
+or stability send an email to quake_bugs@3dfx.com   - Describe your
+system configuration  (CPU Type, CPU Speed, 2D Video Card type, Amount
+of Memory, Virtual Memory Size..etc.) and how to recreate the bug.  
+
+
+Voodoo Graphics is a trademark of 3Dfx Interactive, Inc.  All other
+trademarks are the property of their respective owners.
\ No newline at end of file
--- /dev/null
+++ b/WinQuake/kit/JOYSTICK.TXT
@@ -1,0 +1,167 @@
+
+NEW NOTE FOR 1.08:
+Joysticks are disabled by defualt now, due to problems on some systems without joysticks installed.  Type "joystick 1" at the console, and everything will behave as documented here.  This will be saved in your config file, so you will only have to do it once.
+
+Description of Windows 95 Quake DirectInput support
+By:  FPgaming, Inc. (www.fpgaming.com) -- Creators of the Assassin 3D
+File:  JOYSTICK.TXT
+Created:  02/21/97
+(This may be more information than you ever wanted to know.)
+
+The joystick support with Windows 95 Quake has been significantly enhanced.  Standard joysticks, digital joysticks and new advanced controllers like the FPgaming Assassin 3D, the Logitech WingMan Warrior and the SpaceTec IMC SpaceOrb are all supported.
+
+To make it work, just verify that your joystick or game controller is selected in the Joystick control panel applet and has been calibrated and tested, then launch Windows 95 Quake (WinQuake.exe or glquake.exe).  For standard and new digital joysticks, WinQuake will detect the joystick and automatically configure it.  For advanced controllers, you will additionally need to run a config file (or run it from your autoexec.cfg) prior to the device operating.  This will set the advanced features for your particular device.  This config file should be obtained from your game controller company.  The config files for the comman game controllers are included below.  If you don't want your joystick or game controller enabled, add '-nojoy' to your command-line.
+
+
+Standard Joystick Support
+The standard joystick support has been enhanced to provide the following:
+1. proportional movement (the farther you move the stick, the faster you move) 
+2. support for up to 32 buttons (JOY1-JOY4 and AUX5-AUX32)
+3. sensitivity setting for each control (allows tuning and inverting the control direction)
+4. dead-zone setting for each control
+
+The default joystick setting is for joystick left/right movement to control turning and for joystick forward/backward movement to control moving forward/backward.  For optional strafing, add the 'sidestep' feature to one of your buttons (via the Customize menu).  For optional looking, add the  'mouse look' feature to one of your buttons (also via the Customize menu).  
+
+Additionally, there are several features that you can set from the Options menu.  'Always Run' allows you change your maximum speed from walking to running.  'Invert Mouse' allows you to change the direction the joystick has to move to when looking up and down.  'Lookspring' enables automatic look-forward-when-moving.  And, 'Lookstrafe' automatically enables strafing when the 'mouse look' button is pressed.
+
+The following variables control your sensititivity settings:
+	joyforwardsensitivity - controls the ramp-up speed for moving forward and backward
+	joysidesensitivity - controls the ramp-up speed for moving side to side
+	joypitchsensitivity - controls the speed that you look up and down
+	joyyawsensitivity - controls the speed that you look left to right
+You can set the sensitivity settings to negative numbers.  This inverts the direction of movement for the control.  The default sensitivity settings are 1 (or -1).  There is no limit on the range; whatever feels good.
+
+The following variables control your threshold settings:
+	joyforwardthreshold - controls the dead-zone for moving forward and backward
+	joysidethreshold - controls the dead-zone for moving side to side
+	joypitchthreshold - controls the dead-zone for looking up and down
+	joyyawthreshold - controls the dead-zone for looking left and right
+The threshold settings allow you to control your dead-zone (or no-movement zone).  The default threshold settings are .15 (meaning 15% of the full-range).  The range of the threshold settings is from 0 to 1.  Troublesome analog joysticks may need a larger number (like .2).  Premium joysticks can use a smaller number (like .1).
+
+The joystick sensitivity settings and the threshold settings are not saved after you quit your game as inadvertant settings can really hose your control.  If you want to keep any changes, add them into your autoexec.cfg file.
+
+If your joystick has a POV hat, the buttons are mapped to AUX29-AUX32.  So, you get 8 buttons with the Logitech WingMan Extreme and 12 buttons with the Microsoft SideWinder 3D Pro, etc.
+
+
+Advanced Controller Support
+The following features have been added:
+1. support for all 6 axes (X, Y, Z, R, U, V)
+2. mapping of any axis to any control (Forward, Look, Side, Turn)
+3. proportional movement for all controls
+4. sensitivity setting for any control (allows tuning and inverting the control direction)
+5. threshold setting for any control (allows dead-zone setting)
+6. support for absolute controls (like joysticks) and relative controls (like trackballs and spinners)
+7. support for up to 32 buttons (JOY1-JOY4 and AUX5-AUX32)
+
+To make an advanced controller operate, you will need to get a config file from your game controller company.  This file is typically placed in your quake\id1 directory and then it is called within your autoexec.cfg file.  For example, if your config file is named gamectrl.cfg, place that file within your quake\id1 directory and add 'exec gamectrl.cfg' in your autoexec.cfg file.  If you don't have an autoexec.cfg file, you can create one and just place this one line in it.
+
+******************************************************************************
+NOTE:  The information below is for game controller companies to integrate their device and for anyone wanting to create a custom setup.
+
+In addition to the above new variables, there are six more for axis mapping.  These are:
+	joyadvaxisx - controls mapping of DirectInput axis X (typically joystick left and right)
+	joyadvaxisy - controls mapping of DirectInput axis Y (typically joystick forward and backward)
+	joyadvaxisz - controls mapping of DirectInput axis Z (typically joystick throttle)
+	joyadvaxisr - controls mapping of DirectInput axis R (typically joystick rudder)
+	joyadvaxisu - controls mapping of DirectInput axis U (custom axis - Assassin 3D trackball left and right, WingMan Warrior SpinControl and SpaceOrb roll)
+	joyadvaxisv - controls mapping of DirectInput axis V (custom axis - Assassin 3D trackball forward and backward and SpaceOrb yaw)
+Each joyadvaxis variable can be set to the following controls:
+	0 = Axis not used
+	1 = Axis is for forward and backward movement
+	2 = Axis is for looking up and down (pitch)
+	3 = Axis is for side to side movement
+	4 = Axis is for turning left and right (yaw)
+Additionally, each axis can be designated as an absolute axis (like a joystick) or a relative axis (like the FPgaming trackball or the WingMan Warrior SpinControl).  Absolute axes are defined as having a stopping position whereas relative axes don't have a stopping position and just go around and around.  To designate an axis as a relative axis, add 16 to the above control number.  For example, to set the Assassin 3D's axis U to be looking left and right, type 'joyadvaxisu 20'.  As another example, to make your rudder pedals contol turning left and right, type 'joyadvaxisr 4'.  It's a bit complicated, but only needs to be done once.
+
+The advanced axes variables will not have any effect until joyadvanced is set to 1.0.  Additionally, any changes to the to the axes will not take effect until the joyadvancedupdate command is executed.  So, the procedure for creating an advanced mapping is:
+1.  set 'joyadvanced 1'
+2.  make any desired mapping changes
+3.  make any desired sensitivity changes
+4.  make any desired threshold changes
+3.  call 'joyadvancedupdate'
+
+Here is the config file for the FPgaming Assassin 3D:
+// ADVA3D.CFG
+// Revision 1.0 -- refer to www.fpgaming.com for updates
+joyname "FPgaming Assassin 3D"
+joyadvanced 1
+joyadvaxisx 3
+joyadvaxisy 1
+joyadvaxisz 0
+joyadvaxisr 0
+joyadvaxisu 20
+joyadvaxisv 18
+joyforwardsensitivity -1.0
+joysidesensitivity 1.0
+joypitchsensitivity -0.25
+joyyawsensitivity -0.5
+joyforwardthreshold 0.15
+joysidethreshold 0.15
+joyyawthreshold 0.0
+joypitchthreshold 0.0
++mlook
+joyadvancedupdate
+
+Here is a config file for the Logitech WingMan Warrior:
+// ADVWW.CFG
+// Revision 0.1 -- refer to www.logitech.com for updates
+joyname "Logitech WingMan Warrior"
+joyadvanced 1.0
+joywwhack1 1.0
+joywwhack2 1.0
+joyadvaxisx 3
+joyadvaxisy 1
+joyadvaxisz 0
+joyadvaxisr 0
+joyadvaxisu 20
+joyadvaxisv 0
+joyforwardsensitivity -1.0
+joysidesensitivity 1.0
+joypitchsensitivity 0.0
+joyyawsensitivity -0.6
+joyforwardthreshold 0.15
+joysidethreshold 0.15
+joypitchthreshold 0.0
+joyyawthreshold 0.0
+joyadvancedupdate
+
+Here is a config file for the SpaceTec IMC SpaceOrb:
+// ADVSPORB.CFG
+// Revision 0.1 -- refer to www.spacetec.com for updates
+joyname "SpaceTec IMC SpaceOrb"
+joyadvanced 1.0
+joyadvaxisx 3
+joyadvaxisy 1
+joyadvaxisz 0
+joyadvaxisr 2
+joyadvaxisu 0
+joyadvaxisv 4
+joyforwardsensitivity -1.0
+joysidesensitivity 1.0
+joypitchsensitivity -0.5
+joyyawsensitivity 1
+joyforwardthreshold 0.1
+joysidethreshold 0.1
+joypitchthreshold 0.1
+joyyawthreshold 0.1
++mlook
+joyadvancedupdate
+
+Here is a config file for making your joystick operate looking around and strafing, your rudder pedals control turning left and right and throttle control moving forward and backward:
+joyname "Joystick, Rudder & Throttle"
+joyadvanced 1.0
+joyadvaxisx 3
+joyadvaxisy 2
+joyadvaxisz 1
+joyadvaxisr 4
+joyadvaxisu 0
+joyadvaxisv 0
+joyforwardsensitivity -1.0
+joysidesensitivity -1.0
+joypitchsensitivity 1.0
+joyyawsensitivity -1.0
+joyforwardthreshold 0.15
+joysidethreshold 0.15
+joyyawthreshold 0.15
+joypitchthreshold 0.15
+joyadvancedupdate
\ No newline at end of file
--- /dev/null
+++ b/WinQuake/kit/README.TXT
@@ -1,0 +1,171 @@
+Glquake v0.97, Quake v1.09 release notes
+
+3dfx owners -- read the 3dfx.txt file.
+
+On a standard OpenGL system, all you should need to do to run glquake is put 
+glquake.exe in your quake directory, and run it from there.  DO NOT install 
+the opengl32.dll unless you have a 3dfx!  Glquake should change the screen 
+resolution to 640*480*32k colors and run full screen by default.
+
+If you are running win-95, your desktop must be set to 32k or 64k colors 
+before running glquake.  NT can switch automatically.
+
+Theoretically, glquake will run on any compliant OpenGL that supports the 
+texture objects extensions, but unless it is very powerfull hardware that 
+accelerates everything needed, the game play will not be acceptable.  If it 
+has to go through any software emulation paths, the performance will likely 
+by well under one frame per second.
+
+3dfx has provided an opengl32.dll that implements everything glquake needs, 
+but it is not a full opengl implementation.  Other opengl applications are 
+very unlikely to work with it, so consider it basically a "glquake driver".  
+See the encluded 3dfx.txt for specific instalation notes.  3dfx can only run 
+full screen, but you must still have your desktop set to a 16 bit color mode 
+for glquake to start.
+
+resolution options
+------------------
+We had dynamic resolution changing in glquake for a while, but every single 
+opengl driver I tried it on messed up in one way or another, so it is now 
+limited to startup time only.
+
+glquake -window
+This will start glquake in a window on your desktop instead of switching the 
+screen to lower resolution and covering everything.
+
+glquake -width 800 -height 600
+Tries to run glquake at the specified resolution.  Combined with -window, it 
+creates a desktop window that size, otherwise it tries to set a full screen 
+resolution.
+
+You can also specify the resolution of the console independant of the screen
+resolution.
+
+glquake -conwidth 320
+This will specify a console resolution of 320 by 240 (the height is
+automatically determined by the default 4:3 aspect ratio, you can also
+specify the height directly with -conheight).
+
+In higher resolution modes such as 800x600 and 1024x768, glquake will default
+to a 640x480 console, since the font becomes small enough at higher 
+resolutions to become unreadable.  If do you wish to have a higher resolution
+console and status bar, specify it as well, such as:
+glquake -width 800 -height 600 -conwidth 800
+
+texture options
+---------------
+The amount of textures used in the game can have a large impact on performance.  
+There are several options that let you trade off visual quality for better 
+performance.
+
+There is no way to flush already loaded textures, so it is best to change 
+these options on the command line, or they will only take effect on some of 
+the textures when you change levels.
+
+OpenGL only allows textures to repeat on power of two boundaries (32, 64, 
+128, etc), but software quake had a number of textures that repeated at 24 
+or 96 pixel boundaries.  These need to be either stretched out to the next 
+higher size, or shrunk down to the next lower.  By default, they are filtered 
+down to the smaller size, but you can cause it to use the larger size if you 
+really want by using: 
+
+glquake +gl_round_down 0
+This will generally run well on a normal 4 MB 3dfx card, but for other cards 
+that have either worse texture management or slower texture swapping speeds, 
+there are some additional settings that can drastically lower the amount of 
+textures to be managed.
+
+glquake +gl_picmip 1
+This causes all textures to have one half the dimensions they otherwise would.  
+This makes them blurry, but very small.  You can set this to 2 to make the 
+textures one quarter the resolution on each axis for REALLY blurry textures.
+
+glquake +gl_playermip 1
+This is similar to picmip, but is only used for other players in deathmatch.  
+Each player in a deathmatch requires an individual skin texture, so this can 
+be a serious problem for texture management.  It wouldn't be unreasonable to 
+set this to 2 or even 3 if you are playing competatively (and don't care if 
+the other guys have smudged skins).  If you change this during the game, it 
+will take effect as soon as a player changes their skin colors.
+
+GLQuake also supports the following extensions for faster texture operation:
+
+GL_SGIS_multitexture
+Multitextures support allows certain hardware to render the world in one
+pass instead of two.  GLQuake uses two passes, one for the world textures
+and the second for the lightmaps that are blended on the textures.  On some
+hardware, with a GL_SIGS_multitexture supported OpenGL implementation, this
+can be done in one pass.  On hardware that supports this, you will get a
+60% to 100% increase in frame rate.  Currently, only 3DFX dual TMU cards
+(such as the Obsidian 2220) support this extension, but other hardware will
+soon follow.
+
+This extension will be autodetected and used.  If for some reason it is not
+working correctly, specify the command line option "-nomtex" to disable it.
+
+GL_EXT_shared_texture_palette
+GLQuake uses 16bit textures by default but on OpenGL implementations 
+that support the GL_EXT_shared_texture_palette extension, GLQuake will use
+8bit textures instead.  This results in using half the needed texture memory
+of 16bit texture and can improve performance.  This is very little difference
+in visual quality due to the fact that the textures are 8bit sources to
+begin with.
+
+run time options
+----------------
+At the console, you can set these values to effect drawing.
+
+gl_texturemode GL_NEAREST
+Sets texture mapping to point sampled, which may be faster on some GL systems 
+(not on 3dfx).
+
+gl_texturemode GL_LINEAR_MIPMAP
+This is the default texture mode.
+
+gl_texturemode GL_LINEAR_MIPMAP_LINEAR
+This is the highest quality texture mapping (trilinear), but only very high 
+end hardware (intergraph intense 3D / realizm) supports it.  Not that big of 
+a deal, actually.
+
+gl_finish 0
+This causes the game to not issue a glFinish() call each frame, which may make 
+some hardware run faster.  If this is cleared, the 3dfx will back up a number 
+of frames and not be very playable.
+
+gl_flashblend 0
+By default, glquake just draws a shaded ball around objects that are emiting 
+light.  Clearing this variable will cause it to properly relight the world 
+like normal quake, but it can be a significant speed hit on some systems.
+
+gl_ztrick 0
+Glquake uses a buffering method that avoids clearing the Z buffer, but some 
+hardware platforms don't like it.  If the status bar and console are flashing 
+every other frame, clear this variable.
+
+gl_keeptjunctions 0
+If you clear this, glquake will remove colinear vertexes when it reloads the 
+level.  This can give a few percent speedup, but it can leave a couple stray 
+blinking pixels on the screen.
+
+novelty features
+----------------
+These are some rendering tricks that were easy to do in glquake.  They aren't 
+very robust, but they are pretty cool to look at.
+
+r_shadows 1
+This causes every object to cast a shadow.
+
+r_wateralpha 0.7
+This sets the opacity of water textures, so you can see through it in properly 
+processed maps.  0.3 is very faint, almost like fog.  1 is completely solid 
+(the default).  Unfortunately, the standard quake maps don't contain any 
+visibility information for seeing past water surfaces, so you can't just play 
+quake with this turned on.  If you just want to see what it looks like, you 
+can set "r_novis 1", but that will make things go very slow.  When I get a 
+chance, I will probably release some maps that have been processed properly 
+for this.
+
+r_mirroralpha 0.3
+This changes one particular texture (the stained glass texture in the EASY 
+start hall) into a mirror.  The value is the opacity of the mirror surface.
+
--- /dev/null
+++ b/WinQuake/mathlib.c
@@ -1,0 +1,585 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+// mathlib.c -- math primitives
+
+#include <math.h>
+#include "quakedef.h"
+
+void Sys_Error (char *error, ...);
+
+vec3_t vec3_origin = {0,0,0};
+int nanmask = 255<<23;
+
+/*-----------------------------------------------------------------*/
+
+#define DEG2RAD( a ) ( a * M_PI ) / 180.0F
+
+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];
+}
+
+/*
+** assumes "src" is normalized
+*/
+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 );
+}
+
+#ifdef _WIN32
+#pragma optimize( "", off )
+#endif
+
+
+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;
+
+	zrot[0][0] = cos( DEG2RAD( degrees ) );
+	zrot[0][1] = sin( DEG2RAD( degrees ) );
+	zrot[1][0] = -sin( DEG2RAD( degrees ) );
+	zrot[1][1] = cos( DEG2RAD( degrees ) );
+
+	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];
+	}
+}
+
+#ifdef _WIN32
+#pragma optimize( "", on )
+#endif
+
+/*-----------------------------------------------------------------*/
+
+
+float	anglemod(float a)
+{
+#if 0
+	if (a >= 0)
+		a -= 360*(int)(a/360);
+	else
+		a += 360*( 1 + (int)(-a/360) );
+#endif
+	a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535);
+	return a;
+}
+
+/*
+==================
+BOPS_Error
+
+Split out like this for ASM to call.
+==================
+*/
+void BOPS_Error (void)
+{
+	Sys_Error ("BoxOnPlaneSide:  Bad signbits");
+}
+
+
+#if	!id386
+
+/*
+==================
+BoxOnPlaneSide
+
+Returns 1, 2, or 1 + 2
+==================
+*/
+int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, mplane_t *p)
+{
+	float	dist1, dist2;
+	int		sides;
+
+#if 0	// this is done by the BOX_ON_PLANE_SIDE macro before calling this
+		// function
+// fast axial cases
+	if (p->type < 3)
+	{
+		if (p->dist <= emins[p->type])
+			return 1;
+		if (p->dist >= emaxs[p->type])
+			return 2;
+		return 3;
+	}
+#endif
+	
+// general case
+	switch (p->signbits)
+	{
+	case 0:
+dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+		break;
+	case 1:
+dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+		break;
+	case 2:
+dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+		break;
+	case 3:
+dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+		break;
+	case 4:
+dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+		break;
+	case 5:
+dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+		break;
+	case 6:
+dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+		break;
+	case 7:
+dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+		break;
+	default:
+		dist1 = dist2 = 0;		// shut up compiler
+		BOPS_Error ();
+		break;
+	}
+
+#if 0
+	int		i;
+	vec3_t	corners[2];
+
+	for (i=0 ; i<3 ; i++)
+	{
+		if (plane->normal[i] < 0)
+		{
+			corners[0][i] = emins[i];
+			corners[1][i] = emaxs[i];
+		}
+		else
+		{
+			corners[1][i] = emins[i];
+			corners[0][i] = emaxs[i];
+		}
+	}
+	dist = DotProduct (plane->normal, corners[0]) - plane->dist;
+	dist2 = DotProduct (plane->normal, corners[1]) - plane->dist;
+	sides = 0;
+	if (dist1 >= 0)
+		sides = 1;
+	if (dist2 < 0)
+		sides |= 2;
+
+#endif
+
+	sides = 0;
+	if (dist1 >= p->dist)
+		sides = 1;
+	if (dist2 < p->dist)
+		sides |= 2;
+
+#ifdef PARANOID
+if (sides == 0)
+	Sys_Error ("BoxOnPlaneSide: sides==0");
+#endif
+
+	return sides;
+}
+
+#endif
+
+
+void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up)
+{
+	float		angle;
+	float		sr, sp, sy, cr, cp, cy;
+	
+	angle = angles[YAW] * (M_PI*2 / 360);
+	sy = sin(angle);
+	cy = cos(angle);
+	angle = angles[PITCH] * (M_PI*2 / 360);
+	sp = sin(angle);
+	cp = cos(angle);
+	angle = angles[ROLL] * (M_PI*2 / 360);
+	sr = sin(angle);
+	cr = cos(angle);
+
+	forward[0] = cp*cy;
+	forward[1] = cp*sy;
+	forward[2] = -sp;
+	right[0] = (-1*sr*sp*cy+-1*cr*-sy);
+	right[1] = (-1*sr*sp*sy+-1*cr*cy);
+	right[2] = -1*sr*cp;
+	up[0] = (cr*sp*cy+-sr*-sy);
+	up[1] = (cr*sp*sy+-sr*cy);
+	up[2] = cr*cp;
+}
+
+int VectorCompare (vec3_t v1, vec3_t v2)
+{
+	int		i;
+	
+	for (i=0 ; i<3 ; i++)
+		if (v1[i] != v2[i])
+			return 0;
+			
+	return 1;
+}
+
+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];
+}
+
+
+vec_t _DotProduct (vec3_t v1, vec3_t v2)
+{
+	return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
+}
+
+void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out)
+{
+	out[0] = veca[0]-vecb[0];
+	out[1] = veca[1]-vecb[1];
+	out[2] = veca[2]-vecb[2];
+}
+
+void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out)
+{
+	out[0] = veca[0]+vecb[0];
+	out[1] = veca[1]+vecb[1];
+	out[2] = veca[2]+vecb[2];
+}
+
+void _VectorCopy (vec3_t in, vec3_t out)
+{
+	out[0] = in[0];
+	out[1] = in[1];
+	out[2] = in[2];
+}
+
+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];
+}
+
+double sqrt(double x);
+
+vec_t Length(vec3_t v)
+{
+	int		i;
+	float	length;
+	
+	length = 0;
+	for (i=0 ; i< 3 ; i++)
+		length += v[i]*v[i];
+	length = sqrt (length);		// FIXME
+
+	return length;
+}
+
+float VectorNormalize (vec3_t v)
+{
+	float	length, ilength;
+
+	length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+	length = sqrt (length);		// FIXME
+
+	if (length)
+	{
+		ilength = 1/length;
+		v[0] *= ilength;
+		v[1] *= ilength;
+		v[2] *= ilength;
+	}
+		
+	return length;
+
+}
+
+void VectorInverse (vec3_t v)
+{
+	v[0] = -v[0];
+	v[1] = -v[1];
+	v[2] = -v[2];
+}
+
+void VectorScale (vec3_t in, vec_t scale, vec3_t out)
+{
+	out[0] = in[0]*scale;
+	out[1] = in[1]*scale;
+	out[2] = in[2]*scale;
+}
+
+
+int Q_log2(int val)
+{
+	int answer=0;
+	while (val>>=1)
+		answer++;
+	return answer;
+}
+
+
+/*
+================
+R_ConcatRotations
+================
+*/
+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];
+}
+
+
+/*
+================
+R_ConcatTransforms
+================
+*/
+void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4])
+{
+	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[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] +
+				in1[0][2] * in2[2][3] + in1[0][3];
+	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[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] +
+				in1[1][2] * in2[2][3] + in1[1][3];
+	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];
+	out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] +
+				in1[2][2] * in2[2][3] + in1[2][3];
+}
+
+
+/*
+===================
+FloorDivMod
+
+Returns mathematically correct (floor-based) quotient and remainder for
+numer and denom, both of which should contain no fractional part. The
+quotient must fit in 32 bits.
+====================
+*/
+
+void FloorDivMod (double numer, double denom, int *quotient,
+		int *rem)
+{
+	int		q, r;
+	double	x;
+
+#ifndef PARANOID
+	if (denom <= 0.0)
+		Sys_Error ("FloorDivMod: bad denominator %d\n", denom);
+
+//	if ((floor(numer) != numer) || (floor(denom) != denom))
+//		Sys_Error ("FloorDivMod: non-integer numer or denom %f %f\n",
+//				numer, denom);
+#endif
+
+	if (numer >= 0.0)
+	{
+
+		x = floor(numer / denom);
+		q = (int)x;
+		r = (int)floor(numer - (x * denom));
+	}
+	else
+	{
+	//
+	// perform operations with positive values, and fix mod to make floor-based
+	//
+		x = floor(-numer / denom);
+		q = -(int)x;
+		r = (int)floor(-numer - (x * denom));
+		if (r != 0)
+		{
+			q--;
+			r = (int)denom - r;
+		}
+	}
+
+	*quotient = q;
+	*rem = r;
+}
+
+
+/*
+===================
+GreatestCommonDivisor
+====================
+*/
+int GreatestCommonDivisor (int i1, int i2)
+{
+	if (i1 > i2)
+	{
+		if (i2 == 0)
+			return (i1);
+		return GreatestCommonDivisor (i2, i1 % i2);
+	}
+	else
+	{
+		if (i1 == 0)
+			return (i2);
+		return GreatestCommonDivisor (i1, i2 % i1);
+	}
+}
+
+
+#if	!id386
+
+// TODO: move to nonintel.c
+
+/*
+===================
+Invert24To16
+
+Inverts an 8.24 value to a 16.16 value
+====================
+*/
+
+fixed16_t Invert24To16(fixed16_t val)
+{
+	if (val < 256)
+		return (0xFFFFFFFF);
+
+	return (fixed16_t)
+			(((double)0x10000 * (double)0x1000000 / (double)val) + 0.5);
+}
+
+#endif
--- /dev/null
+++ b/WinQuake/model.c
@@ -1,0 +1,1874 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+// models.c -- model loading and caching
+
+// models are the only shared resource between a client and server running
+// on the same machine.
+
+#include "quakedef.h"
+#include "r_local.h"
+
+model_t	*loadmodel;
+char	loadname[32];	// for hunk tags
+
+void Mod_LoadSpriteModel (model_t *mod, void *buffer);
+void Mod_LoadBrushModel (model_t *mod, void *buffer);
+void Mod_LoadAliasModel (model_t *mod, void *buffer);
+model_t *Mod_LoadModel (model_t *mod, qboolean crash);
+
+byte	mod_novis[MAX_MAP_LEAFS/8];
+
+#define	MAX_MOD_KNOWN	256
+model_t	mod_known[MAX_MOD_KNOWN];
+int		mod_numknown;
+
+// values for model_t's needload
+#define NL_PRESENT		0
+#define NL_NEEDS_LOADED	1
+#define NL_UNREFERENCED	2
+
+/*
+===============
+Mod_Init
+===============
+*/
+void Mod_Init (void)
+{
+	memset (mod_novis, 0xff, sizeof(mod_novis));
+}
+
+/*
+===============
+Mod_Extradata
+
+Caches the data if needed
+===============
+*/
+void *Mod_Extradata (model_t *mod)
+{
+	void	*r;
+	
+	r = Cache_Check (&mod->cache);
+	if (r)
+		return r;
+
+	Mod_LoadModel (mod, true);
+	
+	if (!mod->cache.data)
+		Sys_Error ("Mod_Extradata: caching failed");
+	return mod->cache.data;
+}
+
+/*
+===============
+Mod_PointInLeaf
+===============
+*/
+mleaf_t *Mod_PointInLeaf (vec3_t p, model_t *model)
+{
+	mnode_t		*node;
+	float		d;
+	mplane_t	*plane;
+	
+	if (!model || !model->nodes)
+		Sys_Error ("Mod_PointInLeaf: bad model");
+
+	node = model->nodes;
+	while (1)
+	{
+		if (node->contents < 0)
+			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
+}
+
+
+/*
+===================
+Mod_DecompressVis
+===================
+*/
+byte *Mod_DecompressVis (byte *in, model_t *model)
+{
+	static byte	decompressed[MAX_MAP_LEAFS/8];
+	int		c;
+	byte	*out;
+	int		row;
+
+	row = (model->numleafs+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_LeafPVS (mleaf_t *leaf, model_t *model)
+{
+	if (leaf == model->leafs)
+		return mod_novis;
+	return Mod_DecompressVis (leaf->compressed_vis, model);
+}
+
+/*
+===================
+Mod_ClearAll
+===================
+*/
+void Mod_ClearAll (void)
+{
+	int		i;
+	model_t	*mod;
+
+
+	for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++) {
+		mod->needload = NL_UNREFERENCED;
+//FIX FOR CACHE_ALLOC ERRORS:
+		if (mod->type == mod_sprite) mod->cache.data = NULL;
+	}
+}
+
+/*
+==================
+Mod_FindName
+
+==================
+*/
+model_t *Mod_FindName (char *name)
+{
+	int		i;
+	model_t	*mod;
+	model_t	*avail = NULL;
+
+	if (!name[0])
+		Sys_Error ("Mod_ForName: NULL name");
+		
+//
+// search the currently loaded models
+//
+	for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
+	{
+		if (!strcmp (mod->name, name) )
+			break;
+		if (mod->needload == NL_UNREFERENCED)
+			if (!avail || mod->type != mod_alias)
+				avail = mod;
+	}
+			
+	if (i == mod_numknown)
+	{
+		if (mod_numknown == MAX_MOD_KNOWN)
+		{
+			if (avail)
+			{
+				mod = avail;
+				if (mod->type == mod_alias)
+					if (Cache_Check (&mod->cache))
+						Cache_Free (&mod->cache);
+			}
+			else
+				Sys_Error ("mod_numknown == MAX_MOD_KNOWN");
+		}
+		else
+			mod_numknown++;
+		strcpy (mod->name, name);
+		mod->needload = NL_NEEDS_LOADED;
+	}
+
+	return mod;
+}
+
+/*
+==================
+Mod_TouchModel
+
+==================
+*/
+void Mod_TouchModel (char *name)
+{
+	model_t	*mod;
+	
+	mod = Mod_FindName (name);
+	
+	if (mod->needload == NL_PRESENT)
+	{
+		if (mod->type == mod_alias)
+			Cache_Check (&mod->cache);
+	}
+}
+
+/*
+==================
+Mod_LoadModel
+
+Loads a model into the cache
+==================
+*/
+model_t *Mod_LoadModel (model_t *mod, qboolean crash)
+{
+	unsigned *buf;
+	byte	stackbuf[1024];		// avoid dirtying the cache heap
+
+	if (mod->type == mod_alias)
+	{
+		if (Cache_Check (&mod->cache))
+		{
+			mod->needload = NL_PRESENT;
+			return mod;
+		}
+	}
+	else
+	{
+		if (mod->needload == NL_PRESENT)
+			return mod;
+	}
+
+//
+// because the world is so huge, load it one piece at a time
+//
+	
+//
+// load the file
+//
+	buf = (unsigned *)COM_LoadStackFile (mod->name, stackbuf, sizeof(stackbuf));
+	if (!buf)
+	{
+		if (crash)
+			Sys_Error ("Mod_NumForName: %s not found", mod->name);
+		return NULL;
+	}
+	
+//
+// allocate a new model
+//
+	COM_FileBase (mod->name, loadname);
+	
+	loadmodel = mod;
+
+//
+// fill it in
+//
+
+// call the apropriate loader
+	mod->needload = NL_PRESENT;
+
+	switch (LittleLong(*(unsigned *)buf))
+	{
+	case IDPOLYHEADER:
+		Mod_LoadAliasModel (mod, buf);
+		break;
+		
+	case IDSPRITEHEADER:
+		Mod_LoadSpriteModel (mod, buf);
+		break;
+	
+	default:
+		Mod_LoadBrushModel (mod, buf);
+		break;
+	}
+
+	return mod;
+}
+
+/*
+==================
+Mod_ForName
+
+Loads in a model for the given name
+==================
+*/
+model_t *Mod_ForName (char *name, qboolean crash)
+{
+	model_t	*mod;
+
+	mod = Mod_FindName (name);
+
+	return Mod_LoadModel (mod, crash);
+}
+
+
+/*
+===============================================================================
+
+					BRUSHMODEL LOADING
+
+===============================================================================
+*/
+
+byte	*mod_base;
+
+
+/*
+=================
+Mod_LoadTextures
+=================
+*/
+void Mod_LoadTextures (lump_t *l)
+{
+	int		i, j, pixels, num, max, altmax;
+	miptex_t	*mt;
+	texture_t	*tx, *tx2;
+	texture_t	*anims[10];
+	texture_t	*altanims[10];
+	dmiptexlump_t *m;
+
+	if (!l->filelen)
+	{
+		loadmodel->textures = NULL;
+		return;
+	}
+	m = (dmiptexlump_t *)(mod_base + l->fileofs);
+	
+	m->nummiptex = LittleLong (m->nummiptex);
+	
+	loadmodel->numtextures = m->nummiptex;
+	loadmodel->textures = Hunk_AllocName (m->nummiptex * sizeof(*loadmodel->textures) , loadname);
+
+	for (i=0 ; i<m->nummiptex ; i++)
+	{
+		m->dataofs[i] = LittleLong(m->dataofs[i]);
+		if (m->dataofs[i] == -1)
+			continue;
+		mt = (miptex_t *)((byte *)m + m->dataofs[i]);
+		mt->width = LittleLong (mt->width);
+		mt->height = LittleLong (mt->height);
+		for (j=0 ; j<MIPLEVELS ; j++)
+			mt->offsets[j] = LittleLong (mt->offsets[j]);
+		
+		if ( (mt->width & 15) || (mt->height & 15) )
+			Sys_Error ("Texture %s is not 16 aligned", mt->name);
+		pixels = mt->width*mt->height/64*85;
+		tx = Hunk_AllocName (sizeof(texture_t) +pixels, loadname );
+		loadmodel->textures[i] = tx;
+
+		memcpy (tx->name, mt->name, sizeof(tx->name));
+		tx->width = mt->width;
+		tx->height = mt->height;
+		for (j=0 ; j<MIPLEVELS ; j++)
+			tx->offsets[j] = mt->offsets[j] + sizeof(texture_t) - sizeof(miptex_t);
+		// the pixels immediately follow the structures
+		memcpy ( tx+1, mt+1, pixels);
+		
+		if (!Q_strncmp(mt->name,"sky",3))	
+			R_InitSky (tx);
+	}
+
+//
+// sequence the animations
+//
+	for (i=0 ; i<m->nummiptex ; i++)
+	{
+		tx = loadmodel->textures[i];
+		if (!tx || tx->name[0] != '+')
+			continue;
+		if (tx->anim_next)
+			continue;	// allready sequenced
+
+	// find the number of frames in the animation
+		memset (anims, 0, sizeof(anims));
+		memset (altanims, 0, sizeof(altanims));
+
+		max = tx->name[1];
+		altmax = 0;
+		if (max >= 'a' && max <= 'z')
+			max -= 'a' - 'A';
+		if (max >= '0' && max <= '9')
+		{
+			max -= '0';
+			altmax = 0;
+			anims[max] = tx;
+			max++;
+		}
+		else if (max >= 'A' && max <= 'J')
+		{
+			altmax = max - 'A';
+			max = 0;
+			altanims[altmax] = tx;
+			altmax++;
+		}
+		else
+			Sys_Error ("Bad animating texture %s", tx->name);
+
+		for (j=i+1 ; j<m->nummiptex ; j++)
+		{
+			tx2 = loadmodel->textures[j];
+			if (!tx2 || tx2->name[0] != '+')
+				continue;
+			if (strcmp (tx2->name+2, tx->name+2))
+				continue;
+
+			num = tx2->name[1];
+			if (num >= 'a' && num <= 'z')
+				num -= 'a' - 'A';
+			if (num >= '0' && num <= '9')
+			{
+				num -= '0';
+				anims[num] = tx2;
+				if (num+1 > max)
+					max = num + 1;
+			}
+			else if (num >= 'A' && num <= 'J')
+			{
+				num = num - 'A';
+				altanims[num] = tx2;
+				if (num+1 > altmax)
+					altmax = num+1;
+			}
+			else
+				Sys_Error ("Bad animating texture %s", tx->name);
+		}
+		
+#define	ANIM_CYCLE	2
+	// link them all together
+		for (j=0 ; j<max ; j++)
+		{
+			tx2 = anims[j];
+			if (!tx2)
+				Sys_Error ("Missing frame %i of %s",j, tx->name);
+			tx2->anim_total = max * ANIM_CYCLE;
+			tx2->anim_min = j * ANIM_CYCLE;
+			tx2->anim_max = (j+1) * ANIM_CYCLE;
+			tx2->anim_next = anims[ (j+1)%max ];
+			if (altmax)
+				tx2->alternate_anims = altanims[0];
+		}
+		for (j=0 ; j<altmax ; j++)
+		{
+			tx2 = altanims[j];
+			if (!tx2)
+				Sys_Error ("Missing frame %i of %s",j, tx->name);
+			tx2->anim_total = altmax * ANIM_CYCLE;
+			tx2->anim_min = j * ANIM_CYCLE;
+			tx2->anim_max = (j+1) * ANIM_CYCLE;
+			tx2->anim_next = altanims[ (j+1)%altmax ];
+			if (max)
+				tx2->alternate_anims = anims[0];
+		}
+	}
+}
+
+/*
+=================
+Mod_LoadLighting
+=================
+*/
+void Mod_LoadLighting (lump_t *l)
+{
+	if (!l->filelen)
+	{
+		loadmodel->lightdata = NULL;
+		return;
+	}
+	loadmodel->lightdata = Hunk_AllocName ( l->filelen, loadname);	
+	memcpy (loadmodel->lightdata, mod_base + l->fileofs, l->filelen);
+}
+
+
+/*
+=================
+Mod_LoadVisibility
+=================
+*/
+void Mod_LoadVisibility (lump_t *l)
+{
+	if (!l->filelen)
+	{
+		loadmodel->visdata = NULL;
+		return;
+	}
+	loadmodel->visdata = Hunk_AllocName ( l->filelen, loadname);	
+	memcpy (loadmodel->visdata, mod_base + l->fileofs, l->filelen);
+}
+
+
+/*
+=================
+Mod_LoadEntities
+=================
+*/
+void Mod_LoadEntities (lump_t *l)
+{
+	if (!l->filelen)
+	{
+		loadmodel->entities = NULL;
+		return;
+	}
+	loadmodel->entities = Hunk_AllocName ( l->filelen, loadname);	
+	memcpy (loadmodel->entities, mod_base + l->fileofs, l->filelen);
+}
+
+
+/*
+=================
+Mod_LoadVertexes
+=================
+*/
+void Mod_LoadVertexes (lump_t *l)
+{
+	dvertex_t	*in;
+	mvertex_t	*out;
+	int			i, count;
+
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( count*sizeof(*out), loadname);	
+
+	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]);
+	}
+}
+
+/*
+=================
+Mod_LoadSubmodels
+=================
+*/
+void Mod_LoadSubmodels (lump_t *l)
+{
+	dmodel_t	*in;
+	dmodel_t	*out;
+	int			i, j, count;
+
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( count*sizeof(*out), loadname);	
+
+	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]);
+		}
+		for (j=0 ; j<MAX_MAP_HULLS ; j++)
+			out->headnode[j] = LittleLong (in->headnode[j]);
+		out->visleafs = LittleLong (in->visleafs);
+		out->firstface = LittleLong (in->firstface);
+		out->numfaces = LittleLong (in->numfaces);
+	}
+}
+
+/*
+=================
+Mod_LoadEdges
+=================
+*/
+void Mod_LoadEdges (lump_t *l)
+{
+	dedge_t *in;
+	medge_t *out;
+	int 	i, count;
+
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( (count + 1) * sizeof(*out), loadname);	
+
+	loadmodel->edges = out;
+	loadmodel->numedges = count;
+
+	for ( i=0 ; i<count ; i++, in++, out++)
+	{
+		out->v[0] = (unsigned short)LittleShort(in->v[0]);
+		out->v[1] = (unsigned short)LittleShort(in->v[1]);
+	}
+}
+
+/*
+=================
+Mod_LoadTexinfo
+=================
+*/
+void Mod_LoadTexinfo (lump_t *l)
+{
+	texinfo_t *in;
+	mtexinfo_t *out;
+	int 	i, j, count;
+	int		miptex;
+	float	len1, len2;
+
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( count*sizeof(*out), loadname);	
+
+	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]);
+		len1 = Length (out->vecs[0]);
+		len2 = Length (out->vecs[1]);
+		len1 = (len1 + len2)/2;
+		if (len1 < 0.32)
+			out->mipadjust = 4;
+		else if (len1 < 0.49)
+			out->mipadjust = 3;
+		else if (len1 < 0.99)
+			out->mipadjust = 2;
+		else
+			out->mipadjust = 1;
+#if 0
+		if (len1 + len2 < 0.001)
+			out->mipadjust = 1;		// don't crash
+		else
+			out->mipadjust = 1 / floor( (len1+len2)/2 + 0.1 );
+#endif
+
+		miptex = LittleLong (in->miptex);
+		out->flags = LittleLong (in->flags);
+	
+		if (!loadmodel->textures)
+		{
+			out->texture = r_notexture_mip;	// checkerboard texture
+			out->flags = 0;
+		}
+		else
+		{
+			if (miptex >= loadmodel->numtextures)
+				Sys_Error ("miptex >= loadmodel->numtextures");
+			out->texture = loadmodel->textures[miptex];
+			if (!out->texture)
+			{
+				out->texture = r_notexture_mip; // texture not found
+				out->flags = 0;
+			}
+		}
+	}
+}
+
+/*
+================
+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] = -99999;
+
+	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;
+		if ( !(tex->flags & TEX_SPECIAL) && s->extents[i] > 256)
+			Sys_Error ("Bad surface extents");
+	}
+}
+
+
+/*
+=================
+Mod_LoadFaces
+=================
+*/
+void Mod_LoadFaces (lump_t *l)
+{
+	dface_t		*in;
+	msurface_t 	*out;
+	int			i, count, surfnum;
+	int			planenum, side;
+
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( count*sizeof(*out), loadname);	
+
+	loadmodel->surfaces = out;
+	loadmodel->numsurfaces = count;
+
+	for ( surfnum=0 ; surfnum<count ; surfnum++, in++, out++)
+	{
+		out->firstedge = LittleLong(in->firstedge);
+		out->numedges = LittleShort(in->numedges);		
+		out->flags = 0;
+
+		planenum = LittleShort(in->planenum);
+		side = LittleShort(in->side);
+		if (side)
+			out->flags |= SURF_PLANEBACK;			
+
+		out->plane = loadmodel->planes + planenum;
+
+		out->texinfo = loadmodel->texinfo + LittleShort (in->texinfo);
+
+		CalcSurfaceExtents (out);
+				
+	// lighting info
+
+		for (i=0 ; i<MAXLIGHTMAPS ; i++)
+			out->styles[i] = in->styles[i];
+		i = LittleLong(in->lightofs);
+		if (i == -1)
+			out->samples = NULL;
+		else
+			out->samples = loadmodel->lightdata + i;
+		
+	// set the drawing flags flag
+		
+		if (!Q_strncmp(out->texinfo->texture->name,"sky",3))	// sky
+		{
+			out->flags |= (SURF_DRAWSKY | SURF_DRAWTILED);
+			continue;
+		}
+		
+		if (!Q_strncmp(out->texinfo->texture->name,"*",1))		// turbulent
+		{
+			out->flags |= (SURF_DRAWTURB | SURF_DRAWTILED);
+			for (i=0 ; i<2 ; i++)
+			{
+				out->extents[i] = 16384;
+				out->texturemins[i] = -8192;
+			}
+			continue;
+		}
+	}
+}
+
+
+/*
+=================
+Mod_SetParent
+=================
+*/
+void Mod_SetParent (mnode_t *node, mnode_t *parent)
+{
+	node->parent = parent;
+	if (node->contents < 0)
+		return;
+	Mod_SetParent (node->children[0], node);
+	Mod_SetParent (node->children[1], node);
+}
+
+/*
+=================
+Mod_LoadNodes
+=================
+*/
+void Mod_LoadNodes (lump_t *l)
+{
+	int			i, j, count, p;
+	dnode_t		*in;
+	mnode_t 	*out;
+
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( count*sizeof(*out), loadname);	
+
+	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);
+		
+		for (j=0 ; j<2 ; j++)
+		{
+			p = LittleShort (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
+}
+
+/*
+=================
+Mod_LoadLeafs
+=================
+*/
+void Mod_LoadLeafs (lump_t *l)
+{
+	dleaf_t 	*in;
+	mleaf_t 	*out;
+	int			i, j, count, p;
+
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( count*sizeof(*out), loadname);	
+
+	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->firstmarksurface = loadmodel->marksurfaces +
+			LittleShort(in->firstmarksurface);
+		out->nummarksurfaces = LittleShort(in->nummarksurfaces);
+		
+		p = LittleLong(in->visofs);
+		if (p == -1)
+			out->compressed_vis = NULL;
+		else
+			out->compressed_vis = loadmodel->visdata + p;
+		out->efrags = NULL;
+		
+		for (j=0 ; j<4 ; j++)
+			out->ambient_sound_level[j] = in->ambient_level[j];
+	}	
+}
+
+/*
+=================
+Mod_LoadClipnodes
+=================
+*/
+void Mod_LoadClipnodes (lump_t *l)
+{
+	dclipnode_t *in, *out;
+	int			i, count;
+	hull_t		*hull;
+
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( count*sizeof(*out), loadname);	
+
+	loadmodel->clipnodes = out;
+	loadmodel->numclipnodes = count;
+
+	hull = &loadmodel->hulls[1];
+	hull->clipnodes = out;
+	hull->firstclipnode = 0;
+	hull->lastclipnode = count-1;
+	hull->planes = loadmodel->planes;
+	hull->clip_mins[0] = -16;
+	hull->clip_mins[1] = -16;
+	hull->clip_mins[2] = -24;
+	hull->clip_maxs[0] = 16;
+	hull->clip_maxs[1] = 16;
+	hull->clip_maxs[2] = 32;
+
+	hull = &loadmodel->hulls[2];
+	hull->clipnodes = out;
+	hull->firstclipnode = 0;
+	hull->lastclipnode = count-1;
+	hull->planes = loadmodel->planes;
+	hull->clip_mins[0] = -32;
+	hull->clip_mins[1] = -32;
+	hull->clip_mins[2] = -24;
+	hull->clip_maxs[0] = 32;
+	hull->clip_maxs[1] = 32;
+	hull->clip_maxs[2] = 64;
+
+	for (i=0 ; i<count ; i++, out++, in++)
+	{
+		out->planenum = LittleLong(in->planenum);
+		out->children[0] = LittleShort(in->children[0]);
+		out->children[1] = LittleShort(in->children[1]);
+	}
+}
+
+/*
+=================
+Mod_MakeHull0
+
+Deplicate the drawing hull structure as a clipping hull
+=================
+*/
+void Mod_MakeHull0 (void)
+{
+	mnode_t		*in, *child;
+	dclipnode_t *out;
+	int			i, j, count;
+	hull_t		*hull;
+	
+	hull = &loadmodel->hulls[0];	
+	
+	in = loadmodel->nodes;
+	count = loadmodel->numnodes;
+	out = Hunk_AllocName ( count*sizeof(*out), loadname);	
+
+	hull->clipnodes = out;
+	hull->firstclipnode = 0;
+	hull->lastclipnode = count-1;
+	hull->planes = loadmodel->planes;
+
+	for (i=0 ; i<count ; i++, out++, in++)
+	{
+		out->planenum = in->plane - loadmodel->planes;
+		for (j=0 ; j<2 ; j++)
+		{
+			child = in->children[j];
+			if (child->contents < 0)
+				out->children[j] = child->contents;
+			else
+				out->children[j] = child - loadmodel->nodes;
+		}
+	}
+}
+
+/*
+=================
+Mod_LoadMarksurfaces
+=================
+*/
+void Mod_LoadMarksurfaces (lump_t *l)
+{	
+	int		i, j, count;
+	short		*in;
+	msurface_t **out;
+	
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( count*sizeof(*out), loadname);	
+
+	loadmodel->marksurfaces = out;
+	loadmodel->nummarksurfaces = count;
+
+	for ( i=0 ; i<count ; i++)
+	{
+		j = LittleShort(in[i]);
+		if (j >= loadmodel->numsurfaces)
+			Sys_Error ("Mod_ParseMarksurfaces: bad surface number");
+		out[i] = loadmodel->surfaces + j;
+	}
+}
+
+/*
+=================
+Mod_LoadSurfedges
+=================
+*/
+void Mod_LoadSurfedges (lump_t *l)
+{	
+	int		i, count;
+	int		*in, *out;
+	
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( count*sizeof(*out), loadname);	
+
+	loadmodel->surfedges = out;
+	loadmodel->numsurfedges = count;
+
+	for ( i=0 ; i<count ; i++)
+		out[i] = LittleLong (in[i]);
+}
+
+/*
+=================
+Mod_LoadPlanes
+=================
+*/
+void Mod_LoadPlanes (lump_t *l)
+{
+	int			i, j;
+	mplane_t	*out;
+	dplane_t 	*in;
+	int			count;
+	int			bits;
+	
+	in = (void *)(mod_base + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
+	count = l->filelen / sizeof(*in);
+	out = Hunk_AllocName ( count*2*sizeof(*out), loadname);	
+	
+	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;
+	}
+}
+
+/*
+=================
+RadiusFromBounds
+=================
+*/
+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 Length (corner);
+}
+
+/*
+=================
+Mod_LoadBrushModel
+=================
+*/
+void Mod_LoadBrushModel (model_t *mod, void *buffer)
+{
+	int			i, j;
+	dheader_t	*header;
+	dmodel_t 	*bm;
+	
+	loadmodel->type = mod_brush;
+	
+	header = (dheader_t *)buffer;
+
+	i = LittleLong (header->version);
+	if (i != BSPVERSION)
+		Sys_Error ("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_LoadTextures (&header->lumps[LUMP_TEXTURES]);
+	Mod_LoadLighting (&header->lumps[LUMP_LIGHTING]);
+	Mod_LoadPlanes (&header->lumps[LUMP_PLANES]);
+	Mod_LoadTexinfo (&header->lumps[LUMP_TEXINFO]);
+	Mod_LoadFaces (&header->lumps[LUMP_FACES]);
+	Mod_LoadMarksurfaces (&header->lumps[LUMP_MARKSURFACES]);
+	Mod_LoadVisibility (&header->lumps[LUMP_VISIBILITY]);
+	Mod_LoadLeafs (&header->lumps[LUMP_LEAFS]);
+	Mod_LoadNodes (&header->lumps[LUMP_NODES]);
+	Mod_LoadClipnodes (&header->lumps[LUMP_CLIPNODES]);
+	Mod_LoadEntities (&header->lumps[LUMP_ENTITIES]);
+	Mod_LoadSubmodels (&header->lumps[LUMP_MODELS]);
+
+	Mod_MakeHull0 ();
+	
+	mod->numframes = 2;		// regular and alternate animation
+	mod->flags = 0;
+	
+//
+// set up the submodels (FIXME: this is confusing)
+//
+	for (i=0 ; i<mod->numsubmodels ; i++)
+	{
+		bm = &mod->submodels[i];
+
+		mod->hulls[0].firstclipnode = bm->headnode[0];
+		for (j=1 ; j<MAX_MAP_HULLS ; j++)
+		{
+			mod->hulls[j].firstclipnode = bm->headnode[j];
+			mod->hulls[j].lastclipnode = mod->numclipnodes-1;
+		}
+		
+		mod->firstmodelsurface = bm->firstface;
+		mod->nummodelsurfaces = bm->numfaces;
+		
+		VectorCopy (bm->maxs, mod->maxs);
+		VectorCopy (bm->mins, mod->mins);
+		mod->radius = RadiusFromBounds (mod->mins, mod->maxs);
+		
+		mod->numleafs = bm->visleafs;
+
+		if (i < mod->numsubmodels-1)
+		{	// duplicate the basic information
+			char	name[10];
+
+			sprintf (name, "*%i", i+1);
+			loadmodel = Mod_FindName (name);
+			*loadmodel = *mod;
+			strcpy (loadmodel->name, name);
+			mod = loadmodel;
+		}
+	}
+}
+
+/*
+==============================================================================
+
+ALIAS MODELS
+
+==============================================================================
+*/
+
+/*
+=================
+Mod_LoadAliasFrame
+=================
+*/
+void * Mod_LoadAliasFrame (void * pin, int *pframeindex, int numv,
+	trivertx_t *pbboxmin, trivertx_t *pbboxmax, aliashdr_t *pheader, char *name)
+{
+	trivertx_t		*pframe, *pinframe;
+	int				i, j;
+	daliasframe_t	*pdaliasframe;
+
+	pdaliasframe = (daliasframe_t *)pin;
+
+	strcpy (name, pdaliasframe->name);
+
+	for (i=0 ; i<3 ; i++)
+	{
+	// these are byte values, so we don't have to worry about
+	// endianness
+		pbboxmin->v[i] = pdaliasframe->bboxmin.v[i];
+		pbboxmax->v[i] = pdaliasframe->bboxmax.v[i];
+	}
+
+	pinframe = (trivertx_t *)(pdaliasframe + 1);
+	pframe = Hunk_AllocName (numv * sizeof(*pframe), loadname);
+
+	*pframeindex = (byte *)pframe - (byte *)pheader;
+
+	for (j=0 ; j<numv ; j++)
+	{
+		int		k;
+
+	// these are all byte values, so no need to deal with endianness
+		pframe[j].lightnormalindex = pinframe[j].lightnormalindex;
+
+		for (k=0 ; k<3 ; k++)
+		{
+			pframe[j].v[k] = pinframe[j].v[k];
+		}
+	}
+
+	pinframe += numv;
+
+	return (void *)pinframe;
+}
+
+
+/*
+=================
+Mod_LoadAliasGroup
+=================
+*/
+void * Mod_LoadAliasGroup (void * pin, int *pframeindex, int numv,
+	trivertx_t *pbboxmin, trivertx_t *pbboxmax, aliashdr_t *pheader, char *name)
+{
+	daliasgroup_t		*pingroup;
+	maliasgroup_t		*paliasgroup;
+	int					i, numframes;
+	daliasinterval_t	*pin_intervals;
+	float				*poutintervals;
+	void				*ptemp;
+	
+	pingroup = (daliasgroup_t *)pin;
+
+	numframes = LittleLong (pingroup->numframes);
+
+	paliasgroup = Hunk_AllocName (sizeof (maliasgroup_t) +
+			(numframes - 1) * sizeof (paliasgroup->frames[0]), loadname);
+
+	paliasgroup->numframes = numframes;
+
+	for (i=0 ; i<3 ; i++)
+	{
+	// these are byte values, so we don't have to worry about endianness
+		pbboxmin->v[i] = pingroup->bboxmin.v[i];
+		pbboxmax->v[i] = pingroup->bboxmax.v[i];
+	}
+
+	*pframeindex = (byte *)paliasgroup - (byte *)pheader;
+
+	pin_intervals = (daliasinterval_t *)(pingroup + 1);
+
+	poutintervals = Hunk_AllocName (numframes * sizeof (float), loadname);
+
+	paliasgroup->intervals = (byte *)poutintervals - (byte *)pheader;
+
+	for (i=0 ; i<numframes ; i++)
+	{
+		*poutintervals = LittleFloat (pin_intervals->interval);
+		if (*poutintervals <= 0.0)
+			Sys_Error ("Mod_LoadAliasGroup: interval<=0");
+
+		poutintervals++;
+		pin_intervals++;
+	}
+
+	ptemp = (void *)pin_intervals;
+
+	for (i=0 ; i<numframes ; i++)
+	{
+		ptemp = Mod_LoadAliasFrame (ptemp,
+									&paliasgroup->frames[i].frame,
+									numv,
+									&paliasgroup->frames[i].bboxmin,
+									&paliasgroup->frames[i].bboxmax,
+									pheader, name);
+	}
+
+	return ptemp;
+}
+
+
+/*
+=================
+Mod_LoadAliasSkin
+=================
+*/
+void * Mod_LoadAliasSkin (void * pin, int *pskinindex, int skinsize,
+	aliashdr_t *pheader)
+{
+	int		i;
+	byte	*pskin, *pinskin;
+	unsigned short	*pusskin;
+
+	pskin = Hunk_AllocName (skinsize * r_pixbytes, loadname);
+	pinskin = (byte *)pin;
+	*pskinindex = (byte *)pskin - (byte *)pheader;
+
+	if (r_pixbytes == 1)
+	{
+		Q_memcpy (pskin, pinskin, skinsize);
+	}
+	else if (r_pixbytes == 2)
+	{
+		pusskin = (unsigned short *)pskin;
+
+		for (i=0 ; i<skinsize ; i++)
+			pusskin[i] = d_8to16table[pinskin[i]];
+	}
+	else
+	{
+		Sys_Error ("Mod_LoadAliasSkin: driver set invalid r_pixbytes: %d\n",
+				 r_pixbytes);
+	}
+
+	pinskin += skinsize;
+
+	return ((void *)pinskin);
+}
+
+
+/*
+=================
+Mod_LoadAliasSkinGroup
+=================
+*/
+void * Mod_LoadAliasSkinGroup (void * pin, int *pskinindex, int skinsize,
+	aliashdr_t *pheader)
+{
+	daliasskingroup_t		*pinskingroup;
+	maliasskingroup_t		*paliasskingroup;
+	int						i, numskins;
+	daliasskininterval_t	*pinskinintervals;
+	float					*poutskinintervals;
+	void					*ptemp;
+
+	pinskingroup = (daliasskingroup_t *)pin;
+
+	numskins = LittleLong (pinskingroup->numskins);
+
+	paliasskingroup = Hunk_AllocName (sizeof (maliasskingroup_t) +
+			(numskins - 1) * sizeof (paliasskingroup->skindescs[0]),
+			loadname);
+
+	paliasskingroup->numskins = numskins;
+
+	*pskinindex = (byte *)paliasskingroup - (byte *)pheader;
+
+	pinskinintervals = (daliasskininterval_t *)(pinskingroup + 1);
+
+	poutskinintervals = Hunk_AllocName (numskins * sizeof (float),loadname);
+
+	paliasskingroup->intervals = (byte *)poutskinintervals - (byte *)pheader;
+
+	for (i=0 ; i<numskins ; i++)
+	{
+		*poutskinintervals = LittleFloat (pinskinintervals->interval);
+		if (*poutskinintervals <= 0)
+			Sys_Error ("Mod_LoadAliasSkinGroup: interval<=0");
+
+		poutskinintervals++;
+		pinskinintervals++;
+	}
+
+	ptemp = (void *)pinskinintervals;
+
+	for (i=0 ; i<numskins ; i++)
+	{
+		ptemp = Mod_LoadAliasSkin (ptemp,
+				&paliasskingroup->skindescs[i].skin, skinsize, pheader);
+	}
+
+	return ptemp;
+}
+
+
+/*
+=================
+Mod_LoadAliasModel
+=================
+*/
+void Mod_LoadAliasModel (model_t *mod, void *buffer)
+{
+	int					i;
+	mdl_t				*pmodel, *pinmodel;
+	stvert_t			*pstverts, *pinstverts;
+	aliashdr_t			*pheader;
+	mtriangle_t			*ptri;
+	dtriangle_t			*pintriangles;
+	int					version, numframes, numskins;
+	int					size;
+	daliasframetype_t	*pframetype;
+	daliasskintype_t	*pskintype;
+	maliasskindesc_t	*pskindesc;
+	int					skinsize;
+	int					start, end, total;
+	
+	start = Hunk_LowMark ();
+
+	pinmodel = (mdl_t *)buffer;
+
+	version = LittleLong (pinmodel->version);
+	if (version != ALIAS_VERSION)
+		Sys_Error ("%s has wrong version number (%i should be %i)",
+				 mod->name, version, ALIAS_VERSION);
+
+//
+// allocate space for a working header, plus all the data except the frames,
+// skin and group info
+//
+	size = 	sizeof (aliashdr_t) + (LittleLong (pinmodel->numframes) - 1) *
+			 sizeof (pheader->frames[0]) +
+			sizeof (mdl_t) +
+			LittleLong (pinmodel->numverts) * sizeof (stvert_t) +
+			LittleLong (pinmodel->numtris) * sizeof (mtriangle_t);
+
+	pheader = Hunk_AllocName (size, loadname);
+	pmodel = (mdl_t *) ((byte *)&pheader[1] +
+			(LittleLong (pinmodel->numframes) - 1) *
+			 sizeof (pheader->frames[0]));
+	
+//	mod->cache.data = pheader;
+	mod->flags = LittleLong (pinmodel->flags);
+
+//
+// endian-adjust and copy the data, starting with the alias model header
+//
+	pmodel->boundingradius = LittleFloat (pinmodel->boundingradius);
+	pmodel->numskins = LittleLong (pinmodel->numskins);
+	pmodel->skinwidth = LittleLong (pinmodel->skinwidth);
+	pmodel->skinheight = LittleLong (pinmodel->skinheight);
+
+	if (pmodel->skinheight > MAX_LBM_HEIGHT)
+		Sys_Error ("model %s has a skin taller than %d", mod->name,
+				   MAX_LBM_HEIGHT);
+
+	pmodel->numverts = LittleLong (pinmodel->numverts);
+
+	if (pmodel->numverts <= 0)
+		Sys_Error ("model %s has no vertices", mod->name);
+
+	if (pmodel->numverts > MAXALIASVERTS)
+		Sys_Error ("model %s has too many vertices", mod->name);
+
+	pmodel->numtris = LittleLong (pinmodel->numtris);
+
+	if (pmodel->numtris <= 0)
+		Sys_Error ("model %s has no triangles", mod->name);
+
+	pmodel->numframes = LittleLong (pinmodel->numframes);
+	pmodel->size = LittleFloat (pinmodel->size) * ALIAS_BASE_SIZE_RATIO;
+	mod->synctype = LittleLong (pinmodel->synctype);
+	mod->numframes = pmodel->numframes;
+
+	for (i=0 ; i<3 ; i++)
+	{
+		pmodel->scale[i] = LittleFloat (pinmodel->scale[i]);
+		pmodel->scale_origin[i] = LittleFloat (pinmodel->scale_origin[i]);
+		pmodel->eyeposition[i] = LittleFloat (pinmodel->eyeposition[i]);
+	}
+
+	numskins = pmodel->numskins;
+	numframes = pmodel->numframes;
+
+	if (pmodel->skinwidth & 0x03)
+		Sys_Error ("Mod_LoadAliasModel: skinwidth not multiple of 4");
+
+	pheader->model = (byte *)pmodel - (byte *)pheader;
+
+//
+// load the skins
+//
+	skinsize = pmodel->skinheight * pmodel->skinwidth;
+
+	if (numskins < 1)
+		Sys_Error ("Mod_LoadAliasModel: Invalid # of skins: %d\n", numskins);
+
+	pskintype = (daliasskintype_t *)&pinmodel[1];
+
+	pskindesc = Hunk_AllocName (numskins * sizeof (maliasskindesc_t),
+								loadname);
+
+	pheader->skindesc = (byte *)pskindesc - (byte *)pheader;
+
+	for (i=0 ; i<numskins ; i++)
+	{
+		aliasskintype_t	skintype;
+
+		skintype = LittleLong (pskintype->type);
+		pskindesc[i].type = skintype;
+
+		if (skintype == ALIAS_SKIN_SINGLE)
+		{
+			pskintype = (daliasskintype_t *)
+					Mod_LoadAliasSkin (pskintype + 1,
+									   &pskindesc[i].skin,
+									   skinsize, pheader);
+		}
+		else
+		{
+			pskintype = (daliasskintype_t *)
+					Mod_LoadAliasSkinGroup (pskintype + 1,
+											&pskindesc[i].skin,
+											skinsize, pheader);
+		}
+	}
+
+//
+// set base s and t vertices
+//
+	pstverts = (stvert_t *)&pmodel[1];
+	pinstverts = (stvert_t *)pskintype;
+
+	pheader->stverts = (byte *)pstverts - (byte *)pheader;
+
+	for (i=0 ; i<pmodel->numverts ; i++)
+	{
+		pstverts[i].onseam = LittleLong (pinstverts[i].onseam);
+	// put s and t in 16.16 format
+		pstverts[i].s = LittleLong (pinstverts[i].s) << 16;
+		pstverts[i].t = LittleLong (pinstverts[i].t) << 16;
+	}
+
+//
+// set up the triangles
+//
+	ptri = (mtriangle_t *)&pstverts[pmodel->numverts];
+	pintriangles = (dtriangle_t *)&pinstverts[pmodel->numverts];
+
+	pheader->triangles = (byte *)ptri - (byte *)pheader;
+
+	for (i=0 ; i<pmodel->numtris ; i++)
+	{
+		int		j;
+
+		ptri[i].facesfront = LittleLong (pintriangles[i].facesfront);
+
+		for (j=0 ; j<3 ; j++)
+		{
+			ptri[i].vertindex[j] =
+					LittleLong (pintriangles[i].vertindex[j]);
+		}
+	}
+
+//
+// load the frames
+//
+	if (numframes < 1)
+		Sys_Error ("Mod_LoadAliasModel: Invalid # of frames: %d\n", numframes);
+
+	pframetype = (daliasframetype_t *)&pintriangles[pmodel->numtris];
+
+	for (i=0 ; i<numframes ; i++)
+	{
+		aliasframetype_t	frametype;
+
+		frametype = LittleLong (pframetype->type);
+		pheader->frames[i].type = frametype;
+
+		if (frametype == ALIAS_SINGLE)
+		{
+			pframetype = (daliasframetype_t *)
+					Mod_LoadAliasFrame (pframetype + 1,
+										&pheader->frames[i].frame,
+										pmodel->numverts,
+										&pheader->frames[i].bboxmin,
+										&pheader->frames[i].bboxmax,
+										pheader, pheader->frames[i].name);
+		}
+		else
+		{
+			pframetype = (daliasframetype_t *)
+					Mod_LoadAliasGroup (pframetype + 1,
+										&pheader->frames[i].frame,
+										pmodel->numverts,
+										&pheader->frames[i].bboxmin,
+										&pheader->frames[i].bboxmax,
+										pheader, pheader->frames[i].name);
+		}
+	}
+
+	mod->type = mod_alias;
+
+// FIXME: do this right
+	mod->mins[0] = mod->mins[1] = mod->mins[2] = -16;
+	mod->maxs[0] = mod->maxs[1] = mod->maxs[2] = 16;
+
+//
+// move the complete, relocatable alias model to the cache
+//	
+	end = Hunk_LowMark ();
+	total = end - start;
+	
+	Cache_Alloc (&mod->cache, total, loadname);
+	if (!mod->cache.data)
+		return;
+	memcpy (mod->cache.data, pheader, total);
+
+	Hunk_FreeToLowMark (start);
+}
+
+//=============================================================================
+
+/*
+=================
+Mod_LoadSpriteFrame
+=================
+*/
+void * Mod_LoadSpriteFrame (void * pin, mspriteframe_t **ppframe)
+{
+	dspriteframe_t		*pinframe;
+	mspriteframe_t		*pspriteframe;
+	int					i, width, height, size, origin[2];
+	unsigned short		*ppixout;
+	byte				*ppixin;
+
+	pinframe = (dspriteframe_t *)pin;
+
+	width = LittleLong (pinframe->width);
+	height = LittleLong (pinframe->height);
+	size = width * height;
+
+	pspriteframe = Hunk_AllocName (sizeof (mspriteframe_t) + size*r_pixbytes,
+								   loadname);
+
+	Q_memset (pspriteframe, 0, sizeof (mspriteframe_t) + size);
+	*ppframe = pspriteframe;
+
+	pspriteframe->width = width;
+	pspriteframe->height = height;
+	origin[0] = LittleLong (pinframe->origin[0]);
+	origin[1] = LittleLong (pinframe->origin[1]);
+
+	pspriteframe->up = origin[1];
+	pspriteframe->down = origin[1] - height;
+	pspriteframe->left = origin[0];
+	pspriteframe->right = width + origin[0];
+
+	if (r_pixbytes == 1)
+	{
+		Q_memcpy (&pspriteframe->pixels[0], (byte *)(pinframe + 1), size);
+	}
+	else if (r_pixbytes == 2)
+	{
+		ppixin = (byte *)(pinframe + 1);
+		ppixout = (unsigned short *)&pspriteframe->pixels[0];
+
+		for (i=0 ; i<size ; i++)
+			ppixout[i] = d_8to16table[ppixin[i]];
+	}
+	else
+	{
+		Sys_Error ("Mod_LoadSpriteFrame: driver set invalid r_pixbytes: %d\n",
+				 r_pixbytes);
+	}
+
+	return (void *)((byte *)pinframe + sizeof (dspriteframe_t) + size);
+}
+
+
+/*
+=================
+Mod_LoadSpriteGroup
+=================
+*/
+void * Mod_LoadSpriteGroup (void * pin, mspriteframe_t **ppframe)
+{
+	dspritegroup_t		*pingroup;
+	mspritegroup_t		*pspritegroup;
+	int					i, numframes;
+	dspriteinterval_t	*pin_intervals;
+	float				*poutintervals;
+	void				*ptemp;
+
+	pingroup = (dspritegroup_t *)pin;
+
+	numframes = LittleLong (pingroup->numframes);
+
+	pspritegroup = Hunk_AllocName (sizeof (mspritegroup_t) +
+				(numframes - 1) * sizeof (pspritegroup->frames[0]), loadname);
+
+	pspritegroup->numframes = numframes;
+
+	*ppframe = (mspriteframe_t *)pspritegroup;
+
+	pin_intervals = (dspriteinterval_t *)(pingroup + 1);
+
+	poutintervals = Hunk_AllocName (numframes * sizeof (float), loadname);
+
+	pspritegroup->intervals = poutintervals;
+
+	for (i=0 ; i<numframes ; i++)
+	{
+		*poutintervals = LittleFloat (pin_intervals->interval);
+		if (*poutintervals <= 0.0)
+			Sys_Error ("Mod_LoadSpriteGroup: interval<=0");
+
+		poutintervals++;
+		pin_intervals++;
+	}
+
+	ptemp = (void *)pin_intervals;
+
+	for (i=0 ; i<numframes ; i++)
+	{
+		ptemp = Mod_LoadSpriteFrame (ptemp, &pspritegroup->frames[i]);
+	}
+
+	return ptemp;
+}
+
+
+/*
+=================
+Mod_LoadSpriteModel
+=================
+*/
+void Mod_LoadSpriteModel (model_t *mod, void *buffer)
+{
+	int					i;
+	int					version;
+	dsprite_t			*pin;
+	msprite_t			*psprite;
+	int					numframes;
+	int					size;
+	dspriteframetype_t	*pframetype;
+	
+	pin = (dsprite_t *)buffer;
+
+	version = LittleLong (pin->version);
+	if (version != SPRITE_VERSION)
+		Sys_Error ("%s has wrong version number "
+				 "(%i should be %i)", mod->name, version, SPRITE_VERSION);
+
+	numframes = LittleLong (pin->numframes);
+
+	size = sizeof (msprite_t) +	(numframes - 1) * sizeof (psprite->frames);
+
+	psprite = Hunk_AllocName (size, loadname);
+
+	mod->cache.data = psprite;
+
+	psprite->type = LittleLong (pin->type);
+	psprite->maxwidth = LittleLong (pin->width);
+	psprite->maxheight = LittleLong (pin->height);
+	psprite->beamlength = LittleFloat (pin->beamlength);
+	mod->synctype = LittleLong (pin->synctype);
+	psprite->numframes = numframes;
+
+	mod->mins[0] = mod->mins[1] = -psprite->maxwidth/2;
+	mod->maxs[0] = mod->maxs[1] = psprite->maxwidth/2;
+	mod->mins[2] = -psprite->maxheight/2;
+	mod->maxs[2] = psprite->maxheight/2;
+	
+//
+// load the frames
+//
+	if (numframes < 1)
+		Sys_Error ("Mod_LoadSpriteModel: Invalid # of frames: %d\n", numframes);
+
+	mod->numframes = numframes;
+	mod->flags = 0;
+
+	pframetype = (dspriteframetype_t *)(pin + 1);
+
+	for (i=0 ; i<numframes ; i++)
+	{
+		spriteframetype_t	frametype;
+
+		frametype = LittleLong (pframetype->type);
+		psprite->frames[i].type = frametype;
+
+		if (frametype == SPR_SINGLE)
+		{
+			pframetype = (dspriteframetype_t *)
+					Mod_LoadSpriteFrame (pframetype + 1,
+										 &psprite->frames[i].frameptr);
+		}
+		else
+		{
+			pframetype = (dspriteframetype_t *)
+					Mod_LoadSpriteGroup (pframetype + 1,
+										 &psprite->frames[i].frameptr);
+		}
+	}
+
+	mod->type = mod_sprite;
+}
+
+//=============================================================================
+
+/*
+================
+Mod_Print
+================
+*/
+void Mod_Print (void)
+{
+	int		i;
+	model_t	*mod;
+
+	Con_Printf ("Cached models:\n");
+	for (i=0, mod=mod_known ; i < mod_numknown ; i++, mod++)
+	{
+		Con_Printf ("%8p : %s",mod->cache.data, mod->name);
+		if (mod->needload & NL_UNREFERENCED)
+			Con_Printf (" (!R)");
+		if (mod->needload & NL_NEEDS_LOADED)
+			Con_Printf (" (!P)");
+		Con_Printf ("\n");
+	}
+}
+
+