Skip to content

Chat overhaul: tabbed in-game chat panel (stacked on #96) - #97

Open
errolgr wants to merge 3 commits into
Project-Diablo-2:mainfrom
errolgr:feature/chat-overhaul
Open

errolgr wants to merge 3 commits into
Project-Diablo-2:mainfrom
errolgr:feature/chat-overhaul

Conversation

@errolgr

@errolgr errolgr commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #96 (chat item links). This branch contains #96's commit; the new work is the last two commits: 3061a09 (a small SetLineObserver addition to the ItemLinks API) and b7eaf40 (the chat panel). Once #96 merges, this diff shrinks to just those two.

Summary

Replaces the in-game chat with a tabbed chat panel: All · Game · Whispers · System · Global. It adds 500 lines of scrollback, timestamps, highlighted mentions of your name, input history, and a reply hotkey. Item links from #96 work inside the panel, and .chat off brings back the stock chat at any time.

Panel (All) Whispers tab Item link in the panel
all whispers link
demo-panel.mp4

Problem

D2's chat shows the last few lines for a few seconds and then they're gone. Trade offers, whispers and party messages scroll away, there's no way to scroll back, and whispers get lost between game messages and system spam.

Changes

New module BH/Modules/Chat/: ChatModel (pure logic, unit-tested) + ChatOverhaul (engine glue).

Feature Details
Tabs All: everything. Game: what players in your game say (party members and you included). Whispers: in and out. System: server text, game/party events, BH notifications. Global: placeholder for a future realm channel. Unread counts on each tab.
Scrollback 500-line ring buffer. Mouse wheel, PgUp/PgDn, Home/End. A "Jump to latest (N new)" bar appears when you're scrolled up and new lines arrive.
Closed panel Behaves like stock chat: the last lines of the active tab fade after 10 s.
Input Up/Down recalls sent lines, Ctrl+R starts a reply to the last whisper, Ctrl+Tab cycles tabs.
Mentions Whole-word, case-insensitive matches of your character name (plus optional keywords) are highlighted, with an optional sound.
Commands .chat on | off | ts | clear | status | tab <name> (ts toggles timestamps).
Item links Uses #96's ChatLinkApi.h (FormatMessage / Release / OnClick, plus the new SetLineObserver), so links render and click the same way in the panel.
Safety 1.13c only; hooks are byte-checked and the module falls back to stock chat if anything differs. .chat off removes every hook.
Config chat.enabled (default on), chat.timestamps, chat.history_lines, chat.fade_seconds / fade_rows, chat.always_open, chat.mention_highlight / mention_keywords / mention_sound, chat.reply_hotkey.

How lines are sorted: chat packet 0x26 type 1 from a player = Game, type 2/6 = Whispers, everything printed without a chat packet = System. Battle.net whispers are tagged from BH's existing SID_CHATEVENT handler.

Testing

Check Result
Unit tests: 9 new ChatModel tests (packet lines land in the right tabs, bounded ring buffer evicts oldest, mention matching, colour-code stripping, timestamps, word wrapping + edge cases, sent-message history, unread badges + scroll position) 73 passed, 8 xfail, 0 failed (base #96: 64 / 8 / 0)
In game: realistic mixed chat (players, party, whispers in/out, server text, events, mention, BH notification) → exact rows checked per tab, badges, scroll, "Jump to latest", input history, Ctrl+R, .chat off back to stock PASS
In game: item link in the panel → native tooltip, and again after .chat off PASS
Soak: 10,000 injected lines PASS: memory flat from 2k to 10k lines (working set +180 KB); frame time p95 40.11 ms stock vs 40.23 ms panel open
Launch parity (game starts, menu, shift-click, PD2 and vanilla) PASS
Existing in-game scenarios (boot, nav, single-player enter, script smoke) unchanged vs base
MSVC Release|Win32 build success, 0 new warnings (164, same as base)

Not tested: online play (all runs are single player with injected chat packets), so Battle.net whisper tagging and server-side behaviour need a quick check on a test realm.

Game tab .chat off: stock chat, links still work
game off

…ooltip

Ctrl+Shift+left-click on an identified item puts "[Name] #i<token>" in the chat input. The token
is base64url of the item's save record (without "JM") plus its socket fillers' records.

Receive: the PrintGameString entry is hooked; every "#i<token>" word is parsed by a bounded
validator against the live data tables, canonicalized and re-encoded, and only those bytes reach
the engine decoder (D2Common #11145) to build a transient item unit (never hashed, no room). Valid
links are drawn as "[Item Name]" in the item's quality colour; invalid tokens stay plain text.
Clicking a link opens the game's own item tooltip for the transient copy (D2Client 0x93450 +
D2Win #10110); Esc, right-click or another click closes it.

The tooltip matches PD2's inventory hover: PD2's hover leaves "Quantity: N" out for stacks of an
equippable item type (ItemTypes Body set: javelins, throwing weapons), so the link tooltip drops
that line too and re-stages the popup at the builder's anchor for the shorter text. Keys and
other non-equippable stacks keep it.

The register-convention D2Client helpers are naked __fastcall stubs, so the compiler treats
eax/ecx/edx as clobbered across them. Link hit boxes leave a trailing colour code out of the
measured span (D2Win's TextNWidth measures one that ends the span as glyphs).

API for other chat UIs: BH/Modules/ItemLinks/ChatLinkApi.h (FormatMessage, Release, OnClick).
Config: item_links.enabled (default true). 1.13c only; the feature stays off if any code site
differs. A site another module has detoured (jmp rel32 + int3 padding, as PD2's D2GL
renderer does to D2Win #10085/#10177) still matches when every byte after the padding does.
…own lines

The observer sees every PrintGameString line after the link rewrite; while it is set the entry hook
stays installed even with item links disabled.
New module BH/Modules/Chat (client-only; .chat off / chat.enabled=false = vanilla chat, nothing hooked):
- Tabs All / Party / Whispers / System / Global (placeholder), unread badges per tab.
- Lines classified from the S->C packet that printed them (0x26 chat types 1/2/4/6, 0x5A events,
  party roster), everything else (BH item notifications, engine text) is System.
- 500-line bounded scrollback shared by all tabs: wheel, PgUp/PgDn, Home/End, Jump to latest.
- Optional [hh:mm] timestamps, mention highlight (character name + keywords) with optional sound.
- Input: Up/Down sent history (50, draft kept), Ctrl+R reply to last whisperer, Ctrl+Tab tab cycle,
  .chat commands handled in the chat box itself.
- Hooks (1.13c, verified bytes, removed when disabled): PrintGameString/PrintPartyString entries,
  packet-table slots 0x26/0x5A (chained), the two overlay draw calls, a window subclass.
- Pure parts in ChatModel.{h,cpp} (unit-tested natively by the harness).
- BH.json section chat; README section.
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.

1 participant