mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
.
This commit is contained in:
parent
5c003d8cd4
commit
f8e9cc8988
43 changed files with 2215 additions and 668 deletions
|
|
@ -33,9 +33,6 @@ public class CustomizationManager : ICustomizationManager
|
||||||
public ImGuiScene.TextureWrap GetIcon(uint iconId)
|
public ImGuiScene.TextureWrap GetIcon(uint iconId)
|
||||||
=> _options!.GetIcon(iconId);
|
=> _options!.GetIcon(iconId);
|
||||||
|
|
||||||
public void RemoveIcon(uint iconId)
|
|
||||||
=> _options!.RemoveIcon(iconId);
|
|
||||||
|
|
||||||
public string GetName(CustomName name)
|
public string GetName(CustomName name)
|
||||||
=> _options!.GetName(name);
|
=> _options!.GetName(name);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,6 @@ public partial class CustomizationOptions
|
||||||
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
||||||
=> _icons.LoadIcon(id);
|
=> _icons.LoadIcon(id);
|
||||||
|
|
||||||
internal void RemoveIcon(uint id)
|
|
||||||
=> _icons.RemoveIcon(id);
|
|
||||||
|
|
||||||
private readonly IconStorage _icons;
|
private readonly IconStorage _icons;
|
||||||
|
|
||||||
private static readonly int ListSize = Clans.Length * Genders.Length;
|
private static readonly int ListSize = Clans.Length * Genders.Length;
|
||||||
|
|
|
||||||
|
|
@ -249,7 +249,7 @@ public class CustomizationSet
|
||||||
_ => index switch
|
_ => index switch
|
||||||
{
|
{
|
||||||
CustomizeIndex.Face => Faces.Count,
|
CustomizeIndex.Face => Faces.Count,
|
||||||
CustomizeIndex.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face.Value].Count : 0,
|
CustomizeIndex.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face.Value].Count : HairStyles.Count,
|
||||||
CustomizeIndex.SkinColor => SkinColors.Count,
|
CustomizeIndex.SkinColor => SkinColors.Count,
|
||||||
CustomizeIndex.EyeColorRight => EyeColors.Count,
|
CustomizeIndex.EyeColorRight => EyeColors.Count,
|
||||||
CustomizeIndex.HairColor => HairColors.Count,
|
CustomizeIndex.HairColor => HairColors.Count,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ public enum CustomizeFlag : ulong
|
||||||
public static class CustomizeFlagExtensions
|
public static class CustomizeFlagExtensions
|
||||||
{
|
{
|
||||||
public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul);
|
public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul);
|
||||||
|
public const CustomizeFlag AllRelevant = All & ~CustomizeFlag.BodyType & ~CustomizeFlag.Race;
|
||||||
public const CustomizeFlag RedrawRequired = CustomizeFlag.Race | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.Face | CustomizeFlag.BodyType;
|
public const CustomizeFlag RedrawRequired = CustomizeFlag.Race | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.Face | CustomizeFlag.BodyType;
|
||||||
|
|
||||||
public static bool RequiresRedraw(this CustomizeFlag flags)
|
public static bool RequiresRedraw(this CustomizeFlag flags)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,5 @@ public interface ICustomizationManager
|
||||||
public CustomizationSet GetList(SubRace race, Gender gender);
|
public CustomizationSet GetList(SubRace race, Gender gender);
|
||||||
|
|
||||||
public ImGuiScene.TextureWrap GetIcon(uint iconId);
|
public ImGuiScene.TextureWrap GetIcon(uint iconId);
|
||||||
public void RemoveIcon(uint iconId);
|
|
||||||
public string GetName(CustomName name);
|
public string GetName(CustomName name);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
|
|
@ -45,24 +44,24 @@ public partial class GlamourerIpc
|
||||||
|
|
||||||
|
|
||||||
public void ApplyAll(string base64, string characterName)
|
public void ApplyAll(string base64, string characterName)
|
||||||
=> ApplyDesign(CreateTemporaryFromBase64(base64, true, true), FindActors(characterName));
|
=> ApplyDesign(_designConverter.FromBase64(base64, true, true), FindActors(characterName));
|
||||||
|
|
||||||
public void ApplyAllToCharacter(string base64, Character? character)
|
public void ApplyAllToCharacter(string base64, Character? character)
|
||||||
=> ApplyDesign(CreateTemporaryFromBase64(base64, true, true), FindActors(character));
|
=> ApplyDesign(_designConverter.FromBase64(base64, true, true), FindActors(character));
|
||||||
|
|
||||||
public void ApplyOnlyEquipment(string base64, string characterName)
|
public void ApplyOnlyEquipment(string base64, string characterName)
|
||||||
=> ApplyDesign(CreateTemporaryFromBase64(base64, false, true), FindActors(characterName));
|
=> ApplyDesign(_designConverter.FromBase64(base64, false, true), FindActors(characterName));
|
||||||
|
|
||||||
public void ApplyOnlyEquipmentToCharacter(string base64, Character? character)
|
public void ApplyOnlyEquipmentToCharacter(string base64, Character? character)
|
||||||
=> ApplyDesign(CreateTemporaryFromBase64(base64, false, true), FindActors(character));
|
=> ApplyDesign(_designConverter.FromBase64(base64, false, true), FindActors(character));
|
||||||
|
|
||||||
public void ApplyOnlyCustomization(string base64, string characterName)
|
public void ApplyOnlyCustomization(string base64, string characterName)
|
||||||
=> ApplyDesign(CreateTemporaryFromBase64(base64, true, false), FindActors(characterName));
|
=> ApplyDesign(_designConverter.FromBase64(base64, true, false), FindActors(characterName));
|
||||||
|
|
||||||
public void ApplyOnlyCustomizationToCharacter(string base64, Character? character)
|
public void ApplyOnlyCustomizationToCharacter(string base64, Character? character)
|
||||||
=> ApplyDesign(CreateTemporaryFromBase64(base64, true, false), FindActors(character));
|
=> ApplyDesign(_designConverter.FromBase64(base64, true, false), FindActors(character));
|
||||||
|
|
||||||
private void ApplyDesign(Design? design, IEnumerable<ActorIdentifier> actors)
|
private void ApplyDesign(DesignBase? design, IEnumerable<ActorIdentifier> actors)
|
||||||
{
|
{
|
||||||
if (design == null)
|
if (design == null)
|
||||||
return;
|
return;
|
||||||
|
|
@ -80,33 +79,4 @@ public partial class GlamourerIpc
|
||||||
_stateManager.ApplyDesign(design, state);
|
_stateManager.ApplyDesign(design, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Design? CreateTemporaryFromBase64(string base64, bool customize, bool equip)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var ret = new Design(_items);
|
|
||||||
ret.MigrateBase64(_items, base64);
|
|
||||||
if (!customize)
|
|
||||||
{
|
|
||||||
ret.ApplyCustomize = 0;
|
|
||||||
ret.SetApplyWetness(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!equip)
|
|
||||||
{
|
|
||||||
ret.ApplyEquip = 0;
|
|
||||||
ret.SetApplyHatVisible(false);
|
|
||||||
ret.SetApplyWeaponVisible(false);
|
|
||||||
ret.SetApplyVisorToggle(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Glamourer.Log.Error($"[IPC] Could not parse base64 string [{base64}]:\n{ex}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using Dalamud.Plugin;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Glamourer.Designs;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
|
|
@ -17,17 +18,21 @@ public partial class GlamourerIpc : IDisposable
|
||||||
public const int CurrentApiVersionMajor = 0;
|
public const int CurrentApiVersionMajor = 0;
|
||||||
public const int CurrentApiVersionMinor = 1;
|
public const int CurrentApiVersionMinor = 1;
|
||||||
|
|
||||||
private readonly StateManager _stateManager;
|
private readonly StateManager _stateManager;
|
||||||
private readonly ObjectManager _objects;
|
private readonly ObjectManager _objects;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorService _actors;
|
||||||
private readonly ItemManager _items;
|
private readonly ItemManager _items;
|
||||||
|
private readonly DesignConverter _designConverter;
|
||||||
|
|
||||||
public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorService actors, ItemManager items)
|
|
||||||
|
public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorService actors, ItemManager items,
|
||||||
|
DesignConverter designConverter)
|
||||||
{
|
{
|
||||||
_stateManager = stateManager;
|
_stateManager = stateManager;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
_items = items;
|
_items = items;
|
||||||
|
_designConverter = designConverter;
|
||||||
_apiVersionProvider = new FuncProvider<int>(pi, LabelApiVersion, ApiVersion);
|
_apiVersionProvider = new FuncProvider<int>(pi, LabelApiVersion, ApiVersion);
|
||||||
_apiVersionsProvider = new FuncProvider<(int Major, int Minor)>(pi, LabelApiVersions, ApiVersions);
|
_apiVersionsProvider = new FuncProvider<(int Major, int Minor)>(pi, LabelApiVersions, ApiVersions);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public class AutoDesign
|
||||||
All = Armor | Accessories | Customizations | Weapons | Stains,
|
All = Armor | Accessories | Customizations | Weapons | Stains,
|
||||||
}
|
}
|
||||||
|
|
||||||
public Design Design;
|
public Design Design = null!;
|
||||||
public JobGroup Jobs;
|
public JobGroup Jobs;
|
||||||
public Type ApplicationType;
|
public Type ApplicationType;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,12 @@ public class AutoDesignApplier : IDisposable
|
||||||
private readonly CustomizationService _customizations;
|
private readonly CustomizationService _customizations;
|
||||||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||||
private readonly ItemUnlockManager _itemUnlocks;
|
private readonly ItemUnlockManager _itemUnlocks;
|
||||||
|
private readonly AutomationChanged _event;
|
||||||
|
private readonly ObjectManager _objects;
|
||||||
|
|
||||||
public AutoDesignApplier(Configuration config, AutoDesignManager manager, CodeService code, StateManager state, JobService jobs,
|
public AutoDesignApplier(Configuration config, AutoDesignManager manager, CodeService code, StateManager state, JobService jobs,
|
||||||
CustomizationService customizations, ActorService actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks)
|
CustomizationService customizations, ActorService actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks,
|
||||||
|
AutomationChanged @event, ObjectManager objects)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
|
|
@ -38,14 +41,59 @@ public class AutoDesignApplier : IDisposable
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
_itemUnlocks = itemUnlocks;
|
_itemUnlocks = itemUnlocks;
|
||||||
_customizeUnlocks = customizeUnlocks;
|
_customizeUnlocks = customizeUnlocks;
|
||||||
|
_event = @event;
|
||||||
|
_objects = objects;
|
||||||
_jobs.JobChanged += OnJobChange;
|
_jobs.JobChanged += OnJobChange;
|
||||||
|
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_event.Unsubscribe(OnAutomationChange);
|
||||||
_jobs.JobChanged -= OnJobChange;
|
_jobs.JobChanged -= OnJobChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? _)
|
||||||
|
{
|
||||||
|
if (!_config.EnableAutoDesigns || set is not { Enabled: true })
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case AutomationChanged.Type.ChangeIdentifier:
|
||||||
|
case AutomationChanged.Type.ToggleSet:
|
||||||
|
case AutomationChanged.Type.AddedDesign:
|
||||||
|
case AutomationChanged.Type.DeletedDesign:
|
||||||
|
case AutomationChanged.Type.MovedDesign:
|
||||||
|
case AutomationChanged.Type.ChangedDesign:
|
||||||
|
case AutomationChanged.Type.ChangedConditions:
|
||||||
|
_objects.Update();
|
||||||
|
if (_objects.TryGetValue(set.Identifier, out var data))
|
||||||
|
{
|
||||||
|
if (_state.GetOrCreate(set.Identifier, data.Objects[0], out var state))
|
||||||
|
{
|
||||||
|
Reduce(data.Objects[0], state, set, false);
|
||||||
|
foreach (var actor in data.Objects)
|
||||||
|
_state.ReapplyState(actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_objects.TryGetValueAllWorld(set.Identifier, out data))
|
||||||
|
{
|
||||||
|
foreach (var actor in data.Objects)
|
||||||
|
{
|
||||||
|
var id = actor.GetIdentifier(_actors.AwaitedService);
|
||||||
|
if (_state.GetOrCreate(id, actor, out var state))
|
||||||
|
{
|
||||||
|
Reduce(actor, state, set, false);
|
||||||
|
_state.ReapplyState(actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnJobChange(Actor actor, Job _)
|
private void OnJobChange(Actor actor, Job _)
|
||||||
{
|
{
|
||||||
if (!_config.EnableAutoDesigns || !actor.Identifier(_actors.AwaitedService, out var id))
|
if (!_config.EnableAutoDesigns || !actor.Identifier(_actors.AwaitedService, out var id))
|
||||||
|
|
@ -242,28 +290,28 @@ public class AutoDesignApplier : IDisposable
|
||||||
{
|
{
|
||||||
if (applyHat && (totalMetaFlags & 0x01) == 0)
|
if (applyHat && (totalMetaFlags & 0x01) == 0)
|
||||||
{
|
{
|
||||||
if (!respectManual || state[ActorState.MetaFlag.HatState] is not StateChanged.Source.Manual)
|
if (!respectManual || state[ActorState.MetaIndex.HatState] is not StateChanged.Source.Manual)
|
||||||
_state.ChangeHatState(state, design.IsHatVisible(), StateChanged.Source.Fixed);
|
_state.ChangeHatState(state, design.IsHatVisible(), StateChanged.Source.Fixed);
|
||||||
totalMetaFlags |= 0x01;
|
totalMetaFlags |= 0x01;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applyVisor && (totalMetaFlags & 0x02) == 0)
|
if (applyVisor && (totalMetaFlags & 0x02) == 0)
|
||||||
{
|
{
|
||||||
if (!respectManual || state[ActorState.MetaFlag.VisorState] is not StateChanged.Source.Manual)
|
if (!respectManual || state[ActorState.MetaIndex.VisorState] is not StateChanged.Source.Manual)
|
||||||
_state.ChangeVisorState(state, design.IsVisorToggled(), StateChanged.Source.Fixed);
|
_state.ChangeVisorState(state, design.IsVisorToggled(), StateChanged.Source.Fixed);
|
||||||
totalMetaFlags |= 0x02;
|
totalMetaFlags |= 0x02;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applyWeapon && (totalMetaFlags & 0x04) == 0)
|
if (applyWeapon && (totalMetaFlags & 0x04) == 0)
|
||||||
{
|
{
|
||||||
if (!respectManual || state[ActorState.MetaFlag.WeaponState] is not StateChanged.Source.Manual)
|
if (!respectManual || state[ActorState.MetaIndex.WeaponState] is not StateChanged.Source.Manual)
|
||||||
_state.ChangeWeaponState(state, design.IsWeaponVisible(), StateChanged.Source.Fixed);
|
_state.ChangeWeaponState(state, design.IsWeaponVisible(), StateChanged.Source.Fixed);
|
||||||
totalMetaFlags |= 0x04;
|
totalMetaFlags |= 0x04;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applyWet && (totalMetaFlags & 0x08) == 0)
|
if (applyWet && (totalMetaFlags & 0x08) == 0)
|
||||||
{
|
{
|
||||||
if (!respectManual || state[ActorState.MetaFlag.Wetness] is not StateChanged.Source.Manual)
|
if (!respectManual || state[ActorState.MetaIndex.Wetness] is not StateChanged.Source.Manual)
|
||||||
_state.ChangeWetness(state, design.IsWet(), StateChanged.Source.Fixed);
|
_state.ChangeWetness(state, design.IsWet(), StateChanged.Source.Fixed);
|
||||||
totalMetaFlags |= 0x08;
|
totalMetaFlags |= 0x08;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
|
||||||
private readonly DesignManager _designs;
|
private readonly DesignManager _designs;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorService _actors;
|
||||||
private readonly AutomationChanged _event;
|
private readonly AutomationChanged _event;
|
||||||
private readonly ItemUnlockManager _unlockManager;
|
private readonly ItemUnlockManager _unlockManager;
|
||||||
|
|
||||||
private readonly List<AutoDesignSet> _data = new();
|
private readonly List<AutoDesignSet> _data = new();
|
||||||
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = new();
|
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = new();
|
||||||
|
|
|
||||||
|
|
@ -1,279 +1,95 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.Structs;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
public class Design : ISavable
|
public sealed class Design : DesignBase, ISavable
|
||||||
{
|
{
|
||||||
#region Data
|
#region Data
|
||||||
|
|
||||||
internal Design(ItemManager items)
|
internal Design(ItemManager items)
|
||||||
|
: base(items)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
internal Design(DesignBase other)
|
||||||
|
: base(other)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
internal Design(Design other)
|
||||||
|
: base(other)
|
||||||
{
|
{
|
||||||
DesignData.SetDefaultEquipment(items);
|
Tags = Tags.ToArray();
|
||||||
|
Description = Description;
|
||||||
|
AssociatedMods = new SortedList<Mod, ModSettings>(other.AssociatedMods);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
public const int FileVersion = 1;
|
public new const int FileVersion = 1;
|
||||||
|
|
||||||
public Guid Identifier { get; internal init; }
|
public Guid Identifier { get; internal init; }
|
||||||
public DateTimeOffset CreationDate { get; internal init; }
|
public DateTimeOffset CreationDate { get; internal init; }
|
||||||
public DateTimeOffset LastEdit { get; internal set; }
|
public DateTimeOffset LastEdit { get; internal set; }
|
||||||
public LowerString Name { get; internal set; } = LowerString.Empty;
|
public LowerString Name { get; internal set; } = LowerString.Empty;
|
||||||
public string Description { get; internal set; } = string.Empty;
|
public string Description { get; internal set; } = string.Empty;
|
||||||
public string[] Tags { get; internal set; } = Array.Empty<string>();
|
public string[] Tags { get; internal set; } = Array.Empty<string>();
|
||||||
public int Index { get; internal set; }
|
public int Index { get; internal set; }
|
||||||
|
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = new();
|
||||||
internal DesignData DesignData;
|
|
||||||
|
|
||||||
public string Incognito
|
public string Incognito
|
||||||
=> Identifier.ToString()[..8];
|
=> Identifier.ToString()[..8];
|
||||||
|
|
||||||
/// <summary> Unconditionally apply a design to a designdata. </summary>
|
|
||||||
/// <returns>Whether a redraw is required for the changes to take effect.</returns>
|
|
||||||
public (bool, CustomizeFlag, EquipFlag) ApplyDesign(ref DesignData data)
|
|
||||||
{
|
|
||||||
var modelChanged = data.ModelId != DesignData.ModelId;
|
|
||||||
data.ModelId = DesignData.ModelId;
|
|
||||||
|
|
||||||
CustomizeFlag customizeFlags = 0;
|
|
||||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
|
||||||
{
|
|
||||||
if (!DoApplyCustomize(index))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (data.Customize.Set(index, DesignData.Customize[index]))
|
|
||||||
customizeFlags |= index.ToFlag();
|
|
||||||
}
|
|
||||||
|
|
||||||
EquipFlag equipFlags = 0;
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
|
|
||||||
{
|
|
||||||
if (DoApplyEquip(slot))
|
|
||||||
if (data.SetItem(slot, DesignData.Item(slot)))
|
|
||||||
equipFlags |= slot.ToFlag();
|
|
||||||
|
|
||||||
if (DoApplyStain(slot))
|
|
||||||
if (data.SetStain(slot, DesignData.Stain(slot)))
|
|
||||||
equipFlags |= slot.ToStainFlag();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DoApplyHatVisible())
|
|
||||||
data.SetHatVisible(DesignData.IsHatVisible());
|
|
||||||
|
|
||||||
if (DoApplyVisorToggle())
|
|
||||||
data.SetVisor(DesignData.IsVisorToggled());
|
|
||||||
|
|
||||||
if (DoApplyWeaponVisible())
|
|
||||||
data.SetWeaponVisible(DesignData.IsWeaponVisible());
|
|
||||||
|
|
||||||
if (DoApplyWetness())
|
|
||||||
data.SetIsWet(DesignData.IsWet());
|
|
||||||
return (modelChanged, customizeFlags, equipFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Application Data
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
private enum DesignFlags : byte
|
|
||||||
{
|
|
||||||
ApplyHatVisible = 0x01,
|
|
||||||
ApplyVisorState = 0x02,
|
|
||||||
ApplyWeaponVisible = 0x04,
|
|
||||||
ApplyWetness = 0x08,
|
|
||||||
WriteProtected = 0x10,
|
|
||||||
}
|
|
||||||
|
|
||||||
internal CustomizeFlag ApplyCustomize = CustomizeFlagExtensions.All;
|
|
||||||
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
|
||||||
private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible;
|
|
||||||
|
|
||||||
public bool DoApplyHatVisible()
|
|
||||||
=> _designFlags.HasFlag(DesignFlags.ApplyHatVisible);
|
|
||||||
|
|
||||||
public bool DoApplyVisorToggle()
|
|
||||||
=> _designFlags.HasFlag(DesignFlags.ApplyVisorState);
|
|
||||||
|
|
||||||
public bool DoApplyWeaponVisible()
|
|
||||||
=> _designFlags.HasFlag(DesignFlags.ApplyWeaponVisible);
|
|
||||||
|
|
||||||
public bool DoApplyWetness()
|
|
||||||
=> _designFlags.HasFlag(DesignFlags.ApplyWetness);
|
|
||||||
|
|
||||||
public bool WriteProtected()
|
|
||||||
=> _designFlags.HasFlag(DesignFlags.WriteProtected);
|
|
||||||
|
|
||||||
public bool SetApplyHatVisible(bool value)
|
|
||||||
{
|
|
||||||
var newFlag = value ? _designFlags | DesignFlags.ApplyHatVisible : _designFlags & ~DesignFlags.ApplyHatVisible;
|
|
||||||
if (newFlag == _designFlags)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_designFlags = newFlag;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SetApplyVisorToggle(bool value)
|
|
||||||
{
|
|
||||||
var newFlag = value ? _designFlags | DesignFlags.ApplyVisorState : _designFlags & ~DesignFlags.ApplyVisorState;
|
|
||||||
if (newFlag == _designFlags)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_designFlags = newFlag;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SetApplyWeaponVisible(bool value)
|
|
||||||
{
|
|
||||||
var newFlag = value ? _designFlags | DesignFlags.ApplyWeaponVisible : _designFlags & ~DesignFlags.ApplyWeaponVisible;
|
|
||||||
if (newFlag == _designFlags)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_designFlags = newFlag;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SetApplyWetness(bool value)
|
|
||||||
{
|
|
||||||
var newFlag = value ? _designFlags | DesignFlags.ApplyWetness : _designFlags & ~DesignFlags.ApplyWetness;
|
|
||||||
if (newFlag == _designFlags)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_designFlags = newFlag;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SetWriteProtected(bool value)
|
|
||||||
{
|
|
||||||
var newFlag = value ? _designFlags | DesignFlags.WriteProtected : _designFlags & ~DesignFlags.WriteProtected;
|
|
||||||
if (newFlag == _designFlags)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_designFlags = newFlag;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
internal bool SetApplyEquip(EquipSlot slot, bool value)
|
|
||||||
{
|
|
||||||
var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag();
|
|
||||||
if (newValue == ApplyEquip)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
ApplyEquip = newValue;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool SetApplyStain(EquipSlot slot, bool value)
|
|
||||||
{
|
|
||||||
var newValue = value ? ApplyEquip | slot.ToStainFlag() : ApplyEquip & ~slot.ToStainFlag();
|
|
||||||
if (newValue == ApplyEquip)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
ApplyEquip = newValue;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool SetApplyCustomize(CustomizeIndex idx, bool value)
|
|
||||||
{
|
|
||||||
var newValue = value ? ApplyCustomize | idx.ToFlag() : ApplyCustomize & ~idx.ToFlag();
|
|
||||||
if (newValue == ApplyCustomize)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
ApplyCustomize = newValue;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Serialization
|
#region Serialization
|
||||||
|
|
||||||
private JObject JsonSerialize()
|
public new JObject JsonSerialize()
|
||||||
{
|
|
||||||
var ret = new JObject
|
|
||||||
{
|
|
||||||
["FileVersion"] = FileVersion,
|
|
||||||
["Identifier"] = Identifier,
|
|
||||||
["CreationDate"] = CreationDate,
|
|
||||||
["LastEdit"] = LastEdit,
|
|
||||||
["Name"] = Name.Text,
|
|
||||||
["Description"] = Description,
|
|
||||||
["Tags"] = JArray.FromObject(Tags),
|
|
||||||
["WriteProtected"] = WriteProtected(),
|
|
||||||
["Equipment"] = SerializeEquipment(),
|
|
||||||
["Customize"] = SerializeCustomize(),
|
|
||||||
};
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JObject SerializeEquipment()
|
|
||||||
{
|
|
||||||
static JObject Serialize(uint itemId, StainId stain, bool apply, bool applyStain)
|
|
||||||
=> new()
|
|
||||||
{
|
|
||||||
["ItemId"] = itemId,
|
|
||||||
["Stain"] = stain.Value,
|
|
||||||
["Apply"] = apply,
|
|
||||||
["ApplyStain"] = applyStain,
|
|
||||||
};
|
|
||||||
|
|
||||||
var ret = new JObject();
|
|
||||||
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
|
||||||
{
|
|
||||||
var item = DesignData.Item(slot);
|
|
||||||
var stain = DesignData.Stain(slot);
|
|
||||||
ret[slot.ToString()] = Serialize(item.Id, stain, DoApplyEquip(slot), DoApplyStain(slot));
|
|
||||||
}
|
|
||||||
|
|
||||||
ret["Hat"] = new QuadBool(DesignData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply");
|
|
||||||
ret["Visor"] = new QuadBool(DesignData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply");
|
|
||||||
ret["Weapon"] = new QuadBool(DesignData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply");
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JObject SerializeCustomize()
|
|
||||||
{
|
{
|
||||||
var ret = new JObject()
|
var ret = new JObject()
|
||||||
{
|
|
||||||
["ModelId"] = DesignData.ModelId,
|
|
||||||
};
|
|
||||||
var customize = DesignData.Customize;
|
|
||||||
foreach (var idx in Enum.GetValues<CustomizeIndex>())
|
|
||||||
{
|
|
||||||
ret[idx.ToString()] = new JObject()
|
|
||||||
{
|
{
|
||||||
["Value"] = customize[idx].Value,
|
["FileVersion"] = FileVersion,
|
||||||
["Apply"] = DoApplyCustomize(idx),
|
["Identifier"] = Identifier,
|
||||||
};
|
["CreationDate"] = CreationDate,
|
||||||
}
|
["LastEdit"] = LastEdit,
|
||||||
|
["Name"] = Name.Text,
|
||||||
|
["Description"] = Description,
|
||||||
|
["Tags"] = JArray.FromObject(Tags),
|
||||||
|
["WriteProtected"] = WriteProtected(),
|
||||||
|
["Equipment"] = SerializeEquipment(),
|
||||||
|
["Customize"] = SerializeCustomize(),
|
||||||
|
["Mods"] = SerializeMods(),
|
||||||
|
}
|
||||||
|
;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
ret["Wetness"] = new JObject()
|
private JArray SerializeMods()
|
||||||
|
{
|
||||||
|
var ret = new JArray();
|
||||||
|
foreach (var (mod, settings) in AssociatedMods)
|
||||||
{
|
{
|
||||||
["Value"] = DesignData.IsWet(),
|
var obj = new JObject()
|
||||||
["Apply"] = DoApplyWetness(),
|
{
|
||||||
};
|
["Name"] = mod.Name,
|
||||||
|
["Directory"] = mod.DirectoryName,
|
||||||
|
["Enabled"] = settings.Enabled,
|
||||||
|
};
|
||||||
|
if (settings.Enabled)
|
||||||
|
{
|
||||||
|
obj["Priority"] = settings.Priority;
|
||||||
|
obj["Settings"] = JObject.FromObject(settings.Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -287,8 +103,8 @@ public class Design : ISavable
|
||||||
var version = json["FileVersion"]?.ToObject<int>() ?? 0;
|
var version = json["FileVersion"]?.ToObject<int>() ?? 0;
|
||||||
return version switch
|
return version switch
|
||||||
{
|
{
|
||||||
1 => LoadDesignV1(customizations, items, json),
|
FileVersion => LoadDesignV1(customizations, items, json),
|
||||||
_ => throw new Exception("The design to be loaded has no valid Version."),
|
_ => throw new Exception("The design to be loaded has no valid Version."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -314,128 +130,37 @@ public class Design : ISavable
|
||||||
if (design.LastEdit < creationDate)
|
if (design.LastEdit < creationDate)
|
||||||
design.LastEdit = creationDate;
|
design.LastEdit = creationDate;
|
||||||
|
|
||||||
LoadEquip(items, json["Equipment"], design);
|
LoadEquip(items, json["Equipment"], design, design.Name);
|
||||||
LoadCustomize(customizations, json["Customize"], design);
|
LoadCustomize(customizations, json["Customize"], design, design.Name);
|
||||||
|
LoadMods(json["Mods"], design);
|
||||||
return design;
|
return design;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LoadEquip(ItemManager items, JToken? equip, Design design)
|
private static void LoadMods(JToken? mods, Design design)
|
||||||
{
|
{
|
||||||
if (equip == null)
|
if (mods is not JArray array)
|
||||||
{
|
|
||||||
design.DesignData.SetDefaultEquipment(items);
|
|
||||||
Glamourer.Chat.NotificationMessage("The loaded design does not contain any equipment data, reset to default.", "Warning",
|
|
||||||
NotificationType.Warning);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
static (uint, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item)
|
foreach (var tok in array)
|
||||||
{
|
{
|
||||||
var id = item?["ItemId"]?.ToObject<uint>() ?? ItemManager.NothingId(slot);
|
var name = tok["Name"]?.ToObject<string>();
|
||||||
var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0);
|
var directory = tok["Directory"]?.ToObject<string>();
|
||||||
var apply = item?["Apply"]?.ToObject<bool>() ?? false;
|
var enabled = tok["Enabled"]?.ToObject<bool>();
|
||||||
var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false;
|
if (name == null || directory == null || enabled == null)
|
||||||
return (id, stain, apply, applyStain);
|
{
|
||||||
|
Glamourer.Chat.NotificationMessage("The loaded design contains an invalid mod, skipped.", "Warning", NotificationType.Warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var settingsDict = tok["Settings"]?.ToObject<Dictionary<string, string[]>>() ?? new Dictionary<string, string[]>();
|
||||||
|
var settings = new SortedList<string, IList<string>>(settingsDict.Count);
|
||||||
|
foreach (var (key, value) in settingsDict)
|
||||||
|
settings.Add(key, value);
|
||||||
|
var priority = tok["Priority"]?.ToObject<int>() ?? 0;
|
||||||
|
if (!design.AssociatedMods.TryAdd(new Mod(name, directory), new ModSettings(settings, priority, enabled.Value)))
|
||||||
|
Glamourer.Chat.NotificationMessage("The loaded design contains a mod more than once, skipped.", "Warning",
|
||||||
|
NotificationType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintWarning(string msg)
|
|
||||||
{
|
|
||||||
if (msg.Length > 0)
|
|
||||||
Glamourer.Chat.NotificationMessage($"{msg} ({design.Name})", "Warning", NotificationType.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
|
||||||
{
|
|
||||||
var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]);
|
|
||||||
|
|
||||||
PrintWarning(items.ValidateItem(slot, id, out var item));
|
|
||||||
PrintWarning(items.ValidateStain(stain, out stain));
|
|
||||||
design.DesignData.SetItem(slot, item);
|
|
||||||
design.DesignData.SetStain(slot, stain);
|
|
||||||
design.SetApplyEquip(slot, apply);
|
|
||||||
design.SetApplyStain(slot, applyStain);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var (id, stain, apply, applyStain) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]);
|
|
||||||
if (id == ItemManager.NothingId(EquipSlot.MainHand))
|
|
||||||
id = items.DefaultSword.Id;
|
|
||||||
var (idOff, stainOff, applyOff, applyStainOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]);
|
|
||||||
if (id == ItemManager.NothingId(EquipSlot.OffHand))
|
|
||||||
id = ItemManager.NothingId(FullEquipType.Shield);
|
|
||||||
|
|
||||||
PrintWarning(items.ValidateWeapons(id, idOff, out var main, out var off));
|
|
||||||
PrintWarning(items.ValidateStain(stain, out stain));
|
|
||||||
PrintWarning(items.ValidateStain(stainOff, out stainOff));
|
|
||||||
design.DesignData.SetItem(EquipSlot.MainHand, main);
|
|
||||||
design.DesignData.SetItem(EquipSlot.OffHand, off);
|
|
||||||
design.DesignData.SetStain(EquipSlot.MainHand, stain);
|
|
||||||
design.DesignData.SetStain(EquipSlot.OffHand, stainOff);
|
|
||||||
design.SetApplyEquip(EquipSlot.MainHand, apply);
|
|
||||||
design.SetApplyEquip(EquipSlot.OffHand, applyOff);
|
|
||||||
design.SetApplyStain(EquipSlot.MainHand, applyStain);
|
|
||||||
design.SetApplyStain(EquipSlot.OffHand, applyStainOff);
|
|
||||||
}
|
|
||||||
var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse);
|
|
||||||
design.SetApplyHatVisible(metaValue.Enabled);
|
|
||||||
design.DesignData.SetHatVisible(metaValue.ForcedValue);
|
|
||||||
|
|
||||||
metaValue = QuadBool.FromJObject(equip["Weapon"], "Show", "Apply", QuadBool.NullFalse);
|
|
||||||
design.SetApplyWeaponVisible(metaValue.Enabled);
|
|
||||||
design.DesignData.SetWeaponVisible(metaValue.ForcedValue);
|
|
||||||
|
|
||||||
metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse);
|
|
||||||
design.SetApplyVisorToggle(metaValue.Enabled);
|
|
||||||
design.DesignData.SetVisor(metaValue.ForcedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void LoadCustomize(CustomizationService customizations, JToken? json, Design design)
|
|
||||||
{
|
|
||||||
if (json == null)
|
|
||||||
{
|
|
||||||
design.DesignData.ModelId = 0;
|
|
||||||
design.DesignData.Customize = Customize.Default;
|
|
||||||
Glamourer.Chat.NotificationMessage("The loaded design does not contain any customization data, reset to default.", "Warning",
|
|
||||||
NotificationType.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintWarning(string msg)
|
|
||||||
{
|
|
||||||
if (msg.Length > 0)
|
|
||||||
Glamourer.Chat.NotificationMessage($"{msg} ({design.Name})", "Warning", NotificationType.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
design.DesignData.ModelId = json["ModelId"]?.ToObject<uint>() ?? 0;
|
|
||||||
PrintWarning(customizations.ValidateModelId(design.DesignData.ModelId, out design.DesignData.ModelId));
|
|
||||||
|
|
||||||
var race = (Race)(json[CustomizeIndex.Race.ToString()]?["Value"]?.ToObject<byte>() ?? 0);
|
|
||||||
var clan = (SubRace)(json[CustomizeIndex.Clan.ToString()]?["Value"]?.ToObject<byte>() ?? 0);
|
|
||||||
PrintWarning(customizations.ValidateClan(clan, race, out race, out clan));
|
|
||||||
var gender = (Gender)((json[CustomizeIndex.Gender.ToString()]?["Value"]?.ToObject<byte>() ?? 0) + 1);
|
|
||||||
PrintWarning(customizations.ValidateGender(race, gender, out gender));
|
|
||||||
design.DesignData.Customize.Race = race;
|
|
||||||
design.DesignData.Customize.Clan = clan;
|
|
||||||
design.DesignData.Customize.Gender = gender;
|
|
||||||
design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject<bool>() ?? false);
|
|
||||||
design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject<bool>() ?? false);
|
|
||||||
design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject<bool>() ?? false);
|
|
||||||
|
|
||||||
var set = customizations.AwaitedService.GetList(clan, gender);
|
|
||||||
|
|
||||||
foreach (var idx in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable))
|
|
||||||
{
|
|
||||||
var tok = json[idx.ToString()];
|
|
||||||
var data = (CustomizeValue)(tok?["Value"]?.ToObject<byte>() ?? 0);
|
|
||||||
PrintWarning(CustomizationService.ValidateCustomizeValue(set, design.DesignData.Customize.Face, idx, data, out data));
|
|
||||||
var apply = tok?["Apply"]?.ToObject<bool>() ?? false;
|
|
||||||
design.DesignData.Customize[idx] = data;
|
|
||||||
design.SetApplyCustomize(idx, apply);
|
|
||||||
}
|
|
||||||
|
|
||||||
var wetness = QuadBool.FromJObject(json["Wetness"], "Value", "Apply", QuadBool.NullFalse);
|
|
||||||
design.DesignData.SetIsWet(wetness.ForcedValue);
|
|
||||||
design.SetApplyWetness(wetness.Enabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -459,39 +184,4 @@ public class Design : ISavable
|
||||||
=> Path.GetFileNameWithoutExtension(fileName);
|
=> Path.GetFileNameWithoutExtension(fileName);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public void MigrateBase64(ItemManager items, string base64)
|
|
||||||
{
|
|
||||||
DesignData = DesignBase64Migration.MigrateBase64(items, base64, out var equipFlags, out var customizeFlags, out var writeProtected,
|
|
||||||
out var applyHat, out var applyVisor, out var applyWeapon);
|
|
||||||
ApplyEquip = equipFlags;
|
|
||||||
ApplyCustomize = customizeFlags;
|
|
||||||
SetWriteProtected(writeProtected);
|
|
||||||
SetApplyHatVisible(applyHat);
|
|
||||||
SetApplyVisorToggle(applyVisor);
|
|
||||||
SetApplyWeaponVisible(applyWeapon);
|
|
||||||
SetApplyWetness(DesignData.IsWet());
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
//public static Design CreateTemporaryFromBase64(ItemManager items, string base64, bool customize, bool equip)
|
|
||||||
//{
|
|
||||||
// var ret = new Design(items);
|
|
||||||
// ret.MigrateBase64(items, base64);
|
|
||||||
// if (!customize)
|
|
||||||
// ret._applyCustomize = 0;
|
|
||||||
// if (!equip)
|
|
||||||
// ret._applyEquip = 0;
|
|
||||||
// ret.Wetness = ret.Wetness.SetEnabled(customize);
|
|
||||||
// ret.Visor = ret.Visor.SetEnabled(equip);
|
|
||||||
// ret.Hat = ret.Hat.SetEnabled(equip);
|
|
||||||
// ret.Weapon = ret.Weapon.SetEnabled(equip);
|
|
||||||
// return ret;
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Outdated.
|
|
||||||
//public string CreateOldBase64()
|
|
||||||
// => DesignBase64Migration.CreateOldBase64(in ModelData, _applyEquip, _applyCustomize, Wetness == QuadBool.True, Hat.ForcedValue,
|
|
||||||
// Hat.Enabled,
|
|
||||||
// Visor.ForcedValue, Visor.Enabled, Weapon.ForcedValue, Weapon.Enabled, WriteProtected, 1f);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
375
Glamourer/Designs/DesignBase.cs
Normal file
375
Glamourer/Designs/DesignBase.cs
Normal file
|
|
@ -0,0 +1,375 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using Glamourer.Structs;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
|
public class DesignBase
|
||||||
|
{
|
||||||
|
public const int FileVersion = 1;
|
||||||
|
|
||||||
|
internal DesignBase(ItemManager items)
|
||||||
|
{
|
||||||
|
DesignData.SetDefaultEquipment(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DesignBase(DesignBase clone)
|
||||||
|
{
|
||||||
|
DesignData = clone.DesignData;
|
||||||
|
ApplyCustomize = clone.ApplyCustomize & CustomizeFlagExtensions.All;
|
||||||
|
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
|
||||||
|
_designFlags = clone._designFlags & (DesignFlags) 0x0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DesignData DesignData = new();
|
||||||
|
|
||||||
|
#region Application Data
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum DesignFlags : byte
|
||||||
|
{
|
||||||
|
ApplyHatVisible = 0x01,
|
||||||
|
ApplyVisorState = 0x02,
|
||||||
|
ApplyWeaponVisible = 0x04,
|
||||||
|
ApplyWetness = 0x08,
|
||||||
|
WriteProtected = 0x10,
|
||||||
|
}
|
||||||
|
|
||||||
|
internal CustomizeFlag ApplyCustomize = CustomizeFlagExtensions.All;
|
||||||
|
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
||||||
|
private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible;
|
||||||
|
|
||||||
|
public bool DoApplyHatVisible()
|
||||||
|
=> _designFlags.HasFlag(DesignFlags.ApplyHatVisible);
|
||||||
|
|
||||||
|
public bool DoApplyVisorToggle()
|
||||||
|
=> _designFlags.HasFlag(DesignFlags.ApplyVisorState);
|
||||||
|
|
||||||
|
public bool DoApplyWeaponVisible()
|
||||||
|
=> _designFlags.HasFlag(DesignFlags.ApplyWeaponVisible);
|
||||||
|
|
||||||
|
public bool DoApplyWetness()
|
||||||
|
=> _designFlags.HasFlag(DesignFlags.ApplyWetness);
|
||||||
|
|
||||||
|
public bool WriteProtected()
|
||||||
|
=> _designFlags.HasFlag(DesignFlags.WriteProtected);
|
||||||
|
|
||||||
|
public bool SetApplyHatVisible(bool value)
|
||||||
|
{
|
||||||
|
var newFlag = value ? _designFlags | DesignFlags.ApplyHatVisible : _designFlags & ~DesignFlags.ApplyHatVisible;
|
||||||
|
if (newFlag == _designFlags)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_designFlags = newFlag;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetApplyVisorToggle(bool value)
|
||||||
|
{
|
||||||
|
var newFlag = value ? _designFlags | DesignFlags.ApplyVisorState : _designFlags & ~DesignFlags.ApplyVisorState;
|
||||||
|
if (newFlag == _designFlags)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_designFlags = newFlag;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetApplyWeaponVisible(bool value)
|
||||||
|
{
|
||||||
|
var newFlag = value ? _designFlags | DesignFlags.ApplyWeaponVisible : _designFlags & ~DesignFlags.ApplyWeaponVisible;
|
||||||
|
if (newFlag == _designFlags)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_designFlags = newFlag;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetApplyWetness(bool value)
|
||||||
|
{
|
||||||
|
var newFlag = value ? _designFlags | DesignFlags.ApplyWetness : _designFlags & ~DesignFlags.ApplyWetness;
|
||||||
|
if (newFlag == _designFlags)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_designFlags = newFlag;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetWriteProtected(bool value)
|
||||||
|
{
|
||||||
|
var newFlag = value ? _designFlags | DesignFlags.WriteProtected : _designFlags & ~DesignFlags.WriteProtected;
|
||||||
|
if (newFlag == _designFlags)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_designFlags = newFlag;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DoApplyEquip(EquipSlot slot)
|
||||||
|
=> ApplyEquip.HasFlag(slot.ToFlag());
|
||||||
|
|
||||||
|
public bool DoApplyStain(EquipSlot slot)
|
||||||
|
=> ApplyEquip.HasFlag(slot.ToStainFlag());
|
||||||
|
|
||||||
|
public bool DoApplyCustomize(CustomizeIndex idx)
|
||||||
|
=> idx is not CustomizeIndex.Race and not CustomizeIndex.BodyType && ApplyCustomize.HasFlag(idx.ToFlag());
|
||||||
|
|
||||||
|
internal bool SetApplyEquip(EquipSlot slot, bool value)
|
||||||
|
{
|
||||||
|
var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag();
|
||||||
|
if (newValue == ApplyEquip)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ApplyEquip = newValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool SetApplyStain(EquipSlot slot, bool value)
|
||||||
|
{
|
||||||
|
var newValue = value ? ApplyEquip | slot.ToStainFlag() : ApplyEquip & ~slot.ToStainFlag();
|
||||||
|
if (newValue == ApplyEquip)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ApplyEquip = newValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool SetApplyCustomize(CustomizeIndex idx, bool value)
|
||||||
|
{
|
||||||
|
var newValue = value ? ApplyCustomize | idx.ToFlag() : ApplyCustomize & ~idx.ToFlag();
|
||||||
|
if (newValue == ApplyCustomize)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ApplyCustomize = newValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Serialization
|
||||||
|
|
||||||
|
public JObject JsonSerialize()
|
||||||
|
{
|
||||||
|
var ret = new JObject
|
||||||
|
{
|
||||||
|
["FileVersion"] = FileVersion,
|
||||||
|
["Equipment"] = SerializeEquipment(),
|
||||||
|
["Customize"] = SerializeCustomize(),
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JObject SerializeEquipment()
|
||||||
|
{
|
||||||
|
static JObject Serialize(uint itemId, StainId stain, bool apply, bool applyStain)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
["ItemId"] = itemId,
|
||||||
|
["Stain"] = stain.Value,
|
||||||
|
["Apply"] = apply,
|
||||||
|
["ApplyStain"] = applyStain,
|
||||||
|
};
|
||||||
|
|
||||||
|
var ret = new JObject();
|
||||||
|
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
||||||
|
{
|
||||||
|
var item = DesignData.Item(slot);
|
||||||
|
var stain = DesignData.Stain(slot);
|
||||||
|
ret[slot.ToString()] = Serialize(item.Id, stain, DoApplyEquip(slot), DoApplyStain(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
ret["Hat"] = new QuadBool(DesignData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply");
|
||||||
|
ret["Visor"] = new QuadBool(DesignData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply");
|
||||||
|
ret["Weapon"] = new QuadBool(DesignData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JObject SerializeCustomize()
|
||||||
|
{
|
||||||
|
var ret = new JObject()
|
||||||
|
{
|
||||||
|
["ModelId"] = DesignData.ModelId,
|
||||||
|
};
|
||||||
|
var customize = DesignData.Customize;
|
||||||
|
foreach (var idx in Enum.GetValues<CustomizeIndex>())
|
||||||
|
{
|
||||||
|
ret[idx.ToString()] = new JObject()
|
||||||
|
{
|
||||||
|
["Value"] = customize[idx].Value,
|
||||||
|
["Apply"] = DoApplyCustomize(idx),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ret["Wetness"] = new JObject()
|
||||||
|
{
|
||||||
|
["Value"] = DesignData.IsWet(),
|
||||||
|
["Apply"] = DoApplyWetness(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Deserialization
|
||||||
|
|
||||||
|
public static DesignBase LoadDesignBase(CustomizationService customizations, ItemManager items, JObject json)
|
||||||
|
{
|
||||||
|
var version = json["FileVersion"]?.ToObject<int>() ?? 0;
|
||||||
|
return version switch
|
||||||
|
{
|
||||||
|
FileVersion => LoadDesignV1Base(customizations, items, json),
|
||||||
|
_ => throw new Exception("The design to be loaded has no valid Version."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DesignBase LoadDesignV1Base(CustomizationService customizations, ItemManager items, JObject json)
|
||||||
|
{
|
||||||
|
var ret = new DesignBase(items);
|
||||||
|
LoadEquip(items, json["Equipment"], ret, "Temporary Design");
|
||||||
|
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void LoadEquip(ItemManager items, JToken? equip, DesignBase design, string name)
|
||||||
|
{
|
||||||
|
if (equip == null)
|
||||||
|
{
|
||||||
|
design.DesignData.SetDefaultEquipment(items);
|
||||||
|
Glamourer.Chat.NotificationMessage("The loaded design does not contain any equipment data, reset to default.", "Warning",
|
||||||
|
NotificationType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintWarning(string msg)
|
||||||
|
{
|
||||||
|
if (msg.Length > 0)
|
||||||
|
Glamourer.Chat.NotificationMessage($"{msg} ({name})", "Warning", NotificationType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
{
|
||||||
|
var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]);
|
||||||
|
|
||||||
|
PrintWarning(items.ValidateItem(slot, id, out var item));
|
||||||
|
PrintWarning(items.ValidateStain(stain, out stain));
|
||||||
|
design.DesignData.SetItem(slot, item);
|
||||||
|
design.DesignData.SetStain(slot, stain);
|
||||||
|
design.SetApplyEquip(slot, apply);
|
||||||
|
design.SetApplyStain(slot, applyStain);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var (id, stain, apply, applyStain) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]);
|
||||||
|
if (id == ItemManager.NothingId(EquipSlot.MainHand))
|
||||||
|
id = items.DefaultSword.Id;
|
||||||
|
var (idOff, stainOff, applyOff, applyStainOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]);
|
||||||
|
if (id == ItemManager.NothingId(EquipSlot.OffHand))
|
||||||
|
id = ItemManager.NothingId(FullEquipType.Shield);
|
||||||
|
|
||||||
|
PrintWarning(items.ValidateWeapons(id, idOff, out var main, out var off));
|
||||||
|
PrintWarning(items.ValidateStain(stain, out stain));
|
||||||
|
PrintWarning(items.ValidateStain(stainOff, out stainOff));
|
||||||
|
design.DesignData.SetItem(EquipSlot.MainHand, main);
|
||||||
|
design.DesignData.SetItem(EquipSlot.OffHand, off);
|
||||||
|
design.DesignData.SetStain(EquipSlot.MainHand, stain);
|
||||||
|
design.DesignData.SetStain(EquipSlot.OffHand, stainOff);
|
||||||
|
design.SetApplyEquip(EquipSlot.MainHand, apply);
|
||||||
|
design.SetApplyEquip(EquipSlot.OffHand, applyOff);
|
||||||
|
design.SetApplyStain(EquipSlot.MainHand, applyStain);
|
||||||
|
design.SetApplyStain(EquipSlot.OffHand, applyStainOff);
|
||||||
|
}
|
||||||
|
var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse);
|
||||||
|
design.SetApplyHatVisible(metaValue.Enabled);
|
||||||
|
design.DesignData.SetHatVisible(metaValue.ForcedValue);
|
||||||
|
|
||||||
|
metaValue = QuadBool.FromJObject(equip["Weapon"], "Show", "Apply", QuadBool.NullFalse);
|
||||||
|
design.SetApplyWeaponVisible(metaValue.Enabled);
|
||||||
|
design.DesignData.SetWeaponVisible(metaValue.ForcedValue);
|
||||||
|
|
||||||
|
metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse);
|
||||||
|
design.SetApplyVisorToggle(metaValue.Enabled);
|
||||||
|
design.DesignData.SetVisor(metaValue.ForcedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void LoadCustomize(CustomizationService customizations, JToken? json, DesignBase design, string name)
|
||||||
|
{
|
||||||
|
if (json == null)
|
||||||
|
{
|
||||||
|
design.DesignData.ModelId = 0;
|
||||||
|
design.DesignData.Customize = Customize.Default;
|
||||||
|
Glamourer.Chat.NotificationMessage("The loaded design does not contain any customization data, reset to default.", "Warning",
|
||||||
|
NotificationType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintWarning(string msg)
|
||||||
|
{
|
||||||
|
if (msg.Length > 0)
|
||||||
|
Glamourer.Chat.NotificationMessage($"{msg} ({name})", "Warning", NotificationType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
design.DesignData.ModelId = json["ModelId"]?.ToObject<uint>() ?? 0;
|
||||||
|
PrintWarning(customizations.ValidateModelId(design.DesignData.ModelId, out design.DesignData.ModelId));
|
||||||
|
|
||||||
|
var race = (Race)(json[CustomizeIndex.Race.ToString()]?["Value"]?.ToObject<byte>() ?? 0);
|
||||||
|
var clan = (SubRace)(json[CustomizeIndex.Clan.ToString()]?["Value"]?.ToObject<byte>() ?? 0);
|
||||||
|
PrintWarning(customizations.ValidateClan(clan, race, out race, out clan));
|
||||||
|
var gender = (Gender)((json[CustomizeIndex.Gender.ToString()]?["Value"]?.ToObject<byte>() ?? 0) + 1);
|
||||||
|
PrintWarning(customizations.ValidateGender(race, gender, out gender));
|
||||||
|
design.DesignData.Customize.Race = race;
|
||||||
|
design.DesignData.Customize.Clan = clan;
|
||||||
|
design.DesignData.Customize.Gender = gender;
|
||||||
|
design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject<bool>() ?? false);
|
||||||
|
design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject<bool>() ?? false);
|
||||||
|
design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject<bool>() ?? false);
|
||||||
|
|
||||||
|
var set = customizations.AwaitedService.GetList(clan, gender);
|
||||||
|
|
||||||
|
foreach (var idx in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable))
|
||||||
|
{
|
||||||
|
var tok = json[idx.ToString()];
|
||||||
|
var data = (CustomizeValue)(tok?["Value"]?.ToObject<byte>() ?? 0);
|
||||||
|
PrintWarning(CustomizationService.ValidateCustomizeValue(set, design.DesignData.Customize.Face, idx, data, out data));
|
||||||
|
var apply = tok?["Apply"]?.ToObject<bool>() ?? false;
|
||||||
|
design.DesignData.Customize[idx] = data;
|
||||||
|
design.SetApplyCustomize(idx, apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
var wetness = QuadBool.FromJObject(json["Wetness"], "Value", "Apply", QuadBool.NullFalse);
|
||||||
|
design.DesignData.SetIsWet(wetness.ForcedValue);
|
||||||
|
design.SetApplyWetness(wetness.Enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MigrateBase64(ItemManager items, string base64)
|
||||||
|
{
|
||||||
|
DesignData = DesignBase64Migration.MigrateBase64(items, base64, out var equipFlags, out var customizeFlags, out var writeProtected,
|
||||||
|
out var applyHat, out var applyVisor, out var applyWeapon);
|
||||||
|
ApplyEquip = equipFlags;
|
||||||
|
ApplyCustomize = customizeFlags;
|
||||||
|
SetWriteProtected(writeProtected);
|
||||||
|
SetApplyHatVisible(applyHat);
|
||||||
|
SetApplyVisorToggle(applyVisor);
|
||||||
|
SetApplyWeaponVisible(applyWeapon);
|
||||||
|
SetApplyWetness(DesignData.IsWet());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
121
Glamourer/Designs/DesignConverter.cs
Normal file
121
Glamourer/Designs/DesignConverter.cs
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using Glamourer.State;
|
||||||
|
using Glamourer.Structs;
|
||||||
|
using Glamourer.Utility;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
|
public class DesignConverter
|
||||||
|
{
|
||||||
|
public const byte Version = 3;
|
||||||
|
|
||||||
|
private readonly ItemManager _items;
|
||||||
|
private readonly DesignManager _designs;
|
||||||
|
private readonly CustomizationService _customize;
|
||||||
|
|
||||||
|
public DesignConverter(ItemManager items, DesignManager designs, CustomizationService customize)
|
||||||
|
{
|
||||||
|
_items = items;
|
||||||
|
_designs = designs;
|
||||||
|
_customize = customize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JObject ShareJObject(DesignBase design)
|
||||||
|
=> design.JsonSerialize();
|
||||||
|
|
||||||
|
public JObject ShareJObject(Design design)
|
||||||
|
=> design.JsonSerialize();
|
||||||
|
|
||||||
|
public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags)
|
||||||
|
{
|
||||||
|
var design = Convert(state, equipFlags, customizeFlags);
|
||||||
|
return ShareJObject(design);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ShareBase64(DesignBase design)
|
||||||
|
=> ShareBase64(ShareJObject(design));
|
||||||
|
|
||||||
|
public string ShareBase64(ActorState state)
|
||||||
|
=> ShareBase64(ShareJObject(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All));
|
||||||
|
|
||||||
|
public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags)
|
||||||
|
{
|
||||||
|
var design = _designs.CreateTemporary();
|
||||||
|
design.ApplyEquip = equipFlags & EquipFlagExtensions.All;
|
||||||
|
design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.All;
|
||||||
|
design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head));
|
||||||
|
design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head));
|
||||||
|
design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand));
|
||||||
|
design.SetApplyWetness(design.DesignData.IsWet());
|
||||||
|
design.DesignData = state.ModelData;
|
||||||
|
return design;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DesignBase? FromBase64(string base64, bool customize, bool equip)
|
||||||
|
{
|
||||||
|
var bytes = System.Convert.FromBase64String(base64);
|
||||||
|
|
||||||
|
DesignBase ret;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (bytes[0])
|
||||||
|
{
|
||||||
|
case (byte)'{':
|
||||||
|
var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes));
|
||||||
|
ret = jObj1["Identifier"] != null
|
||||||
|
? Design.LoadDesign(_customize, _items, jObj1)
|
||||||
|
: DesignBase.LoadDesignBase(_customize, _items, jObj1);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
ret = _designs.CreateTemporary();
|
||||||
|
ret.MigrateBase64(_items, base64);
|
||||||
|
break;
|
||||||
|
case Version:
|
||||||
|
var version = bytes.DecompressToString(out var decompressed);
|
||||||
|
var jObj2 = JObject.Parse(decompressed);
|
||||||
|
Debug.Assert(version == Version);
|
||||||
|
ret = jObj2["Identifier"] != null
|
||||||
|
? Design.LoadDesign(_customize, _items, jObj2)
|
||||||
|
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
||||||
|
break;
|
||||||
|
default: throw new Exception($"Unknown Version {bytes[0]}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Error($"[DesignConverter] Could not parse base64 string [{base64}]:\n{ex}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!customize)
|
||||||
|
{
|
||||||
|
ret.ApplyCustomize = 0;
|
||||||
|
ret.SetApplyWetness(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!equip)
|
||||||
|
{
|
||||||
|
ret.ApplyEquip = 0;
|
||||||
|
ret.SetApplyHatVisible(false);
|
||||||
|
ret.SetApplyWeaponVisible(false);
|
||||||
|
ret.SetApplyVisorToggle(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ShareBase64(JObject jObj)
|
||||||
|
{
|
||||||
|
var json = jObj.ToString(Formatting.None);
|
||||||
|
var compressed = json.Compress(Version);
|
||||||
|
return System.Convert.ToBase64String(compressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
@ -101,26 +102,22 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case DesignChanged.Type.Created:
|
case DesignChanged.Type.Created:
|
||||||
var originalName = design.Name.Text.FixName();
|
CreateDuplicateLeaf(Root, design.Name.Text, design);
|
||||||
var name = originalName;
|
return;
|
||||||
var counter = 1;
|
|
||||||
while (Find(name, out _))
|
|
||||||
name = $"{originalName} ({++counter})";
|
|
||||||
|
|
||||||
CreateLeaf(Root, name, design);
|
|
||||||
break;
|
|
||||||
case DesignChanged.Type.Deleted:
|
case DesignChanged.Type.Deleted:
|
||||||
if (FindLeaf(design, out var leaf))
|
if (FindLeaf(design, out var leaf1))
|
||||||
Delete(leaf);
|
Delete(leaf1);
|
||||||
break;
|
return;
|
||||||
case DesignChanged.Type.ReloadedAll:
|
case DesignChanged.Type.ReloadedAll:
|
||||||
Reload();
|
Reload();
|
||||||
break;
|
return;
|
||||||
case DesignChanged.Type.Renamed when data is string oldName:
|
case DesignChanged.Type.Renamed when data is string oldName:
|
||||||
|
if (!FindLeaf(design, out var leaf2))
|
||||||
|
return;
|
||||||
var old = oldName.FixName();
|
var old = oldName.FixName();
|
||||||
if (Find(old, out var child) && child is not Folder)
|
if (old == leaf2.Name || leaf2.Name.IsDuplicateName(out var baseName, out _) && baseName == old)
|
||||||
Rename(child, design.Name);
|
RenameWithDuplicates(leaf2, design.Name);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ using System.Linq;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
|
using Glamourer.State;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
|
@ -78,16 +80,20 @@ public class DesignManager
|
||||||
_event.Invoke(DesignChanged.Type.ReloadedAll, null!);
|
_event.Invoke(DesignChanged.Type.ReloadedAll, null!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Create a new temporary design without adding it to the manager. </summary>
|
||||||
|
public DesignBase CreateTemporary()
|
||||||
|
=> new(_items);
|
||||||
|
|
||||||
/// <summary> Create a new design of a given name. </summary>
|
/// <summary> Create a new design of a given name. </summary>
|
||||||
public Design Create(string name)
|
public Design CreateEmpty(string name)
|
||||||
{
|
{
|
||||||
var design = new Design(_items)
|
var design = new Design(_items)
|
||||||
{
|
{
|
||||||
CreationDate = DateTimeOffset.UtcNow,
|
CreationDate = DateTimeOffset.UtcNow,
|
||||||
LastEdit = DateTimeOffset.UtcNow,
|
LastEdit = DateTimeOffset.UtcNow,
|
||||||
Identifier = CreateNewGuid(),
|
Identifier = CreateNewGuid(),
|
||||||
Index = _designs.Count,
|
|
||||||
Name = name,
|
Name = name,
|
||||||
|
Index = _designs.Count,
|
||||||
};
|
};
|
||||||
_designs.Add(design);
|
_designs.Add(design);
|
||||||
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
|
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
|
||||||
|
|
@ -96,6 +102,43 @@ public class DesignManager
|
||||||
return design;
|
return design;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Create a new design cloning a given temporary design. </summary>
|
||||||
|
public Design CreateClone(DesignBase clone, string name)
|
||||||
|
{
|
||||||
|
var design = new Design(clone)
|
||||||
|
{
|
||||||
|
CreationDate = DateTimeOffset.UtcNow,
|
||||||
|
LastEdit = DateTimeOffset.UtcNow,
|
||||||
|
Identifier = CreateNewGuid(),
|
||||||
|
Name = name,
|
||||||
|
Index = _designs.Count,
|
||||||
|
};
|
||||||
|
_designs.Add(design);
|
||||||
|
Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design.");
|
||||||
|
_saveService.ImmediateSave(design);
|
||||||
|
_event.Invoke(DesignChanged.Type.Created, design);
|
||||||
|
return design;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Create a new design cloning a given design. </summary>
|
||||||
|
public Design CreateClone(Design clone, string name)
|
||||||
|
{
|
||||||
|
var design = new Design(clone)
|
||||||
|
{
|
||||||
|
CreationDate = DateTimeOffset.UtcNow,
|
||||||
|
LastEdit = DateTimeOffset.UtcNow,
|
||||||
|
Identifier = CreateNewGuid(),
|
||||||
|
Name = name,
|
||||||
|
Index = _designs.Count,
|
||||||
|
};
|
||||||
|
_designs.Add(design);
|
||||||
|
Glamourer.Log.Debug(
|
||||||
|
$"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}.");
|
||||||
|
_saveService.ImmediateSave(design);
|
||||||
|
_event.Invoke(DesignChanged.Type.Created, design);
|
||||||
|
return design;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Delete a design. </summary>
|
/// <summary> Delete a design. </summary>
|
||||||
public void Delete(Design design)
|
public void Delete(Design design)
|
||||||
{
|
{
|
||||||
|
|
@ -181,6 +224,41 @@ public class DesignManager
|
||||||
_event.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx));
|
_event.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Add an associated mod to a design. </summary>
|
||||||
|
public void AddMod(Design design, Mod mod, ModSettings settings)
|
||||||
|
{
|
||||||
|
if (!design.AssociatedMods.TryAdd(mod, settings))
|
||||||
|
return;
|
||||||
|
|
||||||
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
_saveService.QueueSave(design);
|
||||||
|
Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} to design {design.Identifier}.");
|
||||||
|
_event.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Remove an associated mod from a design. </summary>
|
||||||
|
public void RemoveMod(Design design, Mod mod)
|
||||||
|
{
|
||||||
|
if (!design.AssociatedMods.Remove(mod, out var settings))
|
||||||
|
return;
|
||||||
|
|
||||||
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
_saveService.QueueSave(design);
|
||||||
|
Glamourer.Log.Debug($"Removed associated mod {mod.DirectoryName} from design {design.Identifier}.");
|
||||||
|
_event.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Set the write protection status of a design. </summary>
|
||||||
|
public void SetWriteProtection(Design design, bool value)
|
||||||
|
{
|
||||||
|
if (!design.SetWriteProtected(value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_saveService.QueueSave(design);
|
||||||
|
Glamourer.Log.Debug($"Set design {design.Identifier} to {(value ? "no longer be " : string.Empty)} write-protected.");
|
||||||
|
_event.Invoke(DesignChanged.Type.WriteProtection, design, value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Change a customization value. </summary>
|
/// <summary> Change a customization value. </summary>
|
||||||
public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value)
|
public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value)
|
||||||
{
|
{
|
||||||
|
|
@ -210,6 +288,7 @@ public class DesignManager
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}.");
|
Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}.");
|
||||||
_saveService.QueueSave(design);
|
_saveService.QueueSave(design);
|
||||||
_event.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx));
|
_event.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx));
|
||||||
|
|
@ -237,6 +316,7 @@ public class DesignManager
|
||||||
if (!design.DesignData.SetItem(slot, item))
|
if (!design.DesignData.SetItem(slot, item))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
Glamourer.Log.Debug(
|
Glamourer.Log.Debug(
|
||||||
$"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}).");
|
$"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}).");
|
||||||
_saveService.QueueSave(design);
|
_saveService.QueueSave(design);
|
||||||
|
|
@ -257,7 +337,6 @@ public class DesignManager
|
||||||
|
|
||||||
if (item.Type != currentMain.Type)
|
if (item.Type != currentMain.Type)
|
||||||
{
|
{
|
||||||
|
|
||||||
var newOffId = FullEquipTypeExtensions.OffhandTypes.Contains(item.Type)
|
var newOffId = FullEquipTypeExtensions.OffhandTypes.Contains(item.Type)
|
||||||
? item.Id
|
? item.Id
|
||||||
: ItemManager.NothingId(item.Type.Offhand());
|
: ItemManager.NothingId(item.Type.Offhand());
|
||||||
|
|
@ -332,6 +411,87 @@ public class DesignManager
|
||||||
_event.Invoke(DesignChanged.Type.ApplyStain, design, slot);
|
_event.Invoke(DesignChanged.Type.ApplyStain, design, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Change the bool value of one of the meta flags. </summary>
|
||||||
|
public void ChangeMeta(Design design, ActorState.MetaIndex metaIndex, bool value)
|
||||||
|
{
|
||||||
|
var change = metaIndex switch
|
||||||
|
{
|
||||||
|
ActorState.MetaIndex.Wetness => design.DesignData.SetIsWet(value),
|
||||||
|
ActorState.MetaIndex.HatState => design.DesignData.SetHatVisible(value),
|
||||||
|
ActorState.MetaIndex.VisorState => design.DesignData.SetVisor(value),
|
||||||
|
ActorState.MetaIndex.WeaponState => design.DesignData.SetWeaponVisible(value),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null),
|
||||||
|
};
|
||||||
|
if (!change)
|
||||||
|
return;
|
||||||
|
|
||||||
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
_saveService.QueueSave(design);
|
||||||
|
Glamourer.Log.Debug($"Set value of {metaIndex} to {value}.");
|
||||||
|
_event.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Change the application value of one of the meta flags. </summary>
|
||||||
|
public void ChangeApplyMeta(Design design, ActorState.MetaIndex metaIndex, bool value)
|
||||||
|
{
|
||||||
|
var change = metaIndex switch
|
||||||
|
{
|
||||||
|
ActorState.MetaIndex.Wetness => design.SetApplyWetness(value),
|
||||||
|
ActorState.MetaIndex.HatState => design.SetApplyHatVisible(value),
|
||||||
|
ActorState.MetaIndex.VisorState => design.SetApplyVisorToggle(value),
|
||||||
|
ActorState.MetaIndex.WeaponState => design.SetApplyWeaponVisible(value),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null),
|
||||||
|
};
|
||||||
|
if (!change)
|
||||||
|
return;
|
||||||
|
|
||||||
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
_saveService.QueueSave(design);
|
||||||
|
Glamourer.Log.Debug($"Set applying of {metaIndex} to {value}.");
|
||||||
|
_event.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Apply an entire design based on its appliance rules piece by piece. </summary>
|
||||||
|
public void ApplyDesign(Design design, DesignBase other)
|
||||||
|
{
|
||||||
|
if (other.DoApplyEquip(EquipSlot.MainHand))
|
||||||
|
ChangeWeapon(design, EquipSlot.MainHand, other.DesignData.Item(EquipSlot.MainHand));
|
||||||
|
|
||||||
|
if (other.DoApplyEquip(EquipSlot.OffHand))
|
||||||
|
ChangeWeapon(design, EquipSlot.OffHand, other.DesignData.Item(EquipSlot.OffHand));
|
||||||
|
|
||||||
|
if (other.DoApplyStain(EquipSlot.MainHand))
|
||||||
|
ChangeStain(design, EquipSlot.MainHand, other.DesignData.Stain(EquipSlot.MainHand));
|
||||||
|
|
||||||
|
if (other.DoApplyStain(EquipSlot.OffHand))
|
||||||
|
ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand));
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
{
|
||||||
|
if (other.DoApplyEquip(slot))
|
||||||
|
ChangeEquip(design, slot, other.DesignData.Item(slot));
|
||||||
|
|
||||||
|
if (other.DoApplyStain(slot))
|
||||||
|
ChangeStain(design, slot, other.DesignData.Stain(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||||
|
{
|
||||||
|
if (other.DoApplyCustomize(index))
|
||||||
|
ChangeCustomize(design, index, other.DesignData.Customize[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other.DoApplyHatVisible())
|
||||||
|
design.DesignData.SetHatVisible(other.DesignData.IsHatVisible());
|
||||||
|
if (other.DoApplyVisorToggle())
|
||||||
|
design.DesignData.SetVisor(other.DesignData.IsVisorToggled());
|
||||||
|
if (other.DoApplyWeaponVisible())
|
||||||
|
design.DesignData.SetWeaponVisible(other.DesignData.IsWeaponVisible());
|
||||||
|
if (other.DoApplyWetness())
|
||||||
|
design.DesignData.SetIsWet(other.DesignData.IsWet());
|
||||||
|
}
|
||||||
|
|
||||||
private void MigrateOldDesigns()
|
private void MigrateOldDesigns()
|
||||||
{
|
{
|
||||||
if (!File.Exists(_saveService.FileNames.MigrationDesignFile))
|
if (!File.Exists(_saveService.FileNames.MigrationDesignFile))
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,11 @@ public sealed class AutomationChanged : EventWrapper<Action<AutomationChanged.Ty
|
||||||
|
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
/// <seealso cref="Gui.Tabs.AutomationTab.SetSelector.OnAutomationChanged"/>
|
/// <seealso cref="Gui.Tabs.AutomationTab.SetSelector.OnAutomationChange"/>
|
||||||
SetSelector = 0,
|
SetSelector = 0,
|
||||||
|
|
||||||
|
/// <seealso cref="AutoDesignApplier.OnAutomationChange"/>
|
||||||
|
AutoDesignApplier,
|
||||||
}
|
}
|
||||||
|
|
||||||
public AutomationChanged()
|
public AutomationChanged()
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,12 @@ public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Desi
|
||||||
/// <summary> An existing design had an existing tag renamed. Data is the old name of the tag, the new name of the tag, and the index it had before being resorted [(string, string, int)]. </summary>
|
/// <summary> An existing design had an existing tag renamed. Data is the old name of the tag, the new name of the tag, and the index it had before being resorted [(string, string, int)]. </summary>
|
||||||
ChangedTag,
|
ChangedTag,
|
||||||
|
|
||||||
|
/// <summary> An existing design had a new associated mod added. Data is the Mod and its Settings [(Mod, ModSettings)]. </summary>
|
||||||
|
AddedMod,
|
||||||
|
|
||||||
|
/// <summary> An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. </summary>
|
||||||
|
RemovedMod,
|
||||||
|
|
||||||
/// <summary> An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
|
/// <summary> An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
|
||||||
Customize,
|
Customize,
|
||||||
|
|
||||||
|
|
@ -61,7 +67,10 @@ public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Desi
|
||||||
/// <summary> An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. </summary>
|
/// <summary> An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. </summary>
|
||||||
ApplyStain,
|
ApplyStain,
|
||||||
|
|
||||||
/// <summary> An existing design changed one of the meta flags. Data is null. </summary>
|
/// <summary> An existing design changed its write protection status. Data is the new value [bool]. </summary>
|
||||||
|
WriteProtection,
|
||||||
|
|
||||||
|
/// <summary> An existing design changed one of the meta flags. Data is the flag, whether it was about their applying and the new value [(MetaFlag, bool, bool)]. </summary>
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ public sealed class StateChanged : EventWrapper<Action<StateChanged.Type, StateC
|
||||||
Game,
|
Game,
|
||||||
Manual,
|
Manual,
|
||||||
Fixed,
|
Fixed,
|
||||||
|
Ipc,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Priority
|
public enum Priority
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ public class Glamourer : IDalamudPlugin
|
||||||
{
|
{
|
||||||
_services = ServiceManager.CreateProvider(pluginInterface, Log);
|
_services = ServiceManager.CreateProvider(pluginInterface, Log);
|
||||||
Chat = _services.GetRequiredService<ChatService>();
|
Chat = _services.GetRequiredService<ChatService>();
|
||||||
_services.GetRequiredService<BackupService>(); // call backup service.
|
|
||||||
_services.GetRequiredService<GlamourerWindowSystem>(); // initialize ui.
|
_services.GetRequiredService<GlamourerWindowSystem>(); // initialize ui.
|
||||||
_services.GetRequiredService<CommandService>(); // initialize commands.
|
_services.GetRequiredService<CommandService>(); // initialize commands.
|
||||||
_services.GetRequiredService<VisorService>();
|
_services.GetRequiredService<VisorService>();
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ public enum ColorId
|
||||||
DisabledAutoSet,
|
DisabledAutoSet,
|
||||||
AutomationActorAvailable,
|
AutomationActorAvailable,
|
||||||
AutomationActorUnavailable,
|
AutomationActorUnavailable,
|
||||||
|
HeaderButtons,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Colors
|
public static class Colors
|
||||||
|
|
@ -36,6 +37,7 @@ public static class Colors
|
||||||
ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ),
|
ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ),
|
||||||
ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ),
|
ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ),
|
||||||
ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ),
|
ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ),
|
||||||
|
ColorId.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ),
|
||||||
_ => (0x00000000, string.Empty, string.Empty ),
|
_ => (0x00000000, string.Empty, string.Empty ),
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -121,17 +121,20 @@ public partial class CustomizationDrawer
|
||||||
PercentageInputInt();
|
PercentageInputInt();
|
||||||
|
|
||||||
ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo));
|
ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo));
|
||||||
|
if (_set.DataByValue(CustomizeIndex.Face, _customize.Face, out _, _customize.Face) < 0)
|
||||||
|
ImGui.TextUnformatted("(Using Face 1)");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMultiIcons()
|
private void DrawMultiIcons()
|
||||||
{
|
{
|
||||||
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
|
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
|
||||||
using var _ = ImRaii.Group();
|
using var group = ImRaii.Group();
|
||||||
|
var face = _set.DataByValue(CustomizeIndex.Face, _customize.Face, out _, _customize.Face) < 0 ? _set.Faces[0].Value : _customize.Face;
|
||||||
foreach (var (featureIdx, idx) in options.WithIndex())
|
foreach (var (featureIdx, idx) in options.WithIndex())
|
||||||
{
|
{
|
||||||
using var id = SetId(featureIdx);
|
using var id = SetId(featureIdx);
|
||||||
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
|
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
|
||||||
var feature = _set.Data(featureIdx, 0, _customize.Face);
|
var feature = _set.Data(featureIdx, 0, face);
|
||||||
var icon = featureIdx == CustomizeIndex.LegacyTattoo
|
var icon = featureIdx == CustomizeIndex.LegacyTattoo
|
||||||
? _legacyTattoo ?? _service.AwaitedService.GetIcon(feature.IconId)
|
? _legacyTattoo ?? _service.AwaitedService.GetIcon(feature.IconId)
|
||||||
: _service.AwaitedService.GetIcon(feature.IconId);
|
: _service.AwaitedService.GetIcon(feature.IconId);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,22 @@
|
||||||
using System.Numerics;
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Xml.Linq;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.Gui.Customization;
|
using Glamourer.Gui.Customization;
|
||||||
using Glamourer.Gui.Equipment;
|
using Glamourer.Gui.Equipment;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
|
using Glamourer.Services;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Tabs.ActorTab;
|
namespace Glamourer.Gui.Tabs.ActorTab;
|
||||||
|
|
||||||
|
|
@ -19,6 +26,8 @@ public class ActorPanel
|
||||||
private readonly StateManager _stateManager;
|
private readonly StateManager _stateManager;
|
||||||
private readonly CustomizationDrawer _customizationDrawer;
|
private readonly CustomizationDrawer _customizationDrawer;
|
||||||
private readonly EquipmentDrawer _equipmentDrawer;
|
private readonly EquipmentDrawer _equipmentDrawer;
|
||||||
|
private readonly HumanModelList _humans;
|
||||||
|
private readonly IdentifierService _identification;
|
||||||
|
|
||||||
private ActorIdentifier _identifier;
|
private ActorIdentifier _identifier;
|
||||||
private string _actorName = string.Empty;
|
private string _actorName = string.Empty;
|
||||||
|
|
@ -27,12 +36,14 @@ public class ActorPanel
|
||||||
private ActorState? _state;
|
private ActorState? _state;
|
||||||
|
|
||||||
public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer,
|
public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer,
|
||||||
EquipmentDrawer equipmentDrawer)
|
EquipmentDrawer equipmentDrawer, HumanModelList humans, IdentifierService identification)
|
||||||
{
|
{
|
||||||
_selector = selector;
|
_selector = selector;
|
||||||
_stateManager = stateManager;
|
_stateManager = stateManager;
|
||||||
_customizationDrawer = customizationDrawer;
|
_customizationDrawer = customizationDrawer;
|
||||||
_equipmentDrawer = equipmentDrawer;
|
_equipmentDrawer = equipmentDrawer;
|
||||||
|
_humans = humans;
|
||||||
|
_identification = identification;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
|
|
@ -47,15 +58,16 @@ public class ActorPanel
|
||||||
private void DrawHeader()
|
private void DrawHeader()
|
||||||
{
|
{
|
||||||
var frameHeight = ImGui.GetFrameHeightWithSpacing();
|
var frameHeight = ImGui.GetFrameHeightWithSpacing();
|
||||||
var color = !_identifier.IsValid ? ImGui.GetColorU32(ImGuiCol.Text) : _data.Valid ? ColorId.ActorAvailable.Value() : ColorId.ActorUnavailable.Value();
|
var color = !_identifier.IsValid ? ImGui.GetColorU32(ImGuiCol.Text) :
|
||||||
|
_data.Valid ? ColorId.ActorAvailable.Value() : ColorId.ActorUnavailable.Value();
|
||||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
ImGuiUtil.DrawTextButton($"{_actorName}##playerHeader", new Vector2(-frameHeight, ImGui.GetFrameHeight()), buttonColor, color);
|
ImGuiUtil.DrawTextButton($"{_actorName}##playerHeader", new Vector2(-frameHeight, ImGui.GetFrameHeight()), buttonColor, color);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||||
using (var c = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value())
|
using (var c = ImRaii.PushColor(ImGuiCol.Text, ColorId.HeaderButtons.Value())
|
||||||
.Push(ImGuiCol.Border, ColorId.FolderExpanded.Value()))
|
.Push(ImGuiCol.Border, ColorId.HeaderButtons.Value()))
|
||||||
{
|
{
|
||||||
if (ImGuiUtil.DrawDisabledButton(
|
if (ImGuiUtil.DrawDisabledButton(
|
||||||
$"{(_selector.IncognitoMode ? FontAwesomeIcon.Eye : FontAwesomeIcon.EyeSlash).ToIconString()}###IncognitoMode",
|
$"{(_selector.IncognitoMode ? FontAwesomeIcon.Eye : FontAwesomeIcon.EyeSlash).ToIconString()}###IncognitoMode",
|
||||||
|
|
@ -79,13 +91,9 @@ public class ActorPanel
|
||||||
return (_selector.IncognitoMode ? _identifier.Incognito(null) : _identifier.ToString(), Actor.Null);
|
return (_selector.IncognitoMode ? _identifier.Incognito(null) : _identifier.ToString(), Actor.Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void DrawPanel()
|
private void DrawHumanPanel()
|
||||||
{
|
{
|
||||||
using var child = ImRaii.Child("##Panel", -Vector2.One, true);
|
if (_customizationDrawer.Draw(_state!.ModelData.Customize, false))
|
||||||
if (!child || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_customizationDrawer.Draw(_state.ModelData.Customize, false))
|
|
||||||
_stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual);
|
_stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual);
|
||||||
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
|
@ -122,6 +130,79 @@ public class ActorPanel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawMonsterPanel()
|
||||||
|
{
|
||||||
|
var names = _identification.AwaitedService.ModelCharaNames(_state!.ModelData.ModelId);
|
||||||
|
var turnHuman = ImGui.Button("Turn Human");
|
||||||
|
ImGui.Separator();
|
||||||
|
using (var box = ImRaii.ListBox("##MonsterList",
|
||||||
|
new Vector2(ImGui.GetContentRegionAvail().X, 10 * ImGui.GetTextLineHeightWithSpacing())))
|
||||||
|
{
|
||||||
|
if (names.Count == 0)
|
||||||
|
ImGui.TextUnformatted("Unknown Monster");
|
||||||
|
else
|
||||||
|
ImGuiClip.ClippedDraw(names, p => ImGui.TextUnformatted($"{p.Name} ({p.Kind.ToName()} #{p.Id})"),
|
||||||
|
ImGui.GetTextLineHeightWithSpacing());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.TextUnformatted("Customization Data");
|
||||||
|
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||||
|
{
|
||||||
|
foreach (var b in _state.ModelData.Customize.Data)
|
||||||
|
{
|
||||||
|
using (var g = ImRaii.Group())
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted($" {b:X2}");
|
||||||
|
ImGui.TextUnformatted($"{b,3}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.GetContentRegionAvail().X < ImGui.GetStyle().ItemSpacing.X + ImGui.CalcTextSize("XXX").X)
|
||||||
|
ImGui.NewLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.GetCursorPosX() != 0)
|
||||||
|
ImGui.NewLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.TextUnformatted("Equipment Data");
|
||||||
|
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||||
|
{
|
||||||
|
foreach (var b in _state.ModelData.GetEquipmentBytes())
|
||||||
|
{
|
||||||
|
using (var g = ImRaii.Group())
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted($" {b:X2}");
|
||||||
|
ImGui.TextUnformatted($"{b,3}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.GetContentRegionAvail().X < ImGui.GetStyle().ItemSpacing.X + ImGui.CalcTextSize("XXX").X)
|
||||||
|
ImGui.NewLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.GetCursorPosX() != 0)
|
||||||
|
ImGui.NewLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turnHuman)
|
||||||
|
_stateManager.TurnHuman(_state, StateChanged.Source.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void DrawPanel()
|
||||||
|
{
|
||||||
|
using var child = ImRaii.Child("##Panel", -Vector2.One, true);
|
||||||
|
if (!child || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_humans.IsHuman(_state.ModelData.ModelId))
|
||||||
|
DrawHumanPanel();
|
||||||
|
else
|
||||||
|
DrawMonsterPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private unsafe void RevertButton()
|
private unsafe void RevertButton()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -99,9 +99,10 @@ public class ActorSelector
|
||||||
_identifier = _objects.Player.GetIdentifier(_actors.AwaitedService);
|
_identifier = _objects.Player.GetIdentifier(_actors.AwaitedService);
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Actor targetActor = _targets.Target?.Address;
|
var (id, data) = _objects.TargetData;
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth,
|
var tt = data.Valid ? $"Select the current target {id} in the list." :
|
||||||
"Select the current target, if it is in the list.", _objects.IsInGPose || !targetActor, true))
|
id.IsValid ? $"The target {id} is not in the list." : "No target selected.";
|
||||||
_identifier = targetActor.GetIdentifier(_actors.AwaitedService);
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, tt, _objects.IsInGPose || !data.Valid, true))
|
||||||
|
_identifier = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,8 @@ public class SetPanel
|
||||||
ImGuiUtil.DrawTextButton(_selector.SelectionName, new Vector2(-frameHeight, ImGui.GetFrameHeight()), buttonColor);
|
ImGuiUtil.DrawTextButton(_selector.SelectionName, new Vector2(-frameHeight, ImGui.GetFrameHeight()), buttonColor);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value())
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.HeaderButtons.Value())
|
||||||
.Push(ImGuiCol.Border, ColorId.FolderExpanded.Value());
|
.Push(ImGuiCol.Border, ColorId.HeaderButtons.Value());
|
||||||
if (ImGuiUtil.DrawDisabledButton(
|
if (ImGuiUtil.DrawDisabledButton(
|
||||||
$"{(_selector.IncognitoMode ? FontAwesomeIcon.Eye : FontAwesomeIcon.EyeSlash).ToIconString()}###IncognitoMode",
|
$"{(_selector.IncognitoMode ? FontAwesomeIcon.Eye : FontAwesomeIcon.EyeSlash).ToIconString()}###IncognitoMode",
|
||||||
new Vector2(frameHeight, ImGui.GetFrameHeight()), string.Empty, false, true))
|
new Vector2(frameHeight, ImGui.GetFrameHeight()), string.Empty, false, true))
|
||||||
|
|
|
||||||
|
|
@ -46,12 +46,12 @@ public class SetSelector : IDisposable
|
||||||
_config = config;
|
_config = config;
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
_event.Subscribe(OnAutomationChanged, AutomationChanged.Priority.SetSelector);
|
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.SetSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_event.Unsubscribe(OnAutomationChanged);
|
_event.Unsubscribe(OnAutomationChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SelectionName
|
public string SelectionName
|
||||||
|
|
@ -60,7 +60,7 @@ public class SetSelector : IDisposable
|
||||||
public string GetSetName(AutoDesignSet? set, int index)
|
public string GetSetName(AutoDesignSet? set, int index)
|
||||||
=> set == null ? "No Selection" : IncognitoMode ? $"Auto Design Set #{index + 1}" : set.Name;
|
=> set == null ? "No Selection" : IncognitoMode ? $"Auto Design Set #{index + 1}" : set.Name;
|
||||||
|
|
||||||
private void OnAutomationChanged(AutomationChanged.Type type, AutoDesignSet? set, object? data)
|
private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? data)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
|
@ -19,7 +20,9 @@ using Glamourer.Interop.Structs;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Glamourer.Unlocks;
|
using Glamourer.Unlocks;
|
||||||
|
using Glamourer.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
|
|
@ -42,7 +45,7 @@ public unsafe class DebugTab : ITab
|
||||||
private readonly ObjectTable _objects;
|
private readonly ObjectTable _objects;
|
||||||
private readonly ObjectManager _objectManager;
|
private readonly ObjectManager _objectManager;
|
||||||
private readonly GlamourerIpc _ipc;
|
private readonly GlamourerIpc _ipc;
|
||||||
private readonly CodeService _code;
|
private readonly CodeService _code;
|
||||||
|
|
||||||
private readonly ItemManager _items;
|
private readonly ItemManager _items;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorService _actors;
|
||||||
|
|
@ -54,6 +57,7 @@ public unsafe class DebugTab : ITab
|
||||||
private readonly DesignManager _designManager;
|
private readonly DesignManager _designManager;
|
||||||
private readonly DesignFileSystem _designFileSystem;
|
private readonly DesignFileSystem _designFileSystem;
|
||||||
private readonly AutoDesignManager _autoDesignManager;
|
private readonly AutoDesignManager _autoDesignManager;
|
||||||
|
private readonly DesignConverter _designConverter;
|
||||||
|
|
||||||
private readonly PenumbraChangedItemTooltip _penumbraTooltip;
|
private readonly PenumbraChangedItemTooltip _penumbraTooltip;
|
||||||
|
|
||||||
|
|
@ -70,7 +74,7 @@ public unsafe class DebugTab : ITab
|
||||||
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
|
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
|
||||||
PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface,
|
PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface,
|
||||||
AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks,
|
AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks,
|
||||||
ItemUnlockManager itemUnlocks)
|
ItemUnlockManager itemUnlocks, DesignConverter designConverter)
|
||||||
{
|
{
|
||||||
_changeCustomizeService = changeCustomizeService;
|
_changeCustomizeService = changeCustomizeService;
|
||||||
_visorService = visorService;
|
_visorService = visorService;
|
||||||
|
|
@ -92,9 +96,10 @@ public unsafe class DebugTab : ITab
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
_autoDesignManager = autoDesignManager;
|
_autoDesignManager = autoDesignManager;
|
||||||
_jobs = jobs;
|
_jobs = jobs;
|
||||||
_code = code;
|
_code = code;
|
||||||
_customizeUnlocks = customizeUnlocks;
|
_customizeUnlocks = customizeUnlocks;
|
||||||
_itemUnlocks = itemUnlocks;
|
_itemUnlocks = itemUnlocks;
|
||||||
|
_designConverter = designConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
|
|
@ -817,6 +822,7 @@ public unsafe class DebugTab : ITab
|
||||||
|
|
||||||
DrawDesignManager();
|
DrawDesignManager();
|
||||||
DrawDesignTester();
|
DrawDesignTester();
|
||||||
|
DrawDesignConverter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawDesignManager()
|
private void DrawDesignManager()
|
||||||
|
|
@ -927,6 +933,83 @@ public unsafe class DebugTab : ITab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string _clipboardText = string.Empty;
|
||||||
|
private byte[] _clipboardData = Array.Empty<byte>();
|
||||||
|
private byte[] _dataUncompressed = Array.Empty<byte>();
|
||||||
|
private byte _version = 0;
|
||||||
|
private string _textUncompressed = string.Empty;
|
||||||
|
private JObject? _json = null;
|
||||||
|
private DesignBase? _tmpDesign = null;
|
||||||
|
private Exception? _clipboardProblem = null;
|
||||||
|
|
||||||
|
private void DrawDesignConverter()
|
||||||
|
{
|
||||||
|
using var tree = ImRaii.TreeNode("Design Converter");
|
||||||
|
if (!tree)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ImGui.Button("Import Clipboard"))
|
||||||
|
{
|
||||||
|
_clipboardText = string.Empty;
|
||||||
|
_clipboardData = Array.Empty<byte>();
|
||||||
|
_dataUncompressed = Array.Empty<byte>();
|
||||||
|
_textUncompressed = string.Empty;
|
||||||
|
_json = null;
|
||||||
|
_tmpDesign = null;
|
||||||
|
_clipboardProblem = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_clipboardText = ImGui.GetClipboardText();
|
||||||
|
_clipboardData = Convert.FromBase64String(_clipboardText);
|
||||||
|
_version = _clipboardData.Decompress(out _dataUncompressed);
|
||||||
|
_textUncompressed = Encoding.UTF8.GetString(_dataUncompressed);
|
||||||
|
_json = JObject.Parse(_textUncompressed);
|
||||||
|
_tmpDesign = _designConverter.FromBase64(_clipboardText, true, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_clipboardProblem = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_clipboardText.Length > 0)
|
||||||
|
{
|
||||||
|
using var f = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||||
|
ImGuiUtil.TextWrapped(_clipboardText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_clipboardData.Length > 0)
|
||||||
|
{
|
||||||
|
using var f = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||||
|
ImGuiUtil.TextWrapped(string.Join(" ", _clipboardData.Select(b => b.ToString("X2"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_dataUncompressed.Length > 0)
|
||||||
|
{
|
||||||
|
using var f = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||||
|
ImGuiUtil.TextWrapped(string.Join(" ", _dataUncompressed.Select(b => b.ToString("X2"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_textUncompressed.Length > 0)
|
||||||
|
{
|
||||||
|
using var f = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||||
|
ImGuiUtil.TextWrapped(_textUncompressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_json != null)
|
||||||
|
ImGui.TextUnformatted("JSON Parsing Successful!");
|
||||||
|
|
||||||
|
if (_tmpDesign != null)
|
||||||
|
DrawDesign(_tmpDesign);
|
||||||
|
|
||||||
|
if (_clipboardProblem != null)
|
||||||
|
{
|
||||||
|
using var f = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||||
|
ImGuiUtil.TextWrapped(_clipboardProblem.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void DrawState(ActorData data, ActorState state)
|
public void DrawState(ActorData data, ActorState state)
|
||||||
{
|
{
|
||||||
using var table = ImRaii.Table("##state", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
|
using var table = ImRaii.Table("##state", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
|
||||||
|
|
@ -955,20 +1038,20 @@ public unsafe class DebugTab : ITab
|
||||||
return $"{item.Name} ({item.ModelId.Value}{(item.WeaponType != 0 ? $"-{item.WeaponType.Value}" : string.Empty)}-{item.Variant})";
|
return $"{item.Name} ({item.ModelId.Value}{(item.WeaponType != 0 ? $"-{item.WeaponType.Value}" : string.Empty)}-{item.Variant})";
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaFlag.ModelId]);
|
PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]);
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state[ActorState.MetaFlag.Wetness]);
|
PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state[ActorState.MetaIndex.Wetness]);
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
|
|
||||||
if (state.BaseData.ModelId == 0 && state.ModelData.ModelId == 0)
|
if (state.BaseData.ModelId == 0 && state.ModelData.ModelId == 0)
|
||||||
{
|
{
|
||||||
PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state[ActorState.MetaFlag.HatState]);
|
PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state[ActorState.MetaIndex.HatState]);
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(),
|
PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(),
|
||||||
state[ActorState.MetaFlag.VisorState]);
|
state[ActorState.MetaIndex.VisorState]);
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(),
|
PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(),
|
||||||
state[ActorState.MetaFlag.WeaponState]);
|
state[ActorState.MetaIndex.WeaponState]);
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
||||||
{
|
{
|
||||||
|
|
@ -1053,33 +1136,36 @@ public unsafe class DebugTab : ITab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawDesign(Design design)
|
private void DrawDesign(DesignBase design)
|
||||||
{
|
{
|
||||||
using var table = ImRaii.Table("##equip", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
|
using var table = ImRaii.Table("##equip", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
|
||||||
ImGuiUtil.DrawTableColumn("Name");
|
if (design is Design d)
|
||||||
ImGuiUtil.DrawTableColumn(design.Name);
|
{
|
||||||
ImGuiUtil.DrawTableColumn($"({design.Index})");
|
ImGuiUtil.DrawTableColumn("Name");
|
||||||
ImGui.TableNextColumn();
|
ImGuiUtil.DrawTableColumn(d.Name);
|
||||||
ImGui.TextUnformatted("Description (Hover)");
|
ImGuiUtil.DrawTableColumn($"({d.Index})");
|
||||||
ImGuiUtil.HoverTooltip(design.Description);
|
ImGui.TableNextColumn();
|
||||||
ImGui.TableNextRow();
|
ImGui.TextUnformatted("Description (Hover)");
|
||||||
|
ImGuiUtil.HoverTooltip(d.Description);
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
|
||||||
ImGuiUtil.DrawTableColumn("Identifier");
|
ImGuiUtil.DrawTableColumn("Identifier");
|
||||||
ImGuiUtil.DrawTableColumn(design.Identifier.ToString());
|
ImGuiUtil.DrawTableColumn(d.Identifier.ToString());
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGuiUtil.DrawTableColumn("Design File System Path");
|
ImGuiUtil.DrawTableColumn("Design File System Path");
|
||||||
ImGuiUtil.DrawTableColumn(_designFileSystem.FindLeaf(design, out var leaf) ? leaf.FullName() : "No Path Known");
|
ImGuiUtil.DrawTableColumn(_designFileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : "No Path Known");
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
|
|
||||||
ImGuiUtil.DrawTableColumn("Creation");
|
ImGuiUtil.DrawTableColumn("Creation");
|
||||||
ImGuiUtil.DrawTableColumn(design.CreationDate.ToString());
|
ImGuiUtil.DrawTableColumn(d.CreationDate.ToString());
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGuiUtil.DrawTableColumn("Update");
|
ImGuiUtil.DrawTableColumn("Update");
|
||||||
ImGuiUtil.DrawTableColumn(design.LastEdit.ToString());
|
ImGuiUtil.DrawTableColumn(d.LastEdit.ToString());
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGuiUtil.DrawTableColumn("Tags");
|
ImGuiUtil.DrawTableColumn("Tags");
|
||||||
ImGuiUtil.DrawTableColumn(string.Join(", ", design.Tags));
|
ImGuiUtil.DrawTableColumn(string.Join(", ", d.Tags));
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
||||||
{
|
{
|
||||||
|
|
@ -1174,10 +1260,8 @@ public unsafe class DebugTab : ITab
|
||||||
foreach (var (identifier, state) in _state.Where(kvp => !_objectManager.ContainsKey(kvp.Key)))
|
foreach (var (identifier, state) in _state.Where(kvp => !_objectManager.ContainsKey(kvp.Key)))
|
||||||
{
|
{
|
||||||
using var t = ImRaii.TreeNode(identifier.ToString());
|
using var t = ImRaii.TreeNode(identifier.ToString());
|
||||||
if (!t)
|
if (t)
|
||||||
return;
|
DrawState(ActorData.Invalid, state);
|
||||||
|
|
||||||
DrawState(ActorData.Invalid, state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
173
Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs
Normal file
173
Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Widgets;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||||
|
|
||||||
|
public class DesignDetailTab
|
||||||
|
{
|
||||||
|
private readonly SaveService _saveService;
|
||||||
|
private readonly DesignFileSystemSelector _selector;
|
||||||
|
private readonly DesignFileSystem _fileSystem;
|
||||||
|
private readonly DesignManager _manager;
|
||||||
|
private readonly TagButtons _tagButtons = new();
|
||||||
|
|
||||||
|
private string? _newPath;
|
||||||
|
private string? _newDescription;
|
||||||
|
private string? _newName;
|
||||||
|
|
||||||
|
private bool _editDescriptionMode;
|
||||||
|
|
||||||
|
public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
_saveService = saveService;
|
||||||
|
_selector = selector;
|
||||||
|
_manager = manager;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
if (!ImGui.CollapsingHeader("Design Details"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
DrawDesignInfoTable();
|
||||||
|
DrawDescription();
|
||||||
|
ImGui.NewLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void DrawDesignInfoTable()
|
||||||
|
{
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f));
|
||||||
|
using var table = ImRaii.Table("Details", 2);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Last Update Datem").X);
|
||||||
|
ImGui.TableSetupColumn("Data", ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
|
||||||
|
ImGuiUtil.DrawFrameColumn("Design Name");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var width = new Vector2(ImGui.GetContentRegionAvail().X, 0);
|
||||||
|
var name = _newName ?? _selector.Selected!.Name;
|
||||||
|
ImGui.SetNextItemWidth(width.X);
|
||||||
|
if (ImGui.InputText("##Name", ref name, 128))
|
||||||
|
_newName = name;
|
||||||
|
|
||||||
|
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||||
|
{
|
||||||
|
_manager.Rename(_selector.Selected!, name);
|
||||||
|
_newName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var identifier = _selector.Selected!.Identifier.ToString();
|
||||||
|
ImGuiUtil.DrawFrameColumn("Unique Identifier");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var fileName = _saveService.FileNames.DesignFile(_selector.Selected!);
|
||||||
|
using (var mono = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||||
|
{
|
||||||
|
if (ImGui.Button(identifier, width))
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Chat.NotificationMessage(ex, $"Could not open file {fileName}.", $"Could not open file {fileName}", "Failure",
|
||||||
|
NotificationType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip($"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice.");
|
||||||
|
|
||||||
|
ImGuiUtil.DrawFrameColumn("Full Selector Path");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var path = _newPath ?? _selector.SelectedLeaf!.FullName();
|
||||||
|
ImGui.SetNextItemWidth(width.X);
|
||||||
|
if (ImGui.InputText("##Path", ref path, 1024))
|
||||||
|
_newPath = path;
|
||||||
|
|
||||||
|
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_fileSystem.RenameAndMove(_selector.SelectedLeaf!, path);
|
||||||
|
_newPath = null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Chat.NotificationMessage(ex, ex.Message, "Could not rename or move design", "Error", NotificationType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.DrawFrameColumn("Creation Date");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGuiUtil.DrawTextButton(_selector.Selected!.CreationDate.LocalDateTime.ToString("F"), width, 0);
|
||||||
|
|
||||||
|
ImGuiUtil.DrawFrameColumn("Last Update Date");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGuiUtil.DrawTextButton(_selector.Selected!.LastEdit.LocalDateTime.ToString("F"), width, 0);
|
||||||
|
|
||||||
|
ImGuiUtil.DrawFrameColumn("Tags");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
DrawTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTags()
|
||||||
|
{
|
||||||
|
var idx = _tagButtons.Draw(string.Empty, string.Empty, _selector.Selected!.Tags, out var editedTag);
|
||||||
|
if (idx < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (idx < _selector.Selected!.Tags.Length)
|
||||||
|
{
|
||||||
|
if (editedTag.Length == 0)
|
||||||
|
_manager.RemoveTag(_selector.Selected!, idx);
|
||||||
|
else
|
||||||
|
_manager.RenameTag(_selector.Selected!, idx, editedTag);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_manager.AddTag(_selector.Selected!, editedTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDescription()
|
||||||
|
{
|
||||||
|
var desc = _selector.Selected!.Description;
|
||||||
|
var size = new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeightWithSpacing());
|
||||||
|
if (!_editDescriptionMode)
|
||||||
|
{
|
||||||
|
using (var textBox = ImRaii.ListBox("##desc", size))
|
||||||
|
{
|
||||||
|
ImGuiUtil.TextWrapped(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("Edit Description"))
|
||||||
|
_editDescriptionMode = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var edit = _newDescription ?? desc;
|
||||||
|
if (ImGui.InputTextMultiline("##desc", ref edit, (uint)Math.Max(2000, 4 * edit.Length), size))
|
||||||
|
_newDescription = edit;
|
||||||
|
|
||||||
|
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||||
|
{
|
||||||
|
_manager.ChangeDescription(_selector.Selected!, edit);
|
||||||
|
_newDescription = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("Stop Editing"))
|
||||||
|
_editDescriptionMode = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
using System.Numerics;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -13,9 +16,14 @@ namespace Glamourer.Gui.Tabs.DesignTab;
|
||||||
|
|
||||||
public sealed class DesignFileSystemSelector : FileSystemSelector<Design, DesignFileSystemSelector.DesignState>
|
public sealed class DesignFileSystemSelector : FileSystemSelector<Design, DesignFileSystemSelector.DesignState>
|
||||||
{
|
{
|
||||||
private readonly DesignManager _designManager;
|
private readonly DesignManager _designManager;
|
||||||
private readonly DesignChanged _event;
|
private readonly DesignChanged _event;
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
|
private readonly DesignConverter _converter;
|
||||||
|
|
||||||
|
private string? _clipboardText;
|
||||||
|
private Design? _cloneDesign = null;
|
||||||
|
private string _newName = string.Empty;
|
||||||
|
|
||||||
public bool IncognitoMode
|
public bool IncognitoMode
|
||||||
{
|
{
|
||||||
|
|
@ -27,24 +35,37 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public new DesignFileSystem.Leaf? SelectedLeaf
|
||||||
|
=> base.SelectedLeaf;
|
||||||
|
|
||||||
public struct DesignState
|
public struct DesignState
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, KeyState keyState, DesignChanged @event,
|
public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, KeyState keyState, DesignChanged @event,
|
||||||
Configuration config)
|
Configuration config, DesignConverter converter)
|
||||||
: base(fileSystem, keyState)
|
: base(fileSystem, keyState)
|
||||||
{
|
{
|
||||||
_designManager = designManager;
|
_designManager = designManager;
|
||||||
_event = @event;
|
_event = @event;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_converter = converter;
|
||||||
_event.Subscribe(OnDesignChange, DesignChanged.Priority.DesignFileSystemSelector);
|
_event.Subscribe(OnDesignChange, DesignChanged.Priority.DesignFileSystemSelector);
|
||||||
AddButton(DeleteButton, 1000);
|
|
||||||
|
AddButton(NewDesignButton, 0);
|
||||||
|
AddButton(ImportDesignButton, 10);
|
||||||
|
AddButton(CloneDesignButton, 20);
|
||||||
|
AddButton(DeleteButton, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawPopups()
|
||||||
|
{
|
||||||
|
DrawNewDesignPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DrawLeafName(FileSystem<Design>.Leaf leaf, in DesignState state, bool selected)
|
protected override void DrawLeafName(FileSystem<Design>.Leaf leaf, in DesignState state, bool selected)
|
||||||
{
|
{
|
||||||
var flag = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
|
var flag = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
|
||||||
var name = IncognitoMode ? leaf.Value.Incognito : leaf.Name;
|
var name = IncognitoMode ? leaf.Value.Incognito : leaf.Value.Name.Text;
|
||||||
using var _ = ImRaii.TreeNode(name, flag);
|
using var _ = ImRaii.TreeNode(name, flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,11 +99,51 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
||||||
case DesignChanged.Type.AddedTag:
|
case DesignChanged.Type.AddedTag:
|
||||||
case DesignChanged.Type.ChangedTag:
|
case DesignChanged.Type.ChangedTag:
|
||||||
case DesignChanged.Type.RemovedTag:
|
case DesignChanged.Type.RemovedTag:
|
||||||
|
case DesignChanged.Type.AddedMod:
|
||||||
|
case DesignChanged.Type.RemovedMod:
|
||||||
|
case DesignChanged.Type.Created:
|
||||||
|
case DesignChanged.Type.Deleted:
|
||||||
SetFilterDirty();
|
SetFilterDirty();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NewDesignButton(Vector2 size)
|
||||||
|
{
|
||||||
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, "Create a new design with default configuration.", false,
|
||||||
|
true))
|
||||||
|
ImGui.OpenPopup("##NewDesign");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ImportDesignButton(Vector2 size)
|
||||||
|
{
|
||||||
|
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), size, "Try to import a design from your clipboard.", false,
|
||||||
|
true))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_clipboardText = ImGui.GetClipboardText();
|
||||||
|
ImGui.OpenPopup("##NewDesign");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Glamourer.Chat.NotificationMessage("Could not import data from clipboard.", "Failure", NotificationType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloneDesignButton(Vector2 size)
|
||||||
|
{
|
||||||
|
var tt = SelectedLeaf == null
|
||||||
|
? "No design selected."
|
||||||
|
: "Clone the currently selected design to a duplicate";
|
||||||
|
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clone.ToIconString(), size, tt, SelectedLeaf == null, true))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_cloneDesign = Selected!;
|
||||||
|
ImGui.OpenPopup("##NewDesign");
|
||||||
|
}
|
||||||
|
|
||||||
private void DeleteButton(Vector2 size)
|
private void DeleteButton(Vector2 size)
|
||||||
{
|
{
|
||||||
var keys = _config.DeleteDesignModifier.IsActive();
|
var keys = _config.DeleteDesignModifier.IsActive();
|
||||||
|
|
@ -91,10 +152,40 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
||||||
: "Delete the currently selected design entirely from your drive.\n"
|
: "Delete the currently selected design entirely from your drive.\n"
|
||||||
+ "This can not be undone.";
|
+ "This can not be undone.";
|
||||||
if (!keys)
|
if (!keys)
|
||||||
tt += $"\nHold {_config.DeleteDesignModifier} while clicking to delete the mod.";
|
tt += $"\nHold {_config.DeleteDesignModifier} while clicking to delete the design.";
|
||||||
|
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true)
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true)
|
||||||
&& Selected != null)
|
&& Selected != null)
|
||||||
_designManager.Delete(Selected);
|
_designManager.Delete(Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawNewDesignPopup()
|
||||||
|
{
|
||||||
|
if (!ImGuiUtil.OpenNameField("##NewDesign", ref _newName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_clipboardText != null)
|
||||||
|
{
|
||||||
|
var design = _converter.FromBase64(_clipboardText, true, true);
|
||||||
|
if (design is Design d)
|
||||||
|
_designManager.CreateClone(d, _newName);
|
||||||
|
else if (design != null)
|
||||||
|
_designManager.CreateClone(design, _newName);
|
||||||
|
else
|
||||||
|
Glamourer.Chat.NotificationMessage("Could not create a design, clipboard did not contain valid design data.", "Failure",
|
||||||
|
NotificationType.Error);
|
||||||
|
_clipboardText = null;
|
||||||
|
}
|
||||||
|
else if (_cloneDesign != null)
|
||||||
|
{
|
||||||
|
_designManager.CreateClone(_cloneDesign, _newName);
|
||||||
|
_cloneDesign = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_designManager.CreateEmpty(_newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
_newName = string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,19 @@
|
||||||
using System.Numerics;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using Glamourer.Automation;
|
||||||
|
using Glamourer.Customization;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Gui.Customization;
|
using Glamourer.Gui.Customization;
|
||||||
using Glamourer.Gui.Equipment;
|
using Glamourer.Gui.Equipment;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
|
using Glamourer.Services;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
|
using Glamourer.Structs;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
|
@ -20,93 +29,334 @@ public class DesignPanel
|
||||||
private readonly CustomizationDrawer _customizationDrawer;
|
private readonly CustomizationDrawer _customizationDrawer;
|
||||||
private readonly StateManager _state;
|
private readonly StateManager _state;
|
||||||
private readonly EquipmentDrawer _equipmentDrawer;
|
private readonly EquipmentDrawer _equipmentDrawer;
|
||||||
|
private readonly CustomizationService _customizationService;
|
||||||
|
private readonly ModAssociationsTab _modAssociations;
|
||||||
|
private readonly DesignDetailTab _designDetails;
|
||||||
|
private readonly DesignConverter _converter;
|
||||||
|
|
||||||
public DesignPanel(DesignFileSystemSelector selector, CustomizationDrawer customizationDrawer, DesignManager manager, ObjectManager objects,
|
public DesignPanel(DesignFileSystemSelector selector, CustomizationDrawer customizationDrawer, DesignManager manager, ObjectManager objects,
|
||||||
StateManager state, EquipmentDrawer equipmentDrawer)
|
StateManager state, EquipmentDrawer equipmentDrawer, CustomizationService customizationService, PenumbraService penumbra,
|
||||||
|
ModAssociationsTab modAssociations, DesignDetailTab designDetails, DesignConverter converter)
|
||||||
{
|
{
|
||||||
_selector = selector;
|
_selector = selector;
|
||||||
_customizationDrawer = customizationDrawer;
|
_customizationDrawer = customizationDrawer;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
_state = state;
|
_state = state;
|
||||||
_equipmentDrawer = equipmentDrawer;
|
_equipmentDrawer = equipmentDrawer;
|
||||||
|
_customizationService = customizationService;
|
||||||
|
_modAssociations = modAssociations;
|
||||||
|
_designDetails = designDetails;
|
||||||
|
_converter = converter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawHeader()
|
private void DrawHeader()
|
||||||
{
|
{
|
||||||
|
var selection = _selector.Selected;
|
||||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||||
var frameHeight = ImGui.GetFrameHeightWithSpacing();
|
var frameHeight = ImGui.GetFrameHeightWithSpacing();
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
ImGuiUtil.DrawTextButton(SelectionName, new Vector2(-frameHeight, ImGui.GetFrameHeight()), buttonColor);
|
ImGuiUtil.DrawTextButton(SelectionName, new Vector2(selection != null ? -2 * frameHeight : -frameHeight, ImGui.GetFrameHeight()),
|
||||||
|
buttonColor);
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value())
|
using var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.HeaderButtons.Value())
|
||||||
.Push(ImGuiCol.Border, ColorId.FolderExpanded.Value());
|
.Push(ImGuiCol.Text, ColorId.HeaderButtons.Value());
|
||||||
|
|
||||||
|
var hoverText = string.Empty;
|
||||||
|
if (selection != null)
|
||||||
|
{
|
||||||
|
if (ImGuiUtil.DrawDisabledButton(
|
||||||
|
$"{(selection.WriteProtected() ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock).ToIconString()}###Locked",
|
||||||
|
new Vector2(frameHeight, ImGui.GetFrameHeight()), string.Empty, false, true))
|
||||||
|
_manager.SetWriteProtection(selection, !selection.WriteProtected());
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
hoverText = selection.WriteProtected() ? "Make this design editable." : "Write-protect this design.";
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
if (ImGuiUtil.DrawDisabledButton(
|
if (ImGuiUtil.DrawDisabledButton(
|
||||||
$"{(_selector.IncognitoMode ? FontAwesomeIcon.Eye : FontAwesomeIcon.EyeSlash).ToIconString()}###IncognitoMode",
|
$"{(_selector.IncognitoMode ? FontAwesomeIcon.Eye : FontAwesomeIcon.EyeSlash).ToIconString()}###IncognitoMode",
|
||||||
new Vector2(frameHeight, ImGui.GetFrameHeight()), string.Empty, false, true))
|
new Vector2(frameHeight, ImGui.GetFrameHeight()), string.Empty, false, true))
|
||||||
_selector.IncognitoMode = !_selector.IncognitoMode;
|
_selector.IncognitoMode = !_selector.IncognitoMode;
|
||||||
var hovered = ImGui.IsItemHovered();
|
if (ImGui.IsItemHovered())
|
||||||
color.Pop(2);
|
hoverText = _selector.IncognitoMode ? "Toggle incognito mode off." : "Toggle incognito mode on.";
|
||||||
if (hovered)
|
|
||||||
ImGui.SetTooltip(_selector.IncognitoMode ? "Toggle incognito mode off." : "Toggle incognito mode on.");
|
if (hoverText.Length > 0)
|
||||||
|
ImGui.SetTooltip(hoverText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string SelectionName
|
private string SelectionName
|
||||||
=> _selector.Selected == null ? "No Selection" : _selector.IncognitoMode ? _selector.Selected.Incognito : _selector.Selected.Name.Text;
|
=> _selector.Selected == null ? "No Selection" : _selector.IncognitoMode ? _selector.Selected.Incognito : _selector.Selected.Name.Text;
|
||||||
|
|
||||||
|
private void DrawMetaData()
|
||||||
|
{
|
||||||
|
if (!ImGui.CollapsingHeader("MetaData"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (var group1 = ImRaii.Group())
|
||||||
|
{
|
||||||
|
var apply = _selector.Selected!.DesignData.IsHatVisible();
|
||||||
|
if (ImGui.Checkbox("Hat Visible", ref apply))
|
||||||
|
_manager.ChangeMeta(_selector.Selected, ActorState.MetaIndex.HatState, apply);
|
||||||
|
|
||||||
|
apply = _selector.Selected.DesignData.IsWeaponVisible();
|
||||||
|
if (ImGui.Checkbox("Weapon Visible", ref apply))
|
||||||
|
_manager.ChangeMeta(_selector.Selected, ActorState.MetaIndex.WeaponState, apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2);
|
||||||
|
|
||||||
|
using (var group2 = ImRaii.Group())
|
||||||
|
{
|
||||||
|
var apply = _selector.Selected.DesignData.IsVisorToggled();
|
||||||
|
if (ImGui.Checkbox("Visor Toggled", ref apply))
|
||||||
|
_manager.ChangeMeta(_selector.Selected, ActorState.MetaIndex.VisorState, apply);
|
||||||
|
|
||||||
|
apply = _selector.Selected.DesignData.IsWet();
|
||||||
|
if (ImGui.Checkbox("Force Wetness", ref apply))
|
||||||
|
_manager.ChangeMeta(_selector.Selected, ActorState.MetaIndex.Wetness, apply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEquipment()
|
||||||
|
{
|
||||||
|
if (!ImGui.CollapsingHeader("Equipment"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
{
|
||||||
|
var stain = _selector.Selected!.DesignData.Stain(slot);
|
||||||
|
if (_equipmentDrawer.DrawStain(stain, slot, out var newStain))
|
||||||
|
_manager.ChangeStain(_selector.Selected!, slot, newStain.RowIndex);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var armor = _selector.Selected!.DesignData.Item(slot);
|
||||||
|
if (_equipmentDrawer.DrawArmor(armor, slot, out var newArmor, _selector.Selected!.DesignData.Customize.Gender,
|
||||||
|
_selector.Selected!.DesignData.Customize.Race))
|
||||||
|
_manager.ChangeEquip(_selector.Selected!, slot, newArmor);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mhStain = _selector.Selected!.DesignData.Stain(EquipSlot.MainHand);
|
||||||
|
if (_equipmentDrawer.DrawStain(mhStain, EquipSlot.MainHand, out var newMhStain))
|
||||||
|
_manager.ChangeStain(_selector.Selected!, EquipSlot.MainHand, newMhStain.RowIndex);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var mh = _selector.Selected!.DesignData.Item(EquipSlot.MainHand);
|
||||||
|
if (_equipmentDrawer.DrawMainhand(mh, true, out var newMh))
|
||||||
|
_manager.ChangeWeapon(_selector.Selected!, EquipSlot.MainHand, newMh);
|
||||||
|
|
||||||
|
if (newMh.Type.Offhand() is not FullEquipType.Unknown)
|
||||||
|
{
|
||||||
|
var ohStain = _selector.Selected!.DesignData.Stain(EquipSlot.OffHand);
|
||||||
|
if (_equipmentDrawer.DrawStain(ohStain, EquipSlot.OffHand, out var newOhStain))
|
||||||
|
_manager.ChangeStain(_selector.Selected!, EquipSlot.OffHand, newOhStain.RowIndex);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var oh = _selector.Selected!.DesignData.Item(EquipSlot.OffHand);
|
||||||
|
if (_equipmentDrawer.DrawMainhand(oh, false, out var newOh))
|
||||||
|
_manager.ChangeWeapon(_selector.Selected!, EquipSlot.OffHand, newOh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCustomize()
|
||||||
|
{
|
||||||
|
if (ImGui.CollapsingHeader("Customization"))
|
||||||
|
_customizationDrawer.Draw(_selector.Selected!.DesignData.Customize, _selector.Selected!.WriteProtected());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawApplicationRules()
|
||||||
|
{
|
||||||
|
if (!ImGui.CollapsingHeader("Application Rules"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (var group1 = ImRaii.Group())
|
||||||
|
{
|
||||||
|
var set = _customizationService.AwaitedService.GetList(_selector.Selected!.DesignData.Customize.Clan,
|
||||||
|
_selector.Selected!.DesignData.Customize.Gender);
|
||||||
|
var all = CustomizationExtensions.All.Where(set.IsAvailable).Select(c => c.ToFlag()).Aggregate((a, b) => a | b);
|
||||||
|
var flags = (_selector.Selected!.ApplyCustomize & all) == 0 ? 0 : (_selector.Selected!.ApplyCustomize & all) == all ? 3 : 1;
|
||||||
|
if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3))
|
||||||
|
{
|
||||||
|
var newFlags = flags == 3;
|
||||||
|
foreach (var index in CustomizationExtensions.All)
|
||||||
|
_manager.ChangeApplyCustomize(_selector.Selected!, index, newFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan);
|
||||||
|
if (ImGui.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan))
|
||||||
|
_manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, applyClan);
|
||||||
|
|
||||||
|
var applyGender = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender);
|
||||||
|
if (ImGui.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender))
|
||||||
|
_manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, applyGender);
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable))
|
||||||
|
{
|
||||||
|
var apply = _selector.Selected!.DoApplyCustomize(index);
|
||||||
|
if (ImGui.Checkbox($"Apply {index.ToDefaultName()}", ref apply))
|
||||||
|
_manager.ChangeApplyCustomize(_selector.Selected!, index, apply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2);
|
||||||
|
using (var group2 = ImRaii.Group())
|
||||||
|
{
|
||||||
|
void ApplyEquip(string label, EquipFlag all, bool stain, IEnumerable<EquipSlot> slots)
|
||||||
|
{
|
||||||
|
var flags = (uint)(all & _selector.Selected!.ApplyEquip);
|
||||||
|
|
||||||
|
var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)all);
|
||||||
|
if (stain)
|
||||||
|
foreach (var slot in slots)
|
||||||
|
{
|
||||||
|
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : _selector.Selected!.DoApplyStain(slot);
|
||||||
|
if (ImGui.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange)
|
||||||
|
_manager.ChangeApplyStain(_selector.Selected!, slot, apply);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
foreach (var slot in slots)
|
||||||
|
{
|
||||||
|
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selector.Selected!.DoApplyEquip(slot);
|
||||||
|
if (ImGui.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange)
|
||||||
|
_manager.ChangeApplyEquip(_selector.Selected!, slot, apply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyEquip("Weapons", AutoDesign.WeaponFlags, false, new[]
|
||||||
|
{
|
||||||
|
EquipSlot.MainHand,
|
||||||
|
EquipSlot.OffHand,
|
||||||
|
});
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
ApplyEquip("Armor", AutoDesign.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots);
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
ApplyEquip("Accessories", AutoDesign.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots);
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
ApplyEquip("Dyes", AutoDesign.StainFlags, true,
|
||||||
|
EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.MainHand).Prepend(EquipSlot.OffHand));
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
const uint all = 0x0Fu;
|
||||||
|
var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00)
|
||||||
|
| (_selector.Selected!.DoApplyVisorToggle() ? 0x02u : 0x00)
|
||||||
|
| (_selector.Selected!.DoApplyWeaponVisible() ? 0x04u : 0x00)
|
||||||
|
| (_selector.Selected!.DoApplyWetness() ? 0x08u : 0x00);
|
||||||
|
var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all);
|
||||||
|
var apply = bigChange ? (flags & 0x01) == 0x01 : _selector.Selected!.DoApplyHatVisible();
|
||||||
|
if (ImGui.Checkbox("Apply Hat Visibility", ref apply) || bigChange)
|
||||||
|
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.HatState, apply);
|
||||||
|
|
||||||
|
apply = bigChange ? (flags & 0x02) == 0x02 : _selector.Selected!.DoApplyVisorToggle();
|
||||||
|
if (ImGui.Checkbox("Apply Visor State", ref apply) || bigChange)
|
||||||
|
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.VisorState, apply);
|
||||||
|
|
||||||
|
apply = bigChange ? (flags & 0x04) == 0x04 : _selector.Selected!.DoApplyWeaponVisible();
|
||||||
|
if (ImGui.Checkbox("Apply Weapon Visibility", ref apply) || bigChange)
|
||||||
|
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.WeaponState, apply);
|
||||||
|
|
||||||
|
apply = bigChange ? (flags & 0x08) == 0x08 : _selector.Selected!.DoApplyWetness();
|
||||||
|
if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange)
|
||||||
|
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.Wetness, apply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
using var group = ImRaii.Group();
|
using var group = ImRaii.Group();
|
||||||
DrawHeader();
|
DrawHeader();
|
||||||
|
|
||||||
var design = _selector.Selected;
|
var design = _selector.Selected;
|
||||||
using var child = ImRaii.Child("##Panel", -Vector2.One, true);
|
using var child = ImRaii.Child("##Panel", -Vector2.One, true);
|
||||||
if (!child || design == null)
|
if (!child || design == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ImGui.Button("TEST"))
|
DrawButtonRow();
|
||||||
{
|
DrawMetaData();
|
||||||
var (id, data) = _objects.PlayerData;
|
DrawCustomize();
|
||||||
|
DrawEquipment();
|
||||||
if (data.Valid && _state.GetOrCreate(id, data.Objects[0], out var state))
|
_designDetails.Draw();
|
||||||
_state.ApplyDesign(design, state);
|
DrawApplicationRules();
|
||||||
}
|
_modAssociations.Draw();
|
||||||
|
}
|
||||||
_customizationDrawer.Draw(design.DesignData.Customize, design.WriteProtected());
|
|
||||||
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
|
||||||
{
|
|
||||||
var stain = design.DesignData.Stain(slot);
|
|
||||||
if (_equipmentDrawer.DrawStain(stain, slot, out var newStain))
|
|
||||||
_manager.ChangeStain(design, slot, newStain.RowIndex);
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
var armor = design.DesignData.Item(slot);
|
|
||||||
if (_equipmentDrawer.DrawArmor(armor, slot, out var newArmor, design.DesignData.Customize.Gender, design.DesignData.Customize.Race))
|
|
||||||
_manager.ChangeEquip(design, slot, newArmor);
|
|
||||||
}
|
|
||||||
|
|
||||||
var mhStain = design.DesignData.Stain(EquipSlot.MainHand);
|
|
||||||
if (_equipmentDrawer.DrawStain(mhStain, EquipSlot.MainHand, out var newMhStain))
|
|
||||||
_manager.ChangeStain(design, EquipSlot.MainHand, newMhStain.RowIndex);
|
|
||||||
|
|
||||||
|
private void DrawButtonRow()
|
||||||
|
{
|
||||||
|
DrawSetFromClipboard();
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var mh = design.DesignData.Item(EquipSlot.MainHand);
|
DrawExportToClipboard();
|
||||||
if (_equipmentDrawer.DrawMainhand(mh, true, out var newMh))
|
ImGui.SameLine();
|
||||||
_manager.ChangeWeapon(design, EquipSlot.MainHand, newMh);
|
DrawApplyToSelf();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawApplyToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
if (newMh.Type.Offhand() is not FullEquipType.Unknown)
|
private void DrawSetFromClipboard()
|
||||||
|
{
|
||||||
|
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
|
||||||
|
"Try to apply a design from your clipboard.", _selector.Selected!.WriteProtected(), true))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var ohStain = design.DesignData.Stain(EquipSlot.OffHand);
|
var text = ImGui.GetClipboardText();
|
||||||
if (_equipmentDrawer.DrawStain(ohStain, EquipSlot.OffHand, out var newOhStain))
|
var design = _converter.FromBase64(text, true, true) ?? throw new Exception("The clipboard did not contain valid data.");
|
||||||
_manager.ChangeStain(design, EquipSlot.OffHand, newOhStain.RowIndex);
|
_manager.ApplyDesign(_selector.Selected!, design);
|
||||||
|
}
|
||||||
ImGui.SameLine();
|
catch (Exception ex)
|
||||||
var oh = design.DesignData.Item(EquipSlot.OffHand);
|
{
|
||||||
if (_equipmentDrawer.DrawMainhand(oh, false, out var newOh))
|
Glamourer.Chat.NotificationMessage(ex, $"Could not apply clipboard to {_selector.Selected!.Name}.",
|
||||||
_manager.ChangeWeapon(design, EquipSlot.OffHand, newOh);
|
$"Could not apply clipboard to design {_selector.Selected!.Identifier}", "Failure", NotificationType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawExportToClipboard()
|
||||||
|
{
|
||||||
|
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Copy.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
|
||||||
|
"Copy the current design to your clipboard.", false, true))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var text = _converter.ShareBase64(_selector.Selected!);
|
||||||
|
ImGui.SetClipboardText(text);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Chat.NotificationMessage(ex, $"Could not copy {_selector.Selected!.Name} data to clipboard.",
|
||||||
|
$"Could not copy data from design {_selector.Selected!.Identifier} to clipboard", "Failure", NotificationType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawApplyToSelf()
|
||||||
|
{
|
||||||
|
var (id, data) = _objects.PlayerData;
|
||||||
|
if (!ImGuiUtil.DrawDisabledButton("Apply to Yourself", Vector2.Zero, "Apply the current design with its settings to your character.",
|
||||||
|
!data.Valid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||||
|
_state.ApplyDesign(_selector.Selected!, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawApplyToTarget()
|
||||||
|
{
|
||||||
|
var (id, data) = _objects.TargetData;
|
||||||
|
var tt = id.IsValid
|
||||||
|
? data.Valid
|
||||||
|
? "Apply the current design with its settings to your current target."
|
||||||
|
: "The current target can not be manipulated."
|
||||||
|
: "No valid target selected.";
|
||||||
|
if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !data.Valid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||||
|
_state.ApplyDesign(_selector.Selected!, state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
144
Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs
Normal file
144
Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||||
|
|
||||||
|
public class ModAssociationsTab
|
||||||
|
{
|
||||||
|
private readonly PenumbraService _penumbra;
|
||||||
|
private readonly DesignFileSystemSelector _selector;
|
||||||
|
private readonly DesignManager _manager;
|
||||||
|
private readonly ModCombo _modCombo;
|
||||||
|
|
||||||
|
public ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager)
|
||||||
|
{
|
||||||
|
_penumbra = penumbra;
|
||||||
|
_selector = selector;
|
||||||
|
_manager = manager;
|
||||||
|
_modCombo = new ModCombo(penumbra);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
if (!ImGui.CollapsingHeader("Mod Associations"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
DrawApplyAllButton();
|
||||||
|
DrawTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawApplyAllButton()
|
||||||
|
{
|
||||||
|
var current = _penumbra.CurrentCollection;
|
||||||
|
if (!ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {current}##applyAll",
|
||||||
|
new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, current is "<Unavailable>"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (mod, settings) in _selector.Selected!.AssociatedMods)
|
||||||
|
_penumbra.SetMod(mod, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTable()
|
||||||
|
{
|
||||||
|
using var table = ImRaii.Table("Mods", 6, ImGuiTableFlags.RowBg);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui.TableSetupColumn("##Delete", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
|
||||||
|
ImGui.TableSetupColumn("Mod Name", ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
ImGui.TableSetupColumn("Directory Name", ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
ImGui.TableSetupColumn("State", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("State").X);
|
||||||
|
ImGui.TableSetupColumn("Priority", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Priority").X);
|
||||||
|
ImGui.TableSetupColumn("##Options", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Try Applyingm").X);
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
Mod? removedMod = null;
|
||||||
|
foreach (var ((mod, settings), idx) in _selector.Selected!.AssociatedMods.WithIndex())
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId(idx);
|
||||||
|
DrawAssociatedModRow(mod, settings, out removedMod);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawNewModRow();
|
||||||
|
|
||||||
|
if (removedMod.HasValue)
|
||||||
|
_manager.RemoveMod(_selector.Selected!, removedMod.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawAssociatedModRow(Mod mod, ModSettings settings, out Mod? removedMod)
|
||||||
|
{
|
||||||
|
removedMod = null;
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
|
||||||
|
"Delete this mod from associations", false, true))
|
||||||
|
removedMod = mod;
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn(mod.Name);
|
||||||
|
ImGuiUtil.DrawTableColumn(mod.DirectoryName);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTextButton((settings.Enabled ? FontAwesomeIcon.Check : FontAwesomeIcon.Cross).ToIconString(),
|
||||||
|
new Vector2(ImGui.GetContentRegionAvail().X, 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGuiUtil.RightAlign(settings.Priority.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGuiUtil.DrawDisabledButton("Try Applying", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty,
|
||||||
|
!_penumbra.Available))
|
||||||
|
{
|
||||||
|
var text = _penumbra.SetMod(mod, settings);
|
||||||
|
if (text.Length > 0)
|
||||||
|
Glamourer.Chat.NotificationMessage(text, "Failure", NotificationType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawAssociatedModTooltip(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawAssociatedModTooltip(ModSettings settings)
|
||||||
|
{
|
||||||
|
if (settings is not { Enabled: true, Settings.Count: > 0 } || !ImGui.IsItemHovered())
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var t = ImRaii.Tooltip();
|
||||||
|
ImGui.TextUnformatted("This will also try to apply the following settings to the current collection:");
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
using (var _ = ImRaii.Group())
|
||||||
|
{
|
||||||
|
ModCombo.DrawSettingsLeft(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2);
|
||||||
|
using (var _ = ImRaii.Group())
|
||||||
|
{
|
||||||
|
ModCombo.DrawSettingsRight(settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawNewModRow()
|
||||||
|
{
|
||||||
|
var currentName = _modCombo.CurrentSelection.Mod.Name;
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var tt = currentName.IsNullOrEmpty()
|
||||||
|
? "Please select a mod first."
|
||||||
|
: _selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod)
|
||||||
|
? "The design already contains an association with the selected mod."
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, tt.Length > 0,
|
||||||
|
true))
|
||||||
|
_manager.AddMod(_selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
_modCombo.Draw("##new", currentName.IsNullOrEmpty() ? "Select new Mod..." : currentName, string.Empty,
|
||||||
|
200 * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Glamourer/Gui/Tabs/DesignTab/ModCombo.cs
Normal file
85
Glamourer/Gui/Tabs/DesignTab/ModCombo.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Widgets;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||||
|
|
||||||
|
public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)>
|
||||||
|
{
|
||||||
|
public ModCombo(PenumbraService penumbra)
|
||||||
|
: base(penumbra.GetMods)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override string ToString((Mod Mod, ModSettings Settings) obj)
|
||||||
|
=> obj.Mod.Name;
|
||||||
|
|
||||||
|
protected override bool IsVisible(int globalIndex, LowerString filter)
|
||||||
|
=> filter.IsContained(Items[globalIndex].Mod.Name) || filter.IsContained(Items[globalIndex].Mod.DirectoryName);
|
||||||
|
|
||||||
|
protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId(globalIdx);
|
||||||
|
var (mod, settings) = Items[globalIdx];
|
||||||
|
bool ret;
|
||||||
|
using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !settings.Enabled))
|
||||||
|
{
|
||||||
|
ret = ImGui.Selectable(mod.Name, selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale);
|
||||||
|
using var tt = ImRaii.Tooltip();
|
||||||
|
var namesDifferent = mod.Name != mod.DirectoryName;
|
||||||
|
ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0));
|
||||||
|
using (var group = ImRaii.Group())
|
||||||
|
{
|
||||||
|
if (namesDifferent)
|
||||||
|
ImGui.TextUnformatted("Directory Name");
|
||||||
|
ImGui.TextUnformatted("Enabled");
|
||||||
|
ImGui.TextUnformatted("Priority");
|
||||||
|
DrawSettingsLeft(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine(Math.Max(ImGui.GetItemRectSize().X + 3 * ImGui.GetStyle().ItemSpacing.X, 150 * ImGuiHelpers.GlobalScale));
|
||||||
|
using (var group = ImRaii.Group())
|
||||||
|
{
|
||||||
|
if (namesDifferent)
|
||||||
|
ImGui.TextUnformatted(mod.DirectoryName);
|
||||||
|
ImGui.TextUnformatted(settings.Enabled.ToString());
|
||||||
|
ImGui.TextUnformatted(settings.Priority.ToString());
|
||||||
|
DrawSettingsRight(settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawSettingsLeft(ModSettings settings)
|
||||||
|
{
|
||||||
|
foreach (var setting in settings.Settings)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(setting.Key);
|
||||||
|
for (var i = 1; i < setting.Value.Count; ++i)
|
||||||
|
ImGui.NewLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawSettingsRight(ModSettings settings)
|
||||||
|
{
|
||||||
|
foreach (var setting in settings.Settings)
|
||||||
|
{
|
||||||
|
if (setting.Value.Count == 0)
|
||||||
|
ImGui.TextUnformatted("<None Enabled>");
|
||||||
|
else
|
||||||
|
foreach (var option in setting.Value)
|
||||||
|
ImGui.TextUnformatted(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,7 +42,6 @@ public class UnlockOverview
|
||||||
|
|
||||||
if (ImGui.Selectable(type.ToName(), _selected1 == type))
|
if (ImGui.Selectable(type.ToName(), _selected1 == type))
|
||||||
{
|
{
|
||||||
ClearIcons(_selected1);
|
|
||||||
_selected1 = type;
|
_selected1 = type;
|
||||||
_selected2 = SubRace.Unknown;
|
_selected2 = SubRace.Unknown;
|
||||||
_selected3 = Gender.Unknown;
|
_selected3 = Gender.Unknown;
|
||||||
|
|
@ -59,7 +58,6 @@ public class UnlockOverview
|
||||||
if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint",
|
if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint",
|
||||||
_selected2 == clan && _selected3 == gender))
|
_selected2 == clan && _selected3 == gender))
|
||||||
{
|
{
|
||||||
ClearIcons(_selected1);
|
|
||||||
_selected1 = FullEquipType.Unknown;
|
_selected1 = FullEquipType.Unknown;
|
||||||
_selected2 = clan;
|
_selected2 = clan;
|
||||||
_selected3 = gender;
|
_selected3 = gender;
|
||||||
|
|
@ -68,15 +66,6 @@ public class UnlockOverview
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearIcons(FullEquipType type)
|
|
||||||
{
|
|
||||||
if (!_items.ItemService.AwaitedService.TryGetValue(type, out var items))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var item in items)
|
|
||||||
_customizations.AwaitedService.RemoveIcon(item.IconId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnlockOverview(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks,
|
public UnlockOverview(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks,
|
||||||
CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureCache textureCache)
|
CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureCache textureCache)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,12 @@ public unsafe class MetaService : IDisposable
|
||||||
if (!actor.IsCharacter)
|
if (!actor.IsCharacter)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_hideHatGearHook.Original(&actor.AsCharacter->DrawData, 0, (byte)(value ? 1 : 0));
|
// The function seems to not do anything if the head is 0, sometimes?
|
||||||
|
var old = actor.AsCharacter->DrawData.Head.Id;
|
||||||
|
if (old == 0)
|
||||||
|
actor.AsCharacter->DrawData.Head.Id = 1;
|
||||||
|
_hideHatGearHook.Original(&actor.AsCharacter->DrawData, 0, (byte)(value ? 0 : 1));
|
||||||
|
actor.AsCharacter->DrawData.Head.Id = old;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetWeaponState(Actor actor, bool value)
|
public void SetWeaponState(Actor actor, bool value)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
|
@ -13,17 +12,19 @@ namespace Glamourer.Interop;
|
||||||
|
|
||||||
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||||
{
|
{
|
||||||
private readonly Framework _framework;
|
private readonly Framework _framework;
|
||||||
private readonly ClientState _clientState;
|
private readonly ClientState _clientState;
|
||||||
private readonly ObjectTable _objects;
|
private readonly ObjectTable _objects;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorService _actors;
|
||||||
|
private readonly TargetManager _targets;
|
||||||
|
|
||||||
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects, ActorService actors)
|
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects, ActorService actors, TargetManager targets)
|
||||||
{
|
{
|
||||||
_framework = framework;
|
_framework = framework;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
|
_targets = targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTime LastUpdate { get; private set; }
|
public DateTime LastUpdate { get; private set; }
|
||||||
|
|
@ -31,7 +32,8 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||||
public bool IsInGPose { get; private set; }
|
public bool IsInGPose { get; private set; }
|
||||||
public ushort World { get; private set; }
|
public ushort World { get; private set; }
|
||||||
|
|
||||||
private readonly Dictionary<ActorIdentifier, ActorData> _identifiers = new(200);
|
private readonly Dictionary<ActorIdentifier, ActorData> _identifiers = new(200);
|
||||||
|
private readonly Dictionary<ActorIdentifier, ActorData> _allWorldIdentifiers = new(200);
|
||||||
|
|
||||||
public IReadOnlyDictionary<ActorIdentifier, ActorData> Identifiers
|
public IReadOnlyDictionary<ActorIdentifier, ActorData> Identifiers
|
||||||
=> _identifiers;
|
=> _identifiers;
|
||||||
|
|
@ -45,6 +47,7 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||||
LastUpdate = lastUpdate;
|
LastUpdate = lastUpdate;
|
||||||
World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
|
World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
|
||||||
_identifiers.Clear();
|
_identifiers.Clear();
|
||||||
|
_allWorldIdentifiers.Clear();
|
||||||
|
|
||||||
for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i)
|
for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -106,6 +109,23 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||||
{
|
{
|
||||||
data.Objects.Add(character);
|
data.Objects.Add(character);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (identifier.Type is not (IdentifierType.Player or IdentifierType.Owned))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var allWorld = _actors.AwaitedService.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue,
|
||||||
|
identifier.Kind,
|
||||||
|
identifier.DataId);
|
||||||
|
|
||||||
|
if (!_allWorldIdentifiers.TryGetValue(allWorld, out var allWorldData))
|
||||||
|
{
|
||||||
|
allWorldData = new ActorData(character, allWorld.ToString());
|
||||||
|
_allWorldIdentifiers[allWorld] = allWorldData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allWorldData.Objects.Add(character);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Actor GPosePlayer
|
public Actor GPosePlayer
|
||||||
|
|
@ -114,6 +134,9 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||||
public Actor Player
|
public Actor Player
|
||||||
=> _objects.GetObjectAddress(0);
|
=> _objects.GetObjectAddress(0);
|
||||||
|
|
||||||
|
public Actor Target
|
||||||
|
=> _targets.Target?.Address ?? nint.Zero;
|
||||||
|
|
||||||
public (ActorIdentifier Identifier, ActorData Data) PlayerData
|
public (ActorIdentifier Identifier, ActorData Data) PlayerData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -121,7 +144,18 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||||
Update();
|
Update();
|
||||||
return Player.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data)
|
return Player.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data)
|
||||||
? (ident, data)
|
? (ident, data)
|
||||||
: (ActorIdentifier.Invalid, ActorData.Invalid);
|
: (ident, ActorData.Invalid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (ActorIdentifier Identifier, ActorData Data) TargetData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Update();
|
||||||
|
return Target.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data)
|
||||||
|
? (ident, data)
|
||||||
|
: (ident, ActorData.Invalid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,15 +168,16 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||||
public int Count
|
public int Count
|
||||||
=> Identifiers.Count;
|
=> Identifiers.Count;
|
||||||
|
|
||||||
/// <summary> Also (inefficiently) handles All Worlds players. </summary>
|
/// <summary> Also handles All Worlds players. </summary>
|
||||||
public bool ContainsKey(ActorIdentifier key)
|
public bool ContainsKey(ActorIdentifier key)
|
||||||
=> Identifiers.ContainsKey(key)
|
=> Identifiers.ContainsKey(key) || _allWorldIdentifiers.ContainsKey(key);
|
||||||
|| key.HomeWorld == ushort.MaxValue
|
|
||||||
&& Identifiers.Keys.FirstOrDefault(i => i.Type is IdentifierType.Player && i.PlayerName == key.PlayerName).IsValid;
|
|
||||||
|
|
||||||
public bool TryGetValue(ActorIdentifier key, out ActorData value)
|
public bool TryGetValue(ActorIdentifier key, out ActorData value)
|
||||||
=> Identifiers.TryGetValue(key, out value);
|
=> Identifiers.TryGetValue(key, out value);
|
||||||
|
|
||||||
|
public bool TryGetValueAllWorld(ActorIdentifier key, out ActorData value)
|
||||||
|
=> _allWorldIdentifiers.TryGetValue(key, out value);
|
||||||
|
|
||||||
public ActorData this[ActorIdentifier key]
|
public ActorData this[ActorIdentifier key]
|
||||||
=> Identifiers[key];
|
=> Identifiers[key];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
|
|
@ -8,21 +12,52 @@ using Penumbra.Api.Helpers;
|
||||||
|
|
||||||
namespace Glamourer.Interop.Penumbra;
|
namespace Glamourer.Interop.Penumbra;
|
||||||
|
|
||||||
|
using CurrentSettings = ValueTuple<PenumbraApiEc, (bool, int, IDictionary<string, IList<string>>, bool)?>;
|
||||||
|
|
||||||
|
public readonly record struct Mod(string Name, string DirectoryName) : IComparable<Mod>
|
||||||
|
{
|
||||||
|
public int CompareTo(Mod other)
|
||||||
|
{
|
||||||
|
var nameComparison = string.Compare(Name, other.Name, StringComparison.Ordinal);
|
||||||
|
if (nameComparison != 0)
|
||||||
|
return nameComparison;
|
||||||
|
|
||||||
|
return string.Compare(DirectoryName, other.DirectoryName, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct ModSettings(IDictionary<string, IList<string>> Settings, int Priority, bool Enabled)
|
||||||
|
{
|
||||||
|
public ModSettings()
|
||||||
|
: this(new Dictionary<string, IList<string>>(), 0, false)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public static ModSettings Empty
|
||||||
|
=> new();
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe class PenumbraService : IDisposable
|
public unsafe class PenumbraService : IDisposable
|
||||||
{
|
{
|
||||||
public const int RequiredPenumbraBreakingVersion = 4;
|
public const int RequiredPenumbraBreakingVersion = 4;
|
||||||
public const int RequiredPenumbraFeatureVersion = 15;
|
public const int RequiredPenumbraFeatureVersion = 15;
|
||||||
|
|
||||||
private readonly DalamudPluginInterface _pluginInterface;
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||||
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||||
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||||
private readonly EventSubscriber<ModSettingChange, string, string, bool> _modSettingChanged;
|
private readonly EventSubscriber<ModSettingChange, string, string, bool> _modSettingChanged;
|
||||||
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||||
private FuncSubscriber<int, int> _cutsceneParent;
|
private FuncSubscriber<int, int> _cutsceneParent;
|
||||||
private FuncSubscriber<int, (bool, bool, string)> _objectCollection;
|
private FuncSubscriber<int, (bool, bool, string)> _objectCollection;
|
||||||
|
private FuncSubscriber<IList<(string, string)>> _getMods;
|
||||||
|
private FuncSubscriber<ApiCollectionType, string> _currentCollection;
|
||||||
|
private FuncSubscriber<string, string, string, bool, CurrentSettings> _getCurrentSettings;
|
||||||
|
private FuncSubscriber<string, string, string, bool, PenumbraApiEc> _setMod;
|
||||||
|
private FuncSubscriber<string, string, string, int, PenumbraApiEc> _setModPriority;
|
||||||
|
private FuncSubscriber<string, string, string, string, string, PenumbraApiEc> _setModSetting;
|
||||||
|
private FuncSubscriber<string, string, string, string, IReadOnlyList<string>, PenumbraApiEc> _setModSettings;
|
||||||
|
|
||||||
private readonly EventSubscriber _initializedEvent;
|
private readonly EventSubscriber _initializedEvent;
|
||||||
private readonly EventSubscriber _disposedEvent;
|
private readonly EventSubscriber _disposedEvent;
|
||||||
|
|
@ -72,6 +107,90 @@ public unsafe class PenumbraService : IDisposable
|
||||||
remove => _modSettingChanged.Event -= value;
|
remove => _modSettingChanged.Event -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<(Mod Mod, ModSettings Settings)> GetMods()
|
||||||
|
{
|
||||||
|
if (!Available)
|
||||||
|
return Array.Empty<(Mod Mod, ModSettings Settings)>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var allMods = _getMods.Invoke();
|
||||||
|
var collection = _currentCollection.Invoke(ApiCollectionType.Current);
|
||||||
|
return allMods
|
||||||
|
.Select(m => (m.Item1, m.Item2, _getCurrentSettings.Invoke(collection, m.Item1, m.Item2, true)))
|
||||||
|
.Where(t => t.Item3.Item1 is PenumbraApiEc.Success)
|
||||||
|
.Select(t => (new Mod(t.Item2, t.Item1),
|
||||||
|
!t.Item3.Item2.HasValue
|
||||||
|
? ModSettings.Empty
|
||||||
|
: new ModSettings(t.Item3.Item2!.Value.Item3, t.Item3.Item2!.Value.Item2, t.Item3.Item2!.Value.Item1)))
|
||||||
|
.OrderByDescending(p => p.Item2.Enabled)
|
||||||
|
.ThenBy(p => p.Item1.Name)
|
||||||
|
.ThenBy(p => p.Item1.DirectoryName)
|
||||||
|
.ThenByDescending(p => p.Item2.Priority)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Error($"Error fetching mods from Penumbra:\n{ex}");
|
||||||
|
return Array.Empty<(Mod Mod, ModSettings Settings)>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CurrentCollection
|
||||||
|
=> Available ? _currentCollection.Invoke(ApiCollectionType.Current) : "<Unavailable>";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to set all mod settings as desired. Only sets when the mod should be enabled.
|
||||||
|
/// If it is disabled, ignore all other settings.
|
||||||
|
/// </summary>
|
||||||
|
public string SetMod(Mod mod, ModSettings settings)
|
||||||
|
{
|
||||||
|
if (!Available)
|
||||||
|
return "Penumbra is not available.";
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var collection = _currentCollection.Invoke(ApiCollectionType.Current);
|
||||||
|
var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled);
|
||||||
|
if (ec is PenumbraApiEc.ModMissing)
|
||||||
|
return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found.";
|
||||||
|
|
||||||
|
Debug.Assert(ec is not PenumbraApiEc.CollectionMissing, "Missing collection should not be possible.");
|
||||||
|
|
||||||
|
if (!settings.Enabled)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
ec = _setModPriority.Invoke(collection, mod.DirectoryName, mod.Name, settings.Priority);
|
||||||
|
Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged, "Setting Priority should not be able to fail.");
|
||||||
|
|
||||||
|
foreach (var (setting, list) in settings.Settings)
|
||||||
|
{
|
||||||
|
ec = list.Count == 1
|
||||||
|
? _setModSetting.Invoke(collection, mod.DirectoryName, mod.Name, setting, list[0])
|
||||||
|
: _setModSettings.Invoke(collection, mod.DirectoryName, mod.Name, setting, (IReadOnlyList<string>)list);
|
||||||
|
switch (ec)
|
||||||
|
{
|
||||||
|
case PenumbraApiEc.OptionGroupMissing:
|
||||||
|
sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}.");
|
||||||
|
break;
|
||||||
|
case PenumbraApiEc.OptionMissing:
|
||||||
|
sb.AppendLine($"Could not find all desired options in the option group {setting} in mod {mod.Name}.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged,
|
||||||
|
"Missing Mod or Collection should not be possible here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return sb.AppendLine(ex.Message).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Obtain the name of the collection currently assigned to the player. </summary>
|
/// <summary> Obtain the name of the collection currently assigned to the player. </summary>
|
||||||
public string GetCurrentPlayerCollection()
|
public string GetCurrentPlayerCollection()
|
||||||
{
|
{
|
||||||
|
|
@ -123,11 +242,19 @@ public unsafe class PenumbraService : IDisposable
|
||||||
_creatingCharacterBase.Enable();
|
_creatingCharacterBase.Enable();
|
||||||
_createdCharacterBase.Enable();
|
_createdCharacterBase.Enable();
|
||||||
_modSettingChanged.Enable();
|
_modSettingChanged.Enable();
|
||||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
|
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
|
||||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
|
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
|
||||||
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
|
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
|
||||||
_objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface);
|
_objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface);
|
||||||
Available = true;
|
_getMods = Ipc.GetMods.Subscriber(_pluginInterface);
|
||||||
|
_currentCollection = Ipc.GetCollectionForType.Subscriber(_pluginInterface);
|
||||||
|
_getCurrentSettings = Ipc.GetCurrentModSettings.Subscriber(_pluginInterface);
|
||||||
|
_setMod = Ipc.TrySetMod.Subscriber(_pluginInterface);
|
||||||
|
_setModPriority = Ipc.TrySetModPriority.Subscriber(_pluginInterface);
|
||||||
|
_setModSetting = Ipc.TrySetModSetting.Subscriber(_pluginInterface);
|
||||||
|
_setModSettings = Ipc.TrySetModSettings.Subscriber(_pluginInterface);
|
||||||
|
|
||||||
|
Available = true;
|
||||||
Glamourer.Log.Debug("Glamourer attached to Penumbra.");
|
Glamourer.Log.Debug("Glamourer attached to Penumbra.");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,22 @@ namespace Glamourer.Services;
|
||||||
|
|
||||||
public class BackupService
|
public class BackupService
|
||||||
{
|
{
|
||||||
|
private readonly Logger _logger;
|
||||||
|
private readonly DirectoryInfo _configDirectory;
|
||||||
|
private readonly IReadOnlyList<FileInfo> _fileNames;
|
||||||
|
|
||||||
public BackupService(Logger logger, FilenameService fileNames)
|
public BackupService(Logger logger, FilenameService fileNames)
|
||||||
{
|
{
|
||||||
var files = GlamourerFiles(fileNames);
|
_logger = logger;
|
||||||
Backup.CreateBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), files);
|
_fileNames = GlamourerFiles(fileNames);
|
||||||
|
_configDirectory = new DirectoryInfo(fileNames.ConfigDirectory);
|
||||||
|
Backup.CreateAutomaticBackup(logger, _configDirectory, _fileNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Create a permanent backup with a given name for migrations. </summary>
|
||||||
|
public void CreateMigrationBackup(string name)
|
||||||
|
=> Backup.CreatePermanentBackup(_logger, _configDirectory, _fileNames, name);
|
||||||
|
|
||||||
/// <summary> Collect all relevant files for glamourer configuration. </summary>
|
/// <summary> Collect all relevant files for glamourer configuration. </summary>
|
||||||
private static IReadOnlyList<FileInfo> GlamourerFiles(FilenameService fileNames)
|
private static IReadOnlyList<FileInfo> GlamourerFiles(FilenameService fileNames)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,16 @@ public class ConfigMigrationService
|
||||||
{
|
{
|
||||||
private readonly SaveService _saveService;
|
private readonly SaveService _saveService;
|
||||||
private readonly FixedDesignMigrator _fixedDesignMigrator;
|
private readonly FixedDesignMigrator _fixedDesignMigrator;
|
||||||
|
private readonly BackupService _backupService;
|
||||||
|
|
||||||
private Configuration _config = null!;
|
private Configuration _config = null!;
|
||||||
private JObject _data = null!;
|
private JObject _data = null!;
|
||||||
|
|
||||||
public ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator)
|
public ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator, BackupService backupService)
|
||||||
{
|
{
|
||||||
_saveService = saveService;
|
_saveService = saveService;
|
||||||
_fixedDesignMigrator = fixedDesignMigrator;
|
_fixedDesignMigrator = fixedDesignMigrator;
|
||||||
|
_backupService = backupService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Migrate(Configuration config)
|
public void Migrate(Configuration config)
|
||||||
|
|
@ -39,6 +41,7 @@ public class ConfigMigrationService
|
||||||
if (_config.Version > 1)
|
if (_config.Version > 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_backupService.CreateMigrationBackup("pre_v1_to_v2_migration");
|
||||||
_fixedDesignMigrator.Migrate(_data["FixedDesigns"]);
|
_fixedDesignMigrator.Migrate(_data["FixedDesigns"]);
|
||||||
_config.Version = 2;
|
_config.Version = 2;
|
||||||
var customizationColor = _data["CustomizationColor"]?.ToObject<uint>() ?? ColorId.CustomizationDesign.Data().DefaultColor;
|
var customizationColor = _data["CustomizationColor"]?.ToObject<uint>() ?? ColorId.CustomizationDesign.Data().DefaultColor;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ using Glamourer.Unlocks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
|
||||||
namespace Glamourer.Services;
|
namespace Glamourer.Services;
|
||||||
|
|
||||||
|
|
@ -73,7 +74,8 @@ public static class ServiceManager
|
||||||
.AddSingleton<ItemService>()
|
.AddSingleton<ItemService>()
|
||||||
.AddSingleton<ActorService>()
|
.AddSingleton<ActorService>()
|
||||||
.AddSingleton<CustomizationService>()
|
.AddSingleton<CustomizationService>()
|
||||||
.AddSingleton<ItemManager>();
|
.AddSingleton<ItemManager>()
|
||||||
|
.AddSingleton<HumanModelList>();
|
||||||
|
|
||||||
private static IServiceCollection AddInterop(this IServiceCollection services)
|
private static IServiceCollection AddInterop(this IServiceCollection services)
|
||||||
=> services.AddSingleton<VisorService>()
|
=> services.AddSingleton<VisorService>()
|
||||||
|
|
@ -93,7 +95,8 @@ public static class ServiceManager
|
||||||
.AddSingleton<DesignFileSystem>()
|
.AddSingleton<DesignFileSystem>()
|
||||||
.AddSingleton<AutoDesignManager>()
|
.AddSingleton<AutoDesignManager>()
|
||||||
.AddSingleton<AutoDesignApplier>()
|
.AddSingleton<AutoDesignApplier>()
|
||||||
.AddSingleton<FixedDesignMigrator>();
|
.AddSingleton<FixedDesignMigrator>()
|
||||||
|
.AddSingleton<DesignConverter>();
|
||||||
|
|
||||||
private static IServiceCollection AddState(this IServiceCollection services)
|
private static IServiceCollection AddState(this IServiceCollection services)
|
||||||
=> services.AddSingleton<StateManager>()
|
=> services.AddSingleton<StateManager>()
|
||||||
|
|
@ -114,6 +117,8 @@ public static class ServiceManager
|
||||||
.AddSingleton<DesignFileSystemSelector>()
|
.AddSingleton<DesignFileSystemSelector>()
|
||||||
.AddSingleton<DesignPanel>()
|
.AddSingleton<DesignPanel>()
|
||||||
.AddSingleton<DesignTab>()
|
.AddSingleton<DesignTab>()
|
||||||
|
.AddSingleton<ModAssociationsTab>()
|
||||||
|
.AddSingleton<DesignDetailTab>()
|
||||||
.AddSingleton<UnlockTable>()
|
.AddSingleton<UnlockTable>()
|
||||||
.AddSingleton<UnlockOverview>()
|
.AddSingleton<UnlockOverview>()
|
||||||
.AddSingleton<UnlocksTab>()
|
.AddSingleton<UnlocksTab>()
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace Glamourer.State;
|
||||||
|
|
||||||
public class ActorState
|
public class ActorState
|
||||||
{
|
{
|
||||||
public enum MetaFlag
|
public enum MetaIndex
|
||||||
{
|
{
|
||||||
Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices,
|
Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices,
|
||||||
HatState,
|
HatState,
|
||||||
|
|
@ -45,6 +45,6 @@ public class ActorState
|
||||||
public ref StateChanged.Source this[CustomizeIndex type]
|
public ref StateChanged.Source this[CustomizeIndex type]
|
||||||
=> ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type];
|
=> ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type];
|
||||||
|
|
||||||
public ref StateChanged.Source this[MetaFlag flag]
|
public ref StateChanged.Source this[MetaIndex index]
|
||||||
=> ref _sources[(int)flag];
|
=> ref _sources[(int)index];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,16 +102,19 @@ public class StateListener : IDisposable
|
||||||
var actor = (Actor)actorPtr;
|
var actor = (Actor)actorPtr;
|
||||||
var identifier = actor.GetIdentifier(_actors.AwaitedService);
|
var identifier = actor.GetIdentifier(_actors.AwaitedService);
|
||||||
|
|
||||||
var modelId = *(uint*)modelPtr;
|
ref var modelId = ref *(uint*)modelPtr;
|
||||||
ref var customize = ref *(Customize*)customizePtr;
|
ref var customize = ref *(Customize*)customizePtr;
|
||||||
if (_manager.TryGetValue(identifier, out var state))
|
if (_manager.TryGetValue(identifier, out var state))
|
||||||
{
|
{
|
||||||
_autoDesignApplier.Reduce(actor, identifier, state);
|
_autoDesignApplier.Reduce(actor, identifier, state);
|
||||||
switch (UpdateBaseData(actor, state, modelId, customizePtr, equipDataPtr))
|
switch (UpdateBaseData(actor, state, modelId, customizePtr, equipDataPtr))
|
||||||
{
|
{
|
||||||
|
// TODO handle right
|
||||||
case UpdateState.Change: break;
|
case UpdateState.Change: break;
|
||||||
case UpdateState.Transformed: break;
|
case UpdateState.Transformed: break;
|
||||||
case UpdateState.NoChange:
|
case UpdateState.NoChange:
|
||||||
|
|
||||||
|
modelId = state.ModelData.ModelId;
|
||||||
switch (UpdateBaseData(actor, state, customize))
|
switch (UpdateBaseData(actor, state, customize))
|
||||||
{
|
{
|
||||||
case UpdateState.Transformed: break;
|
case UpdateState.Transformed: break;
|
||||||
|
|
@ -128,7 +131,7 @@ public class StateListener : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_funModule.ApplyFun(actor, new Span<CharacterArmor>((void*) equipDataPtr, 10), ref customize);
|
_funModule.ApplyFun(actor, new Span<CharacterArmor>((void*)equipDataPtr, 10), ref customize);
|
||||||
if (modelId == 0)
|
if (modelId == 0)
|
||||||
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
|
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +174,7 @@ public class StateListener : IDisposable
|
||||||
// Do nothing. But this usually can not happen because the hooked function also writes to game objects later.
|
// Do nothing. But this usually can not happen because the hooked function also writes to game objects later.
|
||||||
case UpdateState.Transformed: break;
|
case UpdateState.Transformed: break;
|
||||||
case UpdateState.Change:
|
case UpdateState.Change:
|
||||||
if (state[slot, false] is not StateChanged.Source.Fixed)
|
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||||
{
|
{
|
||||||
state.ModelData.SetItem(slot, state.BaseData.Item(slot));
|
state.ModelData.SetItem(slot, state.BaseData.Item(slot));
|
||||||
state[slot, false] = StateChanged.Source.Game;
|
state[slot, false] = StateChanged.Source.Game;
|
||||||
|
|
@ -181,7 +184,7 @@ public class StateListener : IDisposable
|
||||||
apply = true;
|
apply = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state[slot, false] is not StateChanged.Source.Fixed)
|
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||||
{
|
{
|
||||||
state.ModelData.SetStain(slot, state.BaseData.Stain(slot));
|
state.ModelData.SetStain(slot, state.BaseData.Stain(slot));
|
||||||
state[slot, true] = StateChanged.Source.Game;
|
state[slot, true] = StateChanged.Source.Game;
|
||||||
|
|
@ -246,7 +249,7 @@ public class StateListener : IDisposable
|
||||||
// Update model state if not on fixed design.
|
// Update model state if not on fixed design.
|
||||||
case UpdateState.Change:
|
case UpdateState.Change:
|
||||||
var apply = false;
|
var apply = false;
|
||||||
if (state[slot, false] is not StateChanged.Source.Fixed)
|
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||||
{
|
{
|
||||||
state.ModelData.SetItem(slot, state.BaseData.Item(slot));
|
state.ModelData.SetItem(slot, state.BaseData.Item(slot));
|
||||||
state[slot, false] = StateChanged.Source.Game;
|
state[slot, false] = StateChanged.Source.Game;
|
||||||
|
|
@ -256,7 +259,7 @@ public class StateListener : IDisposable
|
||||||
apply = true;
|
apply = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state[slot, true] is not StateChanged.Source.Fixed)
|
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||||
{
|
{
|
||||||
state.ModelData.SetStain(slot, state.BaseData.Stain(slot));
|
state.ModelData.SetStain(slot, state.BaseData.Stain(slot));
|
||||||
state[slot, true] = StateChanged.Source.Game;
|
state[slot, true] = StateChanged.Source.Game;
|
||||||
|
|
@ -364,7 +367,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
// if base state changed, either overwrite the actual value if we have fixed values,
|
// if base state changed, either overwrite the actual value if we have fixed values,
|
||||||
// or overwrite the stored model state with the new one.
|
// or overwrite the stored model state with the new one.
|
||||||
if (state[ActorState.MetaFlag.VisorState] is StateChanged.Source.Fixed)
|
if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
|
||||||
value.Value = state.ModelData.IsVisorToggled();
|
value.Value = state.ModelData.IsVisorToggled();
|
||||||
else
|
else
|
||||||
_manager.ChangeVisorState(state, value, StateChanged.Source.Game);
|
_manager.ChangeVisorState(state, value, StateChanged.Source.Game);
|
||||||
|
|
@ -394,7 +397,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
// if base state changed, either overwrite the actual value if we have fixed values,
|
// if base state changed, either overwrite the actual value if we have fixed values,
|
||||||
// or overwrite the stored model state with the new one.
|
// or overwrite the stored model state with the new one.
|
||||||
if (state[ActorState.MetaFlag.HatState] is StateChanged.Source.Fixed)
|
if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
|
||||||
value.Value = state.ModelData.IsHatVisible();
|
value.Value = state.ModelData.IsHatVisible();
|
||||||
else
|
else
|
||||||
_manager.ChangeHatState(state, value, StateChanged.Source.Game);
|
_manager.ChangeHatState(state, value, StateChanged.Source.Game);
|
||||||
|
|
@ -424,7 +427,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
// if base state changed, either overwrite the actual value if we have fixed values,
|
// if base state changed, either overwrite the actual value if we have fixed values,
|
||||||
// or overwrite the stored model state with the new one.
|
// or overwrite the stored model state with the new one.
|
||||||
if (state[ActorState.MetaFlag.WeaponState] is StateChanged.Source.Fixed)
|
if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
|
||||||
value.Value = state.ModelData.IsWeaponVisible();
|
value.Value = state.ModelData.IsWeaponVisible();
|
||||||
else
|
else
|
||||||
_manager.ChangeWeaponState(state, value, StateChanged.Source.Game);
|
_manager.ChangeWeaponState(state, value, StateChanged.Source.Game);
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
{
|
{
|
||||||
ModelData = FromActor(actor, true),
|
ModelData = FromActor(actor, true),
|
||||||
BaseData = FromActor(actor, false),
|
BaseData = FromActor(actor, false),
|
||||||
LastJob = (byte) (actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0),
|
LastJob = (byte)(actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0),
|
||||||
};
|
};
|
||||||
// state.Identifier is owned.
|
// state.Identifier is owned.
|
||||||
_states.Add(state.Identifier, state);
|
_states.Add(state.Identifier, state);
|
||||||
|
|
@ -192,6 +192,21 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
|
|
||||||
#region Change Values
|
#region Change Values
|
||||||
|
|
||||||
|
/// <summary> Turn a non-human actor human. </summary>
|
||||||
|
public void TurnHuman(ActorState state, StateChanged.Source source)
|
||||||
|
{
|
||||||
|
if (state.ModelData.ModelId == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
state.ModelData.ModelId = 0;
|
||||||
|
state[ActorState.MetaIndex.ModelId] = source;
|
||||||
|
ChangeCustomize(state, Customize.Default, CustomizeFlagExtensions.All, source);
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
ChangeEquip(state, slot, ItemManager.NothingItem(slot), 0, source);
|
||||||
|
ChangeEquip(state, EquipSlot.MainHand, _items.DefaultSword, 0, source);
|
||||||
|
ChangeEquip(state, EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield), 0, source);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Change a customization value. </summary>
|
/// <summary> Change a customization value. </summary>
|
||||||
public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source)
|
public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source)
|
||||||
{
|
{
|
||||||
|
|
@ -256,9 +271,14 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||||
if (source is StateChanged.Source.Manual)
|
if (source is StateChanged.Source.Manual)
|
||||||
if (type == StateChanged.Type.Equip)
|
if (type == StateChanged.Type.Equip)
|
||||||
_editor.ChangeArmor(objects, slot, state.ModelData.Armor(slot));
|
{
|
||||||
|
if (slot is not EquipSlot.Head || state.ModelData.IsHatVisible())
|
||||||
|
_editor.ChangeArmor(objects, slot, state.ModelData.Armor(slot));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_editor.ChangeWeapon(objects, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot));
|
_editor.ChangeWeapon(objects, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot));
|
||||||
|
}
|
||||||
|
|
||||||
// Meta.
|
// Meta.
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
|
|
@ -283,9 +303,14 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||||
if (source is StateChanged.Source.Manual)
|
if (source is StateChanged.Source.Manual)
|
||||||
if (type == StateChanged.Type.Equip)
|
if (type == StateChanged.Type.Equip)
|
||||||
_editor.ChangeArmor(objects, slot, state.ModelData.Armor(slot));
|
{
|
||||||
|
if (slot is not EquipSlot.Head || state.ModelData.IsHatVisible())
|
||||||
|
_editor.ChangeArmor(objects, slot, state.ModelData.Armor(slot));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_editor.ChangeWeapon(objects, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot));
|
_editor.ChangeWeapon(objects, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot));
|
||||||
|
}
|
||||||
|
|
||||||
// Meta.
|
// Meta.
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
|
|
@ -322,7 +347,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
// Update state data.
|
// Update state data.
|
||||||
var old = state.ModelData.IsHatVisible();
|
var old = state.ModelData.IsHatVisible();
|
||||||
state.ModelData.SetHatVisible(value);
|
state.ModelData.SetHatVisible(value);
|
||||||
state[ActorState.MetaFlag.HatState] = source;
|
state[ActorState.MetaIndex.HatState] = source;
|
||||||
|
|
||||||
// Update draw objects / game objects.
|
// Update draw objects / game objects.
|
||||||
_objects.Update();
|
_objects.Update();
|
||||||
|
|
@ -333,7 +358,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
// Meta.
|
// Meta.
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set Head Gear Visibility in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
$"Set Head Gear Visibility in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
||||||
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaFlag.HatState));
|
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaIndex.HatState));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Change weapon visibility. </summary>
|
/// <summary> Change weapon visibility. </summary>
|
||||||
|
|
@ -342,7 +367,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
// Update state data.
|
// Update state data.
|
||||||
var old = state.ModelData.IsWeaponVisible();
|
var old = state.ModelData.IsWeaponVisible();
|
||||||
state.ModelData.SetWeaponVisible(value);
|
state.ModelData.SetWeaponVisible(value);
|
||||||
state[ActorState.MetaFlag.WeaponState] = source;
|
state[ActorState.MetaIndex.WeaponState] = source;
|
||||||
|
|
||||||
// Update draw objects / game objects.
|
// Update draw objects / game objects.
|
||||||
_objects.Update();
|
_objects.Update();
|
||||||
|
|
@ -353,7 +378,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
// Meta.
|
// Meta.
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set Weapon Visibility in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
$"Set Weapon Visibility in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
||||||
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaFlag.WeaponState));
|
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaIndex.WeaponState));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Change visor state. </summary>
|
/// <summary> Change visor state. </summary>
|
||||||
|
|
@ -362,7 +387,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
// Update state data.
|
// Update state data.
|
||||||
var old = state.ModelData.IsVisorToggled();
|
var old = state.ModelData.IsVisorToggled();
|
||||||
state.ModelData.SetVisor(value);
|
state.ModelData.SetVisor(value);
|
||||||
state[ActorState.MetaFlag.VisorState] = source;
|
state[ActorState.MetaIndex.VisorState] = source;
|
||||||
|
|
||||||
// Update draw objects.
|
// Update draw objects.
|
||||||
_objects.Update();
|
_objects.Update();
|
||||||
|
|
@ -373,7 +398,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
// Meta.
|
// Meta.
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set Visor State in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
$"Set Visor State in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
||||||
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaFlag.VisorState));
|
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaIndex.VisorState));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set GPose Wetness. </summary>
|
/// <summary> Set GPose Wetness. </summary>
|
||||||
|
|
@ -382,7 +407,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
// Update state data.
|
// Update state data.
|
||||||
var old = state.ModelData.IsWet();
|
var old = state.ModelData.IsWet();
|
||||||
state.ModelData.SetIsWet(value);
|
state.ModelData.SetIsWet(value);
|
||||||
state[ActorState.MetaFlag.Wetness] = source;
|
state[ActorState.MetaIndex.Wetness] = source;
|
||||||
|
|
||||||
// Update draw objects / game objects.
|
// Update draw objects / game objects.
|
||||||
_objects.Update();
|
_objects.Update();
|
||||||
|
|
@ -392,12 +417,12 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
// Meta.
|
// Meta.
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set Wetness in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
$"Set Wetness in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
||||||
_event.Invoke(StateChanged.Type.Other, state[ActorState.MetaFlag.Wetness], state, objects, (old, value, ActorState.MetaFlag.Wetness));
|
_event.Invoke(StateChanged.Type.Other, state[ActorState.MetaIndex.Wetness], state, objects, (old, value, ActorState.MetaIndex.Wetness));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public void ApplyDesign(Design design, ActorState state)
|
public void ApplyDesign(DesignBase design, ActorState state)
|
||||||
{
|
{
|
||||||
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
|
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
|
||||||
{
|
{
|
||||||
|
|
@ -416,6 +441,12 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.ModelData.ModelId != 0 && design.DesignData.ModelId == 0)
|
||||||
|
TurnHuman(state, StateChanged.Source.Manual);
|
||||||
|
|
||||||
|
if (design.DoApplyHatVisible())
|
||||||
|
ChangeHatState(state, design.DesignData.IsHatVisible(), StateChanged.Source.Manual);
|
||||||
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot));
|
HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot));
|
||||||
|
|
||||||
|
|
@ -428,8 +459,6 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
&& design.DesignData.Item(EquipSlot.OffHand).Type == state.BaseData.Item(EquipSlot.OffHand).Type,
|
&& design.DesignData.Item(EquipSlot.OffHand).Type == state.BaseData.Item(EquipSlot.OffHand).Type,
|
||||||
design.DoApplyStain(EquipSlot.OffHand));
|
design.DoApplyStain(EquipSlot.OffHand));
|
||||||
|
|
||||||
if (design.DoApplyHatVisible())
|
|
||||||
ChangeHatState(state, design.DesignData.IsHatVisible(), StateChanged.Source.Manual);
|
|
||||||
if (design.DoApplyWeaponVisible())
|
if (design.DoApplyWeaponVisible())
|
||||||
ChangeWeaponState(state, design.DesignData.IsWeaponVisible(), StateChanged.Source.Manual);
|
ChangeWeaponState(state, design.DesignData.IsWeaponVisible(), StateChanged.Source.Manual);
|
||||||
if (design.DoApplyVisorToggle())
|
if (design.DoApplyVisorToggle())
|
||||||
|
|
|
||||||
|
|
@ -111,12 +111,12 @@ public class ItemUnlockManager : ISavable, IDisposable
|
||||||
scan |= newArmoireState;
|
scan |= newArmoireState;
|
||||||
}
|
}
|
||||||
|
|
||||||
//var newAchievementState = uiState->Achievement.IsAchievementLoaded();
|
var newAchievementState = uiState->Achievement.IsLoaded();
|
||||||
//if (newAchievementState != _lastAchievementState)
|
if (newAchievementState != _lastAchievementState)
|
||||||
//{
|
{
|
||||||
// _lastAchievementState = newAchievementState;
|
_lastAchievementState = newAchievementState;
|
||||||
// scan |= newAchievementState;
|
scan |= newAchievementState;
|
||||||
//}
|
}
|
||||||
|
|
||||||
if (scan)
|
if (scan)
|
||||||
Scan();
|
Scan();
|
||||||
|
|
|
||||||
55
Glamourer/Utility/CompressExtensions.cs
Normal file
55
Glamourer/Utility/CompressExtensions.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
|
namespace Glamourer.Utility;
|
||||||
|
|
||||||
|
public static class CompressExtensions
|
||||||
|
{
|
||||||
|
/// <summary> Compress a byte array with a prepended version. </summary>
|
||||||
|
public static unsafe byte[] Compress(this byte[] data, byte version)
|
||||||
|
{
|
||||||
|
using var compressedStream = new MemoryStream();
|
||||||
|
using var zipStream = new GZipStream(compressedStream, CompressionMode.Compress);
|
||||||
|
zipStream.Write(data, 0, data.Length);
|
||||||
|
zipStream.Flush();
|
||||||
|
|
||||||
|
var ret = new byte[compressedStream.Length + 1];
|
||||||
|
ret[0] = version;
|
||||||
|
fixed (byte* ptr1 = compressedStream.GetBuffer(), ptr2 = ret)
|
||||||
|
{
|
||||||
|
MemoryUtility.MemCpyUnchecked(ptr2 + 1, ptr1, (int)compressedStream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Compress a string with a prepended version. </summary>
|
||||||
|
public static byte[] Compress(this string data, byte version)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(data);
|
||||||
|
return bytes.Compress(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Decompress a byte array into a returned version byte and an array of the remaining bytes. </summary>
|
||||||
|
public static byte Decompress(this byte[] compressed, out byte[] decompressed)
|
||||||
|
{
|
||||||
|
var ret = compressed[0];
|
||||||
|
using var compressedStream = new MemoryStream(compressed, 1, compressed.Length - 1);
|
||||||
|
using var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress);
|
||||||
|
using var resultStream = new MemoryStream();
|
||||||
|
zipStream.CopyTo(resultStream);
|
||||||
|
decompressed = resultStream.ToArray();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Decompress a byte array into a returned version byte and a string of the remaining bytes as UTF8. </summary>
|
||||||
|
public static byte DecompressToString(this byte[] compressed, out string decompressed)
|
||||||
|
{
|
||||||
|
var ret = compressed.Decompress(out var bytes);
|
||||||
|
decompressed = Encoding.UTF8.GetString(bytes);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue