diff --git a/FFXIV_Vibe_Plugin/App/App.cs b/FFXIV_Vibe_Plugin/App/App.cs index 893f815..f499f33 100644 --- a/FFXIV_Vibe_Plugin/App/App.cs +++ b/FFXIV_Vibe_Plugin/App/App.cs @@ -13,7 +13,7 @@ using Dalamud.Game.Network; using Dalamud.Game.Command; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Objects; -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using Dalamud.Interface.Windowing; // FFXIV_Vibe_Plugin libs @@ -35,10 +35,10 @@ namespace FFXIV_Vibe_Plugin { private ObjectTable GameObjects { get; init; } private DalamudPluginInterface PluginInterface { get; init; } - // Custom variables from Kacie + // Custom variables from Kacie private readonly Plugin Plugin; private readonly bool wasInit = false; - public readonly string CommandName = ""; + public readonly string CommandName = ""; public PluginUI PluginUi { get; init; } private readonly string ShortName = ""; private bool _firstUpdated = false; @@ -205,12 +205,12 @@ namespace FFXIV_Vibe_Plugin { } } - private void DisplayUI() { - this.Plugin.DrawConfigUI(); + private void DisplayUI() { + this.Plugin.DrawConfigUI(); } - private void DisplayConfigUI() { + private void DisplayConfigUI() { this.Plugin.DrawConfigUI(); } diff --git a/FFXIV_Vibe_Plugin/App/Configuration.cs b/FFXIV_Vibe_Plugin/App/Configuration.cs index 7fba94a..fbaf258 100644 --- a/FFXIV_Vibe_Plugin/App/Configuration.cs +++ b/FFXIV_Vibe_Plugin/App/Configuration.cs @@ -1,6 +1,7 @@ using Dalamud.Configuration; using Dalamud.Plugin; -using System; +using System; +using System.IO; using System.Collections.Generic; using FFXIV_Vibe_Plugin.Triggers; @@ -27,7 +28,8 @@ namespace FFXIV_Vibe_Plugin { public bool AUTO_OPEN { get; set; } = false; public List PatternList = new(); public string BUTTPLUG_SERVER_HOST { get; set; } = "127.0.0.1"; - public int BUTTPLUG_SERVER_PORT { get; set; } = 12345; + public int BUTTPLUG_SERVER_PORT { get; set; } = 12345; + public string EXPORT_DIR = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)+"\\FFXIV_Vibe_Plugin"; public List TRIGGERS { get; set; } = new(); public Dictionary VISITED_DEVICES = new(); @@ -36,7 +38,12 @@ namespace FFXIV_Vibe_Plugin { [NonSerialized] private DalamudPluginInterface? pluginInterface; public void Initialize(DalamudPluginInterface pluginInterface) { - this.pluginInterface = pluginInterface; + this.pluginInterface = pluginInterface; + try { + Directory.CreateDirectory(this.EXPORT_DIR); + } catch { + // pass + } } public void Save() { this.pluginInterface!.SavePluginConfig(this); @@ -119,7 +126,9 @@ namespace FFXIV_Vibe_Plugin { public List PatternList = new(); public string BUTTPLUG_SERVER_HOST { get; set; } = "127.0.0.1"; - public int BUTTPLUG_SERVER_PORT { get; set; } = 12345; + public int BUTTPLUG_SERVER_PORT { get; set; } = 12345; + + public string EXPORT_DIR = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\FFXIV_Vibe_Plugin"; public List TRIGGERS { get; set; } = new(); diff --git a/FFXIV_Vibe_Plugin/App/Triggers/Trigger.cs b/FFXIV_Vibe_Plugin/App/Triggers/Trigger.cs index 92aaa19..d984628 100644 --- a/FFXIV_Vibe_Plugin/App/Triggers/Trigger.cs +++ b/FFXIV_Vibe_Plugin/App/Triggers/Trigger.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Security.Cryptography; using FFXIV_Vibe_Plugin.Device; +using System.Xml.Linq; namespace FFXIV_Vibe_Plugin.Triggers { public enum KIND { @@ -20,7 +22,7 @@ namespace FFXIV_Vibe_Plugin.Triggers { Self } - public class Trigger : IComparable { + public class Trigger : IComparable, IEquatable { private static readonly int _initAmountMinValue = -1; private static readonly int _initAmountMaxValue = 10000000; @@ -49,8 +51,10 @@ namespace FFXIV_Vibe_Plugin.Triggers { public List Devices = new(); public Trigger(string name) { - this.Id = Guid.NewGuid().ToString(); this.Name = name; + byte[] textBytes = System.Text.Encoding.UTF8.GetBytes(name); + byte[] hashed = SHA256.Create().ComputeHash(textBytes); + this.Id = BitConverter.ToString(hashed).Replace("-", String.Empty); } public override string ToString() { @@ -59,13 +63,12 @@ namespace FFXIV_Vibe_Plugin.Triggers { public int CompareTo(Trigger? other) { if(other == null) { return 1; } - if(this.SortOder < other.SortOder) { - return 1; - } else if(this.SortOder > other.SortOder) { - return -1; - } else { - return 0; - } + return other.Name.CompareTo(this.Name); + } + + public bool Equals(Trigger? other) { + if (other == null) return false; + return this.Name.Equals(other.Name); } public string GetShortID() { diff --git a/FFXIV_Vibe_Plugin/App/UI/PluginUI.cs b/FFXIV_Vibe_Plugin/App/UI/PluginUI.cs index 51505c4..b1092a6 100644 --- a/FFXIV_Vibe_Plugin/App/UI/PluginUI.cs +++ b/FFXIV_Vibe_Plugin/App/UI/PluginUI.cs @@ -3,17 +3,23 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; - -// Dalamud libs -using ImGuiNET; +using System.Runtime.CompilerServices; + +// Dalamud libs +using ImGuiNET; using Dalamud.Game.Text; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; -using Dalamud.Plugin; -using Dalamud.Interface.Windowing; - +using Dalamud.Plugin; +using Dalamud.Interface.Windowing; + // FFXIV_Vibe_Plugin libs -using FFXIV_Vibe_Plugin.Commons; +using FFXIV_Vibe_Plugin.Commons; +using FFXIV_Vibe_Plugin.Triggers; + +// Json libs +using Newtonsoft.Json; +using System.Text.Json; @@ -74,10 +80,11 @@ namespace FFXIV_Vibe_Plugin { // Trigger private Triggers.Trigger? SelectedTrigger = null; private string triggersViewMode = "default"; // default|edit|delete; - - /** Constructor */ - - + string _tmp_exportPatternResponse = ""; + + /** Constructor */ + + public PluginUI( App currentPlugin, Logger logger, @@ -87,16 +94,16 @@ namespace FFXIV_Vibe_Plugin { Device.DevicesController deviceController, Triggers.TriggersController triggersController, Patterns Patterns - ) : base( + ) : base( "FFXIV_Vibe_Plugin_UI", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar | - ImGuiWindowFlags.NoScrollWithMouse) { - - this.Size = new Vector2(this.WIDTH, this.HEIGHT); + ImGuiWindowFlags.NoScrollWithMouse) { + + this.Size = new Vector2(this.WIDTH, this.HEIGHT); ImGui.SetNextWindowPos(new Vector2(100, 100), ImGuiCond.Appearing); - - //if(ImGui.Begin("FFXIV Vibe Plugin", ref this.visible, ImGuiWindowFlags.None)) { + + //if(ImGui.Begin("FFXIV Vibe Plugin", ref this.visible, ImGuiWindowFlags.None)) { this.Logger = logger; this.Configuration = configuration; this.ConfigurationProfile = profile; @@ -146,47 +153,47 @@ namespace FFXIV_Vibe_Plugin { } - public void DrawMainWindow() { + public void DrawMainWindow() { if (!this._expandedOnce) { ImGui.SetNextWindowCollapsed(false); this._expandedOnce = true; - } - - ImGui.Spacing(); - - FFXIV_Vibe_Plugin.UI.UIBanner.Draw(this.frameCounter, this.Logger, this.loadedImages["icon.png"], this.DonationLink, this.DevicesController); - - // Back to on column - ImGui.Columns(1); - - // Tab header - if (ImGui.BeginTabBar("##ConfigTabBar", ImGuiTabBarFlags.None)) { - if (ImGui.BeginTabItem("Connect")) { - FFXIV_Vibe_Plugin.UI.UIConnect.Draw(this.Configuration, this.ConfigurationProfile, this.app, this.DevicesController); - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem("Options")) { - this.DrawOptionsTab(); - ImGui.EndTabItem(); - } - if (ImGui.BeginTabItem("Devices")) { - this.DrawDevicesTab(); - ImGui.EndTabItem(); - } - if (ImGui.BeginTabItem("Triggers")) { - this.DrawTriggersTab(); - ImGui.EndTabItem(); - } - if (ImGui.BeginTabItem("Patterns")) { - this.DrawPatternsTab(); - ImGui.EndTabItem(); - } - if (ImGui.BeginTabItem("Help")) { - this.DrawHelpTab(); - ImGui.EndTabItem(); - } + } + + ImGui.Spacing(); + + FFXIV_Vibe_Plugin.UI.UIBanner.Draw(this.frameCounter, this.Logger, this.loadedImages["icon.png"], this.DonationLink, this.DevicesController); + + // Back to on column + ImGui.Columns(1); + + // Tab header + if (ImGui.BeginTabBar("##ConfigTabBar", ImGuiTabBarFlags.None)) { + if (ImGui.BeginTabItem("Connect")) { + FFXIV_Vibe_Plugin.UI.UIConnect.Draw(this.Configuration, this.ConfigurationProfile, this.app, this.DevicesController); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem("Options")) { + this.DrawOptionsTab(); + ImGui.EndTabItem(); + } + if (ImGui.BeginTabItem("Devices")) { + this.DrawDevicesTab(); + ImGui.EndTabItem(); + } + if (ImGui.BeginTabItem("Triggers")) { + this.DrawTriggersTab(); + ImGui.EndTabItem(); + } + if (ImGui.BeginTabItem("Patterns")) { + this.DrawPatternsTab(); + ImGui.EndTabItem(); + } + if (ImGui.BeginTabItem("Help")) { + this.DrawHelpTab(); + ImGui.EndTabItem(); + } } } @@ -329,6 +336,39 @@ namespace FFXIV_Vibe_Plugin { ImGui.EndTable(); } + ImGui.EndChild(); + + ImGui.TextColored(ImGuiColors.DalamudViolet, "Trigger Import/Export Settings"); + ImGui.BeginChild("###EXPORT_OPTIONS_ZONE", new Vector2(-1, 100f), true); + { + // Init table + ImGui.BeginTable("###EXPORT_OPTIONS_TABLE", 2); + + ImGui.TableSetupColumn("###EXPORT_OPTIONS_TABLE_COL1", ImGuiTableColumnFlags.WidthFixed, 250); + ImGui.TableSetupColumn("###EXPORT_OPTIONS_TABLE_COL2", ImGuiTableColumnFlags.WidthStretch); + + ImGui.TableNextColumn(); + ImGui.Text("Trigger Import/Export Directory:"); + ImGui.TableNextColumn(); + if (ImGui.InputText("###EXPORT_DIRECTORY_INPUT", ref this.ConfigurationProfile.EXPORT_DIR, 200)) { + this.Configuration.EXPORT_DIR = this.ConfigurationProfile.EXPORT_DIR; + this.Configuration.Save(); + } + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + if (ImGui.Button("Clear Import/Export Directory")) { + if (!this.ConfigurationProfile.EXPORT_DIR.Equals("")) { + try { + foreach (var filename in Directory.GetFiles(this.ConfigurationProfile.EXPORT_DIR)) { + File.Delete(filename); + } + } catch { } + } + } + ImGui.SameLine(); + ImGuiComponents.HelpMarker("Deletes ALL files in the Import/Export Directory."); + ImGui.EndTable(); + } ImGui.EndChild(); if (this.ConfigurationProfile.VERBOSE_CHAT || this.ConfigurationProfile.VERBOSE_SPELL) { @@ -428,8 +468,8 @@ namespace FFXIV_Vibe_Plugin { if (ImGui.BeginChild("###TriggersSelector", new Vector2(ImGui.GetWindowContentRegionMax().X / 3, -ImGui.GetFrameHeightWithSpacing()), true)) { ImGui.SetNextItemWidth(185); ImGui.InputText("###TriggersSelector_SearchBar", ref this.CURRENT_TRIGGER_SELECTOR_SEARCHBAR, 200); - ImGui.Spacing(); - + ImGui.Spacing(); + for (int triggerIndex = 0; triggerIndex < triggers.Count; triggerIndex++) { Triggers.Trigger trigger = triggers[triggerIndex]; if (trigger != null) { @@ -442,8 +482,8 @@ namespace FFXIV_Vibe_Plugin { string triggerNameWithId = $"{triggerName}###{trigger.Id}"; if (!Helpers.RegExpMatch(this.Logger, triggerName, this.CURRENT_TRIGGER_SELECTOR_SEARCHBAR)) { continue; - } - + } + if (ImGui.Selectable($"{triggerNameWithId}", selectedId == trigger.Id)) { // We don't want to show the ID this.SelectedTrigger = trigger; this.triggersViewMode = "edit"; @@ -720,15 +760,15 @@ namespace FFXIV_Vibe_Plugin { if ( this.SelectedTrigger.ActionEffectType == (int)Structures.ActionEffectType.Damage || this.SelectedTrigger.ActionEffectType == (int)Structures.ActionEffectType.Heal - || + || this.SelectedTrigger.Kind == (int)Triggers.KIND.HPChange) { // Min/Max amount values string type = ""; if (this.SelectedTrigger.ActionEffectType == (int)Structures.ActionEffectType.Damage) { type = "damage"; } if (this.SelectedTrigger.ActionEffectType == (int)Structures.ActionEffectType.Heal) { type = "heal"; } - if (this.SelectedTrigger.Kind == (int)Triggers.KIND.HPChange) { type = "health"; } - - // TRIGGER AMOUNT IN PERCENTAGE + if (this.SelectedTrigger.Kind == (int)Triggers.KIND.HPChange) { type = "health"; } + + // TRIGGER AMOUNT IN PERCENTAGE ImGui.TableNextColumn(); ImGui.Text("Amount in percentage?"); ImGui.TableNextColumn(); @@ -736,11 +776,11 @@ namespace FFXIV_Vibe_Plugin { this.SelectedTrigger.AmountMinValue = 0; this.SelectedTrigger.AmountMaxValue = 100; this.Configuration.Save(); - } - - - - // TRIGGER MIN_VALUE + } + + + + // TRIGGER MIN_VALUE ImGui.TableNextColumn(); ImGui.Text($"Min {type} value:"); ImGui.TableNextColumn(); @@ -770,7 +810,17 @@ namespace FFXIV_Vibe_Plugin { } ImGui.TableNextRow(); } - ImGui.EndTable(); + ImGui.EndTable(); + ImGui.Separator(); + + if (ImGui.Button("Export")) { + this._tmp_exportPatternResponse = export_trigger(SelectedTrigger); + } + ImGui.SameLine(); + ImGuiComponents.HelpMarker("Writes this trigger to your export directory."); + ImGui.SameLine(); + ImGui.Text($"{this._tmp_exportPatternResponse}"); + ImGui.Separator(); ImGui.TextColored(ImGuiColors.DalamudViolet, "Actions & Devices"); ImGui.Separator(); @@ -963,8 +1013,13 @@ namespace FFXIV_Vibe_Plugin { ImGui.EndChild(); } - if (ImGui.Button("Add")) { - Triggers.Trigger trigger = new("New Trigger"); + if (ImGui.Button("Add")) { + int index = 0; + Triggers.Trigger trigger = new($"New Trigger {index}"); + while (this.TriggerController.GetTriggers().Contains(trigger)) { + index++; + trigger = new($"New Trigger {index}"); + } this.TriggerController.AddTrigger(trigger); this.SelectedTrigger = trigger; this.triggersViewMode = "edit"; @@ -973,8 +1028,29 @@ namespace FFXIV_Vibe_Plugin { ImGui.SameLine(); if (ImGui.Button("Delete")) { this.triggersViewMode = "delete"; + } + ImGui.SameLine(); + if (ImGui.Button("Import Triggers")) { + if (!this.ConfigurationProfile.EXPORT_DIR.Equals("")) { + try { + foreach (var filename in Directory.GetFiles(this.ConfigurationProfile.EXPORT_DIR)) { + Trigger t = JsonConvert.DeserializeObject(File.ReadAllText(filename)); + // Remove any triggers with the same name due to .Equals override + this.TriggerController.RemoveTrigger(t); + // Import the new trigger + this.TriggerController.AddTrigger(t); + } + } catch { } + } + } + ImGui.SameLine(); + if (ImGui.Button("Export All")) { + if (!this.ConfigurationProfile.EXPORT_DIR.Equals("")) { + foreach (Trigger t in this.TriggerController.GetTriggers()) { + export_trigger(t); + } + } } - } public void DrawPatternsTab() { @@ -1119,6 +1195,22 @@ namespace FFXIV_Vibe_Plugin { this._tmp_void = "50:1000|100:2000"; ImGui.InputText("###HELP_PATTERN_EXAMPLE", ref this._tmp_void, 50); } + public string export_trigger(Trigger trigger) { + if (this.ConfigurationProfile.EXPORT_DIR.Equals("")) { + return "No export directory has been set! Set one in Options."; + } else { + try { + File.WriteAllText( + Path.Join(this.ConfigurationProfile.EXPORT_DIR, $"{trigger.Name}.json"), + JsonConvert.SerializeObject(trigger, Formatting.Indented) + ); + return "Successfully exported trigger!"; + } catch { + return "Something went wrong while exporting!"; + } + } + } + } + - } } diff --git a/README.md b/README.md index d466331..3f1b17e 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ A plugin for FFXIV that will let you vibe your controller or toys. - Custom patterns per motor (save, with easy import, export). - Vibe or trigger a pattern on HP Changed - HP Changed can have custom min/max values or percentages +- Export/Import triggers ## Prerequisites - [FFXIV QuickLauncher](https://github.com/goatcorp/FFXIVQuickLauncher). @@ -64,7 +65,27 @@ not make a living from it. ## Build yourself -You can build yourself, instructions are here: [Build yourself](./Docs/BUILD.md) +You can build yourself, instructions are here: [Build yourself](./Docs/BUILD.md)o + +## Import triggers +1. Start the game and make sure the plugin is working. +2. On your computer, go to your `%userprofile%` folder (eg: C:\\Users\\) folder. +3. The go in the `FFXIV\_Vibe\_Plugin` folder (or create if it does not exists) +4. Add the triggers file you want to import (eg: `MyTrigger.json`) +5. In the plugin go to the `Triggers` tab and click on `Import Triggers` at the bottom. + +That's it. It should load all of the triggers. +Note: you can define a custom directory to read/write in the `Options` tab. + +## Export triggers +1. On your computre, go to your `%userprofile%` folder (eg: C:\\Users\\) folder. +2. Go to the `FFXIV\_Vibe\_Plugin` folder (or create if it does not exists) +3. In the plugin, go to the `Triggers` tab. +4. Create your triggers (if they does not exist). +5. Select the trigger you want to export and click on the `Export` button. + +That's it. You should see them in your userprofile directory. +Note: you can define a custom directory to read/write in the `Options` tab. ## USB Dongle vs Lovense Dongle vs Other We recommend you to use a bluetooth dongle. Here is the one we are using: [TP-Link Nano USB Dongle Bluetooth 5.0](https://www.amazon.fr/gp/product/B09C25VRXD/ref=as_li_tl?ie=UTF8&camp=1642&creative=6746&creativeASIN=B09C25VRXD&linkCode=as2&tag=kaciexx-21&linkId=8b6c8c6e693ab549216c2dacad34e03b)