mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
.
This commit is contained in:
parent
7710cfadfa
commit
2d6fd6015d
88 changed files with 2304 additions and 383 deletions
|
|
@ -5,22 +5,6 @@ public static class Offsets
|
||||||
public static class Character
|
public static class Character
|
||||||
{
|
{
|
||||||
public const int ClassJobContainer = 0x1A8;
|
public const int ClassJobContainer = 0x1A8;
|
||||||
|
|
||||||
public const int Wetness = 0x1ADA;
|
|
||||||
public const int HatVisible = 0x84E;
|
|
||||||
public const int VisorToggled = 0x84F;
|
|
||||||
public const int WeaponHidden1 = 0x84F;
|
|
||||||
public const int WeaponHidden2 = 0x72C;
|
|
||||||
public const int Alpha = 0x19E0;
|
|
||||||
|
|
||||||
public static class Flags
|
|
||||||
{
|
|
||||||
public const byte IsHatHidden = 0x01;
|
|
||||||
public const byte IsVisorToggled = 0x08;
|
|
||||||
public const byte IsWet = 0x80;
|
|
||||||
public const byte IsWeaponHidden1 = 0x01;
|
|
||||||
public const byte IsWeaponHidden2 = 0x02;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public const byte DrawObjectVisorStateFlag = 0x40;
|
public const byte DrawObjectVisorStateFlag = 0x40;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.2.32210.308
|
VisualStudioVersion = 17.2.32210.308
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer", "Glamourer\Glamourer.csproj", "{A5439F6B-83C1-4078-9371-354A147FF554}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GlamourerOld", "GlamourerOld\GlamourerOld.csproj", "{A5439F6B-83C1-4078-9371-354A147FF554}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{383AEE76-D423-431C-893A-7AB3DEA13630}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{383AEE76-D423-431C-893A-7AB3DEA13630}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
|
@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "..\Pen
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "..\Penumbra\OtterGui\OtterGui.csproj", "{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "..\Penumbra\OtterGui\OtterGui.csproj", "{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer", "Glamourer\Glamourer.csproj", "{01EB903D-871F-4285-A8CF-6486561D5B5B}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -39,6 +41,10 @@ Global
|
||||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
35
Glamourer/Events/UpdatedSlot.cs
Normal file
35
Glamourer/Events/UpdatedSlot.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a model flags an equipment slot for an update.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the model with a flagged slot. </item>
|
||||||
|
/// <item>Parameter is the equipment slot changed. </item>
|
||||||
|
/// <item>Parameter is the model values to change the equipment piece to. </item>
|
||||||
|
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class UpdatedSlot : EventWrapper<Action<Model, EquipSlot, Ref<CharacterArmor>, Ref<ulong>>, UpdatedSlot.Priority>
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public UpdatedSlot()
|
||||||
|
: base(nameof(UpdatedSlot))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void Invoke(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue)
|
||||||
|
{
|
||||||
|
var value = new Ref<CharacterArmor>(armor);
|
||||||
|
var @return = new Ref<ulong>(returnValue);
|
||||||
|
Invoke(this, model, slot, value, @return);
|
||||||
|
armor = value;
|
||||||
|
returnValue = @return;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Glamourer/Events/VisorStateChanged.cs
Normal file
32
Glamourer/Events/VisorStateChanged.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when the state of a visor for any draw object is changed.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the model with a changed visor state. </item>
|
||||||
|
/// <item>Parameter is the new state. </item>
|
||||||
|
/// <item>Parameter is whether to call the original function. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class VisorStateChanged : EventWrapper<Action<Model, Ref<bool>, Ref<bool>>, VisorStateChanged.Priority>
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public VisorStateChanged()
|
||||||
|
: base(nameof(VisorStateChanged))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void Invoke(Model model, ref bool state, ref bool callOriginal)
|
||||||
|
{
|
||||||
|
var value = new Ref<bool>(state);
|
||||||
|
var original = new Ref<bool>(callOriginal);
|
||||||
|
Invoke(this, model, value, original);
|
||||||
|
state = value;
|
||||||
|
callOriginal = original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ using OtterGui.Log;
|
||||||
|
|
||||||
namespace Glamourer;
|
namespace Glamourer;
|
||||||
|
|
||||||
public partial class Glamourer : IDalamudPlugin
|
public class Item : IDalamudPlugin
|
||||||
{
|
{
|
||||||
public string Name
|
public string Name
|
||||||
=> "Glamourer";
|
=> "Glamourer";
|
||||||
|
|
@ -20,26 +20,22 @@ public partial class Glamourer : IDalamudPlugin
|
||||||
Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "Unknown";
|
Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "Unknown";
|
||||||
|
|
||||||
|
|
||||||
public static readonly Logger Log = new();
|
public static readonly Logger Log = new();
|
||||||
public static ChatService ChatService { get; private set; } = null!;
|
public static ChatService Chat { get; private set; } = null!;
|
||||||
private readonly ServiceProvider _services;
|
|
||||||
|
|
||||||
public Glamourer(DalamudPluginInterface pluginInterface)
|
|
||||||
|
private readonly ServiceProvider _services;
|
||||||
|
|
||||||
|
public Item(DalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_services = ServiceManager.CreateProvider(pluginInterface, Log);
|
_services = ServiceManager.CreateProvider(pluginInterface, Log);
|
||||||
ChatService = _services.GetRequiredService<ChatService>();
|
Chat = _services.GetRequiredService<ChatService>();
|
||||||
_services.GetRequiredService<BackupService>();
|
_services.GetRequiredService<BackupService>(); // call backup service.
|
||||||
_services.GetRequiredService<GlamourerWindowSystem>();
|
_services.GetRequiredService<GlamourerWindowSystem>(); // initialize ui.
|
||||||
_services.GetRequiredService<CommandService>();
|
_services.GetRequiredService<CommandService>(); // initialize commands.
|
||||||
_services.GetRequiredService<GlamourerIpc>();
|
|
||||||
_services.GetRequiredService<ChangeCustomizeService>();
|
|
||||||
_services.GetRequiredService<JobService>();
|
|
||||||
_services.GetRequiredService<UpdateSlotService>();
|
|
||||||
_services.GetRequiredService<VisorService>();
|
_services.GetRequiredService<VisorService>();
|
||||||
_services.GetRequiredService<WeaponService>();
|
|
||||||
_services.GetRequiredService<RedrawManager>();
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
@ -53,137 +49,4 @@ public partial class Glamourer : IDalamudPlugin
|
||||||
{
|
{
|
||||||
_services?.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;
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Penumbra\OtterGui\OtterGui.csproj" />
|
||||||
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
|
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
|
||||||
<ProjectReference Include="..\..\Penumbra\Penumbra.Api\Penumbra.Api.csproj" />
|
<ProjectReference Include="..\..\Penumbra\Penumbra.Api\Penumbra.Api.csproj" />
|
||||||
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||||
|
|
@ -119,6 +120,10 @@
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Designs\" />
|
||||||
|
</ItemGroup>
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<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 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)" />
|
<Exec Command="if $(Configuration) == Release powershell Copy-Item -Force $(TargetDir)$(SolutionName).json -Destination $(SolutionDir)" />
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ public class GlamourerWindowSystem : IDisposable
|
||||||
{
|
{
|
||||||
private readonly WindowSystem _windowSystem = new("Glamourer");
|
private readonly WindowSystem _windowSystem = new("Glamourer");
|
||||||
private readonly UiBuilder _uiBuilder;
|
private readonly UiBuilder _uiBuilder;
|
||||||
private readonly Interface _ui;
|
private readonly MainWindow _ui;
|
||||||
|
|
||||||
public GlamourerWindowSystem(UiBuilder uiBuilder, Interface ui)
|
public GlamourerWindowSystem(UiBuilder uiBuilder, MainWindow ui)
|
||||||
{
|
{
|
||||||
_uiBuilder = uiBuilder;
|
_uiBuilder = uiBuilder;
|
||||||
_ui = ui;
|
_ui = ui;
|
||||||
|
|
@ -24,7 +24,4 @@ public class GlamourerWindowSystem : IDisposable
|
||||||
_uiBuilder.Draw -= _windowSystem.Draw;
|
_uiBuilder.Draw -= _windowSystem.Draw;
|
||||||
_uiBuilder.OpenConfigUi -= _ui.Toggle;
|
_uiBuilder.OpenConfigUi -= _ui.Toggle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Toggle()
|
|
||||||
=> _ui.Toggle();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
Glamourer/Gui/MainWindow.cs
Normal file
39
Glamourer/Gui/MainWindow.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Glamourer.Gui.Tabs;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui.Widgets;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
public class MainWindow : Window
|
||||||
|
{
|
||||||
|
private readonly ITab[] _tabs;
|
||||||
|
|
||||||
|
public MainWindow(DalamudPluginInterface pi, DebugTab debugTab)
|
||||||
|
: base(GetLabel())
|
||||||
|
{
|
||||||
|
pi.UiBuilder.DisableGposeUiHide = true;
|
||||||
|
SizeConstraints = new WindowSizeConstraints()
|
||||||
|
{
|
||||||
|
MinimumSize = new Vector2(675, 675),
|
||||||
|
MaximumSize = ImGui.GetIO().DisplaySize,
|
||||||
|
};
|
||||||
|
_tabs = new ITab[]
|
||||||
|
{
|
||||||
|
debugTab,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
TabBar.Draw("##tabs", ImGuiTabBarFlags.None, ReadOnlySpan<byte>.Empty, out var currentTab, () => { }, _tabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetLabel()
|
||||||
|
=> Item.Version.Length == 0
|
||||||
|
? "Glamourer###GlamourerMainWindow"
|
||||||
|
: $"Glamourer v{Item.Version}###GlamourerMainWindow";
|
||||||
|
}
|
||||||
491
Glamourer/Gui/Tabs/DebugTab.cs
Normal file
491
Glamourer/Gui/Tabs/DebugTab.cs
Normal file
|
|
@ -0,0 +1,491 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Widgets;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs;
|
||||||
|
|
||||||
|
public unsafe class DebugTab : ITab
|
||||||
|
{
|
||||||
|
private readonly VisorService _visorService;
|
||||||
|
private readonly ChangeCustomizeService _changeCustomizeService;
|
||||||
|
private readonly UpdateSlotService _updateSlotService;
|
||||||
|
private readonly WeaponService _weaponService;
|
||||||
|
private readonly PenumbraService _penumbra;
|
||||||
|
private readonly ObjectTable _objects;
|
||||||
|
|
||||||
|
private readonly IdentifierService _identifier;
|
||||||
|
private readonly ActorService _actors;
|
||||||
|
private readonly ItemService _items;
|
||||||
|
private readonly CustomizationService _customization;
|
||||||
|
|
||||||
|
private int _gameObjectIndex;
|
||||||
|
|
||||||
|
public DebugTab(ChangeCustomizeService changeCustomizeService, VisorService visorService, ObjectTable objects,
|
||||||
|
UpdateSlotService updateSlotService, WeaponService weaponService, PenumbraService penumbra, IdentifierService identifier,
|
||||||
|
ActorService actors, ItemService items, CustomizationService customization)
|
||||||
|
{
|
||||||
|
_changeCustomizeService = changeCustomizeService;
|
||||||
|
_visorService = visorService;
|
||||||
|
_objects = objects;
|
||||||
|
_updateSlotService = updateSlotService;
|
||||||
|
_weaponService = weaponService;
|
||||||
|
_penumbra = penumbra;
|
||||||
|
_identifier = identifier;
|
||||||
|
_actors = actors;
|
||||||
|
_items = items;
|
||||||
|
_customization = customization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> Label
|
||||||
|
=> "Debug"u8;
|
||||||
|
|
||||||
|
public void DrawContent()
|
||||||
|
{
|
||||||
|
DrawInteropHeader();
|
||||||
|
DrawGameDataHeader();
|
||||||
|
DrawPenumbraHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Interop
|
||||||
|
|
||||||
|
private void DrawInteropHeader()
|
||||||
|
{
|
||||||
|
if (!ImGui.CollapsingHeader("Interop"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0);
|
||||||
|
var actor = (Actor)_objects.GetObjectAddress(_gameObjectIndex);
|
||||||
|
var model = actor.Model;
|
||||||
|
using var table = ImRaii.Table("##interopTable", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader("Actor");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableHeader("Model");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn("Address");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGui.Selectable($"0x{model.Address:X}"))
|
||||||
|
ImGui.SetClipboardText($"0x{model.Address:X}");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGui.Selectable($"0x{model.Address:X}"))
|
||||||
|
ImGui.SetClipboardText($"0x{model.Address:X}");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn("Mainhand");
|
||||||
|
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetMainhand().ToString() : "No Character");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var weapon = model.AsDrawObject->Object.ChildObject;
|
||||||
|
if (ImGui.Selectable($"0x{(ulong)weapon:X}"))
|
||||||
|
ImGui.SetClipboardText($"0x{(ulong)weapon:X}");
|
||||||
|
ImGuiUtil.DrawTableColumn("Offhand");
|
||||||
|
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetOffhand().ToString() : "No Character");
|
||||||
|
if (weapon != null && ImGui.Selectable($"0x{(ulong)weapon->NextSiblingObject:X}"))
|
||||||
|
ImGui.SetClipboardText($"0x{(ulong)weapon->NextSiblingObject:X}");
|
||||||
|
DrawVisor(actor, model);
|
||||||
|
DrawHatState(actor, model);
|
||||||
|
DrawWeaponState(actor, model);
|
||||||
|
DrawWetness(actor, model);
|
||||||
|
DrawEquip(actor, model);
|
||||||
|
DrawCustomize(actor, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawVisor(Actor actor, Model model)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId("Visor");
|
||||||
|
ImGuiUtil.DrawTableColumn("Visor State");
|
||||||
|
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->DrawData.IsVisorToggled.ToString() : "No Character");
|
||||||
|
ImGuiUtil.DrawTableColumn(model.IsHuman ? _visorService.GetVisorState(model).ToString() : "No Human");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (!model.IsHuman)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ImGui.SmallButton("Set True"))
|
||||||
|
_visorService.SetVisorState(model, true);
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("Set False"))
|
||||||
|
_visorService.SetVisorState(model, false);
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("Toggle"))
|
||||||
|
_visorService.SetVisorState(model, !_visorService.GetVisorState(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHatState(Actor actor, Model model)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId("HatState");
|
||||||
|
ImGuiUtil.DrawTableColumn("Hat State");
|
||||||
|
ImGuiUtil.DrawTableColumn(actor.IsCharacter
|
||||||
|
? actor.AsCharacter->DrawData.IsHatHidden ? "Hidden" : actor.GetArmor(EquipSlot.Head).ToString()
|
||||||
|
: "No Character");
|
||||||
|
ImGuiUtil.DrawTableColumn(model.IsHuman
|
||||||
|
? model.AsHuman->Head.Value == 0 ? "No Hat" : model.GetArmor(EquipSlot.Head).ToString()
|
||||||
|
: "No Human");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (!model.IsHuman)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ImGui.SmallButton("Hide"))
|
||||||
|
_updateSlotService.UpdateSlot(model, EquipSlot.Head, CharacterArmor.Empty);
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("Show"))
|
||||||
|
_updateSlotService.UpdateSlot(model, EquipSlot.Head, actor.GetArmor(EquipSlot.Head));
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("Toggle"))
|
||||||
|
_updateSlotService.UpdateSlot(model, EquipSlot.Head,
|
||||||
|
model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawWeaponState(Actor actor, Model model)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId("WeaponState");
|
||||||
|
ImGuiUtil.DrawTableColumn("Weapon State");
|
||||||
|
ImGuiUtil.DrawTableColumn(actor.IsCharacter
|
||||||
|
? actor.AsCharacter->DrawData.IsWeaponHidden ? "Hidden" : "Visible"
|
||||||
|
: "No Character");
|
||||||
|
var text = string.Empty;
|
||||||
|
// TODO
|
||||||
|
if (!model.IsHuman)
|
||||||
|
{
|
||||||
|
text = "No Model";
|
||||||
|
}
|
||||||
|
else if (model.AsDrawObject->Object.ChildObject == null)
|
||||||
|
{
|
||||||
|
text = "No Weapon";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var weapon = (DrawObject*)model.AsDrawObject->Object.ChildObject;
|
||||||
|
if ((weapon->Flags & 0x09) == 0x09)
|
||||||
|
text = "Visible";
|
||||||
|
else
|
||||||
|
text = "Hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn(text);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (!model.IsHuman)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawWetness(Actor actor, Model model)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId("Wetness");
|
||||||
|
ImGuiUtil.DrawTableColumn("Wetness");
|
||||||
|
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->IsGPoseWet ? "GPose" : "None" : "No Character");
|
||||||
|
var modelString = model.IsCharacterBase
|
||||||
|
? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n"
|
||||||
|
+ $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n"
|
||||||
|
+ $"{model.AsCharacterBase->ForcedWetness:F4} Forced\n"
|
||||||
|
+ $"{model.AsCharacterBase->WetnessDepth:F4} Depth\n"
|
||||||
|
: "No CharacterBase";
|
||||||
|
ImGuiUtil.DrawTableColumn(modelString);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (!actor.IsCharacter)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ImGui.SmallButton("GPose On"))
|
||||||
|
actor.AsCharacter->IsGPoseWet = true;
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("GPose Off"))
|
||||||
|
actor.AsCharacter->IsGPoseWet = false;
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("GPose Toggle"))
|
||||||
|
actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEquip(Actor actor, Model model)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId("Equipment");
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
{
|
||||||
|
using var id2 = ImRaii.PushId((int)slot);
|
||||||
|
ImGuiUtil.DrawTableColumn(slot.ToName());
|
||||||
|
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetArmor(slot).ToString() : "No Character");
|
||||||
|
ImGuiUtil.DrawTableColumn(model.IsHuman ? model.GetArmor(slot).ToString() : "No Human");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (!model.IsHuman)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ImGui.SmallButton("Change Piece"))
|
||||||
|
_updateSlotService.UpdateArmor(model, slot,
|
||||||
|
new CharacterArmor((SetId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0));
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("Change Stain"))
|
||||||
|
_updateSlotService.UpdateStain(model, slot, 5);
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("Reset"))
|
||||||
|
_updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCustomize(Actor actor, Model model)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId("Customize");
|
||||||
|
var actorCustomize = new Customize(actor.IsCharacter
|
||||||
|
? *(Penumbra.GameData.Structs.CustomizeData*)&actor.AsCharacter->DrawData.CustomizeData
|
||||||
|
: new Penumbra.GameData.Structs.CustomizeData());
|
||||||
|
var modelCustomize = new Customize(model.IsHuman
|
||||||
|
? *(Penumbra.GameData.Structs.CustomizeData*)model.AsHuman->CustomizeData
|
||||||
|
: new Penumbra.GameData.Structs.CustomizeData());
|
||||||
|
foreach (var type in Enum.GetValues<CustomizeIndex>())
|
||||||
|
{
|
||||||
|
using var id2 = ImRaii.PushId((int)type);
|
||||||
|
ImGuiUtil.DrawTableColumn(type.ToDefaultName());
|
||||||
|
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actorCustomize[type].Value.ToString("X2") : "No Character");
|
||||||
|
ImGuiUtil.DrawTableColumn(model.IsHuman ? modelCustomize[type].Value.ToString("X2") : "No Human");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (!model.IsHuman || type.ToFlag().RequiresRedraw())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ImGui.SmallButton("++"))
|
||||||
|
{
|
||||||
|
modelCustomize.Set(type, (CustomizeValue)(modelCustomize[type].Value + 1));
|
||||||
|
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("--"))
|
||||||
|
{
|
||||||
|
modelCustomize.Set(type, (CustomizeValue)(modelCustomize[type].Value - 1));
|
||||||
|
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("Reset"))
|
||||||
|
{
|
||||||
|
modelCustomize.Set(type, actorCustomize[type]);
|
||||||
|
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Penumbra
|
||||||
|
|
||||||
|
private Model _drawObject = Model.Null;
|
||||||
|
|
||||||
|
private void DrawPenumbraHeader()
|
||||||
|
{
|
||||||
|
if (!ImGui.CollapsingHeader("Penumbra"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var table = ImRaii.Table("##PenumbraTable", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn("Available");
|
||||||
|
ImGuiUtil.DrawTableColumn(_penumbra.Available.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGui.SmallButton("Unattach"))
|
||||||
|
_penumbra.Unattach();
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("Reattach"))
|
||||||
|
_penumbra.Reattach();
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn("Draw Object");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var address = _drawObject.Address;
|
||||||
|
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||||
|
if (ImGui.InputScalar("##drawObjectPtr", ImGuiDataType.U64, (nint)(&address), IntPtr.Zero, IntPtr.Zero, "%llx",
|
||||||
|
ImGuiInputTextFlags.CharsHexadecimal))
|
||||||
|
_drawObject = address;
|
||||||
|
ImGuiUtil.DrawTableColumn(_penumbra.Available
|
||||||
|
? $"0x{_penumbra.GameObjectFromDrawObject(_drawObject).Address:X}"
|
||||||
|
: "Penumbra Unavailable");
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn("Cutscene Object");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.InputInt("##CutsceneIndex", ref _gameObjectIndex, 0, 0);
|
||||||
|
ImGuiUtil.DrawTableColumn(_penumbra.Available
|
||||||
|
? _penumbra.CutsceneParent(_gameObjectIndex).ToString()
|
||||||
|
: "Penumbra Unavailable");
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn("Redraw Object");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.InputInt("##redrawObject", ref _gameObjectIndex, 0, 0);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using (var disabled = ImRaii.Disabled(!_penumbra.Available))
|
||||||
|
{
|
||||||
|
if (ImGui.SmallButton("Redraw"))
|
||||||
|
_penumbra.RedrawObject(_objects.GetObjectAddress(_gameObjectIndex), RedrawType.Redraw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region GameData
|
||||||
|
|
||||||
|
private void DrawGameDataHeader()
|
||||||
|
{
|
||||||
|
if (!ImGui.CollapsingHeader("Game Data"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
DrawIdentifierService();
|
||||||
|
DrawActorService();
|
||||||
|
DrawItemService();
|
||||||
|
DrawCustomizationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _gamePath = string.Empty;
|
||||||
|
private int _setId;
|
||||||
|
private int _secondaryId;
|
||||||
|
private int _variant;
|
||||||
|
|
||||||
|
private void DrawIdentifierService()
|
||||||
|
{
|
||||||
|
using var disabled = ImRaii.Disabled(!_identifier.Valid);
|
||||||
|
using var tree = ImRaii.TreeNode("Identifier Service");
|
||||||
|
if (!tree || !_identifier.Valid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
disabled.Dispose();
|
||||||
|
|
||||||
|
|
||||||
|
static void Text(string text)
|
||||||
|
{
|
||||||
|
if (text.Length > 0)
|
||||||
|
ImGui.TextUnformatted(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TextUnformatted("Parse Game Path");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.InputTextWithHint("##gamePath", "Enter game path...", ref _gamePath, 256);
|
||||||
|
var fileInfo = _identifier.AwaitedService.GamePathParser.GetFileInfo(_gamePath);
|
||||||
|
ImGui.TextUnformatted(
|
||||||
|
$"{fileInfo.ObjectType} {fileInfo.EquipSlot} {fileInfo.PrimaryId} {fileInfo.SecondaryId} {fileInfo.Variant} {fileInfo.BodySlot} {fileInfo.CustomizationType}");
|
||||||
|
Text(string.Join("\n", _identifier.AwaitedService.Identify(_gamePath).Keys));
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.TextUnformatted("Identify Model");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.InputInt("##SetId", ref _setId, 0, 0);
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.InputInt("##TypeId", ref _secondaryId, 0, 0);
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.InputInt("##Variant", ref _variant, 0, 0);
|
||||||
|
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
{
|
||||||
|
var identified = _identifier.AwaitedService.Identify((SetId)_setId, (ushort)_variant, slot);
|
||||||
|
Text(string.Join("\n", identified.Select(i => i.Name.ToDalamudString().TextValue)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var main = _identifier.AwaitedService.Identify((SetId)_setId, (WeaponType)_secondaryId, (ushort)_variant, EquipSlot.MainHand);
|
||||||
|
Text(string.Join("\n", main.Select(i => i.Name.ToDalamudString().TextValue)));
|
||||||
|
var off = _identifier.AwaitedService.Identify((SetId)_setId, (WeaponType)_secondaryId, (ushort)_variant, EquipSlot.OffHand);
|
||||||
|
Text(string.Join("\n", off.Select(i => i.Name.ToDalamudString().TextValue)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _bnpcFilter = string.Empty;
|
||||||
|
private string _enpcFilter = string.Empty;
|
||||||
|
private string _companionFilter = string.Empty;
|
||||||
|
private string _mountFilter = string.Empty;
|
||||||
|
private string _ornamentFilter = string.Empty;
|
||||||
|
private string _worldFilter = string.Empty;
|
||||||
|
|
||||||
|
private void DrawActorService()
|
||||||
|
{
|
||||||
|
using var disabled = ImRaii.Disabled(!_actors.Valid);
|
||||||
|
using var tree = ImRaii.TreeNode("Actor Service");
|
||||||
|
if (!tree || !_actors.Valid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
disabled.Dispose();
|
||||||
|
|
||||||
|
DrawNameTable("BNPCs", ref _bnpcFilter, _actors.AwaitedService.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Value)));
|
||||||
|
DrawNameTable("ENPCs", ref _enpcFilter, _actors.AwaitedService.Data.ENpcs.Select(kvp => (kvp.Key, kvp.Value)));
|
||||||
|
DrawNameTable("Companions", ref _companionFilter, _actors.AwaitedService.Data.Companions.Select(kvp => (kvp.Key, kvp.Value)));
|
||||||
|
DrawNameTable("Mounts", ref _mountFilter, _actors.AwaitedService.Data.Mounts.Select(kvp => (kvp.Key, kvp.Value)));
|
||||||
|
DrawNameTable("Ornaments", ref _ornamentFilter, _actors.AwaitedService.Data.Ornaments.Select(kvp => (kvp.Key, kvp.Value)));
|
||||||
|
DrawNameTable("Worlds", ref _worldFilter, _actors.AwaitedService.Data.Worlds.Select(kvp => ((uint)kvp.Key, kvp.Value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawNameTable(string label, ref string filter, IEnumerable<(uint, string)> names)
|
||||||
|
{
|
||||||
|
using var _ = ImRaii.PushId(label);
|
||||||
|
using var tree = ImRaii.TreeNode(label);
|
||||||
|
if (!tree)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref filter, 256);
|
||||||
|
var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y;
|
||||||
|
using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter,
|
||||||
|
new Vector2(-1, 10 * height));
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (resetScroll)
|
||||||
|
ImGui.SetScrollY(0);
|
||||||
|
ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var skips = ImGuiClip.GetNecessarySkips(height);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var f = filter;
|
||||||
|
var remainder = ImGuiClip.FilteredClippedDraw(names.Select(p => (p.Item1.ToString("D5"), p.Item2)), skips,
|
||||||
|
p => p.Item1.Contains(f) || p.Item2.Contains(f, StringComparison.OrdinalIgnoreCase),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTableColumn(p.Item1);
|
||||||
|
ImGuiUtil.DrawTableColumn(p.Item2);
|
||||||
|
});
|
||||||
|
ImGuiClip.DrawEndDummy(remainder, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawItemService()
|
||||||
|
{
|
||||||
|
using var disabled = ImRaii.Disabled(!_items.Valid);
|
||||||
|
using var tree = ImRaii.TreeNode("Item Manager");
|
||||||
|
if (!tree || !_items.Valid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
disabled.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCustomizationService()
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId("Customization");
|
||||||
|
ImGuiUtil.DrawTableColumn("Customization Service");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (!_customization.Valid)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted("Unavailable");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var tree = ImRaii.TreeNode("Available###Customization", ImGuiTreeNodeFlags.NoTreePushOnOpen);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
if (!tree)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
@ -1,24 +1,34 @@
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Interop;
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Access the function the game uses to update customize data on the character screen.
|
||||||
|
/// Changes in Race, body type or Gender are probably ignored.
|
||||||
|
/// This operates on draw objects, not game objects.
|
||||||
|
/// </summary>
|
||||||
public unsafe class ChangeCustomizeService
|
public unsafe class ChangeCustomizeService
|
||||||
{
|
{
|
||||||
public ChangeCustomizeService()
|
public ChangeCustomizeService()
|
||||||
=> SignatureHelper.Initialise(this);
|
=> SignatureHelper.Initialise(this);
|
||||||
|
|
||||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
private delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||||
|
|
||||||
[Signature(Sigs.ChangeCustomize)]
|
[Signature(Sigs.ChangeCustomize)]
|
||||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||||
|
|
||||||
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
public bool UpdateCustomize(Model model, CustomizeData customize)
|
||||||
{
|
{
|
||||||
if (customize.Data == null || !actor.Valid || !actor.DrawObject.Valid)
|
if (!model.IsHuman)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return _changeCustomize(actor.DrawObject.Pointer, customize.Data, 1);
|
Item.Log.Verbose($"[ChangeCustomize] Invoked on 0x{model.Address:X} with {customize}.");
|
||||||
|
return _changeCustomize(model.AsHuman, customize.Data, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
||||||
|
=> UpdateCustomize(actor.Model, customize);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
142
Glamourer/Interop/Penumbra/PenumbraService.cs
Normal file
142
Glamourer/Interop/Penumbra/PenumbraService.cs
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
using System;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Penumbra.Api;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.Api.Helpers;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Penumbra;
|
||||||
|
|
||||||
|
public unsafe class PenumbraService : 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 PenumbraService(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Obtain the game object corresponding to a draw object. </summary>
|
||||||
|
public Actor GameObjectFromDrawObject(Model drawObject)
|
||||||
|
=> Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null;
|
||||||
|
|
||||||
|
/// <summary> Obtain the parent of a cutscene actor if it is known. </summary>
|
||||||
|
public int CutsceneParent(int idx)
|
||||||
|
=> Available ? _cutsceneParent.Invoke(idx) : -1;
|
||||||
|
|
||||||
|
/// <summary> Try to redraw the given actor. </summary>
|
||||||
|
public void RedrawObject(Actor actor, RedrawType settings)
|
||||||
|
{
|
||||||
|
if (!actor || !Available)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_redrawSubscriber.Invoke(actor.AsObject->ObjectIndex, settings);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
PluginLog.Debug($"Failure redrawing object:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Reattach to the currently running Penumbra IPC provider. Unattaches before if necessary. </summary>
|
||||||
|
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;
|
||||||
|
Item.Log.Debug("Glamourer attached to Penumbra.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Item.Log.Debug($"Could not attach to Penumbra:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Unattach from the currently running Penumbra IPC provider. </summary>
|
||||||
|
public void Unattach()
|
||||||
|
{
|
||||||
|
_tooltipSubscriber.Disable();
|
||||||
|
_clickSubscriber.Disable();
|
||||||
|
_creatingCharacterBase.Disable();
|
||||||
|
_createdCharacterBase.Disable();
|
||||||
|
if (Available)
|
||||||
|
{
|
||||||
|
Available = false;
|
||||||
|
Item.Log.Debug("Glamourer detached from Penumbra.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Unattach();
|
||||||
|
_tooltipSubscriber.Dispose();
|
||||||
|
_clickSubscriber.Dispose();
|
||||||
|
_creatingCharacterBase.Dispose();
|
||||||
|
_createdCharacterBase.Dispose();
|
||||||
|
_initializedEvent.Dispose();
|
||||||
|
_disposedEvent.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
97
Glamourer/Interop/Structs/Actor.cs
Normal file
97
Glamourer/Interop/Structs/Actor.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
using System;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Structs;
|
||||||
|
|
||||||
|
public readonly unsafe struct Actor : IEquatable<Actor>
|
||||||
|
{
|
||||||
|
private Actor(nint address)
|
||||||
|
=> Address = address;
|
||||||
|
|
||||||
|
public static readonly Actor Null = new(nint.Zero);
|
||||||
|
|
||||||
|
public readonly nint Address;
|
||||||
|
|
||||||
|
public GameObject* AsObject
|
||||||
|
=> (GameObject*)Address;
|
||||||
|
|
||||||
|
public Character* AsCharacter
|
||||||
|
=> (Character*)Address;
|
||||||
|
|
||||||
|
public bool Valid
|
||||||
|
=> Address != nint.Zero;
|
||||||
|
|
||||||
|
public bool IsCharacter
|
||||||
|
=> Valid && AsObject->IsCharacter();
|
||||||
|
|
||||||
|
public static implicit operator Actor(nint? pointer)
|
||||||
|
=> new(pointer ?? nint.Zero);
|
||||||
|
|
||||||
|
public static implicit operator Actor(GameObject* pointer)
|
||||||
|
=> new((nint)pointer);
|
||||||
|
|
||||||
|
public static implicit operator Actor(Character* pointer)
|
||||||
|
=> new((nint)pointer);
|
||||||
|
|
||||||
|
public static implicit operator nint(Actor actor)
|
||||||
|
=> actor.Address;
|
||||||
|
|
||||||
|
public ActorIdentifier GetIdentifier(ActorManager actors)
|
||||||
|
=> actors.FromObject(AsObject, out _, true, true, false);
|
||||||
|
|
||||||
|
public bool Identifier(ActorManager actors, out ActorIdentifier ident)
|
||||||
|
{
|
||||||
|
if (Valid)
|
||||||
|
{
|
||||||
|
ident = GetIdentifier(actors);
|
||||||
|
return ident.IsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
ident = ActorIdentifier.Invalid;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Model Model
|
||||||
|
=> Valid ? AsObject->DrawObject : null;
|
||||||
|
|
||||||
|
public static implicit operator bool(Actor actor)
|
||||||
|
=> actor.Address != nint.Zero;
|
||||||
|
|
||||||
|
public static bool operator true(Actor actor)
|
||||||
|
=> actor.Address != nint.Zero;
|
||||||
|
|
||||||
|
public static bool operator false(Actor actor)
|
||||||
|
=> actor.Address == nint.Zero;
|
||||||
|
|
||||||
|
public static bool operator !(Actor actor)
|
||||||
|
=> actor.Address == nint.Zero;
|
||||||
|
|
||||||
|
public bool Equals(Actor other)
|
||||||
|
=> Address == other.Address;
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
=> obj is Actor other && Equals(other);
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
=> Address.GetHashCode();
|
||||||
|
|
||||||
|
public static bool operator ==(Actor lhs, Actor rhs)
|
||||||
|
=> lhs.Address == rhs.Address;
|
||||||
|
|
||||||
|
public static bool operator !=(Actor lhs, Actor rhs)
|
||||||
|
=> lhs.Address != rhs.Address;
|
||||||
|
|
||||||
|
/// <summary> Only valid for characters. </summary>
|
||||||
|
public CharacterArmor GetArmor(EquipSlot slot)
|
||||||
|
=> ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()];
|
||||||
|
|
||||||
|
public CharacterWeapon GetMainhand()
|
||||||
|
=> *(CharacterWeapon*)&AsCharacter->DrawData.MainHandModel;
|
||||||
|
|
||||||
|
public CharacterWeapon GetOffhand()
|
||||||
|
=> *(CharacterWeapon*)&AsCharacter->DrawData.OffHandModel;
|
||||||
|
}
|
||||||
26
Glamourer/Interop/Structs/ActorData.cs
Normal file
26
Glamourer/Interop/Structs/ActorData.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Structs;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
92
Glamourer/Interop/Structs/Model.cs
Normal file
92
Glamourer/Interop/Structs/Model.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Structs;
|
||||||
|
|
||||||
|
public readonly unsafe struct Model : IEquatable<Model>
|
||||||
|
{
|
||||||
|
private Model(nint address)
|
||||||
|
=> Address = address;
|
||||||
|
|
||||||
|
public readonly nint Address;
|
||||||
|
|
||||||
|
public static readonly Model Null = new(0);
|
||||||
|
|
||||||
|
public DrawObject* AsDrawObject
|
||||||
|
=> (DrawObject*)Address;
|
||||||
|
|
||||||
|
public CharacterBase* AsCharacterBase
|
||||||
|
=> (CharacterBase*)Address;
|
||||||
|
|
||||||
|
public Human* AsHuman
|
||||||
|
=> (Human*)Address;
|
||||||
|
|
||||||
|
public static implicit operator Model(nint? pointer)
|
||||||
|
=> new(pointer ?? nint.Zero);
|
||||||
|
|
||||||
|
public static implicit operator Model(DrawObject* pointer)
|
||||||
|
=> new((nint)pointer);
|
||||||
|
|
||||||
|
public static implicit operator Model(Human* pointer)
|
||||||
|
=> new((nint)pointer);
|
||||||
|
|
||||||
|
public static implicit operator Model(CharacterBase* pointer)
|
||||||
|
=> new((nint)pointer);
|
||||||
|
|
||||||
|
public static implicit operator nint(Model model)
|
||||||
|
=> model.Address;
|
||||||
|
|
||||||
|
public bool Valid
|
||||||
|
=> Address != nint.Zero;
|
||||||
|
|
||||||
|
public bool IsCharacterBase
|
||||||
|
=> Valid && AsDrawObject->Object.GetObjectType() == ObjectType.CharacterBase;
|
||||||
|
|
||||||
|
public bool IsHuman
|
||||||
|
=> IsCharacterBase && AsCharacterBase->GetModelType() == CharacterBase.ModelType.Human;
|
||||||
|
|
||||||
|
public static implicit operator bool(Model actor)
|
||||||
|
=> actor.Address != nint.Zero;
|
||||||
|
|
||||||
|
public static bool operator true(Model actor)
|
||||||
|
=> actor.Address != nint.Zero;
|
||||||
|
|
||||||
|
public static bool operator false(Model actor)
|
||||||
|
=> actor.Address == nint.Zero;
|
||||||
|
|
||||||
|
public static bool operator !(Model actor)
|
||||||
|
=> actor.Address == nint.Zero;
|
||||||
|
|
||||||
|
public bool Equals(Model other)
|
||||||
|
=> Address == other.Address;
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
=> obj is Model other && Equals(other);
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
=> Address.GetHashCode();
|
||||||
|
|
||||||
|
public static bool operator ==(Model lhs, Model rhs)
|
||||||
|
=> lhs.Address == rhs.Address;
|
||||||
|
|
||||||
|
public static bool operator !=(Model lhs, Model rhs)
|
||||||
|
=> lhs.Address != rhs.Address;
|
||||||
|
|
||||||
|
/// <summary> Only valid for humans. </summary>
|
||||||
|
public CharacterArmor GetArmor(EquipSlot slot)
|
||||||
|
=> ((CharacterArmor*)AsHuman->EquipSlotData)[slot.ToIndex()];
|
||||||
|
|
||||||
|
public CharacterWeapon GetMainhand()
|
||||||
|
{
|
||||||
|
var weapon = AsDrawObject->Object.ChildObject;
|
||||||
|
if (weapon == null)
|
||||||
|
return CharacterWeapon.Empty;
|
||||||
|
weapon
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharacterWeapon GetOffhand()
|
||||||
|
=> *(CharacterWeapon*)&AsCharacter->DrawData.OffHandModel;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
|
using Glamourer.Events;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
|
@ -9,8 +10,11 @@ namespace Glamourer.Interop;
|
||||||
|
|
||||||
public unsafe class UpdateSlotService : IDisposable
|
public unsafe class UpdateSlotService : IDisposable
|
||||||
{
|
{
|
||||||
public UpdateSlotService()
|
public readonly UpdatedSlot Event;
|
||||||
|
|
||||||
|
public UpdateSlotService(UpdatedSlot updatedSlot)
|
||||||
{
|
{
|
||||||
|
Event = updatedSlot;
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
_flagSlotForUpdateHook.Enable();
|
_flagSlotForUpdateHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
@ -19,59 +23,41 @@ public unsafe class UpdateSlotService : IDisposable
|
||||||
=> _flagSlotForUpdateHook.Dispose();
|
=> _flagSlotForUpdateHook.Dispose();
|
||||||
|
|
||||||
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
|
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))]
|
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||||
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
|
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
|
||||||
|
|
||||||
public event FlagSlotForUpdateDelegate? EquipUpdate;
|
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
|
||||||
|
|
||||||
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);
|
if (!drawObject.IsCharacterBase)
|
||||||
|
return;
|
||||||
FlagSlotForUpdateInterop(drawObject, slot, data);
|
FlagSlotForUpdateInterop(drawObject, slot, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
|
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor data)
|
||||||
{
|
{
|
||||||
var armor = drawObject.Equip[slot] with { Stain = stain };
|
if (!drawObject.IsCharacterBase)
|
||||||
UpdateSlot(drawObject, slot, armor);
|
return;
|
||||||
|
|
||||||
|
FlagSlotForUpdateInterop(drawObject, slot, data.With(drawObject.GetArmor(slot).Stain));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain)
|
||||||
|
{
|
||||||
|
if (!drawObject.IsHuman)
|
||||||
|
return;
|
||||||
|
|
||||||
|
FlagSlotForUpdateInterop(drawObject, slot, drawObject.GetArmor(slot).With(stain));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||||
{
|
{
|
||||||
var slot = slotIdx.ToEquipSlot();
|
var slot = slotIdx.ToEquipSlot();
|
||||||
InvokeFlagSlotEvent(drawObject, slot, ref *data);
|
var returnValue = ulong.MaxValue;
|
||||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
Event.Invoke(drawObject, slot, ref *data, ref returnValue);
|
||||||
|
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InvokeFlagSlotEvent(DrawObject drawObject, EquipSlot slot, ref CharacterArmor armor)
|
private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor)
|
||||||
{
|
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &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}.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Runtime.CompilerServices;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using Glamourer.Events;
|
||||||
using Penumbra.GameData.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Interop;
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
public class VisorService : IDisposable
|
public class VisorService : IDisposable
|
||||||
{
|
{
|
||||||
public VisorService()
|
public readonly VisorStateChanged Event;
|
||||||
|
|
||||||
|
public VisorService(VisorStateChanged visorStateChanged)
|
||||||
{
|
{
|
||||||
|
Event = visorStateChanged;
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
_setupVisorHook.Enable();
|
_setupVisorHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
@ -18,61 +21,67 @@ public class VisorService : IDisposable
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
=> _setupVisorHook.Dispose();
|
=> _setupVisorHook.Dispose();
|
||||||
|
|
||||||
public static unsafe bool GetVisorState(nint humanPtr)
|
/// <summary> Obtain the current state of the Visor for the given draw object (true: toggled). </summary>
|
||||||
|
public unsafe bool GetVisorState(Model characterBase)
|
||||||
{
|
{
|
||||||
if (humanPtr == IntPtr.Zero)
|
if (!characterBase.IsCharacterBase)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var data = (Human*)humanPtr;
|
// TODO: use client structs.
|
||||||
var flags = &data->CharacterBase.UnkFlags_01;
|
return (characterBase.AsCharacterBase->UnkFlags_01 & Offsets.DrawObjectVisorStateFlag) != 0;
|
||||||
return (*flags & Offsets.DrawObjectVisorStateFlag) != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void SetVisorState(nint humanPtr, bool on)
|
/// <summary> Manually set the state of the Visor for the given draw object. </summary>
|
||||||
|
/// <param name="human"> The draw object. </param>
|
||||||
|
/// <param name="on"> The desired state (true: toggled). </param>
|
||||||
|
/// <returns> Whether the state was changed. </returns>
|
||||||
|
public unsafe bool SetVisorState(Model human, bool on)
|
||||||
{
|
{
|
||||||
if (humanPtr == IntPtr.Zero)
|
if (!human.IsHuman)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
var data = (Human*)humanPtr;
|
var oldState = GetVisorState(human);
|
||||||
_setupVisorHook.Original(humanPtr, (ushort) data->HeadSetID, on);
|
Item.Log.Verbose($"[SetVisorState] Invoked manually on 0x{human.Address:X} switching from {oldState} to {on}.");
|
||||||
|
if (oldState == on)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
SetupVisorHook(human, (ushort)human.AsHuman->HeadSetID, on);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, bool 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))]
|
[Signature(global::Penumbra.GameData.Sigs.SetupVisor, DetourName = nameof(SetupVisorDetour))]
|
||||||
private readonly Hook<UpdateVisorDelegateInternal> _setupVisorHook = null!;
|
private readonly Hook<UpdateVisorDelegateInternal> _setupVisorHook = null!;
|
||||||
|
|
||||||
public event UpdateVisorDelegate? VisorUpdate;
|
private void SetupVisorDetour(nint human, ushort modelId, bool on)
|
||||||
|
|
||||||
private void SetupVisorDetour(nint humanPtr, ushort modelId, bool on)
|
|
||||||
{
|
{
|
||||||
InvokeVisorEvent(humanPtr, modelId, ref on);
|
var callOriginal = true;
|
||||||
_setupVisorHook.Original(humanPtr, modelId, on);
|
var originalOn = on;
|
||||||
|
// Invoke an event that can change the requested value
|
||||||
|
// and also control whether the function should be called at all.
|
||||||
|
Event.Invoke(human, ref on, ref callOriginal);
|
||||||
|
|
||||||
|
Item.Log.Excessive(
|
||||||
|
$"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn}, call original {callOriginal}).");
|
||||||
|
|
||||||
|
if (callOriginal)
|
||||||
|
SetupVisorHook(human, modelId, on);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InvokeVisorEvent(DrawObject drawObject, SetId modelId, ref bool on)
|
/// <summary>
|
||||||
|
/// The SetupVisor function does not set the visor state for the draw object itself,
|
||||||
|
/// it only sets the "visor is changing" state to false.
|
||||||
|
/// So we wrap a manual change of that flag with the function call.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private unsafe void SetupVisorHook(Model human, ushort modelId, bool on)
|
||||||
{
|
{
|
||||||
if (VisorUpdate == null)
|
// TODO: use client structs.
|
||||||
{
|
human.AsCharacterBase->UnkFlags_01 = (byte)(on
|
||||||
Glamourer.Log.Excessive($"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}.");
|
? human.AsCharacterBase->UnkFlags_01 | Offsets.DrawObjectVisorStateFlag
|
||||||
return;
|
: human.AsCharacterBase->UnkFlags_01 & ~Offsets.DrawObjectVisorStateFlag);
|
||||||
}
|
_setupVisorHook.Original(human.Address, modelId, on);
|
||||||
|
|
||||||
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}.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
|
@ -13,6 +13,7 @@ public unsafe class WeaponService : IDisposable
|
||||||
public WeaponService()
|
public WeaponService()
|
||||||
{
|
{
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
|
_loadWeaponHook = Hook<LoadWeaponDelegate>.FromAddress((nint) DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
|
||||||
_loadWeaponHook.Enable();
|
_loadWeaponHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,68 +22,18 @@ public unsafe class WeaponService : IDisposable
|
||||||
_loadWeaponHook.Dispose();
|
_loadWeaponHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly int CharacterWeaponOffset = (int)Marshal.OffsetOf<Character>("DrawData");
|
private delegate void LoadWeaponDelegate(DrawDataContainer* drawData, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject, byte unk4);
|
||||||
|
|
||||||
public delegate void LoadWeaponDelegate(nint offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook;
|
||||||
byte skipGameObject,
|
|
||||||
byte unk4);
|
|
||||||
|
|
||||||
// Weapons for a specific character are reloaded with this function.
|
private void LoadWeaponDetour(DrawDataContainer* drawData, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
||||||
// 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)
|
byte unk4)
|
||||||
{
|
{
|
||||||
//var oldWeapon = weapon;
|
var actor = (Actor) (nint)drawData->Unk8;
|
||||||
//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.
|
// First call the regular function.
|
||||||
_loadWeaponHook.Original(characterOffset, slot, weapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
_loadWeaponHook.Original(drawData, slot, weapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||||
Glamourer.Log.Excessive($"Weapon reloaded for {(Actor)(characterOffset - CharacterWeaponOffset)} with attributes {slot} {weapon:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}");
|
Item.Log.Information($"Weapon reloaded for 0x{actor.Address:X} 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.
|
// Load a specific weapon for a character by its data and slot.
|
||||||
|
|
@ -91,14 +42,14 @@ public unsafe class WeaponService : IDisposable
|
||||||
switch (slot)
|
switch (slot)
|
||||||
{
|
{
|
||||||
case EquipSlot.MainHand:
|
case EquipSlot.MainHand:
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
LoadWeaponDetour(&character.AsCharacter->DrawData, 0, weapon.Value, 0, 0, 1, 0);
|
||||||
return;
|
return;
|
||||||
case EquipSlot.OffHand:
|
case EquipSlot.OffHand:
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
|
LoadWeaponDetour(&character.AsCharacter->DrawData, 1, weapon.Value, 0, 0, 1, 0);
|
||||||
return;
|
return;
|
||||||
case EquipSlot.BothHand:
|
case EquipSlot.BothHand:
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
LoadWeaponDetour(&character.AsCharacter->DrawData, 0, weapon.Value, 0, 0, 1, 0);
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
LoadWeaponDetour(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
||||||
return;
|
return;
|
||||||
// function can also be called with '2', but does not seem to ever be.
|
// function can also be called with '2', but does not seem to ever be.
|
||||||
}
|
}
|
||||||
|
|
@ -107,14 +58,14 @@ public unsafe class WeaponService : IDisposable
|
||||||
// Load specific Main- and Offhand weapons.
|
// Load specific Main- and Offhand weapons.
|
||||||
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
|
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
|
||||||
{
|
{
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 1, 0, 1, 0);
|
LoadWeaponDetour(&character.AsCharacter->DrawData, 0, main.Value, 1, 0, 1, 0);
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 1, 0, 1, 0);
|
LoadWeaponDetour(&character.AsCharacter->DrawData, 1, off.Value, 1, 0, 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadStain(Actor character, EquipSlot slot, StainId stain)
|
public void LoadStain(Actor character, EquipSlot slot, StainId stain)
|
||||||
{
|
{
|
||||||
var weapon = slot == EquipSlot.OffHand ? character.OffHand : character.MainHand;
|
var value = slot == EquipSlot.OffHand ? character.AsCharacter->DrawData.OffHandModel : character.AsCharacter->DrawData.MainHandModel;
|
||||||
weapon.Stain = stain;
|
var weapon = new CharacterWeapon(value.Value) { Stain = stain.Value };
|
||||||
LoadWeapon(character, slot, weapon);
|
LoadWeapon(character, slot, weapon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
|
using Glamourer.Gui.Tabs;
|
||||||
|
|
||||||
namespace Glamourer.Services;
|
namespace Glamourer.Services;
|
||||||
|
|
||||||
|
|
@ -11,12 +12,12 @@ public class CommandService : IDisposable
|
||||||
private const string ApplyCommandString = "/glamour";
|
private const string ApplyCommandString = "/glamour";
|
||||||
|
|
||||||
private readonly CommandManager _commands;
|
private readonly CommandManager _commands;
|
||||||
private readonly Interface _interface;
|
private readonly MainWindow _mainWindow;
|
||||||
|
|
||||||
public CommandService(CommandManager commands, Interface ui)
|
public CommandService(CommandManager commands, MainWindow mainWindow)
|
||||||
{
|
{
|
||||||
_commands = commands;
|
_commands = commands;
|
||||||
_interface = ui;
|
_mainWindow = mainWindow;
|
||||||
|
|
||||||
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
|
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
|
||||||
_commands.AddHandler(ApplyCommandString, new CommandInfo(OnGlamour) { HelpMessage = $"Use Glamourer Functions: {HelpString}" });
|
_commands.AddHandler(ApplyCommandString, new CommandInfo(OnGlamour) { HelpMessage = $"Use Glamourer Functions: {HelpString}" });
|
||||||
|
|
@ -29,7 +30,7 @@ public class CommandService : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGlamourer(string command, string arguments)
|
private void OnGlamourer(string command, string arguments)
|
||||||
=> _interface.Toggle();
|
=> _mainWindow.Toggle();
|
||||||
|
|
||||||
private void OnGlamour(string command, string arguments)
|
private void OnGlamour(string command, string arguments)
|
||||||
{ }
|
{ }
|
||||||
|
|
|
||||||
49
Glamourer/Services/DalamudServices.cs
Normal file
49
Glamourer/Services/DalamudServices.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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.Services;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Designs;
|
|
||||||
|
|
||||||
namespace Glamourer.Services;
|
namespace Glamourer.Services;
|
||||||
|
|
||||||
|
|
@ -32,9 +30,6 @@ public class FilenameService
|
||||||
yield return new FileInfo(file);
|
yield return new FileInfo(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DesignFile(Design design)
|
|
||||||
=> DesignFile(design.Identifier.ToString());
|
|
||||||
|
|
||||||
public string DesignFile(string identifier)
|
public string DesignFile(string identifier)
|
||||||
=> Path.Combine(DesignDirectory, $"{identifier}.json");
|
=> Path.Combine(DesignDirectory, $"{identifier}.json");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ public class ItemManager : IDisposable
|
||||||
|
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
public readonly IdentifierService IdentifierService;
|
public readonly IdentifierService IdentifierService;
|
||||||
public readonly ExcelSheet<Item> ItemSheet;
|
public readonly ExcelSheet<Lumina.Excel.GeneratedSheets.Item> ItemSheet;
|
||||||
public readonly StainData Stains;
|
public readonly StainData Stains;
|
||||||
public readonly ItemService ItemService;
|
public readonly ItemService ItemService;
|
||||||
public readonly RestrictedGear RestrictedGear;
|
public readonly RestrictedGear RestrictedGear;
|
||||||
|
|
@ -30,7 +30,7 @@ public class ItemManager : IDisposable
|
||||||
public ItemManager(DalamudPluginInterface pi, DataManager gameData, IdentifierService identifierService, ItemService itemService, Configuration config)
|
public ItemManager(DalamudPluginInterface pi, DataManager gameData, IdentifierService identifierService, ItemService itemService, Configuration config)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
ItemSheet = gameData.GetExcelSheet<Item>()!;
|
ItemSheet = gameData.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
|
||||||
IdentifierService = identifierService;
|
IdentifierService = identifierService;
|
||||||
Stains = new StainData(pi, gameData, gameData.Language);
|
Stains = new StainData(pi, gameData, gameData.Language);
|
||||||
ItemService = itemService;
|
ItemService = itemService;
|
||||||
|
|
@ -52,7 +52,7 @@ public class ItemManager : IDisposable
|
||||||
return (false, armor);
|
return (false, armor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly Item DefaultSword;
|
public readonly Lumina.Excel.GeneratedSheets.Item DefaultSword;
|
||||||
|
|
||||||
public static uint NothingId(EquipSlot slot)
|
public static uint NothingId(EquipSlot slot)
|
||||||
=> uint.MaxValue - 128 - (uint)slot.ToSlot();
|
=> uint.MaxValue - 128 - (uint)slot.ToSlot();
|
||||||
|
|
@ -81,7 +81,7 @@ public class ItemManager : IDisposable
|
||||||
return new Designs.Item(SmallClothesNpc, SmallclothesId(slot), new CharacterArmor(SmallClothesNpcModel, 1, 0));
|
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)
|
public (bool Valid, SetId Id, byte Variant, string ItemName) Resolve(EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
|
||||||
{
|
{
|
||||||
slot = slot.ToSlot();
|
slot = slot.ToSlot();
|
||||||
if (itemId == NothingId(slot))
|
if (itemId == NothingId(slot))
|
||||||
|
|
@ -100,7 +100,7 @@ public class ItemManager : IDisposable
|
||||||
return (true, (SetId)item.ModelMain, (byte)(item.ModelMain >> 16), string.Intern(item.Name.ToDalamudString().TextValue));
|
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)
|
public (bool Valid, SetId Id, WeaponType Weapon, byte Variant, string ItemName, FullEquipType Type) Resolve(uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
|
||||||
{
|
{
|
||||||
if (item == null || item.RowId != itemId)
|
if (item == null || item.RowId != itemId)
|
||||||
item = ItemSheet.GetRow(itemId);
|
item = ItemSheet.GetRow(itemId);
|
||||||
|
|
@ -117,7 +117,7 @@ public class ItemManager : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
public (bool Valid, SetId Id, WeaponType Weapon, byte Variant, string ItemName, FullEquipType Type) Resolve(uint itemId,
|
public (bool Valid, SetId Id, WeaponType Weapon, byte Variant, string ItemName, FullEquipType Type) Resolve(uint itemId,
|
||||||
FullEquipType mainType, Item? item = null)
|
FullEquipType mainType, Lumina.Excel.GeneratedSheets.Item? item = null)
|
||||||
{
|
{
|
||||||
var offType = mainType.Offhand();
|
var offType = mainType.Offhand();
|
||||||
if (itemId == NothingId(offType))
|
if (itemId == NothingId(offType))
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Api;
|
using Glamourer.Events;
|
||||||
using Glamourer.Designs;
|
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
|
using Glamourer.Gui.Tabs;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.State;
|
using Glamourer.Interop.Penumbra;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
|
|
@ -18,12 +18,10 @@ public static class ServiceManager
|
||||||
.AddSingleton(log)
|
.AddSingleton(log)
|
||||||
.AddDalamud(pi)
|
.AddDalamud(pi)
|
||||||
.AddMeta()
|
.AddMeta()
|
||||||
.AddConfig()
|
|
||||||
.AddPenumbra()
|
|
||||||
.AddInterop()
|
.AddInterop()
|
||||||
.AddGameData()
|
.AddEvents()
|
||||||
.AddDesigns()
|
.AddData()
|
||||||
.AddInterface()
|
.AddUi()
|
||||||
.AddApi();
|
.AddApi();
|
||||||
|
|
||||||
return services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true });
|
return services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true });
|
||||||
|
|
@ -36,45 +34,33 @@ public static class ServiceManager
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IServiceCollection AddMeta(this IServiceCollection services)
|
private static IServiceCollection AddMeta(this IServiceCollection services)
|
||||||
=> services.AddSingleton<FilenameService>()
|
=> services.AddSingleton<ChatService>()
|
||||||
.AddSingleton<SaveService>()
|
.AddSingleton<FilenameService>()
|
||||||
.AddSingleton<FrameworkManager>()
|
|
||||||
.AddSingleton<ChatService>();
|
|
||||||
|
|
||||||
private static IServiceCollection AddConfig(this IServiceCollection services)
|
|
||||||
=> services.AddSingleton<Configuration>()
|
|
||||||
.AddSingleton<BackupService>();
|
.AddSingleton<BackupService>();
|
||||||
|
|
||||||
private static IServiceCollection AddPenumbra(this IServiceCollection services)
|
private static IServiceCollection AddEvents(this IServiceCollection services)
|
||||||
=> services.AddSingleton<PenumbraAttach>();
|
=> services.AddSingleton<VisorStateChanged>()
|
||||||
|
.AddSingleton<UpdatedSlot>();
|
||||||
|
|
||||||
private static IServiceCollection AddGameData(this IServiceCollection services)
|
private static IServiceCollection AddData(this IServiceCollection services)
|
||||||
=> services.AddSingleton<IdentifierService>()
|
=> services.AddSingleton<IdentifierService>()
|
||||||
.AddSingleton<ActorService>()
|
|
||||||
.AddSingleton<ItemService>()
|
.AddSingleton<ItemService>()
|
||||||
.AddSingleton<ItemManager>()
|
.AddSingleton<ActorService>()
|
||||||
.AddSingleton<CustomizationService>();
|
.AddSingleton<CustomizationService>();
|
||||||
|
|
||||||
private static IServiceCollection AddInterop(this IServiceCollection services)
|
private static IServiceCollection AddInterop(this IServiceCollection services)
|
||||||
=> services.AddSingleton<ChangeCustomizeService>()
|
=> services.AddSingleton<VisorService>()
|
||||||
.AddSingleton<JobService>()
|
.AddSingleton<ChangeCustomizeService>()
|
||||||
.AddSingleton<UpdateSlotService>()
|
.AddSingleton<UpdateSlotService>()
|
||||||
.AddSingleton<VisorService>()
|
|
||||||
.AddSingleton<WeaponService>()
|
.AddSingleton<WeaponService>()
|
||||||
.AddSingleton<ObjectManager>();
|
.AddSingleton<PenumbraService>();
|
||||||
|
|
||||||
private static IServiceCollection AddDesigns(this IServiceCollection services)
|
private static IServiceCollection AddUi(this IServiceCollection services)
|
||||||
=> services.AddSingleton<DesignManager>()
|
=> services
|
||||||
.AddSingleton<DesignFileSystem>()
|
.AddSingleton<DebugTab>()
|
||||||
.AddSingleton<ActiveDesign.Manager>()
|
.AddSingleton<MainWindow>()
|
||||||
.AddSingleton<FixedDesignManager>()
|
|
||||||
.AddSingleton<RedrawManager>();
|
|
||||||
|
|
||||||
private static IServiceCollection AddInterface(this IServiceCollection services)
|
|
||||||
=> services.AddSingleton<Interface>()
|
|
||||||
.AddSingleton<GlamourerWindowSystem>();
|
.AddSingleton<GlamourerWindowSystem>();
|
||||||
|
|
||||||
private static IServiceCollection AddApi(this IServiceCollection services)
|
private static IServiceCollection AddApi(this IServiceCollection services)
|
||||||
=> services.AddSingleton<CommandService>()
|
=> services.AddSingleton<CommandService>();
|
||||||
.AddSingleton<Glamourer.GlamourerIpc>();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ using Penumbra.GameData.Actors;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Glamourer.Api;
|
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ public abstract class AsyncServiceWrapper<T>
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Service = service;
|
Service = service;
|
||||||
Glamourer.Log.Verbose($"[{Name}] Created.");
|
Item.Log.Verbose($"[{Name}] Created.");
|
||||||
_task = null;
|
_task = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -70,7 +70,7 @@ public abstract class AsyncServiceWrapper<T>
|
||||||
_task = null;
|
_task = null;
|
||||||
if (Service is IDisposable d)
|
if (Service is IDisposable d)
|
||||||
d.Dispose();
|
d.Dispose();
|
||||||
Glamourer.Log.Verbose($"[{Name}] Disposed.");
|
Item.Log.Verbose($"[{Name}] Disposed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ public sealed class ItemService : AsyncServiceWrapper<ItemData>
|
||||||
public sealed class ActorService : AsyncServiceWrapper<ActorManager>
|
public sealed class ActorService : AsyncServiceWrapper<ActorManager>
|
||||||
{
|
{
|
||||||
public ActorService(DalamudPluginInterface pi, ObjectTable objects, ClientState clientState, Framework framework, DataManager gameData,
|
public ActorService(DalamudPluginInterface pi, ObjectTable objects, ClientState clientState, Framework framework, DataManager gameData,
|
||||||
GameGui gui, PenumbraAttach penumbra)
|
GameGui gui, PenumbraService penumbra)
|
||||||
: base(nameof(ActorService),
|
: base(nameof(ActorService),
|
||||||
() => new ActorManager(pi, objects, clientState, framework, gameData, gui, idx => (short)penumbra.CutsceneParent(idx)))
|
() => new ActorManager(pi, objects, clientState, framework, gameData, gui, idx => (short)penumbra.CutsceneParent(idx)))
|
||||||
{ }
|
{ }
|
||||||
|
|
|
||||||
190
GlamourerOld/Glamourer.cs
Normal file
190
GlamourerOld/Glamourer.cs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
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;
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
126
GlamourerOld/GlamourerOld.csproj
Normal file
126
GlamourerOld/GlamourerOld.csproj
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
<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>
|
||||||
30
GlamourerOld/Gui/GlamourerWindowSystem.cs
Normal file
30
GlamourerOld/Gui/GlamourerWindowSystem.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
24
GlamourerOld/Interop/ChangeCustomizeService.cs
Normal file
24
GlamourerOld/Interop/ChangeCustomizeService.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
77
GlamourerOld/Interop/UpdateSlotService.cs
Normal file
77
GlamourerOld/Interop/UpdateSlotService.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
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}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
78
GlamourerOld/Interop/VisorService.cs
Normal file
78
GlamourerOld/Interop/VisorService.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
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}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
120
GlamourerOld/Interop/WeaponService.cs
Normal file
120
GlamourerOld/Interop/WeaponService.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
GlamourerOld/LegacyTattoo.raw
Normal file
BIN
GlamourerOld/LegacyTattoo.raw
Normal file
Binary file not shown.
30
GlamourerOld/Services/BackupService.cs
Normal file
30
GlamourerOld/Services/BackupService.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
GlamourerOld/Services/CommandService.cs
Normal file
36
GlamourerOld/Services/CommandService.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
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)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
40
GlamourerOld/Services/FilenameService.cs
Normal file
40
GlamourerOld/Services/FilenameService.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
189
GlamourerOld/Services/ItemManager.cs
Normal file
189
GlamourerOld/Services/ItemManager.cs
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
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 Configuration _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, Configuration 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
GlamourerOld/Services/ServiceManager.cs
Normal file
80
GlamourerOld/Services/ServiceManager.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
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<Configuration>()
|
||||||
|
.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>();
|
||||||
|
}
|
||||||
105
GlamourerOld/Services/ServiceWrapper.cs
Normal file
105
GlamourerOld/Services/ServiceWrapper.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
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))
|
||||||
|
{ }
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue