More stuff.

This commit is contained in:
Ottermandias 2022-11-06 18:28:20 +01:00
parent dad146d043
commit 1a4672a901
23 changed files with 616 additions and 1005 deletions

View file

@ -14,23 +14,23 @@ public class CustomizationSet
{ {
internal CustomizationSet(SubRace clan, Gender gender) internal CustomizationSet(SubRace clan, Gender gender)
{ {
Gender = gender; Gender = gender;
Clan = clan; Clan = clan;
Race = clan.ToRace(); Race = clan.ToRace();
_settingAvailable = 0; SettingAvailable = 0;
} }
public Gender Gender { get; } public Gender Gender { get; }
public SubRace Clan { get; } public SubRace Clan { get; }
public Race Race { get; } public Race Race { get; }
private CustomizeFlag _settingAvailable; public CustomizeFlag SettingAvailable { get; internal set; }
internal void SetAvailable(CustomizeIndex index) internal void SetAvailable(CustomizeIndex index)
=> _settingAvailable |= index.ToFlag(); => SettingAvailable |= index.ToFlag();
public bool IsAvailable(CustomizeIndex index) public bool IsAvailable(CustomizeIndex index)
=> _settingAvailable.HasFlag(index.ToFlag()); => SettingAvailable.HasFlag(index.ToFlag());
// Meta // Meta
public IReadOnlyList<string> OptionName { get; internal set; } = null!; public IReadOnlyList<string> OptionName { get; internal set; } = null!;
@ -138,10 +138,11 @@ public class CustomizationSet
CharaMakeParams.MenuType.ListSelector => GetInteger(out custom), CharaMakeParams.MenuType.ListSelector => GetInteger(out custom),
CharaMakeParams.MenuType.IconSelector => index switch CharaMakeParams.MenuType.IconSelector => index switch
{ {
CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom), CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom),
CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles, value, out custom), CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles,
value, out custom),
CustomizeIndex.TailShape => Get(TailEarShapes, value, out custom), CustomizeIndex.TailShape => Get(TailEarShapes, value, out custom),
CustomizeIndex.FacePaint => Get(FacePaints, value, out custom), CustomizeIndex.FacePaint => Get(FacePaints, value, out custom),
CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom), CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom),
_ => Invalid(out custom), _ => Invalid(out custom),
}, },

View file

@ -108,7 +108,7 @@ public unsafe class PenumbraAttach : IDisposable
if (button != MouseButton.Right || type != ChangedItemType.Item) if (button != MouseButton.Right || type != ChangedItemType.Item)
return; return;
var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(id)!; var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(Dalamud.GameData, id)!;
var writeItem = new Item(item, string.Empty); var writeItem = new Item(item, string.Empty);
UpdateItem(ObjectManager.GPosePlayer, writeItem); UpdateItem(ObjectManager.GPosePlayer, writeItem);

View file

@ -13,6 +13,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Glamourer.Saves; using Glamourer.Saves;
using Penumbra.GameData.Actors;
namespace Glamourer.Designs; namespace Glamourer.Designs;
@ -52,7 +53,7 @@ public class FixedDesign
public string Name { get; private set; } public string Name { get; private set; }
public bool Enabled; public bool Enabled;
public List<Actor.IIdentifier> Actors; public List<ActorIdentifier> Actors;
public List<(FixedCondition, Design)> Customization; public List<(FixedCondition, Design)> Customization;
public List<(FixedCondition, Design)> Equipment; public List<(FixedCondition, Design)> Equipment;
public List<(FixedCondition, Design)> Weapons; public List<(FixedCondition, Design)> Weapons;
@ -60,7 +61,7 @@ public class FixedDesign
public FixedDesign(string name) public FixedDesign(string name)
{ {
Name = name; Name = name;
Actors = new List<Actor.IIdentifier>(); Actors = new List<ActorIdentifier>();
Customization = new List<(FixedCondition, Design)>(); Customization = new List<(FixedCondition, Design)>();
Equipment = new List<(FixedCondition, Design)>(); Equipment = new List<(FixedCondition, Design)>();
Weapons = new List<(FixedCondition, Design)>(); Weapons = new List<(FixedCondition, Design)>();
@ -125,7 +126,7 @@ public class FixedDesign
j.WritePropertyName(nameof(Actors)); j.WritePropertyName(nameof(Actors));
j.WriteStartArray(); j.WriteStartArray();
foreach (var actor in Actors) foreach (var actor in Actors)
actor.ToJson(j); actor.ToJson().WriteTo(j);
j.WriteEndArray(); j.WriteEndArray();
j.WritePropertyName(nameof(Customization)); j.WritePropertyName(nameof(Customization));
j.WriteStartArray(); j.WriteStartArray();

View file

@ -9,6 +9,7 @@ using Glamourer.Interop;
using Glamourer.State; using Glamourer.State;
using OtterGui.Log; using OtterGui.Log;
using Penumbra.GameData; using Penumbra.GameData;
using Penumbra.GameData.Actors;
namespace Glamourer; namespace Glamourer;
@ -31,6 +32,7 @@ public class Glamourer : IDalamudPlugin
public static Logger Log = null!; public static Logger Log = null!;
public static IObjectIdentifier Identifier = null!; public static IObjectIdentifier Identifier = null!;
public static ActorManager Actors = null!;
public static PenumbraAttach Penumbra = null!; public static PenumbraAttach Penumbra = null!;
public static ICustomizationManager Customization = null!; public static ICustomizationManager Customization = null!;
public static RestrictedGear RestrictedGear = null!; public static RestrictedGear RestrictedGear = null!;
@ -60,8 +62,10 @@ public class Glamourer : IDalamudPlugin
Config = GlamourerConfig.Load(); Config = GlamourerConfig.Load();
Identifier = global::Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData); Identifier = global::Penumbra.GameData.GameData.GetIdentifier(Dalamud.PluginInterface, Dalamud.GameData);
Penumbra = new PenumbraAttach(Config.AttachToPenumbra); Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData,
i => (short)Penumbra.CutsceneParent(i));
FixedDesigns = new FixedDesigns(); FixedDesigns = new FixedDesigns();
CurrentManipulations = new CurrentManipulations(); CurrentManipulations = new CurrentManipulations();
//Designs = new DesignManager(); //Designs = new DesignManager();
@ -93,6 +97,8 @@ public class Glamourer : IDalamudPlugin
public void Dispose() public void Dispose()
{ {
Dalamud.PluginInterface.RelinquishData("test1");
RedrawManager?.Dispose(); RedrawManager?.Dispose();
Penumbra?.Dispose(); Penumbra?.Dispose();
if (_windowSystem != null) if (_windowSystem != null)

View file

@ -1,207 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using ImGuiNET;
namespace Glamourer.Gui;
public class ComboWithFilter<T>
{
private readonly string _label;
private readonly string _filterLabel;
private readonly string _listLabel;
private string _currentFilter = string.Empty;
private string _currentFilterLower = string.Empty;
private bool _focus;
private readonly float _size;
private float _previewSize;
private readonly IReadOnlyList<T> _items;
private readonly IReadOnlyList<(string, int)> _itemNamesLower;
private readonly Func<T, string> _itemToName;
private IReadOnlyList<(string, int)> _currentItemNames;
private bool _needsClear;
public Action? PrePreview;
public Action? PostPreview;
public Func<T, bool>? CreateSelectable;
public Action? PreList;
public Action? PostList;
public float? HeightPerItem;
private float _heightPerItem;
public ImGuiComboFlags Flags { get; set; } = ImGuiComboFlags.None;
public int ItemsAtOnce { get; set; } = 12;
private void UpdateFilter(string newFilter)
{
if (newFilter == _currentFilter)
return;
var lower = newFilter.ToLowerInvariant();
if (_currentFilterLower.Any() && lower.Contains(_currentFilterLower))
_currentItemNames = _currentItemNames.Where(p => p.Item1.Contains(lower)).ToArray();
else if (lower.Any())
_currentItemNames = _itemNamesLower.Where(p => p.Item1.Contains(lower)).ToArray();
else
_currentItemNames = _itemNamesLower;
_currentFilter = newFilter;
_currentFilterLower = lower;
}
public ComboWithFilter(string label, float size, float previewSize, IReadOnlyList<T> items, Func<T, string> itemToName)
{
_label = label;
_filterLabel = $"##_{label}_filter";
_listLabel = $"##_{label}_list";
_itemToName = itemToName;
_items = items;
_size = size;
_previewSize = previewSize;
_itemNamesLower = _items.Select((i, idx) => (_itemToName(i).ToLowerInvariant(), idx)).ToArray();
_currentItemNames = _itemNamesLower;
}
public ComboWithFilter(string label, ComboWithFilter<T> other)
{
_label = label;
_filterLabel = $"##_{label}_filter";
_listLabel = $"##_{label}_list";
_itemToName = other._itemToName;
_items = other._items;
_itemNamesLower = other._itemNamesLower;
_currentItemNames = other._currentItemNames;
_size = other._size;
_previewSize = other._previewSize;
PrePreview = other.PrePreview;
PostPreview = other.PostPreview;
CreateSelectable = other.CreateSelectable;
PreList = other.PreList;
PostList = other.PostList;
HeightPerItem = other.HeightPerItem;
Flags = other.Flags;
}
private bool DrawList(string currentName, out int numItems, out int nodeIdx, ref T? value)
{
numItems = ItemsAtOnce;
nodeIdx = -1;
if (!ImGui.BeginChild(_listLabel, new Vector2(_size, ItemsAtOnce * _heightPerItem)))
{
ImGui.EndChild();
return false;
}
var ret = false;
try
{
if (!_focus)
{
ImGui.SetScrollY(0);
_focus = true;
}
var scrollY = Math.Max((int)(ImGui.GetScrollY() / _heightPerItem) - 1, 0);
var restHeight = scrollY * _heightPerItem;
numItems = 0;
nodeIdx = 0;
if (restHeight > 0)
ImGui.Dummy(Vector2.UnitY * restHeight);
for (var i = scrollY; i < _currentItemNames.Count; ++i)
{
if (++numItems > ItemsAtOnce + 2)
continue;
nodeIdx = _currentItemNames[i].Item2;
var item = _items[nodeIdx]!;
bool success;
if (CreateSelectable != null)
{
success = CreateSelectable(item);
}
else
{
var name = _itemToName(item);
success = ImGui.Selectable(name, name == currentName);
}
if (success)
{
value = item;
ImGui.CloseCurrentPopup();
ret = true;
}
}
if (_currentItemNames.Count > ItemsAtOnce + 2)
ImGui.Dummy(Vector2.UnitY * (_currentItemNames.Count - ItemsAtOnce - 2 - scrollY) * _heightPerItem);
}
finally
{
ImGui.EndChild();
}
return ret;
}
public bool Draw(string currentName, out T? value, float? size = null)
{
if (size.HasValue)
_previewSize = size.Value;
value = default;
ImGui.SetNextItemWidth(_previewSize);
PrePreview?.Invoke();
if (!ImGui.BeginCombo(_label, currentName, Flags))
{
if (_needsClear)
{
_needsClear = false;
_focus = false;
UpdateFilter(string.Empty);
}
PostPreview?.Invoke();
return false;
}
_needsClear = true;
PostPreview?.Invoke();
_heightPerItem = HeightPerItem ?? ImGui.GetTextLineHeightWithSpacing();
bool ret;
try
{
ImGui.SetNextItemWidth(-1);
var tmp = _currentFilter;
if (ImGui.InputTextWithHint(_filterLabel, "Filter...", ref tmp, 255))
UpdateFilter(tmp);
var isFocused = ImGui.IsItemActive();
if (!_focus)
ImGui.SetKeyboardFocusHere();
PreList?.Invoke();
ret = DrawList(currentName, out var numItems, out var nodeIdx, ref value);
PostList?.Invoke();
if (!isFocused && numItems <= 1 && nodeIdx >= 0)
{
value = _items[nodeIdx];
ret = true;
ImGui.CloseCurrentPopup();
}
}
finally
{
ImGui.EndCombo();
}
return ret;
}
}

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Dalamud.Interface; using Dalamud.Interface;
using ImGuiNET; using ImGuiNET;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
@ -354,7 +355,7 @@ public partial class EquipmentDrawer
{ {
0 => SmallClothes, 0 => SmallClothes,
9903 => SmallClothesNpc, 9903 => SmallClothesNpc,
_ => Identifier.Identify(set, weapon, variant, slot) ?? Unknown, _ => Identifier.Identify(set, weapon, variant, slot).FirstOrDefault(Unknown),
}; };
} }
} }

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET; using ImGuiNET;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -11,6 +10,11 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment; namespace Glamourer.Gui.Equipment;
public enum ApplicationFlags
{
}
public partial class EquipmentDrawer public partial class EquipmentDrawer
{ {
private Race _race; private Race _race;
@ -26,7 +30,7 @@ public partial class EquipmentDrawer
{ {
Stains = GameData.Stains(Dalamud.GameData); Stains = GameData.Stains(Dalamud.GameData);
StainCombo = new FilterStainCombo(140); StainCombo = new FilterStainCombo(140);
Identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData); Identifier = Glamourer.Identifier;
ItemCombos = EquipSlotExtensions.EqdpSlots.Select(s => new ItemCombo(s)).ToArray(); ItemCombos = EquipSlotExtensions.EqdpSlots.Select(s => new ItemCombo(s)).ToArray();
MainHandCombo = new WeaponCombo(EquipSlot.MainHand); MainHandCombo = new WeaponCombo(EquipSlot.MainHand);
OffHandCombo = new WeaponCombo(EquipSlot.OffHand); OffHandCombo = new WeaponCombo(EquipSlot.OffHand);

View file

@ -20,15 +20,20 @@ public partial class EquipmentDrawer
private sealed class FilterStainCombo : FilterComboBase<Stain> private sealed class FilterStainCombo : FilterComboBase<Stain>
{ {
private readonly float _comboWidth; private readonly float _comboWidth;
private Vector2 _buttonSize; private Vector2 _buttonSize;
public ImRaii.Color Color = new();
public FilterStainCombo(float comboWidth) public FilterStainCombo(float comboWidth)
: base(Stains.Values.ToArray(), false) : base(Stains.Values.ToArray(), false)
=> _comboWidth = comboWidth; => _comboWidth = comboWidth;
protected override float GetFilterWidth() protected override float GetFilterWidth()
=> _buttonSize.X + ImGui.GetStyle().ScrollbarSize; {
// Hack to not color the filter frame.
Color.Pop();
return _buttonSize.X + ImGui.GetStyle().ScrollbarSize;
}
protected override void DrawList(float width, float itemHeight) protected override void DrawList(float width, float itemHeight)
{ {
@ -58,11 +63,12 @@ public partial class EquipmentDrawer
private void DrawStainSelector() private void DrawStainSelector()
{ {
var foundIdx = StainCombo.Items.IndexOf(s => s.RowIndex.Equals(_currentArmor.Stain)); var foundIdx = StainCombo.Items.IndexOf(s => s.RowIndex.Equals(_currentArmor.Stain));
var stain = foundIdx >= 0 ? StainCombo.Items[foundIdx] : default; var stain = foundIdx >= 0 ? StainCombo.Items[foundIdx] : default;
using var color = ImRaii.PushColor(ImGuiCol.FrameBg, stain.RgbaColor, foundIdx >= 0); StainCombo.Color.Push(ImGuiCol.FrameBg, stain.RgbaColor, foundIdx >= 0);
var change = StainCombo.Draw("##stainSelector", string.Empty, ref foundIdx, ImGui.GetFrameHeight(), ImGui.GetFrameHeight(), var change = StainCombo.Draw("##stainSelector", string.Empty, ref foundIdx, ImGui.GetFrameHeight(), ImGui.GetFrameHeight(),
ImGuiComboFlags.NoArrowButton); ImGuiComboFlags.NoArrowButton);
StainCombo.Color.Pop();
if (!change && (byte)_currentArmor.Stain != 0) if (!change && (byte)_currentArmor.Stain != 0)
{ {
ImGuiUtil.HoverTooltip($"{stain.Name}\nRight-click to clear."); ImGuiUtil.HoverTooltip($"{stain.Name}\nRight-click to clear.");
@ -82,7 +88,7 @@ public partial class EquipmentDrawer
} }
private void DrawCheckbox(ref ApplicationFlags flags) private void DrawCheckbox(ref ApplicationFlags flags)
=> DrawCheckbox("##checkbox", "Enable writing this slot in this save.", ref flags, _currentSlot.ToApplicationFlag()); => DrawCheckbox("##checkbox", "Enable writing this slot in this save.", ref flags, 0);
private static void DrawCheckbox(string label, string tooltip, ref ApplicationFlags flags, ApplicationFlags flag) private static void DrawCheckbox(string label, string tooltip, ref ApplicationFlags flags, ApplicationFlags flag)
{ {

View file

@ -12,6 +12,7 @@ using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using ImGui = ImGuiNET.ImGui; using ImGui = ImGuiNET.ImGui;
@ -26,7 +27,7 @@ internal partial class Interface
public ActorTab(CurrentManipulations manipulations) public ActorTab(CurrentManipulations manipulations)
=> _manipulations = manipulations; => _manipulations = manipulations;
private Actor.IIdentifier _identifier = Actor.IIdentifier.Invalid; private ActorIdentifier _identifier = ActorIdentifier.Invalid;
private ObjectManager.ActorData _currentData = ObjectManager.ActorData.Invalid; private ObjectManager.ActorData _currentData = ObjectManager.ActorData.Invalid;
private string _currentLabel = string.Empty; private string _currentLabel = string.Empty;
private CurrentDesign? _currentSave; private CurrentDesign? _currentSave;
@ -50,7 +51,7 @@ internal partial class Interface
private unsafe void DrawPanel() private unsafe void DrawPanel()
{ {
if (_identifier == Actor.IIdentifier.Invalid) if (_identifier == ActorIdentifier.Invalid)
return; return;
@ -65,15 +66,16 @@ internal partial class Interface
RevertButton(); RevertButton();
CustomizationDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, _currentData.Objects, CustomizationDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, _currentData.Objects,
_identifier is Actor.SpecialIdentifier); _identifier.Type == IdentifierType.Special);
EquipmentDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, ref _currentSave.Data.MainHand, ref _currentSave.Data.OffHand, _currentData.Objects, _identifier is Actor.SpecialIdentifier); EquipmentDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, ref _currentSave.Data.MainHand,
ref _currentSave.Data.OffHand, _currentData.Objects, _identifier.Type == IdentifierType.Special);
} }
private const uint RedHeaderColor = 0xFF1818C0; private const uint RedHeaderColor = 0xFF1818C0;
private const uint GreenHeaderColor = 0xFF18C018; private const uint GreenHeaderColor = 0xFF18C018;
private void RevertButton() private unsafe void RevertButton()
{ {
if (ImGui.Button("Revert")) if (ImGui.Button("Revert"))
{ {
@ -88,36 +90,38 @@ internal partial class Interface
_currentSave!.Reset(); _currentSave!.Reset();
} }
VisorBox(); if (_currentData.Objects.Count > 0)
ImGui.TextUnformatted(_currentData.Objects[0].Pointer->GameObject.DataID.ToString());
//VisorBox();
} }
private unsafe void VisorBox() //private unsafe void VisorBox()
{ //{
var (flags, mask) = (_currentSave!.Data.Flags & (ApplicationFlags.SetVisor | ApplicationFlags.Visor)) switch // var (flags, mask) = (_currentSave!.Data.Flags & (ApplicationFlags.SetVisor | ApplicationFlags.Visor)) switch
{ // {
ApplicationFlags.SetVisor => (0u, 3u), // ApplicationFlags.SetVisor => (0u, 3u),
ApplicationFlags.Visor => (1u, 3u), // ApplicationFlags.Visor => (1u, 3u),
ApplicationFlags.SetVisor | ApplicationFlags.Visor => (3u, 3u), // ApplicationFlags.SetVisor | ApplicationFlags.Visor => (3u, 3u),
_ => (2u, 3u), // _ => (2u, 3u),
}; // };
var tmp = flags; // var tmp = flags;
if (ImGui.CheckboxFlags("Visor Toggled", ref tmp, mask)) // if (ImGui.CheckboxFlags("Visor Toggled", ref tmp, mask))
{ // {
_currentSave.Data.Flags = flags switch // _currentSave.Data.Flags = flags switch
{ // {
0 => (_currentSave.Data.Flags | ApplicationFlags.Visor) & ~ApplicationFlags.SetVisor, // 0 => (_currentSave.Data.Flags | ApplicationFlags.Visor) & ~ApplicationFlags.SetVisor,
1 => _currentSave.Data.Flags | ApplicationFlags.SetVisor, // 1 => _currentSave.Data.Flags | ApplicationFlags.SetVisor,
2 => _currentSave.Data.Flags | ApplicationFlags.SetVisor, // 2 => _currentSave.Data.Flags | ApplicationFlags.SetVisor,
_ => _currentSave.Data.Flags & ~(ApplicationFlags.SetVisor | ApplicationFlags.Visor), // _ => _currentSave.Data.Flags & ~(ApplicationFlags.SetVisor | ApplicationFlags.Visor),
}; // };
if (_currentSave.Data.Flags.HasFlag(ApplicationFlags.SetVisor)) // if (_currentSave.Data.Flags.HasFlag(ApplicationFlags.SetVisor))
{ // {
var on = _currentSave.Data.Flags.HasFlag(ApplicationFlags.Visor); // var on = _currentSave.Data.Flags.HasFlag(ApplicationFlags.Visor);
foreach (var actor in _currentData.Objects.Where(a => a.IsHuman && a.DrawObject)) // foreach (var actor in _currentData.Objects.Where(a => a.IsHuman && a.DrawObject))
RedrawManager.SetVisor(actor.DrawObject.Pointer, on); // RedrawManager.SetVisor(actor.DrawObject.Pointer, on);
} // }
} // }
} //}
private void DrawPanelHeader() private void DrawPanelHeader()
{ {
@ -213,10 +217,10 @@ internal partial class Interface
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
} }
private bool CheckFilter((Actor.IIdentifier, ObjectManager.ActorData) pair) private bool CheckFilter((ActorIdentifier, ObjectManager.ActorData) pair)
=> _actorFilter.IsEmpty || pair.Item2.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase); => _actorFilter.IsEmpty || pair.Item2.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase);
private void DrawSelectable((Actor.IIdentifier, ObjectManager.ActorData) pair) private void DrawSelectable((ActorIdentifier, ObjectManager.ActorData) pair)
{ {
var equal = pair.Item1.Equals(_identifier); var equal = pair.Item1.Equals(_identifier);
if (ImGui.Selectable(pair.Item2.Label, equal) && !equal) if (ImGui.Selectable(pair.Item2.Label, equal) && !equal)

View file

@ -9,6 +9,7 @@ using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Actors;
namespace Glamourer.Gui; namespace Glamourer.Gui;
@ -18,10 +19,10 @@ internal partial class Interface
{ {
private readonly CurrentManipulations _currentManipulations; private readonly CurrentManipulations _currentManipulations;
private LowerString _manipulationFilter = LowerString.Empty; private LowerString _manipulationFilter = LowerString.Empty;
private Actor.IIdentifier _selection = Actor.IIdentifier.Invalid; private ActorIdentifier _selection = ActorIdentifier.Invalid;
private CurrentDesign? _save = null; private CurrentDesign? _save = null;
private bool _delete = false; private bool _delete = false;
public DebugStateTab(CurrentManipulations currentManipulations) public DebugStateTab(CurrentManipulations currentManipulations)
=> _currentManipulations = currentManipulations; => _currentManipulations = currentManipulations;
@ -43,7 +44,7 @@ internal partial class Interface
{ {
_delete = false; _delete = false;
_currentManipulations.DeleteSave(_selection); _currentManipulations.DeleteSave(_selection);
_selection = Actor.IIdentifier.Invalid; _selection = ActorIdentifier.Invalid;
} }
} }
@ -72,14 +73,14 @@ internal partial class Interface
DrawSelector(oldSpacing); DrawSelector(oldSpacing);
} }
private bool CheckFilter(KeyValuePair<Actor.IIdentifier, CurrentDesign> data) private bool CheckFilter(KeyValuePair<ActorIdentifier, CurrentDesign> data)
{ {
if (data.Key.Equals(_selection)) if (data.Key.Equals(_selection))
_save = data.Value; _save = data.Value;
return _manipulationFilter.Length == 0 || _manipulationFilter.IsContained(data.Key.ToString()!); return _manipulationFilter.Length == 0 || _manipulationFilter.IsContained(data.Key.ToString()!);
} }
private void DrawSelectable(KeyValuePair<Actor.IIdentifier, CurrentDesign> data) private void DrawSelectable(KeyValuePair<ActorIdentifier, CurrentDesign> data)
{ {
var equal = data.Key.Equals(_selection); var equal = data.Key.Equals(_selection);
if (ImGui.Selectable(data.Key.ToString(), equal)) if (ImGui.Selectable(data.Key.ToString(), equal))

View file

@ -1,375 +0,0 @@
using System;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Utility;
using Lumina.Excel.GeneratedSheets;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.ByteString;
namespace Glamourer.Interop;
public unsafe partial struct Actor
{
public interface IIdentifier : IEquatable<IIdentifier>
{
Utf8String Name { get; }
public bool IsValid { get; }
public IIdentifier CreatePermanent();
public static readonly InvalidIdentifier Invalid = new();
public void ToJson(JsonTextWriter j);
public static IIdentifier? FromJson(JObject j)
{
switch (j["Type"]?.Value<string>() ?? string.Empty)
{
case nameof(PlayerIdentifier):
{
var name = j[nameof(Name)]?.Value<string>();
if (name.IsNullOrEmpty())
return null;
var serverId = j[nameof(PlayerIdentifier.HomeWorld)]?.Value<ushort>() ?? ushort.MaxValue;
return new PlayerIdentifier(Utf8String.FromStringUnsafe(name, false), serverId);
}
case nameof(SpecialIdentifier):
{
var index = j[nameof(SpecialIdentifier.Index)]?.Value<ushort>() ?? ushort.MaxValue;
return new SpecialIdentifier(index);
}
case nameof(OwnedIdentifier):
{
var name = j[nameof(Name)]?.Value<string>();
if (name.IsNullOrEmpty())
return null;
var ownerName = j[nameof(OwnedIdentifier.OwnerName)]?.Value<string>();
if (ownerName.IsNullOrEmpty())
return null;
var ownerHomeWorld = j[nameof(OwnedIdentifier.OwnerHomeWorld)]?.Value<ushort>() ?? ushort.MaxValue;
var dataId = j[nameof(OwnedIdentifier.DataId)]?.Value<ushort>() ?? ushort.MaxValue;
var kind = j[nameof(OwnedIdentifier.Kind)]?.Value<ObjectKind>() ?? ObjectKind.Player;
return new OwnedIdentifier(Utf8String.FromStringUnsafe(name, false), Utf8String.FromStringUnsafe(ownerName, false),
ownerHomeWorld, dataId, kind);
}
case nameof(NpcIdentifier):
{
var name = j[nameof(Name)]?.Value<string>();
if (name.IsNullOrEmpty())
return null;
var dataId = j[nameof(NpcIdentifier.DataId)]?.Value<uint>() ?? uint.MaxValue;
return new NpcIdentifier(Utf8String.FromStringUnsafe(name, false), ushort.MaxValue, dataId);
}
default: return null;
}
}
}
public class InvalidIdentifier : IIdentifier
{
public Utf8String Name
=> Utf8String.Empty;
public bool IsValid
=> false;
public bool Equals(IIdentifier? other)
=> false;
public override int GetHashCode()
=> 0;
public override string ToString()
=> "Invalid";
public IIdentifier CreatePermanent()
=> this;
public void ToJson(JsonTextWriter j)
{ }
}
public class PlayerIdentifier : IIdentifier, IEquatable<PlayerIdentifier>
{
public Utf8String Name { get; }
public readonly ushort HomeWorld;
public bool IsValid
=> true;
public PlayerIdentifier(Utf8String name, ushort homeWorld)
{
Name = name;
HomeWorld = homeWorld;
}
public bool Equals(IIdentifier? other)
=> Equals(other as PlayerIdentifier);
public bool Equals(PlayerIdentifier? other)
=> other?.HomeWorld == HomeWorld && other.Name.Equals(Name);
public override int GetHashCode()
=> HashCode.Combine(Name.Crc32, HomeWorld);
public override string ToString()
=> $"{Name} ({HomeWorld})";
public IIdentifier CreatePermanent()
=> new PlayerIdentifier(Name.Clone(), HomeWorld);
public void ToJson(JsonTextWriter j)
{
j.WriteStartObject();
j.WritePropertyName("Type");
j.WriteValue(GetType().Name);
j.WritePropertyName(nameof(Name));
j.WriteValue(Name);
j.WritePropertyName(nameof(HomeWorld));
j.WriteValue(HomeWorld);
j.WriteEndObject();
}
}
public class SpecialIdentifier : IIdentifier, IEquatable<SpecialIdentifier>
{
public Utf8String Name
=> Utf8String.Empty;
public readonly ushort Index;
public bool IsValid
=> true;
public SpecialIdentifier(ushort index)
=> Index = index;
public bool Equals(IIdentifier? other)
=> Equals(other as SpecialIdentifier);
public bool Equals(SpecialIdentifier? other)
=> other?.Index == Index;
public override int GetHashCode()
=> Index;
public override string ToString()
=> $"Special Actor {Index}";
public IIdentifier CreatePermanent()
=> this;
public void ToJson(JsonTextWriter j)
{
j.WriteStartObject();
j.WritePropertyName("Type");
j.WriteValue(GetType().Name);
j.WritePropertyName(nameof(Index));
j.WriteValue(Index);
j.WriteEndObject();
}
}
public class OwnedIdentifier : IIdentifier, IEquatable<OwnedIdentifier>
{
public Utf8String Name { get; }
public readonly Utf8String OwnerName;
public readonly uint DataId;
public readonly ushort OwnerHomeWorld;
public readonly ObjectKind Kind;
public bool IsValid
=> true;
public OwnedIdentifier(Utf8String actorName, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind)
{
OwnerName = ownerName;
OwnerHomeWorld = ownerHomeWorld;
DataId = dataId;
Kind = kind;
Name = actorName;
switch (Kind)
{
case ObjectKind.MountType:
var mount = Dalamud.GameData.GetExcelSheet<Mount>()!.GetRow(dataId);
if (mount != null)
Name = Utf8String.FromSpanUnsafe(mount.Singular.RawData, false).AsciiToMixed();
break;
case ObjectKind.Companion:
var companion = Dalamud.GameData.GetExcelSheet<Companion>()!.GetRow(dataId);
if (companion != null)
Name = Utf8String.FromSpanUnsafe(companion.Singular.RawData, false).AsciiToMixed();
break;
}
}
public bool Equals(IIdentifier? other)
=> Equals(other as OwnedIdentifier);
public bool Equals(OwnedIdentifier? other)
=> other?.DataId == DataId
&& other.OwnerHomeWorld == OwnerHomeWorld
&& other.Kind == Kind
&& other.OwnerName.Equals(OwnerName);
public override int GetHashCode()
=> HashCode.Combine(OwnerName.Crc32, OwnerHomeWorld, DataId, Kind);
public override string ToString()
=> $"{OwnerName}s {Name}";
public IIdentifier CreatePermanent()
=> new OwnedIdentifier(Name.Clone(), OwnerName.Clone(), OwnerHomeWorld, DataId, Kind);
public void ToJson(JsonTextWriter j)
{
j.WriteStartObject();
j.WritePropertyName("Type");
j.WriteValue(GetType().Name);
j.WritePropertyName(nameof(Name));
j.WriteValue(Name);
j.WritePropertyName(nameof(OwnerName));
j.WriteValue(OwnerName);
j.WritePropertyName(nameof(OwnerHomeWorld));
j.WriteValue(OwnerHomeWorld);
j.WritePropertyName(nameof(Kind));
j.WriteValue(Kind);
j.WritePropertyName(nameof(DataId));
j.WriteValue(DataId);
j.WriteEndObject();
}
}
public class NpcIdentifier : IIdentifier, IEquatable<NpcIdentifier>
{
public Utf8String Name { get; }
public readonly uint DataId;
public readonly ushort ObjectIndex;
public bool IsValid
=> true;
public NpcIdentifier(Utf8String actorName, ushort objectIndex = ushort.MaxValue, uint dataId = uint.MaxValue)
{
Name = actorName;
ObjectIndex = objectIndex;
DataId = dataId;
}
public bool Equals(IIdentifier? other)
=> Equals(other as NpcIdentifier);
public bool Equals(NpcIdentifier? other)
=> (other?.Name.Equals(Name) ?? false)
&& (other.DataId == uint.MaxValue || DataId == uint.MaxValue || other.DataId == DataId)
&& (other.ObjectIndex == ushort.MaxValue || ObjectIndex == ushort.MaxValue || other.ObjectIndex == ObjectIndex);
public override int GetHashCode()
=> Name.Crc32;
public override string ToString()
=> DataId == uint.MaxValue ? ObjectIndex == ushort.MaxValue ? Name.ToString() : $"{Name} at {ObjectIndex}" :
ObjectIndex == ushort.MaxValue ? $"{Name} ({DataId})" : $"{Name} ({DataId}) at {ObjectIndex}";
public IIdentifier CreatePermanent()
=> new NpcIdentifier(Name.Clone(), ObjectIndex, DataId);
public void ToJson(JsonTextWriter j)
{
j.WriteStartObject();
j.WritePropertyName("Type");
j.WriteValue(GetType().Name);
j.WritePropertyName(nameof(Name));
j.WriteValue(Name);
j.WritePropertyName(nameof(DataId));
j.WriteValue(DataId);
j.WriteEndObject();
}
}
private static IIdentifier CreateIdentifier(Actor actor)
{
if (!actor.Valid)
return IIdentifier.Invalid;
var objectIdx = actor.Pointer->GameObject.ObjectIndex;
if (objectIdx is >= 200 and < 240)
{
var parentIdx = Glamourer.Penumbra.CutsceneParent(objectIdx);
if (parentIdx >= 0)
{
var parent = (Actor)Dalamud.Objects.GetObjectAddress(parentIdx);
if (!parent)
return IIdentifier.Invalid;
return CreateIdentifier(parent);
}
}
switch (actor.ObjectKind)
{
case ObjectKind.Player:
{
var name = actor.Utf8Name;
if (name.Length > 0 && actor.Pointer->HomeWorld is > 0 and < ushort.MaxValue)
return new PlayerIdentifier(actor.Utf8Name, actor.Pointer->HomeWorld);
return IIdentifier.Invalid;
}
case ObjectKind.BattleNpc:
{
var ownerId = actor.Pointer->GameObject.OwnerID;
if (ownerId != 0xE0000000)
{
var owner = (Actor)Dalamud.Objects.SearchById(ownerId)?.Address;
if (!owner)
return new InvalidIdentifier();
return new OwnedIdentifier(actor.Utf8Name, owner.Utf8Name, owner.Pointer->HomeWorld,
actor.Pointer->GameObject.DataID, ObjectKind.BattleNpc);
}
return new NpcIdentifier(actor.Utf8Name, actor.Pointer->GameObject.ObjectIndex,
actor.Pointer->GameObject.DataID);
}
case ObjectKind.Retainer:
case ObjectKind.EventNpc:
return new NpcIdentifier(actor.Utf8Name, actor.Pointer->GameObject.ObjectIndex,
actor.Pointer->GameObject.DataID);
case ObjectKind.MountType:
case ObjectKind.Companion:
{
var idx = actor.Pointer->GameObject.ObjectIndex;
if (idx % 2 == 0)
return new InvalidIdentifier();
var owner = (Actor)Dalamud.Objects[idx - 1]?.Address;
if (!owner)
return new InvalidIdentifier();
var dataId = actor.ObjectKind switch
{
ObjectKind.MountType => owner.UsedMountId,
ObjectKind.Companion => actor.CompanionId,
_ => actor.Pointer->GameObject.DataID,
};
var name = actor.Utf8Name;
if (name.IsEmpty && dataId == 0)
return new InvalidIdentifier();
return new OwnedIdentifier(name, owner.Utf8Name, owner.Pointer->HomeWorld,
dataId, actor.ObjectKind);
}
default: return new InvalidIdentifier();
}
}
}

View file

@ -1,9 +1,11 @@
using System; using System;
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using FFXIVClientStructs.FFXIV.Client.System.String;
using Glamourer.Customization; using Glamourer.Customization;
using Penumbra.GameData.ByteString; using Penumbra.GameData.Actors;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.String;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData; using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Interop; namespace Glamourer.Interop;
@ -23,10 +25,10 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
public static implicit operator IntPtr(Actor actor) public static implicit operator IntPtr(Actor actor)
=> actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer; => actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer;
public IIdentifier GetIdentifier() public ActorIdentifier GetIdentifier()
=> CreateIdentifier(this); => Glamourer.Actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer);
public bool Identifier(out IIdentifier ident) public bool Identifier(out ActorIdentifier ident)
{ {
if (Valid) if (Valid)
{ {
@ -34,7 +36,7 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
return true; return true;
} }
ident = IIdentifier.Invalid; ident = ActorIdentifier.Invalid;
return false; return false;
} }
@ -53,7 +55,7 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
set => Pointer->GameObject.ObjectKind = (byte)value; set => Pointer->GameObject.ObjectKind = (byte)value;
} }
public Utf8String Utf8Name public ByteString Utf8Name
=> new(Pointer->GameObject.Name); => new(Pointer->GameObject.Name);
public byte Job public byte Job
@ -123,6 +125,8 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
} }
} }
public bool IsWet { get; set; }
public void SetModelId(int value) public void SetModelId(int value)
{ {

View file

@ -25,6 +25,9 @@ public unsafe partial struct DrawObject : IEquatable<DrawObject>, IDesignable
public uint ModelId public uint ModelId
=> 0; => 0;
public bool IsWet
=> false;
public uint Type public uint Type
=> (*(delegate* unmanaged<Human*, uint>**)Pointer)[50](Pointer); => (*(delegate* unmanaged<Human*, uint>**)Pointer)[50](Pointer);

View file

@ -13,4 +13,5 @@ public interface IDesignable
public CharacterWeapon OffHand { get; } public CharacterWeapon OffHand { get; }
public bool VisorEnabled { get; } public bool VisorEnabled { get; }
public bool WeaponEnabled { get; } public bool WeaponEnabled { get; }
public bool IsWet { get; }
} }

View file

@ -2,6 +2,7 @@
using System.Text; using System.Text;
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Actors;
using static Glamourer.Interop.Actor; using static Glamourer.Interop.Actor;
namespace Glamourer.Interop; namespace Glamourer.Interop;
@ -42,67 +43,29 @@ public static class ObjectManager
public static bool IsInGPose { get; private set; } public static bool IsInGPose { get; private set; }
public static ushort World { get; private set; } public static ushort World { get; private set; }
public static IReadOnlyDictionary<IIdentifier, ActorData> Actors public static IReadOnlyDictionary<ActorIdentifier, ActorData> Actors
=> Identifiers; => Identifiers;
public static IReadOnlyList<(IIdentifier, ActorData)> List public static IReadOnlyList<(ActorIdentifier, ActorData)> List
=> ListData; => ListData;
private static readonly Dictionary<IIdentifier, ActorData> Identifiers = new(200); private static readonly Dictionary<ActorIdentifier, ActorData> Identifiers = new(200);
private static readonly List<(IIdentifier, ActorData)> ListData = new(Dalamud.Objects.Length); private static readonly List<(ActorIdentifier, ActorData)> ListData = new(Dalamud.Objects.Length);
private static void HandleIdentifier(IIdentifier identifier, Actor character) private static void HandleIdentifier(ActorIdentifier identifier, Actor character)
{ {
if (!character.DrawObject) if (!character.DrawObject || !identifier.IsValid)
return; return;
switch (identifier) if (!Identifiers.TryGetValue(identifier, out var data))
{ {
case PlayerIdentifier p: data = new ActorData(character, identifier.ToString());
if (!Identifiers.TryGetValue(p, out var data)) Identifiers[identifier] = data;
{ ListData.Add((identifier, data));
data = new ActorData(character, }
World != p.HomeWorld else
? $"{p.Name} ({Dalamud.GameData.GetExcelSheet<World>()!.GetRow(p.HomeWorld)!.Name})" {
: p.Name.ToString()); data.Objects.Add(character);
Identifiers[p] = data;
ListData.Add((p, data));
}
else
{
data.Objects.Add(character);
}
break;
case NpcIdentifier n when !n.Name.IsEmpty:
if (!Identifiers.TryGetValue(n, out data))
{
data = new ActorData(character, $"{n.Name} (at {n.ObjectIndex})");
Identifiers[n] = data;
ListData.Add((n, data));
}
else
{
data.Objects.Add(character);
}
break;
case OwnedIdentifier o:
if (!Identifiers.TryGetValue(o, out data))
{
data = new ActorData(character,
World != o.OwnerHomeWorld
? $"{o.OwnerName}s {o.Name} ({Dalamud.GameData.GetExcelSheet<World>()!.GetRow(o.OwnerHomeWorld)!.Name})"
: $"{o.OwnerName}s {o.Name}");
Identifiers[o] = data;
ListData.Add((o, data));
}
else
{
data.Objects.Add(character);
}
break;
} }
} }

View file

@ -1,212 +1,305 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Interop;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Saves; namespace Glamourer.Saves;
public class EquipmentDesign public partial class Design
{ {
private Data _data = default; public const int CurrentVersion = 1;
// @formatter:off public FileInfo Identifier { get; private set; } = new(string.Empty);
//public Slot Head => new(ref _data, 0); public string Name { get; private set; } = "New Design";
//public Slot Body => new(ref _data, 1); public string Description { get; private set; } = string.Empty;
//public Slot Hands => new(ref _data, 2);
//public Slot Legs => new(ref _data, 3);
//public Slot Feet => new(ref _data, 4);
//public Slot Ears => new(ref _data, 5);
//public Slot Neck => new(ref _data, 6);
//public Slot Wrist => new(ref _data, 7);
//public Slot RFinger => new(ref _data, 8);
//public Slot LFinger => new(ref _data, 9);
//public Slot MainHand => new(ref _data, 10);
//public Slot OffHand => new(ref _data, 11);
//// @formatter:on
//
//public Slot this[EquipSlot slot]
// => new(ref _data, (int)slot.ToIndex());
//
//public Slot this[int idx]
// => idx is >= 0 and < Data.NumEquipment ? new Slot(ref _data, idx) : throw new IndexOutOfRangeException();
public unsafe struct Data public DateTimeOffset CreationDate { get; private init; } = DateTimeOffset.UtcNow;
public DateTimeOffset LastUpdateDate { get; private set; } = DateTimeOffset.UtcNow;
private DesignFlagsV1 _flags;
public bool VisorState
{ {
public const int NumEquipment = 12; get => _flags.HasFlag(DesignFlagsV1.VisorState);
private set => _flags = value ? _flags | DesignFlagsV1.VisorState : _flags & ~DesignFlagsV1.VisorState;
public fixed uint Ids[NumEquipment];
public fixed byte Stains[NumEquipment];
public ushort Flags;
public ushort StainFlags;
} }
//public ref struct Slot public bool VisorApply
//{
// private readonly ref Data _data;
// private readonly int _index;
// private readonly ushort _flag;
//
// public Slot(ref Data data, int idx)
// {
// _data = data;
// _index = idx;
// _flag = (ushort)(1 << idx);
// }
//
// public unsafe uint ItemId
// {
// get => _data.Ids[_index];
// set => _data.Ids[_index] = value;
// }
//
// public unsafe StainId StainId
// {
// get => _data.Stains[_index];
// set => _data.Stains[_index] = value.Value;
// }
//
// public bool ApplyItem
// {
// get => (_data.Flags & _flag) != 0;
// set => _data.Flags = (ushort)(value ? _data.Flags | _flag : _data.Flags & ~_flag);
// }
//
// public bool ApplyStain
// {
// get => (_data.StainFlags & _flag) != 0;
// set => _data.StainFlags = (ushort)(value ? _data.StainFlags | _flag : _data.StainFlags & ~_flag);
// }
//}
}
public class HumanDesign
{
public unsafe struct Data
{ {
public CustomizeData Values; get => _flags.HasFlag(DesignFlagsV1.VisorApply);
public CustomizeFlag Flag; private set => _flags = value ? _flags | DesignFlagsV1.VisorApply : _flags & ~DesignFlagsV1.VisorApply;
} }
//public ref struct Choice<T> where T : unmanaged public bool WeaponStateShown
//{
// private readonly ref Data _data;
// private readonly CustomizeFlag _flag;
//
// public Choice(ref Data data, CustomizeFlag flag)
// {
// _data = data;
// _flag = flag;
// }
//
// public bool ApplyChoice
// {
// get => _data.Flag.HasFlag(_flag);
// set => _data.Flag = value ? _data.Flag | _flag : _data.Flag & ~_flag;
// }
//}
}
public class Design
{
public string Name { get; private set; }
public string Description { get; private set; }
public DateTimeOffset CreationDate { get; }
public DateTimeOffset LastUpdateDate { get; private set; }
public bool ReadOnly { get; private set; }
public EquipmentDesign? Equipment;
public HumanDesign? Customize;
public string WriteJson()
{ {
return string.Empty; get => _flags.HasFlag(DesignFlagsV1.WeaponStateShown);
private set => _flags = value ? _flags | DesignFlagsV1.WeaponStateShown : _flags & ~DesignFlagsV1.WeaponStateShown;
} }
}
public bool WeaponStateApply
public struct DesignSaveV1 {
{ get => _flags.HasFlag(DesignFlagsV1.WeaponStateApply);
public string Name; private set => _flags = value ? _flags | DesignFlagsV1.WeaponStateApply : _flags & ~DesignFlagsV1.WeaponStateApply;
public string Description; }
public ulong CreationDate; public bool WetnessState
public ulong LastUpdateDate; {
get => _flags.HasFlag(DesignFlagsV1.WetnessState);
public EquipmentPiece Head; private set => _flags = value ? _flags | DesignFlagsV1.WetnessState : _flags & ~DesignFlagsV1.WetnessState;
public EquipmentPiece Body; }
public EquipmentPiece Hands;
public EquipmentPiece Legs; public bool WetnessApply
public EquipmentPiece Feet; {
public EquipmentPiece Ears; get => _flags.HasFlag(DesignFlagsV1.WetnessApply);
public EquipmentPiece Neck; private set => _flags = value ? _flags | DesignFlagsV1.WetnessApply : _flags & ~DesignFlagsV1.WetnessApply;
public EquipmentPiece Wrists; }
public EquipmentPiece LFinger;
public EquipmentPiece RFinger; public bool ReadOnly
{
public EquipmentPiece MainHand; get => _flags.HasFlag(DesignFlagsV1.ReadOnly);
public EquipmentPiece OffHand; private set => _flags = value ? _flags | DesignFlagsV1.ReadOnly : _flags & ~DesignFlagsV1.ReadOnly;
}
public CustomizationChoice<uint> ModelId;
public CustomizationChoice<Race> Race; private static bool FromDesignable(string identifier, string name, IDesignable data, [NotNullWhen(true)] out Design? design,
public CustomizationChoice<Gender> Gender; bool doWeapons = true, bool doFlags = true, bool doEquipment = true, bool doCustomize = true)
public CustomizationChoice BodyType; {
public CustomizationChoice Height; if (!data.Valid)
public CustomizationChoice<SubRace> Clan; {
public CustomizationChoice Face; design = null;
public CustomizationChoice Hairstyle; return false;
public CustomizationChoice<bool> Highlights; }
public CustomizationChoice SkinColor;
public CustomizationChoice EyeColorRight; design = new Design
public CustomizationChoice HairColor; {
public CustomizationChoice HighlightsColor; Identifier = new FileInfo(identifier),
public CustomizationChoice<bool> FacialFeature1; Name = name,
public CustomizationChoice<bool> FacialFeature2; Description = string.Empty,
public CustomizationChoice<bool> FacialFeature3; CreationDate = DateTimeOffset.UtcNow,
public CustomizationChoice<bool> FacialFeature4; LastUpdateDate = DateTimeOffset.UtcNow,
public CustomizationChoice<bool> FacialFeature5; ReadOnly = false,
public CustomizationChoice<bool> FacialFeature6; VisorApply = doFlags,
public CustomizationChoice<bool> FacialFeature7; WeaponStateApply = doFlags,
public CustomizationChoice<bool> LegacyTattoo; WetnessApply = doFlags,
public CustomizationChoice TattooColor; VisorState = data.VisorEnabled,
public CustomizationChoice Eyebrows; WeaponStateShown = data.WeaponEnabled,
public CustomizationChoice EyeColorLeft; WetnessState = data.IsWet,
public CustomizationChoice EyeShape; };
public CustomizationChoice<bool> SmallIris;
public CustomizationChoice Nose; if (doEquipment)
public CustomizationChoice Jaw; {
public CustomizationChoice Mouth; var equipment = data.Equip;
public CustomizationChoice<bool> Lipstick; foreach (var slot in EquipSlotExtensions.EqdpSlots)
public CustomizationChoice MuscleMass; {
public CustomizationChoice TailShape; var s = design[slot];
public CustomizationChoice BustSize; var e = equipment[slot];
public CustomizationChoice FacePaint; s.StainId = e.Stain;
public CustomizationChoice<bool> FacePaintReversed; s.ApplyStain = true;
public CustomizationChoice FacePaintColor; s.ItemId = Glamourer.Identifier.Identify(e.Set, e.Variant, slot).FirstOrDefault()?.RowId ?? 0;
s.ApplyItem = s.ItemId != 0;
public bool ReadOnly; }
}
public override string ToString()
=> Name; if (doWeapons)
} {
var m = design.MainHand;
public struct EquipmentPiece var d = data.MainHand;
{
public uint Item; m.StainId = d.Stain;
public bool ApplyItem; m.ApplyStain = true;
public StainId Stain; m.ItemId = Glamourer.Identifier.Identify(d.Set, d.Type, d.Variant, EquipSlot.MainHand).FirstOrDefault()?.RowId ?? 0;
public bool ApplyStain; m.ApplyItem = m.ItemId != 0;
}
var o = design.OffHand;
public struct CustomizationChoice d = data.OffHand;
{ o.StainId = d.Stain;
public CustomizeValue Value; o.ApplyStain = true;
public bool Apply; o.ItemId = Glamourer.Identifier.Identify(d.Set, d.Type, d.Variant, EquipSlot.MainHand).FirstOrDefault()?.RowId ?? 0;
} o.ApplyItem = o.ItemId != 0;
}
public struct CustomizationChoice<T> where T : struct
{ if (doCustomize)
public T Value; {
public bool Apply; var customize = data.Customize;
design.CustomizeFlags = Glamourer.Customization.GetList(customize.Clan, customize.Gender).SettingAvailable
| CustomizeFlag.Gender
| CustomizeFlag.Race
| CustomizeFlag.Clan;
foreach (var c in Enum.GetValues<CustomizeIndex>())
{
if (!design.CustomizeFlags.HasFlag(c.ToFlag()))
continue;
var choice = design[c];
choice.Value = customize[c];
}
}
return true;
}
public void Save()
{
try
{
using var file = File.Open(Identifier.FullName, File.Exists(Identifier.FullName) ? FileMode.Truncate : FileMode.CreateNew);
WriteJson(file);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not save design {Identifier.Name}:\n{ex}");
}
}
public void WriteJson(Stream s, Formatting formatting = Formatting.Indented)
{
var obj = new JObject();
obj["Version"] = CurrentVersion;
obj[nameof(Name)] = Name;
obj[nameof(Description)] = Description;
obj[nameof(CreationDate)] = CreationDate.ToUnixTimeSeconds();
obj[nameof(LastUpdateDate)] = LastUpdateDate.ToUnixTimeSeconds();
obj[nameof(ReadOnly)] = ReadOnly;
WriteEquipment(obj);
WriteCustomization(obj);
WriteFlags(obj);
using var t = new StreamWriter(s);
using var j = new JsonTextWriter(t) { Formatting = formatting };
obj.WriteTo(j);
}
private void WriteFlags(JObject obj)
{
obj[nameof(VisorState)] = VisorState;
obj[nameof(VisorApply)] = VisorApply;
obj[nameof(WeaponStateShown)] = WeaponStateShown;
obj[nameof(WeaponStateApply)] = WeaponStateApply;
obj[nameof(WetnessState)] = WetnessState;
obj[nameof(WetnessApply)] = WetnessApply;
}
public static bool Load(string fileName, [NotNullWhen(true)] out Design? design)
{
design = null;
if (!File.Exists(fileName))
{
Glamourer.Log.Error($"Could not load design {fileName}:\nFile does not exist.");
return false;
}
try
{
var data = File.ReadAllText(fileName);
var obj = JObject.Parse(data);
return obj["Version"]?.Value<int>() switch
{
null => NoVersion(fileName),
1 => LoadV1(fileName, obj, out design),
_ => UnknownVersion(fileName, obj["Version"]!.Value<int>()),
};
}
catch (Exception e)
{
Glamourer.Log.Error($"Could not load design {fileName}:\n{e}");
}
return false;
}
private static bool NoVersion(string fileName)
{
Glamourer.Log.Error($"Could not load design {fileName}:\nNo version available.");
return false;
}
private static bool UnknownVersion(string fileName, int version)
{
Glamourer.Log.Error($"Could not load design {fileName}:\nThe version {version} can not be handled.");
return false;
}
private static bool LoadV1(string fileName, JObject obj, [NotNullWhen(true)] out Design? design)
{
design = new Design
{
Identifier = new FileInfo(fileName),
Name = obj[nameof(Name)]?.Value<string>() ?? "New Design",
Description = obj[nameof(Description)]?.Value<string>() ?? string.Empty,
CreationDate = GetDateTime(obj[nameof(CreationDate)]?.Value<long>()),
LastUpdateDate = GetDateTime(obj[nameof(LastUpdateDate)]?.Value<long>()),
ReadOnly = obj[nameof(ReadOnly)]?.Value<bool>() ?? false,
VisorState = obj[nameof(VisorState)]?.Value<bool>() ?? false,
VisorApply = obj[nameof(VisorApply)]?.Value<bool>() ?? false,
WeaponStateShown = obj[nameof(WeaponStateShown)]?.Value<bool>() ?? false,
WeaponStateApply = obj[nameof(WeaponStateApply)]?.Value<bool>() ?? false,
WetnessState = obj[nameof(WetnessState)]?.Value<bool>() ?? false,
WetnessApply = obj[nameof(WetnessApply)]?.Value<bool>() ?? false,
};
var equipment = obj[nameof(Equipment)];
if (equipment == null)
{
design.EquipmentFlags = 0;
design.StainFlags = 0;
design._equipmentData = default;
}
else
{
foreach (var slot in design.Equipment)
{
var s = equipment[SlotName[slot.Index]];
if (s == null)
{
slot.ItemId = 0;
slot.ApplyItem = false;
slot.ApplyStain = false;
slot.StainId = 0;
}
else
{
slot.ItemId = s[nameof(Slot.ItemId)]?.Value<uint>() ?? 0u;
slot.ApplyItem = obj[nameof(Slot.ApplyItem)]?.Value<bool>() ?? false;
slot.StainId = new StainId(s[nameof(Slot.StainId)]?.Value<byte>() ?? 0);
slot.ApplyStain = obj[nameof(Slot.ApplyStain)]?.Value<bool>() ?? false;
}
}
}
var customize = obj[nameof(Customization)];
if (customize == null)
{
design.CustomizeFlags = 0;
design._customizeData = Customize.Default;
}
else
{
foreach (var choice in design.Customization)
{
var c = customize[choice.Index.ToDefaultName()];
if (c == null)
{
choice.Value = Customize.Default.Get(choice.Index);
choice.Apply = false;
}
else
{
choice.Value = new CustomizeValue(c[nameof(Choice.Value)]?.Value<byte>() ?? Customize.Default.Get(choice.Index).Value);
choice.Apply = c[nameof(Choice.Apply)]?.Value<bool>() ?? false;
}
}
}
return true;
}
private static DateTimeOffset GetDateTime(long? value)
=> value == null ? DateTimeOffset.UtcNow : DateTimeOffset.FromUnixTimeSeconds(value.Value);
} }

View file

@ -0,0 +1,15 @@
using System;
namespace Glamourer.Saves;
[Flags]
public enum DesignFlagsV1 : byte
{
VisorState = 0x01,
VisorApply = 0x02,
WeaponStateShown = 0x04,
WeaponStateApply = 0x08,
WetnessState = 0x10,
WetnessApply = 0x20,
ReadOnly = 0x40,
}

View file

@ -0,0 +1,115 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Saves;
public partial class Design
{
private unsafe struct EquipmentData
{
public const int NumEquipment = 12;
public fixed uint Ids[NumEquipment];
public fixed byte Stains[NumEquipment];
}
private EquipmentData _equipmentData = default;
public ushort EquipmentFlags { get; private set; }
public ushort StainFlags { get; private set; }
// @formatter:off
public Slot Head => new(this, 0);
public Slot Body => new(this, 1);
public Slot Hands => new(this, 2);
public Slot Legs => new(this, 3);
public Slot Feet => new(this, 4);
public Slot Ears => new(this, 5);
public Slot Neck => new(this, 6);
public Slot Wrists => new(this, 7);
public Slot RFinger => new(this, 8);
public Slot LFinger => new(this, 9);
public Slot MainHand => new(this, 10);
public Slot OffHand => new(this, 11);
// @formatter:on
public Slot this[EquipSlot slot]
=> new(this, (int)slot.ToIndex());
public static readonly string[] SlotName =
{
EquipSlot.Head.ToName(),
EquipSlot.Body.ToName(),
EquipSlot.Hands.ToName(),
EquipSlot.Legs.ToName(),
EquipSlot.Feet.ToName(),
EquipSlot.Ears.ToName(),
EquipSlot.Neck.ToName(),
EquipSlot.Wrists.ToName(),
EquipSlot.RFinger.ToName(),
EquipSlot.LFinger.ToName(),
EquipSlot.MainHand.ToName(),
EquipSlot.OffHand.ToName(),
};
public readonly unsafe struct Slot
{
private readonly Design _data;
public readonly int Index;
public readonly ushort Flag;
public Slot(Design design, int idx)
{
_data = design;
Index = idx;
Flag = (ushort)(1 << idx);
}
public uint ItemId
{
get => _data._equipmentData.Ids[Index];
set => _data._equipmentData.Ids[Index] = value;
}
public StainId StainId
{
get => _data._equipmentData.Stains[Index];
set => _data._equipmentData.Stains[Index] = value.Value;
}
public bool ApplyItem
{
get => (_data.EquipmentFlags & Flag) != 0;
set => _data.EquipmentFlags = (ushort)(value ? _data.EquipmentFlags | Flag : _data.EquipmentFlags & ~Flag);
}
public bool ApplyStain
{
get => (_data.StainFlags & Flag) != 0;
set => _data.StainFlags = (ushort)(value ? _data.StainFlags | Flag : _data.StainFlags & ~Flag);
}
}
public IEnumerable<Slot> Equipment
=> Enumerable.Range(0, EquipmentData.NumEquipment).Select(i => new Slot(this, i));
private void WriteEquipment(JObject obj)
{
var tok = new JObject();
foreach (var slot in Equipment)
{
tok[SlotName] = new JObject
{
[nameof(Slot.ItemId)] = slot.ItemId,
[nameof(Slot.ApplyItem)] = slot.ApplyItem,
[nameof(Slot.StainId)] = slot.StainId.Value,
[nameof(Slot.ApplyStain)] = slot.ApplyStain,
};
}
obj[nameof(Equipment)] = tok;
}
}

View file

@ -0,0 +1,67 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Glamourer.Customization;
using Newtonsoft.Json.Linq;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Saves;
public partial class Design
{
private CustomizeData _customizeData;
public CustomizeFlag CustomizeFlags { get; private set; }
public Choice this[CustomizeIndex index]
=> new(this, index);
public unsafe Customize Customize
=> new((CustomizeData*)Unsafe.AsPointer(ref _customizeData));
public readonly struct Choice
{
private readonly Design _data;
private readonly CustomizeFlag _flag;
private readonly CustomizeIndex _index;
public Choice(Design design, CustomizeIndex index)
{
_data = design;
_index = index;
_flag = index.ToFlag();
}
public CustomizeValue Value
{
get => _data._customizeData.Get(_index);
set => _data._customizeData.Set(_index, value);
}
public bool Apply
{
get => _data.CustomizeFlags.HasFlag(_flag);
set => _data.CustomizeFlags = value ? _data.CustomizeFlags | _flag : _data.CustomizeFlags & ~_flag;
}
public CustomizeIndex Index
=> _index;
}
public IEnumerable<Choice> Customization
=> Enum.GetValues<CustomizeIndex>().Select(index => new Choice(this, index));
public IEnumerable<Choice> ActiveCustomizations
=> Customization.Where(c => c.Apply);
private void WriteCustomization(JObject obj)
{
var tok = new JObject();
foreach (var choice in Customization)
tok[choice.Index.ToString()] = choice.Value.Value;
obj[nameof(Customization)] = tok;
}
}

View file

@ -1,75 +0,0 @@
using Penumbra.GameData.Enums;
using System;
namespace Glamourer.State;
[Flags]
public enum ApplicationFlags : uint
{
Customizations = 0x000001,
MainHand = 0x000002,
OffHand = 0x000004,
Head = 0x000008,
Body = 0x000010,
Hands = 0x000020,
Legs = 0x000040,
Feet = 0x000080,
Ears = 0x000100,
Neck = 0x000200,
Wrist = 0x000400,
RFinger = 0x000800,
LFinger = 0x001000,
SetVisor = 0x002000,
Visor = 0x004000,
SetWeapon = 0x008000,
Weapon = 0x010000,
SetWet = 0x020000,
Wet = 0x040000,
}
public static class ApplicationFlagExtensions
{
public static ApplicationFlags ToApplicationFlag(this EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => ApplicationFlags.MainHand,
EquipSlot.OffHand => ApplicationFlags.OffHand,
EquipSlot.Head => ApplicationFlags.Head,
EquipSlot.Body => ApplicationFlags.Body,
EquipSlot.Hands => ApplicationFlags.Hands,
EquipSlot.Legs => ApplicationFlags.Legs,
EquipSlot.Feet => ApplicationFlags.Feet,
EquipSlot.Ears => ApplicationFlags.Ears,
EquipSlot.Neck => ApplicationFlags.Neck,
EquipSlot.Wrists => ApplicationFlags.Wrist,
EquipSlot.RFinger => ApplicationFlags.RFinger,
EquipSlot.BothHand => ApplicationFlags.MainHand | ApplicationFlags.OffHand,
EquipSlot.LFinger => ApplicationFlags.LFinger,
EquipSlot.HeadBody => ApplicationFlags.Body,
EquipSlot.BodyHandsLegsFeet => ApplicationFlags.Body,
EquipSlot.LegsFeet => ApplicationFlags.Legs,
EquipSlot.FullBody => ApplicationFlags.Body,
EquipSlot.BodyHands => ApplicationFlags.Body,
EquipSlot.BodyLegsFeet => ApplicationFlags.Body,
EquipSlot.ChestHands => ApplicationFlags.Body,
_ => 0,
};
public static EquipSlot ToSlot(this ApplicationFlags flags)
=> flags switch
{
ApplicationFlags.MainHand => EquipSlot.MainHand,
ApplicationFlags.OffHand => EquipSlot.OffHand,
ApplicationFlags.Head => EquipSlot.Head,
ApplicationFlags.Body => EquipSlot.Body,
ApplicationFlags.Hands => EquipSlot.Hands,
ApplicationFlags.Legs => EquipSlot.Legs,
ApplicationFlags.Feet => EquipSlot.Feet,
ApplicationFlags.Ears => EquipSlot.Ears,
ApplicationFlags.Neck => EquipSlot.Neck,
ApplicationFlags.Wrist => EquipSlot.Wrists,
ApplicationFlags.RFinger => EquipSlot.RFinger,
ApplicationFlags.LFinger => EquipSlot.LFinger,
_ => EquipSlot.Unknown,
};
}

View file

@ -2,12 +2,12 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Interop; using Glamourer.Interop;
using ImGuiScene;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.String.Functions;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData; using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
using Functions = Penumbra.GameData.Util.Functions;
namespace Glamourer.State; namespace Glamourer.State;
@ -36,7 +36,6 @@ public struct CharacterData
public const byte CurrentVersion = 3; public const byte CurrentVersion = 3;
public uint ModelId; public uint ModelId;
public ApplicationFlags Flags;
public CustomizeData CustomizeData; public CustomizeData CustomizeData;
public CharacterWeapon MainHand; public CharacterWeapon MainHand;
public CharacterWeapon OffHand; public CharacterWeapon OffHand;
@ -77,7 +76,6 @@ public struct CharacterData
= new() = new()
{ {
ModelId = 0, ModelId = 0,
Flags = 0,
CustomizeData = Customize.Default, CustomizeData = Customize.Default,
MainHand = CharacterWeapon.Empty, MainHand = CharacterWeapon.Empty,
OffHand = CharacterWeapon.Empty, OffHand = CharacterWeapon.Empty,
@ -98,28 +96,12 @@ public struct CharacterData
var data = new CharacterData(); var data = new CharacterData();
fixed (void* ptr = &this) fixed (void* ptr = &this)
{ {
Functions.MemCpyUnchecked(&data, ptr, sizeof(CharacterData)); MemoryUtility.MemCpyUnchecked(&data, ptr, sizeof(CharacterData));
} }
return data; return data;
} }
private const ApplicationFlags SaveFlags = ApplicationFlags.Customizations
| ApplicationFlags.Head
| ApplicationFlags.Body
| ApplicationFlags.Hands
| ApplicationFlags.Legs
| ApplicationFlags.Feet
| ApplicationFlags.Ears
| ApplicationFlags.Neck
| ApplicationFlags.Wrist
| ApplicationFlags.RFinger
| ApplicationFlags.LFinger
| ApplicationFlags.MainHand
| ApplicationFlags.OffHand
| ApplicationFlags.SetVisor
| ApplicationFlags.SetWeapon;
public void Load(IDesignable designable) public void Load(IDesignable designable)
{ {
@ -128,7 +110,6 @@ public struct CharacterData
Equipment.Load(designable.Equip); Equipment.Load(designable.Equip);
MainHand = designable.MainHand; MainHand = designable.MainHand;
OffHand = designable.OffHand; OffHand = designable.OffHand;
Flags = SaveFlags | (designable.VisorEnabled ? ApplicationFlags.Visor : 0) | (designable.WeaponEnabled ? ApplicationFlags.Weapon : 0);
} }
} }

View file

@ -2,14 +2,15 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Glamourer.Interop; using Glamourer.Interop;
using Penumbra.GameData.Actors;
namespace Glamourer.State; namespace Glamourer.State;
public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<Actor.IIdentifier, CurrentDesign>> public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<ActorIdentifier, CurrentDesign>>
{ {
private readonly Dictionary<Actor.IIdentifier, CurrentDesign> _characterSaves = new(); private readonly Dictionary<ActorIdentifier, CurrentDesign> _characterSaves = new();
public IEnumerator<KeyValuePair<Actor.IIdentifier, CurrentDesign>> GetEnumerator() public IEnumerator<KeyValuePair<ActorIdentifier, CurrentDesign>> GetEnumerator()
=> _characterSaves.GetEnumerator(); => _characterSaves.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
@ -32,10 +33,10 @@ public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<Actor.IIden
return save; return save;
} }
public void DeleteSave(Actor.IIdentifier identifier) public void DeleteSave(ActorIdentifier identifier)
=> _characterSaves.Remove(identifier); => _characterSaves.Remove(identifier);
public bool TryGetDesign(Actor.IIdentifier identifier, [NotNullWhen(true)] out CurrentDesign? save) public bool TryGetDesign(ActorIdentifier identifier, [NotNullWhen(true)] out CurrentDesign? save)
=> _characterSaves.TryGetValue(identifier, out save); => _characterSaves.TryGetValue(identifier, out save);
//public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data) //public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)

View file

@ -1,11 +1,12 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Glamourer.Interop; using Glamourer.Interop;
using Penumbra.GameData.Actors;
namespace Glamourer.State; namespace Glamourer.State;
public class FixedDesigns public class FixedDesigns
{ {
public bool TryGetDesign(Actor.IIdentifier actor, [NotNullWhen(true)] out CharacterSave? save) public bool TryGetDesign(ActorIdentifier actor, [NotNullWhen(true)] out CharacterSave? save)
{ {
save = null; save = null;
return false; return false;