/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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, see .
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// pfConsole Functions //
// //
//////////////////////////////////////////////////////////////////////////////
#include "pfConsole.h"
#include "pfConsoleEngine.h"
#include "plPipeline/plDebugText.h"
#include "plInputCore/plInputDevice.h"
#include "plInputCore/plInputInterface.h"
#include "plInputCore/plInputInterfaceMgr.h"
#include "pnInputCore/plKeyMap.h"
#include "pnInputCore/plKeyDef.h"
#include "plMessage/plInputEventMsg.h"
#include "plMessage/plConsoleMsg.h"
#include "plMessage/plInputIfaceMgrMsg.h"
#include "pnKeyedObject/plFixedKey.h"
#include "hsTimer.h"
#include "plgDispatch.h"
#include "plPipeline.h"
#include "hsConfig.h"
#include "pfPython/cyPythonInterface.h"
#include "plNetClient/plNetClientMgr.h"
#ifndef PLASMA_EXTERNAL_RELEASE
#include "pfGameMgr/pfGameMgr.h"
#endif // PLASMA_EXTERNAL_RELEASE
//// Static Class Stuff //////////////////////////////////////////////////////
pfConsole *pfConsole::fTheConsole = nil;
UInt32 pfConsole::fConsoleTextColor = 0xff00ff00;
plPipeline *pfConsole::fPipeline = nil;
//////////////////////////////////////////////////////////////////////////////
//// pfConsoleInputInterface - Input interface layer for the console /////////
//////////////////////////////////////////////////////////////////////////////
class pfConsoleInputInterface : public plInputInterface
{
protected:
pfConsole *fConsole;
virtual hsBool IHandleCtrlCmd( plCtrlCmd *cmd )
{
if( cmd->fControlCode == B_SET_CONSOLE_MODE )
{
if( cmd->fControlActivated )
{
// Activate/deactivate
switch( fConsole->fMode )
{
case pfConsole::kModeHidden:
fConsole->ISetMode( pfConsole::kModeSingleLine );
break;
case pfConsole::kModeSingleLine:
fConsole->ISetMode( pfConsole::kModeFull );
break;
case pfConsole::kModeFull:
fConsole->ISetMode( pfConsole::kModeHidden );
break;
}
}
return true;
}
return false;
}
public:
pfConsoleInputInterface( pfConsole *console )
{
fConsole = console;
SetEnabled( true ); // Always enabled
// Add our control codes to our control map. Do NOT add the key bindings yet.
// Note: HERE is where you specify the actions for each command, i.e. net propagate and so forth.
// This part basically declares us master of the bindings for these commands.
// IF YOU ARE LOOKING TO CHANGE THE DEFAULT KEY BINDINGS, DO NOT LOOK HERE. GO TO
// RestoreDefaultKeyMappings()!!!!
#ifndef PLASMA_EXTERNAL_RELEASE
fControlMap->AddCode( B_SET_CONSOLE_MODE, kControlFlagNormal | kControlFlagNoRepeat );
#endif
// IF YOU ARE LOOKING TO CHANGE THE DEFAULT KEY BINDINGS, DO NOT LOOK HERE. GO TO
// RestoreDefaultKeyMappings()!!!!
}
virtual UInt32 GetPriorityLevel( void ) const { return kConsolePriority; }
virtual UInt32 GetCurrentCursorID( void ) const { return kCursorHidden; }
virtual hsBool HasInterestingCursorID( void ) const { return false; }
virtual hsBool InterpretInputEvent( plInputEventMsg *pMsg )
{
plKeyEventMsg *keyMsg = plKeyEventMsg::ConvertNoRef( pMsg );
// HACK for now to let runlock work always (until we can think of a more generic and good way of doing this)
if( keyMsg != nil && keyMsg->GetKeyCode() != KEY_CAPSLOCK )
{
if( fConsole->fMode )
{
fConsole->IHandleKey( keyMsg );
return true;
}
}
return false;
}
virtual void RefreshKeyMap( void )
{
}
virtual void RestoreDefaultKeyMappings( void )
{
if( fControlMap != nil )
{
fControlMap->UnmapAllBindings();
#ifndef PLASMA_EXTERNAL_RELEASE
fControlMap->BindKey( KEY_TILDE, B_SET_CONSOLE_MODE );
#endif
}
}
};
//// Constructor & Destructor ////////////////////////////////////////////////
pfConsole::pfConsole()
{
fNumDisplayLines = 32;
fDisplayBuffer = nil;
fTheConsole = this;
fFXEnabled = true;
}
pfConsole::~pfConsole()
{
if( fInputInterface != nil )
{
plInputIfaceMgrMsg *msg = TRACKED_NEW plInputIfaceMgrMsg( plInputIfaceMgrMsg::kRemoveInterface );
msg->SetIFace( fInputInterface );
plgDispatch::MsgSend( msg );
hsRefCnt_SafeUnRef( fInputInterface );
fInputInterface = nil;
}
if( fDisplayBuffer != nil )
{
delete [] fDisplayBuffer;
fDisplayBuffer = nil;
}
fTheConsole = nil;
plgDispatch::Dispatch()->UnRegisterForExactType( plConsoleMsg::Index(), GetKey() );
plgDispatch::Dispatch()->UnRegisterForExactType( plControlEventMsg::Index(), GetKey() );
#ifndef PLASMA_EXTERNAL_RELEASE
pfGameMgr::GetInstance()->RemoveReceiver(GetKey());
#endif // PLASMA_EXTERNAL_RELEASE
}
pfConsole * pfConsole::GetInstance () {
return fTheConsole;
}
//// Init ////////////////////////////////////////////////////////////////////
void pfConsole::Init( pfConsoleEngine *engine )
{
fDisplayBuffer = TRACKED_NEW char[ fNumDisplayLines * kMaxCharsWide ];
memset( fDisplayBuffer, 0, fNumDisplayLines * kMaxCharsWide );
memset( fWorkingLine, 0, sizeof( fWorkingLine ) );
fWorkingCursor = 0;
memset( fHistory, 0, sizeof( fHistory ) );
fHistoryCursor = fHistoryRecallCursor = 0;
fEffectCounter = 0;
fMode = 0;
fMsgTimeoutTimer = 0;
fHelpMode = false;
fPythonMode = false;
fPythonFirstTime = true;
fPythonMultiLines = 0;
fHelpTimer = 0;
fCursorTicks = 0;
memset( fLastHelpMsg, 0, sizeof( fLastHelpMsg ) );
fEngine = engine;
fInputInterface = TRACKED_NEW pfConsoleInputInterface( this );
plInputIfaceMgrMsg *msg = TRACKED_NEW plInputIfaceMgrMsg( plInputIfaceMgrMsg::kAddInterface );
msg->SetIFace( fInputInterface );
plgDispatch::MsgSend( msg );
// Register for keyboard event messages
plgDispatch::Dispatch()->RegisterForExactType( plConsoleMsg::Index(), GetKey() );
plgDispatch::Dispatch()->RegisterForExactType( plControlEventMsg::Index(), GetKey() );
#ifndef PLASMA_EXTERNAL_RELEASE
pfGameMgr::GetInstance()->AddReceiver(GetKey());
#endif // PLASMA_EXTERNAL_RELEASE
}
//// ISetMode ////////////////////////////////////////////////////////////////
void pfConsole::ISetMode( UInt8 mode )
{
fMode = mode;
fEffectCounter = ( fFXEnabled ? kEffectDivisions : 0 );
fMsgTimeoutTimer = 0;
fInputInterface->RefreshKeyMap();
}
//// MsgReceive //////////////////////////////////////////////////////////////
hsBool pfConsole::MsgReceive( plMessage *msg )
{
plControlEventMsg *ctrlMsg = plControlEventMsg::ConvertNoRef( msg );
if( ctrlMsg != nil )
{
if( ctrlMsg->ControlActivated() && ctrlMsg->GetControlCode() == B_CONTROL_CONSOLE_COMMAND && plNetClientMgr::GetInstance()->GetFlagsBit(plNetClientMgr::kPlayingGame))
{
fEngine->RunCommand( ctrlMsg->GetCmdString(), IAddLineCallback );
return true;
}
return false;
}
plConsoleMsg *cmd = plConsoleMsg::ConvertNoRef( msg );
if( cmd != nil && cmd->GetString() != nil )
{
if( cmd->GetCmd() == plConsoleMsg::kExecuteFile )
{
if( !fEngine->ExecuteFile( (char *)cmd->GetString() ) )
{
// Change the following line once we have a better way of reporting
// errors in the parsing
static char str[ 256 ];
static char msg[ 1024 ];
sprintf( str, "Error parsing %s", cmd->GetString() );
sprintf( msg, "%s:\n\nCommand: '%s'\n%s", fEngine->GetErrorMsg(), fEngine->GetLastErrorLine(),
#ifdef HS_DEBUGGING
"" );
hsAssert( false, msg );
#else
"\nPress OK to continue parsing files." );
hsMessageBox( msg, str, hsMessageBoxNormal );
#endif
}
}
else if( cmd->GetCmd() == plConsoleMsg::kAddLine )
IAddParagraph( (char *)cmd->GetString() );
else if( cmd->GetCmd() == plConsoleMsg::kExecuteLine )
{
if( !fEngine->RunCommand( (char *)cmd->GetString(), IAddLineCallback ) )
{
// Change the following line once we have a better way of reporting
// errors in the parsing
static char msg[ 1024 ];
sprintf( msg, "%s:\n\nCommand: '%s'\n", fEngine->GetErrorMsg(), fEngine->GetLastErrorLine() );
IAddLineCallback( msg );
}
}
return true;
}
//========================================================================
// pfGameMgrMsg
#ifndef PLASMA_EXTERNAL_RELEASE
if (pfGameMgrMsg * gameMgrMsg = pfGameMgrMsg::ConvertNoRef(msg)) {
switch (gameMgrMsg->netMsg->messageId) {
//================================================================
// InviteReceived
case kSrv2Cli_GameMgr_InviteReceived: {
const Srv2Cli_GameMgr_InviteReceived & gmMsg = *(const Srv2Cli_GameMgr_InviteReceived *)gameMgrMsg->netMsg;
const char * inviterName = plNetClientMgr::GetInstance()->GetPlayerNameById(gmMsg.inviterId);
AddLineF("[GameMgr] Invite received: %S, %u. Inviter: %s", pfGameMgr::GetInstance()->GetGameNameByTypeId(gmMsg.gameTypeId), gmMsg.newGameId, inviterName ? inviterName : "");
}
return true;
//================================================================
// InviteRevoked
case kSrv2Cli_GameMgr_InviteRevoked: {
const Srv2Cli_GameMgr_InviteRevoked & gmMsg = *(const Srv2Cli_GameMgr_InviteRevoked *)gameMgrMsg->netMsg;
const char * inviterName = plNetClientMgr::GetInstance()->GetPlayerNameById(gmMsg.inviterId);
AddLineF("[GameMgr] Invite revoked: %S, %u. Inviter: %s", pfGameMgr::GetInstance()->GetGameNameByTypeId(gmMsg.gameTypeId), gmMsg.newGameId, inviterName ? inviterName : "");
}
return true;
DEFAULT_FATAL(gameMgrMsg->netMsg->messageId);
};
}
#endif // PLASMA_EXTERNAL_RELEASE
//========================================================================
//========================================================================
// pfGameCliMsg
#ifndef PLASMA_EXTERNAL_RELEASE
if (pfGameCliMsg * gameCliMsg = pfGameCliMsg::ConvertNoRef(msg)) {
pfGameCli * cli = gameCliMsg->gameCli;
//====================================================================
// Handle pfGameCli msgs
switch (gameCliMsg->netMsg->messageId) {
//================================================================
// PlayerJoined
case kSrv2Cli_Game_PlayerJoined: {
const Srv2Cli_Game_PlayerJoined & netMsg = *(const Srv2Cli_Game_PlayerJoined *)gameCliMsg->netMsg;
AddLineF(
"[Game %s:%u] Player joined: %s",
cli->GetName(),
cli->GetGameId(),
netMsg.playerId
? plNetClientMgr::GetInstance()->GetPlayerNameById(netMsg.playerId)
: "Computer"
);
}
return true;
//================================================================
// PlayerLeft
case kSrv2Cli_Game_PlayerLeft: {
const Srv2Cli_Game_PlayerLeft & netMsg = *(const Srv2Cli_Game_PlayerLeft *)gameCliMsg->netMsg;
AddLineF(
"[Game %s:%u] Player left: %s",
cli->GetName(),
cli->GetGameId(),
netMsg.playerId
? plNetClientMgr::GetInstance()->GetPlayerNameById(netMsg.playerId)
: "Computer"
);
}
return true;
//================================================================
// InviteFailed
case kSrv2Cli_Game_InviteFailed: {
const Srv2Cli_Game_InviteFailed & netMsg = *(const Srv2Cli_Game_InviteFailed *)gameCliMsg->netMsg;
AddLineF(
"[Game %s:%u] Invite failed for playerId %u, error %u",
cli->GetName(),
cli->GetGameId(),
netMsg.inviteeId,
netMsg.error
);
}
return true;
//================================================================
// OwnerChange
case kSrv2Cli_Game_OwnerChange: {
const Srv2Cli_Game_OwnerChange & netMsg = *(const Srv2Cli_Game_OwnerChange *)gameCliMsg->netMsg;
AddLineF(
"[Game %s:%u] Owner changed to playerId %u",
cli->GetName(),
cli->GetGameId(),
netMsg.ownerId
);
}
return true;
}
//====================================================================
// Handle Tic-Tac-Toe msgs
if (gameCliMsg->gameCli->GetGameTypeId() == kGameTypeId_TicTacToe) {
pfGmTicTacToe * ttt = pfGmTicTacToe::ConvertNoRef(cli);
ASSERT(ttt);
switch (gameCliMsg->netMsg->messageId) {
//============================================================
// GameStarted
case kSrv2Cli_TTT_GameStarted: {
const Srv2Cli_TTT_GameStarted & netMsg = *(const Srv2Cli_TTT_GameStarted *)gameCliMsg->netMsg;
if (netMsg.yourTurn)
AddLineF(
"[Game %s:%u] Game started. You are X's. You go first.",
cli->GetName(),
cli->GetGameId()
);
else
AddLineF(
"[Game %s:%u] Game started. You are O's. Other player goes first.",
cli->GetName(),
cli->GetGameId()
);
ttt->ShowBoard();
}
return true;
//============================================================
// GameOver
case kSrv2Cli_TTT_GameOver: {
const Srv2Cli_TTT_GameOver & netMsg = *(const Srv2Cli_TTT_GameOver *)gameCliMsg->netMsg;
switch (netMsg.result) {
case kTTTGameResultWinner:
if (netMsg.winnerId == NetCommGetPlayer()->playerInt)
AddLineF(
"[Game %s:%u] Game over. You won!",
cli->GetName(),
cli->GetGameId()
);
else
AddLineF(
"[Game %s:%u] Game over. You lost.",
cli->GetName(),
cli->GetGameId()
);
break;
case kTTTGameResultTied:
AddLineF(
"[Game %s:%u] Game over. You tied.",
cli->GetName(),
cli->GetGameId()
);
break;
case kTTTGameResultGave:
AddLineF(
"[Game %s:%u] Game over. You win by default.",
cli->GetName(),
cli->GetGameId()
);
break;
default:
AddLineF(
"[Game %s:%u] Game over. Server-side error %u.",
cli->GetName(),
cli->GetGameId(),
netMsg.result
);
break;
}
}
return true;
//============================================================
// MoveMade
case kSrv2Cli_TTT_MoveMade: {
const Srv2Cli_TTT_MoveMade & netMsg = *(const Srv2Cli_TTT_MoveMade *)gameCliMsg->netMsg;
const char * playerName
= netMsg.playerId
? plNetClientMgr::GetInstance()->GetPlayerNameById(netMsg.playerId)
: "Computer";
AddLineF(
"[Game %s:%u] %s moved:",
cli->GetName(),
cli->GetGameId(),
playerName
);
ttt->ShowBoard();
}
return true;
DEFAULT_FATAL(gameCliMsg->netMsg->messageId);
}
}
else {
FATAL("Unknown game type");
}
return true;
}
#endif // PLASMA_EXTERNAL_RELEASE
//========================================================================
return hsKeyedObject::MsgReceive(msg);
}
//// IHandleKey //////////////////////////////////////////////////////////////
void pfConsole::IHandleKey( plKeyEventMsg *msg )
{
char *c, key;
int i,eol;
static hsBool findAgain = false;
static UInt32 findCounter = 0;
if( !msg->GetKeyDown() )
return;
if( msg->GetKeyCode() == KEY_ESCAPE )
{
fWorkingLine[ fWorkingCursor = 0 ] = 0;
findAgain = false;
findCounter = 0;
fHelpMode = false;
fPythonMode = false;
fPythonMultiLines = 0;
IUpdateTooltip();
}
else if( msg->GetKeyCode() == KEY_TAB )
{
if ( fPythonMode )
{
// if we are in Python mode, then just add two spaces, tab over, sorta
if ( strlen(fWorkingLine) < kWorkingLineSize+2 )
{
strcat(&fWorkingLine[fWorkingCursor], " ");
fWorkingCursor += 2;
}
}
else
{
static char lastSearch[ kMaxCharsWide ];
char search[ kMaxCharsWide ];
if( !findAgain && findCounter == 0 )
strcpy( lastSearch, fWorkingLine );
strcpy( search, lastSearch );
if( findCounter > 0 )
{
// Not found the normal way; try using an unrestricted search
if( fEngine->FindNestedPartialCmd( search, findCounter, true ) )
{
strcpy( fWorkingLine, search );
findCounter++;
}
else
{
/// Try starting over...?
findCounter = 0;
if( fEngine->FindNestedPartialCmd( search, findCounter, true ) )
{
strcpy( fWorkingLine, search );
findCounter++;
}
}
}
else if( fEngine->FindPartialCmd( search, findAgain, true ) )
{
strcpy( fWorkingLine, search );
findAgain = true;
}
else if( findAgain )
{
/// Try starting over
strcpy( search, lastSearch );
if( fEngine->FindPartialCmd( search, findAgain = false, true ) )
{
strcpy( fWorkingLine, search );
findAgain = true;
}
}
else
{
// Not found the normal way; start an unrestricted search
if( fEngine->FindNestedPartialCmd( search, findCounter, true ) )
{
strcpy( fWorkingLine, search );
findCounter++;
}
}
fWorkingCursor = strlen( fWorkingLine );
IUpdateTooltip();
}
}
else if( msg->GetKeyCode() == KEY_UP )
{
i = ( fHistoryRecallCursor > 0 ) ? fHistoryRecallCursor - 1 : kNumHistoryItems - 1;
if( fHistory[ i ][ 0 ] != 0 )
{
fHistoryRecallCursor = i;
strcpy( fWorkingLine, fHistory[ fHistoryRecallCursor ] );
findAgain = false;
findCounter = 0;
fWorkingCursor = strlen( fWorkingLine );
IUpdateTooltip();
}
}
else if( msg->GetKeyCode() == KEY_DOWN )
{
if( fHistoryRecallCursor != fHistoryCursor )
{
i = ( fHistoryRecallCursor < kNumHistoryItems - 1 ) ? fHistoryRecallCursor + 1 : 0;
if( i != fHistoryCursor )
{
fHistoryRecallCursor = i;
strcpy( fWorkingLine, fHistory[ fHistoryRecallCursor ] );
}
else
{
memset( fWorkingLine, 0, sizeof( fWorkingLine ) );
fHistoryRecallCursor = fHistoryCursor;
}
findAgain = false;
findCounter = 0;
fWorkingCursor = strlen( fWorkingLine );
IUpdateTooltip();
}
}
else if( msg->GetKeyCode() == KEY_LEFT )
{
if( fWorkingCursor > 0 )
fWorkingCursor--;
}
else if( msg->GetKeyCode() == KEY_RIGHT )
{
if( fWorkingCursor < strlen( fWorkingLine ) )
fWorkingCursor++;
}
else if( msg->GetKeyCode() == KEY_BACKSPACE )
{
if( fWorkingCursor > 0 )
{
fWorkingCursor--;
c = &fWorkingLine[ fWorkingCursor ];
do
{
*c = *( c + 1 );
c++;
} while( *c != 0 );
findAgain = false;
findCounter = 0;
}
else if( fHelpMode )
fHelpMode = false;
IUpdateTooltip();
}
else if( msg->GetKeyCode() == KEY_DELETE )
{
c = &fWorkingLine[ fWorkingCursor ];
do
{
*c = *( c + 1 );
c++;
} while( *c != 0 );
findAgain = false;
findCounter = 0;
IUpdateTooltip();
}
else if( msg->GetKeyCode() == KEY_ENTER )
{
// leave leading space for Python multi lines (need the indents!)
if ( fPythonMultiLines == 0 )
{
// Clean up working line by removing any leading whitespace
for( c = fWorkingLine; *c == ' ' || *c == '\t'; c++ );
for( i = 0; *c != 0; i++, c++ )
fWorkingLine[ i ] = *c;
fWorkingLine[ i ] = 0;
eol = i;
}
if( fWorkingLine[ 0 ] == 0 && !fHelpMode && !fPythonMode )
{
// Blank line--just print a blank line to the console and skip
IAddLine( "" );
return;
}
// only save history line if there is something there
if( fWorkingLine[ 0 ] != 0 )
{
// Save to history
strcpy( fHistory[ fHistoryCursor ], fWorkingLine );
fHistoryCursor = ( fHistoryCursor < kNumHistoryItems - 1 ) ? fHistoryCursor + 1 : 0;
fHistoryRecallCursor = fHistoryCursor;
}
// EXECUTE!!! (warning: DESTROYS fWorkingLine)
if( fHelpMode )
{
if( fWorkingLine[ 0 ] == 0 )
IPrintSomeHelp();
else if( stricmp( fWorkingLine, "commands" ) == 0 )
fEngine->PrintCmdHelp( "", IAddLineCallback );
else if( !fEngine->PrintCmdHelp( fWorkingLine, IAddLineCallback ) )
{
c = (char *)fEngine->GetErrorMsg();
AddLine( c );
}
fHelpMode = false;
}
// are we in Python mode?
else if ( fPythonMode )
{
// are we in Python multi-line mode?
if ( fPythonMultiLines > 0 )
{
// if there was a line then bump num lines
if ( fWorkingLine[0] != 0 )
{
char displine[300];
sprintf(displine,"... %s",fWorkingLine);
AddLine( displine );
fPythonMultiLines++;
}
// is it time to evaluate all the multi lines that are saved?
if ( fWorkingLine[0] == 0 || fPythonMultiLines >= kNumHistoryItems )
{
if ( fPythonMultiLines >= kNumHistoryItems )
AddLine("Python Multi-line buffer full!");
// get the lines and stuff them in our buffer
char biglines[kNumHistoryItems*(kMaxCharsWide+1)];
biglines[0] = 0;
for ( i=fPythonMultiLines; i>0 ; i--)
{
// reach back in the history and find this line and paste it in here
int recall = fHistoryCursor - i;
if ( recall < 0 )
recall += kNumHistoryItems;
strcat(biglines,fHistory[ recall ]);
strcat(biglines,"\n");
}
// now evaluate this mess they made
PyObject* mymod = PythonInterface::FindModule("__main__");
PythonInterface::RunStringInteractive(biglines,mymod);
std::string output;
// get the messages
PythonInterface::getOutputAndReset(&output);
AddLine( output.c_str() );
// all done doing multi lines...
fPythonMultiLines = 0;
}
}
// else we are just doing single lines
else
{
// was there actually anything in the input buffer?
if ( fWorkingLine[0] != 0 )
{
char displine[300];
sprintf(displine,">>> %s",fWorkingLine);
AddLine( displine );
// check to see if this is going to be a multi line mode ( a ':' at the end)
if ( fWorkingLine[eol-1] == ':' )
{
fPythonMultiLines = 1;
}
else
// else if not the start of a multi-line then execute it
{
PyObject* mymod = PythonInterface::FindModule("__main__");
PythonInterface::RunStringInteractive(fWorkingLine,mymod);
std::string output;
// get the messages
PythonInterface::getOutputAndReset(&output);
AddLine( output.c_str() );
}
}
else
AddLine( ">>> " );
}
// find the end of the line
for( c = fWorkingLine, eol = 0; *c != 0; eol++, c++ );
}
else
{
if( !fEngine->RunCommand( fWorkingLine, IAddLineCallback ) )
{
c = (char *)fEngine->GetErrorMsg();
if( c[ 0 ] != 0 )
AddLine( c );
}
}
// Clear
fWorkingLine[ fWorkingCursor = 0 ] = 0;
findAgain = false;
findCounter = 0;
IUpdateTooltip();
}
else if( msg->GetKeyCode() == KEY_END )
{
fWorkingCursor = strlen( fWorkingLine );
}
else if( msg->GetKeyCode() == KEY_HOME )
{
fWorkingCursor = 0;
}
else
{
key = plKeyboardDevice::KeyEventToChar( msg );
// do they want to go into help mode?
if( !fPythonMode && key == '?' && fWorkingCursor == 0 )
{
/// Go into help mode
fHelpMode = true;
}
// do they want to go into Python mode?
else if( !fHelpMode && key == '\\' && fWorkingCursor == 0 )
{
// toggle Python mode
fPythonMode = fPythonMode ? false:true;
if ( fPythonMode )
{
if ( fPythonFirstTime )
{
IAddLine( "" ); // add a blank line
PyObject* mymod = PythonInterface::FindModule("__main__");
PythonInterface::RunStringInteractive("import sys;print 'Python',sys.version",mymod);
std::string output;
// get the messages
PythonInterface::getOutputAndReset(&output);
AddLine( output.c_str() );
fPythonFirstTime = false; // do this only once!
}
}
}
// or are they just typing in a working line
else if( fWorkingCursor < kMaxCharsWide - 2 && key != 0 )
{
for( i = strlen( fWorkingLine ) + 1; i > fWorkingCursor; i-- )
fWorkingLine[ i ] = fWorkingLine[ i - 1 ];
fWorkingLine[ fWorkingCursor++ ] = key;
findAgain = false;
findCounter = 0;
IUpdateTooltip();
}
}
}
//// IAddLineCallback ////////////////////////////////////////////////////////
void pfConsole::IAddLineCallback( const char *string )
{
fTheConsole->IAddParagraph( string, 0 );
}
//// IAddLine ////////////////////////////////////////////////////////////////
void pfConsole::IAddLine( const char *string, short leftMargin )
{
int i;
char *ptr;
/// Advance upward
for( i = 0, ptr = fDisplayBuffer; i < fNumDisplayLines - 1; i++ )
{
memcpy( ptr, ptr + kMaxCharsWide, kMaxCharsWide );
ptr += kMaxCharsWide;
}
if( string[ 0 ] == '\t' )
{
leftMargin += 4;
string++;
}
memset( ptr, 0, kMaxCharsWide );
memset( ptr, ' ', leftMargin );
strncpy( ptr + leftMargin, string, kMaxCharsWide - leftMargin - 1 );
if( fMode == 0 )
{
/// Console is invisible, so show this line for a bit of time
fMsgTimeoutTimer = kMsgHintTimeout;
}
}
//// IAddParagraph ///////////////////////////////////////////////////////////
void pfConsole::IAddParagraph( const char *s, short margin )
{
char *ptr, *ptr2, *ptr3, *string=(char*)s;
// Special character: if \i is in front of the string, indent it
while( strncmp( string, "\\i", 2 ) == 0 )
{
margin += 3;
string += 2;
}
for( ptr = string; ptr != nil && *ptr != 0; )
{
// Go as far as possible
if( strlen( ptr ) < kMaxCharsWide - margin - margin - 1 )
ptr2 = ptr + strlen( ptr );
else
{
// Back up until we hit a sep
ptr2 = ptr + kMaxCharsWide - margin - margin - 1;
for( ; ptr2 > string && *ptr2 != ' ' && *ptr2 != '\t' && *ptr2 != '\n'; ptr2-- );
}
// Check for carriage return
ptr3 = strchr( ptr, '\n' );
if( ptr3 == ptr )
{
IAddLine( "", margin );
ptr++;
continue;
}
if( ptr3 != nil && ptr3 < ptr2 )
ptr2 = ptr3;
// Add this part
if( ptr2 == ptr || *ptr2 == 0 )
{
IAddLine( ptr, margin );
break;
}
*ptr2 = 0;
IAddLine( ptr, margin );
ptr = ptr2 + 1;
}
}
//// IClear //////////////////////////////////////////////////////////////////
void pfConsole::IClear( void )
{
memset( fDisplayBuffer, 0, kMaxCharsWide * fNumDisplayLines );
}
//// Draw ////////////////////////////////////////////////////////////////////
void pfConsole::Draw( plPipeline *p )
{
int i, yOff, y, x, eOffset, height;
char *line;
char tmp[ kMaxCharsWide ];
hsBool showTooltip = false;
float thisTime; // For making the console FX speed konstant regardless of framerate
const float kEffectDuration = 0.5f;
plDebugText& drawText = plDebugText::Instance();
thisTime = (float)hsTimer::PrecTicksToSecs( hsTimer::GetPrecTickCount() );
if( fMode == kModeHidden && fEffectCounter == 0 )
{
if( fMsgTimeoutTimer > 0 )
{
/// Message hint--draw the last line of the console for a bit
drawText.DrawString( 10, 4, fDisplayBuffer + kMaxCharsWide * ( fNumDisplayLines - 1 ), fConsoleTextColor );
fMsgTimeoutTimer--;
}
fLastTime = thisTime;
return;
}
drawText.SetDrawOnTopMode( true );
yOff = drawText.GetFontHeight() + 2;
if( fMode == kModeSingleLine )
height = yOff * 3 + 14;
else
height = yOff * ( fNumDisplayLines + 2 ) + 14;
if( fHelpTimer == 0 && !fHelpMode && fLastHelpMsg[ 0 ] != 0 )
showTooltip = true;
if( fEffectCounter > 0 )
{
int numElapsed = (int)( (float)kEffectDivisions * ( ( thisTime - fLastTime ) / (float)kEffectDuration ) );
if( numElapsed > fEffectCounter )
numElapsed = fEffectCounter;
else if( numElapsed < 0 )
numElapsed = 0;
if( fMode == kModeSingleLine )
eOffset = fEffectCounter * height / kEffectDivisions;
else if( fMode == kModeFull )
eOffset = fEffectCounter * ( height - yOff * 3 - 14 ) / kEffectDivisions;
else
eOffset = ( kEffectDivisions - fEffectCounter ) * ( height - yOff * 3 - 14 ) / kEffectDivisions;
fEffectCounter -= numElapsed;
}
else
eOffset = 0;
fLastTime = thisTime;
if( fMode == kModeSingleLine )
{
// Bgnd (TEMP ONLY)
x = kMaxCharsWide * drawText.CalcStringWidth( "W" ) + 4;
y = height - eOffset;
drawText.DrawRect( 4, 0, x, y, /*color*/0, 0, 0, 127 );
/// Actual text
if( fEffectCounter == 0 )
drawText.DrawString( 10, 4, "Plasma 2.0 Console", 255, 255, 255, 255 );
if( !showTooltip )
drawText.DrawString( 10, 4 + yOff - eOffset, fDisplayBuffer + kMaxCharsWide * ( fNumDisplayLines - 1 ), fConsoleTextColor );
y = 4 + yOff + yOff - eOffset;
}
else
{
// Bgnd (TEMP ONLY)
x = kMaxCharsWide * drawText.CalcStringWidth( "W" ) + 4;
y = yOff * ( fNumDisplayLines + 2 ) + 14 - eOffset;
drawText.DrawRect( 4, 0, x, y, /*color*/0, 0, 0, 127 );
/// Actual text
drawText.DrawString( 10, 4, "Plasma 2.0 Console", 255, 255, 255, 255 );
static int countDown = 3000;
if( fHelpTimer > 0 || fEffectCounter > 0 || fMode != kModeFull )
countDown = 3000;
else if( countDown > -720 )
countDown--;
// Resource data is encrypted so testers can't peer in to the EXE, plz don't decrypt
static bool rezLoaded = false;
static char tmpSrc[ kMaxCharsWide ];
if( !rezLoaded )
{
memset( tmp, 0, sizeof( tmp ) );
memset( tmpSrc, 0, sizeof( tmpSrc ) );
// Our concession to windows
#ifdef HS_BUILD_FOR_WIN32
#include "../../Apps/plClient/res/resource.h"
HRSRC rsrc = FindResource( nil, MAKEINTRESOURCE( IDR_CNSL1 ), "CNSL" );
if( rsrc != nil )
{
HGLOBAL hdl = LoadResource( nil, rsrc );
if( hdl != nil )
{
UInt8 *ptr = (UInt8 *)LockResource( hdl );
if( ptr != nil )
{
for( i = 0; i < SizeofResource( nil, rsrc ); i++ )
tmpSrc[ i ] = ptr[ i ] + 26;
UnlockResource( hdl );
}
}
}
rezLoaded = true;
#else
// Need to define for other platforms?
#endif
}
memcpy( tmp, tmpSrc, sizeof( tmp ) );
if( countDown <= 0 )
{
y = 4 + yOff - eOffset;
if( countDown <= -480 )
{
tmp[ ( (-countDown - 480)>> 4 ) + 1 ] = 0;
drawText.DrawString( 10, y, tmp, fConsoleTextColor );
}
y += yOff * ( fNumDisplayLines - ( showTooltip ? 1 : 0 ) );
}
else
{
for( i = 0, y = 4 + yOff - eOffset, line = fDisplayBuffer; i < fNumDisplayLines - ( showTooltip ? 1 : 0 ); i++ )
{
drawText.DrawString( 10, y, line, fConsoleTextColor );
y += yOff;
line += kMaxCharsWide;
}
}
if( showTooltip )
y += yOff;
}
// strcpy( tmp, fHelpMode ? "Get Help On:" : "]" );
if ( fHelpMode )
strcpy( tmp, "Get Help On:");
else if (fPythonMode )
if ( fPythonMultiLines == 0 )
strcpy( tmp, ">>>");
else
strcpy( tmp, "...");
else
strcpy( tmp, "]" );
drawText.DrawString( 10, y, tmp, 255, 255, 255, 255 );
i = 10 + drawText.CalcStringWidth( tmp ) + 4;
drawText.DrawString( i, y, fWorkingLine, fConsoleTextColor );
if( fCursorTicks >= 0 )
{
strcpy( tmp, fWorkingLine );
tmp[ fWorkingCursor ] = 0;
x = drawText.CalcStringWidth( tmp );
drawText.DrawString( i + x, y + 2, "_", 255, 255, 255 );
}
fCursorTicks--;
if( fCursorTicks < -kCursorBlinkRate )
fCursorTicks = kCursorBlinkRate;
if( showTooltip )
drawText.DrawString( i, y - yOff, fLastHelpMsg, 255, 255, 0 );
else
fHelpTimer--;
drawText.SetDrawOnTopMode( false );
}
//// IUpdateTooltip //////////////////////////////////////////////////////////
void pfConsole::IUpdateTooltip( void )
{
char tmpStr[ kWorkingLineSize ];
char *c;
strcpy( tmpStr, fWorkingLine );
c = (char *)fEngine->GetCmdSignature( tmpStr );
if( c == nil || strcmp( c, fLastHelpMsg ) != 0 )
{
/// Different--update timer to wait
fHelpTimer = kHelpDelay;
strncpy( fLastHelpMsg, c ? c : "", kMaxCharsWide - 2 );
}
}
//// IPrintSomeHelp //////////////////////////////////////////////////////////
void pfConsole::IPrintSomeHelp( void )
{
char msg1[] = "The console contains commands arranged under groups and subgroups. \
To use a command, you type the group name plus the command, such as 'Console.Clear' or \
'Console Clear'.";
char msg2[] = "To get help on a command or group, type '?' followed by the command or \
group name. Typing '?' and just hitting enter will bring up this message. Typing '?' and \
then 'commands' will bring up a list of all base groups and commands.";
char msg3[] = "You can also have the console auto-complete a command by pressing tab. \
This will search for a group or command that starts with what you have typed. If there is more \
than one match, pressing tab repeatedly will cycle through all the matches.";
AddLine( "" );
AddLine( "How to use the console:" );
IAddParagraph( msg1, 2 );
AddLine( "" );
IAddParagraph( msg2, 2 );
AddLine( "" );
IAddParagraph( msg3, 2 );
AddLine( "" );
}
//============================================================================
void pfConsole::AddLineF(const char * fmt, ...) {
char str[1024];
va_list args;
va_start(args, fmt);
_vsnprintf(str, arrsize(str), fmt, args);
va_end(args);
AddLine(str);
}
//============================================================================
void pfConsole::RunCommandAsync (const char cmd[]) {
plConsoleMsg * consoleMsg = NEWZERO(plConsoleMsg);
consoleMsg->SetCmd(plConsoleMsg::kExecuteLine);
consoleMsg->SetString(cmd);
// consoleMsg->SetBreakBeforeDispatch(true);
consoleMsg->Send(nil, true);
}