diff --git a/DynamicBridge/Checkers/Races.cs b/DynamicBridge/Checkers/Races.cs new file mode 100644 index 0000000..2f6c0de --- /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, + Au_ra = 6, + Hrothgar = 7, + Viera = 8, + } +} diff --git a/DynamicBridge/Configuration/ApplyRule.cs b/DynamicBridge/Configuration/ApplyRule.cs index cf3e524..f4775f4 100644 --- a/DynamicBridge/Configuration/ApplyRule.cs +++ b/DynamicBridge/Configuration/ApplyRule.cs @@ -26,6 +26,7 @@ public class ApplyRule public List Times = []; public List Worlds = []; public List Gearsets = []; + public List Races = []; public List Players = []; public List Precise_Times = [ new TimelineSegment((float)0/24,(float)5/24,0), @@ -38,6 +39,7 @@ public class ApplyRule ]; public List SelectedPresets = []; public bool Passthrough = false; + public bool valid = true; public NotConditions Not = new(); [Serializable] @@ -54,6 +56,7 @@ public class NotConditions public List Times = []; public List Worlds = []; public List Gearsets = []; + public List Races = []; public List Players = []; } } diff --git a/DynamicBridge/Configuration/Config.cs b/DynamicBridge/Configuration/Config.cs index 5603797..8798fd5 100644 --- a/DynamicBridge/Configuration/Config.cs +++ b/DynamicBridge/Configuration/Config.cs @@ -57,6 +57,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 bool Cond_Players = false; public bool Cond_Time_Precise = false; diff --git a/DynamicBridge/DynamicBridge.cs b/DynamicBridge/DynamicBridge.cs index 1b0c00f..8bff4d4 100644 --- a/DynamicBridge/DynamicBridge.cs +++ b/DynamicBridge/DynamicBridge.cs @@ -1,4 +1,4 @@ -using DynamicBridge.Configuration; +using DynamicBridge.Configuration; using DynamicBridge.Core; using DynamicBridge.Gui; using DynamicBridge.IPC; @@ -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; @@ -315,6 +316,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 && @@ -354,7 +363,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_Players || (x.Players.Count == 0 || x.Players.Any(rp => GuiPlayers.SimpleNearbyPlayers().Any(sp => rp == sp.Name && C.selectedPlayers.Any(sel => sel.Name == sp.Name && (sel.Distance >= sp.Distance || sel.Distance >= 150f))))) + (!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"])))) + && (!C.Cond_Players || (x.Players.Count == 0 || x.Players.Any(rp => GuiPlayers.SimpleNearbyPlayers().Any(sp => rp == sp.Name && C.selectedPlayers.Any(sel => sel.Name == sp.Name && (sel.Distance >= sp.Distance || sel.Distance >= 150f))))) && (!C.AllowNegativeConditions || !x.Not.Players.Any(rp => GuiPlayers.SimpleNearbyPlayers().Any(sp => rp == sp.Name && C.selectedPlayers.Any(sel => sel.Name == sp.Name && (sel.Distance >= sp.Distance || sel.Distance >= 150f)))))) ) { @@ -667,4 +678,71 @@ 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; + if (racesToCheck.Count + notRacesToCheck.Count == 0) + return true; + 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) && racesToCheck.Count > 0) + { + 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 0bf53e2..f9badd6 100644 --- a/DynamicBridge/Gui/GuiRules.cs +++ b/DynamicBridge/Gui/GuiRules.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Components; +using Dalamud.Interface.Components; using Dalamud.Interface.Style; using DynamicBridge.Configuration; using DynamicBridge.Core; @@ -78,6 +78,7 @@ void ButtonsRight() C.Cond_World, C.Cond_Zone, C.Cond_ZoneGroup, + C.Cond_Race, C.Cond_Players, ]; @@ -98,6 +99,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"); if(C.Cond_Players) ImGui.TableSetupColumn("Players"); ImGui.TableSetupColumn("Preset"); ImGui.TableSetupColumn(" ", ImGuiTableColumnFlags.NoResize | ImGuiTableColumnFlags.WidthFixed); @@ -133,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); @@ -550,6 +559,26 @@ 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; + DrawSelector(name, cond, rule.Races, rule.Not.Races); + } + ImGui.EndCombo(); + } + if(fullList != null) ImGuiEx.Tooltip(UI.AnyNotice + fullList); + } if (C.Cond_Players) { ImGui.TableNextColumn(); diff --git a/DynamicBridge/Gui/GuiSettings.cs b/DynamicBridge/Gui/GuiSettings.cs index 64c38a2..ea23282 100644 --- a/DynamicBridge/Gui/GuiSettings.cs +++ b/DynamicBridge/Gui/GuiSettings.cs @@ -128,6 +128,7 @@ 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), () => ImGui.Checkbox($"Nearby Players", ref C.Cond_Players), ], (int)(ImGui.GetContentRegionAvail().X / 180f), ImGuiTableFlags.BordersInner); @@ -151,6 +152,62 @@ public static void Draw() } 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) {