2017-07-13 10:26:03 +02:00
|
|
|
/*
|
2023-02-01 16:42:03 +02:00
|
|
|
* CInGameConsole.cpp, part of VCMI engine
|
2017-07-13 10:26:03 +02:00
|
|
|
*
|
|
|
|
* Authors: listed in file AUTHORS in main folder
|
|
|
|
*
|
|
|
|
* License: GNU General Public License v2.0 or later
|
|
|
|
* Full text of license available in license.txt file, in main folder
|
|
|
|
*
|
|
|
|
*/
|
2014-07-15 10:14:49 +03:00
|
|
|
|
2023-02-01 20:42:06 +02:00
|
|
|
#include "StdInc.h"
|
|
|
|
#include "CInGameConsole.h"
|
2014-07-13 20:53:37 +03:00
|
|
|
|
|
|
|
#include "../CGameInfo.h"
|
|
|
|
#include "../CPlayerInterface.h"
|
2024-03-18 19:52:53 +02:00
|
|
|
#include "../CServerHandler.h"
|
|
|
|
#include "../GameChatHandler.h"
|
2023-02-01 20:42:06 +02:00
|
|
|
#include "../ClientCommandManager.h"
|
2023-03-01 17:20:05 +02:00
|
|
|
#include "../gui/CGuiHandler.h"
|
2023-05-16 14:10:26 +02:00
|
|
|
#include "../gui/WindowHandler.h"
|
2023-04-27 19:21:06 +02:00
|
|
|
#include "../gui/Shortcut.h"
|
2023-06-02 15:42:18 +02:00
|
|
|
#include "../gui/TextAlignment.h"
|
2024-05-02 14:55:20 +02:00
|
|
|
#include "../media/ISoundPlayer.h"
|
2023-03-01 17:20:05 +02:00
|
|
|
#include "../render/Colors.h"
|
2023-06-02 15:42:18 +02:00
|
|
|
#include "../render/Canvas.h"
|
2023-09-19 11:20:16 +02:00
|
|
|
#include "../render/IScreenHandler.h"
|
2023-05-08 14:18:34 +02:00
|
|
|
#include "../adventureMap/AdventureMapInterface.h"
|
2023-05-07 19:15:43 +02:00
|
|
|
#include "../windows/CMessage.h"
|
2014-07-13 20:53:37 +03:00
|
|
|
|
|
|
|
#include "../../CCallback.h"
|
2023-02-01 20:42:06 +02:00
|
|
|
#include "../../lib/CConfigHandler.h"
|
2023-08-20 22:09:17 +02:00
|
|
|
#include "../../lib/CThreadHelper.h"
|
2023-02-01 20:42:06 +02:00
|
|
|
#include "../../lib/mapObjects/CArmedInstance.h"
|
2024-07-20 14:55:17 +02:00
|
|
|
#include "../../lib/texts/TextOperations.h"
|
2012-06-13 16:04:06 +03:00
|
|
|
|
2018-04-07 13:34:11 +02:00
|
|
|
CInGameConsole::CInGameConsole()
|
2023-03-22 23:09:43 +02:00
|
|
|
: CIntObject(KEYBOARD | TIME | TEXTINPUT)
|
|
|
|
, prevEntDisp(-1)
|
|
|
|
{
|
2023-07-03 18:24:12 +02:00
|
|
|
setRedrawParent(true);
|
2023-03-22 23:09:43 +02:00
|
|
|
}
|
|
|
|
|
2023-06-02 15:42:18 +02:00
|
|
|
void CInGameConsole::showAll(Canvas & to)
|
2018-04-07 13:34:11 +02:00
|
|
|
{
|
2023-03-22 23:09:43 +02:00
|
|
|
show(to);
|
2018-04-07 13:34:11 +02:00
|
|
|
}
|
|
|
|
|
2023-06-02 15:42:18 +02:00
|
|
|
void CInGameConsole::show(Canvas & to)
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
2023-04-08 17:53:47 +02:00
|
|
|
if (LOCPLINT->cingconsole != this)
|
|
|
|
return;
|
|
|
|
|
2014-07-13 18:39:45 +03:00
|
|
|
int number = 0;
|
|
|
|
|
2023-03-22 23:09:43 +02:00
|
|
|
for(auto & text : texts)
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
2022-12-21 17:02:53 +02:00
|
|
|
Point leftBottomCorner(0, pos.h);
|
2023-03-22 23:09:43 +02:00
|
|
|
Point textPosition(leftBottomCorner.x + 50, leftBottomCorner.y - texts.size() * 20 - 80 + number * 20);
|
2022-12-21 17:02:53 +02:00
|
|
|
|
2023-06-02 15:42:18 +02:00
|
|
|
to.drawText(pos.topLeft() + textPosition, FONT_MEDIUM, Colors::GREEN, ETextAlignment::TOPLEFT, text.text);
|
2023-03-22 23:09:43 +02:00
|
|
|
number++;
|
2014-07-13 18:39:45 +03:00
|
|
|
}
|
2023-03-22 23:09:43 +02:00
|
|
|
}
|
2014-07-13 18:39:45 +03:00
|
|
|
|
2023-03-22 23:09:43 +02:00
|
|
|
void CInGameConsole::tick(uint32_t msPassed)
|
|
|
|
{
|
2024-03-18 19:52:53 +02:00
|
|
|
// Check whether text input is active - we want to keep recent messages visible during this period
|
2024-03-27 13:08:41 +02:00
|
|
|
if(isEnteringText())
|
2024-03-18 19:52:53 +02:00
|
|
|
return;
|
|
|
|
|
2023-03-22 23:09:43 +02:00
|
|
|
size_t sizeBefore = texts.size();
|
|
|
|
|
2024-03-18 19:52:53 +02:00
|
|
|
for(auto & text : texts)
|
|
|
|
text.timeOnScreen += msPassed;
|
2023-03-22 23:09:43 +02:00
|
|
|
|
2024-03-18 19:52:53 +02:00
|
|
|
vstd::erase_if(
|
|
|
|
texts,
|
|
|
|
[&](const auto & value)
|
|
|
|
{
|
|
|
|
return value.timeOnScreen > defaultTimeout;
|
2014-07-13 18:39:45 +03:00
|
|
|
}
|
2024-03-18 19:52:53 +02:00
|
|
|
);
|
2023-03-22 23:09:43 +02:00
|
|
|
|
|
|
|
if(sizeBefore != texts.size())
|
2023-05-16 14:10:26 +02:00
|
|
|
GH.windows().totalRedraw(); // FIXME: ingame console has no parent widget set
|
2014-07-13 18:39:45 +03:00
|
|
|
}
|
|
|
|
|
2024-03-18 19:52:53 +02:00
|
|
|
void CInGameConsole::addMessageSilent(const std::string & timeFormatted, const std::string & senderName, const std::string & messageText)
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
2024-03-18 19:52:53 +02:00
|
|
|
MetaString formatted = MetaString::createFromRawString("[%s] %s: %s");
|
|
|
|
formatted.replaceRawString(timeFormatted);
|
|
|
|
formatted.replaceRawString(senderName);
|
|
|
|
formatted.replaceRawString(messageText);
|
2023-03-22 23:09:43 +02:00
|
|
|
|
2024-03-18 19:52:53 +02:00
|
|
|
// Maximum width for a text line is limited by:
|
|
|
|
// 1) width of adventure map terrain area, for when in-game console is on top of advmap
|
|
|
|
// 2) width of castle/battle window (fixed to 800) when this window is open
|
|
|
|
// 3) arbitrary selected left and right margins
|
|
|
|
int maxWidth = std::min( 800, adventureInt->terrainAreaPixels().w) - 100;
|
2023-05-07 19:15:43 +02:00
|
|
|
|
2024-03-18 19:52:53 +02:00
|
|
|
auto splitText = CMessage::breakText(formatted.toString(), maxWidth, FONT_MEDIUM);
|
2023-05-07 19:15:43 +02:00
|
|
|
|
2024-03-18 19:52:53 +02:00
|
|
|
for(const auto & entry : splitText)
|
|
|
|
texts.push_back({entry, 0});
|
2023-03-22 23:09:43 +02:00
|
|
|
|
2024-03-18 19:52:53 +02:00
|
|
|
while(texts.size() > maxDisplayedTexts)
|
|
|
|
texts.erase(texts.begin());
|
|
|
|
}
|
|
|
|
|
|
|
|
void CInGameConsole::addMessage(const std::string & timeFormatted, const std::string & senderName, const std::string & messageText)
|
|
|
|
{
|
|
|
|
addMessageSilent(timeFormatted, senderName, messageText);
|
2023-03-22 23:09:43 +02:00
|
|
|
|
2023-05-16 14:10:26 +02:00
|
|
|
GH.windows().totalRedraw(); // FIXME: ingame console has no parent widget set
|
2023-09-19 01:08:49 +02:00
|
|
|
|
|
|
|
int volume = CCS->soundh->getVolume();
|
|
|
|
if(volume == 0)
|
|
|
|
CCS->soundh->setVolume(settings["general"]["sound"].Integer());
|
|
|
|
int handle = CCS->soundh->playSound(AudioPath::builtin("CHAT"));
|
|
|
|
if(volume == 0)
|
2023-09-19 11:20:16 +02:00
|
|
|
CCS->soundh->setCallback(handle, [&]() { if(!GH.screenHandler().hasFocus()) CCS->soundh->setVolume(0); });
|
2014-07-13 18:39:45 +03:00
|
|
|
}
|
|
|
|
|
2023-07-04 17:42:52 +02:00
|
|
|
bool CInGameConsole::captureThisKey(EShortcut key)
|
|
|
|
{
|
2024-03-27 13:08:41 +02:00
|
|
|
if (!isEnteringText())
|
2023-07-04 17:42:52 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
switch (key)
|
|
|
|
{
|
|
|
|
case EShortcut::GLOBAL_ACCEPT:
|
|
|
|
case EShortcut::GLOBAL_CANCEL:
|
|
|
|
case EShortcut::GAME_ACTIVATE_CONSOLE:
|
|
|
|
case EShortcut::GLOBAL_BACKSPACE:
|
|
|
|
case EShortcut::MOVE_UP:
|
|
|
|
case EShortcut::MOVE_DOWN:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-27 19:21:06 +02:00
|
|
|
void CInGameConsole::keyPressed (EShortcut key)
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
2023-04-08 17:53:47 +02:00
|
|
|
if (LOCPLINT->cingconsole != this)
|
|
|
|
return;
|
|
|
|
|
2024-03-27 13:08:41 +02:00
|
|
|
if(!isEnteringText() && key != EShortcut::GAME_ACTIVATE_CONSOLE)
|
2023-04-08 17:53:47 +02:00
|
|
|
return; //because user is not entering any text
|
2014-07-13 18:39:45 +03:00
|
|
|
|
2023-02-02 18:02:25 +02:00
|
|
|
switch(key)
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
2023-04-27 19:21:06 +02:00
|
|
|
case EShortcut::GLOBAL_CANCEL:
|
2023-07-04 17:42:52 +02:00
|
|
|
if(!enteredText.empty())
|
2023-04-27 19:21:06 +02:00
|
|
|
endEnteringText(false);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EShortcut::GAME_ACTIVATE_CONSOLE:
|
2023-07-04 17:42:52 +02:00
|
|
|
if(!enteredText.empty())
|
2023-04-27 19:21:06 +02:00
|
|
|
endEnteringText(false);
|
|
|
|
else
|
|
|
|
startEnteringText();
|
|
|
|
break;
|
|
|
|
|
2023-04-28 13:22:03 +02:00
|
|
|
case EShortcut::GLOBAL_ACCEPT:
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
2023-07-04 17:42:52 +02:00
|
|
|
if(!enteredText.empty())
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
2023-01-22 02:14:28 +02:00
|
|
|
bool anyTextExceptCaret = enteredText.size() > 1;
|
|
|
|
endEnteringText(anyTextExceptCaret);
|
2014-07-13 18:39:45 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2023-04-27 19:21:06 +02:00
|
|
|
case EShortcut::GLOBAL_BACKSPACE:
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
|
|
|
if(enteredText.size() > 1)
|
|
|
|
{
|
2023-02-12 23:52:35 +02:00
|
|
|
TextOperations::trimRightUnicode(enteredText,2);
|
2014-07-13 18:39:45 +03:00
|
|
|
enteredText += '_';
|
|
|
|
refreshEnteredText();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2023-04-28 13:22:03 +02:00
|
|
|
case EShortcut::MOVE_UP:
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
2023-03-22 23:09:43 +02:00
|
|
|
if(previouslyEntered.empty())
|
2014-07-13 18:39:45 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
if(prevEntDisp == -1)
|
|
|
|
{
|
2020-10-01 10:38:06 +02:00
|
|
|
prevEntDisp = static_cast<int>(previouslyEntered.size() - 1);
|
2014-07-13 18:39:45 +03:00
|
|
|
enteredText = previouslyEntered[prevEntDisp] + "_";
|
|
|
|
refreshEnteredText();
|
|
|
|
}
|
|
|
|
else if( prevEntDisp > 0)
|
|
|
|
{
|
|
|
|
--prevEntDisp;
|
|
|
|
enteredText = previouslyEntered[prevEntDisp] + "_";
|
|
|
|
refreshEnteredText();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2023-04-28 13:22:03 +02:00
|
|
|
case EShortcut::MOVE_DOWN:
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
|
|
|
if(prevEntDisp != -1 && prevEntDisp+1 < previouslyEntered.size())
|
|
|
|
{
|
|
|
|
++prevEntDisp;
|
|
|
|
enteredText = previouslyEntered[prevEntDisp] + "_";
|
|
|
|
refreshEnteredText();
|
|
|
|
}
|
|
|
|
else if(prevEntDisp+1 == previouslyEntered.size()) //useful feature
|
|
|
|
{
|
|
|
|
prevEntDisp = -1;
|
|
|
|
enteredText = "_";
|
|
|
|
refreshEnteredText();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-24 03:23:26 +02:00
|
|
|
void CInGameConsole::textInputted(const std::string & inputtedText)
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
2023-04-08 17:53:47 +02:00
|
|
|
if (LOCPLINT->cingconsole != this)
|
|
|
|
return;
|
|
|
|
|
2024-03-27 13:08:41 +02:00
|
|
|
if(!isEnteringText())
|
2014-07-13 18:39:45 +03:00
|
|
|
return;
|
2023-07-04 17:42:52 +02:00
|
|
|
|
2014-07-13 18:39:45 +03:00
|
|
|
enteredText.resize(enteredText.size()-1);
|
|
|
|
|
2023-02-02 20:16:41 +02:00
|
|
|
enteredText += inputtedText;
|
2014-07-13 18:39:45 +03:00
|
|
|
enteredText += "_";
|
|
|
|
|
|
|
|
refreshEnteredText();
|
|
|
|
}
|
|
|
|
|
2023-02-02 20:16:41 +02:00
|
|
|
void CInGameConsole::textEdited(const std::string & inputtedText)
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
|
|
|
//do nothing here
|
|
|
|
}
|
|
|
|
|
2024-03-18 19:52:53 +02:00
|
|
|
void CInGameConsole::showRecentChatHistory()
|
|
|
|
{
|
|
|
|
auto const & history = CSH->getGameChat().getChatHistory();
|
|
|
|
|
|
|
|
texts.clear();
|
|
|
|
|
|
|
|
int entriesToShow = std::min<int>(maxDisplayedTexts, history.size());
|
|
|
|
int firstEntryToShow = history.size() - entriesToShow;
|
|
|
|
|
|
|
|
for (int i = firstEntryToShow; i < history.size(); ++i)
|
|
|
|
addMessageSilent(history[i].dateFormatted, history[i].senderName, history[i].messageText);
|
|
|
|
|
|
|
|
GH.windows().totalRedraw();
|
|
|
|
}
|
|
|
|
|
2014-07-13 18:39:45 +03:00
|
|
|
void CInGameConsole::startEnteringText()
|
|
|
|
{
|
2023-05-17 22:22:45 +02:00
|
|
|
if (!isActive())
|
2022-12-19 22:53:31 +02:00
|
|
|
return;
|
|
|
|
|
2024-03-27 13:08:41 +02:00
|
|
|
if(isEnteringText())
|
2024-05-15 12:13:40 +02:00
|
|
|
{
|
|
|
|
// force-reset text input to re-show on-screen keyboard
|
|
|
|
GH.statusbar()->setEnteringMode(false);
|
|
|
|
GH.statusbar()->setEnteringMode(true);
|
|
|
|
GH.statusbar()->setEnteredText(enteredText);
|
2024-01-13 19:59:59 +02:00
|
|
|
return;
|
2024-05-15 12:13:40 +02:00
|
|
|
}
|
2024-01-13 19:59:59 +02:00
|
|
|
|
2022-12-19 22:53:31 +02:00
|
|
|
assert(currentStatusBar.expired());//effectively, nullptr check
|
2014-07-13 18:39:45 +03:00
|
|
|
|
2023-05-16 17:34:23 +02:00
|
|
|
currentStatusBar = GH.statusbar();
|
2022-12-19 22:53:31 +02:00
|
|
|
|
2014-07-13 18:39:45 +03:00
|
|
|
enteredText = "_";
|
|
|
|
|
2023-05-16 17:34:23 +02:00
|
|
|
GH.statusbar()->setEnteringMode(true);
|
|
|
|
GH.statusbar()->setEnteredText(enteredText);
|
2024-03-18 19:52:53 +02:00
|
|
|
|
|
|
|
showRecentChatHistory();
|
2014-07-13 18:39:45 +03:00
|
|
|
}
|
|
|
|
|
2023-01-06 22:01:00 +02:00
|
|
|
void CInGameConsole::endEnteringText(bool processEnteredText)
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
|
|
|
prevEntDisp = -1;
|
2023-01-06 22:01:00 +02:00
|
|
|
if(processEnteredText)
|
2014-07-13 18:39:45 +03:00
|
|
|
{
|
|
|
|
std::string txt = enteredText.substr(0, enteredText.size()-1);
|
|
|
|
previouslyEntered.push_back(txt);
|
2023-01-06 22:01:00 +02:00
|
|
|
|
2023-01-06 23:53:10 +02:00
|
|
|
if(txt.at(0) == '/')
|
|
|
|
{
|
|
|
|
//some commands like gosolo don't work when executed from GUI thread
|
2023-01-15 02:09:58 +02:00
|
|
|
auto threadFunction = [=]()
|
|
|
|
{
|
2023-08-20 22:09:17 +02:00
|
|
|
setThreadName("processCommand");
|
2023-01-15 02:09:58 +02:00
|
|
|
ClientCommandManager commandController;
|
|
|
|
commandController.processCommand(txt.substr(1), true);
|
|
|
|
};
|
|
|
|
|
|
|
|
boost::thread clientCommandThread(threadFunction);
|
2023-01-07 16:42:34 +02:00
|
|
|
clientCommandThread.detach();
|
2023-01-06 23:53:10 +02:00
|
|
|
}
|
2023-01-08 21:30:57 +02:00
|
|
|
else
|
2024-03-18 19:52:53 +02:00
|
|
|
CSH->getGameChat().sendMessageGameplay(txt);
|
2014-07-13 18:39:45 +03:00
|
|
|
}
|
2022-11-15 02:20:55 +02:00
|
|
|
enteredText.clear();
|
2022-12-19 22:53:31 +02:00
|
|
|
|
|
|
|
auto statusbar = currentStatusBar.lock();
|
|
|
|
assert(statusbar);
|
|
|
|
|
|
|
|
if (statusbar)
|
|
|
|
statusbar->setEnteringMode(false);
|
|
|
|
|
|
|
|
currentStatusBar.reset();
|
2014-07-13 18:39:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void CInGameConsole::refreshEnteredText()
|
|
|
|
{
|
2022-12-19 22:53:31 +02:00
|
|
|
auto statusbar = currentStatusBar.lock();
|
|
|
|
assert(statusbar);
|
|
|
|
|
|
|
|
if (statusbar)
|
|
|
|
statusbar->setEnteredText(enteredText);
|
2014-07-13 18:39:45 +03:00
|
|
|
}
|
|
|
|
|
2024-03-27 13:08:41 +02:00
|
|
|
bool CInGameConsole::isEnteringText() const
|
|
|
|
{
|
|
|
|
return !enteredText.empty();
|
|
|
|
}
|