OpenRCT2/src/openrct2/interface/Chat.cpp

306 lines
9.2 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "Chat.h"
#include "../Context.h"
#include "../audio/AudioMixer.h"
#include "../audio/audio.h"
#include "../drawing/Drawing.h"
#include "../localisation/Formatter.h"
#include "../localisation/Formatting.h"
#include "../localisation/Localisation.h"
#include "../network/network.h"
#include "../platform/Platform.h"
#include "../util/Util.h"
#include "../world/Location.hpp"
using namespace OpenRCT2;
using namespace OpenRCT2::Audio;
bool gChatOpen = false;
static u8string _chatCurrentLine;
static std::deque<u8string> _chatHistory;
static std::deque<uint32_t> _chatHistoryTime;
static uint32_t _chatCaretTicks = 0;
static int32_t _chatLeft;
static int32_t _chatTop;
static int32_t _chatRight;
static int32_t _chatBottom;
static int32_t _chatWidth;
static int32_t _chatHeight;
static TextInputSession* _chatTextInputSession;
static const u8string& ChatGetHistory(size_t index);
static uint32_t ChatHistoryGetTime(size_t index);
static void ChatClearInput();
static int32_t ChatHistoryDrawString(DrawPixelInfo& dpi, const char* text, const ScreenCoordsXY& screenCoords, int32_t width);
bool ChatAvailable()
{
return NetworkGetMode() != NETWORK_MODE_NONE && NetworkGetStatus() == NETWORK_STATUS_CONNECTED
&& NetworkGetAuthstatus() == NetworkAuth::Ok;
}
void ChatOpen()
{
gChatOpen = true;
_chatTextInputSession = ContextStartTextInput(_chatCurrentLine, kChatMaxMessageLength);
}
void ChatClose()
{
gChatOpen = false;
ContextStopTextInput();
}
void ChatToggle()
{
if (gChatOpen)
{
ChatClose();
}
else
{
ChatOpen();
}
}
void ChatInit()
{
_chatHistory.clear();
_chatHistoryTime.clear();
}
void ChatUpdate()
{
// Flash the caret
_chatCaretTicks = (_chatCaretTicks + 1) % 30;
}
void ChatDraw(DrawPixelInfo& dpi, uint8_t chatBackgroundColor)
{
thread_local std::string lineBuffer;
if (!ChatAvailable())
{
gChatOpen = false;
return;
}
_chatLeft = 10;
_chatRight = std::min<int16_t>((ContextGetWidth() - 10), kChatMaxWindowWidth);
_chatWidth = _chatRight - _chatLeft;
_chatBottom = ContextGetHeight() - 45;
_chatTop = _chatBottom - 10;
const char* inputLine = _chatCurrentLine.c_str();
int32_t inputLineHeight = 10;
// Draw chat window
if (gChatOpen)
{
inputLineHeight = ChatStringWrappedGetHeight(inputLine, _chatWidth - 10);
_chatTop -= inputLineHeight;
for (const auto& entry : _chatHistory)
{
if (entry.empty())
{
continue;
}
lineBuffer = entry;
int32_t lineHeight = ChatStringWrappedGetHeight(lineBuffer, _chatWidth - 10);
_chatTop -= (lineHeight + 5);
}
_chatHeight = _chatBottom - _chatTop;
if (_chatTop < 50)
{
_chatTop = 50;
}
else if (_chatHeight < 150)
{ // Min height
_chatTop = _chatBottom - 150;
_chatHeight = 150;
}
ScreenCoordsXY topLeft{ _chatLeft, _chatTop };
ScreenCoordsXY bottomRight{ _chatRight, _chatBottom };
ScreenCoordsXY bottomLeft{ _chatLeft, _chatBottom };
GfxSetDirtyBlocks(
{ topLeft - ScreenCoordsXY{ 0, 5 }, bottomRight + ScreenCoordsXY{ 0, 5 } }); // Background area + Textbox
GfxFilterRect(
dpi, { topLeft - ScreenCoordsXY{ 0, 5 }, bottomRight + ScreenCoordsXY{ 0, 5 } },
FilterPaletteID::Palette51); // Opaque grey background
GfxFillRectInset(
dpi, { topLeft - ScreenCoordsXY{ 0, 5 }, bottomRight + ScreenCoordsXY{ 0, 5 } }, chatBackgroundColor,
INSET_RECT_FLAG_FILL_NONE);
GfxFillRectInset(
dpi, { topLeft + ScreenCoordsXY{ 1, -4 }, bottomRight - ScreenCoordsXY{ 1, inputLineHeight + 6 } },
chatBackgroundColor, INSET_RECT_FLAG_BORDER_INSET);
GfxFillRectInset(
dpi, { bottomLeft + ScreenCoordsXY{ 1, -inputLineHeight - 5 }, bottomRight + ScreenCoordsXY{ -1, 4 } },
chatBackgroundColor,
INSET_RECT_FLAG_BORDER_INSET); // Textbox
}
auto screenCoords = ScreenCoordsXY{ _chatLeft + 5, _chatBottom - inputLineHeight - 20 };
int32_t stringHeight = 0;
// Draw chat history
for (size_t i = 0; i < kChatHistorySize; i++, screenCoords.y -= stringHeight)
{
if (i >= _chatHistory.size())
break;
uint32_t expireTime = ChatHistoryGetTime(i) + 10000;
if (!gChatOpen && Platform::GetTicks() > expireTime)
{
break;
}
lineBuffer = ChatGetHistory(i);
auto lineCh = lineBuffer.c_str();
stringHeight = ChatHistoryDrawString(dpi, lineCh, screenCoords, _chatWidth - 10) + 5;
GfxSetDirtyBlocks(
{ { screenCoords - ScreenCoordsXY{ 0, stringHeight } }, { screenCoords + ScreenCoordsXY{ _chatWidth, 20 } } });
if ((screenCoords.y - stringHeight) < 50)
{
break;
}
}
// Draw current chat input
if (gChatOpen)
{
lineBuffer.assign("{OUTLINE}{CELADON}");
lineBuffer += _chatCurrentLine;
screenCoords.y = _chatBottom - inputLineHeight - 5;
auto lineCh = lineBuffer.c_str();
auto ft = Formatter();
ft.Add<const char*>(lineCh);
inputLineHeight = DrawTextWrapped(
dpi, screenCoords + ScreenCoordsXY{ 0, 3 }, _chatWidth - 10, STR_STRING, ft, { TEXT_COLOUR_255 });
GfxSetDirtyBlocks({ screenCoords, { screenCoords + ScreenCoordsXY{ _chatWidth, inputLineHeight + 15 } } });
// TODO: Show caret if the input text has multiple lines
if (_chatCaretTicks < 15 && GfxGetStringWidth(lineBuffer, FontStyle::Medium) < (_chatWidth - 10))
{
lineBuffer.assign(_chatCurrentLine.c_str(), _chatTextInputSession->SelectionStart);
int32_t caretX = screenCoords.x + GfxGetStringWidth(lineBuffer, FontStyle::Medium);
int32_t caretY = screenCoords.y + 14;
GfxFillRect(dpi, { { caretX, caretY }, { caretX + 6, caretY + 1 } }, PALETTE_INDEX_56);
}
}
}
void ChatAddHistory(std::string_view s)
{
// Format a timestamp
time_t timer{};
time(&timer);
auto tmInfo = localtime(&timer);
char timeBuffer[64]{};
StrCatFTime(timeBuffer, sizeof(timeBuffer), "[%H:%M] ", tmInfo);
std::string buffer = timeBuffer;
buffer += s;
if (_chatHistory.size() >= kChatHistorySize)
{
_chatHistory.pop_back();
_chatHistoryTime.pop_back();
}
_chatHistory.push_front(buffer);
_chatHistoryTime.push_front(Platform::GetTicks());
// Log to file (src only as logging does its own timestamp)
NetworkAppendChatLog(s);
CreateAudioChannel(SoundId::NewsItem, 0, kMixerVolumeMax, 0.5f, 1.5f, true);
}
void ChatInput(enum ChatInput input)
{
switch (input)
{
case ChatInput::Send:
if (!_chatCurrentLine.empty())
{
NetworkSendChat(_chatCurrentLine.c_str());
}
ChatClearInput();
ChatClose();
break;
case ChatInput::Close:
ChatClose();
break;
default:
break;
}
}
static const u8string& ChatGetHistory(size_t index)
{
return _chatHistory[index];
}
static uint32_t ChatHistoryGetTime(size_t index)
{
return _chatHistoryTime[index];
}
static void ChatClearInput()
{
_chatCurrentLine.clear();
}
// This method is the same as gfx_draw_string_left_wrapped.
// But this adjusts the initial Y coordinate depending of the number of lines.
static int32_t ChatHistoryDrawString(DrawPixelInfo& dpi, const char* text, const ScreenCoordsXY& screenCoords, int32_t width)
{
int32_t numLines;
u8string wrappedString;
GfxWrapString(FormatString("{OUTLINE}{WHITE}{STRING}", text), width, FontStyle::Medium, &wrappedString, &numLines);
auto lineHeight = FontGetLineHeight(FontStyle::Medium);
int32_t expectedY = screenCoords.y - (numLines * lineHeight);
if (expectedY < 50)
{
return (numLines * lineHeight); // Skip drawing, return total height.
}
const utf8* bufferPtr = wrappedString.data();
int32_t lineY = screenCoords.y;
for (int32_t line = 0; line <= numLines; ++line)
{
GfxDrawString(dpi, { screenCoords.x, lineY - (numLines * lineHeight) }, bufferPtr, { TEXT_COLOUR_254 });
bufferPtr = GetStringEnd(bufferPtr) + 1;
lineY += lineHeight;
}
return lineY - screenCoords.y;
}
// Wrap string without drawing, useful to get the height of a wrapped string.
// Almost the same as gfx_draw_string_left_wrapped
int32_t ChatStringWrappedGetHeight(u8string_view args, int32_t width)
{
int32_t numLines;
GfxWrapString(FormatStringID(STR_STRING, args), width, FontStyle::Medium, nullptr, &numLines);
const int32_t lineHeight = FontGetLineHeight(FontStyle::Medium);
return lineHeight * (numLines + 1);
}