/***************************************************************************** * Copyright (c) 2014-2020 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/Formatting.h" #include "../localisation/Localisation.h" #include "../network/network.h" #include "../platform/platform.h" #include "../util/Util.h" #include "../world/Location.hpp" #include using namespace OpenRCT2; bool gChatOpen = false; static char _chatCurrentLine[CHAT_MAX_MESSAGE_LENGTH]; static char _chatHistory[CHAT_HISTORY_SIZE][CHAT_INPUT_SIZE]; static uint32_t _chatHistoryTime[CHAT_HISTORY_SIZE]; static uint32_t _chatHistoryIndex = 0; 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 char* chat_history_get(uint32_t index); static uint32_t chat_history_get_time(uint32_t index); static void chat_clear_input(); static int32_t chat_history_draw_string( rct_drawpixelinfo* dpi, const char* text, const ScreenCoordsXY& screenCoords, int32_t width); bool chat_available() { return network_get_mode() != NETWORK_MODE_NONE && network_get_status() == NETWORK_STATUS_CONNECTED && network_get_authstatus() == NetworkAuth::Ok; } void chat_open() { gChatOpen = true; _chatTextInputSession = context_start_text_input(_chatCurrentLine, sizeof(_chatCurrentLine)); } void chat_close() { gChatOpen = false; context_stop_text_input(); } void chat_toggle() { if (gChatOpen) { chat_close(); } else { chat_open(); } } void chat_init() { std::memset(_chatHistory, 0x00, sizeof(_chatHistory)); std::memset(_chatHistoryTime, 0x00, sizeof(_chatHistoryTime)); } void chat_update() { // Flash the caret _chatCaretTicks = (_chatCaretTicks + 1) % 30; } void chat_draw(rct_drawpixelinfo* dpi, uint8_t chatBackgroundColor) { thread_local std::string lineBuffer; if (!chat_available()) { gChatOpen = false; return; } _chatLeft = 10; _chatRight = std::min((context_get_width() - 10), CHAT_MAX_WINDOW_WIDTH); _chatWidth = _chatRight - _chatLeft; _chatBottom = context_get_height() - 45; _chatTop = _chatBottom - 10; char* inputLine = _chatCurrentLine; int32_t inputLineHeight = 10; // Draw chat window if (gChatOpen) { inputLineHeight = chat_string_wrapped_get_height(static_cast(&inputLine), _chatWidth - 10); _chatTop -= inputLineHeight; for (int32_t i = 0; i < CHAT_HISTORY_SIZE; i++) { if (strlen(chat_history_get(i)) == 0) { continue; } lineBuffer.assign(chat_history_get(i)); auto lineCh = lineBuffer.c_str(); int32_t lineHeight = chat_string_wrapped_get_height(static_cast(&lineCh), _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 }; gfx_set_dirty_blocks( { topLeft - ScreenCoordsXY{ 0, 5 }, bottomRight + ScreenCoordsXY{ 0, 5 } }); // Background area + Textbox gfx_filter_rect( dpi, { topLeft - ScreenCoordsXY{ 0, 5 }, bottomRight + ScreenCoordsXY{ 0, 5 } }, FilterPaletteID::Palette51); // Opaque gray background gfx_fill_rect_inset( dpi, { topLeft - ScreenCoordsXY{ 0, 5 }, bottomRight + ScreenCoordsXY{ 0, 5 } }, chatBackgroundColor, INSET_RECT_FLAG_FILL_NONE); gfx_fill_rect_inset( dpi, { topLeft + ScreenCoordsXY{ 1, -4 }, bottomRight - ScreenCoordsXY{ 1, inputLineHeight + 6 } }, chatBackgroundColor, INSET_RECT_FLAG_BORDER_INSET); gfx_fill_rect_inset( 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 (int32_t i = 0; i < CHAT_HISTORY_SIZE; i++, screenCoords.y -= stringHeight) { uint32_t expireTime = chat_history_get_time(i) + 10000; if (!gChatOpen && platform_get_ticks() > expireTime) { break; } lineBuffer.assign(chat_history_get(i)); auto lineCh = lineBuffer.c_str(); stringHeight = chat_history_draw_string(dpi, lineCh, screenCoords, _chatWidth - 10) + 5; gfx_set_dirty_blocks( { { 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(); inputLineHeight = DrawTextWrapped( dpi, screenCoords + ScreenCoordsXY{ 0, 3 }, _chatWidth - 10, STR_STRING, static_cast(&lineCh), { TEXT_COLOUR_255 }); gfx_set_dirty_blocks({ screenCoords, { screenCoords + ScreenCoordsXY{ _chatWidth, inputLineHeight + 15 } } }); // TODO: Show caret if the input text has multiple lines if (_chatCaretTicks < 15 && gfx_get_string_width(lineBuffer, FontSpriteBase::MEDIUM) < (_chatWidth - 10)) { lineBuffer.assign(_chatCurrentLine, _chatTextInputSession->SelectionStart); int32_t caretX = screenCoords.x + gfx_get_string_width(lineBuffer, FontSpriteBase::MEDIUM); int32_t caretY = screenCoords.y + 14; gfx_fill_rect(dpi, { { caretX, caretY }, { caretX + 6, caretY + 1 } }, PALETTE_INDEX_56); } } } void chat_history_add(const char* src) { // 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 += src; // Add to history list int32_t index = _chatHistoryIndex % CHAT_HISTORY_SIZE; std::fill_n(_chatHistory[index], CHAT_INPUT_SIZE, 0x00); std::memcpy(_chatHistory[index], buffer.c_str(), std::min(buffer.size(), CHAT_INPUT_SIZE - 1)); _chatHistoryTime[index] = platform_get_ticks(); _chatHistoryIndex++; // Log to file (src only as logging does its own timestamp) network_append_chat_log(src); Mixer_Play_Effect(OpenRCT2::Audio::SoundId::NewsItem, 0, MIXER_VOLUME_MAX, 0.5f, 1.5f, true); } void chat_input(ChatInput input) { switch (input) { case ChatInput::Send: if (strlen(_chatCurrentLine) > 0) { network_send_chat(_chatCurrentLine); } chat_clear_input(); chat_close(); break; case ChatInput::Close: chat_close(); break; default: break; } } static const char* chat_history_get(uint32_t index) { return _chatHistory[(_chatHistoryIndex + CHAT_HISTORY_SIZE - index - 1) % CHAT_HISTORY_SIZE]; } static uint32_t chat_history_get_time(uint32_t index) { return _chatHistoryTime[(_chatHistoryIndex + CHAT_HISTORY_SIZE - index - 1) % CHAT_HISTORY_SIZE]; } static void chat_clear_input() { _chatCurrentLine[0] = 0; } // 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 chat_history_draw_string( rct_drawpixelinfo* dpi, const char* text, const ScreenCoordsXY& screenCoords, int32_t width) { auto buffer = gCommonStringFormatBuffer; FormatStringToBuffer(gCommonStringFormatBuffer, sizeof(gCommonStringFormatBuffer), "{OUTLINE}{WHITE}{STRING}", text); int32_t numLines; gfx_wrap_string(buffer, width, FontSpriteBase::MEDIUM, &numLines); auto lineHeight = font_get_line_height(FontSpriteBase::MEDIUM); int32_t expectedY = screenCoords.y - (numLines * lineHeight); if (expectedY < 50) { return (numLines * lineHeight); // Skip drawing, return total height. } auto lineY = screenCoords.y; for (int32_t line = 0; line <= numLines; ++line) { gfx_draw_string(dpi, { screenCoords.x, lineY - (numLines * lineHeight) }, buffer, { TEXT_COLOUR_254 }); buffer = get_string_end(buffer) + 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 chat_string_wrapped_get_height(void* args, int32_t width) { char* buffer = gCommonStringFormatBuffer; format_string(buffer, 256, STR_STRING, args); int32_t numLines; gfx_wrap_string(buffer, width, FontSpriteBase::MEDIUM, &numLines); int32_t lineHeight = font_get_line_height(FontSpriteBase::MEDIUM); int32_t lineY = 0; for (int32_t line = 0; line <= numLines; ++line) { buffer = get_string_end(buffer) + 1; lineY += lineHeight; } return lineY; }