diff --git a/OverlayPlugin.Core/EventSources/EnmityEventSource.cs b/OverlayPlugin.Core/EventSources/EnmityEventSource.cs index 7a453cc5e..d341c07b6 100644 --- a/OverlayPlugin.Core/EventSources/EnmityEventSource.cs +++ b/OverlayPlugin.Core/EventSources/EnmityEventSource.cs @@ -15,12 +15,12 @@ using RainbowMage.OverlayPlugin.MemoryProcessors.Aggro; using RainbowMage.OverlayPlugin.MemoryProcessors.EnmityHud; using RainbowMage.OverlayPlugin.MemoryProcessors.Target; +using static RainbowMage.OverlayPlugin.MemoryProcessors.InCombat.LineInCombat; namespace RainbowMage.OverlayPlugin.EventSources { public class EnmityEventSource : EventSourceBase { - private IInCombatMemory inCombatMemory; private ICombatantMemory combatantMemory; private ITargetMemory targetMemory; private IEnmityMemory enmityMemory; @@ -43,20 +43,16 @@ internal class InCombatDataObject public bool inACTCombat = false; public bool inGameCombat = false; }; - InCombatDataObject sentCombatData; - // Unlike "sentCombatData" which caches sent data, this variable caches each update. - private bool lastInGameCombat = false; private const int endEncounterOutOfCombatDelayMs = 5000; CancellationTokenSource endEncounterToken; public BuiltinEventConfig Config { get; set; } - public event EventHandler CombatStatusChanged; + public event Action EnmityTick; public EnmityEventSource(TinyIoCContainer container) : base(container) { - inCombatMemory = container.Resolve(); combatantMemory = container.Resolve(); targetMemory = container.Resolve(); enmityMemory = container.Resolve(); @@ -67,6 +63,12 @@ public EnmityEventSource(TinyIoCContainer container) : base(container) EnmityTargetDataEvent, EnmityAggroListEvent, TargetableEnemiesEvent }); RegisterCachedEventType(InCombatEvent); + + var lineInCombat = container.Resolve(); + lineInCombat.OnInCombatChanged += OnInCombatChanged; + + EnmityTick += UpdateEnmity; + EnmityTick += lineInCombat.Update; } public override Control CreateConfigControl() @@ -93,20 +95,24 @@ public override void SaveConfig(IPluginConfig config) { } - private void UpdateInCombat() + private void OnInCombatChanged(object sender, InCombatArgs args) { - if (!inCombatMemory.IsValid()) - return; + var combatData = new InCombatDataObject(); + combatData.inACTCombat = args.InACTCombat; + combatData.inGameCombat = args.InGameCombat; + DispatchAndCacheEvent(JObject.FromObject(combatData)); - // Handle optional "end encounter of combat" logic. - bool inGameCombat = inCombatMemory.GetInCombat(); - if (inGameCombat != lastInGameCombat) + if (!args.InGameCombatChanged) { - logger.Log(LogLevel.Debug, inGameCombat ? "Entered combat" : "Left combat"); + return; } + // Handle optional "end encounter of combat" logic. + bool inGameCombat = args.InGameCombat; + logger.Log(LogLevel.Debug, inGameCombat ? "Entered combat" : "Left combat"); + // If we've transitioned to being out of combat, start a delayed task to end the ACT encounter. - if (Config.EndEncounterOutOfCombat && lastInGameCombat && !inGameCombat) + if (Config.EndEncounterOutOfCombat && !inGameCombat) { endEncounterToken = new CancellationTokenSource(); Task.Run(async delegate @@ -125,24 +131,6 @@ private void UpdateInCombat() endEncounterToken.Cancel(); endEncounterToken = null; } - if (lastInGameCombat != inGameCombat) - { - CombatStatusChanged?.Invoke(this, new CombatStatusChangedArgs(inGameCombat)); - } - lastInGameCombat = inGameCombat; - - if (HasSubscriber(InCombatEvent)) - { - bool inACTCombat = Advanced_Combat_Tracker.ActGlobals.oFormActMain.InCombat; - if (sentCombatData == null || sentCombatData.inACTCombat != inACTCombat || sentCombatData.inGameCombat != inGameCombat) - { - if (sentCombatData == null) - sentCombatData = new InCombatDataObject(); - sentCombatData.inACTCombat = inACTCombat; - sentCombatData.inGameCombat = inGameCombat; - DispatchAndCacheEvent(JObject.FromObject(sentCombatData)); - } - } } private void UpdateEnmity() @@ -153,35 +141,30 @@ private void UpdateEnmity() if (!targetData && !aggroList && !targetableEnemies) return; - var combatants = combatantMemory.GetCombatantList(); - - combatants.RemoveAll((c) => c.Type != ObjectType.PC && c.Type != ObjectType.Monster); - - if (targetData) - { - // See CreateTargetData() below - DispatchEvent(CreateTargetData(combatants)); - } - if (aggroList) - { - DispatchEvent(CreateAggroList(combatants)); - } - if (targetableEnemies) - { - DispatchEvent(CreateTargetableEnemyList(combatants)); - } - } - - protected override void Update() - { try { #if TRACE var stopwatch = new Stopwatch(); stopwatch.Start(); #endif - UpdateInCombat(); - UpdateEnmity(); + + var combatants = combatantMemory.GetCombatantList(); + + combatants.RemoveAll((c) => c.Type != ObjectType.PC && c.Type != ObjectType.Monster); + + if (targetData) + { + // See CreateTargetData() below + DispatchEvent(CreateTargetData(combatants)); + } + if (aggroList) + { + DispatchEvent(CreateAggroList(combatants)); + } + if (targetableEnemies) + { + DispatchEvent(CreateTargetableEnemyList(combatants)); + } #if TRACE Log(LogLevel.Trace, "UpdateEnmity: {0}ms", stopwatch.ElapsedMilliseconds); #endif @@ -192,6 +175,11 @@ protected override void Update() } } + protected override void Update() + { + EnmityTick.Invoke(); + } + [Serializable] internal class EnmityTargetDataObject { @@ -321,16 +309,6 @@ public List GetTargetableEnemyList(List combata } } - public class CombatStatusChangedArgs : EventArgs - { - public bool InCombat { get; private set; } - - public CombatStatusChangedArgs(bool status) - { - InCombat = status; - } - } - [Serializable] public class TargetableEnemyEntry { diff --git a/OverlayPlugin.Core/EventSources/MiniParseEventSource.cs b/OverlayPlugin.Core/EventSources/MiniParseEventSource.cs index cac00a0aa..3b446d325 100644 --- a/OverlayPlugin.Core/EventSources/MiniParseEventSource.cs +++ b/OverlayPlugin.Core/EventSources/MiniParseEventSource.cs @@ -355,10 +355,43 @@ private List> GetCombatants(List ids, List + { + ActGlobals.oFormActMain.EndCombat(true); + })); + } + private void LogLineHandler(bool isImport, LogLineEventArgs args) { if (isImport) { + try + { + var line = args.originalLogLine.Split('|'); + + if (int.TryParse(line[0], out int lineTypeInt)) + { + // If an imported log has split the encounter, also split it while importing. + // TODO: should we also consider the current user's wipe config option here for splitting, + // even if the original log writer did not have it set to true? + LogMessageType lineType = (LogMessageType)lineTypeInt; + if (lineType == LogMessageType.InCombat) + { + var inACTCombat = Convert.ToUInt32(line[2]); + if (inACTCombat == 0) + { + StopACTCombat(); + } + } + } + } + catch + { + return; + } + lock (importedLogs) { importedLogs.Add(args.originalLogLine); @@ -441,10 +474,7 @@ private void LogLineHandler(bool isImport, LogLineEventArgs args) // When CN/KR is on 6.2, this can be removed. if (line[3] == "40000010" || line[3] == "4000000F") { - ActGlobals.oFormActMain.Invoke((Action)(() => - { - ActGlobals.oFormActMain.EndCombat(true); - })); + StopACTCombat(); } break; } diff --git a/OverlayPlugin.Core/Integration/FFXIVRepository.cs b/OverlayPlugin.Core/Integration/FFXIVRepository.cs index 4ab1e67b6..c6440d30a 100644 --- a/OverlayPlugin.Core/Integration/FFXIVRepository.cs +++ b/OverlayPlugin.Core/Integration/FFXIVRepository.cs @@ -54,7 +54,13 @@ public enum LogMessageType PacketDump, Version, Error, - Timer + Timer, + // OverlayPlugin lines + RegisterLogLine = 256, + MapEffect, + FateDirector, + CEDirector, + InCombat, } public enum GameRegion @@ -462,5 +468,10 @@ public void RegisterProcessChangedHandler(Action handler) } } } + + public DateTime GetServerTimestamp() + { + return GetRepository()?.GetServerTimestamp() ?? DateTime.Now; + } } } diff --git a/OverlayPlugin.Core/NetworkProcessors/OverlayPluginLogLines.cs b/OverlayPlugin.Core/Integration/OverlayPluginLogLines.cs similarity index 97% rename from OverlayPlugin.Core/NetworkProcessors/OverlayPluginLogLines.cs rename to OverlayPlugin.Core/Integration/OverlayPluginLogLines.cs index d7247609b..ed55288de 100644 --- a/OverlayPlugin.Core/NetworkProcessors/OverlayPluginLogLines.cs +++ b/OverlayPlugin.Core/Integration/OverlayPluginLogLines.cs @@ -3,9 +3,11 @@ using System.IO; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using RainbowMage.OverlayPlugin.MemoryProcessors.InCombat; +using RainbowMage.OverlayPlugin.NetworkProcessors; using RainbowMage.OverlayPlugin.Updater; -namespace RainbowMage.OverlayPlugin.NetworkProcessors +namespace RainbowMage.OverlayPlugin { using Opcodes = Dictionary>; @@ -17,6 +19,7 @@ public OverlayPluginLogLines(TinyIoCContainer container) container.Register(new LineMapEffect(container)); container.Register(new LineFateControl(container)); container.Register(new LineCEDirector(container)); + container.Register(new LineInCombat(container)); } } diff --git a/OverlayPlugin.Core/Integration/UnstableNewLogLines.cs b/OverlayPlugin.Core/Integration/UnstableNewLogLines.cs index 98c2f29d8..fe95af862 100644 --- a/OverlayPlugin.Core/Integration/UnstableNewLogLines.cs +++ b/OverlayPlugin.Core/Integration/UnstableNewLogLines.cs @@ -8,7 +8,9 @@ using Advanced_Combat_Tracker; using Markdig.Helpers; using RainbowMage.OverlayPlugin.EventSources; +using RainbowMage.OverlayPlugin.MemoryProcessors.InCombat; using RainbowMage.OverlayPlugin.NetworkProcessors; +using static RainbowMage.OverlayPlugin.MemoryProcessors.InCombat.LineInCombat; namespace RainbowMage.OverlayPlugin.Integration { @@ -22,6 +24,7 @@ public class UnstableNewLogLines private string logPath = null; private ConcurrentQueue logQueue = null; private Thread logThread = null; + private LineInCombat lineInCombat = null; public UnstableNewLogLines(TinyIoCContainer container) { @@ -30,8 +33,10 @@ public UnstableNewLogLines(TinyIoCContainer container) enmitySource = container.Resolve(); logger = container.Resolve(); logPath = Path.GetDirectoryName(ActGlobals.oFormActMain.LogFilePath) + "_OverlayPlugin.log"; - var config = container.Resolve(); + lineInCombat = container.Resolve(); + + var config = container.Resolve(); config.LogLinesChanged += (o, e) => { if (config.LogLines) @@ -53,7 +58,7 @@ public UnstableNewLogLines(TinyIoCContainer container) public void Enable() { parser.OnOnlineStatusChanged += OnOnlineStatusChange; - enmitySource.CombatStatusChanged += OnCombatStatusChange; + lineInCombat.OnInCombatChanged += OnCombatStatusChange; logThread = new Thread(new ThreadStart(WriteBackgroundLog)); logThread.IsBackground = true; @@ -63,7 +68,7 @@ public void Enable() public void Disable() { parser.OnOnlineStatusChanged -= OnOnlineStatusChange; - enmitySource.CombatStatusChanged -= OnCombatStatusChange; + lineInCombat.OnInCombatChanged -= OnCombatStatusChange; logQueue?.Enqueue(null); } @@ -134,10 +139,15 @@ private void OnOnlineStatusChange(object sender, OnlineStatusChangedArgs ev) } } - private void OnCombatStatusChange(object sender, CombatStatusChangedArgs ev) + private void OnCombatStatusChange(object sender, InCombatArgs ev) { + if (!ev.InGameCombatChanged) + { + return; + } + string msg; - if (ev.InCombat) + if (ev.InGameCombat) { msg = "Entered combat"; } diff --git a/OverlayPlugin.Core/MemoryProcessors/InCombat/LineInCombat.cs b/OverlayPlugin.Core/MemoryProcessors/InCombat/LineInCombat.cs new file mode 100644 index 000000000..3384f69ad --- /dev/null +++ b/OverlayPlugin.Core/MemoryProcessors/InCombat/LineInCombat.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using RainbowMage.OverlayPlugin.MemoryProcessors.InCombat; +using static RainbowMage.OverlayPlugin.EventSources.EnmityEventSource; + +namespace RainbowMage.OverlayPlugin.MemoryProcessors.InCombat +{ + public class LineInCombat + { + public const uint LogFileLineID = 260; + private ILogger logger; + private readonly FFXIVRepository ffxiv; + private IInCombatMemory inCombatMemory; + private InCombatArgs lastEventArgs; + + private Func logWriter; + + public event EventHandler OnInCombatChanged; + + public class InCombatArgs + { + public bool InACTCombat { get; private set; } + public bool InGameCombat { get; private set; } + public bool InGameCombatChanged { get; private set; } + public InCombatArgs(bool inACTCombat, bool inGameCombat, bool inGameCombatChanged) + { + this.InACTCombat = inACTCombat; + this.InGameCombat = inGameCombat; + this.InGameCombatChanged = inGameCombatChanged; + } + } + + public LineInCombat(TinyIoCContainer container) + { + logger = container.Resolve(); + ffxiv = container.Resolve(); + inCombatMemory = container.Resolve(); + var customLogLines = container.Resolve(); + this.logWriter = customLogLines.RegisterCustomLogLine(new LogLineRegistryEntry() + { + Name = "InCombat", + Source = "OverlayPlugin", + ID = LogFileLineID, + Version = 1, + }); + } + + public void Update() + { + if (!inCombatMemory.IsValid()) + return; + + bool inACTCombat = Advanced_Combat_Tracker.ActGlobals.oFormActMain.InCombat; + bool inGameCombat = inCombatMemory.GetInCombat(); + + if (lastEventArgs != null && lastEventArgs.InACTCombat == inACTCombat && lastEventArgs.InGameCombat == inGameCombat) + { + return; + } + + // TODO: backwards-compatible logic here is to treat the starting value of inCombat as false, + // and to not always set inGameCombatChanged=true for the first event. Some parts of OverlayPlugin + // (e.g. OverlayHider) only care about when inCombat changes, but probably we should consider to + // always set this to true if lastEventArgs == null so that if ACT is started while out of combat, + // the overlays are hidden/shown appropriately. + bool inGameCombatChanged = lastEventArgs == null ? inGameCombat : lastEventArgs.InGameCombat != inGameCombat; + lastEventArgs = new InCombatArgs(inACTCombat, inGameCombat, inGameCombatChanged); + WriteLine(inACTCombat, inGameCombat); + OnInCombatChanged?.Invoke(this, lastEventArgs); + } + + public void WriteLine(bool inACTCombat, bool inGameCombat) + { + var line = $"{(inACTCombat ? 1 : 0)}|{(inGameCombat ? 1 : 0)}"; + logWriter(line, ffxiv.GetServerTimestamp()); + } + } +} diff --git a/OverlayPlugin.Core/OverlayHider.cs b/OverlayPlugin.Core/OverlayHider.cs index 7ee4c00e2..1b9cfc8c8 100644 --- a/OverlayPlugin.Core/OverlayHider.cs +++ b/OverlayPlugin.Core/OverlayHider.cs @@ -6,7 +6,9 @@ using System.Text; using System.Threading.Tasks; using System.Windows.Forms; +using RainbowMage.OverlayPlugin.MemoryProcessors.InCombat; using RainbowMage.OverlayPlugin.NetworkProcessors; +using static RainbowMage.OverlayPlugin.MemoryProcessors.InCombat.LineInCombat; namespace RainbowMage.OverlayPlugin { @@ -31,7 +33,7 @@ public OverlayHider(TinyIoCContainer container) container.Resolve().ActiveWindowChanged += ActiveWindowChangedHandler; container.Resolve().OnOnlineStatusChanged += OnlineStatusChanged; - container.Resolve().CombatStatusChanged += CombatStatusChanged; + container.Resolve().OnInCombatChanged += CombatStatusChanged; try { @@ -139,10 +141,14 @@ private void OnlineStatusChanged(object sender, OnlineStatusChangedArgs e) UpdateOverlays(); } - private void CombatStatusChanged(object sender, EventSources.CombatStatusChangedArgs e) + private void CombatStatusChanged(object sender, InCombatArgs args) { - inCombat = e.InCombat; - UpdateOverlays(); + inCombat = args.InGameCombat; + + if (args.InGameCombatChanged) + { + UpdateOverlays(); + } } } } diff --git a/OverlayPlugin.Core/OverlayPlugin.Core.csproj b/OverlayPlugin.Core/OverlayPlugin.Core.csproj index 752c397ab..b32248bb3 100644 --- a/OverlayPlugin.Core/OverlayPlugin.Core.csproj +++ b/OverlayPlugin.Core/OverlayPlugin.Core.csproj @@ -109,6 +109,7 @@ + @@ -132,7 +133,7 @@ - + UserControl @@ -536,7 +537,7 @@ - xcopy /d /e /i /q /r /h /y $(ProjectDir)\resources $(TargetDir)\..\resources + xcopy /d /e /i /q /r /h /y $(ProjectDir)\resources $(TargetDir)\..\resources - + \ No newline at end of file diff --git a/OverlayPlugin.Core/PluginMain.cs b/OverlayPlugin.Core/PluginMain.cs index 7de264de5..99e4f7cc0 100644 --- a/OverlayPlugin.Core/PluginMain.cs +++ b/OverlayPlugin.Core/PluginMain.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; @@ -252,7 +253,6 @@ public void InitPlugin(TabPage pluginScreenSpace, Label pluginStatusText) _container.Register(new NetworkParser(_container)); _container.Register(new TriggIntegration(_container)); _container.Register(new FFXIVCustomLogLines(_container)); - _container.Register(new OverlayPluginLogLines(_container)); // Register FFXIV memory reading subcomponents. // Must be done before loading addons. @@ -266,6 +266,8 @@ public void InitPlugin(TabPage pluginScreenSpace, Label pluginStatusText) _container.Register(); _container.Register(); + _container.Register(new OverlayPluginLogLines(_container)); + // This timer runs on the UI thread (it has to since we create UI controls) but LoadAddons() // can block for some time. We run it on the background thread to avoid blocking the UI. // We can't run LoadAddons() in the first init phase since it checks other ACT plugins for diff --git a/OverlayPlugin.Core/resources/reserved_log_lines.json b/OverlayPlugin.Core/resources/reserved_log_lines.json index d9a8bd3ac..6c7c4660d 100644 --- a/OverlayPlugin.Core/resources/reserved_log_lines.json +++ b/OverlayPlugin.Core/resources/reserved_log_lines.json @@ -35,7 +35,14 @@ "Comment": "Critical Engagement data" }, { - "StartID": 260, + "ID": 260, + "Name": "InCombat", + "Source": "OverlayPlugin", + "Version": 1, + "Comment": "Combat beginning / ending" + }, + { + "StartID": 261, "EndID": 512, "Source": "OverlayPlugin", "Version": 0,