#include <stdio.h>
#include <string.h>

#include "engine.h"
#include "bstub.h"
#include "build.h"
#include "misc.h"
#include "trig.h"
#include "globals.h"
#include "textio.h"
#include "gameutil.h"
#include "db.h"
#include "debug4g.h"
#include "key.h"
#include "edit2d.h"


enum controlType
{
	TEXT,
	NUMBER,
	CHECKBOX,
	RADIOBUTTON,
	LIST,
	DIALOG
};

typedef struct DIALOG_ITEM *ITEM_PTR;
typedef BYTE ( *ITEM_PROC )( ITEM_PTR, BYTE key );

struct DIALOG_ITEM
{
	int x, y;
	int tabGroup;
	enum controlType type;
	char *label;
	int minValue;
	int maxValue;
	char **names;
	ITEM_PROC fieldHelpProc;
	int value;
};


// dialog helper function prototypes
BYTE GetNextUnusedID( DIALOG_ITEM *control, BYTE key );
BYTE DoSectorFXDialog( DIALOG_ITEM *control, BYTE key );


/*******************************************************************************
								Local Variables
*******************************************************************************/
static char buffer[256] = "";
short sectorhighlight;		// which sector mouse is inside in 2d mode

DIALOG_ITEM dlgSprite[] =
{
	{	0,	0,	1,	LIST,			"Type %4d: %-18.18s", 0, 1023, gSpriteNames },	// 0
	{ 	0,	1,	2,	NUMBER, 		"RX ID: %4d", 0, 1023, NULL, GetNextUnusedID },	// 1
	{ 	0,	2,	3,	NUMBER, 		"TX ID: %4d", 0, 1023, NULL, GetNextUnusedID },	// 2
	{ 	0,	3,	4,	LIST, 			"State %1d: %-3.3s", 0, 1, gBoolNames },		// 3
	{ 	0,	4,	5,	LIST, 			"Cmd: %3d: %-6.6s", 0, 255, gCommandNames },	// 4

	{	0,	5,	0,	TEXT, 			"Send when:" },									// 5
	{	0,	6,	6,	CHECKBOX,		"going ON" },									// 6
	{	0,	7,	7,	CHECKBOX,		"going OFF" },                                	// 7
	{	0,	8,	8,	NUMBER, 		"busyTime = %4d", 0, 4095 },                    // 8
	{	0,	9,	9,	NUMBER, 		"waitTime = %4d", 0, 4095 },                   	// 9
	{	0,	10,	10,	LIST,			"restState %1d: %-3.3s", 0, 1, gBoolNames },   	// 10

	{	30,	0,	0,	TEXT,			"Trigger On:" },								// 11
	{	30,	1,	11,	CHECKBOX,		"Push" },                                     	// 12
	{	30,	2,	12,	CHECKBOX,		"Impact" },                                   	// 13
	{	30,	3,	13,	CHECKBOX,		"Reserved" },                                  	// 14
	{	30,	4,	14,	CHECKBOX,		"Pickup" },                                   	// 15
	{	30,	5,	15,	CHECKBOX, 		"Touch" },                                    	// 16
	{	30,	6,	16,	CHECKBOX, 		"Sight" },                                    	// 17
 	{	30,	7,	17,	CHECKBOX, 		"Proximity" },                                	// 18

	{	46,	0,	0,	TEXT, 			"Trigger Flags:" },                           	// 19
	{	46,	1,	18,	CHECKBOX, 		"Decoupled" },                                	// 20
	{	46,	2,	19,	CHECKBOX,		"1-shot" },                                   	// 21
	{	46,	3,	20,	CHECKBOX,		"Locked" },                                   	// 22
	{	46,	4,	21,	CHECKBOX,		"Interruptable" },								// 23

	{ 	46,	5,	22,	NUMBER, 		"Data1: %5d", 0, 65535 },						// 24
	{ 	46,	6,	23,	NUMBER, 		"Data2: %5d", 0, 65535 },						// 25
	{ 	46,	7,	24,	NUMBER, 		"Data3: %5d", 0, 65535 },						// 26
	{ 	46,	8,	25,	NUMBER,			"Key: %1d", 0, 7 },                           	// 27
	{ 	46,	9,	26,	NUMBER,			"Sound: %3d", 0, 255 },							// 28
	{ 	46,	10,	27,	NUMBER,			"Difficulty: %1d", 0, 3 },                    	// 29

	{ 	62,	0,	0,	TEXT, 			"Respawn:" },									// 30
	{ 	62,	1,	28,	LIST, 			"When %1d: %-6.6s", 0, 3, gRespawnNames },		// 31
	{	62,	2,	29,	NUMBER, 		"Time = %4d", 0, 4095 },						// 32

	{	62,	4,	0,	TEXT, 			"Dude Flags:" },								// 33
	{	62,	5,	30,	CHECKBOX, 		"dudeDeaf" },									// 34
	{	62,	6,	31,	CHECKBOX,		"dudeAmbush" },									// 35
	{	62,	7,	32,	CHECKBOX,		"dudeGuard" },									// 36
	{	62,	8,	33,	CHECKBOX,		"reserved" },									// 37
};


DIALOG_ITEM dlgWall[] =
{
	{	0,	0,	1,	LIST, 			"Type %4d: %-18.18s", 0, 1023, gWallNames },	// 0
	{ 	0,	1,	2,	NUMBER, 		"RX ID: %4d", 0, 1023, NULL, GetNextUnusedID },	// 1
	{ 	0,	2,	3,	NUMBER, 		"TX ID: %4d", 0, 1023, NULL, GetNextUnusedID },	// 2
	{ 	0,	3,	4,	LIST, 			"State %1d: %-3.3s", 0, 1, gBoolNames },		// 3
	{ 	0,	4,	5,	LIST, 			"Cmd: %3d: %-6.6s", 0, 255, gCommandNames },	// 4

	{	0,	5,	0,	TEXT, 			"Send when:" },									// 5
	{	0,	6,	6,	CHECKBOX,		"going ON" },									// 6
	{	0,	7,	7,	CHECKBOX,		"going OFF" },									// 7
	{	0,	8,	8,	NUMBER, 		"busyTime = %4d", 0, 4095 },					// 8
	{	0,	9,	9,	NUMBER, 		"waitTime = %4d", 0, 4095 },					// 9
	{	0,	10,	10,	LIST,			"restState %1d: %-3.3s", 0, 1, gBoolNames },	// 10

	{	30,	0,	0,	TEXT,			"Trigger On:" },								// 11
	{	30,	1,	11,	CHECKBOX,		"Push" },										// 12
	{	30,	2,	12,	CHECKBOX,		"Impact" },										// 13
	{	30,	3,	13,	CHECKBOX,		"Reserved" },									// 14

	{	46,	0,	0,	TEXT, 			"Trigger Flags:" },								// 15
	{	46,	1,	14,	CHECKBOX, 		"Decoupled" },									// 16
	{	46,	2,	15,	CHECKBOX,		"1-shot" },										// 17
	{	46,	3,	16,	CHECKBOX, 		"Locked" },										// 18
	{	46,	4,	17,	CHECKBOX,		"Interruptable" },								// 19

	{ 	46,	6,	18,	NUMBER, 		"Data: %5d", 0, 65535 },						// 20
	{ 	46,	7,	19,	NUMBER,			"Key: %1d", 0, 7 },								// 21
	{	46,	8,	20,	NUMBER, 		"panX = %4d", -128, 127 },						// 22
	{	46,	9,	21,	NUMBER, 		"panY = %4d", -128, 127 },						// 23
	{	46,	10,	22,	CHECKBOX,		"panAlways" },									// 24
};


DIALOG_ITEM dlgSectorFX[] =
{
	{	0,	0,	0,	TEXT, 			"Lighting:" },                                           // 0
	{	0,	1,	1,	LIST, 			"Wave: %1d %-9.9s", 0, 15, gWaveNames },														// 1
	{	0,	2,	2,	NUMBER, 		"Amplitude: %+4d", -128, 127 },                          // 2
	{	0,	3,	3,	NUMBER, 		"Freq:    %3d", 0, 255 },                                // 3
	{	0,	4,	4,	NUMBER, 		"Phase:     %3d", 0, 255 },                              // 4
	{	0,	5,	5,	CHECKBOX,		"floor" },                                               // 5
	{	0,	6,	6,	CHECKBOX,		"ceiling" },                                             // 6
	{	0,	7,	7,	CHECKBOX,		"walls" },                                               // 7
	{	0,	8,	8,	CHECKBOX,		"shadeAlways" },                                         // 8

	{	20,	0,	0,	TEXT, 			"Motion FX:" },                                          // 9
	{	20,	1,	9,	NUMBER, 		"Speed = %4d", 0, 255 },                                 // 10
	{	20,	2,	10,	NUMBER, 		"Angle = %4d", 0, kAngle360-1 },                         // 11
	{	20,	3,	11,	CHECKBOX,		"pan floor" },                                           // 12
	{	20,	4,	12,	CHECKBOX,		"pan ceiling" },                                         // 13
	{	20,	5,	13,	CHECKBOX,		"panAlways" },                                           // 14
	{	20,	6,	14,	CHECKBOX,		"drag" },                                                // 15

	{	35,	2,	15,	LIST, 	   		"Depth = %1d: %-5.5s", 0, 3, gDepthNames },               // 16
	{	35,	3,	16,	LIST, 			"Under = %1d: %-3.3s", 0, 1, gBoolNames },                // 17
	{	35,	4,	17,	LIST,			"Wind  = %1d: %-3.3s", 0, 1, gBoolNames},                 // 18
};


DIALOG_ITEM dlgSector[] =
{
	{	0,	0,	1,	LIST, 		"Type %4d: %-18.18s", 0, 1023, gSectorNames },		// 0
	{ 	0,	1,	2,	NUMBER, 	"RX ID: %4d", 0, 1023, NULL, GetNextUnusedID },		// 1
	{ 	0,	2,	3,	NUMBER, 	"TX ID: %4d", 0, 1023, NULL, GetNextUnusedID },		// 2
	{ 	0,	3,	4,	LIST, 		"State %1d: %-3.3s", 0, 1, gBoolNames },			// 3
	{ 	0,	4,	5,	LIST, 		"Cmd: %3d: %-6.6s", 0, 255, gCommandNames },		// 4

	{	0,	5,	0,	TEXT, 		"Send when:" },										// 5
	{	0,	6,	6,	CHECKBOX,	"going ON" },										// 6
	{	0,	7,	7,	CHECKBOX,	"going OFF" },										// 7
	{	0,	8,	8,	NUMBER, 	"busyTime = %3d", 0, 255 },							// 8
	{	0,	9,	9,	NUMBER, 	"waitTime = %3d", 0, 255 },							// 9
	{	0,	10,	10,	LIST,		"restState %1d: %-3.3s", 0, 1, gBoolNames },		// 10

	{	30,	0,	0,	TEXT, 		"Trigger On:" },									// 11
	{	30,	1,	11,	CHECKBOX,	"Push" },											// 12
	{	30,	2,	12,	CHECKBOX,	"Impact" },                                     	// 13
	{	30,	3,	13,	CHECKBOX,	"Reserved" },										// 14
	{	30,	4,	14,	CHECKBOX,	"Enter" },                                      	// 15
	{	30,	5,	15,	CHECKBOX,	"Exit" },                                       	// 16
	{	30,	6,	16,	CHECKBOX,	"WallPush" },                                   	// 17

	{	46,	0,	0,	TEXT, 		"Trigger Flags:" },									// 18
	{	46,	1,	17,	CHECKBOX, 	"Decoupled" },										// 19
	{	46,	2,	18,	CHECKBOX,	"1-shot" },											// 20
	{	46,	3,	19,	CHECKBOX, 	"Locked" },											// 21
	{	46,	4,	20,	CHECKBOX, 	"Interruptable" },									// 22

	{ 	46,	6,	21,	NUMBER, 	"Data: %5d", 0, 65535 },							// 23
	{ 	46,	7,	22,	NUMBER,		"Key: %1d", 0, 7 },									// 24

	{	65,	10,	23,	DIALOG,		"EFFECTS...",  0, 0, NULL, DoSectorFXDialog },		// 25
};



void DialogText( int x, int y, short nFore, short nBack, char *string)
{
	printext16(x * 8 + 4, y * 8 + 28, nFore, nBack, string, 0);
}

/***********************************************************************
 * PaintDialogItem()
 *
 **********************************************************************/
void PaintDialogItem(DIALOG_ITEM *control, int focus)
{
	short foreColor = 11, backColor = 8;

	if ( focus )
	{
		foreColor = 14;
		backColor = 0;
	}

	if (control->tabGroup == 0)
	{
		foreColor = 15;
		backColor = 2;
	}

	switch ( control->type )
	{
		case TEXT:
		case DIALOG:
			DialogText(control->x, control->y, foreColor, backColor, control->label);
			break;

		case NUMBER:
			sprintf(buffer, control->label, control->value);
			DialogText(control->x, control->y, foreColor, backColor, buffer);
			break;

		case LIST:
			dassert(control->names != NULL);
			sprintf(buffer, control->label, control->value, control->names[control->value]);
			DialogText(control->x, control->y, foreColor, backColor, buffer);
			break;

		case CHECKBOX:
			sprintf(buffer, "%c %s", control->value ? 3 : 2, control->label);
			DialogText(control->x, control->y, foreColor, backColor, buffer);
			break;

		case RADIOBUTTON:
			sprintf(buffer, "%c %s", control->value ? 5 : 4, control->label);
			DialogText(control->x, control->y, foreColor, backColor, buffer);
			break;
	}
}


/***********************************************************************
 * PaintDialog()
 *
 **********************************************************************/
void PaintDialog(DIALOG_ITEM *dialog, int count)
{
	int i;

	clearmidstatbar16();
	for ( i = 0; i < count; i++ )
	{
		PaintDialogItem(&dialog[i], kFalse);
	}
}


/***********************************************************************
 * FindGroup()
 *
 **********************************************************************/
DIALOG_ITEM *FindGroup(DIALOG_ITEM *dialog, int count, int group)
{
	DIALOG_ITEM *control;

	for ( control = dialog; control < &dialog[count]; control++)
	{
		if ( control->tabGroup == group )
			break;
	}
	dassert(control < &dialog[count]);

	if ( control->type == RADIOBUTTON )
	{
		while ( control->value == kFalse )
		{
			control++;
			dassert(control < &dialog[count]);
			dassert(control->type == RADIOBUTTON);
		}
	}

	return control;
}


/***********************************************************************
 * SetControlValue()
 *
 **********************************************************************/
void SetControlValue(DIALOG_ITEM *dialog, int count, int group, int value )
{
	int i;

	for ( i = 0; i < count; i++)
	{
		if ( dialog[i].tabGroup == group )
			break;
	}
	dassert(i < count);

	dialog[i].value = value;
}


/***********************************************************************
 * GetControlValue()
 *
 **********************************************************************/
int GetControlValue(DIALOG_ITEM *dialog, int count, int group )
{
	int i;
	int value = 0;

	for ( i = 0; i < count; i++)
	{
		if ( dialog[i].tabGroup == group )
			break;
	}
	dassert(i < count);

	return dialog[i].value;
}


/***********************************************************************
 * SetRadioButton()
 *
 **********************************************************************/
void SetRadioButton(DIALOG_ITEM *dialog, int count, int group, int value )
{
	int i;

	for ( i = 0; i < count; i++)
	{
		if ( dialog[i].tabGroup == group )
			break;
	}
	dassert(i < count);

	while ( i < count && dialog[i].tabGroup == group )
	{
		dialog[i].value = (value == 0);
		i++;
		value--;
	}
	dassert(value < 0);
}


/***********************************************************************
 * GetRadioButton()
 *
 **********************************************************************/
int GetRadioButton(DIALOG_ITEM *dialog, int count, int group )
{
	int i;
	int value = 0;

	for ( i = 0; i < count; i++)
	{
		if ( dialog[i].tabGroup == group )
			break;
	}
	dassert(i < count);

	while ( i < count )
	{
		dassert(dialog[i].type == RADIOBUTTON);
		if ( dialog[i].value )
			break;
		i++;
		value++;
	}
	dassert(i < count);
	return value;
}


/*******************************************************************************
	FUNCTION:		EditDialog()

	DESCRIPTION:	This is the editing event loop for dialogs.

	PARAMETERS:		dialog is a pointer to the first control, count is the
					number of controls within the dialog.

	RETURNS:        1 if exited via Enter, 0 if exited with Esc
*******************************************************************************/
int EditDialog(DIALOG_ITEM *dialog, int count)
{
	int group = 1, lastGroup = 0;
	int i, value, oldValue;
	DIALOG_ITEM *control;
	BYTE key = 0;

	keyFlushStream();

	PaintDialog(dialog, count);

	for ( i = 0; i < count; i++ )
	{
		if ( dialog[i].tabGroup > lastGroup )
			lastGroup = dialog[i].tabGroup;
	}

	control = FindGroup(dialog, count, group);

	while ( 1 )
	{
		PaintDialogItem(control, kTrue);

		do
		{
			key = keyGet();
		} while ( key == 0 );

		if ( control->fieldHelpProc )
		{
			key = control->fieldHelpProc(control, key);

			// may need to repaint all in case of subdialogs
			PaintDialog(dialog, count);
		}

		switch ( key )
		{
			case KEY_ENTER:
			case KEY_PADENTER:
				keystatus[key] = 0;		// need to clear in 2D mode, got it?
				BeepOkay();
				return 1;

			case KEY_ESC:
				keystatus[key] = 0;
				return 0;

			case KEY_LEFT:
				if ( --group == 0 )
					group = lastGroup;
				PaintDialogItem(control, kFalse);
				control = FindGroup(dialog, count, group);
				break;

			case KEY_RIGHT:
				if ( ++group > lastGroup )
					group = 1;
				PaintDialogItem(control, kFalse);
				control = FindGroup(dialog, count, group);
				break;

			case KEY_TAB:
				if ( keystatus[KEY_LSHIFT] || keystatus[KEY_RSHIFT] )
				{
					if ( --group == 0 )
						group = lastGroup;
					PaintDialogItem(control, kFalse);
					control = FindGroup(dialog, count, group);
					break;
				}
				else
				{
					if ( ++group > lastGroup )
						group = 1;
					PaintDialogItem(control, kFalse);
					control = FindGroup(dialog, count, group);
					break;
				}

			default:
				switch ( control->type )
				{
					case NUMBER:

						oldValue = control->value;

						switch ( key )
						{
							case KEY_BACKSPACE:
								control->value /= 10;
								break;

							case KEY_PADPLUS:
							case KEY_UP:
								control->value++;
								break;

							case KEY_PADMINUS:
							case KEY_DOWN:
								control->value--;
								break;

							case KEY_PAGEUP:
								control->value = IncBy(control->value, 10);
								break;

							case KEY_PAGEDN:
								control->value = DecBy(control->value, 10);
								break;

							default:
								key = ScanToAscii[key];
								if ( key >= '0' && key <= '9' )
									control->value = control->value * 10 + key - '0';
						}

						if ( control->value > control->maxValue )
							control->value = control->maxValue;

						if ( control->value < control->minValue )
							control->value = control->minValue;

						break;

					case CHECKBOX:
						switch ( key )
						{
							case KEY_SPACE:
								control->value = !control->value;
								break;
						}
						break;

					case RADIOBUTTON:
						PaintDialogItem(control, kFalse);
						switch ( key )
						{
							case KEY_UP:
								control->value = kFalse;
								PaintDialogItem(control, kFalse);
								do
								{
									if (--control < dialog)
										control = &dialog[count - 1];

								} while ( control->tabGroup != group );
								control->value = kTrue;
								break;

							case KEY_DOWN:
								control->value = kFalse;
								PaintDialogItem(control, kFalse);
								do
								{
									if (++control == &dialog[count])
										control = dialog;
								} while ( control->tabGroup != group );
								control->value = kTrue;
								break;

						}
						break;

					case LIST:

						oldValue = control->value;
						value = control->value;

						switch ( key )
						{
							case KEY_PADPLUS:
							case KEY_UP:
								do
								{
									value++;
								} while ( value <= control->maxValue && control->names[value] == NULL );
								break;

							case KEY_PADMINUS:
							case KEY_DOWN:
								do
								{
									value--;
								} while ( value >= control->minValue && control->names[value] == NULL );
								break;

							case KEY_PAGEUP:
								do
								{
									value = IncBy(value, 10);
								} while ( value <= control->maxValue && control->names[value] == NULL );
								break;

							case KEY_PAGEDN:
								do
								{
									value = DecBy(value, 10);
								} while ( value >= control->minValue && control->names[value] == NULL );
								break;
						}

						if ( value >= control->minValue && value <= control->maxValue && control->names[value] != NULL )
							control->value = value;

						break;
				}
		}
	}
}


BYTE GetNextUnusedID( DIALOG_ITEM *control, BYTE key )
{
	BOOL used[1024];
	int i;

	switch ( key )
	{
		case KEY_F10:
			memset(used, FALSE, sizeof(used));

			for (i = 0; i < numsectors; i++)
			{
				int nXSector = sector[i].extra;
				if ( nXSector > 0 )
				{
					used[xsector[nXSector].txID] = TRUE;
					used[xsector[nXSector].rxID] = TRUE;
				}
			}

			for (i = 0; i < numwalls; i++)
			{
				int nXWall = wall[i].extra;
				if ( nXWall > 0 )
				{
					used[xwall[nXWall].txID] = TRUE;
					used[xwall[nXWall].rxID] = TRUE;
				}
			}

			for (i = 0; i < kMaxSprites; i++)
			{
				if ( sprite[i].statnum < kMaxStatus )
				{
					int nXSprite = sprite[i].extra;
					if ( nXSprite > 0 )
					{
						used[xsprite[nXSprite].txID] = TRUE;
						used[xsprite[nXSprite].rxID] = TRUE;
					}
				}
			}

			// find the first unused id
			for (i = 100; i < 1024; i++)
				if ( !used[i] )
					break;

			control->value = i;
			return 0;
	}

	return key;
}


BYTE DoSectorFXDialog( DIALOG_ITEM * /* control */, BYTE key )
{
	switch ( key )
	{
		case KEY_ENTER:
			if ( EditDialog(dlgSectorFX, LENGTH(dlgSectorFX)) )
				return KEY_ENTER;
			return 0;
	}

	return key;
}


/***********************************************************************
 * XWallToDialog()
 *
 **********************************************************************/
void XWallToDialog( int nWall )
{
	int nXWall = wall[nWall].extra;
	dassert(nXWall > 0 && nXWall < kMaxXWalls);

	dlgWall[0].value = wall[nWall].type;
	dlgWall[1].value = xwall[nXWall].rxID;
	dlgWall[2].value = xwall[nXWall].txID;
	dlgWall[3].value = xwall[nXWall].state;
	dlgWall[4].value = xwall[nXWall].command;

	dlgWall[6].value = xwall[nXWall].triggerOn;
	dlgWall[7].value = xwall[nXWall].triggerOff;
	dlgWall[8].value = xwall[nXWall].busyTime;
	dlgWall[9].value = xwall[nXWall].waitTime;
	dlgWall[10].value = xwall[nXWall].restState;

	dlgWall[12].value = xwall[nXWall].triggerPush;
	dlgWall[13].value = xwall[nXWall].triggerImpact;
	dlgWall[14].value = xwall[nXWall].triggerReserved0;

	dlgWall[16].value = xwall[nXWall].decoupled;
	dlgWall[17].value = xwall[nXWall].triggerOnce;
	dlgWall[18].value = xwall[nXWall].locked;
	dlgWall[19].value = xwall[nXWall].interruptable;

	dlgWall[20].value = xwall[nXWall].data;
	dlgWall[21].value = xwall[nXWall].key;
	dlgWall[22].value = xwall[nXWall].panXVel;
	dlgWall[23].value = xwall[nXWall].panYVel;
	dlgWall[24].value = xwall[nXWall].panAlways;
}


/***********************************************************************
 * DialogToXWall()
 *
 **********************************************************************/
void DialogToXWall( int nWall )
{
	int nXWall = wall[nWall].extra;
	dassert(nXWall > 0 && nXWall < kMaxXWalls);

	wall[nWall].type=				(short)dlgWall[0].value;
	xwall[nXWall].rxID=				dlgWall[1].value;
	xwall[nXWall].txID=             dlgWall[2].value;
	xwall[nXWall].state=            dlgWall[3].value;
	xwall[nXWall].command=          dlgWall[4].value;

	xwall[nXWall].triggerOn=        dlgWall[6].value;
	xwall[nXWall].triggerOff=       dlgWall[7].value;
	xwall[nXWall].busyTime=         dlgWall[8].value;
	xwall[nXWall].waitTime=         dlgWall[9].value;
	xwall[nXWall].restState=        dlgWall[10].value;

	xwall[nXWall].triggerPush=		dlgWall[12].value;
	xwall[nXWall].triggerImpact=    dlgWall[13].value;
	xwall[nXWall].triggerReserved0=	dlgWall[14].value;

	xwall[nXWall].decoupled=        dlgWall[16].value;
	xwall[nXWall].triggerOnce=      dlgWall[17].value;
	xwall[nXWall].locked=           dlgWall[18].value;
	xwall[nXWall].interruptable=	dlgWall[19].value;

	xwall[nXWall].data=             dlgWall[20].value;
	xwall[nXWall].key=              dlgWall[21].value;
	xwall[nXWall].panXVel=          dlgWall[22].value;
	xwall[nXWall].panYVel=          dlgWall[23].value;
	xwall[nXWall].panAlways=        dlgWall[24].value;
}

/***********************************************************************
 * XSpriteToDialog()
 *
 **********************************************************************/
void XSpriteToDialog( int nSprite )
{
	int nXSprite = sprite[nSprite].extra;
	dassert(nXSprite > 0 && nXSprite < kMaxXSprites);

	dlgSprite[0].value = sprite[nSprite].type;
	dlgSprite[1].value = xsprite[nXSprite].rxID;
	dlgSprite[2].value = xsprite[nXSprite].txID;
	dlgSprite[3].value = xsprite[nXSprite].state;
	dlgSprite[4].value = xsprite[nXSprite].command;

	dlgSprite[6].value = xsprite[nXSprite].triggerOn;
	dlgSprite[7].value = xsprite[nXSprite].triggerOff;
	dlgSprite[8].value = xsprite[nXSprite].busyTime;
	dlgSprite[9].value = xsprite[nXSprite].waitTime;
	dlgSprite[10].value = xsprite[nXSprite].restState;

	dlgSprite[12].value = xsprite[nXSprite].triggerPush;
	dlgSprite[13].value = xsprite[nXSprite].triggerImpact;
	dlgSprite[14].value = xsprite[nXSprite].triggerReserved0;
	dlgSprite[15].value = xsprite[nXSprite].triggerPickup;
 	dlgSprite[16].value = xsprite[nXSprite].triggerTouch;
 	dlgSprite[17].value = xsprite[nXSprite].triggerSight;
 	dlgSprite[18].value = xsprite[nXSprite].triggerProximity;

	dlgSprite[20].value = xsprite[nXSprite].decoupled;
	dlgSprite[21].value = xsprite[nXSprite].triggerOnce;
	dlgSprite[22].value = xsprite[nXSprite].locked;
	dlgSprite[23].value = xsprite[nXSprite].interruptable;

	dlgSprite[24].value = xsprite[nXSprite].data1;
	dlgSprite[25].value = xsprite[nXSprite].data2;
	dlgSprite[26].value = xsprite[nXSprite].data3;
	dlgSprite[27].value = xsprite[nXSprite].key;
	dlgSprite[28].value = xsprite[nXSprite].soundId;
	dlgSprite[29].value = xsprite[nXSprite].difficulty;

	dlgSprite[31].value = xsprite[nXSprite].respawn;
	dlgSprite[32].value = xsprite[nXSprite].respawnTime;

	dlgSprite[34].value = xsprite[nXSprite].dudeDeaf;
	dlgSprite[35].value = xsprite[nXSprite].dudeAmbush;
	dlgSprite[36].value = xsprite[nXSprite].dudeGuard;
	dlgSprite[37].value = xsprite[nXSprite].dudeFlag4;
}


/***********************************************************************
 * DialogToXSprite()
 *
 **********************************************************************/
void DialogToXSprite( int nSprite )
{
	int nXSprite = sprite[nSprite].extra;
	dassert(nXSprite > 0 && nXSprite < kMaxXSprites);

	sprite[nSprite].type		= (short)dlgSprite[0].value;
	xsprite[nXSprite].rxID		= dlgSprite[1].value;
	xsprite[nXSprite].txID		= dlgSprite[2].value;
	xsprite[nXSprite].state		= dlgSprite[3].value;
	xsprite[nXSprite].command	= dlgSprite[4].value;

	xsprite[nXSprite].triggerOn		= dlgSprite[6].value;
	xsprite[nXSprite].triggerOff	= dlgSprite[7].value;
	xsprite[nXSprite].busyTime		= dlgSprite[8].value;
	xsprite[nXSprite].waitTime		= dlgSprite[9].value;
	xsprite[nXSprite].restState		= dlgSprite[10].value;

	xsprite[nXSprite].triggerPush		= dlgSprite[12].value;
	xsprite[nXSprite].triggerImpact		= dlgSprite[13].value;
	xsprite[nXSprite].triggerReserved0	= dlgSprite[14].value;
	xsprite[nXSprite].triggerPickup		= dlgSprite[15].value;
 	xsprite[nXSprite].triggerTouch		= dlgSprite[16].value;
 	xsprite[nXSprite].triggerSight		= dlgSprite[17].value;
 	xsprite[nXSprite].triggerProximity	= dlgSprite[18].value;

	xsprite[nXSprite].decoupled			= dlgSprite[20].value;
	xsprite[nXSprite].triggerOnce		= dlgSprite[21].value;
	xsprite[nXSprite].locked			= dlgSprite[22].value;
	xsprite[nXSprite].interruptable		= dlgSprite[23].value;

	xsprite[nXSprite].data1			= dlgSprite[24].value;
	xsprite[nXSprite].data2			= dlgSprite[25].value;
	xsprite[nXSprite].data3			= dlgSprite[26].value;
	xsprite[nXSprite].key			= dlgSprite[27].value;
	xsprite[nXSprite].soundId		= dlgSprite[28].value;
	xsprite[nXSprite].difficulty	= dlgSprite[29].value;

	xsprite[nXSprite].respawn		= dlgSprite[31].value;
	xsprite[nXSprite].respawnTime	= dlgSprite[32].value;

	xsprite[nXSprite].dudeDeaf=		dlgSprite[34].value;
	xsprite[nXSprite].dudeAmbush=   dlgSprite[35].value;
	xsprite[nXSprite].dudeGuard=    dlgSprite[36].value;
	xsprite[nXSprite].dudeFlag4=    dlgSprite[37].value;
}


/***********************************************************************
 * XSectorToDialog()
 *
 **********************************************************************/
void XSectorToDialog( int nSector )
{
	int nXSector = sector[nSector].extra;
	dassert(nXSector > 0 && nXSector < kMaxXSectors);

	dlgSector[0].value = (short)sector[nSector].type;
	dlgSector[1].value = xsector[nXSector].rxID;
	dlgSector[2].value = xsector[nXSector].txID;
	dlgSector[3].value = xsector[nXSector].state;
	dlgSector[4].value = xsector[nXSector].command;

	dlgSector[6].value = xsector[nXSector].triggerOn;
	dlgSector[7].value = xsector[nXSector].triggerOff;
	dlgSector[8].value = xsector[nXSector].busyTime;
	dlgSector[9].value = xsector[nXSector].waitTime;
	dlgSector[10].value = xsector[nXSector].restState;

	dlgSector[12].value = xsector[nXSector].triggerPush;
	dlgSector[13].value = xsector[nXSector].triggerImpact;
	dlgSector[14].value = xsector[nXSector].triggerReserved0;
	dlgSector[15].value = xsector[nXSector].triggerEnter;
	dlgSector[16].value = xsector[nXSector].triggerExit;
	dlgSector[17].value = xsector[nXSector].triggerWPush;

	dlgSector[19].value = xsector[nXSector].decoupled;
	dlgSector[20].value = xsector[nXSector].triggerOnce;
	dlgSector[21].value = xsector[nXSector].locked;
	dlgSector[22].value = xsector[nXSector].interruptable;

	dlgSector[23].value = xsector[nXSector].data;
	dlgSector[24].value = xsector[nXSector].key;

	dlgSectorFX[1].value = xsector[nXSector].wave;
	dlgSectorFX[2].value = xsector[nXSector].amplitude;
	dlgSectorFX[3].value = xsector[nXSector].freq;
	dlgSectorFX[4].value = xsector[nXSector].phase;
	dlgSectorFX[5].value = xsector[nXSector].shadeFloor;
	dlgSectorFX[6].value = xsector[nXSector].shadeCeiling;
	dlgSectorFX[7].value = xsector[nXSector].shadeWalls;
	dlgSectorFX[8].value = xsector[nXSector].shadeAlways;

	dlgSectorFX[10].value = xsector[nXSector].panVel;
	dlgSectorFX[11].value = xsector[nXSector].panAngle;
	dlgSectorFX[12].value = xsector[nXSector].panFloor;
	dlgSectorFX[13].value = xsector[nXSector].panCeiling;
	dlgSectorFX[14].value = xsector[nXSector].panAlways;
	dlgSectorFX[15].value = xsector[nXSector].drag;

	dlgSectorFX[16].value = xsector[nXSector].depth;
	dlgSectorFX[17].value = xsector[nXSector].underwater;
	dlgSectorFX[18].value = xsector[nXSector].wind;
}


/***********************************************************************
 * DialogToXSector()
 *
 **********************************************************************/
void DialogToXSector( int nSector )
{
	int nXSector = sector[nSector].extra;
	dassert(nXSector > 0 && nXSector < kMaxXSectors);

	sector[nSector].type = (short)dlgSector[0].value;
	xsector[nXSector].rxID = dlgSector[1].value;
	xsector[nXSector].txID = dlgSector[2].value;
	xsector[nXSector].state = dlgSector[3].value;
	xsector[nXSector].command = dlgSector[4].value;

	xsector[nXSector].triggerOn  = dlgSector[6].value;
	xsector[nXSector].triggerOff = dlgSector[7].value;
	xsector[nXSector].busyTime = dlgSector[8].value;
	xsector[nXSector].waitTime = dlgSector[9].value;
	xsector[nXSector].restState = dlgSector[10].value;

	xsector[nXSector].triggerPush		= dlgSector[12].value;
	xsector[nXSector].triggerImpact		= dlgSector[13].value;
	xsector[nXSector].triggerReserved0	= dlgSector[14].value;
	xsector[nXSector].triggerEnter		= dlgSector[15].value;
	xsector[nXSector].triggerExit		= dlgSector[16].value;
	xsector[nXSector].triggerWPush		= dlgSector[17].value;

	xsector[nXSector].decoupled			= dlgSector[19].value;
	xsector[nXSector].triggerOnce		= dlgSector[20].value;
	xsector[nXSector].locked			= dlgSector[21].value;
	xsector[nXSector].interruptable		= dlgSector[22].value;

	xsector[nXSector].data				= dlgSector[23].value;
	xsector[nXSector].key				= dlgSector[24].value;

	xsector[nXSector].wave          = dlgSectorFX[1].value;
	xsector[nXSector].amplitude     = dlgSectorFX[2].value;
	xsector[nXSector].freq        	= dlgSectorFX[3].value;
	xsector[nXSector].phase         = dlgSectorFX[4].value;
	xsector[nXSector].shadeFloor    = dlgSectorFX[5].value;
	xsector[nXSector].shadeCeiling  = dlgSectorFX[6].value;
	xsector[nXSector].shadeWalls    = dlgSectorFX[7].value;
	xsector[nXSector].shadeAlways   = dlgSectorFX[8].value;

	xsector[nXSector].panVel		= dlgSectorFX[10].value;
	xsector[nXSector].panAngle		= dlgSectorFX[11].value;
	xsector[nXSector].panFloor		= dlgSectorFX[12].value;
	xsector[nXSector].panCeiling	= dlgSectorFX[13].value;
	xsector[nXSector].panAlways		= dlgSectorFX[14].value;
	xsector[nXSector].drag			= dlgSectorFX[15].value;

	xsector[nXSector].depth			= dlgSectorFX[16].value;
	xsector[nXSector].underwater	= dlgSectorFX[17].value;
	xsector[nXSector].wind			= dlgSectorFX[18].value;
}


int getpointhighlight(int x, int y)
{
	long i, d, closest, n;

	closest = divscale(gHighlightThreshold, zoom, 14);

	n = -1;

	for (i = 0; i < kMaxSprites; i++)
	{
		if (sprite[i].statnum < kMaxStatus)
		{
			d = qdist(x - sprite[i].x, y - sprite[i].y);
			if (d < closest)
			{
				closest = d;
				n = i | 0x4000;
			}
		}
	}

	for (i = 0; i < numwalls; i++)
	{
		d = qdist(x - wall[i].x, y - wall[i].y);
		if (d < closest)
		{
			closest = d;
			n = i;
		}
	}

	return n;
}


/***********************************************************************
 * ShowSectorData()
 *
 * This function displays the game specific sector data in the status
 * area.
 **********************************************************************/
void ShowSectorData( int nSector )
{
	int nXSector;

	dassert(nSector >= 0 && nSector < kMaxSectors);

	sprintf(buffer, "Sector %d", nSector);
	printmessage16(buffer);

	if (sector[nSector].extra != -1)
	{
		nXSector = sector[nSector].extra;
		dassert(nXSector < kMaxXSectors);

		XSectorToDialog(nSector);
		PaintDialog(dlgSector, LENGTH(dlgSector));
	}
	else
		clearmidstatbar16();
}


/***********************************************************************
 * EditSectorData()
 *
 * This function allows editing of the game specific sector data.
 **********************************************************************/
void EditSectorData( int nSector )
{
	int nXSector;

	dassert(nSector >= 0 && nSector < kMaxSectors);

	CleanUp();

	sprintf(buffer, "Sector %d", nSector);
	printmessage16(buffer);

	nXSector = GetXSector(nSector);
	XSectorToDialog(nSector);
	if ( EditDialog(dlgSector, LENGTH(dlgSector)) )
		DialogToXSector(nSector);

	ShowSectorData(nSector);
	vel = svel = angvel = 0;
	CleanUp();
}


/***********************************************************************
 * ShowWallData()
 *
 * This function displays the game specific wall data in the status
 * area.
 **********************************************************************/
void ShowWallData( int nWall )
{
	int nXWall;

	dassert(nWall >= 0 && nWall < kMaxWalls);
	int nWall2 = wall[nWall].point2;
	int length = qdist(wall[nWall2].x - wall[nWall].x, wall[nWall2].y - wall[nWall].y);

	sprintf(buffer, "Wall %d:  Length = %d", nWall, length);
	printmessage16(buffer);

	if (wall[nWall].extra != -1)
	{
		nXWall = wall[nWall].extra;
		dassert(nXWall < kMaxXWalls);

		XWallToDialog(nWall);
		PaintDialog(dlgWall, LENGTH(dlgWall));
	}
	else
	{
		clearmidstatbar16();
	}
}


/***********************************************************************
 * EditWallData()
 *
 * This function allows editing of the game specific wall data.
 **********************************************************************/
void EditWallData( int nWall )
{
	int nXWall;

	dassert(nWall >= 0 && nWall < kMaxWalls);

	CleanUp();

	sprintf(buffer, "Wall %d", nWall);
	printmessage16(buffer);

	nXWall = GetXWall(nWall);

	XWallToDialog(nWall);

	if ( EditDialog(dlgWall, LENGTH(dlgWall)) )
		DialogToXWall(nWall);

	ShowWallData(nWall);
	vel = svel = angvel = 0;
	CleanUp();
}


/***********************************************************************
 * ShowSpriteData()
 *
 * This function displays the game specific sprite data in the status
 * area.
 **********************************************************************/
void ShowSpriteData( int nSprite )
{
	int nXSprite;

	dassert(nSprite >= 0 && nSprite < kMaxSprites);

	if (sprite[nSprite].extra > 0)
	{
		nXSprite = sprite[nSprite].extra;
		sprintf(buffer, "Sprite %d  Extra %d XRef %d",
			nSprite, nXSprite, xsprite[nXSprite].reference);
	}
	else
	{
		sprintf(buffer, "Sprite %d", nSprite);
	}

	printmessage16(buffer);

	if ( sprite[nSprite].extra > 0 )
	{
		nXSprite = sprite[nSprite].extra;
		dassert(nXSprite < kMaxXSprites);

		XSpriteToDialog(nSprite);
		PaintDialog(dlgSprite, LENGTH(dlgSprite));
	}
	else
		clearmidstatbar16();
}


/***********************************************************************
 * EditSpriteData()
 *
 * This function allows editing of the game specific sprite data.
 **********************************************************************/
void EditSpriteData( int nSprite )
{
	int nXSprite;

	dassert(nSprite >= 0 && nSprite < kMaxSprites);

	CleanUp();

	sprintf(buffer, "Sprite %d", nSprite);
	printmessage16(buffer);

	nXSprite = GetXSprite(nSprite);
	XSpriteToDialog(nSprite);
	if ( EditDialog(dlgSprite, LENGTH(dlgSprite)) )
		DialogToXSprite(nSprite);

	ShowSpriteData(nSprite);
	vel = svel = angvel = 0;
	CleanUp();

	// fix sprite type attributes
	AutoAdjustSprites();
}


int getlinehighlight(int x, int y)
{
	long i, dst, dist, closest, xi, yi;

	dist = divscale(gHighlightThreshold, zoom, 14);
	closest = -1;

	for (i = 0; i < numwalls; i++)
	{
		int lx = wall[wall[i].point2].x - wall[i].x;
		int ly = wall[wall[i].point2].y - wall[i].y;
		int qx = x - wall[i].x;
		int qy = y - wall[i].y;

		// is point on wrong side of wall?
		if ( qx * ly > qy * lx )
			continue;

		int num = dmulscale(qx, lx, qy, ly, 4);
		int den = dmulscale(lx, lx, ly, ly, 4);

		if (num <= 0 || num >= den)
			continue;

		xi = wall[i].x + muldiv(lx, num, den);
		yi = wall[i].y + muldiv(ly, num, den);

		dst = qdist(xi - x, yi - y);
		if (dst <= dist)
		{
			dist = dst;
			closest = i;
		}
	}

	return closest;
}


void setcolor16(int color);
#pragma aux setcolor16 =\
	"mov dx, 0x3ce",\
	"shl ax, 8",\
	"out dx, ax",\
	parm [eax]\

void drawpixel16(int offset);
#pragma aux drawpixel16 =\
	"mov ecx, edi",\
	"and cl, 7",\
	"xor cl, 7",\
	"mov ah, 1",\
	"shl ah, cl",\
	"mov dx, 0x3ce",\
	"mov al, 0x8",\
	"out dx, ax",\
	"shr edi, 3",\
	"or byte ptr [edi+0xA0000], al",\
	parm [edi]\
	modify [eax ecx edx]\


void Draw2dWall( int x0, int y0, int x1, int y1, char color, int thick )
{
	if ( x0 < 0 && x1 < 0 ) return;
	if ( x0 >= 640 && x1 >= 640 ) return;
	if ( y0 < 0 && y1 < 0 ) return;
	if ( y0 >= ydim16 && y1 >= ydim16 ) return;

	drawline16(x0, y0, x1, y1, color);
	if (thick)
	{
		long dx = x1 - x0;
		long dy = y1 - y0;
		RotateVector(&dx, &dy, kAngle45 / 2);
		switch (GetOctant(dx, dy))
		{
			case 0:
			case 4:
				drawline16(x0, y0-1, x1, y1-1, color);
				drawline16(x0, y0+1, x1, y1+1, color);
				break;

			case 1:
			case 5:
				if (qabs(dx) < qabs(dy) )
				{
					drawline16(x0-1, y0+1, x1-1, y1+1, color);
					drawline16(x0-1, y0, x1, y1+1, color);
					drawline16(x0+1, y0, x1+1, y1, color);
				}
				else
				{
					drawline16(x0-1, y0+1, x1-1, y1+1, color);
					drawline16(x0, y0+1, x1, y1+1, color);
					drawline16(x0, y0+1, x1, y1+1, color);
				}
				break;

			case 2:
			case 6:
				drawline16(x0-1, y0, x1-1, y1, color);
				drawline16(x0+1, y0, x1+1, y1, color);
				break;

			case 3:
			case 7:
				if (qabs(dx) < qabs(dy) )
				{
					drawline16(x0-1, y0-1, x1-1, y1-1, color);
					drawline16(x0-1, y0, x1-1, y1, color);
					drawline16(x0+1, y0, x1+1, y1, color);
				}
				else
				{
					drawline16(x0-1, y0-1, x1-1, y1-1, color);
					drawline16(x0, y0-1, x1, y1-1, color);
					drawline16(x0, y0+1, x1, y1+1, color);
				}
				break;
		}
	}
}

void Draw2dVertex( int x, int y, int color )
{
	long templong = y * 640 + x + pageoffset;
	setcolor16(color);
	drawpixel16(templong-2-1280);
	drawpixel16(templong-1-1280);
	drawpixel16(templong+0-1280);
	drawpixel16(templong+1-1280);
	drawpixel16(templong+2-1280);

	drawpixel16(templong-2-640);
	drawpixel16(templong-2+0);
	drawpixel16(templong-2+640);

	drawpixel16(templong+2-640);
	drawpixel16(templong+2+0);
	drawpixel16(templong+2+640);

	drawpixel16(templong-2+1280);
	drawpixel16(templong-1+1280);
	drawpixel16(templong+0+1280);
	drawpixel16(templong+1+1280);
	drawpixel16(templong+2+1280);
}


void Draw2dFaceSprite( int x, int y, int color)
{
	long templong = y * 640 + x + pageoffset;
	setcolor16(color);

	drawpixel16(templong - 1 - 1920);
	drawpixel16(templong + 0 - 1920);
	drawpixel16(templong + 1 - 1920);

	drawpixel16(templong - 2 - 1280);
	drawpixel16(templong + 2 - 1280);

	drawpixel16(templong - 3 - 640);
	drawpixel16(templong + 3 - 640);

	drawpixel16(templong - 3 + 0);
	drawpixel16(templong + 3 + 0);

	drawpixel16(templong - 3 + 640);
	drawpixel16(templong + 3 + 640);

	drawpixel16(templong - 2 + 1280);
	drawpixel16(templong + 2 + 1280);

	drawpixel16(templong - 1 + 1920);
	drawpixel16(templong + 0 + 1920);
	drawpixel16(templong + 1 + 1920);
}


void Draw2dFloorSprite( int x, int y, char color)
{
	drawline16(x - 3, y - 3, x + 3, y - 3, color);
	drawline16(x - 3, y + 3, x + 3, y + 3, color);
	drawline16(x - 3, y - 3, x - 3, y + 3, color);
	drawline16(x + 3, y - 3, x + 3, y + 3, color);
}


void Draw2dMarker( int x, int y, int color)
{
	long templong = y * 640 + x + pageoffset;
	setcolor16(color);

	drawpixel16(templong - 1 - 2560);
	drawpixel16(templong + 0 - 2560);
	drawpixel16(templong + 1 - 2560);

	drawpixel16(templong - 3 - 1920);
	drawpixel16(templong - 2 - 1920);
	drawpixel16(templong + 2 - 1920);
	drawpixel16(templong + 3 - 1920);

	drawpixel16(templong - 3 - 1280);
	drawpixel16(templong - 2 - 1280);
	drawpixel16(templong + 2 - 1280);
	drawpixel16(templong + 3 - 1280);

	drawpixel16(templong - 4 - 640);
	drawpixel16(templong - 1 - 640);
	drawpixel16(templong + 1 - 640);
	drawpixel16(templong + 4 - 640);

	drawpixel16(templong - 4);
	drawpixel16(templong + 0);
	drawpixel16(templong + 4);

	drawpixel16(templong - 4 + 640);
	drawpixel16(templong - 1 + 640);
	drawpixel16(templong + 1 + 640);
	drawpixel16(templong + 4 + 640);

	drawpixel16(templong - 3 + 1280);
	drawpixel16(templong - 2 + 1280);
	drawpixel16(templong + 2 + 1280);
	drawpixel16(templong + 3 + 1280);

	drawpixel16(templong - 3 + 1920);
	drawpixel16(templong - 2 + 1920);
	drawpixel16(templong + 2 + 1920);
	drawpixel16(templong + 3 + 1920);

	drawpixel16(templong - 1 + 2560);
	drawpixel16(templong + 0 + 2560);
	drawpixel16(templong + 1 + 2560);
}


void draw2dscreen(long x, long y, short nAngle, long nZoom, short nGrid )
{
	int i, j, x0, y0, x1, y1;
	char nColor;
	char h = (gFrameClock & 0x08) ? 8 : 0;
	int thick;
	short flags;

	// steal a copy of these variables, because Ken made them static!
	grid = nGrid;
	zoom = nZoom;

	if (qsetmode == 200)
		return;

	for (i = 0; i < numwalls; i++)
	{
		// only draw one side of walls
		if ( wall[i].nextwall > i)
			continue;

		if (wall[i].nextwall == -1)
		{
			nColor = kColorWhite;
			flags = wall[i].cstat;
			thick = 0;
		}
		else
		{
			nColor = kColorRed;
			thick = 0;

			flags = wall[i].cstat | wall[wall[i].nextwall].cstat;

			// if one side is highlighted, only show that side's flags
			if (i == linehighlight)
				flags = wall[i].cstat;
			else if (wall[i].nextwall == linehighlight)
				flags = wall[wall[i].nextwall].cstat;

			if ( flags & kWallHitscan)
				nColor = kColorMagenta;
			if ( flags & kWallBlocking)
				thick = 1;
		}
		if ( flags & kWallMoveForward )
			nColor = kColorLightBlue;
		if ( flags & kWallMoveBackward )
			nColor = kColorLightGreen;

		if ( linehighlight >= 0 && (i == linehighlight || wall[i].nextwall == linehighlight) )
			nColor ^= h;

		x0 = 320 + mulscale(wall[i].x - x, nZoom, 14);
		y0 = 200 + mulscale(wall[i].y - y, nZoom, 14);
		x1 = 320 + mulscale(wall[wall[i].point2].x - x, nZoom, 14);
		y1 = 200 + mulscale(wall[wall[i].point2].y - y, nZoom, 14);

		Draw2dWall( x0, y0, x1, y1, nColor, thick);
		if (nZoom >= 256 && x0 > 4 && x0 < 635 && y0 > 4 && y0 < ydim16-5 )
			Draw2dVertex(x0, y0, kColorBrown);
	}

	// redraw highlighted vertices
	if (nZoom >= 256)
	{
		for (i = 0; i < numwalls; i++)
		{
			if ( i == pointhighlight || (highlightcnt > 0 && TestBitString(show2dwall, i)) )
			{
				x0 = 320 + mulscale(wall[i].x - x, nZoom, 14);
				y0 = 200 + mulscale(wall[i].y - y, nZoom, 14);
				if ( x0 > 4 && x0 < 635 && y0 > 4 && y0 < ydim16-5 )
					Draw2dVertex(x0, y0, kColorBrown ^ h);
			}
		}
	}

	if (nZoom >= 256)
	{
		for (i = 0; i < numsectors; i++)
		{
			j = headspritesect[i];
			while (j != -1)
			{
				x0 = 320 + mulscale(sprite[j].x - x, nZoom, 14);
				y0 = 200 + mulscale(sprite[j].y - y, nZoom, 14);

				if ( sprite[j].statnum == kStatMarker )
				{
					nColor = kColorYellow;

					if ( sprite[j].owner == sectorhighlight )
						nColor = kColorWhite;

					if ((j | 0x4000) == pointhighlight || (highlightcnt > 0 && TestBitString(show2dsprite, j)) )
						nColor ^= h;

					if (x0 > 4 && x0 < 635 && y0 > 4 && y0 < ydim16-5)
						switch( sprite[j].type )
						{
							case kMarkerOff:
							case kMarkerOn:
								Draw2dMarker(x0, y0, nColor);
								break;

							case kMarkerWarpDest:
							case kMarkerAxis:
								Draw2dMarker(x0, y0, nColor);
								x1 = mulscale30(nZoom / 128, Cos(sprite[j].ang));
								y1 = mulscale30(nZoom / 128, Sin(sprite[j].ang));
								Draw2dWall(x0, y0, x0 + x1, y0 + y1, nColor, thick);
								break;
						}

					if ( sprite[j].type == kMarkerOff )
					{
						// draw movement arrow
						int nSector = sprite[j].owner;
						int nXSector = sector[nSector].extra;
						dassert(nXSector > 0 && nXSector < kMaxXSectors);
						int nSprite2 = xsector[nXSector].marker1;
						x1 = 320 + mulscale(sprite[nSprite2].x - x, nZoom, 14);
						y1 = 200 + mulscale(sprite[nSprite2].y - y, nZoom, 14);
						drawline16(x0, y0, x1, y1, kColorLightBlue);
						int nAngle = getangle(x0 - x1, y0 - y1);
						x0 = mulscale30(nZoom / 64, Cos(nAngle + kAngle30));
						y0 = mulscale30(nZoom / 64, Sin(nAngle + kAngle30));
						drawline16(x1, y1, x1 + x0, y1 + y0, kColorLightBlue);
						x0 = mulscale30(nZoom / 64, Cos(nAngle - kAngle30));
						y0 = mulscale30(nZoom / 64, Sin(nAngle - kAngle30));
						drawline16(x1, y1, x1 + x0, y1 + y0, kColorLightBlue);

					}
				}
				else
				{
					nColor = kColorCyan;
					thick = 0;

					if ( sprite[j].cstat & kSpriteHitscan )
						nColor = kColorMagenta;

					if ( sprite[j].cstat & kSpriteMoveForward )
						nColor = kColorLightBlue;

					if ( sprite[j].cstat & kSpriteMoveReverse )
						nColor = kColorLightGreen;

					if ( sprite[j].cstat & kSpriteBlocking )
						thick = 1;

					if ((j | 0x4000) == pointhighlight || (highlightcnt > 0 && TestBitString(show2dsprite, j)) )
						nColor ^= h;

					if (x0 > 4 && x0 < 635 && y0 > 4 && y0 < ydim16-5)
					{
						switch (sprite[j].cstat & kSpriteRMask)
						{
							case kSpriteFace:
							case kSpriteSpin:
								Draw2dFaceSprite(x0, y0, nColor);
								x1 = mulscale30(nZoom / 128, Cos(sprite[j].ang));
								y1 = mulscale30(nZoom / 128, Sin(sprite[j].ang));
								Draw2dWall(x0, y0, x0 + x1, y0 + y1, nColor, thick);
								break;

							case kSpriteWall:
								Draw2dFaceSprite(x0, y0, nColor);
								x1 = mulscale30(nZoom / 128, Cos(sprite[j].ang + kAngle90));
								y1 = mulscale30(nZoom / 128, Sin(sprite[j].ang + kAngle90));
								Draw2dWall(x0 - x1, y0 - y1, x0 + x1, y0 + y1, nColor, thick);
								x1 = mulscale30(nZoom / 256, Cos(sprite[j].ang));
								y1 = mulscale30(nZoom / 256, Sin(sprite[j].ang));
								if (sprite[j].cstat & kSpriteOneSided)
									Draw2dWall(x0, y0, x0 + x1, y0 + y1, nColor, thick);
								else
									Draw2dWall(x0 - x1, y0 - y1, x0 + x1, y0 + y1, nColor, thick);
								break;

							case kSpriteFloor:
								Draw2dFloorSprite(x0, y0, nColor);
								x1 = mulscale30(nZoom / 256, Cos(sprite[j].ang));
								y1 = mulscale30(nZoom / 256, Sin(sprite[j].ang));
								Draw2dWall(x0, y0, x0 + x1, y0 + y1, nColor, thick);
								break;
						}
					}
				}
				j = nextspritesect[j];
			}
		}
	}

	x0 = 320 + mulscale30(nZoom / 128, Cos(nAngle)); //Draw white arrow
	y0 = 200 + mulscale30(nZoom / 128, Sin(nAngle));
	drawline16(x0, y0, 640-x0,400-y0,kColorWhite);
	drawline16(x0, y0, 120+y0,520-x0,kColorWhite);
	drawline16(x0, y0, 520-y0,-120+x0,kColorWhite);
}


void ProcessKeys2D( void )
{
	BYTE key;
	BYTE shift, ctrl, alt;
	int x, y;
	static int nPointLast = -1, nLineLast = -1, nSectorLast = -1;
	static int oldnumwalls, oldnumsectors, oldnumsprites;
	int numsprites = 0;

	// redetermine numsprites (build.c version is static!)
	numsprites = 0;
	for (int i = 0; i < kMaxSprites; i++)
		if ( sprite[i].statnum < kMaxStatus )
			numsprites++;

	// did anything important happen?
	if (numwalls != oldnumwalls || numsectors != oldnumsectors || numsprites != oldnumsprites)
	{
		CleanUp();

		// fix sprite type attributes
		AutoAdjustSprites();

		oldnumwalls = numwalls;
		oldnumsectors = numsectors;
		oldnumsprites = numsprites;
	}

	shift = keystatus[KEY_LSHIFT] | keystatus[KEY_RSHIFT];
	ctrl = keystatus[KEY_LCTRL] | keystatus[KEY_RCTRL];
	alt = keystatus[KEY_LALT] | keystatus[KEY_RALT];

	getpoint(searchx, searchy, &x, &y);

	updatesector(x, y, &sectorhighlight);

	if (pointhighlight != nPointLast || linehighlight != nLineLast || sectorhighlight != nSectorLast)
	{
		nPointLast = pointhighlight;
		nLineLast = linehighlight;
		nSectorLast = sectorhighlight;

		if ((pointhighlight & 0xC000) == 0x4000)
			ShowSpriteData(pointhighlight & 0x3FFF);
		else if (linehighlight >= 0)
			ShowWallData(linehighlight);
		else if (sectorhighlight >= 0)
			ShowSectorData(sectorhighlight);
		else
			clearmidstatbar16();
	}

	key = keyGet();

	switch (key)
	{
		case KEY_HOME:
			if (ctrl)
			{
				short nSprite = getnumber16("Locate sprite #: ",0,kMaxSprites);
				clearmidstatbar16();
				if (nSprite >= 0 && nSprite < kMaxSprites && sprite[nSprite].statnum < kMaxStatus)
				{
					posx	= sprite[nSprite].x;
					posy	= sprite[nSprite].y;
					posz	= sprite[nSprite].z;
					ang		= sprite[nSprite].ang;
					cursectnum = sprite[nSprite].sectnum;
				}
				else
					printmessage16("Sir Not Appearing In This Film");

			}
			break;

		case KEY_K:		// Kinetic sprites/walls
			if ( (pointhighlight & 0xC000) == 0x4000 )	// is a sprite highlighted
			{
				short flags = (short)(sprite[pointhighlight & 0x3FFF].cstat & kSpriteMoveMask);
				switch (flags)
				{
					case kSpriteMoveNone:
						flags = kSpriteMoveForward;
						break;

					case kSpriteMoveForward:
						flags = kSpriteMoveReverse;
						break;

					case kSpriteMoveReverse:
						flags = kSpriteMoveNone;
						break;

					default:
						flags = kSpriteMoveNone;
				}

				sprite[pointhighlight & 0x3FFF].cstat &= ~kSpriteMoveMask;
				sprite[pointhighlight & 0x3FFF].cstat |= flags;
				BeepOkay();
			}
			else if ( linehighlight >=0 )
			{
				short flags = (short)(wall[linehighlight].cstat & kWallMoveMask);
				switch (flags)
				{
					case kWallMoveNone:
						flags = kWallMoveForward;
						break;

					case kWallMoveForward:
						flags = kWallMoveBackward;
						break;

					case kWallMoveBackward:
						flags = kWallMoveNone;
						break;

					default:
						flags = kWallMoveNone;
				}

				wall[linehighlight].cstat &= ~kWallMoveMask;
				wall[linehighlight].cstat |= flags;
				BeepOkay();
			}
			else
				BeepError();
			break;

		case KEY_M:		// Masked walls
			if ( linehighlight >=0 )
			{
				int nWall = linehighlight;
				int nWall2 = wall[nWall].nextwall;

				if (nWall2 < 0)
				{
					BeepError();
					break;
				}

				wall[nWall].cstat |= kWallMasked;
				wall[nWall].cstat &= ~kWallFlipX;
				wall[nWall2].cstat |= kWallFlipX;           //auto other-side flip
				wall[nWall2].cstat |= kWallMasked;
				if (wall[nWall].overpicnum < 0)
					wall[nWall].overpicnum = 0;
				wall[nWall2].overpicnum = wall[nWall].overpicnum;
				wall[nWall].cstat &= ~kWallOneWay;
				wall[nWall2].cstat &= ~kWallOneWay;

				sprintf(buffer, "wall[%i] %s masked", searchwall,
					(wall[searchwall].cstat & kWallMasked) ? "is" : "not");
				printmessage16(buffer);

				BeepOkay();
				break;
			}
			else
				BeepError();
			break;

		case KEY_LBRACE:
			if ( (pointhighlight & 0xC000) == 0x4000 )	// is a sprite highlighted
			{
				int nSprite = pointhighlight & 0x3FFF;

				// rotate sprite ccw
				int step = shift ? 16 : 256;
				sprite[nSprite].ang = (short)DecNext(sprite[nSprite].ang, step);
				sprintf(buffer, "sprite[%i].ang: %i", nSprite, sprite[nSprite].ang);
				printmessage16(buffer);
				BeepOkay();
			}
			break;

		case KEY_RBRACE:
			if ( (pointhighlight & 0xC000) == 0x4000 )	// is a sprite highlighted
			{
				int nSprite = pointhighlight & 0x3FFF;

				// rotate sprite ccw
				int step = shift ? 16 : 256;
				sprite[nSprite].ang = (short)IncNext(sprite[nSprite].ang, step);
				sprintf(buffer, "sprite[%i].ang: %i", nSprite, sprite[nSprite].ang);
				printmessage16(buffer);
				BeepOkay();
			}
			break;
	}

	// let's see if this fixes the input buffering problem
	if (key != 0)
		keyFlushStream();
}
