Skip to content

Conversation

@Halvance
Copy link

@Halvance Halvance commented Dec 17, 2025

Communications System:

  • New CommsManager to handle all in-game communications
  • CommsWidget displaying communications in categorized tabs (Direct, Local, Global)
  • Real-time parsing from GMCP Comm.Channel messages
  • Fallback text-based parsing when GMCP unavailable
  • Tab-based organization (Tell/Whisper, Say/Emote, Narrate/Yell/etc)

Features:

  • Color-coded communication types (customizable)
  • Talker identification (You, Player, NPC, Ally, Neutral, Enemy)
  • Font styling options (CAPS for yells, italic for whispers/emotes)
  • Optional timestamps
  • Communication log saving
  • Tab muting/filtering
  • Dockable communications panel

Configuration:

  • Comprehensive color settings for each comm type
  • Talker-based coloring system
  • Font styling preferences
  • Log directory configuration
  • Per-tab muting controls

GMCP Integration:

  • Requires PR-B (GMCP Broadcast) for sig_rawGameText signal
  • Parses Comm.Channel GMCP messages
  • Falls back to text parsing for compatibility

UI:

  • Added Comms preferences page
  • Integrated with main window dock system
  • Tabbed interface for easy navigation

This provides a centralized, organized view of all game communications with extensive customization options.

Summary by Sourcery

Introduce a configurable communications panel backed by a GMCP‑aware manager, extend graphics and canvas controls, and centralize hotkey handling with configurable shortcuts and visibility filters.

New Features:

  • Add a CommsManager and dockable CommsWidget to collect, categorize, and display in‑game communications from GMCP Comm.Channel messages and raw text.
  • Provide a communications preferences page for per‑channel colors, talker‑based coloring, font styling, timestamps, and log behavior.
  • Introduce a visibility filter toolbar/panel to toggle per‑marker and connection visibility on the map canvas.
  • Add support for texture set selection, optional seasonal textures, and configurable background imagery on the map canvas.
  • Enable global, user‑configurable hotkeys for common actions, views, panels, mouse modes, and room operations, including dual shortcuts for preferences.

Enhancements:

  • Extend canvas configuration with layer transparency, radial transparency toggle, and richer camera angle ranges, and wire these into the main window and menus.
  • Integrate a GMCP‑driven MUME clock broadcast with configurable interval and season‑based texture updates.
  • Persist per‑marker visibility settings and expose them via a dedicated visibility filter widget.
  • Adjust default UI colors (e.g., background) and connect new preferences signals for texture, comms, and hotkey updates at runtime.

Build:

  • Register new communications, visibility filter, and preferences page sources in the CMake build configuration.

Communications System:
- New CommsManager to handle all in-game communications
- CommsWidget displaying communications in categorized tabs (Direct, Local, Global)
- Real-time parsing from GMCP Comm.Channel messages
- Fallback text-based parsing when GMCP unavailable
- Tab-based organization (Tell/Whisper, Say/Emote, Narrate/Yell/etc)

Features:
- Color-coded communication types (customizable)
- Talker identification (You, Player, NPC, Ally, Neutral, Enemy)
- Font styling options (CAPS for yells, italic for whispers/emotes)
- Optional timestamps
- Communication log saving
- Tab muting/filtering
- Dockable communications panel

Configuration:
- Comprehensive color settings for each comm type
- Talker-based coloring system
- Font styling preferences
- Log directory configuration
- Per-tab muting controls

GMCP Integration:
- Requires PR-B (GMCP Broadcast) for sig_rawGameText signal
- Parses Comm.Channel GMCP messages
- Falls back to text parsing for compatibility

UI:
- Added Comms preferences page
- Integrated with main window dock system
- Tabbed interface for easy navigation

This provides a centralized, organized view of all game
communications with extensive customization options.
@sourcery-ai
Copy link

sourcery-ai bot commented Dec 17, 2025

Reviewer's Guide

Introduces a new communications subsystem (CommsManager + CommsWidget) wired to GMCP and raw text, adds configurable comms colors/styles and hotkeys, a dockable Communications panel, and a map visibility filter with background/seasonal texture settings, while refactoring configuration and main window wiring to support these features and GMCP clock broadcasting.

Sequence diagram for GMCP and raw text communications flow

sequenceDiagram
    actor Player
    participant MumeServer
    participant GameObserver
    participant CommsManager
    participant CommsWidget
    participant AutoLogger
    participant MainWindow

    Player->>MumeServer: In‑game communication
    MumeServer-->>GameObserver: GMCP Comm.Channel message
    GameObserver-->>CommsManager: sig2_sentToUserGmcp(gmcp)
    CommsManager->>CommsManager: slot_parseGmcpInput(gmcp)
    CommsManager->>CommsManager: parseCommChannelText(gmcp)
    CommsManager->>CommsManager: getCommTypeFromChannel(channel)
    CommsManager->>CommsManager: getCategoryFromType(type)
    CommsManager->>CommsManager: trackYellMessage(sender,message) [if YELL]
    CommsManager-->>CommsWidget: sig_newMessage(CommMessage)
    CommsManager-->>MainWindow: sig_log(module,message)

    MumeServer-->>GameObserver: raw game text line
    GameObserver-->>CommsManager: sig2_rawGameText(text)
    CommsManager->>CommsManager: slot_parseRawGameText(text)
    CommsManager->>CommsManager: parseFallbackYell(text)
    CommsManager->>CommsManager: isRecentYellDuplicate(sender,message)?
    alt not duplicate and matches yell pattern
        CommsManager->>CommsManager: trackYellMessage(sender,message)
        CommsManager-->>CommsWidget: sig_newMessage(CommMessage{YELL})
    end

    CommsWidget->>CommsWidget: slot_onNewMessage(msg)
    CommsWidget->>CommsWidget: isMessageFiltered(msg)?
    alt not filtered
        CommsWidget->>CommsWidget: appendFormattedMessage(msg)
        CommsWidget->>CommsWidget: stripAnsiCodes()
        CommsWidget->>CommsWidget: cleanSenderName()
        CommsWidget->>CommsWidget: getColorForTalker(talkerType)
        CommsWidget->>CommsWidget: getColorForType(type)
        CommsWidget->>CommsWidget: formatAndInsertMessage()
    end

    MainWindow->>CommsWidget: closeEvent()
    CommsWidget->>CommsWidget: slot_saveLogOnExit()
    CommsWidget->>AutoLogger: getCurrentFileNumber()
    CommsWidget->>CommsWidget: build Comms_Log filename
    CommsWidget->>CommsWidget: write QTextDocument to file
Loading

Class diagram for the new communications and configuration types

classDiagram
    class CommsManager {
        +CommsManager(QObject *parent)
        +~CommsManager()
        +void slot_parseGmcpInput(const GmcpMessage &msg)
        +void slot_parseRawGameText(const QString &rawText)
        -void parseCommChannelText(const GmcpMessage &msg)
        -void parseFallbackYell(const QString &rawText)
        -CommType getCommTypeFromChannel(const QString &channel)
        -CommCategory getCategoryFromType(CommType type)
        -void trackYellMessage(const QString &sender, const QString &message)
        -bool isRecentYellDuplicate(const QString &sender, const QString &message) const
        -QHash~QString,qint64~ m_recentYells
    }

    class CommsWidget {
        +CommsWidget(CommsManager &commsManager, AutoLogger *autoLogger, QWidget *parent)
        +~CommsWidget()
        +void slot_onNewMessage(const CommMessage &msg)
        +void slot_loadSettings()
        +void slot_saveLog()
        +void slot_saveLogOnExit()
        -void setupUI()
        -void connectSignals()
        -void resizeEvent(QResizeEvent *event)
        -void slot_onFilterToggled(CommType type, bool enabled)
        -void slot_onCharMobToggle()
        -void appendFormattedMessage(const CommMessage &msg)
        -void formatAndInsertMessage(QTextCursor &cursor, const CommMessage &msg, const QString &sender, const QString &message)
        -QString stripAnsiCodes(const QString &text)
        -QString cleanSenderName(const QString &sender)
        -QColor getColorForType(CommType type)
        -QColor getColorForTalker(TalkerType talkerType)
        -bool isMessageFiltered(const CommMessage &msg) const
        -void updateFilterButtonAppearance(QPushButton *button, bool enabled)
        -void updateCharMobButtonAppearance()
        -void rebuildDisplay()
        -void updateButtonLabels()
        -CommsManager &m_commsManager
        -AutoLogger *m_autoLogger
        -QTextEdit *m_textDisplay
        -QToolButton *m_charMobToggle
        -QMap~CommType,QPushButton*~ m_filterButtons
        -QMap~CommType,bool~ m_filterStates
        -CharMobFilterEnum m_charMobFilter
        -QList~CachedMessage~ m_messageCache
        -static const int MAX_MESSAGES
    }

    class CommMessage {
        +CommType type
        +CommCategory category
        +QString sender
        +QString message
        +QString timestamp
        +TalkerType talkerType
    }

    class CommsPage {
        +CommsPage(QWidget *parent)
        +~CommsPage()
        +void slot_loadConfig()
        -void setupUI()
        -void connectSignals()
        -void updateColorButton(QPushButton *button, const QColor &color)
        -void slot_onColorClicked()
        -void slot_onBgColorClicked()
        -void slot_onYellAllCapsChanged(Qt::CheckState state)
        -void slot_onWhisperItalicChanged(Qt::CheckState state)
        -void slot_onEmoteItalicChanged(Qt::CheckState state)
        -void slot_onShowTimestampsChanged(Qt::CheckState state)
        -QPushButton *m_tellColorButton
        -QPushButton *m_whisperColorButton
        -QPushButton *m_groupColorButton
        -QPushButton *m_askColorButton
        -QPushButton *m_sayColorButton
        -QPushButton *m_emoteColorButton
        -QPushButton *m_socialColorButton
        -QPushButton *m_yellColorButton
        -QPushButton *m_narrateColorButton
        -QPushButton *m_prayColorButton
        -QPushButton *m_shoutColorButton
        -QPushButton *m_singColorButton
        -QPushButton *m_bgColorButton
        -QPushButton *m_talkerYouColorButton
        -QPushButton *m_talkerPlayerColorButton
        -QPushButton *m_talkerNpcColorButton
        -QPushButton *m_talkerAllyColorButton
        -QPushButton *m_talkerNeutralColorButton
        -QPushButton *m_talkerEnemyColorButton
        -QCheckBox *m_yellAllCapsCheck
        -QCheckBox *m_whisperItalicCheck
        -QCheckBox *m_emoteItalicCheck
        -QCheckBox *m_showTimestampsCheck
    }

    class Configuration {
        +ParserSettings parser
        +CanvasSettings canvas
        +Hotkeys hotkeys
        +CommsSettings comms
        +MumeClockSettings mumeClock
    }

    class ParserSettings {
        +bool enableYellFallbackParsing
    }

    class CanvasSettings {
        +TextureSetEnum textureSet
        +bool enableSeasonalTextures
        +float layerTransparency
        +bool enableRadialTransparency
        +Advanced advanced
        +VisibilityFilter visibilityFilter
    }

    class CanvasSettings_Advanced {
        +bool useBackgroundImage
        +QString backgroundImagePath
        +int backgroundFitMode
        +float backgroundOpacity
        +float backgroundFocusedScale
        +float backgroundFocusedOffsetX
        +float backgroundFocusedOffsetY
        +FixedPoint~1~ fov
        +FixedPoint~1~ verticalAngle
        +FixedPoint~1~ horizontalAngle
        +FixedPoint~1~ layerHeight
    }

    class VisibilityFilter {
        +NamedConfig~bool~ generic
        +NamedConfig~bool~ herb
        +NamedConfig~bool~ river
        +NamedConfig~bool~ place
        +NamedConfig~bool~ mob
        +NamedConfig~bool~ comment
        +NamedConfig~bool~ road
        +NamedConfig~bool~ object
        +NamedConfig~bool~ action
        +NamedConfig~bool~ locality
        +NamedConfig~bool~ connections
        +bool isVisible(InfomarkClassEnum markerClass) const
        +void setVisible(InfomarkClassEnum markerClass, bool visible)
        +bool isConnectionsVisible() const
        +void setConnectionsVisible(bool visible)
        +void showAll()
        +void hideAll()
        +void registerChangeCallback(const ChangeMonitor::Lifetime &lifetime, const ChangeMonitor::Function &callback)
    }

    class Hotkeys {
        +NamedConfig~QString~ fileOpen
        +NamedConfig~QString~ fileSave
        +NamedConfig~QString~ fileReload
        +NamedConfig~QString~ fileQuit
        +NamedConfig~QString~ editUndo
        +NamedConfig~QString~ editRedo
        +NamedConfig~QString~ editPreferences
        +NamedConfig~QString~ editPreferencesAlt
        +NamedConfig~QString~ editFindRooms
        +NamedConfig~QString~ editRoom
        +NamedConfig~QString~ viewZoomIn
        +NamedConfig~QString~ viewZoomOut
        +NamedConfig~QString~ viewZoomReset
        +NamedConfig~QString~ viewLayerUp
        +NamedConfig~QString~ viewLayerDown
        +NamedConfig~QString~ viewLayerReset
        +NamedConfig~QString~ viewRadialTransparency
        +NamedConfig~QString~ viewStatusBar
        +NamedConfig~QString~ viewScrollBars
        +NamedConfig~QString~ viewMenuBar
        +NamedConfig~QString~ viewAlwaysOnTop
        +NamedConfig~QString~ panelLog
        +NamedConfig~QString~ panelClient
        +NamedConfig~QString~ panelGroup
        +NamedConfig~QString~ panelRoom
        +NamedConfig~QString~ panelAdventure
        +NamedConfig~QString~ panelDescription
        +NamedConfig~QString~ panelComms
        +NamedConfig~QString~ modeMoveMap
        +NamedConfig~QString~ modeRaypick
        +NamedConfig~QString~ modeSelectRooms
        +NamedConfig~QString~ modeSelectMarkers
        +NamedConfig~QString~ modeSelectConnection
        +NamedConfig~QString~ modeCreateMarker
        +NamedConfig~QString~ modeCreateRoom
        +NamedConfig~QString~ modeCreateConnection
        +NamedConfig~QString~ modeCreateOnewayConnection
        +NamedConfig~QString~ roomCreate
        +NamedConfig~QString~ roomMoveUp
        +NamedConfig~QString~ roomMoveDown
        +NamedConfig~QString~ roomMergeUp
        +NamedConfig~QString~ roomMergeDown
        +NamedConfig~QString~ roomDelete
        +NamedConfig~QString~ roomConnectNeighbors
        +NamedConfig~QString~ roomMoveToSelected
        +NamedConfig~QString~ roomUpdateSelected
        +void read(const QSettings &conf)
        +void write(QSettings &conf) const
    }

    class CommsSettings {
        +NamedConfig~QColor~ tellColor
        +NamedConfig~QColor~ whisperColor
        +NamedConfig~QColor~ groupColor
        +NamedConfig~QColor~ askColor
        +NamedConfig~QColor~ sayColor
        +NamedConfig~QColor~ emoteColor
        +NamedConfig~QColor~ socialColor
        +NamedConfig~QColor~ yellColor
        +NamedConfig~QColor~ narrateColor
        +NamedConfig~QColor~ prayColor
        +NamedConfig~QColor~ shoutColor
        +NamedConfig~QColor~ singColor
        +NamedConfig~QColor~ backgroundColor
        +NamedConfig~QColor~ talkerYouColor
        +NamedConfig~QColor~ talkerPlayerColor
        +NamedConfig~QColor~ talkerNpcColor
        +NamedConfig~QColor~ talkerAllyColor
        +NamedConfig~QColor~ talkerNeutralColor
        +NamedConfig~QColor~ talkerEnemyColor
        +NamedConfig~bool~ yellAllCaps
        +NamedConfig~bool~ whisperItalic
        +NamedConfig~bool~ emoteItalic
        +NamedConfig~bool~ showTimestamps
        +NamedConfig~bool~ saveLogOnExit
        +NamedConfig~QString~ logDirectory
        +NamedConfig~bool~ muteDirectTab
        +NamedConfig~bool~ muteLocalTab
        +NamedConfig~bool~ muteGlobalTab
        +void read(const QSettings &conf)
        +void write(QSettings &conf) const
    }

    class MumeClockSettings {
        +int64_t startEpoch
        +bool display
        +NamedConfig~bool~ gmcpBroadcast
        +NamedConfig~int~ gmcpBroadcastInterval
        +MumeClockSettings()
        +void read(const QSettings &conf)
        +void write(QSettings &conf) const
    }

    class MainWindow {
        +CommsManager *m_commsManager
        +CommsWidget *m_commsWidget
        +QDockWidget *m_dockDialogComms
        +QAction *saveCommsLogAct
        +void applyHotkeys()
        +void registerGlobalShortcuts()
        +void slot_setRadialTransparency()
    }

    class ConfigDialog {
        +void createIcons()
        +signals sig_commsSettingsChanged()
        +signals sig_hotkeysChanged()
        +signals sig_textureSettingsChanged()
    }

    class HotkeysPage

    class GraphicsPage

    class VisibilityFilterWidget

    %% Relationships
    CommsManager --> CommMessage
    CommsWidget --> CommMessage
    CommsWidget --> CommsManager
    CommsWidget --> AutoLogger

    CommsPage --> CommsSettings
    Configuration --> ParserSettings
    Configuration --> CanvasSettings
    Configuration --> Hotkeys
    Configuration --> CommsSettings
    Configuration --> MumeClockSettings

    CanvasSettings --> CanvasSettings_Advanced
    CanvasSettings --> VisibilityFilter

    MainWindow --> CommsManager
    MainWindow --> CommsWidget
    MainWindow --> VisibilityFilterWidget

    ConfigDialog --> CommsPage
    ConfigDialog --> HotkeysPage
    ConfigDialog --> GraphicsPage

    GraphicsPage --> CanvasSettings
    HotkeysPage --> Hotkeys
Loading

File-Level Changes

Change Details Files
Add CommsManager and CommsWidget to centralize parsing and display of in-game communications.
  • Implement CommsManager to parse GMCP Comm.Channel JSON messages into typed CommMessage objects with talker classification and maintain a short-term yell de-duplication cache.
  • Add fallback text-based parsing for yell lines when GMCP is unavailable or incomplete, guarded by a configuration flag.
  • Create CommsWidget UI with filter buttons (by comm type and by character/NPC), ANSI-stripping, per-type and per-talker coloring, optional timestamps, and a scrollable log view with save and auto-save-on-exit support.
src/comms/CommsManager.h
src/comms/CommsManager.cpp
src/comms/CommsWidget.h
src/comms/CommsWidget.cpp
src/mainwindow/mainwindow.cpp
src/mainwindow/mainwindow.h
src/CMakeLists.txt
Extend configuration system with communications, hotkeys, canvas visibility/background, texture set, and GMCP clock broadcast settings.
  • Introduce CommsSettings and Hotkeys subgroups with NamedConfig entries for colors, talker colors, font styles, timestamps/logging, per-tab muting, and a full set of configurable shortcuts, including panel toggles for the new Communications panel.
  • Augment CanvasSettings with texture set selection, seasonal textures toggle, layer transparency, radial transparency flag, background image options, and a VisibilityFilter struct that can be persisted and observed via callbacks.
  • Add GMCP clock broadcast settings and a parser flag for yell fallback parsing, plus helper functions to map TextureSetEnum to/from persisted int values, and adjust defaults such as the map background color and horizontalAngle range.
  • Wire read/write logic for the new settings into QSettings, including keys for marker visibility, background image parameters, hotkeys, comms colors/styles, and clock broadcast values.
src/configuration/configuration.h
src/configuration/configuration.cpp
Integrate the new Communications and visibility features into the main window and menus, and rework shortcut handling to be configuration-driven and global.
  • Instantiate GameObserver and MumeClock earlier, create CommsManager and CommsWidget, connect GMCP and raw text signals to CommsManager, hook its logging into MainWindow, and expose CommsWidget as a dockable Communications panel with its own save-log action under the Integrated Mud Client menu.
  • Add a VisibilityFilterWidget dock, connect its signals to MapCanvas to selectively rebuild infomark meshes or repaint for connection visibility, and register its toggle action under View→Toolbars and Side Panels.
  • Introduce a Radial Transparency QAction wired to CanvasSettings.enableRadialTransparency, connect MumeClock season-change signals to MapCanvas, and initialize the current season at startup for seasonal textures.
  • Remove hard-coded QAction shortcuts, implement applyHotkeys to pull shortcuts from Configuration::hotkeys (including dual shortcuts for Preferences), and registerGlobalShortcuts to attach all actions and dock toggle actions to the main window so they work globally; call these during construction.
  • On close, trigger CommsWidget log auto-save if enabled in settings.
src/mainwindow/mainwindow.cpp
src/mainwindow/mainwindow.h
src/CMakeLists.txt
Add a Comms preferences page and extend the graphics/preferences UI to expose new settings and signals.
  • Create CommsPage with controls for per-type and per-talker colors, background color, yell/whisper/emote font styles, and timestamp visibility, wiring changes back into Configuration::comms and emitting a sig_commsSettingsChanged signal.
  • Update ConfigDialog to include Hotkeys and Comms pages in the navigation, emit new signals for texture, hotkeys, and comms settings changes, and connect these to MapCanvas::slot_reloadTextures, MainWindow::applyHotkeys, and CommsWidget::slot_loadSettings respectively.
  • Register new sources in the build system so the comms and preferences code is compiled and linked.
src/preferences/commspage.h
src/preferences/commspage.cpp
src/preferences/configdialog.cpp
src/preferences/configdialog.h
src/CMakeLists.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and found some issues that need to be addressed.

  • In CommsManager::isRecentYellDuplicate(), you call m_recentYells[key] in a const method, which will not compile because QHash::operator[] is non-const and also inserts missing keys; use m_recentYells.value(key) (or find/const_iterator) instead to read without modifying the hash.
  • You now have two different ANSI-stripping implementations (CommsWidget::stripAnsiCodes and the inline regex in CommsManager::parseFallbackYell); consider centralizing this into a shared helper to avoid divergence and keep the parsing consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In CommsManager::isRecentYellDuplicate(), you call m_recentYells[key] in a const method, which will not compile because QHash::operator[] is non-const and also inserts missing keys; use m_recentYells.value(key) (or find/const_iterator) instead to read without modifying the hash.
- You now have two different ANSI-stripping implementations (CommsWidget::stripAnsiCodes and the inline regex in CommsManager::parseFallbackYell); consider centralizing this into a shared helper to avoid divergence and keep the parsing consistent.

## Individual Comments

### Comment 1
<location> `src/comms/CommsManager.cpp:258-267` </location>
<code_context>
+    }
+}
+
+bool CommsManager::isRecentYellDuplicate(const QString &sender, const QString &message) const
+{
+    const QString key = QString("%1|%2").arg(sender, message);
+
+    if (!m_recentYells.contains(key)) {
+        return false;
+    }
+
+    // Check if it's recent (within last 2 seconds)
+    const qint64 timestamp = m_recentYells[key];
+    const qint64 now = QDateTime::currentMSecsSinceEpoch();
+    const qint64 age = now - timestamp;
</code_context>

<issue_to_address>
**issue (bug_risk):** Accessing m_recentYells via operator[] in a const method will not compile and also mutates the hash

In `isRecentYellDuplicate`, `m_recentYells[key]` calls the non-const `QHash::operator[]`, which inserts a default value for missing keys. This violates const-correctness and mutates the hash. Use a const-safe lookup instead, e.g. `m_recentYells.value(key, 0)` or `auto it = m_recentYells.constFind(key);` and check `it != m_recentYells.cend()` before reading the timestamp.
</issue_to_address>

### Comment 2
<location> `src/comms/CommsWidget.cpp:559-564` </location>
<code_context>
+    document.close();
+}
+
+void CommsWidget::slot_saveLogOnExit()
+{
+    const auto &comms = getConfig().comms;
+    if (!comms.saveLogOnExit.get()) {
+        return;
+    }
+
+    // Use the same directory as AutoLogger
+    const auto &autoLogConfig = getConfig().autoLog;
+    QString logDir = autoLogConfig.autoLogDirectory;
+    if (logDir.isEmpty()) {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Communications log directory ignores comms.logDirectory setting and always follows AutoLogger

`slot_saveLogOnExit` always uses `autoLog.autoLogDirectory` (or CWD) and never reads `comms.logDirectory`, even though the comms config exposes a dedicated `logDirectory`. If comms logs are meant to be independently configurable, this should first check `comms.logDirectory` (when non-empty) and only fall back to the AutoLogger directory when it is unset.

```suggestion
    // Prefer dedicated comms log directory; fall back to AutoLogger directory, then CWD
    const auto &autoLogConfig = getConfig().autoLog;
    QString logDir = comms.logDirectory;
    if (logDir.isEmpty()) {
        logDir = autoLogConfig.autoLogDirectory;
    }
    if (logDir.isEmpty()) {
        logDir = QDir::current().path();
    }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +258 to +267
bool CommsManager::isRecentYellDuplicate(const QString &sender, const QString &message) const
{
const QString key = QString("%1|%2").arg(sender, message);

if (!m_recentYells.contains(key)) {
return false;
}

// Check if it's recent (within last 2 seconds)
const qint64 timestamp = m_recentYells[key];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Accessing m_recentYells via operator[] in a const method will not compile and also mutates the hash

In isRecentYellDuplicate, m_recentYells[key] calls the non-const QHash::operator[], which inserts a default value for missing keys. This violates const-correctness and mutates the hash. Use a const-safe lookup instead, e.g. m_recentYells.value(key, 0) or auto it = m_recentYells.constFind(key); and check it != m_recentYells.cend() before reading the timestamp.

Comment on lines +559 to +564
// Use the same directory as AutoLogger
const auto &autoLogConfig = getConfig().autoLog;
QString logDir = autoLogConfig.autoLogDirectory;
if (logDir.isEmpty()) {
logDir = QDir::current().path();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Communications log directory ignores comms.logDirectory setting and always follows AutoLogger

slot_saveLogOnExit always uses autoLog.autoLogDirectory (or CWD) and never reads comms.logDirectory, even though the comms config exposes a dedicated logDirectory. If comms logs are meant to be independently configurable, this should first check comms.logDirectory (when non-empty) and only fall back to the AutoLogger directory when it is unset.

Suggested change
// Use the same directory as AutoLogger
const auto &autoLogConfig = getConfig().autoLog;
QString logDir = autoLogConfig.autoLogDirectory;
if (logDir.isEmpty()) {
logDir = QDir::current().path();
}
// Prefer dedicated comms log directory; fall back to AutoLogger directory, then CWD
const auto &autoLogConfig = getConfig().autoLog;
QString logDir = comms.logDirectory;
if (logDir.isEmpty()) {
logDir = autoLogConfig.autoLogDirectory;
}
if (logDir.isEmpty()) {
logDir = QDir::current().path();
}


public slots:
void slot_parseGmcpInput(const GmcpMessage &msg);
void slot_parseRawGameText(const QString &rawText);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where these signals and slots are connected. I guess it got missed in the PR split?

DELETE_CTORS_AND_ASSIGN_OPS(CommsManager);

public slots:
void slot_parseGmcpInput(const GmcpMessage &msg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than slots we should follow the example of the GameObserver and AdventureTracker. GameObserver already has most of the wiring and we can use the same pattern as AdventureTracker to tap in.

CommType CommsManager::getCommTypeFromChannel(const QString &channel)
{
// Map GMCP channel names to CommType (support both singular and plural forms)
if (channel == "tells" || channel == "tell") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does MUME send? It doesn't make sense to support both if MUME only sends one type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually get a list of available channels when we request support: https://mume.org/help/gmcp_comm.channel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants