using System; using AcDream.Core.Chat; using AcDream.UI.Abstractions.Panels.Chat; namespace AcDream.App.UI; /// /// Copies the chat transcript into retail's @log file. /// /// /// /// Retail's log write sits INSIDE ClientSystem::AddTextToScroll /// (fprintf(s_pLogFile, "%ls%ls\n", …) @0x00563E5B) — downstream of /// composition and upstream of glyph layout. So the log records the finished /// display line, timestamp included, and does not re-derive one. /// is acdream's equivalent single fan-in. /// /// /// Attaching happens on OPEN rather than at startup, which is what retail's /// own help promises: "All the information that appears in your chat window /// AFTER you type this command will be copied into a text file." /// /// public sealed class ChatTranscriptLogWriter { private readonly ChatSessionLog _log; private ChatLog? _source; public ChatTranscriptLogWriter(ChatSessionLog log) => _log = log ?? throw new ArgumentNullException(nameof(log)); /// /// Starts copying , detaching from whatever was /// attached before. Re-attaching to the same transcript does not double /// up. /// public void Attach(ChatLog source) { ArgumentNullException.ThrowIfNull(source); Detach(); _source = source; source.EntryAppended += Write; } /// /// Stops copying. Detaches from the instance actually attached to, not /// from whatever is current — a transcript replaced mid-log must not leave /// a handler behind on the old one. /// public void Detach() { if (_source is null) return; _source.EntryAppended -= Write; _source = null; } private void Write(ChatEntry entry) { ChatLog? source = _source; if (source is null) return; // The SAME gate the window uses, so a log never disagrees with the // transcript it is a copy of. bool stamped = source.DisplayTimestampsSource?.Invoke() == true; _log.Write( stamped ? ChatLog.FormatTimestampPrefix(entry.Received) : null, ChatVM.FormatEntry(entry)); } }