diff --git a/Splatoon/Memory/BuffEffectProcessor.cs b/Splatoon/Memory/BuffEffectProcessor.cs new file mode 100644 index 00000000..9af23c03 --- /dev/null +++ b/Splatoon/Memory/BuffEffectProcessor.cs @@ -0,0 +1,290 @@ +using Dalamud.Game.ClientState.Statuses; +using ECommons.Hooks; +using Splatoon.SplatoonScripting; + +namespace Splatoon.Memory; +internal class BuffEffectProcessor +{ + #region types + private enum StatusChangeType + { + NoChange, + Remove, + Gain + } + + private enum StatusChangeResult + { + NoChange, + Change + } + + private struct CharactorStatusInfo + { + public uint ObjectID; + public uint[] StatusIds; + public int NoUpdateCount; + } + + private struct CharactorStatusDiffResult + { + public uint StatusId; + public StatusChangeType ChangeType; + } + #endregion + + #region privateDefine + private Dictionary _charactorStatusInfos = new Dictionary(); + private static bool _isClearRequest = false; + #endregion + + #region public + public void ActorEffectUpdate() + { + if(_isClearRequest) + { + _charactorStatusInfos.Clear(); + _isClearRequest = false; + } + + // Increment the NoUpdateCount for all objects + IncrementNoUpdateCount(); + + // Loop through all objects in Svc.Objects + foreach(var gameObject in Svc.Objects) + { + if(gameObject == null) + continue; + + // Check if it can be cast to IBattleChara + if(gameObject is IBattleChara battleChara) + { + var objectID = battleChara.EntityId; + + // If the object exists, reset the counter + if(_charactorStatusInfos.TryGetValue(objectID, out var statusInfo)) + { + statusInfo.NoUpdateCount = 0; // Reset the counter as the object is confirmed to exist + _charactorStatusInfos[objectID] = statusInfo; + } + + var statuses = battleChara.StatusList; + + // Compare the current and previous status lists + CompareStatusList(objectID, statuses, out var changeStatuses); + + // Log the changes, including gameObject.Name + LogChanges(battleChara, changeStatuses); + + // Save the current status list + CopyStatusList(objectID, statuses); + } + } + + // Remove objects that have not been updated for 10 cycles + RemoveInactiveObjects(); + } + + public static void DirectorCheck(DirectorUpdateCategory category) + { + if(category == DirectorUpdateCategory.Commence || + category == DirectorUpdateCategory.Wipe) + { + _isClearRequest = true; + } + } + #endregion + + #region private + private void IncrementNoUpdateCount() + { + // Increment the NoUpdateCount for all objects + foreach(var key in _charactorStatusInfos.Keys) + { + var statusInfo = _charactorStatusInfos[key]; + statusInfo.NoUpdateCount++; + _charactorStatusInfos[key] = statusInfo; + } + } + + private void RemoveInactiveObjects() + { + // Add objects with NoUpdateCount >= 10 to the removal list + List toRemove = new List(); + foreach(var kvp in _charactorStatusInfos) + { + if(kvp.Value.NoUpdateCount >= 10) + { + toRemove.Add(kvp.Key); + } + } + + // Actually remove the objects + foreach(var objectID in toRemove) + { + _charactorStatusInfos.Remove(objectID); + } + } + + private void CopyStatusList(uint objectID, StatusList statuses) + { + var newStatusIds = GetStatusIds(statuses); + + if(_charactorStatusInfos.TryGetValue(objectID, out var existingInfo)) + { + if(!ArraysEqual(existingInfo.StatusIds, newStatusIds)) + { + _charactorStatusInfos[objectID] = new CharactorStatusInfo + { + ObjectID = objectID, + StatusIds = newStatusIds, + NoUpdateCount = 0 // Reset the counter as it has been updated + }; + } + } + else + { + _charactorStatusInfos.Add(objectID, new CharactorStatusInfo + { + ObjectID = objectID, + StatusIds = newStatusIds, + NoUpdateCount = 0 // Set the counter to 0 when adding a new object + }); + } + } + + private StatusChangeResult CompareStatusList(uint objectID, StatusList statuses, out List changeStatuses) + { + changeStatuses = new List(); + + if(!_charactorStatusInfos.TryGetValue(objectID, out var existingInfo)) + { + return StatusChangeResult.NoChange; + } + + var currentStatusIds = GetStatusIds(statuses); + + CheckGains(currentStatusIds, existingInfo.StatusIds, changeStatuses); + CheckRemovals(currentStatusIds, existingInfo.StatusIds, changeStatuses); + + return changeStatuses.Count > 0 ? StatusChangeResult.Change : StatusChangeResult.NoChange; + } + + private uint[] GetStatusIds(StatusList statuses) + { + var statusIds = new uint[statuses.Length]; + for(int i = 0; i < statuses.Length; i++) + { + statusIds[i] = statuses[i]?.StatusId ?? 0; + } + return statusIds; + } + + private void CheckGains(uint[] currentStatusIds, uint[] oldStatusIds, List changeStatuses) + { + for(int i = 0; i < currentStatusIds.Length; i++) + { + if(System.Array.IndexOf(oldStatusIds, currentStatusIds[i]) < 0) + { + changeStatuses.Add(new CharactorStatusDiffResult + { + StatusId = currentStatusIds[i], + ChangeType = StatusChangeType.Gain + }); + } + } + } + + private void CheckRemovals(uint[] currentStatusIds, uint[] oldStatusIds, List changeStatuses) + { + for(int i = 0; i < oldStatusIds.Length; i++) + { + if(System.Array.IndexOf(currentStatusIds, oldStatusIds[i]) < 0) + { + changeStatuses.Add(new CharactorStatusDiffResult + { + StatusId = oldStatusIds[i], + ChangeType = StatusChangeType.Remove + }); + } + } + } + + private bool ArraysEqual(uint[] array1, uint[] array2) + { + if(array1.Length != array2.Length) + return false; + + for(int i = 0; i < array1.Length; i++) + { + if(array1[i] != array2[i]) + return false; + } + return true; + } + + // Updated LogChanges method + private void LogChanges(IBattleChara battleChara, List changeStatuses) + { + List gainStatusIds = new List(); + List removeStatusIds = new List(); + + foreach(var changeStatus in changeStatuses) + { + switch(changeStatus.ChangeType) + { + case StatusChangeType.Gain: + gainStatusIds.Add(changeStatus.StatusId); + break; + case StatusChangeType.Remove: + removeStatusIds.Add(changeStatus.StatusId); + break; + } + } + + if(gainStatusIds.Count > 0) + { + string text; + if(P.Config.LogPosition) + { + foreach(var statusId in gainStatusIds) + { + text = $"{battleChara.Name} ({battleChara.Position.ToString()}) gains the effect of {statusId} ({battleChara.NameId}:+{statusId})"; + P.ChatMessageQueue.Enqueue(text); + } + } + else + { + foreach(var statusId in gainStatusIds) + { + text = $"{battleChara.Name} gains the effect of {statusId} ({battleChara.NameId}:+{statusId})"; + P.ChatMessageQueue.Enqueue(text); + } + } + ScriptingProcessor.OnGainBuffEffect(battleChara.EntityId, gainStatusIds); + } + + if(removeStatusIds.Count > 0) + { + string text; + if(P.Config.LogPosition) + { + foreach(var statusId in removeStatusIds) + { + text = $"{battleChara.Name} ({battleChara.Position.ToString()}) loses the effect of {statusId} ({battleChara.NameId}:-{statusId})"; + P.ChatMessageQueue.Enqueue(text); + } + } + else + { + foreach(var statusId in removeStatusIds) + { + text = $"{battleChara.Name} loses the effect of {statusId} ({battleChara.NameId}:-{statusId})"; + P.ChatMessageQueue.Enqueue(text); + } + } + ScriptingProcessor.OnRemoveBuffEffect(battleChara.EntityId, removeStatusIds); + } + } + #endregion +} diff --git a/Splatoon/Memory/DirectorUpdateProcessor.cs b/Splatoon/Memory/DirectorUpdateProcessor.cs index cd6a76e3..742cb55f 100644 --- a/Splatoon/Memory/DirectorUpdateProcessor.cs +++ b/Splatoon/Memory/DirectorUpdateProcessor.cs @@ -23,6 +23,7 @@ internal static void ProcessDirectorUpdate(long a1, long a2, DirectorUpdateCateg Logger.Log(text); PluginLog.Verbose(text); } + BuffEffectProcessor.DirectorCheck(a3); ScriptingProcessor.OnDirectorUpdate(a3); } } diff --git a/Splatoon/Splatoon.cs b/Splatoon/Splatoon.cs index b4f4896c..f374796b 100644 --- a/Splatoon/Splatoon.cs +++ b/Splatoon/Splatoon.cs @@ -83,6 +83,7 @@ public unsafe class Splatoon : IDalamudPlugin public NotificationMasterApi NotificationMasterApi; public Archive Archive; private ActorControlProcessor ActorControlProcessor; + internal BuffEffectProcessor BuffEffectProcessor; internal void Load(IDalamudPluginInterface pluginInterface) { @@ -187,6 +188,7 @@ internal void Load(IDalamudPluginInterface pluginInterface) SingletonServiceManager.Initialize(typeof(S)); Archive = EzConfig.LoadConfiguration("Archive.json"); ActorControlProcessor = new ActorControlProcessor(); + BuffEffectProcessor = new(); Init = true; SplatoonIPC.Init(); } @@ -557,6 +559,7 @@ internal void Tick(IFramework framework) } prevCombatState = Svc.Condition[ConditionFlag.InCombat]; CurrentChatMessages.Clear(); + BuffEffectProcessor.ActorEffectUpdate(); ScriptingProcessor.OnUpdate(); } catch (Exception e) diff --git a/Splatoon/SplatoonScripting/ScriptingProcessor.cs b/Splatoon/SplatoonScripting/ScriptingProcessor.cs index 6a71df7b..de519158 100644 --- a/Splatoon/SplatoonScripting/ScriptingProcessor.cs +++ b/Splatoon/SplatoonScripting/ScriptingProcessor.cs @@ -630,6 +630,36 @@ internal static void OnActionEffectEvent(ActionEffectSet set) } } + internal static void OnGainBuffEffect(uint sourceId, List gainBuffIds) + { + for(var i = 0; i < Scripts.Count; i++) + { + if(Scripts[i].IsEnabled) + { + try + { + Scripts[i].OnGainBuffEffect(sourceId, gainBuffIds); + } + catch(Exception e) { Scripts[i].LogError(e, nameof(SplatoonScript.OnGainBuffEffect)); } + } + } + } + + internal static void OnRemoveBuffEffect(uint sourceId, List removeBuffIds) + { + for(var i = 0; i < Scripts.Count; i++) + { + if(Scripts[i].IsEnabled) + { + try + { + Scripts[i].OnRemoveBuffEffect(sourceId, removeBuffIds); + } + catch(Exception e) { Scripts[i].LogError(e, nameof(SplatoonScript.OnRemoveBuffEffect)); } + } + } + } + internal static void TerritoryChanged() { for (var i = 0; i < Scripts.Count; i++) diff --git a/Splatoon/SplatoonScripting/SplatoonScript.cs b/Splatoon/SplatoonScripting/SplatoonScript.cs index e4cd8e58..47dc58c5 100644 --- a/Splatoon/SplatoonScripting/SplatoonScript.cs +++ b/Splatoon/SplatoonScripting/SplatoonScript.cs @@ -4,7 +4,6 @@ using ECommons.Hooks; using ECommons.Hooks.ActionEffectTypes; using ECommons.LanguageHelpers; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Newtonsoft.Json; namespace Splatoon.SplatoonScripting; @@ -190,25 +189,39 @@ public virtual void OnScriptUpdated(uint previousVersion) { } /// public virtual void OnSettingsDraw() { } + /// + /// Will be called when a buff is gained by a game object. This method will only be called if a script is enabled. + /// + /// Source object ID of buff gain. + /// Array of gained buff IDs. + public virtual void OnGainBuffEffect(uint sourceId, List gainBuffIds) { } + + /// + /// Will be called when a buff is removed from a game object. This method will only be called if a script is enabled. + /// + /// Source object ID of buff removal. + /// Array of removed buff IDs. + public virtual void OnRemoveBuffEffect(uint sourceId, List removeBuffIds) { } + internal void DrawRegisteredElements() { ImGuiEx.TextWrapped(ImGuiColors.DalamudRed, $"Non-restricted editing access. Any incorrectly performed changes may cause script to stop working completely. Use reset function if it happens. \n- In general, only edit color, thickness, text, size. \n- If script has it's own color settings, they will be prioritized.\n- Not all script will take whatever you edit here into account.".Loc()); - if (ImGui.Button("Export customized settings to clipboard".Loc())) + if(ImGui.Button("Export customized settings to clipboard".Loc())) { GenericHelpers.Copy(JsonConvert.SerializeObject(InternalData.Overrides, new JsonSerializerSettings() { DefaultValueHandling = DefaultValueHandling.Populate })); //Notify.Success("Copied to clipboard".Loc()); } ImGui.SameLine(); - if (ImGui.Button("Import customized settings from clipboard (hold CTRL+click)".Loc())) + if(ImGui.Button("Import customized settings from clipboard (hold CTRL+click)".Loc())) { - if (ImGui.GetIO().KeyCtrl) + if(ImGui.GetIO().KeyCtrl) { try { var x = JsonConvert.DeserializeObject(GenericHelpers.Paste()); - if (x != null) + if(x != null) { - if (ImGui.GetIO().KeyShift || x.Elements.All(z => Controller.GetRegisteredElements().ContainsKey(z.Key))) + if(ImGui.GetIO().KeyShift || x.Elements.All(z => Controller.GetRegisteredElements().ContainsKey(z.Key))) { InternalData.Overrides = x; Controller.ApplyOverrides(); @@ -220,7 +233,7 @@ internal void DrawRegisteredElements() } } } - catch (Exception e) + catch(Exception e) { e.Log(); Notify.Error(e.Message); @@ -228,34 +241,34 @@ internal void DrawRegisteredElements() } } ImGui.Checkbox($"Enable unconditional element preview".Loc(), ref InternalData.UnconditionalDraw); - if (InternalData.UnconditionalDraw) + if(InternalData.UnconditionalDraw) { - if (ImGui.Button("Preview draw all".Loc())) + if(ImGui.Button("Preview draw all".Loc())) { Controller.GetRegisteredElements().Each(x => InternalData.UnconditionalDrawElements.Add(x.Key)); } ImGui.SameLine(); - if (ImGui.Button("Preview draw none".Loc())) + if(ImGui.Button("Preview draw none".Loc())) { InternalData.UnconditionalDrawElements.Clear(); } } - foreach (var x in Controller.GetRegisteredElements()) + foreach(var x in Controller.GetRegisteredElements()) { ImGui.PushID(x.Value.GUID); - if (InternalData.UnconditionalDraw) + if(InternalData.UnconditionalDraw) { ImGuiEx.HashSetCheckbox($"Preview draw".Loc(), x.Key, InternalData.UnconditionalDrawElements); ImGui.SameLine(); } - if (ImGui.Button("Copy to clipboard".Loc())) + if(ImGui.Button("Copy to clipboard".Loc())) { GenericHelpers.Copy(JsonConvert.SerializeObject(x.Value, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore })); } ImGui.SameLine(); - if (ImGui.Button("Edit".Loc())) + if(ImGui.Button("Edit".Loc())) { - if (!InternalData.Overrides.Elements.ContainsKey(x.Key)) + if(!InternalData.Overrides.Elements.ContainsKey(x.Key)) { Notify.Info($"Created override for {x.Key}"); InternalData.Overrides.Elements[x.Key] = x.Value.JSONClone(); @@ -263,7 +276,7 @@ internal void DrawRegisteredElements() P.PinnedElementEditWindow.Open(this, x.Key); } ImGui.SameLine(); - if (InternalData.Overrides.Elements.ContainsKey(x.Key)) + if(InternalData.Overrides.Elements.ContainsKey(x.Key)) { ImGuiEx.HashSetCheckbox("Reset".Loc(), x.Key, InternalData.ElementsResets); } @@ -271,11 +284,11 @@ internal void DrawRegisteredElements() ImGuiEx.Text($"[{x.Key}] {x.Value.Name}"); ImGui.PopID(); } - if (InternalData.ElementsResets.Count > 0) + if(InternalData.ElementsResets.Count > 0) { - if (ImGui.Button("Reset selected elements and reload script".Loc())) + if(ImGui.Button("Reset selected elements and reload script".Loc())) { - foreach (var x in InternalData.ElementsResets) + foreach(var x in InternalData.ElementsResets) { InternalData.Overrides.Elements.Remove(x); } @@ -289,7 +302,7 @@ internal void DrawRegisteredElements() internal bool Enable() { - if (IsEnabled || IsDisabledByUser || !this.InternalData.Allowed || this.InternalData.Blacklisted) + if(IsEnabled || IsDisabledByUser || !this.InternalData.Allowed || this.InternalData.Blacklisted) { return false; } @@ -298,7 +311,7 @@ internal bool Enable() PluginLog.Information($"Enabling script {this.InternalData.Name}"); this.OnEnable(); } - catch (Exception ex) + catch(Exception ex) { ScriptingProcessor.LogError(this, ex, nameof(Enable)); } @@ -310,7 +323,7 @@ internal bool Enable() internal bool Disable() { this.Controller.SaveConfig(); - if (!IsEnabled) + if(!IsEnabled) { return false; } @@ -320,7 +333,7 @@ internal bool Disable() PluginLog.Information($"Disabling script {this}"); this.OnDisable(); } - catch (Exception ex) + catch(Exception ex) { ScriptingProcessor.LogError(this, ex, nameof(Disable)); } diff --git a/SplatoonScripts/Generic/ScriptEventLogger.cs b/SplatoonScripts/Generic/ScriptEventLogger.cs index 86067267..4d0528d6 100644 --- a/SplatoonScripts/Generic/ScriptEventLogger.cs +++ b/SplatoonScripts/Generic/ScriptEventLogger.cs @@ -24,7 +24,7 @@ namespace SplatoonScriptsOfficial.Generic; internal class ScriptEventLogger :SplatoonScript { public override HashSet? ValidTerritories { get; } = null; - public override Metadata? Metadata => new(2, "Redmoon"); + public override Metadata? Metadata => new(3, "Redmoon"); private Config Conf => Controller.GetConfig(); @@ -221,6 +221,22 @@ public override void OnActionEffectEvent(ActionEffectSet set) PluginLog.Information($"OnActionEffectEvent: {set.Action.Name}({set.Action.RowId}) - Source: {set.Source.Name}{set.Source.Position}(GID: {set.Source.GameObjectId} DID: {set.Source.DataId}) - Target: {set.Target.Name}{set.Target.Position}(GID: {set.Target.GameObjectId} DID: {set.Target.DataId})"); } + public override void OnGainBuffEffect(uint sourceId, List gainBuffIds) + { + if (!Conf.FilterOnGainBuffEffect) + return; + var gameObject = sourceId.GetObject(); + PluginLog.Information($"OnGainBuffEffect: [{gameObject.Name}({sourceId})] {string.Join(", ", gainBuffIds)}"); + } + + public override void OnRemoveBuffEffect(uint sourceId, List removeBuffIds) + { + if (!Conf.FilterOnRemoveBuffEffect) + return; + var gameObject = sourceId.GetObject(); + PluginLog.Information($"OnRemoveBuffEffect: [{gameObject.Name}({sourceId})] {string.Join(", ", removeBuffIds)}"); + } + public override void OnReset() { if (!Conf.FilterOnReset) @@ -256,6 +272,8 @@ public override void OnSettingsDraw() ImGui.Checkbox("OnObjectCreation()", ref Conf.FilterOnObjectCreation); ImGui.Checkbox("OnActorControl()", ref Conf.FilterOnActorControl); ImGui.Checkbox("OnActionEffectEvent()", ref Conf.FilterOnActionEffectEvent); + ImGui.Checkbox("OnGainBuffEffect()", ref Conf.FilterOnGainBuffEffect); + ImGui.Checkbox("OnRemoveBuffEffect()", ref Conf.FilterOnRemoveBuffEffect); ImGui.Checkbox("OnReset()", ref Conf.FilterOnReset); } @@ -280,6 +298,8 @@ public class Config :IEzConfig public bool FilterOnObjectCreation = false; public bool FilterOnActorControl = false; public bool FilterOnActionEffectEvent = true; + public bool FilterOnGainBuffEffect = false; + public bool FilterOnRemoveBuffEffect = false; public bool FilterOnReset = false; public void Reset() @@ -303,6 +323,8 @@ public void Reset() FilterOnObjectCreation = false; FilterOnActorControl = false; FilterOnActionEffectEvent = true; + FilterOnGainBuffEffect = false; + FilterOnRemoveBuffEffect = false; FilterOnReset = false; } } diff --git a/SplatoonScripts/Tests/OnBuffEffectTest.cs b/SplatoonScripts/Tests/OnBuffEffectTest.cs new file mode 100644 index 00000000..c4a14394 --- /dev/null +++ b/SplatoonScripts/Tests/OnBuffEffectTest.cs @@ -0,0 +1,25 @@ +using ECommons.Logging; +using Splatoon.SplatoonScripting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SplatoonScriptsOfficial.Tests; +internal class OnBuffEffectTest : SplatoonScript +{ + public override HashSet? ValidTerritories => null; + + public override void OnGainBuffEffect(uint sourceId, List gainBuffIds) + { + var gameObject = sourceId.GetObject(); + PluginLog.Information($"OnGainBuffEffect: [{gameObject.Name}({sourceId})] {string.Join(", ", gainBuffIds)}"); + } + + public override void OnRemoveBuffEffect(uint sourceId, List removeBuffIds) + { + var gameObject = sourceId.GetObject(); + PluginLog.Information($"OnRemoveBuffEffect: [{gameObject.Name}({sourceId})] {string.Join(", ", removeBuffIds)}"); + } +} diff --git a/SplatoonScripts/Tests/StatusListMonitoring.cs b/SplatoonScripts/Tests/StatusListMonitoring.cs new file mode 100644 index 00000000..ace8aead --- /dev/null +++ b/SplatoonScripts/Tests/StatusListMonitoring.cs @@ -0,0 +1,247 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Statuses; +using ECommons.DalamudServices; +using ECommons.Logging; +using ImGuiNET; +using Splatoon.SplatoonScripting; +using System.Collections.Generic; + +namespace SplatoonScriptsOfficial.Tests; +internal class StatusListMonitoring :SplatoonScript +{ + #region types + private enum StatusChangeType + { + NoChange, + Remove, + Gain + } + + private enum StatusChangeResult + { + NoChange, + Change + } + + private struct CharactorStatusInfo + { + public uint ObjectID; + public uint[] StatusIds; + public int NoUpdateCount; // Changed to int, and object will be removed after 10 cycles without updates + } + + private struct CharactorStatusDiffResult + { + public uint StatusId; + public StatusChangeType ChangeType; + } + #endregion + + public override HashSet? ValidTerritories => null; + + private Dictionary _charactorStatusInfos = new Dictionary(); + + #region public + public override void OnUpdate() + { + // Increment NoUpdateCount for all objects + IncrementNoUpdateCount(); + + // Loop through all objects in Svc.Objects + foreach(var gameObject in Svc.Objects) + { + // Check if the object can be cast to IBattleChara + if(gameObject is IBattleChara battleChara) + { + var objectID = battleChara.EntityId; + + // Reset the counter if the object exists + if(_charactorStatusInfos.TryGetValue(objectID, out var statusInfo)) + { + statusInfo.NoUpdateCount = 0; // Reset the counter as the object is confirmed to exist + _charactorStatusInfos[objectID] = statusInfo; + } + + var statuses = battleChara.StatusList; + + // Compare the current and previous status lists + CompareStatusList(objectID, statuses, out var changeStatuses); + + // Log changes, including gameObject.Name + LogChanges(gameObject, changeStatuses); + + // Save the current status list + CopyStatusList(objectID, statuses); + } + } + + // Remove objects that have not been updated for 10 cycles + RemoveInactiveObjects(); + } + + public override void OnSettingsDraw() + { + ImGui.Text($"Active objects: {_charactorStatusInfos.Count}"); + } + #endregion + + #region private + private void IncrementNoUpdateCount() + { + // Increment NoUpdateCount for all objects + foreach(var key in _charactorStatusInfos.Keys) + { + var statusInfo = _charactorStatusInfos[key]; + statusInfo.NoUpdateCount++; + _charactorStatusInfos[key] = statusInfo; + } + } + + private void RemoveInactiveObjects() + { + // Add objects with NoUpdateCount >= 10 to the removal list + List toRemove = new List(); + foreach(var kvp in _charactorStatusInfos) + { + if(kvp.Value.NoUpdateCount >= 10) + { + toRemove.Add(kvp.Key); + } + } + + // Actually remove the objects + foreach(var objectID in toRemove) + { + _charactorStatusInfos.Remove(objectID); + PluginLog.Information($"Removed object with ID: {objectID} (inactive for 10 cycles)"); + } + } + + private void CopyStatusList(uint objectID, StatusList statuses) + { + var newStatusIds = GetStatusIds(statuses); + + if(_charactorStatusInfos.TryGetValue(objectID, out var existingInfo)) + { + if(!ArraysEqual(existingInfo.StatusIds, newStatusIds)) + { + _charactorStatusInfos[objectID] = new CharactorStatusInfo + { + ObjectID = objectID, + StatusIds = newStatusIds, + NoUpdateCount = 0 // Reset the counter as it has been updated + }; + } + } + else + { + _charactorStatusInfos.Add(objectID, new CharactorStatusInfo + { + ObjectID = objectID, + StatusIds = newStatusIds, + NoUpdateCount = 0 // Set the counter to 0 when adding a new object + }); + } + } + + private StatusChangeResult CompareStatusList(uint objectID, StatusList statuses, out List changeStatuses) + { + changeStatuses = new List(); + + if(!_charactorStatusInfos.TryGetValue(objectID, out var existingInfo)) + { + return StatusChangeResult.NoChange; + } + + var currentStatusIds = GetStatusIds(statuses); + + CheckGains(currentStatusIds, existingInfo.StatusIds, changeStatuses); + CheckRemovals(currentStatusIds, existingInfo.StatusIds, changeStatuses); + + return changeStatuses.Count > 0 ? StatusChangeResult.Change : StatusChangeResult.NoChange; + } + + private uint[] GetStatusIds(StatusList statuses) + { + var statusIds = new uint[statuses.Length]; + for(int i = 0; i < statuses.Length; i++) + { + statusIds[i] = statuses[i]?.StatusId ?? 0; + } + return statusIds; + } + + private void CheckGains(uint[] currentStatusIds, uint[] oldStatusIds, List changeStatuses) + { + for(int i = 0; i < currentStatusIds.Length; i++) + { + if(System.Array.IndexOf(oldStatusIds, currentStatusIds[i]) < 0) + { + changeStatuses.Add(new CharactorStatusDiffResult + { + StatusId = currentStatusIds[i], + ChangeType = StatusChangeType.Gain + }); + } + } + } + + private void CheckRemovals(uint[] currentStatusIds, uint[] oldStatusIds, List changeStatuses) + { + for(int i = 0; i < oldStatusIds.Length; i++) + { + if(System.Array.IndexOf(currentStatusIds, oldStatusIds[i]) < 0) + { + changeStatuses.Add(new CharactorStatusDiffResult + { + StatusId = oldStatusIds[i], + ChangeType = StatusChangeType.Remove + }); + } + } + } + + private bool ArraysEqual(uint[] array1, uint[] array2) + { + if(array1.Length != array2.Length) + return false; + + for(int i = 0; i < array1.Length; i++) + { + if(array1[i] != array2[i]) + return false; + } + return true; + } + + // Updated LogChanges method + private void LogChanges(IGameObject gameObject, List changeStatuses) + { + List gainStatusIds = new List(); + List removeStatusIds = new List(); + + foreach(var changeStatus in changeStatuses) + { + switch(changeStatus.ChangeType) + { + case StatusChangeType.Gain: + gainStatusIds.Add(changeStatus.StatusId); + break; + case StatusChangeType.Remove: + removeStatusIds.Add(changeStatus.StatusId); + break; + } + } + + if(gainStatusIds.Count > 0) + { + PluginLog.Information($"[{gameObject.Name}({gameObject.EntityId})] Gained statuses: {string.Join(", ", gainStatusIds)}"); + } + + if(removeStatusIds.Count > 0) + { + PluginLog.Information($"[{gameObject.Name}({gameObject.EntityId})] Removed statuses: {string.Join(", ", removeStatusIds)}"); + } + } + #endregion +} diff --git a/SplatoonScripts/update.csv b/SplatoonScripts/update.csv index abe4cde8..63d43f97 100644 --- a/SplatoonScripts/update.csv +++ b/SplatoonScripts/update.csv @@ -1,67 +1,67 @@ -SplatoonScriptsOfficial.Tests@GenericTest6,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Tests/GenericTest6.cs -SplatoonScriptsOfficial.Tests@DMParser,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Tests/DMParser.cs -SplatoonScriptsOfficial.Duties.Endwalker@Aloalo_Bombs,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/Aloalo Bombs.cs -SplatoonScriptsOfficial.Duties.Endwalker@DSR_Wrath,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/DSR Wrath.cs -SplatoonScriptsOfficial.Duties.Endwalker@DSR_Dooms,5,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/DSR Dooms.cs -SplatoonScriptsOfficial.Duties.Endwalker@P10S_Tethers,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P10S Tethers.cs +SplatoonScriptsOfficial.Generic@ScriptEventLogger,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ScriptEventLogger.cs +SplatoonScriptsOfficial.Generic@CastExplorer,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/CastExplorer.cs +SplatoonScriptsOfficial.Generic@AutoFateSync,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/AutoFateSync.cs +SplatoonScriptsOfficial.Generic@ActReminder,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ActReminder.cs +SplatoonScriptsOfficial.Generic@ShowTooltipOnKey,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ShowTooltipOnKey.cs +SplatoonScriptsOfficial.Generic@ShowEmote,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ShowEmote.cs +SplatoonScriptsOfficial.Generic@PluginInstallerWindowCollapsible,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/PluginInstallerWindowCollapsible.cs +SplatoonScriptsOfficial.Generic@ZoneNameToast,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ZoneNameToast.cs +SplatoonScriptsOfficial.Generic@ForceSetDirection,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ForceSetDirection.cs +SplatoonScriptsOfficial.Generic@QuestHighlighter,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/QuestHighlighter.cs SplatoonScriptsOfficial.Duties.Endwalker@P12S_Limit_Cut,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P12S Limit Cut.cs -SplatoonScriptsOfficial.Duties.Endwalker@DSR_Towers,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/DSR Towers.cs -SplatoonScriptsOfficial.Duties.Endwalker@P10S_Debuffs,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P10S Debuffs.cs -SplatoonScriptsOfficial.Duties.Endwalker@P9S_Dualspell_InOut,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P9S Dualspell InOut.cs SplatoonScriptsOfficial.Duties.Endwalker@P12S_Wing_Cleaves,6,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P12S Wing Cleaves.cs -SplatoonScriptsOfficial.Duties.Endwalker@P11S_Multiscript,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P11S Multiscript.cs +SplatoonScriptsOfficial.Duties.Endwalker@DSR_Dooms,5,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/DSR Dooms.cs +SplatoonScriptsOfficial.Duties.Endwalker@P8S2_Dominion,8,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P8S2 Dominion.cs SplatoonScriptsOfficial.Duties.Endwalker@P8S2_Limitless_Desolation,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P8S2 Limitless Desolation.cs -SplatoonScriptsOfficial.Duties.Endwalker@P8S2_Dancer_HC_Step,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P8S2 Dancer HC Step.cs +SplatoonScriptsOfficial.Duties.Endwalker@P9S_Dualspell_InOut,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P9S Dualspell InOut.cs SplatoonScriptsOfficial.Duties.Endwalker@P12S_Tethers,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P12S Tethers.cs -SplatoonScriptsOfficial.Duties.Endwalker@P12S_Superchain,7,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P12S Superchain.cs +SplatoonScriptsOfficial.Duties.Endwalker@Aloalo_Bombs,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/Aloalo Bombs.cs SplatoonScriptsOfficial.Duties.Endwalker@P12S_Pangenesis,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P12S Pangenesis.cs -SplatoonScriptsOfficial.Duties.Endwalker@DSR_P6_Cauterize_Unsafe,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/DSR_P6_Cauterize_Unsafe.cs +SplatoonScriptsOfficial.Duties.Endwalker@DSR_Towers,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/DSR Towers.cs +SplatoonScriptsOfficial.Duties.Endwalker@DSR_Wrath,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/DSR Wrath.cs SplatoonScriptsOfficial.Duties.Endwalker@P9S_JP_LC_Strat,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P9S JP LC Strat.cs -SplatoonScriptsOfficial.Duties.Endwalker@P12S_Caloric_Theory,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P12S Caloric Theory.cs -SplatoonScriptsOfficial.Duties.Endwalker@P8S2_Dominion,8,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P8S2 Dominion.cs +SplatoonScriptsOfficial.Duties.Endwalker@P8S2_Dancer_HC_Step,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P8S2 Dancer HC Step.cs +SplatoonScriptsOfficial.Duties.Endwalker@P11S_Multiscript,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P11S Multiscript.cs +SplatoonScriptsOfficial.Duties.Endwalker@P10S_Debuffs,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P10S Debuffs.cs SplatoonScriptsOfficial.Duties.Endwalker@P12S_Classical_Concepts,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P12S Classical Concepts.cs -SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@BSOD_Adjuster,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/BSOD Adjuster.cs +SplatoonScriptsOfficial.Duties.Endwalker@P12S_Caloric_Theory,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P12S Caloric Theory.cs +SplatoonScriptsOfficial.Duties.Endwalker@DSR_P6_Cauterize_Unsafe,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/DSR_P6_Cauterize_Unsafe.cs +SplatoonScriptsOfficial.Duties.Endwalker@P12S_Superchain,7,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P12S Superchain.cs +SplatoonScriptsOfficial.Duties.Endwalker@P10S_Tethers,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/P10S Tethers.cs +SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Hello_Near_Far_World,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Hello Near Far World.cs SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@MF_Target_Enforcer,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/MF Target Enforcer.cs -SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Party_Synergy,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Party Synergy.cs -SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Limitless_Synergy,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Limitless Synergy.cs +SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Program_Loop,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Program Loop.cs +SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@BSOD_Adjuster,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/BSOD Adjuster.cs SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Exasquares,5,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Exasquares.cs -SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Pantokrator_invincible_Reminder,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Pantokrator invincible Reminder.cs -SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Oversampled_Wave_Cannon,7,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Oversampled Wave Cannon.cs -SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Beyond_Defense,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Beyond Defense.cs SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Pantokrator,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Pantokrator.cs SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Hello_World,9,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Hello World.cs -SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Dynamis_Delta,9,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Dynamis Delta.cs -SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Hello_Near_Far_World,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Hello Near Far World.cs SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Dynamis_Sigma,7,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Dynamis Sigma.cs -SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Program_Loop,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Program Loop.cs +SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Beyond_Defense,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Beyond Defense.cs SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Hello_World_MoveGuide,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Hello World MoveGuide.cs +SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Oversampled_Wave_Cannon,7,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Oversampled Wave Cannon.cs +SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Limitless_Synergy,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Limitless Synergy.cs +SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Party_Synergy,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Party Synergy.cs +SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Pantokrator_invincible_Reminder,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Pantokrator invincible Reminder.cs +SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Dynamis_Delta,9,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Dynamis Delta.cs +SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P2_Transition,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA_P2_Transition.cs SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P3_Wormhole_Formation,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA P3 Wormhole Formation.cs -SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P2_1211_Transition,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA P2 1211 Transition.cs SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P2_Nisi,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA P2 Nisi.cs +SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P2_1211_Transition,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA P2 1211 Transition.cs SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P2_Temporal_Stasis,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA P2 Temporal Stasis.cs -SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P2_Transition,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA_P2_Transition.cs -SplatoonScriptsOfficial.Duties.Dawntrail@EX2_Projection_of_Triumph,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/EX2 Projection of Triumph.cs -SplatoonScriptsOfficial.Duties.Dawntrail@R1S_Protean_Highlight,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R1S Protean Highlight.cs -SplatoonScriptsOfficial.Duties.Dawntrail@R1S_Raining_Cats,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R1S Raining Cats.cs -SplatoonScriptsOfficial.Duties.Dawntrail@R4S_Sunrise_Sabbath,5,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R4S Sunrise Sabbath.cs -SplatoonScriptsOfficial.Duties.Dawntrail@R1S_Multiscript,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R1S Multiscript.cs +SplatoonScriptsOfficial.Duties.Stormblood@UCOB_Twisters,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Stormblood/UCOB Twisters.cs +SplatoonScriptsOfficial.Duties.Stormblood@UCOB_Heavensfall_Trio_Towers,6,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Stormblood/UCOB Heavensfall Trio Towers.cs +SplatoonScriptsOfficial.Duties.Stormblood@UCOB_Tethers,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Stormblood/UCOB Tethers.cs +SplatoonScriptsOfficial.Duties.Stormblood@UCOB_dragon_baits,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Stormblood/UCOB dragon baits.cs SplatoonScriptsOfficial.Duties.Dawntrail@R4S_Midnight_Sabbath,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R4S Midnight Sabbath.cs SplatoonScriptsOfficial.Duties.Dawntrail@R4S_Witch_Hunt,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R4S Witch Hunt.cs +SplatoonScriptsOfficial.Duties.Dawntrail@R4S_Chain_Lightning,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R4S Chain Lightning.cs +SplatoonScriptsOfficial.Duties.Dawntrail@R1S_Raining_Cats,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R1S Raining Cats.cs +SplatoonScriptsOfficial.Duties.Dawntrail@R1S_Multiscript,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R1S Multiscript.cs +SplatoonScriptsOfficial.Duties.Dawntrail@EX2_Projection_of_Triumph,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/EX2 Projection of Triumph.cs SplatoonScriptsOfficial.Duties.Dawntrail@R2S_Venom_Love_Pair_Split,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R2S Venom Love Pair Split.cs +SplatoonScriptsOfficial.Duties.Dawntrail@R1S_Protean_Highlight,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R1S Protean Highlight.cs SplatoonScriptsOfficial.Duties.Dawntrail@R4S_Unsafe_Cannon,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R4S Unsafe Cannon.cs SplatoonScriptsOfficial.Duties.Dawntrail@R4S_Electrope_Edge,9,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R4S Electrope Edge.cs -SplatoonScriptsOfficial.Duties.Dawntrail@R4S_Chain_Lightning,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R4S Chain Lightning.cs -SplatoonScriptsOfficial.Duties.Stormblood@UCOB_Twisters,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Stormblood/UCOB Twisters.cs -SplatoonScriptsOfficial.Duties.Stormblood@UCOB_Tethers,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Stormblood/UCOB Tethers.cs -SplatoonScriptsOfficial.Duties.Stormblood@UCOB_Heavensfall_Trio_Towers,6,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Stormblood/UCOB Heavensfall Trio Towers.cs -SplatoonScriptsOfficial.Duties.Stormblood@UCOB_dragon_baits,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Stormblood/UCOB dragon baits.cs -SplatoonScriptsOfficial.Generic@ScriptEventLogger,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ScriptEventLogger.cs -SplatoonScriptsOfficial.Generic@ShowTooltipOnKey,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ShowTooltipOnKey.cs -SplatoonScriptsOfficial.Generic@ForceSetDirection,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ForceSetDirection.cs -SplatoonScriptsOfficial.Generic@ShowEmote,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ShowEmote.cs -SplatoonScriptsOfficial.Generic@AutoFateSync,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/AutoFateSync.cs -SplatoonScriptsOfficial.Generic@QuestHighlighter,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/QuestHighlighter.cs -SplatoonScriptsOfficial.Generic@PluginInstallerWindowCollapsible,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/PluginInstallerWindowCollapsible.cs -SplatoonScriptsOfficial.Generic@ZoneNameToast,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ZoneNameToast.cs -SplatoonScriptsOfficial.Generic@ActReminder,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/ActReminder.cs -SplatoonScriptsOfficial.Generic@CastExplorer,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Generic/CastExplorer.cs \ No newline at end of file +SplatoonScriptsOfficial.Duties.Dawntrail@R4S_Sunrise_Sabbath,5,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R4S Sunrise Sabbath.cs +SplatoonScriptsOfficial.Tests@DMParser,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Tests/DMParser.cs +SplatoonScriptsOfficial.Tests@GenericTest6,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Tests/GenericTest6.cs \ No newline at end of file