Compare commits
No commits in common. "main" and "f811256f45dce306ac4acbbf0e4aa2e319aeaf2b" have entirely different histories.
main
...
f811256f45
|
@ -9,7 +9,7 @@ namespace Glamaholic {
|
|||
this.Plugin = plugin;
|
||||
|
||||
this.Plugin.CommandManager.AddHandler("/glamaholic", new CommandInfo(this.OnCommand) {
|
||||
HelpMessage = $"Toggle visibility of the {Plugin.Name} window",
|
||||
HelpMessage = $"Toggle visibility of the {this.Plugin.Name} window",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -13,19 +13,15 @@ namespace Glamaholic {
|
|||
public bool ShowExamineMenu = true;
|
||||
public bool ShowTryOnMenu = true;
|
||||
public bool ShowKofiButton = true;
|
||||
public bool ItemFilterShowObtainedOnly;
|
||||
|
||||
internal static void SanitisePlate(SavedPlate plate) {
|
||||
internal void AddPlate(SavedPlate plate) {
|
||||
var valid = Enum.GetValues<PlateSlot>();
|
||||
foreach (var slot in plate.Items.Keys.ToArray()) {
|
||||
if (!valid.Contains(slot)) {
|
||||
plate.Items.Remove(slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddPlate(SavedPlate plate) {
|
||||
SanitisePlate(plate);
|
||||
this.Plates.Add(plate);
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +30,6 @@ namespace Glamaholic {
|
|||
internal class SavedPlate {
|
||||
public string Name { get; set; }
|
||||
public Dictionary<PlateSlot, SavedGlamourItem> Items { get; init; } = new();
|
||||
public List<string> Tags { get; } = new();
|
||||
|
||||
public SavedPlate(string name) {
|
||||
this.Name = name;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
@ -8,8 +7,6 @@ using Dalamud.Game.Text;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
@ -19,7 +16,7 @@ using Lumina.Excel.GeneratedSheets;
|
|||
namespace Glamaholic {
|
||||
internal class GameFunctions : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string SetGlamourPlateSlot = "E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B 46 10 8B 1B";
|
||||
internal const string SetGlamourPlateSlot = "E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B 46 10";
|
||||
internal const string ModifyGlamourPlateSlot = "48 89 74 24 ?? 57 48 83 EC 20 80 79 30 00";
|
||||
internal const string ClearGlamourPlateSlot = "80 79 30 00 4C 8B C1";
|
||||
internal const string IsInArmoire = "E8 ?? ?? ?? ?? 84 C0 74 16 8B CB";
|
||||
|
@ -28,8 +25,6 @@ namespace Glamaholic {
|
|||
internal const string ExamineNamePointer = "48 8D 05 ?? ?? ?? ?? 48 89 85 ?? ?? ?? ?? 74 56 49 8B 4F";
|
||||
}
|
||||
|
||||
#region Delegates
|
||||
|
||||
private delegate void SetGlamourPlateSlotDelegate(IntPtr agent, MirageSource mirageSource, int glamId, uint itemId, byte stainId);
|
||||
|
||||
private delegate void ModifyGlamourPlateSlotDelegate(IntPtr agent, PlateSlot slot, byte stainId, IntPtr numbers, int stainItemId);
|
||||
|
@ -40,49 +35,33 @@ namespace Glamaholic {
|
|||
|
||||
private delegate byte TryOnDelegate(uint unknownCanEquip, uint itemBaseId, ulong stainColor, uint itemGlamourId, byte unknownByte);
|
||||
|
||||
#endregion
|
||||
|
||||
private Plugin Plugin { get; }
|
||||
|
||||
#region Functions
|
||||
|
||||
[Signature(Signatures.SetGlamourPlateSlot)]
|
||||
private readonly SetGlamourPlateSlotDelegate _setGlamourPlateSlot = null!;
|
||||
|
||||
[Signature(Signatures.ModifyGlamourPlateSlot)]
|
||||
private readonly ModifyGlamourPlateSlotDelegate _modifyGlamourPlateSlot = null!;
|
||||
|
||||
[Signature(Signatures.ClearGlamourPlateSlot)]
|
||||
private readonly ClearGlamourPlateSlotDelegate _clearGlamourPlateSlot = null!;
|
||||
|
||||
[Signature(Signatures.IsInArmoire)]
|
||||
private readonly IsInArmoireDelegate _isInArmoire = null!;
|
||||
|
||||
[Signature(Signatures.ArmoirePointer, ScanType = ScanType.StaticAddress)]
|
||||
private readonly SetGlamourPlateSlotDelegate _setGlamourPlateSlot;
|
||||
private readonly ModifyGlamourPlateSlotDelegate _modifyGlamourPlateSlot;
|
||||
private readonly ClearGlamourPlateSlotDelegate _clearGlamourPlateSlot;
|
||||
private readonly IsInArmoireDelegate _isInArmoire;
|
||||
private readonly IntPtr _armoirePtr;
|
||||
|
||||
[Signature(Signatures.TryOn)]
|
||||
private readonly TryOnDelegate _tryOn = null!;
|
||||
|
||||
[Signature(Signatures.ExamineNamePointer, ScanType = ScanType.StaticAddress)]
|
||||
private readonly TryOnDelegate _tryOn;
|
||||
private readonly IntPtr _examineNamePtr;
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly List<uint> _filterIds = new();
|
||||
|
||||
internal GameFunctions(Plugin plugin) {
|
||||
this.Plugin = plugin;
|
||||
this.Plugin.GameInteropProvider.InitializeFromAttributes(this);
|
||||
|
||||
this._setGlamourPlateSlot = Marshal.GetDelegateForFunctionPointer<SetGlamourPlateSlotDelegate>(this.Plugin.SigScanner.ScanText(Signatures.SetGlamourPlateSlot));
|
||||
this._modifyGlamourPlateSlot = Marshal.GetDelegateForFunctionPointer<ModifyGlamourPlateSlotDelegate>(this.Plugin.SigScanner.ScanText(Signatures.ModifyGlamourPlateSlot));
|
||||
this._clearGlamourPlateSlot = Marshal.GetDelegateForFunctionPointer<ClearGlamourPlateSlotDelegate>(this.Plugin.SigScanner.ScanText(Signatures.ClearGlamourPlateSlot));
|
||||
this._isInArmoire = Marshal.GetDelegateForFunctionPointer<IsInArmoireDelegate>(this.Plugin.SigScanner.ScanText(Signatures.IsInArmoire));
|
||||
this._armoirePtr = this.Plugin.SigScanner.GetStaticAddressFromSig(Signatures.ArmoirePointer);
|
||||
this._tryOn = Marshal.GetDelegateForFunctionPointer<TryOnDelegate>(this.Plugin.SigScanner.ScanText(Signatures.TryOn));
|
||||
this._examineNamePtr = this.Plugin.SigScanner.GetStaticAddressFromSig(Signatures.ExamineNamePointer);
|
||||
|
||||
this.Plugin.ChatGui.ChatMessage += this.OnChat;
|
||||
this.Plugin.ClientState.Login += OnLogin;
|
||||
this.Plugin.Framework.Update += this.OnFrameworkUpdate;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Plugin.Framework.Update -= this.OnFrameworkUpdate;
|
||||
this.Plugin.ClientState.Login -= OnLogin;
|
||||
this.Plugin.ChatGui.ChatMessage -= this.OnChat;
|
||||
}
|
||||
|
||||
|
@ -96,50 +75,26 @@ namespace Glamaholic {
|
|||
}
|
||||
}
|
||||
|
||||
private static void OnLogin() {
|
||||
_dresserContents = null;
|
||||
}
|
||||
|
||||
private bool _wasEditing;
|
||||
|
||||
private void OnFrameworkUpdate(IFramework framework1) {
|
||||
var editing = Util.IsEditingPlate(this.Plugin.GameGui);
|
||||
if (!this._wasEditing && editing) {
|
||||
// cache dresser
|
||||
var unused = DresserContents;
|
||||
}
|
||||
|
||||
this._wasEditing = editing;
|
||||
}
|
||||
|
||||
internal unsafe bool ArmoireLoaded => *(byte*) this._armoirePtr > 0;
|
||||
|
||||
internal string? ExamineName => this._examineNamePtr == IntPtr.Zero
|
||||
? null
|
||||
: MemoryHelper.ReadStringNullTerminated(this._examineNamePtr);
|
||||
|
||||
private static readonly Stopwatch DresserTimer = new();
|
||||
private static List<GlamourItem>? _dresserContents;
|
||||
|
||||
internal static unsafe List<GlamourItem> DresserContents {
|
||||
get {
|
||||
if (_dresserContents != null && DresserTimer.Elapsed < TimeSpan.FromSeconds(1)) {
|
||||
return _dresserContents;
|
||||
}
|
||||
|
||||
var list = new List<GlamourItem>();
|
||||
|
||||
var agents = Framework.Instance()->GetUiModule()->GetAgentModule();
|
||||
var dresserAgent = agents->GetAgentByInternalId(AgentId.MiragePrismPrismBox);
|
||||
|
||||
// these offsets in 6.3-HF1: AD2BEB
|
||||
var itemsStart = *(IntPtr*) ((IntPtr) dresserAgent + 0x28);
|
||||
if (itemsStart == IntPtr.Zero) {
|
||||
return _dresserContents ?? list;
|
||||
return list;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 800; i++) {
|
||||
var glamItem = *(GlamourItem*) (itemsStart + i * 136);
|
||||
for (var i = 0; i < 400; i++) {
|
||||
var glamItem = *(GlamourItem*) (itemsStart + i * 32);
|
||||
if (glamItem.ItemId == 0) {
|
||||
continue;
|
||||
}
|
||||
|
@ -147,9 +102,6 @@ namespace Glamaholic {
|
|||
list.Add(glamItem);
|
||||
}
|
||||
|
||||
_dresserContents = list;
|
||||
DresserTimer.Restart();
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
@ -168,14 +120,9 @@ namespace Glamaholic {
|
|||
|
||||
var plate = new Dictionary<PlateSlot, SavedGlamourItem>();
|
||||
foreach (var slot in (PlateSlot[]) Enum.GetValues(typeof(PlateSlot))) {
|
||||
// Updated: 6.1
|
||||
// from SetGlamourPlateSlot
|
||||
var item = editorInfo + 44 * (int) slot + 10596;
|
||||
|
||||
var itemId = *(uint*) item;
|
||||
var stainId = *(byte*) (item + 24);
|
||||
var stainPreviewId = *(byte*) (item + 25);
|
||||
var actualStainId = stainPreviewId == 0 ? stainId : stainPreviewId;
|
||||
var itemId = *(uint*) (editorInfo + 44 * (int) slot + 7956);
|
||||
var stainId = *(byte*) (editorInfo + 44 * (int) slot + 7980);
|
||||
|
||||
if (itemId == 0) {
|
||||
continue;
|
||||
|
@ -183,7 +130,7 @@ namespace Glamaholic {
|
|||
|
||||
plate[slot] = new SavedGlamourItem {
|
||||
ItemId = itemId,
|
||||
StainId = actualStainId,
|
||||
StainId = stainId,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -191,7 +138,7 @@ namespace Glamaholic {
|
|||
}
|
||||
}
|
||||
|
||||
private static unsafe AgentInterface* EditorAgent => Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.MiragePrismMiragePlate);
|
||||
private static unsafe AgentInterface* EditorAgent => Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId((AgentId) 293);
|
||||
|
||||
internal unsafe void SetGlamourPlateSlot(MirageSource source, int glamId, uint itemId, byte stainId) {
|
||||
this._setGlamourPlateSlot((IntPtr) EditorAgent, source, glamId, itemId, stainId);
|
||||
|
@ -228,7 +175,6 @@ namespace Glamaholic {
|
|||
return;
|
||||
}
|
||||
|
||||
// Updated: 6.11 C98BC0
|
||||
var editorInfo = *(IntPtr*) ((IntPtr) agent + 0x28);
|
||||
if (editorInfo == IntPtr.Zero) {
|
||||
return;
|
||||
|
@ -238,8 +184,6 @@ namespace Glamaholic {
|
|||
var current = CurrentPlate;
|
||||
var usedStains = new Dictionary<(uint, uint), uint>();
|
||||
|
||||
// Updated: 6.11 C984CF
|
||||
// current plate 6.11 C9AC9F
|
||||
var slotPtr = (PlateSlot*) (editorInfo + 0x18);
|
||||
var initialSlot = *slotPtr;
|
||||
foreach (var (slot, item) in plate.Items) {
|
||||
|
@ -434,15 +378,15 @@ namespace Glamaholic {
|
|||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 136)]
|
||||
[StructLayout(LayoutKind.Explicit, Size = 32)]
|
||||
internal readonly struct GlamourItem {
|
||||
[FieldOffset(0x70)]
|
||||
[FieldOffset(4)]
|
||||
internal readonly uint Index;
|
||||
|
||||
[FieldOffset(0x74)]
|
||||
[FieldOffset(8)]
|
||||
internal readonly uint ItemId;
|
||||
|
||||
[FieldOffset(0x86)]
|
||||
[FieldOffset(26)]
|
||||
internal readonly byte StainId;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,62 +1,69 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<Version>1.9.14</Version>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<Version>1.5.0</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<IgnoredLints>
|
||||
D0008
|
||||
</IgnoredLints>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
|
||||
<DalamudLibPath>$(DALAMUD_HOME)</DalamudLibPath>
|
||||
<Dalamud>$(AppData)\XIVLauncher\addon\Hooks\dev</Dalamud>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(IsCI)' == 'true'">
|
||||
<DalamudLibPath>$(HOME)/dalamud</DalamudLibPath>
|
||||
<Dalamud>$(HOME)/dalamud</Dalamud>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(DalamudLibPath)\Dalamud.dll</HintPath>
|
||||
<HintPath>$(Dalamud)\Dalamud.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFXIVClientStructs">
|
||||
<HintPath>$(DalamudLibPath)\FFXIVClientStructs.dll</HintPath>
|
||||
<HintPath>$(Dalamud)\FFXIVClientStructs.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGui.NET">
|
||||
<HintPath>$(DalamudLibPath)\ImGui.NET.dll</HintPath>
|
||||
<HintPath>$(Dalamud)\ImGui.NET.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGuiScene">
|
||||
<HintPath>$(Dalamud)\ImGuiScene.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(DalamudLibPath)\Lumina.dll</HintPath>
|
||||
<HintPath>$(Dalamud)\Lumina.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(DalamudLibPath)\Lumina.Excel.dll</HintPath>
|
||||
<HintPath>$(Dalamud)\Lumina.Excel.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>$(DalamudLibPath)\Newtonsoft.Json.dll</HintPath>
|
||||
<HintPath>$(Dalamud)\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DalamudPackager" Version="2.1.12" />
|
||||
<PackageReference Include="Fody" Version="6.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Resourcer.Fody" Version="1.8.1" PrivateAssets="all" />
|
||||
<PackageReference Include="DalamudLinter" Version="1.0.3"/>
|
||||
<PackageReference Include="DalamudPackager" Version="2.1.4"/>
|
||||
<PackageReference Include="Fody" Version="6.6.0" PrivateAssets="all"/>
|
||||
<PackageReference Include="Resourcer.Fody" Version="1.8.0" PrivateAssets="all"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="help.txt" />
|
||||
<Content Include="..\icon.png" Link="images/icon.png" CopyToOutputDirectory="PreserveNewest" Visible="false"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="help.txt"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
name: Glamaholic
|
||||
author: Anna
|
||||
author: ascclemens
|
||||
description: |
|
||||
Create and save as many glamour plates as you want. Activate up to 15 of them
|
||||
at once at the Glamour Dresser. Supports exporting and importing plates for
|
||||
easy sharing.
|
||||
punchline: Save and swap your glamour plates.
|
||||
repo_url: https://git.anna.lgbt/anna/Glamaholic
|
||||
repo_url: https://git.sr.ht/~jkcclemens/Glamaholic
|
||||
|
|
|
@ -1,45 +1,33 @@
|
|||
using Dalamud.Game;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Glamaholic {
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
public class Plugin : IDalamudPlugin {
|
||||
internal static string Name => "Glamaholic";
|
||||
|
||||
[PluginService]
|
||||
internal static IPluginLog Log { get; private set; }
|
||||
internal const string PluginName = "Glamaholic";
|
||||
public string Name => PluginName;
|
||||
|
||||
[PluginService]
|
||||
internal DalamudPluginInterface Interface { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IChatGui ChatGui { get; init; }
|
||||
internal ChatGui ChatGui { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IClientState ClientState { get; init; }
|
||||
internal CommandManager CommandManager { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal ICommandManager CommandManager { get; init; }
|
||||
internal DataManager DataManager { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IDataManager DataManager { get; init; }
|
||||
internal GameGui GameGui { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IFramework Framework { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IGameGui GameGui { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal ISigScanner SigScanner { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal ITextureProvider TextureProvider { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IGameInteropProvider GameInteropProvider { get; init; }
|
||||
internal SigScanner SigScanner { get; init; }
|
||||
|
||||
internal Configuration Config { get; }
|
||||
internal GameFunctions Functions { get; }
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface.Internal;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using Glamaholic.Ui;
|
||||
using Glamaholic.Ui.Helpers;
|
||||
using ImGuiScene;
|
||||
|
||||
namespace Glamaholic {
|
||||
internal class PluginUi : IDisposable {
|
||||
internal Plugin Plugin { get; }
|
||||
|
||||
private Dictionary<ushort, IDalamudTextureWrap> Icons { get; } = new();
|
||||
private Dictionary<ushort, TextureWrap> Icons { get; } = new();
|
||||
|
||||
private MainInterface MainInterface { get; }
|
||||
private EditorHelper EditorHelper { get; }
|
||||
|
@ -62,12 +62,12 @@ namespace Glamaholic {
|
|||
this.MainInterface.Toggle();
|
||||
}
|
||||
|
||||
internal IDalamudTextureWrap? GetIcon(ushort id) {
|
||||
internal TextureWrap? GetIcon(ushort id) {
|
||||
if (this.Icons.TryGetValue(id, out var cached)) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
var icon = this.Plugin.TextureProvider.GetIcon(id);
|
||||
var icon = this.Plugin.DataManager.GetImGuiTextureIcon(id);
|
||||
if (icon == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ namespace Glamaholic {
|
|||
void SetTryOnSave(bool save) {
|
||||
var tryOnAgent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Tryon);
|
||||
if (tryOnAgent != IntPtr.Zero) {
|
||||
*(byte*) (tryOnAgent + 0x30A) = (byte) (save ? 1 : 0);
|
||||
*(byte*) (tryOnAgent + 0x2E2) = (byte) (save ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Glamaholic {
|
||||
[Serializable]
|
||||
internal class SharedPlate {
|
||||
public string Name { get; }
|
||||
public Dictionary<PlateSlot, SavedGlamourItem> Items { get; }
|
||||
|
||||
internal SharedPlate(SavedPlate plate) {
|
||||
var clone = plate.Clone();
|
||||
this.Name = clone.Name;
|
||||
this.Items = clone.Items;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private SharedPlate(string name, Dictionary<PlateSlot, SavedGlamourItem> items) {
|
||||
this.Name = name;
|
||||
this.Items = items;
|
||||
}
|
||||
|
||||
internal SavedPlate ToPlate() {
|
||||
return new SavedPlate(this.Name) {
|
||||
Items = this.Items.ToDictionary(entry => entry.Key, entry => entry.Value.Clone()),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -83,16 +83,18 @@ namespace Glamaholic.Ui {
|
|||
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (!alt.IsDyeable) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]);
|
||||
}
|
||||
|
||||
Util.TextIcon(FontAwesomeIcon.FillDrip);
|
||||
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.FillDrip.ToIconString());
|
||||
if (!alt.IsDyeable) {
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.PopFont();
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(alt.Name);
|
||||
}
|
||||
|
@ -134,7 +136,7 @@ namespace Glamaholic.Ui {
|
|||
|
||||
var payload = new SeString(payloadList);
|
||||
|
||||
this.Ui.Plugin.ChatGui.Print(new XivChatEntry {
|
||||
this.Ui.Plugin.ChatGui.PrintChat(new XivChatEntry {
|
||||
Message = payload,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,240 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Dalamud;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamaholic.Ui {
|
||||
internal class FilterInfo {
|
||||
private IDataManager Data { get; }
|
||||
|
||||
private uint MaxLevel { get; }
|
||||
private string Query { get; }
|
||||
private HashSet<ClassJob> WantedJobs { get; } = new();
|
||||
private HashSet<string> Tags { get; } = new();
|
||||
private HashSet<string> ExcludeTags { get; } = new();
|
||||
private HashSet<uint> ItemIds { get; } = new();
|
||||
private HashSet<string> ItemNames { get; } = new();
|
||||
|
||||
internal FilterInfo(IDataManager data, string filter) {
|
||||
this.Data = data;
|
||||
|
||||
var queryWords = new List<string>();
|
||||
|
||||
var quoteType = -1;
|
||||
string? quoted = null;
|
||||
foreach (var immutableWord in filter.Split(' ')) {
|
||||
var word = immutableWord;
|
||||
|
||||
if (quoted != null) {
|
||||
quoted += " ";
|
||||
|
||||
var quoteIndex = word.IndexOf('"');
|
||||
if (quoteIndex > -1) {
|
||||
quoted += word[..quoteIndex];
|
||||
|
||||
switch (quoteType) {
|
||||
case 1:
|
||||
this.Tags.Add(quoted);
|
||||
break;
|
||||
case 2:
|
||||
this.ItemNames.Add(quoted);
|
||||
break;
|
||||
case 3:
|
||||
this.ExcludeTags.Add(quoted);
|
||||
break;
|
||||
}
|
||||
|
||||
quoted = null;
|
||||
quoteType = -1;
|
||||
|
||||
var rest = word[(quoteIndex + 1)..];
|
||||
if (rest.Length > 0) {
|
||||
word = rest;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
quoted += word;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (word.StartsWith("j:")) {
|
||||
var abbr = word[2..].ToLowerInvariant();
|
||||
var job = this.Data.GetExcelSheet<ClassJob>()!.FirstOrDefault(row => row.Abbreviation.RawString.ToLowerInvariant() == abbr);
|
||||
if (job != null) {
|
||||
this.WantedJobs.Add(job);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (word.StartsWith("lvl:")) {
|
||||
if (uint.TryParse(word[4..], out var level)) {
|
||||
this.MaxLevel = level;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (word.StartsWith("t:")) {
|
||||
if (word.StartsWith("t:\"")) {
|
||||
if (word.EndsWith('"') && word.Length >= 5) {
|
||||
this.Tags.Add(word[3..^1]);
|
||||
} else {
|
||||
quoteType = 1;
|
||||
quoted = word[3..];
|
||||
}
|
||||
} else {
|
||||
this.Tags.Add(word[2..]);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (word.StartsWith("!t:")) {
|
||||
if (word.StartsWith("!t:\"")) {
|
||||
if (word.EndsWith('"') && word.Length >= 6) {
|
||||
this.ExcludeTags.Add(word[4..^1]);
|
||||
} else {
|
||||
quoteType = 3;
|
||||
quoted = word[4..];
|
||||
}
|
||||
} else {
|
||||
this.ExcludeTags.Add(word[3..]);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (word.StartsWith("id:")) {
|
||||
if (uint.TryParse(word[3..], out var id)) {
|
||||
this.ItemIds.Add(id);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (word.StartsWith("i:")) {
|
||||
if (word.StartsWith("i:\"")) {
|
||||
if (word.EndsWith('"') && word.Length >= 5) {
|
||||
this.ItemNames.Add(word[3..^1]);
|
||||
} else {
|
||||
quoteType = 2;
|
||||
quoted = word[3..];
|
||||
}
|
||||
} else {
|
||||
this.ItemNames.Add(word[2..]);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
queryWords.Add(word);
|
||||
}
|
||||
|
||||
this.Query = string.Join(' ', queryWords).ToLowerInvariant();
|
||||
}
|
||||
|
||||
internal bool Matches(SavedPlate plate) {
|
||||
// if the name doesn't match the query, it's not a match, obviously
|
||||
if (this.Query.Length != 0 && !plate.Name.ToLowerInvariant().Contains(this.Query)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if there's nothing custom about this filter, this is a match
|
||||
var notCustom = this.MaxLevel == 0
|
||||
&& this.WantedJobs.Count == 0
|
||||
&& this.Tags.Count == 0
|
||||
&& this.ExcludeTags.Count == 0
|
||||
&& this.ItemIds.Count == 0
|
||||
&& this.ItemNames.Count == 0;
|
||||
if (notCustom) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var tag in this.Tags) {
|
||||
if (!plate.Tags.Contains(tag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var tag in this.ExcludeTags) {
|
||||
if (plate.Tags.Contains(tag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ItemIds.Count > 0) {
|
||||
var matching = plate.Items.Values
|
||||
.Select(mirage => mirage.ItemId)
|
||||
.Intersect(this.ItemIds)
|
||||
.Count();
|
||||
|
||||
if (matching != this.ItemIds.Count) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ItemNames.Count > 0) {
|
||||
var sheet = this.Data.GetExcelSheet<Item>()!;
|
||||
|
||||
var names = plate.Items.Values
|
||||
.Select(mirage => sheet.GetRow(mirage.ItemId % Util.HqItemOffset))
|
||||
.Where(item => item != null)
|
||||
.Cast<Item>()
|
||||
.Select(item => item.Name.RawString.ToLowerInvariant())
|
||||
.ToArray();
|
||||
|
||||
foreach (var needle in this.ItemNames) {
|
||||
var lower = needle.ToLowerInvariant();
|
||||
if (!names.Any(name => name.Contains(lower))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var mirage in plate.Items.Values) {
|
||||
var item = this.Data.GetExcelSheet<Item>()!.GetRow(mirage.ItemId % Util.HqItemOffset);
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.MaxLevel != 0 && item.LevelEquip > this.MaxLevel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var job in this.WantedJobs) {
|
||||
var category = item.ClassJobCategory.Value;
|
||||
if (category == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.CanWear(category, job)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CanWear(ClassJobCategory category, ClassJob classJob) {
|
||||
// get english version
|
||||
var job = this.Data.GetExcelSheet<ClassJob>(ClientLanguage.English)!.GetRow(classJob.RowId)!;
|
||||
var getter = category.GetType().GetProperty(job.Abbreviation.RawString, BindingFlags.Public | BindingFlags.Instance);
|
||||
if (getter == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var value = getter.GetValue(category);
|
||||
if (value is bool res) {
|
||||
return res;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ using ImGuiNET;
|
|||
namespace Glamaholic.Ui.Helpers {
|
||||
internal class EditorHelper {
|
||||
private PluginUi Ui { get; }
|
||||
private string _plateName = string.Empty;
|
||||
|
||||
internal EditorHelper(PluginUi ui) {
|
||||
this.Ui = ui;
|
||||
|
@ -25,13 +24,9 @@ namespace Glamaholic.Ui.Helpers {
|
|||
}
|
||||
|
||||
private void DrawDropdown() {
|
||||
if (ImGui.Selectable($"Open {Plugin.Name}")) {
|
||||
if (ImGui.Selectable($"Open {this.Ui.Plugin.Name}")) {
|
||||
this.Ui.OpenMainInterface();
|
||||
}
|
||||
|
||||
if (HelperUtil.DrawCreatePlateMenu(this.Ui, () => GameFunctions.CurrentPlate, ref this._plateName)) {
|
||||
this._plateName = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ using ImGuiNET;
|
|||
namespace Glamaholic.Ui.Helpers {
|
||||
internal class ExamineHelper {
|
||||
private PluginUi Ui { get; }
|
||||
private string _nameInput = string.Empty;
|
||||
|
||||
internal ExamineHelper(PluginUi ui) {
|
||||
this.Ui = ui;
|
||||
|
@ -27,16 +26,10 @@ namespace Glamaholic.Ui.Helpers {
|
|||
}
|
||||
|
||||
private void DrawDropdown() {
|
||||
if (ImGui.Selectable($"Open {Plugin.Name}")) {
|
||||
this.Ui.OpenMainInterface();
|
||||
if (ImGui.Selectable("Create glamour plate")) {
|
||||
this.CopyToGlamourPlate();
|
||||
}
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
this._nameInput = this.Ui.Plugin.Functions.ExamineName ?? "Copied glamour";
|
||||
}
|
||||
|
||||
HelperUtil.DrawCreatePlateMenu(this.Ui, GetItems, ref this._nameInput);
|
||||
|
||||
if (ImGui.Selectable("Try on")) {
|
||||
var items = GetItems();
|
||||
if (items != null) {
|
||||
|
@ -65,7 +58,7 @@ namespace Glamaholic.Ui.Helpers {
|
|||
|
||||
var stainId = item.Stain;
|
||||
|
||||
// for some reason, this still accounts for belts in EW
|
||||
// TODO: remove this logic in endwalker
|
||||
var slot = i > 5 ? i - 1 : i;
|
||||
items[(PlateSlot) slot] = new SavedGlamourItem {
|
||||
ItemId = itemId,
|
||||
|
@ -75,5 +68,31 @@ namespace Glamaholic.Ui.Helpers {
|
|||
|
||||
return items;
|
||||
}
|
||||
|
||||
private unsafe void CopyToGlamourPlate() {
|
||||
var inventory = InventoryManager.Instance()->GetInventoryContainer(InventoryType.Examine);
|
||||
if (inventory == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var name = this.Ui.Plugin.Functions.ExamineName;
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
name = "Copied glamour";
|
||||
}
|
||||
|
||||
var items = GetItems();
|
||||
if (items == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var plate = new SavedPlate(name) {
|
||||
Items = items,
|
||||
};
|
||||
|
||||
this.Ui.Plugin.Config.AddPlate(plate);
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
this.Ui.OpenMainInterface();
|
||||
this.Ui.SwitchPlate(this.Ui.Plugin.Config.Plates.Count - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Logging;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
|
||||
|
@ -44,7 +44,7 @@ namespace Glamaholic.Ui.Helpers {
|
|||
|
||||
internal static float DropdownWidth() {
|
||||
// arrow size is GetFrameHeight
|
||||
return (ImGui.CalcTextSize(Plugin.Name).X + ImGui.GetStyle().ItemInnerSpacing.X * 2 + ImGui.GetFrameHeight()) * ImGuiHelpers.GlobalScale;
|
||||
return (ImGui.CalcTextSize(Plugin.PluginName).X + ImGui.GetStyle().ItemInnerSpacing.X * 2 + ImGui.GetFrameHeight()) * ImGuiHelpers.GlobalScale;
|
||||
}
|
||||
|
||||
internal class HelperStyles : IDisposable {
|
||||
|
@ -75,11 +75,11 @@ namespace Glamaholic.Ui.Helpers {
|
|||
}
|
||||
|
||||
ImGui.SetNextItemWidth(DropdownWidth());
|
||||
if (ImGui.BeginCombo($"##{id}-combo", Plugin.Name)) {
|
||||
if (ImGui.BeginCombo($"##{id}-combo", Plugin.PluginName)) {
|
||||
try {
|
||||
dropdown();
|
||||
} catch (Exception ex) {
|
||||
Plugin.Log.Error(ex, "Error drawing helper combo");
|
||||
PluginLog.LogError(ex, "Error drawing helper combo");
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
|
@ -89,75 +89,5 @@ namespace Glamaholic.Ui.Helpers {
|
|||
|
||||
ImGui.End();
|
||||
}
|
||||
|
||||
internal static bool DrawCreatePlateMenu(PluginUi ui, Func<Dictionary<PlateSlot, SavedGlamourItem>?> getter, ref string nameInput) {
|
||||
var ret = false;
|
||||
|
||||
if (!ImGui.BeginMenu("Create glamour plate")) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const string msg = "Enter a name and press Enter to create a new plate, or choose a plate below to overwrite.";
|
||||
ImGui.PushTextWrapPos(250);
|
||||
if (Util.DrawTextInput("current-name", ref nameInput, message: msg, flags: ImGuiInputTextFlags.AutoSelectAll)) {
|
||||
var items = getter();
|
||||
if (items != null) {
|
||||
CopyToGlamourPlate(ui, nameInput, items, -1);
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.PopTextWrapPos();
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
ImGui.SetKeyboardFocusHere(-1);
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
if (ImGui.BeginChild("helper-overwrite", new Vector2(250, 350))) {
|
||||
for (var i = 0; i < ui.Plugin.Config.Plates.Count; i++) {
|
||||
var plate = ui.Plugin.Config.Plates[i];
|
||||
var ctrl = ImGui.GetIO().KeyCtrl;
|
||||
if (ImGui.Selectable($"{plate.Name}##{i}") && ctrl) {
|
||||
var items = getter();
|
||||
if (items != null) {
|
||||
CopyToGlamourPlate(ui, plate.Name, items, i);
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctrl && ImGui.IsItemHovered()) {
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted("Hold Control and click to overwrite.");
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
ImGui.EndMenu();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void CopyToGlamourPlate(PluginUi ui, string name, Dictionary<PlateSlot, SavedGlamourItem> items, int idx) {
|
||||
var plate = new SavedPlate(name) {
|
||||
Items = items,
|
||||
};
|
||||
|
||||
Configuration.SanitisePlate(plate);
|
||||
|
||||
if (idx == -1) {
|
||||
ui.Plugin.Config.AddPlate(plate);
|
||||
} else {
|
||||
ui.Plugin.Config.Plates[idx] = plate;
|
||||
}
|
||||
|
||||
ui.Plugin.SaveConfig();
|
||||
ui.OpenMainInterface();
|
||||
ui.SwitchPlate(idx == -1 ? ui.Plugin.Config.Plates.Count - 1 : idx, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
@ -9,10 +8,7 @@ using ImGuiNET;
|
|||
|
||||
namespace Glamaholic.Ui.Helpers {
|
||||
internal class TryOnHelper {
|
||||
private const string PlateName = "Fitting Room";
|
||||
|
||||
private PluginUi Ui { get; }
|
||||
private string _nameInput = PlateName;
|
||||
|
||||
internal TryOnHelper(PluginUi ui) {
|
||||
this.Ui = ui;
|
||||
|
@ -29,32 +25,30 @@ namespace Glamaholic.Ui.Helpers {
|
|||
return;
|
||||
}
|
||||
|
||||
var right = this.Ui.Plugin.Interface.InstalledPlugins.Any(state => state.InternalName == "ItemSearchPlugin");
|
||||
var right = this.Ui.Plugin.Interface.PluginInternalNames.Contains("ItemSearchPlugin");
|
||||
HelperUtil.DrawHelper(tryOnAddon, "glamaholic-helper-try-on", right, this.DrawDropdown);
|
||||
}
|
||||
|
||||
private void DrawDropdown() {
|
||||
if (ImGui.Selectable($"Open {Plugin.Name}")) {
|
||||
if (ImGui.Selectable("Create glamour plate")) {
|
||||
this.Ui.Plugin.Config.AddPlate(new SavedPlate("Fitting Room") {
|
||||
Items = GetTryOnItems(),
|
||||
});
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
|
||||
this.Ui.OpenMainInterface();
|
||||
}
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
this._nameInput = PlateName;
|
||||
}
|
||||
|
||||
if (HelperUtil.DrawCreatePlateMenu(this.Ui, GetTryOnItems, ref this._nameInput)) {
|
||||
this._nameInput = PlateName;
|
||||
this.Ui.SwitchPlate(this.Ui.Plugin.Config.Plates.Count - 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe Dictionary<PlateSlot, SavedGlamourItem> GetTryOnItems() {
|
||||
var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Tryon);
|
||||
var firstItem = agent + 0x314;
|
||||
var firstItem = agent + 0x2E8;
|
||||
|
||||
var items = new Dictionary<PlateSlot, SavedGlamourItem>();
|
||||
|
||||
|
||||
for (var i = 0; i < 12; i++) {
|
||||
var item = (TryOnItem*) (firstItem + i * 28);
|
||||
var item = (TryOnItem*) (firstItem + i * 24);
|
||||
if (item->Slot == 14 || item->ItemId == 0) {
|
||||
continue;
|
||||
}
|
||||
|
@ -63,23 +57,19 @@ namespace Glamaholic.Ui.Helpers {
|
|||
if (item->GlamourId != 0) {
|
||||
itemId = item->GlamourId;
|
||||
}
|
||||
|
||||
var stainId = item->StainPreviewId == 0
|
||||
? item->StainId
|
||||
: item->StainPreviewId;
|
||||
|
||||
// for some reason, this still accounts for belts in EW
|
||||
|
||||
// TODO: remove this logic in endwalker
|
||||
var slot = item->Slot > 5 ? item->Slot - 1 : item->Slot;
|
||||
items[(PlateSlot) slot] = new SavedGlamourItem {
|
||||
items[(PlateSlot) slot] =new SavedGlamourItem {
|
||||
ItemId = itemId % Util.HqItemOffset,
|
||||
StainId = stainId,
|
||||
StainId = item->StainId,
|
||||
};
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 28)]
|
||||
[StructLayout(LayoutKind.Explicit, Size = 24)]
|
||||
private readonly struct TryOnItem {
|
||||
[FieldOffset(0)]
|
||||
internal readonly byte Slot;
|
||||
|
@ -87,16 +77,13 @@ namespace Glamaholic.Ui.Helpers {
|
|||
[FieldOffset(2)]
|
||||
internal readonly byte StainId;
|
||||
|
||||
[FieldOffset(3)]
|
||||
internal readonly byte StainPreviewId;
|
||||
|
||||
[FieldOffset(5)]
|
||||
internal readonly byte UnknownByte;
|
||||
|
||||
[FieldOffset(12)]
|
||||
[FieldOffset(8)]
|
||||
internal readonly uint ItemId;
|
||||
|
||||
[FieldOffset(16)]
|
||||
[FieldOffset(12)]
|
||||
internal readonly uint GlamourId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -39,39 +34,32 @@ namespace Glamaholic.Ui {
|
|||
private PluginUi Ui { get; }
|
||||
private List<Item> Items { get; }
|
||||
private List<Item> FilteredItems { get; set; }
|
||||
private Dictionary<string, byte> Stains { get; }
|
||||
|
||||
private FilterInfo? PlateFilter { get; set; }
|
||||
|
||||
private bool _visible;
|
||||
private string _plateName = string.Empty;
|
||||
private int _dragging = -1;
|
||||
private int _selectedPlate = -1;
|
||||
private bool _scrollToSelected;
|
||||
private string _plateFilter = string.Empty;
|
||||
private bool _showRename;
|
||||
private string _renameInput = string.Empty;
|
||||
private string _importInput = string.Empty;
|
||||
private Exception? _importError;
|
||||
private bool _deleteConfirm;
|
||||
private readonly Stopwatch _shareTimer = new();
|
||||
private bool _editing;
|
||||
private SavedPlate? _editingPlate;
|
||||
private string _itemFilter = string.Empty;
|
||||
private string _dyeFilter = string.Empty;
|
||||
private volatile bool _ecImporting;
|
||||
private readonly Dictionary<string, Stopwatch> _timedMessages = new();
|
||||
private string _tagInput = string.Empty;
|
||||
|
||||
internal MainInterface(PluginUi ui) {
|
||||
this.Ui = ui;
|
||||
|
||||
// get all equippable items that aren't soul crystals
|
||||
this.Items = this.Ui.Plugin.DataManager.GetExcelSheet<Item>(ClientLanguage.English)!
|
||||
this.Items = this.Ui.Plugin.DataManager.GetExcelSheet<Item>()!
|
||||
.Where(row => row.EquipSlotCategory.Row is not 0 && row.EquipSlotCategory.Value!.SoulCrystal == 0)
|
||||
.ToList();
|
||||
this.FilteredItems = this.Items;
|
||||
|
||||
this.Stains = this.Ui.Plugin.DataManager.GetExcelSheet<Stain>(ClientLanguage.English)!
|
||||
.Where(row => row.RowId != 0)
|
||||
.Where(row => !string.IsNullOrWhiteSpace(row.Name.RawString))
|
||||
.ToDictionary(row => row.Name.RawString, row => (byte) row.RowId);
|
||||
}
|
||||
|
||||
internal void Open() {
|
||||
|
@ -91,7 +79,7 @@ namespace Glamaholic.Ui {
|
|||
|
||||
ImGui.SetNextWindowSize(new Vector2(415, 650), ImGuiCond.FirstUseEver);
|
||||
|
||||
if (!ImGui.Begin(Plugin.Name, ref this._visible, ImGuiWindowFlags.MenuBar)) {
|
||||
if (!ImGui.Begin(this.Ui.Plugin.Name, ref this._visible, ImGuiWindowFlags.MenuBar)) {
|
||||
ImGui.End();
|
||||
return;
|
||||
}
|
||||
|
@ -101,14 +89,6 @@ namespace Glamaholic.Ui {
|
|||
ImGui.End();
|
||||
}
|
||||
|
||||
private static bool IsValidEorzeaCollectionUrl(string urlString) {
|
||||
if (!Uri.TryCreate(urlString, UriKind.Absolute, out var url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.Host == "ffxiv.eorzeacollection.com" && url.AbsolutePath.StartsWith("/glamour/");
|
||||
}
|
||||
|
||||
private void DrawMenuBar() {
|
||||
if (!ImGui.BeginMenuBar()) {
|
||||
return;
|
||||
|
@ -121,24 +101,53 @@ namespace Glamaholic.Ui {
|
|||
this.SwitchPlate(this.Ui.Plugin.Config.Plates.Count - 1, true);
|
||||
}
|
||||
|
||||
if (ImGui.BeginMenu("Import")) {
|
||||
if (ImGui.MenuItem("Clipboard")) {
|
||||
var json = Util.GetClipboardText();
|
||||
try {
|
||||
var plate = JsonConvert.DeserializeObject<SharedPlate>(json);
|
||||
if (plate != null) {
|
||||
this.Ui.Plugin.Config.AddPlate(plate.ToPlate());
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
this.Ui.SwitchPlate(this.Ui.Plugin.Config.Plates.Count - 1);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Plugin.Log.Warning(ex, "Failed to import glamour plate");
|
||||
if (ImGui.BeginMenu("Add from current plate")) {
|
||||
if (Util.DrawTextInput("current-name", ref this._plateName, message: "Input name and press Enter to save.")) {
|
||||
var current = GameFunctions.CurrentPlate;
|
||||
if (current != null) {
|
||||
var plate = new SavedPlate(this._plateName) {
|
||||
Items = current,
|
||||
};
|
||||
|
||||
this.Ui.Plugin.Config.AddPlate(plate);
|
||||
|
||||
this._plateName = string.Empty;
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
var validUrl = IsValidEorzeaCollectionUrl(Util.GetClipboardText());
|
||||
if (ImGui.MenuItem("Copied Eorzea Collection URL", validUrl) && !this._ecImporting) {
|
||||
this.ImportEorzeaCollection(Util.GetClipboardText());
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
}
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui.BeginMenu("Import")) {
|
||||
if (Util.DrawTextInput("import-input", ref this._importInput, 2048, "Press Enter to import.")) {
|
||||
try {
|
||||
var plate = JsonConvert.DeserializeObject<SavedPlate>(this._importInput);
|
||||
this._importError = null;
|
||||
if (plate != null) {
|
||||
this.Ui.Plugin.Config.AddPlate(plate);
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
}
|
||||
|
||||
this._importInput = string.Empty;
|
||||
} catch (Exception ex) {
|
||||
this._importError = ex;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
this._importError = null;
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
}
|
||||
|
||||
if (this._importError != null) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
||||
Util.TextUnformattedWrapped(this._importError.Message);
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.EndMenu();
|
||||
|
@ -185,7 +194,7 @@ namespace Glamaholic.Ui {
|
|||
ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFFFFF);
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, 0x00000000);
|
||||
if (ImGui.MenuItem(kofiText)) {
|
||||
Process.Start(new ProcessStartInfo("https://ko-fi.com/lojewalo") {
|
||||
Process.Start(new ProcessStartInfo("https://ko-fi.com/ascclemens") {
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
|
@ -200,105 +209,21 @@ namespace Glamaholic.Ui {
|
|||
ImGui.EndMenuBar();
|
||||
}
|
||||
|
||||
private void ImportEorzeaCollection(string url) {
|
||||
if (!IsValidEorzeaCollectionUrl(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._ecImporting = true;
|
||||
|
||||
Task.Run(async () => {
|
||||
var items = new Dictionary<PlateSlot, SavedGlamourItem>();
|
||||
|
||||
var client = new HttpClient();
|
||||
var resp = await client.GetAsync(url);
|
||||
var html = await resp.Content.ReadAsStringAsync();
|
||||
|
||||
var titleParts = html.Split("<title>");
|
||||
var glamName = titleParts.Length > 1
|
||||
? WebUtility.HtmlDecode(titleParts[1].Split('<')[0].Split('|')[0].Trim())
|
||||
: "Eorzea Collection plate";
|
||||
|
||||
var parts = html.Split("c-gear-slot-item-name");
|
||||
foreach (var part in parts) {
|
||||
var nameParts = part.Split('>');
|
||||
if (nameParts.Length < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var rawName = nameParts[1].Split('<')[0].Trim();
|
||||
var name = WebUtility.HtmlDecode(rawName);
|
||||
if (string.IsNullOrWhiteSpace(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var item = this.Items.Find(item => item.Name == name);
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var slot = Util.GetSlot(item);
|
||||
if (slot is PlateSlot.RightRing && items.ContainsKey(PlateSlot.RightRing)) {
|
||||
slot = PlateSlot.LeftRing;
|
||||
}
|
||||
|
||||
if (slot == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var stainId = item.IsDyeable ? this.GetStainIdFromPart(part) : (byte) 0;
|
||||
items[slot.Value] = new SavedGlamourItem {
|
||||
ItemId = item.RowId,
|
||||
StainId = stainId,
|
||||
};
|
||||
}
|
||||
|
||||
this._ecImporting = false;
|
||||
|
||||
var plate = new SavedPlate(glamName) {
|
||||
Items = items,
|
||||
};
|
||||
this.Ui.Plugin.Config.AddPlate(plate);
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
this.SwitchPlate(this.Ui.Plugin.Config.Plates.Count - 1, true);
|
||||
});
|
||||
}
|
||||
|
||||
private byte GetStainIdFromPart(string part) {
|
||||
var stainParts = part.Split('⬤');
|
||||
if (stainParts.Length <= 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var stainSubParts = stainParts[1].Split('>');
|
||||
if (stainSubParts.Length <= 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var rawStainName = stainSubParts[1].Split('<')[0].Trim();
|
||||
var stainName = WebUtility.HtmlDecode(rawStainName);
|
||||
this.Stains.TryGetValue(stainName, out var stainId);
|
||||
return stainId;
|
||||
}
|
||||
|
||||
private void DrawPlateList() {
|
||||
if (!ImGui.BeginChild("plate list", new Vector2(205 * ImGuiHelpers.GlobalScale, 0), true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
if (ImGui.InputTextWithHint("##plate-filter", "Search...", ref this._plateFilter, 512, ImGuiInputTextFlags.AutoSelectAll)) {
|
||||
this.PlateFilter = this._plateFilter.Length == 0
|
||||
? null
|
||||
: new FilterInfo(this.Ui.Plugin.DataManager, this._plateFilter);
|
||||
}
|
||||
ImGui.InputText("##plate-filter", ref this._plateFilter, 512, ImGuiInputTextFlags.AutoSelectAll);
|
||||
|
||||
var filter = this._plateFilter.ToLowerInvariant();
|
||||
(int src, int dst)? drag = null;
|
||||
if (ImGui.BeginChild("plate list actual", Vector2.Zero, false, ImGuiWindowFlags.HorizontalScrollbar)) {
|
||||
for (var i = 0; i < this.Ui.Plugin.Config.Plates.Count; i++) {
|
||||
var plate = this.Ui.Plugin.Config.Plates[i];
|
||||
|
||||
if (this.PlateFilter != null && !this.PlateFilter.Matches(plate)) {
|
||||
if (filter.Length != 0 && !plate.Name.ToLowerInvariant().Contains(filter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -410,7 +335,7 @@ namespace Glamaholic.Ui {
|
|||
ImGui.InputText("##dye-filter", ref this._dyeFilter, 512);
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
ImGui.SetKeyboardFocusHere(-1);
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
}
|
||||
|
||||
if (ImGui.BeginChild("dye picker", new Vector2(250, 350), false, ImGuiWindowFlags.HorizontalScrollbar)) {
|
||||
|
@ -448,21 +373,12 @@ namespace Glamaholic.Ui {
|
|||
}
|
||||
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
if (ImGui.InputTextWithHint("##item-filter", "Search...", ref this._itemFilter, 512, ImGuiInputTextFlags.AutoSelectAll)) {
|
||||
if (ImGui.InputText("##item-filter", ref this._itemFilter, 512, ImGuiInputTextFlags.AutoSelectAll)) {
|
||||
this.FilterItems(slot);
|
||||
}
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
ImGui.SetKeyboardFocusHere(-1);
|
||||
}
|
||||
|
||||
if (GameFunctions.DresserContents.Count > 0) {
|
||||
if (ImGui.Checkbox("Only show items in the armoire/dresser", ref this.Ui.Plugin.Config.ItemFilterShowObtainedOnly)) {
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
this.FilterItems(slot);
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
}
|
||||
|
||||
if (ImGui.BeginChild("item search", new Vector2(250, 450), false, ImGuiWindowFlags.HorizontalScrollbar)) {
|
||||
|
@ -473,26 +389,16 @@ namespace Glamaholic.Ui {
|
|||
id = null;
|
||||
}
|
||||
|
||||
if (ImGui.Selectable("##none-keep", id == null)) {
|
||||
if (ImGui.Selectable("None (keep existing)", id == null)) {
|
||||
plate.Items.Remove(slot);
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
Util.TextIcon(FontAwesomeIcon.Box);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted("None (keep existing)");
|
||||
|
||||
if (ImGui.Selectable("##none-remove)", id == 0)) {
|
||||
if (ImGui.Selectable("None (remove existing)", id == 0)) {
|
||||
plate.Items[slot] = new SavedGlamourItem();
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
Util.TextIcon(FontAwesomeIcon.Box);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted("None (remove existing)");
|
||||
|
||||
var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
|
||||
|
||||
clipper.Begin(this.FilteredItems.Count);
|
||||
|
@ -500,7 +406,7 @@ namespace Glamaholic.Ui {
|
|||
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
|
||||
var item = this.FilteredItems[i];
|
||||
|
||||
if (ImGui.Selectable($"##{item.RowId}", item.RowId == id)) {
|
||||
if (ImGui.Selectable($"{item.Name}##{item.RowId}", item.RowId == id)) {
|
||||
if (!plate.Items.ContainsKey(slot)) {
|
||||
plate.Items[slot] = new SavedGlamourItem();
|
||||
}
|
||||
|
@ -516,24 +422,6 @@ namespace Glamaholic.Ui {
|
|||
if (Util.IsItemMiddleOrCtrlClicked()) {
|
||||
this.Ui.AlternativeFinders.Add(new AlternativeFinder(this.Ui, item));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var has = GameFunctions.DresserContents.Any(saved => saved.ItemId % Util.HqItemOffset == item.RowId) || this.Ui.Plugin.Functions.IsInArmoire(item.RowId);
|
||||
|
||||
if (!has) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]);
|
||||
}
|
||||
|
||||
Util.TextIcon(FontAwesomeIcon.Box);
|
||||
|
||||
if (!has) {
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.TextUnformatted($"{item.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -543,7 +431,7 @@ namespace Glamaholic.Ui {
|
|||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
private unsafe void DrawIcon(PlateSlot slot, SavedPlate plate, int iconSize, int paddingSize) {
|
||||
private unsafe void DrawIcon(PlateSlot slot, SavedPlate plate, bool editingPlate, int iconSize, int paddingSize) {
|
||||
var drawCursor = ImGui.GetCursorScreenPos();
|
||||
var tooltip = slot.Name();
|
||||
ImGui.BeginGroup();
|
||||
|
@ -553,7 +441,7 @@ namespace Glamaholic.Ui {
|
|||
var borderColour = *ImGui.GetStyleColorVec4(ImGuiCol.Border);
|
||||
|
||||
// check for item
|
||||
if (mirage != null && mirage.ItemId != 0 && GameFunctions.DresserContents.Count > 0) {
|
||||
if (mirage != null && mirage.ItemId != 0 && editingPlate) {
|
||||
var has = GameFunctions.DresserContents.Any(saved => saved.ItemId % Util.HqItemOffset == mirage.ItemId) || this.Ui.Plugin.Functions.IsInArmoire(mirage.ItemId);
|
||||
if (!has) {
|
||||
borderColour = ImGuiColors.DalamudYellow;
|
||||
|
@ -650,7 +538,7 @@ namespace Glamaholic.Ui {
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawPlatePreview(SavedPlate plate) {
|
||||
private void DrawPlatePreview(bool editingPlate, SavedPlate plate) {
|
||||
const int paddingSize = 12;
|
||||
|
||||
if (!ImGui.BeginTable("plate item preview", 2, ImGuiTableFlags.SizingFixedFit)) {
|
||||
|
@ -659,9 +547,9 @@ namespace Glamaholic.Ui {
|
|||
|
||||
foreach (var (left, right) in LeftSide.Zip(RightSide)) {
|
||||
ImGui.TableNextColumn();
|
||||
this.DrawIcon(left, plate, IconSize, paddingSize);
|
||||
this.DrawIcon(left, plate, editingPlate, IconSize, paddingSize);
|
||||
ImGui.TableNextColumn();
|
||||
this.DrawIcon(right, plate, IconSize, paddingSize);
|
||||
this.DrawIcon(right, plate, editingPlate, IconSize, paddingSize);
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
|
@ -674,11 +562,7 @@ namespace Glamaholic.Ui {
|
|||
|
||||
ImGui.TableNextColumn();
|
||||
if (Util.IconButton(FontAwesomeIcon.Check, tooltip: "Apply")) {
|
||||
if (!Util.IsEditingPlate(this.Ui.Plugin.GameGui)) {
|
||||
this.AddTimedMessage("The in-game plate editor must be open.");
|
||||
} else {
|
||||
this.Ui.Plugin.Functions.LoadPlate(plate);
|
||||
}
|
||||
this.Ui.Plugin.Functions.LoadPlate(plate);
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
@ -700,60 +584,14 @@ namespace Glamaholic.Ui {
|
|||
|
||||
ImGui.TableNextColumn();
|
||||
if (Util.IconButton(FontAwesomeIcon.ShareAltSquare, tooltip: "Share")) {
|
||||
ImGui.SetClipboardText(JsonConvert.SerializeObject(new SharedPlate(plate)));
|
||||
this.AddTimedMessage("Copied to clipboard.");
|
||||
ImGui.SetClipboardText(JsonConvert.SerializeObject(plate));
|
||||
this._shareTimer.Start();
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
private void DrawPlateTags(SavedPlate plate) {
|
||||
if (this._editing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ImGui.CollapsingHeader($"Tags ({plate.Tags.Count})###plate-tags")) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
if (ImGui.InputTextWithHint("##tag-input", "Input a tag and press Enter", ref this._tagInput, 128, ImGuiInputTextFlags.EnterReturnsTrue)) {
|
||||
if (!string.IsNullOrWhiteSpace(this._tagInput)) {
|
||||
var tag = this._tagInput.Trim();
|
||||
|
||||
if (!plate.Tags.Contains(tag)) {
|
||||
plate.Tags.Add(tag);
|
||||
plate.Tags.Sort();
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
this._tagInput = string.Empty;
|
||||
}
|
||||
|
||||
if (ImGui.BeginChild("tag-list")) {
|
||||
var toRemove = -1;
|
||||
for (var i = 0; i < plate.Tags.Count; i++) {
|
||||
var tag = plate.Tags[i];
|
||||
|
||||
if (Util.IconButton(FontAwesomeIcon.Times, $"remove-tag-{i}")) {
|
||||
toRemove = i;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(tag);
|
||||
}
|
||||
|
||||
if (toRemove > -1) {
|
||||
plate.Tags.RemoveAt(toRemove);
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPlateDetail() {
|
||||
private void DrawPlateDetail(bool editingPlate) {
|
||||
if (!ImGui.BeginChild("plate detail")) {
|
||||
return;
|
||||
}
|
||||
|
@ -761,14 +599,14 @@ namespace Glamaholic.Ui {
|
|||
if (this._selectedPlate > -1 && this._selectedPlate < this.Ui.Plugin.Config.Plates.Count) {
|
||||
var plate = this._editingPlate ?? this.Ui.Plugin.Config.Plates[this._selectedPlate];
|
||||
|
||||
this.DrawPlatePreview(plate);
|
||||
this.DrawPlatePreview(editingPlate, plate);
|
||||
|
||||
var renameWasVisible = this._showRename;
|
||||
|
||||
this.DrawPlateButtons(plate);
|
||||
|
||||
foreach (var (msg, _) in this._timedMessages) {
|
||||
Util.TextUnformattedWrapped(msg);
|
||||
if (this._shareTimer.IsRunning) {
|
||||
Util.TextUnformattedWrapped("Copied to clipboard.");
|
||||
}
|
||||
|
||||
if (this._showRename && Util.DrawTextInput("plate-rename", ref this._renameInput, flags: ImGuiInputTextFlags.AutoSelectAll)) {
|
||||
|
@ -778,7 +616,13 @@ namespace Glamaholic.Ui {
|
|||
}
|
||||
|
||||
if (this._showRename && !renameWasVisible) {
|
||||
ImGui.SetKeyboardFocusHere(-1);
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
}
|
||||
|
||||
if (!this.Ui.Plugin.Functions.ArmoireLoaded) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
|
||||
Util.TextUnformattedWrapped("The Armoire is not loaded. Open it once to enable glamours from the Armoire.");
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
if (this._editing) {
|
||||
|
@ -796,81 +640,44 @@ namespace Glamaholic.Ui {
|
|||
this.ResetEditing();
|
||||
}
|
||||
}
|
||||
|
||||
this.DrawPlateTags(plate);
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
private void DrawWarnings() {
|
||||
var warnings = new List<string>();
|
||||
|
||||
if (!this.Ui.Plugin.Functions.ArmoireLoaded) {
|
||||
warnings.Add("The Armoire is not loaded. Open it once to enable glamours from the Armoire.");
|
||||
}
|
||||
|
||||
if (GameFunctions.DresserContents.Count == 0) {
|
||||
warnings.Add("Glamour Dresser is empty or has not been opened. Glamaholic will not know which items you have.");
|
||||
}
|
||||
|
||||
if (warnings.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
|
||||
var header = ImGui.CollapsingHeader($"Warnings ({warnings.Count})###warnings");
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
if (!header) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < warnings.Count; i++) {
|
||||
if (i != 0) {
|
||||
ImGui.Separator();
|
||||
}
|
||||
|
||||
Util.TextUnformattedWrapped(warnings[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawInner() {
|
||||
var editingPlate = Util.IsEditingPlate(this.Ui.Plugin.GameGui);
|
||||
|
||||
this.DrawMenuBar();
|
||||
|
||||
this.DrawWarnings();
|
||||
if (!editingPlate) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
|
||||
Util.TextUnformattedWrapped("Glamour Plate editor is not open. Certain functions will not work.");
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
this.DrawPlateList();
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
this.DrawPlateDetail();
|
||||
this.DrawPlateDetail(editingPlate);
|
||||
|
||||
ImGui.End();
|
||||
}
|
||||
|
||||
private void HandleTimers() {
|
||||
var keys = this._timedMessages.Keys.ToArray();
|
||||
foreach (var key in keys) {
|
||||
if (this._timedMessages[key].Elapsed > TimeSpan.FromSeconds(5)) {
|
||||
this._timedMessages.Remove(key);
|
||||
}
|
||||
if (this._shareTimer.Elapsed > TimeSpan.FromSeconds(5)) {
|
||||
this._shareTimer.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTimedMessage(string message) {
|
||||
var timer = new Stopwatch();
|
||||
timer.Start();
|
||||
this._timedMessages[message] = timer;
|
||||
}
|
||||
|
||||
internal void SwitchPlate(int idx, bool scrollTo = false) {
|
||||
this._selectedPlate = idx;
|
||||
this._scrollToSelected = scrollTo;
|
||||
this._renameInput = string.Empty;
|
||||
this._showRename = false;
|
||||
this._deleteConfirm = false;
|
||||
this._timedMessages.Clear();
|
||||
this._shareTimer.Reset();
|
||||
this.ResetEditing();
|
||||
}
|
||||
|
||||
|
@ -883,19 +690,7 @@ namespace Glamaholic.Ui {
|
|||
|
||||
private void FilterItems(PlateSlot slot) {
|
||||
var filter = this._itemFilter.ToLowerInvariant();
|
||||
|
||||
IEnumerable<Item> items;
|
||||
if (GameFunctions.DresserContents.Count > 0 && this.Ui.Plugin.Config.ItemFilterShowObtainedOnly) {
|
||||
var sheet = this.Ui.Plugin.DataManager.GetExcelSheet<Item>()!;
|
||||
items = GameFunctions.DresserContents
|
||||
.Select(item => sheet.GetRow(item.ItemId))
|
||||
.Where(item => item != null)
|
||||
.Cast<Item>();
|
||||
} else {
|
||||
items = this.Items;
|
||||
}
|
||||
|
||||
this.FilteredItems = items
|
||||
this.FilteredItems = this.Items
|
||||
.Where(item => !Util.IsItemSkipped(item))
|
||||
.Where(item => Util.MatchesSlot(item.EquipSlotCategory.Value!, slot))
|
||||
.Where(item => this._itemFilter.Length == 0 || item.Name.RawString.ToLowerInvariant().Contains(filter))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
@ -17,12 +17,12 @@ namespace Glamaholic {
|
|||
return addon != null && addon->IsVisible;
|
||||
}
|
||||
|
||||
private static unsafe bool IsOpen(IGameGui gui, string name) {
|
||||
private static unsafe bool IsOpen(GameGui gui, string name) {
|
||||
var addon = (AtkUnitBase*) gui.GetAddonByName(name, 1);
|
||||
return IsOpen(addon);
|
||||
}
|
||||
|
||||
internal static bool IsEditingPlate(IGameGui gui) {
|
||||
internal static bool IsEditingPlate(GameGui gui) {
|
||||
var plateOpen = IsOpen(gui, PlateAddon);
|
||||
var boxOpen = IsOpen(gui, BoxAddon);
|
||||
var armoireOpen = IsOpen(gui, ArmoireAddon);
|
||||
|
@ -66,63 +66,6 @@ namespace Glamaholic {
|
|||
ImGui.PopTextWrapPos();
|
||||
}
|
||||
|
||||
internal static PlateSlot? GetSlot(Item item) {
|
||||
var category = item.EquipSlotCategory.Value;
|
||||
if (category == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (category.MainHand > 0) {
|
||||
return PlateSlot.MainHand;
|
||||
}
|
||||
|
||||
if (category.OffHand > 0) {
|
||||
return PlateSlot.OffHand;
|
||||
}
|
||||
|
||||
if (category.Head > 0) {
|
||||
return PlateSlot.Head;
|
||||
}
|
||||
|
||||
if (category.Body > 0) {
|
||||
return PlateSlot.Body;
|
||||
}
|
||||
|
||||
if (category.Gloves > 0) {
|
||||
return PlateSlot.Hands;
|
||||
}
|
||||
|
||||
if (category.Legs > 0) {
|
||||
return PlateSlot.Legs;
|
||||
}
|
||||
|
||||
if (category.Feet > 0) {
|
||||
return PlateSlot.Feet;
|
||||
}
|
||||
|
||||
if (category.Ears > 0) {
|
||||
return PlateSlot.Ears;
|
||||
}
|
||||
|
||||
if (category.Neck > 0) {
|
||||
return PlateSlot.Neck;
|
||||
}
|
||||
|
||||
if (category.Wrists > 0) {
|
||||
return PlateSlot.Wrists;
|
||||
}
|
||||
|
||||
if (category.FingerR > 0) {
|
||||
return PlateSlot.RightRing;
|
||||
}
|
||||
|
||||
if (category.FingerL > 0) {
|
||||
return PlateSlot.LeftRing;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static bool MatchesSlot(EquipSlotCategory category, PlateSlot slot) {
|
||||
return slot switch {
|
||||
PlateSlot.MainHand => category.MainHand > 0,
|
||||
|
@ -160,19 +103,5 @@ namespace Glamaholic {
|
|||
_ => name.Length == 0 || name.StartsWith("Dated"),
|
||||
};
|
||||
}
|
||||
|
||||
internal static void TextIcon(FontAwesomeIcon icon) {
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
ImGui.TextUnformatted(icon.ToIconString());
|
||||
ImGui.PopFont();
|
||||
}
|
||||
|
||||
internal static string GetClipboardText() {
|
||||
try {
|
||||
return ImGui.GetClipboardText();
|
||||
} catch (Exception) {
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,16 @@ plates into the plugin or apply new plates.
|
|||
|
||||
# Adding glamour plates
|
||||
|
||||
- From the main Glamaholic window, click "Plates" and then "New" to create an
|
||||
empty glamour plate to edit from scratch.
|
||||
Click on the Plates menu and you can add plates to Glamaholic.
|
||||
|
||||
- From the main Glamaholic window, click "Plates", hover over "Import", paste
|
||||
a shared plate, then press Enter to import a shared plate.
|
||||
|
||||
- Click on the Glamaholic menu at the top left of the Glamour Plate Creation
|
||||
window, then hover over "Create glamour plate" to import plates from the game.
|
||||
- Click "New" to add an empty plate, starting from scratch.
|
||||
|
||||
- Hover over "Add from current plate", type in a name, and press Enter to copy a
|
||||
plate from your glamour dresser into Glamaholic.
|
||||
|
||||
- Hover over "Import" and paste in a shared glamour plate to import a plate from
|
||||
elsewhere.
|
||||
|
||||
- Open the Examine window a character, click the "Glamaholic" menu, then click
|
||||
"Create glamour plate" to create a glamour plate from someone else's outfit.
|
||||
|
@ -30,24 +32,3 @@ You can search for items that share the same model as another item by either
|
|||
middle-clicking (click with mouse wheel) or holding Control and left-clicking on
|
||||
any item name or item icon. This will open a window listing items with the same
|
||||
model where you can link or try them on.
|
||||
|
||||
---
|
||||
|
||||
# Advanced search
|
||||
|
||||
The plate search box can be used to search for more than just plate names. Try
|
||||
the options below.
|
||||
|
||||
- j:job can be used to search for a job by its abbreviation. Example: j:pld
|
||||
|
||||
- lvl:maxLevel can be used to search for plates equippable up to a specific
|
||||
level. Example: lvl:1
|
||||
|
||||
- t:tag can be used to search by tag.
|
||||
|
||||
- !t:tag can be used to exclude tags.
|
||||
|
||||
- id:itemId can be used to search for plates with the given item ID.
|
||||
|
||||
- i:itemName can be used to search for plates with the given item name. Example:
|
||||
i:skallic
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net7.0-windows7.0": {
|
||||
"DalamudPackager": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.1.12, )",
|
||||
"resolved": "2.1.12",
|
||||
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
|
||||
},
|
||||
"Fody": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.8.0, )",
|
||||
"resolved": "6.8.0",
|
||||
"contentHash": "hfZ/f8Mezt8aTkgv9nsvFdYoQ809/AqwsJlOGOPYIfBcG2aAIG3v3ex9d8ZqQuFYyMoucjRg4eKy3VleeGodKQ=="
|
||||
},
|
||||
"Resourcer.Fody": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.8.1, )",
|
||||
"resolved": "1.8.1",
|
||||
"contentHash": "FPeK4jKyyX5+mIjTnHNReGZk2/2xDhmu44UsBI5w9WEhbr4oTMmht3rnBr46A+GCGepC4+2N41K4vExDYiGNVQ==",
|
||||
"dependencies": {
|
||||
"Fody": "6.6.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue