This commit is contained in:
Ottermandias 2023-07-02 15:34:27 +02:00
parent b63b02ae5e
commit 60443f6a53
17 changed files with 970 additions and 225 deletions

View file

@ -1,6 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.AccessControl;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
@ -9,8 +8,7 @@ using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Structs;
using Lumina.Excel.GeneratedSheets;
using Newtonsoft.Json.Linq;
using Glamourer.Unlocks;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
@ -18,25 +16,29 @@ namespace Glamourer.Automation;
public class AutoDesignApplier : IDisposable
{
private readonly Configuration _config;
private readonly AutoDesignManager _manager;
private readonly PhrasingService _phrasing;
private readonly StateManager _state;
private readonly JobService _jobs;
private readonly ActorService _actors;
private readonly CustomizationService _customizations;
private readonly Configuration _config;
private readonly AutoDesignManager _manager;
private readonly CodeService _code;
private readonly StateManager _state;
private readonly JobService _jobs;
private readonly ActorService _actors;
private readonly CustomizationService _customizations;
private readonly CustomizeUnlockManager _customizeUnlocks;
private readonly ItemUnlockManager _itemUnlocks;
public AutoDesignApplier(Configuration config, AutoDesignManager manager, PhrasingService phrasing, StateManager state, JobService jobs,
CustomizationService customizations, ActorService actors)
public AutoDesignApplier(Configuration config, AutoDesignManager manager, CodeService code, StateManager state, JobService jobs,
CustomizationService customizations, ActorService actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks)
{
_config = config;
_manager = manager;
_phrasing = phrasing;
_state = state;
_jobs = jobs;
_customizations = customizations;
_actors = actors;
_jobs.JobChanged += OnJobChange;
_config = config;
_manager = manager;
_code = code;
_state = state;
_jobs = jobs;
_customizations = customizations;
_actors = actors;
_itemUnlocks = itemUnlocks;
_customizeUnlocks = customizeUnlocks;
_jobs.JobChanged += OnJobChange;
}
public void Dispose()
@ -79,7 +81,7 @@ public class AutoDesignApplier : IDisposable
private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual)
{
EquipFlag totalEquipFlags = 0;
var totalCustomizeFlags = _phrasing.Phrasing2 ? 0 : CustomizeFlagExtensions.RedrawRequired;
var totalCustomizeFlags = _code.EnabledMesmer ? 0 : CustomizeFlagExtensions.RedrawRequired;
byte totalMetaFlags = 0;
foreach (var design in set.Designs)
{
@ -118,15 +120,18 @@ public class AutoDesignApplier : IDisposable
if (equipFlags == 0)
return;
// TODO add item conditions
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
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;
var item = design.Item(slot);
if (_code.EnabledInventory || _itemUnlocks.IsUnlocked(item.Id, out _))
{
if (!respectManual || state[slot, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, slot, item, StateChanged.Source.Fixed);
totalEquipFlags |= flag;
}
}
var stainFlag = slot.ToStainFlag();
@ -141,7 +146,8 @@ public class AutoDesignApplier : IDisposable
if (equipFlags.HasFlag(EquipFlag.Mainhand))
{
var item = design.Item(EquipSlot.MainHand);
if (state.ModelData.Item(EquipSlot.MainHand).Type == item.Type)
if (state.ModelData.Item(EquipSlot.MainHand).Type == item.Type
&& (_code.EnabledInventory || _itemUnlocks.IsUnlocked(item.Id, out _)))
{
if (!respectManual || state[EquipSlot.MainHand, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, EquipSlot.MainHand, item, StateChanged.Source.Fixed);
@ -152,7 +158,8 @@ public class AutoDesignApplier : IDisposable
if (equipFlags.HasFlag(EquipFlag.Offhand))
{
var item = design.Item(EquipSlot.OffHand);
if (state.ModelData.Item(EquipSlot.OffHand).Type == item.Type)
if (state.ModelData.Item(EquipSlot.OffHand).Type == item.Type
&& (_code.EnabledInventory || _itemUnlocks.IsUnlocked(item.Id, out _)))
{
if (!respectManual || state[EquipSlot.OffHand, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, EquipSlot.OffHand, item, StateChanged.Source.Fixed);
@ -220,7 +227,8 @@ public class AutoDesignApplier : IDisposable
continue;
var value = design.Customize[index];
if (CustomizationService.IsCustomizationValid(set, face, index, value))
if (CustomizationService.IsCustomizationValid(set, face, index, value, out var data)
&& (_code.EnabledInventory || _customizeUnlocks.IsUnlocked(data.Value, out _)))
{
if (!respectManual || state[index] is not StateChanged.Source.Manual)
_state.ChangeCustomize(state, index, value, StateChanged.Source.Fixed);

View file

@ -23,6 +23,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool AutoRedrawEquipOnChanges { get; set; } = false;
public bool EnableAutoDesigns { get; set; } = true;
public bool IncognitoMode { get; set; } = false;
public bool UnlockDetailMode { get; set; } = true;
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
@ -30,8 +31,7 @@ public class Configuration : IPluginConfiguration, ISavable
[JsonProperty(Order = int.MaxValue)]
public ISortMode<Design> SortMode { get; set; } = ISortMode<Design>.FoldersFirst;
public string Phrasing1 { get; set; } = string.Empty;
public string Phrasing2 { get; set; } = string.Empty;
public List<(string Code, bool Enabled)> Codes { get; set; } = new List<(string Code, bool Enabled)>();
#if DEBUG
public bool DebugMode { get; set; } = true;

View file

@ -6,6 +6,7 @@ using Glamourer.Gui.Tabs;
using Glamourer.Gui.Tabs.ActorTab;
using Glamourer.Gui.Tabs.AutomationTab;
using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Gui.Tabs.UnlocksTab;
using ImGuiNET;
using OtterGui.Custom;
using OtterGui.Widgets;
@ -22,6 +23,7 @@ public class MainWindow : Window
Actors = 2,
Designs = 3,
Automation = 4,
Unlocks = 5,
}
private readonly Configuration _config;
@ -32,11 +34,12 @@ public class MainWindow : Window
public readonly DebugTab Debug;
public readonly DesignTab Designs;
public readonly AutomationTab Automation;
public readonly UnlocksTab Unlocks;
public TabType SelectTab = TabType.None;
public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs,
DebugTab debugTab, AutomationTab automation)
DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks)
: base(GetLabel())
{
pi.UiBuilder.DisableGposeUiHide = true;
@ -50,6 +53,7 @@ public class MainWindow : Window
Designs = designs;
Automation = automation;
Debug = debugTab;
Unlocks = unlocks;
_config = config;
_tabs = new ITab[]
{
@ -57,6 +61,7 @@ public class MainWindow : Window
actors,
designs,
automation,
unlocks,
debugTab,
};
@ -81,6 +86,7 @@ public class MainWindow : Window
TabType.Actors => Actors.Label,
TabType.Designs => Designs.Label,
TabType.Automation => Automation.Label,
TabType.Unlocks => Unlocks.Label,
_ => ReadOnlySpan<byte>.Empty,
};
@ -91,6 +97,7 @@ public class MainWindow : Window
if (label == Designs.Label) return TabType.Designs;
if (label == Settings.Label) return TabType.Settings;
if (label == Automation.Label) return TabType.Automation;
if (label == Unlocks.Label) return TabType.Unlocks;
if (label == Debug.Label) return TabType.Debug;
// @formatter:on
return TabType.None;

View file

@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui.Raii;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -45,10 +48,115 @@ public class PenumbraChangedItemTooltip : IDisposable
_penumbra.Click -= OnPenumbraClick;
}
public bool Player()
=> _objects.Player.Valid;
public bool Player([NotNullWhen(true)] out ActorState? player)
{
var (identifier, data) = _objects.PlayerData;
if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out player))
{
player = null;
return false;
}
return true;
}
public void CreateTooltip(EquipItem item, string prefix, bool openTooltip)
{
var slot = item.Type.ToSlot();
var last = _lastItems[slot.ToIndex()];
switch (slot)
{
case EquipSlot.MainHand when !CanApplyWeapon(EquipSlot.MainHand, item):
case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item):
break;
case EquipSlot.RFinger:
using (var tt = !openTooltip ? null : ImRaii.Tooltip())
{
ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor (Right Finger).");
ImGui.TextUnformatted($"{prefix}Shift + Right-Click to apply to current actor (Left Finger).");
if (last.Valid)
ImGui.TextUnformatted(
$"{prefix}Control + Right-Click to re-apply {last.Name} to current actor (Right Finger).");
var last2 = _lastItems[EquipSlot.LFinger.ToIndex()];
if (last2.Valid)
ImGui.TextUnformatted(
$"{prefix}Shift + Control + Right-Click to re-apply {last.Name} to current actor (Left Finger).");
}
break;
default:
using (var tt = !openTooltip ? null : ImRaii.Tooltip())
{
ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor.");
if (last.Valid)
ImGui.TextUnformatted($"{prefix}Control + Right-Click to re-apply {last.Name} to current actor.");
}
break;
}
}
public void ApplyItem(ActorState state, EquipItem item)
{
var slot = item.Type.ToSlot();
var last = _lastItems[slot.ToIndex()];
switch (slot)
{
case EquipSlot.MainHand when !CanApplyWeapon(EquipSlot.MainHand, item):
case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item):
break;
case EquipSlot.RFinger:
switch (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift)
{
case (false, false):
Glamourer.Log.Information($"Applying {item.Name} to Right Finger.");
SetLastItem(EquipSlot.RFinger, item, state);
_stateManager.ChangeItem(state, EquipSlot.RFinger, item, StateChanged.Source.Manual);
break;
case (false, true):
Glamourer.Log.Information($"Applying {item.Name} to Left Finger.");
SetLastItem(EquipSlot.LFinger, item, state);
_stateManager.ChangeItem(state, EquipSlot.LFinger, item, StateChanged.Source.Manual);
break;
case (true, false) when last.Valid:
Glamourer.Log.Information($"Re-Applying {last.Name} to Right Finger.");
SetLastItem(EquipSlot.RFinger, default, state);
_stateManager.ChangeItem(state, EquipSlot.RFinger, last, StateChanged.Source.Manual);
break;
case (true, true) when _lastItems[EquipSlot.LFinger.ToIndex()].Valid:
Glamourer.Log.Information($"Re-Applying {last.Name} to Left Finger.");
SetLastItem(EquipSlot.LFinger, default, state);
_stateManager.ChangeItem(state, EquipSlot.LFinger, last, StateChanged.Source.Manual);
break;
}
return;
default:
if (ImGui.GetIO().KeyCtrl && last.Valid)
{
Glamourer.Log.Information($"Re-Applying {last.Name} to {slot.ToName()}.");
SetLastItem(slot, default, state);
_stateManager.ChangeItem(state, slot, last, StateChanged.Source.Manual);
}
else
{
Glamourer.Log.Information($"Applying {item.Name} to {slot.ToName()}.");
SetLastItem(slot, item, state);
_stateManager.ChangeItem(state, slot, item, StateChanged.Source.Manual);
}
return;
}
}
private void OnPenumbraTooltip(ChangedItemType type, uint id)
{
LastTooltip = DateTime.UtcNow;
if (!_objects.Player.Valid)
if (!Player())
return;
switch (type)
@ -57,33 +165,7 @@ public class PenumbraChangedItemTooltip : IDisposable
if (!_items.ItemService.AwaitedService.TryGetValue(id, out var item))
return;
var slot = item.Type.ToSlot();
var last = _lastItems[slot.ToIndex()];
switch (slot)
{
case EquipSlot.MainHand when !CanApplyWeapon(EquipSlot.MainHand, item):
case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item):
break;
case EquipSlot.RFinger:
ImGui.TextUnformatted("[Glamourer] Right-Click to apply to current actor (Right Finger).");
ImGui.TextUnformatted("[Glamourer] Shift + Right-Click to apply to current actor (Left Finger).");
if (last.Valid)
ImGui.TextUnformatted(
$"[Glamourer] Control + Right-Click to re-apply {last.Name} to current actor (Right Finger).");
var last2 = _lastItems[EquipSlot.LFinger.ToIndex()];
if (last2.Valid)
ImGui.TextUnformatted(
$"[Glamourer] Shift + Control + Right-Click to re-apply {last.Name} to current actor (Left Finger).");
break;
default:
ImGui.TextUnformatted("[Glamourer] Right-Click to apply to current actor.");
if (last.Valid)
ImGui.TextUnformatted($"[Glamourer] Control + Right-Click to re-apply {last.Name} to current actor.");
break;
}
CreateTooltip(item, "[Glamourer] ", false);
return;
}
}
@ -107,60 +189,13 @@ public class PenumbraChangedItemTooltip : IDisposable
if (button is not MouseButton.Right)
return;
var (identifier, data) = _objects.PlayerData;
if (!data.Valid)
return;
if (!_stateManager.GetOrCreate(identifier, data.Objects[0], out var state))
if (!Player(out var state))
return;
if (!_items.ItemService.AwaitedService.TryGetValue(id, out var item))
return;
var slot = item.Type.ToSlot();
var last = _lastItems[slot.ToIndex()];
switch (slot)
{
case EquipSlot.MainHand when !CanApplyWeapon(EquipSlot.MainHand, item):
case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item):
break;
case EquipSlot.RFinger:
switch (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift)
{
case (false, false):
Glamourer.Log.Information($"Applying {item.Name} to Right Finger.");
SetLastItem(EquipSlot.RFinger, item, state);
break;
case (false, true):
Glamourer.Log.Information($"Applying {item.Name} to Left Finger.");
SetLastItem(EquipSlot.LFinger, item, state);
break;
case (true, false) when last.Valid:
Glamourer.Log.Information($"Re-Applying {last.Name} to Right Finger.");
SetLastItem(EquipSlot.RFinger, default, state);
break;
case (true, true) when _lastItems[EquipSlot.LFinger.ToIndex()].Valid:
Glamourer.Log.Information($"Re-Applying {last.Name} to Left Finger.");
SetLastItem(EquipSlot.LFinger, default, state);
break;
}
return;
default:
if (ImGui.GetIO().KeyCtrl && last.Valid)
{
Glamourer.Log.Information($"Re-Applying {last.Name} to {slot.ToName()}.");
SetLastItem(slot, default, state);
}
else
{
Glamourer.Log.Information($"Applying {item.Name} to {slot.ToName()}.");
SetLastItem(slot, item, state);
}
return;
}
ApplyItem(state, item);
return;
}
}

View file

@ -42,7 +42,7 @@ public unsafe class DebugTab : ITab
private readonly ObjectTable _objects;
private readonly ObjectManager _objectManager;
private readonly GlamourerIpc _ipc;
private readonly PhrasingService _phrasing;
private readonly CodeService _code;
private readonly ItemManager _items;
private readonly ActorService _actors;
@ -69,7 +69,7 @@ public unsafe class DebugTab : ITab
ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager,
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface,
AutoDesignManager autoDesignManager, JobService jobs, PhrasingService phrasing, CustomizeUnlockManager customizeUnlocks,
AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks,
ItemUnlockManager itemUnlocks)
{
_changeCustomizeService = changeCustomizeService;
@ -92,7 +92,7 @@ public unsafe class DebugTab : ITab
_pluginInterface = pluginInterface;
_autoDesignManager = autoDesignManager;
_jobs = jobs;
_phrasing = phrasing;
_code = code;
_customizeUnlocks = customizeUnlocks;
_itemUnlocks = itemUnlocks;
}
@ -1190,8 +1190,6 @@ public unsafe class DebugTab : ITab
if (!ImGui.CollapsingHeader("Auto Designs"))
return;
DrawPhrasingService();
foreach (var (set, idx) in _autoDesignManager.WithIndex())
{
using var id = ImRaii.PushId(idx);
@ -1223,24 +1221,6 @@ public unsafe class DebugTab : ITab
}
}
private void DrawPhrasingService()
{
using var tree = ImRaii.TreeNode("Phrasing");
if (!tree)
return;
using var table = ImRaii.Table("phrasing", 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
ImGuiUtil.DrawTableColumn("Phrasing 1");
ImGuiUtil.DrawTableColumn(_config.Phrasing1);
ImGuiUtil.DrawTableColumn(_phrasing.Phrasing1.ToString());
ImGuiUtil.DrawTableColumn("Phrasing 2");
ImGuiUtil.DrawTableColumn(_config.Phrasing2);
ImGuiUtil.DrawTableColumn(_phrasing.Phrasing2.ToString());
}
#endregion
#region Unlocks
@ -1279,7 +1259,7 @@ public unsafe class DebugTab : ITab
ImGuiUtil.DrawTableColumn(t.Value.Data.ToString());
ImGuiUtil.DrawTableColumn(t.Value.Name);
ImGuiUtil.DrawTableColumn(_customizeUnlocks.IsUnlocked(t.Key, out var time)
? time == DateTimeOffset.MaxValue
? time == DateTimeOffset.MinValue
? "Always"
: time.LocalDateTime.ToString("g")
: "Never");
@ -1325,7 +1305,7 @@ public unsafe class DebugTab : ITab
}
ImGuiUtil.DrawTableColumn(_itemUnlocks.IsUnlocked(t.Key, out var time)
? time == DateTimeOffset.MaxValue
? time == DateTimeOffset.MinValue
? "Always"
: time.LocalDateTime.ToString("g")
: "Never");
@ -1372,7 +1352,7 @@ public unsafe class DebugTab : ITab
}
ImGuiUtil.DrawTableColumn(_itemUnlocks.IsUnlocked(t.Key, out var time)
? time == DateTimeOffset.MaxValue
? time == DateTimeOffset.MinValue
? "Always"
: time.LocalDateTime.ToString("g")
: "Never");

View file

@ -1,4 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using Dalamud.Interface;
using Glamourer.Gui.Tabs.DesignTab;
@ -17,24 +18,24 @@ public class SettingsTab : ITab
private readonly Configuration _config;
private readonly DesignFileSystemSelector _selector;
private readonly StateListener _stateListener;
private readonly PhrasingService _phrasingService;
private readonly CodeService _codeService;
private readonly PenumbraAutoRedraw _autoRedraw;
public SettingsTab(Configuration config, DesignFileSystemSelector selector, StateListener stateListener,
PhrasingService phrasingService, PenumbraAutoRedraw autoRedraw)
CodeService codeService, PenumbraAutoRedraw autoRedraw)
{
_config = config;
_selector = selector;
_stateListener = stateListener;
_phrasingService = phrasingService;
_autoRedraw = autoRedraw;
_config = config;
_selector = selector;
_stateListener = stateListener;
_codeService = codeService;
_autoRedraw = autoRedraw;
}
public ReadOnlySpan<byte> Label
=> "Settings"u8;
private string? _tmpPhrasing1 = null;
private string? _tmpPhrasing2 = null;
private string _currentCode = string.Empty;
public void DrawContent()
{
@ -62,27 +63,46 @@ public class SettingsTab : ITab
Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use.", _config.DebugMode, v => _config.DebugMode = v);
DrawColorSettings();
_tmpPhrasing1 ??= _config.Phrasing1;
ImGui.InputText("Phrasing 1", ref _tmpPhrasing1, 512);
if (ImGui.IsItemDeactivatedAfterEdit())
{
_phrasingService.SetPhrasing1(_tmpPhrasing1);
_tmpPhrasing1 = null;
}
_tmpPhrasing2 ??= _config.Phrasing2;
ImGui.InputText("Phrasing 2", ref _tmpPhrasing2, 512);
if (ImGui.IsItemDeactivatedAfterEdit())
{
_phrasingService.SetPhrasing2(_tmpPhrasing2);
_tmpPhrasing2 = null;
}
DrawCodes();
MainWindow.DrawSupportButtons();
}
private void DrawCodes()
{
if (!ImGui.CollapsingHeader("Codes"))
return;
using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, _currentCode.Length > 0))
{
var color = _codeService.CheckCode(_currentCode) != null ? ColorId.ActorAvailable : ColorId.ActorUnavailable;
using var c = ImRaii.PushColor(ImGuiCol.Border, color.Value(), _currentCode.Length > 0);
if (ImGui.InputTextWithHint("##Code", "Enter Code...", ref _currentCode, 512, ImGuiInputTextFlags.EnterReturnsTrue))
{
if (_codeService.AddCode(_currentCode))
_currentCode = string.Empty;
}
}
if (_config.Codes.Count <= 0)
return;
for (var i = 0; i < _config.Codes.Count; ++i)
{
var (code, state) = _config.Codes[i];
var action = _codeService.CheckCode(code);
if (action == null)
continue;
if (ImGui.Checkbox(code, ref state))
{
action(state);
_config.Codes[i] = (code, state);
_config.Save();
}
}
}
/// <summary> Draw the entire Color subsection. </summary>
private void DrawColorSettings()
{

View file

@ -0,0 +1,145 @@
using System;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui.Tabs.UnlocksTab;
public class UnlockOverview
{
private readonly ItemManager _items;
private readonly ItemUnlockManager _itemUnlocks;
private readonly CustomizationService _customizations;
private readonly CustomizeUnlockManager _customizeUnlocks;
private readonly PenumbraChangedItemTooltip _tooltip;
private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f);
public UnlockOverview(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks,
CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip)
{
_items = items;
_customizations = customizations;
_itemUnlocks = itemUnlocks;
_customizeUnlocks = customizeUnlocks;
_tooltip = tooltip;
}
public void Draw()
{
using var color = ImRaii.PushColor(ImGuiCol.Border, ImGui.GetColorU32(ImGuiCol.TableBorderStrong));
using var child = ImRaii.Child("Panel", -Vector2.One, true);
if (!child)
return;
var iconSize = ImGuiHelpers.ScaledVector2(32);
foreach (var type in Enum.GetValues<FullEquipType>())
DrawEquipTypeHeader(iconSize, type);
iconSize = ImGuiHelpers.ScaledVector2(64);
foreach (var gender in _customizations.AwaitedService.Genders)
{
foreach (var clan in _customizations.AwaitedService.Clans)
DrawCustomizationHeader(iconSize, clan, gender);
}
}
private void DrawCustomizationHeader(Vector2 iconSize, SubRace subRace, Gender gender)
{
var set = _customizations.AwaitedService.GetList(subRace, gender);
if (set.HairStyles.Count == 0 && set.FacePaints.Count == 0)
return;
if (!ImGui.CollapsingHeader($"Unlockable {subRace.ToName()} {gender.ToName()} Customizations"))
return;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
foreach (var customization in set.HairStyles.Concat(set.FacePaints))
{
if (!_customizeUnlocks.Unlockable.TryGetValue(customization, out var unlockData))
continue;
var unlocked = _customizeUnlocks.IsUnlocked(customization, out var time);
var icon = _customizations.AwaitedService.GetIcon(customization.IconId);
ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked ? Vector4.One : UnavailableTint);
if (ImGui.IsItemHovered())
{
using var tt = ImRaii.Tooltip();
var size = new Vector2(icon.Width, icon.Height);
if (size.X >= iconSize.X && size.Y >= iconSize.Y)
ImGui.Image(icon.ImGuiHandle, size);
ImGui.TextUnformatted(unlockData.Name);
ImGui.TextUnformatted($"{customization.Index.ToDefaultName()} {customization.Value.Value}");
ImGui.TextUnformatted(unlocked ? $"Unlocked on {time:g}" : "Not unlocked.");
}
ImGui.SameLine();
if (ImGui.GetContentRegionAvail().X < iconSize.X)
ImGui.NewLine();
}
if (ImGui.GetCursorPosX() != 0)
ImGui.NewLine();
}
private void DrawEquipTypeHeader(Vector2 iconSize, FullEquipType type)
{
if (type.IsOffhandType() || !_items.ItemService.AwaitedService.TryGetValue(type, out var items) || items.Count == 0)
return;
if (!ImGui.CollapsingHeader($"{type.ToName()}s"))
return;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale));
foreach (var item in items)
{
if (!ImGui.IsItemVisible())
{ }
var unlocked = _itemUnlocks.IsUnlocked(item.Id, out var time);
var icon = _customizations.AwaitedService.GetIcon(item.IconId);
ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked ? Vector4.One : UnavailableTint);
if (ImGui.IsItemClicked())
{
// TODO link
}
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _tooltip.Player(out var state))
_tooltip.ApplyItem(state, item);
if (ImGui.IsItemHovered())
{
using var tt = ImRaii.Tooltip();
var size = new Vector2(icon.Width, icon.Height);
if (size.X >= iconSize.X && size.Y >= iconSize.Y)
ImGui.Image(icon.ImGuiHandle, size);
ImGui.TextUnformatted(item.Name);
var slot = item.Type.ToSlot();
ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})");
if (item.Type.Offhand().IsOffhandType())
ImGui.TextUnformatted(
$"{item.Weapon()}{(_items.ItemService.AwaitedService.TryGetValue(item.Id, false, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}");
else
ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}");
ImGui.TextUnformatted(
unlocked ? time == DateTimeOffset.MinValue ? "Always Unlocked" : $"Unlocked on {time:g}" : "Not Unlocked.");
_tooltip.CreateTooltip(item, string.Empty, false);
}
ImGui.SameLine();
if (ImGui.GetContentRegionAvail().X < iconSize.X)
ImGui.NewLine();
}
if (ImGui.GetCursorPosX() != 0)
ImGui.NewLine();
}
}

View file

@ -0,0 +1,276 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Interface;
using Glamourer.Services;
using Glamourer.Structs;
using Glamourer.Unlocks;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Table;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.UnlocksTab;
public class UnlockTable : Table<EquipItem>
{
public UnlockTable(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks,
PenumbraChangedItemTooltip tooltip)
: base("ItemUnlockTable", new ItemList(items),
new NameColumn(customizations, tooltip) { Label = "Item Name..." },
new SlotColumn() { Label = "Equip Slot" },
new TypeColumn() { Label = "Item Type..." },
new UnlockDateColumn(itemUnlocks) { Label = "Unlocked" },
new ItemIdColumn() { Label = "Item Id..." },
new ModelDataColumn(items) { Label = "Model Data..." })
{
Sortable = true;
Flags |= ImGuiTableFlags.Hideable;
}
private sealed class NameColumn : ColumnString<EquipItem>
{
private readonly CustomizationService _customizations;
private readonly PenumbraChangedItemTooltip _tooltip;
public override float Width
=> 400 * ImGuiHelpers.GlobalScale;
public NameColumn(CustomizationService customizations, PenumbraChangedItemTooltip tooltip)
{
_customizations = customizations;
_tooltip = tooltip;
Flags |= ImGuiTableColumnFlags.NoHide | ImGuiTableColumnFlags.NoReorder;
}
public override string ToName(EquipItem item)
=> item.Name;
public override void DrawColumn(EquipItem item, int _)
{
var icon = _customizations.AwaitedService.GetIcon(item.IconId);
ImGui.Image(icon.ImGuiHandle, new Vector2(ImGui.GetFrameHeight()));
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
if (ImGui.Selectable(item.Name))
{
// TODO link
}
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _tooltip.Player(out var state))
_tooltip.ApplyItem(state, item);
if (ImGui.IsItemHovered() && _tooltip.Player())
_tooltip.CreateTooltip(item, string.Empty, true);
}
}
private sealed class TypeColumn : ColumnString<EquipItem>
{
public override float Width
=> ImGui.CalcTextSize(FullEquipType.CrossPeinHammer.ToName()).X;
public override string ToName(EquipItem item)
=> item.Type.ToName();
public override void DrawColumn(EquipItem item, int _)
{
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(item.Type.ToName());
}
public override int Compare(EquipItem lhs, EquipItem rhs)
=> lhs.Type.CompareTo(rhs.Type);
}
private sealed class SlotColumn : ColumnFlags<EquipFlag, EquipItem>
{
public override float Width
=> ImGui.CalcTextSize("Equip Slotmm").X;
private EquipFlag _filterValue;
public SlotColumn()
{
AllFlags = Values.Aggregate((a, b) => a | b);
_filterValue = AllFlags;
}
public override void DrawColumn(EquipItem item, int idx)
{
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(ToString(item.Type.ToSlot()));
}
public override EquipFlag FilterValue
=> _filterValue;
protected override IReadOnlyList<EquipFlag> Values
=> new[]
{
EquipFlag.Mainhand,
EquipFlag.Offhand,
EquipFlag.Head,
EquipFlag.Body,
EquipFlag.Hands,
EquipFlag.Legs,
EquipFlag.Feet,
EquipFlag.Ears,
EquipFlag.Neck,
EquipFlag.Wrist,
EquipFlag.RFinger,
};
protected override string[] Names
=> new[]
{
ToString(EquipSlot.MainHand),
ToString(EquipSlot.OffHand),
ToString(EquipSlot.Head),
ToString(EquipSlot.Body),
ToString(EquipSlot.Hands),
ToString(EquipSlot.Legs),
ToString(EquipSlot.Feet),
ToString(EquipSlot.Ears),
ToString(EquipSlot.Neck),
ToString(EquipSlot.Wrists),
ToString(EquipSlot.RFinger),
};
protected override void SetValue(EquipFlag value, bool enable)
=> _filterValue = enable ? _filterValue | value : _filterValue & ~value;
public override int Compare(EquipItem lhs, EquipItem rhs)
=> lhs.Type.ToSlot().CompareTo(rhs.Type.ToSlot());
public override bool FilterFunc(EquipItem item)
=> _filterValue.HasFlag(item.Type.ToSlot().ToFlag());
private static string ToString(EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => "Mainhand",
EquipSlot.OffHand => "Offhand",
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 => "Finger",
_ => string.Empty,
};
}
private sealed class UnlockDateColumn : Column<EquipItem>
{
private readonly ItemUnlockManager _unlocks;
public override float Width
=> 110 * ImGuiHelpers.GlobalScale;
public UnlockDateColumn(ItemUnlockManager unlocks)
=> _unlocks = unlocks;
public override void DrawColumn(EquipItem item, int idx)
{
if (!_unlocks.IsUnlocked(item.Id, out var time))
return;
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(time == DateTimeOffset.MinValue ? "Always" : time.ToString("g"));
}
public override int Compare(EquipItem lhs, EquipItem rhs)
{
var unlockedLhs = _unlocks.IsUnlocked(lhs.Id, out var timeLhs);
var unlockedRhs = _unlocks.IsUnlocked(lhs.Id, out var timeRhs);
var c1 = unlockedLhs.CompareTo(unlockedRhs);
return c1 != 0 ? c1 : timeLhs.CompareTo(timeRhs);
}
}
private sealed class ItemIdColumn : ColumnString<EquipItem>
{
public override float Width
=> 70 * ImGuiHelpers.GlobalScale;
public override int Compare(EquipItem lhs, EquipItem rhs)
=> lhs.Id.CompareTo(rhs.Id);
public override string ToName(EquipItem item)
=> item.Id.ToString();
public override void DrawColumn(EquipItem item, int _)
{
ImGui.AlignTextToFramePadding();
ImGuiUtil.RightAlign(item.Id.ToString());
}
}
private sealed class ModelDataColumn : ColumnString<EquipItem>
{
private readonly ItemManager _items;
public override float Width
=> 100 * ImGuiHelpers.GlobalScale;
public ModelDataColumn(ItemManager items)
=> _items = items;
public override void DrawColumn(EquipItem item, int _)
{
ImGui.AlignTextToFramePadding();
ImGuiUtil.RightAlign(item.ModelString);
if (ImGui.IsItemHovered()
&& item.Type.Offhand().IsOffhandType()
&& _items.ItemService.AwaitedService.TryGetValue(item.Id, false, out var offhand))
{
using var tt = ImRaii.Tooltip();
ImGui.TextUnformatted("Offhand: " + offhand.ModelString);
}
}
public override int Compare(EquipItem lhs, EquipItem rhs)
=> lhs.Weapon().Value.CompareTo(rhs.Weapon().Value);
public override bool FilterFunc(EquipItem item)
{
if (FilterValue.Length == 0)
return true;
if (FilterRegex?.IsMatch(item.ModelString) ?? item.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase))
return true;
if (item.Type.Offhand().IsOffhandType() && _items.ItemService.AwaitedService.TryGetValue(item.Id, false, out var offhand))
return FilterRegex?.IsMatch(offhand.ModelString)
?? offhand.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase);
return false;
}
}
private sealed class ItemList : IReadOnlyCollection<EquipItem>
{
private readonly ItemManager _items;
public ItemList(ItemManager items)
=> _items = items;
public IEnumerator<EquipItem> GetEnumerator()
=> _items.ItemService.AwaitedService.AllItems(true).Select(i => i.Item2).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> _items.ItemService.AwaitedService.TotalItemCount(true);
}
}

View file

@ -0,0 +1,60 @@
using System;
using System.Numerics;
using Dalamud.Interface;
using ImGuiNET;
using OtterGui.Raii;
using OtterGui;
using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.UnlocksTab;
public class UnlocksTab : ITab
{
private readonly Configuration _config;
private readonly UnlockOverview _overview;
private readonly UnlockTable _table;
public UnlocksTab(Configuration config, UnlockOverview overview, UnlockTable table)
{
_config = config;
_overview = overview;
_table = table;
}
private bool DetailMode
{
get => _config.UnlockDetailMode;
set
{
_config.UnlockDetailMode = value;
_config.Save();
}
}
public ReadOnlySpan<byte> Label
=> "Unlocks"u8;
public void DrawContent()
{
DrawTypeSelection();
if (DetailMode)
_table.Draw(ImGui.GetFrameHeightWithSpacing());
else
_overview.Draw();
}
private void DrawTypeSelection()
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
var buttonSize = new Vector2(ImGui.GetContentRegionAvail().X / 2, ImGui.GetFrameHeight());
if (ImGuiUtil.DrawDisabledButton("Overview Mode", buttonSize, "Show tinted icons of sets of unlocks.", !DetailMode))
DetailMode = false;
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Detailed Mode", buttonSize, "Show all unlockable data as a combined filterable and sortable table.",
DetailMode))
DetailMode = true;
}
}

View file

@ -0,0 +1,108 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Penumbra.GameData.Enums;
namespace Glamourer.Services;
public class CodeService
{
private readonly Configuration _config;
private readonly SHA256 _hasher = SHA256.Create();
public enum Sizing
{
None,
Dwarf,
Giant,
}
public bool EnabledClown { get; private set; }
public bool EnabledEmperor { get; private set; }
public bool EnabledIndividual { get; private set; }
public Sizing EnabledSizing { get; private set; }
public Race EnabledOops { get; private set; }
public bool EnabledMesmer { get; private set; }
public bool EnabledInventory { get; private set; }
public CodeService(Configuration config)
=> _config = config;
private void Load()
{
var changes = false;
for (var i = 0; i < _config.Codes.Count; ++i)
{
var enabled = CheckCode(_config.Codes[i].Code);
if (enabled == null)
{
--i;
_config.Codes.RemoveAt(i);
changes = true;
}
else
{
enabled(_config.Codes[i].Enabled);
}
}
if (changes)
_config.Save();
}
public bool AddCode(string name)
{
if (CheckCode(name) == null || _config.Codes.Any(p => p.Code == name))
return false;
_config.Codes.Add((name, false));
_config.Save();
return true;
}
public Action<bool>? CheckCode(string name)
{
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(name));
var sha = (ReadOnlySpan<byte>)_hasher.ComputeHash(stream);
return sha switch
{
_ when CodeClown.SequenceEqual(sha) => v => EnabledClown = v,
_ when CodeEmperor.SequenceEqual(sha) => v => EnabledEmperor = v,
_ when CodeIndividual.SequenceEqual(sha) => v => EnabledIndividual = v,
_ when CodeDwarf.SequenceEqual(sha) => v => EnabledSizing = v ? Sizing.Dwarf : Sizing.None,
_ when CodeGiant.SequenceEqual(sha) => v => EnabledSizing = v ? Sizing.Giant : Sizing.None,
_ when CodeMesmer.SequenceEqual(sha) => v => EnabledMesmer = v,
_ when CodeOops1.SequenceEqual(sha) => v => EnabledOops = v ? Race.Hyur : Race.Unknown,
_ when CodeOops2.SequenceEqual(sha) => v => EnabledOops = v ? Race.Elezen : Race.Unknown,
_ when CodeOops3.SequenceEqual(sha) => v => EnabledOops = v ? Race.Lalafell : Race.Unknown,
_ when CodeOops4.SequenceEqual(sha) => v => EnabledOops = v ? Race.Miqote : Race.Unknown,
_ when CodeOops5.SequenceEqual(sha) => v => EnabledOops = v ? Race.Roegadyn : Race.Unknown,
_ when CodeOops6.SequenceEqual(sha) => v => EnabledOops = v ? Race.AuRa : Race.Unknown,
_ when CodeOops7.SequenceEqual(sha) => v => EnabledOops = v ? Race.Hrothgar : Race.Unknown,
_ when CodeOops8.SequenceEqual(sha) => v => EnabledOops = v ? Race.Viera : Race.Unknown,
_ when CodeInventory.SequenceEqual(sha) => v => EnabledInventory = v,
_ => null,
};
}
// @formatter:off
private static ReadOnlySpan<byte> CodeClown => new byte[] { 0xC4, 0xEE, 0x1D, 0x6F, 0xC5, 0x5D, 0x47, 0xBE, 0x78, 0x63, 0x66, 0x86, 0x81, 0x15, 0xEB, 0xFA, 0xF6, 0x4A, 0x90, 0xEA, 0xC0, 0xE4, 0xEE, 0x86, 0x69, 0x01, 0x8E, 0xDB, 0xCC, 0x69, 0xD1, 0xBD };
private static ReadOnlySpan<byte> CodeEmperor => new byte[] { 0xE2, 0x2D, 0x3E, 0x57, 0x16, 0x82, 0x65, 0x98, 0x7E, 0xE6, 0x8F, 0x45, 0x14, 0x7D, 0x65, 0x31, 0xE9, 0xD8, 0xDB, 0xEA, 0xDC, 0xBF, 0xEE, 0x2A, 0xBA, 0xD5, 0x69, 0x96, 0x78, 0x34, 0x3B, 0x57 };
private static ReadOnlySpan<byte> CodeIndividual => new byte[] { 0x95, 0xA4, 0x71, 0xAC, 0xA3, 0xC2, 0x34, 0x94, 0xC1, 0x65, 0x07, 0xF3, 0x7F, 0x93, 0x57, 0xEE, 0xE3, 0x04, 0xC0, 0xE8, 0x1B, 0xA0, 0xE2, 0x08, 0x68, 0x02, 0x8D, 0xAD, 0x76, 0x03, 0x9B, 0xC5 };
private static ReadOnlySpan<byte> CodeDwarf => new byte[] { 0x55, 0x97, 0xFE, 0xE9, 0x78, 0x64, 0xE8, 0x2F, 0xCD, 0x25, 0xD1, 0xAE, 0xDF, 0x35, 0xE6, 0xED, 0x03, 0x78, 0x54, 0x1D, 0x56, 0x22, 0x34, 0x75, 0x4B, 0x96, 0x6F, 0xBA, 0xAC, 0xEC, 0x00, 0x46 };
private static ReadOnlySpan<byte> CodeGiant => new byte[] { 0x6E, 0xBB, 0x91, 0x1D, 0x67, 0xE3, 0x00, 0x07, 0xA1, 0x0F, 0x2A, 0xF0, 0x26, 0x91, 0x38, 0x63, 0xD3, 0x52, 0x82, 0xF7, 0x5D, 0x93, 0xE8, 0x83, 0xB1, 0xF6, 0xB9, 0x69, 0x78, 0x20, 0xC4, 0xCE };
private static ReadOnlySpan<byte> CodeMesmer => new byte[] { 0x6A, 0x84, 0x12, 0xEA, 0x3B, 0x03, 0x2E, 0xD9, 0xA3, 0x51, 0xB0, 0x4F, 0xE7, 0x4D, 0x59, 0x87, 0xA9, 0xA1, 0x6E, 0x08, 0xC7, 0x3E, 0xD3, 0x15, 0xEE, 0x40, 0x2C, 0xB3, 0x44, 0x78, 0x1F, 0xA0 };
private static ReadOnlySpan<byte> CodeOops1 => new byte[] { 0x4C, 0x51, 0xE2, 0x38, 0xEF, 0xAD, 0x84, 0x0E, 0x4E, 0x11, 0x0F, 0x5E, 0xDE, 0x45, 0x41, 0x9F, 0x6A, 0xF6, 0x5F, 0x5B, 0xA8, 0x91, 0x64, 0x22, 0xEE, 0x62, 0x97, 0x3C, 0x78, 0x18, 0xCD, 0xAF };
private static ReadOnlySpan<byte> CodeOops2 => new byte[] { 0x3D, 0x5B, 0xA9, 0x62, 0xCE, 0xBE, 0x52, 0xF5, 0x94, 0x2A, 0xF9, 0xB7, 0xCF, 0xD9, 0x24, 0x2B, 0x38, 0xC7, 0x4F, 0x28, 0x97, 0x29, 0x1D, 0x01, 0x13, 0x53, 0x44, 0x11, 0x15, 0x6F, 0x9B, 0x56 };
private static ReadOnlySpan<byte> CodeOops3 => new byte[] { 0x85, 0x8D, 0x5B, 0xC2, 0x66, 0x53, 0x2E, 0xB9, 0xE9, 0x85, 0xE5, 0xF8, 0xD3, 0x75, 0x18, 0x7C, 0x58, 0x55, 0xD4, 0x8C, 0x8E, 0x5F, 0x58, 0x2E, 0xF3, 0xF1, 0xAE, 0xA8, 0xA0, 0x81, 0xC6, 0x0E };
private static ReadOnlySpan<byte> CodeOops4 => new byte[] { 0x44, 0x73, 0x8C, 0x39, 0x5A, 0xF1, 0xDB, 0x5F, 0x62, 0xA1, 0x6E, 0x5F, 0xE6, 0x97, 0x9E, 0x90, 0xD7, 0x5C, 0x97, 0x67, 0xB6, 0xC7, 0x99, 0x61, 0x36, 0xCA, 0x34, 0x7E, 0xB9, 0xAC, 0xC3, 0x76 };
private static ReadOnlySpan<byte> CodeOops5 => new byte[] { 0xB7, 0x25, 0x73, 0xDB, 0xBE, 0xD0, 0x49, 0xFB, 0xFF, 0x9C, 0x32, 0x21, 0xB0, 0x8A, 0x2C, 0x0C, 0x77, 0x46, 0xD5, 0xCF, 0x0E, 0x63, 0x2F, 0x91, 0x85, 0x8B, 0x55, 0x5C, 0x4D, 0xD2, 0xB9, 0xB8 };
private static ReadOnlySpan<byte> CodeOops6 => new byte[] { 0x69, 0x93, 0xAF, 0xE4, 0xB8, 0xEC, 0x5F, 0x40, 0xEB, 0x8A, 0x6F, 0xD1, 0x9B, 0xD9, 0x56, 0x0B, 0xEA, 0x64, 0x79, 0x9B, 0x54, 0xA1, 0x41, 0xED, 0xBC, 0x3E, 0x6E, 0x5C, 0xF1, 0x23, 0x60, 0xF8 };
private static ReadOnlySpan<byte> CodeOops7 => new byte[] { 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 };
private static ReadOnlySpan<byte> CodeOops8 => new byte[] { 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B };
private static ReadOnlySpan<byte> CodeInventory => new byte[] { 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB };
// @formatter:on
}

View file

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Dalamud.Data;
@ -105,10 +106,16 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
public bool IsGenderValid(Race race, Gender gender)
=> race is Race.Hrothgar ? gender == Gender.Male : AwaitedService.Genders.Contains(gender);
/// <summary> Returns whether a customization value is valid for a given clan/gender set and face. </summary>
/// <inheritdoc cref="IsCustomizationValid(CustomizationSet,CustomizeValue,CustomizeIndex,CustomizeValue, out CustomizeData?)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static bool IsCustomizationValid(CustomizationSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value)
=> set.DataByValue(type, value, out _, face) >= 0;
=> IsCustomizationValid(set, face, type, value, out _);
/// <summary> Returns whether a customization value is valid for a given clan/gender set and face. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static bool IsCustomizationValid(CustomizationSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value,
[NotNullWhen(true)] out CustomizeData? data)
=> set.DataByValue(type, value, out data, face) >= 0;
/// <summary> Returns whether a customization value is valid for a given clan, gender and face. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]

View file

@ -1,54 +0,0 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Glamourer.Services;
public class PhrasingService
{
private readonly Configuration _config;
private readonly SHA256 _hasher = SHA256.Create();
public bool Phrasing1 { get; private set; }
public bool Phrasing2 { get; private set; }
public PhrasingService(Configuration config)
{
_config = config;
Phrasing1 = CheckPhrasing(_config.Phrasing1, P1);
Phrasing2 = CheckPhrasing(_config.Phrasing2, P2);
}
public void SetPhrasing1(string newPhrasing)
{
if (_config.Phrasing1 == newPhrasing)
return;
_config.Phrasing1 = newPhrasing;
_config.Save();
Phrasing1 = CheckPhrasing(newPhrasing, P1);
}
public void SetPhrasing2(string newPhrasing)
{
if (_config.Phrasing2 == newPhrasing)
return;
_config.Phrasing2 = newPhrasing;
_config.Save();
Phrasing2 = CheckPhrasing(newPhrasing, P2);
}
private bool CheckPhrasing(string phrasing, ReadOnlySpan<byte> data)
{
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(phrasing));
var sha = _hasher.ComputeHash(stream);
return data.SequenceEqual(sha);
}
// @formatter:off
private static ReadOnlySpan<byte> P1 => new byte[] { 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB };
private static ReadOnlySpan<byte> P2 => new byte[] { 0x6A, 0x84, 0x12, 0xEA, 0x3B, 0x03, 0x2E, 0xD9, 0xA3, 0x51, 0xB0, 0x4F, 0xE7, 0x4D, 0x59, 0x87, 0xA9, 0xA1, 0x6E, 0x08, 0xC7, 0x3E, 0xD3, 0x15, 0xEE, 0x40, 0x2C, 0xB3, 0x44, 0x78, 0x1F, 0xA0 };
// @formatter:on
}

View file

@ -10,6 +10,7 @@ using Glamourer.Gui.Tabs;
using Glamourer.Gui.Tabs.ActorTab;
using Glamourer.Gui.Tabs.AutomationTab;
using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Gui.Tabs.UnlocksTab;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.State;
@ -51,7 +52,7 @@ public static class ServiceManager
.AddSingleton<BackupService>()
.AddSingleton<FrameworkManager>()
.AddSingleton<SaveService>()
.AddSingleton<PhrasingService>()
.AddSingleton<CodeService>()
.AddSingleton<ConfigMigrationService>()
.AddSingleton<Configuration>();
@ -95,7 +96,8 @@ public static class ServiceManager
private static IServiceCollection AddState(this IServiceCollection services)
=> services.AddSingleton<StateManager>()
.AddSingleton<StateEditor>()
.AddSingleton<StateListener>();
.AddSingleton<StateListener>()
.AddSingleton<FunModule>();
private static IServiceCollection AddUi(this IServiceCollection services)
=> services.AddSingleton<DebugTab>()
@ -110,6 +112,9 @@ public static class ServiceManager
.AddSingleton<DesignFileSystemSelector>()
.AddSingleton<DesignPanel>()
.AddSingleton<DesignTab>()
.AddSingleton<UnlockTable>()
.AddSingleton<UnlockOverview>()
.AddSingleton<UnlocksTab>()
.AddSingleton<PenumbraChangedItemTooltip>()
.AddSingleton<AutomationTab>()
.AddSingleton<SetSelector>()

View file

@ -0,0 +1,137 @@
using System;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Enums;
using Glamourer.Customization;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
namespace Glamourer.State;
public unsafe class FunModule
{
private readonly ItemManager _items;
private readonly CustomizationService _customizations;
private readonly CodeService _codes;
private readonly Random _rng;
private readonly StainId[] _stains;
public FunModule(CodeService codes, CustomizationService customizations, ItemManager items)
{
_codes = codes;
_customizations = customizations;
_items = items;
_rng = new Random();
_stains = _items.Stains.Keys.Prepend((StainId)0).ToArray();
}
public void ApplyFun(Actor actor, ref CharacterArmor armor, EquipSlot slot)
{
if (actor.AsObject->ObjectKind is not (byte)ObjectKind.Player || !actor.IsCharacter)
return;
if (actor.AsCharacter->CharacterData.ModelCharaId != 0)
return;
ApplyEmperor(new Span<CharacterArmor>(ref armor));
ApplyClown(new Span<CharacterArmor>(ref armor));
}
public void ApplyFun(Actor actor, Span<CharacterArmor> armor, ref Customize customize)
{
if (actor.AsObject->ObjectKind is not (byte)ObjectKind.Player || !actor.IsCharacter)
return;
if (actor.AsCharacter->CharacterData.ModelCharaId != 0)
return;
ApplyEmperor(armor);
ApplyClown(armor);
ApplyOops(ref customize);
ApplyIndividual(ref customize);
ApplySizing(actor, ref customize);
}
public void ApplyClown(Span<CharacterArmor> armors)
{
if (!_codes.EnabledClown)
return;
foreach (ref var armor in armors)
{
var stainIdx = _rng.Next(0, _stains.Length - 1);
armor.Stain = _stains[stainIdx];
}
}
public void ApplyEmperor(Span<CharacterArmor> armors, EquipSlot slot = EquipSlot.Unknown)
{
if (!_codes.EnabledEmperor)
return;
if (armors.Length == 1)
{
ref var piece = ref armors[0];
piece.Variant = 1;
piece.Set = (SetId)(slot.IsAccessory() ? 53 : 279);
return;
}
for (var i = 0; i < armors.Length; ++i)
{
ref var piece = ref armors[i];
piece.Variant = 1;
piece.Set = (SetId)(i < 5 ? 279 : 53);
}
}
public void ApplyOops(ref Customize customize)
{
if (_codes.EnabledOops == Race.Unknown)
return;
var targetClan = (SubRace)((int)_codes.EnabledOops * 2 - (int)customize.Clan % 2);
// TODO Female Hrothgar
if (_codes.EnabledOops is Race.Hrothgar && customize.Gender is Gender.Female)
targetClan = targetClan is SubRace.Lost ? SubRace.Seawolf : SubRace.Hellsguard;
_customizations.ChangeClan(ref customize, targetClan);
}
public void ApplyIndividual(ref Customize customize)
{
if (!_codes.EnabledIndividual)
return;
var set = _customizations.AwaitedService.GetList(customize.Clan, customize.Gender);
foreach (var index in Enum.GetValues<CustomizeIndex>())
{
if (index is CustomizeIndex.Face || !set.IsAvailable(index))
continue;
var valueIdx = _rng.Next(0, set.Count(index) - 1);
customize[index] = set.Data(index, valueIdx).Value;
}
}
public void ApplySizing(Actor actor, ref Customize customize)
{
if (_codes.EnabledSizing == CodeService.Sizing.None)
return;
var size = _codes.EnabledSizing switch
{
CodeService.Sizing.Dwarf when actor.Index == 0 => 0,
CodeService.Sizing.Dwarf when actor.Index != 0 => 100,
CodeService.Sizing.Giant when actor.Index == 0 => 100,
CodeService.Sizing.Giant when actor.Index != 0 => 0,
_ => 0,
};
if (customize.Gender is Gender.Female)
customize[CustomizeIndex.BustSize] = (CustomizeValue)size;
customize[CustomizeIndex.Height] = (CustomizeValue)size;
}
}

View file

@ -29,6 +29,7 @@ public class StateListener : IDisposable
private readonly VisorStateChanged _visorState;
private readonly WeaponVisibilityChanged _weaponVisibility;
private readonly AutoDesignApplier _autoDesignApplier;
private readonly FunModule _funModule;
public bool Enabled
{
@ -38,7 +39,7 @@ public class StateListener : IDisposable
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier)
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule)
{
_manager = manager;
_items = items;
@ -51,6 +52,7 @@ public class StateListener : IDisposable
_weaponVisibility = weaponVisibility;
_headGearVisibility = headGearVisibility;
_autoDesignApplier = autoDesignApplier;
_funModule = funModule;
if (Enabled)
Subscribe();
@ -126,6 +128,7 @@ public class StateListener : IDisposable
}
}
_funModule.ApplyFun(actor, new Span<CharacterArmor>((void*) equipDataPtr, 10), ref customize);
if (modelId == 0)
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
}
@ -141,6 +144,7 @@ public class StateListener : IDisposable
&& _manager.TryGetValue(identifier, out var state))
HandleEquipSlot(actor, state, slot, ref armor.Value);
_funModule.ApplyFun(actor, ref armor.Value, slot);
if (!_config.UseRestrictedGearProtection)
return;

View file

@ -48,9 +48,16 @@ public class CustomizeUnlockManager : IDisposable, ISavable
/// <summary> Check if a customization is unlocked for Glamourer. </summary>
public bool IsUnlocked(CustomizeData data, out DateTimeOffset time)
{
// All other customizations are not unlockable.
if (data.Index is not CustomizeIndex.Hairstyle and not CustomizeIndex.FacePaint)
{
time = DateTime.MinValue;
return true;
}
if (!Unlockable.TryGetValue(data, out var pair))
{
time = DateTime.MaxValue;
time = DateTime.MinValue;
return true;
}
@ -62,7 +69,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable
if (!IsUnlockedGame(pair.Data))
{
time = DateTimeOffset.MinValue;
time = DateTimeOffset.MaxValue;
return false;
}

View file

@ -196,6 +196,7 @@ public class ItemUnlockManager : ISavable, IDisposable
}
}
changes = false;
var inventoryManager = InventoryManager.Instance();
if (inventoryManager != null)
{
@ -216,7 +217,6 @@ public class ItemUnlockManager : ISavable, IDisposable
_currentInventoryIndex = 0;
}
}
if (changes)
Save();
}
@ -226,7 +226,7 @@ public class ItemUnlockManager : ISavable, IDisposable
// Pseudo items are always unlocked.
if (itemId >= _items.ItemSheet.RowCount)
{
time = DateTimeOffset.MaxValue;
time = DateTimeOffset.MinValue;
return true;
}
@ -244,7 +244,7 @@ public class ItemUnlockManager : ISavable, IDisposable
return true;
}
time = DateTimeOffset.MinValue;
time = DateTimeOffset.MaxValue;
return false;
}
@ -283,7 +283,7 @@ public class ItemUnlockManager : ISavable, IDisposable
=> fileNames.UnlockFileItems;
public void Save()
=> _saveService.QueueSave(this);
=> _saveService.DelaySave(this, TimeSpan.FromSeconds(10));
public void Save(StreamWriter writer)
{ }