// --------------------------------------------------------------------------
// Citadel: CTDL.CPP
//
// The root of all evil.

#define MAIN
#define EXTERN
#pragma hdrstop

#include "ctdl.h"

#include "config.h"
#include "net.h"
#include "log.h"
#include "account.h"
#include "aplic.h"
#include "menus.h"
#include "blurbs.h"
#include "helpfile.h"
#include "mdmreslt.h"
#include "cfgfiles.h"
#include "cwindows.h"
#include "room.h"
#include "msg.h"
#include "infofile.h"
#include "miscovl.h"
#include "filecmd.h"
#include "term.h"


static Bool doRegular(Bool x, char c);
static Bool getCommand(char *c, Bool prompt);
static void MainCitadelLoop(void);

#ifndef WINCIT
uint cdecl _stklen = 1024*12;			// set up 12K of stack

#ifndef FLOPPY
	#if VERSION == RELEASE
		uint cdecl _ovrbuffer = 96*64;	// set up 96K for overlays
	#else
		uint cdecl _ovrbuffer = 96*64;	// set up 96K for overlays
	#endif
#endif

#define WARNVALUE	40l*1024l			// Start warning at 40K free...
#define DANGERVALUE 20l*1024l			// ...and get worried at 20K.

#ifdef MSC
int _far _nullcheck(void);				// Undocumented MSC 6 function
#endif

// Contents
//
// doRegular()		fanout for above commands
// getCommand() 	prints prompt and gets command char
// main()			has the central menu code
// doAd()			shows an ad/blurb cfg.ad_chance % of the time

// main(): Call init, general system loop, then shutdown.
//
// Input:
//	argc, argv: Standard C RTL stuff.
//
// Return value:
//	This function never returns.  It ends by calling exitcitadel(), which
//	uses exit() to exit Citadel.

void cdecl main(int argc, const char *argv[])
	{
	startUp(argc, argv);

	MainCitadelLoop();

	exitcitadel();
	}
#else
static HANDLE hAccel;

long FAR PASCAL WndProc(HWND, WORD, WORD, LONG);

int PASCAL WinMain(HANDLE hInst, HANDLE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
	{
	hInstance = hInst;

	if (!(GetWinFlags() | WF_PMODE))
		{
		MessageBox(NULL, "You must be in protected mode.", NULL,
				MB_ICONSTOP | MB_OK);
		return (FALSE);
		}

	if (hPrevInstance)
		{
		MessageBox(NULL, "Cannot run more than one copy.", NULL,
				MB_ICONSTOP | MB_OK);
		return (FALSE);
		}

	startUp(0, NULL);

	MainCitadelLoop();

	exitcitadel();

	return (return_code);
	}

long FAR PASCAL WndProc(HWND hwnd, WORD message, WORD wParam, LONG lParam)
	{
	switch (message)
		{
		case WM_CREATE:
			{
			TEXTMETRIC tm;
			HDC hdc = GetDC(hwnd);

			SelectObject(hdc, GetStockObject(OEM_FIXED_FONT));
			GetTextMetrics(hdc, &tm);
			cxChar = tm.tmAveCharWidth;
			cyChar = tm.tmHeight + tm.tmExternalLeading;

			topBorder = cyChar / 2;

			ReleaseDC(hwnd, hdc);

			haveMainWindow = TRUE;

			return (0);
			}

		case WM_SIZE:
			{
			HDC hdc = GetDC(hwnd);

			cyClient = HIWORD(lParam);
			cxClient = LOWORD(lParam);

			if (memDC)
				{
				DeleteDC(memDC);
				memDC = NULL;
				}

			if (memBM)
				{
				DeleteObject(memBM);
				memBM = NULL;
				}

			memDC = CreateCompatibleDC(hdc);
			if (memDC)
				{
				memBM = CreateCompatibleBitmap(hdc, cxClient, cyClient);

				if (memBM)
					{
					RECT r;

					SelectObject(memDC, memBM);

					r.top = 0;
					r.left = 0;
					r.bottom = cyClient;
					r.right = cxClient;

					FillRect(memDC, &r, GetStockObject(WHITE_BRUSH));

					SelectObject(memDC, GetStockObject(OEM_FIXED_FONT));
					}
				else
					{
					DeleteDC(memDC);
					memDC = NULL;
					}
				}

			ReleaseDC(hwnd, hdc);

			resetTextWindow(cfg.wysiwyg ? ansiattr : cfg.attr, TRUE);
			return (0);
			}

		case WM_PAINT:
			{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hwnd, &ps);

			SelectObject(hdc, GetStockObject(OEM_FIXED_FONT));
			updateText(ps.rcPaint, hdc);

			EndPaint(hwnd, &ps);
			return (0);
			}

		case WM_CHAR:
			{
			if (!KeyboardBuffer.Add(wParam))
				{
				MessageBeep(0);
				}

			return (0);
			}

		case WM_SETFOCUS:
			{
			uchar row, col;

			readpos(&row, &col);

			CreateCaret(hwnd, NULL, cxChar, cyChar);
			SetCaretPos(col * cxChar + cxChar, row * cyChar + topBorder);
			ShowCaret(hwnd);

			haveFocus = TRUE;

			return (0);
			}

		case WM_KILLFOCUS:
			{
			HideCaret(hwnd);
			DestroyCaret();

			haveFocus = FALSE;

			return (0);
			}

		case WM_INITMENUPOPUP:
			{
			if (LOWORD(lParam) == 2)
				{
				CheckMenuItem(wParam, 106,
						sysReq ? MF_CHECKED : MF_UNCHECKED);
				CheckMenuItem(wParam, 110,
						!cfg.noBells ? MF_CHECKED : MF_UNCHECKED);
				CheckMenuItem(wParam, 112,
						!cfg.noChat ? MF_CHECKED : MF_UNCHECKED);
				CheckMenuItem(wParam, 118,
						debug ? MF_CHECKED : MF_UNCHECKED);

				EnableMenuItem(wParam, 109, ConsoleLock.IsLocked() ?
						MF_ENABLED : MF_GRAYED);

				return (0);
				}
			else if (LOWORD(lParam) == 3)
				{
				CheckMenuItem(wParam, 114,
						logBuf.lbflags.AIDE ? MF_CHECKED : MF_UNCHECKED);
				CheckMenuItem(wParam, 115,
						TI()CurrentUser->IsSysop() ? MF_CHECKED : MF_UNCHECKED);
				CheckMenuItem(wParam, 116,
						psycho ? MF_CHECKED : MF_UNCHECKED);
				CheckMenuItem(wParam, 119,
						logBuf.lbflags.PROBLEM ? MF_CHECKED : MF_UNCHECKED);
				CheckMenuItem(wParam, 120,
						logBuf.VERIFIED ? MF_UNCHECKED : MF_CHECKED);
				CheckMenuItem(wParam, 121,
						logBuf.lbflags.NOACCOUNT ? MF_UNCHECKED : MF_CHECKED);

				EnableMenuItem(wParam, 114, ConsoleLock.IsLocked() ?
						MF_GRAYED : MF_ENABLED);
				EnableMenuItem(wParam, 115, ConsoleLock.IsLocked() ?
						MF_GRAYED : MF_ENABLED);
				EnableMenuItem(wParam, 119, ConsoleLock.IsLocked() ?
						MF_GRAYED : MF_ENABLED);
				EnableMenuItem(wParam, 120, ConsoleLock.IsLocked() ?
						MF_GRAYED : MF_ENABLED);
				EnableMenuItem(wParam, 121,
						cfg.accounting ? MF_ENABLED : MF_GRAYED);
				EnableMenuItem(wParam, 123, cfg.accounting &&
						!logBuf.lbflags.NOACCOUNT ? MF_ENABLED : MF_GRAYED);
				EnableMenuItem(wParam, 124, cfg.accounting &&
						!logBuf.lbflags.NOACCOUNT ? MF_ENABLED : MF_GRAYED);

				return (0);
				}

			break;
			}

		case WM_COMMAND:
			{
			switch (wParam)
				{
				// exit
				case 101: { SendMessage(hwnd, WM_CLOSE, 0, 0l); return (0); }

				case 102: { errorDisp(NULL);	return (0); }	// view error
				case 103: { trapDisp(NULL); 	return (0); }	// view trap
				case 104: { fkey(F1 << 8);		return (0); }	// f1
				case 105: { fkey(F2 << 8);		return (0); }	// f2
				case 106: { fkey(F3 << 8);		return (0); }	// f3
				case 107: { fkey(F4 << 8);		return (0); }	// f4
				case 108: { fkey(F5 << 8);		return (0); }	// f5
				case 109: { fkey(F6 << 8);		return (0); }	// f6
				case 110: { fkey(F7 << 8);		return (0); }	// f7
				case 111: { fkey(F8 << 8);		return (0); }	// f8
				case 112: { fkey(F9 << 8);		return (0); }	// f9
				case 113: { fkey(F10 << 8); 	return (0); }	// f10
				case 114: { fkey(ALT_F6 << 8);	return (0); }	// alt-f6
				case 115: { fkey(SFT_F6 << 8);	return (0); }	// sft-f6
				case 116: { fkey(ALT_B << 8);	return (0); }	// alt-b
				case 117: { fkey(CTL_F6 << 8);	return (0); }	// cntl-f6
				case 118: { fkey(ALT_D << 8);	return (0); }	// alt-d
				case 119: { fkey(ALT_T << 8);	return (0); }	// alt-t
				case 120: { fkey(ALT_V << 8);	return (0); }	// alt-v
				case 121: { fkey(ALT_A << 8);	return (0); }	// alt-a
				case 122: { fkey(ALT_Z << 8);	return (0); }	// alt-z
				case 123: { fkey(PGUP << 8);	return (0); }	// pgup
				case 124: { fkey(PGDN << 8);	return (0); }	// pgdn
				}

			break;
			}

		case WM_DESTROY:
			{
			if (memDC)
				{
				DeleteDC(memDC);
				memDC = NULL;
				}

			if (memBM)
				{
				DeleteObject(memBM);
				memBM = NULL;
				}

			PostQuitMessage(0);
			return (0);
			}
		}

	return (DefWindowProc(hwnd, message, wParam, lParam));
	}

void letWindowsMultitask(void)
	{
	MSG msg;

	while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
		if (msg.message == WM_QUIT)
			{
			ExitToMsdos = TRUE;
			return_code = msg.wParam;
			break;
			}

		if (!TranslateAccelerator(hwndMain, hAccel, &msg))
			{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
			}
		}
	}
#endif

static void MainCitadelLoop(void)
	{
	char c;
	Bool x = FALSE;
	Bool modStatMain = FALSE;	// newCarrier && JustLostCarrier
	Bool didterminate = FALSE;	// nodes log in only on the first log-in
	Bool auto_login = FALSE;	// this is so it will go to login when run in door mode

	if (login_pw || login_user || (slv_door && cfg.forcelogin))
		{
		auto_login = TRUE;
		}

	// this is my main citadel loop
	while (!TI()ExitToMsdos)
		{
#ifdef MSC
		if (_nullcheck()) // undocumented MSC function
			{
			doccr();
			}
#endif
		VerifyHeap();

		TI()UserControl.SetOutFlag(IMPERVIOUS);

		StatusLine.Update();

#ifdef __BORLANDC__
		if (heapcheck() < 0)
			{
			crashout(getmsg(744));
			}

		if (farcoreleft() < WARNVALUE)
			{
			CRmPrintfCR(farcoreleft() < DANGERVALUE ? getmsg(610) : getmsg(612));
			compactMemory(1);
			}
#endif

		// Carrier Detect
		if (!TI()loggedIn)
			{
			if ((TI()modStat && !modStatMain) || auto_login)
				{
				TI()showPrompt = TRUE;

				setdefaultTerm(0);	// TTY

				modStatMain = TRUE;
				if (TI()modStat)
					{
					TI()whichIO = MODEM;
					setio();
					}

				TI()SerialPort.Flush();
				greeting();

				if (cfg.forcelogin || auto_login)
					{
					int trys = 0;

					do
						{
						// login modified to handle command line login
						doLogin(2);

						if (!TI()loggedIn && cfg.badPwPause)
							{
							CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
									CitWindowsMsg(NULL, getmsg(1638));

							pause(cfg.badPwPause * 100);

							if (w)
								{
								destroyCitWindow(w, FALSE);
								}
							}

						} while (!TI()loggedIn && HaveConnectionToUser() &&
								trys++ < 3);

					if (!TI()loggedIn)
						{
						if (!onConsole)
							{
							CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
									CitWindowsMsg(NULL, getmsg(1643));

							Hangup();

							if (w)
								{
								destroyCitWindow(w, FALSE);
								}
							}
						else
							{
							TI()whichIO = MODEM; // simulated hangup
							setio();
							}
						}
					}

				auto_login = FALSE;
				}
			}

		if (TI()CurrentUser->IsNode() && TI()loggedIn && TI()modStat &&
				!didterminate)
			{
			SetDoWhat(NETWORKING);

			TI()netError = !net_slave();
			SetDoWhat(DUNO);

			carrloss();

			TI()MRO.Verbose = FALSE;
			terminate(FALSE);	// why not true?

			setdefaultTerm(2);	// Ansi-CLR

			TI()showPrompt = TRUE;
			}

		// Carrier Loss
		if (!HaveConnectionToUser())
			{
			didterminate = FALSE; // node can log in now

			if (slv_door)
				{
				TI()ExitToMsdos = TRUE;
				}

			carrloss();

			if (TI()loggedIn)
				{
				TI()MRO.Verbose = FALSE;
				terminate(FALSE);
				}

			modStatMain = FALSE;

			TI()OC.Echo = BOTH;
			TI()whichIO = CONSOLE;
			TI()UserControl.SetOutFlag(IMPERVIOUS);
			TI()modStat = FALSE;

			setio();

			if (!SerialPort.HaveCarrier())
				{
				CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
						CitWindowsMsg(NULL, getmsg(1611));

				Initport();

				if (w)
					{
					destroyCitWindow(w, FALSE);
					}
				}

			setdefaultTerm(2); // Ansi-CLR

			TI()showPrompt = TRUE;
			}

		// I'm not sure if this really belongs here
		if (!TI()CurrentUser->IsVerified())
			{
			TI()MRO.Verbose = FALSE;
			terminate(TRUE);
			setdefaultTerm(2); // Ansi-CLR
			TI()showPrompt = TRUE;
			}

		// Better do this *BEFORE* a room prompt!
		if (sysReq && !TI()loggedIn && !TI()modStat &&
				!TI()ExitToMsdos)
			{
			sysReq = FALSE;

			if (cfg.offhook)
				{
				offhook();
				}
			else
				{
				TI()SerialPort.DropDtr();
				}

			ringSystemREQ();
			}

		Cron.ResetTimer();
		x = getCommand(&c, TI()showPrompt);
		TI()showPrompt = TRUE;
		Bool NeedToShowPrompt = FALSE;

		if (c)
			{
			Cron.ResetTimer();

			if (toupper(c) == 'T' && !onConsole)
				{
				didterminate = TRUE;	// node cannot log in
				}

			// do greeting from console login
			if ((toupper(c) == 'L') && !x && !TI()loggedIn && !TI()modStat)
				{
				greeting();
				}
			}

		if (TI()chatkey)
			{
			chat();
			NeedToShowPrompt = TRUE;
			}

		if ((eventkey || Cron.IsReady()) && !TI()modStat && !TI()loggedIn)
			{
			if (Cron.Do(CRON_TIMEOUT))
				{
				NeedToShowPrompt = TRUE;
				}

			eventkey = FALSE;
			forceevent = FALSE;
			repeatevent = FALSE;

			setdefaultTerm(2); // Ansi-CLR
			}

		if (sysopkey)
			{
			const ModemConsoleE oldIO = TI()whichIO;
			TI()whichIO = CONSOLE;
			setio();

			mPrintfCR(getmsg(613));

			doSysop();
			if (TI()modStat)
				{
				TI()whichIO = oldIO;
				setio();
				}

			if (!TI()modStat)
				{
				setdefaultTerm(2); // Ansi-CLR
				}

			NeedToShowPrompt = TRUE;
			}

		if (c && (x || toupper(c) != 'P' ||
				(TI()CurrentUser->IsExpert() &&
				(cfg.poop == 1)) || cfg.poop < 1))
			{
			doRegular(x, c);
			}
		else
			{
			TI()showPrompt = NeedToShowPrompt;
			}

		// unsuccessful login
		if ((toupper(c) == 'L') && !x && !TI()loggedIn && !TI()modStat)
			{
			TI()whichIO = MODEM; // simulated hangup
			setio();
			}
		}

	if (TI()loggedIn)
		{
		TI()MRO.Verbose = FALSE;
		terminate(TRUE);
		}
	}


// doRegular(): High-level command menu.
//
// Input:
//	Bool x: TRUE if this is a dot command; FALSE if single-key.
//
//	char c: The character key selected by the user.
//
// Return value:
//	TRUE if command not handled.  Unused.

static Bool doRegular(Bool x, char c)
	{
	Bool toReturn = FALSE;
	Bool didit = FALSE;

	TI()MRO.Reverse = FALSE;

	TI()UserControl.SetOutFlag(IMPERVIOUS);

	if (execDoor(c, x ? 2 : 1))
		{
		return (FALSE);
		}

	if (TI()hitSix)
		{
		if (c == '9')
			{
			mPrintf(getmsg(69));
			didit = TRUE;
			}

		TI()hitSix = FALSE;
		}

	if (toupper(c) != 'W')
		{
		TI()wowcount = 0;
		}

	if (c == '6')
		{
		TI()hitSix = TRUE;
		}

	switch (toupper(c))
		{
		case 'A':
			{
			if (TI()CurrentUser->IsAide())
				{
				doAide(x, 'E');
				}
			else
				{
				toReturn = TRUE;
				}

			break;
			}

		case 'B': doGoto(x, TRUE);      break;
		case 'C': doChat(TRUE, TRUE);   break;
		case 'D': doDownload(x);        break;
		case 'E': doEnter(x, 'm');      break;

		case 'F':
			{
			if (x)
				{
				doFinger();
				}
			else
				{
				doRead(x, 'f');
				}

			break;
			}

		case 'G': doGoto(x, FALSE); break;
		case 'H': doHelp(x, NULL);  break;

		case 'I':
			{
			if (x && (TI()CurrentUser->IsAide() || TI()CurrentUser->IsSysop()))
				{
				doInvite();
				}
			else
				{
				doIntro();
				}
			break;
			}

		case 'J': unGotoRoom();     break;
		case 'K': doKnown(x, 'r');  break;
		case 'L': doLogin(x);       break;

		case 'M':
			{
			if (x)
				{
				doGotoMail();
				}
			else
				{
				doEnter(FALSE, 'e');
				}
			break;
			}

		case 'N': doRead(x, 'n');   break;
		case 'O': doRead(x, 'o');   break;

		case 'P':
			{
			if (x)
				{
				doPersonal();
				}
			else
				{
				toReturn = doPoop(c);
				}

			break;
			}

		case 'Q':
			{
			if (haveRespProt())
				{
				doFileQueue();
				}
			else
				{
				if ((TI()CurrentUser->IsExpert() &&
						(cfg.poop == 1)) || cfg.poop < 1)
					{
					mPrintf(getmsg(1514));
					}
				else
					{
					toReturn = TRUE;
					}
				}

			break;
			}

		case 'R': doRead(x, 'r');   break;

		case 'S':
			{
			if (TI()CurrentUser->IsSysop() && x)
				{
				mPrintfCR(getmsg(38));
				doSysop();
				}
			else
				{
				toReturn = doPoop(c);
				}

			break;
			}

		case 'T': doLogout(x, 'q'); break;
		case 'U': doUpload(x);      break;

		case 'V':
			{
			if (x)
				{
				doVolkswagen(x, NULL);
				}
			else
				{
				doverbose();
				}
			break;
			}

		case 'W': doWow(x);         break;

		case 'X':
			{
			if (!x)
				{
				doEnter(x, 'x');
				}
			else
				{
				doXpert();
				}
			break;
			}

		case 'Y':
			{
			doY(x);
			break;
			}

		case '=':
		case '+': doNext();         break;

		case '\b':
			{
			if (x)
				{
				oChar('\b');
				TI()showPrompt = FALSE;
				break;
				}
			}
		case '-': doPrevious();     break;

		case ']':
		case '>': doNextHall();     break;

		case '[':
		case '<': doPreviousHall(); break;

		case ';': doSmallChat();    break;

		case '*': doReadBull(TRUE); break;

		case '?':
			{
			oChar('?');
			if (x)
				{
				showMenu(M_MAINDOT);
				}
			else
				{
				showMenu(M_MAINOPT);
				}
			break;
			}

		case 0: 	// irrelevant value
			{
			DebugOut(getdbmsg(88), getdbmsg(93));
			break;
			}

		default:
			{
			toReturn = TRUE;
			break;
			}
		}

	if (toReturn && !didit)
		{
		oChar(c);

		if (!TI()CurrentUser->IsExpert())
			{
			CRCRmPrintfCR(getmsg(44));
			}
		else
			{
			mPrintfCR(sqst);
			}
		}

	return (toReturn);
	}

static void ShowPrompt(void)
	{
#ifdef DRAGON
	dragonAct();	// user abuse rutine :-)
#endif
	doAd(FALSE);	// A Genius rutine :-)

//	doRapp(FALSE);	// a dork rutine :-)

	if (cfg.borders)
		{
		doBorder();
		}

	GiveRoomPrompt();
	}


// --------------------------------------------------------------------------
// takeAutoUp(): take an auto upload from a user into directory rooms
//
// Input:
//	char c: The character entered by the user.
//
// Return value:
//	TRUE if upload happened; FALSE if not.

static Bool takeAutoUp(char c)
	{
	protocols *theProt;

	if (!IsRoomDirectory(TI()thisRoom) ||
			(
			IsRoomDownloadOnly(TI()thisRoom) &&
			!TI()CurrentUser->HasRoomPrivileges(TI()thisRoom)
			)
		)
		{
		return (FALSE);
		}

	for (theProt = extProtList; theProt;
			theProt = (protocols *) getNextLL(theProt))
		{
		if ((c == theProt->autoDown[0]) && theProt->batch)
			{
			break;
			}
		}

	if (!theProt)
		{
		return (FALSE);
		}

	twirlypause(15);

	if (!TI()SerialPort.IsInputReady())
		{
		return (FALSE);
		}

	for (int t = 1; theProt->autoDown[t]; t++)
		{
		twirlypause(15);

		if (!TI()SerialPort.IsInputReady() ||
				(TI()SerialPort.Input() != theProt->autoDown[t]))
			{
			return (FALSE);
			}
		}

	SetDoWhat(DOUPLOAD);
	ActuallyDoTheUpload(theProt, FALSE, FALSE);

	ShowPrompt();
	return (TRUE);
	}


// --------------------------------------------------------------------------
// CheckTimeOnSystem(): Check if it is time to kick the user off, and do so
//	if it is.

void CheckTimeOnSystem(void)
	{
	// update user's balance
	if (cfg.accounting && TI()CurrentUser->IsAccounting())
		{
		TI()CurrentUserAccount->UpdateBalance();
		}

	if (TI()altF3Timeout)
		{
		long tme;

		if (time(&tme) < TI()altF3Timeout)
			{
			label Minutes, ZeroMinutes, Seconds;
			char OutMsg[128];

			sprintf(Minutes, pctld, (TI()altF3Timeout - tme) / 60);
			sprintf(ZeroMinutes, getmsg(20), (TI()altF3Timeout - tme) / 60);
			sprintf(Seconds, getmsg(20), (TI()altF3Timeout - tme) % 60);

			sformat(OutMsg, cfg.AltF3Msg, getmsg(1516), Minutes, ZeroMinutes,
					Seconds);

			CRmPrintfCR(pcts, OutMsg);
			}
		else
			{
			label Minutes, ZeroMinutes, Seconds;
			char OutMsg[128];

			sprintf(Minutes, pctd, cfg.altF3Time / 60);
			sprintf(ZeroMinutes, getmsg(47), cfg.altF3Time / 60);
			sprintf(Seconds, getmsg(47), cfg.altF3Time % 60);

			sformat(OutMsg, cfg.AltF3Timeout, getmsg(1516), Minutes,
					ZeroMinutes, Seconds);

			CRmPrintf(pcts, OutMsg);

			TI()MRO.Verbose = TRUE;
			terminate(TRUE);
			TI()altF3Timeout = 0;
			}
		}
	}

// --------------------------------------------------------------------------
// getCommand(): prints menu prompt and gets command char.
//
// Input:
//	char *c: Used for output; see below.
//
//	Bool prompt: I don't know.
//
// Output:
//	char *c: They input selected by the user.
//
// Return value:
//	TRUE if dot command; FALSE if single-key.

static Bool getCommand(char *c, Bool prompt)
	{
	Bool expand = FALSE;

	// Carrier loss!
	if (!HaveConnectionToUser())
		{
		*c = 0;
		return (FALSE);
		}

	TI()UserControl.SetOutFlag(IMPERVIOUS);

	CheckTimeOnSystem();

	if (prompt)
		{
		ShowPrompt();
		}

	SetDoWhat(MAINMENU);

	*c = (char) iCharNE();

	if (!*c)
		{
		DebugOut(getdbmsg(88), getdbmsg(94));
		}

	if (takeAutoUp(*c))
		{
		*c = 0;
		}
	else
		{
		expand = (char)
				((*c == ' ') || (*c == '.') || (*c == ',') || (*c == '/'));

		if (expand)
			{
			oChar('.');
			*c = (char) iCharNE();
			}

		SetDoWhat(DUNO);
		}

	return (expand);
	}

// doAd(): Shows an AD*.BLB cfg.ad_chance % of the time
//
// Input:
//	Bool force: TRUE to force the blurb to display; don't compute chance.
//
// Return value:
//	None.

void doAd(Bool force)
	{
	if (TI()loggedIn && !TI()CurrentUser->IsViewBorders())
		{
		return;
		}

	if ((cfg.ad_chance && (random(100) + 1 <= cfg.ad_chance)) || force)
		{
		BLBRotate(getmsg(1515), getmsg(1438), &cfg.normAd, &cfg.ansiAd, FALSE, B_AD);
		}
	}
