v1.0.1: Same-world + cross-world invites, suppress party-error toast
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -18,6 +18,11 @@ public sealed class ChatMonitorService
|
||||
private readonly IPluginLog _log;
|
||||
private HSRToolsConfiguration _config;
|
||||
|
||||
/// <summary>When set, the next party-error message (e.g. from our fallback invite) will be suppressed.</summary>
|
||||
private DateTime _suppressPartyErrorUntil = DateTime.MinValue;
|
||||
|
||||
private static readonly TimeSpan SuppressPartyErrorWindow = TimeSpan.FromSeconds(4);
|
||||
|
||||
private static readonly XivChatType[] LinkShellTypes =
|
||||
[
|
||||
XivChatType.Ls1, XivChatType.Ls2, XivChatType.Ls3, XivChatType.Ls4,
|
||||
@@ -51,15 +56,81 @@ public sealed class ChatMonitorService
|
||||
public void Start()
|
||||
{
|
||||
_chatGui.ChatMessage += OnChatMessage;
|
||||
_chatGui.CheckMessageHandled += OnCheckMessageHandled;
|
||||
PluginServices.ToastGui.Toast += OnToast;
|
||||
PluginServices.ToastGui.ErrorToast += OnErrorToast;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
PluginServices.ToastGui.ErrorToast -= OnErrorToast;
|
||||
PluginServices.ToastGui.Toast -= OnToast;
|
||||
_chatGui.ChatMessage -= OnChatMessage;
|
||||
_chatGui.CheckMessageHandled -= OnCheckMessageHandled;
|
||||
}
|
||||
|
||||
/// <summary>Suppress party-error toasts (big on-screen message) from our fallback invite. See BurntToast plugin.</summary>
|
||||
private void OnErrorToast(ref SeString message, ref bool isHandled)
|
||||
{
|
||||
if (DateTime.UtcNow >= _suppressPartyErrorUntil || isHandled)
|
||||
return;
|
||||
var msg = message.TextValue;
|
||||
var ord = StringComparison.OrdinalIgnoreCase;
|
||||
if ((msg.Contains("locate", ord) && msg.Contains("that name", ord)) ||
|
||||
msg.Contains("Unable to process party command", ord))
|
||||
{
|
||||
isHandled = true;
|
||||
_suppressPartyErrorUntil = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnToast(ref SeString message, ref Dalamud.Game.Gui.Toast.ToastOptions options, ref bool isHandled)
|
||||
{
|
||||
if (DateTime.UtcNow >= _suppressPartyErrorUntil || isHandled)
|
||||
return;
|
||||
var msg = message.TextValue;
|
||||
var ord = StringComparison.OrdinalIgnoreCase;
|
||||
if ((msg.Contains("locate", ord) && msg.Contains("that name", ord)) ||
|
||||
msg.Contains("Unable to process party command", ord))
|
||||
{
|
||||
isHandled = true;
|
||||
_suppressPartyErrorUntil = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Suppress party-error messages in chat (CheckMessageHandled path).</summary>
|
||||
private void OnCheckMessageHandled(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
if (DateTime.UtcNow >= _suppressPartyErrorUntil)
|
||||
return;
|
||||
var msg = message.TextValue;
|
||||
if (msg.Length == 0)
|
||||
return;
|
||||
var ord = StringComparison.OrdinalIgnoreCase;
|
||||
if ((msg.Contains("locate", ord) && msg.Contains("that name", ord)) ||
|
||||
msg.Contains("Unable to process party command", ord))
|
||||
{
|
||||
isHandled = true;
|
||||
_suppressPartyErrorUntil = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChatMessage(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
var msg = message.TextValue;
|
||||
// Suppress party-error messages from our fallback invite (same-world CWLS tries ContentId then name+world; first can fail).
|
||||
if (DateTime.UtcNow < _suppressPartyErrorUntil)
|
||||
{
|
||||
var ord = StringComparison.OrdinalIgnoreCase;
|
||||
if ((msg.Contains("locate", ord) && msg.Contains("that name", ord)) ||
|
||||
msg.Contains("Unable to process party command", ord))
|
||||
{
|
||||
isHandled = true;
|
||||
_suppressPartyErrorUntil = DateTime.MinValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_config.Enabled || string.IsNullOrWhiteSpace(_config.TriggerText))
|
||||
return;
|
||||
|
||||
@@ -73,6 +144,11 @@ public sealed class ChatMonitorService
|
||||
if (!messageText.Contains(trigger, comparison))
|
||||
return;
|
||||
|
||||
// Read log module first for non-tells (sender data only valid at callback start; overwritten quickly).
|
||||
var (logContentId, logWorldId) = type != XivChatType.TellIncoming
|
||||
? PartyInviteService.GetCurrentChatSenderFromLogModule()
|
||||
: (0UL, (ushort)0);
|
||||
|
||||
// Try to get name and world from the sender's link payload (the clickable name — world is in the payload, not visible text)
|
||||
var (senderName, worldIdFromPayload) = TryGetSenderFromPayloads(sender);
|
||||
if (string.IsNullOrWhiteSpace(senderName))
|
||||
@@ -84,6 +160,7 @@ public sealed class ChatMonitorService
|
||||
|
||||
ulong contentId = 0;
|
||||
ushort worldId = _partyInviteService.GetLocalWorldId();
|
||||
var localWorldId = _partyInviteService.GetLocalWorldId();
|
||||
|
||||
if (type == XivChatType.TellIncoming)
|
||||
{
|
||||
@@ -96,31 +173,35 @@ public sealed class ChatMonitorService
|
||||
}
|
||||
else
|
||||
{
|
||||
// For FC/LS/CWLS: server rejects name+world for cross-world ("Cannot locate character").
|
||||
// Try to get ContentId from RaptureLogModule.AddonMessageSub3488 (filled when message is printed).
|
||||
var (logContentId, logWorldId) = PartyInviteService.GetCurrentChatSenderFromLogModule();
|
||||
if (logContentId != 0)
|
||||
// FC/LS/CWLS: set worldId from payload (or local).
|
||||
if (worldIdFromPayload.HasValue && worldIdFromPayload.Value != 0)
|
||||
worldId = worldIdFromPayload.Value;
|
||||
|
||||
var isFcOrLs = type == XivChatType.FreeCompany || Array.IndexOf(LinkShellTypes, type) >= 0;
|
||||
var isCwls = Array.IndexOf(CrossWorldLinkShellTypes, type) >= 0;
|
||||
|
||||
if (isCwls && logContentId != 0)
|
||||
{
|
||||
contentId = logContentId;
|
||||
if (logWorldId != 0)
|
||||
worldId = logWorldId;
|
||||
}
|
||||
else if (worldIdFromPayload.HasValue && worldIdFromPayload.Value != 0)
|
||||
else if (isFcOrLs)
|
||||
{
|
||||
// Fallback: use world from PlayerPayload (same-world works; cross-world may fail)
|
||||
worldId = worldIdFromPayload.Value;
|
||||
contentId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (_config.Debug)
|
||||
{
|
||||
var payloadInfo = sender.Payloads == null
|
||||
? "null"
|
||||
: string.Join(", ", sender.Payloads.Select(p => p.Type.ToString()));
|
||||
_log.Info($"[HSRTools Debug] Sender payloads ({sender.Payloads?.Count ?? 0}): [{payloadInfo}]. " +
|
||||
$"Resolved name='{senderName}' worldIdFromPayload={worldIdFromPayload?.ToString() ?? "null"} usingWorldId={worldId} contentId={contentId} (0=name+world invite, non-zero=ContentId invite)");
|
||||
_log.Info($"[HSRTools Debug] {type} name='{senderName}' worldId={worldId} localWorldId={localWorldId} contentId={contentId} logContentId={logContentId} (0=name+world, non-zero=ContentId)");
|
||||
}
|
||||
|
||||
// Same-world CWLS with contentId uses fallback (ContentId then name+world); first attempt can show an error — suppress it.
|
||||
var isSameWorldCwls = Array.IndexOf(CrossWorldLinkShellTypes, type) >= 0 && contentId != 0 && localWorldId != 0 && worldId == localWorldId;
|
||||
if (isSameWorldCwls)
|
||||
_suppressPartyErrorUntil = DateTime.UtcNow.Add(SuppressPartyErrorWindow);
|
||||
|
||||
_log.Info($"Trigger \"{trigger}\" detected from {senderName} in {type}. Sending party invite.");
|
||||
var success = _partyInviteService.TryInviteToParty(senderName, contentId, worldId);
|
||||
if (_config.Debug)
|
||||
|
||||
Reference in New Issue
Block a user