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

@ -17,20 +17,20 @@ public class CustomizationSet
Gender = gender;
Clan = clan;
Race = clan.ToRace();
_settingAvailable = 0;
SettingAvailable = 0;
}
public Gender Gender { get; }
public SubRace Clan { get; }
public Race Race { get; }
private CustomizeFlag _settingAvailable;
public CustomizeFlag SettingAvailable { get; internal set; }
internal void SetAvailable(CustomizeIndex index)
=> _settingAvailable |= index.ToFlag();
=> SettingAvailable |= index.ToFlag();
public bool IsAvailable(CustomizeIndex index)
=> _settingAvailable.HasFlag(index.ToFlag());
=> SettingAvailable.HasFlag(index.ToFlag());
// Meta
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
@ -139,7 +139,8 @@ public class CustomizationSet
CharaMakeParams.MenuType.IconSelector => index switch
{
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.FacePaint => Get(FacePaints, value, out custom),
CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom),

View file

@ -108,7 +108,7 @@ public unsafe class PenumbraAttach : IDisposable
if (button != MouseButton.Right || type != ChangedItemType.Item)
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);
UpdateItem(ObjectManager.GPosePlayer, writeItem);

View file

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

View file

@ -9,6 +9,7 @@ using Glamourer.Interop;
using Glamourer.State;
using OtterGui.Log;
using Penumbra.GameData;
using Penumbra.GameData.Actors;
namespace Glamourer;
@ -31,6 +32,7 @@ public class Glamourer : IDalamudPlugin
public static Logger Log = null!;
public static IObjectIdentifier Identifier = null!;
public static ActorManager Actors = null!;
public static PenumbraAttach Penumbra = null!;
public static ICustomizationManager Customization = null!;
public static RestrictedGear RestrictedGear = null!;
@ -60,8 +62,10 @@ public class Glamourer : IDalamudPlugin
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);
Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData,
i => (short)Penumbra.CutsceneParent(i));
FixedDesigns = new FixedDesigns();
CurrentManipulations = new CurrentManipulations();
//Designs = new DesignManager();
@ -93,6 +97,8 @@ public class Glamourer : IDalamudPlugin
public void Dispose()
{
Dalamud.PluginInterface.RelinquishData("test1");
RedrawManager?.Dispose();
Penumbra?.Dispose();
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.Collections.Generic;
using System.Linq;
using Dalamud.Interface;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
@ -354,7 +355,7 @@ public partial class EquipmentDrawer
{
0 => SmallClothes,
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 Glamourer.Customization;
using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
@ -11,6 +10,11 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment;
public enum ApplicationFlags
{
}
public partial class EquipmentDrawer
{
private Race _race;
@ -26,7 +30,7 @@ public partial class EquipmentDrawer
{
Stains = GameData.Stains(Dalamud.GameData);
StainCombo = new FilterStainCombo(140);
Identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData);
Identifier = Glamourer.Identifier;
ItemCombos = EquipSlotExtensions.EqdpSlots.Select(s => new ItemCombo(s)).ToArray();
MainHandCombo = new WeaponCombo(EquipSlot.MainHand);
OffHandCombo = new WeaponCombo(EquipSlot.OffHand);

View file

@ -22,13 +22,18 @@ public partial class EquipmentDrawer
{
private readonly float _comboWidth;
private Vector2 _buttonSize;
public ImRaii.Color Color = new();
public FilterStainCombo(float comboWidth)
: base(Stains.Values.ToArray(), false)
=> _comboWidth = comboWidth;
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)
{
@ -60,9 +65,10 @@ public partial class EquipmentDrawer
{
var foundIdx = StainCombo.Items.IndexOf(s => s.RowIndex.Equals(_currentArmor.Stain));
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(),
ImGuiComboFlags.NoArrowButton);
StainCombo.Color.Pop();
if (!change && (byte)_currentArmor.Stain != 0)
{
ImGuiUtil.HoverTooltip($"{stain.Name}\nRight-click to clear.");
@ -82,7 +88,7 @@ public partial class EquipmentDrawer
}
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)
{

View file

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

View file

@ -9,6 +9,7 @@ using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using Penumbra.GameData.Actors;
namespace Glamourer.Gui;
@ -19,7 +20,7 @@ internal partial class Interface
private readonly CurrentManipulations _currentManipulations;
private LowerString _manipulationFilter = LowerString.Empty;
private Actor.IIdentifier _selection = Actor.IIdentifier.Invalid;
private ActorIdentifier _selection = ActorIdentifier.Invalid;
private CurrentDesign? _save = null;
private bool _delete = false;
@ -43,7 +44,7 @@ internal partial class Interface
{
_delete = false;
_currentManipulations.DeleteSave(_selection);
_selection = Actor.IIdentifier.Invalid;
_selection = ActorIdentifier.Invalid;
}
}
@ -72,14 +73,14 @@ internal partial class Interface
DrawSelector(oldSpacing);
}
private bool CheckFilter(KeyValuePair<Actor.IIdentifier, CurrentDesign> data)
private bool CheckFilter(KeyValuePair<ActorIdentifier, CurrentDesign> data)
{
if (data.Key.Equals(_selection))
_save = data.Value;
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);
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 Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using FFXIVClientStructs.FFXIV.Client.System.String;
using Glamourer.Customization;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Structs;
using Penumbra.String;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Interop;
@ -23,10 +25,10 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
public static implicit operator IntPtr(Actor actor)
=> actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer;
public IIdentifier GetIdentifier()
=> CreateIdentifier(this);
public ActorIdentifier GetIdentifier()
=> Glamourer.Actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer);
public bool Identifier(out IIdentifier ident)
public bool Identifier(out ActorIdentifier ident)
{
if (Valid)
{
@ -34,7 +36,7 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
return true;
}
ident = IIdentifier.Invalid;
ident = ActorIdentifier.Invalid;
return false;
}
@ -53,7 +55,7 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
set => Pointer->GameObject.ObjectKind = (byte)value;
}
public Utf8String Utf8Name
public ByteString Utf8Name
=> new(Pointer->GameObject.Name);
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)
{

View file

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

View file

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

View file

@ -2,6 +2,7 @@
using System.Text;
using Dalamud.Game.ClientState.Objects.Enums;
using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Actors;
using static Glamourer.Interop.Actor;
namespace Glamourer.Interop;
@ -42,68 +43,30 @@ public static class ObjectManager
public static bool IsInGPose { get; private set; }
public static ushort World { get; private set; }
public static IReadOnlyDictionary<IIdentifier, ActorData> Actors
public static IReadOnlyDictionary<ActorIdentifier, ActorData> Actors
=> Identifiers;
public static IReadOnlyList<(IIdentifier, ActorData)> List
public static IReadOnlyList<(ActorIdentifier, ActorData)> List
=> ListData;
private static readonly Dictionary<IIdentifier, ActorData> Identifiers = new(200);
private static readonly List<(IIdentifier, ActorData)> ListData = new(Dalamud.Objects.Length);
private static readonly Dictionary<ActorIdentifier, ActorData> Identifiers = new(200);
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;
switch (identifier)
if (!Identifiers.TryGetValue(identifier, out var data))
{
case PlayerIdentifier p:
if (!Identifiers.TryGetValue(p, out var data))
{
data = new ActorData(character,
World != p.HomeWorld
? $"{p.Name} ({Dalamud.GameData.GetExcelSheet<World>()!.GetRow(p.HomeWorld)!.Name})"
: p.Name.ToString());
Identifiers[p] = data;
ListData.Add((p, data));
data = new ActorData(character, identifier.ToString());
Identifiers[identifier] = data;
ListData.Add((identifier, 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;
}
}
public static void Update()

View file

@ -1,212 +1,305 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Glamourer.Customization;
using Glamourer.Interop;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Saves;
public class EquipmentDesign
public partial class Design
{
private Data _data = default;
public const int CurrentVersion = 1;
// @formatter:off
//public Slot Head => new(ref _data, 0);
//public Slot Body => new(ref _data, 1);
//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 FileInfo Identifier { get; private set; } = new(string.Empty);
public string Name { get; private set; } = "New Design";
public string Description { get; private set; } = string.Empty;
public 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;
public fixed uint Ids[NumEquipment];
public fixed byte Stains[NumEquipment];
public ushort Flags;
public ushort StainFlags;
get => _flags.HasFlag(DesignFlagsV1.VisorState);
private set => _flags = value ? _flags | DesignFlagsV1.VisorState : _flags & ~DesignFlagsV1.VisorState;
}
//public ref struct Slot
//{
// 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 bool VisorApply
{
get => _flags.HasFlag(DesignFlagsV1.VisorApply);
private set => _flags = value ? _flags | DesignFlagsV1.VisorApply : _flags & ~DesignFlagsV1.VisorApply;
}
public class HumanDesign
public bool WeaponStateShown
{
public unsafe struct Data
{
public CustomizeData Values;
public CustomizeFlag Flag;
get => _flags.HasFlag(DesignFlagsV1.WeaponStateShown);
private set => _flags = value ? _flags | DesignFlagsV1.WeaponStateShown : _flags & ~DesignFlagsV1.WeaponStateShown;
}
//public ref struct Choice<T> where T : unmanaged
//{
// 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 bool WeaponStateApply
{
get => _flags.HasFlag(DesignFlagsV1.WeaponStateApply);
private set => _flags = value ? _flags | DesignFlagsV1.WeaponStateApply : _flags & ~DesignFlagsV1.WeaponStateApply;
}
public class Design
public bool WetnessState
{
public string Name { get; private set; }
public string Description { get; private set; }
get => _flags.HasFlag(DesignFlagsV1.WetnessState);
private set => _flags = value ? _flags | DesignFlagsV1.WetnessState : _flags & ~DesignFlagsV1.WetnessState;
}
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()
public bool WetnessApply
{
return string.Empty;
get => _flags.HasFlag(DesignFlagsV1.WetnessApply);
private set => _flags = value ? _flags | DesignFlagsV1.WetnessApply : _flags & ~DesignFlagsV1.WetnessApply;
}
public bool ReadOnly
{
get => _flags.HasFlag(DesignFlagsV1.ReadOnly);
private set => _flags = value ? _flags | DesignFlagsV1.ReadOnly : _flags & ~DesignFlagsV1.ReadOnly;
}
private static bool FromDesignable(string identifier, string name, IDesignable data, [NotNullWhen(true)] out Design? design,
bool doWeapons = true, bool doFlags = true, bool doEquipment = true, bool doCustomize = true)
{
if (!data.Valid)
{
design = null;
return false;
}
design = new Design
{
Identifier = new FileInfo(identifier),
Name = name,
Description = string.Empty,
CreationDate = DateTimeOffset.UtcNow,
LastUpdateDate = DateTimeOffset.UtcNow,
ReadOnly = false,
VisorApply = doFlags,
WeaponStateApply = doFlags,
WetnessApply = doFlags,
VisorState = data.VisorEnabled,
WeaponStateShown = data.WeaponEnabled,
WetnessState = data.IsWet,
};
if (doEquipment)
{
var equipment = data.Equip;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var s = design[slot];
var e = equipment[slot];
s.StainId = e.Stain;
s.ApplyStain = true;
s.ItemId = Glamourer.Identifier.Identify(e.Set, e.Variant, slot).FirstOrDefault()?.RowId ?? 0;
s.ApplyItem = s.ItemId != 0;
}
}
public struct DesignSaveV1
if (doWeapons)
{
public string Name;
public string Description;
var m = design.MainHand;
var d = data.MainHand;
public ulong CreationDate;
public ulong LastUpdateDate;
m.StainId = d.Stain;
m.ApplyStain = true;
m.ItemId = Glamourer.Identifier.Identify(d.Set, d.Type, d.Variant, EquipSlot.MainHand).FirstOrDefault()?.RowId ?? 0;
m.ApplyItem = m.ItemId != 0;
public EquipmentPiece Head;
public EquipmentPiece Body;
public EquipmentPiece Hands;
public EquipmentPiece Legs;
public EquipmentPiece Feet;
public EquipmentPiece Ears;
public EquipmentPiece Neck;
public EquipmentPiece Wrists;
public EquipmentPiece LFinger;
public EquipmentPiece RFinger;
public EquipmentPiece MainHand;
public EquipmentPiece OffHand;
public CustomizationChoice<uint> ModelId;
public CustomizationChoice<Race> Race;
public CustomizationChoice<Gender> Gender;
public CustomizationChoice BodyType;
public CustomizationChoice Height;
public CustomizationChoice<SubRace> Clan;
public CustomizationChoice Face;
public CustomizationChoice Hairstyle;
public CustomizationChoice<bool> Highlights;
public CustomizationChoice SkinColor;
public CustomizationChoice EyeColorRight;
public CustomizationChoice HairColor;
public CustomizationChoice HighlightsColor;
public CustomizationChoice<bool> FacialFeature1;
public CustomizationChoice<bool> FacialFeature2;
public CustomizationChoice<bool> FacialFeature3;
public CustomizationChoice<bool> FacialFeature4;
public CustomizationChoice<bool> FacialFeature5;
public CustomizationChoice<bool> FacialFeature6;
public CustomizationChoice<bool> FacialFeature7;
public CustomizationChoice<bool> LegacyTattoo;
public CustomizationChoice TattooColor;
public CustomizationChoice Eyebrows;
public CustomizationChoice EyeColorLeft;
public CustomizationChoice EyeShape;
public CustomizationChoice<bool> SmallIris;
public CustomizationChoice Nose;
public CustomizationChoice Jaw;
public CustomizationChoice Mouth;
public CustomizationChoice<bool> Lipstick;
public CustomizationChoice MuscleMass;
public CustomizationChoice TailShape;
public CustomizationChoice BustSize;
public CustomizationChoice FacePaint;
public CustomizationChoice<bool> FacePaintReversed;
public CustomizationChoice FacePaintColor;
public bool ReadOnly;
public override string ToString()
=> Name;
var o = design.OffHand;
d = data.OffHand;
o.StainId = d.Stain;
o.ApplyStain = true;
o.ItemId = Glamourer.Identifier.Identify(d.Set, d.Type, d.Variant, EquipSlot.MainHand).FirstOrDefault()?.RowId ?? 0;
o.ApplyItem = o.ItemId != 0;
}
public struct EquipmentPiece
if (doCustomize)
{
public uint Item;
public bool ApplyItem;
public StainId Stain;
public bool ApplyStain;
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];
}
}
public struct CustomizationChoice
{
public CustomizeValue Value;
public bool Apply;
return true;
}
public struct CustomizationChoice<T> where T : struct
public void Save()
{
public T Value;
public bool Apply;
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 Glamourer.Customization;
using Glamourer.Interop;
using ImGuiScene;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.String.Functions;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
using Functions = Penumbra.GameData.Util.Functions;
namespace Glamourer.State;
@ -36,7 +36,6 @@ public struct CharacterData
public const byte CurrentVersion = 3;
public uint ModelId;
public ApplicationFlags Flags;
public CustomizeData CustomizeData;
public CharacterWeapon MainHand;
public CharacterWeapon OffHand;
@ -77,7 +76,6 @@ public struct CharacterData
= new()
{
ModelId = 0,
Flags = 0,
CustomizeData = Customize.Default,
MainHand = CharacterWeapon.Empty,
OffHand = CharacterWeapon.Empty,
@ -98,28 +96,12 @@ public struct CharacterData
var data = new CharacterData();
fixed (void* ptr = &this)
{
Functions.MemCpyUnchecked(&data, ptr, sizeof(CharacterData));
MemoryUtility.MemCpyUnchecked(&data, ptr, sizeof(CharacterData));
}
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)
{
@ -128,7 +110,6 @@ public struct CharacterData
Equipment.Load(designable.Equip);
MainHand = designable.MainHand;
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.Diagnostics.CodeAnalysis;
using Glamourer.Interop;
using Penumbra.GameData.Actors;
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();
IEnumerator IEnumerable.GetEnumerator()
@ -32,10 +33,10 @@ public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<Actor.IIden
return save;
}
public void DeleteSave(Actor.IIdentifier identifier)
public void DeleteSave(ActorIdentifier 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);
//public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)

View file

@ -1,11 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using Glamourer.Interop;
using Penumbra.GameData.Actors;
namespace Glamourer.State;
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;
return false;