mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-18 13:57:43 +01:00
Compare commits
63 commits
68736cfeea
...
447571f42e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
447571f42e | ||
|
|
1822ef1808 | ||
|
|
cb441631e1 | ||
|
|
05f31265eb | ||
|
|
64d4f7061a | ||
|
|
e4eca842d3 | ||
|
|
a88247bdfc | ||
|
|
c79fa96505 | ||
|
|
596af24e95 | ||
|
|
ba0cf4c990 | ||
|
|
9a49a9588b | ||
|
|
750fa58147 | ||
|
|
19a3926051 | ||
|
|
4937a2f4bd | ||
|
|
78ed4a2b01 | ||
|
|
62b9c1f2a1 | ||
|
|
a2e923b051 | ||
|
|
de396e70f8 | ||
|
|
7a8f01f418 | ||
|
|
9d0879148c | ||
|
|
778c82fad2 | ||
|
|
7f2ed9adb6 | ||
|
|
53b94caeb7 | ||
|
|
d1dc81318a | ||
|
|
a48eead85e | ||
|
|
d1bed3ebc5 | ||
|
|
23e7c164d8 | ||
|
|
8a9b47c7a4 | ||
|
|
520e3ea028 | ||
|
|
dd70c5b8ee | ||
|
|
2b2f628096 | ||
|
|
6340afb692 | ||
|
|
928fbba489 | ||
|
|
7bc921f543 | ||
|
|
a37a13e0ba | ||
|
|
e0eff2fe74 | ||
|
|
7d76d27555 | ||
|
|
4e87b4b007 | ||
|
|
497e61f699 | ||
|
|
5cc327c5f9 | ||
|
|
af8b61f08a | ||
|
|
700aaa4a5d | ||
|
|
69caffeb97 | ||
|
|
a06c0e3ed2 | ||
|
|
880add5ab3 | ||
|
|
193d321103 | ||
|
|
6e8efabc3b | ||
|
|
68c02caf37 | ||
|
|
878080d660 | ||
|
|
986dfa04d0 | ||
|
|
3746c47a84 | ||
|
|
c4dd75bdda | ||
|
|
5905afdf10 | ||
|
|
62fdd2c60d | ||
|
|
ba159f8c5f | ||
|
|
6ade5b21cf | ||
|
|
2cf869872d | ||
|
|
bcf651b5c1 | ||
|
|
a55c8ca773 | ||
|
|
153870a053 | ||
|
|
c2fc04c3a8 | ||
|
|
8cac486249 | ||
|
|
4422622e1e |
63 changed files with 4074 additions and 771 deletions
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '9.0.200'
|
||||
dotnet-version: '10.0.100'
|
||||
- name: Define VERSION
|
||||
run: |
|
||||
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@
|
|||
"CompileDalamudCrashHandler",
|
||||
"CompileImGuiNatives",
|
||||
"CompileInjector",
|
||||
"CompileInjectorBoot",
|
||||
"Restore",
|
||||
"SetCILogging",
|
||||
"Test"
|
||||
|
|
@ -115,7 +114,6 @@
|
|||
"CompileDalamudCrashHandler",
|
||||
"CompileImGuiNatives",
|
||||
"CompileInjector",
|
||||
"CompileInjectorBoot",
|
||||
"Restore",
|
||||
"SetCILogging",
|
||||
"Test"
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{8874326B-E755-4D13-90B4-59AB263A3E6B}</ProjectGuid>
|
||||
<RootNamespace>Dalamud_Injector_Boot</RootNamespace>
|
||||
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<TargetName>Dalamud.Injector</TargetName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<OutDir>..\bin\$(Configuration)\</OutDir>
|
||||
<IntDir>obj\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp23</LanguageStandard>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<ProgramDatabaseFile>$(OutDir)$(TargetName).Boot.pdb</ProgramDatabaseFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>false</EnableCOMDATFolding>
|
||||
<OptimizeReferences>false</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ItemGroup>
|
||||
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
|
||||
<Link>nethost.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="dalamud.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="resources.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\Dalamud.Boot\logging.cpp" />
|
||||
<ClCompile Include="..\Dalamud.Boot\unicode.cpp" />
|
||||
<ClCompile Include="..\lib\CoreCLR\boot.cpp" />
|
||||
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\Dalamud.Boot\logging.h" />
|
||||
<ClInclude Include="..\Dalamud.Boot\unicode.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
||||
<Delete Files="$(OutDir)$(TargetName).exp" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{4faac519-3a73-4b2b-96e7-fb597f02c0be}</UniqueIdentifier>
|
||||
<Extensions>ico;rc</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="dalamud.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="resources.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\CoreCLR\boot.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Dalamud.Boot\logging.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Dalamud.Boot\unicode.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Dalamud.Boot\logging.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Dalamud.Boot\unicode.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#include <filesystem>
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
#include "..\Dalamud.Boot\logging.h"
|
||||
#include "..\lib\CoreCLR\CoreCLR.h"
|
||||
#include "..\lib\CoreCLR\boot.h"
|
||||
|
||||
int wmain(int argc, wchar_t** argv)
|
||||
{
|
||||
// Take care: don't redirect stderr/out here, we need to write our pid to stdout for XL to read
|
||||
//logging::start_file_logging("dalamud.injector.boot.log", false);
|
||||
logging::I("Dalamud Injector, (c) 2021 XIVLauncher Contributors");
|
||||
logging::I("Built at : " __DATE__ "@" __TIME__);
|
||||
|
||||
wchar_t _module_path[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, _module_path, sizeof _module_path / 2);
|
||||
std::filesystem::path fs_module_path(_module_path);
|
||||
|
||||
std::wstring runtimeconfig_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.runtimeconfig.json").c_str());
|
||||
std::wstring module_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.dll").c_str());
|
||||
|
||||
// =========================================================================== //
|
||||
|
||||
void* entrypoint_vfn;
|
||||
const auto result = InitializeClrAndGetEntryPoint(
|
||||
GetModuleHandleW(nullptr),
|
||||
false,
|
||||
runtimeconfig_path,
|
||||
module_path,
|
||||
L"Dalamud.Injector.EntryPoint, Dalamud.Injector",
|
||||
L"Main",
|
||||
L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector",
|
||||
&entrypoint_vfn);
|
||||
|
||||
if (FAILED(result))
|
||||
return result;
|
||||
|
||||
typedef int (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(int, wchar_t**);
|
||||
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
|
||||
|
||||
logging::I("Running Dalamud Injector...");
|
||||
const auto ret = entrypoint_fn(argc, argv);
|
||||
logging::I("Done!");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
#pragma once
|
||||
|
|
@ -1 +0,0 @@
|
|||
MAINICON ICON "dalamud.ico"
|
||||
|
|
@ -13,12 +13,13 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Output">
|
||||
<OutputType>Library</OutputType>
|
||||
<OutputType>Exe</OutputType>
|
||||
<OutputPath>..\bin\$(Configuration)\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<ApplicationIcon>dalamud.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Documentation">
|
||||
|
|
|
|||
|
|
@ -25,34 +25,20 @@ namespace Dalamud.Injector
|
|||
/// <summary>
|
||||
/// Entrypoint to the program.
|
||||
/// </summary>
|
||||
public sealed class EntryPoint
|
||||
public sealed class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate used during initialization of the CLR from Dalamud.Injector.Boot.
|
||||
/// </summary>
|
||||
/// <param name="argc">Count of arguments.</param>
|
||||
/// <param name="argvPtr">char** string arguments.</param>
|
||||
/// <returns>Return value (HRESULT).</returns>
|
||||
public delegate int MainDelegate(int argc, IntPtr argvPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Start the Dalamud injector.
|
||||
/// </summary>
|
||||
/// <param name="argc">Count of arguments.</param>
|
||||
/// <param name="argvPtr">byte** string arguments.</param>
|
||||
/// <param name="argsArray">Command line arguments.</param>
|
||||
/// <returns>Return value (HRESULT).</returns>
|
||||
public static int Main(int argc, IntPtr argvPtr)
|
||||
public static int Main(string[] argsArray)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<string> args = new(argc);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var argv = (IntPtr*)argvPtr;
|
||||
for (var i = 0; i < argc; i++)
|
||||
args.Add(Marshal.PtrToStringUni(argv[i]));
|
||||
}
|
||||
// API14 TODO: Refactor
|
||||
var args = argsArray.ToList();
|
||||
args.Insert(0, Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
Init(args);
|
||||
args.Remove("-v"); // Remove "verbose" flag
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
107
Dalamud.Test/Pipes/DalamudUriTests.cs
Normal file
107
Dalamud.Test/Pipes/DalamudUriTests.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Networking.Pipes;
|
||||
using Xunit;
|
||||
|
||||
namespace Dalamud.Test.Pipes
|
||||
{
|
||||
public class DalamudUriTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("https://www.google.com/", false)]
|
||||
[InlineData("dalamud://PluginInstaller/Dalamud.FindAnything", true)]
|
||||
public void ValidatesScheme(string uri, bool valid)
|
||||
{
|
||||
Action act = () => { _ = DalamudUri.FromUri(uri); };
|
||||
|
||||
var ex = Record.Exception(act);
|
||||
if (valid)
|
||||
{
|
||||
Assert.Null(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(ex);
|
||||
Assert.IsType<ArgumentOutOfRangeException>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("dalamud://PluginInstaller/Dalamud.FindAnything", "plugininstaller")]
|
||||
[InlineData("dalamud://Plugin/Dalamud.FindAnything/OpenWindow", "plugin")]
|
||||
[InlineData("dalamud://Test", "test")]
|
||||
public void ExtractsNamespace(string uri, string expectedNamespace)
|
||||
{
|
||||
var dalamudUri = DalamudUri.FromUri(uri);
|
||||
Assert.Equal(expectedNamespace, dalamudUri.Namespace);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("dalamud://foo/bar/baz/qux/?cow=moo", "/bar/baz/qux/")]
|
||||
[InlineData("dalamud://foo/bar/baz/qux?cow=moo", "/bar/baz/qux")]
|
||||
[InlineData("dalamud://foo/bar/baz", "/bar/baz")]
|
||||
[InlineData("dalamud://foo/bar", "/bar")]
|
||||
[InlineData("dalamud://foo/bar/", "/bar/")]
|
||||
[InlineData("dalamud://foo/", "/")]
|
||||
public void ExtractsPath(string uri, string expectedPath)
|
||||
{
|
||||
var dalamudUri = DalamudUri.FromUri(uri);
|
||||
Assert.Equal(expectedPath, dalamudUri.Path);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("dalamud://foo/bar/baz/qux/?cow=moo#frag", "/bar/baz/qux/?cow=moo#frag")]
|
||||
[InlineData("dalamud://foo/bar/baz/qux/?cow=moo", "/bar/baz/qux/?cow=moo")]
|
||||
[InlineData("dalamud://foo/bar/baz/qux?cow=moo", "/bar/baz/qux?cow=moo")]
|
||||
[InlineData("dalamud://foo/bar/baz", "/bar/baz")]
|
||||
[InlineData("dalamud://foo/bar?cow=moo", "/bar?cow=moo")]
|
||||
[InlineData("dalamud://foo/bar", "/bar")]
|
||||
[InlineData("dalamud://foo/bar/?cow=moo", "/bar/?cow=moo")]
|
||||
[InlineData("dalamud://foo/bar/", "/bar/")]
|
||||
[InlineData("dalamud://foo/?cow=moo#chicken", "/?cow=moo#chicken")]
|
||||
[InlineData("dalamud://foo/?cow=moo", "/?cow=moo")]
|
||||
[InlineData("dalamud://foo/", "/")]
|
||||
public void ExtractsData(string uri, string expectedData)
|
||||
{
|
||||
var dalamudUri = DalamudUri.FromUri(uri);
|
||||
|
||||
Assert.Equal(expectedData, dalamudUri.Data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("dalamud://foo/bar", 0)]
|
||||
[InlineData("dalamud://foo/bar?cow=moo", 1)]
|
||||
[InlineData("dalamud://foo/bar?cow=moo&wolf=awoo", 2)]
|
||||
[InlineData("dalamud://foo/bar?cow=moo&wolf=awoo&cat", 3)]
|
||||
public void ExtractsQueryParams(string uri, int queryCount)
|
||||
{
|
||||
var dalamudUri = DalamudUri.FromUri(uri);
|
||||
Assert.Equal(queryCount, dalamudUri.QueryParams.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("dalamud://foo/bar/baz/qux/meh/?foo=bar", 5, true)]
|
||||
[InlineData("dalamud://foo/bar/baz/qux/meh/", 5, true)]
|
||||
[InlineData("dalamud://foo/bar/baz/qux/meh", 5)]
|
||||
[InlineData("dalamud://foo/bar/baz/qux", 4)]
|
||||
[InlineData("dalamud://foo/bar/baz", 3)]
|
||||
[InlineData("dalamud://foo/bar/", 2)]
|
||||
[InlineData("dalamud://foo/bar", 2)]
|
||||
public void ExtractsSegments(string uri, int segmentCount, bool finalSegmentEndsWithSlash = false)
|
||||
{
|
||||
var dalamudUri = DalamudUri.FromUri(uri);
|
||||
var segments = dalamudUri.Segments;
|
||||
|
||||
// First segment must always be `/`
|
||||
Assert.Equal("/", segments[0]);
|
||||
|
||||
Assert.Equal(segmentCount, segments.Length);
|
||||
|
||||
if (finalSegmentEndsWithSlash)
|
||||
{
|
||||
Assert.EndsWith("/", segments.Last());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Dalamud.sln
12
Dalamud.sln
|
|
@ -1,4 +1,4 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32319.34
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
|
|
@ -27,8 +27,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Boot", "Dalamud.Boo
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Injector", "Dalamud.Injector\Dalamud.Injector.csproj", "{5B832F73-5F54-4ADC-870F-D0095EF72C9A}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Injector.Boot", "Dalamud.Injector.Boot\Dalamud.Injector.Boot.vcxproj", "{8874326B-E755-4D13-90B4-59AB263A3E6B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Test\Dalamud.Test.csproj", "{C8004563-1806-4329-844F-0EF6274291FC}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
|
||||
|
|
@ -49,8 +47,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator", "lib\FFX
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator.Runtime", "lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj", "{A6AA1C3F-9470-4922-9D3F-D4549657AB22}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Injector", "Injector", "{19775C83-7117-4A5F-AA00-18889F46A490}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{8F079208-C227-4D96-9427-2BEBE0003944}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimgui", "external\cimgui\cimgui.vcxproj", "{8430077C-F736-4246-A052-8EA1CECE844E}"
|
||||
|
|
@ -103,10 +99,6 @@ Global
|
|||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.Build.0 = Release|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.Build.0 = Release|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
|
|
@ -188,8 +180,6 @@ Global
|
|||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A} = {19775C83-7117-4A5F-AA00-18889F46A490}
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B} = {19775C83-7117-4A5F-AA00-18889F46A490}
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0} = {8F079208-C227-4D96-9427-2BEBE0003944}
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
|
||||
{E0D51896-604F-4B40-8CFE-51941607B3A1} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@
|
|||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
<PackageReference Include="sqlite-net-pcl" />
|
||||
<PackageReference Include="StreamJsonRpc" />
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ internal partial class ChatHandlers : IServiceType
|
|||
}
|
||||
|
||||
// For injections while logged in
|
||||
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
||||
if (clientState.IsLoggedIn && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
||||
this.PrintWelcomeMessage();
|
||||
|
||||
#if !DEBUG && false
|
||||
|
|
|
|||
|
|
@ -63,47 +63,37 @@ public interface IAetheryteEntry
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class representing an aetheryte entry available to the game.
|
||||
/// This struct represents an aetheryte entry available to the game.
|
||||
/// </summary>
|
||||
internal sealed class AetheryteEntry : IAetheryteEntry
|
||||
/// <param name="data">Data read from the Aetheryte List.</param>
|
||||
internal readonly struct AetheryteEntry(TeleportInfo data) : IAetheryteEntry
|
||||
{
|
||||
private readonly TeleportInfo data;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AetheryteEntry"/> class.
|
||||
/// </summary>
|
||||
/// <param name="data">Data read from the Aetheryte List.</param>
|
||||
internal AetheryteEntry(TeleportInfo data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public uint AetheryteId => data.AetheryteId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint AetheryteId => this.data.AetheryteId;
|
||||
public uint TerritoryId => data.TerritoryId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint TerritoryId => this.data.TerritoryId;
|
||||
public byte SubIndex => data.SubIndex;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte SubIndex => this.data.SubIndex;
|
||||
public byte Ward => data.Ward;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte Ward => this.data.Ward;
|
||||
public byte Plot => data.Plot;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte Plot => this.data.Plot;
|
||||
public uint GilCost => data.GilCost;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint GilCost => this.data.GilCost;
|
||||
public bool IsFavourite => data.IsFavourite;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsFavourite => this.data.IsFavourite;
|
||||
public bool IsSharedHouse => data.IsSharedHouse;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSharedHouse => this.data.IsSharedHouse;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsApartment => this.data.IsApartment;
|
||||
public bool IsApartment => data.IsApartment;
|
||||
|
||||
/// <inheritdoc />
|
||||
public RowRef<Lumina.Excel.Sheets.Aetheryte> AetheryteData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Aetheryte>(this.AetheryteId);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
|
@ -22,7 +23,7 @@ namespace Dalamud.Game.ClientState.Aetherytes;
|
|||
internal sealed unsafe partial class AetheryteList : IServiceType, IAetheryteList
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||
|
||||
private readonly Telepo* telepoInstance = Telepo.Instance();
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ internal sealed unsafe partial class AetheryteList : IServiceType, IAetheryteLis
|
|||
{
|
||||
get
|
||||
{
|
||||
if (this.clientState.LocalPlayer == null)
|
||||
if (this.objectTable.LocalPlayer == null)
|
||||
return 0;
|
||||
|
||||
this.Update();
|
||||
|
|
@ -59,7 +60,7 @@ internal sealed unsafe partial class AetheryteList : IServiceType, IAetheryteLis
|
|||
return null;
|
||||
}
|
||||
|
||||
if (this.clientState.LocalPlayer == null)
|
||||
if (this.objectTable.LocalPlayer == null)
|
||||
return null;
|
||||
|
||||
return new AetheryteEntry(this.telepoInstance->TeleportList[index]);
|
||||
|
|
@ -69,7 +70,7 @@ internal sealed unsafe partial class AetheryteList : IServiceType, IAetheryteLis
|
|||
private void Update()
|
||||
{
|
||||
// this is very very important as otherwise it crashes
|
||||
if (this.clientState.LocalPlayer == null)
|
||||
if (this.objectTable.LocalPlayer == null)
|
||||
return;
|
||||
|
||||
this.telepoInstance->UpdateAetheryteList();
|
||||
|
|
@ -87,10 +88,7 @@ internal sealed partial class AetheryteList
|
|||
/// <inheritdoc/>
|
||||
public IEnumerator<IAetheryteEntry> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
yield return this[i];
|
||||
}
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -98,4 +96,30 @@ internal sealed partial class AetheryteList
|
|||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
private struct Enumerator(AetheryteList aetheryteList) : IEnumerator<IAetheryteEntry>
|
||||
{
|
||||
private int index = 0;
|
||||
|
||||
public IAetheryteEntry Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (this.index == aetheryteList.Length) return false;
|
||||
this.Current = aetheryteList[this.index];
|
||||
this.index++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,16 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Game.Player;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using CSBuddy = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy;
|
||||
using CSUIState = FFXIVClientStructs.FFXIV.Client.Game.UI.UIState;
|
||||
|
||||
using CSBuddy = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy;
|
||||
using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Buddy;
|
||||
|
||||
|
|
@ -21,10 +26,10 @@ namespace Dalamud.Game.ClientState.Buddy;
|
|||
#pragma warning restore SA1015
|
||||
internal sealed partial class BuddyList : IServiceType, IBuddyList
|
||||
{
|
||||
private const uint InvalidObjectID = 0xE0000000;
|
||||
private const uint InvalidEntityId = 0xE0000000;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||
private readonly PlayerState playerState = Service<PlayerState>.Get();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private BuddyList()
|
||||
|
|
@ -69,7 +74,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
|
|||
}
|
||||
}
|
||||
|
||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => &UIState.Instance()->Buddy;
|
||||
private unsafe CSBuddy* BuddyListStruct => &UIState.Instance()->Buddy;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IBuddyMember? this[int index]
|
||||
|
|
@ -82,37 +87,37 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe IntPtr GetCompanionBuddyMemberAddress()
|
||||
public unsafe nint GetCompanionBuddyMemberAddress()
|
||||
{
|
||||
return (IntPtr)this.BuddyListStruct->CompanionInfo.Companion;
|
||||
return (nint)this.BuddyListStruct->CompanionInfo.Companion;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe IntPtr GetPetBuddyMemberAddress()
|
||||
public unsafe nint GetPetBuddyMemberAddress()
|
||||
{
|
||||
return (IntPtr)this.BuddyListStruct->PetInfo.Pet;
|
||||
return (nint)this.BuddyListStruct->PetInfo.Pet;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
|
||||
public unsafe nint GetBattleBuddyMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= 3)
|
||||
return IntPtr.Zero;
|
||||
return 0;
|
||||
|
||||
return (IntPtr)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]);
|
||||
return (nint)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IBuddyMember? CreateBuddyMemberReference(IntPtr address)
|
||||
public unsafe IBuddyMember? CreateBuddyMemberReference(nint address)
|
||||
{
|
||||
if (address == 0)
|
||||
return null;
|
||||
|
||||
if (this.clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
var buddy = new BuddyMember(address);
|
||||
if (buddy.ObjectId == InvalidObjectID)
|
||||
var buddy = new BuddyMember((CSBuddyMember*)address);
|
||||
if (buddy.EntityId == InvalidEntityId)
|
||||
return null;
|
||||
|
||||
return buddy;
|
||||
|
|
@ -130,12 +135,35 @@ internal sealed partial class BuddyList
|
|||
/// <inheritdoc/>
|
||||
public IEnumerator<IBuddyMember> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
yield return this[i];
|
||||
}
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private struct Enumerator(BuddyList buddyList) : IEnumerator<IBuddyMember>
|
||||
{
|
||||
private int index = 0;
|
||||
|
||||
public IBuddyMember Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (this.index == buddyList.Length) return false;
|
||||
this.Current = buddyList[this.index];
|
||||
this.index++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Buddy;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||
/// </summary>
|
||||
public interface IBuddyMember
|
||||
public interface IBuddyMember : IEquatable<IBuddyMember>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy in memory.
|
||||
/// </summary>
|
||||
IntPtr Address { get; }
|
||||
nint Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object ID of this buddy.
|
||||
|
|
@ -67,42 +71,34 @@ public interface IBuddyMember
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||
/// This struct represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||
/// </summary>
|
||||
internal unsafe class BuddyMember : IBuddyMember
|
||||
/// <param name="ptr">A pointer to the BuddyMember.</param>
|
||||
internal readonly unsafe struct BuddyMember(CSBuddyMember* ptr) : IBuddyMember
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Buddy address.</param>
|
||||
internal BuddyMember(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public nint Address => (nint)ptr;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IntPtr Address { get; }
|
||||
public uint ObjectId => this.EntityId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint ObjectId => this.Struct->EntityId;
|
||||
public uint EntityId => ptr->EntityId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint EntityId => this.Struct->EntityId;
|
||||
public IGameObject? GameObject => this.objectTable.SearchById(this.EntityId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IGameObject? GameObject => this.objectTable.SearchById(this.ObjectId);
|
||||
public uint CurrentHP => ptr->CurrentHealth;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint CurrentHP => this.Struct->CurrentHealth;
|
||||
public uint MaxHP => ptr->MaxHealth;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint MaxHP => this.Struct->MaxHealth;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint DataID => this.Struct->DataId;
|
||||
public uint DataID => ptr->DataId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public RowRef<Lumina.Excel.Sheets.Mount> MountData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Mount>(this.DataID);
|
||||
|
|
@ -113,5 +109,25 @@ internal unsafe class BuddyMember : IBuddyMember
|
|||
/// <inheritdoc />
|
||||
public RowRef<Lumina.Excel.Sheets.DawnGrowMember> TrustData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.DawnGrowMember>(this.DataID);
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
|
||||
public static bool operator ==(BuddyMember x, BuddyMember y) => x.Equals(y);
|
||||
|
||||
public static bool operator !=(BuddyMember x, BuddyMember y) => !(x == y);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IBuddyMember? other)
|
||||
{
|
||||
return this.EntityId == other.EntityId;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is BuddyMember fate && this.Equals(fate);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.EntityId.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Dalamud.Game.ClientState.Objects;
|
|||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Network.Internal;
|
||||
using Dalamud.Game.Player;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
|
|
@ -15,7 +16,6 @@ using Dalamud.Utility;
|
|||
|
||||
using FFXIVClientStructs.FFXIV.Application.Network;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.Network;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
|
@ -23,6 +23,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||
using Lumina.Excel.Sheets;
|
||||
|
||||
using Action = System.Action;
|
||||
using CSUIState = FFXIVClientStructs.FFXIV.Client.Game.UI.UIState;
|
||||
|
||||
namespace Dalamud.Game.ClientState;
|
||||
|
||||
|
|
@ -46,6 +47,12 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly NetworkHandlers networkHandlers = Service<NetworkHandlers>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly PlayerState playerState = Service<PlayerState>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||
|
||||
private Hook<LogoutCallbackInterface.Delegates.OnLogout> onLogoutHook;
|
||||
private bool initialized;
|
||||
private ushort territoryTypeId;
|
||||
|
|
@ -184,10 +191,10 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPlayerCharacter? LocalPlayer => Service<ObjectTable>.GetNullable()?[0] as IPlayerCharacter;
|
||||
public IPlayerCharacter? LocalPlayer => this.objectTable.LocalPlayer;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe ulong LocalContentId => PlayerState.Instance()->ContentId;
|
||||
public unsafe ulong LocalContentId => this.playerState.ContentId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe bool IsLoggedIn
|
||||
|
|
@ -241,7 +248,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
public bool IsClientIdle(out ConditionFlag blockingFlag)
|
||||
{
|
||||
blockingFlag = 0;
|
||||
if (this.LocalPlayer is null) return true;
|
||||
if (this.objectTable.LocalPlayer is null) return true;
|
||||
|
||||
var condition = Service<Conditions.Condition>.GetNullable();
|
||||
|
||||
|
|
@ -280,7 +287,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
|
||||
this.TerritoryType = (ushort)GameMain.Instance()->CurrentTerritoryTypeId;
|
||||
this.MapId = AgentMap.Instance()->CurrentMapId;
|
||||
this.Instance = UIState.Instance()->PublicInstance.InstanceId;
|
||||
this.Instance = CSUIState.Instance()->PublicInstance.InstanceId;
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
|
|
@ -369,7 +376,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
if (condition == null || gameGui == null || data == null)
|
||||
return;
|
||||
|
||||
if (condition.Any() && this.lastConditionNone && this.LocalPlayer != null)
|
||||
if (condition.Any() && this.lastConditionNone && this.objectTable.LocalPlayer != null)
|
||||
{
|
||||
Log.Debug("Is login");
|
||||
this.lastConditionNone = false;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Player;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Memory;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Fates;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing an fate entry that can be seen in the current area.
|
||||
/// Interface representing a fate entry that can be seen in the current area.
|
||||
/// </summary>
|
||||
public interface IFate : IEquatable<IFate>
|
||||
{
|
||||
|
|
@ -111,133 +115,96 @@ public interface IFate : IEquatable<IFate>
|
|||
/// <summary>
|
||||
/// Gets the address of this Fate in memory.
|
||||
/// </summary>
|
||||
IntPtr Address { get; }
|
||||
nint Address { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents an FFXIV Fate.
|
||||
/// This struct represents a Fate.
|
||||
/// </summary>
|
||||
internal unsafe partial class Fate
|
||||
/// <param name="ptr">A pointer to the FateContext.</param>
|
||||
internal readonly unsafe struct Fate(CSFateContext* ptr) : IFate
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Fate"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this fate in memory.</param>
|
||||
internal Fate(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IntPtr Address { get; }
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address;
|
||||
|
||||
public static bool operator ==(Fate fate1, Fate fate2)
|
||||
{
|
||||
if (fate1 is null || fate2 is null)
|
||||
return Equals(fate1, fate2);
|
||||
|
||||
return fate1.Equals(fate2);
|
||||
}
|
||||
|
||||
public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this Fate is still valid in memory.
|
||||
/// </summary>
|
||||
/// <param name="fate">The fate to check.</param>
|
||||
/// <returns>True or false.</returns>
|
||||
public static bool IsValid(Fate fate)
|
||||
{
|
||||
var clientState = Service<ClientState>.GetNullable();
|
||||
|
||||
if (fate == null || clientState == null)
|
||||
return false;
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this actor is still valid in memory.
|
||||
/// </summary>
|
||||
/// <returns>True or false.</returns>
|
||||
public bool IsValid() => IsValid(this);
|
||||
public nint Address => (nint)ptr;
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool IEquatable<IFate>.Equals(IFate other) => this.FateId == other?.FateId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj) => ((IEquatable<IFate>)this).Equals(obj as IFate);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => this.FateId.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents an FFXIV Fate.
|
||||
/// </summary>
|
||||
internal unsafe partial class Fate : IFate
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ushort FateId => this.Struct->FateId;
|
||||
public ushort FateId => ptr->FateId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.Fate> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Fate>(this.FateId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int StartTimeEpoch => this.Struct->StartTimeEpoch;
|
||||
public int StartTimeEpoch => ptr->StartTimeEpoch;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public short Duration => this.Struct->Duration;
|
||||
public short Duration => ptr->Duration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
|
||||
public SeString Name => MemoryHelper.ReadSeString(&ptr->Name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Description => MemoryHelper.ReadSeString(&this.Struct->Description);
|
||||
public SeString Description => MemoryHelper.ReadSeString(&ptr->Description);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Objective => MemoryHelper.ReadSeString(&this.Struct->Objective);
|
||||
public SeString Objective => MemoryHelper.ReadSeString(&ptr->Objective);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FateState State => (FateState)this.Struct->State;
|
||||
public FateState State => (FateState)ptr->State;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte HandInCount => this.Struct->HandInCount;
|
||||
public byte HandInCount => ptr->HandInCount;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Progress => this.Struct->Progress;
|
||||
public byte Progress => ptr->Progress;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool HasBonus => this.Struct->IsBonus;
|
||||
public bool HasBonus => ptr->IsBonus;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint IconId => this.Struct->IconId;
|
||||
public uint IconId => ptr->IconId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Level => this.Struct->Level;
|
||||
public byte Level => ptr->Level;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte MaxLevel => this.Struct->MaxLevel;
|
||||
public byte MaxLevel => ptr->MaxLevel;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Vector3 Position => this.Struct->Location;
|
||||
public Vector3 Position => ptr->Location;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Radius => this.Struct->Radius;
|
||||
public float Radius => ptr->Radius;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint MapIconId => this.Struct->MapIconId;
|
||||
public uint MapIconId => ptr->MapIconId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the territory this <see cref="Fate"/> is located in.
|
||||
/// </summary>
|
||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->MapMarkers[0].MapMarkerData.TerritoryTypeId);
|
||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(ptr->MapMarkers[0].MapMarkerData.TerritoryTypeId);
|
||||
|
||||
public static bool operator ==(Fate x, Fate y) => x.Equals(y);
|
||||
|
||||
public static bool operator !=(Fate x, Fate y) => !(x == y);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IFate? other)
|
||||
{
|
||||
return this.FateId == other.FateId;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is Fate fate && this.Equals(fate);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.FateId.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.Player;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext;
|
||||
using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Fates;
|
||||
|
|
@ -25,7 +27,7 @@ internal sealed partial class FateTable : IServiceType, IFateTable
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe IntPtr Address => (nint)CSFateManager.Instance();
|
||||
public unsafe nint Address => (nint)CSFateManager.Instance();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe int Length
|
||||
|
|
@ -60,42 +62,37 @@ internal sealed partial class FateTable : IServiceType, IFateTable
|
|||
/// <inheritdoc/>
|
||||
public bool IsValid(IFate fate)
|
||||
{
|
||||
var clientState = Service<ClientState>.GetNullable();
|
||||
|
||||
if (fate == null || clientState == null)
|
||||
if (fate == null)
|
||||
return false;
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
var playerState = Service<PlayerState>.Get();
|
||||
return playerState.IsLoaded == true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe IntPtr GetFateAddress(int index)
|
||||
public unsafe nint GetFateAddress(int index)
|
||||
{
|
||||
if (index >= this.Length)
|
||||
return IntPtr.Zero;
|
||||
return 0;
|
||||
|
||||
var fateManager = CSFateManager.Instance();
|
||||
if (fateManager == null)
|
||||
return IntPtr.Zero;
|
||||
return 0;
|
||||
|
||||
return (IntPtr)fateManager->Fates[index].Value;
|
||||
return (nint)fateManager->Fates[index].Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFate? CreateFateReference(IntPtr offset)
|
||||
public unsafe IFate? CreateFateReference(IntPtr address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
if (address == 0)
|
||||
return null;
|
||||
|
||||
var clientState = Service<ClientState>.Get();
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (offset == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new Fate(offset);
|
||||
return new Fate((CSFateContext*)address);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,12 +107,35 @@ internal sealed partial class FateTable
|
|||
/// <inheritdoc/>
|
||||
public IEnumerator<IFate> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
yield return this[i];
|
||||
}
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private struct Enumerator(FateTable fateTable) : IEnumerator<IFate>
|
||||
{
|
||||
private int index = 0;
|
||||
|
||||
public IFate Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (this.index == fateTable.Length) return false;
|
||||
this.Current = fateTable[this.index];
|
||||
this.index++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
|
|||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.Player;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
|
@ -12,8 +13,6 @@ using Dalamud.Utility;
|
|||
|
||||
using FFXIVClientStructs.Interop;
|
||||
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||
using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager;
|
||||
|
||||
|
|
@ -31,25 +30,20 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
{
|
||||
private static int objectTableLength;
|
||||
|
||||
private readonly ClientState clientState;
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly PlayerState playerState = Service<PlayerState>.Get();
|
||||
|
||||
private readonly CachedEntry[] cachedObjectTable;
|
||||
|
||||
private readonly Enumerator?[] frameworkThreadEnumerators = new Enumerator?[4];
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private unsafe ObjectTable(ClientState clientState)
|
||||
private unsafe ObjectTable()
|
||||
{
|
||||
this.clientState = clientState;
|
||||
|
||||
var nativeObjectTable = CSGameObjectManager.Instance()->Objects.IndexSorted;
|
||||
objectTableLength = nativeObjectTable.Length;
|
||||
|
||||
this.cachedObjectTable = new CachedEntry[objectTableLength];
|
||||
for (var i = 0; i < this.cachedObjectTable.Length; i++)
|
||||
this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i));
|
||||
|
||||
for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++)
|
||||
this.frameworkThreadEnumerators[i] = new(this, i);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -66,6 +60,9 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
/// <inheritdoc/>
|
||||
public int Length => objectTableLength;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPlayerCharacter? LocalPlayer => this[0] as IPlayerCharacter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IBattleChara> PlayerObjects => this.GetPlayerObjects();
|
||||
|
||||
|
|
@ -142,10 +139,10 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
if (this.clientState.LocalContentId == 0)
|
||||
if (address == nint.Zero)
|
||||
return null;
|
||||
|
||||
if (address == nint.Zero)
|
||||
if (!this.playerState.IsLoaded)
|
||||
return null;
|
||||
|
||||
var obj = (CSGameObject*)address;
|
||||
|
|
@ -239,30 +236,14 @@ internal sealed partial class ObjectTable
|
|||
public IEnumerator<IGameObject> GetEnumerator()
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
// If we're on the framework thread, see if there's an already allocated enumerator available for use.
|
||||
foreach (ref var x in this.frameworkThreadEnumerators.AsSpan())
|
||||
{
|
||||
if (x is not null)
|
||||
{
|
||||
var t = x;
|
||||
x = null;
|
||||
t.Reset();
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
// No reusable enumerator is available; allocate a new temporary one.
|
||||
return new Enumerator(this, -1);
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private sealed class Enumerator(ObjectTable owner, int slotId) : IEnumerator<IGameObject>, IResettable
|
||||
private struct Enumerator(ObjectTable owner) : IEnumerator<IGameObject>
|
||||
{
|
||||
private ObjectTable? owner = owner;
|
||||
|
||||
private int index = -1;
|
||||
|
||||
public IGameObject Current { get; private set; } = null!;
|
||||
|
|
@ -274,7 +255,7 @@ internal sealed partial class ObjectTable
|
|||
if (this.index == objectTableLength)
|
||||
return false;
|
||||
|
||||
var cache = this.owner!.cachedObjectTable.AsSpan();
|
||||
var cache = owner.cachedObjectTable.AsSpan();
|
||||
for (this.index++; this.index < objectTableLength; this.index++)
|
||||
{
|
||||
if (cache[this.index].Update() is { } ao)
|
||||
|
|
@ -291,17 +272,6 @@ internal sealed partial class ObjectTable
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.owner is not { } o)
|
||||
return;
|
||||
|
||||
if (slotId != -1)
|
||||
o.frameworkThreadEnumerators[slotId] = this;
|
||||
}
|
||||
|
||||
public bool TryReset()
|
||||
{
|
||||
this.Reset();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.Player;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Memory;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
|
|
@ -170,15 +169,11 @@ internal partial class GameObject
|
|||
/// <returns>True or false.</returns>
|
||||
public static bool IsValid(IGameObject? actor)
|
||||
{
|
||||
var clientState = Service<ClientState>.GetNullable();
|
||||
|
||||
if (actor is null || clientState == null)
|
||||
if (actor == null)
|
||||
return false;
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
var playerState = Service<PlayerState>.Get();
|
||||
return playerState.IsLoaded == true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ using System.Collections.Generic;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Player;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager;
|
||||
using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Party;
|
||||
|
||||
|
|
@ -25,7 +27,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
|||
private const int AllianceLength = 20;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||
private readonly PlayerState playerState = Service<PlayerState>.Get();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private PartyList()
|
||||
|
|
@ -42,20 +44,20 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
|||
public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance();
|
||||
public unsafe nint GroupManagerAddress => (nint)CSGroupManager.Instance();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
|
||||
public nint GroupListAddress => (nint)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr AllianceListAddress => (IntPtr)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
|
||||
public nint AllianceListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long PartyId => this.GroupManagerStruct->MainGroup.PartyId;
|
||||
|
||||
private static int PartyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember>();
|
||||
private static int PartyMemberSize { get; } = Marshal.SizeOf<CSPartyMember>();
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress;
|
||||
private CSGroupManager* GroupManagerStruct => (CSGroupManager*)this.GroupManagerAddress;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPartyMember? this[int index]
|
||||
|
|
@ -80,45 +82,45 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr GetPartyMemberAddress(int index)
|
||||
public nint GetPartyMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= GroupLength)
|
||||
return IntPtr.Zero;
|
||||
return 0;
|
||||
|
||||
return this.GroupListAddress + (index * PartyMemberSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPartyMember? CreatePartyMemberReference(IntPtr address)
|
||||
public IPartyMember? CreatePartyMemberReference(nint address)
|
||||
{
|
||||
if (this.clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
if (address == 0)
|
||||
return null;
|
||||
|
||||
return new PartyMember(address);
|
||||
return new PartyMember((CSPartyMember*)address);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr GetAllianceMemberAddress(int index)
|
||||
public nint GetAllianceMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= AllianceLength)
|
||||
return IntPtr.Zero;
|
||||
return 0;
|
||||
|
||||
return this.AllianceListAddress + (index * PartyMemberSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPartyMember? CreateAllianceMemberReference(IntPtr address)
|
||||
public IPartyMember? CreateAllianceMemberReference(nint address)
|
||||
{
|
||||
if (this.clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
if (address == 0)
|
||||
return null;
|
||||
|
||||
return new PartyMember(address);
|
||||
return new PartyMember((CSPartyMember*)address);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,18 +135,44 @@ internal sealed partial class PartyList
|
|||
/// <inheritdoc/>
|
||||
public IEnumerator<IPartyMember> GetEnumerator()
|
||||
{
|
||||
// Normally using Length results in a recursion crash, however we know the party size via ptr.
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
var member = this[i];
|
||||
|
||||
if (member == null)
|
||||
break;
|
||||
|
||||
yield return member;
|
||||
}
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private struct Enumerator(PartyList partyList) : IEnumerator<IPartyMember>
|
||||
{
|
||||
private int index = 0;
|
||||
|
||||
public IPartyMember Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (this.index == partyList.Length) return false;
|
||||
|
||||
for (; this.index < partyList.Length; this.index++)
|
||||
{
|
||||
var partyMember = partyList[this.index];
|
||||
if (partyMember != null)
|
||||
{
|
||||
this.Current = partyMember;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Statuses;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Memory;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Party;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing a party member.
|
||||
/// </summary>
|
||||
public interface IPartyMember
|
||||
public interface IPartyMember : IEquatable<IPartyMember>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of this party member in memory.
|
||||
/// </summary>
|
||||
IntPtr Address { get; }
|
||||
nint Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of buffs or debuffs applied to this party member.
|
||||
|
|
@ -108,69 +109,81 @@ public interface IPartyMember
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a party member in the group manager.
|
||||
/// This struct represents a party member in the group manager.
|
||||
/// </summary>
|
||||
internal unsafe class PartyMember : IPartyMember
|
||||
/// <param name="ptr">A pointer to the PartyMember.</param>
|
||||
internal unsafe readonly struct PartyMember(CSPartyMember* ptr) : IPartyMember
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyMember"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the party member.</param>
|
||||
internal PartyMember(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public nint Address => (nint)ptr;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Address { get; }
|
||||
public StatusList Statuses => new(&ptr->StatusManager);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public StatusList Statuses => new(&this.Struct->StatusManager);
|
||||
public Vector3 Position => ptr->Position;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Vector3 Position => this.Struct->Position;
|
||||
public long ContentId => (long)ptr->ContentId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long ContentId => (long)this.Struct->ContentId;
|
||||
public uint ObjectId => ptr->EntityId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint ObjectId => this.Struct->EntityId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint EntityId => this.Struct->EntityId;
|
||||
public uint EntityId => ptr->EntityId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.EntityId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint CurrentHP => this.Struct->CurrentHP;
|
||||
public uint CurrentHP => ptr->CurrentHP;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint MaxHP => this.Struct->MaxHP;
|
||||
public uint MaxHP => ptr->MaxHP;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ushort CurrentMP => this.Struct->CurrentMP;
|
||||
public ushort CurrentMP => ptr->CurrentMP;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ushort MaxMP => this.Struct->MaxMP;
|
||||
public ushort MaxMP => ptr->MaxMP;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->TerritoryType);
|
||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(ptr->TerritoryType);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(this.Struct->HomeWorld);
|
||||
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(ptr->HomeWorld);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Name => SeString.Parse(this.Struct->Name);
|
||||
public SeString Name => SeString.Parse(ptr->Name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Sex => this.Struct->Sex;
|
||||
public byte Sex => ptr->Sex;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(this.Struct->ClassJob);
|
||||
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(ptr->ClassJob);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Level => this.Struct->Level;
|
||||
public byte Level => ptr->Level;
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address;
|
||||
public static bool operator ==(PartyMember x, PartyMember y) => x.Equals(y);
|
||||
|
||||
public static bool operator !=(PartyMember x, PartyMember y) => !(x == y);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IPartyMember? other)
|
||||
{
|
||||
return this.EntityId == other.EntityId;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is PartyMember fate && this.Equals(fate);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.EntityId.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,49 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Statuses;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a status effect an actor is afflicted by.
|
||||
/// Interface representing a status.
|
||||
/// </summary>
|
||||
public unsafe class Status
|
||||
public interface IStatus : IEquatable<IStatus>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Status"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Status address.</param>
|
||||
internal Status(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the status in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
nint Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status ID of this status.
|
||||
/// </summary>
|
||||
public uint StatusId => this.Struct->StatusId;
|
||||
uint StatusId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GameData associated with this status.
|
||||
/// </summary>
|
||||
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(this.Struct->StatusId);
|
||||
RowRef<Lumina.Excel.Sheets.Status> GameData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter value of the status.
|
||||
/// </summary>
|
||||
public ushort Param => this.Struct->Param;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stack count of this status.
|
||||
/// Only valid if this is a non-food status.
|
||||
/// </summary>
|
||||
[Obsolete($"Replaced with {nameof(Param)}", true)]
|
||||
public byte StackCount => (byte)this.Struct->Param;
|
||||
ushort Param { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining of this status.
|
||||
/// </summary>
|
||||
public float RemainingTime => this.Struct->RemainingTime;
|
||||
float RemainingTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source ID of this status.
|
||||
/// </summary>
|
||||
public uint SourceId => this.Struct->SourceObject.ObjectId;
|
||||
uint SourceId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source actor associated with this status.
|
||||
|
|
@ -63,7 +51,55 @@ public unsafe class Status
|
|||
/// <remarks>
|
||||
/// This iterates the actor table, it should be used with care.
|
||||
/// </remarks>
|
||||
IGameObject? SourceObject { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This struct represents a status effect an actor is afflicted by.
|
||||
/// </summary>
|
||||
/// <param name="ptr">A pointer to the Status.</param>
|
||||
internal unsafe readonly struct Status(CSStatus* ptr) : IStatus
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public nint Address => (nint)ptr;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint StatusId => ptr->StatusId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(ptr->StatusId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ushort Param => ptr->Param;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float RemainingTime => ptr->RemainingTime;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint SourceId => ptr->SourceObject.ObjectId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId);
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
|
||||
public static bool operator ==(Status x, Status y) => x.Equals(y);
|
||||
|
||||
public static bool operator !=(Status x, Status y) => !(x == y);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IStatus? other)
|
||||
{
|
||||
return this.StatusId == other.StatusId && this.SourceId == other.SourceId && this.Param == other.Param && this.RemainingTime == other.RemainingTime;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is Status fate && this.Equals(fate);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(this.StatusId, this.SourceId, this.Param, this.RemainingTime);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Statuses;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -14,7 +16,7 @@ public sealed unsafe partial class StatusList
|
|||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the status list.</param>
|
||||
internal StatusList(IntPtr address)
|
||||
internal StatusList(nint address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
|
@ -24,14 +26,14 @@ public sealed unsafe partial class StatusList
|
|||
/// </summary>
|
||||
/// <param name="pointer">Pointer to the status list.</param>
|
||||
internal unsafe StatusList(void* pointer)
|
||||
: this((IntPtr)pointer)
|
||||
: this((nint)pointer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the status list in memory.
|
||||
/// </summary>
|
||||
public IntPtr Address { get; }
|
||||
public nint Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of status effect slots the actor has.
|
||||
|
|
@ -47,7 +49,7 @@ public sealed unsafe partial class StatusList
|
|||
/// </summary>
|
||||
/// <param name="index">Status Index.</param>
|
||||
/// <returns>The status at the specified index.</returns>
|
||||
public Status? this[int index]
|
||||
public IStatus? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -64,8 +66,11 @@ public sealed unsafe partial class StatusList
|
|||
/// </summary>
|
||||
/// <param name="address">The address of the status list in memory.</param>
|
||||
/// <returns>The status object containing the requested data.</returns>
|
||||
public static StatusList? CreateStatusListReference(IntPtr address)
|
||||
public static StatusList? CreateStatusListReference(nint address)
|
||||
{
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
// The use case for CreateStatusListReference and CreateStatusReference to be static is so
|
||||
// fake status lists can be generated. Since they aren't exposed as services, it's either
|
||||
// here or somewhere else.
|
||||
|
|
@ -74,7 +79,7 @@ public sealed unsafe partial class StatusList
|
|||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
if (address == 0)
|
||||
return null;
|
||||
|
||||
return new StatusList(address);
|
||||
|
|
@ -85,17 +90,15 @@ public sealed unsafe partial class StatusList
|
|||
/// </summary>
|
||||
/// <param name="address">The address of the status effect in memory.</param>
|
||||
/// <returns>The status object containing the requested data.</returns>
|
||||
public static Status? CreateStatusReference(IntPtr address)
|
||||
public static IStatus? CreateStatusReference(nint address)
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new Status(address);
|
||||
if (address == 0)
|
||||
return null;
|
||||
|
||||
return new Status((CSStatus*)address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -103,22 +106,22 @@ public sealed unsafe partial class StatusList
|
|||
/// </summary>
|
||||
/// <param name="index">The index of the status.</param>
|
||||
/// <returns>The memory address of the status.</returns>
|
||||
public IntPtr GetStatusAddress(int index)
|
||||
public nint GetStatusAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= this.Length)
|
||||
return IntPtr.Zero;
|
||||
return 0;
|
||||
|
||||
return (IntPtr)Unsafe.AsPointer(ref this.Struct->Status[index]);
|
||||
return (nint)Unsafe.AsPointer(ref this.Struct->Status[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the status effects an actor is afflicted by.
|
||||
/// </summary>
|
||||
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
|
||||
public sealed partial class StatusList : IReadOnlyCollection<IStatus>, ICollection
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<Status>.Count => this.Length;
|
||||
int IReadOnlyCollection<IStatus>.Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
int ICollection.Count => this.Length;
|
||||
|
|
@ -130,17 +133,9 @@ public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollectio
|
|||
object ICollection.SyncRoot => this;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<Status> GetEnumerator()
|
||||
public IEnumerator<IStatus> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
var status = this[i];
|
||||
|
||||
if (status == null || status.StatusId == 0)
|
||||
continue;
|
||||
|
||||
yield return status;
|
||||
}
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -155,4 +150,39 @@ public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollectio
|
|||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private struct Enumerator(StatusList statusList) : IEnumerator<IStatus>
|
||||
{
|
||||
private int index = 0;
|
||||
|
||||
public IStatus Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (this.index == statusList.Length) return false;
|
||||
|
||||
for (; this.index < statusList.Length; this.index++)
|
||||
{
|
||||
var status = statusList[this.index];
|
||||
if (status != null && status.StatusId != 0)
|
||||
{
|
||||
this.Current = status;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Structs;
|
||||
|
||||
/// <summary>
|
||||
/// Native memory representation of a FFXIV status effect.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct StatusEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The effect ID.
|
||||
/// </summary>
|
||||
public short EffectId;
|
||||
|
||||
/// <summary>
|
||||
/// How many stacks are present.
|
||||
/// </summary>
|
||||
public byte StackCount;
|
||||
|
||||
/// <summary>
|
||||
/// Additional parameters.
|
||||
/// </summary>
|
||||
public byte Param;
|
||||
|
||||
/// <summary>
|
||||
/// The duration remaining.
|
||||
/// </summary>
|
||||
public float Duration;
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the actor that caused this effect.
|
||||
/// </summary>
|
||||
public int OwnerId;
|
||||
}
|
||||
|
|
@ -11,18 +11,18 @@ using Dalamud.Game.Gui;
|
|||
using Dalamud.Game.Network.Internal.MarketBoardUploaders;
|
||||
using Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis;
|
||||
using Dalamud.Game.Network.Structures;
|
||||
using Dalamud.Game.Player;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Networking.Http;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.Network;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Network.Internal;
|
||||
|
|
@ -269,29 +269,8 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
|
||||
private static (ulong UploaderId, uint WorldId) GetUploaderInfo()
|
||||
{
|
||||
var agentLobby = AgentLobby.Instance();
|
||||
|
||||
var uploaderId = agentLobby->LobbyData.ContentId;
|
||||
if (uploaderId == 0)
|
||||
{
|
||||
var playerState = PlayerState.Instance();
|
||||
if (playerState->IsLoaded)
|
||||
{
|
||||
uploaderId = playerState->ContentId;
|
||||
}
|
||||
}
|
||||
|
||||
var worldId = agentLobby->LobbyData.CurrentWorldId;
|
||||
if (worldId == 0)
|
||||
{
|
||||
var localPlayer = Control.GetLocalPlayer();
|
||||
if (localPlayer != null)
|
||||
{
|
||||
worldId = localPlayer->CurrentWorld;
|
||||
}
|
||||
}
|
||||
|
||||
return (uploaderId, worldId);
|
||||
var playerState = Service<PlayerState>.Get();
|
||||
return (playerState.ContentId, playerState.CurrentWorld.RowId);
|
||||
}
|
||||
|
||||
private unsafe nint CfPopDetour(PublicContentDirector.EnterContentInfoPacket* packetData)
|
||||
|
|
|
|||
27
Dalamud/Game/Player/MentorVersion.cs
Normal file
27
Dalamud/Game/Player/MentorVersion.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace Dalamud.Game.Player;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the mentor certification version for a player.
|
||||
/// </summary>
|
||||
public enum MentorVersion : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the player has never held mentor status in any expansion.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the player was last a mentor during the <c>Shadowbringers</c> expansion.
|
||||
/// </summary>
|
||||
Shadowbringers = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the player was last a mentor during the <c>Endwalker</c> expansion.
|
||||
/// </summary>
|
||||
Endwalker = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the player was last a mentor during the <c>Dawntrail</c> expansion.
|
||||
/// </summary>
|
||||
Dawntrail = 3,
|
||||
}
|
||||
489
Dalamud/Game/Player/PlayerAttribute.cs
Normal file
489
Dalamud/Game/Player/PlayerAttribute.cs
Normal file
|
|
@ -0,0 +1,489 @@
|
|||
namespace Dalamud.Game.Player;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a player's attribute.
|
||||
/// </summary>
|
||||
public enum PlayerAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Strength.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects physical damage dealt by gladiator's arms, marauder's arms, dark knight's arms, gunbreaker's arms, pugilist's arms, lancer's arms, samurai's arms, reaper's arms, thaumaturge's arms, arcanist's arms, red mage's arms, pictomancer's arms, conjurer's arms, astrologian's arms, sage's arms, and blue mage's arms.
|
||||
/// </remarks>
|
||||
Strength = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Dexterity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects physical damage dealt by rogue's arms, viper's arms, archer's arms, machinist's arms, and dancer's arms.
|
||||
/// </remarks>
|
||||
Dexterity = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Vitality.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects maximum HP.
|
||||
/// </remarks>
|
||||
Vitality = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Intelligence.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects attack magic potency when role is DPS.
|
||||
/// </remarks>
|
||||
Intelligence = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Mind.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects healing magic potency. Also affects attack magic potency when role is Healer.
|
||||
/// </remarks>
|
||||
Mind = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Piety.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects MP regeneration. Regeneration rate is determined by piety. Only applicable when in battle and role is Healer.
|
||||
/// </remarks>
|
||||
Piety = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Health Points.
|
||||
/// </summary>
|
||||
HP = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Mana Points.
|
||||
/// </summary>
|
||||
MP = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Tactical Points.
|
||||
/// </summary>
|
||||
TP = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Gathering Point.
|
||||
/// </summary>
|
||||
GP = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Crafting Points.
|
||||
/// </summary>
|
||||
CP = 11,
|
||||
|
||||
/// <summary>
|
||||
/// Physical Damage.
|
||||
/// </summary>
|
||||
PhysicalDamage = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Magic Damage.
|
||||
/// </summary>
|
||||
MagicDamage = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Delay.
|
||||
/// </summary>
|
||||
Delay = 14,
|
||||
|
||||
/// <summary>
|
||||
/// Additional Effect.
|
||||
/// </summary>
|
||||
AdditionalEffect = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Attack Speed.
|
||||
/// </summary>
|
||||
AttackSpeed = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Block Rate.
|
||||
/// </summary>
|
||||
BlockRate = 17,
|
||||
|
||||
/// <summary>
|
||||
/// Block Strength.
|
||||
/// </summary>
|
||||
BlockStrength = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Tenacity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the amount of physical and magic damage dealt and received, as well as HP restored. The higher the value, the more damage dealt, the more HP restored, and the less damage taken. Only applicable when role is Tank.
|
||||
/// </remarks>
|
||||
Tenacity = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Attack Power.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects amount of damage dealt by physical attacks. The higher the value, the more damage dealt.
|
||||
/// </remarks>
|
||||
AttackPower = 20,
|
||||
|
||||
/// <summary>
|
||||
/// Defense.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the amount of damage taken by physical attacks. The higher the value, the less damage taken.
|
||||
/// </remarks>
|
||||
Defense = 21,
|
||||
|
||||
/// <summary>
|
||||
/// Direct Hit Rate.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the rate at which your physical and magic attacks land direct hits, dealing slightly more damage than normal hits. The higher the value, the higher the frequency with which your hits will be direct. Higher values will also result in greater damage for actions which guarantee direct hits.
|
||||
/// </remarks>
|
||||
DirectHitRate = 22,
|
||||
|
||||
/// <summary>
|
||||
/// Evasion.
|
||||
/// </summary>
|
||||
Evasion = 23,
|
||||
|
||||
/// <summary>
|
||||
/// Magic Defense.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the amount of damage taken by magic attacks. The higher the value, the less damage taken.
|
||||
/// </remarks>
|
||||
MagicDefense = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Critical Hit Power.
|
||||
/// </summary>
|
||||
CriticalHitPower = 25,
|
||||
|
||||
/// <summary>
|
||||
/// Critical Hit Resilience.
|
||||
/// </summary>
|
||||
CriticalHitResilience = 26,
|
||||
|
||||
/// <summary>
|
||||
/// Critical Hit.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the amount of physical and magic damage dealt, as well as HP restored. The higher the value, the higher the frequency with which your hits will be critical/higher the potency of critical hits.
|
||||
/// </remarks>
|
||||
CriticalHit = 27,
|
||||
|
||||
/// <summary>
|
||||
/// Critical Hit Evasion.
|
||||
/// </summary>
|
||||
CriticalHitEvasion = 28,
|
||||
|
||||
/// <summary>
|
||||
/// Slashing Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Decreases damage done by slashing attacks.
|
||||
/// </remarks>
|
||||
SlashingResistance = 29,
|
||||
|
||||
/// <summary>
|
||||
/// Piercing Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Decreases damage done by piercing attacks.
|
||||
/// </remarks>
|
||||
PiercingResistance = 30,
|
||||
|
||||
/// <summary>
|
||||
/// Blunt Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Decreases damage done by blunt attacks.
|
||||
/// </remarks>
|
||||
BluntResistance = 31,
|
||||
|
||||
/// <summary>
|
||||
/// Projectile Resistance.
|
||||
/// </summary>
|
||||
ProjectileResistance = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Attack Magic Potency.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the amount of damage dealt by magic attacks.
|
||||
/// </remarks>
|
||||
AttackMagicPotency = 33,
|
||||
|
||||
/// <summary>
|
||||
/// Healing Magic Potency.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the amount of HP restored via healing magic.
|
||||
/// </remarks>
|
||||
HealingMagicPotency = 34,
|
||||
|
||||
/// <summary>
|
||||
/// Enhancement Magic Potency.
|
||||
/// </summary>
|
||||
EnhancementMagicPotency = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Elemental Bonus.
|
||||
/// </summary>
|
||||
ElementalBonus = 36,
|
||||
|
||||
/// <summary>
|
||||
/// Fire Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Decreases fire-aspected damage.
|
||||
/// </remarks>
|
||||
FireResistance = 37,
|
||||
|
||||
/// <summary>
|
||||
/// Ice Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Decreases ice-aspected damage.
|
||||
/// </remarks>
|
||||
IceResistance = 38,
|
||||
|
||||
/// <summary>
|
||||
/// Wind Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Decreases wind-aspected damage.
|
||||
/// </remarks>
|
||||
WindResistance = 39,
|
||||
|
||||
/// <summary>
|
||||
/// Earth Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Decreases earth-aspected damage.
|
||||
/// </remarks>
|
||||
EarthResistance = 40,
|
||||
|
||||
/// <summary>
|
||||
/// Lightning Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Decreases lightning-aspected damage.
|
||||
/// </remarks>
|
||||
LightningResistance = 41,
|
||||
|
||||
/// <summary>
|
||||
/// Water Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Decreases water-aspected damage.
|
||||
/// </remarks>
|
||||
WaterResistance = 42,
|
||||
|
||||
/// <summary>
|
||||
/// Magic Resistance.
|
||||
/// </summary>
|
||||
MagicResistance = 43,
|
||||
|
||||
/// <summary>
|
||||
/// Determination.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the amount of damage dealt by both physical and magic attacks, as well as the amount of HP restored by healing spells.
|
||||
/// </remarks>
|
||||
Determination = 44,
|
||||
|
||||
/// <summary>
|
||||
/// Skill Speed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects both the casting and recast timers, as well as the damage over time potency for weaponskills and auto-attacks. The higher the value, the shorter the timers/higher the potency.
|
||||
/// </remarks>
|
||||
SkillSpeed = 45,
|
||||
|
||||
/// <summary>
|
||||
/// Spell Speed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects both the casting and recast timers for spells. The higher the value, the shorter the timers. Also affects a spell's damage over time or healing over time potency.
|
||||
/// </remarks>
|
||||
SpellSpeed = 46,
|
||||
|
||||
/// <summary>
|
||||
/// Haste.
|
||||
/// </summary>
|
||||
Haste = 47,
|
||||
|
||||
/// <summary>
|
||||
/// Morale.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In PvP, replaces physical and magical defense in determining damage inflicted by other players. Also influences the amount of damage dealt to other players.
|
||||
/// </remarks>
|
||||
Morale = 48,
|
||||
|
||||
/// <summary>
|
||||
/// Enmity.
|
||||
/// </summary>
|
||||
Enmity = 49,
|
||||
|
||||
/// <summary>
|
||||
/// Enmity Reduction.
|
||||
/// </summary>
|
||||
EnmityReduction = 50,
|
||||
|
||||
/// <summary>
|
||||
/// Desynthesis Skill Gain.
|
||||
/// </summary>
|
||||
DesynthesisSkillGain = 51,
|
||||
|
||||
/// <summary>
|
||||
/// EXP Bonus.
|
||||
/// </summary>
|
||||
EXPBonus = 52,
|
||||
|
||||
/// <summary>
|
||||
/// Regen.
|
||||
/// </summary>
|
||||
Regen = 53,
|
||||
|
||||
/// <summary>
|
||||
/// Special Attribute.
|
||||
/// </summary>
|
||||
SpecialAttribute = 54,
|
||||
|
||||
/// <summary>
|
||||
/// Main Attribute.
|
||||
/// </summary>
|
||||
MainAttribute = 55,
|
||||
|
||||
/// <summary>
|
||||
/// Secondary Attribute.
|
||||
/// </summary>
|
||||
SecondaryAttribute = 56,
|
||||
|
||||
/// <summary>
|
||||
/// Slow Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shortens the duration of slow.
|
||||
/// </remarks>
|
||||
SlowResistance = 57,
|
||||
|
||||
/// <summary>
|
||||
/// Petrification Resistance.
|
||||
/// </summary>
|
||||
PetrificationResistance = 58,
|
||||
|
||||
/// <summary>
|
||||
/// Paralysis Resistance.
|
||||
/// </summary>
|
||||
ParalysisResistance = 59,
|
||||
|
||||
/// <summary>
|
||||
/// Silence Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shortens the duration of silence.
|
||||
/// </remarks>
|
||||
SilenceResistance = 60,
|
||||
|
||||
/// <summary>
|
||||
/// Blind Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shortens the duration of blind.
|
||||
/// </remarks>
|
||||
BlindResistance = 61,
|
||||
|
||||
/// <summary>
|
||||
/// Poison Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shortens the duration of poison.
|
||||
/// </remarks>
|
||||
PoisonResistance = 62,
|
||||
|
||||
/// <summary>
|
||||
/// Stun Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shortens the duration of stun.
|
||||
/// </remarks>
|
||||
StunResistance = 63,
|
||||
|
||||
/// <summary>
|
||||
/// Sleep Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shortens the duration of sleep.
|
||||
/// </remarks>
|
||||
SleepResistance = 64,
|
||||
|
||||
/// <summary>
|
||||
/// Bind Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shortens the duration of bind.
|
||||
/// </remarks>
|
||||
BindResistance = 65,
|
||||
|
||||
/// <summary>
|
||||
/// Heavy Resistance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shortens the duration of heavy.
|
||||
/// </remarks>
|
||||
HeavyResistance = 66,
|
||||
|
||||
/// <summary>
|
||||
/// Doom Resistance.
|
||||
/// </summary>
|
||||
DoomResistance = 67,
|
||||
|
||||
/// <summary>
|
||||
/// Reduced Durability Loss.
|
||||
/// </summary>
|
||||
ReducedDurabilityLoss = 68,
|
||||
|
||||
/// <summary>
|
||||
/// Increased Spiritbond Gain.
|
||||
/// </summary>
|
||||
IncreasedSpiritbondGain = 69,
|
||||
|
||||
/// <summary>
|
||||
/// Craftsmanship.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the amount of progress achieved in a single synthesis step.
|
||||
/// </remarks>
|
||||
Craftsmanship = 70,
|
||||
|
||||
/// <summary>
|
||||
/// Control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the amount of quality improved in a single synthesis step.
|
||||
/// </remarks>
|
||||
Control = 71,
|
||||
|
||||
/// <summary>
|
||||
/// Gathering.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects the rate at which items are gathered.
|
||||
/// </remarks>
|
||||
Gathering = 72,
|
||||
|
||||
/// <summary>
|
||||
/// Perception.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Affects item yield when gathering as a botanist or miner, and the size of fish when fishing or spearfishing.
|
||||
/// </remarks>
|
||||
Perception = 73,
|
||||
}
|
||||
232
Dalamud/Game/Player/PlayerState.cs
Normal file
232
Dalamud/Game/Player/PlayerState.cs
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
using CSPlayerState = FFXIVClientStructs.FFXIV.Client.Game.UI.PlayerState;
|
||||
using GrandCompany = Lumina.Excel.Sheets.GrandCompany;
|
||||
|
||||
namespace Dalamud.Game.Player;
|
||||
|
||||
/// <summary>
|
||||
/// This class contains the PlayerState wrappers.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
[ResolveVia<IPlayerState>]
|
||||
internal unsafe class PlayerState : IServiceType, IPlayerState
|
||||
{
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private PlayerState()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsLoaded => CSPlayerState.Instance()->IsLoaded;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string CharacterName => this.IsLoaded ? CSPlayerState.Instance()->CharacterNameString : string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint EntityId => this.IsLoaded ? CSPlayerState.Instance()->EntityId : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong ContentId => this.IsLoaded ? CSPlayerState.Instance()->ContentId : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<World> CurrentWorld
|
||||
{
|
||||
get
|
||||
{
|
||||
var agentLobby = AgentLobby.Instance();
|
||||
return agentLobby->IsLoggedIn
|
||||
? LuminaUtils.CreateRef<World>(agentLobby->LobbyData.CurrentWorldId)
|
||||
: default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<World> HomeWorld
|
||||
{
|
||||
get
|
||||
{
|
||||
var agentLobby = AgentLobby.Instance();
|
||||
return agentLobby->IsLoggedIn
|
||||
? LuminaUtils.CreateRef<World>(agentLobby->LobbyData.HomeWorldId)
|
||||
: default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Sex Sex => this.IsLoaded ? (Sex)CSPlayerState.Instance()->Sex : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Race> Race => this.IsLoaded ? LuminaUtils.CreateRef<Race>(CSPlayerState.Instance()->Race) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Tribe> Tribe => this.IsLoaded ? LuminaUtils.CreateRef<Tribe>(CSPlayerState.Instance()->Tribe) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<ClassJob> ClassJob => this.IsLoaded ? LuminaUtils.CreateRef<ClassJob>(CSPlayerState.Instance()->CurrentClassJobId) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public short Level => this.IsLoaded ? CSPlayerState.Instance()->CurrentLevel : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsLevelSynced => this.IsLoaded && CSPlayerState.Instance()->IsLevelSynced;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public short EffectiveLevel => this.IsLoaded ? (this.IsLevelSynced ? CSPlayerState.Instance()->SyncedLevel : CSPlayerState.Instance()->CurrentLevel) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<GuardianDeity> GuardianDeity => this.IsLoaded ? LuminaUtils.CreateRef<GuardianDeity>(CSPlayerState.Instance()->GuardianDeity) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte BirthMonth => this.IsLoaded ? CSPlayerState.Instance()->BirthMonth : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte BirthDay => this.IsLoaded ? CSPlayerState.Instance()->BirthDay : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<ClassJob> FirstClass => this.IsLoaded ? LuminaUtils.CreateRef<ClassJob>(CSPlayerState.Instance()->FirstClass) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Town> StartTown => this.IsLoaded ? LuminaUtils.CreateRef<Town>(CSPlayerState.Instance()->StartTown) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BaseStrength => this.IsLoaded ? CSPlayerState.Instance()->BaseStrength : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BaseDexterity => this.IsLoaded ? CSPlayerState.Instance()->BaseDexterity : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BaseVitality => this.IsLoaded ? CSPlayerState.Instance()->BaseVitality : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BaseIntelligence => this.IsLoaded ? CSPlayerState.Instance()->BaseIntelligence : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BaseMind => this.IsLoaded ? CSPlayerState.Instance()->BaseMind : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BasePiety => this.IsLoaded ? CSPlayerState.Instance()->BasePiety : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<GrandCompany> GrandCompany => this.IsLoaded ? LuminaUtils.CreateRef<GrandCompany>(CSPlayerState.Instance()->GrandCompany) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Aetheryte> HomeAetheryte => this.IsLoaded ? LuminaUtils.CreateRef<Aetheryte>(CSPlayerState.Instance()->HomeAetheryteId) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<RowRef<Aetheryte>> FavoriteAetherytes
|
||||
{
|
||||
get
|
||||
{
|
||||
var playerState = CSPlayerState.Instance();
|
||||
if (!playerState->IsLoaded)
|
||||
return default;
|
||||
|
||||
var count = playerState->FavouriteAetheryteCount;
|
||||
if (count == 0)
|
||||
return default;
|
||||
|
||||
var array = new RowRef<Aetheryte>[count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
array[i] = LuminaUtils.CreateRef<Aetheryte>(playerState->FavouriteAetherytes[i]);
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Aetheryte> FreeAetheryte => this.IsLoaded ? LuminaUtils.CreateRef<Aetheryte>(CSPlayerState.Instance()->FreeAetheryteId) : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint BaseRestedExperience => this.IsLoaded ? CSPlayerState.Instance()->BaseRestedExperience : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public short PlayerCommendations => this.IsLoaded ? CSPlayerState.Instance()->PlayerCommendations : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte DeliveryLevel => this.IsLoaded ? CSPlayerState.Instance()->DeliveryLevel : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MentorVersion MentorVersion => this.IsLoaded ? (MentorVersion)CSPlayerState.Instance()->MentorVersion : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMentor => this.IsLoaded && CSPlayerState.Instance()->IsMentor();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBattleMentor => this.IsLoaded && CSPlayerState.Instance()->IsBattleMentor();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsTradeMentor => this.IsLoaded && CSPlayerState.Instance()->IsTradeMentor();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsNovice => this.IsLoaded && CSPlayerState.Instance()->IsNovice();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsReturner => this.IsLoaded && CSPlayerState.Instance()->IsReturner();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetAttribute(PlayerAttribute attribute) => this.IsLoaded ? CSPlayerState.Instance()->Attributes[(int)attribute] : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte GetGrandCompanyRank(GrandCompany grandCompany)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return default;
|
||||
|
||||
return grandCompany.RowId switch
|
||||
{
|
||||
1 => CSPlayerState.Instance()->GCRankMaelstrom,
|
||||
2 => CSPlayerState.Instance()->GCRankTwinAdders,
|
||||
3 => CSPlayerState.Instance()->GCRankImmortalFlames,
|
||||
_ => default,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public short GetClassJobLevel(ClassJob classJob)
|
||||
{
|
||||
if (classJob.ExpArrayIndex == -1)
|
||||
return default;
|
||||
|
||||
if (!this.IsLoaded)
|
||||
return default;
|
||||
|
||||
return CSPlayerState.Instance()->ClassJobLevels[classJob.ExpArrayIndex];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetClassJobExperience(ClassJob classJob)
|
||||
{
|
||||
if (classJob.ExpArrayIndex == -1)
|
||||
return default;
|
||||
|
||||
if (!this.IsLoaded)
|
||||
return default;
|
||||
|
||||
return CSPlayerState.Instance()->ClassJobExperience[classJob.ExpArrayIndex];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float GetDesynthesisLevel(ClassJob classJob)
|
||||
{
|
||||
if (classJob.DohDolJobIndex == -1)
|
||||
return default;
|
||||
|
||||
if (!this.IsLoaded)
|
||||
return default;
|
||||
|
||||
return CSPlayerState.Instance()->DesynthesisLevels[classJob.DohDolJobIndex] / 100f;
|
||||
}
|
||||
}
|
||||
17
Dalamud/Game/Player/Sex.cs
Normal file
17
Dalamud/Game/Player/Sex.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace Dalamud.Game.Player;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the sex of a character.
|
||||
/// </summary>
|
||||
public enum Sex : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Male sex.
|
||||
/// </summary>
|
||||
Male = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Female sex.
|
||||
/// </summary>
|
||||
Female = 1,
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ using Dalamud.Configuration.Internal;
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.Config;
|
||||
using Dalamud.Game.Player;
|
||||
using Dalamud.Game.Text.Evaluator.Internal;
|
||||
using Dalamud.Game.Text.Noun;
|
||||
using Dalamud.Game.Text.Noun.Enums;
|
||||
|
|
@ -35,7 +36,6 @@ using Lumina.Text.Payloads;
|
|||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using AddonSheet = Lumina.Excel.Sheets.Addon;
|
||||
using PlayerState = FFXIVClientStructs.FFXIV.Client.Game.UI.PlayerState;
|
||||
using StatusSheet = Lumina.Excel.Sheets.Status;
|
||||
|
||||
namespace Dalamud.Game.Text.Evaluator;
|
||||
|
|
@ -68,6 +68,9 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly SheetRedirectResolver sheetRedirectResolver = Service<SheetRedirectResolver>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly PlayerState playerState = Service<PlayerState>.Get();
|
||||
|
||||
private readonly ConcurrentDictionary<StringCacheKey<ActionKind>, string> actStrCache = [];
|
||||
private readonly ConcurrentDictionary<StringCacheKey<ObjectKind>, string> objStrCache = [];
|
||||
|
||||
|
|
@ -564,7 +567,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
|||
return false;
|
||||
|
||||
// the game uses LocalPlayer here, but using PlayerState seems more safe.
|
||||
return this.ResolveStringExpression(in context, PlayerState.Instance()->EntityId == entityId ? eTrue : eFalse);
|
||||
return this.ResolveStringExpression(in context, this.playerState.EntityId == entityId ? eTrue : eFalse);
|
||||
}
|
||||
|
||||
private bool TryResolveColor(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
||||
|
|
|
|||
95
Dalamud/Game/UnlockState/ItemActionType.cs
Normal file
95
Dalamud/Game/UnlockState/ItemActionType.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.UnlockState;
|
||||
|
||||
/// <summary>
|
||||
/// Enum for <see cref="ItemAction.Type"/>.
|
||||
/// </summary>
|
||||
internal enum ItemActionType : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// No item action.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks a companion (minion).
|
||||
/// </summary>
|
||||
Companion = 853,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks a chocobo companion barding.
|
||||
/// </summary>
|
||||
BuddyEquip = 1013,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks a mount.
|
||||
/// </summary>
|
||||
Mount = 1322,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks recipes from a crafting recipe book.
|
||||
/// </summary>
|
||||
SecretRecipeBook = 2136,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks various types of content (e.g. Riding Maps, Blue Mage Totems, Emotes, Hairstyles).
|
||||
/// </summary>
|
||||
UnlockLink = 2633,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks a Triple Triad Card.
|
||||
/// </summary>
|
||||
TripleTriadCard = 3357,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks gathering nodes of a Folklore Tome.
|
||||
/// </summary>
|
||||
FolkloreTome = 4107,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks an Orchestrion Roll.
|
||||
/// </summary>
|
||||
OrchestrionRoll = 25183,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks portrait designs.
|
||||
/// </summary>
|
||||
FramersKit = 29459,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks Bozjan Field Notes.
|
||||
/// </summary>
|
||||
/// <remarks> These are server-side but are cached client-side. </remarks>
|
||||
FieldNotes = 19743,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks an Ornament (fashion accessory).
|
||||
/// </summary>
|
||||
Ornament = 20086,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks Glasses.
|
||||
/// </summary>
|
||||
Glasses = 37312,
|
||||
|
||||
/// <summary>
|
||||
/// Company Seal Vouchers, which convert the item into Company Seals when used.
|
||||
/// </summary>
|
||||
CompanySealVouchers = 41120,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks Occult Records in Occult Crescent.
|
||||
/// </summary>
|
||||
OccultRecords = 43141,
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks Phantom Jobs in Occult Crescent.
|
||||
/// </summary>
|
||||
SoulShards = 43142,
|
||||
|
||||
/// <summary>
|
||||
/// Star Contributor Certificate, which grants the Star Contributor status in Cosmic Exploration.
|
||||
/// </summary>
|
||||
StarContributorCertificate = 45189,
|
||||
}
|
||||
283
Dalamud/Game/UnlockState/RecipeData.cs
Normal file
283
Dalamud/Game/UnlockState/RecipeData.cs
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
using System.Linq;
|
||||
|
||||
using CommunityToolkit.HighPerformance;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Gui;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.Interop;
|
||||
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.UnlockState;
|
||||
|
||||
/// <summary>
|
||||
/// Represents recipe-related data for all crafting classes.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class RecipeData : IInternalDisposableService
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState.ClientState clientState = Service<ClientState.ClientState>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameGui gameGui = Service<GameGui>.Get();
|
||||
|
||||
private readonly ushort[] craftTypeLevels;
|
||||
private readonly byte[] unlockedNoteBookDivisionsCount;
|
||||
private readonly byte[] unlockedSecretNoteBookDivisionsCount;
|
||||
private readonly ushort[,] noteBookDivisionIds;
|
||||
private byte[]? cachedUnlockedSecretRecipeBooks;
|
||||
private byte[]? cachedUnlockLinks;
|
||||
private byte[]? cachedCompletedQuests;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RecipeData"/> class.
|
||||
/// </summary>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public RecipeData()
|
||||
{
|
||||
var numCraftTypes = this.dataManager.GetExcelSheet<CraftType>().Count();
|
||||
var numSecretNotBookDivisions = this.dataManager.GetExcelSheet<NotebookDivision>().Count(row => row.RowId is >= 1000 and < 2000);
|
||||
|
||||
this.unlockedNoteBookDivisionsCount = new byte[numCraftTypes];
|
||||
this.unlockedSecretNoteBookDivisionsCount = new byte[numCraftTypes];
|
||||
this.noteBookDivisionIds = new ushort[numCraftTypes, numSecretNotBookDivisions];
|
||||
|
||||
this.craftTypeLevels = new ushort[numCraftTypes];
|
||||
|
||||
this.clientState.Login += this.Update;
|
||||
this.clientState.Logout += this.OnLogout;
|
||||
this.clientState.LevelChanged += this.OnlevelChanged;
|
||||
this.gameGui.AgentUpdate += this.OnAgentUpdate;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.clientState.Login -= this.Update;
|
||||
this.clientState.Logout -= this.OnLogout;
|
||||
this.clientState.LevelChanged -= this.OnlevelChanged;
|
||||
this.gameGui.AgentUpdate -= this.OnAgentUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Recipe is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Recipe row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsRecipeUnlocked(Recipe row)
|
||||
{
|
||||
// E8 ?? ?? ?? ?? 48 63 76 (2025.09.04)
|
||||
var division = row.RecipeNotebookList.RowId != 0 && row.RecipeNotebookList.IsValid
|
||||
? (row.RecipeNotebookList.RowId - 1000) / 8 + 1000
|
||||
: ((uint)row.RecipeLevelTable.Value.ClassJobLevel - 1) / 5;
|
||||
|
||||
// E8 ?? ?? ?? ?? 33 ED 84 C0 75 (2025.09.04)
|
||||
foreach (var craftTypeRow in this.dataManager.GetExcelSheet<CraftType>())
|
||||
{
|
||||
var craftType = (byte)craftTypeRow.RowId;
|
||||
|
||||
if (division < this.unlockedNoteBookDivisionsCount[craftType])
|
||||
return true;
|
||||
|
||||
if (this.unlockedNoteBookDivisionsCount[craftType] == 0)
|
||||
continue;
|
||||
|
||||
if (division is 5000 or 5001)
|
||||
return true;
|
||||
|
||||
if (division < 1000)
|
||||
continue;
|
||||
|
||||
if (this.unlockedSecretNoteBookDivisionsCount[craftType] == 0)
|
||||
continue;
|
||||
|
||||
if (this.noteBookDivisionIds.GetRowSpan(craftType).Contains((ushort)division))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnLogout(int type, int code)
|
||||
{
|
||||
this.cachedUnlockedSecretRecipeBooks = null;
|
||||
this.cachedUnlockLinks = null;
|
||||
this.cachedCompletedQuests = null;
|
||||
}
|
||||
|
||||
private void OnlevelChanged(uint classJobId, uint level)
|
||||
{
|
||||
if (this.dataManager.GetExcelSheet<ClassJob>().TryGetRow(classJobId, out var classJobRow) &&
|
||||
classJobRow.ClassJobCategory.RowId == 33) // Crafter
|
||||
{
|
||||
this.Update();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAgentUpdate(AgentUpdateFlag agentUpdateFlag)
|
||||
{
|
||||
if (agentUpdateFlag.HasFlag(AgentUpdateFlag.UnlocksUpdate))
|
||||
this.Update();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// based on Client::Game::UI::RecipeNote.InitializeStructs
|
||||
|
||||
if (!this.clientState.IsLoggedIn || !this.NeedsUpdate())
|
||||
return;
|
||||
|
||||
Array.Clear(this.unlockedNoteBookDivisionsCount, 0, this.unlockedNoteBookDivisionsCount.Length);
|
||||
Array.Clear(this.unlockedSecretNoteBookDivisionsCount, 0, this.unlockedSecretNoteBookDivisionsCount.Length);
|
||||
Array.Clear(this.noteBookDivisionIds, 0, this.noteBookDivisionIds.Length);
|
||||
|
||||
foreach (var craftTypeRow in this.dataManager.GetExcelSheet<CraftType>())
|
||||
{
|
||||
var craftType = (byte)craftTypeRow.RowId;
|
||||
var craftTypeLevel = RecipeNote.Instance()->GetCraftTypeLevel(craftType);
|
||||
if (craftTypeLevel == 0)
|
||||
continue;
|
||||
|
||||
var noteBookDivisionIndex = -1;
|
||||
|
||||
foreach (var noteBookDivisionRow in this.dataManager.GetExcelSheet<NotebookDivision>())
|
||||
{
|
||||
if (noteBookDivisionRow.RowId < 1000)
|
||||
{
|
||||
if (craftTypeLevel >= noteBookDivisionRow.CraftOpeningLevel)
|
||||
this.unlockedNoteBookDivisionsCount[craftType]++;
|
||||
}
|
||||
else if (noteBookDivisionRow.RowId < 2000)
|
||||
{
|
||||
noteBookDivisionIndex++;
|
||||
|
||||
// For future Lumina.Excel update, replace with:
|
||||
// if (!notebookDivisionRow.AllowedCraftTypes[craftType])
|
||||
// continue;
|
||||
|
||||
switch (craftTypeRow.RowId)
|
||||
{
|
||||
case 0 when !noteBookDivisionRow.CRPCraft: continue;
|
||||
case 1 when !noteBookDivisionRow.BSMCraft: continue;
|
||||
case 2 when !noteBookDivisionRow.ARMCraft: continue;
|
||||
case 3 when !noteBookDivisionRow.GSMCraft: continue;
|
||||
case 4 when !noteBookDivisionRow.LTWCraft: continue;
|
||||
case 5 when !noteBookDivisionRow.WVRCraft: continue;
|
||||
case 6 when !noteBookDivisionRow.ALCCraft: continue;
|
||||
case 7 when !noteBookDivisionRow.CULCraft: continue;
|
||||
}
|
||||
|
||||
if (noteBookDivisionRow.GatheringOpeningLevel != byte.MaxValue)
|
||||
continue;
|
||||
|
||||
// For future Lumina.Excel update, replace with:
|
||||
// if (notebookDivisionRow.RequiresSecretRecipeBookGroupUnlock)
|
||||
if (noteBookDivisionRow.Unknown1)
|
||||
{
|
||||
var secretRecipeBookUnlocked = false;
|
||||
|
||||
// For future Lumina.Excel update, iterate over notebookDivisionRow.SecretRecipeBookGroups
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
// For future Lumina.Excel update, replace with:
|
||||
// if (secretRecipeBookGroup.RowId == 0 || !secretRecipeBookGroup.IsValid)
|
||||
// continue;
|
||||
var secretRecipeBookGroupRowId = i switch
|
||||
{
|
||||
0 => noteBookDivisionRow.Unknown2,
|
||||
1 => noteBookDivisionRow.Unknown2,
|
||||
_ => default,
|
||||
};
|
||||
|
||||
if (secretRecipeBookGroupRowId == 0)
|
||||
continue;
|
||||
|
||||
if (!this.dataManager.GetExcelSheet<SecretRecipeBookGroup>().TryGetRow(secretRecipeBookGroupRowId, out var secretRecipeBookGroupRow))
|
||||
continue;
|
||||
|
||||
// For future Lumina.Excel update, replace with:
|
||||
// var bitIndex = secretRecipeBookGroup.Value.UnlockBitIndex[craftType];
|
||||
|
||||
var bitIndex = craftType switch
|
||||
{
|
||||
0 => secretRecipeBookGroupRow.Unknown0,
|
||||
1 => secretRecipeBookGroupRow.Unknown1,
|
||||
2 => secretRecipeBookGroupRow.Unknown2,
|
||||
3 => secretRecipeBookGroupRow.Unknown3,
|
||||
4 => secretRecipeBookGroupRow.Unknown4,
|
||||
5 => secretRecipeBookGroupRow.Unknown5,
|
||||
6 => secretRecipeBookGroupRow.Unknown6,
|
||||
7 => secretRecipeBookGroupRow.Unknown7,
|
||||
_ => default,
|
||||
};
|
||||
|
||||
if (PlayerState.Instance()->UnlockedSecretRecipeBooksBitArray.Get(bitIndex))
|
||||
{
|
||||
secretRecipeBookUnlocked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (noteBookDivisionRow.CraftOpeningLevel > craftTypeLevel && !secretRecipeBookUnlocked)
|
||||
continue;
|
||||
}
|
||||
else if (craftTypeLevel < noteBookDivisionRow.CraftOpeningLevel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (noteBookDivisionRow.QuestUnlock.RowId != 0 && !UIState.Instance()->IsUnlockLinkUnlockedOrQuestCompleted(noteBookDivisionRow.QuestUnlock.RowId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.unlockedSecretNoteBookDivisionsCount[craftType]++;
|
||||
this.noteBookDivisionIds[craftType, noteBookDivisionIndex] = (ushort)noteBookDivisionRow.RowId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool NeedsUpdate()
|
||||
{
|
||||
var changed = false;
|
||||
|
||||
foreach (var craftTypeRow in this.dataManager.GetExcelSheet<CraftType>())
|
||||
{
|
||||
var craftType = (byte)craftTypeRow.RowId;
|
||||
var craftTypeLevel = RecipeNote.Instance()->GetCraftTypeLevel(craftType);
|
||||
|
||||
if (this.craftTypeLevels[craftType] != craftTypeLevel)
|
||||
{
|
||||
this.craftTypeLevels[craftType] = craftTypeLevel;
|
||||
changed |= true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cachedUnlockedSecretRecipeBooks == null || !PlayerState.Instance()->UnlockedSecretRecipeBooks.SequenceEqual(this.cachedUnlockedSecretRecipeBooks))
|
||||
{
|
||||
this.cachedUnlockedSecretRecipeBooks = PlayerState.Instance()->UnlockedSecretRecipeBooks.ToArray();
|
||||
changed |= true;
|
||||
}
|
||||
|
||||
if (this.cachedUnlockLinks == null || !UIState.Instance()->UnlockLinks.SequenceEqual(this.cachedUnlockLinks))
|
||||
{
|
||||
this.cachedUnlockLinks = UIState.Instance()->UnlockLinks.ToArray();
|
||||
changed |= true;
|
||||
}
|
||||
|
||||
if (this.cachedCompletedQuests == null || !QuestManager.Instance()->CompletedQuests.SequenceEqual(this.cachedCompletedQuests))
|
||||
{
|
||||
this.cachedCompletedQuests = QuestManager.Instance()->CompletedQuests.ToArray();
|
||||
changed |= true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
858
Dalamud/Game/UnlockState/UnlockState.cs
Normal file
858
Dalamud/Game/UnlockState/UnlockState.cs
Normal file
|
|
@ -0,0 +1,858 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.Exd;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
using ActionSheet = Lumina.Excel.Sheets.Action;
|
||||
using InstanceContentSheet = Lumina.Excel.Sheets.InstanceContent;
|
||||
using PublicContentSheet = Lumina.Excel.Sheets.PublicContent;
|
||||
|
||||
namespace Dalamud.Game.UnlockState;
|
||||
|
||||
#pragma warning disable UnlockState
|
||||
|
||||
/// <summary>
|
||||
/// This class provides unlock state of various content in the game.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||
{
|
||||
private static readonly ModuleLog Log = new(nameof(UnlockState));
|
||||
|
||||
private readonly ConcurrentDictionary<Type, HashSet<uint>> cachedUnlockedRowIds = [];
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState.ClientState clientState = Service<ClientState.ClientState>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameGui gameGui = Service<GameGui>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly RecipeData recipeData = Service<RecipeData>.Get();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private UnlockState()
|
||||
{
|
||||
this.clientState.Login += this.OnLogin;
|
||||
this.clientState.Logout += this.OnLogout;
|
||||
this.gameGui.AgentUpdate += this.OnAgentUpdate;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IUnlockState.UnlockDelegate Unlock;
|
||||
|
||||
private bool IsLoaded => PlayerState.Instance()->IsLoaded;
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.clientState.Login -= this.OnLogin;
|
||||
this.clientState.Logout -= this.OnLogout;
|
||||
this.gameGui.AgentUpdate -= this.OnAgentUpdate;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsActionUnlocked(ActionSheet row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.UnlockLink.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAetherCurrentUnlocked(AetherCurrent row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return PlayerState.Instance()->IsAetherCurrentUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAetherCurrentCompFlgSetUnlocked(AetherCurrentCompFlgSet row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return PlayerState.Instance()->IsAetherCurrentZoneComplete(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAozActionUnlocked(AozAction row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
if (row.RowId == 0 || !row.Action.IsValid)
|
||||
return false;
|
||||
|
||||
return UIState.Instance()->IsUnlockLinkUnlockedOrQuestCompleted(row.Action.Value.UnlockLink.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerBgUnlocked(BannerBg row)
|
||||
{
|
||||
return row.UnlockCondition.IsValid && this.IsBannerConditionUnlocked(row.UnlockCondition.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerConditionUnlocked(BannerCondition row)
|
||||
{
|
||||
if (row.RowId == 0)
|
||||
return false;
|
||||
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
var rowPtr = ExdModule.GetBannerConditionByIndex(row.RowId);
|
||||
if (rowPtr == null)
|
||||
return false;
|
||||
|
||||
return ExdModule.GetBannerConditionUnlockState(rowPtr) == 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerDecorationUnlocked(BannerDecoration row)
|
||||
{
|
||||
return row.UnlockCondition.IsValid && this.IsBannerConditionUnlocked(row.UnlockCondition.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerFacialUnlocked(BannerFacial row)
|
||||
{
|
||||
return row.UnlockCondition.IsValid && this.IsBannerConditionUnlocked(row.UnlockCondition.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerFrameUnlocked(BannerFrame row)
|
||||
{
|
||||
return row.UnlockCondition.IsValid && this.IsBannerConditionUnlocked(row.UnlockCondition.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerTimelineUnlocked(BannerTimeline row)
|
||||
{
|
||||
return row.UnlockCondition.IsValid && this.IsBannerConditionUnlocked(row.UnlockCondition.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBuddyActionUnlocked(BuddyAction row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.UnlockLink);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBuddyEquipUnlocked(BuddyEquip row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return UIState.Instance()->Buddy.CompanionInfo.IsBuddyEquipUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsCharaMakeCustomizeUnlocked(CharaMakeCustomize row)
|
||||
{
|
||||
return row.IsPurchasable && this.IsUnlockLinkUnlocked(row.UnlockLink);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsChocoboTaxiStandUnlocked(ChocoboTaxiStand row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return UIState.Instance()->IsChocoboTaxiStandUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsCompanionUnlocked(Companion row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return UIState.Instance()->IsCompanionUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsCraftActionUnlocked(CraftAction row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.QuestRequirement.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsCSBonusContentTypeUnlocked(CSBonusContentType row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.UnlockLink);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsEmoteUnlocked(Emote row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.UnlockLink);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsEmjVoiceNpcUnlocked(EmjVoiceNpc row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.Unknown26);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsEmjCostumeUnlocked(EmjCostume row)
|
||||
{
|
||||
return this.dataManager.GetExcelSheet<EmjVoiceNpc>().TryGetRow(row.RowId, out var emjVoiceNpcRow)
|
||||
&& this.IsEmjVoiceNpcUnlocked(emjVoiceNpcRow)
|
||||
&& QuestManager.IsQuestComplete(row.Unknown1);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsGeneralActionUnlocked(GeneralAction row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.UnlockLink);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsGlassesUnlocked(Glasses row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return PlayerState.Instance()->IsGlassesUnlocked((ushort)row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsHowToUnlocked(HowTo row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return UIState.Instance()->IsHowToUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsInstanceContentUnlocked(InstanceContentSheet row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return UIState.IsInstanceContentUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe bool IsItemUnlocked(Item row)
|
||||
{
|
||||
if (row.ItemAction.RowId == 0)
|
||||
return false;
|
||||
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
// To avoid the ExdModule.GetItemRowById call, which can return null if the excel page
|
||||
// is not loaded, we're going to imitate the IsItemActionUnlocked call first:
|
||||
switch ((ItemActionType)row.ItemAction.Value.Type)
|
||||
{
|
||||
case ItemActionType.Companion:
|
||||
return UIState.Instance()->IsCompanionUnlocked(row.ItemAction.Value.Data[0]);
|
||||
|
||||
case ItemActionType.BuddyEquip:
|
||||
return UIState.Instance()->Buddy.CompanionInfo.IsBuddyEquipUnlocked(row.ItemAction.Value.Data[0]);
|
||||
|
||||
case ItemActionType.Mount:
|
||||
return PlayerState.Instance()->IsMountUnlocked(row.ItemAction.Value.Data[0]);
|
||||
|
||||
case ItemActionType.SecretRecipeBook:
|
||||
return PlayerState.Instance()->IsSecretRecipeBookUnlocked(row.ItemAction.Value.Data[0]);
|
||||
|
||||
case ItemActionType.UnlockLink:
|
||||
case ItemActionType.OccultRecords:
|
||||
return UIState.Instance()->IsUnlockLinkUnlocked(row.ItemAction.Value.Data[0]);
|
||||
|
||||
case ItemActionType.TripleTriadCard when row.AdditionalData.Is<TripleTriadCard>():
|
||||
return UIState.Instance()->IsTripleTriadCardUnlocked((ushort)row.AdditionalData.RowId);
|
||||
|
||||
case ItemActionType.FolkloreTome:
|
||||
return PlayerState.Instance()->IsFolkloreBookUnlocked(row.ItemAction.Value.Data[0]);
|
||||
|
||||
case ItemActionType.OrchestrionRoll when row.AdditionalData.Is<Orchestrion>():
|
||||
return PlayerState.Instance()->IsOrchestrionRollUnlocked(row.AdditionalData.RowId);
|
||||
|
||||
case ItemActionType.FramersKit:
|
||||
return PlayerState.Instance()->IsFramersKitUnlocked(row.AdditionalData.RowId);
|
||||
|
||||
case ItemActionType.Ornament:
|
||||
return PlayerState.Instance()->IsOrnamentUnlocked(row.ItemAction.Value.Data[0]);
|
||||
|
||||
case ItemActionType.Glasses:
|
||||
return PlayerState.Instance()->IsGlassesUnlocked((ushort)row.AdditionalData.RowId);
|
||||
|
||||
case ItemActionType.SoulShards when PublicContentOccultCrescent.GetState() is var occultCrescentState && occultCrescentState != null:
|
||||
var supportJobId = (byte)row.ItemAction.Value.Data[0];
|
||||
return supportJobId < occultCrescentState->SupportJobLevels.Length && occultCrescentState->SupportJobLevels[supportJobId] != 0;
|
||||
|
||||
case ItemActionType.CompanySealVouchers:
|
||||
return false;
|
||||
}
|
||||
|
||||
var nativeRow = ExdModule.GetItemRowById(row.RowId);
|
||||
return nativeRow != null && UIState.Instance()->IsItemActionUnlocked(nativeRow) == 1;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMcGuffinUnlocked(McGuffin row)
|
||||
{
|
||||
return PlayerState.Instance()->IsMcGuffinUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMJILandmarkUnlocked(MJILandmark row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.UnlockLink);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMKDLoreUnlocked(MKDLore row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.Unknown2);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMountUnlocked(Mount row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return PlayerState.Instance()->IsMountUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsNotebookDivisionUnlocked(NotebookDivision row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.QuestUnlock.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsOrchestrionUnlocked(Orchestrion row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return PlayerState.Instance()->IsOrchestrionRollUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsOrnamentUnlocked(Ornament row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return PlayerState.Instance()->IsOrnamentUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPerformUnlocked(Perform row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked((uint)row.UnlockLink);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPublicContentUnlocked(PublicContentSheet row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return UIState.IsPublicContentUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRecipeUnlocked(Recipe row)
|
||||
{
|
||||
return this.recipeData.IsRecipeUnlocked(row);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSecretRecipeBookUnlocked(SecretRecipeBook row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return PlayerState.Instance()->IsSecretRecipeBookUnlocked(row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsTraitUnlocked(Trait row)
|
||||
{
|
||||
return this.IsUnlockLinkUnlocked(row.Quest.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsTripleTriadCardUnlocked(TripleTriadCard row)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
return UIState.Instance()->IsTripleTriadCardUnlocked((ushort)row.RowId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsItemUnlockable(Item row)
|
||||
{
|
||||
if (row.ItemAction.RowId == 0)
|
||||
return false;
|
||||
|
||||
return (ItemActionType)row.ItemAction.Value.Type
|
||||
is ItemActionType.Companion
|
||||
or ItemActionType.BuddyEquip
|
||||
or ItemActionType.Mount
|
||||
or ItemActionType.SecretRecipeBook
|
||||
or ItemActionType.UnlockLink
|
||||
or ItemActionType.TripleTriadCard
|
||||
or ItemActionType.FolkloreTome
|
||||
or ItemActionType.OrchestrionRoll
|
||||
or ItemActionType.FramersKit
|
||||
or ItemActionType.Ornament
|
||||
or ItemActionType.Glasses
|
||||
or ItemActionType.OccultRecords
|
||||
or ItemActionType.SoulShards;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRowRefUnlocked<T>(RowRef<T> rowRef) where T : struct, IExcelRow<T>
|
||||
{
|
||||
return this.IsRowRefUnlocked((RowRef)rowRef);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRowRefUnlocked(RowRef rowRef)
|
||||
{
|
||||
if (!this.IsLoaded || rowRef.IsUntyped)
|
||||
return false;
|
||||
|
||||
if (rowRef.TryGetValue<ActionSheet>(out var actionRow))
|
||||
return this.IsActionUnlocked(actionRow);
|
||||
|
||||
if (rowRef.TryGetValue<AetherCurrent>(out var aetherCurrentRow))
|
||||
return this.IsAetherCurrentUnlocked(aetherCurrentRow);
|
||||
|
||||
if (rowRef.TryGetValue<AetherCurrentCompFlgSet>(out var aetherCurrentCompFlgSetRow))
|
||||
return this.IsAetherCurrentCompFlgSetUnlocked(aetherCurrentCompFlgSetRow);
|
||||
|
||||
if (rowRef.TryGetValue<AozAction>(out var aozActionRow))
|
||||
return this.IsAozActionUnlocked(aozActionRow);
|
||||
|
||||
if (rowRef.TryGetValue<BannerBg>(out var bannerBgRow))
|
||||
return this.IsBannerBgUnlocked(bannerBgRow);
|
||||
|
||||
if (rowRef.TryGetValue<BannerCondition>(out var bannerConditionRow))
|
||||
return this.IsBannerConditionUnlocked(bannerConditionRow);
|
||||
|
||||
if (rowRef.TryGetValue<BannerDecoration>(out var bannerDecorationRow))
|
||||
return this.IsBannerDecorationUnlocked(bannerDecorationRow);
|
||||
|
||||
if (rowRef.TryGetValue<BannerFacial>(out var bannerFacialRow))
|
||||
return this.IsBannerFacialUnlocked(bannerFacialRow);
|
||||
|
||||
if (rowRef.TryGetValue<BannerFrame>(out var bannerFrameRow))
|
||||
return this.IsBannerFrameUnlocked(bannerFrameRow);
|
||||
|
||||
if (rowRef.TryGetValue<BannerTimeline>(out var bannerTimelineRow))
|
||||
return this.IsBannerTimelineUnlocked(bannerTimelineRow);
|
||||
|
||||
if (rowRef.TryGetValue<BuddyAction>(out var buddyActionRow))
|
||||
return this.IsBuddyActionUnlocked(buddyActionRow);
|
||||
|
||||
if (rowRef.TryGetValue<BuddyEquip>(out var buddyEquipRow))
|
||||
return this.IsBuddyEquipUnlocked(buddyEquipRow);
|
||||
|
||||
if (rowRef.TryGetValue<CSBonusContentType>(out var csBonusContentTypeRow))
|
||||
return this.IsCSBonusContentTypeUnlocked(csBonusContentTypeRow);
|
||||
|
||||
if (rowRef.TryGetValue<CharaMakeCustomize>(out var charaMakeCustomizeRow))
|
||||
return this.IsCharaMakeCustomizeUnlocked(charaMakeCustomizeRow);
|
||||
|
||||
if (rowRef.TryGetValue<ChocoboTaxiStand>(out var chocoboTaxiStandRow))
|
||||
return this.IsChocoboTaxiStandUnlocked(chocoboTaxiStandRow);
|
||||
|
||||
if (rowRef.TryGetValue<Companion>(out var companionRow))
|
||||
return this.IsCompanionUnlocked(companionRow);
|
||||
|
||||
if (rowRef.TryGetValue<CraftAction>(out var craftActionRow))
|
||||
return this.IsCraftActionUnlocked(craftActionRow);
|
||||
|
||||
if (rowRef.TryGetValue<Emote>(out var emoteRow))
|
||||
return this.IsEmoteUnlocked(emoteRow);
|
||||
|
||||
if (rowRef.TryGetValue<GeneralAction>(out var generalActionRow))
|
||||
return this.IsGeneralActionUnlocked(generalActionRow);
|
||||
|
||||
if (rowRef.TryGetValue<Glasses>(out var glassesRow))
|
||||
return this.IsGlassesUnlocked(glassesRow);
|
||||
|
||||
if (rowRef.TryGetValue<HowTo>(out var howToRow))
|
||||
return this.IsHowToUnlocked(howToRow);
|
||||
|
||||
if (rowRef.TryGetValue<InstanceContentSheet>(out var instanceContentRow))
|
||||
return this.IsInstanceContentUnlocked(instanceContentRow);
|
||||
|
||||
if (rowRef.TryGetValue<Item>(out var itemRow))
|
||||
return this.IsItemUnlocked(itemRow);
|
||||
|
||||
if (rowRef.TryGetValue<MJILandmark>(out var mjiLandmarkRow))
|
||||
return this.IsMJILandmarkUnlocked(mjiLandmarkRow);
|
||||
|
||||
if (rowRef.TryGetValue<MKDLore>(out var mkdLoreRow))
|
||||
return this.IsMKDLoreUnlocked(mkdLoreRow);
|
||||
|
||||
if (rowRef.TryGetValue<McGuffin>(out var mcGuffinRow))
|
||||
return this.IsMcGuffinUnlocked(mcGuffinRow);
|
||||
|
||||
if (rowRef.TryGetValue<Mount>(out var mountRow))
|
||||
return this.IsMountUnlocked(mountRow);
|
||||
|
||||
if (rowRef.TryGetValue<NotebookDivision>(out var notebookDivisionRow))
|
||||
return this.IsNotebookDivisionUnlocked(notebookDivisionRow);
|
||||
|
||||
if (rowRef.TryGetValue<Orchestrion>(out var orchestrionRow))
|
||||
return this.IsOrchestrionUnlocked(orchestrionRow);
|
||||
|
||||
if (rowRef.TryGetValue<Ornament>(out var ornamentRow))
|
||||
return this.IsOrnamentUnlocked(ornamentRow);
|
||||
|
||||
if (rowRef.TryGetValue<Perform>(out var performRow))
|
||||
return this.IsPerformUnlocked(performRow);
|
||||
|
||||
if (rowRef.TryGetValue<PublicContentSheet>(out var publicContentRow))
|
||||
return this.IsPublicContentUnlocked(publicContentRow);
|
||||
|
||||
if (rowRef.TryGetValue<Recipe>(out var recipeRow))
|
||||
return this.IsRecipeUnlocked(recipeRow);
|
||||
|
||||
if (rowRef.TryGetValue<SecretRecipeBook>(out var secretRecipeBookRow))
|
||||
return this.IsSecretRecipeBookUnlocked(secretRecipeBookRow);
|
||||
|
||||
if (rowRef.TryGetValue<Trait>(out var traitRow))
|
||||
return this.IsTraitUnlocked(traitRow);
|
||||
|
||||
if (rowRef.TryGetValue<TripleTriadCard>(out var tripleTriadCardRow))
|
||||
return this.IsTripleTriadCardUnlocked(tripleTriadCardRow);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsUnlockLinkUnlocked(ushort unlockLink)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
if (unlockLink == 0)
|
||||
return false;
|
||||
|
||||
return UIState.Instance()->IsUnlockLinkUnlocked(unlockLink);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsUnlockLinkUnlocked(uint unlockLink)
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return false;
|
||||
|
||||
if (unlockLink == 0)
|
||||
return false;
|
||||
|
||||
return UIState.Instance()->IsUnlockLinkUnlockedOrQuestCompleted(unlockLink);
|
||||
}
|
||||
|
||||
private void OnLogin()
|
||||
{
|
||||
this.Update();
|
||||
}
|
||||
|
||||
private void OnLogout(int type, int code)
|
||||
{
|
||||
this.cachedUnlockedRowIds.Clear();
|
||||
}
|
||||
|
||||
private void OnAgentUpdate(AgentUpdateFlag agentUpdateFlag)
|
||||
{
|
||||
if (agentUpdateFlag.HasFlag(AgentUpdateFlag.UnlocksUpdate))
|
||||
this.Update();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!this.IsLoaded)
|
||||
return;
|
||||
|
||||
this.UpdateUnlocksForSheet<ActionSheet>();
|
||||
this.UpdateUnlocksForSheet<AetherCurrent>();
|
||||
this.UpdateUnlocksForSheet<AetherCurrentCompFlgSet>();
|
||||
this.UpdateUnlocksForSheet<AozAction>();
|
||||
this.UpdateUnlocksForSheet<BannerBg>();
|
||||
this.UpdateUnlocksForSheet<BannerCondition>();
|
||||
this.UpdateUnlocksForSheet<BannerDecoration>();
|
||||
this.UpdateUnlocksForSheet<BannerFacial>();
|
||||
this.UpdateUnlocksForSheet<BannerFrame>();
|
||||
this.UpdateUnlocksForSheet<BannerTimeline>();
|
||||
this.UpdateUnlocksForSheet<BuddyAction>();
|
||||
this.UpdateUnlocksForSheet<BuddyEquip>();
|
||||
this.UpdateUnlocksForSheet<CSBonusContentType>();
|
||||
this.UpdateUnlocksForSheet<CharaMakeCustomize>();
|
||||
this.UpdateUnlocksForSheet<ChocoboTaxi>();
|
||||
this.UpdateUnlocksForSheet<Companion>();
|
||||
this.UpdateUnlocksForSheet<CraftAction>();
|
||||
this.UpdateUnlocksForSheet<EmjVoiceNpc>();
|
||||
this.UpdateUnlocksForSheet<Emote>();
|
||||
this.UpdateUnlocksForSheet<GeneralAction>();
|
||||
this.UpdateUnlocksForSheet<Glasses>();
|
||||
this.UpdateUnlocksForSheet<HowTo>();
|
||||
this.UpdateUnlocksForSheet<InstanceContentSheet>();
|
||||
this.UpdateUnlocksForSheet<Item>();
|
||||
this.UpdateUnlocksForSheet<MJILandmark>();
|
||||
this.UpdateUnlocksForSheet<MKDLore>();
|
||||
this.UpdateUnlocksForSheet<McGuffin>();
|
||||
this.UpdateUnlocksForSheet<Mount>();
|
||||
this.UpdateUnlocksForSheet<NotebookDivision>();
|
||||
this.UpdateUnlocksForSheet<Orchestrion>();
|
||||
this.UpdateUnlocksForSheet<Ornament>();
|
||||
this.UpdateUnlocksForSheet<Perform>();
|
||||
this.UpdateUnlocksForSheet<PublicContentSheet>();
|
||||
this.UpdateUnlocksForSheet<Recipe>();
|
||||
this.UpdateUnlocksForSheet<SecretRecipeBook>();
|
||||
this.UpdateUnlocksForSheet<Trait>();
|
||||
this.UpdateUnlocksForSheet<TripleTriadCard>();
|
||||
|
||||
// Not implemented:
|
||||
// - DescriptionPage: quite complex
|
||||
// - QuestAcceptAdditionCondition: ignored
|
||||
|
||||
// For some other day:
|
||||
// - FishingSpot
|
||||
// - Spearfishing
|
||||
// - Adventure (Sightseeing)
|
||||
// - MinerFolkloreTome
|
||||
// - BotanistFolkloreTome
|
||||
// - FishingFolkloreTome
|
||||
// - VVD or is that unlocked via quest?
|
||||
// - VVDNotebookContents?
|
||||
// - FramersKit (is that just an Item?)
|
||||
// - ... more?
|
||||
|
||||
// Subrow sheets, which are incompatible with the current Unlock event, since RowRef doesn't carry the SubrowId:
|
||||
// - EmjCostume
|
||||
|
||||
// Probably not happening, because it requires fetching data from server:
|
||||
// - Achievements
|
||||
// - Titles
|
||||
// - Bozjan Field Notes
|
||||
// - Support/Phantom Jobs, which require to be in Occult Crescent, because it checks the jobs level for != 0
|
||||
}
|
||||
|
||||
private void UpdateUnlocksForSheet<T>() where T : struct, IExcelRow<T>
|
||||
{
|
||||
var unlockedRowIds = this.cachedUnlockedRowIds.GetOrAdd(typeof(T), _ => []);
|
||||
|
||||
foreach (var row in this.dataManager.GetExcelSheet<T>())
|
||||
{
|
||||
if (unlockedRowIds.Contains(row.RowId))
|
||||
continue;
|
||||
|
||||
var rowRef = LuminaUtils.CreateRef<T>(row.RowId);
|
||||
|
||||
if (!this.IsRowRefUnlocked(rowRef))
|
||||
continue;
|
||||
|
||||
unlockedRowIds.Add(row.RowId);
|
||||
|
||||
Log.Verbose($"Unlock detected: {typeof(T).Name}#{row.RowId}");
|
||||
|
||||
foreach (var action in Delegate.EnumerateInvocationList(this.Unlock))
|
||||
{
|
||||
try
|
||||
{
|
||||
action((RowRef)rowRef);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception during raise of {handler}", action.Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a <see cref="UnlockState"/> service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IUnlockState>]
|
||||
#pragma warning restore SA1015
|
||||
internal class UnlockStatePluginScoped : IInternalDisposableService, IUnlockState
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly UnlockState unlockStateService = Service<UnlockState>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UnlockStatePluginScoped"/> class.
|
||||
/// </summary>
|
||||
internal UnlockStatePluginScoped()
|
||||
{
|
||||
this.unlockStateService.Unlock += this.UnlockForward;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IUnlockState.UnlockDelegate? Unlock;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsActionUnlocked(ActionSheet row) => this.unlockStateService.IsActionUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAetherCurrentCompFlgSetUnlocked(AetherCurrentCompFlgSet row) => this.unlockStateService.IsAetherCurrentCompFlgSetUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAetherCurrentUnlocked(AetherCurrent row) => this.unlockStateService.IsAetherCurrentUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAozActionUnlocked(AozAction row) => this.unlockStateService.IsAozActionUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerBgUnlocked(BannerBg row) => this.unlockStateService.IsBannerBgUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerConditionUnlocked(BannerCondition row) => this.unlockStateService.IsBannerConditionUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerDecorationUnlocked(BannerDecoration row) => this.unlockStateService.IsBannerDecorationUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerFacialUnlocked(BannerFacial row) => this.unlockStateService.IsBannerFacialUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerFrameUnlocked(BannerFrame row) => this.unlockStateService.IsBannerFrameUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBannerTimelineUnlocked(BannerTimeline row) => this.unlockStateService.IsBannerTimelineUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBuddyActionUnlocked(BuddyAction row) => this.unlockStateService.IsBuddyActionUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsBuddyEquipUnlocked(BuddyEquip row) => this.unlockStateService.IsBuddyEquipUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsCharaMakeCustomizeUnlocked(CharaMakeCustomize row) => this.unlockStateService.IsCharaMakeCustomizeUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsChocoboTaxiStandUnlocked(ChocoboTaxiStand row) => this.unlockStateService.IsChocoboTaxiStandUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsCompanionUnlocked(Companion row) => this.unlockStateService.IsCompanionUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsCraftActionUnlocked(CraftAction row) => this.unlockStateService.IsCraftActionUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsCSBonusContentTypeUnlocked(CSBonusContentType row) => this.unlockStateService.IsCSBonusContentTypeUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsEmoteUnlocked(Emote row) => this.unlockStateService.IsEmoteUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsEmjVoiceNpcUnlocked(EmjVoiceNpc row) => this.unlockStateService.IsEmjVoiceNpcUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsEmjCostumeUnlocked(EmjCostume row) => this.unlockStateService.IsEmjCostumeUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsGeneralActionUnlocked(GeneralAction row) => this.unlockStateService.IsGeneralActionUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsGlassesUnlocked(Glasses row) => this.unlockStateService.IsGlassesUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsHowToUnlocked(HowTo row) => this.unlockStateService.IsHowToUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsInstanceContentUnlocked(InstanceContentSheet row) => this.unlockStateService.IsInstanceContentUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsItemUnlockable(Item row) => this.unlockStateService.IsItemUnlockable(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsItemUnlocked(Item row) => this.unlockStateService.IsItemUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMcGuffinUnlocked(McGuffin row) => this.unlockStateService.IsMcGuffinUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMJILandmarkUnlocked(MJILandmark row) => this.unlockStateService.IsMJILandmarkUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMKDLoreUnlocked(MKDLore row) => this.unlockStateService.IsMKDLoreUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMountUnlocked(Mount row) => this.unlockStateService.IsMountUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsNotebookDivisionUnlocked(NotebookDivision row) => this.unlockStateService.IsNotebookDivisionUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsOrchestrionUnlocked(Orchestrion row) => this.unlockStateService.IsOrchestrionUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsOrnamentUnlocked(Ornament row) => this.unlockStateService.IsOrnamentUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPerformUnlocked(Perform row) => this.unlockStateService.IsPerformUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPublicContentUnlocked(PublicContentSheet row) => this.unlockStateService.IsPublicContentUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRecipeUnlocked(Recipe row) => this.unlockStateService.IsRecipeUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRowRefUnlocked(RowRef rowRef) => this.unlockStateService.IsRowRefUnlocked(rowRef);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRowRefUnlocked<T>(RowRef<T> rowRef) where T : struct, IExcelRow<T> => this.unlockStateService.IsRowRefUnlocked(rowRef);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSecretRecipeBookUnlocked(SecretRecipeBook row) => this.unlockStateService.IsSecretRecipeBookUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsTraitUnlocked(Trait row) => this.unlockStateService.IsTraitUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsTripleTriadCardUnlocked(TripleTriadCard row) => this.unlockStateService.IsTripleTriadCardUnlocked(row);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsUnlockLinkUnlocked(uint unlockLink) => this.unlockStateService.IsUnlockLinkUnlocked(unlockLink);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsUnlockLinkUnlocked(ushort unlockLink) => this.unlockStateService.IsUnlockLinkUnlocked(unlockLink);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.unlockStateService.Unlock -= this.UnlockForward;
|
||||
}
|
||||
|
||||
private void UnlockForward(RowRef rowRef) => this.Unlock?.Invoke(rowRef);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.JobGauge;
|
||||
using Dalamud.Game.ClientState.JobGauge.Types;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
|
|
@ -29,10 +29,10 @@ internal class GaugeWidget : IDataWindowWidget
|
|||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
var objectTable = Service<ObjectTable>.Get();
|
||||
var jobGauges = Service<JobGauges>.Get();
|
||||
|
||||
var player = clientState.LocalPlayer;
|
||||
var player = objectTable.LocalPlayer;
|
||||
if (player == null)
|
||||
{
|
||||
ImGui.Text("Player is not present"u8);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Dalamud.Bindings.ImGui;
|
|||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Player;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
|
|
@ -39,12 +40,13 @@ internal class ObjectTableWidget : IDataWindowWidget
|
|||
|
||||
var chatGui = Service<ChatGui>.Get();
|
||||
var clientState = Service<ClientState>.Get();
|
||||
var playerState = Service<PlayerState>.Get();
|
||||
var gameGui = Service<GameGui>.Get();
|
||||
var objectTable = Service<ObjectTable>.Get();
|
||||
|
||||
var stateString = string.Empty;
|
||||
|
||||
if (clientState.LocalPlayer == null)
|
||||
if (objectTable.LocalPlayer == null)
|
||||
{
|
||||
ImGui.Text("LocalPlayer null."u8);
|
||||
}
|
||||
|
|
@ -55,10 +57,10 @@ internal class ObjectTableWidget : IDataWindowWidget
|
|||
else
|
||||
{
|
||||
stateString += $"ObjectTableLen: {objectTable.Length}\n";
|
||||
stateString += $"LocalPlayerName: {clientState.LocalPlayer.Name}\n";
|
||||
stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.ValueNullable?.Name : clientState.LocalPlayer.CurrentWorld.RowId.ToString())}\n";
|
||||
stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.ValueNullable?.Name : clientState.LocalPlayer.HomeWorld.RowId.ToString())}\n";
|
||||
stateString += $"LocalCID: {clientState.LocalContentId:X}\n";
|
||||
stateString += $"LocalPlayerName: {playerState.CharacterName}\n";
|
||||
stateString += $"CurrentWorldName: {(this.resolveGameData ? playerState.CurrentWorld.ValueNullable?.Name : playerState.CurrentWorld.RowId.ToString())}\n";
|
||||
stateString += $"HomeWorldName: {(this.resolveGameData ? playerState.HomeWorld.ValueNullable?.Name : playerState.HomeWorld.RowId.ToString())}\n";
|
||||
stateString += $"LocalCID: {playerState.ContentId:X}\n";
|
||||
stateString += $"LastLinkedItem: {chatGui.LastLinkedItemId}\n";
|
||||
stateString += $"TerritoryType: {clientState.TerritoryType}\n\n";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using Dalamud.Plugin.Ipc.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
|
|
@ -111,12 +111,12 @@ internal class PluginIpcWidget : IDataWindowWidget
|
|||
|
||||
if (ImGui.Button("Action GO"u8))
|
||||
{
|
||||
this.ipcSubGo.InvokeAction(Service<ClientState>.Get().LocalPlayer);
|
||||
this.ipcSubGo.InvokeAction(Service<ObjectTable>.Get().LocalPlayer);
|
||||
}
|
||||
|
||||
if (ImGui.Button("Func GO"u8))
|
||||
{
|
||||
this.callGateResponse = this.ipcSubGo.InvokeFunc(Service<ClientState>.Get().LocalPlayer);
|
||||
this.callGateResponse = this.ipcSubGo.InvokeFunc(Service<ObjectTable>.Get().LocalPlayer);
|
||||
}
|
||||
|
||||
if (!this.callGateResponse.IsNullOrEmpty())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Interface.Utility;
|
||||
|
|
@ -33,7 +33,7 @@ internal class TargetWidget : IDataWindowWidget
|
|||
{
|
||||
ImGui.Checkbox("Resolve GameData"u8, ref this.resolveGameData);
|
||||
|
||||
var clientState = Service<ClientState>.Get();
|
||||
var objectTable = Service<ObjectTable>.Get();
|
||||
var targetMgr = Service<TargetManager>.Get();
|
||||
|
||||
if (targetMgr.Target != null)
|
||||
|
|
@ -80,7 +80,7 @@ internal class TargetWidget : IDataWindowWidget
|
|||
if (ImGui.Button("Clear FT"u8))
|
||||
targetMgr.FocusTarget = null;
|
||||
|
||||
var localPlayer = clientState.LocalPlayer;
|
||||
var localPlayer = objectTable.LocalPlayer;
|
||||
|
||||
if (localPlayer != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.Text.Evaluator;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Plugin.SelfTest;
|
||||
|
|
@ -51,8 +52,8 @@ internal class SeStringEvaluatorSelfTestStep : ISelfTestStep
|
|||
// that it returned the local players name by using its EntityId,
|
||||
// and that it didn't include the world name by checking the HomeWorldId against AgentLobby.Instance()->LobbyData.HomeWorldId.
|
||||
|
||||
var clientState = Service<ClientState>.Get();
|
||||
var localPlayer = clientState.LocalPlayer;
|
||||
var objectTable = Service<ObjectTable>.Get();
|
||||
var localPlayer = objectTable.LocalPlayer;
|
||||
if (localPlayer is null)
|
||||
{
|
||||
ImGui.Text("You need to be logged in for this step."u8);
|
||||
|
|
|
|||
56
Dalamud/Networking/Pipes/Api/PluginLinkHandler.cs
Normal file
56
Dalamud/Networking/Pipes/Api/PluginLinkHandler.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using System.Linq;
|
||||
|
||||
using Dalamud.Console;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Networking.Pipes.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
#pragma warning disable DAL_RPC
|
||||
|
||||
namespace Dalamud.Networking.Pipes.Api;
|
||||
|
||||
/// <inheritdoc cref="IPluginLinkHandler" />
|
||||
[PluginInterface]
|
||||
[ServiceManager.ScopedService]
|
||||
[ResolveVia<IPluginLinkHandler>]
|
||||
public class PluginLinkHandler : IInternalDisposableService, IPluginLinkHandler
|
||||
{
|
||||
private readonly LinkHandlerService linkHandler;
|
||||
private readonly LocalPlugin localPlugin;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginLinkHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="localPlugin">The plugin to bind this service to.</param>
|
||||
/// <param name="linkHandler">The central link handler.</param>
|
||||
internal PluginLinkHandler(LocalPlugin localPlugin, LinkHandlerService linkHandler)
|
||||
{
|
||||
this.linkHandler = linkHandler;
|
||||
this.localPlugin = localPlugin;
|
||||
|
||||
this.linkHandler.Register("plugin", this.HandleUri);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IPluginLinkHandler.PluginUriReceived? OnUriReceived;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DisposeService()
|
||||
{
|
||||
this.OnUriReceived = null;
|
||||
this.linkHandler.Unregister("plugin", this.HandleUri);
|
||||
}
|
||||
|
||||
private void HandleUri(DalamudUri uri)
|
||||
{
|
||||
var target = uri.Path.Split("/").FirstOrDefault();
|
||||
var thisPlugin = ConsoleManagerPluginUtil.GetSanitizedNamespaceName(this.localPlugin.InternalName);
|
||||
if (target == null || !string.Equals(target, thisPlugin, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.OnUriReceived?.Invoke(uri);
|
||||
}
|
||||
}
|
||||
102
Dalamud/Networking/Pipes/DalamudUri.cs
Normal file
102
Dalamud/Networking/Pipes/DalamudUri.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Web;
|
||||
|
||||
namespace Dalamud.Networking.Pipes;
|
||||
|
||||
/// <summary>
|
||||
/// A Dalamud Uri, in the format:
|
||||
/// <code>dalamud://{NAMESPACE}/{ARBITRARY}</code>
|
||||
/// </summary>
|
||||
public record DalamudUri
|
||||
{
|
||||
private readonly Uri rawUri;
|
||||
|
||||
private DalamudUri(Uri uri)
|
||||
{
|
||||
if (uri.Scheme != "dalamud")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(uri), "URI must be of scheme dalamud.");
|
||||
}
|
||||
|
||||
this.rawUri = uri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the namespace that this URI should be routed to. Generally a high level component like "PluginInstaller".
|
||||
/// </summary>
|
||||
public string Namespace => this.rawUri.Authority;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw (untargeted) path and query params for this URI.
|
||||
/// </summary>
|
||||
public string Data =>
|
||||
this.rawUri.GetComponents(UriComponents.PathAndQuery | UriComponents.Fragment, UriFormat.UriEscaped);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw (untargeted) path for this URI.
|
||||
/// </summary>
|
||||
public string Path => this.rawUri.AbsolutePath;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of segments based on the provided Data element.
|
||||
/// </summary>
|
||||
public string[] Segments => this.GetDataSegments();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw query parameters for this URI, if any.
|
||||
/// </summary>
|
||||
public string Query => this.rawUri.Query;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query params (as a parsed NameValueCollection) in this URI.
|
||||
/// </summary>
|
||||
public NameValueCollection QueryParams => HttpUtility.ParseQueryString(this.Query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fragment (if one is specified) in this URI.
|
||||
/// </summary>
|
||||
public string Fragment => this.rawUri.Fragment;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => this.rawUri.ToString();
|
||||
|
||||
private string[] GetDataSegments()
|
||||
{
|
||||
// reimplementation of the System.URI#Segments, under MIT license.
|
||||
var path = this.Path;
|
||||
|
||||
var segments = new List<string>();
|
||||
var current = 0;
|
||||
while (current < path.Length)
|
||||
{
|
||||
var next = path.IndexOf('/', current);
|
||||
if (next == -1)
|
||||
{
|
||||
next = path.Length - 1;
|
||||
}
|
||||
|
||||
segments.Add(path.Substring(current, (next - current) + 1));
|
||||
current = next + 1;
|
||||
}
|
||||
|
||||
return segments.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a DalamudURI from a given URI.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI to convert to a Dalamud URI.</param>
|
||||
/// <returns>Returns a DalamudUri.</returns>
|
||||
public static DalamudUri FromUri(Uri uri)
|
||||
{
|
||||
return new DalamudUri(uri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a DalamudURI from a URI in string format.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI to convert to a Dalamud URI.</param>
|
||||
/// <returns>Returns a DalamudUri.</returns>
|
||||
public static DalamudUri FromUri(string uri) => FromUri(new Uri(uri));
|
||||
}
|
||||
121
Dalamud/Networking/Pipes/Internal/ClientHelloService.cs
Normal file
121
Dalamud/Networking/Pipes/Internal/ClientHelloService.cs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Networking.Pipes.Rpc;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Networking.Pipes.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A minimal service to respond with information about this client.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class ClientHelloService : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClientHelloService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="rpcHostService">Injected host service.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public ClientHelloService(RpcHostService rpcHostService)
|
||||
{
|
||||
rpcHostService.AddMethod("hello", this.HandleHello);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a hello request.
|
||||
/// </summary>
|
||||
/// <param name="request">.</param>
|
||||
/// <returns>Respond with information.</returns>
|
||||
public async Task<ClientHelloResponse> HandleHello(ClientHelloRequest request)
|
||||
{
|
||||
var dalamud = await Service<Dalamud>.GetAsync();
|
||||
|
||||
return new ClientHelloResponse
|
||||
{
|
||||
ApiVersion = "1.0",
|
||||
DalamudVersion = Util.GetScmVersion(),
|
||||
GameVersion = dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown",
|
||||
ClientIdentifier = await this.GetClientIdentifier(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DisposeService()
|
||||
{
|
||||
}
|
||||
|
||||
private async Task<string> GetClientIdentifier()
|
||||
{
|
||||
var framework = await Service<Framework>.GetAsync();
|
||||
var clientState = await Service<ClientState>.GetAsync();
|
||||
var dataManager = await Service<DataManager>.GetAsync();
|
||||
|
||||
var clientIdentifier = $"FFXIV Process ${Environment.ProcessId}";
|
||||
|
||||
await framework.RunOnFrameworkThread(() =>
|
||||
{
|
||||
if (clientState.IsLoggedIn)
|
||||
{
|
||||
var player = clientState.LocalPlayer;
|
||||
if (player != null)
|
||||
{
|
||||
var world = dataManager.GetExcelSheet<World>().GetRow(player.HomeWorld.RowId);
|
||||
clientIdentifier = $"Logged in as {player.Name.TextValue} @ {world.Name.ExtractText()}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clientIdentifier = "On login screen";
|
||||
}
|
||||
});
|
||||
|
||||
return clientIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A request from a client to say hello.
|
||||
/// </summary>
|
||||
internal record ClientHelloRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the API version this client is expecting.
|
||||
/// </summary>
|
||||
public string ApiVersion { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user agent of the client.
|
||||
/// </summary>
|
||||
public string UserAgent { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A response from Dalamud to a hello request.
|
||||
/// </summary>
|
||||
internal record ClientHelloResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the API version this server has offered.
|
||||
/// </summary>
|
||||
public string? ApiVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Dalamud version.
|
||||
/// </summary>
|
||||
public string? DalamudVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current game version.
|
||||
/// </summary>
|
||||
public string? GameVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an identifier for this client.
|
||||
/// </summary>
|
||||
public string? ClientIdentifier { get; init; }
|
||||
}
|
||||
107
Dalamud/Networking/Pipes/Internal/LinkHandlerService.cs
Normal file
107
Dalamud/Networking/Pipes/Internal/LinkHandlerService.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Networking.Pipes.Rpc;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Networking.Pipes.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A service responsible for handling Dalamud URIs and dispatching them accordingly.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class LinkHandlerService : IInternalDisposableService
|
||||
{
|
||||
private readonly ModuleLog log = new("LinkHandler");
|
||||
|
||||
// key: namespace (e.g. "plugin" or "PluginInstaller") -> list of handlers
|
||||
private readonly ConcurrentDictionary<string, List<Action<DalamudUri>>> handlers
|
||||
= new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinkHandlerService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="rpcHostService">The injected RPC host service.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public LinkHandlerService(RpcHostService rpcHostService)
|
||||
{
|
||||
rpcHostService.AddMethod("handleLink", this.HandleLinkCall);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DisposeService()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a handler for a namespace. All URIs with this namespace will be dispatched to the handler.
|
||||
/// </summary>
|
||||
/// <param name="ns">The namespace to use for this subscription.</param>
|
||||
/// <param name="handler">The command handler.</param>
|
||||
public void Register(string ns, Action<DalamudUri> handler)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ns))
|
||||
throw new ArgumentNullException(nameof(ns));
|
||||
|
||||
var list = this.handlers.GetOrAdd(ns, _ => []);
|
||||
lock (list)
|
||||
{
|
||||
list.Add(handler);
|
||||
}
|
||||
|
||||
this.log.Verbose("Registered handler for {Namespace}", ns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister a handler.
|
||||
/// </summary>
|
||||
/// <param name="ns">The namespace to use for this subscription.</param>
|
||||
/// <param name="handler">The command handler.</param>
|
||||
public void Unregister(string ns, Action<DalamudUri> handler)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ns))
|
||||
return;
|
||||
|
||||
if (!this.handlers.TryGetValue(ns, out var list))
|
||||
return;
|
||||
|
||||
list.RemoveAll(x => x == handler);
|
||||
|
||||
if (list.Count == 0)
|
||||
this.handlers.TryRemove(ns, out _);
|
||||
|
||||
this.log.Verbose("Unregistered handler for {Namespace}", ns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatch a URI to matching handlers.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI to parse and dispatch.</param>
|
||||
public void Dispatch(DalamudUri uri)
|
||||
{
|
||||
this.log.Information("Received URI: {Uri}", uri.ToString());
|
||||
|
||||
var ns = uri.Namespace;
|
||||
if (!this.handlers.TryGetValue(ns, out var actions))
|
||||
return;
|
||||
|
||||
foreach (var h in actions)
|
||||
{
|
||||
h.InvokeSafely(uri);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The RPC-invokable link handler.
|
||||
/// </summary>
|
||||
/// <param name="uri">A plain-text URI to parse.</param>
|
||||
public void HandleLinkCall(string uri)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(uri))
|
||||
return;
|
||||
|
||||
var du = DalamudUri.FromUri(uri);
|
||||
this.Dispatch(du);
|
||||
}
|
||||
}
|
||||
167
Dalamud/Networking/Pipes/Rpc/PipeRpcHost.cs
Normal file
167
Dalamud/Networking/Pipes/Rpc/PipeRpcHost.cs
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipes;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Networking.Pipes.Rpc;
|
||||
|
||||
/// <summary>
|
||||
/// Simple multi-client JSON-RPC named pipe host using StreamJsonRpc.
|
||||
/// </summary>
|
||||
internal class PipeRpcHost : IDisposable
|
||||
{
|
||||
private readonly ModuleLog log = new("RPC/Host");
|
||||
|
||||
private readonly RpcServiceRegistry registry = new();
|
||||
private readonly CancellationTokenSource cts = new();
|
||||
private readonly ConcurrentDictionary<Guid, RpcConnection> sessions = new();
|
||||
private Task? acceptLoopTask;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PipeRpcHost"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pipeName">The pipe name to create.</param>
|
||||
public PipeRpcHost(string? pipeName = null)
|
||||
{
|
||||
// Default pipe name based on current process ID for uniqueness per Dalamud instance.
|
||||
this.PipeName = pipeName ?? $"DalamudRPC.{Environment.ProcessId}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the named pipe this RPC host is using.
|
||||
/// </summary>
|
||||
public string PipeName { get; }
|
||||
|
||||
/// <summary>Adds a local object exposing RPC methods callable by clients.</summary>
|
||||
/// <param name="service">An arbitrary service object that will be introspected to add to RPC.</param>
|
||||
public void AddService(object service) => this.registry.AddService(service);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a standalone JSON-RPC method callable by clients.
|
||||
/// </summary>
|
||||
/// <param name="name">The name to add.</param>
|
||||
/// <param name="handler">The delegate that acts as the handler.</param>
|
||||
public void AddMethod(string name, Delegate handler) => this.registry.AddMethod(name, handler);
|
||||
|
||||
/// <summary>Starts accepting client connections.</summary>
|
||||
public void Start()
|
||||
{
|
||||
if (this.acceptLoopTask != null) return;
|
||||
this.acceptLoopTask = Task.Factory.StartNew(this.AcceptLoopAsync, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
/// <summary>Invoke an RPC request on a specific client expecting a result.</summary>
|
||||
/// <param name="clientId">The client ID to invoke.</param>
|
||||
/// <param name="method">The method to invoke.</param>
|
||||
/// <param name="arguments">Any arguments to invoke.</param>
|
||||
/// <returns>An optional return based on the specified RPC.</returns>
|
||||
/// <typeparam name="T">The expected response type.</typeparam>
|
||||
public Task<T> InvokeClientAsync<T>(Guid clientId, string method, params object[] arguments)
|
||||
{
|
||||
if (!this.sessions.TryGetValue(clientId, out var session))
|
||||
throw new KeyNotFoundException($"No client {clientId}");
|
||||
|
||||
return session.Rpc.InvokeAsync<T>(method, arguments);
|
||||
}
|
||||
|
||||
/// <summary>Send a notification to all connected clients (no response expected).</summary>
|
||||
/// <param name="method">The method name to broadcast.</param>
|
||||
/// <param name="arguments">The arguments to broadcast.</param>
|
||||
/// <returns>Returns a Task when completed.</returns>
|
||||
public Task BroadcastNotifyAsync(string method, params object[] arguments)
|
||||
{
|
||||
var list = this.sessions.Values;
|
||||
var tasks = new List<Task>(list.Count);
|
||||
foreach (var s in list)
|
||||
{
|
||||
tasks.Add(s.Rpc.NotifyAsync(method, arguments));
|
||||
}
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of connected client IDs.
|
||||
/// </summary>
|
||||
/// <returns>Connected client IDs.</returns>
|
||||
public IReadOnlyCollection<Guid> GetClientIds() => this.sessions.Keys.AsReadOnlyCollection();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.cts.Cancel();
|
||||
this.acceptLoopTask?.Wait(1000);
|
||||
|
||||
foreach (var kv in this.sessions)
|
||||
{
|
||||
kv.Value.Dispose();
|
||||
}
|
||||
|
||||
this.sessions.Clear();
|
||||
this.cts.Dispose();
|
||||
this.log.Information("PipeRpcHost disposed ({Pipe})", this.PipeName);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private PipeSecurity BuildPipeSecurity()
|
||||
{
|
||||
var ps = new PipeSecurity();
|
||||
ps.AddAccessRule(new PipeAccessRule(WindowsIdentity.GetCurrent().User!, PipeAccessRights.FullControl, AccessControlType.Allow));
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
private async Task AcceptLoopAsync()
|
||||
{
|
||||
this.log.Information("PipeRpcHost starting on pipe {Pipe}", this.PipeName);
|
||||
var token = this.cts.Token;
|
||||
var security = this.BuildPipeSecurity();
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
NamedPipeServerStream? server = null;
|
||||
try
|
||||
{
|
||||
server = NamedPipeServerStreamAcl.Create(
|
||||
this.PipeName,
|
||||
PipeDirection.InOut,
|
||||
NamedPipeServerStream.MaxAllowedServerInstances,
|
||||
PipeTransmissionMode.Message,
|
||||
PipeOptions.Asynchronous,
|
||||
65536,
|
||||
65536,
|
||||
security);
|
||||
|
||||
await server.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||
|
||||
var session = new RpcConnection(server, this.registry);
|
||||
this.sessions.TryAdd(session.Id, session);
|
||||
|
||||
this.log.Debug("RPC connection created: {Id}", session.Id);
|
||||
|
||||
_ = session.Completion.ContinueWith(t =>
|
||||
{
|
||||
this.sessions.TryRemove(session.Id, out _);
|
||||
this.log.Debug("RPC connection removed: {Id}", session.Id);
|
||||
}, TaskScheduler.Default);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
server?.Dispose();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
server?.Dispose();
|
||||
this.log.Error(ex, "Error in pipe accept loop");
|
||||
await Task.Delay(500, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Dalamud/Networking/Pipes/Rpc/RpcConnection.cs
Normal file
92
Dalamud/Networking/Pipes/Rpc/RpcConnection.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Serilog;
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Dalamud.Networking.Pipes.Rpc;
|
||||
|
||||
/// <summary>
|
||||
/// A single RPC client session connected via named pipe.
|
||||
/// </summary>
|
||||
internal class RpcConnection : IDisposable
|
||||
{
|
||||
private readonly NamedPipeServerStream pipe;
|
||||
private readonly RpcServiceRegistry registry;
|
||||
private readonly CancellationTokenSource cts = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RpcConnection"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pipe">The named pipe that this connection will handle.</param>
|
||||
/// <param name="registry">A registry of RPC services.</param>
|
||||
public RpcConnection(NamedPipeServerStream pipe, RpcServiceRegistry registry)
|
||||
{
|
||||
this.Id = Guid.CreateVersion7();
|
||||
this.pipe = pipe;
|
||||
this.registry = registry;
|
||||
|
||||
var formatter = new JsonMessageFormatter();
|
||||
var handler = new HeaderDelimitedMessageHandler(pipe, pipe, formatter);
|
||||
|
||||
this.Rpc = new JsonRpc(handler);
|
||||
this.Rpc.AllowModificationWhileListening = true;
|
||||
this.Rpc.Disconnected += this.OnDisconnected;
|
||||
this.registry.Attach(this.Rpc);
|
||||
|
||||
this.Rpc.StartListening();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GUID for this connection.
|
||||
/// </summary>
|
||||
public Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the JsonRpc instance for this connection.
|
||||
/// </summary>
|
||||
public JsonRpc Rpc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a task that's called on RPC completion.
|
||||
/// </summary>
|
||||
public Task Completion => this.Rpc.Completion;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!this.cts.IsCancellationRequested)
|
||||
{
|
||||
this.cts.Cancel();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.Rpc.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Debug(ex, "Error disposing JsonRpc for client {Id}", this.Id);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.pipe.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Debug(ex, "Error disposing pipe for client {Id}", this.Id);
|
||||
}
|
||||
|
||||
this.cts.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void OnDisconnected(object? sender, JsonRpcDisconnectedEventArgs e)
|
||||
{
|
||||
Log.Debug("RPC client {Id} disconnected: {Reason}", this.Id, e.Description);
|
||||
this.registry.Detach(this.Rpc);
|
||||
this.Dispose();
|
||||
}
|
||||
}
|
||||
49
Dalamud/Networking/Pipes/Rpc/RpcHostService.cs
Normal file
49
Dalamud/Networking/Pipes/Rpc/RpcHostService.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using Dalamud.Logging.Internal;
|
||||
|
||||
namespace Dalamud.Networking.Pipes.Rpc;
|
||||
|
||||
/// <summary>
|
||||
/// The Dalamud service repsonsible for hosting the RPC.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class RpcHostService : IServiceType, IInternalDisposableService
|
||||
{
|
||||
private readonly ModuleLog log = new("RPC");
|
||||
private readonly PipeRpcHost host;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RpcHostService"/> class.
|
||||
/// </summary>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public RpcHostService()
|
||||
{
|
||||
this.host = new PipeRpcHost();
|
||||
this.host.Start();
|
||||
|
||||
this.log.Information("RpcHostService started on pipe {Pipe}", this.host.PipeName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RPC host to drill down.
|
||||
/// </summary>
|
||||
public PipeRpcHost Host => this.host;
|
||||
|
||||
/// <summary>
|
||||
/// Add a new service Object to the RPC host.
|
||||
/// </summary>
|
||||
/// <param name="service">The object to add.</param>
|
||||
public void AddService(object service) => this.host.AddService(service);
|
||||
|
||||
/// <summary>
|
||||
/// Add a new standalone method to the RPC host.
|
||||
/// </summary>
|
||||
/// <param name="name">The method name to add.</param>
|
||||
/// <param name="handler">The handler to add.</param>
|
||||
public void AddMethod(string name, Delegate handler) => this.host.AddMethod(name, handler);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DisposeService()
|
||||
{
|
||||
this.host.Dispose();
|
||||
}
|
||||
}
|
||||
85
Dalamud/Networking/Pipes/Rpc/RpcServiceRegistry.cs
Normal file
85
Dalamud/Networking/Pipes/Rpc/RpcServiceRegistry.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using StreamJsonRpc;
|
||||
|
||||
namespace Dalamud.Networking.Pipes.Rpc;
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe registry of local RPC target objects that are exposed to every connected JsonRpc session.
|
||||
/// New sessions get all previously registered targets; newly added targets are attached to all active sessions.
|
||||
/// </summary>
|
||||
internal class RpcServiceRegistry
|
||||
{
|
||||
private readonly Lock sync = new();
|
||||
private readonly List<object> targets = [];
|
||||
private readonly List<(string Name, Delegate Handler)> methods = [];
|
||||
private readonly List<JsonRpc> activeRpcs = [];
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new local RPC target object. Its public JSON-RPC methods become callable by clients.
|
||||
/// Adds <paramref name="service"/> to the registry and attaches it to all active RPC sessions.
|
||||
/// </summary>
|
||||
/// <param name="service">The service instance containing JSON-RPC callable methods to expose.</param>
|
||||
public void AddService(object service)
|
||||
{
|
||||
lock (this.sync)
|
||||
{
|
||||
this.targets.Add(service);
|
||||
foreach (var rpc in this.activeRpcs)
|
||||
{
|
||||
rpc.AddLocalRpcTarget(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new standalone JSON-RPC method.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the method to add.</param>
|
||||
/// <param name="handler">The handler to add.</param>
|
||||
public void AddMethod(string name, Delegate handler)
|
||||
{
|
||||
lock (this.sync)
|
||||
{
|
||||
this.methods.Add((name, handler));
|
||||
foreach (var rpc in this.activeRpcs)
|
||||
{
|
||||
rpc.AddLocalRpcMethod(name, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a JsonRpc instance <paramref name="rpc"/> to the registry so it receives all existing service targets.
|
||||
/// </summary>
|
||||
/// <param name="rpc">The JsonRpc instance to attach and populate with current targets.</param>
|
||||
internal void Attach(JsonRpc rpc)
|
||||
{
|
||||
lock (this.sync)
|
||||
{
|
||||
this.activeRpcs.Add(rpc);
|
||||
foreach (var t in this.targets)
|
||||
{
|
||||
rpc.AddLocalRpcTarget(t);
|
||||
}
|
||||
|
||||
foreach (var m in this.methods)
|
||||
{
|
||||
rpc.AddLocalRpcMethod(m.Name, m.Handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detaches a JsonRpc instance <paramref name="rpc"/> from the registry (e.g. when a client disconnects).
|
||||
/// </summary>
|
||||
/// <param name="rpc">The JsonRpc instance being detached.</param>
|
||||
internal void Detach(JsonRpc rpc)
|
||||
{
|
||||
lock (this.sync)
|
||||
{
|
||||
this.activeRpcs.Remove(rpc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -109,11 +109,13 @@ public interface IClientState
|
|||
/// <summary>
|
||||
/// Gets the local player character, if one is present.
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(IPlayerState)} or {nameof(IObjectTable)}.{nameof(IObjectTable.LocalPlayer)} if necessary.")]
|
||||
public IPlayerCharacter? LocalPlayer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content ID of the local character.
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(IPlayerState)}.{nameof(IPlayerState.ContentId)}")]
|
||||
public ulong LocalContentId { get; }
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
|
@ -19,6 +20,11 @@ public interface IObjectTable : IEnumerable<IGameObject>
|
|||
/// </summary>
|
||||
public int Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local player character, if one is present.
|
||||
/// </summary>
|
||||
public IPlayerCharacter? LocalPlayer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator for accessing player objects. This will only contain BattleChara objects.
|
||||
/// Does not contain any mounts, minions, or accessories.
|
||||
|
|
|
|||
243
Dalamud/Plugin/Services/IPlayerState.cs
Normal file
243
Dalamud/Plugin/Services/IPlayerState.cs
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.Player;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
#pragma warning disable SA1400 // Access modifier should be declared: Interface members are public by default
|
||||
|
||||
/// <summary>
|
||||
/// Interface for determining the players state.
|
||||
/// </summary>
|
||||
public interface IPlayerState
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the local players data is loaded.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// PlayerState is separate from <see cref="IObjectTable.LocalPlayer"/>,
|
||||
/// and as such the game object might not exist when it's loaded.
|
||||
/// </remarks>
|
||||
bool IsLoaded { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the local character.
|
||||
/// </summary>
|
||||
string CharacterName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity ID of the local character.
|
||||
/// </summary>
|
||||
uint EntityId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content ID of the local character.
|
||||
/// </summary>
|
||||
ulong ContentId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the World row for the local character's current world.
|
||||
/// </summary>
|
||||
RowRef<World> CurrentWorld { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the World row for the local character's home world.
|
||||
/// </summary>
|
||||
RowRef<World> HomeWorld { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sex of the local character.
|
||||
/// </summary>
|
||||
Sex Sex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Race row for the local character.
|
||||
/// </summary>
|
||||
RowRef<Race> Race { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Tribe row for the local character.
|
||||
/// </summary>
|
||||
RowRef<Tribe> Tribe { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClassJob row for the local character's current class/job.
|
||||
/// </summary>
|
||||
RowRef<ClassJob> ClassJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current class/job's level of the local character.
|
||||
/// </summary>
|
||||
short Level { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the local character's level is synced.
|
||||
/// </summary>
|
||||
bool IsLevelSynced { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the effective level of the local character.
|
||||
/// </summary>
|
||||
short EffectiveLevel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GuardianDeity row for the local character.
|
||||
/// </summary>
|
||||
RowRef<GuardianDeity> GuardianDeity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the birth month of the local character.
|
||||
/// </summary>
|
||||
byte BirthMonth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the birth day of the local character.
|
||||
/// </summary>
|
||||
byte BirthDay { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClassJob row for the local character's starting class.
|
||||
/// </summary>
|
||||
RowRef<ClassJob> FirstClass { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Town row for the local character's starting town.
|
||||
/// </summary>
|
||||
RowRef<Town> StartTown { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base strength of the local character.
|
||||
/// </summary>
|
||||
int BaseStrength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base dexterity of the local character.
|
||||
/// </summary>
|
||||
int BaseDexterity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base vitality of the local character.
|
||||
/// </summary>
|
||||
int BaseVitality { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base intelligence of the local character.
|
||||
/// </summary>
|
||||
int BaseIntelligence { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base mind of the local character.
|
||||
/// </summary>
|
||||
int BaseMind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the piety mind of the local character.
|
||||
/// </summary>
|
||||
int BasePiety { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GrandCompany row for the local character's current Grand Company affiliation.
|
||||
/// </summary>
|
||||
RowRef<GrandCompany> GrandCompany { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Aetheryte row for the local player's home aetheryte.
|
||||
/// </summary>
|
||||
RowRef<Aetheryte> HomeAetheryte { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of Aetheryte rows for the local player's favourite aetherytes.
|
||||
/// </summary>
|
||||
IReadOnlyList<RowRef<Aetheryte>> FavoriteAetherytes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Aetheryte row for the local player's free aetheryte.
|
||||
/// </summary>
|
||||
RowRef<Aetheryte> FreeAetheryte { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of received player commendations of the local player.
|
||||
/// </summary>
|
||||
uint BaseRestedExperience { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of received player commendations of the local player.
|
||||
/// </summary>
|
||||
short PlayerCommendations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Carrier Level of Delivery Moogle Quests of the local player.
|
||||
/// </summary>
|
||||
byte DeliveryLevel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mentor version of the local player.
|
||||
/// </summary>
|
||||
MentorVersion MentorVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the local player is any kind of Mentor (Battle or Trade Mentor).
|
||||
/// </summary>
|
||||
bool IsMentor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the local player is a Battle Mentor.
|
||||
/// </summary>
|
||||
bool IsBattleMentor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the local player is a Trade Mentor.
|
||||
/// </summary>
|
||||
bool IsTradeMentor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the local player is a novice (aka. Sprout or New Adventurer).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can be <see langword="false"/> if <c>/nastatus</c> was used to deactivate it.
|
||||
/// </remarks>
|
||||
bool IsNovice { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the local player is a returner.
|
||||
/// </summary>
|
||||
bool IsReturner { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of an attribute of the local character.
|
||||
/// </summary>
|
||||
/// <param name="attribute">The attribute to check.</param>
|
||||
/// <returns>The value of the specific attribute.</returns>
|
||||
int GetAttribute(PlayerAttribute attribute);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Grand Company rank of the local character.
|
||||
/// </summary>
|
||||
/// <param name="grandCompany">The Grand Company to check.</param>
|
||||
/// <returns>The Grand Company rank of the local character.</returns>
|
||||
byte GetGrandCompanyRank(GrandCompany grandCompany);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of the local character's class/job.
|
||||
/// </summary>
|
||||
/// <param name="classJob">The ClassJob row to check.</param>
|
||||
/// <returns>The level of the requested class/job.</returns>
|
||||
short GetClassJobLevel(ClassJob classJob);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the experience of the local character's class/job.
|
||||
/// </summary>
|
||||
/// <param name="classJob">The ClassJob row to check.</param>
|
||||
/// <returns>The experience of the requested class/job.</returns>
|
||||
int GetClassJobExperience(ClassJob classJob);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the desynthesis level of the local character's crafter job.
|
||||
/// </summary>
|
||||
/// <param name="classJob">The ClassJob row to check.</param>
|
||||
/// <returns>The desynthesis level of the requested crafter job.</returns>
|
||||
float GetDesynthesisLevel(ClassJob classJob);
|
||||
}
|
||||
23
Dalamud/Plugin/Services/IPluginLinkHandler.cs
Normal file
23
Dalamud/Plugin/Services/IPluginLinkHandler.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Dalamud.Networking.Pipes;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// A service to allow plugins to subscribe to dalamud:// URIs targeting them. Plugins will receive any URI sent to the
|
||||
/// <code>dalamud://plugin/{PLUGIN_INTERNAL_NAME}/...</code> namespace.
|
||||
/// </summary>
|
||||
[Experimental("DAL_RPC", Message = "This service will be finalized around 7.41 and may change before then.")]
|
||||
public interface IPluginLinkHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate containing the received URI.
|
||||
/// </summary>
|
||||
delegate void PluginUriReceived(DalamudUri uri);
|
||||
|
||||
/// <summary>
|
||||
/// The event fired when a URI targeting this plugin is received.
|
||||
/// </summary>
|
||||
event PluginUriReceived OnUriReceived;
|
||||
}
|
||||
328
Dalamud/Plugin/Services/IUnlockState.cs
Normal file
328
Dalamud/Plugin/Services/IUnlockState.cs
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
#pragma warning disable SA1400 // Access modifier should be declared: Interface members are public by default
|
||||
|
||||
/// <summary>
|
||||
/// Interface for determining unlock state of various content in the game.
|
||||
/// </summary>
|
||||
[Experimental("UnlockState")]
|
||||
public interface IUnlockState
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate type used for the <see cref="Unlock"/> event.
|
||||
/// </summary>
|
||||
/// <param name="rowRef">A RowRef of the unlocked thing.</param>
|
||||
delegate void UnlockDelegate(RowRef rowRef);
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when something was unlocked.
|
||||
/// </summary>
|
||||
event UnlockDelegate? Unlock;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Action is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Action row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsActionUnlocked(Lumina.Excel.Sheets.Action row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified AetherCurrentCompFlgSet is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The AetherCurrentCompFlgSet row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsAetherCurrentCompFlgSetUnlocked(AetherCurrentCompFlgSet row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified AetherCurrent is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The AetherCurrent row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsAetherCurrentUnlocked(AetherCurrent row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified AozAction (Blue Mage Action) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The AozAction row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsAozActionUnlocked(AozAction row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified BannerBg (Portrait Backgrounds) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The BannerBg row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsBannerBgUnlocked(BannerBg row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified BannerCondition is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The BannerCondition row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsBannerConditionUnlocked(BannerCondition row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified BannerDecoration (Portrait Accents) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The BannerDecoration row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsBannerDecorationUnlocked(BannerDecoration row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified BannerFacial (Portrait Expressions) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The BannerFacial row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsBannerFacialUnlocked(BannerFacial row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified BannerFrame (Portrait Frames) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The BannerFrame row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsBannerFrameUnlocked(BannerFrame row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified BannerTimeline (Portrait Poses) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The BannerTimeline row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsBannerTimelineUnlocked(BannerTimeline row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified BuddyAction (Action of the players Chocobo Companion) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The BuddyAction row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsBuddyActionUnlocked(BuddyAction row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified BuddyEquip (Equipment of the players Chocobo Companion) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The BuddyEquip row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsBuddyEquipUnlocked(BuddyEquip row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified CharaMakeCustomize (Hairstyles and Face Paint patterns) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The CharaMakeCustomize row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsCharaMakeCustomizeUnlocked(CharaMakeCustomize row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified ChocoboTaxiStand (Chocobokeeps of the Chocobo Porter service) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The ChocoboTaxiStand row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsChocoboTaxiStandUnlocked(ChocoboTaxiStand row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Companion (Minions) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Companion row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsCompanionUnlocked(Companion row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified CraftAction is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The CraftAction row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsCraftActionUnlocked(CraftAction row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified CSBonusContentType is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The CSBonusContentType row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsCSBonusContentTypeUnlocked(CSBonusContentType row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Emote is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Emote row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsEmoteUnlocked(Emote row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified EmjVoiceNpc (Doman Mahjong Characters) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The EmjVoiceNpc row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsEmjVoiceNpcUnlocked(EmjVoiceNpc row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified EmjCostume (Doman Mahjong Character Costume) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The EmjCostume row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsEmjCostumeUnlocked(EmjCostume row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified GeneralAction is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The GeneralAction row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsGeneralActionUnlocked(GeneralAction row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Glasses is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Glasses row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsGlassesUnlocked(Glasses row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified HowTo is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The HowTo row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsHowToUnlocked(HowTo row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified InstanceContent is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The InstanceContent row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsInstanceContentUnlocked(InstanceContent row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Item is considered unlockable.
|
||||
/// </summary>
|
||||
/// <param name="row">The Item row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlockable; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsItemUnlockable(Item row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Item is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Item row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsItemUnlocked(Item row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified McGuffin is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The McGuffin row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsMcGuffinUnlocked(McGuffin row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified MJILandmark (Island Sanctuary landmark) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The MJILandmark row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsMJILandmarkUnlocked(MJILandmark row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified MKDLore (Occult Record) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The MKDLore row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsMKDLoreUnlocked(MKDLore row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Mount is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Mount row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsMountUnlocked(Mount row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified NotebookDivision (Categories in Crafting/Gathering Log) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The NotebookDivision row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsNotebookDivisionUnlocked(NotebookDivision row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Orchestrion roll is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Orchestrion row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsOrchestrionUnlocked(Orchestrion row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Ornament (Fashion Accessories) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Ornament row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsOrnamentUnlocked(Ornament row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Perform (Performance Instruments) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Perform row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsPerformUnlocked(Perform row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified PublicContent is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The PublicContent row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsPublicContentUnlocked(PublicContent row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Recipe is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Recipe row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsRecipeUnlocked(Recipe row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the underlying RowRef type is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="rowRef">The RowRef to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsRowRefUnlocked(RowRef rowRef);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the underlying RowRef type is unlocked.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the Excel row.</typeparam>
|
||||
/// <param name="rowRef">The RowRef to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsRowRefUnlocked<T>(RowRef<T> rowRef) where T : struct, IExcelRow<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified SecretRecipeBook (Master Recipe Books) is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The SecretRecipeBook row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsSecretRecipeBookUnlocked(SecretRecipeBook row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Trait is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The Trait row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsTraitUnlocked(Trait row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified TripleTriadCard is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="row">The TripleTriadCard row to check.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsTripleTriadCardUnlocked(TripleTriadCard row);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified unlock link is unlocked or quest is completed.
|
||||
/// </summary>
|
||||
/// <param name="unlockLink">The unlock link id or quest id (quest ids in this case are over 65536).</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsUnlockLinkUnlocked(uint unlockLink);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified unlock link is unlocked.
|
||||
/// </summary>
|
||||
/// <param name="unlockLink">The unlock link id.</param>
|
||||
/// <returns><see langword="true"/> if unlocked; otherwise, <see langword="false"/>.</returns>
|
||||
bool IsUnlockLinkUnlocked(ushort unlockLink);
|
||||
}
|
||||
|
|
@ -22,9 +22,13 @@ using Dalamud.Interface.Internal;
|
|||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Support;
|
||||
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using Windows.Win32.System.Memory;
|
||||
using Windows.Win32.System.Ole;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup Label="Target">
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64</Platforms>
|
||||
<LangVersion>13.0</LangVersion>
|
||||
|
|
|
|||
|
|
@ -1,65 +1,68 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Analyzers -->
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4"/>
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556"/>
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.2"/>
|
||||
<ItemGroup>
|
||||
<!-- Analyzers -->
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.2" />
|
||||
|
||||
<!-- Misc Libraries -->
|
||||
<PackageVersion Include="BitFaster.Caching" Version="2.4.1"/>
|
||||
<PackageVersion Include="CheapLoc" Version="1.1.8"/>
|
||||
<PackageVersion Include="MinSharp" Version="1.0.4"/>
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||
<PackageVersion Include="Lumina" Version="6.5.1"/>
|
||||
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.7"/>
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0"/>
|
||||
<PackageVersion Include="System.Drawing.Common" Version="8.0.0"/>
|
||||
<PackageVersion Include="System.Reactive" Version="5.0.0"/>
|
||||
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="8.0.0"/>
|
||||
<PackageVersion Include="System.Resources.Extensions" Version="8.0.0"/>
|
||||
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="1.2.39"/>
|
||||
<PackageVersion Include="sqlite-net-pcl" Version="1.8.116"/>
|
||||
<!-- Misc Libraries -->
|
||||
<PackageVersion Include="BitFaster.Caching" Version="2.4.1" />
|
||||
<PackageVersion Include="CheapLoc" Version="1.1.8" />
|
||||
<PackageVersion Include="MinSharp" Version="1.0.4" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="Lumina" Version="6.5.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Reactive" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Resources.Extensions" Version="10.0.0" />
|
||||
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="1.2.39" />
|
||||
<PackageVersion Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
|
||||
<!-- DirectX / Win32 -->
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.2"/>
|
||||
<PackageVersion Include="SharpDX.Direct3D11" Version="4.2.0"/>
|
||||
<PackageVersion Include="SharpDX.Mathematics" Version="4.2.0"/>
|
||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183"/>
|
||||
<!-- DirectX / Win32 -->
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.2" />
|
||||
<PackageVersion Include="SharpDX.Direct3D11" Version="4.2.0" />
|
||||
<PackageVersion Include="SharpDX.Mathematics" Version="4.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
|
||||
|
||||
<!-- Logging -->
|
||||
<PackageVersion Include="Serilog" Version="4.0.2"/>
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="2.0.0"/>
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0"/>
|
||||
<!-- Logging -->
|
||||
<PackageVersion Include="Serilog" Version="4.0.2" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="2.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
|
||||
<!-- Injector Utilities -->
|
||||
<PackageVersion Include="Iced" Version="1.17.0"/>
|
||||
<PackageVersion Include="PeNet" Version="2.6.4"/>
|
||||
<!-- Injector Utilities -->
|
||||
<PackageVersion Include="Iced" Version="1.17.0" />
|
||||
<PackageVersion Include="PeNet" Version="2.6.4" />
|
||||
|
||||
<!-- HexaGen -->
|
||||
<PackageVersion Include="HexaGen.Runtime" Version="1.1.20"/>
|
||||
<!-- HexaGen -->
|
||||
<PackageVersion Include="HexaGen.Runtime" Version="1.1.20" />
|
||||
|
||||
<!-- Reloaded -->
|
||||
<PackageVersion Include="goatcorp.Reloaded.Hooks" Version="4.2.0-goatcorp7"/>
|
||||
<PackageVersion Include="goatcorp.Reloaded.Assembler" Version="1.0.14-goatcorp5"/>
|
||||
<PackageVersion Include="Reloaded.Memory" Version="7.0.0"/>
|
||||
<PackageVersion Include="Reloaded.Memory.Buffers" Version="2.0.0"/>
|
||||
<!-- Reloaded -->
|
||||
<PackageVersion Include="goatcorp.Reloaded.Hooks" Version="4.2.0-goatcorp7" />
|
||||
<PackageVersion Include="goatcorp.Reloaded.Assembler" Version="1.0.14-goatcorp5" />
|
||||
<PackageVersion Include="Reloaded.Memory" Version="7.0.0" />
|
||||
<PackageVersion Include="Reloaded.Memory.Buffers" Version="2.0.0" />
|
||||
|
||||
<!-- Unit Testing -->
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="16.10.0"/>
|
||||
<PackageVersion Include="xunit" Version="2.4.1"/>
|
||||
<PackageVersion Include="xunit.abstractions" Version="2.0.3"/>
|
||||
<PackageVersion Include="xunit.analyzers" Version="0.10.0"/>
|
||||
<PackageVersion Include="xunit.assert" Version="2.4.1"/>
|
||||
<PackageVersion Include="xunit.core" Version="2.4.1"/>
|
||||
<PackageVersion Include="xunit.extensibility.core" Version="2.4.1"/>
|
||||
<PackageVersion Include="xunit.extensibility.execution" Version="2.4.1"/>
|
||||
<PackageVersion Include="xunit.runner.console" Version="2.4.1"/>
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.3"/>
|
||||
</ItemGroup>
|
||||
<!-- Named Pipes / RPC -->
|
||||
<PackageVersion Include="StreamJsonRpc" Version="2.22.23" />
|
||||
|
||||
<!-- Unit Testing -->
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageVersion Include="xunit" Version="2.4.1" />
|
||||
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
|
||||
<PackageVersion Include="xunit.analyzers" Version="0.10.0" />
|
||||
<PackageVersion Include="xunit.assert" Version="2.4.1" />
|
||||
<PackageVersion Include="xunit.core" Version="2.4.1" />
|
||||
<PackageVersion Include="xunit.extensibility.core" Version="2.4.1" />
|
||||
<PackageVersion Include="xunit.extensibility.execution" Version="2.4.1" />
|
||||
<PackageVersion Include="xunit.runner.console" Version="2.4.1" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -42,10 +42,7 @@ public class DalamudBuild : NukeBuild
|
|||
|
||||
AbsolutePath InjectorProjectDir => RootDirectory / "Dalamud.Injector";
|
||||
AbsolutePath InjectorProjectFile => InjectorProjectDir / "Dalamud.Injector.csproj";
|
||||
|
||||
AbsolutePath InjectorBootProjectDir => RootDirectory / "Dalamud.Injector.Boot";
|
||||
AbsolutePath InjectorBootProjectFile => InjectorBootProjectDir / "Dalamud.Injector.Boot.vcxproj";
|
||||
|
||||
|
||||
AbsolutePath TestProjectDir => RootDirectory / "Dalamud.Test";
|
||||
AbsolutePath TestProjectFile => TestProjectDir / "Dalamud.Test.csproj";
|
||||
|
||||
|
|
@ -172,14 +169,6 @@ public class DalamudBuild : NukeBuild
|
|||
.EnableNoRestore());
|
||||
});
|
||||
|
||||
Target CompileInjectorBoot => _ => _
|
||||
.Executes(() =>
|
||||
{
|
||||
MSBuildTasks.MSBuild(s => s
|
||||
.SetTargetPath(InjectorBootProjectFile)
|
||||
.SetConfiguration(Configuration));
|
||||
});
|
||||
|
||||
Target SetCILogging => _ => _
|
||||
.DependentFor(Compile)
|
||||
.OnlyWhenStatic(() => IsCIBuild)
|
||||
|
|
@ -196,7 +185,6 @@ public class DalamudBuild : NukeBuild
|
|||
.DependsOn(CompileDalamudBoot)
|
||||
.DependsOn(CompileDalamudCrashHandler)
|
||||
.DependsOn(CompileInjector)
|
||||
.DependsOn(CompileInjectorBoot)
|
||||
;
|
||||
|
||||
Target CI => _ => _
|
||||
|
|
@ -250,11 +238,6 @@ public class DalamudBuild : NukeBuild
|
|||
.SetProject(InjectorProjectFile)
|
||||
.SetConfiguration(Configuration));
|
||||
|
||||
MSBuildTasks.MSBuild(s => s
|
||||
.SetProjectFile(InjectorBootProjectFile)
|
||||
.SetConfiguration(Configuration)
|
||||
.SetTargets("Clean"));
|
||||
|
||||
FileSystemTasks.DeleteDirectory(ArtifactsDirectory);
|
||||
Directory.CreateDirectory(ArtifactsDirectory);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nuke.Common" Version="6.2.1" />
|
||||
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="9.0.0" />
|
||||
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="10.0.0" />
|
||||
<PackageReference Remove="Microsoft.CodeAnalysis.BannedApiAnalyzers" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "9.0.0",
|
||||
"version": "10.0.0",
|
||||
"rollForward": "latestMinor",
|
||||
"allowPrerelease": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
<PackageReference Include="Veldrid" Version="4.9.0" />
|
||||
<PackageReference Include="Veldrid.SDL2" Version="4.9.0" />
|
||||
<PackageReference Include="Veldrid.StartupUtilities" Version="4.9.0" />
|
||||
<PackageReference Remove="Microsoft.CodeAnalysis.BannedApiAnalyzers"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit f6c479b3fa0b452b44403c8ea53d592bec415e1e
|
||||
Subproject commit 0afa6b67288e5e667da74c1d3ad582e6c964644c
|
||||
Loading…
Add table
Add a link
Reference in a new issue