From dd8eee7eede02b71b86864b533eed4102f0c584e Mon Sep 17 00:00:00 2001 From: Crystalwarrior Date: Sun, 31 Jul 2022 19:33:10 +0300 Subject: [PATCH] Pending ghost messages (#736) This feature allows text to be posted to the IC log as a "ghost" before it has been displayed in-game. When desync chat is enabled, it's common for the player to wait a little while before their messages are displayed. Ghosts only apply to the active player's messages, not all queued messages. They are also pinned at the end of the IC log until they are displayed. Co-authored-by: oldmud0 Co-authored-by: stonedDiscord --- include/aoapplication.h | 4 +- include/courtroom.h | 15 ++- include/datatypes.h | 2 +- src/charselect.cpp | 3 +- src/courtroom.cpp | 188 +++++++++++++++++++++++++----------- src/packet_distribution.cpp | 2 +- 6 files changed, 149 insertions(+), 65 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 52b7673..3b63541 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -107,8 +107,8 @@ public: ///////////////loading info/////////////////// - // player number, it's hardly used but might be needed for some old servers - int s_pv = 0; + // client ID. Not useful, to be removed eventually + int client_id = 0; QString server_software = ""; diff --git a/include/courtroom.h b/include/courtroom.h index d981bff..e8c4e00 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -245,10 +245,11 @@ public: enum LogMode { IO_ONLY, DISPLAY_ONLY, - DISPLAY_AND_IO + DISPLAY_AND_IO, + QUEUED, }; // Log the message contents and information such as evidence presenting etc. into the log file, the IC log, or both. - void log_chatmessage(QString f_message, int f_char_id, QString f_showname = "", QString f_char = "", QString f_objection_mod = "", int f_evi_id = 0, int f_color = 0, LogMode f_log_mode=IO_ONLY); + void log_chatmessage(QString f_message, int f_char_id, QString f_showname = "", QString f_char = "", QString f_objection_mod = "", int f_evi_id = 0, int f_color = 0, LogMode f_log_mode=IO_ONLY, bool sender = false); // Log the message contents and information such as evidence presenting etc. into the IC logs void handle_callwords(); @@ -292,7 +293,12 @@ public: // selected // or the user isn't already scrolled to the top void append_ic_text(QString p_text, QString p_name = "", QString action = "", - int color = 0, bool selfname = false, QDateTime timestamp = QDateTime::currentDateTime()); + int color = 0, bool selfname = false, QDateTime timestamp = QDateTime::currentDateTime(), + bool ghost = false); + + // clear sent messages that appear on the IC log but haven't been delivered + // yet to other players + void pop_ic_ghost(); // prints who played the song to IC chat and plays said song(if found on local // filesystem) takes in a list where the first element is the song name and @@ -485,6 +491,9 @@ private: // amount by which we multiply the delay when we parse punctuation chars const int punctuation_modifier = 3; + // amount of ghost blocks + int ghost_blocks = 0; + // Minumum and maximum number of parameters in the MS packet static const int MS_MINIMUM = 15; static const int MS_MAXIMUM = 30; diff --git a/include/datatypes.h b/include/datatypes.h index 3289bd8..d5d8551 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -110,7 +110,7 @@ enum CHAT_MESSAGE { FRAME_REALIZATION, FRAME_SFX, ADDITIVE, - EFFECTS + EFFECTS, }; enum EMOTE_MOD { diff --git a/src/charselect.cpp b/src/charselect.cpp index bcf230d..fb25cce 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -180,7 +180,8 @@ void Courtroom::char_clicked(int n_char) ao_app->send_server_packet( new AOPacket("PW", {ui_char_password->text()})); ao_app->send_server_packet( - new AOPacket("CC", {QString::number(ao_app->s_pv), QString::number(n_char), get_hdid()})); + new AOPacket("CC", {QString::number(ao_app->client_id), + QString::number(n_char), get_hdid()})); } if (n_char == m_cid || n_char == -1) { update_character(n_char); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index df3d76a..e54c1ae 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2161,10 +2161,16 @@ void Courtroom::chatmessage_enqueue(QStringList p_contents) if (mute_map.value(f_char_id)) return; - // Reset input UI elements if the char ID matches our client's char ID (most likely, this is our message coming back to us) - if (f_char_id == m_cid) { - reset_ui(); - } + // Use null showname if packet does not support 2.6+ extensions + QString showname = QString(); + if (SHOWNAME < p_contents.size()) + showname = p_contents[SHOWNAME]; + + // if the char ID matches our client's char ID (most likely, this is our message coming back to us) + bool sender = f_char_id == m_cid; + + // Record the log I/O, log files should be accurate. + LogMode log_mode = IO_ONLY; // User-created blankpost if (p_contents[MESSAGE].trimmed().isEmpty()) { @@ -2172,6 +2178,20 @@ void Courtroom::chatmessage_enqueue(QStringList p_contents) p_contents[MESSAGE] = ""; } + // If we determine we sent this message + if (sender) { + // Reset input UI elements, clear input box, etc. + reset_ui(); + } + // If we determine we sent this message, or we have desync enabled + if (sender || ao_app->is_desyncrhonized_logs_enabled()) { + // Initialize operation "message queue ghost" + log_chatmessage(p_contents[MESSAGE], p_contents[CHAR_ID].toInt(), + p_contents[SHOWNAME], p_contents[CHAR_NAME], + p_contents[OBJECTION_MOD], p_contents[EVIDENCE_ID].toInt(), + p_contents[TEXT_COLOR].toInt(), QUEUED, sender || ao_app->is_desyncrhonized_logs_enabled()); + } + bool is_objection = false; // If the user wants to clear queue on objection if (ao_app->is_instant_objection_enabled()) @@ -2184,17 +2204,9 @@ void Courtroom::chatmessage_enqueue(QStringList p_contents) skip_chatmessage_queue(); } } + // Log the IO file + log_chatmessage(p_contents[MESSAGE], f_char_id, showname, p_contents[CHAR_NAME], p_contents[OBJECTION_MOD], p_contents[EVIDENCE_ID].toInt(), p_contents[TEXT_COLOR].toInt(), log_mode, sender); - // Record the log I/O, log files should be accurate. - // If desynced logs are on, display the log IC immediately. - LogMode log_mode = ao_app->is_desyncrhonized_logs_enabled() ? DISPLAY_AND_IO : IO_ONLY; - - // Use null showname if packet does not support 2.6+ extensions - QString showname = QString(); - if (SHOWNAME < p_contents.size()) - showname = p_contents[SHOWNAME]; - - log_chatmessage(p_contents[MESSAGE], f_char_id, showname, p_contents[CHAR_NAME], p_contents[OBJECTION_MOD], p_contents[EVIDENCE_ID].toInt(), p_contents[TEXT_COLOR].toInt(), log_mode); // Send this boi into the queue chatmessage_queue.enqueue(p_contents); @@ -2221,14 +2233,11 @@ void Courtroom::chatmessage_dequeue() void Courtroom::skip_chatmessage_queue() { - if (ao_app->is_desyncrhonized_logs_enabled()) { - chatmessage_queue.clear(); - return; - } - while (!chatmessage_queue.isEmpty()) { QStringList p_contents = chatmessage_queue.dequeue(); - log_chatmessage(p_contents[MESSAGE], p_contents[CHAR_ID].toInt(), p_contents[SHOWNAME], p_contents[CHAR_NAME], p_contents[OBJECTION_MOD], p_contents[EVIDENCE_ID].toInt(), p_contents[TEXT_COLOR].toInt(), DISPLAY_ONLY); + // if the char ID matches our client's char ID (most likely, this is our message coming back to us) + bool sender = ao_app->is_desyncrhonized_logs_enabled() || p_contents[CHAR_ID].toInt() == m_cid; + log_chatmessage(p_contents[MESSAGE], p_contents[CHAR_ID].toInt(), p_contents[SHOWNAME], p_contents[CHAR_NAME], p_contents[OBJECTION_MOD], p_contents[EVIDENCE_ID].toInt(), p_contents[TEXT_COLOR].toInt(), DISPLAY_ONLY, sender); } } @@ -2249,10 +2258,11 @@ void Courtroom::unpack_chatmessage(QStringList p_contents) } } - if (!ao_app->is_desyncrhonized_logs_enabled()) { - // We have logs displaying as soon as we reach the message in our queue, which is a less confusing but also less accurate experience for the user. - log_chatmessage(m_chatmessage[MESSAGE], m_chatmessage[CHAR_ID].toInt(), m_chatmessage[SHOWNAME], m_chatmessage[CHAR_NAME], m_chatmessage[OBJECTION_MOD], m_chatmessage[EVIDENCE_ID].toInt(), m_chatmessage[TEXT_COLOR].toInt(), DISPLAY_ONLY); - } + // if the char ID matches our client's char ID (most likely, this is our message coming back to us) + bool sender = ao_app->is_desyncrhonized_logs_enabled() || m_chatmessage[CHAR_ID].toInt() == m_cid; + + // We have logs displaying as soon as we reach the message in our queue, which is a less confusing but also less accurate experience for the user. + log_chatmessage(m_chatmessage[MESSAGE], m_chatmessage[CHAR_ID].toInt(), m_chatmessage[SHOWNAME], m_chatmessage[CHAR_NAME], m_chatmessage[OBJECTION_MOD], m_chatmessage[EVIDENCE_ID].toInt(), m_chatmessage[TEXT_COLOR].toInt(), DISPLAY_ONLY, sender); // Process the callwords for this message handle_callwords(); @@ -2270,7 +2280,7 @@ void Courtroom::unpack_chatmessage(QStringList p_contents) handle_ic_message(); } -void Courtroom::log_chatmessage(QString f_message, int f_char_id, QString f_showname, QString f_char, QString f_objection_mod, int f_evi_id, int f_color, LogMode f_log_mode) +void Courtroom::log_chatmessage(QString f_message, int f_char_id, QString f_showname, QString f_char, QString f_objection_mod, int f_evi_id, int f_color, LogMode f_log_mode, bool sender) { // Display name will use the showname QString f_displayname = f_showname; @@ -2282,6 +2292,13 @@ void Courtroom::log_chatmessage(QString f_message, int f_char_id, QString f_show if (f_displayname.trimmed().isEmpty()) f_displayname = f_showname; + bool ghost = f_log_mode == QUEUED; + // Detect if we're trying to log a blankpost + bool blankpost = (f_log_mode != IO_ONLY && // if we're not in I/O only mode, + f_message.isEmpty() && // our current message is a blankpost, + !ic_chatlog_history.isEmpty() && // the chat log isn't empty, + last_ic_message == f_displayname + ":" && // the chat log's last message is a blank post, and + last_ic_message.mid(0, last_ic_message.lastIndexOf(":")) == f_displayname); // the blankpost's showname is the same as ours bool selfname = f_char_id == m_cid; if (log_ic_actions) { @@ -2299,6 +2316,7 @@ void Courtroom::log_chatmessage(QString f_message, int f_char_id, QString f_show //QString f_custom_theme = ao_app->get_chat(f_char); if (objection_mod <= 4 && objection_mod >= 1) { + blankpost = false; QString shout_message; switch (objection_mod) { case 1: @@ -2335,16 +2353,23 @@ void Courtroom::log_chatmessage(QString f_message, int f_char_id, QString f_show log_ic_text(f_char, f_displayname, shout_message, tr("shouts"), 0, selfname); break; case DISPLAY_AND_IO: - log_ic_text(f_char, f_displayname, shout_message, tr("shouts"), 0, selfname); - [[fallthrough]]; + log_ic_text(f_char, f_displayname, shout_message, tr("shouts")); + append_ic_text(shout_message, f_displayname, tr("shouts"), + 0, selfname, QDateTime::currentDateTime(), false); + break; case DISPLAY_ONLY: - append_ic_text(shout_message, f_displayname, tr("shouts"), 0, selfname); + case QUEUED: + if (!ghost && sender) + pop_ic_ghost(); + append_ic_text(shout_message, f_displayname, tr("shouts"), + 0, selfname, QDateTime::currentDateTime(), ghost); break; } } // If the evidence ID is in the valid range if (f_evi_id > 0 && f_evi_id <= local_evidence_list.size()) { + blankpost = false; // Obtain the evidence name QString f_evi_name = local_evidence_list.at(f_evi_id - 1).name; switch (f_log_mode) { @@ -2352,34 +2377,46 @@ void Courtroom::log_chatmessage(QString f_message, int f_char_id, QString f_show log_ic_text(f_showname, f_displayname, f_evi_name, tr("has presented evidence"), 0, selfname); break; case DISPLAY_AND_IO: - log_ic_text(f_showname, f_displayname, f_evi_name, tr("has presented evidence"), 0, selfname); - [[fallthrough]]; + log_ic_text(f_showname, f_displayname, f_evi_name, tr("has presented evidence")); + append_ic_text(f_evi_name, f_displayname, tr("has presented evidence"), + 0, selfname, QDateTime::currentDateTime(), false); + break; case DISPLAY_ONLY: - append_ic_text(f_evi_name, f_displayname, tr("has presented evidence"), 0, selfname); + case QUEUED: + if (!ghost && sender) + pop_ic_ghost(); + append_ic_text(f_evi_name, f_displayname, tr("has presented evidence"), + 0, selfname, QDateTime::currentDateTime(), ghost); break; } } } - if ((f_log_mode != IO_ONLY) && // if we're not in I/O only mode, - f_message.isEmpty() && // our current message is a blankpost, - !ic_chatlog_history.isEmpty() && // the chat log isn't empty, - last_ic_message == f_displayname + ":" && // the chat log's last message is a blank post, and - last_ic_message.mid(0, last_ic_message.lastIndexOf(":")) == f_displayname) // the blankpost's showname is the same as ours - return; // Skip adding message + // Do not display anything on a repeated blankpost if it's not a ghost + if (blankpost && !ghost) { + // Don't forget to clear the ghost if it's us + if (sender) + pop_ic_ghost(); + return; + } + switch (f_log_mode) { case IO_ONLY: log_ic_text(f_showname, f_displayname, f_message, "", f_color, selfname); break; case DISPLAY_AND_IO: log_ic_text(f_showname, f_displayname, f_message, "", f_color, selfname); - [[fallthrough]]; + append_ic_text(f_message, f_displayname, "", + f_color, selfname, QDateTime::currentDateTime(), false); + break; case DISPLAY_ONLY: - append_ic_text(f_message, f_displayname, "", f_color, selfname); + case QUEUED: + if (!ghost && sender) + pop_ic_ghost(); + append_ic_text(f_message, f_displayname, "", + f_color, selfname, QDateTime::currentDateTime(), ghost); break; } - if (!ui_showname_enable->isChecked()) - regenerate_ic_chatlog(); } bool Courtroom::handle_objection() @@ -3264,9 +3301,9 @@ void Courtroom::log_ic_text(QString p_name, QString p_showname, } void Courtroom::append_ic_text(QString p_text, QString p_name, QString p_action, - int color, bool selfname, QDateTime timestamp) + int color, bool selfname, QDateTime timestamp, bool ghost) { - last_ic_message = p_name + ":" + p_text; + QColor chatlog_color = ao_app->get_color("ic_chatlog_color", "courtroom_fonts.ini"); QTextCharFormat bold; QTextCharFormat normal; QTextCharFormat italics; @@ -3292,14 +3329,35 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, QString p_action, log_goes_downwards ? ui_ic_chatlog->verticalScrollBar()->maximum() : ui_ic_chatlog->verticalScrollBar()->minimum(); + if (ghost) { + ghost_blocks++; + chatlog_color.setAlpha(128); + bold.setForeground(chatlog_color); + normal.setForeground(chatlog_color); + italics.setForeground(chatlog_color); + } + else { + last_ic_message = p_name + ":" + p_text; + } + ui_ic_chatlog->moveCursor(log_goes_downwards ? QTextCursor::End : QTextCursor::Start); + if (!ghost && ghost_blocks > 0) { + for (int i = 0; i < ghost_blocks; ++i) { + ui_ic_chatlog->moveCursor(log_goes_downwards ? QTextCursor::PreviousBlock + : QTextCursor::NextBlock); + } + ui_ic_chatlog->moveCursor(log_goes_downwards ? QTextCursor::EndOfBlock + : QTextCursor::StartOfBlock); + } + // Only prepend with newline if log goes downwards if (log_goes_downwards && need_newline) { ui_ic_chatlog->textCursor().insertBlock(format); } + // Timestamp if we're doing that meme if (log_timestamp) { // Format the timestamp @@ -3322,14 +3380,13 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, QString p_action, // Make shout text bold else if (p_action == tr("shouts") && log_ic_actions) { ui_ic_chatlog->textCursor().insertText(" " + p_action + " ", normal); - if (log_colors) { + if (log_colors && !ghost) { ui_ic_chatlog->textCursor().insertHtml( "" + filter_ic_text(p_text, true, -1, 0) .replace( "$c0", - ao_app->get_color("ic_chatlog_color", "courtroom_fonts.ini") - .name(QColor::HexRgb)) + + chatlog_color.name(QColor::HexArgb)) + ""); } else @@ -3360,9 +3417,13 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, QString p_action, // Format the result according to html if (log_colors) { QString p_text_filtered = filter_ic_text(p_text, true, -1, color); - p_text_filtered = p_text_filtered.replace("$c0", ao_app->get_color("ic_chatlog_color", "courtroom_fonts.ini").name(QColor::HexRgb)); + p_text_filtered = p_text_filtered.replace("$c0", chatlog_color.name(QColor::HexArgb)); for (int c = 1; c < max_colors; ++c) { - p_text_filtered = p_text_filtered.replace("$c" + QString::number(c), default_color_rgb_list.at(c).name(QColor::HexRgb)); + QColor color_result = default_color_rgb_list.at(c); + if (ghost) { + color_result.setAlpha(128); + } + p_text_filtered = p_text_filtered.replace("$c" + QString::number(c), color_result.name(QColor::HexArgb)); } ui_ic_chatlog->textCursor().insertHtml(p_text_filtered); } @@ -3378,14 +3439,15 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, QString p_action, // If we got too many blocks in the current log, delete some. while (ui_ic_chatlog->document()->blockCount() > log_maximum_blocks && log_maximum_blocks > 0) { - ui_ic_chatlog->moveCursor(log_goes_downwards ? QTextCursor::Start + QTextCursor temp_curs = ui_ic_chatlog->textCursor(); + temp_curs.movePosition(log_goes_downwards ? QTextCursor::Start : QTextCursor::End); - ui_ic_chatlog->textCursor().select(QTextCursor::BlockUnderCursor); - ui_ic_chatlog->textCursor().removeSelectedText(); + temp_curs.select(QTextCursor::BlockUnderCursor); + temp_curs.removeSelectedText(); if (log_goes_downwards) - ui_ic_chatlog->textCursor().deleteChar(); + temp_curs.deleteChar(); else - ui_ic_chatlog->textCursor().deletePreviousChar(); + temp_curs.deletePreviousChar(); } // Finally, scroll the scrollbar to the correct position. @@ -3397,15 +3459,27 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, QString p_action, ui_ic_chatlog->verticalScrollBar()->setValue(old_scrollbar_value); } else { - // The user hasn't selected any text and the scrollbar is at the bottom: - // scroll to the bottom. - ui_ic_chatlog->moveCursor(log_goes_downwards ? QTextCursor::End - : QTextCursor::Start); ui_ic_chatlog->verticalScrollBar()->setValue( log_goes_downwards ? ui_ic_chatlog->verticalScrollBar()->maximum() : 0); } } +void Courtroom::pop_ic_ghost() +{ + QTextCursor ghost = ui_ic_chatlog->textCursor(); + ghost.movePosition(log_goes_downwards ? QTextCursor::End + : QTextCursor::Start); + for (int i = 1; i < ghost_blocks; ++i) { + ghost.movePosition(log_goes_downwards ? QTextCursor::PreviousBlock + : QTextCursor::NextBlock); + } + ghost.select(QTextCursor::BlockUnderCursor); + ghost.removeSelectedText(); + if (ghost_blocks <= 1 && !log_goes_downwards) + ghost.deleteChar(); + ghost_blocks--; +} + void Courtroom::play_preanim(bool immediate) { QString f_char = m_chatmessage[CHAR_NAME]; diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 3555e21..4784faf 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -68,7 +68,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() < 2) goto end; - s_pv = f_contents.at(0).toInt(); + client_id = f_contents.at(0).toInt(); server_software = f_contents.at(1); if (lobby_constructed)