From 78eb50e7dfa3cd73cf6667be2cc05bc65adb0de1 Mon Sep 17 00:00:00 2001 From: keagdo Date: Tue, 11 Mar 2025 04:49:09 -0400 Subject: [PATCH 1/5] Add rule for applying by current race.... Users must go through additional popup window warning about possible infinite loops before Cond_Race is enabled. --- DynamicBridge/Checkers/Races.cs | 21 +++++++ DynamicBridge/Configuration/ApplyRule.cs | 2 + DynamicBridge/Configuration/Config.cs | 3 +- DynamicBridge/DynamicBridge.cs | 10 ++++ DynamicBridge/Gui/GuiRules.cs | 31 +++++++++- DynamicBridge/Gui/GuiSettings.cs | 57 +++++++++++++++++++ .../IPC/Glamourer/CursedActionManager.cs | 30 ++++++++++ .../IPC/Glamourer/GlamourerManager.cs | 14 +++++ 8 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 DynamicBridge/Checkers/Races.cs create mode 100644 DynamicBridge/IPC/Glamourer/CursedActionManager.cs diff --git a/DynamicBridge/Checkers/Races.cs b/DynamicBridge/Checkers/Races.cs new file mode 100644 index 0000000..c558c83 --- /dev/null +++ b/DynamicBridge/Checkers/Races.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DynamicBridge.Core +{ + public enum Races + { + No_Race = 0, + Hyur = 1, + Elezen = 2, + Lalafell = 3, + Miqote = 4, + Roegadyn = 5, + Aura = 6, + Hrothgar = 7, + Viera = 8, + } +} diff --git a/DynamicBridge/Configuration/ApplyRule.cs b/DynamicBridge/Configuration/ApplyRule.cs index 0ba1e0b..f43d43f 100644 --- a/DynamicBridge/Configuration/ApplyRule.cs +++ b/DynamicBridge/Configuration/ApplyRule.cs @@ -25,6 +25,7 @@ public class ApplyRule public List Times = []; public List Worlds = []; public List Gearsets = []; + public List Races = []; public List SelectedPresets = []; public bool Passthrough = false; @@ -44,6 +45,7 @@ public class NotConditions public List Times = []; public List Worlds = []; public List Gearsets = []; + public List Races = []; } } } diff --git a/DynamicBridge/Configuration/Config.cs b/DynamicBridge/Configuration/Config.cs index 5f237fe..757f7ce 100644 --- a/DynamicBridge/Configuration/Config.cs +++ b/DynamicBridge/Configuration/Config.cs @@ -47,7 +47,8 @@ public class Config : IEzConfig public bool Cond_Job = true; public bool Cond_World = false; public bool Cond_Gearset = false; - + public bool Cond_Race = false; + public bool Cond_Race_Bonus = false; public Dictionary> GearsetNameCacheCID = []; public string CensorSeed = Guid.NewGuid().ToString(); diff --git a/DynamicBridge/DynamicBridge.cs b/DynamicBridge/DynamicBridge.cs index bf5d51a..b31dc48 100644 --- a/DynamicBridge/DynamicBridge.cs +++ b/DynamicBridge/DynamicBridge.cs @@ -20,6 +20,7 @@ using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using Glamourer.Api.Enums; using System.IO; using System.IO.Compression; using System.Linq; @@ -90,6 +91,7 @@ public DynamicBridge(IDalamudPluginInterface pi) new EzFrameworkUpdate(OnUpdate); new EzLogout(Logout); new EzTerritoryChanged(TerritoryChanged); + new EzStateChanged(StateChanged); TaskManager = new() { TimeLimitMS = 2000, @@ -298,6 +300,9 @@ private void OnUpdate() && (!C.Cond_Gearset || ((x.Gearsets.Count == 0 || x.Gearsets.Contains(RaptureGearsetModule.Instance()->CurrentGearsetIndex)) && (!C.AllowNegativeConditions || !x.Not.Gearsets.Contains(RaptureGearsetModule.Instance()->CurrentGearsetIndex)))) + && + (!C.Cond_Race || ((x.Races.Count == 0 || x.Races.Any(s => s == (Races)(int)GlamourerManager.GetMyState()["Customize"]["Race"]["Value"])) + && (!C.AllowNegativeConditions || !x.Not.Races.Any(s => s == (Races)(int)GlamourerManager.GetMyState()["Customize"]["Race"]["Value"])))) ) { newRule.Add(x); @@ -478,6 +483,11 @@ private void TerritoryChanged(ushort id) SoftForceUpdate = true; } + private void StateChanged(nint objectId, StateChangeType changeType) + { + // PluginLog.Debug($"STATE CHANGED!!!!! Type: {changeType}"); + } + private void ApplyPresetPenumbra(Preset preset, ref bool DoNullPenumbra) { if(preset.PenumbraType == SpecialPenumbraAssignment.Remove_Individual_Assignment) diff --git a/DynamicBridge/Gui/GuiRules.cs b/DynamicBridge/Gui/GuiRules.cs index 4c7361a..2744814 100644 --- a/DynamicBridge/Gui/GuiRules.cs +++ b/DynamicBridge/Gui/GuiRules.cs @@ -23,7 +23,7 @@ public static unsafe class GuiRules { private static Vector2 iconSize => new(24f); - private static string[] Filters = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]; + private static string[] Filters = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]; private static bool[] OnlySelected = new bool[20]; private static string CurrentDrag = ""; @@ -78,6 +78,7 @@ void ButtonsRight() C.Cond_World, C.Cond_Zone, C.Cond_ZoneGroup, + C.Cond_Race, ]; List<(Vector2 RowPos, Vector2 ButtonPos, Action BeginDraw, Action AcceptDraw)> MoveCommands = []; @@ -97,6 +98,7 @@ void ButtonsRight() if(C.Cond_Job) ImGui.TableSetupColumn("Job"); if(C.Cond_World) ImGui.TableSetupColumn("World"); if(C.Cond_Gearset) ImGui.TableSetupColumn("Gearset"); + if(C.Cond_Race) ImGui.TableSetupColumn("Race"); ImGui.TableSetupColumn("Preset"); ImGui.TableSetupColumn(" ", ImGuiTableColumnFlags.NoResize | ImGuiTableColumnFlags.WidthFixed); ImGui.TableHeadersRow(); @@ -514,6 +516,33 @@ void FiltersSelection() } filterCnt++; + if(C.Cond_Race) + { + ImGui.TableNextColumn(); + //Race + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if(ImGui.BeginCombo("##race", rule.Races.PrintRange(rule.Not.Races, out var fullList), C.ComboSize)) + { + FiltersSelection(); + foreach(var cond in Enum.GetValues()) + { + if(cond == Races.No_Race) continue; + var name = cond.ToString().Replace("_", " "); + if(Filters[filterCnt].Length > 0 && !name.Contains(Filters[filterCnt], StringComparison.OrdinalIgnoreCase)) continue; + if(OnlySelected[filterCnt] && !rule.Races.Contains(cond)) continue; + // if(ThreadLoadImageHandler.TryGetTextureWrap(Path.Combine(Svc.PluginInterface.AssemblyLocation.DirectoryName, "res", "race", $"{(int)cond}.png"), out var texture)) + // { + // ImGui.Image(texture.ImGuiHandle, iconSize); + // ImGui.SameLine(); + // } + DrawSelector(name, cond, rule.Races, rule.Not.Races); + } + ImGui.EndCombo(); + } + if(fullList != null) ImGuiEx.Tooltip(UI.AnyNotice + fullList); + } + filterCnt++; + ImGui.TableNextColumn(); { diff --git a/DynamicBridge/Gui/GuiSettings.cs b/DynamicBridge/Gui/GuiSettings.cs index 01e0a1a..0543ad4 100644 --- a/DynamicBridge/Gui/GuiSettings.cs +++ b/DynamicBridge/Gui/GuiSettings.cs @@ -85,10 +85,67 @@ public static void Draw() () => ImGui.Checkbox($"Job", ref C.Cond_Job), () => ImGui.Checkbox($"World", ref C.Cond_World), () => ImGui.Checkbox($"Gearset", ref C.Cond_Gearset), + () => ImGui.Checkbox($"Current Race", ref C.Cond_Race_Bonus), ], (int)(ImGui.GetContentRegionAvail().X / 180f), ImGuiTableFlags.BordersInner); ImGuiGroup.EndGroupBox(); } + bool Cond_Race_Bonus_Window = C.Cond_Race_Bonus && !C.Cond_Race; + if (Cond_Race_Bonus_Window) + { + // Measure the longest text line + Vector2 textSize1 = ImGui.CalcTextSize("ARE YOU SURE YOU WANT TO ENABLE THE RACE CONDITION FOR RULES?"); + Vector2 textSize2 = ImGui.CalcTextSize("It is VERY easy to be stuck in infinite loops with this condition"); + Vector2 textSize3 = ImGui.CalcTextSize("By selecting YES, I yeild my right to ask for help if I can't fix the loop"); + float width = Math.Max(textSize1.X, Math.Max(textSize2.X, textSize3.X)) + 40; + float height = textSize1.Y + textSize2.Y + textSize3.Y + textSize3.Y + 100; + + ImGui.SetNextWindowSize(new Vector2(width, height), ImGuiCond.Always); + + if (ImGui.Begin("ARE YOU SURE", ref Cond_Race_Bonus_Window, ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize)) + { + float windowWidth = ImGui.GetWindowSize().X; + + float time = (float)ImGui.GetTime(); + bool flash = (int)(time * 2) % 2 == 0; + Vector4 textColor = flash ? new Vector4(1f, 0f, 0f, 1f) : new Vector4(1f, 1f, 1f, 1f); + + ImGui.SetCursorPosX((windowWidth - textSize1.X) * 0.5f); + ImGui.TextColored(textColor, "ARE YOU SURE YOU WANT TO ENABLE THE RACE CONDITION FOR RULES?"); + + ImGui.SetCursorPosX((windowWidth - textSize2.X) * 0.5f); + ImGui.Text("It is VERY easy to be stuck in infinite loops with this condition"); + + ImGui.SetCursorPosX((windowWidth - textSize3.X) * 0.5f); + ImGui.Text("By selecting YES, I yeild my right to ask for help if I can't fix out the loop"); + + ImGui.NewLine(); + + float buttonWidth = 80f; + float buttonSpacing = 10f; + float totalButtonWidth = (buttonWidth * 2) + buttonSpacing; + + ImGui.SetCursorPosX((windowWidth - totalButtonWidth) * 0.5f); + if (ImGui.Button("NO", new Vector2(buttonWidth, 30))) + { + C.Cond_Race_Bonus = false; + C.Cond_Race = false; + Cond_Race_Bonus_Window = false; + } + + ImGui.SameLine(); + + if (ImGui.Button("YES", new Vector2(buttonWidth, 30))) + { + C.Cond_Race_Bonus = true; + C.Cond_Race = true; + Cond_Race_Bonus_Window = false; + } + } + ImGui.End(); + } + + if(!C.Cond_Race_Bonus){C.Cond_Race=false;} if(ImGuiGroup.BeginGroupBox("Integrations")) { diff --git a/DynamicBridge/IPC/Glamourer/CursedActionManager.cs b/DynamicBridge/IPC/Glamourer/CursedActionManager.cs new file mode 100644 index 0000000..1cf5cf5 --- /dev/null +++ b/DynamicBridge/IPC/Glamourer/CursedActionManager.cs @@ -0,0 +1,30 @@ +using ECommons.DalamudServices; +using Glamourer.Api.Enums; +using Glamourer.Api.Helpers; +using Glamourer.Api.IpcSubscribers; +using SharpDX.Win32; +using System; +using System.Collections.Generic; + +//I have no idea what I'm doing, I'm just stealing code from everwhere.... But either way, I don't think this is actually very useful. Too many Customize changes. +public class EzStateChanged : IDisposable +{ + internal static List Registered = []; + internal EventSubscriber Subscriber; + + public EzStateChanged(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + // Create the subscriber for StateChangedWithType + Subscriber = StateChangedWithType.Subscriber(Svc.PluginInterface, handler); + + // Register instance + Registered.Add(this); + } + + public void Dispose() + { + Subscriber.Dispose(); + Registered.Remove(this); + } +} diff --git a/DynamicBridge/IPC/Glamourer/GlamourerManager.cs b/DynamicBridge/IPC/Glamourer/GlamourerManager.cs index 943147c..2823a64 100644 --- a/DynamicBridge/IPC/Glamourer/GlamourerManager.cs +++ b/DynamicBridge/IPC/Glamourer/GlamourerManager.cs @@ -72,6 +72,20 @@ public string GetMyCustomization() } } + private GetState GetState = new(Svc.PluginInterface); + public Newtonsoft.Json.Linq.JObject GetMyState() + { + try + { + return GetState.Invoke(0).Item2; + } + catch(Exception e) + { + e.Log(); + return null; + } + } + private ApplyState ApplyState = new(Svc.PluginInterface); public void SetMyCustomization(string customization) { From ea260a22f0cc7484cd4a92be234a9ef21528e32b Mon Sep 17 00:00:00 2001 From: keagdo Date: Sat, 5 Apr 2025 05:46:27 -0400 Subject: [PATCH 2/5] Fix Infinte Looping for race rule. Similar method can be used for other glamourer value detections. --- DynamicBridge/Checkers/Races.cs | 2 +- DynamicBridge/Configuration/ApplyRule.cs | 1 + DynamicBridge/DynamicBridge.cs | 73 ++++++++++++++++++++++++ DynamicBridge/Gui/GuiRules.cs | 16 +++--- 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/DynamicBridge/Checkers/Races.cs b/DynamicBridge/Checkers/Races.cs index c558c83..2f6c0de 100644 --- a/DynamicBridge/Checkers/Races.cs +++ b/DynamicBridge/Checkers/Races.cs @@ -14,7 +14,7 @@ public enum Races Lalafell = 3, Miqote = 4, Roegadyn = 5, - Aura = 6, + Au_ra = 6, Hrothgar = 7, Viera = 8, } diff --git a/DynamicBridge/Configuration/ApplyRule.cs b/DynamicBridge/Configuration/ApplyRule.cs index 663bf3e..b56ac67 100644 --- a/DynamicBridge/Configuration/ApplyRule.cs +++ b/DynamicBridge/Configuration/ApplyRule.cs @@ -30,6 +30,7 @@ public class ApplyRule public List Players = []; public List SelectedPresets = []; public bool Passthrough = false; + public bool valid = true; public NotConditions Not = new(); [Serializable] diff --git a/DynamicBridge/DynamicBridge.cs b/DynamicBridge/DynamicBridge.cs index d231de8..882b750 100644 --- a/DynamicBridge/DynamicBridge.cs +++ b/DynamicBridge/DynamicBridge.cs @@ -317,6 +317,14 @@ private void OnUpdate() { foreach(var x in profile.Rules) { + if (C.Cond_Race) + { + x.valid = CheckValidRace(profile.Presets, x, "Customize,Race"); + if (x.Enabled) + { + x.Enabled = x.valid; + } + } if( x.Enabled && @@ -673,4 +681,69 @@ public void Dispose() P = null; C = null; } + + private static bool CheckValidRace(List presets, ApplyRule rule, string path) + { + bool valid = true; + List racesToCheck = rule.Races; + List notRacesToCheck = rule.Not.Races; + foreach(string preset_name in rule.SelectedPresets) + { + foreach(Preset preset in presets) + { + if(preset_name == preset.Name) + { + foreach(string design_name in preset.Glamourer) + { + var design = (GlamourerDesignInfo)Utils.GetDesignByGUID(design_name); + var guid = design.Identifier; + string targetFile = Path.Combine(Svc.PluginInterface.ConfigDirectory.Parent!.FullName,"Glamourer","Designs",$"{guid}.json"); + if (File.Exists(targetFile)) + { + string jsonContent = File.ReadAllText(targetFile); + + var designObject = Newtonsoft.Json.Linq.JObject.Parse(jsonContent); + Newtonsoft.Json.Linq.JToken current = designObject; + string[] keys = path.Split(','); + foreach (var key in keys) + { + current = current?[key]; + } + Races design_race = (Races)(int)current["Value"]; + + bool applied = false; + + foreach (var property in ((Newtonsoft.Json.Linq.JObject)designObject[keys[0]]).Properties()) + { + if(property.Name != "ModelId" && property.Name != "BodyType") + { + if ((string)designObject[keys[0]][property.Name]["Apply"] == "True") + { + applied = true; + break; + } + } + } + + if (applied && !racesToCheck.Contains(design_race)) + { + valid = false; + } + if (applied && notRacesToCheck.Contains(design_race)) + { + valid = false; + } + PluginLog.Information($"Valid: {valid} | Design: {design.Name} | Race: {design_race} | Applied: {applied} | racesToCheck: {racesToCheck.FirstOrNull()},{!racesToCheck.Contains(design_race)} | notRacesToCheck: {notRacesToCheck.FirstOrNull()},{notRacesToCheck.Contains(design_race)} | Guid: {guid}"); + } + else + { + PluginLog.Warning($"Design file '{guid}.json' not found."); + } + } + break; + } + } + } + return valid; + } } diff --git a/DynamicBridge/Gui/GuiRules.cs b/DynamicBridge/Gui/GuiRules.cs index b975c3a..f8e56d7 100644 --- a/DynamicBridge/Gui/GuiRules.cs +++ b/DynamicBridge/Gui/GuiRules.cs @@ -135,7 +135,14 @@ void FiltersSelection() //Sorting var rowPos = ImGui.GetCursorPos(); ImGui.Checkbox("##enable", ref rule.Enabled); - ImGuiEx.Tooltip("Enable this rule"); + if (!rule.valid) + { + ImGuiEx.Tooltip("There is a race/preset conflict, to enable this rule, please resolve."); + } + else + { + ImGuiEx.Tooltip("Enable this rule"); + } ImGui.SameLine(); ImGui.PushFont(UiBuilder.IconFont); @@ -529,14 +536,9 @@ void FiltersSelection() foreach(var cond in Enum.GetValues()) { if(cond == Races.No_Race) continue; - var name = cond.ToString().Replace("_", " "); + var name = cond.ToString().Replace("_", "'"); if(Filters[filterCnt].Length > 0 && !name.Contains(Filters[filterCnt], StringComparison.OrdinalIgnoreCase)) continue; if(OnlySelected[filterCnt] && !rule.Races.Contains(cond)) continue; - // if(ThreadLoadImageHandler.TryGetTextureWrap(Path.Combine(Svc.PluginInterface.AssemblyLocation.DirectoryName, "res", "race", $"{(int)cond}.png"), out var texture)) - // { - // ImGui.Image(texture.ImGuiHandle, iconSize); - // ImGui.SameLine(); - // } DrawSelector(name, cond, rule.Races, rule.Not.Races); } ImGui.EndCombo(); From acd2206f4645caa61c66e4ccf44bc4bd0fa97e21 Mon Sep 17 00:00:00 2001 From: keagdo Date: Sat, 5 Apr 2025 15:20:51 -0400 Subject: [PATCH 3/5] Woops. Left a debug print in there that spams the log. --- DynamicBridge/DynamicBridge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DynamicBridge/DynamicBridge.cs b/DynamicBridge/DynamicBridge.cs index 882b750..2b90174 100644 --- a/DynamicBridge/DynamicBridge.cs +++ b/DynamicBridge/DynamicBridge.cs @@ -733,7 +733,7 @@ private static bool CheckValidRace(List presets, ApplyRule rule, string { valid = false; } - PluginLog.Information($"Valid: {valid} | Design: {design.Name} | Race: {design_race} | Applied: {applied} | racesToCheck: {racesToCheck.FirstOrNull()},{!racesToCheck.Contains(design_race)} | notRacesToCheck: {notRacesToCheck.FirstOrNull()},{notRacesToCheck.Contains(design_race)} | Guid: {guid}"); + // PluginLog.Information($"Valid: {valid} | Design: {design.Name} | Race: {design_race} | Applied: {applied} | racesToCheck: {racesToCheck.FirstOrNull()},{!racesToCheck.Contains(design_race)} | notRacesToCheck: {notRacesToCheck.FirstOrNull()},{notRacesToCheck.Contains(design_race)} | Guid: {guid}"); } else { From f687c9b17ccc07057cc234e747799e631f17d9bd Mon Sep 17 00:00:00 2001 From: keagdo Date: Sun, 6 Apr 2025 00:53:46 -0400 Subject: [PATCH 4/5] Quick Bug Fix --- DynamicBridge/DynamicBridge.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DynamicBridge/DynamicBridge.cs b/DynamicBridge/DynamicBridge.cs index 2b90174..26c9b90 100644 --- a/DynamicBridge/DynamicBridge.cs +++ b/DynamicBridge/DynamicBridge.cs @@ -687,6 +687,8 @@ private static bool CheckValidRace(List presets, ApplyRule rule, string bool valid = true; List racesToCheck = rule.Races; List notRacesToCheck = rule.Not.Races; + if (racesToCheck.Count + notRacesToCheck.Count == 0) + return true; foreach(string preset_name in rule.SelectedPresets) { foreach(Preset preset in presets) @@ -725,7 +727,7 @@ private static bool CheckValidRace(List presets, ApplyRule rule, string } } - if (applied && !racesToCheck.Contains(design_race)) + if (applied && !racesToCheck.Contains(design_race) && racesToCheck.Count > 0) { valid = false; } From 3e7db6cfc62f0988eff6a712bcac45c258d34e43 Mon Sep 17 00:00:00 2001 From: keagdo Date: Sun, 6 Apr 2025 12:04:32 -0400 Subject: [PATCH 5/5] Don't need state changed event anymore --- DynamicBridge/DynamicBridge.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/DynamicBridge/DynamicBridge.cs b/DynamicBridge/DynamicBridge.cs index 26c9b90..e53aba1 100644 --- a/DynamicBridge/DynamicBridge.cs +++ b/DynamicBridge/DynamicBridge.cs @@ -94,7 +94,6 @@ public DynamicBridge(IDalamudPluginInterface pi) new EzFrameworkUpdate(OnUpdate); new EzLogout(Logout); new EzTerritoryChanged(TerritoryChanged); - new EzStateChanged(StateChanged); TaskManager = new() { TimeLimitMS = 2000, @@ -550,11 +549,6 @@ private void TerritoryChanged(ushort id) SoftForceUpdate = true; } - private void StateChanged(nint objectId, StateChangeType changeType) - { - // PluginLog.Debug($"STATE CHANGED!!!!! Type: {changeType}"); - } - private void ApplyPresetPenumbra(Preset preset, ref bool DoNullPenumbra) { if(preset.PenumbraType == SpecialPenumbraAssignment.Remove_Individual_Assignment)