This commit is contained in:
Ottermandias 2023-01-23 12:09:32 +01:00
parent 9c6256bf1b
commit 6e7e6530cf
12 changed files with 360 additions and 203 deletions

View file

@ -24,8 +24,8 @@ public readonly struct Item
public bool IsBothHand
=> (EquipSlot)Base.EquipSlotCategory.Row == EquipSlot.BothHand;
public WeaponCategory WeaponCategory
=> (WeaponCategory?) Base.ItemUICategory?.Row ?? WeaponCategory.Unknown;
public FullEquipType WeaponCategory
=> ((WeaponCategory) (Base.ItemUICategory?.Row ?? 0)).ToEquipType();
// Create a new item from its sheet list with the given name and either the inferred equip slot or the given one.
public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)

Binary file not shown.

View file

@ -0,0 +1,301 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Plugin;
using ImGuizmoNET;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Filesystem;
namespace Glamourer.Designs;
public sealed class DesignFileSystem : FileSystem<Design>, IDisposable
{
public readonly string DesignFileSystemFile;
private readonly Design.Manager _designManager;
public DesignFileSystem(Design.Manager designManager, DalamudPluginInterface pi)
{
DesignFileSystemFile = Path.Combine(pi.GetPluginConfigDirectory(), "sort_order.json");
_designManager = designManager;
}
public struct CreationDate : ISortMode<Design>
{
public string Name
=> "Creation Date (Older First)";
public string Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date.";
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate));
}
public struct InverseCreationDate : ISortMode<Design>
{
public string Name
=> "Creation Date (Newer First)";
public string Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date.";
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate));
}
private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3)
{
if (type != FileSystemChangeType.Reload)
{
SaveFilesystem();
}
}
private void SaveFilesystem()
{
SaveToFile(new FileInfo(DesignFileSystemFile), SaveDesign, true);
Glamourer.Log.Verbose($"Saved design filesystem.");
}
private void Save()
=> Glamourer.Framework.RegisterDelayed(nameof(SaveFilesystem), SaveFilesystem);
private void OnDataChange(Design.Manager.DesignChangeType type, Design design, string? oldName, string? _2, int _3)
{
switch (type)
{
}
if (type == Design.Manager.DesignChangeType.Renamed && oldName != null)
{
var old = oldName.FixName();
if (Find(old, out var child) && child is not Folder)
{
Rename(child, design.Name);
}
}
}
// Used for saving and loading.
private static string DesignToIdentifier(Design design)
=> design.Identifier.ToString();
private static string DesignToName(Design design)
=> design.Name.FixName();
private static bool DesignHasDefaultPath(Design design, string fullPath)
{
var regex = new Regex($@"^{Regex.Escape(DesignToName(design))}( \(\d+\))?$");
return regex.IsMatch(fullPath);
}
private static (string, bool) SaveDesign(Design design, string fullPath)
// Only save pairs with non-default paths.
=> DesignHasDefaultPath(design, fullPath)
? (string.Empty, false)
: (DesignToName(design), true);
}
public partial class Design
{
public partial class Manager
{
public const string DesignFolderName = "designs";
public readonly string DesignFolder;
private readonly List<Design> _designs = new();
public enum DesignChangeType
{
Created,
Deleted,
ReloadedAll,
Renamed,
ChangedDescription,
AddedTag,
RemovedTag,
ChangedTag,
}
public delegate void DesignChangeDelegate(DesignChangeType type, int designIdx, string? oldData = null, string? newData = null,
int tagIdx = -1);
public event DesignChangeDelegate? DesignChange;
public IReadOnlyList<Design> Designs
=> _designs;
public Manager(DalamudPluginInterface pi)
=> DesignFolder = SetDesignFolder(pi);
private static string SetDesignFolder(DalamudPluginInterface pi)
{
var ret = Path.Combine(pi.GetPluginConfigDirectory(), DesignFolderName);
if (Directory.Exists(ret))
return ret;
try
{
Directory.CreateDirectory(ret);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not create design folder directory at {ret}:\n{ex}");
}
return ret;
}
private string CreateFileName(Design design)
=> Path.Combine(DesignFolder, $"{design.Name.RemoveInvalidPathSymbols()}_{design.Identifier}.json");
public void SaveDesign(Design design)
{
var fileName = CreateFileName(design);
try
{
var data = design.JsonSerialize().ToString(Formatting.Indented);
File.WriteAllText(fileName, data);
Glamourer.Log.Debug($"Saved design {design.Identifier}.");
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not save design {design.Identifier} to file:\n{ex}");
}
}
public void LoadDesigns()
{
_designs.Clear();
foreach (var file in new DirectoryInfo(DesignFolder).EnumerateFiles("*.json", SearchOption.TopDirectoryOnly))
{
try
{
var text = File.ReadAllText(file.FullName);
var data = JObject.Parse(text);
var design = LoadDesign(data);
design.Index = _designs.Count;
_designs.Add(design);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not load design, skipped:\n{ex}");
}
}
Glamourer.Log.Information($"Loaded {_designs.Count} designs.");
DesignChange?.Invoke(DesignChangeType.ReloadedAll, -1);
}
public Design Create(string name)
{
var design = new Design()
{
CreationDate = DateTimeOffset.UtcNow,
Identifier = Guid.NewGuid(),
Index = _designs.Count,
Name = name,
};
_designs.Add(design);
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
DesignChange?.Invoke(DesignChangeType.Created, design.Index);
return design;
}
public void Delete(Design design)
{
_designs.RemoveAt(design.Index);
foreach (var d in _designs.Skip(design.Index + 1))
--d.Index;
var fileName = CreateFileName(design);
try
{
File.Delete(fileName);
Glamourer.Log.Debug($"Deleted design {design.Identifier}.");
DesignChange?.Invoke(DesignChangeType.Deleted, design.Index);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not delete design file for {design.Identifier}:\n{ex}");
}
}
public void Rename(Design design, string newName)
{
var oldName = design.Name;
var oldFileName = CreateFileName(design);
if (File.Exists(oldFileName))
try
{
File.Delete(oldFileName);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not delete old design file for rename from {design.Identifier}:\n{ex}");
return;
}
design.Name = newName;
SaveDesign(design);
Glamourer.Log.Debug($"Renamed design {design.Identifier}.");
DesignChange?.Invoke(DesignChangeType.Renamed, design.Index, oldName, newName);
}
public void ChangeDescription(Design design, string description)
{
var oldDescription = design.Description;
design.Description = description;
SaveDesign(design);
Glamourer.Log.Debug($"Renamed design {design.Identifier}.");
DesignChange?.Invoke(DesignChangeType.ChangedDescription, design.Index, oldDescription, description);
}
public void AddTag(Design design, string tag)
{
if (design.Tags.Contains(tag))
return;
design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray();
var idx = design.Tags.IndexOf(tag);
SaveDesign(design);
Glamourer.Log.Debug($"Added tag at {idx} to design {design.Identifier}.");
DesignChange?.Invoke(DesignChangeType.AddedTag, design.Index, null, tag, idx);
}
public void RemoveTag(Design design, string tag)
{
var idx = design.Tags.IndexOf(tag);
if (idx >= 0)
RemoveTag(design, idx);
}
public void RemoveTag(Design design, int tagIdx)
{
var oldTag = design.Tags[tagIdx];
design.Tags = design.Tags.Take(tagIdx).Concat(design.Tags.Skip(tagIdx + 1)).ToArray();
SaveDesign(design);
Glamourer.Log.Debug($"Removed tag at {tagIdx} from design {design.Identifier}.");
DesignChange?.Invoke(DesignChangeType.RemovedTag, design.Index, oldTag, null, tagIdx);
}
public void RenameTag(Design design, int tagIdx, string newTag)
{
var oldTag = design.Tags[tagIdx];
if (oldTag == newTag)
return;
design.Tags[tagIdx] = newTag;
Array.Sort(design.Tags);
SaveDesign(design);
Glamourer.Log.Debug($"Renamed tag at {tagIdx} in design {design.Identifier} and resorted tags.");
DesignChange?.Invoke(DesignChangeType.ChangedTag, design.Index, oldTag, newTag, tagIdx);
}
}
}

View file

@ -0,0 +1,44 @@
using System;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace Glamourer.Designs;
public partial class Design
{
public Guid Identifier { get; private init; }
public DateTimeOffset CreationDate { get; private init; }
public string Name { get; private set; } = string.Empty;
public string Description { get; private set; } = string.Empty;
public string[] Tags { get; private set; } = Array.Empty<string>();
public int Index { get; private set; }
public JObject JsonSerialize()
{
var ret = new JObject
{
[nameof(Identifier)] = Identifier,
[nameof(CreationDate)] = CreationDate,
[nameof(Name)] = Name,
[nameof(Description)] = Description,
[nameof(Tags)] = JArray.FromObject(Tags),
};
return ret;
}
public static Design LoadDesign(JObject json)
=> new()
{
CreationDate = json[nameof(CreationDate)]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException(nameof(CreationDate)),
Identifier = json[nameof(Identifier)]?.ToObject<Guid>() ?? throw new ArgumentNullException(nameof(Identifier)),
Name = json[nameof(Name)]?.ToObject<string>() ?? throw new ArgumentNullException(nameof(Name)),
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
Tags = ParseTags(json),
};
private static string[] ParseTags(JObject json)
{
var tags = json[nameof(Tags)]?.ToObject<string[]>() ?? Array.Empty<string>();
return tags.OrderBy(t => t).Distinct().ToArray();
}
}

View file

@ -1,174 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Newtonsoft.Json;
namespace Glamourer.Designs;
public class DesignManager
{
//public const string FileName = "Designs.json";
//private readonly FileInfo _saveFile;
//
//public SortedList<string, CharacterSave> Designs = null!;
//public FileSystem.FileSystem FileSystem { get; } = new();
//
//public DesignManager()
//{
// var saveFolder = new DirectoryInfo(Dalamud.PluginInterface.GetPluginConfigDirectory());
// if (!saveFolder.Exists)
// Directory.CreateDirectory(saveFolder.FullName);
//
// _saveFile = new FileInfo(Path.Combine(saveFolder.FullName, FileName));
//
// LoadFromFile();
//}
//
//private void BuildStructure()
//{
// FileSystem.Clear();
// var anyChanges = false;
// foreach (var (path, save) in Designs.ToArray())
// {
// try
// {
// var (folder, name) = FileSystem.CreateAllFolders(path);
// var design = new Design(folder, name) { Data = save };
// folder.FindOrAddChild(design);
// var fixedPath = design.FullName();
// if (string.Equals(fixedPath, path, StringComparison.InvariantCultureIgnoreCase))
// continue;
//
// Designs.Remove(path);
// Designs[fixedPath] = save;
// anyChanges = true;
// PluginLog.Debug($"Problem loading saved designs, {path} was renamed to {fixedPath}.");
// }
// catch (Exception e)
// {
// PluginLog.Error($"Problem loading saved designs, {path} was removed because:\n{e}");
// Designs.Remove(path);
// }
// }
//
// if (anyChanges)
// SaveToFile();
//}
//
//private bool UpdateRoot(string oldPath, Design child)
//{
// var newPath = child.FullName();
// if (string.Equals(newPath, oldPath, StringComparison.InvariantCultureIgnoreCase))
// return false;
//
// Designs.Remove(oldPath);
// Designs[child.FullName()] = child.Data;
// return true;
//}
//
//private void UpdateChild(string oldRootPath, string newRootPath, Design child)
//{
// var newPath = child.FullName();
// var oldPath = $"{oldRootPath}{newPath.Remove(0, newRootPath.Length)}";
// Designs.Remove(oldPath);
// Designs[newPath] = child.Data;
//}
//
//public void DeleteAllChildren(IFileSystemBase root, bool deleteEmpty)
//{
// if (root is Folder f)
// foreach (var child in f.AllLeaves(SortMode.Lexicographical))
// Designs.Remove(child.FullName());
// var fullPath = root.FullName();
// root.Parent.RemoveChild(root, deleteEmpty);
// Designs.Remove(fullPath);
//
// SaveToFile();
//}
//
//public void UpdateAllChildren(string oldPath, IFileSystemBase root)
//{
// var changes = false;
// switch (root)
// {
// case Design d:
// changes |= UpdateRoot(oldPath, d);
// break;
// case Folder f:
// {
// var newRootPath = root.FullName();
// if (!string.Equals(oldPath, newRootPath, StringComparison.InvariantCultureIgnoreCase))
// {
// changes = true;
// foreach (var descendant in f.AllLeaves(SortMode.Lexicographical).Where(l => l is Design).Cast<Design>())
// UpdateChild(oldPath, newRootPath, descendant);
// }
//
// break;
// }
// }
//
// if (changes)
// SaveToFile();
//}
//
//public void SaveToFile()
//{
// try
// {
// var data = JsonConvert.SerializeObject(Designs, Formatting.Indented);
// File.WriteAllText(_saveFile.FullName, data);
// }
// catch (Exception e)
// {
// PluginLog.Error($"Could not write to save file {_saveFile.FullName}:\n{e}");
// }
//}
//
//public void LoadFromFile()
//{
// _saveFile.Refresh();
// Designs = new SortedList<string, CharacterSave>();
// var changes = false;
// if (_saveFile.Exists)
// try
// {
// var data = File.ReadAllText(_saveFile.FullName);
// var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
// if (json == null)
// {
// PluginLog.Error($"Save file {_saveFile.FullName} corrupted.");
// json = new Dictionary<string, string>();
// }
// foreach (var (name, saveString) in json)
// {
// try
// {
// var save = CharacterSave.FromString(saveString, out var oldVersion);
// changes |= oldVersion;
// changes |= !Designs.TryAdd(name, save);
// }
// catch (Exception e)
// {
// PluginLog.Error($"Character Save for {name} is invalid:\n{e}");
// changes = true;
// }
// }
// }
// catch (Exception e)
// {
// PluginLog.Error($"Could not load save file {_saveFile.FullName}:\n{e}");
// changes = true;
// }
// else
// changes = true;
// if (changes)
// SaveToFile();
// BuildStructure();
//}
}

View file

@ -96,7 +96,6 @@ public class Glamourer : IDalamudPlugin
public void Dispose()
{
Dalamud.PluginInterface.RelinquishData("test1");
RedrawManager?.Dispose();
Penumbra?.Dispose();
if (_windowSystem != null)

View file

@ -1,15 +1,6 @@
using System;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Logging;
using Glamourer.Designs;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
namespace Glamourer.Gui.Designs;
namespace Glamourer.Gui;
//internal partial class Interface
//{

View file

@ -91,7 +91,7 @@ public partial class EquipmentDrawer
private CharacterWeapon _lastWeapon = new(ulong.MaxValue);
private string _lastPreview = string.Empty;
private int _lastIndex;
public WeaponCategory LastCategory { get; private set; }
public FullEquipType LastCategory { get; private set; }
private bool _drawAll;
public WeaponCombo(EquipSlot slot)
@ -124,12 +124,12 @@ public partial class EquipmentDrawer
}
UpdateItem(weapon);
UpdateCategory((WeaponCategory?)LastItem!.ItemUICategory?.Row ?? WeaponCategory.Unknown);
UpdateCategory(((WeaponCategory) (LastItem!.ItemUICategory?.Row ?? 0)).ToEquipType());
newIdx = _lastIndex;
return Draw(Label, _lastPreview, ref newIdx, ItemComboWidth * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight());
}
public bool Draw(CharacterWeapon weapon, WeaponCategory category, out int newIdx)
public bool Draw(CharacterWeapon weapon, FullEquipType category, out int newIdx)
{
if (_drawAll)
{
@ -166,7 +166,7 @@ public partial class EquipmentDrawer
_lastPreview = _lastIndex >= 0 ? Items[_lastIndex].Name : LastItem.Name.ToString();
}
private void UpdateCategory(WeaponCategory category)
private void UpdateCategory(FullEquipType category)
{
if (category == LastCategory)
return;
@ -225,7 +225,7 @@ public partial class EquipmentDrawer
Glamourer.RedrawManager.LoadWeapon(actor, _currentSlot, mainHand);
}
private void DrawOffHandSelector(ref CharacterWeapon offHand, WeaponCategory category)
private void DrawOffHandSelector(ref CharacterWeapon offHand, FullEquipType category)
{
var change = OffHandCombo.Draw(offHand, category, out var newIdx);
var newWeapon = change ? ToWeapon(OffHandCombo.Items[newIdx], offHand.Stain) : CharacterWeapon.Empty;

View file

@ -100,8 +100,8 @@ public partial class EquipmentDrawer
DrawStainCombo();
ImGui.SameLine();
DrawMainHandSelector(ref mainHand);
var offhand = MainHandCombo.LastCategory.AllowsOffHand();
if (offhand != WeaponCategory.Unknown)
var offhand = MainHandCombo.LastCategory.Offhand();
if (offhand != FullEquipType.Unknown)
{
_currentSlot = EquipSlot.OffHand;
DrawStainCombo();
@ -128,8 +128,8 @@ public partial class EquipmentDrawer
DrawStainCombo();
ImGui.SameLine();
DrawMainHandSelector(ref mainHand);
var offhand = MainHandCombo.LastCategory.AllowsOffHand();
if (offhand != WeaponCategory.Unknown)
var offhand = MainHandCombo.LastCategory.Offhand();
if (offhand != FullEquipType.Unknown)
{
_currentSlot = EquipSlot.OffHand;
DrawCheckbox(ref flags);

View file

@ -26,7 +26,7 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
=> actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer;
public ActorIdentifier GetIdentifier()
=> Glamourer.Actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer);
=> Glamourer.Actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer, out _, true, true);
public bool Identifier(out ActorIdentifier ident)
{

View file

@ -24,7 +24,7 @@ public unsafe partial class RedrawManager
// redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one.
// skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change)
// unk4 seemed to be the same as unk1.
[Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))]
[Signature(Penumbra.GameData.Sigs.WeaponReload, DetourName = nameof(LoadWeaponDetour))]
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,

View file

@ -102,9 +102,5 @@ public unsafe partial class RedrawManager : IDisposable
private static void OnCharacterRedrawFinished(IntPtr gameObject, string collection, IntPtr drawObject)
{
//SetVisor((Human*)drawObject, true);
if (Glamourer.Models.FromCharacterBase((CharacterBase*)drawObject, out var data))
PluginLog.Information($"Name: {data.FirstName} ({data.Id})");
else
PluginLog.Information($"Key: {Glamourer.Models.KeyFromCharacterBase((CharacterBase*)drawObject):X16}");
}
}