850 lines
20 KiB
C++
850 lines
20 KiB
C++
/**
|
|
* =========================================================================
|
|
* File : CConsole.cpp
|
|
* Project : 0 A.D.
|
|
* Description : Implements the in-game console with scripting support.
|
|
* =========================================================================
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#include <wctype.h>
|
|
|
|
#include "CConsole.h"
|
|
|
|
#include "lib/ogl.h"
|
|
#include "lib/res/file/vfs.h"
|
|
#include "lib/res/graphics/unifont.h"
|
|
#include "lib/sysdep/sysdep.h"
|
|
#include "maths/MathUtil.h"
|
|
#include "network/Client.h"
|
|
#include "network/Server.h"
|
|
#include "ps/CLogger.h"
|
|
#include "ps/Globals.h"
|
|
#include "ps/Hotkey.h"
|
|
#include "ps/Interact.h"
|
|
#include "ps/Pyrogenesis.h"
|
|
#include "scripting/ScriptingHost.h"
|
|
#include "simulation/Entity.h"
|
|
|
|
CConsole* g_Console = 0;
|
|
|
|
CConsole::CConsole()
|
|
{
|
|
|
|
m_bToggle = false;
|
|
m_bVisible = false;
|
|
|
|
m_fVisibleFrac = 0.0f;
|
|
|
|
m_szBuffer = new wchar_t[CONSOLE_BUFFER_SIZE];
|
|
FlushBuffer();
|
|
|
|
m_iMsgHistPos = 1;
|
|
m_charsPerPage=0;
|
|
|
|
m_ScriptObject = NULL; // scripting host isn't initialised yet - we'll set this later
|
|
|
|
InsertMessage(L"[ 0 A.D. Console v0.12 ] type \"\\info\" for help");
|
|
InsertMessage(L"");
|
|
|
|
if (vfs_exists("gui/text/help.txt"))
|
|
{
|
|
FileIOBuf buf;
|
|
size_t size;
|
|
if ( vfs_load("gui/text/help.txt", buf, size) < 0 )
|
|
{
|
|
LOG( ERROR,"Console", "Help file not found for console" );
|
|
file_buf_free(buf);
|
|
return;
|
|
}
|
|
// TODO: read in text mode, or at least get rid of the \r\n somehow
|
|
// TODO: maybe the help file should be UTF-8 - we assume it's iso-8859-1 here
|
|
m_helpText = CStrW(CStr( (const char*)buf ));
|
|
file_buf_free(buf);
|
|
}
|
|
else
|
|
{
|
|
InsertMessage(L"No help file found.");
|
|
}
|
|
}
|
|
|
|
CConsole::~CConsole()
|
|
{
|
|
m_mapFuncList.clear();
|
|
m_deqMsgHistory.clear();
|
|
m_deqBufHistory.clear();
|
|
delete[] m_szBuffer;
|
|
|
|
if (m_ScriptObject)
|
|
JS_RemoveRoot(g_ScriptingHost.GetContext(), &m_ScriptObject);
|
|
}
|
|
|
|
|
|
void CConsole::SetSize(float X, float Y, float W, float H)
|
|
{
|
|
m_fX = X;
|
|
m_fY = Y;
|
|
m_fWidth = W;
|
|
m_fHeight = H;
|
|
}
|
|
|
|
void CConsole::UpdateScreenSize(int w, int h)
|
|
{
|
|
float height = h * 0.6f;
|
|
SetSize(0, h-height, (float)w, height);
|
|
}
|
|
|
|
|
|
void CConsole::ToggleVisible()
|
|
{
|
|
m_bToggle = true;
|
|
m_bVisible = !m_bVisible;
|
|
}
|
|
|
|
void CConsole::SetVisible( bool visible )
|
|
{
|
|
if( visible != m_bVisible )
|
|
m_bToggle = true;
|
|
m_bVisible = visible;
|
|
}
|
|
|
|
void CConsole::FlushBuffer(void)
|
|
{
|
|
// Clear the buffer and set the cursor and length to 0
|
|
memset(m_szBuffer, '\0', sizeof(wchar_t) * CONSOLE_BUFFER_SIZE);
|
|
m_iBufferPos = m_iBufferLength = 0;
|
|
}
|
|
|
|
|
|
void CConsole::ToLower(wchar_t* szMessage, uint iSize)
|
|
{
|
|
uint L = (uint)wcslen(szMessage);
|
|
|
|
if (L <= 0) return;
|
|
|
|
if (iSize && iSize < L) L = iSize;
|
|
|
|
for(uint i = 0; i < L; i++)
|
|
szMessage[i] = towlower(szMessage[i]);
|
|
}
|
|
|
|
|
|
void CConsole::Trim(wchar_t* szMessage, const wchar_t cChar, uint iSize)
|
|
{
|
|
size_t L = wcslen(szMessage);
|
|
if(!L)
|
|
return;
|
|
|
|
if (iSize && iSize < L) L = iSize;
|
|
|
|
wchar_t szChar[2] = { cChar, 0 };
|
|
|
|
// Find the first point at which szChar does not
|
|
// exist in the message
|
|
size_t ofs = wcsspn(szMessage, szChar);
|
|
if(ofs == 0) // no leading <cChar> chars - we're done
|
|
return;
|
|
|
|
// move everything <ofs> chars left, replacing leading cChar chars
|
|
L -= ofs;
|
|
memmove(szMessage, szMessage+ofs, L*sizeof(wchar_t));
|
|
|
|
for(ssize_t i = (ssize_t)L; i >= 0; i--)
|
|
{
|
|
szMessage[i] = '\0';
|
|
if (szMessage[i - 1] != cChar) break;
|
|
}
|
|
}
|
|
|
|
|
|
void CConsole::RegisterFunc(fptr F, const wchar_t* szName)
|
|
{
|
|
// need to allocate a copy - szName may be a const string literal
|
|
// (we'll change it - stripping out spaces and converting to lowercase).
|
|
wchar_t copy[CONSOLE_BUFFER_SIZE];
|
|
copy[CONSOLE_BUFFER_SIZE-1] = '\0';
|
|
wcsncpy(copy, szName, CONSOLE_BUFFER_SIZE-1);
|
|
|
|
Trim(copy);
|
|
ToLower(copy);
|
|
|
|
m_mapFuncList.insert(std::pair<std::wstring, fptr>(copy, F));
|
|
}
|
|
|
|
|
|
void CConsole::Update(const float DeltaTime)
|
|
{
|
|
if(m_bToggle)
|
|
{
|
|
const float AnimateTime = .30f;
|
|
const float Delta = DeltaTime / AnimateTime;
|
|
if(m_bVisible)
|
|
{
|
|
m_fVisibleFrac += Delta;
|
|
if(m_fVisibleFrac > 1.0f)
|
|
{
|
|
m_fVisibleFrac = 1.0f;
|
|
m_bToggle = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fVisibleFrac -= Delta;
|
|
if(m_fVisibleFrac < 0.0f)
|
|
{
|
|
m_fVisibleFrac = 0.0f;
|
|
m_bToggle = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Render Manager.
|
|
void CConsole::Render()
|
|
{
|
|
if (! (m_bVisible || m_bToggle) ) return;
|
|
|
|
// animation: slide in from top of screen
|
|
const float MaxY = m_fHeight;
|
|
const float DeltaY = (1.0f - m_fVisibleFrac) * MaxY;
|
|
|
|
glTranslatef(m_fX, m_fY + DeltaY, 0.0f); //Move to window position
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
DrawWindow();
|
|
DrawHistory();
|
|
DrawBuffer();
|
|
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
|
|
void CConsole::DrawWindow(void)
|
|
{
|
|
// TODO: Add texturing
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
glPushMatrix();
|
|
|
|
// Draw Background
|
|
// Set the color to a translucent blue
|
|
glColor4f(0.0f, 0.0f, 0.5f, 0.6f);
|
|
glBegin(GL_QUADS);
|
|
glVertex2f(0.0f, 0.0f);
|
|
glVertex2f(m_fWidth-1.0f, 0.0f);
|
|
glVertex2f(m_fWidth-1.0f, m_fHeight-1.0f);
|
|
glVertex2f(0.0f, m_fHeight-1.0f);
|
|
glEnd();
|
|
|
|
// Draw Border
|
|
// Set the color to a translucent yellow
|
|
glColor4f(0.5f, 0.5f, 0.0f, 0.6f);
|
|
glBegin(GL_LINE_LOOP);
|
|
glVertex2f(0.0f, 0.0f);
|
|
glVertex2f(m_fWidth-1.0f, 0.0f);
|
|
glVertex2f(m_fWidth-1.0f, m_fHeight-1.0f);
|
|
glVertex2f(0.0f, m_fHeight-1.0f);
|
|
glEnd();
|
|
|
|
if (m_fHeight > m_iFontHeight + 4)
|
|
{
|
|
glBegin(GL_LINES);
|
|
glVertex2f(0.0f, (GLfloat)(m_iFontHeight + 4));
|
|
glVertex2f(m_fWidth, (GLfloat)(m_iFontHeight + 4));
|
|
glEnd();
|
|
}
|
|
glPopMatrix();
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
|
|
void CConsole::DrawHistory(void) {
|
|
int i = 1;
|
|
|
|
std::deque<std::wstring>::iterator Iter; //History iterator
|
|
|
|
glPushMatrix();
|
|
glColor3f(1.0f, 1.0f, 1.0f); //Set color of text
|
|
glTranslatef(9.0f, (float)m_iFontOffset, 0.0f); //move away from the border
|
|
|
|
// Draw the text upside-down, because it's aligned with
|
|
// the GUI (which uses the top-left as (0,0))
|
|
glScalef(1.0f, -1.0f, 1.0f);
|
|
|
|
for (Iter = m_deqMsgHistory.begin();
|
|
Iter != m_deqMsgHistory.end()
|
|
&& (((i - m_iMsgHistPos + 1) * m_iFontHeight) < m_fHeight);
|
|
Iter++)
|
|
{
|
|
if (i >= m_iMsgHistPos){
|
|
glTranslatef(0.0f, -(float)m_iFontHeight, 0.0f);
|
|
|
|
glPushMatrix();
|
|
glwprintf(L"%ls", Iter->data());
|
|
glPopMatrix();
|
|
}
|
|
|
|
i++;
|
|
}
|
|
glPopMatrix();
|
|
}
|
|
|
|
//Renders the buffer to the screen.
|
|
void CConsole::DrawBuffer(void)
|
|
{
|
|
if (m_fHeight < m_iFontHeight) return;
|
|
|
|
glPushMatrix();
|
|
glColor3f(1.0f, 1.0f, 0.0f);
|
|
glTranslatef(2.0f, (float)m_iFontOffset, 0);
|
|
glScalef(1.0f, -1.0f, 1.0f);
|
|
|
|
glwprintf(L"]");
|
|
|
|
glColor3f(1.0f, 1.0f, 1.0f);
|
|
if (m_iBufferPos==0) DrawCursor();
|
|
|
|
for (int i = 0; i < m_iBufferLength; i++){
|
|
glwprintf(L"%lc", m_szBuffer[i]);
|
|
if (m_iBufferPos-1==i) DrawCursor();
|
|
}
|
|
glPopMatrix();
|
|
}
|
|
|
|
|
|
void CConsole::DrawCursor(void)
|
|
{
|
|
// (glPushMatrix is necessary because glwprintf does glTranslatef)
|
|
glPushMatrix();
|
|
// Slightly translucent yellow
|
|
glColor4f(1.0f, 1.0f, 0.0f, 0.8f);
|
|
|
|
// U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE
|
|
// (sort of like a | which is aligned to the left of most characters)
|
|
glwprintf(L"%lc", 0xFE33);
|
|
|
|
// Revert to the standard text colour
|
|
glColor3f(1.0f, 1.0f, 1.0f);
|
|
glPopMatrix();
|
|
}
|
|
|
|
|
|
//Inserts a character into the buffer.
|
|
void CConsole::InsertChar(const int szChar, const wchar_t cooked )
|
|
{
|
|
static int iHistoryPos = -1;
|
|
|
|
if (!m_bVisible) return;
|
|
|
|
switch (szChar){
|
|
case SDLK_RETURN:
|
|
iHistoryPos = -1;
|
|
m_iMsgHistPos = 1;
|
|
ProcessBuffer(m_szBuffer);
|
|
FlushBuffer();
|
|
return;
|
|
|
|
case SDLK_TAB:
|
|
// Auto Complete
|
|
return;
|
|
|
|
case SDLK_BACKSPACE:
|
|
if (IsEmpty() || IsBOB()) return;
|
|
|
|
if (m_iBufferPos == m_iBufferLength)
|
|
m_szBuffer[m_iBufferPos - 1] = '\0';
|
|
else{
|
|
for(int j=m_iBufferPos-1; j<m_iBufferLength-1; j++)
|
|
m_szBuffer[j] = m_szBuffer[j+1]; // move chars to left
|
|
m_szBuffer[m_iBufferLength-1] = '\0';
|
|
}
|
|
|
|
m_iBufferPos--;
|
|
m_iBufferLength--;
|
|
return;
|
|
|
|
case SDLK_DELETE:
|
|
if (IsEmpty() || IsEOB()) return;
|
|
|
|
if (m_iBufferPos == m_iBufferLength-1)
|
|
{
|
|
m_szBuffer[m_iBufferPos] = '\0';
|
|
m_iBufferLength--;
|
|
}
|
|
else
|
|
{
|
|
if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
|
|
{
|
|
// Make Ctrl-Delete delete up to end of line
|
|
m_szBuffer[m_iBufferPos] = '\0';
|
|
m_iBufferLength = m_iBufferPos;
|
|
}
|
|
else
|
|
{
|
|
// Delete just one char and move the others left
|
|
for(int j=m_iBufferPos; j<m_iBufferLength-1; j++)
|
|
m_szBuffer[j] = m_szBuffer[j+1];
|
|
m_szBuffer[m_iBufferLength-1] = '\0';
|
|
m_iBufferLength--;
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
case SDLK_HOME:
|
|
if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
|
|
{
|
|
int linesShown = (int)m_fHeight/m_iFontHeight - 4;
|
|
m_iMsgHistPos = clamp((int)m_deqMsgHistory.size() - linesShown, 1, (int)m_deqMsgHistory.size());
|
|
}
|
|
else
|
|
{
|
|
m_iBufferPos = 0;
|
|
}
|
|
return;
|
|
|
|
case SDLK_END:
|
|
if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
|
|
{
|
|
m_iMsgHistPos = 1;
|
|
}
|
|
else
|
|
{
|
|
m_iBufferPos = m_iBufferLength;
|
|
}
|
|
return;
|
|
|
|
case SDLK_LEFT:
|
|
if (m_iBufferPos) m_iBufferPos--;
|
|
return;
|
|
|
|
case SDLK_RIGHT:
|
|
if (m_iBufferPos != m_iBufferLength) m_iBufferPos++;
|
|
return;
|
|
|
|
// BEGIN: Buffer History Lookup
|
|
case SDLK_UP:
|
|
if ( m_deqBufHistory.size() )
|
|
{
|
|
int oldHistoryPos = iHistoryPos;
|
|
while( iHistoryPos != (int)m_deqBufHistory.size() - 1)
|
|
{
|
|
iHistoryPos++;
|
|
std::wstring& histString = m_deqBufHistory.at(iHistoryPos);
|
|
if((int)histString.length() >= m_iBufferPos)
|
|
{
|
|
bool bad = false;
|
|
for(int i=0; i<m_iBufferPos; i++)
|
|
{
|
|
if(histString[i] != m_szBuffer[i])
|
|
{
|
|
bad = true; break;
|
|
}
|
|
}
|
|
if(!bad)
|
|
{
|
|
SetBuffer(m_deqBufHistory.at(iHistoryPos).data());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// if we got here, failed to find a string with the right prefix;
|
|
// revert to the old position in case the user types more stuff
|
|
iHistoryPos = oldHistoryPos;
|
|
}
|
|
return;
|
|
|
|
case SDLK_DOWN:
|
|
if ( m_deqBufHistory.size() && iHistoryPos > 0 )
|
|
{
|
|
int oldHistoryPos = iHistoryPos;
|
|
while( iHistoryPos != 0)
|
|
{
|
|
iHistoryPos--;
|
|
std::wstring& histString = m_deqBufHistory.at(iHistoryPos);
|
|
if((int)histString.length() >= m_iBufferPos)
|
|
{
|
|
bool bad = false;
|
|
for(int i=0; i<m_iBufferPos; i++)
|
|
{
|
|
if(histString[i] != m_szBuffer[i])
|
|
{
|
|
bad = true; break;
|
|
}
|
|
}
|
|
if(!bad)
|
|
{
|
|
SetBuffer(m_deqBufHistory.at(iHistoryPos).data());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// if we got here, failed to find a string with the right prefix;
|
|
// revert to the old position in case the user types more stuff,
|
|
// and also clear any complietion we might've added going up
|
|
iHistoryPos = oldHistoryPos;
|
|
m_szBuffer[m_iBufferPos] = 0;
|
|
m_iBufferLength = m_iBufferPos;
|
|
}
|
|
return;
|
|
// END: Buffer History Lookup
|
|
|
|
// BEGIN: Message History Lookup
|
|
case SDLK_PAGEUP:
|
|
if (m_iMsgHistPos != (int)m_deqMsgHistory.size()) m_iMsgHistPos++;
|
|
return;
|
|
|
|
case SDLK_PAGEDOWN:
|
|
if (m_iMsgHistPos != 1) m_iMsgHistPos--;
|
|
return;
|
|
// END: Message History Lookup
|
|
|
|
default: //Insert a character
|
|
if (IsFull()) return;
|
|
if (cooked == 0) return;
|
|
|
|
if (IsEOB()) //are we at the end of the buffer?
|
|
m_szBuffer[m_iBufferPos] = cooked; //cat char onto end
|
|
else{ //we need to insert
|
|
int i;
|
|
for(i=m_iBufferLength; i>m_iBufferPos; i--)
|
|
m_szBuffer[i] = m_szBuffer[i-1]; // move chars to right
|
|
m_szBuffer[i] = cooked;
|
|
}
|
|
|
|
m_iBufferPos++;
|
|
m_iBufferLength++;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
void CConsole::InsertMessage(const wchar_t* szMessage, ...)
|
|
{
|
|
va_list args;
|
|
wchar_t szBuffer[CONSOLE_MESSAGE_SIZE];
|
|
|
|
va_start(args, szMessage);
|
|
if (vswprintf(szBuffer, CONSOLE_MESSAGE_SIZE, szMessage, args) == -1)
|
|
{
|
|
debug_printf("Error printfing console message (buffer size exceeded?)\n");
|
|
|
|
// Make it obvious that the text was trimmed (assuming it was)
|
|
wcscpy(szBuffer+CONSOLE_MESSAGE_SIZE-4, L"...");
|
|
}
|
|
va_end(args);
|
|
|
|
InsertMessageRaw(CStrW(szBuffer));
|
|
}
|
|
|
|
|
|
void CConsole::InsertMessageRaw(const CStrW& message)
|
|
{
|
|
// (TODO: this text-wrapping is rubbish since we now use variable-width fonts)
|
|
|
|
//Insert newlines to wraparound text where needed
|
|
CStrW wrapAround(message);
|
|
CStrW newline(L'\n');
|
|
size_t oldNewline=0;
|
|
size_t distance;
|
|
|
|
//make sure everything has been initialized
|
|
if ( m_charsPerPage != 0 )
|
|
{
|
|
while ( oldNewline+m_charsPerPage < wrapAround.length() )
|
|
{
|
|
distance = wrapAround.find(newline, oldNewline) - oldNewline;
|
|
if ( distance > m_charsPerPage )
|
|
{
|
|
oldNewline += m_charsPerPage;
|
|
wrapAround.insert( oldNewline++, newline );
|
|
}
|
|
else
|
|
oldNewline += distance+1;
|
|
}
|
|
}
|
|
// Split into lines and add each one individually
|
|
oldNewline = 0;
|
|
|
|
while ( (distance = wrapAround.find(newline, oldNewline)) != wrapAround.npos)
|
|
{
|
|
distance -= oldNewline;
|
|
m_deqMsgHistory.push_front(wrapAround.substr(oldNewline, distance));
|
|
oldNewline += distance+1;
|
|
}
|
|
m_deqMsgHistory.push_front(wrapAround.substr(oldNewline));
|
|
}
|
|
|
|
const wchar_t* CConsole::GetBuffer()
|
|
{
|
|
m_szBuffer[m_iBufferLength] = 0;
|
|
return( m_szBuffer );
|
|
}
|
|
|
|
void CConsole::SetBuffer(const wchar_t* szMessage, ...)
|
|
{
|
|
int oldBufferPos = m_iBufferPos; // remember since FlushBuffer will set it to 0
|
|
|
|
va_list args;
|
|
wchar_t szBuffer[CONSOLE_BUFFER_SIZE];
|
|
|
|
va_start(args, szMessage);
|
|
vswprintf(szBuffer, CONSOLE_BUFFER_SIZE, szMessage, args);
|
|
va_end(args);
|
|
|
|
FlushBuffer();
|
|
|
|
wcsncpy(m_szBuffer, szMessage, CONSOLE_BUFFER_SIZE);
|
|
m_iBufferLength = (int)wcslen(m_szBuffer);
|
|
m_iBufferPos = std::min(oldBufferPos, m_iBufferLength);
|
|
}
|
|
|
|
void CConsole::UseHistoryFile(const CStr& filename, int max_history_lines)
|
|
{
|
|
m_MaxHistoryLines = max_history_lines;
|
|
|
|
m_sHistoryFile = filename;
|
|
LoadHistory();
|
|
}
|
|
|
|
void CConsole::ProcessBuffer(const wchar_t* szLine)
|
|
{
|
|
if (szLine == NULL) return;
|
|
if (wcslen(szLine) <= 0) return;
|
|
|
|
debug_assert(wcslen(szLine) < CONSOLE_BUFFER_SIZE);
|
|
|
|
m_deqBufHistory.push_front(szLine);
|
|
SaveHistory(); // Do this each line for the moment; if a script causes
|
|
// a crash it's a useful record.
|
|
|
|
wchar_t szCommand[CONSOLE_BUFFER_SIZE] = { 0 };
|
|
|
|
std::map<std::wstring, fptr>::iterator Iter;
|
|
|
|
if (szLine[0] == '\\')
|
|
{
|
|
if (swscanf(szLine, L"\\%ls", szCommand) != 1)
|
|
return;
|
|
|
|
Trim(szCommand);
|
|
ToLower(szCommand);
|
|
|
|
if (!wcscmp(szCommand, L"info"))
|
|
{
|
|
InsertMessage(L"");
|
|
InsertMessage(L"[Information]");
|
|
InsertMessage(L" -View commands \"\\commands\"");
|
|
InsertMessage(L" -Call command \"\\<command>\"");
|
|
InsertMessage(L" -Say \"<string>\"");
|
|
InsertMessage(L" -Help - Lists functions usable from console");
|
|
InsertMessage(L"");
|
|
}
|
|
else if (!wcscmp(szCommand, L"commands"))
|
|
{
|
|
InsertMessage(L"");
|
|
InsertMessage(L"[Commands]");
|
|
|
|
if (!m_mapFuncList.size()) InsertMessage(L" (none registered)");
|
|
|
|
for (Iter = m_mapFuncList.begin(); Iter != m_mapFuncList.end(); Iter++)
|
|
InsertMessage(L" \\%ls", Iter->first.data());
|
|
|
|
InsertMessage(L"");
|
|
}
|
|
else if (! (wcscmp(szCommand, L"Help") && wcscmp(szCommand, L"help")) )
|
|
{
|
|
InsertMessage(L"");
|
|
InsertMessage(L"[Help]");
|
|
InsertMessageRaw(m_helpText);
|
|
}
|
|
else
|
|
{
|
|
Iter = m_mapFuncList.find(szCommand);
|
|
if (Iter == m_mapFuncList.end())
|
|
InsertMessage(L"unknown command <%ls>", szCommand);
|
|
else
|
|
Iter->second();
|
|
}
|
|
}
|
|
else if (szLine[0] == ':' || szLine[0] == '?')
|
|
{
|
|
// Process it as JavaScript
|
|
|
|
// Run the script inside the first selected entity, if there is one.
|
|
// (Actually do it by using a separate object with the entity as its parent, so the script
|
|
// can read the entities variables but will define new variables in a private scope)
|
|
// (NOTE: this doesn't actually work, because the entities don't really have properties
|
|
// since they aren't sufficiently like real JS objects, which makes them get ignored in
|
|
// this situation. But this code is here so that it will work when the entities get fixed.)
|
|
if (! m_ScriptObject)
|
|
{
|
|
m_ScriptObject = JS_NewObject(g_ScriptingHost.GetContext(), NULL, NULL, NULL);
|
|
JS_AddRoot(g_ScriptingHost.GetContext(), &m_ScriptObject); // gets unrooted in ~CConsole
|
|
}
|
|
if (! g_Selection.m_selected.empty())
|
|
{
|
|
JS_SetParent(g_ScriptingHost.GetContext(), m_ScriptObject, g_Selection.m_selected[0]->GetScript());
|
|
}
|
|
|
|
jsval rval = g_ScriptingHost.ExecuteScript( CStrW( szLine + 1 ), L"Console", m_ScriptObject );
|
|
if (szLine[0] == '?' && rval)
|
|
{
|
|
try {
|
|
InsertMessage( L"%ls", g_ScriptingHost.ValueToUCString( rval ).c_str() );
|
|
} catch (PSERROR_Scripting_ConversionFailed) {
|
|
InsertMessage( L"%hs", "<error converting return value to string>" );
|
|
}
|
|
}
|
|
|
|
JS_SetParent(g_ScriptingHost.GetContext(), m_ScriptObject, JS_GetGlobalObject(g_ScriptingHost.GetContext())); // so the previous parent can get garbage-collected
|
|
}
|
|
else
|
|
{
|
|
SendChatMessage(szLine);
|
|
}
|
|
}
|
|
|
|
void CConsole::LoadHistory()
|
|
{
|
|
// note: we don't care if this file doesn't exist or can't be read;
|
|
// just don't load anything in that case.
|
|
|
|
// do this before vfs_load to avoid an error message if file not found.
|
|
if (!vfs_exists(m_sHistoryFile))
|
|
return;
|
|
|
|
FileIOBuf buf; size_t buflen;
|
|
if (vfs_load(m_sHistoryFile, buf, buflen) < 0)
|
|
return;
|
|
|
|
CStr bytes ((char*)buf, buflen);
|
|
(void)file_buf_free(buf);
|
|
|
|
CStrW str (bytes.FromUTF8());
|
|
size_t pos = 0;
|
|
while (pos != CStrW::npos)
|
|
{
|
|
pos = str.find('\n');
|
|
if (pos != CStrW::npos)
|
|
{
|
|
if (pos > 0)
|
|
m_deqBufHistory.push_front(str.Left(str[pos-1] == '\r' ? pos - 1 : pos));
|
|
str = str.substr(pos + 1);
|
|
}
|
|
else if (str.length() > 0)
|
|
m_deqBufHistory.push_front(str);
|
|
}
|
|
}
|
|
|
|
void CConsole::SaveHistory()
|
|
{
|
|
CStr buffer;
|
|
std::deque<std::wstring>::iterator it;
|
|
int line_count = 0;
|
|
for (it = m_deqBufHistory.begin(); it != m_deqBufHistory.end(); ++it)
|
|
{
|
|
if (line_count++ >= m_MaxHistoryLines)
|
|
break;
|
|
buffer = CStrW(*it).ToUTF8() + "\n" + buffer;
|
|
}
|
|
vfs_store(m_sHistoryFile, (const void*)buffer.c_str(), buffer.length(), FILE_NO_AIO);
|
|
}
|
|
|
|
void CConsole::SendChatMessage(const wchar_t *szMessage)
|
|
{
|
|
if (g_NetClient || g_NetServer)
|
|
{
|
|
CChatMessage *msg=new CChatMessage();
|
|
msg->m_Recipient = PS_CHAT_RCP_ALL;
|
|
msg->m_Message = szMessage;
|
|
if (g_NetClient)
|
|
g_NetClient->Push(msg);
|
|
else
|
|
{
|
|
msg->m_Sender=0;
|
|
ReceivedChatMessage(g_NetServer->GetServerPlayerName(), msg->m_Message.c_str());
|
|
g_NetServer->Broadcast(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CConsole::ReceivedChatMessage(const wchar_t *szSender, const wchar_t *szMessage)
|
|
{
|
|
InsertMessage(L"%ls: %ls", szSender, szMessage);
|
|
}
|
|
|
|
static bool isUnprintableChar(SDL_keysym key)
|
|
{
|
|
// U+0000 to U+001F are control characters
|
|
if (key.unicode < 0x20)
|
|
{
|
|
switch (key.sym)
|
|
{
|
|
// We want to allow some, which are handled specially
|
|
case SDLK_RETURN: case SDLK_TAB:
|
|
case SDLK_BACKSPACE: case SDLK_DELETE:
|
|
case SDLK_HOME: case SDLK_END:
|
|
case SDLK_LEFT: case SDLK_RIGHT:
|
|
case SDLK_UP: case SDLK_DOWN:
|
|
case SDLK_PAGEUP: case SDLK_PAGEDOWN:
|
|
return false;
|
|
|
|
// Ignore the others
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
InReaction conInputHandler(const SDL_Event_* ev)
|
|
{
|
|
if( ev->ev.type == SDL_HOTKEYDOWN )
|
|
{
|
|
if( ev->ev.user.code == HOTKEY_CONSOLE_TOGGLE )
|
|
{
|
|
g_Console->ToggleVisible();
|
|
return IN_HANDLED;
|
|
}
|
|
else if( ev->ev.user.code == HOTKEY_CONSOLE_COPY )
|
|
{
|
|
sys_clipboard_set( g_Console->GetBuffer() );
|
|
return IN_HANDLED;
|
|
}
|
|
else if( ev->ev.user.code == HOTKEY_CONSOLE_PASTE )
|
|
{
|
|
wchar_t* text = sys_clipboard_get();
|
|
if(text)
|
|
{
|
|
for(wchar_t* c = text; *c; c++)
|
|
g_Console->InsertChar(0, *c);
|
|
|
|
sys_clipboard_free(text);
|
|
}
|
|
return IN_HANDLED;
|
|
}
|
|
}
|
|
|
|
if( ev->ev.type != SDL_KEYDOWN)
|
|
return IN_PASS;
|
|
|
|
SDLKey sym = ev->ev.key.keysym.sym;
|
|
|
|
if(!g_Console->IsActive())
|
|
return IN_PASS;
|
|
|
|
// Stop unprintable characters (ctrl+, alt+ and escape),
|
|
// also prevent ` and/or ~ appearing in console every time it's toggled.
|
|
if( !isUnprintableChar(ev->ev.key.keysym) &&
|
|
!hotkeys[HOTKEY_CONSOLE_TOGGLE] )
|
|
g_Console->InsertChar(sym, (wchar_t)ev->ev.key.keysym.unicode );
|
|
|
|
return IN_PASS;
|
|
}
|