mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
deadsa
This commit is contained in:
parent
33c2ed7903
commit
e27a194cc6
47 changed files with 2037 additions and 1167 deletions
|
|
@ -47,6 +47,8 @@ public enum CustomizeFlag : ulong
|
|||
|
||||
public static class CustomizeFlagExtensions
|
||||
{
|
||||
public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static CustomizeIndex ToIndex(this CustomizeFlag flag)
|
||||
=> flag switch
|
||||
|
|
@ -87,6 +89,6 @@ public static class CustomizeFlagExtensions
|
|||
CustomizeFlag.FacePaint => CustomizeIndex.FacePaint,
|
||||
CustomizeFlag.FacePaintReversed => CustomizeIndex.FacePaintReversed,
|
||||
CustomizeFlag.FacePaintColor => CustomizeIndex.FacePaintColor,
|
||||
_ => (CustomizeIndex) byte.MaxValue,
|
||||
_ => (CustomizeIndex)byte.MaxValue,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,30 +6,29 @@ using Dalamud.Data;
|
|||
using Glamourer.Structs;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Item = Glamourer.Structs.Item;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public static class GameData
|
||||
{
|
||||
private static Dictionary<EquipSlot, List<Item>>? _itemsBySlot;
|
||||
private static Dictionary<EquipSlot, List<Item2>>? _itemsBySlot;
|
||||
private static Dictionary<byte, Job>? _jobs;
|
||||
private static Dictionary<ushort, JobGroup>? _jobGroups;
|
||||
|
||||
public static IReadOnlyDictionary<EquipSlot, List<Item>> ItemsBySlot(DataManager dataManager)
|
||||
public static IReadOnlyDictionary<EquipSlot, List<Item2>> ItemsBySlot(DataManager dataManager)
|
||||
{
|
||||
if (_itemsBySlot != null)
|
||||
return _itemsBySlot;
|
||||
|
||||
var sheet = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
|
||||
|
||||
Item EmptySlot(EquipSlot slot)
|
||||
Item2 EmptySlot(EquipSlot slot)
|
||||
=> new(sheet.First(), "Nothing", slot);
|
||||
|
||||
static Item EmptyNpc(EquipSlot slot)
|
||||
static Item2 EmptyNpc(EquipSlot slot)
|
||||
=> new(new Lumina.Excel.GeneratedSheets.Item() { ModelMain = 9903 }, "Smallclothes (NPC)", slot);
|
||||
|
||||
_itemsBySlot = new Dictionary<EquipSlot, List<Item>>()
|
||||
_itemsBySlot = new Dictionary<EquipSlot, List<Item2>>()
|
||||
{
|
||||
[EquipSlot.Head] = new(200)
|
||||
{
|
||||
|
|
@ -93,7 +92,7 @@ public static class GameData
|
|||
if (slot == EquipSlot.OffHand)
|
||||
slot = EquipSlot.MainHand;
|
||||
if (_itemsBySlot.TryGetValue(slot, out var list))
|
||||
list.Add(new Item(item, name, slot));
|
||||
list.Add(new Item2(item, name, slot));
|
||||
}
|
||||
|
||||
foreach (var list in _itemsBySlot.Values)
|
||||
|
|
|
|||
34
Glamourer.GameData/Offsets.cs
Normal file
34
Glamourer.GameData/Offsets.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
namespace Glamourer;
|
||||
|
||||
public static class Offsets
|
||||
{
|
||||
public static class Character
|
||||
{
|
||||
public const int ClassJobContainer = 0x1A8;
|
||||
|
||||
public const int Wetness = 0x1ADA;
|
||||
public const int HatVisible = 0x84E;
|
||||
public const int VisorToggled = 0x84F;
|
||||
public const int WeaponHidden1 = 0x84F;
|
||||
public const int WeaponHidden2 = 0x72C;
|
||||
public const int Alpha = 0x19E0;
|
||||
|
||||
public static class Flags
|
||||
{
|
||||
public const byte IsHatHidden = 0x01;
|
||||
public const byte IsVisorToggled = 0x08;
|
||||
public const byte IsWet = 0x80;
|
||||
public const byte IsWeaponHidden1 = 0x01;
|
||||
public const byte IsWeaponHidden2 = 0x02;
|
||||
}
|
||||
}
|
||||
|
||||
public const byte DrawObjectVisorStateFlag = 0x40;
|
||||
public const byte DrawObjectVisorToggleFlag = 0x80;
|
||||
}
|
||||
|
||||
public static class Sigs
|
||||
{
|
||||
public const string ChangeJob = "88 51 ?? 44 3B CA";
|
||||
public const string FlagSlotForUpdate = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A";
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ using Penumbra.GameData.Structs;
|
|||
namespace Glamourer.Structs;
|
||||
|
||||
// An Item wrapper struct that contains the item table, a precomputed name and the associated equip slot.
|
||||
public readonly struct Item
|
||||
public readonly struct Item2
|
||||
{
|
||||
public readonly Lumina.Excel.GeneratedSheets.Item Base;
|
||||
public readonly string Name;
|
||||
|
|
@ -28,7 +28,7 @@ public readonly struct Item
|
|||
=> ((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)
|
||||
public Item2(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)
|
||||
{
|
||||
Base = item;
|
||||
Name = name;
|
||||
|
|
@ -36,7 +36,7 @@ public readonly struct Item
|
|||
}
|
||||
|
||||
// Create empty Nothing items.
|
||||
public static Item Nothing(EquipSlot slot)
|
||||
public static Item2 Nothing(EquipSlot slot)
|
||||
=> new("Nothing", slot);
|
||||
|
||||
// Produce the relevant model information for a given item and equip slot.
|
||||
|
|
@ -58,7 +58,7 @@ public readonly struct Item
|
|||
}
|
||||
|
||||
// Used for 'Nothing' items.
|
||||
private Item(string name, EquipSlot slot)
|
||||
private Item2(string name, EquipSlot slot)
|
||||
{
|
||||
Name = name;
|
||||
Base = new Lumina.Excel.GeneratedSheets.Item();
|
||||
|
|
@ -109,42 +109,42 @@ public unsafe class PenumbraAttach : IDisposable
|
|||
return;
|
||||
|
||||
var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(Dalamud.GameData, id)!;
|
||||
var writeItem = new Item(item, string.Empty);
|
||||
var writeItem = new Item2(item, string.Empty);
|
||||
|
||||
UpdateItem(ObjectManager.GPosePlayer, writeItem);
|
||||
UpdateItem(ObjectManager.Player, writeItem);
|
||||
}
|
||||
|
||||
private static void UpdateItem(Actor actor, Item item)
|
||||
private static void UpdateItem(Actor actor, Item2 item2)
|
||||
{
|
||||
if (!actor || !actor.DrawObject)
|
||||
return;
|
||||
|
||||
switch (item.EquippableTo)
|
||||
switch (item2.EquippableTo)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
{
|
||||
var off = item.HasSubModel
|
||||
? new CharacterWeapon(item.SubModel.id, item.SubModel.type, item.SubModel.variant, actor.DrawObject.OffHand.Stain)
|
||||
: item.IsBothHand
|
||||
var off = item2.HasSubModel
|
||||
? new CharacterWeapon(item2.SubModel.id, item2.SubModel.type, item2.SubModel.variant, actor.DrawObject.OffHand.Stain)
|
||||
: item2.IsBothHand
|
||||
? CharacterWeapon.Empty
|
||||
: actor.OffHand;
|
||||
var main = new CharacterWeapon(item.MainModel.id, item.MainModel.type, item.MainModel.variant, actor.DrawObject.MainHand.Stain);
|
||||
var main = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, actor.DrawObject.MainHand.Stain);
|
||||
Glamourer.RedrawManager.LoadWeapon(actor, main, off);
|
||||
return;
|
||||
}
|
||||
case EquipSlot.OffHand:
|
||||
{
|
||||
var off = new CharacterWeapon(item.MainModel.id, item.MainModel.type, item.MainModel.variant, actor.DrawObject.OffHand.Stain);
|
||||
var off = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, actor.DrawObject.OffHand.Stain);
|
||||
var main = actor.MainHand;
|
||||
Glamourer.RedrawManager.LoadWeapon(actor, main, off);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var current = actor.DrawObject.Equip[item.EquippableTo];
|
||||
var armor = new CharacterArmor(item.MainModel.id, (byte)item.MainModel.variant, current.Stain);
|
||||
Glamourer.RedrawManager.ChangeEquip(actor.DrawObject, item.EquippableTo, armor);
|
||||
var current = actor.DrawObject.Equip[item2.EquippableTo];
|
||||
var armor = new CharacterArmor(item2.MainModel.id, (byte)item2.MainModel.variant, current.Stain);
|
||||
Glamourer.RedrawManager.ChangeEquip(actor.DrawObject, item2.EquippableTo, armor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Gui;
|
||||
|
|
@ -18,13 +19,14 @@ public class Dalamud
|
|||
|
||||
// @formatter:off
|
||||
[PluginService][RequiredVersion("1.0")] public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static CommandManager Commands { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static DataManager GameData { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static ClientState ClientState { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static GameGui GameGui { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static ChatGui Chat { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static Framework Framework { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static TargetManager Targets { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static CommandManager Commands { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static DataManager GameData { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static ClientState ClientState { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static GameGui GameGui { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static ChatGui Chat { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static Framework Framework { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static TargetManager Targets { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static KeyState KeyState { get; private set; } = null!;
|
||||
// @formatter:on
|
||||
}
|
||||
|
|
|
|||
88
Glamourer/Designs/CharacterSave.cs
Normal file
88
Glamourer/Designs/CharacterSave.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String.Functions;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct CharacterData
|
||||
{
|
||||
public uint ModelId;
|
||||
public CustomizeData CustomizeData;
|
||||
public CharacterWeapon MainHand;
|
||||
public CharacterWeapon OffHand;
|
||||
public CharacterArmor Head;
|
||||
public CharacterArmor Body;
|
||||
public CharacterArmor Hands;
|
||||
public CharacterArmor Legs;
|
||||
public CharacterArmor Feet;
|
||||
public CharacterArmor Ears;
|
||||
public CharacterArmor Neck;
|
||||
public CharacterArmor Wrists;
|
||||
public CharacterArmor RFinger;
|
||||
public CharacterArmor LFinger;
|
||||
|
||||
public unsafe Customize Customize
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (CustomizeData* ptr = &CustomizeData)
|
||||
{
|
||||
return new Customize(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe CharacterEquip Equipment
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (CharacterArmor* ptr = &Head)
|
||||
{
|
||||
return new CharacterEquip(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly CharacterData Default
|
||||
= new()
|
||||
{
|
||||
ModelId = 0,
|
||||
CustomizeData = Customize.Default,
|
||||
MainHand = CharacterWeapon.Empty,
|
||||
OffHand = CharacterWeapon.Empty,
|
||||
Head = CharacterArmor.Empty,
|
||||
Body = CharacterArmor.Empty,
|
||||
Hands = CharacterArmor.Empty,
|
||||
Legs = CharacterArmor.Empty,
|
||||
Feet = CharacterArmor.Empty,
|
||||
Ears = CharacterArmor.Empty,
|
||||
Neck = CharacterArmor.Empty,
|
||||
Wrists = CharacterArmor.Empty,
|
||||
RFinger = CharacterArmor.Empty,
|
||||
LFinger = CharacterArmor.Empty,
|
||||
};
|
||||
|
||||
public readonly unsafe CharacterData Clone()
|
||||
{
|
||||
var data = new CharacterData();
|
||||
fixed (void* ptr = &this)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(&data, ptr, sizeof(CharacterData));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void Load(IDesignable designable)
|
||||
{
|
||||
ModelId = designable.ModelId;
|
||||
Customize.Load(designable.Customize);
|
||||
Equipment.Load(designable.Equip);
|
||||
MainHand = designable.MainHand;
|
||||
OffHand = designable.OffHand;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,106 +2,18 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuizmoNET;
|
||||
using Dalamud.Utility;
|
||||
using Glamourer.Customization;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Filesystem;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
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
|
||||
|
|
@ -109,7 +21,8 @@ public partial class Design
|
|||
public const string DesignFolderName = "designs";
|
||||
public readonly string DesignFolder;
|
||||
|
||||
private readonly List<Design> _designs = new();
|
||||
private readonly FrameworkManager _framework;
|
||||
private readonly List<Design> _designs = new();
|
||||
|
||||
public enum DesignChangeType
|
||||
{
|
||||
|
|
@ -121,18 +34,54 @@ public partial class Design
|
|||
AddedTag,
|
||||
RemovedTag,
|
||||
ChangedTag,
|
||||
Customize,
|
||||
Equip,
|
||||
Weapon,
|
||||
Stain,
|
||||
ApplyCustomize,
|
||||
ApplyEquip,
|
||||
Other,
|
||||
}
|
||||
|
||||
public delegate void DesignChangeDelegate(DesignChangeType type, int designIdx, string? oldData = null, string? newData = null,
|
||||
int tagIdx = -1);
|
||||
public delegate void DesignChangeDelegate(DesignChangeType type, Design design, object? changeData = null);
|
||||
|
||||
public event DesignChangeDelegate? DesignChange;
|
||||
public event DesignChangeDelegate DesignChange;
|
||||
|
||||
public IReadOnlyList<Design> Designs
|
||||
=> _designs;
|
||||
|
||||
public Manager(DalamudPluginInterface pi)
|
||||
=> DesignFolder = SetDesignFolder(pi);
|
||||
public Manager(DalamudPluginInterface pi, FrameworkManager framework)
|
||||
{
|
||||
_framework = framework;
|
||||
DesignFolder = SetDesignFolder(pi);
|
||||
DesignChange += OnChange;
|
||||
LoadDesigns();
|
||||
MigrateOldDesigns(pi, Path.Combine(new DirectoryInfo(DesignFolder).Parent!.FullName, "Designs.json"));
|
||||
}
|
||||
|
||||
private void OnChange(DesignChangeType type, Design design, object? _)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DesignChangeType.Created:
|
||||
SaveDesignInternal(design);
|
||||
return;
|
||||
case DesignChangeType.Renamed:
|
||||
case DesignChangeType.ChangedDescription:
|
||||
case DesignChangeType.AddedTag:
|
||||
case DesignChangeType.RemovedTag:
|
||||
case DesignChangeType.ChangedTag:
|
||||
case DesignChangeType.Customize:
|
||||
case DesignChangeType.Equip:
|
||||
case DesignChangeType.Weapon:
|
||||
case DesignChangeType.Stain:
|
||||
case DesignChangeType.ApplyCustomize:
|
||||
case DesignChangeType.ApplyEquip:
|
||||
case DesignChangeType.Other:
|
||||
SaveDesign(design);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static string SetDesignFolder(DalamudPluginInterface pi)
|
||||
{
|
||||
|
|
@ -153,9 +102,12 @@ public partial class Design
|
|||
}
|
||||
|
||||
private string CreateFileName(Design design)
|
||||
=> Path.Combine(DesignFolder, $"{design.Name.RemoveInvalidPathSymbols()}_{design.Identifier}.json");
|
||||
=> Path.Combine(DesignFolder, $"{design.Identifier}.json");
|
||||
|
||||
public void SaveDesign(Design design)
|
||||
=> _framework.RegisterDelayed($"{nameof(SaveDesign)}_{design.Identifier}", () => SaveDesignInternal(design));
|
||||
|
||||
private void SaveDesignInternal(Design design)
|
||||
{
|
||||
var fileName = CreateFileName(design);
|
||||
try
|
||||
|
|
@ -173,24 +125,54 @@ public partial class Design
|
|||
public void LoadDesigns()
|
||||
{
|
||||
_designs.Clear();
|
||||
List<(Design, string)> invalidNames = new();
|
||||
var skipped = 0;
|
||||
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);
|
||||
var design = LoadDesign(data, out var changes);
|
||||
if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(file.Name))
|
||||
invalidNames.Add((design, file.FullName));
|
||||
if (_designs.Any(f => f.Identifier == design.Identifier))
|
||||
throw new Exception($"Identifier {design.Identifier} was not unique.");
|
||||
|
||||
// TODO something when changed?
|
||||
design.Index = _designs.Count;
|
||||
_designs.Add(design);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not load design, skipped:\n{ex}");
|
||||
++skipped;
|
||||
}
|
||||
}
|
||||
|
||||
Glamourer.Log.Information($"Loaded {_designs.Count} designs.");
|
||||
DesignChange?.Invoke(DesignChangeType.ReloadedAll, -1);
|
||||
var failed = 0;
|
||||
foreach (var (design, name) in invalidNames)
|
||||
{
|
||||
try
|
||||
{
|
||||
var correctName = CreateFileName(design);
|
||||
File.Move(name, correctName, false);
|
||||
Glamourer.Log.Information($"Moved invalid design file from {Path.GetFileName(name)} to {Path.GetFileName(correctName)}.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
++failed;
|
||||
Glamourer.Log.Error($"Failed to move invalid design file from {Path.GetFileName(name)}:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidNames.Count > 0)
|
||||
Glamourer.Log.Information(
|
||||
$"Moved {invalidNames.Count - failed} designs to correct names.{(failed > 0 ? $" Failed to move {failed} designs to correct names." : string.Empty)}");
|
||||
|
||||
Glamourer.Log.Information(
|
||||
$"Loaded {_designs.Count} designs.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}");
|
||||
DesignChange.Invoke(DesignChangeType.ReloadedAll, null!);
|
||||
}
|
||||
|
||||
public Design Create(string name)
|
||||
|
|
@ -198,13 +180,13 @@ public partial class Design
|
|||
var design = new Design()
|
||||
{
|
||||
CreationDate = DateTimeOffset.UtcNow,
|
||||
Identifier = Guid.NewGuid(),
|
||||
Identifier = CreateNewGuid(),
|
||||
Index = _designs.Count,
|
||||
Name = name,
|
||||
};
|
||||
_designs.Add(design);
|
||||
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
|
||||
DesignChange?.Invoke(DesignChangeType.Created, design.Index);
|
||||
DesignChange.Invoke(DesignChangeType.Created, design);
|
||||
return design;
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +200,7 @@ public partial class Design
|
|||
{
|
||||
File.Delete(fileName);
|
||||
Glamourer.Log.Debug($"Deleted design {design.Identifier}.");
|
||||
DesignChange?.Invoke(DesignChangeType.Deleted, design.Index);
|
||||
DesignChange.Invoke(DesignChangeType.Deleted, design);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -228,7 +210,7 @@ public partial class Design
|
|||
|
||||
public void Rename(Design design, string newName)
|
||||
{
|
||||
var oldName = design.Name;
|
||||
var oldName = design.Name.Text;
|
||||
var oldFileName = CreateFileName(design);
|
||||
if (File.Exists(oldFileName))
|
||||
try
|
||||
|
|
@ -242,18 +224,15 @@ public partial class Design
|
|||
}
|
||||
|
||||
design.Name = newName;
|
||||
SaveDesign(design);
|
||||
Glamourer.Log.Debug($"Renamed design {design.Identifier}.");
|
||||
DesignChange?.Invoke(DesignChangeType.Renamed, design.Index, oldName, newName);
|
||||
DesignChange.Invoke(DesignChangeType.Renamed, design, oldName);
|
||||
}
|
||||
|
||||
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);
|
||||
Glamourer.Log.Debug($"Changed description of design {design.Identifier}.");
|
||||
DesignChange.Invoke(DesignChangeType.ChangedDescription, design);
|
||||
}
|
||||
|
||||
public void AddTag(Design design, string tag)
|
||||
|
|
@ -263,9 +242,8 @@ public partial class Design
|
|||
|
||||
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);
|
||||
Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}.");
|
||||
DesignChange.Invoke(DesignChangeType.AddedTag, design);
|
||||
}
|
||||
|
||||
public void RemoveTag(Design design, string tag)
|
||||
|
|
@ -279,9 +257,8 @@ public partial class Design
|
|||
{
|
||||
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);
|
||||
Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}.");
|
||||
DesignChange.Invoke(DesignChangeType.RemovedTag, design);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -294,8 +271,156 @@ public partial class Design
|
|||
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);
|
||||
Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags.");
|
||||
DesignChange.Invoke(DesignChangeType.ChangedTag, design);
|
||||
}
|
||||
|
||||
public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value)
|
||||
{
|
||||
var old = design.GetCustomize(idx);
|
||||
if (design.SetCustomize(idx, value))
|
||||
{
|
||||
Glamourer.Log.Debug($"Changed customize {idx} in design {design.Identifier} from {old.Value} to {value.Value}");
|
||||
DesignChange.Invoke(DesignChangeType.Customize, design, idx);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
|
||||
{
|
||||
if (design.SetApplyCustomize(idx, value))
|
||||
{
|
||||
Glamourer.Log.Debug($"Set applying of customization {idx} to {value}.");
|
||||
DesignChange.Invoke(DesignChangeType.ApplyCustomize, design, idx);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeEquip(Design design, EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
|
||||
{
|
||||
var old = design.Armor(slot);
|
||||
if (design.SetArmor(slot, itemId, item))
|
||||
{
|
||||
var n = design.Armor(slot);
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {slot} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {n.Name} ({n.ItemId}).");
|
||||
DesignChange.Invoke(DesignChangeType.Equip, design, slot);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeWeapon(Design design, uint itemId, EquipSlot offhand, Lumina.Excel.GeneratedSheets.Item? item = null)
|
||||
{
|
||||
var (old, change, n) = offhand == EquipSlot.OffHand
|
||||
? (design.WeaponOff, design.SetOffhand(itemId, item), design.WeaponOff)
|
||||
: (design.WeaponMain, design.SetMainhand(itemId, item), design.WeaponMain);
|
||||
if (change)
|
||||
{
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {offhand} weapon in design {design.Identifier} from {old.Name} ({old.ItemId}) to {n.Name} ({n.ItemId}).");
|
||||
DesignChange.Invoke(DesignChangeType.Weapon, design, offhand);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeApplyEquip(Design design, EquipSlot slot, bool value)
|
||||
{
|
||||
if (design.SetApplyEquip(slot, value))
|
||||
{
|
||||
Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}.");
|
||||
DesignChange.Invoke(DesignChangeType.ApplyEquip, design, slot);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeStain(Design design, EquipSlot slot, StainId stain)
|
||||
{
|
||||
if (design.SetStain(slot, stain))
|
||||
{
|
||||
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Value}.");
|
||||
DesignChange.Invoke(DesignChangeType.Stain, design, slot);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeApplyStain(Design design, EquipSlot slot, bool value)
|
||||
{
|
||||
if (design.SetApplyStain(slot, value))
|
||||
{
|
||||
Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}.");
|
||||
DesignChange.Invoke(DesignChangeType.Stain, design, slot);
|
||||
}
|
||||
}
|
||||
|
||||
private Guid CreateNewGuid()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var guid = Guid.NewGuid();
|
||||
if (_designs.All(d => d.Identifier != guid))
|
||||
return guid;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Add(Design design, string? message)
|
||||
{
|
||||
if (_designs.Any(d => d == design || d.Identifier == design.Identifier))
|
||||
return false;
|
||||
|
||||
design.Index = _designs.Count;
|
||||
_designs.Add(design);
|
||||
if (!message.IsNullOrEmpty())
|
||||
Glamourer.Log.Debug(message);
|
||||
DesignChange.Invoke(DesignChangeType.Created, design);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MigrateOldDesigns(DalamudPluginInterface pi, string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
return;
|
||||
|
||||
var errors = 0;
|
||||
var successes = 0;
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(filePath);
|
||||
var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(text) ?? new Dictionary<string, string>();
|
||||
var migratedFileSystemPaths = new Dictionary<string, string>(dict.Count);
|
||||
foreach (var (name, base64) in dict)
|
||||
{
|
||||
try
|
||||
{
|
||||
var actualName = Path.GetFileName(name);
|
||||
var design = new Design()
|
||||
{
|
||||
CreationDate = DateTimeOffset.UtcNow,
|
||||
Identifier = CreateNewGuid(),
|
||||
Name = actualName,
|
||||
};
|
||||
design.MigrateBase64(base64);
|
||||
Add(design, $"Migrated old design to {design.Identifier}.");
|
||||
migratedFileSystemPaths.Add(design.Identifier.ToString(), name);
|
||||
++successes;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not migrate design {name}:\n{ex}");
|
||||
++errors;
|
||||
}
|
||||
}
|
||||
|
||||
DesignFileSystem.MigrateOldPaths(pi, migratedFileSystemPaths);
|
||||
Glamourer.Log.Information($"Successfully migrated {successes} old designs. Failed to migrate {errors} designs.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not migrate old design file {filePath}:\n{e}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Move(filePath, Path.ChangeExtension(filePath, ".json.bak"));
|
||||
Glamourer.Log.Information($"Moved migrated design file {filePath} to backup file.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not move migrated design file {filePath} to backup file:\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,339 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Util;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public partial class Design
|
||||
public partial class Design : DesignBase
|
||||
{
|
||||
public const int FileVersion = 1;
|
||||
|
||||
public Guid Identifier { get; private init; }
|
||||
public DateTimeOffset CreationDate { get; private init; }
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
public LowerString Name { get; private set; } = LowerString.Empty;
|
||||
public string Description { get; private set; } = string.Empty;
|
||||
public string[] Tags { get; private set; } = Array.Empty<string>();
|
||||
public int Index { get; private set; }
|
||||
|
||||
private EquipFlag _applyEquip;
|
||||
private CustomizeFlag _applyCustomize;
|
||||
public QuadBool Wetness { get; private set; } = QuadBool.NullFalse;
|
||||
public QuadBool Visor { get; private set; } = QuadBool.NullFalse;
|
||||
public QuadBool Hat { get; private set; } = QuadBool.NullFalse;
|
||||
public QuadBool Weapon { get; private set; } = QuadBool.NullFalse;
|
||||
public bool WriteProtected { get; private set; }
|
||||
|
||||
public bool DoApplyEquip(EquipSlot slot)
|
||||
=> _applyEquip.HasFlag(slot.ToFlag());
|
||||
|
||||
public bool DoApplyStain(EquipSlot slot)
|
||||
=> _applyEquip.HasFlag(slot.ToStainFlag());
|
||||
|
||||
public bool DoApplyCustomize(CustomizeIndex idx)
|
||||
=> _applyCustomize.HasFlag(idx.ToFlag());
|
||||
|
||||
private bool SetApplyEquip(EquipSlot slot, bool value)
|
||||
{
|
||||
var newValue = value ? _applyEquip | slot.ToFlag() : _applyEquip & ~slot.ToFlag();
|
||||
if (newValue == _applyEquip)
|
||||
return false;
|
||||
|
||||
_applyEquip = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SetApplyStain(EquipSlot slot, bool value)
|
||||
{
|
||||
var newValue = value ? _applyEquip | slot.ToStainFlag() : _applyEquip & ~slot.ToStainFlag();
|
||||
if (newValue == _applyEquip)
|
||||
return false;
|
||||
|
||||
_applyEquip = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SetApplyCustomize(CustomizeIndex idx, bool value)
|
||||
{
|
||||
var newValue = value ? _applyCustomize | idx.ToFlag() : _applyCustomize & ~idx.ToFlag();
|
||||
if (newValue == _applyCustomize)
|
||||
return false;
|
||||
|
||||
_applyCustomize = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private Design()
|
||||
{ }
|
||||
|
||||
public JObject JsonSerialize()
|
||||
{
|
||||
var ret = new JObject
|
||||
{
|
||||
[nameof(Identifier)] = Identifier,
|
||||
[nameof(CreationDate)] = CreationDate,
|
||||
[nameof(Name)] = Name,
|
||||
[nameof(Description)] = Description,
|
||||
[nameof(Tags)] = JArray.FromObject(Tags),
|
||||
[nameof(FileVersion)] = FileVersion,
|
||||
[nameof(Identifier)] = Identifier,
|
||||
[nameof(CreationDate)] = CreationDate,
|
||||
[nameof(Name)] = Name.Text,
|
||||
[nameof(Description)] = Description,
|
||||
[nameof(Tags)] = JArray.FromObject(Tags),
|
||||
[nameof(WriteProtected)] = WriteProtected,
|
||||
[nameof(CharacterData.Equipment)] = SerializeEquipment(),
|
||||
[nameof(CharacterData.Customize)] = SerializeCustomize(),
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Design LoadDesign(JObject json)
|
||||
=> new()
|
||||
public JObject SerializeEquipment()
|
||||
{
|
||||
static JObject Serialize(uint itemId, StainId stain, bool apply, bool applyStain)
|
||||
=> new()
|
||||
{
|
||||
[nameof(Item.ItemId)] = itemId,
|
||||
[nameof(Item.Stain)] = stain.Value,
|
||||
["Apply"] = apply,
|
||||
["ApplyStain"] = applyStain,
|
||||
};
|
||||
|
||||
var ret = new JObject()
|
||||
{
|
||||
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,
|
||||
[nameof(MainHand)] =
|
||||
Serialize(MainHand, CharacterData.MainHand.Stain, DoApplyEquip(EquipSlot.MainHand), DoApplyStain(EquipSlot.MainHand)),
|
||||
[nameof(OffHand)] = Serialize(OffHand, CharacterData.OffHand.Stain, DoApplyEquip(EquipSlot.OffHand), DoApplyStain(EquipSlot.OffHand)),
|
||||
};
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var armor = Armor(slot);
|
||||
ret[slot.ToString()] = Serialize(armor.ItemId, armor.Stain, DoApplyEquip(slot), DoApplyStain(slot));
|
||||
}
|
||||
|
||||
ret[nameof(Hat)] = Hat.ToJObject("Show", "Apply");
|
||||
ret[nameof(Weapon)] = Weapon.ToJObject("Show", "Apply");
|
||||
ret[nameof(Visor)] = Visor.ToJObject("IsToggled", "Apply");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public JObject SerializeCustomize()
|
||||
{
|
||||
var ret = new JObject()
|
||||
{
|
||||
[nameof(ModelId)] = ModelId,
|
||||
};
|
||||
var customize = CharacterData.Customize;
|
||||
foreach (var idx in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
var data = customize[idx];
|
||||
ret[idx.ToString()] = new JObject()
|
||||
{
|
||||
["Value"] = data.Value,
|
||||
["Apply"] = true,
|
||||
};
|
||||
}
|
||||
|
||||
ret[nameof(Wetness)] = Wetness.ToJObject("IsWet", "Apply");
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Design LoadDesign(JObject json, out bool changes)
|
||||
{
|
||||
var version = json[nameof(FileVersion)]?.ToObject<int>() ?? 0;
|
||||
return version switch
|
||||
{
|
||||
1 => LoadDesignV1(json, out changes),
|
||||
_ => throw new Exception("The design to be loaded has no valid Version."),
|
||||
};
|
||||
}
|
||||
|
||||
private static Design LoadDesignV1(JObject json, out bool changes)
|
||||
{
|
||||
static string[] ParseTags(JObject json)
|
||||
{
|
||||
var tags = json["Tags"]?.ToObject<string[]>() ?? Array.Empty<string>();
|
||||
return tags.OrderBy(t => t).Distinct().ToArray();
|
||||
}
|
||||
|
||||
var design = new Design()
|
||||
{
|
||||
CreationDate = json["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate"),
|
||||
Identifier = json["Identifier"]?.ToObject<Guid>() ?? throw new ArgumentNullException("Identifier"),
|
||||
Name = new LowerString(json["Name"]?.ToObject<string>() ?? throw new ArgumentNullException("Name")),
|
||||
Description = json["Description"]?.ToObject<string>() ?? string.Empty,
|
||||
Tags = ParseTags(json),
|
||||
};
|
||||
|
||||
private static string[] ParseTags(JObject json)
|
||||
changes = LoadEquip(json["Equipment"], design);
|
||||
changes |= LoadCustomize(json["Customize"], design);
|
||||
return design;
|
||||
}
|
||||
|
||||
private static bool LoadEquip(JToken? equip, Design design)
|
||||
{
|
||||
var tags = json[nameof(Tags)]?.ToObject<string[]>() ?? Array.Empty<string>();
|
||||
return tags.OrderBy(t => t).Distinct().ToArray();
|
||||
if (equip == null)
|
||||
return true;
|
||||
|
||||
static (uint, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item)
|
||||
{
|
||||
var id = item?["ItemId"]?.ToObject<uint>() ?? ItemManager.NothingId(slot);
|
||||
var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0);
|
||||
var apply = item?["Apply"]?.ToObject<bool>() ?? false;
|
||||
var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false;
|
||||
return (id, stain, apply, applyStain);
|
||||
}
|
||||
|
||||
var changes = false;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]);
|
||||
changes |= !design.SetArmor(slot, id);
|
||||
changes |= !design.SetStain(slot, stain);
|
||||
design.SetApplyEquip(slot, apply);
|
||||
design.SetApplyStain(slot, applyStain);
|
||||
}
|
||||
|
||||
var main = equip["MainHand"];
|
||||
if (main == null)
|
||||
{
|
||||
changes = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var id = main["ItemId"]?.ToObject<uint>() ?? Glamourer.Items.DefaultSword.RowId;
|
||||
var stain = (StainId)(main["Stain"]?.ToObject<byte>() ?? 0);
|
||||
var apply = main["Apply"]?.ToObject<bool>() ?? false;
|
||||
var applyStain = main["ApplyStain"]?.ToObject<bool>() ?? false;
|
||||
changes |= !design.SetMainhand(id);
|
||||
changes |= !design.SetStain(EquipSlot.MainHand, stain);
|
||||
design.SetApplyEquip(EquipSlot.MainHand, apply);
|
||||
design.SetApplyStain(EquipSlot.MainHand, applyStain);
|
||||
}
|
||||
|
||||
var off = equip["OffHand"];
|
||||
if (off == null)
|
||||
{
|
||||
changes = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var id = off["ItemId"]?.ToObject<uint>() ?? ItemManager.NothingId(design.MainhandType.Offhand());
|
||||
var stain = (StainId)(off["Stain"]?.ToObject<byte>() ?? 0);
|
||||
var apply = off["Apply"]?.ToObject<bool>() ?? false;
|
||||
var applyStain = off["ApplyStain"]?.ToObject<bool>() ?? false;
|
||||
changes |= !design.SetOffhand(id);
|
||||
changes |= !design.SetStain(EquipSlot.OffHand, stain);
|
||||
design.SetApplyEquip(EquipSlot.OffHand, apply);
|
||||
design.SetApplyStain(EquipSlot.OffHand, applyStain);
|
||||
}
|
||||
|
||||
design.Hat = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse);
|
||||
design.Weapon = QuadBool.FromJObject(equip["Weapon"], "Show", "Apply", QuadBool.NullFalse);
|
||||
design.Visor = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse);
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private static bool LoadCustomize(JToken? json, Design design)
|
||||
{
|
||||
if (json == null)
|
||||
return true;
|
||||
|
||||
var customize = design.CharacterData.Customize;
|
||||
foreach (var idx in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
var tok = json[idx.ToString()];
|
||||
var data = (CustomizeValue)(tok?["Value"]?.ToObject<byte>() ?? 0);
|
||||
var apply = tok?["Apply"]?.ToObject<bool>() ?? false;
|
||||
customize[idx] = data;
|
||||
design.SetApplyCustomize(idx, apply);
|
||||
}
|
||||
|
||||
design.Wetness = QuadBool.FromJObject(json["Wetness"], "IsWet", "Apply", QuadBool.NullFalse);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void MigrateBase64(string base64)
|
||||
{
|
||||
static void CheckSize(int length, int requiredLength)
|
||||
{
|
||||
if (length != requiredLength)
|
||||
throw new Exception(
|
||||
$"Can not parse Base64 string into CharacterSave:\n\tInvalid size {length} instead of {requiredLength}.");
|
||||
}
|
||||
|
||||
var bytes = Convert.FromBase64String(base64);
|
||||
|
||||
byte applicationFlags;
|
||||
ushort equipFlags;
|
||||
|
||||
switch (bytes[0])
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
CheckSize(bytes.Length, 86);
|
||||
applicationFlags = bytes[1];
|
||||
equipFlags = BitConverter.ToUInt16(bytes, 2);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
CheckSize(bytes.Length, 91);
|
||||
applicationFlags = bytes[1];
|
||||
equipFlags = BitConverter.ToUInt16(bytes, 2);
|
||||
Hat = Hat.SetValue((bytes[90] & 0x01) == 0);
|
||||
Visor = Visor.SetValue((bytes[90] & 0x10) != 0);
|
||||
Weapon = Weapon.SetValue((bytes[90] & 0x02) == 0);
|
||||
break;
|
||||
}
|
||||
default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}.");
|
||||
}
|
||||
|
||||
_applyCustomize = (applicationFlags & 0x01) != 0 ? CustomizeFlagExtensions.All : 0;
|
||||
Wetness = (applicationFlags & 0x02) != 0 ? QuadBool.True : QuadBool.NullFalse;
|
||||
Hat = Hat.SetEnabled((applicationFlags & 0x04) != 0);
|
||||
Weapon = Weapon.SetEnabled((applicationFlags & 0x08) != 0);
|
||||
Visor = Visor.SetEnabled((applicationFlags & 0x10) != 0);
|
||||
WriteProtected = (applicationFlags & 0x20) != 0;
|
||||
|
||||
CharacterData.ModelId = 0;
|
||||
|
||||
SetApplyEquip(EquipSlot.MainHand, (equipFlags & 0x0001) != 0);
|
||||
SetApplyEquip(EquipSlot.OffHand, (equipFlags & 0x0002) != 0);
|
||||
SetApplyStain(EquipSlot.MainHand, (equipFlags & 0x0001) != 0);
|
||||
SetApplyStain(EquipSlot.OffHand, (equipFlags & 0x0002) != 0);
|
||||
var flag = 0x0002u;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
flag <<= 1;
|
||||
var apply = (equipFlags & flag) != 0;
|
||||
SetApplyEquip(slot, apply);
|
||||
SetApplyStain(slot, apply);
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
CharacterData.CustomizeData.Read(ptr + 4);
|
||||
var cur = (CharacterWeapon*)(ptr + 30);
|
||||
|
||||
UpdateMainhand(cur[0]);
|
||||
SetStain(EquipSlot.MainHand, cur[0].Stain);
|
||||
UpdateOffhand(cur[1]);
|
||||
SetStain(EquipSlot.OffHand, cur[1].Stain);
|
||||
var eq = (CharacterArmor*)(cur + 2);
|
||||
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
|
||||
{
|
||||
UpdateArmor(slot, eq[idx], true);
|
||||
SetStain(slot, eq[idx].Stain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
330
Glamourer/Designs/DesignBase.cs
Normal file
330
Glamourer/Designs/DesignBase.cs
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Util;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class DesignBase
|
||||
{
|
||||
protected CharacterData CharacterData = CharacterData.Default;
|
||||
public FullEquipType MainhandType { get; protected set; }
|
||||
|
||||
public uint Head { get; protected set; } = ItemManager.NothingId(EquipSlot.Head);
|
||||
public uint Body { get; protected set; } = ItemManager.NothingId(EquipSlot.Body);
|
||||
public uint Hands { get; protected set; } = ItemManager.NothingId(EquipSlot.Hands);
|
||||
public uint Legs { get; protected set; } = ItemManager.NothingId(EquipSlot.Legs);
|
||||
public uint Feet { get; protected set; } = ItemManager.NothingId(EquipSlot.Feet);
|
||||
public uint Ears { get; protected set; } = ItemManager.NothingId(EquipSlot.Ears);
|
||||
public uint Neck { get; protected set; } = ItemManager.NothingId(EquipSlot.Neck);
|
||||
public uint Wrists { get; protected set; } = ItemManager.NothingId(EquipSlot.Wrists);
|
||||
public uint RFinger { get; protected set; } = ItemManager.NothingId(EquipSlot.RFinger);
|
||||
public uint LFinger { get; protected set; } = ItemManager.NothingId(EquipSlot.RFinger);
|
||||
public uint MainHand { get; protected set; }
|
||||
public uint OffHand { get; protected set; }
|
||||
|
||||
public string HeadName { get; protected set; } = ItemManager.Nothing;
|
||||
public string BodyName { get; protected set; } = ItemManager.Nothing;
|
||||
public string HandsName { get; protected set; } = ItemManager.Nothing;
|
||||
public string LegsName { get; protected set; } = ItemManager.Nothing;
|
||||
public string FeetName { get; protected set; } = ItemManager.Nothing;
|
||||
public string EarsName { get; protected set; } = ItemManager.Nothing;
|
||||
public string NeckName { get; protected set; } = ItemManager.Nothing;
|
||||
public string WristsName { get; protected set; } = ItemManager.Nothing;
|
||||
public string RFingerName { get; protected set; } = ItemManager.Nothing;
|
||||
public string LFingerName { get; protected set; } = ItemManager.Nothing;
|
||||
public string MainhandName { get; protected set; }
|
||||
public string OffhandName { get; protected set; }
|
||||
|
||||
public Customize Customize()
|
||||
=> CharacterData.Customize;
|
||||
|
||||
public CharacterEquip Equipment()
|
||||
=> CharacterData.Equipment;
|
||||
|
||||
public DesignBase()
|
||||
{
|
||||
MainHand = Glamourer.Items.DefaultSword.RowId;
|
||||
(_, CharacterData.MainHand.Set, CharacterData.MainHand.Type, CharacterData.MainHand.Variant, MainhandName, MainhandType) =
|
||||
Glamourer.Items.Resolve(MainHand, Glamourer.Items.DefaultSword);
|
||||
OffHand = ItemManager.NothingId(MainhandType.Offhand());
|
||||
(_, CharacterData.OffHand.Set, CharacterData.OffHand.Type, CharacterData.OffHand.Variant, OffhandName, _) =
|
||||
Glamourer.Items.Resolve(OffHand, MainhandType);
|
||||
}
|
||||
|
||||
public uint ModelId
|
||||
=> CharacterData.ModelId;
|
||||
|
||||
public Item Armor(EquipSlot slot)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.Head => new Item(HeadName, Head, CharacterData.Head),
|
||||
EquipSlot.Body => new Item(BodyName, Body, CharacterData.Body),
|
||||
EquipSlot.Hands => new Item(HandsName, Hands, CharacterData.Hands),
|
||||
EquipSlot.Legs => new Item(LegsName, Legs, CharacterData.Legs),
|
||||
EquipSlot.Feet => new Item(FeetName, Feet, CharacterData.Feet),
|
||||
EquipSlot.Ears => new Item(EarsName, Ears, CharacterData.Ears),
|
||||
EquipSlot.Neck => new Item(NeckName, Neck, CharacterData.Neck),
|
||||
EquipSlot.Wrists => new Item(WristsName, Wrists, CharacterData.Wrists),
|
||||
EquipSlot.RFinger => new Item(RFingerName, RFinger, CharacterData.RFinger),
|
||||
EquipSlot.LFinger => new Item(LFingerName, LFinger, CharacterData.LFinger),
|
||||
_ => throw new Exception("Invalid equip slot for item."),
|
||||
};
|
||||
}
|
||||
|
||||
public Weapon WeaponMain
|
||||
=> new(MainhandName, MainHand, CharacterData.MainHand, MainhandType);
|
||||
|
||||
public Weapon WeaponOff
|
||||
=> Designs.Weapon.Offhand(OffhandName, OffHand, CharacterData.OffHand, MainhandType);
|
||||
|
||||
public CustomizeValue GetCustomize(CustomizeIndex idx)
|
||||
=> Customize()[idx];
|
||||
|
||||
protected bool SetCustomize(CustomizeIndex idx, CustomizeValue value)
|
||||
{
|
||||
var c = Customize();
|
||||
if (c[idx] == value)
|
||||
return false;
|
||||
|
||||
c[idx] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool SetArmor(EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
|
||||
{
|
||||
var (valid, set, variant, name) = Glamourer.Items.Resolve(slot, itemId, item);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
return SetArmor(slot, set, variant, name, itemId);
|
||||
}
|
||||
|
||||
protected bool UpdateArmor(EquipSlot slot, CharacterArmor armor, bool force)
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.Head when CharacterData.Head.Value == armor.Value: return false;
|
||||
case EquipSlot.Body when CharacterData.Body.Value == armor.Value: return false;
|
||||
case EquipSlot.Hands when CharacterData.Hands.Value == armor.Value: return false;
|
||||
case EquipSlot.Legs when CharacterData.Legs.Value == armor.Value: return false;
|
||||
case EquipSlot.Feet when CharacterData.Feet.Value == armor.Value: return false;
|
||||
case EquipSlot.Ears when CharacterData.Ears.Value == armor.Value: return false;
|
||||
case EquipSlot.Neck when CharacterData.Neck.Value == armor.Value: return false;
|
||||
case EquipSlot.Wrists when CharacterData.Wrists.Value == armor.Value: return false;
|
||||
case EquipSlot.RFinger when CharacterData.RFinger.Value == armor.Value: return false;
|
||||
case EquipSlot.LFinger when CharacterData.LFinger.Value == armor.Value: return false;
|
||||
}
|
||||
}
|
||||
|
||||
var (valid, id, name) = Glamourer.Items.Identify(slot, armor.Set, armor.Variant);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
return SetArmor(slot, armor.Set, armor.Variant, name, id);
|
||||
}
|
||||
|
||||
protected bool SetMainhand(uint mainId, Lumina.Excel.GeneratedSheets.Item? main = null)
|
||||
{
|
||||
if (mainId == MainHand)
|
||||
return false;
|
||||
|
||||
var (valid, set, weapon, variant, name, type) = Glamourer.Items.Resolve(mainId, main);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
var fixOffhand = type.Offhand() != MainhandType.Offhand();
|
||||
|
||||
MainHand = mainId;
|
||||
MainhandName = name;
|
||||
MainhandType = type;
|
||||
CharacterData.MainHand.Set = set;
|
||||
CharacterData.MainHand.Type = weapon;
|
||||
CharacterData.MainHand.Variant = variant;
|
||||
if (fixOffhand)
|
||||
SetOffhand(ItemManager.NothingId(type.Offhand()));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool SetOffhand(uint offId, Lumina.Excel.GeneratedSheets.Item? off = null)
|
||||
{
|
||||
if (offId == OffHand)
|
||||
return false;
|
||||
|
||||
var (valid, set, weapon, variant, name, type) = Glamourer.Items.Resolve(offId, MainhandType, off);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
OffHand = offId;
|
||||
OffhandName = name;
|
||||
CharacterData.OffHand.Set = set;
|
||||
CharacterData.OffHand.Type = weapon;
|
||||
CharacterData.OffHand.Variant = variant;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool UpdateMainhand(CharacterWeapon weapon)
|
||||
{
|
||||
if (weapon.Value == CharacterData.MainHand.Value)
|
||||
return false;
|
||||
|
||||
var (valid, id, name, type) = Glamourer.Items.Identify(EquipSlot.MainHand, weapon.Set, weapon.Type, (byte)weapon.Variant);
|
||||
if (!valid || id == MainHand)
|
||||
return false;
|
||||
|
||||
var fixOffhand = type.Offhand() != MainhandType.Offhand();
|
||||
|
||||
MainHand = id;
|
||||
MainhandName = name;
|
||||
MainhandType = type;
|
||||
CharacterData.MainHand.Set = weapon.Set;
|
||||
CharacterData.MainHand.Type = weapon.Type;
|
||||
CharacterData.MainHand.Variant = weapon.Variant;
|
||||
CharacterData.MainHand.Stain = weapon.Stain;
|
||||
if (fixOffhand)
|
||||
SetOffhand(ItemManager.NothingId(type.Offhand()));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool UpdateOffhand(CharacterWeapon weapon)
|
||||
{
|
||||
if (weapon.Value == CharacterData.OffHand.Value)
|
||||
return false;
|
||||
|
||||
var (valid, id, name, _) = Glamourer.Items.Identify(EquipSlot.OffHand, weapon.Set, weapon.Type, (byte)weapon.Variant, MainhandType);
|
||||
if (!valid || id == OffHand)
|
||||
return false;
|
||||
|
||||
OffHand = id;
|
||||
OffhandName = name;
|
||||
CharacterData.OffHand.Set = weapon.Set;
|
||||
CharacterData.OffHand.Type = weapon.Type;
|
||||
CharacterData.OffHand.Variant = weapon.Variant;
|
||||
CharacterData.OffHand.Stain = weapon.Stain;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool SetStain(EquipSlot slot, StainId id)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.MainHand => SetIfDifferent(ref CharacterData.MainHand.Stain, id),
|
||||
EquipSlot.OffHand => SetIfDifferent(ref CharacterData.OffHand.Stain, id),
|
||||
EquipSlot.Head => SetIfDifferent(ref CharacterData.Head.Stain, id),
|
||||
EquipSlot.Body => SetIfDifferent(ref CharacterData.Body.Stain, id),
|
||||
EquipSlot.Hands => SetIfDifferent(ref CharacterData.Hands.Stain, id),
|
||||
EquipSlot.Legs => SetIfDifferent(ref CharacterData.Legs.Stain, id),
|
||||
EquipSlot.Feet => SetIfDifferent(ref CharacterData.Feet.Stain, id),
|
||||
EquipSlot.Ears => SetIfDifferent(ref CharacterData.Ears.Stain, id),
|
||||
EquipSlot.Neck => SetIfDifferent(ref CharacterData.Neck.Stain, id),
|
||||
EquipSlot.Wrists => SetIfDifferent(ref CharacterData.Wrists.Stain, id),
|
||||
EquipSlot.RFinger => SetIfDifferent(ref CharacterData.RFinger.Stain, id),
|
||||
EquipSlot.LFinger => SetIfDifferent(ref CharacterData.LFinger.Stain, id),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
protected static bool SetIfDifferent<T>(ref T old, T value) where T : IEquatable<T>
|
||||
{
|
||||
if (old.Equals(value))
|
||||
return false;
|
||||
|
||||
old = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private bool SetArmor(EquipSlot slot, SetId set, byte variant, string name, uint id)
|
||||
{
|
||||
var changes = false;
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.Head:
|
||||
changes |= SetIfDifferent(ref CharacterData.Head.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Head.Variant, variant);
|
||||
changes |= HeadName != name;
|
||||
HeadName = name;
|
||||
changes |= Head != id;
|
||||
Head = id;
|
||||
return changes;
|
||||
case EquipSlot.Body:
|
||||
changes |= SetIfDifferent(ref CharacterData.Body.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Body.Variant, variant);
|
||||
changes |= BodyName != name;
|
||||
BodyName = name;
|
||||
changes |= Body != id;
|
||||
Body = id;
|
||||
return changes;
|
||||
case EquipSlot.Hands:
|
||||
changes |= SetIfDifferent(ref CharacterData.Hands.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Hands.Variant, variant);
|
||||
changes |= HandsName != name;
|
||||
HandsName = name;
|
||||
changes |= Hands != id;
|
||||
Hands = id;
|
||||
return changes;
|
||||
case EquipSlot.Legs:
|
||||
changes |= SetIfDifferent(ref CharacterData.Legs.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Legs.Variant, variant);
|
||||
changes |= LegsName != name;
|
||||
LegsName = name;
|
||||
changes |= Legs != id;
|
||||
Legs = id;
|
||||
return changes;
|
||||
case EquipSlot.Feet:
|
||||
changes |= SetIfDifferent(ref CharacterData.Feet.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Feet.Variant, variant);
|
||||
changes |= FeetName != name;
|
||||
FeetName = name;
|
||||
changes |= Feet != id;
|
||||
Feet = id;
|
||||
return changes;
|
||||
case EquipSlot.Ears:
|
||||
changes |= SetIfDifferent(ref CharacterData.Ears.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Ears.Variant, variant);
|
||||
changes |= EarsName != name;
|
||||
EarsName = name;
|
||||
changes |= Ears != id;
|
||||
Ears = id;
|
||||
return changes;
|
||||
case EquipSlot.Neck:
|
||||
changes |= SetIfDifferent(ref CharacterData.Neck.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Neck.Variant, variant);
|
||||
changes |= NeckName != name;
|
||||
NeckName = name;
|
||||
changes |= Neck != id;
|
||||
Neck = id;
|
||||
return changes;
|
||||
case EquipSlot.Wrists:
|
||||
changes |= SetIfDifferent(ref CharacterData.Wrists.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Wrists.Variant, variant);
|
||||
changes |= WristsName != name;
|
||||
WristsName = name;
|
||||
changes |= Wrists != id;
|
||||
Wrists = id;
|
||||
return changes;
|
||||
case EquipSlot.RFinger:
|
||||
changes |= SetIfDifferent(ref CharacterData.RFinger.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.RFinger.Variant, variant);
|
||||
changes |= RFingerName != name;
|
||||
RFingerName = name;
|
||||
changes |= RFinger != id;
|
||||
RFinger = id;
|
||||
return changes;
|
||||
case EquipSlot.LFinger:
|
||||
changes |= SetIfDifferent(ref CharacterData.LFinger.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.LFinger.Variant, variant);
|
||||
changes |= LFingerName != name;
|
||||
LFingerName = name;
|
||||
changes |= LFinger != id;
|
||||
LFinger = id;
|
||||
return changes;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
179
Glamourer/Designs/DesignFileSystem.cs
Normal file
179
Glamourer/Designs/DesignFileSystem.cs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Plugin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Filesystem;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public sealed class DesignFileSystem : FileSystem<Design>, IDisposable
|
||||
{
|
||||
public static string GetDesignFileSystemFile(DalamudPluginInterface pi)
|
||||
=> Path.Combine(pi.GetPluginConfigDirectory(), "sort_order.json");
|
||||
|
||||
public readonly string DesignFileSystemFile;
|
||||
private readonly FrameworkManager _framework;
|
||||
private readonly Design.Manager _designManager;
|
||||
|
||||
public DesignFileSystem(Design.Manager designManager, DalamudPluginInterface pi, FrameworkManager framework)
|
||||
{
|
||||
DesignFileSystemFile = GetDesignFileSystemFile(pi);
|
||||
_designManager = designManager;
|
||||
_framework = framework;
|
||||
_designManager.DesignChange += OnDataChange;
|
||||
Changed += OnChange;
|
||||
Reload();
|
||||
}
|
||||
|
||||
private void Reload()
|
||||
{
|
||||
if (Load(new FileInfo(DesignFileSystemFile), _designManager.Designs, DesignToIdentifier, DesignToName))
|
||||
SaveFilesystem();
|
||||
|
||||
Glamourer.Log.Debug("Reloaded design filesystem.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_designManager.DesignChange -= OnDataChange;
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
public void Save()
|
||||
=> _framework.RegisterDelayed(nameof(SaveFilesystem), SaveFilesystem);
|
||||
|
||||
private void OnDataChange(Design.Manager.DesignChangeType type, Design design, object? data)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Design.Manager.DesignChangeType.Created:
|
||||
var originalName = design.Name.Text.FixName();
|
||||
var name = originalName;
|
||||
var counter = 1;
|
||||
while (Find(name, out _))
|
||||
name = $"{originalName} ({++counter})";
|
||||
|
||||
CreateLeaf(Root, name, design);
|
||||
break;
|
||||
case Design.Manager.DesignChangeType.Deleted:
|
||||
if (FindLeaf(design, out var leaf))
|
||||
Delete(leaf);
|
||||
break;
|
||||
case Design.Manager.DesignChangeType.ReloadedAll:
|
||||
Reload();
|
||||
break;
|
||||
case Design.Manager.DesignChangeType.Renamed when data is string oldName:
|
||||
var old = oldName.FixName();
|
||||
if (Find(old, out var child) && child is not Folder)
|
||||
Rename(child, design.Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Used for saving and loading.
|
||||
private static string DesignToIdentifier(Design design)
|
||||
=> design.Identifier.ToString();
|
||||
|
||||
private static string DesignToName(Design design)
|
||||
=> design.Name.Text.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)
|
||||
: (DesignToIdentifier(design), true);
|
||||
|
||||
// Search the entire filesystem for the leaf corresponding to a design.
|
||||
public bool FindLeaf(Design design, [NotNullWhen(true)] out Leaf? leaf)
|
||||
{
|
||||
leaf = Root.GetAllDescendants(ISortMode<Design>.Lexicographical)
|
||||
.OfType<Leaf>()
|
||||
.FirstOrDefault(l => l.Value == design);
|
||||
return leaf != null;
|
||||
}
|
||||
|
||||
internal static void MigrateOldPaths(DalamudPluginInterface pi, Dictionary<string, string> oldPaths)
|
||||
{
|
||||
if (oldPaths.Count == 0)
|
||||
return;
|
||||
|
||||
var file = GetDesignFileSystemFile(pi);
|
||||
try
|
||||
{
|
||||
JObject jObject;
|
||||
if (File.Exists(file))
|
||||
{
|
||||
var text = File.ReadAllText(file);
|
||||
jObject = JObject.Parse(text);
|
||||
var dict = jObject["Data"]?.ToObject<Dictionary<string, string>>();
|
||||
if (dict != null)
|
||||
foreach (var (key, value) in dict)
|
||||
oldPaths.TryAdd(key, value);
|
||||
|
||||
jObject["Data"] = JToken.FromObject(oldPaths);
|
||||
}
|
||||
else
|
||||
{
|
||||
jObject = new JObject
|
||||
{
|
||||
["Data"] = JToken.FromObject(oldPaths),
|
||||
["EmptyFolders"] = JToken.FromObject(Array.Empty<string>()),
|
||||
};
|
||||
}
|
||||
|
||||
var data = jObject.ToString(Formatting.Indented);
|
||||
File.WriteAllText(file, data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not migrate old folder paths to new version:\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
72
Glamourer/Designs/EquipFlag.cs
Normal file
72
Glamourer/Designs/EquipFlag.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
[Flags]
|
||||
public enum EquipFlag : uint
|
||||
{
|
||||
Head = 0x00000001,
|
||||
Body = 0x00000002,
|
||||
Hands = 0x00000004,
|
||||
Legs = 0x00000008,
|
||||
Feet = 0x00000010,
|
||||
Ears = 0x00000020,
|
||||
Neck = 0x00000040,
|
||||
Wrist = 0x00000080,
|
||||
RFinger = 0x00000100,
|
||||
LFinger = 0x00000200,
|
||||
Mainhand = 0x00000400,
|
||||
Offhand = 0x00000800,
|
||||
HeadStain = 0x00001000,
|
||||
BodyStain = 0x00002000,
|
||||
HandsStain = 0x00004000,
|
||||
LegsStain = 0x00008000,
|
||||
FeetStain = 0x00010000,
|
||||
EarsStain = 0x00020000,
|
||||
NeckStain = 0x00040000,
|
||||
WristStain = 0x00080000,
|
||||
RFingerStain = 0x00100000,
|
||||
LFingerStain = 0x00200000,
|
||||
MainhandStain = 0x00400000,
|
||||
OffhandStain = 0x00800000,
|
||||
}
|
||||
|
||||
public static class EquipFlagExtensions
|
||||
{
|
||||
public static EquipFlag ToFlag(this EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => EquipFlag.Mainhand,
|
||||
EquipSlot.OffHand => EquipFlag.Offhand,
|
||||
EquipSlot.Head => EquipFlag.Head,
|
||||
EquipSlot.Body => EquipFlag.Body,
|
||||
EquipSlot.Hands => EquipFlag.Hands,
|
||||
EquipSlot.Legs => EquipFlag.Legs,
|
||||
EquipSlot.Feet => EquipFlag.Feet,
|
||||
EquipSlot.Ears => EquipFlag.Ears,
|
||||
EquipSlot.Neck => EquipFlag.Neck,
|
||||
EquipSlot.Wrists => EquipFlag.Wrist,
|
||||
EquipSlot.RFinger => EquipFlag.RFinger,
|
||||
EquipSlot.LFinger => EquipFlag.LFinger,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public static EquipFlag ToStainFlag(this EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => EquipFlag.MainhandStain,
|
||||
EquipSlot.OffHand => EquipFlag.OffhandStain,
|
||||
EquipSlot.Head => EquipFlag.HeadStain,
|
||||
EquipSlot.Body => EquipFlag.BodyStain,
|
||||
EquipSlot.Hands => EquipFlag.HandsStain,
|
||||
EquipSlot.Legs => EquipFlag.LegsStain,
|
||||
EquipSlot.Feet => EquipFlag.FeetStain,
|
||||
EquipSlot.Ears => EquipFlag.EarsStain,
|
||||
EquipSlot.Neck => EquipFlag.NeckStain,
|
||||
EquipSlot.Wrists => EquipFlag.WristStain,
|
||||
EquipSlot.RFinger => EquipFlag.RFingerStain,
|
||||
EquipSlot.LFinger => EquipFlag.LFingerStain,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Glamourer.State;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class RevertableDesigns
|
||||
{
|
||||
public readonly Dictionary<string, CharacterSave> Saves = new();
|
||||
|
||||
public bool Add(Character actor)
|
||||
{
|
||||
//var name = actor.Name.ToString();
|
||||
//if (Saves.TryGetValue(name, out var save))
|
||||
// return false;
|
||||
//
|
||||
//save = new CharacterSave();
|
||||
//save.LoadCharacter(actor);
|
||||
//Saves[name] = save;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RevertByNameWithoutApplication(string actorName)
|
||||
{
|
||||
if (!Saves.ContainsKey(actorName))
|
||||
return false;
|
||||
|
||||
Saves.Remove(actorName);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Revert(Character actor)
|
||||
{
|
||||
//if (!Saves.TryGetValue(actor.Name.ToString(), out var save))
|
||||
// return false;
|
||||
//
|
||||
//save.Apply(actor);
|
||||
//Saves.Remove(actor.Name.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
72
Glamourer/Designs/Structs.cs
Normal file
72
Glamourer/Designs/Structs.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public readonly struct Item
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly uint ItemId;
|
||||
public readonly CharacterArmor Model;
|
||||
|
||||
public SetId ModelBase
|
||||
=> Model.Set;
|
||||
|
||||
public byte Variant
|
||||
=> Model.Variant;
|
||||
|
||||
public StainId Stain
|
||||
=> Model.Stain;
|
||||
|
||||
|
||||
public Item(string name, uint itemId, CharacterArmor armor)
|
||||
{
|
||||
Name = name;
|
||||
ItemId = itemId;
|
||||
Model.Set = armor.Set;
|
||||
Model.Variant = armor.Variant;
|
||||
Model.Stain = armor.Stain;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct Weapon
|
||||
{
|
||||
public readonly string Name = string.Empty;
|
||||
public readonly uint ItemId;
|
||||
public readonly FullEquipType Type;
|
||||
public readonly bool Valid;
|
||||
public readonly CharacterWeapon Model;
|
||||
|
||||
public SetId ModelBase
|
||||
=> Model.Set;
|
||||
|
||||
public WeaponType WeaponBase
|
||||
=> Model.Type;
|
||||
|
||||
public byte Variant
|
||||
=> (byte)Model.Variant;
|
||||
|
||||
public StainId Stain
|
||||
=> Model.Stain;
|
||||
|
||||
|
||||
public Weapon(string name, uint itemId, CharacterWeapon weapon, FullEquipType type)
|
||||
{
|
||||
Name = name;
|
||||
ItemId = itemId;
|
||||
Type = type;
|
||||
Valid = true;
|
||||
Model.Set = weapon.Set;
|
||||
Model.Type = weapon.Type;
|
||||
Model.Variant = (byte)weapon.Variant;
|
||||
Model.Stain = weapon.Stain;
|
||||
}
|
||||
|
||||
public static Weapon Offhand(string name, uint itemId, CharacterWeapon weapon, FullEquipType type)
|
||||
{
|
||||
var offType = type.Offhand();
|
||||
return offType is FullEquipType.Unknown
|
||||
? new Weapon()
|
||||
: new Weapon(name, itemId, weapon, offType);
|
||||
}
|
||||
}
|
||||
34
Glamourer/Fixed/FixedCondition.cs
Normal file
34
Glamourer/Fixed/FixedCondition.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using Glamourer.Interop;
|
||||
using Glamourer.Structs;
|
||||
|
||||
namespace Glamourer.Fixed;
|
||||
|
||||
public struct FixedCondition
|
||||
{
|
||||
private const ulong _territoryFlag = 1ul << 32;
|
||||
private const ulong _jobFlag = 1ul << 33;
|
||||
private ulong _data;
|
||||
|
||||
public static FixedCondition TerritoryCondition(ushort territoryType)
|
||||
=> new() { _data = territoryType | _territoryFlag };
|
||||
|
||||
public static FixedCondition JobCondition(JobGroup group)
|
||||
=> new() { _data = group.Id | _jobFlag };
|
||||
|
||||
public bool Check(Actor actor)
|
||||
{
|
||||
if ((_data & (_territoryFlag | _jobFlag)) == 0)
|
||||
return true;
|
||||
|
||||
if ((_data & _territoryFlag) != 0)
|
||||
return Dalamud.ClientState.TerritoryType == (ushort)_data;
|
||||
|
||||
if (actor && GameData.JobGroups(Dalamud.GameData).TryGetValue((ushort)_data, out var group) && group.Fits(actor.Job))
|
||||
return true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> _data.ToString();
|
||||
}
|
||||
|
|
@ -2,69 +2,34 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using Dalamud.Logging;
|
||||
using System.Runtime;
|
||||
using System.Text;
|
||||
using Dalamud.Utility;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Glamourer.Saves;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Glamourer.Designs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public struct FixedCondition
|
||||
{
|
||||
private const ulong _territoryFlag = 1ul << 32;
|
||||
private const ulong _jobFlag = 1ul << 33;
|
||||
private ulong _data;
|
||||
|
||||
public static FixedCondition TerritoryCondition(ushort territoryType)
|
||||
=> new() { _data = territoryType | _territoryFlag };
|
||||
|
||||
public static FixedCondition JobCondition(JobGroup group)
|
||||
=> new() { _data = group.Id | _jobFlag };
|
||||
|
||||
public bool Check(Actor actor)
|
||||
{
|
||||
if ((_data & (_territoryFlag | _jobFlag)) == 0)
|
||||
return true;
|
||||
|
||||
if ((_data & _territoryFlag) != 0)
|
||||
return Dalamud.ClientState.TerritoryType == (ushort)_data;
|
||||
|
||||
if (actor && GameData.JobGroups(Dalamud.GameData).TryGetValue((ushort)_data, out var group) && group.Fits(actor.Job))
|
||||
return true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> _data.ToString();
|
||||
}
|
||||
namespace Glamourer.Fixed;
|
||||
|
||||
public class FixedDesign
|
||||
{
|
||||
public const int CurrentVersion = 0;
|
||||
|
||||
public string Name { get; private set; }
|
||||
public bool Enabled;
|
||||
public List<ActorIdentifier> Actors;
|
||||
public string Name { get; private set; }
|
||||
public bool Enabled;
|
||||
public List<ActorIdentifier> Actors;
|
||||
public List<(FixedCondition, Design)> Customization;
|
||||
public List<(FixedCondition, Design)> Equipment;
|
||||
public List<(FixedCondition, Design)> Weapons;
|
||||
|
||||
public FixedDesign(string name)
|
||||
{
|
||||
Name = name;
|
||||
Actors = new List<ActorIdentifier>();
|
||||
Name = name;
|
||||
Actors = new List<ActorIdentifier>();
|
||||
Customization = new List<(FixedCondition, Design)>();
|
||||
Equipment = new List<(FixedCondition, Design)>();
|
||||
Weapons = new List<(FixedCondition, Design)>();
|
||||
Equipment = new List<(FixedCondition, Design)>();
|
||||
Weapons = new List<(FixedCondition, Design)>();
|
||||
}
|
||||
|
||||
public static FixedDesign? Load(JObject j)
|
||||
|
|
@ -82,7 +47,7 @@ public class FixedDesign
|
|||
return version switch
|
||||
{
|
||||
CurrentVersion => LoadCurrentVersion(j, name),
|
||||
_ => null,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -100,7 +65,7 @@ public class FixedDesign
|
|||
Enabled = enabled,
|
||||
};
|
||||
|
||||
var actors = j[nameof(Actors)];
|
||||
var actors = j[nameof(Actors)];
|
||||
//foreach(var pair in actors?.Children().)
|
||||
return null;
|
||||
}
|
||||
|
|
@ -164,7 +129,7 @@ public class FixedDesign
|
|||
|
||||
public static bool Load(FileInfo path, [NotNullWhen(true)] out FixedDesign? result)
|
||||
{
|
||||
result = null;
|
||||
result = null!;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,23 @@
|
|||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Api;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Util;
|
||||
using ImGuizmoNET;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Data;
|
||||
using FixedDesigns = Glamourer.State.FixedDesigns;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
|
|
@ -29,20 +36,21 @@ public class Glamourer : IDalamudPlugin
|
|||
Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "Unknown";
|
||||
|
||||
|
||||
public static GlamourerConfig Config = null!;
|
||||
public static Logger Log = null!;
|
||||
|
||||
public static IObjectIdentifier Identifier = null!;
|
||||
public static ActorManager Actors = null!;
|
||||
public static PenumbraAttach Penumbra = null!;
|
||||
public static ICustomizationManager Customization = null!;
|
||||
public static RestrictedGear RestrictedGear = null!;
|
||||
public static RedrawManager RedrawManager = null!;
|
||||
public static GlamourerConfig Config = null!;
|
||||
public static Logger Log = null!;
|
||||
public static ActorManager Actors = null!;
|
||||
public static PenumbraAttach Penumbra = null!;
|
||||
public static ICustomizationManager Customization = null!;
|
||||
public static RedrawManager RedrawManager = null!;
|
||||
public static ItemManager Items = null!;
|
||||
public readonly FixedDesigns FixedDesigns;
|
||||
public readonly CurrentManipulations CurrentManipulations;
|
||||
|
||||
private readonly WindowSystem _windowSystem = new("Glamourer");
|
||||
private readonly Interface _interface;
|
||||
private readonly Design.Manager _designManager;
|
||||
private readonly DesignFileSystem _fileSystem;
|
||||
private readonly FrameworkManager _framework;
|
||||
private readonly WindowSystem _windowSystem = new("Glamourer");
|
||||
private readonly Interface _interface;
|
||||
|
||||
//public readonly DesignManager Designs;
|
||||
|
||||
|
|
@ -56,17 +64,22 @@ public class Glamourer : IDalamudPlugin
|
|||
Dalamud.Initialize(pluginInterface);
|
||||
Log = new Logger();
|
||||
|
||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData);
|
||||
_framework = new FrameworkManager(Dalamud.Framework, Log);
|
||||
|
||||
Config = GlamourerConfig.Load();
|
||||
Items = new ItemManager(Dalamud.PluginInterface, Dalamud.GameData);
|
||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData);
|
||||
|
||||
Identifier = global::Penumbra.GameData.GameData.GetIdentifier(Dalamud.PluginInterface, Dalamud.GameData);
|
||||
Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
|
||||
Backup.CreateBackup(pluginInterface.ConfigDirectory, BackupFiles(Dalamud.PluginInterface));
|
||||
Config = GlamourerConfig.Load();
|
||||
|
||||
Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
|
||||
Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, Dalamud.GameGui,
|
||||
i => (short)Penumbra.CutsceneParent(i));
|
||||
|
||||
_designManager = new Design.Manager(Dalamud.PluginInterface, _framework);
|
||||
_fileSystem = new DesignFileSystem(_designManager, Dalamud.PluginInterface, _framework);
|
||||
FixedDesigns = new FixedDesigns();
|
||||
CurrentManipulations = new CurrentManipulations();
|
||||
//Designs = new DesignManager();
|
||||
|
||||
//GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface);
|
||||
RedrawManager = new RedrawManager(FixedDesigns, CurrentManipulations);
|
||||
|
|
@ -80,7 +93,7 @@ public class Glamourer : IDalamudPlugin
|
|||
HelpMessage = $"Use Glamourer Functions: {HelpString}",
|
||||
});
|
||||
|
||||
_interface = new Interface(this);
|
||||
_interface = new Interface(CurrentManipulations, _designManager, _fileSystem);
|
||||
_windowSystem.AddWindow(_interface);
|
||||
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
|
||||
//FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x);
|
||||
|
|
@ -95,13 +108,15 @@ public class Glamourer : IDalamudPlugin
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
RedrawManager?.Dispose();
|
||||
Penumbra?.Dispose();
|
||||
if (_windowSystem != null)
|
||||
Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
|
||||
_interface?.Dispose();
|
||||
_fileSystem?.Dispose();
|
||||
//GlamourerIpc.Dispose();
|
||||
_framework?.Dispose();
|
||||
Items?.Dispose();
|
||||
Dalamud.Commands.RemoveHandler(ApplyCommandString);
|
||||
Dalamud.Commands.RemoveHandler(MainCommandString);
|
||||
}
|
||||
|
|
@ -241,4 +256,25 @@ public class Glamourer : IDalamudPlugin
|
|||
// return;
|
||||
//}
|
||||
}
|
||||
|
||||
// Collect all relevant files for glamourer configuration.
|
||||
private static IReadOnlyList<FileInfo> BackupFiles(DalamudPluginInterface pi)
|
||||
{
|
||||
var list = new List<FileInfo>(16)
|
||||
{
|
||||
pi.ConfigFile,
|
||||
new(DesignFileSystem.GetDesignFileSystemFile(pi)),
|
||||
};
|
||||
|
||||
var configDir = Dalamud.PluginInterface.ConfigDirectory;
|
||||
if (Directory.Exists(configDir.FullName))
|
||||
{
|
||||
list.Add(new FileInfo(Path.Combine(configDir.FullName, "Designs.json"))); // migration
|
||||
var designDir = new DirectoryInfo(Path.Combine(configDir.FullName, Design.Manager.DesignFolderName));
|
||||
if (designDir.Exists)
|
||||
list.AddRange(designDir.EnumerateFiles("*.json", SearchOption.TopDirectoryOnly));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
|
|
|
|||
39
Glamourer/Gui/Designs/DesignFileSystemSelector.cs
Normal file
39
Glamourer/Gui/Designs/DesignFileSystemSelector.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using Glamourer.Designs;
|
||||
using OtterGui.FileSystem.Selector;
|
||||
|
||||
namespace Glamourer.Gui.Designs;
|
||||
|
||||
public sealed class DesignFileSystemSelector : FileSystemSelector<Design, DesignFileSystemSelector.DesignState>
|
||||
{
|
||||
private readonly Design.Manager _manager;
|
||||
|
||||
public struct DesignState
|
||||
{ }
|
||||
|
||||
public DesignFileSystemSelector(Design.Manager manager, DesignFileSystem fileSystem)
|
||||
: base(fileSystem, Dalamud.KeyState)
|
||||
{
|
||||
_manager = manager;
|
||||
_manager.DesignChange += OnDesignChange;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
_manager.DesignChange -= OnDesignChange;
|
||||
}
|
||||
|
||||
private void OnDesignChange(Design.Manager.DesignChangeType type, Design design, object? oldData)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Design.Manager.DesignChangeType.ReloadedAll:
|
||||
case Design.Manager.DesignChangeType.Renamed:
|
||||
case Design.Manager.DesignChangeType.AddedTag:
|
||||
case Design.Manager.DesignChangeType.ChangedTag:
|
||||
case Design.Manager.DesignChangeType.RemovedTag:
|
||||
SetFilterDirty();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
namespace Glamourer.Gui.Designs;
|
||||
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Glamourer.Designs;
|
||||
using OtterGui.Filesystem;
|
||||
using OtterGui.FileSystem.Selector;
|
||||
|
||||
namespace Glamourer.Gui.Designs;
|
||||
|
||||
//internal partial class Interface
|
||||
//{
|
||||
|
|
|
|||
|
|
@ -2,17 +2,16 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Text;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Item = Glamourer.Structs.Item;
|
||||
|
||||
namespace Glamourer.Gui.Equipment;
|
||||
|
||||
|
|
@ -20,15 +19,15 @@ public partial class EquipmentDrawer
|
|||
{
|
||||
public const int ItemComboWidth = 320;
|
||||
|
||||
private sealed class ItemCombo : FilterComboBase<Item>
|
||||
private sealed class ItemCombo : FilterComboBase<Item2>
|
||||
{
|
||||
public readonly string Label;
|
||||
public readonly EquipSlot Slot;
|
||||
|
||||
public Lumina.Excel.GeneratedSheets.Item? LastItem;
|
||||
private CharacterArmor _lastArmor;
|
||||
private string _lastPreview = string.Empty;
|
||||
private int _lastIndex;
|
||||
public Item? LastItem;
|
||||
private CharacterArmor _lastArmor;
|
||||
private string _lastPreview = string.Empty;
|
||||
private int _lastIndex;
|
||||
|
||||
public ItemCombo(EquipSlot slot)
|
||||
: base(GetItems(slot), false)
|
||||
|
|
@ -37,7 +36,7 @@ public partial class EquipmentDrawer
|
|||
Slot = slot;
|
||||
}
|
||||
|
||||
protected override string ToString(Item obj)
|
||||
protected override string ToString(Item2 obj)
|
||||
=> obj.Name;
|
||||
|
||||
private static string GetLabel(EquipSlot slot)
|
||||
|
|
@ -78,21 +77,21 @@ public partial class EquipmentDrawer
|
|||
_lastPreview = _lastIndex >= 0 ? Items[_lastIndex].Name : LastItem.Name.ToString();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Item> GetItems(EquipSlot slot)
|
||||
=> GameData.ItemsBySlot(Dalamud.GameData).TryGetValue(slot, out var list) ? list : Array.Empty<Item>();
|
||||
private static IReadOnlyList<Item2> GetItems(EquipSlot slot)
|
||||
=> GameData.ItemsBySlot(Dalamud.GameData).TryGetValue(slot, out var list) ? list : Array.Empty<Item2>();
|
||||
}
|
||||
|
||||
private sealed class WeaponCombo : FilterComboBase<Item>
|
||||
private sealed class WeaponCombo : FilterComboBase<Item2>
|
||||
{
|
||||
public readonly string Label;
|
||||
public readonly EquipSlot Slot;
|
||||
|
||||
public Lumina.Excel.GeneratedSheets.Item? LastItem;
|
||||
private CharacterWeapon _lastWeapon = new(ulong.MaxValue);
|
||||
private string _lastPreview = string.Empty;
|
||||
private int _lastIndex;
|
||||
public FullEquipType LastCategory { get; private set; }
|
||||
private bool _drawAll;
|
||||
public Item? LastItem;
|
||||
private CharacterWeapon _lastWeapon = new(ulong.MaxValue);
|
||||
private string _lastPreview = string.Empty;
|
||||
private int _lastIndex;
|
||||
public FullEquipType LastCategory { get; private set; }
|
||||
private bool _drawAll;
|
||||
|
||||
public WeaponCombo(EquipSlot slot)
|
||||
: base(GetItems(slot), false)
|
||||
|
|
@ -101,7 +100,7 @@ public partial class EquipmentDrawer
|
|||
Slot = slot;
|
||||
}
|
||||
|
||||
protected override string ToString(Item obj)
|
||||
protected override string ToString(Item2 obj)
|
||||
=> obj.Name;
|
||||
|
||||
private static string GetLabel(EquipSlot slot)
|
||||
|
|
@ -124,7 +123,7 @@ public partial class EquipmentDrawer
|
|||
}
|
||||
|
||||
UpdateItem(weapon);
|
||||
UpdateCategory(((WeaponCategory) (LastItem!.ItemUICategory?.Row ?? 0)).ToEquipType());
|
||||
UpdateCategory(((WeaponCategory)(LastItem!.ItemUICategory?.Row ?? 0)).ToEquipType());
|
||||
newIdx = _lastIndex;
|
||||
return Draw(Label, _lastPreview, ref newIdx, ItemComboWidth * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight());
|
||||
}
|
||||
|
|
@ -175,8 +174,8 @@ public partial class EquipmentDrawer
|
|||
ResetFilter();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Item> GetItems(EquipSlot slot)
|
||||
=> GameData.ItemsBySlot(Dalamud.GameData).TryGetValue(EquipSlot.MainHand, out var list) ? list : Array.Empty<Item>();
|
||||
private static IReadOnlyList<Item2> GetItems(EquipSlot slot)
|
||||
=> GameData.ItemsBySlot(Dalamud.GameData).TryGetValue(EquipSlot.MainHand, out var list) ? list : Array.Empty<Item2>();
|
||||
}
|
||||
|
||||
private static readonly IObjectIdentifier Identifier;
|
||||
|
|
@ -203,15 +202,15 @@ public partial class EquipmentDrawer
|
|||
UpdateActors();
|
||||
}
|
||||
|
||||
private static CharacterArmor ToArmor(Item item, StainId stain)
|
||||
private static CharacterArmor ToArmor(Item2 item2, StainId stain)
|
||||
{
|
||||
var (id, _, variant) = item.MainModel;
|
||||
var (id, _, variant) = item2.MainModel;
|
||||
return new CharacterArmor(id, (byte)variant, stain);
|
||||
}
|
||||
|
||||
private static CharacterWeapon ToWeapon(Item item, StainId stain)
|
||||
private static CharacterWeapon ToWeapon(Item2 item2, StainId stain)
|
||||
{
|
||||
var (id, type, variant) = item.MainModel;
|
||||
var (id, type, variant) = item2.MainModel;
|
||||
return new CharacterWeapon(id, type, variant, stain);
|
||||
}
|
||||
|
||||
|
|
@ -331,31 +330,31 @@ public partial class EquipmentDrawer
|
|||
//
|
||||
|
||||
|
||||
private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothes = new()
|
||||
private static readonly Item SmallClothes = new()
|
||||
{
|
||||
Name = new SeString("Nothing"),
|
||||
RowId = 0,
|
||||
};
|
||||
|
||||
private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothesNpc = new()
|
||||
private static readonly Item SmallClothesNpc = new()
|
||||
{
|
||||
Name = new SeString("Smallclothes (NPC)"),
|
||||
RowId = 1,
|
||||
};
|
||||
|
||||
private static readonly Lumina.Excel.GeneratedSheets.Item Unknown = new()
|
||||
private static readonly Item Unknown = new()
|
||||
{
|
||||
Name = new SeString("Unknown"),
|
||||
RowId = 2,
|
||||
};
|
||||
|
||||
private static Lumina.Excel.GeneratedSheets.Item Identify(SetId set, WeaponType weapon, ushort variant, EquipSlot slot)
|
||||
private static Item Identify(SetId set, WeaponType weapon, ushort variant, EquipSlot slot)
|
||||
{
|
||||
return (uint)set switch
|
||||
{
|
||||
0 => SmallClothes,
|
||||
9903 => SmallClothesNpc,
|
||||
_ => Identifier.Identify(set, weapon, variant, slot).FirstOrDefault(Unknown),
|
||||
_ => Identifier.Identify(set, weapon, variant, slot.ToSlot()).FirstOrDefault(Unknown),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ public enum ApplicationFlags
|
|||
|
||||
public partial class EquipmentDrawer
|
||||
{
|
||||
private static readonly FilterComboColors _stainCombo;
|
||||
private static readonly StainData _stainData;
|
||||
private static readonly FilterComboColors StainCombo;
|
||||
private static readonly StainData StainData;
|
||||
|
||||
private Race _race;
|
||||
private Gender _gender;
|
||||
|
|
@ -31,10 +31,10 @@ public partial class EquipmentDrawer
|
|||
|
||||
static EquipmentDrawer()
|
||||
{
|
||||
_stainData = new StainData(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.GameData.Language);
|
||||
_stainCombo = new FilterComboColors(140,
|
||||
_stainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))));
|
||||
Identifier = Glamourer.Identifier;
|
||||
StainData = Glamourer.Items.Stains;
|
||||
StainCombo = new FilterComboColors(140,
|
||||
StainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))));
|
||||
Identifier = Glamourer.Items.Identifier;
|
||||
ItemCombos = EquipSlotExtensions.EqdpSlots.Select(s => new ItemCombo(s)).ToArray();
|
||||
MainHandCombo = new WeaponCombo(EquipSlot.MainHand);
|
||||
OffHandCombo = new WeaponCombo(EquipSlot.OffHand);
|
||||
|
|
@ -82,8 +82,8 @@ public partial class EquipmentDrawer
|
|||
|
||||
private void DrawStainCombo()
|
||||
{
|
||||
var found = _stainData.TryGetValue(_currentArmor.Stain, out var stain);
|
||||
_stainCombo.Draw("##stain", stain.RgbaColor, found);
|
||||
var found = StainData.TryGetValue(_currentArmor.Stain, out var stain);
|
||||
StainCombo.Draw("##stain", stain.RgbaColor, found);
|
||||
}
|
||||
|
||||
private void DrawInternal(ref CharacterWeapon mainHand, ref CharacterWeapon offHand)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
using Glamourer.Interop;
|
||||
|
|
@ -13,7 +10,6 @@ using OtterGui;
|
|||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using ImGui = ImGuiNET.ImGui;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
|
@ -30,7 +26,7 @@ internal partial class Interface
|
|||
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
|
||||
private ObjectManager.ActorData _currentData = ObjectManager.ActorData.Invalid;
|
||||
private string _currentLabel = string.Empty;
|
||||
private CurrentDesign? _currentSave;
|
||||
private ActiveDesign? _currentSave;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ internal partial class Interface
|
|||
|
||||
private LowerString _manipulationFilter = LowerString.Empty;
|
||||
private ActorIdentifier _selection = ActorIdentifier.Invalid;
|
||||
private CurrentDesign? _save = null;
|
||||
private ActiveDesign? _save = null;
|
||||
private bool _delete = false;
|
||||
|
||||
public DebugStateTab(CurrentManipulations currentManipulations)
|
||||
|
|
@ -73,14 +73,14 @@ internal partial class Interface
|
|||
DrawSelector(oldSpacing);
|
||||
}
|
||||
|
||||
private bool CheckFilter(KeyValuePair<ActorIdentifier, CurrentDesign> data)
|
||||
private bool CheckFilter(KeyValuePair<ActorIdentifier, ActiveDesign> data)
|
||||
{
|
||||
if (data.Key.Equals(_selection))
|
||||
_save = data.Value;
|
||||
return _manipulationFilter.Length == 0 || _manipulationFilter.IsContained(data.Key.ToString()!);
|
||||
}
|
||||
|
||||
private void DrawSelectable(KeyValuePair<ActorIdentifier, CurrentDesign> data)
|
||||
private void DrawSelectable(KeyValuePair<ActorIdentifier, ActiveDesign> data)
|
||||
{
|
||||
var equal = data.Key.Equals(_selection);
|
||||
if (ImGui.Selectable(data.Key.ToString(), equal))
|
||||
|
|
|
|||
64
Glamourer/Gui/Interface.DesignTab.cs
Normal file
64
Glamourer/Gui/Interface.DesignTab.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Designs;
|
||||
using Glamourer.Gui.Equipment;
|
||||
using Glamourer.Interop;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private class DesignTab : IDisposable
|
||||
{
|
||||
public readonly DesignFileSystemSelector Selector;
|
||||
private readonly DesignFileSystem _fileSystem;
|
||||
private readonly Design.Manager _manager;
|
||||
|
||||
public DesignTab(Design.Manager manager, DesignFileSystem fileSystem)
|
||||
{
|
||||
_manager = manager;
|
||||
_fileSystem = fileSystem;
|
||||
Selector = new DesignFileSystemSelector(manager, fileSystem);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> Selector.Dispose();
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Designs");
|
||||
if (!tab)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Selector.Draw(GetDesignSelectorSize());
|
||||
ImGui.SameLine();
|
||||
DrawDesignPanel();
|
||||
}
|
||||
|
||||
public float GetDesignSelectorSize()
|
||||
=> 200f * ImGuiHelpers.GlobalScale;
|
||||
|
||||
public void DrawDesignPanel()
|
||||
{
|
||||
using var child = ImRaii.Child("##DesignPanel", new Vector2(-0.001f), true, ImGuiWindowFlags.HorizontalScrollbar);
|
||||
if (!child || Selector.Selected == null)
|
||||
return;
|
||||
|
||||
CustomizationDrawer.Draw(Selector.Selected.Customize(), Selector.Selected.Equipment(), true);
|
||||
var weapon = Selector.Selected.WeaponMain;
|
||||
var mw = new CharacterWeapon(weapon.ModelBase, weapon.WeaponBase, weapon.Variant, weapon.Stain);
|
||||
weapon = Selector.Selected.WeaponOff;
|
||||
var ow = new CharacterWeapon(weapon.ModelBase, weapon.WeaponBase, weapon.Variant, weapon.Stain);
|
||||
ApplicationFlags f = 0;
|
||||
EquipmentDrawer.Draw(Selector.Selected.Customize(), Selector.Selected.Equipment(), ref mw, ref ow, ref f, Array.Empty<Actor>(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
|
||||
|
|
@ -11,16 +13,14 @@ namespace Glamourer.Gui;
|
|||
|
||||
internal partial class Interface : Window, IDisposable
|
||||
{
|
||||
private readonly Glamourer _plugin;
|
||||
|
||||
private readonly ActorTab _actorTab;
|
||||
private readonly DesignTab _designTab;
|
||||
private readonly DebugStateTab _debugStateTab;
|
||||
private readonly DebugDataTab _debugDataTab;
|
||||
|
||||
public Interface(Glamourer plugin)
|
||||
public Interface(CurrentManipulations manipulations, Design.Manager manager, DesignFileSystem fileSystem)
|
||||
: base(GetLabel())
|
||||
{
|
||||
_plugin = plugin;
|
||||
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += Toggle;
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
|
|
@ -28,9 +28,10 @@ internal partial class Interface : Window, IDisposable
|
|||
MinimumSize = new Vector2(675, 675),
|
||||
MaximumSize = ImGui.GetIO().DisplaySize,
|
||||
};
|
||||
_actorTab = new ActorTab(_plugin.CurrentManipulations);
|
||||
_debugStateTab = new DebugStateTab(_plugin.CurrentManipulations);
|
||||
_actorTab = new ActorTab(manipulations);
|
||||
_debugStateTab = new DebugStateTab(manipulations);
|
||||
_debugDataTab = new DebugDataTab(Glamourer.Customization);
|
||||
_designTab = new DesignTab(manager, fileSystem);
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
|
|
@ -44,6 +45,7 @@ internal partial class Interface : Window, IDisposable
|
|||
UpdateState();
|
||||
|
||||
_actorTab.Draw();
|
||||
_designTab.Draw();
|
||||
DrawSettingsTab();
|
||||
_debugStateTab.Draw();
|
||||
_debugDataTab.Draw();
|
||||
|
|
@ -61,6 +63,7 @@ internal partial class Interface : Window, IDisposable
|
|||
{
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= Toggle;
|
||||
CustomizationDrawer.Dispose();
|
||||
_designTab.Dispose();
|
||||
}
|
||||
|
||||
private static string GetLabel()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using System.Reflection;
|
|||
using ImGuiNET;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Item = Glamourer.Structs.Item;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
namespace Glamourer.Interop;
|
||||
|
||||
public static class Offsets
|
||||
{
|
||||
public static class Character
|
||||
{
|
||||
public const int Wetness = 0x1ADA;
|
||||
public const int HatVisible = 0x84E;
|
||||
public const int VisorToggled = 0x84F;
|
||||
public const int WeaponHidden1 = 0x84F;
|
||||
public const int WeaponHidden2 = 0x72C;
|
||||
public const int Alpha = 0x19E0;
|
||||
|
||||
public static class Flags
|
||||
{
|
||||
public const byte IsHatHidden = 0x01;
|
||||
public const byte IsVisorToggled = 0x08;
|
||||
public const byte IsWet = 0x80;
|
||||
public const byte IsWeaponHidden1 = 0x01;
|
||||
public const byte IsWeaponHidden2 = 0x02;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ public unsafe partial class RedrawManager
|
|||
[Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
|
||||
|
||||
|
||||
public bool UpdateCustomize(Actor actor, Customize customize)
|
||||
{
|
||||
if (!actor.Valid || !actor.DrawObject.Valid)
|
||||
|
|
@ -43,10 +45,11 @@ public unsafe partial class RedrawManager
|
|||
return;
|
||||
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
var state = (*flags & 0x40) != 0;
|
||||
var state = (*flags & Offsets.DrawObjectVisorStateFlag) != 0;
|
||||
if (state == on)
|
||||
return;
|
||||
|
||||
*flags = (byte)((on ? *flags | 0x40 : *flags & 0xBF) | 0x80);
|
||||
var newFlag = (byte)(on ? *flags | Offsets.DrawObjectVisorStateFlag : *flags & ~Offsets.DrawObjectVisorStateFlag);
|
||||
*flags = (byte) (newFlag | Offsets.DrawObjectVisorToggleFlag);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Designs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -10,59 +9,76 @@ namespace Glamourer.Interop;
|
|||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data);
|
||||
private delegate ulong FlagSlotForUpdateDelegate(nint drawObject, uint slot, CharacterArmor* data);
|
||||
|
||||
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
||||
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A", DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
private readonly Hook<FlagSlotForUpdateDelegate> _flagSlotForUpdateHook = null!;
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, CharacterArmor* data)
|
||||
public void UpdateSlot(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
=> FlagSlotForUpdateDetourBase(drawObject.Address, slot.ToIndex(), &data, true);
|
||||
|
||||
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
|
||||
{
|
||||
var armor = drawObject.Equip[slot] with { Stain = stain};
|
||||
UpdateSlot(drawObject, slot, armor);
|
||||
}
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||
=> FlagSlotForUpdateDetourBase(drawObject, slotIdx, data, false);
|
||||
|
||||
private ulong FlagSlotForUpdateDetourBase(nint drawObject, uint slotIdx, CharacterArmor* data, bool manual)
|
||||
{
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
try
|
||||
{
|
||||
var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||
var identifier = actor.GetIdentifier();
|
||||
|
||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||
{
|
||||
PluginLog.Information($"Loaded {slot} from fixed design for {identifier}.");
|
||||
(var replaced, *data) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(save.Equipment[slot], slot, (Race)drawObject->Race, (Gender)drawObject->Sex);
|
||||
}
|
||||
else if (_currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
{
|
||||
PluginLog.Information($"Updated {slot} from current designs for {identifier}.");
|
||||
(var replaced, *data) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1));
|
||||
save2.Data.Equipment[slot] = *data;
|
||||
}
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
Glamourer.Log.Verbose(
|
||||
$"Flagged slot {slot} of 0x{(ulong)drawObject:X} for update with {data->Set.Value}-{data->Variant} (Stain {data->Stain.Value}).");
|
||||
HandleEquipUpdate(drawObject, slot, ref *data, manual);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error($"Error on loading new gear:\n{e}");
|
||||
Glamourer.Log.Error($"Error invoking SlotUpdate:\n{ex}");
|
||||
}
|
||||
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
|
||||
//try
|
||||
//{
|
||||
// var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||
// var identifier = actor.GetIdentifier();
|
||||
//
|
||||
// if (_fixedDesigns.TryGetDesign(identifier, out var design))
|
||||
// {
|
||||
// PluginLog.Information($"Loaded {slot} from fixed design for {identifier}.");
|
||||
// (var replaced, *data) =
|
||||
// Glamourer.Items.RestrictedGear.ResolveRestricted(design.Armor(slot).Model, slot, (Race)drawObject->Race, (Gender)drawObject->Sex);
|
||||
// }
|
||||
// else if (_currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
// {
|
||||
// PluginLog.Information($"Updated {slot} from current designs for {identifier}.");
|
||||
// (var replaced, *data) =
|
||||
// Glamourer.Items.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1));
|
||||
// save2.Data.Equipment[slot] = *data;
|
||||
// }
|
||||
//}
|
||||
//catch (Exception e)
|
||||
//{
|
||||
// PluginLog.Error($"Error on loading new gear:\n{e}");
|
||||
//}
|
||||
//
|
||||
//return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
public bool ChangeEquip(DrawObject drawObject, uint slotIdx, CharacterArmor data)
|
||||
private void HandleEquipUpdate(nint drawObject, EquipSlot slot, ref CharacterArmor data, bool manual)
|
||||
{
|
||||
if (!drawObject)
|
||||
return false;
|
||||
var actor = Glamourer.Penumbra.GameObjectFromDrawObject(drawObject);
|
||||
var identifier = actor.GetIdentifier();
|
||||
|
||||
if (slotIdx > 9)
|
||||
return false;
|
||||
if (!_currentManipulations.TryGetDesign(identifier, out var design))
|
||||
return;
|
||||
|
||||
return FlagSlotForUpdateDetour(drawObject.Pointer, slotIdx, &data) != 0;
|
||||
var flag = slot.ToFlag();
|
||||
var stainFlag = slot.ToStainFlag();
|
||||
}
|
||||
|
||||
public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
|
||||
=> actor && ChangeEquip(actor.DrawObject, slot.ToIndex(), data);
|
||||
|
||||
public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
=> ChangeEquip(drawObject, slot.ToIndex(), data);
|
||||
|
||||
public bool ChangeEquip(Actor actor, uint slotIdx, CharacterArmor data)
|
||||
=> actor && ChangeEquip(actor.DrawObject, slotIdx, data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ public unsafe partial class RedrawManager
|
|||
PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
|
||||
weapon = slot switch
|
||||
{
|
||||
0 => save.MainHand.Value,
|
||||
1 => save.OffHand.Value,
|
||||
0 => save.WeaponMain.Model.Value,
|
||||
1 => save.WeaponOff.Model.Value,
|
||||
_ => weapon,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,40 @@
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public class DesignBaseValidator
|
||||
{
|
||||
private readonly CustomizationManager _manager;
|
||||
private readonly RestrictedGear _restrictedGear;
|
||||
|
||||
public DesignBaseValidator(CustomizationManager manager, RestrictedGear restrictedGear)
|
||||
{
|
||||
_manager = manager;
|
||||
_restrictedGear = restrictedGear;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
private delegate void ChangeJobDelegate(IntPtr data, uint job);
|
||||
|
||||
[Signature("88 51 ?? 44 3B CA", DetourName = nameof(ChangeJobDetour))]
|
||||
[Signature(Sigs.ChangeJob, DetourName = nameof(ChangeJobDetour))]
|
||||
private readonly Hook<ChangeJobDelegate> _changeJobHook = null!;
|
||||
|
||||
private void ChangeJobDetour(IntPtr data, uint job)
|
||||
{
|
||||
_changeJobHook.Original(data, job);
|
||||
JobChanged?.Invoke(data - 0x1A8, GameData.Jobs(Dalamud.GameData)[(byte)job]);
|
||||
JobChanged?.Invoke(data - Offsets.Character.ClassJobContainer, GameData.Jobs(Dalamud.GameData)[(byte)job]);
|
||||
}
|
||||
|
||||
public event Action<Actor, Job>? JobChanged;
|
||||
|
|
@ -70,18 +83,18 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
// Apply customization if they correspond and there is customization to apply.
|
||||
var gameObjectCustomize = new Customize((CustomizeData*)actor.Pointer->CustomizeData);
|
||||
if (gameObjectCustomize.Equals(customize))
|
||||
customize.Load(save!.Data.Customize);
|
||||
customize.Load(save!.Customize());
|
||||
|
||||
// Compare game object equip data against draw object equip data for transformations.
|
||||
// Apply each piece of equip that should be applied if they correspond.
|
||||
var gameObjectEquip = new CharacterEquip((CharacterArmor*)actor.Pointer->EquipSlotData);
|
||||
if (gameObjectEquip.Equals(equip))
|
||||
{
|
||||
var saveEquip = save!.Data.Equipment;
|
||||
var saveEquip = save!.Equipment();
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
(_, equip[slot]) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender);
|
||||
Glamourer.Items.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,305 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Saves;
|
||||
|
||||
public partial class Design
|
||||
{
|
||||
public const int CurrentVersion = 1;
|
||||
|
||||
public FileInfo Identifier { get; private set; } = new(string.Empty);
|
||||
public string Name { get; private set; } = "New Design";
|
||||
public string Description { get; private set; } = string.Empty;
|
||||
|
||||
public DateTimeOffset CreationDate { get; private init; } = DateTimeOffset.UtcNow;
|
||||
public DateTimeOffset LastUpdateDate { get; private set; } = DateTimeOffset.UtcNow;
|
||||
|
||||
private DesignFlagsV1 _flags;
|
||||
|
||||
public bool VisorState
|
||||
{
|
||||
get => _flags.HasFlag(DesignFlagsV1.VisorState);
|
||||
private set => _flags = value ? _flags | DesignFlagsV1.VisorState : _flags & ~DesignFlagsV1.VisorState;
|
||||
}
|
||||
|
||||
public bool VisorApply
|
||||
{
|
||||
get => _flags.HasFlag(DesignFlagsV1.VisorApply);
|
||||
private set => _flags = value ? _flags | DesignFlagsV1.VisorApply : _flags & ~DesignFlagsV1.VisorApply;
|
||||
}
|
||||
|
||||
public bool WeaponStateShown
|
||||
{
|
||||
get => _flags.HasFlag(DesignFlagsV1.WeaponStateShown);
|
||||
private set => _flags = value ? _flags | DesignFlagsV1.WeaponStateShown : _flags & ~DesignFlagsV1.WeaponStateShown;
|
||||
}
|
||||
|
||||
public bool WeaponStateApply
|
||||
{
|
||||
get => _flags.HasFlag(DesignFlagsV1.WeaponStateApply);
|
||||
private set => _flags = value ? _flags | DesignFlagsV1.WeaponStateApply : _flags & ~DesignFlagsV1.WeaponStateApply;
|
||||
}
|
||||
|
||||
public bool WetnessState
|
||||
{
|
||||
get => _flags.HasFlag(DesignFlagsV1.WetnessState);
|
||||
private set => _flags = value ? _flags | DesignFlagsV1.WetnessState : _flags & ~DesignFlagsV1.WetnessState;
|
||||
}
|
||||
|
||||
public bool WetnessApply
|
||||
{
|
||||
get => _flags.HasFlag(DesignFlagsV1.WetnessApply);
|
||||
private set => _flags = value ? _flags | DesignFlagsV1.WetnessApply : _flags & ~DesignFlagsV1.WetnessApply;
|
||||
}
|
||||
|
||||
public bool ReadOnly
|
||||
{
|
||||
get => _flags.HasFlag(DesignFlagsV1.ReadOnly);
|
||||
private set => _flags = value ? _flags | DesignFlagsV1.ReadOnly : _flags & ~DesignFlagsV1.ReadOnly;
|
||||
}
|
||||
|
||||
private static bool FromDesignable(string identifier, string name, IDesignable data, [NotNullWhen(true)] out Design? design,
|
||||
bool doWeapons = true, bool doFlags = true, bool doEquipment = true, bool doCustomize = true)
|
||||
{
|
||||
if (!data.Valid)
|
||||
{
|
||||
design = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
design = new Design
|
||||
{
|
||||
Identifier = new FileInfo(identifier),
|
||||
Name = name,
|
||||
Description = string.Empty,
|
||||
CreationDate = DateTimeOffset.UtcNow,
|
||||
LastUpdateDate = DateTimeOffset.UtcNow,
|
||||
ReadOnly = false,
|
||||
VisorApply = doFlags,
|
||||
WeaponStateApply = doFlags,
|
||||
WetnessApply = doFlags,
|
||||
VisorState = data.VisorEnabled,
|
||||
WeaponStateShown = data.WeaponEnabled,
|
||||
WetnessState = data.IsWet,
|
||||
};
|
||||
|
||||
if (doEquipment)
|
||||
{
|
||||
var equipment = data.Equip;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var s = design[slot];
|
||||
var e = equipment[slot];
|
||||
s.StainId = e.Stain;
|
||||
s.ApplyStain = true;
|
||||
s.ItemId = Glamourer.Identifier.Identify(e.Set, e.Variant, slot).FirstOrDefault()?.RowId ?? 0;
|
||||
s.ApplyItem = s.ItemId != 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (doWeapons)
|
||||
{
|
||||
var m = design.MainHand;
|
||||
var d = data.MainHand;
|
||||
|
||||
m.StainId = d.Stain;
|
||||
m.ApplyStain = true;
|
||||
m.ItemId = Glamourer.Identifier.Identify(d.Set, d.Type, d.Variant, EquipSlot.MainHand).FirstOrDefault()?.RowId ?? 0;
|
||||
m.ApplyItem = m.ItemId != 0;
|
||||
|
||||
var o = design.OffHand;
|
||||
d = data.OffHand;
|
||||
o.StainId = d.Stain;
|
||||
o.ApplyStain = true;
|
||||
o.ItemId = Glamourer.Identifier.Identify(d.Set, d.Type, d.Variant, EquipSlot.MainHand).FirstOrDefault()?.RowId ?? 0;
|
||||
o.ApplyItem = o.ItemId != 0;
|
||||
}
|
||||
|
||||
if (doCustomize)
|
||||
{
|
||||
var customize = data.Customize;
|
||||
design.CustomizeFlags = Glamourer.Customization.GetList(customize.Clan, customize.Gender).SettingAvailable
|
||||
| CustomizeFlag.Gender
|
||||
| CustomizeFlag.Race
|
||||
| CustomizeFlag.Clan;
|
||||
foreach (var c in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
if (!design.CustomizeFlags.HasFlag(c.ToFlag()))
|
||||
continue;
|
||||
|
||||
var choice = design[c];
|
||||
choice.Value = customize[c];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var file = File.Open(Identifier.FullName, File.Exists(Identifier.FullName) ? FileMode.Truncate : FileMode.CreateNew);
|
||||
WriteJson(file);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not save design {Identifier.Name}:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteJson(Stream s, Formatting formatting = Formatting.Indented)
|
||||
{
|
||||
var obj = new JObject();
|
||||
obj["Version"] = CurrentVersion;
|
||||
obj[nameof(Name)] = Name;
|
||||
obj[nameof(Description)] = Description;
|
||||
obj[nameof(CreationDate)] = CreationDate.ToUnixTimeSeconds();
|
||||
obj[nameof(LastUpdateDate)] = LastUpdateDate.ToUnixTimeSeconds();
|
||||
obj[nameof(ReadOnly)] = ReadOnly;
|
||||
WriteEquipment(obj);
|
||||
WriteCustomization(obj);
|
||||
WriteFlags(obj);
|
||||
|
||||
using var t = new StreamWriter(s);
|
||||
using var j = new JsonTextWriter(t) { Formatting = formatting };
|
||||
obj.WriteTo(j);
|
||||
}
|
||||
|
||||
private void WriteFlags(JObject obj)
|
||||
{
|
||||
obj[nameof(VisorState)] = VisorState;
|
||||
obj[nameof(VisorApply)] = VisorApply;
|
||||
obj[nameof(WeaponStateShown)] = WeaponStateShown;
|
||||
obj[nameof(WeaponStateApply)] = WeaponStateApply;
|
||||
obj[nameof(WetnessState)] = WetnessState;
|
||||
obj[nameof(WetnessApply)] = WetnessApply;
|
||||
}
|
||||
|
||||
public static bool Load(string fileName, [NotNullWhen(true)] out Design? design)
|
||||
{
|
||||
design = null;
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
Glamourer.Log.Error($"Could not load design {fileName}:\nFile does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var data = File.ReadAllText(fileName);
|
||||
var obj = JObject.Parse(data);
|
||||
|
||||
return obj["Version"]?.Value<int>() switch
|
||||
{
|
||||
null => NoVersion(fileName),
|
||||
1 => LoadV1(fileName, obj, out design),
|
||||
_ => UnknownVersion(fileName, obj["Version"]!.Value<int>()),
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not load design {fileName}:\n{e}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool NoVersion(string fileName)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not load design {fileName}:\nNo version available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool UnknownVersion(string fileName, int version)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not load design {fileName}:\nThe version {version} can not be handled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool LoadV1(string fileName, JObject obj, [NotNullWhen(true)] out Design? design)
|
||||
{
|
||||
design = new Design
|
||||
{
|
||||
Identifier = new FileInfo(fileName),
|
||||
Name = obj[nameof(Name)]?.Value<string>() ?? "New Design",
|
||||
Description = obj[nameof(Description)]?.Value<string>() ?? string.Empty,
|
||||
CreationDate = GetDateTime(obj[nameof(CreationDate)]?.Value<long>()),
|
||||
LastUpdateDate = GetDateTime(obj[nameof(LastUpdateDate)]?.Value<long>()),
|
||||
ReadOnly = obj[nameof(ReadOnly)]?.Value<bool>() ?? false,
|
||||
VisorState = obj[nameof(VisorState)]?.Value<bool>() ?? false,
|
||||
VisorApply = obj[nameof(VisorApply)]?.Value<bool>() ?? false,
|
||||
WeaponStateShown = obj[nameof(WeaponStateShown)]?.Value<bool>() ?? false,
|
||||
WeaponStateApply = obj[nameof(WeaponStateApply)]?.Value<bool>() ?? false,
|
||||
WetnessState = obj[nameof(WetnessState)]?.Value<bool>() ?? false,
|
||||
WetnessApply = obj[nameof(WetnessApply)]?.Value<bool>() ?? false,
|
||||
};
|
||||
|
||||
var equipment = obj[nameof(Equipment)];
|
||||
if (equipment == null)
|
||||
{
|
||||
design.EquipmentFlags = 0;
|
||||
design.StainFlags = 0;
|
||||
design._equipmentData = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var slot in design.Equipment)
|
||||
{
|
||||
var s = equipment[SlotName[slot.Index]];
|
||||
if (s == null)
|
||||
{
|
||||
slot.ItemId = 0;
|
||||
slot.ApplyItem = false;
|
||||
slot.ApplyStain = false;
|
||||
slot.StainId = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
slot.ItemId = s[nameof(Slot.ItemId)]?.Value<uint>() ?? 0u;
|
||||
slot.ApplyItem = obj[nameof(Slot.ApplyItem)]?.Value<bool>() ?? false;
|
||||
slot.StainId = new StainId(s[nameof(Slot.StainId)]?.Value<byte>() ?? 0);
|
||||
slot.ApplyStain = obj[nameof(Slot.ApplyStain)]?.Value<bool>() ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var customize = obj[nameof(Customization)];
|
||||
if (customize == null)
|
||||
{
|
||||
design.CustomizeFlags = 0;
|
||||
design._customizeData = Customize.Default;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var choice in design.Customization)
|
||||
{
|
||||
var c = customize[choice.Index.ToDefaultName()];
|
||||
if (c == null)
|
||||
{
|
||||
choice.Value = Customize.Default.Get(choice.Index);
|
||||
choice.Apply = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
choice.Value = new CustomizeValue(c[nameof(Choice.Value)]?.Value<byte>() ?? Customize.Default.Get(choice.Index).Value);
|
||||
choice.Apply = c[nameof(Choice.Apply)]?.Value<bool>() ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static DateTimeOffset GetDateTime(long? value)
|
||||
=> value == null ? DateTimeOffset.UtcNow : DateTimeOffset.FromUnixTimeSeconds(value.Value);
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Glamourer.Saves;
|
||||
|
||||
[Flags]
|
||||
public enum DesignFlagsV1 : byte
|
||||
{
|
||||
VisorState = 0x01,
|
||||
VisorApply = 0x02,
|
||||
WeaponStateShown = 0x04,
|
||||
WeaponStateApply = 0x08,
|
||||
WetnessState = 0x10,
|
||||
WetnessApply = 0x20,
|
||||
ReadOnly = 0x40,
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Saves;
|
||||
|
||||
public partial class Design
|
||||
{
|
||||
private unsafe struct EquipmentData
|
||||
{
|
||||
public const int NumEquipment = 12;
|
||||
public fixed uint Ids[NumEquipment];
|
||||
public fixed byte Stains[NumEquipment];
|
||||
}
|
||||
|
||||
private EquipmentData _equipmentData = default;
|
||||
public ushort EquipmentFlags { get; private set; }
|
||||
public ushort StainFlags { get; private set; }
|
||||
|
||||
// @formatter:off
|
||||
public Slot Head => new(this, 0);
|
||||
public Slot Body => new(this, 1);
|
||||
public Slot Hands => new(this, 2);
|
||||
public Slot Legs => new(this, 3);
|
||||
public Slot Feet => new(this, 4);
|
||||
public Slot Ears => new(this, 5);
|
||||
public Slot Neck => new(this, 6);
|
||||
public Slot Wrists => new(this, 7);
|
||||
public Slot RFinger => new(this, 8);
|
||||
public Slot LFinger => new(this, 9);
|
||||
public Slot MainHand => new(this, 10);
|
||||
public Slot OffHand => new(this, 11);
|
||||
// @formatter:on
|
||||
|
||||
public Slot this[EquipSlot slot]
|
||||
=> new(this, (int)slot.ToIndex());
|
||||
|
||||
|
||||
public static readonly string[] SlotName =
|
||||
{
|
||||
EquipSlot.Head.ToName(),
|
||||
EquipSlot.Body.ToName(),
|
||||
EquipSlot.Hands.ToName(),
|
||||
EquipSlot.Legs.ToName(),
|
||||
EquipSlot.Feet.ToName(),
|
||||
EquipSlot.Ears.ToName(),
|
||||
EquipSlot.Neck.ToName(),
|
||||
EquipSlot.Wrists.ToName(),
|
||||
EquipSlot.RFinger.ToName(),
|
||||
EquipSlot.LFinger.ToName(),
|
||||
EquipSlot.MainHand.ToName(),
|
||||
EquipSlot.OffHand.ToName(),
|
||||
};
|
||||
|
||||
|
||||
public readonly unsafe struct Slot
|
||||
{
|
||||
private readonly Design _data;
|
||||
public readonly int Index;
|
||||
public readonly ushort Flag;
|
||||
|
||||
public Slot(Design design, int idx)
|
||||
{
|
||||
_data = design;
|
||||
Index = idx;
|
||||
Flag = (ushort)(1 << idx);
|
||||
}
|
||||
|
||||
public uint ItemId
|
||||
{
|
||||
get => _data._equipmentData.Ids[Index];
|
||||
set => _data._equipmentData.Ids[Index] = value;
|
||||
}
|
||||
|
||||
public StainId StainId
|
||||
{
|
||||
get => _data._equipmentData.Stains[Index];
|
||||
set => _data._equipmentData.Stains[Index] = value.Value;
|
||||
}
|
||||
|
||||
public bool ApplyItem
|
||||
{
|
||||
get => (_data.EquipmentFlags & Flag) != 0;
|
||||
set => _data.EquipmentFlags = (ushort)(value ? _data.EquipmentFlags | Flag : _data.EquipmentFlags & ~Flag);
|
||||
}
|
||||
|
||||
public bool ApplyStain
|
||||
{
|
||||
get => (_data.StainFlags & Flag) != 0;
|
||||
set => _data.StainFlags = (ushort)(value ? _data.StainFlags | Flag : _data.StainFlags & ~Flag);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Slot> Equipment
|
||||
=> Enumerable.Range(0, EquipmentData.NumEquipment).Select(i => new Slot(this, i));
|
||||
|
||||
private void WriteEquipment(JObject obj)
|
||||
{
|
||||
var tok = new JObject();
|
||||
foreach (var slot in Equipment)
|
||||
{
|
||||
tok[SlotName] = new JObject
|
||||
{
|
||||
[nameof(Slot.ItemId)] = slot.ItemId,
|
||||
[nameof(Slot.ApplyItem)] = slot.ApplyItem,
|
||||
[nameof(Slot.StainId)] = slot.StainId.Value,
|
||||
[nameof(Slot.ApplyStain)] = slot.ApplyStain,
|
||||
};
|
||||
}
|
||||
|
||||
obj[nameof(Equipment)] = tok;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Glamourer.Customization;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Saves;
|
||||
|
||||
public partial class Design
|
||||
{
|
||||
private CustomizeData _customizeData;
|
||||
public CustomizeFlag CustomizeFlags { get; private set; }
|
||||
|
||||
public Choice this[CustomizeIndex index]
|
||||
=> new(this, index);
|
||||
|
||||
public unsafe Customize Customize
|
||||
=> new((CustomizeData*)Unsafe.AsPointer(ref _customizeData));
|
||||
|
||||
public readonly struct Choice
|
||||
{
|
||||
private readonly Design _data;
|
||||
private readonly CustomizeFlag _flag;
|
||||
private readonly CustomizeIndex _index;
|
||||
|
||||
public Choice(Design design, CustomizeIndex index)
|
||||
{
|
||||
_data = design;
|
||||
_index = index;
|
||||
_flag = index.ToFlag();
|
||||
}
|
||||
|
||||
public CustomizeValue Value
|
||||
{
|
||||
get => _data._customizeData.Get(_index);
|
||||
set => _data._customizeData.Set(_index, value);
|
||||
}
|
||||
|
||||
public bool Apply
|
||||
{
|
||||
get => _data.CustomizeFlags.HasFlag(_flag);
|
||||
set => _data.CustomizeFlags = value ? _data.CustomizeFlags | _flag : _data.CustomizeFlags & ~_flag;
|
||||
}
|
||||
|
||||
public CustomizeIndex Index
|
||||
=> _index;
|
||||
}
|
||||
|
||||
public IEnumerable<Choice> Customization
|
||||
=> Enum.GetValues<CustomizeIndex>().Select(index => new Choice(this, index));
|
||||
|
||||
|
||||
public IEnumerable<Choice> ActiveCustomizations
|
||||
=> Customization.Where(c => c.Apply);
|
||||
|
||||
private void WriteCustomization(JObject obj)
|
||||
{
|
||||
var tok = new JObject();
|
||||
foreach (var choice in Customization)
|
||||
tok[choice.Index.ToString()] = choice.Value.Value;
|
||||
|
||||
obj[nameof(Customization)] = tok;
|
||||
}
|
||||
}
|
||||
64
Glamourer/State/ActiveDesign.StateManager.cs
Normal file
64
Glamourer/State/ActiveDesign.StateManager.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using System.Collections;
|
||||
using Glamourer.Interop;
|
||||
using Penumbra.GameData.Actors;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Glamourer.Designs;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public sealed partial class ActiveDesign
|
||||
{
|
||||
public partial class Manager : IReadOnlyDictionary<ActorIdentifier, ActiveDesign>
|
||||
{
|
||||
private readonly ActorManager _actors;
|
||||
|
||||
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
|
||||
|
||||
public Manager(ActorManager actors)
|
||||
=> _actors = actors;
|
||||
|
||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
|
||||
=> _characterSaves.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public int Count
|
||||
=> _characterSaves.Count;
|
||||
|
||||
public bool ContainsKey(ActorIdentifier key)
|
||||
=> _characterSaves.ContainsKey(key);
|
||||
|
||||
public bool TryGetValue(ActorIdentifier key, [NotNullWhen(true)] out ActiveDesign? value)
|
||||
=> _characterSaves.TryGetValue(key, out value);
|
||||
|
||||
public ActiveDesign this[ActorIdentifier key]
|
||||
=> _characterSaves[key];
|
||||
|
||||
public IEnumerable<ActorIdentifier> Keys
|
||||
=> _characterSaves.Keys;
|
||||
|
||||
public IEnumerable<ActiveDesign> Values
|
||||
=> _characterSaves.Values;
|
||||
|
||||
public void DeleteSave(ActorIdentifier identifier)
|
||||
=> _characterSaves.Remove(identifier);
|
||||
|
||||
public ActiveDesign GetOrCreateSave(Actor actor)
|
||||
{
|
||||
var id = actor.GetIdentifier();
|
||||
if (_characterSaves.TryGetValue(id, out var save))
|
||||
{
|
||||
save.Update(actor);
|
||||
return save;
|
||||
}
|
||||
|
||||
save = new ActiveDesign();
|
||||
save.Update(actor);
|
||||
_characterSaves.Add(id.CreatePermanent(), save);
|
||||
return save;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Glamourer/State/ActiveDesign.cs
Normal file
93
Glamourer/State/ActiveDesign.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public sealed partial class ActiveDesign : DesignBase
|
||||
{
|
||||
private CharacterData _initialData = new();
|
||||
|
||||
private CustomizeFlag _changedCustomize;
|
||||
private CustomizeFlag _fixedCustomize;
|
||||
|
||||
private EquipFlag _changedEquip;
|
||||
private EquipFlag _fixedEquip;
|
||||
|
||||
public bool IsHatVisible { get; private set; } = false;
|
||||
public bool IsWeaponVisible { get; private set; } = false;
|
||||
public bool IsVisorToggled { get; private set; } = false;
|
||||
public bool IsWet { get; private set; } = false;
|
||||
|
||||
private ActiveDesign()
|
||||
{ }
|
||||
|
||||
//public void ApplyToActor(Actor actor)
|
||||
//{
|
||||
// if (!actor)
|
||||
// return;
|
||||
//
|
||||
// void Redraw()
|
||||
// => Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||
//
|
||||
// if (_drawData.ModelId != actor.ModelId)
|
||||
// {
|
||||
// Redraw();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var customize1 = _drawData.Customize;
|
||||
// var customize2 = actor.Customize;
|
||||
// if (RedrawManager.NeedsRedraw(customize1, customize2))
|
||||
// {
|
||||
// Redraw();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// Glamourer.RedrawManager.UpdateCustomize(actor, customize2);
|
||||
// foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
// Glamourer.RedrawManager.ChangeEquip(actor, slot, actor.Equip[slot]);
|
||||
// Glamourer.RedrawManager.LoadWeapon(actor, actor.MainHand, actor.OffHand);
|
||||
// if (actor.IsHuman && actor.DrawObject)
|
||||
// RedrawManager.SetVisor(actor.DrawObject.Pointer, actor.VisorEnabled);
|
||||
//}
|
||||
//
|
||||
public void Update(Actor actor)
|
||||
{
|
||||
if (!actor)
|
||||
return;
|
||||
|
||||
if (!_initialData.Customize.Equals(actor.Customize))
|
||||
{
|
||||
_initialData.Customize.Load(actor.Customize);
|
||||
Customize().Load(actor.Customize);
|
||||
}
|
||||
|
||||
var initialEquip = _initialData.Equipment;
|
||||
var currentEquip = actor.Equip;
|
||||
var equipment = Equipment();
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var current = currentEquip[slot];
|
||||
if (initialEquip[slot] != current)
|
||||
{
|
||||
initialEquip[slot] = current;
|
||||
equipment[slot] = current;
|
||||
}
|
||||
}
|
||||
|
||||
if (_initialData.MainHand != actor.MainHand)
|
||||
{
|
||||
_initialData.MainHand = actor.MainHand;
|
||||
UpdateMainhand(actor.MainHand);
|
||||
}
|
||||
|
||||
if (_initialData.OffHand != actor.OffHand)
|
||||
{
|
||||
_initialData.OffHand = actor.OffHand;
|
||||
UpdateMainhand(actor.OffHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using ImGuiScene;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String.Functions;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public class CharacterSaveConverter : JsonConverter<CharacterSave>
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, CharacterSave? value, JsonSerializer serializer)
|
||||
{
|
||||
var s = value?.ToBase64() ?? string.Empty;
|
||||
serializer.Serialize(writer, s);
|
||||
}
|
||||
|
||||
public override CharacterSave ReadJson(JsonReader reader, Type objectType, CharacterSave? existingValue, bool hasExistingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
var token = JToken.Load(reader);
|
||||
var s = token.ToObject<string>();
|
||||
return CharacterSave.FromString(s!);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct CharacterData
|
||||
{
|
||||
public const byte CurrentVersion = 3;
|
||||
|
||||
public uint ModelId;
|
||||
public CustomizeData CustomizeData;
|
||||
public CharacterWeapon MainHand;
|
||||
public CharacterWeapon OffHand;
|
||||
public CharacterArmor Head;
|
||||
public CharacterArmor Body;
|
||||
public CharacterArmor Hands;
|
||||
public CharacterArmor Legs;
|
||||
public CharacterArmor Feet;
|
||||
public CharacterArmor Ears;
|
||||
public CharacterArmor Neck;
|
||||
public CharacterArmor Wrist;
|
||||
public CharacterArmor RFinger;
|
||||
public CharacterArmor LFinger;
|
||||
|
||||
public unsafe Customize Customize
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (CustomizeData* ptr = &CustomizeData)
|
||||
{
|
||||
return new Customize(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe CharacterEquip Equipment
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (CharacterArmor* ptr = &Head)
|
||||
{
|
||||
return new CharacterEquip(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly CharacterData Default
|
||||
= new()
|
||||
{
|
||||
ModelId = 0,
|
||||
CustomizeData = Customize.Default,
|
||||
MainHand = CharacterWeapon.Empty,
|
||||
OffHand = CharacterWeapon.Empty,
|
||||
Head = CharacterArmor.Empty,
|
||||
Body = CharacterArmor.Empty,
|
||||
Hands = CharacterArmor.Empty,
|
||||
Legs = CharacterArmor.Empty,
|
||||
Feet = CharacterArmor.Empty,
|
||||
Ears = CharacterArmor.Empty,
|
||||
Neck = CharacterArmor.Empty,
|
||||
Wrist = CharacterArmor.Empty,
|
||||
RFinger = CharacterArmor.Empty,
|
||||
LFinger = CharacterArmor.Empty,
|
||||
};
|
||||
|
||||
public unsafe CharacterData Clone()
|
||||
{
|
||||
var data = new CharacterData();
|
||||
fixed (void* ptr = &this)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(&data, ptr, sizeof(CharacterData));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
public void Load(IDesignable designable)
|
||||
{
|
||||
ModelId = designable.ModelId;
|
||||
Customize.Load(designable.Customize);
|
||||
Equipment.Load(designable.Equip);
|
||||
MainHand = designable.MainHand;
|
||||
OffHand = designable.OffHand;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(CharacterSaveConverter))]
|
||||
public class CharacterSave
|
||||
{
|
||||
private CharacterData _data = CharacterData.Default;
|
||||
|
||||
public CharacterSave()
|
||||
{ }
|
||||
|
||||
public CharacterSave(Actor actor)
|
||||
{
|
||||
Load(actor);
|
||||
}
|
||||
|
||||
public void Load<T>(T actor) where T : IDesignable
|
||||
{
|
||||
_data.Load(actor);
|
||||
}
|
||||
|
||||
public string ToBase64()
|
||||
=> string.Empty;
|
||||
|
||||
public Customize Customize
|
||||
=> _data.Customize;
|
||||
|
||||
public CharacterEquip Equipment
|
||||
=> _data.Equipment;
|
||||
|
||||
public ref CharacterWeapon MainHand
|
||||
=> ref _data.MainHand;
|
||||
|
||||
public ref CharacterWeapon OffHand
|
||||
=> ref _data.OffHand;
|
||||
|
||||
public static CharacterSave FromString(string data)
|
||||
=> new();
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
using Glamourer.Interop;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public unsafe class CurrentDesign : IDesign
|
||||
{
|
||||
public ref CharacterData Data
|
||||
=> ref _drawData;
|
||||
|
||||
private CharacterData _drawData;
|
||||
private CharacterData _initialData;
|
||||
|
||||
public CurrentDesign(Actor actor)
|
||||
{
|
||||
_initialData = new CharacterData();
|
||||
if (!actor)
|
||||
return;
|
||||
|
||||
_initialData.Load(actor);
|
||||
var drawObject = actor.DrawObject;
|
||||
if (drawObject.Valid)
|
||||
_drawData.Load(drawObject);
|
||||
else
|
||||
_drawData = _initialData.Clone();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
=> _drawData = _initialData;
|
||||
|
||||
public void ApplyToActor(Actor actor)
|
||||
{
|
||||
if (!actor)
|
||||
return;
|
||||
|
||||
void Redraw()
|
||||
=> Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||
|
||||
if (_drawData.ModelId != actor.ModelId)
|
||||
{
|
||||
Redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
var customize1 = _drawData.Customize;
|
||||
var customize2 = actor.Customize;
|
||||
if (RedrawManager.NeedsRedraw(customize1, customize2))
|
||||
{
|
||||
Redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor, customize2);
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
Glamourer.RedrawManager.ChangeEquip(actor, slot, actor.Equip[slot]);
|
||||
Glamourer.RedrawManager.LoadWeapon(actor, actor.MainHand, actor.OffHand);
|
||||
if (actor.IsHuman && actor.DrawObject)
|
||||
RedrawManager.SetVisor(actor.DrawObject.Pointer, actor.VisorEnabled);
|
||||
}
|
||||
|
||||
public void Update(Actor actor)
|
||||
{
|
||||
if (!actor)
|
||||
return;
|
||||
|
||||
if (!_initialData.Customize.Equals(actor.Customize))
|
||||
{
|
||||
_initialData.Customize.Load(actor.Customize);
|
||||
_drawData.Customize.Load(actor.Customize);
|
||||
}
|
||||
|
||||
var initialEquip = _initialData.Equipment;
|
||||
var currentEquip = actor.Equip;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var current = currentEquip[slot];
|
||||
if (initialEquip[slot] != current)
|
||||
{
|
||||
initialEquip[slot] = current;
|
||||
_drawData.Equipment[slot] = current;
|
||||
}
|
||||
}
|
||||
|
||||
if (_initialData.MainHand != actor.MainHand)
|
||||
{
|
||||
_initialData.MainHand = actor.MainHand;
|
||||
_drawData.MainHand = actor.MainHand;
|
||||
}
|
||||
|
||||
if (_initialData.OffHand != actor.OffHand)
|
||||
{
|
||||
_initialData.OffHand = actor.OffHand;
|
||||
_drawData.OffHand = actor.OffHand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,11 @@ using Penumbra.GameData.Actors;
|
|||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<ActorIdentifier, CurrentDesign>>
|
||||
public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<ActorIdentifier, ActiveDesign>>
|
||||
{
|
||||
private readonly Dictionary<ActorIdentifier, CurrentDesign> _characterSaves = new();
|
||||
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
|
||||
|
||||
public IEnumerator<KeyValuePair<ActorIdentifier, CurrentDesign>> GetEnumerator()
|
||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
|
||||
=> _characterSaves.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
|
|
@ -19,7 +19,7 @@ public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<ActorIdenti
|
|||
public int Count
|
||||
=> _characterSaves.Count;
|
||||
|
||||
public CurrentDesign GetOrCreateSave(Actor actor)
|
||||
public ActiveDesign GetOrCreateSave(Actor actor)
|
||||
{
|
||||
var id = actor.GetIdentifier();
|
||||
if (_characterSaves.TryGetValue(id, out var save))
|
||||
|
|
@ -28,7 +28,7 @@ public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<ActorIdenti
|
|||
return save;
|
||||
}
|
||||
|
||||
save = new CurrentDesign(actor);
|
||||
save = new ActiveDesign(actor);
|
||||
_characterSaves.Add(id.CreatePermanent(), save);
|
||||
return save;
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<ActorIdenti
|
|||
public void DeleteSave(ActorIdentifier identifier)
|
||||
=> _characterSaves.Remove(identifier);
|
||||
|
||||
public bool TryGetDesign(ActorIdentifier identifier, [NotNullWhen(true)] out CurrentDesign? save)
|
||||
public bool TryGetDesign(ActorIdentifier identifier, [NotNullWhen(true)] out ActiveDesign? save)
|
||||
=> _characterSaves.TryGetValue(identifier, out save);
|
||||
|
||||
//public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
||||
|
|
@ -6,7 +7,7 @@ namespace Glamourer.State;
|
|||
|
||||
public class FixedDesigns
|
||||
{
|
||||
public bool TryGetDesign(ActorIdentifier actor, [NotNullWhen(true)] out CharacterSave? save)
|
||||
public bool TryGetDesign(ActorIdentifier actor, [NotNullWhen(true)] out Design? save)
|
||||
{
|
||||
save = null;
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Glamourer.Interop;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,6 @@ public static unsafe class CustomizeExtensions
|
|||
return;
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
(_, equip[slot]) = Glamourer.RestrictedGear.ResolveRestricted(equip[slot], slot, race, gender);
|
||||
(_, equip[slot]) = Glamourer.Items.RestrictedGear.ResolveRestricted(equip[slot], slot, race, gender);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
161
Glamourer/Util/ItemManager.cs
Normal file
161
Glamourer/Util/ItemManager.cs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Util;
|
||||
|
||||
public class ItemManager : IDisposable
|
||||
{
|
||||
public const string Nothing = "Nothing";
|
||||
public const string SmallClothesNpc = "Smallclothes (NPC)";
|
||||
public const ushort SmallClothesNpcModel = 9903;
|
||||
|
||||
public readonly IObjectIdentifier Identifier;
|
||||
public readonly ExcelSheet<Item> ItemSheet;
|
||||
public readonly StainData Stains;
|
||||
public readonly ItemData Items;
|
||||
public readonly RestrictedGear RestrictedGear;
|
||||
|
||||
public ItemManager(DalamudPluginInterface pi, DataManager gameData)
|
||||
{
|
||||
ItemSheet = gameData.GetExcelSheet<Item>()!;
|
||||
Identifier = Penumbra.GameData.GameData.GetIdentifier(pi, gameData, gameData.Language);
|
||||
Stains = new StainData(pi, gameData, gameData.Language);
|
||||
Items = new ItemData(pi, gameData, gameData.Language);
|
||||
RestrictedGear = new RestrictedGear(pi, gameData.Language, gameData);
|
||||
DefaultSword = ItemSheet.GetRow(1601)!; // Weathered Shortsword
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stains.Dispose();
|
||||
Items.Dispose();
|
||||
Identifier.Dispose();
|
||||
RestrictedGear.Dispose();
|
||||
}
|
||||
|
||||
public readonly Item DefaultSword;
|
||||
|
||||
public static uint NothingId(EquipSlot slot)
|
||||
=> uint.MaxValue - 128 - (uint)slot.ToSlot();
|
||||
|
||||
public static uint SmallclothesId(EquipSlot slot)
|
||||
=> uint.MaxValue - 256 - (uint)slot.ToSlot();
|
||||
|
||||
public static uint NothingId(FullEquipType type)
|
||||
=> uint.MaxValue - 384 - (uint)type;
|
||||
|
||||
public (bool Valid, SetId Id, byte Variant, string ItemName) Resolve(EquipSlot slot, uint itemId, Item? item = null)
|
||||
{
|
||||
slot = slot.ToSlot();
|
||||
if (itemId == NothingId(slot))
|
||||
return (true, 0, 0, Nothing);
|
||||
if (itemId == SmallclothesId(slot))
|
||||
return (true, SmallClothesNpcModel, 1, SmallClothesNpc);
|
||||
|
||||
if (item == null || item.RowId != itemId)
|
||||
item = ItemSheet.GetRow(itemId);
|
||||
|
||||
if (item == null)
|
||||
return (false, 0, 0, string.Intern($"Unknown #{itemId}"));
|
||||
if (item.ToEquipType().ToSlot() != slot)
|
||||
return (false, 0, 0, string.Intern($"Invalid ({item.Name.ToDalamudString()})"));
|
||||
|
||||
return (true, (SetId)item.ModelMain, (byte)(item.ModelMain >> 16), string.Intern(item.Name.ToDalamudString().TextValue));
|
||||
}
|
||||
|
||||
public (bool Valid, SetId Id, WeaponType Weapon, byte Variant, string ItemName, FullEquipType Type) Resolve(uint itemId, Item? item = null)
|
||||
{
|
||||
if (item == null || item.RowId != itemId)
|
||||
item = ItemSheet.GetRow(itemId);
|
||||
|
||||
if (item == null)
|
||||
return (false, 0, 0, 0, string.Intern($"Unknown #{itemId}"), FullEquipType.Unknown);
|
||||
|
||||
var type = item.ToEquipType();
|
||||
if (type.ToSlot() != EquipSlot.MainHand)
|
||||
return (false, 0, 0, 0, string.Intern($"Invalid ({item.Name.ToDalamudString()})"), type);
|
||||
|
||||
return (true, (SetId)item.ModelMain, (WeaponType)(item.ModelMain >> 16), (byte)(item.ModelMain >> 32),
|
||||
string.Intern(item.Name.ToDalamudString().TextValue), type);
|
||||
}
|
||||
|
||||
public (bool Valid, SetId Id, WeaponType Weapon, byte Variant, string ItemName, FullEquipType Type) Resolve(uint itemId,
|
||||
FullEquipType mainType, Item? item = null)
|
||||
{
|
||||
var offType = mainType.Offhand();
|
||||
if (itemId == NothingId(offType))
|
||||
return (true, 0, 0, 0, Nothing, offType);
|
||||
|
||||
if (item == null || item.RowId != itemId)
|
||||
item = ItemSheet.GetRow(itemId);
|
||||
|
||||
if (item == null)
|
||||
return (false, 0, 0, 0, string.Intern($"Unknown #{itemId}"), FullEquipType.Unknown);
|
||||
|
||||
|
||||
var type = item.ToEquipType();
|
||||
if (offType != type)
|
||||
return (false, 0, 0, 0, string.Intern($"Invalid ({item.Name.ToDalamudString()})"), type);
|
||||
|
||||
var (m, w, v) = offType.ToSlot() == EquipSlot.MainHand
|
||||
? ((SetId)item.ModelSub, (WeaponType)(item.ModelSub >> 16), (byte)(item.ModelSub >> 32))
|
||||
: ((SetId)item.ModelMain, (WeaponType)(item.ModelMain >> 16), (byte)(item.ModelMain >> 32));
|
||||
|
||||
return (true, m, w, v, string.Intern(item.Name.ToDalamudString().TextValue), type);
|
||||
}
|
||||
|
||||
public (bool Valid, uint ItemId, string ItemName) Identify(EquipSlot slot, SetId id, byte variant)
|
||||
{
|
||||
slot = slot.ToSlot();
|
||||
if (!slot.IsEquipmentPiece())
|
||||
return (false, 0, string.Intern($"Unknown ({id.Value}-{variant})"));
|
||||
|
||||
switch (id.Value)
|
||||
{
|
||||
case 0: return (true, NothingId(slot), Nothing);
|
||||
case SmallClothesNpcModel: return (true, SmallclothesId(slot), SmallClothesNpc);
|
||||
default:
|
||||
var item = Identifier.Identify(id, variant, slot).FirstOrDefault();
|
||||
return item == null
|
||||
? (false, 0, string.Intern($"Unknown ({id.Value}-{variant})"))
|
||||
: (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue));
|
||||
}
|
||||
}
|
||||
|
||||
public (bool Valid, uint ItemId, string ItemName, FullEquipType Type) Identify(EquipSlot slot, SetId id, WeaponType type, byte variant,
|
||||
FullEquipType mainhandType = FullEquipType.Unknown)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
{
|
||||
var item = Identifier.Identify(id, type, variant, slot).FirstOrDefault();
|
||||
return item != null
|
||||
? (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue), item.ToEquipType())
|
||||
: (false, 0, string.Intern($"Unknown ({id.Value}-{type.Value}-{variant})"), mainhandType);
|
||||
}
|
||||
case EquipSlot.OffHand:
|
||||
{
|
||||
var weaponType = mainhandType.Offhand();
|
||||
if (id.Value == 0)
|
||||
return (true, NothingId(weaponType), Nothing, weaponType);
|
||||
|
||||
var item = Identifier.Identify(id, type, variant, slot).FirstOrDefault();
|
||||
return item != null
|
||||
? (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue), item.ToEquipType())
|
||||
: (false, 0, string.Intern($"Unknown ({id.Value}-{type.Value}-{variant})"),
|
||||
weaponType);
|
||||
}
|
||||
default: return (false, 0, string.Intern($"Unknown ({id.Value}-{type.Value}-{variant})"), FullEquipType.Unknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue