This commit is contained in:
Ottermandias 2023-02-09 18:52:53 +01:00
parent 145b64bb7a
commit 4cf082aa19
37 changed files with 1362 additions and 1255 deletions

View file

@ -59,7 +59,7 @@ public partial class CustomizationOptions
public partial class CustomizationOptions
{
internal readonly bool Valid;
private readonly bool _valid;
public string GetName(CustomName name)
=> _names[(int)name];
@ -68,7 +68,7 @@ public partial class CustomizationOptions
{
var tmp = new TemporaryData(gameData, this);
_icons = new IconStorage(pi, gameData, _customizationSets.Length * 50);
Valid = tmp.Valid;
_valid = tmp.Valid;
SetNames(gameData, tmp);
foreach (var race in Clans)
{
@ -77,7 +77,6 @@ public partial class CustomizationOptions
}
}
// Obtain localized names of customization options and race names from the game data.
private readonly string[] _names = new string[Enum.GetValues<CustomName>().Length];
@ -430,10 +429,14 @@ public partial class CustomizationOptions
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
var hairRow = _customizeSheet.GetRow(customizeIdx);
hairList.Add(hairRow != null
? new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
(ushort)hairRow.RowId)
: new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
if (hairRow == null)
{
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
}
else if (_options._icons.IconExists(hairRow.Icon))
{
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId));
}
}
return hairList.ToArray();

View file

@ -10,6 +10,14 @@ public unsafe struct Customize
public Customize(Penumbra.GameData.Structs.CustomizeData* data)
=> Data = data;
public Customize(ref Penumbra.GameData.Structs.CustomizeData data)
{
fixed (Penumbra.GameData.Structs.CustomizeData* ptr = &data)
{
Data = ptr;
}
}
public Race Race
{
get => (Race)Data->Get(CustomizeIndex.Race).Value;
@ -96,4 +104,18 @@ public unsafe struct Customize
public string WriteBase64()
=> Data->WriteBase64();
public static CustomizeFlag Compare(Customize lhs, Customize rhs)
{
CustomizeFlag ret = 0;
foreach (var idx in Enum.GetValues<CustomizeIndex>())
{
var l = lhs[idx];
var r = rhs[idx];
if (l.Value != r.Value)
ret |= idx.ToFlag();
}
return ret;
}
}

View file

@ -48,6 +48,10 @@ public enum CustomizeFlag : ulong
public static class CustomizeFlagExtensions
{
public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul);
public const CustomizeFlag RedrawRequired = CustomizeFlag.Race | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.Face;
public static bool RequiresRedraw(this CustomizeFlag flags)
=> (flags & RedrawRequired) != 0;
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static CustomizeIndex ToIndex(this CustomizeFlag flag)

View file

@ -31,4 +31,5 @@ public static class Sigs
{
public const string ChangeJob = "88 51 ?? 44 3B CA";
public const string FlagSlotForUpdate = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A";
public const string ChangeCustomize = "E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86";
}

View file

@ -21,7 +21,7 @@ public readonly struct JobGroup
Id = group.RowId;
Name = group.Name.ToString();
Debug.Assert(jobs.RowCount < 64);
Debug.Assert(jobs.RowCount < 64, $"Number of Jobs exceeded 63 ({jobs.RowCount}).");
foreach (var job in jobs)
{
var abbr = job.Abbreviation.ToString();
@ -29,7 +29,7 @@ public readonly struct JobGroup
continue;
var prop = group.GetType().GetProperty(abbr);
Debug.Assert(prop != null);
Debug.Assert(prop != null, $"Could not get job abbreviation {abbr} property.");
if (!(bool)prop.GetValue(group)!)
continue;

View file

@ -2,13 +2,9 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
using Glamourer.Interop;
using Glamourer.Structs;
using ImGuiNET;
using Penumbra.Api;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Api;
@ -17,33 +13,76 @@ public unsafe class PenumbraAttach : IDisposable
public const int RequiredPenumbraBreakingVersion = 4;
public const int RequiredPenumbraFeatureVersion = 15;
private EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
private EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
private ActionSubscriber<GameObject, RedrawType> _redrawSubscriber;
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
public EventSubscriber<nint, string, nint, nint, nint> CreatingCharacterBase;
public EventSubscriber<nint, string, nint> CreatedCharacterBase;
private FuncSubscriber<int, int> _cutsceneParent;
private readonly EventSubscriber _initializedEvent;
private readonly EventSubscriber _disposedEvent;
public bool Available { get; private set; }
public PenumbraAttach(bool attach)
public PenumbraAttach()
{
_initializedEvent = Ipc.Initialized.Subscriber(Dalamud.PluginInterface, Reattach);
_disposedEvent = Ipc.Disposed.Subscriber(Dalamud.PluginInterface, Unattach);
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface, PenumbraTooltip);
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface, PenumbraRightClick);
CreatedCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface);
CreatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface);
Reattach(attach);
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface);
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface);
_createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface);
_creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface);
Reattach();
}
private void Reattach()
=> Reattach(Glamourer.Config.AttachToPenumbra);
public event Action<MouseButton, ChangedItemType, uint> Click
{
add => _clickSubscriber.Event += value;
remove => _clickSubscriber.Event -= value;
}
public void Reattach(bool attach)
public event Action<ChangedItemType, uint> Tooltip
{
add => _tooltipSubscriber.Event += value;
remove => _tooltipSubscriber.Event -= value;
}
public event Action<nint, string, nint, nint, nint> CreatingCharacterBase
{
add => _creatingCharacterBase.Event += value;
remove => _creatingCharacterBase.Event -= value;
}
public event Action<nint, string, nint> CreatedCharacterBase
{
add => _createdCharacterBase.Event += value;
remove => _createdCharacterBase.Event -= value;
}
public Actor GameObjectFromDrawObject(IntPtr drawObject)
=> Available ? _drawObjectInfo.Invoke(drawObject).Item1 : Actor.Null;
public int CutsceneParent(int idx)
=> Available ? _cutsceneParent.Invoke(idx) : -1;
public void RedrawObject(Actor actor, RedrawType settings)
{
if (!actor || !Available)
return;
try
{
_redrawSubscriber.Invoke(actor.Index, settings);
}
catch (Exception e)
{
PluginLog.Debug($"Failure redrawing object:\n{e}");
}
}
public void Reattach()
{
try
{
@ -54,22 +93,19 @@ public unsafe class PenumbraAttach : IDisposable
throw new Exception(
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
if (!attach)
return;
_tooltipSubscriber.Enable();
_clickSubscriber.Enable();
CreatingCharacterBase.Enable();
CreatedCharacterBase.Enable();
_creatingCharacterBase.Enable();
_createdCharacterBase.Enable();
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(Dalamud.PluginInterface);
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(Dalamud.PluginInterface);
_redrawSubscriber = Ipc.RedrawObject.Subscriber(Dalamud.PluginInterface);
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(Dalamud.PluginInterface);
Available = true;
PluginLog.Debug("Glamourer attached to Penumbra.");
Glamourer.Log.Debug("Glamourer attached to Penumbra.");
}
catch (Exception e)
{
PluginLog.Debug($"Could not attach to Penumbra:\n{e}");
Glamourer.Log.Debug($"Could not attach to Penumbra:\n{e}");
}
}
@ -77,8 +113,8 @@ public unsafe class PenumbraAttach : IDisposable
{
_tooltipSubscriber.Disable();
_clickSubscriber.Disable();
CreatingCharacterBase.Disable();
CreatedCharacterBase.Disable();
_creatingCharacterBase.Disable();
_createdCharacterBase.Disable();
if (Available)
{
Available = false;
@ -91,85 +127,66 @@ public unsafe class PenumbraAttach : IDisposable
Unattach();
_tooltipSubscriber.Dispose();
_clickSubscriber.Dispose();
CreatingCharacterBase.Dispose();
CreatedCharacterBase.Dispose();
_creatingCharacterBase.Dispose();
_createdCharacterBase.Dispose();
_initializedEvent.Dispose();
_disposedEvent.Dispose();
}
private static void PenumbraTooltip(ChangedItemType type, uint _)
{
if (type == ChangedItemType.Item)
ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
}
//private static void PenumbraTooltip(ChangedItemType type, uint _)
//{
// if (type == ChangedItemType.Item)
// ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
//}
//
//private void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id)
//{
// if (button != MouseButton.Right || type != ChangedItemType.Item)
// return;
//
// var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(Dalamud.GameData, id)!;
// var writeItem = new Item2(item, string.Empty);
//
// UpdateItem(_objects.GPosePlayer, writeItem);
// UpdateItem(_objects.Player, writeItem);
//}
private static void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id)
{
if (button != MouseButton.Right || type != ChangedItemType.Item)
return;
var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(Dalamud.GameData, id)!;
var writeItem = new Item2(item, string.Empty);
UpdateItem(ObjectManager.GPosePlayer, writeItem);
UpdateItem(ObjectManager.Player, writeItem);
}
private static void UpdateItem(Actor actor, Item2 item2)
{
if (!actor || !actor.DrawObject)
return;
switch (item2.EquippableTo)
{
case EquipSlot.MainHand:
{
var off = item2.HasSubModel
? new CharacterWeapon(item2.SubModel.id, item2.SubModel.type, item2.SubModel.variant, actor.DrawObject.OffHand.Stain)
: item2.IsBothHand
? CharacterWeapon.Empty
: actor.OffHand;
var main = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, actor.DrawObject.MainHand.Stain);
Glamourer.RedrawManager.LoadWeapon(actor, main, off);
return;
}
case EquipSlot.OffHand:
{
var off = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, actor.DrawObject.OffHand.Stain);
var main = actor.MainHand;
Glamourer.RedrawManager.LoadWeapon(actor, main, off);
return;
}
default:
{
var current = actor.DrawObject.Equip[item2.EquippableTo];
var armor = new CharacterArmor(item2.MainModel.id, (byte)item2.MainModel.variant, current.Stain);
Glamourer.RedrawManager.UpdateSlot(actor.DrawObject, item2.EquippableTo, armor);
return;
}
}
}
public Actor GameObjectFromDrawObject(IntPtr drawObject)
=> Available ? _drawObjectInfo.Invoke(drawObject).Item1 : IntPtr.Zero;
public int CutsceneParent(int idx)
=> Available ? _cutsceneParent.Invoke(idx) : -1;
public void RedrawObject(GameObject? actor, RedrawType settings)
{
if (actor == null || !Available)
return;
try
{
_redrawSubscriber.Invoke(actor, settings);
}
catch (Exception e)
{
PluginLog.Debug($"Failure redrawing object:\n{e}");
}
}
//private static void UpdateItem(Actor actor, Item2 item2)
//{
// if (!actor || !actor.DrawObject)
// return;
//
// switch (item2.EquippableTo)
// {
// case EquipSlot.MainHand:
// {
// var off = item2.HasSubModel
// ? new CharacterWeapon(item2.SubModel.id, item2.SubModel.type, item2.SubModel.variant, actor.DrawObject.OffHand.Stain)
// : item2.IsBothHand
// ? CharacterWeapon.Empty
// : actor.OffHand;
// var main = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant,
// actor.DrawObject.MainHand.Stain);
// Glamourer.RedrawManager.LoadWeapon(actor, main, off);
// return;
// }
// case EquipSlot.OffHand:
// {
// var off = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant,
// actor.DrawObject.OffHand.Stain);
// var main = actor.MainHand;
// Glamourer.RedrawManager.LoadWeapon(actor, main, off);
// return;
// }
// default:
// {
// var current = actor.DrawObject.Equip[item2.EquippableTo];
// var armor = new CharacterArmor(item2.MainModel.id, (byte)item2.MainModel.variant, current.Stain);
// Glamourer.RedrawManager.UpdateSlot(actor.DrawObject, item2.EquippableTo, armor);
// return;
// }
// }
//}
// Update objects without triggering PlayerWatcher Events,
// then manually redraw using Penumbra.

View file

@ -1,7 +1,6 @@
using System;
using Glamourer.Customization;
using Glamourer.Util;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -103,6 +102,9 @@ public class DesignBase
return SetArmor(slot, set, variant, name, itemId);
}
protected bool SetArmor(EquipSlot slot, Item item)
=> SetArmor(slot, item.ModelBase, item.Variant, item.Name, item.ItemId);
protected bool UpdateArmor(EquipSlot slot, CharacterArmor armor, bool force)
{
if (!force)

View file

@ -1,5 +1,4 @@
using Dalamud.Utility;
using Glamourer.Util;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -62,6 +61,9 @@ public readonly struct Weapon
=> Model.Stain;
public Weapon WithStain(StainId id)
=> new(Name, ItemId, Model with { Stain = id }, Type);
public Weapon(string name, uint itemId, CharacterWeapon weapon, FullEquipType type)
{
Name = name;
@ -81,4 +83,23 @@ public readonly struct Weapon
? new Weapon()
: new Weapon(name, itemId, weapon, offType);
}
public Weapon(Lumina.Excel.GeneratedSheets.Item item, bool offhand)
{
Name = string.Intern(item.Name.ToDalamudString().TextValue);
ItemId = item.RowId;
Type = item.ToEquipType();
var offType = Type.Offhand();
var model = offhand && offType == Type ? item.ModelSub : item.ModelMain;
Valid = Type.ToSlot() switch
{
EquipSlot.MainHand when !offhand => true,
EquipSlot.MainHand when offhand && offType == Type => true,
EquipSlot.OffHand when offhand => true,
_ => false,
};
Model.Set = (SetId)model;
Model.Type = (WeaponType)(model >> 16);
Model.Variant = (byte)(model >> 32);
}
}

View file

@ -0,0 +1,88 @@
using System;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Api;
using Glamourer.Customization;
using Glamourer.Interop;
using Glamourer.State;
using Glamourer.Util;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer;
public class DrawObjectManager : IDisposable
{
private readonly ItemManager _items;
private readonly ActorManager _actors;
private readonly ActiveDesign.Manager _manager;
private readonly Interop.Interop _interop;
private readonly PenumbraAttach _penumbra;
public DrawObjectManager(ItemManager items, ActorManager actors, ActiveDesign.Manager manager, Interop.Interop interop,
PenumbraAttach penumbra)
{
_items = items;
_actors = actors;
_manager = manager;
_interop = interop;
_penumbra = penumbra;
_interop.EquipUpdate += FixEquipment;
_penumbra.CreatingCharacterBase += ApplyActiveDesign;
}
private void FixEquipment(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item)
{
var customize = drawObject.Customize;
var (changed, newArmor) = _items.RestrictedGear.ResolveRestricted(item, slot, customize.Race, customize.Gender);
if (!changed)
return;
Glamourer.Log.Verbose(
$"Invalid armor {item.Set.Value}-{item.Variant} for {customize.Gender} {customize.Race} changed to {newArmor.Set.Value}-{newArmor.Variant}.");
item = newArmor;
}
private unsafe void ApplyActiveDesign(nint gameObjectPtr, string collectionName, nint modelIdPtr, nint customizePtr, nint equipDataPtr)
{
var gameObject = (Actor)gameObjectPtr;
ref var modelId = ref *(uint*)modelIdPtr;
// Do not apply anything if the game object model id does not correspond to the draw object model id.
// This is the case if the actor is transformed to a different creature.
if (gameObject.ModelId != modelId)
return;
var identifier = _actors.FromObject((GameObject*)gameObjectPtr, out _, true, true);
if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var design))
return;
// Compare game object customize data against draw object customize data for transformations.
// Apply customization if they correspond and there is customization to apply.
var gameObjectCustomize = gameObject.Customize;
var customize = new Customize((CustomizeData*)customizePtr);
if (gameObjectCustomize.Equals(customize))
customize.Load(design.Customize());
// Compare game object equip data against draw object equip data for transformations.
// Apply each piece of equip that should be applied if they correspond.
var gameObjectEquip = gameObject.Equip;
var equip = new CharacterEquip((CharacterArmor*)equipDataPtr);
if (gameObjectEquip.Equals(equip))
{
var saveEquip = design.Equipment();
foreach (var slot in EquipSlotExtensions.EquipmentSlots)
{
(_, equip[slot]) =
Glamourer.Items.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender);
}
}
}
public void Dispose()
{
_penumbra.CreatingCharacterBase -= ApplyActiveDesign;
_interop.EquipUpdate -= FixEquipment;
}
}

View file

@ -1,11 +1,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using Dalamud.Game.Command;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Api;
using Glamourer.Customization;
using Glamourer.Designs;
@ -13,7 +12,6 @@ using Glamourer.Gui;
using Glamourer.Interop;
using Glamourer.State;
using Glamourer.Util;
using ImGuizmoNET;
using OtterGui.Classes;
using OtterGui.Log;
using Penumbra.GameData.Actors;
@ -39,14 +37,17 @@ public class Glamourer : IDalamudPlugin
public static GlamourerConfig Config = null!;
public static Logger Log = null!;
public static ActorManager Actors = null!;
public static PenumbraAttach Penumbra = null!;
public static ICustomizationManager Customization = null!;
public static RedrawManager RedrawManager = null!;
public static ItemManager Items = null!;
public readonly FixedDesigns FixedDesigns;
public readonly CurrentManipulations CurrentManipulations;
private readonly Interop.Interop _interop;
private readonly PenumbraAttach _penumbra;
private readonly ObjectManager _objectManager;
private readonly Design.Manager _designManager;
private readonly ActiveDesign.Manager _stateManager;
private readonly DrawObjectManager _drawObjectManager;
private readonly DesignFileSystem _fileSystem;
private readonly FrameworkManager _framework;
private readonly WindowSystem _windowSystem = new("Glamourer");
@ -65,6 +66,8 @@ public class Glamourer : IDalamudPlugin
Log = new Logger();
_framework = new FrameworkManager(Dalamud.Framework, Log);
_interop = new Interop.Interop();
_penumbra = new PenumbraAttach();
Items = new ItemManager(Dalamud.PluginInterface, Dalamud.GameData);
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData);
@ -72,17 +75,19 @@ public class Glamourer : IDalamudPlugin
Backup.CreateBackup(pluginInterface.ConfigDirectory, BackupFiles(Dalamud.PluginInterface));
Config = GlamourerConfig.Load();
Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, Dalamud.GameGui,
i => (short)Penumbra.CutsceneParent(i));
_objectManager = new ObjectManager(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects);
Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.Framework, Dalamud.GameData,
Dalamud.GameGui, i => (short)_penumbra.CutsceneParent(i));
_designManager = new Design.Manager(Dalamud.PluginInterface, _framework);
_fileSystem = new DesignFileSystem(_designManager, Dalamud.PluginInterface, _framework);
FixedDesigns = new FixedDesigns();
CurrentManipulations = new CurrentManipulations();
_stateManager = new ActiveDesign.Manager(Actors, _objectManager, _interop, _penumbra);
//GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface);
RedrawManager = new RedrawManager(FixedDesigns, CurrentManipulations);
RedrawManager = new RedrawManager(FixedDesigns, _stateManager);
_drawObjectManager = new DrawObjectManager(Items, Actors, _stateManager, _interop, _penumbra);
Dalamud.Commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer)
{
@ -93,7 +98,7 @@ public class Glamourer : IDalamudPlugin
HelpMessage = $"Use Glamourer Functions: {HelpString}",
});
_interface = new Interface(Items, CurrentManipulations, _designManager, _fileSystem);
_interface = new Interface(Dalamud.PluginInterface, Items, _stateManager, _designManager, _fileSystem, _objectManager);
_windowSystem.AddWindow(_interface);
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
//FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x);
@ -108,13 +113,15 @@ public class Glamourer : IDalamudPlugin
public void Dispose()
{
_drawObjectManager?.Dispose();
RedrawManager?.Dispose();
Penumbra?.Dispose();
if (_windowSystem != null)
Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
_interface?.Dispose();
_fileSystem?.Dispose();
//GlamourerIpc.Dispose();
_interop?.Dispose();
_penumbra?.Dispose();
_framework?.Dispose();
Items?.Dispose();
Dalamud.Commands.RemoveHandler(ApplyCommandString);

View file

@ -1,11 +1,12 @@
using System.Numerics;
using System;
using System.Numerics;
using Glamourer.Customization;
using ImGuiNET;
using OtterGui.Raii;
namespace Glamourer.Gui.Customization;
internal partial class CustomizationDrawer
public partial class CustomizationDrawer
{
private const string ColorPickerPopupName = "ColorPicker";
@ -26,7 +27,6 @@ internal partial class CustomizationDrawer
DataInputInt(current);
ImGui.TextUnformatted(_currentOption);
}
DrawColorPickerPopup();
}
@ -56,10 +56,9 @@ internal partial class CustomizationDrawer
private (int, CustomizeData) GetCurrentCustomization(CustomizeIndex index)
{
var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face);
if (!_set.IsAvailable(index) || current >= 0)
return (current, custom!.Value);
if (_set.IsAvailable(index) && current < 0)
throw new Exception($"Read invalid customization value {_customize[index]} for {index}.");
Glamourer.Log.Warning($"Read invalid customization value {_customize[index]} for {index}.");
return (0, _set.Data(index, 0));
return (current, custom!.Value);
}
}

View file

@ -6,12 +6,12 @@ using Glamourer.Util;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Customization;
internal partial class CustomizationDrawer
public partial class CustomizationDrawer
{
private void DrawRaceGenderSelector()
{
@ -27,20 +27,19 @@ internal partial class CustomizationDrawer
private void DrawGenderSelector()
{
using var font = ImRaii.PushFont(UiBuilder.IconFont);
var icon = _customize.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
var restricted = _customize.Race == Race.Hrothgar;
if (restricted)
icon = FontAwesomeIcon.MarsDouble;
var icon = _customize.Gender switch
{
Gender.Male when _customize.Race is Race.Hrothgar => FontAwesomeIcon.MarsDouble,
Gender.Male => FontAwesomeIcon.Mars,
Gender.Female => FontAwesomeIcon.Venus,
if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, restricted, true))
_ => throw new Exception($"Gender value {_customize.Gender} is not a valid gender for a design."),
};
if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, icon == FontAwesomeIcon.MarsDouble, true))
return;
var gender = _customize.Gender == Gender.Male ? Gender.Female : Gender.Male;
if (!_customize.ChangeGender(_equip, gender))
return;
foreach (var actor in _actors.Where(a => a))
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
Changed |= _customize.ChangeGender(CharacterEquip.Null, _customize.Gender is Gender.Male ? Gender.Female : Gender.Male);
}
private void DrawRaceCombo()
@ -52,12 +51,8 @@ internal partial class CustomizationDrawer
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
{
if (!ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan)
|| !_customize.ChangeRace(_equip, subRace))
continue;
foreach (var actor in _actors.Where(a => a && a.DrawObject))
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan))
Changed |= _customize.ChangeRace(CharacterEquip.Null, subRace);
}
}
}

View file

@ -1,16 +1,14 @@
using System;
using System.Linq;
using System.Numerics;
using Glamourer.Customization;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui.Customization;
internal partial class CustomizationDrawer
public partial class CustomizationDrawer
{
private const string IconSelectorPopup = "Style Picker";
@ -40,22 +38,23 @@ internal partial class CustomizationDrawer
FaceInputInt(current);
else
DataInputInt(current);
ImGui.TextUnformatted($"{label} ({custom.Value.Value})");
}
DrawIconPickerPopup();
}
private void UpdateFace(CustomizeData data)
private bool UpdateFace(CustomizeData data)
{
// Hrothgar Hack
var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value;
if (_customize.Face == value)
return;
return false;
_customize.Face = value;
foreach (var actor in _actors)
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
Changed |= CustomizeFlag.Face;
return true;
}
private void FaceInputInt(int currentIndex)
@ -78,6 +77,7 @@ internal partial class CustomizationDrawer
if (!popup)
return;
var ret = false;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
for (var i = 0; i < _currentCount; ++i)
@ -107,4 +107,45 @@ internal partial class CustomizationDrawer
ImGui.SameLine();
}
}
// Only used for facial features, so fixed ID.
private void DrawMultiIconSelector()
{
using var bigGroup = ImRaii.Group();
DrawMultiIcons();
ImGui.SameLine();
using var group = ImRaii.Group();
ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y / 2));
_currentCount = 256;
PercentageInputInt();
ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo));
}
private void DrawMultiIcons()
{
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
using var _ = ImRaii.Group();
foreach (var (featureIdx, idx) in options.WithIndex())
{
using var id = SetId(featureIdx);
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
var feature = _set.Data(featureIdx, 0, _customize.Face);
var icon = featureIdx == CustomizeIndex.LegacyTattoo
? _legacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
: Glamourer.Customization.GetIcon(feature.IconId);
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
Vector4.Zero, enabled ? Vector4.One : _redTint))
{
_customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max);
Changed |= _currentFlag;
}
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
if (idx % 4 != 3)
ImGui.SameLine();
}
}
}

View file

@ -1,137 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Reflection;
using Glamourer.Customization;
using Glamourer.Interop;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Customization;
internal partial class CustomizationDrawer
{
private static readonly Vector4 RedTint = new(0.6f, 0.3f, 0.3f, 1f);
private static readonly ImGuiScene.TextureWrap? LegacyTattoo;
private readonly Vector2 _iconSize;
private readonly Vector2 _framedIconSize;
private readonly float _inputIntSize;
private readonly float _comboSelectorSize;
private readonly float _raceSelectorWidth;
private Customize _customize;
private CharacterEquip _equip;
private IReadOnlyCollection<Actor> _actors = Array.Empty<Actor>();
private CustomizationSet _set = null!;
private CustomizationDrawer()
{
_iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2);
_framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
_inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X;
_comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
}
static CustomizationDrawer()
=> LegacyTattoo = GetLegacyTattooIcon();
public static void Dispose()
=> LegacyTattoo?.Dispose();
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon()
{
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
if (resource == null)
return null;
var rawImage = new byte[resource.Length];
var length = resource.Read(rawImage, 0, (int)resource.Length);
return length == resource.Length
? Dalamud.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4)
: null;
}
public static void Draw(Customize customize, CharacterEquip equip, IReadOnlyCollection<Actor> actors, bool locked)
{
var d = new CustomizationDrawer()
{
_customize = customize,
_equip = equip,
_actors = actors,
};
if (!ImGui.CollapsingHeader("Character Customization"))
return;
using var disabled = ImRaii.Disabled(locked);
d.DrawRaceGenderSelector();
d._set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
foreach (var id in d._set.Order[CharaMakeParams.MenuType.Percentage])
d.PercentageSelector(id);
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.IconSelector], d.DrawIconSelector, ImGui.SameLine);
d.DrawMultiIconSelector();
foreach (var id in d._set.Order[CharaMakeParams.MenuType.ListSelector])
d.DrawListSelector(id);
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.ColorPicker], d.DrawColorPicker, ImGui.SameLine);
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.Checkmark], d.DrawCheckbox,
() => ImGui.SameLine(d._inputIntSize + d._framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X));
}
public static void Draw(Customize customize, IReadOnlyCollection<Actor> actors, bool locked = false)
=> Draw(customize, CharacterEquip.Null, actors, locked);
public static void Draw(Customize customize, CharacterEquip equip, bool locked = false)
=> Draw(customize, equip, Array.Empty<Actor>(), locked);
public static void Draw(Customize customize, bool locked = false)
=> Draw(customize, CharacterEquip.Null, Array.Empty<Actor>(), locked);
// Set state for drawing of current customization.
private CustomizeIndex _currentIndex;
private CustomizeValue _currentByte = CustomizeValue.Zero;
private int _currentCount;
private string _currentOption = string.Empty;
// Prepare a new customization option.
private ImRaii.Id SetId(CustomizeIndex index)
{
_currentIndex = index;
_currentByte = _customize[index];
_currentCount = _set.Count(index, _customize.Face);
_currentOption = _set.Option(index);
return ImRaii.PushId((int)index);
}
// Update the current id with a value,
// also update actors if any.
private void UpdateValue(CustomizeValue value)
{
if (_customize[_currentIndex] == value)
return;
_customize[_currentIndex] = value;
UpdateActors();
}
// Update all relevant Actors by calling the UpdateCustomize game function.
private void UpdateActors()
{
foreach (var actor in _actors)
Glamourer.RedrawManager.UpdateCustomize(actor, _customize);
}
}

View file

@ -1,50 +0,0 @@
using System.Numerics;
using Glamourer.Customization;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
namespace Glamourer.Gui.Customization;
internal partial class CustomizationDrawer
{
// Only used for facial features, so fixed ID.
private void DrawMultiIconSelector()
{
using var bigGroup = ImRaii.Group();
DrawMultiIcons();
ImGui.SameLine();
using var group = ImRaii.Group();
ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y / 2));
_currentCount = 256;
PercentageInputInt();
ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo));
}
private void DrawMultiIcons()
{
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
using var _ = ImRaii.Group();
foreach (var (featureIdx, idx) in options.WithIndex())
{
using var id = SetId(featureIdx);
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
var feature = _set.Data(featureIdx, 0, _customize.Face);
var icon = featureIdx == CustomizeIndex.LegacyTattoo
? LegacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
: Glamourer.Customization.GetIcon(feature.IconId);
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
Vector4.Zero, enabled ? Vector4.One : RedTint))
{
_customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max);
UpdateActors();
}
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
if (idx % 4 != 3)
ImGui.SameLine();
}
}
}

View file

@ -6,8 +6,52 @@ using OtterGui.Raii;
namespace Glamourer.Gui.Customization;
internal partial class CustomizationDrawer
public partial class CustomizationDrawer
{
private void PercentageSelector(CustomizeIndex index)
{
using var _ = SetId(index);
using var bigGroup = ImRaii.Group();
DrawPercentageSlider();
ImGui.SameLine();
PercentageInputInt();
ImGui.SameLine();
ImGui.TextUnformatted(_currentOption);
}
private void DrawPercentageSlider()
{
var tmp = (int)_currentByte.Value;
ImGui.SetNextItemWidth(_comboSelectorSize);
if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp))
UpdateValue((CustomizeValue)tmp);
}
private void PercentageInputInt()
{
var tmp = (int)_currentByte.Value;
ImGui.SetNextItemWidth(_inputIntSize);
if (ImGui.InputInt("##text", ref tmp, 1, 1))
UpdateValue((CustomizeValue)Math.Clamp(tmp, 0, _currentCount - 1));
ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]");
}
// Integral input for an icon- or color based item.
private void DataInputInt(int currentIndex)
{
++currentIndex;
ImGui.SetNextItemWidth(_inputIntSize);
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
{
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
var data = _set.Data(_currentIndex, currentIndex, _customize.Face);
UpdateValue(data.Value);
}
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
}
private void DrawListSelector(CustomizeIndex index)
{
using var _ = SetId(index);
@ -44,67 +88,15 @@ internal partial class CustomizationDrawer
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
}
private void PercentageSelector(CustomizeIndex index)
{
using var _ = SetId(index);
using var bigGroup = ImRaii.Group();
DrawPercentageSlider();
ImGui.SameLine();
PercentageInputInt();
ImGui.SameLine();
ImGui.TextUnformatted(_currentOption);
}
private void DrawPercentageSlider()
{
var tmp = (int)_currentByte.Value;
ImGui.SetNextItemWidth(_comboSelectorSize);
if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp))
UpdateValue((CustomizeValue)tmp);
}
private void PercentageInputInt()
{
var tmp = (int)_currentByte.Value;
ImGui.SetNextItemWidth(_inputIntSize);
if (ImGui.InputInt("##text", ref tmp, 1, 1))
UpdateValue((CustomizeValue)Math.Clamp(tmp, 0, _currentCount - 1));
ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]");
}
// Draw one of the four checkboxes for single bool customization options.
private void Checkbox(string label, bool current, Action<bool> setter)
{
var tmp = current;
if (ImGui.Checkbox(label, ref tmp) && tmp != current)
{
setter(tmp);
UpdateActors();
}
}
// Draw a customize checkbox.
private void DrawCheckbox(CustomizeIndex idx)
{
using var id = SetId(idx);
Checkbox(_set.Option(idx), _customize.Get(idx) != CustomizeValue.Zero,
b => _customize.Set(idx, b ? CustomizeValue.Max : CustomizeValue.Zero));
}
// Integral input for an icon- or color based item.
private void DataInputInt(int currentIndex)
var tmp = _currentByte != CustomizeValue.Zero;
if (ImGui.Checkbox(_currentOption, ref tmp))
{
++currentIndex;
ImGui.SetNextItemWidth(_inputIntSize);
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
{
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
var data = _set.Data(_currentIndex, currentIndex, _customize.Face);
UpdateValue(data.Value);
_customize.Set(idx, tmp ? CustomizeValue.Max : CustomizeValue.Zero);
Changed |= _currentFlag;
}
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
}
}

View file

@ -0,0 +1,172 @@
using System;
using System.Numerics;
using System.Reflection;
using System.Runtime.InteropServices;
using Dalamud.Plugin;
using Glamourer.Customization;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Gui.Customization;
public partial class CustomizationDrawer : IDisposable
{
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
private readonly ImGuiScene.TextureWrap? _legacyTattoo;
private bool _withFlags = false;
private Exception? _terminate = null;
private readonly unsafe CustomizeData* _data;
private Customize _customize;
private CustomizationSet _set = null!;
public unsafe CustomizeData CustomizeData
=> *_data;
public CustomizeFlag CurrentFlag { get; private set; }
public CustomizeFlag Changed { get; private set; }
public bool RequiresRedraw
=> Changed.RequiresRedraw();
private bool _locked = false;
private Vector2 _iconSize;
private Vector2 _framedIconSize;
private float _inputIntSize;
private float _comboSelectorSize;
private float _raceSelectorWidth;
public CustomizationDrawer(DalamudPluginInterface pi)
{
_legacyTattoo = GetLegacyTattooIcon(pi);
unsafe
{
_data = (CustomizeData*)Marshal.AllocHGlobal(sizeof(CustomizeData));
*_data = Customize.Default;
_customize = new Customize(_data);
}
}
public void Dispose()
{
_legacyTattoo?.Dispose();
unsafe
{
Marshal.FreeHGlobal((nint)_data);
}
}
public bool Draw(Customize current, bool locked)
{
_withFlags = false;
CurrentFlag = CustomizeFlagExtensions.All;
Init(current, locked);
return DrawInternal();
}
public bool Draw(Customize current, CustomizeFlag currentFlags, bool locked)
{
_withFlags = true;
CurrentFlag = currentFlags;
Init(current, locked);
return DrawInternal();
}
private void Init(Customize current, bool locked)
{
UpdateSizes();
_terminate = null;
Changed = 0;
_customize.Load(current);
_locked = locked;
}
// Set state for drawing of current customization.
private CustomizeIndex _currentIndex;
private CustomizeFlag _currentFlag;
private CustomizeValue _currentByte = CustomizeValue.Zero;
private int _currentCount;
private string _currentOption = string.Empty;
// Prepare a new customization option.
private ImRaii.Id SetId(CustomizeIndex index)
{
_currentIndex = index;
_currentFlag = index.ToFlag();
_currentByte = _customize[index];
_currentCount = _set.Count(index, _customize.Face);
_currentOption = _set.Option(index);
return ImRaii.PushId((int)index);
}
// Update the current id with a new value.
private void UpdateValue(CustomizeValue value)
{
if (_currentByte == value)
return;
_customize[_currentIndex] = value;
Changed |= _currentFlag;
}
private bool DrawInternal()
{
using var disabled = ImRaii.Disabled(_locked);
try
{
DrawRaceGenderSelector();
_set = Glamourer.Customization.GetList(_customize.Clan, _customize.Gender);
foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage])
PercentageSelector(id);
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.IconSelector], DrawIconSelector, ImGui.SameLine);
DrawMultiIconSelector();
foreach (var id in _set.Order[CharaMakeParams.MenuType.ListSelector])
DrawListSelector(id);
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine);
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.Checkmark], DrawCheckbox,
() => ImGui.SameLine(_inputIntSize + _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X));
return Changed != 0;
}
catch (Exception ex)
{
_terminate = ex;
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF4040FF);
ImGui.NewLine();
ImGuiUtil.TextWrapped(_terminate.ToString());
return false;
}
}
private void UpdateSizes()
{
_iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2);
_framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
_inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X;
_comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
}
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi)
{
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
if (resource == null)
return null;
var rawImage = new byte[resource.Length];
var length = resource.Read(rawImage, 0, (int)resource.Length);
return length == resource.Length
? pi.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4)
: null;
}
}

View file

@ -1,7 +1,9 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Dalamud.Interface;
using Dalamud.Utility;
using Glamourer.Designs;
using Glamourer.Util;
using ImGuiNET;
@ -13,26 +15,55 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment;
public partial class EquipmentDrawer
public class EquipmentDrawer
{
private readonly ItemManager _items;
private readonly FilterComboColors _stainCombo;
private readonly StainData _stainData;
private readonly ItemCombo[] _itemCombo;
private readonly Dictionary<(FullEquipType, EquipSlot), WeaponCombo> _weaponCombo;
public EquipmentDrawer(ItemManager items)
{
_items = items;
_stainData = items.Stains;
_stainCombo = new FilterComboColors(140,
_stainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))));
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(items, e)).ToArray();
_weaponCombo = new Dictionary<(FullEquipType, EquipSlot), WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
foreach (var type in Enum.GetValues<FullEquipType>())
{
if (type.ToSlot() is EquipSlot.MainHand)
_weaponCombo.TryAdd((type, EquipSlot.MainHand), new WeaponCombo(items, type, EquipSlot.MainHand));
else if (type.ToSlot() is EquipSlot.OffHand)
_weaponCombo.TryAdd((type, EquipSlot.OffHand), new WeaponCombo(items, type, EquipSlot.OffHand));
var offhand = type.Offhand();
if (offhand is not FullEquipType.Unknown && !_weaponCombo.ContainsKey((offhand, EquipSlot.OffHand)))
_weaponCombo.TryAdd((offhand, EquipSlot.OffHand), new WeaponCombo(items, type, EquipSlot.OffHand));
}
public bool DrawArmor(DesignBase design, EquipSlot slot, out Item armor)
_weaponCombo.Add((FullEquipType.Unknown, EquipSlot.MainHand), new WeaponCombo(items, FullEquipType.Unknown, EquipSlot.MainHand));
}
private string VerifyRestrictedGear(Item gear, EquipSlot slot, Gender gender, Race race)
{
Debug.Assert(slot.IsEquipment() || slot.IsAccessory());
if (slot.IsAccessory())
return gear.Name;
var (changed, _) = _items.RestrictedGear.ResolveRestricted(gear.Model, slot, race, gender);
if (changed)
return gear.Name + " (Restricted)";
return gear.Name;
}
public bool DrawArmor(Item current, EquipSlot slot, out Item armor, Gender gender = Gender.Unknown, Race race = Race.Unknown)
{
Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(DrawArmor)} on {slot}.");
var combo = _itemCombo[slot.ToIndex()];
armor = design.Armor(slot);
var change = combo.Draw(armor.Name, armor.ItemId, 320 * ImGuiHelpers.GlobalScale);
armor = current;
var change = combo.Draw(VerifyRestrictedGear(armor, slot, gender, race), armor.ItemId, 320 * ImGuiHelpers.GlobalScale);
if (armor.ModelBase.Value != 0)
{
ImGuiUtil.HoverTooltip("Right-click to clear.");
@ -41,6 +72,10 @@ public partial class EquipmentDrawer
change = true;
armor = ItemManager.NothingItem(slot);
}
else if (change)
{
armor = combo.CurrentSelection.WithStain(armor.Stain);
}
}
else if (change)
{
@ -50,14 +85,77 @@ public partial class EquipmentDrawer
return change;
}
public bool DrawStain(DesignBase design, EquipSlot slot, out Stain stain)
public bool DrawStain(StainId current, EquipSlot slot, out Stain stain)
{
Debug.Assert(slot.IsEquipment() || slot.IsAccessory());
var armor = design.Armor(slot);
var found = _stainData.TryGetValue(armor.Stain, out stain);
if (!_stainCombo.Draw($"##stain{slot}", stain.RgbaColor, found))
var found = _stainData.TryGetValue(current, out stain);
if (!_stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found))
return false;
return _stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain);
}
public bool DrawMainhand(Weapon current, bool drawAll, out Weapon weapon)
{
weapon = current;
if (!_weaponCombo.TryGetValue((drawAll ? FullEquipType.Unknown : current.Type, EquipSlot.MainHand), out var combo))
return false;
if (!combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale))
return false;
weapon = combo.CurrentSelection.WithStain(current.Stain);
return true;
}
public bool DrawOffhand(Weapon current, FullEquipType mainType, out Weapon weapon)
{
weapon = current;
var offType = mainType.Offhand();
if (offType == FullEquipType.Unknown)
return false;
if (!_weaponCombo.TryGetValue((offType, EquipSlot.OffHand), out var combo))
return false;
var change = combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale);
if (offType.ToSlot() is EquipSlot.OffHand && weapon.ModelBase.Value != 0)
{
ImGuiUtil.HoverTooltip("Right-click to clear.");
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
change = true;
weapon = ItemManager.NothingItem(offType);
}
}
else if (change)
{
weapon = combo.CurrentSelection.WithStain(current.Stain);
}
return change;
}
public bool DrawApply(Design design, EquipSlot slot, out bool enabled)
{
enabled = design.DoApplyEquip(slot);
var tmp = enabled;
if (!ImGuiUtil.Checkbox($"##apply{slot}", string.Empty, enabled, v => tmp = v))
return false;
var change = enabled != tmp;
enabled = tmp;
return change;
}
public bool DrawApplyStain(Design design, EquipSlot slot, out bool enabled)
{
enabled = design.DoApplyStain(slot);
var tmp = enabled;
if (!ImGuiUtil.Checkbox($"##applyStain{slot}", string.Empty, enabled, v => tmp = v))
return false;
var change = enabled != tmp;
enabled = tmp;
return change;
}
}

View file

@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Interface;
using Glamourer.Structs;
using Glamourer.Util;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
@ -12,7 +8,6 @@ using OtterGui.Classes;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Item = Glamourer.Designs.Item;
namespace Glamourer.Gui.Equipment;
@ -20,37 +15,38 @@ namespace Glamourer.Gui.Equipment;
public sealed class ItemCombo : FilterComboCache<Item>
{
public readonly string Label;
public readonly EquipSlot Slot;
private uint _lastItemId;
private uint _previewId;
private uint _currentItem;
public ItemCombo(ItemManager items, EquipSlot slot)
: base(GetItems(items, slot))
: base(() => GetItems(items, slot))
{
Label = GetLabel(slot);
Slot = slot;
_lastItemId = ItemManager.NothingId(slot);
_previewId = _lastItemId;
_currentItem = ItemManager.NothingId(slot);
}
protected override void DrawList(float width, float itemHeight)
{
if (_previewId != _lastItemId)
{
_lastItemId = _previewId;
CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _lastItemId);
CurrentSelection = Items[CurrentSelectionIdx];
}
base.DrawList(width, itemHeight);
if (NewSelection != null && Items.Count > NewSelection.Value)
CurrentSelection = Items[NewSelection.Value];
}
protected override int UpdateCurrentSelected(int currentSelected)
{
if (CurrentSelection.ItemId != _currentItem)
{
CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem);
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
return base.UpdateCurrentSelected(CurrentSelectionIdx);
}
return currentSelected;
}
public bool Draw(string previewName, uint previewIdx, float width)
{
_previewId = previewIdx;
return Draw(Label, previewName, width, ImGui.GetTextLineHeightWithSpacing());
_currentItem = previewIdx;
return Draw(Label, previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
}
protected override bool DrawSelectable(int globalIdx, bool selected)
@ -58,6 +54,7 @@ public sealed class ItemCombo : FilterComboCache<Item>
var obj = Items[globalIdx];
var name = ToString(obj);
var ret = ImGui.Selectable(name, selected);
ImGui.SameLine();
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
ImGuiUtil.RightAlign($"({obj.ModelBase.Value}-{obj.Variant})");
return ret;
@ -89,12 +86,18 @@ public sealed class ItemCombo : FilterComboCache<Item>
};
}
private static IEnumerable<Item> GetItems(ItemManager items, EquipSlot slot)
private static IReadOnlyList<Item> GetItems(ItemManager items, EquipSlot slot)
{
var nothing = ItemManager.NothingItem(slot);
if (!items.Items.TryGetValue(slot.ToEquipType(), out var list))
return new[] { nothing };
return new[]
{
nothing,
};
return list.Select(i => new Item(i)).Append(ItemManager.SmallClothesItem(slot)).OrderBy(i => i.Name).Prepend(nothing);
var enumerable = list.Select(i => new Item(i));
if (slot.IsEquipment())
enumerable = enumerable.Append(ItemManager.SmallClothesItem(slot));
return enumerable.OrderBy(i => i.Name).Prepend(nothing).ToList();
}
}

View file

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Glamourer.Designs;
using Glamourer.Util;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui.Equipment;
public sealed class WeaponCombo : FilterComboCache<Weapon>
{
public readonly string Label;
private uint _currentItem;
public WeaponCombo(ItemManager items, FullEquipType type, EquipSlot offhand)
: base(offhand is EquipSlot.OffHand ? () => GetOff(items, type) : () => GetMain(items, type))
=> Label = GetLabel(offhand);
protected override void DrawList(float width, float itemHeight)
{
base.DrawList(width, itemHeight);
if (NewSelection != null && Items.Count > NewSelection.Value)
CurrentSelection = Items[NewSelection.Value];
}
protected override int UpdateCurrentSelected(int currentSelected)
{
if (CurrentSelection.ItemId != _currentItem)
{
CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem);
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
return base.UpdateCurrentSelected(CurrentSelectionIdx);
}
return currentSelected;
}
public bool Draw(string previewName, uint previewIdx, float width)
{
_currentItem = previewIdx;
return Draw(Label, previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
}
protected override bool DrawSelectable(int globalIdx, bool selected)
{
var obj = Items[globalIdx];
var name = ToString(obj);
var ret = ImGui.Selectable(name, selected);
ImGui.SameLine();
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
ImGuiUtil.RightAlign($"({obj.ModelBase.Value}-{obj.WeaponBase.Value}-{obj.Variant})");
return ret;
}
protected override bool IsVisible(int globalIndex, LowerString filter)
=> base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelBase.ToString());
protected override string ToString(Weapon obj)
=> obj.Name;
private static string GetLabel(EquipSlot offhand)
{
var sheet = Dalamud.GameData.GetExcelSheet<Addon>()!;
return offhand is EquipSlot.OffHand
? sheet.GetRow(739)?.Text.ToString() ?? "Off Hand"
: sheet.GetRow(738)?.Text.ToString() ?? "Main Hand";
}
private static IReadOnlyList<Weapon> GetMain(ItemManager items, FullEquipType type)
{
var list = new List<Weapon>();
if (type is FullEquipType.Unknown)
foreach (var t in Enum.GetValues<FullEquipType>().Where(t => t.ToSlot() == EquipSlot.MainHand))
list.AddRange(items.Items[t].Select(w => new Weapon(w, false)));
else if (type.ToSlot() is EquipSlot.MainHand)
list.AddRange(items.Items[type].Select(w => new Weapon(w, false)));
list.Sort((w1, w2) => string.CompareOrdinal(w1.Name, w2.Name));
return list;
}
private static IReadOnlyList<Weapon> GetOff(ItemManager items, FullEquipType type)
{
if (type.ToSlot() == EquipSlot.OffHand)
{
var nothing = ItemManager.NothingItem(type);
if (!items.Items.TryGetValue(type, out var list))
return new[]
{
nothing,
};
return list.Select(w => new Weapon(w, true)).OrderBy(w => w.Name).Prepend(nothing).ToList();
}
else if (items.Items.TryGetValue(type, out var list))
{
return list.Select(w => new Weapon(w, true)).OrderBy(w => w.Name).ToList();
}
return Array.Empty<Weapon>();
}
}

View file

@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Interface;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET;
@ -20,12 +19,14 @@ internal partial class Interface
private class ActorTab
{
private readonly Interface _main;
private readonly CurrentManipulations _manipulations;
private readonly ActiveDesign.Manager _activeDesigns;
private readonly ObjectManager _objects;
public ActorTab(Interface main, CurrentManipulations manipulations)
public ActorTab(Interface main, ActiveDesign.Manager activeDesigns, ObjectManager objects)
{
_main = main;
_manipulations = manipulations;
_activeDesigns = activeDesigns;
_objects = objects;
}
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
@ -40,7 +41,7 @@ internal partial class Interface
return;
DrawActorSelector();
if (!ObjectManager.Actors.TryGetValue(_identifier, out _currentData))
if (!_objects.TryGetValue(_identifier, out _currentData))
_currentData = ObjectManager.ActorData.Invalid;
else
_currentLabel = _currentData.Label;
@ -66,14 +67,31 @@ internal partial class Interface
_currentSave.Update(_currentData.Objects[0]);
RevertButton();
CustomizationDrawer.Draw(_currentSave.Customize(), _currentSave.Equipment(), _currentData.Objects,
_identifier.Type == IdentifierType.Special);
if (_main._customizationDrawer.Draw(_currentSave.Customize(), _identifier.Type == IdentifierType.Special))
_activeDesigns.ChangeCustomize(_currentSave, _main._customizationDrawer.Changed, _main._customizationDrawer.CustomizeData, false);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
_main._equipmentDrawer.DrawStain(_currentSave, slot, out var stain);
var current = _currentSave.Armor(slot);
if (_main._equipmentDrawer.DrawStain(current.Stain, slot, out var stain))
_activeDesigns.ChangeStain(_currentSave, slot, stain.RowIndex, false);
ImGui.SameLine();
_main._equipmentDrawer.DrawArmor(_currentSave, slot, out var armor);
if (_main._equipmentDrawer.DrawArmor(current, slot, out var armor, _currentSave.Customize().Gender, _currentSave.Customize().Race))
_activeDesigns.ChangeEquipment(_currentSave, slot, armor, false);
}
var currentMain = _currentSave.WeaponMain;
if (_main._equipmentDrawer.DrawStain(currentMain.Stain, EquipSlot.MainHand, out var stainMain))
_activeDesigns.ChangeStain(_currentSave, EquipSlot.MainHand, stainMain.RowIndex, false);
ImGui.SameLine();
_main._equipmentDrawer.DrawMainhand(currentMain, true, out var main);
if (currentMain.Type.Offhand() != FullEquipType.Unknown)
{
var currentOff = _currentSave.WeaponOff;
if (_main._equipmentDrawer.DrawStain(currentOff.Stain, EquipSlot.OffHand, out var stainOff))
_activeDesigns.ChangeStain(_currentSave, EquipSlot.OffHand, stainOff.RowIndex, false);
ImGui.SameLine();
_main._equipmentDrawer.DrawOffhand(currentOff, main.Type, out var off);
}
}
@ -83,9 +101,7 @@ internal partial class Interface
private unsafe void RevertButton()
{
if (ImGui.Button("Revert"))
{
_manipulations.DeleteSave(_identifier);
_activeDesigns.RevertDesign(_currentSave!);
//foreach (var actor in _currentData.Objects)
// _currentSave!.ApplyToActor(actor);
//
@ -93,8 +109,6 @@ internal partial class Interface
// _currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]);
//
//_currentSave!.Reset();
}
if (_currentData.Objects.Count > 0)
ImGui.TextUnformatted(_currentData.Objects[0].Pointer->GameObject.DataID.ToString());
//VisorBox();
@ -215,24 +229,24 @@ internal partial class Interface
if (!child)
return;
ObjectManager.Update();
_objects.Update();
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
var remainder = ImGuiClip.FilteredClippedDraw(ObjectManager.List, skips, CheckFilter, DrawSelectable);
var remainder = ImGuiClip.FilteredClippedDraw(_objects, skips, CheckFilter, DrawSelectable);
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
}
private bool CheckFilter((ActorIdentifier, ObjectManager.ActorData) pair)
=> _actorFilter.IsEmpty || pair.Item2.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase);
private bool CheckFilter(KeyValuePair<ActorIdentifier, ObjectManager.ActorData> pair)
=> _actorFilter.IsEmpty || pair.Value.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase);
private void DrawSelectable((ActorIdentifier, ObjectManager.ActorData) pair)
private void DrawSelectable(KeyValuePair<ActorIdentifier, ObjectManager.ActorData> pair)
{
var equal = pair.Item1.Equals(_identifier);
if (ImGui.Selectable(pair.Item2.Label, equal) && !equal)
var equal = pair.Key.Equals(_identifier);
if (ImGui.Selectable(pair.Value.Label, equal) && !equal)
{
_identifier = pair.Item1.CreatePermanent();
_currentData = pair.Item2;
_currentSave = _currentData.Valid ? _manipulations.GetOrCreateSave(_currentData.Objects[0]) : null;
_identifier = pair.Key.CreatePermanent();
_currentData = pair.Value;
_currentSave = _currentData.Valid ? _activeDesigns.GetOrCreateSave(_currentData.Objects[0]) : null;
}
}
@ -243,325 +257,14 @@ internal partial class Interface
var buttonWidth = new Vector2(_actorSelectorWidth / 2, 0);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
, "Select the local player character.", !ObjectManager.Player, true))
_identifier = ObjectManager.Player.GetIdentifier();
, "Select the local player character.", !_objects.Player, true))
_identifier = _objects.Player.GetIdentifier();
ImGui.SameLine();
Actor targetActor = Dalamud.Targets.Target?.Address;
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth,
"Select the current target, if it is in the list.", ObjectManager.IsInGPose || !targetActor, true))
"Select the current target, if it is in the list.", _objects.IsInGPose || !targetActor, true))
_identifier = targetActor.GetIdentifier();
}
}
}
//internal partial class Interface
//{
// private readonly CharacterSave _currentSave = new();
// private string _newDesignName = string.Empty;
// private bool _keyboardFocus;
// private bool _holdShift;
// private bool _holdCtrl;
// private const string DesignNamePopupLabel = "Save Design As...";
// private const uint RedHeaderColor = 0xFF1818C0;
// private const uint GreenHeaderColor = 0xFF18C018;
//
// private void DrawPlayerHeader()
// {
// var color = _player == null ? RedHeaderColor : GreenHeaderColor;
// var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
// using var c = ImRaii.PushColor(ImGuiCol.Text, color)
// .Push(ImGuiCol.Button, buttonColor)
// .Push(ImGuiCol.ButtonHovered, buttonColor)
// .Push(ImGuiCol.ButtonActive, buttonColor);
// using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
// .Push(ImGuiStyleVar.FrameRounding, 0);
// ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f);
// }
//
// private static void DrawCopyClipboardButton(CharacterSave save)
// {
// ImGui.PushFont(UiBuilder.IconFont);
// if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString()))
// ImGui.SetClipboardText(save.ToBase64());
// ImGui.PopFont();
// ImGuiUtil.HoverTooltip("Copy customization code to clipboard.");
// }
//
// private static unsafe void ConditionalApply(CharacterSave save, FFXIVClientStructs.FFXIV.Client.Game.Character.Character* player)
// {
// //if (ImGui.GetIO().KeyShift)
// // save.ApplyOnlyCustomizations(player);
// //else if (ImGui.GetIO().KeyCtrl)
// // save.ApplyOnlyEquipment(player);
// //else
// // save.Apply(player);
// }
//
// private static unsafe void ConditionalApply(CharacterSave save, Character player)
// => ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player.Address);
//
// private static CharacterSave ConditionalCopy(CharacterSave save, bool shift, bool ctrl)
// {
// var copy = save.Copy();
// if (shift)
// {
// copy.Load(new CharacterEquipment());
// copy.SetHatState = false;
// copy.SetVisorState = false;
// copy.SetWeaponState = false;
// copy.WriteEquipment = CharacterEquipMask.None;
// }
// else if (ctrl)
// {
// copy.Load(CharacterCustomization.Default);
// copy.SetHatState = false;
// copy.SetVisorState = false;
// copy.SetWeaponState = false;
// copy.WriteCustomizations = false;
// }
//
// return copy;
// }
//
// private bool DrawApplyClipboardButton()
// {
// ImGui.PushFont(UiBuilder.IconFont);
// var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()) && _player != null;
// ImGui.PopFont();
// ImGuiUtil.HoverTooltip(
// "Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment.");
//
// if (!applyButton)
// return false;
//
// try
// {
// var text = ImGui.GetClipboardText();
// if (!text.Any())
// return false;
//
// var save = CharacterSave.FromString(text);
// ConditionalApply(save, _player!);
// }
// catch (Exception e)
// {
// PluginLog.Information($"{e}");
// return false;
// }
//
// return true;
// }
//
// private void DrawSaveDesignButton()
// {
// ImGui.PushFont(UiBuilder.IconFont);
// if (ImGui.Button(FontAwesomeIcon.Save.ToIconString()))
// OpenDesignNamePopup(DesignNameUse.SaveCurrent);
//
// ImGui.PopFont();
// ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment.");
//
// DrawDesignNamePopup(DesignNameUse.SaveCurrent);
// }
//
// private void DrawTargetPlayerButton()
// {
// if (ImGui.Button("Target Player"))
// Dalamud.Targets.SetTarget(_player);
// }
//
// private unsafe void DrawApplyToPlayerButton(CharacterSave save)
// {
// if (!ImGui.Button("Apply to Self"))
// return;
//
// var player = _inGPose
// ? (Character?)Dalamud.Objects[GPoseObjectId]
// : Dalamud.ClientState.LocalPlayer;
// var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null;
// if (player == null)
// return;
//
// ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player.Address);
// if (_inGPose)
// ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)fallback!.Address);
// Glamourer.Penumbra.UpdateCharacters(player, fallback);
// }
//
//
// private static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.Character* TransformToCustomizable(
// FFXIVClientStructs.FFXIV.Client.Game.Character.Character* actor)
// {
// if (actor == null)
// return null;
//
// if (actor->ModelCharaId == 0)
// return actor;
//
// actor->ModelCharaId = 0;
// CharacterCustomization.Default.Write(actor);
// return actor;
// }
//
// private static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Convert(GameObject? actor)
// {
// return actor switch
// {
// null => null,
// PlayerCharacter p => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)p.Address,
// BattleChara b => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)b.Address,
// _ => actor.ObjectKind switch
// {
// ObjectKind.BattleNpc => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
// ObjectKind.Companion => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
// ObjectKind.Retainer => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
// ObjectKind.EventNpc => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
// _ => null,
// },
// };
// }
//
// private unsafe void DrawApplyToTargetButton(CharacterSave save)
// {
// if (!ImGui.Button("Apply to Target"))
// return;
//
// var player = TransformToCustomizable(Convert(Dalamud.Targets.Target));
// if (player == null)
// return;
//
// var fallBackCharacter = _gPoseActors.TryGetValue(new Utf8String(player->GameObject.Name).ToString(), out var f) ? f : null;
// ConditionalApply(save, player);
// if (fallBackCharacter != null)
// ConditionalApply(save, fallBackCharacter!);
// //Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter);
// }
//
// private void DrawRevertButton()
// {
// if (!ImGuiUtil.DrawDisabledButton("Revert", Vector2.Zero, string.Empty, _player == null))
// return;
//
// Glamourer.RevertableDesigns.Revert(_player!);
// var fallBackCharacter = _gPoseActors.TryGetValue(_player!.Name.ToString(), out var f) ? f : null;
// if (fallBackCharacter != null)
// Glamourer.RevertableDesigns.Revert(fallBackCharacter);
// Glamourer.Penumbra.UpdateCharacters(_player, fallBackCharacter);
// }
//
// private void SaveNewDesign(CharacterSave save)
// {
// try
// {
// var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName);
// if (!name.Any())
// return;
//
// var newDesign = new Design(folder, name) { Data = save };
// folder.AddChild(newDesign);
// _designs.Designs[newDesign.FullName()] = save;
// _designs.SaveToFile();
// }
// catch (Exception e)
// {
// PluginLog.Error($"Could not save new design {_newDesignName}:\n{e}");
// }
// }
//
// private unsafe void DrawMonsterPanel()
// {
// if (DrawApplyClipboardButton())
// Glamourer.Penumbra.UpdateCharacters(_player!);
//
// ImGui.SameLine();
// if (ImGui.Button("Convert to Character"))
// {
// //TransformToCustomizable(_player);
// _currentLabel = _currentLabel.Replace("(Monster)", "(NPC)");
// Glamourer.Penumbra.UpdateCharacters(_player!);
// }
//
// if (!_inGPose)
// {
// ImGui.SameLine();
// DrawTargetPlayerButton();
// }
//
// var currentModel = ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player!.Address)->ModelCharaId;
// using var combo = ImRaii.Combo("Model Id", currentModel.ToString());
// if (!combo)
// return;
//
// foreach (var (id, _) in _models.Skip(1))
// {
// if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel)
// continue;
//
// ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player!.Address)->ModelCharaId = 0;
// Glamourer.Penumbra.UpdateCharacters(_player!);
// }
// }
//
// private void DrawPlayerPanel()
// {
// DrawCopyClipboardButton(_currentSave);
// ImGui.SameLine();
// var changes = !_currentSave.WriteProtected && DrawApplyClipboardButton();
// ImGui.SameLine();
// DrawSaveDesignButton();
// ImGui.SameLine();
// DrawApplyToPlayerButton(_currentSave);
// if (!_inGPose)
// {
// ImGui.SameLine();
// DrawApplyToTargetButton(_currentSave);
// if (_player != null && !_currentSave.WriteProtected)
// {
// ImGui.SameLine();
// DrawTargetPlayerButton();
// }
// }
//
// var data = _currentSave;
// if (!_currentSave.WriteProtected)
// {
// ImGui.SameLine();
// DrawRevertButton();
// }
// else
// {
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
// data = data.Copy();
// }
//
// if (DrawCustomization(ref data.Customizations) && _player != null)
// {
// Glamourer.RevertableDesigns.Add(_player);
// _currentSave.Customizations.Write(_player.Address);
// changes = true;
// }
//
// changes |= DrawEquip(data.Equipment);
// changes |= DrawMiscellaneous(data, _player);
//
// if (_player != null && changes)
// Glamourer.Penumbra.UpdateCharacters(_player);
// if (_currentSave.WriteProtected)
// ImGui.PopStyleVar();
// }
//
// private unsafe void DrawActorPanel()
// {
// using var group = ImRaii.Group();
// DrawPlayerHeader();
// using var child = ImRaii.Child("##playerData", -Vector2.One, true);
// if (!child)
// return;
//
// if (_player == null || ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player.Address)->ModelCharaId == 0)
// DrawPlayerPanel();
// else
// DrawMonsterPanel();
// }
//}

View file

@ -2,8 +2,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using Glamourer.Gui.Customization;
using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
@ -17,15 +15,15 @@ internal partial class Interface
{
private class DebugStateTab
{
private readonly CurrentManipulations _currentManipulations;
private readonly ActiveDesign.Manager _activeDesigns;
private LowerString _manipulationFilter = LowerString.Empty;
private ActorIdentifier _selection = ActorIdentifier.Invalid;
private ActiveDesign? _save = null;
private bool _delete = false;
public DebugStateTab(CurrentManipulations currentManipulations)
=> _currentManipulations = currentManipulations;
public DebugStateTab(ActiveDesign.Manager activeDesigns)
=> _activeDesigns = activeDesigns;
[Conditional("DEBUG")]
public void Draw()
@ -43,7 +41,7 @@ internal partial class Interface
if (_delete)
{
_delete = false;
_currentManipulations.DeleteSave(_selection);
_activeDesigns.DeleteSave(_selection);
_selection = ActorIdentifier.Invalid;
}
}
@ -56,7 +54,7 @@ internal partial class Interface
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
var remainder = ImGuiClip.FilteredClippedDraw(_currentManipulations, skips, CheckFilter, DrawSelectable);
var remainder = ImGuiClip.FilteredClippedDraw(_activeDesigns, skips, CheckFilter, DrawSelectable);
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
}

View file

@ -1,6 +1,7 @@
using System;
using System.Numerics;
using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Designs;
@ -37,9 +38,7 @@ internal partial class Interface
{
using var tab = ImRaii.TabItem("Designs");
if (!tab)
{
return;
}
Selector.Draw(GetDesignSelectorSize());
ImGui.SameLine();
@ -55,12 +54,25 @@ internal partial class Interface
if (!child || Selector.Selected == null)
return;
CustomizationDrawer.Draw(Selector.Selected.Customize(), Selector.Selected.Equipment(), true);
_main._customizationDrawer.Draw(Selector.Selected.Customize(), CustomizeFlagExtensions.All, true);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
_main._equipmentDrawer.DrawStain(Selector.Selected, slot, out var stain);
var current = Selector.Selected.Armor(slot);
_main._equipmentDrawer.DrawStain(current.Stain, slot, out var stain);
ImGui.SameLine();
_main._equipmentDrawer.DrawArmor(Selector.Selected, slot, out var armor);
_main._equipmentDrawer.DrawArmor(current, slot, out var armor);
}
var currentMain = Selector.Selected.WeaponMain;
_main._equipmentDrawer.DrawStain(currentMain.Stain, EquipSlot.MainHand, out var stainMain);
ImGui.SameLine();
_main._equipmentDrawer.DrawMainhand(currentMain, true, out var main);
if (currentMain.Type.Offhand() != FullEquipType.Unknown)
{
var currentOff = Selector.Selected.WeaponOff;
_main._equipmentDrawer.DrawStain(currentOff.Stain, EquipSlot.OffHand, out var stainOff);
ImGui.SameLine();
_main._equipmentDrawer.DrawOffhand(currentOff, main.Type, out var off);
}
}
}

View file

@ -50,8 +50,9 @@ internal partial class Interface
return;
}
if (ImGui.Button(buttonLabel))
Glamourer.Penumbra.Reattach(true);
// TODO
//if (ImGui.Button(buttonLabel))
// Glamourer.Penumbra.Reattach(true);
ImGuiUtil.HoverTooltip(
"If Penumbra did not register the functions for some reason, pressing this button might help restore functionality.");
@ -73,18 +74,6 @@ internal partial class Interface
v => cfg.ColorDesigns = v);
Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", cfg.ShowLocks,
v => cfg.ShowLocks = v);
Checkmark("Attach to Penumbra",
"Allows you to right-click items in the Changed Items tab of a mod in Penumbra to apply them to your player character.",
cfg.AttachToPenumbra,
v =>
{
cfg.AttachToPenumbra = v;
if (v)
Glamourer.Penumbra.Reattach(true);
else
Glamourer.Penumbra.Unattach();
});
ImGui.SameLine();
DrawRestorePenumbraButton();
Checkmark("Apply Fixed Designs",

View file

@ -1,32 +1,38 @@
using System;
using System.Linq;
using System.Numerics;
using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
using Glamourer.State;
using Glamourer.Util;
using ImGuiNET;
using OtterGui.Raii;
using Penumbra.GameData.Data;
namespace Glamourer.Gui;
internal partial class Interface : Window, IDisposable
{
private readonly DalamudPluginInterface _pi;
private readonly EquipmentDrawer _equipmentDrawer;
private readonly CustomizationDrawer _customizationDrawer;
private readonly ActorTab _actorTab;
private readonly DesignTab _designTab;
private readonly DebugStateTab _debugStateTab;
private readonly DebugDataTab _debugDataTab;
public Interface(ItemManager items, CurrentManipulations manipulations, Design.Manager manager, DesignFileSystem fileSystem)
public Interface(DalamudPluginInterface pi, ItemManager items, ActiveDesign.Manager activeDesigns, Design.Manager manager,
DesignFileSystem fileSystem, ObjectManager objects)
: base(GetLabel())
{
_pi = pi;
_equipmentDrawer = new EquipmentDrawer(items);
_customizationDrawer = new CustomizationDrawer(pi);
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += Toggle;
SizeConstraints = new WindowSizeConstraints()
@ -34,8 +40,8 @@ internal partial class Interface : Window, IDisposable
MinimumSize = new Vector2(675, 675),
MaximumSize = ImGui.GetIO().DisplaySize,
};
_actorTab = new ActorTab(this, manipulations);
_debugStateTab = new DebugStateTab(manipulations);
_actorTab = new ActorTab(this, activeDesigns, objects);
_debugStateTab = new DebugStateTab(activeDesigns);
_debugDataTab = new DebugDataTab(Glamourer.Customization);
_designTab = new DesignTab(this, manager, fileSystem);
}
@ -67,8 +73,8 @@ internal partial class Interface : Window, IDisposable
public void Dispose()
{
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= Toggle;
CustomizationDrawer.Dispose();
_pi.UiBuilder.OpenConfigUi -= Toggle;
_customizationDrawer.Dispose();
_designTab.Dispose();
}
@ -77,90 +83,3 @@ internal partial class Interface : Window, IDisposable
? "Glamourer###GlamourerConfigWindow"
: $"Glamourer v{Glamourer.Version}###GlamourerConfigWindow";
}
//public const float SelectorWidth = 200;
//public const float MinWindowWidth = 675;
//public const int GPoseObjectId = 201;
//private const string PluginName = "Glamourer";
//private readonly string _glamourerHeader;
//
//private readonly IReadOnlyDictionary<byte, Stain> _stains;
//private readonly IReadOnlyDictionary<uint, ModelCeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeehara> _models;
//private readonly IObjectIdentifier _identifier;
//private readonly Dictionary<EquipSlot, (ComboWithFilter<Item>, ComboWithFilter<Stain>)> _combos;
//private readonly ImGuiScene.TextureWrap? _legacyTattooIcon;
//private readonly Dictionary<EquipSlot, string> _equipSlotNames;
//private readonly DesignManager _designs;
//private readonly Glamourer _plugin;
//
//private bool _visible;
//private bool _inGPose;
//
//public Interface(Glamourer plugin)
//{
// _plugin = plugin;
// _designs = plugin.Designs;
// _glamourerHeader = Glamourer.Version.Length > 0
// ? $"{PluginName} v{Glamourer.Version}###{PluginName}Main"
// : $"{PluginName}###{PluginName}Main";
// Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
// Dalamud.PluginInterface.UiBuilder.Draw += Draw;
// Dalamud.PluginInterface.UiBuilder.OpenConfigUi += ToggleVisibility;
//
// _equipSlotNames = GetEquipSlotNames();
//
// _stains = GameData.Stains(Dalamud.GameData);
// _models = GameData.Models(Dalamud.GameData);
// _identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
//
//
// var stainCombo = CreateDefaultStainCombo(_stains.Values.ToArray());
//
// var equip = GameData.ItemsBySlot(Dalamud.GameData);
// _combos = equip.ToDictionary(kvp => kvp.Key, kvp => CreateCombos(kvp.Key, kvp.Value, stainCombo));
// _legacyTattooIcon = GetLegacyTattooIcon();
//}
//
//public void ToggleVisibility()
// => _visible = !_visible;
//
//
//private void Draw()
//{
// if (!_visible)
// return;
//
// ImGui.SetNextWindowSizeConstraints(Vector2.One * MinWindowWidth * ImGui.GetIO().FontGlobalScale,
// Vector2.One * 5000 * ImGui.GetIO().FontGlobalScale);
// if (!ImGui.Begin(_glamourerHeader, ref _visible))
// {
// ImGui.End();
// return;
// }
//
// try
// {
// using var tabBar = ImRaii.TabBar("##tabBar");
// if (!tabBar)
// return;
//
// _inGPose = Dalamud.Objects[GPoseObjectId] != null;
// _iconSize = Vector2.One * ImGui.GetTextLineHeightWithSpacing() * 2;
// _actualIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
// _comboSelectorSize = 4 * _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
// _percentageSize = _comboSelectorSize;
// _inputIntSize = 2 * _actualIconSize.X + ImGui.GetStyle().ItemSpacing.X;
// _raceSelectorWidth = _inputIntSize + _percentageSize - _actualIconSize.X;
// _itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1;
//
// DrawPlayerTab();
// DrawSaves();
// DrawFixedDesignsTab();
// DrawConfigTab();
// DrawRevertablesTab();
// }
// finally
// {
// ImGui.End();
// }
//}

View file

@ -66,6 +66,9 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
public bool Valid
=> Pointer != null;
public int Index
=> Pointer->GameObject.ObjectIndex;
public uint ModelId
{
get => (uint)Pointer->ModelCharaId;

View file

@ -1,22 +1,15 @@
using System.Collections.Generic;
using System.Text;
using Dalamud.Game.ClientState.Objects.Enums;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Penumbra.GameData.Actors;
using static Glamourer.Interop.Actor;
namespace Glamourer.Interop;
public static class ObjectManager
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ObjectManager.ActorData>
{
private const int CutsceneIndex = 200;
private const int GPosePlayerIndex = 201;
private const int CharacterScreenIndex = 240;
private const int ExamineScreenIndex = 241;
private const int FittingRoomIndex = 242;
private const int DyePreviewIndex = 243;
private const int PortraitIndex = 244;
public readonly struct ActorData
{
public readonly List<Actor> Objects;
@ -40,28 +33,22 @@ public static class ObjectManager
}
}
public static bool IsInGPose { get; private set; }
public static ushort World { get; private set; }
public DateTime LastUpdate { get; private set; }
public static IReadOnlyDictionary<ActorIdentifier, ActorData> Actors
=> Identifiers;
public bool IsInGPose { get; private set; }
public ushort World { get; private set; }
public static IReadOnlyList<(ActorIdentifier, ActorData)> List
=> ListData;
private readonly Dictionary<ActorIdentifier, ActorData> _identifiers = new(200);
private static readonly Dictionary<ActorIdentifier, ActorData> Identifiers = new(200);
private static readonly List<(ActorIdentifier, ActorData)> ListData = new(Dalamud.Objects.Length);
private static void HandleIdentifier(ActorIdentifier identifier, Actor character)
private void HandleIdentifier(ActorIdentifier identifier, Actor character)
{
if (!character.DrawObject || !identifier.IsValid)
return;
if (!Identifiers.TryGetValue(identifier, out var data))
if (!_identifiers.TryGetValue(identifier, out var data))
{
data = new ActorData(character, identifier.ToString());
Identifiers[identifier] = data;
ListData.Add((identifier, data));
_identifiers[identifier] = data;
}
else
{
@ -69,88 +56,100 @@ public static class ObjectManager
}
}
public static void Update()
{
World = (ushort)(Dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
Identifiers.Clear();
ListData.Clear();
private readonly Framework _framework;
private readonly ClientState _clientState;
private readonly ObjectTable _objects;
for (var i = 0; i < CutsceneIndex; ++i)
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects)
{
Actor character = Dalamud.Objects.GetObjectAddress(i);
_framework = framework;
_clientState = clientState;
_objects = objects;
}
public void Update()
{
var lastUpdate = _framework.LastUpdate;
if (lastUpdate <= LastUpdate)
return;
LastUpdate = lastUpdate;
World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
_identifiers.Clear();
for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i)
{
Actor character = _objects.GetObjectAddress(i);
if (character.Identifier(out var identifier))
HandleIdentifier(identifier, character);
}
for (var i = CutsceneIndex; i < CharacterScreenIndex; ++i)
for (var i = (int)ScreenActor.CutsceneStart; i < (int)ScreenActor.CutsceneEnd; ++i)
{
Actor character = Dalamud.Objects.GetObjectAddress(i);
Actor character = _objects.GetObjectAddress(i);
if (!character.Identifier(out var identifier))
break;
HandleIdentifier(identifier, character);
}
void AddSpecial(int idx, string label)
void AddSpecial(ScreenActor idx, string label)
{
Actor actor = Dalamud.Objects.GetObjectAddress(idx);
Actor actor = _objects.GetObjectAddress((int)idx);
if (actor.Identifier(out var ident))
{
var data = new ActorData(actor, label);
Identifiers.Add(ident, data);
ListData.Add((ident, data));
_identifiers.Add(ident, data);
}
}
AddSpecial(CharacterScreenIndex, "Character Screen Actor");
AddSpecial(ExamineScreenIndex, "Examine Screen Actor");
AddSpecial(FittingRoomIndex, "Fitting Room Actor");
AddSpecial(DyePreviewIndex, "Dye Preview Actor");
AddSpecial(PortraitIndex, "Portrait Actor");
AddSpecial(ScreenActor.CharacterScreen, "Character Screen Actor");
AddSpecial(ScreenActor.ExamineScreen, "Examine Screen Actor");
AddSpecial(ScreenActor.FittingRoom, "Fitting Room Actor");
AddSpecial(ScreenActor.DyePreview, "Dye Preview Actor");
AddSpecial(ScreenActor.Portrait, "Portrait Actor");
AddSpecial(ScreenActor.Card6, "Card Actor 6");
AddSpecial(ScreenActor.Card7, "Card Actor 7");
AddSpecial(ScreenActor.Card8, "Card Actor 8");
for (var i = PortraitIndex + 1; i < Dalamud.Objects.Length; ++i)
for (var i = (int)ScreenActor.ScreenEnd; i < Dalamud.Objects.Length; ++i)
{
Actor character = Dalamud.Objects.GetObjectAddress(i);
Actor character = _objects.GetObjectAddress(i);
if (character.Identifier(out var identifier))
HandleIdentifier(identifier, character);
}
Actor gPose = Dalamud.Objects.GetObjectAddress(GPosePlayerIndex);
var gPose = GPosePlayer;
IsInGPose = gPose && gPose.Utf8Name.Length > 0;
}
public static Actor GPosePlayer
=> Dalamud.Objects.GetObjectAddress(GPosePlayerIndex);
public Actor GPosePlayer
=> _objects.GetObjectAddress((int)ScreenActor.GPosePlayer);
public static Actor Player
=> Dalamud.Objects.GetObjectAddress(0);
public Actor Player
=> _objects.GetObjectAddress(0);
private static unsafe string GetLabel(Actor player, string playerName, int num, bool gPose)
{
var sb = new StringBuilder(64);
sb.Append(playerName);
public IEnumerator<KeyValuePair<ActorIdentifier, ActorData>> GetEnumerator()
=> _identifiers.GetEnumerator();
if (gPose)
{
sb.Append(" (GPose");
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
if (player.ObjectKind == ObjectKind.Player)
sb.Append(')');
else
sb.Append(player.ModelId == 0 ? ", NPC)" : ", Monster)");
}
else if (player.ObjectKind != ObjectKind.Player)
{
sb.Append(player.ModelId == 0 ? " (NPC)" : " (Monster)");
}
public int Count
=> _identifiers.Count;
if (num > 1)
{
sb.Append(" #");
sb.Append(num);
}
public bool ContainsKey(ActorIdentifier key)
=> _identifiers.ContainsKey(key);
return sb.ToString();
}
public bool TryGetValue(ActorIdentifier key, out ActorData value)
=> _identifiers.TryGetValue(key, out value);
public ActorData this[ActorIdentifier key]
=> _identifiers[key];
public IEnumerable<ActorIdentifier> Keys
=> _identifiers.Keys;
public IEnumerable<ActorData> Values
=> _identifiers.Values;
}

View file

@ -8,35 +8,6 @@ namespace Glamourer.Interop;
public unsafe partial class RedrawManager
{
// Update
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
[Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")]
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
public bool UpdateCustomize(Actor actor, Customize customize)
{
if (!actor.Valid || !actor.DrawObject.Valid)
return false;
var d = actor.DrawObject;
if (NeedsRedraw(d.Customize, customize))
{
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
return true;
}
return _changeCustomize(d.Pointer, (byte*)customize.Data, 1);
}
public static bool NeedsRedraw(Customize lhs, Customize rhs)
=> lhs.Race != rhs.Race
|| lhs.Gender != rhs.Gender
|| lhs.BodyType != rhs.BodyType
|| lhs.Face != rhs.Face
|| lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan;
public static void SetVisor(Human* data, bool on)

View file

@ -69,16 +69,4 @@ public unsafe partial class RedrawManager
//
//return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
}
private void HandleEquipUpdate(nint drawObject, EquipSlot slot, ref CharacterArmor data, bool manual)
{
var actor = Glamourer.Penumbra.GameObjectFromDrawObject(drawObject);
var identifier = actor.GetIdentifier();
if (!_currentManipulations.TryGetDesign(identifier, out var design))
return;
var flag = slot.ToFlag();
var stainFlag = slot.ToStainFlag();
}
}

View file

@ -45,7 +45,7 @@ public unsafe partial class RedrawManager
_ => weapon,
};
}
else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2))
else if (redrawOnEquality == 1 && _stateManager.TryGetValue(identifier, out var save2))
{
PluginLog.Information($"Loaded weapon from current design for {identifier}.");
//switch (slot)

View file

@ -1,31 +1,104 @@
using System;
using System.Diagnostics;
using System.Linq;
using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization;
using Glamourer.State;
using Glamourer.Structs;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Interop;
public class DesignBaseValidator
public partial class Interop : IDisposable
{
private readonly CustomizationManager _manager;
private readonly RestrictedGear _restrictedGear;
public DesignBaseValidator(CustomizationManager manager, RestrictedGear restrictedGear)
public Interop()
{
_manager = manager;
_restrictedGear = restrictedGear;
SignatureHelper.Initialise(this);
_changeJobHook.Enable();
}
public void Dispose()
{
_changeJobHook.Dispose();
}
}
public unsafe partial class Interop
{
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
public delegate void FlagSlotForUpdateDelegate(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item);
public unsafe partial class RedrawManager
// This gets called when one of the ten equip items of an existing draw object gets changed.
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
public event FlagSlotForUpdateDelegate? EquipUpdate;
public ulong FlagSlotForUpdateInterop(DrawObject drawObject, EquipSlot slot, CharacterArmor armor)
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
public void UpdateSlot(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
{
InvokeFlagSlotEvent(drawObject, slot, ref data);
FlagSlotForUpdateInterop(drawObject, slot, data);
}
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
{
var armor = drawObject.Equip[slot] with { Stain = stain };
UpdateSlot(drawObject, slot, armor);
}
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
{
var slot = slotIdx.ToEquipSlot();
InvokeFlagSlotEvent(drawObject, slot, ref *data);
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
}
private void InvokeFlagSlotEvent(DrawObject drawObject, EquipSlot slot, ref CharacterArmor armor)
{
if (EquipUpdate == null)
return;
foreach (var del in EquipUpdate.GetInvocationList().OfType<FlagSlotForUpdateDelegate>())
{
try
{
del(drawObject, slot, ref armor);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not invoke {nameof(EquipUpdate)} Subscriber:\n{ex}");
}
}
}
}
public unsafe partial class Interop
{
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
[Signature(Sigs.ChangeCustomize)]
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
public bool UpdateCustomize(Actor actor, CustomizeData customize)
{
Debug.Assert(customize.Data != null, "Customize was invalid.");
if (!actor.Valid || !actor.DrawObject.Valid)
return false;
return _changeCustomize(actor.DrawObject.Pointer, customize.Data, 1);
}
}
public partial class Interop
{
private delegate void ChangeJobDelegate(IntPtr data, uint job);
@ -44,27 +117,21 @@ public unsafe partial class RedrawManager
public unsafe partial class RedrawManager : IDisposable
{
private readonly FixedDesigns _fixedDesigns;
private readonly CurrentManipulations _currentManipulations;
private readonly ActiveDesign.Manager _stateManager;
public RedrawManager(FixedDesigns fixedDesigns, CurrentManipulations currentManipulations)
public RedrawManager(FixedDesigns fixedDesigns, ActiveDesign.Manager stateManager)
{
SignatureHelper.Initialise(this);
Glamourer.Penumbra.CreatingCharacterBase.Event += OnCharacterRedraw;
Glamourer.Penumbra.CreatedCharacterBase.Event += OnCharacterRedrawFinished;
_fixedDesigns = fixedDesigns;
_currentManipulations = currentManipulations;
_stateManager = stateManager;
_flagSlotForUpdateHook.Enable();
_loadWeaponHook.Enable();
_changeJobHook.Enable();
}
public void Dispose()
{
_flagSlotForUpdateHook.Dispose();
_loadWeaponHook.Dispose();
_changeJobHook.Dispose();
Glamourer.Penumbra.CreatingCharacterBase.Event -= OnCharacterRedraw;
Glamourer.Penumbra.CreatedCharacterBase.Event -= OnCharacterRedrawFinished;
}
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
@ -76,7 +143,7 @@ public unsafe partial class RedrawManager : IDisposable
// Check if we have a current design in use, or if not if the actor has a fixed design.
var identifier = actor.GetIdentifier();
if (!(_currentManipulations.TryGetDesign(identifier, out var save) || _fixedDesigns.TryGetDesign(identifier, out var save2)))
if (!(_stateManager.TryGetValue(identifier, out var save) || _fixedDesigns.TryGetDesign(identifier, out var save2)))
return;
// Compare game object customize data against draw object customize data for transformations.

View file

@ -0,0 +1,211 @@
using System;
using System.Collections;
using Glamourer.Interop;
using Penumbra.GameData.Actors;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Api;
using Glamourer.Customization;
using Glamourer.Designs;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
using Item = Glamourer.Designs.Item;
using Lumina.Excel.GeneratedSheets;
namespace Glamourer.State;
public sealed partial class ActiveDesign
{
public partial class Manager : IReadOnlyDictionary<ActorIdentifier, ActiveDesign>
{
private readonly ActorManager _actors;
private readonly ObjectManager _objects;
private readonly Interop.Interop _interop;
private readonly PenumbraAttach _penumbra;
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
public Manager(ActorManager actors, ObjectManager objects, Interop.Interop interop, PenumbraAttach penumbra)
{
_actors = actors;
_objects = objects;
_interop = interop;
_penumbra = penumbra;
}
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
=> _characterSaves.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> _characterSaves.Count;
public bool ContainsKey(ActorIdentifier key)
=> _characterSaves.ContainsKey(key);
public bool TryGetValue(ActorIdentifier key, [NotNullWhen(true)] out ActiveDesign? value)
=> _characterSaves.TryGetValue(key, out value);
public ActiveDesign this[ActorIdentifier key]
=> _characterSaves[key];
public IEnumerable<ActorIdentifier> Keys
=> _characterSaves.Keys;
public IEnumerable<ActiveDesign> Values
=> _characterSaves.Values;
public void DeleteSave(ActorIdentifier identifier)
=> _characterSaves.Remove(identifier);
public unsafe ActiveDesign GetOrCreateSave(Actor actor)
{
var id = _actors.FromObject((GameObject*)actor.Pointer, out _, false, false);
if (_characterSaves.TryGetValue(id, out var save))
{
save.Update(actor);
return save;
}
id = id.CreatePermanent();
save = new ActiveDesign(id, actor);
save.Update(actor);
_characterSaves.Add(id, save);
return save;
}
public void RevertDesign(ActiveDesign design)
{
RevertCustomize(design, design.ChangedCustomize);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
RevertEquipment(design, slot, design.ChangedEquip.HasFlag(slot.ToFlag()), design.ChangedEquip.HasFlag(slot.ToStainFlag()));
RevertMainHand(design);
RevertOffHand(design);
}
public void RevertMainHand(ActiveDesign design)
{ }
public void RevertOffHand(ActiveDesign design)
{ }
public void RevertCustomize(ActiveDesign design, CustomizeFlag flags)
=> ChangeCustomize(design, flags, design._initialData.CustomizeData, false);
public void ChangeCustomize(ActiveDesign design, CustomizeFlag flags, CustomizeData newValue, bool fromFixed)
{
var customize = new Customize(ref newValue);
var anyChanges = false;
foreach (var option in Enum.GetValues<CustomizeIndex>())
{
var flag = option.ToFlag();
var apply = flags.HasFlag(flag);
anyChanges |= apply && design.SetCustomize(option, customize[option]);
if (design.GetCustomize(option).Value != design._initialData.Customize[option].Value)
design.ChangedCustomize |= flag;
else
design.ChangedCustomize &= ~flag;
if (fromFixed)
design.FixedCustomize |= flag;
else
design.FixedCustomize &= ~flag;
}
if (!anyChanges)
return;
_objects.Update();
if (!_objects.TryGetValue(design.Identifier, out var data))
return;
var redraw = flags.RequiresRedraw();
foreach (var obj in data.Objects)
{
if (redraw)
_penumbra.RedrawObject(obj, RedrawType.Redraw);
else
_interop.UpdateCustomize(obj, design.CharacterData.CustomizeData);
}
}
public void RevertEquipment(ActiveDesign design, EquipSlot slot, bool equip, bool stain)
{
var item = design._initialData.Equipment[slot];
if (equip)
{
var flag = slot.ToFlag();
design.UpdateArmor(slot, item, true);
design.ChangedEquip &= ~flag;
design.FixedEquip &= ~flag;
}
if (stain)
{
var flag = slot.ToStainFlag();
design.SetStain(slot, item.Stain);
design.ChangedEquip &= ~flag;
design.FixedEquip &= ~flag;
}
_objects.Update();
if (!_objects.TryGetValue(design.Identifier, out var data))
return;
foreach (var obj in data.Objects)
_interop.UpdateSlot(obj.DrawObject, slot, item);
}
public void ChangeEquipment(ActiveDesign design, EquipSlot slot, Item item, bool fromFixed)
{
var flag = slot.ToFlag();
design.SetArmor(slot, item);
var current = design.Armor(slot);
var initial = design._initialData.Equipment[slot];
if (current.ModelBase.Value != initial.Set.Value || current.Variant != initial.Variant)
design.ChangedEquip |= flag;
else
design.ChangedEquip &= ~flag;
if (fromFixed)
design.FixedEquip |= flag;
else
design.FixedEquip &= ~flag;
_objects.Update();
if (!_objects.TryGetValue(design.Identifier, out var data))
return;
foreach (var obj in data.Objects)
_interop.UpdateSlot(obj.DrawObject, slot, item.Model);
}
public void ChangeStain(ActiveDesign design, EquipSlot slot, StainId stain, bool fromFixed)
{
var flag = slot.ToStainFlag();
design.SetStain(slot, stain);
var current = design.Armor(slot);
var initial = design._initialData.Equipment[slot];
if (current.Stain.Value != initial.Stain.Value)
design.ChangedEquip |= flag;
else
design.ChangedEquip &= ~flag;
if (fromFixed)
design.FixedEquip |= flag;
else
design.FixedEquip &= ~flag;
_objects.Update();
if (!_objects.TryGetValue(design.Identifier, out var data))
return;
foreach (var obj in data.Objects)
_interop.UpdateStain(obj.DrawObject, slot, stain);
}
}
}

View file

@ -1,64 +0,0 @@
using System.Collections;
using Glamourer.Interop;
using Penumbra.GameData.Actors;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Glamourer.Designs;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
public sealed partial class ActiveDesign
{
public partial class Manager : IReadOnlyDictionary<ActorIdentifier, ActiveDesign>
{
private readonly ActorManager _actors;
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
public Manager(ActorManager actors)
=> _actors = actors;
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
=> _characterSaves.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> _characterSaves.Count;
public bool ContainsKey(ActorIdentifier key)
=> _characterSaves.ContainsKey(key);
public bool TryGetValue(ActorIdentifier key, [NotNullWhen(true)] out ActiveDesign? value)
=> _characterSaves.TryGetValue(key, out value);
public ActiveDesign this[ActorIdentifier key]
=> _characterSaves[key];
public IEnumerable<ActorIdentifier> Keys
=> _characterSaves.Keys;
public IEnumerable<ActiveDesign> Values
=> _characterSaves.Values;
public void DeleteSave(ActorIdentifier identifier)
=> _characterSaves.Remove(identifier);
public ActiveDesign GetOrCreateSave(Actor actor)
{
var id = actor.GetIdentifier();
if (_characterSaves.TryGetValue(id, out var save))
{
save.Update(actor);
return save;
}
save = new ActiveDesign();
save.Update(actor);
_characterSaves.Add(id.CreatePermanent(), save);
return save;
}
}
}

View file

@ -2,33 +2,38 @@
using Glamourer.Designs;
using Glamourer.Interop;
using Penumbra.Api.Enums;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
namespace Glamourer.State;
public sealed partial class ActiveDesign : DesignBase
{
public readonly ActorIdentifier Identifier;
private CharacterData _initialData = new();
private CustomizeFlag _changedCustomize;
private CustomizeFlag _fixedCustomize;
public CustomizeFlag ChangedCustomize { get; private set; } = 0;
public CustomizeFlag FixedCustomize { get; private set; } = 0;
private EquipFlag _changedEquip;
private EquipFlag _fixedEquip;
public EquipFlag ChangedEquip { get; private set; } = 0;
public EquipFlag FixedEquip { get; private set; } = 0;
public bool IsHatVisible { get; private set; } = false;
public bool IsWeaponVisible { get; private set; } = false;
public bool IsVisorToggled { get; private set; } = false;
public bool IsWet { get; private set; } = false;
private ActiveDesign()
{ }
private ActiveDesign(ActorIdentifier identifier)
=> Identifier = identifier;
public ActiveDesign(Actor actor)
private ActiveDesign(ActorIdentifier identifier, Actor actor)
{
Identifier = identifier;
Update(actor);
}
//public void ApplyToActor(Actor actor)
//{
// if (!actor)
@ -72,14 +77,14 @@ public sealed partial class ActiveDesign : DesignBase
var initialEquip = _initialData.Equipment;
var currentEquip = actor.Equip;
var equipment = Equipment();
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var current = currentEquip[slot];
if (initialEquip[slot] != current)
{
initialEquip[slot] = current;
equipment[slot] = current;
UpdateArmor(slot, current, true);
SetStain(slot, current.Stain);
}
}
@ -87,12 +92,14 @@ public sealed partial class ActiveDesign : DesignBase
{
_initialData.MainHand = actor.MainHand;
UpdateMainhand(actor.MainHand);
SetStain(EquipSlot.MainHand, actor.MainHand.Stain);
}
if (_initialData.OffHand != actor.OffHand)
{
_initialData.OffHand = actor.OffHand;
UpdateMainhand(actor.OffHand);
UpdateOffhand(actor.OffHand);
SetStain(EquipSlot.OffHand, actor.OffHand.Stain);
}
}
}

View file

@ -1,74 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Glamourer.Interop;
using Penumbra.GameData.Actors;
namespace Glamourer.State;
public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<ActorIdentifier, ActiveDesign>>
{
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
=> _characterSaves.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> _characterSaves.Count;
public ActiveDesign GetOrCreateSave(Actor actor)
{
var id = actor.GetIdentifier();
if (_characterSaves.TryGetValue(id, out var save))
{
save.Update(actor);
return save;
}
save = new ActiveDesign(actor);
_characterSaves.Add(id.CreatePermanent(), save);
return save;
}
public void DeleteSave(ActorIdentifier identifier)
=> _characterSaves.Remove(identifier);
public bool TryGetDesign(ActorIdentifier identifier, [NotNullWhen(true)] out ActiveDesign? save)
=> _characterSaves.TryGetValue(identifier, out save);
//public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
//{
// var save = CreateSave(actor);
// (_, data) = _restrictedGear.ResolveRestricted(data, slot, save.Customize.Race, save.Customize.Gender);
// if (save.Equipment[slot] == data)
// return null;
//
// save.Equipment[slot] = data;
// return data;
//}
//
//public bool ChangeWeapon(Actor actor, CharacterWeapon main)
//{
// var save = CreateSave(actor);
// if (save.MainHand == main)
// return false;
//
// save.MainHand = main;
// return true;
//}
//
//public bool ChangeWeapon(Actor actor, CharacterWeapon main, CharacterWeapon off)
//{
// var save = CreateSave(actor);
// if (main == save.MainHand && off == save.OffHand)
// return false;
//
// save.MainHand = main;
// save.OffHand = off;
// return true;
//}
//
}

View file

@ -5,7 +5,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Util;
public static unsafe class CustomizeExtensions
public static class CustomizeExtensions
{
// In languages other than english the actual clan name may depend on gender.
public static string ClanName(SubRace race, Gender gender)
@ -57,58 +57,39 @@ public static unsafe class CustomizeExtensions
// Change a gender and fix up all required customizations afterwards.
public static bool ChangeGender(this Customize customize, CharacterEquip equip, Gender gender)
public static CustomizeFlag ChangeGender(this Customize customize, CharacterEquip equip, Gender gender)
{
if (customize.Gender == gender)
return false;
return 0;
FixRestrictedGear(customize, equip, gender, customize.Race);
customize.Gender = gender;
FixUpAttributes(customize);
return true;
return CustomizeFlag.Gender | FixUpAttributes(customize);
}
// Change a race and fix up all required customizations afterwards.
public static bool ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan)
public static CustomizeFlag ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan)
{
if (customize.Clan == clan)
return false;
return 0;
var race = clan.ToRace();
var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar
FixRestrictedGear(customize, equip, gender, race);
var flags = CustomizeFlag.Race | CustomizeFlag.Clan;
if (gender != customize.Gender)
flags |= CustomizeFlag.Gender;
customize.Gender = gender;
customize.Race = race;
customize.Clan = clan;
FixUpAttributes(customize);
return true;
}
public static void ChangeCustomization(this Customize customize, CharacterEquip equip, Customize newCustomize)
{
FixRestrictedGear(customize, equip, newCustomize.Gender, newCustomize.Race);
customize.Load(newCustomize);
}
public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizeIndex index, CustomizeValue value)
{
switch (index)
{
case CustomizeIndex.Race: return customize.ChangeRace(equip, (SubRace)value.Value);
case CustomizeIndex.Gender: return customize.ChangeGender(equip, (Gender)value.Value);
}
if (customize[index] == value)
return false;
customize[index] = value;
return true;
return flags | FixUpAttributes(customize);
}
// Go through a whole customization struct and fix up all settings that need fixing.
private static void FixUpAttributes(Customize customize)
private static CustomizeFlag FixUpAttributes(Customize customize)
{
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
CustomizeFlag flags = 0;
foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex)))
{
switch (id)
@ -122,10 +103,16 @@ public static unsafe class CustomizeExtensions
default:
var count = set.Count(id);
if (set.DataByValue(id, customize[id], out _, customize.Face) < 0)
{
customize[id] = count == 0 ? CustomizeValue.Zero : set.Data(id, 0).Value;
flags |= id.ToFlag();
}
break;
}
}
return flags;
}
private static void FixRestrictedGear(Customize customize, CharacterEquip equip, Gender gender, Race race)

View file

@ -56,13 +56,19 @@ public class ItemManager : IDisposable
public static Designs.Item NothingItem(EquipSlot slot)
{
Debug.Assert(slot.IsEquipment() || slot.IsAccessory());
Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(NothingItem)} on {slot}.");
return new Designs.Item(Nothing, NothingId(slot), CharacterArmor.Empty);
}
public static Designs.Weapon NothingItem(FullEquipType type)
{
Debug.Assert(type.ToSlot() == EquipSlot.OffHand, $"Called {nameof(NothingItem)} on {type}.");
return new Designs.Weapon(Nothing, NothingId(type), CharacterWeapon.Empty, type);
}
public static Designs.Item SmallClothesItem(EquipSlot slot)
{
Debug.Assert(slot.IsEquipment());
Debug.Assert(slot.IsEquipment(), $"Called {nameof(SmallClothesItem)} on {slot}.");
return new Designs.Item(SmallClothesNpc, SmallclothesId(slot), new CharacterArmor(SmallClothesNpcModel, 1, 0));
}