#include <string.h>

#include "engine.h"
#include "multi.h"

#include "typedefs.h"
#include "misc.h"
#include "gameutil.h"
#include "trig.h"
#include "view.h"
#include "globals.h"
#include "textio.h"
#include "names.h"
#include "db.h"
#include "debug4g.h"
#include "resource.h"
#include "error.h"
#include "player.h"
#include "screen.h"
#include "options.h"
#include "tile.h"
#include "levels.h"
#include "actor.h"
#include "sectorfx.h"
#include "mirrors.h"
#include "fire.h"
#include "weapon.h"
#include "map2d.h"
#include "gfx.h"

#include <memcheck.h>

#define kStatBarHeight	25

/***********************************************************************
 * Variables
 **********************************************************************/
int gViewIndex = 0;
int gViewSize = 1;
int gViewMode = kView3D;
VIEWPOS gViewPos = kViewPosCenter;
int gZoom = 1024;
int gViewX0, gViewY0, gViewX1, gViewY1;
int gViewXCenter, gViewYCenter;
static long messageTime = 0;
static char message[256];

static int pcBackground = 0;

int gShowFrameRate = FALSE;
int gShowFrags = FALSE;

long gScreenTilt = 0;

int deliriumTilt = 0;
int deliriumTurn = 0;
int deliriumPitch = 0;

struct PLOCATION
{
	int x, y, z;
	int bobHeight, bobWidth;
	int swayHeight, swayWidth;
	int horiz;
	short ang;
} gPrevPlayerLoc[kMaxPlayers];

struct LOCATION
{
	int x, y, z;
	short ang;
} gPrevSpriteLoc[kMaxSprites];

int gInterpolate;

int gViewPosAngle[] =
{
	0,						// kViewPosCenter
	kAngle180,				// kViewPosBack
	kAngle180 - kAngle45,	// kViewPosLeftBack
	kAngle90,				// kViewPosLeft
	kAngle45,				// kViewPosLeftFront
	0,						// kViewPosFront
	-kAngle45,				// kViewPosRightFront
	-kAngle90,				// kViewPosRight
	-kAngle90 - kAngle45,	// kViewPosRightBack
};


void viewDrawChar( QFONT *pFont, BYTE c, int x, int y, BYTE *pPalookup )
{
	dassert(pFont != NULL);
	y += pFont->baseline;

	CHARINFO *pInfo = &pFont->info[c];
	int sizeX = pInfo->cols;
	int sizeY = pInfo->rows;
	int nSize = sizeX * sizeY;
	if ( nSize == 0 )
		return;

	Rect dest(x, y + pInfo->voffset, x + sizeX, y + sizeY + pInfo->voffset);
	Rect screen(0, 0, xdim, ydim);
	dest &= screen;

	if ( !dest )
		return;

	BYTE *pSource = &pFont->data[pInfo->offset];

	for (int i = 0; i < 4; i++)
	{
		palookupoffse[i] = pPalookup;
		vince[i] = 0x00010000;
	}

 	BYTE *bufplc = pSource;
	BYTE *p = frameplace + ylookup[dest.y0] + dest.x0;

	x = dest.x0;

	// dword align to video memory
	while (x < dest.x1 && (x & 3) )
	{
		mvlineasm1(0x00010000, pPalookup, sizeY - 1, 0, bufplc, p);
		bufplc += sizeY;
		p++;
		x++;
	}

	while ( x + 3 < dest.x1 )
	{
		for (i = 0; i < 4; i++)
		{
			bufplce[i] 	= bufplc;
			bufplc += sizeY;
			vplce[i] = 0;
		}
		mvlineasm4(sizeY, p);
		p += 4;
		x += 4;
	}

	while ( x < dest.x1 )
	{
		mvlineasm1(0x00010000, pPalookup, sizeY - 1, 0, bufplc, p);
		bufplc += sizeY;
		p++;
		x++;
	}
}


void viewDrawText( FONT_ID nFontId, char *string, int x, int y, int shade, int nPLU, int nAlign )
{
	char *s;
	dassert(string != NULL);

	RESHANDLE hFont = gSysRes.Lookup(nFontId, ".QFN");
	dassert(hFont != NULL);
	QFONT *pFont = (QFONT *)gSysRes.Lock(hFont);

	BYTE *pPalookup = palookup[nPLU] + (getpalookup(0, shade) << 8);
	setupmvlineasm(16);

	if ( nAlign != TA_LEFT )
	{
		int nWidth = -pFont->charSpace;
		for ( s = string; *s; s++ )
			nWidth += pFont->info[*s].hspace + pFont->charSpace;

		if ( nAlign == TA_CENTER )
			nWidth >>= 1;

		x -= nWidth;
	}

	for ( s = string; *s; s++ )
	{
		viewDrawChar(pFont, *s, x, y, pPalookup);
		x += pFont->info[*s].hspace + pFont->charSpace;
	}

	gSysRes.Unlock(hFont);
}




static void TileSprite( int nTile, int shade, int nPLU, int x0, int y0, int x1, int y1)
{
	long x, y, i;

	Rect clip(x0, y0, x1, y1);
	Rect screen(0, 0, xdim, ydim);
	clip &= screen;

	if ( !clip)
		return;

	dassert(nTile >= 0 && nTile < kMaxTiles);
	int sizeX = tilesizx[nTile];
	int sizeY = tilesizy[nTile];
	int nSize = sizeX * sizeY;
	if ( nSize == 0 )
		return;

	BYTE *palookupoffs = palookup[nPLU] + (getpalookup(0, shade) << 8);
	BYTE *pTile = tileLoadTile(nTile);
	BYTE *wrap = pTile + nSize;

	setupvlineasm(16);

	for (i = 0; i < 4; i++)
	{
		palookupoffse[i] = palookupoffs;
		vince[i] = 0x00010000;
	}

	int yNext;
	for (y = clip.y0; y < clip.y1; y = yNext)
	{
		yNext = ClipHigh(IncBy(y, sizeY), clip.y1);
		x = clip.x0;

		BYTE *bufplc = pTile + (x % sizeX) * sizeY + (y % sizeY);
		BYTE *p = frameplace + ylookup[y] + x;

		// dword align to video memory
		while (x < clip.x1 && (x & 3) )
		{
			vlineasm1(0x00010000, palookupoffs, yNext - y - 1, 0, bufplc, p);
			bufplc += sizeY;
			if (bufplc >= wrap) bufplc -= nSize;
			p++;
			x++;
		}

		while ( x + 3 < clip.x1 )
		{
			for (i = 0; i < 4; i++)
			{
				bufplce[i] 	= bufplc;
				bufplc += sizeY;
				if (bufplc >= wrap) bufplc -= nSize;
				vplce[i] = 0;
			}
			vlineasm4(yNext - y, p);
			p += 4;
			x += 4;
		}

		while ( x < clip.x1 )
		{
			vlineasm1(0x00010000, palookupoffs, yNext - y - 1, 0, bufplc, p);
			bufplc += sizeY;
			if (bufplc >= wrap) bufplc -= nSize;
			p++;
			x++;
		}
	}
}


static void ClipSprite( int x, int y, int nTile, int shade, int nPLU, int x0, int y0, int x1, int y1 )
{
	Rect clip(x0, y0, x1, y1);
	Rect screen(0, 0, xdim, ydim);
	clip &= screen;

	if ( !clip)
		return;

	dassert(nTile >= 0 && nTile < kMaxTiles);
	int sizeX = tilesizx[nTile];
	int sizeY = tilesizy[nTile];
	int nSize = sizeX * sizeY;
	if ( nSize == 0 )
		return;

	Rect dest(x, y, x + sizeX, y + sizeY);
	dest &= clip;

	if ( !dest )
		return;

	Rect source(dest);
	source.offset(-x, -y);

	BYTE *palookupoffs = palookup[nPLU] + (getpalookup(0, shade) << 8);
	BYTE *pTile = tileLoadTile(nTile);

	setupvlineasm(16);

	for (int i = 0; i < 4; i++)
	{
		palookupoffse[i] = palookupoffs;
		vince[i] = 0x00010000;
	}

	BYTE *bufplc = pTile + (source.x0 % sizeX) * sizeY + (source.y0 % sizeY);
	BYTE *p = frameplace + ylookup[dest.y0] + dest.x0;

	x = dest.x0;

	// dword align to video memory
	while (x < dest.x1 && (x & 3) )
	{
		vlineasm1(0x00010000, palookupoffs, source.rows() - 1, 0, bufplc, p);
		bufplc += sizeY;
		p++;
		x++;
	}

	while ( x + 3 < dest.x1 )
	{
		for (i = 0; i < 4; i++)
		{
			bufplce[i] 	= bufplc;
			bufplc += sizeY;
			vplce[i] = 0;
		}
		vlineasm4(source.rows(), p);
		p += 4;
		x += 4;
	}

	while ( x < dest.x1 )
	{
		vlineasm1(0x00010000, palookupoffs, source.rows() - 1, 0, bufplc, p);
		bufplc += sizeY;
		p++;
		x++;
	}
}


void InitStatusBar( void )
{
	tileLoadTile(kPicStatBar);
}


static void DrawStatSprite( int nTile, int x, int y, int nShade = 0, int nPLU = kPLUNormal )
{
	rotatesprite(x << 16, y << 16, 0x10000, 0, (short)nTile, (schar)nShade, (char)nPLU,
		kRotateStatus | kRotateNoMask, 0, 0, xdim-1, ydim-1);
}


static void DrawStatMaskedSprite( int nTile, int x, int y, int nShade = 0, int nPLU = kPLUNormal )
{
	rotatesprite(x << 16, y << 16, 0x10000, 0, (short)nTile, (schar)nShade, (char)nPLU,
		kRotateStatus, 0, 0, xdim-1, ydim-1);
}


static void DrawStatNumber( char *sFormat, int n, int nTile, int x, int y, int xSpace )
{
	char buffer[80];
	sprintf(buffer, sFormat, n);
	for (int i = 0; i < strlen(buffer); i++)
	{
		if ( buffer[i] != ' ' )
			DrawStatSprite(nTile + buffer[i] - '0', x + i * xSpace, y);
	}
}


static void TileHGauge( int nTile, int x, int y, int n, int total )
{
	int nGauge = n * tilesizx[nTile] / total;

	ClipSprite(x, y, nTile, 0, 0, 0, 0, x + nGauge, ydim);
}


static void UpdateStatusBar(void)
{
	SPRITE *pSprite = gView->sprite;
	XSPRITE *pXSprite = gView->xsprite;
	int i;

	if (gViewMode == kView2D || gViewSize > 0)		// status bar present?
	{
		DrawStatMaskedSprite(2200, 160, 178);	// status bar
		DrawStatMaskedSprite(2234, 296, 179);	// heart

		int nHealthTile = 2231;	// green
		if ( pXSprite->health < (80 << 4) )
			nHealthTile = 2232;	// yellow
		if ( pXSprite->health < (40 << 4) )
			nHealthTile = 2227;	// red

		// show health amount
		DrawStatNumber("%3d", pXSprite->health >> 4, 2250, 157, 182, 5);

		TileHGauge(nHealthTile, 174, 180, pXSprite->health, 100 << 4);	// health meter
		TileHGauge(2226, 221, 192, gView->bloodlust, 100 << 4);	// lust

		// draw keys
		for (i = 0; i < 6; i++)
		{
			if ( gView->hasKey[i+1] )
				DrawStatSprite(2220 + i, 156 + i * 10, 193);
			else
				DrawStatSprite(2220 + i, 156 + i * 10, 193, 40, kPLUGrayish);
		}

		if ( gView->weapon != 0 )
		{
			DrawStatMaskedSprite(2229, 38 + gView->weapon * 13, 185);	// highlight

			// show ammo count
			if ( gView->weaponAmmo != kAmmoNone )
				DrawStatNumber("%3d", gView->ammoCount[gView->weaponAmmo], 2190, 13, 180, 9);
		}

		// display level name
		if ( gViewMode == kView2D )
			gfxDrawText(0, 160, gStdColor[kColorWhite], gLevelDescription);
	}

	if (gCacheMiss)
	{
		DrawStatSprite(kPicDisk, xdim - 15, ydim - 15);
		gCacheMiss = ClipLow(gCacheMiss - kFrameTicks, 0);
	}
}


void viewInit(void)
{
	gViewXCenter = xdim / 2;
	gViewYCenter = ydim / 2;

	gViewSize 		= BloodINI.GetKeyInt("View", "Size", 1);

	tioPrint("Initializing status bar");
	InitStatusBar();
}


void viewResizeView(int size)
{
	//int x0, y0, x1, y1;

	gViewSize = ClipRange(size, 0, 8);

	// full screen mode
	if (gViewSize == 0)
	{
		gViewX0 = 0;
		gViewY0 = 0;
		gViewX1 = xdim - 1;
		gViewY1 = ydim - 1;

		setview(gViewX0, gViewY0, gViewX1, gViewY1);
		return;
	}

	gViewX0 = 0;
	gViewY0 = 0;
	gViewX1 = xdim - 1;
	gViewY1 = ydim - kStatBarHeight;

	gViewX0 += (gViewSize - 1) * xdim / 16;
	gViewX1 -= (gViewSize - 1) * xdim / 16;
	gViewY0 += (gViewSize - 1) * ydim / 16;
	gViewY1 -= (gViewSize - 1) * ydim / 16;

	setview(gViewX0, gViewY0, gViewX1, gViewY1);

	pcBackground = numpages;
}


#define kBackTile 230
void viewDrawInterface( void )
{
	int x0, y0, x1, y1;

	if (gViewMode == kView3D && gViewSize > 1)
	{
		if ( pcBackground )
		{
			x0 = gViewX0;
			y0 = gViewY0;
			x1 = gViewX1;
			y1 = gViewY1;

			TileSprite(kBackTile, 20, kPLUNormal, 0, 0, xdim, y0-3); 		// top
			TileSprite(kBackTile, 20, kPLUNormal, 0, y1+4, xdim, ydim);	// bottom
			TileSprite(kBackTile, 20, kPLUNormal, 0, y0-3, x0-3, y1+4);		// left
			TileSprite(kBackTile, 20, kPLUNormal, x1+4, y0-3, xdim, y1+4);	// right

			TileSprite(kBackTile, 40, kPLUNormal, x0-3, y0-3, x0, y1+1);		// left
			TileSprite(kBackTile, 40, kPLUNormal, x0, y0-3, x1+4, y0); 		// top
			TileSprite(kBackTile, 0, kPLUNormal, x1+1, y0, x1+4, y1+4);		// right
			TileSprite(kBackTile, 0, kPLUNormal, x0-3, y1+1, x1+1, y1+4);		// bottom

			pcBackground--;
		}
	}

	UpdateStatusBar();

	powerupDraw( gView );
}


#if 0
short viewInsertTSprite( short nSector, short nStatus )
{
	short nTSprite = -1;

	nTSprite = (short)spritesortcnt;
	SPRITE *pTSprite = &tsprite[nTSprite];
	memset(pTSprite, 0, sizeof(SPRITE));
	pTSprite->type = kNothing;
	pTSprite->sectnum = nSector;
	pTSprite->statnum = nStatus;
	pTSprite->cstat = kSpriteOriginAlign;
	pTSprite->xrepeat = 64;
	pTSprite->yrepeat = 64;
	pTSprite->owner = -1;
	pTSprite->extra = -1;
	spritesortcnt++;

	return nTSprite;
}
#endif

static SPRITE *viewInsertTSprite( short nSector, short nStatus, SPRITE *pSource = NULL );

static SPRITE *viewInsertTSprite( short nSector, short nStatus, SPRITE *pSource )
{
	short nTSprite = -1;

	nTSprite = (short)spritesortcnt;
	SPRITE *pTSprite = &tsprite[nTSprite];
	memset(pTSprite, 0, sizeof(SPRITE));
	pTSprite->type = (short)-spritesortcnt;			// negated temporary tsprite index - cleared after display
	pTSprite->sectnum = nSector;
	pTSprite->statnum = nStatus;
	pTSprite->cstat = kSpriteOriginAlign;
	pTSprite->xrepeat = 64;
	pTSprite->yrepeat = 64;
	pTSprite->owner = -1;
	pTSprite->extra = -1;
	spritesortcnt++;

	if ( pSource != NULL )
	{
		pTSprite->x = pSource->x;
		pTSprite->y = pSource->y;
		pTSprite->z = pSource->z;
		pTSprite->owner = pSource->owner;
		pTSprite->ang = pSource->ang;
	}

	return &tsprite[nTSprite];
}


enum VIEW_EFFECT {
	kViewEffectShadow = 0,
	kViewEffectFlareHalo,
	kViewEffectCeilGlow,
	kViewEffectFloorGlow,
	kViewEffectTorchHigh,
	kViewEffectTorchLow,
	kViewEffectSmokeHigh,
	kViewEffectSmokeLow,
	kViewEffectFlame,
	kViewEffectSpear,
	kViewEffectBloodSpray,
	kViewEffectPhase,

	kViewEffectMax,
};

int effectDetail[ kViewEffectMax ] =
{
	kDetailLevelMax,	// kViewEffectShadow
	kDetailLevelMax,	// kViewEffectFlareHalo
	kDetailLevelMax,	// kViewEffectCeilGlow,
	kDetailLevelMax,	// kViewEffectFloorGlow,
	kDetailLevelMin,	// kViewEffectTorchHigh
	kDetailLevelMin,	// kViewEffectTorchLow
	kDetailLevelMin,	// kViewEffectSmokeHigh
	kDetailLevelMin,	// kViewEffectSmokeLow
	kDetailLevelMin,	// kViewEffectFlame
	kDetailLevelMin,	// kViewEffectSpear
	kDetailLevel2,		// kViewEffectBloodSpray
};


static void viewAddEffect( int nTSprite, VIEW_EFFECT nViewEffect )
{
	dassert( nViewEffect >= 0 && nViewEffect < kViewEffectMax );
	SPRITE *pTSprite = &tsprite[nTSprite];

	if ( gDetail < effectDetail[nViewEffect] )	// skip effects at higher detail levels
		return;

	// can more tsprites be inserted?
	if ( spritesortcnt < kMaxViewSprites )
	{
		switch (nViewEffect)
		{
			case kViewEffectPhase:
			{
				int nAngle = pTSprite->ang;

				// remember: things like butcher knive sprites are already +kAngle90 because they are wall sprites
				if (pTSprite->cstat & kSpriteWall)
					 nAngle = (nAngle + kAngle90) & kAngleMask;
				else
					 nAngle = (nAngle + kAngle180) & kAngleMask;

				for (int i=0; i<5 && spritesortcnt < kMaxViewSprites; i++)
				{
					short nSector;
					SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF );
					pTEffect->ang = pTSprite->ang;
					pTEffect->x = pTSprite->x + mulscale30( (i+1) << 7, Cos(nAngle));
					pTEffect->y = pTSprite->y + mulscale30( (i+1) << 7, Sin(nAngle));
					pTEffect->z = pTSprite->z;
			        FindSector(pTEffect->x, pTEffect->y, pTEffect->z, &nSector);
					pTEffect->sectnum = nSector;
					pTEffect->owner = pTSprite->owner;
					pTEffect->picnum = pTSprite->picnum;
					pTEffect->cstat = (uchar)(pTSprite->cstat & ~kSpriteBlocking);
					pTEffect->cstat |= kSpriteTranslucent;
					if (i < 2)
						pTEffect->cstat |= kSpriteTranslucentR;
					pTEffect->shade = (schar)ClipLow(pTSprite->shade - 16, -128);
					pTEffect->xrepeat = pTSprite->xrepeat;
					pTEffect->yrepeat = pTSprite->yrepeat;
					pTEffect->picnum = pTSprite->picnum;
				}
				break;
			}
			case kViewEffectFlame:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				pTEffect->z = pTSprite->z;
				pTEffect->cstat = (uchar)(pTSprite->cstat & ~kSpriteBlocking);
				pTEffect->shade = kMaxShade;
				pTEffect->xrepeat = pTEffect->yrepeat =
					(uchar)(tilesizx[pTSprite->picnum] * pTSprite->xrepeat / 64);
				pTEffect->picnum = kAnmFlame3;
				pTEffect->statnum = kStatDefault; // show up in front of burning objects
				break;
			}

			case kViewEffectSmokeHigh:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				int zTop, zBot;
				GetSpriteExtents(pTSprite, &zTop, &zBot);
				pTEffect->z = zTop;
				if ( IsDudeSprite(pTSprite->owner)  )
					pTEffect->picnum = kAnmSmoke1;
				else
					pTEffect->picnum = kAnmSmoke2;
				pTEffect->cstat = (short)(pTSprite->cstat | kSpriteTranslucent & ~kSpriteBlocking);
				pTEffect->shade = 8;
				pTEffect->xrepeat = pTSprite->xrepeat;
				pTEffect->yrepeat = pTSprite->yrepeat;
				break;
			}

			case kViewEffectSmokeLow:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				int zTop, zBot;
				GetSpriteExtents(pTSprite, &zTop, &zBot);
				pTEffect->z = zBot;
				if ( IsDudeSprite(pTSprite->owner)  )
					pTEffect->picnum = kAnmSmoke1;
				else
					pTEffect->picnum = kAnmSmoke2;
				pTEffect->cstat = (short)(pTSprite->cstat | kSpriteTranslucent & ~kSpriteBlocking);
				pTEffect->shade = 8;
				pTEffect->xrepeat = pTSprite->xrepeat;
				pTEffect->yrepeat = pTSprite->yrepeat;
				break;
			}

			case kViewEffectTorchHigh:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				int zTop, zBot;
				GetSpriteExtents(pTSprite, &zTop, &zBot);
				pTEffect->z = zTop;
				pTEffect->picnum = kAnmFlame2;
				pTEffect->cstat = (short)(pTSprite->cstat & ~kSpriteBlocking);
				pTEffect->shade = kMaxShade;
				pTEffect->xrepeat = pTEffect->yrepeat =
					(uchar)(tilesizx[pTSprite->picnum] * pTSprite->xrepeat / 32);
				break;
			}

			case kViewEffectTorchLow:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				int zTop, zBot;
				GetSpriteExtents(pTSprite, &zTop, &zBot);
				pTEffect->z = zBot;
				pTEffect->picnum = kAnmFlame2;
				pTEffect->cstat = (short)(pTSprite->cstat & ~kSpriteBlocking);
				pTEffect->shade = kMaxShade;
				pTEffect->xrepeat = pTEffect->yrepeat =
					(uchar)(tilesizx[pTSprite->picnum] * pTSprite->xrepeat / 32);
				break;
			}

			case kViewEffectShadow:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				// insert a shadow
				pTEffect->z = getflorzofslope(pTSprite->sectnum, pTEffect->x, pTEffect->y);

				pTEffect->cstat = (uchar)(pTSprite->cstat | kSpriteTranslucent);
				pTEffect->shade = kMinShade;
				pTEffect->xrepeat = pTSprite->xrepeat;
				pTEffect->yrepeat = (uchar)(pTSprite->yrepeat >> 2);
				pTEffect->picnum = pTSprite->picnum;
				int nTile = pTEffect->picnum;

				// position it so it's based on the floor
				pTEffect->z -= (tilesizy[nTile] - (tilesizy[nTile] / 2 + picanm[nTile].ycenter)) * (pTEffect->yrepeat << 2);
				break;
			}

			case kViewEffectFlareHalo:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				// insert a lens flare halo effect around flare missiles
				pTEffect->z = pTSprite->z;
				pTEffect->cstat = (uchar)(pTSprite->cstat | kSpriteTranslucent);
				pTEffect->shade = kMaxShade;
				pTEffect->pal = kPLURed;
				pTEffect->xrepeat = pTSprite->xrepeat;
				pTEffect->yrepeat = pTSprite->yrepeat;
				pTEffect->picnum = kAnmFlareHalo;
				break;
			}

			case kViewEffectCeilGlow:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				SECTOR *pSector = &sector[pTSprite->sectnum];
				int zDist = (pTSprite->z - pSector->ceilingz) >> 8;
				pTEffect->x = pTSprite->x;
				pTEffect->y = pTSprite->y;
				pTEffect->z = pSector->ceilingz;
				pTEffect->cstat = (uchar)(pTSprite->cstat | kSpriteTranslucent | kSpriteFloor | kSpriteOneSided | kSpriteFlipY);
				pTEffect->shade = (schar)(-64 + zDist);
				pTEffect->pal = kPLURed;
				pTEffect->xrepeat = 64;
				pTEffect->yrepeat = 64;
				pTEffect->picnum = kAnmGlowSpot1;
				pTEffect->ang = pTSprite->ang;
				pTEffect->owner = pTSprite->owner;	//(short)nTSprite;
				break;
			}

			case kViewEffectFloorGlow:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				SECTOR *pSector = &sector[pTSprite->sectnum];
				int zDist = (pSector->floorz - pTSprite->z) >> 8;
				pTEffect->x = pTSprite->x;
				pTEffect->y = pTSprite->y;
				pTEffect->z = pSector->floorz;
				pTEffect->cstat = (uchar)(pTSprite->cstat | kSpriteTranslucent | kSpriteFloor | kSpriteOneSided);
				pTEffect->shade = (schar)(-32 + zDist);
				pTEffect->pal = kPLURed;
				pTEffect->xrepeat = (uchar)zDist;
				pTEffect->yrepeat = (uchar)zDist;
				pTEffect->picnum = kAnmGlowSpot1;
				pTEffect->ang = pTSprite->ang;
				pTEffect->owner = pTSprite->owner;	//(short)nTSprite;
				break;
			}

			case kViewEffectSpear:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				pTEffect->z = pTSprite->z;
				pTEffect->cstat = (uchar)((pTSprite->cstat & ~kSpriteRMask) | kSpriteFloor);
				pTEffect->shade = pTSprite->shade;
				pTEffect->xrepeat = pTSprite->xrepeat;
				pTEffect->yrepeat = pTSprite->yrepeat;
				pTEffect->picnum = pTSprite->picnum;
				pTSprite->ang += kAngle180;
				pTSprite->ang &= kAngleMask;
				break;
			}

			case kViewEffectBloodSpray:
			{
				SPRITE *pTEffect = viewInsertTSprite( pTSprite->sectnum, 0x7FFF, pTSprite);
				pTEffect->z = pTSprite->z;
				pTEffect->cstat = (uchar)(pTSprite->cstat & ~kSpriteBlocking);
				if (gDetail > kDetailLevel2)
					pTEffect->cstat |= kSpriteTranslucent | kSpriteTranslucentR;
				pTEffect->shade = (schar)ClipLow((int)(pTSprite->shade - 32), -128);
				pTEffect->xrepeat = pTSprite->xrepeat;
				pTEffect->yrepeat = 64;
				pTEffect->picnum = kAnmBloodSpray;
				break;
			}
		}
	}
}


void viewProcessSprites( int nViewX, int nViewY, int nViewZ )
{
	int nTSprite, nOctant;
	short nXSprite;
	long dx, dy;

	// count down so we don't process shadows
	for (nTSprite = spritesortcnt - 1; nTSprite >= 0; nTSprite--)
	{
		SPRITE *pTSprite = &tsprite[nTSprite];
		XSPRITE *pTXSprite = NULL;
		nXSprite = pTSprite->extra;

		if ( nXSprite > 0 )
			pTXSprite = &xsprite[nXSprite];

		// KEN'S CODE TO SHADE SPRITES
		// Add relative shading code for textures, and possibly sector cstat bit to
		// indicate where shading comes from (i.e. shadows==floorshade, sky==ceilingshade)
		// ALSO: This code does not affect sprite effects added to the tsprite list

		pTSprite->shade = (schar)ClipRange( pTSprite->shade + 6, -128, 127 );
		if ( (sector[pTSprite->sectnum].ceilingstat & 1)
		&& !(sector[pTSprite->sectnum].floorstat & kSectorFloorShade) )
			pTSprite->shade = (schar)ClipRange( pTSprite->shade + sector[pTSprite->sectnum].ceilingshade, -128, 127 );
		else
			pTSprite->shade = (schar)ClipRange( pTSprite->shade + sector[pTSprite->sectnum].floorshade, -128, 127 );

		int nTile = pTSprite->picnum;
		if (nTile < 0 || nTile >= kMaxTiles)
		{
			dprintf("tsprite[].cstat = %i\n", pTSprite->cstat);
			dprintf("tsprite[].shade = %i\n", pTSprite->shade);
			dprintf("tsprite[].pal = %i\n", pTSprite->pal);
			dprintf("tsprite[].picnum = %i\n", pTSprite->picnum);
			dprintf("tsprite[].ang = %i\n", pTSprite->ang);
			dprintf("tsprite[].owner = %i\n", pTSprite->owner);
			dprintf("tsprite[].sectnum = %i\n", pTSprite->sectnum);
			dprintf("tsprite[].statnum = %i\n", pTSprite->statnum);
			dprintf("tsprite[].type = %i\n", pTSprite->type);
			dprintf("tsprite[].flags = %i\n", pTSprite->flags);
			dprintf("tsprite[].extra = %i\n", pTSprite->extra);
			dassert(nTile >= 0 && nTile < kMaxTiles);
		}

		// only interpolate certain moving things
		if ( pTSprite->flags & kAttrMove )
		{
			long x = gPrevSpriteLoc[pTSprite->owner].x;
			long y = gPrevSpriteLoc[pTSprite->owner].y;
			long z = gPrevSpriteLoc[pTSprite->owner].z;
			short nAngle = gPrevSpriteLoc[pTSprite->owner].ang;

			// interpolate sprite position
			x += mulscale16(pTSprite->x - gPrevSpriteLoc[pTSprite->owner].x, gInterpolate);
			y += mulscale16(pTSprite->y - gPrevSpriteLoc[pTSprite->owner].y, gInterpolate);
			z += mulscale16(pTSprite->z - gPrevSpriteLoc[pTSprite->owner].z, gInterpolate);
			nAngle += mulscale16(((pTSprite->ang - gPrevSpriteLoc[pTSprite->owner].ang + kAngle180) & kAngleMask) - kAngle180, gInterpolate);

			pTSprite->x = x;
			pTSprite->y = y;
			pTSprite->z = z;
			pTSprite->ang = nAngle;
		}

		int nFrames = picanm[nTile].frames + 1;

		switch ( picanm[nTile].view )
		{
			case kSpriteViewSingle:

				if (nXSprite > 0)
				{
					dassert(nXSprite < kMaxXSprites);
					switch ( pTSprite->type )
					{
						case kSwitchToggle:
						case kSwitchMomentary:
							if ( xsprite[nXSprite].state )
								pTSprite->picnum = (short)(nTile + nFrames);
								break;

						case kSwitchCombination:
							pTSprite->picnum = (short)(nTile + xsprite[nXSprite].data1 * nFrames);
							break;

					}
				}
				break;

			case kSpriteView5Full:

				// Calculate which of the 8 angles of the sprite to draw (0-7)
				dx = nViewX - pTSprite->x;
				dy = nViewY - pTSprite->y;
				RotateVector(&dx, &dy, -pTSprite->ang + kAngle45 / 2);
				nOctant = GetOctant(dx, dy);

				if (nOctant <= 4)
				{
					pTSprite->picnum = (short)(nTile + nOctant);
					pTSprite->cstat &= ~4;   // clear x-flipping bit
				}
				else
				{
					pTSprite->picnum = (short)(nTile + 8 - nOctant);
					pTSprite->cstat |= 4;   // set x-flipping bit
				}
				break;

			case kSpriteView8Full:
				// Calculate which of the 8 angles of the sprite to draw (0-7)
				dx = nViewX - pTSprite->x;
				dy = nViewY - pTSprite->y;
				RotateVector(&dx, &dy, -pTSprite->ang + kAngle45 / 2);
				nOctant = GetOctant(dx, dy);

				pTSprite->picnum = (short)(nTile + nOctant);
				break;

			case kSpriteView5Half:
				break;
		}

		if ( spritesortcnt < kMaxViewSprites )
		{
			if (pTSprite->statnum == kStatDude || pTSprite->statnum == kStatThing || pTSprite->statnum == kStatMissile)
			{
				if ( pTXSprite && actGetBurnTime(pTXSprite) > 0 )
					viewAddEffect( nTSprite, kViewEffectSmokeLow );

				if ( pTSprite->flags & kAttrSmoke )
					viewAddEffect(nTSprite, kViewEffectSmokeHigh);

				if (powerupCheck(gView, kItemRoseGlasses - kItemBase) > 0 && pTSprite->statnum == kStatDude)
					pTSprite->shade = kMaxShade;

				if (pTSprite->type >= kDudePlayer1 && pTSprite->type <= kDudePlayer8)
				{
					PLAYER *pPlayer = &gPlayer[pTSprite->type - kDudePlayer1];

					if ( powerupCheck(pPlayer, kItemLtdInvisibility - kItemBase) > 0
					&& powerupCheck(gView, kItemRoseGlasses - kItemBase) == 0 )
					{
						pTSprite->cstat |= kSpriteTranslucent; // kSpriteInvisible;
						pTSprite->pal = kPLUGray;
					}
					// if not invisible, show the temporary player ID
					if ( !(pTSprite->cstat & kSpriteTranslucent) )
						pTSprite->pal = (uchar )(kPLUPlayer1 + ( pPlayer->teamID & 3 ));
				}

				switch (pTSprite->type)
				{
					case kMissileButcherKnife:
						viewAddEffect( nTSprite, kViewEffectPhase );
						break;

					case kMissileSpear:
						viewAddEffect( nTSprite, kViewEffectSpear );
						break;

					case kMissileFlare:
					{
						viewAddEffect( nTSprite, kViewEffectFlareHalo );
						SECTOR *pSector = &sector[pTSprite->sectnum];
						int zDist = (pTSprite->z - pSector->ceilingz) >> 8;
						if ( !(pSector->ceilingstat & kSectorParallax) && zDist < 64 )
							viewAddEffect( nTSprite, kViewEffectCeilGlow );
						zDist = (pSector->floorz - pTSprite->z) >> 8;
						if ( !(pSector->floorstat & kSectorParallax) && zDist < 64 )
							viewAddEffect( nTSprite, kViewEffectFloorGlow );
						break;
					}

					case kThingTNTStick:
						if ( gSpriteHit[nXSprite].floorHit == 0 )
						{
							pTSprite->picnum = 2036;
							viewAddEffect(nTSprite, kViewEffectShadow);
						}
						break;

					case kThingTNTBundle:
						if ( gSpriteHit[nXSprite].floorHit == 0 )
						{
							pTSprite->picnum = 2028;
							viewAddEffect(nTSprite, kViewEffectShadow);
						}
						break;

					default:
						// only add shadow if will be below the view z
						if ( getflorzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) >= nViewZ)
						{
							// don't add to kThings already on the ground
							if (pTSprite->type >= kThingBase && pTSprite->type < kThingMax)
							{
								if ( gSpriteHit[nXSprite].floorHit != 0 )
									break;
							}
							viewAddEffect( nTSprite, kViewEffectShadow );
						}
						break;
				}
			}
			else switch ( pTSprite->type )
			{
				case kMiscTorch:
					if ( pTXSprite )
					{
						if ( pTXSprite->state > 0 )
						{
							pTSprite->picnum++;
							viewAddEffect( nTSprite, kViewEffectTorchHigh );
						} else {
							viewAddEffect( nTSprite, kViewEffectSmokeHigh );
						}
					}
					else
					{
						pTSprite->picnum++;
						viewAddEffect( nTSprite, kViewEffectTorchHigh );
					}
					break;
				case kMiscHangingTorch:
					if ( pTXSprite )
					{
						if ( pTXSprite->state > 0 )
						{
							pTSprite->picnum++;
							viewAddEffect( nTSprite, kViewEffectTorchLow );
						} else {
							viewAddEffect( nTSprite, kViewEffectSmokeLow );
						}
					}
					else
					{
						pTSprite->picnum++;
						viewAddEffect( nTSprite, kViewEffectTorchLow );
					}
					break;
				case kTrapSawBlade:
					if ( pTXSprite->state )
					{
						if ( pTXSprite->data1 )	// has been bloodied
						{
							pTSprite->picnum = kAnmCircSaw2;
							if ( pTXSprite->data2 )
								viewAddEffect( nTSprite, kViewEffectBloodSpray );
						}
					}
					else
					{
						if ( pTXSprite->data1 )	// this blade has been bloodied
							pTSprite->picnum = kAnmCircSaw2 + 1;
						else
							pTSprite->picnum = kAnmCircSaw1 + 1;
					}
					break;
			}
		}
	}
}


#define kViewDistance	(80 << 4)
extern void viewCalcPosition( SPRITE *pSprite, long *px, long *py, long *pz, short *pAngle, short *pSector )
{
	int nAngle = (*pAngle + gViewPosAngle[gViewPos]) & kAngleMask;
	int dx = mulscale30(kViewDistance, Cos(nAngle));
	int dy = mulscale30(kViewDistance, Sin(nAngle));

	*pAngle = (short)((nAngle + kAngle180) & kAngleMask);

	ushort cstat = pSprite->cstat;
	pSprite->cstat &= ~kSpriteBlocking;
	ClipMove(px, py, pz, pSector, dx, dy, 4 << 4, 1, 1, 0);
	pSprite->cstat = cstat;
}


void viewBackupPlayerLoc( int nPlayer )
{
	SPRITE *pSprite = gPlayer[nPlayer].sprite;
	PLOCATION *pPLocation = &gPrevPlayerLoc[nPlayer];
	pPLocation->x = pSprite->x;
	pPLocation->y = pSprite->y;
	pPLocation->z = pSprite->z;
	pPLocation->ang = pSprite->ang;
	pPLocation->horiz = gPlayer[nPlayer].horiz;
	pPLocation->bobHeight = gPlayer[nPlayer].bobHeight;
	pPLocation->bobWidth = gPlayer[nPlayer].bobWidth;
	pPLocation->swayHeight = gPlayer[nPlayer].swayHeight;
	pPLocation->swayWidth = gPlayer[nPlayer].swayWidth;
}


void viewBackupSpriteLoc( int nSprite, SPRITE *pSprite )
{
	LOCATION *pLocation = &gPrevSpriteLoc[nSprite];
	pLocation->x = pSprite->x;
	pLocation->y = pSprite->y;
	pLocation->z = pSprite->z;
	pLocation->ang = pSprite->ang;
}


void viewBackupAllSpriteLoc( void )
{
	SPRITE *pSprite = &sprite[0];
	LOCATION *pLocation = &gPrevSpriteLoc[0];
	for (int i = 0; i < kMaxSprites; i++, pSprite++, pLocation++)
	{
		if ( pSprite->flags & kAttrMove )
		{
			pLocation->x = pSprite->x;
			pLocation->y = pSprite->y;
			pLocation->z = pSprite->z;
			pLocation->ang = pSprite->ang;
		}
	}
}


/***********************************************************************
 * viewDrawSprite()
 *
 * Draws tiles to the screen using a unified set of flags
 * similar to those in overwritesprite/rotatesprite
 **********************************************************************/
void viewDrawSprite( long sx, long sy,
	long nZoom, int nAngle, short nTile, schar nShade, char nPLU, ushort nFlags,
	long wx1, long wy1, long wx2, long wy2 )
{
	// convert x-flipping
	if ( nFlags & kDrawXFlip )
	{
		nAngle = (nAngle + kAngle180) & kAngleMask;
		nFlags ^= kDrawYFlip;
	}

	// call rotatesprite passing only compatible bits in nFlags
	rotatesprite( sx, sy, nZoom, (short)nAngle, nTile, nShade, nPLU,
		(char)nFlags, wx1, wy1, wx2, wy2 );
}

#define kMaxViewFlames	9

static struct {
	short	tile;
	uchar	flags;
	char	pal;
	long	zoom;
	short	x;
	short	y;
} burnTable[ kMaxViewFlames ] = {
	{	kAnmFlame2,	(uchar)(kDrawScale),			0,	0x22000,	10,		200 },
	{	kAnmFlame2,	(uchar)(kDrawScale|kDrawXFlip),	0,	0x20000,	40,		200 },
	{	kAnmFlame2,	(uchar)(kDrawScale),			0,	0x19000,	85,		200 },
	{	kAnmFlame2,	(uchar)(kDrawScale),			0,	0x16000,	120,	200 },
	{	kAnmFlame2,	(uchar)(kDrawScale|kDrawXFlip),	0,	0x14000,	160,	200 },
	{	kAnmFlame2,	(uchar)(kDrawScale|kDrawXFlip),	0,	0x17000,	200,	200 },
	{	kAnmFlame2,	(uchar)(kDrawScale),			0,	0x18000,	235,	200 },
	{	kAnmFlame2,	(uchar)(kDrawScale),			0,	0x20000,	275,	200 },
	{	kAnmFlame2,	(uchar)(kDrawScale|kDrawXFlip),	0,	0x23000,	310,	200 },
};


/***********************************************************************
 * viewBurnTime()
 **********************************************************************/
void viewBurnTime( int nTicks )
{
	if ( nTicks == 0 )
		return;

	for ( int i = 0; i < kMaxViewFlames; i++ )
	{
		// fake a sprite type for animateoffs to derive an index into an animation sequence
		short nTile = burnTable[i].tile;
		nTile += animateoffs( nTile, (ushort)(0x8000 + i));
		long nZoom = burnTable[i].zoom;

		// size diminishes during last 5 seconds
		if ( nTicks < 5 * kTimerRate )
			nZoom = muldiv(nZoom, nTicks, 5 * kTimerRate);

		viewDrawSprite( burnTable[i].x << 16, burnTable[i].y << 16, nZoom, 0, nTile, 0,
			burnTable[i].pal, burnTable[i].flags, windowx1, windowy1, windowx2, windowy2);
	}
}


void viewSetMessage( char *s )
{
	messageTime = gFrameClock;
	strcpy(message, s);
}


void viewDisplayMessage( void )
{

	if (gFrameClock < messageTime + kTimerRate * 2)
		viewDrawText(kFontMessage, message, windowx1 + 1, windowy1 + 1, 0, 0);
}


void viewDrawScreen( void )
{
	static lastDacUpdate = 0;
	BOOL fInterpolateRangeError = FALSE;

	if ( gViewMode == kView3D || gViewMode == kView2DIcon || gOverlayMap )
	{
	 	DoSectorLighting();
		DoSectorPanning();
	}

	if ((gViewMode == kView3D) || (gOverlayMap))
	{
		int theView = gViewIndex;
		if ( powerupCheck(gMe, kItemCrystalBall - kItemBase) )
			theView = myconnectindex;

		short nSector = gView->sprite->sectnum;

		if (gInterpolate < 0 || gInterpolate > 0x10000)
		{
			fInterpolateRangeError = TRUE;
			gInterpolate = ClipRange(gInterpolate, 0, 0x10000);
		}

		long x = gPrevPlayerLoc[theView].x;
		long y = gPrevPlayerLoc[theView].y;
		long z = gPrevPlayerLoc[theView].z;
		short nAngle = gPrevPlayerLoc[theView].ang;
		int nHoriz = gPrevPlayerLoc[theView].horiz;
		int bobWidth = gPrevPlayerLoc[theView].bobWidth;
		int bobHeight = gPrevPlayerLoc[theView].bobHeight;
		int swayWidth = gPrevPlayerLoc[theView].swayWidth;
		int swayHeight = gPrevPlayerLoc[theView].swayHeight;

		// interpolate player position
		x += mulscale16(gView->sprite->x - gPrevPlayerLoc[theView].x, gInterpolate);
		y += mulscale16(gView->sprite->y - gPrevPlayerLoc[theView].y, gInterpolate);
		z += mulscale16(gView->sprite->z - gPrevPlayerLoc[theView].z, gInterpolate);
		nAngle += mulscale16(((gView->sprite->ang - gPrevPlayerLoc[theView].ang + kAngle180) & kAngleMask) - kAngle180, gInterpolate);
		nHoriz += mulscale16(gView->horiz - gPrevPlayerLoc[theView].horiz, gInterpolate);
		bobWidth += mulscale16(gView->bobWidth - gPrevPlayerLoc[theView].bobWidth, gInterpolate);
		bobHeight += mulscale16(gView->bobHeight - gPrevPlayerLoc[theView].bobHeight, gInterpolate);
		swayWidth += mulscale16(gView->swayWidth - gPrevPlayerLoc[theView].swayWidth, gInterpolate);
		swayHeight += mulscale16(gView->swayHeight - gPrevPlayerLoc[theView].swayHeight, gInterpolate);

//		nAngle = gPrevPlayerLoc[theView].ang;

		if ( gViewPos == kViewPosCenter )
		{
			x -= mulscale30(bobWidth, Sin(nAngle)) >> 4;
			y += mulscale30(bobWidth, Cos(nAngle)) >> 4;
			z += bobHeight + gView->compression - gView->eyeAboveZ + nHoriz * 10;
		}
		else
		{
			viewCalcPosition( gView->sprite, &x, &y, &z, &nAngle, &nSector );
			z -= gView->eyeAboveZ + (16 << 8);
		}

// 		should modify the z to handle earthquake type effects

//		something like this might come in handy for the crystal ball
//		need to mod based on number of players, and exclude self
//		int nEyes = (gGameGlock & 0x700) >> 8; // show through new "eyes" every two seconds
//		then render the other view to a tile and draw the tile (after the screen) using a lens effect

		long tiltLock = gScreenTilt;

		BOOL bDelirious   = FALSE;
		BOOL bClairvoyant = FALSE;

		bDelirious = ( powerupCheck(gView, kItemShroomDelirium - kItemBase) > 0 );
		if ( tiltLock != 0 || bDelirious )
		{
			if ( waloff[ TILTBUFFER ] == NULL )
				allocatepermanenttile( TILTBUFFER, 320L, 320L );

			dassert( waloff[ TILTBUFFER ] != NULL );
			setviewtotile( TILTBUFFER, 320L, 320L );
		}
		else
		{
			bClairvoyant = ( powerupCheck(gView, kItemCrystalBall - kItemBase) > 0 );
			if ( bClairvoyant && gViewIndex != theView )
			{
				dprintf("CLAIR SETUP\n");
				PLAYER *pViewed = &gPlayer[gViewIndex];
				if ( waloff[ BALLBUFFER ] == NULL )
					allocatepermanenttile( BALLBUFFER, 128L, 128L );
				dassert( waloff[ BALLBUFFER ] != NULL );
				setviewtotile( BALLBUFFER, 128L, 128L );
				long x = gPrevPlayerLoc[gViewIndex].x;
				long y = gPrevPlayerLoc[gViewIndex].y;
				long z = gPrevPlayerLoc[gViewIndex].z;
				short nAngle = gPrevPlayerLoc[gViewIndex].ang;
				int bobWidth = gPrevPlayerLoc[gViewIndex].bobWidth;
				int bobHeight = gPrevPlayerLoc[gViewIndex].bobHeight;
				int swayWidth = gPrevPlayerLoc[gViewIndex].swayWidth;
				int swayHeight = gPrevPlayerLoc[gViewIndex].swayHeight;
				x += mulscale16(pViewed->sprite->x - gPrevPlayerLoc[gViewIndex].x, gInterpolate);
				y += mulscale16(pViewed->sprite->y - gPrevPlayerLoc[gViewIndex].y, gInterpolate);
				z += mulscale16(pViewed->sprite->z - gPrevPlayerLoc[gViewIndex].z, gInterpolate);
//				nAngle += mulscale16(((pViewed->sprite->ang - gPrevPlayerLoc[gViewIndex].ang + kAngle180) & kAngleMask) - kAngle180, gInterpolate);
//				bobWidth += mulscale16(pViewed->bobWidth - gPrevPlayerLoc[gViewIndex].bobWidth, gInterpolate);
//				bobHeight += mulscale16(pViewed->bobHeight - gPrevPlayerLoc[gViewIndex].bobHeight, gInterpolate);
//				swayWidth += mulscale16(pViewed->swayWidth - gPrevPlayerLoc[gViewIndex].swayWidth, gInterpolate);
//				swayHeight += mulscale16(pViewed->swayHeight - gPrevPlayerLoc[gViewIndex].swayHeight, gInterpolate);
//				viewCalcPosition( pViewed->sprite, &x, &y, &z, &nAngle, &nSector );
//				z -= pViewed->eyeAboveZ + (16 << 8);
				DrawMirrors(x, y, z, nAngle, kHorizDefault + pViewed->horiz );
				drawrooms(x, y, z, nAngle, kHorizDefault + pViewed->horiz, nSector);
				viewProcessSprites(x, y, z);
				drawmasks();
				setviewback();
			}
		}

		if ( !bDelirious )
		{
			deliriumTilt = 0;
			deliriumTurn = 0;
			deliriumPitch = 0;
		}

		nAngle = (short)((nAngle + deliriumTurn) & kAngleMask);

		// prevent the player from being drawn (not taken care of automatically if x,y different)
		ushort oldcstat = gView->sprite->cstat;
		if ( gViewPos == kViewPosCenter )
			gView->sprite->cstat |= kSpriteInvisible;

		DrawMirrors(x, y, z, nAngle, kHorizDefault + nHoriz + deliriumPitch);
		drawrooms(x, y, z, nAngle, kHorizDefault + nHoriz + deliriumPitch, nSector);
		viewProcessSprites(x, y, z);
		drawmasks();

		gView->sprite->cstat = oldcstat;

		// process gotpics
		if ( TestBitString(gotpic, kPicFire) )
		{
			FireProcess();
			ClearBitString(gotpic, kPicFire);
		}

		if ( tiltLock != 0 || bDelirious )
		{
			dassert( waloff[ TILTBUFFER ] != NULL );

			setviewback();

			uchar nFlags = kRotateScale | kRotateYFlip | kRotateNoMask;
			if ( bDelirious )
				nFlags |= kRotateTranslucent;
			rotatesprite( 320 << 15, 200 << 15, 0x10000, (short)(tiltLock + kAngle90), TILTBUFFER, 0, 0, nFlags,
				gViewX0, gViewY0, gViewX1, gViewY1);
		}
		else
		{
			if ( bClairvoyant && gViewIndex != theView )
			{
				dprintf("CLAIR DRAW\n");
				rotatesprite(0,0,0x10000,0,BALLBUFFER,0,0,0,gViewX0, gViewY0, gViewX1, gViewY1);
			}
		}

		if ( gViewPos == kViewPosCenter )
		{
			if ( gAimReticle )
			{
				// draw aiming reticle
				dassert(gView->relAim.dx > 0);

				x = 160 + gView->relAim.dy * 160 / gView->relAim.dx;
				y = kHorizDefault + nHoriz + (gView->relAim.dz >> 7);
				rotatesprite(x << 16, y << 16, 0x10000, 0, 2319, 0, 0, kRotateScale,
					gViewX0, gViewY0, gViewX1, gViewY1);
			}

			x = 160 + (swayWidth >> 8);
			y = 200 + gPosture[gView->lifemode].swayV + (swayHeight >> 8) + (gView->compression >> 8);
			int nShade = sector[nSector].floorshade;
			WeaponDraw(gView, nShade, x, y);
		}

		if ( gViewPos == kViewPosCenter && actGetBurnTime(gView->xsprite) > (kTimerRate / 2) )
			viewBurnTime( actGetBurnTime(gView->xsprite)) ;

		if ( powerupCheck(gView, kItemDivingSuit - kItemBase) > 0 )
		{
			uchar nFlags = kRotateScale|kRotateCorner;

			// upper-left
			rotatesprite( 0, 0, 0x10000, 0, kMaskDivingSuit, 0, 0, nFlags,
				gViewX0, gViewY0, gViewX1, gViewY1);

			// upper-right
			rotatesprite(  320 << 16, 0, 0x10000, 1024, kMaskDivingSuit, 0, 0, (uchar)(nFlags|kRotateYFlip),
				gViewX0, gViewY0, gViewX1, gViewY1);

			// lower-left
			rotatesprite(  0, 200 << 16, 0x10000, 0, kMaskDivingSuit, 0, 0, (uchar)(nFlags|kRotateYFlip),
				gViewX0, gViewY0, gViewX1, gViewY1);

			// lower-right
			rotatesprite(  320 << 16, 200 << 16, 0x10000, 1024, kMaskDivingSuit, 0, 0, nFlags,
				gViewX0, gViewY0, gViewX1, gViewY1);

			if ( gDetail >= kDetailLevelMax )
			{
				// upper-left reflection
				rotatesprite( 15 << 16, 3 << 16, 0x10000, 0, kMaskReflect1, 32, 0, (uchar)(nFlags|kRotateTranslucent),
					gViewX0, gViewY0, gViewX1, gViewY1);

				// lower-right reflection
				rotatesprite( 212 << 16, 77 << 16, 0x10000, 0, kMaskReflect2, 32, 0, (uchar)(nFlags|kRotateTranslucent),
					gViewX0, gViewY0, gViewX1, gViewY1);
			}
		}

		if ( powerupCheck(gView, kItemAsbestosArmor - kItemBase) > 0 )
		{
			uchar nFlags = kRotateScale|kRotateCorner;

			// upper-left
			rotatesprite( 0, 0, 0x10000, 0, kMaskFireSuit, 0, 0, nFlags,
				gViewX0, gViewY0, gViewX1, gViewY1);

			// upper-right
			rotatesprite(  320 << 16, 0, 0x10000, 1024, kMaskFireSuit, 0, 0, (uchar)(nFlags|kRotateYFlip),
				gViewX0, gViewY0, gViewX1, gViewY1);

			// lower-left
			rotatesprite(  0, 200 << 16, 0x10000, 0, kMaskFireSuit, 0, 0, (uchar)(nFlags|kRotateYFlip),
				gViewX0, gViewY0, gViewX1, gViewY1);

			// lower-right
			rotatesprite(  320 << 16, 200 << 16, 0x10000, 1024, kMaskFireSuit, 0, 0, nFlags,
				gViewX0, gViewY0, gViewX1, gViewY1);
		}
	}
	else
		clearview(0);  // Clear screen to specified color

	if ( gViewMode == kView2DIcon )
		drawmapview(gView->sprite->x, gView->sprite->y, gZoom >> 2, gView->sprite->ang);

	if ( gViewMode == kView2D /*|| gViewMode == kView2DIcon */)
	{
//		drawoverheadmap(gView->sprite->x, gView->sprite->y, gZoom, gView->sprite->ang);
		DrawMap(gView->sprite->x, gView->sprite->y, gView->sprite->ang, gZoom);
	}

	viewDrawInterface();

	viewDisplayMessage();

	CalcFrameRate();
	if ( gShowFrameRate )
	{
		char buffer[16];
		sprintf(buffer, "%3i", gFrameRate);
		printext256(gViewX1 - 12, gViewY0, 31, -1, buffer, 1);
	}
	if ( gShowFrags )
	{
		for ( int i = 0; i < numplayers; i++)
		{
			char buffer[16];
			sprintf(buffer, "%i:%i", i, gPlayer[i].fragCount);
			printext256(gViewX0 + 12, gViewY0+(i*8), 31, -1, buffer, 1);
		}
	}

	if ( gPaused )
		viewDrawText(kFontMenu, "PAUSED", gViewX1 / 2, gViewY1 / 2, 0, 0, TA_CENTER);

	if ( fInterpolateRangeError )
		gfxDrawText(windowx2 - 16, windowy1, gStdColor[kColorWhite], "I");

	if ( gView->xsprite->moveState == kMoveFall )
		gfxDrawText(windowx2 - 24, windowy1, gStdColor[kColorWhite], "F");

	scrSetDac(gGameClock - lastDacUpdate);
	lastDacUpdate = gGameClock;

	scrNextPage();
}


void Detail( int nType, BOOL bDetail )
{
	switch ( nType )
	{
		case kDetailSprites:
		{
			int nSprite, nNext;
			for (nSprite = headspritestat[kStatProximity]; nSprite >= 0; nSprite = nNext)
			{
				nNext = nextspritestat[nSprite];

				if (bDetail)
					sprite[nSprite].cstat &= ~kSpriteInvisible;
				else
					sprite[nSprite].cstat |= kSpriteInvisible;
			}
			break;
		}

		default:
			break;
	}
}


void viewSetDetail( int nDetail )
{
	dassert( nDetail >= kDetailLevelMin && nDetail <= kDetailLevelMax );

	switch ( nDetail )
	{
		// no kStatDetail sprites, no fake gibs, no shadows, no smoke trail effects, no glow or lens flares
		case kDetailLevelMin:
			Detail( kDetailSprites, FALSE );
			break;

		case kDetailLevel2:
			Detail( kDetailSprites, FALSE );
			break;

		case kDetailLevel3:
			Detail( kDetailSprites, FALSE );
			break;

		case kDetailLevel4:
			Detail( kDetailSprites, FALSE );
			break;

		case kDetailLevelMax:
			Detail( kDetailSprites, TRUE );
			break;
	}
	gDetail = nDetail;
}
