mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
More stuff.
This commit is contained in:
parent
dad146d043
commit
1a4672a901
23 changed files with 616 additions and 1005 deletions
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@ public interface IDesignable
|
|||
public CharacterWeapon OffHand { get; }
|
||||
public bool VisorEnabled { get; }
|
||||
public bool WeaponEnabled { get; }
|
||||
public bool IsWet { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
15
Glamourer/Saves/DesignFlagsV1.cs
Normal file
15
Glamourer/Saves/DesignFlagsV1.cs
Normal 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,
|
||||
}
|
||||
115
Glamourer/Saves/EquipmentDesign.cs
Normal file
115
Glamourer/Saves/EquipmentDesign.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
67
Glamourer/Saves/HumanDesign.cs
Normal file
67
Glamourer/Saves/HumanDesign.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue