mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +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)
|
||||
=> _options!.GetIcon(iconId);
|
||||
|
||||
public void RemoveIcon(uint iconId)
|
||||
=> _options!.RemoveIcon(iconId);
|
||||
|
||||
public string GetName(CustomName name)
|
||||
=> _options!.GetName(name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,9 +38,6 @@ public partial class CustomizationOptions
|
|||
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
||||
=> _icons.LoadIcon(id);
|
||||
|
||||
internal void RemoveIcon(uint id)
|
||||
=> _icons.RemoveIcon(id);
|
||||
|
||||
private readonly IconStorage _icons;
|
||||
|
||||
private static readonly int ListSize = Clans.Length * Genders.Length;
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ public class CustomizationSet
|
|||
_ => index switch
|
||||
{
|
||||
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.EyeColorRight => EyeColors.Count,
|
||||
CustomizeIndex.HairColor => HairColors.Count,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ public enum CustomizeFlag : ulong
|
|||
public static class CustomizeFlagExtensions
|
||||
{
|
||||
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 static bool RequiresRedraw(this CustomizeFlag flags)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,5 @@ public interface ICustomizationManager
|
|||
public CustomizationSet GetList(SubRace race, Gender gender);
|
||||
|
||||
public ImGuiScene.TextureWrap GetIcon(uint iconId);
|
||||
public void RemoveIcon(uint iconId);
|
||||
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.Plugin;
|
||||
using Glamourer.Designs;
|
||||
|
|
@ -45,24 +44,24 @@ public partial class GlamourerIpc
|
|||
|
||||
|
||||
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)
|
||||
=> ApplyDesign(CreateTemporaryFromBase64(base64, true, true), FindActors(character));
|
||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, true), FindActors(character));
|
||||
|
||||
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)
|
||||
=> ApplyDesign(CreateTemporaryFromBase64(base64, false, true), FindActors(character));
|
||||
=> ApplyDesign(_designConverter.FromBase64(base64, false, true), FindActors(character));
|
||||
|
||||
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)
|
||||
=> 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)
|
||||
return;
|
||||
|
|
@ -80,33 +79,4 @@ public partial class GlamourerIpc
|
|||
_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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
|
|
@ -17,17 +18,21 @@ public partial class GlamourerIpc : IDisposable
|
|||
public const int CurrentApiVersionMajor = 0;
|
||||
public const int CurrentApiVersionMinor = 1;
|
||||
|
||||
private readonly StateManager _stateManager;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly ActorService _actors;
|
||||
private readonly ItemManager _items;
|
||||
private readonly StateManager _stateManager;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly ActorService _actors;
|
||||
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;
|
||||
_objects = objects;
|
||||
_actors = actors;
|
||||
_items = items;
|
||||
_designConverter = designConverter;
|
||||
_apiVersionProvider = new FuncProvider<int>(pi, LabelApiVersion, ApiVersion);
|
||||
_apiVersionsProvider = new FuncProvider<(int Major, int Minor)>(pi, LabelApiVersions, ApiVersions);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public class AutoDesign
|
|||
All = Armor | Accessories | Customizations | Weapons | Stains,
|
||||
}
|
||||
|
||||
public Design Design;
|
||||
public Design Design = null!;
|
||||
public JobGroup Jobs;
|
||||
public Type ApplicationType;
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,12 @@ public class AutoDesignApplier : IDisposable
|
|||
private readonly CustomizationService _customizations;
|
||||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||
private readonly ItemUnlockManager _itemUnlocks;
|
||||
private readonly AutomationChanged _event;
|
||||
private readonly ObjectManager _objects;
|
||||
|
||||
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;
|
||||
_manager = manager;
|
||||
|
|
@ -38,14 +41,59 @@ public class AutoDesignApplier : IDisposable
|
|||
_actors = actors;
|
||||
_itemUnlocks = itemUnlocks;
|
||||
_customizeUnlocks = customizeUnlocks;
|
||||
_event = @event;
|
||||
_objects = objects;
|
||||
_jobs.JobChanged += OnJobChange;
|
||||
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_event.Unsubscribe(OnAutomationChange);
|
||||
_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 _)
|
||||
{
|
||||
if (!_config.EnableAutoDesigns || !actor.Identifier(_actors.AwaitedService, out var id))
|
||||
|
|
@ -242,28 +290,28 @@ public class AutoDesignApplier : IDisposable
|
|||
{
|
||||
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);
|
||||
totalMetaFlags |= 0x01;
|
||||
}
|
||||
|
||||
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);
|
||||
totalMetaFlags |= 0x02;
|
||||
}
|
||||
|
||||
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);
|
||||
totalMetaFlags |= 0x04;
|
||||
}
|
||||
|
||||
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);
|
||||
totalMetaFlags |= 0x08;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
|
|||
private readonly DesignManager _designs;
|
||||
private readonly ActorService _actors;
|
||||
private readonly AutomationChanged _event;
|
||||
private readonly ItemUnlockManager _unlockManager;
|
||||
private readonly ItemUnlockManager _unlockManager;
|
||||
|
||||
private readonly List<AutoDesignSet> _data = new();
|
||||
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = new();
|
||||
|
|
|
|||
|
|
@ -1,279 +1,95 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class Design : ISavable
|
||||
public sealed class Design : DesignBase, ISavable
|
||||
{
|
||||
#region Data
|
||||
|
||||
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
|
||||
public const int FileVersion = 1;
|
||||
public new const int FileVersion = 1;
|
||||
|
||||
public Guid Identifier { get; internal init; }
|
||||
public DateTimeOffset CreationDate { get; internal init; }
|
||||
public DateTimeOffset LastEdit { get; internal set; }
|
||||
public LowerString Name { get; internal set; } = LowerString.Empty;
|
||||
public string Description { get; internal set; } = string.Empty;
|
||||
public string[] Tags { get; internal set; } = Array.Empty<string>();
|
||||
public int Index { get; internal set; }
|
||||
|
||||
internal DesignData DesignData;
|
||||
public Guid Identifier { get; internal init; }
|
||||
public DateTimeOffset CreationDate { get; internal init; }
|
||||
public DateTimeOffset LastEdit { get; internal set; }
|
||||
public LowerString Name { get; internal set; } = LowerString.Empty;
|
||||
public string Description { get; internal set; } = string.Empty;
|
||||
public string[] Tags { get; internal set; } = Array.Empty<string>();
|
||||
public int Index { get; internal set; }
|
||||
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = new();
|
||||
|
||||
public string Incognito
|
||||
=> 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
|
||||
|
||||
#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
|
||||
|
||||
private 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()
|
||||
public new JObject JsonSerialize()
|
||||
{
|
||||
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),
|
||||
};
|
||||
}
|
||||
["FileVersion"] = FileVersion,
|
||||
["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(),
|
||||
["Apply"] = DoApplyWetness(),
|
||||
};
|
||||
var obj = new JObject()
|
||||
{
|
||||
["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;
|
||||
}
|
||||
|
|
@ -287,8 +103,8 @@ public class Design : ISavable
|
|||
var version = json["FileVersion"]?.ToObject<int>() ?? 0;
|
||||
return version switch
|
||||
{
|
||||
1 => LoadDesignV1(customizations, items, json),
|
||||
_ => throw new Exception("The design to be loaded has no valid Version."),
|
||||
FileVersion => LoadDesignV1(customizations, items, json),
|
||||
_ => throw new Exception("The design to be loaded has no valid Version."),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -314,128 +130,37 @@ public class Design : ISavable
|
|||
if (design.LastEdit < creationDate)
|
||||
design.LastEdit = creationDate;
|
||||
|
||||
LoadEquip(items, json["Equipment"], design);
|
||||
LoadCustomize(customizations, json["Customize"], design);
|
||||
LoadEquip(items, json["Equipment"], design, design.Name);
|
||||
LoadCustomize(customizations, json["Customize"], design, design.Name);
|
||||
LoadMods(json["Mods"], design);
|
||||
return design;
|
||||
}
|
||||
|
||||
private static void LoadEquip(ItemManager items, JToken? equip, Design design)
|
||||
private static void LoadMods(JToken? mods, Design design)
|
||||
{
|
||||
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);
|
||||
if (mods is not JArray array)
|
||||
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 stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0);
|
||||
var apply = item?["Apply"]?.ToObject<bool>() ?? false;
|
||||
var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false;
|
||||
return (id, stain, apply, applyStain);
|
||||
var name = tok["Name"]?.ToObject<string>();
|
||||
var directory = tok["Directory"]?.ToObject<string>();
|
||||
var enabled = tok["Enabled"]?.ToObject<bool>();
|
||||
if (name == null || directory == null || enabled == null)
|
||||
{
|
||||
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
|
||||
|
|
@ -459,39 +184,4 @@ public class Design : ISavable
|
|||
=> Path.GetFileNameWithoutExtension(fileName);
|
||||
|
||||
#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.Text.RegularExpressions;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
|
@ -101,26 +102,22 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
|||
switch (type)
|
||||
{
|
||||
case DesignChanged.Type.Created:
|
||||
var originalName = design.Name.Text.FixName();
|
||||
var name = originalName;
|
||||
var counter = 1;
|
||||
while (Find(name, out _))
|
||||
name = $"{originalName} ({++counter})";
|
||||
|
||||
CreateLeaf(Root, name, design);
|
||||
break;
|
||||
CreateDuplicateLeaf(Root, design.Name.Text, design);
|
||||
return;
|
||||
case DesignChanged.Type.Deleted:
|
||||
if (FindLeaf(design, out var leaf))
|
||||
Delete(leaf);
|
||||
break;
|
||||
if (FindLeaf(design, out var leaf1))
|
||||
Delete(leaf1);
|
||||
return;
|
||||
case DesignChanged.Type.ReloadedAll:
|
||||
Reload();
|
||||
break;
|
||||
return;
|
||||
case DesignChanged.Type.Renamed when data is string oldName:
|
||||
if (!FindLeaf(design, out var leaf2))
|
||||
return;
|
||||
var old = oldName.FixName();
|
||||
if (Find(old, out var child) && child is not Folder)
|
||||
Rename(child, design.Name);
|
||||
break;
|
||||
if (old == leaf2.Name || leaf2.Name.IsDuplicateName(out var baseName, out _) && baseName == old)
|
||||
RenameWithDuplicates(leaf2, design.Name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ using System.Linq;
|
|||
using Dalamud.Utility;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
|
|
@ -78,16 +80,20 @@ public class DesignManager
|
|||
_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>
|
||||
public Design Create(string name)
|
||||
public Design CreateEmpty(string name)
|
||||
{
|
||||
var design = new Design(_items)
|
||||
{
|
||||
CreationDate = DateTimeOffset.UtcNow,
|
||||
LastEdit = DateTimeOffset.UtcNow,
|
||||
Identifier = CreateNewGuid(),
|
||||
Index = _designs.Count,
|
||||
Name = name,
|
||||
Index = _designs.Count,
|
||||
};
|
||||
_designs.Add(design);
|
||||
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
|
||||
|
|
@ -96,6 +102,43 @@ public class DesignManager
|
|||
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>
|
||||
public void Delete(Design design)
|
||||
{
|
||||
|
|
@ -181,6 +224,41 @@ public class DesignManager
|
|||
_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>
|
||||
public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value)
|
||||
{
|
||||
|
|
@ -210,6 +288,7 @@ public class DesignManager
|
|||
break;
|
||||
}
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}.");
|
||||
_saveService.QueueSave(design);
|
||||
_event.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx));
|
||||
|
|
@ -237,6 +316,7 @@ public class DesignManager
|
|||
if (!design.DesignData.SetItem(slot, item))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}).");
|
||||
_saveService.QueueSave(design);
|
||||
|
|
@ -257,7 +337,6 @@ public class DesignManager
|
|||
|
||||
if (item.Type != currentMain.Type)
|
||||
{
|
||||
|
||||
var newOffId = FullEquipTypeExtensions.OffhandTypes.Contains(item.Type)
|
||||
? item.Id
|
||||
: ItemManager.NothingId(item.Type.Offhand());
|
||||
|
|
@ -332,6 +411,87 @@ public class DesignManager
|
|||
_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()
|
||||
{
|
||||
if (!File.Exists(_saveService.FileNames.MigrationDesignFile))
|
||||
|
|
|
|||
|
|
@ -56,8 +56,11 @@ public sealed class AutomationChanged : EventWrapper<Action<AutomationChanged.Ty
|
|||
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Gui.Tabs.AutomationTab.SetSelector.OnAutomationChanged"/>
|
||||
/// <seealso cref="Gui.Tabs.AutomationTab.SetSelector.OnAutomationChange"/>
|
||||
SetSelector = 0,
|
||||
|
||||
/// <seealso cref="AutoDesignApplier.OnAutomationChange"/>
|
||||
AutoDesignApplier,
|
||||
}
|
||||
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public sealed class StateChanged : EventWrapper<Action<StateChanged.Type, StateC
|
|||
Game,
|
||||
Manual,
|
||||
Fixed,
|
||||
Ipc,
|
||||
}
|
||||
|
||||
public enum Priority
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ public class Glamourer : IDalamudPlugin
|
|||
{
|
||||
_services = ServiceManager.CreateProvider(pluginInterface, Log);
|
||||
Chat = _services.GetRequiredService<ChatService>();
|
||||
_services.GetRequiredService<BackupService>(); // call backup service.
|
||||
_services.GetRequiredService<GlamourerWindowSystem>(); // initialize ui.
|
||||
_services.GetRequiredService<CommandService>(); // initialize commands.
|
||||
_services.GetRequiredService<VisorService>();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ public enum ColorId
|
|||
DisabledAutoSet,
|
||||
AutomationActorAvailable,
|
||||
AutomationActorUnavailable,
|
||||
HeaderButtons,
|
||||
}
|
||||
|
||||
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.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.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ),
|
||||
_ => (0x00000000, string.Empty, string.Empty ),
|
||||
// @formatter:on
|
||||
};
|
||||
|
|
|
|||
|
|
@ -121,17 +121,20 @@ public partial class CustomizationDrawer
|
|||
PercentageInputInt();
|
||||
|
||||
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()
|
||||
{
|
||||
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
|
||||
using var _ = ImRaii.Group();
|
||||
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
|
||||
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())
|
||||
{
|
||||
using var id = SetId(featureIdx);
|
||||
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
|
||||
? _legacyTattoo ?? _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 Glamourer.Events;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.ActorTab;
|
||||
|
||||
|
|
@ -19,6 +26,8 @@ public class ActorPanel
|
|||
private readonly StateManager _stateManager;
|
||||
private readonly CustomizationDrawer _customizationDrawer;
|
||||
private readonly EquipmentDrawer _equipmentDrawer;
|
||||
private readonly HumanModelList _humans;
|
||||
private readonly IdentifierService _identification;
|
||||
|
||||
private ActorIdentifier _identifier;
|
||||
private string _actorName = string.Empty;
|
||||
|
|
@ -27,12 +36,14 @@ public class ActorPanel
|
|||
private ActorState? _state;
|
||||
|
||||
public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer,
|
||||
EquipmentDrawer equipmentDrawer)
|
||||
EquipmentDrawer equipmentDrawer, HumanModelList humans, IdentifierService identification)
|
||||
{
|
||||
_selector = selector;
|
||||
_stateManager = stateManager;
|
||||
_customizationDrawer = customizationDrawer;
|
||||
_equipmentDrawer = equipmentDrawer;
|
||||
_humans = humans;
|
||||
_identification = identification;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
|
|
@ -47,15 +58,16 @@ public class ActorPanel
|
|||
private void DrawHeader()
|
||||
{
|
||||
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);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
ImGuiUtil.DrawTextButton($"{_actorName}##playerHeader", new Vector2(-frameHeight, ImGui.GetFrameHeight()), buttonColor, color);
|
||||
ImGui.SameLine();
|
||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
using (var c = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value())
|
||||
.Push(ImGuiCol.Border, ColorId.FolderExpanded.Value()))
|
||||
using (var c = ImRaii.PushColor(ImGuiCol.Text, ColorId.HeaderButtons.Value())
|
||||
.Push(ImGuiCol.Border, ColorId.HeaderButtons.Value()))
|
||||
{
|
||||
if (ImGuiUtil.DrawDisabledButton(
|
||||
$"{(_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);
|
||||
}
|
||||
|
||||
private unsafe void DrawPanel()
|
||||
private void DrawHumanPanel()
|
||||
{
|
||||
using var child = ImRaii.Child("##Panel", -Vector2.One, true);
|
||||
if (!child || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state))
|
||||
return;
|
||||
|
||||
if (_customizationDrawer.Draw(_state.ModelData.Customize, false))
|
||||
if (_customizationDrawer.Draw(_state!.ModelData.Customize, false))
|
||||
_stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual);
|
||||
|
||||
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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -99,9 +99,10 @@ public class ActorSelector
|
|||
_identifier = _objects.Player.GetIdentifier(_actors.AwaitedService);
|
||||
|
||||
ImGui.SameLine();
|
||||
Actor targetActor = _targets.Target?.Address;
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth,
|
||||
"Select the current target, if it is in the list.", _objects.IsInGPose || !targetActor, true))
|
||||
_identifier = targetActor.GetIdentifier(_actors.AwaitedService);
|
||||
var (id, data) = _objects.TargetData;
|
||||
var tt = data.Valid ? $"Select the current target {id} in the list." :
|
||||
id.IsValid ? $"The target {id} is not in the list." : "No target selected.";
|
||||
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);
|
||||
ImGui.SameLine();
|
||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value())
|
||||
.Push(ImGuiCol.Border, ColorId.FolderExpanded.Value());
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.HeaderButtons.Value())
|
||||
.Push(ImGuiCol.Border, ColorId.HeaderButtons.Value());
|
||||
if (ImGuiUtil.DrawDisabledButton(
|
||||
$"{(_selector.IncognitoMode ? FontAwesomeIcon.Eye : FontAwesomeIcon.EyeSlash).ToIconString()}###IncognitoMode",
|
||||
new Vector2(frameHeight, ImGui.GetFrameHeight()), string.Empty, false, true))
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ public class SetSelector : IDisposable
|
|||
_config = config;
|
||||
_actors = actors;
|
||||
_objects = objects;
|
||||
_event.Subscribe(OnAutomationChanged, AutomationChanged.Priority.SetSelector);
|
||||
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.SetSelector);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_event.Unsubscribe(OnAutomationChanged);
|
||||
_event.Unsubscribe(OnAutomationChange);
|
||||
}
|
||||
|
||||
public string SelectionName
|
||||
|
|
@ -60,7 +60,7 @@ public class SetSelector : IDisposable
|
|||
public string GetSetName(AutoDesignSet? set, int index)
|
||||
=> 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface;
|
||||
|
|
@ -19,7 +20,9 @@ using Glamourer.Interop.Structs;
|
|||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Unlocks;
|
||||
using Glamourer.Utility;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
|
|
@ -42,7 +45,7 @@ public unsafe class DebugTab : ITab
|
|||
private readonly ObjectTable _objects;
|
||||
private readonly ObjectManager _objectManager;
|
||||
private readonly GlamourerIpc _ipc;
|
||||
private readonly CodeService _code;
|
||||
private readonly CodeService _code;
|
||||
|
||||
private readonly ItemManager _items;
|
||||
private readonly ActorService _actors;
|
||||
|
|
@ -54,6 +57,7 @@ public unsafe class DebugTab : ITab
|
|||
private readonly DesignManager _designManager;
|
||||
private readonly DesignFileSystem _designFileSystem;
|
||||
private readonly AutoDesignManager _autoDesignManager;
|
||||
private readonly DesignConverter _designConverter;
|
||||
|
||||
private readonly PenumbraChangedItemTooltip _penumbraTooltip;
|
||||
|
||||
|
|
@ -70,7 +74,7 @@ public unsafe class DebugTab : ITab
|
|||
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
|
||||
PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface,
|
||||
AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks,
|
||||
ItemUnlockManager itemUnlocks)
|
||||
ItemUnlockManager itemUnlocks, DesignConverter designConverter)
|
||||
{
|
||||
_changeCustomizeService = changeCustomizeService;
|
||||
_visorService = visorService;
|
||||
|
|
@ -92,9 +96,10 @@ public unsafe class DebugTab : ITab
|
|||
_pluginInterface = pluginInterface;
|
||||
_autoDesignManager = autoDesignManager;
|
||||
_jobs = jobs;
|
||||
_code = code;
|
||||
_code = code;
|
||||
_customizeUnlocks = customizeUnlocks;
|
||||
_itemUnlocks = itemUnlocks;
|
||||
_designConverter = designConverter;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
@ -817,6 +822,7 @@ public unsafe class DebugTab : ITab
|
|||
|
||||
DrawDesignManager();
|
||||
DrawDesignTester();
|
||||
DrawDesignConverter();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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})";
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
|
||||
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();
|
||||
PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(),
|
||||
state[ActorState.MetaFlag.VisorState]);
|
||||
state[ActorState.MetaIndex.VisorState]);
|
||||
ImGui.TableNextRow();
|
||||
PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(),
|
||||
state[ActorState.MetaFlag.WeaponState]);
|
||||
state[ActorState.MetaIndex.WeaponState]);
|
||||
ImGui.TableNextRow();
|
||||
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);
|
||||
ImGuiUtil.DrawTableColumn("Name");
|
||||
ImGuiUtil.DrawTableColumn(design.Name);
|
||||
ImGuiUtil.DrawTableColumn($"({design.Index})");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted("Description (Hover)");
|
||||
ImGuiUtil.HoverTooltip(design.Description);
|
||||
ImGui.TableNextRow();
|
||||
if (design is Design d)
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn("Name");
|
||||
ImGuiUtil.DrawTableColumn(d.Name);
|
||||
ImGuiUtil.DrawTableColumn($"({d.Index})");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted("Description (Hover)");
|
||||
ImGuiUtil.HoverTooltip(d.Description);
|
||||
ImGui.TableNextRow();
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Identifier");
|
||||
ImGuiUtil.DrawTableColumn(design.Identifier.ToString());
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn("Design File System Path");
|
||||
ImGuiUtil.DrawTableColumn(_designFileSystem.FindLeaf(design, out var leaf) ? leaf.FullName() : "No Path Known");
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn("Identifier");
|
||||
ImGuiUtil.DrawTableColumn(d.Identifier.ToString());
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn("Design File System Path");
|
||||
ImGuiUtil.DrawTableColumn(_designFileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : "No Path Known");
|
||||
ImGui.TableNextRow();
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Creation");
|
||||
ImGuiUtil.DrawTableColumn(design.CreationDate.ToString());
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn("Update");
|
||||
ImGuiUtil.DrawTableColumn(design.LastEdit.ToString());
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn("Tags");
|
||||
ImGuiUtil.DrawTableColumn(string.Join(", ", design.Tags));
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn("Creation");
|
||||
ImGuiUtil.DrawTableColumn(d.CreationDate.ToString());
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn("Update");
|
||||
ImGuiUtil.DrawTableColumn(d.LastEdit.ToString());
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn("Tags");
|
||||
ImGuiUtil.DrawTableColumn(string.Join(", ", d.Tags));
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
|
||||
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)))
|
||||
{
|
||||
using var t = ImRaii.TreeNode(identifier.ToString());
|
||||
if (!t)
|
||||
return;
|
||||
|
||||
DrawState(ActorData.Invalid, state);
|
||||
if (t)
|
||||
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.Interface;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using ImGuiNET;
|
||||
|
|
@ -13,9 +16,14 @@ namespace Glamourer.Gui.Tabs.DesignTab;
|
|||
|
||||
public sealed class DesignFileSystemSelector : FileSystemSelector<Design, DesignFileSystemSelector.DesignState>
|
||||
{
|
||||
private readonly DesignManager _designManager;
|
||||
private readonly DesignChanged _event;
|
||||
private readonly Configuration _config;
|
||||
private readonly DesignManager _designManager;
|
||||
private readonly DesignChanged _event;
|
||||
private readonly Configuration _config;
|
||||
private readonly DesignConverter _converter;
|
||||
|
||||
private string? _clipboardText;
|
||||
private Design? _cloneDesign = null;
|
||||
private string _newName = string.Empty;
|
||||
|
||||
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 DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, KeyState keyState, DesignChanged @event,
|
||||
Configuration config)
|
||||
Configuration config, DesignConverter converter)
|
||||
: base(fileSystem, keyState)
|
||||
{
|
||||
_designManager = designManager;
|
||||
_event = @event;
|
||||
_config = config;
|
||||
_converter = converter;
|
||||
_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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -78,11 +99,51 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
|||
case DesignChanged.Type.AddedTag:
|
||||
case DesignChanged.Type.ChangedTag:
|
||||
case DesignChanged.Type.RemovedTag:
|
||||
case DesignChanged.Type.AddedMod:
|
||||
case DesignChanged.Type.RemovedMod:
|
||||
case DesignChanged.Type.Created:
|
||||
case DesignChanged.Type.Deleted:
|
||||
SetFilterDirty();
|
||||
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)
|
||||
{
|
||||
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"
|
||||
+ "This can not be undone.";
|
||||
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)
|
||||
&& Selected != null)
|
||||
_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.Internal.Notifications;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
|
@ -20,45 +29,247 @@ public class DesignPanel
|
|||
private readonly CustomizationDrawer _customizationDrawer;
|
||||
private readonly StateManager _state;
|
||||
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,
|
||||
StateManager state, EquipmentDrawer equipmentDrawer)
|
||||
StateManager state, EquipmentDrawer equipmentDrawer, CustomizationService customizationService, PenumbraService penumbra,
|
||||
ModAssociationsTab modAssociations, DesignDetailTab designDetails, DesignConverter converter)
|
||||
{
|
||||
_selector = selector;
|
||||
_customizationDrawer = customizationDrawer;
|
||||
_manager = manager;
|
||||
_objects = objects;
|
||||
_state = state;
|
||||
_equipmentDrawer = equipmentDrawer;
|
||||
_selector = selector;
|
||||
_customizationDrawer = customizationDrawer;
|
||||
_manager = manager;
|
||||
_objects = objects;
|
||||
_state = state;
|
||||
_equipmentDrawer = equipmentDrawer;
|
||||
_customizationService = customizationService;
|
||||
_modAssociations = modAssociations;
|
||||
_designDetails = designDetails;
|
||||
_converter = converter;
|
||||
}
|
||||
|
||||
private void DrawHeader()
|
||||
{
|
||||
var selection = _selector.Selected;
|
||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||
var frameHeight = ImGui.GetFrameHeightWithSpacing();
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.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();
|
||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value())
|
||||
.Push(ImGuiCol.Border, ColorId.FolderExpanded.Value());
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.HeaderButtons.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(
|
||||
$"{(_selector.IncognitoMode ? FontAwesomeIcon.Eye : FontAwesomeIcon.EyeSlash).ToIconString()}###IncognitoMode",
|
||||
new Vector2(frameHeight, ImGui.GetFrameHeight()), string.Empty, false, true))
|
||||
_selector.IncognitoMode = !_selector.IncognitoMode;
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
color.Pop(2);
|
||||
if (hovered)
|
||||
ImGui.SetTooltip(_selector.IncognitoMode ? "Toggle incognito mode off." : "Toggle incognito mode on.");
|
||||
if (ImGui.IsItemHovered())
|
||||
hoverText = _selector.IncognitoMode ? "Toggle incognito mode off." : "Toggle incognito mode on.";
|
||||
|
||||
if (hoverText.Length > 0)
|
||||
ImGui.SetTooltip(hoverText);
|
||||
}
|
||||
|
||||
private string SelectionName
|
||||
=> _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()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
using var group = ImRaii.Group();
|
||||
DrawHeader();
|
||||
|
||||
var design = _selector.Selected;
|
||||
|
|
@ -66,47 +277,86 @@ public class DesignPanel
|
|||
if (!child || design == null)
|
||||
return;
|
||||
|
||||
if (ImGui.Button("TEST"))
|
||||
{
|
||||
var (id, data) = _objects.PlayerData;
|
||||
|
||||
if (data.Valid && _state.GetOrCreate(id, data.Objects[0], out var state))
|
||||
_state.ApplyDesign(design, state);
|
||||
}
|
||||
|
||||
_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);
|
||||
DrawButtonRow();
|
||||
DrawMetaData();
|
||||
DrawCustomize();
|
||||
DrawEquipment();
|
||||
_designDetails.Draw();
|
||||
DrawApplicationRules();
|
||||
_modAssociations.Draw();
|
||||
}
|
||||
|
||||
private void DrawButtonRow()
|
||||
{
|
||||
DrawSetFromClipboard();
|
||||
ImGui.SameLine();
|
||||
var mh = design.DesignData.Item(EquipSlot.MainHand);
|
||||
if (_equipmentDrawer.DrawMainhand(mh, true, out var newMh))
|
||||
_manager.ChangeWeapon(design, EquipSlot.MainHand, newMh);
|
||||
DrawExportToClipboard();
|
||||
ImGui.SameLine();
|
||||
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);
|
||||
if (_equipmentDrawer.DrawStain(ohStain, EquipSlot.OffHand, out var newOhStain))
|
||||
_manager.ChangeStain(design, EquipSlot.OffHand, newOhStain.RowIndex);
|
||||
|
||||
ImGui.SameLine();
|
||||
var oh = design.DesignData.Item(EquipSlot.OffHand);
|
||||
if (_equipmentDrawer.DrawMainhand(oh, false, out var newOh))
|
||||
_manager.ChangeWeapon(design, EquipSlot.OffHand, newOh);
|
||||
var text = ImGui.GetClipboardText();
|
||||
var design = _converter.FromBase64(text, true, true) ?? throw new Exception("The clipboard did not contain valid data.");
|
||||
_manager.ApplyDesign(_selector.Selected!, design);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Chat.NotificationMessage(ex, $"Could not apply clipboard to {_selector.Selected!.Name}.",
|
||||
$"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))
|
||||
{
|
||||
ClearIcons(_selected1);
|
||||
_selected1 = type;
|
||||
_selected2 = SubRace.Unknown;
|
||||
_selected3 = Gender.Unknown;
|
||||
|
|
@ -59,7 +58,6 @@ public class UnlockOverview
|
|||
if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint",
|
||||
_selected2 == clan && _selected3 == gender))
|
||||
{
|
||||
ClearIcons(_selected1);
|
||||
_selected1 = FullEquipType.Unknown;
|
||||
_selected2 = clan;
|
||||
_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,
|
||||
CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureCache textureCache)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@ public unsafe class MetaService : IDisposable
|
|||
if (!actor.IsCharacter)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
|
|
@ -13,17 +12,19 @@ namespace Glamourer.Interop;
|
|||
|
||||
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||
{
|
||||
private readonly Framework _framework;
|
||||
private readonly ClientState _clientState;
|
||||
private readonly ObjectTable _objects;
|
||||
private readonly ActorService _actors;
|
||||
private readonly Framework _framework;
|
||||
private readonly ClientState _clientState;
|
||||
private readonly ObjectTable _objects;
|
||||
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;
|
||||
_clientState = clientState;
|
||||
_objects = objects;
|
||||
_actors = actors;
|
||||
_targets = targets;
|
||||
}
|
||||
|
||||
public DateTime LastUpdate { get; private set; }
|
||||
|
|
@ -31,7 +32,8 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
public bool IsInGPose { 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
|
||||
=> _identifiers;
|
||||
|
|
@ -45,6 +47,7 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
LastUpdate = lastUpdate;
|
||||
World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
|
||||
_identifiers.Clear();
|
||||
_allWorldIdentifiers.Clear();
|
||||
|
||||
for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i)
|
||||
{
|
||||
|
|
@ -106,6 +109,23 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
{
|
||||
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
|
||||
|
|
@ -114,6 +134,9 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
public Actor Player
|
||||
=> _objects.GetObjectAddress(0);
|
||||
|
||||
public Actor Target
|
||||
=> _targets.Target?.Address ?? nint.Zero;
|
||||
|
||||
public (ActorIdentifier Identifier, ActorData Data) PlayerData
|
||||
{
|
||||
get
|
||||
|
|
@ -121,7 +144,18 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
Update();
|
||||
return Player.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var 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
|
||||
=> Identifiers.Count;
|
||||
|
||||
/// <summary> Also (inefficiently) handles All Worlds players. </summary>
|
||||
/// <summary> Also handles All Worlds players. </summary>
|
||||
public bool ContainsKey(ActorIdentifier key)
|
||||
=> Identifiers.ContainsKey(key)
|
||||
|| key.HomeWorld == ushort.MaxValue
|
||||
&& Identifiers.Keys.FirstOrDefault(i => i.Type is IdentifierType.Player && i.PlayerName == key.PlayerName).IsValid;
|
||||
=> Identifiers.ContainsKey(key) || _allWorldIdentifiers.ContainsKey(key);
|
||||
|
||||
public bool TryGetValue(ActorIdentifier key, out ActorData value)
|
||||
=> Identifiers.TryGetValue(key, out value);
|
||||
|
||||
public bool TryGetValueAllWorld(ActorIdentifier key, out ActorData value)
|
||||
=> _allWorldIdentifiers.TryGetValue(key, out value);
|
||||
|
||||
public ActorData this[ActorIdentifier key]
|
||||
=> Identifiers[key];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Interop.Structs;
|
||||
|
|
@ -8,21 +12,52 @@ using Penumbra.Api.Helpers;
|
|||
|
||||
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 const int RequiredPenumbraBreakingVersion = 4;
|
||||
public const int RequiredPenumbraFeatureVersion = 15;
|
||||
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||
private readonly EventSubscriber<ModSettingChange, string, string, bool> _modSettingChanged;
|
||||
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||
private FuncSubscriber<int, int> _cutsceneParent;
|
||||
private FuncSubscriber<int, (bool, bool, string)> _objectCollection;
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||
private readonly EventSubscriber<ModSettingChange, string, string, bool> _modSettingChanged;
|
||||
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||
private FuncSubscriber<int, int> _cutsceneParent;
|
||||
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 _disposedEvent;
|
||||
|
|
@ -72,6 +107,90 @@ public unsafe class PenumbraService : IDisposable
|
|||
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>
|
||||
public string GetCurrentPlayerCollection()
|
||||
{
|
||||
|
|
@ -123,11 +242,19 @@ public unsafe class PenumbraService : IDisposable
|
|||
_creatingCharacterBase.Enable();
|
||||
_createdCharacterBase.Enable();
|
||||
_modSettingChanged.Enable();
|
||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
|
||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
|
||||
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
|
||||
_objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface);
|
||||
Available = true;
|
||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
|
||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
|
||||
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
|
||||
_objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface);
|
||||
_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.");
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
|||
|
|
@ -7,12 +7,22 @@ namespace Glamourer.Services;
|
|||
|
||||
public class BackupService
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly DirectoryInfo _configDirectory;
|
||||
private readonly IReadOnlyList<FileInfo> _fileNames;
|
||||
|
||||
public BackupService(Logger logger, FilenameService fileNames)
|
||||
{
|
||||
var files = GlamourerFiles(fileNames);
|
||||
Backup.CreateBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), files);
|
||||
_logger = logger;
|
||||
_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>
|
||||
private static IReadOnlyList<FileInfo> GlamourerFiles(FilenameService fileNames)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,14 +10,16 @@ public class ConfigMigrationService
|
|||
{
|
||||
private readonly SaveService _saveService;
|
||||
private readonly FixedDesignMigrator _fixedDesignMigrator;
|
||||
private readonly BackupService _backupService;
|
||||
|
||||
private Configuration _config = null!;
|
||||
private JObject _data = null!;
|
||||
|
||||
public ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator)
|
||||
public ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator, BackupService backupService)
|
||||
{
|
||||
_saveService = saveService;
|
||||
_fixedDesignMigrator = fixedDesignMigrator;
|
||||
_backupService = backupService;
|
||||
}
|
||||
|
||||
public void Migrate(Configuration config)
|
||||
|
|
@ -39,6 +41,7 @@ public class ConfigMigrationService
|
|||
if (_config.Version > 1)
|
||||
return;
|
||||
|
||||
_backupService.CreateMigrationBackup("pre_v1_to_v2_migration");
|
||||
_fixedDesignMigrator.Migrate(_data["FixedDesigns"]);
|
||||
_config.Version = 2;
|
||||
var customizationColor = _data["CustomizationColor"]?.ToObject<uint>() ?? ColorId.CustomizationDesign.Data().DefaultColor;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ using Glamourer.Unlocks;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
using Penumbra.GameData.Data;
|
||||
|
||||
namespace Glamourer.Services;
|
||||
|
||||
|
|
@ -73,7 +74,8 @@ public static class ServiceManager
|
|||
.AddSingleton<ItemService>()
|
||||
.AddSingleton<ActorService>()
|
||||
.AddSingleton<CustomizationService>()
|
||||
.AddSingleton<ItemManager>();
|
||||
.AddSingleton<ItemManager>()
|
||||
.AddSingleton<HumanModelList>();
|
||||
|
||||
private static IServiceCollection AddInterop(this IServiceCollection services)
|
||||
=> services.AddSingleton<VisorService>()
|
||||
|
|
@ -93,7 +95,8 @@ public static class ServiceManager
|
|||
.AddSingleton<DesignFileSystem>()
|
||||
.AddSingleton<AutoDesignManager>()
|
||||
.AddSingleton<AutoDesignApplier>()
|
||||
.AddSingleton<FixedDesignMigrator>();
|
||||
.AddSingleton<FixedDesignMigrator>()
|
||||
.AddSingleton<DesignConverter>();
|
||||
|
||||
private static IServiceCollection AddState(this IServiceCollection services)
|
||||
=> services.AddSingleton<StateManager>()
|
||||
|
|
@ -114,6 +117,8 @@ public static class ServiceManager
|
|||
.AddSingleton<DesignFileSystemSelector>()
|
||||
.AddSingleton<DesignPanel>()
|
||||
.AddSingleton<DesignTab>()
|
||||
.AddSingleton<ModAssociationsTab>()
|
||||
.AddSingleton<DesignDetailTab>()
|
||||
.AddSingleton<UnlockTable>()
|
||||
.AddSingleton<UnlockOverview>()
|
||||
.AddSingleton<UnlocksTab>()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace Glamourer.State;
|
|||
|
||||
public class ActorState
|
||||
{
|
||||
public enum MetaFlag
|
||||
public enum MetaIndex
|
||||
{
|
||||
Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices,
|
||||
HatState,
|
||||
|
|
@ -45,6 +45,6 @@ public class ActorState
|
|||
public ref StateChanged.Source this[CustomizeIndex type]
|
||||
=> ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type];
|
||||
|
||||
public ref StateChanged.Source this[MetaFlag flag]
|
||||
=> ref _sources[(int)flag];
|
||||
public ref StateChanged.Source this[MetaIndex index]
|
||||
=> ref _sources[(int)index];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,16 +102,19 @@ public class StateListener : IDisposable
|
|||
var actor = (Actor)actorPtr;
|
||||
var identifier = actor.GetIdentifier(_actors.AwaitedService);
|
||||
|
||||
var modelId = *(uint*)modelPtr;
|
||||
ref var modelId = ref *(uint*)modelPtr;
|
||||
ref var customize = ref *(Customize*)customizePtr;
|
||||
if (_manager.TryGetValue(identifier, out var state))
|
||||
{
|
||||
_autoDesignApplier.Reduce(actor, identifier, state);
|
||||
switch (UpdateBaseData(actor, state, modelId, customizePtr, equipDataPtr))
|
||||
{
|
||||
// TODO handle right
|
||||
case UpdateState.Change: break;
|
||||
case UpdateState.Transformed: break;
|
||||
case UpdateState.NoChange:
|
||||
|
||||
modelId = state.ModelData.ModelId;
|
||||
switch (UpdateBaseData(actor, state, customize))
|
||||
{
|
||||
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)
|
||||
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.
|
||||
case UpdateState.Transformed: break;
|
||||
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[slot, false] = StateChanged.Source.Game;
|
||||
|
|
@ -181,7 +184,7 @@ public class StateListener : IDisposable
|
|||
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[slot, true] = StateChanged.Source.Game;
|
||||
|
|
@ -246,7 +249,7 @@ public class StateListener : IDisposable
|
|||
// Update model state if not on fixed design.
|
||||
case UpdateState.Change:
|
||||
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[slot, false] = StateChanged.Source.Game;
|
||||
|
|
@ -256,7 +259,7 @@ public class StateListener : IDisposable
|
|||
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[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,
|
||||
// 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();
|
||||
else
|
||||
_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,
|
||||
// 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();
|
||||
else
|
||||
_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,
|
||||
// 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();
|
||||
else
|
||||
_manager.ChangeWeaponState(state, value, StateChanged.Source.Game);
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
{
|
||||
ModelData = FromActor(actor, true),
|
||||
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.
|
||||
_states.Add(state.Identifier, state);
|
||||
|
|
@ -192,6 +192,21 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
|
||||
#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>
|
||||
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;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
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
|
||||
{
|
||||
_editor.ChangeWeapon(objects, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot));
|
||||
}
|
||||
|
||||
// Meta.
|
||||
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;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
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
|
||||
{
|
||||
_editor.ChangeWeapon(objects, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot));
|
||||
}
|
||||
|
||||
// Meta.
|
||||
Glamourer.Log.Verbose(
|
||||
|
|
@ -322,7 +347,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
// Update state data.
|
||||
var old = state.ModelData.IsHatVisible();
|
||||
state.ModelData.SetHatVisible(value);
|
||||
state[ActorState.MetaFlag.HatState] = source;
|
||||
state[ActorState.MetaIndex.HatState] = source;
|
||||
|
||||
// Update draw objects / game objects.
|
||||
_objects.Update();
|
||||
|
|
@ -333,7 +358,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
// Meta.
|
||||
Glamourer.Log.Verbose(
|
||||
$"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>
|
||||
|
|
@ -342,7 +367,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
// Update state data.
|
||||
var old = state.ModelData.IsWeaponVisible();
|
||||
state.ModelData.SetWeaponVisible(value);
|
||||
state[ActorState.MetaFlag.WeaponState] = source;
|
||||
state[ActorState.MetaIndex.WeaponState] = source;
|
||||
|
||||
// Update draw objects / game objects.
|
||||
_objects.Update();
|
||||
|
|
@ -353,7 +378,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
// Meta.
|
||||
Glamourer.Log.Verbose(
|
||||
$"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>
|
||||
|
|
@ -362,7 +387,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
// Update state data.
|
||||
var old = state.ModelData.IsVisorToggled();
|
||||
state.ModelData.SetVisor(value);
|
||||
state[ActorState.MetaFlag.VisorState] = source;
|
||||
state[ActorState.MetaIndex.VisorState] = source;
|
||||
|
||||
// Update draw objects.
|
||||
_objects.Update();
|
||||
|
|
@ -373,7 +398,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
// Meta.
|
||||
Glamourer.Log.Verbose(
|
||||
$"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>
|
||||
|
|
@ -382,7 +407,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
// Update state data.
|
||||
var old = state.ModelData.IsWet();
|
||||
state.ModelData.SetIsWet(value);
|
||||
state[ActorState.MetaFlag.Wetness] = source;
|
||||
state[ActorState.MetaIndex.Wetness] = source;
|
||||
|
||||
// Update draw objects / game objects.
|
||||
_objects.Update();
|
||||
|
|
@ -392,12 +417,12 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
// Meta.
|
||||
Glamourer.Log.Verbose(
|
||||
$"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
|
||||
|
||||
public void ApplyDesign(Design design, ActorState state)
|
||||
public void ApplyDesign(DesignBase design, ActorState state)
|
||||
{
|
||||
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)
|
||||
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.DoApplyStain(EquipSlot.OffHand));
|
||||
|
||||
if (design.DoApplyHatVisible())
|
||||
ChangeHatState(state, design.DesignData.IsHatVisible(), StateChanged.Source.Manual);
|
||||
if (design.DoApplyWeaponVisible())
|
||||
ChangeWeaponState(state, design.DesignData.IsWeaponVisible(), StateChanged.Source.Manual);
|
||||
if (design.DoApplyVisorToggle())
|
||||
|
|
|
|||
|
|
@ -111,12 +111,12 @@ public class ItemUnlockManager : ISavable, IDisposable
|
|||
scan |= newArmoireState;
|
||||
}
|
||||
|
||||
//var newAchievementState = uiState->Achievement.IsAchievementLoaded();
|
||||
//if (newAchievementState != _lastAchievementState)
|
||||
//{
|
||||
// _lastAchievementState = newAchievementState;
|
||||
// scan |= newAchievementState;
|
||||
//}
|
||||
var newAchievementState = uiState->Achievement.IsLoaded();
|
||||
if (newAchievementState != _lastAchievementState)
|
||||
{
|
||||
_lastAchievementState = newAchievementState;
|
||||
scan |= newAchievementState;
|
||||
}
|
||||
|
||||
if (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