mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
.
This commit is contained in:
parent
80ab57e96d
commit
d1d369a56b
31 changed files with 1637 additions and 80 deletions
|
|
@ -44,6 +44,8 @@ public enum CustomizeIndex : byte
|
||||||
|
|
||||||
public static class CustomizationExtensions
|
public static class CustomizationExtensions
|
||||||
{
|
{
|
||||||
|
public const int NumIndices = ((int)CustomizeIndex.FacePaintColor + 1);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
public static (int ByteIdx, byte Mask) ToByteAndMask(this CustomizeIndex index)
|
public static (int ByteIdx, byte Mask) ToByteAndMask(this CustomizeIndex index)
|
||||||
=> index switch
|
=> index switch
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@ public enum EquipFlag : uint
|
||||||
|
|
||||||
public static class EquipFlagExtensions
|
public static class EquipFlagExtensions
|
||||||
{
|
{
|
||||||
public const EquipFlag All = (EquipFlag)(((uint)EquipFlag.OffhandStain << 1) - 1);
|
public const EquipFlag All = (EquipFlag)(((uint)EquipFlag.OffhandStain << 1) - 1);
|
||||||
|
public const int NumEquipFlags = 24;
|
||||||
|
|
||||||
public static EquipFlag ToFlag(this EquipSlot slot)
|
public static EquipFlag ToFlag(this EquipSlot slot)
|
||||||
=> slot switch
|
=> slot switch
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Dalamud.Interface.Internal.Notifications;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using OtterGui.Classes;
|
||||||
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
||||||
|
|
||||||
namespace Glamourer;
|
namespace Glamourer;
|
||||||
|
|
@ -15,6 +16,7 @@ public class Configuration : IPluginConfiguration, ISavable
|
||||||
{
|
{
|
||||||
public bool UseRestrictedGearProtection { get; set; } = true;
|
public bool UseRestrictedGearProtection { get; set; } = true;
|
||||||
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
|
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
|
||||||
|
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -161,7 +159,7 @@ public unsafe struct DesignData
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly bool IsWeaponVisible()
|
public readonly bool IsWeaponVisible()
|
||||||
=> (_states & 0x08) == 0x09;
|
=> (_states & 0x08) == 0x08;
|
||||||
|
|
||||||
public bool SetWeaponVisible(bool value)
|
public bool SetWeaponVisible(bool value)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
_designManager = designManager;
|
_designManager = designManager;
|
||||||
_saveService = saveService;
|
_saveService = saveService;
|
||||||
_designChanged = designChanged;
|
_designChanged = designChanged;
|
||||||
_designChanged.Subscribe(OnDataChange, DesignChanged.Priority.DesignFileSystem);
|
_designChanged.Subscribe(OnDesignChange, DesignChanged.Priority.DesignFileSystem);
|
||||||
Changed += OnChange;
|
Changed += OnChange;
|
||||||
Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_designChanged.Unsubscribe(OnDataChange);
|
_designChanged.Unsubscribe(OnDesignChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CreationDate : ISortMode<Design>
|
public struct CreationDate : ISortMode<Design>
|
||||||
|
|
@ -96,7 +96,7 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
_saveService.QueueSave(this);
|
_saveService.QueueSave(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDataChange(DesignChanged.Type type, Design design, object? data)
|
private void OnDesignChange(DesignChanged.Type type, Design design, object? data)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,9 @@ public class DesignManager
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!design.DesignData.Customize.Set(idx, value))
|
if (!_customizations.IsCustomizationValid(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender,
|
||||||
|
design.DesignData.Customize.Face, idx, value)
|
||||||
|
|| !design.DesignData.Customize.Set(idx, value))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -228,7 +230,7 @@ public class DesignManager
|
||||||
/// <summary> Change a non-weapon equipment piece. </summary>
|
/// <summary> Change a non-weapon equipment piece. </summary>
|
||||||
public void ChangeEquip(Design design, EquipSlot slot, EquipItem item)
|
public void ChangeEquip(Design design, EquipSlot slot, EquipItem item)
|
||||||
{
|
{
|
||||||
if (_items.ValidateItem(slot, item.Id, out item).Length > 0)
|
if (!_items.IsItemValid(slot, item.Id, out item))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var old = design.DesignData.Item(slot);
|
var old = design.DesignData.Item(slot);
|
||||||
|
|
@ -250,32 +252,31 @@ public class DesignManager
|
||||||
{
|
{
|
||||||
case EquipSlot.MainHand:
|
case EquipSlot.MainHand:
|
||||||
var newOff = currentOff;
|
var newOff = currentOff;
|
||||||
if (item.Type == currentMain.Type)
|
if (!_items.IsItemValid(EquipSlot.MainHand, item.Id, out item))
|
||||||
{
|
return;
|
||||||
if (_items.ValidateWeapons(item.Id, currentOff.Id, out _, out _).Length != 0)
|
|
||||||
return;
|
if (item.Type != currentMain.Type)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
|
|
||||||
var newOffId = FullEquipTypeExtensions.OffhandTypes.Contains(item.Type)
|
var newOffId = FullEquipTypeExtensions.OffhandTypes.Contains(item.Type)
|
||||||
? item.Id
|
? item.Id
|
||||||
: ItemManager.NothingId(item.Type.Offhand());
|
: ItemManager.NothingId(item.Type.Offhand());
|
||||||
if (_items.ValidateWeapons(item.Id, newOffId, out _, out newOff).Length != 0)
|
if (!_items.IsOffhandValid(item, newOffId, out newOff))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
design.DesignData.SetItem(EquipSlot.MainHand, item);
|
if (!design.DesignData.SetItem(EquipSlot.MainHand, item) && !design.DesignData.SetItem(EquipSlot.OffHand, newOff))
|
||||||
design.DesignData.SetItem(EquipSlot.OffHand, newOff);
|
return;
|
||||||
|
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
_saveService.QueueSave(design);
|
_saveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug(
|
Glamourer.Log.Debug(
|
||||||
$"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.Id}) to {item.Name} ({item.Id}).");
|
$"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.Id}) to {item.Name} ({item.Id}).");
|
||||||
_event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff));
|
_event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case EquipSlot.OffHand:
|
case EquipSlot.OffHand:
|
||||||
if (item.Type != currentOff.Type)
|
if (!_items.IsOffhandValid(currentOff.Type, item.Id, out item))
|
||||||
return;
|
|
||||||
if (_items.ValidateWeapons(currentMain.Id, item.Id, out _, out _).Length > 0)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!design.DesignData.SetItem(EquipSlot.OffHand, item))
|
if (!design.DesignData.SetItem(EquipSlot.OffHand, item))
|
||||||
|
|
|
||||||
|
|
@ -60,11 +60,18 @@ public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Desi
|
||||||
|
|
||||||
/// <summary> An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. </summary>
|
/// <summary> An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. </summary>
|
||||||
ApplyStain,
|
ApplyStain,
|
||||||
|
|
||||||
|
/// <summary> An existing design changed one of the meta flags. Data is null. </summary>
|
||||||
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
|
/// <seealso cref="DesignFileSystem.OnDesignChange"/>
|
||||||
DesignFileSystem = 0,
|
DesignFileSystem = 0,
|
||||||
|
|
||||||
|
/// <seealso cref="Gui.Tabs.DesignTab.DesignFileSystemSelector.OnDesignChange"/>
|
||||||
|
DesignFileSystemSelector = -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
public DesignChanged()
|
public DesignChanged()
|
||||||
|
|
|
||||||
55
Glamourer/Events/StateChanged.cs
Normal file
55
Glamourer/Events/StateChanged.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
using System;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Glamourer.State;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a Design is edited in any way.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the type of the change </item>
|
||||||
|
/// <item>Parameter is the changed saved state. </item>
|
||||||
|
/// <item>Parameter is the existing actors using this saved state. </item>
|
||||||
|
/// <item>Parameter is any additional data depending on the type of change. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class StateChanged : EventWrapper<Action<StateChanged.Type, StateChanged.Source, ActorState, ActorData, object?>, StateChanged.Priority>
|
||||||
|
{
|
||||||
|
public enum Type
|
||||||
|
{
|
||||||
|
/// <summary> A characters saved state had a customization value changed. Data is the old value, the new value and the type. [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
|
||||||
|
Customize,
|
||||||
|
|
||||||
|
/// <summary> A characters saved state had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary>
|
||||||
|
Equip,
|
||||||
|
|
||||||
|
/// <summary> A characters saved state had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. </summary>
|
||||||
|
Weapon,
|
||||||
|
|
||||||
|
/// <summary> A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
||||||
|
Stain,
|
||||||
|
|
||||||
|
/// <summary> A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Source : byte
|
||||||
|
{
|
||||||
|
Game,
|
||||||
|
Manual,
|
||||||
|
Fixed,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public StateChanged()
|
||||||
|
: base(nameof(StateChanged))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void Invoke(Type type, Source source, ActorState state, ActorData actors, object? data = null)
|
||||||
|
=> Invoke(this, type, source, state, actors, data);
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,10 @@ namespace Glamourer.Events;
|
||||||
public sealed class UpdatedSlot : EventWrapper<Action<Model, EquipSlot, Ref<CharacterArmor>, Ref<ulong>>, UpdatedSlot.Priority>
|
public sealed class UpdatedSlot : EventWrapper<Action<Model, EquipSlot, Ref<CharacterArmor>, Ref<ulong>>, UpdatedSlot.Priority>
|
||||||
{
|
{
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{ }
|
{
|
||||||
|
/// <seealso cref="State.StateManager.OnSlotUpdated"/>
|
||||||
|
StateManager = 0,
|
||||||
|
}
|
||||||
|
|
||||||
public UpdatedSlot()
|
public UpdatedSlot()
|
||||||
: base(nameof(UpdatedSlot))
|
: base(nameof(UpdatedSlot))
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,26 @@ public enum ColorId
|
||||||
CustomizationDesign,
|
CustomizationDesign,
|
||||||
StateDesign,
|
StateDesign,
|
||||||
EquipmentDesign,
|
EquipmentDesign,
|
||||||
|
ActorAvailable,
|
||||||
|
ActorUnavailable,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Colors
|
public static class Colors
|
||||||
{
|
{
|
||||||
public const uint DiscordColor = 0xFFDA8972;
|
public const uint DiscordColor = 0xFFDA8972;
|
||||||
public const uint ReniColorButton = 0xFFCC648D;
|
public const uint ReniColorButton = 0xFFCC648D;
|
||||||
public const uint ReniColorHovered = 0xFFB070B0;
|
public const uint ReniColorHovered = 0xFFB070B0;
|
||||||
public const uint ReniColorActive = 0xFF9070E0;
|
public const uint ReniColorActive = 0xFF9070E0;
|
||||||
|
|
||||||
public static (uint DefaultColor, string Name, string Description) Data(this ColorId color)
|
public static (uint DefaultColor, string Name, string Description) Data(this ColorId color)
|
||||||
=> color switch
|
=> color switch
|
||||||
{
|
{
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ),
|
ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ),
|
||||||
ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that only changes meta state on a character." ),
|
ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that only changes meta state on a character." ),
|
||||||
ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ),
|
ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ),
|
||||||
|
ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ),
|
||||||
|
ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ),
|
||||||
_ => (0x00000000, string.Empty, string.Empty ),
|
_ => (0x00000000, string.Empty, string.Empty ),
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
};
|
};
|
||||||
|
|
|
||||||
64
Glamourer/Gui/Customization/CustomizationDrawer.Color.cs
Normal file
64
Glamourer/Gui/Customization/CustomizationDrawer.Color.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
|
public partial class CustomizationDrawer
|
||||||
|
{
|
||||||
|
private const string ColorPickerPopupName = "ColorPicker";
|
||||||
|
|
||||||
|
private void DrawColorPicker(CustomizeIndex index)
|
||||||
|
{
|
||||||
|
using var _ = SetId(index);
|
||||||
|
var (current, custom) = GetCurrentCustomization(index);
|
||||||
|
var color = ImGui.ColorConvertU32ToFloat4(custom.Color);
|
||||||
|
|
||||||
|
// Print 1-based index instead of 0.
|
||||||
|
if (ImGui.ColorButton($"{current + 1}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
|
||||||
|
ImGui.OpenPopup(ColorPickerPopupName);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
using (var group = ImRaii.Group())
|
||||||
|
{
|
||||||
|
DataInputInt(current);
|
||||||
|
ImGui.TextUnformatted(_currentOption);
|
||||||
|
}
|
||||||
|
DrawColorPickerPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawColorPickerPopup()
|
||||||
|
{
|
||||||
|
using var popup = ImRaii.Popup(ColorPickerPopupName, ImGuiWindowFlags.AlwaysAutoResize);
|
||||||
|
if (!popup)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
|
for (var i = 0; i < _currentCount; ++i)
|
||||||
|
{
|
||||||
|
var custom = _set.Data(_currentIndex, i, _customize[CustomizeIndex.Face]);
|
||||||
|
if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
|
||||||
|
{
|
||||||
|
UpdateValue(custom.Value);
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i % 8 != 7)
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain the current customization and print a warning if it is not known.
|
||||||
|
private (int, CustomizeData) GetCurrentCustomization(CustomizeIndex index)
|
||||||
|
{
|
||||||
|
var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face);
|
||||||
|
if (_set.IsAvailable(index) && current < 0)
|
||||||
|
throw new Exception($"Read invalid customization value {_customize[index]} for {index}.");
|
||||||
|
|
||||||
|
return (current, custom!.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
|
public partial class CustomizationDrawer
|
||||||
|
{
|
||||||
|
private void DrawRaceGenderSelector()
|
||||||
|
{
|
||||||
|
DrawGenderSelector();
|
||||||
|
ImGui.SameLine();
|
||||||
|
using var group = ImRaii.Group();
|
||||||
|
DrawRaceCombo();
|
||||||
|
var gender = _service.AwaitedService.GetName(CustomName.Gender);
|
||||||
|
var clan = _service.AwaitedService.GetName(CustomName.Clan);
|
||||||
|
ImGui.TextUnformatted($"{gender} & {clan}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGenderSelector()
|
||||||
|
{
|
||||||
|
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||||
|
var icon = _customize.Gender switch
|
||||||
|
{
|
||||||
|
Gender.Male when _customize.Race is Race.Hrothgar => FontAwesomeIcon.MarsDouble,
|
||||||
|
Gender.Male => FontAwesomeIcon.Mars,
|
||||||
|
Gender.Female => FontAwesomeIcon.Venus,
|
||||||
|
|
||||||
|
_ => 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;
|
||||||
|
|
||||||
|
_service.ChangeGender(ref _customize, _customize.Gender is Gender.Male ? Gender.Female : Gender.Male);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRaceCombo()
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth(_raceSelectorWidth);
|
||||||
|
using var combo = ImRaii.Combo("##subRaceCombo", _service.ClanName(_customize.Clan, _customize.Gender));
|
||||||
|
if (!combo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(_service.ClanName(subRace, _customize.Gender), subRace == _customize.Clan))
|
||||||
|
_service.ChangeClan(ref _customize, subRace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs
Normal file
150
Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
|
public partial class CustomizationDrawer
|
||||||
|
{
|
||||||
|
private const string IconSelectorPopup = "Style Picker";
|
||||||
|
|
||||||
|
private void DrawIconSelector(CustomizeIndex index)
|
||||||
|
{
|
||||||
|
using var _ = SetId(index);
|
||||||
|
using var bigGroup = ImRaii.Group();
|
||||||
|
var label = _currentOption;
|
||||||
|
|
||||||
|
var current = _set.DataByValue(index, _currentByte, out var custom, _customize.Face);
|
||||||
|
if (current < 0)
|
||||||
|
{
|
||||||
|
label = $"{_currentOption} (Custom #{_customize[index]})";
|
||||||
|
current = 0;
|
||||||
|
custom = _set.Data(index, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon = _service.AwaitedService.GetIcon(custom!.Value.IconId);
|
||||||
|
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||||
|
ImGui.OpenPopup(IconSelectorPopup);
|
||||||
|
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
using (var group = ImRaii.Group())
|
||||||
|
{
|
||||||
|
if (_currentIndex == CustomizeIndex.Face)
|
||||||
|
FaceInputInt(current);
|
||||||
|
else
|
||||||
|
DataInputInt(current);
|
||||||
|
|
||||||
|
ImGui.TextUnformatted($"{label} ({custom.Value.Value})");
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawIconPickerPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool UpdateFace(CustomizeData data)
|
||||||
|
{
|
||||||
|
// Hrothgar Hack
|
||||||
|
var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value;
|
||||||
|
if (_customize.Face == value)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_customize.Face = value;
|
||||||
|
Changed |= CustomizeFlag.Face;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FaceInputInt(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);
|
||||||
|
UpdateFace(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawIconPickerPopup()
|
||||||
|
{
|
||||||
|
using var popup = ImRaii.Popup(IconSelectorPopup, ImGuiWindowFlags.AlwaysAutoResize);
|
||||||
|
if (!popup)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
|
for (var i = 0; i < _currentCount; ++i)
|
||||||
|
{
|
||||||
|
var custom = _set.Data(_currentIndex, i, _customize.Face);
|
||||||
|
var icon = _service.AwaitedService.GetIcon(custom.IconId);
|
||||||
|
using (var _ = ImRaii.Group())
|
||||||
|
{
|
||||||
|
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||||
|
{
|
||||||
|
if (_currentIndex == CustomizeIndex.Face)
|
||||||
|
UpdateFace(custom);
|
||||||
|
else
|
||||||
|
UpdateValue(custom.Value);
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||||
|
|
||||||
|
var text = custom.Value.ToString();
|
||||||
|
var textWidth = ImGui.CalcTextSize(text).X;
|
||||||
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (_iconSize.X - textWidth + 2 * ImGui.GetStyle().FramePadding.X) / 2);
|
||||||
|
ImGui.TextUnformatted(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i % 8 != 7)
|
||||||
|
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 ?? _service.AwaitedService.GetIcon(feature.IconId)
|
||||||
|
: _service.AwaitedService.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
102
Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs
Normal file
102
Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
using System;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
|
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);
|
||||||
|
using var bigGroup = ImRaii.Group();
|
||||||
|
|
||||||
|
ListCombo();
|
||||||
|
ImGui.SameLine();
|
||||||
|
ListInputInt();
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextUnformatted(_currentOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ListCombo()
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
||||||
|
using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{_currentByte.Value + 1}");
|
||||||
|
|
||||||
|
if (!combo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = 0; i < _currentCount; ++i)
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == _currentByte.Value))
|
||||||
|
UpdateValue((CustomizeValue)i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ListInputInt()
|
||||||
|
{
|
||||||
|
var tmp = _currentByte.Value + 1;
|
||||||
|
ImGui.SetNextItemWidth(_inputIntSize);
|
||||||
|
if (ImGui.InputInt("##text", ref tmp, 1, 1) && tmp > 0 && tmp <= _currentCount)
|
||||||
|
UpdateValue((CustomizeValue)Math.Clamp(tmp - 1, 0, _currentCount - 1));
|
||||||
|
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a customize checkbox.
|
||||||
|
private void DrawCheckbox(CustomizeIndex idx)
|
||||||
|
{
|
||||||
|
using var id = SetId(idx);
|
||||||
|
var tmp = _currentByte != CustomizeValue.Zero;
|
||||||
|
if (ImGui.Checkbox(_currentOption, ref tmp))
|
||||||
|
{
|
||||||
|
_customize.Set(idx, tmp ? CustomizeValue.Max : CustomizeValue.Zero);
|
||||||
|
Changed |= _currentFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
152
Glamourer/Gui/Customization/CustomizationDrawer.cs
Normal file
152
Glamourer/Gui/Customization/CustomizationDrawer.cs
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
|
||||||
|
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 Exception? _terminate = null;
|
||||||
|
|
||||||
|
private Customize _customize;
|
||||||
|
private CustomizationSet _set = null!;
|
||||||
|
|
||||||
|
public Customize Customize;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
private readonly CustomizationService _service;
|
||||||
|
|
||||||
|
public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service)
|
||||||
|
{
|
||||||
|
_service = service;
|
||||||
|
_legacyTattoo = GetLegacyTattooIcon(pi);
|
||||||
|
Customize = Customize.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_legacyTattoo?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Draw(Customize current, bool locked)
|
||||||
|
{
|
||||||
|
CurrentFlag = CustomizeFlagExtensions.All;
|
||||||
|
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 = _service.AwaitedService.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ using System.Numerics;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Gui.Tabs;
|
using Glamourer.Gui.Tabs;
|
||||||
|
using Glamourer.Gui.Tabs.ActorTab;
|
||||||
|
using Glamourer.Gui.Tabs.DesignTab;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui.Custom;
|
using OtterGui.Custom;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
|
|
@ -16,17 +18,22 @@ public class MainWindow : Window
|
||||||
None = -1,
|
None = -1,
|
||||||
Settings = 0,
|
Settings = 0,
|
||||||
Debug = 1,
|
Debug = 1,
|
||||||
|
Actors = 2,
|
||||||
|
Designs = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly ITab[] _tabs;
|
private readonly ITab[] _tabs;
|
||||||
|
|
||||||
public readonly SettingsTab Settings;
|
public readonly SettingsTab Settings;
|
||||||
|
public readonly ActorTab Actors;
|
||||||
public readonly DebugTab Debug;
|
public readonly DebugTab Debug;
|
||||||
|
public readonly DesignTab Designs;
|
||||||
|
|
||||||
public TabType SelectTab = TabType.None;
|
public TabType SelectTab = TabType.None;
|
||||||
|
|
||||||
public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, DebugTab debugTab)
|
public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs,
|
||||||
|
DebugTab debugTab)
|
||||||
: base(GetLabel())
|
: base(GetLabel())
|
||||||
{
|
{
|
||||||
pi.UiBuilder.DisableGposeUiHide = true;
|
pi.UiBuilder.DisableGposeUiHide = true;
|
||||||
|
|
@ -37,10 +44,14 @@ public class MainWindow : Window
|
||||||
};
|
};
|
||||||
Settings = settings;
|
Settings = settings;
|
||||||
Debug = debugTab;
|
Debug = debugTab;
|
||||||
|
Designs = designs;
|
||||||
|
Actors = actors;
|
||||||
_config = config;
|
_config = config;
|
||||||
_tabs = new ITab[]
|
_tabs = new ITab[]
|
||||||
{
|
{
|
||||||
settings,
|
settings,
|
||||||
|
actors,
|
||||||
|
designs,
|
||||||
debugTab,
|
debugTab,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -62,12 +73,16 @@ public class MainWindow : Window
|
||||||
{
|
{
|
||||||
TabType.Settings => Settings.Label,
|
TabType.Settings => Settings.Label,
|
||||||
TabType.Debug => Debug.Label,
|
TabType.Debug => Debug.Label,
|
||||||
|
TabType.Actors => Actors.Label,
|
||||||
|
TabType.Designs => Designs.Label,
|
||||||
_ => ReadOnlySpan<byte>.Empty,
|
_ => ReadOnlySpan<byte>.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
private TabType FromLabel(ReadOnlySpan<byte> label)
|
private TabType FromLabel(ReadOnlySpan<byte> label)
|
||||||
{
|
{
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
if (label == Actors.Label) return TabType.Actors;
|
||||||
|
if (label == Designs.Label) return TabType.Designs;
|
||||||
if (label == Settings.Label) return TabType.Settings;
|
if (label == Settings.Label) return TabType.Settings;
|
||||||
if (label == Debug.Label) return TabType.Debug;
|
if (label == Debug.Label) return TabType.Debug;
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
|
||||||
218
Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs
Normal file
218
Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using Glamourer.Gui.Customization;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Glamourer.State;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs.ActorTab;
|
||||||
|
|
||||||
|
public class ActorPanel
|
||||||
|
{
|
||||||
|
private readonly ActorSelector _selector;
|
||||||
|
private readonly StateManager _stateManager;
|
||||||
|
private readonly CustomizationDrawer _customizationDrawer;
|
||||||
|
|
||||||
|
private ActorIdentifier _identifier;
|
||||||
|
private string _actorName = string.Empty;
|
||||||
|
private Actor _actor = Actor.Null;
|
||||||
|
private ActorData _data;
|
||||||
|
private ActorState? _state;
|
||||||
|
|
||||||
|
public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer)
|
||||||
|
{
|
||||||
|
_selector = selector;
|
||||||
|
_stateManager = stateManager;
|
||||||
|
_customizationDrawer = customizationDrawer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
if (!_selector.HasSelection)
|
||||||
|
return;
|
||||||
|
|
||||||
|
(_identifier, _data) = _selector.Selection;
|
||||||
|
if (_data.Valid)
|
||||||
|
{
|
||||||
|
_actorName = _data.Label;
|
||||||
|
_actor = _data.Objects[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_actorName = _identifier.ToString();
|
||||||
|
_actor = Actor.Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_stateManager.GetOrCreate(_identifier, _actor, out _state))
|
||||||
|
return;
|
||||||
|
|
||||||
|
//if (_state != null)
|
||||||
|
// _stateManager.Update(ref _state.Data, _actor);
|
||||||
|
|
||||||
|
using var group = ImRaii.Group();
|
||||||
|
DrawHeader();
|
||||||
|
DrawPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHeader()
|
||||||
|
{
|
||||||
|
var color = _data.Valid ? ColorId.ActorAvailable.Value() : ColorId.ActorUnavailable.Value();
|
||||||
|
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($"{(_data.Valid ? _data.Label : _identifier.ToString())}##playerHeader", -Vector2.UnitX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void DrawPanel()
|
||||||
|
{
|
||||||
|
using var child = ImRaii.Child("##ActorPanel", -Vector2.One, true);
|
||||||
|
if (!child || _state == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_customizationDrawer.Draw(_state.Data.Customize, false))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
// if (_currentData.Valid)
|
||||||
|
// _currentSave.Initialize(_items, _currentData.Objects[0]);
|
||||||
|
//
|
||||||
|
// RevertButton();
|
||||||
|
// ActorDebug.Draw(_currentSave.ModelData);
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// if (_main._customizationDrawer.Draw(_currentSave.ModelData.Customize, _identifier.Type == IdentifierType.Special))
|
||||||
|
// _activeDesigns.ChangeCustomize(_currentSave, _main._customizationDrawer.Changed, _main._customizationDrawer.Customize.Data,
|
||||||
|
// false);
|
||||||
|
//
|
||||||
|
// foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
// {
|
||||||
|
// var current = _currentSave.Armor(slot);
|
||||||
|
// if (_main._equipmentDrawer.DrawStain(current.Stain, slot, out var stain))
|
||||||
|
// _activeDesigns.ChangeStain(_currentSave, slot, stain.RowIndex, false);
|
||||||
|
// ImGui.SameLine();
|
||||||
|
// if (_main._equipmentDrawer.DrawArmor(current, slot, out var armor, _currentSave.ModelData.Customize.Gender,
|
||||||
|
// _currentSave.ModelData.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);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (_main._equipmentDrawer.DrawVisor(_currentSave, out var value))
|
||||||
|
// _activeDesigns.ChangeVisor(_currentSave, value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private unsafe void RevertButton()
|
||||||
|
{
|
||||||
|
//if (ImGui.Button("Revert"))
|
||||||
|
// _activeDesigns.RevertDesign(_currentSave!);
|
||||||
|
//foreach (var actor in _currentData.Objects)
|
||||||
|
// _currentSave!.ApplyToActor(actor);
|
||||||
|
//
|
||||||
|
//if (_currentData.Objects.Count > 0)
|
||||||
|
// _currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]);
|
||||||
|
//
|
||||||
|
//_currentSave!.Reset();
|
||||||
|
//if (_currentData.Objects.Count > 0)
|
||||||
|
// ImGui.TextUnformatted(_currentData.Objects[0].Pointer->GameObject.DataID.ToString());
|
||||||
|
//VisorBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
//private unsafe void VisorBox()
|
||||||
|
//{
|
||||||
|
// var (flags, mask) = (_currentSave!.Data.Flags & (ApplicationFlags.SetVisor | ApplicationFlags.Visor)) switch
|
||||||
|
// {
|
||||||
|
// ApplicationFlags.SetVisor => (0u, 3u),
|
||||||
|
// ApplicationFlags.Visor => (1u, 3u),
|
||||||
|
// ApplicationFlags.SetVisor | ApplicationFlags.Visor => (3u, 3u),
|
||||||
|
// _ => (2u, 3u),
|
||||||
|
// };
|
||||||
|
// var tmp = flags;
|
||||||
|
// if (ImGui.CheckboxFlags("Visor Toggled", ref tmp, mask))
|
||||||
|
// {
|
||||||
|
// _currentSave.Data.Flags = flags switch
|
||||||
|
// {
|
||||||
|
// 0 => (_currentSave.Data.Flags | ApplicationFlags.Visor) & ~ApplicationFlags.SetVisor,
|
||||||
|
// 1 => _currentSave.Data.Flags | ApplicationFlags.SetVisor,
|
||||||
|
// 2 => _currentSave.Data.Flags | ApplicationFlags.SetVisor,
|
||||||
|
// _ => _currentSave.Data.Flags & ~(ApplicationFlags.SetVisor | ApplicationFlags.Visor),
|
||||||
|
// };
|
||||||
|
// if (_currentSave.Data.Flags.HasFlag(ApplicationFlags.SetVisor))
|
||||||
|
// {
|
||||||
|
// var on = _currentSave.Data.Flags.HasFlag(ApplicationFlags.Visor);
|
||||||
|
// foreach (var actor in _currentData.Objects.Where(a => a.IsHuman && a.DrawObject))
|
||||||
|
// RedrawManager.SetVisor(actor.DrawObject.Pointer, on);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//private void DrawActorPanel()
|
||||||
|
//{
|
||||||
|
// using var group = ImRaii.Group();
|
||||||
|
// if (!_data.Identifier.IsValid)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// if (DrawCustomization(_currentSave.Customize, _currentSave.Equipment, !_data.Modifiable))
|
||||||
|
// //Glamourer.RedrawManager.Set(_data.Actor.Address, _character);
|
||||||
|
// Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true);
|
||||||
|
//
|
||||||
|
// if (ImGui.Button("Set Machinist Goggles"))
|
||||||
|
// Glamourer.RedrawManager.ChangeEquip(_data.Actor, EquipSlot.Head, new CharacterArmor(265, 1, 0));
|
||||||
|
//
|
||||||
|
// if (ImGui.Button("Set Weapon"))
|
||||||
|
// Glamourer.RedrawManager.LoadWeapon(_data.Actor.Address, new CharacterWeapon(0x00C9, 0x004E, 0x0001, 0x00),
|
||||||
|
// new CharacterWeapon(0x0065, 0x003D, 0x0001, 0x00));
|
||||||
|
//
|
||||||
|
// if (ImGui.Button("Set Customize"))
|
||||||
|
// {
|
||||||
|
// unsafe
|
||||||
|
// {
|
||||||
|
// var data = _data.Actor.Customize.Data->Clone();
|
||||||
|
// Glamourer.RedrawManager.UpdateCustomize(_data.Actor.DrawObject, new Customize(&data)
|
||||||
|
// {
|
||||||
|
// SkinColor = 154,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//private void DrawMonsterPanel()
|
||||||
|
//{
|
||||||
|
// using var group = ImRaii.Group();
|
||||||
|
// var currentModel = (uint)_data.Actor.ModelId;
|
||||||
|
// var models = GameData.Models(Dalamud.GameData);
|
||||||
|
// var currentData = models.Models.TryGetValue(currentModel, out var c) ? c.FirstName : $"#{currentModel}";
|
||||||
|
// using var combo = ImRaii.Combo("Model Id", currentData);
|
||||||
|
// if (!combo)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// foreach (var (id, data) in models.Models)
|
||||||
|
// {
|
||||||
|
// if (ImGui.Selectable(data.FirstName, id == currentModel) && id != currentModel)
|
||||||
|
// {
|
||||||
|
// _data.Actor.SetModelId((int)id);
|
||||||
|
// Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ImGuiUtil.HoverTooltip(data.AllNames);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
95
Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs
Normal file
95
Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs.ActorTab;
|
||||||
|
|
||||||
|
public class ActorSelector
|
||||||
|
{
|
||||||
|
private readonly ObjectManager _objects;
|
||||||
|
private readonly ActorService _actors;
|
||||||
|
private readonly TargetManager _targets;
|
||||||
|
|
||||||
|
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
|
||||||
|
|
||||||
|
public ActorSelector(ObjectManager objects, TargetManager targets, ActorService actors)
|
||||||
|
{
|
||||||
|
_objects = objects;
|
||||||
|
_targets = targets;
|
||||||
|
_actors = actors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LowerString _actorFilter = LowerString.Empty;
|
||||||
|
private Vector2 _defaultItemSpacing;
|
||||||
|
private float _width;
|
||||||
|
|
||||||
|
public (ActorIdentifier Identifier, ActorData Data) Selection
|
||||||
|
=> _objects.TryGetValue(_identifier, out var data) ? (_identifier, data) : (_identifier, ActorData.Invalid);
|
||||||
|
|
||||||
|
public bool HasSelection
|
||||||
|
=> _identifier.IsValid;
|
||||||
|
|
||||||
|
public void Draw(float width)
|
||||||
|
{
|
||||||
|
_width = width;
|
||||||
|
using var group = ImRaii.Group();
|
||||||
|
_defaultItemSpacing = ImGui.GetStyle().ItemSpacing;
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
|
ImGui.SetNextItemWidth(_width);
|
||||||
|
LowerString.InputWithHint("##actorFilter", "Filter...", ref _actorFilter, 64);
|
||||||
|
|
||||||
|
DrawSelector();
|
||||||
|
DrawSelectionButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSelector()
|
||||||
|
{
|
||||||
|
using var child = ImRaii.Child("##actorSelector", new Vector2(_width, -ImGui.GetFrameHeight()), true);
|
||||||
|
if (!child)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_objects.Update();
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
|
||||||
|
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
|
||||||
|
var remainder = ImGuiClip.FilteredClippedDraw(_objects, skips, CheckFilter, DrawSelectable);
|
||||||
|
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckFilter(KeyValuePair<ActorIdentifier, ActorData> pair)
|
||||||
|
=> _actorFilter.IsEmpty || pair.Value.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private void DrawSelectable(KeyValuePair<ActorIdentifier, ActorData> pair)
|
||||||
|
{
|
||||||
|
var equals = pair.Key.Equals(_identifier);
|
||||||
|
if (ImGui.Selectable(pair.Value.Label, equals) && !equals)
|
||||||
|
_identifier = pair.Key.CreatePermanent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSelectionButtons()
|
||||||
|
{
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
|
var buttonWidth = new Vector2(_width / 2, 0);
|
||||||
|
|
||||||
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
|
||||||
|
, "Select the local player character.", !_objects.Player, true))
|
||||||
|
_identifier = _objects.Player.GetIdentifier(_actors.AwaitedService);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
Actor targetActor = _targets.Target?.Address;
|
||||||
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth,
|
||||||
|
"Select the current target, if it is in the list.", _objects.IsInGPose || !targetActor, true))
|
||||||
|
_identifier = targetActor.GetIdentifier(_actors.AwaitedService);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Glamourer/Gui/Tabs/ActorTab/ActorTab.cs
Normal file
28
Glamourer/Gui/Tabs/ActorTab/ActorTab.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui.Widgets;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs.ActorTab;
|
||||||
|
|
||||||
|
public class ActorTab : ITab
|
||||||
|
{
|
||||||
|
private readonly ActorSelector _selector;
|
||||||
|
private readonly ActorPanel _panel;
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> Label
|
||||||
|
=> "Actors"u8;
|
||||||
|
|
||||||
|
public void DrawContent()
|
||||||
|
{
|
||||||
|
_selector.Draw(200 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.SameLine();
|
||||||
|
_panel.Draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorTab(ActorSelector selector, ActorPanel panel)
|
||||||
|
{
|
||||||
|
_selector = selector;
|
||||||
|
_panel = panel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -66,7 +66,7 @@ public unsafe class DebugTab : ITab
|
||||||
_designFileSystem = designFileSystem;
|
_designFileSystem = designFileSystem;
|
||||||
_designManager = designManager;
|
_designManager = designManager;
|
||||||
_state = state;
|
_state = state;
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
|
|
@ -74,6 +74,10 @@ public unsafe class DebugTab : ITab
|
||||||
|
|
||||||
public void DrawContent()
|
public void DrawContent()
|
||||||
{
|
{
|
||||||
|
using var child = ImRaii.Child("MainWindowChild");
|
||||||
|
if (!child)
|
||||||
|
return;
|
||||||
|
|
||||||
DrawInteropHeader();
|
DrawInteropHeader();
|
||||||
DrawGameDataHeader();
|
DrawGameDataHeader();
|
||||||
DrawPenumbraHeader();
|
DrawPenumbraHeader();
|
||||||
|
|
@ -829,7 +833,7 @@ public unsafe class DebugTab : ITab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawDesignData(in DesignData data)
|
public static void DrawDesignData(in DesignData data)
|
||||||
{
|
{
|
||||||
if (data.ModelId == 0)
|
if (data.ModelId == 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
66
Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs
Normal file
66
Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Events;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Filesystem;
|
||||||
|
using OtterGui.FileSystem.Selector;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||||
|
|
||||||
|
public sealed class DesignFileSystemSelector : FileSystemSelector<Design, DesignFileSystemSelector.DesignState>
|
||||||
|
{
|
||||||
|
private readonly DesignManager _designManager;
|
||||||
|
private readonly DesignChanged _event;
|
||||||
|
private readonly Configuration _config;
|
||||||
|
|
||||||
|
public struct DesignState
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, KeyState keyState, DesignChanged @event,
|
||||||
|
Configuration config)
|
||||||
|
: base(fileSystem, keyState)
|
||||||
|
{
|
||||||
|
_designManager = designManager;
|
||||||
|
_event = @event;
|
||||||
|
_config = config;
|
||||||
|
_event.Subscribe(OnDesignChange, DesignChanged.Priority.DesignFileSystemSelector);
|
||||||
|
AddButton(DeleteButton, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
_event.Unsubscribe(OnDesignChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDesignChange(DesignChanged.Type type, Design design, object? oldData)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case DesignChanged.Type.ReloadedAll:
|
||||||
|
case DesignChanged.Type.Renamed:
|
||||||
|
case DesignChanged.Type.AddedTag:
|
||||||
|
case DesignChanged.Type.ChangedTag:
|
||||||
|
case DesignChanged.Type.RemovedTag:
|
||||||
|
SetFilterDirty();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteButton(Vector2 size)
|
||||||
|
{
|
||||||
|
var keys = _config.DeleteDesignModifier.IsActive();
|
||||||
|
var tt = SelectedLeaf == null
|
||||||
|
? "No design selected."
|
||||||
|
: "Delete the currently selected design entirely from your drive.\n"
|
||||||
|
+ "This can not be undone.";
|
||||||
|
if (!keys)
|
||||||
|
tt += $"\nHold {_config.DeleteDesignModifier} while clicking to delete the mod.";
|
||||||
|
|
||||||
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true)
|
||||||
|
&& Selected != null)
|
||||||
|
_designManager.Delete(Selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs
Normal file
33
Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Gui.Customization;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||||
|
|
||||||
|
public class DesignPanel
|
||||||
|
{
|
||||||
|
private readonly DesignFileSystemSelector _selector;
|
||||||
|
private readonly DesignManager _manager;
|
||||||
|
private readonly CustomizationDrawer _customizationDrawer;
|
||||||
|
|
||||||
|
public DesignPanel(DesignFileSystemSelector selector, CustomizationDrawer customizationDrawer, DesignManager manager)
|
||||||
|
{
|
||||||
|
_selector = selector;
|
||||||
|
_customizationDrawer = customizationDrawer;
|
||||||
|
_manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
var design = _selector.Selected;
|
||||||
|
if (design == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var child = ImRaii.Child("##panel", -Vector2.One, true);
|
||||||
|
if (!child)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_customizationDrawer.Draw(design.DesignData.Customize, design.WriteProtected());
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Glamourer/Gui/Tabs/DesignTab/DesignTab.cs
Normal file
39
Glamourer/Gui/Tabs/DesignTab/DesignTab.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Interop;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui.Widgets;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||||
|
|
||||||
|
public class DesignTab : ITab
|
||||||
|
{
|
||||||
|
public readonly DesignFileSystemSelector Selector;
|
||||||
|
private readonly DesignFileSystem _fileSystem;
|
||||||
|
private readonly DesignManager _designManager;
|
||||||
|
private readonly DesignPanel _panel;
|
||||||
|
private readonly ObjectManager _objects;
|
||||||
|
|
||||||
|
public DesignTab(DesignFileSystemSelector selector, DesignFileSystem fileSystem, DesignManager designManager, ObjectManager objects, DesignPanel panel)
|
||||||
|
{
|
||||||
|
Selector = selector;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_designManager = designManager;
|
||||||
|
_objects = objects;
|
||||||
|
_panel = panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> Label
|
||||||
|
=> "Designs"u8;
|
||||||
|
|
||||||
|
public void DrawContent()
|
||||||
|
{
|
||||||
|
Selector.Draw(GetDesignSelectorSize());
|
||||||
|
ImGui.SameLine();
|
||||||
|
_panel.Draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetDesignSelectorSize()
|
||||||
|
=> 200f * ImGuiHelpers.GlobalScale;
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
|
|
||||||
|
|
@ -20,20 +22,23 @@ public class SettingsTab : ITab
|
||||||
|
|
||||||
public void DrawContent()
|
public void DrawContent()
|
||||||
{
|
{
|
||||||
using var child = ImRaii.Child("##SettingsTab", -Vector2.One, false);
|
using var child = ImRaii.Child("MainWindowChild");
|
||||||
if (!child)
|
if (!child)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Checkbox("Restricted Gear Protection",
|
Checkbox("Restricted Gear Protection",
|
||||||
"Use gender- and race-appropriate models when detecting certain items not available for a characters current gender and race.",
|
"Use gender- and race-appropriate models when detecting certain items not available for a characters current gender and race.",
|
||||||
_config.UseRestrictedGearProtection, v => _config.UseRestrictedGearProtection = v);
|
_config.UseRestrictedGearProtection, v => _config.UseRestrictedGearProtection = v);
|
||||||
|
if (Widget.DoubleModifierSelector("Design Deletion Modifier",
|
||||||
|
"A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale,
|
||||||
|
_config.DeleteDesignModifier, v => _config.DeleteDesignModifier = v))
|
||||||
|
_config.Save();
|
||||||
Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use.", _config.DebugMode, v => _config.DebugMode = v);
|
Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use.", _config.DebugMode, v => _config.DebugMode = v);
|
||||||
DrawColorSettings();
|
DrawColorSettings();
|
||||||
|
|
||||||
MainWindow.DrawSupportButtons();
|
MainWindow.DrawSupportButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary> Draw the entire Color subsection. </summary>
|
/// <summary> Draw the entire Color subsection. </summary>
|
||||||
private void DrawColorSettings()
|
private void DrawColorSettings()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.AccessControl;
|
using System.Runtime.CompilerServices;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -60,6 +59,26 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Returns whether a clan is valid. </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public bool IsClanValid(SubRace clan)
|
||||||
|
=> AwaitedService.Clans.Contains(clan);
|
||||||
|
|
||||||
|
/// <summary> Returns whether a gender is valid for the given race. </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public bool IsGenderValid(Race race, Gender gender)
|
||||||
|
=> race is Race.Hrothgar ? gender == Gender.Male : AwaitedService.Genders.Contains(gender);
|
||||||
|
|
||||||
|
/// <summary> Returns whether a customization value is valid for a given clan/gender set and face. </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public static bool IsCustomizationValid(CustomizationSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value)
|
||||||
|
=> set.DataByValue(type, value, out _, face) >= 0;
|
||||||
|
|
||||||
|
/// <summary> Returns whether a customization value is valid for a given clan, gender and face. </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public bool IsCustomizationValid(SubRace race, Gender gender, CustomizeValue face, CustomizeIndex type, CustomizeValue value)
|
||||||
|
=> AwaitedService.GetList(race, gender).DataByValue(type, value, out _, face) >= 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check that the given race and clan are valid.
|
/// Check that the given race and clan are valid.
|
||||||
/// The returned race and clan fit together and are valid.
|
/// The returned race and clan fit together and are valid.
|
||||||
|
|
@ -67,7 +86,7 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ValidateClan(SubRace clan, Race race, out Race actualRace, out SubRace actualClan)
|
public string ValidateClan(SubRace clan, Race race, out Race actualRace, out SubRace actualClan)
|
||||||
{
|
{
|
||||||
if (AwaitedService.Clans.Contains(clan))
|
if (IsClanValid(clan))
|
||||||
{
|
{
|
||||||
actualClan = clan;
|
actualClan = clan;
|
||||||
actualRace = actualClan.ToRace();
|
actualRace = actualClan.ToRace();
|
||||||
|
|
@ -113,7 +132,7 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Female Hrothgar
|
// TODO: Female Hrothgar
|
||||||
if (gender == Gender.Female && race == Race.Hrothgar)
|
if (gender is Gender.Female && race is Race.Hrothgar)
|
||||||
{
|
{
|
||||||
actualGender = Gender.Male;
|
actualGender = Gender.Male;
|
||||||
return $"{Race.Hrothgar.ToName()} do not currently support {Gender.Female.ToName()} characters, reset to {Gender.Male.ToName()}.";
|
return $"{Race.Hrothgar.ToName()} do not currently support {Gender.Female.ToName()} characters, reset to {Gender.Male.ToName()}.";
|
||||||
|
|
@ -134,7 +153,6 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
|
||||||
return modelId != 0 ? $"Model IDs different from 0 are not currently allowed, reset {modelId} to 0." : string.Empty;
|
return modelId != 0 ? $"Model IDs different from 0 are not currently allowed, reset {modelId} to 0." : string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validate a single customization value against a given set of race and gender (and face).
|
/// Validate a single customization value against a given set of race and gender (and face).
|
||||||
/// The returned actualValue is either the correct value or the one with index 0.
|
/// The returned actualValue is either the correct value or the one with index 0.
|
||||||
|
|
@ -143,9 +161,7 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
|
||||||
public static string ValidateCustomizeValue(CustomizationSet set, CustomizeValue face, CustomizeIndex index, CustomizeValue value,
|
public static string ValidateCustomizeValue(CustomizationSet set, CustomizeValue face, CustomizeIndex index, CustomizeValue value,
|
||||||
out CustomizeValue actualValue)
|
out CustomizeValue actualValue)
|
||||||
{
|
{
|
||||||
var count = set.Count(index, face);
|
if (IsCustomizationValid(set, face, index, value))
|
||||||
var idx = set.DataByValue(index, value, out var data, face);
|
|
||||||
if (idx >= 0 && idx < count)
|
|
||||||
{
|
{
|
||||||
actualValue = value;
|
actualValue = value;
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
|
|
@ -135,6 +136,14 @@ public class ItemManager : IDisposable
|
||||||
: new EquipItem($"Unknown ({id.Value}-{type.Value}-{variant})", 0, 0, id, type, variant, 0);
|
: new EquipItem($"Unknown ({id.Value}-{type.Value}-{variant})", 0, 0, id, type, variant, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Returns whether an item id represents a valid item for a slot and gives the item. </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public bool IsItemValid(EquipSlot slot, uint itemId, out EquipItem item)
|
||||||
|
{
|
||||||
|
item = Resolve(slot, itemId);
|
||||||
|
return item.Valid;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check whether an item id resolves to an existing item of the correct slot (which should not be weapons.)
|
/// Check whether an item id resolves to an existing item of the correct slot (which should not be weapons.)
|
||||||
/// The returned item is either the resolved correct item, or the Nothing item for that slot.
|
/// The returned item is either the resolved correct item, or the Nothing item for that slot.
|
||||||
|
|
@ -145,22 +154,26 @@ public class ItemManager : IDisposable
|
||||||
if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
|
if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
|
||||||
throw new Exception("Internal Error: Used armor functionality for weapons.");
|
throw new Exception("Internal Error: Used armor functionality for weapons.");
|
||||||
|
|
||||||
item = Resolve(slot, itemId);
|
if (IsItemValid(slot, itemId, out item))
|
||||||
if (item.Valid)
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
item = NothingItem(slot);
|
item = NothingItem(slot);
|
||||||
return $"The {slot.ToName()} item {itemId} does not exist, reset to Nothing.";
|
return $"The {slot.ToName()} item {itemId} does not exist, reset to Nothing.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Returns whether a stain id is a valid stain. </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public bool IsStainValid(StainId stain)
|
||||||
|
=> stain.Value == 0 || Stains.ContainsKey(stain);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check whether a stain id is an existing stain.
|
/// Check whether a stain id is an existing stain.
|
||||||
/// The returned stain id is either the input or 0.
|
/// The returned stain id is either the input or 0.
|
||||||
/// The return value is an empty string if there was no problem and a warning otherwise.
|
/// The return value is an empty string if there was no problem and a warning otherwise.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ValidateStain(StainId stain, out StainId ret)
|
public string ValidateStain(StainId stain, out StainId ret)
|
||||||
{
|
{
|
||||||
if (stain.Value == 0 || Stains.ContainsKey(stain))
|
if (IsStainValid(stain))
|
||||||
{
|
{
|
||||||
ret = stain;
|
ret = stain;
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
@ -170,6 +183,19 @@ public class ItemManager : IDisposable
|
||||||
return $"The Stain {stain} does not exist, reset to unstained.";
|
return $"The Stain {stain} does not exist, reset to unstained.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Returns whether an offhand is valid given the required offhand type. </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public bool IsOffhandValid(FullEquipType offType, uint offId, out EquipItem off)
|
||||||
|
{
|
||||||
|
off = Resolve(offType, offId);
|
||||||
|
return off.Valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Returns whether an offhand is valid given mainhand. </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public bool IsOffhandValid(in EquipItem main, uint offId, out EquipItem off)
|
||||||
|
=> IsOffhandValid(main.Type.Offhand(), offId, out off);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check whether a combination of an item id for a mainhand and for an offhand is valid.
|
/// Check whether a combination of an item id for a mainhand and for an offhand is valid.
|
||||||
/// The returned items are either the resolved correct items,
|
/// The returned items are either the resolved correct items,
|
||||||
|
|
@ -180,42 +206,30 @@ public class ItemManager : IDisposable
|
||||||
public string ValidateWeapons(uint mainId, uint offId, out EquipItem main, out EquipItem off)
|
public string ValidateWeapons(uint mainId, uint offId, out EquipItem main, out EquipItem off)
|
||||||
{
|
{
|
||||||
var ret = string.Empty;
|
var ret = string.Empty;
|
||||||
main = Resolve(EquipSlot.MainHand, mainId);
|
if (!IsItemValid(EquipSlot.MainHand, mainId, out main))
|
||||||
if (!main.Valid)
|
|
||||||
{
|
{
|
||||||
main = DefaultSword;
|
main = DefaultSword;
|
||||||
ret = $"The mainhand weapon {mainId} does not exist, reset to default sword.";
|
ret = $"The mainhand weapon {mainId} does not exist, reset to default sword.";
|
||||||
}
|
}
|
||||||
|
|
||||||
var offhandType = main.Type.Offhand();
|
var offType = main.Type.Offhand();
|
||||||
off = Resolve(offhandType, offId);
|
if (IsOffhandValid(offType, offId, out off))
|
||||||
if (off.Valid)
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
// Try implicit offhand.
|
// Try implicit offhand.
|
||||||
off = Resolve(offhandType, mainId);
|
// Can not be set to default sword before because then it could not be valid.
|
||||||
if (off.Valid)
|
if (IsOffhandValid(offType, mainId, out off))
|
||||||
|
return $"The offhand weapon {offId} does not exist, reset to implied offhand.";
|
||||||
|
|
||||||
|
if (FullEquipTypeExtensions.OffhandTypes.Contains(offType))
|
||||||
{
|
{
|
||||||
// Can not be set to default sword before because then it could not be valid.
|
main = DefaultSword;
|
||||||
ret = $"The offhand weapon {offId} does not exist, reset to implied offhand.";
|
off = NothingItem(FullEquipType.Shield);
|
||||||
}
|
return
|
||||||
else
|
$"The offhand weapon {offId} does not exist, but no default could be restored, reset mainhand to default sword and offhand to nothing.";
|
||||||
{
|
|
||||||
if (FullEquipTypeExtensions.OffhandTypes.Contains(offhandType))
|
|
||||||
{
|
|
||||||
main = DefaultSword;
|
|
||||||
off = NothingItem(FullEquipType.Shield);
|
|
||||||
ret =
|
|
||||||
$"The offhand weapon {offId} does not exist, but no default could be restored, reset mainhand to default sword and offhand to nothing.";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
off = NothingItem(offhandType);
|
|
||||||
if (ret.Length == 0)
|
|
||||||
ret = $"The offhand weapon {offId} does not exist, reset to no offhand.";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
off = NothingItem(offType);
|
||||||
|
return ret.Length == 0 ? $"The offhand weapon {offId} does not exist, reset to no offhand." : ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
|
using Glamourer.Gui.Customization;
|
||||||
using Glamourer.Gui.Tabs;
|
using Glamourer.Gui.Tabs;
|
||||||
|
using Glamourer.Gui.Tabs.ActorTab;
|
||||||
|
using Glamourer.Gui.Tabs.DesignTab;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.Interop.Penumbra;
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
|
|
@ -49,7 +52,8 @@ public static class ServiceManager
|
||||||
private static IServiceCollection AddEvents(this IServiceCollection services)
|
private static IServiceCollection AddEvents(this IServiceCollection services)
|
||||||
=> services.AddSingleton<VisorStateChanged>()
|
=> services.AddSingleton<VisorStateChanged>()
|
||||||
.AddSingleton<UpdatedSlot>()
|
.AddSingleton<UpdatedSlot>()
|
||||||
.AddSingleton<DesignChanged>();
|
.AddSingleton<DesignChanged>()
|
||||||
|
.AddSingleton<StateChanged>();
|
||||||
|
|
||||||
private static IServiceCollection AddData(this IServiceCollection services)
|
private static IServiceCollection AddData(this IServiceCollection services)
|
||||||
=> services.AddSingleton<IdentifierService>()
|
=> services.AddSingleton<IdentifierService>()
|
||||||
|
|
@ -76,8 +80,15 @@ public static class ServiceManager
|
||||||
private static IServiceCollection AddUi(this IServiceCollection services)
|
private static IServiceCollection AddUi(this IServiceCollection services)
|
||||||
=> services.AddSingleton<DebugTab>()
|
=> services.AddSingleton<DebugTab>()
|
||||||
.AddSingleton<SettingsTab>()
|
.AddSingleton<SettingsTab>()
|
||||||
|
.AddSingleton<ActorTab>()
|
||||||
|
.AddSingleton<ActorSelector>()
|
||||||
|
.AddSingleton<ActorPanel>()
|
||||||
.AddSingleton<MainWindow>()
|
.AddSingleton<MainWindow>()
|
||||||
.AddSingleton<GlamourerWindowSystem>();
|
.AddSingleton<GlamourerWindowSystem>()
|
||||||
|
.AddSingleton<CustomizationDrawer>()
|
||||||
|
.AddSingleton<DesignFileSystemSelector>()
|
||||||
|
.AddSingleton<DesignPanel>()
|
||||||
|
.AddSingleton<DesignTab>();
|
||||||
|
|
||||||
private static IServiceCollection AddApi(this IServiceCollection services)
|
private static IServiceCollection AddApi(this IServiceCollection services)
|
||||||
=> services.AddSingleton<CommandService>();
|
=> services.AddSingleton<CommandService>();
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,39 @@
|
||||||
using Glamourer.Designs;
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Events;
|
||||||
|
using Glamourer.Structs;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using System.Linq;
|
||||||
|
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
|
||||||
|
|
||||||
namespace Glamourer.State;
|
namespace Glamourer.State;
|
||||||
|
|
||||||
public class ActorState
|
public class ActorState
|
||||||
{
|
{
|
||||||
|
public enum MetaFlag
|
||||||
|
{
|
||||||
|
Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices,
|
||||||
|
HatState,
|
||||||
|
VisorState,
|
||||||
|
WeaponState,
|
||||||
|
}
|
||||||
|
|
||||||
public ActorIdentifier Identifier { get; internal init; }
|
public ActorIdentifier Identifier { get; internal init; }
|
||||||
public DesignData Data { get; internal set; }
|
public DesignData Data;
|
||||||
|
|
||||||
|
private readonly StateChanged.Source[] _sources = Enumerable
|
||||||
|
.Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 4).ToArray();
|
||||||
|
|
||||||
internal ActorState(ActorIdentifier identifier)
|
internal ActorState(ActorIdentifier identifier)
|
||||||
=> Identifier = identifier;
|
=> Identifier = identifier;
|
||||||
|
|
||||||
|
public ref StateChanged.Source this[EquipSlot slot, bool stain]
|
||||||
|
=> ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)];
|
||||||
|
|
||||||
|
public ref StateChanged.Source this[CustomizeIndex type]
|
||||||
|
=> ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type];
|
||||||
|
|
||||||
|
public ref StateChanged.Source this[MetaFlag flag]
|
||||||
|
=> ref _sources[(int)flag];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
123
Glamourer/State/StateEditor.cs
Normal file
123
Glamourer/State/StateEditor.cs
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
using System.Linq;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.State;
|
||||||
|
|
||||||
|
public class StateEditor
|
||||||
|
{
|
||||||
|
private readonly UpdateSlotService _updateSlot;
|
||||||
|
private readonly VisorService _visor;
|
||||||
|
private readonly WeaponService _weapon;
|
||||||
|
private readonly ChangeCustomizeService _changeCustomize;
|
||||||
|
private readonly ItemManager _items;
|
||||||
|
|
||||||
|
public StateEditor(UpdateSlotService updateSlot, VisorService visor, WeaponService weapon, ChangeCustomizeService changeCustomize,
|
||||||
|
ItemManager items)
|
||||||
|
{
|
||||||
|
_updateSlot = updateSlot;
|
||||||
|
_visor = visor;
|
||||||
|
_weapon = weapon;
|
||||||
|
_changeCustomize = changeCustomize;
|
||||||
|
_items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeCustomize(ActorData data, Customize customize)
|
||||||
|
{
|
||||||
|
foreach (var actor in data.Objects)
|
||||||
|
_changeCustomize.UpdateCustomize(actor, customize.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeCustomize(ActorData data, CustomizeIndex idx, CustomizeValue value)
|
||||||
|
{
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
{
|
||||||
|
var mdl = actor.Model;
|
||||||
|
var customize = mdl.GetCustomize();
|
||||||
|
customize[idx] = value;
|
||||||
|
_changeCustomize.UpdateCustomize(mdl, customize.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeArmor(ActorData data, EquipSlot slot, EquipItem item)
|
||||||
|
{
|
||||||
|
var idx = slot.ToIndex();
|
||||||
|
if (idx >= 10)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var armor = item.Armor();
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
{
|
||||||
|
var mdl = actor.Model;
|
||||||
|
var customize = mdl.IsHuman ? mdl.GetCustomize() : actor.GetCustomize();
|
||||||
|
var (_, resolvedItem) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
|
||||||
|
_updateSlot.UpdateArmor(actor.Model, slot, resolvedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeStain(ActorData data, EquipSlot slot, StainId stain)
|
||||||
|
{
|
||||||
|
var idx = slot.ToIndex();
|
||||||
|
switch (idx)
|
||||||
|
{
|
||||||
|
case < 10:
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
_updateSlot.UpdateStain(actor.Model, slot, stain);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
_weapon.LoadStain(actor, EquipSlot.MainHand, stain);
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
_weapon.LoadStain(actor, EquipSlot.OffHand, stain);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeMainhand(ActorData data, EquipItem weapon)
|
||||||
|
{
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
_weapon.LoadWeapon(actor, EquipSlot.MainHand, weapon.Weapon());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeOffhand(ActorData data, EquipItem weapon)
|
||||||
|
{
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeVisor(ActorData data, bool value)
|
||||||
|
{
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
{
|
||||||
|
var mdl = actor.Model;
|
||||||
|
if (!mdl.IsHuman)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_visor.SetVisorState(mdl, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void ChangeWetness(ActorData data, bool value)
|
||||||
|
{
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
actor.AsCharacter->IsGPoseWet = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void ChangeHatState(ActorData data, bool value)
|
||||||
|
{
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
actor.AsCharacter->DrawData.HideHeadgear(0, !value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void ChangeWeaponState(ActorData data, bool value)
|
||||||
|
{
|
||||||
|
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||||
|
actor.AsCharacter->DrawData.HideWeapons(!value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,30 +5,78 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Events;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
|
using OtterGui.Classes;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.State;
|
namespace Glamourer.State;
|
||||||
|
|
||||||
public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>, IDisposable
|
||||||
{
|
{
|
||||||
private readonly ActorService _actors;
|
private readonly ActorService _actors;
|
||||||
private readonly ItemManager _items;
|
private readonly ItemManager _items;
|
||||||
private readonly CustomizationService _customizations;
|
private readonly CustomizationService _customizations;
|
||||||
private readonly VisorService _visor;
|
private readonly VisorService _visor;
|
||||||
|
private readonly StateChanged _event;
|
||||||
|
|
||||||
|
private readonly PenumbraService _penumbra;
|
||||||
|
private readonly UpdatedSlot _updatedSlot;
|
||||||
|
|
||||||
private readonly Dictionary<ActorIdentifier, ActorState> _states = new();
|
private readonly Dictionary<ActorIdentifier, ActorState> _states = new();
|
||||||
|
|
||||||
public StateManager(ActorService actors, ItemManager items, CustomizationService customizations, VisorService visor)
|
public StateManager(ActorService actors, ItemManager items, CustomizationService customizations, VisorService visor, StateChanged @event,
|
||||||
|
UpdatedSlot updatedSlot, PenumbraService penumbra)
|
||||||
{
|
{
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
_items = items;
|
_items = items;
|
||||||
_customizations = customizations;
|
_customizations = customizations;
|
||||||
_visor = visor;
|
_visor = visor;
|
||||||
|
_event = @event;
|
||||||
|
_updatedSlot = updatedSlot;
|
||||||
|
_penumbra = penumbra;
|
||||||
|
_updatedSlot.Subscribe(OnSlotUpdated, UpdatedSlot.Priority.StateManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_updatedSlot.Unsubscribe(OnSlotUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void OnSlotUpdated(Model model, EquipSlot slot, Ref<CharacterArmor> armor, Ref<ulong> returnValue)
|
||||||
|
{
|
||||||
|
var actor = _penumbra.GameObjectFromDrawObject(model);
|
||||||
|
var customize = model.GetCustomize();
|
||||||
|
if (!actor.AsCharacter->DrawData.IsHatHidden && actor.Identifier(_actors.AwaitedService, out var identifier) && _states.TryGetValue(identifier, out var state))
|
||||||
|
{
|
||||||
|
ref var armorState = ref state[slot, false];
|
||||||
|
ref var stainState = ref state[slot, true];
|
||||||
|
if (armorState != StateChanged.Source.Fixed)
|
||||||
|
{
|
||||||
|
armorState = StateChanged.Source.Game;
|
||||||
|
var current = state.Data.Item(slot);
|
||||||
|
if (current.ModelId.Value != armor.Value.Set.Value || current.Variant != armor.Value.Variant)
|
||||||
|
{
|
||||||
|
var item = _items.Identify(slot, armor.Value.Set, armor.Value.Variant);
|
||||||
|
state.Data.SetItem(slot, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stainState != StateChanged.Source.Fixed)
|
||||||
|
{
|
||||||
|
stainState = StateChanged.Source.Game;
|
||||||
|
state.Data.SetStain(slot, armor.Value.Stain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (replaced, replacedArmor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
|
||||||
|
if (replaced)
|
||||||
|
armor.Assign(replacedArmor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state)
|
public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state)
|
||||||
|
|
@ -52,6 +100,87 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe void Update(ref DesignData data, Actor actor)
|
||||||
|
{
|
||||||
|
if (!actor.IsCharacter)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (actor.AsCharacter->ModelCharaId != data.ModelId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var model = actor.Model;
|
||||||
|
|
||||||
|
static bool EqualArmor(CharacterArmor armor, EquipItem item)
|
||||||
|
=> armor.Set.Value == item.ModelId.Value && armor.Variant == item.Variant;
|
||||||
|
|
||||||
|
static bool EqualWeapon(CharacterWeapon weapon, EquipItem item)
|
||||||
|
=> weapon.Set.Value == item.ModelId.Value && weapon.Type.Value == item.WeaponType.Value && weapon.Variant == item.Variant;
|
||||||
|
|
||||||
|
data.SetHatVisible(!actor.AsCharacter->DrawData.IsHatHidden);
|
||||||
|
data.SetIsWet(actor.AsCharacter->IsGPoseWet);
|
||||||
|
data.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden);
|
||||||
|
|
||||||
|
CharacterWeapon main;
|
||||||
|
CharacterWeapon off;
|
||||||
|
if (model.IsHuman)
|
||||||
|
{
|
||||||
|
var head = data.IsHatVisible() ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
|
||||||
|
data.SetStain(EquipSlot.Head, head.Stain);
|
||||||
|
if (!EqualArmor(head, data.Item(EquipSlot.Head)))
|
||||||
|
{
|
||||||
|
var headItem = _items.Identify(EquipSlot.Head, head.Set, head.Variant);
|
||||||
|
data.SetItem(EquipSlot.Head, headItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
|
||||||
|
{
|
||||||
|
var armor = model.GetArmor(slot);
|
||||||
|
data.SetStain(slot, armor.Stain);
|
||||||
|
if (EqualArmor(armor, data.Item(slot)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var item = _items.Identify(slot, armor.Set, armor.Variant);
|
||||||
|
data.SetItem(slot, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Customize = model.GetCustomize();
|
||||||
|
(_, _, main, off) = model.GetWeapons(actor);
|
||||||
|
data.SetVisor(_visor.GetVisorState(model));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
{
|
||||||
|
var armor = actor.GetArmor(slot);
|
||||||
|
data.SetStain(slot, armor.Stain);
|
||||||
|
if (EqualArmor(armor, data.Item(slot)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var item = _items.Identify(slot, armor.Set, armor.Variant);
|
||||||
|
data.SetItem(slot, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Customize = actor.GetCustomize();
|
||||||
|
main = actor.GetMainhand();
|
||||||
|
off = actor.GetOffhand();
|
||||||
|
data.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.SetStain(EquipSlot.MainHand, main.Stain);
|
||||||
|
data.SetStain(EquipSlot.OffHand, off.Stain);
|
||||||
|
if (!EqualWeapon(main, data.Item(EquipSlot.MainHand)))
|
||||||
|
{
|
||||||
|
var mainItem = _items.Identify(EquipSlot.MainHand, main.Set, main.Type, (byte)main.Variant);
|
||||||
|
data.SetItem(EquipSlot.MainHand, mainItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!EqualWeapon(off, data.Item(EquipSlot.OffHand)))
|
||||||
|
{
|
||||||
|
var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, (byte)off.Variant, data.Item(EquipSlot.MainHand).Type);
|
||||||
|
data.SetItem(EquipSlot.OffHand, offItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActorState>> GetEnumerator()
|
public IEnumerator<KeyValuePair<ActorIdentifier, ActorState>> GetEnumerator()
|
||||||
=> _states.GetEnumerator();
|
=> _states.GetEnumerator();
|
||||||
|
|
||||||
|
|
@ -143,4 +272,141 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
ret.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden);
|
ret.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Change a customization value. </summary>
|
||||||
|
public void ChangeCustomize(ActorState state, ActorData data, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source,
|
||||||
|
bool force)
|
||||||
|
{
|
||||||
|
ref var s = ref state[idx];
|
||||||
|
if (s is StateChanged.Source.Fixed && source is StateChanged.Source.Game)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var oldValue = state.Data.Customize[idx];
|
||||||
|
if (oldValue == value && !force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
state.Data.Customize[idx] = value;
|
||||||
|
|
||||||
|
Glamourer.Log.Excessive(
|
||||||
|
$"Changed customize {idx.ToDefaultName()} for {state.Identifier} ({string.Join(", ", data.Objects.Select(o => $"0x{o.Address}"))}) from {oldValue.Value} to {value.Value}.");
|
||||||
|
_event.Invoke(StateChanged.Type.Customize, source, state, data, (oldValue, value, idx));
|
||||||
|
}
|
||||||
|
//
|
||||||
|
///// <summary> Change whether to apply a specific customize value. </summary>
|
||||||
|
//public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
|
||||||
|
//{
|
||||||
|
// if (!design.SetApplyCustomize(idx, value))
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
// _saveService.QueueSave(design);
|
||||||
|
// Glamourer.Log.Debug($"Set applying of customization {idx.ToDefaultName()} to {value}.");
|
||||||
|
// _event.Invoke(DesignChanged.Type.ApplyCustomize, design, idx);
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///// <summary> Change a non-weapon equipment piece. </summary>
|
||||||
|
//public void ChangeEquip(Design design, EquipSlot slot, EquipItem item)
|
||||||
|
//{
|
||||||
|
// if (_items.ValidateItem(slot, item.Id, out item).Length > 0)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// var old = design.DesignData.Item(slot);
|
||||||
|
// if (!design.DesignData.SetItem(slot, item))
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// Glamourer.Log.Debug(
|
||||||
|
// $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}).");
|
||||||
|
// _saveService.QueueSave(design);
|
||||||
|
// _event.Invoke(DesignChanged.Type.Equip, design, (old, item, slot));
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///// <summary> Change a weapon. </summary>
|
||||||
|
//public void ChangeWeapon(Design design, EquipSlot slot, EquipItem item)
|
||||||
|
//{
|
||||||
|
// var currentMain = design.DesignData.Item(EquipSlot.MainHand);
|
||||||
|
// var currentOff = design.DesignData.Item(EquipSlot.OffHand);
|
||||||
|
// switch (slot)
|
||||||
|
// {
|
||||||
|
// case EquipSlot.MainHand:
|
||||||
|
// var newOff = currentOff;
|
||||||
|
// if (item.Type == currentMain.Type)
|
||||||
|
// {
|
||||||
|
// if (_items.ValidateWeapons(item.Id, currentOff.Id, out _, out _).Length != 0)
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// var newOffId = FullEquipTypeExtensions.OffhandTypes.Contains(item.Type)
|
||||||
|
// ? item.Id
|
||||||
|
// : ItemManager.NothingId(item.Type.Offhand());
|
||||||
|
// if (_items.ValidateWeapons(item.Id, newOffId, out _, out newOff).Length != 0)
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// design.DesignData.SetItem(EquipSlot.MainHand, item);
|
||||||
|
// design.DesignData.SetItem(EquipSlot.OffHand, newOff);
|
||||||
|
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
// _saveService.QueueSave(design);
|
||||||
|
// Glamourer.Log.Debug(
|
||||||
|
// $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.Id}) to {item.Name} ({item.Id}).");
|
||||||
|
// _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff));
|
||||||
|
// return;
|
||||||
|
// case EquipSlot.OffHand:
|
||||||
|
// if (item.Type != currentOff.Type)
|
||||||
|
// return;
|
||||||
|
// if (_items.ValidateWeapons(currentMain.Id, item.Id, out _, out _).Length > 0)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// if (!design.DesignData.SetItem(EquipSlot.OffHand, item))
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
// _saveService.QueueSave(design);
|
||||||
|
// Glamourer.Log.Debug(
|
||||||
|
// $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.Id}) to {item.Name} ({item.Id}).");
|
||||||
|
// _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item));
|
||||||
|
// return;
|
||||||
|
// default: return;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///// <summary> Change whether to apply a specific equipment piece. </summary>
|
||||||
|
//public void ChangeApplyEquip(Design design, EquipSlot slot, bool value)
|
||||||
|
//{
|
||||||
|
// if (!design.SetApplyEquip(slot, value))
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
// _saveService.QueueSave(design);
|
||||||
|
// Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}.");
|
||||||
|
// _event.Invoke(DesignChanged.Type.ApplyEquip, design, slot);
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///// <summary> Change the stain for any equipment piece. </summary>
|
||||||
|
//public void ChangeStain(Design design, EquipSlot slot, StainId stain)
|
||||||
|
//{
|
||||||
|
// if (_items.ValidateStain(stain, out _).Length > 0)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// var oldStain = design.DesignData.Stain(slot);
|
||||||
|
// if (!design.DesignData.SetStain(slot, stain))
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
// _saveService.QueueSave(design);
|
||||||
|
// Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Value}.");
|
||||||
|
// _event.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot));
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///// <summary> Change whether to apply a specific stain. </summary>
|
||||||
|
//public void ChangeApplyStain(Design design, EquipSlot slot, bool value)
|
||||||
|
//{
|
||||||
|
// if (!design.SetApplyStain(slot, value))
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
// _saveService.QueueSave(design);
|
||||||
|
// Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}.");
|
||||||
|
// _event.Invoke(DesignChanged.Type.ApplyStain, design, slot);
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@ public enum EquipFlag : uint
|
||||||
|
|
||||||
public static class EquipFlagExtensions
|
public static class EquipFlagExtensions
|
||||||
{
|
{
|
||||||
public const EquipFlag All = (EquipFlag)(((uint)EquipFlag.OffhandStain << 1) - 1);
|
public const EquipFlag All = (EquipFlag)(((uint)EquipFlag.OffhandStain << 1) - 1);
|
||||||
|
public const int NumEquipFlags = 24;
|
||||||
|
|
||||||
public static EquipFlag ToFlag(this EquipSlot slot)
|
public static EquipFlag ToFlag(this EquipSlot slot)
|
||||||
=> slot switch
|
=> slot switch
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue