#include "diablo.h"
#include "../3rdParty/Storm/Source/storm.h"
#include "../DiabloUI/diabloui.h"

HWND ghMainWnd;
int glMid1Seed[NUMLEVELS];
int glMid2Seed[NUMLEVELS];
int gnLevelTypeTbl[NUMLEVELS];
int MouseY; // idb
int MouseX; // idb
BOOL gbGameLoopStartup; // idb
int glSeedTbl[NUMLEVELS];
int gbRunGame; // weak
int glMid3Seed[NUMLEVELS];
int gbRunGameResult; // weak
int zoomflag; // weak
int gbProcessPlayers; // weak
int glEndSeed[NUMLEVELS];
int gbLoadGame; // weak
HINSTANCE ghInst; // idb
int DebugMonsters[10];
char cineflag; // weak
int force_redraw; // weak
int visiondebug; // weak
int scrollflag; /* unused */
int light4flag; // weak
int leveldebug; // weak
int monstdebug; // weak
int trigdebug; /* unused */
int setseed; // weak
int debugmonsttypes; // weak
int PauseMode; // weak
int sgnTimeoutCurs;
char sgbMouseDown; // weak
int color_cycle_timer; // weak

/* rdata */

int fullscreen = 1; // weak
#ifdef _DEBUG
int showintrodebug = 1;
int questdebug = -1;
int debug_mode_key_s;
int debug_mode_key_w;
int debug_mode_key_inverted_v;
int debug_mode_dollar_sign;
int debug_mode_key_d;
int debug_mode_key_i;
int dbgplr;
int dbgqst;
int dbgmon;
int arrowdebug;
int frameflag;
int frameend;
int framerate;
int framestart;
#endif
int FriendlyMode = 1; // weak
char *spszMsgTbl[4] =
{
  "I need help! Come Here!",
  "Follow me.",
  "Here's something for you.",
  "Now you DIE!"
}; // weak
char *spszMsgKeyTbl[4] = { "F9", "F10", "F11", "F12" }; // weak

void FreeGameMem()
{
	music_stop();

	MemFreeDbg(pDungeonCels);
	MemFreeDbg(pMegaTiles);
	MemFreeDbg(pLevelPieces);
	MemFreeDbg(pSpecialCels);
	MemFreeDbg(pSpeedCels);

	FreeMissiles();
	FreeMonsters();
	FreeObjectGFX();
	FreeMonsterSnd();
	FreeTownerGFX();
}

BOOL StartGame(BOOL bNewGame, BOOL bSinglePlayer)
{
	BOOL fExitProgram;
	unsigned int uMsg;

	gbSelectProvider = 1;

	do {
		fExitProgram = FALSE;
		gbLoadGame = 0;

		if(!NetInit(bSinglePlayer, &fExitProgram)) {
			gbRunGameResult = fExitProgram == FALSE;
			break;
		}

		gbSelectProvider = 0;

		if(bNewGame || !gbValidSaveFile) {
			InitLevels();
			InitQuests();
			InitPortals();
			InitDungMsgs(myplr);
		}
		if(!gbValidSaveFile || !gbLoadGame)
			uMsg = WM_DIABNEWGAME;
		else
			uMsg = WM_DIABLOADGAME;

		run_game_loop(uMsg);
		NetClose();
		pfile_create_player_description(0, 0);
	} while(gbRunGameResult);

	SNetDestroy();
	return gbRunGameResult;
}

void run_game_loop(unsigned int uMsg)
{
	BOOL bLoop;
	WNDPROC saveProc;
	MSG msg;

	nthread_ignore_mutex(TRUE);
	start_game(uMsg);
	/// ASSERT: assert(ghMainWnd);
	saveProc = SetWindowProc(GM_Game);
	control_update_life_mana();
	run_delta_info();
	gbRunGame = TRUE;
	gbProcessPlayers = TRUE;
	gbRunGameResult = TRUE;
	force_redraw = 255;
	DrawAndBlit();
	PaletteFadeIn(8);
	force_redraw = 255;
	gbGameLoopStartup = TRUE;
	nthread_ignore_mutex(FALSE);

	while(gbRunGame) {
		diablo_color_cyc_logic();
		if(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
			SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
			while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
				if(msg.message == WM_QUIT) {
					gbRunGameResult = FALSE;
					gbRunGame = FALSE;
					break;
				}
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			bLoop = gbRunGame && nthread_has_500ms_passed(FALSE);
			SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
			if(!bLoop) {
				continue;
			}
		} else if(!nthread_has_500ms_passed(FALSE)) {
#ifdef SLEEPFIX
			Sleep(1);
#endif
			continue;
		}
		multi_process_network_packets();
		game_loop(gbGameLoopStartup);
		msgcmd_send_chat();
		gbGameLoopStartup = FALSE;
		DrawAndBlit();
	}

	if(gbMaxPlayers > 1) {
		pfile_write_hero();
	}

	pfile_flush_W();
	PaletteFadeOut(8);
	SetCursor_(0);
	ClearScreenBuffer();
	force_redraw = 255;
	scrollrt_draw_game_screen(TRUE);
	saveProc = SetWindowProc(saveProc);
	/// ASSERT: assert(saveProc == GM_Game);
	free_game();

	if(cineflag) {
		cineflag = FALSE;
		DoEnding();
	}
}

void start_game(unsigned int uMsg)
{
	zoomflag = 1;
	cineflag = 0;
	InitCursor();
	InitLightTable();
	LoadDebugGFX();
	/// ASSERT: assert(ghMainWnd);
	music_stop();
	ShowProgress(uMsg);
	gmenu_init_menu();
	InitLevelCursor();
	sgnTimeoutCurs = 0;
	sgbMouseDown = 0;
	track_repeat_walk(0);
}

void free_game()
{
	int i;

	FreeControlPan();
	FreeInvGFX();
	FreeGMenu();
	FreeQuestText();
	FreeStoreMem();

	for(i = 0; i < MAX_PLRS; i++) {
		FreePlayerGFX(i);
	}

	FreeItemGFX();
	FreeCursor();
	FreeLightTable();
	FreeDebugGFX();
	FreeGameMem();
}

BOOL diablo_get_not_running()
{
	SetLastError(0);
	CreateEvent(NULL, FALSE, FALSE, "DiabloEvent");
	return GetLastError() != ERROR_ALREADY_EXISTS;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HINSTANCE hInst;
	DWORD dwData;
	char szFileName[MAX_PATH];

	hInst = hInstance;
#ifndef DEBUGGER
	diablo_reload_process(hInstance);
#endif
	ghInst = hInst;

	if(RestrictedTest())
		ErrOkDlg(IDD_DIALOG10, 0, "C:\\Src\\Diablo\\Source\\DIABLO.CPP", 877);
	if(ReadOnlyTest()) {
		if(!GetModuleFileName(ghInst, szFileName, sizeof(szFileName)))
			szFileName[0] = '\0';
		DirErrorDlg(szFileName);
	}

	ShowCursor(FALSE);
	srand(GetTickCount());
	InitHash();
	exception_get_filter();

	BOOL bNoEvent = diablo_get_not_running();
	if(!diablo_find_window("DIABLO") && bNoEvent) {
#ifdef _DEBUG
		SFileEnableDirectAccess(TRUE);
#endif
		diablo_init_screen();
		diablo_parse_flags(lpCmdLine);
		init_create_window(nCmdShow);
		ui_sound_init();
		UiInitialize();

#ifdef _DEBUG
		if(showintrodebug)
#endif
			play_movie("gendata\\logo.smk", TRUE);

		char szValueName[] = "Intro";
		if(!SRegLoadValue("Diablo", szValueName, 0, &dwData))
			dwData = 1;
		if(dwData)
			play_movie("gendata\\diablo1.smk", TRUE);
		SRegSaveValue("Diablo", szValueName, 0, 0);

#ifdef _DEBUG
		if(showintrodebug) {
#endif
			UiTitleDialog(7);
			BlackPalette();
#ifdef _DEBUG
		}
#endif

		mainmenu_loop();
		UiDestroy();
		SaveGamma();

		if(ghMainWnd) {
			Sleep(300);
			DestroyWindow(ghMainWnd);
		}
	}

	return FALSE;
}

void diablo_parse_flags(char *args)
{
	char c;
#ifdef _DEBUG
	int i;
#endif

	while(*args != '\0') {
		while(isspace(*args)) {
			args++;
		}
		if(_strnicmp("dd_emulate", args, strlen("dd_emulate")) == 0) {
			gbEmulate = 1;
			args += strlen("dd_emulate");
		} else if(_strnicmp("dd_backbuf", args, strlen("dd_backbuf")) == 0) {
			gbBackBuf = 1;
			args += strlen("dd_backbuf");
		} else if(_strnicmp("ds_noduplicates", args, strlen("ds_noduplicates")) == 0) {
			gbDupSounds = 0;
			args += strlen("ds_noduplicates");
		} else {
			c = tolower(*args);
			args++;
#ifdef _DEBUG
			switch(c) {
			case '^':
				debug_mode_key_inverted_v = 1;
				break;
			case '$':
				debug_mode_dollar_sign = 1;
				break;
			case 'b':
			/*
				debug_mode_key_b = 1;
			*/
				break;
			case 'd':
				showintrodebug = 0;
				debug_mode_key_d = 1;
				break;
			case 'f':
				EnableFrameCount();
				break;
			case 'i':
				debug_mode_key_i = 1;
				break;
			case 'j':
			/*
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				debug_mode_key_J_trigger = i;
			*/
				break;
			case 'l':
				setlevel = 0;
				leveldebug = 1;
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				leveltype = i;
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				currlevel = i;
				plr[0].plrlevel = i;
				break;
			case 'm':
				monstdebug = 1;
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				DebugMonsters[debugmonsttypes++] = i;
				break;
			case 'n':
				showintrodebug = 0;
				break;
			case 'q':
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				questdebug = i;
				break;
			case 'r':
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				setseed = i;
				break;
			case 's':
				debug_mode_key_s = 1;
				break;
			case 't':
				leveldebug = 1;
				setlevel = 1;
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				setlvlnum = i;
				break;
			case 'v':
				visiondebug = 1;
				break;
			case 'w':
				debug_mode_key_w = 1;
				break;
			case 'x':
				fullscreen = 0;
				break;
			}
#endif
		}
	}
}

void diablo_init_screen()
{
	int i;

	MouseX = SCREEN_WIDTH / 2;
	MouseY = SCREEN_HEIGHT / 2;
	ScrollInfo._sdx = 0;
	ScrollInfo._sdy = 0;
	ScrollInfo._sxoff = 0;
	ScrollInfo._syoff = 0;
	ScrollInfo._sdir = 0;

	for(i = 0; i < 1024; i++) {
		PitchTbl[i] = i * BUFFER_WIDTH;
	}

	ClrDiabloMsg();
}

BOOL diablo_find_window(LPCSTR lpClassName)
{
	HWND result; // eax
	HWND v2; // esi
	HWND v3; // eax
	HWND v4; // edi

	result = FindWindow(lpClassName, 0);
	v2 = result;
	if ( !result )
		return 0;

	v3 = GetLastActivePopup(result);
	if ( v3 )
		v2 = v3;
	v4 = GetTopWindow(v2);
	if ( !v4 )
		v4 = v2;
	SetForegroundWindow(v2);
	SetFocus(v4);
	return 1;
}

void diablo_reload_process(HINSTANCE hInstance)
{
	DWORD dwSize, dwProcessId;
	BOOL bNoExist;
	char *s;
	long *plMap;
	HWND hWnd, hPrev;
	HANDLE hMap;
	STARTUPINFO si;
	SYSTEM_INFO sinf;
	PROCESS_INFORMATION pi;
	char szReload[MAX_PATH + 16];
	char szFileName[MAX_PATH] = "";

	GetModuleFileName(hInstance, szFileName, sizeof(szFileName));
	wsprintf(szReload, "Reload-%s", szFileName);
	for(s = szReload; *s != '\0'; s++) {
		if(*s == '\\') {
			*s = '/';
		}
	}

	GetSystemInfo(&sinf);
	dwSize = sinf.dwPageSize;
	if(dwSize < 4096) {
		dwSize = 4096;
	}

	hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, SEC_COMMIT | PAGE_READWRITE, 0, dwSize, szReload);
	bNoExist = GetLastError() != ERROR_ALREADY_EXISTS;
	if(hMap == NULL) {
		return;
	}
	plMap = (long *)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, dwSize);
	if(plMap == NULL) {
		return;
	}

	if(bNoExist) {
		plMap[0] = -1;
		plMap[1] = 0;
		memset(&si, 0, sizeof(si));
		si.cb = sizeof(si);
		CreateProcess(szFileName, NULL, NULL, NULL, FALSE, CREATE_NEW_PROCESS_GROUP, NULL, NULL, &si, &pi);
		WaitForInputIdle(pi.hProcess, INFINITE);
		CloseHandle(pi.hThread);
		CloseHandle(pi.hProcess);
		while(plMap[0] < 0) {
			Sleep(1000);
		}
		UnmapViewOfFile(plMap);
		CloseHandle(hMap);
		ExitProcess(0);
	}

	if(InterlockedIncrement(plMap) == 0) {
		plMap[1] = GetCurrentProcessId();
	} else {
		hPrev = GetForegroundWindow();
		hWnd = hPrev;
		while(1) {
			hPrev = GetWindow(hPrev, GW_HWNDPREV);
			if(hPrev == NULL) {
				break;
			}
			hWnd = hPrev;
		}
		while(1) {
			GetWindowThreadProcessId(hWnd, &dwProcessId);
			if(dwProcessId == plMap[1]) {
				SetForegroundWindow(hWnd);
				break;
			}
			hWnd = GetWindow(hWnd, GW_HWNDNEXT);
			if(hWnd == NULL) {
				break;
			}
		}
		UnmapViewOfFile(plMap);
		CloseHandle(hMap);
		ExitProcess(0);
	}
}

BOOL PressEscKey()
{
	BOOL rv = FALSE;

	if(doomflag) {
		doom_close();
		rv = TRUE;
	}
	if(helpflag) {
		helpflag = 0;
		rv = TRUE;
	}

	if(qtextflag) {
		qtextflag = 0;
		stream_stop();
		rv = TRUE;
	} else if(stextflag) {
		STextESC();
		rv = TRUE;
	}

	if(msgflag) {
		msgdelay = 0;
		rv = TRUE;
	}
	if(talkflag) {
		control_reset_talk();
		rv = TRUE;
	}
	if(dropGoldFlag) {
		control_drop_gold(VK_ESCAPE);
		rv = TRUE;
	}
	if(spselflag) {
		spselflag = 0;
		rv = TRUE;
	}

	return rv;
}

LRESULT CALLBACK DisableInputWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg) {
	case WM_KEYDOWN:
	case WM_KEYUP:
	case WM_CHAR:
	case WM_SYSKEYDOWN:
	case WM_SYSCOMMAND:
	case WM_MOUSEMOVE:
		return 0;
	case WM_LBUTTONDOWN:
		if(sgbMouseDown == 0) {
			sgbMouseDown = 1;
			SetCapture(hWnd);
		}
		return 0;
	case WM_LBUTTONUP:
		if(sgbMouseDown == 1) {
			sgbMouseDown = 0;
			ReleaseCapture();
		}
		return 0;
	case WM_RBUTTONDOWN:
		if(sgbMouseDown == 0) {
			sgbMouseDown = 2;
			SetCapture(hWnd);
		}
		return 0;
	case WM_RBUTTONUP:
		if(sgbMouseDown == 2) {
			sgbMouseDown = 0;
			ReleaseCapture();
		}
		return 0;
	case WM_CAPTURECHANGED:
		if(hWnd != (HWND)lParam)
			sgbMouseDown = 0;
		return 0;
	}

	return MainWndProc(hWnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK GM_Game(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg) {
	case WM_KEYDOWN:
		PressKey(wParam);
		return 0;
	case WM_KEYUP:
		ReleaseKey(wParam);
		return 0;
	case WM_CHAR:
		PressChar(wParam);
		return 0;
	case WM_SYSKEYDOWN:
		if(PressSysKey(wParam))
			return 0;
		break;
	case WM_SYSCOMMAND:
		if(wParam == SC_CLOSE) {
			gbRunGame = FALSE;
			gbRunGameResult = FALSE;
			return 0;
		}
		break;
	case WM_MOUSEMOVE:
		MouseX = LOWORD(lParam);
		MouseY = HIWORD(lParam);
		gmenu_on_mouse_move();
		return 0;
	case WM_LBUTTONDOWN:
		MouseX = LOWORD(lParam);
		MouseY = HIWORD(lParam);
		if(sgbMouseDown == 0) {
			sgbMouseDown = 1;
			SetCapture(hWnd);
			track_repeat_walk(LeftMouseDown(wParam));
		}
		return 0;
	case WM_LBUTTONUP:
		MouseX = LOWORD(lParam);
		MouseY = HIWORD(lParam);
		if(sgbMouseDown == 1) {
			sgbMouseDown = 0;
			LeftMouseUp();
			track_repeat_walk(FALSE);
			ReleaseCapture();
		}
		return 0;
	case WM_RBUTTONDOWN:
		MouseX = LOWORD(lParam);
		MouseY = HIWORD(lParam);
		if(sgbMouseDown == 0) {
			sgbMouseDown = 2;
			SetCapture(hWnd);
			RightMouseDown();
		}
		return 0;
	case WM_RBUTTONUP:
		MouseX = LOWORD(lParam);
		MouseY = HIWORD(lParam);
		if(sgbMouseDown == 2) {
			sgbMouseDown = 0;
			ReleaseCapture();
		}
		return 0;
	case WM_CAPTURECHANGED:
		if(hWnd != (HWND)lParam) {
			sgbMouseDown = 0;
			track_repeat_walk(FALSE);
		}
		break;
	case WM_DIABNEXTLVL:
	case WM_DIABPREVLVL:
	case WM_DIABRTNLVL:
	case WM_DIABSETLVL:
	case WM_DIABWARPLVL:
	case WM_DIABTOWNWARP:
	case WM_DIABTWARPUP:
	case WM_DIABRETOWN:
		if(gbMaxPlayers > 1)
			pfile_write_hero();
		nthread_ignore_mutex(TRUE);
		PaletteFadeOut(8);
		sound_stop();
		music_stop();
		track_repeat_walk(FALSE);
		sgbMouseDown = 0;
		ReleaseCapture();
		ShowProgress(uMsg);
		force_redraw = 255;
		DrawAndBlit();
		if(gbRunGame)
			PaletteFadeIn(8);
		nthread_ignore_mutex(FALSE);
		gbGameLoopStartup = TRUE;
		return 0;
	}

	return MainWndProc(hWnd, uMsg, wParam, lParam);
}

BOOL LeftMouseDown(int wParam)
{
	if ( gmenu_left_mouse(1) || control_check_talk_btn() || sgnTimeoutCurs )
		return 0;
	if ( deathflag )
	{
		control_check_btn_press();
		return 0;
	}
	if ( PauseMode == 2 )
		return 0;
	if ( doomflag )
	{
		doom_close();
		return 0;
	}
	if ( spselflag )
	{
		SetSpell();
		return 0;
	}
	if ( stextflag )
	{
		CheckStoreBtn();
		return 0;
	}
	if ( MouseY >= 352 )
	{
		if ( !talkflag && !dropGoldFlag )
		{
			if ( !gmenu_is_active() )
				CheckInvScrn();
		}
		DoPanBtn();
		if ( pcurs <= 1 || pcurs >= 12 )
			return 0;
		goto LABEL_48;
	}
	if ( gmenu_is_active() || TryIconCurs() )
		return 0;
	if ( questlog && MouseX > 32 && MouseX < 288 && MouseY > 32 && MouseY < 308 )
	{
		QuestlogESC();
		return 0;
	}
	if ( qtextflag )
	{
		qtextflag = 0;
		stream_stop();
		return 0;
	}
	if ( chrflag && MouseX < 320 )
	{
		CheckChrBtns();
		return 0;
	}
	if ( invflag && MouseX > 320 )
	{
		if ( !dropGoldFlag )
			CheckInvItem();
		return 0;
	}
	if ( sbookflag && MouseX > 320 )
	{
		CheckSBook();
		return 0;
	}
	if ( pcurs >= CURSOR_FIRSTITEM )
	{
		if ( !TryInvPut() )
			return 0;
		NetSendCmdPItem(1u, CMD_PUTITEM, cursmx, cursmy);
LABEL_48:
		SetCursor_(CURSOR_HAND);
		return 0;
	}
	if ( plr[myplr]._pStatPts && !spselflag )
		CheckLvlBtn();
	if ( !lvlbtndown )
		return LeftMouseCmd(wParam == MK_SHIFT+MK_LBUTTON);
	return 0;
}

BOOL LeftMouseCmd(BOOL bShift)
{
	BOOL bNear;

	/// ASSERT: assert(MouseY < 352);

	if(leveltype == DTYPE_TOWN) {
		if(pcursitem != -1 && pcurs == CURSOR_HAND) {
			NetSendCmdLocParam1(1, invflag ? CMD_GOTOGETITEM : CMD_GOTOAGETITEM, cursmx, cursmy, pcursitem);
		}
		if(pcursmonst != -1) {
			NetSendCmdLocParam1(1, CMD_TALKXY, cursmx, cursmy, pcursmonst);
		}
		if(pcursitem == -1 && pcursmonst == -1 && pcursplr == -1) {
			return TRUE;
		}
	} else {
		bNear = abs(plr[myplr].WorldX - cursmx) < 2 && abs(plr[myplr].WorldY - cursmy) < 2;
		if(pcursitem != -1 && pcurs == CURSOR_HAND && !bShift) {
			NetSendCmdLocParam1(1, invflag ? CMD_GOTOGETITEM : CMD_GOTOAGETITEM, cursmx, cursmy, pcursitem);
		} else if(pcursobj != -1 && (!bShift || bNear && object[pcursobj]._oBreak == 1)) {
			NetSendCmdLocParam1(1, pcurs == CURSOR_DISARM ? CMD_DISARMXY : CMD_OPOBJXY, cursmx, cursmy, pcursobj);
		} else if(plr[myplr]._pwtype == 1) {
			if(bShift) {
				NetSendCmdLoc(1, CMD_RATTACKXY, cursmx, cursmy);
			} else if(pcursmonst != -1) {
				if(CanTalkToMonst(pcursmonst)) {
					NetSendCmdParam1(1, CMD_ATTACKID, pcursmonst);
				} else {
					NetSendCmdParam1(1, CMD_RATTACKID, pcursmonst);
				}
			} else if(pcursplr != -1 && !FriendlyMode) {
				NetSendCmdParam1(1, CMD_RATTACKPID, pcursplr);
			}
		} else {
			if(bShift) {
				if(pcursmonst != -1) {
					if(CanTalkToMonst(pcursmonst)) {
						NetSendCmdParam1(1, CMD_ATTACKID, pcursmonst);
					} else {
						NetSendCmdLoc(1, CMD_SATTACKXY, cursmx, cursmy);
					}
				} else {
					NetSendCmdLoc(1, CMD_SATTACKXY, cursmx, cursmy);
				}
			} else if(pcursmonst != -1) {
				NetSendCmdParam1(1, CMD_ATTACKID, pcursmonst);
			} else if(pcursplr != -1 && !FriendlyMode) {
				NetSendCmdParam1(1, CMD_ATTACKPID, pcursplr);
			}
		}
		if(!bShift && pcursitem == -1 && pcursobj == -1 && pcursmonst == -1 && pcursplr == -1) {
			return TRUE;
		}
	}

	return FALSE;
}

BOOL TryIconCurs()
{
	if(pcurs == CURSOR_RESURRECT) {
		NetSendCmdParam1(TRUE, CMD_RESURRECT, pcursplr);
		return TRUE;
	} else if(pcurs == CURSOR_HEALOTHER) {
		NetSendCmdParam1(TRUE, CMD_HEALOTHER, pcursplr);
		return TRUE;
	} else if(pcurs == CURSOR_TELEKINESIS) {
		DoTelekinesis();
		return TRUE;
	} else if(pcurs == CURSOR_IDENTIFY) {
		if(pcursinvitem != -1) {
			CheckIdentify(myplr, pcursinvitem);
		} else {
			SetCursor_(CURSOR_HAND);
		}
		return TRUE;
	} else if(pcurs == CURSOR_REPAIR) {
		if(pcursinvitem != -1) {
			DoRepair(myplr, pcursinvitem);
		} else {
			SetCursor_(CURSOR_HAND);
		}
		return TRUE;
	} else if(pcurs == CURSOR_RECHARGE) {
		if(pcursinvitem != -1) {
			DoRecharge(myplr, pcursinvitem);
		} else {
			SetCursor_(CURSOR_HAND);
		}
		return TRUE;
	} else if(pcurs == CURSOR_TELEPORT) {
		if(pcursmonst != -1) {
			NetSendCmdParam3(TRUE, CMD_TSPELLID, pcursmonst, plr[myplr]._pTSpell, GetSpellLevel(myplr, plr[myplr]._pTSpell));
		} else if(pcursplr != -1) {
			NetSendCmdParam3(TRUE, CMD_TSPELLPID, pcursplr, plr[myplr]._pTSpell, GetSpellLevel(myplr, plr[myplr]._pTSpell));
		} else {
			NetSendCmdLocParam2(TRUE, CMD_TSPELLXY, cursmx, cursmy, plr[myplr]._pTSpell, GetSpellLevel(myplr, plr[myplr]._pTSpell));
		}
		SetCursor_(CURSOR_HAND);
		return TRUE;
	} else if(pcurs == CURSOR_DISARM && pcursobj == -1) {
		SetCursor_(CURSOR_HAND);
		return TRUE;
	}

	return FALSE;
}

void LeftMouseUp()
{
	gmenu_left_mouse(0);
	control_release_talk_btn();
	if ( panbtndown )
		CheckBtnUp();
	if ( chrbtnactive )
		ReleaseChrBtns();
	if ( lvlbtndown )
		ReleaseLvlBtn();
	if ( stextflag )
		ReleaseStoreBtn();
}

void RightMouseDown()
{
	if ( !gmenu_is_active() && sgnTimeoutCurs == CURSOR_NONE && PauseMode != 2 && !plr[myplr]._pInvincible )
	{
		if ( doomflag )
		{
			doom_close();
		}
		else if ( !stextflag )
		{
			if ( spselflag )
			{
				SetSpell();
			}
			else if ( MouseY >= 352
				   || (!sbookflag || MouseX <= 320)
				   && !TryIconCurs()
				   && (pcursinvitem == -1 || !UseInvItem(myplr, pcursinvitem)) )
			{
				if ( pcurs == 1 )
				{
					if ( pcursinvitem == -1 || !UseInvItem(myplr, pcursinvitem) )
						CheckPlrSpell();
				}
				else if ( pcurs > 1 && pcurs < 12 )
				{
					SetCursor_(CURSOR_HAND);
				}
			}
		}
	}
}

BOOL PressSysKey(int wParam)
{
	if ( gmenu_is_active() || wParam != VK_F10 )
		return FALSE;
	diablo_hotkey_msg(1);
	return TRUE;
}

void diablo_hotkey_msg(DWORD dwMsg)
{
	char *s;
	char szFileName[MAX_PATH];
	char szMsg[MAX_SEND_STR_LEN];

	if(gbMaxPlayers == 1) {
		return;
	}
	if(GetModuleFileName(ghInst, szFileName, sizeof(szFileName)) == 0) {
		app_fatal("Can't get program name");
	}

	s = strrchr(szFileName, '\\');
	if(s != NULL) {
		*s = '\0';
	}

	strcat(szFileName, "\\Diablo.ini");
	/// ASSERT: assert(dwMsg < sizeof(spszMsgTbl) / sizeof(spszMsgTbl[0]));
	GetPrivateProfileString("NetMsg", spszMsgKeyTbl[dwMsg], spszMsgTbl[dwMsg], szMsg, sizeof(szMsg), szFileName);
	NetSendCmdString(-1, szMsg);
}

void ReleaseKey(int vkey)
{
	if ( vkey == VK_SNAPSHOT )
		CaptureScreen();
}

void PressKey(int vkey)
{
	if(gmenu_presskeys(vkey) || control_presskeys(vkey)) {
		return;
	}

	if(deathflag) {
		if(sgnTimeoutCurs != 0) {
			return;
		}
		if(vkey == VK_F9) {
			diablo_hotkey_msg(0);
		}
		if(vkey == VK_F10) {
			diablo_hotkey_msg(1);
		}
		if(vkey == VK_F11) {
			diablo_hotkey_msg(2);
		}
		if(vkey == VK_F12) {
			diablo_hotkey_msg(3);
		}
		if(vkey == VK_RETURN) {
			control_type_message();
		}
		if(vkey != VK_ESCAPE) {
			return;
		}
	}
	if(vkey == VK_ESCAPE) {
		if(!PressEscKey()) {
			track_repeat_walk(0);
			gamemenu_on();
		}
		return;
	}

	if(sgnTimeoutCurs != 0 || dropGoldFlag) {
		return;
	}
	if(vkey == VK_PAUSE) {
		diablo_pause_game();
		return;
	}
	if(PauseMode == 2) {
		return;
	}

	if(vkey == VK_RETURN) {
		if(stextflag) {
			STextEnter();
		} else if(questlog) {
			QuestlogEnter();
		} else {
			control_type_message();
		}
	} else if(vkey == VK_F1) {
		if(helpflag) {
			helpflag = 0;
		} else if(stextflag) {
			ClearPanel();
			AddPanelString("No help available", 1); /// BUGFIX: message isn't displayed
			AddPanelString("while in stores", 1);
			track_repeat_walk(0);
		} else {
			invflag = 0;
			chrflag = 0;
			sbookflag = 0;
			spselflag = 0;
			if(qtextflag && leveltype == DTYPE_TOWN) {
				qtextflag = 0;
				stream_stop();
			}
			questlog = 0;
			automapflag = 0;
			msgdelay = 0;
			gamemenu_off();
			DisplayHelp();
			doom_close();
		}
	}
#ifdef _DEBUG
	else if(vkey == VK_F2) {
	}
#endif
#ifdef _DEBUG
	else if(vkey == VK_F3) {
		if(pcursitem != -1) {
			sprintf(
				tempstr,
				"IDX = %i  :  Seed = %i  :  CF = %i",
				item[pcursitem].IDidx,
				item[pcursitem]._iSeed,
				item[pcursitem]._iCreateInfo);
			NetSendCmdString(1 << myplr, tempstr);
		}
		sprintf(tempstr, "Numitems : %i", numitems);
		NetSendCmdString(1 << myplr, tempstr);
	}
#endif
#ifdef _DEBUG
	else if(vkey == VK_F4) {
		PrintDebugQuest();
	}
#endif
	else if(vkey == VK_F5) {
		if(spselflag) {
			SetSpeedSpell(0);
		} else {
			ToggleSpell(0);
		}
	} else if(vkey == VK_F6) {
		if(spselflag) {
			SetSpeedSpell(1);
		} else {
			ToggleSpell(1);
		}
	} else if(vkey == VK_F7) {
		if(spselflag) {
			SetSpeedSpell(2);
		} else {
			ToggleSpell(2);
		}
	} else if(vkey == VK_F8) {
		if(spselflag) {
			SetSpeedSpell(3);
		} else {
			ToggleSpell(3);
		}
	} else if(vkey == VK_F9) {
		diablo_hotkey_msg(0);
	} else if(vkey == VK_F10) {
		diablo_hotkey_msg(1);
	} else if(vkey == VK_F11) {
		diablo_hotkey_msg(2);
	} else if(vkey == VK_F12) {
		diablo_hotkey_msg(3);
	} else if(vkey == VK_UP) {
		if(stextflag) {
			STextUp();
		} else if(questlog) {
			QuestlogUp();
		} else if(helpflag) {
			HelpScrollUp();
		} else if(automapflag) {
			AutomapUp();
		}
	} else if(vkey == VK_DOWN) {
		if(stextflag) {
			STextDown();
		} else if(questlog) {
			QuestlogDown();
		} else if(helpflag) {
			HelpScrollDown();
		} else if(automapflag) {
			AutomapDown();
		}
	} else if(vkey == VK_PRIOR) {
		if(stextflag) {
			STextPrior();
		}
	} else if(vkey == VK_NEXT) {
		if(stextflag) {
			STextNext();
		}
	} else if(vkey == VK_LEFT) {
		if(automapflag && !talkflag) {
			AutomapLeft();
		}
	} else if(vkey == VK_RIGHT) {
		if(automapflag && !talkflag) {
			AutomapRight();
		}
	} else if(vkey == VK_TAB) {
		DoAutoMap();
	} else if(vkey == VK_SPACE) {
		if(!chrflag && invflag && MouseX < 480 && MouseY < 352) {
			SetCursorPos(MouseX + 160, MouseY);
		}
		if(!invflag && chrflag && MouseX > 160 && MouseY < 352) {
			SetCursorPos(MouseX - 160, MouseY);
		}
		helpflag = 0;
		invflag = 0;
		chrflag = 0;
		sbookflag = 0;
		spselflag = 0;
		if(qtextflag && leveltype == DTYPE_TOWN) {
			qtextflag = 0;
			stream_stop();
		}
		questlog = 0;
		automapflag = 0;
		msgdelay = 0;
		gamemenu_off();
		doom_close();
	}
}

void diablo_pause_game()
{
	if ( (unsigned char)gbMaxPlayers <= 1u )
	{
		if ( PauseMode )
		{
			PauseMode = 0;
		}
		else
		{
			PauseMode = 2;
			sound_stop();
			track_repeat_walk(0);
		}
		force_redraw = 255;
	}
}

/* NOTE: `return` must be used instead of `break` to be bin exact as C++ */
void PressChar(int vkey)
{
	if(gmenu_is_active() || control_talk_last_key(vkey) || sgnTimeoutCurs != 0 || deathflag) {
		return;
	}
	if((char)vkey == 'p' || (char)vkey == 'P') {
		diablo_pause_game();
		return;
	}
	if(PauseMode == 2) {
		return;
	}
	if(doomflag) {
		doom_close();
		return;
	}
	if(dropGoldFlag) {
		control_drop_gold(vkey);
		return;
	}

	switch(vkey) {
	case 'G':
	case 'g':
		DecreaseGamma();
		return;
	case 'F':
	case 'f':
		IncreaseGamma();
		return;
	case 'I':
	case 'i':
		if(!stextflag) {
			sbookflag = 0;
			invflag = invflag == 0;
			if(!invflag || chrflag) {
				if(MouseX < 480 && MouseY < 352) {
					SetCursorPos(MouseX + 160, MouseY);
				}
			} else {
				if(MouseX > 160 && MouseY < 352) {
					SetCursorPos(MouseX - 160, MouseY);
				}
			}
		}
		return;
	case 'C':
	case 'c':
		if(!stextflag) {
			questlog = 0;
			chrflag = chrflag == 0;
			if(!chrflag || invflag) {
				if(MouseX > 160 && MouseY < 352) {
					SetCursorPos(MouseX - 160, MouseY);
				}
			} else {
				if(MouseX < 480 && MouseY < 352) {
					SetCursorPos(MouseX + 160, MouseY);
				}
			}
		}
		return;
	case 'Q':
	case 'q':
		if(!stextflag) {
			chrflag = 0;
			if(!questlog) {
				StartQuestlog();
			} else {
				questlog = 0;
			}
		}
		return;
	case 'Z':
	case 'z':
		zoomflag = zoomflag == 0;
		return;
	case 'S':
	case 's':
		if(!stextflag) {
			invflag = 0;
			if(!spselflag) {
				DoSpeedBook();
			} else {
				spselflag = 0;
			}
			track_repeat_walk(0);
		}
		return;
	case 'B':
	case 'b':
		if(!stextflag) {
			invflag = 0;
			sbookflag = sbookflag == 0;
		}
		return;
	case '+':
	case '=':
		if(automapflag) {
			AutomapZoomIn();
		}
		return;
	case '-':
	case '_':
		if(automapflag) {
			AutomapZoomOut();
		}
		return;
	case 'v':
		NetSendCmdString(1 << myplr, gszProductName);
		return;
	case 'V':
		NetSendCmdString(1 << myplr, gszVersionNumber);
		return;
	case '!':
	case '1':
		if(plr[myplr].SpdList[0]._itype != -1 && plr[myplr].SpdList[0]._itype != 11) {
			UseInvItem(myplr, 47);
		}
		return;
	case '@':
	case '2':
		if(plr[myplr].SpdList[1]._itype != -1 && plr[myplr].SpdList[1]._itype != 11) {
			UseInvItem(myplr, 48);
		}
		return;
	case '#':
	case '3':
		if(plr[myplr].SpdList[2]._itype != -1 && plr[myplr].SpdList[2]._itype != 11) {
			UseInvItem(myplr, 49);
		}
		return;
	case '$':
	case '4':
		if(plr[myplr].SpdList[3]._itype != -1 && plr[myplr].SpdList[3]._itype != 11) {
			UseInvItem(myplr, 50);
		}
		return;
	case '%':
	case '5':
		if(plr[myplr].SpdList[4]._itype != -1 && plr[myplr].SpdList[4]._itype != 11) {
			UseInvItem(myplr, 51);
		}
		return;
	case '^':
	case '6':
		if(plr[myplr].SpdList[5]._itype != -1 && plr[myplr].SpdList[5]._itype != 11) {
			UseInvItem(myplr, 52);
		}
		return;
	case '&':
	case '7':
		if(plr[myplr].SpdList[6]._itype != -1 && plr[myplr].SpdList[6]._itype != 11) {
			UseInvItem(myplr, 53);
		}
		return;
	case '*':
	case '8':
#ifdef _DEBUG
		if(debug_mode_key_inverted_v || debug_mode_key_w) {
			NetSendCmd(TRUE, CMD_CHEAT_EXPERIENCE);
			return;
		}
#endif
		if(plr[myplr].SpdList[7]._itype != -1 && plr[myplr].SpdList[7]._itype != 11) {
			UseInvItem(myplr, 54);
		}
		return;
#ifdef _DEBUG
	case ')':
	case '0':
		if(debug_mode_key_inverted_v) {
			if(arrowdebug > 2) {
				arrowdebug = 0;
			}
			if(arrowdebug == 0) {
				plr[myplr]._pIFlags &= ~ISPL_FIRE_ARROWS;
				plr[myplr]._pIFlags &= ~ISPL_LIGHT_ARROWS;
			}
			if(arrowdebug == 1) {
				plr[myplr]._pIFlags |= ISPL_FIRE_ARROWS;
			}
			if(arrowdebug == 2) {
				plr[myplr]._pIFlags |= ISPL_LIGHT_ARROWS;
			}
			arrowdebug++;
		}
		return;
	case ':':
		if(currlevel == 0 && debug_mode_key_w) {
			SetAllSpellsCheat();
		}
		return;
	case '[':
		if(currlevel == 0 && debug_mode_key_w) {
			TakeGoldCheat();
		}
		return;
	case ']':
		if(currlevel == 0 && debug_mode_key_w) {
			MaxSpellsCheat();
		}
		return;
	case 'a':
		if(debug_mode_key_inverted_v) {
			spelldata[SPL_TELEPORT].sTownSpell = 1;
			plr[myplr]._pSplLvl[plr[myplr]._pSpell]++;
		}
		return;
	case 'D':
		PrintDebugPlayer(1);
		return;
	case 'd':
		PrintDebugPlayer(0);
		return;
	case 'e':
		if(debug_mode_key_d) {
			sprintf(tempstr, "EFlag = %i", plr[myplr]._peflag);
			NetSendCmdString(1 << myplr, tempstr);
		}
		return;
	case 'L':
	case 'l':
		if(debug_mode_key_inverted_v) {
			ToggleLighting();
		}
		return;
	case 'M':
		NextDebugMonster();
		return;
	case 'm':
		GetDebugMonster();
		return;
	case 'R':
	case 'r':
		sprintf(tempstr, "seed = %i", glSeedTbl[currlevel]);
		NetSendCmdString(1 << myplr, tempstr);
		sprintf(tempstr, "Mid1 = %i : Mid2 = %i : Mid3 = %i", glMid1Seed[currlevel], glMid2Seed[currlevel], glMid3Seed[currlevel]);
		NetSendCmdString(1 << myplr, tempstr);
		sprintf(tempstr, "End = %i", glEndSeed[currlevel]);
		NetSendCmdString(1 << myplr, tempstr);
		return;
	case 'T':
	case 't':
		if(debug_mode_key_inverted_v) {
			sprintf(tempstr, "PX = %i  PY = %i", plr[myplr].WorldX, plr[myplr].WorldY);
			NetSendCmdString(1 << myplr, tempstr);
			sprintf(tempstr, "CX = %i  CY = %i  DP = %i", cursmx, cursmy, dungeon[cursmx][cursmy]);
			NetSendCmdString(1 << myplr, tempstr);
		}
		return;
	case '|':
		if(currlevel == 0 && debug_mode_key_w) {
			GiveGoldCheat();
		}
		return;
	case '~':
		if(currlevel == 0 && debug_mode_key_w) {
			StoresCheat();
		}
		return;
#endif
	}
}

void LoadLvlGFX()
{
	/// ASSERT: assert(! pDungeonCels);

	switch((unsigned char)leveltype) {
	case DTYPE_TOWN:
		pDungeonCels = DiabLoad("Levels\\TownData\\Town.CEL", NULL, 'TILE');
		pMegaTiles = DiabLoad("Levels\\TownData\\Town.TIL", NULL, 'TILE');
		pLevelPieces = DiabLoad("Levels\\TownData\\Town.MIN", NULL, 'TILE');
		pSpecialCels = DiabLoad("Levels\\TownData\\TownS.CEL", NULL, 'TILE');
		break;
	case DTYPE_CATHEDRAL:
		pDungeonCels = DiabLoad("Levels\\L1Data\\L1.CEL", NULL, 'TILE');
		pMegaTiles = DiabLoad("Levels\\L1Data\\L1.TIL", NULL, 'TILE');
		pLevelPieces = DiabLoad("Levels\\L1Data\\L1.MIN", NULL, 'TILE');
		pSpecialCels = DiabLoad("Levels\\L1Data\\L1S.CEL", NULL, 'TILE');
		break;
	case DTYPE_CATACOMBS:
		pDungeonCels = DiabLoad("Levels\\L2Data\\L2.CEL", NULL, 'TILE');
		pMegaTiles = DiabLoad("Levels\\L2Data\\L2.TIL", NULL, 'TILE');
		pLevelPieces = DiabLoad("Levels\\L2Data\\L2.MIN", NULL, 'TILE');
		pSpecialCels = DiabLoad("Levels\\L2Data\\L2S.CEL", NULL, 'TILE');
		break;
	case DTYPE_CAVES:
		pDungeonCels = DiabLoad("Levels\\L3Data\\L3.CEL", NULL, 'TILE');
		pMegaTiles = DiabLoad("Levels\\L3Data\\L3.TIL", NULL, 'TILE');
		pLevelPieces = DiabLoad("Levels\\L3Data\\L3.MIN", NULL, 'TILE');
		pSpecialCels = DiabLoad("Levels\\L1Data\\L1S.CEL", NULL, 'TILE');
		break;
	case DTYPE_HELL:
		pDungeonCels = DiabLoad("Levels\\L4Data\\L4.CEL", NULL, 'TILE');
		pMegaTiles = DiabLoad("Levels\\L4Data\\L4.TIL", NULL, 'TILE');
		pLevelPieces = DiabLoad("Levels\\L4Data\\L4.MIN", NULL, 'TILE');
		pSpecialCels = DiabLoad("Levels\\L2Data\\L2S.CEL", NULL, 'TILE');
		break;
	default:
		app_fatal("LoadLvlGFX");
		break;
	}
}

void LoadAllGFX()
{
	/// ASSERT: assert(! pSpeedCels);
	pSpeedCels = DiabloAllocPtr(0x100000);
	IncProgress();
	IncProgress();
	InitObjectGFX();
	IncProgress();
	InitMissileGFX();
	IncProgress();
}

void CreateLevel(int lvldir)
{
	switch((unsigned char)leveltype) {
	case DTYPE_TOWN:
		CreateTown(lvldir);
		InitTownTriggers();
		LoadRndLvlPal(0);
		break;
	case DTYPE_CATHEDRAL:
		CreateL5Dungeon(glSeedTbl[currlevel], lvldir);
		InitL1Triggers();
		Freeupstairs();
		LoadRndLvlPal(1);
		break;
	case DTYPE_CATACOMBS:
		CreateL2Dungeon(glSeedTbl[currlevel], lvldir);
		InitL2Triggers();
		Freeupstairs();
		LoadRndLvlPal(2);
		break;
	case DTYPE_CAVES:
		CreateL3Dungeon(glSeedTbl[currlevel], lvldir);
		InitL3Triggers();
		Freeupstairs();
		LoadRndLvlPal(3);
		break;
	case DTYPE_HELL:
		CreateL4Dungeon(glSeedTbl[currlevel], lvldir);
		InitL4Triggers();
		Freeupstairs();
		LoadRndLvlPal(4);
		break;
	default:
		app_fatal("CreateLevel");
		break;
	}
}

void LoadGameLevel(BOOL firstflag, int lvldir)
{
	int i, j;
	BOOL visited;

	if(setseed)
		glSeedTbl[currlevel] = setseed;

	music_stop();
	SetCursor_(CURSOR_HAND);
	SetRndSeed(glSeedTbl[currlevel]);
	IncProgress();
	MakeLightTable();
	LoadLvlGFX();
	IncProgress();

	if(firstflag) {
		InitInv();
		InitItemGFX();
		InitQuestText();

		for(i = 0; i < gbMaxPlayers; i++)
			InitPlrGFXMem(i);

		InitStores();
		InitAutomapOnce();
		InitHelp();
	}

	SetRndSeed(glSeedTbl[currlevel]);

	if(leveltype == DTYPE_TOWN)
		SetupTownStores();

	IncProgress();
	InitAutomap();

	if(leveltype != DTYPE_TOWN && lvldir != 4) {
		InitLighting();
		InitVision();
	}

	InitLevelMonsters();
	IncProgress();

	if(!setlevel) {
		CreateLevel(lvldir);
		IncProgress();
		FillSolidBlockTbls();
		SetRndSeed(glSeedTbl[currlevel]);

		if(leveltype != DTYPE_TOWN) {
			GetLevelMTypes();
			InitThemes();
			LoadAllGFX();
		} else {
			InitMissileGFX();
		}

		IncProgress();

		if(lvldir == 3)
			GetReturnLvlPos();
		if(lvldir == 5)
			GetPortalLvlPos();

		IncProgress();

		for(i = 0; i < MAX_PLRS; i++) {
			if(plr[i].plractive && currlevel == plr[i].plrlevel) {
				InitPlayerGFX(i);
				if(lvldir != 4)
					InitPlayer(i, firstflag);
			}
		}

		PlayDungMsgs();
		InitMultiView();
		IncProgress();

		visited = FALSE;
		for(i = 0; i < gbMaxPlayers; i++) {
			if(plr[i].plractive)
				visited = visited || plr[i]._pLvlVisited[currlevel];
		}

		SetRndSeed(glSeedTbl[currlevel]);

		if(leveltype != DTYPE_TOWN) {
			if(firstflag || lvldir == 4 || !plr[myplr]._pLvlVisited[currlevel] || gbMaxPlayers != 1) {
				HoldThemeRooms();
				glMid1Seed[currlevel] = GetRndSeed();
				InitMonsters();
				glMid2Seed[currlevel] = GetRndSeed();
				InitObjects();
				InitItems();
				CreateThemeRooms();
				glMid3Seed[currlevel] = GetRndSeed();
				InitMissiles();
				InitDead();
				glEndSeed[currlevel] = GetRndSeed();

				if(gbMaxPlayers != 1)
					DeltaLoadLevel();

				IncProgress();
				SavePreLighting();
			} else {
				InitMonsters();
				InitMissiles();
				InitDead();
				IncProgress();
				LoadLevel();
				IncProgress();
			}
		} else {
			for(i = 0; i < MAXDUNX; i++) {
				for(j = 0; j < MAXDUNY; j++)
					dFlags[i][j] |= 0x40;
			}

			InitTowners();
			InitItems();
			InitMissiles();
			IncProgress();

			if(!firstflag && lvldir != 4 && plr[myplr]._pLvlVisited[currlevel] && gbMaxPlayers == 1)
				LoadLevel();
			if(gbMaxPlayers != 1)
				DeltaLoadLevel();

			IncProgress();
		}
		if(gbMaxPlayers == 1)
			ResyncQuests();
		else
			ResyncMPQuests();
	} else {
		/// ASSERT: assert(! pSpeedCels);
		pSpeedCels = DiabloAllocPtr(0x100000);
		LoadSetMap();
		IncProgress();
		GetLevelMTypes();
		InitMonsters();
		InitMissileGFX();
		InitDead();
		FillSolidBlockTbls();
		IncProgress();

		if(lvldir == 5)
			GetPortalLvlPos();

		for(i = 0; i < MAX_PLRS; i++) {
			if(plr[i].plractive && currlevel == plr[i].plrlevel) {
				InitPlayerGFX(i);
				if(lvldir != 4)
					InitPlayer(i, firstflag);
			}
		}

		InitMultiView();
		IncProgress();

		if(firstflag || lvldir == 4 || !plr[myplr]._pSLvlVisited[(unsigned char)setlvlnum]) {
			InitItems();
			SavePreLighting();
		} else {
			LoadLevel();
		}

		InitMissiles();
		IncProgress();
	}

	SyncPortals();

	for(i = 0; i < MAX_PLRS; i++) {
		if(plr[i].plractive && plr[i].plrlevel == currlevel && (!plr[i]._pLvlChanging || i == myplr)) {
			if(plr[i]._pHitPoints > 0) {
				if(gbMaxPlayers == 1)
					dPlayer[plr[i].WorldX][plr[i].WorldY] = i + 1;
				else
					SyncInitPlrPos(i);
			} else {
				dFlags[plr[i].WorldX][plr[i].WorldY] |= 4;
			}
		}
	}

	if(leveltype != DTYPE_TOWN)
		SetDungeonMicros();

	InitLightMax();
	IncProgress();
	IncProgress();

	if(firstflag) {
		InitControlPan();
		IncProgress();
	}
	if(leveltype != DTYPE_TOWN) {
		ProcessLightList();
		ProcessVisionList();
	}

	music_start((unsigned char)leveltype);

	while(!IncProgress());

	if(setlevel && setlvlnum == SL_SKELKING && quests[12]._qactive == 2)
		PlaySFX(USFX_SKING1);
}

void game_loop(BOOL bStartup)
{
	int i;

	i = bStartup ? 60 : 3;

	while(i--) {
		if(!multi_handle_delta()) {
			timeout_cursor(1);
			break;
		} else {
			timeout_cursor(0);
			game_logic();
		}
		if(!gbRunGame || gbMaxPlayers == 1 || !nthread_has_500ms_passed(1))
			break;
	}
}

void game_logic()
{
	if(PauseMode == 2) {
		return;
	}
	if(PauseMode == 1) {
		PauseMode = 2;
	}
	if(gbMaxPlayers == 1 && gmenu_is_active()) {
		force_redraw |= 1;
		return;
	}

	if(!gmenu_is_active() && sgnTimeoutCurs == 0) {
		CheckCursMove();
		track_process();
	}
	if(gbProcessPlayers) {
		ProcessPlayers();
	}
	if(leveltype != DTYPE_TOWN) {
		ProcessMonsters();
		ProcessObjects();
		ProcessMissiles();
		ProcessItems();
		ProcessLightList();
		ProcessVisionList();
	} else {
		ProcessTowners();
		ProcessItems();
		ProcessMissiles();
	}

#ifdef _DEBUG
	if(debug_mode_key_inverted_v && GetAsyncKeyState(VK_SHIFT) & 0x8000) {
		ScrollView();
	}
#endif

	sound_update();
	ClearPlrMsg();
	CheckTriggers();
	CheckQuests();
	force_redraw |= 1;
	pfile_update(FALSE);
}

void timeout_cursor(BOOL bTimeout)
{
	if ( bTimeout )
	{
		if ( sgnTimeoutCurs == CURSOR_NONE && !sgbMouseDown )
		{
			sgnTimeoutCurs = pcurs;
			multi_net_ping();
			ClearPanel();
			AddPanelString("-- Network timeout --", 1);
			AddPanelString("-- Waiting for players --", 1);
			SetCursor_(CURSOR_HOURGLASS);
			force_redraw = 255;
		}
		scrollrt_draw_game_screen(1);
	}
	else if ( sgnTimeoutCurs )
	{
		SetCursor_(sgnTimeoutCurs);
		sgnTimeoutCurs = 0;
		ClearPanel();
		force_redraw = 255;
	}
}

void diablo_color_cyc_logic()
{
	DWORD v0; // eax

	v0 = GetTickCount();
	if ( v0 - color_cycle_timer >= 0x32 )
	{
		color_cycle_timer = v0;
		if ( palette_get_colour_cycling() )
		{
			if ( leveltype == DTYPE_HELL )
			{
				lighting_color_cycling();
			}
			else if ( leveltype == DTYPE_CAVES )
			{
				if ( fullscreen )
					palette_update_caves();
			}
		}
	}
}
