Serious-Engine/Sources/GameMP/Console.cpp
2016-03-11 18:20:51 -06:00

566 lines
18 KiB
C++

/* Copyright (c) 2002-2012 Croteam Ltd.
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
#include "StdAfx.h"
#include "LCDDrawing.h"
extern CGame *_pGame;
// console variables
static const FLOAT tmConsoleFade = 0.0f; // how many seconds it takes console to fade in/out
static FLOAT fConsoleFadeValue = 0.0f; // faded value of console (0..1)
static CTimerValue tvConsoleLast;
static CTString strConsole;
static CTString strInputHistory = "\n";
static CTString strEditingLine;
static CTString strExpandStart;
static CTString strLastExpanded;
static CTString strCurrentLine;
static BOOL bLastExpandedFound;
static INDEX iSymbolOffset;
static INDEX iHistoryLine=0;
static INDEX iCursorPos=0;
static INDEX ctConsoleLinesOnScreen;
static INDEX con_iFirstLine = 1;
extern FLOAT con_fHeightFactor;
extern FLOAT con_tmLastLines;
// find a line with given number in a multi-line string counting from end of string
BOOL GetLineCountBackward(const char *pchrStringStart, const char *pchrStringEnd,
INDEX iBackwardLine, CTString &strResult)
{
// start at the end of string
const char *pchrCurrent = pchrStringEnd;
INDEX ctLinesDone=0;
// while line of given number is not found
while(ctLinesDone!=iBackwardLine) {
// go one character back
pchrCurrent--;
// if got before start of string
if (pchrCurrent < pchrStringStart) {
// line is not found
return FALSE;
}
// if at line break
if(*pchrCurrent == '\n') {
// count lines
ctLinesDone++;
}
}
// check that pointer is not outside range
ASSERT(pchrCurrent>=pchrStringStart && pchrCurrent<pchrStringEnd);
// if exactly on line break, skip it
if (*pchrCurrent=='\n') {
pchrCurrent++;
}
// find end of that line
const char *pchrLineEnd=strchr(pchrCurrent, '\r');
if (pchrLineEnd==NULL) {
pchrLineEnd = pchrStringEnd;
}
// copy the found line
char achrLine[ 1024];
strncpy( achrLine, pchrCurrent, pchrLineEnd-pchrCurrent);
achrLine[ pchrLineEnd-pchrCurrent]=0;
strResult = achrLine;
return TRUE;
}
void CGame::ConsoleRender(CDrawPort *pdp)
{
if( _pGame->gm_csConsoleState==CS_OFF) {
con_iFirstLine = 1;
tvConsoleLast = _pTimer->GetHighPrecisionTimer();
return;
}
// get console height
con_fHeightFactor = Clamp( con_fHeightFactor, 0.1f, 1.0f);
FLOAT fHeightFactor = con_fHeightFactor;
if( !gm_bGameOn) fHeightFactor = 0.9f;
// calculate up-down speed to be independent of refresh speed
CTimerValue tvNow = _pTimer->GetHighPrecisionTimer();
CTimerValue tvDelta = tvNow - tvConsoleLast;
tvConsoleLast = tvNow;
FLOAT fFadeSpeed = (FLOAT)(tvDelta.GetSeconds() / tmConsoleFade);
// if console is dropping down
if( _pGame->gm_csConsoleState==CS_TURNINGON) {
// move it down
fConsoleFadeValue += fFadeSpeed;
// if finished moving
if( fConsoleFadeValue>1.0f) {
// stop
fConsoleFadeValue = 1.0f;
_pGame->gm_csConsoleState = CS_ON;
}
}
// if console is pulling up
if( _pGame->gm_csConsoleState==CS_TURNINGOFF) {
// move it up
fConsoleFadeValue -= fFadeSpeed;
// if finished moving
if( fConsoleFadeValue<0.0f) {
// stop
fConsoleFadeValue = 0.0f;
_pGame->gm_csConsoleState = CS_OFF;
// if not in network
if (!_pNetwork->IsNetworkEnabled()) {
// don't show last lines on screen after exiting console
CON_DiscardLastLineTimes();
}
return;
}
}
if (_pGame->gm_csConsoleState==CS_TALK) {
fHeightFactor = 0.1f;
fConsoleFadeValue = 1.0f;
}
// calculate size of console box so that it covers upper half of the screen
FLOAT fHeight = ClampUp( fHeightFactor*fConsoleFadeValue*2, fHeightFactor);
CDrawPort dpConsole( pdp, 0.0f, 0.0f, 1.0f, fHeight);
// lock drawport
if( !dpConsole.Lock()) return;
LCDPrepare(fConsoleFadeValue);
LCDSetDrawport(&dpConsole);
dpConsole.Fill(LCDFadedColor(C_BLACK|225));
PIX pixSizeI = dpConsole.GetWidth();
PIX pixSizeJ = dpConsole.GetHeight();
COLOR colLight = LCDFadedColor(C_WHITE|255);
COLOR colDark = LCDFadedColor(SE_COL_BLUE_LIGHT|255);
INDEX iBackwardLine = con_iFirstLine;
if( iBackwardLine>1) Swap( colLight, colDark);
PIX pixLineSpacing = _pfdConsoleFont->fd_pixCharHeight + _pfdConsoleFont->fd_pixLineSpacing;
LCDRenderCloudsForComp();
//LCDRenderGrid();
LCDRenderClouds2();
dpConsole.DrawLine( 0, pixSizeJ-1, pixSizeI, pixSizeJ-1, LCDFadedColor(SE_COL_BLUE_NEUTRAL|255));
const COLOR colFill = (colDark & ~CT_AMASK) | 0x2F;
dpConsole.Fill( 0, pixSizeJ-pixLineSpacing*1.6f, pixSizeI, pixLineSpacing*1.6f, colFill);
// setup font
PIX pixTextX = (PIX)(dpConsole.GetWidth()*0.01f);
PIX pixYLine = dpConsole.GetHeight()-14;
dpConsole.SetFont( _pfdConsoleFont);
// print editing line of text
dpConsole.SetTextMode(-1);
CTString strPrompt;
if (_pGame->gm_csConsoleState == CS_TALK) {
strPrompt = TRANS("say: ");
} else {
strPrompt = "=> ";
}
CTString strLineOnScreen = strPrompt + strEditingLine;
dpConsole.PutText( strLineOnScreen, pixTextX, pixYLine, colLight);
dpConsole.SetTextMode(+1);
// add blinking cursor
if( ((ULONG)(_pTimer->GetRealTimeTick()*2)) & 1) {
CTString strCursor="_";
FLOAT fTextScalingX = dpConsole.dp_fTextScaling * dpConsole.dp_fTextAspect;
PIX pixCellSize = _pfdConsoleFont->fd_pixCharWidth * fTextScalingX + dpConsole.dp_pixTextCharSpacing;
PIX pixCursorX = pixTextX + (iCursorPos+strlen(strPrompt))*pixCellSize;
dpConsole.PutText( strCursor, pixCursorX, pixYLine+2, colDark);
}
// render previous outputs
con_iFirstLine = ClampDn( con_iFirstLine, 1L);
pixYLine -= (PIX)(pixLineSpacing * 1.333f);
ctConsoleLinesOnScreen = pixYLine/pixLineSpacing;
while( pixYLine >= 0) {
CTString strLineOnScreen = CON_GetLastLine(iBackwardLine);
dpConsole.PutText( strLineOnScreen, pixTextX, pixYLine, colDark);
iBackwardLine++;
pixYLine -= pixLineSpacing;
}
// all done
dpConsole.Unlock();
}
// print last few lines from console to top of screen
void CGame::ConsolePrintLastLines(CDrawPort *pdp)
{
// get number of lines to print
con_tmLastLines = Clamp( con_tmLastLines, 1.0f, 10.0f);
INDEX ctLines = CON_NumberOfLinesAfter( _pTimer->GetRealTimeTick() - con_tmLastLines);
// if no lines left to print, just skip it
if( ctLines==0) return;
// setup font
_pfdConsoleFont->SetFixedWidth();
pdp->SetFont( _pfdConsoleFont);
PIX pixCharHeight = _pfdConsoleFont->GetHeight() -1;
// put some filter underneath for easier reading
pdp->Fill( 0, 0, pdp->GetWidth(), pixCharHeight*ctLines, C_BLACK|128);
// for each line
for( INDEX iLine=0; iLine<ctLines; iLine++) {
CTString strLine = CON_GetLastLine(iLine+1);
pdp->PutText( strLine, 0, pixCharHeight*(ctLines-iLine-1), SE_COL_BLUE_LIGHT|255);
}
}
static void Key_Backspace( BOOL bShift, BOOL bRight)
{
// do nothing if string is empty
INDEX ctChars = strlen(strEditingLine);
if( ctChars==0) return;
if( bRight && iCursorPos<ctChars) { // DELETE key
if( bShift) { // delete to end of line
strEditingLine.TrimRight(iCursorPos);
} else { // delete only one char
strEditingLine.DeleteChar(iCursorPos);
}
}
if( !bRight && iCursorPos>0) { // BACKSPACE key
if( bShift) { // delete to start of line
strEditingLine.TrimLeft(ctChars-iCursorPos);
iCursorPos=0;
} else { // delete only one char
strEditingLine.DeleteChar(iCursorPos-1);
iCursorPos--;
}
}
}
static void Key_ArrowUp(void)
{
CTString strSlash = "/";
CTString strHistoryLine;
if( iHistoryLine==0) strCurrentLine = strEditingLine;
INDEX iCurrentHistoryLine = iHistoryLine;
do {
// determine previous line in history
iCurrentHistoryLine++;
const char *pchrHistoryStart = (const char*)strInputHistory;
const char *pchrHistoryEnd = pchrHistoryStart +strlen( strInputHistory) -1;
// we reach top of history, if line doesn't exist in history
if( !GetLineCountBackward( pchrHistoryStart, pchrHistoryEnd, iCurrentHistoryLine, strHistoryLine)) return;
} while( strCurrentLine!="" &&
strnicmp( strHistoryLine, strCurrentLine, Min(strlen(strHistoryLine), strlen(strCurrentLine)))!=0 &&
strnicmp( strHistoryLine, strSlash+strCurrentLine, Min(strlen(strHistoryLine), strlen(strCurrentLine)+1))!=0);
// set new editing line
iHistoryLine = iCurrentHistoryLine;
strEditingLine = strHistoryLine;
iCursorPos = strlen(strEditingLine);
}
static void Key_ArrowDown(void)
{
CTString strSlash = "/";
CTString strHistoryLine;
if( iHistoryLine==0) strCurrentLine = strEditingLine;
INDEX iCurrentHistoryLine = iHistoryLine;
while( iCurrentHistoryLine>1) {
iCurrentHistoryLine--;
const char *pchrHistoryStart = (const char *) strInputHistory;
const char *pchrHistoryEnd = pchrHistoryStart +strlen(strInputHistory) -1;
// line must exist in history
BOOL bExists = GetLineCountBackward( pchrHistoryStart, pchrHistoryEnd, iCurrentHistoryLine, strHistoryLine);
ASSERT( bExists);
// set new editing line
if( strCurrentLine=="" ||
strnicmp( strHistoryLine, strCurrentLine, Min(strlen(strHistoryLine), strlen(strCurrentLine))) ==0 ||
strnicmp( strHistoryLine, strSlash+strCurrentLine, Min(strlen(strHistoryLine), strlen(strCurrentLine)+1))==0) {
iHistoryLine = iCurrentHistoryLine;
strEditingLine = strHistoryLine;
iCursorPos = strlen(strEditingLine);
return;
}
}
}
void DoCheat(const CTString &strCommand, const CTString &strVar)
{
_pShell->SetINDEX(strVar, !_pShell->GetINDEX(strVar));
BOOL bNew = _pShell->GetINDEX(strVar);
CPrintF("%s: %s\n", strCommand, bNew?"ON":"OFF");
}
static void Key_Return(void)
{
// ignore keydown return (keyup will be handled) - because of key bind to return
//if( _pGame->gm_csConsoleState==CS_TALK) return;
// clear editing line from whitespaces
strEditingLine.TrimSpacesLeft();
strEditingLine.TrimSpacesRight();
// reset display position
con_iFirstLine = 1;
// ignore empty lines
if( strEditingLine=="" || strEditingLine=="/") {
strEditingLine = "";
iCursorPos = 0;
return;
}
// add to history
strInputHistory += strEditingLine +"\r\n";
iHistoryLine = 0;
// check for cheats
#define CHEAT_PREFIX "please"
if (strEditingLine.HasPrefix(CHEAT_PREFIX) || strEditingLine.HasPrefix("/" CHEAT_PREFIX)) {
strEditingLine.RemovePrefix(CHEAT_PREFIX);
strEditingLine.RemovePrefix("/ " CHEAT_PREFIX );
strEditingLine.TrimSpacesLeft();
if (strEditingLine=="god") {
DoCheat(strEditingLine, "cht_bGod");
} else if (strEditingLine=="giveall") {
DoCheat(strEditingLine, "cht_bGiveAll");
} else if (strEditingLine=="killall") {
DoCheat(strEditingLine, "cht_bKillAll");
} else if (strEditingLine=="open") {
DoCheat(strEditingLine, "cht_bOpen");
} else if (strEditingLine=="tellall") {
DoCheat(strEditingLine, "cht_bAllMessages");
} else if (strEditingLine=="fly") {
DoCheat(strEditingLine, "cht_bFly");
} else if (strEditingLine=="ghost") {
DoCheat(strEditingLine, "cht_bGhost");
} else if (strEditingLine=="invisible") {
DoCheat(strEditingLine, "cht_bInvisible");
} else if (strEditingLine=="refresh") {
DoCheat(strEditingLine, "cht_bRefresh");
} else {
CPrintF("sorry?\n");
}
// parse editing line
} else if( strEditingLine[0]=='/') {
// add to output and execute
CPrintF( "-> %s\n", strEditingLine);
strEditingLine+=";";
_pShell->Execute(strEditingLine+1);
}
else if( !_pGame->gm_bGameOn) {
// add to output and execute
CPrintF( "-> %s\n", strEditingLine);
strEditingLine+=";";
_pShell->Execute(strEditingLine);
}
else {
// just send chat
_pNetwork->SendChat(-1, -1, strEditingLine);
}
// reset editing line
strEditingLine = "";
iCursorPos = 0;
}
// find first character that is not part of a symbol, backwards
char *strrnonsym(const char *strString)
{
const char *pch = strString+strlen(strString)-1;
while( pch>=strString) {
char ch = *pch;
if( !isalnum(ch) && ch!='_') return (char*)pch;
pch--;
}
return NULL;
}
static void Key_Tab( BOOL bShift)
{
// clear editing line from whitespaces
strEditingLine.TrimSpacesLeft();
strEditingLine.TrimSpacesRight();
// eventualy prepend the command like with '/'
if (strEditingLine[0]!='/') strEditingLine = CTString("/")+strEditingLine;
// find symbol letter typed so far
CTString strSymbol;
CTString strLastThatCanBeExpanded;
if( strLastExpanded == "") {
strExpandStart = strEditingLine;
iSymbolOffset = 0;
char *pcLastSymbol = strrnonsym( strEditingLine);
// remember symbol text and offset inside editing line
if( pcLastSymbol!=NULL) {
strExpandStart = pcLastSymbol+1;
iSymbolOffset = (INDEX)(pcLastSymbol+1 - (const char*)strEditingLine);
}
// printout all symbols that matches (if not only one, and not TAB only)
INDEX ctSymbolsFound=0;
BOOL bFirstFound = FALSE;
CTString strLastMatched;
{FOREACHINDYNAMICARRAY( _pShell->sh_assSymbols, CShellSymbol, itss) {
// TAB only pressd?
if( strExpandStart=="") break;
// get completion name if current symbol is for user
if( !(itss->ss_ulFlags&SSF_USER)) continue;
strSymbol = itss->GetCompletionString();
// if this symbol can be expanded
if( strnicmp( strSymbol, strExpandStart, Min(strlen(strSymbol),strlen(strExpandStart))) == 0) {
// can we print last found symbol ?
if( strLastMatched!="") {
if( !bFirstFound) CPrintF( " -\n");
CPrintF( " %s\n", strLastMatched);
bFirstFound = TRUE;
}
strLastMatched = strSymbol;
ctSymbolsFound++;
}
}}
// print last symbol
if( ctSymbolsFound>1) CPrintF( " %s\n", strLastMatched);
}
// for each of symbols in the shell
bLastExpandedFound = FALSE;
BOOL bTabSymbolFound = FALSE;
{FOREACHINDYNAMICARRAY( _pShell->sh_assSymbols, CShellSymbol, itss)
{
// skip if it is not visible to user
if( !(itss->ss_ulFlags&SSF_USER)) continue;
// get completion name for that symbol
strSymbol = itss->GetCompletionString();
// if this symbol can be expanded
if( strnicmp( strSymbol, strExpandStart, Min(strlen(strSymbol),strlen(strExpandStart))) == 0)
{
// at least one symbol is found, so tab will work
bTabSymbolFound = TRUE;
// if this is first time we are doing this
if( strLastExpanded == "") {
// remember symbol as last expanded and set it as current
strLastExpanded = strSymbol;
break;
}
// if last expanded was already found, set this symbol as result and remember it as last expanded
if( bLastExpandedFound) {
strLastExpanded = strSymbol;
break;
}
// if last expanded was not found yet, check if this one is last expanded
if( stricmp( strLastExpanded, strSymbol) == 0) {
// if looping backward (Shift+Tab)
if( bShift) {
// if we can loop backwards
if( strLastThatCanBeExpanded != "") {
strLastExpanded = strLastThatCanBeExpanded;
break;
// act like no symbols are found
} else {
bTabSymbolFound = FALSE;
break;
}
}
// if so, mark it
bLastExpandedFound = TRUE;
}
// remember current as last that can be expanded (for loopbing back)
strLastThatCanBeExpanded = strSymbol;
}
}}
// if symbol was found
if( bTabSymbolFound) {
// set it in current editing line
*((char*)(const char*)strEditingLine +iSymbolOffset) = '\0';
strEditingLine += strLastExpanded;
}
iCursorPos = strlen(strEditingLine);
}
static void Key_PgUp( BOOL bShift)
{
if( bShift) con_iFirstLine += ctConsoleLinesOnScreen;
else con_iFirstLine++;
}
static void Key_PgDn( BOOL bShift)
{
if( bShift) con_iFirstLine -= ctConsoleLinesOnScreen;
else con_iFirstLine--;
con_iFirstLine = ClampDn( con_iFirstLine, 1L);
}
void CGame::ConsoleKeyDown( MSG msg)
{
// if console is off
if (_pGame->gm_csConsoleState==CS_OFF || _pGame->gm_csConsoleState==CS_TURNINGOFF) {
// do nothing
return;
}
BOOL bShift = GetKeyState(VK_SHIFT) & 0x8000;
switch( msg.wParam) {
case VK_RETURN: Key_Return(); break;
case VK_UP: Key_ArrowUp(); break;
case VK_DOWN: Key_ArrowDown(); break;
case VK_TAB: Key_Tab(bShift); break;
case VK_PRIOR: Key_PgUp(bShift); break;
case VK_NEXT: Key_PgDn(bShift); break;
case VK_BACK: Key_Backspace(bShift, FALSE); break;
case VK_DELETE: Key_Backspace(bShift, TRUE); break;
case VK_LEFT: if( iCursorPos > 0) iCursorPos--; break;
case VK_RIGHT: if( iCursorPos < strlen(strEditingLine)) iCursorPos++; break;
case VK_HOME: iCursorPos = 0; break;
case VK_END: iCursorPos = strlen(strEditingLine); break;
}
}
void CGame::ConsoleChar( MSG msg)
{
// if console is off, do nothing
if (_pGame->gm_csConsoleState==CS_OFF) return;
// for all keys except tab and shift, discard last found tab browsing symbol
char chrKey = msg.wParam;
if( msg.wParam!=VK_TAB && msg.wParam!=VK_SHIFT) strLastExpanded = "";
// if key with letter pressed
if( isprint(chrKey) && chrKey!='`') {
// insert it to editing line
strEditingLine.InsertChar( iCursorPos, chrKey);
iCursorPos++;
// reset history line
iHistoryLine = 0;
}
}