/*==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); }