Remove GlamourerOld.

This commit is contained in:
Ottermandias 2023-06-29 15:05:17 +02:00
parent d621369094
commit c505286220
75 changed files with 384 additions and 8734 deletions

View file

@ -25,6 +25,14 @@ public class AutoDesign
public JobGroup Jobs;
public Type ApplicationType;
public AutoDesign Clone()
=> new()
{
Design = Design,
ApplicationType = ApplicationType,
Jobs = Jobs,
};
public unsafe bool IsActive(Actor actor)
=> actor.IsCharacter && Jobs.Fits(actor.AsCharacter->CharacterData.ClassJob);

View file

@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.AccessControl;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
@ -8,6 +9,8 @@ using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Structs;
using Lumina.Excel.GeneratedSheets;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
@ -46,13 +49,19 @@ public class AutoDesignApplier : IDisposable
if (!_config.EnableAutoDesigns || !actor.Identifier(_actors.AwaitedService, out var id))
return;
if (!_manager.EnabledSets.TryGetValue(id, out var set))
if (!GetPlayerSet(id, out var set))
{
if (_state.TryGetValue(id, out var s))
s.LastJob = actor.Job;
return;
}
if (!_state.GetOrCreate(id, actor, out var state))
return;
Reduce(actor, state, set);
var sameJob = state.LastJob == actor.Job;
state.LastJob = actor.Job;
Reduce(actor, state, set, sameJob);
_state.ReapplyState(actor);
}
@ -63,14 +72,14 @@ public class AutoDesignApplier : IDisposable
if (!GetPlayerSet(identifier, out var set))
return;
Reduce(actor, state, set);
Reduce(actor, state, set, true);
}
private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set)
private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual)
{
EquipFlag totalEquipFlags = 0;
//var totalCustomizeFlags = _phrasing.Phrasing2 ? 0 : CustomizeFlagExtensions.RedrawRequired;
var totalCustomizeFlags = CustomizeFlagExtensions.RedrawRequired;
var totalCustomizeFlags = _phrasing.Phrasing2 ? 0 : CustomizeFlagExtensions.RedrawRequired;
byte totalMetaFlags = 0;
foreach (var design in set.Designs)
{
@ -84,9 +93,9 @@ public class AutoDesignApplier : IDisposable
continue;
var (equipFlags, customizeFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat();
Reduce(state, in design.Design.DesignData, equipFlags, ref totalEquipFlags);
Reduce(state, in design.Design.DesignData, customizeFlags, ref totalCustomizeFlags);
Reduce(state, in design.Design.DesignData, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags);
Reduce(state, in design.Design.DesignData, equipFlags, ref totalEquipFlags, respectManual);
Reduce(state, in design.Design.DesignData, customizeFlags, ref totalCustomizeFlags, respectManual);
Reduce(state, in design.Design.DesignData, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual);
}
}
@ -103,7 +112,7 @@ public class AutoDesignApplier : IDisposable
return _manager.EnabledSets.TryGetValue(identifier, out set);
}
private void Reduce(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags)
private void Reduce(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual)
{
equipFlags &= ~totalEquipFlags;
if (equipFlags == 0)
@ -115,6 +124,7 @@ public class AutoDesignApplier : IDisposable
var flag = slot.ToFlag();
if (equipFlags.HasFlag(flag))
{
if (!respectManual || state[slot, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, slot, design.Item(slot), StateChanged.Source.Fixed);
totalEquipFlags |= flag;
}
@ -122,6 +132,7 @@ public class AutoDesignApplier : IDisposable
var stainFlag = slot.ToStainFlag();
if (equipFlags.HasFlag(stainFlag))
{
if (!respectManual || state[slot, true] is not StateChanged.Source.Manual)
_state.ChangeStain(state, slot, design.Stain(slot), StateChanged.Source.Fixed);
totalEquipFlags |= stainFlag;
}
@ -132,6 +143,7 @@ public class AutoDesignApplier : IDisposable
var item = design.Item(EquipSlot.MainHand);
if (state.ModelData.Item(EquipSlot.MainHand).Type == item.Type)
{
if (!respectManual || state[EquipSlot.MainHand, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, EquipSlot.MainHand, item, StateChanged.Source.Fixed);
totalEquipFlags |= EquipFlag.Mainhand;
}
@ -142,6 +154,7 @@ public class AutoDesignApplier : IDisposable
var item = design.Item(EquipSlot.OffHand);
if (state.ModelData.Item(EquipSlot.OffHand).Type == item.Type)
{
if (!respectManual || state[EquipSlot.OffHand, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, EquipSlot.OffHand, item, StateChanged.Source.Fixed);
totalEquipFlags |= EquipFlag.Offhand;
}
@ -149,24 +162,55 @@ public class AutoDesignApplier : IDisposable
if (equipFlags.HasFlag(EquipFlag.MainhandStain))
{
if (!respectManual || state[EquipSlot.MainHand, true] is not StateChanged.Source.Manual)
_state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), StateChanged.Source.Fixed);
totalEquipFlags |= EquipFlag.MainhandStain;
}
if (equipFlags.HasFlag(EquipFlag.OffhandStain))
{
if (!respectManual || state[EquipSlot.OffHand, true] is not StateChanged.Source.Manual)
_state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), StateChanged.Source.Fixed);
totalEquipFlags |= EquipFlag.OffhandStain;
}
}
private void Reduce(ActorState state, in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag totalCustomizeFlags)
private void Reduce(ActorState state, in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag totalCustomizeFlags,
bool respectManual)
{
customizeFlags &= ~totalCustomizeFlags;
if (customizeFlags == 0)
return;
// TODO add race/gender handling
var customize = state.ModelData.Customize;
CustomizeFlag fixFlags = 0;
if (customizeFlags.HasFlag(CustomizeFlag.Clan))
{
if (!respectManual || state[CustomizeIndex.Clan] is not StateChanged.Source.Manual)
fixFlags |= _customizations.ChangeClan(ref customize, design.Customize.Clan);
customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race);
totalCustomizeFlags |= CustomizeFlag.Clan | CustomizeFlag.Race;
}
if (customizeFlags.HasFlag(CustomizeFlag.Gender))
{
if (!respectManual || state[CustomizeIndex.Gender] is not StateChanged.Source.Manual)
fixFlags |= _customizations.ChangeGender(ref customize, design.Customize.Gender);
customizeFlags &= ~CustomizeFlag.Gender;
totalCustomizeFlags |= CustomizeFlag.Gender;
}
if (fixFlags != 0)
_state.ChangeCustomize(state, customize, fixFlags, StateChanged.Source.Fixed);
if (customizeFlags.HasFlag(CustomizeFlag.Face))
{
if (!respectManual || state[CustomizeIndex.Face] is not StateChanged.Source.Manual)
_state.ChangeCustomize(state, CustomizeIndex.Face, design.Customize.Face, StateChanged.Source.Fixed);
customizeFlags &= ~CustomizeFlag.Face;
totalCustomizeFlags |= CustomizeFlag.Face;
}
var set = _customizations.AwaitedService.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
var face = state.ModelData.Customize.Face;
foreach (var index in Enum.GetValues<CustomizeIndex>())
@ -178,6 +222,7 @@ public class AutoDesignApplier : IDisposable
var value = design.Customize[index];
if (CustomizationService.IsCustomizationValid(set, face, index, value))
{
if (!respectManual || state[index] is not StateChanged.Source.Manual)
_state.ChangeCustomize(state, index, value, StateChanged.Source.Fixed);
totalCustomizeFlags |= flag;
}
@ -185,28 +230,32 @@ public class AutoDesignApplier : IDisposable
}
private void Reduce(ActorState state, in DesignData design, bool applyHat, bool applyVisor, bool applyWeapon, bool applyWet,
ref byte totalMetaFlags)
ref byte totalMetaFlags, bool respectManual)
{
if (applyHat && (totalMetaFlags & 0x01) == 0)
{
if (!respectManual || state[ActorState.MetaFlag.HatState] is not StateChanged.Source.Manual)
_state.ChangeHatState(state, design.IsHatVisible(), StateChanged.Source.Fixed);
totalMetaFlags |= 0x01;
}
if (applyVisor && (totalMetaFlags & 0x02) == 0)
{
if (!respectManual || state[ActorState.MetaFlag.VisorState] is not StateChanged.Source.Manual)
_state.ChangeVisorState(state, design.IsVisorToggled(), StateChanged.Source.Fixed);
totalMetaFlags |= 0x02;
}
if (applyWeapon && (totalMetaFlags & 0x04) == 0)
{
if (!respectManual || state[ActorState.MetaFlag.WeaponState] is not StateChanged.Source.Manual)
_state.ChangeWeaponState(state, design.IsWeaponVisible(), StateChanged.Source.Fixed);
totalMetaFlags |= 0x04;
}
if (applyWet && (totalMetaFlags & 0x08) == 0)
{
if (!respectManual || state[ActorState.MetaFlag.Wetness] is not StateChanged.Source.Manual)
_state.ChangeWetness(state, design.IsWet(), StateChanged.Source.Fixed);
totalMetaFlags |= 0x08;
}

View file

@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Utility;
using Glamourer.Designs;
@ -66,7 +67,32 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
var newSet = new AutoDesignSet(name, identifier.CreatePermanent()) { Enabled = false };
_data.Add(newSet);
Save();
Glamourer.Log.Debug($"Created new design set for {identifier.Incognito(null)}.");
Glamourer.Log.Debug($"Created new design set for {newSet.Identifier.Incognito(null)}.");
_event.Invoke(AutomationChanged.Type.AddedSet, newSet, (_data.Count - 1, name));
}
public void DuplicateDesignSet(AutoDesignSet set)
{
var name = set.Name;
var match = Regex.Match(name, @"\(Duplicate( (?<Number>\d+))?\)$",
RegexOptions.Compiled | RegexOptions.NonBacktracking | RegexOptions.ExplicitCapture);
if (match.Success)
{
var number = match.Groups["Number"];
var replacement = number.Success ? $"(Duplicate {int.Parse(number.Value) + 1})" : "(Duplicate 2)";
name = name.Replace(match.Value, replacement);
}
else
{
name += " (Duplicate)";
}
var newSet = new AutoDesignSet(name, set.Identifier) { Enabled = false };
newSet.Designs.AddRange(set.Designs.Select(d => d.Clone()));
_data.Add(newSet);
Save();
Glamourer.Log.Debug(
$"Duplicated new design set for {newSet.Identifier.Incognito(null)} with {newSet.Designs.Count} auto designs from existing set.");
_event.Invoke(AutomationChanged.Type.AddedSet, newSet, (_data.Count - 1, name));
}
@ -82,6 +108,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
_enabled.Remove(set.Identifier);
}
_data.RemoveAt(whichSet);
Save();
Glamourer.Log.Debug($"Deleted design set {whichSet + 1}.");
_event.Invoke(AutomationChanged.Type.DeletedSet, set, whichSet);
@ -156,6 +183,10 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
oldEnabled.Enabled = false;
_enabled.Add(set.Identifier, set);
}
else
{
_enabled.Remove(set.Identifier, out oldEnabled);
}
Save();
Glamourer.Log.Debug($"Changed enabled state of design set {whichSet + 1} to {value}.");

View file

@ -14,6 +14,8 @@ public enum ColorId
FolderLine,
EnabledAutoSet,
DisabledAutoSet,
AutomationActorAvailable,
AutomationActorUnavailable,
}
public static class Colors
@ -32,6 +34,8 @@ public static class Colors
ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ),
ColorId.EnabledAutoSet => (0xFFA0F0A0, "Enabled Automation Set", "An automation set that is currently enabled. Only one set can be enabled for each identifier at once." ),
ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ),
ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ),
ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ),
_ => (0x00000000, string.Empty, string.Empty ),
// @formatter:on
};

View file

@ -5,9 +5,12 @@ using System.Numerics;
using Dalamud.Interface;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Interop;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.AutomationTab;
@ -16,10 +19,20 @@ public class SetPanel
private readonly AutoDesignManager _manager;
private readonly SetSelector _selector;
public SetPanel(SetSelector selector, AutoDesignManager manager)
private readonly DesignCombo _designCombo;
private readonly JobGroupCombo _jobGroupCombo;
private string? _tempName;
private int _dragIndex = -1;
private Action? _endAction;
public SetPanel(SetSelector selector, AutoDesignManager manager, DesignManager designs, JobService jobs)
{
_selector = selector;
_manager = manager;
_designCombo = new DesignCombo(_manager, designs);
_jobGroupCombo = new JobGroupCombo(manager, jobs);
}
private AutoDesignSet Selection
@ -43,8 +56,6 @@ public class SetPanel
ImGuiUtil.DrawTextButton(Selection.Name, -Vector2.UnitX, buttonColor);
}
private string? _tempName;
private void DrawPanel()
{
using var child = ImRaii.Child("##SetPanel", -Vector2.One, true);
@ -66,47 +77,84 @@ public class SetPanel
if (ImGui.Checkbox("Enabled", ref enabled))
_manager.SetState(_selector.SelectionIndex, enabled);
using var table = ImRaii.Table("SetTable", 4, ImGuiTableFlags.RowBg);
DrawDesignTable();
}
private void DrawDesignTable()
{
using var table = ImRaii.Table("SetTable", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY);
if (!table)
return;
ImGui.TableSetupColumn("##Index", ImGuiTableColumnFlags.WidthFixed, 40 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("##del", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
ImGui.TableSetupColumn("##Index", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed, 5 * ImGui.GetFrameHeight() + 4 * 2 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("Job Restrictions", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableHeadersRow();
foreach (var (design, idx) in Selection.Designs.WithIndex())
{
ImGuiUtil.DrawTableColumn($"#{idx:D2}");
using var id = ImRaii.PushId(idx);
ImGui.TableNextColumn();
DrawDesignCombo(Selection, design, idx);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
"Remove this design from the set.", false, true))
_endAction = () => _manager.DeleteDesign(Selection, idx);
ImGui.TableNextColumn();
ImGui.Selectable($"#{idx + 1:D2}");
DrawDragDrop(Selection, idx);
ImGui.TableNextColumn();
DrawJobGroupCombo(Selection, design, idx);
}
_designCombo.Draw(Selection, design, idx);
DrawDragDrop(Selection, idx);
ImGui.TableNextColumn();
DrawApplicationTypeBoxes(Selection, design, idx);
ImGui.TableNextColumn();
_jobGroupCombo.Draw(Selection, design, idx);
}
private void DrawDesignCombo(AutoDesignSet set, AutoDesign design, int autoDesignIndex)
ImGui.TableNextColumn();
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("New");
ImGui.TableNextColumn();
_designCombo.Draw(Selection, null, -1);
ImGui.TableNextColumn();
ImGui.TableNextColumn();
_endAction?.Invoke();
_endAction = null;
}
private void DrawDragDrop(AutoDesignSet set, int index)
{
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
using var combo = ImRaii.Combo("##design", design.Design.Name);
if (!combo)
return;
const string dragDropLabel = "DesignDragDrop";
using (var target = ImRaii.DragDropTarget())
{
if (target.Success && ImGuiUtil.IsDropping(dragDropLabel))
{
if (_dragIndex >= 0)
{
var idx = _dragIndex;
_endAction = () => _manager.MoveDesign(set, idx, index);
}
private void DrawJobGroupCombo(AutoDesignSet set, AutoDesign design, int autoDesignIndex)
_dragIndex = -1;
}
}
using (var source = ImRaii.DragDropSource())
{
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
using var combo = ImRaii.Combo("##JobGroups", design.Jobs.Name);
if (!combo)
return;
if (source.Success && ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0))
{
_dragIndex = index;
ImGui.TextUnformatted($"Moving design #{index + 1:D2}...");
}
}
}
private void DrawApplicationTypeBoxes(AutoDesignSet set, AutoDesign design, int autoDesignIndex)
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, 2 * ImGuiHelpers.GlobalScale);
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale));
var newType = design.ApplicationType;
foreach (var (type, description) in Types)
{
@ -114,7 +162,10 @@ public class SetPanel
if (ImGui.Checkbox($"##{(byte)type}", ref value))
newType = value ? newType | type : newType & ~type;
ImGuiUtil.HoverTooltip(description);
ImGui.SameLine();
}
_manager.ChangeApplicationType(set, autoDesignIndex, newType);
}
private static readonly IReadOnlyList<(AutoDesign.Type, string)> Types = new[]
@ -126,4 +177,64 @@ public class SetPanel
(AutoDesign.Type.Stains, "Apply all dye changes that are enabled in this design."),
(AutoDesign.Type.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."),
};
private sealed class JobGroupCombo : FilterComboCache<JobGroup>
{
private readonly AutoDesignManager _manager;
private readonly JobService _jobs;
public JobGroupCombo(AutoDesignManager manager, JobService jobs)
: base(() => jobs.JobGroups.Values.ToList())
{
_manager = manager;
_jobs = jobs;
}
public void Draw(AutoDesignSet set, AutoDesign design, int autoDesignIndex)
{
CurrentSelection = design.Jobs;
CurrentSelectionIdx = _jobs.JobGroups.Values.IndexOf(j => j.Id == design.Jobs.Id);
if (Draw("##JobGroups", design.Jobs.Name,
"Select for which job groups this design should be applied.\nControl + Right-Click to set to all classes.",
ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight())
&& CurrentSelectionIdx >= 0)
_manager.ChangeJobCondition(set, autoDesignIndex, CurrentSelection);
else if (ImGui.GetIO().KeyCtrl && ImGui.IsItemClicked(ImGuiMouseButton.Right))
_manager.ChangeJobCondition(set, autoDesignIndex, _jobs.JobGroups[1]);
}
protected override string ToString(JobGroup obj)
=> obj.Name;
}
private sealed class DesignCombo : FilterComboCache<Design>
{
private readonly AutoDesignManager _manager;
private readonly DesignManager _designs;
public DesignCombo(AutoDesignManager manager, DesignManager designs)
: base(() => designs.Designs.OrderBy(d => d.Name).ToList())
{
_designs = designs;
_manager = manager;
}
public void Draw(AutoDesignSet set, AutoDesign? design, int autoDesignIndex)
{
CurrentSelection = design?.Design ?? (Items.Count > 0 ? Items[0] : null);
CurrentSelectionIdx = design?.Design.Index ?? (Items.Count > 0 ? 0 : -1);
if (Draw("##design", CurrentSelection?.Name.Text ?? string.Empty, string.Empty, 220 * ImGuiHelpers.GlobalScale,
ImGui.GetTextLineHeight())
&& CurrentSelection != null)
{
if (autoDesignIndex >= 0)
_manager.ChangeDesign(set, autoDesignIndex, CurrentSelection);
else
_manager.AddDesign(set, CurrentSelection);
}
}
protected override string ToString(Design obj)
=> obj.Name.Text;
}
}

View file

@ -1,31 +1,44 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Interface;
using Glamourer.Automation;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using Penumbra.String;
using Vector2 = FFXIVClientStructs.FFXIV.Common.Math.Vector2;
namespace Glamourer.Gui.Tabs.AutomationTab;
public class SetSelector : IDisposable
{
private readonly Configuration _config;
private readonly AutoDesignManager _manager;
private readonly AutomationChanged _event;
public AutoDesignSet? Selection { get; private set; }
public int SelectionIndex = -1;
private bool IncognitoMode;
private readonly ActorService _actors;
private readonly ObjectManager _objects;
private readonly List<AutoDesignSet> _list = new();
public SetSelector(AutoDesignManager manager, AutomationChanged @event)
public AutoDesignSet? Selection { get; private set; }
public int SelectionIndex { get; private set; } = -1;
private bool IncognitoMode;
private int _dragIndex = -1;
private Action? _endAction;
public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorService actors, ObjectManager objects)
{
_manager = manager;
_event = @event;
_config = config;
_actors = actors;
_objects = objects;
_event.Subscribe(OnAutomationChanged, AutomationChanged.Priority.SetSelector);
}
@ -41,13 +54,17 @@ public class SetSelector : IDisposable
case AutomationChanged.Type.DeletedSet:
if (set == Selection)
{
Selection = null;
SelectionIndex = -1;
SelectionIndex = _manager.Count == 0 ? -1 : SelectionIndex == 0 ? 0 : SelectionIndex - 1;
Selection = SelectionIndex >= 0 ? _manager[SelectionIndex] : null;
}
_dirty = true;
break;
case AutomationChanged.Type.AddedSet:
SelectionIndex = (((int, string))data!).Item1;
Selection = set!;
_dirty = true;
break;
case AutomationChanged.Type.RenamedSet:
case AutomationChanged.Type.MovedSet:
case AutomationChanged.Type.ChangeIdentifier:
@ -109,6 +126,7 @@ public class SetSelector : IDisposable
_dirty = true;
ImGui.SameLine();
var f = _enabledFilter;
if (ImGui.CheckboxFlags("##enabledFilter", ref f, 3))
{
_enabledFilter = _enabledFilter switch
@ -120,6 +138,11 @@ public class SetSelector : IDisposable
_dirty = true;
}
var pos = ImGui.GetItemRectMin();
pos.X -= ImGuiHelpers.GlobalScale;
ImGui.GetWindowDrawList().AddLine(pos, pos with { Y = ImGui.GetItemRectMax().Y }, ImGui.GetColorU32(ImGuiCol.Border),
ImGuiHelpers.GlobalScale);
ImGuiUtil.HoverTooltip("Filter to show only enabled or disabled sets.");
DrawSelector();
@ -135,7 +158,10 @@ public class SetSelector : IDisposable
UpdateList();
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
_selectableSize = new Vector2(0, 2 * ImGui.GetTextLineHeight() + ImGui.GetStyle().ItemSpacing.Y);
_objects.Update();
ImGuiClip.ClippedDraw(_list, DrawSetSelectable, _selectableSize.Y + 2 * ImGui.GetStyle().ItemSpacing.Y);
_endAction?.Invoke();
_endAction = null;
}
private void DrawSetSelectable(AutoDesignSet set, int index)
@ -150,22 +176,84 @@ public class SetSelector : IDisposable
}
}
DrawDragDrop(set, index);
var text = set.Identifier.ToString();
if (IncognitoMode)
text = set.Identifier.Incognito(text);
var textSize = ImGui.CalcTextSize(text);
var textColor = _objects.ContainsKey(set.Identifier) ? ColorId.AutomationActorAvailable : ColorId.AutomationActorUnavailable;
ImGui.SetCursorPos(new Vector2(ImGui.GetContentRegionAvail().X - textSize.X,
ImGui.GetCursorPosY() - ImGui.GetTextLineHeightWithSpacing()));
ImGui.TextUnformatted(text);
ImGuiUtil.TextColored(textColor.Value(), text);
}
private void DrawSelectionButtons()
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
var buttonWidth = new Vector2(_width / 1, 0);
// TODO
ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
, "Select the local player character.", true, true);
var buttonWidth = new Vector2(_width / 3, 0);
NewSetButton(buttonWidth);
ImGui.SameLine();
DuplicateSetButton(buttonWidth);
ImGui.SameLine();
DeleteSetButton(buttonWidth);
}
private void NewSetButton(Vector2 size)
{
var id = _actors.AwaitedService.GetCurrentPlayer();
if (!id.IsValid)
id = _actors.AwaitedService.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size,
$"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true))
_manager.AddDesignSet("New Design", id);
}
private void DuplicateSetButton(Vector2 size)
{
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clone.ToIconString(), size, "Duplicate the current Automatic Design Set.",
Selection == null, true))
_manager.DuplicateDesignSet(Selection!);
}
private void DeleteSetButton(Vector2 size)
{
var keyValid = _config.DeleteDesignModifier.IsActive();
var (disabled, tt) = HasSelection
? keyValid
? (false, "Delete the currently selected design set.")
: (true, $"Delete the currently selected design set.\n{_config.DeleteDesignModifier.ToString()}")
: (true, "No Automatic Design Set selected.");
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, disabled, true))
_manager.DeleteDesignSet(SelectionIndex);
}
private void DrawDragDrop(AutoDesignSet set, int index)
{
const string dragDropLabel = "DesignSetDragDrop";
using (var target = ImRaii.DragDropTarget())
{
if (target.Success && ImGuiUtil.IsDropping(dragDropLabel))
{
if (_dragIndex >= 0)
{
var idx = _dragIndex;
_endAction = () => _manager.MoveSet(idx, index);
}
_dragIndex = -1;
}
}
using (var source = ImRaii.DragDropSource())
{
if (source.Success && ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0))
{
_dragIndex = index;
ImGui.TextUnformatted($"Moving design set {set.Name} from position {index + 1}...");
}
}
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
@ -133,8 +134,11 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
public int Count
=> Identifiers.Count;
/// <summary> Also (inefficiently) handles All Worlds players. </summary>
public bool ContainsKey(ActorIdentifier key)
=> Identifiers.ContainsKey(key);
=> Identifiers.ContainsKey(key)
|| key.HomeWorld == ushort.MaxValue
&& Identifiers.Keys.FirstOrDefault(i => i.Type is IdentifierType.Player && i.PlayerName == key.PlayerName).IsValid;
public bool TryGetValue(ActorIdentifier key, out ActorData value)
=> Identifiers.TryGetValue(key, out value);

View file

@ -67,6 +67,9 @@ public readonly unsafe struct Actor : IEquatable<Actor>
public Model Model
=> Valid ? AsObject->DrawObject : null;
public byte Job
=> IsCharacter ? AsCharacter->CharacterData.ClassJob : (byte)0;
public static implicit operator bool(Actor actor)
=> actor.Address != nint.Zero;

View file

@ -29,6 +29,9 @@ public class ActorState
/// <summary> This should be the desired state of the draw object. </summary>
public DesignData ModelData;
/// <summary> The last seen job. </summary>
public byte LastJob;
/// <summary> This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. </summary>
private readonly StateChanged.Source[] _sources = Enumerable
.Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5).ToArray();

View file

@ -68,7 +68,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
=> GetOrCreate(actor.GetIdentifier(_actors.AwaitedService), actor, out state);
/// <summary> Try to obtain or create a new state for an existing actor. Returns false if no state could be created. </summary>
public bool GetOrCreate(ActorIdentifier identifier, Actor actor, [NotNullWhen(true)] out ActorState? state)
public unsafe bool GetOrCreate(ActorIdentifier identifier, Actor actor, [NotNullWhen(true)] out ActorState? state)
{
if (TryGetValue(identifier, out state))
return true;
@ -81,6 +81,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
{
ModelData = FromActor(actor, true),
BaseData = FromActor(actor, false),
LastJob = (byte) (actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0),
};
// state.Identifier is owned.
_states.Add(state.Identifier, state);

View file

@ -1,318 +0,0 @@
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using System;
using Glamourer.Api;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.Api.Enums;
namespace Glamourer;
public partial class Glamourer
{
public class GlamourerIpc : IDisposable
{
public const int CurrentApiVersion = 0;
public const string LabelProviderApiVersion = "Glamourer.ApiVersion";
public const string LabelProviderGetAllCustomization = "Glamourer.GetAllCustomization";
public const string LabelProviderGetAllCustomizationFromCharacter = "Glamourer.GetAllCustomizationFromCharacter";
public const string LabelProviderApplyAll = "Glamourer.ApplyAll";
public const string LabelProviderApplyAllToCharacter = "Glamourer.ApplyAllToCharacter";
public const string LabelProviderApplyOnlyEquipment = "Glamourer.ApplyOnlyEquipment";
public const string LabelProviderApplyOnlyEquipmentToCharacter = "Glamourer.ApplyOnlyEquipmentToCharacter";
public const string LabelProviderApplyOnlyCustomization = "Glamourer.ApplyOnlyCustomization";
public const string LabelProviderApplyOnlyCustomizationToCharacter = "Glamourer.ApplyOnlyCustomizationToCharacter";
public const string LabelProviderRevert = "Glamourer.Revert";
public const string LabelProviderRevertCharacter = "Glamourer.RevertCharacter";
private readonly ObjectTable _objectTable;
private readonly DalamudPluginInterface _pluginInterface;
private readonly ActiveDesign.Manager _stateManager;
private readonly ItemManager _items;
private readonly PenumbraAttach _penumbra;
private readonly ActorService _actors;
internal ICallGateProvider<string, string?>? ProviderGetAllCustomization;
internal ICallGateProvider<Character?, string?>? ProviderGetAllCustomizationFromCharacter;
internal ICallGateProvider<string, string, object>? ProviderApplyAll;
internal ICallGateProvider<string, Character?, object>? ProviderApplyAllToCharacter;
internal ICallGateProvider<string, string, object>? ProviderApplyOnlyCustomization;
internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyCustomizationToCharacter;
internal ICallGateProvider<string, string, object>? ProviderApplyOnlyEquipment;
internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyEquipmentToCharacter;
internal ICallGateProvider<string, object>? ProviderRevert;
internal ICallGateProvider<Character?, object>? ProviderRevertCharacter;
internal ICallGateProvider<int>? ProviderGetApiVersion;
public GlamourerIpc(ObjectTable objectTable, DalamudPluginInterface pluginInterface, ActiveDesign.Manager stateManager,
ItemManager items, PenumbraAttach penumbra, ActorService actors)
{
_objectTable = objectTable;
_pluginInterface = pluginInterface;
_stateManager = stateManager;
_items = items;
_penumbra = penumbra;
_actors = actors;
InitializeProviders();
}
public void Dispose()
=> DisposeProviders();
private void DisposeProviders()
{
ProviderGetAllCustomization?.UnregisterFunc();
ProviderGetAllCustomizationFromCharacter?.UnregisterFunc();
ProviderApplyAll?.UnregisterAction();
ProviderApplyAllToCharacter?.UnregisterAction();
ProviderApplyOnlyCustomization?.UnregisterAction();
ProviderApplyOnlyCustomizationToCharacter?.UnregisterAction();
ProviderApplyOnlyEquipment?.UnregisterAction();
ProviderApplyOnlyEquipmentToCharacter?.UnregisterAction();
ProviderRevert?.UnregisterAction();
ProviderRevertCharacter?.UnregisterAction();
ProviderGetApiVersion?.UnregisterFunc();
}
private void InitializeProviders()
{
try
{
ProviderGetApiVersion = _pluginInterface.GetIpcProvider<int>(LabelProviderApiVersion);
ProviderGetApiVersion.RegisterFunc(GetApiVersion);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}.");
}
try
{
ProviderGetAllCustomization = _pluginInterface.GetIpcProvider<string, string?>(LabelProviderGetAllCustomization);
ProviderGetAllCustomization.RegisterFunc(GetAllCustomization);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
}
try
{
ProviderGetAllCustomizationFromCharacter =
_pluginInterface.GetIpcProvider<Character?, string?>(LabelProviderGetAllCustomizationFromCharacter);
ProviderGetAllCustomizationFromCharacter.RegisterFunc(GetAllCustomization);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderGetAllCustomizationFromCharacter}.");
}
try
{
ProviderApplyAll =
_pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyAll);
ProviderApplyAll.RegisterAction(ApplyAll);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}.");
}
try
{
ProviderApplyAllToCharacter =
_pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyAllToCharacter);
ProviderApplyAllToCharacter.RegisterAction(ApplyAll);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}.");
}
try
{
ProviderApplyOnlyCustomization =
_pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyOnlyCustomization);
ProviderApplyOnlyCustomization.RegisterAction(ApplyOnlyCustomization);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}.");
}
try
{
ProviderApplyOnlyCustomizationToCharacter =
_pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyOnlyCustomizationToCharacter);
ProviderApplyOnlyCustomizationToCharacter.RegisterAction(ApplyOnlyCustomization);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}.");
}
try
{
ProviderApplyOnlyEquipment =
_pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyOnlyEquipment);
ProviderApplyOnlyEquipment.RegisterAction(ApplyOnlyEquipment);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
}
try
{
ProviderApplyOnlyEquipmentToCharacter =
_pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyOnlyEquipmentToCharacter);
ProviderApplyOnlyEquipmentToCharacter.RegisterAction(ApplyOnlyEquipment);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
}
try
{
ProviderRevert =
_pluginInterface.GetIpcProvider<string, object>(LabelProviderRevert);
ProviderRevert.RegisterAction(Revert);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}.");
}
try
{
ProviderRevertCharacter =
_pluginInterface.GetIpcProvider<Character?, object>(LabelProviderRevertCharacter);
ProviderRevertCharacter.RegisterAction(Revert);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}.");
}
}
private static int GetApiVersion()
=> CurrentApiVersion;
private void ApplyAll(string customization, string characterName)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.Name.ToString() == characterName)
{
ApplyAll(customization, gameObject as Character);
return;
}
}
}
private void ApplyAll(string customization, Character? character)
{
if (character == null)
return;
var design = Design.CreateTemporaryFromBase64(_items, customization, true, true);
var active = _stateManager.GetOrCreateSave(character.Address);
_stateManager.ApplyDesign(active, design, false);
}
private void ApplyOnlyCustomization(string customization, string characterName)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.Name.ToString() == characterName)
{
ApplyOnlyCustomization(customization, gameObject as Character);
return;
}
}
}
private void ApplyOnlyCustomization(string customization, Character? character)
{
if (character == null)
return;
var design = Design.CreateTemporaryFromBase64(_items, customization, true, false);
var active = _stateManager.GetOrCreateSave(character.Address);
_stateManager.ApplyDesign(active, design, false);
}
private void ApplyOnlyEquipment(string customization, string characterName)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.Name.ToString() != characterName)
continue;
ApplyOnlyEquipment(customization, gameObject as Character);
return;
}
}
private void ApplyOnlyEquipment(string customization, Character? character)
{
if (character == null)
return;
var design = Design.CreateTemporaryFromBase64(_items, customization, false, true);
var active = _stateManager.GetOrCreateSave(character.Address);
_stateManager.ApplyDesign(active, design, false);
}
private void Revert(string characterName)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.Name.ToString() != characterName)
continue;
Revert(gameObject as Character);
}
}
private void Revert(Character? character)
{
if (character == null)
return;
var ident = _actors.AwaitedService.FromObject(character, true, false, false);
_stateManager.DeleteSave(ident);
_penumbra.RedrawObject(character.Address, RedrawType.Redraw);
}
private string? GetAllCustomization(Character? character)
{
if (character == null)
return null;
var ident = _actors.AwaitedService.FromObject(character, true, false, false);
if (!_stateManager.TryGetValue(ident, out var design))
design = new ActiveDesign(_items, ident, character.Address);
return design.CreateOldBase64();
}
private string? GetAllCustomization(string characterName)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.Name.ToString() == characterName)
return GetAllCustomization(gameObject as Character);
}
return null;
}
}
}

View file

@ -1,207 +0,0 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
using Dalamud.Plugin;
using Glamourer.Interop;
using Penumbra.Api;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
namespace Glamourer.Api;
public unsafe class PenumbraAttach : IDisposable
{
public const int RequiredPenumbraBreakingVersion = 4;
public const int RequiredPenumbraFeatureVersion = 15;
private readonly DalamudPluginInterface _pluginInterface;
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
private FuncSubscriber<int, int> _cutsceneParent;
private readonly EventSubscriber _initializedEvent;
private readonly EventSubscriber _disposedEvent;
public bool Available { get; private set; }
public PenumbraAttach(DalamudPluginInterface pi)
{
_pluginInterface = pi;
_initializedEvent = Ipc.Initialized.Subscriber(pi, Reattach);
_disposedEvent = Ipc.Disposed.Subscriber(pi, Unattach);
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(pi);
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(pi);
_createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(pi);
_creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(pi);
Reattach();
}
public event Action<MouseButton, ChangedItemType, uint> Click
{
add => _clickSubscriber.Event += value;
remove => _clickSubscriber.Event -= value;
}
public event Action<ChangedItemType, uint> Tooltip
{
add => _tooltipSubscriber.Event += value;
remove => _tooltipSubscriber.Event -= value;
}
public event Action<nint, string, nint, nint, nint> CreatingCharacterBase
{
add => _creatingCharacterBase.Event += value;
remove => _creatingCharacterBase.Event -= value;
}
public event Action<nint, string, nint> CreatedCharacterBase
{
add => _createdCharacterBase.Event += value;
remove => _createdCharacterBase.Event -= value;
}
public Actor GameObjectFromDrawObject(IntPtr drawObject)
=> Available ? _drawObjectInfo.Invoke(drawObject).Item1 : Actor.Null;
public int CutsceneParent(int idx)
=> Available ? _cutsceneParent.Invoke(idx) : -1;
public void RedrawObject(Actor actor, RedrawType settings)
{
if (!actor || !Available)
return;
try
{
_redrawSubscriber.Invoke(actor.Index, settings);
}
catch (Exception e)
{
PluginLog.Debug($"Failure redrawing object:\n{e}");
}
}
public void Reattach()
{
try
{
Unattach();
var (breaking, feature) = Ipc.ApiVersions.Subscriber(_pluginInterface).Invoke();
if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion)
throw new Exception(
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
_tooltipSubscriber.Enable();
_clickSubscriber.Enable();
_creatingCharacterBase.Enable();
_createdCharacterBase.Enable();
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
Available = true;
Glamourer.Log.Debug("Glamourer attached to Penumbra.");
}
catch (Exception e)
{
Glamourer.Log.Debug($"Could not attach to Penumbra:\n{e}");
}
}
public void Unattach()
{
_tooltipSubscriber.Disable();
_clickSubscriber.Disable();
_creatingCharacterBase.Disable();
_createdCharacterBase.Disable();
if (Available)
{
Available = false;
Glamourer.Log.Debug("Glamourer detached from Penumbra.");
}
}
public void Dispose()
{
Unattach();
_tooltipSubscriber.Dispose();
_clickSubscriber.Dispose();
_creatingCharacterBase.Dispose();
_createdCharacterBase.Dispose();
_initializedEvent.Dispose();
_disposedEvent.Dispose();
}
//private static void PenumbraTooltip(ChangedItemType type, uint _)
//{
// if (type == ChangedItemType.Item)
// ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
//}
//
//private void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id)
//{
// if (button != MouseButton.Right || type != ChangedItemType.Item)
// return;
//
// var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(Dalamud.GameData, id)!;
// var writeItem = new Item2(item, string.Empty);
//
// UpdateItem(_objects.GPosePlayer, writeItem);
// UpdateItem(_objects.Player, writeItem);
//}
//private static void UpdateItem(Actor actor, Item2 item2)
//{
// if (!actor || !actor.DrawObject)
// return;
//
// switch (item2.EquippableTo)
// {
// case EquipSlot.MainHand:
// {
// var off = item2.HasSubModel
// ? new CharacterWeapon(item2.SubModel.id, item2.SubModel.type, item2.SubModel.variant, actor.DrawObject.OffHand.Stain)
// : item2.IsBothHand
// ? CharacterWeapon.Empty
// : actor.OffHand;
// var main = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant,
// actor.DrawObject.MainHand.Stain);
// Glamourer.RedrawManager.LoadWeapon(actor, main, off);
// return;
// }
// case EquipSlot.OffHand:
// {
// var off = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant,
// actor.DrawObject.OffHand.Stain);
// var main = actor.MainHand;
// Glamourer.RedrawManager.LoadWeapon(actor, main, off);
// return;
// }
// default:
// {
// var current = actor.DrawObject.Equip[item2.EquippableTo];
// var armor = new CharacterArmor(item2.MainModel.id, (byte)item2.MainModel.variant, current.Stain);
// Glamourer.RedrawManager.UpdateSlot(actor.DrawObject, item2.EquippableTo, armor);
// return;
// }
// }
//}
// Update objects without triggering PlayerWatcher Events,
// then manually redraw using Penumbra.
public void UpdateCharacters(Character character, Character? gPoseOriginalCharacter = null)
{
//RedrawObject(character, RedrawType.Redraw, true);
//
//// Special case for carrying over changes to the gPose player to the regular player, too.
//if (gPoseOriginalCharacter == null)
// return;
//
//newEquip.Write(gPoseOriginalCharacter.Address);
//RedrawObject(gPoseOriginalCharacter, RedrawType.AfterGPose, false);
}
}

View file

@ -1,172 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using Dalamud.Configuration;
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
namespace Glamourer;
public class Configuration : IPluginConfiguration, ISavable
{
public bool UseRestrictedGearProtection = true;
public int Version { get; set; } = 2;
public Dictionary<ColorId, uint> Colors { get; set; }
= Enum.GetValues<ColorId>().ToDictionary(c => c, c => c.Data().DefaultColor);
[JsonIgnore]
private readonly SaveService _saveService;
public Configuration(SaveService saveService, ConfigMigrationService migrator)
{
_saveService = saveService;
Load(migrator);
}
public void Save()
=> _saveService.QueueSave(this);
public void Load(ConfigMigrationService migrator)
{
static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs)
{
Glamourer.Log.Error(
$"Error parsing Configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}");
errorArgs.ErrorContext.Handled = true;
}
if (!File.Exists(_saveService.FileNames.ConfigFile))
return;
if (File.Exists(_saveService.FileNames.ConfigFile))
try
{
var text = File.ReadAllText(_saveService.FileNames.ConfigFile);
JsonConvert.PopulateObject(text, this, new JsonSerializerSettings
{
Error = HandleDeserializationError,
});
}
catch (Exception ex)
{
Glamourer.ChatService.NotificationMessage(ex,
"Error reading Configuration, reverting to default.\nYou may be able to restore your configuration using the rolling backups in the XIVLauncher/backups/Glamourer directory.",
"Error reading Configuration", "Error", NotificationType.Error);
}
migrator.Migrate(this);
}
public string ToFilename(FilenameService fileNames)
=> fileNames.ConfigFile;
public void Save(StreamWriter writer)
{
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
serializer.Serialize(jWriter, this);
}
}
public class ConfigMigrationService
{
private readonly SaveService _saveService;
private Configuration _config = null!;
private JObject _data = null!;
public ConfigMigrationService(SaveService saveService)
=> _saveService = saveService;
public void Migrate(Configuration config)
{
_config = config;
_data = JObject.Parse(File.ReadAllText(_saveService.FileNames.ConfigFile));
MigrateV1To2();
}
private void MigrateV1To2()
{
if (_config.Version > 1)
return;
_config.Version = 2;
}
}
public class ConfigurationOld : IPluginConfiguration, ISavable
{
[JsonIgnore]
private readonly SaveService _saveService;
public class FixedDesign
{
public string Name = string.Empty;
public string Path = string.Empty;
public uint JobGroups;
public bool Enabled;
}
public int Version { get; set; } = 1;
public const uint DefaultCustomizationColor = 0xFFC000C0;
public const uint DefaultStateColor = 0xFF00C0C0;
public const uint DefaultEquipmentColor = 0xFF00C000;
public bool UseRestrictedGearProtection { get; set; } = true;
public bool FoldersFirst { get; set; } = false;
public bool ColorDesigns { get; set; } = true;
public bool ShowLocks { get; set; } = true;
public bool ApplyFixedDesigns { get; set; } = true;
public uint CustomizationColor { get; set; } = DefaultCustomizationColor;
public uint StateColor { get; set; } = DefaultStateColor;
public uint EquipmentColor { get; set; } = DefaultEquipmentColor;
public List<FixedDesign> FixedDesigns { get; set; } = new();
public void Save()
=> _saveService.QueueSave(this);
public ConfigurationOld(SaveService saveService)
{
_saveService = saveService;
Load();
}
public void Load()
{
static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs)
{
Glamourer.Log.Error(
$"Error parsing Configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}");
errorArgs.ErrorContext.Handled = true;
}
if (!File.Exists(_saveService.FileNames.ConfigFile))
return;
var text = File.ReadAllText(_saveService.FileNames.ConfigFile);
JsonConvert.PopulateObject(text, this, new JsonSerializerSettings
{
Error = HandleDeserializationError,
});
}
public string ToFilename(FilenameService fileNames)
=> fileNames.ConfigFile;
public void Save(StreamWriter writer)
{
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
serializer.Serialize(jWriter, this);
}
}

View file

@ -1,49 +0,0 @@
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.IoC;
using Dalamud.Plugin;
using Microsoft.Extensions.DependencyInjection;
namespace Glamourer;
public class DalamudServices
{
public DalamudServices(DalamudPluginInterface pi)
{
pi.Inject(this);
}
public void AddServices(IServiceCollection services)
{
services.AddSingleton(PluginInterface);
services.AddSingleton(Commands);
services.AddSingleton(GameData);
services.AddSingleton(ClientState);
services.AddSingleton(GameGui);
services.AddSingleton(Chat);
services.AddSingleton(Framework);
services.AddSingleton(Targets);
services.AddSingleton(Objects);
services.AddSingleton(KeyState);
services.AddSingleton(this);
services.AddSingleton(PluginInterface.UiBuilder);
}
// @formatter:off
[PluginService][RequiredVersion("1.0")] public DalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public CommandManager Commands { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public DataManager GameData { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public ClientState ClientState { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public GameGui GameGui { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public ChatGui Chat { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public Framework Framework { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public TargetManager Targets { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public ObjectTable Objects { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public KeyState KeyState { get; private set; } = null!;
// @formatter:on
}

View file

@ -1,320 +0,0 @@
using System;
using System.IO;
using System.Linq;
using Glamourer.Customization;
using Glamourer.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Designs;
public partial class Design : DesignData, ISavable
{
public const int FileVersion = 1;
public Guid Identifier { get; internal init; }
public DateTimeOffset CreationDate { get; internal init; }
public LowerString Name { get; internal set; } = LowerString.Empty;
public string Description { get; internal set; } = string.Empty;
public string[] Tags { get; internal set; } = Array.Empty<string>();
public int Index { get; internal set; }
public EquipFlag ApplyEquip { get; internal set; }
public CustomizeFlag ApplyCustomize { get; internal set; }
public QuadBool Wetness { get; internal set; } = QuadBool.NullFalse;
public QuadBool Visor { get; internal set; } = QuadBool.NullFalse;
public QuadBool Hat { get; internal set; } = QuadBool.NullFalse;
public QuadBool Weapon { get; internal set; } = QuadBool.NullFalse;
public bool WriteProtected { get; internal set; }
public bool DoApplyEquip(EquipSlot slot)
=> ApplyEquip.HasFlag(slot.ToFlag());
public bool DoApplyStain(EquipSlot slot)
=> ApplyEquip.HasFlag(slot.ToStainFlag());
public bool DoApplyCustomize(CustomizeIndex idx)
=> ApplyCustomize.HasFlag(idx.ToFlag());
internal bool SetApplyEquip(EquipSlot slot, bool value)
{
var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag();
if (newValue == ApplyEquip)
return false;
ApplyEquip = newValue;
return true;
}
internal bool SetApplyStain(EquipSlot slot, bool value)
{
var newValue = value ? ApplyEquip | slot.ToStainFlag() : ApplyEquip & ~slot.ToStainFlag();
if (newValue == ApplyEquip)
return false;
ApplyEquip = newValue;
return true;
}
internal bool SetApplyCustomize(CustomizeIndex idx, bool value)
{
var newValue = value ? ApplyCustomize | idx.ToFlag() : ApplyCustomize & ~idx.ToFlag();
if (newValue == ApplyCustomize)
return false;
ApplyCustomize = newValue;
return true;
}
internal Design(ItemManager items)
: base(items)
{ }
public JObject JsonSerialize()
{
var ret = new JObject
{
[nameof(FileVersion)] = FileVersion,
[nameof(Identifier)] = Identifier,
[nameof(CreationDate)] = CreationDate,
[nameof(Name)] = Name.Text,
[nameof(Description)] = Description,
[nameof(Tags)] = JArray.FromObject(Tags),
[nameof(WriteProtected)] = WriteProtected,
["Equipment"] = SerializeEquipment(),
[nameof(ModelData.Customize)] = SerializeCustomize(),
};
return ret;
}
public JObject SerializeEquipment()
{
static JObject Serialize(uint itemId, StainId stain, bool apply, bool applyStain)
=> new()
{
[nameof(Item.ItemId)] = itemId,
[nameof(Item.Stain)] = stain.Value,
["Apply"] = apply,
["ApplyStain"] = applyStain,
};
var ret = new JObject()
{
[nameof(MainHandId)] =
Serialize(MainHandId, ModelData.MainHand.Stain, DoApplyEquip(EquipSlot.MainHand), DoApplyStain(EquipSlot.MainHand)),
[nameof(OffHandId)] = Serialize(OffHandId, ModelData.OffHand.Stain, DoApplyEquip(EquipSlot.OffHand),
DoApplyStain(EquipSlot.OffHand)),
};
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var armor = Armor(slot);
ret[slot.ToString()] = Serialize(armor.ItemId, armor.Stain, DoApplyEquip(slot), DoApplyStain(slot));
}
ret[nameof(Hat)] = Hat.ToJObject("Show", "Apply");
ret[nameof(Weapon)] = Weapon.ToJObject("Show", "Apply");
ret[nameof(Visor)] = Visor.ToJObject("IsToggled", "Apply");
return ret;
}
public JObject SerializeCustomize()
{
var ret = new JObject()
{
[nameof(ModelId)] = ModelId,
};
var customize = ModelData.Customize;
foreach (var idx in Enum.GetValues<CustomizeIndex>())
{
var data = customize[idx];
ret[idx.ToString()] = new JObject()
{
["Value"] = data.Value,
["Apply"] = true,
};
}
ret[nameof(Wetness)] = Wetness.ToJObject("IsWet", "Apply");
return ret;
}
public static Design LoadDesign(ItemManager items, JObject json, out bool changes)
{
var version = json[nameof(FileVersion)]?.ToObject<int>() ?? 0;
return version switch
{
1 => LoadDesignV1(items, json, out changes),
_ => throw new Exception("The design to be loaded has no valid Version."),
};
}
internal static Design LoadDesignV1(ItemManager items, JObject json, out bool changes)
{
static string[] ParseTags(JObject json)
{
var tags = json["Tags"]?.ToObject<string[]>() ?? Array.Empty<string>();
return tags.OrderBy(t => t).Distinct().ToArray();
}
var design = new Design(items)
{
CreationDate = json["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate"),
Identifier = json["Identifier"]?.ToObject<Guid>() ?? throw new ArgumentNullException("Identifier"),
Name = new LowerString(json["Name"]?.ToObject<string>() ?? throw new ArgumentNullException("Name")),
Description = json["Description"]?.ToObject<string>() ?? string.Empty,
Tags = ParseTags(json),
};
changes = LoadEquip(items, json["Equipment"], design);
changes |= LoadCustomize(json["Customize"], design);
return design;
}
internal static bool LoadEquip(ItemManager items, JToken? equip, Design design)
{
if (equip == null)
return true;
static (uint, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item)
{
var id = item?["ItemId"]?.ToObject<uint>() ?? ItemManager.NothingId(slot);
var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0);
var apply = item?["Apply"]?.ToObject<bool>() ?? false;
var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false;
return (id, stain, apply, applyStain);
}
var changes = false;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]);
changes |= !design.SetArmor(items, slot, id);
changes |= !design.SetStain(slot, stain);
design.SetApplyEquip(slot, apply);
design.SetApplyStain(slot, applyStain);
}
var main = equip["MainHand"];
if (main == null)
{
changes = true;
}
else
{
var id = main["ItemId"]?.ToObject<uint>() ?? items.DefaultSword.RowId;
var stain = (StainId)(main["Stain"]?.ToObject<byte>() ?? 0);
var apply = main["Apply"]?.ToObject<bool>() ?? false;
var applyStain = main["ApplyStain"]?.ToObject<bool>() ?? false;
changes |= !design.SetMainhand(items, id);
changes |= !design.SetStain(EquipSlot.MainHand, stain);
design.SetApplyEquip(EquipSlot.MainHand, apply);
design.SetApplyStain(EquipSlot.MainHand, applyStain);
}
var off = equip["OffHand"];
if (off == null)
{
changes = true;
}
else
{
var id = off["ItemId"]?.ToObject<uint>() ?? ItemManager.NothingId(design.MainhandType.Offhand());
var stain = (StainId)(off["Stain"]?.ToObject<byte>() ?? 0);
var apply = off["Apply"]?.ToObject<bool>() ?? false;
var applyStain = off["ApplyStain"]?.ToObject<bool>() ?? false;
changes |= !design.SetOffhand(items, id);
changes |= !design.SetStain(EquipSlot.OffHand, stain);
design.SetApplyEquip(EquipSlot.OffHand, apply);
design.SetApplyStain(EquipSlot.OffHand, applyStain);
}
design.Hat = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse);
design.Weapon = QuadBool.FromJObject(equip["Weapon"], "Show", "Apply", QuadBool.NullFalse);
design.Visor = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse);
return changes;
}
internal static bool LoadCustomize(JToken? json, Design design)
{
if (json == null)
return true;
ref var customize = ref design.ModelData.Customize;
foreach (var idx in Enum.GetValues<CustomizeIndex>())
{
var tok = json[idx.ToString()];
var data = (CustomizeValue)(tok?["Value"]?.ToObject<byte>() ?? 0);
var apply = tok?["Apply"]?.ToObject<bool>() ?? false;
customize[idx] = data;
design.SetApplyCustomize(idx, apply);
}
design.Wetness = QuadBool.FromJObject(json["Wetness"], "IsWet", "Apply", QuadBool.NullFalse);
return false;
}
public void MigrateBase64(ItemManager items, string base64)
{
var data = DesignBase64Migration.MigrateBase64(base64, out var applyEquip, out var applyCustomize, out var writeProtected, out var wet,
out var hat,
out var visor, out var weapon);
UpdateMainhand(items, data.MainHand);
UpdateOffhand(items, data.OffHand);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
UpdateArmor(items, slot, data.Armor(slot), true);
ModelData.Customize = data.Customize;
ApplyEquip = applyEquip;
ApplyCustomize = applyCustomize;
WriteProtected = writeProtected;
Wetness = wet;
Hat = hat;
Visor = visor;
Weapon = weapon;
}
public static Design CreateTemporaryFromBase64(ItemManager items, string base64, bool customize, bool equip)
{
var ret = new Design(items);
ret.MigrateBase64(items, base64);
if (!customize)
ret.ApplyCustomize = 0;
if (!equip)
ret.ApplyEquip = 0;
ret.Wetness = ret.Wetness.SetEnabled(customize);
ret.Visor = ret.Visor.SetEnabled(equip);
ret.Hat = ret.Hat.SetEnabled(equip);
ret.Weapon = ret.Weapon.SetEnabled(equip);
return ret;
}
// Outdated.
public string CreateOldBase64()
=> DesignBase64Migration.CreateOldBase64(in ModelData, ApplyEquip, ApplyCustomize, Wetness == QuadBool.True, Hat.ForcedValue,
Hat.Enabled,
Visor.ForcedValue, Visor.Enabled, Weapon.ForcedValue, Weapon.Enabled, WriteProtected, 1f);
public string ToFilename(FilenameService fileNames)
=> fileNames.DesignFile(this);
public void Save(StreamWriter writer)
{
using var j = new JsonTextWriter(writer)
{
Formatting = Formatting.Indented,
};
var obj = JsonSerialize();
obj.WriteTo(j);
}
public string LogName(string fileName)
=> Path.GetFileNameWithoutExtension(fileName);
}

View file

@ -1,430 +0,0 @@
using System;
using Glamourer.Customization;
using Glamourer.Services;
using OtterGui.Classes;
using OtterGui;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Designs;
public class DesignData
{
internal ModelData ModelData;
public FullEquipType MainhandType { get; internal set; }
public uint Head = ItemManager.NothingId(EquipSlot.Head);
public uint Body = ItemManager.NothingId(EquipSlot.Body);
public uint Hands = ItemManager.NothingId(EquipSlot.Hands);
public uint Legs = ItemManager.NothingId(EquipSlot.Legs);
public uint Feet = ItemManager.NothingId(EquipSlot.Feet);
public uint Ears = ItemManager.NothingId(EquipSlot.Ears);
public uint Neck = ItemManager.NothingId(EquipSlot.Neck);
public uint Wrists = ItemManager.NothingId(EquipSlot.Wrists);
public uint RFinger = ItemManager.NothingId(EquipSlot.RFinger);
public uint LFinger = ItemManager.NothingId(EquipSlot.RFinger);
public uint MainHandId;
public uint OffHandId;
public string HeadName = ItemManager.Nothing;
public string BodyName = ItemManager.Nothing;
public string HandsName = ItemManager.Nothing;
public string LegsName = ItemManager.Nothing;
public string FeetName = ItemManager.Nothing;
public string EarsName = ItemManager.Nothing;
public string NeckName = ItemManager.Nothing;
public string WristsName = ItemManager.Nothing;
public string RFingerName = ItemManager.Nothing;
public string LFingerName = ItemManager.Nothing;
public string MainhandName;
public string OffhandName;
public DesignData(ItemManager items)
{
MainHandId = items.DefaultSword.RowId;
(_, var set, var type, var variant, MainhandName, MainhandType) = items.Resolve(MainHandId, items.DefaultSword);
ModelData = new ModelData(new CharacterWeapon(set, type, variant, 0));
OffHandId = ItemManager.NothingId(MainhandType.Offhand());
(_, ModelData.OffHand.Set, ModelData.OffHand.Type, ModelData.OffHand.Variant, OffhandName, _) =
items.Resolve(OffHandId, MainhandType);
}
public uint ModelId
=> ModelData.ModelId;
public Item Armor(EquipSlot slot)
{
return slot switch
{
EquipSlot.Head => new Item(HeadName, Head, ModelData.Head),
EquipSlot.Body => new Item(BodyName, Body, ModelData.Body),
EquipSlot.Hands => new Item(HandsName, Hands, ModelData.Hands),
EquipSlot.Legs => new Item(LegsName, Legs, ModelData.Legs),
EquipSlot.Feet => new Item(FeetName, Feet, ModelData.Feet),
EquipSlot.Ears => new Item(EarsName, Ears, ModelData.Ears),
EquipSlot.Neck => new Item(NeckName, Neck, ModelData.Neck),
EquipSlot.Wrists => new Item(WristsName, Wrists, ModelData.Wrists),
EquipSlot.RFinger => new Item(RFingerName, RFinger, ModelData.RFinger),
EquipSlot.LFinger => new Item(LFingerName, LFinger, ModelData.LFinger),
_ => throw new Exception("Invalid equip slot for item."),
};
}
public Weapon WeaponMain
=> new(MainhandName, MainHandId, ModelData.MainHand, MainhandType);
public Weapon WeaponOff
=> Weapon.Offhand(OffhandName, OffHandId, ModelData.OffHand, MainhandType);
public CustomizeValue GetCustomize(CustomizeIndex idx)
=> ModelData.Customize[idx];
internal bool SetCustomize(CustomizeIndex idx, CustomizeValue value)
=> ModelData.Customize.Set(idx, value);
internal bool SetArmor(ItemManager items, EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
{
var (valid, set, variant, name) = items.Resolve(slot, itemId, item);
if (!valid)
return false;
return SetArmor(slot, set, variant, name, itemId);
}
internal bool SetArmor(EquipSlot slot, Item item)
=> SetArmor(slot, item.ModelBase, item.Variant, item.Name, item.ItemId);
internal bool UpdateArmor(ItemManager items, EquipSlot slot, CharacterArmor armor, bool force)
{
var (valid, id, name) = items.Identify(slot, armor.Set, armor.Variant);
if (!valid)
return false;
return SetArmor(slot, armor.Set, armor.Variant, name, id) | SetStain(slot, armor.Stain);
}
internal bool SetMainhand(ItemManager items, uint mainId, Lumina.Excel.GeneratedSheets.Item? main = null)
{
if (mainId == MainHandId)
return false;
var (valid, set, weapon, variant, name, type) = items.Resolve(mainId, main);
if (!valid)
return false;
var fixOffhand = type.Offhand() != MainhandType.Offhand();
MainHandId = mainId;
MainhandName = name;
MainhandType = type;
ModelData.MainHand.Set = set;
ModelData.MainHand.Type = weapon;
ModelData.MainHand.Variant = variant;
if (fixOffhand)
SetOffhand(items, ItemManager.NothingId(type.Offhand()));
return true;
}
internal bool SetOffhand(ItemManager items, uint offId, Lumina.Excel.GeneratedSheets.Item? off = null)
{
if (offId == OffHandId)
return false;
var (valid, set, weapon, variant, name, type) = items.Resolve(offId, MainhandType, off);
if (!valid)
return false;
OffHandId = offId;
OffhandName = name;
ModelData.OffHand.Set = set;
ModelData.OffHand.Type = weapon;
ModelData.OffHand.Variant = variant;
return true;
}
internal bool UpdateMainhand(ItemManager items, CharacterWeapon weapon)
{
if (weapon.Value == ModelData.MainHand.Value)
return false;
var (valid, id, name, type) = items.Identify(EquipSlot.MainHand, weapon.Set, weapon.Type, (byte)weapon.Variant);
if (!valid || id == MainHandId)
return false;
var fixOffhand = type.Offhand() != MainhandType.Offhand();
MainHandId = id;
MainhandName = name;
MainhandType = type;
ModelData.MainHand.Set = weapon.Set;
ModelData.MainHand.Type = weapon.Type;
ModelData.MainHand.Variant = weapon.Variant;
ModelData.MainHand.Stain = weapon.Stain;
if (fixOffhand)
SetOffhand(items, ItemManager.NothingId(type.Offhand()));
return true;
}
internal bool UpdateOffhand(ItemManager items, CharacterWeapon weapon)
{
if (weapon.Value == ModelData.OffHand.Value)
return false;
var (valid, id, name, _) = items.Identify(EquipSlot.OffHand, weapon.Set, weapon.Type, (byte)weapon.Variant, MainhandType);
if (!valid || id == OffHandId)
return false;
OffHandId = id;
OffhandName = name;
ModelData.OffHand.Set = weapon.Set;
ModelData.OffHand.Type = weapon.Type;
ModelData.OffHand.Variant = weapon.Variant;
ModelData.OffHand.Stain = weapon.Stain;
return true;
}
internal bool SetStain(EquipSlot slot, StainId id)
{
return slot switch
{
EquipSlot.MainHand => SetIfDifferent(ref ModelData.MainHand.Stain, id),
EquipSlot.OffHand => SetIfDifferent(ref ModelData.OffHand.Stain, id),
EquipSlot.Head => SetIfDifferent(ref ModelData.Head.Stain, id),
EquipSlot.Body => SetIfDifferent(ref ModelData.Body.Stain, id),
EquipSlot.Hands => SetIfDifferent(ref ModelData.Hands.Stain, id),
EquipSlot.Legs => SetIfDifferent(ref ModelData.Legs.Stain, id),
EquipSlot.Feet => SetIfDifferent(ref ModelData.Feet.Stain, id),
EquipSlot.Ears => SetIfDifferent(ref ModelData.Ears.Stain, id),
EquipSlot.Neck => SetIfDifferent(ref ModelData.Neck.Stain, id),
EquipSlot.Wrists => SetIfDifferent(ref ModelData.Wrists.Stain, id),
EquipSlot.RFinger => SetIfDifferent(ref ModelData.RFinger.Stain, id),
EquipSlot.LFinger => SetIfDifferent(ref ModelData.LFinger.Stain, id),
_ => false,
};
}
internal static bool SetIfDifferent<T>(ref T old, T value) where T : IEquatable<T>
{
if (old.Equals(value))
return false;
old = value;
return true;
}
private bool SetArmor(EquipSlot slot, SetId set, byte variant, string name, uint id)
{
var changes = false;
switch (slot)
{
case EquipSlot.Head:
changes |= SetIfDifferent(ref ModelData.Head.Set, set);
changes |= SetIfDifferent(ref ModelData.Head.Variant, variant);
changes |= HeadName != name;
HeadName = name;
changes |= Head != id;
Head = id;
return changes;
case EquipSlot.Body:
changes |= SetIfDifferent(ref ModelData.Body.Set, set);
changes |= SetIfDifferent(ref ModelData.Body.Variant, variant);
changes |= BodyName != name;
BodyName = name;
changes |= Body != id;
Body = id;
return changes;
case EquipSlot.Hands:
changes |= SetIfDifferent(ref ModelData.Hands.Set, set);
changes |= SetIfDifferent(ref ModelData.Hands.Variant, variant);
changes |= HandsName != name;
HandsName = name;
changes |= Hands != id;
Hands = id;
return changes;
case EquipSlot.Legs:
changes |= SetIfDifferent(ref ModelData.Legs.Set, set);
changes |= SetIfDifferent(ref ModelData.Legs.Variant, variant);
changes |= LegsName != name;
LegsName = name;
changes |= Legs != id;
Legs = id;
return changes;
case EquipSlot.Feet:
changes |= SetIfDifferent(ref ModelData.Feet.Set, set);
changes |= SetIfDifferent(ref ModelData.Feet.Variant, variant);
changes |= FeetName != name;
FeetName = name;
changes |= Feet != id;
Feet = id;
return changes;
case EquipSlot.Ears:
changes |= SetIfDifferent(ref ModelData.Ears.Set, set);
changes |= SetIfDifferent(ref ModelData.Ears.Variant, variant);
changes |= EarsName != name;
EarsName = name;
changes |= Ears != id;
Ears = id;
return changes;
case EquipSlot.Neck:
changes |= SetIfDifferent(ref ModelData.Neck.Set, set);
changes |= SetIfDifferent(ref ModelData.Neck.Variant, variant);
changes |= NeckName != name;
NeckName = name;
changes |= Neck != id;
Neck = id;
return changes;
case EquipSlot.Wrists:
changes |= SetIfDifferent(ref ModelData.Wrists.Set, set);
changes |= SetIfDifferent(ref ModelData.Wrists.Variant, variant);
changes |= WristsName != name;
WristsName = name;
changes |= Wrists != id;
Wrists = id;
return changes;
case EquipSlot.RFinger:
changes |= SetIfDifferent(ref ModelData.RFinger.Set, set);
changes |= SetIfDifferent(ref ModelData.RFinger.Variant, variant);
changes |= RFingerName != name;
RFingerName = name;
changes |= RFinger != id;
RFinger = id;
return changes;
case EquipSlot.LFinger:
changes |= SetIfDifferent(ref ModelData.LFinger.Set, set);
changes |= SetIfDifferent(ref ModelData.LFinger.Variant, variant);
changes |= LFingerName != name;
LFingerName = name;
changes |= LFinger != id;
LFinger = id;
return changes;
default: return false;
}
}
}
public static class DesignBase64Migration
{
public const int Base64Size = 91;
public static ModelData MigrateBase64(string base64, out EquipFlag equipFlags, out CustomizeFlag customizeFlags,
out bool writeinternal, out QuadBool wet, out QuadBool hat, out QuadBool visor, out QuadBool weapon)
{
static void CheckSize(int length, int requiredLength)
{
if (length != requiredLength)
throw new Exception(
$"Can not parse Base64 string into CharacterSave:\n\tInvalid size {length} instead of {requiredLength}.");
}
byte applicationFlags;
ushort equipFlagsS;
var bytes = Convert.FromBase64String(base64);
hat = QuadBool.Null;
visor = QuadBool.Null;
weapon = QuadBool.Null;
switch (bytes[0])
{
case 1:
{
CheckSize(bytes.Length, 86);
applicationFlags = bytes[1];
equipFlagsS = BitConverter.ToUInt16(bytes, 2);
break;
}
case 2:
{
CheckSize(bytes.Length, Base64Size);
applicationFlags = bytes[1];
equipFlagsS = BitConverter.ToUInt16(bytes, 2);
hat = hat.SetValue((bytes[90] & 0x01) == 0);
visor = visor.SetValue((bytes[90] & 0x10) != 0);
weapon = weapon.SetValue((bytes[90] & 0x02) == 0);
break;
}
default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}.");
}
customizeFlags = (applicationFlags & 0x01) != 0 ? CustomizeFlagExtensions.All : 0;
wet = (applicationFlags & 0x02) != 0 ? QuadBool.True : QuadBool.NullFalse;
hat = hat.SetEnabled((applicationFlags & 0x04) != 0);
weapon = weapon.SetEnabled((applicationFlags & 0x08) != 0);
visor = visor.SetEnabled((applicationFlags & 0x10) != 0);
writeinternal = (applicationFlags & 0x20) != 0;
equipFlags = 0;
equipFlags |= (equipFlagsS & 0x0001) != 0 ? EquipFlag.Mainhand | EquipFlag.MainhandStain : 0;
equipFlags |= (equipFlagsS & 0x0002) != 0 ? EquipFlag.Offhand | EquipFlag.OffhandStain : 0;
var flag = 0x0002u;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
flag <<= 1;
equipFlags |= (equipFlagsS & flag) != 0 ? slot.ToFlag() | slot.ToStainFlag() : 0;
}
var data = new ModelData();
unsafe
{
fixed (byte* ptr = bytes)
{
data.Customize.Load(*(Customize*) (ptr + 4));
var cur = (CharacterWeapon*)(ptr + 30);
data.MainHand = cur[0];
data.OffHand = cur[1];
var eq = (CharacterArmor*)(cur + 2);
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
{
var mdl = eq[idx];
data.SetPiece(slot, mdl.Set, mdl.Variant, mdl.Stain, out _);
}
}
}
return data;
}
public static unsafe string CreateOldBase64(in ModelData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, bool wet, bool hat,
bool setHat, bool visor, bool setVisor, bool weapon, bool setWeapon, bool writeinternal, float alpha)
{
var data = stackalloc byte[Base64Size];
data[0] = 2;
data[1] = (byte)((customizeFlags == CustomizeFlagExtensions.All ? 0x01 : 0)
| (wet ? 0x02 : 0)
| (setHat ? 0x04 : 0)
| (setWeapon ? 0x08 : 0)
| (setVisor ? 0x10 : 0)
| (writeinternal ? 0x20 : 0));
data[2] = (byte)((equipFlags.HasFlag(EquipFlag.Mainhand) ? 0x01 : 0)
| (equipFlags.HasFlag(EquipFlag.Offhand) ? 0x02 : 0)
| (equipFlags.HasFlag(EquipFlag.Head) ? 0x04 : 0)
| (equipFlags.HasFlag(EquipFlag.Body) ? 0x08 : 0)
| (equipFlags.HasFlag(EquipFlag.Hands) ? 0x10 : 0)
| (equipFlags.HasFlag(EquipFlag.Legs) ? 0x20 : 0)
| (equipFlags.HasFlag(EquipFlag.Feet) ? 0x40 : 0)
| (equipFlags.HasFlag(EquipFlag.Ears) ? 0x80 : 0));
data[3] = (byte)((equipFlags.HasFlag(EquipFlag.Neck) ? 0x01 : 0)
| (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0)
| (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0)
| (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0));
save.Customize.Load(*(Customize*) (data + 4));
((CharacterWeapon*)(data + 30))[0] = save.MainHand;
((CharacterWeapon*)(data + 30))[1] = save.OffHand;
((CharacterArmor*)(data + 44))[0] = save.Head;
((CharacterArmor*)(data + 44))[1] = save.Body;
((CharacterArmor*)(data + 44))[2] = save.Hands;
((CharacterArmor*)(data + 44))[3] = save.Legs;
((CharacterArmor*)(data + 44))[4] = save.Feet;
((CharacterArmor*)(data + 44))[5] = save.Ears;
((CharacterArmor*)(data + 44))[6] = save.Neck;
((CharacterArmor*)(data + 44))[7] = save.Wrists;
((CharacterArmor*)(data + 44))[8] = save.RFinger;
((CharacterArmor*)(data + 44))[9] = save.LFinger;
*(float*)(data + 84) = 1f;
data[88] = (byte)((hat ? 0x01 : 0)
| (visor ? 0x10 : 0)
| (weapon ? 0x02 : 0));
return Convert.ToBase64String(new Span<byte>(data, Base64Size));
}
}

View file

@ -1,178 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Plugin;
using Glamourer.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui.Filesystem;
namespace Glamourer.Designs;
public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
{
public static string GetDesignFileSystemFile(DalamudPluginInterface pi)
=> Path.Combine(pi.GetPluginConfigDirectory(), "sort_order.json");
public readonly string DesignFileSystemFile;
private readonly SaveService _saveService;
private readonly DesignManager _designManager;
public DesignFileSystem(DesignManager designManager, DalamudPluginInterface pi, SaveService saveService)
{
DesignFileSystemFile = GetDesignFileSystemFile(pi);
_designManager = designManager;
_saveService = saveService;
_designManager.DesignChange += OnDataChange;
Changed += OnChange;
Reload();
}
private void Reload()
{
if (Load(new FileInfo(DesignFileSystemFile), _designManager.Designs, DesignToIdentifier, DesignToName))
_saveService.ImmediateSave(this);
Glamourer.Log.Debug("Reloaded design filesystem.");
}
public void Dispose()
{
_designManager.DesignChange -= OnDataChange;
}
public struct CreationDate : ISortMode<Design>
{
public string Name
=> "Creation Date (Older First)";
public string Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date.";
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate));
}
public struct InverseCreationDate : ISortMode<Design>
{
public string Name
=> "Creation Date (Newer First)";
public string Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date.";
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate));
}
private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3)
{
if (type != FileSystemChangeType.Reload)
_saveService.QueueSave(this);
}
private void OnDataChange(DesignManager.DesignChangeType type, Design design, object? data)
{
switch (type)
{
case DesignManager.DesignChangeType.Created:
var originalName = design.Name.Text.FixName();
var name = originalName;
var counter = 1;
while (Find(name, out _))
name = $"{originalName} ({++counter})";
CreateLeaf(Root, name, design);
break;
case DesignManager.DesignChangeType.Deleted:
if (FindLeaf(design, out var leaf))
Delete(leaf);
break;
case DesignManager.DesignChangeType.ReloadedAll:
Reload();
break;
case DesignManager.DesignChangeType.Renamed when data is string oldName:
var old = oldName.FixName();
if (Find(old, out var child) && child is not Folder)
Rename(child, design.Name);
break;
}
}
// Used for saving and loading.
private static string DesignToIdentifier(Design design)
=> design.Identifier.ToString();
private static string DesignToName(Design design)
=> design.Name.Text.FixName();
private static bool DesignHasDefaultPath(Design design, string fullPath)
{
var regex = new Regex($@"^{Regex.Escape(DesignToName(design))}( \(\d+\))?$");
return regex.IsMatch(fullPath);
}
private static (string, bool) SaveDesign(Design design, string fullPath)
// Only save pairs with non-default paths.
=> DesignHasDefaultPath(design, fullPath)
? (string.Empty, false)
: (DesignToIdentifier(design), true);
// Search the entire filesystem for the leaf corresponding to a design.
public bool FindLeaf(Design design, [NotNullWhen(true)] out Leaf? leaf)
{
leaf = Root.GetAllDescendants(ISortMode<Design>.Lexicographical)
.OfType<Leaf>()
.FirstOrDefault(l => l.Value == design);
return leaf != null;
}
internal static void MigrateOldPaths(DalamudPluginInterface pi, Dictionary<string, string> oldPaths)
{
if (oldPaths.Count == 0)
return;
var file = GetDesignFileSystemFile(pi);
try
{
JObject jObject;
if (File.Exists(file))
{
var text = File.ReadAllText(file);
jObject = JObject.Parse(text);
var dict = jObject["Data"]?.ToObject<Dictionary<string, string>>();
if (dict != null)
foreach (var (key, value) in dict)
oldPaths.TryAdd(key, value);
jObject["Data"] = JToken.FromObject(oldPaths);
}
else
{
jObject = new JObject
{
["Data"] = JToken.FromObject(oldPaths),
["EmptyFolders"] = JToken.FromObject(Array.Empty<string>()),
};
}
var data = jObject.ToString(Formatting.Indented);
File.WriteAllText(file, data);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not migrate old folder paths to new version:\n{ex}");
}
}
public string ToFilename(FilenameService fileNames)
=> fileNames.DesignFileSystem;
public void Save(StreamWriter writer)
{
SaveToFile(writer, SaveDesign, true);
}
}

View file

@ -1,372 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Plugin;
using Dalamud.Utility;
using Glamourer.Customization;
using Glamourer.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Designs;
public class DesignManager
{
public const string DesignFolderName = "designs";
public readonly string DesignFolder;
private readonly CustomizationManager _customizations;
private readonly ItemManager _items;
private readonly SaveService _saveService;
private readonly List<Design> _designs = new();
public enum DesignChangeType
{
Created,
Deleted,
ReloadedAll,
Renamed,
ChangedDescription,
AddedTag,
RemovedTag,
ChangedTag,
Customize,
Equip,
Weapon,
Stain,
ApplyCustomize,
ApplyEquip,
Other,
}
public delegate void DesignChangeDelegate(DesignChangeType type, Design design, object? changeData = null);
public event DesignChangeDelegate? DesignChange;
public IReadOnlyList<Design> Designs
=> _designs;
public DesignManager(DalamudPluginInterface pi, SaveService saveService, ItemManager items)
{
_saveService = saveService;
_items = items;
DesignFolder = SetDesignFolder(pi);
LoadDesigns();
MigrateOldDesigns(pi, Path.Combine(new DirectoryInfo(DesignFolder).Parent!.FullName, "Designs.json"));
}
private static string SetDesignFolder(DalamudPluginInterface pi)
{
var ret = Path.Combine(pi.GetPluginConfigDirectory(), DesignFolderName);
if (Directory.Exists(ret))
return ret;
try
{
Directory.CreateDirectory(ret);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not create design folder directory at {ret}:\n{ex}");
}
return ret;
}
public void LoadDesigns()
{
_designs.Clear();
List<(Design, string)> invalidNames = new();
var skipped = 0;
foreach (var file in new DirectoryInfo(DesignFolder).EnumerateFiles("*.json", SearchOption.TopDirectoryOnly))
{
try
{
var text = File.ReadAllText(file.FullName);
var data = JObject.Parse(text);
var design = Design.LoadDesign(_items, data, out var changes);
if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(file.Name))
invalidNames.Add((design, file.FullName));
if (_designs.Any(f => f.Identifier == design.Identifier))
throw new Exception($"Identifier {design.Identifier} was not unique.");
// TODO something when changed?
design.Index = _designs.Count;
_designs.Add(design);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not load design, skipped:\n{ex}");
++skipped;
}
}
var failed = 0;
foreach (var (design, name) in invalidNames)
{
try
{
var correctName = _saveService.FileNames.DesignFile(design);
File.Move(name, correctName, false);
Glamourer.Log.Information($"Moved invalid design file from {Path.GetFileName(name)} to {Path.GetFileName(correctName)}.");
}
catch (Exception ex)
{
++failed;
Glamourer.Log.Error($"Failed to move invalid design file from {Path.GetFileName(name)}:\n{ex}");
}
}
if (invalidNames.Count > 0)
Glamourer.Log.Information(
$"Moved {invalidNames.Count - failed} designs to correct names.{(failed > 0 ? $" Failed to move {failed} designs to correct names." : string.Empty)}");
Glamourer.Log.Information(
$"Loaded {_designs.Count} designs.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}");
DesignChange?.Invoke(DesignChangeType.ReloadedAll, null!);
}
public Design Create(string name)
{
var design = new Design(_items)
{
CreationDate = DateTimeOffset.UtcNow,
Identifier = CreateNewGuid(),
Index = _designs.Count,
Name = name,
};
_designs.Add(design);
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
_saveService.ImmediateSave(design);
DesignChange?.Invoke(DesignChangeType.Created, design);
return design;
}
public void Delete(Design design)
{
_designs.RemoveAt(design.Index);
foreach (var d in _designs.Skip(design.Index + 1))
--d.Index;
_saveService.ImmediateDelete(design);
}
public void Rename(Design design, string newName)
{
var oldName = design.Name.Text;
_saveService.ImmediateDelete(design);
design.Name = newName;
Glamourer.Log.Debug($"Renamed design {design.Identifier}.");
_saveService.ImmediateSave(design);
DesignChange?.Invoke(DesignChangeType.Renamed, design, oldName);
}
public void ChangeDescription(Design design, string description)
{
design.Description = description;
Glamourer.Log.Debug($"Changed description of design {design.Identifier}.");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.ChangedDescription, design);
}
public void AddTag(Design design, string tag)
{
if (design.Tags.Contains(tag))
return;
design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray();
var idx = design.Tags.IndexOf(tag);
Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}.");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.AddedTag, design);
}
public void RemoveTag(Design design, string tag)
{
var idx = design.Tags.IndexOf(tag);
if (idx >= 0)
RemoveTag(design, idx);
}
public void RemoveTag(Design design, int tagIdx)
{
var oldTag = design.Tags[tagIdx];
design.Tags = design.Tags.Take(tagIdx).Concat(design.Tags.Skip(tagIdx + 1)).ToArray();
Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}.");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.RemovedTag, design);
}
public void RenameTag(Design design, int tagIdx, string newTag)
{
var oldTag = design.Tags[tagIdx];
if (oldTag == newTag)
return;
design.Tags[tagIdx] = newTag;
Array.Sort(design.Tags);
Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags.");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.ChangedTag, design);
}
public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value)
{
var old = design.GetCustomize(idx);
if (!design.SetCustomize(idx, value))
return;
Glamourer.Log.Debug($"Changed customize {idx} in design {design.Identifier} from {old.Value} to {value.Value}");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.Customize, design, idx);
}
public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
{
if (!design.SetApplyCustomize(idx, value))
return;
Glamourer.Log.Debug($"Set applying of customization {idx} to {value}.");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.ApplyCustomize, design, idx);
}
public void ChangeEquip(Design design, EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
{
var old = design.Armor(slot);
if (!design.SetArmor(_items, slot, itemId, item))
return;
var n = design.Armor(slot);
Glamourer.Log.Debug(
$"Set {slot} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {n.Name} ({n.ItemId}).");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.Equip, design, slot);
}
public void ChangeWeapon(Design design, uint itemId, EquipSlot offhand, Lumina.Excel.GeneratedSheets.Item? item = null)
{
var (old, change, n) = offhand == EquipSlot.OffHand
? (design.WeaponOff, design.SetOffhand(_items, itemId, item), design.WeaponOff)
: (design.WeaponMain, design.SetMainhand(_items, itemId, item), design.WeaponMain);
if (!change)
return;
Glamourer.Log.Debug(
$"Set {offhand} weapon in design {design.Identifier} from {old.Name} ({old.ItemId}) to {n.Name} ({n.ItemId}).");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.Weapon, design, offhand);
}
public void ChangeApplyEquip(Design design, EquipSlot slot, bool value)
{
if (!design.SetApplyEquip(slot, value))
return;
Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}.");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.ApplyEquip, design, slot);
}
public void ChangeStain(Design design, EquipSlot slot, StainId stain)
{
if (!design.SetStain(slot, stain))
return;
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Value}.");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.Stain, design, slot);
}
public void ChangeApplyStain(Design design, EquipSlot slot, bool value)
{
if (!design.SetApplyStain(slot, value))
return;
Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}.");
_saveService.QueueSave(design);
DesignChange?.Invoke(DesignChangeType.Stain, design, slot);
}
private Guid CreateNewGuid()
{
while (true)
{
var guid = Guid.NewGuid();
if (_designs.All(d => d.Identifier != guid))
return guid;
}
}
private bool Add(Design design, string? message)
{
if (_designs.Any(d => d == design || d.Identifier == design.Identifier))
return false;
design.Index = _designs.Count;
_designs.Add(design);
if (!message.IsNullOrEmpty())
Glamourer.Log.Debug(message);
_saveService.ImmediateSave(design);
DesignChange?.Invoke(DesignChangeType.Created, design);
return true;
}
private void MigrateOldDesigns(DalamudPluginInterface pi, string filePath)
{
if (!File.Exists(filePath))
return;
var errors = 0;
var successes = 0;
try
{
var text = File.ReadAllText(filePath);
var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(text) ?? new Dictionary<string, string>();
var migratedFileSystemPaths = new Dictionary<string, string>(dict.Count);
foreach (var (name, base64) in dict)
{
try
{
var actualName = Path.GetFileName(name);
var design = new Design(_items)
{
CreationDate = DateTimeOffset.UtcNow,
Identifier = CreateNewGuid(),
Name = actualName,
};
design.MigrateBase64(_items, base64);
Add(design, $"Migrated old design to {design.Identifier}.");
migratedFileSystemPaths.Add(design.Identifier.ToString(), name);
++successes;
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not migrate design {name}:\n{ex}");
++errors;
}
}
DesignFileSystem.MigrateOldPaths(pi, migratedFileSystemPaths);
Glamourer.Log.Information($"Successfully migrated {successes} old designs. Failed to migrate {errors} designs.");
}
catch (Exception e)
{
Glamourer.Log.Error($"Could not migrate old design file {filePath}:\n{e}");
}
try
{
File.Move(filePath, Path.ChangeExtension(filePath, ".json.bak"));
Glamourer.Log.Information($"Moved migrated design file {filePath} to backup file.");
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not move migrated design file {filePath} to backup file:\n{ex}");
}
}
}

View file

@ -1,75 +0,0 @@
using System;
using Penumbra.GameData.Enums;
namespace Glamourer.Designs;
[Flags]
public enum EquipFlag : uint
{
Head = 0x00000001,
Body = 0x00000002,
Hands = 0x00000004,
Legs = 0x00000008,
Feet = 0x00000010,
Ears = 0x00000020,
Neck = 0x00000040,
Wrist = 0x00000080,
RFinger = 0x00000100,
LFinger = 0x00000200,
Mainhand = 0x00000400,
Offhand = 0x00000800,
HeadStain = 0x00001000,
BodyStain = 0x00002000,
HandsStain = 0x00004000,
LegsStain = 0x00008000,
FeetStain = 0x00010000,
EarsStain = 0x00020000,
NeckStain = 0x00040000,
WristStain = 0x00080000,
RFingerStain = 0x00100000,
LFingerStain = 0x00200000,
MainhandStain = 0x00400000,
OffhandStain = 0x00800000,
}
public static class EquipFlagExtensions
{
public const EquipFlag All = (EquipFlag)(((uint)EquipFlag.OffhandStain << 1) - 1);
public const int NumEquipFlags = 24;
public static EquipFlag ToFlag(this EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => EquipFlag.Mainhand,
EquipSlot.OffHand => EquipFlag.Offhand,
EquipSlot.Head => EquipFlag.Head,
EquipSlot.Body => EquipFlag.Body,
EquipSlot.Hands => EquipFlag.Hands,
EquipSlot.Legs => EquipFlag.Legs,
EquipSlot.Feet => EquipFlag.Feet,
EquipSlot.Ears => EquipFlag.Ears,
EquipSlot.Neck => EquipFlag.Neck,
EquipSlot.Wrists => EquipFlag.Wrist,
EquipSlot.RFinger => EquipFlag.RFinger,
EquipSlot.LFinger => EquipFlag.LFinger,
_ => 0,
};
public static EquipFlag ToStainFlag(this EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => EquipFlag.MainhandStain,
EquipSlot.OffHand => EquipFlag.OffhandStain,
EquipSlot.Head => EquipFlag.HeadStain,
EquipSlot.Body => EquipFlag.BodyStain,
EquipSlot.Hands => EquipFlag.HandsStain,
EquipSlot.Legs => EquipFlag.LegsStain,
EquipSlot.Feet => EquipFlag.FeetStain,
EquipSlot.Ears => EquipFlag.EarsStain,
EquipSlot.Neck => EquipFlag.NeckStain,
EquipSlot.Wrists => EquipFlag.WristStain,
EquipSlot.RFinger => EquipFlag.RFingerStain,
EquipSlot.LFinger => EquipFlag.LFingerStain,
_ => 0,
};
}

View file

@ -1,496 +0,0 @@
using System;
using Glamourer.Customization;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.String.Functions;
namespace Glamourer.Designs;
public struct ItemPiece
{
public string Name;
public uint ItemId;
public SetId ModelId;
}
[Flags]
public enum ModelFlags : ushort
{
HatVisible = 0x01,
WeaponVisible = 0x02,
VisorToggled = 0x04,
}
public struct ModelData
{
public readonly unsafe ModelData Clone()
{
var data = new ModelData(MainHand);
fixed (void* ptr = &this)
{
MemoryUtility.MemCpyUnchecked(&data, ptr, sizeof(ModelData));
}
return data;
}
public Customize Customize = Customize.Default;
public ModelFlags Flags = ModelFlags.HatVisible | ModelFlags.WeaponVisible;
public CharacterWeapon MainHand;
public CharacterWeapon OffHand = CharacterWeapon.Empty;
public uint ModelId = 0;
public CharacterArmor Head = CharacterArmor.Empty;
public CharacterArmor Body = CharacterArmor.Empty;
public CharacterArmor Hands = CharacterArmor.Empty;
public CharacterArmor Legs = CharacterArmor.Empty;
public CharacterArmor Feet = CharacterArmor.Empty;
public CharacterArmor Ears = CharacterArmor.Empty;
public CharacterArmor Neck = CharacterArmor.Empty;
public CharacterArmor Wrists = CharacterArmor.Empty;
public CharacterArmor RFinger = CharacterArmor.Empty;
public CharacterArmor LFinger = CharacterArmor.Empty;
public ModelData(CharacterWeapon mainHand)
=> MainHand = mainHand;
public readonly CharacterArmor Armor(EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => MainHand.ToArmor(),
EquipSlot.OffHand => OffHand.ToArmor(),
EquipSlot.Head => Head,
EquipSlot.Body => Body,
EquipSlot.Hands => Hands,
EquipSlot.Legs => Legs,
EquipSlot.Feet => Feet,
EquipSlot.Ears => Ears,
EquipSlot.Neck => Neck,
EquipSlot.Wrists => Wrists,
EquipSlot.RFinger => RFinger,
EquipSlot.LFinger => LFinger,
_ => CharacterArmor.Empty,
};
public readonly CharacterWeapon Piece(EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => MainHand,
EquipSlot.OffHand => OffHand,
EquipSlot.Head => Head.ToWeapon(),
EquipSlot.Body => Body.ToWeapon(),
EquipSlot.Hands => Hands.ToWeapon(),
EquipSlot.Legs => Legs.ToWeapon(),
EquipSlot.Feet => Feet.ToWeapon(),
EquipSlot.Ears => Ears.ToWeapon(),
EquipSlot.Neck => Neck.ToWeapon(),
EquipSlot.Wrists => Wrists.ToWeapon(),
EquipSlot.RFinger => RFinger.ToWeapon(),
EquipSlot.LFinger => LFinger.ToWeapon(),
_ => CharacterWeapon.Empty,
};
public bool SetPiece(EquipSlot slot, SetId model, byte variant, out CharacterWeapon ret)
{
var changes = false;
switch (slot)
{
case EquipSlot.MainHand:
changes |= SetIfDifferent(ref MainHand.Set, model);
changes |= SetIfDifferent(ref MainHand.Variant, variant);
ret = MainHand;
return changes;
case EquipSlot.OffHand:
changes |= SetIfDifferent(ref OffHand.Set, model);
changes |= SetIfDifferent(ref OffHand.Variant, variant);
ret = OffHand;
return changes;
case EquipSlot.Head:
changes |= SetIfDifferent(ref Head.Set, model);
changes |= SetIfDifferent(ref Head.Variant, variant);
ret = Head.ToWeapon();
return changes;
case EquipSlot.Body:
changes |= SetIfDifferent(ref Body.Set, model);
changes |= SetIfDifferent(ref Body.Variant, variant);
ret = Body.ToWeapon();
return changes;
case EquipSlot.Hands:
changes |= SetIfDifferent(ref Hands.Set, model);
changes |= SetIfDifferent(ref Hands.Variant, variant);
ret = Hands.ToWeapon();
return changes;
case EquipSlot.Legs:
changes |= SetIfDifferent(ref Legs.Set, model);
changes |= SetIfDifferent(ref Legs.Variant, variant);
ret = Legs.ToWeapon();
return changes;
case EquipSlot.Feet:
changes |= SetIfDifferent(ref Feet.Set, model);
changes |= SetIfDifferent(ref Feet.Variant, variant);
ret = Feet.ToWeapon();
return changes;
case EquipSlot.Ears:
changes |= SetIfDifferent(ref Ears.Set, model);
changes |= SetIfDifferent(ref Ears.Variant, variant);
ret = Ears.ToWeapon();
return changes;
case EquipSlot.Neck:
changes |= SetIfDifferent(ref Neck.Set, model);
changes |= SetIfDifferent(ref Neck.Variant, variant);
ret = Neck.ToWeapon();
return changes;
case EquipSlot.Wrists:
changes |= SetIfDifferent(ref Wrists.Set, model);
changes |= SetIfDifferent(ref Wrists.Variant, variant);
ret = Wrists.ToWeapon();
return changes;
case EquipSlot.RFinger:
changes |= SetIfDifferent(ref RFinger.Set, model);
changes |= SetIfDifferent(ref RFinger.Variant, variant);
ret = RFinger.ToWeapon();
return changes;
case EquipSlot.LFinger:
changes |= SetIfDifferent(ref LFinger.Set, model);
changes |= SetIfDifferent(ref LFinger.Variant, variant);
ret = LFinger.ToWeapon();
return changes;
default:
ret = CharacterWeapon.Empty;
return changes;
}
}
public bool SetPiece(EquipSlot slot, SetId model, WeaponType type, byte variant, out CharacterWeapon ret)
{
var changes = false;
switch (slot)
{
case EquipSlot.MainHand:
changes |= SetIfDifferent(ref MainHand.Set, model);
changes |= SetIfDifferent(ref MainHand.Type, type);
changes |= SetIfDifferent(ref MainHand.Variant, variant);
ret = MainHand;
return changes;
case EquipSlot.OffHand:
changes |= SetIfDifferent(ref OffHand.Set, model);
changes |= SetIfDifferent(ref OffHand.Type, type);
changes |= SetIfDifferent(ref OffHand.Variant, variant);
ret = OffHand;
return changes;
case EquipSlot.Head:
changes |= SetIfDifferent(ref Head.Set, model);
changes |= SetIfDifferent(ref Head.Variant, variant);
ret = Head.ToWeapon();
return changes;
case EquipSlot.Body:
changes |= SetIfDifferent(ref Body.Set, model);
changes |= SetIfDifferent(ref Body.Variant, variant);
ret = Body.ToWeapon();
return changes;
case EquipSlot.Hands:
changes |= SetIfDifferent(ref Hands.Set, model);
changes |= SetIfDifferent(ref Hands.Variant, variant);
ret = Hands.ToWeapon();
return changes;
case EquipSlot.Legs:
changes |= SetIfDifferent(ref Legs.Set, model);
changes |= SetIfDifferent(ref Legs.Variant, variant);
ret = Legs.ToWeapon();
return changes;
case EquipSlot.Feet:
changes |= SetIfDifferent(ref Feet.Set, model);
changes |= SetIfDifferent(ref Feet.Variant, variant);
ret = Feet.ToWeapon();
return changes;
case EquipSlot.Ears:
changes |= SetIfDifferent(ref Ears.Set, model);
changes |= SetIfDifferent(ref Ears.Variant, variant);
ret = Ears.ToWeapon();
return changes;
case EquipSlot.Neck:
changes |= SetIfDifferent(ref Neck.Set, model);
changes |= SetIfDifferent(ref Neck.Variant, variant);
ret = Neck.ToWeapon();
return changes;
case EquipSlot.Wrists:
changes |= SetIfDifferent(ref Wrists.Set, model);
changes |= SetIfDifferent(ref Wrists.Variant, variant);
ret = Wrists.ToWeapon();
return changes;
case EquipSlot.RFinger:
changes |= SetIfDifferent(ref RFinger.Set, model);
changes |= SetIfDifferent(ref RFinger.Variant, variant);
ret = RFinger.ToWeapon();
return changes;
case EquipSlot.LFinger:
changes |= SetIfDifferent(ref LFinger.Set, model);
changes |= SetIfDifferent(ref LFinger.Variant, variant);
ret = LFinger.ToWeapon();
return changes;
default:
ret = CharacterWeapon.Empty;
return changes;
}
}
public bool SetPiece(EquipSlot slot, SetId model, byte variant, StainId stain, out CharacterWeapon ret)
{
var changes = false;
switch (slot)
{
case EquipSlot.MainHand:
changes |= SetIfDifferent(ref MainHand.Set, model);
changes |= SetIfDifferent(ref MainHand.Variant, variant);
changes |= SetIfDifferent(ref MainHand.Stain, stain);
ret = MainHand;
return changes;
case EquipSlot.OffHand:
changes |= SetIfDifferent(ref OffHand.Set, model);
changes |= SetIfDifferent(ref OffHand.Variant, variant);
changes |= SetIfDifferent(ref OffHand.Stain, stain);
ret = OffHand;
return changes;
case EquipSlot.Head:
changes |= SetIfDifferent(ref Head.Set, model);
changes |= SetIfDifferent(ref Head.Variant, variant);
changes |= SetIfDifferent(ref Head.Stain, stain);
ret = Head.ToWeapon();
return changes;
case EquipSlot.Body:
changes |= SetIfDifferent(ref Body.Set, model);
changes |= SetIfDifferent(ref Body.Variant, variant);
changes |= SetIfDifferent(ref Body.Stain, stain);
ret = Body.ToWeapon();
return changes;
case EquipSlot.Hands:
changes |= SetIfDifferent(ref Hands.Set, model);
changes |= SetIfDifferent(ref Hands.Variant, variant);
changes |= SetIfDifferent(ref Hands.Stain, stain);
ret = Hands.ToWeapon();
return changes;
case EquipSlot.Legs:
changes |= SetIfDifferent(ref Legs.Set, model);
changes |= SetIfDifferent(ref Legs.Variant, variant);
changes |= SetIfDifferent(ref Legs.Stain, stain);
ret = Legs.ToWeapon();
return changes;
case EquipSlot.Feet:
changes |= SetIfDifferent(ref Feet.Set, model);
changes |= SetIfDifferent(ref Feet.Variant, variant);
changes |= SetIfDifferent(ref Feet.Stain, stain);
ret = Feet.ToWeapon();
return changes;
case EquipSlot.Ears:
changes |= SetIfDifferent(ref Ears.Set, model);
changes |= SetIfDifferent(ref Ears.Variant, variant);
changes |= SetIfDifferent(ref Ears.Stain, stain);
ret = Ears.ToWeapon();
return changes;
case EquipSlot.Neck:
changes |= SetIfDifferent(ref Neck.Set, model);
changes |= SetIfDifferent(ref Neck.Variant, variant);
changes |= SetIfDifferent(ref Neck.Stain, stain);
ret = Neck.ToWeapon();
return changes;
case EquipSlot.Wrists:
changes |= SetIfDifferent(ref Wrists.Set, model);
changes |= SetIfDifferent(ref Wrists.Variant, variant);
changes |= SetIfDifferent(ref Wrists.Stain, stain);
ret = Wrists.ToWeapon();
return changes;
case EquipSlot.RFinger:
changes |= SetIfDifferent(ref RFinger.Set, model);
changes |= SetIfDifferent(ref RFinger.Variant, variant);
changes |= SetIfDifferent(ref RFinger.Stain, stain);
ret = RFinger.ToWeapon();
return changes;
case EquipSlot.LFinger:
changes |= SetIfDifferent(ref LFinger.Set, model);
changes |= SetIfDifferent(ref LFinger.Variant, variant);
changes |= SetIfDifferent(ref LFinger.Stain, stain);
ret = LFinger.ToWeapon();
return changes;
default:
ret = CharacterWeapon.Empty;
return changes;
}
}
public bool SetPiece(EquipSlot slot, CharacterWeapon weapon, out CharacterWeapon ret)
=> SetPiece(slot, weapon.Set, weapon.Type, (byte)weapon.Variant, weapon.Stain, out ret);
public bool SetPiece(EquipSlot slot, CharacterArmor armor, out CharacterWeapon ret)
=> SetPiece(slot, armor.Set, armor.Variant, armor.Stain, out ret);
public bool SetPiece(EquipSlot slot, SetId model, WeaponType type, byte variant, StainId stain, out CharacterWeapon ret)
{
var changes = false;
switch (slot)
{
case EquipSlot.MainHand:
changes |= SetIfDifferent(ref MainHand.Set, model);
changes |= SetIfDifferent(ref MainHand.Type, type);
changes |= SetIfDifferent(ref MainHand.Variant, variant);
changes |= SetIfDifferent(ref MainHand.Stain, stain);
ret = MainHand;
return changes;
case EquipSlot.OffHand:
changes |= SetIfDifferent(ref OffHand.Set, model);
changes |= SetIfDifferent(ref OffHand.Type, type);
changes |= SetIfDifferent(ref OffHand.Variant, variant);
changes |= SetIfDifferent(ref OffHand.Stain, stain);
ret = OffHand;
return changes;
case EquipSlot.Head:
changes |= SetIfDifferent(ref Head.Set, model);
changes |= SetIfDifferent(ref Head.Variant, variant);
changes |= SetIfDifferent(ref Head.Stain, stain);
ret = Head.ToWeapon();
return changes;
case EquipSlot.Body:
changes |= SetIfDifferent(ref Body.Set, model);
changes |= SetIfDifferent(ref Body.Variant, variant);
changes |= SetIfDifferent(ref Body.Stain, stain);
ret = Body.ToWeapon();
return changes;
case EquipSlot.Hands:
changes |= SetIfDifferent(ref Hands.Set, model);
changes |= SetIfDifferent(ref Hands.Variant, variant);
changes |= SetIfDifferent(ref Hands.Stain, stain);
ret = Hands.ToWeapon();
return changes;
case EquipSlot.Legs:
changes |= SetIfDifferent(ref Legs.Set, model);
changes |= SetIfDifferent(ref Legs.Variant, variant);
changes |= SetIfDifferent(ref Legs.Stain, stain);
ret = Legs.ToWeapon();
return changes;
case EquipSlot.Feet:
changes |= SetIfDifferent(ref Feet.Set, model);
changes |= SetIfDifferent(ref Feet.Variant, variant);
changes |= SetIfDifferent(ref Feet.Stain, stain);
ret = Feet.ToWeapon();
return changes;
case EquipSlot.Ears:
changes |= SetIfDifferent(ref Ears.Set, model);
changes |= SetIfDifferent(ref Ears.Variant, variant);
changes |= SetIfDifferent(ref Ears.Stain, stain);
ret = Ears.ToWeapon();
return changes;
case EquipSlot.Neck:
changes |= SetIfDifferent(ref Neck.Set, model);
changes |= SetIfDifferent(ref Neck.Variant, variant);
changes |= SetIfDifferent(ref Neck.Stain, stain);
ret = Neck.ToWeapon();
return changes;
case EquipSlot.Wrists:
changes |= SetIfDifferent(ref Wrists.Set, model);
changes |= SetIfDifferent(ref Wrists.Variant, variant);
changes |= SetIfDifferent(ref Wrists.Stain, stain);
ret = Wrists.ToWeapon();
return changes;
case EquipSlot.RFinger:
changes |= SetIfDifferent(ref RFinger.Set, model);
changes |= SetIfDifferent(ref RFinger.Variant, variant);
changes |= SetIfDifferent(ref RFinger.Stain, stain);
ret = RFinger.ToWeapon();
return changes;
case EquipSlot.LFinger:
changes |= SetIfDifferent(ref LFinger.Set, model);
changes |= SetIfDifferent(ref LFinger.Variant, variant);
changes |= SetIfDifferent(ref LFinger.Stain, stain);
ret = LFinger.ToWeapon();
return changes;
default:
ret = CharacterWeapon.Empty;
return changes;
}
}
public StainId Stain(EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => MainHand.Stain,
EquipSlot.OffHand => OffHand.Stain,
EquipSlot.Head => Head.Stain,
EquipSlot.Body => Body.Stain,
EquipSlot.Hands => Hands.Stain,
EquipSlot.Legs => Legs.Stain,
EquipSlot.Feet => Feet.Stain,
EquipSlot.Ears => Ears.Stain,
EquipSlot.Neck => Neck.Stain,
EquipSlot.Wrists => Wrists.Stain,
EquipSlot.RFinger => RFinger.Stain,
EquipSlot.LFinger => LFinger.Stain,
_ => 0,
};
public bool SetStain(EquipSlot slot, StainId stain, out CharacterWeapon ret)
{
var changes = false;
switch (slot)
{
case EquipSlot.MainHand:
changes = SetIfDifferent(ref MainHand.Stain, stain);
ret = MainHand;
return changes;
case EquipSlot.OffHand:
changes = SetIfDifferent(ref OffHand.Stain, stain);
ret = OffHand;
return changes;
case EquipSlot.Head:
changes = SetIfDifferent(ref Head.Stain, stain);
ret = Head.ToWeapon();
return changes;
case EquipSlot.Body:
changes = SetIfDifferent(ref Body.Stain, stain);
ret = Body.ToWeapon();
return changes;
case EquipSlot.Hands:
changes = SetIfDifferent(ref Hands.Stain, stain);
ret = Hands.ToWeapon();
return changes;
case EquipSlot.Legs:
changes = SetIfDifferent(ref Legs.Stain, stain);
ret = Legs.ToWeapon();
return changes;
case EquipSlot.Feet:
changes = SetIfDifferent(ref Feet.Stain, stain);
ret = Feet.ToWeapon();
return changes;
case EquipSlot.Ears:
changes = SetIfDifferent(ref Ears.Stain, stain);
ret = Ears.ToWeapon();
return changes;
case EquipSlot.Neck:
changes = SetIfDifferent(ref Neck.Stain, stain);
ret = Neck.ToWeapon();
return changes;
case EquipSlot.Wrists:
changes = SetIfDifferent(ref Wrists.Stain, stain);
ret = Wrists.ToWeapon();
return changes;
case EquipSlot.RFinger:
changes = SetIfDifferent(ref RFinger.Stain, stain);
ret = RFinger.ToWeapon();
return changes;
case EquipSlot.LFinger:
changes = SetIfDifferent(ref LFinger.Stain, stain);
ret = LFinger.ToWeapon();
return changes;
default:
ret = CharacterWeapon.Empty;
return false;
}
}
private static bool SetIfDifferent<T>(ref T old, T value) where T : IEquatable<T>
{
if (old.Equals(value))
return false;
old = value;
return true;
}
}

View file

@ -1,105 +0,0 @@
using Dalamud.Utility;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Designs;
public readonly struct Item
{
public readonly string Name;
public readonly uint ItemId;
public readonly CharacterArmor Model;
public SetId ModelBase
=> Model.Set;
public byte Variant
=> Model.Variant;
public StainId Stain
=> Model.Stain;
public Item WithStain(StainId id)
=> new(Name, ItemId, Model with { Stain = id });
public Item(string name, uint itemId, CharacterArmor armor)
{
Name = name;
ItemId = itemId;
Model.Set = armor.Set;
Model.Variant = armor.Variant;
Model.Stain = armor.Stain;
}
public Item(Lumina.Excel.GeneratedSheets.Item item)
{
Name = string.Intern(item.Name.ToDalamudString().TextValue);
ItemId = item.RowId;
Model.Set = (SetId)item.ModelMain;
Model.Variant = (byte)(item.ModelMain >> 16);
}
}
public readonly struct Weapon
{
public readonly string Name = string.Empty;
public readonly uint ItemId;
public readonly FullEquipType Type;
public readonly bool Valid;
public readonly CharacterWeapon Model;
public SetId ModelBase
=> Model.Set;
public WeaponType WeaponBase
=> Model.Type;
public byte Variant
=> (byte)Model.Variant;
public StainId Stain
=> Model.Stain;
public Weapon WithStain(StainId id)
=> new(Name, ItemId, Model with { Stain = id }, Type);
public Weapon(string name, uint itemId, CharacterWeapon weapon, FullEquipType type)
{
Name = name;
ItemId = itemId;
Type = type;
Valid = true;
Model.Set = weapon.Set;
Model.Type = weapon.Type;
Model.Variant = (byte)weapon.Variant;
Model.Stain = weapon.Stain;
}
public static Weapon Offhand(string name, uint itemId, CharacterWeapon weapon, FullEquipType type)
{
var offType = type.Offhand();
return offType is FullEquipType.Unknown
? new Weapon()
: new Weapon(name, itemId, weapon, offType);
}
public Weapon(Lumina.Excel.GeneratedSheets.Item item, bool offhand)
{
Name = string.Intern(item.Name.ToDalamudString().TextValue);
ItemId = item.RowId;
Type = item.ToEquipType();
var offType = Type.Offhand();
var model = offhand && offType == Type ? item.ModelSub : item.ModelMain;
Valid = Type.ToSlot() switch
{
EquipSlot.MainHand when !offhand => true,
EquipSlot.MainHand when offhand && offType == Type => true,
EquipSlot.OffHand when offhand => true,
_ => false,
};
Model.Set = (SetId)model;
Model.Type = (WeaponType)(model >> 16);
Model.Variant = (byte)(model >> 32);
}
}

View file

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

View file

@ -1,34 +0,0 @@
using Glamourer.Interop;
using Glamourer.Structs;
namespace Glamourer.Fixed;
public struct FixedCondition
{
private const ulong _territoryFlag = 1ul << 32;
private const ulong _jobFlag = 1ul << 33;
private ulong _data;
public static FixedCondition TerritoryCondition(ushort territoryType)
=> new() { _data = territoryType | _territoryFlag };
public static FixedCondition JobCondition(JobGroup group)
=> new() { _data = group.Id | _jobFlag };
public bool Check(Actor actor)
{
//if ((_data & (_territoryFlag | _jobFlag)) == 0)
// return true;
//
//if ((_data & _territoryFlag) != 0)
// return Dalamud.ClientState.TerritoryType == (ushort)_data;
//
//if (actor && GameData.JobGroups(Dalamud.GameData).TryGetValue((ushort)_data, out var group) && group.Fits(actor.Job))
// return true;
//
return true;
}
public override string ToString()
=> _data.ToString();
}

View file

@ -1,315 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Dalamud.Logging;
using System.Text;
using Dalamud.Utility;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Actors;
using Glamourer.Designs;
namespace Glamourer.Fixed;
public class FixedDesign
{
public const int CurrentVersion = 0;
public string Name { get; private set; }
public bool Enabled;
public List<ActorIdentifier> Actors;
public List<(FixedCondition, Design)> Customization;
public List<(FixedCondition, Design)> Equipment;
public List<(FixedCondition, Design)> Weapons;
public FixedDesign(string name)
{
Name = name;
Actors = new List<ActorIdentifier>();
Customization = new List<(FixedCondition, Design)>();
Equipment = new List<(FixedCondition, Design)>();
Weapons = new List<(FixedCondition, Design)>();
}
public static FixedDesign? Load(JObject j)
{
try
{
var name = j[nameof(Name)]?.Value<string>();
if (name.IsNullOrEmpty())
return null;
var version = j["Version"]?.Value<int>();
if (version == null)
return null;
return version switch
{
CurrentVersion => LoadCurrentVersion(j, name),
_ => null,
};
}
catch (Exception e)
{
PluginLog.Error($"Error loading fixed design:\n{e}");
return null;
}
}
private static FixedDesign? LoadCurrentVersion(JObject j, string name)
{
var enabled = j[nameof(Enabled)]?.Value<bool>() ?? false;
var ret = new FixedDesign(name)
{
Enabled = enabled,
};
var actors = j[nameof(Actors)];
//foreach(var pair in actors?.Children().)
return null;
}
public void Save(FileInfo file)
{
try
{
using var s = file.Exists ? file.Open(FileMode.Truncate) : file.Open(FileMode.CreateNew);
using var w = new StreamWriter(s, Encoding.UTF8);
using var j = new JsonTextWriter(w)
{
Formatting = Formatting.Indented,
};
j.WriteStartObject();
j.WritePropertyName(nameof(Name));
j.WriteValue(Name);
j.WritePropertyName("Version");
j.WriteValue(CurrentVersion);
j.WritePropertyName(nameof(Enabled));
j.WriteValue(Enabled);
j.WritePropertyName(nameof(Actors));
j.WriteStartArray();
foreach (var actor in Actors)
actor.ToJson().WriteTo(j);
j.WriteEndArray();
j.WritePropertyName(nameof(Customization));
j.WriteStartArray();
foreach (var (condition, design) in Customization)
{
j.WritePropertyName(condition.ToString());
j.WriteValue(design.Name);
}
j.WriteEndArray();
j.WritePropertyName(nameof(Equipment));
j.WriteStartArray();
foreach (var (condition, design) in Equipment)
{
j.WritePropertyName(condition.ToString());
j.WriteValue(design.Name);
}
j.WriteEndArray();
j.WritePropertyName(nameof(Weapons));
j.WriteStartArray();
foreach (var (condition, design) in Weapons)
{
j.WritePropertyName(condition.ToString());
j.WriteValue(design.Name);
}
j.WriteEndArray();
}
catch (Exception e)
{
PluginLog.Error($"Could not save collection {Name}:\n{e}");
}
}
public static bool Load(FileInfo path, [NotNullWhen(true)] out FixedDesign? result)
{
result = null!;
return true;
}
}
public class FixedDesigns : IDisposable
{
//public class FixedDesign
//{
// public string Name;
// public JobGroup Jobs;
// public Design Design;
// public bool Enabled;
//
// public GlamourerConfig.FixedDesign ToSave()
// => new()
// {
// Name = Name,
// Path = Design.FullName(),
// Enabled = Enabled,
// JobGroups = Jobs.Id,
// };
//
// public FixedDesign(string name, Design design, bool enabled, JobGroup jobs)
// {
// Name = name;
// Design = design;
// Enabled = enabled;
// Jobs = jobs;
// }
//}
//
//public List<FixedDesign> Data;
//public Dictionary<string, List<FixedDesign>> EnabledDesigns;
//public readonly IReadOnlyDictionary<ushort, JobGroup> JobGroups;
//
//public bool EnableDesign(FixedDesign design)
//{
// var changes = !design.Enabled;
//
// if (!EnabledDesigns.TryGetValue(design.Name, out var designs))
// {
// EnabledDesigns[design.Name] = new List<FixedDesign> { design };
// // TODO
// changes = true;
// }
// else if (!designs.Contains(design))
// {
// designs.Add(design);
// changes = true;
// }
//
// design.Enabled = true;
// // TODO
// //if (Glamourer.Config.ApplyFixedDesigns)
// //{
// // var character =
// // CharacterFactory.Convert(Dalamud.Objects.FirstOrDefault(o
// // => o.ObjectKind == ObjectKind.Player && o.Name.ToString() == design.Name));
// // if (character != null)
// // OnPlayerChange(character);
// //}
//
// return changes;
//}
//
//public bool DisableDesign(FixedDesign design)
//{
// if (!design.Enabled)
// return false;
//
// design.Enabled = false;
// if (!EnabledDesigns.TryGetValue(design.Name, out var designs))
// return false;
// if (!designs.Remove(design))
// return false;
//
// if (designs.Count == 0)
// {
// EnabledDesigns.Remove(design.Name);
// // TODO
// }
//
// return true;
//}
//
//public FixedDesigns(DesignManager designs)
//{
// JobGroups = GameData.JobGroups(Dalamud.GameData);
// Data = new List<FixedDesign>(Glamourer.Config.FixedDesigns.Count);
// EnabledDesigns = new Dictionary<string, List<FixedDesign>>(Glamourer.Config.FixedDesigns.Count);
// var changes = false;
// for (var i = 0; i < Glamourer.Config.FixedDesigns.Count; ++i)
// {
// var save = Glamourer.Config.FixedDesigns[i];
// if (designs.FileSystem.Find(save.Path, out var d) && d is Design design)
// {
// if (!JobGroups.TryGetValue((ushort)save.JobGroups, out var jobGroup))
// jobGroup = JobGroups[1];
// Data.Add(new FixedDesign(save.Name, design, save.Enabled, jobGroup));
// if (save.Enabled)
// changes |= EnableDesign(Data.Last());
// }
// else
// {
// PluginLog.Warning($"{save.Path} does not exist anymore, removing {save.Name} from fixed designs.");
// Glamourer.Config.FixedDesigns.RemoveAt(i--);
// changes = true;
// }
// }
//
// if (changes)
// Glamourer.Config.Save();
//}
//
//private void OnPlayerChange(Character character)
//{
// //var name = character.Name.ToString();
// //if (!EnabledDesigns.TryGetValue(name, out var designs))
// // return;
// //
// //var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(character.ClassJob.Id));
// //if (design == null)
// // return;
// //
// //PluginLog.Debug("Redrawing {CharacterName} with {DesignName} for job {JobGroup}.", name, design.Design.FullName(),
// // design.Jobs.Name);
// //design.Design.Data.Apply(character);
// //Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character);
// //Glamourer.Penumbra.RedrawObject(character, RedrawType.Redraw, false);
//}
//
//public void Add(string name, Design design, JobGroup group, bool enabled = false)
//{
// Data.Add(new FixedDesign(name, design, enabled, group));
// Glamourer.Config.FixedDesigns.Add(Data.Last().ToSave());
//
// if (enabled)
// EnableDesign(Data.Last());
//
// Glamourer.Config.Save();
//}
//
//public void Remove(FixedDesign design)
//{
// var idx = Data.IndexOf(design);
// if (idx < 0)
// return;
//
// Data.RemoveAt(idx);
// Glamourer.Config.FixedDesigns.RemoveAt(idx);
// if (design.Enabled)
// {
// EnabledDesigns.Remove(design.Name);
// // TODO
// }
//
// Glamourer.Config.Save();
//}
//
//public void Move(FixedDesign design, int newIdx)
//{
// if (newIdx < 0)
// newIdx = 0;
// if (newIdx >= Data.Count)
// newIdx = Data.Count - 1;
//
// var idx = Data.IndexOf(design);
// if (idx < 0 || idx == newIdx)
// return;
//
// Data.RemoveAt(idx);
// Data.Insert(newIdx, design);
// Glamourer.Config.FixedDesigns.RemoveAt(idx);
// Glamourer.Config.FixedDesigns.Insert(newIdx, design.ToSave());
// Glamourer.Config.Save();
//}
//
public void Dispose()
{
//Glamourer.Config.FixedDesigns = Data.Select(d => d.ToSave()).ToList();
//Glamourer.Config.Save();
}
}

View file

@ -1,190 +0,0 @@
using System.Reflection;
using Dalamud.Plugin;
using Glamourer.Gui;
using Glamourer.Interop;
using Glamourer.Services;
using Microsoft.Extensions.DependencyInjection;
using OtterGui.Classes;
using OtterGui.Log;
namespace Glamourer;
public partial class Glamourer : IDalamudPlugin
{
public string Name
=> "Glamourer";
public static readonly string Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty;
public static readonly string CommitHash =
Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "Unknown";
public static readonly Logger Log = new();
public static ChatService ChatService { get; private set; } = null!;
private readonly ServiceProvider _services;
public Glamourer(DalamudPluginInterface pluginInterface)
{
try
{
EventWrapper.ChangeLogger(Log);
_services = ServiceManager.CreateProvider(pluginInterface, Log);
ChatService = _services.GetRequiredService<ChatService>();
_services.GetRequiredService<BackupService>();
_services.GetRequiredService<GlamourerWindowSystem>();
_services.GetRequiredService<CommandService>();
_services.GetRequiredService<GlamourerIpc>();
_services.GetRequiredService<ChangeCustomizeService>();
_services.GetRequiredService<JobService>();
_services.GetRequiredService<UpdateSlotService>();
_services.GetRequiredService<VisorService>();
_services.GetRequiredService<WeaponService>();
_services.GetRequiredService<RedrawManager>();
}
catch
{
Dispose();
throw;
}
}
public void Dispose()
{
_services?.Dispose();
}
//private static GameObject? GetPlayer(string name)
//{
// var lowerName = name.ToLowerInvariant();
// return lowerName switch
// {
// "" => null,
// "<me>" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer,
// "self" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer,
// "<t>" => Dalamud.Targets.Target,
// "target" => Dalamud.Targets.Target,
// "<f>" => Dalamud.Targets.FocusTarget,
// "focus" => Dalamud.Targets.FocusTarget,
// "<mo>" => Dalamud.Targets.MouseOverTarget,
// "mouseover" => Dalamud.Targets.MouseOverTarget,
// _ => Dalamud.Objects.LastOrDefault(
// a => string.Equals(a.Name.ToString(), lowerName, StringComparison.InvariantCultureIgnoreCase)),
// };
//}
//
//public void CopyToClipboard(Character player)
//{
// var save = new CharacterSave();
// save.LoadCharacter(player);
// ImGui.SetClipboardText(save.ToBase64());
//}
//
//public void ApplyCommand(Character player, string target)
//{
// CharacterSave? save = null;
// if (target.ToLowerInvariant() == "clipboard")
// try
// {
// save = CharacterSave.FromString(ImGui.GetClipboardText());
// }
// catch (Exception)
// {
// Dalamud.Chat.PrintError("Clipboard does not contain a valid customization string.");
// }
// else if (!Designs.FileSystem.Find(target, out var child) || child is not Design d)
// Dalamud.Chat.PrintError("The given path to a saved design does not exist or does not point to a design.");
// else
// save = d.Data;
//
// save?.Apply(player);
// Penumbra.UpdateCharacters(player);
//}
//
//public void SaveCommand(Character player, string path)
//{
// var save = new CharacterSave();
// save.LoadCharacter(player);
// try
// {
// var (folder, name) = Designs.FileSystem.CreateAllFolders(path);
// var design = new Design(folder, name) { Data = save };
// folder.FindOrAddChild(design);
// Designs.Designs.Add(design.FullName(), design.Data);
// Designs.SaveToFile();
// }
// catch (Exception e)
// {
// Dalamud.Chat.PrintError("Could not save file:");
// Dalamud.Chat.PrintError($" {e.Message}");
// }
//}
//
public void OnGlamour(string command, string arguments)
{
//static void PrintHelp()
//{
// Dalamud.Chat.Print("Usage:");
// Dalamud.Chat.Print($" {HelpString}");
//}
//arguments = arguments.Trim();
//if (!arguments.Any())
//{
// PrintHelp();
// return;
//}
//
//var split = arguments.Split(new[]
//{
// ',',
//}, 3, StringSplitOptions.RemoveEmptyEntries);
//
//if (split.Length < 2)
//{
// PrintHelp();
// return;
//}
//
//var player = GetPlayer(split[1]) as Character;
//if (player == null)
//{
// Dalamud.Chat.Print($"Could not find object for {split[1]} or it was not a Character.");
// return;
//}
//
//switch (split[0].ToLowerInvariant())
//{
// case "copy":
// CopyToClipboard(player);
// return;
// case "apply":
// {
// if (split.Length < 3)
// {
// Dalamud.Chat.Print("Applying requires a name for the save to be applied or 'clipboard'.");
// return;
// }
//
// ApplyCommand(player, split[2]);
//
// return;
// }
// case "save":
// {
// if (split.Length < 3)
// {
// Dalamud.Chat.Print("Saving requires a name for the save.");
// return;
// }
//
// SaveCommand(player, split[2]);
// return;
// }
// default:
// PrintHelp();
// return;
//}
}
}

View file

@ -1,126 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<LangVersion>preview</LangVersion>
<PlatformTarget>x64</PlatformTarget>
<RootNamespace>Glamourer</RootNamespace>
<AssemblyName>GlamourerOld</AssemblyName>
<FileVersion>0.2.0.0</FileVersion>
<AssemblyVersion>0.2.0.0</AssemblyVersion>
<Company>SoftOtter</Company>
<Product>Glamourer</Product>
<Copyright>Copyright © 2020</Copyright>
<Deterministic>true</Deterministic>
<OutputType>Library</OutputType>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<OutputPath>bin\$(Configuration)\</OutputPath>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<None Remove="LegacyTattoo.raw" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="LegacyTattoo.raw" />
</ItemGroup>
<PropertyGroup>
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGuiScene">
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
<ProjectReference Include="..\..\Penumbra\Penumbra.Api\Penumbra.Api.csproj" />
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low">
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" />
</Exec>
<PropertyGroup>
<InformationalVersion>$(GitCommitHash)</InformationalVersion>
</PropertyGroup>
</Target>
<ItemGroup>
<None Update="Glamourer.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="if $(Configuration) == Release powershell Compress-Archive -Force $(TargetPath), $(TargetDir)$(SolutionName).json, $(TargetDir)$(SolutionName).GameData.dll, $(TargetDir)Penumbra.GameData.dll, $(TargetDir)Penumbra.Api.dll, $(TargetDir)Penumbra.String.dll $(SolutionDir)$(SolutionName).zip" />
<Exec Command="if $(Configuration) == Release powershell Copy-Item -Force $(TargetDir)$(SolutionName).json -Destination $(SolutionDir)" />
</Target>
</Project>

View file

@ -1,57 +0,0 @@
using Glamourer.Designs;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui;
public static class ActorDebug
{
/// <summary> Draw the model data values as straight table data without evaluation. </summary>
public static unsafe void Draw(in ModelData model)
{
using var table = ImRaii.Table("##drawObjectData", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
if (!table)
return;
ImGuiUtil.DrawTableColumn("Model ID");
ImGuiUtil.DrawTableColumn(model.ModelId.ToString());
ImGuiUtil.DrawTableColumn(model.ModelId.ToString("X8"));
for (var i = 0; i < Penumbra.GameData.Structs.CustomizeData.Size; ++i)
{
ImGuiUtil.DrawTableColumn($"Customize[{i:D2}]");
ImGuiUtil.DrawTableColumn(model.Customize.Data.Data[i].ToString());
ImGuiUtil.DrawTableColumn(model.Customize.Data.Data[i].ToString("X2"));
}
ImGuiUtil.DrawTableColumn("Race");
ImGuiUtil.DrawTableColumn(model.Customize.Race.ToString());
ImGui.TableNextColumn();
ImGuiUtil.DrawTableColumn("Clan");
ImGuiUtil.DrawTableColumn(model.Customize.Clan.ToString());
ImGui.TableNextColumn();
ImGuiUtil.DrawTableColumn("Gender");
ImGuiUtil.DrawTableColumn(model.Customize.Gender.ToString());
ImGui.TableNextColumn();
for (var i = 0; i < 10; ++i)
{
var slot = EquipSlotExtensions.EqdpSlots[i];
ImGuiUtil.DrawTableColumn($"Equipment[{i}] ({slot})");
var armor = model.Armor(slot);
ImGuiUtil.DrawTableColumn($"{armor.Set.Value}, {armor.Variant}, {armor.Stain.Value}");
ImGuiUtil.DrawTableColumn(armor.Value.ToString("X8"));
}
ImGuiUtil.DrawTableColumn("Mainhand");
ImGuiUtil.DrawTableColumn($"{model.MainHand.Set.Value}, {model.MainHand.Type.Value}, {model.MainHand.Variant}, {model.MainHand.Stain.Value}");
ImGuiUtil.DrawTableColumn(model.MainHand.Value.ToString("X16"));
ImGuiUtil.DrawTableColumn("Offhand");
ImGuiUtil.DrawTableColumn($"{model.OffHand.Set.Value}, {model.OffHand.Type.Value}, {model.OffHand.Variant}, {model.OffHand.Stain.Value}");
ImGuiUtil.DrawTableColumn(model.OffHand.Value.ToString("X16"));
}
}

View file

@ -1,64 +0,0 @@
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);
}
}

View file

@ -1,58 +0,0 @@
using System;
using System.Linq;
using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.Util;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
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;
Changed |= _customize.ChangeGender(CharacterEquip.Null, _customize.Gender is Gender.Male ? Gender.Female : Gender.Male, _items, _service.AwaitedService);
}
private void DrawRaceCombo()
{
ImGui.SetNextItemWidth(_raceSelectorWidth);
using var combo = ImRaii.Combo("##subRaceCombo", _customize.ClanName(_service.AwaitedService));
if (!combo)
return;
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
{
if (ImGui.Selectable(CustomizeExtensions.ClanName(_service.AwaitedService, subRace, _customize.Gender), subRace == _customize.Clan))
Changed |= _customize.ChangeRace(CharacterEquip.Null, subRace, _items, _service.AwaitedService);
}
}
}

View file

@ -1,150 +0,0 @@
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();
}
}
}

View file

@ -1,102 +0,0 @@
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;
}
}
}

View file

@ -1,164 +0,0 @@
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 bool _withFlags = false;
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;
private readonly ItemManager _items;
public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service, ItemManager items)
{
_service = service;
_items = items;
_legacyTattoo = GetLegacyTattooIcon(pi);
Customize = Customize.Default;
}
public void Dispose()
{
_legacyTattoo?.Dispose();
}
public bool Draw(Customize current, bool locked)
{
_withFlags = false;
CurrentFlag = CustomizeFlagExtensions.All;
Init(current, locked);
return DrawInternal();
}
public bool Draw(Customize current, CustomizeFlag currentFlags, bool locked)
{
_withFlags = true;
CurrentFlag = currentFlags;
Init(current, locked);
return DrawInternal();
}
private void Init(Customize current, bool locked)
{
UpdateSizes();
_terminate = null;
Changed = 0;
_customize.Load(current);
_locked = locked;
}
// Set state for drawing of current customization.
private CustomizeIndex _currentIndex;
private CustomizeFlag _currentFlag;
private CustomizeValue _currentByte = CustomizeValue.Zero;
private int _currentCount;
private string _currentOption = string.Empty;
// Prepare a new customization option.
private ImRaii.Id SetId(CustomizeIndex index)
{
_currentIndex = index;
_currentFlag = index.ToFlag();
_currentByte = _customize[index];
_currentCount = _set.Count(index, _customize.Face);
_currentOption = _set.Option(index);
return ImRaii.PushId((int)index);
}
// Update the current id with a new value.
private void UpdateValue(CustomizeValue value)
{
if (_currentByte == value)
return;
_customize[_currentIndex] = value;
Changed |= _currentFlag;
}
private bool DrawInternal()
{
using var disabled = ImRaii.Disabled(_locked);
try
{
DrawRaceGenderSelector();
_set = _service.AwaitedService.GetList(_customize.Clan, _customize.Gender);
foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage])
PercentageSelector(id);
CustomGui.IteratePairwise(_set.Order[CharaMakeParams.MenuType.IconSelector], DrawIconSelector, ImGui.SameLine);
DrawMultiIconSelector();
foreach (var id in _set.Order[CharaMakeParams.MenuType.ListSelector])
DrawListSelector(id);
CustomGui.IteratePairwise(_set.Order[CharaMakeParams.MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine);
CustomGui.IteratePairwise(_set.Order[CharaMakeParams.MenuType.Checkmark], DrawCheckbox,
() => ImGui.SameLine(_inputIntSize + _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X));
return Changed != 0;
}
catch (Exception ex)
{
_terminate = ex;
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF4040FF);
ImGui.NewLine();
ImGuiUtil.TextWrapped(_terminate.ToString());
return false;
}
}
private void UpdateSizes()
{
_iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2);
_framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
_inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X;
_comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
}
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi)
{
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
if (resource == null)
return null;
var rawImage = new byte[resource.Length];
var length = resource.Read(rawImage, 0, (int)resource.Length);
return length == resource.Length
? pi.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4)
: null;
}
}

View file

@ -1,59 +0,0 @@
using System.Numerics;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Interface;
using Glamourer.Designs;
using OtterGui;
using OtterGui.FileSystem.Selector;
namespace Glamourer.Gui.Designs;
public sealed class DesignFileSystemSelector : FileSystemSelector<Design, DesignFileSystemSelector.DesignState>
{
private readonly DesignManager _designManager;
public struct DesignState
{ }
public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, KeyState keyState)
: base(fileSystem, keyState)
{
_designManager = designManager;
_designManager.DesignChange += OnDesignChange;
AddButton(DeleteButton, 1000);
}
public override void Dispose()
{
base.Dispose();
_designManager.DesignChange -= OnDesignChange;
}
private void OnDesignChange(DesignManager.DesignChangeType type, Design design, object? oldData)
{
switch (type)
{
case DesignManager.DesignChangeType.ReloadedAll:
case DesignManager.DesignChangeType.Renamed:
case DesignManager.DesignChangeType.AddedTag:
case DesignManager.DesignChangeType.ChangedTag:
case DesignManager.DesignChangeType.RemovedTag:
SetFilterDirty();
break;
}
}
private void DeleteButton(Vector2 size)
{
var keys = true;
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.DeleteModModifier} while clicking to delete the mod.";
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true)
&& Selected != null)
_designManager.Delete(Selected);
}
}

View file

@ -1,368 +0,0 @@
using Dalamud.Game.ClientState.Keys;
using Glamourer.Designs;
using OtterGui.Filesystem;
using OtterGui.FileSystem.Selector;
namespace Glamourer.Gui.Designs;
//internal partial class Interface
//{
// private int _totalObject;
//
// private bool _inDesignMode;
// private Design? _selection;
// private string _newChildName = string.Empty;
//
// private void DrawDesignSelector()
// {
// _totalObject = 0;
// ImGui.BeginGroup();
// if (ImGui.BeginChild("##selector", new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
// {
// DrawFolderContent(_designs.FileSystem.Root, Glamourer.Config.FoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical);
// ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
// ImGui.EndChild();
// ImGui.PopStyleVar();
// }
//
// DrawDesignSelectorButtons();
// ImGui.EndGroup();
// }
//
// private void DrawPasteClipboardButton()
// {
// if (_selection!.Data.WriteProtected)
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
//
// ImGui.PushFont(UiBuilder.IconFont);
// var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString());
// ImGui.PopFont();
// if (_selection!.Data.WriteProtected)
// ImGui.PopStyleVar();
//
// ImGuiUtil.HoverTooltip("Overwrite with customization code from clipboard.");
//
// if (_selection!.Data.WriteProtected || !applyButton)
// return;
//
// var text = ImGui.GetClipboardText();
// if (!text.Any())
// return;
//
// try
// {
// _selection!.Data.Load(text, out _);
// _designs.SaveToFile();
// }
// catch (Exception e)
// {
// PluginLog.Information($"{e}");
// }
// }
//
// private void DrawNewFolderButton()
// {
// ImGui.PushFont(UiBuilder.IconFont);
// if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
// OpenDesignNamePopup(DesignNameUse.NewFolder);
// ImGui.PopFont();
// ImGuiUtil.HoverTooltip("Create a new, empty Folder.");
//
// DrawDesignNamePopup(DesignNameUse.NewFolder);
// }
//
// private void DrawNewDesignButton()
// {
// ImGui.PushFont(UiBuilder.IconFont);
// if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
// OpenDesignNamePopup(DesignNameUse.NewDesign);
// ImGui.PopFont();
// ImGuiUtil.HoverTooltip("Create a new, empty Design.");
//
// DrawDesignNamePopup(DesignNameUse.NewDesign);
// }
//
// private void DrawClipboardDesignButton()
// {
// ImGui.PushFont(UiBuilder.IconFont);
// if (ImGui.Button(FontAwesomeIcon.Paste.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
// OpenDesignNamePopup(DesignNameUse.FromClipboard);
// ImGui.PopFont();
// ImGuiUtil.HoverTooltip("Create a new design from the customization string in your clipboard.");
//
// DrawDesignNamePopup(DesignNameUse.FromClipboard);
// }
//
// private void DrawDeleteDesignButton()
// {
// ImGui.PushFont(UiBuilder.IconFont);
// var style = _selection == null;
// if (style)
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
// if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
// {
// _designs.DeleteAllChildren(_selection, false);
// _selection = null;
// }
//
// ImGui.PopFont();
// if (style)
// ImGui.PopStyleVar();
// ImGuiUtil.HoverTooltip("Delete the currently selected Design.");
// }
//
// private void DrawDuplicateDesignButton()
// {
// ImGui.PushFont(UiBuilder.IconFont);
// if (_selection == null)
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
// if (ImGui.Button(FontAwesomeIcon.Clone.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
// OpenDesignNamePopup(DesignNameUse.DuplicateDesign);
// ImGui.PopFont();
// if (_selection == null)
// ImGui.PopStyleVar();
// ImGuiUtil.HoverTooltip(
// "Clone the currently selected Design.\nHold Shift to only clone the customizations.\nHold Control to only clone the equipment.");
//
// DrawDesignNamePopup(DesignNameUse.DuplicateDesign);
// }
//
// private void DrawDesignSelectorButtons()
// {
// using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
// .Push(ImGuiStyleVar.FrameRounding, 0f);
//
// DrawNewFolderButton();
// ImGui.SameLine();
// DrawNewDesignButton();
// ImGui.SameLine();
// DrawClipboardDesignButton();
// ImGui.SameLine();
// DrawDuplicateDesignButton();
// ImGui.SameLine();
// DrawDeleteDesignButton();
// }
//
// private void DrawDesignHeaderButtons()
// {
// DrawCopyClipboardButton(_selection!.Data);
// ImGui.SameLine();
// DrawPasteClipboardButton();
// ImGui.SameLine();
// DrawApplyToPlayerButton(_selection!.Data);
// if (!_inGPose)
// {
// ImGui.SameLine();
// DrawApplyToTargetButton(_selection!.Data);
// }
//
// ImGui.SameLine();
// DrawCheckbox("Write Protected", _selection!.Data.WriteProtected, v => _selection!.Data.WriteProtected = v, false);
// }
//
// private void DrawDesignPanel()
// {
// if (ImGui.BeginChild("##details", -Vector2.One * 0.001f, true))
// {
// DrawDesignHeaderButtons();
// var data = _selection!.Data;
// var prot = _selection!.Data.WriteProtected;
// if (prot)
// {
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
// data = data.Copy();
// }
//
// DrawGeneralSettings(data, prot);
// var mask = data.WriteEquipment;
// if (DrawEquip(data.Equipment, ref mask) && !prot)
// {
// data.WriteEquipment = mask;
// _designs.SaveToFile();
// }
//
// if (DrawCustomization(ref data.Customizations) && !prot)
// _designs.SaveToFile();
//
// if (DrawMiscellaneous(data, null) && !prot)
// _designs.SaveToFile();
//
// if (prot)
// ImGui.PopStyleVar();
//
// ImGui.EndChild();
// }
// }
//
// private void DrawSaves()
// {
// using var style = ImRaii.PushStyle(ImGuiStyleVar.IndentSpacing, 12.5f * ImGui.GetIO().FontGlobalScale);
// using var tab = ImRaii.TabItem("Designs");
// _inDesignMode = tab.Success;
// if (!_inDesignMode)
// return;
//
// DrawDesignSelector();
//
// if (_selection != null)
// {
// ImGui.SameLine();
// DrawDesignPanel();
// }
// }
//
// private void DrawCheckbox(string label, bool value, Action<bool> setter, bool prot)
// {
// var tmp = value;
// if (ImGui.Checkbox(label, ref tmp) && tmp != value)
// {
// setter(tmp);
// if (!prot)
// _designs.SaveToFile();
// }
// }
//
// private void DrawGeneralSettings(CharacterSave data, bool prot)
// {
// ImGui.BeginGroup();
// DrawCheckbox("Apply Customizations", data.WriteCustomizations, v => data.WriteCustomizations = v, prot);
// DrawCheckbox("Write Weapon State", data.SetWeaponState, v => data.SetWeaponState = v, prot);
// ImGui.EndGroup();
// ImGui.SameLine();
// ImGui.BeginGroup();
// DrawCheckbox("Write Hat State", data.SetHatState, v => data.SetHatState = v, prot);
// DrawCheckbox("Write Visor State", data.SetVisorState, v => data.SetVisorState = v, prot);
// ImGui.EndGroup();
// }
//
// private void RenameChildInput(IFileSystemBase child)
// {
// ImGui.SetNextItemWidth(150);
// if (!ImGui.InputTextWithHint("##fsNewName", "Rename...", ref _newChildName, 64,
// ImGuiInputTextFlags.EnterReturnsTrue))
// return;
//
// if (_newChildName.Any() && _newChildName != child.Name)
// try
// {
// var oldPath = child.FullName();
// if (_designs.FileSystem.Rename(child, _newChildName))
// _designs.UpdateAllChildren(oldPath, child);
// }
// catch (Exception e)
// {
// PluginLog.Error($"Could not rename {child.Name} to {_newChildName}:\n{e}");
// }
// else if (child is Folder f)
// try
// {
// var oldPath = child.FullName();
// if (_designs.FileSystem.Merge(f, f.Parent, true))
// _designs.UpdateAllChildren(oldPath, f.Parent);
// }
// catch (Exception e)
// {
// PluginLog.Error($"Could not merge folder {child.Name} into parent:\n{e}");
// }
//
// _newChildName = string.Empty;
// }
//
// private void ContextMenu(IFileSystemBase child)
// {
// var label = $"##fsPopup{child.FullName()}";
// if (ImGui.BeginPopup(label))
// {
// if (ImGui.MenuItem("Delete") && ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift)
// _designs.DeleteAllChildren(child, false);
// ImGuiUtil.HoverTooltip("Hold Control and Shift to delete.");
//
// RenameChildInput(child);
//
// if (child is Design d && ImGui.MenuItem("Copy to Clipboard"))
// ImGui.SetClipboardText(d.Data.ToBase64());
//
// ImGui.EndPopup();
// }
//
// if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
// {
// _newChildName = child.Name;
// ImGui.OpenPopup(label);
// }
// }
//
// private static uint GetDesignColor(CharacterSave save)
// {
// const uint white = 0xFFFFFFFF;
// const uint grey = 0xFF808080;
// if (!Glamourer.Config.ColorDesigns)
// return white;
//
// var changesStates = save.SetHatState || save.SetVisorState || save.SetWeaponState || save.IsWet || save.Alpha != 1.0f;
// if (save.WriteCustomizations)
// if (save.WriteEquipment != CharacterEquipMask.None)
// return white;
// else
// return changesStates ? white : Glamourer.Config.CustomizationColor;
//
// if (save.WriteEquipment != CharacterEquipMask.None)
// return changesStates ? white : Glamourer.Config.EquipmentColor;
//
// return changesStates ? Glamourer.Config.StateColor : grey;
// }
//
// private void DrawFolderContent(Folder folder, SortMode mode)
// {
// foreach (var child in folder.AllChildren(mode).ToArray())
// {
// if (child.IsFolder(out var subFolder))
// {
// var treeNode = ImGui.TreeNodeEx($"{subFolder.Name}##{_totalObject}");
// DrawOrnaments(child);
//
// if (treeNode)
// {
// DrawFolderContent(subFolder, mode);
// ImGui.TreePop();
// }
// else
// {
// _totalObject += subFolder.TotalDescendantLeaves();
// }
// }
// else
// {
// if (child is not Design d)
// continue;
//
// ++_totalObject;
// var color = GetDesignColor(d.Data);
// using var c = ImRaii.PushColor(ImGuiCol.Text, color);
//
// var selected = ImGui.Selectable($"{child.Name}##{_totalObject}", ReferenceEquals(child, _selection));
// c.Pop();
// DrawOrnaments(child);
//
// if (Glamourer.Config.ShowLocks && d.Data.WriteProtected)
// {
// ImGui.SameLine();
// using var font = ImRaii.PushFont(UiBuilder.IconFont);
// c.Push(ImGuiCol.Text, color);
// ImGui.TextUnformatted(FontAwesomeIcon.Lock.ToIconString());
// }
//
// if (selected)
// _selection = d;
// }
// }
// }
//
// private void DrawOrnaments(IFileSystemBase child)
// {
// FileSystemImGui.DragDropSource(child);
// if (FileSystemImGui.DragDropTarget(_designs.FileSystem, child, out var oldPath, out var draggedFolder))
// _designs.UpdateAllChildren(oldPath, draggedFolder!);
// ContextMenu(child);
// }
//}

View file

@ -1,187 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Dalamud.Data;
using Dalamud.Interface;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using OtterGui.Widgets;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment;
public class EquipmentDrawer
{
private readonly ItemManager _items;
private readonly FilterComboColors _stainCombo;
private readonly StainData _stainData;
private readonly ItemCombo[] _itemCombo;
private readonly Dictionary<(FullEquipType, EquipSlot), WeaponCombo> _weaponCombo;
public EquipmentDrawer(DataManager gameData, ItemManager items)
{
_items = items;
_stainData = items.Stains;
_stainCombo = new FilterComboColors(140,
_stainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))));
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e)).ToArray();
_weaponCombo = new Dictionary<(FullEquipType, EquipSlot), WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
foreach (var type in Enum.GetValues<FullEquipType>())
{
if (type.ToSlot() is EquipSlot.MainHand)
_weaponCombo.TryAdd((type, EquipSlot.MainHand), new WeaponCombo(gameData, items, type, EquipSlot.MainHand));
else if (type.ToSlot() is EquipSlot.OffHand)
_weaponCombo.TryAdd((type, EquipSlot.OffHand), new WeaponCombo(gameData, items, type, EquipSlot.OffHand));
var offhand = type.Offhand();
if (offhand is not FullEquipType.Unknown && !_weaponCombo.ContainsKey((offhand, EquipSlot.OffHand)))
_weaponCombo.TryAdd((offhand, EquipSlot.OffHand), new WeaponCombo(gameData, items, type, EquipSlot.OffHand));
}
_weaponCombo.Add((FullEquipType.Unknown, EquipSlot.MainHand), new WeaponCombo(gameData, items, FullEquipType.Unknown, EquipSlot.MainHand));
}
private string VerifyRestrictedGear(Item gear, EquipSlot slot, Gender gender, Race race)
{
if (slot.IsAccessory())
return gear.Name;
var (changed, _) = _items.ResolveRestrictedGear(gear.Model, slot, race, gender);
if (changed)
return gear.Name + " (Restricted)";
return gear.Name;
}
public bool DrawArmor(Item current, EquipSlot slot, out Item armor, Gender gender = Gender.Unknown, Race race = Race.Unknown)
{
Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(DrawArmor)} on {slot}.");
var combo = _itemCombo[slot.ToIndex()];
armor = current;
var change = combo.Draw(VerifyRestrictedGear(armor, slot, gender, race), armor.ItemId, 320 * ImGuiHelpers.GlobalScale);
if (armor.ModelBase.Value != 0)
{
ImGuiUtil.HoverTooltip("Right-click to clear.");
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
change = true;
armor = ItemManager.NothingItem(slot);
}
else if (change)
{
armor = combo.CurrentSelection.WithStain(armor.Stain);
}
}
else if (change)
{
armor = combo.CurrentSelection.WithStain(armor.Stain);
}
return change;
}
public bool DrawStain(StainId current, EquipSlot slot, out Stain stain)
{
var found = _stainData.TryGetValue(current, out stain);
if (!_stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found))
return false;
return _stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain);
}
public bool DrawMainhand(Weapon current, bool drawAll, out Weapon weapon)
{
weapon = current;
if (!_weaponCombo.TryGetValue((drawAll ? FullEquipType.Unknown : current.Type, EquipSlot.MainHand), out var combo))
return false;
if (!combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale))
return false;
weapon = combo.CurrentSelection.WithStain(current.Stain);
return true;
}
public bool DrawOffhand(Weapon current, FullEquipType mainType, out Weapon weapon)
{
weapon = current;
var offType = mainType.Offhand();
if (offType == FullEquipType.Unknown)
return false;
if (!_weaponCombo.TryGetValue((offType, EquipSlot.OffHand), out var combo))
return false;
var change = combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale);
if (offType.ToSlot() is EquipSlot.OffHand && weapon.ModelBase.Value != 0)
{
ImGuiUtil.HoverTooltip("Right-click to clear.");
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
change = true;
weapon = ItemManager.NothingItem(offType);
}
}
else if (change)
{
weapon = combo.CurrentSelection.WithStain(current.Stain);
}
return change;
}
public bool DrawApply(Design design, EquipSlot slot, out bool enabled)
=> DrawCheckbox($"##apply{slot}", design.DoApplyEquip(slot), out enabled);
public bool DrawApplyStain(Design design, EquipSlot slot, out bool enabled)
=> DrawCheckbox($"##applyStain{slot}", design.DoApplyStain(slot), out enabled);
private static bool DrawCheckbox(string label, bool value, out bool on)
{
var ret = ImGuiUtil.Checkbox(label, string.Empty, value, v => value = v);
on = value;
return ret;
}
public bool DrawVisor(Design design, out bool on)
=> DrawCheckbox("##visorToggled", design.Visor.ForcedValue, out on);
public bool DrawVisor(ActiveDesign design, out bool on)
=> DrawCheckbox("##visorToggled", design.IsVisorToggled, out on);
public bool DrawHat(Design design, out bool on)
=> DrawCheckbox("##hatVisible", design.Hat.ForcedValue, out on);
public bool DrawHat(ActiveDesign design, out bool on)
=> DrawCheckbox("##hatVisible", design.IsHatVisible, out on);
public bool DrawWeapon(Design design, out bool on)
=> DrawCheckbox("##weaponVisible", design.Weapon.ForcedValue, out on);
public bool DrawWeapon(ActiveDesign design, out bool on)
=> DrawCheckbox("##weaponVisible", design.IsWeaponVisible, out on);
public bool DrawWetness(Design design, out bool on)
=> DrawCheckbox("##wetness", design.Wetness.ForcedValue, out on);
public bool DrawWetness(ActiveDesign design, out bool on)
=> DrawCheckbox("##wetnessVisible", design.IsWet, out on);
public bool DrawApplyVisor(Design design, out bool on)
=> DrawCheckbox("##applyVisor", design.Visor.Enabled, out on);
public bool DrawApplyWetness(Design design, out bool on)
=> DrawCheckbox("##applyWetness", design.Wetness.Enabled, out on);
public bool DrawApplyHatState(Design design, out bool on)
=> DrawCheckbox("##applyHatState", design.Hat.Enabled, out on);
public bool DrawApplyWeaponState(Design design, out bool on)
=> DrawCheckbox("##applyWeaponState", design.Weapon.Enabled, out on);
}

View file

@ -1,105 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Data;
using Glamourer.Designs;
using Glamourer.Services;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.GameData.Enums;
using Item = Glamourer.Designs.Item;
namespace Glamourer.Gui.Equipment;
public sealed class ItemCombo : FilterComboCache<Item>
{
public readonly string Label;
private uint _currentItem;
public ItemCombo(DataManager gameData, ItemManager items, EquipSlot slot)
: base(() => GetItems(items, slot))
{
Label = GetLabel(gameData, slot);
_currentItem = ItemManager.NothingId(slot);
}
protected override void DrawList(float width, float itemHeight)
{
base.DrawList(width, itemHeight);
if (NewSelection != null && Items.Count > NewSelection.Value)
CurrentSelection = Items[NewSelection.Value];
}
protected override int UpdateCurrentSelected(int currentSelected)
{
if (CurrentSelection.ItemId != _currentItem)
{
CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem);
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
return base.UpdateCurrentSelected(CurrentSelectionIdx);
}
return currentSelected;
}
public bool Draw(string previewName, uint previewIdx, float width)
{
_currentItem = previewIdx;
return Draw(Label, previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
}
protected override bool DrawSelectable(int globalIdx, bool selected)
{
var obj = Items[globalIdx];
var name = ToString(obj);
var ret = ImGui.Selectable(name, selected);
ImGui.SameLine();
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
ImGuiUtil.RightAlign($"({obj.ModelBase.Value}-{obj.Variant})");
return ret;
}
protected override bool IsVisible(int globalIndex, LowerString filter)
=> base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelBase.ToString());
protected override string ToString(Item obj)
=> obj.Name;
private static string GetLabel(DataManager gameData, EquipSlot slot)
{
var sheet = gameData.GetExcelSheet<Addon>()!;
return slot switch
{
EquipSlot.Head => sheet.GetRow(740)?.Text.ToString() ?? "Head",
EquipSlot.Body => sheet.GetRow(741)?.Text.ToString() ?? "Body",
EquipSlot.Hands => sheet.GetRow(742)?.Text.ToString() ?? "Hands",
EquipSlot.Legs => sheet.GetRow(744)?.Text.ToString() ?? "Legs",
EquipSlot.Feet => sheet.GetRow(745)?.Text.ToString() ?? "Feet",
EquipSlot.Ears => sheet.GetRow(746)?.Text.ToString() ?? "Ears",
EquipSlot.Neck => sheet.GetRow(747)?.Text.ToString() ?? "Neck",
EquipSlot.Wrists => sheet.GetRow(748)?.Text.ToString() ?? "Wrists",
EquipSlot.RFinger => sheet.GetRow(749)?.Text.ToString() ?? "Right Ring",
EquipSlot.LFinger => sheet.GetRow(750)?.Text.ToString() ?? "Left Ring",
_ => string.Empty,
};
}
private static IReadOnlyList<Item> GetItems(ItemManager items, EquipSlot slot)
{
var nothing = ItemManager.NothingItem(slot);
if (!items.ItemService.AwaitedService.TryGetValue(slot.ToEquipType(), out var list))
return new[]
{
nothing,
};
var enumerable = list.Select(i => new Item(i));
if (slot.IsEquipment())
enumerable = enumerable.Append(ItemManager.SmallClothesItem(slot));
return enumerable.OrderBy(i => i.Name).Prepend(nothing).ToList();
}
}

View file

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

View file

@ -1,30 +0,0 @@
using System;
using Dalamud.Interface;
using Dalamud.Interface.Windowing;
namespace Glamourer.Gui;
public class GlamourerWindowSystem : IDisposable
{
private readonly WindowSystem _windowSystem = new("Glamourer");
private readonly UiBuilder _uiBuilder;
private readonly Interface _ui;
public GlamourerWindowSystem(UiBuilder uiBuilder, Interface ui)
{
_uiBuilder = uiBuilder;
_ui = ui;
_windowSystem.AddWindow(ui);
_uiBuilder.Draw += _windowSystem.Draw;
_uiBuilder.OpenConfigUi += _ui.Toggle;
}
public void Dispose()
{
_uiBuilder.Draw -= _windowSystem.Draw;
_uiBuilder.OpenConfigUi -= _ui.Toggle;
}
public void Toggle()
=> _ui.Toggle();
}

View file

@ -1,291 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using ImGui = ImGuiNET.ImGui;
namespace Glamourer.Gui;
public partial class Interface
{
private class ActorTab
{
private readonly Interface _main;
private readonly ActiveDesign.Manager _activeDesigns;
private readonly ObjectManager _objects;
private readonly TargetManager _targets;
private readonly ActorService _actors;
private readonly ItemManager _items;
public ActorTab(Interface main, ActiveDesign.Manager activeDesigns, ObjectManager objects, TargetManager targets, ActorService actors,
ItemManager items)
{
_main = main;
_activeDesigns = activeDesigns;
_objects = objects;
_targets = targets;
_actors = actors;
_items = items;
}
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
private ActorData _currentData = ActorData.Invalid;
private ActiveDesign? _currentSave;
public void Draw()
{
using var tab = ImRaii.TabItem("Actors");
if (!tab)
return;
DrawActorSelector();
if (!_objects.TryGetValue(_identifier, out _currentData))
_currentData = ActorData.Invalid;
ImGui.SameLine();
DrawPanel();
}
private unsafe void DrawPanel()
{
if (_identifier == ActorIdentifier.Invalid)
return;
using var group = ImRaii.Group();
DrawPanelHeader();
using var child = ImRaii.Child("##ActorPanel", -Vector2.One, true);
if (!child || _currentSave == null)
return;
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 const uint RedHeaderColor = 0xFF1818C0;
private const uint GreenHeaderColor = 0xFF18C018;
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 DrawPanelHeader()
{
var color = _currentData.Valid ? GreenHeaderColor : RedHeaderColor;
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($"{_currentData.Label}##playerHeader", -Vector2.UnitX);
}
//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);
// }
//}
private LowerString _actorFilter = LowerString.Empty;
private void DrawActorSelector()
{
using var group = ImRaii.Group();
var oldSpacing = ImGui.GetStyle().ItemSpacing;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
ImGui.SetNextItemWidth(_actorSelectorWidth);
LowerString.InputWithHint("##actorFilter", "Filter...", ref _actorFilter, 64);
DrawSelector(oldSpacing);
DrawSelectionButtons();
}
private void DrawSelector(Vector2 oldSpacing)
{
using var child = ImRaii.Child("##actorSelector", new Vector2(_actorSelectorWidth, -ImGui.GetFrameHeight()), true);
if (!child)
return;
_objects.Update();
if (!_activeDesigns.TryGetValue(_identifier, out _currentSave))
_currentSave = null;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
var remainder = ImGuiClip.FilteredClippedDraw(_objects, skips, CheckFilter, DrawSelectable);
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
if (_currentSave == null)
{
_identifier = ActorIdentifier.Invalid;
_currentData = ActorData.Invalid;
}
}
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 equal = pair.Key.Equals(_identifier);
if (ImGui.Selectable(pair.Value.Label, equal) || equal)
{
_identifier = pair.Key.CreatePermanent();
_currentData = pair.Value;
if (!_activeDesigns.TryGetValue(_identifier, out _currentSave))
_currentSave = _currentData.Valid ? _activeDesigns.GetOrCreateSave(_currentData.Objects[0]) : null;
}
}
private void DrawSelectionButtons()
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
var buttonWidth = new Vector2(_actorSelectorWidth / 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);
}
}
}

View file

@ -1,55 +0,0 @@
using System;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.Util;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
namespace Glamourer.Gui;
public partial class Interface
{
private class DebugDataTab
{
private readonly CustomizationService _service;
public DebugDataTab(CustomizationService service)
=> _service = service;
public void Draw()
{
if (!_service.Valid)
return;
using var tab = ImRaii.TabItem("Debug");
if (!tab)
return;
foreach (var clan in _service.AwaitedService.Clans)
{
foreach (var gender in _service.AwaitedService.Genders)
DrawCustomizationInfo(_service.AwaitedService.GetList(clan, gender));
}
}
public void DrawCustomizationInfo(CustomizationSet set)
{
if (!ImGui.CollapsingHeader($"{CustomizeExtensions.ClanName(_service.AwaitedService, set.Clan, set.Gender)} {set.Gender}"))
return;
using var table = ImRaii.Table("data", 5);
if (!table)
return;
foreach (var index in Enum.GetValues<CustomizeIndex>())
{
ImGuiUtil.DrawTableColumn(index.ToString());
ImGuiUtil.DrawTableColumn(set.Option(index));
ImGuiUtil.DrawTableColumn(set.IsAvailable(index) ? "Available" : "Unavailable");
ImGuiUtil.DrawTableColumn(set.Type(index).ToString());
ImGuiUtil.DrawTableColumn(set.Count(index).ToString());
}
}
}
}

View file

@ -1,98 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using Penumbra.GameData.Actors;
namespace Glamourer.Gui;
public partial class Interface
{
private class DebugStateTab
{
private readonly ActiveDesign.Manager _activeDesigns;
private LowerString _manipulationFilter = LowerString.Empty;
private ActorIdentifier _selection = ActorIdentifier.Invalid;
private ActiveDesign? _save = null;
private bool _delete = false;
public DebugStateTab(ActiveDesign.Manager activeDesigns)
=> _activeDesigns = activeDesigns;
[Conditional("DEBUG")]
public void Draw()
{
using var tab = ImRaii.TabItem("Current Manipulations");
if (!tab)
return;
DrawManipulationSelector();
if (_save == null)
return;
ImGui.SameLine();
DrawActorPanel();
if (_delete)
{
_delete = false;
_activeDesigns.DeleteSave(_selection);
_selection = ActorIdentifier.Invalid;
}
}
private void DrawSelector(Vector2 oldSpacing)
{
using var child = ImRaii.Child("##actorSelector", new Vector2(_actorSelectorWidth, -1), true);
if (!child)
return;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
var remainder = ImGuiClip.FilteredClippedDraw(_activeDesigns, skips, CheckFilter, DrawSelectable);
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
}
private void DrawManipulationSelector()
{
using var group = ImRaii.Group();
var oldSpacing = ImGui.GetStyle().ItemSpacing;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
ImGui.SetNextItemWidth(_actorSelectorWidth);
LowerString.InputWithHint("##actorFilter", "Filter...", ref _manipulationFilter, 64);
_save = null;
DrawSelector(oldSpacing);
}
private bool CheckFilter(KeyValuePair<ActorIdentifier, ActiveDesign> data)
{
if (data.Key.Equals(_selection))
_save = data.Value;
return _manipulationFilter.Length == 0 || _manipulationFilter.IsContained(data.Key.ToString()!);
}
private void DrawSelectable(KeyValuePair<ActorIdentifier, ActiveDesign> data)
{
var equal = data.Key.Equals(_selection);
if (ImGui.Selectable(data.Key.ToString(), equal))
{
_selection = data.Key;
_save = data.Value;
}
}
private void DrawActorPanel()
{
using var group = ImRaii.Group();
if (ImGui.Button("Delete"))
_delete = true;
}
}
}

View file

@ -1,101 +0,0 @@
using System;
using System.Numerics;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Gui.Designs;
using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui;
public partial class Interface
{
private class DesignTab : IDisposable
{
public readonly DesignFileSystemSelector Selector;
private readonly Interface _main;
private readonly DesignFileSystem _fileSystem;
private readonly DesignManager _designManager;
private readonly ActiveDesign.Manager _activeDesignManager;
private readonly ObjectManager _objects;
public DesignTab(Interface main, DesignManager designManager, DesignFileSystem fileSystem, KeyState keyState,
ActiveDesign.Manager activeDesignManager, ObjectManager objects)
{
_main = main;
_designManager = designManager;
_fileSystem = fileSystem;
_activeDesignManager = activeDesignManager;
_objects = objects;
Selector = new DesignFileSystemSelector(designManager, fileSystem, keyState);
}
public void Dispose()
=> Selector.Dispose();
public void Draw()
{
using var tab = ImRaii.TabItem("Designs");
if (!tab)
return;
Selector.Draw(GetDesignSelectorSize());
ImGui.SameLine();
DrawDesignPanel();
}
public float GetDesignSelectorSize()
=> 200f * ImGuiHelpers.GlobalScale;
private void ApplySelfButton()
{
var self = _objects.Player;
if (!ImGuiUtil.DrawDisabledButton("Apply to Self", Vector2.Zero, string.Empty, !self.Valid))
return;
var design = _activeDesignManager.GetOrCreateSave(self);
_activeDesignManager.ApplyDesign(design, Selector.Selected!, false);
}
public void DrawDesignPanel()
{
if (Selector.Selected == null)
return;
using var group = ImRaii.Group();
ApplySelfButton();
using var child = ImRaii.Child("##DesignPanel", new Vector2(-0.001f), true, ImGuiWindowFlags.HorizontalScrollbar);
if (!child)
return;
ActorDebug.Draw(Selector.Selected.ModelData);
_main._customizationDrawer.Draw(Selector.Selected.ModelData.Customize, CustomizeFlagExtensions.All, true);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var current = Selector.Selected.Armor(slot);
_main._equipmentDrawer.DrawStain(current.Stain, slot, out var stain);
ImGui.SameLine();
_main._equipmentDrawer.DrawArmor(current, slot, out var armor);
}
var currentMain = Selector.Selected.WeaponMain;
_main._equipmentDrawer.DrawStain(currentMain.Stain, EquipSlot.MainHand, out var stainMain);
ImGui.SameLine();
_main._equipmentDrawer.DrawMainhand(currentMain, true, out var main);
if (currentMain.Type.Offhand() != FullEquipType.Unknown)
{
var currentOff = Selector.Selected.WeaponOff;
_main._equipmentDrawer.DrawStain(currentOff.Stain, EquipSlot.OffHand, out var stainOff);
ImGui.SameLine();
_main._equipmentDrawer.DrawOffhand(currentOff, main.Type, out var off);
}
}
}
}

View file

@ -1,84 +0,0 @@
using System;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
namespace Glamourer.Gui;
public partial class Interface
{
private void Checkmark(string label, string tooltip, bool value, Action<bool> setter)
{
if (ImGuiUtil.Checkbox(label, tooltip, value, setter))
_config.Save();
}
private void ChangeAndSave<T>(T value, T currentValue, Action<T> setter) where T : IEquatable<T>
{
if (value.Equals(currentValue))
return;
setter(value);
_config.Save();
}
private void DrawColorPicker(string name, string tooltip, uint value, uint defaultValue, Action<uint> setter)
{
const ImGuiColorEditFlags flags = ImGuiColorEditFlags.AlphaPreviewHalf | ImGuiColorEditFlags.NoInputs;
var tmp = ImGui.ColorConvertU32ToFloat4(value);
if (ImGui.ColorEdit4($"##{name}", ref tmp, flags))
ChangeAndSave(ImGui.ColorConvertFloat4ToU32(tmp), value, setter);
ImGui.SameLine();
if (ImGui.Button($"Default##{name}"))
ChangeAndSave(defaultValue, value, setter);
ImGuiUtil.HoverTooltip(
$"Reset to default: #{defaultValue & 0xFF:X2}{(defaultValue >> 8) & 0xFF:X2}{(defaultValue >> 16) & 0xFF:X2}{defaultValue >> 24:X2}");
ImGui.SameLine();
ImGui.Text(name);
ImGuiUtil.HoverTooltip(tooltip);
}
private static void DrawRestorePenumbraButton()
{
//const string buttonLabel = "Re-Register Penumbra";
// TODO
//if (ImGui.Button(buttonLabel))
// Glamourer.Penumbra.Reattach(true);
//ImGuiUtil.HoverTooltip(
// "If Penumbra did not register the functions for some reason, pressing this button might help restore functionality.");
}
private void DrawSettingsTab()
{
using var tab = ImRaii.TabItem("Settings");
if (!tab)
return;
ImGui.Dummy(_spacing);
Checkmark("Folders First", "Sort Folders before all designs instead of lexicographically.", _config.FoldersFirst,
v => _config.FoldersFirst = v);
Checkmark("Color Designs", "Color the names of designs in the selector using the colors from below for the given cases.",
_config.ColorDesigns,
v => _config.ColorDesigns = v);
Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", _config.ShowLocks,
v => _config.ShowLocks = v);
DrawRestorePenumbraButton();
Checkmark("Apply Fixed Designs",
"Automatically apply fixed designs to characters and redraw them when anything changes.",
_config.ApplyFixedDesigns,
v => { _config.ApplyFixedDesigns = v; });
ImGui.Dummy(_spacing);
DrawColorPicker("Customization Color", "The color for designs that only apply their character customization.",
_config.CustomizationColor, ConfigurationOld.DefaultCustomizationColor, c => _config.CustomizationColor = c);
DrawColorPicker("Equipment Color", "The color for designs that only apply some or all of their equipment slots and stains.",
_config.EquipmentColor, ConfigurationOld.DefaultEquipmentColor, c => _config.EquipmentColor = c);
DrawColorPicker("State Color", "The color for designs that only apply some state modification.",
_config.StateColor, ConfigurationOld.DefaultStateColor, c => _config.StateColor = c);
}
}

View file

@ -1,17 +0,0 @@
using System.Numerics;
using Dalamud.Interface;
using ImGuiNET;
namespace Glamourer.Gui;
public partial class Interface
{
private static Vector2 _spacing = Vector2.Zero;
private static float _actorSelectorWidth;
private static void UpdateState()
{
_spacing = _spacing with { Y = ImGui.GetTextLineHeightWithSpacing() / 2 };
_actorSelectorWidth = 200 * ImGuiHelpers.GlobalScale;
}
}

View file

@ -1,88 +0,0 @@
using System;
using System.Numerics;
using Dalamud.Data;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.State;
using ImGuiNET;
using OtterGui.Raii;
namespace Glamourer.Gui;
public partial class Interface : Window, IDisposable
{
private readonly DalamudPluginInterface _pi;
private readonly EquipmentDrawer _equipmentDrawer;
private readonly CustomizationDrawer _customizationDrawer;
private readonly ConfigurationOld _config;
private readonly ActorTab _actorTab;
private readonly DesignTab _designTab;
private readonly DebugStateTab _debugStateTab;
private readonly DebugDataTab _debugDataTab;
public Interface(DalamudPluginInterface pi, ItemManager items, ActiveDesign.Manager activeDesigns, DesignManager designManager,
DesignFileSystem fileSystem, ObjectManager objects, CustomizationService customization, ConfigurationOld config, DataManager gameData, TargetManager targets, ActorService actors, KeyState keyState)
: base(GetLabel())
{
_pi = pi;
_config = config;
_equipmentDrawer = new EquipmentDrawer(gameData, items);
_customizationDrawer = new CustomizationDrawer(pi, customization, items);
pi.UiBuilder.DisableGposeUiHide = true;
SizeConstraints = new WindowSizeConstraints()
{
MinimumSize = new Vector2(675, 675),
MaximumSize = ImGui.GetIO().DisplaySize,
};
_actorTab = new ActorTab(this, activeDesigns, objects, targets, actors, items);
_debugStateTab = new DebugStateTab(activeDesigns);
_debugDataTab = new DebugDataTab(customization);
_designTab = new DesignTab(this, designManager, fileSystem, keyState, activeDesigns, objects);
}
public override void Draw()
{
using var tabBar = ImRaii.TabBar("##Tabs");
if (!tabBar)
return;
try
{
UpdateState();
_actorTab.Draw();
_designTab.Draw();
DrawSettingsTab();
_debugStateTab.Draw();
_debugDataTab.Draw();
// DrawSaves();
// DrawFixedDesignsTab();
// DrawRevertablesTab();
}
catch (Exception e)
{
PluginLog.Error($"Unexpected Error during Draw:\n{e}");
}
}
public void Dispose()
{
_pi.UiBuilder.OpenConfigUi -= Toggle;
_customizationDrawer.Dispose();
_designTab.Dispose();
}
private static string GetLabel()
=> Glamourer.Version.Length == 0
? "Glamourer###GlamourerConfigWindow"
: $"Glamourer v{Glamourer.Version}###GlamourerConfigWindow";
}

View file

@ -1,290 +0,0 @@

namespace Glamourer.Gui;
public partial class Interface
{
//private readonly CharacterSave _currentSave = new();
//private string _newDesignName = string.Empty;
//private bool _keyboardFocus;
//private bool _holdShift;
//private bool _holdCtrl;
//private const string DesignNamePopupLabel = "Save Design As...";
//
//private void DrawPlayerHeader()
//{
// var color = _player == null ? RedHeaderColor : GreenHeaderColor;
// var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
// using var c = ImRaii.PushColor(ImGuiCol.Text, color)
// .Push(ImGuiCol.Button, buttonColor)
// .Push(ImGuiCol.ButtonHovered, buttonColor)
// .Push(ImGuiCol.ButtonActive, buttonColor);
// using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
// .Push(ImGuiStyleVar.FrameRounding, 0);
// ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f);
//}
//
//private static void DrawCopyClipboardButton(CharacterSave save)
//{
// ImGui.PushFont(UiBuilder.IconFont);
// if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString()))
// ImGui.SetClipboardText(save.ToBase64());
// ImGui.PopFont();
// ImGuiUtil.HoverTooltip("Copy customization code to clipboard.");
//}
//
//private static void ConditionalApply(CharacterSave save, Character player)
//{
// if (ImGui.GetIO().KeyShift)
// save.ApplyOnlyCustomizations(player);
// else if (ImGui.GetIO().KeyCtrl)
// save.ApplyOnlyEquipment(player);
// else
// save.Apply(player);
//}
//
//private static CharacterSave ConditionalCopy(CharacterSave save, bool shift, bool ctrl)
//{
// var copy = save.Copy();
// if (shift)
// {
// copy.Load(new CharacterEquipment());
// copy.SetHatState = false;
// copy.SetVisorState = false;
// copy.SetWeaponState = false;
// copy.WriteEquipment = CharacterEquipMask.None;
// }
// else if (ctrl)
// {
// copy.Load(CharacterCustomization.Default);
// copy.SetHatState = false;
// copy.SetVisorState = false;
// copy.SetWeaponState = false;
// copy.WriteCustomizations = false;
// }
//
// return copy;
//}
//
//private bool DrawApplyClipboardButton()
//{
// ImGui.PushFont(UiBuilder.IconFont);
// var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()) && _player != null;
// ImGui.PopFont();
// ImGuiUtil.HoverTooltip(
// "Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment.");
//
// if (!applyButton)
// return false;
//
// try
// {
// var text = ImGui.GetClipboardText();
// if (!text.Any())
// return false;
//
// var save = CharacterSave.FromString(text);
// ConditionalApply(save, _player!);
// }
// catch (Exception e)
// {
// PluginLog.Information($"{e}");
// return false;
// }
//
// return true;
//}
//
//private void DrawSaveDesignButton()
//{
// ImGui.PushFont(UiBuilder.IconFont);
// if (ImGui.Button(FontAwesomeIcon.Save.ToIconString()))
// OpenDesignNamePopup(DesignNameUse.SaveCurrent);
//
// ImGui.PopFont();
// ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment.");
//
// DrawDesignNamePopup(DesignNameUse.SaveCurrent);
//}
//
//private void DrawTargetPlayerButton()
//{
// if (ImGui.Button("Target Player"))
// Dalamud.Targets.SetTarget(_player);
//}
//
//private void DrawApplyToPlayerButton(CharacterSave save)
//{
// if (!ImGui.Button("Apply to Self"))
// return;
//
// var player = _inGPose
// ? (Character?)Dalamud.Objects[GPoseObjectId]
// : Dalamud.ClientState.LocalPlayer;
// var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null;
// if (player == null)
// return;
//
// ConditionalApply(save, player);
// if (_inGPose)
// ConditionalApply(save, fallback!);
// Glamourer.Penumbra.UpdateCharacters(player, fallback);
//}
//
//
//private static Character? TransformToCustomizable(Character? actor)
//{
// if (actor == null)
// return null;
//
// if (actor.ModelType() == 0)
// return actor;
//
// actor.SetModelType(0);
// CharacterCustomization.Default.Write(actor.Address);
// return actor;
//}
//
//private void DrawApplyToTargetButton(CharacterSave save)
//{
// if (!ImGui.Button("Apply to Target"))
// return;
//
// var player = TransformToCustomizable(CharacterFactory.Convert(Dalamud.Targets.Target));
// if (player == null)
// return;
//
// var fallBackCharacter = _gPoseActors.TryGetValue(player.Name.ToString(), out var f) ? f : null;
// ConditionalApply(save, player);
// if (fallBackCharacter != null)
// ConditionalApply(save, fallBackCharacter!);
// Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter);
//}
//
//private void DrawRevertButton()
//{
// if (!ImGuiUtil.DrawDisabledButton("Revert", Vector2.Zero, string.Empty, _player == null))
// return;
//
// Glamourer.RevertableDesigns.Revert(_player!);
// var fallBackCharacter = _gPoseActors.TryGetValue(_player!.Name.ToString(), out var f) ? f : null;
// if (fallBackCharacter != null)
// Glamourer.RevertableDesigns.Revert(fallBackCharacter);
// Glamourer.Penumbra.UpdateCharacters(_player, fallBackCharacter);
//}
//
//private void SaveNewDesign(CharacterSave save)
//{
// try
// {
// var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName);
// if (!name.Any())
// return;
//
// var newDesign = new Design(folder, name) { Data = save };
// folder.AddChild(newDesign);
// _designs.Designs[newDesign.FullName()] = save;
// _designs.SaveToFile();
// }
// catch (Exception e)
// {
// PluginLog.Error($"Could not save new design {_newDesignName}:\n{e}");
// }
//}
//
//private void DrawMonsterPanel()
//{
// if (DrawApplyClipboardButton())
// Glamourer.Penumbra.UpdateCharacters(_player!);
//
// ImGui.SameLine();
// if (ImGui.Button("Convert to Character"))
// {
// TransformToCustomizable(_player);
// _currentLabel = _currentLabel.Replace("(Monster)", "(NPC)");
// Glamourer.Penumbra.UpdateCharacters(_player!);
// }
//
// if (!_inGPose)
// {
// ImGui.SameLine();
// DrawTargetPlayerButton();
// }
//
// var currentModel = _player!.ModelType();
// using var combo = ImRaii.Combo("Model Id", currentModel.ToString());
// if (!combo)
// return;
//
// foreach (var (id, _) in _models.Skip(1))
// {
// if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel)
// continue;
//
// _player!.SetModelType((int)id);
// Glamourer.Penumbra.UpdateCharacters(_player!);
// }
//}
//
//private void DrawPlayerPanel()
//{
// DrawCopyClipboardButton(_currentSave);
// ImGui.SameLine();
// var changes = !_currentSave.WriteProtected && DrawApplyClipboardButton();
// ImGui.SameLine();
// DrawSaveDesignButton();
// ImGui.SameLine();
// DrawApplyToPlayerButton(_currentSave);
// if (!_inGPose)
// {
// ImGui.SameLine();
// DrawApplyToTargetButton(_currentSave);
// if (_player != null && !_currentSave.WriteProtected)
// {
// ImGui.SameLine();
// DrawTargetPlayerButton();
// }
// }
//
// var data = _currentSave;
// if (!_currentSave.WriteProtected)
// {
// ImGui.SameLine();
// DrawRevertButton();
// }
// else
// {
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
// data = data.Copy();
// }
//
// if (DrawCustomization(ref data.Customizations) && _player != null)
// {
// Glamourer.RevertableDesigns.Add(_player);
// _currentSave.Customizations.Write(_player.Address);
// changes = true;
// }
//
// changes |= DrawEquip(data.Equipment);
// changes |= DrawMiscellaneous(data, _player);
//
// if (_player != null && changes)
// Glamourer.Penumbra.UpdateCharacters(_player);
// if (_currentSave.WriteProtected)
// ImGui.PopStyleVar();
//}
//
//private void DrawActorPanel()
//{
// using var group = ImRaii.Group();
// DrawPlayerHeader();
// using var child = ImRaii.Child("##playerData", -Vector2.One, true);
// if (!child)
// return;
//
// if (_player == null || _player.ModelType() == 0)
// DrawPlayerPanel();
// else
// DrawMonsterPanel();
//}
}

View file

@ -1,196 +0,0 @@
using Dalamud.Interface;
using Glamourer.Structs;
using ImGuiNET;
using Lumina.Text;
using OtterGui;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui;
//internal partial class Interface
//{
// private bool DrawStainSelector(ComboWithFilter<Stain> stainCombo, EquipSlot slot, StainId stainIdx)
// {
// stainCombo.PostPreview = null;
// if (_stains.TryGetValue((byte)stainIdx, out var stain))
// {
// var previewPush = PushColor(stain, ImGuiCol.FrameBg);
// stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush);
// }
//
// var change = stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx);
// if (!change && (byte)stainIdx != 0)
// {
// ImGuiUtil.HoverTooltip("Right-click to clear.");
// if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
// {
// change = true;
// newStain = Stain.None;
// }
// }
//
// if (!change)
// return false;
//
// if (_player == null)
// return _inDesignMode && (_selection?.Data.WriteStain(slot, newStain.RowIndex) ?? false);
//
// Glamourer.RevertableDesigns.Add(_player);
// newStain.Write(_player.Address, slot);
// return true;
// }
//
// private bool DrawItemSelector(ComboWithFilter<Item> equipCombo, Lumina.Excel.GeneratedSheets.Item item, EquipSlot slot = EquipSlot.Unknown)
// {
// var currentName = item.Name.ToString();
// var change = equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && newItem.Base.RowId != item.RowId;
// if (!change && !ReferenceEquals(item, SmallClothes))
// {
// ImGuiUtil.HoverTooltip("Right-click to clear.");
// if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
// {
// change = true;
// newItem = Item.Nothing(slot);
// }
// }
//
// if (!change)
// return false;
//
// newItem = new Item(newItem.Base, newItem.Name, slot);
// if (_player == null)
// return _inDesignMode && (_selection?.Data.WriteItem(newItem) ?? false);
//
// Glamourer.RevertableDesigns.Add(_player);
// newItem.Write(_player.Address);
// return true;
// }
//
// private static bool DrawCheckbox(CharacterEquipMask flag, ref CharacterEquipMask mask)
// {
// var tmp = (uint)mask;
// var ret = false;
// if (ImGui.CheckboxFlags($"##flag_{(uint)flag}", ref tmp, (uint)flag) && tmp != (uint)mask)
// {
// mask = (CharacterEquipMask)tmp;
// ret = true;
// }
//
// if (ImGui.IsItemHovered())
// ImGui.SetTooltip("Enable writing this slot in this save.");
// return ret;
// }
//
// private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothes = new()
// {
// Name = new SeString("Nothing"),
// RowId = 0,
// };
//
// private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothesNpc = new()
// {
// Name = new SeString("Smallclothes (NPC)"),
// RowId = 1,
// };
//
// private static readonly Lumina.Excel.GeneratedSheets.Item Unknown = new()
// {
// Name = new SeString("Unknown"),
// RowId = 2,
// };
//
// private Lumina.Excel.GeneratedSheets.Item Identify(SetId set, WeaponType weapon, ushort variant, EquipSlot slot)
// {
// return (uint)set switch
// {
// 0 => SmallClothes,
// 9903 => SmallClothesNpc,
// _ => _identifier.Identify(set, weapon, variant, slot.ToSlot()).FirstOrDefault() ?? Unknown,
// };
// }
//
// private bool DrawEquipSlot(EquipSlot slot, CharacterArmor equip)
// {
// var (equipCombo, stainCombo) = _combos[slot];
//
// var ret = DrawStainSelector(stainCombo, slot, equip.Stain);
// ImGui.SameLine();
// var item = Identify(equip.Set, new WeaponType(), equip.Variant, slot);
// ret |= DrawItemSelector(equipCombo, item, slot);
//
// return ret;
// }
//
// private bool DrawEquipSlotWithCheck(EquipSlot slot, CharacterArmor equip, CharacterEquipMask flag, ref CharacterEquipMask mask)
// {
// var ret = DrawCheckbox(flag, ref mask);
// ImGui.SameLine();
// ret |= DrawEquipSlot(slot, equip);
// return ret;
// }
//
// private bool DrawWeapon(EquipSlot slot, CharacterWeapon weapon)
// {
// var (equipCombo, stainCombo) = _combos[slot];
//
// var ret = DrawStainSelector(stainCombo, slot, weapon.Stain);
// ImGui.SameLine();
// var item = Identify(weapon.Set, weapon.Type, weapon.Variant, slot);
// ret |= DrawItemSelector(equipCombo, item, slot);
//
// return ret;
// }
//
// private bool DrawWeaponWithCheck(EquipSlot slot, CharacterWeapon weapon, CharacterEquipMask flag, ref CharacterEquipMask mask)
// {
// var ret = DrawCheckbox(flag, ref mask);
// ImGui.SameLine();
// ret |= DrawWeapon(slot, weapon);
// return ret;
// }
//
// private bool DrawEquip(CharacterEquipment equip)
// {
// var ret = false;
// if (ImGui.CollapsingHeader("Character Equipment"))
// {
// ret |= DrawWeapon(EquipSlot.MainHand, equip.MainHand);
// ret |= DrawWeapon(EquipSlot.OffHand, equip.OffHand);
// ret |= DrawEquipSlot(EquipSlot.Head, equip.Head);
// ret |= DrawEquipSlot(EquipSlot.Body, equip.Body);
// ret |= DrawEquipSlot(EquipSlot.Hands, equip.Hands);
// ret |= DrawEquipSlot(EquipSlot.Legs, equip.Legs);
// ret |= DrawEquipSlot(EquipSlot.Feet, equip.Feet);
// ret |= DrawEquipSlot(EquipSlot.Ears, equip.Ears);
// ret |= DrawEquipSlot(EquipSlot.Neck, equip.Neck);
// ret |= DrawEquipSlot(EquipSlot.Wrists, equip.Wrists);
// ret |= DrawEquipSlot(EquipSlot.RFinger, equip.RFinger);
// ret |= DrawEquipSlot(EquipSlot.LFinger, equip.LFinger);
// }
//
// return ret;
// }
//
// private bool DrawEquip(CharacterEquipment equip, ref CharacterEquipMask mask)
// {
// var ret = false;
// if (ImGui.CollapsingHeader("Character Equipment"))
// {
// ret |= DrawWeaponWithCheck(EquipSlot.MainHand, equip.MainHand, CharacterEquipMask.MainHand, ref mask);
// ret |= DrawWeaponWithCheck(EquipSlot.OffHand, equip.OffHand, CharacterEquipMask.OffHand, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Head, equip.Head, CharacterEquipMask.Head, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Body, equip.Body, CharacterEquipMask.Body, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Hands, equip.Hands, CharacterEquipMask.Hands, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Legs, equip.Legs, CharacterEquipMask.Legs, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Feet, equip.Feet, CharacterEquipMask.Feet, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Ears, equip.Ears, CharacterEquipMask.Ears, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Neck, equip.Neck, CharacterEquipMask.Neck, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Wrists, equip.Wrists, CharacterEquipMask.Wrists, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.RFinger, equip.RFinger, CharacterEquipMask.RFinger, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.LFinger, equip.LFinger, CharacterEquipMask.LFinger, ref mask);
// }
//
// return ret;
// }
//}

View file

@ -1,168 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Glamourer.Designs;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui.Raii;
namespace Glamourer.Gui;
//internal partial class Interface
//{
// private const string FixDragDropLabel = "##FixDragDrop";
//
// private List<string>? _fullPathCache;
// private string _newFixCharacterName = string.Empty;
// private string _newFixDesignPath = string.Empty;
// private JobGroup? _newFixDesignGroup;
// private Design? _newFixDesign;
// private int _fixDragDropIdx = -1;
//
// private static unsafe bool IsDropping()
// => ImGui.AcceptDragDropPayload(FixDragDropLabel).NativePtr != null;
//
// private void DrawFixedDesignsTab()
// {
// _newFixDesignGroup ??= Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
//
// using var tabItem = ImRaii.TabItem("Fixed Designs");
// if (!tabItem)
// {
// _fullPathCache = null;
// _newFixDesign = null;
// _newFixDesignPath = string.Empty;
// _newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
// return;
// }
//
// _fullPathCache ??= Glamourer.FixedDesignManager.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList();
//
// using var table = ImRaii.Table("##FixedTable", 4);
// var buttonWidth = 23.5f * ImGuiHelpers.GlobalScale;
//
// ImGui.TableSetupColumn("##DeleteColumn", ImGuiTableColumnFlags.WidthFixed, 2 * buttonWidth);
// ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale);
// ImGui.TableSetupColumn("Jobs", ImGuiTableColumnFlags.WidthFixed, 175 * ImGuiHelpers.GlobalScale);
// ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthStretch);
// ImGui.TableHeadersRow();
// var xPos = 0f;
//
// using var style = new ImRaii.Style();
// using var font = new ImRaii.Font();
// for (var i = 0; i < _fullPathCache.Count; ++i)
// {
// var path = _fullPathCache[i];
// var name = Glamourer.FixedDesignManager.FixedDesigns.Data[i];
//
// ImGui.TableNextRow();
// ImGui.TableNextColumn();
// style.Push(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2);
// font.Push(UiBuilder.IconFont);
// if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconChar()}##{i}"))
// {
// _fullPathCache.RemoveAt(i--);
// Glamourer.FixedDesignManager.FixedDesigns.Remove(name);
// continue;
// }
//
// var tmp = name.Enabled;
// ImGui.SameLine();
// xPos = ImGui.GetCursorPosX();
// if (ImGui.Checkbox($"##Enabled{i}", ref tmp))
// if (tmp && Glamourer.FixedDesignManager.FixedDesigns.EnableDesign(name)
// || !tmp && Glamourer.FixedDesignManager.FixedDesigns.DisableDesign(name))
// {
// Glamourer.Config.FixedDesigns[i].Enabled = tmp;
// Glamourer.Config.Save();
// }
//
// style.Pop();
// font.Pop();
// ImGui.TableNextColumn();
// ImGui.Selectable($"{name.Name}##Fix{i}");
// if (ImGui.BeginDragDropSource())
// {
// _fixDragDropIdx = i;
// ImGui.SetDragDropPayload("##FixDragDrop", IntPtr.Zero, 0);
// ImGui.Text($"Dragging {name.Name} ({path})...");
// ImGui.EndDragDropSource();
// }
//
// if (ImGui.BeginDragDropTarget())
// {
// if (IsDropping() && _fixDragDropIdx >= 0)
// {
// var d = Glamourer.FixedDesignManager.FixedDesigns.Data[_fixDragDropIdx];
// Glamourer.FixedDesignManager.FixedDesigns.Move(d, i);
// var p = _fullPathCache[_fixDragDropIdx];
// _fullPathCache.RemoveAt(_fixDragDropIdx);
// _fullPathCache.Insert(i, p);
// _fixDragDropIdx = -1;
// }
//
// ImGui.EndDragDropTarget();
// }
//
// ImGui.TableNextColumn();
// ImGui.Text(Glamourer.FixedDesignManager.FixedDesigns.Data[i].Jobs.Name);
// ImGui.TableNextColumn();
// ImGui.Text(path);
// }
//
// ImGui.TableNextRow();
// ImGui.TableNextColumn();
// font.Push(UiBuilder.IconFont);
//
// ImGui.SetCursorPosX(xPos);
// if (_newFixDesign == null || _newFixCharacterName == string.Empty)
// {
// style.Push(ImGuiStyleVar.Alpha, 0.5f);
// ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix");
// style.Pop();
// }
// else if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix"))
// {
// _fullPathCache.Add(_newFixDesignPath);
// Glamourer.FixedDesignManager.FixedDesigns.Add(_newFixCharacterName, _newFixDesign, _newFixDesignGroup.Value, false);
// _newFixCharacterName = string.Empty;
// _newFixDesignPath = string.Empty;
// _newFixDesign = null;
// _newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
// }
//
// font.Pop();
// ImGui.TableNextColumn();
// ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
// ImGui.InputTextWithHint("##NewFix", "Enter new Character", ref _newFixCharacterName, 32);
// ImGui.TableNextColumn();
// ImGui.SetNextItemWidth(-1);
// using var combo = ImRaii.Combo("##NewFixDesignGroup", _newFixDesignGroup.Value.Name);
// if (combo)
// foreach (var (id, group) in Glamourer.FixedDesignManager.FixedDesigns.JobGroups)
// {
// ImGui.SetNextItemWidth(-1);
// if (ImGui.Selectable($"{group.Name}##NewFixDesignGroup", group.Name == _newFixDesignGroup.Value.Name))
// _newFixDesignGroup = group;
// }
//
// ImGui.TableNextColumn();
// ImGui.SetNextItemWidth(-1);
// using var combo2 = ImRaii.Combo("##NewFixPath", _newFixDesignPath);
// if (!combo2)
// return;
//
// foreach (var design in _plugin.Designs.FileSystem.Root.AllLeaves(SortMode.Lexicographical).Cast<Design>())
// {
// var fullName = design.FullName();
// ImGui.SetNextItemWidth(-1);
// if (!ImGui.Selectable($"{fullName}##NewFixDesign", fullName == _newFixDesignPath))
// continue;
//
// _newFixDesignPath = fullName;
// _newFixDesign = design;
// }
// }
//}

View file

@ -1,90 +0,0 @@
using System;
using System.Linq;
using Dalamud.Logging;
using Glamourer.Customization;
using Glamourer.Structs;
using ImGuiNET;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui;
//internal partial class Interface
//{
//
//
//
//
// private enum DesignNameUse
// {
// SaveCurrent,
// NewDesign,
// DuplicateDesign,
// NewFolder,
// FromClipboard,
// }
//
// private void DrawDesignNamePopup(DesignNameUse use)
// {
// if (ImGui.BeginPopup($"{DesignNamePopupLabel}{use}"))
// {
// if (ImGui.InputText("##designName", ref _newDesignName, 64, ImGuiInputTextFlags.EnterReturnsTrue)
// && _newDesignName.Any())
// {
// switch (use)
// {
// case DesignNameUse.SaveCurrent:
// SaveNewDesign(ConditionalCopy(_currentSave, _holdShift, _holdCtrl));
// break;
// case DesignNameUse.NewDesign:
// var empty = new CharacterSave();
// empty.Load(CharacterCustomization.Default);
// empty.WriteCustomizations = false;
// SaveNewDesign(empty);
// break;
// case DesignNameUse.DuplicateDesign:
// SaveNewDesign(ConditionalCopy(_selection!.Data, _holdShift, _holdCtrl));
// break;
// case DesignNameUse.NewFolder:
// _designs.FileSystem
// .CreateAllFolders($"{_newDesignName}/a"); // Filename is just ignored, but all folders are created.
// break;
// case DesignNameUse.FromClipboard:
// try
// {
// var text = ImGui.GetClipboardText();
// var save = CharacterSave.FromString(text);
// SaveNewDesign(save);
// }
// catch (Exception e)
// {
// PluginLog.Information($"Could not save new Design from Clipboard:\n{e}");
// }
//
// break;
// }
//
// _newDesignName = string.Empty;
// ImGui.CloseCurrentPopup();
// }
//
// if (_keyboardFocus)
// {
// ImGui.SetKeyboardFocusHere();
// _keyboardFocus = false;
// }
//
// ImGui.EndPopup();
// }
// }
//
// private void OpenDesignNamePopup(DesignNameUse use)
// {
// _newDesignName = string.Empty;
// _keyboardFocus = true;
// _holdCtrl = ImGui.GetIO().KeyCtrl;
// _holdShift = ImGui.GetIO().KeyShift;
// ImGui.OpenPopup($"{DesignNamePopupLabel}{use}");
// }
//}

View file

@ -1,82 +0,0 @@
using System.Collections.Generic;
using System.Numerics;
using System.Reflection;
using ImGuiNET;
using Penumbra.GameData.Enums;
using Lumina.Excel.GeneratedSheets;
namespace Glamourer.Gui;
//internal partial class Interface
//{
// private const float ColorButtonWidth = 22.5f;
// private const float ColorComboWidth = 140f;
// private const float ItemComboWidth = 350f;
//
// private static readonly Vector4 GreyVector = new(0.5f, 0.5f, 0.5f, 1);
//
// private static ComboWithFilter<Stain> CreateDefaultStainCombo(IReadOnlyList<Stain> stains)
// => new("##StainCombo", ColorComboWidth, ColorButtonWidth, stains,
// s => s.Name.ToString())
// {
// Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge,
// PreList = () =>
// {
// ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
// ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
// ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
// },
// PostList = () => { ImGui.PopStyleVar(3); },
// CreateSelectable = s =>
// {
// var push = PushColor(s);
// var ret = ImGui.Button($"{s.Name}##Stain{(byte)s.RowIndex}",
// Vector2.UnitX * (ColorComboWidth - ImGui.GetStyle().ScrollbarSize));
// ImGui.PopStyleColor(push);
// return ret;
// },
// ItemsAtOnce = 12,
// };
//
// private ComboWithFilter<Item> CreateItemCombo(EquipSlot slot, IReadOnlyList<Item> items)
// => new($"{_equipSlotNames[slot]}##Equip", ItemComboWidth, ItemComboWidth, items, i => i.Name)
// {
// Flags = ImGuiComboFlags.HeightLarge,
// CreateSelectable = i =>
// {
// var ret = ImGui.Selectable(i.Name);
// var setId = $"({(int)i.MainModel.id})";
// var size = ImGui.CalcTextSize(setId).X;
// ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - size - ImGui.GetStyle().ItemInnerSpacing.X);
// ImGui.TextColored(GreyVector, setId);
// return ret;
// },
// };
//
// private (ComboWithFilter<Item>, ComboWithFilter<Stain>) CreateCombos(EquipSlot slot, IReadOnlyList<Item> items,
// ComboWithFilter<Stain> defaultStain)
// => (CreateItemCombo(slot, items), new ComboWithFilter<Stain>($"##{slot}Stain", defaultStain));
//
//
// private static Dictionary<EquipSlot, string> GetEquipSlotNames()
// {
// var sheet = Dalamud.GameData.GetExcelSheet<Addon>()!;
// var ret = new Dictionary<EquipSlot, string>(12)
// {
// [EquipSlot.MainHand] = sheet.GetRow(738)?.Text.ToString() ?? "Main Hand",
// [EquipSlot.OffHand] = sheet.GetRow(739)?.Text.ToString() ?? "Off Hand",
// [EquipSlot.Head] = sheet.GetRow(740)?.Text.ToString() ?? "Head",
// [EquipSlot.Body] = sheet.GetRow(741)?.Text.ToString() ?? "Body",
// [EquipSlot.Hands] = sheet.GetRow(742)?.Text.ToString() ?? "Hands",
// [EquipSlot.Legs] = sheet.GetRow(744)?.Text.ToString() ?? "Legs",
// [EquipSlot.Feet] = sheet.GetRow(745)?.Text.ToString() ?? "Feet",
// [EquipSlot.Ears] = sheet.GetRow(746)?.Text.ToString() ?? "Ears",
// [EquipSlot.Neck] = sheet.GetRow(747)?.Text.ToString() ?? "Neck",
// [EquipSlot.Wrists] = sheet.GetRow(748)?.Text.ToString() ?? "Wrists",
// [EquipSlot.RFinger] = sheet.GetRow(749)?.Text.ToString() ?? "Right Ring",
// [EquipSlot.LFinger] = sheet.GetRow(750)?.Text.ToString() ?? "Left Ring",
// };
// return ret;
// }
//}

View file

@ -1,52 +0,0 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using ImGuiNET;
namespace Glamourer.Gui;
//internal partial class Interface
//{
//
// private static bool DrawMiscellaneous(CharacterSave save, Character? player)
// {
// var ret = false;
// if (!ImGui.CollapsingHeader("Miscellaneous"))
// return ret;
//
// ret |= DrawCheckMark("Hat Visible", save.HatState, v =>
// {
// save.HatState = v;
// player?.SetHatVisible(v);
// });
//
// ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v =>
// {
// save.WeaponState = v;
// player?.SetWeaponHidden(!v);
// });
//
// ret |= DrawCheckMark("Visor Toggled", save.VisorState, v =>
// {
// save.VisorState = v;
// player?.SetVisorToggled(v);
// });
//
// ret |= DrawCheckMark("Is Wet", save.IsWet, v =>
// {
// save.IsWet = v;
// player?.SetWetness(v);
// });
//
// var alpha = save.Alpha;
// if (ImGui.DragFloat("Alpha", ref alpha, 0.01f, 0f, 1f, "%.2f") && alpha != save.Alpha)
// {
// alpha = (float)Math.Round(alpha > 1 ? 1 : alpha < 0 ? 0 : alpha, 2);
// save.Alpha = alpha;
// ret = true;
// if (player != null)
// player.Alpha() = alpha;
// }
//
// return ret;
// }
//}

View file

@ -1,87 +0,0 @@
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using ImGuiNET;
using OtterGui.Raii;
namespace Glamourer.Gui;
//internal partial class Interface
//{
// private string? _currentRevertableName;
// private CharacterSave? _currentRevertable;
//
// private void DrawRevertablesSelector()
// {
// ImGui.BeginGroup();
// DrawPlayerFilter();
// if (!ImGui.BeginChild("##playerSelector",
// new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
// {
// ImGui.EndChild();
// ImGui.EndGroup();
// return;
// }
//
// foreach (var (name, save) in Glamourer.RevertableDesigns.Saves)
// {
// if (name.ToLowerInvariant().Contains(_playerFilterLower) && ImGui.Selectable(name, name == _currentRevertableName))
// {
// _currentRevertableName = name;
// _currentRevertable = save;
// }
// }
//
// using (var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
// {
// ImGui.EndChild();
// }
//
// DrawSelectionButtons();
// ImGui.EndGroup();
// }
//
// private void DrawRevertablePanel()
// {
// using var group = ImRaii.Group();
// {
// var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
// using var color = ImRaii.PushColor(ImGuiCol.Text, GreenHeaderColor)
// .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($"{_currentRevertableName}##playerHeader", -Vector2.UnitX * 0.0001f);
// }
//
// if (!ImGui.BeginChild("##revertableData", -Vector2.One, true))
// {
// ImGui.EndChild();
// return;
// }
//
// var save = _currentRevertable!.Copy();
// DrawCustomization(ref save.Customizations);
// DrawEquip(save.Equipment);
// DrawMiscellaneous(save, null);
//
// ImGui.EndChild();
// }
//
// [Conditional("DEBUG")]
// private void DrawRevertablesTab()
// {
// using var tabItem = ImRaii.TabItem("Revertables");
// if (!tabItem)
// return;
//
// DrawRevertablesSelector();
//
// if (_currentRevertableName == null)
// return;
//
// ImGui.SameLine();
// DrawRevertablePanel();
// }
//}

View file

@ -1,168 +0,0 @@
using System;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Glamourer.Customization;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Structs;
using Penumbra.String;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Interop;
public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
{
public static readonly Actor Null = new() { Pointer = null };
public FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Pointer;
public IntPtr Address
=> (IntPtr)Pointer;
public static implicit operator Actor(IntPtr? pointer)
=> new() { Pointer = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)(pointer ?? IntPtr.Zero) };
public static implicit operator IntPtr(Actor actor)
=> actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer;
public ActorIdentifier GetIdentifier(ActorManager actors)
=> actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer, out _, true, true, false);
public bool Identifier(ActorManager actors, out ActorIdentifier ident)
{
if (Valid)
{
ident = GetIdentifier(actors);
return true;
}
ident = ActorIdentifier.Invalid;
return false;
}
public override string ToString()
=> Pointer != null ? Utf8Name.ToString() : "Invalid";
public bool IsAvailable
=> Pointer->GameObject.GetIsTargetable();
public bool IsHuman
=> Pointer != null && Pointer->ModelCharaId == 0;
public ObjectKind ObjectKind
{
get => (ObjectKind)Pointer->GameObject.ObjectKind;
set => Pointer->GameObject.ObjectKind = (byte)value;
}
public ByteString Utf8Name
=> new(Pointer->GameObject.Name);
public byte Job
=> Pointer->ClassJob;
public DrawObject DrawObject
=> (IntPtr)Pointer->GameObject.DrawObject;
public bool Valid
=> Pointer != null;
public int Index
=> Valid ? Pointer->GameObject.ObjectIndex : -1;
public uint ModelId
{
get => (uint)Pointer->ModelCharaId;
set => Pointer->ModelCharaId = (int)value;
}
public ushort UsedMountId
=> !IsHuman ? (ushort)0 : *(ushort*)((byte*)Pointer + 0x668);
public ushort CompanionId
=> ObjectKind == ObjectKind.Companion ? *(ushort*)((byte*)Pointer + 0x1AAC) : (ushort)0;
public Customize Customize
=> new(*(CustomizeData*)&Pointer->DrawData.CustomizeData);
public CharacterEquip Equip
=> new((CharacterArmor*)&Pointer->DrawData.Head);
public CharacterWeapon MainHand
{
get => *(CharacterWeapon*)&Pointer->DrawData.MainHandModel;
set => *(CharacterWeapon*)&Pointer->DrawData.MainHandModel = value;
}
public CharacterWeapon OffHand
{
get => *(CharacterWeapon*)&Pointer->DrawData.OffHandModel;
set => *(CharacterWeapon*)&Pointer->DrawData.OffHandModel = value;
}
public unsafe bool VisorEnabled
{
get => (*(byte*)(Address + Offsets.Character.VisorToggled) & Offsets.Character.Flags.IsVisorToggled) != 0;
set => *(byte*)(Address + Offsets.Character.VisorToggled) = (byte)(value
? *(byte*)(Address + Offsets.Character.VisorToggled) | Offsets.Character.Flags.IsVisorToggled
: *(byte*)(Address + Offsets.Character.VisorToggled) & ~Offsets.Character.Flags.IsVisorToggled);
}
public unsafe bool WeaponEnabled
{
get => (*(byte*)(Address + Offsets.Character.WeaponHidden1) & Offsets.Character.Flags.IsWeaponHidden1) == 0;
set
{
ref var w1 = ref *(byte*)(Address + Offsets.Character.WeaponHidden1);
ref var w2 = ref *(byte*)(Address + Offsets.Character.WeaponHidden2);
if (value)
{
w1 = (byte)(w1 & ~Offsets.Character.Flags.IsWeaponHidden1);
w2 = (byte)(w2 & ~Offsets.Character.Flags.IsWeaponHidden2);
}
else
{
w1 = (byte)(w1 | Offsets.Character.Flags.IsWeaponHidden1);
w2 = (byte)(w2 | Offsets.Character.Flags.IsWeaponHidden2);
}
}
}
public bool IsWet { get; set; }
public void SetModelId(int value)
{
if (Pointer != null)
Pointer->ModelCharaId = value;
}
public static implicit operator bool(Actor actor)
=> actor.Pointer != null;
public static bool operator true(Actor actor)
=> actor.Pointer != null;
public static bool operator false(Actor actor)
=> actor.Pointer == null;
public static bool operator !(Actor actor)
=> actor.Pointer == null;
public bool Equals(Actor other)
=> Pointer == other.Pointer;
public override bool Equals(object? obj)
=> obj is Actor other && Equals(other);
public override int GetHashCode()
=> ((ulong)Pointer).GetHashCode();
public static bool operator ==(Actor lhs, Actor rhs)
=> lhs.Pointer == rhs.Pointer;
public static bool operator !=(Actor lhs, Actor rhs)
=> lhs.Pointer != rhs.Pointer;
public string AddressString()
=> $"0x{Address:X}";
}

View file

@ -1,24 +0,0 @@
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop;
public unsafe class ChangeCustomizeService
{
public ChangeCustomizeService()
=> SignatureHelper.Initialise(this);
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
[Signature(Sigs.ChangeCustomize)]
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
public bool UpdateCustomize(Actor actor, CustomizeData customize)
{
if (customize.Data == null || !actor.Valid || !actor.DrawObject.Valid)
return false;
return _changeCustomize(actor.DrawObject.Pointer, customize.Data, 1);
}
}

View file

@ -1,100 +0,0 @@
using System;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization;
using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Interop;
public unsafe partial struct DrawObject : IEquatable<DrawObject>, IDesignable
{
public Human* Pointer;
public IntPtr Address
=> (IntPtr)Pointer;
public static implicit operator DrawObject(IntPtr? pointer)
=> new() { Pointer = (Human*)(pointer ?? IntPtr.Zero) };
public static implicit operator IntPtr(DrawObject drawObject)
=> drawObject.Pointer == null ? IntPtr.Zero : (IntPtr)drawObject.Pointer;
public bool Valid
=> Pointer != null;
public uint ModelId
=> 0;
public bool IsWet
=> false;
public uint Type
=> (*(delegate* unmanaged<Human*, uint>**)Pointer)[50](Pointer);
public Customize Customize
=> *(Customize*)Pointer->CustomizeData;
public CharacterEquip Equip
=> new((CharacterArmor*)Pointer->EquipSlotData);
public CharacterWeapon MainHand
{
get
{
var child = (byte*)Pointer->CharacterBase.DrawObject.Object.ChildObject;
if (child == null)
return CharacterWeapon.Empty;
return *(CharacterWeapon*)(child + 0x8F0);
}
}
public unsafe CharacterWeapon OffHand
{
get
{
var child = Pointer->CharacterBase.DrawObject.Object.ChildObject;
if (child == null)
return CharacterWeapon.Empty;
var sibling = (byte*)child->NextSiblingObject;
if (sibling == null)
return CharacterWeapon.Empty;
return *(CharacterWeapon*)(sibling + 0x8F0);
}
}
public unsafe bool VisorEnabled
=> (*(byte*)(Address + 0x90) & 0x40) != 0;
public unsafe bool WeaponEnabled
=> false;
public static implicit operator bool(DrawObject actor)
=> actor.Pointer != null;
public static bool operator true(DrawObject actor)
=> actor.Pointer != null;
public static bool operator false(DrawObject actor)
=> actor.Pointer == null;
public static bool operator !(DrawObject actor)
=> actor.Pointer == null;
public bool Equals(DrawObject other)
=> Pointer == other.Pointer;
public override bool Equals(object? obj)
=> obj is DrawObject other && Equals(other);
public override int GetHashCode()
=> unchecked((int)(long)Pointer);
public static bool operator ==(DrawObject lhs, DrawObject rhs)
=> lhs.Pointer == rhs.Pointer;
public static bool operator !=(DrawObject lhs, DrawObject rhs)
=> lhs.Pointer != rhs.Pointer;
}

View file

@ -1,17 +0,0 @@
using Glamourer.Customization;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop;
public interface IDesignable
{
public bool Valid { get; }
public uint ModelId { get; }
public Customize Customize { get; }
public CharacterEquip Equip { get; }
public CharacterWeapon MainHand { get; }
public CharacterWeapon OffHand { get; }
public bool VisorEnabled { get; }
public bool WeaponEnabled { get; }
public bool IsWet { get; }
}

View file

@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using Dalamud.Data;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using Glamourer.Structs;
namespace Glamourer.Interop;
public class JobService : IDisposable
{
public readonly IReadOnlyDictionary<byte, Job> Jobs;
public readonly IReadOnlyDictionary<ushort, JobGroup> JobGroups;
public event Action<Actor, Job>? JobChanged;
public JobService(DataManager gameData)
{
SignatureHelper.Initialise(this);
Jobs = GameData.Jobs(gameData);
JobGroups = GameData.JobGroups(gameData);
_changeJobHook.Enable();
}
public void Dispose()
{
_changeJobHook.Dispose();
}
private delegate void ChangeJobDelegate(nint data, uint job);
[Signature(Sigs.ChangeJob, DetourName = nameof(ChangeJobDetour))]
private readonly Hook<ChangeJobDelegate> _changeJobHook = null!;
private void ChangeJobDetour(nint data, uint jobIndex)
{
_changeJobHook.Original(data, jobIndex);
var actor = (Actor)(data - Offsets.Character.ClassJobContainer);
var job = Jobs[(byte)jobIndex];
Glamourer.Log.Excessive($"{actor} changed job to {job}");
JobChanged?.Invoke(actor, job);
}
}

View file

@ -1,160 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Glamourer.Services;
using Penumbra.GameData.Actors;
namespace Glamourer.Interop;
public readonly struct ActorData
{
public readonly List<Actor> Objects;
public readonly string Label;
public bool Valid
=> Objects.Count > 0;
public ActorData(Actor actor, string label)
{
Objects = new List<Actor> { actor };
Label = label;
}
public static readonly ActorData Invalid = new(false);
private ActorData(bool _)
{
Objects = new List<Actor>(0);
Label = string.Empty;
}
}
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
{
private readonly Framework _framework;
private readonly ClientState _clientState;
private readonly ObjectTable _objects;
private readonly ActorService _actors;
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects, ActorService actors)
{
_framework = framework;
_clientState = clientState;
_objects = objects;
_actors = actors;
}
public DateTime LastUpdate { get; private set; }
public bool IsInGPose { get; private set; }
public ushort World { get; private set; }
private readonly Dictionary<ActorIdentifier, ActorData> _identifiers = new(200);
private void HandleIdentifier(ActorIdentifier identifier, Actor character)
{
if (!character.DrawObject || !identifier.IsValid)
return;
if (!_identifiers.TryGetValue(identifier, out var data))
{
data = new ActorData(character, identifier.ToString());
_identifiers[identifier] = data;
}
else
{
data.Objects.Add(character);
}
}
public void Update()
{
var lastUpdate = _framework.LastUpdate;
if (lastUpdate <= LastUpdate)
return;
LastUpdate = lastUpdate;
World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
_identifiers.Clear();
for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i)
{
Actor character = _objects.GetObjectAddress(i);
if (character.Identifier(_actors.AwaitedService, out var identifier))
HandleIdentifier(identifier, character);
}
for (var i = (int)ScreenActor.CutsceneStart; i < (int)ScreenActor.CutsceneEnd; ++i)
{
Actor character = _objects.GetObjectAddress(i);
if (!character.Identifier(_actors.AwaitedService, out var identifier))
break;
HandleIdentifier(identifier, character);
}
void AddSpecial(ScreenActor idx, string label)
{
Actor actor = _objects.GetObjectAddress((int)idx);
if (actor.Identifier(_actors.AwaitedService, out var ident))
{
var data = new ActorData(actor, label);
_identifiers.Add(ident, data);
}
}
AddSpecial(ScreenActor.CharacterScreen, "Character Screen Actor");
AddSpecial(ScreenActor.ExamineScreen, "Examine Screen Actor");
AddSpecial(ScreenActor.FittingRoom, "Fitting Room Actor");
AddSpecial(ScreenActor.DyePreview, "Dye Preview Actor");
AddSpecial(ScreenActor.Portrait, "Portrait Actor");
AddSpecial(ScreenActor.Card6, "Card Actor 6");
AddSpecial(ScreenActor.Card7, "Card Actor 7");
AddSpecial(ScreenActor.Card8, "Card Actor 8");
for (var i = (int)ScreenActor.ScreenEnd; i < _objects.Length; ++i)
{
Actor character = _objects.GetObjectAddress(i);
if (character.Identifier(_actors.AwaitedService, out var identifier))
HandleIdentifier(identifier, character);
}
var gPose = GPosePlayer;
IsInGPose = gPose && gPose.Utf8Name.Length > 0;
}
public Actor GPosePlayer
=> _objects.GetObjectAddress((int)ScreenActor.GPosePlayer);
public Actor Player
=> _objects.GetObjectAddress(0);
public IEnumerator<KeyValuePair<ActorIdentifier, ActorData>> GetEnumerator()
=> _identifiers.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> _identifiers.Count;
public bool ContainsKey(ActorIdentifier key)
=> _identifiers.ContainsKey(key);
public bool TryGetValue(ActorIdentifier key, out ActorData value)
=> _identifiers.TryGetValue(key, out value);
public ActorData this[ActorIdentifier key]
=> _identifiers[key];
public IEnumerable<ActorIdentifier> Keys
=> _identifiers.Keys;
public IEnumerable<ActorData> Values
=> _identifiers.Values;
}

View file

@ -1,93 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using Glamourer.Api;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Interop;
public unsafe partial class RedrawManager : IDisposable
{
private readonly ItemManager _items;
private readonly ActorService _actors;
private readonly FixedDesignManager _fixedDesignManager;
private readonly ActiveDesign.Manager _stateManager;
private readonly PenumbraAttach _penumbra;
private readonly WeaponService _weapons;
public RedrawManager(FixedDesignManager fixedDesignManager, ActiveDesign.Manager stateManager, ItemManager items, ActorService actors,
PenumbraAttach penumbra, WeaponService weapons)
{
SignatureHelper.Initialise(this);
_fixedDesignManager = fixedDesignManager;
_stateManager = stateManager;
_items = items;
_actors = actors;
_penumbra = penumbra;
_weapons = weapons;
_penumbra.CreatingCharacterBase += OnCharacterRedraw;
_penumbra.CreatedCharacterBase += OnCharacterRedrawFinished;
}
public void Dispose()
{
}
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
{
// Do not apply anything if the game object model id does not correspond to the draw object model id.
// This is the case if the actor is transformed to a different creature.
if (actor.ModelId != *modelId)
return;
// Check if we have a current design in use, or if not if the actor has a fixed design.
var identifier = actor.GetIdentifier(_actors.AwaitedService);
if (!_stateManager.TryGetValue(identifier, out var save))
return;
// Compare game object customize data against draw object customize data for transformations.
// Apply customization if they correspond and there is customization to apply.
var gameObjectCustomize = new Customize(*(CustomizeData*)&actor.Pointer->DrawData.CustomizeData);
if (gameObjectCustomize.Equals(customize))
customize.Load(save.ModelData.Customize);
// Compare game object equip data against draw object equip data for transformations.
// Apply each piece of equip that should be applied if they correspond.
var gameObjectEquip = new CharacterEquip((CharacterArmor*)&actor.Pointer->DrawData.Head);
if (gameObjectEquip.Equals(equip))
{
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
(_, equip[slot]) =
_items.ResolveRestrictedGear(save.ModelData.Armor(slot), slot, customize.Race, customize.Gender);
}
}
}
private void OnCharacterRedraw(IntPtr gameObject, string collection, IntPtr modelId, IntPtr customize, IntPtr equipData)
{
try
{
OnCharacterRedraw(gameObject, (uint*)modelId, new Customize(*(CustomizeData*)customize),
new CharacterEquip((CharacterArmor*)equipData));
}
catch (Exception e)
{
PluginLog.Error($"Error on new draw object creation:\n{e}");
}
}
private static void OnCharacterRedrawFinished(IntPtr gameObject, string collection, IntPtr drawObject)
{
//SetVisor((Human*)drawObject, true);
}
}

View file

@ -1,77 +0,0 @@
using System;
using System.Linq;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop;
public unsafe class UpdateSlotService : IDisposable
{
public UpdateSlotService()
{
SignatureHelper.Initialise(this);
_flagSlotForUpdateHook.Enable();
}
public void Dispose()
=> _flagSlotForUpdateHook.Dispose();
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
public delegate void FlagSlotForUpdateDelegate(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item);
// This gets called when one of the ten equip items of an existing draw object gets changed.
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
public event FlagSlotForUpdateDelegate? EquipUpdate;
public ulong FlagSlotForUpdateInterop(DrawObject drawObject, EquipSlot slot, CharacterArmor armor)
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
public void UpdateSlot(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
{
InvokeFlagSlotEvent(drawObject, slot, ref data);
FlagSlotForUpdateInterop(drawObject, slot, data);
}
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
{
var armor = drawObject.Equip[slot] with { Stain = stain };
UpdateSlot(drawObject, slot, armor);
}
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
{
var slot = slotIdx.ToEquipSlot();
InvokeFlagSlotEvent(drawObject, slot, ref *data);
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
}
private void InvokeFlagSlotEvent(DrawObject drawObject, EquipSlot slot, ref CharacterArmor armor)
{
if (EquipUpdate == null)
{
Glamourer.Log.Excessive(
$"{slot} updated on 0x{drawObject.Address:X} to {armor.Set.Value}-{armor.Variant} with stain {armor.Stain.Value}.");
return;
}
var iv = armor;
foreach (var del in EquipUpdate.GetInvocationList().OfType<FlagSlotForUpdateDelegate>())
{
try
{
del(drawObject, slot, ref armor);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not invoke {nameof(EquipUpdate)} Subscriber:\n{ex}");
}
}
Glamourer.Log.Excessive(
$"{slot} updated on 0x{drawObject.Address:X} to {armor.Set.Value}-{armor.Variant} with stain {armor.Stain.Value}, initial armor was {iv.Set.Value}-{iv.Variant} with stain {iv.Stain.Value}.");
}
}

View file

@ -1,78 +0,0 @@
using System;
using System.Linq;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop;
public class VisorService : IDisposable
{
public VisorService()
{
SignatureHelper.Initialise(this);
_setupVisorHook.Enable();
}
public void Dispose()
=> _setupVisorHook.Dispose();
public static unsafe bool GetVisorState(nint humanPtr)
{
if (humanPtr == IntPtr.Zero)
return false;
var data = (Human*)humanPtr;
var flags = &data->CharacterBase.UnkFlags_01;
return (*flags & Offsets.DrawObjectVisorStateFlag) != 0;
}
public unsafe void SetVisorState(nint humanPtr, bool on)
{
if (humanPtr == IntPtr.Zero)
return;
var data = (Human*)humanPtr;
_setupVisorHook.Original(humanPtr, (ushort) data->HeadSetID, on);
}
private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, bool on);
public delegate void UpdateVisorDelegate(DrawObject human, SetId modelId, ref bool on);
[Signature(Penumbra.GameData.Sigs.SetupVisor, DetourName = nameof(SetupVisorDetour))]
private readonly Hook<UpdateVisorDelegateInternal> _setupVisorHook = null!;
public event UpdateVisorDelegate? VisorUpdate;
private void SetupVisorDetour(nint humanPtr, ushort modelId, bool on)
{
InvokeVisorEvent(humanPtr, modelId, ref on);
_setupVisorHook.Original(humanPtr, modelId, on);
}
private void InvokeVisorEvent(DrawObject drawObject, SetId modelId, ref bool on)
{
if (VisorUpdate == null)
{
Glamourer.Log.Excessive($"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}.");
return;
}
var initialValue = on;
foreach (var del in VisorUpdate.GetInvocationList().OfType<UpdateVisorDelegate>())
{
try
{
del(drawObject, modelId, ref on);
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not invoke {nameof(VisorUpdate)} Subscriber:\n{ex}");
}
}
Glamourer.Log.Excessive(
$"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}, initial call was {initialValue}.");
}
}

View file

@ -1,120 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop;
public unsafe class WeaponService : IDisposable
{
public WeaponService()
{
SignatureHelper.Initialise(this);
_loadWeaponHook.Enable();
}
public void Dispose()
{
_loadWeaponHook.Dispose();
}
public static readonly int CharacterWeaponOffset = (int)Marshal.OffsetOf<Character>("DrawData");
public delegate void LoadWeaponDelegate(nint offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
byte skipGameObject,
byte unk4);
// Weapons for a specific character are reloaded with this function.
// The first argument is a pointer to the game object but shifted a bit inside.
// slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data.
// weapon argument is the new weapon data.
// redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one.
// skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change)
// unk4 seemed to be the same as unk1.
[Signature(Penumbra.GameData.Sigs.WeaponReload, DetourName = nameof(LoadWeaponDetour))]
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
private void LoadWeaponDetour(nint characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
byte unk4)
{
//var oldWeapon = weapon;
//var character = (Actor)(characterOffset - CharacterWeaponOffset);
//try
//{
// var identifier = character.GetIdentifier(_actors.AwaitedService);
// if (_fixedDesignManager.TryGetDesign(identifier, out var save))
// {
// PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
// weapon = slot switch
// {
// 0 => save.WeaponMain.Model.Value,
// 1 => save.WeaponOff.Model.Value,
// _ => weapon,
// };
// }
// else if (redrawOnEquality == 1 && _stateManager.TryGetValue(identifier, out var save2))
// {
// PluginLog.Information($"Loaded weapon from current design for {identifier}.");
// //switch (slot)
// //{
// // case 0:
// // save2.MainHand = new CharacterWeapon(weapon);
// // break;
// // case 1:
// // save2.Data.OffHand = new CharacterWeapon(weapon);
// // break;
// //}
// }
//}
//catch (Exception e)
//{
// PluginLog.Error($"Error on loading new weapon:\n{e}");
//}
// First call the regular function.
_loadWeaponHook.Original(characterOffset, slot, weapon, redrawOnEquality, unk2, skipGameObject, unk4);
Glamourer.Log.Excessive($"Weapon reloaded for {(Actor)(characterOffset - CharacterWeaponOffset)} with attributes {slot} {weapon:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}");
// // If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object.
// if (oldWeapon != weapon)
// _loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4);
// // If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems.
// else if (slot != 1 && character.OffHand.Value == 0)
// _loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4);
}
// Load a specific weapon for a character by its data and slot.
public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon)
{
switch (slot)
{
case EquipSlot.MainHand:
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
return;
case EquipSlot.OffHand:
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
return;
case EquipSlot.BothHand:
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
return;
// function can also be called with '2', but does not seem to ever be.
}
}
// Load specific Main- and Offhand weapons.
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
{
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 1, 0, 1, 0);
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 1, 0, 1, 0);
}
public void LoadStain(Actor character, EquipSlot slot, StainId stain)
{
var weapon = slot == EquipSlot.OffHand ? character.OffHand : character.MainHand;
weapon.Stain = stain;
LoadWeapon(character, slot, weapon);
}
}

Binary file not shown.

View file

@ -1,30 +0,0 @@
using System.Collections.Generic;
using System.IO;
using OtterGui.Classes;
using OtterGui.Log;
namespace Glamourer.Services;
public class BackupService
{
public BackupService(Logger logger, FilenameService fileNames)
{
var files = GlamourerFiles(fileNames);
Backup.CreateBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), files);
}
/// <summary> Collect all relevant files for glamourer configuration. </summary>
private static IReadOnlyList<FileInfo> GlamourerFiles(FilenameService fileNames)
{
var list = new List<FileInfo>(16)
{
new(fileNames.ConfigFile),
new(fileNames.DesignFileSystem),
new(fileNames.MigrationDesignFile),
};
list.AddRange(fileNames.Designs());
return list;
}
}

View file

@ -1,36 +0,0 @@
using System;
using Dalamud.Game.Command;
using Glamourer.Gui;
namespace Glamourer.Services;
public class CommandService : IDisposable
{
private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],<Name for Save>";
private const string MainCommandString = "/glamourer";
private const string ApplyCommandString = "/glamour";
private readonly CommandManager _commands;
private readonly Interface _interface;
public CommandService(CommandManager commands, Interface ui)
{
_commands = commands;
_interface = ui;
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
_commands.AddHandler(ApplyCommandString, new CommandInfo(OnGlamour) { HelpMessage = $"Use Glamourer Functions: {HelpString}" });
}
public void Dispose()
{
_commands.RemoveHandler(MainCommandString);
_commands.RemoveHandler(ApplyCommandString);
}
private void OnGlamourer(string command, string arguments)
=> _interface.Toggle();
private void OnGlamour(string command, string arguments)
{ }
}

View file

@ -1,40 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using Dalamud.Plugin;
using Glamourer.Designs;
namespace Glamourer.Services;
public class FilenameService
{
public readonly string ConfigDirectory;
public readonly string ConfigFile;
public readonly string DesignFileSystem;
public readonly string MigrationDesignFile;
public readonly string DesignDirectory;
public FilenameService(DalamudPluginInterface pi)
{
ConfigDirectory = pi.ConfigDirectory.FullName;
ConfigFile = pi.ConfigFile.FullName;
DesignFileSystem = Path.Combine(ConfigDirectory, "sort_order.json");
MigrationDesignFile = Path.Combine(ConfigDirectory, "Designs.json");
DesignDirectory = Path.Combine(ConfigDirectory, "designs");
}
public IEnumerable<FileInfo> Designs()
{
if (!Directory.Exists(DesignDirectory))
yield break;
foreach (var file in Directory.EnumerateFiles(DesignDirectory, "*.json", SearchOption.TopDirectoryOnly))
yield return new FileInfo(file);
}
public string DesignFile(Design design)
=> DesignFile(design.Identifier.ToString());
public string DesignFile(string identifier)
=> Path.Combine(DesignDirectory, $"{identifier}.json");
}

View file

@ -1,189 +0,0 @@
using System;
using System.Diagnostics;
using System.Linq;
using Dalamud.Data;
using Dalamud.Plugin;
using Dalamud.Utility;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
using Lumina.Text;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Race = Penumbra.GameData.Enums.Race;
namespace Glamourer.Services;
public class ItemManager : IDisposable
{
public const string Nothing = "Nothing";
public const string SmallClothesNpc = "Smallclothes (NPC)";
public const ushort SmallClothesNpcModel = 9903;
private readonly ConfigurationOld _config;
public readonly IdentifierService IdentifierService;
public readonly ExcelSheet<Item> ItemSheet;
public readonly StainData Stains;
public readonly ItemService ItemService;
public readonly RestrictedGear RestrictedGear;
public ItemManager(DalamudPluginInterface pi, DataManager gameData, IdentifierService identifierService, ItemService itemService, ConfigurationOld config)
{
_config = config;
ItemSheet = gameData.GetExcelSheet<Item>()!;
IdentifierService = identifierService;
Stains = new StainData(pi, gameData, gameData.Language);
ItemService = itemService;
RestrictedGear = new RestrictedGear(pi, gameData.Language, gameData);
DefaultSword = ItemSheet.GetRow(1601)!; // Weathered Shortsword
}
public void Dispose()
{
Stains.Dispose();
RestrictedGear.Dispose();
}
public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender)
{
if (_config.UseRestrictedGearProtection)
return RestrictedGear.ResolveRestricted(armor, slot, race, gender);
return (false, armor);
}
public readonly Item DefaultSword;
public static uint NothingId(EquipSlot slot)
=> uint.MaxValue - 128 - (uint)slot.ToSlot();
public static uint SmallclothesId(EquipSlot slot)
=> uint.MaxValue - 256 - (uint)slot.ToSlot();
public static uint NothingId(FullEquipType type)
=> uint.MaxValue - 384 - (uint)type;
public static Designs.Item NothingItem(EquipSlot slot)
{
Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(NothingItem)} on {slot}.");
return new Designs.Item(Nothing, NothingId(slot), CharacterArmor.Empty);
}
public static Designs.Weapon NothingItem(FullEquipType type)
{
Debug.Assert(type.ToSlot() == EquipSlot.OffHand, $"Called {nameof(NothingItem)} on {type}.");
return new Designs.Weapon(Nothing, NothingId(type), CharacterWeapon.Empty, type);
}
public static Designs.Item SmallClothesItem(EquipSlot slot)
{
Debug.Assert(slot.IsEquipment(), $"Called {nameof(SmallClothesItem)} on {slot}.");
return new Designs.Item(SmallClothesNpc, SmallclothesId(slot), new CharacterArmor(SmallClothesNpcModel, 1, 0));
}
public (bool Valid, SetId Id, byte Variant, string ItemName) Resolve(EquipSlot slot, uint itemId, Item? item = null)
{
slot = slot.ToSlot();
if (itemId == NothingId(slot))
return (true, 0, 0, Nothing);
if (itemId == SmallclothesId(slot))
return (true, SmallClothesNpcModel, 1, SmallClothesNpc);
if (item == null || item.RowId != itemId)
item = ItemSheet.GetRow(itemId);
if (item == null)
return (false, 0, 0, string.Intern($"Unknown #{itemId}"));
if (item.ToEquipType().ToSlot() != slot)
return (false, 0, 0, string.Intern($"Invalid ({item.Name.ToDalamudString()})"));
return (true, (SetId)item.ModelMain, (byte)(item.ModelMain >> 16), string.Intern(item.Name.ToDalamudString().TextValue));
}
public (bool Valid, SetId Id, WeaponType Weapon, byte Variant, string ItemName, FullEquipType Type) Resolve(uint itemId, Item? item = null)
{
if (item == null || item.RowId != itemId)
item = ItemSheet.GetRow(itemId);
if (item == null)
return (false, 0, 0, 0, string.Intern($"Unknown #{itemId}"), FullEquipType.Unknown);
var type = item.ToEquipType();
if (type.ToSlot() != EquipSlot.MainHand)
return (false, 0, 0, 0, string.Intern($"Invalid ({item.Name.ToDalamudString()})"), type);
return (true, (SetId)item.ModelMain, (WeaponType)(item.ModelMain >> 16), (byte)(item.ModelMain >> 32),
string.Intern(item.Name.ToDalamudString().TextValue), type);
}
public (bool Valid, SetId Id, WeaponType Weapon, byte Variant, string ItemName, FullEquipType Type) Resolve(uint itemId,
FullEquipType mainType, Item? item = null)
{
var offType = mainType.Offhand();
if (itemId == NothingId(offType))
return (true, 0, 0, 0, Nothing, offType);
if (item == null || item.RowId != itemId)
item = ItemSheet.GetRow(itemId);
if (item == null)
return (false, 0, 0, 0, string.Intern($"Unknown #{itemId}"), FullEquipType.Unknown);
var type = item.ToEquipType();
if (offType != type)
return (false, 0, 0, 0, string.Intern($"Invalid ({item.Name.ToDalamudString()})"), type);
var (m, w, v) = offType.ToSlot() == EquipSlot.MainHand
? ((SetId)item.ModelSub, (WeaponType)(item.ModelSub >> 16), (byte)(item.ModelSub >> 32))
: ((SetId)item.ModelMain, (WeaponType)(item.ModelMain >> 16), (byte)(item.ModelMain >> 32));
return (true, m, w, v, string.Intern(item.Name.ToDalamudString().TextValue), type);
}
public (bool Valid, uint ItemId, string ItemName) Identify(EquipSlot slot, SetId id, byte variant)
{
slot = slot.ToSlot();
if (!slot.IsEquipmentPiece())
return (false, 0, string.Intern($"Unknown ({id.Value}-{variant})"));
switch (id.Value)
{
case 0: return (true, NothingId(slot), Nothing);
case SmallClothesNpcModel: return (true, SmallclothesId(slot), SmallClothesNpc);
default:
var item = IdentifierService.AwaitedService.Identify(id, variant, slot).FirstOrDefault();
return item == null
? (false, 0, string.Intern($"Unknown ({id.Value}-{variant})"))
: (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue));
}
}
public (bool Valid, uint ItemId, string ItemName, FullEquipType Type) Identify(EquipSlot slot, SetId id, WeaponType type, byte variant,
FullEquipType mainhandType = FullEquipType.Unknown)
{
switch (slot)
{
case EquipSlot.MainHand:
{
var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault();
return item != null
? (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue), item.ToEquipType())
: (false, 0, string.Intern($"Unknown ({id.Value}-{type.Value}-{variant})"), mainhandType);
}
case EquipSlot.OffHand:
{
var weaponType = mainhandType.Offhand();
if (id.Value == 0)
return (true, NothingId(weaponType), Nothing, weaponType);
var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault();
return item != null
? (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue), item.ToEquipType())
: (false, 0, string.Intern($"Unknown ({id.Value}-{type.Value}-{variant})"),
weaponType);
}
default: return (false, 0, string.Intern($"Unknown ({id.Value}-{type.Value}-{variant})"), FullEquipType.Unknown);
}
}
}

View file

@ -1,20 +0,0 @@
using System;
using System.IO;
using System.Text;
using OtterGui.Classes;
using OtterGui.Log;
namespace Glamourer.Services;
/// <summary>
/// Any file type that we want to save via SaveService.
/// </summary>
public interface ISavable : ISavable<FilenameService>
{ }
public sealed class SaveService : SaveServiceBase<FilenameService>
{
public SaveService(Logger log, FrameworkManager framework, FilenameService fileNames)
: base(log, framework, fileNames)
{ }
}

View file

@ -1,80 +0,0 @@
using Dalamud.Plugin;
using Glamourer.Api;
using Glamourer.Designs;
using Glamourer.Gui;
using Glamourer.Interop;
using Glamourer.State;
using Microsoft.Extensions.DependencyInjection;
using OtterGui.Classes;
using OtterGui.Log;
namespace Glamourer.Services;
public static class ServiceManager
{
public static ServiceProvider CreateProvider(DalamudPluginInterface pi, Logger log)
{
var services = new ServiceCollection()
.AddSingleton(log)
.AddDalamud(pi)
.AddMeta()
.AddConfig()
.AddPenumbra()
.AddInterop()
.AddGameData()
.AddDesigns()
.AddInterface()
.AddApi();
return services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true });
}
private static IServiceCollection AddDalamud(this IServiceCollection services, DalamudPluginInterface pi)
{
new DalamudServices(pi).AddServices(services);
return services;
}
private static IServiceCollection AddMeta(this IServiceCollection services)
=> services.AddSingleton<FilenameService>()
.AddSingleton<SaveService>()
.AddSingleton<FrameworkManager>()
.AddSingleton<ChatService>();
private static IServiceCollection AddConfig(this IServiceCollection services)
=> services.AddSingleton<ConfigurationOld>()
.AddSingleton<BackupService>();
private static IServiceCollection AddPenumbra(this IServiceCollection services)
=> services.AddSingleton<PenumbraAttach>();
private static IServiceCollection AddGameData(this IServiceCollection services)
=> services.AddSingleton<IdentifierService>()
.AddSingleton<ActorService>()
.AddSingleton<ItemService>()
.AddSingleton<ItemManager>()
.AddSingleton<CustomizationService>();
private static IServiceCollection AddInterop(this IServiceCollection services)
=> services.AddSingleton<ChangeCustomizeService>()
.AddSingleton<JobService>()
.AddSingleton<UpdateSlotService>()
.AddSingleton<VisorService>()
.AddSingleton<WeaponService>()
.AddSingleton<ObjectManager>();
private static IServiceCollection AddDesigns(this IServiceCollection services)
=> services.AddSingleton<DesignManager>()
.AddSingleton<DesignFileSystem>()
.AddSingleton<ActiveDesign.Manager>()
.AddSingleton<FixedDesignManager>()
.AddSingleton<RedrawManager>();
private static IServiceCollection AddInterface(this IServiceCollection services)
=> services.AddSingleton<Interface>()
.AddSingleton<GlamourerWindowSystem>();
private static IServiceCollection AddApi(this IServiceCollection services)
=> services.AddSingleton<CommandService>()
.AddSingleton<Glamourer.GlamourerIpc>();
}

View file

@ -1,105 +0,0 @@
using Dalamud.Data;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState;
using Dalamud.Game.Gui;
using Dalamud.Plugin;
using Penumbra.GameData.Actors;
using System;
using System.Threading.Tasks;
using Dalamud.Game;
using Glamourer.Api;
using Glamourer.Customization;
using Penumbra.GameData.Data;
using Penumbra.GameData;
namespace Glamourer.Services;
public abstract class AsyncServiceWrapper<T>
{
public string Name { get; }
public T? Service { get; private set; }
public T AwaitedService
{
get
{
_task?.Wait();
return Service!;
}
}
public bool Valid
=> Service != null && !_isDisposed;
public event Action? FinishedCreation;
private Task? _task;
private bool _isDisposed;
protected AsyncServiceWrapper(string name, Func<T> factory)
{
Name = name;
_task = Task.Run(() =>
{
var service = factory();
if (_isDisposed)
{
if (service is IDisposable d)
d.Dispose();
}
else
{
Service = service;
Glamourer.Log.Verbose($"[{Name}] Created.");
_task = null;
}
});
_task.ContinueWith((t, x) =>
{
if (!_isDisposed)
FinishedCreation?.Invoke();
}, null);
}
public void Dispose()
{
if (_isDisposed)
return;
_isDisposed = true;
_task = null;
if (Service is IDisposable d)
d.Dispose();
Glamourer.Log.Verbose($"[{Name}] Disposed.");
}
}
public sealed class IdentifierService : AsyncServiceWrapper<IObjectIdentifier>
{
public IdentifierService(DalamudPluginInterface pi, DataManager data)
: base(nameof(IdentifierService), () => Penumbra.GameData.GameData.GetIdentifier(pi, data))
{ }
}
public sealed class ItemService : AsyncServiceWrapper<ItemData>
{
public ItemService(DalamudPluginInterface pi, DataManager gameData)
: base(nameof(ItemService), () => new ItemData(pi, gameData, gameData.Language))
{ }
}
public sealed class ActorService : AsyncServiceWrapper<ActorManager>
{
public ActorService(DalamudPluginInterface pi, ObjectTable objects, ClientState clientState, Framework framework, DataManager gameData,
GameGui gui, PenumbraAttach penumbra)
: base(nameof(ActorService),
() => new ActorManager(pi, objects, clientState, framework, gameData, gui, idx => (short)penumbra.CutsceneParent(idx)))
{ }
}
public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationManager>
{
public CustomizationService(DalamudPluginInterface pi, DataManager gameData)
: base(nameof(CustomizationService), () => CustomizationManager.Create(pi, gameData))
{ }
}

View file

@ -1,305 +0,0 @@
using System;
using System.Collections;
using Glamourer.Interop;
using Penumbra.GameData.Actors;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Api;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Services;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.State;
public sealed partial class ActiveDesign
{
[Flags]
public enum ChangeType
{
Default = 0x00,
Changed = 0x01,
Fixed = 0x02,
}
public class Manager : IReadOnlyDictionary<ActorIdentifier, ActiveDesign>
{
private readonly ActorService _actors;
private readonly ObjectManager _objects;
private readonly PenumbraAttach _penumbra;
private readonly ItemManager _items;
private readonly VisorService _visor;
private readonly ChangeCustomizeService _customize;
private readonly UpdateSlotService _updateSlot;
private readonly WeaponService _weaponService;
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
public Manager(ActorService actors, ObjectManager objects, PenumbraAttach penumbra, ItemManager items, VisorService visor,
ChangeCustomizeService customize, UpdateSlotService updateSlot, WeaponService weaponService)
{
_actors = actors;
_objects = objects;
_penumbra = penumbra;
_items = items;
_visor = visor;
_customize = customize;
_updateSlot = updateSlot;
_weaponService = weaponService;
}
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
=> _characterSaves.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> _characterSaves.Count;
public bool ContainsKey(ActorIdentifier key)
=> _characterSaves.ContainsKey(key);
public bool TryGetValue(ActorIdentifier key, [NotNullWhen(true)] out ActiveDesign? value)
=> _characterSaves.TryGetValue(key, out value);
public ActiveDesign this[ActorIdentifier key]
=> _characterSaves[key];
public IEnumerable<ActorIdentifier> Keys
=> _characterSaves.Keys;
public IEnumerable<ActiveDesign> Values
=> _characterSaves.Values;
public void DeleteSave(ActorIdentifier identifier)
=> _characterSaves.Remove(identifier);
public unsafe ActiveDesign GetOrCreateSave(Actor actor)
{
var id = _actors.AwaitedService.FromObject((GameObject*)actor.Pointer, out _, false, false, false);
if (_characterSaves.TryGetValue(id, out var save))
{
save.Initialize(_items, actor);
return save;
}
id = id.CreatePermanent();
save = new ActiveDesign(_items, id, actor);
save.Initialize(_items, actor);
_characterSaves.Add(id, save);
return save;
}
public void SetWetness(ActiveDesign design, bool wet, bool fromFixed)
=> design.IsWet = wet;
public void SetHatVisible(ActiveDesign design, bool visible, bool fromFixed)
=> design.IsHatVisible = visible;
public void SetVisor(ActiveDesign design, bool toggled, bool fromFixed)
=> design.IsVisorToggled = toggled;
public void SetWeaponVisible(ActiveDesign design, bool visible, bool fromFixed)
=> design.IsWeaponVisible = visible;
public unsafe void ApplyDesign(ActiveDesign to, Design from, bool fromFixed)
{
if (to.ModelId != from.ModelId)
return;
if (from.DoApplyEquip(EquipSlot.MainHand))
ChangeMainHand(to, from.MainHandId, fromFixed);
if (from.DoApplyStain(EquipSlot.MainHand))
ChangeStain(to, EquipSlot.MainHand, from.WeaponMain.Stain, fromFixed);
if (from.DoApplyEquip(EquipSlot.OffHand))
ChangeOffHand(to, from.OffHandId, fromFixed);
if (from.DoApplyStain(EquipSlot.OffHand))
ChangeStain(to, EquipSlot.OffHand, from.WeaponOff.Stain, fromFixed);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var armor = from.Armor(slot);
if (from.DoApplyEquip(slot))
ChangeEquipment(to, slot, armor, fromFixed);
if (from.DoApplyStain(slot))
ChangeStain(to, slot, armor.Stain, fromFixed);
}
ChangeCustomize(to, from.ApplyCustomize, from.ModelData.Customize.Data, fromFixed);
if (from.Wetness.Enabled)
SetWetness(to, from.Wetness.ForcedValue, fromFixed);
if (from.Hat.Enabled)
SetHatVisible(to, from.Hat.ForcedValue, fromFixed);
if (from.Visor.Enabled)
SetVisor(to, from.Visor.ForcedValue, fromFixed);
if (from.Weapon.Enabled)
SetWeaponVisible(to, from.Weapon.ForcedValue, fromFixed);
}
public void RevertDesign(ActiveDesign design)
{
RevertCustomize(design, design.ChangedCustomize);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
RevertEquipment(design, slot, design.ChangedEquip.HasFlag(slot.ToFlag()), design.ChangedEquip.HasFlag(slot.ToStainFlag()));
RevertMainHand(design);
RevertOffHand(design);
}
public void ChangeMainHand(ActiveDesign design, uint itemId, bool fromFixed)
=> design.SetMainhand(_items, itemId);
public void ChangeOffHand(ActiveDesign design, uint itemId, bool fromFixed)
=> design.SetOffhand(_items, itemId);
public void RevertMainHand(ActiveDesign design)
{ }
public void RevertOffHand(ActiveDesign design)
{ }
public void RevertCustomize(ActiveDesign design, CustomizeFlag flags)
=> ChangeCustomize(design, flags, design._initialData.Customize.Data, false);
public void ChangeCustomize(ActiveDesign design, CustomizeFlag flags, CustomizeData newValue, bool fromFixed)
{
var customize = new Customize(newValue);
var anyChanges = false;
foreach (var option in Enum.GetValues<CustomizeIndex>())
{
var flag = option.ToFlag();
var apply = flags.HasFlag(flag);
anyChanges |= apply && design.SetCustomize(option, customize[option]);
if (design.GetCustomize(option).Value != design._initialData.Customize[option].Value)
design.ChangedCustomize |= flag;
else
design.ChangedCustomize &= ~flag;
if (fromFixed)
design.FixedCustomize |= flag;
else
design.FixedCustomize &= ~flag;
}
if (!anyChanges)
return;
_objects.Update();
if (!_objects.TryGetValue(design.Identifier, out var data))
return;
var redraw = flags.RequiresRedraw();
foreach (var obj in data.Objects)
{
if (redraw)
_penumbra.RedrawObject(obj, RedrawType.Redraw);
else
_customize.UpdateCustomize(obj, design.ModelData.Customize.Data);
}
}
public void RevertEquipment(ActiveDesign design, EquipSlot slot, bool equip, bool stain)
{
var item = design._initialData.Armor(slot);
if (equip)
{
var flag = slot.ToFlag();
design.UpdateArmor(_items, slot, item, true);
design.ChangedEquip &= ~flag;
design.FixedEquip &= ~flag;
}
if (stain)
{
var flag = slot.ToStainFlag();
design.SetStain(slot, item.Stain);
design.ChangedEquip &= ~flag;
design.FixedEquip &= ~flag;
}
_objects.Update();
if (!_objects.TryGetValue(design.Identifier, out var data))
return;
foreach (var obj in data.Objects)
_updateSlot.UpdateSlot(obj.DrawObject, slot, item);
}
public void ChangeEquipment(ActiveDesign design, EquipSlot slot, Item item, bool fromFixed)
{
var flag = slot.ToFlag();
design.SetArmor(slot, item);
var current = design.Armor(slot);
var initial = design._initialData.Armor(slot);
if (current.ModelBase.Value != initial.Set.Value || current.Variant != initial.Variant)
design.ChangedEquip |= flag;
else
design.ChangedEquip &= ~flag;
if (fromFixed)
design.FixedEquip |= flag;
else
design.FixedEquip &= ~flag;
_objects.Update();
if (!_objects.TryGetValue(design.Identifier, out var data))
return;
foreach (var obj in data.Objects)
_updateSlot.UpdateSlot(obj.DrawObject, slot, item.Model);
}
public void ChangeStain(ActiveDesign design, EquipSlot slot, StainId stain, bool fromFixed)
{
var flag = slot.ToStainFlag();
design.SetStain(slot, stain);
var (current, initial, weapon) = slot switch
{
EquipSlot.MainHand => (design.WeaponMain.Stain, design._initialData.MainHand.Stain, true),
EquipSlot.OffHand => (design.WeaponOff.Stain, design._initialData.OffHand.Stain, true),
_ => (design.Armor(slot).Stain, design._initialData.Armor(slot).Stain, false),
};
if (current.Value != initial.Value)
design.ChangedEquip |= flag;
else
design.ChangedEquip &= ~flag;
if (fromFixed)
design.FixedEquip |= flag;
else
design.FixedEquip &= ~flag;
_objects.Update();
if (!_objects.TryGetValue(design.Identifier, out var data))
return;
foreach (var obj in data.Objects)
{
if (weapon)
_weaponService.LoadStain(obj, EquipSlot.MainHand, stain);
else
_updateSlot.UpdateStain(obj.DrawObject, slot, stain);
}
}
public void ChangeVisor(ActiveDesign design, bool on, bool fromFixed)
{
var current = design.IsVisorToggled;
if (current == on)
return;
design.IsVisorToggled = on;
_objects.Update();
if (!_objects.TryGetValue(design.Identifier, out var data))
return;
foreach (var obj in data.Objects)
_visor.SetVisorState(obj.DrawObject, on);
}
}
}

View file

@ -1,117 +0,0 @@
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Interop;
using Glamourer.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
namespace Glamourer.State;
public sealed partial class ActiveDesign : DesignData
{
public readonly ActorIdentifier Identifier;
private ModelData _initialData = new();
public CustomizeFlag ChangedCustomize { get; private set; } = 0;
public CustomizeFlag FixedCustomize { get; private set; } = 0;
public EquipFlag ChangedEquip { get; private set; } = 0;
public EquipFlag FixedEquip { get; private set; } = 0;
public bool IsHatVisible { get; private set; } = false;
public bool IsWeaponVisible { get; private set; } = false;
public bool IsVisorToggled { get; private set; } = false;
public bool IsWet { get; private set; } = false;
private ActiveDesign(ItemManager items, ActorIdentifier identifier)
: base(items)
=> Identifier = identifier;
public ActiveDesign(ItemManager items, ActorIdentifier identifier, Actor actor)
: base(items)
{
Identifier = identifier;
Initialize(items, actor);
}
//public void ApplyToActor(Actor actor)
//{
// if (!actor)
// return;
//
// void Redraw()
// => Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
//
// if (_drawData.ModelId != actor.ModelId)
// {
// Redraw();
// return;
// }
//
// var customize1 = _drawData.Customize;
// var customize2 = actor.Customize;
// if (RedrawManager.NeedsRedraw(customize1, customize2))
// {
// Redraw();
// return;
// }
//
// Glamourer.RedrawManager.UpdateCustomize(actor, customize2);
// foreach (var slot in EquipSlotExtensions.EqdpSlots)
// Glamourer.RedrawManager.ChangeEquip(actor, slot, actor.Equip[slot]);
// Glamourer.RedrawManager.LoadWeapon(actor, actor.MainHand, actor.OffHand);
// if (actor.IsHuman && actor.DrawObject)
// RedrawManager.SetVisor(actor.DrawObject.Pointer, actor.VisorEnabled);
//}
//
public void Initialize(ItemManager items, Actor actor)
{
if (!actor)
return;
if (!_initialData.Customize.Equals(actor.Customize))
{
_initialData.Customize.Load(actor.Customize);
ModelData.Customize.Load(actor.Customize);
}
var currentEquip = actor.Equip;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var current = currentEquip[slot];
if (_initialData.Armor(slot) != current)
{
_initialData.SetPiece(slot, current.Set, current.Variant, current.Stain, out _);
UpdateArmor(items, slot, current, true);
SetStain(slot, current.Stain);
}
}
if (_initialData.MainHand != actor.MainHand)
{
_initialData.MainHand = actor.MainHand;
UpdateMainhand(items, actor.MainHand);
SetStain(EquipSlot.MainHand, actor.MainHand.Stain);
}
if (_initialData.OffHand != actor.OffHand)
{
_initialData.OffHand = actor.OffHand;
UpdateOffhand(items, actor.OffHand);
SetStain(EquipSlot.OffHand, actor.OffHand.Stain);
}
var visor = VisorService.GetVisorState(actor.DrawObject);
if (IsVisorToggled != visor)
IsVisorToggled = visor;
}
public string CreateOldBase64()
=> DesignBase64Migration.CreateOldBase64(in ModelData, EquipFlagExtensions.All, CustomizeFlagExtensions.All, IsWet, IsHatVisible, true,
IsVisorToggled,
true, IsWeaponVisible, true, false, 1f);
}

View file

@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
using Glamourer.Designs;
using Glamourer.Interop;
using Glamourer.Structs;
using Penumbra.GameData.Actors;
namespace Glamourer.State;
public class FixedDesignManager
{
public class FixedDesign
{
public Design Design = null!;
public byte? JobCondition;
public ushort? TerritoryCondition;
public bool Applies(byte job, ushort territoryType)
=> (!JobCondition.HasValue || JobCondition.Value == job)
&& (!TerritoryCondition.HasValue || TerritoryCondition.Value == territoryType);
}
public IReadOnlyList<FixedDesign> GetDesigns(ActorIdentifier actor)
{
return Array.Empty<FixedDesign>();
}
}

View file

@ -1,72 +0,0 @@
using Dalamud.Game.ClientState.Objects.Types;
using Glamourer.Interop;
namespace Glamourer.Util;
public static class CharacterExtensions
{
public static unsafe bool IsWet(this Character a)
=> ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*) a.Address)->IsGPoseWet;
public static unsafe bool SetWetness(this Character a, bool value)
{
var current = a.IsWet();
if (current == value)
return false;
((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)a.Address)->IsGPoseWet = value;
return true;
}
public static unsafe bool IsHatVisible(this Character a)
=> !((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)a.Address)->DrawData.IsHatHidden;
public static unsafe bool SetHatVisible(this Character a, bool visible)
{
var current = a.IsHatVisible();
if (current == visible)
return false;
((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)a.Address)->DrawData.IsHatHidden = !visible;
return true;
}
public static unsafe bool IsVisorToggled(this Character a)
=> ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)a.Address)->DrawData.IsVisorToggled;
public static unsafe bool SetVisorToggled(this Character a, bool toggled)
{
var current = a.IsVisorToggled();
if (current == toggled)
return false;
((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)a.Address)->DrawData.IsVisorToggled = toggled;
return true;
}
public static unsafe bool IsWeaponHidden(this Character a)
=> ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)a.Address)->DrawData.IsWeaponHidden
&& ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)a.Address)->DrawData.IsMainHandHidden;
public static unsafe bool SetWeaponHidden(this Character a, bool value)
{
var hidden = a.IsWeaponHidden();
if (hidden == value)
return false;
var drawData = &((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)a.Address)->DrawData;
drawData->IsWeaponHidden = value;
drawData->IsMainHandHidden = value;
// TODO
if (value)
*(&drawData->MainHandState + ((long) &drawData->OffHandModel - (long) &drawData->MainHandModel)) |= 0x02;
else
*(&drawData->MainHandState + ((long) &drawData->OffHandModel - (long) &drawData->MainHandModel)) &= 0xFD;
return true;
}
public static unsafe ref float Alpha(this Character a)
=> ref ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)a.Address)->Alpha;
}

View file

@ -1,127 +0,0 @@
using System;
using Glamourer.Customization;
using Glamourer.Services;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Util;
public static class CustomizeExtensions
{
// In languages other than english the actual clan name may depend on gender.
public static string ClanName(ICustomizationManager customization, SubRace race, Gender gender)
{
if (gender == Gender.FemaleNpc)
gender = Gender.Female;
if (gender == Gender.MaleNpc)
gender = Gender.Male;
return (gender, race) switch
{
(Gender.Male, SubRace.Midlander) => customization.GetName(CustomName.MidlanderM),
(Gender.Male, SubRace.Highlander) => customization.GetName(CustomName.HighlanderM),
(Gender.Male, SubRace.Wildwood) => customization.GetName(CustomName.WildwoodM),
(Gender.Male, SubRace.Duskwight) => customization.GetName(CustomName.DuskwightM),
(Gender.Male, SubRace.Plainsfolk) => customization.GetName(CustomName.PlainsfolkM),
(Gender.Male, SubRace.Dunesfolk) => customization.GetName(CustomName.DunesfolkM),
(Gender.Male, SubRace.SeekerOfTheSun) => customization.GetName(CustomName.SeekerOfTheSunM),
(Gender.Male, SubRace.KeeperOfTheMoon) => customization.GetName(CustomName.KeeperOfTheMoonM),
(Gender.Male, SubRace.Seawolf) => customization.GetName(CustomName.SeawolfM),
(Gender.Male, SubRace.Hellsguard) => customization.GetName(CustomName.HellsguardM),
(Gender.Male, SubRace.Raen) => customization.GetName(CustomName.RaenM),
(Gender.Male, SubRace.Xaela) => customization.GetName(CustomName.XaelaM),
(Gender.Male, SubRace.Helion) => customization.GetName(CustomName.HelionM),
(Gender.Male, SubRace.Lost) => customization.GetName(CustomName.LostM),
(Gender.Male, SubRace.Rava) => customization.GetName(CustomName.RavaM),
(Gender.Male, SubRace.Veena) => customization.GetName(CustomName.VeenaM),
(Gender.Female, SubRace.Midlander) => customization.GetName(CustomName.MidlanderF),
(Gender.Female, SubRace.Highlander) => customization.GetName(CustomName.HighlanderF),
(Gender.Female, SubRace.Wildwood) => customization.GetName(CustomName.WildwoodF),
(Gender.Female, SubRace.Duskwight) => customization.GetName(CustomName.DuskwightF),
(Gender.Female, SubRace.Plainsfolk) => customization.GetName(CustomName.PlainsfolkF),
(Gender.Female, SubRace.Dunesfolk) => customization.GetName(CustomName.DunesfolkF),
(Gender.Female, SubRace.SeekerOfTheSun) => customization.GetName(CustomName.SeekerOfTheSunF),
(Gender.Female, SubRace.KeeperOfTheMoon) => customization.GetName(CustomName.KeeperOfTheMoonF),
(Gender.Female, SubRace.Seawolf) => customization.GetName(CustomName.SeawolfF),
(Gender.Female, SubRace.Hellsguard) => customization.GetName(CustomName.HellsguardF),
(Gender.Female, SubRace.Raen) => customization.GetName(CustomName.RaenF),
(Gender.Female, SubRace.Xaela) => customization.GetName(CustomName.XaelaF),
(Gender.Female, SubRace.Helion) => customization.GetName(CustomName.HelionM),
(Gender.Female, SubRace.Lost) => customization.GetName(CustomName.LostM),
(Gender.Female, SubRace.Rava) => customization.GetName(CustomName.RavaF),
(Gender.Female, SubRace.Veena) => customization.GetName(CustomName.VeenaF),
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
};
}
public static string ClanName(this Customize customize, ICustomizationManager customization)
=> ClanName(customization, customize.Clan, customize.Gender);
// Change a gender and fix up all required customizations afterwards.
public static CustomizeFlag ChangeGender(this Customize customize, CharacterEquip equip, Gender gender, ItemManager items, ICustomizationManager customization)
{
if (customize.Gender == gender)
return 0;
FixRestrictedGear(items, customize, equip, gender, customize.Race);
customize.Gender = gender;
return CustomizeFlag.Gender | FixUpAttributes(customization, customize);
}
// Change a race and fix up all required customizations afterwards.
public static CustomizeFlag ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan, ItemManager items, ICustomizationManager customization)
{
if (customize.Clan == clan)
return 0;
var race = clan.ToRace();
var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar
FixRestrictedGear(items, customize, equip, gender, race);
var flags = CustomizeFlag.Race | CustomizeFlag.Clan;
if (gender != customize.Gender)
flags |= CustomizeFlag.Gender;
customize.Gender = gender;
customize.Race = race;
customize.Clan = clan;
return flags | FixUpAttributes(customization, customize);
}
// Go through a whole customization struct and fix up all settings that need fixing.
private static CustomizeFlag FixUpAttributes(ICustomizationManager customization, Customize customize)
{
var set = customization.GetList(customize.Clan, customize.Gender);
CustomizeFlag flags = 0;
foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex)))
{
switch (id)
{
case CustomizeIndex.Race: break;
case CustomizeIndex.Clan: break;
case CustomizeIndex.BodyType: break;
case CustomizeIndex.Gender: break;
case CustomizeIndex.Highlights: break;
case CustomizeIndex.Face: break;
default:
var count = set.Count(id);
if (set.DataByValue(id, customize[id], out _, customize.Face) < 0)
{
customize[id] = count == 0 ? CustomizeValue.Zero : set.Data(id, 0).Value;
flags |= id.ToFlag();
}
break;
}
}
return flags;
}
private static void FixRestrictedGear(ItemManager items, Customize customize, CharacterEquip equip, Gender gender, Race race)
{
if (!equip || race == customize.Race && gender == customize.Gender)
return;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
(_, equip[slot]) = items.ResolveRestrictedGear(equip[slot], slot, race, gender);
}
}