namespace Aeshnidae.DiscordRelay; /// /// The tap into ACE's chat: the exact point where a global chat message has been /// delivered. /// /// TurbineChatHandler.LogTurbineChat is called once per message, on the last /// line of TurbineChatReceived, and every rejection path returns before /// reaching it - gags, chat_echo_only, the account-age / played-time / level /// gates, and a channel switched off with chat_disable_trade. So a patch here /// relays exactly the messages players actually saw, exactly once each. /// /// That is why this is the hook rather than the more obvious /// GameMessageTurbineChat constructor: that runs before the gates, so it would /// also push refused messages to Discord, and it runs for the acknowledgement packets /// too. /// /// A prefix rather than a postfix, because LogTurbineChat itself returns early when /// the matching chat_log_* server property is off. Whether the operator wants /// chat in the server log is a separate question from whether they want it in Discord. /// The prefix returns void, so ACE's own logging is untouched either way. /// [HarmonyPatch] public static class Patches { /// /// Private in ACE, so it is referenced by name. checks /// it still resolves before patching, rather than letting a rename upstream leave /// the mod stuck Inactive. /// internal const string LogTurbineChat = "LogTurbineChat"; /// /// Parameter names must match ACE's exactly - that is how Harmony binds them. /// Runs on the network thread mid-packet, so it hands off to a queue and returns. /// [HarmonyPrefix] [HarmonyPatch(typeof(TurbineChatHandler), LogTurbineChat)] public static void PreLogTurbineChat(uint channelID, string name, string message, uint senderID, ChatType chatType) { try { Mod.Relay?.SubmitChat(chatType, name, message); } catch (Exception ex) { // Throwing here would surface as an ACE bug on every line of global chat. ModManager.Log($"[{Mod.Name}] relay of {chatType} from {name} failed: {ex}", ModManager.LogLevel.Error); } } }